|
1 | | -# Zelen - Selen integration with MiniZinc |
| 1 | +# Zelen - FlatZinc Frontend for Selen CSP Solver |
2 | 2 |
|
3 | 3 | [](https://crates.io/crates/zelen) |
4 | | -[](https://docs.rs/zelen) |
| 4 | +[](https://docs.rs/zelen) |
5 | 5 | [](https://opensource.org/licenses/MIT) |
6 | 6 |
|
7 | | -This crate aims to integrate the Selen Solver with the FlatZinc format from MiniZinc. |
| 7 | +Zelen is a FlatZinc parser and integration library for the [Selen](https://github.com/radevgit/selen) constraint solver. It allows you to solve constraint satisfaction and optimization problems written in the FlatZinc format, which is the intermediate language used by [MiniZinc](https://www.minizinc.org/). |
8 | 8 |
|
9 | | -## Status |
| 9 | +## Features |
| 10 | + |
| 11 | +- ✅ **Complete FlatZinc parser** - Parses FlatZinc models into an AST |
| 12 | +- ✅ **Seamless Selen integration** - Maps FlatZinc constraints to Selen's constraint model |
| 13 | +- ✅ **Extensive constraint support** - Arithmetic, comparison, linear, boolean, global constraints (alldiff, table, etc.) |
| 14 | +- ✅ **Array handling** - Full support for arrays and array indexing |
| 15 | +- ✅ **Reification** - Support for reified constraints |
| 16 | +- ✅ **Optimization** - Handles both satisfaction and optimization problems (minimize/maximize) |
| 17 | +- ✅ **High compatibility** - Successfully parses 96%+ of real-world FlatZinc files |
| 18 | + |
| 19 | +## Installation |
| 20 | + |
| 21 | +Add this to your `Cargo.toml`: |
| 22 | + |
| 23 | +```toml |
| 24 | +[dependencies] |
| 25 | +zelen = "0.1" |
| 26 | +``` |
| 27 | + |
| 28 | +## Quick Start |
| 29 | + |
| 30 | +### From FlatZinc String |
| 31 | + |
| 32 | +```rust |
| 33 | +use zelen::prelude::*; |
| 34 | + |
| 35 | +let fzn = r#" |
| 36 | + var 1..10: x; |
| 37 | + var 1..10: y; |
| 38 | + constraint int_eq(x, y); |
| 39 | + constraint int_lt(x, 5); |
| 40 | + solve satisfy; |
| 41 | +"#; |
| 42 | + |
| 43 | +let mut model = Model::default(); |
| 44 | +model.from_flatzinc_str(fzn)?; |
| 45 | + |
| 46 | +match model.solve() { |
| 47 | + Ok(solution) => println!("Solution found: {:?}", solution), |
| 48 | + Err(_) => println!("No solution exists"), |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +### From FlatZinc File |
| 53 | + |
| 54 | +```rust |
| 55 | +use zelen::prelude::*; |
| 56 | + |
| 57 | +let mut model = Model::default(); |
| 58 | +model.from_flatzinc_file("problem.fzn")?; |
| 59 | + |
| 60 | +let solution = model.solve()?; |
| 61 | +println!("Solution: {:?}", solution); |
| 62 | +``` |
| 63 | + |
| 64 | +### N-Queens Example |
| 65 | + |
| 66 | +```rust |
| 67 | +use zelen::prelude::*; |
| 68 | + |
| 69 | +// 4-Queens problem in FlatZinc |
| 70 | +let fzn = r#" |
| 71 | + array[1..4] of var 1..4: queens; |
| 72 | + constraint all_different(queens); |
| 73 | + constraint all_different([queens[i] + i | i in 1..4]); |
| 74 | + constraint all_different([queens[i] - i | i in 1..4]); |
| 75 | + solve satisfy; |
| 76 | +"#; |
| 77 | + |
| 78 | +let mut model = Model::default(); |
| 79 | +model.from_flatzinc_str(fzn)?; |
| 80 | + |
| 81 | +if let Ok(solution) = model.solve() { |
| 82 | + println!("Found a solution for 4-Queens!"); |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +### Optimization Example |
| 87 | + |
| 88 | +```rust |
| 89 | +use zelen::prelude::*; |
| 90 | + |
| 91 | +let fzn = r#" |
| 92 | + var 1..100: x; |
| 93 | + var 1..100: y; |
| 94 | + constraint int_plus(x, y, 50); |
| 95 | + solve minimize x; |
| 96 | +"#; |
| 97 | + |
| 98 | +let mut model = Model::default(); |
| 99 | +model.from_flatzinc_str(fzn)?; |
| 100 | + |
| 101 | +if let Ok(solution) = model.solve() { |
| 102 | + println!("Optimal solution found"); |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +### FlatZinc-Compliant Output |
| 107 | + |
| 108 | +Zelen provides a formatter to output solutions according to the FlatZinc specification: |
| 109 | + |
| 110 | +```rust |
| 111 | +use zelen::prelude::*; |
| 112 | +use zelen::output::format_solution; |
| 113 | +use std::collections::HashMap; |
| 114 | + |
| 115 | +let fzn = r#" |
| 116 | + var 1..10: x; |
| 117 | + var 1..10: y; |
| 118 | + constraint int_eq(x, 5); |
| 119 | + constraint int_eq(y, 3); |
| 120 | + solve satisfy; |
| 121 | +"#; |
| 122 | + |
| 123 | +let mut model = Model::default(); |
| 124 | +model.from_flatzinc_str(fzn)?; |
| 125 | + |
| 126 | +match model.solve() { |
| 127 | + Ok(solution) => { |
| 128 | + // Get variable names from the parser (you'd track these during parsing) |
| 129 | + let var_names = HashMap::from([ |
| 130 | + (x_var_id, "x".to_string()), |
| 131 | + (y_var_id, "y".to_string()), |
| 132 | + ]); |
| 133 | + |
| 134 | + // Format according to FlatZinc spec |
| 135 | + let output = format_solution(&solution, &var_names); |
| 136 | + print!("{}", output); |
| 137 | + // Outputs: |
| 138 | + // x = 5; |
| 139 | + // y = 3; |
| 140 | + // ---------- |
| 141 | + } |
| 142 | + Err(_) => { |
| 143 | + println!("{}", zelen::output::format_no_solution()); |
| 144 | + // Outputs: =====UNSATISFIABLE===== |
| 145 | + } |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +The output format follows the [FlatZinc specification](https://docs.minizinc.dev/en/stable/fzn-spec.html#output): |
| 150 | +- Each variable assignment on its own line: `varname = value;` |
| 151 | +- Separator line `----------` marks the end of a solution |
| 152 | +- `=====UNSATISFIABLE=====` when no solution exists |
| 153 | +- `=====UNKNOWN=====` when satisfiability cannot be determined |
| 154 | + |
| 155 | +## Using with MiniZinc |
| 156 | + |
| 157 | +You can use Zelen to solve MiniZinc models by first compiling them to FlatZinc: |
| 158 | + |
| 159 | +```bash |
| 160 | +# Compile MiniZinc model to FlatZinc |
| 161 | +minizinc --solver gecode -c model.mzn -d data.dzn -o problem.fzn |
| 162 | + |
| 163 | +# Then solve with your Rust program using Zelen |
| 164 | +cargo run --release -- problem.fzn |
| 165 | +``` |
| 166 | + |
| 167 | +## Supported Constraints |
| 168 | + |
| 169 | +### Comparison Constraints |
| 170 | +- `int_eq`, `int_ne`, `int_lt`, `int_le`, `int_gt`, `int_ge` |
| 171 | +- Reified versions: `int_eq_reif`, `int_ne_reif`, etc. |
| 172 | + |
| 173 | +### Arithmetic Constraints |
| 174 | +- `int_abs`, `int_plus`, `int_minus`, `int_times`, `int_div`, `int_mod` |
| 175 | +- `int_min`, `int_max` |
| 176 | + |
| 177 | +### Linear Constraints |
| 178 | +- `int_lin_eq`, `int_lin_le`, `int_lin_ne` |
| 179 | +- Reified: `int_lin_eq_reif`, `int_lin_le_reif` |
| 180 | + |
| 181 | +### Boolean Constraints |
| 182 | +- `bool_eq`, `bool_le`, `bool_not`, `bool_xor` |
| 183 | +- `bool_clause`, `array_bool_and`, `array_bool_or` |
| 184 | +- `bool2int` |
| 185 | + |
| 186 | +### Global Constraints |
| 187 | +- `all_different` - All variables must take different values |
| 188 | +- `table_int`, `table_bool` - Table/extensional constraints |
| 189 | +- `lex_less`, `lex_lesseq` - Lexicographic ordering |
| 190 | +- `nvalue` - Count distinct values |
| 191 | +- `global_cardinality` - Cardinality constraints |
| 192 | +- `cumulative` - Resource scheduling |
| 193 | + |
| 194 | +### Array Constraints |
| 195 | +- `array_int_minimum`, `array_int_maximum` |
| 196 | +- `array_int_element`, `array_bool_element` |
| 197 | +- `count_eq` - Count occurrences |
| 198 | + |
| 199 | +### Set Constraints |
| 200 | +- `set_in`, `set_in_reif` - Set membership |
| 201 | + |
| 202 | +## Architecture |
| 203 | + |
| 204 | +Zelen follows a three-stage pipeline: |
| 205 | + |
| 206 | +1. **Tokenization** (`tokenizer.rs`) - Lexical analysis of FlatZinc source |
| 207 | +2. **Parsing** (`parser.rs`) - Recursive descent parser building an AST |
| 208 | +3. **Mapping** (`mapper.rs`) - Maps AST to Selen's constraint model |
| 209 | + |
| 210 | +``` |
| 211 | +FlatZinc Source → Tokens → AST → Selen Model → Solution |
| 212 | +``` |
| 213 | + |
| 214 | +## Performance |
| 215 | + |
| 216 | +Zelen has been tested on 851 real-world FlatZinc files from the OR-Tools test suite: |
| 217 | +- **819 files (96.2%)** parse and solve successfully |
| 218 | +- **32 files (3.8%)** use unsupported features (mostly set constraints) |
| 219 | + |
| 220 | +## Examples |
| 221 | + |
| 222 | +Check the `examples/` directory for more complete examples: |
| 223 | + |
| 224 | +```bash |
| 225 | +# Basic FlatZinc parsing and solving |
| 226 | +cargo run --example flatzinc_simple |
| 227 | + |
| 228 | +# FlatZinc-compliant output formatting |
| 229 | +cargo run --example flatzinc_output |
| 230 | + |
| 231 | +# Complete solver demo with multiple problem types |
| 232 | +cargo run --example solver_demo |
| 233 | +``` |
| 234 | + |
| 235 | +## Testing |
| 236 | + |
| 237 | +Run the test suite: |
| 238 | + |
| 239 | +```bash |
| 240 | +# Unit and integration tests |
| 241 | +cargo test |
| 242 | + |
| 243 | +# Run slower batch tests (tests 819 FlatZinc files) |
| 244 | +cargo test -- --ignored |
| 245 | +``` |
| 246 | + |
| 247 | +## Relationship with Selen |
| 248 | + |
| 249 | +Zelen depends on [Selen](https://github.com/radevgit/selen) v0.9+ as its underlying constraint solver. While Selen provides the core CSP solving capabilities, Zelen adds the FlatZinc parsing and integration layer, making it easy to use Selen with MiniZinc models. |
| 250 | + |
| 251 | + |
| 252 | +## License |
| 253 | + |
| 254 | +Licensed under the MIT license. See [LICENSE](LICENSE) for details. |
| 255 | + |
| 256 | +## See Also |
| 257 | + |
| 258 | +- [Selen](https://github.com/radevgit/selen) - The underlying CSP solver |
| 259 | +- [MiniZinc](https://www.minizinc.org/) - Constraint modeling language |
| 260 | +- [FlatZinc Specification](https://docs.minizinc.dev/en/stable/fzn-spec.html) |
10 | 261 |
|
11 | | -In implementation. |
|
0 commit comments