Release notes
Added
- A CPMpy Logo #1060
- IO module with file format readers and writers #842
- Datasets: PyTorch-compatible dataset class providing single-line access to datastes from the CO community #900 #1037 #1055:
- XCSP3
- JSPLib
- PSPLib rcpsp
- MIPLib
- MaxSAT Eval
- OPB
- SAT
- Nurse rostering
- New Solvers
- New transformations
- New Globals
- MyPy static type checking #864 #867
- Proper documentation for NumPy compatibility #1051
- Support
Regularglobal natively for MiniZinc #952 - Linearize reified variables #855 #860
- Custom and typed CSEMap object for Common Subexpression Elimination #917
- Add encoding variables as expressions to CSE #781
- IIS-based MUS algorithm #880 #971
- Native MUS computation for Exact #909
- Multi-instance tuner #757
- Add verbosity parameter to tuners #771
- 'subsolvers' filter argument for
SolverLookup.supported()#1030 - Decorator for non-strict variable name checks #839
Descriptionclass for human-readable metadata inExpression#903- Aspirational unit tests #883
- Instructions on python-cov #937
- Sudoku variant examples #777
- Template docstrings #724
- Dev script to time transformations #853 848febd
- Dev script to extract release notes #671
Breaking changes
With this v1.0.0 release, a lot has changed to CPMpy's internal workings. Many transformations have been completely rewritten, internal API signatures have changed and datastructures have been replaced by new ones. We split the breaking changes into two groups: changes that affect what and how you can model (with instructions on how to update your code), and changes to CPMpy's internals, which you will only notice if your code uses those internals directly or if you saved models to .pickle files.
Changes to the modeling API — how to update your code
- Float objectives are now expressed with the new
FloatSumglobal function #957. Float coefficients are no longer allowed inside regular expressions: multiplying a decision variable with a float constant (e.g.0.5 * x) raises aTypeError, andModel.minimize()/Model.maximize()only accept integer-valued expressions.- If you had a float objective: use
FloatSumand pass it directly to a solver object'sminimize()/maximize()(supported by OR-Tools, Gurobi, CPLEX, SCIP, HiGHS, Z3, MiniZinc and Hexaly):Note thatobj = cp.FloatSum([0.5, 1.5], [x, y]) # replaces 0.5*x + 1.5*y s = cp.SolverLookup.get("ortools", model) # model contains only the constraints s.minimize(obj) s.solve() print(obj.value()) # read the objective from the FloatSum; # s.objective_value() stays None when the optimum is not integral
FloatSumis not anExpression: it cannot be nested inside constraints or other expressions. - If you had float coefficients in constraints: rescale them to integers (e.g. replace
0.5*x + 1.5*y <= 2byx + 3*y <= 4).
- If you had a float objective: use
- The deprecated lowercase constraint aliases
alldifferent(),allequal()andcircuit()are removed #905 #1050 (deprecated since 0.9.0). They are no longer exported from thecpmpynamespace and the functions themselves have been removed fromcpmpy.expressions.globalconstraints; use the global constraint classesAllDifferent,AllEqualandCircuitinstead. - Other functions deprecated since the 0.9.x series are removed #1050:
BoolVar(),IntVar()andcparray(): useboolvar(),intvar()andcpm_array()instead.Model.deepcopy()andExpression.deepcopy(): usecopy.deepcopy()instead.cpmpy.transformations.negation.negated_normal(): userecurse_negation()(orpush_down_negation()on the full expression tree) instead.cpmpy.transformations.get_variables.vars_expr(): useget_variables()instead.cpmpy.solvers.utils.get_supported_solvers()and thebuiltin_solverslist: useSolverLookup.supported()andSolverLookup.get(name)instead.
- Multiplication is now a global function instead of an operator #850.
x * ycreates aMultiplication(x, y)global function (still named"mul") andOperator("mul", ...)can no longer be constructed. Modeling with*is unaffected, but code that inspects expressions withisinstance(expr, Operator)no longer matches multiplications; useisinstance(expr, Multiplication)instead. Elementonly accepts 1-dimensional arrays #926. Indexing a multi-dimensional array now creates the newNDElementglobal function. Keep indexing with comma-separated indices (Arr[i,j]) or constructNDElement(Arr, [i,j])directly; code that constructedElementwith a flattened index, or that checksisinstance(expr, Element), must be updated accordingly.NDVarArrayis no longer anExpressionsubclass #886. Variable arrays (as returned byintvar(..., shape=...),boolvar(shape=...)andcpm_array()) are now plainnumpy.ndarraysubclasses. All modeling functionality is unaffected (vectorized operators,.sum(),.min()/.max(),.any()/.all(),.value(),.implies(), indexing), but code that treats an array as an expression must be updated:isinstance(arr, Expression)now returnsFalse, andarr.args,arr.nameandarr.is_bool()no longer exist.- Expression arguments are read-only tuples #894.
expr.argsnow returns atupleinstead of alist, so it can no longer be modified in place (e.g.expr.args.append(...)orexpr.args[0] = ...). Construct a new expression instead, or useexpr.update_args(new_args)for an explicit in-place update. Table,ShortTableandNegativeTablerequire a rectangular table of integers and a flat array argument #895. The table is converted to, and stored as, a two-dimensional integernumpyarray; ragged tables and non-integer entries now raise an error. Thearrayargument is no longer flattened: a nested Python list of variables now raises an error (a multi-dimensionalNDVarArrayis still accepted and reshaped internally); flatten nested lists yourself, e.g. withcpmpy.expressions.utils.flatlist.MinimumandMaximumno longer flatten nested lists #965. Constructing e.g.Minimum([[x,y],[z]])no longer works (it creates an invalid expression that fails when solving); pass a flat sequence of expressions instead, e.g.Minimum([x,y,z])orMinimum(arr.flat). Other globals with variable-length argument lists (AllDifferent,AllEqual,Xor,Increasing, ..., as well ascp.min/cp.max/cp.sumon arrays) are unaffected and still flatten their input.CumulativeandNoOverlapno longer storeendin their.argswhen it is not given #830. Code that unpacks.args(e.g.start, dur, end, demand, cap = c.args) must first checklen(c.args); when noendwas given, the end times are implicitlystart + duration.
Changes to CPMpy's internals — only relevant if you use internals directly or saved models to pickle files
Due to the changes below, models saved to a .pickle file with CPMpy < 1.0.0 will no longer load (or will fail when used) in v1.0.0. Pickle files are tied to the CPMpy version that created them: re-run your model-building code under v1.0.0 and save again. For longer-term storage, consider writing models to a solver-independent text format with the new IO module.
- All
Expressionarguments are now stored as immutable tuples instead of lists #894, and thehas_subexpr()cache is an eagerly-initialised attribute #895. - Expressions created by
x * yareMultiplicationglobal functions instead ofOperator("mul", ...)#850; multi-dimensional indexing createsNDElementinstead of a flattenedElement#926. Table-like constraints store their table as a 2Dnumpyarray and keepNDVarArrayarguments as-is instead of converting them to Python lists #895.CumulativeandNoOverlapstore 4 resp. 2 arguments when noendis given, instead of aNoneplaceholder #830.set_description()now stores aDescriptionobject inexpr._description; the oldexpr.descattribute no longer exists #903.- The transformations have been largely rewritten in a new, typed pattern:
- The deprecated
decompose_global()anddo_decompose()are removed; usedecompose_in_tree()#835, which now also accepts custom (positive) decompositions #929 #980 #1006. - The
csemap=argument of transformations expects the newCSEMapobject instead of a plaindict#917. - Several transformations gained parameters or dedicated objective-variants (
flatten_constraint(..., do_simplify=),decompose_objective,push_down_negation_objective,safen_objective,decompose_linear,linearize_reified_variables, ...); consult thecpmpy.transformationsdocumentation when upgrading code that calls transformations directly.
- The deprecated
- Solver interfaces: the internal
_varmapis keyed by variable name instead of by variable object #990, andsolver_var()of the SAT-based interfaces (PySAT, Pindakaas) consistently returns Boolean literals only #1017. - The deprecated
objective_value=parameter ofSolverInterface._solve_return()is removed, as is the internal helpercpmpy.transformations.get_variables._uniquify()#1050.
Minor behavior changes, including bug fixes that may affect code relying on the old behavior
Expression <op> ndarraynow broadcasts correctly #1035. Using a CPMpy expression on the left-hand side of an operator with a numpy array on the right (e.g.x + np.array([1,2,3])) now broadcasts element-wise and returns an array of expressions, just like the mirroredndarray <op> Expressionalways did. Code relying on the old (faulty) single-expression result must be updated.value()returnsNonefor partially-assigned global constraints #872.Xor,CumulativeandCircuitnow returnNonefrom.value()when some of their arguments have no value (consistent with other expressions), instead of raising an error or computing an incorrect result.- Variable values are cleared after an unsatisfiable solve #1001. With Exact and Pindakaas,
.value()of variables now returnsNoneafter an UNSAT solve call, instead of returning stale values from a previous (satisfiable) solve. cp.sum()over a single expression returns that expression itself, instead of wrapping it in a single-argument sum expression.- Expression printing has been refactored #893. The exact output of
str(expr)/repr(expr)can differ slightly from previous versions; do not rely on the textual form of expressions.
Deprecated
Old names keep working for now (with a DeprecationWarning where applicable), but will be removed in a future release — switch to the new ones:
- DIMACS tooling moved to the new IO module #842;
cpmpy.tools.dimacsis kept as a backward-compatible wrapper aroundcpmpy.tools.io.dimacs:read_dimacs(fname)is deprecated; usecpmpy.tools.io.load_dimacs(...), which also accepts the DIMACS content as a string or an open file object.write_dimacs(model, fname)moved tocpmpy.tools.io.write_dimacs(model, path). Note that the second parameter was renamed (fname→path) and that thep cnfheader line is no longer written by default; passp_header=Trueto restore it.
- XCSP3 loading:
cpmpy.tools.xcsp3.read_xcsp3()is deprecated; usecpmpy.tools.io.load_xcsp3(), which accepts a path, string content or an open file object. XCSP3Datasetmoved to the new datasets module #900: import it fromcpmpy.tools.datasets(it remains re-exported fromcpmpy.tools.xcsp3for backward compatibility).- More generally, the new IO module provides
cpmpy.tools.io.load()andcpmpy.tools.io.write()as one-stop entry points that automatically select the format (DIMACS, WCNF, OPB, XCSP3, ...) based on the file extension. SolverLookup.solvernames()now emits aDeprecationWarning; useSolverLookup.supported()instead #1050. Similarly, the deprecated_toplevel/nestedarguments ofdecompose_in_tree()and_toplevel/_nbcofno_partial_functions()now emit aDeprecationWarningand are ignored, instead of raising an assertion error.
Widened and extended APIs (non-breaking)
solve()now accepts adisplay=...callback that reports intermediate solutions during optimisation (supported for OR-Tools, Gurobi, GCS, CP Optimizer, HiGHS and Hexaly), likesolveAll()already did #561.solve(assumptions=...)accepts any iterable of Boolean literals, not just a list #712.- Global constraint and function constructors are formally typed with the new
ExprLike/ListLiketype aliases and accept any list-like (list, tuple, numpy array,NDVarArray) of expressions or constants, including numpy integers #871 #873 #874 #877. SolverLookup.supported()gained asubsolvers=flag to optionally exclude subsolver names from the list #1030.- The parameter tuners can tune over multiple problem instances at once #757 and take a
verbose=level #771. - Several globals gained a positive-context decomposition (
decompose_positive()) for use in linear/SAT contexts #980 #1006, andAllDifferenta linear one (decompose_linear()) #836. - For advanced users:
Expression.implies(..., simplify=),flatten_constraint(..., do_simplify=)anddecompose_in_tree(..., decompose_custom=, decompose_custom_positive=)expose new optional behavior.
Internal improvements
- Improvements to toplevel safening #1026
- New pattern for
simplify_boolean#959 push_down_negationbeforedecompose#916- Improved MUS for Gurobi #993 #1016
- Improved
GCCdecomposition #1007 - Save
lhsandrhsof reification toCSEMap#1010 - Reduction technique for MDD #949
- Use order encoding for inequalities #994
- Linear decomposition for
TableusingMDD#945 - Fast path solver var #995
- Canonicalize inequalities #996
- Refactor
decomposein typed pattern #929 - Simple
solver_varsimprovements #992 - Canonicalize
neqcomparison inCSEMap#969 - Avoid making auxiliary variables in
safening#935 - Loop optimisations #928
- Refactor
push_down_negation#914 - Make
expr._has_subexpran attribute #895 - Simplify
CircuitandInversedecompositions #889 - Refactor safening transformation #875
- PySAT pb reification optimisation; let PySAT handle conditionals #783
- Optimize
has_subexprfor tables #596 - Refactor
decompose_in_tree#835
Changed
- Remove very old deprecated code #1050
- Improve docs for testing a (new) solver #1031
- Standardize reporting intermediate solutions #561
- Remove
frozendictdependency #1019 - Varname in varmap instead of variable object #990
- GCS refresh install docs, sanitise variable names with commas #973 (thanks Ciaran)
- Update incrementality test #976
- Convert assumptions to list #712
- Modernize examples
- Print display in solver interface superclass #921
- Small docs/solvers update to be more complete #920
- Modern
__init__files #905 linearizedoc update #898- Use is None ruff warning #904
- Remove import * from cpmpy/ tests/ and docs/ #890
- Don't download examples data files during testsuite #899
- Refactor infix printing in
Operatorfor improved readability #893 - Seperate GitHub action runners #885
- Upgrade pindakaas to v0.5.0 & re-enable BVA for CaDiCal #852 #869
- Upgrade Z3 lower version to v5.0.0 for upstream bugfix #1052
- Upgrade Pumpkin interface to v0.3.0 #854
- OR-Tools and Python version bumps #847
- Remove solver version upper limits #848
- Convert TestCase to plain pytest classes #818
Fixed
- Pumpkin
InDomainhas_subexpr#1057 - XCSP3 fixes to parser callbacks #1056 #1058
- Remove 'mul' from supported for CPLEX #1045
- Throw
NotImplementedErrorfor base classesGlobalConstraintandGlobalFunction#1047 - XCSP3 runner 'verbose' option #962
- Implication with single bool in lhs #1043
NDVarArrayvectorized ops to use NumPy broadcasting #1043- Decomposition bugs in
RegularandMDD#1039 - Remove trivial wrapping of bool in linearize_constraint #1042
- Trivial decompose crash with 0 constraints #1041
- Flattening of wsum remove in-place edits #1040
- Fix
Expr OP NDArrby doingNDArr ROP Expr#1035 - Solver var consistent return type #1017
- Enforce version upper limit on HiGHS due to upstream bug #1032
- Enforce version upper limit on Choco due to upstream bug #1013
- Missing RC2 depdendencies in setup.py #1028
Tablesubexpression check #1029- Mark inconsistent tests as 'flaky' #1024
- Fix tests and update
requires_solverdocs #1002 - Edge case in
canonical_comparison#1014 - Bounds for
powandmod#1012 - Fix URL PSPLib #1004
- Read integers with
roundinstead ofintfor ILP solvers #884 - Set stale values to None after infeasability (Exact and pindakaas) #1001
- Upstream criptominisat fix, remove suppression #978
- Re-export
make_assump_modelfromtools.explainfor legacy code #974 - Edge case in tuner where time limit can become negative #970
- Use
get_or_make_varfor end task variables #964 - Don't store
endargs when not given forCumulativeandNopOverlap#830 - MiniZinc
time_limitdeprecation warning #927 - Remove duplicate
Comparisonhandling #951 - Forgotten comma in
supported_globalsOR-Tools #942 Xorsimplification in decomposition #908- Fix
self +=pattern #930 - Warnings in testsuite #919
- Re-introduce deprecated variable functions #915
- Resolve duplicate test names #863
- Update expression test to not randomly fail #865
- Fix solution values in int2bool #857
- Skip tests if Pumpkin not supported #856
- Make tests solver-independent #779
test_transf_compwithout increasing counters #638- Update value checks on
Xor,CumulativeandCircuit#872
Full Changelog: v0.10.1...v1.0.0