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}