Zulip Chat Archive

Stream: mathlib4

Topic: Show termination using Lex (ℕ × ℕ)


Markus Schmaus (Mar 16 2024 at 12:19):

I'm trying to use Lex (ℕ × ℕ) for showing termination. But it doesn't work the way I thought it would. Is there a way to make it work?

import Mathlib

def aggr (ns : List ) :  :=
  let rec go (n : ) (ns : List ) :=
    if nil : ns = List.nil then n
    else
      have : List.length (List.tail ns) < List.length ns := by
        have := List.length_pos_of_ne_nil nil |> ne_zero_of_lt
        simp_all only [ne_eq, List.length_tail,  Nat.pred_eq_sub_one, not_false_eq_true,
          Nat.pred_lt]
      go n.succ ns.tail -- works
  termination_by (List.length ns)

  go 0 ns

abbrev finiteBy (ns : List ) : Lex ( × ) :=
  toLex (ns.length, ns.headD 0)

def aggr' (ns : List ) :  :=
  let rec go (n : ) (ns : List ) :=
    if nil : ns = List.nil then n
    else
      have : finiteBy (List.tail ns) < finiteBy ns := by
        have := List.length_pos_of_ne_nil nil |> ne_zero_of_lt
        simp only [finiteBy, List.length_tail, List.headD_eq_head?, Prod.Lex.lt_iff]
        apply Or.inl
        simp_all only [ne_eq, List.length_tail,  Nat.pred_eq_sub_one, not_false_eq_true,
          Nat.pred_lt]
      go n.succ ns.tail -- failed to prove termination, suggests proving `False`
  termination_by finiteBy ns

  go 0 ns

Of course in this mwe it doesn't really make sense to use finiteBy instead of length, but in my real use case I need lexicographical ordering.

Joachim Breitner (Mar 16 2024 at 12:21):

I don't think you need to use Lex explicitly, just use termination_by (ns.lengh, ns.headD 0) maybe?

Markus Schmaus (Mar 16 2024 at 12:40):

termination_by (ns.lengh, ns.headD 0) doesn't seem to use lexicographical ordering, here is a mwe that shows this:

import Mathlib

def aggr'' (ns : List ) :  :=
  let rec go (n : ) (ns : List ) :=
    if nil : ns = List.nil then n
    else if head_zero : ns.headD 0 = 0 then
      have : List.length (List.tail ns) < List.length ns := by
        have := List.length_pos_of_ne_nil nil |> ne_zero_of_lt
        simp_all only [ne_eq, List.length_tail,  Nat.pred_eq_sub_one, not_false_eq_true,
          Nat.pred_lt]
      go n.succ ns.tail -- This works
    else
      let ns' := (ns.headD 0).pred :: ns.tail
      have : ns'.length = ns.length := by
        unfold_let ns'
        have := List.length_pos_of_ne_nil nil |> ne_zero_of_lt |> Nat.succ_pred
        simp_all only [List.headD_eq_head?, List.length_cons, List.length_tail, 
          Nat.pred_eq_sub_one]
      have : ns'.headD 0 < ns.headD 0 := by
        unfold_let ns'
        simp only [List.headD_cons]
        simp_all only [List.headD_eq_head?]
      go n.succ ns' -- termination fails here
  termination_by (ns.length, ns.headD 0)

  go 0 ns

Joachim Breitner (Mar 16 2024 at 15:01):

Hmm, it is using the lexicographic ordering, but the the problem is that the default decreasing_tactic is not complete:

It uses docs#Prod.Lex.right, but in your case you need the more general docs#Prod.Lex.right'. You can make progress with

decreasing_by
    · simp_wf; omega
    · simp_wf
      apply Prod.Lex.right'
      · sorry
      · sorry

e.g.

def aggr'' (ns : List ) :  :=
  let rec go (n : ) (ns : List ) :=
    if nil : ns = List.nil then n
    else if head_zero : ns.headD 0 = 0 then
      have : List.length (List.tail ns) < List.length ns := by
        have := List.length_pos_of_ne_nil nil |> ne_zero_of_lt
        simp_all only [ne_eq, List.length_tail,  Nat.pred_eq_sub_one, not_false_eq_true,
          Nat.pred_lt]
      go n.succ ns.tail -- This works
    else
      let ns' := (ns.headD 0).pred :: ns.tail
      go n.succ ns' -- termination fails here
  termination_by (ns.length, ns.headD 0)
  decreasing_by
    · simp_wf; omega
    · simp_wf
      apply Prod.Lex.right'
      · apply Nat.le_of_eq
        exact RelSeries.fromListChain'.proof_1 ns nil
      · apply Nat.pred_lt
        simpa using head_zero
  go 0 ns

(the odd lemma is picked up by appy?:shrug: )


Last updated: May 02 2025 at 03:31 UTC