Zulip Chat Archive
Stream: new members
Topic: cannot mix theorems and definitions
Yiftach S (Feb 19 2024 at 16:43):
Hello! I noticed I cannot use mutual on a function with a theorem.
I want to write a recursive function, that uses a theorem about itself.
Is there any way to do that?
Junyan Xu (Feb 19 2024 at 18:53):
Can you provide a #mwe? Without knowing your exact problem, I guess it might help to define a function to a docs#Subtype.
Yiftach S (Feb 19 2024 at 19:18):
Junyan Xu said:
Can you provide a #mwe? Without knowing your exact problem, I guess it might help to define a function to a docs#Subtype.
Sure thing. My full code is much longer, but this a simplification of the issue:
inductive MyBinTree where
| leaf
| node (left : MyBinTree) (key : Nat) (right : MyBinTree)
def MyBinTree.sizeOf : MyBinTree → Nat
| .leaf => 0
| .node l _ r => 1 + l.sizeOf + r.sizeOf
def func (t : MyBinTree) : MyBinTree :=
match ht : t with
| .leaf => .leaf
| .node left key right =>
let t1 := func left
-- Here I'd like to use func_size, but func and func_size cannot be mutual
have help : left.sizeOf = t1.sizeOf := sorry
t
theorem func_size (t : MyBinTree) : t.sizeOf = (func t).sizeOf :=
sorry
Junyan Xu (Feb 19 2024 at 19:36):
In your example the constructors of MyBinTree doesn't require any proofs, so it's not clear why you'd like to use a theorem to define func
. Can you not define func
first and prove func_size
later? If not then I'm afraid I can't provide suggestions without a more detailed example that include the essential difficulty.
Yiftach S (Feb 19 2024 at 20:00):
Junyan Xu said:
In your example the constructors of MyBinTree doesn't require any proofs, so it's not clear why you'd like to use a theorem to define
func
. Can you not definefunc
first and provefunc_size
later? If not then I'm afraid I can't provide suggestions without a more detailed example that include the essential difficulty.
Thanks. The issue is that func is recursive, and it needs a proof about what it does in order to work.
I'll also add my actual problem, but it's rather long so I thought that the shorter version might be easier to respond to.
The problems are marked with "TODO".
import Mathlib.Tactic.Linarith
import Mathlib.Algebra.Order.Monoid.Lemmas
inductive TreeBin where
| leaf
| node (left : TreeBin) (key : Nat) (right : TreeBin)
def TreeBin.repr : TreeBin → String
| TreeBin.leaf => "" --"."
| TreeBin.node l k r => "(" ++ TreeBin.repr l ++ (Nat.repr k) ++ TreeBin.repr r ++ ")"
def TreeBin.sizeOf : TreeBin → Nat
| .leaf => 0
| .node l _ r => 1 + l.sizeOf + r.sizeOf
-- t1 ≤ t2 definition by key
instance : LE TreeBin where
le t1 t2 :=
match t1, t2 with
| .leaf, _ => true
| .node _ _ _, .leaf => false
| .node _ k1 _, .node _ k2 _ => k1 ≤ k2
-- Unfolding TreeBin comparison
macro "unfold_ge" : tactic => `(tactic| (unfold GE.ge LE.le instLETreeBin))
macro "unfold_le" : tactic => `(tactic| (unfold LE.le instLETreeBin))
theorem not_ge {a b : TreeBin} : (¬(a ≥ b)) → (b ≥ a) := by
intro h
match ha : a, hb : b with
| .leaf, _ => unfold_ge; trivial
| .node _ _ _, .leaf => let h3 : ¬ (a ≥ b) := by simp [ha, hb, h]
let h2 : a ≥ b := by unfold_ge; trivial
exact absurd h2 h3
| .node _ k1 _, .node _ k2 _ => unfold_ge; simp [ha, hb];
sorry
-- LE is decidable
instance TreeBin.decLE (t₁ t₂ : TreeBin) : Decidable (t₁ ≤ t₂) :=
match t₁, t₂ with
| .leaf, _ => by simp [LE.le]; exact isTrue True.intro
| .node _ _ _, .leaf => by simp [LE.le]; exact isFalse False.elim
| .node _ k1 _, .node _ k2 _ => Nat.decLe k1 k2
-- Local max heap definition - the node doesn't have direct children that are bigger
def local_max (t : TreeBin) : Prop :=
match t with
| .leaf => True
| .node l _ r => (t ≥ l) ∧ (t ≥ r)
-- local_max is decidable
instance decidable_local_max (t : TreeBin) : Decidable (local_max t) :=
match t with
| .leaf => by simp [local_max]; exact isTrue True.intro
| .node l _ r => instDecidableAnd
-- max heap condition - local_max and children are also max heaps
inductive is_max_heap : TreeBin → Prop
| leaf : is_max_heap .leaf
| node : local_max (.node left key right) →
is_max_heap left → is_max_heap right →
is_max_heap (.node left key right)
def MaxHeap := { t : TreeBin // is_max_heap t }
-- Proof that LE is transitive for TreeBin
theorem trans_le {a b c : TreeBin} : a ≤ b → b ≤ c → a ≤ c := by
intro h1 h2
cases a with
| leaf => trivial -- A leaf is always little
| node al ak ar =>
cases b with
| leaf => trivial -- b being a leaf contradicts a ≤ b
| node bl bk br =>
cases c with
| leaf => trivial -- c being a leaf contradicts b ≤ c
-- All 3 are nodes, so this is the final case which is simply comparing numbers
| node cl ck cr => exact Nat.le_trans h1 h2
-- Make lean use trans_le
instance : Trans (LE.le : TreeBin → TreeBin → Prop) (LE.le : TreeBin → TreeBin → Prop) (LE.le : TreeBin → TreeBin → Prop) where
trans := fun p q => trans_le p q
-- Same but for GE
instance : Trans (GE.ge : TreeBin → TreeBin → Prop) (GE.ge : TreeBin → TreeBin → Prop) (GE.ge : TreeBin → TreeBin → Prop) where
trans := fun p q => trans_le q p
theorem plus_le1 (a b : Nat) : a < 1 + a + b := by
linarith
theorem plus_le2 (a b : Nat) : b < 1 + a + b := by
linarith
-- Convert a TreeBin to a heap recursively
def heapify (t : TreeBin) : MaxHeap :=
match ht : t with
| .leaf => ⟨.leaf, by simp [is_max_heap.leaf]⟩
| .node left key right =>
-- used to prove termination
have left_l_t : left.sizeOf < t.sizeOf := by
conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf
simp [ht, plus_le1]
have _ : right.sizeOf < t.sizeOf := by
conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf
simp [ht, plus_le2]
-- recursively heapify children
let l1 := heapify left
let r1 := heapify right
-- a default return value in case local_max is OK
let ret_t_default := TreeBin.node l1.val key r1.val
if default_local_max : local_max ret_t_default then
⟨ret_t_default, by
simp [is_max_heap.node, default_local_max, l1.property, r1.property]⟩
-- Second case l2 on top
else if l1_ge_r1 : l1.val ≥ r1.val then
match hl1 : l1.val with
| .leaf => by -- l1 being a leaf is absurd
have ret_ge_l1 : ret_t_default ≥ l1.val := by unfold_ge; simp [hl1]
have ret_ge_r1 : ret_t_default ≥ r1.val := by calc _ ≥ l1.val := ret_ge_l1
_ ≥ r1.val := l1_ge_r1
have ret_local_max : local_max ret_t_default := by simp [local_max, ret_ge_l1, ret_ge_r1]
exact absurd ret_local_max default_local_max
| .node ll lk lr =>
let l2_t := TreeBin.node ll key lr
have _ : l2_t.sizeOf < t.sizeOf := calc _ = l1.val.sizeOf := by unfold TreeBin.sizeOf; simp [hl1]
-- TODO: how to prove that heapify doesn't change the size?
_ = left.sizeOf := by sorry
_ < t.sizeOf := left_l_t
let l2 := heapify l2_t
let ret_t := TreeBin.node l2.val lk r1.val
-- TODO: I didn't manage to complete this proof.
-- l2_t consists of ll + lr + key.
-- key is smaller than lk because of default_local_max + l1_ge_r1.
-- ll and lr are recursively smaller than lk because l1 is a max heap.
-- This proves that l1 is greater than ANY value in l2_t, and heapify doesn't add any values.
have l1_ge_l2 : l1.val ≥ l2.val := by sorry
let local_max_ret_t : local_max ret_t := by {
unfold local_max;
apply And.intro
· calc ret_t ≥ l1.val := by unfold_ge; simp [hl1]
_ ≥ l2.val := l1_ge_l2
· calc ret_t ≥ l1.val := by unfold_ge; simp [hl1] -- Same key
_ ≥ r1.val := l1_ge_r1
}
⟨ret_t, by simp [is_max_heap.node, l2.property, r1.property, local_max_ret_t]⟩
-- Final case r1 on top
-- I didn't bother with this as much, because the privious case is similar
else
match hr1 : r1.val with
| .leaf => by -- l1 being a leaf is absurd
have ret_ge_r1 : ret_t_default ≥ r1.val := by unfold_ge; simp [hr1]
have ret_ge_l1 : ret_t_default ≥ l1.val := by calc _ ≥ r1.val := ret_ge_r1
_ ≥ l1.val := not_ge l1_ge_r1
have ret_local_max : local_max ret_t_default := by simp [local_max, ret_ge_l1, ret_ge_r1]
exact absurd ret_local_max default_local_max
| .node rl rk rr =>
let r2_t := TreeBin.node rl key rr
have _ : r2_t.sizeOf < t.sizeOf := sorry
let r2 := heapify r2_t
let ret_t := TreeBin.node l1.val rk r2.val
let local_max_ret_t : local_max ret_t := by sorry
⟨ret_t, by simp [is_max_heap.node, r2.property, l1.property, local_max_ret_t]⟩
termination_by t.sizeOf
Kyle Miller (Feb 19 2024 at 20:13):
I think Junyan's subtype suggestion would be to define def heapifyAux (t : TreeBin) : {mh : MaxHeap // mh.val.sizeOf = t.sizeOf}
to interleave the proof with the definition.
Yiftach S (Feb 19 2024 at 20:25):
Kyle Miller said:
I think Junyan's subtype suggestion would be to define
def heapifyAux (t : TreeBin) : {mh : MaxHeap // mh.val.sizeOf = t.sizeOf}
to interleave the proof with the definition.
Oh, I didn't realize you could define a subtype while returning it. This indeed solves the problem, Thanks!
I still doubt I'll manage the second TODO (the long one).
I need to show that heapify doesn't change the values and adds something bigger than there used to be in the tree. Is there any elegant way to go about that?
Junyan Xu (Feb 19 2024 at 22:31):
(deleted)
Junyan Xu (Feb 19 2024 at 22:33):
Hopefully this helps:
/-- Define the set of values inductively. -/
def TreeBin.values : TreeBin → Set Nat
| .leaf => ∅
| .node left key right => left.values ∪ {key} ∪ right.values
/-- Define `heapify` without incorporating the is_max_heap condition; it's easier to prove it separately. -/
def heapify (t : TreeBin) : {t' : TreeBin // t'.sizeOf = t.sizeOf} :=
match ht : t with
| .leaf => ⟨.leaf, by simp⟩
| .node left key right =>
have : left.sizeOf < t.sizeOf := by
conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf
simp [ht, plus_le1]
have : right.sizeOf < t.sizeOf := by
conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf
simp [ht, plus_le2]
let l1 := heapify left
let r1 := heapify right
let ret_t_default : {t' : TreeBin // t'.sizeOf = (TreeBin.node left key right).sizeOf} :=
⟨TreeBin.node l1 key r1, by simp_rw [TreeBin.sizeOf, l1.2, r1.2]⟩
if local_max ret_t_default then ret_t_default
else if l1.val ≥ r1.val then
match hl1 : l1.val with
| .leaf => ret_t_default -- this can't happen, you're free to put any value
| .node ll lk lr =>
let l2_t := TreeBin.node ll key lr
have : l2_t.sizeOf < t.sizeOf := by
have hl1 := congr_arg TreeBin.sizeOf hl1
rw [TreeBin.sizeOf] at hl1
simp_rw [ht, TreeBin.sizeOf, ← hl1, l1.2, plus_le1]
⟨TreeBin.node (heapify l2_t).val lk r1, by
simp [TreeBin.sizeOf, (heapify l2_t).2, r1.2, ← l1.2, hl1]⟩
else match hr1 : r1.val with
| .leaf => ret_t_default -- can't happen
| .node rl rk rr =>
let r2_t := TreeBin.node rl key rr
have : r2_t.sizeOf < t.sizeOf := by
have hr1 := congr_arg TreeBin.sizeOf hr1
rw [TreeBin.sizeOf] at hr1
simp_rw [ht, TreeBin.sizeOf, ← hr1, r1.2, plus_le2]
⟨TreeBin.node l1 rk (heapify r2_t), by
simp [TreeBin.sizeOf, (heapify r2_t).2, l1.2, ← r1.2, hr1]⟩
termination_by t.sizeOf
/-- We can now state the fact that `heapify` preserves the set of values. -/
lemma values_heapify (t : TreeBin) : (heapify t).1.values = t.values := sorry
Yiftach S (Feb 23 2024 at 17:35):
Junyan Xu said:
Hopefully this helps:
/-- Define the set of values inductively. -/ def TreeBin.values : TreeBin → Set Nat | .leaf => ∅ | .node left key right => left.values ∪ {key} ∪ right.values /-- Define `heapify` without incorporating the is_max_heap condition; it's easier to prove it separately. -/ def heapify (t : TreeBin) : {t' : TreeBin // t'.sizeOf = t.sizeOf} := match ht : t with | .leaf => ⟨.leaf, by simp⟩ | .node left key right => have : left.sizeOf < t.sizeOf := by conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf simp [ht, plus_le1] have : right.sizeOf < t.sizeOf := by conv in TreeBin.sizeOf t => unfold TreeBin.sizeOf simp [ht, plus_le2] let l1 := heapify left let r1 := heapify right let ret_t_default : {t' : TreeBin // t'.sizeOf = (TreeBin.node left key right).sizeOf} := ⟨TreeBin.node l1 key r1, by simp_rw [TreeBin.sizeOf, l1.2, r1.2]⟩ if local_max ret_t_default then ret_t_default else if l1.val ≥ r1.val then match hl1 : l1.val with | .leaf => ret_t_default -- this can't happen, you're free to put any value | .node ll lk lr => let l2_t := TreeBin.node ll key lr have : l2_t.sizeOf < t.sizeOf := by have hl1 := congr_arg TreeBin.sizeOf hl1 rw [TreeBin.sizeOf] at hl1 simp_rw [ht, TreeBin.sizeOf, ← hl1, l1.2, plus_le1] ⟨TreeBin.node (heapify l2_t).val lk r1, by simp [TreeBin.sizeOf, (heapify l2_t).2, r1.2, ← l1.2, hl1]⟩ else match hr1 : r1.val with | .leaf => ret_t_default -- can't happen | .node rl rk rr => let r2_t := TreeBin.node rl key rr have : r2_t.sizeOf < t.sizeOf := by have hr1 := congr_arg TreeBin.sizeOf hr1 rw [TreeBin.sizeOf] at hr1 simp_rw [ht, TreeBin.sizeOf, ← hr1, r1.2, plus_le2] ⟨TreeBin.node l1 rk (heapify r2_t), by simp [TreeBin.sizeOf, (heapify r2_t).2, l1.2, ← r1.2, hr1]⟩ termination_by t.sizeOf /-- We can now state the fact that `heapify` preserves the set of values. -/ lemma values_heapify (t : TreeBin) : (heapify t).1.values = t.values := sorry
Thanks! That's seems to be the way to do it
Last updated: May 02 2025 at 03:31 UTC