├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── le.rs ├── magic_square.rs ├── prime.rs └── walk.rs └── src └── lib.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: bvssvni 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linear_solver" 3 | version = "0.2.2" 4 | authors = ["Sven Nilsen "] 5 | edition = "2018" 6 | keywords = ["linear", "solver", "theorem", "proving", "reasoning"] 7 | description = "A linear solver designed to be easy to use with Rust enum expressions" 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/advancedresearch/linear_solver.git" 11 | homepage = "https://github.com/advancedresearch/linear_solver" 12 | documentation = "https://docs.rs/linear_solver" 13 | categories = ["algorithms", "science"] 14 | 15 | [dependencies] 16 | cuckoofilter = "0.3.2" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 advancedresearch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linear_solver 2 | A linear solver designed to be easy to use with Rust enums. 3 | 4 | This is a library for automated theorem proving. 5 | 6 | Linear solving means that some facts can be replaced with others. 7 | This technique can also be used to make theorem proving more efficient. 8 | 9 | If you are looking for a solver that does not remove facts, 10 | see [monotonic_solver](https://github.com/advancedresearch/monotonic_solver) 11 | 12 | *Notice! This solver does not support multiple histories. 13 | It assumes that when facts are simplified, 14 | they prove the same set of facts without the simplifaction.* 15 | 16 | A linear solver can be used to: 17 | 18 | - Prove some things in linear logic 19 | - Prove some things in classical logic more efficiently 20 | - Prove some things about where resources are "consumed" 21 | - Constraint solving 22 | - Implement some constraint solving programming language 23 | 24 | This project was heavily inspired by 25 | [CHR (Constraint Handling Rules)](https://dtai.cs.kuleuven.be/CHR/) 26 | 27 | ### Example: Walk 28 | 29 | ```rust 30 | /* 31 | 32 | In this example, we reduce a walk (left, right, up, down): 33 | 34 | l, l, u, l, r, d, d, r 35 | ---------------------- 36 | l, u 37 | 38 | */ 39 | 40 | extern crate linear_solver; 41 | 42 | use linear_solver::{solve_minimum, Inference}; 43 | use linear_solver::Inference::*; 44 | 45 | use std::collections::HashSet; 46 | 47 | use self::Expr::*; 48 | 49 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 50 | pub enum Expr { 51 | Left, 52 | Right, 53 | Up, 54 | Down, 55 | } 56 | 57 | pub fn infer(cache: &HashSet, _facts: &[Expr]) -> Option> { 58 | // Put simplification rules first to find simplest set of facts. 59 | if cache.contains(&Left) && cache.contains(&Right) { 60 | return Some(ManyTrue {from: vec![Left, Right]}); 61 | } 62 | if cache.contains(&Up) && cache.contains(&Down) { 63 | return Some(ManyTrue {from: vec![Up, Down]}); 64 | } 65 | None 66 | } 67 | 68 | fn main() { 69 | let start = vec![ 70 | Left, 71 | Left, 72 | Up, 73 | Left, 74 | Right, 75 | Down, 76 | Down, 77 | Right, 78 | ]; 79 | 80 | let res = solve_minimum(start, infer); 81 | for i in 0..res.len() { 82 | println!("{:?}", res[i]); 83 | } 84 | } 85 | ``` 86 | 87 | ### Example: Less or Equal 88 | 89 | ```rust 90 | /* 91 | 92 | In this example, we prove the following: 93 | 94 | X <= Y 95 | Y <= Z 96 | Z <= X 97 | ------ 98 | Y = Z 99 | Y = X 100 | 101 | */ 102 | 103 | extern crate linear_solver; 104 | 105 | use linear_solver::{solve_minimum, Inference}; 106 | use linear_solver::Inference::*; 107 | 108 | use std::collections::HashSet; 109 | 110 | use self::Expr::*; 111 | 112 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 113 | pub enum Expr { 114 | Var(&'static str), 115 | Le(Box, Box), 116 | Eq(Box, Box), 117 | } 118 | 119 | pub fn infer(cache: &HashSet, facts: &[Expr]) -> Option> { 120 | // Put simplification rules first to find simplest set of facts. 121 | for ea in facts { 122 | if let Le(ref a, ref b) = *ea { 123 | if a == b { 124 | // (X <= X) <=> true 125 | return Some(OneTrue {from: ea.clone()}); 126 | } 127 | 128 | for eb in facts { 129 | if let Le(ref c, ref d) = *eb { 130 | if a == d && b == c { 131 | // (X <= Y) ∧ (Y <= X) <=> (X = Y) 132 | return Some(Inference::replace( 133 | vec![ea.clone(), eb.clone()], 134 | Eq(a.clone(), b.clone()), 135 | cache 136 | )) 137 | } 138 | } 139 | } 140 | } 141 | 142 | if let Eq(ref a, ref b) = *ea { 143 | for eb in facts { 144 | if let Le(ref c, ref d) = *eb { 145 | if c == b { 146 | // (X = Y) ∧ (Y <= Z) <=> (X = Y) ∧ (X <= Z) 147 | return Some(Inference::replace_one( 148 | eb.clone(), 149 | Le(a.clone(), d.clone()), 150 | cache 151 | )); 152 | } else if d == b { 153 | // (X = Y) ∧ (Z <= Y) <=> (X = Y) ∧ (Z <= X) 154 | return Some(Inference::replace_one( 155 | eb.clone(), 156 | Le(c.clone(), a.clone()), 157 | cache 158 | )); 159 | } 160 | } 161 | 162 | if let Eq(ref c, ref d) = *eb { 163 | if b == c { 164 | // (X = Y) ∧ (Y = Z) <=> (X = Y) ∧ (X = Z) 165 | return Some(Inference::replace_one( 166 | eb.clone(), 167 | Eq(a.clone(), d.clone()), 168 | cache 169 | )); 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | // Put propagation rules last to find simplest set of facts. 177 | for ea in facts { 178 | if let Le(ref a, ref b) = *ea { 179 | for eb in facts { 180 | if let Le(ref c, ref d) = *eb { 181 | if b == c { 182 | // (X <= Y) ∧ (Y <= Z) => (X <= Z) 183 | let new_expr = Le(a.clone(), d.clone()); 184 | if !cache.contains(&new_expr) {return Some(Propagate(new_expr))}; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | None 191 | } 192 | 193 | pub fn var(name: &'static str) -> Box {Box::new(Var(name))} 194 | 195 | fn main() { 196 | let start = vec![ 197 | Le(var("X"), var("Y")), // X <= Y 198 | Le(var("Y"), var("Z")), // Y <= Z 199 | Le(var("Z"), var("X")), // Z <= X 200 | ]; 201 | 202 | let res = solve_minimum(start, infer); 203 | for i in 0..res.len() { 204 | println!("{:?}", res[i]); 205 | } 206 | } 207 | ``` 208 | 209 | ### Linear logic 210 | 211 | When some facts are simplified, e.g.: 212 | 213 | ```text 214 | X, Y <=> Z 215 | ``` 216 | 217 | You need two new copies of `X` and `Y` to infer another `Z`. 218 | This is because the solver does not remove all copies. 219 | 220 | When doing classical theorem proving with a linear solver, 221 | it is common to check that every fact is unique in the input, 222 | and that the `cache` is checked before adding new facts. 223 | This ensures that the solver does not add redundant facts. 224 | 225 | However, when doing linear theorem proving, 226 | one can generate redundant facts `Z` for every `X` and `Y`. 227 | 228 | ### Meaning of goals 229 | 230 | Since a linear solver can both introduce new facts 231 | and remove them, it means that termination in the sense of proving a 232 | goal does not make sense, since the goal can be removed later. 233 | 234 | Instead, a goal is considered proved when it belongs to the same "cycle". 235 | This is the repeated list of sets of facts that follows from 236 | using a deterministic solver with rules that stops expanding. 237 | 238 | The minimum set of facts in the cycle is considered the implicit goal, 239 | because all the other facts in the cycle can be inferred from 240 | this set of facts. 241 | 242 | Notice that this a minimum set of facts in a cycle is different 243 | from a minimum set of axioms. A minimum set of axioms is a set of facts 244 | that proves a minimum set of facts with even fewer facts. 245 | With other words, the minimum set of axioms starts outside the cycle. 246 | When it moves inside the cycle, it is identical to some minimum set of facts. 247 | 248 | Both the minimum set of facts and the minimum set of axioms can be used 249 | to identify an equivalence between two sets of facts. 250 | 251 | ### Intuition of `false` and `true` 252 | 253 | The intuition of `false` can be thought of as: 254 | 255 | - Some fact which everything can be proven from 256 | - Some fact which every contradiction can be simplified to 257 | - A language that contains a contradiction for every truth value 258 | 259 | The minimum set of facts in a such language, 260 | when a cycle contains `false`, is `false`. 261 | 262 | The intuition of `true` can be thought of as: 263 | 264 | - Some fact which every initial fact can be proven from. 265 | - Some fact that contradicts `false` 266 | 267 | This means that the initial facts implies `true` and 268 | since it contradicts `false`, if there exists a contradiction 269 | in the initial facts, then they can prove `false`. 270 | 271 | Therefore a proof from initial facts is `true` 272 | if it's minimum set of facts does not equals `false`. 273 | -------------------------------------------------------------------------------- /examples/le.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | In this example, we prove the following: 4 | 5 | X <= Y 6 | Y <= Z 7 | Z <= X 8 | ------ 9 | Y = Z 10 | Y = X 11 | 12 | */ 13 | 14 | extern crate linear_solver; 15 | 16 | use linear_solver::{solve_minimum, Inference}; 17 | use linear_solver::Inference::*; 18 | 19 | use std::collections::HashSet; 20 | 21 | use self::Expr::*; 22 | 23 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 24 | pub enum Expr { 25 | Var(&'static str), 26 | Le(Box, Box), 27 | Eq(Box, Box), 28 | } 29 | 30 | pub fn infer(cache: &HashSet, facts: &[Expr]) -> Option> { 31 | // Put simplification rules first to find simplest set of facts. 32 | for ea in facts { 33 | if let Le(ref a, ref b) = *ea { 34 | if a == b { 35 | // (X <= X) <=> true 36 | return Some(OneTrue {from: ea.clone()}); 37 | } 38 | 39 | for eb in facts { 40 | if let Le(ref c, ref d) = *eb { 41 | if a == d && b == c { 42 | // (X <= Y) ∧ (Y <= X) <=> (X = Y) 43 | return Some(Inference::replace( 44 | vec![ea.clone(), eb.clone()], 45 | Eq(a.clone(), b.clone()), 46 | cache 47 | )) 48 | } 49 | } 50 | } 51 | } 52 | 53 | if let Eq(ref a, ref b) = *ea { 54 | for eb in facts { 55 | if let Le(ref c, ref d) = *eb { 56 | if c == b { 57 | // (X = Y) ∧ (Y <= Z) <=> (X = Y) ∧ (X <= Z) 58 | return Some(Inference::replace_one( 59 | eb.clone(), 60 | Le(a.clone(), d.clone()), 61 | cache 62 | )); 63 | } else if d == b { 64 | // (X = Y) ∧ (Z <= Y) <=> (X = Y) ∧ (Z <= X) 65 | return Some(Inference::replace_one( 66 | eb.clone(), 67 | Le(c.clone(), a.clone()), 68 | cache 69 | )); 70 | } 71 | } 72 | 73 | if let Eq(ref c, ref d) = *eb { 74 | if b == c { 75 | // (X = Y) ∧ (Y = Z) <=> (X = Y) ∧ (X = Z) 76 | return Some(Inference::replace_one( 77 | eb.clone(), 78 | Eq(a.clone(), d.clone()), 79 | cache 80 | )); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | // Put propagation rules last to find simplest set of facts. 88 | for ea in facts { 89 | if let Le(ref a, ref b) = *ea { 90 | for eb in facts { 91 | if let Le(ref c, ref d) = *eb { 92 | if b == c { 93 | // (X <= Y) ∧ (Y <= Z) => (X <= Z) 94 | let new_expr = Le(a.clone(), d.clone()); 95 | if !cache.contains(&new_expr) {return Some(Propagate(new_expr))}; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | None 102 | } 103 | 104 | pub fn var(name: &'static str) -> Box {Box::new(Var(name))} 105 | 106 | fn main() { 107 | let start = vec![ 108 | Le(var("X"), var("Y")), // X <= Y 109 | Le(var("Y"), var("Z")), // Y <= Z 110 | Le(var("Z"), var("X")), // Z <= X 111 | ]; 112 | 113 | let res = solve_minimum(start, infer); 114 | for i in 0..res.len() { 115 | println!("{:?}", res[i]); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/magic_square.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | In this example, we will prove the following solutions of the 3x3 magic square: 4 | 5 | |2|7|6| |4|3|8| |4|9|c| |6|1|8| |6|7|2| |8|1|6| |8|3|4| 6 | |9|5|1| |9|5|1| |3|5|7| |7|5|3| |1|5|9| |3|5|7| |1|5|9| 7 | |4|3|8| |2|7|6| |8|1|6| |2|9|4| |8|3|4| |4|9|2| |6|7|2| 8 | 9 | */ 10 | 11 | extern crate linear_solver; 12 | 13 | use linear_solver::{solve_minimum, Inference}; 14 | use linear_solver::Inference::*; 15 | 16 | use std::collections::HashSet; 17 | 18 | use self::Expr::*; 19 | 20 | /// Stores expression. 21 | #[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] 22 | pub enum Expr { 23 | /// The proof is false. 24 | False, 25 | /// Constant. 26 | Const(u8), 27 | /// Variable. 28 | Var(&'static str), 29 | /// An equation of the form `a + b + ... = d + e + ...`. 30 | Sum(Vec, Vec), 31 | /// Sorts equations internally and on both sides. 32 | SortAll, 33 | // Expands equations by equality of each side. 34 | ExpandAll, 35 | /// Subtract constants on both sides of equation. 36 | SubtractConstants, 37 | /// Remove equations of the form `a = a`. 38 | RemoveRefl, 39 | /// Remove equal terms on both sides `(a + b = a + c) => (b = c)`. 40 | RemoveEqualTermsOnBothSides, 41 | /// Insert assignments e.g `a = 3` into `a + b = 5` = `3 + b = 5`. 42 | InsertAssignments, 43 | /// Check contradicting constants, e.g. `3 = 5`. 44 | CheckContradictingConstants, 45 | /// Require that there are no negative numbers. 46 | AbsoluteNumbers, 47 | /// Sum up constants, e.g. `3 + 5 + a` becomes `8 + a`. 48 | SumConstants, 49 | /// Specify a range for a variable. 50 | Range {var: &'static str, start: u8, end: u8}, 51 | /// Check that an assignment is within a range. 52 | CheckRange, 53 | /// Check that all variables are assigned different values. 54 | UniqueAssignments, 55 | /// Remove range when variable is assigned. 56 | /// This is just to clean up result. 57 | /// Must be executed after the range has been checked. 58 | RemoveRangeWhenAssigned, 59 | /// Generate alternatives for a variable by 60 | /// recursive theorem proving by using the range. 61 | Narrow(&'static str), 62 | /// Stores alternatives for a variable. 63 | Alternatives(&'static str, Vec), 64 | } 65 | 66 | impl Expr { 67 | /// Returns assignment. 68 | pub fn assignment(&self) -> Option<(&'static str, u8)> { 69 | if let Sum(ref ls, ref rs) = *self { 70 | if ls.len() == 1 { 71 | if let Var(a) = ls[0] { 72 | if rs.len() == 1 { 73 | if let Const(x) = rs[0] {return Some((a, x))} 74 | } else if rs.len() == 0 {return Some((a, 0))} 75 | } 76 | } 77 | } 78 | None 79 | } 80 | } 81 | 82 | pub fn infer(cache: &HashSet, facts: &[Expr]) -> Option> { 83 | if cache.contains(&False) {return None}; 84 | 85 | // Put simplification rules first to find simplest set of facts. 86 | 87 | // Sorting makes it easier for rules to do their job, 88 | // and it makes the output easier to read. 89 | // Wait for `ExpandAll` to finish to avoid premature cycle detection. 90 | if cache.contains(&SortAll) && !cache.contains(&ExpandAll) { 91 | for ea in facts { 92 | if let Sum(ref ls, ref rs) = *ea { 93 | // Sort terms on left and right side. 94 | let mut sorted_ls = ls.clone(); 95 | sorted_ls.sort(); 96 | let mut sorted_rs = rs.clone(); 97 | sorted_rs.sort(); 98 | if &sorted_ls != ls || &sorted_rs != rs { 99 | let new_expr = Sum(sorted_ls, sorted_rs); 100 | return Some(SimplifyOne {from: ea.clone(), to: new_expr}); 101 | } 102 | } 103 | 104 | if let Sum(ref ls, ref rs) = *ea { 105 | // Reorder left and right side. 106 | if ls < rs { 107 | return Some(Inference::replace_one( 108 | ea.clone(), 109 | Sum(rs.clone(), ls.clone()), 110 | cache 111 | )); 112 | } 113 | } 114 | } 115 | } 116 | 117 | // Wait for `ExpandAll` to finish so a cycle detection is not triggered prematurely. 118 | if !cache.contains(&ExpandAll) { 119 | 120 | if cache.contains(&CheckRange) { 121 | for ea in facts { 122 | if let Range {var, start, end} = *ea { 123 | for eb in facts { 124 | if let Some((a, x)) = eb.assignment() { 125 | if var == a && (x < start || x > end) { 126 | return Some(Propagate(False)); 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | if cache.contains(&UniqueAssignments) { 135 | let mut vars = vec![]; 136 | let mut rss = vec![]; 137 | // Find all isolated variables. 138 | for ea in facts { 139 | if let Sum(ref ls, ref rs) = *ea { 140 | if ls.len() == 1 { 141 | if let Var(a) = ls[0] { 142 | vars.push(a); 143 | rss.push(rs.clone()); 144 | } 145 | } 146 | } 147 | } 148 | 149 | // Check for other variables 150 | for i in 0..vars.len() { 151 | let var = vars[i]; 152 | for j in 0..vars.len() { 153 | if vars[j] != var { 154 | if rss[j] == rss[i] { 155 | return Some(Propagate(False)); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | for ea in facts { 163 | 164 | if cache.contains(&RemoveRefl) { 165 | if let Sum(ref ls, ref rs) = *ea { 166 | if ls == rs { 167 | return Some(OneTrue {from: ea.clone()}); 168 | } 169 | } 170 | } 171 | 172 | if cache.contains(&RemoveRangeWhenAssigned) { 173 | if let Some((a, _)) = ea.assignment() { 174 | for eb in facts { 175 | if let Range {var, ..} = *eb { 176 | if var == a { 177 | return Some(OneTrue {from: eb.clone()}); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | if cache.contains(&CheckContradictingConstants) { 185 | if let Sum(ref ls, ref rs) = *ea { 186 | if rs.len() == 0 && ls.len() == 1 { 187 | if let Const(x) = ls[0] { 188 | if x != 0 { 189 | return Some(Propagate(False)); 190 | } 191 | } 192 | } 193 | } 194 | } 195 | 196 | if cache.contains(&AbsoluteNumbers) { 197 | if let Sum(ref ls, ref rs) = *ea { 198 | if rs.len() == 0 && ls.len() == 2 { 199 | if let Const(x) = ls[0] { 200 | if let Var(_) = ls[1] { 201 | if x != 0 { 202 | return Some(Propagate(False)); 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | 210 | if cache.contains(&SumConstants) { 211 | if let Sum(ref ls, ref rs) = *ea { 212 | let mut sum = 0; 213 | let mut count = 0; 214 | for i in 0..ls.len() { 215 | if let Const(x) = ls[i] { 216 | sum += x; 217 | count += 1; 218 | } 219 | } 220 | if count > 1 { 221 | let mut new_ls = vec![]; 222 | for i in 0..ls.len() { 223 | if let Const(_) = ls[i] {continue} 224 | new_ls.push(ls[i].clone()); 225 | } 226 | new_ls.push(Const(sum)); 227 | return Some(Inference::replace_one( 228 | ea.clone(), 229 | Sum(new_ls, rs.clone()), 230 | cache 231 | )); 232 | } 233 | } 234 | } 235 | 236 | if cache.contains(&RemoveEqualTermsOnBothSides) { 237 | if let Sum(ref ls, ref rs) = *ea { 238 | for i in 0..ls.len() { 239 | for j in 0..rs.len() { 240 | if ls[i] == rs[j] { 241 | let mut new_ls = vec![]; 242 | for k in 0..ls.len() { 243 | if k == i {continue} else {new_ls.push(ls[k].clone())} 244 | } 245 | let mut new_rs = vec![]; 246 | for k in 0..rs.len() { 247 | if k == j {continue} else {new_rs.push(rs[k].clone())} 248 | } 249 | return Some(Inference::replace_one( 250 | ea.clone(), 251 | Sum(new_ls, new_rs), 252 | cache 253 | )); 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | // Insert assignment into other equations. 261 | if cache.contains(&InsertAssignments) { 262 | if let Sum(ref ls, ref rs) = *ea { 263 | if ls.len() == 1 && rs.len() == 1 { 264 | if let Const(_) = rs[0] { 265 | for eb in facts { 266 | if ea == eb {continue}; 267 | if let Sum(ref ls2, ref rs2) = *eb { 268 | for i in 0..ls2.len() { 269 | if ls2[i] == ls[0] { 270 | let new_ls: Vec = ls2.clone().into_iter() 271 | .filter(|n| n != &ls[0]) 272 | .chain(rs.clone().into_iter()) 273 | .collect(); 274 | return Some(Inference::replace_one( 275 | eb.clone(), 276 | Sum(new_ls, rs2.clone()), 277 | cache 278 | )); 279 | } 280 | } 281 | } 282 | } 283 | } 284 | } 285 | } 286 | } 287 | 288 | // Subtract constants on both sides. 289 | if cache.contains(&SubtractConstants) { 290 | if let Sum(ref ls, ref rs) = *ea { 291 | for i in 0..ls.len() { 292 | for j in 0..rs.len() { 293 | if let (&Const(x), &Const(y)) = (&ls[i], &rs[j]) { 294 | let mut new_ls = vec![]; 295 | for k in 0..ls.len() { 296 | if k == i { 297 | if x == y {continue} 298 | else if x > y {new_ls.push(Const(x-y))} 299 | } else { 300 | new_ls.push(ls[k].clone()) 301 | } 302 | } 303 | let mut new_rs = vec![]; 304 | for k in 0..rs.len() { 305 | if k == j { 306 | if x == y {continue} 307 | else if y > x {new_rs.push(Const(y-x))} 308 | } else { 309 | new_rs.push(rs[k].clone()) 310 | } 311 | } 312 | return Some(Inference::replace_one( 313 | ea.clone(), 314 | Sum(new_ls, new_rs), 315 | cache 316 | )); 317 | } 318 | } 319 | } 320 | } 321 | } 322 | } 323 | } 324 | 325 | if cache.contains(&ExpandAll) { 326 | for ea in facts { 327 | if let Sum(ref ls, ref rs) = *ea { 328 | for eb in facts { 329 | if ea == eb {continue}; 330 | if let Sum(ref ls2, ref rs2) = *eb { 331 | if ls == ls2 { 332 | // X = Y & X = Z => Y = Z 333 | let new_expr = Sum(rs.clone(), rs2.clone()); 334 | if !cache.contains(&new_expr) { 335 | return Some(Propagate(new_expr)); 336 | } 337 | } 338 | if rs == rs2 { 339 | // X = Y & Z = Y => X = Z 340 | let new_expr = Sum(ls.clone(), ls2.clone()); 341 | if !cache.contains(&new_expr) { 342 | return Some(Propagate(new_expr)); 343 | } 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | // Consume `ExpandAll` to allow other simplifications to take place. 351 | return Some(OneTrue {from: ExpandAll}); 352 | } 353 | 354 | // Narrow down alternatives with recursive theorem proving. 355 | for ea in facts { 356 | // A unique alternative means there is an assignment. 357 | if let Alternatives(a, ref alternatives) = *ea { 358 | if alternatives.len() == 1 { 359 | return Some(Inference::replace_one( 360 | ea.clone(), 361 | Sum(vec![Var(a)], vec![Const(alternatives[0])]), 362 | cache 363 | )); 364 | } 365 | if alternatives.len() == 0 { 366 | return Some(Propagate(False)); 367 | } 368 | } 369 | 370 | if let Narrow(a) = *ea { 371 | for eb in facts { 372 | if let Range {var, start, end} = *eb { 373 | if var == a { 374 | // Try the whole range. 375 | // Call the solver recursively 376 | let mut alternatives = vec![]; 377 | for k in start..end+1 { 378 | let mut new_facts = vec![]; 379 | for i in 0..facts.len() { 380 | // Ignore `Narrow` directive to avoid infinite recursion. 381 | if &facts[i] == ea {continue}; 382 | new_facts.push(facts[i].clone()); 383 | } 384 | new_facts.push(Sum(vec![Var(var)], vec![Const(k)])); 385 | let res = solve_minimum(new_facts, infer); 386 | if !res.iter().any(|n| n == &False) { 387 | alternatives.push(k); 388 | } 389 | } 390 | let new_expr = Alternatives(a, alternatives); 391 | return Some(SimplifyOne {from: ea.clone(), to: new_expr}); 392 | } 393 | } 394 | } 395 | } 396 | } 397 | 398 | None 399 | } 400 | 401 | fn main() { 402 | let start = vec![ 403 | // a + b + c = 15 404 | Sum(vec![Var("a"), Var("b"), Var("c")], vec![Const(15)]), 405 | // d + e + f = 15 406 | Sum(vec![Var("d"), Var("e"), Var("f")], vec![Const(15)]), 407 | // g + h + i = 15 408 | Sum(vec![Var("g"), Var("h"), Var("i")], vec![Const(15)]), 409 | 410 | // a + d + g = 15 411 | Sum(vec![Var("a"), Var("d"), Var("g")], vec![Const(15)]), 412 | // b + e + h = 15 413 | Sum(vec![Var("b"), Var("e"), Var("h")], vec![Const(15)]), 414 | // c + f + i = 15 415 | Sum(vec![Var("c"), Var("f"), Var("i")], vec![Const(15)]), 416 | 417 | // a + e + i = 15 418 | Sum(vec![Var("a"), Var("e"), Var("i")], vec![Const(15)]), 419 | // c + e + g = 15 420 | Sum(vec![Var("c"), Var("e"), Var("g")], vec![Const(15)]), 421 | 422 | Range {var: "a", start: 1, end: 9}, 423 | Range {var: "b", start: 1, end: 9}, 424 | Range {var: "c", start: 1, end: 9}, 425 | Range {var: "d", start: 1, end: 9}, 426 | Range {var: "e", start: 1, end: 9}, 427 | Range {var: "f", start: 1, end: 9}, 428 | Range {var: "g", start: 1, end: 9}, 429 | Range {var: "h", start: 1, end: 9}, 430 | Range {var: "i", start: 1, end: 9}, 431 | 432 | // List of tactics. 433 | SortAll, 434 | ExpandAll, 435 | RemoveRefl, 436 | RemoveEqualTermsOnBothSides, 437 | SubtractConstants, 438 | InsertAssignments, 439 | CheckContradictingConstants, 440 | AbsoluteNumbers, 441 | SumConstants, 442 | CheckRange, 443 | UniqueAssignments, 444 | RemoveRangeWhenAssigned, 445 | 446 | // Uncomment/comment the following to investiage various solutions. 447 | 448 | /* 449 | // Get the alternative values that "a" can have. 450 | // Multiple `Narrow` means nested recursive theorem proving, 451 | // such that the top alternative is narrowed down. 452 | Narrow("a"), 453 | Narrow("b"), 454 | // Narrow("c"), // skip "c" because we don't need it. 455 | // Better to try "d", since it is better at narrowing down results. 456 | Narrow("d"), 457 | */ 458 | 459 | Sum(vec![Var("a")], vec![Const(2)]), 460 | Sum(vec![Var("b")], vec![Const(7)]), 461 | Narrow("d"), 462 | 463 | /* 464 | Sum(vec![Var("a")], vec![Const(4)]), 465 | Sum(vec![Var("b")], vec![Const(3)]), 466 | Narrow("d"), 467 | */ 468 | 469 | /* 470 | Sum(vec![Var("a")], vec![Const(4)]), 471 | Sum(vec![Var("b")], vec![Const(9)]), 472 | Narrow("d"), 473 | */ 474 | 475 | /* 476 | Sum(vec![Var("a")], vec![Const(6)]), 477 | Sum(vec![Var("b")], vec![Const(1)]), 478 | Narrow("d"), 479 | */ 480 | 481 | /* 482 | Sum(vec![Var("a")], vec![Const(6)]), 483 | Sum(vec![Var("b")], vec![Const(7)]), 484 | Narrow("d"), 485 | */ 486 | 487 | /* 488 | Sum(vec![Var("a")], vec![Const(8)]), 489 | Sum(vec![Var("b")], vec![Const(1)]), 490 | Narrow("d"), 491 | */ 492 | 493 | /* 494 | Sum(vec![Var("a")], vec![Const(8)]), 495 | Sum(vec![Var("b")], vec![Const(3)]), 496 | Narrow("d"), 497 | */ 498 | ]; 499 | 500 | let res = solve_minimum(start, infer); 501 | for i in 0..res.len() { 502 | println!("{:?}", res[i]); 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /examples/prime.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | In this example, we the algorithm Sieve of Eratosthenes to find prime numbers. 4 | The input are numbers `2, 3, 4, ... 99`. 5 | The output are prime numbers in that range. 6 | 7 | One only has to check divisibility by prime numbers, 8 | so all numbers that are not primes can be removed. 9 | 10 | */ 11 | 12 | extern crate linear_solver; 13 | 14 | use linear_solver::{solve_minimum, Inference}; 15 | use linear_solver::Inference::*; 16 | 17 | use std::collections::HashSet; 18 | 19 | use self::Expr::*; 20 | 21 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 22 | pub enum Expr { 23 | Prime(u32), 24 | Upto(u32), 25 | } 26 | 27 | pub fn infer(cache: &HashSet, facts: &[Expr]) -> Option> { 28 | // Put simplification rules first to find simplest set of facts. 29 | for (ind, ea) in facts.iter().enumerate() { 30 | match *ea { 31 | Prime(i) => { 32 | for (ind2, eb) in facts.iter().enumerate() { 33 | if ind == ind2 {continue}; 34 | match *eb { 35 | Prime(j) => { 36 | if i % j == 0 { 37 | // Remove the number. 38 | return Some(OneTrue {from: ea.clone()}); 39 | } 40 | } 41 | _ => {} 42 | } 43 | } 44 | } 45 | Upto(n) => { 46 | if n > 1 { 47 | return Some(Inference::replace_many( 48 | vec![ea.clone()], 49 | vec![Prime(n), Upto(n-1)], 50 | cache 51 | )); 52 | } else { 53 | return Some(OneTrue {from: ea.clone()}); 54 | } 55 | } 56 | } 57 | } 58 | None 59 | } 60 | 61 | fn main() { 62 | let start = vec![Upto(100)]; 63 | 64 | let res = solve_minimum(start, infer); 65 | for i in 0..res.len() { 66 | println!("{:?}", res[i]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/walk.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | In this example, we reduce a walk (left, right, up, down): 4 | 5 | l, l, u, l, r, d, d, r 6 | ---------------------- 7 | l, u 8 | 9 | */ 10 | 11 | extern crate linear_solver; 12 | 13 | use linear_solver::{solve_minimum, Inference}; 14 | use linear_solver::Inference::*; 15 | 16 | use std::collections::HashSet; 17 | 18 | use self::Expr::*; 19 | 20 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 21 | pub enum Expr { 22 | Left, 23 | Right, 24 | Up, 25 | Down, 26 | } 27 | 28 | pub fn infer(cache: &HashSet, _facts: &[Expr]) -> Option> { 29 | // Put simplification rules first to find simplest set of facts. 30 | if cache.contains(&Left) && cache.contains(&Right) { 31 | return Some(ManyTrue {from: vec![Left, Right]}); 32 | } 33 | if cache.contains(&Up) && cache.contains(&Down) { 34 | return Some(ManyTrue {from: vec![Up, Down]}); 35 | } 36 | None 37 | } 38 | 39 | fn main() { 40 | let start = vec![ 41 | Left, 42 | Left, 43 | Up, 44 | Left, 45 | Right, 46 | Down, 47 | Down, 48 | Right, 49 | ]; 50 | 51 | let res = solve_minimum(start, infer); 52 | for i in 0..res.len() { 53 | println!("{:?}", res[i]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A linear solver designed to be easy to use with Rust enums. 4 | //! 5 | //! This is a library for automated theorem proving. 6 | //! 7 | //! Linear solving means that some facts can be replaced with others. 8 | //! This technique can also be used to make theorem proving more efficient. 9 | //! 10 | //! If you are looking for a solver that does not remove facts, 11 | //! see [monotonic_solver](https://github.com/advancedresearch/monotonic_solver) 12 | //! 13 | //! *Notice! This solver does not support multiple histories. 14 | //! It assumes that when facts are simplified, 15 | //! they prove the same set of facts without the simplifaction.* 16 | //! 17 | //! A linear solver can be used to: 18 | //! 19 | //! - Prove some things in linear logic 20 | //! - Prove some things in classical logic more efficiently 21 | //! - Prove some things about where resources are "consumed" 22 | //! - Constraint solving 23 | //! - Implement some constraint solving programming language 24 | //! 25 | //! This project was heavily inspired by 26 | //! [CHR (Constraint Handling Rules)](https://dtai.cs.kuleuven.be/CHR/) 27 | //! 28 | //! ### Example: Walk 29 | //! 30 | //! ```rust 31 | //! /* 32 | //! 33 | //! In this example, we reduce a walk (left, right, up, down): 34 | //! 35 | //! l, l, u, l, r, d, d, r 36 | //! ---------------------- 37 | //! l, u 38 | //! 39 | //! */ 40 | //! 41 | //! extern crate linear_solver; 42 | //! 43 | //! use linear_solver::{solve_minimum, Inference}; 44 | //! use linear_solver::Inference::*; 45 | //! 46 | //! use std::collections::HashSet; 47 | //! 48 | //! use self::Expr::*; 49 | //! 50 | //! #[derive(Clone, PartialEq, Eq, Debug, Hash)] 51 | //! pub enum Expr { 52 | //! Left, 53 | //! Right, 54 | //! Up, 55 | //! Down, 56 | //! } 57 | //! 58 | //! pub fn infer(cache: &HashSet, _facts: &[Expr]) -> Option> { 59 | //! // Put simplification rules first to find simplest set of facts. 60 | //! if cache.contains(&Left) && cache.contains(&Right) { 61 | //! return Some(ManyTrue {from: vec![Left, Right]}); 62 | //! } 63 | //! if cache.contains(&Up) && cache.contains(&Down) { 64 | //! return Some(ManyTrue {from: vec![Up, Down]}); 65 | //! } 66 | //! None 67 | //! } 68 | //! 69 | //! fn main() { 70 | //! let start = vec![ 71 | //! Left, 72 | //! Left, 73 | //! Up, 74 | //! Left, 75 | //! Right, 76 | //! Down, 77 | //! Down, 78 | //! Right, 79 | //! ]; 80 | //! 81 | //! let res = solve_minimum(start, infer); 82 | //! for i in 0..res.len() { 83 | //! println!("{:?}", res[i]); 84 | //! } 85 | //! } 86 | //! ``` 87 | //! 88 | //! ### Example: Less or Equal 89 | //! 90 | //! ```rust 91 | //! /* 92 | //! 93 | //! In this example, we prove the following: 94 | //! 95 | //! X <= Y 96 | //! Y <= Z 97 | //! Z <= X 98 | //! ------ 99 | //! Y = Z 100 | //! Y = X 101 | //! 102 | //! */ 103 | //! 104 | //! extern crate linear_solver; 105 | //! 106 | //! use linear_solver::{solve_minimum, Inference}; 107 | //! use linear_solver::Inference::*; 108 | //! 109 | //! use std::collections::HashSet; 110 | //! 111 | //! use self::Expr::*; 112 | //! 113 | //! #[derive(Clone, PartialEq, Eq, Debug, Hash)] 114 | //! pub enum Expr { 115 | //! Var(&'static str), 116 | //! Le(Box, Box), 117 | //! Eq(Box, Box), 118 | //! } 119 | //! 120 | //! pub fn infer(cache: &HashSet, facts: &[Expr]) -> Option> { 121 | //! // Put simplification rules first to find simplest set of facts. 122 | //! for ea in facts { 123 | //! if let Le(ref a, ref b) = *ea { 124 | //! if a == b { 125 | //! // (X <= X) <=> true 126 | //! return Some(OneTrue {from: ea.clone()}); 127 | //! } 128 | //! 129 | //! for eb in facts { 130 | //! if let Le(ref c, ref d) = *eb { 131 | //! if a == d && b == c { 132 | //! // (X <= Y) ∧ (Y <= X) <=> (X = Y) 133 | //! return Some(Inference::replace( 134 | //! vec![ea.clone(), eb.clone()], 135 | //! Eq(a.clone(), b.clone()), 136 | //! cache 137 | //! )) 138 | //! } 139 | //! } 140 | //! } 141 | //! } 142 | //! 143 | //! if let Eq(ref a, ref b) = *ea { 144 | //! for eb in facts { 145 | //! if let Le(ref c, ref d) = *eb { 146 | //! if c == b { 147 | //! // (X = Y) ∧ (Y <= Z) <=> (X = Y) ∧ (X <= Z) 148 | //! return Some(Inference::replace_one( 149 | //! eb.clone(), 150 | //! Le(a.clone(), d.clone()), 151 | //! cache 152 | //! )); 153 | //! } else if d == b { 154 | //! // (X = Y) ∧ (Z <= Y) <=> (X = Y) ∧ (Z <= X) 155 | //! return Some(Inference::replace_one( 156 | //! eb.clone(), 157 | //! Le(c.clone(), a.clone()), 158 | //! cache 159 | //! )); 160 | //! } 161 | //! } 162 | //! 163 | //! if let Eq(ref c, ref d) = *eb { 164 | //! if b == c { 165 | //! // (X = Y) ∧ (Y = Z) <=> (X = Y) ∧ (X = Z) 166 | //! return Some(Inference::replace_one( 167 | //! eb.clone(), 168 | //! Eq(a.clone(), d.clone()), 169 | //! cache 170 | //! )); 171 | //! } 172 | //! } 173 | //! } 174 | //! } 175 | //! } 176 | //! 177 | //! // Put propagation rules last to find simplest set of facts. 178 | //! for ea in facts { 179 | //! if let Le(ref a, ref b) = *ea { 180 | //! for eb in facts { 181 | //! if let Le(ref c, ref d) = *eb { 182 | //! if b == c { 183 | //! // (X <= Y) ∧ (Y <= Z) => (X <= Z) 184 | //! let new_expr = Le(a.clone(), d.clone()); 185 | //! if !cache.contains(&new_expr) {return Some(Propagate(new_expr))}; 186 | //! } 187 | //! } 188 | //! } 189 | //! } 190 | //! } 191 | //! None 192 | //! } 193 | //! 194 | //! pub fn var(name: &'static str) -> Box {Box::new(Var(name))} 195 | //! 196 | //! fn main() { 197 | //! let start = vec![ 198 | //! Le(var("X"), var("Y")), // X <= Y 199 | //! Le(var("Y"), var("Z")), // Y <= Z 200 | //! Le(var("Z"), var("X")), // Z <= X 201 | //! ]; 202 | //! 203 | //! let res = solve_minimum(start, infer); 204 | //! for i in 0..res.len() { 205 | //! println!("{:?}", res[i]); 206 | //! } 207 | //! } 208 | //! ``` 209 | //! 210 | //! ### Linear logic 211 | //! 212 | //! When some facts are simplified, e.g.: 213 | //! 214 | //! ```text 215 | //! X, Y <=> Z 216 | //! ``` 217 | //! 218 | //! You need two new copies of `X` and `Y` to infer another `Z`. 219 | //! This is because the solver does not remove all copies. 220 | //! 221 | //! When doing classical theorem proving with a linear solver, 222 | //! it is common to check that every fact is unique in the input, 223 | //! and that the `cache` is checked before adding new facts. 224 | //! This ensures that the solver does not add redundant facts. 225 | //! 226 | //! However, when doing linear theorem proving, 227 | //! one can generate redundant facts `Z` for every `X` and `Y`. 228 | //! 229 | //! ### Meaning of goals 230 | //! 231 | //! Since a linear solver can both introduce new facts 232 | //! and remove them, it means that termination in the sense of proving a 233 | //! goal does not make sense, since the goal can be removed later. 234 | //! 235 | //! Instead, a goal is considered proved when it belongs to the same "cycle". 236 | //! This is the repeated list of sets of facts that follows from 237 | //! using a deterministic solver with rules that stops expanding. 238 | //! 239 | //! The minimum set of facts in the cycle is considered the implicit goal, 240 | //! because all the other facts in the cycle can be inferred from 241 | //! this set of facts. 242 | //! 243 | //! Notice that this a minimum set of facts in a cycle is different 244 | //! from a minimum set of axioms. A minimum set of axioms is a set of facts 245 | //! that proves a minimum set of facts with even fewer facts. 246 | //! With other words, the minimum set of axioms starts outside the cycle. 247 | //! When it moves inside the cycle, it is identical to some minimum set of facts. 248 | //! 249 | //! Both the minimum set of facts and the minimum set of axioms can be used 250 | //! to identify an equivalence between two sets of facts. 251 | //! 252 | //! ### Intuition of `false` and `true` 253 | //! 254 | //! The intuition of `false` can be thought of as: 255 | //! 256 | //! - Some fact which everything can be proven from 257 | //! - Some fact which every contradiction can be simplified to 258 | //! - A language that contains a contradiction for every truth value 259 | //! 260 | //! The minimum set of facts in a such language, 261 | //! when a cycle contains `false`, is `false`. 262 | //! 263 | //! The intuition of `true` can be thought of as: 264 | //! 265 | //! - Some fact which every initial fact can be proven from. 266 | //! - Some fact that contradicts `false` 267 | //! 268 | //! This means that the initial facts implies `true` and 269 | //! since it contradicts `false`, if there exists a contradiction 270 | //! in the initial facts, then they can prove `false`. 271 | //! 272 | //! Therefore a proof from initial facts is `true` 273 | //! if it's minimum set of facts does not equals `false`. 274 | //! 275 | 276 | extern crate cuckoofilter; 277 | 278 | use cuckoofilter::CuckooFilter; 279 | use std::collections::HashSet; 280 | use std::collections::hash_map::DefaultHasher; 281 | use std::hash::Hash; 282 | 283 | /// Tells the solver how to treat inference. 284 | pub enum Inference { 285 | /// Consumes `from` while producing nothing. 286 | OneTrue { 287 | /// Fact to remove from context. 288 | from: T, 289 | }, 290 | /// Consumes all `from` while producing nothing. 291 | ManyTrue { 292 | /// Facts to remove from context to be replaced by nothing. 293 | from: Vec 294 | }, 295 | /// Consumes `from` and replaces it with `to`. 296 | Simplify { 297 | /// Facts to remove from context. 298 | from: Vec, 299 | /// Fact to replace removed facts. 300 | to: T 301 | }, 302 | /// Consumes `from` and replaces it with `to`. 303 | SimplifyOne { 304 | /// Fact to remove from context. 305 | from: T, 306 | /// Fact to replace removed fact. 307 | to: T, 308 | }, 309 | /// Consumes all `from` while producing multiple facts `to`. 310 | SimplifyMany { 311 | /// Facts to remove from context to be replaced by new ones. 312 | from: Vec, 313 | /// Multiple facts to be added to context. 314 | to: Vec 315 | }, 316 | /// Add new fact. 317 | Propagate(T), 318 | } 319 | 320 | impl Inference { 321 | /// Replace `from` with `to`, checking the cache. 322 | /// 323 | /// Returns `OneTrue` if `to` already exists, 324 | /// and `SimplifyOne` if `to` does not exist. 325 | pub fn replace_one(from: T, to: T, cache: &HashSet) -> Self { 326 | if cache.contains(&to) { 327 | Inference::OneTrue {from} 328 | } else { 329 | Inference::SimplifyOne {from, to} 330 | } 331 | } 332 | 333 | /// Replace `from` with `to`, checking the cache. 334 | /// 335 | /// Returns `ManyTrue` if `to` already exists, 336 | /// and `Simplify` if `to` does not exist. 337 | pub fn replace(from: Vec, to: T, cache: &HashSet) -> Self { 338 | if cache.contains(&to) { 339 | Inference::ManyTrue {from} 340 | } else { 341 | Inference::Simplify {from, to} 342 | } 343 | } 344 | 345 | /// Replace `from` with `to`, checking the cache. 346 | /// 347 | /// Returns modified `SimplifyMany` where existing terms are removed. 348 | pub fn replace_many(from: Vec, mut to: Vec, cache: &HashSet) -> Self { 349 | for i in (0..to.len()).rev() { 350 | if cache.contains(&to[i]) { 351 | to.swap_remove(i); 352 | } 353 | } 354 | Inference::SimplifyMany {from, to} 355 | } 356 | } 357 | 358 | enum State { 359 | // Infer new facts. 360 | Solving, 361 | // Go to a state where the least amount of facts were present. 362 | SearchMinimum(Vec), 363 | } 364 | 365 | /// Solves the starting condition using the `infer` function for inference. 366 | /// 367 | /// Assumes that `infer` is deterministic and leading to a cycle for every input. 368 | /// Finds the minimum set of facts in the cycle. 369 | pub fn solve_minimum( 370 | mut facts: Vec, 371 | infer: fn(cache: &HashSet, &[T]) -> Option> 372 | ) -> Vec { 373 | fn remove_from(from: &[T], cache: &mut HashSet, facts: &mut Vec) { 374 | for new_fact in from { 375 | let mut unique = false; 376 | let mut i = 0; 377 | loop { 378 | if i >= facts.len() {break}; 379 | if new_fact == &facts[i] { 380 | if unique { 381 | unique = false; 382 | break; 383 | } 384 | // Since using swap remove, 385 | // should check the same index twice. 386 | facts.swap_remove(i); 387 | unique = true; 388 | } else { 389 | i += 1; 390 | } 391 | } 392 | if unique { 393 | cache.remove(&new_fact); 394 | } 395 | } 396 | } 397 | 398 | // Replace existing fact with new one to stabilize order. 399 | fn replace(from: &T, to: &T, cache: &mut HashSet, facts: &mut Vec) { 400 | let mut unique = false; 401 | for i in 0..facts.len() { 402 | if from == &facts[i] { 403 | if unique { 404 | unique = false; 405 | break; 406 | } 407 | facts[i] = to.clone(); 408 | unique = true; 409 | } 410 | } 411 | if unique { 412 | cache.remove(&from); 413 | } 414 | } 415 | 416 | let mut cache = HashSet::new(); 417 | for s in &facts { 418 | cache.insert(s.clone()); 419 | } 420 | 421 | // Cuckoo filter of previous sets of facts. 422 | // Used to detect whether a given set of facts has already been inferred. 423 | // Set to a value such that a false positive likely does not happen in practice. 424 | let filter_capacity = 1 << 22; 425 | let mut filter: CuckooFilter = CuckooFilter::with_capacity(filter_capacity); 426 | 427 | let mut state = State::Solving; 428 | 429 | loop { 430 | match state { 431 | State::Solving => { 432 | if filter.contains(&facts) { 433 | state = State::SearchMinimum(facts.clone()); 434 | filter = CuckooFilter::with_capacity(filter_capacity); 435 | } 436 | } 437 | State::SearchMinimum(ref fa) if filter.contains(&facts) => { 438 | // Completed cycle, minimum set of facts is found. 439 | if fa.len() < facts.len() { 440 | facts = fa.clone(); 441 | } 442 | break; 443 | } 444 | State::SearchMinimum(ref fa) if facts.len() < fa.len() => { 445 | // Found less amounts of facts in cycle. 446 | state = State::SearchMinimum(facts.clone()); 447 | } 448 | _ => {} 449 | } 450 | filter.add(&facts); 451 | if let Some(x) = infer(&cache, &facts) { 452 | match x { 453 | Inference::ManyTrue {from} => { 454 | remove_from(&from, &mut cache, &mut facts); 455 | } 456 | Inference::OneTrue {from} => { 457 | remove_from(&[from], &mut cache, &mut facts); 458 | } 459 | Inference::Simplify {from, to} => { 460 | remove_from(&from, &mut cache, &mut facts); 461 | facts.push(to.clone()); 462 | cache.insert(to); 463 | } 464 | Inference::SimplifyOne {from, to} => { 465 | replace(&from, &to, &mut cache, &mut facts); 466 | cache.insert(to); 467 | } 468 | Inference::SimplifyMany {from, to} => { 469 | remove_from(&from, &mut cache, &mut facts); 470 | for fact in &to { 471 | cache.insert(fact.clone()); 472 | } 473 | facts.extend(to.into_iter()); 474 | } 475 | Inference::Propagate(x) => { 476 | facts.push(x.clone()); 477 | cache.insert(x); 478 | } 479 | } 480 | } else {break} 481 | } 482 | facts 483 | } 484 | 485 | #[cfg(test)] 486 | mod tests { 487 | #[test] 488 | fn it_works() { 489 | } 490 | } 491 | --------------------------------------------------------------------------------