Documentation

LeanPool.IsoGraph.Canon.Progress

Refinement splits cells, so the search terminates with a leaf in hand #

The fuel of the search is n + 1, and canonical falls back on the identity labelling if the search comes back empty-handed. This file rules that out.

The measure is the number of cells, numCells. Individualising a vertex of a non-singleton cell creates a new cell start, and refinement never merges cells — that is Refines, proved by following the cell starts through splitCell, splitCellsFrom, refineStep and refineLoop. So each level of the tree has at least one more cell than its parent (numCells_child), the tree has depth at most n, and the fuel is enough (dfsNode_best). The other thing needed is that the first child of a node is never orbit-pruned, which holds because the orbit mark starts out empty and is only refreshed when an automorphism has been found — which happens only at a leaf.

Refinement only splits cells #

def IsoGraph.Canon.Refines (n : ) (q p : Part) :

q refines p: every cell of p is a union of cells of q. Since a cell is determined by its start position (Part.WF.cst_eq_iff), this is the same as saying every cell start of p is still a cell start of q.

Equations
Instances For
    theorem IsoGraph.Canon.Refines.trans {n : } {p q r : Part} (h1 : Refines n q r) (h2 : Refines n p q) :
    Refines n p r
    theorem IsoGraph.Canon.splitCell_cst_self {n : } {cnt : Array } {c : } {st : SplitState} (hinv : SplitInv n cnt st) (hc : c < n) (hcst : st.cst[c]! = c) :
    (splitCell cnt c st).cst[c]! = c

    The cell being split keeps its start: the first fragment starts where the cell did.

    theorem IsoGraph.Canon.splitCell_refines {n : } {cnt : Array } {c : } {st : SplitState} (hinv : SplitInv n cnt st) (hc : c < n) (hcst : st.cst[c]! = c) :
    Refines n (splitCell cnt c st).part st.part

    Splitting one cell leaves every cell start in place.

    theorem IsoGraph.Canon.splitCellsFrom_refines {n : } {cnt cells : Array } (hnd : cells.toList.Nodup) (fuel j : ) (st : SplitState) :
    SplitInv n cnt st(∀ (j' : ), j j'j' < cells.sizecells[j']! < n st.cst[cells[j']!]! = cells[j']!)Refines n (splitCellsFrom cnt cells fuel j st).part st.part
    theorem IsoGraph.Canon.refineStep_refines {n : } {f : Bool} {p : Part} (hp : Part.WF n p) {s : } (hs : s < n) (hcst : p.cst[s]! = s) (inW : Array Bool) (tr : UInt64) :
    Refines n (refineStep (Graph.ofOracle n f) p inW s tr (Scratch.empty n)).1 p
    theorem IsoGraph.Canon.refineLoop_refines {n : } {f : Bool} (fuel : ) (p : Part) (inW : Array Bool) (tr : UInt64) :
    Part.WF n pRefines n (refineLoop (Graph.ofOracle n f) fuel p inW tr (Scratch.empty n)).1 p
    theorem IsoGraph.Canon.refine_refines {n : } {f : Bool} {p : Part} (hp : Part.WF n p) (inW : Array Bool) (tr : UInt64) :
    Refines n (refine (Graph.ofOracle n f) p inW tr).1 p

    Individualisation creates a cell, refinement keeps it #

    theorem IsoGraph.Canon.cenTargetFrom_big {n : } {c : Array } (fuel i j : ) :
    cenTargetFrom c n fuel i = some jc[j]! - j > 1
    theorem IsoGraph.Canon.targetCell_big {n : } {p : Part} {c : } (h : p.targetCell n = some c) :
    p.cen[c]! - c > 1

    The target cell has at least two vertices.

    theorem IsoGraph.Canon.individualize_refines {n : } {p : Part} {v : } (hp : Part.WF n p) (hv : v < n) :
    theorem IsoGraph.Canon.individualize_new_start {n : } {p : Part} {v : } (hp : Part.WF n p) (hv : v < n) (hbig : p.cst[p.pos[v]!]! + 1 < p.cen[p.pos[v]!]!) :

    Individualising v makes c + 1 a cell start, where c is the start of v's cell.

    theorem IsoGraph.Canon.child_refines {n : } {f : Bool} {p : Part} {v : } (hp : Part.WF n p) (hv : v < n) :
    Refines n (child (Graph.ofOracle n f) p v).1 p

    The number of cells, and why the search terminates in n levels #

    The number of cells of p inside {0, …, n-1}.

    Equations
    Instances For
      theorem IsoGraph.Canon.numCells_mono {n : } {p q : Part} (h : Refines n q p) :
      theorem IsoGraph.Canon.numCells_lt {n : } {p q : Part} (h : Refines n q p) {i0 : } (hi0 : i0 < n) (h1 : q.cst[i0]! = i0) (h2 : p.cst[i0]! i0) :
      theorem IsoGraph.Canon.numCells_child {n : } {f : Bool} {p : Part} {c v : } (hp : Part.WF n p) (htc : p.targetCell n = some c) (hv : v < n) (hcell : p.cst[p.pos[v]!]! = c) :
      numCells n p < numCells n (child (Graph.ofOracle n f) p v).1

      Each level of the search splits a cell. This is what bounds the depth of the tree by n and makes the fuel n + 1 enough to reach a leaf.

      The search always finishes with a leaf in hand #

      theorem IsoGraph.Canon.pruneNode_abortTo {invPath : Array UInt64} {st st' : St} (h : pruneNode invPath st = some st') :
      theorem IsoGraph.Canon.pruneNode_none_best {invPath : Array UInt64} {st : St} (h : pruneNode invPath st = none) :
      theorem IsoGraph.Canon.leafUpdate_best_isSome (G : Graph) (path : Array ) (invPath : Array UInt64) (lab : Array ) (st : St) :
      (leafUpdate G path invPath lab st).best.isSome = true
      theorem IsoGraph.Canon.orbRefresh_eq {G : Graph} {path processed : Array } {orb : Orbits} {st : St} (h : orb.nGens = st.autos.size) :
      orbRefresh G path processed orb st = orb
      theorem IsoGraph.Canon.dfsNode_best (n : ) (f : Bool) (fuel : ) (path : Array ) (invPath : Array UInt64) (p : Part) (st : St) :
      Part.WF n pn + 1 numCells n p + fuel(st.abortTo.isSome = truest.best.isSome = true)(dfsNode (Graph.ofOracle n f) fuel path invPath p st).best.isSome = true

      The search always ends holding a leaf. The fuel n + 1 is enough because every level of the tree splits a cell, so a node at depth d has at least d + 1 cells and the tree has depth at most n - 1; and the first child of a node is never orbit-pruned, so the descent always reaches a leaf, which leafUpdate stores.

      The search always returns a genuine leaf: canonical never falls back on the identity labelling.