use std::collections::{BTreeMap, BTreeSet};
use crate::id::{GraphEdgeId, GraphId, GraphNodeId, index_of, position};
use super::index::{AnalysisContext, SelectedAdjacency, SelectedIndexes};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComponentIdKind;
pub type GraphComponentId = GraphId<ComponentIdKind>;
pub struct GraphComponent {
id: GraphComponentId,
members: Box<[GraphNodeId]>,
cyclic: bool,
}
impl GraphComponent {
pub fn id(&self) -> GraphComponentId {
self.id
}
pub fn members(&self) -> &[GraphNodeId] {
&self.members
}
pub fn is_cyclic(&self) -> bool {
self.cyclic
}
}
pub struct GraphComponents {
components: Box<[GraphComponent]>,
membership: Box<[Option<GraphComponentId>]>,
}
impl GraphComponents {
pub fn components(&self) -> &[GraphComponent] {
&self.components
}
pub fn component(&self, id: GraphComponentId) -> Option<&GraphComponent> {
self.components.get(index_of(id.index()))
}
pub fn component_of(&self, node: GraphNodeId) -> Option<GraphComponentId> {
self.membership
.get(index_of(node.index()))
.copied()
.flatten()
}
}
pub struct CondensationEdge {
source: GraphComponentId,
target: GraphComponentId,
edges: Box<[GraphEdgeId]>,
}
impl CondensationEdge {
pub fn source(&self) -> GraphComponentId {
self.source
}
pub fn target(&self) -> GraphComponentId {
self.target
}
pub fn edges(&self) -> &[GraphEdgeId] {
&self.edges
}
}
pub struct CondensationGraph {
components: GraphComponents,
edges: Box<[CondensationEdge]>,
order: Box<[GraphComponentId]>,
}
impl CondensationGraph {
pub fn components(&self) -> &GraphComponents {
&self.components
}
pub fn edges(&self) -> &[CondensationEdge] {
&self.edges
}
pub fn topological_order(&self) -> &[GraphComponentId] {
&self.order
}
}
pub(crate) fn discover(analysis: &AnalysisContext<'_>) -> GraphComponents {
let indexes = analysis.indexes();
let finished = finish_order(indexes);
assembled(grouped_backwards(indexes, &finished), indexes)
}
pub(crate) fn condense(analysis: &AnalysisContext<'_>) -> CondensationGraph {
let indexes = analysis.indexes();
let components = discover(analysis);
let edges = coalesced(indexes, &components);
let order = ordered(&edges, components.components.len());
CondensationGraph {
components,
edges,
order,
}
}
fn finish_order(indexes: &SelectedIndexes) -> Box<[GraphNodeId]> {
let width = indexes.node_count();
let mut seen: Vec<bool> = vec![false; width];
let mut finished: Vec<GraphNodeId> = Vec::with_capacity(width);
let mut stack: Vec<(GraphNodeId, usize)> = Vec::new();
for at in 0..width {
begin(&mut seen, &mut stack, GraphNodeId::new(position(at)));
drain(indexes, (&mut seen, &mut stack, &mut finished));
}
finished.into_boxed_slice()
}
fn begin(seen: &mut [bool], stack: &mut Vec<(GraphNodeId, usize)>, node: GraphNodeId) {
if unclaimed(seen, node) {
stack.push((node, 0));
}
}
fn unclaimed(flags: &mut [bool], node: GraphNodeId) -> bool {
match flags.get_mut(index_of(node.index())) {
Some(slot) if !*slot => {
*slot = true;
true
}
_ => false,
}
}
fn drain(
indexes: &SelectedIndexes,
state: (
&mut [bool],
&mut Vec<(GraphNodeId, usize)>,
&mut Vec<GraphNodeId>,
),
) {
let (seen, stack, finished) = state;
while let Some(step) = stack.pop() {
advance(indexes, (seen, stack, finished), step);
}
}
fn advance(
indexes: &SelectedIndexes,
state: (
&mut [bool],
&mut Vec<(GraphNodeId, usize)>,
&mut Vec<GraphNodeId>,
),
step: (GraphNodeId, usize),
) {
let (seen, stack, finished) = state;
let (node, at) = step;
match indexes.outgoing(node).get(at) {
Some(entry) => resume(seen, stack, (node, at + 1), entry.node()),
None => finished.push(node),
}
}
fn resume(
seen: &mut [bool],
stack: &mut Vec<(GraphNodeId, usize)>,
step: (GraphNodeId, usize),
reached: GraphNodeId,
) {
stack.push(step);
begin(seen, stack, reached);
}
fn grouped_backwards(indexes: &SelectedIndexes, finished: &[GraphNodeId]) -> Vec<Vec<GraphNodeId>> {
let mut taken: Vec<bool> = vec![false; indexes.node_count()];
let mut found: Vec<Vec<GraphNodeId>> = Vec::new();
for node in finished.iter().rev() {
let members = gathered(indexes, &mut taken, *node);
match members.is_empty() {
true => (),
false => found.push(members),
}
}
found
}
fn gathered(indexes: &SelectedIndexes, taken: &mut [bool], root: GraphNodeId) -> Vec<GraphNodeId> {
let mut members: Vec<GraphNodeId> = Vec::new();
let mut stack: Vec<GraphNodeId> = Vec::new();
claim(taken, &mut stack, root);
while let Some(node) = stack.pop() {
members.push(node);
for entry in indexes.incoming(node) {
claim(taken, &mut stack, entry.node());
}
}
members
}
fn claim(taken: &mut [bool], stack: &mut Vec<GraphNodeId>, node: GraphNodeId) {
if unclaimed(taken, node) {
stack.push(node);
}
}
fn assembled(grouped: Vec<Vec<GraphNodeId>>, indexes: &SelectedIndexes) -> GraphComponents {
let mut ordered = grouped;
for members in &mut ordered {
members.sort_unstable();
}
ordered.sort_unstable_by_key(|members| members.first().copied());
let components: Box<[GraphComponent]> = ordered
.into_iter()
.enumerate()
.map(|(at, members)| built(at, members, indexes))
.collect();
let membership = placed(&components, indexes.node_count());
GraphComponents {
components,
membership,
}
}
fn built(at: usize, members: Vec<GraphNodeId>, indexes: &SelectedIndexes) -> GraphComponent {
GraphComponent {
id: GraphComponentId::new(position(at)),
cyclic: cyclic(&members, indexes),
members: members.into_boxed_slice(),
}
}
fn cyclic(members: &[GraphNodeId], indexes: &SelectedIndexes) -> bool {
match members {
[only] => indexes
.outgoing(*only)
.iter()
.any(|entry| entry.node() == *only),
_ => members.len() > 1,
}
}
fn placed(components: &[GraphComponent], width: usize) -> Box<[Option<GraphComponentId>]> {
let mut table: Vec<Option<GraphComponentId>> = vec![None; width];
for component in components {
for member in component.members.iter() {
record(&mut table, *member, component.id);
}
}
table.into_boxed_slice()
}
fn record(table: &mut [Option<GraphComponentId>], node: GraphNodeId, component: GraphComponentId) {
if let Some(slot) = table.get_mut(index_of(node.index())) {
*slot = Some(component);
}
}
fn coalesced(indexes: &SelectedIndexes, components: &GraphComponents) -> Box<[CondensationEdge]> {
let mut crossing: Crossings = BTreeMap::new();
for at in 0..indexes.node_count() {
cross(
indexes,
components,
&mut crossing,
GraphNodeId::new(position(at)),
);
}
crossing.into_iter().map(joined).collect()
}
type Crossings = BTreeMap<(GraphComponentId, GraphComponentId), Vec<GraphEdgeId>>;
fn cross(
indexes: &SelectedIndexes,
components: &GraphComponents,
crossing: &mut Crossings,
node: GraphNodeId,
) {
let source = match components.component_of(node) {
Some(found) => found,
None => return,
};
for entry in indexes.outgoing(node) {
leaving(components, crossing, (source, *entry));
}
}
fn leaving(
components: &GraphComponents,
crossing: &mut Crossings,
step: (GraphComponentId, SelectedAdjacency),
) {
let (source, entry) = step;
let target = match components.component_of(entry.node()) {
Some(found) => found,
None => return,
};
match source == target {
true => (),
false => crossing
.entry((source, target))
.or_default()
.push(entry.edge()),
}
}
fn joined(crossing: ((GraphComponentId, GraphComponentId), Vec<GraphEdgeId>)) -> CondensationEdge {
let ((source, target), mut edges) = crossing;
edges.sort_unstable();
CondensationEdge {
source,
target,
edges: edges.into_boxed_slice(),
}
}
fn ordered(edges: &[CondensationEdge], count: usize) -> Box<[GraphComponentId]> {
let mut waiting: Vec<usize> = vec![0; count];
for edge in edges {
bump(&mut waiting, edge.target);
}
let mut ready: BTreeSet<GraphComponentId> = (0..count)
.map(|at| GraphComponentId::new(position(at)))
.filter(|component| waiting.get(index_of(component.index())) == Some(&0))
.collect();
let mut consumed: Vec<GraphComponentId> = Vec::with_capacity(count);
while let Some(least) = ready.pop_first() {
consumed.push(least);
release(edges, least, (&mut waiting, &mut ready));
}
consumed.into_boxed_slice()
}
fn bump(waiting: &mut [usize], component: GraphComponentId) {
if let Some(slot) = waiting.get_mut(index_of(component.index())) {
*slot += 1;
}
}
fn release(
edges: &[CondensationEdge],
consumed: GraphComponentId,
state: (&mut [usize], &mut BTreeSet<GraphComponentId>),
) {
let (waiting, ready) = state;
ready.extend(
leaving_pairs(edges, consumed)
.iter()
.filter_map(|edge| arrived(waiting, edge.target)),
);
}
fn leaving_pairs(edges: &[CondensationEdge], source: GraphComponentId) -> &[CondensationEdge] {
let start = edges.partition_point(|edge| edge.source < source);
let end = edges.partition_point(|edge| edge.source <= source);
&edges[start..end]
}
fn arrived(waiting: &mut [usize], component: GraphComponentId) -> Option<GraphComponentId> {
let slot = waiting.get_mut(index_of(component.index()))?;
*slot = slot.checked_sub(1)?;
match *slot {
0 => Some(component),
_ => None,
}
}