Zulip Chat Archive

Stream: new members

Topic: Proving the jealous mathematician riddle


Willem vanhulle (Jun 19 2025 at 14:59):

I am working on solving the jealous mathematician riddle. I have managed to state the problem, the required properties of the solution and a solution.

I can prove that individual steps of the solution to the problem are mostly correct on first sight.

When I try to prove that each step in the solution does not break the contract, I run into a problem. I don't know how to proof that all elements of a generated state vector satisfy some property.

The source code is here:
https://github.com/wvhulle/riddle-proofs/blob/main/RiddleProofs/JealousMath.lean

I am stuck with the proof

theorem all_states_safe :  i : Fin 11, safe (solution_states.get i) := by
  sorry

I have tried rcases and fin_cases (from Mathlib) but it does not work out. I feel like I am doing something wrong?

Aaron Liu (Jun 19 2025 at 15:02):

Did you intro i first?

Willem vanhulle (Jun 19 2025 at 15:02):

Aaron Liu zei:

Did you intro i first?

Yes, I have done that

Aaron Liu (Jun 19 2025 at 15:05):

What exactly is being a problem?

Willem vanhulle (Jun 19 2025 at 15:06):

I have tried many different styles of proofs. I would like to prove that the solution state at time i is always correct. (No notebooks left-behind for other mathematicians). I am struggling with the inductive step.

Willem vanhulle (Jun 19 2025 at 15:07):

I have a feeling the recursive statement of the solution might be causing problems

Willem vanhulle (Jun 19 2025 at 15:09):

I can prove that the first state in the problem and the last step are safe, but not the 9 intermediate states.

Aaron Liu (Jun 19 2025 at 15:11):

Sorry, I still don't quite understand. What about the nine intermediate states makes it hard to show they are safe? Did you try unfolding everything?

By the way, I noticed that your solution_state_at function has its termination shown by well-founded recursion. This makes it irreducible, which could be a problem for automation.

Willem vanhulle (Jun 19 2025 at 15:13):

So the solution to mathematician riddle looks like this

def solution_cross_vec : Vector (State  State) 11 :=
  Vector.ofFn (fun
    | 0 => move_math 0 right  move_notebook 0 right  move_boat right
    | 1 => move_math 0 left  move_notebook 0 left  move_boat left
    | 2 => move_math 1 right  move_notebook 1 right  move_boat right
    | 3 => move_math 0 right  move_notebook 0 right  move_boat right
    | 4 => move_math 2 right  move_notebook 2 right  move_boat right
    | 5 => move_math 0 left  move_notebook 0 left  move_boat left
    | 6 => move_math 0 right  move_notebook 0 right  move_boat right
    | 7 => move_math 1 left  move_notebook 1 left  move_boat left
    | 8 => move_math 1 right  move_notebook 1 right  move_boat right
    | 9 => move_math 0 left  move_notebook 0 left  move_boat left
    | 10 => move_math 0 right  move_notebook 0 right)

I am trying to prove by induction or fin_cases on the step but unfolding always get stuck I think

Willem vanhulle (Jun 19 2025 at 15:14):

Solution state is computed recursively based on these "crossings"

def solution_state_at : Fin 11  State
| 0, _⟩ => all_left
| n+1, h =>
  have n_lt_9 : n < 11 := by
    have : n + 1 < 11 := h
    cases Nat.lt_or_ge n 11 with
    | inl hlt => exact hlt
    | inr hge =>
      exact Nat.lt_of_succ_lt h
  (solution_cross_vec.get n, n_lt_9) (solution_state_at n, Nat.lt_of_succ_lt h)

Willem vanhulle (Jun 19 2025 at 15:16):

Did I do something wrong?

Aaron Liu (Jun 19 2025 at 15:16):

What goes wrong during unfolding?

Willem vanhulle (Jun 19 2025 at 15:21):

When I try this:

