Skip to content

XCSP3 competition #684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 261 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
261 commits
Select commit Hold shift + click to select a range
bc49f24
update pysdd
IgnaceBleukx Jan 28, 2025
5602a13
solve from model instead of solver obj
IgnaceBleukx Jan 28, 2025
71cb367
add semantics of status to docs
IgnaceBleukx Feb 18, 2025
93ec003
Merge remote-tracking branch 'origin/master' into solve_status
IgnaceBleukx Feb 18, 2025
56bc777
update test for solveAll
IgnaceBleukx Feb 18, 2025
3787f8d
add callbacks to tools
Wout4 Feb 20, 2025
a2fd4ee
Merge branch 'master' into tools_xcsp3
Wout4 Feb 20, 2025
10b8cc9
add solution formatting in xml
Wout4 Feb 20, 2025
0f4cc45
add xcsp3 globals
Wout4 Feb 21, 2025
180db05
more globals
Wout4 Feb 21, 2025
47e5fca
more globals
Wout4 Feb 21, 2025
4b029f8
fix imports
Wout4 Feb 21, 2025
9e0ac46
optimize has_subexpr for tables
Wout4 Feb 21, 2025
2f9d93b
optimize has_subexpr for tables
Wout4 Feb 21, 2025
4d0b1f9
basic model runner
Wout4 Feb 21, 2025
c0b60af
write timings to csv
Wout4 Feb 21, 2025
6dbfa3f
add installer to download instances
Wout4 Feb 21, 2025
d0beb0c
add installer to run_model
Wout4 Feb 21, 2025
01efa75
cleanup prints
Wout4 Feb 21, 2025
1a4bc87
option to only run transform
Wout4 Feb 21, 2025
012af56
disable garbage collection
Wout4 Feb 24, 2025
65d7628
also write solver to csv
Wout4 Feb 24, 2025
af4dd1c
add t_transform t_add and t_parse
Wout4 Feb 24, 2025
4ab2b94
fix only_transform parameter
Wout4 Feb 24, 2025
062b82f
ifthenelsenum in xglobals.py
Wout4 Feb 24, 2025
5963581
ifthenelsenum in xglobals.py
Wout4 Feb 24, 2025
f556353
add timelimit, and only 6 cores for ortools
Wout4 Feb 24, 2025
13e8eb9
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
Feb 24, 2025
00b9436
Merge branch 'table_has_subexpr' into tools_xcsp3
Wout4 Feb 25, 2025
7c9fc9c
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
Feb 25, 2025
3574340
cleanup printing a bit
tias Feb 26, 2025
f74590c
timings results
Feb 28, 2025
02692ef
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
Feb 28, 2025
31c4f87
add simple plots for results
Wout4 Feb 28, 2025
72e5938
refactor file structure
Wout4 Feb 28, 2025
f5f28f5
remove has_subexpr from this pr
Wout4 Feb 28, 2025
472b02a
Add some docstrings
ThomSerg Mar 19, 2025
964ea90
copy competition runner as-is
ThomSerg Mar 19, 2025
0370d5c
Make competition executable runnable
ThomSerg Mar 19, 2025
c0202e0
Add basic CLI to downloader
ThomSerg Mar 19, 2025
5559052
Add some notes
ThomSerg Mar 19, 2025
bfd7ba4
Small typos in docs
ThomSerg Apr 9, 2025
f777f0e
Merge remote-tracking branch 'origin/master' into solve_status
ThomSerg Apr 16, 2025
e31f136
Update solve status
ThomSerg Apr 17, 2025
8f97701
Update solveAll
ThomSerg Apr 17, 2025
2047356
Update test
ThomSerg Apr 17, 2025
58da4a0
Update another test
ThomSerg Apr 18, 2025
63a4b74
Merge remote-tracking branch 'origin/master' into solve_status
ThomSerg Apr 18, 2025
4231b43
remove unfinished benchmarking script
tias Apr 23, 2025
a9eca83
remove unfinished csv comparison functions
tias Apr 23, 2025
223664f
Fix xglobals renaming
ThomSerg Apr 24, 2025
b014fe2
Merge remote-tracking branch 'origin/tools_xcsp3' into tools_xcsp3
ThomSerg Apr 24, 2025
98f6a87
Fix downloader script for 2023
ThomSerg Apr 24, 2025
0c1f65b
Merge branch 'master' into tools_xcsp3
ThomSerg May 9, 2025
3e3e0d3
replace downloader by generic xcsp3 dataset
tias May 9, 2025
357d7ec
initial benchmark script, updated dataset
tias May 9, 2025
743e735
first analysis tool, plots performance profile
tias May 10, 2025
ec7d524
refactor the runner (not yet a full cleanup)
tias May 10, 2025
277e660
update benchmark to use runner
tias May 10, 2025
91bbfb1
Merge remote-tracking branch 'refs/remotes/origin/tools_xcsp3' into t…
tias May 10, 2025
f42e2c5
benchm: add subtimings
tias May 10, 2025
ea3df8f
bench: nice tqdm progress
tias May 10, 2025
f6711b2
limit handling updates
tias May 11, 2025
5b3455b
more robust error handling
tias May 12, 2025
7c1c1ba
File in memory for decompression
ThomSerg May 12, 2025
71da236
Duplicate streamls when verbose
ThomSerg May 12, 2025
05cc9db
Reusable CPMpy tools
ThomSerg May 12, 2025
c6e8205
Merge remote-tracking branch 'origin/master' into tools_xcsp3
ThomSerg May 12, 2025
c6bd37e
Add missing init file
ThomSerg May 12, 2025
9a86ca2
Add xcsp3-specific native constraints
ThomSerg May 14, 2025
868b7aa
Temp disable table type checks
ThomSerg May 15, 2025
4051f44
Ensure benchmark doesn't hang forever
ThomSerg May 15, 2025
df7dce5
Add added_natives to other solvers
ThomSerg May 15, 2025
2ae74b6
Benchmark enforce hard timeout
May 15, 2025
2f377ff
Typo in type hint for added_natives
ThomSerg May 15, 2025
7810623
Merge remote-tracking branch 'origin/tools_xcsp3' into tools_xcsp3
ThomSerg May 15, 2025
c301e99
Faster flatlist
ThomSerg May 15, 2025
e60817a
Proper cross-platform lockfiles
ThomSerg May 15, 2025
0b5afa2
Merge branch 'solve_status' into tools_xcsp3
ThomSerg May 15, 2025
a544089
Fix memory limit 0
ThomSerg May 15, 2025
d5af920
Remove leftover print statement
ThomSerg May 15, 2025
2eecebf
choco table star must be int
May 15, 2025
e94d2a5
disable mem-limit for choco due to GraalVM
May 15, 2025
7baa353
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
May 15, 2025
e22f88c
fix gurobi args
May 15, 2025
6beda7c
Add natives to exact
ThomSerg May 15, 2025
c41f5c4
Add solver args for cpo
May 15, 2025
35772df
Small improvements to analyser
May 15, 2025
e3dcf6a
xcsp3 dataset: tweaks
tias May 15, 2025
77d113b
cleanup, raising/catching
tias May 15, 2025
afaeef0
benchmark: use 'status', some cleanups
tias May 15, 2025
776e5b6
analyze: use status
tias May 15, 2025
0e37e82
more raising for better benchmark logs
tias May 15, 2025
e16b494
bench: exit status fix
tias May 15, 2025
45a681c
default mem to 8gb to avoid the memouts on 2024 minicop
tias May 15, 2025
a1a9449
cpmpy cmd: also uncompress lzma on the fly
tias May 15, 2025
8a24a71
bench: more robust error catching/logging
tias May 15, 2025
823aaff
RLIMIT_CPU approach
ThomSerg May 16, 2025
725cdb5
function for performance profiles
ThomSerg May 16, 2025
446b994
Add missing xcsp3 global
ThomSerg May 16, 2025
33e96e0
also unsat is valid outcome
tias May 16, 2025
4c1ac39
Merge remote-tracking branch 'refs/remotes/origin/tools_xcsp3' into t…
tias May 16, 2025
a26456c
simplify multiprocessing...
tias May 16, 2025
c0ee946
Revert "simplify multiprocessing..."
tias May 16, 2025
2dcf277
quick fixes
tias May 17, 2025
edd570b
I don't get where the total time overhead comes from
tias May 17, 2025
fb5d60f
more lzma decompr out of main
tias May 17, 2025
38062fb
InDomain now cpmpy global (and better implemented)
tias May 17, 2025
adde167
Competition os signals
ThomSerg May 19, 2025
7ce6539
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
ThomSerg May 19, 2025
71eff77
Extract signal handlers
ThomSerg May 19, 2025
2f1eaca
Clean exit on signal
ThomSerg May 19, 2025
73af484
Cleanup
ThomSerg May 19, 2025
5f3a839
xcsp3 analyze: print nr of solved in legend
tias May 19, 2025
6cabb23
cpo cumulative 0-duration tasks
ThomSerg May 20, 2025
9dcc866
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
ThomSerg May 20, 2025
eb31a22
cpo solution callback
ThomSerg May 20, 2025
f2ae34d
xcsp3 cpo intermediate solutions
ThomSerg May 20, 2025
7973872
xcsp3 benchmark intermediate solutions
ThomSerg May 20, 2025
396c118
benchmark capture intermediate obj
ThomSerg May 20, 2025
ff5c46e
analyse perf profile intermediate obj
ThomSerg May 20, 2025
05fac42
cpo intermediate missing line
ThomSerg May 20, 2025
4f6dfc3
use process start time
ThomSerg May 20, 2025
b21857f
1s time buffer
ThomSerg May 20, 2025
89ba19f
avoid benchmark getting stuck
ThomSerg May 20, 2025
6991328
Move adapted shorttable to xcsp3 globals
ThomSerg May 20, 2025
651bfc1
add postive-variant of shorttable
IgnaceBleukx May 20, 2025
c2f7681
Merge branch 'tools_xcsp3' of github.com:CPMpy/cpmpy into tools_xcsp3
IgnaceBleukx May 20, 2025
badc6a9
only positive decomp for shortttable
IgnaceBleukx May 20, 2025
43cd1b9
update regular constraint
IgnaceBleukx May 20, 2025
489885c
add naive context switch in flatten_constraint
IgnaceBleukx May 20, 2025
cce98cb
allow to run 1 specific instance in benchmark-mode
IgnaceBleukx May 20, 2025
dc9b150
Add lzma decompression to read_xcsp3
ThomSerg May 20, 2025
a9cc97a
Add documentation
ThomSerg May 20, 2025
949de07
Merge remote-tracking branch 'origin/tools_xcsp3' into tools_xcsp3
ThomSerg May 20, 2025
e6264d4
Undo benchmark instance
ThomSerg May 20, 2025
bb076de
Revert added_natives
ThomSerg May 20, 2025
1538f8f
Revert more changes to core CPMpy
ThomSerg May 20, 2025
07aed9d
Simplify line parsing
ThomSerg May 20, 2025
aaef741
comment -nocompile (needed for docs but breaks cli)
ThomSerg May 20, 2025
3a53128
Fix simplified line parsing
ThomSerg May 20, 2025
1d1fa5f
Disable test for pysdd
ThomSerg May 20, 2025
00868ba
Merge branch 'master' into tools_xcsp3
ThomSerg May 21, 2025
9ba893e
Remove unused imports
ThomSerg May 21, 2025
388f8fa
Merge branch 'tools_xcsp3' into xcsp3_competition
ThomSerg May 21, 2025
674fb1f
Revert "add naive context switch in flatten_constraint"
ThomSerg May 21, 2025
ae870e7
Convert natives to directconstraints
ThomSerg May 21, 2025
ba7b4e3
Scan and replace natives
ThomSerg May 21, 2025
150036f
Basic readme
ThomSerg May 21, 2025
64343fb
xcsp3 as optional dep
ThomSerg May 21, 2025
0dfea3c
Small fix for natives args
ThomSerg May 21, 2025
d46a2ab
Extract objective from solution
ThomSerg May 21, 2025
e6f0be4
Merge branch 'tools_xcsp3' of https://github.com/CPMpy/cpmpy into too…
ThomSerg May 21, 2025
211f92d
args to _args
ThomSerg May 21, 2025
a7b7e0b
Fix natives init
ThomSerg May 21, 2025
cd1b845
Merge branch 'tools_xcsp3' into xcsp3_competition
ThomSerg May 21, 2025
dd61512
Add automated benchmark runner
ThomSerg May 21, 2025
9088acf
Add result copier of latest runs
ThomSerg May 21, 2025
ba04259
Solvers without added_natives
ThomSerg May 21, 2025
69ba401
Add filelock to requirements
ThomSerg May 21, 2025
ab825ae
Merge branch 'tools_xcsp3' into xcsp3_competition
ThomSerg May 21, 2025
f7ff2f4
trying to speed up the tables/shorttables
tias May 22, 2025
a1cb719
Add ECDF plotting tool
ThomSerg May 22, 2025
be3cf5b
Merge branch 'master' into xcsp3_competition
IgnaceBleukx May 22, 2025
29ed626
Slightly improved analyze plot
ThomSerg May 22, 2025
2c33361
Merge branch 'xcsp3_competition' of https://github.com/CPMpy/cpmpy in…
ThomSerg May 22, 2025
977eb33
re-add positive context to flattening
IgnaceBleukx May 22, 2025
78c192f
disable rewrite of BV1 -> BV2 == BV3
IgnaceBleukx May 22, 2025
34c59b4
Merge branch 'xcsp3_competition' of github.com:CPMpy/cpmpy into xcsp3…
IgnaceBleukx May 22, 2025
b1826ae
Optional solution checker
ThomSerg May 22, 2025
d58ea4d
Merge remote-tracking branch 'origin/xcsp3_competition' into xcsp3_co…
ThomSerg May 22, 2025
4a99198
Consistent colors
ThomSerg May 22, 2025
995b570
Benchmark ignore warnings
ThomSerg May 22, 2025
c0d5168
Merge branch 'xcsp3_competition' of https://github.com/CPMpy/cpmpy in…
ThomSerg May 22, 2025
9dffecc
Disable checker by default
ThomSerg May 23, 2025
741fed3
OR-Tools solver options
ThomSerg May 23, 2025
b7ee491
Missing check
ThomSerg May 23, 2025
b596938
Missing "not" in ortools options
ThomSerg May 23, 2025
1015a5d
Add yappi profiler
ThomSerg May 23, 2025
446a89b
add MapDomain global
tias May 25, 2025
ab1c32d
decompose of MapDomain
tias May 25, 2025
e0e4268
use ort's native MapDomain
tias May 25, 2025
3d19422
add MapDomain to decomp of ShortTable
tias May 25, 2025
e05fac6
add MapDomain to some more xcsp3 globals
tias May 25, 2025
9902eff
AddMapDomain not yet in my ort version!?
tias May 25, 2025
e24512e
Small shape bug
ThomSerg May 26, 2025
35c3641
Fix MapDomain native global constraint ortools
ThomSerg May 26, 2025
8030a1f
untested table decomp of gleb
tias May 27, 2025
a89c32f
untested element, ilp gleb style
tias May 27, 2025
6d5293c
add small intro example of gleb paper
tias May 27, 2025
a113f64
use var == var in alldiff linearize
IgnaceBleukx May 27, 2025
e1d6f79
use xglobals.element for 2d array
IgnaceBleukx May 27, 2025
f5c4d6c
add decompose_numerical to element
IgnaceBleukx May 27, 2025
658b72c
do flatlist
IgnaceBleukx May 27, 2025
beef21d
more work on linear's alldifferent with MapDomain
tias May 28, 2025
447e168
globals, some ILP friendly with MapDomain
tias May 28, 2025
6463a92
getting the inline element to work...
tias May 28, 2025
279d68f
getting count ILP friendly too, and a range of side effects exposed
tias May 28, 2025
84737df
Fix expression order
ThomSerg May 28, 2025
0dbb8ee
Fix small decomposition issues
ThomSerg May 28, 2025
d48b622
Fix ints in global function
ThomSerg May 30, 2025
fb7ec82
Fix missing constraint in table
ThomSerg May 31, 2025
61bc086
IP decomposition for regular
ThomSerg May 31, 2025
192f67b
Use xglobals table
ThomSerg Jun 3, 2025
4516a2f
Missing cp ref for exact
ThomSerg Jun 4, 2025
11b7322
Don't add aux vars to user vars
ThomSerg Jun 4, 2025
7fceb5f
Linearize decomposed MapDomain (for exact)
ThomSerg Jun 4, 2025
82958a5
Standardize internal adding of constraints
ThomSerg Jun 4, 2025
a3fe247
Leftover :
ThomSerg Jun 4, 2025
2d6d6dc
Prevent aux vars from obj in uservars
ThomSerg Jun 4, 2025
9f7477d
Add task-resource decomp for cumulative
ThomSerg Jun 4, 2025
40270f2
Alternative GCC decomposition
ThomSerg Jun 4, 2025
e8f4d79
Remove unnecessary constraint in new table decomp
ThomSerg Jun 4, 2025
9909c8b
Even more aux vars in user vars
ThomSerg Jun 4, 2025
a56c9c2
Even more hidden aux vars
ThomSerg Jun 5, 2025
9adb579
Filter for all the rest
ThomSerg Jun 5, 2025
7d4d31d
Default time buffer of 0
ThomSerg Jun 5, 2025
a58e9ce
Add missing pycsp3 dependency
ThomSerg Jun 5, 2025
0b57bbc
add cpo config sample file
ThomSerg Jun 5, 2025
09b6488
Add subpath to bin dir
ThomSerg Jun 5, 2025
2e4f742
Add competition instructions
ThomSerg Jun 5, 2025
10eb619
Add fast track
ThomSerg Jun 5, 2025
eedbc87
Small disclaimer tested version rocky linux
ThomSerg Jun 5, 2025
8532f19
Add to aux var filter check
ThomSerg Jun 5, 2025
b169526
Add minizinc solvers and // track
ThomSerg Jun 5, 2025
88af77b
Small mistake in table
ThomSerg Jun 5, 2025
2573ddd
MapDomain earlier in alldiff
ThomSerg Jun 5, 2025
75d37a0
Merge branch 'xcsp3_competition' of https://github.com/CPMpy/cpmpy in…
ThomSerg Jun 5, 2025
6b240c9
Cumulative dynamic decomp out of parser
ThomSerg Jun 5, 2025
1504755
Revert "Cumulative dynamic decomp out of parser"
ThomSerg Jun 5, 2025
21b1237
Default time buffer to 1s (same as benchmark)
ThomSerg Jun 5, 2025
697bde0
Hacky Z3 dummy natives
ThomSerg Jun 5, 2025
772bd91
Merge branch 'xcsp3_competition' of https://github.com/CPMpy/cpmpy in…
ThomSerg Jun 5, 2025
31cbf69
Add multicore option & make mem enforcement optional
ThomSerg Jun 5, 2025
51c36f5
Merge branch 'xcsp3_competition' of https://github.com/CPMpy/cpmpy in…
ThomSerg Jun 5, 2025
27d25bc
Fix AllEqual adding a generator
ThomSerg Jun 5, 2025
a121e59
Add missing bound restrictions in InDomain
ThomSerg Jun 5, 2025
b6fdc66
MapDomain breaks 'count' in combination with 'NotInDomain'
ThomSerg Jun 5, 2025
7a5ce7c
Remove bad alternative decomp for NotInDomain
ThomSerg Jun 5, 2025
4f2150d
Missing cp.
ThomSerg Jun 5, 2025
9e758ff
Fixes to parser for new xcsp3 expressions
ThomSerg Jun 5, 2025
305987a
Add missing vars to user vars
ThomSerg Jun 13, 2025
b99829d
Minizinc replace # in variable name
ThomSerg Jun 13, 2025
e54b696
Minizinc hack non-standard posting of DirectConstraint
ThomSerg Jun 13, 2025
3cf7d8b
Z3 nested globals in objective
ThomSerg Jun 13, 2025
ca50254
Exact recalculate objective
ThomSerg Jun 13, 2025
1a410b7
Add Minizinc subcircuitwithstart
ThomSerg Jun 13, 2025
7b7d1bd
Fix SubCircuitWithStart decomposition
ThomSerg Jun 13, 2025
47b27fb
Always flatten objective for Z3
ThomSerg Jun 13, 2025
1e446ea
Add count to second decompose_numerical location
ThomSerg Jun 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpmpy/expressions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ def _wsum_should(arg):
all substractions are transformed into less readable wsums)
"""
return isinstance(arg, Operator) and \
(arg.name == 'wsum' or \
(arg.name == 'sum' or arg.name == 'wsum' or \
(arg.name == 'mul' and len(arg.args) == 2 and \
any(is_num(a) for a in arg.args)
) )
Expand Down
125 changes: 117 additions & 8 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,18 @@ def __init__(self, *args):
def decompose(self):
"""Returns the decomposition
"""
return [var1 != var2 for var1, var2 in all_pairs(self.args)], []
if False:
return [var1 != var2 for var1, var2 in all_pairs(self.args)], []
else:
# DO NOT COMMIT IN MAINLINE yet
# switch to ILP friendly decomposition
cons = []
lbs, ubs = get_bounds(self.args)
M = [cp.MapDomain(var) for var in self.args if isinstance(var, Expression)]
for val in range(min(lbs), max(ubs)+1):
# each value can be taken at most once (not necessarily exactly once)
cons.append(cp.sum(x == val for x in self.args) <= 1)
return cons, M

def value(self):
return len(set(argvals(self.args))) == len(self.args)
Expand All @@ -210,8 +221,20 @@ def __init__(self, arr, n):
super().__init__("alldifferent_except_n", [flatarr, n])

def decompose(self):
# equivalent to (var1 == n) | (var2 == n) | (var1 != var2)
return [(var1 == var2).implies(cp.any(var1 == a for a in self.args[1])) for var1, var2 in all_pairs(self.args[0])], []
if False:
# equivalent to (var1 == n) | (var2 == n) | (var1 != var2)
return [(var1 == var2).implies(cp.any(var1 == a for a in self.args[1])) for var1, var2 in all_pairs(self.args[0])], []
else:
# DO NOT COMMIT IN MAINLINE yet
# switch to ILP friendly decomposition
cons = []
arr, n = self.args
lbs, ubs = get_bounds(arr)
for val in range(min(lbs), max(ubs)+1):
if val != n:
# each value can be taken at most once (not necessarily exactly once)
cons.append(cp.sum(x == val for x in arr) <= 1)
return cons, [MapDomain(x) for x in arr]

def value(self):
vals = [argval(a) for a in self.args[0] if argval(a) not in argvals(self.args[1])]
Expand Down Expand Up @@ -246,8 +269,19 @@ def __init__(self, *args):
def decompose(self):
"""Returns the decomposition
"""
# arg0 == arg1, arg1 == arg2, arg2 == arg3... no need to post n^2 equalities
return [var1 == var2 for var1, var2 in zip(self.args[:-1], self.args[1:])], []
# Not sure we need the Boolean version here...
if False:
# arg0 == arg1, arg1 == arg2, arg2 == arg3... no need to post n^2 equalities
return [var1 == var2 for var1, var2 in zip(self.args[:-1], self.args[1:])], []
else:
# DO NOT COMMIT IN MAINLINE yet
# switch to ILP friendly decomposition
cons = []
lbs, ubs = get_bounds(self.args)
for val in range(min(lbs), max(ubs)+1):
# each value can be taken at most once (not necessarily exactly once)
cons.extend((x1 == val) == (x2 == val) for x1,x2 in all_pairs(self.args))
return cons, [MapDomain(x) for x in self.args]

def value(self):
return len(set(argvals(self.args))) == 1
Expand All @@ -265,8 +299,21 @@ def __init__(self, arr, n):
super().__init__("allequal_except_n", [flatarr, n])

def decompose(self):
return [(cp.any(var1 == a for a in self.args[1]) | (var1 == var2) | cp.any(var2 == a for a in self.args[1]))
for var1, var2 in all_pairs(self.args[0])], []
# Not sure we need the Boolean version here...
if False:
return [(cp.any(var1 == a for a in self.args[1]) | (var1 == var2) | cp.any(var2 == a for a in self.args[1]))
for var1, var2 in all_pairs(self.args[0])], []
else:
# DO NOT COMMIT IN MAINLINE yet
# switch to ILP friendly decomposition
cons = []
arr, n = self.args
lbs, ubs = get_bounds(arr)
for val in range(min(lbs), max(ubs)+1):
if val != n:
# each value can be taken at most once (not necessarily exactly once)
cons.append((x1 == val) == (x2 == val) for x1,x2 in all_pairs(arr))
return cons, [MapDomain(x) for x in arr]

def value(self):
vals = [argval(a) for a in self.args[0] if argval(a) not in argvals(self.args[1])]
Expand Down Expand Up @@ -303,6 +350,7 @@ def decompose(self):
MiniZinc has slightly different one:
https://github.com/MiniZinc/libminizinc/blob/master/share/minizinc/std/fzn_circuit.mzn
"""
# XXX Right, so for ILP solvers, we would ideally do lazy subcircuit elimination (otherwise MTZ)...
succ = cpm_array(self.args)
n = len(succ)
order = intvar(0,n-1, shape=n)
Expand Down Expand Up @@ -386,6 +434,7 @@ def decompose(self):

