use crate::graph::{
snapshot::Cyberlink,
vocab::ParticleIndex,
};
pub struct Csr {
pub n: usize, pub row_ptr: Vec<u32>, pub col_idx: Vec<u32>, pub values: Vec<f32>, }
impl Csr {
pub fn empty() -> Self {
Self { n: 0, row_ptr: vec![0], col_idx: vec![], values: vec![] }
}
pub fn build<'a>(
links: impl Iterator<Item = Cyberlink>,
vocab: &ParticleIndex,
) -> Self {
let n = vocab.len();
let mut edges: Vec<(u32, u32, f64)> = Vec::new();
let mut max_amount = 1.0f64;
for link in links {
let Some(p) = vocab.get(&link.from) else { continue };
let Some(q) = vocab.get(&link.to) else { continue };
if p == q { continue; }
let w = link.amount as f64;
if w > max_amount { max_amount = w; }
edges.push((p, q, w));
edges.push((q, p, w));
}
edges.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
let mut col_idx: Vec<u32> = Vec::with_capacity(edges.len());
let mut values: Vec<f32> = Vec::with_capacity(edges.len());
let mut row_ptr: Vec<u32> = vec![0u32; n + 1];
let mut ei = 0;
while ei < edges.len() {
let (r, c, mut w) = edges[ei];
ei += 1;
while ei < edges.len() && edges[ei].0 == r && edges[ei].1 == c {
w += edges[ei].2;
ei += 1;
}
col_idx.push(c);
values.push((w / max_amount) as f32);
row_ptr[r as usize + 1] += 1;
}
for i in 0..n {
row_ptr[i + 1] += row_ptr[i];
}
Self { n, row_ptr, col_idx, values }
}
pub fn nnz(&self) -> usize { self.col_idx.len() }
pub fn row(&self, i: usize) -> (&[u32], &[f32]) {
let s = self.row_ptr[i] as usize;
let e = self.row_ptr[i + 1] as usize;
(&self.col_idx[s..e], &self.values[s..e])
}
pub fn degree(&self, i: usize) -> u32 {
self.row_ptr[i + 1] - self.row_ptr[i]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{snapshot::Cyberlink, vocab::ParticleIndex};
fn hash(v: u8) -> [u8; 32] { let mut h = [0u8; 32]; h[0] = v; h }
fn link(from: u8, to: u8, amount: u128) -> Cyberlink {
Cyberlink { neuron: [0u8;32], from: hash(from), to: hash(to),
token: 0, amount, valence: 1, block: 1 }
}
#[test]
fn triangle_symmetric() {
let links = vec![link(0,1,10), link(1,2,20), link(0,2,30)];
let vocab = ParticleIndex::build(links.iter().copied());
let csr = Csr::build(links.into_iter(), &vocab);
assert_eq!(csr.n, 3);
assert_eq!(csr.nnz(), 6);
for i in 0..3 {
assert_eq!(csr.degree(i), 2, "degree of {i}");
}
}
#[test]
fn parallel_edges_accumulate() {
let links = vec![link(0,1,10), link(0,1,20)]; let vocab = ParticleIndex::build(links.iter().copied());
let csr = Csr::build(links.into_iter(), &vocab);
assert_eq!(csr.nnz(), 2);
}
}