use crate::partitioning::PartitionMap;
use crate::partitioning::error::PartitionError;
use crate::partitioning::graph_traits::PartitionableGraph;
use hashbrown::HashMap;
use rayon::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "mpi-support")]
pub fn build_vertex_cuts<G>(
graph: &G,
pm: &PartitionMap<G::VertexId>,
_salt: u64,
) -> Result<(Vec<usize>, Vec<Vec<(G::VertexId, usize)>>), PartitionError>
where
G: PartitionableGraph<VertexId = usize> + Sync,
{
let verts: Vec<G::VertexId> = graph.vertices().collect();
let n = verts.len();
let vert_idx: HashMap<G::VertexId, usize> = verts
.iter()
.copied()
.enumerate()
.map(|(i, v)| (v, i))
.collect();
let num_parts = {
let mut max = None;
for (_v, &p) in pm.iter() {
max = Some(max.map_or(p, |m: usize| m.max(p)));
}
match max {
Some(m) => m + 1,
None => return Err(PartitionError::NoParts),
}
};
type PartId = usize;
type VIdx = usize;
#[inline]
fn bump(map: &mut HashMap<PartId, u32>, p: PartId) {
*map.entry(p).or_insert(0) = map.get(&p).copied().unwrap_or(0).saturating_add(1);
}
struct LocalHist {
map: HashMap<VIdx, HashMap<PartId, u32>>,
}
let hist = graph
.edges()
.fold(
|| LocalHist {
map: HashMap::new(),
},
|mut acc, (u, v)| {
let pu = *pm
.get(&u)
.ok_or(PartitionError::MissingPartition(u))
.unwrap();
let pv = *pm
.get(&v)
.ok_or(PartitionError::MissingPartition(v))
.unwrap();
let ui = *vert_idx
.get(&u)
.ok_or(PartitionError::VertexNotFound(u))
.unwrap();
let vi = *vert_idx
.get(&v)
.ok_or(PartitionError::VertexNotFound(v))
.unwrap();
bump(acc.map.entry(ui).or_insert_with(HashMap::new), pv);
bump(acc.map.entry(vi).or_insert_with(HashMap::new), pu);
acc
},
)
.reduce(
|| LocalHist {
map: HashMap::new(),
},
|mut a, b| {
for (vx, hb) in b.map {
let ha = a.map.entry(vx).or_insert_with(HashMap::new);
for (p, c) in hb {
*ha.entry(p).or_insert(0) =
ha.get(&p).copied().unwrap_or(0).saturating_add(c);
}
}
a
},
)
.map;
let part_owner_load: Vec<AtomicUsize> = (0..num_parts).map(|_| AtomicUsize::new(0)).collect();
let mut primary: Vec<usize> = vec![usize::MAX; n];
#[cfg(not(feature = "deterministic-owners"))]
{
primary.par_iter_mut().enumerate().for_each(|(ui, slot)| {
let v = verts[ui];
let mut cand = hist.get(&ui).cloned().unwrap_or_default();
let own = *pm.get(&v).expect("partition missing for vertex");
cand.entry(own).or_insert(0);
let mut best: Option<(usize, usize, PartId)> = None;
for (&p, °) in &cand {
let load = part_owner_load[p].load(Ordering::Relaxed);
let t = (load, usize::MAX - deg as usize, p);
if best.map_or(true, |b| t < b) {
best = Some(t);
}
}
let chosen = best.unwrap().2;
part_owner_load[chosen].fetch_add(1, Ordering::Relaxed);
*slot = chosen;
});
}
#[cfg(feature = "deterministic-owners")]
{
let mut loads = vec![0usize; num_parts];
for ui in 0..n {
let v = verts[ui];
let mut cand = hist.get(&ui).cloned().unwrap_or_default();
let own = *pm.get(&v).ok_or(PartitionError::MissingPartition(v))?;
cand.entry(own).or_insert(0);
let mut best: Option<(usize, usize, PartId)> = None;
for (&p, °) in &cand {
let t = (loads[p], usize::MAX - deg as usize, p);
if best.map_or(true, |b| t < b) {
best = Some(t);
}
}
let chosen = best.unwrap().2;
loads[chosen] += 1;
primary[ui] = chosen;
}
}
struct LocalRep<G: PartitionableGraph> {
map: HashMap<VIdx, Vec<(G::VertexId, usize)>>,
}
let rep_map = graph
.edges()
.fold(
|| LocalRep::<G> {
map: HashMap::new(),
},
|mut acc, (u, v)| {
let ui = *vert_idx
.get(&u)
.ok_or(PartitionError::VertexNotFound(u))
.unwrap();
let vi = *vert_idx
.get(&v)
.ok_or(PartitionError::VertexNotFound(v))
.unwrap();
let pu = primary[ui];
let pv = primary[vi];
if pu != pv {
acc.map.entry(ui).or_default().push((v, pv));
acc.map.entry(vi).or_default().push((u, pu));
}
acc
},
)
.reduce(
|| LocalRep::<G> {
map: HashMap::new(),
},
|mut a, b| {
for (vx, mut list) in b.map {
a.map.entry(vx).or_default().append(&mut list);
}
a
},
)
.map;
let mut replicas: Vec<Vec<(G::VertexId, usize)>> = vec![Vec::new(); n];
for (ui, mut list) in rep_map {
list.sort_unstable();
list.dedup();
replicas[ui] = list;
}
Ok((primary, replicas))
}
#[cfg(any(test, feature = "exact-metrics"))]
pub fn build_vertex_cuts_fixed<G>(
graph: &G,
pm: &PartitionMap<G::VertexId>,
) -> (Vec<usize>, Vec<Vec<(G::VertexId, usize)>>)
where
G: PartitionableGraph<VertexId = usize> + Sync,
{
use hashbrown::HashMap;
let verts: Vec<_> = graph.vertices().collect();
let vert_idx: HashMap<usize, usize> = verts
.iter()
.copied()
.enumerate()
.map(|(i, v)| (v, i))
.collect();
let mut primary = vec![0usize; verts.len()];
for (i, &v) in verts.iter().enumerate() {
primary[i] = pm.part_of(v);
}
let mut replicas: Vec<Vec<(G::VertexId, usize)>> = vec![Vec::new(); verts.len()];
let edges: Vec<_> = graph.edges().collect();
for (u, v) in edges {
let ui = vert_idx[&u];
let vi = vert_idx[&v];
let pu = primary[ui];
let pv = primary[vi];
if pu != pv {
replicas[ui].push((v, pv));
replicas[vi].push((u, pu));
}
}
for list in &mut replicas {
list.sort_unstable();
list.dedup();
}
(primary, replicas)
}
#[cfg(test)]
#[cfg(feature = "mpi-support")]
mod tests {
use super::*;
use rayon::iter::IntoParallelIterator;
struct TestGraph {
edges: Vec<(usize, usize)>,
n: usize,
}
impl TestGraph {
fn new(edges: Vec<(usize, usize)>, n: usize) -> Self {
TestGraph { edges, n }
}
}
impl PartitionableGraph for TestGraph {
type VertexId = usize;
type VertexParIter<'a>
= rayon::vec::IntoIter<usize>
where
Self: 'a;
type NeighParIter<'a>
= rayon::vec::IntoIter<usize>
where
Self: 'a;
type NeighIter<'a>
= std::vec::IntoIter<usize>
where
Self: 'a;
fn vertices(&self) -> Self::VertexParIter<'_> {
(0..self.n).collect::<Vec<_>>().into_par_iter()
}
fn neighbors(&self, v: usize) -> Self::NeighParIter<'_> {
self.neighbors_seq(v).collect::<Vec<_>>().into_par_iter()
}
fn neighbors_seq(&self, v: usize) -> Self::NeighIter<'_> {
let ns = self
.edges
.iter()
.filter_map(|&(a, b)| {
if a == v {
Some(b)
} else if b == v {
Some(a)
} else {
None
}
})
.collect::<Vec<_>>();
ns.into_iter()
}
fn degree(&self, v: usize) -> usize {
self.neighbors_seq(v).count()
}
fn edges(&self) -> rayon::vec::IntoIter<(usize, usize)> {
self.edges.clone().into_par_iter()
}
}
#[test]
fn vertex_cut_cycle_replicas() {
let g = TestGraph::new(vec![(0, 1), (1, 2), (2, 3), (3, 0)], 4);
let mut pm = PartitionMap::with_capacity(4);
pm.insert(0, 0);
pm.insert(1, 0);
pm.insert(2, 1);
pm.insert(3, 1);
let (primary, replicas) = build_vertex_cuts_fixed(&g, &pm);
for &(u, v) in &[(1, 2), (3, 0)] {
let pu = primary[u];
let pv = primary[v];
assert!(
replicas[u].contains(&(v, pv)),
"missing replica for {u}->{v}"
);
assert!(
replicas[v].contains(&(u, pu)),
"missing replica for {v}->{u}"
);
}
}
#[test]
fn vertex_cut_isolated_vertex() {
let g = TestGraph::new(vec![], 1);
let mut pm = PartitionMap::with_capacity(1);
pm.insert(0, 0);
let (primary, replicas) = build_vertex_cuts(&g, &pm, 99).unwrap();
assert_eq!(primary, vec![0]);
assert!(replicas[0].is_empty());
}
#[test]
fn vertex_cut_two_node_replicas() {
let g = TestGraph::new(vec![(0, 1)], 2);
let mut pm = PartitionMap::with_capacity(2);
pm.insert(0, 0);
pm.insert(1, 1);
let (primary, replicas) = build_vertex_cuts(&g, &pm, 0).unwrap();
assert!(replicas[0].contains(&(1, primary[1])));
assert!(replicas[1].contains(&(0, primary[0])));
}
}