PyCSP3-Explain
Explanation tools for PyCSP3 constraint models. Find minimal unsatisfiable subsets (MUS), maximal satisfiable subsets (MSS), and minimal correction sets (MCS) to debug and understand infeasible constraint problems.
Features
- MUS (Minimal Unsatisfiable Subset): Find the minimal set of conflicting constraints
mus()- Assumption-based using ACE core extractionmus_naive()- Deletion-based (works with any solver)quickxplain()- Preferred MUS using QuickXplain (Junker, 2004)
- Optimal MUS (SMUS/OCUS): Prefer smaller or lower-weight explanations
smus()- Smallest MUS (fewest constraints)optimal_mus()- Minimum total weight MUSocus()- OCUS with subset constraints/predicatesocus_naive()- Naive OCUS (alias for weighted MUS)
Note: IHS uses a small CP model in PyCSP3 and falls back to enumeration if needed.
- MSS (Maximal Satisfiable Subset): Find the maximum satisfiable portion
mss()- Assumption-based with core extractionmss_naive()- Greedy growing algorithm
- Weighted MSS/MCS: Optimize which constraints to keep/remove
mss_opt()- Maximize total weight of kept constraintsmcs_opt()- Minimize total weight of removed constraintsmss_heuristic()- Heuristic weighted MSSmcs_heuristic()- Heuristic weighted MCS
- MCS (Minimal Correction Set): Find the minimal changes needed to restore satisfiability
mcs()- Via assumption-based MSS complementmcs_naive()- Via naive MSS complement
- MARCO: Enumerate all MUSes and MCSes
marco()- Generator over MUS/MCSmarco_naive()- Naive MARCO implementationall_mus()/all_mcs()- Convenience wrappersall_mus_naive()- Naive MUS enumeration
Installation
# Clone the repository
git clone https://github.com/sohaibafifi/pycsp3-explain.git
cd pycsp3-explain
# Install dependencies
pip install -e .
Requirements
- Python 3.10+
- PyCSP3 (>= 2.5)
- lxml (>= 4.9)
- Java runtime (for ACE/Choco)
Quick Start
Using satisfy() with Explain Tools
from pycsp3 import *
from pycsp3_explain import *
x = VarArray(size=2, dom=range(10))
satisfy( [
x[0] == 5,
x[0] == 7, # conflicts with x[0] == 5
x[1] >= 3,
])
constraints = flatten_constraints(posted())
if solve() == UNSAT:
print("MUS:", mus(constraints))
print("Optimal MUS:", optimal_mus(constraints, weights=[1, 1, 1]))
for kind, subset in marco(constraints):
print(kind, subset)
# or use the helper function
mus_result = explain_unsat("mus", soft=constraints, check=False)
print("MUS:", mus_result)
optimal = explain_unsat("optimal_mus", soft=constraints, check=False, weights=[1] * len(constraints))
print("Optimal MUS:", optimal)
for kind, subset in explain_unsat("marco", soft=constraints, check=False):
print(kind, subset)
Notes
mus()andmss()use ACE core extraction for efficiency; they fall back to naive versions for non-ACE solvers.- MCS is the complement of MSS:
MCS = Soft \ MSS - Removing MCS constraints from soft constraints leaves MSS, which is satisfiable.
How It Works
Constraint Explanation Problem
Given an infeasible constraint satisfaction problem (CSP), explanation tools help identify:
| Concept | Question | Description |
|---|---|---|
| MUS | Why is it infeasible? | Minimal Unsatisfiable Subset - the smallest set of constraints that conflict |
| MSS | What can be satisfied? | Maximal Satisfiable Subset - the largest set of constraints that can hold together |
| MCS | What to fix? | Minimal Correction Set - the smallest set of constraints to remove for satisfiability |
Relationships
- Every MUS and every MCS share at least one constraint (hitting set duality)
MCS = Soft \ MSS(MCS is the complement of MSS)- Multiple MUSes/MSSes/MCSes may exist for a given problem
API Reference
MUS Functions
mus(soft, hard=None, solver="ace")- Assumption-based MUSmus_naive(soft, hard=None, solver="ace")- Deletion-based MUSquickxplain(soft, hard=None, solver="ace")- Assumption-based preferred MUS (Junker, 2004)is_mus(subset, hard=None, solver="ace")- Verify MUS validityall_mus_naive(soft, hard=None, solver="ace")- Enumerate MUSes (naive)optimal_mus(soft, hard=None, weights=None, solver="ace")- Minimum-weight MUSoptimal_mus_naive(soft, hard=None, weights=None, solver="ace")- Naive minimum-weight MUSsmus(soft, hard=None, solver="ace")- Smallest MUS (fewest constraints)ocus(soft, hard=None, weights=None, solver="ace", ...)- OCUS with subset constraintsocus_naive(soft, hard=None, weights=None, solver="ace")- Naive OCUSOCUSException- Exception for optimal MUS/OCUS routines
MSS/MCS Functions
mss(soft, hard=None, solver="ace")- Assumption-based MSSmss_naive(soft, hard=None, solver="ace")- Greedy growing MSSmss_opt(soft, hard=None, weights=None, solver="ace")- Weighted MSSis_mss(subset, soft, hard=None, solver="ace")- Verify MSS validitymcs(soft, hard=None, solver="ace")- Assumption-based MCSmcs_naive(soft, hard=None, solver="ace")- Naive MCSmcs_opt(soft, hard=None, weights=None, solver="ace")- Weighted MCSmcs_from_mss(mss, soft)- Complement of MSSis_mcs(subset, soft, hard=None, solver="ace")- Verify MCS validity
MARCO Enumeration
marco(soft, hard=None, solver="ace")- Generator over MUS/MCSmarco_naive(soft, hard=None, solver="ace")- Naive MARCOall_mus(soft, hard=None, solver="ace")- Collect all MUSesall_mcs(soft, hard=None, solver="ace")- Collect all MCSes
License
MIT License
References
Metadata
Release files for pycsp3-explain 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pycsp3_explain-0.3.0.tar.gz | 101.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pycsp3_explain-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.8 kB
Release files / pycsp3_explain-0.3.0.tar.gz
| Download URL | pycsp3_explain-0.3.0.tar.gz |
|---|---|
| Size | 101.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f825d75aa4bdc4e70ca9f37d0e8bdde3c4242ae3d5bf4ac883e9898c18400fbb
|
|
BLAKE2b-256 checksum How to use checksums |
8e9f5918f40ff41c87220ccd1c4beea02a76dd4b6a69c98780e27143f8d05613
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 25, 2026.
Transparency logRelease files / pycsp3_explain-0.3.0-py3-none-any.whl
| Download URL | pycsp3_explain-0.3.0-py3-none-any.whl |
|---|---|
| Size | 33.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
aea325d75e9c6d20a6f221561adddb02671b10ec8a898c5eb1f2c67c895daa1f
|
|
BLAKE2b-256 checksum How to use checksums |
ca90a4c01621f89519d2f05aa478427d915d1de2e0af59fab1afec601d03167a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 25, 2026.
Transparency log