//! Connectivity measures over a topology. use apsis_topology::Topology; /// Returns the set of nodes reachable from a node, following edges in their /// direction. /// /// The start node is included, since a node reaches itself by a path of no /// hops. The result is in node order. /// /// Returns an empty vector when the node is out of range. pub fn reachable_from(topology: &Topology, source: usize) -> Vec { if source >= topology.node_count() { return Vec::new(); } let mut visited = vec![false; topology.node_count()]; visited[source] = true; let mut stack = vec![source]; while let Some(node) = stack.pop() { for edge in topology.edges().iter().filter(|edge| edge.from == node) { if !visited[edge.to] { visited[edge.to] = true; stack.push(edge.to); } } } (0..topology.node_count()) .filter(|node| visited[*node]) .collect() } /// Returns the number of independent paths between two nodes. /// /// Two paths are independent when they share no edge. By Menger's theorem /// that number is the minimum number of edges whose removal disconnects the /// two nodes, so it is the number of link failures the connection survives. /// /// The value is computed as a maximum flow of unit capacity between the two /// nodes, using the Edmonds-Karp variant of the Ford-Fulkerson method, which /// augments along a shortest path at each step. For the small graphs of a /// satellite mesh this is fast, and it is exact. /// /// Returns `Some(0)` when no path exists, and `None` when either node is out /// of range or the two nodes are the same, for which the question is not /// defined. pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) -> Option { if source >= topology.node_count() || destination >= topology.node_count() { return None; } if source == destination { return None; } // A residual capacity for each edge, indexed by the edge position. let edges = topology.edges(); let mut residual: Vec = vec![1; edges.len()]; let mut flow = 0; loop { // Breadth first search for an augmenting path in the residual // graph. let mut parent_edge: Vec> = vec![None; topology.node_count()]; let mut visited = vec![false; topology.node_count()]; visited[source] = true; let mut queue = std::collections::VecDeque::new(); queue.push_back(source); while let Some(node) = queue.pop_front() { if node == destination { break; } for (index, edge) in edges.iter().enumerate() { // Forward edges with residual capacity. if edge.from == node && residual[index] > 0 && !visited[edge.to] { visited[edge.to] = true; parent_edge[edge.to] = Some(index); queue.push_back(edge.to); continue; } // Reverse edges, which exist when flow has been pushed the // other way and which cancel it. if edge.to == node && residual[index] == 0 && !visited[edge.from] { visited[edge.from] = true; parent_edge[edge.from] = Some(index); queue.push_back(edge.from); } } } if !visited[destination] { break; } // Walk the path back, adjusting the residual capacities. An edge // traversed in its own direction consumes one unit; an edge // traversed against its direction restores one. let mut node = destination; while node != source { let index = parent_edge[node]?; let edge = edges[index]; if edge.to == node { residual[index] -= 1; node = edge.from; } else { residual[index] += 1; node = edge.to; } } flow += 1; } Some(flow) } /// Returns the number of strongly connected components. /// /// A component is strongly connected when every node in it can reach every /// other following the edges in their direction. This is the count of /// separate networks that can route internally, which for a satellite mesh /// with directed inter-satellite links is the right measure of how many /// pieces it has. /// /// The algorithm is Kosaraju's: a depth first search records the order in /// which nodes finish, and a second search over the reversed edges, taking /// the nodes in reverse finishing order, emits the components. /// /// Source: Kosaraju's algorithm as described in Cormen et al., /// *Introduction to Algorithms*, 3rd edition, MIT Press, 2009, chapter 22. pub fn strongly_connected_components(topology: &Topology) -> usize { let node_count = topology.node_count(); if node_count == 0 { return 0; } // First pass: the finishing order over the forward edges. let mut visited = vec![false; node_count]; let mut order = Vec::with_capacity(node_count); for start in 0..node_count { if visited[start] { continue; } // An iterative depth first search, pushing a node again to mark the // point at which its children are done. let mut stack = vec![(start, false)]; while let Some((node, processed)) = stack.pop() { if processed { order.push(node); continue; } if visited[node] { continue; } visited[node] = true; stack.push((node, true)); for edge in topology.edges().iter().filter(|edge| edge.from == node) { if !visited[edge.to] { stack.push((edge.to, false)); } } } } // Second pass: the reverse edges, taking nodes in reverse finishing // order. let mut assigned = vec![false; node_count]; let mut components = 0; for node in order.into_iter().rev() { if assigned[node] { continue; } components += 1; let mut stack = vec![node]; assigned[node] = true; while let Some(current) = stack.pop() { for edge in topology.edges().iter().filter(|edge| edge.to == current) { if !assigned[edge.from] { assigned[edge.from] = true; stack.push(edge.from); } } } } components }