Formal mathematics laboratory
Lumenary.FollowingInstructions
Following and giving precise instructions
Formal library
PosstructureDefinitionNo axioms
PosLean 4Formally verified /-- A square on a grid map: `x` squares to the right and `y` squares up from the start. -/ structure Pos where x : Int y : Int deriving DecidableEq, ReprArrowstructureDefinitionNo axioms
ArrowLean 4Formally verified /-- The four arrow cards. -/ inductive Arrow where | up | down | left | right deriving DecidableEq, ReprstepdefDefinitionNo axioms
stepLean 4Formally verified /-- What one arrow card does: move one square in its direction. -/ def step : Arrow → Pos → Pos | .up, p => ⟨p.x, p.y + 1⟩ | .down, p => ⟨p.x, p.y - 1⟩ | .left, p => ⟨p.x - 1, p.y⟩ | .right, p => ⟨p.x + 1, p.y⟩rundefDefinitionNo axioms
runLean 4Formally verified /-- Follow a program of arrow cards from the square `p`: do the first card, then follow the rest of the program from where you are. -/ def run : List Arrow → Pos → Pos | [], p => p | a :: prog, p => run prog (step a p)startdefDefinitionNo axioms
startLean 4Formally verified /-- The start square. -/ def start : Pos := ⟨0, 0⟩run_appendtheoremProved here from basic factsNo axioms
run_appendLean 4Proved here from basic factsFormally verified /-- Following program `p` and then program `q` is the same as following the joined program `p ++ q`. Proof by induction on `p`. -/ theorem run_append (p q : List Arrow) (s : Pos) : run (p ++ q) s = run q (run p s) := by induction p generalizing s with | nil => rfl | cons a p ih => exact ih (step a s)to_the_treetheoremProved here from basic factsNo axioms
to_the_treeLean 4Proved here from basic factsFormally verified /-- The program `→ → ↑` takes you from the start to the square 2 across and 1 up. -/ theorem to_the_tree : run [.right, .right, .up] start = ⟨2, 1⟩ := rfltwo_programstheoremProved here from basic factsNo axioms
two_programsLean 4Proved here from basic factsFormally verified /-- There is more than one correct program: `↑ → →` and `→ ↑ →` are different programs that end on the same square as `→ → ↑`. -/ theorem two_programs : run [.up, .right, .right] start = ⟨2, 1⟩ ∧ run [.right, .up, .right] start = ⟨2, 1⟩ ∧ [Arrow.up, .right, .right] ≠ [.right, .up, .right] := by decidetaxidefDefinitionNo axioms
taxiLean 4Formally verified /-- The "across plus up" distance between two squares: how many squares across, plus how many squares up or down. (Grown-ups call it the taxicab distance.) -/ def taxi (p q : Pos) : Nat := (q.x - p.x).natAbs + (q.y - p.y).natAbstaxi_steptheoremProved here from basic factspropext, Quot.sound
taxi_stepLean 4Proved here from basic factsFormally verified /-- One arrow card changes the "across plus up" distance from a fixed square by at most 1. -/ theorem taxi_step (s p : Pos) (a : Arrow) : taxi s (step a p) ≤ taxi s p + 1 := by cases a <;> simp only [taxi, step] <;> omegaAxioms used: propext, Quot.soundformal/Lumenary/FollowingInstructions.leanTry it in the Lean web editortaxi_le_lengththeoremProved here from basic factspropext, Quot.sound
taxi_le_lengthLean 4Proved here from basic factsFormally verified /-- **No program is shorter than the distance.** Every arrow program that goes from `s` to a square uses at least as many cards as the "across plus up" distance to that square: each card moves you at most one square closer. Proof by induction on the program. -/ theorem taxi_le_length (prog : List Arrow) (s : Pos) : taxi s (run prog s) ≤ prog.length := by -- Strengthen: start the program at any square `p`, measuring from `s`. suffices h : ∀ p : Pos, taxi s (run prog p) ≤ taxi s p + prog.length by have := h s simpa [taxi] using this induction prog with | nil => intro p; simp [run] | cons a prog ih => intro p have h1 := ih (step a p) have h2 := taxi_step s p a simp only [run, List.length_cons] omegaAxioms used: propext, Quot.soundformal/Lumenary/FollowingInstructions.leanTry it in the Lean web editorstraight_routetheoremProved here from basic factspropext, Quot.sound
straight_routeLean 4Proved here from basic factsFormally verified /-- **A shortest program always exists**: to reach the square `a` across and `b` up, use `a` right arrows and then `b` up arrows — exactly `a + b` cards, the distance. -/ theorem straight_route (a b : Nat) : run (List.replicate a .right ++ List.replicate b .up) start = ⟨a, b⟩ := by rw [run_append] -- Each block of identical arrows moves you that many squares. have hr : ∀ (n : Nat) (p : Pos), run (List.replicate n .right) p = ⟨p.x + n, p.y⟩ := by intro n induction n with | zero => intro p; simp [run] | succ n ih => intro p simp only [List.replicate, run, step, ih] congr 1 omega have hu : ∀ (n : Nat) (p : Pos), run (List.replicate n .up) p = ⟨p.x, p.y + n⟩ := by intro n induction n with | zero => intro p; simp [run] | succ n ih => intro p simp only [List.replicate, run, step, ih] congr 1 omega rw [hr, hu] simp [start]Axioms used: propext, Quot.soundformal/Lumenary/FollowingInstructions.leanTry it in the Lean web editorHeadingstructureDefinitionNo axioms
HeadingLean 4Formally verified /-- Which way the robot is facing. -/ inductive Heading where | north | east | south | west deriving DecidableEq, ReprRobotstructureDefinitionNo axioms
RobotLean 4Formally verified /-- A floor robot: where it is, and which way it faces. -/ structure Robot where pos : Pos facing : Heading deriving DecidableEq, ReprCmdstructureDefinitionNo axioms
CmdLean 4Formally verified /-- The robot's three buttons. Turning happens on the spot: the robot stays on its square. -/ inductive Cmd where | forward | turnLeft | turnRight deriving DecidableEq, ReprexecdefDefinitionNo axioms
execLean 4Formally verified /-- What one button does to the robot. -/ def exec : Cmd → Robot → Robot | .forward, ⟨p, .north⟩ => ⟨⟨p.x, p.y + 1⟩, .north⟩ | .forward, ⟨p, .east⟩ => ⟨⟨p.x + 1, p.y⟩, .east⟩ | .forward, ⟨p, .south⟩ => ⟨⟨p.x, p.y - 1⟩, .south⟩ | .forward, ⟨p, .west⟩ => ⟨⟨p.x - 1, p.y⟩, .west⟩ | .turnLeft, ⟨p, .north⟩ => ⟨p, .west⟩ | .turnLeft, ⟨p, .west⟩ => ⟨p, .south⟩ | .turnLeft, ⟨p, .south⟩ => ⟨p, .east⟩ | .turnLeft, ⟨p, .east⟩ => ⟨p, .north⟩ | .turnRight, ⟨p, .north⟩ => ⟨p, .east⟩ | .turnRight, ⟨p, .east⟩ => ⟨p, .south⟩ | .turnRight, ⟨p, .south⟩ => ⟨p, .west⟩ | .turnRight, ⟨p, .west⟩ => ⟨p, .north⟩runRobotdefDefinitionNo axioms
runRobotLean 4Formally verified /-- Press the buttons of a robot program one after another. -/ def runRobot : List Cmd → Robot → Robot | [], r => r | c :: prog, r => runRobot prog (exec c r)homedefDefinitionNo axioms
homeLean 4Formally verified /-- The robot starts at the start square, facing north (up the page). -/ def home : Robot := ⟨start, .north⟩order_matterstheoremProved here from basic factsNo axioms
order_mattersLean 4Proved here from basic factsFormally verified /-- **The order of the steps matters.** "Forward, then turn right" leaves the robot one square up, facing east; "turn right, then forward" leaves it one square to the right. -/ theorem order_matters : runRobot [.forward, .turnRight] home = ⟨⟨0, 1⟩, .east⟩ ∧ runRobot [.turnRight, .forward] home = ⟨⟨1, 0⟩, .east⟩ := by decideleft_then_righttheoremProved here from basic factsNo axioms
left_then_rightLean 4Proved here from basic factsFormally verified /-- "Turn left, then turn right" does nothing at all, wherever the robot is and whichever way it faces. (Check all four headings.) -/ theorem left_then_right (r : Robot) : exec .turnRight (exec .turnLeft r) = r := by obtain ⟨p, h⟩ := r cases h <;> rflfour_leftstheoremProved here from basic factsNo axioms
four_leftsLean 4Proved here from basic factsFormally verified /-- Four left turns bring the robot back to facing the way it started. -/ theorem four_lefts (r : Robot) : runRobot [.turnLeft, .turnLeft, .turnLeft, .turnLeft] r = r := by obtain ⟨p, h⟩ := r cases h <;> rfldebugtheoremProved here from basic factsNo axioms
debugLean 4Proved here from basic factsFormally verified /-- **Finding and fixing a bug.** The target is 2 squares up and 1 square to the right. The program "forward, forward, turn left, forward" goes the wrong way (left instead of right); changing the one wrong instruction to "turn right" fixes it. -/ theorem debug : (runRobot [.forward, .forward, .turnLeft, .forward] home).pos = ⟨-1, 2⟩ ∧ (runRobot [.forward, .forward, .turnRight, .forward] home).pos = ⟨1, 2⟩ := by decide