use super::vertex::VertexId;
use formualizer_common::Coord as AbsCoord;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_csr_construction() {
let edges = vec![
(0u32, vec![1u32, 2u32]),
(1u32, vec![2u32, 3u32]),
(2u32, vec![3u32]),
(3u32, vec![]),
];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
AbsCoord::new(1, 1),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.out_edges(VertexId(0)), &[VertexId(1), VertexId(2)]);
assert_eq!(csr.out_edges(VertexId(1)), &[VertexId(2), VertexId(3)]);
assert_eq!(csr.out_edges(VertexId(3)), &[]);
}
#[test]
fn test_csr_memory_efficiency() {
let mut edges = Vec::new();
let mut coords = Vec::new();
for i in 0..10_000u32 {
let targets: Vec<_> = (0..4).map(|j| (i + j + 1) % 10_000).collect();
edges.push((i, targets));
coords.push(AbsCoord::new(i, i));
}
let csr = CsrEdges::from_adjacency(edges, &coords);
assert!(csr.memory_usage() < 410_000, "{}", csr.memory_usage());
}
#[test]
fn test_csr_edge_ordering() {
let edges = vec![
(0u32, vec![3u32, 1u32, 2u32]), ];
let coords = vec![
AbsCoord::new(0, 0), AbsCoord::new(0, 5), AbsCoord::new(0, 3), AbsCoord::new(1, 0), ];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(
csr.out_edges(VertexId(0)),
&[VertexId(2), VertexId(1), VertexId(3)]
);
}
#[test]
fn test_csr_empty_graph() {
let edges: Vec<(u32, Vec<u32>)> = vec![];
let coords: Vec<AbsCoord> = vec![];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.num_vertices(), 0);
assert_eq!(csr.num_edges(), 0);
assert_eq!(csr.memory_usage(), 8);
}
#[test]
fn test_csr_single_vertex() {
let edges = vec![(0u32, vec![])];
let coords = vec![AbsCoord::new(0, 0)];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.num_vertices(), 1);
assert_eq!(csr.num_edges(), 0);
assert_eq!(csr.out_edges(VertexId(0)), &[]);
}
#[test]
fn test_csr_self_loop() {
let edges = vec![(0u32, vec![0u32])]; let coords = vec![AbsCoord::new(0, 0)];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.out_edges(VertexId(0)), &[VertexId(0)]);
assert_eq!(csr.num_edges(), 1);
}
#[test]
fn test_csr_duplicate_edges() {
let edges = vec![(0u32, vec![1u32, 1u32, 2u32, 1u32])];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(0, 2),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(
csr.out_edges(VertexId(0)),
&[VertexId(1), VertexId(1), VertexId(1), VertexId(2)]
);
}
#[test]
fn test_degree_calculation() {
let edges = vec![
(0u32, vec![1u32, 2u32, 3u32]),
(1u32, vec![2u32]),
(2u32, vec![]),
(3u32, vec![0u32, 1u32]),
];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
AbsCoord::new(1, 1),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.out_degree(VertexId(0)), 3);
assert_eq!(csr.out_degree(VertexId(1)), 1);
assert_eq!(csr.out_degree(VertexId(2)), 0);
assert_eq!(csr.out_degree(VertexId(3)), 2);
}
#[test]
fn test_out_of_bounds_access() {
let edges = vec![(0u32, vec![])];
let coords = vec![AbsCoord::new(0, 0)];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.out_edges(VertexId(1)), &[]);
}
#[test]
fn test_csr_iterator() {
let edges = vec![
(0u32, vec![1u32, 2u32]),
(1u32, vec![3u32]),
(2u32, vec![1u32, 3u32]),
(3u32, vec![]),
];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
AbsCoord::new(1, 1),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
let collected: Vec<_> = csr.iter().collect();
assert_eq!(collected.len(), 4);
assert_eq!(collected[0].0, VertexId(0));
assert_eq!(collected[0].1, &[VertexId(1), VertexId(2)]);
assert_eq!(collected[3].1, &[]);
}
#[test]
fn test_has_edge() {
let edges = vec![
(0u32, vec![1u32, 2u32]),
(1u32, vec![3u32]),
(2u32, vec![]),
(3u32, vec![0u32]), ];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
AbsCoord::new(1, 1),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert!(csr.has_edge(VertexId(0), VertexId(1)));
assert!(csr.has_edge(VertexId(0), VertexId(2)));
assert!(!csr.has_edge(VertexId(0), VertexId(3)));
assert!(csr.has_edge(VertexId(3), VertexId(0))); assert!(!csr.has_edge(VertexId(2), VertexId(0))); }
#[test]
fn test_csr_with_offset_vertex_ids() {
let base_id = 1024u32;
let edges = vec![
(base_id, vec![base_id + 1, base_id + 2]),
(base_id + 1, vec![base_id + 3]),
(base_id + 2, vec![base_id + 3]),
(base_id + 3, vec![]),
];
let coords = vec![
AbsCoord::new(0, 0),
AbsCoord::new(0, 1),
AbsCoord::new(1, 0),
AbsCoord::new(1, 1),
];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.min_vertex_id, base_id);
assert_eq!(
csr.out_edges(VertexId(base_id)),
&[VertexId(base_id + 1), VertexId(base_id + 2)]
);
assert_eq!(
csr.out_edges(VertexId(base_id + 1)),
&[VertexId(base_id + 3)]
);
assert_eq!(
csr.out_edges(VertexId(base_id + 2)),
&[VertexId(base_id + 3)]
);
assert_eq!(csr.out_edges(VertexId(base_id + 3)), &[]);
assert_eq!(csr.out_edges(VertexId(0)), &[]); assert_eq!(csr.out_edges(VertexId(base_id + 100)), &[]); }
#[test]
fn test_csr_with_sparse_vertex_ids() {
let edges = vec![
(100u32, vec![300u32, 500u32]),
(300u32, vec![500u32]),
(500u32, vec![100u32]), ];
let coords = vec![
AbsCoord::new(0, 0), AbsCoord::new(0, 0), AbsCoord::new(1, 0), AbsCoord::new(0, 0), AbsCoord::new(2, 0), ];
let csr = CsrEdges::from_adjacency(edges, &coords);
assert_eq!(csr.min_vertex_id, 100);
assert_eq!(
csr.out_edges(VertexId(100)),
&[VertexId(300), VertexId(500)]
);
assert_eq!(csr.out_edges(VertexId(300)), &[VertexId(500)]);
assert_eq!(csr.out_edges(VertexId(500)), &[VertexId(100)]);
assert_eq!(csr.out_edges(VertexId(200)), &[]);
assert_eq!(csr.out_edges(VertexId(400)), &[]);
}
}
#[derive(Debug, Clone)]
pub struct CsrEdges {
offsets: Vec<u32>,
edges: Vec<VertexId>,
reverse_offsets: Vec<u32>,
reverse_edges: Vec<VertexId>,
min_vertex_id: u32,
}
impl CsrEdges {
pub fn from_adjacency(adj: Vec<(u32, Vec<u32>)>, coords: &[AbsCoord]) -> Self {
if adj.is_empty() {
return Self {
offsets: vec![0],
edges: Vec::new(),
reverse_offsets: vec![0],
reverse_edges: Vec::new(),
min_vertex_id: 0,
};
}
let mut min_id = u32::MAX;
let mut max_id = 0;
for &(vid, ref targets) in &adj {
min_id = min_id.min(vid);
max_id = max_id.max(vid);
for &target in targets {
min_id = min_id.min(target);
max_id = max_id.max(target);
}
}
if min_id == u32::MAX {
return Self {
offsets: vec![0],
edges: Vec::new(),
reverse_offsets: vec![0],
reverse_edges: Vec::new(),
min_vertex_id: 0,
};
}
let num_vertices = (max_id - min_id + 1) as usize;
let mut offsets = vec![0u32; num_vertices + 1];
let mut edges = Vec::new();
let mut adj_by_offset: Vec<Vec<u32>> = vec![Vec::new(); num_vertices];
for (vid, targets) in adj {
let offset_idx = (vid - min_id) as usize;
adj_by_offset[offset_idx] = targets;
}
for (idx, mut targets) in adj_by_offset.clone().into_iter().enumerate() {
targets.sort_by_key(|&t| {
let coord_idx = (t - min_id) as usize;
if coord_idx < coords.len() {
let coord = coords[coord_idx];
(coord.row(), coord.col(), t)
} else {
(u32::MAX, u32::MAX, t)
}
});
edges.extend(targets.into_iter().map(VertexId));
offsets[idx + 1] = edges.len() as u32;
}
let mut reverse_offsets = vec![0u32; num_vertices + 1];
let mut reverse_edges = Vec::new();
let mut reverse_adj: Vec<Vec<u32>> = vec![Vec::new(); num_vertices];
for (idx, targets) in adj_by_offset.into_iter().enumerate() {
let source = min_id + idx as u32;
for target in targets {
let target_idx = (target - min_id) as usize;
if target_idx < num_vertices {
reverse_adj[target_idx].push(source);
}
}
}
for (idx, mut sources) in reverse_adj.into_iter().enumerate() {
sources.sort_by_key(|&s| {
let coord_idx = (s - min_id) as usize;
if coord_idx < coords.len() {
let coord = coords[coord_idx];
(coord.row(), coord.col(), s)
} else {
(u32::MAX, u32::MAX, s)
}
});
reverse_edges.extend(sources.into_iter().map(VertexId));
reverse_offsets[idx + 1] = reverse_edges.len() as u32;
}
Self {
offsets,
edges,
reverse_offsets,
reverse_edges,
min_vertex_id: min_id,
}
}
#[inline]
pub fn out_edges(&self, v: VertexId) -> &[VertexId] {
if self.offsets.len() <= 1 {
return &[];
}
if v.0 < self.min_vertex_id {
return &[];
}
let idx = (v.0 - self.min_vertex_id) as usize;
if idx >= self.offsets.len() - 1 {
return &[];
}
let start = self.offsets[idx] as usize;
let end = self.offsets[idx + 1] as usize;
&self.edges[start..end]
}
#[inline]
pub fn in_edges(&self, v: VertexId) -> &[VertexId] {
if self.reverse_offsets.len() <= 1 {
return &[];
}
if v.0 < self.min_vertex_id {
return &[];
}
let idx = (v.0 - self.min_vertex_id) as usize;
if idx >= self.reverse_offsets.len() - 1 {
return &[];
}
let start = self.reverse_offsets[idx] as usize;
let end = self.reverse_offsets[idx + 1] as usize;
&self.reverse_edges[start..end]
}
#[inline]
pub fn out_degree(&self, v: VertexId) -> usize {
if self.offsets.len() <= 1 {
return 0;
}
if v.0 < self.min_vertex_id {
return 0;
}
let idx = (v.0 - self.min_vertex_id) as usize;
if idx >= self.offsets.len() - 1 {
return 0;
}
let start = self.offsets[idx];
let end = self.offsets[idx + 1];
(end - start) as usize
}
#[inline]
pub fn in_degree(&self, v: VertexId) -> usize {
self.in_edges(v).len()
}
#[inline]
pub fn num_vertices(&self) -> usize {
self.offsets.len().saturating_sub(1)
}
#[inline]
pub fn num_edges(&self) -> usize {
self.edges.len()
}
pub fn memory_usage(&self) -> usize {
self.offsets.len() * std::mem::size_of::<u32>()
+ self.edges.len() * std::mem::size_of::<VertexId>()
+ self.reverse_offsets.len() * std::mem::size_of::<u32>()
+ self.reverse_edges.len() * std::mem::size_of::<VertexId>()
}
pub fn empty() -> Self {
Self {
offsets: vec![0],
edges: Vec::new(),
reverse_offsets: vec![0],
reverse_edges: Vec::new(),
min_vertex_id: 0,
}
}
pub fn builder() -> CsrBuilder {
CsrBuilder::new()
}
pub fn iter(&'_ self) -> CsrIterator<'_> {
CsrIterator {
csr: self,
current_vertex: 0,
}
}
pub fn has_edge(&self, from: VertexId, to: VertexId) -> bool {
self.out_edges(from).contains(&to)
}
}
pub struct CsrIterator<'a> {
csr: &'a CsrEdges,
current_vertex: usize,
}
impl<'a> Iterator for CsrIterator<'a> {
type Item = (VertexId, &'a [VertexId]);
fn next(&mut self) -> Option<Self::Item> {
if self.current_vertex >= self.csr.num_vertices() {
return None;
}
let vertex_id = VertexId(self.current_vertex as u32 + self.csr.min_vertex_id);
let edges = self.csr.out_edges(vertex_id);
self.current_vertex += 1;
Some((vertex_id, edges))
}
}
pub struct CsrBuilder {
adjacency: Vec<Vec<usize>>,
coords: Vec<AbsCoord>,
}
impl Default for CsrBuilder {
fn default() -> Self {
Self::new()
}
}
impl CsrBuilder {
pub fn new() -> Self {
Self {
adjacency: Vec::new(),
coords: Vec::new(),
}
}
pub fn add_vertex(&mut self, coord: AbsCoord) -> usize {
let idx = self.adjacency.len();
self.adjacency.push(Vec::new());
self.coords.push(coord);
idx
}
pub fn add_edge(&mut self, from: usize, to: usize) {
if from < self.adjacency.len() {
self.adjacency[from].push(to);
}
}
pub fn build(self) -> CsrEdges {
let adj: Vec<_> = self
.adjacency
.into_iter()
.enumerate()
.map(|(idx, edges)| (idx as u32, edges.into_iter().map(|e| e as u32).collect()))
.collect();
CsrEdges::from_adjacency(adj, &self.coords)
}
}