Skip to main content

radiate_gp/collections/graphs/
iter.rs

1use crate::{Node, NodeType, collections::GraphNode};
2use radiate_core::Valid;
3use std::collections::VecDeque;
4
5/// [GraphIterator] is a trait that provides an iterator over any &[[`GraphNode<T>`]]. The iterator is used to
6/// traverse the said nodes in a pseudo-topological order.
7pub trait GraphIterator<'a, T> {
8    fn iter_topological(&'a self) -> GraphTopologicalIterator<'a, T>;
9
10    fn get_nodes_of_type(
11        &'a self,
12        node_type: NodeType,
13    ) -> impl Iterator<Item = &'a GraphNode<T>> + 'a
14    where
15        Self: AsRef<[GraphNode<T>]>,
16        T: 'a,
17    {
18        self.as_ref()
19            .iter()
20            .filter(move |node| node.node_type() == node_type)
21    }
22}
23
24impl<'a, G: AsRef<[GraphNode<T>]>, T> GraphIterator<'a, T> for G {
25    fn iter_topological(&'a self) -> GraphTopologicalIterator<'a, T> {
26        GraphTopologicalIterator::new(self.as_ref())
27    }
28}
29
30/// [GraphIterator] is an iterator that traverses a &[[`GraphNode<T>`]] in sudo-topological order. I say
31/// "sudo-topological" because it is not a true topological order, but rather a topological order
32/// that allows for recurrent connections.
33pub struct GraphTopologicalIterator<'a, T> {
34    graph: &'a [GraphNode<T>],
35    completed: Vec<bool>,
36    index_queue: VecDeque<usize>,
37    pending_index: usize,
38}
39
40impl<'a, T> GraphTopologicalIterator<'a, T> {
41    /// Create a new `GraphIterator` from a reference to a [`GraphNode<T>`].
42    ///
43    /// # Arguments
44    /// - `graph`: A reference to the `Graph` to iterate over.
45    pub fn new(graph: &'a [GraphNode<T>]) -> Self {
46        let is_valid = !graph.iter().any(|node| !node.is_valid());
47        GraphTopologicalIterator {
48            graph,
49            completed: vec![false; graph.len()],
50            index_queue: VecDeque::with_capacity(graph.len()),
51            pending_index: if is_valid { 0 } else { graph.len() },
52        }
53    }
54}
55
56/// Implement the `Iterator` trait for [GraphTopologicalIterator].
57/// The `Item` type is a reference to a [GraphNode].
58///
59/// This implementation is a bit more complex than the typical iterator implementation. The iterator
60/// must traverse the graph in a pseudo-topological order. This means that it must iterate over the
61/// nodes in the graph in an order that respects the dependencies between the nodes. We
62/// do this by keeping track of which nodes have been completed and which nodes are pending, it
63/// then iterates over the nodes in the graph, checking the dependencies of each node to determine
64/// if it can be completed. If a node can be completed, it is added to the index queue, which is
65/// used to determine the order in which the nodes are returned by the iterator.
66/// It is a 'pseudo' topological order because it allows for recurrent connections in the graph.
67impl<'a, T> Iterator for GraphTopologicalIterator<'a, T> {
68    type Item = &'a GraphNode<T>;
69
70    #[inline]
71    fn next(&mut self) -> Option<Self::Item> {
72        let mut min_pending_index = self.graph.len();
73        for index in self.pending_index..self.graph.len() {
74            if self.completed[index] {
75                continue;
76            }
77
78            let node = &self.graph[index];
79
80            let mut degree = node.incoming().len();
81            for incoming_index in node.incoming() {
82                let incoming_node = &self.graph[*incoming_index];
83                if self.completed[incoming_node.index()] || incoming_node.is_recurrent() {
84                    degree -= 1;
85                }
86            }
87
88            if degree == 0 {
89                self.completed[node.index()] = true;
90                self.index_queue.push_back(node.index());
91            } else {
92                min_pending_index = std::cmp::min(min_pending_index, node.index());
93            }
94        }
95
96        self.pending_index = min_pending_index;
97        self.index_queue.pop_front().map(|idx| &self.graph[idx])
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::collections::{Graph, GraphNode, NodeType};
105    use crate::ops::Op;
106
107    #[test]
108    fn test_graph_iterator() {
109        let graph = Graph::new(vec![
110            GraphNode::from((0, NodeType::Input, Op::var(0))).with_outgoing([2]),
111            GraphNode::from((1, NodeType::Input, Op::var(1))).with_outgoing([2]),
112            GraphNode::from((2, NodeType::Vertex, Op::add()))
113                .with_incoming([0, 1])
114                .with_outgoing([3]),
115            GraphNode::from((3, NodeType::Output, Op::linear())).with_incoming([2]),
116        ]);
117
118        let mut iter = graph.iter_topological();
119
120        assert_eq!(iter.next().unwrap().index(), 0);
121        assert_eq!(iter.next().unwrap().index(), 1);
122        assert_eq!(iter.next().unwrap().index(), 2);
123        assert_eq!(iter.next().unwrap().index(), 3);
124        assert!(iter.next().is_none());
125    }
126
127    #[test]
128    fn test_graph_iterator_recurrent() {
129        let nodes = vec![
130            GraphNode::from((0, NodeType::Input, Op::var(0), vec![], vec![2])),
131            GraphNode::from((1, NodeType::Input, Op::var(1), vec![], vec![2])),
132            GraphNode::from((2, NodeType::Vertex, Op::add(), vec![0, 1], vec![3])),
133            GraphNode::from((3, NodeType::Vertex, Op::mul(), vec![2], vec![2])),
134            GraphNode::from((4, NodeType::Output, Op::linear(), vec![3], vec![])),
135        ];
136
137        let graph = Graph::new(nodes);
138        let mut iter = graph.iter_topological();
139
140        assert_eq!(iter.next().unwrap().index(), 0);
141        assert_eq!(iter.next().unwrap().index(), 1);
142        assert_eq!(iter.next().unwrap().index(), 2);
143        assert_eq!(iter.next().unwrap().index(), 3);
144        assert_eq!(iter.next().unwrap().index(), 4);
145        assert!(iter.next().is_none());
146    }
147
148    #[test]
149    fn test_graph_iterator_disconnected() {
150        let nodes = vec![
151            GraphNode::from((0, NodeType::Input, Op::var(0))).with_outgoing([2]),
152            GraphNode::from((1, NodeType::Input, Op::var(1))),
153            GraphNode::from((2, NodeType::Vertex, Op::add())).with_incoming([0]),
154            GraphNode::from((3, NodeType::Output, Op::linear())).with_incoming([2]),
155        ];
156
157        let results = Graph::new(nodes)
158            .iter_topological()
159            .map(|node| node.index())
160            .collect::<Vec<usize>>();
161
162        assert!(results.is_empty());
163    }
164
165    #[test]
166    fn test_graph_deep_cycles() {
167        let mut graph = Graph::<Op<f32>>::default();
168
169        graph.insert(NodeType::Input, Op::var(0));
170        graph.insert(NodeType::Vertex, Op::diff());
171        graph.insert(NodeType::Output, Op::sigmoid());
172        graph.insert(NodeType::Vertex, Op::div());
173        graph.insert(NodeType::Vertex, Op::pow());
174        graph.insert(NodeType::Edge, Op::weight());
175        graph.insert(NodeType::Edge, Op::identity());
176        graph.insert(NodeType::Vertex, Op::exp());
177        graph.insert(NodeType::Vertex, Op::cos());
178        graph.insert(NodeType::Edge, Op::weight());
179
180        graph.attach(0, 1);
181        graph.attach(1, 1);
182        graph.attach(4, 1);
183        graph.attach(7, 1);
184        graph.attach(1, 2);
185        graph.attach(3, 2);
186        graph.attach(9, 2);
187        graph.attach(0, 3);
188        graph.attach(5, 3);
189        graph.attach(0, 4);
190        graph.attach(8, 4);
191        graph.attach(1, 5);
192        graph.attach(3, 6);
193        graph.attach(4, 7);
194        graph.attach(6, 8);
195        graph.attach(7, 9);
196
197        graph.set_cycles(vec![]);
198
199        let results = graph
200            .iter_topological()
201            .map(|node| node.index())
202            .collect::<Vec<usize>>();
203
204        assert_eq!(results, vec![0, 1, 3, 4, 5, 6, 7, 8, 9, 2]);
205    }
206}