#![cfg(feature = "mpi-support")]
use super::{PartitionMap, PartitionableGraph};
use rayon::iter::ParallelIterator;
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::atomic::{AtomicUsize, Ordering};
impl<V: Eq + Hash + Copy> PartitionMap<V> {
pub fn part_of(&self, v: V) -> usize {
*self.get(&v).expect("vertex not found in PartitionMap")
}
}
#[inline]
fn num_parts_in_pm<V: Eq + Hash + Copy>(pm: &PartitionMap<V>) -> Option<usize> {
let mut maxp: Option<usize> = None;
for (_, &p) in pm.iter() {
maxp = Some(maxp.map_or(p, |m| m.max(p)));
}
maxp.map(|m| m + 1)
}
const WORD_BITS: usize = usize::BITS as usize;
#[derive(Debug)]
struct AtomicBitset {
words: Box<[AtomicUsize]>,
}
impl AtomicBitset {
#[inline]
fn new(num_parts: usize) -> Self {
let n_words = (num_parts + WORD_BITS - 1) / WORD_BITS;
let mut v = Vec::with_capacity(n_words);
for _ in 0..n_words {
v.push(AtomicUsize::new(0));
}
Self {
words: v.into_boxed_slice(),
}
}
#[inline]
fn or_bit(&self, part: usize) {
let wi = part / WORD_BITS;
let bi = part % WORD_BITS;
let mask = 1usize << bi;
self.words[wi].fetch_or(mask, Ordering::Relaxed);
}
#[inline]
fn popcount(&self) -> usize {
self.words
.iter()
.map(|w| w.load(Ordering::Relaxed).count_ones() as usize)
.sum()
}
}
pub fn edge_cut<G>(g: &G, pm: &PartitionMap<G::VertexId>) -> usize
where
G: PartitionableGraph,
G::VertexId: PartialOrd + Eq + Hash + Copy + Sync + 'static,
{
g.edges()
.map(|(u, v)| (pm.part_of(u) != pm.part_of(v)) as usize)
.sum()
}
pub fn replication_factor<G>(g: &G, pm: &PartitionMap<G::VertexId>) -> f64
where
G: PartitionableGraph,
G::VertexId: Eq + Hash + Copy + Sync + 'static,
{
use rayon::prelude::*;
let verts: Vec<G::VertexId> = g.vertices().collect();
let n = verts.len();
if n == 0 {
return 0.0;
}
let idx_map: HashMap<G::VertexId, usize> = verts
.iter()
.copied()
.enumerate()
.map(|(i, v)| (v, i))
.collect();
let num_parts = match num_parts_in_pm(pm) {
Some(np) if np > 0 => np,
_ => return 0.0,
};
let masks: Vec<AtomicBitset> = (0..n).map(|_| AtomicBitset::new(num_parts)).collect();
g.edges().for_each(|(u, v)| {
let pu = pm.part_of(u);
let pv = pm.part_of(v);
let ui = *idx_map.get(&u).expect("vertex not found");
let vi = *idx_map.get(&v).expect("vertex not found");
masks[vi].or_bit(pu);
masks[ui].or_bit(pv);
});
verts.par_iter().for_each(|&u| {
let ui = idx_map[&u];
let p = pm.part_of(u);
masks[ui].or_bit(p);
});
let total_owned: usize = masks.par_iter().map(|m| m.popcount()).sum();
total_owned as f64 / n as f64
}
#[allow(dead_code)]
pub fn rf_exact<V>(primary: &[usize], replicas: &[Vec<(V, usize)>]) -> f64 {
use std::collections::HashSet;
let n = primary.len();
assert_eq!(n, replicas.len(), "length mismatch");
if n == 0 {
return 0.0;
}
let mut total = 0usize;
for (p, reps) in primary.iter().zip(replicas.iter()) {
let mut parts: HashSet<usize> = HashSet::new();
parts.insert(*p);
for &(_, part) in reps {
parts.insert(part);
}
total += parts.len();
}
total as f64 / n as f64
}
#[allow(dead_code)]
pub fn load_balance_parts<G>(pm: &PartitionMap<G::VertexId>, g: &G) -> (u64, u64, f64)
where
G: PartitionableGraph,
G::VertexId: Eq + Hash + Copy + Sync,
{
let num_parts = match num_parts_in_pm(pm) {
Some(n) if n > 0 => n,
_ => return (0, 0, 1.0),
};
let mut loads = vec![0u64; num_parts];
let verts: Vec<G::VertexId> = g.vertices().collect();
for v in verts {
let part = pm.part_of(v);
loads[part] += g.degree(v) as u64;
}
let min = *loads.iter().min().unwrap_or(&0);
let max = *loads.iter().max().unwrap_or(&0);
let ratio = if min == 0 {
f64::INFINITY
} else {
max as f64 / (min as f64 + f64::EPSILON)
};
(min, max, ratio)
}
#[cfg(feature = "mem-snapshot")]
pub fn memory_snapshot_bytes() -> Option<u64> {
use std::fs::File;
use std::io::Read;
let mut buf = String::new();
if File::open("/proc/self/statm")
.and_then(|mut f| f.read_to_string(&mut buf))
.is_ok()
{
if let Some(rss_pages) = buf.split_whitespace().nth(1) {
if let Ok(pages) = rss_pages.parse::<u64>() {
return Some(pages * 4096);
}
}
}
None
}
#[cfg(not(feature = "mem-snapshot"))]
pub fn memory_snapshot_bytes() -> Option<u64> {
None
}
#[cfg(test)]
#[cfg(feature = "mpi-support")]
mod tests {
use super::*;
use crate::partitioning::graph_traits::PartitionableGraph;
use rayon::iter::IntoParallelIterator;
struct TestGraph {
edges: Vec<(usize, usize)>,
n: usize,
}
impl PartitionableGraph for TestGraph {
type VertexId = usize;
type VertexParIter<'a> = rayon::vec::IntoIter<usize>;
type NeighParIter<'a> = rayon::vec::IntoIter<usize>;
type NeighIter<'a> = std::vec::IntoIter<usize>;
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: Vec<_> = self
.edges
.iter()
.filter_map(|&(a, b)| {
if a == v {
Some(b)
} else if b == v {
Some(a)
} else {
None
}
})
.collect();
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 triangle_same_part() {
let g = TestGraph {
edges: vec![(0, 1), (1, 2), (0, 2)],
n: 3,
};
let mut pm = PartitionMap::with_capacity(3);
for v in 0..3 {
pm.insert(v, 0);
}
assert_eq!(edge_cut(&g, &pm), 0);
let rf = replication_factor(&g, &pm);
assert!((rf - 1.0).abs() < 1e-6);
}
#[test]
fn two_vertices_different_parts() {
let g = TestGraph {
edges: vec![(0, 1)],
n: 2,
};
let mut pm = PartitionMap::with_capacity(2);
pm.insert(0, 0);
pm.insert(1, 1);
assert_eq!(edge_cut(&g, &pm), 1);
let rf = replication_factor(&g, &pm);
assert!((rf - 2.0).abs() < 1e-6);
}
#[test]
fn star_graph_replication() {
let g = TestGraph {
edges: vec![(0, 1), (0, 2), (0, 3), (0, 4)],
n: 5,
};
let mut pm = PartitionMap::with_capacity(5);
pm.insert(0, 0);
for v in 1..5 {
pm.insert(v, 1);
}
assert_eq!(edge_cut(&g, &pm), 4);
let rf = replication_factor(&g, &pm);
assert!((rf - 2.0).abs() < 1e-6);
}
#[test]
fn isolated_vertex() {
let g = TestGraph {
edges: vec![],
n: 1,
};
let mut pm = PartitionMap::with_capacity(1);
pm.insert(0, 0);
assert_eq!(edge_cut(&g, &pm), 0);
let rf = replication_factor(&g, &pm);
assert!((rf - 1.0).abs() < 1e-6);
}
#[test]
fn many_parts_bitset() {
let mut edges = Vec::new();
for v in 1..200 {
edges.push((0, v));
}
let g = TestGraph { edges, n: 200 };
let mut pm = PartitionMap::with_capacity(200);
for v in 0..200 {
pm.insert(v, v);
}
let rf = replication_factor(&g, &pm);
assert!((rf - 2.99).abs() < 1e-2);
}
#[test]
fn replication_factor_deterministic() {
let g = TestGraph {
edges: vec![(0, 1), (1, 2)],
n: 3,
};
let mut pm = PartitionMap::with_capacity(3);
pm.insert(0, 0);
pm.insert(1, 1);
pm.insert(2, 2);
let rf1 = replication_factor(&g, &pm);
let rf2 = replication_factor(&g, &pm);
assert!((rf1 - rf2).abs() < 1e-12);
}
}