lb, ub = get_bounds(x)
if lb >= 0 and ub < len(rev): # safe, index is within bounds
# XXX Both rev and x are variables... is there an ILP friendly decomp?
constraining.append(rev[x] == i)
else: # partial! need safening here
is_defined, total_expr, toplevel = cp.transformations.safening._safen_range(rev[x], (0, len(rev)-1), 1)
Expand Down Expand Up @@ -620,7 +669,10 @@ def decompose(self):
if expressions:
return [cp.any(expr == a for a in arr)], defining
else:
return [expr != val for val in range(lb, ub + 1) if val not in arr], defining
# XXX do we properly capture that x!=v with b:=x==v should be ~b?
# Can't do MapDomain, would need to check if `expr` is a variable...
# TODO is this even efficient when there are many gaps? (similar to NotInDomain)
return [expr != val for val in range(lb, ub + 1) if val not in arr] + [expr >= min(arr), expr <= max(arr)], defining


def value(self):
Expand All @@ -629,6 +681,62 @@ def value(self):
def __repr__(self):
return "{} in {}".format(self.args[0], self.args[1])

class MapDomain(GlobalConstraint):
"""
Maps an integer decision variable to an array of Boolean variables,
one for each value in the domain, e.g. |ub+1 - lb| Boolean variables.

No Boolean variables are returned or accessible;
after declaring this constraint, just
use 'ivar == v' and it will be replaced by the correct Boolean ?in flatten()?.

Note: the `decompose()` takes an optional `csemap` and `is_supported` argument!

TODO: what with solvers (e.g. SMT) that do not need flatten?
(they also don't need us to do CSE, so might be fine?)

TODO: multiple (e.g. by global constraints) MapDomain's can be declared
for the same integer variable...
"""
def __init__(self, ivar):
super().__init__("mapdomain", [ivar])

