use crate::AletheiaDB;
use crate::core::NodeId;
use crate::core::error::Result;
use std::collections::HashMap;
pub type PropagationState = HashMap<NodeId, Vec<f32>>;
pub trait PropagationModel {
fn next_state(
&self,
node_id: NodeId,
current_self: Option<&[f32]>,
neighbors: &[(NodeId, &[f32])],
) -> Option<Vec<f32>>;
}
pub struct LinearPropagation {
pub alpha: f32,
}
impl LinearPropagation {
pub fn new(alpha: f32) -> Self {
Self {
alpha: alpha.clamp(0.0, 1.0),
}
}
}
impl PropagationModel for LinearPropagation {
fn next_state(
&self,
_node_id: NodeId,
current_self: Option<&[f32]>,
neighbors: &[(NodeId, &[f32])],
) -> Option<Vec<f32>> {
if neighbors.is_empty() {
return current_self.map(|v| v.to_vec());
}
let dim = neighbors[0].1.len();
let mut sum = vec![0.0; dim];
let mut count = 0;
for (_, vec) in neighbors {
if vec.len() == dim {
for i in 0..dim {
sum[i] += vec[i];
}
count += 1;
}
}
if count == 0 {
return current_self.map(|v| v.to_vec());
}
let avg: Vec<f32> = sum.iter().map(|x| x / count as f32).collect();
match current_self {
Some(self_vec) => {
if self_vec.len() != dim {
return Some(self_vec.to_vec());
}
let new_vec: Vec<f32> = self_vec
.iter()
.zip(avg.iter())
.map(|(s, a)| (1.0 - self.alpha) * s + self.alpha * a)
.collect();
Some(new_vec)
}
None => {
Some(avg)
}
}
}
}
pub struct Sybil<'a> {
db: &'a AletheiaDB,
}
impl<'a> Sybil<'a> {
pub fn new(db: &'a AletheiaDB) -> Self {
Self { db }
}
pub fn simulate<M: PropagationModel>(
&self,
property_name: &str,
model: &M,
steps: usize,
) -> Result<PropagationState> {
let mut current_state = self.load_initial_state(property_name)?;
for _step in 0..steps {
let mut next_state = HashMap::new();
let mut active_nodes: Vec<NodeId> = current_state.keys().cloned().collect();
let mut expansion_candidates = Vec::new();
for &node_id in &active_nodes {
let edges = self.db.get_outgoing_edges(node_id); for edge_id in edges {
if let Ok(target) = self.db.get_edge_target(edge_id) {
expansion_candidates.push(target);
}
}
}
active_nodes.extend(expansion_candidates);
active_nodes.sort();
active_nodes.dedup();
for &node_id in &active_nodes {
let incoming_edges = self.db.get_incoming_edges(node_id);
let mut neighbor_vectors = Vec::new();
for edge_id in incoming_edges {
if let Ok(source) = self.db.get_edge_source(edge_id)
&& let Some(vec) = current_state.get(&source)
{
neighbor_vectors.push((source, vec.as_slice()));
}
}
let current_vec = current_state.get(&node_id).map(|v| v.as_slice());
if let Some(new_vec) = model.next_state(node_id, current_vec, &neighbor_vectors) {
next_state.insert(node_id, new_vec);
}
}
current_state = next_state;
}
Ok(current_state)
}
fn load_initial_state(&self, property_name: &str) -> Result<PropagationState> {
let mut state = HashMap::new();
let results = self.db.query().scan(None).execute(self.db)?;
for row in results {
let row = row?;
if let Some(node) = row.entity.as_node()
&& let Some(prop) = node.get_property(property_name)
&& let Some(vec) = prop.as_vector()
{
state.insert(node.id, vec.to_vec());
}
}
Ok(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::property::PropertyMapBuilder;
#[test]
fn test_linear_propagation_consensus() {
let db = AletheiaDB::new().unwrap();
let props_a = PropertyMapBuilder::new()
.insert_vector("opinion", &[0.0, 0.0])
.build();
let node_a = db.create_node("Person", props_a).unwrap();
let props_b = PropertyMapBuilder::new()
.insert_vector("opinion", &[1.0, 1.0])
.build();
let node_b = db.create_node("Person", props_b).unwrap();
db.create_edge(
node_a,
node_b,
"INFLUENCES",
PropertyMapBuilder::new().build(),
)
.unwrap();
db.create_edge(
node_b,
node_a,
"INFLUENCES",
PropertyMapBuilder::new().build(),
)
.unwrap();
let sybil = Sybil::new(&db);
let model = LinearPropagation::new(0.5);
let state = sybil.simulate("opinion", &model, 1).unwrap();
let vec_a = state.get(&node_a).unwrap();
let vec_b = state.get(&node_b).unwrap();
assert_eq!(vec_a, &[0.5, 0.5]);
assert_eq!(vec_b, &[0.5, 0.5]);
}
#[test]
fn test_propagation_chain() {
let db = AletheiaDB::new().unwrap();
let node_a = db
.create_node(
"Node",
PropertyMapBuilder::new()
.insert_vector("val", &[1.0])
.build(),
)
.unwrap();
let node_b = db
.create_node(
"Node",
PropertyMapBuilder::new()
.insert_vector("val", &[0.0])
.build(),
)
.unwrap();
let node_c = db
.create_node(
"Node",
PropertyMapBuilder::new()
.insert_vector("val", &[0.0])
.build(),
)
.unwrap();
db.create_edge(node_a, node_b, "LINK", Default::default())
.unwrap();
db.create_edge(node_b, node_c, "LINK", Default::default())
.unwrap();
let sybil = Sybil::new(&db);
let model = LinearPropagation::new(1.0);
let state_1 = sybil.simulate("val", &model, 1).unwrap();
assert_eq!(state_1.get(&node_b).unwrap(), &[1.0]);
assert_eq!(state_1.get(&node_c).unwrap(), &[0.0]);
let state_2 = sybil.simulate("val", &model, 2).unwrap();
assert_eq!(state_2.get(&node_c).unwrap(), &[1.0]);
}
}