1use core::fmt;
12
13use crate::{HEADER_SIZE, MAGIC, NODE_STRIDE, VERSION};
14
15#[derive(Debug, Clone, PartialEq)]
38pub struct GraphNode {
39 pub id: u32,
41 pub x: f32,
43 pub y: f32,
45 pub domain: u16,
47 pub category: u16,
49 pub flags: u8,
51 pub degree: u32,
53 pub label: String,
55 pub iri: String,
57}
58
59#[derive(Debug, Clone, Default, PartialEq, Eq)]
66pub struct Csr {
67 pub row_ptr: Vec<u32>,
69 pub col_idx: Vec<u32>,
71 pub edge_type: Vec<u8>,
73}
74
75#[derive(Debug, Clone, Default, PartialEq)]
78pub struct Graph {
79 pub nodes: Vec<GraphNode>,
81 pub csr: Csr,
83}
84
85impl Graph {
86 pub fn encode(&self) -> Result<Vec<u8>, EncodeError> {
91 encode(&self.nodes, &self.csr)
92 }
93}
94
95#[must_use]
110pub fn build_csr(num_nodes: usize, edges: &[(u32, u32, u8)]) -> Csr {
111 let mut counts = vec![0u32; num_nodes];
112 for (src, _, _) in edges {
113 counts[*src as usize] += 1;
114 }
115 let mut row_ptr = vec![0u32; num_nodes + 1];
116 for i in 0..num_nodes {
117 row_ptr[i + 1] = row_ptr[i] + counts[i];
118 }
119 let mut col_idx = vec![0u32; edges.len()];
120 let mut edge_type = vec![0u8; edges.len()];
121 let mut cursor: Vec<u32> = row_ptr[..num_nodes].to_vec();
122 for (src, tgt, t) in edges {
123 let p = cursor[*src as usize] as usize;
124 col_idx[p] = *tgt;
125 edge_type[p] = *t;
126 cursor[*src as usize] += 1;
127 }
128 Csr {
129 row_ptr,
130 col_idx,
131 edge_type,
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
137#[non_exhaustive]
138pub enum EncodeError {
139 RowPtrLength {
141 found: usize,
143 expected: usize,
145 },
146 EdgeTypeLength {
148 found: usize,
150 expected: usize,
152 },
153 RowPtr {
155 index: usize,
157 },
158 EdgeTarget {
160 edge: usize,
162 target: u32,
164 },
165 TooLarge,
167}
168
169impl fmt::Display for EncodeError {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 Self::RowPtrLength { found, expected } => {
173 write!(f, "row_ptr length {found}, expected {expected}")
174 }
175 Self::EdgeTypeLength { found, expected } => {
176 write!(f, "edge_type length {found}, expected {expected}")
177 }
178 Self::RowPtr { index } => write!(f, "row_ptr[{index}] breaks the CSR invariants"),
179 Self::EdgeTarget { edge, target } => {
180 write!(f, "edge {edge} targets node {target}, which does not exist")
181 }
182 Self::TooLarge => f.write_str("graph exceeds the u32 limits of the NGG1 format"),
183 }
184 }
185}
186
187impl std::error::Error for EncodeError {}
188
189const fn pad4(n: usize) -> usize {
190 (4 - (n & 3)) & 3
191}
192
193fn to_u32(v: usize) -> Result<u32, EncodeError> {
194 u32::try_from(v).map_err(|_| EncodeError::TooLarge)
195}
196
197pub fn encode(nodes: &[GraphNode], csr: &Csr) -> Result<Vec<u8>, EncodeError> {
231 let node_count = nodes.len();
232 let edge_count = csr.col_idx.len();
233 if csr.row_ptr.len() != node_count + 1 {
234 return Err(EncodeError::RowPtrLength {
235 found: csr.row_ptr.len(),
236 expected: node_count + 1,
237 });
238 }
239 if csr.edge_type.len() != edge_count {
240 return Err(EncodeError::EdgeTypeLength {
241 found: csr.edge_type.len(),
242 expected: edge_count,
243 });
244 }
245 let mut last = 0u32;
246 if csr.row_ptr[0] != 0 {
247 return Err(EncodeError::RowPtr { index: 0 });
248 }
249 for (i, &v) in csr.row_ptr.iter().enumerate().skip(1) {
250 if v < last || v as usize > edge_count {
251 return Err(EncodeError::RowPtr { index: i });
252 }
253 last = v;
254 }
255 if last as usize != edge_count {
256 return Err(EncodeError::RowPtr { index: node_count });
257 }
258 if let Some((edge, &target)) = csr
259 .col_idx
260 .iter()
261 .enumerate()
262 .find(|&(_, &t)| t as usize >= node_count)
263 {
264 return Err(EncodeError::EdgeTarget { edge, target });
265 }
266
267 let n = node_count as u64;
269 let e = edge_count as u64;
270 let off_nodes = HEADER_SIZE as u64;
271 let off_adjacency = off_nodes + n * NODE_STRIDE as u64;
272 let off_edge_types = off_adjacency + (n + 1) * 4 + e * 4;
273 let off_strings = off_edge_types + e + pad4(edge_count) as u64;
274 let off_nodes = HEADER_SIZE as u32;
275 let off_adjacency = u32::try_from(off_adjacency).map_err(|_| EncodeError::TooLarge)?;
276 let off_edge_types = u32::try_from(off_edge_types).map_err(|_| EncodeError::TooLarge)?;
277 let off_strings = u32::try_from(off_strings).map_err(|_| EncodeError::TooLarge)?;
278
279 let mut out: Vec<u8> = Vec::with_capacity(off_strings as usize + 8 + node_count * 8);
280 out.extend_from_slice(MAGIC);
281 out.extend_from_slice(&VERSION.to_le_bytes());
282 out.extend_from_slice(&0u16.to_le_bytes()); for v in [
284 to_u32(node_count)?,
285 to_u32(edge_count)?,
286 off_nodes,
287 off_adjacency,
288 off_edge_types,
289 off_strings,
290 ] {
291 out.extend_from_slice(&v.to_le_bytes());
292 }
293
294 for node in nodes {
295 out.extend_from_slice(&node.id.to_le_bytes());
296 out.extend_from_slice(&node.x.to_le_bytes());
297 out.extend_from_slice(&node.y.to_le_bytes());
298 out.extend_from_slice(&node.domain.to_le_bytes());
299 out.extend_from_slice(&node.category.to_le_bytes());
300 out.push(node.flags);
301 out.extend_from_slice(&[0u8; 3]); out.extend_from_slice(&node.degree.to_le_bytes());
303 }
304
305 for v in &csr.row_ptr {
306 out.extend_from_slice(&v.to_le_bytes());
307 }
308 for v in &csr.col_idx {
309 out.extend_from_slice(&v.to_le_bytes());
310 }
311
312 out.extend_from_slice(&csr.edge_type);
313 out.extend(std::iter::repeat_n(0u8, pad4(edge_count)));
314
315 let mut blob: Vec<u8> = Vec::new();
316 let mut offsets: Vec<u32> = Vec::with_capacity(node_count * 2);
317 for node in nodes {
318 offsets.push(to_u32(blob.len())?);
319 blob.extend_from_slice(node.label.as_bytes());
320 offsets.push(to_u32(blob.len())?);
321 blob.extend_from_slice(node.iri.as_bytes());
322 }
323 out.extend_from_slice(&to_u32(offsets.len())?.to_le_bytes());
324 out.extend_from_slice(&to_u32(blob.len())?.to_le_bytes());
325 for v in &offsets {
326 out.extend_from_slice(&v.to_le_bytes());
327 }
328 out.extend_from_slice(&blob);
329
330 debug_assert_eq!(
331 out.len(),
332 off_strings as usize + 8 + offsets.len() * 4 + blob.len()
333 );
334 Ok(out)
335}