use itertools::Itertools;
use ojo_graph::Graph;
use ojo_multimap::MMap;
use std::collections::{BTreeMap, HashSet};
use crate::NodeId;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ChainGraggle {
chains: Vec<Vec<NodeId>>,
edges: MMap<usize, usize>,
clusters: Vec<HashSet<usize>>,
}
fn on_chain<G: Graph>(g: &G, node: &G::Node) -> bool {
g.out_edges(node).take(2).count() <= 1 && g.in_edges(node).take(2).count() <= 1
}
fn chain_first<G: Graph>(g: &G, node: &G::Node) -> G::Node {
if !on_chain(g, node) {
*node
} else {
let mut ret = *node;
while let Some(prev) = g.in_neighbors(&ret).next() {
if on_chain(g, &prev) {
ret = prev;
} else {
return ret;
}
}
ret
}
}
fn collect_chain<G: Graph>(g: &G, first: &G::Node) -> Vec<G::Node> {
let mut ret = Vec::new();
let mut cur = *first;
ret.push(cur);
if !on_chain(g, &cur) {
return ret;
}
while let Some(next) = g.out_neighbors(&cur).next() {
if on_chain(g, &next) {
ret.push(next);
cur = next;
} else {
break;
}
}
ret
}
impl ChainGraggle {
pub fn num_chains(&self) -> usize {
self.chains.len()
}
pub fn chain(&self, i: usize) -> &[NodeId] {
&self.chains[i]
}
pub fn clusters(&self) -> impl Iterator<Item = &HashSet<usize>> {
self.clusters.iter()
}
pub fn from_graph<G: Graph<Node = NodeId>>(g: G) -> ChainGraggle
where
G::Edge: ojo_graph::Edge<NodeId>,
{
let sccs = g.tarjan();
let mut singles = sccs
.parts()
.filter(|part| part.len() == 1)
.flat_map(|part| part.iter())
.collect::<HashSet<_>>();
let (others1, others2) = sccs
.parts()
.filter(|part| part.len() > 1)
.flat_map(|part| part.iter())
.cloned()
.tee();
let mut chains = others1.map(|node| vec![node]).collect::<Vec<_>>();
let mut node_part = others2
.enumerate()
.map(|(x, y)| (y, x))
.collect::<BTreeMap<NodeId, usize>>();
while !singles.is_empty() {
let u = chain_first(&g, singles.iter().next().unwrap());
let chain = collect_chain(&g, &u);
for v in &chain {
singles.remove(v);
node_part.insert(*v, chains.len());
}
chains.push(chain);
}
let mut edges = MMap::new();
for u in g.nodes() {
for v in g.out_neighbors(&u) {
let u_idx = node_part[&u];
let v_idx = node_part[&v];
if u_idx != v_idx {
edges.insert(u_idx, v_idx);
}
}
}
let clusters = sccs
.parts()
.filter(|part| part.len() > 1)
.map(|part| part.iter().map(|id| node_part[id]).collect::<HashSet<_>>())
.collect::<Vec<_>>();
ChainGraggle {
chains,
edges,
clusters,
}
}
}
impl Graph for ChainGraggle {
type Node = usize;
type Edge = usize;
fn nodes(&'_ self) -> Box<dyn Iterator<Item = usize> + '_> {
Box::new(0..self.chains.len())
}
fn out_edges(&'_ self, u: &usize) -> Box<dyn Iterator<Item = usize> + '_> {
Box::new(self.edges.get(u).cloned())
}
fn in_edges(&'_ self, _u: &usize) -> Box<dyn Iterator<Item = usize> + '_> {
panic!("in-edges not implemented for this graph");
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::storage::graggle::tests::arb_live_graggle;
#[test]
fn diamond() {
let graggle = graggle!(
live: 0, 1, 2, 3
edges: 0-1, 0-2, 1-3, 2-3
);
let decomp = ChainGraggle::from_graph(graggle.as_graggle().as_live_graph());
assert_eq!(decomp.chains.len(), 4);
for ch in &decomp.chains {
assert_eq!(ch.len(), 1);
}
}
proptest! {
#[test]
fn partition(ref d in arb_live_graggle(20)) {
let decomp = ChainGraggle::from_graph(d.as_graggle().as_live_graph());
let decomp_nodes = decomp.chains.iter().flat_map(|chain| chain.iter()).cloned().collect::<Vec<_>>();
let decomp_node_set = decomp_nodes.iter().cloned().collect::<HashSet<_>>();
assert_eq!(decomp_nodes.len(), decomp_node_set.len());
assert_eq!(decomp_nodes.len(), d.as_graggle().nodes().count());
}
}
}