def decompose(self, is_supported=False, csemap=None):
"""
is_supported: if True, only the CSE map is filled
(yes, ortools declares a global for this, called... MapDomain )
csemap: if given, will populate the csemap with the Boolean variables
"""
ivar = self.args[0]
lb, ub = get_bounds(ivar)

bvs = cp.boolvar(shape=(ub+1-lb,), name=f"B#{ivar.name}")
all_in_csemap = True
if csemap is not None:
for i,v in enumerate(range(lb, ub+1)):
expr = (ivar == v)
if expr in csemap:
bvs[i] = csemap[expr] # overwrite with pre-created one
else:
all_in_csemap = False
csemap[expr] = bvs[i]

if is_supported:
# TRICKY HACK to use 2nd argument as all_in_csemap...
return [], all_in_csemap

# ILP friendly decomposition
# TODO: if the 'ivar' is eliminated from the model, no need for 2nd constraint...
# TRICKY HACK to use 2nd argument as all_in_csemap...
return [cp.sum(bvs) == 1,
cp.sum(bvs[i]*v for i,v in enumerate(range(lb, ub+1))) == ivar], all_in_csemap

def value(self):
# not much to say...
return True

def __repr__(self):
return f"MapDomain({self.args[0]})"

class Xor(GlobalConstraint):
"""
Expand All @@ -648,6 +756,7 @@ def __init__(self, arg_list):

def decompose(self):
# there are multiple decompositions possible, Recursively using sum allows it to be efficient for all solvers.
# and ILP friendly...
decomp = [sum(self.args[:2]) == 1]
if len(self.args) > 2:
decomp = Xor([decomp,self.args[2:]]).decompose()[0]
Expand Down
10 changes: 10 additions & 0 deletions cpmpy/expressions/globalfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ def decompose_comparison(self, cmp_op, cmp_rhs):
arr, val = self.args
return [eval_comparison(cmp_op, Operator('sum',[ai==val for ai in arr]), cmp_rhs)], []

def decompose_numerical(self):
"""
Return a numerical expression to replace the array loopup with in the expression tree
XXX DOES NOT work automatically, needs hacking into decompose_global.py
"""
arr, val = self.args
expr = cp.sum(ai == val for ai in arr)
return expr, []# [cp.expressions.globalconstraints.MapDomain(ai) for ai in arr] <- somehow breaks the constraint in the setting "count(..., ...) not in [...]""


def value(self):
arr, val = self.args
val = argval(val)
Expand Down
9 changes: 5 additions & 4 deletions cpmpy/solvers/choco.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def objective(self, expr, minimize):

# make objective function non-nested
obj_var = intvar(*get_bounds(expr))
self += obj_var == expr
self.add(obj_var == expr, internal=True)

self.obj = obj_var
self.minimize_obj = minimize # Choco has as default to maximize
Expand Down Expand Up @@ -406,7 +406,7 @@ def transform(self, cpm_expr):

return cpm_cons

def add(self, cpm_expr):
def add(self, cpm_expr, internal:bool=False):
"""
Eagerly add a constraint to the underlying solver.

