graph_builder/lib.rs
1//! # Graph-Builder
2//! An algorithm for generating graphs with post-filtering and edge composition.
3//!
4//! This algorithm is used by automated theorem provers on typical group/category problems.
5//! For example, to study algebra or path semantics.
6//!
7//! The advantage of this algorithm is that it exploits symmetry of Category Theory.
8//! For every morphism `A -> B` and `B -> C`, there exists a morphism `A -> C`.
9//! This means that for a large number of objects, one only needs to keep neighbour morphisms.
10//!
11//! Constructing a graph using small operations makes it possible to
12//! minimize the work required to get from one node to another.
13//!
14//! For information of how use this library, see the documentation on the various functions.
15
16#![deny(missing_docs)]
17
18use std::hash::Hash;
19use std::error::Error;
20
21/// A graph is a tuple of nodes and edges between nodes.
22pub type Graph<T, U> = (Vec<T>, Vec<([usize; 2], U)>);
23
24/// Stores settings for generating graph.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct GenerateSettings {
27 /// The maximum number of nodes before terminating.
28 pub max_nodes: usize,
29 /// The maximum number of edges before terminating.
30 pub max_edges: usize,
31}
32
33/// Stores a graph generating error.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub enum GenerateError {
36 /// Hit limit maximum number of nodes.
37 MaxNodes,
38 /// Hit limit maximum number of edges.
39 MaxEdges,
40}
41
42impl std::fmt::Display for GenerateError {
43 fn fmt(&self, w: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
44 match *self {
45 GenerateError::MaxNodes => write!(w, "Reached limit maximum number of nodes"),
46 GenerateError::MaxEdges => write!(w, "Reached limit maximum number of edges"),
47 }
48 }
49}
50
51impl Error for GenerateError {}
52
53impl From<GenerateError> for () {
54 fn from(_: GenerateError) -> () {()}
55}
56
57/// Generates a graph from:
58///
59/// - an initial seed state `a`
60/// - maximum `n` edges
61/// - a function `f` to generate a new node with edge
62/// - a filter `g` to post-process nodes
63/// - a composer `h` to use for transforming edges when nodes are filtered
64/// - settings to control usage of memory
65///
66/// Returns a list of nodes and a list of edges.
67///
68/// - `Ok` if generation was successful without hitting memory limits
69/// - `Err` if generation hit memory limits
70///
71/// Waiting with filtering until post-processing makes it possible
72/// to create an edge between nodes that require multiple steps.
73///
74/// The maximum number of edges is usually determined from the length of a list of valid operations.
75///
76/// ### Error handling
77///
78/// The algorithm continues to post-processing when hitting memory limits.
79/// It is assumed that one wants to make use of the data generated,
80/// where memory limits are met often due to combinatorial explosions.
81///
82/// The algorithm assumes that there are other limits besides those in `GenerateSettings`.
83/// To handle errors, one adds a type that implements `From<GenerateError>`.
84/// The unit type `()` can be used when error handling is not needed.
85///
86/// Tip: Since this function requires 6 generic type arguments,
87/// it is easier to set the error type on the output type and let Rust infer the rest.
88///
89/// For example:
90///
91/// ```ignore
92/// let (nodes, edges) = match gen(start, n, f, g, h, &settings) {
93/// Ok(x) => x,
94/// Err((x, ())) => x,
95/// };
96/// ```
97///
98/// When an error happens during composing edges, one can choose whether to
99/// report the error with `Err(Some(err))`, or ignore it with `Err(None)`.
100/// This is useful because sometimes you want to filter edges without reporting errors.
101/// The algorithm assumes that one wishes to continue generating the graph
102/// when encountering an error. Only the first error will be reported.
103pub fn gen<T, U, F, G, H, E>(
104 (mut nodes, mut edges): Graph<T, U>,
105 n: usize,
106 f: F,
107 g: G,
108 h: H,
109 settings: &GenerateSettings,
110) -> Result<Graph<T, U>, (Graph<T, U>, E)>
111 where T: Eq + Hash + Clone,
112 F: Fn(&T, usize) -> Result<(T, U), E>,
113 G: Fn(&T) -> bool,
114 H: Fn(&U, &U) -> Result<U, Option<E>>,
115 E: From<GenerateError>
116{
117 use std::collections::{HashMap, HashSet};
118
119 let mut error: Option<E> = None;
120 let mut has: HashMap<T, usize> = HashMap::new();
121 let mut has_edge: HashSet<[usize; 2]> = HashSet::new();
122 for n in &nodes {
123 has.insert(n.clone(), 0);
124 }
125 for edge in &edges {
126 has_edge.insert(edge.0);
127 }
128 let mut i = 0;
129 'outer: while i < nodes.len() {
130 for j in 0..n {
131 match f(&nodes[i], j) {
132 Ok((new_node, new_edge)) => {
133 let id = if let Some(&id) = has.get(&new_node) {id}
134 else {
135 let id = nodes.len();
136 has.insert(new_node.clone(), id);
137 nodes.push(new_node);
138 id
139 };
140 has_edge.insert([i, id]);
141 edges.push(([i, id], new_edge));
142
143 if nodes.len() >= settings.max_nodes {
144 if error.is_none() {
145 error = Some(GenerateError::MaxNodes.into());
146 }
147 break 'outer;
148 } else if edges.len() >= settings.max_edges {
149 if error.is_none() {
150 error = Some(GenerateError::MaxEdges.into());
151 }
152 break 'outer;
153 }
154 }
155 Err(err) => {
156 error = Some(err);
157 }
158 }
159 }
160 i += 1;
161 }
162 let mut removed: HashSet<usize> = HashSet::new();
163 // Hash nodes that do not passes filter.
164 for i in 0..nodes.len() {if !g(&nodes[i]) {removed.insert(i);}}
165 let edges_count = edges.len();
166 let mut removed_edges: Vec<usize> = vec![];
167 let mut j = 0;
168 // Generate new edges by composing them if they got removed.
169 while j < edges.len() {
170 let [a, b] = edges[j].0;
171 if removed.contains(&b) {
172 removed_edges.push(j);
173 // Look for all edges that starts with removed node.
174 for k in 0..edges_count {
175 let [c, d] = edges[k].0;
176 if c == b && !has_edge.contains(&[a, d]) {
177 // Compose the two edges into a new one that
178 // no longer refers to the removed node.
179 match h(&edges[j].1, &edges[k].1) {
180 Ok(new_edge) => {
181 edges.push(([a, d], new_edge));
182 has_edge.insert([a, d]);
183 }
184 Err(None) => {}
185 Err(Some(err)) => {
186 if error.is_none() {
187 error = Some(err);
188 }
189 }
190 }
191 }
192 }
193 }
194 j += 1;
195 }
196
197 let mut new_nodes = vec![];
198 let mut map_nodes: Vec<Option<usize>> = vec![];
199 for (i, node) in nodes.into_iter().enumerate() {
200 if removed.contains(&i) {
201 map_nodes.push(None);
202 } else {
203 let id = new_nodes.len();
204 map_nodes.push(Some(id));
205 new_nodes.push(node);
206 }
207 }
208 for j in (0..edges.len()).rev() {
209 let [a, b] = edges[j].0;
210 if let (Some(a), Some(b)) = (map_nodes[a], map_nodes[b]) {
211 edges[j].0 = [a, b];
212 } else {
213 edges.swap_remove(j);
214 }
215 }
216
217 if let Some(err) = error {
218 Err(((new_nodes, edges), err))
219 } else {
220 Ok((new_nodes, edges))
221 }
222}
223
224/// Filters edges such that only those who are equal in both directions remains.
225///
226/// Removes redundant edges and edges which only exist in one direction.
227///
228/// Does not preserve the order of edges.
229/// The order of the edges is unsorted afterwards.
230///
231/// Assumes that there are maximum two edges between nodes.
232pub fn bidir<T: PartialEq + std::fmt::Debug>(edges: &mut Vec<([usize; 2], T)>) {
233 if edges.len() == 0 {return};
234
235 // Fix indices such that they pair up.
236 for j in 0..edges.len() {
237 let [a, b] = edges[j].0;
238 edges[j].0 = [a.min(b), a.max(b)];
239 }
240 edges.sort_by_key(|s| s.0);
241 let mut pair = false;
242 for j in (0..edges.len()).rev() {
243 let k = j + 1;
244 if pair {
245 if k >= edges.len() {
246 edges.swap_remove(j);
247 } else {
248 if edges[j] == edges[k] {
249 edges.swap_remove(k);
250 } else {
251 edges.swap_remove(j);
252 }
253 pair = false;
254 }
255 } else {
256 pair = true;
257 }
258 }
259}