limen_core/graph/
validate.rs1use crate::edge::link::EdgeDescriptor;
4use crate::errors::GraphError;
5use crate::node::link::NodeDescriptor;
6use crate::node::source::EXTERNAL_INGRESS_NODE;
7use crate::node::NodeKind;
8
9#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::vec;
12
13pub trait GraphValidator {
15 fn validate(&self) -> Result<(), GraphError>;
17}
18
19#[derive(Debug, Clone)]
21pub struct GraphDescBuf<const N: usize, const E: usize> {
22 nodes: [NodeDescriptor; N],
24 edges: [EdgeDescriptor; E],
26}
27
28impl<const N: usize, const E: usize> GraphDescBuf<N, E> {
29 #[inline]
31 pub fn new(nodes: [NodeDescriptor; N], edges: [EdgeDescriptor; E]) -> Self {
32 Self { nodes, edges }
33 }
34
35 #[inline]
37 pub fn nodes(&self) -> &[NodeDescriptor; N] {
38 &self.nodes
39 }
40
41 #[inline]
43 pub fn edges(&self) -> &[EdgeDescriptor; E] {
44 &self.edges
45 }
46}
47
48impl<const N: usize, const E: usize> GraphValidator for GraphDescBuf<N, E> {
49 #[inline]
50 fn validate(&self) -> Result<(), GraphError> {
51 validate_ports(&self.nodes, &self.edges)?;
52 #[cfg(not(feature = "alloc"))]
53 {
54 validate_acyclic_buf::<N>(&self.nodes, &self.edges)?;
55 }
56 #[cfg(feature = "alloc")]
57 {
58 validate_acyclic_alloc(&self.nodes, &self.edges)?;
59 }
60
61 Ok(())
62 }
63}
64
65pub fn validate_ports(
72 nodes: &[NodeDescriptor],
73 edges: &[EdgeDescriptor],
74) -> Result<(), GraphError> {
75 let n = nodes.len();
76
77 for (i, nd) in nodes.iter().enumerate() {
79 if nd.id().as_usize() != &i {
80 return Err(GraphError::IncompatiblePorts);
81 }
82 match nd.kind() {
83 NodeKind::Source => {
84 if nd.in_ports() != &0 || nd.out_ports() < &1 {
85 return Err(GraphError::IncompatiblePorts);
86 }
87 }
88 NodeKind::Sink => {
89 if nd.in_ports() < &1 || nd.out_ports() != &0 {
90 return Err(GraphError::IncompatiblePorts);
91 }
92 }
93 NodeKind::Split => {
94 if nd.in_ports() < &1 || nd.out_ports() < &2 {
95 return Err(GraphError::IncompatiblePorts);
96 }
97 }
98 NodeKind::Join => {
99 if nd.in_ports() < &2 || nd.out_ports() < &1 {
100 return Err(GraphError::IncompatiblePorts);
101 }
102 }
103 NodeKind::Process | NodeKind::Model => {
104 if nd.in_ports() < &1 || nd.out_ports() < &1 {
105 return Err(GraphError::IncompatiblePorts);
106 }
107 }
108 NodeKind::External => {
109 }
111 }
112 }
113
114 for (i, ed) in edges.iter().enumerate() {
118 if ed.id().as_usize() != &i {
120 return Err(GraphError::IncompatiblePorts);
121 }
122
123 let is_monitor = ed.upstream().node() == &EXTERNAL_INGRESS_NODE;
124 let t = ed.downstream().node().as_usize();
125
126 if is_monitor {
127 if t >= &n {
129 return Err(GraphError::IncompatiblePorts);
130 }
131 if nodes[*t].kind() != &NodeKind::Source {
132 return Err(GraphError::IncompatiblePorts);
133 }
134
135 for (j, other) in edges.iter().enumerate() {
137 if j == i {
138 continue;
139 }
140 if other.upstream().node() == &EXTERNAL_INGRESS_NODE
141 && other.downstream().node().as_usize() == t
142 {
143 return Err(GraphError::IncompatiblePorts);
144 }
145 }
146
147 continue;
149 }
150
151 let f = ed.upstream().node().as_usize();
153 if f >= &n || t >= &n {
154 return Err(GraphError::IncompatiblePorts);
155 }
156
157 let nf = &nodes[*f];
158 let nt = &nodes[*t];
159
160 if *ed.upstream().port().as_usize() >= *nf.out_ports() as usize {
162 return Err(GraphError::IncompatiblePorts);
163 }
164 if *ed.downstream().port().as_usize() >= *nt.in_ports() as usize {
165 return Err(GraphError::IncompatiblePorts);
166 }
167 }
168
169 for (i, ei) in edges.iter().enumerate() {
172 if ei.upstream().node() == &EXTERNAL_INGRESS_NODE {
173 continue; }
175 for ej in edges.iter().skip(i + 1) {
176 if ej.upstream().node() == &EXTERNAL_INGRESS_NODE {
177 continue; }
179 if ei.downstream().node().as_usize() == ej.downstream().node().as_usize()
180 && ei.downstream().port().as_usize() == ej.downstream().port().as_usize()
181 {
182 return Err(GraphError::IncompatiblePorts);
183 }
184 }
185 }
186
187 Ok(())
188}
189
190pub fn validate_acyclic_buf<const N: usize>(
197 _nodes: &[NodeDescriptor; N],
198 edges: &[EdgeDescriptor],
199) -> Result<(), GraphError> {
200 let mut indeg = [0usize; N];
202
203 for e in edges {
205 if e.upstream().node() == &EXTERNAL_INGRESS_NODE {
206 continue; }
208 let u = e.upstream().node().as_usize();
209 let v = e.downstream().node().as_usize();
210 if u >= &N || v >= &N {
211 return Err(GraphError::IncompatiblePorts);
212 }
213 indeg[*v] += 1;
214 }
215
216 let mut stack = [0usize; N];
217 let mut top = 0usize;
218
219 for (i, °) in indeg.iter().enumerate() {
221 if deg == 0 {
222 stack[top] = i;
223 top += 1;
224 }
225 }
226
227 let mut visited = 0usize;
228 while top > 0 {
229 top -= 1;
230 let u = stack[top];
231 visited += 1;
232
233 for e in edges.iter().filter(|e| {
235 e.upstream().node() != &EXTERNAL_INGRESS_NODE && e.upstream().node().as_usize() == &u
236 }) {
237 let v = e.downstream().node().as_usize();
238 debug_assert!(v < &N, "downstream index validated earlier");
239 indeg[*v] -= 1;
240 if indeg[*v] == 0 {
241 stack[top] = *v;
242 top += 1;
243 }
244 }
245 }
246
247 if visited != N {
248 return Err(GraphError::Cyclic);
249 }
250 Ok(())
251}
252
253#[cfg(feature = "alloc")]
255pub fn validate_acyclic_alloc(
256 nodes: &[NodeDescriptor],
257 edges: &[EdgeDescriptor],
258) -> Result<(), GraphError> {
259 use alloc::vec::Vec;
260
261 let n = nodes.len();
262 let mut indeg = vec![0usize; n];
263
264 for e in edges {
266 if e.upstream().node() == &EXTERNAL_INGRESS_NODE {
267 continue;
268 }
269 let u = e.upstream().node().as_usize();
270 let v = e.downstream().node().as_usize();
271 if u >= &n || v >= &n {
272 return Err(GraphError::IncompatiblePorts);
273 }
274 indeg[*v] += 1;
275 }
276
277 let mut stack = Vec::with_capacity(n);
279 for (i, °) in indeg.iter().take(n).enumerate() {
280 if deg == 0 {
281 stack.push(i);
282 }
283 }
284
285 let mut visited = 0usize;
287 while let Some(u) = stack.pop() {
288 visited += 1;
289 for e in edges.iter().filter(|e| {
290 e.upstream().node() != &EXTERNAL_INGRESS_NODE && e.upstream().node().as_usize() == &u
291 }) {
292 let v = e.downstream().node().as_usize();
293 indeg[*v] -= 1;
294 if indeg[*v] == 0 {
295 stack.push(*v);
296 }
297 }
298 }
299
300 if visited != n {
301 return Err(GraphError::Cyclic);
302 }
303 Ok(())
304}