Propaga
A modern, propagator-based constraint solver written in Rust.
Propaga provides a clean propagation engine with typed variable handles, pluggable domains, and composable propagators. The goal is to make constraint propagation safe, extensible, and approachable — suitable for both learning and real problems.
Installation
CLI (crates.io):
Prebuilt binaries: GitHub Releases
Library crates: add to Cargo.toml (see docs.rs):
= "0.1"
= "0.1"
Known limitations: Propaga implements a FlatZinc subset. See benchmarks/minizinc/COMPATIBILITY.md before compiling MiniZinc models.
Crates
| Crate | Description |
|---|---|
propaga-core |
Core types: VariableId, Domain, Propagator, explanations, nogoods |
propaga-domains |
IntervalDomain, BitsetDomain, HybridDomain |
propaga-engine |
Propagation engine with trail and event scheduling |
propaga-propagators |
Built-in propagators (equality, linear, ordering, disjunctive, GAC all-different, GCC, table, element, cumulative, nogood) |
propaga-search |
Search with MRV/DOM/W-DEG, LCV, first-UIP nogood learning, branch-and-bound optimization, Luby restarts |
propaga-model |
High-level modeling API |
propaga-flatzinc |
FlatZinc subset parser and compiler |
propaga-cli |
Command-line solver (propaga sudoku, propaga n-queens, propaga solve, propaga schedule) |
Features
Search (Sprint 1, 8–10, 16, 18)
- First-UIP conflict analysis: backward explanation resolution with 1UIP nogood trimming
- Sound propagator conflicts: propagator pruning no longer expands nogoods to all prior branches
- Cumulative overload nogoods: mandatory overlapping tasks only (not full branch history)
- Disjunctive overlap nogoods: fixed overlapping start pair recorded on conflict
- Nogood propagator: learned nogoods posted into the engine for early pruning
- Root propagation: full fixpoint propagation before search and after each restart
- Luby restarts: configurable restart policy (
--restarts luby,luby:256,none) - Phase saving: last assigned value is tried first on backtrack/restart (
--no-phase-savingto disable) - SearchConfig:
Model::set_search_config()for learning/restart/value-ordering tuning
Propagation (Sprint 2–3, 17)
- AllDifferent GAC via Hopcroft-Karp matching plus Regin SCC batch value pruning (single matching, Tarjan SCC, free-value handling)
- GlobalCardinality (GCC): bounds-consistent cardinality propagator via
Model::gcc - Table, Element, and Cumulative (overload + time-table edge finding) propagators
- Explanation-aware trail with synchronized backtracking
FlatZinc (Sprint 2–5, 19–21, 23)
- Subset parser:
var/arraydeclarations,int/boolvariables,int/intarray params,all_different,int_eq,int_ne,int_le,int_lt,int_ge,int_gt,int_*_reif,int_lin_eq,int_lin_le,int_lin_ge,int_lin_*_reif,bool_eq,bool2int,element,cumulative,disjunctive,global_cardinality,table,output,solve satisfy|minimize|maximize - CLI:
propaga solve --file benchmarks/magic_square.fznorpropaga solve --dir benchmarks/ - Compatibility: benchmarks/minizinc/COMPATIBILITY.md
FlatZinc GCC and table (Sprint 19)
global_cardinality: two-arg(cover, vars)and four-arg(vars, cover, lbound, ubound)forms compiled toModel::gcctable: tuple sets{...}compiled toModel::table- Benchmarks:
gcc_exact.fzn,table_puzzle.fzn
FlatZinc output (Sprint 20)
- Parses
output [ show("text", var), ... ];directives - Plain output renders formatted solution lines; JSON includes an
outputsarray magic_square.fznincludes a sample output directive
Optimization (Sprint 21)
solve minimize/solve maximizein FlatZinc with branch-and-bound over a single objective variableModel::optimize()andOptimizationSearchinpropaga-search- CLI prints
objective (minimize|maximize) = Nand JSONobjectivefield - Benchmarks:
maximize_x.fzn,minimize_cost.fzn
Search heuristics and limits (Sprint 22)
- Variable ordering:
--var-ordering mrv(default),dom, ordom-wdeg - Solution cap:
--solutions Nwith--allstops afterNsolutions (Sudoku, N-Queens, FlatZinc) - CI runs
cargo fmt --checkandcargo clippy -D warnings
Scheduling (Sprint 3–8)
- JSON schedule format (
capacity,horizon,tasks, optionalmodeor legacysequential/disjunctiveflags) - Modes:
cumulative(default),sequential,disjunctive - Cumulative mandatory intervals only when start/end is fixed or singleton (no false root overload)
- Per-task resource
demand(default 1); FlatZinccumulative(..., heights, capacity)supported - CLI:
propaga schedule --file benchmarks/schedule_three_tasks.json --stats - Benchmarks:
schedule_cumulative.json,schedule_cumulative_cap2.json,schedule_cumulative_demand.json,schedule_disjunctive.json,schedule_cumulative_mode.json - Cumulative mode supports nogood learning via overload conflict literals
Linear inequalities (Sprint 7–9, 14)
- FlatZinc
int_lin_le/int_lin_ge/int_lin_eqwith unit or general integer coefficients (LinearScalarLe/Ge) - Reified linear:
int_lin_le_reif,int_lin_ge_reif,int_lin_eq_reif(ReifiedScalarLe/Ge/Eq); eq reif=0 prunes values that would force the sum - Benchmarks:
bounded_sum.fzn,bounded_sum_ge.fzn,weighted_sum.fzn,weighted_sum_ge.fzn,reified_lin_le.fzn,reified_lin_eq.fzn,reified_lin_ne.fzn - MiniZinc workflow:
benchmarks/minizinc/README.md
Search ordering (Sprint 4)
- LCV value ordering:
--value-ordering lcvtries values that appear in fewer other domains first - Default remains ascending (
--value-ordering asc)
Ordering constraints (Sprint 5–6)
LessEqual/LessThanpropagators: bound-consistent<=and<viaModel::less_equal/Model::less_thangreater_equal/greater_than: swapped posting helpers on the model API- FlatZinc:
int_le,int_lt,int_ge,int_gt Disjunctivepropagator: pairwise single-machine disjunctive scheduling with fixed-start edge finding, theta-tree mandatory precedences, and energy overload detection; FlatZincdisjunctiveReifiedpropagators:int_eq_reif,int_ne_reif,int_le_reif,int_lt_reif,int_ge_reif,int_gt_reifover 0/1 reification variables- Benchmarks:
ordered_chain.fzn,strict_chain.fzn,disjunctive_two.fzn,disjunctive_three.fzn - Schedule output reads fixed
endtimes from the engine after search (not only start decision vars)
CLI
- Plain/JSON output, batch Sudoku, stats,
--all,--visual --no-learningto disable nogood learning--restartsfor restart policy control--value-ordering(ascorlcv)--var-ordering(mrv,dom, ordom-wdeg)--solutions Nto cap enumeration with--all--time-limit SECSto cap search wall-clock time--no-phase-savingto disable phase saving
Sprint 23 (near-term roadmap)
--time-limit SECS: wall-clock cutoff during search; plainTIMEOUT/ JSONstatus: timeout- FlatZinc bool:
var bool,array of var bool,bool_eq,bool2int(modeled as 0..1 integers) - Batch solve:
propaga solve --dir benchmarks/runs all.fznfiles; JSON batch summary - Criterion benches:
cargo bench -p propaga-propagatorsfor all-different GAC and cumulative propagation - Compatibility matrix: benchmarks/minizinc/COMPATIBILITY.md
Heuristic comparison (Sprint 22)
| Flag combo | Effect |
|---|---|
| default | MRV + ascending values + learning + Luby restarts |
--no-learning |
DFS without nogoods (often more backtracks) |
--value-ordering lcv |
Least constraining value first |
--var-ordering dom |
Domain size tie-break ordering |
--var-ordering dom-wdeg |
DOM divided by conflict weights |
--all --solutions 3 |
Stop after three solutions |
Quick start
License
Licensed under either of MIT or Apache-2.0 at your option.