Zulip Chat Archive

Stream: lean4

Topic: mvcgen: how to fill this sorry?


Asei Inoue (Nov 16 2025 at 12:34):

import Lean

def fastExpo (root n : Nat) : Nat := Id.run do
  let mut x := root
  let mut y := 1
  let mut e := n
  for _ in [0:n] do
    if e % 2 = 1 then
      y := x * y
      e := e - 1
    else
      x := x * x
      e := e / 2
    if e = 0 then
      break
  return y

open Std.Do

set_option mvcgen.warning false

theorem fastExpo_spec (root n : Nat) :
    fastExpo root n = root ^ n := by
  generalize h : fastExpo root n = r
  apply Id.of_wp_run_eq h

  mvcgen invariants
  · ⇓⟨cursor, x, y, e => y * x ^ e = root ^ n  e + cursor.pos  n
  with grind

  case vc1 =>
    expose_names
    obtain x', y', e' := b
    simp_all
    constructor
    · dsimp [x_1, x, y]
      suffices e' = 0  y' = root ^ n from by
        grind
      dsimp [e, x, y] at *
      sorry
    · admit

  all_goals admit

Asei Inoue (Nov 16 2025 at 12:35):

How to fill this sorry? (you can ignore admit)
I beleive invariant condition is correct, but it seems to be impossible to show.... :(

Asei Inoue (Nov 16 2025 at 14:26):

@Sebastian Graf

I realized my mistake — the loop invariant I had in mind was wrong.
However, I still don’t understand why the variables appear in this order, rather than in the order in which they were defined with let mut.
Why is it specified to work this way?

import Lean

def fastExpo (root n : Nat) : Nat := Id.run do
  let mut x := root
  let mut y := 1
  let mut e := n
  for _ in [0:n] do
    if e % 2 = 1 then
      y := x * y
      e := e - 1
    else
      x := x * x
      e := e / 2
    if e = 0 then
      break
  return y

open Std.Do

set_option mvcgen.warning false

theorem fastExpo_spec (root n : Nat) :
    fastExpo root n = root ^ n := by
  generalize h : fastExpo root n = r
  apply Id.of_wp_run_eq h

  mvcgen invariants
  -- why `e → x → y` order?
  -- why not `x → y → e`?
  · ⇓⟨cursor, e, x, y => y * x ^ e = root ^ n  e + cursor.pos  n

  -- precondition:
  case vc5.a.pre =>
    expose_names
    simp

  case vc1 =>
    expose_names
    obtain e', x', y' := b
    simp at *
    constructor
    · dsimp [x_1, x, y]
      dsimp [e] at *
      have : e' = 1 := by grind only
      simp [this] at h_3 
      grind
    · simp_all

  case vc2 =>
    expose_names
    obtain e', x', y' := b
    simp at *
    refine ⟨?_, by grind
    dsimp [x_1, x, y, e] at *
    suffices y' * x' ^ e' = root ^ n from by
      rw [ this]
      have : e' - 1 + 1 = e' := by grind
      have : x' * y' * x' ^ (e' - 1) = y' * x' ^ e' := calc
        _ = y' * x' * x' ^ (e' - 1) := by grind
        _ = y' * x' ^ ((e' - 1) + 1) := by grind
        _ = y' * x' ^ e' := by simp_all
      assumption
    grind

  all_goals admit

Sebastian Graf (Nov 17 2025 at 10:01):

However, I still don’t understand why the variables appear in this order, rather than in the order in which they were defined with let mut.

They appear in alphabetical oder, because that's what the do elaborator does when constructing the state tuple for forIn. Try to rename e to z and notice that it now appears last:

import Lean

def fastExpo (root n : Nat) : Nat := Id.run do
  let mut x := root
  let mut y := 1
  let mut z := n
  for _ in [0:n] do
    if z % 2 = 1 then
      y := x * y
      z := z - 1
    else
      x := x * x
      z := z / 2
    if z = 0 then
      break
  return y

open Std.Do

set_option mvcgen.warning false

theorem fastExpo_spec (root n : Nat) :
    fastExpo root n = root ^ n := by
  generalize h : fastExpo root n = r
  apply Id.of_wp_run_eq h

  mvcgen invariants
  -- why `e → x → y` order?
  -- why not `x → y → e`?
  · ⇓⟨cursor, x, y, e => y * x ^ e = root ^ n  e + cursor.pos  n

  -- precondition:
  case vc5.a.pre =>
    expose_names
    simp

  case vc1 =>
    expose_names
    obtain x', y', e' := b
    simp at *
    constructor
    · dsimp [x_1, x, y, z]
      have : e' = 1 := by grind only
      simp [this] at h_3 
      grind
    · simp_all

  case vc2 =>
    expose_names
    obtain x', y', e' := b
    simp at *
    refine ⟨?_, by grind
    dsimp [x_1, x, y, z] at *
    suffices y' * x' ^ e' = root ^ n from by
      rw [ this]
      have : e' - 1 + 1 = e' := by grind
      have : x' * y' * x' ^ (e' - 1) = y' * x' ^ e' := calc
        _ = y' * x' * x' ^ (e' - 1) := by grind
        _ = y' * x' ^ ((e' - 1) + 1) := by grind
        _ = y' * x' ^ e' := by simp_all
      assumption
    grind

  all_goals admit

The new do elaborator will change this to preserve the declaration order, i.e., x before y before e.

Asei Inoue (Nov 17 2025 at 10:09):

@Sebastian Graf Thank you!!!

Asei Inoue (Nov 17 2025 at 15:09):

I have completed proof! Thanks again!!

import Lean

def binaryExpo (root n : Nat) : Nat := Id.run do
  let mut x := root
  let mut y := 1
  let mut e := n
  for _ in [0:n] do
    if e % 2 = 1 then
      y := x * y
      e := e - 1
    else
      x := x * x
      e := e / 2
    if e = 0 then
      break
  return y

open Std.Do

set_option mvcgen.warning false

@[grind =]
theorem Nat.Grind.pow_zero {n a : Nat} (h : a = 0) : n ^ a = 1 := by
  simp_all

@[grind =]
theorem Nat.pow_mul_self_halve_of_even (x e : Nat) (he : e % 2 = 0) :
    (x * x) ^ (e / 2) = x ^ e := calc
  _ = (x ^ 2) ^ (e / 2) := by grind
  _ = x ^ (2 * (e / 2)) := by grind [Nat.pow_mul]
  _ = x ^ e := by grind

@[grind! ·]
theorem Nat.mul_pow_sub_one_of_odd (x e : Nat) (he : e % 2 = 1) :
    x * x ^ (e - 1) = x ^ e := calc
  _ = x * x ^ (e - 1) := by grind
  _ = x ^ (1 + (e - 1)) := by grind
  _ = x ^ e := by congr; grind

theorem binaryExpo_spec (root n : Nat) :
    binaryExpo root n = root ^ n := by
  generalize h : binaryExpo root n = r
  apply Id.of_wp_run_eq h

  mvcgen invariants
  · ⇓⟨cursor, e, x, y => y * x ^ e = root ^ n  e + cursor.pos  n
  with grind

Last updated: Dec 20 2025 at 21:32 UTC