theorem all_states_safe :  i : Fin 11, safe (solution_states.get i) := by
  intro state_i
  fin_cases state_i <;> (
    simp
    unfold solution_states
  )

I get these leftover cases

unsolved goals
case «0»
⊢ safe ((Vector.ofFn solution_state_at).get 0)


case «1»
⊢ safe ((Vector.ofFn solution_state_at).get 1)


case «2»
⊢ safe ((Vector.ofFn solution_state_at).get 2)

I don't know whether I should use unfold or simp

Willem vanhulle (Jun 19 2025 at 15:22):

Also, the fact my safe property expects i, j i \neq j makes it very nested

Aaron Liu (Jun 19 2025 at 15:26):

docs#Vector.get_ofFn

Willem vanhulle (Jun 19 2025 at 15:30):

I tried this

theorem all_states_safe :  i : Fin 11, safe (solution_states.get i) := by
  intro i
  simp [Vector.get_ofFn]

Also imported the module but it cant make any progress

Willem vanhulle (Jun 19 2025 at 15:33):

I made a little progress with

theorem all_states_safe :  i : Fin 11, safe (solution_states.get i) := by
  intro i
  fin_cases i <;> (
    unfold solution_states
    simp [Vector.get_ofFn _]
    repeat
      unfold solution_state_at
      unfold solution_cros_vec
      simp [Vector.get_ofFn _]


  )

Aaron Liu (Jun 19 2025 at 15:35):

etc etc

def solution_state_atAux : (n : Nat)  n < 11  State
| 0, _ => all_left
| n+1, h =>
  have n_lt_9 : n < 11 := by
    have : n + 1 < 11 := h
    cases Nat.lt_or_ge n 11 with
    | inl hlt => exact hlt
    | inr hge =>
      exact Nat.lt_of_succ_lt h
  (solution_cross_vec.get n, n_lt_9) (solution_state_atAux n <| Nat.lt_of_succ_lt h)
-- Helper function to compute state at step i
def solution_state_at : Fin 11  State := fun f => solution_state_atAux f.val f.isLt

etc etc

instance : DecidablePred safe := by
  unfold safe
  infer_instance

theorem all_states_safe :  i : Fin 11, safe (solution_states.get i) := by
  decide

Willem vanhulle (Jun 19 2025 at 15:38):

Thanks a lot @Aaron Liu that seems to fix it. What was the main thing you changed?

Willem vanhulle (Jun 19 2025 at 15:38):

Is it because you made safe decidable?

Aaron Liu (Jun 19 2025 at 15:38):

maybe?

Willem vanhulle (Jun 19 2025 at 15:41):

Now I can clean it up. I probably will try to get rid of the constant numeral 11

Notification Bot (Jun 19 2025 at 18:02):

This topic was moved here from #metaprogramming / tactics > Proving the jealous mathematician riddle by Patrick Massot.

Willem vanhulle (Jun 19 2025 at 18:26):

@Aaron Liu do you know why your solution works with aux but when I merge the two functions I get problems with decidability?

Aaron Liu (Jun 19 2025 at 18:26):

It's because when you merge the two you are now defining the function by well founded recursion which is irreducible

Willem vanhulle (Jun 19 2025 at 18:27):

What exactly is breaking well founded recursion?

Aaron Liu (Jun 19 2025 at 18:28):

I don't understand what you mean by that, can you elaborate a bit?

Willem vanhulle (Jun 19 2025 at 18:29):

Isn’t welfounded recursion just checking that recursive calls are only applied to a smaller part of the outer argument? When I merge both I still see n+1 and at the end n as argument so it seems to me like it is still well founded recursion?

Aaron Liu (Jun 19 2025 at 18:30):

No, it was using structural recursion when they're separated (which is not irreducible) but when you merge them it switches to using well-founded recursion (which is irreducible)

Aaron Liu (Jun 19 2025 at 18:31):

decide will not unfold irreducible things


Last updated: Dec 20 2025 at 21:32 UTC