hydro2_network/
validate.rs

1// ---------------- [ File: src/validate.rs ]
2crate::ix!();
3
4impl<NetworkItem> Network<NetworkItem> 
5where NetworkItem: Debug + Send + Sync
6{
7    pub fn validate(&self) -> NetResult<()> {
8        let node_count = self.nodes().len();
9
10        // 1) check each edge's node idx, port idx
11        for (edge_idx, edge) in self.edges().iter().enumerate() {
12
13            let src = edge.source_index();
14            let so  = edge.source_output_idx();
15            let dst = edge.dest_index();
16            let di  = edge.dest_input_idx();
17
18            if *src >= node_count {
19                return Err(NetworkError::InvalidConfiguration {
20                    details: format!("Edge #{} has invalid src node={}", edge_idx, src),
21                });
22            }
23            if *dst >= node_count {
24                return Err(NetworkError::InvalidConfiguration {
25                    details: format!("Edge #{} has invalid dst node={}", edge_idx, dst),
26                });
27            }
28            if *so >= 4 {
29                return Err(NetworkError::InvalidConfiguration {
30                    details: format!("Edge #{} has invalid src port={}", edge_idx, so),
31                });
32            }
33            if *di >= 4 {
34                return Err(NetworkError::InvalidConfiguration {
35                    details: format!("Edge #{} has invalid dst port={}", edge_idx, di),
36                });
37            }
38        }
39
40        // 2) check for cycles => BFS or Kahn's
41        let mut in_degree = vec![0; node_count];
42        for edge in self.edges() {
43            in_degree[*edge.dest_index()] += 1;
44        }
45
46        let mut queue = std::collections::VecDeque::new();
47        for i in 0..node_count {
48            if in_degree[i] == 0 {
49                queue.push_back(i);
50            }
51        }
52        let mut processed = 0;
53        while let Some(n) = queue.pop_front() {
54            processed += 1;
55            // decrement children
56            for e in self.edges() {
57                if *e.source_index() == n {
58                    let dst = e.dest_index();
59                    in_degree[*dst] -= 1;
60                    if in_degree[*dst] == 0 {
61                        queue.push_back(*dst);
62                    }
63                }
64            }
65        }
66        if processed < node_count {
67            return Err(NetworkError::InvalidConfiguration {
68                details: "Cycle detected in network graph".into()
69            });
70        }
71
72        Ok(())
73    }
74}
75
76#[cfg(test)]
77mod validate_network_tests {
78    use super::*;
79
80    #[test]
81    fn test_validate_empty_ok() -> Result<(), NetworkError> {
82        let net = NetworkBuilder::<TestWireIO<i32>>::default()
83            .nodes(vec![])
84            .edges(vec![])
85            .build()
86            .unwrap();
87        net.validate()?;
88        Ok(())
89    }
90
91    #[test]
92    fn test_validate_single_node_no_edges_ok() -> Result<(), NetworkError> {
93        // single node => no edges => no cycle => valid
94        let n0: NetworkNode<TestWireIO<i32>> = node!(0 => NoOpOperator::default());
95        let net = NetworkBuilder::<TestWireIO<i32>>::default()
96            .nodes(vec![n0])
97            .edges(vec![])
98            .build()
99            .unwrap();
100        net.validate()?;
101        Ok(())
102    }
103
104    #[test]
105    fn test_validate_cycle() {
106        // Node0 => Node1 => Node0
107        let n0: NetworkNode<TestWireIO<i32>> = node!(0 => NoOpOperator::default());
108        let n1: NetworkNode<TestWireIO<i32>> = node!(1 => NoOpOperator::default());
109        let e0 = edge!(0:0 -> 1:0);
110        let e1 = edge!(1:0 -> 0:0); // cycle
111
112        let net = NetworkBuilder::<TestWireIO<i32>>::default()
113            .nodes(vec![n0, n1])
114            .edges(vec![e0, e1])
115            .build()
116            .unwrap();
117
118        let res = net.validate();
119        assert!(res.is_err());
120        if let Err(NetworkError::InvalidConfiguration{details}) = res {
121            assert!(details.contains("Cycle detected"), "Expected cycle error, got: {}", details);
122        }
123    }
124
125    #[test]
126    fn test_validate_out_of_range_edge() {
127        // Node0 => Node1 => but we have an edge referencing node=99
128        let n0: NetworkNode<TestWireIO<i32>> = node!(0 => ConstantOp::new(42));
129        let n1: NetworkNode<TestWireIO<i32>> = node!(1 => AddOp::new(5));
130        // edge => source=0 => dest=99 => invalid
131        let e_bad = edge!(0:0 -> 99:0);
132        let net = NetworkBuilder::<TestWireIO<i32>>::default()
133            .nodes(vec![n0, n1])
134            .edges(vec![e_bad])
135            .build()
136            .unwrap();
137
138        let res = net.validate();
139        assert!(res.is_err());
140        if let Err(NetworkError::InvalidConfiguration{details}) = res {
141            assert!(details.contains("invalid dst node=99"));
142        }
143    }
144
145    #[test]
146    fn test_validate_disconnected_multiple_nodes() -> Result<(), NetworkError> {
147        // 3 nodes => no edges => no cycle => valid
148        // It's “disconnected” but no rule says we must connect them.
149        let n0: NetworkNode<TestWireIO<i32>> = node!(0 => ConstantOp::new(1));
150        let n1: NetworkNode<TestWireIO<i32>> = node!(1 => AddOp::new(5));
151        let n2: NetworkNode<TestWireIO<i32>> = node!(2 => MultiplyOp::new(2));
152
153        let net = NetworkBuilder::<TestWireIO<i32>>::default()
154            .nodes(vec![n0, n1, n2])
155            .edges(vec![])
156            .build()
157            .unwrap();
158
159        net.validate()?;
160        Ok(())
161    }
162
163    #[test]
164    fn test_validate_ok_chained() -> Result<(), NetworkError> {
165        // Node0 => Node1 => Node2 => no cycle
166        let n0: NetworkNode<TestWireIO<i32>> = node!(0 => ConstantOp::new(1));
167        let n1: NetworkNode<TestWireIO<i32>> = node!(1 => MultiplyOp::new(5));
168        let n2: NetworkNode<TestWireIO<i32>> = node!(2 => AddOp::new(100));
169        let e0 = edge!(0:0 -> 1:0);
170        let e1 = edge!(1:0 -> 2:0);
171
172        let net = NetworkBuilder::<TestWireIO<i32>>::default()
173            .nodes(vec![n0, n1, n2])
174            .edges(vec![e0, e1])
175            .build()
176            .unwrap();
177
178        net.validate()?;
179        Ok(())
180    }
181}