use super::next_id;
use crate::{
db::{
api::{mutation::AdditionOps, view::*},
graph::graph::Graph,
},
prelude::{NodeStateOps, NO_PROPS},
};
use rand::{rngs::StdRng, Rng, SeedableRng};
use raphtory_api::core::storage::timeindex::AsTime;
use std::collections::HashSet;
use tracing::error;
pub fn ba_preferential_attachment(
graph: &Graph,
nodes_to_add: usize,
edges_per_step: usize,
seed: Option<[u8; 32]>,
) {
let mut rng: StdRng;
if let Some(seed_value) = seed {
rng = StdRng::from_seed(seed_value);
} else {
rng = StdRng::from_entropy();
}
let mut latest_time = graph.latest_time().map_or(0, |t| t.t());
let view = graph;
let mut ids = graph.nodes().id().iter_values().collect::<Vec<_>>();
let mut degrees: Vec<usize> = view.nodes().degree().iter_values().collect();
let mut edge_count: usize = degrees.iter().sum();
let mut max_id = next_id(view, ids.iter().max().cloned());
while ids.len() < edges_per_step {
max_id = next_id(view, Some(max_id));
graph
.add_node(latest_time, &max_id, NO_PROPS, None)
.map_err(|err| error!("{:?}", err))
.ok();
degrees.push(0);
ids.push(max_id.clone());
}
if graph.count_edges() < edges_per_step {
for pos in 1..ids.len() {
graph
.add_edge(latest_time, &ids[pos], &ids[pos - 1], NO_PROPS, None)
.expect("Not able to add edge");
edge_count += 2;
degrees[pos] += 1;
degrees[pos - 1] += 1;
}
}
for _ in 0..nodes_to_add {
max_id = next_id(view, Some(max_id));
latest_time += 1;
let mut normalisation = edge_count;
let mut positions_to_skip: HashSet<usize> = HashSet::new();
for _ in 0..edges_per_step {
let mut sum = 0;
let rand_num = rng.gen_range(1..=normalisation);
for pos in 0..ids.len() {
if !positions_to_skip.contains(&pos) {
sum += degrees[pos];
if sum >= rand_num {
positions_to_skip.insert(pos);
normalisation -= degrees[pos];
break;
}
}
}
}
for pos in positions_to_skip {
let dst = &ids[pos];
degrees[pos] += 1;
graph
.add_edge(latest_time, &max_id, dst, NO_PROPS, None)
.expect("Not able to add edge");
}
ids.push(max_id.clone());
degrees.push(edges_per_step);
edge_count += edges_per_step * 2;
}
}
#[cfg(test)]
mod preferential_attachment_tests {
use super::*;
use crate::graphgen::random_attachment::random_attachment;
use raphtory_api::core::utils::logging::global_info_logger;
#[test]
fn blank_graph() {
let graph = Graph::new();
ba_preferential_attachment(&graph, 1000, 10, None);
assert_eq!(graph.count_edges(), 10009);
assert_eq!(graph.count_nodes(), 1010);
}
#[test]
fn only_nodes() {
global_info_logger();
let graph = Graph::new();
for i in 0..10 {
graph
.add_node(i, i as u64, NO_PROPS, None)
.map_err(|err| error!("{:?}", err))
.ok();
}
ba_preferential_attachment(&graph, 1000, 5, None);
assert_eq!(graph.count_edges(), 5009);
assert_eq!(graph.count_nodes(), 1010);
}
#[test]
fn prior_graph() {
let graph = Graph::new();
random_attachment(&graph, 1000, 3, None);
ba_preferential_attachment(&graph, 500, 4, None);
assert_eq!(graph.count_edges(), 5000);
assert_eq!(graph.count_nodes(), 1503);
}
}