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 haveH
as a computational object. - Define a function
H_n
which takes a graph of depthn
(or maximal depthn
) and only haveH_{n+1}
callH_n
orH_k
withk\leq n
. Then defineH
in terms ofH_n
wheren
is the size of the input graph. But this seems ugly to me. I'd rather just defineH
directly 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: Dec 20 2023 at 11:08 UTC