use crate::graph::node_index::NodeIndex;
use crate::graph::store_models::EdgeKind;
pub const STRIDE: usize = 11;
pub mod slot {
pub const CALLS_OUT: usize = 0;
pub const CALLS_IN: usize = 1;
pub const IMPORTS_OUT: usize = 2;
pub const IMPORTS_IN: usize = 3;
pub const CONTAINS_OUT: usize = 4;
pub const CONTAINS_IN: usize = 5;
pub const INHERITS_OUT: usize = 6;
pub const INHERITS_IN: usize = 7;
pub const USES_OUT: usize = 8;
pub const USES_IN: usize = 9;
pub const MODIFIED_IN_OUT: usize = 10;
}
fn edge_kind_slots(kind: EdgeKind) -> (usize, Option<usize>) {
match kind {
EdgeKind::Calls => (slot::CALLS_OUT, Some(slot::CALLS_IN)),
EdgeKind::Imports => (slot::IMPORTS_OUT, Some(slot::IMPORTS_IN)),
EdgeKind::Contains => (slot::CONTAINS_OUT, Some(slot::CONTAINS_IN)),
EdgeKind::Inherits => (slot::INHERITS_OUT, Some(slot::INHERITS_IN)),
EdgeKind::Uses => (slot::USES_OUT, Some(slot::USES_IN)),
EdgeKind::ModifiedIn => (slot::MODIFIED_IN_OUT, None),
}
}
pub struct CsrStorage {
offsets: Vec<u32>,
neighbors: Vec<u32>,
node_count: usize,
}
impl CsrStorage {
pub fn build(node_count: usize, edges: &[(u32, u32, EdgeKind)]) -> Self {
if node_count == 0 {
return Self {
offsets: vec![0],
neighbors: Vec::new(),
node_count: 0,
};
}
let mut entries: Vec<(u32, usize, u32)> = Vec::with_capacity(edges.len() * 2);
for &(src, dst, kind) in edges {
let (out_slot, in_slot) = edge_kind_slots(kind);
entries.push((src, out_slot, dst));
if let Some(in_s) = in_slot {
entries.push((dst, in_s, src));
}
}
entries.sort_unstable();
let total_slots = node_count * STRIDE;
let mut offsets = vec![0u32; total_slots + 1];
let mut neighbors = Vec::with_capacity(entries.len());
for &(node, s, _) in &entries {
let slot_idx = node as usize * STRIDE + s;
offsets[slot_idx + 1] += 1;
}
for i in 1..=total_slots {
offsets[i] += offsets[i - 1];
}
neighbors.extend(entries.iter().map(|&(_, _, neighbor)| neighbor));
Self {
offsets,
neighbors,
node_count,
}
}
#[inline]
pub fn neighbors(&self, node: u32, s: usize) -> &[u32] {
let slot_idx = node as usize * STRIDE + s;
debug_assert!(
slot_idx + 1 < self.offsets.len(),
"slot index out of bounds"
);
let start = self.offsets[slot_idx] as usize;
let end = self.offsets[slot_idx + 1] as usize;
&self.neighbors[start..end]
}
#[inline]
pub fn neighbors_as_node_index(&self, node: usize, s: usize) -> &[NodeIndex] {
let raw = self.neighbors(node as u32, s);
unsafe { std::mem::transmute::<&[u32], &[NodeIndex]>(raw) }
}
#[inline]
pub fn node_count(&self) -> usize {
self.node_count
}
#[inline]
pub fn neighbor_count(&self) -> usize {
self.neighbors.len()
}
}
pub fn bfs_reorder(node_count: usize, edges: &[(u32, u32, EdgeKind)]) -> Vec<u32> {
if node_count == 0 {
return Vec::new();
}
let mut degree = vec![0u32; node_count];
for &(src, dst, _) in edges {
degree[src as usize] += 1;
degree[dst as usize] += 1;
}
let mut adj: Vec<Vec<u32>> = vec![vec![]; node_count];
for &(src, dst, _) in edges {
adj[src as usize].push(dst);
adj[dst as usize].push(src);
}
for list in &mut adj {
list.sort_unstable();
list.dedup();
}
let mut perm = vec![u32::MAX; node_count];
let mut visited = vec![false; node_count];
let mut next_id = 0u32;
let mut queue = std::collections::VecDeque::with_capacity(node_count);
while (next_id as usize) < node_count {
let seed = (0..node_count)
.filter(|&i| !visited[i])
.max_by_key(|&i| (degree[i], std::cmp::Reverse(i)))
.expect("unvisited node must exist");
visited[seed] = true;
perm[seed] = next_id;
next_id += 1;
queue.push_back(seed);
while let Some(v) = queue.pop_front() {
for &neighbor in &adj[v] {
let n = neighbor as usize;
if !visited[n] {
visited[n] = true;
perm[n] = next_id;
next_id += 1;
queue.push_back(n);
}
}
}
}
perm
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_csr() {
let csr = CsrStorage::build(0, &[]);
assert_eq!(csr.node_count(), 0);
assert_eq!(csr.neighbor_count(), 0);
}
#[test]
fn test_basic_edges() {
let edges = vec![(0u32, 1u32, EdgeKind::Calls)];
let csr = CsrStorage::build(2, &edges);
assert_eq!(csr.neighbors(0, slot::CALLS_OUT), &[1]);
assert_eq!(csr.neighbors(1, slot::CALLS_IN), &[0]);
assert!(csr.neighbors(0, slot::CALLS_IN).is_empty());
assert!(csr.neighbors(1, slot::CALLS_OUT).is_empty());
}
#[test]
fn test_bidirectional_consistency() {
let edges = vec![(0u32, 1u32, EdgeKind::Calls), (1u32, 2u32, EdgeKind::Calls)];
let csr = CsrStorage::build(3, &edges);
assert_eq!(csr.neighbors(0, slot::CALLS_OUT), &[1]);
assert_eq!(csr.neighbors(1, slot::CALLS_IN), &[0]);
assert_eq!(csr.neighbors(1, slot::CALLS_OUT), &[2]);
assert_eq!(csr.neighbors(2, slot::CALLS_IN), &[1]);
}
#[test]
fn test_sorted_neighbors() {
let edges = vec![
(0u32, 2u32, EdgeKind::Calls),
(0u32, 1u32, EdgeKind::Calls),
(0u32, 3u32, EdgeKind::Calls),
];
let csr = CsrStorage::build(4, &edges);
assert_eq!(csr.neighbors(0, slot::CALLS_OUT), &[1, 2, 3]);
}
#[test]
fn test_modified_in_unidirectional() {
let edges = vec![(0u32, 1u32, EdgeKind::ModifiedIn)];
let csr = CsrStorage::build(2, &edges);
assert_eq!(csr.neighbors(0, slot::MODIFIED_IN_OUT), &[1]);
assert!(csr.neighbors(1, slot::MODIFIED_IN_OUT).is_empty());
}
#[test]
fn test_neighbors_as_node_index() {
let edges = vec![(0u32, 1u32, EdgeKind::Calls)];
let csr = CsrStorage::build(2, &edges);
let ni = csr.neighbors_as_node_index(0, slot::CALLS_OUT);
assert_eq!(ni.len(), 1);
assert_eq!(ni[0], NodeIndex::new(1));
}
#[test]
fn test_multiple_edge_kinds() {
let edges = vec![
(0u32, 1u32, EdgeKind::Calls),
(0u32, 1u32, EdgeKind::Contains),
(0u32, 1u32, EdgeKind::Imports),
];
let csr = CsrStorage::build(2, &edges);
assert_eq!(csr.neighbors(0, slot::CALLS_OUT), &[1]);
assert_eq!(csr.neighbors(0, slot::CONTAINS_OUT), &[1]);
assert_eq!(csr.neighbors(0, slot::IMPORTS_OUT), &[1]);
assert_eq!(csr.neighbors(1, slot::CALLS_IN), &[0]);
assert_eq!(csr.neighbors(1, slot::CONTAINS_IN), &[0]);
assert_eq!(csr.neighbors(1, slot::IMPORTS_IN), &[0]);
}
#[test]
fn test_bfs_reorder_highest_degree_first() {
let edges = vec![
(0u32, 2, EdgeKind::Calls),
(1, 2, EdgeKind::Calls),
(2, 0, EdgeKind::Calls),
(2, 1, EdgeKind::Calls),
(2, 3, EdgeKind::Calls),
];
let perm = bfs_reorder(4, &edges);
assert_eq!(perm[2], 0); }
#[test]
fn test_bfs_reorder_deterministic() {
let edges = vec![(0u32, 1, EdgeKind::Calls), (1, 2, EdgeKind::Calls)];
let perm1 = bfs_reorder(3, &edges);
let perm2 = bfs_reorder(3, &edges);
assert_eq!(perm1, perm2);
}
#[test]
fn test_bfs_reorder_empty() {
let perm = bfs_reorder(0, &[]);
assert!(perm.is_empty());
}
#[test]
fn test_bfs_reorder_disconnected() {
let edges = vec![(0u32, 1, EdgeKind::Calls), (2, 3, EdgeKind::Calls)];
let perm = bfs_reorder(4, &edges);
let mut sorted = perm.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2, 3]);
}
#[test]
fn test_bfs_reorder_is_permutation() {
let edges = vec![
(0u32, 1, EdgeKind::Calls),
(1, 2, EdgeKind::Imports),
(2, 3, EdgeKind::Contains),
(3, 0, EdgeKind::Uses),
];
let perm = bfs_reorder(4, &edges);
let mut sorted = perm.clone();
sorted.sort();
assert_eq!(sorted, vec![0, 1, 2, 3]);
}
}