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 unknown_value_types: HashSet<ValueId>,
39 unknown_value_shapes: HashSet<ValueId>,
40}
41
42impl Graph {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn node(&self, id: NodeId) -> &Node {
53 self.nodes.get(id).expect("node id not live in graph")
54 }
55
56 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
58 self.nodes.get_mut(id).expect("node id not live in graph")
59 }
60
61 pub fn try_node(&self, id: NodeId) -> Option<&Node> {
63 self.nodes.get(id)
64 }
65
66 pub fn value(&self, id: ValueId) -> &Value {
69 self.values.get(id).expect("value id not live in graph")
70 }
71
72 pub fn value_mut(&mut self, id: ValueId) -> &mut Value {
74 self.values.get_mut(id).expect("value id not live in graph")
75 }
76
77 pub fn try_value(&self, id: ValueId) -> Option<&Value> {
79 self.values.get(id)
80 }
81
82 pub fn uses(&self, value: ValueId) -> Vec<(NodeId, u32)> {
84 self.value(value).consumers.uses()
85 }
86
87 pub fn consumers(&self, value: ValueId) -> Vec<NodeId> {
89 self.value(value).consumers.nodes()
90 }
91
92 pub fn num_uses(&self, value: ValueId) -> usize {
94 self.value(value).consumers.len()
95 }
96
97 pub fn has_uses(&self, value: ValueId) -> bool {
99 !self.value(value).consumers.is_empty()
100 }
101
102 pub fn num_nodes(&self) -> usize {
104 self.nodes.len()
105 }
106
107 pub fn num_values(&self) -> usize {
109 self.values.len()
110 }
111
112 pub fn value_type_is_known(&self, id: ValueId) -> bool {
114 !self.unknown_value_types.contains(&id)
115 }
116
117 pub fn value_shape_is_known(&self, id: ValueId) -> bool {
119 !self.unknown_value_shapes.contains(&id)
120 }
121
122 pub fn mark_value_type_unknown(&mut self, id: ValueId) {
124 self.unknown_value_types.insert(id);
125 }
126
127 pub fn mark_value_type_known(&mut self, id: ValueId) {
129 self.unknown_value_types.remove(&id);
130 }
131
132 pub fn mark_value_shape_unknown(&mut self, id: ValueId) {
134 self.unknown_value_shapes.insert(id);
135 }
136
137 pub fn create_symbol(&mut self, name: Option<String>) -> SymbolId {
141 let id = SymbolId(self.next_symbol);
142 self.next_symbol += 1;
143 self.symbol_constraints
144 .insert(id, SymbolConstraints::new(id, name.clone()));
145 if let Some(n) = name {
146 self.symbol_names.insert(n, id);
147 }
148 id
149 }
150
151 pub fn intern_symbol(&mut self, name: &str) -> SymbolId {
154 if let Some(id) = self.symbol_names.get(name) {
155 return *id;
156 }
157 self.create_symbol(Some(name.to_string()))
158 }
159
160 pub fn create_value(&mut self, dtype: DataType, shape: Shape) -> ValueId {
164 self.values
165 .insert_with(|vid| Value::new(vid, dtype, shape.clone()))
166 }
167
168 pub fn create_named_value(
170 &mut self,
171 name: impl Into<String>,
172 dtype: DataType,
173 shape: Shape,
174 ) -> ValueId {
175 let id = self.create_value(dtype, shape);
176 self.value_mut(id).name = Some(name.into());
177 id
178 }
179
180 pub fn add_input(&mut self, value: ValueId) {
182 self.value_mut(value).is_graph_input = true;
183 self.inputs.push(value);
184 debug_assert!(self.value(value).is_graph_input);
185 }
186
187 pub fn add_output(&mut self, value: ValueId) {
189 self.value_mut(value).is_graph_output = true;
190 self.outputs.push(value);
191 debug_assert!(self.value(value).is_graph_output);
192 }
193
194 pub fn insert_output(&mut self, index: usize, value: ValueId) {
196 self.value_mut(value).is_graph_output = true;
197 self.outputs.insert(index, value);
198 debug_assert!(self.value(value).is_graph_output);
199 }
200
201 pub fn remove_input(&mut self, index: usize) -> ValueId {
203 let value = self.inputs.remove(index);
204 if !self.inputs.contains(&value) {
205 self.value_mut(value).is_graph_input = false;
206 }
207 debug_assert_eq!(
208 self.value(value).is_graph_input,
209 self.inputs.contains(&value)
210 );
211 value
212 }
213
214 pub fn remove_output(&mut self, index: usize) -> ValueId {
216 let value = self.outputs.remove(index);
217 if !self.outputs.contains(&value) {
218 self.value_mut(value).is_graph_output = false;
219 }
220 debug_assert_eq!(
221 self.value(value).is_graph_output,
222 self.outputs.contains(&value)
223 );
224 value
225 }
226
227 pub fn set_inputs(&mut self, inputs: Vec<ValueId>) {
229 for value in self.inputs.drain(..) {
230 if let Some(metadata) = self.values.get_mut(value) {
231 metadata.is_graph_input = false;
232 }
233 }
234 for &value in &inputs {
235 self.value_mut(value).is_graph_input = true;
236 }
237 self.inputs = inputs;
238 debug_assert!(
239 self.inputs
240 .iter()
241 .all(|&value| self.value(value).is_graph_input)
242 );
243 }
244
245 pub fn set_outputs(&mut self, outputs: Vec<ValueId>) {
247 for value in self.outputs.drain(..) {
248 if let Some(metadata) = self.values.get_mut(value) {
249 metadata.is_graph_output = false;
250 }
251 }
252 for &value in &outputs {
253 self.value_mut(value).is_graph_output = true;
254 }
255 self.outputs = outputs;
256 debug_assert!(
257 self.outputs
258 .iter()
259 .all(|&value| self.value(value).is_graph_output)
260 );
261 }
262
263 pub fn set_initializer(&mut self, value: ValueId, weight: WeightRef) {
265 self.initializers.insert(value, weight);
266 }
267
268 pub fn predecessors(&self, node: NodeId) -> Vec<NodeId> {
272 let mut out = Vec::new();
273 let mut seen = HashSet::new();
274 for v in self.node(node).input_values() {
275 if let Some(val) = self.values.get(v)
276 && let Some(prod) = val.producer
277 && seen.insert(prod)
278 {
279 out.push(prod);
280 }
281 }
282 out.sort_unstable_by_key(|node| node.0);
283 out
284 }
285
286 pub fn successors(&self, node: NodeId) -> Vec<NodeId> {
288 let mut out = Vec::new();
289 let mut seen = HashSet::new();
290 for &v in &self.node(node).outputs {
291 if let Some(val) = self.values.get(v) {
292 for c in val.consumers.nodes() {
293 if seen.insert(c) {
294 out.push(c);
295 }
296 }
297 }
298 }
299 out.sort_unstable_by_key(|node| node.0);
300 out
301 }
302
303 pub fn nodes_between(&self, inputs: &[ValueId], outputs: &[ValueId]) -> Vec<NodeId> {
308 let boundary: HashSet<ValueId> = inputs.iter().copied().collect();
309 let mut nodes = Vec::new();
310 let mut seen_nodes = HashSet::new();
311 let mut seen_values = HashSet::new();
312 let mut stack: Vec<ValueId> = outputs.to_vec();
313 while let Some(v) = stack.pop() {
314 if boundary.contains(&v) || !seen_values.insert(v) {
315 continue;
316 }
317 let Some(val) = self.values.get(v) else {
318 continue;
319 };
320 if let Some(prod) = val.producer {
321 if seen_nodes.insert(prod) {
322 nodes.push(prod);
323 }
324 for iv in self.node(prod).input_values() {
325 stack.push(iv);
326 }
327 }
328 }
329 nodes
330 }
331
332 pub fn topological_order(&self) -> Result<Vec<NodeId>, GraphError> {
337 const VACANT: usize = usize::MAX;
338 let mut in_degree = vec![VACANT; self.nodes.capacity()];
339 let mut adj = vec![Vec::<NodeId>::new(); self.nodes.capacity()];
340 for node in self.nodes.keys() {
341 in_degree[node.0 as usize] = 0;
342 }
343
344 for (nid, node) in self.nodes.iter() {
345 for v in node.input_values() {
346 if let Some(val) = self.values.get(v)
347 && let Some(prod) = val.producer
348 && self.nodes.contains(prod)
349 {
350 adj[prod.0 as usize].push(nid);
351 in_degree[nid.0 as usize] += 1;
352 }
353 }
354 }
355
356 let mut ready: BinaryHeap<std::cmp::Reverse<u32>> = in_degree
358 .iter()
359 .enumerate()
360 .filter(|(_, degree)| **degree == 0)
361 .map(|(raw, _)| std::cmp::Reverse(raw as u32))
362 .collect();
363
364 let mut order = Vec::with_capacity(self.nodes.len());
365 while let Some(std::cmp::Reverse(raw)) = ready.pop() {
366 let nid = NodeId(raw);
367 order.push(nid);
368 for &successor in &adj[raw as usize] {
369 let degree = &mut in_degree[successor.0 as usize];
370 *degree -= 1;
371 if *degree == 0 {
372 ready.push(std::cmp::Reverse(successor.0));
373 }
374 }
375 }
376
377 if order.len() != self.nodes.len() {
378 return Err(GraphError::CycleDetected);
379 }
380 Ok(order)
381 }
382
383 pub fn normalize_domains(&mut self) {
394 for node in self.nodes.values_mut() {
395 if node.domain == crate::AI_ONNX_DOMAIN {
396 node.domain.clear();
397 }
398 }
399 if let Some(version) = self.opset_imports.remove(crate::AI_ONNX_DOMAIN) {
400 let entry = self.opset_imports.entry(String::new()).or_insert(version);
401 *entry = (*entry).max(version);
402 }
403 for subgraph in self.subgraphs.values_mut() {
404 subgraph.normalize_domains();
405 }
406 }
407
408 pub fn insert_node(&mut self, node: Node) -> NodeId {
411 let id = self.nodes.insert_with(|nid| {
412 let mut node = node;
413 node.id = nid;
414 node
415 });
416 self.connect_edges(id);
417 id
418 }
419
420 pub fn remove_node(&mut self, id: NodeId) {
423 if !self.nodes.contains(id) {
424 return;
425 }
426 self.disconnect_edges(id);
427 let outputs = self.node(id).outputs.clone();
428 self.nodes.remove(id);
429 for v in outputs {
430 self.gc_value_if_orphan(v);
431 }
432 }
433
434 pub fn remove_nodes(&mut self, ids: &[NodeId]) {
440 for &id in ids {
441 self.remove_node(id);
442 }
443 }
444
445 pub fn replace_node_groups(
455 &mut self,
456 groups: Vec<(Vec<NodeId>, Node)>,
457 graph_outputs: &HashSet<ValueId>,
458 ) -> Vec<NodeId> {
459 debug_assert_eq!(
460 graph_outputs,
461 &self.outputs.iter().copied().collect::<HashSet<_>>()
462 );
463 let mut removed_nodes = HashSet::new();
464 for (node_ids, _) in &groups {
465 assert!(
466 !node_ids.is_empty(),
467 "replace_node_groups: group must not be empty"
468 );
469 for &id in node_ids {
470 assert!(
471 self.nodes.contains(id),
472 "replace_node_groups: node id not live"
473 );
474 assert!(
475 removed_nodes.insert(id),
476 "replace_node_groups: groups must be disjoint"
477 );
478 }
479 }
480
481 let mut inserted = Vec::with_capacity(groups.len());
482 for (node_ids, replacement) in groups {
483 for id in node_ids {
484 self.remove_node(id);
485 }
486 inserted.push(self.insert_node(replacement));
487 }
488
489 inserted
490 }
491
492 pub fn replace_node(&mut self, old: NodeId, new: Node) -> NodeId {
498 assert!(self.nodes.contains(old), "replace_node: old id not live");
499 self.disconnect_edges(old);
500 {
501 let slot = self.nodes.get_mut(old).expect("old live");
502 let mut new = new;
503 new.id = old;
504 *slot = new;
505 }
506 self.connect_edges(old);
507 old
508 }
509
510 pub fn insert_on_edge(&mut self, value: ValueId, new_node: Node) -> NodeId {
516 let (dtype, shape) = {
517 let v = self.value(value);
518 (v.dtype, v.shape.clone())
519 };
520 let new_value = self.create_value(dtype, shape);
521 self.replace_all_uses(value, new_value);
524
525 let mut new_node = new_node;
526 new_node.inputs = vec![Some(value)];
527 new_node.outputs = vec![new_value];
528 self.insert_node(new_node)
529 }
530
531 pub fn replace_input(
536 &mut self,
537 node: NodeId,
538 input_index: usize,
539 new_value: Option<ValueId>,
540 ) -> Option<ValueId> {
541 assert!(self.nodes.contains(node), "replace_input: node id not live");
542 assert!(
543 input_index < self.node(node).inputs.len(),
544 "replace_input: input index out of bounds"
545 );
546 if let Some(value) = new_value {
547 assert!(
548 self.values.contains(value),
549 "replace_input: value id not live"
550 );
551 }
552
553 let old_value = self.node(node).inputs[input_index];
554 if old_value == new_value {
555 return old_value;
556 }
557 if let Some(value) = old_value {
558 let removed = self
559 .value_mut(value)
560 .consumers
561 .remove(node, input_index as u32);
562 debug_assert!(removed, "old input edge must be present");
563 }
564 self.node_mut(node).inputs[input_index] = new_value;
565 if let Some(value) = new_value {
566 self.value_mut(value)
567 .consumers
568 .insert(node, input_index as u32);
569 }
570 old_value
571 }
572
573 pub fn replace_all_uses(&mut self, old_value: ValueId, new_value: ValueId) {
576 if old_value == new_value {
577 return;
578 }
579 let uses = match self.values.get(old_value) {
580 Some(value) => value.consumers.uses(),
581 None => return,
582 };
583 for (node, input_index) in uses {
584 if self.nodes.contains(node) {
585 self.replace_input(node, input_index as usize, Some(new_value));
586 }
587 }
588 if self.value(old_value).is_graph_output {
589 let mut outputs = self.outputs.clone();
590 for output in &mut outputs {
591 if *output == old_value {
592 *output = new_value;
593 }
594 }
595 self.set_outputs(outputs);
596 }
597 }
598
599 pub fn validate(&self) -> Result<(), Vec<GraphError>> {
603 let mut errors = Vec::new();
604 let graph_inputs: HashSet<_> = self.inputs.iter().copied().collect();
605 let graph_outputs: HashSet<_> = self.outputs.iter().copied().collect();
606 for (value, metadata) in self.values.iter() {
607 debug_assert_eq!(
608 metadata.is_graph_input,
609 graph_inputs.contains(&value),
610 "graph-input membership flag drifted for {value:?}"
611 );
612 debug_assert_eq!(
613 metadata.is_graph_output,
614 graph_outputs.contains(&value),
615 "graph-output membership flag drifted for {value:?}"
616 );
617 }
618
619 let mut produced: HashMap<ValueId, NodeId> = HashMap::new();
621 for (nid, node) in self.nodes.iter() {
622 for (input_index, input) in node.inputs.iter().enumerate() {
623 if let Some(value) = input {
624 if !self.values.contains(*value) {
625 errors.push(GraphError::DanglingValue(*value));
626 } else if !self
627 .value(*value)
628 .consumers
629 .contains(nid, input_index as u32)
630 {
631 errors.push(GraphError::ConsumerLinkMismatch(*value));
632 }
633 }
634 }
635 for &v in &node.outputs {
636 if !self.values.contains(v) {
637 errors.push(GraphError::DanglingValue(v));
638 continue;
639 }
640 if produced.insert(v, nid).is_some() {
641 errors.push(GraphError::DuplicateOutput(v));
642 }
643 }
644 }
645
646 for (vid, val) in self.values.iter() {
648 if let Some(p) = val.producer {
649 if !self.nodes.contains(p) {
650 errors.push(GraphError::DanglingNode(p));
651 } else if !self.node(p).outputs.contains(&vid) {
652 errors.push(GraphError::ProducerLinkMismatch(vid));
653 }
654 }
655 for (consumer, input_index) in val.consumers.uses() {
656 if !self.nodes.contains(consumer) {
657 errors.push(GraphError::DanglingNode(consumer));
658 } else if self.node(consumer).inputs.get(input_index as usize) != Some(&Some(vid)) {
659 errors.push(GraphError::ConsumerLinkMismatch(vid));
660 }
661 }
662 }
663
664 for &inp in &self.inputs {
666 if let Some(val) = self.values.get(inp) {
667 if val.producer.is_some() {
668 errors.push(GraphError::InputHasProducer(inp));
669 }
670 debug_assert!(val.is_graph_input);
671 } else {
672 errors.push(GraphError::DanglingValue(inp));
673 }
674 }
675
676 for &out in &self.outputs {
679 match self.values.get(out) {
680 Some(val) => {
681 debug_assert!(val.is_graph_output);
682 let is_source = val.is_graph_input || self.initializers.contains_key(&out);
683 if val.producer.is_none() && !is_source {
684 errors.push(GraphError::MissingProducer(out));
685 }
686 }
687 None => errors.push(GraphError::DanglingValue(out)),
688 }
689 }
690
691 if let Err(e) = self.topological_order() {
693 errors.push(e);
694 }
695
696 for (domain, &version) in &self.opset_imports {
698 if version == 0 {
699 errors.push(GraphError::InvalidOpsetImport {
700 domain: domain.clone(),
701 version,
702 });
703 }
704 }
705
706 for sub in self.subgraphs.values() {
708 if let Err(mut sub_errors) = sub.validate() {
709 errors.append(&mut sub_errors);
710 }
711 }
712
713 if errors.is_empty() {
714 Ok(())
715 } else {
716 Err(errors)
717 }
718 }
719
720 fn connect_edges(&mut self, id: NodeId) {
724 let inputs = self.node(id).inputs.clone();
725 let outputs = self.node(id).outputs.clone();
726 for (input_index, input) in inputs.into_iter().enumerate() {
727 if let Some(value) = input
728 && let Some(metadata) = self.values.get_mut(value)
729 {
730 metadata.consumers.insert(id, input_index as u32);
731 }
732 }
733 for v in outputs {
734 if let Some(val) = self.values.get_mut(v) {
735 val.producer = Some(id);
736 }
737 }
738 }
739
740 fn disconnect_edges(&mut self, id: NodeId) {
743 let input_count = self.node(id).inputs.len();
744 let outputs = self.node(id).outputs.clone();
745 for input_index in 0..input_count {
746 self.replace_input(id, input_index, None);
747 }
748 for v in outputs {
749 if let Some(val) = self.values.get_mut(v)
750 && val.producer == Some(id)
751 {
752 val.producer = None;
753 }
754 }
755 }
756
757 pub fn gc_value_if_orphan(&mut self, value: ValueId) {
763 let orphan = match self.values.get(value) {
764 Some(v) => {
765 v.producer.is_none()
766 && v.consumers.is_empty()
767 && !v.is_graph_input
768 && !v.is_graph_output
769 && !self.initializers.contains_key(&value)
770 }
771 None => false,
772 };
773 if orphan {
774 self.values.remove(value);
775 self.unknown_value_types.remove(&value);
776 self.unknown_value_shapes.remove(&value);
777 }
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784 use crate::shape::static_shape;
785 use crate::tensor::TensorData;
786
787 fn assert_graphs_identical(
788 mut batched: Graph,
789 mut sequential: Graph,
790 node_probes: usize,
791 value_probes: usize,
792 trial: usize,
793 ) {
794 assert_eq!(
795 format!("{batched:#?}"),
796 format!("{sequential:#?}"),
797 "graph mismatch on randomized trial {trial}"
798 );
799
800 for _ in 0..node_probes {
803 let batched_id =
804 batched.insert_node(Node::new(NodeId(0), "Probe", Vec::new(), Vec::new()));
805 let sequential_id =
806 sequential.insert_node(Node::new(NodeId(0), "Probe", Vec::new(), Vec::new()));
807 assert_eq!(
808 batched_id, sequential_id,
809 "node arena mismatch on randomized trial {trial}"
810 );
811 }
812 for _ in 0..value_probes {
813 let batched_id = batched.create_value(DataType::Float32, static_shape([1]));
814 let sequential_id = sequential.create_value(DataType::Float32, static_shape([1]));
815 assert_eq!(
816 batched_id, sequential_id,
817 "value arena mismatch on randomized trial {trial}"
818 );
819 }
820 }
821
822 struct TestRng(u64);
823
824 impl TestRng {
825 fn next(&mut self) -> u64 {
826 self.0 ^= self.0 << 13;
827 self.0 ^= self.0 >> 7;
828 self.0 ^= self.0 << 17;
829 self.0
830 }
831
832 fn usize(&mut self, upper: usize) -> usize {
833 (self.next() as usize) % upper
834 }
835 }
836
837 fn reference_remove_node(graph: &mut Graph, id: NodeId) {
838 if !graph.nodes.contains(id) {
839 return;
840 }
841 let (inputs, outputs) = {
842 let node = graph.node(id);
843 (
844 node.input_values().collect::<Vec<_>>(),
845 node.outputs.clone(),
846 )
847 };
848 let unique_inputs: HashSet<_> = inputs.into_iter().collect();
849 for value in unique_inputs {
850 if let Some(metadata) = graph.values.get_mut(value) {
851 for (consumer, input_index) in metadata.consumers.uses() {
852 if consumer == id {
853 metadata.consumers.remove(consumer, input_index);
854 }
855 }
856 }
857 }
858 for &value in &outputs {
859 if let Some(metadata) = graph.values.get_mut(value)
860 && metadata.producer == Some(id)
861 {
862 metadata.producer = None;
863 }
864 }
865 graph.nodes.remove(id);
866 for value in outputs {
867 let orphan = graph.values.get(value).is_some_and(|metadata| {
868 metadata.producer.is_none()
869 && metadata.consumers.is_empty()
870 && !graph.inputs.contains(&value)
871 && !graph.outputs.contains(&value)
872 && !graph.initializers.contains_key(&value)
873 });
874 if orphan {
875 graph.values.remove(value);
876 graph.unknown_value_types.remove(&value);
877 graph.unknown_value_shapes.remove(&value);
878 }
879 }
880 }
881
882 fn reference_topological_order(graph: &Graph) -> Result<Vec<NodeId>, GraphError> {
883 let mut in_degree: HashMap<NodeId, usize> =
884 graph.nodes.keys().map(|node| (node, 0)).collect();
885 let mut adjacency: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
886 for (node_id, node) in graph.nodes.iter() {
887 for value in node.input_values() {
888 if let Some(producer) = graph.value(value).producer
889 && graph.nodes.contains(producer)
890 {
891 adjacency.entry(producer).or_default().push(node_id);
892 *in_degree.get_mut(&node_id).unwrap() += 1;
893 }
894 }
895 }
896 let mut ready: BinaryHeap<std::cmp::Reverse<u32>> = in_degree
897 .iter()
898 .filter(|(_, degree)| **degree == 0)
899 .map(|(node, _)| std::cmp::Reverse(node.0))
900 .collect();
901 let mut order = Vec::with_capacity(graph.num_nodes());
902 while let Some(std::cmp::Reverse(raw)) = ready.pop() {
903 let node = NodeId(raw);
904 order.push(node);
905 if let Some(successors) = adjacency.get(&node) {
906 for successor in successors {
907 let degree = in_degree.get_mut(successor).unwrap();
908 *degree -= 1;
909 if *degree == 0 {
910 ready.push(std::cmp::Reverse(successor.0));
911 }
912 }
913 }
914 }
915 if order.len() == graph.num_nodes() {
916 Ok(order)
917 } else {
918 Err(GraphError::CycleDetected)
919 }
920 }
921
922 fn sample_graph() -> Graph {
924 let mut g = Graph::new();
925 g.opset_imports.insert(String::new(), 17);
926 let a = g.create_named_value("a", DataType::Float32, static_shape([4]));
927 let c = g.create_named_value("c", DataType::Float32, static_shape([4]));
928 g.add_input(a);
929 g.add_input(c);
930
931 let b = g.create_value(DataType::Float32, static_shape([4]));
932 let relu = Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]);
933 g.insert_node(relu);
934
935 let d = g.create_named_value("d", DataType::Float32, static_shape([4]));
936 let add = Node::new(NodeId(0), "Add", vec![Some(b), Some(c)], vec![d]);
937 g.insert_node(add);
938 g.add_output(d);
939 g
940 }
941
942 #[test]
943 fn edges_are_wired_on_insert() {
944 let g = sample_graph();
945 assert_eq!(g.num_nodes(), 2);
946 let b = g.value(ValueId(2));
948 assert!(b.producer.is_some());
949 assert_eq!(b.consumers.len(), 1);
950 }
951
952 #[test]
953 fn topo_order_is_valid_and_deterministic() {
954 let g = sample_graph();
955 let order = g.topological_order().unwrap();
956 assert_eq!(order.len(), 2);
957 assert_eq!(order, vec![NodeId(0), NodeId(1)]);
959 }
960
961 #[test]
962 fn uses_preserve_input_multiplicity_and_replace_one_slot() {
963 let mut graph = Graph::new();
964 let old = graph.create_value(DataType::Float32, static_shape([1]));
965 let new = graph.create_value(DataType::Float32, static_shape([1]));
966 graph.add_input(old);
967 graph.add_input(new);
968 let output = graph.create_value(DataType::Float32, static_shape([1]));
969 let node = graph.insert_node(Node::new(
970 NodeId(0),
971 "Add",
972 vec![Some(old), Some(old)],
973 vec![output],
974 ));
975
976 assert_eq!(graph.uses(old), vec![(node, 0), (node, 1)]);
977 assert_eq!(graph.consumers(old), vec![node]);
978 assert_eq!(graph.replace_input(node, 1, Some(new)), Some(old));
979 assert_eq!(graph.uses(old), vec![(node, 0)]);
980 assert_eq!(graph.uses(new), vec![(node, 1)]);
981 assert!(graph.validate().is_ok());
982
983 graph.remove_node(node);
984 assert!(graph.uses(old).is_empty());
985 assert!(graph.uses(new).is_empty());
986 }
987
988 #[test]
989 fn io_membership_flags_follow_ordered_lists() {
990 let mut graph = Graph::new();
991 let a = graph.create_value(DataType::Float32, static_shape([1]));
992 let b = graph.create_value(DataType::Float32, static_shape([1]));
993 graph.add_input(a);
994 graph.add_output(a);
995 graph.insert_output(0, b);
996 assert!(graph.value(a).is_graph_input);
997 assert!(graph.value(a).is_graph_output);
998 assert!(graph.value(b).is_graph_output);
999
1000 assert_eq!(graph.remove_output(1), a);
1001 assert!(!graph.value(a).is_graph_output);
1002 graph.set_inputs(vec![b]);
1003 assert!(!graph.value(a).is_graph_input);
1004 assert!(graph.value(b).is_graph_input);
1005 graph.set_outputs(vec![a]);
1006 assert!(graph.value(a).is_graph_output);
1007 assert!(!graph.value(b).is_graph_output);
1008 }
1009
1010 #[test]
1011 fn consumer_hash_insertion_order_is_not_observable() {
1012 let mut first = Graph::new();
1013 let hub = first.create_value(DataType::Float32, static_shape([1]));
1014 first.add_input(hub);
1015 let mut nodes = Vec::new();
1016 for _ in 0..32 {
1017 let output = first.create_value(DataType::Float32, static_shape([1]));
1018 nodes.push(first.insert_node(Node::new(
1019 NodeId(0),
1020 "Add",
1021 vec![Some(hub), Some(hub)],
1022 vec![output],
1023 )));
1024 }
1025 let mut shuffled = first.clone();
1026 let mut uses = shuffled.uses(hub);
1027 for &(node, input_index) in &uses {
1028 shuffled.replace_input(node, input_index as usize, None);
1029 }
1030 let mut rng = TestRng(0x6a09_e667_f3bc_c909);
1031 for index in (1..uses.len()).rev() {
1032 uses.swap(index, rng.usize(index + 1));
1033 }
1034 for (node, input_index) in uses {
1035 shuffled.replace_input(node, input_index as usize, Some(hub));
1036 }
1037
1038 assert_eq!(first.uses(hub), shuffled.uses(hub));
1039 assert_eq!(first.consumers(hub), shuffled.consumers(hub));
1040 assert_eq!(first.topological_order(), shuffled.topological_order());
1041 assert_eq!(format!("{first:#?}"), format!("{shuffled:#?}"));
1042 assert_eq!(
1043 format!("{:#?}", sample_graph()),
1044 format!("{:#?}", sample_graph())
1045 );
1046 assert_eq!(nodes, first.consumers(hub));
1047 }
1048
1049 #[test]
1050 fn validate_accepts_wellformed_graph() {
1051 let g = sample_graph();
1052 assert!(g.validate().is_ok());
1053 }
1054
1055 #[test]
1056 fn predecessors_and_successors() {
1057 let g = sample_graph();
1058 assert_eq!(g.successors(NodeId(0)), vec![NodeId(1)]);
1059 assert_eq!(g.predecessors(NodeId(1)), vec![NodeId(0)]);
1060 }
1061
1062 #[test]
1063 fn nodes_between_walks_back() {
1064 let g = sample_graph();
1065 let between = g.nodes_between(&[ValueId(0), ValueId(1)], &[ValueId(3)]);
1066 assert!(between.contains(&NodeId(0)));
1067 assert!(between.contains(&NodeId(1)));
1068 assert_eq!(between.len(), 2);
1069 }
1070
1071 #[test]
1072 fn replace_all_uses_redirects_consumers() {
1073 let mut g = sample_graph();
1074 let e = g.create_value(DataType::Float32, static_shape([4]));
1076 g.replace_all_uses(ValueId(2), e);
1077 let add = g.node(NodeId(1));
1079 assert!(add.input_values().any(|v| v == e));
1080 assert!(!add.input_values().any(|v| v == ValueId(2)));
1081 assert_eq!(g.consumers(e), vec![NodeId(1)]);
1082 assert!(g.value(ValueId(2)).consumers.is_empty());
1083 }
1084
1085 #[test]
1086 fn insert_on_edge_splices_node() {
1087 let mut g = sample_graph();
1088 let ident = Node::new(NodeId(0), "Identity", vec![], vec![]);
1090 let nid = g.insert_on_edge(ValueId(2), ident);
1091 let new_out = g.node(nid).outputs[0];
1093 assert_eq!(g.value(new_out).producer, Some(nid));
1094 assert!(g.node(NodeId(1)).input_values().any(|v| v == new_out));
1095 assert!(g.validate().is_ok());
1096 }
1097
1098 #[test]
1099 fn remove_node_disconnects_and_gcs() {
1100 let mut g = sample_graph();
1101 g.remove_node(NodeId(1)); assert_eq!(g.num_nodes(), 1);
1103 assert!(g.value(ValueId(3)).producer.is_none());
1105 assert!(g.value(ValueId(2)).consumers.is_empty());
1107 }
1108
1109 #[test]
1110 fn remove_nodes_filters_wide_shared_input_once() {
1111 let mut graph = Graph::new();
1112 let input = graph.create_value(DataType::Float32, static_shape([1]));
1113 graph.add_input(input);
1114 let mut dead = Vec::new();
1115 let mut outputs = Vec::new();
1116 for _ in 0..1_000 {
1117 let output = graph.create_value(DataType::Float32, static_shape([1]));
1118 dead.push(graph.insert_node(Node::new(
1119 NodeId(0),
1120 "Relu",
1121 vec![Some(input)],
1122 vec![output],
1123 )));
1124 outputs.push(output);
1125 }
1126
1127 graph.remove_nodes(&dead);
1128
1129 assert_eq!(graph.num_nodes(), 0);
1130 assert!(graph.value(input).consumers.is_empty());
1131 assert!(
1132 outputs
1133 .into_iter()
1134 .all(|output| graph.try_value(output).is_none())
1135 );
1136 }
1137
1138 #[test]
1139 fn remove_nodes_keeps_surviving_shared_consumer() {
1140 let mut graph = Graph::new();
1141 let input = graph.create_value(DataType::Float32, static_shape([1]));
1142 graph.add_input(input);
1143 let mut nodes = Vec::new();
1144 for op_type in ["Relu", "Neg", "Abs"] {
1145 let output = graph.create_value(DataType::Float32, static_shape([1]));
1146 nodes.push(graph.insert_node(Node::new(
1147 NodeId(0),
1148 op_type,
1149 vec![Some(input)],
1150 vec![output],
1151 )));
1152 }
1153
1154 graph.remove_nodes(&nodes[..2]);
1155
1156 assert_eq!(graph.consumers(input), vec![nodes[2]]);
1157 assert!(graph.try_node(nodes[2]).is_some());
1158 assert!(graph.validate().is_ok());
1159 }
1160
1161 #[test]
1162 fn remove_nodes_collects_value_after_all_consumers_are_removed() {
1163 let mut graph = Graph::new();
1164 let input = graph.create_value(DataType::Float32, static_shape([1]));
1165 graph.add_input(input);
1166 let shared = graph.create_value(DataType::Float32, static_shape([1]));
1167 let producer = graph.insert_node(Node::new(
1168 NodeId(0),
1169 "Relu",
1170 vec![Some(input)],
1171 vec![shared],
1172 ));
1173 let mut consumers = Vec::new();
1174 for op_type in ["Neg", "Abs"] {
1175 let output = graph.create_value(DataType::Float32, static_shape([1]));
1176 consumers.push(graph.insert_node(Node::new(
1177 NodeId(0),
1178 op_type,
1179 vec![Some(shared)],
1180 vec![output],
1181 )));
1182 }
1183
1184 graph.remove_nodes(&[consumers[0], consumers[1], producer]);
1185
1186 assert!(graph.try_value(shared).is_none());
1187 }
1188
1189 #[test]
1190 fn remove_nodes_keeps_graph_outputs_and_initializers() {
1191 let mut graph = Graph::new();
1192 let input = graph.create_value(DataType::Float32, static_shape([1]));
1193 graph.add_input(input);
1194
1195 let graph_output = graph.create_value(DataType::Float32, static_shape([1]));
1196 let output_node = graph.insert_node(Node::new(
1197 NodeId(0),
1198 "Relu",
1199 vec![Some(input)],
1200 vec![graph_output],
1201 ));
1202 graph.add_output(graph_output);
1203
1204 let initializer = graph.create_value(DataType::Float32, static_shape([1]));
1205 let initializer_node = graph.insert_node(Node::new(
1206 NodeId(0),
1207 "Neg",
1208 vec![Some(input)],
1209 vec![initializer],
1210 ));
1211 graph.set_initializer(
1212 initializer,
1213 WeightRef::Inline(TensorData::from_raw(
1214 DataType::Float32,
1215 vec![1],
1216 0.0f32.to_le_bytes().to_vec(),
1217 )),
1218 );
1219
1220 graph.remove_nodes(&[output_node, initializer_node]);
1221
1222 assert!(graph.try_value(graph_output).is_some());
1223 assert!(graph.value(graph_output).producer.is_none());
1224 assert!(graph.try_value(initializer).is_some());
1225 assert!(graph.value(initializer).producer.is_none());
1226 }
1227
1228 #[test]
1229 fn remove_nodes_ignores_duplicate_and_nonlive_ids() {
1230 let graph = sample_graph();
1231 let ids = [NodeId(u32::MAX), NodeId(1), NodeId(1)];
1232 let mut sequential = graph.clone();
1233 for id in ids {
1234 sequential.remove_node(id);
1235 }
1236 let mut batched = graph;
1237 batched.remove_nodes(&ids);
1238
1239 assert_graphs_identical(batched, sequential, 3, 5, 0);
1240 }
1241
1242 #[test]
1243 fn remove_nodes_matches_sequential_removal_on_random_dags() {
1244 let mut rng = TestRng(0x4d59_5df4_d0f3_3173);
1245
1246 for trial in 0..10_000 {
1247 let input_count = 1 + rng.usize(3);
1248 let node_count = 1 + rng.usize(12);
1249 let mut graph = Graph::new();
1250 let mut values = Vec::new();
1251 for _ in 0..input_count {
1252 let input = graph.create_value(DataType::Float32, static_shape([1]));
1253 graph.add_input(input);
1254 values.push(input);
1255 }
1256
1257 let mut nodes = Vec::with_capacity(node_count);
1258 for _ in 0..node_count {
1259 let input_arity = 1 + rng.usize(3);
1260 let inputs = (0..input_arity)
1261 .map(|_| Some(values[rng.usize(values.len())]))
1262 .collect();
1263 let output = graph.create_value(DataType::Float32, static_shape([1]));
1264 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1265 if rng.usize(5) == 0 {
1266 graph.mark_value_type_unknown(output);
1267 }
1268 if rng.usize(5) == 0 {
1269 graph.mark_value_shape_unknown(output);
1270 }
1271 values.push(output);
1272 }
1273
1274 for _ in 0..rng.usize(4) {
1275 let output = values[rng.usize(values.len())];
1276 graph.add_output(output);
1277 }
1278
1279 for i in (1..nodes.len()).rev() {
1280 let j = rng.usize(i + 1);
1281 nodes.swap(i, j);
1282 }
1283 nodes.truncate(rng.usize(nodes.len() + 1));
1284
1285 let mut sequential = graph.clone();
1286 for &id in &nodes {
1287 sequential.remove_node(id);
1288 }
1289 let mut batched = graph;
1290 batched.remove_nodes(&nodes);
1291
1292 assert_graphs_identical(
1293 batched,
1294 sequential,
1295 node_count + 1,
1296 input_count + node_count + 1,
1297 trial,
1298 );
1299 }
1300 }
1301
1302 #[test]
1303 fn single_node_removal_matches_vector_reference_on_random_dags() {
1304 let mut rng = TestRng(0xbb67_ae85_84ca_a73b);
1305 for trial in 0..2_000 {
1306 let mut graph = Graph::new();
1307 let input_count = 1 + rng.usize(3);
1308 let node_count = 1 + rng.usize(24);
1309 let mut values = Vec::new();
1310 for _ in 0..input_count {
1311 let value = graph.create_value(DataType::Float32, static_shape([1]));
1312 graph.add_input(value);
1313 values.push(value);
1314 }
1315 let mut nodes = Vec::new();
1316 for _ in 0..node_count {
1317 let input_count = 1 + rng.usize(4);
1318 let inputs = (0..input_count)
1319 .map(|_| Some(values[rng.usize(values.len())]))
1320 .collect();
1321 let output = graph.create_value(DataType::Float32, static_shape([1]));
1322 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1323 values.push(output);
1324 }
1325 for _ in 0..rng.usize(4) {
1326 graph.add_output(values[rng.usize(values.len())]);
1327 }
1328
1329 let mut removals = nodes.clone();
1330 for index in (1..removals.len()).rev() {
1331 removals.swap(index, rng.usize(index + 1));
1332 }
1333 removals.truncate(rng.usize(removals.len() + 1));
1334 if let Some(&duplicate) = removals.first() {
1335 removals.push(duplicate);
1336 }
1337 removals.push(NodeId(u32::MAX));
1338
1339 let mut actual = graph.clone();
1340 let mut reference = graph;
1341 for &node in &removals {
1342 actual.remove_node(node);
1343 reference_remove_node(&mut reference, node);
1344 }
1345
1346 assert_eq!(
1347 format!("{actual:#?}"),
1348 format!("{reference:#?}"),
1349 "debug mismatch on trial {trial}"
1350 );
1351 assert_eq!(
1352 actual.topological_order(),
1353 reference.topological_order(),
1354 "topology mismatch on trial {trial}"
1355 );
1356 for value in actual.values.keys() {
1357 assert_eq!(
1358 actual.uses(value),
1359 reference.uses(value),
1360 "uses mismatch for {value:?} on trial {trial}"
1361 );
1362 assert_eq!(
1363 actual.consumers(value),
1364 reference.consumers(value),
1365 "consumers mismatch for {value:?} on trial {trial}"
1366 );
1367 }
1368 }
1369 }
1370
1371 #[test]
1372 fn vec_indexed_topology_matches_hashmap_reference_on_random_dags() {
1373 let mut rng = TestRng(0x3c6e_f372_fe94_f82b);
1374 for trial in 0..2_000 {
1375 let mut graph = Graph::new();
1376 let input = graph.create_value(DataType::Float32, static_shape([1]));
1377 graph.add_input(input);
1378 let mut values = vec![input];
1379 let mut nodes = Vec::new();
1380 for _ in 0..(1 + rng.usize(48)) {
1381 let inputs = (0..(1 + rng.usize(4)))
1382 .map(|_| Some(values[rng.usize(values.len())]))
1383 .collect();
1384 let output = graph.create_value(DataType::Float32, static_shape([1]));
1385 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1386 values.push(output);
1387 }
1388 for &node in nodes.iter().filter(|_| rng.usize(5) == 0) {
1389 graph.remove_node(node);
1390 }
1391 assert_eq!(
1392 graph.topological_order(),
1393 reference_topological_order(&graph),
1394 "topology mismatch on trial {trial}"
1395 );
1396 }
1397 }
1398
1399 #[test]
1400 fn replace_node_groups_matches_sequential_mutation() {
1401 let mut sequential = Graph::new();
1402 let input = sequential.create_value(DataType::Float32, static_shape([1]));
1403 sequential.add_input(input);
1404 let interior = sequential.create_value(DataType::Float32, static_shape([1]));
1405 let first = sequential.insert_node(Node::new(
1406 NodeId(0),
1407 "Relu",
1408 vec![Some(input)],
1409 vec![interior],
1410 ));
1411 let output = sequential.create_value(DataType::Float32, static_shape([1]));
1412 let second = sequential.insert_node(Node::new(
1413 NodeId(0),
1414 "Relu",
1415 vec![Some(interior)],
1416 vec![output],
1417 ));
1418 sequential.add_output(output);
1419 let sibling_output = sequential.create_value(DataType::Float32, static_shape([1]));
1420 let sibling = sequential.insert_node(Node::new(
1421 NodeId(0),
1422 "Neg",
1423 vec![Some(input)],
1424 vec![sibling_output],
1425 ));
1426 sequential.add_output(sibling_output);
1427
1428 let mut batched = sequential.clone();
1429 let graph_outputs: HashSet<_> = batched.outputs.iter().copied().collect();
1430 let replacement0 = Node::new(NodeId(0), "EPContext", vec![Some(input)], vec![output]);
1431 let replacement1 = Node::new(
1432 NodeId(0),
1433 "EPContext",
1434 vec![Some(input)],
1435 vec![sibling_output],
1436 );
1437
1438 sequential.remove_node(first);
1439 sequential.remove_node(second);
1440 let replacement0_id = sequential.insert_node(replacement0.clone());
1441 sequential.remove_node(sibling);
1442 let replacement1_id = sequential.insert_node(replacement1.clone());
1443
1444 let inserted = batched.replace_node_groups(
1445 vec![
1446 (vec![first, second], replacement0),
1447 (vec![sibling], replacement1),
1448 ],
1449 &graph_outputs,
1450 );
1451 assert_eq!(inserted, vec![replacement0_id, replacement1_id]);
1452
1453 let sequential_nodes: Vec<_> = sequential
1454 .nodes
1455 .iter()
1456 .map(|(id, node)| {
1457 (
1458 id,
1459 node.op_type.clone(),
1460 node.inputs.clone(),
1461 node.outputs.clone(),
1462 )
1463 })
1464 .collect();
1465 let batched_nodes: Vec<_> = batched
1466 .nodes
1467 .iter()
1468 .map(|(id, node)| {
1469 (
1470 id,
1471 node.op_type.clone(),
1472 node.inputs.clone(),
1473 node.outputs.clone(),
1474 )
1475 })
1476 .collect();
1477 assert_eq!(batched_nodes, sequential_nodes);
1478
1479 let sequential_values: Vec<_> = sequential
1480 .values
1481 .iter()
1482 .map(|(id, value)| (id, value.producer, value.consumers.clone()))
1483 .collect();
1484 let batched_values: Vec<_> = batched
1485 .values
1486 .iter()
1487 .map(|(id, value)| (id, value.producer, value.consumers.clone()))
1488 .collect();
1489 assert_eq!(batched_values, sequential_values);
1490
1491 let next_sequential =
1492 sequential.insert_node(Node::new(NodeId(0), "Identity", Vec::new(), Vec::new()));
1493 let next_batched =
1494 batched.insert_node(Node::new(NodeId(0), "Identity", Vec::new(), Vec::new()));
1495 assert_eq!(next_batched, next_sequential);
1496 }
1497
1498 #[test]
1499 fn replace_node_groups_matches_sequential_orphan_collection() {
1500 let mut sequential = Graph::new();
1501 let input = sequential.create_value(DataType::Float32, static_shape([1]));
1502 sequential.add_input(input);
1503 let interior = sequential.create_value(DataType::Float32, static_shape([1]));
1504 let producer = sequential.insert_node(Node::new(
1505 NodeId(0),
1506 "Relu",
1507 vec![Some(input)],
1508 vec![interior],
1509 ));
1510 let output = sequential.create_value(DataType::Float32, static_shape([1]));
1511 let consumer = sequential.insert_node(Node::new(
1512 NodeId(0),
1513 "Relu",
1514 vec![Some(interior)],
1515 vec![output],
1516 ));
1517 sequential.add_output(output);
1518
1519 let mut batched = sequential.clone();
1520 let graph_outputs: HashSet<_> = batched.outputs.iter().copied().collect();
1521 let replacement = Node::new(NodeId(0), "EPContext", vec![Some(input)], vec![output]);
1522
1523 sequential.remove_node(consumer);
1524 sequential.remove_node(producer);
1525 sequential.insert_node(replacement.clone());
1526 batched.replace_node_groups(
1527 vec![(vec![consumer, producer], replacement)],
1528 &graph_outputs,
1529 );
1530
1531 assert!(sequential.try_value(interior).is_none());
1532 assert!(batched.try_value(interior).is_none());
1533 let next_sequential = sequential.create_value(DataType::Float32, static_shape([1]));
1534 let next_batched = batched.create_value(DataType::Float32, static_shape([1]));
1535 assert_eq!(next_batched, next_sequential);
1536 }
1537
1538 #[test]
1539 fn replace_node_preserves_id() {
1540 let mut g = sample_graph();
1541 let d = g.node(NodeId(1)).outputs[0];
1542 let b = g.node(NodeId(1)).inputs[0];
1543 let c = g.node(NodeId(1)).inputs[1];
1544 let sub = Node::new(NodeId(0), "Sub", vec![b, c], vec![d]);
1545 let id = g.replace_node(NodeId(1), sub);
1546 assert_eq!(id, NodeId(1));
1547 assert_eq!(g.node(NodeId(1)).op_type, "Sub");
1548 assert!(g.validate().is_ok());
1549 }
1550
1551 #[test]
1552 fn cycle_is_detected() {
1553 let mut g = Graph::new();
1554 let v0 = g.create_value(DataType::Float32, static_shape([1]));
1555 let v1 = g.create_value(DataType::Float32, static_shape([1]));
1556 g.insert_node(Node::new(NodeId(0), "A", vec![Some(v1)], vec![v0]));
1558 g.insert_node(Node::new(NodeId(0), "B", vec![Some(v0)], vec![v1]));
1559 assert_eq!(g.topological_order(), Err(GraphError::CycleDetected));
1560 assert!(g.validate().is_err());
1561 }
1562
1563 #[test]
1564 fn intern_symbol_dedups_by_name() {
1565 let mut g = Graph::new();
1566 let s1 = g.intern_symbol("batch");
1567 let s2 = g.intern_symbol("batch");
1568 let s3 = g.intern_symbol("seq");
1569 assert_eq!(s1, s2);
1570 assert_ne!(s1, s3);
1571 }
1572}