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 symbol_unifications: Vec<(SymbolId, SymbolId)>,
47 pub symbol_derivations: Vec<(SymbolId, SymbolId)>,
62 pub symbol_opaque: Vec<SymbolId>,
68 pub inference_symbol_floor: Option<u32>,
75 pub opset_imports: HashMap<String, u64>,
77 pub subgraphs: HashMap<(NodeId, String), Graph>,
79 pub model_functions: HashMap<ModelFunctionKey, ModelFunction>,
88 pub ambiguous_model_functions: HashSet<ModelFunctionKey>,
90
91 next_symbol: u32,
92 symbol_names: HashMap<String, SymbolId>,
93 unknown_value_types: HashSet<ValueId>,
94 unknown_value_shapes: HashSet<ValueId>,
95}
96
97pub type ModelFunctionKey = (String, String);
99
100#[derive(Clone, Debug)]
102pub struct ModelFunction {
103 pub domain: String,
104 pub name: String,
105 pub inputs: Vec<String>,
106 pub outputs: Vec<String>,
107 pub attributes: Vec<String>,
112 pub has_attribute_refs: bool,
115 pub body: Graph,
116}
117
118impl Graph {
125 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn effective_opset(&self, node: &Node) -> Option<u64> {
146 node.local_opset()
147 .or_else(|| self.opset_imports.get(node.domain.as_str()).copied())
148 }
149
150 pub fn node(&self, id: NodeId) -> &Node {
153 self.nodes.get(id).expect("node id not live in graph")
154 }
155
156 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
158 self.nodes.get_mut(id).expect("node id not live in graph")
159 }
160
161 pub fn try_node(&self, id: NodeId) -> Option<&Node> {
163 self.nodes.get(id)
164 }
165
166 pub fn value(&self, id: ValueId) -> &Value {
169 self.values.get(id).expect("value id not live in graph")
170 }
171
172 pub fn value_mut(&mut self, id: ValueId) -> &mut Value {
174 self.values.get_mut(id).expect("value id not live in graph")
175 }
176
177 pub fn try_value(&self, id: ValueId) -> Option<&Value> {
179 self.values.get(id)
180 }
181
182 pub fn uses(&self, value: ValueId) -> Vec<(NodeId, u32)> {
184 self.value(value).consumers.uses()
185 }
186
187 pub fn consumers(&self, value: ValueId) -> Vec<NodeId> {
189 self.value(value).consumers.nodes()
190 }
191
192 pub fn num_uses(&self, value: ValueId) -> usize {
194 self.value(value).consumers.len()
195 }
196
197 pub fn has_uses(&self, value: ValueId) -> bool {
199 !self.value(value).consumers.is_empty()
200 }
201
202 pub fn num_nodes(&self) -> usize {
204 self.nodes.len()
205 }
206
207 pub fn num_values(&self) -> usize {
209 self.values.len()
210 }
211
212 pub fn value_type_is_known(&self, id: ValueId) -> bool {
214 !self.unknown_value_types.contains(&id)
215 }
216
217 pub fn value_shape_is_known(&self, id: ValueId) -> bool {
219 !self.unknown_value_shapes.contains(&id)
220 }
221
222 pub fn mark_value_type_unknown(&mut self, id: ValueId) {
224 self.unknown_value_types.insert(id);
225 }
226
227 pub fn mark_value_type_known(&mut self, id: ValueId) {
229 self.unknown_value_types.remove(&id);
230 }
231
232 pub fn mark_value_shape_unknown(&mut self, id: ValueId) {
234 self.unknown_value_shapes.insert(id);
235 }
236
237 pub fn mark_value_shape_known(&mut self, id: ValueId) {
240 self.unknown_value_shapes.remove(&id);
241 }
242
243 pub fn create_symbol(&mut self, name: Option<String>) -> SymbolId {
247 let id = SymbolId(self.next_symbol);
248 self.next_symbol += 1;
249 self.symbol_constraints
250 .insert(id, SymbolConstraints::new(id, name.clone()));
251 if let Some(n) = name {
252 self.symbol_names.insert(n, id);
253 }
254 id
255 }
256
257 pub fn intern_symbol(&mut self, name: &str) -> SymbolId {
260 if let Some(id) = self.symbol_names.get(name) {
261 return *id;
262 }
263 self.create_symbol(Some(name.to_string()))
264 }
265
266 pub fn create_value(&mut self, dtype: DataType, shape: Shape) -> ValueId {
270 self.values
271 .insert_with(|vid| Value::new(vid, dtype, shape.clone()))
272 }
273
274 pub fn create_named_value(
276 &mut self,
277 name: impl Into<String>,
278 dtype: DataType,
279 shape: Shape,
280 ) -> ValueId {
281 let id = self.create_value(dtype, shape);
282 self.value_mut(id).name = Some(name.into());
283 id
284 }
285
286 pub fn add_input(&mut self, value: ValueId) {
288 self.value_mut(value).is_graph_input = true;
289 self.inputs.push(value);
290 debug_assert!(self.value(value).is_graph_input);
291 }
292
293 pub fn add_output(&mut self, value: ValueId) {
295 self.value_mut(value).is_graph_output = true;
296 self.outputs.push(value);
297 debug_assert!(self.value(value).is_graph_output);
298 }
299
300 pub fn insert_output(&mut self, index: usize, value: ValueId) {
302 self.value_mut(value).is_graph_output = true;
303 self.outputs.insert(index, value);
304 debug_assert!(self.value(value).is_graph_output);
305 }
306
307 pub fn remove_input(&mut self, index: usize) -> ValueId {
309 let value = self.inputs.remove(index);
310 if !self.inputs.contains(&value) {
311 self.value_mut(value).is_graph_input = false;
312 }
313 debug_assert_eq!(
314 self.value(value).is_graph_input,
315 self.inputs.contains(&value)
316 );
317 value
318 }
319
320 pub fn remove_output(&mut self, index: usize) -> ValueId {
322 let value = self.outputs.remove(index);
323 if !self.outputs.contains(&value) {
324 self.value_mut(value).is_graph_output = false;
325 }
326 debug_assert_eq!(
327 self.value(value).is_graph_output,
328 self.outputs.contains(&value)
329 );
330 value
331 }
332
333 pub fn set_inputs(&mut self, inputs: Vec<ValueId>) {
335 for value in self.inputs.drain(..) {
336 if let Some(metadata) = self.values.get_mut(value) {
337 metadata.is_graph_input = false;
338 }
339 }
340 for &value in &inputs {
341 self.value_mut(value).is_graph_input = true;
342 }
343 self.inputs = inputs;
344 debug_assert!(
345 self.inputs
346 .iter()
347 .all(|&value| self.value(value).is_graph_input)
348 );
349 }
350
351 pub fn set_outputs(&mut self, outputs: Vec<ValueId>) {
353 for value in self.outputs.drain(..) {
354 if let Some(metadata) = self.values.get_mut(value) {
355 metadata.is_graph_output = false;
356 }
357 }
358 for &value in &outputs {
359 self.value_mut(value).is_graph_output = true;
360 }
361 self.outputs = outputs;
362 debug_assert!(
363 self.outputs
364 .iter()
365 .all(|&value| self.value(value).is_graph_output)
366 );
367 }
368
369 pub fn set_initializer(&mut self, value: ValueId, weight: WeightRef) {
371 self.initializers.insert(value, weight);
372 }
373
374 pub fn predecessors(&self, node: NodeId) -> Vec<NodeId> {
378 let mut out = Vec::new();
379 let mut seen = HashSet::new();
380 for v in self.node(node).input_values() {
381 if let Some(val) = self.values.get(v)
382 && let Some(prod) = val.producer
383 && seen.insert(prod)
384 {
385 out.push(prod);
386 }
387 }
388 out.sort_unstable_by_key(|node| node.0);
389 out
390 }
391
392 pub fn successors(&self, node: NodeId) -> Vec<NodeId> {
394 let mut out = Vec::new();
395 let mut seen = HashSet::new();
396 for &v in &self.node(node).outputs {
397 if let Some(val) = self.values.get(v) {
398 for c in val.consumers.nodes() {
399 if seen.insert(c) {
400 out.push(c);
401 }
402 }
403 }
404 }
405 out.sort_unstable_by_key(|node| node.0);
406 out
407 }
408
409 pub fn nodes_between(&self, inputs: &[ValueId], outputs: &[ValueId]) -> Vec<NodeId> {
414 let boundary: HashSet<ValueId> = inputs.iter().copied().collect();
415 let mut nodes = Vec::new();
416 let mut seen_nodes = HashSet::new();
417 let mut seen_values = HashSet::new();
418 let mut stack: Vec<ValueId> = outputs.to_vec();
419 while let Some(v) = stack.pop() {
420 if boundary.contains(&v) || !seen_values.insert(v) {
421 continue;
422 }
423 let Some(val) = self.values.get(v) else {
424 continue;
425 };
426 if let Some(prod) = val.producer {
427 if seen_nodes.insert(prod) {
428 nodes.push(prod);
429 }
430 for iv in self.node(prod).input_values() {
431 stack.push(iv);
432 }
433 }
434 }
435 nodes
436 }
437
438 pub fn topological_order(&self) -> Result<Vec<NodeId>, GraphError> {
443 const VACANT: usize = usize::MAX;
444 let mut in_degree = vec![VACANT; self.nodes.capacity()];
445 let mut adj = vec![Vec::<NodeId>::new(); self.nodes.capacity()];
446 for node in self.nodes.keys() {
447 in_degree[node.0 as usize] = 0;
448 }
449
450 for (nid, node) in self.nodes.iter() {
451 for v in node.input_values() {
452 if let Some(val) = self.values.get(v)
453 && let Some(prod) = val.producer
454 && self.nodes.contains(prod)
455 {
456 adj[prod.0 as usize].push(nid);
457 in_degree[nid.0 as usize] += 1;
458 }
459 }
460 }
461
462 let mut ready: BinaryHeap<std::cmp::Reverse<u32>> = in_degree
464 .iter()
465 .enumerate()
466 .filter(|(_, degree)| **degree == 0)
467 .map(|(raw, _)| std::cmp::Reverse(raw as u32))
468 .collect();
469
470 let mut order = Vec::with_capacity(self.nodes.len());
471 while let Some(std::cmp::Reverse(raw)) = ready.pop() {
472 let nid = NodeId(raw);
473 order.push(nid);
474 for &successor in &adj[raw as usize] {
475 let degree = &mut in_degree[successor.0 as usize];
476 *degree -= 1;
477 if *degree == 0 {
478 ready.push(std::cmp::Reverse(successor.0));
479 }
480 }
481 }
482
483 if order.len() != self.nodes.len() {
484 return Err(GraphError::CycleDetected);
485 }
486 Ok(order)
487 }
488
489 pub fn normalize_domains(&mut self) {
500 for node in self.nodes.values_mut() {
501 if node.domain == crate::AI_ONNX_DOMAIN {
502 node.domain.clear();
503 }
504 }
505 if let Some(version) = self.opset_imports.remove(crate::AI_ONNX_DOMAIN) {
506 let entry = self.opset_imports.entry(String::new()).or_insert(version);
507 *entry = (*entry).max(version);
508 }
509 for subgraph in self.subgraphs.values_mut() {
510 subgraph.normalize_domains();
511 }
512 }
513
514 pub fn insert_node(&mut self, node: Node) -> NodeId {
517 let id = self.nodes.insert_with(|nid| {
518 let mut node = node;
519 node.id = nid;
520 node
521 });
522 self.connect_edges(id);
523 id
524 }
525
526 pub fn remove_node(&mut self, id: NodeId) {
529 if !self.nodes.contains(id) {
530 return;
531 }
532 self.disconnect_edges(id);
533 let outputs = self.node(id).outputs.clone();
534 self.nodes.remove(id);
535 for v in outputs {
536 self.gc_value_if_orphan(v);
537 }
538 }
539
540 pub fn remove_nodes(&mut self, ids: &[NodeId]) {
546 for &id in ids {
547 self.remove_node(id);
548 }
549 }
550
551 pub fn replace_node_groups(
561 &mut self,
562 groups: Vec<(Vec<NodeId>, Node)>,
563 graph_outputs: &HashSet<ValueId>,
564 ) -> Vec<NodeId> {
565 debug_assert_eq!(
566 graph_outputs,
567 &self.outputs.iter().copied().collect::<HashSet<_>>()
568 );
569 let mut removed_nodes = HashSet::new();
570 for (node_ids, _) in &groups {
571 assert!(
572 !node_ids.is_empty(),
573 "replace_node_groups: group must not be empty"
574 );
575 for &id in node_ids {
576 assert!(
577 self.nodes.contains(id),
578 "replace_node_groups: node id not live"
579 );
580 assert!(
581 removed_nodes.insert(id),
582 "replace_node_groups: groups must be disjoint"
583 );
584 }
585 }
586
587 let mut inserted = Vec::with_capacity(groups.len());
588 for (node_ids, replacement) in groups {
589 for id in node_ids {
590 self.remove_node(id);
591 }
592 inserted.push(self.insert_node(replacement));
593 }
594
595 inserted
596 }
597
598 pub fn replace_node(&mut self, old: NodeId, new: Node) -> NodeId {
604 assert!(self.nodes.contains(old), "replace_node: old id not live");
605 self.disconnect_edges(old);
606 {
607 let slot = self.nodes.get_mut(old).expect("old live");
608 let mut new = new;
609 new.id = old;
610 *slot = new;
611 }
612 self.connect_edges(old);
613 old
614 }
615
616 pub fn insert_on_edge(&mut self, value: ValueId, new_node: Node) -> NodeId {
622 let (dtype, shape) = {
623 let v = self.value(value);
624 (v.dtype, v.shape.clone())
625 };
626 let new_value = self.create_value(dtype, shape);
627 self.replace_all_uses(value, new_value);
630
631 let mut new_node = new_node;
632 new_node.inputs = vec![Some(value)];
633 new_node.outputs = vec![new_value];
634 self.insert_node(new_node)
635 }
636
637 pub fn replace_input(
642 &mut self,
643 node: NodeId,
644 input_index: usize,
645 new_value: Option<ValueId>,
646 ) -> Option<ValueId> {
647 assert!(self.nodes.contains(node), "replace_input: node id not live");
648 assert!(
649 input_index < self.node(node).inputs.len(),
650 "replace_input: input index out of bounds"
651 );
652 if let Some(value) = new_value {
653 assert!(
654 self.values.contains(value),
655 "replace_input: value id not live"
656 );
657 }
658
659 let old_value = self.node(node).inputs[input_index];
660 if old_value == new_value {
661 return old_value;
662 }
663 if let Some(value) = old_value {
664 let removed = self
665 .value_mut(value)
666 .consumers
667 .remove(node, input_index as u32);
668 debug_assert!(removed, "old input edge must be present");
669 }
670 self.node_mut(node).inputs[input_index] = new_value;
671 if let Some(value) = new_value {
672 self.value_mut(value)
673 .consumers
674 .insert(node, input_index as u32);
675 }
676 old_value
677 }
678
679 pub fn replace_all_uses(&mut self, old_value: ValueId, new_value: ValueId) {
682 if old_value == new_value {
683 return;
684 }
685 let uses = match self.values.get(old_value) {
686 Some(value) => value.consumers.uses(),
687 None => return,
688 };
689 for (node, input_index) in uses {
690 if self.nodes.contains(node) {
691 self.replace_input(node, input_index as usize, Some(new_value));
692 }
693 }
694 if self.value(old_value).is_graph_output {
695 let mut outputs = self.outputs.clone();
696 for output in &mut outputs {
697 if *output == old_value {
698 *output = new_value;
699 }
700 }
701 self.set_outputs(outputs);
702 }
703 }
704
705 pub fn validate(&self) -> Result<(), Vec<GraphError>> {
709 let mut errors = Vec::new();
710 let graph_inputs: HashSet<_> = self.inputs.iter().copied().collect();
711 let graph_outputs: HashSet<_> = self.outputs.iter().copied().collect();
712 for (value, metadata) in self.values.iter() {
713 debug_assert_eq!(
714 metadata.is_graph_input,
715 graph_inputs.contains(&value),
716 "graph-input membership flag drifted for {value:?}"
717 );
718 debug_assert_eq!(
719 metadata.is_graph_output,
720 graph_outputs.contains(&value),
721 "graph-output membership flag drifted for {value:?}"
722 );
723 }
724
725 let mut produced: HashMap<ValueId, NodeId> = HashMap::new();
727 for (nid, node) in self.nodes.iter() {
728 for (input_index, input) in node.inputs.iter().enumerate() {
729 if let Some(value) = input {
730 if !self.values.contains(*value) {
731 errors.push(GraphError::DanglingValue(*value));
732 } else if !self
733 .value(*value)
734 .consumers
735 .contains(nid, input_index as u32)
736 {
737 errors.push(GraphError::ConsumerLinkMismatch(*value));
738 }
739 }
740 }
741 for &v in &node.outputs {
742 if !self.values.contains(v) {
743 errors.push(GraphError::DanglingValue(v));
744 continue;
745 }
746 if produced.insert(v, nid).is_some() {
747 errors.push(GraphError::DuplicateOutput(v));
748 }
749 }
750 }
751
752 for (vid, val) in self.values.iter() {
754 if let Some(p) = val.producer {
755 if !self.nodes.contains(p) {
756 errors.push(GraphError::DanglingNode(p));
757 } else if !self.node(p).outputs.contains(&vid) {
758 errors.push(GraphError::ProducerLinkMismatch(vid));
759 }
760 }
761 for (consumer, input_index) in val.consumers.uses() {
762 if !self.nodes.contains(consumer) {
763 errors.push(GraphError::DanglingNode(consumer));
764 } else if self.node(consumer).inputs.get(input_index as usize) != Some(&Some(vid)) {
765 errors.push(GraphError::ConsumerLinkMismatch(vid));
766 }
767 }
768 }
769
770 for &inp in &self.inputs {
772 if let Some(val) = self.values.get(inp) {
773 if val.producer.is_some() {
774 errors.push(GraphError::InputHasProducer(inp));
775 }
776 debug_assert!(val.is_graph_input);
777 } else {
778 errors.push(GraphError::DanglingValue(inp));
779 }
780 }
781
782 for &out in &self.outputs {
785 match self.values.get(out) {
786 Some(val) => {
787 debug_assert!(val.is_graph_output);
788 let is_source = val.is_graph_input || self.initializers.contains_key(&out);
789 if val.producer.is_none() && !is_source {
790 errors.push(GraphError::MissingProducer(out));
791 }
792 }
793 None => errors.push(GraphError::DanglingValue(out)),
794 }
795 }
796
797 if let Err(e) = self.topological_order() {
799 errors.push(e);
800 }
801
802 for (domain, &version) in &self.opset_imports {
804 if version == 0 {
805 errors.push(GraphError::InvalidOpsetImport {
806 domain: domain.clone(),
807 version,
808 });
809 }
810 }
811
812 for sub in self.subgraphs.values() {
814 if let Err(mut sub_errors) = sub.validate() {
815 errors.append(&mut sub_errors);
816 }
817 }
818
819 if errors.is_empty() {
820 Ok(())
821 } else {
822 Err(errors)
823 }
824 }
825
826 fn connect_edges(&mut self, id: NodeId) {
830 let inputs = self.node(id).inputs.clone();
831 let outputs = self.node(id).outputs.clone();
832 for (input_index, input) in inputs.into_iter().enumerate() {
833 if let Some(value) = input
834 && let Some(metadata) = self.values.get_mut(value)
835 {
836 metadata.consumers.insert(id, input_index as u32);
837 }
838 }
839 for v in outputs {
840 if let Some(val) = self.values.get_mut(v) {
841 val.producer = Some(id);
842 }
843 }
844 }
845
846 fn disconnect_edges(&mut self, id: NodeId) {
849 let input_count = self.node(id).inputs.len();
850 let outputs = self.node(id).outputs.clone();
851 for input_index in 0..input_count {
852 self.replace_input(id, input_index, None);
853 }
854 for v in outputs {
855 if let Some(val) = self.values.get_mut(v)
856 && val.producer == Some(id)
857 {
858 val.producer = None;
859 }
860 }
861 }
862
863 pub fn gc_value_if_orphan(&mut self, value: ValueId) {
869 let orphan = match self.values.get(value) {
870 Some(v) => {
871 v.producer.is_none()
872 && v.consumers.is_empty()
873 && !v.is_graph_input
874 && !v.is_graph_output
875 && !self.initializers.contains_key(&value)
876 }
877 None => false,
878 };
879 if orphan {
880 self.values.remove(value);
881 self.unknown_value_types.remove(&value);
882 self.unknown_value_shapes.remove(&value);
883 }
884 }
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890 use crate::shape::static_shape;
891 use crate::tensor::TensorData;
892
893 fn assert_graphs_identical(
894 mut batched: Graph,
895 mut sequential: Graph,
896 node_probes: usize,
897 value_probes: usize,
898 trial: usize,
899 ) {
900 assert_eq!(
901 format!("{batched:#?}"),
902 format!("{sequential:#?}"),
903 "graph mismatch on randomized trial {trial}"
904 );
905
906 for _ in 0..node_probes {
909 let batched_id =
910 batched.insert_node(Node::new(NodeId(0), "Probe", Vec::new(), Vec::new()));
911 let sequential_id =
912 sequential.insert_node(Node::new(NodeId(0), "Probe", Vec::new(), Vec::new()));
913 assert_eq!(
914 batched_id, sequential_id,
915 "node arena mismatch on randomized trial {trial}"
916 );
917 }
918 for _ in 0..value_probes {
919 let batched_id = batched.create_value(DataType::Float32, static_shape([1]));
920 let sequential_id = sequential.create_value(DataType::Float32, static_shape([1]));
921 assert_eq!(
922 batched_id, sequential_id,
923 "value arena mismatch on randomized trial {trial}"
924 );
925 }
926 }
927
928 struct TestRng(u64);
929
930 impl TestRng {
931 fn next(&mut self) -> u64 {
932 self.0 ^= self.0 << 13;
933 self.0 ^= self.0 >> 7;
934 self.0 ^= self.0 << 17;
935 self.0
936 }
937
938 fn usize(&mut self, upper: usize) -> usize {
939 (self.next() as usize) % upper
940 }
941 }
942
943 fn reference_remove_node(graph: &mut Graph, id: NodeId) {
944 if !graph.nodes.contains(id) {
945 return;
946 }
947 let (inputs, outputs) = {
948 let node = graph.node(id);
949 (
950 node.input_values().collect::<Vec<_>>(),
951 node.outputs.clone(),
952 )
953 };
954 let unique_inputs: HashSet<_> = inputs.into_iter().collect();
955 for value in unique_inputs {
956 if let Some(metadata) = graph.values.get_mut(value) {
957 for (consumer, input_index) in metadata.consumers.uses() {
958 if consumer == id {
959 metadata.consumers.remove(consumer, input_index);
960 }
961 }
962 }
963 }
964 for &value in &outputs {
965 if let Some(metadata) = graph.values.get_mut(value)
966 && metadata.producer == Some(id)
967 {
968 metadata.producer = None;
969 }
970 }
971 graph.nodes.remove(id);
972 for value in outputs {
973 let orphan = graph.values.get(value).is_some_and(|metadata| {
974 metadata.producer.is_none()
975 && metadata.consumers.is_empty()
976 && !graph.inputs.contains(&value)
977 && !graph.outputs.contains(&value)
978 && !graph.initializers.contains_key(&value)
979 });
980 if orphan {
981 graph.values.remove(value);
982 graph.unknown_value_types.remove(&value);
983 graph.unknown_value_shapes.remove(&value);
984 }
985 }
986 }
987
988 fn reference_topological_order(graph: &Graph) -> Result<Vec<NodeId>, GraphError> {
989 let mut in_degree: HashMap<NodeId, usize> =
990 graph.nodes.keys().map(|node| (node, 0)).collect();
991 let mut adjacency: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
992 for (node_id, node) in graph.nodes.iter() {
993 for value in node.input_values() {
994 if let Some(producer) = graph.value(value).producer
995 && graph.nodes.contains(producer)
996 {
997 adjacency.entry(producer).or_default().push(node_id);
998 *in_degree.get_mut(&node_id).unwrap() += 1;
999 }
1000 }
1001 }
1002 let mut ready: BinaryHeap<std::cmp::Reverse<u32>> = in_degree
1003 .iter()
1004 .filter(|(_, degree)| **degree == 0)
1005 .map(|(node, _)| std::cmp::Reverse(node.0))
1006 .collect();
1007 let mut order = Vec::with_capacity(graph.num_nodes());
1008 while let Some(std::cmp::Reverse(raw)) = ready.pop() {
1009 let node = NodeId(raw);
1010 order.push(node);
1011 if let Some(successors) = adjacency.get(&node) {
1012 for successor in successors {
1013 let degree = in_degree.get_mut(successor).unwrap();
1014 *degree -= 1;
1015 if *degree == 0 {
1016 ready.push(std::cmp::Reverse(successor.0));
1017 }
1018 }
1019 }
1020 }
1021 if order.len() == graph.num_nodes() {
1022 Ok(order)
1023 } else {
1024 Err(GraphError::CycleDetected)
1025 }
1026 }
1027
1028 fn sample_graph() -> Graph {
1030 let mut g = Graph::new();
1031 g.opset_imports.insert(String::new(), 17);
1032 let a = g.create_named_value("a", DataType::Float32, static_shape([4]));
1033 let c = g.create_named_value("c", DataType::Float32, static_shape([4]));
1034 g.add_input(a);
1035 g.add_input(c);
1036
1037 let b = g.create_value(DataType::Float32, static_shape([4]));
1038 let relu = Node::new(NodeId(0), "Relu", vec![Some(a)], vec![b]);
1039 g.insert_node(relu);
1040
1041 let d = g.create_named_value("d", DataType::Float32, static_shape([4]));
1042 let add = Node::new(NodeId(0), "Add", vec![Some(b), Some(c)], vec![d]);
1043 g.insert_node(add);
1044 g.add_output(d);
1045 g
1046 }
1047
1048 #[test]
1049 fn edges_are_wired_on_insert() {
1050 let g = sample_graph();
1051 assert_eq!(g.num_nodes(), 2);
1052 let b = g.value(ValueId(2));
1054 assert!(b.producer.is_some());
1055 assert_eq!(b.consumers.len(), 1);
1056 }
1057
1058 #[test]
1059 fn topo_order_is_valid_and_deterministic() {
1060 let g = sample_graph();
1061 let order = g.topological_order().unwrap();
1062 assert_eq!(order.len(), 2);
1063 assert_eq!(order, vec![NodeId(0), NodeId(1)]);
1065 }
1066
1067 #[test]
1068 fn uses_preserve_input_multiplicity_and_replace_one_slot() {
1069 let mut graph = Graph::new();
1070 let old = graph.create_value(DataType::Float32, static_shape([1]));
1071 let new = graph.create_value(DataType::Float32, static_shape([1]));
1072 graph.add_input(old);
1073 graph.add_input(new);
1074 let output = graph.create_value(DataType::Float32, static_shape([1]));
1075 let node = graph.insert_node(Node::new(
1076 NodeId(0),
1077 "Add",
1078 vec![Some(old), Some(old)],
1079 vec![output],
1080 ));
1081
1082 assert_eq!(graph.uses(old), vec![(node, 0), (node, 1)]);
1083 assert_eq!(graph.consumers(old), vec![node]);
1084 assert_eq!(graph.replace_input(node, 1, Some(new)), Some(old));
1085 assert_eq!(graph.uses(old), vec![(node, 0)]);
1086 assert_eq!(graph.uses(new), vec![(node, 1)]);
1087 assert!(graph.validate().is_ok());
1088
1089 graph.remove_node(node);
1090 assert!(graph.uses(old).is_empty());
1091 assert!(graph.uses(new).is_empty());
1092 }
1093
1094 #[test]
1095 fn io_membership_flags_follow_ordered_lists() {
1096 let mut graph = Graph::new();
1097 let a = graph.create_value(DataType::Float32, static_shape([1]));
1098 let b = graph.create_value(DataType::Float32, static_shape([1]));
1099 graph.add_input(a);
1100 graph.add_output(a);
1101 graph.insert_output(0, b);
1102 assert!(graph.value(a).is_graph_input);
1103 assert!(graph.value(a).is_graph_output);
1104 assert!(graph.value(b).is_graph_output);
1105
1106 assert_eq!(graph.remove_output(1), a);
1107 assert!(!graph.value(a).is_graph_output);
1108 graph.set_inputs(vec![b]);
1109 assert!(!graph.value(a).is_graph_input);
1110 assert!(graph.value(b).is_graph_input);
1111 graph.set_outputs(vec![a]);
1112 assert!(graph.value(a).is_graph_output);
1113 assert!(!graph.value(b).is_graph_output);
1114 }
1115
1116 #[test]
1117 fn consumer_hash_insertion_order_is_not_observable() {
1118 let mut first = Graph::new();
1119 let hub = first.create_value(DataType::Float32, static_shape([1]));
1120 first.add_input(hub);
1121 let mut nodes = Vec::new();
1122 for _ in 0..32 {
1123 let output = first.create_value(DataType::Float32, static_shape([1]));
1124 nodes.push(first.insert_node(Node::new(
1125 NodeId(0),
1126 "Add",
1127 vec![Some(hub), Some(hub)],
1128 vec![output],
1129 )));
1130 }
1131 let mut shuffled = first.clone();
1132 let mut uses = shuffled.uses(hub);
1133 for &(node, input_index) in &uses {
1134 shuffled.replace_input(node, input_index as usize, None);
1135 }
1136 let mut rng = TestRng(0x6a09_e667_f3bc_c909);
1137 for index in (1..uses.len()).rev() {
1138 uses.swap(index, rng.usize(index + 1));
1139 }
1140 for (node, input_index) in uses {
1141 shuffled.replace_input(node, input_index as usize, Some(hub));
1142 }
1143
1144 assert_eq!(first.uses(hub), shuffled.uses(hub));
1145 assert_eq!(first.consumers(hub), shuffled.consumers(hub));
1146 assert_eq!(first.topological_order(), shuffled.topological_order());
1147 assert_eq!(format!("{first:#?}"), format!("{shuffled:#?}"));
1148 assert_eq!(
1149 format!("{:#?}", sample_graph()),
1150 format!("{:#?}", sample_graph())
1151 );
1152 assert_eq!(nodes, first.consumers(hub));
1153 }
1154
1155 #[test]
1156 fn validate_accepts_wellformed_graph() {
1157 let g = sample_graph();
1158 assert!(g.validate().is_ok());
1159 }
1160
1161 #[test]
1162 fn predecessors_and_successors() {
1163 let g = sample_graph();
1164 assert_eq!(g.successors(NodeId(0)), vec![NodeId(1)]);
1165 assert_eq!(g.predecessors(NodeId(1)), vec![NodeId(0)]);
1166 }
1167
1168 #[test]
1169 fn nodes_between_walks_back() {
1170 let g = sample_graph();
1171 let between = g.nodes_between(&[ValueId(0), ValueId(1)], &[ValueId(3)]);
1172 assert!(between.contains(&NodeId(0)));
1173 assert!(between.contains(&NodeId(1)));
1174 assert_eq!(between.len(), 2);
1175 }
1176
1177 #[test]
1178 fn replace_all_uses_redirects_consumers() {
1179 let mut g = sample_graph();
1180 let e = g.create_value(DataType::Float32, static_shape([4]));
1182 g.replace_all_uses(ValueId(2), e);
1183 let add = g.node(NodeId(1));
1185 assert!(add.input_values().any(|v| v == e));
1186 assert!(!add.input_values().any(|v| v == ValueId(2)));
1187 assert_eq!(g.consumers(e), vec![NodeId(1)]);
1188 assert!(g.value(ValueId(2)).consumers.is_empty());
1189 }
1190
1191 #[test]
1192 fn insert_on_edge_splices_node() {
1193 let mut g = sample_graph();
1194 let ident = Node::new(NodeId(0), "Identity", vec![], vec![]);
1196 let nid = g.insert_on_edge(ValueId(2), ident);
1197 let new_out = g.node(nid).outputs[0];
1199 assert_eq!(g.value(new_out).producer, Some(nid));
1200 assert!(g.node(NodeId(1)).input_values().any(|v| v == new_out));
1201 assert!(g.validate().is_ok());
1202 }
1203
1204 #[test]
1205 fn remove_node_disconnects_and_gcs() {
1206 let mut g = sample_graph();
1207 g.remove_node(NodeId(1)); assert_eq!(g.num_nodes(), 1);
1209 assert!(g.value(ValueId(3)).producer.is_none());
1211 assert!(g.value(ValueId(2)).consumers.is_empty());
1213 }
1214
1215 #[test]
1216 fn remove_nodes_filters_wide_shared_input_once() {
1217 let mut graph = Graph::new();
1218 let input = graph.create_value(DataType::Float32, static_shape([1]));
1219 graph.add_input(input);
1220 let mut dead = Vec::new();
1221 let mut outputs = Vec::new();
1222 for _ in 0..1_000 {
1223 let output = graph.create_value(DataType::Float32, static_shape([1]));
1224 dead.push(graph.insert_node(Node::new(
1225 NodeId(0),
1226 "Relu",
1227 vec![Some(input)],
1228 vec![output],
1229 )));
1230 outputs.push(output);
1231 }
1232
1233 graph.remove_nodes(&dead);
1234
1235 assert_eq!(graph.num_nodes(), 0);
1236 assert!(graph.value(input).consumers.is_empty());
1237 assert!(
1238 outputs
1239 .into_iter()
1240 .all(|output| graph.try_value(output).is_none())
1241 );
1242 }
1243
1244 #[test]
1245 fn remove_nodes_keeps_surviving_shared_consumer() {
1246 let mut graph = Graph::new();
1247 let input = graph.create_value(DataType::Float32, static_shape([1]));
1248 graph.add_input(input);
1249 let mut nodes = Vec::new();
1250 for op_type in ["Relu", "Neg", "Abs"] {
1251 let output = graph.create_value(DataType::Float32, static_shape([1]));
1252 nodes.push(graph.insert_node(Node::new(
1253 NodeId(0),
1254 op_type,
1255 vec![Some(input)],
1256 vec![output],
1257 )));
1258 }
1259
1260 graph.remove_nodes(&nodes[..2]);
1261
1262 assert_eq!(graph.consumers(input), vec![nodes[2]]);
1263 assert!(graph.try_node(nodes[2]).is_some());
1264 assert!(graph.validate().is_ok());
1265 }
1266
1267 #[test]
1268 fn remove_nodes_collects_value_after_all_consumers_are_removed() {
1269 let mut graph = Graph::new();
1270 let input = graph.create_value(DataType::Float32, static_shape([1]));
1271 graph.add_input(input);
1272 let shared = graph.create_value(DataType::Float32, static_shape([1]));
1273 let producer = graph.insert_node(Node::new(
1274 NodeId(0),
1275 "Relu",
1276 vec![Some(input)],
1277 vec![shared],
1278 ));
1279 let mut consumers = Vec::new();
1280 for op_type in ["Neg", "Abs"] {
1281 let output = graph.create_value(DataType::Float32, static_shape([1]));
1282 consumers.push(graph.insert_node(Node::new(
1283 NodeId(0),
1284 op_type,
1285 vec![Some(shared)],
1286 vec![output],
1287 )));
1288 }
1289
1290 graph.remove_nodes(&[consumers[0], consumers[1], producer]);
1291
1292 assert!(graph.try_value(shared).is_none());
1293 }
1294
1295 #[test]
1296 fn remove_nodes_keeps_graph_outputs_and_initializers() {
1297 let mut graph = Graph::new();
1298 let input = graph.create_value(DataType::Float32, static_shape([1]));
1299 graph.add_input(input);
1300
1301 let graph_output = graph.create_value(DataType::Float32, static_shape([1]));
1302 let output_node = graph.insert_node(Node::new(
1303 NodeId(0),
1304 "Relu",
1305 vec![Some(input)],
1306 vec![graph_output],
1307 ));
1308 graph.add_output(graph_output);
1309
1310 let initializer = graph.create_value(DataType::Float32, static_shape([1]));
1311 let initializer_node = graph.insert_node(Node::new(
1312 NodeId(0),
1313 "Neg",
1314 vec![Some(input)],
1315 vec![initializer],
1316 ));
1317 graph.set_initializer(
1318 initializer,
1319 WeightRef::Inline(TensorData::from_raw(
1320 DataType::Float32,
1321 vec![1],
1322 0.0f32.to_le_bytes().to_vec(),
1323 )),
1324 );
1325
1326 graph.remove_nodes(&[output_node, initializer_node]);
1327
1328 assert!(graph.try_value(graph_output).is_some());
1329 assert!(graph.value(graph_output).producer.is_none());
1330 assert!(graph.try_value(initializer).is_some());
1331 assert!(graph.value(initializer).producer.is_none());
1332 }
1333
1334 #[test]
1335 fn remove_nodes_ignores_duplicate_and_nonlive_ids() {
1336 let graph = sample_graph();
1337 let ids = [NodeId(u32::MAX), NodeId(1), NodeId(1)];
1338 let mut sequential = graph.clone();
1339 for id in ids {
1340 sequential.remove_node(id);
1341 }
1342 let mut batched = graph;
1343 batched.remove_nodes(&ids);
1344
1345 assert_graphs_identical(batched, sequential, 3, 5, 0);
1346 }
1347
1348 #[test]
1349 fn remove_nodes_matches_sequential_removal_on_random_dags() {
1350 let mut rng = TestRng(0x4d59_5df4_d0f3_3173);
1351
1352 for trial in 0..10_000 {
1353 let input_count = 1 + rng.usize(3);
1354 let node_count = 1 + rng.usize(12);
1355 let mut graph = Graph::new();
1356 let mut values = Vec::new();
1357 for _ in 0..input_count {
1358 let input = graph.create_value(DataType::Float32, static_shape([1]));
1359 graph.add_input(input);
1360 values.push(input);
1361 }
1362
1363 let mut nodes = Vec::with_capacity(node_count);
1364 for _ in 0..node_count {
1365 let input_arity = 1 + rng.usize(3);
1366 let inputs = (0..input_arity)
1367 .map(|_| Some(values[rng.usize(values.len())]))
1368 .collect();
1369 let output = graph.create_value(DataType::Float32, static_shape([1]));
1370 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1371 if rng.usize(5) == 0 {
1372 graph.mark_value_type_unknown(output);
1373 }
1374 if rng.usize(5) == 0 {
1375 graph.mark_value_shape_unknown(output);
1376 }
1377 values.push(output);
1378 }
1379
1380 for _ in 0..rng.usize(4) {
1381 let output = values[rng.usize(values.len())];
1382 graph.add_output(output);
1383 }
1384
1385 for i in (1..nodes.len()).rev() {
1386 let j = rng.usize(i + 1);
1387 nodes.swap(i, j);
1388 }
1389 nodes.truncate(rng.usize(nodes.len() + 1));
1390
1391 let mut sequential = graph.clone();
1392 for &id in &nodes {
1393 sequential.remove_node(id);
1394 }
1395 let mut batched = graph;
1396 batched.remove_nodes(&nodes);
1397
1398 assert_graphs_identical(
1399 batched,
1400 sequential,
1401 node_count + 1,
1402 input_count + node_count + 1,
1403 trial,
1404 );
1405 }
1406 }
1407
1408 #[test]
1409 fn single_node_removal_matches_vector_reference_on_random_dags() {
1410 let mut rng = TestRng(0xbb67_ae85_84ca_a73b);
1411 for trial in 0..2_000 {
1412 let mut graph = Graph::new();
1413 let input_count = 1 + rng.usize(3);
1414 let node_count = 1 + rng.usize(24);
1415 let mut values = Vec::new();
1416 for _ in 0..input_count {
1417 let value = graph.create_value(DataType::Float32, static_shape([1]));
1418 graph.add_input(value);
1419 values.push(value);
1420 }
1421 let mut nodes = Vec::new();
1422 for _ in 0..node_count {
1423 let input_count = 1 + rng.usize(4);
1424 let inputs = (0..input_count)
1425 .map(|_| Some(values[rng.usize(values.len())]))
1426 .collect();
1427 let output = graph.create_value(DataType::Float32, static_shape([1]));
1428 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1429 values.push(output);
1430 }
1431 for _ in 0..rng.usize(4) {
1432 graph.add_output(values[rng.usize(values.len())]);
1433 }
1434
1435 let mut removals = nodes.clone();
1436 for index in (1..removals.len()).rev() {
1437 removals.swap(index, rng.usize(index + 1));
1438 }
1439 removals.truncate(rng.usize(removals.len() + 1));
1440 if let Some(&duplicate) = removals.first() {
1441 removals.push(duplicate);
1442 }
1443 removals.push(NodeId(u32::MAX));
1444
1445 let mut actual = graph.clone();
1446 let mut reference = graph;
1447 for &node in &removals {
1448 actual.remove_node(node);
1449 reference_remove_node(&mut reference, node);
1450 }
1451
1452 assert_eq!(
1453 format!("{actual:#?}"),
1454 format!("{reference:#?}"),
1455 "debug mismatch on trial {trial}"
1456 );
1457 assert_eq!(
1458 actual.topological_order(),
1459 reference.topological_order(),
1460 "topology mismatch on trial {trial}"
1461 );
1462 for value in actual.values.keys() {
1463 assert_eq!(
1464 actual.uses(value),
1465 reference.uses(value),
1466 "uses mismatch for {value:?} on trial {trial}"
1467 );
1468 assert_eq!(
1469 actual.consumers(value),
1470 reference.consumers(value),
1471 "consumers mismatch for {value:?} on trial {trial}"
1472 );
1473 }
1474 }
1475 }
1476
1477 #[test]
1478 fn vec_indexed_topology_matches_hashmap_reference_on_random_dags() {
1479 let mut rng = TestRng(0x3c6e_f372_fe94_f82b);
1480 for trial in 0..2_000 {
1481 let mut graph = Graph::new();
1482 let input = graph.create_value(DataType::Float32, static_shape([1]));
1483 graph.add_input(input);
1484 let mut values = vec![input];
1485 let mut nodes = Vec::new();
1486 for _ in 0..(1 + rng.usize(48)) {
1487 let inputs = (0..(1 + rng.usize(4)))
1488 .map(|_| Some(values[rng.usize(values.len())]))
1489 .collect();
1490 let output = graph.create_value(DataType::Float32, static_shape([1]));
1491 nodes.push(graph.insert_node(Node::new(NodeId(0), "Random", inputs, vec![output])));
1492 values.push(output);
1493 }
1494 for &node in nodes.iter().filter(|_| rng.usize(5) == 0) {
1495 graph.remove_node(node);
1496 }
1497 assert_eq!(
1498 graph.topological_order(),
1499 reference_topological_order(&graph),
1500 "topology mismatch on trial {trial}"
1501 );
1502 }
1503 }
1504
1505 #[test]
1506 fn replace_node_groups_matches_sequential_mutation() {
1507 let mut sequential = Graph::new();
1508 let input = sequential.create_value(DataType::Float32, static_shape([1]));
1509 sequential.add_input(input);
1510 let interior = sequential.create_value(DataType::Float32, static_shape([1]));
1511 let first = sequential.insert_node(Node::new(
1512 NodeId(0),
1513 "Relu",
1514 vec![Some(input)],
1515 vec![interior],
1516 ));
1517 let output = sequential.create_value(DataType::Float32, static_shape([1]));
1518 let second = sequential.insert_node(Node::new(
1519 NodeId(0),
1520 "Relu",
1521 vec![Some(interior)],
1522 vec![output],
1523 ));
1524 sequential.add_output(output);
1525 let sibling_output = sequential.create_value(DataType::Float32, static_shape([1]));
1526 let sibling = sequential.insert_node(Node::new(
1527 NodeId(0),
1528 "Neg",
1529 vec![Some(input)],
1530 vec![sibling_output],
1531 ));
1532 sequential.add_output(sibling_output);
1533
1534 let mut batched = sequential.clone();
1535 let graph_outputs: HashSet<_> = batched.outputs.iter().copied().collect();
1536 let replacement0 = Node::new(NodeId(0), "EPContext", vec![Some(input)], vec![output]);
1537 let replacement1 = Node::new(
1538 NodeId(0),
1539 "EPContext",
1540 vec![Some(input)],
1541 vec![sibling_output],
1542 );
1543
1544 sequential.remove_node(first);
1545 sequential.remove_node(second);
1546 let replacement0_id = sequential.insert_node(replacement0.clone());
1547 sequential.remove_node(sibling);
1548 let replacement1_id = sequential.insert_node(replacement1.clone());
1549
1550 let inserted = batched.replace_node_groups(
1551 vec![
1552 (vec![first, second], replacement0),
1553 (vec![sibling], replacement1),
1554 ],
1555 &graph_outputs,
1556 );
1557 assert_eq!(inserted, vec![replacement0_id, replacement1_id]);
1558
1559 let sequential_nodes: Vec<_> = sequential
1560 .nodes
1561 .iter()
1562 .map(|(id, node)| {
1563 (
1564 id,
1565 node.op_type.clone(),
1566 node.inputs.clone(),
1567 node.outputs.clone(),
1568 )
1569 })
1570 .collect();
1571 let batched_nodes: Vec<_> = batched
1572 .nodes
1573 .iter()
1574 .map(|(id, node)| {
1575 (
1576 id,
1577 node.op_type.clone(),
1578 node.inputs.clone(),
1579 node.outputs.clone(),
1580 )
1581 })
1582 .collect();
1583 assert_eq!(batched_nodes, sequential_nodes);
1584
1585 let sequential_values: Vec<_> = sequential
1586 .values
1587 .iter()
1588 .map(|(id, value)| (id, value.producer, value.consumers.clone()))
1589 .collect();
1590 let batched_values: Vec<_> = batched
1591 .values
1592 .iter()
1593 .map(|(id, value)| (id, value.producer, value.consumers.clone()))
1594 .collect();
1595 assert_eq!(batched_values, sequential_values);
1596
1597 let next_sequential =
1598 sequential.insert_node(Node::new(NodeId(0), "Identity", Vec::new(), Vec::new()));
1599 let next_batched =
1600 batched.insert_node(Node::new(NodeId(0), "Identity", Vec::new(), Vec::new()));
1601 assert_eq!(next_batched, next_sequential);
1602 }
1603
1604 #[test]
1605 fn replace_node_groups_matches_sequential_orphan_collection() {
1606 let mut sequential = Graph::new();
1607 let input = sequential.create_value(DataType::Float32, static_shape([1]));
1608 sequential.add_input(input);
1609 let interior = sequential.create_value(DataType::Float32, static_shape([1]));
1610 let producer = sequential.insert_node(Node::new(
1611 NodeId(0),
1612 "Relu",
1613 vec![Some(input)],
1614 vec![interior],
1615 ));
1616 let output = sequential.create_value(DataType::Float32, static_shape([1]));
1617 let consumer = sequential.insert_node(Node::new(
1618 NodeId(0),
1619 "Relu",
1620 vec![Some(interior)],
1621 vec![output],
1622 ));
1623 sequential.add_output(output);
1624
1625 let mut batched = sequential.clone();
1626 let graph_outputs: HashSet<_> = batched.outputs.iter().copied().collect();
1627 let replacement = Node::new(NodeId(0), "EPContext", vec![Some(input)], vec![output]);
1628
1629 sequential.remove_node(consumer);
1630 sequential.remove_node(producer);
1631 sequential.insert_node(replacement.clone());
1632 batched.replace_node_groups(
1633 vec![(vec![consumer, producer], replacement)],
1634 &graph_outputs,
1635 );
1636
1637 assert!(sequential.try_value(interior).is_none());
1638 assert!(batched.try_value(interior).is_none());
1639 let next_sequential = sequential.create_value(DataType::Float32, static_shape([1]));
1640 let next_batched = batched.create_value(DataType::Float32, static_shape([1]));
1641 assert_eq!(next_batched, next_sequential);
1642 }
1643
1644 #[test]
1645 fn replace_node_preserves_id() {
1646 let mut g = sample_graph();
1647 let d = g.node(NodeId(1)).outputs[0];
1648 let b = g.node(NodeId(1)).inputs[0];
1649 let c = g.node(NodeId(1)).inputs[1];
1650 let sub = Node::new(NodeId(0), "Sub", vec![b, c], vec![d]);
1651 let id = g.replace_node(NodeId(1), sub);
1652 assert_eq!(id, NodeId(1));
1653 assert_eq!(g.node(NodeId(1)).op_type, "Sub");
1654 assert!(g.validate().is_ok());
1655 }
1656
1657 #[test]
1658 fn cycle_is_detected() {
1659 let mut g = Graph::new();
1660 let v0 = g.create_value(DataType::Float32, static_shape([1]));
1661 let v1 = g.create_value(DataType::Float32, static_shape([1]));
1662 g.insert_node(Node::new(NodeId(0), "A", vec![Some(v1)], vec![v0]));
1664 g.insert_node(Node::new(NodeId(0), "B", vec![Some(v0)], vec![v1]));
1665 assert_eq!(g.topological_order(), Err(GraphError::CycleDetected));
1666 assert!(g.validate().is_err());
1667 }
1668
1669 #[test]
1670 fn intern_symbol_dedups_by_name() {
1671 let mut g = Graph::new();
1672 let s1 = g.intern_symbol("batch");
1673 let s2 = g.intern_symbol("batch");
1674 let s3 = g.intern_symbol("seq");
1675 assert_eq!(s1, s2);
1676 assert_ne!(s1, s3);
1677 }
1678}
1679
1680#[cfg(test)]
1681mod effective_opset_tests {
1682 use super::*;
1683 use crate::node::Node;
1684
1685 fn node_with(version: Option<i64>) -> Node {
1686 let mut node = Node::new(NodeId(0), "Swish", vec![], vec![]);
1687 node.version = version;
1688 node
1689 }
1690
1691 #[test]
1693 fn a_node_version_overrides_the_graph_import() {
1694 let mut graph = Graph::default();
1695 graph.opset_imports.insert(String::new(), 13);
1696 assert_eq!(graph.effective_opset(&node_with(Some(24))), Some(24));
1697 }
1698
1699 #[test]
1701 fn no_node_version_falls_back_to_the_graph() {
1702 let mut graph = Graph::default();
1703 graph.opset_imports.insert(String::new(), 13);
1704 assert_eq!(graph.effective_opset(&node_with(None)), Some(13));
1705 }
1706
1707 #[test]
1715 fn implausible_versions_defer_to_the_graph() {
1716 let mut graph = Graph::default();
1717 graph.opset_imports.insert(String::new(), 13);
1718 for version in [-1, 0, i64::MAX, i64::from(i32::MAX) + 1] {
1719 assert_eq!(
1720 graph.effective_opset(&node_with(Some(version))),
1721 Some(13),
1722 "version {version} is not usable and must not override the graph"
1723 );
1724 }
1725 }
1726
1727 #[test]
1729 fn an_unimported_domain_resolves_to_nothing() {
1730 let graph = Graph::default();
1731 assert_eq!(graph.effective_opset(&node_with(None)), None);
1732 }
1733}