Skip to main content

radiate_gp/collections/graphs/
graph.rs

1use super::transaction::TransactionResult;
2use crate::collections::graphs::GraphTransaction;
3use crate::collections::{Direction, GraphNode};
4use crate::{GraphIterator, NodeType};
5use radiate_core::Valid;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::fmt::Debug;
10use std::hash::Hash;
11use std::ops::{Index, IndexMut};
12
13/// A graph structure that represents a collection of interconnected nodes.
14///
15/// The [Graph] struct is a fundamental data structure in Radiate's genetic programming system.
16/// Unlike traditional graphs that separate edges and vertices, this graph is a collection of nodes
17/// where each node maintains its own connections. Each node has a unique index that corresponds
18/// to its position in the internal vector, and connections are represented by these indices.
19///
20/// # Type Parameters
21/// * `T` - The type of value stored in each node. This type must implement `Clone`, `PartialEq`,
22///   and other traits required by the genetic programming operations.
23///
24/// # Structure
25/// A [Graph] is simply a 'Vec' of [GraphNode]'s.
26///
27/// It's important to note that this graph differs from a traditional graph in that it is not
28/// a collection of edges and vertices. Instead, it is a collection of nodes that are connected
29/// to one another. Each node has a unique index that is used to reference it in the graph
30/// and must be identical to its position in the 'Vec'.
31/// Each [GraphNode] has a set of ordered incoming and outgoing connections. These connections are
32/// represented by the index of the connected node in the graph. Because of this representation,
33/// an edge is not a separate entity, it's just a node. The 'NodeType' enum is used to distinguish
34/// different types of nodes. This allows for a more flexible representation of the graph
35/// while still maintaining the ability to represent traditional graphs.
36///
37/// By default, a [Graph] is a directed acyclic graph (DAG). However, it is possible to create
38/// cycles in the graph by setting the 'direction' field of a [GraphNode] to [Direction::Backward].
39/// The [Graph] struct provides methods for attaching and detaching nodes from one another.
40/// It also provides methods for iterating over the nodes in the graph in a pseudo-topological order.
41///
42/// Each node:
43/// * Has a unique index matching its position in the vector
44/// * Maintains sets of incoming and outgoing connections
45/// * Can be of different types (Input, Output, Vertex, Edge) as defined by `NodeType`
46/// * Can have a direction (Forward or Backward) for handling cycles
47///
48/// # Examples
49/// ```
50/// use radiate_gp::{Graph, NodeType, Op};
51///
52/// // Create a simple graph with one input and one output node
53/// let mut graph = Graph::<Op<f32>>::default();
54/// let input_idx = graph.insert(NodeType::Input, Op::var(0));
55/// let output_idx = graph.insert(NodeType::Output, Op::linear());
56/// graph.attach(input_idx, output_idx);
57///
58/// // Create a directed graph with 2 inputs and 2 outputs
59/// let values: Vec<(NodeType, Vec<Op<f32>>)> = vec![
60///     (NodeType::Input, vec![Op::var(0), Op::var(1)]),
61///     (NodeType::Output, vec![Op::sigmoid(), Op::tanh()]),
62/// ];
63/// let graph = Graph::directed(2, 2, values);
64/// ```
65///
66/// # Graph Types
67/// The struct provides several factory methods for creating different types of graphs:
68/// * `directed()` - Creates a directed acyclic graph (DAG) with specified input and output nodes
69/// * `recurrent()` - Creates a graph with recurrent connections
70/// * `weighted_directed()` - Creates a directed graph with weighted edges
71/// * `weighted_recurrent()` - Creates a recurrent graph with weighted edges
72///
73/// # Node Operations
74/// The struct provides methods for manipulating nodes:
75/// * `insert()` - Adds a new node and returns its index
76/// * `push()` - Adds a node to the end of the graph
77/// * `pop()` - Removes and returns the last node
78/// * `get()` - Returns a reference to a node by index
79/// * `get_mut()` - Returns a mutable reference to a node by index
80///
81/// # Connection Management
82/// The struct provides methods for managing node connections:
83/// * `attach()` - Creates a connection between two nodes
84/// * `detach()` - Removes a connection between two nodes
85/// * `set_cycles()` - Configures nodes to support cyclic connections
86///
87/// # Graph Traversal
88/// The struct implements the `GraphIterator` trait, providing:
89/// * `iter_topological()` - Traverses the graph in a pseudo-topological order
90/// * `iter()` - Iterates over all nodes
91/// * `iter_mut()` - Iterates over all nodes with mutable references
92///
93/// # Node Type Queries
94/// The struct provides methods to get nodes by type:
95/// * `inputs()` - Returns all input nodes
96/// * `outputs()` - Returns all output nodes
97/// * `vertices()` - Returns all vertex nodes
98/// * `edges()` - Returns all edge nodes
99///
100/// # Graph Properties
101/// The struct provides methods to query graph properties:
102/// * `len()` - Returns the number of nodes
103/// * `is_empty()` - Checks if the graph is empty
104/// * `is_valid()` - Checks if all nodes in the graph are valid
105///
106/// # Implementation Details
107/// The struct implements several traits:
108/// * `Clone` - Allows cloning of the entire graph
109/// * `PartialEq` - Enables equality comparison between graphs
110/// * `Default` - Provides a way to create an empty graph
111/// * `Debug` - Provides debug formatting
112/// * `AsRef<[GraphNode<T>]>` - Allows treating the graph as a slice of nodes
113/// * `AsMut<[GraphNode<T>]>` - Allows treating the graph as a mutable slice of nodes
114/// * `Index<usize>` - Enables indexing with `graph[index]`
115/// * `IndexMut<usize>` - Enables mutable indexing with `graph[index]`
116/// * `IntoIterator` - Allows iterating over nodes
117/// * `FromIterator<GraphNode<T>>` - Allows creating a graph from an iterator of nodes
118///
119/// # Genetic Programming
120/// The [Graph] struct is particularly useful in genetic programming as it can represent:
121/// * Neural networks (using `Op<f32>` or `Op<bool>` for values)
122/// * Decision graphs
123/// * Program flow graphs
124/// * Other interconnected structures
125///
126/// It supports genetic operations through the `GraphChromosome` type, including:
127///
128/// # Serialization
129/// When the "serde" feature is enabled, the struct implements `Serialize` and `Deserialize` traits.
130///
131/// # Performance Considerations
132/// * All nodes (vertices, edges, inputs, outputs) are represented as [GraphNode] instances
133/// * Node lookups are O(1) due to vector indexing
134/// * Connection operations (attach/detach) are O(log n) due to BTreeSet usage for incoming/outgoing connections
135/// * Graph traversal is O(V + E) where V is the number of nodes and E is the total number of connections
136/// * Memory usage is O(V + E) for storing nodes and their connections
137/// * The distinction between node types (Vertex, Edge, Input, Output) is purely semantic and
138///   does not affect the underlying data structure or performance characteristics
139#[derive(Clone, PartialEq)]
140#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
141pub struct Graph<T> {
142    nodes: Vec<GraphNode<T>>,
143}
144
145impl<T> Graph<T> {
146    /// Create a new 'Graph' from a 'Vec' of [GraphNode]s.
147    ///
148    /// # Arguments
149    /// - nodes: A 'Vec' of [GraphNode]s.
150    pub fn new(nodes: Vec<GraphNode<T>>) -> Self {
151        Graph { nodes }
152    }
153
154    pub fn take_nodes(&mut self) -> Vec<GraphNode<T>> {
155        std::mem::take(&mut self.nodes)
156    }
157
158    pub fn push(&mut self, node: impl Into<GraphNode<T>>) {
159        self.nodes.push(node.into());
160    }
161
162    pub fn insert(&mut self, node_type: NodeType, val: T) -> usize {
163        self.push((self.len(), node_type, val));
164        self.len() - 1
165    }
166
167    pub fn pop(&mut self) -> Option<GraphNode<T>> {
168        self.nodes.pop()
169    }
170
171    pub fn len(&self) -> usize {
172        self.nodes.len()
173    }
174
175    pub fn is_empty(&self) -> bool {
176        self.nodes.is_empty()
177    }
178
179    pub fn get_mut(&mut self, index: usize) -> Option<&mut GraphNode<T>> {
180        self.nodes.get_mut(index)
181    }
182
183    pub fn get(&self, index: usize) -> Option<&GraphNode<T>> {
184        self.nodes.get(index)
185    }
186
187    pub fn iter(&self) -> impl Iterator<Item = &GraphNode<T>> {
188        self.nodes.iter()
189    }
190
191    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut GraphNode<T>> {
192        self.nodes.iter_mut()
193    }
194
195    pub fn inputs(&self) -> impl Iterator<Item = &GraphNode<T>> {
196        self.get_nodes_of_type(NodeType::Input)
197    }
198
199    pub fn outputs(&self) -> impl Iterator<Item = &GraphNode<T>> {
200        self.get_nodes_of_type(NodeType::Output)
201    }
202
203    pub fn vertices(&self) -> impl Iterator<Item = &GraphNode<T>> {
204        self.get_nodes_of_type(NodeType::Vertex)
205    }
206
207    pub fn edges(&self) -> impl Iterator<Item = &GraphNode<T>> {
208        self.get_nodes_of_type(NodeType::Edge)
209    }
210
211    /// Attach and detach nodes from one another. This is the primary way to modify the graph.
212    /// Note that this method does not check if the nodes are already connected. This is because
213    /// the connections are represented by 'BTreeSet's which do not allow duplicates.
214    /// Its also important to note that the 'incoming' and 'outgoing' indices are the indices of the
215    /// nodes in the graph, not the indices of the connections in the 'incoming' and 'outgoing' 'BTreeSet's.
216    /// We must also remember that the [GraphNode] cares about the 'Arity' of the 'Operation' it contains,
217    /// so if we add a connection that would violate the 'Arity' of the 'Operation', the connection will result
218    /// in a [GraphNode] that is not 'Valid'.
219    ///
220    /// Attaches the node at the 'incoming' index to the node at the 'outgoing' index.
221    /// This means that the node at the 'incoming' index will have an outgoing connection
222    /// to the node at the 'outgoing' index, and the node at the 'outgoing' index will have
223    /// an incoming connection from the node at the 'incoming' index.
224    ///
225    /// # Arguments
226    /// - incoming: The index of the node that will have an outgoing connection to the node at the 'outgoing' index.
227    /// - outgoing: The index of the node that will have an incoming connection from the node at the 'incoming' index.
228    pub fn attach(&mut self, incoming: usize, outgoing: usize) -> &mut Self {
229        self.as_mut()[incoming].insert_outgoing(outgoing);
230        self.as_mut()[outgoing].insert_incoming(incoming);
231        self
232    }
233    /// Detaches the node at the 'incoming' index from the node at the 'outgoing' index.
234    /// This means that the node at the 'incoming' index will no longer have an outgoing connection
235    /// to the node at the 'outgoing' index, and the node at the 'outgoing' index will no longer have
236    /// an incoming connection from the node at the 'incoming' index.
237    ///
238    /// # Arguments
239    /// - incoming: The index of the node that will no longer have an outgoing connection to the node at the 'outgoing' index.
240    /// - outgoing: The index of the node that will no longer have an incoming connection from the node at the 'incoming' index.
241    pub fn detach(&mut self, incoming: usize, outgoing: usize) -> &mut Self {
242        self.as_mut()[incoming].remove_outgoing(&outgoing);
243        self.as_mut()[outgoing].remove_incoming(&incoming);
244        self
245    }
246
247    /// tries to modify the graph using a [GraphTransaction]. If the transaction is successful,
248    /// we return true and do nothing. If the transaction is not successful, we roll back the transaction
249    /// by undoing all the changes made by the transaction and return false.
250    ///
251    /// # Arguments
252    ///  - mutation: A closure that takes a mutable reference to a [GraphTransaction] and returns a 'bool'.
253    #[inline]
254    pub fn try_modify<F>(&mut self, mutation: F) -> TransactionResult<T>
255    where
256        F: FnOnce(GraphTransaction<T>) -> TransactionResult<T>,
257        T: Clone,
258    {
259        mutation(GraphTransaction::new(self))
260    }
261
262    /// Given a list of node indices, this function will set the 'direction' field of the nodes
263    /// at those indices to [Direction::Backward] if they are part of a cycle. If they are not part
264    /// of a cycle, the 'direction' field will be set to [Direction::Forward].
265    /// If no indices are provided, the function will set the 'direction' field of all nodes in the graph.
266    #[inline]
267    pub fn set_cycles(&mut self, indecies: Vec<usize>) {
268        if indecies.is_empty() {
269            let all_indices = self
270                .as_ref()
271                .iter()
272                .map(|node| node.index())
273                .collect::<Vec<usize>>();
274
275            return self.set_cycles(all_indices);
276        }
277
278        for idx in indecies {
279            let cycles = self.get_cycles(idx);
280
281            if cycles.is_empty() {
282                if let Some(node) = self.get_mut(idx) {
283                    node.set_direction(Direction::Forward);
284                }
285            } else {
286                for cycle in cycles {
287                    if let Some(node) = self.get_mut(cycle) {
288                        node.set_direction(Direction::Backward);
289                    }
290                }
291            }
292        }
293    }
294
295    /// Get the cycles in the graph that include the node at the specified index.
296    ///
297    /// # Arguments
298    /// - index: The index of the node to get the cycles for.
299    #[inline]
300    pub fn get_cycles(&self, from: usize) -> std::collections::HashSet<usize> {
301        let n = self.len();
302        let mut on_stack = vec![false; n];
303        let mut visited = vec![false; n];
304        let mut cycles = vec![false; n];
305        let mut stack = Vec::with_capacity(n.min(64));
306
307        fn dfs<T>(
308            g: &Graph<T>,
309            u: usize,
310            visited: &mut [bool],
311            on_stack: &mut [bool],
312            cycles: &mut [bool],
313            stack: &mut Vec<usize>,
314        ) {
315            visited[u] = true;
316            on_stack[u] = true;
317            stack.push(u);
318
319            for &v in g.get(u).unwrap().outgoing() {
320                if !visited[v] {
321                    dfs(g, v, visited, on_stack, cycles, stack);
322                } else if on_stack[v] {
323                    let start = stack.iter().rposition(|&x| x == v).unwrap();
324                    for &w in &stack[start..] {
325                        cycles[w] = true;
326                    }
327                }
328            }
329
330            stack.pop();
331            on_stack[u] = false;
332        }
333
334        dfs(
335            self,
336            from,
337            &mut visited,
338            &mut on_stack,
339            &mut cycles,
340            &mut stack,
341        );
342
343        let mut out = HashSet::with_capacity(stack.len());
344        for (i, &c) in cycles.iter().enumerate() {
345            if c {
346                out.insert(i);
347            }
348        }
349        out
350    }
351}
352
353impl<T> Valid for Graph<T> {
354    #[inline]
355    fn is_valid(&self) -> bool {
356        self.iter().all(|node| node.is_valid())
357    }
358}
359
360impl<T> AsRef<[GraphNode<T>]> for Graph<T> {
361    fn as_ref(&self) -> &[GraphNode<T>] {
362        &self.nodes
363    }
364}
365
366impl<T> AsMut<[GraphNode<T>]> for Graph<T> {
367    fn as_mut(&mut self) -> &mut [GraphNode<T>] {
368        &mut self.nodes
369    }
370}
371
372impl<T> Index<usize> for Graph<T> {
373    type Output = GraphNode<T>;
374
375    fn index(&self, index: usize) -> &Self::Output {
376        &self.nodes[index]
377    }
378}
379
380impl<T> IndexMut<usize> for Graph<T> {
381    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
382        &mut self.nodes[index]
383    }
384}
385
386impl<T> IntoIterator for Graph<T> {
387    type Item = GraphNode<T>;
388    type IntoIter = std::vec::IntoIter<GraphNode<T>>;
389
390    fn into_iter(self) -> Self::IntoIter {
391        self.nodes.into_iter()
392    }
393}
394
395impl<T> FromIterator<GraphNode<T>> for Graph<T> {
396    fn from_iter<I: IntoIterator<Item = GraphNode<T>>>(iter: I) -> Self {
397        Graph {
398            nodes: iter.into_iter().collect(),
399        }
400    }
401}
402
403impl<T> Default for Graph<T> {
404    fn default() -> Self {
405        Graph { nodes: Vec::new() }
406    }
407}
408
409impl<T: Hash> Hash for Graph<T> {
410    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
411        for node in self.as_ref() {
412            node.hash(state);
413        }
414    }
415}
416
417impl<T: Debug> Debug for Graph<T> {
418    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419        writeln!(f, "Graph {{")?;
420        for node in self.as_ref() {
421            writeln!(f, "  {:?},", node)?;
422        }
423        write!(f, "}}")
424    }
425}
426
427#[cfg(test)]
428mod test {
429    use super::*;
430    use crate::{Arity, Node, Op};
431
432    #[test]
433    fn test_graph_is_valid() {
434        let mut graph_one = Graph::default();
435        graph_one.push((0, NodeType::Input, 123));
436        graph_one.push((1, NodeType::Output, 42));
437        graph_one.attach(0, 1);
438
439        let mut graph_two = Graph::default();
440        graph_two.push((0, NodeType::Input, 0));
441        graph_two.push((1, NodeType::Vertex, 1));
442
443        assert!(graph_one.is_valid());
444        assert!(!graph_two.is_valid());
445    }
446
447    #[test]
448    fn test_graph_attach() {
449        let mut graph = Graph::default();
450        graph.push((0, NodeType::Input, 0));
451        graph.push((1, NodeType::Output, 1));
452        graph.attach(0, 1);
453
454        assert_eq!(graph[0].outgoing(), &[1]);
455        assert_eq!(graph[1].incoming(), &[0]);
456    }
457
458    #[test]
459    fn test_graph_node_creations() {
460        let mut graph_one = Graph::from_iter(vec![
461            GraphNode::new(0, NodeType::Input, 0),
462            GraphNode::new(1, NodeType::Vertex, 1),
463            GraphNode::new(2, NodeType::Output, 1),
464        ]);
465
466        graph_one.attach(0, 1).attach(1, 2);
467
468        assert_eq!(graph_one.len(), 3);
469        assert!(graph_one.is_valid());
470        assert_eq!(graph_one[0].arity(), Arity::Zero);
471        assert_eq!(graph_one[1].arity(), Arity::Any);
472        assert_eq!(graph_one[2].arity(), Arity::Any);
473
474        let mut graph_two = Graph::new(vec![
475            GraphNode::new(0, NodeType::Input, Op::var(0)),
476            GraphNode::new(1, NodeType::Input, Op::constant(5.0)),
477            GraphNode::with_arity(2, NodeType::Vertex, Op::add(), Arity::Exact(2)),
478            GraphNode::new(3, NodeType::Output, Op::linear()),
479        ]);
480
481        graph_two.attach(0, 2).attach(1, 2).attach(2, 3);
482
483        assert_eq!(graph_two.len(), 4);
484        assert!(graph_two.is_valid());
485        assert_eq!(graph_two[0].arity(), Arity::Zero);
486        assert_eq!(graph_two[1].arity(), Arity::Zero);
487        assert_eq!(graph_two[2].arity(), Arity::Exact(2));
488        assert_eq!(graph_two[3].arity(), Arity::Any);
489    }
490
491    #[test]
492    fn test_simple_graph() {
493        let mut graph = Graph::<i32>::default();
494
495        let idx_one = graph.insert(NodeType::Input, 0);
496        let idx_two = graph.insert(NodeType::Vertex, 1);
497        let idx_three = graph.insert(NodeType::Output, 2);
498
499        graph.attach(idx_one, idx_two).attach(idx_two, idx_three);
500
501        assert_eq!(graph.len(), 3);
502
503        assert!(graph.is_valid());
504        assert!(graph[0].is_valid());
505        assert!(graph[1].is_valid());
506        assert!(graph[2].is_valid());
507
508        assert_eq!(graph[0].incoming().len(), 0);
509        assert_eq!(graph[0].outgoing().len(), 1);
510        assert_eq!(graph[1].incoming().len(), 1);
511        assert_eq!(graph[1].outgoing().len(), 1);
512        assert_eq!(graph[2].incoming().len(), 1);
513        assert_eq!(graph[2].outgoing().len(), 0);
514    }
515
516    #[test]
517    fn test_graph_with_cycles() {
518        let mut graph = Graph::<i32>::default();
519
520        graph.insert(NodeType::Input, 0);
521        graph.insert(NodeType::Vertex, 1);
522        graph.insert(NodeType::Vertex, 2);
523        graph.insert(NodeType::Output, 3);
524
525        graph.attach(0, 1).attach(1, 2).attach(2, 1).attach(2, 3);
526
527        assert_eq!(graph.len(), 4);
528
529        assert!(graph.is_valid());
530        assert!(graph[0].is_valid());
531        assert!(graph[1].is_valid());
532        assert!(graph[2].is_valid());
533        assert!(graph[3].is_valid());
534
535        assert_eq!(graph[0].incoming().len(), 0);
536        assert_eq!(graph[0].outgoing().len(), 1);
537        assert_eq!(graph[1].incoming().len(), 2);
538        assert_eq!(graph[1].outgoing().len(), 1);
539        assert_eq!(graph[2].incoming().len(), 1);
540        assert_eq!(graph[2].outgoing().len(), 2);
541        assert_eq!(graph[3].incoming().len(), 1);
542        assert_eq!(graph[3].outgoing().len(), 0);
543    }
544
545    #[test]
546    fn test_graph_with_cycles_and_recurrent_nodes() {
547        let mut graph = Graph::<i32>::default();
548
549        let idx_one = graph.insert(NodeType::Input, 0);
550        let idx_two = graph.insert(NodeType::Vertex, 1);
551        let idx_three = graph.insert(NodeType::Vertex, 2);
552        let idx_four = graph.insert(NodeType::Output, 3);
553
554        graph
555            .attach(idx_one, idx_two)
556            .attach(idx_two, idx_three)
557            .attach(idx_three, idx_two)
558            .attach(idx_three, idx_four)
559            .attach(idx_four, idx_two);
560
561        graph.set_cycles(vec![]);
562
563        assert_eq!(graph.len(), 4);
564
565        assert!(graph.is_valid());
566        assert!(graph[0].is_valid());
567        assert!(graph[1].is_valid());
568        assert!(graph[2].is_valid());
569        assert!(graph[3].is_valid());
570
571        assert_eq!(graph[0].incoming().len(), 0);
572        assert_eq!(graph[0].outgoing().len(), 1);
573        assert_eq!(graph[1].incoming().len(), 3);
574        assert_eq!(graph[1].outgoing().len(), 1);
575        assert_eq!(graph[2].incoming().len(), 1);
576        assert_eq!(graph[2].outgoing().len(), 2);
577        assert_eq!(graph[3].incoming().len(), 1);
578        assert_eq!(graph[3].outgoing().len(), 1);
579
580        assert_eq!(graph[0].direction(), Direction::Forward);
581        assert_eq!(graph[1].direction(), Direction::Backward);
582        assert_eq!(graph[2].direction(), Direction::Backward);
583        assert_eq!(graph[3].direction(), Direction::Backward);
584    }
585
586    #[test]
587    fn test_graph_set_cycles() {
588        let mut graph = Graph::<i32>::default();
589
590        let idx_one = graph.insert(NodeType::Input, 0);
591        let idx_two = graph.insert(NodeType::Vertex, 1);
592        let idx_three = graph.insert(NodeType::Vertex, 2);
593        let idx_four = graph.insert(NodeType::Output, 3);
594
595        graph
596            .attach(idx_one, idx_two)
597            .attach(idx_two, idx_three)
598            .attach(idx_three, idx_two)
599            .attach(idx_two, idx_four);
600
601        for node in graph.iter() {
602            assert!(node.is_valid());
603            assert_eq!(node.direction(), Direction::Forward);
604        }
605
606        graph.set_cycles(vec![]);
607
608        for node in graph.iter() {
609            assert!(node.is_valid());
610            if node.node_type() == NodeType::Vertex {
611                assert_eq!(node.direction(), Direction::Backward);
612            } else {
613                assert_eq!(node.direction(), Direction::Forward);
614            }
615        }
616    }
617
618    #[test]
619    fn test_graph_clone_and_partial_eq() {
620        let mut graph1 = Graph::default();
621        let input_idx = graph1.insert(NodeType::Input, 42);
622        let output_idx = graph1.insert(NodeType::Output, 24);
623        graph1.attach(input_idx, output_idx);
624
625        let graph2 = graph1.clone();
626        assert_eq!(graph1, graph2);
627
628        let mut graph3 = graph1.clone();
629        graph3[input_idx].set_direction(Direction::Backward);
630        assert_ne!(graph1, graph3);
631
632        let mut graph4 = graph1.clone();
633        if let Some(node) = graph4.get_mut(input_idx) {
634            *node.value_mut() = 100;
635        }
636        assert_ne!(graph1, graph4);
637    }
638
639    #[test]
640    fn test_graph_arity_validation() {
641        let mut graph = Graph::default();
642        let input_idx = graph.insert(NodeType::Input, 0);
643        graph.push((1, NodeType::Vertex, 1, Arity::Exact(2)));
644        let output_idx = graph.insert(NodeType::Output, 2);
645
646        graph.attach(input_idx, 1);
647        graph.attach(1, output_idx);
648
649        // Should be invalid - vertex needs exactly 2 incoming connections
650        assert!(!graph.is_valid());
651
652        // Add one connection - should still be invalid, connections are unique so this just
653        // replaces the existing one with the same index
654        graph.attach(input_idx, 1);
655        assert!(!graph.is_valid());
656
657        // Add third connection - should still be valid with Arity::Any
658        let input3_idx = graph.insert(NodeType::Input, 3);
659        graph.attach(input3_idx, 1);
660        println!("{:?}", graph);
661        assert!(graph.is_valid());
662    }
663
664    #[test]
665    fn test_graph_indexing() {
666        let mut graph = Graph::default();
667        let input_idx = graph.insert(NodeType::Input, 42);
668        let output_idx = graph.insert(NodeType::Output, 24);
669
670        // Test Index trait
671        assert_eq!(graph[input_idx].value(), &42);
672        assert_eq!(graph[output_idx].value(), &24);
673
674        // Test IndexMut trait
675        graph[input_idx].set_direction(Direction::Backward);
676        assert_eq!(graph[input_idx].direction(), Direction::Backward);
677
678        // Test get() and get_mut()
679        assert_eq!(graph.get(input_idx).unwrap().value(), &42);
680        assert_eq!(graph.get_mut(output_idx).unwrap().value(), &24);
681
682        // Test out of bounds
683        assert!(graph.get(999).is_none());
684        assert!(graph.get_mut(999).is_none());
685    }
686
687    #[test]
688    fn test_graph_node_type_queries() {
689        let mut graph = Graph::default();
690        graph.insert(NodeType::Input, 0);
691        graph.insert(NodeType::Input, 1);
692        graph.insert(NodeType::Vertex, 2);
693        graph.insert(NodeType::Vertex, 3);
694        graph.insert(NodeType::Output, 4);
695        graph.insert(NodeType::Output, 5);
696
697        // Test inputs()
698        let inputs = graph.inputs().collect::<Vec<_>>();
699        assert_eq!(inputs.len(), 2);
700        assert!(
701            inputs
702                .iter()
703                .all(|node| node.node_type() == NodeType::Input)
704        );
705
706        // Test vertices()
707        let vertices = graph.vertices().collect::<Vec<_>>();
708        assert_eq!(vertices.len(), 2);
709        assert!(
710            vertices
711                .iter()
712                .all(|node| node.node_type() == NodeType::Vertex)
713        );
714
715        // Test outputs()
716        let outputs = graph.outputs().collect::<Vec<_>>();
717        assert_eq!(outputs.len(), 2);
718        assert!(
719            outputs
720                .iter()
721                .all(|node| node.node_type() == NodeType::Output)
722        );
723    }
724
725    #[test]
726    fn test_graph_iterators() {
727        let mut graph = Graph::default();
728        let input_idx = graph.insert(NodeType::Input, 0);
729        let vertex_idx = graph.insert(NodeType::Vertex, 1);
730        let output_idx = graph.insert(NodeType::Output, 2);
731
732        graph.attach(input_idx, vertex_idx);
733        graph.attach(vertex_idx, output_idx);
734
735        // Test iter()
736        let nodes: Vec<_> = graph.iter().collect();
737        assert_eq!(nodes.len(), 3);
738        assert_eq!(nodes[0].value(), &0);
739        assert_eq!(nodes[1].value(), &1);
740        assert_eq!(nodes[2].value(), &2);
741
742        // Test iter_mut()
743        for node in graph.iter_mut() {
744            if node.node_type() == NodeType::Vertex {
745                node.set_direction(Direction::Backward);
746            }
747        }
748        assert_eq!(graph[vertex_idx].direction(), Direction::Backward);
749
750        // Test into_iter()
751        let values: Vec<_> = graph.into_iter().map(|node| *node.value()).collect();
752        assert_eq!(values, vec![0, 1, 2]);
753    }
754
755    #[test]
756    fn test_graph_detach() {
757        let mut graph = Graph::default();
758        let input_idx = graph.insert(NodeType::Input, 0);
759        let output_idx = graph.insert(NodeType::Output, 1);
760
761        // Test attaching and detaching
762        graph.attach(input_idx, output_idx);
763        assert!(graph[input_idx].outgoing().contains(&output_idx));
764        assert!(graph[output_idx].incoming().contains(&input_idx));
765
766        graph.detach(input_idx, output_idx);
767        assert!(!graph[input_idx].outgoing().contains(&output_idx));
768        assert!(!graph[output_idx].incoming().contains(&input_idx));
769
770        // Test detaching non-existent connection
771        graph.detach(input_idx, output_idx); // Should not panic
772    }
773
774    #[test]
775    #[cfg(feature = "serde")]
776    fn test_graph_eval_serde() {
777        use crate::Eval;
778
779        let mut graph = Graph::default();
780
781        graph.insert(NodeType::Input, 0);
782        graph.insert(NodeType::Vertex, 1);
783        graph.insert(NodeType::Output, 2);
784        graph.attach(0, 1);
785        graph.attach(1, 2);
786
787        let serialized = serde_json::to_string(&graph).unwrap();
788        let deserialized: Graph<i32> = serde_json::from_str(&serialized).unwrap();
789
790        assert_eq!(graph, deserialized);
791
792        let values = vec![
793            (NodeType::Input, vec![Op::var(0), Op::var(1)]),
794            (NodeType::Edge, vec![Op::weight()]),
795            (NodeType::Vertex, vec![Op::sub(), Op::mul(), Op::linear()]),
796            (NodeType::Output, vec![Op::linear()]),
797        ];
798
799        let op_graph = Graph::directed(2, 2, values);
800        let eval_one = op_graph.eval(&vec![vec![0.5, 1.5]]);
801
802        let serialized_op = serde_json::to_string(&op_graph).unwrap();
803        let deserialized_op: Graph<Op<f32>> = serde_json::from_str(&serialized_op).unwrap();
804
805        let deserialized_eval = deserialized_op.eval(&vec![vec![0.5, 1.5]]);
806
807        assert_eq!(eval_one, deserialized_eval);
808        assert_eq!(op_graph, deserialized_op);
809    }
810
811    #[test]
812    #[cfg(feature = "serde")]
813    fn test_graph_pre_built_serde() {
814        use crate::Eval;
815
816        let mut graph = Graph::<Op<f32>>::default();
817
818        let idx_one = graph.insert(NodeType::Input, Op::var(0));
819        let idx_two = graph.insert(NodeType::Input, Op::constant(5_f32));
820        let idx_three = graph.insert(NodeType::Vertex, Op::add());
821        let idx_four = graph.insert(NodeType::Output, Op::linear());
822
823        graph
824            .attach(idx_one, idx_three)
825            .attach(idx_two, idx_three)
826            .attach(idx_three, idx_four);
827
828        let eval_to_six_one = graph.eval(&vec![vec![1_f32]]);
829        let eval_to_seven_one = graph.eval(&vec![vec![2_f32]]);
830        let eval_to_eight_one = graph.eval(&vec![vec![3_f32]]);
831
832        assert_eq!(eval_to_six_one, &[&[6_f32]]);
833        assert_eq!(eval_to_seven_one, &[&[7_f32]]);
834        assert_eq!(eval_to_eight_one, &[&[8_f32]]);
835        assert_eq!(graph.len(), 4);
836
837        let serialized = serde_json::to_string(&graph).unwrap();
838        let deserialized: Graph<Op<f32>> = serde_json::from_str(&serialized).unwrap();
839
840        assert_eq!(graph, deserialized);
841
842        let eval_to_six_two = deserialized.eval(&vec![vec![1_f32]]);
843        let eval_to_seven_two = deserialized.eval(&vec![vec![2_f32]]);
844        let eval_to_eight_two = deserialized.eval(&vec![vec![3_f32]]);
845
846        assert_eq!(eval_to_six_two, &[&[6_f32]]);
847        assert_eq!(eval_to_seven_two, &[&[7_f32]]);
848        assert_eq!(eval_to_eight_two, &[&[8_f32]]);
849        assert_eq!(deserialized.len(), 4);
850    }
851}