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)]
22pub struct NetworkData {
23 units: TabularData,
24 edges: Arc<[NetworkEdge]>,
25 incoming: Arc<[Arc<[NetworkEdge]>]>,
26}
27
28impl NetworkData {
29 pub fn try_new(
35 units: TabularData,
36 edges: impl Into<Arc<[NetworkEdge]>>,
37 ) -> Result<Self, DataError> {
38 let edges = edges.into();
39 let n = units.row_count();
40 let mut sorted = edges.to_vec();
41 sorted.sort_by_key(|edge| (edge.from, edge.to));
42 for (i, edge) in sorted.iter().enumerate() {
43 if edge.from as usize >= n || edge.to as usize >= n {
44 return Err(DataError::InvalidArgument {
45 message: "network edge row index is outside the unit table".into(),
46 });
47 }
48 if edge.from == edge.to {
49 return Err(DataError::InvalidArgument {
50 message: "network self-edges are not allowed".into(),
51 });
52 }
53 if !edge.weight.is_finite() || edge.weight < 0.0 {
54 return Err(DataError::InvalidArgument {
55 message: "network weights must be finite and non-negative".into(),
56 });
57 }
58 if i > 0 && (sorted[i - 1].from, sorted[i - 1].to) == (edge.from, edge.to) {
59 return Err(DataError::InvalidArgument {
60 message: "duplicate network edge".into(),
61 });
62 }
63 }
64 let edges: Arc<[NetworkEdge]> = sorted.into();
65 let mut incoming = vec![Vec::new(); n];
66 for edge in edges.iter().copied() {
67 incoming[edge.to as usize].push(edge);
68 }
69 let incoming =
70 incoming.into_iter().map(Arc::<[NetworkEdge]>::from).collect::<Vec<_>>().into();
71 Ok(Self { units, edges, incoming })
72 }
73
74 #[must_use]
76 pub const fn units(&self) -> &TabularData {
77 &self.units
78 }
79
80 #[must_use]
82 pub fn edges(&self) -> &[NetworkEdge] {
83 &self.edges
84 }
85
86 pub fn incoming(&self, unit: usize) -> Result<&[NetworkEdge], DataError> {
92 self.incoming.get(unit).map(AsRef::as_ref).ok_or(DataError::InvalidArgument {
93 message: "network unit index is out of range".into(),
94 })
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn empty_network_is_valid_and_has_no_incoming_edges() {
104 let values = [1.0, 2.0];
105 let table = TabularData::from_f64_columns([("y", values.as_slice())]).unwrap();
106 let network = NetworkData::try_new(table, []).unwrap();
107 assert!(network.incoming(0).unwrap().is_empty());
108 }
109}