graph_solver/
lib.rs

1#![deny(missing_docs)]
2
3//! # Graph Solver
4//! An undirected graph constraint solver for node and edge colors.
5//!
6//! Brought to you by the [AdvancedResearch](https://github.com/advancedresearch) community!
7//!
8//! - If you are looking for a generic solver that does not remove facts,
9//! see [monotonic_solver](https://github.com/advancedresearch/monotonic_solver)
10//! - If you are looking for a generic solver that can remove facts,
11//! see [linear_solver](https://github.com/advancedresearch/linear_solver)
12//! - If you are looking for a brute-force automated theorem prover for classical and path semantical logic,
13//! see [pocket_prover](https://github.com/advancedresearch/pocket_prover)
14//!
15//! ### Motivation
16//!
17//! Graph solving is used to find visual representations of various algebras,
18//! such as [Adinkra diagrams](https://en.wikipedia.org/wiki/Adinkra_symbols_(physics)),
19//! but where the rules for generating the graphs efficiently is unknown.
20//!
21//! ![adinkra](https://raw.githubusercontent.com/advancedresearch/graph_solver/master/images/adinkra.png)
22//!
23//! ### Example: Cube
24//!
25//! To construct a cube using a graph solver
26//! requires specifying how nodes connect to other nodes.
27//! The information you give the solver is which colors these connections have,
28//! but without telling which nodes in the graph should be connected.
29//!
30//! ![cube](https://raw.githubusercontent.com/advancedresearch/graph_solver/master/images/cube.png)
31//!
32//! ```rust̨
33//! /*
34//! === CUBE EXAMPLE ===
35//!
36//! Run with GraphViz (https://graphviz.org/):
37//!
38//!     cargo run --example cube | dot -Tpng > test.png
39//!
40//! */
41//!
42//! use graph_solver::*;
43//!
44//! // Notice that edges starts with `2`.
45//! // This is because `0` is empty and `1` is no-edge.
46//! const EDGE: Color = 2;
47//!
48//! fn main() {
49//!     let mut g = Graph::new();
50//!
51//!     // Create a node pattern with 3 edges.
52//!     let a = Node {
53//!         color: 0,
54//!         self_connected: false,
55//!         edges: vec![Constraint {edge: EDGE, node: 0}; 3]
56//!     };
57//!
58//!     // Add 8 vertices.
59//!     for _ in 0..8 {g.push(a.clone())}
60//!     g.no_triangles = true;
61//!
62//!     let solve_settings = SolveSettings::new();
63//!     if let Some(solution) = g.solve(solve_settings) {
64//!         println!("{}", solution.puzzle.graphviz(
65//!             "sfdp",
66//!             &["black"],
67//!             &["black"]
68//!         ));
69//!     } else {
70//!         eprintln!("<no solution>");
71//!     }
72//! }
73//! ```
74//!
75//! ### Introduction to Graph Constraint Solving
76//!
77//! Each node has a color and a list of edge constraints.
78//! The edge constraint stores an edge color and a target color for the adjacent node.
79//!
80//! This technique creates a powerful language to describe graphs compactly.
81//! For example, all nodes that are locally similar,
82//! can use a common description.
83//!
84//! Any graphs can be determined using
85//! sufficient local information about the nodes and edges.
86//! To do this, one can assign each node and edge a unique number.
87//!
88//! Therefore, to describe a graph in more detail,
89//! one can simply add more colors!
90//!
91//! ### Design
92//!
93//! Uses the [quickbacktrack](https://crates.io/crates/quickbacktrack) library
94//! for constraint solving.
95//!
96//! This allows starting with a partially solved graph,
97//! override solving strategies, debug, or comparing the solution vs the original.
98//!
99//! - Node and edge colors are represents as `u64`
100//! - A node color can be any value
101//! - Edge colors usually start with `2`
102//! - An edge color `0` is means no choice (neither empty or colored).
103//! - An edge color `1` is means empty
104
105pub use quickbacktrack::*;
106
107/// The type of color.
108pub type Color = u64;
109
110/// Edges with value 0 are treated as empty.
111pub const EMPTY_EDGE: Color = 0;
112/// Edges with value 1 are treated as diconnected.
113pub const DISCONNECTED_EDGE: Color = 1;
114
115/// Stores information about graph.
116///
117/// An edge value `0` means no edge.
118#[derive(Clone, Debug)]
119pub struct Graph {
120    /// Nodes.
121    pub nodes: Vec<Node>,
122    /// Edges.
123    pub edges: Vec<Vec<Color>>,
124    /// Pair constraints, using indices.
125    pub pairs: Vec<(usize, usize)>,
126    /// Whether triangle cycles are allowed.
127    pub no_triangles: bool,
128    /// Whether any shortest cycle for any vertex must be 4 or less.
129    pub meet_quad: bool,
130    /// Whether any node can be reached from any other node.
131    pub connected: bool,
132    /// Whether commutativity/anticommutativity is enabled for quads.
133    ///
134    /// When a quad commutes, the edges along one dimension have same colors.
135    /// When a quad anticommutes, the edges along one dimension have same colors,
136    /// but with an odd number of positive and negative signs (1+3 or 3+1).
137    ///
138    /// It is assumed that even and odd colors for edges
139    /// above `2` anticommutes, e.g. `2` and `3` anticommutes.
140    ///
141    /// - When set to `Some(true)`, every quad commutes.
142    /// - When set to `Some(false)`, every quad anticommutes.
143    /// - When set to `None`
144    pub commute_quad: Option<bool>,
145    cache_has_triangles: std::cell::Cell<bool>,
146    cache_connected: std::cell::Cell<bool>,
147    cache_upper_triangle_disconnected: std::cell::Cell<bool>,
148    cache_commute_quad_satisfied: std::cell::Cell<bool>,
149    cache_node_satisfied: Vec<std::cell::Cell<bool>>,
150}
151
152impl Puzzle for Graph {
153    type Pos = (usize, usize);
154    type Val = Color;
155    fn set(&mut self, (i, j): (usize, usize), val: Color) {
156        let old = if j <= i {self.edges[i][j]} else {self.edges[j][i]};
157        if j <= i {self.edges[i][j] = val} else {self.edges[j][i] = val}
158        if old != 0 && val < 2 {
159            self.cache_connected.set(false);
160            self.cache_upper_triangle_disconnected.set(false);
161        }
162        if !(old == 0 && val == 1) {
163            self.cache_commute_quad_satisfied.set(false);
164        }
165        if old != 0 {
166            self.cache_has_triangles.set(false);
167            self.cache_node_satisfied[i].set(false);
168            self.cache_node_satisfied[j].set(false);
169        }
170    }
171    fn get(&self, (i, j): (usize, usize)) -> Color {
172        if j <= i {self.edges[i][j]} else {self.edges[j][i]}
173    }
174    fn print(&self) {
175        for i in 0..self.nodes.len() {
176            eprint!("{} ", self.nodes[i].color);
177        }
178        eprintln!("\n========================================");
179        for i in 0..self.nodes.len() {
180            for j in 0..self.nodes.len() {
181                eprint!("{} ", self.get((i, j)));
182            }
183            eprintln!("");
184        }
185    }
186    fn solve_simple<F: FnMut(&mut Self, Self::Pos, Self::Val)>(&mut self, mut f: F) {
187        let n = self.nodes.len();
188        for i in 0..n {
189            for j in i+1..n {
190                let colors = self.colors((i, j));
191                if colors.len() == 1 {
192                    f(self, (i, j), colors[0]);
193                }
194            }
195        }
196    }
197    fn is_solved(&self) -> bool {
198        self.all_satisfied() &&
199        self.pairs_satisfied() &&
200        if self.no_triangles {!self.has_triangles()} else {true} &&
201        if self.connected {self.is_connected()} else {true} &&
202        if let Some(val) = self.commute_quad {self.commute_quad_satisfied(val)} else {true} &&
203        if self.meet_quad {self.meet_quad_satisfied()} else {true}
204    }
205    fn remove(&mut self, other: &Graph) {
206        let n = self.nodes.len();
207        for i in 0..n {
208            for j in i..n {
209                if other.get((i, j)) != 0 {
210                    self.set((i, j), 0);
211                }
212            }
213        }
214    }
215}
216
217impl Default for Graph {
218    fn default() -> Graph {Graph::new()}
219}
220
221impl Graph {
222    /// Creates a new graph.
223    ///
224    /// Initialized with these default settings:
225    /// - no-triangles: false
226    /// - meet-quad: false
227    /// - connected: false
228    pub fn new() -> Graph {
229        Graph {
230            nodes: vec![],
231            edges: vec![],
232            pairs: vec![],
233            no_triangles: false,
234            meet_quad: false,
235            connected: false,
236            commute_quad: None,
237            cache_has_triangles: std::cell::Cell::new(false),
238            cache_connected: std::cell::Cell::new(false),
239            cache_upper_triangle_disconnected: std::cell::Cell::new(false),
240            cache_commute_quad_satisfied: std::cell::Cell::new(false),
241            cache_node_satisfied: vec![],
242        }
243    }
244
245    /// Generates a GraphViz dot format.
246    pub fn graphviz(&self, layout: &str, node_colors: &[&str], edge_colors: &[&str]) -> String {
247        use std::fmt::Write;
248
249        let mut s = String::new();
250        writeln!(&mut s, "strict graph {{").unwrap();
251        writeln!(&mut s, "  layout={}; edge[penwidth=4]", layout).unwrap();
252        for i in 0..self.nodes.len() {
253            writeln!(&mut s, "  {}[regular=true,style=filled,fillcolor={}];", i,
254                   node_colors[self.nodes[i].color as usize % node_colors.len()]).unwrap();
255        }
256        for i in 0..self.nodes.len() {
257            for (j, &ed) in self.edges[i].iter().enumerate() {
258                if ed < 2 {continue};
259                writeln!(&mut s, "  {} -- {}[color={}];", i, j,
260                edge_colors[(ed - 2) as usize % edge_colors.len()]).unwrap();
261            }
262        }
263        writeln!(&mut s, "}}").unwrap();
264        s
265    }
266
267    /// Finds the first empty edge.
268    pub fn fst_empty(&self) -> Option<(usize, usize)> {
269        let n = self.nodes.len();
270        for i in 0..n {
271            for j in i..n {
272                let s = self.colors((i, j)).len();
273                if s == 0 {continue};
274                if self.get((i, j)) == 0 {
275                    return Some((i, j));
276                }
277            }
278        }
279        None
280    }
281
282    /// Finds the edge with the least possible colors.
283    pub fn min_colors(&self) -> Option<(usize, usize)> {
284        let mut min: Option<(usize, usize, usize)> = None;
285        let n = self.nodes.len();
286        'outer: for i in 0..n {
287            for j in i..n {
288                let s = self.colors((i, j)).len();
289                if s == 0 {continue};
290                if min.is_none() || min.unwrap().2 > s {
291                    min = Some((i, j, s));
292                    if s == 1 {break 'outer}
293                }
294            }
295        }
296        min.map(|n| (n.0, n.1))
297    }
298
299    /// Solves the graph puzzle using default strategy.
300    ///
301    /// The default strategy is `Graph::min_colors, Graph::colors`.
302    pub fn solve(self, solve_settings: SolveSettings) -> Option<Solution<Graph>> {
303        let solver = BackTrackSolver::new(self, solve_settings);
304        solver.solve(
305            Graph::min_colors,
306            Graph::colors
307        )
308    }
309
310    /// Adds a node description.
311    pub fn push(&mut self, node: Node) {
312        self.nodes.push(node);
313        self.edges.push(vec![0; self.nodes.len()]);
314        self.cache_node_satisfied.push(std::cell::Cell::new(false));
315    }
316
317    /// Adds a pair constraint.
318    pub fn push_pair(&mut self, (i, j): (usize, usize)) {
319        self.pairs.push((i.min(j), i.max(j)));
320    }
321
322    /// Returns a list of edge constraints that makes a node unsatisfied.
323    ///
324    /// If the returned list is empty, then the node is satisfied.
325    pub fn node_satisfied(&self, i: usize) -> Vec<Constraint> {
326        if self.cache_node_satisfied[i].get() {return vec![]};
327        let mut res = vec![];
328        let mut m = vec![false; self.nodes[i].edges.len()];
329        for j in 0..self.nodes.len() {
330            let edge = self.get((i, j));
331            if edge == 0 {continue};
332            for k in 0..m.len() {
333                if m[k] {continue};
334                let con = &self.nodes[i].edges[k];
335                if con.edge == edge &&
336                   con.node == self.nodes[j].color
337                {
338                    m[k] = true;
339                    break;
340                }
341            }
342        }
343        for k in 0..m.len() {
344            if !m[k] {
345                res.push(self.nodes[i].edges[k].clone());
346            }
347        }
348        if res.len() == 0 {
349            self.cache_node_satisfied[i].set(true);
350        }
351        res
352    }
353
354    /// Returns `true` if all nodes are satisfied.
355    pub fn all_satisfied(&self) -> bool {
356        for i in 0..self.nodes.len() {
357            if self.node_satisfied(i).len() != 0 {return false}
358        }
359        true
360    }
361
362    /// Returns `true` if all pair constraints are satisfied.
363    pub fn pairs_satisfied(&self) -> bool {
364        for &(i, j) in &self.pairs {
365            if self.edges[j][i] < 2 {return false}
366        }
367        true
368    }
369
370    /// Returns whether the graph contains triangles.
371    pub fn has_triangles(&self) -> bool {
372        if self.cache_has_triangles.get() {return true};
373        let n = self.nodes.len();
374        for i in 0..n {
375            for j in i+1..n {
376                if self.get((i, j)) < 2 {continue};
377                for k in j+1..n {
378                    if self.get((j, k)) >= 2 &&
379                       self.get((i, k)) >= 2
380                    {
381                        self.cache_has_triangles.set(true);
382                        return true
383                    }
384                }
385            }
386        }
387        false
388    }
389
390    /// Returns `true` when for any node,
391    /// the greatest shortest cycle is either 3 or 4.
392    pub fn meet_quad_satisfied(&self) -> bool {
393        let n = self.nodes.len();
394        for i in 0..n {
395            let mut found = false;
396            'outer: for j in 0..n {
397                if i == j {continue};
398                if self.get((i, j)) < 2 {continue};
399                for k in j+1..n {
400                    if k == i {continue};
401                    if self.get((j, k)) < 2 &&
402                       self.get((i, k)) < 2 {continue};
403                    if self.get((j, k)) >= 2 &&
404                       self.get((i, k)) >= 2 {
405                        // Triangle.
406                        found = true;
407                        break 'outer;
408                    }
409                    for k2 in 0..n {
410                        if k2 == i || k2 == j || k2 == k {continue};
411                        if self.get((k, k2)) >= 2 &&
412                           (
413                            self.get((j, k)) >= 2 &&
414                            self.get((i, k2)) >= 2 ||
415                            self.get((i, k)) >= 2 &&
416                            self.get((j, k2)) >= 2
417                           )
418                        {
419                            found = true;
420                            break 'outer;
421                        }
422                    }
423                }
424            }
425
426            if !found {
427                return false
428            }
429        }
430        true
431    }
432
433    /// Returns `true` when for any quad,
434    /// the commute property is satisfied.
435    ///
436    /// For more information, see `Graph::commute`.
437    pub fn commute_quad_satisfied(&self, commute: bool) -> bool {
438        if self.cache_commute_quad_satisfied.get() {return true};
439        let n = self.nodes.len();
440        for i in 0..n {
441            for j in 0..n {
442                if i == j {continue};
443                if self.get((i, j)) < 2 {continue};
444                for k in j+1..n {
445                    if k == i {continue};
446                    if self.get((j, k)) < 2 &&
447                       self.get((i, k)) < 2 {continue};
448                    for k2 in 0..n {
449                        if k2 == i || k2 == j || k2 == k {continue};
450                        if self.get((k, k2)) >= 2 &&
451                           self.get((j, k)) >= 2 &&
452                           self.get((i, k2)) >= 2
453                        {
454                            let s = if commute {
455                                self.get((i, j)) == self.get((k, k2)) &&
456                                self.get((i, k2)) == self.get((j, k))
457                            } else {
458                                let ij = self.get((i, j));
459                                let jk = self.get((j, k));
460                                let kk2 = self.get((k, k2));
461                                let ik2 = self.get((i, k2));
462                                let x0 = (ij ^ 1) == kk2;
463                                let x1 = ij == kk2;
464                                let y0 = (jk ^ 1) == ik2;
465                                let y1 = jk == ik2;
466                                if (x0 ^ x1) && (y0 ^ y1) {x0 ^ y0} else {false}
467                            };
468                            if !s {return false}
469                        } else if self.get((k, k2)) >= 2 &&
470                                  self.get((i, k)) >= 2 &&
471                                  self.get((j, k2)) >= 2
472                        {
473                            let s = if commute {
474                                self.get((i, k)) == self.get((j, k2)) &&
475                                self.get((i, j)) == self.get((k, k2))
476                            } else {
477                                let ik = self.get((i, k));
478                                let ij = self.get((i, j));
479                                let jk2 = self.get((j, k2));
480                                let kk2 = self.get((k, k2));
481                                let x0 = (ik ^ 1) == jk2;
482                                let x1 = ik == jk2;
483                                let y0 = (ij ^ 1) == kk2;
484                                let y1 = ij == kk2;
485                                if (x0 ^ x1) && (y0 ^ y1) {x0 ^ y0} else {false}
486                            };
487                            if !s {return false}
488                        }
489                    }
490                }
491            }
492        }
493        self.cache_commute_quad_satisfied.set(true);
494        true
495    }
496
497    /// Returns `true` if all nodes can be reached from any node.
498    pub fn is_connected(&self) -> bool {
499        if self.cache_connected.get() {return true};
500        let n = self.nodes.len();
501        let mut reachable = vec![false; n];
502        for i in 0..n {
503            if self.get((0, i)) >= 2 {
504                reachable[i] = true;
505            }
506        }
507        loop {
508            let mut changed = false;
509            for i in 0..n {
510                if !reachable[i] {
511                    for j in 0..n {
512                        if reachable[j] && self.get((i, j)) >= 2 {
513                            reachable[i] = true;
514                            changed = true;
515                            break;
516                        }
517                    }
518                }
519            }
520            if !changed {break}
521        }
522
523        let val = reachable.iter().all(|&b| b);
524        if val {self.cache_connected.set(true)};
525        val
526    }
527
528    /// Returns `true` if no-edges covers the upper right rectangle of the matrix form.
529    ///
530    /// This means that the graph will be disconnected.
531    pub fn is_upper_right_disconnected(&self) -> bool {
532        if self.cache_upper_triangle_disconnected.get() {return true};
533        let n = self.nodes.len();
534        if n % 2 != 0 {return false}
535        for i in 0..n/2 {
536            for j in n/2..n {
537                if i == j {continue}
538                if self.get((i, j)) != 1 {return false}
539            }
540        }
541        self.cache_upper_triangle_disconnected.set(true);
542        true
543    }
544
545    /// Returns a list of possible actions for a node.
546    pub fn colors(&self, (i, j): (usize, usize)) -> Vec<Color> {
547        if self.get((i, j)) != 0 {return vec![]};
548        if !self.nodes[i].self_connected && i == j {return vec![]};
549        if self.no_triangles && self.has_triangles() {return vec![]};
550        if self.connected && self.is_upper_right_disconnected() {return vec![]};
551        if let Some(val) = self.commute_quad {if !self.commute_quad_satisfied(val) {return vec![]}};
552        let mut res = vec![];
553        let errors = self.node_satisfied(i);
554        let other_errors = self.node_satisfied(j);
555        for err in &errors {
556            if err.node != self.nodes[j].color {continue}
557            for other_err in &other_errors {
558                if err.edge == other_err.edge &&
559                   other_err.node == self.nodes[i].color
560                {
561                    res.push(err.edge);
562                    break;
563                }
564            }
565        }
566        res.push(1);
567        res.sort();
568        res.dedup();
569        res
570    }
571}
572
573/// Stores edge constraint.
574#[derive(Copy, Clone, Debug, PartialEq, Eq)]
575pub struct Constraint {
576    /// The edge color.
577    pub edge: Color,
578    /// The node color.
579    pub node: Color,
580}
581
582/// Stores a description of a node.
583#[derive(Clone, Debug, PartialEq, Eq)]
584pub struct Node {
585    /// The color of the node.
586    pub color: Color,
587    /// Whether the node can be self-connected.
588    pub self_connected: bool,
589    /// The edges constraints of the node.
590    pub edges: Vec<Constraint>,
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn simple1() {
599        let mut g = Graph::new();
600        let a = Node {
601            color: 1,
602            self_connected: false,
603            edges: vec![Constraint {edge: 2, node: 1}],
604        };
605        assert_eq!(g.nodes.len(), 0);
606        g.push(a.clone());
607        assert_eq!(g.node_satisfied(0), vec![
608            Constraint {edge: 2, node: 1}
609        ]);
610        g.push(a.clone());
611        assert_eq!(g.node_satisfied(0), vec![
612            Constraint {edge: 2, node: 1}
613        ]);
614        assert_eq!(g.node_satisfied(1), vec![
615            Constraint {edge: 2, node: 1}
616        ]);
617        assert_eq!(g.colors((0, 1)), vec![1, 2]);
618        g.set((0, 1), 2);
619        assert_eq!(g.node_satisfied(0), vec![]);
620        g.set((0, 1), 2);
621        assert!(g.all_satisfied());
622    }
623}