hydra-0.15.0: src/main/haskell/Hydra/Sorting.hs
-- Note: this is an automatically generated file. Do not edit.
-- | Utilities for sorting. This module includes an implementation of Tarjan's algorithm, originally based on GraphSCC by Iavor S. Diatchki: https://hackage.haskell.org/package/GraphSCC.
module Hydra.Sorting where
import qualified Hydra.Constants as Constants
import qualified Hydra.Lib.Equality as Equality
import qualified Hydra.Lib.Lists as Lists
import qualified Hydra.Lib.Logic as Logic
import qualified Hydra.Lib.Maps as Maps
import qualified Hydra.Lib.Math as Math
import qualified Hydra.Lib.Maybes as Maybes
import qualified Hydra.Lib.Pairs as Pairs
import qualified Hydra.Lib.Sets as Sets
import qualified Hydra.Topology as Topology
import Prelude hiding (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
import qualified Data.Scientific as Sci
import qualified Data.Map as M
import qualified Data.Set as S
-- | Convert an adjacency list to a map, concatenating values for duplicate keys
adjacencyListToMap :: Ord t0 => ([(t0, [t1])] -> M.Map t0 [t1])
adjacencyListToMap pairs =
Lists.foldl (\mp -> \p ->
let k = Pairs.first p
vs = Pairs.second p
existing = Maybes.maybe [] Equality.identity (Maps.lookup k mp)
in (Maps.insert k (Lists.concat2 existing vs) mp)) Maps.empty pairs
-- | Given a list of adjacency lists represented as (key, [key]) pairs, construct a graph along with a function mapping each vertex (an Int) back to its original key (Nothing for unknown vertices).
adjacencyListsToGraph :: Ord t0 => ([(t0, [t0])] -> (M.Map Int [Int], (Int -> Maybe t0)))
adjacencyListsToGraph edges0 =
let sortedEdges = Lists.sortOn Pairs.first edges0
indexedEdges = Lists.zip (Math.range 0 (Lists.length sortedEdges)) sortedEdges
keyToVertex =
Maps.fromList (Lists.map (\vkNeighbors ->
let v = Pairs.first vkNeighbors
kNeighbors = Pairs.second vkNeighbors
k = Pairs.first kNeighbors
in (k, v)) indexedEdges)
vertexMap =
Maps.fromList (Lists.map (\vkNeighbors ->
let v = Pairs.first vkNeighbors
kNeighbors = Pairs.second vkNeighbors
k = Pairs.first kNeighbors
in (v, k)) indexedEdges)
graph =
Maps.fromList (Lists.map (\vkNeighbors ->
let v = Pairs.first vkNeighbors
kNeighbors = Pairs.second vkNeighbors
neighbors = Pairs.second kNeighbors
in (v, (Maybes.mapMaybe (\k -> Maps.lookup k keyToVertex) neighbors))) indexedEdges)
vertexToKey = \v -> Maps.lookup v vertexMap
in (graph, vertexToKey)
createOrderingIsomorphism :: Ord t0 => ([t0] -> [t0] -> Topology.OrderingIsomorphism t1)
createOrderingIsomorphism sourceOrd targetOrd =
let sourceToTargetMapping =
\els ->
let mp = Maps.fromList (Lists.zip sourceOrd els)
in (Maybes.cat (Lists.map (\n -> Maps.lookup n mp) targetOrd))
targetToSourceMapping =
\els ->
let mp = Maps.fromList (Lists.zip targetOrd els)
in (Maybes.cat (Lists.map (\n -> Maps.lookup n mp) sourceOrd))
in Topology.OrderingIsomorphism {
Topology.orderingIsomorphismEncode = sourceToTargetMapping,
Topology.orderingIsomorphismDecode = targetToSourceMapping}
-- | Given an adjacency function and a distinguished root node, find all reachable nodes (including the root node)
findReachableNodes :: Ord t0 => ((t0 -> S.Set t0) -> t0 -> S.Set t0)
findReachableNodes adj root =
let visit =
\visited -> \node ->
let toVisit = Sets.difference (adj node) visited
in (Logic.ifElse (Sets.null toVisit) visited (Lists.foldl (\v -> \n -> visit (Sets.insert n v) n) visited (Sets.toList toVisit)))
in (visit (Sets.singleton root) root)
-- | Initial state for Tarjan's algorithm
initialState :: Topology.TarjanState
initialState =
Topology.TarjanState {
Topology.tarjanStateCounter = 0,
Topology.tarjanStateIndices = Maps.empty,
Topology.tarjanStateLowLinks = Maps.empty,
Topology.tarjanStateStack = [],
Topology.tarjanStateOnStack = Sets.empty,
Topology.tarjanStateSccs = []}
-- | Pop vertices off the stack until the given vertex is reached, collecting the current strongly connected component
popStackUntil :: Int -> Topology.TarjanState -> ([Int], Topology.TarjanState)
popStackUntil v st0 =
let go =
\acc -> \st -> Maybes.maybe (Lists.reverse acc, st) (\uc ->
let x = Pairs.first uc
xs = Pairs.second uc
newSt =
Topology.TarjanState {
Topology.tarjanStateCounter = (Topology.tarjanStateCounter st),
Topology.tarjanStateIndices = (Topology.tarjanStateIndices st),
Topology.tarjanStateLowLinks = (Topology.tarjanStateLowLinks st),
Topology.tarjanStateStack = xs,
Topology.tarjanStateOnStack = (Topology.tarjanStateOnStack st),
Topology.tarjanStateSccs = (Topology.tarjanStateSccs st)}
newSt2 =
Topology.TarjanState {
Topology.tarjanStateCounter = (Topology.tarjanStateCounter newSt),
Topology.tarjanStateIndices = (Topology.tarjanStateIndices newSt),
Topology.tarjanStateLowLinks = (Topology.tarjanStateLowLinks newSt),
Topology.tarjanStateStack = (Topology.tarjanStateStack newSt),
Topology.tarjanStateOnStack = (Sets.delete x (Topology.tarjanStateOnStack st)),
Topology.tarjanStateSccs = (Topology.tarjanStateSccs newSt)}
acc_ = Lists.cons x acc
in (Logic.ifElse (Equality.equal x v) (Lists.reverse acc_, newSt2) (go acc_ newSt2))) (Lists.uncons (Topology.tarjanStateStack st))
in (go [] st0)
-- | Given a graph as an adjacency list of edges and a list of explicit tags per node, compute the full set of tags for each node by propagating tags through edges. If there is an edge from n1 to n2 and n2 has tag t, then n1 also has tag t. Note: pairs in the output are not ordered.
propagateTags :: (Ord t0, Ord t1) => ([(t0, [t0])] -> [(t0, [t1])] -> [(t0, (S.Set t1))])
propagateTags edges nodeTags =
let adjMap = adjacencyListToMap edges
tagMap = Maps.map Sets.fromList (adjacencyListToMap nodeTags)
allNodes = Sets.toList (Sets.fromList (Lists.concat2 (Lists.map Pairs.first edges) (Lists.map Pairs.first nodeTags)))
getTagsForNode =
\node ->
let reachable = findReachableNodes (\n -> Sets.fromList (Maybes.maybe [] Equality.identity (Maps.lookup n adjMap))) node
in (Sets.unions (Lists.map (\n -> Maybes.maybe Sets.empty Equality.identity (Maps.lookup n tagMap)) (Sets.toList reachable)))
in (Lists.map (\n -> (n, (getTagsForNode n))) allNodes)
-- | Visit a vertex and recursively explore its successors
strongConnect :: M.Map Int [Int] -> Int -> Topology.TarjanState -> Topology.TarjanState
strongConnect graph v st =
let i = Topology.tarjanStateCounter st
newSt =
Topology.TarjanState {
Topology.tarjanStateCounter = (Math.add i 1),
Topology.tarjanStateIndices = (Maps.insert v i (Topology.tarjanStateIndices st)),
Topology.tarjanStateLowLinks = (Maps.insert v i (Topology.tarjanStateLowLinks st)),
Topology.tarjanStateStack = (Lists.cons v (Topology.tarjanStateStack st)),
Topology.tarjanStateOnStack = (Sets.insert v (Topology.tarjanStateOnStack st)),
Topology.tarjanStateSccs = (Topology.tarjanStateSccs st)}
neighbors = Maps.findWithDefault [] v graph
processNeighbor =
\st_ -> \w ->
let lowLink =
\s ->
let lowV1 = Maps.findWithDefault Constants.maxInt32 v (Topology.tarjanStateLowLinks s)
idx_w = Maps.findWithDefault Constants.maxInt32 w (Topology.tarjanStateIndices s)
in Topology.TarjanState {
Topology.tarjanStateCounter = (Topology.tarjanStateCounter s),
Topology.tarjanStateIndices = (Topology.tarjanStateIndices s),
Topology.tarjanStateLowLinks = (Maps.insert v (Equality.min lowV1 idx_w) (Topology.tarjanStateLowLinks s)),
Topology.tarjanStateStack = (Topology.tarjanStateStack s),
Topology.tarjanStateOnStack = (Topology.tarjanStateOnStack s),
Topology.tarjanStateSccs = (Topology.tarjanStateSccs s)}
in (Logic.ifElse (Logic.not (Maps.member w (Topology.tarjanStateIndices st_))) (
let stAfter = strongConnect graph w st_
lowV2 = Maps.findWithDefault Constants.maxInt32 v (Topology.tarjanStateLowLinks stAfter)
low_w = Maps.findWithDefault Constants.maxInt32 w (Topology.tarjanStateLowLinks stAfter)
in Topology.TarjanState {
Topology.tarjanStateCounter = (Topology.tarjanStateCounter stAfter),
Topology.tarjanStateIndices = (Topology.tarjanStateIndices stAfter),
Topology.tarjanStateLowLinks = (Maps.insert v (Equality.min lowV2 low_w) (Topology.tarjanStateLowLinks stAfter)),
Topology.tarjanStateStack = (Topology.tarjanStateStack stAfter),
Topology.tarjanStateOnStack = (Topology.tarjanStateOnStack stAfter),
Topology.tarjanStateSccs = (Topology.tarjanStateSccs stAfter)}) (Logic.ifElse (Sets.member w (Topology.tarjanStateOnStack st_)) (lowLink st_) st_))
stAfterNeighbors = Lists.foldl processNeighbor newSt neighbors
low_v = Maps.findWithDefault Constants.maxInt32 v (Topology.tarjanStateLowLinks stAfterNeighbors)
idx_v = Maps.findWithDefault Constants.maxInt32 v (Topology.tarjanStateIndices stAfterNeighbors)
in (Logic.ifElse (Equality.equal low_v idx_v) (
let compResult = popStackUntil v stAfterNeighbors
comp = Pairs.first compResult
stPopped = Pairs.second compResult
in Topology.TarjanState {
Topology.tarjanStateCounter = (Topology.tarjanStateCounter stPopped),
Topology.tarjanStateIndices = (Topology.tarjanStateIndices stPopped),
Topology.tarjanStateLowLinks = (Topology.tarjanStateLowLinks stPopped),
Topology.tarjanStateStack = (Topology.tarjanStateStack stPopped),
Topology.tarjanStateOnStack = (Topology.tarjanStateOnStack stPopped),
Topology.tarjanStateSccs = (Lists.cons comp (Topology.tarjanStateSccs stPopped))}) stAfterNeighbors)
-- | Compute the strongly connected components of the given graph. The components are returned in reverse topological order
stronglyConnectedComponents :: M.Map Int [Int] -> [[Int]]
stronglyConnectedComponents graph =
let verts = Maps.keys graph
finalState =
Lists.foldl (\st -> \v -> Logic.ifElse (Maps.member v (Topology.tarjanStateIndices st)) st (strongConnect graph v st)) initialState verts
in (Lists.reverse (Lists.map Lists.sort (Topology.tarjanStateSccs finalState)))
-- | Sort a directed acyclic graph (DAG) based on an adjacency list. Yields a list of nontrivial strongly connected components if the graph has cycles, otherwise a simple list.
topologicalSort :: Ord t0 => ([(t0, [t0])] -> Either [[t0]] [t0])
topologicalSort pairs =
let sccs = topologicalSortComponents pairs
isCycle = \scc -> Equality.gt (Lists.length scc) 1
withCycles = Lists.filter isCycle sccs
in (Logic.ifElse (Lists.null withCycles) (Right (Lists.concat sccs)) (Left withCycles))
-- | Find the strongly connected components (including cycles and isolated vertices) of a graph, in (reverse) topological order, i.e. dependencies before dependents
topologicalSortComponents :: Ord t0 => ([(t0, [t0])] -> [[t0]])
topologicalSortComponents pairs =
let graphResult = adjacencyListsToGraph pairs
g = Pairs.first graphResult
in (Lists.map (\comp -> Maybes.mapMaybe (Pairs.second graphResult) comp) (stronglyConnectedComponents g))
-- | Sort a directed acyclic graph (DAG) of nodes using two helper functions: one for node keys, and one for the adjacency list of connected node keys. The result is a list of strongly-connected components (cycles), in which singleton lists represent acyclic nodes.
topologicalSortNodes :: Ord t1 => ((t0 -> t1) -> (t0 -> [t1]) -> [t0] -> [[t0]])
topologicalSortNodes getKey getAdj nodes =
let nodesByKey = Maps.fromList (Lists.map (\n -> (getKey n, n)) nodes)
pairs = Lists.map (\n -> (getKey n, (getAdj n))) nodes
comps = topologicalSortComponents pairs
in (Lists.map (\c -> Maybes.cat (Lists.map (\k -> Maps.lookup k nodesByKey) c)) comps)