Directed Graphs #
Small directed graph module providing the core graph-theoretic mechanisms used by the generalization linter.
Main definitions
Digraph: Implemented as an adjacency list.Condensation: Condensation of digraph into DAG of SCCs.minCommonAncestors: Find the minimal common ancestors.
References
- Alex J. Best. 2023. Automatically Generalizing Theorems Using Typeclasses. In Fifth Workshop on Formal Mathematics for Mathematicians. CEUR Workshop Proceedings. Retrieved from https://ceur-ws.org/Vol-3377/fmm12.pdf.
- David J. King and John Launchbury. 1995. Structuring depth-first search algorithms in Haskell. In Proceedings of the 22nd ACM SIGPLAN-SIGACT symposium on Principles of programming languages - POPL ’95, 1995. ACM Press, San Francisco, California, United States, 344–354. https://doi.org/10.1145/199448.199530
Directed graph with vertices of type V, implemented through an adjacency list
adj.
- adj : Std.HashMap V (Array V)
Adjacency list of the directed graph.
Given a vertex
a,adj[a]is the array of immediate successors ofa.Default:
{}
Example
1 → 2 ─┐ ↓ ↑ │ 5 3 4 ←┘For the graph shown above (where the
5is an isolated vertex), we'd have:adj[1] = #[2, 3]adj[2] = #[4]adj[3] = #[]adj[4] = #[2]adj[5] = #[]
Implementation Notes
Storing the successors as an
Arrayinstead of aListis more efficient for our purposes. Conceptually, either one would work.
Instances For
Equations
Inserts edge s → t into G, and returns updated G. (s stands for "source
vertex" and t stands for "target vertex" of the edge to be inserted.)
Examples
-- `G := ({} : Digraph Nat).insertEdge 1 2`
G.vertices = #[1, 2]
G.succs 1 = #[2]
(G.insertEdge 1 2).succs 1 = #[2]
Equations
- One or more equations did not get rendered due to their size.
Instances For
Reachability #
Depth-first search to compute transitive closure of v under G.
accis an accumulator: the returned array isaccwith the vertices newly visited by this search pushed onto it, in DFS postorder (so, ifGis acyclic, in reverse topological order). The newly visited vertices are those reachable fromvalong a path that avoidsvis(anything lying only behind an already-visited vertex is skipped). (sccsrelies on exactly this, threadingvisacross its calls so that each call returns one strongly-connected component.)visis the set of visited vertices. The returned set isvistogether with the same newly visited vertices that were pushed ontoacc.
Examples
/-
Suppose `G` has edges `1 → 2`, `1 → 3`, `2 → 4`, `4 → 2`, and an isolated vertex `5`:
1 → 2 ─┐
↓ ↑ │ 5
3 4 ←┘
-/
G.dfs 1 #[] {} = (#[4, 2, 3, 1], {1, 2, 3, 4})
G.dfs 1 #[] {2} = (#[3, 1], {1, 2, 3})
G.dfs 1 #[] {1} = (#[], {1})
G.dfs 3 #[0] {} = (#[0, 3], {3})
G.dfs 9 #[] {} = (#[9], {9})
Returns the set of vertices of G reachable from v, including v.
Warning: If v is not a vertex of G, this returns the singleton set {v} rather than {}.
Instances For
Returns map from vertices to the set of vertices that each can reach.
Equations
- G.downSets = Std.HashMap.fold (fun (sets : Std.HashMap V (Std.HashSet V)) (v : V) (x : Array V) => sets.insert v (G.downSet v)) ∅ G.adj
Instances For
Condensation #
Strongly-connected components of G, computed with Kosaraju–Sharir's
algorithm.
Examples
/-
Suppose `G` has edges `1 → 2`, `1 → 3`, `2 → 4`, `4 → 2`, and an isolated vertex `5`:
1 → 2 ─┐
↓ ↑ │ 5
3 4 ←┘
-/
G.sccs = #[#[5], #[1], #[3], #[4, 2]]
-- Suppose `H` has edges `1 → 2` and `2 → 1`.
H.sccs = #[#[2, 1]]
Equations
- One or more equations did not get rendered due to their size.
Instances For
Digraph of indices. Guaranteed to be a DAG.
- members : Std.HashMap Nat (Array V)
Maps an index to the array of vertices contained in the SCC corresponding to the index.
- componentsMap : Std.HashMap V Nat
Maps a vertex to the index of the SCC which contains it.
- downSetsByIndex : Std.HashMap Nat (Std.HashSet Nat)
Maps an index to its corresponding down-set.
Instances For
Condense digraph G into DAG of SCCs of G and return the result as a Condensation structure.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Returns indices of SCCs containing the vertices vs.
Examples
-- Suppose `H` has edges `1 → 2` and `2 → 1`, so that `H.condense` has a single SCC, at index `0`.
H.condense.indicesOf {1, 2} = {0}
H.condense.indicesOf {2, 9} = {0}
H.condense.indicesOf {9} = {}
Equations
- c.indicesOf vs = Std.HashSet.fold (fun (indices : Std.HashSet Nat) (v : V) => match c.componentsMap[v]? with | some idx => indices.insert idx | none => indices) ∅ vs
Instances For
Minimal Common Ancestors #
Establishes how minCommonAncestors should handle witness sets where none of the witnesses
corresponds to a vertex of the condensation.
- failClosed : AbsencePolicy
If any witness set has none of its witnesses corresponding to a vertex of the condensation, then
minCommonAncestorsabandons the whole query and returns#[]. (Default.) - failOpenGuarded : AbsencePolicy
Witness sets where none of the witnesses corresponds to a vertex of the condensation are discarded, leading to
minCommonAncestorsnot taking them into account and hence returning ancestors which will not have any of these witness sets' witnesses as a descendant.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Idea: Given the set of classes that a theorem uses, find the minimal common ancestor of that set in the class DAG, i.e., the weakest common ancestor of the elements of the set (i.e., their join), if a unique one exists. The class DAG is not a lattice, however, so the join is not guaranteed to exist. In those cases, an antichain of incomparable answers is returned.
Implementation notes
The set of used classes is given as an array (witnessSets) of sets.
The idea is that the array of sets communicates the requirements as a general AND of ORs. Let's call each set a set of witnesses. The question then becomes:
Find a class that is a common ancestor to at least one witness of each requirement (i.e., a common ancestor of at least one transversal of the sets of witnesses).
The answer is returned as an array of arrays. Any element of any of the inner arrays is a class which satisfies, on its own, all the given requirements. The structure within which the classes are given encodes the relationships between all of these classes:
- All classes within each inner array are mutually equipotent. Each inner array therefore represents a single SCC of the class graph pre-condensation (or, equivalently, a single vertex in the class DAG, i.e., in the condensation of the class graph).
- The SCCs listed in the outer array are pairwise unreachable in the class graph. Alternatively, roughly speaking, the outer array represents the antichain of vertices of the class DAG which satisfy the requirements. More precisely, each of these vertices is a bona-fide minimal common ancestor; it's just that minimality doesn't generally imply uniqueness in our case, since the class DAG is not a lattice.
Examples (as of Mathlib v4.32.1)
Unique minimal common ancestor: Sometimes, there is exactly one minimal common ancestor.
minCommonAncestors #[{Monoid #0}, {CommSemigroup #0}] = #[#[CommMonoid #0]]No common ancestors: Quite often, there may not exist any class which satisfies all the given requirements.
minCommonAncestors #[{Inv #0}, {SDiff #0}] = #[]Minimal common ancestors of single classes: Within an SCC of the class graph (pre-condensation), all classes are pairwise equipotent. This means that any element of the SCC is a minimal common ancestor of any other element or subset of the SCC. Note that the vast majority of the SCCs of the class graph (pre-condensation) are singletons.
minCommonAncestors #[{One #0}] = #[#[OfNat #0 1, One #0]] minCommonAncestors #[{Monoid #0}] = #[#[Monoid #0]]Multiple non-equipotent minimal common ancestors: Rarely, requirements may have multiple non-equipotent minimal common ancestors.
minCommonAncestors #[{Add #0}, {Mul #0}] = #[ #[Lean.Grind.Semiring #0], #[Distrib #0], ]
Equations
- One or more equations did not get rendered due to their size.
Instances For
Given the condensation of a graph and two vertices s and t of the graph
(pre-condensation), returns whether s reaches t in the graph
(pre-condensation).
Examples
/-
Suppose `G` has edges `1 → 2`, `1 → 3`, `2 → 4`, `4 → 2`, and an isolated vertex `5`:
1 → 2 ─┐
↓ ↑ │ 5
3 4 ←┘
-/
G.condense.reaches 1 4 = true
G.condense.reaches 4 2 = true
G.condense.reaches 4 1 = false
G.condense.reaches 5 5 = true
G.condense.reaches 9 9 = false
Equations
Instances For
Given an array xs and methods
fuseto merge two elements of the array andconnectedto query whether two elements of the array should be merged,
returns an array in which no two elements should be merged anymore.
We require the following preconditions:
connectedmust be a symmetric relation.- For any
a b c : α,connected a corconnected b cimpliesconnected (fuse a b) c.
Examples
coalesceWith (·.append ·) (fun x y : List Nat => x.any y.contains)
#[[1], [1, 2], [3], [2, 4], [5, 6]] = #[[1, 1, 2, 2, 4], [3], [5, 6]]
coalesceWith (·.union ·) (fun x y : HashSet Nat => !(x.inter y).isEmpty)
#[{1}, {1, 2}, {3}, {2, 4}, {5, 6}] = #[{1, 2, 4}, {3}, {5, 6}]
Equations
- GeneralizationLinter.Digraph.Condensation.coalesceWith fuse connected xs = GeneralizationLinter.Digraph.Condensation.coalesceWith.loop✝ fuse connected xs (xs.size * xs.size + 1)
Instances For
Partitions used into subsets whose down-sets are disconnected according to conn.
Example
Roughly speaking, in our use-case, if partitionByDesc was called on {Semigroup #0, MulOneClass #0, IsPreorder #0}, it would return #[{Semigroup #0, MulOneClass #0}, {IsPreorder #0}]. See
MCAContext.sharesDataDesc for more information.
Equations
- One or more equations did not get rendered due to their size.