antecedent_data/
network.rs1use std::sync::Arc;
6
7use crate::{DataError, TableView, TabularData};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct NetworkEdge {
12 pub from: u32,
14 pub to: u32,
16 pub weight: f64,
18}
19
20#[derive(Clone, Debug)]
27pub struct NetworkData {
28 units: TabularData,
29 edges: Arc<[NetworkEdge]>,
30 incoming_edges: Arc<[NetworkEdge]>,
33 incoming_offsets: Arc<[usize]>,
34}
35
36impl NetworkData {
37 pub fn try_new(
43 units: TabularData,
44 edges: impl Into<Arc<[NetworkEdge]>>,
45 ) -> Result<Self, DataError> {
46 let edges = edges.into();
47 let n = units.row_count();
48 let mut sorted = edges.to_vec();
49 sorted.sort_by_key(|edge| (edge.from, edge.to));
50 for (i, edge) in sorted.iter().enumerate() {
51 if edge.from as usize >= n || edge.to as usize >= n {
52 return Err(DataError::InvalidArgument {
53 message: "network edge row index is outside the unit table".into(),
54 });
55 }
56 if edge.from == edge.to {
57 return Err(DataError::InvalidArgument {
58 message: "network self-edges are not allowed".into(),
59 });
60 }
61 if !edge.weight.is_finite() || edge.weight < 0.0 {
62 return Err(DataError::InvalidArgument {
63 message: "network weights must be finite and non-negative".into(),
64 });
65 }
66 if i > 0 && (sorted[i - 1].from, sorted[i - 1].to) == (edge.from, edge.to) {
67 return Err(DataError::InvalidArgument {
68 message: "duplicate network edge".into(),
69 });
70 }
71 }
72 let edges: Arc<[NetworkEdge]> = sorted.into();
73 let mut by_to = edges.to_vec();
76 by_to.sort_by_key(|edge| (edge.to, edge.from));
77 let mut incoming_offsets = vec![0usize; n + 1];
78 for edge in &by_to {
79 incoming_offsets[edge.to as usize + 1] += 1;
80 }
81 for u in 0..n {
82 incoming_offsets[u + 1] += incoming_offsets[u];
83 }
84 Ok(Self {
85 units,
86 edges,
87 incoming_edges: by_to.into(),
88 incoming_offsets: incoming_offsets.into(),
89 })
90 }
91
92 #[must_use]
94 pub const fn units(&self) -> &TabularData {
95 &self.units
96 }
97
98 #[must_use]
100 pub fn edges(&self) -> &[NetworkEdge] {
101 &self.edges
102 }
103
104 pub fn incoming(&self, unit: usize) -> Result<&[NetworkEdge], DataError> {
110 if unit + 1 >= self.incoming_offsets.len() {
111 return Err(DataError::InvalidArgument {
112 message: "network unit index is out of range".into(),
113 });
114 }
115 Ok(&self.incoming_edges[self.incoming_offsets[unit]..self.incoming_offsets[unit + 1]])
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn empty_network_is_valid_and_has_no_incoming_edges() {
125 let values = [1.0, 2.0];
126 let table = TabularData::from_f64_columns([("y", values.as_slice())]).unwrap();
127 let network = NetworkData::try_new(table, []).unwrap();
128 assert!(network.incoming(0).unwrap().is_empty());
129 }
130}