1use super::{Direction, Graph, GraphNode};
20use crate::{Arity, NodeType, graphs::node::InnovationId, node::Node};
21use radiate_core::{RdRand, Valid, random_provider};
22use radiate_utils::SortedBuffer;
23use std::{fmt::Debug, ops::Deref};
24
25const SOURCE_NODE_TYPES: &[NodeType] = &[NodeType::Input, NodeType::Vertex, NodeType::Edge];
26const TARGET_NODE_TYPES: &[NodeType] = &[NodeType::Output, NodeType::Vertex, NodeType::Edge];
27const MAX_REPAIR_ATTEMPTS: usize = 10;
28const MAX_SOURCE_ATTEMPTS: usize = 10;
29
30#[derive(Debug, Clone)]
35pub enum MutationStep {
36 AddNode(usize),
37 AddEdge(usize, usize),
38 RemoveEdge(usize, usize),
39 DirectionChange {
40 index: usize,
41 previous_direction: Direction,
42 },
43 InnovationChange {
44 node_idx: usize,
45 previous_innovation: Option<InnovationId>,
46 },
47}
48
49#[derive(Clone)]
55pub enum ReplayStep<T> {
56 AddNode(usize, Option<GraphNode<T>>),
57 AddEdge(usize, usize),
58 RemoveEdge(usize, usize),
59 DirectionChange(usize, Direction),
60 InnovationChange(usize, Option<InnovationId>),
61}
62
63pub enum TransactionResult<T> {
70 Valid(Vec<MutationStep>),
71 Invalid(Vec<MutationStep>, Vec<ReplayStep<T>>),
72}
73
74impl<T> TransactionResult<T> {
75 pub fn is_valid(&self) -> bool {
76 matches!(self, TransactionResult::Valid(_))
77 }
78
79 pub fn is_invalid(&self) -> bool {
80 matches!(self, TransactionResult::Invalid(_, _))
81 }
82
83 pub fn replay(&self, graph: &mut Graph<T>)
84 where
85 T: Clone,
86 {
87 if let TransactionResult::Invalid(_, replay_steps) = self {
88 let mut transaction = GraphTransaction::new(graph);
89 transaction.replay(replay_steps.clone());
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum InsertStep {
99 Detach(usize, usize),
100 Connect(usize, usize),
101 NewStructure(usize, usize, usize, NodeType),
102 Invalid,
103}
104
105pub struct GraphTransaction<'a, T> {
113 graph: &'a mut Graph<T>,
114 steps: Vec<MutationStep>,
115 effects: SortedBuffer<usize>,
116}
117
118impl<'a, T> GraphTransaction<'a, T> {
119 pub fn new(graph: &'a mut Graph<T>) -> Self {
120 GraphTransaction {
121 graph,
122 steps: Vec::with_capacity(5),
123 effects: SortedBuffer::new(),
124 }
125 }
126
127 pub fn commit(self) -> TransactionResult<T> {
133 self.commit_internal::<fn(&Graph<T>) -> bool>(None)
134 }
135
136 pub fn commit_with(self, validator: impl Fn(&Graph<T>) -> bool) -> TransactionResult<T> {
140 self.commit_internal(Some(validator))
141 }
142
143 pub fn try_commit(mut self) -> TransactionResult<T> {
148 let mut repaired = false;
149 let mut attempts = 0;
150
151 self.set_cycles();
152 while !repaired && attempts < MAX_REPAIR_ATTEMPTS {
153 repaired = self.repair_invalid_nodes();
154 if repaired {
155 self.set_cycles();
156 }
157
158 attempts += 1;
159 }
160
161 self.commit()
162 }
163
164 pub fn push(&mut self, node: impl Into<GraphNode<T>>) -> usize {
166 let index = self.graph.len();
167 self.steps.push(MutationStep::AddNode(index));
168 self.graph.push(node);
169 SortedBuffer::insert_sorted_unique(&mut self.effects, index);
170 index
171 }
172
173 pub fn attach(&mut self, from: usize, to: usize) {
175 self.steps.push(MutationStep::AddEdge(from, to));
176 self.graph.attach(from, to);
177 SortedBuffer::insert_sorted_unique(&mut self.effects, from);
178 SortedBuffer::insert_sorted_unique(&mut self.effects, to);
179 }
180
181 pub fn detach(&mut self, from: usize, to: usize) {
183 self.steps.push(MutationStep::RemoveEdge(from, to));
184 self.graph.detach(from, to);
185 SortedBuffer::insert_sorted_unique(&mut self.effects, from);
186 SortedBuffer::insert_sorted_unique(&mut self.effects, to);
187 }
188
189 pub fn change_direction(&mut self, index: usize, direction: Direction) {
193 if let Some(node) = self.graph.get_mut(index) {
194 if node.direction() == direction {
195 return;
196 }
197
198 self.steps.push(MutationStep::DirectionChange {
199 index,
200 previous_direction: node.direction(),
201 });
202 node.set_direction(direction);
203 }
204 }
205
206 pub fn rollback(self) -> Vec<ReplayStep<T>> {
211 let mut replay_steps = Vec::new();
212 for step in self.steps.into_iter().rev() {
213 match step {
214 MutationStep::AddNode(_) => {
215 let added_node = self.graph.pop();
216 replay_steps.push(ReplayStep::AddNode(self.graph.len(), added_node));
217 }
218 MutationStep::AddEdge(from, to) => {
219 self.graph.detach(from, to);
220 replay_steps.push(ReplayStep::AddEdge(from, to));
221 }
222 MutationStep::RemoveEdge(from, to) => {
223 self.graph.attach(from, to);
224 replay_steps.push(ReplayStep::RemoveEdge(from, to));
225 }
226 MutationStep::DirectionChange {
227 index,
228 previous_direction,
229 ..
230 } => {
231 if let Some(node) = self.graph.get_mut(index) {
232 let prev_dir = node.direction();
233 node.set_direction(previous_direction);
234 replay_steps.push(ReplayStep::DirectionChange(index, prev_dir));
235 }
236 }
237 MutationStep::InnovationChange {
238 node_idx,
239 previous_innovation,
240 } => {
241 if let Some(node) = self.graph.get_mut(node_idx) {
242 let current_innovation = node.innovation();
243 node.set_innovation(previous_innovation);
244 replay_steps
245 .push(ReplayStep::InnovationChange(node_idx, current_innovation));
246 }
247 }
248 }
249 }
250
251 replay_steps.reverse();
252 replay_steps
253 }
254
255 pub fn replay(&mut self, steps: Vec<ReplayStep<T>>) {
259 for step in steps {
260 match step {
261 ReplayStep::AddNode(_, node) => {
262 if let Some(node) = node {
263 self.push(node);
264 }
265 }
266 ReplayStep::AddEdge(from, to) => {
267 self.attach(from, to);
268 }
269 ReplayStep::RemoveEdge(from, to) => {
270 self.detach(from, to);
271 }
272 ReplayStep::DirectionChange(index, direction) => {
273 self.change_direction(index, direction);
274 }
275 ReplayStep::InnovationChange(node_idx, innovation) => {
276 self.set_innovation(node_idx, innovation);
277 }
278 }
279 }
280 }
281
282 pub fn set_cycles(&mut self) {
286 let effects = self.effects.clone();
287
288 for &idx in effects.iter() {
289 let node_cycles = self.graph.get_cycles(idx);
290
291 if node_cycles.is_empty() {
292 self.change_direction(idx, Direction::Forward);
293 } else {
294 for cycle_idx in node_cycles {
295 self.change_direction(cycle_idx, Direction::Backward);
296 }
297 }
298 }
299 }
300
301 pub fn set_innovation(&mut self, node_idx: usize, innovation: Option<InnovationId>) {
302 if let Some(node) = self.graph.get_mut(node_idx) {
303 let previous_innovation = node.innovation();
304 node.set_innovation(innovation);
305 self.steps.push(MutationStep::InnovationChange {
306 node_idx,
307 previous_innovation,
308 });
309 }
310 }
311
312 #[inline]
320 pub fn get_insertion_steps(
321 &self,
322 source_idx: usize,
323 target_idx: usize,
324 new_node_idx: usize,
325 rand: &mut RdRand,
326 ) -> Vec<InsertStep> {
327 let target_node = self.graph.get(target_idx).unwrap();
328 let source_node = self.graph.get(source_idx).unwrap();
329 let new_node = self.graph.get(new_node_idx).unwrap();
330
331 let mut steps = Vec::with_capacity(4);
332
333 let source_is_edge = source_node.node_type() == NodeType::Edge;
334 let target_is_edge = target_node.node_type() == NodeType::Edge;
335 let new_node_arity = new_node.arity();
336
337 if new_node_arity == Arity::Zero && !target_node.is_locked() {
338 steps.push(InsertStep::Connect(new_node_idx, target_idx));
339 return steps;
340 }
341
342 if source_is_edge {
343 let source_outgoing = *rand.choose(source_node.outgoing());
344
345 if source_outgoing == new_node_idx {
346 steps.push(InsertStep::Connect(source_idx, new_node_idx));
347 } else {
348 steps.push(InsertStep::Connect(source_idx, new_node_idx));
349 steps.push(InsertStep::Connect(new_node_idx, source_outgoing));
350 steps.push(InsertStep::Detach(source_idx, source_outgoing));
351 steps.push(InsertStep::NewStructure(
352 source_idx,
353 new_node_idx,
354 source_outgoing,
355 source_node.node_type(),
356 ));
357 }
358 } else if target_is_edge || target_node.is_locked() {
359 let target_incoming = *rand.choose(target_node.incoming());
360
361 if target_incoming == new_node_idx {
362 steps.push(InsertStep::Connect(target_incoming, new_node_idx));
363 } else {
364 steps.push(InsertStep::Connect(target_incoming, new_node_idx));
365 steps.push(InsertStep::Connect(new_node_idx, target_idx));
366 steps.push(InsertStep::Detach(target_incoming, target_idx));
367 steps.push(InsertStep::NewStructure(
368 target_incoming,
369 new_node_idx,
370 target_idx,
371 target_node.node_type(),
372 ));
373 }
374 } else {
375 steps.push(InsertStep::Connect(source_idx, new_node_idx));
376 steps.push(InsertStep::Connect(new_node_idx, target_idx));
377 steps.push(InsertStep::NewStructure(
378 source_idx,
379 new_node_idx,
380 target_idx,
381 new_node.node_type(),
382 ));
383 }
384
385 steps
386 }
387
388 #[inline]
397 pub fn random_source_node(&self, rand: &mut RdRand) -> Option<&GraphNode<T>> {
398 self.random_node_of_type(SOURCE_NODE_TYPES, rand)
399 }
400
401 #[inline]
404 pub fn random_target_node(&self, rand: &mut RdRand) -> Option<&GraphNode<T>> {
405 self.random_node_of_type(TARGET_NODE_TYPES, rand)
406 }
407
408 #[inline]
409 pub fn unique_random_source_node(
410 &self,
411 arity: &Arity,
412 rand: &mut RdRand,
413 ) -> Option<Vec<&GraphNode<T>>> {
414 let needed_insertions = match arity {
415 Arity::Exact(n) => *n,
416 _ => 1,
417 };
418
419 if self.graph.len() < needed_insertions {
420 return None;
421 }
422
423 if needed_insertions == 1 {
424 return self.random_source_node(rand).map(|n| vec![n]);
425 }
426
427 let mut sorted = SortedBuffer::new();
428
429 let mut attempts = 0;
430 while sorted.len() < needed_insertions && attempts < MAX_SOURCE_ATTEMPTS {
431 if let Some(node) = self.random_source_node(rand) {
432 SortedBuffer::insert_sorted_unique(&mut sorted, node.index());
433 }
434
435 attempts += 1;
436 }
437
438 Some(
439 sorted
440 .iter()
441 .map(|&idx| &self.graph[idx])
442 .collect::<Vec<&GraphNode<T>>>(),
443 )
444 }
445
446 #[inline]
449 pub fn random_target_node_where<F>(&self, rand: &mut RdRand, filter: F) -> Option<&GraphNode<T>>
450 where
451 F: Fn(&GraphNode<T>) -> bool,
452 {
453 let candidates = self
454 .iter()
455 .filter(|node| TARGET_NODE_TYPES.contains(&node.node_type()) && filter(node))
456 .collect::<Vec<&GraphNode<T>>>();
457
458 if candidates.is_empty() {
459 return None;
460 }
461
462 Some(*rand.choose(&candidates))
463 }
464
465 #[inline]
468 fn random_source_node_where<F>(&self, rand: &mut RdRand, filter: F) -> Option<&GraphNode<T>>
469 where
470 F: Fn(&GraphNode<T>) -> bool,
471 {
472 let candidates = self
473 .iter()
474 .filter(|node| SOURCE_NODE_TYPES.contains(&node.node_type()) && filter(node))
475 .collect::<Vec<&GraphNode<T>>>();
476
477 if candidates.is_empty() {
478 return None;
479 }
480
481 Some(*rand.choose(&candidates))
482 }
483
484 fn repair_invalid_nodes(&mut self) -> bool {
485 if self.is_valid() {
486 return false;
487 }
488
489 let mut repaired = false;
490
491 let invalid_nodes = self
492 .iter()
493 .filter(|node| !node.is_valid())
494 .map(|n| n.index())
495 .collect::<Vec<usize>>();
496
497 for idx in invalid_nodes.iter() {
498 let arity = self.graph[*idx].arity();
499 match arity {
500 Arity::Zero if self.repair_zero_arity_node(*idx) => {
501 repaired = true;
502 }
503 Arity::Exact(_) if self.repair_exact_arity_node(*idx) => {
504 repaired = true;
505 }
506 _ => {}
507 }
508 }
509
510 repaired
511 }
512
513 fn repair_zero_arity_node(&mut self, node_idx: usize) -> bool {
514 let node = self.graph.get(node_idx).unwrap();
515 if node.arity() != Arity::Zero {
516 return false;
517 }
518
519 if node.outgoing().is_empty() {
520 let random_target = random_provider::with_rng(|rand| {
521 self.random_target_node_where(rand, |n| !n.is_locked() && n.index() != node_idx)
522 .map(|n| n.index())
523 });
524
525 if let Some(target) = random_target {
526 self.attach(node.index(), target);
527
528 if !self.graph[node_idx].outgoing().is_empty() {
529 return true;
530 }
531 }
532 }
533
534 false
535 }
536
537 fn repair_exact_arity_node(&mut self, node_idx: usize) -> bool {
538 let arity = self.graph[node_idx].arity();
539 if let Arity::Exact(n) = arity {
540 let current_incoming = self.graph[node_idx].incoming().len();
541 if current_incoming < n {
542 let needed = n - current_incoming;
543
544 let available_sources = random_provider::with_rng(|rand| {
545 (0..needed)
546 .filter_map(|_| {
547 self.random_source_node_where(rand, |n| {
548 !n.is_locked() && n.index() != node_idx
549 })
550 .map(|n| n.index())
551 })
552 .collect::<Vec<usize>>()
553 });
554
555 for src in available_sources {
556 self.attach(src, node_idx);
557 }
558
559 if self.graph[node_idx].incoming().len() == n {
560 return true;
561 }
562 } else if current_incoming > n {
563 let to_detach = current_incoming - n;
564
565 let valid_incoming = self.graph[node_idx]
566 .incoming()
567 .iter()
568 .cloned()
569 .filter(|incoming| self.graph[*incoming].outgoing().len() > 1)
570 .collect::<Vec<usize>>();
571
572 let rand_indices = random_provider::shuffled_indices(0..valid_incoming.len());
573 let rand_indices = &rand_indices[0..to_detach];
574
575 for &i in rand_indices.iter() {
576 let source_idx = valid_incoming[i];
577 self.detach(source_idx, node_idx);
578 }
579
580 if self.graph[node_idx].incoming().len() == n {
581 return true;
582 }
583 }
584 }
585
586 false
587 }
588
589 #[inline]
593 fn random_node_of_type(
594 &self,
595 node_types: &[NodeType],
596 rand: &mut RdRand,
597 ) -> Option<&GraphNode<T>> {
598 if node_types.is_empty() {
599 return None;
600 }
601
602 let gene_node_type = rand.choose(node_types);
603
604 let genes = match gene_node_type {
605 NodeType::Input => self
606 .iter()
607 .filter(|node| node.node_type() == NodeType::Input)
608 .collect::<Vec<&GraphNode<T>>>(),
609 NodeType::Output => self
610 .iter()
611 .filter(|node| node.node_type() == NodeType::Output)
612 .collect::<Vec<&GraphNode<T>>>(),
613 NodeType::Vertex => self
614 .iter()
615 .filter(|node| node.node_type() == NodeType::Vertex)
616 .collect::<Vec<&GraphNode<T>>>(),
617 NodeType::Edge => self
618 .iter()
619 .filter(|node| node.node_type() == NodeType::Edge)
620 .collect::<Vec<&GraphNode<T>>>(),
621 _ => vec![],
622 };
623
624 if genes.is_empty() {
625 return self.random_node_of_type(
626 node_types
627 .iter()
628 .filter(|nt| *nt != gene_node_type)
629 .cloned()
630 .collect::<Vec<NodeType>>()
631 .as_slice(),
632 rand,
633 );
634 }
635
636 Some(*rand.choose(&genes))
637 }
638
639 fn commit_internal<F: Fn(&Graph<T>) -> bool>(
640 mut self,
641 validator: Option<F>,
642 ) -> TransactionResult<T> {
643 self.set_cycles();
644 let result_steps = self.steps.iter().map(|step| (*step).clone()).collect();
645
646 if let Some(validator) = validator {
647 return if validator(self.graph) && self.is_valid() {
648 TransactionResult::Valid(result_steps)
649 } else {
650 let replay_steps = self.rollback();
651 TransactionResult::Invalid(result_steps, replay_steps)
652 };
653 }
654
655 if self.is_valid() {
656 TransactionResult::Valid(result_steps)
657 } else {
658 let replay_steps = self.rollback();
659 TransactionResult::Invalid(result_steps, replay_steps)
660 }
661 }
662}
663
664impl<T> Deref for GraphTransaction<'_, T> {
665 type Target = Graph<T>;
666
667 fn deref(&self) -> &Self::Target {
668 self.graph
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::{GraphTransaction, InsertStep, MutationStep, TransactionResult};
675 use crate::collections::graphs::{Direction, Graph, GraphNode, InnovationId};
676 use crate::{Arity, Node, NodeType};
677 use radiate_core::{Valid, random_provider};
678
679 fn assert_has_direction_change(steps: &[MutationStep], idxs: &[usize]) {
680 let mut seen = vec![];
681 for s in steps {
682 if let MutationStep::DirectionChange { index, .. } = s {
683 seen.push(*index);
684 }
685 }
686 for idx in idxs {
687 assert!(
688 seen.contains(idx),
689 "Expected DirectionChange for node {} not found in steps: {:?}",
690 idx,
691 steps
692 );
693 }
694 }
695
696 #[test]
697 fn commit_valid_add_and_attach() {
698 let mut g = Graph::<i32>::default();
699 let mut tx = GraphTransaction::new(&mut g);
700
701 let i = tx.push((0, NodeType::Input, 0));
702 let o = tx.push((1, NodeType::Output, 1));
703 tx.attach(i, o);
704
705 match tx.commit() {
706 TransactionResult::Valid(steps) => {
707 assert_eq!(steps.len(), 3);
708 assert!(matches!(steps[0], MutationStep::AddNode(0)));
709 assert!(matches!(steps[1], MutationStep::AddNode(1)));
710 assert!(matches!(steps[2], MutationStep::AddEdge(0, 1)));
711 assert!(g.is_valid());
712 assert_eq!(g[0].outgoing().len(), 1);
713 assert_eq!(g[1].incoming().len(), 1);
714 assert_eq!(g[0].direction(), Direction::Forward);
715 assert_eq!(g[1].direction(), Direction::Forward);
716 }
717 _ => panic!("expected Valid"),
718 }
719 }
720
721 #[test]
722 fn commit_invalid_rolls_back_and_replay_restores() {
723 let mut g = Graph::<i32>::default();
724
725 let mut tx = GraphTransaction::new(&mut g);
727 let input = tx.push((0, NodeType::Input, 0));
728 let vertex = tx.push((1, NodeType::Vertex, 1, Arity::Exact(2)));
729 let output = tx.push((2, NodeType::Output, 2));
730
731 tx.attach(input, vertex);
732 tx.attach(vertex, output);
733
734 let (steps, replay) = match tx.commit() {
735 TransactionResult::Invalid(steps, replay) => (steps, replay),
736 _ => panic!("expected Invalid"),
737 };
738
739 assert_eq!(g.len(), 0, "graph should be rolled back to empty");
741 assert!(g.is_valid());
742
743 let mut tx2 = GraphTransaction::new(&mut g);
745 tx2.replay(replay);
746
747 assert_eq!(g.len(), 3);
748 assert_eq!(g[0].node_type(), NodeType::Input);
749 assert_eq!(g[1].node_type(), NodeType::Vertex);
750 assert_eq!(g[2].node_type(), NodeType::Output);
751 assert!(g[0].outgoing().contains(&1));
752 assert!(g[1].incoming().contains(&0));
753 assert!(g[1].outgoing().contains(&2));
754 assert!(g[2].incoming().contains(&1));
755
756 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(0))));
758 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(1))));
759 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(2))));
760 assert!(
761 steps
762 .iter()
763 .any(|s| matches!(s, MutationStep::AddEdge(0, 1)))
764 );
765 assert!(
766 steps
767 .iter()
768 .any(|s| matches!(s, MutationStep::AddEdge(1, 2)))
769 );
770 }
771
772 #[test]
773 fn commit_sets_cycles_and_marks_backward() {
774 let mut g = Graph::<i32>::default();
775 let mut tx = GraphTransaction::new(&mut g);
776
777 let a = tx.push((0, NodeType::Vertex, 10));
778 let b = tx.push((1, NodeType::Vertex, 20));
779 tx.attach(a, b);
780 tx.attach(b, a); match tx.commit() {
783 TransactionResult::Valid(steps) => {
784 assert!(g.is_valid());
785 assert_eq!(g[0].direction(), Direction::Backward);
787 assert_eq!(g[1].direction(), Direction::Backward);
788 assert_has_direction_change(&steps, &[0, 1]);
790 }
791 _ => panic!("expected Valid"),
792 }
793 }
794
795 #[test]
796 fn insertion_steps_new_zero_arity_connects_to_target_when_unlocked() {
797 let mut g = Graph::<i32>::default();
798 let mut tx = GraphTransaction::new(&mut g);
799
800 let src = tx.push((0, NodeType::Input, 0));
801 let tgt = tx.push((1, NodeType::Vertex, 1)); let newn = tx.push((2, NodeType::Input, 2)); let steps = random_provider::with_rng(|r| tx.get_insertion_steps(src, tgt, newn, r));
805 assert_eq!(steps, vec![InsertStep::Connect(newn, tgt)]);
806 }
807
808 #[test]
809 fn insertion_steps_source_is_edge_with_single_outgoing_equal_new() {
810 let mut g = Graph::<i32>::default();
811 let mut tx = GraphTransaction::new(&mut g);
812
813 let source = tx
815 .push(GraphNode::with_arity(0, NodeType::Edge, 0, Arity::Exact(1)).with_outgoing([2]));
816 let target = tx.push((1, NodeType::Vertex, 1));
817 let newn = tx.push((2, NodeType::Vertex, 2));
818
819 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
820 assert_eq!(steps, vec![InsertStep::Connect(source, newn)]);
821 }
822
823 #[test]
824 fn insertion_steps_source_is_edge_redirects_through_new() {
825 let mut g = Graph::<i32>::default();
826 let mut tx = GraphTransaction::new(&mut g);
827
828 let source = tx
830 .push(GraphNode::with_arity(0, NodeType::Edge, 0, Arity::Exact(1)).with_outgoing([1]));
831 let target = tx.push((1, NodeType::Vertex, 1));
832 let newn = tx.push((2, NodeType::Vertex, 2));
833
834 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
835 assert_eq!(
836 steps[..3],
837 vec![
838 InsertStep::Connect(source, newn),
839 InsertStep::Connect(newn, target),
840 InsertStep::Detach(source, target),
841 ]
842 );
843 }
844
845 #[test]
846 fn insertion_steps_target_locked_prefers_detach_rewire() {
847 let mut g = Graph::<i32>::default();
848 let mut tx = GraphTransaction::new(&mut g);
849
850 let source = tx.push((0, NodeType::Vertex, 0));
853 let target = tx.push(
854 GraphNode::with_arity(1, NodeType::Vertex, 1, Arity::Exact(1)).with_incoming([0]),
855 );
856 let newn = tx.push((2, NodeType::Vertex, 2));
857
858 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
859 assert_eq!(
860 steps[..3],
861 vec![
862 InsertStep::Connect(source, newn),
863 InsertStep::Connect(newn, target),
864 InsertStep::Detach(source, target),
865 ]
866 );
867 }
868
869 #[test]
870 fn random_node_helpers_can_return_edges_when_only_edges_exist() {
871 random_provider::seed(1337);
872 random_provider::with_rng(|rand| {
873 let mut g = Graph::<i32>::default();
874 let mut tx = GraphTransaction::new(&mut g);
875
876 tx.push((0, NodeType::Edge, 0, Arity::Exact(1)));
878 tx.push((1, NodeType::Edge, 1, Arity::Exact(1)));
879
880 let src = tx.random_source_node(rand).unwrap();
881 let tgt = tx.random_target_node(rand).unwrap();
882
883 assert_eq!(src.node_type(), NodeType::Edge);
884 assert_eq!(tgt.node_type(), NodeType::Edge);
885 });
886 }
887
888 #[test]
889 fn rollback_restores_previous_innovation_and_replay_reapplies() {
890 let mut g = Graph::<i32>::default();
891 let initial = InnovationId::new();
892 let updated = InnovationId::new();
893
894 {
895 let mut tx = GraphTransaction::new(&mut g);
896 let input = tx.push((0, NodeType::Input, 0));
897 let output = tx.push((1, NodeType::Output, 1));
898 tx.attach(input, output);
899 tx.set_innovation(input, Some(initial));
900 assert!(matches!(tx.commit(), TransactionResult::Valid(_)));
901 }
902 assert_eq!(g[0].innovation(), Some(initial));
903
904 let replay = {
905 let mut tx = GraphTransaction::new(&mut g);
906 tx.set_innovation(0, Some(updated));
907 match tx.commit_with(|_| false) {
908 TransactionResult::Invalid(_, replay) => replay,
909 _ => panic!("expected forced rejection"),
910 }
911 };
912
913 assert_eq!(
914 g[0].innovation(),
915 Some(initial),
916 "rollback should restore previous innovation, not clear it"
917 );
918
919 {
920 let mut tx = GraphTransaction::new(&mut g);
921 tx.replay(replay);
922 assert!(matches!(tx.commit(), TransactionResult::Valid(_)));
923 }
924 assert_eq!(
925 g[0].innovation(),
926 Some(updated),
927 "replay should reapply the innovation change"
928 );
929 }
930}