Expand All @@ -425,7 +425,8 @@ def add(self, cpm_expr):
:return: self
"""
# add new user vars to the set
get_variables(cpm_expr, collect=self.user_vars)
if not internal:
get_variables(cpm_expr, collect=self.user_vars)
# ensure all vars are known to solver

# transform and post the constraints
Expand Down Expand Up @@ -596,7 +597,7 @@ def _get_constraint(self, cpm_expr):
table = table.astype(float) # nan's require float dtype
# Choco requires a wildcard value not present in dom of args,
# take value lower than anything else
chc_star = min(np.nanmin(table), *get_bounds(array)[0]) -1
chc_star = int(min(np.nanmin(table), *get_bounds(array)[0]) -1) # should be an int
chc_table = np.nan_to_num(table, nan=chc_star).astype(int).tolist()
return self.chc_model.table(self.solver_vars(array), chc_table, universal_value=chc_star, algo="STR2+")
elif cpm_expr.name == "regular":
Expand Down
3 changes: 2 additions & 1 deletion cpmpy/solvers/cpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
CPM_cpo
"""

import shutil
import time
import warnings

Expand Down Expand Up @@ -346,6 +345,8 @@ def objective(self, expr, minimize=True):

technical side note: any constraints created during conversion of the objective are permanently posted to the solver
"""
get_variables(expr, collect=self.user_vars)

dom = self.get_docp().modeler
if self.has_objective():
self.cpo_model.remove(self.cpo_model.get_objective_expression())
Expand Down
24 changes: 12 additions & 12 deletions cpmpy/solvers/exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ def solve(self, time_limit=None, assumptions=None, **kwargs):
self.cpm_status.runtime = end - start

self.objective_value_ = None

self._fillVars()
if self.has_objective():
self.objective_value_ = self.objective_.value()

# translate exit status
# see 'toOptimum' documentation:
# https://gitlab.com/nonfiction-software/exact/-/blob/main/src/interface/IntProg.cpp#L877
Expand All @@ -235,19 +240,13 @@ def solve(self, time_limit=None, assumptions=None, **kwargs):
elif my_status == "INCONSISTENT": # found inconsistency over assumptions
self.cpm_status.exitstatus = ExitStatus.UNSATISFIABLE
elif my_status == "TIMEOUT": # found timeout
if self.xct_solver.hasSolution(): # found a (sub-)optimal solution
if self.objective_value_ is not None: # found a (sub-)optimal solution
self.cpm_status.exitstatus = ExitStatus.FEASIBLE
else: # no solution found
self.cpm_status.exitstatus = ExitStatus.UNKNOWN
else:
raise NotImplementedError(my_status) # a new status type was introduced, please report on github

self._fillVars()
if self.has_objective():
if self.objective_is_min_:
self.objective_value_ = obj_val
else: # maximize, so actually negative value
self.objective_value_ = -obj_val

# True/False depending on self.cpm_status
return self._solve_return(self.cpm_status)
Expand Down Expand Up @@ -423,10 +422,10 @@ def objective(self, expr, minimize):
self.objective_is_min_ = minimize

# make objective function non-nested and with positive BoolVars only
get_variables(expr, collect=self.user_vars) # add objvars to vars
(flat_obj, flat_cons) = flatten_objective(expr)
flat_obj = only_positive_bv_wsum(flat_obj) # remove negboolviews
self.user_vars.update(get_variables(flat_obj)) # add objvars to vars
self += flat_cons # add potentially created constraints
self.add(flat_cons, internal=True) # add potentially created constraints

# make objective function or variable and post
xct_cfvars,xct_rhs = self._make_numexpr(flat_obj,0)
Expand Down Expand Up @@ -517,7 +516,7 @@ def _add_xct_reif_right(self, head, sign, xct_cfvars, xct_rhs):
def is_multiplication(cpm_expr): # helper function
return isinstance(cpm_expr, Operator) and cpm_expr.name == 'mul'

def add(self, cpm_expr_orig):
def add(self, cpm_expr_orig, internal:bool=False):
"""
Eagerly add a constraint to the underlying solver.

