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