Zulip Chat Archive

Stream: new members

Topic: termination_by failure in new version of Lean and Mathlib


Bbbbbbbbba (Jan 06 2026 at 10:53):

I found that some old code I have written breaks with my current version of Lean and Mathlib:

import Mathlib

lemma dec_lemma {b : +} {x : } : 0 < x  Nat.log b x < x  0 < x / b ^ Nat.log b x  x - b ^ Nat.log b x * (x / b ^ Nat.log b x) < x := by
  sorry -- Proof not important

def nat_to_nat (b : +) (b1 : +) (x : ) :=
  if h : 0 < x then
    let e := Nat.log b x;
    let n := x / b ^ e;
    have hdec : e < x  0 < n  x - b ^ e * n < x := dec_lemma h
    have _ := hdec.right.right
    b1 ^ (nat_to_nat b b1 e) * n + (nat_to_nat b b1 (x - b ^ e * n : ))
  else 0
  termination_by x

The error is:

failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
b : +
x : 
h : 0 < x
e :  := Nat.log (b) x
n :  := x / b ^ e
hdec : e < x  0 < n  x - b ^ e * n < x
x : x - b ^ e * n < x
 0 < x  b ^ Nat.log (b) x  x

The hypothesis x✝ is supposed to be what I need, but now Lean seems to have automatically reduced the goal into something different. Is it possible for me to prevent it from doing so?

Bbbbbbbbba (Jan 06 2026 at 11:00):

Oh, I got decreasing_by to work. However now I get a warning saying that hdec is unused. What would be a graceful way to silence this warning?

def nat_to_nat (b : +) (b1 : +) (x : ) :=
  if h : 0 < x then
    let e := Nat.log b x;
    let n := x / b ^ e;
    have hdec : e < x  0 < n  x - b ^ e * n < x := dec_lemma h
    b1 ^ (nat_to_nat b b1 e) * n + (nat_to_nat b b1 (x - b ^ e * n : ))
  else 0
  termination_by x
  decreasing_by
    exact hdec.left
    exact hdec.right.right

Mirek Olšák (Jan 06 2026 at 11:02):

One way to suppress unused variable warning is replacing hdec with _hdec

Eric Wieser (Jan 06 2026 at 11:02):

That seems undesirable though, since then you have to use _hdec in the decreasing_by

Eric Wieser (Jan 06 2026 at 11:02):

This looks like a design flaw in the warning here

Mirek Olšák (Jan 06 2026 at 11:05):

or remove the label, and use that the default name for have is this

Mirek Olšák (Jan 06 2026 at 11:07):

Both of these are of course workarounds.

Mirek Olšák (Jan 06 2026 at 11:08):

By the way, do you know what caused the first issue, that looked like a bigger magic to me.

Robin Arnez (Jan 06 2026 at 14:15):

The issue is simp:

import Mathlib

lemma dec_lemma {b : +} {x : } : 0 < x  Nat.log b x < x  0 < x / b ^ Nat.log b x  x - b ^ Nat.log b x * (x / b ^ Nat.log b x) < x := by
  sorry -- Proof not important

/--
info: Try this:
  [apply] simp only [tsub_lt_self_iff, PNat.pos, pow_pos, mul_pos_iff_of_pos_left, Nat.div_pos_iff, true_and]
---
error: unsolved goals
b : ℕ+
x : ℕ
h : 1 ≤ x
e : ℕ := Nat.log (↑b) x
n : ℕ := x / ↑b ^ e
hdec : e < x ∧ 0 < n ∧ x - ↑b ^ e * n < x
x✝ : x - ↑b ^ e * n < x
⊢ 0 < x ∧ ↑b ^ Nat.log (↑b) x ≤ x
-/
#guard_msgs in
def nat_to_nat (b : +) (b1 : +) (x : ) :=
  if h : 1  x then
    let e := Nat.log b x;
    let n := x / b ^ e;
    have hdec : e < x  0 < n  x - b ^ e * n < x := dec_lemma h
    have _ := hdec.right.right
    b1 ^ (nat_to_nat b b1 e) * n + (nat_to_nat b b1 (x - b ^ e * n : ))
  else 0
termination_by x
decreasing_by
  · sorry
  · simp?

Robin Arnez (Jan 06 2026 at 14:16):

I would've expected it to try assumption before simp but apparently not?

Bbbbbbbbba (Jan 06 2026 at 15:21):

Here is another unintuitive thing I have encountered, though my MWE is unfortunately still quite long. The problem is that the proof breaks if if h1 : 0 < x - b ^ Nat.log b x * (x / b ^ Nat.log b x) then is replaced with if h1 : 0 < x - b ^ e * n then, even though I have defined let e := Nat.log b x; and let n := x / b ^ e;.

import Mathlib

open ONote

def nat_to_ord (b : +) (x : ) :=
  if h : 0 < x then
    let e := Nat.log b x;
    let n := x / b ^ e;
    oadd (nat_to_ord b e) (n, by sorry : +) (nat_to_ord b (x - b ^ e * n : ))
  else 0
  termination_by x
  decreasing_by
    all_goals sorry

lemma nat_to_ord_cmp_lt {b : +} (hlt: x0 < x1) : (nat_to_ord b x0).cmp (nat_to_ord b x1) = Ordering.lt := by
  sorry

theorem nat_to_ord_nf : (nat_to_ord b x).NF := by
  unfold nat_to_ord
  if h : 0 < x then
    let e := Nat.log b x;
    let n := x / b ^ e;
    simp [h]
    apply NF.oadd
    . exact nat_to_ord_nf
    . have : (nat_to_ord b e).NF := nat_to_ord_nf
      apply nfBelow_iff_topBelow.mpr
      constructor
      . exact nat_to_ord_nf
      . unfold TopBelow
        rw [nat_to_ord]
        if h1 : 0 < x - b ^ Nat.log b x * (x / b ^ Nat.log b x) then
        -- if h1 : 0 < x - b ^ e * n then
          simp [h1]
          apply nat_to_ord_cmp_lt
          apply Nat.log_lt_of_lt_pow
          . exact Ne.symm (Nat.ne_of_lt h1)
          . apply Nat.sub_lt_left_of_lt_add
            . exact Nat.mul_div_le x (b ^ e)
            . rw [ mul_add_one]
              apply Nat.lt_mul_div_succ
              exact Nat.pos_of_neZero (b ^ e)
        else
          simp [h1]
  else
    simp [h]
    exact NF.zero
termination_by x
decreasing_by
  all_goals sorry

Robin Arnez (Jan 06 2026 at 15:39):

simp per default treats let declarations in the context as opaque, you can use simp +zetaDelta to change that though

Robin Arnez (Jan 06 2026 at 15:41):

Another approach that works here is

rw [dif_pos h]
extract_lets e n

instead of

let e := Nat.log b x;
let n := x / b ^ e;
simp [h]

because it doesn't unfold the let declarations in the goal

Bbbbbbbbba (Jan 06 2026 at 16:11):

Thanks, this is much cleaner too as I no longer need to repeat my lets!


Last updated: Feb 28 2026 at 14:05 UTC