1use std::collections::{BinaryHeap, HashMap, HashSet};
5
6use crate::arena::Arena;
7use crate::dtype::DataType;
8use crate::error::GraphError;
9use crate::node::{Node, NodeId};
10use crate::shape::{Shape, SymbolConstraints, SymbolId};
11use crate::tensor::WeightRef;
12use crate::value::{Value, ValueId};
13
14#[derive(Clone, Debug, Default)]
20pub struct Graph {
21 pub nodes: Arena<NodeId, Node>,
22 pub values: Arena<ValueId, Value>,
23 pub inputs: Vec<ValueId>,
25 pub outputs: Vec<ValueId>,
27 pub initializers: HashMap<ValueId, WeightRef>,
29 pub symbol_constraints: HashMap<SymbolId, SymbolConstraints>,
31 pub opset_imports: HashMap<String, u64>,
33 pub subgraphs: HashMap<(NodeId, String), Graph>,
35
36 next_symbol: u32,
37 symbol_names: HashMap<String, SymbolId>,
38}
39
40impl Graph {
41 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn node(&self, id: NodeId) -> &Node {
51 self.nodes.get(id).expect("node id not live in graph")
52 }
53
54 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
56 self.nodes.get_mut(id).expect("node id not live in graph")
57 }
58
59 pub fn try_node(&self, id: NodeId) -> Option<&Node> {
61 self.nodes.get(id)
62 }
63
64 pub fn value(&self, id: ValueId) -> &Value {
67 self.values.get(id).expect("value id not live in graph")
68 }
69
70 pub fn value_mut(&mut self, id: ValueId) -> &mut Value {
72 self.values.get_mut(id).expect("value id not live in graph")
73 }
74
75 pub fn try_value(&self, id: ValueId) -> Option<&Value> {
77 self.values.get(id)
78 }
79
80 pub fn num_nodes(&self) -> usize {
82 self.nodes.len()
83 }
84
85 pub fn num_values(&self) -> usize {
87 self.values.len()
88 }
89
90 pub fn create_symbol(&mut self, name: Option<String>) -> SymbolId {
94 let id = SymbolId(self.next_symbol);
95 self.next_symbol += 1;
96 self.symbol_constraints
97 .insert(id, SymbolConstraints::new(id, name.clone()));
98 if let Some(n) = name {
99 self.symbol_names.insert(n, id);
100 }
101 id
102 }
103
104 pub fn intern_symbol(&mut self, name: &str) -> SymbolId {
107 if let Some(id) = self.symbol_names.get(name) {
108 return *id;
109 }
110 self.create_symbol(Some(name.to_string()))
111 }
112
113 pub fn create_value(&mut self, dtype: DataType, shape: Shape) -> ValueId {
117 self.values
118 .insert_with(|vid| Value::new(vid, dtype, shape.clone()))
119 }
120
121 pub fn create_named_value(
123 &mut self,
124 name: impl Into<String>,
125 dtype: DataType,
126 shape: Shape,
127 ) -> ValueId {
128 let id = self.create_value(dtype, shape);
129 self.value_mut(id).name = Some(name.into());
130 id
131 }
132
133 pub fn add_input(&mut self, value: ValueId) {
135 self.inputs.push(value);
136 }
137
138 pub fn add_output(&mut self, value: ValueId) {
140 self.outputs.push(value);
141 }
142
143 pub fn set_initializer(&mut self, value: ValueId, weight: WeightRef) {
145 self.initializers.insert(value, weight);
146 }
147
148 pub fn predecessors(&self, node: NodeId) -> Vec<NodeId> {
152 let mut out = Vec::new();
153 let mut seen = HashSet::new();
154 for v in self.node(node).input_values() {
155 if let Some(val) = self.values.get(v)
156 && let Some(prod) = val.producer
157 && seen.insert(prod)
158 {
159 out.push(prod);
160 }
161 }
162 out
163 }
164
165 pub fn successors(&self, node: NodeId) -> Vec<NodeId> {
167 let mut out = Vec::new();
168 let mut seen = HashSet::new();
169 for &v in &self.node(node).outputs {
170 if let Some(val) = self.values.get(v) {
171 for &c in &val.consumers {
172 if seen.insert(c) {
173 out.push(c);
174 }
175 }
176 }
177 }
178 out
179 }
180
181 pub fn nodes_between(&self, inputs: &[ValueId], outputs: &[ValueId]) -> Vec<NodeId> {
186 let boundary: HashSet<ValueId> = inputs.iter().copied().collect();
187 let mut nodes = Vec::new();
188 let mut seen_nodes = HashSet::new();
189 let mut seen_values = HashSet::new();
190 let mut stack: Vec<ValueId> = outputs.to_vec();
191 while let Some(v) = stack.pop() {
192 if boundary.contains(&v) || !seen_values.insert(v) {
193 continue;
194 }
195 let Some(val) = self.values.get(v) else { continue };
196 if let Some(prod) = val.producer {
197 if seen_nodes.insert(prod) {
198 nodes.push(prod);
199 }
200 for iv in self.node(prod).input_values() {
201 stack.push(iv);
202 }
203 }
204 }
205 nodes
206 }
207
208 pub fn topological_order(&self) -> Result<Vec<NodeId>, GraphError> {
213 let mut in_degree: HashMap<NodeId, usize> =
214 self.nodes.keys().map(|k| (k, 0usize)).collect();
215 let mut adj: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
216
217 for (nid, node) in self.nodes.iter() {
218 for v in node.input_values() {
219 if let Some(val) = self.values.get(v)
220 && let Some(prod) = val.producer
221 && self.nodes.contains(prod)
222 {
223 adj.entry(prod).or_default().push(nid);
224 *in_degree.entry(nid).or_insert(0) += 1;
225 }
226 }
227 }
228
229 let mut ready: BinaryHeap<std::cmp::Reverse<u32>> = in_degree
231 .iter()
232 .filter(|(_, d)| **d == 0)
233 .map(|(k, _)| std::cmp::Reverse(k.0))
234 .collect();
235
236 let mut order = Vec::with_capacity(self.nodes.len());
237 while let Some(std::cmp::Reverse(raw)) = ready.pop() {
238 let nid = NodeId(raw);
239 order.push(nid);
240 if let Some(succs) = adj.get(&nid) {
241 for &s in succs {
242 let d = in_degree.get_mut(&s).expect("successor tracked");
243 *d -= 1;
244 if *d == 0 {
245 ready.push(std::cmp::Reverse(s.0));
246 }
247 }
248 }
249 }
250
251 if order.len() != self.nodes.len() {
252 return Err(GraphError::CycleDetected);
253 }
254 Ok(order)
255 }
256
257 pub fn insert_node(&mut self, node: Node) -> NodeId {
262 let id = self.nodes.insert_with(|nid| {
263 let mut node = node;
264 node.id = nid;
265 node
266 });
267 self.connect_edges(id);
268 id
269 }
270
271 pub fn remove_node(&mut self, id: NodeId) {
274 if !self.nodes.contains(id) {
275 return;
276 }
277 self.disconnect_edges(id);
278 let outputs = self.node(id).outputs.clone();
279 self.nodes.remove(id);
280 for v in outputs {
281 self.gc_value_if_orphan(v);
282 }
283 }
284
285 pub fn replace_node(&mut self, old: NodeId, new: Node) -> NodeId {
291 assert!(self.nodes.contains(old), "replace_node: old id not live");
292 self.disconnect_edges(old);
293 {
294 let slot = self.nodes.get_mut(old).expect("old live");
295 let mut new = new;
296 new.id = old;
297 *slot = new;
298 }
299 self.connect_edges(old);
300 old
301 }
302
303 pub fn insert_on_edge(&mut self, value: ValueId, new_node: Node) -> NodeId {
309 let (dtype, shape) = {
310 let v = self.value(value);
311 (v.dtype, v.shape.clone())
312 };
313 let new_value = self.create_value(dtype, shape);
314 self.replace_all_uses(value, new_value);
317
318 let mut new_node = new_node;
319 new_node.inputs = vec![Some(value)];
320 new_node.outputs = vec![new_value];
321 self.insert_node(new_node)
322 }
323
324 pub fn replace_all_uses(&mut self, old_value: ValueId, new_value: ValueId) {
327 if old_value == new_value {
328 return;
329 }
330 let consumers = match self.values.get(old_value) {
331 Some(v) => v.consumers.clone(),
332 None => return,
333 };
334 for nid in &consumers {
335 if let Some(node) = self.nodes.get_mut(*nid) {
336 for slot in node.inputs.iter_mut() {
337 if *slot == Some(old_value) {
338 *slot = Some(new_value);
339 }
340 }
341 }
342 }
343 let mut moved = std::mem::take(&mut self.value_mut(old_value).consumers);
345 if let Some(nv) = self.values.get_mut(new_value) {
346 nv.consumers.append(&mut moved);
347 }
348 for out in self.outputs.iter_mut() {
350 if *out == old_value {
351 *out = new_value;
352 }
353 }
354 }
355
356 pub fn validate(&self) -> Result<(), Vec<GraphError>> {
360 let mut errors = Vec::new();
361
362 let mut produced: HashMap<ValueId, NodeId> = HashMap::new();
364 for (nid, node) in self.nodes.iter() {
365 for v in node.input_values() {
366 if !self.values.contains(v) {
367 errors.push(GraphError::DanglingValue(v));
368 }
369 }
370 for &v in &node.outputs {
371 if !self.values.contains(v) {
372 errors.push(GraphError::DanglingValue(v));
373 continue;
374 }
375 if produced.insert(v, nid).is_some() {
376 errors.push(GraphError::DuplicateOutput(v));
377 }
378 }
379 }
380
381 for (vid, val) in self.values.iter() {
383 if let Some(p) = val.producer {
384 if !self.nodes.contains(p) {
385 errors.push(GraphError::DanglingNode(p));
386 } else if !self.node(p).outputs.contains(&vid) {
387 errors.push(GraphError::ProducerLinkMismatch(vid));
388 }
389 }
390 for &c in &val.consumers {
391 if !self.nodes.contains(c) {
392 errors.push(GraphError::DanglingNode(c));
393 } else if !self.node(c).input_values().any(|iv| iv == vid) {
394 errors.push(GraphError::ConsumerLinkMismatch(vid));
395 }
396 }
397 }
398
399 for &inp in &self.inputs {
401 if let Some(val) = self.values.get(inp) {
402 if val.producer.is_some() {
403 errors.push(GraphError::InputHasProducer(inp));
404 }
405 } else {
406 errors.push(GraphError::DanglingValue(inp));
407 }
408 }
409
410 for &out in &self.outputs {
413 match self.values.get(out) {
414 Some(val) => {
415 let is_source = self.inputs.contains(&out)
416 || self.initializers.contains_key(&out);
417 if val.producer.is_none() && !is_source {
418 errors.push(GraphError::MissingProducer(out));
419 }
420 }
421 None => errors.push(GraphError::DanglingValue(out)),
422 }
423 }
424
425 if let Err(e) = self.topological_order() {
427 errors.push(e);
428 }
429
430 for (domain, &version) in &self.opset_imports {
432 if version == 0 {
433 errors.push(GraphError::InvalidOpsetImport {
434 domain: domain.clone(),
435 version,
436 });
437 }
438 }
439
440 for sub in self.subgraphs.values() {
442 if let Err(mut sub_errors) = sub.validate() {
443 errors.append(&mut sub_errors);
444 }
445 }
446
447 if errors.is_empty() {
448 Ok(())
449 } else {
450 Err(errors)
451 }
452 }
453
454 fn connect_edges(&mut self, id: NodeId) {
458 let inputs: Vec<ValueId> = self.node(id).input_values().collect();
459 let outputs = self.node(id).outputs.clone();
460 for v in inputs {
461 if let Some(val) = self.values.get_mut(v) {
462 val.consumers.push(id);
463 }
464 }
465 for v in outputs {
466 if let Some(val) = self.values.get_mut(v) {
467 val.producer = Some(id);
468 }
469 }
470 }
471
472 fn disconnect_edges(&mut self, id: NodeId) {
475 let inputs: Vec<ValueId> = self.node(id).input_values().collect();
476 let outputs = self.node(id).outputs.clone();
477 for v in inputs {
478 if let Some(val) = self.values.get_mut(v) {
479 val.consumers.retain(|&c| c != id);
480 }
481 }
482 for v in outputs {
483 if let Some(val) = self.values.get_mut(v)
484 && val.producer == Some(id)
485 {
486 val.producer = None;
487 }
488 }
489 }
490
491 fn gc_value_if_orphan(&mut self, value: ValueId) {
494 let orphan = match self.values.get(value) {
495 Some(v) => {
496 v.producer.is_none()
497 && v.consumers.is_empty()
498 && !self.inputs.contains(&value)
499 && !self.outputs.contains(&value)
500 && !self.initializers.contains_key(&value)
501 }
502 None => false,
503 };
504 if orphan {
505 self.values.remove(value);
506 }
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use crate::shape::static_shape;
514
515 fn sample_graph() -> Graph {
517 let mut g = Graph::new();
518 g.opset_imports.insert(String::new(), 17);
519 let a = g.create_named_value("a", DataType::Float32, static_shape([4]));
520 let c = g.create_named_value("c", DataType::Float32, static_shape([4]));
521 g.add_input(a);
522 g.add_input(c);
523
524 let b = g.create_value(DataType::Float32, static_shape([4]));
525 let relu = Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]);
526 g.insert_node(relu);
527
528 let d = g.create_named_value("d", DataType::Float32, static_shape([4]));
529 let add = Node::new(NodeId(0), "Add", vec![Some(b), Some(c)], vec![d]);
530 g.insert_node(add);
531 g.add_output(d);
532 g
533 }
534
535 #[test]
536 fn edges_are_wired_on_insert() {
537 let g = sample_graph();
538 assert_eq!(g.num_nodes(), 2);
539 let b = g.value(ValueId(2));
541 assert!(b.producer.is_some());
542 assert_eq!(b.consumers.len(), 1);
543 }
544
545 #[test]
546 fn topo_order_is_valid_and_deterministic() {
547 let g = sample_graph();
548 let order = g.topological_order().unwrap();
549 assert_eq!(order.len(), 2);
550 assert_eq!(order, vec![NodeId(0), NodeId(1)]);
552 }
553
554 #[test]
555 fn validate_accepts_wellformed_graph() {
556 let g = sample_graph();
557 assert!(g.validate().is_ok());
558 }
559
560 #[test]
561 fn predecessors_and_successors() {
562 let g = sample_graph();
563 assert_eq!(g.successors(NodeId(0)), vec![NodeId(1)]);
564 assert_eq!(g.predecessors(NodeId(1)), vec![NodeId(0)]);
565 }
566
567 #[test]
568 fn nodes_between_walks_back() {
569 let g = sample_graph();
570 let between = g.nodes_between(&[ValueId(0), ValueId(1)], &[ValueId(3)]);
571 assert!(between.contains(&NodeId(0)));
572 assert!(between.contains(&NodeId(1)));
573 assert_eq!(between.len(), 2);
574 }
575
576 #[test]
577 fn replace_all_uses_redirects_consumers() {
578 let mut g = sample_graph();
579 let e = g.create_value(DataType::Float32, static_shape([4]));
581 g.replace_all_uses(ValueId(2), e);
582 let add = g.node(NodeId(1));
584 assert!(add.input_values().any(|v| v == e));
585 assert!(!add.input_values().any(|v| v == ValueId(2)));
586 assert_eq!(g.value(e).consumers, vec![NodeId(1)]);
587 assert!(g.value(ValueId(2)).consumers.is_empty());
588 }
589
590 #[test]
591 fn insert_on_edge_splices_node() {
592 let mut g = sample_graph();
593 let ident = Node::new(NodeId(0), "Identity", vec![], vec![]);
595 let nid = g.insert_on_edge(ValueId(2), ident);
596 let new_out = g.node(nid).outputs[0];
598 assert_eq!(g.value(new_out).producer, Some(nid));
599 assert!(g.node(NodeId(1)).input_values().any(|v| v == new_out));
600 assert!(g.validate().is_ok());
601 }
602
603 #[test]
604 fn remove_node_disconnects_and_gcs() {
605 let mut g = sample_graph();
606 g.remove_node(NodeId(1)); assert_eq!(g.num_nodes(), 1);
608 assert!(g.value(ValueId(3)).producer.is_none());
610 assert!(g.value(ValueId(2)).consumers.is_empty());
612 }
613
614 #[test]
615 fn replace_node_preserves_id() {
616 let mut g = sample_graph();
617 let d = g.node(NodeId(1)).outputs[0];
618 let b = g.node(NodeId(1)).inputs[0];
619 let c = g.node(NodeId(1)).inputs[1];
620 let sub = Node::new(NodeId(0), "Sub", vec![b, c], vec![d]);
621 let id = g.replace_node(NodeId(1), sub);
622 assert_eq!(id, NodeId(1));
623 assert_eq!(g.node(NodeId(1)).op_type, "Sub");
624 assert!(g.validate().is_ok());
625 }
626
627 #[test]
628 fn cycle_is_detected() {
629 let mut g = Graph::new();
630 let v0 = g.create_value(DataType::Float32, static_shape([1]));
631 let v1 = g.create_value(DataType::Float32, static_shape([1]));
632 g.insert_node(Node::new(NodeId(0), "A", vec![Some(v1)], vec![v0]));
634 g.insert_node(Node::new(NodeId(0), "B", vec![Some(v0)], vec![v1]));
635 assert_eq!(g.topological_order(), Err(GraphError::CycleDetected));
636 assert!(g.validate().is_err());
637 }
638
639 #[test]
640 fn intern_symbol_dedups_by_name() {
641 let mut g = Graph::new();
642 let s1 = g.intern_symbol("batch");
643 let s2 = g.intern_symbol("batch");
644 let s3 = g.intern_symbol("seq");
645 assert_eq!(s1, s2);
646 assert_ne!(s1, s3);
647 }
648}