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(¤t_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}