Lax11.GraphEncoding
Compressed sparse row encoding of a graph
concepts/Lax11/GraphEncoding.lean · lax-11
No public endorsements yet.
Loading review…
Sign in with ORCIDConcept map
Definition
A graph is handed to a word random access machine as a word of numbers in compressed sparse row form, the adjacency-array format every textbook assumes: the number n of vertices, the number m of edges, then n+1 offsets, then the target array. The target array lists, for each vertex in turn, the neighbors of that vertex; the offsets say where each vertex's block of neighbors begins, the first offset being 0 and the last one the length of the target array. So the neighbors of vertex u are the entries of the target array at the positions from the u-th offset up to, but excluding, the (u+1)-st.
Lean source view on GitHub
| 1 | import Mathlib.Combinatorics.SimpleGraph.Basic |
| 2 | |
| 3 | /-! |
| 4 | --- |
| 5 | title: Compressed sparse row encoding of a graph |
| 6 | type: definition |
| 7 | --- |
| 8 | A graph is handed to a word random access machine as a word of numbers |
| 9 | in compressed sparse row form, the adjacency-array format every |
| 10 | textbook assumes: the number *n* of vertices, the number *m* of edges, then |
| 11 | *n+1* offsets, then the target array. The target array lists, for each |
| 12 | vertex in turn, the neighbors of that vertex; the offsets say where |
| 13 | each vertex's block of neighbors begins, the first offset being 0 and |
| 14 | the last one the length of the target array. So the neighbors of vertex |
| 15 | *u* are the entries of the target array at the positions from the *u*-th |
| 16 | offset up to, but excluding, the *(u+1)*-st. |
| 17 | |
| 18 | # Formalization notes |
| 19 | |
| 20 | The encoding is deliberately dumb: it is exactly the input format, |
| 21 | with nothing precomputed that an algorithm would otherwise have to |
| 22 | compute. The word is required to have the right length, the offsets to |
| 23 | be nondecreasing with the right two endpoints, and every target entry |
| 24 | to be a vertex; beyond that the only condition is that each vertex's |
| 25 | block lists exactly its neighbors. In particular the blocks are not |
| 26 | required to be sorted and repetitions are not forbidden — the fewer |
| 27 | conditions the encoding imposes, the more inputs a claim about |
| 28 | programs reading it has to handle, so leaving them out strengthens |
| 29 | every such claim rather than weakening it. For the same reason `m` is |
| 30 | only the declared length of the target array (which is `2m`): it is at |
| 31 | least the number of edges, and equals it exactly when no block repeats |
| 32 | a neighbor. Nothing forces each edge to be listed from both of its |
| 33 | endpoints either — that is automatic, since adjacency in a simple |
| 34 | graph is symmetric. |
| 35 | |
| 36 | Cells are read with `List.getD`, which returns `0` outside the word; |
| 37 | the length condition pins down the word completely, so this default |
| 38 | value is never reached at any position the other conditions constrain. |
| 39 | The conditions are bundled as a structure so that each one is a named |
| 40 | obligation a reader can check off separately. |
| 41 | |
| 42 | Nothing here mentions the word length of the machine the encoding is |
| 43 | handed to. It does not have to: every entry of an encoding is a vertex |
| 44 | number, an offset into the target array, or one of the two header |
| 45 | numbers, and each of those is smaller than the length of the word |
| 46 | itself, so an encoding that a machine can address at all is one whose |
| 47 | entries fit into that machine's words. The condition that it does fit |
| 48 | belongs to the claims made about programs reading the encoding, and is |
| 49 | stated there, once, as an explicit inequality against `2 ^ w`. |
| 50 | -/ |
| 51 | |
| 52 | namespace Lax11.GraphEncoding |
| 53 | |
| 54 | /-- The number of vertices declared by a word: its first entry. -/ |
| 55 | def vertexCount (x : List ℕ) : ℕ := x.getD 0 0 |
| 56 | |
| 57 | /-- The number of edges declared by a word: its second entry. -/ |
| 58 | def edgeCount (x : List ℕ) : ℕ := x.getD 1 0 |
| 59 | |
| 60 | /-- The `i`-th offset of a word: the `n+1` offsets follow the two |
| 61 | header entries. -/ |
| 62 | def offset (x : List ℕ) (i : ℕ) : ℕ := x.getD (2 + i) 0 |
| 63 | |
| 64 | /-- The `j`-th entry of the target array of a word, which follows the |
| 65 | header and the offsets. -/ |
| 66 | def target (x : List ℕ) (j : ℕ) : ℕ := x.getD (3 + vertexCount x + j) 0 |
| 67 | |
| 68 | /-- The word `x` is a compressed sparse row encoding of the graph `G` |
| 69 | on `n` vertices. -/ |
| 70 | structure EncodesGraph (x : List ℕ) (n : ℕ) (G : SimpleGraph (Fin n)) : |
| 71 | Prop where |
| 72 | /-- The word declares `n` vertices. -/ |
| 73 | vertexCount_eq : vertexCount x = n |
| 74 | /-- The word consists of the two header entries, the `n+1` offsets, |
| 75 | and a target array of length twice the declared number of edges. -/ |
| 76 | length_eq : x.length = 3 + n + 2 * edgeCount x |
| 77 | /-- The block of the first vertex begins at the start of the target |
| 78 | array. -/ |
| 79 | offset_zero : offset x 0 = 0 |
| 80 | /-- The block of the last vertex ends at the end of the target |
| 81 | array. -/ |
| 82 | offset_last : offset x n = 2 * edgeCount x |
| 83 | /-- The offsets are nondecreasing, so they cut the target array into |
| 84 | one block per vertex. -/ |
| 85 | offset_mono : ∀ i < n, offset x i ≤ offset x (i + 1) |
| 86 | /-- Every entry of the target array is a vertex. -/ |
| 87 | target_lt : ∀ j < 2 * edgeCount x, target x j < n |
| 88 | /-- The block of a vertex lists exactly its neighbors. -/ |
| 89 | adj_iff : ∀ u v : Fin n, G.Adj u v ↔ |
| 90 | ∃ j, offset x u ≤ j ∧ j < offset x (u + 1) ∧ target x j = v |
| 91 | |
| 92 | end Lax11.GraphEncoding |
| 93 |
Formalization notes
The encoding is deliberately dumb: it is exactly the input format, with nothing precomputed that an algorithm would otherwise have to compute. The word is required to have the right length, the offsets to be nondecreasing with the right two endpoints, and every target entry to be a vertex; beyond that the only condition is that each vertex's block lists exactly its neighbors. In particular the blocks are not required to be sorted and repetitions are not forbidden — the fewer conditions the encoding imposes, the more inputs a claim about programs reading it has to handle, so leaving them out strengthens every such claim rather than weakening it. For the same reason is only the declared length of the target array (which is ): it is at least the number of edges, and equals it exactly when no block repeats a neighbor. Nothing forces each edge to be listed from both of its endpoints either — that is automatic, since adjacency in a simple graph is symmetric.
Cells are read with , which returns outside the word; the length condition pins down the word completely, so this default value is never reached at any position the other conditions constrain. The conditions are bundled as a structure so that each one is a named obligation a reader can check off separately.
Nothing here mentions the word length of the machine the encoding is handed to. It does not have to: every entry of an encoding is a vertex number, an offset into the target array, or one of the two header numbers, and each of those is smaller than the length of the word itself, so an encoding that a machine can address at all is one whose entries fit into that machine's words. The condition that it does fit belongs to the claims made about programs reading the encoding, and is stated there, once, as an explicit inequality against .
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