LeanCSP
A Lean 4 project formalizing Constraint Satisfaction Problems, with proofs of equivalence, equisatisfiability, and symmetry breaking. The L2S (LeanToSolver) framework lets you use Lean as a constraint programming language and export models to MiniZinc and SMT-LIB.
LeanCSP can also prove a CSP unsatisfiable. It compiles the model to pseudo-Boolean constraints through a verified order encoding, runs an external PB solver, and re-checks the solver's proof in Lean. The resulting theorems require no trust in the external solver.
Requirements
| Tool | Purpose | Link |
|---|---|---|
| Lean 4 | Theorem prover (required) | lean-lang.org/install |
| MiniZinc | Constraint solver (optional) | minizinc.org |
| Z3 / cvc5 | SMT solvers (optional) | z3 / cvc5 |
| RoundingSat | PB solver, to generate a new certificate (optional) | gitlab.com/MIAOresearch/software/roundingsat |
| veripb | PB proof elaborator, to generate a new certificate (optional) | gitlab.com/MIAOresearch/software/VeriPB |
Make sure to have elan installed to avoid building the Mathlib dependencies. Elan auto-downloads the correct Lean version:
# Install git and curl
sudo apt install git curl
# Install elan (choose 1 to accept the default install option)
curl https://elan.lean-lang.org/elan-init.sh -sSf | sh
# Add the elan executables to the PATH
source $HOME/.elan/env
RoundingSat and veripb are only needed to generate a new certificate. Re-checking the committed ones needs nothing but Lean. The Lean dependency PBLean, which provides the verified proof checker, is fetched automatically by Lake.
Building the Project
# 1. Clone the repository
git clone https://github.com/leansolving/leancsp.git
cd leancsp
# 2. Download pre-built Mathlib cache (IMPORTANT: saves 30+ min build time)
lake exe cache get
# 3. Build the project
lake build
The lakefile builds every submodule, so a bare lake build also re-checks every committed
certificate. It ends with Build completed successfully.
Usage
Modelling CSPs
The L2S framework uses IntCSP with a number of variables and a list of constraints. A
variable's range comes from a bound constraint:
import CSP.L2S.Core
import CSP.L2S.Constraints
open CSP.L2S
def graph_coloring (nodes : ℕ) (edges : List (Fin nodes × Fin nodes)) (colors : ℕ) : IntCSP :=
let bounds := (List.finRange nodes).map fun v => bound v 1 colors
let edge_constrs := edges.map fun (u, v) => not_equal u v
⟨nodes, bounds ++ edge_constrs⟩
Available constraints: alldifferent, count, element, sum_eq, linear_eq,
not_equal, at_most_k, at_least_k, bound, and 40+ more (see L2S/Constraints.lean).
Solver translation
Export CSPs to MiniZinc or SMT-LIB:
import CSP.L2S.Translate
open CSP.L2S
def my_csp : IntCSP := ...
def main : IO Unit := do
saveTo my_csp "model.mzn" BackendType.MiniZinc
saveTo my_csp "model.smt2" BackendType.SMTLIB
Then solve the generated files:
lake env lean --run MyFile.lean
minizinc model.mzn
z3 model.smt2
cvc5 --produce-models model.smt2
Proving unsatisfiability
Each instance is one line plus a committed certificate:
theorem php_3_2_unsat : ¬ php_3_2.isSatisfiableInt :=
csp_unsat_file php_3_2 3 "certs/php_3_2.pbp"
Everything else is derived from the CSP. To add your own instance, generate a certificate
with experiments/gen_cert.py <Module> <cspExpr> <out>, which needs roundingsat and
veripb, and prints the numVars argument to pass.
How it works
- Encode.
encodeCSPcompiles each constraint to pseudo-Boolean constraints over threshold variables, where an integer variable with domainDbecomes bits "x ≤ dⱼ". One soundness theorem covers this step for every CSP, so instances need no proofs of their own. - Solve. The constraints are written as OPB, RoundingSat solves them, and veripb
elaborates its log into a certificate committed under
Problems/certs/. - Check. PBLean's verified checker re-runs the certificate against the Lean-side
constraints, and
csp_unsatturns the result into¬ csp.isSatisfiableInt. A bad certificate makes the check fail, so the theorem does not go through.
The same pattern works for satisfiability: csp_sat_file re-checks an external solver's
witness with kernel decide.
Project structure
CSP/
├── Core.lean # General CSP definitions
├── Symmetry.lean # General symmetry theory
├── Equivalence.lean # General equivalence theory
├── GlobalConstraints.lean # Predefined constraints
│
└── L2S/ # LeanToSolver framework
├── Core.lean # IntCSP foundations
├── Constraints.lean # 50+ constraint types
├── Translate.lean # Unified translation API
├── Symmetry.lean # Symmetry breaking theory
├── Equivalence.lean # Equivalence theory
├── ValuePrecedence.lean # Value precedence as a symmetry break
├── Witness.lean # SAT witness checking
├── Backends/
│ ├── MiniZinc.lean # MiniZinc code generation
│ ├── SMTLIB.lean # SMT-LIB code generation
│ └── PB/ # Verified pseudo-Boolean UNSAT backend
│ └── Problems/ # Instances and their certificates
├── Proofs/ # Verified theorems (see below)
├── EndToEnd/ # Results about the original CSPs
└── Tests/ # 36 example problems
experiments/ # Reproducible studies (see experiments/README.md)
Verified Results
UNSAT theorems
Instances live in CSP/L2S/Backends/PB/Problems/, each with a committed certificate. The
*SBC.lean and *VP.lean modules repeat these families with a symmetry-breaking constraint
added.
| Module | Certifies |
|---|---|
Pigeonhole.lean | p pigeons do not fit injectively into h < p holes |
GraphColoring.lean | K₃ is not 2-colourable, K₄ is not 3-colourable |
OddCycle.lean | C₅, C₇ and C₉ are not 2-colourable |
Schur.lean, Schur3.lean | {1..5} has no sum-free 2-colouring, {1..14} none with 3 |
VanDerWaerden.lean | Every 2-colouring of {1..9} has a monochromatic 3-term AP |
Ramsey.lean | Every 2-colouring of K₆'s edges has a monochromatic triangle |
Paley.lean | Paley(13) has no independent set of size 4 |
Langford.lean | The Langford pairing L(2,2) has no solution |
Sudoku.lean, Latin.lean | Instances with contradictory givens are unsolvable |
MagicHexagon.lean | There is no order-2 normal magic hexagon |
MutilatedChessboard*.lean | A 4×4 or 6×6 board minus two same-colour corners has no domino tiling |
NQueens.lean, BlockedQueens.lean | 2- and 3-Queens, and 4-Queens with a blocked column, have no solution |
PeaceableArmies.lean | No 3+3 peaceable queens fit on a 4×4 board |
Circuit.lean, CircuitEquiv.lean, FullAdder.lean, RippleCarry.lean | Gate-level circuits meet their specifications |
CSP/L2S/EndToEnd/ combines these with a verified symmetry break to state results about the
original CSP. SchurCertify.lean pairs a solver witness with a certificate to pin the exact
Schur numbers S(2) = 4 and S(3) = 13, and proves S(4) ≥ 44.
Symmetry breaking
Each file proves a candidate constraint is a valid symmetry break, so adding it preserves satisfiability.
| File | Problem | Symmetry | Constraint |
|---|---|---|---|
NQueensSB.lean | N-Queens | Horizontal reflection | First queen in the first half |
GraphColoringSB.lean | Graph Coloring | Colour swap | Node 0 has colour 0 |
LatinSquareSB.lean | Latin Square | Column permutation | First row sorted |
SudokuSB.lean | Sudoku | Value swap | Cell (0,0) fixed |
SchurSB.lean | Schur | Colour swap | x₀ = 0 |
MatchingSB.lean | Perfect matching | Vertex transposition | Order the swapped edge variables |
MutilatedSB.lean | Mutilated chessboard | Diagonal reflection | Order the swapped domino variables |
LangfordSB.lean | Langford | Sequence reversal | Digit 0 in the second half |
ValuePrecedence.lean proves the Law-Lee value precedence constraint sound for any CSP whose
solutions are closed under colour permutations; *ValuePrecedence.lean files discharge that
for graph colouring, Schur, Ramsey, van der Waerden and pigeonhole.
SchurReversalCounterexample.lean is the cautionary case: the strict lexicographic reversal
leader is sound for van der Waerden but not for ordinary Schur.
Equivalence proofs
All use π-equivalence, an explicit projection and lifting between two formulations, which transports both solutions and unsatisfiability across the reformulation.
| File | Formulations |
|---|---|
NQueensEquivalence.lean | Column model ↔ board model |
GraphColoringEquivalence.lean | Vertex model ↔ binary one-hot matrix |
LatinSquareEquivalence.lean | Value-compact ↔ one-hot binary |
SudokuEquivalence.lean | Value-compact ↔ one-hot expanded |
SchurEquivalence.lean | Compact colour model ↔ binary matrix |
MutilatedEquivalence.lean | Orientation model ↔ edge model |
Circuit theorems
| File | Result |
|---|---|
UnreachableInputElimination.lean | Inputs with no path to outputs can be fixed to any value |
ParityPathTheorem.lean | Inputs with uniform parity to all outputs can be optimally fixed |
CircuitInputSymmetryBreaking.lean | Inputs with identical fanout can be ordered |
Citing
If you use LeanCSP in your research, please cite the accompanying paper:
Pablo Manrique and Stefan Szeider. LeanCSP: A Framework for Certifying Constraint Reformulation and Solving in Lean. arXiv:2607.28459, 2026. https://arxiv.org/abs/2607.28459
BibTeX citation:
@misc{manrique2026leancspframeworkcertifyingconstraint,
title={LeanCSP: A Framework for Certifying Constraint Reformulation and Solving in Lean},
author={Pablo Manrique and Stefan Szeider},
year={2026},
eprint={2607.28459},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2607.28459},
}
License
Apache 2.0, see LICENSE.