Zulip Chat Archive
Stream: new members
Topic: How to decide no loops in list of references
aron (Sep 26 2025 at 23:17):
How do I write this function?
inductive LeafOrRef
| leaf : LeafOrRef
| ref : Nat → LeafOrRef
deriving DecidableEq
instance : Membership Nat LeafOrRef where
mem lor n :=
match lor with
| .leaf => False
| .ref i => i = n
inductive NoLoop (list : List LeafOrRef) : (lor : LeafOrRef) → Prop
| leaf : NoLoop list .leaf
| ref {i} {prf : i < list.length} : NoLoop list list[i] → NoLoop list (.ref i)
def NoLoop.decide
(list : List LeafOrRef)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(lor : LeafOrRef)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
: Decidable (NoLoop list lor) := by
sorry
I've tried so many different things but I can't work out how to do this. Especially because I keep having to prove termination, but the whole point of this type is that it tells you whether there are loops or not, and since the list is finite in length, it should always terminate.
Aaron Liu (Sep 26 2025 at 23:30):
what have you tried
aron (Sep 27 2025 at 10:48):
This is the point at which I grind (no pun intended) to a halt :sweat:
import Mathlib
inductive LeafOrRef
| leaf : LeafOrRef
| ref : Nat → LeafOrRef
deriving DecidableEq, BEq
@[simp,grind] instance instMembershipNatLeafOrRef : Membership Nat LeafOrRef where
mem lor n :=
match lor with
| .leaf => False
| .ref i => i = n
@[simp,grind] def instMembershipNatLeafOrRef.decide (i : Nat) (lor : LeafOrRef) : Decidable (i ∈ lor) := by
match h : lor with
| .leaf =>
refine .isFalse ?_
simp
| .ref j =>
if hh : j = i then
refine .isTrue ?_
simp
trivial
else
refine .isFalse ?_
simp
trivial
/-- Proves that `i` is not contained in `lor` or any of the items it points to. This prop also contains proofs for any refs we find along the way, so it indeed proves not only that `i` isn't contained along the paths, but there are no loops anywhere along the path. -/
inductive NoLoop (list : List LeafOrRef) : (i : Nat) → (lor : LeafOrRef) → Prop
| leaf : NoLoop list i .leaf
| ref {prf : j < list.length} :
i ≠ j →
NoLoop list i list[j] → -- doesn't contain the ref that this thing is about
NoLoop list j list[j] → -- also doesn't contain the current ref, recursively
NoLoop list i (.ref j)
@[grind] theorem List.rev_pigeonhole_principle {l : List Nat}
(nodup : l.Nodup)
(heach : ∀ item ∈ l, item < n)
(hitemlen : item < n)
(hitemin : item ∉ l)
: l.length < n := by
let s : Finset Nat := Finset.mk l nodup
have hs1 (x) : x ∈ l ↔ x ∈ s := Iff.rfl
have hs2 : l.length = s.card := rfl
simp only [hs1, implies_true, hs2] at *
have : s ⊂ Finset.range n := by
constructor
· intro a ha
simp_all
· intro hs
exact hitemin (hs (by simpa using hitemlen))
simpa using Finset.card_lt_card this
def NoLoop.decideInner
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(seenRefs : List Nat)
(refInSeens : ref ∈ seenRefs)
(seensLen : seenRefs.length ≤ list.length)
(seensNodup : seenRefs.Nodup)
(seenEachSize : ∀ i ∈ seenRefs, i < list.length)
(seenNinLor : ∀ i ∈ seenRefs, i ∉ lor)
: Decidable (NoLoop list ref lor) := by
cases hlor : lor with
| leaf => refine .isTrue .leaf
| ref i =>
simp at hvalid_lor
rw [hlor] at hvalid_lor
replace hvalid_lor := hvalid_lor i rfl
let refItem := list[i]'hvalid_lor
match instMembershipNatLeafOrRef.decide i refItem, instMembershipNatLeafOrRef.decide ref refItem with
| .isTrue prfI, _ =>
refine .isFalse ?_
intro nl
cases nl
expose_names
unfold refItem at prfI
cases hh : list[i] with
| leaf =>
rw [hh] at prfI
simp at prfI
| ref =>
rw [hh] at prfI h_2
simp at prfI
subst_eqs
simp at seenNinLor
cases h_2
expose_names
rw [hh] at h_2
cases h_2
contradiction
| _, .isTrue prfRef =>
refine .isFalse ?_
intro nl
cases nl
expose_names
unfold refItem at prfRef
cases hh : list[i] with
| leaf =>
rw [hh] at prfRef
simp at prfRef
| ref =>
rw [hh] at prfRef h_2
simp at prfRef
subst_eqs
simp at seenNinLor
cases h_2
contradiction
| .isFalse prfI, .isFalse prfRef =>
-- For proving termination
have : seenRefs.length < list.length := by
have : i ∉ seenRefs := by
rw [hlor] at seenNinLor
simp at seenNinLor
grind
have pig := List.rev_pigeonhole_principle (n:=list.length) seensNodup ?_ ?_ this
grind
grind
trivial
let decRef :=
decideInner
list
refItem
ref
(by
have : refItem ∈ list := by grind
refine hvalid_refs refItem this)
hvalid_refs
(i :: seenRefs)
(by tauto)
(by
have : i ∉ seenRefs := by
rw [hlor] at seenNinLor
simp at seenNinLor
grind
have pig := List.rev_pigeonhole_principle (n:=list.length) seensNodup ?_ ?_ this
grind
grind
trivial)
(by
have : i ∈ lor := by simp [hlor]
grind)
(by
simp
refine ⟨hvalid_lor, ?_⟩
tauto)
(by
simp only [List.mem_cons, forall_eq_or_imp]
refine ⟨prfI,?_⟩
sorry)
match decRef with
| .isTrue prfRef' =>
have termin_proof : seenRefs.length < list.length := this
let decI :=
decideInner
list
refItem
i
(by
have : refItem ∈ list := list.getElem_mem hvalid_lor
refine hvalid_refs refItem this)
hvalid_refs
seenRefs
(by tauto)
(by
have : ref ∉ seenRefs := by
rw [hlor] at seenNinLor
simp at seenNinLor
grind
have pig := List.rev_pigeonhole_principle (n:=list.length) seensNodup ?_ ?_ this
grind
grind
trivial)
(by
have : ref ∈ lor := by simp [hlor]
grind)
(by
simp
have := seenEachSize ref refInSeens
refine ⟨this, ?_⟩
tauto)
(by
simp only [List.mem_cons, forall_eq_or_imp]
refine ⟨prfRef,?_⟩
sorry)
match decI with
| .isTrue prfI =>
refine .isTrue ?_
subst_eqs
refine NoLoop.ref ?_ prfRef' prfI
tauto
| .isFalse prfI =>
refine .isFalse ?_
intro nl
cases nl
unfold refItem at prfRef
contradiction
| .isFalse prf =>
refine .isFalse ?_
intro nl
cases nl
unfold refItem at prf
contradiction
termination_by (list.length - seenRefs.length)
aron (Sep 27 2025 at 10:49):
For some reason grind stops working altogether in the second call to decideInner with this error:
tactic 'grind' failed, the goal mentions the declaration `decideInner`, which is being defined. To avoid circular reasoning, try rewriting the goal to eliminate `decideInner` before using `grind`.
Even though the goal is just Decidable (NoLoop list ref (LeafOrRef.ref i)) which doesn't mention decideInner at all?
aron (Sep 27 2025 at 10:51):
Plus I just don't even know if I'm going down the right track at all.
Intuitively what I'm doing is pretty simple: recurse down the list with refs, keeping a list of refs we've seen so far. If you come across a ref you've already seen, you know you've hit a loop. Otherwise keep going until you hit a .leaf. In which case you know there are no loops.
aron (Sep 27 2025 at 10:52):
But as you can see even just trying that approach is... a lot. I feel like there must be a simpler way to do this. I just can't figure out how
Aaron Liu (Sep 27 2025 at 11:07):
Tricky
Aaron Liu (Sep 27 2025 at 11:08):
Try writing a Bool-valued function and proving it correct
aron (Sep 27 2025 at 11:20):
I feel like that's going to be harder, no?
Aaron Liu (Sep 27 2025 at 11:26):
Since you have no dependent types
Aaron Liu (Sep 27 2025 at 11:26):
and you can delay the proof obligation
aron (Sep 27 2025 at 11:29):
Right so the implementation would be smoother, bc I can do it however I want. But I don't know how I'd prove that my implementation is equal to decidability
Aaron Liu (Sep 27 2025 at 11:31):
you can think about that after you have any working implementation
aron (Sep 27 2025 at 11:42):
well sure but the bool version shouldn't take too long. I'm working on it now
aron (Sep 27 2025 at 11:53):
I went with an allowlist approach instead of the disallowlist of the above approach. This is my Bool-valued version. Although ofc I'm not 100% it's correct yet.
def NoLoop.decideDumb
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(refValid : ref < list.length)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(allowedRefs : List Nat)
(allowedRefsNodup : allowedRefs.Nodup)
(hrefNinAlloweds : ref ∉ allowedRefs)
: Bool :=
match hlor : lor with
| .leaf => true
| .ref i =>
if i = ref then
false
else
if hin : i ∈ allowedRefs then
by
simp at hvalid_lor
let refItem := list[i]'hvalid_lor
have : (allowedRefs.erase i).length < allowedRefs.length := by grind
refine decideDumb list refItem i hvalid_lor ?_ hvalid_refs (allowedRefs.erase i) ?_ ?_
refine hvalid_refs refItem (by grind)
exact List.Nodup.erase i allowedRefsNodup
exact List.Nodup.not_mem_erase allowedRefsNodup
else
false
termination_by (allowedRefs.length)
Aaron Liu (Sep 27 2025 at 11:58):
great that works
Aaron Liu (Sep 27 2025 at 12:01):
now we prove it correct
aron (Sep 27 2025 at 12:14):
Like this, by filling out the two theorems?
theorem NoLoop.decideDumb_true_iff_NoLoop {ref refValid}
: NoLoop.decideDumb list lor ref refValid hvalid_lor hvalid_refs = true ↔ NoLoop list ref lor
:= by sorry
theorem NoLoop.decideDumb_false_iff_NoLoop {ref refValid}
: NoLoop.decideDumb list lor ref refValid hvalid_lor hvalid_refs = false ↔ ¬NoLoop list ref lor
:= by sorry
def NoLoop.decide
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(refValid : ref < list.length)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
: Decidable (NoLoop list ref lor) := by
let dec := decideDumb list lor ref refValid hvalid_lor hvalid_refs
if h : dec then
refine .isTrue (NoLoop.decideDumb_true_iff_NoLoop.mp h)
else
simp at h
refine .isFalse (NoLoop.decideDumb_false_iff_NoLoop.mp h)
Aaron Liu (Sep 27 2025 at 12:20):
well you only need one theorem
Aaron Liu (Sep 27 2025 at 12:20):
don't try proving both it's redundant
Aaron Liu (Sep 27 2025 at 12:21):
and the decidable definition can use docs#decidable_of_bool
aron (Sep 27 2025 at 12:28):
Ok nice. But then the way to do this is indeed to prove NoLoop.decideDumb_true_iff_NoLoop as I've defined the theorem statement?
Aaron Liu (Sep 27 2025 at 12:29):
probably yeah
aron (Sep 27 2025 at 12:30):
And would NoLoop.decideDumbInner.induct be a good way to prove it?
Aaron Liu (Sep 27 2025 at 12:30):
you should try out the fun_induction tactic I think it will be useful here
aron (Sep 27 2025 at 18:34):
ok this is as far as I got
import Mathlib
inductive LeafOrRef
| leaf : LeafOrRef
| ref : Nat → LeafOrRef
deriving DecidableEq, BEq
@[simp,grind] instance instMembershipNatLeafOrRef : Membership Nat LeafOrRef where
mem lor n :=
match lor with
| .leaf => False
| .ref i => i = n
@[simp,grind] def instMembershipNatLeafOrRef.decide (i : Nat) (lor : LeafOrRef) : Decidable (i ∈ lor) := by
match h : lor with
| .leaf =>
refine .isFalse ?_
simp
| .ref j =>
if hh : j = i then
refine .isTrue ?_
simp
trivial
else
refine .isFalse ?_
simp
trivial
/-- Proves that `i` is not contained in `lor` or any of the items it points to. This prop also contains proofs for any refs we find along the way, so it indeed proves not only that `i` isn't contained along the paths, but there are no loops anywhere along the path. -/
inductive NoLoop (list : List LeafOrRef) : (i : Nat) → (lor : LeafOrRef) → Prop
| leaf : NoLoop list i .leaf
| ref {prf : j < list.length} :
i ≠ j →
NoLoop list i list[j] → -- doesn't contain the ref that this thing is about
NoLoop list j list[j] → -- also doesn't contain the current ref, recursively
NoLoop list i (.ref j)
def NoLoop.decideDumbInner
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(refValid : ref < list.length)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(allowedRefs : List Nat)
(allowedRefsNodup : allowedRefs.Nodup)
(allowedRefsInRange : ∀ i ∈ allowedRefs, i < list.length)
(hrefNinAlloweds : ref ∉ allowedRefs)
: Bool :=
match hlor : lor with
| .leaf => true
| .ref i =>
if i = ref then
false
else
if hin : i ∈ allowedRefs then
by
simp at hvalid_lor
let refItem := list[i]'hvalid_lor
-- For termination proving
have erased_len : (allowedRefs.erase i).length < allowedRefs.length := by grind
-- This should be `false` both if we encounter `i` *or* `ref` again – because neither are present in `allowedRefs`!
let innerI := decideDumbInner list refItem i hvalid_lor (hvalid_refs refItem (by grind)) hvalid_refs (allowedRefs.erase i) (List.Nodup.erase i allowedRefsNodup) (by grind) (List.Nodup.not_mem_erase allowedRefsNodup)
refine innerI
else
false
termination_by (allowedRefs.length)
theorem NoLoop.decideDumbInner_true_iff_NoLoop {ref refValid allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds}
: NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds = true ↔ (NoLoop list ref lor ∧ (∀ i, (h : i ∈ lor) → NoLoop list i (list[i]'(hvalid_lor i h)) ∧ NoLoop list ref (list[i]'(hvalid_lor i h))))
:= by
apply Iff.intro
·
have forward :
NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds = true →
-- NoLoop list ref lor ∧
-- (∀ i ∈ lor, NoLoop list i (list[i]'(?_)) ∧ NoLoop list ref (list[i]'(?_))) ∧
-- (∀ i < list.length, (hin : i ∉ allowedRefs) → (NoLoop list i lor ∧ NoLoop list i (list[i]'(?_)) ∧ NoLoop list ref (list[i]'(?_))))
NoLoop list ref lor ∧
(∀ i, (h : i ∈ lor) → NoLoop list i (list[i]'(hvalid_lor i h)) ∧ NoLoop list ref (list[i]'(hvalid_lor i h)))
-- ∧
-- (∀ i < list.length, (hin : i ∉ allowedRefs) → (NoLoop list i lor ∧ NoLoop list i (list[i]'(?_)) ∧ NoLoop list ref (list[i]'(?_))))
:= by
fun_induction NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds with
| case1 ref refValid allowedRefs allowedRefsNodup hrefNinAlloweds hvalid_lor =>
intro _
refine ⟨ .leaf, ?_ ⟩
simp
| case2 =>
intro _
contradiction
| case3 =>
-- ref refValid allowedRefs allowedRefsNodup hrefNinAlloweds hvalid_lor _ _ i hneq hin h refItem hvalid_lor erased_len innerI ih
expose_names
intro inner
replace ih1 := ih1 inner
unfold refItem at ih1
have left := ih1.left
have right := ih1.right
refine ⟨ .ref (Ne.symm h) ?_ left, ?_ ⟩
· sorry -- ???
· intro j jin
simp at jin
subst_eqs
refine ⟨left,?_⟩
sorry -- ???
| case4 =>
expose_names
intro _
contradiction
refine forward
· sorry
def NoLoop.decide
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(refValid : ref < list.length)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(allowedRefs : List Nat)
(allowedRefsNodup : allowedRefs.Nodup)
(allowedRefsInRange : ∀ i ∈ allowedRefs, i < list.length)
(hrefNinAlloweds : ref ∉ allowedRefs)
: Decidable (NoLoop list ref lor) := by
let result :=
decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds
let dec :=
decidable_of_bool result NoLoop.decideDumbInner_true_iff_NoLoop
cases dec with
| isTrue prf =>
refine .isTrue prf.left
| isFalse prf =>
simp only [not_and, not_forall] at prf
refine .isFalse ?_
intro nl
have ⟨x,h,bla⟩ := prf nl
cases nl
· simp at h
· simp at h
subst_eqs
expose_names
have := bla h
contradiction
aron (Sep 27 2025 at 18:35):
I cannot work out how to tell Lean about the link between not being in allowedRefs and being able to construct a NoLoop for it :sweat:
aron (Sep 27 2025 at 19:14):
E.g. at this point:
| case3 =>
expose_names
intro inner
replace ih1 := ih1 inner
unfold refItem at ih1
have left := ih1.left
have right := ih1.right
refine ⟨ .ref (Ne.symm h) ?_ left, ?_ ⟩
·
-- 👈 HERE
sorry
the target is NoLoop list ref_1 list[i]. And I would indeed expect there to be one of these available in ih1. Because I specified it in the induction goal (see screenshot). But somehow there doesn't appear to be one of these available
Screenshot 2025-09-27 at 20.13.24.png
aron (Sep 27 2025 at 21:05):
Ok I modified the induction target a bit and now I can get further – but I still am left with one part I don't know how to solve
def NoLoop.decideDumbInner
(list : List LeafOrRef)
(lor : LeafOrRef)
(ref : Nat)
(refValid : ref < list.length)
(hvalid_lor : ∀ i ∈ lor, i < list.length)
(hvalid_refs : ∀ item ∈ list, ∀ i ∈ item, i < list.length)
(allowedRefs : List Nat)
(allowedRefsNodup : allowedRefs.Nodup)
(allowedRefsInRange : ∀ i ∈ allowedRefs, i < list.length)
(hrefNinAlloweds : ref ∉ allowedRefs)
: Bool :=
match hlor : lor with
| .leaf => true
| .ref i =>
if i = ref then
false
else
if hin : i ∈ allowedRefs then
by
simp at hvalid_lor
let refItem := list[i]'hvalid_lor
-- For termination proving
have erased_len : (allowedRefs.erase i).length < allowedRefs.length := by grind
-- This should be `false` both if we encounter `i` *or* `ref` again – because neither are present in `allowedRefs`!
let innerI := decideDumbInner list refItem i hvalid_lor (hvalid_refs refItem (by grind)) hvalid_refs (allowedRefs.erase i) (List.Nodup.erase i allowedRefsNodup) (by grind) (List.Nodup.not_mem_erase allowedRefsNodup)
refine innerI
else
false
termination_by (allowedRefs.length)
theorem NoLoop.decideDumbInner_true_iff_NoLoop {ref refValid allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds}
: NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds = true ↔ NoLoop list ref lor ∧
(∀ old ∉ allowedRefs, NoLoop list old lor) ∧
(∀ i, (h : i ∈ lor) →
(NoLoop list i (list[i]'(hvalid_lor i h)) ∧
NoLoop list ref (list[i]'(hvalid_lor i h))))
:= by
apply Iff.intro
·
have forward :
NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds = true →
NoLoop list ref lor ∧
(∀ old ∉ allowedRefs, NoLoop list old lor) ∧
(∀ i, (h : i ∈ lor) →
(NoLoop list i (list[i]'(hvalid_lor i h)) ∧
NoLoop list ref (list[i]'(hvalid_lor i h))))
:= by
fun_induction NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds with
| case1 ref refValid allowedRefs allowedRefsNodup hrefNinAlloweds hvalid_lor =>
intro _
refine ⟨ .leaf, ?_ ⟩
simp
tauto
| case2 =>
intro _
contradiction
| case3 =>
expose_names
intro inner
replace ih1 := ih1 inner
unfold refItem at ih1
have left := ih1.left
have right := ih1.right
refine ⟨ .ref (Ne.symm h) ?_ left, ?_ ⟩
· have rl := right.left
refine rl ref_1 (by grind)
· simp [*]
constructor
· have rl := right.left
intro j jin
refine .ref (prf:=by grind) (by grind) (by grind) (by grind)
· intro _ h
subst_eqs
constructor
· grind
· grind
| case4 =>
expose_names
intro _
contradiction
refine forward
·
have backward :
NoLoop list ref lor ∧
(∀ old ∉ allowedRefs, NoLoop list old lor) ∧
(∀ i, (h : i ∈ lor) →
(NoLoop list i (list[i]'(hvalid_lor i h)) ∧
NoLoop list ref (list[i]'(hvalid_lor i h)))) →
NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds = true
:= by
fun_induction NoLoop.decideDumbInner list lor ref refValid hvalid_lor hvalid_refs allowedRefs allowedRefsNodup allowedRefsInRange hrefNinAlloweds with
| case1 =>
intro _
rfl
| case2 =>
intro prem
have := prem.left
cases this
contradiction
| case3 =>
intro prem
expose_names
apply ih1
have ⟨ll,lr,r⟩ := prem
constructor
· simp at r
refine r i rfl |>.left
· constructor
· simp at r
replace r := r i rfl
unfold refItem
intro old oldnin
have : (old ∉ allowedRefs_1.erase i) = (old = i ∨ old ∉ allowedRefs_1) := by grind
rw [this] at oldnin
cases oldnin
· subst_eqs
exact r.left
· rename_i h
cases lr old h
trivial
· intro j jin
simp at r
unfold refItem at jin
-- replace r := r i rfl
-- cases lr ref_1 hrefNinAlloweds
-- cases ll
sorry -- <-- STUCK HERE
| case4 =>
intro prem
have ⟨ll,lr,r⟩ := prem
simp at r
expose_names
cases lr i h_1
contradiction
refine backward
aron (Sep 27 2025 at 21:09):
The whole #mwe (for anyone keeping up)
aron (Sep 27 2025 at 21:14):
In short: I don't have any way to prove anything about the item at list[j]
Screenshot 2025-09-27 at 22.12.53.png
aron (Sep 27 2025 at 21:15):
I wonder if I need to add something explicit about the relationship between allowedRefs and NoLoopness in the induction target :thinking:
aron (Sep 27 2025 at 21:16):
because I've managed to prove the forward direction of the iff (:tada:) but I'm stuck on this part in the backward direction. And I'm not sure I can prove it without saying something about j being in allowedRefs
Aaron Liu (Sep 27 2025 at 21:26):
When I'm back at a keyboard I'll try helping
aron (Sep 28 2025 at 22:56):
@Aaron Liu any ideas?
Aaron Liu (Sep 28 2025 at 22:56):
oh I forgot about this sorry
Aaron Liu (Sep 28 2025 at 22:57):
let's see...
Aaron Liu (Sep 28 2025 at 23:17):
sorry I don't have any ideas
aron (Sep 28 2025 at 23:19):
are you familiar with the stuff in Mathlib? any idea if some of the theorems and/or decidable instances in SimpleGraph would be useful to me?
Aaron Liu (Sep 28 2025 at 23:20):
not simple graphs this is a thing with directed graphs
aron (Sep 28 2025 at 23:20):
ah right. and SimpleGraph can't handle those? is there anything in Mathlib for directed graphs?
Aaron Liu (Sep 28 2025 at 23:20):
Aaron Liu (Sep 28 2025 at 23:21):
can't find anything about cycles
aron (Sep 28 2025 at 23:23):
hm yeah Digraphs doesn't seem to have that much in it. Is that it for graph types in mathlib? surely not?
Aaron Liu (Sep 28 2025 at 23:23):
aron (Sep 28 2025 at 23:27):
hm ok. looks promising. but i can't see anything in there to decide loop-ness
Matt Diamond (Sep 29 2025 at 04:16):
I don't understand what you're trying to model... LeafOrRef seems identical to Option Nat, how do loops come into play here? How would a list of Option Nat have or not have a loop?
aron (Sep 29 2025 at 10:01):
@Matt Diamond the Nats reference other LeafOrRefs by their index in the list
Aaron Liu (Sep 29 2025 at 10:02):
Matt Diamond said:
I don't understand what you're trying to model...
LeafOrRefseems identical toOption Nat, how do loops come into play here? How would a list ofOption Nathave or not have a loop?
You can interpret it as a directed graph on Fin l.length using Adj i j := l[i] = some j
Robert Maxton (Sep 30 2025 at 12:02):
mm, is the Matrix library still mostly noncomputable? I remember having trouble working with it back in January
If not, then since you basically have the adjacency matrix you can prove that a walk of length n exists between two vertices iff the nth power of the adjacency matrix has a nonzero entry at that point; so if there are V vertices, then the graph has a cycle iff the Vth power has any nonzero entries along its diagonal, which is certainly decidable.
Last updated: Dec 20 2025 at 21:32 UTC