Lax11.InstanceEncoding
Encoding a graph together with a k-expression
concepts/Lax11/InstanceEncoding.lean · lax-11
No public endorsements yet.
Loading review…
Sign in with ORCIDConcept map
Definition
An algorithm on graphs of bounded cliquewidth is handed a graph together with a k-expression for it, and this is the word that presents the pair. It has two blocks. The first is the graph itself, in the compressed sparse row form used everywhere in this submission. The second is the k-expression, as a tree: the number of nodes, then one number per node giving that node's parent, then one per node giving the operation performed there, then one per node giving the vertex name it creates. Children are numbered before their parents and the root is the last node.
Lean source view on GitHub
| 1 | import Lax11.GraphEncoding |
| 2 | import Lax11.CliqueExpr |
| 3 | |
| 4 | /-! |
| 5 | --- |
| 6 | title: Encoding a graph together with a k-expression |
| 7 | type: definition |
| 8 | --- |
| 9 | An algorithm on graphs of bounded cliquewidth is handed a graph |
| 10 | together with a *k*-expression for it, and this is the word that |
| 11 | presents the pair. It has two blocks. The first is the graph itself, in |
| 12 | the compressed sparse row form used everywhere in this submission. The |
| 13 | second is the *k*-expression, as a tree: the number of nodes, then one |
| 14 | number per node giving that node's parent, then one per node giving the |
| 15 | operation performed there, then one per node giving the vertex name it |
| 16 | creates. Children are numbered before their parents and the root is the |
| 17 | last node. |
| 18 | |
| 19 | # Formalization notes |
| 20 | |
| 21 | The expression block is a certificate, and it is required to be a |
| 22 | correct one: the encoding says that the arrays describe some valid |
| 23 | expression that evaluates to exactly the graph in the first block. |
| 24 | Since the two blocks describe the same graph, a statement is not |
| 25 | weakened by carrying both — the first block is what makes a sentence |
| 26 | `Sat G φ` refer to a graph, and the second is what makes the input |
| 27 | admissible. The arrays determine the expression completely, the vertex |
| 28 | names included, so the existential quantifier over expressions ranges |
| 29 | over at most one thing: the certificate is data in the word, not a |
| 30 | choice made about it. |
| 31 | |
| 32 | The vertex-name array is part of the input even though a program |
| 33 | evaluating the expression never reads it. Without it the second block |
| 34 | would not be a *k*-expression, only the shape of one, and a reader |
| 35 | could not check against the first block what the certificate claims. |
| 36 | Its presence costs nothing: it is one number per node, so it only |
| 37 | lengthens the input, and a linear bound is linear in that length. |
| 38 | |
| 39 | Children are numbered before their parents, so the root is the last |
| 40 | node. That is what lets a machine evaluate the expression in a single |
| 41 | left-to-right sweep, with no recursion, no stack and no second pass, |
| 42 | and it is where a linear bound comes from. It restricts the *encoding*, |
| 43 | not the class of graphs: every rooted tree admits such a numbering — |
| 44 | any postorder is one — and one can be produced from an arbitrary |
| 45 | numbering by a sort, which this format simply asks the writer of the |
| 46 | input to have done. An encoding that accepted arbitrary parent arrays |
| 47 | would describe the same graphs at the price of a renumbering pass |
| 48 | inside the program. |
| 49 | |
| 50 | Cells are read with `List.getD`, which returns `0` outside the word, |
| 51 | exactly as in the compressed sparse row encoding; the length condition |
| 52 | pins the expression block down completely, so the default value is |
| 53 | never reached at a position the other conditions constrain. |
| 54 | -/ |
| 55 | |
| 56 | namespace Lax11.InstanceEncoding |
| 57 | |
| 58 | open Lax11.GraphEncoding Lax11.CliqueExpr |
| 59 | |
| 60 | /-- The number of nodes declared by an expression block: its first |
| 61 | entry. -/ |
| 62 | def nodeCount (t : List ℕ) : ℕ := t.getD 0 0 |
| 63 | |
| 64 | /-- The parent of node `i`: the parents follow the header entry. -/ |
| 65 | def parent (t : List ℕ) (i : ℕ) : ℕ := t.getD (1 + i) 0 |
| 66 | |
| 67 | /-- The operation performed at node `i`, as a number: the operations |
| 68 | follow the parents. -/ |
| 69 | def opCode (t : List ℕ) (i : ℕ) : ℕ := t.getD (1 + nodeCount t + i) 0 |
| 70 | |
| 71 | /-- The vertex name created at node `i`, meaningful when the operation |
| 72 | there creates a vertex: the names follow the operations. -/ |
| 73 | def vertexName (t : List ℕ) (i : ℕ) : ℕ := t.getD (1 + 2 * nodeCount t + i) 0 |
| 74 | |
| 75 | /-- The children of node `i`: the earlier nodes whose parent is `i`, |
| 76 | in increasing order. -/ |
| 77 | def children (par : ℕ → ℕ) (i : ℕ) : List ℕ := |
| 78 | (List.range i).filter (fun c => par c == i) |
| 79 | |
| 80 | /-- The word `t` is an expression block for a tree whose operations are |
| 81 | operations of `k`-expressions. The number of nodes is the block's own |
| 82 | first entry, so it is read off `t` rather than quantified over. -/ |
| 83 | structure EncodesExprTree (t : List ℕ) (k : ℕ) : Prop where |
| 84 | /-- There is at least one node: an expression has a root. -/ |
| 85 | pos : 1 ≤ nodeCount t |
| 86 | /-- The block consists of the header entry and three arrays of one |
| 87 | number per node. -/ |
| 88 | length_eq : t.length = 1 + 3 * nodeCount t |
| 89 | /-- Every node but the last has a parent, which is a later node — so |
| 90 | children are numbered before their parents and the root is the last |
| 91 | node. -/ |
| 92 | parent_gt : ∀ i, i + 1 < nodeCount t → i < parent t i ∧ parent t i < nodeCount t |
| 93 | /-- Every operation entry is the number of an operation. -/ |
| 94 | opCode_lt : ∀ i < nodeCount t, opCode t i < opCard k |
| 95 | |
| 96 | /-- The node `i` of the tree given by the three arrays is the |
| 97 | expression `e`: the operation number at `i` is the one of `e`'s |
| 98 | outermost operation, the vertex name at `i` is the one `e` creates if |
| 99 | `e` is a leaf, and the children of `i` — which are listed in increasing |
| 100 | order — are the nodes of `e`'s immediate subexpressions, the left one |
| 101 | first. -/ |
| 102 | def EncodesExpr {n k : ℕ} (par lab ids : ℕ → ℕ) : ℕ → Expr n k → Prop |
| 103 | | i, .leaf v l => children par i = [] ∧ lab i = (Op.leaf l).code ∧ ids i = (v : ℕ) |
| 104 | | i, .union e₁ e₂ => ∃ c₁ c₂, children par i = [c₁, c₂] ∧ |
| 105 | lab i = (Op.union : Op k).code ∧ |
| 106 | EncodesExpr par lab ids c₁ e₁ ∧ EncodesExpr par lab ids c₂ e₂ |
| 107 | | i, .addEdges a b e => ∃ c, children par i = [c] ∧ |
| 108 | lab i = (Op.eta a b).code ∧ EncodesExpr par lab ids c e |
| 109 | | i, .relabel a b e => ∃ c, children par i = [c] ∧ |
| 110 | lab i = (Op.rho a b).code ∧ EncodesExpr par lab ids c e |
| 111 | |
| 112 | /-- The word `x` presents the graph `G` on `n` vertices together with a |
| 113 | `k`-expression for it: a compressed sparse row block encoding `G`, |
| 114 | followed by an expression block whose arrays describe a valid |
| 115 | `k`-expression, rooted at its last node, that evaluates to `G`. This is |
| 116 | the instance format of a model checking problem on graphs of bounded |
| 117 | cliquewidth. -/ |
| 118 | def EncodesModelCheckingInstance (x : List ℕ) (n : ℕ) (G : SimpleGraph (Fin n)) |
| 119 | (k : ℕ) : Prop := |
| 120 | ∃ (g t : List ℕ) (e : Expr n k), |
| 121 | x = g ++ t ∧ EncodesGraph g n G ∧ EncodesExprTree t k ∧ |
| 122 | EncodesExpr (parent t) (opCode t) (vertexName t) (nodeCount t - 1) e ∧ ValidFor e G |
| 123 | |
| 124 | end Lax11.InstanceEncoding |
| 125 |
Formalization notes
The expression block is a certificate, and it is required to be a correct one: the encoding says that the arrays describe some valid expression that evaluates to exactly the graph in the first block. Since the two blocks describe the same graph, a statement is not weakened by carrying both — the first block is what makes a sentence refer to a graph, and the second is what makes the input admissible. The arrays determine the expression completely, the vertex names included, so the existential quantifier over expressions ranges over at most one thing: the certificate is data in the word, not a choice made about it.
The vertex-name array is part of the input even though a program evaluating the expression never reads it. Without it the second block would not be a k-expression, only the shape of one, and a reader could not check against the first block what the certificate claims. Its presence costs nothing: it is one number per node, so it only lengthens the input, and a linear bound is linear in that length.
Children are numbered before their parents, so the root is the last node. That is what lets a machine evaluate the expression in a single left-to-right sweep, with no recursion, no stack and no second pass, and it is where a linear bound comes from. It restricts the encoding, not the class of graphs: every rooted tree admits such a numbering — any postorder is one — and one can be produced from an arbitrary numbering by a sort, which this format simply asks the writer of the input to have done. An encoding that accepted arbitrary parent arrays would describe the same graphs at the price of a renumbering pass inside the program.
Cells are read with , which returns outside the word, exactly as in the compressed sparse row encoding; the length condition pins the expression block down completely, so the default value is never reached at a position the other conditions constrain.
Community review
Discussion
Ask a question or add context. Endorsements and structured flags are kept in the review panel above; your ORCID profile must share a public name.
0 comments