use std::collections::{BTreeMap, BTreeSet, HashMap};
use super::graph::{DependencyEdge, DependencyGraph, NodeKey, add_body_atom_edges};
use super::types::{HeadGraphClass, Stratification, StratificationError, Stratum};
use crate::ir::{AggregateBody, Literal, PredicateSymbol, Program};
pub(crate) fn stratify(program: &Program) -> Result<Stratification, StratificationError> {
let graph = DependencyGraph::build(program);
let sccs = graph.strongly_connected_components();
let mut scc_of: HashMap<NodeKey, usize> = HashMap::new();
for (idx, scc) in sccs.iter().enumerate() {
for node in scc {
scc_of.insert(node.clone(), idx);
}
}
for (idx, scc) in sccs.iter().enumerate() {
for src in scc {
let Some(edges) = graph.edges.get(src) else {
continue;
};
for edge in edges {
if edge.negated && scc_of.get(&edge.target) == Some(&idx) && scc.len() > 1 {
let mut predicates: Vec<_> = scc.iter().map(|(p, _)| p.clone()).collect();
predicates.sort();
predicates.dedup();
return Err(StratificationError::UnstratifiableCycle { predicates });
}
if edge.negated && edge.target == *src {
return Err(StratificationError::UnstratifiableCycle {
predicates: vec![src.0.clone()],
});
}
}
}
}
let mut scc_stratum: Vec<usize> = vec![0; sccs.len()];
for idx in (0..sccs.len()).rev() {
let mut stratum = 0usize;
for (from_node, edges) in &graph.edges {
let Some(&from_scc) = scc_of.get(from_node) else {
continue;
};
if from_scc == idx {
continue;
}
for edge in edges {
let Some(&target_scc) = scc_of.get(&edge.target) else {
continue;
};
if target_scc != idx {
continue;
}
let pred_stratum = scc_stratum[from_scc];
let candidate = if edge.negated {
pred_stratum + 1
} else {
pred_stratum
};
if candidate > stratum {
stratum = candidate;
}
}
}
scc_stratum[idx] = stratum;
}
let max_stratum = scc_stratum.iter().copied().max().unwrap_or(0);
let mut strata: Vec<Stratum> = (0..=max_stratum).map(|_| Stratum::default()).collect();
let mut predicate_stratum: BTreeMap<PredicateSymbol, usize> = BTreeMap::new();
for (scc_idx, scc) in sccs.iter().enumerate() {
let stratum = scc_stratum[scc_idx];
for (predicate, _class) in scc {
let entry = predicate_stratum
.entry(predicate.clone())
.or_insert(stratum);
if *entry < stratum {
*entry = stratum;
}
}
}
for (predicate, stratum) in &predicate_stratum {
strata[*stratum].predicates.insert(predicate.clone());
}
while strata
.last()
.map(|s| s.predicates.is_empty())
.unwrap_or(false)
{
strata.pop();
}
Ok(Stratification {
strata,
predicate_stratum,
})
}
pub(super) fn add_aggregate_edges(
agg: &AggregateBody,
head_key: &NodeKey,
head_nodes: &BTreeMap<PredicateSymbol, BTreeSet<HeadGraphClass>>,
nodes: &mut BTreeSet<NodeKey>,
edges: &mut BTreeMap<NodeKey, Vec<DependencyEdge>>,
) {
for lit in &agg.inner_body {
let inner_atom = match lit {
Literal::Positive(atom) | Literal::Negative(atom) => atom,
Literal::Filter(_) | Literal::Bind { .. } | Literal::Aggregate(_) => continue,
};
add_body_atom_edges(inner_atom, head_key, true, head_nodes, nodes, edges);
}
}