CSP: A Formalization in Lean of Constraint Satisfaction Problems

This Lean 4 project formalizes Constraint Satisfaction Problems (CSPs), with proofs of equivalence, equisatisfiability, and symmetry-breaking. The L2S (LeanToSolver) framework enables using Lean as a constraint programming language with automatic export to MiniZinc and SMT-LIB.

Project Structure

CSP/
├── Core.lean                 # General CSP definitions
├── Symmetry.lean             # General symmetry theory
├── Equivalence.lean          # General equivalence theory
├── GlobalConstraints.lean    # Predefined constraints
├── Transport.lean            # Type casting utilities
│
└── L2S/                      # LeanToSolver framework
    ├── Core.lean             # HomogeneousCSP foundations
    ├── Constraints.lean      # 50+ constraint types
    ├── Translate.lean        # Unified translation API
    ├── Backends/
    │   ├── MiniZinc.lean     # MiniZinc code generation
    │   └── SMTLIB.lean       # SMT-LIB code generation
    ├── Embedding.lean        # Embedding to heterogeneous CSP
    ├── Equivalence.lean      # Equivalence theory
    ├── Symmetry.lean         # Symmetry breaking theory
    ├── Proofs/               # Verified theorems (see below)
    └── Tests/                # 32 example problems

Verified Proofs

Symmetry Breaking

FileProblemSymmetryConstraint
NQueensSB.leanN-QueensHorizontal reflectionFirst queen in upper half
GraphColoringSB.leanGraph ColoringColor swap (0 ↔ c)Node 0 has color 0
LatinSquareSB.leanLatin SquareColumn permutationFirst row sorted

Equivalence Proofs

FileFormulationsMethod
NQueensEquivalence.lean1D (column vars) ↔ 2D (cell vars)π-equivalence via projection
GraphColoringEquivalence.leanVertex model ↔ Binary matrixπ-equivalence via one-hot encoding

Circuit Optimization Theorems

FileResult
UnreachableInputElimination.leanInputs with no path to outputs can be fixed to any value
ParityPathTheorem.leanInputs with uniform parity to all outputs can be optimally fixed

Requirements

ToolPurposeLink
Lean 4Theorem prover (required)lean-lang.org/install
MiniZincConstraint solver (optional)minizinc.org
Z3SMT solver (optional)github.com/Z3Prover/z3
CVC5SMT solver (optional)github.com/cvc5/cvc5

Make sure to have elan installed to avoid building the Mathlib dependencies. Elan auto-downloads the correct Lean version. You can install it this way:

# 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

Once elan is installed, it will manage all Lean versions for you automatically.

Building the Project

Even if you have installed Lean and elan with using the VS Code extension, you will need to build the project using the following commands to avoid building Mathlib from source:

# 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

Once the project is built, you should see the message Build completed successfully (7277 jobs), which confirms that everything worked fine. Then, you will be able to open it in your editor (VS Code recommended) and do not wait for Mathlib to build.

Usage

Modelling CSPs

The L2S framework uses HomogeneousCSP with a number of variables and a list of constraints:

import CSP.L2S.Core
import CSP.L2S.Constraints

open CSP.L2S

def graph_coloring (nodes : ℕ) (edges : List (Fin nodes × Fin nodes)) (colors : ℕ) : HomogeneousCSP :=
  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, less_than, not_equal, 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 : HomogeneousCSP := ...

-- Save to file
def main : IO Unit := do
  saveTo my_csp "model.mzn" BackendType.MiniZinc
  saveTo my_csp "model.smt2" BackendType.SMTLIB

Convenience functions:

translateTo my_csp BackendType.MiniZinc     -- Returns MiniZinc string
translateTo my_csp BackendType.SMTLIB       -- Returns SMT-LIB string

Solving

After generating solver files:

# Run Lean to generate files
lake env lean --run CSP/L2S/Tests/lean/08_queens.lean

# Solve with MiniZinc
minizinc model.mzn

# Or solve with Z3
z3 model.smt2

# Or solve with CVC5
cvc5 model.smt2

Example: Petersen Graph Coloring

import CSP.L2S.Core
import CSP.L2S.Constraints
import CSP.L2S.Translate

open CSP.L2S

def petersen : HomogeneousCSP :=
  let nodes := 10
  let edges := [
    (0, 1), (1, 2), (2, 3), (3, 4), (4, 0),  -- outer pentagon
    (5, 7), (7, 9), (9, 6), (6, 8), (8, 5),  -- inner pentagram
    (0, 5), (1, 6), (2, 7), (3, 8), (4, 9)   -- connections
  ]
  let bounds := (List.finRange nodes).map fun v => bound v 1 3
  let edge_constrs := edges.map fun (u, v) => not_equal ⟨u, by omega⟩ ⟨v, by omega⟩
  ⟨nodes, bounds ++ edge_constrs⟩

def main : IO Unit := do
  saveTo petersen "petersen.mzn" BackendType.MiniZinc

Run:

lake env lean --run MyFile.lean
minizinc petersen.mzn

Main Definitions

CSP Equivalence

Two CSPs are equivalent if there exists a bijection between their solution sets:

def equivalent (csp₁ : CSP VarIndex₁ DomainType₁) (csp₂ : CSP VarIndex₂ DomainType₂) : Prop :=
  ∃ f : {x // x ∈ sol_set csp₁} → {x // x ∈ sol_set csp₂}, Function.Bijective f

CSP Equisatisfiability

Two CSPs are equisatisfiable if satisfiability is preserved:

def equisatisfiable (csp₁ : CSP VarIndex₁ DomainType₁) (csp₂ : CSP VarIndex₂ DomainType₂) : Prop :=
  is_satisfiable csp₁ ↔ is_satisfiable csp₂