Documentation

PossiblyInfiniteTrees.PossiblyInfiniteTree.FiniteDegreeTree.Basic

FiniteDegreeTree #

This file defines a FiniteDegreeTree which is a PossiblyInfiniteTree where each node has only finitely many children. The offered functions are similar to the ones of PossiblyInfiniteTree. The tree can now only infinite in one dimensions, i.e. it can have infinite depth but each node has only finitely many children by definition. However, note that there is no (global) bound for the number of children. The number of children might grow arbitrarily along the tree as long as it is finite everywhere.

structure FiniteDegreeTree (α : Type u) :

A FiniteDegreeTree is a PossiblyInfiniteTree with the finitely_many_children property.

Instances For

    Basics #

    The essential functions on infinite trees, mainly get, drop, and root. The childTrees function is defined separately here as for the PossiblyInfiniteTree.

    def FiniteDegreeTree.get? {α : Type u_1} (t : FiniteDegreeTree α) (ns : List Nat) :

    Obtains the element of the tree at the given address.

    Equations
    Instances For

      Obtains the subtree at the given address (by dropping everything else).

      Equations
      Instances For

        Get the element at the root of the tree (i.e. at the empty address).

        Equations
        Instances For
          Equations
          theorem FiniteDegreeTree.mem_iff {α : Type u_1} {t : FiniteDegreeTree α} {e : α} :
          e t (ns : List Nat), t.get? ns = some e

          An element is a member of the tree iff it occurs at some address.

          theorem FiniteDegreeTree.ext {α : Type u_1} {t1 t2 : FiniteDegreeTree α} :
          (∀ (ns : List Nat), t1.get? ns = t2.get? ns)t1 = t2

          Two FiniteDegreeTrees are the same if they agree on all addresses.

          theorem FiniteDegreeTree.ext_iff {α : Type u_1} {t1 t2 : FiniteDegreeTree α} :
          t1 = t2 ∀ (ns : List Nat), t1.get? ns = t2.get? ns
          theorem FiniteDegreeTree.get?_drop {α : Type u_1} {t : FiniteDegreeTree α} {ns ns' : List Nat} :
          (t.drop ns).get? ns' = t.get? (ns ++ ns')

          Get after drop can be rewritten into get.

          theorem FiniteDegreeTree.drop_nil {α : Type u_1} {t : FiniteDegreeTree α} :
          t.drop [] = t

          Dropping the empty address changes nothing.

          theorem FiniteDegreeTree.drop_drop {α : Type u_1} {t : FiniteDegreeTree α} {ns ns' : List Nat} :
          (t.drop ns).drop ns' = t.drop (ns ++ ns')

          Two calls to drop can be combined.

          theorem FiniteDegreeTree.root_eq {α : Type u_1} {t : FiniteDegreeTree α} :

          The root is equal to getting the empty address.

          theorem FiniteDegreeTree.root_mem {α : Type u_1} {t : FiniteDegreeTree α} (r : α) :
          r t.rootr t

          The root is in the tree.

          theorem FiniteDegreeTree.root_drop {α : Type u_1} {t : FiniteDegreeTree α} {ns : List Nat} :
          (t.drop ns).root = t.get? ns

          The root of the dropped tree at address ns is exactly the element at address ns.

          Empty Infinite Trees #

          The empty FiniteDegreeTree is simply the FiniteDegreeTree that is none on all addresses. That is, its underlying PossiblyInfiniteTree is PossiblyInfiniteTree.empty.

          The empty FiniteDegreeTree is essentially PossiblyInfiniteTree.empty.

          Equations
          Instances For
            theorem FiniteDegreeTree.get?_empty {α : Type u_1} {n : List Nat} :

            Getting from the empty tree always returns none.

            theorem FiniteDegreeTree.drop_empty {α : Type u_1} {ns : List Nat} :

            Dropping from the empty tree again yields the empty tree.

            The root of the empty tree is none.

            A tree is empty if and only if the root is none.

            Child Trees #

            Defining the childTrees function requires a bit of machinery. We only want to return the child trees that are not already empty. Then all returned trees have a non-none root, which we aim to capture directly in the return type.

            @[reducible, inline]

            The FiniteDegreeTreeWithRoot is a FiniteDegreeTree where the root is not none.

            Equations
            Instances For

              FiniteDegreeTreeWithRoot #

              For the FiniteDegreeTreeWithRoot we mainly provide some functions to convert FiniteDegreeTree to and from Option FiniteDegreeTreeWithRoot. Clearly, if a FiniteDegreeTree has a non-none root, we can convert it directly into a FiniteDegreeTreeWithRoot, otherwise, we simply convert it to none. Also in the other direction, none can just be converted to PossiblyInfiniteTree.empty and any FiniteDegreeTreeWithRoot is already a FiniteDegreeTree.

              So far this is similar to PossiblyInfiniteTreeWithRoot and mainly implemented using its existing methods. For this purpose, we also provide methods from_possibly_infinite and to_possibly_infinite that allow to convert between FiniteDegreeTreeWithRoot and PossiblyInfiniteTreeWithRoot.

              Equations
              • One or more equations did not get rendered due to their size.
              Instances For
                Equations
                • One or more equations did not get rendered due to their size.
                Instances For

                  The actual childTrees definition #

                  With FiniteDegreeTreeWithRoot in place, we can now define the actual childTrees function.

                  The childTrees of a FiniteDegreeTree are the List of all child trees that are not empty, i.e. it only consists of FiniteDegreeTreeWithRoot. Note that we can indeed build a finite List since we have finitely_many_children in place.

                  Equations
                  • One or more equations did not get rendered due to their size.
                  Instances For

                    Getting a child tree is the same as dropping the corresponding singleton address.

                    theorem FiniteDegreeTree.get_childTrees {α : Type u_1} {t : FiniteDegreeTree α} (n : Nat) (lt : n < t.childTrees.length) :

                    Getting a child tree is the same as dropping the corresponding singleton address.

                    Getting at an address in a child tree can be combined into a single get call.

                    The childTrees of the empty tree are exactly [].

                    Node Constructor #

                    Similar to the PossiblyInfiniteTree, we can also define a node constructor on the FiniteDegreeTree.

                    def FiniteDegreeTree.node {α : Type u_1} (root : α) (childTrees : List (FiniteDegreeTreeWithRoot α)) :

                    Construct a FiniteDegreeTree from a List of FiniteDegreeTreeWithRoot and a new root element.

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For
                      theorem FiniteDegreeTree.get?_node_nil {α : Type u_1} {root : α} {childTrees : List (FiniteDegreeTreeWithRoot α)} :
                      (node root childTrees).get? [] = some root

                      Getting the element at address [] on node is the new root.

                      theorem FiniteDegreeTree.get?_node_cons {α : Type u_1} {root : α} {childTrees : List (FiniteDegreeTreeWithRoot α)} (n : Nat) (ns : List Nat) :
                      (node root childTrees).get? (n :: ns) = (FiniteDegreeTreeWithRoot.opt_to_tree childTrees[n]?).get? ns

                      Getting any address != [] on node yields the respective element from the previous FiniteDegreeTreeWithRoot.

                      theorem FiniteDegreeTree.drop_node_cons {α : Type u_1} {root : α} {childTrees : List (FiniteDegreeTreeWithRoot α)} {n : Nat} {ns : List Nat} :
                      (node root childTrees).drop (n :: ns) = (FiniteDegreeTreeWithRoot.opt_to_tree childTrees[n]?).drop ns

                      Dropping from node with an address of the form n::ns is the same as getting the n child from the child trees used in the construction and then dropping ns there.

                      theorem FiniteDegreeTree.root_node {α : Type u_1} {root : α} {childTrees : List (FiniteDegreeTreeWithRoot α)} :
                      (node root childTrees).root = some root

                      The root of node is the root used in the construction.

                      theorem FiniteDegreeTree.childTrees_node {α : Type u_1} {root : α} {childTrees : List (FiniteDegreeTreeWithRoot α)} :
                      (node root childTrees).childTrees = childTrees

                      The childTrees of node are the childTrees used in the construction.

                      theorem FiniteDegreeTree.node_root_childTrees {α : Type u_1} {t : FiniteDegreeTree α} {root : α} (h : t.root = some root) :
                      t = node root t.childTrees

                      Any FiniteDegreeTree where the root is not none can be written using the node constructor.

                      ChildNodes #

                      It can be convenient to obtain a list of the immediate child nodes of a given tree. This is equivalent to mapping each child tree to its root.

                      Getting the nth childNodes is the root of the nth childTrees.

                      theorem FiniteDegreeTree.mem_of_mem_childNodes {α : Type u_1} {t : FiniteDegreeTree α} (c : α) :
                      c t.childNodesc t

                      Each child node is a tree member.

                      Suffixes #

                      Here, we define a suffix relation on FiniteDegreeTree inspired by List.IsSuffix. For t1 and t2, t1 <:+ t2 denotes that t1 is a subtree of t2.

                      The suffix relation is reflexive and transitive but not necesarrily antisymmetric! Note also that InfiniteList.suffix_or_suffix_of_suffix has no equivalent statement here, i.e. just because two trees are subtrees of the same parent tree, we cannot say anything about their relation to one another. They might be totally "disconnected".

                      A suffix relation on infinite trees. This is inspired by List.IsSuffix. Read t1 <:+ t2 as: t1 is a subtree of t2.

                      Equations
                      Instances For
                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For
                          theorem FiniteDegreeTree.IsSuffix_iff {α : Type u_1} {t1 t2 : FiniteDegreeTree α} :
                          t1 <:+ t2 (ns : List Nat), t2.drop ns = t1

                          t1 is a subtree of t2 if t1 can be obtained from t2 by dropping.

                          theorem FiniteDegreeTree.IsSuffix_refl {α : Type u_1} {t : FiniteDegreeTree α} :
                          t <:+ t

                          The suffix relation is reflexive.

                          theorem FiniteDegreeTree.IsSuffix_trans {α : Type u_1} {t1 t2 t3 : FiniteDegreeTree α} :
                          t1 <:+ t2t2 <:+ t3t1 <:+ t3

                          The suffix relation is transitive.

                          theorem FiniteDegreeTree.mem_of_mem_suffix {α : Type u_1} {t1 t2 : FiniteDegreeTree α} (suffix : t1 <:+ t2) (e : α) :
                          e t1e t2

                          A member of a subtree is also a member of the current tree.

                          theorem FiniteDegreeTree.IsSuffix_drop {α : Type u_1} {t : FiniteDegreeTree α} (ns : List Nat) :
                          t.drop ns <:+ t

                          Dropping elements yields a subtree.

                          Each child tree is a subtree.

                          Every suffix of the empty tree is empty.

                          theorem FiniteDegreeTree.no_orphans {α : Type u_1} {t : FiniteDegreeTree α} (subtree : FiniteDegreeTree α) :
                          subtree <:+ tsubtree.root = nonesubtree.childNodes = []

                          We can express the PossiblyInfiniteTree.no_orphans' condition directly on FiniteDegreeTree.

                          Recursor for Members #

                          We define a recursion (induction) principle for members (Elements) of an FiniteDegreeTree called mem_rec. This can be used with the induction tactic to prove a property for each Element of an FiniteDegreeTree. Note that for using this coveniently, the goal needs to expressed (rewritten) using an Element.

                          def FiniteDegreeTree.Element {α : Type u_1} (t : FiniteDegreeTree α) :
                          Type u_1

                          A tree Element is a Subtype featuring a proof of being a tree member.

                          Equations
                          Instances For
                            theorem FiniteDegreeTree.mem_rec {α : Type u_1} {t : FiniteDegreeTree α} {motive : t.ElementProp} (root : ∀ (r : α) (mem : r t.root), motive r, ) (step : ∀ (t2 : FiniteDegreeTree α) (suffix : t2 <:+ t), ( (r : α), (mem : r t2.root), motive r, ) → ∀ {c : α} (mem : c t2.childNodes), motive c, ) (a : t.Element) :
                            motive a

                            A recursor for proving properties about tree members (Elements) via induction.

                            Branches #

                            Branches are essentially PossiblyInfiniteLists in a FiniteDegreeTree and can be characterizes by an infinite "address", i.e. InfiniteList Nat. Here, we merely define them as the branches of the underlying PossiblyInfiniteTree.

                            The branches in the FiniteDegreeTree are exactly the branches in the underlying PossiblyInfiniteTree.

                            Equations
                            Instances For

                              The set of branches can equivalently be expressed as the set of all PossiblyInfiniteLists where the head equals the root of the tree and the tail occurs in the branches of some childTree. If there are no childTrees, then the tail needs to be empty.

                              Branch Generation #

                              We can use PossiblyInfiniteList.generate to construct branches in a FiniteDegreeTree. Here, we just proxy the existing function PossiblyInfiniteTree.generate_branch. First of all, this requires that the mapper function produces a FiniteDegreeTreeWithRoot. Intuitively, using all the roots of these trees gives us a branch. But this is only true if the generate trees are always child trees of each other and the generation indeed creates a maximal branch, meaning that once the generation stops, there would indeed by no childTrees to continue. This condition is used in the generate_branch_mem_branches theorem to proof that a PossiblyInfiniteList from the generate_branch function is indeed in branches.

                              def FiniteDegreeTree.generate_branch {β : Type u_1} {α : Type u_2} (start : Option β) (generator : βOption β) (mapper : βFiniteDegreeTreeWithRoot α) :

                              Given a generator and a mapper that maps generated elements to FiniteDegreeTreeWithRoot, construct an PossiblyInfiniteList with the goal of constructing a branch in a tree.

                              Equations
                              Instances For
                                theorem FiniteDegreeTree.generate_branch_mem_branches {β : Type u_1} {α : Type u_2} {start : Option β} {generator : βOption β} {mapper : βFiniteDegreeTreeWithRoot α} (next_is_child : ∀ (b b' : β), b' generator bmapper b' (mapper b).val.childTrees) (maximal : ∀ (b : β), generator b = none(mapper b).val.childTrees = []) (isSome_start : start.isSome = true) :
                                generate_branch start generator mapper (mapper (start.get isSome_start)).val.branches

                                If the generated trees are childTrees of each other and the generated branch is maximal, i.e. the generation only stops if there are no childTrees available anymore, then the generated PossiblyInfiniteList is indeed a branch.

                                theorem FiniteDegreeTree.head_generate_branch {β : Type u_1} {α : Type u_2} {start : Option β} {generator : βOption β} {mapper : βFiniteDegreeTreeWithRoot α} :
                                (generate_branch start generator mapper).head = Option.map (fun (s : β) => (mapper s).val.root.get ) start

                                The PossiblyInfiniteList.head of generate_branch is the root of the first tree.

                                theorem FiniteDegreeTree.get?_generate_branch {β : Type u_1} {α : Type u_2} {start : Option β} {generator : βOption β} {mapper : βFiniteDegreeTreeWithRoot α} (n : Nat) :
                                (generate_branch start generator mapper).get? n = Option.map (fun (t : FiniteDegreeTreeWithRoot α) => t.val.root.get ) ((PossiblyInfiniteList.generate start generator mapper).get? n)

                                Getting the nth element from a generate_branch result is the root of the nth generated tree.

                                theorem FiniteDegreeTree.tail_generate_branch {β : Type u_1} {α : Type u_2} {start : Option β} {generator : βOption β} {mapper : βFiniteDegreeTreeWithRoot α} :
                                (generate_branch start generator mapper).tail = generate_branch (start.bind generator) generator mapper

                                The PossiblyInfiniteList.tail of generate_branch is the branch generated when applying the generator function once on the starting element before the actual generation.

                                Leaves #

                                The leaves of a FiniteDegreeTree is the set of elements that occur in a node that has no childNodes. The function is simply defined via PossiblyInfiniteTree.leaves for the underlying tree.

                                def FiniteDegreeTree.leaves {α : Type u_1} (t : FiniteDegreeTree α) :
                                Set α
                                Equations
                                Instances For

                                  Constructing a Tree from a Branch #

                                  A PossiblyInfiniteList directly corresponds to the FiniteDegreeTree where the list is the "first" branch (with the address that only consists of zeros) and all other nodes are none.

                                  Equations
                                  Instances For