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 least upper bounds.
References
- [A. J. Best. 2023. Automatically Generalizing Theorems Using Typeclasses.][best2023automaticallyGeneralizingTheorems]
- [D. J. King, J. Launchbury. 1995. Structuring depth-first search algorithms in Haskell.][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.)
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 array that will eventually contain all vertices reachable fromv, includingvitself. IfGis acyclic, this list will be returned in postorder.visis the set of visited vertices. It will eventually also contain all vertices reachable fromv, includingvitself.
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.
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 its parent SCC.
- 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.
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
Least Upper Bounds #
Establishes how minCommonAncestors should handle inputs that are unexpectedly
not vertices of the given condensation.
- failClosed : AbsencePolicy
Return an empty array. (Default.)
- failOpenGuarded : AbsencePolicy
Filter out bad inputs and proceed as usual.
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 meet), if a unique one exists. The class DAG is not a lattice, however, so the meet 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 (requirements) of smaller sets. Currently, these
smaller sets are usually singletons, except for used classes that are not universe polymorphic. In
those cases, the corresponding smaller set consists of the used class with the specific universe
level, and the universe-polymorphic version of the used class.
The idea is that the array of smaller sets communicates the requirements as a general AND of ORs. Let's call each smaller 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
Unique minimal common ancestor: Sometimes, there is exactly one minimal common ancestor.
minCommonAncestors #[{Monoid}, {CommSemigroup}] = #[#[CommMonoid]]No common ancestors: Quite often, there may not exist any class which satisfies all the given requirements.
minCommonAncestors #[{Inv}, {SDiff}] = #[]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 #[{Nonempty}] = #[#[Inhabited, Nonempty]] minCommonAncestors #[{Monoid}] = #[#[Monoid]]Multiple non-equipotent minimal common ancestors: Rarely, requirements may have multiple non-equipotent minimal common ancestors.
minCommonAncestors #[{Add}, {Mul}] = #[ #[FirstOrder.Language.Structure] #[Lean.Grind.Semiring], #[Distrib], ]
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).
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 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, MulOneClass, IsPreorder, IsTotal}, it would return [{Semigroup, MulOneClass}, {IsPreorder}, {IsTotal}]. Refer
to the examples documented for sharesDataDesc for more information.
Equations
- One or more equations did not get rendered due to their size.