Zulip Chat Archive
Stream: new members
Topic: Formalizing a proof in Lean, not sure how
Chris M (Jul 28 2020 at 10:32):
I have an informal proof and I'm curious how to formalize it in Lean. I'll strip away the details: Broadly the algorithm goes like this:
We have a tree (i.e. a DAG) G, where each node N has some data associated to it in the form of a function D:G.Nodes\to DataType1. We are also given a function F that takes a tree with data(G,D), and outputs another tree (G,D')where D' differs from D on the root node of the tree and its child nodes. We want to use F to recursively adjust the data in the tree.
I have an algorithm H, which recursively applies F, but it recurses "forwards" on the graph. This is pseudocode:
def H (G,D) := --informal definition
for each child node c of the root node of G,
Let (G_c, D_c) be the subgraph of G starting with c
Let (G_x, D'_c) be the result of H applied to (G_c, D_c)
Let (G,\tilde D') be the result of replacing the subgraphs (G_c, D_c) in (G, D) with (G_c, D'_c) for each c,
Let H(G,D) = (G,D') be the result of applying F to (G,\tilde D')
Intuitively, the algorithm first moves all the way to the leaves of the tree, applies F there, and then recurses backwards. We know that the graph is finite.
Here is what I don't know how to formalize: I want to use this algorithm in an existence proof (H shows that for any pair (G,D) there exists a pair (G,D') with certain properties that I won't go into), but this definition uses a form of recursion that is different from well-founded recursion e.g. on the natural numbers. In fact it seems to me like I can't even define H in lean without the meta keyword because it is not using well-founded recursion. Intuitively I can just say "H keeps calling a graph that is smaller than the original, and the size of graphs is bounded above 0, so it is well-defined", but I'm not sure how to formalize this.
I can think of two solutions:
- Drop the idea of actually defining
H, and just prove the result by mathematical induction on the depth of the graph. I'd prefer not to do this since I want to haveHas a computational object. - Define a function
H_nwhich takes a graph of depthn(or maximal depthn) and only haveH_{n+1}callH_norH_kwithk\leq n. Then defineHin terms ofH_nwherenis the size of the input graph. But this seems ugly to me. I'd rather just defineHdirectly for reasons of simplicity and clarity.
Eric Wieser (Jul 28 2020 at 10:35):
If you can express your recursion using a match statement, then you can always provide a using_well_founded definition explicitly - there are some recent discussions about this elsewhere on zulip
Reid Barton (Jul 28 2020 at 10:37):
It sounds like you just do structural recursion on trees though.
Reid Barton (Jul 28 2020 at 10:40):
I guess it might not be so simple depending on the representation of trees
Eric Wieser (Jul 28 2020 at 10:44):
Does the type of this declaration faithfully represent your problem?
inductive graph
| mk : list (graph) → graph
def H {α β : Type*} (G : graph) (D : graph → α) (F : graph → (graph → α) → (graph → β)) : graph → β := sorry
Chris M (Jul 28 2020 at 10:45):
Let me just copy it and put line breaks in:
inductive graph
| mk : list (graph) → graph
def H {α β : Type*} (G : graph) (D : graph → α)
(F : graph → (graph → α) → (graph → β))
: graph → β
:= sorry
Chris M (Jul 28 2020 at 10:52):
I have to think about it, but I have to leave now. I'll look at this again soon.
Eric Wieser (Jul 28 2020 at 10:56):
Actually it looks like alpha and beta have to be the same
Eric Wieser (Jul 28 2020 at 11:32):
To be clear, is this a DAG or a tree? I don't think your operation makes sense on a general DAG.
Alistair Tucker (Jul 28 2020 at 15:36):
This is how I have interpreted it:
mutual inductive tree, list_tree (α : Type)
with tree : Type
| node : α → list_tree → tree
with list_tree : Type
| nil {} : list_tree
| cons : tree → list_tree → list_tree
def F {α β : Type} : α → list_tree β → β := sorry
mutual def H, H_tilde {α β : Type}
with H : tree α → tree β
| (tree.node a l) := tree.node (F a (H_tilde l)) (H_tilde l)
with H_tilde : list_tree α → list_tree β
| list_tree.nil := list_tree.nil
| (list_tree.cons t ts) := list_tree.cons (H t) (H_tilde ts)
But I'm sure there's some much neater way to express it using the equation compiler and (as Eric Wieser suggests)
inductive tree (α : Type)
| mk : α → list tree → tree
I think the authoritative source on this stuff is TPIL chapters 7 and 8.
Alistair Tucker (Jul 29 2020 at 14:56):
@Chris M This seems to work too (at least it typechecks!) and perhaps it's closer to what you asked for.
inductive tree {α : Type} (β : α → Type)
| mk (a : α) (φ : β a → tree) : tree
variables {α : Type} {β : α → Type} {δ δ' : Type}
inductive node : tree β → Type
| root {G : tree β} : node G
| child {a : α} {φ : β a → tree β} (c : β a) (n : node (φ c)) : node ⟨a, φ⟩
def f : Π {a : α} {φ : β a → tree β} (D' : Π c, node (φ c) → δ'), δ → δ' :=
sorry
def F {a : α} {φ : β a → tree β} (D' : Π c, node (φ c) → δ') (d : δ) : node ⟨a, φ⟩ → δ'
| (node.root) := f D' d
| (node.child c n) := D' c n
def H : Π (G : tree β) (D : node G → δ), node G → δ'
| ⟨a, φ⟩ D := F (λ c, H (φ c) (λ n, D (node.child c n))) (D node.root)
Last updated: May 02 2025 at 03:31 UTC