Expand All @@ -537,7 +536,8 @@ def add(self, cpm_expr_orig):
"""

# add new user vars to the set
get_variables(cpm_expr_orig, collect=self.user_vars)
if not internal:
get_variables(cpm_expr_orig, collect=self.user_vars)

# transform and post the constraints
for cpm_expr in self.transform(cpm_expr_orig):
Expand All @@ -552,7 +552,7 @@ def add(self, cpm_expr_orig):
assert pkg_resources.require("exact>=2.1.0"), f"Multiplication constraint {cpm_expr} " \
f"only supported by Exact version 2.1.0 and above"
if is_num(rhs): # make dummy var
rhs = intvar(rhs, rhs)
rhs = cp.intvar(rhs, rhs)
xct_rhs = self.solver_var(rhs)
assert all(isinstance(v, _IntVarImpl) for v in lhs.args), "constant * var should be " \
"rewritten by linearize"
Expand Down
17 changes: 9 additions & 8 deletions cpmpy/solvers/gcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,11 @@ def objective(self, expr, minimize=True):
"""
# make objective function non-nested
(flat_obj, flat_cons) = flatten_objective(expr)
self += flat_cons # add potentially created constraints
self.add(flat_cons, internal=True) # add potentially created constraints
self.user_vars.update(get_variables(flat_obj)) # add objvars to vars

