1use crate::ordinal::{Ordinal, to_usize};
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
12pub enum CsrError {
13 #[error("ordinal {ordinal} is out of range for {nodes} nodes")]
15 OutOfRange {
16 ordinal: Ordinal,
18 nodes: u32,
20 },
21 #[error("offsets are inconsistent with {targets} targets")]
23 Offsets {
24 targets: usize,
26 },
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Csr {
32 offsets: Vec<u32>,
33 targets: Vec<u32>,
34}
35
36impl Csr {
37 pub fn build(
46 nodes: u32,
47 edges: impl IntoIterator<Item = (Ordinal, Ordinal)>,
48 ) -> Result<Self, CsrError> {
49 let mut pairs: Vec<(u32, u32)> = Vec::new();
50 for (from, to) in edges {
51 for ordinal in [from, to] {
52 if ordinal.index() >= nodes {
53 return Err(CsrError::OutOfRange { ordinal, nodes });
54 }
55 }
56 pairs.push((from.index(), to.index()));
57 }
58 pairs.sort_unstable();
59 pairs.dedup();
60 let count = |targets: &Vec<u32>| {
63 let Ok(count) = u32::try_from(targets.len()) else {
64 return Err(CsrError::Offsets {
65 targets: targets.len(),
66 });
67 };
68 Ok(count)
69 };
70 let mut offsets = Vec::with_capacity(to_usize(nodes).saturating_add(1));
71 let mut targets = Vec::with_capacity(pairs.len());
72 let mut cursor = 0_usize;
73 for node in 0..nodes {
74 offsets.push(count(&targets)?);
75 while let Some((from, to)) = pairs.get(cursor).copied() {
76 if from != node {
77 break;
78 }
79 targets.push(to);
80 cursor = cursor.saturating_add(1);
81 }
82 }
83 offsets.push(count(&targets)?);
84 Ok(Self { offsets, targets })
85 }
86
87 pub fn from_parts(offsets: Vec<u32>, targets: Vec<u32>) -> Result<Self, CsrError> {
94 let consistent = offsets.first() == Some(&0)
95 && offsets.windows(2).all(|w| w.first() <= w.get(1))
96 && offsets.last().copied().map(to_usize) == Some(targets.len());
97 if !consistent {
98 return Err(CsrError::Offsets {
99 targets: targets.len(),
100 });
101 }
102 Ok(Self { offsets, targets })
103 }
104
105 #[must_use]
107 pub fn nodes(&self) -> u32 {
108 u32::try_from(self.offsets.len().saturating_sub(1)).unwrap_or(u32::MAX)
109 }
110
111 #[must_use]
113 pub fn edges(&self) -> usize {
114 self.targets.len()
115 }
116
117 #[must_use]
120 pub fn size_in_bytes(&self) -> usize {
121 crate::footprint::vector(&self.offsets)
122 .saturating_add(crate::footprint::vector(&self.targets))
123 }
124
125 #[must_use]
127 pub fn neighbours(&self, node: Ordinal) -> &[u32] {
128 let start = self.offsets.get(node.as_usize()).copied();
129 let end = self.offsets.get(node.as_usize().saturating_add(1)).copied();
130 match (start, end) {
131 (Some(start), Some(end)) => self
132 .targets
133 .get(to_usize(start)..to_usize(end))
134 .unwrap_or(&[]),
135 _ => &[],
136 }
137 }
138
139 #[must_use]
141 pub fn offsets(&self) -> &[u32] {
142 &self.offsets
143 }
144
145 #[must_use]
147 pub fn targets(&self) -> &[u32] {
148 &self.targets
149 }
150
151 pub fn transpose(&self) -> Result<Self, CsrError> {
157 let nodes = self.nodes();
158 let reversed = (0..nodes).flat_map(|from| {
159 self.neighbours(Ordinal::new(from))
160 .iter()
161 .map(move |to| (Ordinal::new(*to), Ordinal::new(from)))
162 });
163 Self::build(nodes, reversed)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::{Csr, CsrError};
170 use crate::ordinal::Ordinal;
171
172 fn o(i: u32) -> Ordinal {
173 Ordinal::new(i)
174 }
175
176 #[test]
177 fn rows_are_sorted_and_deduplicated() {
178 let csr = Csr::build(4, [(o(2), o(1)), (o(0), o(3)), (o(0), o(1)), (o(0), o(1))])
179 .expect("builds");
180 assert_eq!(csr.nodes(), 4);
181 assert_eq!(csr.edges(), 3);
182 assert_eq!(csr.neighbours(o(0)), &[1, 3]);
183 assert!(csr.neighbours(o(1)).is_empty());
184 assert_eq!(csr.neighbours(o(2)), &[1]);
185 assert!(csr.neighbours(o(9)).is_empty());
186 assert_eq!(csr.offsets(), &[0, 2, 2, 3, 3]);
187 }
188
189 #[test]
190 fn transpose_reverses_every_edge() {
191 let csr = Csr::build(3, [(o(0), o(1)), (o(0), o(2)), (o(1), o(2))]).expect("builds");
192 let back = csr.transpose().expect("transposes");
193 assert_eq!(back.neighbours(o(2)), &[0, 1]);
194 assert_eq!(back.neighbours(o(1)), &[0]);
195 assert!(back.neighbours(o(0)).is_empty());
196 assert_eq!(back.transpose().expect("transposes"), csr);
197 }
198
199 #[test]
200 fn out_of_range_and_inconsistent_parts_are_refused() {
201 assert_eq!(
202 Csr::build(2, [(o(0), o(2))]),
203 Err(CsrError::OutOfRange {
204 ordinal: o(2),
205 nodes: 2
206 })
207 );
208 assert!(Csr::from_parts(vec![0, 2, 1], vec![1]).is_err());
209 assert!(Csr::from_parts(vec![1, 1], Vec::new()).is_err());
210 assert!(Csr::from_parts(vec![0, 1], vec![0]).is_ok());
211 }
212}