1use super::{Direction, Graph, GraphNode};
21use crate::{Arity, NodeType, node::Node};
22use radiate_core::{RdRand, Valid, random_provider};
23use radiate_utils::SortedBuffer;
24use std::{fmt::Debug, ops::Deref};
25
26const SOURCE_NODE_TYPES: &[NodeType] = &[NodeType::Input, NodeType::Vertex, NodeType::Edge];
27const TARGET_NODE_TYPES: &[NodeType] = &[NodeType::Output, NodeType::Vertex, NodeType::Edge];
28const MAX_REPAIR_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}
44
45#[derive(Clone)]
51pub enum ReplayStep<T> {
52 AddNode(usize, Option<GraphNode<T>>),
53 AddEdge(usize, usize),
54 RemoveEdge(usize, usize),
55 DirectionChange(usize, Direction),
56}
57
58pub enum TransactionResult<T> {
65 Valid(Vec<MutationStep>),
66 Invalid(Vec<MutationStep>, Vec<ReplayStep<T>>),
67}
68
69impl<T> TransactionResult<T> {
70 pub fn is_valid(&self) -> bool {
71 matches!(self, TransactionResult::Valid(_))
72 }
73
74 pub fn is_invalid(&self) -> bool {
75 matches!(self, TransactionResult::Invalid(_, _))
76 }
77
78 pub fn replay(&self, graph: &mut Graph<T>)
79 where
80 T: Clone,
81 {
82 if let TransactionResult::Invalid(_, replay_steps) = self {
83 let mut transaction = GraphTransaction::new(graph);
84 transaction.replay(replay_steps.clone());
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum InsertStep {
94 Detach(usize, usize),
95 Connect(usize, usize),
96 Invalid,
97}
98
99pub struct GraphTransaction<'a, T> {
107 graph: &'a mut Graph<T>,
108 steps: Vec<MutationStep>,
109 effects: SortedBuffer<usize>,
110}
111
112impl<'a, T> GraphTransaction<'a, T> {
113 pub fn new(graph: &'a mut Graph<T>) -> Self {
114 GraphTransaction {
115 graph,
116 steps: Vec::with_capacity(5),
117 effects: SortedBuffer::new(),
118 }
119 }
120
121 pub fn commit(self) -> TransactionResult<T> {
127 self.commit_internal::<fn(&Graph<T>) -> bool>(None)
128 }
129
130 pub fn commit_with(self, validator: impl Fn(&Graph<T>) -> bool) -> TransactionResult<T> {
134 self.commit_internal(Some(validator))
135 }
136
137 pub fn try_commit(mut self) -> TransactionResult<T> {
142 let mut repaired = false;
143 let mut attempts = 0;
144
145 self.set_cycles();
146 while repaired == false && attempts < MAX_REPAIR_ATTEMPTS {
147 repaired = self.repair_invalid_nodes();
148 if repaired {
149 self.set_cycles();
150 }
151
152 attempts += 1;
153 }
154
155 self.commit()
156 }
157
158 pub fn push(&mut self, node: impl Into<GraphNode<T>>) -> usize {
160 let index = self.graph.len();
161 self.steps.push(MutationStep::AddNode(index));
162 self.graph.push(node);
163 SortedBuffer::insert_sorted_unique(&mut self.effects, index);
164 index
165 }
166
167 pub fn attach(&mut self, from: usize, to: usize) {
169 self.steps.push(MutationStep::AddEdge(from, to));
170 self.graph.attach(from, to);
171 SortedBuffer::insert_sorted_unique(&mut self.effects, from);
172 SortedBuffer::insert_sorted_unique(&mut self.effects, to);
173 }
174
175 pub fn detach(&mut self, from: usize, to: usize) {
177 self.steps.push(MutationStep::RemoveEdge(from, to));
178 self.graph.detach(from, to);
179 SortedBuffer::insert_sorted_unique(&mut self.effects, from);
180 SortedBuffer::insert_sorted_unique(&mut self.effects, to);
181 }
182
183 pub fn change_direction(&mut self, index: usize, direction: Direction) {
187 if let Some(node) = self.graph.get_mut(index) {
188 if node.direction() == direction {
189 return;
190 }
191
192 self.steps.push(MutationStep::DirectionChange {
193 index,
194 previous_direction: node.direction(),
195 });
196 node.set_direction(direction);
197 }
198 }
199
200 pub fn rollback(self) -> Vec<ReplayStep<T>> {
205 let mut replay_steps = Vec::new();
206 for step in self.steps.into_iter().rev() {
207 match step {
208 MutationStep::AddNode(_) => {
209 let added_node = self.graph.pop();
210 replay_steps.push(ReplayStep::AddNode(self.graph.len(), added_node));
211 }
212 MutationStep::AddEdge(from, to) => {
213 self.graph.detach(from, to);
214 replay_steps.push(ReplayStep::AddEdge(from, to));
215 }
216 MutationStep::RemoveEdge(from, to) => {
217 self.graph.attach(from, to);
218 replay_steps.push(ReplayStep::RemoveEdge(from, to));
219 }
220 MutationStep::DirectionChange {
221 index,
222 previous_direction,
223 ..
224 } => {
225 if let Some(node) = self.graph.get_mut(index) {
226 let prev_dir = node.direction();
227 node.set_direction(previous_direction);
228 replay_steps.push(ReplayStep::DirectionChange(index, prev_dir));
229 }
230 }
231 }
232 }
233
234 replay_steps.reverse();
235 replay_steps
236 }
237
238 pub fn replay(&mut self, steps: Vec<ReplayStep<T>>) {
242 for step in steps {
243 match step {
244 ReplayStep::AddNode(_, node) => {
245 if let Some(node) = node {
246 self.push(node);
247 }
248 }
249 ReplayStep::AddEdge(from, to) => {
250 self.attach(from, to);
251 }
252 ReplayStep::RemoveEdge(from, to) => {
253 self.detach(from, to);
254 }
255 ReplayStep::DirectionChange(index, direction) => {
256 self.change_direction(index, direction);
257 }
258 }
259 }
260 }
261
262 pub fn set_cycles(&mut self) {
266 let effects = self.effects.clone();
267
268 for &idx in effects.iter() {
269 let node_cycles = self.graph.get_cycles(idx);
270
271 if node_cycles.is_empty() {
272 self.change_direction(idx, Direction::Forward);
273 } else {
274 for cycle_idx in node_cycles {
275 self.change_direction(cycle_idx, Direction::Backward);
276 }
277 }
278 }
279 }
280
281 #[inline]
289 pub fn get_insertion_steps(
290 &self,
291 source_idx: usize,
292 target_idx: usize,
293 new_node_idx: usize,
294 rand: &mut RdRand,
295 ) -> Vec<InsertStep> {
296 let target_node = self.graph.get(target_idx).unwrap();
297 let source_node = self.graph.get(source_idx).unwrap();
298 let new_node = self.graph.get(new_node_idx).unwrap();
299
300 let mut steps = Vec::with_capacity(4);
301
302 let source_is_edge = source_node.node_type() == NodeType::Edge;
303 let target_is_edge = target_node.node_type() == NodeType::Edge;
304 let new_node_arity = new_node.arity();
305
306 if new_node_arity == Arity::Zero && !target_node.is_locked() {
307 steps.push(InsertStep::Connect(new_node_idx, target_idx));
308 return steps;
309 }
310
311 if source_is_edge {
312 let source_outgoing = *rand.choose(source_node.outgoing());
313
314 if source_outgoing == new_node_idx {
315 steps.push(InsertStep::Connect(source_idx, new_node_idx));
316 } else {
317 steps.push(InsertStep::Connect(source_idx, new_node_idx));
318 steps.push(InsertStep::Connect(new_node_idx, source_outgoing));
319 steps.push(InsertStep::Detach(source_idx, source_outgoing));
320 }
321 } else if target_is_edge || target_node.is_locked() {
322 let target_incoming = *rand.choose(target_node.incoming());
323
324 if target_incoming == new_node_idx {
325 steps.push(InsertStep::Connect(target_incoming, new_node_idx));
326 } else {
327 steps.push(InsertStep::Connect(target_incoming, new_node_idx));
328 steps.push(InsertStep::Connect(new_node_idx, target_idx));
329 steps.push(InsertStep::Detach(target_incoming, target_idx));
330 }
331 } else {
332 steps.push(InsertStep::Connect(source_idx, new_node_idx));
333 steps.push(InsertStep::Connect(new_node_idx, target_idx));
334 }
335
336 steps
337 }
338
339 #[inline]
348 pub fn random_source_node(&self, rand: &mut RdRand) -> Option<&GraphNode<T>> {
349 self.random_node_of_type(SOURCE_NODE_TYPES, rand)
350 }
351
352 #[inline]
355 pub fn random_target_node(&self, rand: &mut RdRand) -> Option<&GraphNode<T>> {
356 self.random_node_of_type(TARGET_NODE_TYPES, rand)
357 }
358
359 #[inline]
362 pub fn random_target_node_where<F>(&self, rand: &mut RdRand, filter: F) -> Option<&GraphNode<T>>
363 where
364 F: Fn(&GraphNode<T>) -> bool,
365 {
366 let candidates = self
367 .iter()
368 .filter(|node| TARGET_NODE_TYPES.contains(&node.node_type()) && filter(node))
369 .collect::<Vec<&GraphNode<T>>>();
370
371 if candidates.is_empty() {
372 return None;
373 }
374
375 Some(*rand.choose(&candidates))
376 }
377
378 #[inline]
381 fn random_source_node_where<F>(&self, rand: &mut RdRand, filter: F) -> Option<&GraphNode<T>>
382 where
383 F: Fn(&GraphNode<T>) -> bool,
384 {
385 let candidates = self
386 .iter()
387 .filter(|node| SOURCE_NODE_TYPES.contains(&node.node_type()) && filter(node))
388 .collect::<Vec<&GraphNode<T>>>();
389
390 if candidates.is_empty() {
391 return None;
392 }
393
394 Some(*rand.choose(&candidates))
395 }
396
397 fn repair_invalid_nodes(&mut self) -> bool {
398 if self.is_valid() {
399 return false;
400 }
401
402 let mut repaired = false;
403
404 let invalid_nodes = self
405 .iter()
406 .filter(|node| !node.is_valid())
407 .map(|n| n.index())
408 .collect::<Vec<usize>>();
409
410 for idx in invalid_nodes.iter() {
411 let arity = self.graph[*idx].arity();
412 match arity {
413 Arity::Zero => {
414 if self.repair_zero_arity_node(*idx) {
415 repaired = true;
416 }
417 }
418 Arity::Exact(_) => {
419 if self.repair_exact_arity_node(*idx) {
420 repaired = true;
421 }
422 }
423 _ => {}
424 }
425 }
426
427 repaired
428 }
429
430 fn repair_zero_arity_node(&mut self, node_idx: usize) -> bool {
431 let node = self.graph.get(node_idx).unwrap();
432 if node.arity() != Arity::Zero {
433 return false;
434 }
435
436 if node.outgoing().is_empty() {
437 let random_target = random_provider::with_rng(|rand| {
438 self.random_target_node_where(rand, |n| !n.is_locked() && n.index() != node_idx)
439 .map(|n| n.index())
440 });
441
442 if let Some(target) = random_target {
443 self.attach(node.index(), target);
444
445 if self.graph[node_idx].outgoing().len() > 0 {
446 return true;
447 }
448 }
449 }
450
451 false
452 }
453
454 fn repair_exact_arity_node(&mut self, node_idx: usize) -> bool {
455 let arity = self.graph[node_idx].arity();
456 if let Arity::Exact(n) = arity {
457 let current_incoming = self.graph[node_idx].incoming().len();
458 if current_incoming < n {
459 let needed = n - current_incoming;
460
461 let available_sources = random_provider::with_rng(|rand| {
462 (0..needed)
463 .filter_map(|_| {
464 self.random_source_node_where(rand, |n| {
465 !n.is_locked() && n.index() != node_idx
466 })
467 .map(|n| n.index())
468 })
469 .collect::<Vec<usize>>()
470 });
471
472 for src in available_sources {
473 self.attach(src, node_idx);
474 }
475
476 if self.graph[node_idx].incoming().len() == n {
477 return true;
478 }
479 } else if current_incoming > n {
480 let to_detach = current_incoming - n;
481
482 let valid_incoming = self.graph[node_idx]
483 .incoming()
484 .iter()
485 .cloned()
486 .filter(|incoming| self.graph[*incoming].outgoing().len() > 1)
487 .collect::<Vec<usize>>();
488
489 let rand_indices = random_provider::shuffled_indices(0..valid_incoming.len());
490 let rand_indices = &rand_indices[0..to_detach];
491
492 for &i in rand_indices.iter() {
493 let source_idx = valid_incoming[i];
494 self.detach(source_idx, node_idx);
495 }
496
497 if self.graph[node_idx].incoming().len() == n {
498 return true;
499 }
500 }
501 }
502
503 false
504 }
505
506 #[inline]
510 fn random_node_of_type(
511 &self,
512 node_types: &[NodeType],
513 rand: &mut RdRand,
514 ) -> Option<&GraphNode<T>> {
515 if node_types.is_empty() {
516 return None;
517 }
518
519 let gene_node_type = rand.choose(&node_types);
520
521 let genes = match gene_node_type {
522 NodeType::Input => self
523 .iter()
524 .filter(|node| node.node_type() == NodeType::Input)
525 .collect::<Vec<&GraphNode<T>>>(),
526 NodeType::Output => self
527 .iter()
528 .filter(|node| node.node_type() == NodeType::Output)
529 .collect::<Vec<&GraphNode<T>>>(),
530 NodeType::Vertex => self
531 .iter()
532 .filter(|node| node.node_type() == NodeType::Vertex)
533 .collect::<Vec<&GraphNode<T>>>(),
534 NodeType::Edge => self
535 .iter()
536 .filter(|node| node.node_type() == NodeType::Edge)
537 .collect::<Vec<&GraphNode<T>>>(),
538 _ => vec![],
539 };
540
541 if genes.is_empty() {
542 return self.random_node_of_type(
543 node_types
544 .iter()
545 .filter(|nt| *nt != gene_node_type)
546 .cloned()
547 .collect::<Vec<NodeType>>()
548 .as_slice(),
549 rand,
550 );
551 }
552
553 Some(*rand.choose(&genes))
554 }
555
556 fn commit_internal<F: Fn(&Graph<T>) -> bool>(
557 mut self,
558 validator: Option<F>,
559 ) -> TransactionResult<T> {
560 self.set_cycles();
561 let result_steps = self.steps.iter().map(|step| (*step).clone()).collect();
562
563 if let Some(validator) = validator {
564 return if validator(self.graph) && self.is_valid() {
565 TransactionResult::Valid(result_steps)
566 } else {
567 let replay_steps = self.rollback();
568 TransactionResult::Invalid(result_steps, replay_steps)
569 };
570 }
571
572 if self.is_valid() {
573 TransactionResult::Valid(result_steps)
574 } else {
575 let replay_steps = self.rollback();
576 TransactionResult::Invalid(result_steps, replay_steps)
577 }
578 }
579}
580
581impl<T> Deref for GraphTransaction<'_, T> {
582 type Target = Graph<T>;
583
584 fn deref(&self) -> &Self::Target {
585 self.graph
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::{GraphTransaction, InsertStep, MutationStep, TransactionResult};
592 use crate::collections::graphs::{Direction, Graph, GraphNode};
593 use crate::{Arity, Node, NodeType};
594 use radiate_core::{Valid, random_provider};
595
596 fn assert_has_direction_change(steps: &[MutationStep], idxs: &[usize]) {
597 let mut seen = vec![];
598 for s in steps {
599 if let MutationStep::DirectionChange { index, .. } = s {
600 seen.push(*index);
601 }
602 }
603 for idx in idxs {
604 assert!(
605 seen.contains(idx),
606 "Expected DirectionChange for node {} not found in steps: {:?}",
607 idx,
608 steps
609 );
610 }
611 }
612
613 #[test]
614 fn commit_valid_add_and_attach() {
615 let mut g = Graph::<i32>::default();
616 let mut tx = GraphTransaction::new(&mut g);
617
618 let i = tx.push((0, NodeType::Input, 0));
619 let o = tx.push((1, NodeType::Output, 1));
620 tx.attach(i, o);
621
622 match tx.commit() {
623 TransactionResult::Valid(steps) => {
624 assert_eq!(steps.len(), 3);
625 assert!(matches!(steps[0], MutationStep::AddNode(0)));
626 assert!(matches!(steps[1], MutationStep::AddNode(1)));
627 assert!(matches!(steps[2], MutationStep::AddEdge(0, 1)));
628 assert!(g.is_valid());
629 assert_eq!(g[0].outgoing().len(), 1);
630 assert_eq!(g[1].incoming().len(), 1);
631 assert_eq!(g[0].direction(), Direction::Forward);
632 assert_eq!(g[1].direction(), Direction::Forward);
633 }
634 _ => panic!("expected Valid"),
635 }
636 }
637
638 #[test]
639 fn commit_invalid_rolls_back_and_replay_restores() {
640 let mut g = Graph::<i32>::default();
641
642 let mut tx = GraphTransaction::new(&mut g);
644 let input = tx.push((0, NodeType::Input, 0));
645 let vertex = tx.push((1, NodeType::Vertex, 1, Arity::Exact(2)));
646 let output = tx.push((2, NodeType::Output, 2));
647
648 tx.attach(input, vertex);
649 tx.attach(vertex, output);
650
651 let (steps, replay) = match tx.commit() {
652 TransactionResult::Invalid(steps, replay) => (steps, replay),
653 _ => panic!("expected Invalid"),
654 };
655
656 assert_eq!(g.len(), 0, "graph should be rolled back to empty");
658 assert!(g.is_valid());
659
660 let mut tx2 = GraphTransaction::new(&mut g);
662 tx2.replay(replay);
663
664 assert_eq!(g.len(), 3);
665 assert_eq!(g[0].node_type(), NodeType::Input);
666 assert_eq!(g[1].node_type(), NodeType::Vertex);
667 assert_eq!(g[2].node_type(), NodeType::Output);
668 assert!(g[0].outgoing().contains(&1));
669 assert!(g[1].incoming().contains(&0));
670 assert!(g[1].outgoing().contains(&2));
671 assert!(g[2].incoming().contains(&1));
672
673 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(0))));
675 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(1))));
676 assert!(steps.iter().any(|s| matches!(s, MutationStep::AddNode(2))));
677 assert!(
678 steps
679 .iter()
680 .any(|s| matches!(s, MutationStep::AddEdge(0, 1)))
681 );
682 assert!(
683 steps
684 .iter()
685 .any(|s| matches!(s, MutationStep::AddEdge(1, 2)))
686 );
687 }
688
689 #[test]
690 fn commit_sets_cycles_and_marks_backward() {
691 let mut g = Graph::<i32>::default();
692 let mut tx = GraphTransaction::new(&mut g);
693
694 let a = tx.push((0, NodeType::Vertex, 10));
695 let b = tx.push((1, NodeType::Vertex, 20));
696 tx.attach(a, b);
697 tx.attach(b, a); match tx.commit() {
700 TransactionResult::Valid(steps) => {
701 assert!(g.is_valid());
702 assert_eq!(g[0].direction(), Direction::Backward);
704 assert_eq!(g[1].direction(), Direction::Backward);
705 assert_has_direction_change(&steps, &[0, 1]);
707 }
708 _ => panic!("expected Valid"),
709 }
710 }
711
712 #[test]
713 fn insertion_steps_new_zero_arity_connects_to_target_when_unlocked() {
714 let mut g = Graph::<i32>::default();
715 let mut tx = GraphTransaction::new(&mut g);
716
717 let src = tx.push((0, NodeType::Input, 0));
718 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));
722 assert_eq!(steps, vec![InsertStep::Connect(newn, tgt)]);
723 }
724
725 #[test]
726 fn insertion_steps_source_is_edge_with_single_outgoing_equal_new() {
727 let mut g = Graph::<i32>::default();
728 let mut tx = GraphTransaction::new(&mut g);
729
730 let source = tx
732 .push(GraphNode::with_arity(0, NodeType::Edge, 0, Arity::Exact(1)).with_outgoing([2]));
733 let target = tx.push((1, NodeType::Vertex, 1));
734 let newn = tx.push((2, NodeType::Vertex, 2));
735
736 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
737 assert_eq!(steps, vec![InsertStep::Connect(source, newn)]);
738 }
739
740 #[test]
741 fn insertion_steps_source_is_edge_redirects_through_new() {
742 let mut g = Graph::<i32>::default();
743 let mut tx = GraphTransaction::new(&mut g);
744
745 let source = tx
747 .push(GraphNode::with_arity(0, NodeType::Edge, 0, Arity::Exact(1)).with_outgoing([1]));
748 let target = tx.push((1, NodeType::Vertex, 1));
749 let newn = tx.push((2, NodeType::Vertex, 2));
750
751 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
752 assert_eq!(
753 steps,
754 vec![
755 InsertStep::Connect(source, newn),
756 InsertStep::Connect(newn, target),
757 InsertStep::Detach(source, target),
758 ]
759 );
760 }
761
762 #[test]
763 fn insertion_steps_target_locked_prefers_detach_rewire() {
764 let mut g = Graph::<i32>::default();
765 let mut tx = GraphTransaction::new(&mut g);
766
767 let source = tx.push((0, NodeType::Vertex, 0));
770 let target = tx.push(
771 GraphNode::with_arity(1, NodeType::Vertex, 1, Arity::Exact(1)).with_incoming([0]),
772 );
773 let newn = tx.push((2, NodeType::Vertex, 2));
774
775 let steps = random_provider::with_rng(|r| tx.get_insertion_steps(source, target, newn, r));
776 assert_eq!(
777 steps,
778 vec![
779 InsertStep::Connect(source, newn),
780 InsertStep::Connect(newn, target),
781 InsertStep::Detach(source, target),
782 ]
783 );
784 }
785
786 #[test]
787 fn random_node_helpers_can_return_edges_when_only_edges_exist() {
788 random_provider::set_seed(1337);
789 random_provider::with_rng(|rand| {
790 let mut g = Graph::<i32>::default();
791 let mut tx = GraphTransaction::new(&mut g);
792
793 tx.push((0, NodeType::Edge, 0, Arity::Exact(1)));
795 tx.push((1, NodeType::Edge, 1, Arity::Exact(1)));
796
797 let src = tx.random_source_node(rand).unwrap();
798 let tgt = tx.random_target_node(rand).unwrap();
799
800 assert_eq!(src.node_type(), NodeType::Edge);
801 assert_eq!(tgt.node_type(), NodeType::Edge);
802 });
803 }
804}