(obj, obj_cons) = get_or_make_var(flat_obj, csemap=self._csemap)
self += obj_cons
self.add(obj_cons, internal=True)

self.objective_var = obj

Expand Down Expand Up @@ -471,7 +471,7 @@ def verify(self, name=None, location=".", time_limit=None, display_output=False,

return self.veripb_return_code

def add(self, cpm_cons):
def add(self, cpm_cons, internal:bool=False):
"""
Post a (list of) CPMpy constraints(=expressions) to the solver
Note that we don't store the constraints in a cpm_model,
Expand All @@ -482,7 +482,8 @@ def add(self, cpm_cons):
"""
# add new user vars to the set
# add new user vars to the set
get_variables(cpm_cons, collect=self.user_vars)
if not internal:
get_variables(cpm_cons, collect=self.user_vars)

for con in self.transform(cpm_cons):
cpm_expr = con
Expand Down Expand Up @@ -551,10 +552,10 @@ def add(self, cpm_cons):
# lt == x < y
# gt == x > y
lt_bool, gt_bool = boolvar(shape=2)
self += (lhs < rhs) == lt_bool
self += (lhs > rhs) == gt_bool
self.add( (lhs < rhs) == lt_bool, internal=True )
self.add( (lhs > rhs) == gt_bool, internal=True )
if fully_reify:
self += (~bool_lhs).implies(lhs == rhs)
self.add( (~bool_lhs).implies(lhs == rhs), internal=True )
self.gcs.post_or_reif(self.solver_vars([lt_bool, gt_bool]), reif_var, False)
else:
raise NotImplementedError("Not currently supported by Glasgow Constraint Solver API '{}' {}".format)
Expand Down Expand Up @@ -665,7 +666,7 @@ def add(self, cpm_cons):
elif isinstance(cpm_expr, GlobalConstraint):
# GCS also has SmartTable, Regular Language Membership, Knapsack constraints
# which could be added in future.
self += cpm_expr.decompose() # assumes a decomposition exists...
self.add(cpm_expr.decompose(), internal=True) # assumes a decomposition exists...
else:
# Hopefully we don't end up here.
raise NotImplementedError(cpm_expr)
Expand Down
Loading
Loading