use std::collections::{BTreeMap, BTreeSet, HashMap};
use rayon::prelude::*;
use crate::file::Rete;
use crate::reach::reach_one;
use crate::terms::NodeId;
use crate::tiling::Tile;
pub fn count_predicate_serial(rete: &Rete, pred: &str) -> u64 {
match rete.dictionary().predicate_id(pred) {
Some(pid) => rete.match_ids((None, Some(pid), None)).len() as u64,
None => 0,
}
}
pub fn count_predicate_parallel(tiles: &[Tile], pid: u32) -> u64 {
tiles
.par_iter()
.map(|t| t.triples.iter().filter(|&&(_, p, _)| p == pid).count() as u64)
.sum()
}
pub fn count_predicate_tiles_serial(tiles: &[Tile], pid: u32) -> u64 {
tiles
.iter()
.map(|t| t.triples.iter().filter(|&&(_, p, _)| p == pid).count() as u64)
.sum()
}
pub fn out_degree_serial(tiles: &[Tile]) -> BTreeMap<u32, u64> {
let mut acc: BTreeMap<u32, u64> = BTreeMap::new();
for t in tiles {
for &(s, _, _) in &t.triples {
*acc.entry(s).or_default() += 1;
}
}
acc
}
pub fn out_degree_parallel(tiles: &[Tile]) -> BTreeMap<u32, u64> {
tiles
.par_iter()
.map(|t| {
let mut local: BTreeMap<u32, u64> = BTreeMap::new();
for &(s, _, _) in &t.triples {
*local.entry(s).or_default() += 1;
}
local
})
.reduce(BTreeMap::new, |mut a, b| {
for (k, v) in b {
*a.entry(k).or_default() += v;
}
a
})
}
pub fn batch_reach_parallel(
adj: &HashMap<NodeId, Vec<NodeId>>,
seeds: &[NodeId],
) -> Vec<BTreeSet<NodeId>> {
seeds.par_iter().map(|&s| reach_one(adj, s)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dictionary::DictionaryBuilder;
use crate::file::{build_pyramid_meta, write_dataset, Rete, DEFAULT_TILE_BUDGET};
use crate::index::GraphIndexBuilder;
use crate::pyramid::{build_dendrogram, project_graph};
use crate::tiling::{choose_round_for_budget, tile_by_community};
fn fixture() -> (Rete, Vec<crate::tiling::Tile>) {
let edges = [
("A", "B"),
("B", "C"),
("A", "C"),
("C", "A"),
("D", "E"),
("E", "F"),
("D", "F"),
("F", "D"),
("C", "D"),
];
let mut db = DictionaryBuilder::new();
for (s, o) in edges {
db.observe(s, "knows", o);
db.observe(s, "age", "30");
}
let dict = db.build();
let mut triples: Vec<(u32, u32, u32)> = edges
.iter()
.map(|(s, o)| dict.encode(s, "knows", o).unwrap())
.collect();
for (s, _) in edges {
triples.push(dict.encode(s, "age", "30").unwrap());
}
triples.sort_unstable();
triples.dedup();
let mut def = GraphIndexBuilder::new();
for &t in &triples {
def.push(t);
}
let (meta, levels) = build_pyramid_meta(&dict, &triples, DEFAULT_TILE_BUDGET);
let bytes = write_dataset(&dict, &def.build(), &[], false, &meta, levels);
let rete = Rete::open(&bytes).unwrap();
let g = project_graph(&dict, &triples);
let dend = build_dendrogram(&g);
let round = choose_round_for_budget(&dict, &triples, &dend, DEFAULT_TILE_BUDGET);
let tiles = tile_by_community(&dict, &triples, &dend, round);
(rete, tiles)
}
#[test]
fn predicate_count_serial_eq_parallel() {
let (rete, tiles) = fixture();
let pid = rete.dictionary().predicate_id("knows").unwrap();
let serial = count_predicate_serial(&rete, "knows");
let par = count_predicate_parallel(&tiles, pid);
assert_eq!(serial, par);
assert_eq!(serial, count_predicate_tiles_serial(&tiles, pid));
assert!(serial > 0);
}
#[test]
fn out_degree_serial_eq_parallel() {
let (_rete, tiles) = fixture();
let serial = out_degree_serial(&tiles);
let par = out_degree_parallel(&tiles);
assert_eq!(serial, par);
assert!(!serial.is_empty());
}
#[test]
fn batch_reach_serial_eq_parallel() {
use crate::reach::{batch_reach_serial, build_adjacency};
let (rete, _tiles) = fixture();
let adj = build_adjacency(&rete, "knows");
let seeds: Vec<u32> = adj.keys().copied().collect();
let serial = batch_reach_serial(&adj, &seeds);
let par = batch_reach_parallel(&adj, &seeds);
assert_eq!(serial, par);
assert!(serial.iter().any(|r| r.len() >= 4));
}
}