radiate_gp/collections/graphs/
graph.rs

1use crate::NodeType;
2use crate::collections::graphs::GraphTransaction;
3use crate::collections::{Direction, GraphNode};
4use radiate::Valid;
5use std::collections::{HashSet, VecDeque};
6use std::fmt::Debug;
7use std::ops::{Index, IndexMut};
8
9use super::transaction::TransactionResult;
10
11/// A 'Graph' is simply a 'Vec' of 'GraphNode's.
12///
13/// Its important to note that this graph differs from a traditional graph in that it is not
14/// a collection of edges and vertices. Instead, it is a collection of nodes that are connected
15/// to one another. Each node has a unique index that is used to reference it in the graph
16/// and must be identical to its position in the 'Vec'.
17/// Each 'GraphNode' has a set of ordered incoming and outgoing connections. These connections are
18/// represented by the index of the connected node in the graph. Because of this representation,
19/// an edge is not a separate entity, its just a node. The 'NodeType' enum is used to distinguish
20/// different types of nodes. This allows for a more flexible representation of the graph
21/// while still maintaining the ability to represent traditional graphs.
22///
23/// By default, a 'Graph' is a directed acyclic graph (DAG). However, it is possible to create
24/// cycles in the graph by setting the 'direction' field of a 'GraphNode' to 'Direction::Backward'.
25/// The 'Graph' struct provides methods for attaching and detaching nodes from one another.
26/// It also provides methods for iterating over the nodes in the graph in a sudo topological order.
27//
28#[derive(Clone, Default, PartialEq)]
29pub struct Graph<T> {
30    nodes: Vec<GraphNode<T>>,
31}
32
33/// The 'Graph' struct provides methods for creating, modifying, and iterating over a graph.
34impl<T> Graph<T> {
35    /// Create a new 'Graph' from a 'Vec' of 'GraphNode's.
36    ///
37    /// # Arguments
38    /// - nodes: A 'Vec' of 'GraphNode's.
39    pub fn new(nodes: Vec<GraphNode<T>>) -> Self {
40        Graph { nodes }
41    }
42
43    /// Push a 'GraphNode' onto the last position in the graph.
44    pub fn push(&mut self, node: impl Into<GraphNode<T>>) {
45        self.nodes.push(node.into());
46    }
47
48    pub fn insert(&mut self, node_type: NodeType, val: T) -> usize {
49        self.push((self.len(), node_type, val));
50        self.len() - 1
51    }
52
53    /// Pop the last 'GraphNode' from the graph.
54    pub fn pop(&mut self) -> Option<GraphNode<T>> {
55        self.nodes.pop()
56    }
57
58    /// Returns the number of nodes in the graph.
59    pub fn len(&self) -> usize {
60        self.nodes.len()
61    }
62
63    /// Returns true if the graph is empty.
64    pub fn is_empty(&self) -> bool {
65        self.nodes.is_empty()
66    }
67
68    /// Returns a mutable reference to the node at the specified index.
69    pub fn get_mut(&mut self, index: usize) -> Option<&mut GraphNode<T>> {
70        self.nodes.get_mut(index)
71    }
72
73    /// Returns a reference to the node at the specified index.
74    pub fn get(&self, index: usize) -> Option<&GraphNode<T>> {
75        self.nodes.get(index)
76    }
77
78    /// iterates over the nodes in the graph. The nodes are returned in the order they
79    /// were added, so there is no real order to this iterator.
80    pub fn iter(&self) -> impl Iterator<Item = &GraphNode<T>> {
81        self.nodes.iter()
82    }
83
84    /// mutably iterates over the nodes in the graph. The nodes are returned in the order they
85    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut GraphNode<T>> {
86        self.nodes.iter_mut()
87    }
88
89    /// Attach and detach nodes from one another. This is the primary way to modify the graph.
90    /// Note that this method does not check if the nodes are already connected. This is because
91    /// the connections are represented by 'HashSet's which do not allow duplicates.
92    /// Its also important to note that the 'incoming' and 'outgoing' indices are the indices of the
93    /// nodes in the graph, not the indices of the connections in the 'incoming' and 'outgoing' 'HashSet's.
94    /// We must also remember that the 'GraphNode' cares about the 'Arity' of the 'Operation' it contains,
95    /// so if we add a connection that would violate the 'Arity' of the 'Operation', the connection will result
96    /// in a 'GraphNode' that is not 'Valid'.
97    ///
98    /// Attaches the node at the 'incoming' index to the node at the 'outgoing' index.
99    /// This means that the node at the 'incoming' index will have an outgoing connection
100    /// to the node at the 'outgoing' index, and the node at the 'outgoing' index will have
101    /// an incoming connection from the node at the 'incoming' index.
102    ///
103    /// # Arguments
104    /// - incoming: The index of the node that will have an outgoing connection to the node at the 'outgoing' index.
105    /// - outgoing: The index of the node that will have an incoming connection from the node at the 'incoming' index.
106    pub fn attach(&mut self, incoming: usize, outgoing: usize) -> &mut Self {
107        self.as_mut()[incoming].outgoing_mut().insert(outgoing);
108        self.as_mut()[outgoing].incoming_mut().insert(incoming);
109        self
110    }
111    /// Detaches the node at the 'incoming' index from the node at the 'outgoing' index.
112    /// This means that the node at the 'incoming' index will no longer have an outgoing connection
113    /// to the node at the 'outgoing' index, and the node at the 'outgoing' index will no longer have
114    /// an incoming connection from the node at the 'incoming' index.
115    ///
116    /// # Arguments
117    /// - incoming: The index of the node that will no longer have an outgoing connection to the node at the 'outgoing' index.
118    /// - outgoing: The index of the node that will no longer have an incoming connection from the node at the 'incoming' index.
119    pub fn detach(&mut self, incoming: usize, outgoing: usize) -> &mut Self {
120        self.as_mut()[incoming].outgoing_mut().remove(&outgoing);
121        self.as_mut()[outgoing].incoming_mut().remove(&incoming);
122        self
123    }
124}
125
126/// Functinos for modifying the graph.
127impl<T> Graph<T> {
128    /// Given a list of node indices, this function will set the 'direction' field of the nodes
129    /// at those indices to 'Direction::Backward' if they are part of a cycle. If they are not part
130    /// of a cycle, the 'direction' field will be set to 'Direction::Forward'.
131    /// If no indices are provided, the function will set the 'direction' field of all nodes in the graph.
132    #[inline]
133    pub fn set_cycles(&mut self, indecies: Vec<usize>) {
134        if indecies.is_empty() {
135            let all_indices = self
136                .as_ref()
137                .iter()
138                .map(|node| node.index())
139                .collect::<Vec<usize>>();
140
141            return self.set_cycles(all_indices);
142        }
143
144        for idx in indecies {
145            let node_cycles = self.get_cycles(idx);
146
147            if node_cycles.is_empty() {
148                if let Some(node) = self.get_mut(idx) {
149                    node.set_direction(Direction::Forward);
150                }
151            } else {
152                for cycle_idx in node_cycles {
153                    if let Some(node) = self.get_mut(cycle_idx) {
154                        node.set_direction(Direction::Backward);
155                    }
156                }
157            }
158        }
159    }
160
161    /// tries to modify the graph using a 'GraphTransaction'. If the transaction is successful,
162    /// we return true and do nothing. If the transaction is not successful, we rollback the transaction
163    /// by undoing all the changes made by the transaction and return false.
164    ///
165    /// # Arguments
166    ///  - mutation: A closure that takes a mutable reference to a 'GraphTransaction' and returns a 'bool'.
167    #[inline]
168    pub fn try_modify<F>(&mut self, mutation: F) -> TransactionResult<T>
169    where
170        F: FnOnce(GraphTransaction<T>) -> TransactionResult<T>,
171        T: Clone + Default + PartialEq,
172    {
173        mutation(GraphTransaction::new(self))
174    }
175}
176
177/// Functions for checking the validity of the graph or connections between nodes. These are
178/// useful for modifying the graph in a way that maintains its integrity.
179impl<T> Graph<T> {
180    /// Get the cycles in the graph that include the node at the specified index.
181    ///
182    /// # Arguments
183    /// - index: The index of the node to get the cycles for.
184    #[inline]
185    pub fn get_cycles(&self, index: usize) -> Vec<usize> {
186        let mut path = Vec::new();
187        let mut seen = HashSet::new();
188        let mut current = self
189            .get(index)
190            .map(|node| node.outgoing().iter().cloned().collect::<VecDeque<usize>>())
191            .unwrap_or_default();
192
193        while !current.is_empty() {
194            let current_index = current.pop_front().unwrap();
195            if let Some(current_node) = self.get(current_index) {
196                if seen.contains(&current_index) {
197                    continue;
198                }
199
200                if current_index == index {
201                    path.push(current_index);
202                    return path;
203                }
204
205                seen.insert(current_index);
206
207                if !current_node.outgoing().is_empty() {
208                    path.push(current_index);
209                    for outgoing in current_node.outgoing().iter() {
210                        current.push_back(*outgoing);
211                    }
212                }
213            }
214        }
215
216        Vec::new()
217    }
218}
219
220impl<T> Valid for Graph<T> {
221    #[inline]
222    fn is_valid(&self) -> bool {
223        self.iter().all(|node| node.is_valid())
224    }
225}
226
227impl<T> AsRef<[GraphNode<T>]> for Graph<T> {
228    fn as_ref(&self) -> &[GraphNode<T>] {
229        &self.nodes
230    }
231}
232
233impl<T> AsMut<[GraphNode<T>]> for Graph<T> {
234    fn as_mut(&mut self) -> &mut [GraphNode<T>] {
235        &mut self.nodes
236    }
237}
238
239impl<T> Index<usize> for Graph<T> {
240    type Output = GraphNode<T>;
241
242    fn index(&self, index: usize) -> &Self::Output {
243        &self.nodes[index]
244    }
245}
246
247impl<T> IndexMut<usize> for Graph<T> {
248    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
249        &mut self.nodes[index]
250    }
251}
252
253impl<T> IntoIterator for Graph<T> {
254    type Item = GraphNode<T>;
255    type IntoIter = std::vec::IntoIter<GraphNode<T>>;
256
257    fn into_iter(self) -> Self::IntoIter {
258        self.nodes.into_iter()
259    }
260}
261
262impl<T> FromIterator<GraphNode<T>> for Graph<T> {
263    fn from_iter<I: IntoIterator<Item = GraphNode<T>>>(iter: I) -> Self {
264        Graph {
265            nodes: iter.into_iter().collect(),
266        }
267    }
268}
269
270impl<T: Debug + PartialEq + Clone> Debug for Graph<T> {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        write!(f, "Graph {{\n")?;
273        for node in self.as_ref() {
274            write!(f, "  {:?},\n", node)?;
275        }
276        write!(f, "}}")
277    }
278}