diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml index 2653d6dce5..87d70dcefb 100644 --- a/.github/workflows/maven-test.yml +++ b/.github/workflows/maven-test.yml @@ -7,7 +7,7 @@ on: [push, pull_request] jobs: test-ibex: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest # The different steps steps: - uses: actions/checkout@v4 @@ -27,7 +27,9 @@ jobs: # Regression tests - name: Test Ibex - run: mvn --file pom.xml test -Pcoverage -DtestFailureIgnore=true -Dgroups=ibex + run: | + export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/ibex/3rd + mvn --file pom.xml test -Pcoverage -DtestFailureIgnore=true -Dgroups=ibex test-solver: runs-on: ubuntu-latest diff --git a/CHANGES.md b/CHANGES.md index c20e3a6658..3afb740357 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,13 +14,19 @@ NEXT MILESTONE ### Other closed issues and pull requests: See [milestone 5.0.0-beta.2](https://github.com/chocoteam/choco-solver/milestone/41) +- Performance improvement in PropXPlusYEqZ and PropAbsolute - Fix bug in ConflictOrderingSearch (monitor not plugged) - Performance improvement for LastConflict, ConflictOrderingSearch and StrategySequencer. - Fix bug : regular constraints parsed from XCSP now support negative values +- Fix bug : Unexpected behavior when using Task with scalar (#1114) +- Replace Cumulative implementation by state-of-the-art implementation of TimeTabling and OverloadChecking +- Fix bug : IntVar no longer extends Iterable to avoid errors in nested loops +- Fix bug : Fix iterator issue when using ISet, especially within graph variables (#1174 and #1175) #### Contributors to this release: - [Charles Prud'homme](https://github.com/cprudhom) (@cprudhom) - [Jean-Guillaume Fages](https://github.com/jgFages) (@jgFages) +- [Arthur Godet](https://github.com/ArthurGodet) (@ArthurGodet) **Full Changelog**: https://github.com/chocoteam/choco-solver/compare/v5.0.0-beta.1...v5.0.0-beta.2 @@ -70,23 +76,6 @@ See [milestone 5.0.0](https://github.com/chocoteam/choco-solver/milestone/40) **Full Changelog**: https://github.com/chocoteam/choco-solver/compare/v4.10.17...v4.10.18 -4.10.17 - 23 Sep 2024 - ------------------- - - ### Hotfix - - Fix bug in `PropHybridTable` (#1102) - - **Full Changelog**: https://github.com/chocoteam/choco-solver/compare/v4.10.16...v4.10.17 - -4.10.16 - 12 Sep 2024 -------------------- - -### Hotfix -- Fix bug in `IntAffineView` (#1101) - -**Full Changelog**: https://github.com/chocoteam/choco-solver/compare/v4.10.15...v4.10.16 - - 4.10.17 - 23 Sep 2024 ------------------- diff --git a/Makefile b/Makefile index 666129168f..fea79341cb 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # like running test suites ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -PRETTY_DATE := $(shell date +'%m/%d/%Y %H:%M') +PRETTY_DATE := $(shell date +'%Y-%m-%dT%H:%M') DATE := $(shell date +'%y%m%d_%H%M') CURRENT_VERSION := $(shell mvn help:evaluate -Dexpression=project.version | grep -v "\[INFO\]" | grep -v "\[WARNING\]") @@ -53,8 +53,8 @@ tests : 1s 10s ibex checker mzn xcsp mps dimacs expl lcg mvn test -DtestFailureIgnore=true -Dgroups="$@" update_date: - @sed -i '' 's|\s*System.out.printf("c Choco.*|System.out.printf("c Choco%s ($(PRETTY_DATE))\\n", lcg? " with LCG" : "");|' parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java - @sed -i '' 's|\s*System.out.printf("%% Choco%s (.*|System.out.printf("%% Choco%s ($(PRETTY_DATE))\\n", lcg? " with LCG" : "");|' parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java + @sed -i '' 's|\s*System.out.printf("c Choco.*|System.out.printf("c Choco-solver%s ($(CURRENT_VERSION), $(PRETTY_DATE))\\n", lcg? " with LCG" : "");|' parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java + @sed -i '' 's|\s*System.out.printf("%% Choco.*|System.out.printf("%% Choco-solver%s ($(CURRENT_VERSION), $(PRETTY_DATE))\\n", lcg? " with LCG" : "");|' parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java compet: update_date clean package @@ -70,6 +70,11 @@ delmsc: @rm ~/.minizinc/solvers/choco-$(VERSION).msc docker: compet + # add an image with LCG support + @sed -i '' 's|python3.*|python3 "$$(dirname "$$0")/fzn-choco.py" "$$@" "-lcg"|' parsers/src/main/minizinc/fzn-choco.sh + @docker build -f $(ROOT_DIR)/parsers/src/main/minizinc/docker/Dockerfile.dms -t chocoteam/choco-solver-mzn:$(CURRENT_VERSION)-X $(ROOT_DIR) + # add an image without LCG support + @sed -i '' 's|python3.*|python3 "$$(dirname "$$0")/fzn-choco.py" "$$@"|' parsers/src/main/minizinc/fzn-choco.sh @docker build -f $(ROOT_DIR)/parsers/src/main/minizinc/docker/Dockerfile.dms -t chocoteam/choco-solver-mzn:$(CURRENT_VERSION) $(ROOT_DIR) antlr: diff --git a/examples/src/test/java/org/chocosolver/examples/integer/CostasArraysTest.java b/examples/src/test/java/org/chocosolver/examples/integer/CostasArraysTest.java index 1279f65f8d..2da5c75508 100644 --- a/examples/src/test/java/org/chocosolver/examples/integer/CostasArraysTest.java +++ b/examples/src/test/java/org/chocosolver/examples/integer/CostasArraysTest.java @@ -36,7 +36,7 @@ public void test(){ CostasArrays ca = new CostasArrays(); ca.execute(); Assert.assertEquals(ca.getModel().getSolver().getSolutionCount(), 1); - Assert.assertEquals(ca.getModel().getSolver().getNodeCount(), 6539); + Assert.assertEquals(ca.getModel().getSolver().getNodeCount(), 8_987); } @Test(groups = "10s", timeOut = 60000) diff --git a/examples/src/test/java/org/chocosolver/examples/integer/KnapsackTest.java b/examples/src/test/java/org/chocosolver/examples/integer/KnapsackTest.java index a71c669738..de6c3e89af 100644 --- a/examples/src/test/java/org/chocosolver/examples/integer/KnapsackTest.java +++ b/examples/src/test/java/org/chocosolver/examples/integer/KnapsackTest.java @@ -131,7 +131,7 @@ public void testOPT13() throws IOException, SetUpException { ks.solveIt(s, true); Assert.assertEquals(s.getSolver().getBestSolutionValue().intValue(), 2657, "obj val"); Assert.assertEquals(s.getSolver().getSolutionCount(), 3, "nb sol"); - Assert.assertEquals(s.getSolver().getNodeCount(), 60, "nb nod"); + Assert.assertEquals(s.getSolver().getNodeCount(), 59, "nb nod"); } @Test(groups = "1s", timeOut = 60000) diff --git a/parsers/pom.xml b/parsers/pom.xml index fb8b05f8d1..b84979cc2f 100644 --- a/parsers/pom.xml +++ b/parsers/pom.xml @@ -44,7 +44,7 @@ org.xcsp xcsp3-tools - 2.4 + 2.5 org.antlr @@ -114,11 +114,41 @@ org/apache/fontbox/** + + junit:junit + + ** + + + + maven-resources-plugin + 3.3.1 + + + copy-licenses + process-resources + + copy-resources + + + ${project.build.outputDirectory}/META-INF + + + ../target/classes/META-INF + + THIRD-PARTY-LICENSES.txt + + + + + + + maven-assembly-plugin 3.4.2 diff --git a/parsers/src/main/java/org/chocosolver/parser/RegParser.java b/parsers/src/main/java/org/chocosolver/parser/RegParser.java index f658cb1e27..ad74263227 100644 --- a/parsers/src/main/java/org/chocosolver/parser/RegParser.java +++ b/parsers/src/main/java/org/chocosolver/parser/RegParser.java @@ -95,8 +95,8 @@ public abstract class RegParser implements IParser { @Option(name = "-flush", forbids = {"-varsel"}, - usage = "Autoflush weights on black-box strategies (default: 32).") - protected int flushRate = 32; + usage = "Autoflush weights on black-box strategies (default: 20).") + protected int flushRate = 20; @Option(name = "-varsel", handler = VarSelHandler.class, @@ -166,7 +166,7 @@ public abstract class RegParser implements IParser { protected int nb_cores = 1; @Option(name = "-seed", usage = "Set the seed for random number generator. ") - protected long seed = 0L; + protected long seed = 1_000_000_007L; @Option(name = "--cp-profiler", usage = "Connect to CP-Profiler. Two comma-separated values are expected: the execution id and the port.") public String cpProfiler = null; diff --git a/parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java b/parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java index 920a13de4a..bffe196490 100644 --- a/parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java +++ b/parsers/src/main/java/org/chocosolver/parser/flatzinc/Flatzinc.java @@ -71,8 +71,8 @@ public enum CompleteSearch{ protected boolean oss = false; @Option(name = "-ocs", usage = "Opens the complementary search to all variables of the problem\n" + - "(default: OUTPUT, i.e., restricted to the variables declared in output).") - protected CompleteSearch ocs = CompleteSearch.OUTPUT; + "(default: ALL, i.e., complete the search with a search on all variables.).") + protected CompleteSearch ocs = CompleteSearch.ALL; //*********************************************************************************** // VARIABLES diff --git a/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FConstraint.java b/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FConstraint.java index c8df368d1b..2f3f50e0e8 100755 --- a/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FConstraint.java +++ b/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FConstraint.java @@ -22,6 +22,7 @@ import org.chocosolver.solver.expression.discrete.logical.NaLoExpression; import org.chocosolver.solver.variables.*; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; +import org.chocosolver.util.tools.ArrayUtils; import org.chocosolver.util.tools.VariableUtils; import java.util.ArrayList; @@ -752,25 +753,7 @@ public void build(Model model, Datas datas, String id, List exps, Li IntVar[] vars = exps.get(0).toIntVarArray(model); if (vars.length > 1) { - if(model.getSettings().isLCG()){ - if (model.getSettings().warnUser()) { - model.getSolver().log().white().println( - "Warning: fzn_alldifferent_except_0 constraint is decomposed (due to LCG)."); - } - IntIterableRangeSet values = new IntIterableRangeSet(); - for(int i = 0; i < vars.length; i++){ - values.addAll(vars[i]); - } - values.add(0); - int[] valuesArray = values.toArray(); - IntVar[] occurrences = new IntVar[valuesArray.length]; - for(int i = 0; i < valuesArray.length; i++){ - occurrences[i] = model.intVar(0, valuesArray[i] == 0?vars.length:1); - } - model.globalCardinality(vars, valuesArray, occurrences, true).post(); - }else { - model.allDifferentExcept0(vars).post(); - } + model.allDifferentExcept0(vars).post(); } } @@ -1079,21 +1062,11 @@ public void build(Model model, Datas datas, String id, List exps, Li final IntVar[] resources = exps.get(2).toIntVarArray(model); final IntVar limit = exps.get(3).intVarValue(model); String decomp = (String) model.getHook("CUMULATIVE"); + if (decomp == null) { + decomp = "GLB"; // TODO: make it configurable + } int n = starts.length; switch (decomp) { - case "GLB": - final IntVar[] ends = new IntVar[n]; - Task[] tasks = new Task[n]; - for (int i = 0; i < n; i++) { - ends[i] = model.intVar(starts[i].getName() + "_" + durations[i].getName(), - starts[i].getLB() + durations[i].getLB(), - starts[i].getUB() + durations[i].getUB(), - true); - assert durations[i].getUB() >= 0 && resources[i].getUB() >= 0; - tasks[i] = new Task(starts[i], durations[i], ends[i]); - } - model.cumulative(tasks, resources, limit, true/*, Cumulative.Filter.NAIVETIME*/).post(); - break; case "MZN": model.cumulativeTimeDec(starts, Arrays.stream(durations).mapToInt(IntVar::getUB).toArray(), @@ -1178,6 +1151,20 @@ public void build(Model model, Datas datas, String id, List exps, Li -resources[i].getValue() + limit.getValue()).post(); } break; + case "GLB": + default: + final IntVar[] ends = new IntVar[n]; + Task[] tasks = new Task[n]; + for (int i = 0; i < n; i++) { + ends[i] = model.intVar(starts[i].getName() + "_" + durations[i].getName(), + starts[i].getLB() + durations[i].getLB(), + starts[i].getUB() + durations[i].getUB(), + true); + assert durations[i].getUB() >= 0 && resources[i].getUB() >= 0; + tasks[i] = new Task(starts[i], durations[i], ends[i]); + } + model.cumulative(tasks, resources, limit/*, Cumulative.Filter.NAIVETIME*/).post(); + break; } } }, @@ -1716,6 +1703,9 @@ public void build(Model model, Datas datas, String id, List exps, Li for (int[] couple : t) { tuples.add(couple); } + if(x.length == 1) { + model.member(x[0], ArrayUtils.flatten(tuples.toMatrix())).post(); + } else if (x.length == 2) { model.table(x[0], x[1], tuples).post(); } else { diff --git a/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FVariable.java b/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FVariable.java index a74bacb1ea..8baf39001a 100755 --- a/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FVariable.java +++ b/parsers/src/main/java/org/chocosolver/parser/flatzinc/ast/FVariable.java @@ -249,10 +249,24 @@ private static IntVar buildOnExpression(String name, Expression expression, Data private static SetVar buildWithSet(String name, DSet type, Expression expression, Datas datas, Model model) { Declaration what = type.getWhat(); SetVar sv = null; - if(expression != null){ - Exit.log("Unknown expression"); + int[] ub = null; + if(expression != null) { + switch (expression.getTypeOf()) { + case SET_B: + DInt2 bounds = (DInt2) what; + ub = new int[bounds.getUpp() - bounds.getLow() + 1]; + for (int i = 0; i < ub.length; i++) { + ub[i] = i + bounds.getLow(); + } + break; + case SET_L: + DManyInt mint = (DManyInt) what; + ub = mint.getValues(); + break; + default: + Exit.log("Unknown expression"); + } }else { - int[] ub = null; switch (what.typeOf) { case INT2: DInt2 bounds = (DInt2) what; @@ -269,9 +283,9 @@ private static SetVar buildWithSet(String name, DSet type, Expression expression Exit.log("Unknown set type"); break; } - if(ub != null){ - sv = model.setVar(DEBUG ? name : NO_NAME, new int[]{}, ub); - } + } + if(ub != null){ + sv = model.setVar(DEBUG ? name : NO_NAME, new int[]{}, ub); } datas.register(name, sv); return sv; diff --git a/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java b/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java index 4db0b2ff27..42dd58ed19 100644 --- a/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java +++ b/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSP.java @@ -14,6 +14,8 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.ResolutionPolicy; import org.chocosolver.solver.Solver; +import org.chocosolver.solver.search.restart.InnerOuterCutoff; +import org.chocosolver.solver.search.restart.Restarter; import org.chocosolver.solver.search.strategy.BlackBoxConfigurator; import org.chocosolver.solver.search.strategy.Search; import org.chocosolver.solver.search.strategy.SearchParams; @@ -134,7 +136,17 @@ public void buildModel() { public void parse(Model target, XCSPParser parser) throws Exception { parser.model(target, instance); // and define a search strategy - freesearch(target.getSolver()); + BlackBoxConfigurator bb = BlackBoxConfigurator.init(); + // variable selection + bb.setIntVarStrategy(Search::roundRobinSearch) + .setRestartPolicy(s -> new Restarter(new InnerOuterCutoff(50, 1.01, 1.01), + c -> s.getFailCount() >= c, 50_000, true)) + .setNogoodOnRestart(!lcg) + .setRestartOnSolution(lcg) + .setRefinedPartialAssignmentGeneration(false) + .setExcludeObjective(true) + .setExcludeViews(false); + bb.make(target); } @@ -264,6 +276,9 @@ private void onSolution(Solver solver, XCSPParser parser) { try { output.insert(0, "s SATISFIABLE\n"); new SolutionChecker(true, instance, new ByteArrayInputStream(output.toString().getBytes())); +// new SolutionChecker(instance, new ByteArrayInputStream(output.toString().getBytes()), +// solver.getObjectiveManager().isOptimization() ? solver.getObjectiveManager().getBestSolutionValue().longValue() : null, +// null, true); } catch (Exception e) { throw new RuntimeException("wrong solution found twice"); } @@ -329,6 +344,9 @@ private void finalOutPut(Solver solver) { if (cs) { try { new SolutionChecker(true, instance, new ByteArrayInputStream(output.toString().getBytes())); +// new SolutionChecker(instance, new ByteArrayInputStream(output.toString().getBytes()), +// solver.getObjectiveManager().isOptimization() ? solver.getObjectiveManager().getBestSolutionValue().longValue() : null, +// null, true); } catch (Exception e) { e.printStackTrace(); } diff --git a/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSPParser.java b/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSPParser.java index 3d0cb1a3fd..698de803f8 100644 --- a/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSPParser.java +++ b/parsers/src/main/java/org/chocosolver/parser/xcsp/XCSPParser.java @@ -30,7 +30,6 @@ import org.chocosolver.solver.variables.Task; import org.chocosolver.util.objects.graphs.MultivaluedDecisionDiagram; import org.chocosolver.util.objects.queues.CircularQueue; -import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; import org.chocosolver.util.tools.ArrayUtils; import org.chocosolver.util.tools.MathUtils; import org.chocosolver.util.tools.VariableUtils; @@ -77,6 +76,7 @@ public class XCSPParser implements XCallbacks2 { protected TIntObjectHashMap intToSymbol; protected int unusedSymbol = 0; private ArrayList ovars; + private WeakHashMap weakHashMap = new WeakHashMap<>(); /** * The model to feed */ @@ -97,14 +97,34 @@ public void model(Model model, String instance) throws Exception { } else { throw new RuntimeException("FILE DOES NOT EXIST"); } + weakHashMap.clear(); } @Override public void endArray(XVariables.XArray a) { - IntVar[] array = Arrays.stream(a.vars).map(x -> mvars.get(x)).toArray(IntVar[]::new); + IntVar[] array = Arrays.stream(a.vars) + .filter(Objects::nonNull) + .filter(v -> v.degree != 0) + .map(x -> mvars.get(x)) + .toArray(IntVar[]::new); model.addAsGroup(a.id, array); } + @Override + public void loadGroup(XConstraints.XGroup g) { + beginGroup(g); + //System.out.println(g.argss.length+" occs"); + if (g.template instanceof XConstraints.XCtr) + loadCtrs((XConstraints.XCtr) g.template, g.argss, g); + else if (g.template instanceof XConstraints.XLogic && ((XConstraints.XLogic) g.template).getType() == Types.TypeCtr.not) { + XConstraints.CEntryReifiable child = ((XConstraints.XLogic) g.template).components[0]; + // http://sofdem.github.io/gccat/aux/pdf/not_all_equal.pdf + Stream.of(g.argss).forEach(o -> model.notAllEqual(vars((XVariables.XVarInteger[]) o)).post()); + } else + unimplementedCase(g); + endGroup(g); + } + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// //////////////////////////////////////// VARIABLES ////////////////////////////////////////////////////////////// /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -139,19 +159,20 @@ public void buildVarSymbolic(XVariables.XVarSymbolic x, String[] values) { @Override public void buildCtrIntension(String id, XVariables.XVarInteger[] scope, XNodeParent tree) { + manageExpression(scope, tree); + } + + + private void manageExpression(V[] scope, XNodeParent tree) { if (tree.type == IF) { ReExpression b = buildRe(tree.sons[0]); b.imp(buildRe(tree.sons[1])).post(); b.not().imp(buildRe(tree.sons[2])).post(); - - } else { - ReExpression exp = buildRe(tree); - if (VariableUtils.domainCardinality(vars(scope)) < Integer.MAX_VALUE / 1000) { - exp.extension().post(); - } else { - exp.decompose().post(); - } + return; } + + ReExpression exp = buildRe(tree); + exp.embody().post(); } private ArExpression[] extractAr(XNode[] sons) { @@ -182,7 +203,11 @@ private ReExpression buildRe(XNode tree) { case GT: return buildAr(sons[0]).gt(buildAr(sons[1])); case NE: - return buildAr(sons[0]).ne(buildAr(sons[1])); + if (sons.length == 2) { + return buildAr(sons[0]).ne(buildAr(sons[1])); + } else { + return new NaReExpression(ReExpression.Operator.NE, extractAr(sons)); + } case IN: List set0 = new ArrayList<>(); for (XNode sonsons : sons[1].sons) { @@ -236,15 +261,28 @@ private ArExpression buildAr(XNode node) { switch (type) { // logical case AND: + if (res.length == 2) { + return res[0].and(res[1]); + } return new NaLoExpression(LoExpression.Operator.AND, res); case OR: + if (res.length == 2) { + return res[0].or(res[1]); + } return new NaLoExpression(LoExpression.Operator.OR, res); case XOR: + if (res.length == 2) { + return res[0].xor(res[1]); + } return new NaLoExpression(LoExpression.Operator.XOR, res); case IFF: + if (res.length == 2) { + return res[0].iff(res[1]); + } return new NaLoExpression(LoExpression.Operator.IFF, res); case IMP: - return new NaLoExpression(LoExpression.Operator.OR, res); + assert res.length == 2; + return res[0].imp(res[1]); case NOT: return res[0].not(); default: @@ -272,14 +310,26 @@ private ArExpression buildAr(XNode node) { // arithmetic // return this == ADD || this == SUB || this == MUL || this == DIV || this == MOD || this == POW || this == DIST; case ADD: + if (aes.length == 2) { + return aes[0].add(aes[1]); + } return new NaArExpression(ArExpression.Operator.ADD, aes); case SUB: return aes[0].sub(aes[1]); case MUL: + if (aes.length == 2) { + return aes[0].mul(aes[1]); + } return new NaArExpression(ArExpression.Operator.MUL, aes); case MIN: + if (aes.length == 2) { + return aes[0].min(aes[1]); + } return new NaArExpression(ArExpression.Operator.MIN, aes); case MAX: + if (aes.length == 2) { + return aes[0].max(aes[1]); + } return new NaArExpression(ArExpression.Operator.MAX, aes); case DIV: return aes[0].div(aes[1]); @@ -387,12 +437,7 @@ public String printSolution(boolean format) { @Override public void buildCtrIntension(String id, XVariables.XVarSymbolic[] scope, XNodeParent syntaxTreeRoot) { - ReExpression exp = buildRe(syntaxTreeRoot); - if (VariableUtils.domainCardinality(vars(scope)) < Integer.MAX_VALUE / 1000) { - exp.extension().post(); - } else { - exp.decompose().post(); - } + manageExpression(scope, syntaxTreeRoot); } @Override @@ -416,17 +461,15 @@ public void buildCtrExtension(String id, XVariables.XVarSymbolic x, String[] val public void buildCtrExtension(String id, XVariables.XVarSymbolic[] list, String[][] tuples, boolean positive, Set flags) { //noinspection StatementWithEmptyBody if (flags.contains(Types.TypeFlag.UNCLEAN_TUPLES)) { - // do you have to clean the tuples, so as to remove those that cannot be built from variable domains ? + // do you have to clean the tuples, to remove those that cannot be built from variable domains? } - Tuples mTuples = new Tuples(Arrays.stream(tuples) - .map(t -> Arrays.stream(t).mapToInt(e -> symbolToInt.get(e)).toArray()) - .toArray(int[][]::new), positive); - if (flags.contains(Types.TypeFlag.STARRED_TUPLES)) { - if (!positive) { - // can you manage tables with symbol * ? - throw new ParserException("Negative tables with symbol * are not supported"); - } - mTuples.setUniversalValue(STAR_INT); + Tuples mTuples = (Tuples) weakHashMap.get(tuples); + if (mTuples == null) { + mTuples = new Tuples(Arrays.stream(tuples) + .map(t -> Arrays.stream(t).mapToInt(e -> symbolToInt.get(e)).toArray()) + .toArray(int[][]::new), positive, + flags.contains(Types.TypeFlag.STARRED_TUPLES) ? OptionalInt.of(STAR_INT) : OptionalInt.empty()); + weakHashMap.put(tuples, mTuples); } model.table(vars(list), mTuples).post(); } @@ -435,15 +478,13 @@ public void buildCtrExtension(String id, XVariables.XVarSymbolic[] list, String[ public void buildCtrExtension(String id, XVariables.XVarInteger[] list, int[][] tuples, boolean positive, Set flags) { //noinspection StatementWithEmptyBody if (flags.contains(Types.TypeFlag.UNCLEAN_TUPLES)) { - // do you have to clean the tuples, so as to remove those that cannot be built from variable domains ? + // do you have to clean the tuples, to remove those that cannot be built from variable domains ? } - Tuples mTuples = new Tuples(tuples, positive); - if (flags.contains(Types.TypeFlag.STARRED_TUPLES)) { - if (!positive) { - // can you manage tables with symbol * ? - throw new ParserException("Negative tables with symbol * are not supported"); - } - mTuples.setUniversalValue(STAR_INT); + Tuples mTuples = (Tuples) weakHashMap.get(tuples); + if (mTuples == null) { + mTuples = new Tuples(tuples, positive, + flags.contains(Types.TypeFlag.STARRED_TUPLES) ? OptionalInt.of(STAR_INT) : OptionalInt.empty()); + weakHashMap.put(tuples, mTuples); } model.table(vars(list), mTuples).post(); } @@ -750,11 +791,10 @@ public void buildCtrAllDifferentMatrix(String id, XVariables.XVarInteger[][] mat public void buildCtrAllDifferentExcept(String id, XVariables.XVarInteger[] list, int[] except) { if (except.length == 0) { model.allDifferent(vars(list)).post(); - } else if (except.length == 1) { - model.allDifferentUnderCondition(vars(list), x -> !x.contains(except[0]), true).post(); + } else if (except.length == 1 && except[0] == 0) { + model.allDifferentExcept0(vars(list)).post(); } else { - IntIterableRangeSet set = new IntIterableRangeSet(except); - model.allDifferentUnderCondition(vars(list), x -> !set.intersect(x), true).post(); + model.allDifferentExceptValues(vars(list), except).post(); } } @@ -1006,27 +1046,8 @@ public void buildCtrCount(String id, XVariables.XVarInteger[] list, XVariables.X private void buildCtrNValues(String id, IntVar[] vars, Condition condition) { if (condition instanceof Condition.ConditionRel) { Condition.ConditionRel conditionRel = (Condition.ConditionRel) condition; - switch (conditionRel.operator) { - case LT: - case LE: - model.atMostNValues(vars, condToVar(condition, 0, vars.length), false).post(); - return; - case GE: - case GT: - //TODO - model.atLeastNValues(vars, condToVar(condition, 0, vars.length), false).post(); - return; - case NE: { - IntVar count = model.intVar(0, vars.length); - model.nValues(vars, count).post(); - IntVar limit = condToVar(condition, 0, vars.length); - model.arithm(count, "!=", limit).post(); - } - return; - case EQ: - model.nValues(vars, condToVar(condition, 0, vars.length)).post(); - return; - } + model.nValues(vars, condToVar(condition, 0, vars.length)).post(); + return; } this.unimplementedCase(id); } @@ -1052,7 +1073,7 @@ public void buildCtrRegular(String id, XVariables.XVarInteger[] list, Transition FiniteAutomaton auto = new FiniteAutomaton(); TObjectIntHashMap s2s = new TObjectIntHashMap<>(16, 1.5f, -1); // add offset to support negative values in transitions - int minVal = Arrays.stream(transitions).mapToInt(tr -> ((Long)tr.value).intValue()).min().getAsInt(); + int minVal = Arrays.stream(transitions).mapToInt(tr -> ((Long) tr.value).intValue()).min().getAsInt(); int offset = minVal < 0 ? -minVal : 0; for (Transition tr : transitions) { int f = s2s.get(tr.start); @@ -1074,55 +1095,59 @@ public void buildCtrRegular(String id, XVariables.XVarInteger[] list, Transition if (offset > 0) { vars = Arrays.stream(vars).map(v -> model.intView(1, v, offset)).toArray(IntVar[]::new); } - model.regular(vars, auto).post(); + model.regularDec(vars, auto);//.post(); } @Override public void buildCtrMDD(String id, XVariables.XVarInteger[] list, Transition[] transitions) { - HashMap> layers = new HashMap<>(); - HashSet possibleRoots = new HashSet<>(), notRoots = new HashSet<>(); - Set possibleWells = new HashSet<>(), notWells = new HashSet<>(); - for (int t = 0; t < transitions.length; t++) { - String src = transitions[t].start, tgt = transitions[t].end; - notWells.add(src); - notRoots.add(tgt); - if (!notRoots.contains(src)) { - possibleRoots.add(src); - } - if (!notWells.contains(tgt)) { - possibleWells.add(tgt); + int[][] mtransitions = (int[][]) weakHashMap.get(transitions); + if (mtransitions == null) { + HashMap> layers = new HashMap<>(); + HashSet possibleRoots = new HashSet<>(), notRoots = new HashSet<>(); + Set possibleWells = new HashSet<>(), notWells = new HashSet<>(); + for (int t = 0; t < transitions.length; t++) { + String src = transitions[t].start, tgt = transitions[t].end; + notWells.add(src); + notRoots.add(tgt); + if (!notRoots.contains(src)) { + possibleRoots.add(src); + } + if (!notWells.contains(tgt)) { + possibleWells.add(tgt); + } + possibleRoots.remove(tgt); + possibleWells.remove(src); + List succs = layers.computeIfAbsent(src, k -> new ArrayList<>()); + succs.add(transitions[t]); } - possibleRoots.remove(tgt); - possibleWells.remove(src); - List succs = layers.computeIfAbsent(src, k -> new ArrayList<>()); - succs.add(transitions[t]); - } - String first = possibleRoots.toArray(new String[1])[0]; - String last = possibleWells.toArray(new String[1])[0]; - TObjectIntHashMap map = new TObjectIntHashMap<>(); - map.put(first, 0); - map.put(last, -1); - possibleRoots.add(last); - int n = 1; - - int[][] mtransitions = new int[transitions.length][3]; - int k = 0; - CircularQueue queue = new CircularQueue<>(layers.size()); - queue.addLast(first); - while (!queue.isEmpty()) { - String src = queue.pollFirst(); - List succs = layers.get(src); - if (succs == null) continue; - for (Transition t : succs) { - String tgt = t.end; - if (!possibleRoots.contains(tgt)) { - queue.addLast(tgt); - possibleRoots.add(tgt); - map.put(tgt, n++); + String first = possibleRoots.toArray(new String[1])[0]; + String last = possibleWells.toArray(new String[1])[0]; + TObjectIntHashMap map = new TObjectIntHashMap<>(); + map.put(first, 0); + map.put(last, -1); + possibleRoots.add(last); + int n = 1; + + mtransitions = new int[transitions.length][3]; + int k = 0; + CircularQueue queue = new CircularQueue<>(layers.size()); + queue.addLast(first); + while (!queue.isEmpty()) { + String src = queue.pollFirst(); + List succs = layers.get(src); + if (succs == null) continue; + for (Transition t : succs) { + String tgt = t.end; + if (!possibleRoots.contains(tgt)) { + queue.addLast(tgt); + possibleRoots.add(tgt); + map.put(tgt, n++); + } + mtransitions[k++] = new int[]{map.get(src), ((Long) t.value).intValue(), map.get(tgt)}; } - mtransitions[k++] = new int[]{map.get(src), ((Long) t.value).intValue(), map.get(tgt)}; } + weakHashMap.put(transitions, mtransitions); } IntVar[] mVars = vars(list); MultivaluedDecisionDiagram mdd = new MultivaluedDecisionDiagram(mVars, mtransitions); @@ -1720,9 +1745,13 @@ public void buildCtrFlow(String id, XVariables.XVarInteger[] list, int[] balance @Override public void buildCtrInstantiation(String id, XVariables.XVarInteger[] list, int[] values) { - Tuples tuples = new Tuples(true); - tuples.add(values); - model.table(vars(list), tuples).post(); + Tuples mTuples = (Tuples) weakHashMap.get(values); + if (mTuples == null) { + mTuples = new Tuples(true); + mTuples.add(values); + weakHashMap.put(values, mTuples); + } + model.table(vars(list), mTuples).post(); } /** @@ -1829,20 +1858,6 @@ public Implem implem() { return implem; } - @Override - public void loadGroup(XConstraints.XGroup g) { - beginGroup(g); - if (g.template instanceof XConstraints.XCtr) - loadCtrs((XConstraints.XCtr) g.template, g.argss, g); - else if (g.template instanceof XConstraints.XLogic && ((XConstraints.XLogic) g.template).getType() == Types.TypeCtr.not) { - XConstraints.CEntryReifiable child = ((XConstraints.XLogic) g.template).components[0]; - // http://sofdem.github.io/gccat/aux/pdf/not_all_equal.pdf - Stream.of(g.argss).forEach(o -> model.notAllEqual(vars((XVariables.XVarInteger[]) o)).post()); - } else - unimplementedCase(g); - endGroup(g); - } - /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////// ANNOTATIONS ///////////////////////////////////////////////////////////// /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/parsers/src/main/minizinc/docker/Dockerfile.dms b/parsers/src/main/minizinc/docker/Dockerfile.dms index 02af14b166..f75233ea9e 100644 --- a/parsers/src/main/minizinc/docker/Dockerfile.dms +++ b/parsers/src/main/minizinc/docker/Dockerfile.dms @@ -10,7 +10,7 @@ # 1. Stage: Setup of Choco in the MiniZinc Challenge docker image # Using the MiniZinc Challenge docker image -FROM minizinc/mznc2024:latest as build +FROM minizinc/mznc2025:latest AS build # Install Java 21 @ RUN apt-get update && \ @@ -23,7 +23,7 @@ RUN apt-get update && \ # The following command is used to disable caching for the ADD command and the next ones ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache # Copy Choco's files from the local repository -COPY ./parsers/target/choco-parsers-*-light.jar /choco/choco.jar +COPY ./parsers/target/choco-solver-*-light.jar /choco/choco.jar COPY ./parsers/src/main/minizinc/fzn-choco.py /choco/fzn-choco.py COPY ./parsers/src/main/minizinc/fzn-choco.sh /choco/fzn-choco.sh COPY ./parsers/src/main/minizinc/choco.msc /choco/choco.msc diff --git a/parsers/src/test/java/org/chocosolver/flatzinc/PerformanceTest.java b/parsers/src/test/java/org/chocosolver/flatzinc/PerformanceTest.java index 6538120c63..cda88763d3 100644 --- a/parsers/src/test/java/org/chocosolver/flatzinc/PerformanceTest.java +++ b/parsers/src/test/java/org/chocosolver/flatzinc/PerformanceTest.java @@ -9,6 +9,7 @@ */ package org.chocosolver.flatzinc; +import org.chocosolver.parser.Level; import org.chocosolver.parser.PerformanceListener; import org.chocosolver.parser.SetUpException; import org.chocosolver.parser.flatzinc.Flatzinc; @@ -40,7 +41,7 @@ public class PerformanceTest { private static final String COMMENT = "#"; private static final String DELIMITER = ","; - private static final String LEVEL = "SILENT"; + private static final String LEVEL = Level.SILENT.name(); public Object[][] getInstances(String name) { @@ -185,8 +186,8 @@ public void test_is_A3PZaPjnUz() throws SetUpException { fzn.solve(); Assert.assertEquals(fzn.getModel().getSolver().getSearchState(), SearchState.TERMINATED, "Unexpected search state"); Assert.assertEquals(fzn.getModel().getSolver().getObjectiveManager().getBestSolutionValue(), 103_936, "Unexpected best solution"); - Assert.assertEquals(fzn.getModel().getSolver().getNodeCount(), 2_164_075, "Unexpected number of nodes"); - Assert.assertEquals(fzn.getModel().getSolver().getFailCount(), 2_164_010, "Unexpected number of failures"); + Assert.assertEquals(fzn.getModel().getSolver().getNodeCount(), 2_174_212, "Unexpected number of nodes"); + Assert.assertEquals(fzn.getModel().getSolver().getFailCount(), 2_174_147, "Unexpected number of failures"); Assert.assertEquals(fzn.getModel().getSolver().getSolutionCount(), 33, "Unexpected number of solutions"); } @@ -266,8 +267,8 @@ public void test_arithmetic_target_814() throws SetUpException { Assert.assertEquals(fzn.getModel().getSolver().getSearchState(), SearchState.STOPPED, "Unexpected search state"); Assert.assertEquals(fzn.getModel().getSolver().getObjectiveManager().getBestSolutionValue(), 7422, "Unexpected best solution"); Assert.assertEquals(fzn.getModel().getSolver().getSolutionCount(), 22, "Unexpected number of solutions"); - Assert.assertEquals(fzn.getModel().getSolver().getNodeCount(), 313, "Unexpected number of nodes"); - Assert.assertEquals(fzn.getModel().getSolver().getFailCount(), 257, "Unexpected number of failures"); + Assert.assertEquals(fzn.getModel().getSolver().getNodeCount(), 3725, "Unexpected number of nodes"); + Assert.assertEquals(fzn.getModel().getSolver().getFailCount(), 3523, "Unexpected number of failures"); } @DataProvider() diff --git a/parsers/src/test/java/org/chocosolver/parser/flatzinc/parser/T_solve_goal.java b/parsers/src/test/java/org/chocosolver/parser/flatzinc/parser/T_solve_goal.java index ac26dde2a9..2a2a9a9d53 100644 --- a/parsers/src/test/java/org/chocosolver/parser/flatzinc/parser/T_solve_goal.java +++ b/parsers/src/test/java/org/chocosolver/parser/flatzinc/parser/T_solve_goal.java @@ -98,6 +98,7 @@ public void testSatisfy3() throws IOException { public void testNestedSeq() throws IOException { datas.register("x", mSolver.intVar("x", 0, 10, true)); datas.register("y", mSolver.intVar("y", 0, 10, true)); + datas.register("p", mSolver.boolVar("p")); Flatzinc4Parser fp = parser( "solve :: seq_search([\n" + " seq_search([\n" + diff --git a/parsers/src/test/resources/dimacs/instances.csv b/parsers/src/test/resources/dimacs/instances.csv index 7409268642..15c1125342 100644 --- a/parsers/src/test/resources/dimacs/instances.csv +++ b/parsers/src/test/resources/dimacs/instances.csv @@ -1,9 +1,9 @@ #name,sat,nodes,fails -hole6.cnf,false,8240,6786 -par8-1-c.cnf,true,125,77 +hole6.cnf,false,7410,6233 +par8-1-c.cnf,true,46,29 quinn.cnf,true,7,2 -zebra_v155_c1135.cnf,true,10092,8009 -ais6.cnf,true,676,170 -qg1-07.cnf,true,1077,294 -bmc-ibm-2.cnf,true,1627,445 -aim-50-1_6-yes1-4.cnf,true,673,318 \ No newline at end of file +zebra_v155_c1135.cnf,true,5192,3888 +ais6.cnf,true,386,97 +qg1-07.cnf,true,3268,1969 +bmc-ibm-2.cnf,true,53033,48641 +aim-50-1_6-yes1-4.cnf,true,358,156 \ No newline at end of file diff --git a/parsers/src/test/resources/flatzinc/2025/hitori_h5-1.fzn b/parsers/src/test/resources/flatzinc/2025/hitori_h5-1.fzn new file mode 100644 index 0000000000..471483d109 --- /dev/null +++ b/parsers/src/test/resources/flatzinc/2025/hitori_h5-1.fzn @@ -0,0 +1,1043 @@ +% Generated by MiniZinc 2.9.3 +% Solver library: /Users/kyzrsoze/Sources/CHOCO/continuous-branch/parsers/src/main/minizinc/mzn_lib +% Command line invocation: minizinc --solver org.choco.choco@5.0.0-beta.1 -c -m hitori.mzn -d h5-1.dzn -o hitori_h5-1.fzn + +predicate fzn_if_then_else_int(array [int] of var bool: c,array [int] of int: x,var int: y); +predicate fzn_alldifferent_except_0(array [int] of var int: x); +predicate choco_array_var_bool_element_nonshifted(var int: idx,int: os,array [int] of var bool: x,var bool: c); +predicate choco_array_var_int_element_nonshifted(var int: idx,int: os,array [int] of var int: x,var int: c); +predicate int_eq_imp(var int: a,var int: b,var bool: r); +predicate int_ne_imp(var int: a,var int: b,var bool: r); +predicate int_lin_eq_imp(array [int] of int: as,array [int] of var int: bs,int: c,var bool: r); +array [1..2] of int: X_INTRODUCED_44_ = [0,4]; +array [1..2] of int: X_INTRODUCED_47_ = [0,2]; +array [1..2] of int: X_INTRODUCED_52_ = [0,3]; +array [1..2] of int: X_INTRODUCED_55_ = [0,1]; +array [1..2] of int: X_INTRODUCED_70_ = [0,5]; +array [1..2] of int: X_INTRODUCED_461_ = [1,-1]; +var bool: X_INTRODUCED_17_; +var bool: X_INTRODUCED_19_; +var bool: X_INTRODUCED_20_; +var bool: X_INTRODUCED_21_; +var bool: X_INTRODUCED_25_; +var bool: X_INTRODUCED_26_; +var bool: X_INTRODUCED_27_; +var bool: X_INTRODUCED_29_; +var bool: X_INTRODUCED_30_; +var bool: X_INTRODUCED_31_; +var bool: X_INTRODUCED_35_; +var bool: X_INTRODUCED_36_; +var bool: X_INTRODUCED_37_; +var bool: X_INTRODUCED_39_; +var bool: X_INTRODUCED_41_; +var 0..4: X_INTRODUCED_43_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_46_ ::var_is_introduced :: is_defined_var; +var 0..4: X_INTRODUCED_49_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_51_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_54_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_57_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_59_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_61_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_63_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_65_ ::var_is_introduced :: is_defined_var; +var 0..4: X_INTRODUCED_67_ ::var_is_introduced :: is_defined_var; +var 0..5: X_INTRODUCED_69_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_72_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_74_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_76_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_78_ ::var_is_introduced :: is_defined_var; +var 0..2: X_INTRODUCED_80_ ::var_is_introduced :: is_defined_var; +var 0..5: X_INTRODUCED_82_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_84_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_86_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_88_ ::var_is_introduced :: is_defined_var; +var 0..3: X_INTRODUCED_90_ ::var_is_introduced :: is_defined_var; +var 0..4: X_INTRODUCED_92_ ::var_is_introduced :: is_defined_var; +var 0..5: X_INTRODUCED_94_ ::var_is_introduced :: is_defined_var; +var 0..4: X_INTRODUCED_96_ ::var_is_introduced :: is_defined_var; +var 0..71: obj:: is_defined_var; +var 0..1: X_INTRODUCED_98_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_104_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_106_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_109_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_115_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_117_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_119_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_124_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_126_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_127_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_135_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_137_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_139_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_142_ ::var_is_introduced :: is_defined_var; +var 0..1: X_INTRODUCED_146_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_149_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_150_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_152_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_154_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_156_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_158_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_160_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_162_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_164_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_166_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_169_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_172_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_175_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_178_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_180_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_183_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_186_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_189_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_192_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_194_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_197_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_200_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_203_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_206_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_208_ ::var_is_introduced :: is_defined_var; +var 1..25: X_INTRODUCED_236_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_243_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_244_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_245_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_246_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_247_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_248_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_250_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_251_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_252_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_253_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_254_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_255_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_256_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_257_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_258_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_260_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_261_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_262_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_263_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_264_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_265_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_266_ ::var_is_introduced ; +var 0..24: X_INTRODUCED_267_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_269_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_270_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_271_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_272_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_273_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_274_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_276_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_277_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_278_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_279_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_280_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_281_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_282_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_283_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_284_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_286_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_287_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_288_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_289_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_290_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_291_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_292_ ::var_is_introduced ; +var 1..25: X_INTRODUCED_293_ ::var_is_introduced ; +var bool: X_INTRODUCED_295_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_296_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_297_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_298_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_299_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_300_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_302_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_303_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_304_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_305_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_306_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_307_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_308_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_309_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_310_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_312_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_313_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_314_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_315_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_316_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_317_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_318_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_319_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_345_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_346_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_348_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_349_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_351_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_352_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_354_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_355_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_357_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_358_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_360_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_361_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_366_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_367_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_369_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_370_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_372_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_373_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_375_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_376_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_378_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_379_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_381_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_382_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_384_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_385_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_387_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_388_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_390_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_391_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_396_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_397_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_399_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_400_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_402_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_403_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_405_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_406_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_408_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_409_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_411_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_412_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_414_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_415_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_417_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_418_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_420_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_421_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_422_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_423_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_424_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_425_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_426_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_427_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_428_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_429_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_430_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_431_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_432_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_433_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_434_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_435_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_436_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_437_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_438_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_439_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_440_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_441_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_442_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_443_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_444_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_445_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_446_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_447_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_448_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_449_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_450_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_451_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_452_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_453_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_454_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_455_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_456_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_457_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_459_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_462_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_463_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_465_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_466_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_468_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_469_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_471_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_472_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_474_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_475_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_477_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_478_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_481_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_483_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_484_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_486_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_487_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_489_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_490_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_492_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_493_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_495_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_496_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_498_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_499_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_501_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_502_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_504_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_505_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_507_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_508_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_511_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_513_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_514_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_516_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_517_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_519_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_520_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_522_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_523_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_525_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_526_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_528_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_529_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_531_ ::var_is_introduced :: is_defined_var; +var 0..24: X_INTRODUCED_532_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_534_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_535_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_536_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_537_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_538_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_540_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_541_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_542_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_543_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_544_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_545_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_547_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_548_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_549_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_550_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_551_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_552_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_554_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_555_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_556_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_557_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_558_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_559_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_561_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_562_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_563_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_564_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_566_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_567_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_568_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_569_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_570_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_571_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_582_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_583_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_584_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_585_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_586_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_587_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_588_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_589_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_591_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_592_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_593_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_594_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_595_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_596_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_597_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_598_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_600_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_601_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_602_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_603_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_604_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_605_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_607_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_608_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_609_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_610_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_611_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_612_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_614_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_615_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_616_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_617_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_618_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_619_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_620_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_621_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_623_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_624_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_625_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_626_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_627_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_628_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_629_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_630_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_632_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_633_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_634_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_635_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_636_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_637_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_638_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_639_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_641_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_642_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_643_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_644_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_645_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_646_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_648_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_649_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_650_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_651_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_652_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_653_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_664_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_665_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_666_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_667_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_668_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_669_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_670_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_671_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_673_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_674_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_675_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_676_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_677_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_678_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_679_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_680_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_682_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_683_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_684_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_685_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_686_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_687_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_689_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_690_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_691_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_692_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_694_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_695_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_696_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_697_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_698_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_699_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_701_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_702_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_703_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_704_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_705_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_706_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_708_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_709_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_710_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_711_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_712_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_713_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_715_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_716_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_717_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_718_ ::var_is_introduced :: is_defined_var; +var bool: X_INTRODUCED_18_ = false; +var bool: X_INTRODUCED_22_ = false; +var bool: X_INTRODUCED_23_ = true; +var bool: X_INTRODUCED_24_ = false; +var bool: X_INTRODUCED_28_ = false; +var bool: X_INTRODUCED_32_ = false; +var bool: X_INTRODUCED_33_ = true; +var bool: X_INTRODUCED_34_ = false; +var bool: X_INTRODUCED_38_ = false; +var bool: X_INTRODUCED_40_ = false; +array [1..25] of var bool: X_INTRODUCED_16_:: output_array([1..25]) = [X_INTRODUCED_17_,false,X_INTRODUCED_19_,X_INTRODUCED_20_,X_INTRODUCED_21_,false,true,false,X_INTRODUCED_25_,X_INTRODUCED_26_,X_INTRODUCED_27_,false,X_INTRODUCED_29_,X_INTRODUCED_30_,X_INTRODUCED_31_,false,true,false,X_INTRODUCED_35_,X_INTRODUCED_36_,X_INTRODUCED_37_,false,X_INTRODUCED_39_,false,X_INTRODUCED_41_]; +array [1..2] of var bool: X_INTRODUCED_45_ ::var_is_introduced = [X_INTRODUCED_17_,true]; +array [1..2] of var bool: X_INTRODUCED_48_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_50_ ::var_is_introduced = [X_INTRODUCED_19_,true]; +array [1..2] of var bool: X_INTRODUCED_53_ ::var_is_introduced = [X_INTRODUCED_20_,true]; +array [1..2] of var bool: X_INTRODUCED_56_ ::var_is_introduced = [X_INTRODUCED_21_,true]; +array [1..2] of var bool: X_INTRODUCED_58_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_60_ ::var_is_introduced = [true,true]; +array [1..2] of var bool: X_INTRODUCED_62_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_64_ ::var_is_introduced = [X_INTRODUCED_25_,true]; +array [1..2] of var bool: X_INTRODUCED_66_ ::var_is_introduced = [X_INTRODUCED_26_,true]; +array [1..2] of var bool: X_INTRODUCED_68_ ::var_is_introduced = [X_INTRODUCED_27_,true]; +array [1..2] of var bool: X_INTRODUCED_71_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_73_ ::var_is_introduced = [X_INTRODUCED_29_,true]; +array [1..2] of var bool: X_INTRODUCED_75_ ::var_is_introduced = [X_INTRODUCED_30_,true]; +array [1..2] of var bool: X_INTRODUCED_77_ ::var_is_introduced = [X_INTRODUCED_31_,true]; +array [1..2] of var bool: X_INTRODUCED_79_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_81_ ::var_is_introduced = [true,true]; +array [1..2] of var bool: X_INTRODUCED_83_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_85_ ::var_is_introduced = [X_INTRODUCED_35_,true]; +array [1..2] of var bool: X_INTRODUCED_87_ ::var_is_introduced = [X_INTRODUCED_36_,true]; +array [1..2] of var bool: X_INTRODUCED_89_ ::var_is_introduced = [X_INTRODUCED_37_,true]; +array [1..2] of var bool: X_INTRODUCED_91_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_93_ ::var_is_introduced = [X_INTRODUCED_39_,true]; +array [1..2] of var bool: X_INTRODUCED_95_ ::var_is_introduced = [false,true]; +array [1..2] of var bool: X_INTRODUCED_97_ ::var_is_introduced = [X_INTRODUCED_41_,true]; +array [1..5] of var int: X_INTRODUCED_216_ ::var_is_introduced = [X_INTRODUCED_43_,X_INTRODUCED_46_,X_INTRODUCED_49_,X_INTRODUCED_51_,X_INTRODUCED_54_]; +array [1..5] of var int: X_INTRODUCED_218_ ::var_is_introduced = [X_INTRODUCED_57_,X_INTRODUCED_59_,X_INTRODUCED_61_,X_INTRODUCED_63_,X_INTRODUCED_65_]; +array [1..5] of var int: X_INTRODUCED_220_ ::var_is_introduced = [X_INTRODUCED_67_,X_INTRODUCED_69_,X_INTRODUCED_72_,X_INTRODUCED_74_,X_INTRODUCED_76_]; +array [1..5] of var int: X_INTRODUCED_222_ ::var_is_introduced = [X_INTRODUCED_78_,X_INTRODUCED_80_,X_INTRODUCED_82_,X_INTRODUCED_84_,X_INTRODUCED_86_]; +array [1..5] of var int: X_INTRODUCED_224_ ::var_is_introduced = [X_INTRODUCED_88_,X_INTRODUCED_90_,X_INTRODUCED_92_,X_INTRODUCED_94_,X_INTRODUCED_96_]; +array [1..5] of var int: X_INTRODUCED_226_ ::var_is_introduced = [X_INTRODUCED_43_,X_INTRODUCED_57_,X_INTRODUCED_67_,X_INTRODUCED_78_,X_INTRODUCED_88_]; +array [1..5] of var int: X_INTRODUCED_228_ ::var_is_introduced = [X_INTRODUCED_46_,X_INTRODUCED_59_,X_INTRODUCED_69_,X_INTRODUCED_80_,X_INTRODUCED_90_]; +array [1..5] of var int: X_INTRODUCED_230_ ::var_is_introduced = [X_INTRODUCED_49_,X_INTRODUCED_61_,X_INTRODUCED_72_,X_INTRODUCED_82_,X_INTRODUCED_92_]; +array [1..5] of var int: X_INTRODUCED_232_ ::var_is_introduced = [X_INTRODUCED_51_,X_INTRODUCED_63_,X_INTRODUCED_74_,X_INTRODUCED_84_,X_INTRODUCED_94_]; +array [1..5] of var int: X_INTRODUCED_234_ ::var_is_introduced = [X_INTRODUCED_54_,X_INTRODUCED_65_,X_INTRODUCED_76_,X_INTRODUCED_86_,X_INTRODUCED_96_]; +array [1..25] of var bool: X_INTRODUCED_235_ ::var_is_introduced = [X_INTRODUCED_149_,true,X_INTRODUCED_154_,X_INTRODUCED_158_,X_INTRODUCED_162_,true,false,true,X_INTRODUCED_164_,X_INTRODUCED_166_,X_INTRODUCED_169_,true,X_INTRODUCED_175_,X_INTRODUCED_178_,X_INTRODUCED_180_,true,false,true,X_INTRODUCED_192_,X_INTRODUCED_194_,X_INTRODUCED_197_,true,X_INTRODUCED_203_,true,X_INTRODUCED_208_]; +array [1..25] of var int: X_INTRODUCED_240_ ::var_is_introduced = [X_INTRODUCED_243_,X_INTRODUCED_244_,X_INTRODUCED_245_,X_INTRODUCED_246_,X_INTRODUCED_247_,X_INTRODUCED_248_,0,X_INTRODUCED_250_,X_INTRODUCED_251_,X_INTRODUCED_252_,X_INTRODUCED_253_,X_INTRODUCED_254_,X_INTRODUCED_255_,X_INTRODUCED_256_,X_INTRODUCED_257_,X_INTRODUCED_258_,0,X_INTRODUCED_260_,X_INTRODUCED_261_,X_INTRODUCED_262_,X_INTRODUCED_263_,X_INTRODUCED_264_,X_INTRODUCED_265_,X_INTRODUCED_266_,X_INTRODUCED_267_]; +array [1..25] of var int: X_INTRODUCED_241_ ::var_is_introduced = [X_INTRODUCED_269_,X_INTRODUCED_270_,X_INTRODUCED_271_,X_INTRODUCED_272_,X_INTRODUCED_273_,X_INTRODUCED_274_,7,X_INTRODUCED_276_,X_INTRODUCED_277_,X_INTRODUCED_278_,X_INTRODUCED_279_,X_INTRODUCED_280_,X_INTRODUCED_281_,X_INTRODUCED_282_,X_INTRODUCED_283_,X_INTRODUCED_284_,17,X_INTRODUCED_286_,X_INTRODUCED_287_,X_INTRODUCED_288_,X_INTRODUCED_289_,X_INTRODUCED_290_,X_INTRODUCED_291_,X_INTRODUCED_292_,X_INTRODUCED_293_]; +array [1..25] of var bool: X_INTRODUCED_720_ ::var_is_introduced = [X_INTRODUCED_17_,false,X_INTRODUCED_19_,X_INTRODUCED_20_,X_INTRODUCED_21_,false,true,false,X_INTRODUCED_25_,X_INTRODUCED_26_,X_INTRODUCED_27_,false,X_INTRODUCED_29_,X_INTRODUCED_30_,X_INTRODUCED_31_,false,true,false,X_INTRODUCED_35_,X_INTRODUCED_36_,X_INTRODUCED_37_,false,X_INTRODUCED_39_,false,X_INTRODUCED_41_]; +constraint fzn_if_then_else_int(X_INTRODUCED_45_,X_INTRODUCED_44_,X_INTRODUCED_43_):: defines_var(X_INTRODUCED_43_); +constraint fzn_if_then_else_int(X_INTRODUCED_48_,X_INTRODUCED_47_,X_INTRODUCED_46_):: defines_var(X_INTRODUCED_46_); +constraint fzn_if_then_else_int(X_INTRODUCED_50_,X_INTRODUCED_44_,X_INTRODUCED_49_):: defines_var(X_INTRODUCED_49_); +constraint fzn_if_then_else_int(X_INTRODUCED_53_,X_INTRODUCED_52_,X_INTRODUCED_51_):: defines_var(X_INTRODUCED_51_); +constraint fzn_if_then_else_int(X_INTRODUCED_56_,X_INTRODUCED_55_,X_INTRODUCED_54_):: defines_var(X_INTRODUCED_54_); +constraint fzn_if_then_else_int(X_INTRODUCED_58_,X_INTRODUCED_52_,X_INTRODUCED_57_):: defines_var(X_INTRODUCED_57_); +constraint fzn_if_then_else_int(X_INTRODUCED_60_,X_INTRODUCED_47_,X_INTRODUCED_59_):: defines_var(X_INTRODUCED_59_); +constraint fzn_if_then_else_int(X_INTRODUCED_62_,X_INTRODUCED_55_,X_INTRODUCED_61_):: defines_var(X_INTRODUCED_61_); +constraint fzn_if_then_else_int(X_INTRODUCED_64_,X_INTRODUCED_47_,X_INTRODUCED_63_):: defines_var(X_INTRODUCED_63_); +constraint fzn_if_then_else_int(X_INTRODUCED_66_,X_INTRODUCED_47_,X_INTRODUCED_65_):: defines_var(X_INTRODUCED_65_); +constraint fzn_if_then_else_int(X_INTRODUCED_68_,X_INTRODUCED_44_,X_INTRODUCED_67_):: defines_var(X_INTRODUCED_67_); +constraint fzn_if_then_else_int(X_INTRODUCED_71_,X_INTRODUCED_70_,X_INTRODUCED_69_):: defines_var(X_INTRODUCED_69_); +constraint fzn_if_then_else_int(X_INTRODUCED_73_,X_INTRODUCED_52_,X_INTRODUCED_72_):: defines_var(X_INTRODUCED_72_); +constraint fzn_if_then_else_int(X_INTRODUCED_75_,X_INTRODUCED_55_,X_INTRODUCED_74_):: defines_var(X_INTRODUCED_74_); +constraint fzn_if_then_else_int(X_INTRODUCED_77_,X_INTRODUCED_47_,X_INTRODUCED_76_):: defines_var(X_INTRODUCED_76_); +constraint fzn_if_then_else_int(X_INTRODUCED_79_,X_INTRODUCED_47_,X_INTRODUCED_78_):: defines_var(X_INTRODUCED_78_); +constraint fzn_if_then_else_int(X_INTRODUCED_81_,X_INTRODUCED_47_,X_INTRODUCED_80_):: defines_var(X_INTRODUCED_80_); +constraint fzn_if_then_else_int(X_INTRODUCED_83_,X_INTRODUCED_70_,X_INTRODUCED_82_):: defines_var(X_INTRODUCED_82_); +constraint fzn_if_then_else_int(X_INTRODUCED_85_,X_INTRODUCED_52_,X_INTRODUCED_84_):: defines_var(X_INTRODUCED_84_); +constraint fzn_if_then_else_int(X_INTRODUCED_87_,X_INTRODUCED_52_,X_INTRODUCED_86_):: defines_var(X_INTRODUCED_86_); +constraint fzn_if_then_else_int(X_INTRODUCED_89_,X_INTRODUCED_55_,X_INTRODUCED_88_):: defines_var(X_INTRODUCED_88_); +constraint fzn_if_then_else_int(X_INTRODUCED_91_,X_INTRODUCED_52_,X_INTRODUCED_90_):: defines_var(X_INTRODUCED_90_); +constraint fzn_if_then_else_int(X_INTRODUCED_93_,X_INTRODUCED_44_,X_INTRODUCED_92_):: defines_var(X_INTRODUCED_92_); +constraint fzn_if_then_else_int(X_INTRODUCED_95_,X_INTRODUCED_70_,X_INTRODUCED_94_):: defines_var(X_INTRODUCED_94_); +constraint fzn_if_then_else_int(X_INTRODUCED_97_,X_INTRODUCED_44_,X_INTRODUCED_96_):: defines_var(X_INTRODUCED_96_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_216_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_218_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_220_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_222_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_224_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_226_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_228_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_230_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_232_); +constraint fzn_alldifferent_except_0(X_INTRODUCED_234_); +constraint bool_clause([],[X_INTRODUCED_19_,X_INTRODUCED_20_]); +constraint bool_clause([],[X_INTRODUCED_20_,X_INTRODUCED_21_]); +constraint bool_clause([],[X_INTRODUCED_25_,X_INTRODUCED_26_]); +constraint bool_clause([],[X_INTRODUCED_29_,X_INTRODUCED_30_]); +constraint bool_clause([],[X_INTRODUCED_30_,X_INTRODUCED_31_]); +constraint bool_clause([],[X_INTRODUCED_35_,X_INTRODUCED_36_]); +constraint bool_clause([],[X_INTRODUCED_20_,X_INTRODUCED_25_]); +constraint bool_clause([],[X_INTRODUCED_25_,X_INTRODUCED_30_]); +constraint bool_clause([],[X_INTRODUCED_30_,X_INTRODUCED_35_]); +constraint bool_clause([],[X_INTRODUCED_21_,X_INTRODUCED_26_]); +constraint bool_clause([],[X_INTRODUCED_26_,X_INTRODUCED_31_]); +constraint bool_clause([],[X_INTRODUCED_31_,X_INTRODUCED_36_]); +constraint bool_clause([],[X_INTRODUCED_36_,X_INTRODUCED_41_]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_236_,1,X_INTRODUCED_235_,true); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_236_,1,X_INTRODUCED_240_,0); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_236_,1,X_INTRODUCED_241_,X_INTRODUCED_236_); +constraint bool_clause([X_INTRODUCED_295_],[X_INTRODUCED_17_]); +constraint bool_clause([X_INTRODUCED_297_],[X_INTRODUCED_19_]); +constraint bool_clause([X_INTRODUCED_298_],[X_INTRODUCED_20_]); +constraint bool_clause([X_INTRODUCED_299_],[X_INTRODUCED_21_]); +constraint bool_clause([X_INTRODUCED_303_],[X_INTRODUCED_25_]); +constraint bool_clause([X_INTRODUCED_304_],[X_INTRODUCED_26_]); +constraint bool_clause([X_INTRODUCED_305_],[X_INTRODUCED_27_]); +constraint bool_clause([X_INTRODUCED_307_],[X_INTRODUCED_29_]); +constraint bool_clause([X_INTRODUCED_308_],[X_INTRODUCED_30_]); +constraint bool_clause([X_INTRODUCED_309_],[X_INTRODUCED_31_]); +constraint bool_clause([X_INTRODUCED_313_],[X_INTRODUCED_35_]); +constraint bool_clause([X_INTRODUCED_314_],[X_INTRODUCED_36_]); +constraint bool_clause([X_INTRODUCED_315_],[X_INTRODUCED_37_]); +constraint bool_clause([X_INTRODUCED_317_],[X_INTRODUCED_39_]); +constraint bool_clause([X_INTRODUCED_319_],[X_INTRODUCED_41_]); +constraint bool_clause([X_INTRODUCED_17_,X_INTRODUCED_345_,X_INTRODUCED_346_],[]); +constraint bool_clause([X_INTRODUCED_19_,X_INTRODUCED_351_,X_INTRODUCED_352_],[]); +constraint bool_clause([X_INTRODUCED_20_,X_INTRODUCED_354_,X_INTRODUCED_355_],[]); +constraint bool_clause([X_INTRODUCED_21_,X_INTRODUCED_357_,X_INTRODUCED_358_],[]); +constraint bool_clause([X_INTRODUCED_25_,X_INTRODUCED_369_,X_INTRODUCED_370_],[]); +constraint bool_clause([X_INTRODUCED_26_,X_INTRODUCED_372_,X_INTRODUCED_373_],[]); +constraint bool_clause([X_INTRODUCED_27_,X_INTRODUCED_375_,X_INTRODUCED_376_],[]); +constraint bool_clause([X_INTRODUCED_29_,X_INTRODUCED_381_,X_INTRODUCED_382_],[]); +constraint bool_clause([X_INTRODUCED_30_,X_INTRODUCED_384_,X_INTRODUCED_385_],[]); +constraint bool_clause([X_INTRODUCED_31_,X_INTRODUCED_387_,X_INTRODUCED_388_],[]); +constraint bool_clause([X_INTRODUCED_35_,X_INTRODUCED_399_,X_INTRODUCED_400_],[]); +constraint bool_clause([X_INTRODUCED_36_,X_INTRODUCED_402_,X_INTRODUCED_403_],[]); +constraint bool_clause([X_INTRODUCED_37_,X_INTRODUCED_405_,X_INTRODUCED_406_],[]); +constraint bool_clause([X_INTRODUCED_39_,X_INTRODUCED_411_,X_INTRODUCED_412_],[]); +constraint bool_clause([X_INTRODUCED_41_,X_INTRODUCED_417_,X_INTRODUCED_418_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_269_,1,X_INTRODUCED_235_,X_INTRODUCED_420_):: defines_var(X_INTRODUCED_420_); +constraint bool_clause([X_INTRODUCED_295_,X_INTRODUCED_421_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_270_,1,X_INTRODUCED_235_,X_INTRODUCED_422_):: defines_var(X_INTRODUCED_422_); +constraint bool_clause([X_INTRODUCED_296_,X_INTRODUCED_422_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_271_,1,X_INTRODUCED_235_,X_INTRODUCED_423_):: defines_var(X_INTRODUCED_423_); +constraint bool_clause([X_INTRODUCED_297_,X_INTRODUCED_424_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_272_,1,X_INTRODUCED_235_,X_INTRODUCED_425_):: defines_var(X_INTRODUCED_425_); +constraint bool_clause([X_INTRODUCED_298_,X_INTRODUCED_426_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_273_,1,X_INTRODUCED_235_,X_INTRODUCED_427_):: defines_var(X_INTRODUCED_427_); +constraint bool_clause([X_INTRODUCED_299_,X_INTRODUCED_428_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_274_,1,X_INTRODUCED_235_,X_INTRODUCED_429_):: defines_var(X_INTRODUCED_429_); +constraint bool_clause([X_INTRODUCED_300_,X_INTRODUCED_429_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_276_,1,X_INTRODUCED_235_,X_INTRODUCED_430_):: defines_var(X_INTRODUCED_430_); +constraint bool_clause([X_INTRODUCED_302_,X_INTRODUCED_430_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_277_,1,X_INTRODUCED_235_,X_INTRODUCED_431_):: defines_var(X_INTRODUCED_431_); +constraint bool_clause([X_INTRODUCED_303_,X_INTRODUCED_432_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_278_,1,X_INTRODUCED_235_,X_INTRODUCED_433_):: defines_var(X_INTRODUCED_433_); +constraint bool_clause([X_INTRODUCED_304_,X_INTRODUCED_434_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_279_,1,X_INTRODUCED_235_,X_INTRODUCED_435_):: defines_var(X_INTRODUCED_435_); +constraint bool_clause([X_INTRODUCED_305_,X_INTRODUCED_436_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_280_,1,X_INTRODUCED_235_,X_INTRODUCED_437_):: defines_var(X_INTRODUCED_437_); +constraint bool_clause([X_INTRODUCED_306_,X_INTRODUCED_437_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_281_,1,X_INTRODUCED_235_,X_INTRODUCED_438_):: defines_var(X_INTRODUCED_438_); +constraint bool_clause([X_INTRODUCED_307_,X_INTRODUCED_439_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_282_,1,X_INTRODUCED_235_,X_INTRODUCED_440_):: defines_var(X_INTRODUCED_440_); +constraint bool_clause([X_INTRODUCED_308_,X_INTRODUCED_441_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_283_,1,X_INTRODUCED_235_,X_INTRODUCED_442_):: defines_var(X_INTRODUCED_442_); +constraint bool_clause([X_INTRODUCED_309_,X_INTRODUCED_443_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_284_,1,X_INTRODUCED_235_,X_INTRODUCED_444_):: defines_var(X_INTRODUCED_444_); +constraint bool_clause([X_INTRODUCED_310_,X_INTRODUCED_444_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_286_,1,X_INTRODUCED_235_,X_INTRODUCED_445_):: defines_var(X_INTRODUCED_445_); +constraint bool_clause([X_INTRODUCED_312_,X_INTRODUCED_445_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_287_,1,X_INTRODUCED_235_,X_INTRODUCED_446_):: defines_var(X_INTRODUCED_446_); +constraint bool_clause([X_INTRODUCED_313_,X_INTRODUCED_447_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_288_,1,X_INTRODUCED_235_,X_INTRODUCED_448_):: defines_var(X_INTRODUCED_448_); +constraint bool_clause([X_INTRODUCED_314_,X_INTRODUCED_449_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_289_,1,X_INTRODUCED_235_,X_INTRODUCED_450_):: defines_var(X_INTRODUCED_450_); +constraint bool_clause([X_INTRODUCED_315_,X_INTRODUCED_451_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_290_,1,X_INTRODUCED_235_,X_INTRODUCED_452_):: defines_var(X_INTRODUCED_452_); +constraint bool_clause([X_INTRODUCED_316_,X_INTRODUCED_452_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_291_,1,X_INTRODUCED_235_,X_INTRODUCED_453_):: defines_var(X_INTRODUCED_453_); +constraint bool_clause([X_INTRODUCED_317_,X_INTRODUCED_454_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_292_,1,X_INTRODUCED_235_,X_INTRODUCED_455_):: defines_var(X_INTRODUCED_455_); +constraint bool_clause([X_INTRODUCED_318_,X_INTRODUCED_455_],[]); +constraint choco_array_var_bool_element_nonshifted(X_INTRODUCED_293_,1,X_INTRODUCED_235_,X_INTRODUCED_456_):: defines_var(X_INTRODUCED_456_); +constraint bool_clause([X_INTRODUCED_319_,X_INTRODUCED_457_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_269_,1,X_INTRODUCED_240_,X_INTRODUCED_459_):: defines_var(X_INTRODUCED_459_); +constraint bool_clause([X_INTRODUCED_295_,X_INTRODUCED_462_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_270_,1,X_INTRODUCED_240_,X_INTRODUCED_463_):: defines_var(X_INTRODUCED_463_); +constraint bool_clause([X_INTRODUCED_296_,X_INTRODUCED_465_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_271_,1,X_INTRODUCED_240_,X_INTRODUCED_466_):: defines_var(X_INTRODUCED_466_); +constraint bool_clause([X_INTRODUCED_297_,X_INTRODUCED_468_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_272_,1,X_INTRODUCED_240_,X_INTRODUCED_469_):: defines_var(X_INTRODUCED_469_); +constraint bool_clause([X_INTRODUCED_298_,X_INTRODUCED_471_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_273_,1,X_INTRODUCED_240_,X_INTRODUCED_472_):: defines_var(X_INTRODUCED_472_); +constraint bool_clause([X_INTRODUCED_299_,X_INTRODUCED_474_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_274_,1,X_INTRODUCED_240_,X_INTRODUCED_475_):: defines_var(X_INTRODUCED_475_); +constraint bool_clause([X_INTRODUCED_300_,X_INTRODUCED_477_],[]); +constraint choco_array_var_int_element_nonshifted(7,1,X_INTRODUCED_240_,X_INTRODUCED_478_):: defines_var(X_INTRODUCED_478_); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_276_,1,X_INTRODUCED_240_,X_INTRODUCED_481_):: defines_var(X_INTRODUCED_481_); +constraint bool_clause([X_INTRODUCED_302_,X_INTRODUCED_483_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_277_,1,X_INTRODUCED_240_,X_INTRODUCED_484_):: defines_var(X_INTRODUCED_484_); +constraint bool_clause([X_INTRODUCED_303_,X_INTRODUCED_486_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_278_,1,X_INTRODUCED_240_,X_INTRODUCED_487_):: defines_var(X_INTRODUCED_487_); +constraint bool_clause([X_INTRODUCED_304_,X_INTRODUCED_489_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_279_,1,X_INTRODUCED_240_,X_INTRODUCED_490_):: defines_var(X_INTRODUCED_490_); +constraint bool_clause([X_INTRODUCED_305_,X_INTRODUCED_492_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_280_,1,X_INTRODUCED_240_,X_INTRODUCED_493_):: defines_var(X_INTRODUCED_493_); +constraint bool_clause([X_INTRODUCED_306_,X_INTRODUCED_495_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_281_,1,X_INTRODUCED_240_,X_INTRODUCED_496_):: defines_var(X_INTRODUCED_496_); +constraint bool_clause([X_INTRODUCED_307_,X_INTRODUCED_498_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_282_,1,X_INTRODUCED_240_,X_INTRODUCED_499_):: defines_var(X_INTRODUCED_499_); +constraint bool_clause([X_INTRODUCED_308_,X_INTRODUCED_501_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_283_,1,X_INTRODUCED_240_,X_INTRODUCED_502_):: defines_var(X_INTRODUCED_502_); +constraint bool_clause([X_INTRODUCED_309_,X_INTRODUCED_504_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_284_,1,X_INTRODUCED_240_,X_INTRODUCED_505_):: defines_var(X_INTRODUCED_505_); +constraint bool_clause([X_INTRODUCED_310_,X_INTRODUCED_507_],[]); +constraint choco_array_var_int_element_nonshifted(17,1,X_INTRODUCED_240_,X_INTRODUCED_508_):: defines_var(X_INTRODUCED_508_); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_286_,1,X_INTRODUCED_240_,X_INTRODUCED_511_):: defines_var(X_INTRODUCED_511_); +constraint bool_clause([X_INTRODUCED_312_,X_INTRODUCED_513_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_287_,1,X_INTRODUCED_240_,X_INTRODUCED_514_):: defines_var(X_INTRODUCED_514_); +constraint bool_clause([X_INTRODUCED_313_,X_INTRODUCED_516_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_288_,1,X_INTRODUCED_240_,X_INTRODUCED_517_):: defines_var(X_INTRODUCED_517_); +constraint bool_clause([X_INTRODUCED_314_,X_INTRODUCED_519_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_289_,1,X_INTRODUCED_240_,X_INTRODUCED_520_):: defines_var(X_INTRODUCED_520_); +constraint bool_clause([X_INTRODUCED_315_,X_INTRODUCED_522_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_290_,1,X_INTRODUCED_240_,X_INTRODUCED_523_):: defines_var(X_INTRODUCED_523_); +constraint bool_clause([X_INTRODUCED_316_,X_INTRODUCED_525_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_291_,1,X_INTRODUCED_240_,X_INTRODUCED_526_):: defines_var(X_INTRODUCED_526_); +constraint bool_clause([X_INTRODUCED_317_,X_INTRODUCED_528_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_292_,1,X_INTRODUCED_240_,X_INTRODUCED_529_):: defines_var(X_INTRODUCED_529_); +constraint bool_clause([X_INTRODUCED_318_,X_INTRODUCED_531_],[]); +constraint choco_array_var_int_element_nonshifted(X_INTRODUCED_293_,1,X_INTRODUCED_240_,X_INTRODUCED_532_):: defines_var(X_INTRODUCED_532_); +constraint bool_clause([X_INTRODUCED_319_,X_INTRODUCED_534_],[]); +constraint bool_clause([X_INTRODUCED_295_,X_INTRODUCED_536_,X_INTRODUCED_538_],[]); +constraint bool_clause([X_INTRODUCED_296_,X_INTRODUCED_541_,X_INTRODUCED_543_,X_INTRODUCED_545_],[]); +constraint bool_clause([X_INTRODUCED_297_,X_INTRODUCED_548_,X_INTRODUCED_550_,X_INTRODUCED_552_],[]); +constraint bool_clause([X_INTRODUCED_298_,X_INTRODUCED_555_,X_INTRODUCED_557_,X_INTRODUCED_559_],[]); +constraint bool_clause([X_INTRODUCED_299_,X_INTRODUCED_562_,X_INTRODUCED_564_],[]); +constraint bool_clause([X_INTRODUCED_300_,X_INTRODUCED_567_,X_INTRODUCED_569_,X_INTRODUCED_571_],[]); +constraint bool_clause([X_INTRODUCED_302_,X_INTRODUCED_583_,X_INTRODUCED_585_,X_INTRODUCED_587_,X_INTRODUCED_589_],[]); +constraint bool_clause([X_INTRODUCED_303_,X_INTRODUCED_592_,X_INTRODUCED_594_,X_INTRODUCED_596_,X_INTRODUCED_598_],[]); +constraint bool_clause([X_INTRODUCED_304_,X_INTRODUCED_601_,X_INTRODUCED_603_,X_INTRODUCED_605_],[]); +constraint bool_clause([X_INTRODUCED_305_,X_INTRODUCED_608_,X_INTRODUCED_610_,X_INTRODUCED_612_],[]); +constraint bool_clause([X_INTRODUCED_306_,X_INTRODUCED_615_,X_INTRODUCED_617_,X_INTRODUCED_619_,X_INTRODUCED_621_],[]); +constraint bool_clause([X_INTRODUCED_307_,X_INTRODUCED_624_,X_INTRODUCED_626_,X_INTRODUCED_628_,X_INTRODUCED_630_],[]); +constraint bool_clause([X_INTRODUCED_308_,X_INTRODUCED_633_,X_INTRODUCED_635_,X_INTRODUCED_637_,X_INTRODUCED_639_],[]); +constraint bool_clause([X_INTRODUCED_309_,X_INTRODUCED_642_,X_INTRODUCED_644_,X_INTRODUCED_646_],[]); +constraint bool_clause([X_INTRODUCED_310_,X_INTRODUCED_649_,X_INTRODUCED_651_,X_INTRODUCED_653_],[]); +constraint bool_clause([X_INTRODUCED_312_,X_INTRODUCED_665_,X_INTRODUCED_667_,X_INTRODUCED_669_,X_INTRODUCED_671_],[]); +constraint bool_clause([X_INTRODUCED_313_,X_INTRODUCED_674_,X_INTRODUCED_676_,X_INTRODUCED_678_,X_INTRODUCED_680_],[]); +constraint bool_clause([X_INTRODUCED_314_,X_INTRODUCED_683_,X_INTRODUCED_685_,X_INTRODUCED_687_],[]); +constraint bool_clause([X_INTRODUCED_315_,X_INTRODUCED_690_,X_INTRODUCED_692_],[]); +constraint bool_clause([X_INTRODUCED_316_,X_INTRODUCED_695_,X_INTRODUCED_697_,X_INTRODUCED_699_],[]); +constraint bool_clause([X_INTRODUCED_317_,X_INTRODUCED_702_,X_INTRODUCED_704_,X_INTRODUCED_706_],[]); +constraint bool_clause([X_INTRODUCED_318_,X_INTRODUCED_709_,X_INTRODUCED_711_,X_INTRODUCED_713_],[]); +constraint bool_clause([X_INTRODUCED_319_,X_INTRODUCED_716_,X_INTRODUCED_718_],[]); +constraint bool_clause([],[X_INTRODUCED_17_,X_INTRODUCED_149_]); +constraint bool_clause([],[X_INTRODUCED_19_,X_INTRODUCED_154_]); +constraint bool_clause([X_INTRODUCED_20_],[X_INTRODUCED_19_,X_INTRODUCED_154_]); +constraint bool_clause([X_INTRODUCED_19_],[X_INTRODUCED_20_,X_INTRODUCED_158_]); +constraint bool_clause([X_INTRODUCED_21_],[X_INTRODUCED_20_,X_INTRODUCED_158_]); +constraint bool_clause([X_INTRODUCED_20_],[X_INTRODUCED_21_,X_INTRODUCED_162_]); +constraint bool_clause([X_INTRODUCED_25_],[X_INTRODUCED_20_,X_INTRODUCED_158_]); +constraint bool_clause([X_INTRODUCED_20_],[X_INTRODUCED_25_,X_INTRODUCED_164_]); +constraint bool_clause([X_INTRODUCED_26_],[X_INTRODUCED_21_,X_INTRODUCED_162_]); +constraint bool_clause([X_INTRODUCED_21_],[X_INTRODUCED_26_,X_INTRODUCED_166_]); +constraint bool_clause([],[X_INTRODUCED_27_,X_INTRODUCED_169_]); +constraint bool_clause([],[X_INTRODUCED_25_,X_INTRODUCED_164_]); +constraint bool_clause([],[X_INTRODUCED_29_,X_INTRODUCED_175_]); +constraint bool_clause([X_INTRODUCED_26_],[X_INTRODUCED_25_,X_INTRODUCED_164_]); +constraint bool_clause([X_INTRODUCED_25_],[X_INTRODUCED_26_,X_INTRODUCED_166_]); +constraint bool_clause([X_INTRODUCED_30_],[X_INTRODUCED_25_,X_INTRODUCED_164_]); +constraint bool_clause([X_INTRODUCED_25_],[X_INTRODUCED_30_,X_INTRODUCED_178_]); +constraint bool_clause([X_INTRODUCED_31_],[X_INTRODUCED_26_,X_INTRODUCED_166_]); +constraint bool_clause([X_INTRODUCED_26_],[X_INTRODUCED_31_,X_INTRODUCED_180_]); +constraint bool_clause([X_INTRODUCED_30_],[X_INTRODUCED_29_,X_INTRODUCED_175_]); +constraint bool_clause([X_INTRODUCED_29_],[X_INTRODUCED_30_,X_INTRODUCED_178_]); +constraint bool_clause([X_INTRODUCED_31_],[X_INTRODUCED_30_,X_INTRODUCED_178_]); +constraint bool_clause([X_INTRODUCED_30_],[X_INTRODUCED_31_,X_INTRODUCED_180_]); +constraint bool_clause([X_INTRODUCED_35_],[X_INTRODUCED_30_,X_INTRODUCED_178_]); +constraint bool_clause([X_INTRODUCED_30_],[X_INTRODUCED_35_,X_INTRODUCED_192_]); +constraint bool_clause([X_INTRODUCED_36_],[X_INTRODUCED_31_,X_INTRODUCED_180_]); +constraint bool_clause([X_INTRODUCED_31_],[X_INTRODUCED_36_,X_INTRODUCED_194_]); +constraint bool_clause([],[X_INTRODUCED_37_,X_INTRODUCED_197_]); +constraint bool_clause([],[X_INTRODUCED_35_,X_INTRODUCED_192_]); +constraint bool_clause([],[X_INTRODUCED_39_,X_INTRODUCED_203_]); +constraint bool_clause([X_INTRODUCED_36_],[X_INTRODUCED_35_,X_INTRODUCED_192_]); +constraint bool_clause([X_INTRODUCED_35_],[X_INTRODUCED_36_,X_INTRODUCED_194_]); +constraint bool_clause([X_INTRODUCED_41_],[X_INTRODUCED_36_,X_INTRODUCED_194_]); +constraint bool_clause([X_INTRODUCED_36_],[X_INTRODUCED_41_,X_INTRODUCED_208_]); +constraint bool_clause([],[X_INTRODUCED_41_,X_INTRODUCED_208_]); +constraint bool_clause([X_INTRODUCED_348_,X_INTRODUCED_349_],[]); +constraint bool_clause([X_INTRODUCED_360_,X_INTRODUCED_361_],[]); +constraint bool_clause([X_INTRODUCED_366_,X_INTRODUCED_367_],[]); +constraint bool_clause([X_INTRODUCED_378_,X_INTRODUCED_379_],[]); +constraint bool_clause([X_INTRODUCED_390_,X_INTRODUCED_391_],[]); +constraint bool_clause([X_INTRODUCED_396_,X_INTRODUCED_397_],[]); +constraint bool_clause([X_INTRODUCED_408_,X_INTRODUCED_409_],[]); +constraint bool_clause([X_INTRODUCED_414_,X_INTRODUCED_415_],[]); +constraint bool_not(X_INTRODUCED_17_,X_INTRODUCED_149_):: defines_var(X_INTRODUCED_149_); +constraint bool_not(false,X_INTRODUCED_150_):: defines_var(X_INTRODUCED_150_); +constraint bool_not(false,X_INTRODUCED_152_):: defines_var(X_INTRODUCED_152_); +constraint bool_not(X_INTRODUCED_19_,X_INTRODUCED_154_):: defines_var(X_INTRODUCED_154_); +constraint bool_not(true,X_INTRODUCED_156_):: defines_var(X_INTRODUCED_156_); +constraint bool_not(X_INTRODUCED_20_,X_INTRODUCED_158_):: defines_var(X_INTRODUCED_158_); +constraint bool_not(false,X_INTRODUCED_160_):: defines_var(X_INTRODUCED_160_); +constraint bool_not(X_INTRODUCED_21_,X_INTRODUCED_162_):: defines_var(X_INTRODUCED_162_); +constraint bool_not(X_INTRODUCED_25_,X_INTRODUCED_164_):: defines_var(X_INTRODUCED_164_); +constraint bool_not(X_INTRODUCED_26_,X_INTRODUCED_166_):: defines_var(X_INTRODUCED_166_); +constraint bool_not(X_INTRODUCED_27_,X_INTRODUCED_169_):: defines_var(X_INTRODUCED_169_); +constraint bool_not(false,X_INTRODUCED_172_):: defines_var(X_INTRODUCED_172_); +constraint bool_not(X_INTRODUCED_29_,X_INTRODUCED_175_):: defines_var(X_INTRODUCED_175_); +constraint bool_not(X_INTRODUCED_30_,X_INTRODUCED_178_):: defines_var(X_INTRODUCED_178_); +constraint bool_not(X_INTRODUCED_31_,X_INTRODUCED_180_):: defines_var(X_INTRODUCED_180_); +constraint bool_not(false,X_INTRODUCED_183_):: defines_var(X_INTRODUCED_183_); +constraint bool_not(true,X_INTRODUCED_186_):: defines_var(X_INTRODUCED_186_); +constraint bool_not(false,X_INTRODUCED_189_):: defines_var(X_INTRODUCED_189_); +constraint bool_not(X_INTRODUCED_35_,X_INTRODUCED_192_):: defines_var(X_INTRODUCED_192_); +constraint bool_not(X_INTRODUCED_36_,X_INTRODUCED_194_):: defines_var(X_INTRODUCED_194_); +constraint bool_not(X_INTRODUCED_37_,X_INTRODUCED_197_):: defines_var(X_INTRODUCED_197_); +constraint bool_not(false,X_INTRODUCED_200_):: defines_var(X_INTRODUCED_200_); +constraint bool_not(X_INTRODUCED_39_,X_INTRODUCED_203_):: defines_var(X_INTRODUCED_203_); +constraint bool_not(false,X_INTRODUCED_206_):: defines_var(X_INTRODUCED_206_); +constraint bool_not(X_INTRODUCED_41_,X_INTRODUCED_208_):: defines_var(X_INTRODUCED_208_); +constraint int_eq_imp(X_INTRODUCED_243_,0,X_INTRODUCED_17_); +constraint int_eq_imp(X_INTRODUCED_245_,0,X_INTRODUCED_19_); +constraint int_eq_imp(X_INTRODUCED_246_,0,X_INTRODUCED_20_); +constraint int_eq_imp(X_INTRODUCED_247_,0,X_INTRODUCED_21_); +constraint int_eq_imp(X_INTRODUCED_251_,0,X_INTRODUCED_25_); +constraint int_eq_imp(X_INTRODUCED_252_,0,X_INTRODUCED_26_); +constraint int_eq_imp(X_INTRODUCED_253_,0,X_INTRODUCED_27_); +constraint int_eq_imp(X_INTRODUCED_255_,0,X_INTRODUCED_29_); +constraint int_eq_imp(X_INTRODUCED_256_,0,X_INTRODUCED_30_); +constraint int_eq_imp(X_INTRODUCED_257_,0,X_INTRODUCED_31_); +constraint int_eq_imp(X_INTRODUCED_261_,0,X_INTRODUCED_35_); +constraint int_eq_imp(X_INTRODUCED_262_,0,X_INTRODUCED_36_); +constraint int_eq_imp(X_INTRODUCED_263_,0,X_INTRODUCED_37_); +constraint int_eq_imp(X_INTRODUCED_265_,0,X_INTRODUCED_39_); +constraint int_eq_imp(X_INTRODUCED_267_,0,X_INTRODUCED_41_); +constraint int_lin_eq([4,4,3,1,2,2,4,3,1,2,3,3,1,4,4,-1],[X_INTRODUCED_98_,X_INTRODUCED_104_,X_INTRODUCED_106_,X_INTRODUCED_109_,X_INTRODUCED_115_,X_INTRODUCED_117_,X_INTRODUCED_119_,X_INTRODUCED_124_,X_INTRODUCED_126_,X_INTRODUCED_127_,X_INTRODUCED_135_,X_INTRODUCED_137_,X_INTRODUCED_139_,X_INTRODUCED_142_,X_INTRODUCED_146_,obj],-4):: defines_var(obj); +constraint bool2int(X_INTRODUCED_17_,X_INTRODUCED_98_):: defines_var(X_INTRODUCED_98_); +constraint bool2int(X_INTRODUCED_19_,X_INTRODUCED_104_):: defines_var(X_INTRODUCED_104_); +constraint bool2int(X_INTRODUCED_20_,X_INTRODUCED_106_):: defines_var(X_INTRODUCED_106_); +constraint bool2int(X_INTRODUCED_21_,X_INTRODUCED_109_):: defines_var(X_INTRODUCED_109_); +constraint bool2int(X_INTRODUCED_25_,X_INTRODUCED_115_):: defines_var(X_INTRODUCED_115_); +constraint bool2int(X_INTRODUCED_26_,X_INTRODUCED_117_):: defines_var(X_INTRODUCED_117_); +constraint bool2int(X_INTRODUCED_27_,X_INTRODUCED_119_):: defines_var(X_INTRODUCED_119_); +constraint bool2int(X_INTRODUCED_29_,X_INTRODUCED_124_):: defines_var(X_INTRODUCED_124_); +constraint bool2int(X_INTRODUCED_30_,X_INTRODUCED_126_):: defines_var(X_INTRODUCED_126_); +constraint bool2int(X_INTRODUCED_31_,X_INTRODUCED_127_):: defines_var(X_INTRODUCED_127_); +constraint bool2int(X_INTRODUCED_35_,X_INTRODUCED_135_):: defines_var(X_INTRODUCED_135_); +constraint bool2int(X_INTRODUCED_36_,X_INTRODUCED_137_):: defines_var(X_INTRODUCED_137_); +constraint bool2int(X_INTRODUCED_37_,X_INTRODUCED_139_):: defines_var(X_INTRODUCED_139_); +constraint bool2int(X_INTRODUCED_39_,X_INTRODUCED_142_):: defines_var(X_INTRODUCED_142_); +constraint bool2int(X_INTRODUCED_41_,X_INTRODUCED_146_):: defines_var(X_INTRODUCED_146_); +constraint int_eq_reif(X_INTRODUCED_269_,1,X_INTRODUCED_295_):: defines_var(X_INTRODUCED_295_); +constraint int_eq_reif(X_INTRODUCED_270_,2,X_INTRODUCED_296_):: defines_var(X_INTRODUCED_296_); +constraint int_eq_reif(X_INTRODUCED_271_,3,X_INTRODUCED_297_):: defines_var(X_INTRODUCED_297_); +constraint int_eq_reif(X_INTRODUCED_272_,4,X_INTRODUCED_298_):: defines_var(X_INTRODUCED_298_); +constraint int_eq_reif(X_INTRODUCED_273_,5,X_INTRODUCED_299_):: defines_var(X_INTRODUCED_299_); +constraint int_eq_reif(X_INTRODUCED_274_,6,X_INTRODUCED_300_):: defines_var(X_INTRODUCED_300_); +constraint int_eq_reif(X_INTRODUCED_276_,8,X_INTRODUCED_302_):: defines_var(X_INTRODUCED_302_); +constraint int_eq_reif(X_INTRODUCED_277_,9,X_INTRODUCED_303_):: defines_var(X_INTRODUCED_303_); +constraint int_eq_reif(X_INTRODUCED_278_,10,X_INTRODUCED_304_):: defines_var(X_INTRODUCED_304_); +constraint int_eq_reif(X_INTRODUCED_279_,11,X_INTRODUCED_305_):: defines_var(X_INTRODUCED_305_); +constraint int_eq_reif(X_INTRODUCED_280_,12,X_INTRODUCED_306_):: defines_var(X_INTRODUCED_306_); +constraint int_eq_reif(X_INTRODUCED_281_,13,X_INTRODUCED_307_):: defines_var(X_INTRODUCED_307_); +constraint int_eq_reif(X_INTRODUCED_282_,14,X_INTRODUCED_308_):: defines_var(X_INTRODUCED_308_); +constraint int_eq_reif(X_INTRODUCED_283_,15,X_INTRODUCED_309_):: defines_var(X_INTRODUCED_309_); +constraint int_eq_reif(X_INTRODUCED_284_,16,X_INTRODUCED_310_):: defines_var(X_INTRODUCED_310_); +constraint int_eq_reif(X_INTRODUCED_286_,18,X_INTRODUCED_312_):: defines_var(X_INTRODUCED_312_); +constraint int_eq_reif(X_INTRODUCED_287_,19,X_INTRODUCED_313_):: defines_var(X_INTRODUCED_313_); +constraint int_eq_reif(X_INTRODUCED_288_,20,X_INTRODUCED_314_):: defines_var(X_INTRODUCED_314_); +constraint int_eq_reif(X_INTRODUCED_289_,21,X_INTRODUCED_315_):: defines_var(X_INTRODUCED_315_); +constraint int_eq_reif(X_INTRODUCED_290_,22,X_INTRODUCED_316_):: defines_var(X_INTRODUCED_316_); +constraint int_eq_reif(X_INTRODUCED_291_,23,X_INTRODUCED_317_):: defines_var(X_INTRODUCED_317_); +constraint int_eq_reif(X_INTRODUCED_292_,24,X_INTRODUCED_318_):: defines_var(X_INTRODUCED_318_); +constraint int_eq_reif(X_INTRODUCED_293_,25,X_INTRODUCED_319_):: defines_var(X_INTRODUCED_319_); +constraint int_eq_imp(X_INTRODUCED_236_,1,X_INTRODUCED_345_):: defines_var(X_INTRODUCED_345_); +constraint int_ne_imp(X_INTRODUCED_269_,1,X_INTRODUCED_346_):: defines_var(X_INTRODUCED_346_); +constraint int_eq_reif(X_INTRODUCED_236_,2,X_INTRODUCED_348_):: defines_var(X_INTRODUCED_348_); +constraint int_ne_reif(X_INTRODUCED_270_,2,X_INTRODUCED_349_):: defines_var(X_INTRODUCED_349_); +constraint int_eq_imp(X_INTRODUCED_236_,3,X_INTRODUCED_351_):: defines_var(X_INTRODUCED_351_); +constraint int_ne_imp(X_INTRODUCED_271_,3,X_INTRODUCED_352_):: defines_var(X_INTRODUCED_352_); +constraint int_eq_imp(X_INTRODUCED_236_,4,X_INTRODUCED_354_):: defines_var(X_INTRODUCED_354_); +constraint int_ne_imp(X_INTRODUCED_272_,4,X_INTRODUCED_355_):: defines_var(X_INTRODUCED_355_); +constraint int_eq_imp(X_INTRODUCED_236_,5,X_INTRODUCED_357_):: defines_var(X_INTRODUCED_357_); +constraint int_ne_imp(X_INTRODUCED_273_,5,X_INTRODUCED_358_):: defines_var(X_INTRODUCED_358_); +constraint int_eq_reif(X_INTRODUCED_236_,6,X_INTRODUCED_360_):: defines_var(X_INTRODUCED_360_); +constraint int_ne_reif(X_INTRODUCED_274_,6,X_INTRODUCED_361_):: defines_var(X_INTRODUCED_361_); +constraint int_eq_reif(X_INTRODUCED_236_,8,X_INTRODUCED_366_):: defines_var(X_INTRODUCED_366_); +constraint int_ne_reif(X_INTRODUCED_276_,8,X_INTRODUCED_367_):: defines_var(X_INTRODUCED_367_); +constraint int_eq_imp(X_INTRODUCED_236_,9,X_INTRODUCED_369_):: defines_var(X_INTRODUCED_369_); +constraint int_ne_imp(X_INTRODUCED_277_,9,X_INTRODUCED_370_):: defines_var(X_INTRODUCED_370_); +constraint int_eq_imp(X_INTRODUCED_236_,10,X_INTRODUCED_372_):: defines_var(X_INTRODUCED_372_); +constraint int_ne_imp(X_INTRODUCED_278_,10,X_INTRODUCED_373_):: defines_var(X_INTRODUCED_373_); +constraint int_eq_imp(X_INTRODUCED_236_,11,X_INTRODUCED_375_):: defines_var(X_INTRODUCED_375_); +constraint int_ne_imp(X_INTRODUCED_279_,11,X_INTRODUCED_376_):: defines_var(X_INTRODUCED_376_); +constraint int_eq_reif(X_INTRODUCED_236_,12,X_INTRODUCED_378_):: defines_var(X_INTRODUCED_378_); +constraint int_ne_reif(X_INTRODUCED_280_,12,X_INTRODUCED_379_):: defines_var(X_INTRODUCED_379_); +constraint int_eq_imp(X_INTRODUCED_236_,13,X_INTRODUCED_381_):: defines_var(X_INTRODUCED_381_); +constraint int_ne_imp(X_INTRODUCED_281_,13,X_INTRODUCED_382_):: defines_var(X_INTRODUCED_382_); +constraint int_eq_imp(X_INTRODUCED_236_,14,X_INTRODUCED_384_):: defines_var(X_INTRODUCED_384_); +constraint int_ne_imp(X_INTRODUCED_282_,14,X_INTRODUCED_385_):: defines_var(X_INTRODUCED_385_); +constraint int_eq_imp(X_INTRODUCED_236_,15,X_INTRODUCED_387_):: defines_var(X_INTRODUCED_387_); +constraint int_ne_imp(X_INTRODUCED_283_,15,X_INTRODUCED_388_):: defines_var(X_INTRODUCED_388_); +constraint int_eq_reif(X_INTRODUCED_236_,16,X_INTRODUCED_390_):: defines_var(X_INTRODUCED_390_); +constraint int_ne_reif(X_INTRODUCED_284_,16,X_INTRODUCED_391_):: defines_var(X_INTRODUCED_391_); +constraint int_eq_reif(X_INTRODUCED_236_,18,X_INTRODUCED_396_):: defines_var(X_INTRODUCED_396_); +constraint int_ne_reif(X_INTRODUCED_286_,18,X_INTRODUCED_397_):: defines_var(X_INTRODUCED_397_); +constraint int_eq_imp(X_INTRODUCED_236_,19,X_INTRODUCED_399_):: defines_var(X_INTRODUCED_399_); +constraint int_ne_imp(X_INTRODUCED_287_,19,X_INTRODUCED_400_):: defines_var(X_INTRODUCED_400_); +constraint int_eq_imp(X_INTRODUCED_236_,20,X_INTRODUCED_402_):: defines_var(X_INTRODUCED_402_); +constraint int_ne_imp(X_INTRODUCED_288_,20,X_INTRODUCED_403_):: defines_var(X_INTRODUCED_403_); +constraint int_eq_imp(X_INTRODUCED_236_,21,X_INTRODUCED_405_):: defines_var(X_INTRODUCED_405_); +constraint int_ne_imp(X_INTRODUCED_289_,21,X_INTRODUCED_406_):: defines_var(X_INTRODUCED_406_); +constraint int_eq_reif(X_INTRODUCED_236_,22,X_INTRODUCED_408_):: defines_var(X_INTRODUCED_408_); +constraint int_ne_reif(X_INTRODUCED_290_,22,X_INTRODUCED_409_):: defines_var(X_INTRODUCED_409_); +constraint int_eq_imp(X_INTRODUCED_236_,23,X_INTRODUCED_411_):: defines_var(X_INTRODUCED_411_); +constraint int_ne_imp(X_INTRODUCED_291_,23,X_INTRODUCED_412_):: defines_var(X_INTRODUCED_412_); +constraint int_eq_reif(X_INTRODUCED_236_,24,X_INTRODUCED_414_):: defines_var(X_INTRODUCED_414_); +constraint int_ne_reif(X_INTRODUCED_292_,24,X_INTRODUCED_415_):: defines_var(X_INTRODUCED_415_); +constraint int_eq_imp(X_INTRODUCED_236_,25,X_INTRODUCED_417_):: defines_var(X_INTRODUCED_417_); +constraint int_ne_imp(X_INTRODUCED_293_,25,X_INTRODUCED_418_):: defines_var(X_INTRODUCED_418_); +constraint array_bool_and([X_INTRODUCED_149_,X_INTRODUCED_420_],X_INTRODUCED_421_):: defines_var(X_INTRODUCED_421_); +constraint array_bool_and([X_INTRODUCED_154_,X_INTRODUCED_423_],X_INTRODUCED_424_):: defines_var(X_INTRODUCED_424_); +constraint array_bool_and([X_INTRODUCED_158_,X_INTRODUCED_425_],X_INTRODUCED_426_):: defines_var(X_INTRODUCED_426_); +constraint array_bool_and([X_INTRODUCED_162_,X_INTRODUCED_427_],X_INTRODUCED_428_):: defines_var(X_INTRODUCED_428_); +constraint array_bool_and([X_INTRODUCED_164_,X_INTRODUCED_431_],X_INTRODUCED_432_):: defines_var(X_INTRODUCED_432_); +constraint array_bool_and([X_INTRODUCED_166_,X_INTRODUCED_433_],X_INTRODUCED_434_):: defines_var(X_INTRODUCED_434_); +constraint array_bool_and([X_INTRODUCED_169_,X_INTRODUCED_435_],X_INTRODUCED_436_):: defines_var(X_INTRODUCED_436_); +constraint array_bool_and([X_INTRODUCED_175_,X_INTRODUCED_438_],X_INTRODUCED_439_):: defines_var(X_INTRODUCED_439_); +constraint array_bool_and([X_INTRODUCED_178_,X_INTRODUCED_440_],X_INTRODUCED_441_):: defines_var(X_INTRODUCED_441_); +constraint array_bool_and([X_INTRODUCED_180_,X_INTRODUCED_442_],X_INTRODUCED_443_):: defines_var(X_INTRODUCED_443_); +constraint array_bool_and([X_INTRODUCED_192_,X_INTRODUCED_446_],X_INTRODUCED_447_):: defines_var(X_INTRODUCED_447_); +constraint array_bool_and([X_INTRODUCED_194_,X_INTRODUCED_448_],X_INTRODUCED_449_):: defines_var(X_INTRODUCED_449_); +constraint array_bool_and([X_INTRODUCED_197_,X_INTRODUCED_450_],X_INTRODUCED_451_):: defines_var(X_INTRODUCED_451_); +constraint array_bool_and([X_INTRODUCED_203_,X_INTRODUCED_453_],X_INTRODUCED_454_):: defines_var(X_INTRODUCED_454_); +constraint array_bool_and([X_INTRODUCED_208_,X_INTRODUCED_456_],X_INTRODUCED_457_):: defines_var(X_INTRODUCED_457_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_243_,X_INTRODUCED_459_],1,X_INTRODUCED_462_):: defines_var(X_INTRODUCED_462_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_244_,X_INTRODUCED_463_],1,X_INTRODUCED_465_):: defines_var(X_INTRODUCED_465_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_245_,X_INTRODUCED_466_],1,X_INTRODUCED_468_):: defines_var(X_INTRODUCED_468_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_246_,X_INTRODUCED_469_],1,X_INTRODUCED_471_):: defines_var(X_INTRODUCED_471_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_247_,X_INTRODUCED_472_],1,X_INTRODUCED_474_):: defines_var(X_INTRODUCED_474_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_248_,X_INTRODUCED_475_],1,X_INTRODUCED_477_):: defines_var(X_INTRODUCED_477_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_250_,X_INTRODUCED_481_],1,X_INTRODUCED_483_):: defines_var(X_INTRODUCED_483_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_251_,X_INTRODUCED_484_],1,X_INTRODUCED_486_):: defines_var(X_INTRODUCED_486_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_252_,X_INTRODUCED_487_],1,X_INTRODUCED_489_):: defines_var(X_INTRODUCED_489_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_253_,X_INTRODUCED_490_],1,X_INTRODUCED_492_):: defines_var(X_INTRODUCED_492_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_254_,X_INTRODUCED_493_],1,X_INTRODUCED_495_):: defines_var(X_INTRODUCED_495_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_255_,X_INTRODUCED_496_],1,X_INTRODUCED_498_):: defines_var(X_INTRODUCED_498_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_256_,X_INTRODUCED_499_],1,X_INTRODUCED_501_):: defines_var(X_INTRODUCED_501_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_257_,X_INTRODUCED_502_],1,X_INTRODUCED_504_):: defines_var(X_INTRODUCED_504_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_258_,X_INTRODUCED_505_],1,X_INTRODUCED_507_):: defines_var(X_INTRODUCED_507_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_260_,X_INTRODUCED_511_],1,X_INTRODUCED_513_):: defines_var(X_INTRODUCED_513_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_261_,X_INTRODUCED_514_],1,X_INTRODUCED_516_):: defines_var(X_INTRODUCED_516_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_262_,X_INTRODUCED_517_],1,X_INTRODUCED_519_):: defines_var(X_INTRODUCED_519_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_263_,X_INTRODUCED_520_],1,X_INTRODUCED_522_):: defines_var(X_INTRODUCED_522_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_264_,X_INTRODUCED_523_],1,X_INTRODUCED_525_):: defines_var(X_INTRODUCED_525_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_265_,X_INTRODUCED_526_],1,X_INTRODUCED_528_):: defines_var(X_INTRODUCED_528_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_266_,X_INTRODUCED_529_],1,X_INTRODUCED_531_):: defines_var(X_INTRODUCED_531_); +constraint int_lin_eq_imp(X_INTRODUCED_461_,[X_INTRODUCED_267_,X_INTRODUCED_532_],1,X_INTRODUCED_534_):: defines_var(X_INTRODUCED_534_); +constraint int_eq_imp(X_INTRODUCED_269_,2,X_INTRODUCED_535_):: defines_var(X_INTRODUCED_535_); +constraint array_bool_and([X_INTRODUCED_149_,X_INTRODUCED_150_,X_INTRODUCED_535_],X_INTRODUCED_536_):: defines_var(X_INTRODUCED_536_); +constraint int_eq_imp(X_INTRODUCED_269_,6,X_INTRODUCED_537_):: defines_var(X_INTRODUCED_537_); +constraint array_bool_and([X_INTRODUCED_149_,X_INTRODUCED_152_,X_INTRODUCED_537_],X_INTRODUCED_538_):: defines_var(X_INTRODUCED_538_); +constraint int_eq_imp(X_INTRODUCED_270_,1,X_INTRODUCED_540_):: defines_var(X_INTRODUCED_540_); +constraint array_bool_and([X_INTRODUCED_149_,X_INTRODUCED_150_,X_INTRODUCED_540_],X_INTRODUCED_541_):: defines_var(X_INTRODUCED_541_); +constraint int_eq_imp(X_INTRODUCED_270_,3,X_INTRODUCED_542_):: defines_var(X_INTRODUCED_542_); +constraint array_bool_and([X_INTRODUCED_150_,X_INTRODUCED_154_,X_INTRODUCED_542_],X_INTRODUCED_543_):: defines_var(X_INTRODUCED_543_); +constraint int_eq_imp(X_INTRODUCED_270_,7,X_INTRODUCED_544_):: defines_var(X_INTRODUCED_544_); +constraint array_bool_and([X_INTRODUCED_150_,X_INTRODUCED_156_,X_INTRODUCED_544_],X_INTRODUCED_545_):: defines_var(X_INTRODUCED_545_); +constraint int_eq_imp(X_INTRODUCED_271_,2,X_INTRODUCED_547_):: defines_var(X_INTRODUCED_547_); +constraint array_bool_and([X_INTRODUCED_150_,X_INTRODUCED_154_,X_INTRODUCED_547_],X_INTRODUCED_548_):: defines_var(X_INTRODUCED_548_); +constraint int_eq_imp(X_INTRODUCED_271_,4,X_INTRODUCED_549_):: defines_var(X_INTRODUCED_549_); +constraint array_bool_and([X_INTRODUCED_154_,X_INTRODUCED_158_,X_INTRODUCED_549_],X_INTRODUCED_550_):: defines_var(X_INTRODUCED_550_); +constraint int_eq_imp(X_INTRODUCED_271_,8,X_INTRODUCED_551_):: defines_var(X_INTRODUCED_551_); +constraint array_bool_and([X_INTRODUCED_154_,X_INTRODUCED_160_,X_INTRODUCED_551_],X_INTRODUCED_552_):: defines_var(X_INTRODUCED_552_); +constraint int_eq_imp(X_INTRODUCED_272_,3,X_INTRODUCED_554_):: defines_var(X_INTRODUCED_554_); +constraint array_bool_and([X_INTRODUCED_154_,X_INTRODUCED_158_,X_INTRODUCED_554_],X_INTRODUCED_555_):: defines_var(X_INTRODUCED_555_); +constraint int_eq_imp(X_INTRODUCED_272_,5,X_INTRODUCED_556_):: defines_var(X_INTRODUCED_556_); +constraint array_bool_and([X_INTRODUCED_158_,X_INTRODUCED_162_,X_INTRODUCED_556_],X_INTRODUCED_557_):: defines_var(X_INTRODUCED_557_); +constraint int_eq_imp(X_INTRODUCED_272_,9,X_INTRODUCED_558_):: defines_var(X_INTRODUCED_558_); +constraint array_bool_and([X_INTRODUCED_158_,X_INTRODUCED_164_,X_INTRODUCED_558_],X_INTRODUCED_559_):: defines_var(X_INTRODUCED_559_); +constraint int_eq_imp(X_INTRODUCED_273_,4,X_INTRODUCED_561_):: defines_var(X_INTRODUCED_561_); +constraint array_bool_and([X_INTRODUCED_158_,X_INTRODUCED_162_,X_INTRODUCED_561_],X_INTRODUCED_562_):: defines_var(X_INTRODUCED_562_); +constraint int_eq_imp(X_INTRODUCED_273_,10,X_INTRODUCED_563_):: defines_var(X_INTRODUCED_563_); +constraint array_bool_and([X_INTRODUCED_162_,X_INTRODUCED_166_,X_INTRODUCED_563_],X_INTRODUCED_564_):: defines_var(X_INTRODUCED_564_); +constraint int_eq_imp(X_INTRODUCED_274_,1,X_INTRODUCED_566_):: defines_var(X_INTRODUCED_566_); +constraint array_bool_and([X_INTRODUCED_149_,X_INTRODUCED_152_,X_INTRODUCED_566_],X_INTRODUCED_567_):: defines_var(X_INTRODUCED_567_); +constraint int_eq_imp(X_INTRODUCED_274_,7,X_INTRODUCED_568_):: defines_var(X_INTRODUCED_568_); +constraint array_bool_and([X_INTRODUCED_152_,X_INTRODUCED_156_,X_INTRODUCED_568_],X_INTRODUCED_569_):: defines_var(X_INTRODUCED_569_); +constraint int_eq_imp(X_INTRODUCED_274_,11,X_INTRODUCED_570_):: defines_var(X_INTRODUCED_570_); +constraint array_bool_and([X_INTRODUCED_152_,X_INTRODUCED_169_,X_INTRODUCED_570_],X_INTRODUCED_571_):: defines_var(X_INTRODUCED_571_); +constraint int_eq_imp(X_INTRODUCED_276_,3,X_INTRODUCED_582_):: defines_var(X_INTRODUCED_582_); +constraint array_bool_and([X_INTRODUCED_154_,X_INTRODUCED_160_,X_INTRODUCED_582_],X_INTRODUCED_583_):: defines_var(X_INTRODUCED_583_); +constraint int_eq_imp(X_INTRODUCED_276_,7,X_INTRODUCED_584_):: defines_var(X_INTRODUCED_584_); +constraint array_bool_and([X_INTRODUCED_156_,X_INTRODUCED_160_,X_INTRODUCED_584_],X_INTRODUCED_585_):: defines_var(X_INTRODUCED_585_); +constraint int_eq_imp(X_INTRODUCED_276_,9,X_INTRODUCED_586_):: defines_var(X_INTRODUCED_586_); +constraint array_bool_and([X_INTRODUCED_160_,X_INTRODUCED_164_,X_INTRODUCED_586_],X_INTRODUCED_587_):: defines_var(X_INTRODUCED_587_); +constraint int_eq_imp(X_INTRODUCED_276_,13,X_INTRODUCED_588_):: defines_var(X_INTRODUCED_588_); +constraint array_bool_and([X_INTRODUCED_160_,X_INTRODUCED_175_,X_INTRODUCED_588_],X_INTRODUCED_589_):: defines_var(X_INTRODUCED_589_); +constraint int_eq_imp(X_INTRODUCED_277_,4,X_INTRODUCED_591_):: defines_var(X_INTRODUCED_591_); +constraint array_bool_and([X_INTRODUCED_158_,X_INTRODUCED_164_,X_INTRODUCED_591_],X_INTRODUCED_592_):: defines_var(X_INTRODUCED_592_); +constraint int_eq_imp(X_INTRODUCED_277_,8,X_INTRODUCED_593_):: defines_var(X_INTRODUCED_593_); +constraint array_bool_and([X_INTRODUCED_160_,X_INTRODUCED_164_,X_INTRODUCED_593_],X_INTRODUCED_594_):: defines_var(X_INTRODUCED_594_); +constraint int_eq_imp(X_INTRODUCED_277_,10,X_INTRODUCED_595_):: defines_var(X_INTRODUCED_595_); +constraint array_bool_and([X_INTRODUCED_164_,X_INTRODUCED_166_,X_INTRODUCED_595_],X_INTRODUCED_596_):: defines_var(X_INTRODUCED_596_); +constraint int_eq_imp(X_INTRODUCED_277_,14,X_INTRODUCED_597_):: defines_var(X_INTRODUCED_597_); +constraint array_bool_and([X_INTRODUCED_164_,X_INTRODUCED_178_,X_INTRODUCED_597_],X_INTRODUCED_598_):: defines_var(X_INTRODUCED_598_); +constraint int_eq_imp(X_INTRODUCED_278_,5,X_INTRODUCED_600_):: defines_var(X_INTRODUCED_600_); +constraint array_bool_and([X_INTRODUCED_162_,X_INTRODUCED_166_,X_INTRODUCED_600_],X_INTRODUCED_601_):: defines_var(X_INTRODUCED_601_); +constraint int_eq_imp(X_INTRODUCED_278_,9,X_INTRODUCED_602_):: defines_var(X_INTRODUCED_602_); +constraint array_bool_and([X_INTRODUCED_164_,X_INTRODUCED_166_,X_INTRODUCED_602_],X_INTRODUCED_603_):: defines_var(X_INTRODUCED_603_); +constraint int_eq_imp(X_INTRODUCED_278_,15,X_INTRODUCED_604_):: defines_var(X_INTRODUCED_604_); +constraint array_bool_and([X_INTRODUCED_166_,X_INTRODUCED_180_,X_INTRODUCED_604_],X_INTRODUCED_605_):: defines_var(X_INTRODUCED_605_); +constraint int_eq_imp(X_INTRODUCED_279_,6,X_INTRODUCED_607_):: defines_var(X_INTRODUCED_607_); +constraint array_bool_and([X_INTRODUCED_152_,X_INTRODUCED_169_,X_INTRODUCED_607_],X_INTRODUCED_608_):: defines_var(X_INTRODUCED_608_); +constraint int_eq_imp(X_INTRODUCED_279_,12,X_INTRODUCED_609_):: defines_var(X_INTRODUCED_609_); +constraint array_bool_and([X_INTRODUCED_169_,X_INTRODUCED_172_,X_INTRODUCED_609_],X_INTRODUCED_610_):: defines_var(X_INTRODUCED_610_); +constraint int_eq_imp(X_INTRODUCED_279_,16,X_INTRODUCED_611_):: defines_var(X_INTRODUCED_611_); +constraint array_bool_and([X_INTRODUCED_169_,X_INTRODUCED_183_,X_INTRODUCED_611_],X_INTRODUCED_612_):: defines_var(X_INTRODUCED_612_); +constraint int_eq_imp(X_INTRODUCED_280_,7,X_INTRODUCED_614_):: defines_var(X_INTRODUCED_614_); +constraint array_bool_and([X_INTRODUCED_156_,X_INTRODUCED_172_,X_INTRODUCED_614_],X_INTRODUCED_615_):: defines_var(X_INTRODUCED_615_); +constraint int_eq_imp(X_INTRODUCED_280_,11,X_INTRODUCED_616_):: defines_var(X_INTRODUCED_616_); +constraint array_bool_and([X_INTRODUCED_169_,X_INTRODUCED_172_,X_INTRODUCED_616_],X_INTRODUCED_617_):: defines_var(X_INTRODUCED_617_); +constraint int_eq_imp(X_INTRODUCED_280_,13,X_INTRODUCED_618_):: defines_var(X_INTRODUCED_618_); +constraint array_bool_and([X_INTRODUCED_172_,X_INTRODUCED_175_,X_INTRODUCED_618_],X_INTRODUCED_619_):: defines_var(X_INTRODUCED_619_); +constraint int_eq_imp(X_INTRODUCED_280_,17,X_INTRODUCED_620_):: defines_var(X_INTRODUCED_620_); +constraint array_bool_and([X_INTRODUCED_172_,X_INTRODUCED_186_,X_INTRODUCED_620_],X_INTRODUCED_621_):: defines_var(X_INTRODUCED_621_); +constraint int_eq_imp(X_INTRODUCED_281_,8,X_INTRODUCED_623_):: defines_var(X_INTRODUCED_623_); +constraint array_bool_and([X_INTRODUCED_160_,X_INTRODUCED_175_,X_INTRODUCED_623_],X_INTRODUCED_624_):: defines_var(X_INTRODUCED_624_); +constraint int_eq_imp(X_INTRODUCED_281_,12,X_INTRODUCED_625_):: defines_var(X_INTRODUCED_625_); +constraint array_bool_and([X_INTRODUCED_172_,X_INTRODUCED_175_,X_INTRODUCED_625_],X_INTRODUCED_626_):: defines_var(X_INTRODUCED_626_); +constraint int_eq_imp(X_INTRODUCED_281_,14,X_INTRODUCED_627_):: defines_var(X_INTRODUCED_627_); +constraint array_bool_and([X_INTRODUCED_175_,X_INTRODUCED_178_,X_INTRODUCED_627_],X_INTRODUCED_628_):: defines_var(X_INTRODUCED_628_); +constraint int_eq_imp(X_INTRODUCED_281_,18,X_INTRODUCED_629_):: defines_var(X_INTRODUCED_629_); +constraint array_bool_and([X_INTRODUCED_175_,X_INTRODUCED_189_,X_INTRODUCED_629_],X_INTRODUCED_630_):: defines_var(X_INTRODUCED_630_); +constraint int_eq_imp(X_INTRODUCED_282_,9,X_INTRODUCED_632_):: defines_var(X_INTRODUCED_632_); +constraint array_bool_and([X_INTRODUCED_164_,X_INTRODUCED_178_,X_INTRODUCED_632_],X_INTRODUCED_633_):: defines_var(X_INTRODUCED_633_); +constraint int_eq_imp(X_INTRODUCED_282_,13,X_INTRODUCED_634_):: defines_var(X_INTRODUCED_634_); +constraint array_bool_and([X_INTRODUCED_175_,X_INTRODUCED_178_,X_INTRODUCED_634_],X_INTRODUCED_635_):: defines_var(X_INTRODUCED_635_); +constraint int_eq_imp(X_INTRODUCED_282_,15,X_INTRODUCED_636_):: defines_var(X_INTRODUCED_636_); +constraint array_bool_and([X_INTRODUCED_178_,X_INTRODUCED_180_,X_INTRODUCED_636_],X_INTRODUCED_637_):: defines_var(X_INTRODUCED_637_); +constraint int_eq_imp(X_INTRODUCED_282_,19,X_INTRODUCED_638_):: defines_var(X_INTRODUCED_638_); +constraint array_bool_and([X_INTRODUCED_178_,X_INTRODUCED_192_,X_INTRODUCED_638_],X_INTRODUCED_639_):: defines_var(X_INTRODUCED_639_); +constraint int_eq_imp(X_INTRODUCED_283_,10,X_INTRODUCED_641_):: defines_var(X_INTRODUCED_641_); +constraint array_bool_and([X_INTRODUCED_166_,X_INTRODUCED_180_,X_INTRODUCED_641_],X_INTRODUCED_642_):: defines_var(X_INTRODUCED_642_); +constraint int_eq_imp(X_INTRODUCED_283_,14,X_INTRODUCED_643_):: defines_var(X_INTRODUCED_643_); +constraint array_bool_and([X_INTRODUCED_178_,X_INTRODUCED_180_,X_INTRODUCED_643_],X_INTRODUCED_644_):: defines_var(X_INTRODUCED_644_); +constraint int_eq_imp(X_INTRODUCED_283_,20,X_INTRODUCED_645_):: defines_var(X_INTRODUCED_645_); +constraint array_bool_and([X_INTRODUCED_180_,X_INTRODUCED_194_,X_INTRODUCED_645_],X_INTRODUCED_646_):: defines_var(X_INTRODUCED_646_); +constraint int_eq_imp(X_INTRODUCED_284_,11,X_INTRODUCED_648_):: defines_var(X_INTRODUCED_648_); +constraint array_bool_and([X_INTRODUCED_169_,X_INTRODUCED_183_,X_INTRODUCED_648_],X_INTRODUCED_649_):: defines_var(X_INTRODUCED_649_); +constraint int_eq_imp(X_INTRODUCED_284_,17,X_INTRODUCED_650_):: defines_var(X_INTRODUCED_650_); +constraint array_bool_and([X_INTRODUCED_183_,X_INTRODUCED_186_,X_INTRODUCED_650_],X_INTRODUCED_651_):: defines_var(X_INTRODUCED_651_); +constraint int_eq_imp(X_INTRODUCED_284_,21,X_INTRODUCED_652_):: defines_var(X_INTRODUCED_652_); +constraint array_bool_and([X_INTRODUCED_183_,X_INTRODUCED_197_,X_INTRODUCED_652_],X_INTRODUCED_653_):: defines_var(X_INTRODUCED_653_); +constraint int_eq_imp(X_INTRODUCED_286_,13,X_INTRODUCED_664_):: defines_var(X_INTRODUCED_664_); +constraint array_bool_and([X_INTRODUCED_175_,X_INTRODUCED_189_,X_INTRODUCED_664_],X_INTRODUCED_665_):: defines_var(X_INTRODUCED_665_); +constraint int_eq_imp(X_INTRODUCED_286_,17,X_INTRODUCED_666_):: defines_var(X_INTRODUCED_666_); +constraint array_bool_and([X_INTRODUCED_186_,X_INTRODUCED_189_,X_INTRODUCED_666_],X_INTRODUCED_667_):: defines_var(X_INTRODUCED_667_); +constraint int_eq_imp(X_INTRODUCED_286_,19,X_INTRODUCED_668_):: defines_var(X_INTRODUCED_668_); +constraint array_bool_and([X_INTRODUCED_189_,X_INTRODUCED_192_,X_INTRODUCED_668_],X_INTRODUCED_669_):: defines_var(X_INTRODUCED_669_); +constraint int_eq_imp(X_INTRODUCED_286_,23,X_INTRODUCED_670_):: defines_var(X_INTRODUCED_670_); +constraint array_bool_and([X_INTRODUCED_189_,X_INTRODUCED_203_,X_INTRODUCED_670_],X_INTRODUCED_671_):: defines_var(X_INTRODUCED_671_); +constraint int_eq_imp(X_INTRODUCED_287_,14,X_INTRODUCED_673_):: defines_var(X_INTRODUCED_673_); +constraint array_bool_and([X_INTRODUCED_178_,X_INTRODUCED_192_,X_INTRODUCED_673_],X_INTRODUCED_674_):: defines_var(X_INTRODUCED_674_); +constraint int_eq_imp(X_INTRODUCED_287_,18,X_INTRODUCED_675_):: defines_var(X_INTRODUCED_675_); +constraint array_bool_and([X_INTRODUCED_189_,X_INTRODUCED_192_,X_INTRODUCED_675_],X_INTRODUCED_676_):: defines_var(X_INTRODUCED_676_); +constraint int_eq_imp(X_INTRODUCED_287_,20,X_INTRODUCED_677_):: defines_var(X_INTRODUCED_677_); +constraint array_bool_and([X_INTRODUCED_192_,X_INTRODUCED_194_,X_INTRODUCED_677_],X_INTRODUCED_678_):: defines_var(X_INTRODUCED_678_); +constraint int_eq_imp(X_INTRODUCED_287_,24,X_INTRODUCED_679_):: defines_var(X_INTRODUCED_679_); +constraint array_bool_and([X_INTRODUCED_192_,X_INTRODUCED_206_,X_INTRODUCED_679_],X_INTRODUCED_680_):: defines_var(X_INTRODUCED_680_); +constraint int_eq_imp(X_INTRODUCED_288_,15,X_INTRODUCED_682_):: defines_var(X_INTRODUCED_682_); +constraint array_bool_and([X_INTRODUCED_180_,X_INTRODUCED_194_,X_INTRODUCED_682_],X_INTRODUCED_683_):: defines_var(X_INTRODUCED_683_); +constraint int_eq_imp(X_INTRODUCED_288_,19,X_INTRODUCED_684_):: defines_var(X_INTRODUCED_684_); +constraint array_bool_and([X_INTRODUCED_192_,X_INTRODUCED_194_,X_INTRODUCED_684_],X_INTRODUCED_685_):: defines_var(X_INTRODUCED_685_); +constraint int_eq_imp(X_INTRODUCED_288_,25,X_INTRODUCED_686_):: defines_var(X_INTRODUCED_686_); +constraint array_bool_and([X_INTRODUCED_194_,X_INTRODUCED_208_,X_INTRODUCED_686_],X_INTRODUCED_687_):: defines_var(X_INTRODUCED_687_); +constraint int_eq_imp(X_INTRODUCED_289_,16,X_INTRODUCED_689_):: defines_var(X_INTRODUCED_689_); +constraint array_bool_and([X_INTRODUCED_183_,X_INTRODUCED_197_,X_INTRODUCED_689_],X_INTRODUCED_690_):: defines_var(X_INTRODUCED_690_); +constraint int_eq_imp(X_INTRODUCED_289_,22,X_INTRODUCED_691_):: defines_var(X_INTRODUCED_691_); +constraint array_bool_and([X_INTRODUCED_197_,X_INTRODUCED_200_,X_INTRODUCED_691_],X_INTRODUCED_692_):: defines_var(X_INTRODUCED_692_); +constraint int_eq_imp(X_INTRODUCED_290_,17,X_INTRODUCED_694_):: defines_var(X_INTRODUCED_694_); +constraint array_bool_and([X_INTRODUCED_186_,X_INTRODUCED_200_,X_INTRODUCED_694_],X_INTRODUCED_695_):: defines_var(X_INTRODUCED_695_); +constraint int_eq_imp(X_INTRODUCED_290_,21,X_INTRODUCED_696_):: defines_var(X_INTRODUCED_696_); +constraint array_bool_and([X_INTRODUCED_197_,X_INTRODUCED_200_,X_INTRODUCED_696_],X_INTRODUCED_697_):: defines_var(X_INTRODUCED_697_); +constraint int_eq_imp(X_INTRODUCED_290_,23,X_INTRODUCED_698_):: defines_var(X_INTRODUCED_698_); +constraint array_bool_and([X_INTRODUCED_200_,X_INTRODUCED_203_,X_INTRODUCED_698_],X_INTRODUCED_699_):: defines_var(X_INTRODUCED_699_); +constraint int_eq_imp(X_INTRODUCED_291_,18,X_INTRODUCED_701_):: defines_var(X_INTRODUCED_701_); +constraint array_bool_and([X_INTRODUCED_189_,X_INTRODUCED_203_,X_INTRODUCED_701_],X_INTRODUCED_702_):: defines_var(X_INTRODUCED_702_); +constraint int_eq_imp(X_INTRODUCED_291_,22,X_INTRODUCED_703_):: defines_var(X_INTRODUCED_703_); +constraint array_bool_and([X_INTRODUCED_200_,X_INTRODUCED_203_,X_INTRODUCED_703_],X_INTRODUCED_704_):: defines_var(X_INTRODUCED_704_); +constraint int_eq_imp(X_INTRODUCED_291_,24,X_INTRODUCED_705_):: defines_var(X_INTRODUCED_705_); +constraint array_bool_and([X_INTRODUCED_203_,X_INTRODUCED_206_,X_INTRODUCED_705_],X_INTRODUCED_706_):: defines_var(X_INTRODUCED_706_); +constraint int_eq_imp(X_INTRODUCED_292_,19,X_INTRODUCED_708_):: defines_var(X_INTRODUCED_708_); +constraint array_bool_and([X_INTRODUCED_192_,X_INTRODUCED_206_,X_INTRODUCED_708_],X_INTRODUCED_709_):: defines_var(X_INTRODUCED_709_); +constraint int_eq_imp(X_INTRODUCED_292_,23,X_INTRODUCED_710_):: defines_var(X_INTRODUCED_710_); +constraint array_bool_and([X_INTRODUCED_203_,X_INTRODUCED_206_,X_INTRODUCED_710_],X_INTRODUCED_711_):: defines_var(X_INTRODUCED_711_); +constraint int_eq_imp(X_INTRODUCED_292_,25,X_INTRODUCED_712_):: defines_var(X_INTRODUCED_712_); +constraint array_bool_and([X_INTRODUCED_206_,X_INTRODUCED_208_,X_INTRODUCED_712_],X_INTRODUCED_713_):: defines_var(X_INTRODUCED_713_); +constraint int_eq_imp(X_INTRODUCED_293_,20,X_INTRODUCED_715_):: defines_var(X_INTRODUCED_715_); +constraint array_bool_and([X_INTRODUCED_194_,X_INTRODUCED_208_,X_INTRODUCED_715_],X_INTRODUCED_716_):: defines_var(X_INTRODUCED_716_); +constraint int_eq_imp(X_INTRODUCED_293_,24,X_INTRODUCED_717_):: defines_var(X_INTRODUCED_717_); +constraint array_bool_and([X_INTRODUCED_206_,X_INTRODUCED_208_,X_INTRODUCED_717_],X_INTRODUCED_718_):: defines_var(X_INTRODUCED_718_); +solve :: bool_search(X_INTRODUCED_720_,input_order,indomain_max,complete) maximize obj; diff --git a/parsers/src/test/resources/flatzinc/basics.csv b/parsers/src/test/resources/flatzinc/basics.csv index 70627c9a01..4871cb3b70 100644 --- a/parsers/src/test/resources/flatzinc/basics.csv +++ b/parsers/src/test/resources/flatzinc/basics.csv @@ -1,8 +1,8 @@ #year,name,solutions,best,nodes,fails basics,all_different+test_globals.fzn,20,1,219,180 basics,basic+test_basic.fzn,10,1,19,0 -basics,basic2+test_basic2+large.fzn,2497,2500,4994,1 -basics,basic2+test_basic2+small.fzn,16,19,33,2 +basics,basic2+test_basic2+large.fzn,2497,2500,4999,6 +basics,basic2+test_basic2+small.fzn,16,19,38,7 basics,bool_indomain_max+input_order.fzn,1,_,2,0 basics,bool_indomain_min+input_order.fzn,1,_,2,0 basics,circuit+test_globals.fzn,1,_,5,0 @@ -40,5 +40,5 @@ basics,seq_search_3+FD_seq_search.fzn,1,_,5,0 basics,seq_search_3.fzn,1,_,5,0 basics,seq_search_4+FD_seq_search.fzn,1,_,6,0 basics,seq_search_4.fzn,1,_,6,0 -basics,test_set_lt_1.fzn,1,_,1370724,1370664 -basics,test_set_lt_2.fzn,1,_,2585,2568 \ No newline at end of file +basics,test_set_lt_1.fzn,1,_,5342740,5342676 +basics,test_set_lt_2.fzn,1,_,9906,9889 \ No newline at end of file diff --git a/parsers/src/test/resources/flatzinc/hard_instances.csv b/parsers/src/test/resources/flatzinc/hard_instances.csv index f481191880..25aec2da20 100644 --- a/parsers/src/test/resources/flatzinc/hard_instances.csv +++ b/parsers/src/test/resources/flatzinc/hard_instances.csv @@ -3,6 +3,6 @@ 2020,stable-goods-solution+s-d6.fzn,13,6264,144303,144278 2018,rotating-workforce+ex1479.fzn,1,_,125855,125753 2016,tpp+6_3_20_1.fzn,65,131,2813024,2812895 -2012,still-life-wastage+still-life+09.fzn,6,43,109396,109385 -2012,still-life-wastage+still-life+10.fzn,8,54,164569,164554 +2012,still-life-wastage+still-life+09.fzn,5,43,109471,109462 +2012,still-life-wastage+still-life+10.fzn,7,54,164574,164561 2012,tpp+tpp+tpp_5_5_20_1.fzn,54,115,3215964,3215857 \ No newline at end of file diff --git a/parsers/src/test/resources/flatzinc/instances.csv b/parsers/src/test/resources/flatzinc/instances.csv index 148490b5f2..13f7a5c0c4 100644 --- a/parsers/src/test/resources/flatzinc/instances.csv +++ b/parsers/src/test/resources/flatzinc/instances.csv @@ -1,29 +1,30 @@ #year,name,solutions,best,nodes,fails -2021,atsp+instance5_0p15.fzn,1,685043,41,40 +2025,hitori_h5-1.fzn,1,_,3141,3140 +2021,atsp+instance5_0p15.fzn,1,685043,10973,10972 2021,yumi-dynamic+p_10.fzn,0,_,0,1 2021,feistel.fzn,1,_,174,160 2021,mapping_mesh2x2_2.fzn,2,970,96,93 2021,steiner-systems.fzn,1,_,31,9 2020,bnn+inventory_4_8s.fzn,1,17,16,15 2020,is+v1HjuSBQMb.fzn,18,454656,13893,13858 -2020,is+v1HjuSBQMb_new.fzn,18,454656,13893,13858 +2020,is+v1HjuSBQMb_new.fzn,18,454656,18369,18334 2020,p1f-pjs+10.fzn,27,300,12498,12445 -2020,p1f-pjs+10_new.fzn,27,300,11713,11660 +2020,p1f-pjs+10_new.fzn,27,300,11719,11666 2020,pentominoes+02.fzn,1,_,79,64 2020,radiation-i8-9.fzn,1,917,1097,1096 #2020,skill_allocation+mzn_1m_1.fzn,1,2,6196,6195 2020,soccer+xIGData_22_12_22_5.fzn,1,_,4031,3725 #2020,stable-goods-solution+s-d6.fzn,13,6264,144303,144278 #2019,amaze3+2012-03-19.fzn,1,_,1920663,1920646 -2019,ccmcp+3_20_0_25_5.fzn,22,1247,113086,113043 -2019,fm3_3.fzn,322,165922,6688,6045 +2019,ccmcp+3_20_0_25_5.fzn,22,1247,111490,111447 +2019,fm3_3.fzn,322,165922,12593,11950 2019,group+u6g1pref1.fzn,14,120,284,257 2019,median_string_dp+p2_10_8-0.fzn,4,34,6207,6200 2019,mknapsack_global+mknap2-1.fzn,1,7772,5808,5807 2019,vrp-s4-v2-c3_svrp-v2-c3_det.fzn,8,117,926,911 -2019,zephyrus+12_6_6_3.fzn,1,780,62511,62510 +2019,zephyrus+12_6_6_3.fzn,1,780,62559,62558 2018,oocsp_racks+050_r1.fzn,1,_,9,4 -2018,oocsp_racks+050_r1n.fzn,1,_,9,4 +2018,oocsp_racks+050_r1n.fzn,1,_,405,4 #2018,rotating-workforce+ex1479.fzn,1,_,128979,128877 2018,test-scheduling+t30m10r3-15.fzn,1,2785,7684,7683 2017,group+u12g1pref0.fzn,13,207,819,794 @@ -36,7 +37,7 @@ 2016,nfc_18_3_5.fzn,12,1338,362,339 2016,nfc_18_3_10.fzn,1,1452,83,82 2016,nfc_24_4_10.fzn,16,1912,404,373 -2015,gfd-schedule+n120f5d50m50k20.fzn,1,1,30414,30413 +2015,gfd-schedule+n120f5d50m50k20.fzn,1,1,37803,37802 2015,mapping_full2x2.fzn,6,793,104,93 2015,mapping_mesh2x2.fzn,35,515,408,339 2015,mapping_mesh3x3_2.fzn,8,900,1695,1680 @@ -45,18 +46,18 @@ 2012,amaze+amaze+2012-06-22.fzn,1,928,321050,321049 #2012,amaze+amaze+2012-07-04.fzn,1,481,244,243 2012,fast-food+fastfood+ff3.fzn,195,1330,71699,71310 -2012,filters+filter+ar_1_2.fzn,4,18,28624,28617 +2012,filters+filter+ar_1_2.fzn,4,18,26682,26675 2012,filters+filter+dct_1_1.fzn,1,34,49,48 2012,filters+filter+ewf_1_1.fzn,1,28,37,36 2012,filters+filter+fir16_1_1.fzn,1,35,34,33 2012,filters+filter+fir_1_1.fzn,1,18,24,23 -2012,mspsp+mspsp+easy_01.fzn,5,26,1251640,1251631 -2012,mspsp+mspsp+medium_03.fzn,5,26,1251640,1251631 +2012,mspsp+mspsp+easy_01.fzn,5,26,1251641,1251632 +2012,mspsp+mspsp+medium_03.fzn,5,26,1251641,1251632 2012,nonogram+non+non_fast_8.fzn,1,_,1932,1928 2012,pattern-set-mining-k2+pattern_set_mining_k2+audiology.fzn,38,54,212187,212112 2012,radiation+radiation+m06_15_15.fzn,1,711,307742,307741 -2012,ship-schedule+ship-schedule.cp+5Ships.fzn,359,483650,1467,750 -2012,ship-schedule+ship-schedule.cp+6ShipsMixed.fzn,237,301650,16082,15609 +2012,ship-schedule+ship-schedule.cp+5Ships.fzn,359,483650,3139,2422 +2012,ship-schedule+ship-schedule.cp+6ShipsMixed.fzn,237,301650,16101,15628 2012,solbat+sb+sb_12_12_5_1.fzn,1,_,1315,1284 2012,solbat+sb+sb_14_14_6_0.fzn,1,_,383,352 #2012,still-life-wastage+still-life+09.fzn,6,43,109396,109385 diff --git a/parsers/src/test/resources/mps/instances.csv b/parsers/src/test/resources/mps/instances.csv index 65c6ba573e..485b60c7f8 100644 --- a/parsers/src/test/resources/mps/instances.csv +++ b/parsers/src/test/resources/mps/instances.csv @@ -9,7 +9,7 @@ enlight9.mps.gz,0,1000,511,512,true pigeon-11.mps.gz,1,0.0,1618,500,true bg512142.mps.gz,0,999.0001,0,1,true glass4.mps.gz,0,999.0001,0,1,true -mik-250-1-100-1.mps.gz,5,-998.9999999999981,12272,10500,true +mik-250-1-100-1.mps.gz,4,-998.9999999999986,826,688,true m100n500k4r1.mps.gz,12,-17,5076212,5075748,false noswot.mps.gz,4,-41,1195217,1187590,false ns1766074.mps.gz,0,1000,5,6,true @@ -21,4 +21,4 @@ p2m2p1m1p0n100.mps.gz,0,1000,0,1,true timtab1.mps.gz,0,999.0001,0,1,true pigeon-10.mps.gz,1,0.0,1471,500,true pigeon-12.mps.gz,1,0.0,1765,500,true -markshare_5_0.mps.gz,28,3.0000000000000036,3300384,3274864,false \ No newline at end of file +#markshare_5_0.mps.gz,35,3.0000000000000036,3300384,3274864,false \ No newline at end of file diff --git a/parsers/src/test/resources/xcsp/2024.csv b/parsers/src/test/resources/xcsp/2024.csv new file mode 100644 index 0000000000..49149bdbf7 --- /dev/null +++ b/parsers/src/test/resources/xcsp/2024.csv @@ -0,0 +1,13 @@ +#dir;name;solutions;best;nodes;fails +2024;AztecDiamondSym-12_c24.xml.lzma;231;222316;18934;4061 +2024;BinPacking-n1c1w1a_c24.xml.lzma;1;25;100;50 +2024;Cargo-01-0s-1913_m17.xml.lzma;26;0;132928;88423 +2024;Charlotte-18-2_c24.xml.lzma;67;14627;77346;34456 +2024;Drinking-010000_c24.xml.lzma;1;1000;6859;0 +2024;FoolSolitaire-dec2-0-2.xml.lzma;2;21;182;108 +2024;SameQueensKnights-b-15_c24.xml.lzma;25;24;15266;6641 +# Pour stillLife, COS semble aider à trouver 76 +2024;StillLife-12-12_c24.xml.lzma;34;74;308794;151680 +2024;TestScheduling-t020m10r03-1_c24.xml.lzma;226;1876;2574;1300 +2024;TravelingTournament-galaxy06_c24.xml.lzma;16;1398;37040;25044 +2024;VRP_LC-10-05-20-s3_c24.xml.lzma;22;426;60298;27755 \ No newline at end of file diff --git a/parsers/src/test/resources/xcsp/2024/AztecDiamondSym-12_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/AztecDiamondSym-12_c24.xml.lzma new file mode 100644 index 0000000000..8308364229 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/AztecDiamondSym-12_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/BinPacking-n1c1w1a_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/BinPacking-n1c1w1a_c24.xml.lzma new file mode 100644 index 0000000000..9a0d0ed168 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/BinPacking-n1c1w1a_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/Cargo-01-0s-1913_m17.xml.lzma b/parsers/src/test/resources/xcsp/2024/Cargo-01-0s-1913_m17.xml.lzma new file mode 100644 index 0000000000..a967a7da02 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/Cargo-01-0s-1913_m17.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/Charlotte-18-2_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/Charlotte-18-2_c24.xml.lzma new file mode 100644 index 0000000000..2246e94819 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/Charlotte-18-2_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/Drinking-010000_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/Drinking-010000_c24.xml.lzma new file mode 100644 index 0000000000..21a95fff86 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/Drinking-010000_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/FoolSolitaire-dec2-0-2.xml.lzma b/parsers/src/test/resources/xcsp/2024/FoolSolitaire-dec2-0-2.xml.lzma new file mode 100644 index 0000000000..23de51868d Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/FoolSolitaire-dec2-0-2.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/SameQueensKnights-b-15_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/SameQueensKnights-b-15_c24.xml.lzma new file mode 100644 index 0000000000..2ffb67ebb0 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/SameQueensKnights-b-15_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/StillLife-12-12_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/StillLife-12-12_c24.xml.lzma new file mode 100644 index 0000000000..cbfd419d54 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/StillLife-12-12_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/TestScheduling-t020m10r03-1_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/TestScheduling-t020m10r03-1_c24.xml.lzma new file mode 100644 index 0000000000..994a70b820 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/TestScheduling-t020m10r03-1_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/TravelingTournament-galaxy06_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/TravelingTournament-galaxy06_c24.xml.lzma new file mode 100644 index 0000000000..8e7bb47d90 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/TravelingTournament-galaxy06_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/2024/VRP_LC-10-05-20-s3_c24.xml.lzma b/parsers/src/test/resources/xcsp/2024/VRP_LC-10-05-20-s3_c24.xml.lzma new file mode 100644 index 0000000000..d85f3a2205 Binary files /dev/null and b/parsers/src/test/resources/xcsp/2024/VRP_LC-10-05-20-s3_c24.xml.lzma differ diff --git a/parsers/src/test/resources/xcsp/instances.csv b/parsers/src/test/resources/xcsp/instances.csv index beabc4a240..6a988d283d 100644 --- a/parsers/src/test/resources/xcsp/instances.csv +++ b/parsers/src/test/resources/xcsp/instances.csv @@ -1,104 +1,104 @@ #dir;name;solutions;best;nodes;fails basics;Allergy.xml.lzma;1;_;1;0 -basics;AllInterval-005.xml.lzma;1;_;13;9 -basics;Auction-cnt-example_c18.xml.lzma;2;54;7;4 -basics;Auction-sum-example_c18.xml.lzma;2;54;6;3 -basics;Bacp-m1-06_c18.xml.lzma;3;10;38111;37700 -basics;Bacp-m2-06_c18.xml.lzma;3;10;152931;151070 -basics;Bibd-sc-06-050-25-03-10.xml.lzma;1;_;714;666 -basics;Bibd-sum-06-050-25-03-10.xml.lzma;1;_;498;462 -basics;Blackhole-04-3-00.xml.lzma;1;_;11;0 -basics;BusScheduling-cnt-t1.xml.lzma;1;7;334;333 -basics;CarSequencing-dingbas.xml.lzma;1;_;23;16 -basics;ChessboardColoration-07-07.xml.lzma;2;2;1958;1909 -basics;ColouredQueens-07.xml.lzma;1;_;90;78 -basics;CostasArray-12.xml.lzma;1;_;47;39 -basics;Crossword-lex-vg-5-6.xml.lzma;1;_;9742;9593 -basics;CrosswordDesign-03-4-rom_c18.xml.lzma;2;12;20;17 -basics;CryptoPuzzle-cross-roads-danger.xml.lzma;1;_;6;1 -basics;Cutstock-small.xml.lzma;1;4;10;9 +basics;AllInterval-005.xml.lzma;1;_;9;4 +basics;Auction-cnt-example_c18.xml.lzma;3;54;9;4 +basics;Auction-sum-example_c18.xml.lzma;3;54;9;4 +basics;Bacp-m1-06_c18.xml.lzma;6;10;85444;73520 +basics;Bacp-m2-06_c18.xml.lzma;6;10;286861;238402 +basics;Bibd-sc-06-050-25-03-10.xml.lzma;1;_;3525;2231 +basics;Bibd-sum-06-050-25-03-10.xml.lzma;1;_;997;587 +basics;Blackhole-04-3-00.xml.lzma;1;_;7;0 +basics;BusScheduling-cnt-t1.xml.lzma;1;7;185;174 +basics;CarSequencing-dingbas.xml.lzma;1;_;12;7 +basics;ChessboardColoration-07-07.xml.lzma;26;2;8434;7256 +basics;ColouredQueens-07.xml.lzma;1;_;23;10 +basics;CostasArray-12.xml.lzma;1;_;261;233 +basics;Crossword-lex-vg-5-6.xml.lzma;1;_;7316;6668 +basics;CrosswordDesign-03-4-rom_c18.xml.lzma;5;12;66;56 +basics;CryptoPuzzle-cross-roads-danger.xml.lzma;1;_;9;4 +basics;Cutstock-small.xml.lzma;2;4;21;18 basics;Domino-300-300.xml.lzma;1;_;1;0 -basics;driverlogw-09.xml.lzma;1;_;2738;2649 -basics;Fapp-m2s-ex2_c18.xml.lzma;5;13871;151;142 -basics;Filters-dct_2_3.xml.lzma;17;16;55552;53838 -basics;FlexibleJobshop-easy01.xml.lzma;94;253;768;565 -basics;FlexibleJobshop-easy02.xml.lzma;28;11;3174;2811 -basics;Furniture.xml.lzma;1;603;21;20 -basics;GracefulGraph-K02-P04.xml.lzma;1;_;73;64 -basics;GraphColoring-3-fullins-4.xml.lzma;1;6;22854;22403 +basics;driverlogw-09.xml.lzma;1;_;1758;1211 +basics;Fapp-m2s-ex2_c18.xml.lzma;16;13871;282;180 +basics;Filters-dct_2_3.xml.lzma;19;16;2051;1100 +basics;FlexibleJobshop-easy01.xml.lzma;31;253;262;171 +basics;FlexibleJobshop-easy02.xml.lzma;59;11;5770;2403 +basics;Furniture.xml.lzma;1;603;33;32 +basics;GracefulGraph-K02-P04.xml.lzma;1;_;85;73 +basics;GraphColoring-3-fullins-4.xml.lzma;11;6;101587;76903 basics;GraphColoring-qwhdec-o5-h10-1.xml.lzma;1;4;1;0 basics;Hanoi-05.xml.lzma;1;_;31;0 basics;Kakuro-easy-000-ext.xml.lzma;1;_;1;0 -basics;Kakuro-easy-000-sumdiff.xml.lzma;1;_;6;3 -basics;Knapsack-30-100-00.xml.lzma;15;709;147767;146413 -basics;KnightTour-06-ext03.xml.lzma;1;_;944;923 -basics;KnightTour-06-int.xml.lzma;1;_;10017;9879 -basics;Langford-3-10.xml.lzma;1;_;372;367 -basics;LangfordBin-08.xml.lzma;1;_;78;72 -basics;LowAutocorrelation-015.xml.lzma;3;15;6635;6578 -basics;MagicSequence-008-ca.xml.lzma;1;_;5;1 -basics;MagicSequence-008-co.xml.lzma;1;_;5;1 -basics;MagicSquare-4-table.xml.lzma;1;_;20;13 -basics;MagicSquare-6-mdd.xml.lzma;1;_;693;662 -basics;MagicSquare-6-sum.xml.lzma;1;_;960;928 -basics;Mapping-full2x2_mp3.xml.lzma;4;1100;1757;1674 -basics;Mario-easy-4.xml.lzma;4;545;1913;1883 -basics;MarketSplit-01.xml.lzma;1;_;1243457;1236746 -basics;MSPSP-easy_01.xml.lzma;7;26;248;235 -basics;MSPSP-hard_01.xml.lzma;11;35;2402;2310 +basics;Kakuro-easy-000-sumdiff.xml.lzma;1;_;9;7 +basics;Knapsack-30-100-00.xml.lzma;39;709;46790;38340 +basics;KnightTour-06-ext03.xml.lzma;1;_;147;110 +basics;KnightTour-06-int.xml.lzma;1;_;168;128 +basics;Langford-3-10.xml.lzma;1;_;864;820 +basics;LangfordBin-08.xml.lzma;1;_;529;465 +basics;LowAutocorrelation-015.xml.lzma;25;15;52114;38962 +basics;MagicSequence-008-ca.xml.lzma;1;_;6;4 +basics;MagicSequence-008-co.xml.lzma;1;_;6;4 +basics;MagicSquare-4-table.xml.lzma;1;_;9;3 +basics;MagicSquare-6-mdd.xml.lzma;1;_;209;157 +basics;MagicSquare-6-sum.xml.lzma;1;_;217;162 +basics;Mapping-full2x2_mp3.xml.lzma;1;1100;1345;1203 +basics;Mario-easy-4.xml.lzma;16;545;9345;8664 +#basics;MarketSplit-01.xml.lzma;0;_;1113575;919484 +basics;MSPSP-easy_01.xml.lzma;14;26;1816;800 +basics;MSPSP-hard_01.xml.lzma;4;35;627;512 basics;MultiKnapsack-1-0_X2.xml.lzma;1;_;2;0 basics;MultiKnapsack-1-01.xml.lzma;1;_;2;0 -basics;NFC-12_2_10.xml.lzma;1;848;12;11 -basics;NFC-12_2_5.xml.lzma;1;1074;11;10 -basics;Nonogram-001-regular.xml.lzma;1;_;18;7 -basics;Nonogram-001-table.xml.lzma;1;_;18;7 -basics;NurseRostering-00_c18.xml.lzma;9;1202;69734;69097 -basics;Opd-07-007-003.xml.lzma;3;1;220;215 -basics;Ortholatin-005.xml.lzma;1;_;10;0 -basics;Pb-gr-05.xml.lzma;5;11;703;685 -basics;Pb-robin08.xml.lzma;1;_;2395;1438 -basics;PeacableArmies-m1-05_c18.xml.lzma;3;4;3074;3020 -basics;PeacableArmies-m2-05_c18.xml.lzma;5;4;2058;2017 -basics;PizzaVoucher-10a_c18.xml.lzma;8;210;36531;36289 -basics;Primes-15-20-2-1.xml.lzma;1;_;7;0 -basics;PrizeCollecting-15-3-5-0.xml.lzma;4;20;1635;1590 -basics;qcp-15-120-00_X2.xml.lzma;1;_;484;458 -basics;QuadraticAssignment-qap_c18.xml.lzma;10;4776;945;924 -basics;QuadraticAssignment-qap.xml.lzma;10;4776;945;924 +basics;NFC-12_2_10.xml.lzma;31;848;173;108 +basics;NFC-12_2_5.xml.lzma;14;1074;76;49 +basics;Nonogram-001-regular.xml.lzma;1;_;76;51 +basics;Nonogram-001-table.xml.lzma;1;_;90;68 +basics;NurseRostering-00_c18.xml.lzma;15;1202;115351;94073 +basics;Opd-07-007-003.xml.lzma;3;1;92;76 +basics;Ortholatin-005.xml.lzma;1;_;79;66 +basics;Pb-gr-05.xml.lzma;3;11;232;212 +basics;Pb-robin08.xml.lzma;1;_;1652;578 +basics;PeacableArmies-m1-05_c18.xml.lzma;5;4;6482;5148 +basics;PeacableArmies-m2-05_c18.xml.lzma;5;4;2997;2551 +basics;PizzaVoucher-10a_c18.xml.lzma;16;210;11508;10338 +basics;Primes-15-20-2-1.xml.lzma;1;_;6;0 +basics;PrizeCollecting-15-3-5-0.xml.lzma;8;20;1946;1785 +basics;qcp-15-120-00_X2.xml.lzma;1;_;1126;786 +basics;QuadraticAssignment-qap_c18.xml.lzma;13;4776;2606;2452 +basics;QuadraticAssignment-qap.xml.lzma;13;4776;2606;2452 basics;QuasiGroup-3-04.xml.lzma;1;_;2;0 -basics;QuasiGroup-7-09.xml.lzma;1;_;419;410 -basics;QueenAttacking-06.xml.lzma;3;0;18563;18240 -basics;Queens-0008-m1.xml.lzma;1;_;6;0 -basics;RadarSurveillance-8-24-3-2-00.xml.lzma;1;_;75;4 -basics;Ramsey-12.xml.lzma;3;2;13885;13107 -basics;Rcpsp-j30-01-01_c18.xml.lzma;7;43;159;146 -basics;Rlfap-graph-04-opt_c18.xml.lzma;1;394;197;196 -basics;RoomMate-sr0050-int.xml.lzma;1;_;6;4 -basics;SocialGolfers-4-3-4-cp.xml.lzma;1;_;55;38 -basics;Sonet-s2ring02.xml.lzma;7;14;459188;454754 -basics;SportsScheduling-08.xml.lzma;1;_;3220;3154 -basics;SteelMillSlab-m1-simple_c18.xml.lzma;4;0;85;78 -basics;SteelMillSlab-m2-simple_c18.xml.lzma;4;0;124;117 -basics;SteelMillSlab-m2s-mini-simple_c18.xml.lzma;4;0;115;108 -basics;StillLife-03-06.xml.lzma;2;10;211;208 -basics;StillLife-wastage-03.xml.lzma;3;6;22;17 -basics;StripPacking-C1P1.xml.lzma;1;_;7948;7731 -basics;Subisomorphism-A-10.xml.lzma;1;_;18;16 +basics;QuasiGroup-7-09.xml.lzma;1;_;1070;907 +basics;QueenAttacking-06.xml.lzma;8;0;5386;4210 +basics;Queens-0008-m1.xml.lzma;1;_;31;27 +basics;RadarSurveillance-8-24-3-2-00.xml.lzma;1;_;92;22 +basics;Ramsey-12.xml.lzma;45;2;71913;41237 +basics;Rcpsp-j30-01-01_c18.xml.lzma;7;43;204;175 +basics;Rlfap-graph-04-opt_c18.xml.lzma;5;394;1492;801 +basics;RoomMate-sr0050-int.xml.lzma;1;_;2;0 +basics;SocialGolfers-4-3-4-cp.xml.lzma;1;_;225;151 +#basics;Sonet-s2ring02.xml.lzma;9;14;815744;621835 +basics;SportsScheduling-08.xml.lzma;1;_;270;224 +basics;SteelMillSlab-m1-simple_c18.xml.lzma;5;0;190;161 +basics;SteelMillSlab-m2-simple_c18.xml.lzma;1;0;70;50 +basics;SteelMillSlab-m2s-mini-simple_c18.xml.lzma;1;0;138;50 +basics;StillLife-03-06.xml.lzma;5;10;424;381 +basics;StillLife-wastage-03.xml.lzma;3;6;23;18 +basics;StripPacking-C1P1.xml.lzma;1;_;23702;17004 +basics;Subisomorphism-A-10.xml.lzma;1;_;30;28 basics;Sudoku-s01a-alldiff.xml.lzma;1;_;1;0 -basics;SumColoring-myciel4_c18.xml.lzma;5;22;27017;26883 -basics;Taillard-os-04-04-0.xml.lzma;19;193;15620;15434 -basics;Tal-01_c18.xml.lzma;1;6;4;3 -basics;TeamAssignment-data1_4_6.xml.lzma;12;2948;5508;5441 -basics;TemplateDesign-m1-1_c18.xml.lzma;1;2;10;9 -basics;TemplateDesign-m1s-1_c18.xml.lzma;1;2;12;11 -basics;TemplateDesign-m2-1_c18.xml.lzma;1;2;10;9 -basics;testExtension1.xml.lzma;1;_;4;0 -basics;testExtension2.xml.lzma;1;_;4;0 -basics;testExtension3.xml.lzma;0;_;15;16 -basics;testObjective1.xml.lzma;3;11;11;6 +basics;SumColoring-myciel4_c18.xml.lzma;4;22;45690;42079 +basics;Taillard-os-04-04-0.xml.lzma;52;193;13313;11765 +basics;Tal-01_c18.xml.lzma;2;6;11;8 +basics;TeamAssignment-data1_4_6.xml.lzma;20;2948;11657;10545 +basics;TemplateDesign-m1-1_c18.xml.lzma;2;2;133;129 +basics;TemplateDesign-m1s-1_c18.xml.lzma;1;2;10;9 +basics;TemplateDesign-m2-1_c18.xml.lzma;2;2;167;161 +basics;testExtension1.xml.lzma;1;_;6;2 +basics;testExtension2.xml.lzma;1;_;6;2 +basics;testExtension3.xml.lzma;0;_;17;18 +basics;testObjective1.xml.lzma;2;11;8;5 basics;testPrimitive.xml.lzma;1;_;3;1 #basics;TestSchedulingM18-t30m10r3-15.xml.lzma;93;4149;3450;2650 -basics;Tpp-3-3-20-1.xml.lzma;5;126;87;78 -basics;TravelingTournament-a3-galaxy04_c18.xml.lzma;4;416;4180;4139 -basics;Warehouse-opl.xml.lzma;1;383;568;557 -basics;Zebra.xml.lzma;1;_;9;3 \ No newline at end of file +basics;Tpp-3-3-20-1.xml.lzma;9;126;264;216 +basics;TravelingTournament-a3-galaxy04_c18.xml.lzma;9;416;3843;3502 +basics;Warehouse-opl.xml.lzma;19;383;128;86 +basics;Zebra.xml.lzma;1;_;9;2 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9fb0e3cfc0..de319588c0 100644 --- a/pom.xml +++ b/pom.xml @@ -109,18 +109,12 @@ - ossrhDeploy + centralDeploy - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + central + https://central.sonatype.com - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - default - true - false @@ -169,16 +163,6 @@ - - githubDeploy - - - github - GitHub Packages - https://maven.pkg.github.com/chocoteam/choco-solver - - - coverage @@ -380,14 +364,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - ossrh - https://oss.sonatype.org/ - true + central + false @@ -425,6 +408,32 @@ + + org.codehaus.mojo + license-maven-plugin + 2.6.0 + + + + aggregate-third-party + + aggregate-add-third-party + + generate-resources + + + ${project.build.outputDirectory}/META-INF + + THIRD-PARTY-LICENSES.txt + + The BSD licence|BSD License|BSD + The Apache Software License, Version 2.0|Apache License, Version 2.0 + + + + + + org.apache.maven.plugins maven-source-plugin diff --git a/scripts/Ibex.dms b/scripts/Ibex.dms new file mode 100644 index 0000000000..2218ba69d1 --- /dev/null +++ b/scripts/Ibex.dms @@ -0,0 +1,26 @@ +FROM ubuntu:latest + +# install Java 21 +RUN apt-get update && \ + apt-get install -y sudo openjdk-21-jdk-headless default-jdk python3 vim git maven curl g++ gcc flex bison cmake git pkg-config + +RUN curl https://codeload.github.com/ibex-team/ibex-lib/tar.gz/ibex-2.9.1 > ibex-2.9.1.tar.gz && \ + tar -xzf ibex-2.9.1.tar.gz && \ + cd ibex-lib-ibex-2.9.1 && \ +# RUN #git clone https://github.com/ibex-team/ibex-lib.git && \ +# cd ibex-lib && \ + mkdir build && \ + cd build && \ + cmake -DLP_LIB=soplex -DBUILD_JAVA_INTERFACE=ON -DBUILD_SHARED_LIBS=ON -DJAVA_PACKAGE=org.chocosolver.solver.constraints.real .. && \ + make && \ + sudo make install && \ + export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/ibex/3rd + +RUN git clone --recurse-submodules https://github.com/chocoteam/choco-solver.git && \ + cd choco-solver && \ + # git checkout develop && \ + mvn -q clean install -DskipTests +RUN cd choco-solver && \ + # exporting LD_LIBRARY_PATH twice (or at least here) is mandatory + export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/ibex/3rd && \ + mvn --file pom.xml test -DtestFailureIgnore=true -Dgroups=ibex diff --git a/scripts/install-ibex.sh b/scripts/install-ibex.sh index ee4d58b0e6..3ec5414c06 100644 --- a/scripts/install-ibex.sh +++ b/scripts/install-ibex.sh @@ -1,20 +1,21 @@ #!/usr/bin/env bash set -ex # download Ibex and untar it -ibexver=2.8.8 -ibexjavaver=1.2.0 +root=$(pwd) +ibexver=2.9.1 curl https://codeload.github.com/ibex-team/ibex-lib/tar.gz/ibex-${ibexver} > ibex-${ibexver}.tar.gz tar -xzf ibex-${ibexver}.tar.gz -curl https://codeload.github.com/ibex-team/ibex-java/tar.gz/${ibexjavaver} > ibex-java-${ibexjavaver}.tar.gz -tar -xzf ibex-java-${ibexjavaver}.tar.gz --directory ibex-lib-ibex-${ibexver}/plugins/ + # prepare installation cd ibex-lib-ibex-${ibexver} -./waf configure --enable-shared --with-jni --java-package-name=org.chocosolver.solver.constraints.real -./waf build -sudo ./waf install -export LD_LIBRARY_PATH=/usr/local/lib -cd - +mkdir build +cd build +cmake -DLP_LIB=soplex -DBUILD_JAVA_INTERFACE=ON -DBUILD_SHARED_LIBS=ON -DJAVA_PACKAGE=org.chocosolver.solver.constraints.real .. +make +sudo make install +export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/ibex/3rd + +cd ${root} sudo rm -r ibex-${ibexver}.tar.gz -sudo rm -r ibex-java-${ibexjavaver}.tar.gz ibexsolve -v \ No newline at end of file diff --git a/scripts/release.sh b/scripts/release.sh index 89a00a6527..12c495b42a 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -30,7 +30,7 @@ git tag -a v${VERSION} -m "create tag ${VERSION}" || quit "Unable to tag with ${ git push --tags || quit "Unable to push the tag ${VERSION}" # Proceed to the deployment -mvn -P ossrhDeploy javadoc:jar source:jar deploy -DskipTests -B -U ||quit "Unable to deploy to master" +mvn -P centralDeploy javadoc:jar source:jar deploy -DskipTests -B -U ||quit "Unable to deploy to master" ## Merge back to develop git checkout develop || quit "unable to check develop out" diff --git a/scripts/settings.xml b/scripts/settings.xml index 8a3db0f675..5d1760a1c8 100644 --- a/scripts/settings.xml +++ b/scripts/settings.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> - ossrh + central ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD} diff --git a/solver/pom.xml b/solver/pom.xml index 16c56b26c4..8db383dd2e 100644 --- a/solver/pom.xml +++ b/solver/pom.xml @@ -50,8 +50,8 @@ org.slf4j slf4j-nop - 2.0.5 - test + 1.7.36 + @@ -114,7 +114,7 @@ - ${argLine} -Djava.library.path=/usr/local/lib + ${argLine} -Djava.library.path=/usr/local/lib:/usr/local/lib/ibex/3rd listener diff --git a/solver/src/main/java/org/chocosolver/sat/Clause.java b/solver/src/main/java/org/chocosolver/sat/Clause.java index d0163e259a..ffe02338a2 100644 --- a/solver/src/main/java/org/chocosolver/sat/Clause.java +++ b/solver/src/main/java/org/chocosolver/sat/Clause.java @@ -11,9 +11,9 @@ import gnu.trove.list.TIntList; -import java.util.stream.IntStream; +import java.util.Arrays; -import static org.chocosolver.sat.MiniSat.*; +import static org.chocosolver.sat.MiniSat.clauseCounter; /** * Clause -- a simple class for representing a clause @@ -55,7 +55,7 @@ public Clause(int[] ps, boolean learnt) { } this.learnt = learnt; this.id = clauseCounter.get(); - clauseCounter.set(clauseCounter.get() + 1); + clauseCounter.set(id + 1); } /** @@ -67,21 +67,18 @@ public Clause(int[] ps, boolean learnt) { * @return the reduced clause */ private static int[] reduceOs(int[] ps) { - int nb0 = (int) IntStream.of(ps).filter(p -> p == 0).count(); - if (nb0 <= 1) { - return ps; - } else { - int[] reduced = new int[ps.length - nb0 + 1]; - int j = 0; - for (int i = 0; i < ps.length; i++) { - if (ps[i] != 0) { - reduced[j++] = ps[i]; - } else if (j == 0) { - reduced[j++] = 0; - } + // move 0s to the end of the array + // the first 0 will be kept, the others will be removed + int j = 1; + for (int i = 1; i < ps.length; i++) { + if (ps[i] != 0) { + ps[j++] = ps[i]; } - return reduced; } + if(j < ps.length) { + ps = Arrays.copyOf(ps, j); + } + return ps; } /** @@ -104,7 +101,7 @@ public Clause(TIntList ps, boolean learnt) { literals_ = ps.toArray(); this.learnt = learnt; this.id = clauseCounter.get(); - clauseCounter.set(clauseCounter.get() + 1); + clauseCounter.set(id + 1); } /** diff --git a/solver/src/main/java/org/chocosolver/sat/MiniSat.java b/solver/src/main/java/org/chocosolver/sat/MiniSat.java index a18485c689..eef87d55d4 100644 --- a/solver/src/main/java/org/chocosolver/sat/MiniSat.java +++ b/solver/src/main/java/org/chocosolver/sat/MiniSat.java @@ -12,6 +12,8 @@ import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.stack.TIntStack; +import gnu.trove.stack.array.TIntArrayStack; import org.chocosolver.solver.variables.impl.LitVar; import java.util.ArrayList; @@ -81,9 +83,10 @@ public class MiniSat implements SatFactory { // Number of variables int num_vars_; int rootlvl = 0; - private final int ccmin_mode = 0; // Controls conflict clause minimization (0=none, 1=basic, 2=deep) + private final int ccmin_mode = 1; // Controls conflict clause minimization (0=none, 1=basic, 2=deep) private double cla_inc = 1; private final double clause_decay = 0.999; + int learnt_first_removable = 0; // index of first removable learnt clause (related to #reduceDB only). double learntsize_adjust_confl = 100; int learntsize_adjust_cnt = 100; @@ -100,6 +103,7 @@ public class MiniSat implements SatFactory { double max_learnts; final BitSet seen = new BitSet(); private final TIntArrayList analyze_toclear = new TIntArrayList(); + private final TIntStack analyze_stack = new TIntArrayStack(); private final TIntArrayList temporary_add_vector_ = new TIntArrayList(); /** @@ -120,7 +124,7 @@ public MiniSat(boolean addTautology) { seen.set(v); // false literal v = newVariable(); - l = makeLiteral(v, true); + l = makeLiteral(v, false); assignment_.set(v, makeBoolean(sgn(l))); vardata.set(v, new VarData(R_Undef, trailMarker(), trail_.size())); trail_.add(l); @@ -255,12 +259,30 @@ public boolean addClause(int p, int q, int r) { return addClause(temporary_add_vector_); } - public void addLearnt(TIntList learnt_clause) { + /** + * Add a learnt clause to the solver. + * If the param unforgettable is true, the clause will not be removed during {@link #doReduceDB()}. + * This is useful for clauses that prohibit certain parts of the search space (e.g. solution clauses). + * + * @param learnt_clause the clause to add + * @param unforgettable if true, the clause will not be removed during {@link #doReduceDB()} + */ + public void addLearnt(TIntList learnt_clause, boolean unforgettable) { if (learnt_clause.size() == 1) { uncheckedEnqueue(learnt_clause.get(0)); } else { Clause cr = new Clause(learnt_clause, true); learnts.add(cr); + if (unforgettable) { // in the case of a solution, for instance. + if (learnts.size() > 1) { + // swap the clause with the first removable clause + learnts.set(learnts.size() - 1, learnts.get(learnt_first_removable)); + // replace it by the new clause + learnts.set(learnt_first_removable, cr); + } + // increment the index + learnt_first_removable++; + } attachClause(cr); claBumpActivity(cr); uncheckedEnqueue(learnt_clause.get(0), Reason.r(cr)); @@ -372,9 +394,33 @@ public void pushTrailMarker() { trail_markers_.add(trail_.size()); } + /** + * Indicates if all literals but one are false in the clause. + * By implication, the remaining literal is the asserting literal. + * By construction, the literal at index 0 is the asserting literal and should not be considered. + * + * @param sat the solver + * @param c the clause to consider + * @return true if all literals but one (at index 0) are false, false otherwise + * @implNote This method can only be called on clauses learnt during propagation. + * Indeed, the method assumes that the literal at index 0 is the asserting literal and all other literals are false. + * In such situation, the clause is a reason (i.e. it is the reason of a propagation). + * If the clause has been propagated once (ie, is not a reason anymore), the clause may be in any order + * or may not be unit anymore. + */ + private static boolean isAssertingClause(MiniSat sat, Clause c) { + for (int i = 1; i < c.size(); i++) { + if (sat.valueLit(c._g(i)) != MiniSat.lFalse) { + return false; + } + } + return true; + } + // Enqueue a literal. Assumes value of literal is undefined. public void uncheckedEnqueue(int l, Reason from) { assert valueLit(l) == lUndef : "l: " + printLit(l) + " from: " + from; + assert isAssertingClause(this, from.getConflict()) : "the reason " + showReason(from) + " is not valid because it is not unit"; int v = var(l); if (assignment_.getQuick(v) == lUndef) { onLiteralPushed(l); @@ -409,9 +455,11 @@ public void uncheckedEnqueue(int l) { public void cEnqueue(int l, Reason r) { assert valueLit(l) != lTrue; + assert r != null : "reason is null for " + printLit(l); + assert isAssertingClause(this, r.getConflict()) : "the reason " + showReason(r) + " is not valid because it is not unit"; int v = var(l); if (valueLit(l) == lFalse) { - if (r == null || r == R_Undef) { + if (r == R_Undef) { // assert(decisionLevel() == 0); confl = C_Fail; // todo: check } else { @@ -589,30 +637,26 @@ public int analyze(Clause confl, TIntArrayList out_learnt) { replaceUnreliableLits(out_learnt); // Simplify conflict clause: // - int i, j = 1; + int i, j; analyze_toclear.resetQuick(); analyze_toclear.addAll(out_learnt); - /*if (ccmin_mode == 2) { - uint32_t abstract_level = 0; + if (ccmin_mode == 2) { + int abstract_level = 0; for (i = 1; i < out_learnt.size(); i++) abstract_level |= abstractLevel(var(out_learnt.get(i))); // (maintain an abstraction of levels involved in conflict) for (i = j = 1; i < out_learnt.size(); i++) - if (reason(var(out_learnt.get(i))) == CR_Undef || !litRedundant(out_learnt.get(i), abstract_level)) + if (reason(var(out_learnt.get(i))) == C_Undef || !litRedundant(out_learnt.get(i), abstract_level)) out_learnt.set(j++, out_learnt.get(i)); - } else */ - if (ccmin_mode == 1) { - //todo fix ccmin_mode + } else if (ccmin_mode == 1) { for (i = j = 1; i < out_learnt.size(); i++) { - int x = var(out_learnt.get(i)); - - if (getConfl(x) == C_Undef) + Clause c = getConfl(out_learnt.get(i)); + if (c == C_Undef) out_learnt.set(j++, out_learnt.get(i)); else { - Clause c = getConfl(var(out_learnt.get(i))); for (int k = 1; k < c.size(); k++) - if (!seen.get(var(c._g(k))) && level(var(c._g(k))) > rootlvl) { + if (!seen.get(var(c._g(k))) && level(var(c._g(k))) > 0) { // or root_level ? out_learnt.set(j++, out_learnt.get(i)); break; } @@ -659,7 +703,7 @@ private void analyseConflict(Clause confl, TIntList out_learnt, int p, int pathC int x = var(q); if (!seen.get(x) && level(x) > rootlvl) { assert p == litUndef || pos(var(p)) > pos(x) : "chronological inconsistency :(" + printLit(p) + " @ " + pos(var(p)) + - ") is explained by a previous event (" + printLit(x) + " @ " + pos(x) + ") " + c; + ") is explained by an older event (" + printLit(x) + " @ " + pos(x) + ") " + c; varBumpActivity(x); seen.set(x); if (DEBUG > 1) System.out.printf("mark %d\n", x); @@ -685,6 +729,40 @@ private void analyseConflict(Clause confl, TIntList out_learnt, int p, int pathC out_learnt.set(0, neg(p)); } + /* Check if 'p' can be removed. 'abstract_levels' is used to abort early if the algorithm is + * visiting literals at levels that cannot be removed later. + */ + boolean litRedundant(int p, int abstract_levels) { + analyze_stack.clear(); + analyze_stack.push(p); + int top = analyze_toclear.size(); + while (analyze_stack.size() > 0) { + assert (reason(var(analyze_stack.peek())) != C_Undef); + Clause c = getConfl(analyze_stack.peek()); + analyze_stack.pop(); + + for (int i = 1; i < c.size(); i++) { + p = c._g(i); + if (!seen.get(var(p)) && level(var(p)) > 0) { + if (reason(var(p)) != C_Undef && (abstractLevel(var(p)) & abstract_levels) != 0) { + seen.set(var(p)); + analyze_stack.push(p); + analyze_toclear.add(p); + } else { + for (int j = top; j < analyze_toclear.size(); j++) + seen.clear(var(analyze_toclear.get(j))); + + //analyze_toclear.shrink(analyze_toclear.size() - top); + analyze_toclear.remove(top, analyze_toclear.size() - top); + return false; + } + } + } + } + + return true; + } + /** * Some lits cannot be used in clause (the ones related to instantiation in lazy lits vars). * They need to be replaced by their explanation. @@ -765,6 +843,10 @@ void removeSatisfied(ArrayList cs) { Clause c = cs.get(i); if (satisfied(c)) { removeClause(cs.get(i)); + if (i < learnt_first_removable) { // maintain the index + learnt_first_removable--; + assert learnt_first_removable >= 0; + } } else { cs.set(j++, cs.get(i)); @@ -796,10 +878,12 @@ public void doReduceDB() { int i, j; double extra_lim = cla_inc / learnts.size(); // Remove any clause below this activity - learnts.sort(Comparator.comparingDouble(c -> c.activity)); - // Don't delete binary or locked clauses. From the rest, delete clauses from the first half + learnts.subList(learnt_first_removable, learnts.size()) // only removable clauses + .sort(Comparator.comparingDouble(c -> c.activity)); + // Don't delete binary or locked clauses or unforgettable clauses. + // From the rest, delete clauses from the first half // and clauses with activity smaller than 'extra_lim': - for (i = j = 0; i < learnts.size(); i++) { + for (i = j = learnt_first_removable; i < learnts.size(); i++) { Clause c = learnts.get(i); if (c.size() > 2 && !locked(c) && (i < learnts.size() / 2 || c.activity < extra_lim)) removeClause(learnts.get(i)); @@ -820,11 +904,15 @@ Reason reason(int x) { return vardata.get(x).cr; } - int level(int x) { + public int level(int x) { return vardata.get(x).level; } - int pos(int x) { + int abstractLevel(int x) { + return 1 << (level(x) & 31); + } + + public int pos(int x) { return vardata.get(x).pos; } @@ -846,8 +934,8 @@ void removeClause(Clause cr) { void claBumpActivity(Clause c) { if ((c.activity += cla_inc) > 1e20d) { - // Rescale: - for (int i = 0; i < learnts.size(); i++) { + // Rescale, only clauses that can be removed with reduceDB + for (int i = learnt_first_removable; i < learnts.size(); i++) { learnts.get(i).activity *= 1e-20d; } cla_inc *= 1e-20d; @@ -963,8 +1051,8 @@ private String showReason(Reason r) { st.append("clause ("); Clause cl = (Clause) r; for (int i = 1; i < cl.size(); i++) { - if (i > 1) st.append(" ∧ "); - st.append(printLit(neg(cl._g(i)))); + if (i > 1) st.append(" ∨ "); + st.append(printLit(cl._g(i))); } st.append(")"); //st.append(" -> ").append(printLit(r.cl._g(0))); @@ -973,11 +1061,11 @@ private String showReason(Reason r) { // ss << "absorbed binary clause?"; // break; case 2: - st.append("single literal ").append(printLit(neg(((Reason.Reason1) r).d1))); + st.append("single literal ").append(printLit(((Reason.Reason1) r).d1)); break; case 3: - st.append("two literals ").append(printLit(neg(((Reason.Reason2) r).d1))) - .append(" ∧ ").append(printLit(neg(((Reason.Reason2) r).d2))); + st.append("two literals ").append(printLit(((Reason.Reason2) r).d1)) + .append(" ∨ ").append(printLit(((Reason.Reason2) r).d2)); break; } return st.toString(); diff --git a/solver/src/main/java/org/chocosolver/sat/MiniSatSolver.java b/solver/src/main/java/org/chocosolver/sat/MiniSatSolver.java index f68aa0c2fb..ce75ffebc3 100644 --- a/solver/src/main/java/org/chocosolver/sat/MiniSatSolver.java +++ b/solver/src/main/java/org/chocosolver/sat/MiniSatSolver.java @@ -91,11 +91,11 @@ private void insertVarOrder(int v) { } } - public void addLearnt(TIntList learnt_clause) { + public void addLearnt(TIntList learnt_clause, boolean unforgettable) { for (int v = 0; v < nVars(); v++) { assert valueVar(v) != MiniSat.lUndef || order_heap.contains(v) : v + " not heaped"; } - super.addLearnt(learnt_clause); + super.addLearnt(learnt_clause, unforgettable); varDecayActivity(); } @@ -195,7 +195,7 @@ private ESat search(int nof_conflicts) { learnt_clause.resetQuick(); backtrack_level = analyze(confl, learnt_clause); cancelUntil(backtrack_level); - addLearnt(learnt_clause); + addLearnt(learnt_clause, false); } else { // NO CONFLICT diff --git a/solver/src/main/java/org/chocosolver/sat/Reason.java b/solver/src/main/java/org/chocosolver/sat/Reason.java index 28bfda539b..e19b8f2398 100644 --- a/solver/src/main/java/org/chocosolver/sat/Reason.java +++ b/solver/src/main/java/org/chocosolver/sat/Reason.java @@ -12,7 +12,7 @@ import org.chocosolver.solver.ICause; /** - * A class to explain a propagation. + * A class to explain a modification. * A reason is always associated with one or more literals. *
* @@ -26,7 +26,7 @@ public abstract class Reason implements ICause { */ static final Clause UNDEF = new Clause(new int[]{0}); /** - * A thread-local clause to explain a propagation with one literal. + * A thread-local clause to explain a modification with one literal. * This clause is static and can be reused in the same thread. */ private final static ThreadLocal short_expl_2 = ThreadLocal.withInitial(() -> new Clause(new int[]{0, 0})); @@ -104,9 +104,9 @@ public static Reason r(int p, int q) { */ public static Reason r(int... ps) { if (ps.length == 1) { - return new Reason1(ps[0]); + return r(ps[0]); } else if (ps.length == 2) { - return new Reason2(ps[0], ps[1]); + return r(ps[0], ps[1]); } else if (ps.length > 2) { assert ps[0] == 0 : "The first literal should be left empty for the asserting literal"; return new Clause(ps); diff --git a/solver/src/main/java/org/chocosolver/sat/SatDecorator.java b/solver/src/main/java/org/chocosolver/sat/SatDecorator.java index d5790ddd7c..bf1fb4804b 100644 --- a/solver/src/main/java/org/chocosolver/sat/SatDecorator.java +++ b/solver/src/main/java/org/chocosolver/sat/SatDecorator.java @@ -88,7 +88,7 @@ public void learnClause(int... ps) { return; default: Clause cr = new Clause(ps); - removeDominated(cr); + //removeDominated(cr); dynClauses.add(cr); attachClause(cr); break; diff --git a/solver/src/main/java/org/chocosolver/solver/Model.java b/solver/src/main/java/org/chocosolver/solver/Model.java index 21a1f74ddc..c7cc34eaaa 100755 --- a/solver/src/main/java/org/chocosolver/solver/Model.java +++ b/solver/src/main/java/org/chocosolver/solver/Model.java @@ -50,7 +50,7 @@ * @see org.chocosolver.solver.constraints.Constraint * @since 0.01 */ -public final class Model implements IModel { +public class Model implements IModel { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// PRIVATE FIELDS ///////////////////////////////////////////////////////////// @@ -983,7 +983,7 @@ public void postTemp(Constraint... cs) throws ContradictionException { "A call to Model.post(Constraint) is more appropriate."); } else { for (Propagator p : c.getPropagators()) { - getSolver().getEngine().execute(p); + getSolver().getEngine().execute(p, false); } } } diff --git a/solver/src/main/java/org/chocosolver/solver/ParallelPortfolio.java b/solver/src/main/java/org/chocosolver/solver/ParallelPortfolio.java index f87fbff446..2193f3a209 100644 --- a/solver/src/main/java/org/chocosolver/solver/ParallelPortfolio.java +++ b/solver/src/main/java/org/chocosolver/solver/ParallelPortfolio.java @@ -113,11 +113,6 @@ public class ParallelPortfolio { */ private final List models; - /** - * whether or not to use default search configurations for the different threads - **/ - private final boolean searchAutoConf; - /** * This manager is used to synchronize nogood sharing. */ @@ -129,10 +124,16 @@ public class ParallelPortfolio { private boolean isPrepared = false; /** - * List of {@link Model}s to be executed in parallel. + * Stores whether the solvers' search heuristics is reliable. + * That is, whether the solver is expected to prove the absence of a solution. */ private final HashMap reliableness; + /** + * Stores whether the search heuristics of the different solvers should be changed automatically. + */ + private final HashMap unalterable; + private final AtomicBoolean solverTerminated = new AtomicBoolean(false); private final AtomicBoolean solutionFound = new AtomicBoolean(false); private final AtomicInteger solverRunning = new AtomicInteger(0); @@ -152,11 +153,14 @@ public class ParallelPortfolio { * * @param searchAutoConf changes the search heuristics of the different solvers, except the first one (true by default). * Must be set to false if search heuristics of the different threads are specified manually, so that they are not erased + * @deprecated the 'searchAutoConf' parameter is ignored, this is now configured when a model is added. + * @see ParallelPortfolio#addModel(Model, boolean, boolean) */ + @Deprecated public ParallelPortfolio(boolean searchAutoConf) { this.models = new ArrayList<>(); this.reliableness = new HashMap<>(); - this.searchAutoConf = searchAutoConf; + this.unalterable = new HashMap<>(); } /** @@ -187,13 +191,13 @@ public void stealNogoodsOnRestarts() { /** *

* Adds a model to the list of models to run in parallel. - * The model can either be a fresh one, ready for populating, or a populated one. + * The model can either be fresh, ready for populating, or a populated one. *

*

* Important: *

    *
  • the populating process is not managed by this ParallelPortfolio - * and should be done externally, with a dedicated method for example. + * and should be done externally, with a dedicated method, for example. *
  • *
  • * when dealing with optimization problems, the objective variables HAVE to be declared eagerly with @@ -204,9 +208,11 @@ public void stealNogoodsOnRestarts() { *

    * * @param model a model to add + * @implSpec This method is equivalent to calling {@link #addModel(Model, boolean, boolean)} with + * {@code unalterable} set to {@code false} and {@code reliable} set to {@code true}. */ public void addModel(Model model) { - addModel(model, true); + addModel(model, false, true); } /** @@ -241,11 +247,13 @@ public void addModel(Model model) { *

    * * @param model a model to add + * @param unalterable set to {@code true} if the model's search heuristics should not be changed automatically. * @param reliable set to {@code true} if the model is reliable. */ - public void addModel(Model model, boolean reliable) { + public void addModel(Model model, boolean unalterable, boolean reliable) { this.models.add(model); this.reliableness.put(model, reliable); + this.unalterable.put(model, unalterable); } /** @@ -279,7 +287,7 @@ public boolean solve() { })).get(); } catch (InterruptedException | ExecutionException | SolverException e) { getSolverRunning().decrementAndGet(); - //If a InvalidSolutionException occurs and at least one model is not reliable + //If an InvalidSolutionException occurs and at least one model is not reliable // the exception may come from this model and should be ignored if (e.getCause() instanceof InvalidSolutionException) { InvalidSolutionException ex = (InvalidSolutionException) e.getCause(); @@ -391,12 +399,14 @@ public int characteristics() { public void prepare() { isPrepared = true; check(); + int searchID = 0; for (int i = 0; i < models.size(); i++) { - Solver s = models.get(i).getSolver(); - s.addStopCriterion(() -> getSolverTerminated().get()); - s.plugMonitor((IMonitorSolution) () -> updateFromSolution(s.getModel())); - if (searchAutoConf) { - configureModel(i); + Model model = models.get(i); + Solver solver = model.getSolver(); + solver.addStopCriterion(() -> getSolverTerminated().get()); + solver.plugMonitor((IMonitorSolution) () -> updateFromSolution(solver.getModel())); + if (!unalterable.get(model)) { + configureModel(model, searchID++); } } } @@ -421,8 +431,7 @@ private synchronized void updateFromSolution(Model m) { } } - private void configureModel(int workerID) { - Model worker = getModels().get(workerID); + private void configureModel(Model worker, int searchID) { ResolutionPolicy policy = worker.getResolutionPolicy(); boolean opt = policy != ResolutionPolicy.SATISFACTION; BlackBoxConfigurator bb = BlackBoxConfigurator.init(); @@ -435,7 +444,7 @@ private void configureModel(int workerID) { Function intValSel; SearchParams.VarSelConf intVarConf; BiFunction> intVarSel; - switch (workerID) { + switch (searchID) { case 0: intValConf = new SearchParams.ValSelConf( SearchParams.ValueSelection.MIN, opt, 16, true); diff --git a/solver/src/main/java/org/chocosolver/solver/Settings.java b/solver/src/main/java/org/chocosolver/solver/Settings.java index 8c34d0797c..f78fe1b9b4 100644 --- a/solver/src/main/java/org/chocosolver/solver/Settings.java +++ b/solver/src/main/java/org/chocosolver/solver/Settings.java @@ -49,7 +49,7 @@ public class Settings { private int maxTupleSizeForSubstitution = 10_000; - private int timeLimitForPreprocessing = -1; + private long maxSizeInMBToUseCompactTable = 1024L; private boolean sortPropagatorActivationWRTPriority = true; @@ -118,7 +118,7 @@ public static Settings prod() { .setModelChecker(s -> true) .setWarnUser(false) .setCheckDeclaredConstraints(false) - .setCheckDeclaredViews(false) + .setCheckDeclaredViews(true) .setCheckDeclaredMonitors(false) .setPrintAllUndeclaredConstraints(false); } @@ -307,27 +307,23 @@ public Settings setMaxTupleSizeForSubstitution(int maxTupleSizeForSubstitution) } /** - * @return the time allocated for the preprocessing + * @return maximum estimated size, in MB, of the table to use compact table representation */ - public long getTimeLimitForPreprocessing() { - return timeLimitForPreprocessing; + public long getMaxSizeInMBToUseCompactTable() { + return maxSizeInMBToUseCompactTable; } /** - * Set the time allocated for the preprocessing step. - * If the time limit is reached, the preprocessing is stopped and the resolution starts. - * Set a negative value to disable the time limit. + * Define the maximum estimated size, in MB, of the table to use compact table representation. * - * @param timeLimitForPreprocessing time limit for Strong Arc Consistency (in milliseconds) + * @param maxSizeInMBToUseCompactTable size threshold (in MB) to use compact table representation * @return the current instance - * @see org.chocosolver.solver.Solver#preprocessing(long) */ - public Settings setTimeLimitForPreprocessing(int timeLimitForPreprocessing) { - this.timeLimitForPreprocessing = timeLimitForPreprocessing; + public Settings setMaxSizeInMBToUseCompactTable(int maxSizeInMBToUseCompactTable) { + this.maxSizeInMBToUseCompactTable = maxSizeInMBToUseCompactTable; return this; } - /** * @return {@code true} if propagators are sorted wrt their priority on initial activation. */ @@ -664,7 +660,7 @@ public boolean intVarLazyLitWithWeakBounds() { * when a bound is modified, the channeling is done with all known values between the previous and the new bound. * It provides stronger reasons, which are slower to compute but more informative. * - * @param intVarLazyLitWithWeakBounds + * @param intVarLazyLitWithWeakBounds weak chaining or not * @return the current instance */ public Settings setIntVarLazyLitWithWeakBounds(boolean intVarLazyLitWithWeakBounds) { diff --git a/solver/src/main/java/org/chocosolver/solver/Solver.java b/solver/src/main/java/org/chocosolver/solver/Solver.java index c639d7fd3c..2ad7c6915d 100644 --- a/solver/src/main/java/org/chocosolver/solver/Solver.java +++ b/solver/src/main/java/org/chocosolver/solver/Solver.java @@ -401,7 +401,6 @@ private boolean initialize() { M.setTopDecisionPosition(0); pushTrail(); // store state before initial propagation; w = 0 -> 1 try { - checkTasks(); mMeasures.incFixpointCount(); // check sat if (isLCG() && !getSat().ok_) { @@ -430,7 +429,6 @@ private boolean initialize() { defaultSearch = true; mModel.getSettings().makeDefaultSearch(mModel); } - preprocessing(getModel().getSettings().getTimeLimitForPreprocessing()); if (completeSearch && !defaultSearch) { BlackBoxConfigurator bb = BlackBoxConfigurator.init(); bb.complete(mModel, M.getStrategy()); @@ -570,16 +568,6 @@ private void checkExplainedConstraints() { } } - private void checkTasks() throws ContradictionException { - if (mModel.getHook(Model.TASK_SET_HOOK_NAME) != null) { - //noinspection unchecked - ArrayList tset = (ArrayList) mModel.getHook(Model.TASK_SET_HOOK_NAME); - for (int i = 0; i < tset.size(); i++) { - tset.get(i).ensureBoundConsistency(); - } - } - } - /** * Push a world on the environment's stack, and same for MiniSat if relevant */ @@ -602,63 +590,6 @@ public void cancelTrail() { } } - /** - * This method is called after the initial propagation and before the search loop starts. - * It sequentially applies Arc Consistency on every combination of (variable, value). - * If a value is not supported by any other variable, it is removed from the domain of the variable. - * The method ends when the time limit is reached or when all combination have been checked. - * - * @implSpec A first propagation must have been done before calling this method. - */ - public void preprocessing(long timeLimitInMS) { - if (!getEngine().isInitialized()) { - throw new SolverException("A call to solver.propagate() must be done before calling solver.preprocessing()"); - } - if (timeLimitInMS > 0 && getModel().getSettings().warnUser()) { - logger.white().printf("Running preprocessing step (%dms).\n", timeLimitInMS); - } - long tl = System.currentTimeMillis() + timeLimitInMS; - IntVar[] ivars = mModel.retrieveIntVars(true); - loop: - for (int i = 0; i < ivars.length; i++) { - IntVar v = ivars[i]; - if (!v.isInstantiated()) { // if the variable is not instantiated - DisposableValueIterator it = v.getValueIterator(true); - while (it.hasNext()) { - if (System.currentTimeMillis() > tl) { - break loop; - } - int a = it.next(); - if (!hasSupport(v, a)) { - try { - v.removeValue(a, Cause.Null); - if (getModel().getSettings().warnUser()) { - logger.white().printf("Preprocessing removed value %d from %s\n", a, v.getName()); - } - } catch (ContradictionException e) { - throw new SolverException("Preprocessing failed"); - } - } - } - it.dispose(); - } - } - } - - private boolean hasSupport(IntVar var, int val) { - mModel.getEnvironment().worldPush(); - try { - var.instantiateTo(val, Cause.Null); - mModel.getSolver().getEngine().propagate(); - return true; - } catch (ContradictionException e) { - mModel.getSolver().getEngine().flush(); - return false; - } finally { - mModel.getEnvironment().worldPop(); - } - } - /** * Basic propagation: *
      @@ -894,7 +825,6 @@ public void propagate() throws ContradictionException { if (!engine.isInitialized()) { engine.initialize(); } - checkTasks(); try { engine.propagate(); } finally { diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/IConstraintFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/IConstraintFactory.java index 3a35c2a83c..04b6131edd 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/IConstraintFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/IConstraintFactory.java @@ -22,7 +22,7 @@ * * @author Jean-Guillaume FAGES */ -public interface IConstraintFactory extends IIntConstraintFactory, IRealConstraintFactory, ISetConstraintFactory, IGraphConstraintFactory { +public interface IConstraintFactory extends IIntConstraintFactory, IRealConstraintFactory, ISchedulingFactory, ISetConstraintFactory, IGraphConstraintFactory { /** * Create a constraint that acts as an observer. diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/IDecompositionFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/IDecompositionFactory.java index 6bae841a4e..c01e0a3f1f 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/IDecompositionFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/IDecompositionFactory.java @@ -14,6 +14,7 @@ import org.chocosolver.solver.ISelf; import org.chocosolver.solver.Model; import org.chocosolver.solver.constraints.extension.Tuples; +import org.chocosolver.solver.constraints.extension.TuplesFactory; import org.chocosolver.solver.constraints.nary.automata.FA.IAutomaton; import org.chocosolver.solver.constraints.nary.flow.PropMinCostMaxFlow; import org.chocosolver.solver.exception.SolverException; @@ -21,6 +22,7 @@ import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.SetVar; import org.chocosolver.solver.variables.Task; +import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; import org.chocosolver.util.tools.ArrayUtils; import java.util.ArrayList; @@ -46,6 +48,55 @@ */ public interface IDecompositionFactory extends ISelf { + /** + * Creates and posts a decomposition of an allDifferent_except constraint. + *

      + * This constraint ensures that all variables from vars are pairwise different, except those which are in except. + *

      + * This decomposition is based on the simple arithmetic reasoning. + * + * @param vars array of integer variables + * @param except array of values that can be excluded from the allDifferent constraint + */ + default void allDifferentExceptDec(IntVar[] vars, int[] except) { + BoolVar[] bs = new BoolVar[vars.length]; + IntIterableRangeSet set = new IntIterableRangeSet(except); + for (int i = 0; i < vars.length; i++) { + bs[i] = ref().boolVar(); + ref().reifyXnotinS(vars[i], set, bs[i]); + } + for (int i = 0; i < vars.length - 1; i++) { + for (int j = i + 1; j < vars.length; j++) { + ref().impXrelYC(vars[i], "!=", vars[j], 0, bs[i].and(bs[j]).boolVar()); + } + } + } + + /** + * Creates and posts a decomposition of an allDifferent_except constraint. + *

      + * This constraint ensures that all variables from vars are pairwise different, except those which are in except. + *

      + * This decomposition is based on the global cardinality constraint. + * + * @param vars array of integer variables + * @param except array of values that can be excluded from the allDifferent constraint + */ + default void allDifferentExceptDecWithGCC(IntVar[] vars, int[] except) { + IntIterableRangeSet values = new IntIterableRangeSet(); + for (IntVar var : vars) { + values.addAll(var); + } + values.addAll(except); + IntIterableRangeSet sexcept = new IntIterableRangeSet(except); + int[] valuesArray = values.toArray(); + IntVar[] occurrences = new IntVar[valuesArray.length]; + for (int i = 0; i < valuesArray.length; i++) { + occurrences[i] = ref().intVar(0, sexcept.contains(valuesArray[i]) ? vars.length : 1); + } + ref().globalCardinality(vars, valuesArray, occurrences, true).post(); + } + /** * Posts a decomposition of an among constraint. * nbVar is the number of variables of the collection vars that take their value in values. @@ -81,7 +132,7 @@ default void amongDec(IntVar nbVar, IntVar[] vars, IntVar[] values) { * @param durations processing time of each task * @param heights resource consumption of each task * @param capacity resource capacity - * @see org.chocosolver.solver.constraints.IIntConstraintFactory#cumulative(IntVar[], int[], + * @see org.chocosolver.solver.constraints.ISchedulingFactory#cumulative(IntVar[], int[], * int[], int) */ default void cumulativeTimeDec(IntVar[] starts, int[] durations, int[] heights, int capacity) { @@ -119,7 +170,7 @@ default void cumulativeTimeDec(IntVar[] starts, int[] durations, int[] heights, * @param tasks set of tasks * @param heights resource consumption of each task * @param capacity resource capacity - * @see org.chocosolver.solver.constraints.IIntConstraintFactory#cumulative(IntVar[], int[], + * @see org.chocosolver.solver.constraints.ISchedulingFactory#cumulative(IntVar[], int[], * int[], int) */ default void cumulativeDec(Task[] tasks, IntVar[] heights, IntVar capacity) { @@ -139,7 +190,7 @@ default void cumulativeDec(Task[] tasks, IntVar[] heights, IntVar capacity) { * @param durations processing time of each task * @param heights resource consumption of each task * @param capacity resource capacity - * @see org.chocosolver.solver.constraints.IIntConstraintFactory#cumulative(IntVar[], int[], + * @see org.chocosolver.solver.constraints.ISchedulingFactory#cumulative(IntVar[], int[], * int[], int) */ default void cumulativeDec(IntVar[] starts, IntVar[] durations, IntVar[] heights, IntVar capacity) { @@ -187,6 +238,32 @@ default void cumulativeDec(IntVar[] starts, IntVar[] durations, IntVar[] heights } + /** + * Creates a decomposition of the disjunctive constraint. + * This constraint ensures that no two tasks overlap. + *

      + * If a task is optional, it can be performed with a duration of 0 and the other mandatory tasks can overlap it. + * Otherwise, the task must be performed and cannot be overlapped by any other task even if it has a duration of 0. + * + * @param tasks array of tasks + * @param strict set to true to prevent overlapping even if the duration is equal to 0 + */ + default void disjunctiveDec(Task[] tasks, boolean strict) { + // all durations must be positive or zero + for (int i = 0; i < tasks.length; i++) { + tasks[i].getDuration().ge(0).post(); + BoolVar di0 = strict ? ref().boolVar(false) : ref().isEq(tasks[i].getDuration(), 0); + for (int j = i + 1; j < tasks.length; j++) { + BoolVar dj0 = strict ? ref().boolVar(false) : ref().isEq(tasks[j].getDuration(), 0); + BoolVar ibeforej = ref().scalar(new IntVar[]{tasks[i].getStart(), tasks[i].getDuration(), tasks[j].getStart()}, + new int[]{1, 1, -1}, "<=", 0).reify(); + BoolVar jbeforei = ref().scalar(new IntVar[]{tasks[j].getStart(), tasks[j].getDuration(), tasks[i].getStart()}, + new int[]{1, 1, -1}, "<=", 0).reify(); + ref().addClauses(new BoolVar[]{di0, dj0, ibeforej, jbeforei}, new BoolVar[]{}); + } + } + } + /** * Creates an element constraint: value = matrix[rowIndex-offset][colIndex-colOffset] * @@ -204,7 +281,8 @@ default void element(IntVar value, int[][] matrix, IntVar rowIndex, int rowOffse IntVar rv = ref().intView(1, rowIndex, -rowOffset); IntVar cv = ref().intView(1, colIndex, -colOffset); IntVar idx = ref().intVar(0, d0 * d1 - 1); - ref().scalar(new IntVar[]{rv, cv}, new int[]{d1, 1}, "=", idx).post(); + ref().table(new IntVar[]{rv, cv, idx}, + TuplesFactory.scalar(new IntVar[]{rv, cv}, new int[]{d1, 1}, idx, 1)).post(); // flatten the array int[] mvars = ArrayUtils.flatten(matrix); // post the element constraint @@ -359,7 +437,7 @@ default void binPackingDec(IntVar[] bin, int[] w, IntVar[] load, int offset) { default void circuitDec(IntVar[] S, int offset) { int n = S.length; ref().allDifferent(S, "AC").post(); - IntVar[] t = ref().intVarArray("t", n - 1, offset + 1, n + offset -1 ); + IntVar[] t = ref().intVarArray("t", n - 1, offset + 1, n + offset - 1); ref().allDifferent(t, "AC").post(); ref().arithm(t[0], "=", S[0]).post(); for (int i = 1; i < n - 2; i++) { @@ -433,9 +511,8 @@ default void argminDec(IntVar z, int offset, IntVar[] vars) { * @implNote This is encoded thanks to a table constraint. */ default void ifThenElseDec(BoolVar[] c, int[] x, IntVar y) { - Tuples tuples = new Tuples(); int star = Math.max(2, y.getUB() + 1); - tuples.setUniversalValue(star); + Tuples tuples = new Tuples(star); int[] t = new int[c.length + 1]; Arrays.fill(t, 0); t[c.length] = star; @@ -478,9 +555,8 @@ default void ifThenElseDec(BoolVar[] c, IntVar[] x, IntVar y) { //y.eq(x[i]).decompose().impliedBy(c[i].and(d[i]).boolVar()); c[i].and(d[i]).imp(y.eq(x[i])).post(); }/*/ - Tuples tuples = new Tuples(); - int univ = Math.max(2, y.getUB() + 1); - tuples.setUniversalValue(univ); + int univ = Math.min(0, Stream.of(c).mapToInt(IntVar::getLB).min().orElse(0)) - 1; + Tuples tuples = new Tuples(univ); int[] t = new int[c.length + 1]; Arrays.fill(t, 0); t[c.length] = c.length; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/IIntConstraintFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/IIntConstraintFactory.java index a74a6a73fe..852de3e744 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/IIntConstraintFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/IIntConstraintFactory.java @@ -14,6 +14,7 @@ import gnu.trove.set.hash.TIntHashSet; import org.chocosolver.solver.ISelf; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Settings; import org.chocosolver.solver.constraints.binary.*; import org.chocosolver.solver.constraints.binary.element.ElementFactory; import org.chocosolver.solver.constraints.extension.Tuples; @@ -44,8 +45,6 @@ import org.chocosolver.solver.constraints.nary.circuit.*; import org.chocosolver.solver.constraints.nary.count.PropCountVar; import org.chocosolver.solver.constraints.nary.count.PropCount_AC; -import org.chocosolver.solver.constraints.nary.cumulative.CumulFilter; -import org.chocosolver.solver.constraints.nary.cumulative.Cumulative; import org.chocosolver.solver.constraints.nary.element.PropElementV_fast; import org.chocosolver.solver.constraints.nary.globalcardinality.GlobalCardinality; import org.chocosolver.solver.constraints.nary.knapsack.PropKnapsack; @@ -82,7 +81,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import java.util.stream.IntStream; import java.util.stream.Stream; import static java.lang.Math.abs; @@ -223,7 +221,26 @@ default Constraint notMember(IntVar var, IntIterableRangeSet set) { */ default Constraint absolute(IntVar var1, IntVar var2) { assert var1.getModel() == var2.getModel(); - return new Constraint(ConstraintsName.ABSOLUTE, new PropAbsolute(var1, var2)); + if (var2.isInstantiated()) { + if (var2.getValue() < 0) { + return arithm(var1, "=", -var2.getValue()); + } else { + return arithm(var1, "=", var2.getValue()); + } + } else if (var1.isInstantiated()) { + if (var1.getValue() == 0) { + var2.eq(0).post(); + } else if (var1.getValue() > 0) { + return member(var2, new int[]{-var1.getValue(), var1.getValue()}); + } else { + return ref().falseConstraint(); + } + } + return new Constraint(ConstraintsName.ABSOLUTE, + ref().getSolver().isLCG() ? + new PropAbsoluteLight(var1, var2) : + new PropAbsolute(var1, var2) + ); } /** @@ -432,15 +449,15 @@ default Constraint square(IntVar var1, IntVar var2) { * @param var2 second variable */ default Constraint table(IntVar var1, IntVar var2, Tuples tuples) { - if (!var1.hasEnumeratedDomain() || !var2.hasEnumeratedDomain()) { - return table(var1, var2, tuples, "CT+"); - } else { - return table(var1, var2, tuples, "AC3bit+rm"); - } + String algo = "CT"; + return table(var1, var2, tuples, algo); } /** * Creates a table constraint over a couple of variables var1 and var2:
      + *

      + * - CT: table constraint which applies the Compact-Table algorithm,
      + * - CT+: table constraint which applies the Compact-Table algorithm on allowed tuples,
      * - AC2001: table constraint which applies the AC2001 algorithm,
      * - AC3: table constraint which applies the AC3 algorithm,
      * - AC3rm: table constraint which applies the AC3 rm algorithm,
      @@ -449,7 +466,7 @@ default Constraint table(IntVar var1, IntVar var2, Tuples tuples) { * * @param var1 first variable * @param var2 second variable - * @param tuples the relation between the two variables, among {"AC3", "AC3rm", "AC3bit+rm", "AC2001", "CT+", "FC"} + * @param tuples the relation between the two variables, among {"AC3", "AC3rm", "AC3bit+rm", "AC2001", "CT+", "CT", "FC"} */ default Constraint table(IntVar var1, IntVar var2, Tuples tuples, String algo) { Object[] args = variableUniqueness(new IntVar[]{var1, var2}); @@ -463,13 +480,27 @@ default Constraint table(IntVar var1, IntVar var2, Tuples tuples, String algo) { ref().addTable(new IntVar[]{var1, var2}, tuples); return ref().voidConstraint(); } + if (algo.endsWith("+") && !tuples.isFeasible()) { + throw new SolverException(algo + " table algorithm cannot be used with forbidden tuples."); + } + if (tuples.allowUniversalValue() && !(algo.contains("CT+") || algo.contains("STR2+"))) { + throw new SolverException(algo + " table algorithm cannot be used with short tuples."); + } + Propagator p; if (tuples.allowUniversalValue()) { p = new PropCompactTableStar(new IntVar[]{var1, var2}, tuples); } else { switch (algo) { + case "CT": case "CT+": - p = new PropCompactTable(new IntVar[]{var1, var2}, tuples); + if (tuples.allowUniversalValue()) { + p = new PropCompactTableStar(new IntVar[]{var1, var2}, tuples); + } else if (tuples.isFeasible()) { + p = new PropCompactTable(new IntVar[]{var1, var2}, tuples); + } else { + p = new PropCompactTableNeg(new IntVar[]{var1, var2}, tuples); + } break; case "AC2001": p = new PropBinAC2001(var1, var2, tuples); @@ -561,8 +592,10 @@ default Constraint pow(IntVar base, IntVar exponent, IntVar result) { } // table decomposition todo as intension constraint Tuples tuples = new Tuples(true); - for (int val1 : base) { - for (int val2 : exponent) { + int baseUB = base.getUB(); + for (int val1 = base.getLB(); val1 <= baseUB; val1 = base.nextValue(val1)) { + int exponentUB = exponent.getUB(); + for (int val2 = exponent.getLB(); val2 <= exponentUB; val2 = exponent.nextValue(val2)) { int res = (int) Math.pow(val1, val2); if (result.contains(res)) { tuples.add(val1, val2, res); @@ -689,6 +722,17 @@ default Constraint distance(IntVar var1, IntVar var2, String op, IntVar var3) { * @param result result */ default Constraint div(IntVar dividend, IntVar divisor, IntVar result) { + if (ref().getSolver().isLCG()) { + if (PropDivXYZLight.getSign(dividend) != 0 + && PropDivXYZLight.getSign(divisor) != 0 + && PropDivXYZLight.getSign(result) != 0) { + try { + return new Constraint(ConstraintsName.DIVISION, new PropDivXYZLight(dividend, divisor, result)); + } catch (SolverException e) { + // If the light propagator fails, we fall back to the full one + } + } + } return new Constraint(ConstraintsName.DIVISION, new PropDivXYZ(dividend, divisor, result)); } @@ -895,9 +939,34 @@ default Constraint allDifferentUnderCondition(IntVar[] vars, Condition condition * @param vars collection of variables */ default Constraint allDifferentExcept0(IntVar[] vars) { + if (ref().getSolver().isLCG()) { + return allDifferentExceptValues(vars, 0); + } return allDifferentUnderCondition(vars, Condition.EXCEPT_0, true); } + /** + * Creates an allDifferent constraint for variables that are not equal to any of the specified values. + * There can be multiple variables equal to 0. + * + * @param vars collection of variables + */ + default Constraint allDifferentExceptValues(IntVar[] vars, int... values) { + if (ref().getSolver().isLCG()) { + if (ref().getSettings().warnUser()) { + ref().getSolver().log().white().println( + "Warning: allDifferentExceptValues constraint is decomposed (due to LCG)."); + } + ref().allDifferentExceptDec(vars, values); + return ref().voidConstraint(); + } + final IntIterableRangeSet svalues = new IntIterableRangeSet(values); + if (svalues.size() == 1) { + return allDifferentUnderCondition(vars, v -> !v.contains(svalues.min()), true); + } + return allDifferentUnderCondition(vars, v -> !svalues.intersect(v), true); + } + /** * Creates an AllDiffPrec constraint. The predecessors and successors matrix are built as following: * with n = |variables|, for all i in [0,n-1], if there is k such that predecessors[i][k] = j then variables[j] is a predecessor of variables[i]. @@ -1440,144 +1509,6 @@ default Constraint count(IntVar value, IntVar[] vars, IntVar limit) { } } - /** - * Creates a cumulative constraint: Enforces that at each point in time, - * the cumulated height of the set of tasks that overlap that point - * does not exceed a given limit. - *

      - * Task duration and height should be >= 0 - * Discards tasks whose duration or height is equal to zero - * - * @param tasks Task objects containing start, duration and end variables - * @param heights integer variables representing the resource consumption of each task - * @param capacity integer variable representing the resource capacity - * @return a cumulative constraint - */ - default Constraint cumulative(Task[] tasks, IntVar[] heights, IntVar capacity) { - return cumulative(tasks, heights, capacity, true); - } - - /** - * Creates a cumulative constraint: Enforces that at each point in time, - * the cumulated height of the set of tasks that overlap that point - * does not exceed a given limit. - *

      - * Task duration and height should be >= 0 - * Discards tasks whose duration or height is equal to zero - * - * @param tasks Task objects containing start, duration and end variables - * @param heights integer variables representing the resource consumption of each task - * @param capacity integer variable representing the resource capacity - * @param incremental specifies if an incremental propagation should be applied - * @return a cumulative constraint - */ - default Constraint cumulative(Task[] tasks, IntVar[] heights, IntVar capacity, boolean incremental) { - return cumulative(tasks, heights, capacity, incremental, Cumulative.Filter.DEFAULT.make(tasks.length)); - } - - /** - * Creates a cumulative constraint: Enforces that at each point in time, - * the cumulated height of the set of tasks that overlap that point - * does not exceed a given limit. - *

      - * Task duration and height should be >= 0 - * Discards tasks whose duration or height is equal to zero - * - * @param tasks Task objects containing start, duration and end variables - * @param heights integer variables representing the resource consumption of each task - * @param capacity integer variable representing the resource capacity - * @param incremental specifies if an incremental propagation should be applied - * @param filters specifies which filtering algorithms to apply - * @return a cumulative constraint - */ - default Constraint cumulative(Task[] tasks, IntVar[] heights, IntVar capacity, boolean incremental, Cumulative.Filter... filters) { - return cumulative(tasks, heights, capacity, incremental, Arrays.stream(filters).map(f -> f.make(tasks.length)).toArray(CumulFilter[]::new)); - } - - /** - * Creates a cumulative constraint: Enforces that at each point in time, - * the cumulated height of the set of tasks that overlap that point - * does not exceed a given limit. - *

      - * Task duration and height should be >= 0 - * Discards tasks whose duration or height is equal to zero - * - * @param tasks Task objects containing start, duration and end variables - * @param heights integer variables representing the resource consumption of each task - * @param capacity integer variable representing the resource capacity - * @param incremental specifies if an incremental propagation should be applied - * @param filters specifies which filtering algorithms to apply - * @return a cumulative constraint - */ - default Constraint cumulative(Task[] tasks, IntVar[] heights, IntVar capacity, boolean incremental, CumulFilter... filters) { - if (ref().getSolver().isLCG()) { - if (ref().getSettings().warnUser()) { - ref().getSolver().log().white().println( - "Warning: cumulative constraint is decomposed (due to LCG)."); - } - ref().cumulativeDec(tasks, heights, capacity); - return ref().voidConstraint(); - } - if (tasks.length != heights.length) { - throw new SolverException("Tasks and heights arrays should have same size"); - } - int nbUseFull = 0; - for (int h = 0; h < heights.length; h++) { - if (heights[h].getUB() > 0 && tasks[h].getDuration().getUB() > 0) { - nbUseFull++; - } - } - // remove tasks that have no impact on resource consumption - if (nbUseFull < tasks.length) { - if (nbUseFull == 0) return arithm(capacity, ">=", 0); - Task[] T2 = new Task[nbUseFull]; - IntVar[] H2 = new IntVar[nbUseFull]; - int idx = 0; - for (int h = 0; h < heights.length; h++) { - if (heights[h].getUB() > 0 && tasks[h].getDuration().getUB() > 0) { - T2[idx] = tasks[h]; - H2[idx] = heights[h]; - idx++; - } - } - tasks = T2; - heights = H2; - } - return new Cumulative(tasks, heights, capacity, incremental, filters); - } - - /** - * Creates and posts a decomposition of a cumulative constraint: - * Enforces that at each point in time, - * the cumulated height of the set of tasks that overlap that point - * does not exceed a given limit. - *

      - * Task duration and height should be >= 0 - * Discards tasks whose duration or height is equal to zero - * - * @param starts starting time of each task - * @param durations processing time of each task - * @param heights resource consumption of each task - * @param capacity resource capacity - */ - default Constraint cumulative(IntVar[] starts, int[] durations, int[] heights, int capacity) { - int n = starts.length; - final IntVar[] d = new IntVar[n]; - final IntVar[] h = new IntVar[n]; - final IntVar[] e = new IntVar[n]; - Task[] tasks = new Task[n]; - for (int i = 0; i < n; i++) { - d[i] = ref().intVar(durations[i]); - h[i] = ref().intVar(heights[i]); - e[i] = ref().intVar(starts[i].getName() + "_e", - starts[i].getLB() + durations[i], - starts[i].getUB() + durations[i], - true); - tasks[i] = new Task(starts[i], d[i], e[i]); - } - return ref().cumulative(tasks, h, ref().intVar(capacity), false, Cumulative.Filter.NAIVETIME); - } - /** *

      * Create a decreasing constraint which ensures that the variables in {@code vars} are decreasing. @@ -1647,9 +1578,9 @@ default Constraint diffN(IntVar[] X, IntVar[] Y, IntVar[] width, IntVar[] height return Constraint.merge(ConstraintsName.DIFFNWITHCUMULATIVE, diffNCons, min(minX, X), max(maxX, EX), scalar(new IntVar[]{maxX, minX}, new int[]{1, -1}, "=", diffX), - cumulative(TX, height, diffY), + model.cumulative(TX, height, diffY), min(minY, Y), max(maxY, EY), scalar(new IntVar[]{maxY, minY}, new int[]{1, -1}, "=", diffY), - cumulative(TY, width, diffX) + model.cumulative(TY, width, diffX) ); } else { return diffNCons; @@ -1845,9 +1776,9 @@ default Constraint inverseChanneling(IntVar[] vars1, IntVar[] vars2, int offset1 } Propagator ip = allEnum ? new PropInverseChannelAC(vars1, vars2, offset1, offset2) : new PropInverseChannelBC(vars1, vars2, offset1, offset2); - Constraint alldiff1 = allDifferent(vars1, ac ? "AC" : ""); + Constraint alldiff1 = allDifferent(vars1, ac ? "AC" : "BC"); alldiff1.ignore(); - Constraint alldiff2 = allDifferent(vars2, ac ? "AC" : ""); + Constraint alldiff2 = allDifferent(vars2, ac ? "AC" : "BC"); alldiff2.ignore(); return new Constraint(ConstraintsName.INVERSECHANNELING, ArrayUtils.append( alldiff1.getPropagators(), @@ -2713,28 +2644,26 @@ default Constraint sum(BoolVar[] vars, String operator, IntVar sum, int minCardF } /** - * Creates a table constraint specifying that the sequence of variables vars must belong to the list of tuples - * (or must NOT belong in case of infeasible tuples) + * Creates a table constraint specifying that the sequence of variables 'vars' + * must belong to the list of tuples (or must NOT belong in case of infeasible tuples) *

      - * Default configuration with GACSTR+ algorithm for feasible tuples and GAC3rm otherwise + * Default configuration is "CT" if memory consumption is not too high, "STR2+" otherwise. * * @param vars variables forming the tuples * @param tuples the relation between the variables (list of allowed/forbidden tuples) + * @see Settings#getMaxSizeInMBToUseCompactTable() + * @see Settings#setMaxSizeInMBToUseCompactTable(int) */ default Constraint table(IntVar[] vars, Tuples tuples) { - String algo = "GAC3rm"; - if (tuples.isFeasible()) { - //noinspection OptionalGetWithoutIsPresent - if (tuples.nbTuples() > 512 && - (IntStream.range(0, vars.length) - .map(i -> tuples.max(i) - tuples.min(i)) - .max().getAsInt()) < 512) { - algo = "CT+"; - } else if (tuples.allowUniversalValue()) { - // STR2+ or CT+, depending on dom size + String algo = "CT"; + long estimatedMem = 0; + long maxMem = ref().getSettings().getMaxSizeInMBToUseCompactTable(); + for (int i = 0; i < vars.length && tuples.isFeasible(); i++) { + // on word encoded 64 bits on a long (of 64 bits) + estimatedMem += ((tuples.nbTuples() / 64L) * vars[i].getRange()) * 64 / 1024 / 1024; // in MB + if (estimatedMem < 0 || estimatedMem > maxMem) { algo = "STR2+"; - } else { - algo = "GACSTR+"; + break; } } return table(vars, tuples, algo); @@ -2743,7 +2672,10 @@ default Constraint table(IntVar[] vars, Tuples tuples) { /** * Creates a table constraint, with the specified algorithm defined algo *

      - * - CT+: Compact-Table algorithm (AC), + * - CT: Compact-Table algorithm (AC), + *
      + *

      + * - CT+: Compact-Table algorithm for allowed tuples only (AC), *
      * - GAC2001: Arc Consistency version 2001 for tuples, *
      @@ -2763,11 +2695,13 @@ default Constraint table(IntVar[] vars, Tuples tuples) { * * @param vars variables forming the tuples * @param tuples the relation between the variables (list of allowed/forbidden tuples). Should not be modified once passed to the constraint. - * @param algo to choose among {"CT+", "GAC3rm", "GAC2001", "GACSTR", "GAC2001+", "GAC3rm+", "FC", "STR2+"} + * @param algo to choose among {"CT", "CT+", "AC3rm", "GAC3rm", "AC2001", "GAC2001", "GACSTR", "GAC2001+", "GAC3rm+", "FC", "STR2+"} */ default Constraint table(IntVar[] vars, Tuples tuples, String algo) { // if some variables appears more than one time, the filtering algorithm can be not correct vars = (IntVar[]) variableUniqueness(vars)[0]; + // check views too + if (ref().getSolver().isLCG()) { if (ref().getSettings().warnUser()) { ref().getSolver().log().white().println( @@ -2780,6 +2714,7 @@ default Constraint table(IntVar[] vars, Tuples tuples, String algo) { switch (algo) { case "FC": return table(vars[0], vars[1], tuples, algo); + case "AC2001": case "GAC2001": return table(vars[0], vars[1], tuples, "AC2001"); case "CT+": @@ -2790,19 +2725,22 @@ default Constraint table(IntVar[] vars, Tuples tuples, String algo) { return table(vars[0], vars[1], tuples); } } - if (algo.contains("+") && !tuples.isFeasible()) { + if (algo.endsWith("+") && !tuples.isFeasible()) { throw new SolverException(algo + " table algorithm cannot be used with forbidden tuples."); } - if (tuples.allowUniversalValue() && !(algo.contains("CT+") || algo.contains("STR2+"))) { + if (tuples.allowUniversalValue() && !(algo.startsWith("CT") || algo.contains("STR2+"))) { throw new SolverException(algo + " table algorithm cannot be used with short tuples."); } Propagator p; switch (algo) { + case "CT": case "CT+": { if (tuples.allowUniversalValue()) { p = new PropCompactTableStar(vars, tuples); - } else { + } else if (tuples.isFeasible()) { p = new PropCompactTable(vars, tuples); + } else { + p = new PropCompactTableNeg(vars, tuples); } } break; @@ -2812,9 +2750,11 @@ default Constraint table(IntVar[] vars, Tuples tuples, String algo) { case "FC": p = new PropLargeFC(vars, tuples); break; + case "AC3rm": case "GAC3rm": p = new PropLargeGAC3rm(vars, tuples); break; + case "AC2001": case "GAC2001": p = new PropLargeGAC2001(vars, tuples); break; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/IReificationFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/IReificationFactory.java index 6d8b88d86c..8de02441f6 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/IReificationFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/IReificationFactory.java @@ -462,10 +462,10 @@ default void reifyXnotinS(IntVar X, IntIterableRangeSet S, BoolVar B) { /** - * Posts one constraint that expresses : (X op C) ⇔ B. + * Posts one constraint that expresses: (X op C) ⇔ B. * * @param X an integer variable - * @param op an operator (allowed: =, !=, <, <=, >, >=) + * @param op an operator (allowed: "=", "!=", "<", "<=", ">", ">=") * @param C an int * @param B a boolean variable */ @@ -508,15 +508,19 @@ default void reifXrelC(IntVar X, String op, int C, BoolVar B) { } /** - * Posts one constraint that expresses : (X op Y + C) ⇔ B. + * Posts one constraint that expresses: (X op Y + C) ⇔ B. * * @param X an integer variable - * @param op an operator (allowed: =, !=, <, <=, >, >=) + * @param op an operator (allowed: "=", "!=", "<", "<=", ">", ">=") * @param Y an integer variable * @param C an int * @param B a boolean variable */ default void reifXrelYC(IntVar X, String op, IntVar Y, int C, BoolVar B) { + if (X.isInstantiated()) { + reifXrelC(Y, Operator.getFlip(op), X.getValue() - C, B); + return; + } if (Y.isInstantiated()) { reifXrelC(X, op, Y.getValue() + C, B); return; @@ -556,10 +560,10 @@ default void reifXrelYC(IntVar X, String op, IntVar Y, int C, BoolVar B) { } /** - * Posts one constraint that expresses : (X op C) ⇐ B. + * Posts one constraint that expresses: (X op C) ⇐ B. * * @param X an integer variable - * @param op an operator (allowed: =, !=, <, <=, >, >=) + * @param op an operator (allowed: "=", "!=", "<", "<=", ">", ">=") * @param C an int * @param B a boolean variable */ @@ -597,15 +601,19 @@ default void impXrelC(IntVar X, String op, int C, BoolVar B) { } /** - * Posts one constraint that expresses : (X op Y + C) ⇐ B. + * Posts one constraint that expresses: (X op Y + C) ⇐ B. * * @param X an integer variable - * @param op an operator (allowed: =, !=, <, <=, >, >=) + * @param op an operator (allowed: "=", "!=", "<", "<=", ">", ">=") * @param Y an integer variable * @param C an int * @param B a boolean variable */ default void impXrelYC(IntVar X, String op, IntVar Y, int C, BoolVar B) { + if (X.isInstantiated()) { + impXrelC(Y, Operator.getFlip(op), X.getValue() - C, B); + return; + } if (Y.isInstantiated()) { impXrelC(X, op, Y.getValue() + C, B); return; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ISchedulingFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/ISchedulingFactory.java new file mode 100644 index 0000000000..fb8a289b10 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ISchedulingFactory.java @@ -0,0 +1,270 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints; + +import java.util.*; +import java.util.function.Function; + +import org.chocosolver.solver.ISelf; +import org.chocosolver.solver.Model; +import org.chocosolver.solver.constraints.nary.cumulative.PropagatorCumulative; +import org.chocosolver.solver.exception.SolverException; +import org.chocosolver.solver.search.strategy.Search; +import org.chocosolver.solver.search.strategy.selectors.values.IntDomainMin; +import org.chocosolver.solver.search.strategy.strategy.IntStrategy; +import org.chocosolver.solver.search.strategy.strategy.SetTimes; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.Task; + +/** + * Interface to make and declare everything useful for scheduling problems (Task objects, constraints, search heuristics, etc.) + * + * @author Arthur GODET + * @since 17/08/2021 + */ +public interface ISchedulingFactory extends ISelf { + ///////////////////////////////////////////////////////////////////////////// + //////////////////////////////// CUMULATIVE ///////////////////////////////// + ///////////////////////////////////////////////////////////////////////////// + + /** + * Creates a cumulative constraint: Enforces that at each point in time, + * the cumulated height of the set of tasks that overlap that point + * does not exceed a given limit. + *

      + * heights should be >= 0 + * Discards tasks whose duration or height is equal to zero + * + * @param tasks Task objects containing start, duration and end variables + * @param heights integer variables representing the resource consumption of each task + * @param capacity integer variable representing the resource capacity + * @return a cumulative constraint + */ + default Constraint cumulative(final List tasks, final List heights, final IntVar capacity) { + if (tasks.size() != heights.size()) { + throw new SolverException("Tasks and heights arrays should have same size"); + } + Task[] tasksArray = new Task[tasks.size()]; + IntVar[] heightsArray = new IntVar[tasks.size()]; + for (int i = 0; i < tasks.size(); ++i) { + tasksArray[i] = tasks.get(i); + heightsArray[i] = heights.get(i); + } + return cumulative(tasksArray, heightsArray, capacity); + } + + /** + * Creates a cumulative constraint: Enforces that at each point in time, + * the cumulated height of the set of tasks that overlap that point + * does not exceed a given limit. + *

      + * heights should be >= 0 + * Discards tasks whose duration or height is equal to zero + * + * @param tasks Task objects containing start, duration and end variables + * @param heights integer variables representing the resource consumption of each task + * @param capacity integer variable representing the resource capacity + * @return a cumulative constraint + */ + default Constraint cumulative(final Task[] tasks, final IntVar[] heights, final IntVar capacity) { + return cumulative(tasks, heights, capacity, false, false); + } + + /** + * Creates a cumulative constraint: Enforces that at each point in time, + * the cumulated height of the set of tasks that overlap that point + * does not exceed a given limit. + *

      + * heights should be >= 0 + * Discards tasks whose duration or height is equal to zero + * + * @param tasks Task objects containing start, duration and end variables + * @param heights integer variables representing the resource consumption of each task + * @param capacity integer variable representing the resource capacity + * @param energyNaive whether to apply the naive energy filtering + * @param disjunctiveEnergyNaive whether to apply the naive disjunctive energy filtering + * @return a cumulative constraint + */ + default Constraint cumulative( + final Task[] tasks, + final IntVar[] heights, + final IntVar capacity, + final boolean energyNaive, + final boolean disjunctiveEnergyNaive + ) { + if (tasks.length != heights.length) { + throw new SolverException("Tasks and heights arrays should have same size"); + } + // keep only tasks that can have impact on resource consumption + final List tasksToKeep = new ArrayList<>(); + final List heightsToKeep = new ArrayList<>(); + for (int i = 0; i < heights.length; i++) { + if (heights[i].getUB() > 0 && tasks[i].getMaxDuration() > 0 && tasks[i].mayBePerformed()) { + tasksToKeep.add(tasks[i]); + heightsToKeep.add(heights[i]); + } + } + if (tasksToKeep.isEmpty()) { + return ref().arithm(capacity, ">=", 0); + } + final Task[] keptTasks = new Task[tasksToKeep.size()]; + final IntVar[] keptHeights = new IntVar[tasksToKeep.size()]; + for (int i = 0; i < tasksToKeep.size(); ++i) { + keptTasks[i] = tasksToKeep.get(i); + keptHeights[i] = heightsToKeep.get(i); + } + return new Constraint( + ConstraintsName.CUMULATIVE, + new PropagatorCumulative(keptTasks, keptHeights, capacity, energyNaive, disjunctiveEnergyNaive) + ); + } + + /** + * Creates a cumulative constraint: + * Enforces that at each point in time, + * the cumulated height of the set of tasks that overlap that point + * does not exceed a given limit. + *

      + * Task duration and height should be >= 0 + * Discards tasks whose duration or height is equal to zero + * + * @param starts starting time of each task + * @param durations processing time of each task + * @param heights resource consumption of each task + * @param capacity resource capacity + */ + default Constraint cumulative(final IntVar[] starts, final int[] durations, final int[] heights, final int capacity) { + int n = starts.length; + final IntVar[] d = new IntVar[n]; + final IntVar[] h = new IntVar[n]; + final IntVar[] e = new IntVar[n]; + Task[] tasks = new Task[n]; + for (int i = 0; i < n; i++) { + d[i] = ref().intVar(durations[i]); + h[i] = ref().intVar(heights[i]); + e[i] = ref().intVar(starts[i].getName() + "_e", + starts[i].getLB() + durations[i], + starts[i].getUB() + durations[i], + true); + tasks[i] = ref().taskVar(starts[i], d[i], e[i]); + } + return cumulative(tasks, h, ref().intVar(capacity)); + } + + ///////////////////////////////////////////////////////////////////////////// + ////////////////////////////////// SEARCH /////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////// + + static IntVar[] extractVars(final Task[] tasks, final Function function) { + return Arrays.stream(tasks).map(function).toArray(IntVar[]::new); + } + + static IntVar[] extractStartVars(final Task[] tasks) { + return extractVars(tasks, Task::getStart); + } + + static IntVar[] extractDurationVars(final Task[] tasks) { + return extractVars(tasks, Task::getDuration); + } + + static IntVar[] extractEndVars(final Task[] tasks) { + return extractVars(tasks, Task::getEnd); + } + + /** + * Search for scheduling problems described in the following paper : + * Godard, D., Laborie, P., Nuijten, W.: Randomized large neighborhood search for cumulative scheduling. In: Biundo, S., Myers, K.L., Rajan, K. (eds.) Proceedings of the Fifteenth International Conference on Automated Planning and Scheduling(ICAPS 2005), June 5-10 2005, Monterey, California, USA. pp. 81–89. AAAI (2005),http://www.aaai.org/Library/ICAPS/2005/icaps05-009.php + *

      + * Beware that the search is not complete in general, as stated in the paper. + * + * @param tasks the tasks + * @return the strategy + */ + default IntStrategy setTimes(final Task[] tasks) { + SetTimes setTimes = new SetTimes(tasks); + ref().post(new Constraint("SET_TIMES", setTimes)); + return Search.intVarSearch(setTimes, new IntDomainMin(), extractStartVars(tasks)); + } + + enum ArbitrationRule { + MIN_EST, MAX_EST, MIN_LST, MAX_LST, MIN_ECT, MAX_ECT, MIN_LCT, MAX_LCT + } + + /** + * Returns true if the first task is before the second task according to the rule. + * If the rule is MIN_EST(0), then returns task1.est < task2.est + * If the rule is MAX_EST(1), then returns task1.est > task2.est + * If the rule is MIN_LST(2), then returns task1.lst < task2.lst + * If the rule is MAX_LST(3), then returns task1.lst > task2.lst + * If the rule is MIN_ECT(4), then returns task1.eet < task2.eet + * If the rule is MAX_ECT(5), then returns task1.eet > task2.eet + * If the rule is MIN_LCT(6), then returns task1.let < task2.let + * If the rule is MAX_LCT(7), then returns task1.let > task2.let + * + * @param task1 the first task + * @param task2 the second task + * @param rule the rule + * @return true iff task1 is before task2 according to the rule + */ + default boolean before(final Task task1, final Task task2, final ArbitrationRule rule) { + switch (rule) { + case MIN_EST: + return task1.getStart().getLB() < task2.getStart().getLB(); + case MAX_EST: + return task1.getStart().getLB() > task2.getStart().getLB(); + case MIN_LST: + return task1.getStart().getUB() < task2.getStart().getUB(); + case MAX_LST: + return task1.getStart().getUB() > task2.getStart().getUB(); + case MIN_ECT: + return task1.getEnd().getLB() < task2.getEnd().getLB(); + case MAX_ECT: + return task1.getEnd().getLB() > task2.getEnd().getLB(); + case MIN_LCT: + return task1.getEnd().getUB() < task2.getEnd().getUB(); + case MAX_LCT: + return task1.getEnd().getUB() > task2.getEnd().getUB(); + default: + throw new UnsupportedOperationException("Task comparison should be either MIN_EST(0), MAX_EST(1), MIN_LST(2), MAX_LST(3), MIN_EET(4), MAX_EET(5), MIN_LET(6) or MAX_LET(7)."); + } + } + + /** + * Builds a strategy that selects the task's start with the smallest lower bound (in case of equality, it selects the task with the smallest end's lower bound). + * + * @param tasks the tasks + * @return the strategy + */ + default IntStrategy smallest(final Task[] tasks) { + return smallest(tasks, ArbitrationRule.MIN_ECT); + } + + default IntStrategy smallest(final Task[] tasks, final ArbitrationRule arbitrationRule) { + IntVar[] starts = extractStartVars(tasks); + return Search.intVarSearch( + variables -> { + int idx = -1; + for (int i = 0; i < tasks.length; i++) { + if (!starts[i].isInstantiated()) { + if (idx == -1 + || starts[i].getLB() < starts[idx].getLB() + || starts[i].getLB() == starts[idx].getLB() && before(tasks[i], tasks[idx], arbitrationRule) + ) { + idx = i; + } + } + } + return idx == -1 ? null : starts[idx]; + }, + new IntDomainMin(), + starts + ); + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ImpliedConstraint.java b/solver/src/main/java/org/chocosolver/solver/constraints/ImpliedConstraint.java index 8a809d2746..5b8c25e5b4 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/ImpliedConstraint.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ImpliedConstraint.java @@ -96,8 +96,7 @@ public void activate(int idx) throws ContradictionException { for (int p = indices[idx]; p < indices[idx + 1]; p++) { assert (propagators[p].isReifiedAndSilent()); propagators[p].setReifiedTrue(); - model.getSolver().getEngine().execute(propagators[p]); - model.getSolver().getEngine().onPropagatorExecution(propagators[p]); + model.getSolver().getEngine().execute(propagators[p], true); } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/Propagator.java b/solver/src/main/java/org/chocosolver/solver/constraints/Propagator.java index 1e4ef5570c..c44b32434a 100755 --- a/solver/src/main/java/org/chocosolver/solver/constraints/Propagator.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/Propagator.java @@ -624,7 +624,7 @@ public boolean isReified() { @Override public Function manageReification() { - if (isReified() && reifVar.isInstantiated()) { + if (model.getSolver().isLCG() && isReified() && reifVar.isInstantiated()) { return r -> Reason.gather(r, reifVar.isInstantiated() ? reifVar.getValLit() : 1); } else { return ICause.super.manageReification(); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ReificationConstraint.java b/solver/src/main/java/org/chocosolver/solver/constraints/ReificationConstraint.java index 20d615120b..be39e887cb 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/ReificationConstraint.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ReificationConstraint.java @@ -106,8 +106,7 @@ public void activate(int idx) throws ContradictionException { for (int p = indices[idx]; p < indices[idx + 1]; p++) { assert (propagators[p].isReifiedAndSilent()); propagators[p].setReifiedTrue(); - model.getSolver().getEngine().execute(propagators[p]); - model.getSolver().getEngine().onPropagatorExecution(propagators[p]); + model.getSolver().getEngine().execute(propagators[p], true); } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsolute.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsolute.java index 2097353c97..82d2462f64 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsolute.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsolute.java @@ -38,7 +38,7 @@ public PropAbsolute(IntVar X, IntVar Y) { super(ArrayUtils.toArray(X, Y), PropagatorPriority.BINARY, true); this.X = vars[0]; this.Y = vars[1]; - bothEnumerated = X.hasEnumeratedDomain() && Y.hasEnumeratedDomain(); + bothEnumerated = X.hasUnfixedEnumeratedDomain() && Y.hasUnfixedEnumeratedDomain(); } @Override @@ -101,8 +101,8 @@ public void propagate(int varIdx, int mask) throws ContradictionException { Y.updateLowerBound(-val, this, lcg() ? Reason.r(X.getValLit()) : Reason.undef()); Y.updateUpperBound(val, this, lcg() ? Reason.r(X.getValLit()) : Reason.undef()); val--; - for (int v = -val; v <= val; v = Y.nextValue(v)) { - Y.removeValue(v, this, lcg() ? Reason.r(X.getValLit()) : Reason.undef()); + if (val >= 0) { + removeInterval(Y, -val, val, lcg() ? Reason.r(X.getValLit()) : Reason.undef()); } setPassive(); } else { @@ -124,10 +124,9 @@ private void setBounds() throws ContradictionException { int min = X.getLB(); Y.updateLowerBound(-max, this, lcg() ? Reason.r(X.getMaxLit()) : Reason.undef()); Y.updateUpperBound(max, this, lcg() ? Reason.r(X.getMaxLit()) : Reason.undef()); - for (int v = 1 - min; v <= min - 1; v = Y.nextValue(v)) { - Y.removeValue(v, this, lcg() ? Reason.r(X.getMinLit()) : Reason.undef()); + if (1 - min <= min -1) { + removeInterval(Y, 1 - min, min - 1, lcg() ? Reason.r(X.getMinLit()) : Reason.undef()); } - //if (!lcg()) Y.removeInterval(1 - min, min - 1, this); ///////////////////////////////////////////////// int prevLB = X.getLB(); int prevUB = X.getUB(); @@ -178,4 +177,13 @@ private void enumeratedFiltering() throws ContradictionException { // EXPLANATIONS //*********************************************************************************** + private void removeInterval(IntVar intVar, int fromIncl, int toIncl, Reason reason) throws ContradictionException { + if (!lcg()) { + intVar.removeInterval(fromIncl, toIncl, this); + } else { + for (int i=fromIncl; i<=toIncl; i++) { + intVar.removeValue(i, this, reason); + } + } + } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsoluteLight.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsoluteLight.java new file mode 100644 index 0000000000..e71d413bb8 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropAbsoluteLight.java @@ -0,0 +1,104 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.binary; + +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.constraints.Explained; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.constraints.PropagatorPriority; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.events.IntEventType; +import org.chocosolver.util.ESat; +import org.chocosolver.util.tools.ArrayUtils; + +/** + * Enforces X = |Y| + *
      + * + * @author Charles Prud'homme + * @since 04/07/2025 + */ +@Explained +public class PropAbsoluteLight extends Propagator { + + private final IntVar absY; + private final IntVar Y; + + public PropAbsoluteLight(IntVar X, IntVar Y) { + super(ArrayUtils.toArray(X, Y), PropagatorPriority.BINARY, false); + this.absY = vars[0]; + this.Y = vars[1]; + } + + @Override + public int getPropagationConditions(int vIdx) { + if (vIdx == 0) { + return IntEventType.upperBoundAndInst(); + } else { + return IntEventType.boundAndInst(); + } + } + + @Override + public ESat isEntailed() { + if (vars[0].getUB() < 0) { + return ESat.FALSE; + } else if (vars[0].isInstantiated()) { + if (vars[1].isInstantiated()) { + return ESat.eval(vars[0].getValue() == Math.abs(vars[1].getValue())); + } else if (vars[1].getDomainSize() == 2 && + vars[1].contains(vars[0].getValue()) && + vars[1].contains(-vars[0].getValue())) { + return ESat.TRUE; + } else if (!vars[1].contains(vars[0].getValue()) && + !vars[1].contains(-vars[0].getValue())) { + return ESat.FALSE; + } else { + return ESat.UNDEFINED; + } + } else { + return ESat.UNDEFINED; + } + } + + @Override + public String toString() { + return String.format("%s = |%s|", vars[0].toString(), vars[1].toString()); + } + + //*********************************************************************************** + // FILTERING + //*********************************************************************************** + + @Override + public void propagate(int evtmask) throws ContradictionException { + absY.updateLowerBound(0, this, Reason.undef()); + boolean loop; + do { + int l = Y.getLB(); + int u = Y.getUB(); + if (l >= 0) { + absY.updateLowerBound(l, this, lcg() ? Reason.r(Y.getMinLit()) : Reason.undef()); + absY.updateUpperBound(u, this, lcg() ? Reason.r(Y.getMinLit(), Y.getMaxLit()) : Reason.undef()); + } else if (u <= 0) { + absY.updateLowerBound(-u, this, lcg() ? Reason.r(Y.getMaxLit()) : Reason.undef()); + absY.updateUpperBound(-l, this, lcg() ? Reason.r(Y.getMaxLit(), Y.getMinLit()) : Reason.undef()); + } else { + int t = Math.max(-l, u); + absY.updateUpperBound(t, this, lcg() ? Reason.r(Y.getMaxLit(), Y.getMinLit()) : Reason.undef()); + } + int au = absY.getUB(); + loop = Y.updateUpperBound(au, this, lcg() ? Reason.r(absY.getMaxLit()) : Reason.undef()); + loop |= Y.updateLowerBound(-au, this, lcg() ? Reason.r(absY.getMaxLit()) : Reason.undef()); + }while(loop); + } + +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualXY_C.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualXY_C.java index 6c68f546b0..f8156a8204 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualXY_C.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualXY_C.java @@ -51,7 +51,7 @@ public PropEqualXY_C(IntVar[] vars, int c) { idms[0] = vars[0].monitorDelta(this); idms[1] = vars[1].monitorDelta(this); rem_proc = i -> vars[indexToFilter].removeValue(cste - i, this, - lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_NE)) : Reason.undef()); + lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_EQ)) : Reason.undef()); } } @@ -71,13 +71,13 @@ public void propagate(int evtmask) throws ContradictionException { int ub = x.getUB(); for (int val = x.getLB(); val <= ub; val = x.nextValue(val)) { if (!y.contains(cste - val)) { - x.removeValue(val, this, lcg() ? Reason.r(x.getLit(val, IntVar.LR_NE)) : Reason.undef()); + x.removeValue(val, this, lcg() ? Reason.r(y.getLit(cste - val, IntVar.LR_EQ)) : Reason.undef()); } } ub = y.getUB(); for (int val = y.getLB(); val <= ub; val = y.nextValue(val)) { if (!x.contains(cste - val)) { - y.removeValue(val, this, lcg() ? Reason.r(y.getLit(val, IntVar.LR_NE)) : Reason.undef()); + y.removeValue(val, this, lcg() ? Reason.r(x.getLit(cste - val, IntVar.LR_EQ)) : Reason.undef()); } } idms[0].startMonitoring(); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_Y.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_Y.java index 9e9532979d..f0c236b632 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_Y.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_Y.java @@ -51,7 +51,7 @@ public PropEqualX_Y(IntVar x, IntVar y) { idms[0] = vars[0].monitorDelta(this); idms[1] = vars[1].monitorDelta(this); rem_proc = i -> vars[indexToFilter].removeValue(i, this, - lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_NE)) : Reason.undef()); + lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_EQ)) : Reason.undef()); } } @@ -81,13 +81,13 @@ public void propagate(int evtmask) throws ContradictionException { int ub = x.getUB(); for (int val = x.getLB(); val <= ub; val = x.nextValue(val)) { if (!(y.contains(val))) { - x.removeValue(val, this, lcg() ? Reason.r(y.getLit(val, IntVar.LR_NE)) : Reason.undef()); + x.removeValue(val, this, lcg() ? Reason.r(y.getLit(val, IntVar.LR_EQ)) : Reason.undef()); } } ub = y.getUB(); for (int val = y.getLB(); val <= ub; val = y.nextValue(val)) { if (!(x.contains(val))) { - y.removeValue(val, this, lcg() ? Reason.r(x.getLit(val, IntVar.LR_NE)) : Reason.undef()); + y.removeValue(val, this, lcg() ? Reason.r(x.getLit(val, IntVar.LR_EQ)) : Reason.undef()); } } idms[0].startMonitoring(); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_YC.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_YC.java index f8734d607c..5a26eb060a 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_YC.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropEqualX_YC.java @@ -53,7 +53,7 @@ public PropEqualX_YC(IntVar[] vars, int c) { idms[0] = vars[0].monitorDelta(this); idms[1] = vars[1].monitorDelta(this); rem_proc = i -> vars[indexToFilter].removeValue(i + offSet, this, - lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_NE)) : Reason.undef()); + lcg() ? Reason.r(vars[1 - indexToFilter].getLit(i, IntVar.LR_EQ)) : Reason.undef()); } } @@ -73,13 +73,13 @@ public void propagate(int evtmask) throws ContradictionException { int ub = x.getUB(); for (int val = x.getLB(); val <= ub; val = x.nextValue(val)) { if (!y.contains(val - cste)) { - x.removeValue(val, this, lcg() ? Reason.r(y.getLit(val - cste, IntVar.LR_NE)) : Reason.undef()); + x.removeValue(val, this, lcg() ? Reason.r(y.getLit(val - cste, IntVar.LR_EQ)) : Reason.undef()); } } ub = y.getUB(); for (int val = y.getLB(); val <= ub; val = y.nextValue(val)) { if (!x.contains(val + cste)) { - y.removeValue(val, this, lcg() ? Reason.r(x.getLit(val + cste, IntVar.LR_NE)) : Reason.undef()); + y.removeValue(val, this, lcg() ? Reason.r(x.getLit(val + cste, IntVar.LR_EQ)) : Reason.undef()); } } idms[0].startMonitoring(); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropScale.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropScale.java index 154ec63da2..d53b91729b 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropScale.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/PropScale.java @@ -63,7 +63,7 @@ public final void propagate(int evtmask) throws ContradictionException { int zub = X.getUB(); for (int v = X.getLB(); v <= zub; v = X.nextValue(v)) { if (!Z.contains(v * Y)) { - X.removeValue(v, this, lcg() ? Reason.r(Z.getLit(v * Y, IntVar.LR_NE)) : Reason.undef()); + X.removeValue(v, this, lcg() ? Reason.r(Z.getLit(v * Y, IntVar.LR_EQ)) : Reason.undef()); } } int v = Z.getLB(); @@ -73,7 +73,7 @@ public final void propagate(int evtmask) throws ContradictionException { Z.removeValue(v, this, Reason.undef()); } if (!X.contains(v / Y)) { - Z.removeValue(v, this, lcg() ? Reason.r(X.getLit(v / Y, IntVar.LR_NE)) : Reason.undef()); + Z.removeValue(v, this, lcg() ? Reason.r(X.getLit(v / Y, IntVar.LR_EQ)) : Reason.undef()); } } } else if (hasChanged && Z.hasEnumeratedDomain()) { diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/binary/element/PropElement.java b/solver/src/main/java/org/chocosolver/solver/constraints/binary/element/PropElement.java index 70ad7726e2..12885fb819 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/binary/element/PropElement.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/binary/element/PropElement.java @@ -33,65 +33,66 @@ @Explained(ignored = true, comment = "Turned into clauses") public class PropElement extends Propagator { - /** - * Table of values - */ - private final int[] values; + /** + * Table of values + */ + private final int[] values; - /** - * To match indices in {@link #values} and {@link #index} - */ - private final int offset; + /** + * To match indices in {@link #values} and {@link #index} + */ + private final int offset; - /** - * Index variable - */ - private final IntVar index; + /** + * Index variable + */ + private final IntVar index; - /** - * Resulting variable - */ - private final IntVar result; + /** + * Resulting variable + */ + private final IntVar result; - /** - * Set of forbidden indices and possible values - */ - private final IntIterableBitSet fidx, pVals; + /** + * Set of forbidden indices and possible values + */ + private final IntIterableBitSet fidx, pVals; - /** - * Create a propagator which ensures that VALUE = TABLE[INDEX-OFFSET] holds. - * @param value integer variable - * @param values array of ints - * @param index integer variable - * @param offset int - */ - public PropElement(IntVar value, int[] values, IntVar index, int offset) { - super(ArrayUtils.toArray(value, index), PropagatorPriority.BINARY, false); - this.values = values; - this.offset = offset; - this.index = index; - this.result = value; - fidx = new IntIterableBitSet(); - fidx.setOffset(index.getLB()); - pVals = new IntIterableBitSet(); - pVals.setOffset(result.getLB()); - } + /** + * Create a propagator which ensures that VALUE = TABLE[INDEX-OFFSET] holds. + * + * @param value integer variable + * @param values array of ints + * @param index integer variable + * @param offset int + */ + public PropElement(IntVar value, int[] values, IntVar index, int offset) { + super(ArrayUtils.toArray(value, index), PropagatorPriority.BINARY, false); + this.values = values; + this.offset = offset; + this.index = index; + this.result = value; + fidx = new IntIterableBitSet(); + fidx.setOffset(index.getLB()); + pVals = new IntIterableBitSet(); + pVals.setOffset(result.getLB()); + } - @Override - public void propagate(int evtmask) throws ContradictionException { + @Override + public void propagate(int evtmask) throws ContradictionException { index.updateBounds(offset, values.length - 1 + offset, this); fidx.clear(); pVals.clear(); int iub = index.getUB(); for (int i = index.getLB(); i <= iub; i = index.nextValue(i)) { int value = values[i - offset]; - if (result.contains(value)){ + if (result.contains(value)) { pVals.add(value); - }else{ + } else { fidx.add(i); } } - result.removeAllValuesBut(pVals,this); + result.removeAllValuesBut(pVals, this); if (!fidx.isEmpty()) { index.removeValues(fidx, this); } @@ -100,57 +101,59 @@ public void propagate(int evtmask) throws ContradictionException { } } - @Override - public ESat isEntailed() { - if (index.getUB() < offset || index.getLB() >= offset + values.length) { - return ESat.FALSE; - } - if (isCompletelyInstantiated()) { - return ESat.eval(result.contains(values[index.getValue() - offset])); - } else if(result.isInstantiated()){ - int val = result.getValue(); - boolean foundVal = false; - boolean foundOther = false; - for(int i:index){ - if(i>=offset && i=offset && i= offset + values.length) { + return ESat.FALSE; + } + if (isCompletelyInstantiated()) { + return ESat.eval(result.contains(values[index.getValue() - offset])); + } else if (result.isInstantiated()) { + int val = result.getValue(); + boolean foundVal = false; + boolean foundOther = false; + int indexUB = index.getUB(); + for (int i = index.getLB(); i <= indexUB; i = index.nextValue(i)) { + if (i >= offset && i < values.length + offset && values[i - offset] == val) { + foundVal = true; + if (foundOther) break; + } else { + foundOther = true; + if (foundVal) break; + } + } + if (foundVal) { + if (foundOther) { + return ESat.UNDEFINED; + } else { + return ESat.TRUE; + } + } else { + return ESat.FALSE; + } + } else { + int indexUB = index.getUB(); + for (int i = index.getLB(); i <= indexUB; i = index.nextValue(i)) { + if (i >= offset && i < values.length + offset && result.contains(values[i - offset])) { + return ESat.UNDEFINED; + } + } + return ESat.FALSE; + } + } - public String toString() { - StringBuilder sb = new StringBuilder(32); - sb.append("element(").append(this.result).append(" = "); - sb.append(" <"); - int i = 0; - for (; i < Math.min(this.values.length - 1, 5); i++) { - sb.append(this.values[i]).append(", "); - } - if (i == 5 && this.values.length - 1 > 5) sb.append("..., "); - sb.append(this.values[values.length - 1]); - sb.append("> [").append(this.index).append("])"); - return sb.toString(); - } + public String toString() { + StringBuilder sb = new StringBuilder(32); + sb.append("element(").append(this.result).append(" = "); + sb.append(" <"); + int i = 0; + for (; i < Math.min(this.values.length - 1, 5); i++) { + sb.append(this.values[i]).append(", "); + } + if (i == 5 && this.values.length - 1 > 5) sb.append("..., "); + sb.append(this.values[values.length - 1]); + sb.append("> [").append(this.index).append("])"); + return sb.toString(); + } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/Tuples.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/Tuples.java index 3c4d30fd08..6dce0ec1a2 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/Tuples.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/Tuples.java @@ -13,10 +13,8 @@ import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.ESat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; +import java.lang.ref.SoftReference; +import java.util.*; /** * A unique interface to declare tuples for table constraints. @@ -28,235 +26,252 @@ */ public class Tuples { - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** + //*********************************************************************************** + // VARIABLES + //*********************************************************************************** - private final boolean feasible; - protected final List tuples; - private int arity; - private int[] ranges; - private boolean allowStar; - private int star; + private final boolean feasible; + protected final List tuples; + private int arity; + private int[] ranges; + private final boolean allowStar; + private final int star; + private SoftReference cachedMatrix; - //*********************************************************************************** - // CONSTRUCTOR - //*********************************************************************************** + //*********************************************************************************** + // CONSTRUCTOR + //*********************************************************************************** /** * Create a list of tuples which represents all allowed tuples if feasible=true * or a set of forbidden tuples if feasible=false. - * Lately, one can allow the presence of universal values, calling {@link #setUniversalValue(int)}, - * meaning that some variables can take any values from their domain. * - * @param values list of tuples + * @param values list of tuples * @param feasible indicates whether the tuples are allowed or forbidden + * @param universalValue the value of the symbol which denotes that some variables can take any values from their domain */ - public Tuples(int[][] values, boolean feasible) { + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public Tuples(int[][] values, boolean feasible, OptionalInt universalValue) { + if(!feasible && universalValue.isPresent()) { + throw new SolverException("A universal value cannot be used with forbidden tuples"); + } this.feasible = feasible; tuples = new ArrayList<>(); - for(int[] t : values){ + this.allowStar = universalValue.isPresent(); + this.star = universalValue.orElse(0); + for (int[] t : values) { add(t); } } - /** - * Create a list of tuples which represents all allowed tuples if feasible=true - * or a set of forbidden tuples if feasible=false - * - * @param feasible indicates whether the tuples are allowed or forbidden - */ - public Tuples(boolean feasible) { - this.feasible = feasible; - tuples = new ArrayList<>(); - } + /** + * Create an empty tuples which represents all allowed tuples with universal value. + * + * @param universalValue the value of the symbol which denotes that some variables can take any values from their domain + */ + public Tuples(int universalValue) { + this(new int[0][], true, OptionalInt.of(universalValue)); + } + + /** + * Create a list of tuples which represents all allowed tuples if feasible=true + * or a set of forbidden tuples if feasible=false + * + * @param feasible indicates whether the tuples are allowed or forbidden + */ + public Tuples(boolean feasible) { + this(new int[0][], feasible, OptionalInt.empty()); + } - /** - * Create a list of tuples which represents all allowed tuples, i.e. other tuples are forbidden - */ - public Tuples() { - this(true); - } + /** + * Create a list of tuples which represents all allowed tuples, i.e. other tuples are forbidden + */ + public Tuples() { + this(true); + } - //*********************************************************************************** - // METHODS - //*********************************************************************************** + //*********************************************************************************** + // METHODS + //*********************************************************************************** - /** - * One can allow the presence of universal values, - * meaning that some variables can take any values from their domain. - * @param star the universal value that can appear in any tuple. - */ - public void setUniversalValue(int star){ - this.star = star; - this.allowStar = true; - } + /** + * @deprecated the universal value is now set in the constructor + */ + @Deprecated + public void setUniversalValue(int star) { + } - /** - * @return true if the presence of universal values is allowed. - */ - public boolean allowUniversalValue(){ - return this.allowStar; - } + /** + * @return true if the presence of universal values is allowed. + */ + public boolean allowUniversalValue() { + return this.allowStar; + } - /** - * @return the value of the symbol which denotes that - * some variables can take any values from their domain. - */ - public int getStarValue(){ - assert allowUniversalValue(); - return this.star; - } + /** + * @return the value of the symbol which denotes that + * some variables can take any values from their domain. + */ + public int getStarValue() { + assert allowUniversalValue(); + return this.star; + } - /** - * Checks entailment of a table constraint over vars with this Tuples object - * @param vars set of integer variables to test - * @return an ESat object indicating the entailement of the table over vars and this - */ - public ESat check(IntVar... vars) { - if(nbTuples() == 0){ - return isFeasible()? ESat.FALSE: ESat.TRUE; - } - if(vars.length != arity){ - throw new SolverException("The given variable array does not match the arity: " + arity); - } - int[] values = new int[vars.length]; - for (int i=0;i(matrix); + } + return cachedMatrix.get(); + } - public void sort() { - tuples.sort(new TupleComparator()); - } + public void sort() { + tuples.sort(new TupleComparator()); + } - private static class TupleComparator implements Comparator { + private static class TupleComparator implements Comparator { - @Override - public int compare(int[] o1, int[] o2) { - int i = 0; - int l = o1.length; - while (i < l && o1[i] == o2[i]) { - i++; - } - return (i == l ? 0 : o1[i] - o2[i]); - } - } + @Override + public int compare(int[] o1, int[] o2) { + int i = 0; + int l = o1.length; + while (i < l && o1[i] == o2[i]) { + i++; + } + return (i == l ? 0 : o1[i] - o2[i]); + } + } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/TuplesFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/TuplesFactory.java index b7bb822503..c6268a1b9e 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/TuplesFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/TuplesFactory.java @@ -102,6 +102,70 @@ public static Tuples generateTuples(TupleValidator filter, boolean feasible, Int } + /** + * A method that generates all tuples from a set of variables and stores (and returns) the valid tuples wrt to the filter. + * One may keep in mind that tuples generation directly depends on the product of domain cardinality, but also on the algorithm defines in the filter. + * + * @param filter tuple validator + * @param vars concerned variables + * @return two Tuples objects, the first one contains the valid tuples and the second one contains the invalid tuples + */ + public static Tuples[] generateTuples(TupleValidator filter, IntVar... vars) { + Tuples[] tuples = new Tuples[2]; + tuples[0] = new Tuples(true); + tuples[1] = new Tuples(false); + int n = vars.length; + int[] cvalue = new int[n]; + int[] t = new int[n]; + for (int j = 0; j < n; j++) { + t[j] = cvalue[j] = vars[j].getLB(); + } + while (true) { + if (filter.valid(t)) { + tuples[0].add(t.clone()); + } else { + tuples[1].add(t.clone()); + } + int j; + for (j = 0; j < n; j++) { + int v = t[j] = cvalue[j] = vars[j].nextValue(cvalue[j]); + if (v < Integer.MAX_VALUE) { + break; + } + t[j] = cvalue[j] = vars[j].getLB(); + } + if (j == n) break; + } + return tuples; + + } + + /** + * Randomly generate n tuples from vars and return the number of tuples that are valid according to filter + * as the ratio of valid tuples over the total number of tuples. + * @param n number of tuples to generate + * @param filter tuple validator + * @param vars concerned variables + * @return the ratio of valid tuples over the total number of tuples + */ + public static double sample(int n, Random rnd, TupleValidator filter, IntVar... vars){ + int valid = 0; + for (int i = 0; i < n; i++) { + int[] t = new int[vars.length]; + for (int j = 0; j < vars.length; j++) { + int v = vars[j].getLB() + rnd.nextInt(vars[j].getUB() - vars[j].getLB() + 1); + while(!vars[j].contains(v)){ + v = vars[j].getLB() + rnd.nextInt(vars[j].getUB() - vars[j].getLB() + 1); + } + t[j] = v; + } + if (filter.valid(t)) { + valid++; + } + } + return ((double) valid) / n; + } + /** * A method that randomly generates tuples from a set of variables. * @@ -120,7 +184,7 @@ public static Tuples generateTuples(TupleValidator filter, boolean feasible, Int *

      */ public static Tuples randomTuples(final double proba, final Random random, IntVar... vars) { - return generateTuples(vs -> random.nextDouble() < proba, true,vars); + return generateTuples(vs -> random.nextDouble() < proba, true, vars); } // BEWARE: PLEASE, keep signatures sorted by increasing arity and alphabetical order!! diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTable.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTable.java index cdd1a946e3..f21114829a 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTable.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTable.java @@ -9,6 +9,7 @@ */ package org.chocosolver.solver.constraints.extension.nary; +import gnu.trove.map.hash.TIntObjectHashMap; import org.chocosolver.memory.IEnvironment; import org.chocosolver.memory.IStateInt; import org.chocosolver.memory.IStateLong; @@ -20,8 +21,13 @@ import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.delta.IIntDeltaMonitor; import org.chocosolver.solver.variables.events.PropagatorEventType; +import org.chocosolver.solver.variables.view.IntView; import org.chocosolver.util.ESat; import org.chocosolver.util.procedure.UnaryIntProcedure; +import org.chocosolver.util.tools.VariableUtils; + +import java.util.HashSet; +import java.util.LinkedHashSet; /** * Propagator for table constraint based on "Compact-Table: Efficiently Filtering Table Constraints @@ -34,22 +40,120 @@ @Explained(ignored = true, comment = "Turned into clauses") public class PropCompactTable extends Propagator { + /** + * This interface is used to create a support structure for the table constraint. + */ + protected interface ISupport { + + /** + * Factory method to create a support structure. + * The choice of the support structure depends on the range and the number of values. + * @param range the range of the variable, that is the difference between the upper and lower bounds. + * @param nValues the number of values in the domain of the variable. + * @param nWords the number of words in the bitset. + * @return a support structure. + */ + static ISupport make(int range, int nValues, int nWords){ + if(range > 256 && range >= 1.1 * nValues){ + // If the variable seems sparse, we use a sparse support structure + return new SparseSupport(nValues, nWords); + } else { + // If the variable seems dense, we use a dense support structure + return new DenseSupport(range, nWords); + } + } + + /** + * Get the support for a given value. + * @param val the value for which we want the support. + * @return the support for the given value. + */ + long[] get(int val); + } + + /** + * Dense support structure. + * It is used when the variable is dense, that is when the range is equal to the number of values. + * In that case, we use a simple array of long arrays to store the supports. + */ + protected final static class DenseSupport implements ISupport { + private final long[][] supports; + + public DenseSupport(int range, int nWords) { + this.supports = new long[range][nWords]; + } + + @Override + public long[] get(int val) { + return supports[val]; + } + } + + /** + * Sparse support structure. + * It is used when the variable is sparse, that is when the range is greater than the number of values. + * In that case, we use a hash map to store the supports. + */ + protected final static class SparseSupport implements ISupport { + private final TIntObjectHashMap map; + private final int nWords; + + public SparseSupport(int nValues, int nWords) { + this.nWords = nWords; + this.map = new TIntObjectHashMap<>(nValues, 1.1f, -1); + } + + @Override + public long[] get(int val) { + long[] m = map.get(val); + if(m == null){ + m = new long[nWords]; + map.put(val, m); + } + return m; + } + } + //*********************************************************************************** // VARIABLES //*********************************************************************************** RSparseBitSet currTable; protected Tuples tuples; // only for checker - protected long[][][] supports; + protected ISupport[] supports; int[][] residues; protected int[] offset; protected IIntDeltaMonitor[] monitors; - private final UnaryIntProcedure onValRem; - + protected final UnaryIntProcedure onValRem; + protected final boolean uniqueness; + protected final boolean invariant; //*********************************************************************************** // CONSTRUCTOR //*********************************************************************************** + /** + * This method ensures that among the variables, there is no duplicate variable nor + * views of scope's variables . + * + * @param vars scope + * @return true if all variables are unique and not views of each other + */ + private static boolean uniqueness(IntVar[] vars) { + HashSet set = new LinkedHashSet<>(); + for (IntVar var : vars) { + if (VariableUtils.isView(var)) { + //noinspection unchecked + IntView view = (IntView) var; + var = view.getVariables()[0]; + } + if (set.contains(var)) { + return false; + } + set.add(var); + } + return true; + } + /** * Create a propagator for table constraint Only for feasible Tuples * @@ -57,7 +161,7 @@ public class PropCompactTable extends Propagator { * @param tuples list of feasible tuples */ public PropCompactTable(IntVar[] vars, Tuples tuples) { - super(vars, PropagatorPriority.QUADRATIC, true); + super(vars, vars.length <= 3 ? PropagatorPriority.get(vars.length) : PropagatorPriority.QUADRATIC, true, tuples.allowUniversalValue()); this.tuples = tuples; this.currTable = new RSparseBitSet(model.getEnvironment(), this.tuples.nbTuples()); computeSupports(tuples); @@ -66,6 +170,8 @@ public PropCompactTable(IntVar[] vars, Tuples tuples) { monitors[i] = vars[i].monitorDelta(this); } onValRem = makeProcedure(); + uniqueness = uniqueness(vars); + invariant = uniqueness && !tuples.allowUniversalValue(); } //*********************************************************************************** @@ -86,7 +192,7 @@ public UnaryIntProcedure set(Integer o) { @Override public void execute(int i) { - currTable.addToMask((supports[var][i - off])); + currTable.addToMask((supports[var].get(i - off))); } }; } @@ -94,18 +200,18 @@ public void execute(int i) { protected void computeSupports(Tuples tuples) { int n = vars.length; offset = new int[n]; - supports = new long[n][][]; + supports = new ISupport[n]; residues = new int[n][]; long[] tmp; for (int i = 0; i < n; i++) { int lb = vars[i].getLB(); int ub = vars[i].getUB(); offset[i] = lb; - supports[i] = new long[ub - lb + 1][currTable.words.length]; + supports[i] = ISupport.make(vars[i].getRange(), vars[i].getDomainSize(), currTable.words.length); residues[i] = new int[ub - lb + 1]; } int wI = 0; - byte bI = 63; + byte bI = 0; top: for (int ti = 0; ti < tuples.nbTuples(); ti++) { int[] tuple = tuples.get(ti); @@ -115,11 +221,11 @@ protected void computeSupports(Tuples tuples) { } } for (int i = 0; i < tuple.length; i++) { - tmp = supports[i][tuple[i] - offset[i]]; + tmp = supports[i].get(tuple[i] - offset[i]); tmp[wI] |= 1L << (bI); } - if (--bI < 0) { - bI = 63; + if (++bI > 63) { + bI = 0; wI++; } } @@ -136,7 +242,7 @@ public void propagate(int evtmask) throws ContradictionException { currTable.clearMask(); int ub = vars[i].getUB(); for (int v = vars[i].getLB(); v <= ub; v = vars[i].nextValue(v)) { - currTable.addToMask(supports[i][v - offset[i]]); + currTable.addToMask(supports[i].get(v - offset[i])); } currTable.intersectWithMask(); } @@ -150,13 +256,17 @@ public void propagate(int evtmask) throws ContradictionException { @Override public void propagate(int vIdx, int mask) throws ContradictionException { currTable.clearMask(); - if (vars[vIdx].getDomainSize() > monitors[vIdx].sizeApproximation()) { - monitors[vIdx].forEachRemVal(onValRem.set(vIdx)); - currTable.reverseMask(); + if (vars[vIdx].isInstantiated()) { + currTable.addToMask(supports[vIdx].get(vars[vIdx].getValue() - offset[vIdx])); } else { - int ub = vars[vIdx].getUB(); - for (int v = vars[vIdx].getLB(); v <= ub; v = vars[vIdx].nextValue(v)) { - currTable.addToMask(supports[vIdx][v - offset[vIdx]]); + if (vars[vIdx].getDomainSize() > monitors[vIdx].sizeApproximation()) { + monitors[vIdx].forEachRemVal(onValRem.set(vIdx)); + currTable.reverseMask(); + } else { + int ub = vars[vIdx].getUB(); + for (int v = vars[vIdx].getLB(); v <= ub; v = vars[vIdx].nextValue(v)) { + currTable.addToMask(supports[vIdx].get(v - offset[vIdx])); + } } } currTable.intersectWithMask(); @@ -166,15 +276,23 @@ public void propagate(int vIdx, int mask) throws ContradictionException { forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); } - private void filterDomains() throws ContradictionException { - if (currTable.isEmpty()) {// to keep as we skip instantiated vars + protected void filterDomains() throws ContradictionException { + long count = currTable.nb1s(); + if (count == 0) { // Invariant 3.4 (Invariant 7.8 for CT*) fails(); - } - for (int i = 0; i < vars.length; i++) { - if (vars[i].hasEnumeratedDomain()) { - enumFilter(i); - } else { - boundFilter(i); + } else { + // --> does not work with views + if (invariant && count == VariableUtils.domainCardinality(vars)) { // Invariant 3.3, only for positive table + setPassive(); + return; + } + for (int i = 0; i < vars.length; i++) { + if (uniqueness && vars[i].isInstantiated()) continue; // --> does not work with views + if (vars[i].hasEnumeratedDomain()) { + enumFilter(i); + } else { + boundFilter(i); + } } } } @@ -184,8 +302,8 @@ private void boundFilter(int i) throws ContradictionException { int ub = vars[i].getUB(); for (int v = lb; v <= ub; v++) { int index = residues[i][v - offset[i]]; - if ((currTable.words[index].get() & supports[i][v - offset[i]][index]) == 0L) { - index = currTable.intersectIndex(supports[i][v - offset[i]]); + if ((currTable.words[index].get() & supports[i].get(v - offset[i])[index]) == 0L) { + index = currTable.intersectIndex(supports[i].get(v - offset[i])); if (index == -1) { lb++; } else { @@ -199,8 +317,8 @@ private void boundFilter(int i) throws ContradictionException { vars[i].updateLowerBound(lb, this); for (int v = ub; v >= lb; v--) { int index = residues[i][v - offset[i]]; - if ((currTable.words[index].get() & supports[i][v - offset[i]][index]) == 0L) { - index = currTable.intersectIndex(supports[i][v - offset[i]]); + if ((currTable.words[index].get() & supports[i].get(v - offset[i])[index]) == 0L) { + index = currTable.intersectIndex(supports[i].get(v - offset[i])); if (index == -1) { ub--; } else { @@ -218,8 +336,8 @@ private void enumFilter(int i) throws ContradictionException { int ub = vars[i].getUB(); for (int v = vars[i].getLB(); v <= ub; v = vars[i].nextValue(v)) { int index = residues[i][v - offset[i]]; - if ((currTable.words[index].get() & supports[i][v - offset[i]][index]) == 0L) { - index = currTable.intersectIndex(supports[i][v - offset[i]]); + if ((currTable.words[index].get() & supports[i].get(v - offset[i])[index]) == 0L) { + index = currTable.intersectIndex(supports[i].get(v - offset[i])); if (index == -1) { vars[i].removeValue(v, this); } else { @@ -239,6 +357,14 @@ public ESat isEntailed() { // RSparseBitSet //*********************************************************************************** + /** + * A reversible sparse bitset. + * + * @implNote An equivalent implementation of this class but relying on IStateBitSet was evaluated + * but the bottleneck was the calls to nextSetBit and nextClearBit which are called a lot in + * the #clearMask and #intersectWithMask methods. + */ + protected static class RSparseBitSet { protected IStateLong[] words; private final int[] index; @@ -258,7 +384,7 @@ protected RSparseBitSet(IEnvironment environment, int nbBits) { } } - private boolean isEmpty() { + protected boolean isEmpty() { return limit.get() == -1; } @@ -283,22 +409,43 @@ protected void addToMask(long[] wordsToAdd) { } } - private void intersectWithMask() { - for (int i = limit.get(); i >= 0; i--) { + protected void intersectWithMask() { + int l = this.limit.get(); + for (int i = l; i >= 0; i--) { + int offset = index[i]; + long wo = words[offset].get(); + long w = wo & mask[offset]; + if (wo != w) { + words[offset].set(w); + if (w == 0L) { + index[i] = index[l]; + index[l] = offset; + l--; + } + } + } + limit.set(l); + } + + protected void intersectWithNotMask() { + int l = this.limit.get(); + for (int i = l; i >= 0; i--) { int offset = index[i]; - long w = words[offset].get() & mask[offset]; - if (words[offset].get() != w) { + long wo = words[offset].get(); + long w = wo & ~mask[offset]; + if (wo != w) { words[offset].set(w); if (w == 0L) { - index[i] = index[limit.get()]; - index[limit.get()] = offset; - limit.add(-1); + index[i] = index[l]; + index[l] = offset; + l--; } } } + this.limit.set(l); } - private int intersectIndex(long[] m) { + protected int intersectIndex(long[] m) { for (int i = limit.get(); i >= 0; i--) { int offset = index[i]; if ((words[offset].get() & m[offset]) != 0L) { @@ -307,5 +454,23 @@ private int intersectIndex(long[] m) { } return -1; } + + protected int nb1s() { + int cnt = 0; + for (int i = limit.get(); i >= 0; i--) { + int offset = index[i]; + cnt += Long.bitCount(words[offset].get()); + } + return cnt; + } + + protected long nb1s(long[] m) { + long cnt = 0; + for (int i = limit.get(); i >= 0; i--) { + int offset = index[i]; + cnt += Long.bitCount(words[offset].get() & m[offset]); + } + return cnt; + } } } \ No newline at end of file diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableNeg.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableNeg.java new file mode 100644 index 0000000000..2666fc515a --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableNeg.java @@ -0,0 +1,139 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.extension.nary; + +import org.chocosolver.solver.constraints.Explained; +import org.chocosolver.solver.constraints.extension.Tuples; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.events.PropagatorEventType; +import org.chocosolver.util.tools.VariableUtils; + +/** + * Propagator for table constraint based on + * "Extending Compact-Table to Negative and Short Tables", + * H. Verhaeghe and C. Lecoutre and P. Schauss, AAAI-17. + * It deals with negative tuples. + * + * @author Charles Prud'homme + * @since 05/05/2025 + */ +@Explained(ignored = true, comment = "Turned into clauses") +public final class PropCompactTableNeg extends PropCompactTable { + + //*********************************************************************************** + // CONSTRUCTOR + //*********************************************************************************** + + /** + * Create a propagator for table constraint Only for infeasible Tuples + * + * @param vars scope + * @param tuples list of feasible tuples + */ + public PropCompactTableNeg(IntVar[] vars, Tuples tuples) { + super(vars, tuples); + assert !tuples.isFeasible(); + } + + //*********************************************************************************** + // FILTERING + //*********************************************************************************** + + @Override + public void propagate(int vIdx, int mask) throws ContradictionException { + currTable.clearMask(); + if (vars[vIdx].isInstantiated()) { + currTable.addToMask(supports[vIdx].get(vars[vIdx].getValue() - offset[vIdx])); + } else if (vars[vIdx].getDomainSize() > monitors[vIdx].sizeApproximation()) { + monitors[vIdx].forEachRemVal(onValRem.set(vIdx)); + currTable.reverseMask(); + } else { + int ub = vars[vIdx].getUB(); + for (int v = vars[vIdx].getLB(); v <= ub; v = vars[vIdx].nextValue(v)) { + currTable.addToMask(supports[vIdx].get(v - offset[vIdx])); + } + } + currTable.intersectWithMask(); + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); + } + + + @Override + protected void filterDomains() throws ContradictionException { + long count = currTable.nb1s(); + if (count == 0) { // Invariant 8.4 + setPassive(); + } else { + // check for views + long initThreshold = VariableUtils.domainCardinality(vars); + if (invariant && count == initThreshold) { // Invariant 8.4 + fails(); + } + currTable.clearMask(); + for (int i = 0; i < vars.length; i++) { + if (uniqueness && vars[i].isInstantiated()) continue; + if (vars[i].hasEnumeratedDomain()) { + enumFilter(i, initThreshold); + } else { + boundFilter(i, initThreshold); + } + } + currTable.intersectWithNotMask(); + } + } + + private void enumFilter(int i, long initThreshold) throws ContradictionException { + int ub = vars[i].getUB(); + int domSize = vars[i].getDomainSize(); + long threshold = initThreshold / domSize; + for (int v = vars[i].getLB(); v <= ub; v = vars[i].nextValue(v)) { + long nb1s = currTable.nb1s(supports[i].get(v - offset[i])); + if (nb1s == threshold) { + vars[i].removeValue(v, this); + currTable.addToMask(supports[i].get(v - offset[i])); + domSize--; + threshold = initThreshold / domSize; + } + } + } + + private void boundFilter(int i, long initThreshold) throws ContradictionException { + int lb = vars[i].getLB(); + int ub = vars[i].getUB(); + int domSize = vars[i].getDomainSize(); + long threshold = initThreshold / domSize; + for (int v = lb; v <= ub; v++) { + long nb1s = currTable.nb1s(supports[i].get(v - offset[i])); + if (nb1s == threshold) { + lb++; + currTable.addToMask(supports[i].get(v - offset[i])); + domSize--; + threshold = initThreshold / domSize; + } else { + break; + } + } + vars[i].updateLowerBound(lb, this); + for (int v = ub; v >= lb; v--) { + long nb1s = currTable.nb1s(supports[i].get(v - offset[i])); + if (nb1s == threshold) { + ub--; + currTable.addToMask(supports[i].get(v - offset[i])); + domSize--; + threshold = initThreshold / domSize; + } else { + break; + } + } + vars[i].updateUpperBound(ub, this); + } + +} \ No newline at end of file diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableStar.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableStar.java index c4e272a02c..fecd238d1e 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableStar.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropCompactTableStar.java @@ -26,13 +26,13 @@ * @since 16/05/2017 */ @Explained(ignored = true, comment = "Turned into clauses") -public class PropCompactTableStar extends PropCompactTable { +public final class PropCompactTableStar extends PropCompactTable { //*********************************************************************************** // VARIABLES //*********************************************************************************** - private long[][][] inc_supports; + private ISupport[] inc_supports; //*********************************************************************************** // CONSTRUCTOR @@ -53,12 +53,13 @@ public PropCompactTableStar(IntVar[] vars, Tuples tuples) { // INITIALIZATION //*********************************************************************************** + @Override protected UnaryIntProcedure makeProcedure() { return new UnaryIntProcedure() { int var, off; @Override - public UnaryIntProcedure set(Integer o) { + public UnaryIntProcedure set(Integer o) { var = o; off = offset[var]; return this; @@ -67,27 +68,28 @@ public UnaryIntProcedure set(Integer o) { @Override public void execute(int i) throws ContradictionException { // main reason we re-wrote the class - currTable.addToMask((inc_supports[var][i - off])); + currTable.addToMask((inc_supports[var].get(i - off))); } }; } + @Override protected void computeSupports(Tuples tuples) { int n = vars.length; offset = new int[n]; - supports = new long[n][][]; - inc_supports = new long[n][][]; + supports = new ISupport[n]; + inc_supports = new ISupport[n]; residues = new int[n][]; for (int i = 0; i < n; i++) { int lb = vars[i].getLB(); int ub = vars[i].getUB(); offset[i] = lb; - supports[i] = new long[ub - lb + 1][currTable.words.length]; - inc_supports[i] = new long[ub - lb + 1][currTable.words.length]; + supports[i] = ISupport.make(vars[i].getRange(), vars[i].getDomainSize(), currTable.words.length); + inc_supports[i] = ISupport.make(vars[i].getRange(), vars[i].getDomainSize(), currTable.words.length); residues[i] = new int[ub - lb + 1]; } int wI = 0; - byte bI = 63; + byte bI = 0; int star = tuples.getStarValue(); top: for (int ti = 0; ti < tuples.nbTuples(); ti++) { @@ -101,17 +103,17 @@ protected void computeSupports(Tuples tuples) { for (int i = 0; i < tuple.length; i++) { int val = tuple[i]; if (val != star) { - supports[i][val - offset[i]][wI] |= index; - inc_supports[i][val - offset[i]][wI] |= index; + supports[i].get(val - offset[i])[wI] |= index; + inc_supports[i].get(val - offset[i])[wI] |= index; } else { - int u = supports[i].length + offset[i]; + int u = vars[i].getUB(); for (val = offset[i]; val <= u; val = vars[i].nextValue(val)) { - supports[i][val - offset[i]][wI] |= index; + supports[i].get(val - offset[i])[wI] |= index; } } } - if (--bI < 0) { - bI = 63; + if (++bI > 63) { + bI = 0; wI++; } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropLargeMDDC.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropLargeMDDC.java index efc87ab003..73fab020f1 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropLargeMDDC.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropLargeMDDC.java @@ -9,8 +9,6 @@ */ package org.chocosolver.solver.constraints.extension.nary; -import gnu.trove.set.TIntSet; -import gnu.trove.set.hash.TIntHashSet; import org.chocosolver.solver.constraints.Propagator; import org.chocosolver.solver.constraints.PropagatorPriority; import org.chocosolver.solver.exception.ContradictionException; @@ -19,6 +17,8 @@ import org.chocosolver.util.objects.StoredSparseSet; import org.chocosolver.util.objects.graphs.MultivaluedDecisionDiagram; +import java.util.BitSet; + /** * Implementation based on "Maintaining GAC on adhoc r-ary constraints", Cheng and Yap, CP12. *

      @@ -27,8 +27,8 @@ */ public class PropLargeMDDC extends Propagator { - private final TIntSet yes; - private final TIntSet[] sets; + private final BitSet yes; + private final BitSet[] sets; private final StoredSparseSet no; private final MultivaluedDecisionDiagram MDD; private final int nvars; @@ -44,11 +44,11 @@ public PropLargeMDDC(MultivaluedDecisionDiagram MDD, IntVar... VARS) { super(VARS, PropagatorPriority.QUADRATIC, false); this.MDD = MDD; this.nvars = vars.length; - this.yes = new TIntHashSet(); + this.yes = new BitSet(); this.no = new StoredSparseSet(VARS[0].getEnvironment()); - this.sets = new TIntHashSet[nvars]; + this.sets = new BitSet[nvars]; for (int i = 0; i < nvars; i++) { - this.sets[i] = new TIntHashSet(vars[i].getDomainSize()); + this.sets[i] = new BitSet(vars[i].getDomainSize()); } } @@ -80,22 +80,25 @@ private void mddc() throws ContradictionException { int o = MDD.getOffset(i); int UB = vars[i].getUB(); for (int j = vars[i].getLB(); j <= UB; j = vars[i].nextValue(j)) { - sets[i].add(j - o); + sets[i].set(j - o); } } mddcSeekSupport(0, 0); for (int i = 0; i < nvars; i++) { int o = MDD.getOffset(i); - int[] values = sets[i].toArray(); - for (int j = 0; j < values.length; j++) { - vars[i].removeValue(values[j] + o, this); +// int[] values = sets[i].toArray(); +// for (int j = 0; j < values.length; j++) { +// vars[i].removeValue(values[j] + o, this); +// } + for (int v = sets[i].nextSetBit(0); v > -1; v = sets[i].nextSetBit(v + 1)) { + vars[i].removeValue(v + o, this); } } } private boolean mddcSeekSupport(int node, int layer) { // If the node has already been visited - if (yes.contains(node)) return true; + if (yes.get(node)) return true; if (no.contains(node)) return false; // otherwise ... boolean res = false; @@ -107,7 +110,7 @@ private boolean mddcSeekSupport(int node, int layer) { if (sG != MultivaluedDecisionDiagram.EMPTY && vars[layer].contains(i + o) && (sG == MultivaluedDecisionDiagram.TERMINAL || mddcSeekSupport(sG, layer + 1))) { res = true; - sets[layer].remove(i); + sets[layer].clear(i); int l2 = layer; while (l2 < nvars && sets[l2].isEmpty()) { l2++; @@ -116,7 +119,7 @@ private boolean mddcSeekSupport(int node, int layer) { } } if (res) { - yes.add(node); + yes.set(node); } else { no.add(node); } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropTableStr2.java b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropTableStr2.java index f2baaf2db2..b8f20a63ff 100755 --- a/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropTableStr2.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/extension/nary/PropTableStr2.java @@ -19,9 +19,7 @@ import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.ESat; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.SetFactory; -import org.chocosolver.util.objects.setDataStructures.SetType; +import org.chocosolver.util.tools.ArrayUtils; import java.util.ArrayList; import java.util.BitSet; @@ -41,11 +39,11 @@ public class PropTableStr2 extends Propagator { private final int[][] table; private final Str2_var[] str2vars; - private final ISet tuples; + private final int[] tuples; + private final IStateInt idx; private final ArrayList ssup; private final ArrayList sval; private boolean firstProp = true; - private final Tuples tuplesObject; private final int star; //*********************************************************************************** @@ -53,9 +51,8 @@ public class PropTableStr2 extends Propagator { //*********************************************************************************** public PropTableStr2(IntVar[] vars_, Tuples tuplesObject) { - super(vars_, PropagatorPriority.LINEAR, false); + super(vars_, PropagatorPriority.QUADRATIC, false); this.table = tuplesObject.toMatrix(); - this.tuplesObject = tuplesObject; int size = 0; if (table.length > 0) { @@ -68,9 +65,11 @@ public PropTableStr2(IntVar[] vars_, Tuples tuplesObject) { max = Math.max(max, vars_[i].getUB()); } this.star = tuplesObject.allowUniversalValue() ? tuplesObject.getStarValue() : max + 1; - tuples = SetFactory.makeStoredSet(SetType.BIPARTITESET, 0, model); + tuples = ArrayUtils.array(0, table.length); + idx = model.getEnvironment().makeInt(table.length); ssup = new ArrayList<>(); sval = new ArrayList<>(); + initializeSupports(); } //*********************************************************************************** @@ -82,31 +81,30 @@ public void propagate(int evtmask) throws ContradictionException { if (firstProp) { firstProp = false; model.getEnvironment().save(() -> firstProp = true); - initialPropagate(); + if (idx.get() == 0) { + this.fails(); + } } Filter(); } @Override public ESat isEntailed() { - if (firstProp) { // data structure not ready - return tuplesObject.check(vars); - } else { - boolean hasSupport = false; - for (int tuple : tuples) { - if (is_tuple_supported(tuple)) { - hasSupport = true; - } + boolean hasSupport = false; + for (int i = 0; i < idx.get(); i++) { + int tuple = tuples[i]; + if (is_tuple_supported(tuple)) { + hasSupport = true; } - if (hasSupport) { - if (isCompletelyInstantiated()) { - return ESat.TRUE; - } else { - return ESat.UNDEFINED; - } + } + if (hasSupport) { + if (isCompletelyInstantiated()) { + return ESat.TRUE; } else { - return ESat.FALSE; + return ESat.UNDEFINED; } + } else { + return ESat.FALSE; } } @@ -122,26 +120,23 @@ public String toString() { private boolean is_tuple_supported(int tuple_index) { for (int i = 0; i < sval.size(); i++) { Str2_var v = sval.get(i); - if (table[tuple_index][v.index] != star && - !v.var.contains(table[tuple_index][v.index])) { + int val = table[tuple_index][v.index]; + if (val != star && !v.var.contains(val)) { return false; } } return true; } - private void initialPropagate() throws ContradictionException { - for (int t = 0; t < table.length; t++) { - tuples.add(t); - } - if (tuples.isEmpty()) { - this.fails(); - } + /** + * Initialize the supports of all values of all variables + */ + private void initializeSupports() { + prepare(); + checkTuples(); } - private void Filter() throws ContradictionException { - ssup.clear(); - sval.clear(); + private void prepare() { for (int i = 0; i < str2vars.length; i++) { Str2_var tmp = str2vars[i]; ssup.add(tmp); @@ -151,7 +146,12 @@ private void Filter() throws ContradictionException { tmp.last_size.set(tmp.cnt); } } - for (int tuple : tuples) { + } + + private void checkTuples() { + int sz = idx.get(); + for (int i = 0; i < sz; i++) { + int tuple = tuples[i]; if (is_tuple_supported(tuple)) { for (int var = 0; var < ssup.size(); var++) { Str2_var v = ssup.get(var); @@ -171,9 +171,19 @@ private void Filter() throws ContradictionException { } } } else { - tuples.remove(tuple); + sz--; + tuples[i] = tuples[sz]; + tuples[sz] = tuple; + i--; } } + idx.set(sz); + } + + private void Filter() throws ContradictionException { + ssup.clear(); + sval.clear(); + initializeSupports(); for (int i = 0; i < ssup.size(); i++) { ssup.get(i).remove_unsupported_value(this); } @@ -202,7 +212,7 @@ private static class Str2_var { */ private int offset; /** - * Count the number of value to remove + * Count the number of values to remove */ private int cnt; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/graph/connectivity/PropConnected.java b/solver/src/main/java/org/chocosolver/solver/constraints/graph/connectivity/PropConnected.java index 0b313b2abf..6fe4211c9a 100755 --- a/solver/src/main/java/org/chocosolver/solver/constraints/graph/connectivity/PropConnected.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/graph/connectivity/PropConnected.java @@ -82,10 +82,10 @@ public void propagate(int evtmask) throws ContradictionException { // 2 --- enforce articulation points and bridges that link two mandatory nodes helper.computeMandatoryArticulationPointsAndBridges(); - for(int ap:helper.getArticulationPoints()) { + for (int ap : helper.getArticulationPoints()) { g.enforceNode(ap, this); } - for(int[] bridge:helper.getBridges()) { + for (int[] bridge : helper.getBridges()) { g.enforceEdge(bridge[0], bridge[1], this); } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/AllDifferent.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/AllDifferent.java index e9bebf453f..ff05458bbc 100755 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/AllDifferent.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/AllDifferent.java @@ -24,13 +24,19 @@ */ public class AllDifferent extends Constraint { - public static final String AC = "AC"; - public static final String AC_REGIN = "AC_REGIN"; - public static final String AC_ZHANG = "AC_ZHANG"; - public static final String BC = "BC"; - public static final String FC = "FC"; - public static final String NEQS = "NEQS"; - public static final String DEFAULT = "DEFAULT"; + public enum Consistency { + AC, + AC_REGIN, + AC_ZHANG, + AC_CLASSIC, + AC_COMPLEMENT, + AC_PARTIAL, + AC_TUNED, + BC, + FC, + NEQS, + DEFAULT + } public AllDifferent(IntVar[] vars, String type) { super(ConstraintsName.ALLDIFFERENT, createPropagators(vars, type)); @@ -40,22 +46,25 @@ private static Propagator[] createPropagators(IntVar[] VARS, String consistency) Model model = VARS[0].getModel(); if (model.getSolver().isLCG()) { String message = ""; - if (consistency.equals("AC") || consistency.equals("AC_ZHANG")) { - consistency = "AC_REGIN"; - message = "Warning: Adjust consistency level of AllDifferent to \"AC_REGIN\" due to LCG resolution."; + if (consistency.equals("AC_ZHANG")) { + consistency = "AC_TUNED"; + message = "Warning: Adjust consistency level of AllDifferent from \"AC_ZHANG\" to " + + "\"AC_TUNED\" due to LCG."; } boolean allEnum = Arrays.stream(VARS).allMatch(IntVar::hasEnumeratedDomain); if (!allEnum) { - if (consistency.equals("AC_REGIN") || consistency.equals("DEFAULT")) { + if (consistency.startsWith("AC") || consistency.equals("DEFAULT")) { consistency = "BC"; - message = "Warning: Adjust consistency level of AllDifferent to \"BC\" due to LCG resolution."; + message = "Warning: Adjust consistency level of AllDifferent to \"BC\" due to LCG" + + "because not all variables are enumerated."; } } if (!message.isEmpty() && model.getSettings().warnUser()) { model.getSolver().log().white().println(message); } } - switch (consistency) { + Consistency choice = Consistency.valueOf(consistency); + switch (choice) { case NEQS: { int s = VARS.length; int k = 0; @@ -71,24 +80,23 @@ private static Propagator[] createPropagators(IntVar[] VARS, String consistency) return new Propagator[]{new PropAllDiffInst(VARS)}; case BC: return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffBC(VARS)}; - case AC_REGIN: - return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffAC(VARS, false)}; case AC: + case AC_REGIN: case AC_ZHANG: - return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffAC(VARS, true)}; + case AC_CLASSIC: + case AC_COMPLEMENT: + case AC_PARTIAL: + case AC_TUNED: + return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffAC(VARS, choice)}; case DEFAULT: default: { // adds a Probabilistic AC (only if at least some variables have an enumerated domain) - boolean enumDom = false; - for (int i = 0; i < VARS.length && !enumDom; i++) { - if (VARS[i].hasEnumeratedDomain()) { - enumDom = true; - } - } - if (enumDom) { - return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffBC(VARS), new PropAllDiffAdaptative(VARS)}; + boolean allEnum = Arrays.stream(VARS).allMatch(IntVar::hasEnumeratedDomain); + if (allEnum) { + return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffBC(VARS), + new PropAllDiffAdaptative(VARS, Consistency.valueOf("AC_TUNED"))}; } else { - return createPropagators(VARS, "BC"); + return new Propagator[]{new PropAllDiffInst(VARS), new PropAllDiffBC(VARS)}; } } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAC.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAC.java index 2f970ea864..7235662f3c 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAC.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAC.java @@ -14,6 +14,8 @@ import org.chocosolver.solver.constraints.PropagatorPriority; import org.chocosolver.solver.constraints.nary.alldifferent.algo.AlgoAllDiffAC; import org.chocosolver.solver.constraints.nary.alldifferent.algo.AlgoAllDiffACFast; +import org.chocosolver.solver.constraints.nary.alldifferent.algo.AlgoAllDiffBimodal; +import org.chocosolver.solver.constraints.nary.alldifferent.algo.IAlldifferentAlgorithm; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.ESat; @@ -30,14 +32,14 @@ * * @author Jean-Guillaume Fages */ -@Explained(partial = true, comment = "Fast algorithm not explained") +@Explained(partial = true, comment = "AC_ZHANG not explained") public class PropAllDiffAC extends Propagator { //*********************************************************************************** // VARIABLES //*********************************************************************************** - protected AlgoAllDiffAC filter; + protected IAlldifferentAlgorithm filter; //*********************************************************************************** // CONSTRUCTORS @@ -48,12 +50,21 @@ public class PropAllDiffAC extends Propagator { * enables to control the cardinality of the matching * * @param variables array of integer variables + * @param mode name of the filtering algorithm */ - public PropAllDiffAC(IntVar[] variables, boolean fast) { + public PropAllDiffAC(IntVar[] variables, AllDifferent.Consistency mode) { super(variables, PropagatorPriority.QUADRATIC, false); - this.filter = fast ? - new AlgoAllDiffACFast(variables, this): - new AlgoAllDiffAC(variables, this); + switch (mode) { + case AC_REGIN: + this.filter = new AlgoAllDiffAC(variables, this); + break; + case AC_ZHANG: + this.filter = new AlgoAllDiffACFast(variables, this); + break; + default: + this.filter = new AlgoAllDiffBimodal(variables, this, mode); + break; + } } //*********************************************************************************** diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAdaptative.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAdaptative.java index f5cd22e12d..9c109954a8 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAdaptative.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffAdaptative.java @@ -42,9 +42,10 @@ public class PropAllDiffAdaptative extends PropAllDiffAC { * enables to control the cardinality of the matching * * @param variables array of integer variables + * @param mode name of the filtering algorithm */ - public PropAllDiffAdaptative(IntVar[] variables) { - super(variables, !variables[0].getModel().getSolver().isLCG()); + public PropAllDiffAdaptative(IntVar[] variables, AllDifferent.Consistency mode) { + super(variables, mode); rd = new Random(vars[0].getModel().getSeed()); calls = success = 1; } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffInst.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffInst.java index ebad22ca0b..f68aaebf78 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffInst.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/PropAllDiffInst.java @@ -9,7 +9,6 @@ */ package org.chocosolver.solver.constraints.nary.alldifferent; -import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; import gnu.trove.stack.array.TIntArrayStack; import org.chocosolver.sat.Reason; @@ -22,6 +21,8 @@ import org.chocosolver.util.ESat; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; +import java.util.Arrays; + /** * Propagator for AllDifferent that only reacts on instantiation * @@ -52,7 +53,7 @@ void resetQuick() { public PropAllDiffInst(IntVar[] variables) { super(variables, PropagatorPriority.UNARY, true); n = vars.length; - if (lcg()) { + if (lcg() && Arrays.stream(variables).allMatch(IntVar::hasEnumeratedDomain)) { IntIterableRangeSet set = new IntIterableRangeSet(); for (IntVar var : vars) { set.addAll(var); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffAC.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffAC.java index e5e6afbd5b..149cd2bf14 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffAC.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffAC.java @@ -39,7 +39,7 @@ * * @author Jean-Guillaume Fages */ -public class AlgoAllDiffAC { +public class AlgoAllDiffAC implements IAlldifferentAlgorithm { //*********************************************************************************** // VARIABLES diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffBimodal.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffBimodal.java new file mode 100644 index 0000000000..c3a17f13bb --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/AlgoAllDiffBimodal.java @@ -0,0 +1,759 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.nary.alldifferent.algo; + +import org.chocosolver.memory.IEnvironment; +import org.chocosolver.sat.MiniSat; +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.Model; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.constraints.nary.alldifferent.AllDifferent; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.util.objects.BipartiteMatching; +import org.chocosolver.util.objects.TrackingList; + +import static org.chocosolver.solver.variables.IntVar.*; + + +/** + * Algorithm of Alldifferent ensuring GAC. + *

      + * Uses a variant of Regin algorithm based on the partially complemented (PC) approach + *

      + * Keeps track of previous matching and the sets of relevant variables and values for further calls + *

      + * + * @author Sulian Le Bozec-Chiffoleau + */ +public class AlgoAllDiffBimodal implements IAlldifferentAlgorithm { + + //*********************************************************************************** + // VARIABLES + //*********************************************************************************** + + public static final int BFS = 0; + public static final int DFS = 1; + public static final int PRUNE = 2; + + Propagator aCause; + Model model; + protected IntVar[] vars; + private final int R; // Total number of variables + private final TrackingList variablesDynamic; // The dynamic list of uninstantiated variables + private final int minValue; + private final int maxValue; + private final int D; // Total number of values + private final TrackingList valuesDynamic; // The dynamic list of values present in the domain of at least one variable and not matched to an instantiated variable + private final int fail; // Symbol signifying we couldn't find an augmenting path + private final BipartiteMatching matching; // The matching used dynamically + private final int[] parentBFS; // Array storing the parent of each value-node in the BFS tree + private final int[] queueBFS; // Queue of the variables to explore during the BFS + private int headBFS; + private int tailBFS; + private int minValSearchedNodes; // For failure explanation + private int maxValSearchedNodes; // For failure explanation + private final int t_node; // Symbol representing the artificial sink node of the Residual Graph + private final TrackingList complementSCC; // List of the values that are not in the discovered SCC + private final int[] tarjanStack; // The stack used in Tarjan's algorithm to find the SCCs + private int topTarjan; + private final boolean[] inStack; // Boolean array informing the presence of a value in the stack + private final int[] pre; // Pre visit order of the values + private final int[] low; // Low point of the values + private int numVisit; // Current visit number of the DFS in Tarjan's algorithm + private boolean firstSCC; // Indicates if the discovered SCC is the first discovered one in the current propagation + private final AllDifferent.Consistency mode; // Indicating the mode in which we are using the procedure (Classic, Complement, Hybrid or Tuned) + private boolean pruned; // True if some variable-value pairs were pruned + + private final int[] sccPartition; // Partition of the values relative to the SCCs + private final int[] sccIndices; // End point of each SCC in the partition + private int numberOfSCCs; + private final int[] sccBelonging; // index of the SCC each value belongs to + private final int[] upToDateSCC; // indicating for each value if its SCC has been updated in this call + private int updateKey; // This key is incremented at each call, to reset the upToDate information + + private final boolean allEnum; + + //*********************************************************************************** + // CONSTRUCTORS + //*********************************************************************************** + + public AlgoAllDiffBimodal(IntVar[] variables, Propagator cause, AllDifferent.Consistency acMode) { + // Variables and data structures for the whole procedure + this.aCause = cause; + this.model = variables[0].getModel(); + this.vars = variables; + this.R = variables.length; + this.variablesDynamic = new TrackingList(0, R-1); + int tempMinValue = vars[0].getLB(); + int tempMaxValue = vars[0].getUB(); + for (IntVar x : vars) { + tempMinValue = Math.min(tempMinValue, x.getLB()); + tempMaxValue = Math.max(tempMaxValue, x.getUB()); + } + this.minValue = tempMinValue; + this.maxValue = tempMaxValue; + this.D = maxValue - minValue + 1; + this.valuesDynamic = new TrackingList(minValue, maxValue); + refineUniverse(valuesDynamic); + this.fail = minValue - 1; + this.matching = new BipartiteMatching(0, R-1, minValue, maxValue); + + this.mode = acMode; + + // Specific data structures for finding the maximum matching + this.parentBFS = new int[D]; + this.queueBFS = new int[R]; + this.headBFS = 0; + this.tailBFS = 0; + + // Specific data structures for computing the strongly connected components + this.t_node = minValue - 1; + this.complementSCC = new TrackingList(minValue, maxValue); + refineUniverse(complementSCC); + this.tarjanStack = new int[D]; + this.topTarjan = 0; + this.inStack = new boolean[D]; + this.pre = new int[D]; + this.low = new int[D]; + + // Specific data structures for generating the explanations + this.sccPartition = new int[D]; + this.sccIndices = new int[D]; + this.numberOfSCCs = 0; + this.sccBelonging = new int[D]; + this.upToDateSCC = new int[D]; + this.updateKey = 0; + + boolean temp = true; + for (IntVar x : vars) { + if (!x.hasEnumeratedDomain()) { + temp = false; + break; + } + } + this.allEnum = temp; + } + + private void refineUniverse(TrackingList valueUniverse) { // The tracking list initially contains an interval, so we refine it by removing the values that are present in no variables' domain (which may contain holes) + boolean valuePresent = false; + for (int value = minValue; value <= maxValue; value++) { + for (IntVar variable : vars) { + if (variable.contains(value)) { + valuePresent = true; + break; + } + } + if (!valuePresent) {valueUniverse.removeFromUniverse(value);} + } + } + + //*********************************************************************************** + // PROPAGATION + //*********************************************************************************** + + public boolean propagate() throws ContradictionException { + this.pruned = false; + updateDynamicStructuresOpening(); + findMaximumMatching(); + filter(); + updateDynamicStructuresEnding(); + return this.pruned; + } + + //*********************************************************************************** + // MAXIMUM MATCHING + //*********************************************************************************** + + + private void findMaximumMatching() throws ContradictionException { + complementSCC.refill(); // This will be used to find the cut for a failure explanation + int var = variablesDynamic.getSource(); + while (variablesDynamic.hasNext(var)) { // We increase the size of the current matching until no unmatched variable remains + var = variablesDynamic.getNext(var); + if (!matching.inMatchingU(var)) { + valuesDynamic.refill(); // We refill the list with the recently removed elements, instead of recreating it from scratch + int val = augmentingPath(var); + if (val != fail) { + augmentMatching(val); + } + else { + // It is not possible to get a maximum matching --> the constraint can not be satisfied + generateFailureExplanation(); + } + } + } + valuesDynamic.refill(); // valuesDynamic is a global variable used in the whole filtering procedure, so we must refill it + } + + + private void augmentMatching(int root) { // By knowing the parent of each value in the BFS tree and the current match of the variables, we can retrieve the augmenting path from the last value in the path + int v = root; + while (matching.inMatchingU(getParent(v))) { + int v_next = matching.getMatchU(getParent(v)); + // We switch the edges of the matching on the augmenting path + matching.unMatch(getParent(v), v_next); + matching.setMatch(getParent(v), v); + v = v_next; + } + // The last variable we encounter is the one we performed the BFS from, and is then unmatched + matching.setMatch(getParent(v), v); + } + + private int augmentingPath(int root) { + // Additional global variables for explanations + minValSearchedNodes = maxValue + 1; + maxValSearchedNodes = minValue - 1; + // Starting the bimodalBFS + headBFS = 0; + tailBFS = 1; + queueBFS[0] = root; + while (headBFS != tailBFS) { + int var = queueBFS[headBFS]; + headBFS++; + int val; + if (choice(BFS, var)) { // If var has a small domain, we iterate over its domain and explore the unvisited values + int ub = vars[var].getUB(); + for (val = vars[var].getLB(); val <= ub; val = vars[var].nextValue(val)) { + if (valuesDynamic.isPresent(val) && stop(var, val)) {return val;} + } + } else { // If var has a large domain, we iterate over the unvisited values and explore the ones that are in the domain of var + val = valuesDynamic.getSource(); + while (valuesDynamic.hasNext(val)) { + val = valuesDynamic.getNext(val); + if (vars[var].contains(val) && stop(var, val)) {return val;} + } + } + } + return fail; + } + + private boolean stop(int var, int val) { + setParent(var, val); + if (matching.inMatchingV(val)) { // If the value is already matched, we continue the exploration from its matched variable + valuesDynamic.remove(val); + queueBFS[tailBFS] = matching.getMatchV(val); + tailBFS++; + // For explanations + minValSearchedNodes = Math.min(minValSearchedNodes, val); + maxValSearchedNodes = Math.max(maxValSearchedNodes, val); + return false; + } else {return true;} // If the value is not matched, we can stop the exploration because we found an augmenting path + } + + public void generateFailureExplanation() throws ContradictionException { + Reason reason = Reason.undef(); + if (aCause.lcg()) { + int nVars = tailBFS; + int nVals = tailBFS - 1; + int[] explanation = new int[1 + nVars * (2 + (maxValSearchedNodes - minValSearchedNodes + 1) - nVals)]; + // Store the values between minValSearchedNodes and maxValSearchedNodes that does not belong to the searched nodes + int[] nonValues = new int[(maxValSearchedNodes - minValSearchedNodes + 1) - nVals]; + int m = 0; + for (int value = minValSearchedNodes + 1; value < maxValSearchedNodes; value++) { + if (!isInSearchedNodes(value)) { // The value does not belong to the searched nodes + nonValues[m++] = value; + } + } + assert m == nonValues.length; + // Start generating the explanation + m = 1; + for (int i = 0; i < tailBFS; i++) { + int var = queueBFS[i]; + explanation[m++] = MiniSat.neg(vars[var].getLit(minValSearchedNodes, LR_GE)); + for (int value : nonValues) { + explanation[m++] = MiniSat.neg(vars[var].getLit(value, LR_NE)); + } + explanation[m++] = MiniSat.neg(vars[var].getLit(maxValSearchedNodes, LR_LE)); + } + assert m == explanation.length; + reason = Reason.r(explanation); + } + valuesDynamic.refill(); // valuesDynamic is a backtrackable TrackingList, we must refill it to avoid breaking its structure when backtracking + aCause.fails(reason); + } + + private boolean isInSearchedNodes(int val) { + // At this point of the filtering procedure, valuesDynamic is the list of unvisited values in the reduced variable-value graph and complementSCC is the list of values in the reduced variable-value graph + // So test whether a value has been visited we must verify that it does not belong to valuesDynamic AND it does belong to complementSCC + return !valuesDynamic.isPresent(val) && complementSCC.isPresent(val); + } + + //*********************************************************************************** + // SCC + PRUNING + //*********************************************************************************** + + private void filter() throws ContradictionException { + this.numVisit = 1; + this.firstSCC = true; + resetSCCs(); + int var = variablesDynamic.getSource(); + + while(variablesDynamic.hasNext(var)) { + var = variablesDynamic.getNext(var); + if (valuesDynamic.isPresent(matching.getMatchU(var))) { + bimodalDFS(var); + } + } + if (topTarjan != 0) {prune(t_node);} // If the artificial node t_node is alone, no pruning is possible and the structures tarjanStack and inStack are already cleared. + } + + private void bimodalDFS(int var) throws ContradictionException { + setPre(matching.getMatchU(var), numVisit); + setLow(matching.getMatchU(var), numVisit); + numVisit++; + valuesDynamic.remove(matching.getMatchU(var)); + tarjanStack[topTarjan] = matching.getMatchU(var); + topTarjan++; + declareInStack(matching.getMatchU(var), true); + int val; + + if(choice(DFS, var)) { // If var has a small domain then iterate over the domain + int ub = vars[var].getUB(); + for (val = vars[var].getLB(); val <= ub; val = vars[var].nextValue(val)) { + // ======================= Case 1 : explore a non-visited value ======================= + if (val != matching.getMatchU(var) && valuesDynamic.isPresent(val)) {process(var, val);} + + // ======================= Case 2 : update M(var).low via an already visited and unassigned value ======================= + else if (val != matching.getMatchU(var) && isInStack(val)) {setLow(matching.getMatchU(var), Math.min(getLow(matching.getMatchU(var)), getPre(val)));} // M(var).low = min(M(var).low, val.pre) + } + + } else { // If var has a large domain then iterate over the unvisited values and over the values in Tarjan's stack + + // ======================= Step 1: explore the non-visited values ======================= +// int pointerVar = valuesDynamic.getSource(); + int pointerVar = valuesDynamic.getPrevious(vars[var].getLB()); //Optimisation: start iterating from the lower bound instead of the beginning of the list (the list is sorted in ascending order) + int var_ub = vars[var].getUB(); //Optimisation + while (valuesDynamic.hasNext(pointerVar) && pointerVar < var_ub) { // Explore all the branches going out of var in the DFS tree + pointerVar = valuesDynamic.trackPrev(pointerVar); // Go back in the list of unvisited values + + while(valuesDynamic.hasNext(pointerVar) && pointerVar < var_ub && !vars[var].contains(valuesDynamic.getNext(pointerVar))) { // Go to the last consecutive non-domain value + pointerVar = valuesDynamic.getNext(pointerVar); + } + if (valuesDynamic.hasNext(pointerVar) && pointerVar < var_ub) {// If we did not reach the end of the list of unvisited values, the next value is a domain value + process(var, valuesDynamic.getNext(pointerVar)); + var_ub = vars[var].getUB(); //Optimisation: iterate until the upper bound instead of the end of the list (the list is sorted in ascending order) + } + } + + // ======================= Step 2 : update M(var).low thanks to the most ancient visited and unassigned value ======================= + for (int index = 0; index < topTarjan; index++) { // Iterate over tarjan's stack from the bottom until you find a value in the domain of var, or until it is not possible to decrease M(var).low + val = tarjanStack[index]; + if (vars[var].contains(val) || getPre(val) >= getLow(matching.getMatchU(var))) { + setLow(matching.getMatchU(var), Math.min(getLow(matching.getMatchU(var)), getPre(val))); // M(var).low = min(M(var).low, val.pre) + break; + } + } + } + if (getPre(matching.getMatchU(var)) == getLow(matching.getMatchU(var))) { // If M(var) is the root of its SCC, then we run the pruning procedure + prune(matching.getMatchU(var)); + } + + } + + + private void process(int var, int val) throws ContradictionException { + if (matching.inMatchingV(val)) { // If the value is already matched, we continue the exploration from its matched variable + bimodalDFS(matching.getMatchV(val)); + setLow(matching.getMatchU(var), Math.min(getLow(matching.getMatchU(var)), getLow(val))); // M(var).low = min(M(var).low, val.low) + } else { // If the value is not matched it leads to the artificial node t_node, so we artificially explore it + setPre(val, numVisit); + setLow(val, 0); + numVisit++; + setLow(matching.getMatchU(var), 0); // M(var).low = 0 + valuesDynamic.remove(val); + tarjanStack[topTarjan] = val; + topTarjan++; + declareInStack(val, true); + } + } + + + /** + * In this function we prune all the arcs coming from the variables of the discovered SCC and pointing toward values outside the discovered SCC + */ + private void prune(int root) throws ContradictionException { + complementSCC.refill(); + int var; + int val; + + + //======================= Step 1 : Get all the values of the discovered SCC and construct the complement ======================= + + // We will use the max and min values in the SCC to update the lower and upper bounds of the variables in this SCC + int minValueSCC = maxValue; + int maxValueSCC = minValue; + + int rootIndex = topTarjan; + // Declare the creation of a new SCC + newSCC(); + + do { + // Empty Tarjan's stack + rootIndex--; + val = tarjanStack[rootIndex]; + declareInStack(val, false); + // Empty the complement + complementSCC.remove(val); + // Update the bounds of the current SCC + minValueSCC = Math.min(val, minValueSCC); + maxValueSCC = Math.max(val, maxValueSCC); + // Store the SCC + addToSCC(val, getLastSCC()); + } while (val != root && rootIndex != 0); + + + //======================= Step 2 : For each variable of the SCC, we prune their domain values that are not in the same SCC ======================= + + + // Particular case where we can force the instantiation of the matched variable to the unique value of the SCC, and remove them from the universes of variables and values + if (topTarjan - rootIndex == 1) { + // The unique value of the SCC is necessarily matched, otherwise it would have been in the same SCC as t_node + var = matching.getMatchV(val); + //NOTE: We can force the instantiation of var only if we do not use LCG, otherwise we remove the other values one by one and generate the corresponding explanations + if (!vars[var].getModel().getSolver().isLCG()) { + val = tarjanStack[rootIndex]; + if (vars[var].getDomainSize() > 1) {pruned = true;} + vars[var].instantiateTo(val, aCause); + } + + } + + if (!firstSCC) { // Run the pruning procedure only if it is not the first SCC discovered + + for (int index = rootIndex; index < topTarjan; index++) { + val = tarjanStack[index]; + if (matching.inMatchingV(val)) { + var = matching.getMatchV(val); + + //NOTE: we don't keep the bound update when using LCG + if (!vars[var].getModel().getSolver().isLCG() && vars[var].updateBounds(minValueSCC, maxValueSCC, aCause)) { // All values outside [minValueSCC, maxValueSCC] can not be present in the discovered SCC, and thus are pruned from the domain of every variable of the SCC + pruned = true; + } + + if (vars[var].getDomainSize() > 1) { // If the domain is a singleton there is nothing to prune + + if (vars[var].hasEnumeratedDomain()) { // If the domain is enumerated, proceed with the bimodal pruning + if (choice(PRUNE, var)) { // If var has a small domain then iterate over the domain and prune the values that are in the complement + int ub = vars[var].getUB(); + for (int domainValue = vars[var].getLB(); domainValue <= ub; domainValue = vars[var].nextValue(domainValue)) { + if (complementSCC.isPresent(domainValue)) { + pruned = true; + // Prune the pair (var, domainValue) + Explanation + generatePruningExplanation(var, domainValue); + } + } + + } else { // If var has a large domain then iterate over the values in the complement and prune the ones that are in the domain of var + int complementValue = complementSCC.getSource(); + while (complementSCC.hasNext(complementValue)) { + complementValue = complementSCC.getNext(complementValue); + if (vars[var].contains(complementValue)) { + pruned = true; + // Prune the pair (var, complementValue) + Explanation + generatePruningExplanation(var, complementValue); + } + } + } + } else { // If the domain is not enumerated but is an interval, then we prune the bounds in the right order + assert !vars[var].getModel().getSolver().isLCG() : "not compatible with LCG yet"; + // Iterate over the interval from left to right until we reach a value that we can not prune + int ub = vars[var].getUB(); + boolean boundPruned = true; + for (int boundValue = vars[var].getLB(); boundValue <= ub && boundPruned; boundValue++) { + if (complementSCC.isPresent(boundValue)) { + generatePruningExplanation(var, boundValue); + } else { + boundPruned = false; + } + } + // Iterate over the interval from right to left until we reach a value that we can not prune + int lb = vars[var].getLB(); + boundPruned = true; + for (int boundValue = vars[var].getUB(); boundValue >= lb && boundPruned; boundValue--) { + if (complementSCC.isPresent(boundValue)) { + generatePruningExplanation(var, boundValue); + } else { + boundPruned = false; + } + } + + } + + } + } + } + } + firstSCC = false; + topTarjan = rootIndex; // Remove the discovered SCC from Tarjan's stack + } + + public void generatePruningExplanation(int sourceVar, int destinationVal) throws ContradictionException { + Reason reason = Reason.undef(); + if (vars[sourceVar].getModel().getSolver().isLCG()) { + int minValSCC = maxValue; + int maxValSCC = minValue; + int scc = getSCC(destinationVal); + if (getSizeScc(scc) == 1) { // Got a specific way to generate the explanation in the case of forced instantiation + int matchedVar = matching.getMatchV(destinationVal); + assert vars[matchedVar].isInstantiatedTo(destinationVal); + reason = Reason.r(vars[matchedVar].getValLit()); + } else { + // Get the minimum and maximum values of the destination SCC + for (int i = getStartPositionSCC(scc); i < getEndPositionSCC(scc); i++) { + minValSCC = Math.min(minValSCC, sccPartition[i]); + maxValSCC = Math.max(maxValSCC, sccPartition[i]); + } + + // Create the array of the explanation with the right size + int[] explanation = new int[1 + getSizeScc(scc) * (2 + (maxValSCC - minValSCC + 1) - getSizeScc(scc))]; + + // Store the values between minValSCC and maxValSCC that does not belong to the SCC + int[] nonValues = new int[(maxValSCC - minValSCC + 1) - getSizeScc(scc)]; + int m = 0; + for (int value = minValSCC + 1; value < maxValSCC; value++) { + if (!isInSCC(value, scc)) { + nonValues[m++] = value; + } + } + assert m == nonValues.length; + // Start generating the explanation + m = 1; + for (int i = getStartPositionSCC(scc); i < getEndPositionSCC(scc); i++) { + int var = matching.getMatchV(sccPartition[i]); + explanation[m++] = MiniSat.neg(vars[var].getLit(minValSCC, LR_GE)); + for (int value : nonValues) { + explanation[m++] = MiniSat.neg(vars[var].getLit(value, LR_NE)); + } + explanation[m++] = MiniSat.neg(vars[var].getLit(maxValSCC, LR_LE)); + } + assert m == explanation.length; + reason = Reason.r(explanation); + + } + } + vars[sourceVar].removeValue(destinationVal, aCause, reason); + } + + //*********************************************************************************** + // Dynamic Structures and Backtrack Management + // In this section we manage the decrementality and backtrack of the universe of variables variablesDynamic + // and the universes of values valuesDynamic and complementSCC + // The backtrack operations are managed within the removeFromUniverse method of the TrackingList + //*********************************************************************************** + + + private void updateDynamicStructuresOpening(){ + // Repair the matching + int var = variablesDynamic.getSource(); + while (variablesDynamic.hasNext(var)) { + var = variablesDynamic.getNext(var); + if (vars[var].isInstantiated()) { + if (matching.inMatchingU(var)) { // Unmatch the instantiated variable from its current matched value + matching.unMatch(var, matching.getMatchU(var)); + } + if (matching.inMatchingV(vars[var].getValue())) { // Unmatch the value of the instantiated variable from its current matched variable + matching.unMatch(matching.getMatchV(vars[var].getValue()), vars[var].getValue()); + } + matching.setMatch(var, vars[var].getValue()); //Match the instantiated variable and its value together + } else if (matching.inMatchingU(var) && !vars[var].contains(matching.getMatchU(var))) { // Unmatch a variable from its matched value if it does not belong to the domain anymore + matching.unMatch(var, matching.getMatchU(var)); + } + } + } + + private void updateDynamicStructuresEnding() { + IEnvironment env = model.getEnvironment(); + + // The remaining unvisited values are present in the domain of no variables, thus we can remove them from the universe of values for the next call to the propagator + int val = valuesDynamic.getSource(); + while (valuesDynamic.hasNext(val)) { + val = valuesDynamic.getNext(val); + // Here we reuse tarjan's stack instead of creating a new data structure + tarjanStack[topTarjan] = val; + topTarjan++; + } + // We must refill the tracking lists before removing some elements from their universe, otherwise their structure is broken + valuesDynamic.refill(); + complementSCC.refill(); + while (topTarjan != 0) { + valuesDynamic.removeFromUniverse(tarjanStack[topTarjan - 1], env); + complementSCC.removeFromUniverse(tarjanStack[topTarjan - 1], env); + topTarjan--; + } + + // Now that the pruning is done, we can remove from the universes of variables and values the pairs that were instantiated either before or during the call to this propagator + int var = variablesDynamic.getSource(); + if (allEnum) { // If all the domains are enumerated, then we can safely proceed with the removals as all the required pruning has been done + while (variablesDynamic.hasNext(var)) { + var = variablesDynamic.getNext(var); + if (vars[var].isInstantiated()) { + // The instantiated variables are removed from the universe + variablesDynamic.removeFromUniverse(var, env); + + // The values of the instantiated variables are removed from the universe + valuesDynamic.removeFromUniverse(vars[var].getValue(), env); + complementSCC.removeFromUniverse(vars[var].getValue(), env); + } + } + } else { // If all domains are not enumerated, then some instantiated values may still belong to the domain of a bounded variable. Thus, it must remain in the graph to be pruned in a future call to the propagator. + while (variablesDynamic.hasNext(var)) { + var = variablesDynamic.getNext(var); + if (vars[var].isInstantiated() && !isInAnotherBoundedDomain(var, vars[var].getValue())) { + // The instantiated variables are removed from the universe + variablesDynamic.removeFromUniverse(var, env); + + // The values of the instantiated variables are removed from the universe + valuesDynamic.removeFromUniverse(vars[var].getValue(), env); + complementSCC.removeFromUniverse(vars[var].getValue(), env); + } + } + } + } + + private boolean isInAnotherBoundedDomain(int var, int val) { + int x = variablesDynamic.getSource(); + while (variablesDynamic.hasNext(x)) { + x = variablesDynamic.getNext(x); + if (x != var && !vars[x].hasEnumeratedDomain() && vars[x].contains(val)) { + return true; + } + } + return false; + } + + //*********************************************************************************** + // Choice Functions for the Search Algorithms + // These functions decide whether the domain is considered as small or large + //*********************************************************************************** + + private boolean choice(int algo, int var) { + switch (mode) { + case AC_CLASSIC: + return true; + case AC_COMPLEMENT: + return false; + case AC_PARTIAL: + if (algo == BFS || algo == DFS) { +// return vars[var].getDomainSize() < valuesDynamic.getSize(); + return Math.max((vars[var].getUB() - vars[var].getLB() + 63) >> 6, vars[var].getDomainSize()) < valuesDynamic.getSize(); // More suited to BitSet domain representation + } else { +// return vars[var].getDomainSize() < complementSCC.getSize(); + return Math.max((vars[var].getUB() - vars[var].getLB() + 63) >> 6, vars[var].getDomainSize()) < complementSCC.getSize(); // More suited to BitSet domain representation + } + case AC_TUNED: + if (algo == BFS) { +// return vars[var].getDomainSize() < valuesDynamic.getSize(); + return Math.max((vars[var].getUB() - vars[var].getLB() + 63) >> 6, vars[var].getDomainSize()) < valuesDynamic.getSize(); // More suited to BitSet domain representation + } else if (algo == DFS) { +// return vars[var].getDomainSize() < Math.sqrt(valuesDynamic.getSize()); + return Math.max((vars[var].getUB() - vars[var].getLB() + 63) >> 6, vars[var].getDomainSize()) < Math.sqrt(valuesDynamic.getSize()); // More suited to BitSet domain representation + } else { +// return vars[var].getDomainSize() < complementSCC.getSize(); + return Math.max((vars[var].getUB() - vars[var].getLB() + 63) >> 6, vars[var].getDomainSize()) < complementSCC.getSize(); // More suited to BitSet domain representation + } + } + return true; + } + + + //*********************************************************************************** + // Getter and Setter for Internal Data Structures + //*********************************************************************************** + + private int getParent(int val) { + return parentBFS[val - minValue]; + } + + private void setParent(int var, int val) { + parentBFS[val - minValue] = var; + } + + private int getPre(int val) { + return pre[val - minValue]; + } + + private void setPre(int val, int order) { + pre[val - minValue] = order; + } + + private int getLow(int val) { + return low[val - minValue]; + } + + private void setLow(int val, int point) { + low[val - minValue] = point; + } + + private boolean isInStack(int val) { + return inStack[val - minValue]; + } + + private void declareInStack(int val, boolean present) { + inStack[val - minValue] = present; + } + + private int getSCC(int val) { + return sccBelonging[val - minValue]; + } + + private void setSCC(int val, int scc) { + sccBelonging[val - minValue] = scc; + } + + private boolean isUpToDateSCC(int val) {return upToDateSCC[val - minValue] == updateKey; } + + private void setUpToDateSCC(int val) { + upToDateSCC[val - minValue] = updateKey; + } + + private void resetSCCs() { + numberOfSCCs = 0; + updateKey++; + } + + private int getLastSCC() {return numberOfSCCs - 1;} + + private void newSCC() { + numberOfSCCs++; + int lastSCC = getLastSCC(); + if (lastSCC == 0) { + sccIndices[lastSCC] = 0;} + else { + sccIndices[lastSCC] = sccIndices[lastSCC - 1];} + } + + private void addToSCC(int val, int scc) { + sccPartition[sccIndices[scc]] = val; + sccIndices[scc]++; + setSCC(val, scc); + setUpToDateSCC(val); + } + + private boolean isInSCC(int val, int scc) { + return getSCC(val) == scc && isUpToDateSCC(val); + // The structure SCCbelonging is not reset between each call to the propagator, so some values may be outdated + // So we ensure that the current SCCbelonging is up to date + } + + private int getStartPositionSCC(int scc) { + if(scc == 0) { + return 0; + } else { + return sccIndices[scc - 1]; + } + } + + private int getEndPositionSCC(int scc) { + return sccIndices[scc]; + } + + private int getSizeScc(int scc) {return getEndPositionSCC(scc) - getStartPositionSCC(scc);} +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/IAlldifferentAlgorithm.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/IAlldifferentAlgorithm.java new file mode 100644 index 0000000000..8c54be4125 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/alldifferent/algo/IAlldifferentAlgorithm.java @@ -0,0 +1,22 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.nary.alldifferent.algo; + +import org.chocosolver.solver.exception.ContradictionException; + +/** + *
      + * + * @author Charles Prud'homme + * @since 17/10/2024 + */ +public interface IAlldifferentAlgorithm { + boolean propagate() throws ContradictionException; +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/CumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/CumulFilter.java deleted file mode 100644 index 68a5806eb5..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/CumulFilter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; - - - -/** - * Class able to filter a subset of tasks for the cumulative constraint - * @author Jean-Guillaume Fages - */ -public abstract class CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - protected int nbMaxTasks; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - /** - * An object which can filter subset of tasks for the cumulative constraint - * @param nbMaxTasks maximum number of tasks - */ - public CumulFilter(int nbMaxTasks){ - this.nbMaxTasks = nbMaxTasks; - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - /** - * Filters the cumulative constraint over the subset of tasks induced by tasks - * @param s start variables - * @param d duration variables - * @param e end variables - * @param h height variables - * @param capa maximum capacity variable - * @param tasks subset of tasks to filter - * @param aCause a cumulative propagator - * @throws ContradictionException - */ - public abstract void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException; -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Cumulative.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Cumulative.java deleted file mode 100755 index 14fe08de2c..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Cumulative.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Constraint; -import org.chocosolver.solver.constraints.ConstraintsName; -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.solver.variables.Task; -import org.chocosolver.util.tools.ArrayUtils; - -import java.util.Arrays; - -/** - * Cumulative constraint - * @author Jean-Guillaume Fages - * @since 22/10/13 - */ -public class Cumulative extends Constraint { - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - /** - * Cumulative constraint - * - * @param tasks task variables (embed start, duration and end variables) - * @param heights height variables (represent the consumption of each task on the resource) - * @param capacity maximal capacity of the resource (same at each point in time) - * @param graphBased parameter indicating how to filter: - * - TRUE: applies on subset of overlapping tasks - * - FALSE: applies on all tasks - * @param filters Filtering algorithm to use: - * - TIME: filters time-table from considering each point in time - * (efficient in practice as long as the time horizon is not too high) - * - SWEEP: filters time-table with a sweep-based algorithm - * - NRJ: greedy energy-based filter. - * BEWARE: should not be used alone, use it in addition to either SWEEP or TIME. - * - */ - public Cumulative(Task[] tasks, IntVar[] heights, IntVar capacity, boolean graphBased, Filter... filters) { - super(ConstraintsName.CUMULATIVE,createPropagators(tasks, heights, capacity, graphBased, Arrays.stream(filters).map(f->f.make(tasks.length)).toArray(CumulFilter[]::new))); - } - - /** - * Cumulative constraint - * - * @param tasks task variables (embed start, duration and end variables) - * @param heights height variables (represent the consumption of each task on the resource) - * @param capacity maximal capacity of the resource (same at each point in time) - * @param graphBased parameter indicating how to filter: - * - TRUE: applies on subset of overlapping tasks - * - FALSE: applies on all tasks - * @param filters Filtering algorithm to use: - * - TIME: filters time-table from considering each point in time - * (efficient in practice as long as the time horizon is not too high) - * - SWEEP: filters time-table with a sweep-based algorithm - * - NRJ: greedy energy-based filter. - * BEWARE: should not be used alone, use it in addition to either SWEEP or TIME. - * - */ - public Cumulative(Task[] tasks, IntVar[] heights, IntVar capacity, boolean graphBased, CumulFilter... filters) { - super(ConstraintsName.CUMULATIVE,createPropagators(tasks, heights, capacity, graphBased, filters)); - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - private static Propagator[] createPropagators(Task[] tasks, IntVar[] heights, IntVar capa, boolean graphBased, CumulFilter... filters){ - int n = tasks.length; - assert n==heights.length && n > 0; - IntVar[] vars = extract(tasks,heights,capa); - IntVar[] s = Arrays.copyOfRange(vars,0,n); - IntVar[] d = Arrays.copyOfRange(vars,n,2*n); - IntVar[] e = Arrays.copyOfRange(vars,2*n,3*n); - IntVar[] h = Arrays.copyOfRange(vars,3*n,4*n); - // propagators are posted twice, to achieve fixpoint - if(graphBased){ - return new Propagator[]{ - new PropGraphCumulative(s,d,e,h,capa,false, filters), - new PropGraphCumulative(s,d,e,h,capa,true, filters) - }; - }else{ - return new Propagator[]{ - new PropCumulative(s,d,e,h,capa, filters), - new PropCumulative(s,d,e,h,capa, filters) - }; - } - } - - public static IntVar[] extract(Task[] tasks, IntVar[] heights, IntVar capa){ - int n = tasks.length; - IntVar[] starts = new IntVar[n]; - IntVar[] durations = new IntVar[n]; - IntVar[] ends = new IntVar[n]; - for (int i = 0; i < n; i++) { - starts[i] = tasks[i].getStart(); - durations[i] = tasks[i].getDuration(); - ends[i] = tasks[i].getEnd(); - } - return ArrayUtils.append(starts,durations,ends,heights,new IntVar[]{capa}); - } - - //*********************************************************************************** - // FILTERING ALGORITHMS - //*********************************************************************************** - - /** - * Filtering algorithms for Cumulative constraint - */ - public enum Filter { - /** - * filters height variables only (sweep-based algorithm) - * idempotent (on the given set of variables only) - */ - HEIGHTS{ - public CumulFilter make(int n){ - return new HeightCumulFilter(n); - } - }, - /** - * time-table algorithm based on each point in time - * not idempotent - */ - TIME{ - public CumulFilter make(int n){ - return new TimeCumulFilter(n); - } - }, - /** - * time-table algorithm based on each point in time - * not idempotent - */ - NAIVETIME{ - @Override - public CumulFilter make(int n) { - return new NaiveTimeCumulFilter(n); - } - }, - /** - * time-table algorithm based on a sweep line - * idempotent (on the given set of variables only) - */ - SWEEP{ - public CumulFilter make(int n){ - return new SweepCumulFilter(n); - } - }, - /** - * time-table algorithm based on a sweep line - * idempotent (on the given set of variables only) - */ - SWEEP_HEI_SORT { - public CumulFilter make(int n){ - return new SweepHeiSortCumulFilter(n); - } - }, - /** - * energetic reasoning to filter - * not idempotent - * not enough to ensure correctness (only an additional filtering) - */ - NRJ{ - public CumulFilter make(int n){ - return new NRJCumulFilter(n); - } - }, - /** - * energetic reasoning to filter disjunctive constraint - * Only propagated on variable subsets of size < 30 - * not idempotent - * not enough to ensure correctness (only an additional filtering) - */ - DISJUNCTIVE_TASK_INTERVAL { - public CumulFilter make(int n){ - return new DisjunctiveTaskIntervalFilter(n); - } - }, - /** - * Combines above filters as a black-box - * not idempotent - */ - DEFAULT { - public CumulFilter make(int n){ - return new DefaultCumulFilter(n); - } - }; - - /** - * Create an instance of the filtering algorithm - * @param n maximum number of tasks - * @return an instance of the filtering algorithm - */ - public abstract CumulFilter make(int n); - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DefaultCumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DefaultCumulFilter.java deleted file mode 100644 index a047565a97..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DefaultCumulFilter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; - -/** - * Default filtering for cumulative - * @author Jean-Guillaume Fages - */ -public class DefaultCumulFilter extends CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - private CumulFilter time; - private CumulFilter sweep; - private final CumulFilter nrj; - private CumulFilter heights; - private CumulFilter disjTaskInter; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public DefaultCumulFilter(int nbMaxTasks) { - super(nbMaxTasks); - nrj = Cumulative.Filter.NRJ.make(nbMaxTasks); - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - int min = Integer.MAX_VALUE; - int max = Integer.MIN_VALUE; - boolean hInst = true; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - min = Math.min(min, s[t].getLB()); - max = Math.max(max, e[t].getUB()); - hInst &= h[t].isInstantiated(); - } - if (max - min < tasks.size() * tasks.size()) { - getTime().filter(s, d, e, h, capa, tasks, aCause); - } else { - getSweep().filter(s, d, e, h, capa, tasks, aCause); - if (!hInst) { - getHeights().filter(s, d, e, h, capa, tasks, aCause); - } - } - nrj.filter(s, d, e, h, capa, tasks, aCause); - // only propagated on less than 50 tasks (too costly otherwise) - if (tasks.size() < 50) { - if (capa.isInstantiatedTo(1)) { - getDisjTaskInter().filter(s, d, e, h, capa, tasks, aCause); - } - } - } - - //*********************************************************************************** - // Lazy creation (saves memory) - //*********************************************************************************** - - private CumulFilter getTime() { - if(time==null)time = Cumulative.Filter.TIME.make(nbMaxTasks); - return time; - } - - private CumulFilter getSweep() { - if(sweep==null)sweep = Cumulative.Filter.SWEEP.make(nbMaxTasks); - return sweep; - } - - private CumulFilter getHeights() { - if(heights==null)heights = Cumulative.Filter.HEIGHTS.make(nbMaxTasks); - return heights; - } - - private CumulFilter getDisjTaskInter() { - if(disjTaskInter==null)disjTaskInter = Cumulative.Filter.DISJUNCTIVE_TASK_INTERVAL.make(nbMaxTasks); - return disjTaskInter; - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DisjunctiveTaskIntervalFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DisjunctiveTaskIntervalFilter.java deleted file mode 100644 index 9d7d00eb53..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/DisjunctiveTaskIntervalFilter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.sort.ArraySort; -import org.chocosolver.util.sort.IntComparator; - -/** - * @author Jean-Guillaume FAGES - */ -public class DisjunctiveTaskIntervalFilter extends CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - private final ArraySort sort; - private final StartComparator comp; - private final int[] tsks; - //*********************************************************************************** - // CONSTRUCTOR - //*********************************************************************************** - - public DisjunctiveTaskIntervalFilter(int nbMaxTasks) { - super(nbMaxTasks); - sort = new ArraySort<>(nbMaxTasks, false, true); - comp = new StartComparator(); - tsks = new int[nbMaxTasks]; - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - @Override - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - // filtering algorithm for disjunctive constraint - capa.updateUpperBound(1, aCause); - // remove tasks that do not consume any resource - int tskSize = 0; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()) { - int t = tIter.nextInt(); - if (d[t].getLB() > 0 && h[t].getLB() > 0) { - tsks[tskSize++] = t; - } - } - comp.setS(s); - sort.sort(tsks, tskSize, comp); - // run energetic reasoning - for (int x = 0; x < tskSize; x++) { - int task1 = tsks[x]; - for (int y = 0; y < tskSize; y++) { - if (x != y) { - int task2 = tsks[y]; - int t1 = s[task1].getLB(); - int t2 = e[task2].getUB(); - if (e[task1].getLB() > s[task2].getUB()) { - s[task1].updateLowerBound(e[task2].getLB(), aCause); - e[task2].updateUpperBound(s[task1].getUB(), aCause); - } else if (t1 < t2 && (t1 < e[task2].getLB() || t2 > s[task1].getUB())) { - int W = 0; - for (int z = 0; z < tskSize; z++) { - int task3 = tsks[z]; - if (task3 != task1 && task3 != task2) { - if (s[task3].getLB() >= t2) { - break; - } - int pB = d[task3].getLB() * h[task3].getLB(); - int pbt1 = Math.max(0, pB - Math.max(0, t1 - s[task3].getLB())); - int pbt2 = Math.max(0, pB - Math.max(0, e[task3].getUB() - t2)); - int pbt = Math.min(pbt1, pbt2); - W += Math.min(t2 - t1, pbt); - } - } - if (W + d[task1].getLB() + d[task2].getLB() > t2 - t1) { - s[task1].updateLowerBound(e[task2].getLB(), aCause); - e[task2].updateUpperBound(s[task1].getUB(), aCause); - } - } - } - } - } - } - - private static class StartComparator implements IntComparator { - IntVar[] s; - - void setS(IntVar[] s) { - this.s = s; - } - - @Override - public int compare(int i1, int i2) { - return s[i1].getLB() - s[i2].getLB(); - } - } - -} \ No newline at end of file diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Event.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Event.java new file mode 100644 index 0000000000..bdd1ce9935 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Event.java @@ -0,0 +1,120 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.nary.cumulative; + +/** + * The class Event is used to represent a meaningful time event inside an {@link EventPointSeries}. + * + * @author Arthur Godet + * @since 19/10/2023 + */ +public class Event implements Comparable { + /** + * A constant representing the SCP event, which is the start of the compulsory part of a task. + */ + public static final int SCP = 0; + /** + * A constant representing the ECP event, which is the end of the compulsory part of a task. + */ + public static final int ECP = 1; + + /** + * The type of the event (should be SCP or ECP). + */ + private int type; + /** + * The index of the task in a mapping array or list. + */ + private int indexTask; + /** + * The time-point of the event. + */ + private int time; + + /** + * Instantiates a new Event. + */ + public Event() { + // Nothing to instantiate here, it will be done in the set() function + } + + /** + * Set the event. + * + * @param type the type of the event (should be SCP or ECP) + * @param indexTask the index of the task in a mapping array or list + * @param time the time-point of the event + */ + public void set(int type, int indexTask, int time) { + this.type = type; + this.indexTask = indexTask; + this.time = time; + } + + /** + * Returns the type of the event. + * + * @return the type of the event + */ + public int getType() { + return this.type; + } + + /** + * Returns the index of the task in the mapping array or list. + * + * @return the index of the task in the mapping array or list + */ + public int getIndexTask() { + return this.indexTask; + } + + /** + * Return the time-point of the event. + * + * @return the time-point of the event + */ + public int getTime() { + return this.time; + } + + @Override + public int compareTo(Event o) { + if (this.time == o.time) { + return -(this.type - o.type); + } + return this.time - o.time; + } + + @Override + public String toString() { + String typeStr; + switch (this.type) { + case SCP: + typeStr = "SCP"; + break; + case ECP: + typeStr = "ECP"; + break; + default: + throw new UnsupportedOperationException(); + } + return "Event<" + typeStr + "," + indexTask + "," + time + ">"; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Event) { + Event event = (Event) obj; + return this.type == event.type && this.time == event.time && this.indexTask == event.indexTask; + } + return false; + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/EventPointSeries.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/EventPointSeries.java new file mode 100644 index 0000000000..e051a989c5 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/EventPointSeries.java @@ -0,0 +1,159 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +/* +@author Arthur Godet +@since 25/09/2020 +*/ + +package org.chocosolver.solver.constraints.nary.cumulative; + +import org.chocosolver.memory.IStateBitSet; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.Task; +import org.chocosolver.util.sort.ArraySort; + +import java.util.Comparator; +import java.util.List; + +import static org.chocosolver.solver.constraints.nary.cumulative.SchedulingUtils.mustBePerformed; + +/** + * Class representing a series of {@link Event}. + * + * @author Arthur Godet + * @since 19/10/2023 + */ +public class EventPointSeries { + /** + * The array of events. + */ + private final Event[] eventsArray; + /** + * The index of the last event in the series. + */ + private int nbEvents; + /** + * The index of the first event in the series. + */ + private int indexFirstEvent; + /** + * The comparator used on events. + */ + private final Comparator comparator = Event::compareTo; + /** + * Used to sort the array of events. + */ + private final ArraySort sort; + + /** + * Instantiates a new EventPointSeries. + * + * @param nbTasks the maximum number of tasks the EventPointSeries will manage + */ + public EventPointSeries(int nbTasks) { + eventsArray = new Event[2 * nbTasks]; + for (int i = 0; i < eventsArray.length; i++) { + eventsArray[i] = new Event(); + } + sort = new ArraySort<>(eventsArray.length, true, false); + } + + /** + * Returns true iff the series is empty. + * + * @return true iff the series is empty + */ + public boolean isEmpty() { + return indexFirstEvent >= nbEvents; + } + + /** + * Returns the size of the series. + * + * @return the size of the series + */ + public int size() { + return nbEvents - indexFirstEvent; + } + + /** + * Generates SCP and ECP events (start and end of compulsory parts) of the tasks and sorts them. + * + * @param tasks the tasks for which SCP and ECP events should be generated + * @param heights the height variables of the tasks + */ + public void generateEvents(final List tasks, final List heights) { + nbEvents = 0; + indexFirstEvent = 0; + for (int i = 0; i < tasks.size(); i++) { + final Task task = tasks.get(i); + if (mustBePerformed(task, heights.get(i)) && task.hasCompulsoryPart()) { + eventsArray[nbEvents++].set(Event.SCP, i, task.getLst()); + eventsArray[nbEvents++].set(Event.ECP, i, task.getEct()); + } + } + if (!isEmpty()) { + sort.sort(eventsArray, nbEvents, comparator); + } + } + + /** + * Generates SCP and ECP events (start and end of compulsory parts) of the tasks and sorts them. + * + * @param tasks the tasks for which SCP and ECP events should be generated + * @param heights the height variables of the tasks + * @param activeTasks the indexes of tasks to consider + */ + public void generateEvents(final Task[] tasks, final IntVar[] heights, final IStateBitSet activeTasks) { + nbEvents = 0; + indexFirstEvent = 0; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + final Task task = tasks[i]; + if (mustBePerformed(task, heights[i]) && task.hasCompulsoryPart()) { + eventsArray[nbEvents++].set(Event.SCP, i, task.getLst()); + eventsArray[nbEvents++].set(Event.ECP, i, task.getEct()); + } + } + if (!isEmpty()) { + sort.sort(eventsArray, nbEvents, comparator); + } + } + + /** + * Returns the time of the first event in the series. + * + * @return the time of the first event in the series + */ + public int getTimeFirstEvent() { + return eventsArray[indexFirstEvent].getTime(); + } + + /** + * Removes and returns the first event in the series. + * + * @return the first event in the series + */ + public Event removeFirstEvent() { + return eventsArray[indexFirstEvent++]; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("EventPointSeries["); + for (int pos = indexFirstEvent; pos < nbEvents; pos++) { + sb.append(eventsArray[pos]); + if (pos < nbEvents - 1) { + sb.append(","); + } + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/HeightCumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/HeightCumulFilter.java deleted file mode 100644 index 2b36a096a9..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/HeightCumulFilter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; - -/** - * Filtering (sweep-based) algorithm to filter task maximum heights - * @author Jean-Guillaume Fages, Thierry Petit - * @since 16/10/13 - */ -public class HeightCumulFilter extends SweepCumulFilter { - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public HeightCumulFilter(int n){ - super(n); - FIXPOINT = false; - } - - //*********************************************************************************** - // SWEEP ALGORITHM - //*********************************************************************************** - - // do not filter min and max (heights only) - protected void pruneMin(IntVar[] s) throws ContradictionException {} - protected void pruneMax(IntVar[] e) throws ContradictionException {} - - protected boolean sweep(IntVar capamax, IntVar[] h, int nbT, Propagator aCause) throws ContradictionException { - generateMinEvents(nbT); - if(nbEvents==0){ - return false;// might happen on randomly generated cases - } - sort.sort(events,nbEvents,eventComparator); - //Arrays.sort(events,0,nbEvents,eventComparator); - int timeIndex = 0; - int currentDate = events[timeIndex].date; - tprune.resetQuick(); - int capa = capamax.getUB(); - int currentConso = 0; - while(timeIndex=0;i--){ - int index = tprune.get(i); - // the envelope overlaps the event - assert (sub[index]<=currentDate && currentDate < elb[index]); - // filter consumption variable from remaining capacity - h[map[index]].updateUpperBound(capa-(currentConso- hlb[index]),aCause); - if(nextDate< elb[index]) { - temp.add(index); - } - } - tprune.resetQuick(); - for(int i=temp.size()-1;i>=0;i--){ - tprune.add(temp.getQuick(i)); - } - } - // handle the current event - Event event = events[timeIndex++]; - currentDate = event.date; - if(event.type == SCP) { - currentConso += hlb[event.index]; - // filter the capa max LB from the compulsory part consumptions - capamax.updateLowerBound(currentConso, aCause); - if (!h[map[event.index]].isInstantiated()) { - tprune.add(event.index); - } - }else{ - assert event.type == ECP; - currentConso -= hlb[event.index]; - } - } - return false; - } - - protected void generateMinEvents(int nbT) { - nbEvents = 0; - for(int i=0; i sorter; - private final IntComparator comparator; - private final int[] slb; - private final int[] dlb; - private final int[] eub; - private final int[] hlb; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public NRJCumulFilter(int n){ - super(n); - sor_array = new int[n]; - sorter = new ArraySort<>(n,false,true); - slb = new int[n]; - dlb = new int[n]; - eub = new int[n]; - hlb = new int[n]; - comparator = (i1, i2) -> { - int coef1 = (100*dlb[i1]*hlb[i1])/(eub[i1]-slb[i1]); - int coef2 = (100*dlb[i2]*hlb[i2])/(eub[i2]-slb[i2]); - return coef2 - coef1; - }; - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - @Override - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - int idx = 0; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int i = tIter.nextInt(); - if(d[i].getLB()>0){ - slb[i] = s[i].getLB(); - dlb[i] = d[i].getLB(); - eub[i] = e[i].getUB(); - hlb[i] = h[i].getLB(); - assert eub[i]>slb[i]; - sor_array[idx++] = i; - } - } - sorter.sort(sor_array,idx,comparator); - double xMin = Integer.MAX_VALUE / 2d; - double xMax = Integer.MIN_VALUE / 2d; - double surface = 0; - double camax = capa.getUB(); - for(int k=0; k= xMin){ - double availSurf = ((xMax-xMin)*camax-surface); - if(dlb[i]>0) - h[i].updateUpperBound((int)Math.floor((availSurf/(double)dlb[i])+0.01),aCause); - if(hlb[i]>0) - d[i].updateUpperBound((int)Math.floor((availSurf/(double)hlb[i])+0.01),aCause); - surface += (long) dlb[i] * hlb[i]; // potential overflow - if(xMax>xMin){ - capa.updateLowerBound((int)Math.ceil(surface/(xMax-xMin)-0.01),aCause); - }if(surface>(xMax-xMin)*camax){ - aCause.fails(); // TODO: could be more precise, for explanation purpose - } - } - } - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/NaiveTimeCumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/NaiveTimeCumulFilter.java deleted file mode 100644 index 2a81565c42..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/NaiveTimeCumulFilter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; - -import java.util.Arrays; -import java.util.OptionalInt; -import java.util.stream.IntStream; - -/** - * Time-based filtering (compute the profile over every point in time) - * - * @author Jean-Guillaume Fages, Charles Prud'homme - */ -public class NaiveTimeCumulFilter extends CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - private int[] time = new int[31]; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public NaiveTimeCumulFilter(int nbMaxTasks) { - super(nbMaxTasks); - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - - @Override - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - int min = Integer.MAX_VALUE / 2; - int max = Integer.MIN_VALUE / 2; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()) { - int i = tIter.nextInt(); - if (s[i].getUB() < s[i].getLB() + d[i].getValue()) { - min = Math.min(min, s[i].getUB()); - max = Math.max(max, s[i].getLB() + d[i].getValue()); - } - } - if (min < max) { - if (max - min > time.length) { - time = new int[max - min]; - } else { - Arrays.fill(time, 0, max - min, 0); - } - int capaMax = capa.getValue(); - // fill mandatory parts and filter capacity - int elb, hlb; - - tIter = tasks.iterator(); - while (tIter.hasNext()) { - int i = tIter.nextInt(); - elb = s[i].getLB() + d[i].getValue(); - hlb = h[i].getValue(); - for (int t = s[i].getUB(); t < elb; t++) { - time[t - min] += hlb; - } - } - for (int i : tasks) { - if (d[i].getValue() > 0 && h[i].getValue() > 0) { - if (s[i].getLB() + d[i].getValue() > min) { - filterInf(s[i], d[i].getValue(), h[i].getValue(), min, max, time, capaMax, aCause); - } - if (s[i].getUB() < max) { - filterSup(s[i], d[i].getValue(), h[i].getValue(), min, max, time, capaMax, aCause); - } - } - } - OptionalInt tmax = IntStream.range(0, max - min) - .filter(k -> time[k] > capaMax).findAny(); - if (tmax.isPresent()) { - int t = tmax.getAsInt() + min; - // should fail - int[] _tasks = IntStream.range(0, s.length) - .filter(i -> - d[i].getValue() > 0 - && h[i].getValue() > 0 - && s[i].getUB() <= t - && t < s[i].getLB() + d[i].getValue()) - .toArray(); - if (_tasks.length > 0) { - s[_tasks[0]].updateUpperBound(t - d[_tasks[0]].getValue(), aCause); - } else { - throw new IllegalStateException(); - } - } - } - } - - private void filterInf(IntVar start, int dlb, int hlb, int min, int max, int[] time, int capaMax, Propagator aCause) throws ContradictionException { - int nbOk = 0; - int sub = start.getUB(); - for (int t = start.getLB(); t < sub; t++) { - if (t < min || t >= max || hlb + time[t - min] <= capaMax) { - nbOk++; - if (nbOk == dlb) { - return; - } - } else { - nbOk = 0; - start.updateLowerBound(t + 1, aCause); - } - } - } - - private void filterSup(IntVar start, int dlb, int hlb, int min, int max, int[] time, int capaMax, Propagator aCause) throws ContradictionException { - int nbOk = 0; - int elb = start.getLB() + dlb; - for (int t = start.getUB() + dlb; t > elb; t--) { - if (t - 1 < min || t - 1 >= max || hlb + time[t - min - 1] <= capaMax) { - nbOk++; - if (nbOk == dlb) { - return; - } - } else { - nbOk = 0; - start.updateUpperBound(t - 1 - dlb, aCause); - } - } - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Profile.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Profile.java new file mode 100644 index 0000000000..41945ee67e --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/Profile.java @@ -0,0 +1,327 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.nary.cumulative; + +import gnu.trove.list.array.TIntArrayList; +import org.chocosolver.memory.IStateBitSet; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.Task; + +import java.util.Arrays; +import java.util.List; + +import static org.chocosolver.solver.constraints.nary.cumulative.SchedulingUtils.mustBePerformed; + +/** + * Class representing the Profile data structure described in Gay, S., Hartert, R., and Schaus, P.: “Simple and Scalable Time-Table Filtering for the Cumulative Constraint”. In: Principles and Practice of Constraint Programming - 21st International Conference, CP 2015, Cork, Ireland, August 31 - September 4, 2015, Proceedings. Ed. by Gilles Pesant. Vol. 9255. Lecture Notes in Computer Science. Springer, 2015, pp. 149–157. + * + * @author Arthur Godet + * @since 19/10/2023 + */ +public class Profile { + private final int[] timePoints; + private final int[] heights; + private final TIntArrayList[] indexesTask; + private final EventPointSeries eventPointSeries; + private final TIntArrayList list; + private int idx; + private int[] time; + private int min; + private int max; + + /** + * Instantiates a new Profile. + * + * @param nbTasks the maximum number of tasks the Profile will manage + */ + public Profile(final int nbTasks) { + idx = 0; + timePoints = new int[2 * (nbTasks + 1)]; + heights = new int[2 * (nbTasks + 1)]; + indexesTask = new TIntArrayList[2 * (nbTasks + 1)]; + for (int i = 0; i < indexesTask.length; ++i) { + indexesTask[i] = new TIntArrayList(); + } + eventPointSeries = new EventPointSeries(nbTasks); + list = new TIntArrayList(nbTasks); + time = new int[31]; + } + + /** + * Clears the profile. + */ + public void clear() { + idx = 0; + } + + /** + * Returns the size of the profile, i.e. the number of rectangles. + * + * @return the size of the profile + */ + public int size() { + return idx - 1; + } + + /** + * Returns the start of the rectangle at index j. + * + * @param j the index of the rectangle + * @return the start of the rectangle at index j + */ + public int getStartRectangle(final int j) { + return timePoints[j]; + } + + /** + * Returns the end of the rectangle at index j. + * + * @param j the index of the rectangle + * @return the end of the rectangle at index j + */ + public int getEndRectangle(final int j) { + return timePoints[j + 1]; + } + + /** + * Returns the height of the rectangle at index j. + * + * @param j the index of the rectangle + * @return the height of the rectangle at index j + */ + public int getHeightRectangle(final int j) { + return heights[j]; + } + + /** + * Returns the indexes of the tasks whose compulsory part contributes to the rectangle at index j. + * + * @param j the index of the rectangle + * @return the indexes of the tasks whose compulsory part contributes to the rectangle at index j + */ + public TIntArrayList getIndexesTaskRectangle(final int j) { + return indexesTask[j]; + } + + /** + * Builds the profile for the tasks and their corresponding heights variables. + * + * @param tasks the tasks + * @param tasksHeights the heights variables of the tasks + * @param activeTasks the indexes of tasks to consider + */ + public void buildProfile(final Task[] tasks, final IntVar[] tasksHeights, final IStateBitSet activeTasks) { + clear(); + timePoints[idx] = Integer.MIN_VALUE; + heights[idx] = 0; + idx++; + min = Integer.MAX_VALUE; + max = Integer.MIN_VALUE; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + min = Math.min(min, tasks[i].getEst()); + max = Math.max(max, tasks[i].getLct()); + } + if (max - min < activeTasks.cardinality() * activeTasks.cardinality()) { + buildProfileNaive(tasks, tasksHeights, activeTasks); + } else { + buildProfileSweep(tasks, tasksHeights, activeTasks); + } + timePoints[idx] = Integer.MAX_VALUE; + heights[idx] = 0; + idx++; + } + + /** + * Builds the profile for the tasks and their corresponding heights variables, using a sweep algorithm. + * + * @param tasks the tasks + * @param tasksHeights the heights variables of the tasks + * @param activeTasks the indexes of tasks to consider + */ + private void buildProfileSweep(final Task[] tasks, final IntVar[] tasksHeights, final IStateBitSet activeTasks) { + eventPointSeries.generateEvents(tasks, tasksHeights, activeTasks); + if (!eventPointSeries.isEmpty()) { + int h = 0; + list.clear(); + final boolean lcg = tasksHeights[0].getModel().getSolver().isLCG(); + while (!eventPointSeries.isEmpty()) { + timePoints[idx] = eventPointSeries.getTimeFirstEvent(); + while (!eventPointSeries.isEmpty() && eventPointSeries.getTimeFirstEvent() == timePoints[idx]) { + Event event = eventPointSeries.removeFirstEvent(); + if (event.getType() == Event.SCP) { + if (lcg) { + list.add(event.getIndexTask()); + } + h += tasksHeights[event.getIndexTask()].getLB(); + } else { + if (lcg) { + list.remove(event.getIndexTask()); + } + h -= tasksHeights[event.getIndexTask()].getLB(); + } + } + if (lcg || h != heights[idx - 1]) { + heights[idx] = h; + if (lcg) { + indexesTask[idx].clear(); + indexesTask[idx].addAll(list); + } + idx++; + } + assert h >= 0; + } + assert h == 0; + } + } + + /** + * Builds the profile for the tasks and their corresponding heights variables, using a naive approach. + * + * @param tasks the tasks + * @param tasksHeights the heights variables of the tasks + * @param activeTasks the indexes of tasks to consider + */ + private void buildProfileNaive(final Task[] tasks, final IntVar[] tasksHeights, final IStateBitSet activeTasks) { + min = Integer.MAX_VALUE / 2; + max = Integer.MIN_VALUE / 2; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (tasks[i].hasCompulsoryPart() && mustBePerformed(tasks[i], tasksHeights[i])) { + min = Math.min(min, tasks[i].getLst()); + max = Math.max(max, tasks[i].getEct()); + } + } + if (min < max) { + if (max - min > time.length) { + time = new int[max - min]; + } else { + Arrays.fill(time, 0, max - min, 0); + } + fillTime(tasks, tasksHeights, activeTasks); + fillRectangles(); + if (tasksHeights[0].getModel().getSolver().isLCG()) { + fillLists(tasks, tasksHeights, activeTasks); + } + } + } + + /** + * Fills the time array, representing the resource consumption at a given time point, from the compulsory part of + * tasks that must be performed. + * + * @param tasks the tasks + * @param tasksHeights the heights variables + * @param activeTasks the indexes of tasks to consider + */ + private void fillTime(final Task[] tasks, final IntVar[] tasksHeights, final IStateBitSet activeTasks) { + // Fill mandatory parts + int ect; + int hlb; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (tasks[i].hasCompulsoryPart() && mustBePerformed(tasks[i], tasksHeights[i])) { + ect = tasks[i].getEct(); + hlb = tasksHeights[i].getLB(); + for (int t = tasks[i].getLst(); t < ect; t++) { + time[t - min] += hlb; + } + } + } + } + + /** + * Fills the indexesTask lists of tasks' indexes contributing to the rectangles of the profile. + * + * @param tasks the tasks + * @param tasksHeights the heights variables + * @param activeTasks the indexes of tasks to consider + */ + private void fillLists(final Task[] tasks, final IntVar[] tasksHeights, final IStateBitSet activeTasks) { + for (int k = 1; k < size(); ++k) { + final TIntArrayList listIndexes = indexesTask[k]; + listIndexes.clear(); + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (tasks[i].hasCompulsoryPart() + && mustBePerformed(tasks[i], tasksHeights[i]) + && tasks[i].getLst() < getEndRectangle(k) + && getStartRectangle(k) < tasks[i].getEct()) { + listIndexes.add(i); + } + } + } + } + + /** + * Fills the rectangles data structures from the time array. Should be called after the + * {@link #fillTime(Task[], IntVar[], IStateBitSet)} function. + */ + private void fillRectangles() { + int t = min; + int currentHeight = time[t - min]; + timePoints[idx] = t; + heights[idx] = currentHeight; + idx++; + while (t < max) { + while (t < max && time[t - min] == currentHeight) { + ++t; + } + if (t < max) { + currentHeight = time[t - min]; + } else { + currentHeight = 0; + } + timePoints[idx] = t; + heights[idx] = currentHeight; + idx++; + } + } + + /** + * Finds and returns the index of the rectangle containing the time-point in parameter. + * Runs in logarithmic time in the number of rectangles. + * + * @param time the time-point + * @return the index of the rectangle containing the time-point + */ + public int find(final int time) { + int i1 = 0; + int i2 = size(); + while (i1 < i2) { + int im = (i1 + i2) / 2; + if (timePoints[im] <= time && time < timePoints[im + 1]) { + i1 = im; + i2 = im; + } else if (timePoints[im] < time) { + i1 = im + 1; + } else if (timePoints[im] > time) { + i2 = im - 1; + } + } + return i1; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Profile["); + if (size() > 0) { + for (int i = 0; i < size(); i++) { + sb.append("<").append(timePoints[i]).append(",").append(timePoints[i + 1]).append(",").append(heights[i]).append(">"); + if (i < size() - 1) { + sb.append(","); + } + } + sb.append("<").append(timePoints[size()]).append(",").append(Integer.MAX_VALUE).append(",").append(0).append(">"); + } else { + sb.append("<").append(Integer.MIN_VALUE).append(",").append(Integer.MAX_VALUE).append(",").append(0).append(">"); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropCumulative.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropCumulative.java deleted file mode 100644 index 74da7e101c..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropCumulative.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.memory.IStateInt; -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.constraints.PropagatorPriority; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.solver.variables.events.IntEventType; -import org.chocosolver.solver.variables.events.PropagatorEventType; -import org.chocosolver.util.ESat; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.objects.setDataStructures.SetFactory; -import org.chocosolver.util.objects.setDataStructures.SetType; -import org.chocosolver.util.tools.ArrayUtils; - -import java.util.Arrays; - -/** - * Cumulative propagator - * Performs energy checking and mandatory part based filtering - * BEWARE : not idempotent, use two propagators to get the fix point - * - * @author Jean-Guillaume Fages - * @since 31/01/13 - */ -public class PropCumulative extends Propagator { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - protected final int n; - protected final IntVar[] s, d, e, h; - protected final IntVar capa; - protected CumulFilter[] filters; - protected ISet allTasks; - protected final IStateInt lastCapaMax; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - /** - * protected constructor, should not be called by a user - */ - protected PropCumulative(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, - boolean reactToFineEvt, CumulFilter... filters) { - super(ArrayUtils.append(s, d, e, h, new IntVar[]{capa}), PropagatorPriority.QUADRATIC, reactToFineEvt); - this.n = s.length; - if (!(n == d.length && n == e.length && n == h.length)) { - throw new UnsupportedOperationException(); - } - this.s = Arrays.copyOfRange(vars, 0, n); - this.d = Arrays.copyOfRange(vars, n, n * 2); - this.e = Arrays.copyOfRange(vars, n * 2, n * 3); - this.h = Arrays.copyOfRange(vars, n * 3, n * 4); - this.capa = this.vars[4 * n]; - this.filters = filters; - lastCapaMax = model.getEnvironment().makeInt(capa.getUB() + 1); - allTasks = SetFactory.makeStoredSet(SetType.BIPARTITESET,0,getModel()); - for(int t=0;t0) { - h[i].updateUpperBound(capaMax, this); - }else if(h[i].getLB()>capaMax){ - d[i].instantiateTo(0,this); - } - } - } - } - - public void filter(ISet tasks) throws ContradictionException { - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - if(h[t].getUB()==0 || d[t].getUB()==0){ - tasks.remove(t); - } - } - for (CumulFilter cf : filters) { - cf.filter(s, d, e, h, capa, tasks, this); - } - } - - @Override - public ESat isEntailed() { - int min = s[0].getUB(); - int max = e[0].getLB(); - // check start + duration = end - for (int i = 0; i < n; i++) { - min = Math.min(min, s[i].getUB()); - max = Math.max(max, e[i].getLB()); - if (s[i].getLB() + d[i].getLB() > e[i].getUB() - || s[i].getUB() + d[i].getUB() < e[i].getLB()) { - return ESat.FALSE; - } - } - // check capacity - int maxLoad = 0; - if (min <= max) { - int capamax = capa.getUB(); - int[] consoMin = new int[max - min]; - for (int i = 0; i < n; i++) { - for (int t = s[i].getUB(); t < e[i].getLB(); t++) { - consoMin[t - min] += h[i].getLB(); - if (consoMin[t - min] > capamax) { - return ESat.FALSE; - } - maxLoad = Math.max(maxLoad, consoMin[t - min]); - } - } - } - // check variables are instantiated - for (int i = 0; i < vars.length - 1; i++) { - if (!vars[i].isInstantiated()) { - return ESat.UNDEFINED; - } - } - assert min <= max; - // capacity check entailed - if (maxLoad <= vars[4 * n].getLB()) { - return ESat.TRUE; - } - // capacity not instantiated - return ESat.UNDEFINED; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(this.getClass().getSimpleName() + "("); - for (int i = 0; i < n; i++) { - sb.append("[").append(vars[i].toString()); - sb.append(",").append(vars[i + n].toString()); - sb.append(",").append(vars[i + 2 * n].toString()); - sb.append(",").append(vars[i + 3 * n].toString()).append("],"); - } - sb.append(vars[4 * n].toString()).append(")"); - return sb.toString(); - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropGraphCumulative.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropGraphCumulative.java deleted file mode 100644 index 5603345919..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropGraphCumulative.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.solver.variables.events.PropagatorEventType; -import org.chocosolver.util.objects.graphs.UndirectedGraph; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.objects.setDataStructures.SetFactory; -import org.chocosolver.util.objects.setDataStructures.SetType; -import org.chocosolver.util.sort.ArraySort; - -import java.util.BitSet; -import java.util.Comparator; - -/** - * Graph based cumulative - * Maintains incrementally overlapping tasks - * Performs energy checking and mandatory part based filtering - * BEWARE : not idempotent, use two propagators to get the fix point - * - * @author Jean-Guillaume Fages - * @since 31/01/13 - */ -public class PropGraphCumulative extends PropCumulative { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - private final UndirectedGraph g; - private final ISet tasks; - private final ISet toCompute; - private long timestamp; - private boolean full; - private final boolean fast; - - //*********************************************************************************** - // CONSTRUCTOR - //*********************************************************************************** - - /** - * Graph-based cumulative propagator: - * - only filters over subsets of overlapping tasks - * - * @param s start variables - * @param d duration variables - * @param e end variables - * @param h height variables - * @param capa capacity variable - * @param fast reduces the number of propagation (less filtering) - * @param filters filtering algorithm to use - */ - public PropGraphCumulative(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, boolean fast, - CumulFilter... filters) { - super(s, d, e, h, capa, true, filters); - this.g = new UndirectedGraph(model, n, SetType.BITSET, true); - this.tasks = SetFactory.makeBipartiteSet(0); - this.toCompute = SetFactory.makeBipartiteSet(0); - this.fast = fast; - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - @Override - public void propagate(int evtmask) throws ContradictionException { - if (PropagatorEventType.isFullPropagation(evtmask)) { - super.propagate(evtmask); - graphComputation(); - } else { - if (full) { - filter(allTasks); - } else { - int count = 0; - ISetIterator tcIt = toCompute.iterator(); - while (tcIt.hasNext()) { - int i = tcIt.nextInt(); - ISetIterator it = g.getNeighborsOf(i).iterator(); - while (it.hasNext()) { - int j = it.nextInt(); - if (disjoint(i, j)) { - g.removeEdge(i, j); - } - } - count += g.getNeighborsOf(i).size(); - if (count >= 2 * n) break; - } - if (count >= 2 * n) { - filter(allTasks); - } else { - ISetIterator iter = toCompute.iterator(); - while (iter.hasNext()) { - filterAround(iter.nextInt()); - } - } - } - } - toCompute.clear(); - full = false; - } - - @Override - public void propagate(int varIdx, int mask) throws ContradictionException { - if (timestamp != model.getEnvironment().getTimeStamp()) { - timestamp = model.getEnvironment().getTimeStamp(); - toCompute.clear(); - full = false; - } - if (varIdx < 4 * n) { - int v = varIdx % n; - if (h[v].getUB() == 0 || d[v].getUB() == 0) { - allTasks.remove(v); - ISetIterator gIt = g.getNeighborsOf(v).iterator(); - while (gIt.hasNext()) { - g.removeEdge(v, gIt.nextInt()); - } - } else if (s[v].getUB() < e[v].getLB() || !fast) { - toCompute.add(v); - } - } else { - updateMaxCapa(); - full = true; - } - forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); - } - - protected void filterAround(int taskIndex) throws ContradictionException { - tasks.clear(); - tasks.add(taskIndex); - ISetIterator env = g.getNeighborsOf(taskIndex).iterator(); - while (env.hasNext()) { - tasks.add(env.nextInt()); - } - filter(tasks); - } - - private boolean disjoint(int i, int j) { - return s[i].getLB() >= e[j].getUB() || s[j].getLB() >= e[i].getUB(); - } - - private void graphComputation() { - for (int i = 0; i < n; i++) { - g.getNeighborsOf(i).clear(); - } - Event[] events = new Event[2 * n]; - ArraySort sort = new ArraySort<>(events.length, true, false); - Comparator eventComparator = (e1, e2) -> { - if (e1.date == e2.date) { - return e1.type - e2.type; - } - return e1.date - e2.date; - }; - BitSet tprune = new BitSet(n); - for (int i = 0; i < n; i++) { - events[i] = new Event(); - events[i].set(START, i, s[i].getLB()); - events[i + n] = new Event(); - events[i + n].set(END, i, e[i].getUB()); - } - sort.sort(events, 2 * n, eventComparator); - int timeIndex = 0; - while (timeIndex < n * 2) { - Event event = events[timeIndex++]; - switch (event.type) { - case (START): - boolean eok = h[event.index].getUB() > 0 && d[event.index].getUB() > 0; - if (eok) { - for (int i = tprune.nextSetBit(0); i >= 0; i = tprune.nextSetBit(i + 1)) { - if (h[i].getUB() > 0 && d[i].getUB() > 0) { - g.addEdge(i, event.index); - } - } - } - tprune.set(event.index); - break; - case (END): - tprune.clear(event.index); - break; - default: - throw new UnsupportedOperationException(); - } - } - } - - private static class Event { - protected int type; - protected int index; - protected int date; - - protected void set(int t, int i, int d) { - date = d; - type = t; - index = i; - } - } - - private final static int START = 1, END = 2; - -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropagatorCumulative.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropagatorCumulative.java new file mode 100644 index 0000000000..16c405a9c7 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/PropagatorCumulative.java @@ -0,0 +1,798 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.nary.cumulative; + +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.map.hash.TIntIntHashMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import org.chocosolver.memory.IStateBitSet; +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.constraints.Explained; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.constraints.PropagatorPriority; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.OptionalTask; +import org.chocosolver.solver.variables.Task; +import org.chocosolver.solver.variables.events.IntEventType; +import org.chocosolver.solver.variables.events.PropagatorEventType; +import org.chocosolver.util.ESat; +import org.chocosolver.util.sort.ArraySort; +import org.chocosolver.util.sort.IntComparator; + +import java.util.*; + +import static org.chocosolver.solver.constraints.nary.cumulative.SchedulingUtils.*; + +/** + * Propagator for the Cumulative constraint. + * It uses :

        + *
      • the scalable TimeTable algorithm from Gay et al. [Gay2015]
      • + *
      • the OverloadChecking algorithm from Vilim [Vilim2011]
      • + *
      + * The explanations are the ones described in [Schutt2011]. + *
      + * Gay, S., Hartert, R., and Schaus, P.: “Simple and Scalable Time-Table Filtering for the Cumulative Constraint”. In: Principles and Practice of Constraint Programming - 21st International Conference, CP 2015, Cork, Ireland, August 31 - September 4, 2015, Proceedings. Ed. by Gilles Pesant. Vol. 9255. Lecture Notes in Computer Science. Springer, 2015, pp. 149–157 + *
      + * Petr Vilım: “Timetable Edge Finding Filtering Algorithm for Discrete Cumulative Resources”. In: Integration of AI and OR Techniques in Constraint Programming for Combinatorial Optimization Problems - 8th International Conference, CPAIOR 2011, Berlin, Germany, May 23-27, 2011. Proceedings. Ed. by Tobias Achterberg and J. Christopher Beck. Vol. 6697. Lecture Notes in Computer Science. Springer, 2011, pp. 230–245 + *
      + * Andreas Schutt et al. “Explaining the cumulative propagator”. In: Constraints An Int. J. 16.3 (2011), pp. 250–282 + * + * @author Arthur Godet + * @since 19/10/2023 + */ +@Explained +public class PropagatorCumulative extends Propagator { + private static int getFreeDuration(final Task task) { + final int pTT = Math.max(0, task.getEct() - task.getLst()); + return task.getDuration().getLB() - pTT; + } + + private static int compareTaskWithFreeParts(final Task a, final Task b) { + if (a.getEst() == b.getEst()) { + return a.getEst() + getFreeDuration(a) - (b.getEst() + getFreeDuration(b)); + } + return a.getEst() - b.getEst(); + } + + private static IntVar[] extractIntVars(final Task[] tasks, final IntVar[] heights, final IntVar capacity) { + final List list = new ArrayList<>(); + for (int i = 0; i < tasks.length; i++) { + list.add(tasks[i].getStart()); + list.add(tasks[i].getDuration()); + list.add(tasks[i].getEnd()); + list.add(heights[i]); + } + list.add(capacity); + for (int i = 0; i < tasks.length; i++) { + if (!tasks[i].mustBePerformed() && tasks[i].mayBePerformed()) { + list.add(((OptionalTask) tasks[i]).getPerformed()); + } + } + return list.toArray(new IntVar[0]); + } + + protected final Task[] tasks; + protected final IntVar[] heights; + private final IntVar capacity; + private final IStateBitSet activeTasks; + private final Map mapPerformedVarToIdxTask; + // For the TimeTable + private final Profile profile; + // For the overloadChecking + private final TIntIntHashMap ttAfter; + private final List tasksWithFreeParts; + private final Comparator comparatorFreeParts; + private final Map mapTaskToHeight; + // For energy naive + private final boolean energyNaive; + private final IntComparator comparator; + private final int[] sorArray; + private final ArraySort sorter; + // For disjunctive energy naive + private final boolean disjunctiveEnergyNaive; + private final ArraySort sort; + private final IntComparator comp; + private final int[] tsks; + // For explanations + private final TIntArrayList literals; + + public PropagatorCumulative(final Task[] tasks, final IntVar[] heights, final IntVar capacity) { + this(tasks, heights, capacity, false, false); + } + + public PropagatorCumulative( + final Task[] tasks, + final IntVar[] heights, + final IntVar capacity, + final boolean energyNaive, + final boolean disjunctiveEnergyNaive + ) { + super(extractIntVars(tasks, heights, capacity), PropagatorPriority.QUADRATIC, true); + this.tasks = tasks; + this.heights = heights; + this.capacity = capacity; + this.activeTasks = this.getModel().getEnvironment().makeBitSet(tasks.length); + mapPerformedVarToIdxTask = new HashMap<>(); + profile = new Profile(tasks.length); + tasksWithFreeParts = new ArrayList<>(tasks.length); + ttAfter = new TIntIntHashMap(2 * tasks.length); + comparatorFreeParts = PropagatorCumulative::compareTaskWithFreeParts; + mapTaskToHeight = new HashMap<>(tasks.length); + this.energyNaive = energyNaive; + sorArray = new int[tasks.length]; + sorter = new ArraySort<>(tasks.length,false,true); + comparator = (i1, i2) -> { + int coef1 = (100 * tasks[i1].getMinDuration() * heights[i1].getLB()) / (tasks[i1].getLct() - tasks[i1].getEst()); + int coef2 = (100 * tasks[i2].getMinDuration() * heights[i2].getLB()) / (tasks[i2].getLct() - tasks[i2].getEst()); + return coef2 - coef1; + }; + this.disjunctiveEnergyNaive = disjunctiveEnergyNaive; + sort = new ArraySort<>(tasks.length, false, true); + comp = (i1, i2) -> tasks[i1].getEst() - tasks[i2].getEst(); + tsks = new int[tasks.length]; + + for (int i = 0; i < tasks.length; ++i) { + if (!tasks[i].mustBePerformed() && tasks[i].mayBePerformed()) { + mapPerformedVarToIdxTask.put(((OptionalTask) tasks[i]).getPerformed(), i); + } + mapTaskToHeight.put(tasks[i], heights[i]); + } + literals = new TIntArrayList(4 * tasks.length + 3); + } + + @Override + public int getPropagationConditions(int idx) { + final int n = tasks.length; + if (idx == 4 * n) { // capacity variable + return IntEventType.upperBoundAndInst(); + } else if (idx % 4 == 3 && idx < 4 * n) { // heights variables + return IntEventType.lowerBoundAndInst(); + } else { + return IntEventType.boundAndInst(); + } + } + + @Override + public void propagate(int evtmask) throws ContradictionException { + if (PropagatorEventType.isFullPropagation(evtmask)) { + capacity.updateLowerBound(0, this); + activeTasks.clear(); + for (int i = 0; i < tasks.length; ++i) { + heights[i].updateLowerBound(0, this, Reason.undef()); + tasks[i].updateMinDuration(0, this, Reason.undef()); + propagateTaskHeight(i); + if (isActive(i)) { + activeTasks.set(i); + } + } + } + if (activeTasks.size() <= 1) { + // No need for TimeTable or OverloadChecking in such a case + return; + } + scalableTimeTable(); + overloadChecking(); + updateHeights(); + if (energyNaive) { + energyNaive(); + } + if (disjunctiveEnergyNaive && capacity.isInstantiatedTo(1) && activeTasks.size() < 50) { + disjunctiveEnergyNaive(); + } + } + + @Override + public void propagate(int varIdx, int mask) throws ContradictionException { + final int n = tasks.length; + if (varIdx == 4 * n) { // capacity variable + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + propagateTaskHeight(i); + if (!isActive(i)) { + activeTasks.clear(i); + } + } + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); + } else if (varIdx % 4 == 3 && varIdx < 4 * n) { // heights variables + final int idxTask = varIdx / 4; + propagateTaskHeight(idxTask); + if (!isActive(idxTask)) { + activeTasks.clear(idxTask); + } else { + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); + } + } else if (varIdx > 4 * n) { // performed variables + final int idxTask = mapPerformedVarToIdxTask.get(vars[varIdx]); + if (!isActive(idxTask)) { + activeTasks.clear(idxTask); + } + } else { + final int idxTask = varIdx / 4; + if (!isActive(idxTask)) { + activeTasks.clear(idxTask); + } else { + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); + } + } + } + + @Override + public ESat isEntailed() { + final int n = tasks.length; + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < n; i++) { + if (mustBePerformed(tasks[i], heights[i])) { + min = Math.min(min, tasks[i].getLst()); + max = Math.max(max, tasks[i].getEct()); + } + } + // check capacity + int maxLoad = 0; + if (min <= max) { + int[] consoMin = new int[max - min]; + for (int i = 0; i < n; i++) { + if (mustBePerformed(tasks[i], heights[i])) { + for (int t = tasks[i].getLst(); t < tasks[i].getEct(); t++) { + consoMin[t - min] += heights[i].getLB(); + if (consoMin[t - min] > capacity.getUB()) { + return ESat.FALSE; + } + maxLoad = Math.max(maxLoad, consoMin[t - min]); + } + } + } + } + // check variables are instantiated + for (int i = 0; i < n; i++) { + if (!tasks[i].getStart().isInstantiated() + || !tasks[i].getDuration().isInstantiated() + || !tasks[i].getEnd().isInstantiated() + || !heights[i].isInstantiated() + || tasks[i].mayBePerformed() && !tasks[i].mustBePerformed() + ) { + return ESat.UNDEFINED; + } + } + if (!capacity.isInstantiated()) { + return ESat.UNDEFINED; + } else if (min <= max && maxLoad <= capacity.getValue()) { + return ESat.TRUE; + } + return ESat.UNDEFINED; + } + + /** + * Builds the profil and applies TimeTable filtering (idempotent). + * + * @throws ContradictionException when a filtering error is encountered + */ + private void scalableTimeTable() throws ContradictionException { + boolean hasFiltered; + do { + buildProfile(); + hasFiltered = scalableTimeTableFilter(); + } while (hasFiltered); + } + + /** + * Builds the profile (and stores explanations if lcg() is true) and filters the capacity lower bound as the + * maximum of the height of the rectangles in the profile. + * + * @throws ContradictionException when a filtering error is encountered + */ + private void buildProfile() throws ContradictionException { + profile.buildProfile(tasks, heights, activeTasks); + int idxRectMaxHeight = 0; + for (int j = 0; j < profile.size(); j++) { + if (profile.getHeightRectangle(idxRectMaxHeight) < profile.getHeightRectangle(j)) { + idxRectMaxHeight = j; + } + } + if (lcg()) { + literals.clear(); + final int begin = profile.getStartRectangle(idxRectMaxHeight) + + ((profile.getEndRectangle(idxRectMaxHeight) - profile.getStartRectangle(idxRectMaxHeight) - 1) / 2); + addLiteralsTasks( + literals, + profile.getIndexesTaskRectangle(idxRectMaxHeight), + begin, + begin + 1 + ); + capacity.updateLowerBound(profile.getHeightRectangle(idxRectMaxHeight), this, buildReason(literals)); + } else { + capacity.updateLowerBound(profile.getHeightRectangle(idxRectMaxHeight), this); + } + } + + /** + * Returns the negated literal -[[v <= val]] = [[v >= val + 1]]. + * + * @param v an integer variable + * @param val an integer value + * @return the negated literal -[[v <= val]] = [[v >= val + 1]] + */ + private int getNegLeqLit(final IntVar v, final int val) { + if (v.isInstantiated()) { + return v.getValLit(); + } + return v.hasEnumeratedDomain() ? v.getGELit(val + 1) : v.getMaxLit(); + } + + /** + * Returns the negated literal -[[v >= val]] = [[ v <= val - 1]]. + * + * @param v an integer variable + * @param val an integer value + * @return the negated literal -[[v >= val]] = [[ v <= val - 1]] + */ + private int getNegGeqLit(final IntVar v, final int val) { + if (v.isInstantiated()) { + return v.getValLit(); + } + return v.hasEnumeratedDomain() ? v.getLELit(val - 1) : v.getMinLit(); + } + + /** + * Add the max literal of the capacity and the literals of tasks whose index is in the list in parameters. Four + * literals are added : one for the task's start, one for the task's end, one for the task's duration and one for + * the task's height. + * + * @param literals the list of literals + * @param indexesTask the list of indexes of tasks + * @param begin the begin instant + * @param end then end instant + */ + private void addLiteralsCapacityAndTasks( + final TIntArrayList literals, + final TIntArrayList indexesTask, + final int begin, + final int end + ) { + literals.add(capacity.getMaxLit()); + addLiteralsTasks(literals, indexesTask, begin, end); + } + + /** + * Add literals of tasks whose index is in the list in parameters. Four literals are added : one for the task's + * start, one for the task's end, one for the task's duration and one for the task's height. + * + * @param literals the list of literals + * @param indexesTask the list of indexes of tasks + * @param begin the begin instant + * @param end then end instant + */ + private void addLiteralsTasks( + final TIntArrayList literals, + final TIntArrayList indexesTask, + final int begin, + final int end + ) { + for (int k = 0; k < indexesTask.size(); ++k) { + final int i = indexesTask.getQuick(k); + literals.add(getNegGeqLit(tasks[i].getEnd(), Math.min(tasks[i].getEnd().getLB(), end))); + literals.add(getNegLeqLit(tasks[i].getStart(), Math.max(tasks[i].getStart().getUB(), begin))); + literals.add(tasks[i].getDuration().getMinLit()); + literals.add(heights[i].getMinLit()); + } + } + + /** + * Builds a Reason from the literals in parameters. + * + * @param literals a list of literals + * @return the reason + */ + private Reason buildReason(final TIntArrayList literals) { + final int[] ps = new int[literals.size() + 1]; + for (int i = 0; i < literals.size(); ++i) { + ps[i + 1] = literals.getQuick(i); + } + return Reason.r(ps); + } + + /** + * Applies filtering according to the TimeTable rule, and returns whether the profile should be recomputed or not + * (a task that must be performed and with a (potentially new) compulsory part has been filtered). + * + * @return true iff the profile should be recomputed + * @throws ContradictionException when a filtering error is encountered + */ + private boolean scalableTimeTableFilter() throws ContradictionException { + boolean shouldRecomputeTimeTable = false; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + final Task task = tasks[i]; + if (mayBePerformed(task, heights[i])) { + if (scalableTimeTableFilterEst(task, heights[i])) { + shouldRecomputeTimeTable |= task.hasCompulsoryPart() && mustBePerformed(task, heights[i]); + } + if (scalableTimeTableFilterLct(task, heights[i])) { + shouldRecomputeTimeTable |= task.hasCompulsoryPart() && mustBePerformed(task, heights[i]); + } + } + if (!isActive(i)) { + activeTasks.clear(i); + } + } + return shouldRecomputeTimeTable; + } + + /** + * Applies filtering according to the TimeTable rule on the earliest start time, and returns whether the task or the + * height variable have been filtered. + * + * @param task a task + * @param height the height variable + * @return true iff the task or the height variable have been filtered + * @throws ContradictionException when a filtering error is encountered + */ + private boolean scalableTimeTableFilterEst(final Task task, final IntVar height) throws ContradictionException { + boolean hasFiltered = false; + if (!task.getStart().isInstantiated()) { + int j = profile.find(task.getEst()); + while (j < profile.size() && profile.getStartRectangle(j) < Math.min(task.getEct(), task.getLst())) { + if (capacity.getUB() - height.getLB() < profile.getHeightRectangle(j)) { + final Reason reason; + if (lcg()) { + literals.clear(); + final int end = Math.min(task.getEct(), profile.getEndRectangle(j)); + literals.add(getNegGeqLit(task.getEnd(), end)); + literals.add(task.getDuration().getMinLit()); + literals.add(height.getMinLit()); + addLiteralsCapacityAndTasks(literals, profile.getIndexesTaskRectangle(j), end - 1, end); + reason = buildReason(literals); + } else { + reason = Reason.undef(); + } + hasFiltered |= filterEst(task, height, Math.min(task.getLst(), profile.getEndRectangle(j)), this, reason); + } + j++; + } + } + return hasFiltered; + } + + /** + * Applies filtering according to the TimeTable rule on the latest completion time, and returns whether the task or + * the height variable have been filtered. + * + * @param task a task + * @param height the height variable + * @return true iff the task or the height variable have been filtered + * @throws ContradictionException when a filtering error is encountered + */ + private boolean scalableTimeTableFilterLct(final Task task, final IntVar height) throws ContradictionException { + boolean hasFiltered = false; + if (!task.getEnd().isInstantiated()) { + int j = profile.find(task.getLct() - 1); + while (j >= 1 && profile.getEndRectangle(j) > Math.max(task.getLst(), task.getEct())) { + if (capacity.getUB() - height.getLB() < profile.getHeightRectangle(j)) { + final Reason reason; + if (lcg()) { + literals.clear(); + final int begin = Math.max(profile.getStartRectangle(j), task.getLst()); + literals.add(getNegLeqLit(task.getStart(), begin)); + literals.add(task.getDuration().getMinLit()); + literals.add(height.getMinLit()); + addLiteralsCapacityAndTasks(literals, profile.getIndexesTaskRectangle(j), begin, begin + 1); + reason = buildReason(literals); + } else { + reason = Reason.undef(); + } + hasFiltered |= filterLct(task, height, Math.max(profile.getStartRectangle(j), task.getEct()), this, reason); + } + j--; + } + } + return hasFiltered; + } + + /** + * Computes the ttAfter map, which, to a given time point, associates the total amount of energy in the profile + * after said time point. + */ + private void computeTtAfter() { + ttAfter.clear(); + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (mustBePerformed(tasks[i], heights[i])) { + final int est = tasks[i].getEst(); + final int lct = tasks[i].getLct(); + if (!ttAfter.containsKey(est) || !ttAfter.containsKey(lct)) { + computeTtAfter(est, lct); + } + } + } + } + + /** + * Computes and adds to the ttAfter map, the total amount of energy in the profile after the latest completion time + * and the earliest start time of a task. + * + * @param est the earliest start time of a task + * @param lct the latest completion time of a task + */ + private void computeTtAfter(final int est, final int lct) { + int ttAfterTime = 0; + int idx = profile.size() - 1; + while (0 <= idx && lct <= profile.getStartRectangle(idx)) { + ttAfterTime += profile.getHeightRectangle(idx) * (profile.getEndRectangle(idx) - profile.getStartRectangle(idx)); + idx--; + } + if (0 <= idx && lct < profile.getEndRectangle(idx)) { + ttAfter.put(lct, ttAfterTime + profile.getHeightRectangle(idx) * (profile.getEndRectangle(idx) - lct)); + } else { + ttAfter.put(lct, ttAfterTime); + } + while (0 <= idx && est <= profile.getStartRectangle(idx)) { + ttAfterTime += profile.getHeightRectangle(idx) * (profile.getEndRectangle(idx) - profile.getStartRectangle(idx)); + idx--; + } + if (0 <= idx && est < profile.getEndRectangle(idx)) { + ttAfter.put(est, ttAfterTime + profile.getHeightRectangle(idx) * (profile.getEndRectangle(idx) - est)); + } else { + ttAfter.put(est, ttAfterTime); + } + } + + /** + * Computes the list of tasks that must be performed and with a non-null free duration, and sorts it. + */ + private void computeTasksWithFreeParts() { + tasksWithFreeParts.clear(); + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (mustBePerformed(tasks[i], heights[i]) && getFreeDuration(tasks[i]) > 0) { + tasksWithFreeParts.add(tasks[i]); + } + } + // Sort free parts + tasksWithFreeParts.sort(comparatorFreeParts); + } + + /** + * Applies the OverloadChecking rule. Should be called after {@link #scalableTimeTable()}. + * + * @throws ContradictionException when a filtering error is encountered + */ + private void overloadChecking() throws ContradictionException { + // From PropCumulativeVilim2011 + computeTtAfter(); + computeTasksWithFreeParts(); + int eEF; + Task a; + Task b; + int lctB; + for (int i = 0; i < tasksWithFreeParts.size(); i++) { + b = tasksWithFreeParts.get(i); + lctB = b.getLct(); + eEF = 0; + for (int k = tasksWithFreeParts.size() - 1; k >= 0; k--) { + a = tasksWithFreeParts.get(k); + if (a.getLct() <= b.getLct()) { + eEF += getFreeDuration(a) * mapTaskToHeight.get(a).getLB(); + if (capacity.getUB() * (lctB - a.getEst()) < eEF + ttAfter.get(a.getEst()) - ttAfter.get(lctB)) { + fails(); // TODO : should be explained + break; + } + } + } + } + } + + /** + * Filters the heights variables based on the profile: the height of a task cannot be higher than the capacity + * minus the maximum height of rectangles that the task intersects. + * Should be called after {@link #scalableTimeTable()}. + * + * @throws ContradictionException when a filtering error is encountered + */ + private void updateHeights() throws ContradictionException { + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + final Task task = tasks[i]; + if (task.getLst() < task.getEct() && task.mustBePerformed()) { + final IntVar height = heights[i]; + int j = profile.find(task.getLst()); + while (j < profile.size() && profile.getStartRectangle(j) < task.getEct()) { + if (lcg()) { + literals.clear(); + final int begin = Math.max(profile.getStartRectangle(j), task.getLst()); + literals.add(getNegGeqLit(task.getEnd(), begin)); + literals.add(task.getDuration().getMinLit()); + literals.add(height.getMinLit()); + addLiteralsCapacityAndTasks(literals, profile.getIndexesTaskRectangle(j), begin, begin + 1); + final Reason reason = buildReason(literals); + height.updateUpperBound(capacity.getUB() - (profile.getHeightRectangle(j) - height.getLB()), this, reason); + } else { + height.updateUpperBound(capacity.getUB() - (profile.getHeightRectangle(j) - height.getLB()), this); + } + j++; + } + if (!isActive(i)) { + activeTasks.clear(i); + } + } + } + } + + /** + * Applies a naive version of the Energetic Reasoning rule. + * TODO : should be completed/replaced with state-of-the-art filtering algorithm (with explanations) + * + * @throws ContradictionException when a filtering error is encountered + */ + private void energyNaive() throws ContradictionException { + int idx = 0; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (mustBePerformed(tasks[i], heights[i]) && tasks[i].getMinDuration() > 0) { + sorArray[idx++] = i; + } + } + sorter.sort(sorArray,idx,comparator); + double xMin = Integer.MAX_VALUE / 2d; + double xMax = Integer.MIN_VALUE / 2d; + double surface = 0; + double camax = capacity.getUB(); + for (int k = 0; k < idx; k++) { + final int i = sorArray[k]; + xMax = Math.max(xMax, tasks[i].getLct()); + xMin = Math.min(xMin, tasks[i].getEst()); + if (xMax >= xMin) { + final double availSurf = (xMax - xMin) * camax - surface; + heights[i].updateUpperBound((int) Math.floor((availSurf / tasks[i].getMinDuration()) + 0.01), this); + tasks[i].updateMaxDuration((int) Math.floor((availSurf / heights[i].getLB()) + 0.01), this); + surface += (long) tasks[i].getMinDuration() * heights[i].getLB(); // potential overflow + if (xMax > xMin) { + capacity.updateLowerBound((int) Math.ceil(surface / (xMax - xMin) - 0.01), this); + } + if (surface > (xMax - xMin) * camax) { + fails(); // TODO: could be more precise, for explanation purpose + } + } + } + } + + /** + * Applies a naive version of the Energetic Reasoning rule for Disjunctive constraint. + * TODO : should be completed/replaced with state-of-the-art filtering algorithm (with explanations) + * + * @throws ContradictionException when a filtering error is encountered + */ + private void disjunctiveEnergyNaive() throws ContradictionException { + // filtering algorithm for disjunctive constraint + capacity.updateUpperBound(1, this); + // remove tasks that do not consume any resource + int tskSize = 0; + for (int i = activeTasks.nextSetBit(0); i != -1; i = activeTasks.nextSetBit(i + 1)) { + if (mustBePerformed(tasks[i], heights[i]) && tasks[i].getMinDuration() > 0) { + tsks[tskSize++] = i; + } + } + sort.sort(tsks, tskSize, comp); + // run energetic reasoning + for (int x = 0; x < tskSize; x++) { + final int task1 = tsks[x]; + for (int y = 0; y < tskSize; y++) { + if (x != y) { + final int task2 = tsks[y]; + final int t1 = tasks[task1].getEst(); + final int t2 = tasks[task2].getLct(); + if (tasks[task1].getEct() > tasks[task2].getLst()) { + tasks[task1].updateEst(tasks[task2].getEct(), this); + tasks[task2].updateLct(tasks[task1].getLst(), this); + } else if (t1 < t2 && (t1 < tasks[task2].getEct() || t2 > tasks[task1].getLst())) { + int w = 0; + for (int z = 0; z < tskSize; z++) { + final int task3 = tsks[z]; + if (task3 != task1 && task3 != task2) { + if (tasks[task3].getEst() >= t2) { + break; + } + int pB = tasks[task3].getMinDuration() * heights[task3].getLB(); + int pbt1 = Math.max(0, pB - Math.max(0, t1 - tasks[task3].getEst())); + int pbt2 = Math.max(0, pB - Math.max(0, tasks[task3].getLct() - t2)); + int pbt = Math.min(pbt1, pbt2); + w += Math.min(t2 - t1, pbt); + } + } + if (w + tasks[task1].getMinDuration() + tasks[task2].getMinDuration() > t2 - t1) { + tasks[task1].updateEst(tasks[task2].getEct(), this); + tasks[task2].updateLct(tasks[task1].getLst(), this); + } + } + } + } + } + } + + /** + * Returns true iff the task at the index in parameter should be considered, i.e. the task may be performed, + * its height variable's upper bound is strictly greater than 0, and the task duration's upper bound is strictly + * greater than 0. + * + * @param idxTask the index of the task + * @return true iff the task at the index in parameter should be considered + */ + private boolean isActive(final int idxTask) { + return mayBePerformed(tasks[idxTask], heights[idxTask]) && tasks[idxTask].getMaxDuration() > 0; + } + + /** + * Propagates the capacity constraint for the task at index i. More particularly, it enforces the following equation : + *

      + * tasks[i].mustBePerformed() ⇒ (heights[i] ≤ capacity ∨ tasks[i].duration = 0). + * + * @param i the index of the task + * @throws ContradictionException whenever a filtering error occurs + */ + private void propagateTaskHeight(final int i) throws ContradictionException { + if (lcg()) { + propTaskHeightExplained(i); + } else { + propTaskHeight(i); + } + } + + /** + * Propagates the capacity constraint for the task at index i. More particularly, it enforces the following equation : + *

      + * tasks[i].mustBePerformed() ⇒ (heights[i] ≤ capacity ∨ tasks[i].duration = 0). + * + * @param i the index of the task + * @throws ContradictionException whenever a filtering error occurs + */ + private void propTaskHeight(final int i) throws ContradictionException { + if (capacity.getUB() < heights[i].getLB()) { + if (tasks[i].mustBePerformed()) { + tasks[i].updateDuration(0, 0, this); + } else { + tasks[i].forceToBeOptional(this); + } + } else if (tasks[i].mustBePerformed() && tasks[i].getMinDuration() > 0) { + heights[i].updateUpperBound(capacity.getUB(), this); + capacity.updateLowerBound(heights[i].getLB(), this); + } + } + + /** + * Propagates the capacity constraint for the task at index i with explanations. More particularly, it enforces the + * following equation : + *

      + * tasks[i].mustBePerformed() ⇒ (heights[i] ≤ capacity ∨ tasks[i].duration = 0). + * + * @param i the index of the task + * @throws ContradictionException whenever a filtering error occurs + */ + private void propTaskHeightExplained(final int i) throws ContradictionException { + if (capacity.getUB() < heights[i].getLB()) { + if (tasks[i].mustBePerformed()) { + final Reason reason = tasks[i] instanceof OptionalTask + ? Reason.r(heights[i].getLELit(heights[i].getLB() - 1), capacity.getGELit(capacity.getUB() + 1), ((OptionalTask) tasks[i]).getPerformed().getEQLit(0)) + : Reason.r(heights[i].getLELit(heights[i].getLB() - 1), capacity.getGELit(capacity.getUB() + 1)); + tasks[i].updateMaxDuration(0, this, reason); + } else { + tasks[i].forceToBeOptional(this, Reason.r(heights[i].getLELit(heights[i].getLB() - 1), capacity.getGELit(capacity.getUB() + 1))); + } + } else if (tasks[i].mustBePerformed() && tasks[i].getMinDuration() > 0) { + final int durationLit = tasks[i].getDuration().getEQLit(0); + final Reason reasonHeight; + final Reason reasonCapacity; + if (tasks[i] instanceof OptionalTask) { + final int performedLit = ((OptionalTask) tasks[i]).getPerformed().getEQLit(0); + reasonHeight = Reason.r(capacity.getGELit(capacity.getUB() + 1), durationLit, performedLit); + reasonCapacity = Reason.r(heights[i].getLELit(heights[i].getLB() - 1), durationLit, performedLit); + } else { + reasonHeight = Reason.r(capacity.getGELit(capacity.getUB() + 1), durationLit); + reasonCapacity = Reason.r(heights[i].getLELit(heights[i].getLB() - 1), durationLit); + } + heights[i].updateUpperBound(capacity.getUB(), this, reasonHeight); + capacity.updateLowerBound(heights[i].getLB(), this, reasonCapacity); + } + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SchedulingUtils.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SchedulingUtils.java new file mode 100644 index 0000000000..82b5751051 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SchedulingUtils.java @@ -0,0 +1,293 @@ +package org.chocosolver.solver.constraints.nary.cumulative; + +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.ICause; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.OptionalTask; +import org.chocosolver.solver.variables.Task; +import org.chocosolver.solver.variables.events.PropagatorEventType; + +import java.util.List; + +public class SchedulingUtils { + /** + * Returns true iff the combination of the task and the height variable can be considered as optional. + * + * @param task a task + * @param height its height variable in a propagator + * @return true iff the combination of the task and the height variable can be considered as optional + */ + public static boolean mayBePerformed(Task task, IntVar height) { + return (height == null || height.getUB() > 0) && task.mayBePerformed(); + } + + /** + * Returns true iff the combination of the task and the height variable must be considered as a performed task (not optional). + * + * @param task a task + * @param height its height variable in a propagator + * @return true iff the combination of the task and the height variable must be considered as a performed task (not optional) + */ + public static boolean mustBePerformed(Task task, IntVar height) { + return (height == null || height.getLB() > 0) && task.mustBePerformed(); + } + + /** + * Forces the task to be considered as optional. If the task must be performed, the height variable cannot be higher than 0. Otherwise, if the + * task is optional, it is set as such. Else, a ContradictionException is thrown. + * + * @param task a task + * @param height a height variable + * @param cause the propagator that forces the task to be count as optional + * @return true iff setting the task as optional has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterOptionalTask(Task task, IntVar height, ICause cause) throws ContradictionException { + boolean isOpt = task instanceof OptionalTask; + if (height != null && (!isOpt || task.mustBePerformed())) { + // if the task is not optional or if it must be performed + return height.updateUpperBound(0, cause); + } else if (isOpt) { + // if the task is optional and can be set as such + return task.forceToBeOptional(cause); + } else { + ContradictionException ex = new ContradictionException(); + ex.set(cause, task.getStart(), "Task " + task + " should be set optional but it cannot be"); + throw ex; + } + } + + /** + * Updates the earliest start time (est) of the task, considering its height variable. The height variable's upper bound is set to 0, or the task + * can become optional if updating its est would empty the start variable's domain. + * + * @param task a task + * @param height a height variable + * @param est the est + * @param cause the propagator that filters the est + * @return true iff updating the est has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterEst(Task task, IntVar height, int est, ICause cause) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && est > task.getLst()) { + return height.updateUpperBound(0, cause); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateEst(est, cause); + } else { + return false; + } + } + + /** + * Updates the earliest start time (est) of the task, considering its height variable. The height variable's upper bound is set to 0, or the task + * can become optional if updating its est would empty the start variable's domain. + * + * @param task a task + * @param height a height variable + * @param est the est + * @param cause the propagator that filters the est + * @param reason the reason of the filtering + * @return true iff updating the est has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterEst(Task task, IntVar height, int est, ICause cause, Reason reason) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && est > task.getLst()) { + return height.updateUpperBound(0, cause, Reason.gather(reason, task.getStart().getMaxLit())); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateEst(est, cause, reason); + } else { + return false; + } + } + + /** + * Updates the latest start time (lst) of the task, considering its height variable. The height variable's upper bound is set to 0, or the task + * can become optional if updating its lst would empty the start variable's domain. + * + * @param task a task + * @param height a height variable + * @param lst the lst + * @param cause the propagator that filters the est + * @return true iff updating the lst has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterLst(Task task, IntVar height, int lst, ICause cause) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && lst < task.getEst()) { + return height.updateUpperBound(0, cause); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateLst(lst, cause); + } else { + return false; + } + } + + /** + * Updates the latest start time (lst) of the task, considering its height variable. The height variable's upper bound is set to 0, or the task + * can become optional if updating its lst would empty the start variable's domain. + * + * @param task a task + * @param height a height variable + * @param lst the lst + * @param cause the propagator that filters the est + * @param reason the reason of the filtering + * @return true iff updating the lst has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterLst(Task task, IntVar height, int lst, ICause cause, Reason reason) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && lst < task.getEst()) { + return height.updateUpperBound(0, cause, Reason.gather(reason, task.getStart().getMinLit())); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateLst(lst, cause, reason); + } else { + return false; + } + } + + /** + * Updates the earliest completion time (ect) of the task, considering its height variable. The height variable's upper bound is set to 0, or the + * task can become optional if updating its ect would empty the end variable's domain. + * + * @param task a task + * @param height a height variable + * @param ect the ect + * @param cause the propagator that filters the est + * @return true iff updating the ect has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterEct(Task task, IntVar height, int ect, ICause cause) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && ect > task.getLct()) { + return height.updateUpperBound(0, cause); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateEct(ect, cause); + } else { + return false; + } + } + + /** + * Updates the earliest completion time (ect) of the task, considering its height variable. The height variable's upper bound is set to 0, or the + * task can become optional if updating its ect would empty the end variable's domain. + * + * @param task a task + * @param height a height variable + * @param ect the ect + * @param cause the propagator that filters the est + * @param reason the reason of the filtering + * @return true iff updating the ect has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterEct(Task task, IntVar height, int ect, ICause cause, Reason reason) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && ect > task.getLct()) { + return height.updateUpperBound(0, cause, Reason.gather(reason, task.getEnd().getMaxLit())); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateEct(ect, cause, reason); + } else { + return false; + } + } + + /** + * Updates the latest completion time (lct) of the task, considering its height variable. The height variable's upper bound is set to 0, or the + * task can become optional if updating its lct would empty the end variable's domain. + * + * @param task a task + * @param height a height variable + * @param lct the lct + * @param cause the propagator that filters the est + * @return true iff updating the lct has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterLct(Task task, IntVar height, int lct, ICause cause) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && lct < task.getEct()) { + return height.updateUpperBound(0, cause); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateLct(lct, cause); + } else { + return false; + } + } + + /** + * Updates the latest completion time (lct) of the task, considering its height variable. The height variable's upper bound is set to 0, or the + * task can become optional if updating its lct would empty the end variable's domain. + * + * @param task a task + * @param height a height variable + * @param lct the lct + * @param cause the propagator that filters the est + * @param reason the reason of the filtering + * @return true iff updating the lct has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterLct(Task task, IntVar height, int lct, ICause cause, Reason reason) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && lct < task.getEct()) { + return height.updateUpperBound(0, cause, Reason.gather(reason, task.getEnd().getMinLit())); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateLct(lct, cause, reason); + } else { + return false; + } + } + + /** + * Updates the bounds of the task's duration variable, considering its height variable. The height variable's upper bound is set to 0, or the + * task becomes optional if updating empties the duration variable's domain. + * + * @param task a task + * @param height a height variable + * @param minDuration the min duration + * @param maxDuration the max duration + * @param cause the propagator that filters the est + * @return true iff updating the bounds of the task's duration variable has filtered a variable + * @throws ContradictionException an exception if a domain has been emptied + */ + public static boolean filterDuration(Task task, IntVar height, int minDuration, int maxDuration, ICause cause) throws ContradictionException { + if (height != null && height.getLB() == 0 && task.mustBePerformed() && + (maxDuration < task.getMinDuration() || task.getMaxDuration() < minDuration)) { + return height.updateUpperBound(0, cause); + } else if (height == null || height.getLB() > 0 || task instanceof OptionalTask) { + return task.updateDuration(minDuration, maxDuration, cause); + } else { + return false; + } + } + + /** + * Returns true iff the two tasks may be performed and they are intersecting, i.e. a.lst < b.ect && b.lst < a.ect. + * + * @param a a task + * @param b another task + * @return true iff the two tasks may be performed and they are intersecting + */ + public static boolean intersect(Task a, Task b) { + return a.mayBePerformed() && b.mayBePerformed() && + b.getLst() < a.getEct() && a.getLst() < b.getEct(); + } + + /** + * Returns true iff either of the two tasks may not be performed or they are disjoint, i.e. a.est >= b.lct && b.est >= a.lct. + * + * @param a a task + * @param b another task + * @return true iff either of the two tasks may not be performed or they are disjoint + */ + public static boolean disjoint(Task a, Task b) { + return !a.mayBePerformed() || !b.mayBePerformed() || a.getEst() >= b.getLct() || b.getEst() >= a.getLct(); + } + + /** + * Enforces the relation between start, duration and end variables of the tasks. + * + * @param tasks the tasks + * @throws ContradictionException an exception thrown when enforcing task relation + */ + public static void enforceTaskVariablesRelation(final List tasks) throws ContradictionException { + for (int i = 0; i < tasks.size(); i++) { + tasks.get(i).propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); + } + } + + private SchedulingUtils() { + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SweepCumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SweepCumulFilter.java deleted file mode 100644 index 315788ef94..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/SweepCumulFilter.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import gnu.trove.list.array.TIntArrayList; -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.objects.setDataStructures.SetFactory; -import org.chocosolver.util.sort.ArraySort; - -import java.util.Comparator; - -/** - * Basic implementation of Sweep-based Time-Table for cumulative - * @author Thierry Petit, Jean-Guillaume Fages - * @since 16/10/13 - */ -public class SweepCumulFilter extends CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - // variable bound copies (for both performance and convenience) - protected final int[] slb, sub; - protected final int[] elb, eub; - protected final int[] dlb; - protected final int[] hlb; - // sweep events: Prune / StartCompulsoryPart / EndCompulsoryPart - protected final static int PRU = 1, SCP = 2,ECP = 3; - protected final Event[] events; - protected int nbEvents; - // map to deal with subsets of variables - protected final int[] map; - // > 0 duration subset - protected final ISet tasksToUSe; - protected boolean FIXPOINT = true; - protected TIntArrayList temp = new TIntArrayList(); - protected TIntArrayList tprune = new TIntArrayList(); - protected ArraySort sort; - protected Comparator eventComparator; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public SweepCumulFilter(int n){ - super(n); - map = new int[n]; - slb = new int[n]; - sub = new int[n]; - elb = new int[n]; - eub = new int[n]; - dlb = new int[n]; - hlb = new int[n]; - events = new Event[3*n]; - for(int i=0;i<3*n;i++){ - events[i] = new Event(); - } - tasksToUSe = SetFactory.makeBipartiteSet(0); - sort = new ArraySort<>(events.length,true,false); - eventComparator = (e1, e2) -> { - if(e1.date == e2.date){ - return e2.type-e1.type; - } - return e1.date - e2.date; - }; - } - - //*********************************************************************************** - // GENERAL METHODS - //*********************************************************************************** - - @Override - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - // removing tasks with a duration lower bound equal to 0 - removeNullDurations(d, tasks); - int nbT = tasksToUSe.size(); - // filtering start lower bounds - boolean again; - ISetIterator tIter; - do{ - again = false; - int i = 0; - tIter = tasksToUSe.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - slb[i]=s[t].getLB(); - sub[i]=s[t].getUB(); - elb[i]=e[t].getLB(); - eub[i]=e[t].getUB(); - dlb[i]=d[t].getLB(); - hlb[i]=h[t].getLB(); - map[i] = t; - i++; - } - while (sweep(capa, h, nbT, aCause)){ - again = true; - if(!FIXPOINT)break; - } - pruneMin(s, aCause); - // symmetric approach for the end upper bounds - i = 0; - tIter = tasksToUSe.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - slb[i]=-e[t].getUB()+1; - sub[i]=-e[t].getLB()+1; - elb[i]=-s[t].getUB()+1; - eub[i]=-s[t].getLB()+1; - i++; - } - while (sweep(capa, h, nbT, aCause)){ - again = true; - if(!FIXPOINT)break; - } - pruneMax(e, aCause); - }while(FIXPOINT && again); - } - - protected void removeNullDurations(IntVar[] d, ISet tasks){ - tasksToUSe.clear(); - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - if(d[t].getLB()>0){ - tasksToUSe.add(t); - } - } - } - - protected void pruneMin(IntVar[] s, Propagator aCause) throws ContradictionException { - int i = 0; - ISetIterator tIter = tasksToUSe.iterator(); - while (tIter.hasNext()) - s[tIter.nextInt()].updateLowerBound(slb[i++], aCause); - } - - protected void pruneMax(IntVar[] e, Propagator aCause) throws ContradictionException { - int i = 0; - ISetIterator tIter = tasksToUSe.iterator(); - while (tIter.hasNext()) - e[tIter.nextInt()].updateUpperBound(1- slb[i++], aCause); - } - - //*********************************************************************************** - // SWEEP ALGORITHM - //*********************************************************************************** - - protected boolean sweep(IntVar capamax, IntVar[] h, int nbT, Propagator aCause) throws ContradictionException { - generateMinEvents(nbT); - if(nbEvents==0){ - return false;// might happen on randomly generated cases - } - sort.sort(events,nbEvents,eventComparator); - int timeIndex = 0; - int currentDate = events[timeIndex].date; - tprune.resetQuick(); - int capa = capamax.getUB(); - int currentConso = 0; - boolean active = false; - while(timeIndex=0;i--){ - int index = tprune.get(i); - // task min start might be filtered - if(currentDate< sub[index] || sub[index]>= elb[index]){ - // the current task should overlap the current event - if(currentConso+ hlb[index]>capa) { - // filter min start to next event - slb[index]=nextDate; - if(nextDate> sub[index]) {// early fail detection - aCause.fails(); // TODO: could be more precise, for explanation purpose - } - active = true;// perform fix point - temp.add(index); - } - else if(nextDate< slb[index]+ dlb[index]) { - temp.add(index); - } - } - } - tprune.resetQuick(); - for(int i=temp.size()-1;i>=0;i--){ - tprune.add(temp.getQuick(i)); - } - } - // handle the current event - Event event = events[timeIndex++]; - currentDate = event.date; - switch(event.type) { - case(SCP): - currentConso += hlb[event.index]; - // filter the capa max LB from the compulsory part consumptions - capamax.updateLowerBound(currentConso, aCause); - break; - case(ECP): - currentConso -= hlb[event.index]; - break; - case(PRU): - tprune.add(event.index); - break; - default:throw new UnsupportedOperationException("unknown event type"); - } - } - return active; - } - - protected void generateMinEvents(int nbT) { - nbEvents = 0; - for(int i=0; i taskSorter; - private final IntComparator comparator; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public SweepHeiSortCumulFilter(int n){ - super(n); - sortedTasks = new int[n]; - taskSorter = new ArraySort<>(n,false,true); - comparator = (i1, i2) -> hlb[map[i2]]-hlb[map[i1]]; - } - - //*********************************************************************************** - // GENERAL METHODS - //*********************************************************************************** - - @Override - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - int size = 0; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int t = tIter.nextInt(); - if(d[t].getLB()>0){ - map[size] = t; - sortedTasks[size] = size; - hlb[t] = h[t].getLB(); - size++; - } - } - taskSorter.sort(sortedTasks,size,comparator); - assert checkSort(h,size); - super.filter(s,d,e,h,capa,tasks,aCause); - } - - //*********************************************************************************** - // SWEEP ALGORITHM - //*********************************************************************************** - - @Override - protected boolean sweep(IntVar capamax, IntVar[] h, int nbT, Propagator aCause) throws ContradictionException { - generateMinEvents(nbT); - if(nbEvents==0){ - return false;// might happen on randomly generated cases - } - sort.sort(events,nbEvents,eventComparator); - int timeIndex = 0; - int currentDate = events[timeIndex].date; - int capa = capamax.getUB(); - int currentConso = 0; - boolean active = false; - while(timeIndex=currentDate; - // pruning - if(currentDatecapa) { - // task min start can be filtered (no mand part, overlaps envelope before smin+dmin - if((currentDate< sub[index] || sub[index]>= elb[index]) - && slb[index] sub[index]) {// early fail detection - aCause.fails(); // TODO: could be more precise, for explanation purpose - } - active = true;// perform fix point - } - }else{ - // remaining tasks have a (fixed) smaller height => no more filtering can occur - break; - } - } - } - // handle the current event - Event event = events[timeIndex++]; - currentDate = event.date; - if(event.type==SCP){ - assert (events[timeIndex].date>event.date||events[timeIndex].type==SCP); - currentConso += hlb[event.index]; - // filter the capa max LB from the compulsory part consumptions - capamax.updateLowerBound(currentConso, aCause); - }else { - assert event.type==ECP; - currentConso -= hlb[event.index]; - } - } - return active; - } - - @Override - protected void generateMinEvents(int nbT) { - // no PRU events - nbEvents = 0; - for(int i=0; i=h[map[idx2]].getLB(); - } - } - return true; - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/TimeCumulFilter.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/TimeCumulFilter.java deleted file mode 100644 index 85f49e425e..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/cumulative/TimeCumulFilter.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.constraints.nary.cumulative; - -import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; - -import java.util.Arrays; - -/** - * Time-based filtering (compute the profile over every point in time) - * @author Jean-Guillaume Fages - */ -public class TimeCumulFilter extends CumulFilter { - - //*********************************************************************************** - // VARIABLES - //*********************************************************************************** - - private int[] time = new int[31]; - - //*********************************************************************************** - // CONSTRUCTORS - //*********************************************************************************** - - public TimeCumulFilter(int nbMaxTasks) { - super(nbMaxTasks); - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - public void filter(IntVar[] s, IntVar[] d, IntVar[] e, IntVar[] h, IntVar capa, ISet tasks, Propagator aCause) throws ContradictionException { - int min = Integer.MAX_VALUE / 2; - int max = Integer.MIN_VALUE / 2; - ISetIterator tIter = tasks.iterator(); - while (tIter.hasNext()){ - int i = tIter.nextInt(); - if (s[i].getUB() < e[i].getLB()) { - min = Math.min(min, s[i].getUB()); - max = Math.max(max, e[i].getLB()); - } - } - if (min < max) { - if(max-min>time.length){ - time = new int[max-min]; - } - else{ - Arrays.fill(time, 0, max - min, 0); - } - int capaMax = capa.getUB(); - // fill mandatory parts and filter capacity - int elb,hlb; - int maxC=0; - - tIter = tasks.iterator(); - while (tIter.hasNext()){ - int i = tIter.nextInt(); - elb = e[i].getLB(); - hlb = h[i].getLB(); - for (int t = s[i].getUB(); t < elb; t++) { - time[t - min] += hlb; - maxC = Math.max(maxC,time[t - min]); - } - } - capa.updateLowerBound(maxC, aCause); - // filter max height - int minH; - - tIter = tasks.iterator(); - while (tIter.hasNext()){ - int i = tIter.nextInt(); - if(!h[i].isInstantiated()){ - minH = h[i].getUB(); - elb = e[i].getLB(); - hlb = h[i].getLB(); - for (int t = s[i].getUB(); t < elb; t++) { - minH = Math.min(minH,capaMax-(time[t-min]-hlb)); - } - h[i].updateUpperBound(minH,aCause); - } - } - tIter = tasks.iterator(); - while (tIter.hasNext()){ - int i = tIter.nextInt(); - if (h[i].getLB() > 0) { - // filters - if (s[i].getLB() + d[i].getLB() > min) { - filterInf(s[i],e[i].getLB(),d[i].getLB(),h[i].getLB(), min, max, time, capaMax, aCause); - } - if (e[i].getUB() - d[i].getLB() < max) { - filterSup(s[i].getUB(),e[i],d[i].getLB(),h[i].getLB(), min, max, time, capaMax, aCause); - } - } - } - } - } - - protected void filterInf(IntVar start, int elb, int dlb, int hlb, int min, int max, int[] time, int capaMax, Propagator aCause) throws ContradictionException { - int nbOk = 0; - int sub = start.getUB(); - for (int t = start.getLB(); t < sub; t++) { - if (t < min || t >= max || hlb + time[t - min] <= capaMax) { - nbOk++; - if (nbOk == dlb) { - return; - } - } else { - if(dlb==0 && t >= elb)return; - nbOk = 0; - start.updateLowerBound(t + 1, aCause); - } - } - } - - protected void filterSup(int sub, IntVar end, int dlb, int hlb, int min, int max, int[] time, int capaMax, Propagator aCause) throws ContradictionException { - int nbOk = 0; - int elb = end.getLB(); - for (int t = end.getUB(); t > elb; t--) { - if (t - 1 < min || t - 1 >= max || hlb + time[t - min - 1] <= capaMax) { - nbOk++; - if (nbOk == dlb) { - return; - } - } else { - if(dlb==0 && t <= sub)return; - nbOk = 0; - end.updateUpperBound(t - 1, aCause); - } - } - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/IntLinCombFactory.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/IntLinCombFactory.java index fab879befe..40ecb44ee3 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/IntLinCombFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/IntLinCombFactory.java @@ -14,7 +14,6 @@ import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.constraints.Operator; import org.chocosolver.solver.constraints.extension.TuplesFactory; -import org.chocosolver.solver.constraints.ternary.PropXplusYeqZ; import org.chocosolver.solver.exception.SolverException; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.view.integer.IntAffineView; @@ -248,27 +247,6 @@ public static Constraint selectSum(IntVar[] VARS, int[] COEFFS, Operator OPERATO assert COEFFS[0] == -1 && COEFFS[1] == -1; return s.arithm(VARS[0], "+", VARS[1], Operator.getFlip(OPERATOR.toString()), -RESULT); } - case 3: - if(RESULT == 0 && OPERATOR == Operator.EQ) { - // deal with X + Y = Z - if ((COEFFS[0] == 1 && COEFFS[1] == 1 && COEFFS[2] == -1) - || (COEFFS[0] == -1 && COEFFS[1] == -1 && COEFFS[2] == 1)) { - return new SumConstraint( - new PropXplusYeqZ(VARS[0], VARS[1], VARS[2])); - } - // deal with X + Z = Y - if ((COEFFS[0] == 1 && COEFFS[1] == -1 && COEFFS[2] == 1) - || (COEFFS[0] == -1 && COEFFS[1] == 1 && COEFFS[2] == -1)) { - return new SumConstraint( - new PropXplusYeqZ(VARS[0], VARS[2], VARS[1])); - } - // deal with Y + Z = X - if ((COEFFS[0] == -1 && COEFFS[1] == 1 && COEFFS[2] == 1) - || (COEFFS[0] == 1 && COEFFS[1] == -1 && COEFFS[2] == -1)) { - return new SumConstraint( - new PropXplusYeqZ(VARS[1], VARS[2], VARS[0])); - } - } default: int b = 0, e = VARS.length; IntVar[] tmpV = new IntVar[e]; @@ -372,7 +350,9 @@ public static Constraint selectScalar(IntVar[] VARS, int[] COEFFS, Operator OPER return s.times(VARS[0], COEFFS[0], VARS[1]); } } - if (Operator.EQ == OPERATOR && VARS[VARS.length - 1].hasEnumeratedDomain() && TuplesFactory.canBeTupled(Arrays.copyOf(VARS, VARS.length - 1))) { + if (Operator.EQ == OPERATOR + && VariableUtils.allEnumerated(VARS) + && TuplesFactory.canBeTupled(Arrays.copyOf(VARS, VARS.length - 1))) { return s.table(VARS, TuplesFactory.scalar(Arrays.copyOf(VARS, VARS.length - 1), Arrays.copyOf(COEFFS, COEFFS.length - 1), OPERATOR.toString(), VARS[VARS.length - 1], -COEFFS[COEFFS.length - 1], RESULT)); } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/SumConstraint.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/SumConstraint.java index 4cecb63a69..7cf6bbf4b2 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/SumConstraint.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/sum/SumConstraint.java @@ -11,9 +11,7 @@ import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.constraints.ConstraintsName; -import org.chocosolver.solver.constraints.Operator; import org.chocosolver.solver.constraints.Propagator; -import org.chocosolver.solver.constraints.ternary.PropXplusYeqZ; /** *

      @@ -42,14 +40,6 @@ protected Constraint makeOpposite() { if (propagators[0] instanceof PropSum) { PropSum me = (PropSum) propagators[0]; return new SumConstraint(me.opposite()); - } else if (propagators[0] instanceof PropXplusYeqZ) { - PropXplusYeqZ me = (PropXplusYeqZ) propagators[0]; - return new SumConstraint(new PropSum( - me.getVars(), - 2, - Operator.NQ, - 0 - )); } return super.makeOpposite(); } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropLocalConDis.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropLocalConDis.java index 63265e89ab..0f86597a10 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropLocalConDis.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropLocalConDis.java @@ -116,9 +116,7 @@ private boolean applyDeductions() throws ContradictionException { for (int p = 0; p < propagators[0].length; p++) { assert (propagators[0][p].isReifiedAndSilent()); propagators[0][p].setReifiedTrue(); - //todo: solver.getEventObserver().activePropagator(bool, propagators[p]); - propagators[0][p].propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); - model.getSolver().getEngine().onPropagatorExecution(propagators[0][p]); + model.getSolver().getEngine().execute(propagators[0][p], true); } change = false; } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqCHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqCHalfReif.java index 2f61cacd7d..92be7f45f5 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqCHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqCHalfReif.java @@ -46,7 +46,7 @@ public int getPropagationConditions(int vIdx) { if (vIdx < 1) { return IntEventType.all(); } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override @@ -60,7 +60,15 @@ public void propagate(int evtmask) throws ContradictionException { } // if b is undefined and x does not contain c, b must be false else if (!x.contains(c)) { - b.setToFalse(this, lcg() ? Reason.r(x.getLit(c, IntVar.LR_EQ)) : Reason.undef()); + if(x.hasEnumeratedDomain()) { + b.setToFalse(this, lcg() ? Reason.r(x.getLit(c, IntVar.LR_EQ)) : Reason.undef()); + }else{ + if(x.getUB() < c){ + b.setToFalse(this, lcg() ? Reason.r(x.getMaxLit()) : Reason.undef()); + } else if (x.getLB() > c) { + b.setToFalse(this, lcg() ? Reason.r(x.getMinLit()) : Reason.undef()); + } + } setPassive(); } } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqYHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqYHalfReif.java index a4b966fd90..d3420cdb95 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqYHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXeqYHalfReif.java @@ -48,7 +48,7 @@ public int getPropagationConditions(int vIdx) { if (vIdx < 2) { return IntEventType.all(); } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override @@ -86,14 +86,14 @@ public void propagate(int evtmask) throws ContradictionException { for (int val = vars[0].getLB(); val <= ub; val = vars[0].nextValue(val)) { if (!vars[1].contains(val)) { vars[0].removeValue(val, this, - lcg() ? Reason.r(vars[1].getLit(val, IntVar.LR_NE), b.getValLit()) : Reason.undef()); + lcg() ? Reason.r(vars[1].getLit(val, IntVar.LR_EQ), b.getValLit()) : Reason.undef()); } } ub = vars[1].getUB(); for (int val = vars[1].getLB(); val <= ub; val = vars[1].nextValue(val)) { if (!vars[0].contains(val)) { vars[1].removeValue(val, this, - lcg() ? Reason.r(vars[0].getLit(val, IntVar.LR_NE), b.getValLit()) : Reason.undef()); + lcg() ? Reason.r(vars[0].getLit(val, IntVar.LR_EQ), b.getValLit()) : Reason.undef()); } } } @@ -111,13 +111,29 @@ public void propagate(int evtmask) throws ContradictionException { break; case 0b10: if (!x.contains(y.getValue())) { - b.setToFalse(this, lcg() ? Reason.r(y.getValLit(), x.getLit(y.getValue(), IntVar.LR_EQ)) : Reason.undef()); + if(x.hasEnumeratedDomain() && y.hasEnumeratedDomain()) { + b.setToFalse(this, lcg() ? Reason.r(y.getValLit(), x.getLit(y.getValue(), IntVar.LR_EQ)) : Reason.undef()); + }else{ + if(x.getUB() < y.getValue()) { + b.setToFalse(this, lcg() ? Reason.r(x.getMaxLit(), y.getValLit()) : Reason.undef()); + } else if (x.getLB() > y.getValue()) { + b.setToFalse(this, lcg() ? Reason.r(x.getMinLit(), y.getValLit()) : Reason.undef()); + } + } setPassive(); } break; case 0b01: if (!y.contains(x.getValue())) { - b.setToFalse(this, lcg() ? Reason.r(x.getValLit(), y.getLit(x.getValue(), IntVar.LR_EQ)) : Reason.undef()); + if(x.hasEnumeratedDomain() && y.hasEnumeratedDomain()) { + b.setToFalse(this, lcg() ? Reason.r(x.getValLit(), y.getLit(x.getValue(), IntVar.LR_EQ)) : Reason.undef()); + }else{ + if(y.getUB() < x.getValue()) { + b.setToFalse(this, lcg() ? Reason.r(y.getMaxLit(), x.getValLit()) : Reason.undef()); + } else if (y.getLB() > x.getValue()) { + b.setToFalse(this, lcg() ? Reason.r(y.getMinLit(), x.getValLit()) : Reason.undef()); + } + } setPassive(); } break; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXgeCHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXgeCHalfReif.java index 105b88d478..826642baa1 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXgeCHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXgeCHalfReif.java @@ -46,7 +46,7 @@ public int getPropagationConditions(int vIdx) { if (vIdx < 1) { return IntEventType.DECUPP.getMask(); } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXinSHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXinSHalfReif.java index c84ede941b..8b6e28f78c 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXinSHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXinSHalfReif.java @@ -45,7 +45,7 @@ public int getPropagationConditions(int vIdx) { if (vIdx == 0) { return IntEventType.all(); } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleCHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleCHalfReif.java index 1578bad731..aadf5371e2 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleCHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleCHalfReif.java @@ -43,7 +43,10 @@ public PropXleCHalfReif(IntVar x, int c, BoolVar b) { @Override public int getPropagationConditions(int vIdx) { - return IntEventType.INCLOW.getMask(); + if(vIdx < 1){ + return IntEventType.INCLOW.getMask(); + } + return IntEventType.INSTANTIATE.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleYHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleYHalfReif.java index 2a17c246c1..096978f96c 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleYHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXleYHalfReif.java @@ -41,10 +41,13 @@ public PropXleYHalfReif(IntVar x, IntVar y, BoolVar b) { @Override public int getPropagationConditions(int vIdx) { - if (vIdx == 1) { + if (vIdx == 0) { + return IntEventType.INCLOW.getMask(); + } else if (vIdx == 1) { return IntEventType.DECUPP.getMask(); + }else{ + return IntEventType.INSTANTIATE.getMask(); } - return IntEventType.INCLOW.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneCHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneCHalfReif.java index b898917bd3..27747c4e15 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneCHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneCHalfReif.java @@ -43,10 +43,7 @@ public PropXneCHalfReif(IntVar x, int c, BoolVar b) { @Override public int getPropagationConditions(int vIdx) { - if (vIdx < 1) { - return IntEventType.INSTANTIATE.getMask(); - } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneYHalfReif.java b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneYHalfReif.java index b2e4a0ad5d..d85b822771 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneYHalfReif.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/reification/PropXneYHalfReif.java @@ -43,10 +43,7 @@ public PropXneYHalfReif(IntVar x, IntVar y, BoolVar b) { @Override public int getPropagationConditions(int vIdx) { - if (vIdx < 2) { - return IntEventType.INSTANTIATE.getMask(); - } - return IntEventType.INCLOW.getMask(); + return IntEventType.INSTANTIATE.getMask(); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZ.java b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZ.java index 1774f74a60..adf5b62892 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZ.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZ.java @@ -19,6 +19,7 @@ import org.chocosolver.util.ESat; import org.chocosolver.util.tools.MathUtils; +import static org.chocosolver.solver.variables.IntVar.LR_EQ; import static org.chocosolver.solver.variables.IntVar.LR_NE; /** @@ -104,7 +105,7 @@ public void propagate(int evtmask) throws ContradictionException { // sZ = 0 && sX!=0 => |x| < |y| if (Z.isInstantiatedTo(0) && !X.contains(0)) { hasChanged |= absX.updateUpperBound(absY.getUB() - 1, this, - lcg() ? Reason.r(0, Z.getValLit(), X.getLit(0, LR_NE), absY.getMaxLit()) : Reason.undef()); + lcg() ? Reason.r(0, Z.getValLit(), X.getLit(0, LR_EQ), absY.getMaxLit()) : Reason.undef()); } break; case 5: // X and Z are instanciated @@ -366,7 +367,7 @@ protected boolean sameSign(IntVar a, IntVar b, Reason r) throws ContradictionExc } if (!b.contains(0)) { res |= a.removeValue(0, this, - lcg() ? Reason.gather(r, b.getLit(0, LR_NE)) : Reason.undef()); + lcg() ? Reason.gather(r, b.getLit(0, LR_EQ)) : Reason.undef()); } return res; } diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZLight.java b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZLight.java new file mode 100644 index 0000000000..3ad65426fb --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropDivXYZLight.java @@ -0,0 +1,189 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.constraints.ternary; + +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.Model; +import org.chocosolver.solver.constraints.Explained; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.constraints.PropagatorPriority; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.exception.SolverException; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.events.IntEventType; +import org.chocosolver.util.ESat; + +/** + * Z = ceil(X/Y) + * A propagator for the constraint Z = ceil(X / Y) where X, Y and Z are possibly negative integer variables. + * The constructor introduces views of the variables to handle the sign of the variables correctly. + */ +@Explained +public class PropDivXYZLight extends Propagator { + + // Z = X/Y + private final IntVar X, Y, Z; + + public static int getSign(IntVar var) { + if (var.getLB() >= 0) { + return 1; // positive + } else if (var.getUB() < 0) { + return -1; // negative + } else { + return 0; // zero + } + } + + private static IntVar[] scope(IntVar x, IntVar y, IntVar z) { + boolean x_flip = (getSign(x) == -1); + boolean y_flip = (getSign(y) == -1); + boolean z_flip = (getSign(z) == -1); + Model m = x.getModel(); + if (!x_flip && !y_flip && !z_flip) { + // ceil(x+1 / y) = z+1 + return new IntVar[]{m.intView(1, x, 1), y, m.intView(1, z, 1)}; + } else if (!x_flip && y_flip && z_flip) { + // ceil(x / -y) = -z + return new IntVar[]{x, m.intView(-1, y, 0), z}; + } else if (x_flip && !y_flip && z_flip) { + // ceil(-x / y) = -z + return new IntVar[]{m.intView(-1, x, 0), y, m.intView(-1, z, 0)}; + } else if (x_flip && y_flip && !z_flip) { + // ceil(-x+1 / -y) = z+1 + return new IntVar[]{m.intView(-1, x, 1), m.intView(-1, y, 0), m.intView(1, z, 1)}; + } else { + throw new SolverException("Unexpected sign combination for variables in PropDivXYZLight"); + } + } + + + public PropDivXYZLight(IntVar x, IntVar y, IntVar z) { + super(scope(x, y, z), PropagatorPriority.TERNARY, false); + this.X = vars[0]; + this.Y = vars[1]; + this.Z = vars[2]; + } + + @Override + public int getPropagationConditions(int vIdx) { + return IntEventType.boundAndInst(); + } + + /** + * The main propagation method that filters, according to the constraint definition + * + * @param evtmask is it the initial propagation or not? + * @throws ContradictionException if failure occurs + */ + @Override + public void propagate(int evtmask) throws ContradictionException { + Y.removeValue(0, this, Reason.undef()); + boolean hasChanged; + do { + int x_min = X.getLB(); + int x_max = X.getUB(); + int y_min = Y.getLB(); + int y_max = Y.getUB(); + int z_min = Z.getLB(); + int z_max = Z.getUB(); + + // z >= ceil(x.min / y.max) + hasChanged = Z.updateLowerBound((x_min + y_max - 1) / y_max, this, + lcg() ? Reason.r(X.getMinLit(), Y.getMaxLit()) : Reason.undef()); + // z <= ceil(x.max / y.min) + hasChanged |= Z.updateUpperBound((x_max + y_min - 1) / y_min, this, + lcg() ? Reason.r(X.getMaxLit(), Y.getMinLit()) : Reason.undef()); + + // x >= y.min * (z.min - 1) + 1 + hasChanged |= X.updateLowerBound(y_min * (z_min - 1) + 1, this, + lcg() ? Reason.r(Y.getMinLit(), Z.getMinLit()) : Reason.undef()); + // x <= y.max * z.max + hasChanged |= X.updateUpperBound(y_max * z_max, this, + lcg() ? Reason.r(Y.getMaxLit(), Z.getMaxLit()) : Reason.undef()); + + // y >= ceil(x.min / z.max) + if (z_max >= 1) { + hasChanged |= Y.updateLowerBound((x_min + z_max - 1) / z_max, this, + lcg() ? Reason.r(X.getMinLit(), Z.getMaxLit()) : Reason.undef()); + } + + // y <= ceil(x.max / z.min-1) - 1 + if (z_min >= 2) { + hasChanged |= Y.updateUpperBound((x_max + z_min - 2) / (z_min - 1) - 1, this, + lcg() ? Reason.r(X.getMaxLit(), Z.getMinLit()) : Reason.undef()); + } + + } while (hasChanged); + } + + + @Override + public ESat isEntailed() { + // forbid Y=0 + if (Y.isInstantiatedTo(0)) { + return ESat.FALSE; + } + // check tuple + if (isCompletelyInstantiated()) { + return ESat.eval(Math.ceil(X.getValue() * 1. / Y.getValue()) == Z.getValue()); + } + // X=0 => Z=0 + if (X.isInstantiatedTo(0) && !Z.contains(0)) { + return ESat.FALSE; + } + // check sign + boolean pos = (X.getLB() >= 0 && Y.getLB() >= 0) || (X.getUB() < 0 && Y.getUB() < 0); + if (pos && Z.getUB() < 0) { + return ESat.FALSE; + } + boolean neg = (X.getLB() >= 0 && Y.getUB() < 0) || (X.getUB() < 0 && Y.getLB() >= 0); + if (neg && Z.getLB() > 0) { + return ESat.FALSE; + } + // compute absolute bounds + int minAbsX; + if (X.getLB() > 0) { + minAbsX = X.getLB(); + } else if (X.getUB() < 0) { + minAbsX = -X.getUB(); + } else { + minAbsX = 0; + } + int maxAbsX = Math.max(X.getUB(), -X.getLB()); + int minAbsY; + if (Y.getLB() > 0) { + minAbsY = Y.getLB(); + } else if (Y.getUB() < 0) { + minAbsY = -Y.getUB(); + } else { + minAbsY = 1; + } + int maxAbsY = Math.max(Y.getUB(), -Y.getLB()); + int minAbsZ; + if (Z.getLB() > 0) { + minAbsZ = Z.getLB(); + } else if (Z.getUB() < 0) { + minAbsZ = -Z.getUB(); + } else { + minAbsZ = 0; + } + int maxAbsZ = Math.max(Z.getUB(), -Z.getLB()); + // check absolute bounds + if ((minAbsZ > maxAbsX / minAbsY) || (maxAbsZ < minAbsX / maxAbsY)) { + return ESat.FALSE; + } + // check case Z=0 + if ((Z.isInstantiatedTo(0) && minAbsX > maxAbsY) || (maxAbsX < minAbsY && !Z.contains(0))) { + return ESat.FALSE; + } + return ESat.UNDEFINED; + } + +} diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropTimesNaiveWithLong.java b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropTimesNaiveWithLong.java index 9dd7a452da..59815f23c7 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropTimesNaiveWithLong.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropTimesNaiveWithLong.java @@ -64,24 +64,25 @@ public final ESat isEntailed() { return ESat.UNDEFINED; } - private boolean div(IntVar var, int a, int b, int c, int d) throws ContradictionException { + private boolean div(IntVar var, long a, long b, long c, long d) throws ContradictionException { long min, max; if (a <= 0 && b >= 0 && c <= 0 && d >= 0) { // case 1 min = MIN; max = MAX; return var.updateLowerBound(min, this) | var.updateUpperBound(max, this); - } else if (c == 0 && d == 0 && (a > 0 || b < 0)) // case 2 - fails(); // TODO: could be more precise, for explanation purpose - else if (c < 0 && d > 0 && (a > 0 || b < 0)) { // case 3 - max = Math.max(Math.abs(a), Math.abs(b)); - min = -max; - return var.updateLowerBound(min, this) | var.updateUpperBound(max, this); - } else if (c == 0 && d != 0 && (a > 0 || b < 0)) // case 4 a - return div(var, a, b, 1, d); - else if (c != 0 && d == 0 && (a > 0 || b < 0)) // case 4 b - return div(var, a, b, c, -1); - else { // if (c > 0 || d < 0) { // case 5 + } else if (a > 0 || b < 0) { + if (c == 0 && d == 0) // case 2 + fails(); // TODO: could be more precise, for explanation purpose + else if (c < 0 && d > 0) { // case 3 + max = Math.max(Math.abs(a), Math.abs(b)); + min = -max; + return var.updateLowerBound(min, this) | var.updateUpperBound(max, this); + } else if (c == 0) // case 4 a + return div(var, a, b, 1, d); + else if (d == 0) // case 4 b + return div(var, a, b, c, -1); + } else { // if (c > 0 || d < 0) { // case 5 float ac = (float) a / c, ad = (float) a / d, bc = (float) b / c, bd = (float) b / d; float low = Math.min(Math.min(ac, ad), Math.min(bc, bd)); @@ -94,7 +95,7 @@ else if (c != 0 && d == 0 && (a > 0 || b < 0)) // case 4 b return false; } - private boolean mul(IntVar var, int a, int b, int c, int d) throws ContradictionException { + private boolean mul(IntVar var, long a, long b, long c, long d) throws ContradictionException { long min = Math.min(Math.min(a * c, a * d), Math.min(b * c, b * d)); long max = Math.max(Math.max(a * c, a * d), Math.max(b * c, b * d)); return var.updateLowerBound(min, this) | var.updateUpperBound(max, this); diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropXplusYeqZ.java b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropXplusYeqZ.java index d3f510f37d..ba9ed3d9a7 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropXplusYeqZ.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/ternary/PropXplusYeqZ.java @@ -18,6 +18,8 @@ import org.chocosolver.util.ESat; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; +import java.util.Arrays; + /** * A propagator to ensure that X + Y = Z holds, where X, Y and Z are IntVar. * This propagator ensures AC when all variables are enumerated, BC otherwise. @@ -32,9 +34,10 @@ public class PropXplusYeqZ extends Propagator { private static final int THRESHOLD = 300; /** - * Set to true if X, Y and Z are bounded + * Set to true if at least two variables from X, Y and Z are either fixed or bounded + * Or if LCG is used, as AC is not explained yet */ - private final boolean allbounded; + private final boolean isRangeOnly; /** * Temporary structure to ease filtering */ @@ -49,7 +52,9 @@ public class PropXplusYeqZ extends Propagator { */ public PropXplusYeqZ(IntVar X, IntVar Y, IntVar Z) { super(new IntVar[]{X, Y, Z}, PropagatorPriority.TERNARY, false); - allbounded = (!X.hasEnumeratedDomain() & !Y.hasEnumeratedDomain() & !Z.hasEnumeratedDomain()); + isRangeOnly = lcg() || Arrays.stream(vars) + .filter(v -> !v.hasUnfixedEnumeratedDomain()) + .count() >= 2; set = new IntIterableRangeSet(); } @@ -77,8 +82,7 @@ private boolean filterPlus() throws ContradictionException { lcg() ? Reason.r(vars[0].getMinLit(), vars[1].getMinLit()) : Reason.undef()); change |= vars[2].updateUpperBound(ub, this, lcg() ? Reason.r(vars[0].getMaxLit(), vars[1].getMaxLit()) : Reason.undef()); - if (!allbounded) { - if ((long) vars[0].getDomainSize() * vars[1].getDomainSize() > THRESHOLD || lcg()) return change; + if (isAC(vars[0], vars[1])) { set.clear(); int ub1 = vars[0].getUB(); int ub2 = vars[1].getUB(); @@ -115,8 +119,7 @@ private boolean filterMinus(int vr) throws ContradictionException { lcg() ? Reason.r(vars[2].getMinLit(), vars[vo].getMaxLit()) : Reason.undef()); change |= vars[vr].updateUpperBound(ub, this, lcg() ? Reason.r(vars[2].getMaxLit(), vars[vo].getMinLit()) : Reason.undef()); - if (!allbounded) { - if ((long) vars[2].getDomainSize() * vars[vo].getDomainSize() > THRESHOLD || lcg()) return change; + if (isAC(vars[2], vars[vo])) { set.clear(); int ub1 = vars[2].getUB(); int ub2 = vars[vo].getUB(); @@ -138,6 +141,10 @@ private boolean filterMinus(int vr) throws ContradictionException { return change; } + private boolean isAC(IntVar v1, IntVar v2) { + return !isRangeOnly && (long) v1.getDomainSize() * v2.getDomainSize() < THRESHOLD; + } + @Override public ESat isEntailed() { int sumUB = 0, sumLB = 0, i = 0; diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/unary/PropMember.java b/solver/src/main/java/org/chocosolver/solver/constraints/unary/PropMember.java index 1a7a1821e9..953067a1b4 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/unary/PropMember.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/unary/PropMember.java @@ -64,7 +64,8 @@ public void propagate(int evtmask) throws ContradictionException { private static boolean enforce(IntVar var, IntIterableRangeSet fset, Propagator prop) throws ContradictionException { if (prop.lcg()) { boolean rem = false; - for (int i : var) { + int varUB = var.getUB(); + for (int i = var.getLB(); i <= varUB; i = var.nextValue(i)) { if (fset.contains(i)) continue; rem |= var.removeValue(i, prop, Reason.undef()); } diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/ArExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/ArExpression.java index 6b78d4d1b8..983e425d53 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/ArExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/ArExpression.java @@ -332,6 +332,17 @@ default void extractVar(HashSet variables) { } } + /** + * @return the maximum depth of this expression, ie the maximum number of nested expressions + */ + default int maxDepth() { + int max = 0; + for (ArExpression e : getExpressionChild()) { + max = Math.max(max, e.maxDepth()); + } + return max + 1; + } + /** * @param values int values to evaluate * @param map mapping between variables of the topmost expression and position in values @@ -764,7 +775,6 @@ public boolean isExpressionLeaf() { @Override public void extractVar(HashSet variables) { - variables.add(this.intVar()); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/UnArExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/UnArExpression.java index 67582aa006..e2e039612e 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/UnArExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/arithmetic/UnArExpression.java @@ -79,8 +79,21 @@ public ArExpression[] getExpressionChild() { @Override public IntVar intVar() { if (me == null) { + if (op == Operator.ABS && e instanceof BiArExpression && ((BiArExpression) e).getOp() == Operator.SUB + && !getModel().getSolver().isLCG()) { + BiArExpression be = (BiArExpression) e; + IntVar v1 = be.getExpressionChild()[0].intVar(); + IntVar v2 = be.getExpressionChild()[1].intVar(); + int[] bounds = VariableUtils.boundsForSubstraction(v1, v2); + me = model.intVar(model.generateName("abs_exp_"), + Math.max(bounds[0], 0), + Math.max(Math.abs(bounds[0]), Math.abs(bounds[1]))); + model.distance(v1, v2, "=", me).post(); + return me; + } + IntVar v = e.intVar(); - switch (op){ + switch (op) { case NEG: me = model.neg(v); break; @@ -93,7 +106,7 @@ public IntVar intVar() { model.square(me, v).post(); break; default: - throw new UnsupportedOperationException("Unary arithmetic expressions does not support "+op.name()); + throw new UnsupportedOperationException("Unary arithmetic expressions does not support " + op.name()); } } return me; diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/BiLoExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/BiLoExpression.java index 2fc7be4034..2baae788c0 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/BiLoExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/BiLoExpression.java @@ -12,6 +12,7 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.exception.SolverException; +import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression; import org.chocosolver.solver.expression.discrete.relational.ReExpression; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; @@ -85,6 +86,11 @@ public void extractVar(HashSet variables) { e2.extractVar(variables); } + @Override + public ArExpression[] getExpressionChild() { + return new ArExpression[]{e1, e2}; + } + @Override public Constraint decompose() { BoolVar v1 = e1.boolVar(); diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/LoExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/LoExpression.java index ea526480de..0c173938e5 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/LoExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/LoExpression.java @@ -100,6 +100,14 @@ public LoExpression(Model model, Operator op) { */ public abstract Model getModel(); + /** + * Return the operator of this logical expression + * @return the operator of this logical expression + */ + public Operator getOperator() { + return op; + } + /** * @return the logical expression as an {@link BoolVar}. * If necessary, it creates intermediary variable and posts intermediary constraints diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/NaLoExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/NaLoExpression.java index a6c8e082fe..abbc7f7b7c 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/NaLoExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/NaLoExpression.java @@ -11,6 +11,7 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.constraints.Constraint; +import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression; import org.chocosolver.solver.expression.discrete.relational.ReExpression; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; @@ -104,6 +105,11 @@ public void extractVar(HashSet variables) { Arrays.stream(es).forEach(e -> e.extractVar(variables)); } + @Override + public ArExpression[] getExpressionChild() { + return Arrays.stream(es).toArray(ReExpression[]::new); + } + @Override public Constraint decompose() { BoolVar[] vs = Arrays.stream(es).map(ReExpression::boolVar).toArray(BoolVar[]::new); diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/UnLoExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/UnLoExpression.java index 2078842ef8..ffb38545cd 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/UnLoExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/logical/UnLoExpression.java @@ -12,6 +12,7 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.exception.SolverException; +import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression; import org.chocosolver.solver.expression.discrete.relational.ReExpression; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; @@ -68,6 +69,11 @@ public void extractVar(HashSet variables) { e.extractVar(variables); } + @Override + public ArExpression[] getExpressionChild() { + return new ArExpression[]{e}; + } + @Override public Constraint decompose() { BoolVar v1 = e.boolVar(); diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/BiReExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/BiReExpression.java index 730b0512ab..c0a7003346 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/BiReExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/BiReExpression.java @@ -117,6 +117,11 @@ public void extractVar(HashSet variables) { e2.extractVar(variables); } + @Override + public ArExpression[] getExpressionChild() { + return new ArExpression[]{e1, e2}; + } + private static ArExpression.Operator detectOperator(ArExpression e){ int nochild = e.getNoChild(); if(nochild == 1){ diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/NaReExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/NaReExpression.java index 0be10028c0..e739c0c2f4 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/NaReExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/NaReExpression.java @@ -92,12 +92,20 @@ public BoolVar boolVar() { model.nValues(vs, count).post(); model.reifyXeqC(count, 1, me); } - }else if(op == Operator.IN){ + } else if (op == Operator.NE) { + if (vs.length == 2) { + model.reifyXneY(vs[0], vs[1], me); + } else { + IntVar count = model.intVar(op + "_count_", 1, vs.length); + model.nValues(vs, count).post(); + model.reifyXgtC(count, 1, me); + } + } else if (op == Operator.IN) { BoolVar[] reifs = model.boolVarArray(vs.length - 1); for(int i = 1; i < vs.length; i++) { model.reifyXeqY(vs[0], vs[i], reifs[i-1]); } - model.addClausesSumBoolArrayGreaterEqVar(reifs,me); + model.addClausesBoolOrArrayEqVar(reifs,me); } else if (op == Operator.NIN) { BoolVar[] reifs = model.boolVarArray(vs.length - 1); for (int i = 1; i < vs.length; i++) { @@ -127,6 +135,12 @@ public Constraint decompose() { }else { return model.allEqual(vs); } + case NE: + if (vs.length == 2) { + return model.arithm(vs[0], "!=", vs[1]); + } else { + return model.notAllEqual(vs); + } case IN: return model.count(vs[0], Arrays.copyOfRange(vs, 1, vs.length), @@ -140,6 +154,11 @@ public Constraint decompose() { throw new SolverException("Unexpected case"); } + @Override + public ArExpression[] getExpressionChild() { + return Arrays.stream(es).toArray(ArExpression[]::new); + } + @Override public boolean beval(int[] values, Map map) { boolean eval; @@ -151,6 +170,7 @@ public boolean beval(int[] values, Map map) { eval = op.eval(es[0].ieval(values, map), es[i].ieval(values, map)); } break; + case NE: case IN: eval = false; for (int i = 1; i < es.length; i++) { diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/ReExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/ReExpression.java index 562a38c36e..3603e4f75c 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/ReExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/ReExpression.java @@ -21,10 +21,12 @@ import org.chocosolver.solver.expression.discrete.logical.UnLoExpression; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.util.tools.VariableUtils; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -37,6 +39,8 @@ * @since 28/04/2016. */ public interface ReExpression extends ArExpression { + int MAX_SAMPLING_SIZE = 1000; + double FEASIBILITY_THRESHOLD = 0.9; /** * List of available operator for relational expression @@ -107,8 +111,7 @@ boolean eval(int i1, int i2) { boolean eval(int i1, int i2) { return i1 != i2; } - } - ; + }; abstract boolean eval(int i1, int i2); } @@ -131,12 +134,6 @@ default IntVar intVar() { return boolVar(); } - /** - * Extract the variables from this expression - * @param variables set of variables - */ - void extractVar(HashSet variables); - /** * Post the decomposition of this expression in the solver */ @@ -148,50 +145,138 @@ default void post() { * @return the topmost constraint representing the expression. If needed, a call to this method * creates additional variables and posts additional constraints. */ - default Constraint decompose(){ + default Constraint decompose() { throw new UnsupportedOperationException(); } /** - * @param algo an indication for the algorithm to use for the table constraint + * Represents the relational expression as an extension constraint (aka, table). + *

      + * If the provided algo is empty, the default algorithm is used for the table constraint. + *

      + * If the provided algo ends with a plus sign ('+'), it indicates that the table should only contain feasible tuples. + * In this case, the feasible parameter is ignored and set to true. + * If the provided algo does not end with a plus sign, the feasible parameter determines whether + * the table should contain feasible or infeasible tuples. + *

      + * + * @param algo an indication for the algorithm to use for the table constraint + * @param positiveCombinations if true, only feasible tuples are kept in the table, otherwise only infeasible tuples are kept. * @return a TABLE constraint that captures the expression * @see Model#table(IntVar[], Tuples, String) * @see Model#table(IntVar, IntVar, Tuples, String) */ - default Constraint extension(String algo) { + default Constraint extension(String algo, boolean positiveCombinations) { + // override the feasibility + boolean feasible = algo.endsWith("+") || positiveCombinations; + // Extract variables from the expression, map individual variables to their positions HashSet avars = new LinkedHashSet<>(); extractVar(avars); IntVar[] uvars = avars.stream().sorted().toArray(IntVar[]::new); Map map = IntStream.range(0, uvars.length).boxed().collect(Collectors.toMap(i -> uvars[i], i -> i)); - Tuples tuples = TuplesFactory.generateTuples(values -> beval(values, map), true, uvars); -// System.out.printf("%d -> %d\n", VariableUtils.domainCardinality(uvars), tuples.nbTuples()); - if(algo.equals("")){ + // Generate tuples based on the relational expression + Tuples tuples = TuplesFactory.generateTuples(values -> beval(values, map) == feasible, feasible, uvars); + if (algo.isEmpty()) { return getModel().table(uvars, tuples); - }else{ + } else { return getModel().table(uvars, tuples, algo); } } /** + * Represents the relational expression as an extension constraint (aka, table). + * This method is a shortcut for {@link #extension(String, boolean)} with + * algo set to an empty string and feasible set to true. + * This means that the table will only contain feasible tuples. + *

      + * + * @param algo an indication for the algorithm to use for the table constraint * @return a TABLE constraint that captures the expression + * @see Model#table(IntVar[], Tuples, String) + * @see Model#table(IntVar, IntVar, Tuples, String) + */ + default Constraint extension(String algo) { + return extension(algo, true); + } + + /** + * Represents the relational expression as an extension constraint (aka, table). + * This method is a shortcut for {@link #extension(String, boolean)} with + * algo set to an empty string and feasible set to true. + * This means that the table will only contain feasible tuples. + *

      + * + * @return a TABLE constraint that captures the expression + * @see Model#table(IntVar[], Tuples, String) + * @see Model#table(IntVar, IntVar, Tuples, String) */ default Constraint extension() { return extension(""); } + /** + * Embodies the relational expression as a constraint. + * This method is a shortcut for {@link #decompose()} or {@link #extension(String, boolean)}. + * It decides whether to decompose the expression into a set of constraints + * or to represent it as an extension constraint based on the size of the domain and the number of variables involved. + * If the cardinality of the domain is small enough and the number of variables is limited, it uses an extension constraint. + * Otherwise, it decomposes the expression into a set of constraints. + *

      + * + * @return a Constraint that captures the relational expression. + */ + default Constraint embody() { + HashSet avars = new LinkedHashSet<>(); + extractVar(avars); + IntVar[] uvars = avars.stream().sorted().toArray(IntVar[]::new); + if (this instanceof LoExpression) { + LoExpression exp = (LoExpression) this; + if (exp.getOperator() == LoExpression.Operator.OR + && this.maxDepth() == 3) { + // if the expression is a hidden clause, we decompose it + int n = exp.getExpressionChild().length; + int un = 0; + int bi = 0; + for (ArExpression e : exp.getExpressionChild()) { + if (e instanceof UnCReExpression) { + un++; + } else if (e instanceof BiReExpression) { + bi++; + } + } + if(un == n){ + // hidden clause composed of unary expressions only + return exp.decompose(); + }else if(bi > 0 && un > bi && bi + un == n) { + // hidden clause composed of unary and binary expressions only + return exp.decompose(); + } + } + } + int card = (int) VariableUtils.domainCardinality(uvars); + Map map = IntStream.range(0, uvars.length).boxed().collect(Collectors.toMap(i -> uvars[i], i -> i)); + double s = TuplesFactory.sample(Math.min(card, MAX_SAMPLING_SIZE), new Random(uvars[0].getModel().getSeed()), values -> beval(values, map), uvars); + + if (card <= Math.pow(2, 19) && uvars.length <= 4 && s * card <= Math.pow(2, 16)) { + return extension("CT", s <= FEASIBILITY_THRESHOLD); + } else { + return decompose(); + } + } + /** * @param values int values to evaluate - * @param map mapping between variables of the topmost expression and position in values + * @param map mapping between variables of the topmost expression and position in values * @return an evaluation of this relational expression based on a tuple */ - default boolean beval(int[] values, Map map){ + default boolean beval(int[] values, Map map) { assert this instanceof BoolVar; return values[map.get(this)] == 1; } @Override default int ieval(int[] values, Map map) { - return beval(values, map)?1:0; + return beval(values, map) ? 1 : 0; } /** @@ -235,9 +320,9 @@ default ReExpression imp(ReExpression y) { * @return return the expression "x ⇔ y_1 ⇔ y_2 ⇔ ..." where this is "x" */ default ReExpression iff(ReExpression... y) { - if(y.length == 1){ + if (y.length == 1) { return new BiLoExpression(LoExpression.Operator.IFF, this, y[0]); - }else { + } else { return new NaLoExpression(LoExpression.Operator.IFF, this, y); } } diff --git a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/UnCReExpression.java b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/UnCReExpression.java index 39fef83f67..242036fb86 100644 --- a/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/UnCReExpression.java +++ b/solver/src/main/java/org/chocosolver/solver/expression/discrete/relational/UnCReExpression.java @@ -114,7 +114,11 @@ public BoolVar boolVar() { @Override public void extractVar(HashSet variables) { e1.extractVar(variables); - variables.add(model.intVar(e2)); + } + + @Override + public ArExpression[] getExpressionChild() { + return new ArExpression[]{e1}; } private static ArExpression.Operator detectOperator(ArExpression e) { diff --git a/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java b/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java index 9392f866f5..aa7f6f8b09 100644 --- a/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java +++ b/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java @@ -271,24 +271,33 @@ private void activatePropagators() throws ContradictionException { int cw = model.getEnvironment().getWorldIndex(); // get current index dynPropagators.descending(cw, consumer); while (!awake_queue.isEmpty()) { - execute(awake_queue.pollFirst()); + execute(awake_queue.pollFirst(), false); } } /** - * Execute 'coarse' propagation on a newly added propagator - * or one that should be propagated on backtrack + * Execute a propagator immediately, without scheduling it. + * If the propagator is stateless, that is, it has not been executed ever, it is set to active first. + * Then, if the propagator is active, that is not reified or passive, it is fully propagated. + * Finally, if the solver is in LCG mode, the SAT solver is propagated. * * @param propagator a propagator to propagate * @throws ContradictionException if propagation fails + * @implSpec In the case the propagator is activated by a reification, + * then {@link #onPropagatorExecution(Propagator)} is called after propagation. */ - public void execute(Propagator propagator) throws ContradictionException { + public void execute(Propagator propagator, boolean activatedByReification) throws ContradictionException { if (propagator.isStateLess()) { propagator.setActive(); } if (propagator.isActive()) { + // first, fully propagate the propagator model.getSolver().getMeasures().incPropagationCount(); propagator.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); + if (activatedByReification){ + onPropagatorExecution(propagator); + } + // then, if required (LCG), propagate the SAT solver model.getSolver().getMeasures().incPropagationCount(); propagateSat(); while (!var_queue.isEmpty()) { diff --git a/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngineObserver.java b/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngineObserver.java index 76e0bd2c07..e240f52391 100644 --- a/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngineObserver.java +++ b/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngineObserver.java @@ -47,11 +47,11 @@ public PropagationEngineObserver(Model model, PropagationObserver observer) { * @apiNote This is the main reason this class exists, to notify a propagation observer */ @Override - public void execute(Propagator propagator) throws ContradictionException { + public void execute(Propagator propagator, boolean activatedByReification) throws ContradictionException { if (propagator.isStateLess() || propagator.isActive()) { observer.onCoarseEvent(propagator); } - super.execute(propagator); + super.execute(propagator, activatedByReification); } /** diff --git a/solver/src/main/java/org/chocosolver/solver/search/loop/learn/LazyClauseGeneration.java b/solver/src/main/java/org/chocosolver/solver/search/loop/learn/LazyClauseGeneration.java index be23a412f3..b35b79055e 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/loop/learn/LazyClauseGeneration.java +++ b/solver/src/main/java/org/chocosolver/solver/search/loop/learn/LazyClauseGeneration.java @@ -23,6 +23,7 @@ import org.chocosolver.util.tools.VariableUtils; import java.util.Arrays; +import java.util.Comparator; /** * This class aims at defining a lazy clause generation algorithm as a {@link Learn} object. @@ -38,9 +39,11 @@ */ public class LazyClauseGeneration implements Learn { public static boolean VERBOSE = false; + public static boolean SORT_LITS_ON_SOLUTION = false; + public static boolean SORT_LITS_ON_FAILURE = false; + private static final String ON_FAILURE = "On SAT failure,"; private static final String ON_SOLUTION = "On solution,"; - /** * The solver that is watched */ @@ -61,10 +64,14 @@ public class LazyClauseGeneration implements Learn { * Maintains the number of restarts, required for {@link Learn#forget()}. */ private long nbRestarts = 0; + /** + * Indicates whether the current clause comes from on a solution (or on a failure). + */ + private boolean onSolution = false; /** * A temporary storage for learnt clauses. */ - private final TIntArrayList learnt_clause = new TIntArrayList(); + private final SortableIntArrayList learnt_clause = new SortableIntArrayList(SORT_LITS_ON_SOLUTION || SORT_LITS_ON_FAILURE); public LazyClauseGeneration(Solver solver, MiniSat sat) { this.mSolver = solver; @@ -95,7 +102,10 @@ public void forget() { mSolver.cancelTrail(); mSolver.getDecisionPath().synchronize(true, learnt_clause.size() > 1); if (!learnt_clause.isEmpty()) { - mSat.addLearnt(learnt_clause); + if (onSolution ? SORT_LITS_ON_SOLUTION : SORT_LITS_ON_FAILURE) { + learnt_clause.quicksort(1, learnt_clause.size() - 1); + } + mSat.addLearnt(learnt_clause, onSolution); } } else { nbRestarts = mSolver.getRestartCount(); @@ -108,6 +118,7 @@ public void forget() { private void onFailure() { ContradictionException cex = mSolver.getContradictionException(); int backtrack_level = analyze(cex, ON_FAILURE); + onSolution = false; // to indicate that we are learning on a failure, for #forget() int upto = mSolver.getEnvironment().getWorldIndex() - backtrack_level; if (upto > 1) { mSolver.getMeasures().incBackjumpCount(); @@ -124,12 +135,13 @@ private void onSolution() { mSat.confl = new Clause(learnt_clause, false /*?*/); int backtrack_level = analyze(mSolver.getContradictionException().set(Cause.Sat, null, null), ON_SOLUTION); + onSolution = true; // to indicate that we are learning on a solution, for #forget() int upto = mSolver.getEnvironment().getWorldIndex() - backtrack_level; if (upto > 1) { mSolver.getMeasures().incBackjumpCount(); } mSolver.setJumpTo(upto); - } // else always restart on a solution, managed in Solver + } // else always restart on a solution, managed in Solver } private void extractFromVariables() { @@ -218,4 +230,51 @@ private int analyze(ContradictionException cex, String message) { } return level; } + + private class SortableIntArrayList extends TIntArrayList { + Comparator comparator; + + public SortableIntArrayList() { + this(false); + } + + public SortableIntArrayList(final boolean sort) { + super(); + if (sort) { + comparator = Comparator.comparingInt(a -> mSat.pos(MiniSat.var(a))); + } + } + + public void quicksort(int low, int high) { + assert high < this._pos; + _quicksort(this._data, low, high); + } + + private void _quicksort(int[] tab, int low, int high) { + if (low < high) { + int pivotIndex = _partition(tab, low, high); + _quicksort(tab, low, pivotIndex - 1); + _quicksort(tab, pivotIndex + 1, high); + } + } + + private int _partition(int[] tab, int low, int high) { + int pivot = tab[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (comparator.compare(tab[j], pivot) <= 0) { + i++; + // swap tab[i] and tab[j] + int temp = tab[i]; + tab[i] = tab[j]; + tab[j] = temp; + } + } + // swap tab[i + 1] and tab[high] (or pivot) + int temp = tab[i + 1]; + tab[i + 1] = tab[high]; + tab[high] = temp; + return i + 1; + } + } } diff --git a/solver/src/main/java/org/chocosolver/solver/search/loop/monitors/NogoodFromRestarts.java b/solver/src/main/java/org/chocosolver/solver/search/loop/monitors/NogoodFromRestarts.java index 1b8873a644..01a9ba5146 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/loop/monitors/NogoodFromRestarts.java +++ b/solver/src/main/java/org/chocosolver/solver/search/loop/monitors/NogoodFromRestarts.java @@ -20,9 +20,11 @@ import org.chocosolver.solver.search.strategy.decision.DecisionPath; import org.chocosolver.solver.search.strategy.decision.IntDecision; import org.chocosolver.solver.search.strategy.decision.SetDecision; +import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.SetVar; import org.chocosolver.solver.variables.Variable; +import org.chocosolver.util.tools.VariableUtils; import java.util.ArrayDeque; import java.util.Arrays; @@ -136,6 +138,9 @@ private int asLit(Decision decision) { } private int asLit(IntVar var, DecisionOperator op, int val) { + if(VariableUtils.isBool(var)){ + return asLit((BoolVar) var, op, val); + } int l; if (DecisionOperatorFactory.makeIntEq().equals(op)) { l = MiniSat.makeLiteral(png.makeIntEq(var, val), false); @@ -153,6 +158,28 @@ private int asLit(IntVar var, DecisionOperator op, int val) { return l; } + private int asLit(BoolVar var, DecisionOperator op, int val) { + int l; + if (DecisionOperatorFactory.makeIntEq().equals(op)) { + assert val == 0 || val == 1 : "Value must be either 0 or 1 for BoolVar"; + l = MiniSat.makeLiteral(png.makeBool(var), val == 0); + } else if (DecisionOperatorFactory.makeIntNeq().equals(op)) { + assert val == 0 || val == 1 : "Value must be either 0 or 1 for BoolVar"; + l = MiniSat.makeLiteral(png.makeBool(var), val == 1); + } else if (DecisionOperatorFactory.makeIntSplit().equals(op) + || op instanceof ObjectiveStrategy.BottomUpDecisionOperator) { + assert val == 0 : "Value must be 0"; + l = MiniSat.makeLiteral(png.makeBool(var), true); + } else if (DecisionOperatorFactory.makeIntReverseSplit().equals(op) + || op instanceof ObjectiveStrategy.TopDownDecisionOperator) { + assert val == 1 : "Value must be 1"; + l = MiniSat.makeLiteral(png.makeBool(var), false); + } else { + throw new UnsupportedOperationException("Cannot deal with such operator: " + op); + } + return l; + } + private int asLit(SetVar var, DecisionOperator op, int val) { int l; if (DecisionOperatorFactory.makeSetForce().equals(op)) { diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/BlackBoxConfigurator.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/BlackBoxConfigurator.java index f211f28ac6..2639c7211e 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/BlackBoxConfigurator.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/BlackBoxConfigurator.java @@ -23,9 +23,12 @@ import org.chocosolver.util.tools.VariableUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; /** * A blackbox declaration assistant. @@ -225,11 +228,16 @@ public void complete(Model model, AbstractStrategy declaredStrategy) { List lsvars = new ArrayList<>(); // set variables List> lgvars = new ArrayList<>(); // graph variables List lrvars = new ArrayList<>();// real variables. + if(model.getGroups().isEmpty()){ + model.addAsGroup("default", model.getVars()); + } + Set inputVars = model.getGroups().stream().flatMap(g -> Arrays.stream(g.getVariables())).collect(Collectors.toSet()); Variable[] variables = model.getVars(); Variable objective = null; for (Variable var : variables) { if (VariableUtils.isConstant(var)) continue; if (VariableUtils.isView(var) && excludeViews) continue; + if (!inputVars.isEmpty() && !inputVars.contains(var)) continue; int type = var.getTypeAndKind(); int kind = type & Variable.KIND; switch (kind) { @@ -298,6 +306,9 @@ public void complete(Model model, AbstractStrategy declaredStrategy) { } AbstractStrategy main = Search.sequencer(strats.toArray(new AbstractStrategy[0])); solver.addRestarter(restartPolicy.apply(solver)); + if(restartOnSolution){ + solver.setRestartOnSolutions(); + } if (nogoodOnRestart) { solver.setNoGoodRecordingFromRestarts(); } @@ -368,8 +379,9 @@ public BlackBoxConfigurator setRestartOnSolution(boolean restartOnSolution) { return this; } - public void setRefinedPartialAssignmentGeneration(boolean rgpa) { + public BlackBoxConfigurator setRefinedPartialAssignmentGeneration(boolean rgpa) { this.generatePartialAssignment = rgpa; + return this; } public BlackBoxConfigurator setExcludeViews(boolean excludeViews) { diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/Search.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/Search.java index 86e6c3e528..efb3df625a 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/Search.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/Search.java @@ -30,6 +30,12 @@ import org.chocosolver.solver.search.strategy.selectors.variables.*; import org.chocosolver.solver.search.strategy.strategy.*; import org.chocosolver.solver.variables.*; +import org.chocosolver.util.bandit.Policy; +import org.chocosolver.util.bandit.UCB1; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.ToDoubleBiFunction; public class Search { @@ -80,7 +86,7 @@ public static AbstractStrategy lastConflict( throw new UnsupportedOperationException( "the search strategy in parameter cannot be null! Consider using Search.defaultSearch(model)"); } - return new LastConflict<>(formerSearch, k); + return new LastConflict<>(formerSearch.getVariables()[0].getModel(), formerSearch, k); } /** @@ -91,7 +97,8 @@ public static AbstractStrategy lastConflict( */ public static AbstractStrategy conflictOrderingSearch( AbstractStrategy formerSearch) { - return new ConflictOrderingSearch<>(formerSearch); + return new ConflictOrderingSearch<>(formerSearch.getVariables()[0].getModel(), + formerSearch); } /** @@ -596,16 +603,7 @@ public static AbstractStrategy pickOnDom(IntVar... vars) { * @return assignment strategy */ public static IntStrategy randomSearch(IntVar[] vars, long seed) { - IntValueSelector value = new IntDomainRandom(seed); - IntValueSelector bound = new IntDomainRandomBound(seed); - IntValueSelector selector = var -> { - if (var.hasEnumeratedDomain()) { - return value.selectValue(var); - } else { - return bound.selectValue(var); - } - }; - return intVarSearch(new Random<>(seed), selector, vars); + return intVarSearch(new Random<>(seed), new IntDomainRandom(seed), vars); } /** @@ -622,7 +620,14 @@ public static AbstractStrategy objectiveStrategy(IntVar objective, } /** - * Defines a round-robin search strategy over the variables. + * Defines round-robin search strategy over the variables. + *

      + * The strategy is composed of the following meta strategies: + * *

        + *
      • LastConflict-1
      • + *
      • LastConflict-0
      • + *
      • ConflictOrderingSearch
      • + *
      *

      * The strategy is composed of the following variable selectors: *

        @@ -639,24 +644,137 @@ public static AbstractStrategy objectiveStrategy(IntVar objective, *
      • IntDomainRandom
      • *
      * In addition, the phase-saving mechanism is activated. + *

      + * Note that this strategy must be combined with (fast) restarts to be useful. + *

      * * @param vars list of variables * @return a round-robin search strategy */ public static AbstractStrategy roundRobinSearch(IntVar... vars) { - long seed = 1_000_000_007; - return new RoundRobin(vars, - new VariableSelector[]{ - new DomOverWDegRef<>(vars, seed), - new DomOverWDeg<>(vars, seed), - new PickOnDom<>(vars, 32), - new FailureBased<>(vars, seed, 4) - }, - new IntValueSelector[]{ - new IntDomainMin(), - new IntDomainMax(), - new IntDomainRandom(seed)}, - true, false); + Model model = vars[0].getModel(); + long seed = + model.getSeed(); + int flushThr = 20; + MetaStrategy[] metaStrategies = new MetaStrategy[]{ + new LastConflict<>(model, RoundRobin.NULL_STRATEGY, 1), + new LastConflict<>(model, RoundRobin.NULL_STRATEGY, 0), + new ConflictOrderingSearch<>(model, RoundRobin.NULL_STRATEGY) + }; + VariableSelector[] variableSelectors = new VariableSelector[]{ + new RoundRobinGroup<>(model, seed), + new DomOverWDegRef<>(vars, seed, flushThr), + new DomOverWDeg<>(vars, seed, flushThr), + new PickOnDom<>(vars, flushThr), + new FailureBased<>(vars, seed, 4) + }; + IntValueSelector[] valueSelectors = new IntValueSelector[]{ + new IntDomainMin(), + new IntDomainMax(), + new IntDomainRandom(seed) + }; + + List combinations = new ArrayList<>(); + combinations.add(new int[]{0, 0, 0}); + combinations.add(new int[]{1, 0, 0}); + combinations.add(new int[]{2, 0, 0}); +// then all other combinations + for (int k = 0; k < metaStrategies.length; k++) { + for (int j = 1; j < variableSelectors.length; j++) { + for (int i = 0; i < valueSelectors.length; i++) { + combinations.add(new int[]{i, j, k}); + } + } + } + return new RoundRobin(vars, metaStrategies, variableSelectors, valueSelectors, + combinations, + () -> model.getSolver().getSolutionCount() == 0, + () -> model.getSolver().getRestartCount() % 18 < 14, + () -> false, + new Policy.NextModuloN(combinations.size()), + (s, a) -> 0.); + } + + /** + * Defines an adaptive round-robin search strategy over the variables. + *

      + * The strategy is composed of the following meta strategies: + * *

        + *
      • LastConflict-1
      • + *
      • LastConflict-0
      • + *
      • ConflictOrderingSearch
      • + *
      + *

      + * The strategy is composed of the following variable selectors: + *

        + *
      • DomOverWDegRef
      • + *
      • DomOverWDeg
      • + *
      • PickOnDom
      • + *
      • FailureBased
      • + *
      + *

      + * The strategy is composed of the following value selectors: + *

        + *
      • IntDomainMin
      • + *
      • IntDomainMax
      • + *
      • IntDomainRandom
      • + *
      + * In addition, the phase-saving mechanism is activated. + * + *

      + * Note that this strategy must be combined with (fast) restarts to be useful. + *

      + * + * @param vars list of variables + * @return a round-robin search strategy + */ + public static AbstractStrategy adaptiveRoundRobinSearch(IntVar... vars) { + Model model = vars[0].getModel(); + long seed = model.getSeed(); + int flushThr = 20; + MetaStrategy[] metaStrategies = new MetaStrategy[]{ + new LastConflict<>(model, RoundRobin.NULL_STRATEGY, 1), + new LastConflict<>(model, RoundRobin.NULL_STRATEGY, 0), + new ConflictOrderingSearch<>(model, RoundRobin.NULL_STRATEGY)}; + VariableSelector[] variableSelectors = new VariableSelector[]{ + new RoundRobinGroup<>(model, seed), + new DomOverWDeg<>(vars, seed, flushThr), + new DomOverWDegRef<>(vars, seed, flushThr), + new FailureBased<>(vars, seed, 4),}; + IntValueSelector[] valueSelectors = new IntValueSelector[]{ + new IntDomainMin(), + new IntDomainMax(), + new IntDomainRandom(seed),}; + int comb = metaStrategies.length * variableSelectors.length * valueSelectors.length; + Policy policy = new UCB1(comb); + long[] lastStats = new long[4]; // 0: opt (if any), 1: # solutions, 2: # nodes, 3: # failures + lastStats[0] = model.getSolver().getObjectiveManager().isOptimization() ? model.getSolver().getBestSolutionValue().intValue() : 0; + ToDoubleBiFunction reward = (s, a) -> { + long opt = model.getSolver().getObjectiveManager().isOptimization() ? model.getSolver().getBestSolutionValue().intValue() : 0; + long nsols = model.getSolver().getSolutionCount(); + long nnodes = model.getSolver().getNodeCount(); + long nfail = model.getSolver().getFailCount(); + + long dopt = opt - lastStats[0]; + long dsols = nsols - lastStats[1]; + long dnodes = nnodes - lastStats[2]; + long dfails = nfail - lastStats[3]; + + lastStats[0] += dopt; + lastStats[1] += dsols; + lastStats[2] += dnodes; + lastStats[3] += dfails; + + // now, compute the reward. + return .5 * Math.log(Math.abs(dopt) + 1) + .3 * Math.sqrt(dsols) + .2 * dfails / dnodes; + }; + + return new RoundRobin(vars, metaStrategies, variableSelectors, valueSelectors, + () -> true, + () -> true, + () -> false, + policy, + reward); } // ************************************************************************************ diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/DecisionPath.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/DecisionPath.java index 68f9e0c74e..95a6097e80 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/DecisionPath.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/DecisionPath.java @@ -119,7 +119,7 @@ public void synchronize(boolean free, boolean caching) { for (int f = decisions.size() - 1; f >= t; f--) { Decision d = decisions.remove(f); if (free) d.free(); - if (caching) cache = d; + if (caching) cache = decisions.get(f-1); } } } @@ -184,9 +184,9 @@ public String lastDecisionToString() { st.append(String.format("[%d/%d] %s", decision.getArity() - decision.triesLeft() + 1, decision.getArity(), decision) ); - } else if (cache != null) { + } else if (cache != null) { // for lcg only st.append(String.format("[%d/%d] %s", - cache.getArity() - cache.triesLeft() + 1, cache.getArity(), cache) + cache.getArity() - cache.triesLeft(), cache.getArity(), cache) ); cache = null; } else { diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/IntDecision.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/IntDecision.java index b0ed987bdd..c5b8d6a8c1 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/IntDecision.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/decision/IntDecision.java @@ -146,13 +146,13 @@ public String toString() { return String.format("d_%d: %s%s%d", getPosition(), var.getName(), - nonrefuted ? "=": '\\', + nonrefuted ? "=": "!=", value); } else if (assignment.getClass().equals(DecisionOperatorFactory.makeIntNeq().getClass())) { return String.format("d_%d: %s%s%d", getPosition(), var.getName(), - nonrefuted ? "\u2260" : '=', + nonrefuted ? "!=" : '=', value); } else if (assignment.getClass().equals(DecisionOperatorFactory.makeIntSplit().getClass())) { return String.format("d_%d: %s%s%s%d,%d]", diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandom.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandom.java index 5e9eb8a892..af012fe5fb 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandom.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandom.java @@ -14,7 +14,7 @@ import java.util.Random; /** - * Selects randomly a value in the variable domain. + * Randomly selects a value in the variable domain. * * BEWARE: this should not be used within assignments and/or value removals if variables * have a bounded domain. @@ -26,22 +26,44 @@ public class IntDomainRandom implements IntValueSelector { private final Random rand; + private final boolean boundOnly; + /** + * Creates a value selector that randomly selects a value in the variable domain. + *

      + * By default, if the variable has a bounded domain, + * only the lower or upper bound of the variable will be selected randomly. + * @param seed the seed for the random number generator + */ public IntDomainRandom(long seed) { - this.rand = new Random(seed); + this(seed, false); } + /** + * Creates a value selector that randomly selects a value in the variable domain. + * @param seed the seed for the random number generator + * @param boundOnly if true, only the lower or upper bound of the variable will be selected randomly + */ + public IntDomainRandom(long seed, boolean boundOnly) { + this.rand = new Random(seed); + this.boundOnly = boundOnly; + } + /** * {@inheritDoc} */ @Override public int selectValue(IntVar var) { - int i = rand.nextInt(var.getDomainSize()); - int value = var.getLB(); - while (i > 0) { - value = var.nextValue(value); - i--; - } - return value; + if (boundOnly || !var.hasEnumeratedDomain()) { + return rand.nextBoolean() ? var.getLB() : var.getUB(); + } else { + int i = rand.nextInt(var.getDomainSize()); + int value = var.getLB(); + while (i > 0) { + value = var.nextValue(value); + i--; + } + return value; + } } } diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandomBound.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandomBound.java deleted file mode 100644 index 23884ebe7d..0000000000 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainRandomBound.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of choco-solver, http://choco-solver.org/ - * - * Copyright (c) 2025, IMT Atlantique. All rights reserved. - * - * Licensed under the BSD 4-clause license. - * - * See LICENSE file in the project root for full license information. - */ -package org.chocosolver.solver.search.strategy.selectors.values; - -import org.chocosolver.solver.variables.IntVar; - -import java.util.Random; - -/** - * Selects randomly between the lower and the upper bound of the variable - *
      - * - * @author Jean-Guillaume Fages, Charles Prud'homme - * @since 2 april 2014 - */ -public class IntDomainRandomBound implements IntValueSelector { - - private final Random rand; - - public IntDomainRandomBound(long seed) { - this.rand = new Random(seed); - } - - /** - * {@inheritDoc} - */ - @Override - public int selectValue(IntVar var) { - return rand.nextBoolean() ? var.getLB() : var.getUB(); - } -} diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainSticky.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainSticky.java new file mode 100644 index 0000000000..5901de8ac7 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/values/IntDomainSticky.java @@ -0,0 +1,106 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.search.strategy.selectors.values; + +import gnu.trove.map.hash.TIntIntHashMap; +import org.chocosolver.solver.variables.IVariableMonitor; +import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.solver.variables.events.IEventType; +import org.chocosolver.solver.variables.events.IntEventType; + +import java.util.OptionalInt; +import java.util.function.BiPredicate; +import java.util.function.Function; + +/** + * A value selector that returns the last value assigned to a variable while propagating. + * If no value was assigned, falls back to 'mainSelector'. + * This value selector monitors the variables and stores the last value assigned to each variable. + * Then, when a value has to be selected, it checks if the last assigned value is still in the domain of the variable. + * If it is, it returns this value, otherwise it falls back to the main selector. + * + * @author Charles Prud'homme + */ +public final class IntDomainSticky implements IntValueSelector, Function, IVariableMonitor { + + /** + * The last solution found + */ + private final TIntIntHashMap sticks; + /** + * The default value selector + */ + private final IntValueSelector mainSelector; + /** + * The condition to force, when return true, or skip, when return false, sticky value selection. + * Can be null, in that case will be considered as true. + */ + private final BiPredicate condition; + + /** + * Create a value selector that returns the last value assigned to a variable while propagating. + * If no value was assigned, falls back to 'mainSelector'. + * + * @param observed variables to observe + * @param mainSelector falling back selector + * @param condition condition to force, when return true, or skip, when return false, phase saving. + * Can be null, in that case will be considered as true. + */ + public IntDomainSticky(IntVar[] observed, IntValueSelector mainSelector, + BiPredicate condition) { + this.mainSelector = mainSelector; + if (condition == null) { + this.condition = (var, val) -> true; + } else { + this.condition = condition; + } + this.sticks = new TIntIntHashMap(observed.length, 1.5f, -1, Integer.MAX_VALUE); + for (IntVar v : observed) { + v.addMonitor(this); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int selectValue(IntVar var) { + int value = sticks.get(var.getId()); + if (relevant(var, value) && condition.test(var, value)) { + return value; + } + return mainSelector.selectValue(var); + } + + @Override + public OptionalInt apply(IntVar var) { + int value = sticks.get(var.getId()); + if (relevant(var, value) && condition.test(var, value)) { + return OptionalInt.of(value); + } + return OptionalInt.empty(); + } + + private boolean relevant(IntVar var, int value) { + return ( + (var.hasEnumeratedDomain() && var.contains(value)) + || (!var.hasEnumeratedDomain() && + (var.getLB() == value || var.getUB() == value)) + ); + } + + @Override + public void onUpdate(IntVar var, IEventType evt) { + if (IntEventType.isInstantiate(evt.getMask())) { + sticks.put(var.getId(), var.getValue()); + } + } +} + diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/AbstractCriterionBasedVariableSelector.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/AbstractCriterionBasedVariableSelector.java index 2441c6dfd0..98463dde79 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/AbstractCriterionBasedVariableSelector.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/AbstractCriterionBasedVariableSelector.java @@ -32,12 +32,7 @@ public abstract class AbstractCriterionBasedVariableSelector IMonitorContradiction, IMonitorRestart { // TO MANAGE FLUSHING - protected static final int FLUSH_TOPS = 20; - protected static final double FLUSH_RATIO = .9 * FLUSH_TOPS; - protected int flushThs; - protected final HashSet tops = new HashSet<>(); - protected int loop = 0; - + protected long flushThs; /** * Randomness to break ties */ @@ -65,19 +60,24 @@ public abstract class AbstractCriterionBasedVariableSelector /** * Create a variable selector based on a criterion - * @param vars decision variables - * @param seed seed for breaking ties randomly, if set to -1, ties are broken by the variable position - * @param flush flush threshold, when reached, it flushes scores + * + * @param vars decision variables + * @param seed seed for breaking ties randomly, if set to -1, ties are broken by the variable position + * @param flush flush-threshold. Counts the number of restarts, and flushes the weights if it exceeds this value. */ public AbstractCriterionBasedVariableSelector(V[] vars, long seed, int flush) { - this.random = new java.util.Random(seed); + this.random = seed > -1 ? new java.util.Random(seed) : null; this.solver = vars[0].getModel().getSolver(); this.environment = vars[0].getModel().getEnvironment(); this.last = environment.makeInt(vars.length - 1); - this.flushThs = flush; this.weights = new IdentityToDouble<>(); + flushThs = flush; } + /** + * @implNote + * Seems to be a bad idea to aggregate the weights of the views + */ @Override public final V getVariable(V[] vars) { V best = null; @@ -88,7 +88,6 @@ public final V getVariable(V[] vars) { int domSize = vars[idx].getDomainSize(); if (domSize > 1) { double weight = weight(vars[idx]) / domSize; - //System.out.printf("%3f%n", weight); if (w < weight) { bests.resetQuick(); bests.add(idx); @@ -108,7 +107,11 @@ public final V getVariable(V[] vars) { last.set(to); if (!bests.isEmpty()) { //System.out.printf("%s%n", bests); - int currentVar = bests.get(random.nextInt(bests.size())); + int currentVar = bests.get(0); + + if (random != null) { + currentVar = bests.get(random.nextInt(bests.size())); + } best = vars[currentVar]; } return best; @@ -127,21 +130,12 @@ int remapInc() { * @return true if the weights should be flushed */ protected boolean flushWeights() { - List temp = weights.getTop(FLUSH_TOPS); - long cnt = temp.stream().filter(tops::contains).count(); - if (cnt >= FLUSH_RATIO) { - loop++; - } else { - loop = 0; - } - tops.clear(); - if (loop == flushThs) { - loop = 0; + if (solver.getRestartCount() >= flushThs) { + flushThs = solver.getRestartCount(); + flushThs += 20; return true; - } else { - tops.addAll(temp); - return false; } + return false; } } diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/ConflictHistorySearch.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/ConflictHistorySearch.java index c53a46f2d8..98aa08e3fb 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/ConflictHistorySearch.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/ConflictHistorySearch.java @@ -69,7 +69,7 @@ public class ConflictHistorySearch * @param seed seed for breaking ties randomly */ public ConflictHistorySearch(V[] vars, long seed) { - super(vars, seed, Integer.MAX_VALUE); + super(vars, seed, 20); } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDeg.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDeg.java index c2889fbe35..1ce3283549 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDeg.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDeg.java @@ -13,7 +13,6 @@ import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.search.loop.monitors.IMonitorRestart; import org.chocosolver.solver.variables.IVariableMonitor; -import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.Variable; import org.chocosolver.solver.variables.events.IEventType; import org.chocosolver.util.tools.VariableUtils; @@ -53,7 +52,7 @@ public Element(int count, int w0, int w1) { */ final HashMap, Element> failCount = new HashMap<>(); - private final BiFunction, double[], double[]> remapWeights = + protected final BiFunction, double[], double[]> remapWeights = (p, w) -> { if (w == null) { // if absent @@ -96,7 +95,7 @@ public Element(int count, int w0, int w1) { * @param seed seed for breaking ties randomly */ public DomOverWDeg(V[] variables, long seed) { - this(variables, seed, Integer.MAX_VALUE); + this(variables, seed, 20); } /** @@ -195,16 +194,19 @@ void increase(Propagator prop, Element elt, double[] ws) { int dk = prop.getVar(elt.ws[1]).instantiationWorldIndex(); boolean futVar1 = Math.min(dj, dk) < s; // that is, futvars == 1 until we reach 'dk' for (int i = 0; i < prop.getNbVars(); i++) { - if (prop.getVar(i).isAConstant() || !VariableUtils.isInt(prop.getVar(i))) continue; - IntVar ivar = (IntVar) prop.getVar(i); + Variable ivar = prop.getVar(i); + if (ivar.isAConstant() || !VariableUtils.isInt(ivar)) continue; + // variables instanced in previous worlds are not incremented + if (ivar.instantiationWorldIndex() < s) continue; + final double inc = 1.; // recall that variable at 0 is the 'deepest' one if (i == elt.ws[0] && futVar1) { // it should be restored upon backtrack - environment.saveAt(() -> weights.inc(ivar, 1), dk); + environment.saveAt(() -> weights.inc(ivar, inc), dk); } else { - weights.inc(ivar, 1.); + weights.inc(ivar, inc); } - ws[i] += 1; + ws[i] += inc; } } @@ -266,9 +268,10 @@ public final void onUpdate(Variable var, IEventType evt) { /** * Update the watched literals of a propagator when a variable is instantiated - * @param p the propagator + * + * @param p the propagator * @param elt the element associated with the propagator - * @param i the index of the watched variable to update + * @param i the index of the watched variable to update */ private void updateFutvars(Propagator p, Element elt, int i) { assert (p.getVar(elt.ws[i]).isInstantiated()); @@ -287,16 +290,18 @@ private void updateFutvars(Propagator p, Element elt, int i) { if (!other.isInstantiated()) { // 'var' is the last one not instantiated, // so this counter will not be taken into account - double[] delta = {0.}; + double delta; double[] ws = refinedWeights.get(p); if (elt.ws[k] < ws.length) { // may happen propagators (like PropSat) with dynamic variable addition - delta[0] = ws[elt.ws[k]]; + delta = ws[elt.ws[k]]; + } else { + delta = 0.; } - weights.inc(other, -delta[0]); + weights.inc(other, -delta); // but it should be restored upon backtrack environment.save(() -> { - double ww = weights.get(other) + delta[0]; + double ww = weights.get(other) + delta; ww = Math.max(ww, 0.); weights.set(other, ww); }); @@ -305,31 +310,27 @@ private void updateFutvars(Propagator p, Element elt, int i) { } // <-- FOR DEBUGGING PURPOSE ONLY - /*double weightW(IntVar v) { - int w = 0; - int nbp = v.getNbProps(); - for (int i = 0; i < nbp; i++) { - Propagator prop = v.getPropagator(i); + /*double weightW(Variable v) { + final double[] w = {0}; // BEWARE: propagators that accept to add dynamically variables led to trouble - // when it comes to compute their weight incrementally. - w += futvarsW(prop); - } - return w; + // when it comes to compute their weight incrementally. + v.streamPropagators().forEach(p -> w[0] += futvarsW(p, v)); + return w[0]; } - int futvarsW(Propagator prop) { + double futvarsW(Propagator prop, Variable v) { int futVars = 0; for (int i = 0; i < prop.getNbVars(); i++) { if (!prop.getVar(i).isInstantiated()) { if (++futVars > 1) { Element elt = failCount.get(prop); if (elt != null) { - return elt.ws[2]; + return elt.ws[2] * 1.; } else break; } } } - return 0; + return 0.; }*/ // FOR DEBUGGING PURPOSE ONLY --> } diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDegRef.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDegRef.java index 6ba4487242..091eb7410c 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDegRef.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/DomOverWDegRef.java @@ -39,30 +39,70 @@ public DomOverWDegRef(V[] variables, long seed) { * * @param variables decision variables * @param seed seed for breaking ties randomly - * @param flushThs flush threshold, when reached, it flushes scores + * @param flushThs flush threshold, when reached, it flushes scores */ public DomOverWDegRef(V[] variables, long seed, int flushThs) { super(variables, seed, flushThs); } /** - * @implNote - * This is the reason this class exists. + * @implNote This is the reason this class exists. * The only difference with {@link DomOverWDeg} is the increment * which is not 1 for each variable. */ @Override void increase(Propagator prop, Element elt, double[] ws) { - long futvars = prop.getNbVars(); - // Increase weights of all variables in this propagator - // even if they are already instantiated - final double[] inc = new double[]{1.}; + int s = prop.getModel().getEnvironment().getWorldIndex(); + int dj = prop.getVar(elt.ws[0]).instantiationWorldIndex(); + int dk = prop.getVar(elt.ws[1]).instantiationWorldIndex(); + boolean futVar1 = Math.min(dj, dk) < s; // that is, futvars == 1 until we reach 'dk' + long futvars = 0; for (int i = 0; i < prop.getNbVars(); i++) { + futvars += prop.getVar(i).isInstantiated() ? 0 : 1; + } + for (int i = 0; i < prop.getNbVars() && futvars > 0; i++) { if (prop.getVar(i).isAConstant() || !VariableUtils.isInt(prop.getVar(i))) continue; + // variables instanced in previous worlds are not incremented + if (prop.getVar(i).instantiationWorldIndex() < s) continue; IntVar ivar = (IntVar) prop.getVar(i); - inc[0] = 1.0 / (futvars * (ivar.getDomainSize() == 0 ? 0.5 : ivar.getDomainSize())); - weights.inc(ivar, inc[0]); - ws[i] += inc[0]; + // recall that variable at 0 is the 'deepest' one + final double inc = 1.0 / (futvars * (ivar.getDomainSize() == 0 ? 0.5 : ivar.getDomainSize())); + // recall that variable at 0 is the 'deepest' one + if (i == elt.ws[0] && futVar1) { + // it should be restored upon backtrack + environment.saveAt(() -> weights.inc(ivar, inc), dk); + } else { + weights.inc(ivar, inc); + } + ws[i] += inc; } } + + + // <-- FOR DEBUGGING PURPOSE ONLY + /*@Override + double futvarsW(Propagator prop, Variable v) { + int futVars = 0; + for (int i = 0; i < prop.getNbVars(); i++) { + if (!prop.getVar(i).isInstantiated()) { + if (++futVars > 1) { + Element elt = failCount.get(prop); + if (elt != null) { + int j = 0; + while (j < prop.getNbVars() && prop.getVar(j) != v) { + j++; + } + if (j < prop.getNbVars()) { + // recall that variable at 0 is the 'deepest' one + double[] ws = refinedWeights.compute(prop, remapWeights); + return ws[j]; + } + } else break; + } + } + } + return 0.; + } */ + // FOR DEBUGGING PURPOSE ONLY --> + } diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/FailureBased.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/FailureBased.java index cd4ad108f7..bdf26623f7 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/FailureBased.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/FailureBased.java @@ -55,7 +55,7 @@ public class FailureBased implements IMonitorContradiction, * @param sType: the score type. 1->FRB; 2->FRBA; 3->FLB; 4->FLBA. */ public FailureBased(V[] vars, long seed, int sType) { - ran = new Random(seed); + this.ran = seed > -1 ? new java.util.Random(seed) : null; solver = vars[0].getModel().getSolver(); solver.plugMonitor(this); varNum = vars.length; @@ -106,8 +106,11 @@ public V getVariable(V[] vars) { currenFixNum++; } } - if (bests.size() > 0) { - currentVarIndex = bests.get(ran.nextInt(bests.size())); + if (!bests.isEmpty()) { + currentVarIndex = bests.get(0); + if (ran != null) { + currentVarIndex = bests.get(ran.nextInt(bests.size())); + } best = vars[currentVarIndex]; assignTimes[currentVarIndex]++; } diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/PickOnDom.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/PickOnDom.java index 326f484b91..4e18dc5362 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/PickOnDom.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/PickOnDom.java @@ -40,7 +40,7 @@ public class PickOnDom extends AbstractCriterionBasedVariabl private final ArrayList lVars; public PickOnDom(V[] vars) { - this(vars, 32); + this(vars, 20); } public PickOnDom(V[] vars, int flushRate) { diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/RoundRobinGroup.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/RoundRobinGroup.java new file mode 100644 index 0000000000..d0a8154291 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/selectors/variables/RoundRobinGroup.java @@ -0,0 +1,60 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.search.strategy.selectors.variables; + +import org.chocosolver.memory.IStateInt; +import org.chocosolver.solver.Model; +import org.chocosolver.solver.variables.Group; +import org.chocosolver.solver.variables.Variable; + +import java.util.ArrayList; + +/** + *
      + * + * @author Charles Prud'homme + * @since 24/02/2025 + */ +public class RoundRobinGroup implements VariableSelector { + private final IStateInt lastG; // index of the last non-instantiated variable + private final IStateInt lastIdx; // index of the last non-instantiated variable + private final ArrayList> groups; + private final java.util.Random rnd; + + public RoundRobinGroup(Model model, long seed) { + this.groups = new ArrayList<>(); + groups.addAll(model.getGroups()); + this.lastG = model.getEnvironment().makeInt(0); + this.lastIdx = model.getEnvironment().makeInt(0); + this.rnd = new java.util.Random(seed); + } + + @Override + public V getVariable(V[] variables) { + if(lastG.get() == 0 && lastIdx.get() == 0){ + groups.add(groups.remove(0)); +// Collections.shuffle(groups, rnd); + } + for (int gI = lastG.get(); gI < groups.size(); gI++) { + Group g = groups.get(gI); + Variable[] variables1 = g.getVariables(); + for (int idx = lastIdx.get(); idx < variables1.length; idx++) { + if (!variables1[idx].isInstantiated()) { + lastIdx.set(idx); + //noinspection unchecked + return (V) variables1[idx]; + } + } + lastIdx.set(0); + lastG.add(1); + } + return null; + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/AbstractStrategy.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/AbstractStrategy.java index 61c80ec4ec..4fa5352acc 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/AbstractStrategy.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/AbstractStrategy.java @@ -44,6 +44,19 @@ public abstract class AbstractStrategy { */ @SafeVarargs protected AbstractStrategy(V... variables) { + this(variables.length > 0 ? variables[0].getModel() : null, variables); + } + + /** + * Create a search strategy based on an array of variables + * + * @param variables array of variables + * @implNote the first variable is used to get the model and the decision path. + * If the first variable is null, the model and decision path are null. + * @implNote the variables are cloned to avoid side effects + */ + @SafeVarargs + protected AbstractStrategy(Model model, V... variables) { this.model = variables.length > 0 ? variables[0].getModel() : null; this.decisionPath = model != null ? model.getSolver().getDecisionPath() : null; this.vars = variables.clone(); diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearch.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearch.java index 5f3e5b0ccc..beabe5e07f 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearch.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearch.java @@ -12,14 +12,15 @@ import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntIntHashMap; - +import org.chocosolver.solver.Model; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.search.loop.monitors.IMonitorContradiction; import org.chocosolver.solver.search.strategy.decision.Decision; import org.chocosolver.solver.search.strategy.decision.RootDecision; import org.chocosolver.solver.variables.Variable; -import java.util.*; +import java.util.ArrayList; +import java.util.List; /** * Conflict Ordering Search @@ -30,16 +31,12 @@ * @author Charles Prud'homme * @since 15/06/2016 */ -public class ConflictOrderingSearch extends AbstractStrategy implements IMonitorContradiction { +public class ConflictOrderingSearch extends MetaStrategy implements IMonitorContradiction { //*********************************************************************************** // VARIABLES //*********************************************************************************** - /** - * The main strategy declared in the solver - */ - private final AbstractStrategy mainStrategy; /** * Store the variables in conflict */ @@ -70,9 +67,8 @@ public class ConflictOrderingSearch extends AbstractStrategy * * @param mainStrategy the main strategy declared */ - public ConflictOrderingSearch(AbstractStrategy mainStrategy) { - super(mainStrategy.vars); - this.mainStrategy = mainStrategy; + public ConflictOrderingSearch(Model model, AbstractStrategy mainStrategy) { + super(model, mainStrategy); // internal datastructures vars = new ArrayList<>(); var2pos = new TIntIntHashMap(16, .5f, -1, -1); @@ -81,37 +77,8 @@ public ConflictOrderingSearch(AbstractStrategy mainStrategy) { pcft = -1; } - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - @Override - public boolean init() { - if (!model.getSolver().getSearchMonitors().contains(this)) { - model.getSolver().plugMonitor(this); - } - return mainStrategy.init(); - } - - @Override - public void remove() { - this.mainStrategy.remove(); - if (model.getSolver().getSearchMonitors().contains(this)) { - model.getSolver().unplugMonitor(this); - } - } - - @SuppressWarnings("unchecked") - @Override - public Decision getDecision() { - V decVar = firstNotInst(); - if (decVar != null) { - Decision d = mainStrategy.computeDecision(decVar); - if (d != null) { - return d; - } - } - return mainStrategy.getDecision(); + public V getSelectedVariable() { + return firstNotInst(); } //*********************************************************************************** diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/LastConflict.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/LastConflict.java index f4fe064095..967fa81f2a 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/LastConflict.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/LastConflict.java @@ -9,11 +9,10 @@ */ package org.chocosolver.solver.search.strategy.strategy; +import org.chocosolver.solver.Model; import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.search.loop.monitors.IMonitorContradiction; import org.chocosolver.solver.search.loop.monitors.IMonitorRestart; import org.chocosolver.solver.search.loop.monitors.IMonitorSolution; -import org.chocosolver.solver.search.strategy.decision.Decision; import org.chocosolver.solver.variables.Variable; /** @@ -24,22 +23,12 @@ * @author Jean-Guillaume Fages, Charles Prud'homme * @since 03/05/2013 */ -public class LastConflict extends AbstractStrategy implements IMonitorRestart, IMonitorSolution, IMonitorContradiction { +public class LastConflict extends MetaStrategy implements IMonitorRestart, IMonitorSolution { //*********************************************************************************** // VARIABLES //*********************************************************************************** - /** - * The main strategy declared in the solver - */ - private final AbstractStrategy mainStrategy; - - /** - * Set to true when this strategy is active - */ - protected boolean active; - /** * Number of conflicts stored */ @@ -57,13 +46,13 @@ public class LastConflict extends AbstractStrategy implem /** * Creates a last conflict heuristic * + * @param model the solver to attach this to * @param mainStrategy the main strategy declared * @param k the maximum number of conflicts to store */ - public LastConflict(AbstractStrategy mainStrategy, int k) { - super(mainStrategy.vars); - assert k > 0 : "parameter K of last conflict must be strictly positive!"; - this.mainStrategy = mainStrategy; + public LastConflict(Model model, AbstractStrategy mainStrategy, int k) { + super(model, mainStrategy); +// assert k > 0 : "parameter K of last conflict must be strictly positive!"; //noinspection unchecked conflictingVariables = (V[]) new Variable[k]; nbCV = 0; @@ -75,36 +64,8 @@ public LastConflict(AbstractStrategy mainStrategy, int k) { //*********************************************************************************** @Override - public boolean init() { - if (!model.getSolver().getSearchMonitors().contains(this)) { - model.getSolver().plugMonitor(this); - } - - return mainStrategy.init(); - } - - @Override - public void remove() { - this.mainStrategy.remove(); - if (model.getSolver().getSearchMonitors().contains(this)) { - model.getSolver().unplugMonitor(this); - } - } - - @SuppressWarnings("unchecked") - @Override - public Decision getDecision() { - if (active) { - V decVar = firstNotInst(); - if (decVar != null) { - Decision d = mainStrategy.computeDecision(decVar); - if (d != null) { - return d; - } - } - } - active = true; - return mainStrategy.getDecision(); + public V getSelectedVariable() { + return firstNotInst(); } //*********************************************************************************** @@ -128,9 +89,6 @@ public void onContradiction(ContradictionException cex) { } } - @Override - public void beforeRestart() { - } @Override public void afterRestart() { diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/MetaStrategy.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/MetaStrategy.java new file mode 100644 index 0000000000..500088275c --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/MetaStrategy.java @@ -0,0 +1,82 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.search.strategy.strategy; + +/** + *
      + * + * @author Charles Prud'homme + * @since 11/03/2025 + */ + +import org.chocosolver.solver.Model; +import org.chocosolver.solver.search.loop.monitors.IMonitorContradiction; +import org.chocosolver.solver.search.strategy.decision.Decision; +import org.chocosolver.solver.variables.Variable; + +public abstract class MetaStrategy extends AbstractStrategy implements IMonitorContradiction { + + + /** + * The target solver + */ + protected Model model; + /** + * The main strategy declared in the solver + */ + protected final AbstractStrategy mainStrategy; + /** + * Set to true when this strategy is active + */ + protected boolean active; + + public MetaStrategy(Model model, AbstractStrategy mainStrategy) { + super(model, mainStrategy.vars); + this.mainStrategy = mainStrategy; + this.model = model; + } + + //*********************************************************************************** + // METHODS + //*********************************************************************************** + @Override + public boolean init() { + if (!model.getSolver().getSearchMonitors().contains(this)) { + model.getSolver().plugMonitor(this); + } + return mainStrategy.init(); + } + + @Override + public void remove() { + this.mainStrategy.remove(); + if (model.getSolver().getSearchMonitors().contains(this)) { + model.getSolver().unplugMonitor(this); + } + } + + @SuppressWarnings("unchecked") + @Override + public Decision getDecision() { + if (active) { + V decVar = getSelectedVariable(); + if (decVar != null) { + Decision d = mainStrategy.computeDecision(decVar); + if (d != null) { + return d; + } + } + } + active = true; + return mainStrategy.getDecision(); + } + + public abstract V getSelectedVariable(); +} diff --git a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/RoundRobin.java b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/RoundRobin.java index 365013a0e4..dce150216c 100644 --- a/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/RoundRobin.java +++ b/solver/src/main/java/org/chocosolver/solver/search/strategy/strategy/RoundRobin.java @@ -16,14 +16,18 @@ import org.chocosolver.solver.search.strategy.decision.Decision; import org.chocosolver.solver.search.strategy.selectors.values.IntDomainBest; import org.chocosolver.solver.search.strategy.selectors.values.IntDomainLast; +import org.chocosolver.solver.search.strategy.selectors.values.IntDomainSticky; import org.chocosolver.solver.search.strategy.selectors.values.IntValueSelector; import org.chocosolver.solver.search.strategy.selectors.variables.VariableSelector; import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.util.bandit.Policy; import java.util.ArrayList; import java.util.List; import java.util.OptionalInt; +import java.util.function.BooleanSupplier; import java.util.function.Function; +import java.util.function.ToDoubleBiFunction; /** * A search strategy that selects the next strategy to apply in a round-robin fashion. @@ -36,16 +40,48 @@ */ public class RoundRobin extends AbstractStrategy implements IMonitorRestart { + /** + * A strategy that does nothing but return null. + * It is used to set the main strategy to null in meta-strategies + */ + private static class NullStrategy extends AbstractStrategy { + + protected NullStrategy() { + super(); + } + + @Override + public Decision getDecision() { + return null; + } + } + + // Singleton of the null strategy + public static final AbstractStrategy NULL_STRATEGY = new NullStrategy(); + + // Order of importance of the selectors, the smaller the index, the quicker the selector is changed + private static final int VALUE = 0; + private static final int VARIABLE = 1; + private static final int META = 2; + + + // the meta strategies to select the next strategy to apply + private final MetaStrategy[] metaStrategies; // the variable selectors to select the next variable to branch on - final VariableSelector[] variableSelectors; - // the index of the current combination - int currentCombination; + private final VariableSelector[] variableSelectors; // the value selectors to select the next value to assign to the selected variable - final IntValueSelector[] valueSelectors; + private final IntValueSelector[] valueSelectors; + Function stickyFirst = v -> OptionalInt.empty(); // true if the solution has to be saved Function solutionFirst = v -> OptionalInt.empty(); Function bestFirst = v -> OptionalInt.empty(); - final List combinations; + + private final Policy bandit; + private final ToDoubleBiFunction reward; + + private final List combinations; + private int action; + private int step; /** * Creates a RoundRobin strategy. @@ -56,33 +92,111 @@ public class RoundRobin extends AbstractStrategy implements IMonitorRest * @param variables the variables to branch on * @param variableSelectors the variable selectors * @param valueSelectors the value selectors - * @param solutionSaving true if the solution has to be saved - * (the first solution found is saved and used to guide the search) - * @param bestUntilFirstSolution true if the value selection is guided by the objective function - * until the first solution is found + * @param combinations the combinations of meta, variable and value selectors + * @param stickyValSel conditions(s) to apply sticky value selector + * @param solutionSaving condition(s) to apply solution saving + * @param bestUntilFirstSolution condition(s) to apply best value selection */ public RoundRobin(IntVar[] variables, + MetaStrategy[] metaStrategies, VariableSelector[] variableSelectors, IntValueSelector[] valueSelectors, - boolean solutionSaving, - boolean bestUntilFirstSolution) { + List combinations, + BooleanSupplier stickyValSel, + BooleanSupplier solutionSaving, + BooleanSupplier bestUntilFirstSolution, + Policy bandit, + ToDoubleBiFunction reward) { super(variables); + this.metaStrategies = metaStrategies; this.variableSelectors = variableSelectors; this.valueSelectors = valueSelectors; - this.currentCombination = 0; - if (solutionSaving) { - Solution solution = vars[0].getModel().getSolver().defaultSolution(); - solutionFirst = new IntDomainLast(solution, valueSelectors[0], null); - } - if (bestUntilFirstSolution) { - bestFirst = new IntDomainBest(valueSelectors[0], v -> variables[0].getModel().getSolver().getSolutionCount() == 0); + + if (combinations == null) { + this.combinations = populate(this.metaStrategies, this.variableSelectors, this.valueSelectors); + } else { + this.combinations = combinations; } - this.combinations = new ArrayList<>(); - for (int i = 0; i < variableSelectors.length; i++) { - for (int j = 0; j < valueSelectors.length; j++) { - combinations.add(new int[]{i, j}); + + this.action = 0; + this.step = 0; + + stickyFirst = new IntDomainSticky(variables, valueSelectors[0], + (x, v) -> stickyValSel.getAsBoolean()); + Solution solution = vars[0].getModel().getSolver().defaultSolution(); + solutionFirst = new IntDomainLast(solution, valueSelectors[0], + (x, v) -> solutionSaving.getAsBoolean()); + bestFirst = new IntDomainBest(valueSelectors[0], + v -> bestUntilFirstSolution.getAsBoolean()); + this.bandit = bandit; + this.reward = reward; + } + + /** + * Creates a RoundRobin strategy. + * The strategy selects the next variable to branch on using the variable selectors in the order they are given. + * The strategy selects the next value to assign to the selected variable using the value selectors in the order they are given. + * The selectors are changed on each restart. + * + * @param variables the variables to branch on + * @param variableSelectors the variable selectors + * @param valueSelectors the value selectors + * @param stickyValSel conditions(s) to apply sticky value selector + * @param solutionSaving condition(s) to apply solution saving + * @param bestUntilFirstSolution condition(s) to apply best value selection + */ + public RoundRobin(IntVar[] variables, + MetaStrategy[] metaStrategies, + VariableSelector[] variableSelectors, + IntValueSelector[] valueSelectors, + BooleanSupplier stickyValSel, + BooleanSupplier solutionSaving, + BooleanSupplier bestUntilFirstSolution, + Policy bandit, + ToDoubleBiFunction reward) { + this(variables, + metaStrategies, + variableSelectors, + valueSelectors, + null, + stickyValSel, + solutionSaving, + bestUntilFirstSolution, + bandit, + reward); + + } + + public static List populate(MetaStrategy[] metaStrategies, + VariableSelector[] variableSelectors, + IntValueSelector[] valueSelectors) { + + int[][] sizes = new int[3][]; + sizes[VALUE] = new int[valueSelectors.length]; + sizes[VARIABLE] = new int[variableSelectors.length]; + sizes[META] = new int[metaStrategies.length]; + + List combinations = new ArrayList<>(); + int n = sizes.length; + int[] tmp = new int[n]; + while (true) { + // save the current indices + int[] currentIndices = new int[n]; + System.arraycopy(tmp, 0, currentIndices, 0, n); + combinations.add(currentIndices); + int j; + for (j = 0; j < n; j++) { + tmp[j]++; + if (tmp[j] < sizes[j].length) { + break; + } + tmp[j] = 0; + } + if (j == n) { + break; } } + return combinations; } @Override @@ -92,6 +206,9 @@ public boolean init() { solver.plugMonitor(this); } boolean init = true; + for (MetaStrategy m : metaStrategies) { + init &= m.init(); + } for (VariableSelector vs : variableSelectors) { init &= vs.init(); } @@ -100,6 +217,9 @@ public boolean init() { @Override public void remove() { + for (MetaStrategy m : metaStrategies) { + m.remove(); + } for (VariableSelector vs : variableSelectors) { vs.remove(); } @@ -111,8 +231,17 @@ public void remove() { @Override public Decision getDecision() { - IntVar variable = variableSelectors[combinations.get(currentCombination)[0]].getVariable(vars); - return computeDecision(variable); + int[] currentIndices = combinations.get(action); + //1. call the meta strategy + IntVar selectedVariable = null; + MetaStrategy metaStrategy = metaStrategies[currentIndices[META]]; + selectedVariable = metaStrategy.getSelectedVariable(); + if (selectedVariable == null) { + //2. call the strategy, if needed + VariableSelector strategy = variableSelectors[currentIndices[VARIABLE]]; + selectedVariable = strategy.getVariable(vars); + } + return computeDecision(selectedVariable); } @Override @@ -120,18 +249,34 @@ public Decision computeDecision(IntVar variable) { if (variable == null || variable.isInstantiated()) { return null; } + int[] currentIndices = combinations.get(action); + // 3. apply best value selection, if the condition is met OptionalInt opt = bestFirst.apply(variable); if (!opt.isPresent()) { + // 3. apply sticky value selection, if the condition is met + opt = stickyFirst.apply(variable); + } + if (!opt.isPresent()) { + //5. apply phase saving, if the condition is met opt = solutionFirst.apply(variable); } + //6. call the value selector if (!opt.isPresent()) { - opt = OptionalInt.of(valueSelectors[combinations.get(currentCombination)[1]].selectValue(variable)); + IntValueSelector valueSelector = valueSelectors[currentIndices[VALUE]]; + opt = OptionalInt.of(valueSelector.selectValue(variable)); } return decisionPath.makeIntDecision(variable, DecisionOperatorFactory.makeIntEq(), opt.getAsInt()); } @Override public void afterRestart() { - this.currentCombination = (currentCombination + 1) % combinations.size(); + step++; + bandit.update(action, reward.applyAsDouble(action, step)); + action = bandit.nextAction(step); + /*System.out.printf("c Action %d : [%s, %s, %s]\n", + action, + metaStrategies[combinations.get(action)[META]].getClass().getSimpleName(), + variableSelectors[combinations.get(action)[VARIABLE]].getClass().getSimpleName(), + valueSelectors[combinations.get(action)[VALUE]].getClass().getSimpleName());*/ } } diff --git a/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java b/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java index fab7240235..1e6260aa04 100644 --- a/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java +++ b/solver/src/main/java/org/chocosolver/solver/trace/CPProfiler.java @@ -15,6 +15,7 @@ import java.io.Closeable; import java.io.IOException; +import java.util.Random; /** * A search monitor to send data to cp-profiler. @@ -62,7 +63,11 @@ public CPProfiler(Solver aSolver) { */ public CPProfiler(Solver aSolver, int executionId, int port, boolean sendDomain) { super(aSolver, sendDomain); - connect(aSolver.getModelName(), executionId, port); + if (executionId == -1) { + executionId = (int) System.currentTimeMillis(); + } + connect(aSolver.getModelName()+"_"+ Long.toString(Math.abs(new Random().nextLong()), 36).substring(0, 5) + , executionId, port); } protected boolean connect(String label, int executionId, int port) { @@ -116,6 +121,18 @@ protected void sendRestart(int rid) { } } + /** + * Send a message to cp-profiler + * + * @param nc current node's identifier + * @param pid identifier of node's parent + * @param alt which of its siblings the node is (0 for the left-most) + * @param kid number of children + * @param rid number of restarts so far + * @param status determines the node's type (solution, failure, branching etc) + * @param label some text-based information to go along with the node (ie branching decision + * @param info complementary information (eg variable domains) + */ private void send(int nc, int pid, int alt, int kid, int rid, Message.NodeStatus status, String label, String info) { try { connector.createNode(nc, pid, rid, alt, kid, status) diff --git a/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java b/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java index c2d6d63b2b..4d40e87610 100644 --- a/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java +++ b/solver/src/main/java/org/chocosolver/solver/trace/SearchViz.java @@ -21,6 +21,7 @@ import org.chocosolver.solver.search.loop.monitors.IMonitorUpBranch; import org.chocosolver.solver.search.strategy.decision.Decision; import org.chocosolver.solver.search.strategy.decision.DecisionPath; +import org.chocosolver.solver.search.strategy.decision.RootDecision; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.Variable; import org.chocosolver.util.iterators.DisposableRangeIterator; @@ -170,7 +171,7 @@ public final void beforeDownBranch(boolean left) { if (last > 0) { String pdec; pdec = pretty(dp.getDecision(last - 1)); - Decision dec = dp.getLastDecision(); + Decision dec = dp.getLastDecision(); int ari = dec.getArity(); sendNode(nc, pid_stack.peek(), alt_stack.pop(), ari, rid, pdec, sendDomain? domainMessage.print():""); @@ -220,24 +221,12 @@ public final void afterRestart() { nc = 0; } - private static String pretty(Decision dec) { - if (dec == null) { - return "ROOT"; + private static String pretty(Decision dec) { + if (dec == null || dec instanceof RootDecision) { + return ""; } else { // to print decision correctly (since the previous one is sent) - int a = dec.getArity(); - int b = dec.triesLeft(); - dec.rewind(); - while (dec.triesLeft() > b + 1) { - a--; - dec.buildNext(); - } - String pretty = dec.toString(); - while (a > b) { - b++; - dec.buildNext(); - } - return pretty; + return dec.toString(); } } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/IVariableFactory.java b/solver/src/main/java/org/chocosolver/solver/variables/IVariableFactory.java index 4bf3ac5194..f80552ecd5 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/IVariableFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/IVariableFactory.java @@ -555,48 +555,49 @@ default IntVar[][] intVarMatrix(String name, int dim1, int dim2, int[] values) { // TASK VARIABLES //************************************************************************************* + /** + * Creates a task variable, based on an earliest starting time est, a latest completion time lst, + * a duration d, an earliest completion time ect and a latest completion time lct + * such that: start + duration = end. + * + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time + * @return a task variable. + */ + default Task taskVar(int est, int lst, int d, int ect, int lct) { + return taskVar(est, lst, d, ect, lct, false); + } + /** * Creates a task variable, based on a starting time s and a duration d * such that: s + d = e, where e is the ending time. - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variable, starting time * @param d fixed duration * @return a task variable. */ default Task taskVar(IntVar s, int d) { - return new Task(s, d); + return taskVar(s, d, false); } /** * Creates a task variable, based on a starting time s and a duration d * such that: s + d = e, where e is the ending time. - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variable, starting time * @param d integer variable, duration * @return a task variable. */ default Task taskVar(IntVar s, IntVar d) { - if (d.isInstantiated()) { - return new Task(s, d, ref().offset(s, d.getValue())); - } else { - int[] bounds = VariableUtils.boundsForAddition(s, d); - IntVar end = ref().intVar(bounds[0], bounds[1]); - return new Task(s, d, end); - } + return taskVar(s, d, false); } /** * Creates a task variable, based on a starting time s and a duration d * such that: s + d = e, where e is the ending time. - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variable, starting time * @param d fixed duration @@ -604,15 +605,12 @@ default Task taskVar(IntVar s, IntVar d) { * @return a task variable. */ default Task taskVar(IntVar s, int d, IntVar e) { - return new Task(s, d, e); + return taskVar(s, d, e, false); } /** * Creates a task variable, made of a starting time s, * a duration d and an ending time e such that: s + d = e. - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variable, starting time * @param d integer variable, duration @@ -620,16 +618,175 @@ default Task taskVar(IntVar s, int d, IntVar e) { * @return a task variable. */ default Task taskVar(IntVar s, IntVar d, IntVar e) { - return new Task(s, d, e); + return taskVar(s, d, e, false); + } + + /** + * Creates a task variable, based on an earliest starting time est, a latest completion time lst, + * a duration d, an earliest completion time ect and a latest completion time lct + * such that: start + duration = end. + * + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time + * @param optional whether to create an OptionalTask or a Task + * @return a task variable. + */ + default Task taskVar(int est, int lst, int d, int ect, int lct, boolean optional) { + if (optional) { + return new OptionalTask(ref(), est, lst, d, ect, lct); + } else { + return new Task(ref(), est, lst, d, ect, lct); + } + } + + /** + * Creates a task variable, based on an earliest starting time est, a latest completion time lst, + * a duration d, an earliest completion time ect and a latest completion time lct + * such that: start + duration = end. + * + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time + * @param performed boolean variable, whether the task is performed or not + * @return a task variable. + */ + default OptionalTask taskVar(int est, int lst, int d, int ect, int lct, BoolVar performed) { + return new OptionalTask(ref(), est, lst, d, ect, lct, performed); + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d fixed duration + * @param optional whether to create an OptionalTask or a Task + * @return a task variable. + */ + default Task taskVar(IntVar s, int d, boolean optional) { + if (optional) { + return new OptionalTask(s, d); + } else { + return new Task(s, d); + } + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d fixed duration + * @param performed boolean variable, whether the task is performed or not + * @return a task variable. + */ + default OptionalTask taskVar(IntVar s, int d, BoolVar performed) { + return new OptionalTask(s, d, performed); + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d integer variable, duration + * @param optional whether to create an OptionalTask or a Task + * @return a task variable. + */ + default Task taskVar(IntVar s, IntVar d, boolean optional) { + if (optional) { + return new OptionalTask(s, d); + } else { + return new Task(s, d); + } + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d integer variable, duration + * @param performed boolean variable, whether the task is performed or not + * @return a task variable. + */ + default OptionalTask taskVar(IntVar s, IntVar d, BoolVar performed) { + return new OptionalTask(s, d, performed); + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d fixed duration + * @param e integer variable, ending time + * @param optional whether to create an OptionalTask or a Task + * @return a task variable. + */ + default Task taskVar(IntVar s, int d, IntVar e, boolean optional) { + if (optional) { + return new OptionalTask(s, d, e); + } else { + return new Task(s, d, e); + } + } + + /** + * Creates a task variable, based on a starting time s and a duration d + * such that: s + d = e, where e is the ending time. + * + * @param s integer variable, starting time + * @param d fixed duration + * @param e integer variable, ending time + * @param performed boolean variable, whether the task is performed or not + * @return a task variable. + */ + default OptionalTask taskVar(IntVar s, int d, IntVar e, BoolVar performed) { + return new OptionalTask(s, d, e, performed); + } + + /** + * Creates a task variable, made of a starting time s, + * a duration d and an ending time e such that: s + d = e. + * + * @param s integer variable, starting time + * @param d integer variable, duration + * @param e integer variable, ending time + * @param optional whether to create an OptionalTask or a Task + * @return a task variable. + */ + default Task taskVar(IntVar s, IntVar d, IntVar e, boolean optional) { + if (optional) { + return new OptionalTask(s, d, e); + } else { + return new Task(s, d, e); + } + } + + /** + * Creates a task variable, made of a starting time s, + * a duration d and an ending time e such that: s + d = e. + * + * @param s integer variable, starting time + * @param d integer variable, duration + * @param e integer variable, ending time + * @param performed boolean variable, whether the task is performed or not + * @return a task variable. + */ + default OptionalTask taskVar(IntVar s, IntVar d, IntVar e, BoolVar performed) { + return new OptionalTask(s, d, e, performed); } /** * Creates an array of s.length task variables, * where task i is made of a starting time s_i, * a processing time p_i and an ending time e_i such that: s_i + p_i = e_i. - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variables, starting times * @param p integer variables, processing times @@ -652,9 +809,6 @@ default Task[] taskVarArray(IntVar[] s, IntVar[] p, IntVar[] e) { * where task i,j is made of a starting time s_(i,j), * a processing time p_(i,j) and an ending time e_(i,j) such that: * s_(i,j) + p_(i,j) = e_(i,j). - *

      - * A call to {@link Task#ensureBoundConsistency()} is required before launching the resolution, - * this will not be done automatically. * * @param s integer variables, starting times * @param p integer variables, processing times diff --git a/solver/src/main/java/org/chocosolver/solver/variables/IViewFactory.java b/solver/src/main/java/org/chocosolver/solver/variables/IViewFactory.java index ab82ee73f6..3324db40aa 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/IViewFactory.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/IViewFactory.java @@ -187,11 +187,7 @@ default IntVar abs(IntVar var) { int ub = max(-var.getLB(), var.getUB()); String name = "|" + var.getName() + "|"; IntVar abs; - if (var.hasEnumeratedDomain()) { - abs = ref().intVar(name, 0, ub, false); - } else { - abs = ref().intVar(name, 0, ub, true); - } + abs = ref().intVar(name, 0, ub); ref().absolute(abs, var).post(); return abs; } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/IntVar.java b/solver/src/main/java/org/chocosolver/solver/variables/IntVar.java index 851e9e8f7e..3f6b313e01 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/IntVar.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/IntVar.java @@ -37,7 +37,7 @@ * @author Charles Prud'homme * @since 18 nov. 2010 */ -public interface IntVar extends ICause, Variable, Iterable, ArExpression { +public interface IntVar extends ICause, Variable, ArExpression { /** * Provide a minimum value for integer variable lower bound. @@ -618,7 +618,9 @@ default boolean updateBounds(int lb, int ub, ICause cause, Reason reason) throws * * @return the range of this domain */ - int getRange(); + default int getRange(){ + return getUB() - getLB() + 1; + } /** * Returns the first value just after v in this which is in the domain. @@ -759,6 +761,14 @@ default boolean updateBounds(int lb, int ub, ICause cause, Reason reason) throws */ boolean hasEnumeratedDomain(); + /** + * Indicates wether (or not) this has an enumerated domain (represented in extension) and is not a single value + * @return true if the domain is enumerated and the variable is not instantiated yet, false otherwise. + */ + default boolean hasUnfixedEnumeratedDomain() { + return !isInstantiated() && hasEnumeratedDomain(); + } + /** * Allow to monitor removed values of this. * diff --git a/solver/src/main/java/org/chocosolver/solver/variables/OptionalTask.java b/solver/src/main/java/org/chocosolver/solver/variables/OptionalTask.java new file mode 100644 index 0000000000..effd4ba875 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/solver/variables/OptionalTask.java @@ -0,0 +1,493 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.solver.variables; + +import org.chocosolver.sat.Reason; +import org.chocosolver.solver.ICause; +import org.chocosolver.solver.Model; +import org.chocosolver.solver.exception.ContradictionException; + +/** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed + * + * @author Arthur Godet + * @since 25/11/2023 + */ +public class OptionalTask extends Task { + + //*********************************************************************************** + // VARIABLES + //*********************************************************************************** + + private final BoolVar performed; + + //*********************************************************************************** + // CONSTRUCTORS + //*********************************************************************************** + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param model the Model of the variables + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time + */ + public OptionalTask(Model model, int est, int lst, int d, int ect, int lct) { + this(model, est, lst, d, ect, lct, model.boolVar()); + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param model the Model of the variables + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time + * @param performed performed variable + */ + public OptionalTask(Model model, int est, int lst, int d, int ect, int lct, BoolVar performed) { + super(model, est, lst, d, ect, lct); + this.performed = performed; + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param s start variable + * @param d duration value + */ + public OptionalTask(IntVar s, int d) { + this(s, d, s.getModel().boolVar()); + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param s start variable + * @param d duration value + * @param performed performed variable + */ + public OptionalTask(IntVar s, int d, BoolVar performed) { + super(s, d); + this.performed = performed; + } + + /** + * Container representing a task: + * It ensures that: start + duration = end, if and only if the task is performed. + * + * @param s start variable + * @param d duration variable + */ + public OptionalTask(IntVar s, IntVar d) { + this(s, d, s.getModel().boolVar()); + } + + /** + * Container representing a task: + * It ensures that: start + duration = end, if and only if the task is performed. + * + * @param s start variable + * @param d duration variable + * @param performed performed variable + */ + public OptionalTask(IntVar s, IntVar d, BoolVar performed) { + super(s, d); + this.performed = performed; + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param s start variable + * @param d duration value + * @param e end variable + */ + public OptionalTask(IntVar s, int d, IntVar e) { + this(s, d, e, s.getModel().boolVar()); + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed, end being an offset view of start + duration. + * + * @param s start variable + * @param d duration value + * @param e end variable + * @param performed performed variable + */ + public OptionalTask(IntVar s, int d, IntVar e, BoolVar performed) { + super(s, d, e); + this.performed = performed; + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed + * + * @param s start variable + * @param d duration variable + * @param e end variable + */ + public OptionalTask(IntVar s, IntVar d, IntVar e) { + this(s, d, e, s.getModel().boolVar()); + } + + /** + * Container representing an optional task: + * It ensures that: start + duration = end, if and only if the task is performed + * + * @param s start variable + * @param d duration variable + * @param e end variable + * @param performed performed variable + */ + public OptionalTask(IntVar s, IntVar d, IntVar e, BoolVar performed) { + super(s, d, e); + this.performed = performed; + } + + //*********************************************************************************** + // ACCESSORS + //*********************************************************************************** + + @Override + public boolean mayBePerformed() { + return performed.getUB() == 1; + } + + @Override + public boolean mustBePerformed() { + return performed.getLB() == 1; + } + + public BoolVar getPerformed() { + return this.performed; + } + + @Override + public int getEst() { + if (mayBePerformed()) { + return start.getLB(); + } else { + return Integer.MIN_VALUE; + } + } + + @Override + public int getLst() { + if (mayBePerformed()) { + return start.getUB(); + } else { + return Integer.MIN_VALUE; + } + } + + @Override + public int getEct() { + if (mayBePerformed()) { + return end.getLB(); + } else { + return Integer.MAX_VALUE; + } + } + + @Override + public int getLct() { + if (mayBePerformed()) { + return end.getUB(); + } else { + return Integer.MAX_VALUE; + } + } + + @Override + public int getMinDuration() { + if (mayBePerformed()) { + return duration.getLB(); + } else { + return 0; + } + } + + @Override + public int getMaxDuration() { + if (mayBePerformed()) { + return duration.getUB(); + } else { + return Integer.MAX_VALUE; + } + } + + @Override + public boolean forceToBePerformed(ICause cause) throws ContradictionException { + return performed.updateLowerBound(1, cause); + } + + @Override + public boolean forceToBePerformed(ICause cause, Reason reason) throws ContradictionException { + return performed.updateLowerBound(1, cause, reason); + } + + @Override + public boolean forceToBeOptional(ICause cause) throws ContradictionException { + return performed.updateUpperBound(0, cause); + } + + @Override + public boolean forceToBeOptional(ICause cause, Reason reason) throws ContradictionException { + return performed.updateUpperBound(0, cause, reason); + } + + @Override + public boolean updateEst(int est, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (est <= start.getUB()) { + return super.updateEst(est, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateLst(int lst, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (lst >= start.getLB()) { + return super.updateLst(lst, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateEct(int ect, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (ect <= end.getUB()) { + return super.updateEct(ect, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateLct(int lct, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (lct >= end.getLB()) { + return super.updateLct(lct, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateMinDuration(int minDuration, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (minDuration <= duration.getUB()) { + return super.updateMinDuration(minDuration, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateMaxDuration(int maxDuration, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (maxDuration >= duration.getLB()) { + return super.updateMaxDuration(maxDuration, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateDuration(int minDuration, int maxDuration, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (minDuration <= maxDuration && minDuration <= duration.getUB() && maxDuration >= duration.getLB()) { + return super.updateDuration(minDuration, maxDuration, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean instantiateStartAt(int t, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (start.getLB() <= t && t <= start.getUB()) { + return super.instantiateStartAt(t, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean instantiateEndAt(int t, ICause cause) throws ContradictionException { + if (mayBePerformed()) { + if (end.getLB() <= t && t <= end.getUB()) { + return super.instantiateEndAt(t, cause); + } else { + performed.updateUpperBound(0, cause); + } + } + return false; + } + + @Override + public boolean updateEst(int est, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (est <= start.getUB()) { + return super.updateEst(est, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateLst(int lst, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (lst >= start.getLB()) { + return super.updateLst(lst, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateEct(int ect, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (ect <= end.getUB()) { + return super.updateEct(ect, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateLct(int lct, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (lct >= end.getLB()) { + return super.updateLct(lct, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateMinDuration(int minDuration, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (minDuration <= duration.getUB()) { + return super.updateMinDuration(minDuration, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateMaxDuration(int maxDuration, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (maxDuration >= duration.getLB()) { + return super.updateMaxDuration(maxDuration, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean updateDuration(int minDuration, int maxDuration, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (minDuration <= maxDuration && minDuration <= duration.getUB() && maxDuration >= duration.getLB()) { + return super.updateDuration(minDuration, maxDuration, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean instantiateStartAt(int t, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (start.getLB() <= t && t <= start.getUB()) { + return super.instantiateStartAt(t, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public boolean instantiateEndAt(int t, ICause cause, Reason reason) throws ContradictionException { + if (mayBePerformed()) { + if (end.getLB() <= t && t <= end.getUB()) { + return super.instantiateEndAt(t, cause, reason); + } else { + performed.updateUpperBound(0, cause, reason); + } + } + return false; + } + + @Override + public Task getMirror() { + if (mirror == null) { + mirror = new OptionalTask(end.neg().intVar(), duration, start.neg().intVar(), performed); + } + return mirror; + } + + @Override + public String toString() { + return "OptionalTask[" + + "start=" + start + + ", duration=" + duration + + ", end=" + end + + ", performed=" + performed + + ']'; + } +} diff --git a/solver/src/main/java/org/chocosolver/solver/variables/Task.java b/solver/src/main/java/org/chocosolver/solver/variables/Task.java index 7d8b279225..d72b0c31d8 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/Task.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/Task.java @@ -9,33 +9,41 @@ */ package org.chocosolver.solver.variables; +import org.chocosolver.solver.ICause; import org.chocosolver.sat.Reason; import org.chocosolver.solver.Model; +import org.chocosolver.solver.constraints.Constraint; +import org.chocosolver.solver.constraints.Explained; +import org.chocosolver.solver.constraints.Propagator; +import org.chocosolver.solver.constraints.PropagatorPriority; import org.chocosolver.solver.exception.ContradictionException; -import org.chocosolver.solver.variables.events.IEventType; -import org.chocosolver.solver.variables.events.IntEventType; +import org.chocosolver.solver.variables.events.PropagatorEventType; import org.chocosolver.solver.variables.view.integer.IntAffineView; +import org.chocosolver.util.ESat; -import java.util.ArrayList; import java.util.function.Consumer; /** * Container representing a task: * It ensures that: start + duration = end * - * @author Jean-Guillaume Fages - * @since 04/02/2013 + * @author Arthur Godet + * @since 25/11/2023 */ -public class Task { +@Explained +public class Task extends Propagator { //*********************************************************************************** // VARIABLES //*********************************************************************************** - private final IntVar start; - private final IntVar duration; - private final IntVar end; - private IVariableMonitor update; + protected final IntVar start; + protected final IntVar duration; + protected final IntVar end; + + protected Task mirror = null; + + private boolean insidePropagation = false; //*********************************************************************************** // CONSTRUCTORS @@ -46,21 +54,21 @@ public class Task { * It ensures that: start + duration = end, end being an offset view of start + duration. * * @param model the Model of the variables - * @param est earliest starting time - * @param lst latest starting time - * @param d duration - * @param ect earliest completion time - * @param lct latest completion time time + * @param est earliest starting time + * @param lst latest starting time + * @param d duration + * @param ect earliest completion time + * @param lct latest completion time */ public Task(Model model, int est, int lst, int d, int ect, int lct) { - start = model.intVar(est, lst); - duration = model.intVar(d); - if (ect == est + d && lct == lst + d) { - end = start.getModel().offset(start, d); - } else { - end = model.intVar(ect, lct); - declareMonitor(); - } + this(buildVars(model, est, lst, d, ect, lct)); + } + + private static IntVar[] buildVars(Model model, int est, int lst, int d, int ect, int lct) { + IntVar start = model.intVar(Math.max(est, ect - d), Math.min(lst, lct - d)); + IntVar duration = model.intVar(d); + IntVar end = start.getModel().offset(start, d); + return new IntVar[]{start, duration, end}; } /** @@ -71,9 +79,23 @@ public Task(Model model, int est, int lst, int d, int ect, int lct) { * @param d duration value */ public Task(IntVar s, int d) { - start = s; - duration = start.getModel().intVar(d); - end = start.getModel().offset(start, d); + this(s, s.getModel().intVar(d), s.getModel().offset(s, d)); + } + + /** + * Container representing a task: + * It ensures that: start + duration = end, end being an offset view of start + duration. + * + * @param s start variable + * @param d duration value + */ + public Task(IntVar s, IntVar d) { + this( + s, + d, + d.isInstantiated() ? s.getModel().offset(s, d.getValue()) + : s.getModel().intVar(s.getLB() + d.getLB(), s.getUB() + d.getUB()) + ); } /** @@ -85,12 +107,7 @@ public Task(IntVar s, int d) { * @param e end variable */ public Task(IntVar s, int d, IntVar e) { - start = s; - duration = start.getModel().intVar(d); - end = e; - if (!isOffsetView(s, d, e)) { - declareMonitor(); - } + this(s, s.getModel().intVar(d), e); } /** @@ -102,49 +119,37 @@ public Task(IntVar s, int d, IntVar e) { * @param e end variable */ public Task(IntVar s, IntVar d, IntVar e) { + super(new IntVar[]{s, d, e}, PropagatorPriority.TERNARY, false, false); start = s; duration = d; end = e; - if (!d.isInstantiated() || !isOffsetView(s, d.getValue(), e)) { - declareMonitor(); + if (shouldPassivate(s, d, e)) { + setActive(); + setPassive(); + } else { + this.getModel().post(new Constraint("Task relation", this)); } } - private static boolean isOffsetView(IntVar s, int d, IntVar e) { + private Task(IntVar[] vars) { + this(vars[0], vars[1], vars[2]); + } + + public static boolean isOffsetView(IntVar s, int d, IntVar e) { if (e instanceof IntAffineView) { IntAffineView intOffsetView = (IntAffineView) e; - return intOffsetView.equals(s, 1, d); + if (intOffsetView.p) { + return intOffsetView.equals(s, 1, d); + } else { + intOffsetView = (IntAffineView) s; + return intOffsetView.equals(e.neg().intVar(), -1, -d); + } } return false; } - private void declareMonitor() { - if (start.hasEnumeratedDomain() || duration.hasEnumeratedDomain() || end.hasEnumeratedDomain()) { - update = new TaskMonitor(start, duration, end, true); - } else { - update = new TaskMonitor(start, duration, end, false); - } - Model model = start.getModel(); - //noinspection unchecked - ArrayList tset = (ArrayList) model.getHook(Model.TASK_SET_HOOK_NAME); - if (tset == null) { - tset = new ArrayList<>(); - model.addHook(Model.TASK_SET_HOOK_NAME, tset); - } - tset.add(this); - } - - //*********************************************************************************** - // METHODS - //*********************************************************************************** - - /** - * Applies BC-filtering so that start + duration = end - * - * @throws ContradictionException thrown if a inconsistency has been detected between start, end and duration - */ - public void ensureBoundConsistency() throws ContradictionException { - update.onUpdate(start, IntEventType.REMOVE); + public static boolean shouldPassivate(final IntVar s, final IntVar d, final IntVar e) { + return d.isInstantiated() && isOffsetView(s, d.getValue(), e); } //*********************************************************************************** @@ -163,8 +168,290 @@ public IntVar getEnd() { return end; } - public IVariableMonitor getMonitor() { - return update; + public int getEst() { + return start.getLB(); + } + + public int getLst() { + return start.getUB(); + } + + public int getEct() { + return end.getLB(); + } + + public int getLct() { + return end.getUB(); + } + + public int getMinDuration() { + return duration.getLB(); + } + + public int getMaxDuration() { + return duration.getUB(); + } + + public boolean hasCompulsoryPart() { + return getLst() < getEct(); + } + + public boolean isStartInstantiated() { + return start.isInstantiated(); + } + + public boolean isEndInstantiated() { + return end.isInstantiated(); + } + + public boolean updateEst(int est, ICause cause) throws ContradictionException { + if (start.updateLowerBound(est, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateLst(int lst, ICause cause) throws ContradictionException { + if (start.updateUpperBound(lst, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateEct(int ect, ICause cause) throws ContradictionException { + if (end.updateLowerBound(ect, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateLct(int lct, ICause cause) throws ContradictionException { + if (end.updateUpperBound(lct, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateMinDuration(int minDuration, ICause cause) throws ContradictionException { + if (duration.updateLowerBound(minDuration, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateMaxDuration(int maxDuration, ICause cause) throws ContradictionException { + if (duration.updateUpperBound(maxDuration, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateDuration(int minDuration, int maxDuration, ICause cause) throws ContradictionException { + if (duration.updateBounds(minDuration, maxDuration, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean updateEst(int est, ICause cause, Reason reason) throws ContradictionException { + if (start.updateLowerBound(est, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateLst(int lst, ICause cause, Reason reason) throws ContradictionException { + if (start.updateUpperBound(lst, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateEct(int ect, ICause cause, Reason reason) throws ContradictionException { + if (end.updateLowerBound(ect, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateLct(int lct, ICause cause, Reason reason) throws ContradictionException { + if (end.updateUpperBound(lct, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateMinDuration(int minDuration, ICause cause, Reason reason) throws ContradictionException { + if (duration.updateLowerBound(minDuration, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateMaxDuration(int maxDuration, ICause cause, Reason reason) throws ContradictionException { + if (duration.updateUpperBound(maxDuration, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean updateDuration(int minDuration, int maxDuration, ICause cause, Reason reason) throws ContradictionException { + if (duration.updateLowerBound(minDuration, cause, reason) + || duration.updateUpperBound(maxDuration, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean instantiateStartAt(int t, ICause cause) throws ContradictionException { + if (start.instantiateTo(t, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean instantiateEndAt(int t, ICause cause) throws ContradictionException { + if (end.instantiateTo(t, cause)) { + propagate(); + return true; + } + return false; + } + + public boolean instantiateStartAt(int t, ICause cause, Reason reason) throws ContradictionException { + if (start.instantiateTo(t, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean instantiateEndAt(int t, ICause cause, Reason reason) throws ContradictionException { + if (end.instantiateTo(t, cause, reason)) { + propagate(); + return true; + } + return false; + } + + public boolean mayBePerformed() { + return true; + } + + public boolean mustBePerformed() { + return true; + } + + public boolean forceToBePerformed(ICause cause) throws ContradictionException { + return false; + } + + public boolean forceToBePerformed(ICause cause, Reason reason) throws ContradictionException { + return false; + } + + public boolean forceToBeOptional(ICause cause) throws ContradictionException { + ContradictionException ex = new ContradictionException(); + ex.set(cause, null, "forcing Task to be optional"); + throw ex; + } + + public boolean forceToBeOptional(ICause cause, Reason reason) throws ContradictionException { + ContradictionException ex = new ContradictionException(); + ex.set(cause, null, "forcing Task to be optional"); + throw ex; + } + + public Task getMirror() { + if (mirror == null) { + mirror = new Task(end.neg().intVar(), duration, start.neg().intVar()); + } + return mirror; + } + + @Override + public void propagate(int evtmask) throws ContradictionException { + insidePropagation = true; + if (model.getSolver().getNodeCount() == 0) { // Only at root node + updateMinDuration(0, this); + } + boolean hasFiltered; + final boolean lcg = getModel().getSolver().isLCG(); + do { + hasFiltered = false; + if (mayBePerformed()) { + if (lcg) { + hasFiltered = updateEst( + end.getLB() - duration.getUB(), + this, +// Reason.r(end.getMinLit(), duration.getMaxLit()) + Reason.r(end.getLELit(end.getLB() - 1), duration.getGELit(duration.getUB() + 1)) + ); + hasFiltered |= updateLst( + end.getUB() - duration.getLB(), + this, + Reason.r(end.getMaxLit(), duration.getMinLit()) + ); + + hasFiltered |= updateEct( + start.getLB() + duration.getLB(), + this, + Reason.r(start.getMinLit(), duration.getMinLit()) + ); + hasFiltered |= updateLct( + start.getUB() + duration.getUB(), + this, + Reason.r(start.getMaxLit(), duration.getMaxLit()) + ); + + hasFiltered |= updateMinDuration( + end.getLB() - start.getUB(), + this, + Reason.r(end.getMinLit(), start.getMaxLit()) + ); + hasFiltered |= updateMaxDuration( + end.getUB() - start.getLB(), + this, + Reason.r(end.getMaxLit(), start.getMinLit()) + ); + } else { + hasFiltered = updateEst(end.getLB() - duration.getUB(), this); + hasFiltered |= updateLst(end.getUB() - duration.getLB(), this); + + hasFiltered |= updateEct(start.getLB() + duration.getLB(), this); + hasFiltered |= updateLct(start.getUB() + duration.getUB(), this); + + hasFiltered |= updateMinDuration(end.getLB() - start.getUB(), this); + hasFiltered |= updateMaxDuration(end.getUB() - start.getLB(), this); + } + // TODO : add filtering for enumerated domains + } + } while (hasFiltered); + insidePropagation = false; + } + + @Override + public ESat isEntailed() { + if (start.isInstantiated() && duration.isInstantiated() && end.isInstantiated()) { + return ESat.eval(start.getValue() + duration.getValue() == end.getValue()); + } else { + return ESat.UNDEFINED; + } } @Override @@ -176,53 +463,16 @@ public String toString() { ']'; } - private static class TaskMonitor implements IVariableMonitor { - private final IntVar S, D, E; - private final boolean isEnum; - - private TaskMonitor(IntVar S, IntVar D, IntVar E, boolean isEnum) { - this.S = S; - this.D = D; - this.E = E; - S.addMonitor(this); - D.addMonitor(this); - E.addMonitor(this); - this.isEnum = isEnum; - } - - @Override - public void onUpdate(IntVar var, IEventType evt) throws ContradictionException { - boolean lcg = var.getModel().getSolver().isLCG(); - boolean fixpoint; - do { - // start - fixpoint = S.updateLowerBound(E.getLB() - D.getUB(), this, - lcg ? Reason.r(E.getMinLit(), D.getMaxLit()) : Reason.undef()); - fixpoint |= S.updateUpperBound(E.getUB() - D.getLB(), this, - lcg ? Reason.r(E.getMaxLit(), D.getMinLit()) : Reason.undef()); - // end - fixpoint |= E.updateLowerBound(S.getLB() + D.getLB(), this, - lcg ? Reason.r(S.getMinLit(), D.getMinLit()) : Reason.undef()); - fixpoint |= E.updateUpperBound(S.getUB() + D.getUB(), this, - lcg ? Reason.r(S.getMaxLit(), D.getMaxLit()) : Reason.undef()); - // duration - fixpoint |= D.updateLowerBound(E.getLB() - S.getUB(), this, - lcg ? Reason.r(E.getMinLit(), S.getMaxLit()) : Reason.undef()); - fixpoint |= D.updateUpperBound(E.getUB() - S.getLB(), this, - lcg ? Reason.r(E.getMaxLit(), S.getMinLit()) : Reason.undef()); - } while (fixpoint && isEnum); - } - - @Override - public void forEachIntVar(Consumer action) { - action.accept(S); - action.accept(D); - action.accept(E); - } - - @Override - public String toString() { - return "Task[" + S.getName() + "+" + D.getName() + "=" + E.getName() + "]"; + @Override + public void forEachIntVar(Consumer action) { + action.accept(start); + action.accept(duration); + action.accept(end); + } + + private void propagate() throws ContradictionException { + if (!insidePropagation) { + propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); } } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/EnumDeltaMonitor.java b/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/EnumDeltaMonitor.java index e4b0349317..b4e4917b4e 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/EnumDeltaMonitor.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/EnumDeltaMonitor.java @@ -96,6 +96,6 @@ public String toString() { @Override public int sizeApproximation() { - return last - first; + return delta.size() - (needReset()? 0 : this.last); } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/IntervalDeltaMonitor.java b/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/IntervalDeltaMonitor.java index f040635183..e664d0ec94 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/IntervalDeltaMonitor.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/delta/monitor/IntervalDeltaMonitor.java @@ -103,6 +103,6 @@ public String toString() { @Override public int sizeApproximation() { - return last - first; + return delta.size() - (needReset()? 0 : this.last); } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/AbstractGraphVar.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/AbstractGraphVar.java index 8c82002f1d..6fad93a0c4 100755 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/AbstractGraphVar.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/AbstractGraphVar.java @@ -22,6 +22,7 @@ import org.chocosolver.util.iterators.EvtScheduler; import org.chocosolver.util.objects.graphs.IGraph; import org.chocosolver.util.objects.setDataStructures.ISet; +import org.chocosolver.util.objects.setDataStructures.ISetIterator; public abstract class AbstractGraphVar extends AbstractVariable implements GraphVar { @@ -63,7 +64,9 @@ public boolean isInstantiated() { return false; } ISet suc; - for (int i : getUB().getNodes()) { + ISetIterator iterator = getUB().getNodes().newIterator(); + while (iterator.hasNext()) { + int i = iterator.next(); suc = UB.getSuccessorsOf(i); if (suc.size() != getLB().getSuccessorsOf(i).size()) { return false; diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/BitsetIntVarImpl.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/BitsetIntVarImpl.java index 2568fd35c6..e51d60a6f3 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/BitsetIntVarImpl.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/BitsetIntVarImpl.java @@ -27,12 +27,9 @@ import org.chocosolver.util.iterators.DisposableRangeIterator; import org.chocosolver.util.iterators.DisposableValueIterator; import org.chocosolver.util.iterators.EvtScheduler; -import org.chocosolver.util.iterators.IntVarValueIterator; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; import org.chocosolver.util.tools.ArrayUtils; -import java.util.Iterator; - /** *
      * @@ -83,11 +80,6 @@ public final class BitsetIntVarImpl extends AbstractVariable implements IntVar { */ private DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private IntVarValueIterator _javaIterator; - /** * Create an enumerated IntVar based on a bitset * @@ -585,11 +577,6 @@ public int getDomainSize() { return SIZE.get(); } - @Override - public int getRange() { - return getUB() - getLB() + 1; - } - @Override public int nextValue(int aValue) { aValue -= OFFSET; @@ -817,13 +804,4 @@ public int max() { } return _riterator; } - - @Override - public Iterator iterator() { - if (_javaIterator == null) { - _javaIterator = new IntVarValueIterator(this); - } - _javaIterator.reset(); - return _javaIterator; - } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarEagerLit.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarEagerLit.java index d4ef34ba29..80b59a5ab8 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarEagerLit.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarEagerLit.java @@ -27,8 +27,6 @@ import org.chocosolver.util.ESat; import org.chocosolver.util.iterators.*; -import java.util.Iterator; - import static org.chocosolver.sat.MiniSat.C_Undef; /** @@ -63,10 +61,6 @@ public class BoolVarEagerLit extends AbstractVariable implements BoolVar, LitVar * To iterate over ranges */ private DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private IntVarValueIterator _javaIterator; /** * Set to true if this variable reacts is associated with at least one propagator which reacts * on value removal @@ -301,6 +295,7 @@ public int getDomainSize() { @Override public int getRange() { + // For boolean variables, the range is always equal to the domain size return getDomainSize(); } @@ -424,15 +419,6 @@ public DisposableRangeIterator getRangeIterator(boolean bottomUp) { return _riterator; } - @Override - public Iterator iterator() { - if (_javaIterator == null) { - _javaIterator = new IntVarValueIterator(this); - } - _javaIterator.reset(); - return _javaIterator; - } - @Override public void _setNot(BoolVar neg) { this.not = neg; diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarImpl.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarImpl.java index 1ccd179562..b8d2dfc69a 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarImpl.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/BoolVarImpl.java @@ -26,8 +26,6 @@ import org.chocosolver.util.iterators.*; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import java.util.Iterator; - /** *
      * @@ -56,10 +54,6 @@ public class BoolVarImpl extends AbstractVariable implements BoolVar { * To iterate over ranges */ private DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private IntVarValueIterator _javaIterator; /** * Set to true if this variable reacts is associated with at least one propagator which reacts * on value removal @@ -250,7 +244,7 @@ public boolean updateBounds(int lb, int ub, ICause cause) throws ContradictionEx } return hasChanged; } - + @Override public boolean isInstantiated() { return mValue < kUNDEF; @@ -270,7 +264,7 @@ public boolean contains(int aValue) { } @Override - public int getValue() throws IllegalStateException{ + public int getValue() throws IllegalStateException { if (!isInstantiated()) { throw new IllegalStateException("getValue() can be only called on instantiated variable. " + name + " is not instantiated"); @@ -319,6 +313,7 @@ public int getDomainSize() { @Override public int getRange() { + // For boolean variables, the range is always equal to the domain size return getDomainSize(); } @@ -442,15 +437,6 @@ public DisposableRangeIterator getRangeIterator(boolean bottomUp) { return _riterator; } - @Override - public Iterator iterator() { - if (_javaIterator == null) { - _javaIterator = new IntVarValueIterator(this); - } - _javaIterator.reset(); - return _javaIterator; - } - @Override public void _setNot(BoolVar neg) { this.not = neg; diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/FixedIntVarImpl.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/FixedIntVarImpl.java index 321391d664..ba9001850b 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/FixedIntVarImpl.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/FixedIntVarImpl.java @@ -25,11 +25,8 @@ import org.chocosolver.util.iterators.DisposableRangeIterator; import org.chocosolver.util.iterators.DisposableValueIterator; import org.chocosolver.util.iterators.EvtScheduler; -import org.chocosolver.util.iterators.IntVarValueIterator; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import java.util.Iterator; - /** * A IntVar with one domain value. *

      @@ -57,11 +54,6 @@ public class FixedIntVarImpl extends AbstractVariable implements IntVar { */ private DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private final IntVarValueIterator _javaIterator = new IntVarValueIterator(this); - /** * Creates a variable whom domain is natively reduced to the singleton {constante}. * @@ -397,10 +389,4 @@ public int max() { return _riterator; } - @Override - public Iterator iterator() { - _javaIterator.reset(); - return _javaIterator; - } - } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarEagerLit.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarEagerLit.java index 175bcc05c8..c9d1cd24ac 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarEagerLit.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarEagerLit.java @@ -26,7 +26,6 @@ import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; import java.util.Arrays; -import java.util.Iterator; import static org.chocosolver.sat.MiniSat.*; @@ -72,7 +71,12 @@ public IntVarEagerLit(IntVar var) { lit_max = var.getUB(); int capacity = lit_max - lit_min + 1; - if (capacity > 30 && (lit_max - lit_min) / var.getDomainSize() > 5) { + // if the underlying variable is a constant + if (capacity == 1) { + values = var.stream().toArray(); + base_vlit = base_blit = -1; + } else if (capacity > 30 && (lit_max - lit_min) / var.getDomainSize() > 5) { + // if the domain is sparse values = var.stream().toArray(); // init vlits base_vlit = 2 * (sat.nVars()); @@ -80,6 +84,7 @@ public IntVarEagerLit(IntVar var) { base_blit = 2 * (sat.nVars() + values.length) + 1; initSparseDomain(); } else { + // if the domain is dense values = null; // init vlits base_vlit = 2 * (sat.nVars() - lit_min); @@ -121,6 +126,12 @@ private void initSparseDomain() { for (int v : values) { sat.newVariable(new MiniSat.ChannelInfo(this, 1, 1, v)); } + for (int i = lit_min; i <= lit_min; i++) { + sat.cEnqueue(getGELit(i), Reason.undef()); + } + for (int i = lit_max; i <= lit_max; i++) { + sat.cEnqueue(getLELit(i), Reason.undef()); + } } @Override @@ -176,8 +187,8 @@ private int findIndex(int v, RoundMode type) { return u; case ROUND_UP: return l; - default: case ROUND_NONE: + default: return -1; } } @@ -195,6 +206,9 @@ public int getLit(int v, int t) { if (values == null) { return base_vlit + 2 * v; } else { + if (values.length == 1) { + return values[0] == v ? 0 : 1; + } int u = findIndex(v, RoundMode.ROUND_NONE); return (u == -1 ? 1 : base_vlit + 2 * u); } @@ -203,16 +217,35 @@ public int getLit(int v, int t) { if (values == null) { return base_vlit + 2 * v + 1; } else { + if (values.length == 1) { + return values[0] == v ? 1 : 0; + } int u = findIndex(v, RoundMode.ROUND_NONE); return (u == -1 ? 0 : base_vlit + 2 * u + 1); } } case LR_GE: { - int u = values == null ? v : findIndex(v, RoundMode.ROUND_UP); + int u; + if (values == null) { + u = v; + } else { + if (values.length == 1) { + return values[0] > v ? 0 : 1; + } + u = findIndex(v, RoundMode.ROUND_UP); + } return base_blit + 2 * u; } case LR_LE: { - int u = values == null ? v : findIndex(v, RoundMode.ROUND_DOWN); + int u; + if (values == null) { + u = v; + } else { + if (values.length == 1) { + return values[0] < v ? 0 : 1; + } + u = findIndex(v, RoundMode.ROUND_DOWN); + } return base_blit + 2 * u + 1; } default: @@ -581,11 +614,6 @@ public IIntDeltaMonitor monitorDelta(ICause propagator) { return var.monitorDelta(propagator); } - @Override - public Iterator iterator() { - return var.iterator(); - } - @Override public boolean isInstantiated() { return var.isInstantiated(); diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarLazyLit.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarLazyLit.java index 74a25df16f..4af0537bba 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarLazyLit.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntVarLazyLit.java @@ -28,8 +28,6 @@ import org.chocosolver.util.iterators.EvtScheduler; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import java.util.Iterator; - import static org.chocosolver.sat.MiniSat.C_Undef; /** @@ -115,10 +113,10 @@ public void channel(int val, int val_type, int sign) { @Override public int getLit(int val, int type) { - if (val < getLB()) { + if (val < min0) { return 1 ^ (type & 1); // undefined, undefined, true, false } - if (val > getUB()) { + if (val > max0) { return type & 1; // undefined, undefined, false, true } switch (type) { @@ -361,11 +359,6 @@ public IIntDeltaMonitor monitorDelta(ICause propagator) { return var.monitorDelta(propagator); } - @Override - public Iterator iterator() { - return var.iterator(); - } - @Override public boolean isInstantiated() { return var.isInstantiated(); diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntervalIntVarImpl.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntervalIntVarImpl.java index 7e033a7000..3b1e126a80 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/IntervalIntVarImpl.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/IntervalIntVarImpl.java @@ -26,8 +26,6 @@ import org.chocosolver.util.iterators.*; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import java.util.Iterator; - /** *
      * @@ -62,11 +60,6 @@ public final class IntervalIntVarImpl extends AbstractVariable implements IntVar */ private DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private IntVarValueIterator _javaIterator; - /** * Create a bounded domain IntVar : [min,max] * @@ -348,8 +341,8 @@ public boolean contains(int aValue) { } @Override - public int getValue() throws IllegalStateException{ - if(!isInstantiated()) { + public int getValue() throws IllegalStateException { + if (!isInstantiated()) { throw new IllegalStateException("getValue() can be only called on instantiated variable. " + name + " is not instantiated"); } @@ -383,6 +376,7 @@ public int getDomainSize() { @Override public int getRange() { + // For bounded variables, the range is always equal to the domain size return getDomainSize(); } @@ -506,13 +500,4 @@ public DisposableRangeIterator getRangeIterator(boolean bottomUp) { return _riterator; } - @Override - public Iterator iterator() { - if (_javaIterator == null) { - _javaIterator = new IntVarValueIterator(this); - } - _javaIterator.reset(); - return _javaIterator; - } - } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/impl/scheduler/BoolEvtScheduler.java b/solver/src/main/java/org/chocosolver/solver/variables/impl/scheduler/BoolEvtScheduler.java index 6619c61af7..4f76b31ab8 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/impl/scheduler/BoolEvtScheduler.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/impl/scheduler/BoolEvtScheduler.java @@ -18,8 +18,6 @@ */ public class BoolEvtScheduler implements EvtScheduler { - private static final int[] DIS = new int[]{0, 1, -1, // INSTANTIATE - }; private int i = 0; @Override @@ -38,12 +36,12 @@ public int select(int mask) { @Override public boolean hasNext() { - return DIS[i] > -1; + return i < 2; // Only INSTANTIATE and BOUND } @Override public int next() { - return DIS[i++]; + return i++; } @Override diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/IntView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/IntView.java index c0b5667fb1..db24ec6d22 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/IntView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/IntView.java @@ -19,7 +19,6 @@ import org.chocosolver.solver.variables.events.IEventType; import org.chocosolver.util.iterators.*; -import java.util.Iterator; import java.util.function.Consumer; /** @@ -58,15 +57,11 @@ public abstract class IntView extends AbstractView implemen */ protected DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private final IntVarValueIterator _javaIterator = new IntVarValueIterator(this); - /** * Create a view based on {@code var} + * * @param name name of the view - * @param var observed variable + * @param var observed variable */ protected IntView(String name, I var) { super(name, var); @@ -88,11 +83,6 @@ public int getDomainSize() { return var.getDomainSize(); } - @Override - public int getRange() { - return var.getRange(); - } - @Override public boolean hasEnumeratedDomain() { return var.hasEnumeratedDomain(); @@ -103,7 +93,7 @@ public boolean isInstantiated() { return getVariable().isInstantiated(); } - @Override + @Override public IDelta getDelta() { return var.getDelta(); } @@ -131,7 +121,7 @@ public DisposableValueIterator getValueIterator(boolean bottomUp) { return _viterator; } - @Override + @Override public DisposableRangeIterator getRangeIterator(boolean bottomUp) { if (_riterator == null || _riterator.isNotReusable()) { _riterator = new DisposableRangeBoundIterator(this); @@ -144,12 +134,6 @@ public DisposableRangeIterator getRangeIterator(boolean bottomUp) { return _riterator; } - @Override - public Iterator iterator() { - _javaIterator.reset(); - return _javaIterator; - } - @Override public void forEachIntVar(Consumer action) { action.accept(var); diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolEqView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolEqView.java index f1c3200b90..069a863eba 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolEqView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolEqView.java @@ -53,6 +53,7 @@ public ESat getBooleanValue() { @Override public boolean instantiateTo(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause boolean done = false; if (value < 0 || value > 1) { if (getModel().getSolver().isLCG()) { diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolGeqView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolGeqView.java index 3069908de8..d74964b287 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolGeqView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolGeqView.java @@ -52,6 +52,7 @@ public ESat getBooleanValue() { @Override public boolean instantiateTo(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause boolean done = false; if (value < 0 || value > 1) { if (getModel().getSolver().isLCG()) { diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolNotView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolNotView.java index c46dedcbdd..7d9a2fa205 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolNotView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolNotView.java @@ -96,6 +96,7 @@ public boolean removeInterval(int from, int to, ICause cause) throws Contradicti @Override public boolean instantiateTo(int value, ICause cause, Reason reason) throws ContradictionException { if (!isInstantiatedTo(value)) { + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause boolean done = var.instantiateTo(1 - value, this, reason); notifyPropagators(IntEventType.INSTANTIATE, cause); return done; diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolSetView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolSetView.java index 97ea61c73b..badf3b9cda 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolSetView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/bool/BoolSetView.java @@ -30,8 +30,6 @@ import org.chocosolver.util.iterators.*; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import java.util.Iterator; - /** * Boolean view b over a set variable S: * With v an integer, b = true iff S contains v. @@ -78,10 +76,6 @@ public class BoolSetView extends AbstractView implements Bo * Range iterator */ protected DisposableRangeIterator _riterator; - /** - * Value iterator allowing for(int i:this) loops - */ - private final IntVarValueIterator _javaIterator = new IntVarValueIterator(this); public BoolSetView(int v, S setVar) { super("boolSetView[" + v + " in " + setVar.getName() + "]", setVar); @@ -167,7 +161,8 @@ public int getDomainSize() { @Override public int getRange() { - return getUB() - getLB(); + // For boolean variables, the range is always equal to the domain size + return getDomainSize(); } @Override @@ -205,7 +200,7 @@ public final int getTypeAndKind() { } @Override - public final int getValue() throws IllegalStateException{ + public final int getValue() throws IllegalStateException { if (!isInstantiated()) { throw new IllegalStateException("getValue() can be only called on instantiated variable. " + name + " is not instantiated"); @@ -401,9 +396,4 @@ public final DisposableRangeIterator getRangeIterator(boolean bottomUp) { return _riterator; } - @Override - public Iterator iterator() { - _javaIterator.reset(); - return _javaIterator; - } } diff --git a/solver/src/main/java/org/chocosolver/solver/variables/view/integer/IntAffineView.java b/solver/src/main/java/org/chocosolver/solver/variables/view/integer/IntAffineView.java index 3e5aaf9802..74229aeb74 100644 --- a/solver/src/main/java/org/chocosolver/solver/variables/view/integer/IntAffineView.java +++ b/solver/src/main/java/org/chocosolver/solver/variables/view/integer/IntAffineView.java @@ -74,6 +74,7 @@ protected int transform(int value) { @Override public boolean removeValue(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause int inf = getLB(); int sup = getUB(); if (inf > value || value > sup) return false; @@ -106,6 +107,7 @@ public boolean removeValue(int value, ICause cause, Reason reason) throws Contra @Override public boolean instantiateTo(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause value -= b; if (a > 1) { if ((value % a) != 0) { @@ -129,6 +131,7 @@ public boolean instantiateTo(int value, ICause cause, Reason reason) throws Cont @Override public boolean updateLowerBound(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause int old = this.getLB(); if (old >= value) return false; value--; @@ -156,6 +159,7 @@ public boolean updateLowerBound(int value, ICause cause, Reason reason) throws C @Override public boolean updateUpperBound(int value, ICause cause, Reason reason) throws ContradictionException { assert cause != null; + reason = cause.manageReification().apply(reason); // to deal with reification of the earliest cause int old = this.getUB(); if (old <= value) return false; value -= b; diff --git a/solver/src/main/java/org/chocosolver/util/bandit/Policy.java b/solver/src/main/java/org/chocosolver/util/bandit/Policy.java index 7809e3619e..5b23ad9cbe 100644 --- a/solver/src/main/java/org/chocosolver/util/bandit/Policy.java +++ b/solver/src/main/java/org/chocosolver/util/bandit/Policy.java @@ -41,4 +41,26 @@ public interface Policy { */ void update(int action, double reward); + class NextModuloN implements Policy { + + int n; + + public NextModuloN(int n) { + this.n = n; + } + + @Override + public void init() { + } + + @Override + public int nextAction(int step) { + return step % n; + } + + @Override + public void update(int action, double reward) { + } + } + } diff --git a/solver/src/main/java/org/chocosolver/util/graphOperations/connectivity/UGVarConnectivityHelper.java b/solver/src/main/java/org/chocosolver/util/graphOperations/connectivity/UGVarConnectivityHelper.java index 6aabeff467..ba0d266364 100755 --- a/solver/src/main/java/org/chocosolver/util/graphOperations/connectivity/UGVarConnectivityHelper.java +++ b/solver/src/main/java/org/chocosolver/util/graphOperations/connectivity/UGVarConnectivityHelper.java @@ -41,7 +41,7 @@ public class UGVarConnectivityHelper { private final List bridges = new ArrayList<>(); // --- constructor - public UGVarConnectivityHelper(UndirectedGraphVar g){ + public UGVarConnectivityHelper(UndirectedGraphVar g) { this.g = g; this.n = g.getNbMaxNodes(); this.fifo = new int[n]; @@ -78,12 +78,14 @@ public void computeMandatoryArticulationPointsAndBridges() { articulationPoints.clear(); bridges.clear(); ISet mNodes = g.getMandatoryNodes(); - if(g.getMandatoryNodes().size()<2) return; + if (g.getMandatoryNodes().size() < 2) { + return; + } visited.clear(); Arrays.fill(num, 0); - for(int root : mNodes) { - if(!visited.get(root)) { + for (int root : mNodes) { + if (!visited.get(root)) { // root node init visited.set(root); // DFS from root @@ -94,28 +96,29 @@ public void computeMandatoryArticulationPointsAndBridges() { /** * Computes ridge and articulation point detection linking mandatory nodes. + * * @param s root node, must be a mandatory node itself */ private void computeMandatoryArticulationPointsAndBridgesFrom(int s) { assert g.getMandatoryNodes().contains(s); numOrder = 1; num[s] = numOrder++; - for (int next:g.getPotentialNeighborsOf(s)) { + for (int next : g.getPotentialNeighborsOf(s)) { if (num[next] == 0) { int[] LowMand = doFindArticulation(next, s); int lowN = LowMand[0]; int mandN = LowMand[1]; - if(num[next] == lowN && mandN == 1 && !g.getMandatoryNeighborsOf(s).contains(next)){ - bridges.add(new int[]{s,next}); + if (num[next] == lowN && mandN == 1 && !g.getMandatoryNeighborsOf(s).contains(next)) { + bridges.add(new int[]{s, next}); } } } } - private int[] doFindArticulation (int s, int parent) { + private int[] doFindArticulation(int s, int parent) { int lowpt = num[s] = numOrder++; - int mand = g.getMandatoryNodes().contains(s)?1:0; - for (int next:g.getPotentialNeighborsOf(s)) { + int mand = g.getMandatoryNodes().contains(s) ? 1 : 0; + for (int next : g.getPotentialNeighborsOf(s)) { if (num[next] == 0) { int[] LowMand = doFindArticulation(next, s); int lowN = LowMand[0]; @@ -124,13 +127,13 @@ private int[] doFindArticulation (int s, int parent) { mand = Math.max(mand, mandN); if (lowN >= num[s] && mandN == 1) { articulationPoints.add(s); - if(num[next] == lowN && !g.getMandatoryNeighborsOf(s).contains(next)){ - bridges.add(new int[]{s,next}); + if (num[next] == lowN && !g.getMandatoryNeighborsOf(s).contains(next)) { + bridges.add(new int[]{s, next}); } } } else if (num[next] < num[s] && next != parent) { - lowpt = Math.min(num[next],lowpt); - mand = Math.max(mand, g.getMandatoryNodes().contains(next)?1:0); + lowpt = Math.min(num[next], lowpt); + mand = Math.max(mand, g.getMandatoryNodes().contains(next) ? 1 : 0); } } return new int[]{lowpt, mand}; diff --git a/solver/src/main/java/org/chocosolver/util/objects/BipartiteMatching.java b/solver/src/main/java/org/chocosolver/util/objects/BipartiteMatching.java new file mode 100644 index 0000000000..099569f381 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/util/objects/BipartiteMatching.java @@ -0,0 +1,144 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.util.objects; + + +/** + * Stores a matching in a bipartite graph G = (U,V,E) + * The set of vertices U and V are intervals of integers + * The matching is implemented as an array of integers + * We distinguish the operations over U and V + * @author Sulian Le Bozec-Chiffoleau + * @since 17 Oct. 2024 + */ + +public class BipartiteMatching { + private final int minU; + private final int maxU; + private final int sizeU; + private final int minV; + private final int maxV; + private final int sizeV; + private final int unmatched; + private final int[] matchingU; + private final int[] matchingV; + private int size; + + public BipartiteMatching(int minU, int maxU, int minV, int maxV) { + this.minU = minU; + this.maxU = maxU; + this.sizeU = maxU - minU + 1; + this.minV = minV; + this.maxV = maxV; + this.sizeV = maxV - minV + 1; + this.unmatched = minU < minV ? minU - 1 : minV - 1; + this.matchingU = new int[sizeU]; + for (int i = 0; i < sizeU; i++) { + matchingU[i] = unmatched; + } + this.matchingV = new int[sizeV]; + for (int i = 0; i < sizeV; i++) { + matchingV[i] = unmatched; + } + this.size = 0; + } + + public int getSizeU() {return this.sizeU;} + + public int getSizeV() {return this.sizeV;} + + public boolean inMatchingU(int u) { + return matchingU[u - minU] != unmatched; + } + + public boolean inMatchingV(int v) { + return matchingV[v - minV] != unmatched; + } + + public int getMatchU(int u) { + if (!inMatchingU(u)) { + throw new Error("Error: This vertex is not matched"); + } + return matchingU[u - minU]; + } + + public int getMatchV(int v) { + if (!inMatchingV(v)) { + throw new Error("Error: This vertex is not matched"); + } + return matchingV[v - minV]; + } + + public void setMatch(int u, int v) { + if (inMatchingU(u) || inMatchingV(v)) { + throw new Error("Error: The vertices you want to match are already matched"); + } + matchingU[u - minU] = v; + matchingV[v - minV] = u; + size++; + } + + public void unMatch(int u, int v) { + if (!inMatchingU(u) || !inMatchingV(v) || getMatchU(u) != v || getMatchV(v) != u) { + throw new Error("Error: The pair you want to unmatch is not part of the matching"); + } + matchingU[u - minU] = unmatched; + matchingV[v - minV] = unmatched; + size--; + } + + public int getSize() { + return size; + } + + public boolean isMaximum() { + return (size == sizeU) || (size == sizeV); + } + + public boolean isValid() { + boolean[] scanned = new boolean[sizeV]; + int count = 0; + for (int u = minU; u <= maxU; u++) { + if (inMatchingU(u)) { + count++; + int v = getMatchU(u); + if (scanned[v - minV] || getMatchV(v) != u) {return false;} + scanned[v - minV] = true; + } + } + for (int v = minV; v <= maxV; v++) { + if (inMatchingV(v)) { + count--; + } + } + return count == 0; + } + + @Override + public String toString() { + String printedString = ""; + for (int u = minU; u <= maxU; u++) { + if (inMatchingU(u)) { + printedString += u + " <--> " + getMatchU(u) + '\n'; + } + } + return printedString; + } + + public boolean equals(BipartiteMatching otherMatching) { + if (sizeU != otherMatching.getSizeU() || sizeV != otherMatching.getSizeV()) {return false;} + for (int u = minU; u <= maxU; u++) { + if ((inMatchingU(u) && !otherMatching.inMatchingU(u)) || (!inMatchingU(u) && otherMatching.inMatchingU(u)) || getMatchU(u) != otherMatching.getMatchU(u)) { + return false; + } + } + return true; + } +} diff --git a/solver/src/main/java/org/chocosolver/util/objects/IntCircularQueue.java b/solver/src/main/java/org/chocosolver/util/objects/IntCircularQueue.java index 3ee1d3bf32..86aa7eaca2 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/IntCircularQueue.java +++ b/solver/src/main/java/org/chocosolver/util/objects/IntCircularQueue.java @@ -167,7 +167,7 @@ private void doubleCapacity() { @Override public String toString() { final StringBuilder buf = new StringBuilder("{"); - for (int i = 0; i < size - 1; i++) { + for (int i = 0; i < size; i++) { int pos = convert(head + i, 0); buf.append(elementData[pos]); buf.append(", "); diff --git a/solver/src/main/java/org/chocosolver/util/objects/TrackingList.java b/solver/src/main/java/org/chocosolver/util/objects/TrackingList.java new file mode 100644 index 0000000000..bf6cc2856a --- /dev/null +++ b/solver/src/main/java/org/chocosolver/util/objects/TrackingList.java @@ -0,0 +1,207 @@ +/* + * This file is part of choco-solver, http://choco-solver.org/ + * + * Copyright (c) 2025, IMT Atlantique. All rights reserved. + * + * Licensed under the BSD 4-clause license. + * + * See LICENSE file in the project root for full license information. + */ +package org.chocosolver.util.objects; + +import org.chocosolver.memory.IEnvironment; + +/** + * The Tracking list is similar to a doubly linked list + * for which each element has a predecessor and a successor, implemented by arrays + * An artificial source node is added at the beginning of the list + * An artificial sink node is added at the end of the list + * The tracking list allows to call the function TrackPrev that is specific to this data structure + * Furthermore, it is possible to distinguish the elements removed/reinserted to the list and to the universe in case the tracking list is used in a dynamic environment + * @author Sulian Le Bozec-Chiffoleau + * @since 17 Oct. 2024 + */ + +public class TrackingList { + private final int[] successor; + private final int[] predecessor; + private final int[] stackRemoved; + private int topRemoved; + private final boolean[] present; + private final int source; + private final int sink; + private final int minValue; + private final int maxValue; + private final int maxSize; + private int universeSize; + + public TrackingList(int a, int b) { + this.minValue = a; + this.maxValue = b; + this.maxSize = maxValue - minValue + 1; + this.source = minValue -1; + this.sink = maxValue + 1; + this.successor = new int[maxSize+1]; // Position 0 indicates the successor of the source node + this.predecessor = new int[maxSize+1]; // Position maxSize indicates the predecessor of the sink node + for (int i = 0; i < maxSize+1; i++) { + this.successor[i] = i; + this.predecessor[i] = i-1; + } + this.universeSize = maxSize; + this.stackRemoved = new int[maxSize]; + this.topRemoved = 0; + this.present = new boolean[maxSize]; + for (int i = 0; i < maxSize; i++) {present[i] = true;} + } + + + public int getSize() {return universeSize - topRemoved;} + + public int getUniverseSize() {return universeSize;} + + /** + * Returns the next element in the in-list + * Starts by converting the element into its equivalent index and then gets the next index in the in-list + * Ends by converting the found index into the actual element and returns it + */ + public int getNext(int e) {return convertToValue(successor[convertToIndex(e) +1]);} + + /** + * Returns the previous element in the in-list + * Starts by converting the element into its equivalent index and then gets the previous index in the in-list + * Ends by converting the found index into the actual element and returns it + */ + public int getPrevious(int e) {return convertToValue(predecessor[convertToIndex(e)]);} + + /** + * Returns the artificial source element + */ + public int getSource(){return source;} + + /** + * Returns the artificial sink element + */ + public int getSink(){return sink;} + + public boolean isEmpty(){return topRemoved == universeSize;} + + /** + * Returns true iff the element is present in the in-list + */ + public boolean isPresent(int e) { + return present[convertToIndex(e)]; + } + + /** + * Returns true iff the element is not the first element in the in-list + */ + public boolean hasPrevious(int e) { + return (getPrevious(e) != source); + } + + /** + * Returns true iff the element is not the last element in the in-list + */ + public boolean hasNext(int e) { + return (getNext(e) != sink); + } + + /** + * Removes an element from the in-list + * @Warning You can only remove an element that is present in the in-list and that is not the source nor the sink + */ + public void remove(int e) { + if (!isPresent(e)) {throw new Error("Error: This element is not present in the in-list");} + int i = convertToIndex(e); + successor[predecessor[i] + 1] = successor[i + 1]; + predecessor[successor[i + 1]] = predecessor[i]; + stackRemoved[topRemoved] = i; + topRemoved++; + present[i] = false; + } + + /** + * This method allows to reduce the universe so that it is not an interval anymore + * @Warning This method can be called only if every element of the universe is present in the in-list + */ + public void removeFromUniverse(int e) { + if (!isPresent(e)) {throw new Error("Error: This element is not present in the in-list");} + else if (topRemoved != 0) {throw new Error("Error: The tracking has not been refilled yet with all elements from the universe");} + int i = convertToIndex(e); + successor[predecessor[i] + 1] = successor[i + 1]; + predecessor[successor[i + 1]] = predecessor[i]; + universeSize--; + present[i] = false; + } + + /** + * This method is used when using the Tracking List as a backtrackable structure in Choco + * @Warning This method can be called only if every element of the universe is present in the in-list + */ + public void removeFromUniverse(int e, IEnvironment env) { + if (!isPresent(e)) {throw new Error("Error: This element is not present in the in-list");} + else if (topRemoved != 0) {throw new Error("Error: The tracking has not been refilled yet with all elements from the universe");} + int i = convertToIndex(e); + int pi = predecessor[i]; + int si = successor[i + 1]; + successor[pi + 1] = si; + predecessor[si] = pi; + universeSize--; + present[i] = false; + + // Here we store the operations to call during the backtrack + env.save(() -> { + successor[pi + 1] = i; + predecessor[si] = i; + universeSize++; + present[i] = true; + }); + } + + /** + * Refills the tracking list with all the elements from the universe + */ + public void refill() { + while (topRemoved != 0) { + int i = stackRemoved[topRemoved - 1]; + successor[predecessor[i] + 1] = i; + predecessor[successor[i + 1]] = i; + topRemoved--; + present[i] = true; + } + } + + /** + * Special elementary function of the tracking list + * Returns the first element in the in-list from a given element and toward the predecessors + */ + public int trackPrev(int e) { + int i = convertToIndex(e); + int indexSource = convertToIndex(source); + int indexSink = convertToIndex(sink); + while (i != indexSource && i != indexSink && !present[i]) { + i = predecessor[i]; + } + return convertToValue(i); + } + + @Override + public String toString() { + int node = getSource(); + String printedString = ""; + while (hasNext(node)) { + node = getNext(node); + printedString += node + " "; + } + return printedString; + } + + ////////////////////////////////////////// + // Private functions of the Tracking List + ////////////////////////////////////////// + + private int convertToIndex(int e) {return e - minValue;} + + private int convertToValue(int i) {return i + minValue;} + +} diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/FixedIntArrayIterator.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/FixedIntArrayIterator.java new file mode 100644 index 0000000000..fd7bf9f483 --- /dev/null +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/FixedIntArrayIterator.java @@ -0,0 +1,26 @@ +package org.chocosolver.util.objects.setDataStructures; + +public class FixedIntArrayIterator implements ISetIterator { + + private final int[] array; + private int index = 0; + + public FixedIntArrayIterator(int[] array) { + this.array = array; + } + + @Override + public void reset() { + index = 0; + } + + @Override + public int nextInt() { + return array[index++]; + } + + @Override + public boolean hasNext() { + return index < array.length; + } +} diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISet.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISet.java index a8d731b03c..ada974b2c0 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISet.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISet.java @@ -142,11 +142,11 @@ default void notifyCleared(int idx) { * Copies the set in an array if integers * @return an array containing every integer of the set */ - default int[] toArray(){ + default int[] toArray() { int[] a = new int[size()]; int idx = 0; - ISetIterator iter = iterator(); - while(iter.hasNext()){ + ISetIterator iter = newIterator(); + while (iter.hasNext()) { a[idx++] = iter.nextInt(); } return a; diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISetIterator.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISetIterator.java index 8cc9898048..64d4e8c8db 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISetIterator.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/ISetIterator.java @@ -37,15 +37,6 @@ public interface ISetIterator extends Iterator { */ void reset(); - /** - * Inform the iterator that value item has been removed - * (may require to update iterator structure) - * @param item removed value - */ - default void notifyRemoving(int item){ - // nothing to do by default - } - /** * Returns the next int in the iteration. * diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/linkedlist/Set_LinkedList.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/linkedlist/Set_LinkedList.java index f7fbb45c9c..c222ce71a9 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/linkedlist/Set_LinkedList.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/linkedlist/Set_LinkedList.java @@ -10,6 +10,7 @@ package org.chocosolver.util.objects.setDataStructures.linkedlist; import org.chocosolver.util.objects.setDataStructures.AbstractSet; +import org.chocosolver.util.objects.setDataStructures.FixedIntArrayIterator; import org.chocosolver.util.objects.setDataStructures.ISetIterator; import org.chocosolver.util.objects.setDataStructures.SetType; @@ -29,10 +30,11 @@ public class Set_LinkedList extends AbstractSet { // VARIABLE //*********************************************************************************** - private IntCell first, last; + private IntCell first; + private IntCell last; private int size; private IntCell poolGC; - private final ISetIterator iter = newIterator(); + private final ListSetIterator iter = new ListSetIterator(); //*********************************************************************************** // METHODS @@ -46,8 +48,8 @@ public int size() { @Override public boolean contains(int element) { IntCell cell = first; - while(cell != null){ - if(cell.element == element){ + while (cell != null && cell != poolGC) { + if (cell.element == element) { return true; } cell = cell.next; @@ -57,20 +59,28 @@ public boolean contains(int element) { @Override public boolean add(int element) { - if(contains(element)){ + if (contains(element)) { return false; } + // add at end if (poolGC == null) { - first = new IntCell(element, first); + IntCell newCell = new IntCell(element, null); + if (last != null) { + last.next = newCell; + } + last = newCell; } else { IntCell recycled = poolGC; poolGC = poolGC.next; - recycled.init(element, first); - first = recycled; + recycled.init(element, null); + if (last != null) { + last.next = recycled; + } + last = recycled; } - if(last==null){ - assert size==0; - last=first; + if (first == null) { + assert size == 0; + first = last; } this.size++; notifyObservingElementAdded(element); @@ -79,23 +89,27 @@ public boolean add(int element) { @Override public boolean remove(int element) { - if(first == null){ + if (first == null) { return false; - } else if(first.element == element){ + } else if (first.element == element) { iter.notifyRemoving(element); first = first.next; - if(first==null)last=null; + if (first == null) { + last = null; + } size--; notifyObservingElementRemoved(element); return true; - }else { + } else { IntCell previous = first; IntCell current = first.next; while (current != null) { if (current.element == element) { iter.notifyRemoving(element); previous.next = current.next; - if(previous.next==null) last = previous; + if (previous.next == null) { + last = previous; + } current.next = poolGC; poolGC = current; size--; @@ -118,17 +132,17 @@ public void clear() { first = null; last = null; size = 0; - notifyObservingCleared(); + notifyObservingCleared(); } @Override public int min() { - if(isEmpty()) throw new IllegalStateException("cannot find minimum of an empty set"); + if (isEmpty()) throw new IllegalStateException("cannot find minimum of an empty set"); IntCell current = first; int min = current.element; - while(current.next!=null){ + while (current.next != null) { current = current.next; - if(min > current.element){ + if (min > current.element) { min = current.element; } } @@ -137,12 +151,12 @@ public int min() { @Override public int max() { - if(isEmpty()) throw new IllegalStateException("cannot find maximum of an empty set"); + if (isEmpty()) throw new IllegalStateException("cannot find maximum of an empty set"); IntCell current = first; int max = current.element; - while(current.next!=null){ + while (current.next != null) { current = current.next; - if(max < current.element){ + if (max < current.element) { max = current.element; } } @@ -150,54 +164,42 @@ public int max() { } @Override - public SetType getSetType(){ + public SetType getSetType() { return SetType.LINKED_LIST; } + @Override + public int[] toArray() { + int[] copy = new int[size]; + IntCell current = first; + int idx = 0; + while (current != null) { + copy[idx++] = current.element; + current = current.next; + } + return copy; + } + //*********************************************************************************** // ITERATOR //*********************************************************************************** @Override - public ISetIterator iterator(){ + public ISetIterator iterator() { iter.reset(); return iter; } @Override - public ISetIterator newIterator(){ - return new ISetIterator() { - int r =0; - private IntCell nextCell = first; - @Override - public void reset() { - nextCell = first; - r++; - } - @Override - public void notifyRemoving(int item) { - if(nextCell != null && nextCell.element == item){ - nextCell = nextCell.next; - } - } - @Override - public boolean hasNext() { - return nextCell != null; - } - @Override - public int nextInt() { - int e = nextCell.element; - nextCell = nextCell.next; - return e; - } - }; + public ISetIterator newIterator() { + return new FixedIntArrayIterator(toArray()); } //*********************************************************************************** // STRUCTURE //*********************************************************************************** - private class IntCell { + private static class IntCell { private int element; private IntCell next; @@ -212,8 +214,36 @@ public void init(int element, IntCell next) { } @Override - public String toString(){ - return element+""; + public String toString() { + return element + ""; + } + } + + private class ListSetIterator implements ISetIterator { + + private IntCell nextCell = first; + + @Override + public void reset() { + nextCell = first; + } + + public void notifyRemoving(int item) { + if (nextCell != null && nextCell.element == item) { + nextCell = nextCell.next; + } + } + + @Override + public boolean hasNext() { + return nextCell != null && size() > 0; + } + + @Override + public int nextInt() { + int e = nextCell.element; + nextCell = nextCell.next; + return e; } } } diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Std_Swap2.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Std_Swap2.java index 56728ad0d5..1114c89b52 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Std_Swap2.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Std_Swap2.java @@ -9,12 +9,8 @@ */ package org.chocosolver.util.objects.setDataStructures.swapList; -import gnu.trove.list.array.TIntArrayList; import org.chocosolver.memory.IEnvironment; import org.chocosolver.memory.IStateInt; -import org.chocosolver.util.objects.setDataStructures.AbstractSet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.objects.setDataStructures.SetType; /** * Set of integers based on BipartiteSet implementation for small sets (arraylist inside) @@ -22,16 +18,13 @@ * * @author : Charles Prud'homme, Jean-Guillaume FAGES (fix remove) */ -public class Set_Std_Swap2 extends AbstractSet { +public class Set_Std_Swap2 extends Set_Swap2 { //*********************************************************************************** // VARIABLES //*********************************************************************************** - protected IStateInt size; - protected TIntArrayList values; - private final ISetIterator iter = newIterator(); - + private final IStateInt size; //*********************************************************************************** // CONSTRUCTOR @@ -42,116 +35,26 @@ public class Set_Std_Swap2 extends AbstractSet { * @param e backtracking environment */ public Set_Std_Swap2(IEnvironment e){ + super(); size = e.makeInt(0); - values = new TIntArrayList(4); } //*********************************************************************************** // METHODS //*********************************************************************************** - @Override - public boolean add(int element) { - if(!contains(element)){ - int pos = size.add(1); - values.insert(pos - 1, element); - notifyObservingElementAdded(element); - return true; - } - return false; - } - - @Override - public boolean remove(int element) { - int pos = values.indexOf(element); - int s = size(); - if(pos > -1 && pos < s){ - iter.notifyRemoving(element); - s--; - int t = values.get(s); - values.set(pos, t); - values.set(s, element); - size.add(-1); - notifyObservingElementRemoved(element); - return true; - }else return false; - } - - @Override - public boolean contains(int element) { - int pos = values.indexOf(element); - return pos > -1 && pos < size(); - } - @Override public int size() { return size.get(); } @Override - public void clear() { - size.set(0); - notifyObservingCleared(); - } - - @Override - public int min() { - if(isEmpty()) throw new IllegalStateException("cannot find minimum of an empty set"); - int min = values.get(0); - for(int i = 1; i< size(); i++){ - if(min > values.get(i)){ - min = values.get(i); - } - } - return min; - } - - @Override - public int max() { - if(isEmpty()) throw new IllegalStateException("cannot find maximum of an empty set"); - int max = values.get(0); - for(int i = 1; i< size(); i++){ - if(max < values.get(i)){ - max = values.get(i); - } - } - return max; - } - - @Override - public SetType getSetType() { - return SetType.SMALLBIPARTITESET; - } - - @Override - public ISetIterator iterator(){ - iter.reset(); - return iter; + protected void setSize(int s) { + size.set(s); } @Override - public ISetIterator newIterator(){ - return new ISetIterator() { - private int idx; - @Override - public void reset() { - idx = 0; - } - @Override - public void notifyRemoving(int item) { - if(idx>0 && item == values.get(idx-1)){ - idx--; - } - } - @Override - public boolean hasNext() { - return idx < size(); - } - @Override - public int nextInt() { - idx ++; - return values.get(idx-1); - } - }; + protected void addSize(int delta) { + size.add(delta); } } diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap.java index b2107e08ad..ae765596ff 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap.java @@ -10,10 +10,9 @@ package org.chocosolver.util.objects.setDataStructures.swapList; import org.chocosolver.solver.exception.SolverException; -import org.chocosolver.util.objects.setDataStructures.AbstractSet; -import org.chocosolver.util.objects.setDataStructures.ISet; -import org.chocosolver.util.objects.setDataStructures.ISetIterator; -import org.chocosolver.util.objects.setDataStructures.SetType; +import org.chocosolver.util.objects.setDataStructures.*; + +import java.util.Arrays; /** * Bipartite set of integers: @@ -33,8 +32,9 @@ public class Set_Swap extends AbstractSet implements ISet.WithOffset { private int size; private final int mapOffset; - private int[] values, map; - private final ISetIterator iter = newIterator(); + private int[] values; + private int[] map; + private final MyISetIterator iter = new MyISetIterator(); //*********************************************************************************** // CONSTRUCTOR @@ -44,12 +44,12 @@ public class Set_Swap extends AbstractSet implements ISet.WithOffset { * Creates an empty bipartite set having numbers greater or equal than offSet (possibly < 0) * @param offSet smallest allowed value in this set (possibly < 0) */ - public Set_Swap(int offSet){ + public Set_Swap(int offSet) { mapOffset = offSet; size = 0; values = new int[16]; map = new int[16]; - for(int i=0;i 1) { int idx = map[element-mapOffset]; - int replacer = values[size - 1]; - map[replacer-mapOffset] = idx; - values[idx] = replacer; - map[element-mapOffset] = size - 1; - values[size - 1] = element; + swap(idx, size-1); + if (iter.idx < size()) { + if (idx == iter.idx - 1) { + iter.idx--; + } else if (idx < iter.idx - 1) { + swap(idx, iter.idx - 1); + iter.idx--; + } + } } addSize(-1); notifyObservingElementRemoved(element); return true; } + private void swap(int idx1, int idx2) { + int value1 = values[idx1]; + int value2 = values[idx2]; + map[value2 - mapOffset] = idx1; + values[idx1] = value2; + map[value1 - mapOffset] = idx2; + values[idx2] = value1; + } + @Override public boolean contains(int element) { - if(element= mapOffset+map.length){ + if (element < mapOffset || element >= mapOffset + map.length) { return false; } - return map[element-mapOffset] >= 0 && map[element-mapOffset] < size() && values[map[element-mapOffset]] == element; + return map[element - mapOffset] >= 0 && map[element - mapOffset] < size() && values[map[element - mapOffset]] == element; } @Override @@ -178,6 +190,13 @@ public SetType getSetType(){ return SetType.BIPARTITESET; } + @Override + public int[] toArray(){ + int[] valuesScreenshot = new int[size()]; + System.arraycopy(values,0,valuesScreenshot,0,size()); + return valuesScreenshot; + } + //*********************************************************************************** // ITERATOR //*********************************************************************************** @@ -189,28 +208,26 @@ public ISetIterator iterator(){ } @Override - public ISetIterator newIterator(){ - return new ISetIterator() { - private int idx; - @Override - public void reset() { - idx = 0; - } - @Override - public void notifyRemoving(int item) { - if(idx>0 && item == values[idx-1]){ - idx--; - } - } - @Override - public boolean hasNext() { - return idx < size(); - } - @Override - public int nextInt() { - idx ++; - return values[idx-1]; - } - }; + public ISetIterator newIterator() { + return new FixedIntArrayIterator(toArray()); + } + + private class MyISetIterator implements ISetIterator { + private int idx; + + @Override + public void reset() { + idx = 0; + } + + @Override + public boolean hasNext() { + return idx < size(); + } + + @Override + public int nextInt() { + return values[idx++]; + } } } diff --git a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap2.java b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap2.java index fe2d155351..3bee040a52 100644 --- a/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap2.java +++ b/solver/src/main/java/org/chocosolver/util/objects/setDataStructures/swapList/Set_Swap2.java @@ -11,6 +11,7 @@ import gnu.trove.list.array.TIntArrayList; import org.chocosolver.util.objects.setDataStructures.AbstractSet; +import org.chocosolver.util.objects.setDataStructures.FixedIntArrayIterator; import org.chocosolver.util.objects.setDataStructures.ISetIterator; import org.chocosolver.util.objects.setDataStructures.SetType; @@ -26,10 +27,9 @@ public class Set_Swap2 extends AbstractSet { // VARIABLES //*********************************************************************************** - protected int size; - protected TIntArrayList values; - private final ISetIterator iter = newIterator(); - + private int size; + protected final TIntArrayList values; + protected final Swap2SetIterator iter = new Swap2SetIterator(); //*********************************************************************************** // CONSTRUCTOR @@ -50,8 +50,8 @@ public Set_Swap2(){ @Override public boolean add(int element) { if(!contains(element)){ - values.insert(size, element); - size++; + values.insert(size(), element); + addSize(1); notifyObservingElementAdded(element); return true; } @@ -60,18 +60,32 @@ public boolean add(int element) { @Override public boolean remove(int element) { - int pos = values.indexOf(element); - int s = size(); - if(pos > -1 && pos < s){ - iter.notifyRemoving(element); - s--; - int t = values.get(s); - values.set(pos, t); - values.set(s, element); - size--; - notifyObservingElementRemoved(element); - return true; - }else return false; + int idx = values.indexOf(element); + if (idx <= -1 || idx >= size()) { + return false; + } + int size = size(); + if (size > 1) { + swap(idx, size-1); + if (iter.idx < size()) { + if (idx == iter.idx - 1) { + iter.idx--; + } else if (idx < iter.idx - 1) { + swap(idx, iter.idx - 1); + iter.idx--; + } + } + } + addSize(-1); + notifyObservingElementRemoved(element); + return true; + } + + private void swap(int idx1, int idx2) { + int value1 = values.get(idx1); + int value2 = values.get(idx2); + values.set(idx1, value2); + values.set(idx2, value1); } @Override @@ -85,9 +99,17 @@ public int size() { return size; } + protected void setSize(int s) { + size = s; + } + + protected void addSize(int delta) { + size += delta; + } + @Override public void clear() { - size = 0; + setSize(0); notifyObservingCleared(); } @@ -120,6 +142,15 @@ public SetType getSetType() { return SetType.SMALLBIPARTITESET; } + @Override + public int[] toArray(){ + return values.toArray(0, size()); + } + + //*********************************************************************************** + // ITERATOR + //*********************************************************************************** + @Override public ISetIterator iterator(){ iter.reset(); @@ -127,28 +158,27 @@ public ISetIterator iterator(){ } @Override - public ISetIterator newIterator(){ - return new ISetIterator() { - private int idx; - @Override - public void reset() { - idx = 0; - } - @Override - public void notifyRemoving(int item) { - if(idx>0 && item == values.get(idx-1)){ - idx--; - } - } - @Override - public boolean hasNext() { - return idx < size(); - } - @Override - public int nextInt() { - idx ++; - return values.get(idx-1); - } - }; + public ISetIterator newIterator() { + return new FixedIntArrayIterator(toArray()); + } + + private class Swap2SetIterator implements ISetIterator { + + private int idx; + + @Override + public void reset() { + idx = 0; + } + + @Override + public boolean hasNext() { + return idx < size(); + } + + @Override + public int nextInt() { + return values.get(idx++); + } } } diff --git a/solver/src/main/java/org/chocosolver/util/tools/PreProcessing.java b/solver/src/main/java/org/chocosolver/util/tools/PreProcessing.java index e2f158e441..ab4bdb74fd 100644 --- a/solver/src/main/java/org/chocosolver/util/tools/PreProcessing.java +++ b/solver/src/main/java/org/chocosolver/util/tools/PreProcessing.java @@ -10,17 +10,30 @@ package org.chocosolver.util.tools; import gnu.trove.map.hash.TIntIntHashMap; +import org.chocosolver.solver.Cause; import org.chocosolver.solver.Model; import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.constraints.ConstraintsName; +import org.chocosolver.solver.constraints.Propagator; import org.chocosolver.solver.constraints.binary.PropEqualX_Y; +import org.chocosolver.solver.constraints.binary.PropNotEqualX_Y; +import org.chocosolver.solver.constraints.nary.alldifferent.AllDifferent; +import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.exception.SolverException; import org.chocosolver.solver.search.SearchState; import org.chocosolver.solver.variables.IntVar; +import org.chocosolver.util.iterators.DisposableValueIterator; import org.chocosolver.util.objects.graphs.UndirectedGraph; import org.chocosolver.util.objects.setDataStructures.SetType; +import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; +import org.jgrapht.Graph; +import org.jgrapht.alg.clique.BronKerboschCliqueFinder; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.SimpleGraph; import java.util.*; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * This class contains various methods for applying pre-processing rules to a model. @@ -37,6 +50,7 @@ public class PreProcessing { private static final Predicate ARITHM = c -> c.getName().equals(ConstraintsName.ARITHM); private static final Predicate POSTED = c -> c.getStatus() == Constraint.Status.POSTED; private static final Predicate EQXY = c -> c.getPropagator(0) instanceof PropEqualX_Y; + private static final Predicate NEQXY = c -> c.getPropagator(0) instanceof PropNotEqualX_Y; /** @@ -72,16 +86,19 @@ public static List> detectIntEqualities(Model model) { for (IntVar i : ivars) { id2pos.put(i.getId(), pos++); } - // 3. create a undirected graph to store equality constraints + // 3. create an undirected graph to store equality constraints UndirectedGraph g = new UndirectedGraph(pos, SetType.LINKED_LIST, false); + int[] nbEqs = {0, 0}; Arrays.stream(model.getCstrs()) .filter(ARITHM.and(POSTED).and(EQXY)) - .map(c -> c.getPropagator(0)) - .forEach(p -> { + .forEach(c -> { + Propagator p = c.getPropagator(0); g.addNode(id2pos.get(p.getVar(0).getId())); g.addNode(id2pos.get(p.getVar(1).getId())); g.addEdge(id2pos.get(p.getVar(0).getId()), id2pos.get(p.getVar(1).getId())); + model.unpost(c); + nbEqs[0]++; }); // 4. Prepare graph DFS for all unlabelled nodes List> components = new ArrayList<>(); @@ -94,22 +111,92 @@ public static List> detectIntEqualities(Model model) { components.add(component); } } - // 5. Remove all equality constraints - model.unpost(Arrays.stream(model.getCstrs()) - .filter(ARITHM.and(POSTED).and(EQXY)) - .toArray(Constraint[]::new)); - // 6 Post new equality constraints + // 5 Post new equality constraints + int nbAllEqs = 0; for (List component : components) { if (component.size() > 2) { model.allEqual(component.toArray(new IntVar[0])).post(); + nbAllEqs++; + nbEqs[1] += component.size(); } else if (component.size() == 2) { component.get(0).eq(component.get(1)).post(); } } - // 7. return the list of strongly connected component, to better design search strategy. + model.getSolver().log().white() + .printf("%d 'eq' constraint%s found\n> Replace %d of them with %d 'allEqual' constraint%s\n", + nbEqs[0], nbEqs[0]>1?"s":"", nbEqs[1], nbAllEqs, nbAllEqs>1?"s":""); + // 6. return the list of strongly connected component, to better design search strategy. return components; } + /** + * Preprocess the model to detect integer (and boolean) equality constraint + * and turn them into bigger arity constraint when possible. + *

      + * If the resolution has already started ({@code model.getSolver().getSearchState() != SearchState.NEW}), + * the method stops without applying any pre-processing. + * + * @param model the model to pre-process + * @implNote The model's constraints are iterated in order to filter constraint with the + * following properties: + *

    • + *
        ConstraintsName.ARITHM
      + *
        Constraint.Status.POSTED
      + *
        instanceof PropEqualX_Y
      + *
    • + * For each them, add two nodes and an edge in a {@link UndirectedGraph}. + * Then, look for strongly connected components with a DFS and labelling operations. + * Once found, old constraints are removed and new ones are posted instead. + */ + public static void detectIntInequalities(Model model) { + // 1. if the search started, skip the call + if (model.getSolver().getSearchState() != SearchState.NEW) { + return; + } + // 2. get all integer variables and a map to their position in the array + IntVar[] ivars = model.retrieveIntVars(true); + int pos = 0; + TIntIntHashMap id2pos = new TIntIntHashMap(); + for (IntVar i : ivars) { + id2pos.put(i.getId(), pos++); + } + Graph cliques = new SimpleGraph<>(DefaultEdge.class); + // 3. create an undirected graph to store equality constraints + int[] nbNqs = {0, 0}; + Arrays.stream(model.getCstrs()) + .filter(ARITHM.and(POSTED).and(NEQXY)) + .forEach(c -> { + Propagator p = c.getPropagator(0); + cliques.addVertex(id2pos.get(p.getVar(0).getId())); + cliques.addVertex(id2pos.get(p.getVar(1).getId())); + cliques.addEdge(id2pos.get(p.getVar(0).getId()), id2pos.get(p.getVar(1).getId())); + model.unpost(c); + nbNqs[0]++; + }); + // 5. Post new equality constraints + BronKerboschCliqueFinder max = new BronKerboschCliqueFinder<>(cliques); + int nbAlldiff = 0; + for (Set cl : max) { + Integer[] clique = cl.toArray(new Integer[0]); + if (clique.length == 1) + continue; + IntVar[] diff = Arrays.stream(clique) + .mapToInt(i -> i) + .mapToObj(i -> ivars[i]) + .toArray(IntVar[]::new); + model.allDifferent(diff).post(); + nbAlldiff++; + nbNqs[1] += diff.length; + } + model.getSolver().log().white() + .printf("%d 'neq' constraint%s found\n> Replace %d of them with %d 'allDifferent' constraint%s\n", + nbNqs[0], nbNqs[0] > 1 ? "s" : "", nbNqs[1], nbAlldiff, nbAlldiff > 1 ? "s" : ""); + if (nbAlldiff > 0){ + impliedConstraints(model); + } + } + + /** * Depth-first search of {@code g} to get strongly connected component from a node {@code v}. * Stores the elements of the component in {@code component}. @@ -133,4 +220,228 @@ private static void scc(int v, BitSet visited, UndirectedGraph g, } } } + + /** + * This method adds constraints implied by AllDifferent constraints. + * For each of them, it finds two assignments of the variables + * using the minimum cost maximum flow formulation and a sum constraint on both sides. + * + * @param model the model to pre-process + */ + public static void impliedConstraints(Model model) { + // Min Cost Max Flow formulation + List cstrs = Arrays.stream(model.getCstrs()) + .filter(c -> c instanceof AllDifferent) + .map(c -> (AllDifferent) c) + .collect(Collectors.toList()); + IntIterableRangeSet values = new IntIterableRangeSet(); + int nbImplied = 0; + for (AllDifferent c : cstrs) { + values.clear(); + //noinspection unchecked + IntVar[] vars = ((Propagator) c.getPropagator(0)).getVars(); + for (IntVar v : vars) { + values.addAll(v); + } + int n = c.getPropagator(0).getNbVars(); + int lb = values.stream().limit(n).sum(); + int ub = values.stream().skip(values.size() - n).sum(); + model.sum(vars, ">=", lb).post(); + model.sum(vars, "<=", ub).post(); + nbImplied += 2; // two constraints posted + } + model.getSolver().log().white() + .printf("%d 'allDifferent' constraint%s found\n> Add %d implied constraints\n", + cstrs.size(), cstrs.size() > 1 ? "s" : "", nbImplied); + } + + + /** + * This method is called after the initial propagation and before the search loop starts. + * It sequentially applies Singleton Arc Consistency on every combination of (variable, value). + * If a combination leads to inconsistency, the value is removed from the domain of the variable. + * The method ends when the time limit is reached or when all combination have been checked. + * + * @implSpec A first propagation must have been done before calling this method. + */ + public static void sac(Model m, long timeLimitInMS) { + if (!m.getSolver().getEngine().isInitialized()) { + try { + m.getSolver().propagate(); + } catch (ContradictionException e) { + throw new SolverException("Preprocessing failed"); + + } + } + if (timeLimitInMS > 0 && m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Running SAC step (%dms).\n", timeLimitInMS); + } + long tl = System.currentTimeMillis() + timeLimitInMS; + IntVar[] ivars = m.retrieveIntVars(true); + Arrays.sort(ivars, (v, w) -> { + int d = v.getNbProps() - w.getNbProps(); + if (d == 0) { + d = w.getDomainSize() - v.getDomainSize(); + if (d == 0) { + d = v.getId() - w.getId(); + } + } + return d; + }); + long before = Arrays.stream(ivars) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + boolean hasChanged = true; + loop: + while (hasChanged) { + hasChanged = false; + for (int i = 0; i < ivars.length; i++) { + IntVar v = ivars[i]; + if (!v.isInstantiated()) { // if the variable is not instantiated + DisposableValueIterator it = v.getValueIterator(true); + while (it.hasNext()) { + if (timeLimitInMS > 0 && System.currentTimeMillis() > tl) { + break loop; + } + int a = it.next(); + m.getSolver().pushTrail(); + boolean noSupport = hasNoSupport(m, v, a); + m.getSolver().cancelTrail(); + if (noSupport) { + try { + hasChanged |= v.removeValue(a, Cause.Null); + m.getSolver().getEngine().propagate(); + /*if (m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Preprocessing removed value %d from %s\n", a, v.getName()); + }*/ + } catch (ContradictionException e) { + throw new SolverException("Preprocessing failed"); + } + } + } + it.dispose(); + } + } + } + long after = Arrays.stream(ivars) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + if (before - after > 0 && m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Preprocessing reduces the search space by approximately %.2f%%\n", (before - after) * 100. / before); + } + } + + /** + * It sequentially applies Singleton Arc Consistency on bounds of each integer variable. + * If a value leads to inconsistency, it is removed from the domain of the variable. + * The method ends when the time limit is reached or when all combination have been checked. + * + * @implSpec A first propagation must have been done before calling this method. + */ + public static void sacBound(Model m, long timeLimitInMS) { + if (!m.getSolver().getEngine().isInitialized()) { + try { + m.getSolver().propagate(); + } catch (ContradictionException e) { + throw new SolverException("Preprocessing failed"); + + } + } + if (timeLimitInMS > 0 && m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Running SACBound step (%dms).\n", timeLimitInMS); + } + long tl = System.currentTimeMillis() + timeLimitInMS; + IntVar[] ivars = m.retrieveIntVars(true); + long before = Arrays.stream(ivars) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Arrays.sort(ivars, (v, w) -> { + int d = v.getNbProps() - w.getNbProps(); + if (d == 0) { + d = w.getDomainSize() - v.getDomainSize(); + if (d == 0) { + d = v.getId() - w.getId(); + } + } + return d; + }); + boolean hasChanged = true; + loop: + while (hasChanged) { + hasChanged = false; + for (int i = 0; i < ivars.length; i++) { + IntVar v = ivars[i]; + int lb = v.getLB(); + int ub = v.getUB(); + int val = lb; + while (val <= ub) { + if (System.currentTimeMillis() > tl) { + break loop; + } + m.getSolver().pushTrail(); + boolean noSupport = hasNoSupport(m, v, val); + m.getSolver().cancelTrail(); + if (noSupport) { + try { + // try to permanently remove the value + hasChanged |= v.removeValue(val, Cause.Null); + m.getSolver().getEngine().propagate(); + /*if (m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Preprocessing removed value %d from %s\n", val, v.getName()); + }*/ + } catch (ContradictionException e) { + throw new SolverException("Preprocessing failed"); + } + } else break; + val = v.getLB(); + } + while (val >= lb) { + if (System.currentTimeMillis() > tl) { + break loop; + } + m.getSolver().pushTrail(); + boolean noSupport = hasNoSupport(m, v, val); + m.getSolver().cancelTrail(); + if (noSupport) { + try { + // try to permanently remove the value + hasChanged |= v.removeValue(val, Cause.Null); + m.getSolver().getEngine().propagate(); + if (m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Preprocessing removed value %d from %s\n", val, v.getName()); + } + } catch (ContradictionException e) { + throw new SolverException("Preprocessing failed"); + } + } else break; + val = v.getUB(); + } + } + } + long after = Arrays.stream(ivars) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + if (before - after > 0 && m.getSettings().warnUser()) { + m.getSolver().log().white().printf("Preprocessing reduces the search space by approximately %.2f%%\n", (before - after) * 100. / before); + } + } + + /** + * Check if a value is supported by the variable. + * + * @param m the model + * @param var the variable + * @param val the value to check + * @return true if the value is not supported by the variable, false otherwise + */ + private static boolean hasNoSupport(Model m, IntVar var, int val) { + try { + var.instantiateTo(val, Cause.Null); + m.getSolver().getEngine().propagate(); + return false; + } catch (ContradictionException e) { + m.getSolver().getEngine().flush(); + return true; + } + } } diff --git a/solver/src/main/java/org/chocosolver/util/tools/VariableUtils.java b/solver/src/main/java/org/chocosolver/util/tools/VariableUtils.java index f0791b07ce..abc121620d 100644 --- a/solver/src/main/java/org/chocosolver/util/tools/VariableUtils.java +++ b/solver/src/main/java/org/chocosolver/util/tools/VariableUtils.java @@ -338,6 +338,20 @@ public static long domainCardinality(IntVar... vars) { return Math.min(Integer.MAX_VALUE, card); } + /** + * Check if all variables in the array have an enumerated domain. + * @param vars array of variables + * @return true if all variables have an enumerated domain, false otherwise + */ + public static boolean allEnumerated(IntVar[] vars) { + for (IntVar var : vars) { + if (!var.hasEnumeratedDomain()) { + return false; + } + } + return true; + } + /** * Compute the search space size * diff --git a/solver/src/test/java/org/chocosolver/solver/PortFolioTest.java b/solver/src/test/java/org/chocosolver/solver/PortFolioTest.java index 88c0df8739..e92a5f5c84 100644 --- a/solver/src/test/java/org/chocosolver/solver/PortFolioTest.java +++ b/solver/src/test/java/org/chocosolver/solver/PortFolioTest.java @@ -152,11 +152,11 @@ public void testP21() { @Test(groups = "10s", timeOut = 300000) public void testP3() { for (int iter = 0; iter < 50; iter++) { - ParallelPortfolio pares = new ParallelPortfolio(false); + ParallelPortfolio pares = new ParallelPortfolio(); for (int i = 0; i < 20; i++) { Model m = knapsack(); m.getSolver().setSearch(activityBasedSearch(m.retrieveIntVars(true))); - pares.addModel(m); + pares.addModel(m, true, true); } while (pares.solve()) ; Model finder = pares.getBestModel(); @@ -184,11 +184,11 @@ public void testP3bug() { @Test(groups = "10s", timeOut = 300000) public void testP3bug2() { for (int iter = 0; iter < 50; iter++) { - ParallelPortfolio pares = new ParallelPortfolio(false); + ParallelPortfolio pares = new ParallelPortfolio(); for (int i = 0; i < 20; i++) { Model m = knapsack(); m.getSolver().setSearch(inputOrderLBSearch(m.retrieveIntVars(true))); - pares.addModel(m); + pares.addModel(m, true, true); } while (pares.solve()) ; Model finder = pares.getBestModel(); @@ -232,11 +232,11 @@ public void testP4() { @Test(groups = "10s", timeOut = 300000) public void testP5() { for (int iter = 0; iter < 50; iter++) { - ParallelPortfolio pares = new ParallelPortfolio(false); + ParallelPortfolio pares = new ParallelPortfolio(); for (int i = 0; i < 10; i++) { Model m = knapsack(); m.getSolver().setSearch(randomSearch(m.retrieveIntVars(true), iter)); - pares.addModel(m); + pares.addModel(m, true, true); } while (pares.solve()) ; Model finder = pares.getBestModel(); @@ -248,14 +248,14 @@ public void testP5() { @Test(groups = "10s", timeOut = 300000) public void testP6() { for (int iter = 0; iter < 50; iter++) { - ParallelPortfolio pares = new ParallelPortfolio(true); + ParallelPortfolio pares = new ParallelPortfolio(); for (int i = 0; i < 10; i++) { Model m = knapsack(); m.setName("Model_" + i); if (i < 5) { m.getObjective().asIntVar().gt(50 + i).post(); // for test purpose } - pares.addModel(m, i >= 5); + pares.addModel(m, false, i >= 5); } while (pares.solve()) ; Model finder = pares.getBestModel(); @@ -267,7 +267,7 @@ public void testP6() { @Test(groups = "10s", timeOut = 300000) public void testP7() { for (int iter = 0; iter < 50; iter++) { - ParallelPortfolio pares = new ParallelPortfolio(false); + ParallelPortfolio pares = new ParallelPortfolio(); for (int i = 0; i < 10; i++) { Model m = knapsack(); m.setName("Model_" + i); @@ -278,7 +278,7 @@ public void testP7() { } else { m.setName("Model_" + i); } - pares.addModel(m, i >= 5); + pares.addModel(m, false, i >= 5); } while (pares.solve()) ; Model finder = pares.getBestModel(); diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/binary/SquareTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/binary/SquareTest.java index 272c97acb2..0c25e1b928 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/binary/SquareTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/binary/SquareTest.java @@ -63,8 +63,8 @@ public void testBigEnumBound() { solver.setSearch(Search.randomSearch(ArrayUtils.append(x,x2), 0)); while (solver.solve()) ; assertEquals(solver.getSolutionCount(), 184320); - assertEquals(solver.getFailCount(), 11417); - assertEquals(solver.getNodeCount(), 380056); + assertEquals(solver.getFailCount(), 11383); + assertEquals(solver.getNodeCount(), 380022); } @Test(groups = "10s", timeOut = 60000) diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/checker/DomainBuilder.java b/solver/src/test/java/org/chocosolver/solver/constraints/checker/DomainBuilder.java index 6dd5358998..ea5986c90c 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/checker/DomainBuilder.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/checker/DomainBuilder.java @@ -14,6 +14,7 @@ import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.SetVar; +import org.chocosolver.solver.variables.Task; import java.util.Arrays; import java.util.Random; @@ -199,4 +200,32 @@ public static BoolVar makeBoolVar(Model model, Random rnd, int pos) { return var; } + public static Task makeTask(Model model, Random rnd, int pos) { + String name = "T_" + pos; + int v = rnd.nextInt(5); + switch (v) { + case 0: // fixed duration and end as a view + IntVar start0 = makeIntVar(model, rnd, pos); + int duration0 = 1 + rnd.nextInt(10); + return new Task(start0, duration0); + case 1: // fixed duration + IntVar start1 = makeIntVar(model, rnd, pos); + int duration1 = 1 + rnd.nextInt(10); + IntVar end1 = model.intVar(name + ".end", start1.getLB() + duration1, start1.getUB() + duration1); + return new Task(start1, duration1, end1); + default: // all variables + int[] times = new int[]{ + MIN_LOW_BND + rnd.nextInt(MAX_DOM_SIZE), + MIN_LOW_BND + rnd.nextInt(MAX_DOM_SIZE), + MIN_LOW_BND + rnd.nextInt(MAX_DOM_SIZE), + MIN_LOW_BND + rnd.nextInt(MAX_DOM_SIZE) + }; + Arrays.sort(times); + IntVar start2 = model.intVar(name + ".start", times[0], times[1]); + IntVar duration2 = model.intVar(name + ".duration", times[2] - times[1], times[3] - times[0]); + IntVar end2 = model.intVar(name + ".end", times[2], times[3]); + return new Task(start2, duration2, end2); + } + } + } diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/checker/Modeler.java b/solver/src/test/java/org/chocosolver/solver/constraints/checker/Modeler.java index 0091201a15..40d54920d2 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/checker/Modeler.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/checker/Modeler.java @@ -670,7 +670,7 @@ public Model model(int n, int[][] domains, THashMap map, Object p h[i] = vars[i + 3 * k]; } IntVar capa = vars[vars.length - 1]; - model.cumulative(tasks, h, capa, (boolean)parameters).post(); + model.cumulative(tasks, h, capa).post(); model.getSolver().setSearch(randomSearch(vars, 0)); return model; } @@ -898,26 +898,26 @@ public String name() { } }; - Modeler modelplusac = new Modeler() { - @Override - public Model model(int n, int[][] domains, THashMap map, Object parameters) { - Model s = new Model("plus" + n); - IntVar[] vars = new IntVar[n]; - for (int i = 0; i < vars.length; i++) { - vars[i] = s.intVar("X_" + i, domains[i]); - if (map != null) map.put(domains[i], vars[i]); - } - - s.arithm(vars[0], "+", vars[1], "=", vars[2]).post(); - s.getSolver().setSearch(randomSearch(vars, 0)); - return s; - } - - @Override - public String name() { - return "X+Y=Z (ac)"; - } - }; +// Modeler modelplusac = new Modeler() { +// @Override +// public Model model(int n, int[][] domains, THashMap map, Object parameters) { +// Model s = new Model("plus" + n); +// IntVar[] vars = new IntVar[n]; +// for (int i = 0; i < vars.length; i++) { +// vars[i] = s.intVar("X_" + i, domains[i]); +// if (map != null) map.put(domains[i], vars[i]); +// } +// +// s.arithm(vars[0], "+", vars[1], "=", vars[2]).post(); +// s.getSolver().setSearch(randomSearch(vars, 0)); +// return s; +// } +// +// @Override +// public String name() { +// return "X+Y=Z (ac)"; +// } +// }; Modeler modelmodulobc = new Modeler() { @Override diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/checker/consistency/TestConsistency.java b/solver/src/test/java/org/chocosolver/solver/constraints/checker/consistency/TestConsistency.java index 70ed201ed8..2473dd4338 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/checker/consistency/TestConsistency.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/checker/consistency/TestConsistency.java @@ -273,13 +273,13 @@ public void testPLUSBC() { } } - @Test(groups="checker", timeOut=60000) - public void testPLUSAC() { - long seed = System.currentTimeMillis(); - for (int i = 0; i < 99; i++) { - checkConsistency(Modeler.modelplusac, 3, -8, 8, null, seed + i, "ac"); - } - } +// @Test(groups="checker", timeOut=60000) +// public void testPLUSAC() { +// long seed = System.currentTimeMillis(); +// for (int i = 0; i < 99; i++) { +// checkConsistency(Modeler.modelplusac, 3, -8, 8, null, seed + i, "ac"); +// } +// } // MODULO ******************************************************* diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/checker/correctness/TestCorrectness.java b/solver/src/test/java/org/chocosolver/solver/constraints/checker/correctness/TestCorrectness.java index 26004df1bd..aa9e4c7bd9 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/checker/correctness/TestCorrectness.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/checker/correctness/TestCorrectness.java @@ -391,15 +391,15 @@ public void testPLUSBC() { } } - @Test(groups="checker", timeOut=60000) - public void testPLUSAC() { - for (int i = 0; i < 4; i++) { - long seed = System.currentTimeMillis(); - for (int n = 2; n < (1 << 7) + 1; n *= 2) { - CorrectnessChecker.checkCorrectness(Modeler.modelplusac, 3, -n, 2 * n, seed, false); - } - } - } +// @Test(groups="checker", timeOut=60000) +// public void testPLUSAC() { +// for (int i = 0; i < 4; i++) { +// long seed = System.currentTimeMillis(); +// for (int n = 2; n < (1 << 7) + 1; n *= 2) { +// CorrectnessChecker.checkCorrectness(Modeler.modelplusac, 3, -n, 2 * n, seed, false); +// } +// } +// } @Test(groups = "checker", timeOut = 60000) public void testMODBC() { diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/checker/explanation/TestExplanation.java b/solver/src/test/java/org/chocosolver/solver/constraints/checker/explanation/TestExplanation.java index f8b53787f8..5a9a3c6ef4 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/checker/explanation/TestExplanation.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/checker/explanation/TestExplanation.java @@ -18,6 +18,7 @@ import org.chocosolver.solver.constraints.extension.Tuples; import org.chocosolver.solver.constraints.extension.TuplesFactory; import org.chocosolver.solver.constraints.nary.PropIntValuePrecedeChain; +import org.chocosolver.solver.constraints.nary.alldifferent.AllDifferent; import org.chocosolver.solver.constraints.nary.alldifferent.PropAllDiffAC; import org.chocosolver.solver.constraints.nary.alldifferent.PropAllDiffBC; import org.chocosolver.solver.constraints.nary.alldifferent.PropAllDiffInst; @@ -29,10 +30,7 @@ import org.chocosolver.solver.constraints.nary.min_max.PropMax; import org.chocosolver.solver.constraints.nary.min_max.PropMin; import org.chocosolver.solver.constraints.nary.sum.*; -import org.chocosolver.solver.constraints.reification.PropXeqYHalfReif; -import org.chocosolver.solver.constraints.reification.PropXinSHalfReif; -import org.chocosolver.solver.constraints.reification.PropXleYHalfReif; -import org.chocosolver.solver.constraints.reification.PropXneYHalfReif; +import org.chocosolver.solver.constraints.reification.*; import org.chocosolver.solver.constraints.ternary.*; import org.chocosolver.solver.constraints.unary.*; import org.chocosolver.solver.search.SearchState; @@ -42,7 +40,7 @@ import org.chocosolver.solver.trace.IMessage; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.solver.variables.SetVar; +import org.chocosolver.solver.variables.Task; import org.chocosolver.solver.variables.Variable; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; import org.chocosolver.util.tools.VariableUtils; @@ -127,16 +125,21 @@ public Object[][] getPropagators() { {PropXeqYHalfReif.class, new Class[]{IntVar.class, IntVar.class, BoolVar.class}, new Object[]{null, null, null}}, {PropXneYHalfReif.class, new Class[]{IntVar.class, IntVar.class, BoolVar.class}, new Object[]{null, null, null}}, {PropXinSHalfReif.class, new Class[]{IntVar.class, IntIterableRangeSet.class, BoolVar.class}, new Object[]{null, null, null}}, - {PropXplusYeqZ.class, new Class[]{IntVar.class, IntVar.class, IntVar.class}, new Object[]{null, null, null}}, {PropAllDiffInst.class, new Class[]{IntVar[].class}, new Object[]{6}}, {PropAllDiffBC.class, new Class[]{IntVar[].class}, new Object[]{6}}, - {PropAllDiffAC.class, new Class[]{IntVar[].class, boolean.class}, new Object[]{6, false}}, + {PropAllDiffAC.class, new Class[]{IntVar[].class, AllDifferent.Consistency.class}, new Object[]{6, AllDifferent.Consistency.AC_REGIN}}, + {PropAllDiffAC.class, new Class[]{IntVar[].class, AllDifferent.Consistency.class}, new Object[]{6, AllDifferent.Consistency.AC_TUNED}}, + {PropAllDiffAC.class, new Class[]{IntVar[].class, AllDifferent.Consistency.class}, new Object[]{6, AllDifferent.Consistency.AC_CLASSIC}}, + {PropAllDiffAC.class, new Class[]{IntVar[].class, AllDifferent.Consistency.class}, new Object[]{6, AllDifferent.Consistency.AC_COMPLEMENT}}, + {PropAllDiffAC.class, new Class[]{IntVar[].class, AllDifferent.Consistency.class}, new Object[]{6, AllDifferent.Consistency.AC_PARTIAL}}, {PropLex.class, new Class[]{IntVar[].class, IntVar[].class, boolean.class}, new Object[]{5, 5, null}}, {PropElementV_fast.class, new Class[]{IntVar.class, IntVar[].class, IntVar.class}, new Object[]{null, 6, null}}, {PropEnumDomainChanneling.class, new Class[]{BoolVar[].class, IntVar.class, int.class}, new Object[]{5, null, null}}, {PropInverseChannelBC.class, new Class[]{IntVar[].class, IntVar[].class, int.class, int.class}, new Object[]{6, 6, 1, 1}}, {PropInverseChannelAC.class, new Class[]{IntVar[].class, IntVar[].class, int.class, int.class}, new Object[]{6, 6, 1, 1}}, {PropIntValuePrecedeChain.class, new Class[]{IntVar[].class, int.class, int.class}, new Object[]{6, 1, 3}}, + {PropXeqCHalfReif.class, new Class[]{IntVar.class, int.class, BoolVar.class}, new Object[]{null, null, null}}, + {PropAbsoluteLight.class, new Class[]{IntVar.class, IntVar.class}, new Object[]{null}}, //{PropCardinality.class, new Class[]{SetVar.class, IntVar.class}, new Object[]{null, null}} }; return Providers.merge(objs, getSeeds()); @@ -146,6 +149,7 @@ public Object[][] getPropagators() { public Object[][] getConstraints() { Object[][] objs = new Object[][]{ {"allDifferent", new Class[]{IntVar[].class}, new Object[]{6}}, + {"allDifferentExcept0", new Class[]{IntVar[].class}, new Object[]{6}}, {"allEqual", new Class[]{IntVar[].class}, new Object[]{6}}, {"allEqual", new Class[]{IntVar[].class}, new Object[]{6}}, {"among", new Class[]{IntVar.class, IntVar[].class, int[].class}, new Object[]{null, 6, 6}}, @@ -161,6 +165,7 @@ public Object[][] getConstraints() { {"circuit", new Class[]{IntVar[].class, int.class}, new Object[]{6, null}}, {"count", new Class[]{IntVar.class, IntVar[].class, IntVar.class}, new Object[]{null, 6, null}}, {"count", new Class[]{int.class, IntVar[].class, IntVar.class}, new Object[]{null, 6, null}}, + {"cumulative", new Class[]{Task[].class, IntVar[].class, IntVar.class, boolean.class, boolean.class}, new Object[]{6, 6, null, null, null}}, {"decreasing", new Class[]{IntVar[].class, int.class}, new Object[]{6, null}}, {"div", new Class[]{IntVar.class, IntVar.class, IntVar.class}, new Object[]{null, null, null}}, {"increasing", new Class[]{IntVar[].class, int.class}, new Object[]{6, null}}, @@ -186,7 +191,6 @@ public Object[][] getConstraints() { {"times", new Class[]{IntVar.class, int.class, IntVar.class}, new Object[]{null, null, null}}, //not supported {"allDifferentExcept0", new Class[]{IntVar[].class}, new Object[]{6}}, - //not supported {"cumulative", new Class[]{IntVar[].class, int[].class, int[].class, int.class}, new Object[]{6, 6, 6, null}}, //not supported {"diffN", new Class[]{IntVar[].class, IntVar[].class, IntVar[].class, IntVar[].class, boolean.class}, new Object[]{4, 4, 4, 4, null}}, //not supported {"knapsack", new Class[]{IntVar[].class, IntVar.class, IntVar.class, int[].class, int[].class}, new Object[]{4, null, null, 4, 4}}, // ignored {"clausesIntChanneling", new Class[]{IntVar.class, BoolVar[].class, BoolVar[].class}, new Object[]{null, 6, 6}}, @@ -230,6 +234,14 @@ public Object[][] getIntConstraints() { info[i] = 5; classes[i] = "IntVar[].class"; objects[i] = String.valueOf(6); + } else if (Task.class.isAssignableFrom(parameterType)) { + info[i] = 5; + classes[i] = "Task.class"; + objects[i] = null; + } else if (Task[].class.isAssignableFrom(parameterType)) { + info[i] = 5; + classes[i] = "Task[].class"; + objects[i] = String.valueOf(6); } else if (int.class.isAssignableFrom(parameterType)) { info[i] = null; classes[i] = "int.class"; @@ -441,6 +453,15 @@ private static void buildInputParameters(Class[] parameterTypes, Object[] variables.add(_variables[j]); } parameters[i] = _variables; + } else if (Task[].class == parameterType) { + Task[] _tasks = new Task[(int) info[i]]; + for (int j = 0; j < _tasks.length; j++) { + _tasks[j] = DomainBuilder.makeTask(model, rnd, variables.size()); + variables.add(_tasks[j].getStart()); + variables.add(_tasks[j].getDuration()); + variables.add(_tasks[j].getEnd()); + } + parameters[i] = _tasks; } else if (int[].class == parameterType) { if (info[i] instanceof Integer) { int[] _constants = new int[(int) info[i]]; @@ -467,6 +488,12 @@ private static void buildInputParameters(Class[] parameterTypes, Object[] IntVar _variable = DomainBuilder.makeIntVar(model, rnd, variables.size()); variables.add(_variable); parameters[i] = _variable; + } else if (Task.class == parameterType) { + Task _task = DomainBuilder.makeTask(model, rnd, variables.size()); + variables.add(_task.getStart()); + variables.add(_task.getDuration()); + variables.add(_task.getEnd()); + parameters[i] = _task; } else if (Operator.class == parameterType) { parameters[i] = Operator.get((String) info[i]); } else if (IntIterableRangeSet.class == parameterType) { @@ -477,6 +504,11 @@ private static void buildInputParameters(Class[] parameterTypes, Object[] } else { parameters[i] = info[i]; } + }else { + if (info[i] == null) { + throw new UnsupportedOperationException("Unknown value for parameter "+ parameterType); + } + parameters[i] = info[i]; } } i++; diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/graph/connectivity/ReifiedConnectivityTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/graph/connectivity/ReifiedConnectivityTest.java new file mode 100644 index 0000000000..312f915d3d --- /dev/null +++ b/solver/src/test/java/org/chocosolver/solver/constraints/graph/connectivity/ReifiedConnectivityTest.java @@ -0,0 +1,820 @@ +package org.chocosolver.solver.constraints.graph.connectivity; + +import org.chocosolver.solver.Model; +import org.chocosolver.solver.Providers; +import org.chocosolver.solver.Solver; +import org.chocosolver.solver.constraints.Constraint; +import org.chocosolver.solver.expression.discrete.relational.ReExpression; +import org.chocosolver.solver.variables.BoolVar; +import org.chocosolver.solver.variables.UndirectedGraphVar; +import org.chocosolver.util.objects.graphs.UndirectedGraph; +import org.chocosolver.util.objects.setDataStructures.SetType; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.*; + +public class ReifiedConnectivityTest { + + @DataProvider + public Object[][] params() { + return Providers.merge(new Object[][]{ + {SetType.BIPARTITESET}, + {SetType.SMALLBIPARTITESET}, + {SetType.LINKED_LIST}, + {SetType.BITSET}, + {SetType.RANGESET} + }, new Object[][]{ + {2, 828, 367}, + {3, 5895, 2582}, + {4, 21179, 12384}, + {5, 38201, 40704}, + {6, 29592, 92768}, + {7, 4752, 144896} + }); + } + + + @Test(groups="10s", timeOut=120_000, dataProvider = "params") + public void testReifiedConnectivity(SetType setType, int k, int f, int nf) { + buildModelAndSolve(k, setType, f, nf); + } + + /** + * builds the model exactly as the Scala version does and enumerates all solutions + */ + private static void buildModelAndSolve(int k, SetType setType, int nbfree, int nbtif) { + Model model = new Model(); + + /* ------------------------------------------------------------------ * + * 1 – Transactions (boolean variables) * + * ------------------------------------------------------------------ */ + String[] transactions = { + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0", + "ioServer_SPI", + "spi_CA", + "spi_CB" + }; + + Map transactionVar = new HashMap<>(); + for (String id : transactions) { + transactionVar.put(id, model.boolVar(id)); + } + + /* ------------------------------------------------------------------ * + * 2 – Exclusive transaction groups (C¹) * + * ------------------------------------------------------------------ */ + Map exclusiveTr = new HashMap<>(); + exclusiveTr.put("KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0" + }); + exclusiveTr.put("KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0" + }); + exclusiveTr.put("KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0" + }); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "spi_CB", + new String[]{"ioServer_SPI", "spi_CA"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0"}); + exclusiveTr.put( + "ioServer_SPI", + new String[]{"spi_CA", "spi_CB"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_isram_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_CorePac2_isram_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "spi_CA", + new String[]{"ioServer_SPI", "spi_CB"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_isram_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_CorePac1_dsram_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_dsram_store_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_CorePac1_isram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_isram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_isram_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + new String[]{ + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + new String[]{ + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + new String[]{ + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_CorePac3_isram_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_isram_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0"}); + exclusiveTr.put( + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + new String[]{ + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_isram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_CorePac0_dsram_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_isram_store_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_CorePac0_dsram_store_0" + }); + + // C¹ – exclusive constraints + for (Map.Entry e : exclusiveTr.entrySet()) { + String tr = e.getKey(); + String[] ex = e.getValue(); + ReExpression acc = model.boolVar(true); + if (ex.length > 0) { + BoolVar[] notEx = Arrays.stream(ex) + .map(t2 -> transactionVar.get(t2).not()) + .toArray(BoolVar[]::new); + for (BoolVar b : notEx) { + acc = acc.and(b); + } + transactionVar.get(tr).imp(acc).post(); + } + } + + // C² – cardinality constraint + model.sum(transactionVar.values().toArray(new BoolVar[0]), "=", k).post(); + + /* ------------------------------------------------------------------ * + * 3 – Graph definition (nodes, edges, upper/lower bound) * + * ------------------------------------------------------------------ */ + // ---- nodes -------------------------------------------------------- + String[] nodeNames = { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + }; + Map nodes = new HashMap<>(); + for (int i = 0; i < nodeNames.length; i++) { + nodes.put(nodeNames[i], i); + } + + // ---- edges -------------------------------------------------------- + String[][] edges = { + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""} + }; + + // Upper bound graph (contains all possible nodes/edges) + UndirectedGraph UB = new UndirectedGraph(model, nodeNames.length, setType, setType, false); + for (int i = 0; i < nodeNames.length; i++) { + UB.addNode(i); + } + for (String[] e : edges) { + UB.addEdge(nodes.get(e[0]), nodes.get(e[1])); + } + + // Lower bound graph (empty graph) + UndirectedGraph LB = new UndirectedGraph(model, nodeNames.length, setType, setType, false); + + // Graph variable + UndirectedGraphVar g = model.graphVar("q", LB, UB); + + // ---- node channeling ------------------------------------------------ + Map nodeVars = new HashMap<>(); + for (String n : nodeNames) { + nodeVars.put(n, model.boolVar(n)); + } + // ordering must follow the index order used in the graph + BoolVar[] nodeVarArray = Arrays.stream(nodeNames) + .sorted(Comparator.comparingInt(nodes::get)) + .map(nodeVars::get) + .toArray(BoolVar[]::new); + model.nodesChanneling(g, nodeVarArray).post(); + + // ---- edge channeling ------------------------------------------------ + Map edgeVars = new HashMap<>(); + for (String[] e : edges) { + String key = e[0] + "--" + e[1]; + BoolVar ev = model.boolVar(key); + edgeVars.put(key, ev); + model.edgeChanneling(g, ev, nodes.get(e[0]), nodes.get(e[1])).post(); + } + + /* ------------------------------------------------------------------ * + * 4 – Association node ↔ transactions (nodeToTr) * + * ------------------------------------------------------------------ */ + Map nodeToTr = new HashMap<>(); + nodeToTr.put("", new String[]{ + "spi_CA", "ioServer_SPI", "spi_CB", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_EDMARegister_store_0" + }); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_EDMARegister_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "spi_CA", "ioServer_SPI", "spi_CB", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0" + }); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "spi_CA", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "spi_CB", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0" + }); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0"}); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_CorePac3_dsp_store_KeystoneWithRosace_CorePac3_dsram_store_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac3_dsram_load_0"}); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0"}); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac2_dsram_load_0", + "KeystoneWithRosace_CorePac2_dsp_store_KeystoneWithRosace_CorePac2_dsram_store_0"}); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_DDR_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_DDR_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_DDR_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_DDR_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_DDR_Bank0_load_0"}); + nodeToTr.put("", new String[]{ + "ioServer_SPI", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_CorePac4_dsram_load_0", + "KeystoneWithRosace_CorePac4_dsp_store_KeystoneWithRosace_CorePac4_dsram_store_0"}); + nodeToTr.put("", new String[]{ + "spi_CA", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac0_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_CorePac0_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM0_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0", + "KeystoneWithRosace_ARMPac_ARM1_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM0_core_store_KeystoneWithRosace_MSMC_SRAM_Bank0_store_0", + "KeystoneWithRosace_ARMPac_ARM1_core_load_KeystoneWithRosace_MSMC_SRAM_Bank0_load_0"}); + nodeToTr.put("", new String[]{ + "KeystoneWithRosace_ARMPac_ARM2_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM2_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_ARMPac_ARM3_core_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0", + "KeystoneWithRosace_ARMPac_ARM3_core_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "spi_CB", + "KeystoneWithRosace_CorePac2_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac3_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_MSMC_SRAM_Bank1_load_0", + "KeystoneWithRosace_CorePac1_dsp_store_KeystoneWithRosace_MSMC_SRAM_Bank1_store_0"}); + nodeToTr.put("", new String[]{ + "spi_CA", + "ioServer_SPI", + "spi_CB", + "KeystoneWithRosace_CorePac4_dsp_load_KeystoneWithRosace_SPI_load_0"}); + + // ---- transaction → nodes (inverse of nodeToTr) ---------------------- + Map trToNode = new HashMap<>(); + for (String tr : transactions) { + List list = new ArrayList<>(); + for (Map.Entry e : nodeToTr.entrySet()) { + if (Arrays.asList(e.getValue()).contains(tr)) { + list.add(e.getKey()); + } + } + trToNode.put(tr, list.toArray(new String[0])); + } + + /* ------------------------------------------------------------------ * + * 5 – Constraints on nodes (C_Node) * + * ------------------------------------------------------------------ */ + for (Map.Entry e : nodeToTr.entrySet()) { + String nodeId = e.getKey(); + String[] trList = e.getValue(); + BoolVar[] trBools = Arrays.stream(trList) + .map(transactionVar::get) + .toArray(BoolVar[]::new); + // node ↔ (sum of related transactions ≥ 2) + ReExpression sumGe2 = model.sum("sum", trBools).ge(2); + nodeVars.get(nodeId).eq(sumGe2).post(); + } + + /* ------------------------------------------------------------------ * + * 6 – Constraints on edges (C_Edge) * + * ------------------------------------------------------------------ */ + // compute, for each edge, the set of transactions that use both its ends + Map edgeToTr = new HashMap<>(); + for (String[] e : edges) { + Set left = new HashSet<>(Arrays.asList(nodeToTr.get(e[0]))); + Set right = new HashSet<>(Arrays.asList(nodeToTr.get(e[1]))); + left.retainAll(right); // common transactions + BoolVar[] vars = left.stream() + .map(transactionVar::get) + .toArray(BoolVar[]::new); + edgeToTr.put(e[0] + "--" + e[1], vars); + } + + for (String[] e : edges) { + String key = e[0] + "--" + e[1]; + BoolVar ev = edgeVars.get(key); + BoolVar leftNode = nodeVars.get(e[0]); + BoolVar rightNode = nodeVars.get(e[1]); + BoolVar[] commonTr = edgeToTr.get(key); + ReExpression trOr = model.boolVar(false); + for (BoolVar tr : commonTr) { + trOr = trOr.or(tr); + } + // edge ↔ (leftNode ∧ rightNode ∧ trOr) + ReExpression edgeDef = leftNode.and(rightNode, trOr); + ev.eq(edgeDef).post(); + } + + /* ------------------------------------------------------------------ * + * 7 – Connectivity & global constraints * + * ------------------------------------------------------------------ */ + // connectivity + Constraint isConnected = model.connected(g); + // non‑empty graph + ReExpression isNonEmpty = model.boolVar(false); + for (BoolVar n : nodeVars.values()) { + isNonEmpty = isNonEmpty.or(n); + } + // empty graph + ReExpression isEmpty = isNonEmpty.not(); + + // transaction contribution: a transaction must activate at least one of its nodes + List trContrib = new ArrayList<>(); + for (Map.Entry e : trToNode.entrySet()) { + BoolVar trVar = transactionVar.get(e.getKey()); + String[] nodesArr = e.getValue(); + if (nodesArr.length == 0) { + trContrib.add(trVar.eq(model.boolVar(false))); + } else { + ReExpression accNode = model.boolVar(false); + BoolVar[] nodeBools = Arrays.stream(nodesArr) + .map(nodeVars::get) + .toArray(BoolVar[]::new); + for (BoolVar n : nodeBools) { + accNode = accNode.or(n); + } + trContrib.add(trVar.imp(accNode)); + } + } + ReExpression trContribCst = trContrib.get(0); + for (int i = 1; i < trContrib.size(); i++) { + trContribCst = trContribCst.and(trContrib.get(i)); + } + + // ITF (connected, non‑empty, and transaction contribution satisfied) + BoolVar isITF = model.boolVar("isITF"); + ReExpression itfDef = isConnected.reify() + .and(isNonEmpty, trContribCst); + isITF.eq(itfDef).post(); + + // Free (empty graph) + BoolVar isFree = model.boolVar("isFree"); + isFree.eq(isEmpty).post(); + + // exactly one of (Free, ITF) holds + isFree.xor(isITF).post(); + + /* ------------------------------------------------------------------ * + * 8 – Solve & enumerate * + * ------------------------------------------------------------------ */ + Solver solver = model.getSolver(); + int free = 0; + int itf = 0; + + while (solver.solve()) { + if(isFree.getValue() == 1) + free++; + else + itf++; + } + //System.out.println("free: " + free +" itf: "+ itf); + Assert.assertEquals(free, nbfree); + Assert.assertEquals(itf, nbtif); + } +} \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/AllDifferentTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/AllDifferentTest.java index 116db037d9..1cb8733ed6 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/AllDifferentTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/AllDifferentTest.java @@ -9,12 +9,6 @@ */ package org.chocosolver.solver.constraints.nary; -import static org.chocosolver.solver.constraints.checker.DomainBuilder.buildFullDomains; -import static org.chocosolver.solver.search.strategy.Search.inputOrderLBSearch; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import java.util.Random; import org.chocosolver.solver.Cause; import org.chocosolver.solver.Model; import org.chocosolver.solver.Solver; @@ -22,6 +16,13 @@ import org.chocosolver.solver.variables.IntVar; import org.testng.annotations.Test; +import java.util.Random; + +import static org.chocosolver.solver.constraints.checker.DomainBuilder.buildFullDomains; +import static org.chocosolver.solver.search.strategy.Search.inputOrderLBSearch; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + /** *
      * @@ -256,7 +257,7 @@ public void testB() { while (model.getSolver().solve()) ; assertEquals(model.getSolver().getSolutionCount(), 10); - assertEquals(model.getSolver().getNodeCount(), 23); + assertEquals(model.getSolver().getNodeCount(), 24); } @Test(groups="1s", timeOut=60000) diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/CumulativeTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/CumulativeTest.java index 5dc23db739..146b70d7a9 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/CumulativeTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/CumulativeTest.java @@ -9,18 +9,17 @@ */ package org.chocosolver.solver.constraints.nary; -import org.chocosolver.solver.Model; -import org.chocosolver.solver.Providers; -import org.chocosolver.solver.Settings; -import org.chocosolver.solver.Solver; -import org.chocosolver.solver.constraints.nary.cumulative.Cumulative; +import org.chocosolver.solver.*; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.solver.variables.Task; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.stream.IntStream; import static org.chocosolver.solver.search.strategy.Search.lastConflict; @@ -29,7 +28,7 @@ /** * Tests the various filtering algorithms of the cumulative constraint * - * @author Thierry Petit, Jean-Guillaume Fages + * @author Thierry Petit, Jean-Guillaume Fages, Arthur GODET */ public class CumulativeTest { @@ -66,7 +65,7 @@ public void testDur0() { m.intVar(8, 14) ); - m.cumulative(new Task[]{t1, t2}, new IntVar[]{m.intVar(1), m.intVar(1)}, m.intVar(1), true, Cumulative.Filter.TIME.make(2)).post(); + m.cumulative(new Task[]{t1, t2}, new IntVar[]{m.intVar(1), m.intVar(1)}, m.intVar(1)).post(); Solver s = m.getSolver(); @@ -132,30 +131,13 @@ public void testMed() { } public void test(int n, int capamax, int dmin, int hmax, long seed, int mode) { - if (VERBOSE) + if (VERBOSE) { System.out.println(n + " - " + capamax + " - " + dmin + " - " + hmax + " - " + seed + " - " + mode); - Cumulative.Filter[][] filters = new Cumulative.Filter[][]{ - {Cumulative.Filter.TIME}, - {Cumulative.Filter.TIME, Cumulative.Filter.NRJ}, - {Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP}, - {Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP_HEI_SORT}, - {Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP, Cumulative.Filter.NRJ}, - {Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP_HEI_SORT, Cumulative.Filter.NRJ}, - {Cumulative.Filter.TIME, Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP, Cumulative.Filter.NRJ}, - {Cumulative.Filter.TIME, Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP_HEI_SORT, Cumulative.Filter.NRJ}, - {Cumulative.Filter.TIME, Cumulative.Filter.HEIGHTS, Cumulative.Filter.SWEEP, Cumulative.Filter.SWEEP_HEI_SORT, Cumulative.Filter.NRJ} - }; - long ref = solve(n, capamax, dmin, hmax, seed, true, mode); - if (ref == -1) return; - for (boolean g : new boolean[]{true, false}) // graph-based - for (int f = 1; f < filters.length; f++) { - long val = solve(n, capamax, dmin, hmax, seed, g, mode); - assert ref == val || val == -1 : "filter " + f + " failed (can be due to the heuristic in case of timeout)"; - } + } + long ref = solve(n, capamax, dmin, hmax, seed, mode); } - public static long solve(int n, int capamax, int dmin, int hmax, long seed, - boolean graph, int mode) { + public static long solve(int n, int capamax, int dmin, int hmax, long seed, int mode) { final Model model = new Model(); int dmax = 5 + dmin * 2; final IntVar[] s = model.intVarArray("s", n, 0, n * dmax, false); @@ -169,7 +151,7 @@ public static long solve(int n, int capamax, int dmin, int hmax, long seed, t[i] = new Task(s[i], d[i], e[i]); model.arithm(e[i], "<=", last).post(); } - model.cumulative(t, h, capa, graph).post(); + model.cumulative(t, h, capa).post(); Solver r = model.getSolver(); r.setSearch(lastConflict(randomSearch(model.retrieveIntVars(false), seed))); model.getSolver().limitTime(5000); @@ -241,4 +223,462 @@ public void testGCCAT(boolean lcg) { Assert.assertEquals(solver.getSolutionCount(), 8); } -} + + @Test(groups = "1s", timeOut = 60000, dataProvider = "provideFilterHeight") + public void testFilterHeight(Integer hMax) { + final Model model = new Model(); + final int[][] s = new int[][]{{0, 0}, {10, 10}, {0, 3}, {6, 8}, {3, 6}}; + final int[][] d = new int[][]{{5, 5}, {10, 10}, {3, 6}, {5, 7}, {3, 6}}; + final int[][] e = new int[][]{{5, 5}, {20, 20}, {4, 20}, {1, 20}, {1, 20}}; + final int[][] h = new int[][]{{hMax, hMax}, {hMax, hMax}, {0, 1}, {0, 1}, {0, 1}}; + + Task[] tasks = IntStream.range(0, 5) + .mapToObj(i -> new Task( + model.intVar("s" + i, s[i][0], s[i][1]), + model.intVar("d" + i, d[i][0], d[i][1]), + model.intVar("e" + i, e[i][0], e[i][1]))) + .toArray(Task[]::new); + + IntVar[] height = IntStream.range(0, 5) + .mapToObj(i -> model.intVar("h" + i, h[i][0], h[i][1])) + .toArray(IntVar[]::new); + + model.cumulative(tasks, height, model.intVar(hMax)).post(); + + try { + model.getSolver().propagate(); + } catch (ContradictionException ex) { + Assert.fail(); + } + Assert.assertEquals(height[2].getUB(), 0); + Assert.assertEquals(height[3].getUB(), 0); + Assert.assertEquals(height[4].getLB(), 0); + Assert.assertEquals(height[4].getUB(), 1); + } + + @DataProvider(name = "provideFilterHeight") + private Object[][] provideFilterHeight() { + return new Integer[][]{{1}, {2}}; + } + + @DataProvider(name = "provideRcpsp") + private Object[][] provideRcpsp() { + return new Object[][]{j3010_1(), j3010_4(), j3017_5()}; + } + + private Object[] j3010_1() { + int optimalMakespan = 42; + int[] resourcesCapacity = new int[]{24, 23, 25, 33}; + int[] durations = new int[]{0, 2, 5, 6, 4, 2, 9, 9, 4, 8, 7, 10, 1, 1, 1, 5, 2, 5, 5, 9, 10, 1, 5, 4, 10, 5, 9, 10, 2, 10, 3, 0}; + int[][] resourcesConsumption = new int[][]{ + {0, 0, 0, 0}, + {1, 2, 4, 0}, + {0, 5, 9, 10}, + {8, 10, 10, 0}, + {8, 3, 0, 8}, + {3, 1, 8, 5}, + {5, 0, 5, 10}, + {4, 0, 0, 3}, + {8, 0, 8, 10}, + {0, 0, 0, 5}, + {1, 6, 6, 6}, + {2, 10, 3, 8}, + {10, 8, 2, 8}, + {1, 3, 6, 1}, + {4, 0, 9, 9}, + {0, 9, 6, 9}, + {4, 3, 9, 10}, + {0, 10, 9, 0}, + {2, 7, 9, 0}, + {0, 5, 4, 1}, + {0, 8, 0, 8}, + {9, 0, 0, 6}, + {4, 8, 3, 8}, + {5, 7, 3, 5}, + {9, 0, 0, 1}, + {1, 8, 0, 7}, + {7, 3, 4, 7}, + {7, 0, 0, 3}, + {1, 3, 0, 10}, + {4, 0, 3, 7}, + {0, 4, 5, 1}, + {0, 0, 0, 0} + }; + int[][] successors = new int[][]{ + {1, 2, 3}, + {9, 10, 27}, + {8, 14, 15}, + {4, 5, 6}, + {7}, + {17, 26}, + {13, 17}, + {11, 12, 18}, + {9}, + {21}, + {24}, + {29}, + {19}, + {30}, + {20, 23}, + {16, 26, 29}, + {17}, + {20}, + {28}, + {22}, + {25}, + {22}, + {25}, + {24}, + {25, 26, 29}, + {28}, + {30}, + {30}, + {31}, + {31}, + {31}, + {} + }; + return new Object[]{resourcesCapacity, durations, resourcesConsumption, successors, optimalMakespan}; + } + + private Object[] j3010_4() { + int optimalMakespan = 58; + int[] resourcesCapacity = new int[]{20, 19, 23, 23}; + int[] durations = new int[]{0, 9, 5, 9, 10, 8, 1, 10, 6, 3, 9, 10, 4, 2, 4, 10, 9, 9, 10, 1, 7, 9, 5, 5, 3, 8, 4, 2, 1, 7, 8, 0}; + int[][] resourcesConsumption = new int[][]{ + {0, 0, 0, 0}, + {3, 7, 0, 10}, + {0, 2, 10, 6}, + {7, 5, 7, 0}, + {4, 6, 10, 8}, + {8, 4, 7, 8}, + {9, 7, 1, 3}, + {3, 0, 2, 4}, + {4, 0, 3, 8}, + {0, 7, 4, 0}, + {4, 0, 4, 8}, + {2, 0, 5, 2}, + {6, 0, 5, 3}, + {4, 10, 9, 5}, + {0, 0, 4, 7}, + {3, 6, 4, 0}, + {6, 9, 0, 7}, + {5, 2, 3, 4}, + {0, 9, 0, 0}, + {5, 4, 4, 2}, + {7, 0, 6, 4}, + {5, 5, 2, 1}, + {4, 0, 9, 9}, + {5, 0, 0, 7}, + {0, 8, 0, 3}, + {7, 10, 9, 0}, + {0, 7, 8, 10}, + {3, 0, 1, 0}, + {0, 9, 2, 7}, + {2, 2, 3, 8}, + {0, 7, 2, 5}, + {0, 0, 0, 0} + }; + int[][] successors = new int[][]{ + {1, 2, 3}, + {4, 9, 10}, + {6, 12, 17}, + {5, 8, 15}, + {14}, + {7, 11, 16}, + {21}, + {13}, + {21}, + {13, 28, 30}, + {20}, + {29}, + {15, 24}, + {24}, + {18}, + {19, 25}, + {22}, + {21}, + {20, 22}, + {23}, + {27}, + {24, 28}, + {29}, + {27, 30}, + {25}, + {26}, + {27}, + {29}, + {31}, + {31}, + {31}, + {} + }; + return new Object[]{resourcesCapacity, durations, resourcesConsumption, successors, optimalMakespan}; + } + + private Object[] j3017_5() { + int optimalMakespan = 47; + int[] resourcesCapacity = new int[]{9, 11, 9, 14}; + int[] durations = new int[]{0, 10, 3, 1, 1, 7, 10, 4, 7, 2, 4, 2, 1, 4, 7, 9, 10, 8, 5, 10, 9, 2, 3, 6, 9, 2, 9, 4, 7, 2, 1, 0}; + int[][] resourcesConsumption = new int[][]{ + {0, 0, 0, 0}, + {2, 0, 0, 0}, + {8, 0, 0, 0}, + {0, 5, 0, 0}, + {0, 0, 6, 0}, + {5, 0, 0, 0}, + {0, 6, 0, 0}, + {0, 0, 6, 0}, + {0, 3, 0, 0}, + {0, 0, 0, 3}, + {0, 0, 7, 0}, + {0, 5, 0, 0}, + {0, 3, 0, 0}, + {0, 0, 8, 0}, + {0, 0, 7, 0}, + {2, 0, 0, 0}, + {1, 0, 0, 0}, + {0, 7, 0, 0}, + {0, 9, 0, 0}, + {0, 5, 0, 0}, + {7, 0, 0, 0}, + {0, 0, 0, 2}, + {0, 8, 0, 0}, + {0, 0, 0, 9}, + {5, 0, 0, 0}, + {0, 0, 0, 9}, + {4, 0, 0, 0}, + {0, 10, 0, 0}, + {0, 0, 0, 10}, + {0, 0, 0, 4}, + {0, 0, 6, 0}, + {0, 0, 0, 0} + }; + int[][] successors = new int[][]{ + {1, 2, 3}, + {16, 19, 20}, + {8, 9, 12}, + {4, 7, 11}, + {5, 6, 13}, + {10, 18}, + {9, 27}, + {8, 17, 24}, + {15, 16, 19}, + {10, 24}, + {14, 22}, + {23}, + {17, 23, 25}, + {14, 15, 30}, + {25, 28}, + {28}, + {21}, + {18}, + {26}, + {22}, + {24}, + {22, 27, 28}, + {23, 25}, + {29}, + {30}, + {29}, + {27, 29}, + {30}, + {31}, + {31}, + {31}, + {} + }; + return new Object[]{resourcesCapacity, durations, resourcesConsumption, successors, optimalMakespan}; + } + + @Test(groups = "1s", timeOut = 60000, dataProvider = "provideRcpsp") + public void testRcpsp( + int[] resourcesCapacity, + int[] durations, + int[][] resourcesConsumption, + int[][] successors, + int optimalMakespan + ) { + int size = durations.length; + Model model = new Model(); + Task[] tasks = new Task[size]; + IntVar[] starts = new IntVar[size]; + int hor = Arrays.stream(durations).sum(); + for (int k = 0; k < tasks.length; k++) { + int d = durations[k]; + starts[k] = model.intVar("start[" + k + "]", 0, hor - d); + tasks[k] = new Task(starts[k], d); + } + + // precedence relations + for (int k = 0; k < tasks.length; k++) { + for (int s : successors[k]) { + model.arithm(tasks[k].getEnd(), "<=", tasks[s].getStart()).post(); + } + } + + // resource management + List taskVars = new ArrayList<>(); + List heightVars = new ArrayList<>(); + for (int i = 0; i < resourcesCapacity.length; i++) { + taskVars.clear(); + heightVars.clear(); + for (int k = 0; k < tasks.length; k++) { + int resourceConsumption = resourcesConsumption[k][i]; + if (resourceConsumption > 0) { + IntVar height = model.intVar(resourceConsumption); + heightVars.add(height); + taskVars.add(tasks[k]); + } + } + if (!taskVars.isEmpty()) { + model.cumulative( + taskVars.toArray(new Task[0]), + heightVars.toArray(new IntVar[0]), + model.intVar(resourcesCapacity[i]) + ).post(); + } + } + + // declare makespan + IntVar makespan = model.intVar("makespan", 0, hor); + model.max(makespan, Arrays.stream(tasks).map(Task::getEnd).toArray(IntVar[]::new)).post(); + model.setObjective(false, makespan); + + while (model.getSolver().solve()); + Assert.assertEquals(model.getSolver().getBestSolutionValue(), optimalMakespan); + } + + @DataProvider(name = "provideJssp") + private Object[][] provideJssp() { + return new Object[][]{ft06()}; + } + + private Object[] ft06() { + int optimalMakespan = 55; + int[][] durations = new int[][]{ + {1, 3, 6, 7, 3, 6}, + {8, 5, 10, 10, 10, 4}, + {5, 4, 8, 9, 1, 7}, + {5, 5, 5, 3, 8, 9}, + {9, 3, 5, 4, 3, 1}, + {3, 3, 9, 10, 4, 1} + }; + int[][] idMachines = new int[][]{ + {2, 0, 1, 3, 5, 4}, + {1, 2, 4, 5, 0, 3}, + {2, 3, 5, 0, 1, 4}, + {1, 0, 2, 3, 4, 5}, + {2, 1, 4, 5, 0, 3}, + {1, 3, 5, 0, 4, 2} + }; + return new Object[]{durations, idMachines, 6, optimalMakespan}; + } + + @Test(groups = "1s", timeOut = 60000, dataProvider = "provideJssp") + public void testJssp( + int[][] durations, + int[][] idMachines, + int nbMachines, + int optimalMakespan + ) { + int size = durations.length; + Model model = new Model(); + Task[] tasks = new Task[durations.length * nbMachines]; + Task[][] tasksPerJob = new Task[size][]; + int hor = Arrays.stream(durations).flatMapToInt(Arrays::stream).sum(); + int idxTask = 0; + for (int i = 0; i < durations.length; i++) { + tasksPerJob[i] = new Task[durations[i].length]; + for (int j = 0; j < durations[i].length; j++) { + final int duration = durations[i][j]; + IntVar start = model.intVar("start[" + i + "][" + j + "]", 0, hor - duration); + tasks[idxTask] = new Task(start, duration); + tasksPerJob[i][j] = tasks[idxTask]; + idxTask++; + } + } + + // precedence relations + for (int i = 0; i < tasksPerJob.length; i++) { + for (int j = 0; j + 1 < tasksPerJob[i].length; j++) { + model.arithm(tasksPerJob[i][j].getEnd(), "<=", tasksPerJob[i][j + 1].getStart()).post(); + } + } + + // resource management + List taskVars = new ArrayList<>(); + List heightVars = new ArrayList<>(); + for (int k = 0; k < nbMachines; k++) { + taskVars.clear(); + heightVars.clear(); + for (int i = 0; i < idMachines.length; i++) { + for (int j = 0; j < idMachines[i].length; j++) { + if (idMachines[i][j] == k) { + taskVars.add(tasksPerJob[i][j]); + heightVars.add(model.intVar(1)); + } + } + } + if (!taskVars.isEmpty()) { + model.cumulative( + taskVars.toArray(new Task[0]), + heightVars.toArray(new IntVar[0]), + model.intVar(1) + ).post(); + } + } + + // declare makespan + IntVar makespan = model.intVar("makespan", 0, hor); + model.max(makespan, Arrays.stream(tasks).map(Task::getEnd).toArray(IntVar[]::new)).post(); + model.setObjective(false, makespan); + + while (model.getSolver().solve()); + Assert.assertEquals(model.getSolver().getBestSolutionValue(), optimalMakespan); + } + + @Test(groups = "1s", timeOut = 60000) + public void testFilteringHeightsAndCapacity() { + Model model = new Model(); + IntVar capacity = model.intVar("capacity", 2, 4); + + Task optTask = model.taskVar(model.intVar("optTask.start", 0, 100), 10, true); + IntVar height = model.intVar("height", 5, 7); + Task task = model.taskVar(model.intVar("task.start", 0, 100), 0); + + Task[] tasks = new Task[]{ + model.taskVar(model.intVar("start[0]", 0, 100), 10, true), + model.taskVar(model.intVar("start[1]", 0, 100), 10), + model.taskVar(model.intVar("start[2]", 0, 100), 10) + }; + IntVar[] heights = new IntVar[]{ + model.intVar("height[0]", 5, 7), + model.intVar("height[1]", 3, 5), + model.intVar("height[2]", 1, 7) + }; + + model.cumulative(new Task[]{optTask}, new IntVar[]{height}, capacity).post(); + model.cumulative(new Task[]{task}, new IntVar[]{height}, capacity).post(); + model.cumulative(tasks, heights, capacity).post(); + + try { + model.getSolver().propagate(); + } catch (ContradictionException ex) { + Assert.fail(); + } + Assert.assertFalse(optTask.mayBePerformed()); + Assert.assertEquals(height.getLB(), 5); + Assert.assertEquals(height.getUB(), 7); + + Assert.assertFalse(tasks[0].mayBePerformed()); + Assert.assertEquals(heights[0].getLB(), 5); + Assert.assertEquals(heights[0].getUB(), 7); + Assert.assertEquals(heights[1].getLB(), 3); + Assert.assertEquals(heights[1].getUB(), 4); + Assert.assertEquals(heights[2].getLB(), 1); + Assert.assertEquals(heights[2].getUB(), 4); + Assert.assertEquals(capacity.getLB(), 3); + Assert.assertEquals(capacity.getUB(), 4); + } +} \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/NValueTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/NValueTest.java index db3720ffc9..49cf9f1573 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/NValueTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/NValueTest.java @@ -53,6 +53,7 @@ public void testAtLeast() { } }); while (model.getSolver().solve()) ; + Assert.assertEquals(model.getSolver().getSolutionCount(), 114); } @Test(groups = "1s", timeOut = 60000) @@ -80,6 +81,7 @@ public void testAtMost() { } }); while (model.getSolver().solve()) ; + Assert.assertEquals(model.getSolver().getSolutionCount(), 126); } @@ -143,6 +145,7 @@ public void testNValuesOrderSearch() { model.nValues(x, n).post(); model.getSolver().setSearch(Search.inputOrderLBSearch(ArrayUtils.append(new IntVar[]{n}, x))); while (model.getSolver().solve()) ; + Assert.assertEquals(model.getSolver().getSolutionCount(), 161051); Model model2 = new Model(); IntVar[] x2 = model2.intVarArray("x2", nVars, 0, domainSize, false); @@ -150,15 +153,14 @@ public void testNValuesOrderSearch() { model2.nValues(x2, n2).post(); model2.getSolver().setSearch(Search.inputOrderLBSearch(ArrayUtils.concat(x2, n2))); while (model2.getSolver().solve()) ; + Assert.assertEquals(model2.getSolver().getSolutionCount(), 161051); Model model3 = new Model(); IntVar[] x3 = model3.intVarArray("x3", nVars, 0, domainSize, false); IntVar n3 = model3.intVar("n3", 1, nVars, true); model3.nValues(x3, n3).post(); while (model3.getSolver().solve()) ; - - Assert.assertEquals(model.getSolver().getSolutionCount(), model2.getSolver().getSolutionCount()); - Assert.assertEquals(model.getSolver().getSolutionCount(), model3.getSolver().getSolutionCount()); + Assert.assertEquals(model3.getSolver().getSolutionCount(), 161051); } @Test(groups = "10s", timeOut = 60000) diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/ScalarTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/ScalarTest.java index 670579581e..ac76582212 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/ScalarTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/ScalarTest.java @@ -11,6 +11,7 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.Settings; +import org.chocosolver.solver.Solver; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.search.strategy.Search; import org.chocosolver.solver.variables.IntVar; @@ -190,4 +191,59 @@ public void testBigCoeffs3() throws ContradictionException { } } -} + @Test(groups = "1s") + public void testWithLongs() { + // 1. Create a model + Model model = new Model("Simple Example"); + int w = 16; + int n = 2; + + // 2. variables + int[] sign_pow2 = new int[(w + 1) * 2]; + for (int i = 0; i < w + 1; i++) { + sign_pow2[i] = -(1 << i); + sign_pow2[i + w + 1] = (1 << i); + } + IntVar[][] y = new IntVar[n][2]; + IntVar[][] z = new IntVar[n][2]; + IntVar[] c = new IntVar[n + 1]; + IntVar[][] b = new IntVar[n][2]; + IntVar[][] ind = new IntVar[n][2]; + + for (int i = 0; i < n; i++) { + for (int j = 0; j < 2; j++) { + z[i][j] = model.intVar("z_" + (i + 1) + "_" + j, 0, 1 << w); + b[i][j] = model.intVar("b_" + (i + 1) + "_" + j, -1 << (w + 1), 1 << (w + 1)); + y[i][j] = model.intVar("y_" + (i + 1) + "_" + j, sign_pow2); + ind[i][j] = model.intVar("ind_" + (i + 1) + "_" + j, 0, i); + } + c[i + 1] = model.intVar("c_" + (i + 1), 0, 1 << w); + } + c[0] = model.intVar("c0", 1); + + //3. add constraints + boolean active = false; + if (active) { + c[1].eq(8191).post(); + c[2].eq(32765).post(); + + y[0][0].eq(8192).post(); + y[0][1].eq(-1).post(); + y[1][0].eq(4).post(); + y[1][1].eq(1).post(); + } + + for (int i = 0; i < n; i++) { + for (int j = 0; j < 2; j++) { + model.element(z[i][j], c, ind[i][j], 0).post(); // z_ij = c_[ind_ij] + model.times(y[i][j], z[i][j], b[i][j]).post(); // y_ij * z_ij = b_ij + } + model.arithm(b[i][0], "+", b[i][1], "=", c[i + 1]).post(); // b_i0 + b_i1 = ci + } + // 4. Solve the problem + Solver solver = model.getSolver(); + Assert.assertTrue(solver.solve(), "No solution found"); + + + } +} diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/TableTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/TableTest.java index b9a372064d..02eccde396 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/TableTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/TableTest.java @@ -20,6 +20,7 @@ import org.chocosolver.solver.constraints.extension.TuplesFactory; import org.chocosolver.solver.constraints.extension.hybrid.HybridTuples; import org.chocosolver.solver.constraints.extension.hybrid.ISupportable; +import org.chocosolver.solver.constraints.extension.nary.PropCompactTableNeg; import org.chocosolver.solver.constraints.extension.nary.TuplesLargeTable; import org.chocosolver.solver.constraints.extension.nary.TuplesTable; import org.chocosolver.solver.constraints.extension.nary.TuplesVeryLargeTable; @@ -50,7 +51,7 @@ public class TableTest { private static final String[] ALGOS = {"CT+", "FC", "GAC2001", "GACSTR+", "GAC2001+", "GAC3rm+", "GAC3rm", "STR2+", "MDD+"}; - private static final String[] BIN_ALGOS = {"FC", "AC2001", "AC3", "AC3rm", "AC3bit+rm", "CT+"}; + private static final String[] BIN_ALGOS = {"FC", "AC2001", "AC3", "AC3rm", "AC3bit+rm", "CT", "CT+"}; @DataProvider(name = "algos") public Object[][] algos() { @@ -532,9 +533,8 @@ public void testST1(String staralgo) { IntVar x = model.intVar(1, 3); IntVar y = model.intVar(1, 3); IntVar z = model.intVar(1, 3); - Tuples ts = new Tuples(true); int ST = 99; - ts.setUniversalValue(ST); + Tuples ts = new Tuples(ST); ts.add(3, ST, 1); ts.add(1, 2, 3); ts.add(2, 3, 2); @@ -553,9 +553,8 @@ public void testST2(String staralgo) { IntVar x = model.intVar(1, 3); IntVar y = model.intVar(1, 3); IntVar z = model.intVar(1, 3); - Tuples ts = new Tuples(true); int ST = 99; - ts.setUniversalValue(ST); + Tuples ts = new Tuples(ST); ts.add(ST, ST, ST); model.table(new IntVar[]{x, y, z}, ts, staralgo).post(); @@ -572,9 +571,8 @@ public void testST3(String staralgo) { IntVar x = model.intVar("x", 0, 1); IntVar y = model.intVar("y", 0, 1); IntVar z = model.intVar("z", 0, 1); - Tuples ts = new Tuples(true); int ST = 99; - ts.setUniversalValue(ST); + Tuples ts = new Tuples(ST); ts.add(1, ST, ST); ts.add(ST, 1, ST); ts.add(ST, ST, 1); @@ -594,9 +592,8 @@ public void testST4(String staralgo) { IntVar cp = model.intVar("y", 2, 2); IntVar t = model.intVar("z", 0, 999); IntVar l = model.intVar("z", 1, 70_000, true); - Tuples ts = new Tuples(true); int ST = -1; - ts.setUniversalValue(ST); + Tuples ts = new Tuples(ST); ts.add(1, 2, ST, 3500); ts.add(2, 2, ST, 3500); model.table(new IntVar[]{st, cp, t, l}, ts, staralgo).post(); @@ -872,7 +869,7 @@ public void testReify(String a) { @Test(groups = "1s", timeOut = 60000, dataProvider = "algos") public void testMany1(String a) { - for(int i = 0; i < 200; i++) { + for (int i = 0; i < 200; i++) { Model model = new Model(); IntVar x = model.intVar("x", new int[]{-4, -1, 2}); IntVar y = model.intVar("y", -2, -1); @@ -891,7 +888,7 @@ public void testMany1(String a) { @Test(groups = "1s", timeOut = 60000, dataProvider = "balgos") public void testMany2(String a) { - for(int i = 0; i < 200; i++) { + for (int i = 0; i < 200; i++) { Model model = new Model(); IntVar x = model.intVar("x", new int[]{1, 2, 4, 7, 18}); Tuples t = new Tuples(); @@ -911,6 +908,29 @@ public void testMany2(String a) { } } + @Test(groups = "1s", timeOut = 60000, dataProvider = "balgos") + public void testMany3(String a) { + if (a.contains("+")) return; + for (int i = 0; i < 200; i++) { + Model model = new Model(); + IntVar x = model.intVar("x", new int[]{1, 2, 4, 7, 18}); + Tuples t = new Tuples(false); + t.add(1, 4); + t.add(2, 1); + t.add(4, 7); + t.add(7, 2); + t.add(7, 7); + t.add(18, 18); + model.table(x, x, t, a).post(); + Solver solver = model.getSolver(); + solver.setSearch(new FullyRandom(new IntVar[]{x}, i)); + while (model.getSolver().solve()) { + out.printf("%d - %d\n", x.getValue(), x.getValue()); + } + Assert.assertEquals(model.getSolver().getSolutionCount(), 3); + } + } + @Test(groups = "1s", timeOut = 60000, dataProvider = "balgos") public void testForbidden1(String a) { if (a.equals("CT+")) return; @@ -964,4 +984,53 @@ public void testHybrid() { } } + @Test + public void testNegCT() { + Model model = new Model(); + IntVar x = model.intVar("x", 0, 2); + IntVar y = model.intVar("y", 0, 2); + IntVar z = model.intVar("z", 0, 2); + Tuples t = new Tuples(false); + t.add(0, 0, 0); + t.add(1, 1, 1); + t.add(2, 2, 2); + new Constraint("TABLE", new PropCompactTableNeg(new IntVar[]{x, y, z}, t)).post(); + while (model.getSolver().solve()) { + out.printf("%d - %d - %d\n", x.getValue(), y.getValue(), z.getValue()); + } + Assert.assertEquals(model.getSolver().getSolutionCount(), (int) Math.pow(3, 3) - 3); + } + + @Test(groups = "1s", dataProvider = "algos") + public void testExp1(String a) { + Model model = new Model(); + BoolVar x = model.boolVar("x"); + IntVar y = model.intVar("y", 0, 121); + IntVar z = model.intVar("z", 0, 121); + x.ne(0).or(y.dist(z).eq(1)).extension(a).post(); + x.eq(0).post(); + y.eq(0).post(); + while (model.getSolver().solve()) { + out.printf("%d - %d - %d\n", x.getValue(), y.getValue(), z.getValue()); + } + Assert.assertEquals(model.getSolver().getSolutionCount(), 1); + } + + @Test(groups = "1s", dataProvider = "algos") + public void testExp2(String a) { + Model model = new Model(); + BoolVar x = model.boolVar("x"); + int n = 122; + IntVar y = model.intVar("y", 0, n); + IntVar z = model.intVar("z", 0, n); + x.ne(1).or(y.dist(z).eq(1)).extension(a).post(); + x.eq(0).post(); + y.eq(0).post(); +// model.getSolver().showDecisions(128); + while (model.getSolver().solve()) { + out.printf("%d - %d - %d\n", x.getValue(), y.getValue(), z.getValue()); + } + Assert.assertEquals(model.getSolver().getSolutionCount(), n+1); + } + } \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/reification/IfThenElseTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/reification/IfThenElseTest.java index 716aa3a993..573c89c20a 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/reification/IfThenElseTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/reification/IfThenElseTest.java @@ -10,6 +10,7 @@ package org.chocosolver.solver.constraints.reification; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Providers; import org.chocosolver.solver.Solver; import org.chocosolver.solver.search.strategy.Search; import org.chocosolver.solver.variables.BoolVar; @@ -52,10 +53,60 @@ public void test2() { model.ifThenElseDec(c, x, y); Solver solver = model.getSolver(); //solver.showSolutions(); - solver.setSearch(Search.randomSearch(ArrayUtils.append(c, x,new IntVar[]{y}), 0)); + solver.setSearch(Search.randomSearch(ArrayUtils.append(c, x, new IntVar[]{y}), 0)); while (solver.solve()) { //System.out.printf("%s %s %s%n", Arrays.toString(c), Arrays.toString(x), y); } Assert.assertEquals(solver.getSolutionCount(), 180); } + + @Test(groups = "1s", dataProvider = "random", dataProviderClass = Providers.class) + @Providers.Arguments(values = {"0", "20", "1"}) + public void test3(long seed) { + Model model = new Model(); + BoolVar[] c = new BoolVar[]{ + model.boolVar("c1"), + model.boolVar("c2"), + model.boolVar(true) + }; + IntVar[] x = new IntVar[]{ + model.intVar("x1", new int[]{0, 4}), + model.intVar("x1", new int[]{0, 6}), + model.intVar("x2", 10), + }; + IntVar y = model.intVar("y", 1, 10); + model.ifThenElseDec(c, x, y); + Solver solver = model.getSolver(); + //solver.showSolutions(); + solver.setSearch(Search.randomSearch(ArrayUtils.append(c, x, new IntVar[]{y}), seed)); + while (solver.solve()) { +// System.out.printf("%s %s %s%n", Arrays.toString(c), Arrays.toString(x), y); + } + Assert.assertEquals(solver.getSolutionCount(), 10); + } + + @Test(groups = "1s", dataProvider = "random", dataProviderClass = Providers.class) + @Providers.Arguments(values = {"0", "20", "1"}) + public void test4(long seed) { + Model model = new Model(); + BoolVar[] c = new BoolVar[]{ + model.boolVar("c1"), + model.boolVar("c2"), + model.boolVar(true) + }; + BoolVar[] x = new BoolVar[]{ + model.boolVar("x1"), + model.boolVar("x2"), + model.boolVar("x3") + }; + IntVar y = model.intVar("y", 0, 1); + model.ifThenElseDec(c, x, y); + Solver solver = model.getSolver(); + //solver.showSolutions(); + solver.setSearch(Search.randomSearch(ArrayUtils.append(c, x, new IntVar[]{y}), seed)); + while (solver.solve()) { + //System.out.printf("%s %s %s%n", Arrays.toString(c), Arrays.toString(x), y); + } + Assert.assertEquals(solver.getSolutionCount(), 32); + } } diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/ternary/DivTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/ternary/DivTest.java index a8c5686ca5..16f78eb66d 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/ternary/DivTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/ternary/DivTest.java @@ -10,8 +10,10 @@ package org.chocosolver.solver.constraints.ternary; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Solution; import org.chocosolver.solver.Solver; import org.chocosolver.solver.constraints.Constraint; +import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression; import org.chocosolver.solver.search.loop.monitors.IMonitorSolution; import org.chocosolver.solver.variables.IntVar; import org.testng.Assert; @@ -78,4 +80,32 @@ public void testTP1(){ r.solve(); Assert.assertEquals(r.getSolutionCount(), 1); } + + @Test(groups = "1s", timeOut = 2000) + public void testSlow() { + Model model = new Model(); + + // Compute lost potential + int deadline = 50460; + IntVar jobPackageStart = model.intVar(0, 879840); + int cost = 72000; // if <10 000, very fast + int period = 103680; + + model.arithm(jobPackageStart, "<=", deadline).post(); + + ArExpression deviation = model.intVar(deadline).sub(jobPackageStart); + deviation = deviation.div(60); + period = (int) Math.round(period / 60.0); + + // Warning : PropDivXYZ very slow (initial propagation) + IntVar potential = deviation.mul(cost).div(period).intVar(); // if we remove .mul(cost) or .div(period), very + // fast + + Solver solver = model.getSolver(); + Solution sol = solver.findOptimalSolution(potential, false); + // very slow : + // Building time : 14,074s + // Resolution time : 32,093s + // Time to best solution : 32,084s + } } diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/ternary/TimesTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/ternary/TimesTest.java index a4d0e7202c..ee2843df73 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/ternary/TimesTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/ternary/TimesTest.java @@ -13,9 +13,11 @@ import org.chocosolver.solver.Model; import org.chocosolver.solver.Providers; import org.chocosolver.solver.Settings; +import org.chocosolver.solver.Solver; import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.constraints.Propagator; import org.chocosolver.solver.exception.ContradictionException; +import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; import org.testng.Assert; import org.testng.annotations.Test; @@ -150,4 +152,24 @@ public void testMats1(boolean tableSubs) { model.times(i1, i2, i1).post(); Assert.assertFalse(model.getSolver().solve()); } + + @Test(groups = "1s", dataProviderClass = Providers.class, dataProvider = "trueOrFalse", timeOut = 60000) + public void testJBSC1(boolean lcg){ + Model model = new Model("model", Settings.dev().setLCG(lcg)); + + // X =b_0_[2] = [0,1] + BoolVar x = model.boolVar("x"); + // IV_33 = {-1..0} + IntVar i1 = model.intVar("i1", new int[]{-1, 0}); + // Y = 2.((IV_33≥0)) + 0[0,2] + IntVar y = model.intView(2, model.isGeq(i1, 0), 0); + //Z=nH2_0 = {0..2} + IntVar z = model.intVar("z", 0,2); + + model.times(x, y, z).post(); + Solver solver = model.getSolver(); + solver.showSolutions(() -> String.format("%d x %d = %d", x.getValue(), y.getValue(), z.getValue())); + solver.findAllSolutions(); + Assert.assertEquals(solver.getSolutionCount(), 4); + } } diff --git a/solver/src/test/java/org/chocosolver/solver/expression/discrete/ExpressionTest.java b/solver/src/test/java/org/chocosolver/solver/expression/discrete/ExpressionTest.java index 6bde522c97..c81e2971d0 100644 --- a/solver/src/test/java/org/chocosolver/solver/expression/discrete/ExpressionTest.java +++ b/solver/src/test/java/org/chocosolver/solver/expression/discrete/ExpressionTest.java @@ -10,6 +10,8 @@ package org.chocosolver.solver.expression.discrete; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Solution; +import org.chocosolver.solver.Solver; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression; import org.chocosolver.solver.expression.discrete.relational.ReExpression; @@ -525,6 +527,22 @@ public void test51(int p) { eval(model, x.in(y, z), p, 22); } + @Test(groups = "1s", timeOut = 600000) + public void test511() { + Model model = new Model(); + int[] marginDomain = new int[]{4}; // id slot in margin + IntVar slotAssigned = model.intVar(0, 4); + BoolVar inMarginVars = slotAssigned.in(marginDomain).boolVar(); // works with notin() + Solver solver = model.getSolver(); + IntVar[] objectives = new IntVar[2]; + objectives[0] = inMarginVars./*neg().*/intVar(); // (if notin() l.17) + objectives[1] = slotAssigned.neg().intVar(); + // Lexico Objective : 1. Minimize Id slot in margin; 2. Maximize Id Slot + Solution sol = solver.findLexOptimalSolution(objectives, Model.MINIMIZE); + Assert.assertEquals(sol.getIntVal(inMarginVars), 0); + Assert.assertEquals(sol.getIntVal(slotAssigned), 3); + } + @Test(groups = "1s", timeOut = 60000, dataProvider = "post") public void test52(int p) { Model model = new Model(); @@ -598,7 +616,7 @@ public void testJoao2_2() throws ContradictionException { IntVar z = model.intVar("z", new int[]{0, 1, 2}); z.ne(1).post(); - x.eq(1).imp(y.sub(z).eq(0)).post(); // wrong -> y = {0..2} + x.eq(1).imp(y.sub(z).eq(0)).extension().post(); // wrong -> y = {0..2} x.eq(1).post(); model.getSolver().propagate(); diff --git a/solver/src/test/java/org/chocosolver/solver/lcg/LCGTest.java b/solver/src/test/java/org/chocosolver/solver/lcg/LCGTest.java index 259dfb3ac1..1922f9aed0 100644 --- a/solver/src/test/java/org/chocosolver/solver/lcg/LCGTest.java +++ b/solver/src/test/java/org/chocosolver/solver/lcg/LCGTest.java @@ -9,20 +9,15 @@ */ package org.chocosolver.solver.lcg; -import gnu.trove.set.hash.TIntHashSet; import org.chocosolver.solver.Model; import org.chocosolver.solver.Providers; import org.chocosolver.solver.Settings; import org.chocosolver.solver.Solver; import org.chocosolver.solver.constraints.Constraint; +import org.chocosolver.solver.constraints.nary.cnf.LogOp; import org.chocosolver.solver.search.strategy.Search; -import org.chocosolver.solver.search.strategy.selectors.values.SetDomainMin; -import org.chocosolver.solver.search.strategy.selectors.variables.InputOrder; -import org.chocosolver.solver.search.strategy.selectors.variables.Random; -import org.chocosolver.solver.search.strategy.strategy.SetStrategy; import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; -import org.chocosolver.solver.variables.SetVar; import org.chocosolver.util.tools.ArrayUtils; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -62,9 +57,9 @@ public static Object[][] seed() { .toArray(Object[][]::new); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////// NQUEEN ////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////// NQUEEN ////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @DataProvider public static Object[][] nqueen() { @@ -135,6 +130,42 @@ public void testNQueenAllSolutions(boolean bounded, boolean view, int n, int s, Assert.assertEquals(solver.getSolutionCount(), s, "seed: " + seed); } + @Test(groups = "lcg", dataProvider = "nqueen", timeOut = 60000) + public void testNQueenAllSolutionsLim(boolean bounded, boolean view, int n, int s, long seed) { + // to ensure that prohibiting-solution clauses are not removed by the clause database reduction + Model model = new Model("LCG Queens enum", + Settings.init().setLCG(true) + .setNbMaxLearntClauses(100) + ); + IntVar[] vars = model.intVarArray("Q", n, 1, n, bounded); + + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + model.arithm(vars[i], "!=", vars[j]).post(); + if (view) { + model.arithm(vars[i], "!=", vars[j], "+", j - i).post(); + model.arithm(vars[i], "!=", vars[j], "-", j - i).post(); + } else { + model.arithm(vars[i], "!=", model.intView(1, vars[j], j - i)).post(); + model.arithm(vars[i], "!=", model.intView(1, vars[j], -j + i)).post(); + } + } + } + Solver solver = model.getSolver(); + if (seed == 0) { + solver.setSearch(Search.inputOrderLBSearch(vars)); + } else { + solver.setSearch(Search.randomSearch(vars, seed)); + } + while (solver.solve()) { +// System.out.printf("#%d : %s%n", +// solver.getSolutionCount(), +// Arrays.toString(Arrays.stream(vars).mapToInt(IntVar::getValue).toArray())); + } + ; + Assert.assertEquals(solver.getSolutionCount(), s, "seed: " + seed); + } + @Test(groups = "lcg", dataProvider = "nqueen", timeOut = 60000) public void testNQueenOptim(boolean bounded, boolean view, int n, int s, long seed) { Model model = new Model("LCG Queens opt", @@ -168,9 +199,9 @@ public void testNQueenOptim(boolean bounded, boolean view, int n, int s, long se Assert.assertEquals(solver.getObjectiveManager().getBestSolutionValue(), n + 1); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////// GOLOMB ////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////// GOLOMB ////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @DataProvider public static Object[][] alldiff() { @@ -286,9 +317,9 @@ public void testGolombRuler2(boolean bounded, boolean view, int m, int o, String Assert.assertEquals(solver.getObjectiveManager().getBestSolutionValue(), o); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////// MILP //////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////// MILP //////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @DataProvider public static Object[][] milp() { @@ -403,9 +434,9 @@ public void testMILPBin(boolean bounded, boolean view, long seed) { } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////// SCHUR LEMMA ////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////// SCHUR LEMMA ////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @DataProvider public static Object[][] schurlemma() { @@ -561,9 +592,9 @@ public void testBoolVars3() { Assert.assertEquals(solver.getSolutionCount(), 0); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////// REIFICATION ////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// ///////////////////////////////////////// REIFICATION ////////////////////////////////////////////////////////// + /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Test(groups = "lcg", dataProvider = "seed", timeOut = 60000) public void testEq(long seed) { @@ -666,4 +697,76 @@ public void testIsGeq(boolean view, long seed) { while (solver.solve()) ; Assert.assertEquals(solver.getSolutionCount(), 3); } + + @Test(groups = "lcg", timeOut = 60000) + public void testReification() { + Model model = new Model(Settings.init().setLCG(true)); + IntVar[] x = model.intVarArray("x", 6, 0, 1); + IntVar[] y = model.intVarArray("y", 6, 0, 1); + BoolVar b = model.boolVar("b"); + model.lexLessEq(x, y).reifyWith(b); + + x[0].eq(1).post(); + y[0].eq(1).post(); + x[1].eq(0).post(); + y[1].eq(0).post(); + x[5].eq(1).post(); + y[5].eq(1).post(); + + y[2].eq(0).post(); + x[3].eq(0).post(); + y[4].eq(0).post(); + + // if b is true and x[2] = 0, then x[4] = 1 and y[3] = 0 + model.addClauses( + LogOp.implies( + LogOp.and(b, model.isEq(x[2], 0)), + LogOp.and(model.isEq(x[4], 1), model.isEq(y[3], 0)) + )); +// + Solver solver = model.getSolver(); + solver.setSearch(Search.inputOrderUBSearch(b, x[2], y[3], x[4])); + solver.findAllSolutions(); + Assert.assertEquals(solver.getSolutionCount(), 5); + } + + @Test(groups = "lcg", timeOut = 600000) + public void test685() { + + for (int i = 1024; i >= 908; i /= 2) { + System.out.printf("Testing with range [%d,%d]%n", Short.MIN_VALUE / i, Short.MAX_VALUE / i); + + //{a*b + c == 2020, a + b*c == 2021} + Model model = new Model(Settings.init().setLCG(true)); + IntVar a = model.intVar("a", Short.MIN_VALUE / i, Short.MAX_VALUE / i); + IntVar b = model.intVar("b", Short.MIN_VALUE / i, Short.MAX_VALUE / i); + IntVar c = model.intVar("c", Short.MIN_VALUE / i, Short.MAX_VALUE / i); + a.mul(b).add(c).eq(20).post(); + a.add(b.mul(c)).eq(21).post(); + //model.displayVariableOccurrences(); + //model.displayPropagatorOccurrences(); + Solver solver = model.getSolver(); + solver.showShortStatistics(); + //solver.showDecisions(()->""); + while (solver.solve()) { + System.out.printf("a=%d b=%d c=%d%n", a.getValue(), b.getValue(), c.getValue()); + } + Assert.assertEquals(solver.getSolutionCount(), 1); + } + } + + @DataProvider + public Object[][] offsets() { + return new Object[][]{{0}, {1}}; + } + + @Test(groups = "lcg", timeOut = 600000, dataProvider = "offsets") + public void testJG1(int offset) { + // When the domain is sparse, the lit-based domain was not correctly instantiated + Model model = new Model(Settings.init().setLCG(true)); + IntVar a = model.intVar("a", offset, 31); + IntVar b = model.intVar("b", new int[]{offset, 10, 20, 30}); + model.arithm(a, "=", b).post(); + model.getSolver().solve(); + } } diff --git a/solver/src/test/java/org/chocosolver/solver/lcg/LitVarTests.java b/solver/src/test/java/org/chocosolver/solver/lcg/LitVarTests.java index eb7622c136..8fd82dc50e 100644 --- a/solver/src/test/java/org/chocosolver/solver/lcg/LitVarTests.java +++ b/solver/src/test/java/org/chocosolver/solver/lcg/LitVarTests.java @@ -595,5 +595,43 @@ public void testBoolGeqView0(int l, int u) throws ContradictionException { Assert.assertEquals(b.getLit(2, LR_LE), 1); } + @Test(groups = "{1s,lcg}") + public void testCste10() { + Model model = new Model("LCG testCste10", + Settings.init().setLCG(true) + .setWarnUser(true)); + int l = 10; + IntVar b = new IntVarEagerLit(new BitsetIntVarImpl("b", l, l, model)); + + Assert.assertEquals(b.getLit(l - 100, LR_EQ), 0); + Assert.assertEquals(b.getLit(l - 1, LR_EQ), 0); + Assert.assertEquals(b.getLit(l, LR_EQ), 1); + Assert.assertEquals(b.getLit(l + 1, LR_EQ), 0); + Assert.assertEquals(b.getLit(l + 100, LR_EQ), 0); + + Assert.assertEquals(b.getLit(l - 100, LR_NE), 1); + Assert.assertEquals(b.getLit(l - 1, LR_NE), 1); + Assert.assertEquals(b.getLit(l, LR_NE), 0); + Assert.assertEquals(b.getLit(l + 1, LR_NE), 1); + Assert.assertEquals(b.getLit(l + 100, LR_NE), 1); + + Assert.assertEquals(b.getLit(l - 100, LR_GE), 1); + Assert.assertEquals(b.getLit(l - 1, LR_GE), 1); + Assert.assertEquals(b.getLit(l, LR_GE), 1); + Assert.assertEquals(b.getLit(l + 1, LR_GE), 0); + Assert.assertEquals(b.getLit(l + 100, LR_GE), 0); + + Assert.assertEquals(b.getLit(l - 100, LR_LE), 0); + Assert.assertEquals(b.getLit(l - 1, LR_LE), 0); + Assert.assertEquals(b.getLit(l, LR_LE), 1); + Assert.assertEquals(b.getLit(l + 1, LR_LE), 1); + Assert.assertEquals(b.getLit(l + 100, LR_LE), 1); + + Assert.assertEquals(b.getMinLit(), MiniSat.neg(b.getLit(l, LR_GE))); + Assert.assertEquals(b.getMaxLit(), MiniSat.neg(b.getLit(l, LR_LE))); + Assert.assertEquals(b.getLit(l - 1, LR_LE), MiniSat.neg(b.getLit(l, LR_GE))); + Assert.assertEquals(b.getLit(l + 1, LR_GE), MiniSat.neg(b.getLit(l, LR_LE))); + } + } diff --git a/solver/src/test/java/org/chocosolver/solver/search/BlackBoxTest.java b/solver/src/test/java/org/chocosolver/solver/search/BlackBoxTest.java index 8164076234..aac93bd342 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/BlackBoxTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/BlackBoxTest.java @@ -85,7 +85,7 @@ public void testNQueen(Function> strat) { Assert.assertEquals(solver.getSolutionCount(), 724); } - @Test(groups = "1s", dataProvider = "strategies") + @Test(groups = "1s", timeOut = 60000, dataProvider = "strategies") public void testBios(Function> strat) { int n = 2; Model model = new Model("all different"); @@ -111,7 +111,7 @@ public Object[][] setstrategies() { } - @Test(groups = "1s", dataProvider = "setstrategies") + @Test(groups = "1s", timeOut = 10000, dataProvider = "setstrategies") public void testSet(Function> strat) { Model model = ProblemMaker.makeSteiner(7); SetVar[] vars = model.retrieveSetVars(); diff --git a/solver/src/test/java/org/chocosolver/solver/search/ObjectiveTest.java b/solver/src/test/java/org/chocosolver/solver/search/ObjectiveTest.java index 0e65fa398a..d4a51e6f48 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/ObjectiveTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/ObjectiveTest.java @@ -319,8 +319,8 @@ public void testDefSearch() { best = objective.getValue(); } assertEquals(best, 34); - assertEquals(model.getSolver().getSolutionCount(), 10); - assertEquals(model.getSolver().getNodeCount(), 1475); + assertEquals(model.getSolver().getSolutionCount(), 8); + assertEquals(model.getSolver().getNodeCount(), 1807); } diff --git a/solver/src/test/java/org/chocosolver/solver/search/ParetoTest.java b/solver/src/test/java/org/chocosolver/solver/search/ParetoTest.java index dbfb3963e3..0825284a9b 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/ParetoTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/ParetoTest.java @@ -153,6 +153,6 @@ private void runMOQAP() { List front = m.getSolver().findParetoFront(new IntVar[]{m.neg(obj1), m.neg(obj2)}, Model.MAXIMIZE); Assert.assertEquals(front.size(), 26); Assert.assertEquals(m.getSolver().getSolutionCount(), 233); - Assert.assertEquals(m.getSolver().getNodeCount(), 95190); + Assert.assertEquals(m.getSolver().getNodeCount(), 95208); } } diff --git a/solver/src/test/java/org/chocosolver/solver/search/loop/LNSTest.java b/solver/src/test/java/org/chocosolver/solver/search/loop/LNSTest.java index d4ef009440..f3339eae31 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/loop/LNSTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/loop/LNSTest.java @@ -102,8 +102,12 @@ private void knapsack20(final int lns) { bp = power.getValue(); bw = scalar.getValue(); } - Assert.assertEquals(bp, 8372); - Assert.assertEquals(bw, 1092); + // Some combinations of LNS and search strategies do not find the optimal solution + // So, the least we can do is to check that a solution is found + Assert.assertTrue(r.getSolutionCount() > 0, "No solution found with LNS=" + lns); + // We can also check that the weight is not that bad + Assert.assertTrue(bp >= 7900, "Power is too low with LNS=" + lns); + Assert.assertTrue(bw >= 1090, "Weight is too low with LNS=" + lns); } @DataProvider(name = "lns") diff --git a/solver/src/test/java/org/chocosolver/solver/search/loop/SolverTest.java b/solver/src/test/java/org/chocosolver/solver/search/loop/SolverTest.java index eefa5f6699..64b59efb88 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/loop/SolverTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/loop/SolverTest.java @@ -29,6 +29,7 @@ import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.ProblemMaker; +import org.chocosolver.util.tools.PreProcessing; import org.chocosolver.util.tools.VariableUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -122,7 +123,7 @@ public void test1HBFS() { r.setHBFS(.05, .1, 32); while (model.getSolver().solve()) ; assertEquals(model.getSolver().getSolutionCount(), 7); - assertEquals(model.getSolver().getNodeCount(), 4542); + assertEquals(model.getSolver().getNodeCount(), 5881); } @Test(groups = "1s", timeOut = 60000) @@ -178,7 +179,7 @@ public void test3() { while (model.getSolver().solve()) ; assertEquals(model.getSolver().getSolutionCount(), 3); - assertEquals(model.getSolver().getNodeCount(), 16); + assertEquals(model.getSolver().getNodeCount(), 17); } @Test(groups = "1s", timeOut = 60000) @@ -443,8 +444,11 @@ public void testPreprocessing() throws ContradictionException { long before = VariableUtils.domainCardinality(model.retrieveIntVars(true)); Assert.assertEquals(before, 256); solver.propagate(); - solver.preprocessing(2000); + PreProcessing.sac(model, -1); long after = VariableUtils.domainCardinality(model.retrieveIntVars(true)); - Assert.assertEquals(after,32); + Assert.assertEquals(after,16); + solver.findAllSolutions(); + Assert.assertEquals(solver.getSolutionCount(), 2); + Assert.assertEquals(solver.getFailCount(), 0); } } diff --git a/solver/src/test/java/org/chocosolver/solver/search/restart/RestartTest.java b/solver/src/test/java/org/chocosolver/solver/search/restart/RestartTest.java index 5cc153ea22..ce2d993482 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/restart/RestartTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/restart/RestartTest.java @@ -134,7 +134,8 @@ public void testGolombRuler() { ); while (solver.solve()) ; assertEquals(solver.getRestartCount(), 2); - assertEquals(solver.getSolutionCount(), 10); + assertEquals(solver.getSolutionCount(), 7); + assertEquals(solver.getObjectiveManager().getBestSolutionValue(), 55); } @Test(groups = "lcg", dataProvider = "random", dataProviderClass = Providers.class) @@ -159,12 +160,12 @@ public void testGolombRulerWithReset() { solver.setSearch(domOverWDegSearch(ticks)); while (solver.solve()) ; assertEquals(solver.getRestartCount(), 2); - assertEquals(solver.getSolutionCount(), 3); + assertEquals(solver.getSolutionCount(), 6); assertEquals(solver.getObjectiveManager().getBestSolutionValue(), 34); solver.hardReset(); while (solver.solve()) ; assertEquals(solver.getRestartCount(), 0); - assertEquals(solver.getSolutionCount(), 10); + assertEquals(solver.getSolutionCount(), 8); assertEquals(solver.getObjectiveManager().getBestSolutionValue(), 34); } diff --git a/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearchTest.java b/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearchTest.java index 6b56a52b94..f5ffb2653b 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearchTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/ConflictOrderingSearchTest.java @@ -42,7 +42,7 @@ public class ConflictOrderingSearchTest { public void before() { model = new Model(); mvars = model.intVarArray(10, 0, 5); - cos = new ConflictOrderingSearch(Search.inputOrderLBSearch(mvars)); + cos = new ConflictOrderingSearch<>(model, Search.inputOrderLBSearch(mvars)); } diff --git a/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/WarmStartTest.java b/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/WarmStartTest.java index 64cd16afd2..0dccdfbaeb 100644 --- a/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/WarmStartTest.java +++ b/solver/src/test/java/org/chocosolver/solver/search/strategy/strategy/WarmStartTest.java @@ -16,6 +16,8 @@ import org.testng.Assert; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + /** *
      * @@ -32,6 +34,7 @@ public void test1() { s.addHint(vars[6], 34); while (s.solve()) ; Assert.assertEquals(s.getSolutionCount(), 1); + assertEquals(s.getObjectiveManager().getBestSolutionValue(), 34); } @Test(groups = "1s") @@ -42,7 +45,9 @@ public void test2() { s.addHint(vars[6], 34); s.removeHints(); while (s.solve()) ; - Assert.assertEquals(s.getSolutionCount(), 10); + Assert.assertEquals(s.getSolutionCount(), 8); + assertEquals(s.getObjectiveManager().getBestSolutionValue(), 34); + } @Test(groups = "1s") @@ -53,10 +58,12 @@ public void test3() { s.addHint(vars[6], 34); while (s.solve()) ; Assert.assertEquals(s.getSolutionCount(), 1); + assertEquals(s.getObjectiveManager().getBestSolutionValue(), 34); s.removeHints(); s.hardReset(); while (s.solve()) ; - Assert.assertEquals(s.getSolutionCount(), 10); + Assert.assertEquals(s.getSolutionCount(), 8); + assertEquals(s.getObjectiveManager().getBestSolutionValue(), 34); } } \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/solver/variables/TaskTest.java b/solver/src/test/java/org/chocosolver/solver/variables/TaskTest.java index 4b159444c6..5469d9125c 100644 --- a/solver/src/test/java/org/chocosolver/solver/variables/TaskTest.java +++ b/solver/src/test/java/org/chocosolver/solver/variables/TaskTest.java @@ -12,10 +12,9 @@ import org.chocosolver.solver.Cause; import org.chocosolver.solver.Model; import org.chocosolver.solver.Solver; -import org.chocosolver.solver.constraints.Constraint; -import org.chocosolver.solver.constraints.ternary.PropXplusYeqZ; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.search.strategy.Search; +import org.chocosolver.solver.variables.events.PropagatorEventType; import org.chocosolver.solver.variables.impl.BitsetIntVarImpl; import org.chocosolver.solver.variables.view.integer.IntAffineView; import org.chocosolver.util.ESat; @@ -50,9 +49,11 @@ public void setup() { public void testDecreaseDuration() throws ContradictionException { checkVariable(duration, 0, 10); start.removeValue(0, Cause.Null); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); checkVariable(duration, 0, 9); end.removeValue(10, Cause.Null); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); checkVariable(duration, 0, 8); } @@ -60,11 +61,13 @@ public void testDecreaseDuration() throws ContradictionException { @Test(groups = "1s", timeOut = 60000) public void testIncreaseDuration() throws ContradictionException { start.removeInterval(4, 5, Cause.Null); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); checkVariable(duration, 2, 10); end.removeValue(5, Cause.Null); end.removeValue(6, Cause.Null); end.removeValue(7, Cause.Null); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); checkVariable(duration, 5, 10); } @@ -72,7 +75,7 @@ public void testIncreaseDuration() throws ContradictionException { public void testBadDomainFilteringOK() throws ContradictionException { Task task = new Task(end, duration, start); System.out.println(task); - task.ensureBoundConsistency(); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); checkVariable(duration, 0, 0); checkVariable(start, 5, 5); checkVariable(end, 5, 5); @@ -83,7 +86,7 @@ public void testBadDomainFilteringKO() throws ContradictionException { IntVar start = model.intVar(5, 6); IntVar end = model.intVar(1, 2); task = new Task(start, duration, end); - task.ensureBoundConsistency(); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); } @@ -99,17 +102,18 @@ public void testUpdateStart() throws ContradictionException { public void testRemoveValueSameVariable() throws ContradictionException { IntVar start = model.intVar(-5, 0); IntVar durationAndEnd = model.intVar(10); - new Task(start, durationAndEnd, durationAndEnd); + Task task = new Task(start, durationAndEnd, durationAndEnd); start.removeValue(0, Cause.Null); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); } - @Test(groups = "1s", timeOut = 60000) + @Test(groups = "1s", timeOut = 60000, expectedExceptions = ContradictionException.class) public void testNegativeDuration() throws ContradictionException { IntVar start = model.intVar(0); IntVar end = model.intVar(-5); IntVar duration = model.intVar(-5); Task task = new Task(start, duration, end); - task.ensureBoundConsistency(); + task.propagate(PropagatorEventType.FULL_PROPAGATION.getMask()); // TODO: 21/03/2016 is it possible ? System.out.println("odd behaviour: " + task); } @@ -168,104 +172,192 @@ private void checkVariable(IntVar var, int lb, int ub) { /////////////////////////////////////////////////////////////// private static boolean sameTaskVars(Task t1, Task t2) { - return t1.getStart().getLB() == t2.getStart().getLB() && t1.getStart().getUB() == t2.getStart().getUB() + return t1.getEst() == t2.getEst() && t1.getLst() == t2.getLst() && t1.getDuration().getLB() == t2.getDuration().getLB() && t1.getDuration().getUB() == t2.getDuration().getUB() - && t1.getEnd().getLB() == t2.getEnd().getLB() && t1.getEnd().getUB() == t2.getEnd().getUB(); + && t1.getEct() == t2.getEct() && t1.getLct() == t2.getLct(); } - private static boolean hasTaskMonitor(Task task) { - return task.getMonitor() != null; + @FunctionalInterface + private interface TaskCreator { + Task create(); } - private void specificToTask(Model m) { - // Task(Model model, int est, int lst, int d, int ect, int lct) - Task t1 = new Task(m, 0, 10, 2, 0, 10); - Assert.assertTrue(hasTaskMonitor(t1)); - Task t2 = new Task(m, 0, 10, 2, 2, 12); - Assert.assertFalse(hasTaskMonitor(t2)); + private static Task[] createTasks(TaskCreator[] creators) { + Task[] tasks = new Task[creators.length]; + for (int i = 0; i < tasks.length; i++) { + tasks[i] = creators[i].create(); + } + return tasks; } - private void specificToIVariableFactory(Model m) { - // taskVar(IntVar s, IntVar d) - IntVar s = m.intVar(0, 10); - IntVar d = m.intVar(1, 5); - Task t1 = m.taskVar(s, d); - Assert.assertTrue(hasTaskMonitor(t1)); - - Task t2 = m.taskVar(s, m.intVar(2)); - Assert.assertTrue(t2.getEnd() instanceof IntAffineView); - Assert.assertFalse(hasTaskMonitor(t2)); + @FunctionalInterface + private interface TaskTester { + boolean test(Task task); } - private void inCommon(Model m) { + private void testTaskVars(Task[] tasks, boolean shouldHaveMonitor, TaskTester[] testers) { + for (int i = 0; i < tasks.length; i++) { + Assert.assertEquals(!tasks[i].isPassive(), shouldHaveMonitor); + if (testers != null) { + for (int j = 0; j < testers.length; j++) { + Assert.assertTrue(testers[j].test(tasks[i])); + } + } + if (tasks[i] instanceof OptionalTask) { + Assert.assertTrue(tasks[i].mayBePerformed()); + Assert.assertFalse(tasks[i].mustBePerformed()); + } else { + Assert.assertTrue(tasks[i].mayBePerformed()); + Assert.assertTrue(tasks[i].mustBePerformed()); + } + if (i > 0) { + Assert.assertTrue(sameTaskVars(tasks[0], tasks[i])); + } + } + } + + @Test(groups = "1s", timeOut = 60000) + public void testTaskConstructors() { + Model m = new Model(); int d = 2; + boolean shouldHaveMonitor = false; + + // Task(Model m, int est, int lst, int d, int ect, int lct) + TaskCreator[] creators = new TaskCreator[]{ + () -> new Task(m, 1, 10, 2, 0, 10), + () -> new OptionalTask(m, 1, 10, 2, 0, 10), + () -> new OptionalTask(m, 1, 10, 2, 0, 10, m.boolVar()), + () -> m.taskVar(1, 10, 2, 0, 10), + () -> m.taskVar(1, 10, 2, 0, 10, false), + () -> m.taskVar(1, 10, 2, 0, 10, true), + () -> m.taskVar(1, 10, 2, 0, 10, m.boolVar()) + }; + TaskTester[] testers = new TaskTester[]{ + t -> t.getEnd() instanceof IntAffineView, + t -> t.getEst() == 1, + t -> t.getLst() == 8, + t -> t.getEct() == 3, + t -> t.getLct() == 10 + }; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); + + shouldHaveMonitor = false; + creators = new TaskCreator[]{ + () -> new Task(m, 0, 10, 2, 2, 12), + () -> new OptionalTask(m, 0, 10, 2, 2, 12), + () -> new OptionalTask(m, 0, 10, 2, 2, 12, m.boolVar()), + () -> m.taskVar(0, 10, 2, 2, 12), + () -> m.taskVar(0, 10, 2, 2, 12, false), + () -> m.taskVar(0, 10, 2, 2, 12, true), + () -> m.taskVar(0, 10, 2, 2, 12, m.boolVar()) + }; + testers = new TaskTester[]{ + t -> t.getEnd() instanceof IntAffineView, + t -> t.getEst() == 0, + t -> t.getLst() == 10, + t -> t.getEct() == 2, + t -> t.getLct() == 12 + }; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); // Task(IntVar s, int d) - Task t1 = new Task(m.intVar(0, 10), d); - Task t2 = m.taskVar(m.intVar(0, 10), d); - Assert.assertTrue(sameTaskVars(t1, t2)); - Assert.assertFalse(hasTaskMonitor(t1)); - Assert.assertFalse(hasTaskMonitor(t2)); + shouldHaveMonitor = false; + creators = new TaskCreator[]{ + () -> new Task(m.intVar(0, 10), d), + () -> new OptionalTask(m.intVar(0, 10), d), + () -> new OptionalTask(m.intVar(0, 10), d, m.boolVar()), + () -> m.taskVar(m.intVar(0, 10), d), + () -> m.taskVar(m.intVar(0, 10), d, false), + () -> m.taskVar(m.intVar(0, 10), d, true), + () -> m.taskVar(m.intVar(0, 10), d, m.boolVar()) + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); // Task(IntVar s, int d, IntVar e) - Task t3 = new Task(m.intVar(0, 10), d, m.intVar(0, 10)); - Task t4 = m.taskVar(m.intVar(0, 10), d, m.intVar(0, 10)); - Assert.assertTrue(sameTaskVars(t3, t4)); - Assert.assertTrue(hasTaskMonitor(t3)); - Assert.assertTrue(hasTaskMonitor(t4)); - - IntVar s5 = m.intVar(0, 10); - Task t5 = new Task(s5, d, m.offset(s5, d)); - IntVar s6 = m.intVar(0, 10); - Task t6 = m.taskVar(s6, d, m.offset(s6, d)); - Assert.assertTrue(sameTaskVars(t5, t6)); - Assert.assertFalse(hasTaskMonitor(t5)); - Assert.assertFalse(hasTaskMonitor(t6)); + shouldHaveMonitor = true; + creators = new TaskCreator[]{ + () -> new Task(m.intVar(0, 10), d, m.intVar(0, 10)), + () -> new OptionalTask(m.intVar(0, 10), d, m.intVar(0, 10)), + () -> new OptionalTask(m.intVar(0, 10), d, m.intVar(0, 10), m.boolVar()), + () -> m.taskVar(m.intVar(0, 10), d, m.intVar(0, 10)), + () -> m.taskVar(m.intVar(0, 10), d, m.intVar(0, 10), false), + () -> m.taskVar(m.intVar(0, 10), d, m.intVar(0, 10), true), + () -> m.taskVar(m.intVar(0, 10), d, m.intVar(0, 10), m.boolVar()) + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); + + shouldHaveMonitor = false; + creators = new TaskCreator[]{ + () -> {IntVar s = m.intVar(0, 10); return new Task(s, d, m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, d, m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, d, m.offset(s, d), m.boolVar());}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, d, m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, d, m.offset(s, d), false);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, d, m.offset(s, d), true);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, d, m.offset(s, d), m.boolVar());} + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); // Task(IntVar s, IntVar d, IntVar e) - Task t7 = new Task(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)); - Task t8 = m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)); - Assert.assertTrue(sameTaskVars(t7, t8)); - Assert.assertTrue(hasTaskMonitor(t7)); - Assert.assertTrue(hasTaskMonitor(t8)); - - IntVar s9 = m.intVar(0, 10); - Task t9 = new Task(s9, m.intVar(d), m.offset(s9, d)); - IntVar s10 = m.intVar(0, 10); - Task t10 = m.taskVar(s10, m.intVar(d), m.offset(s10, d)); - Assert.assertTrue(sameTaskVars(t9, t10)); - Assert.assertFalse(hasTaskMonitor(t9)); - Assert.assertFalse(hasTaskMonitor(t10)); - - // Task(IntVar s, IntVar d, IntVar e, boolean declareMonitor) - Task t11 = new Task(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)); - Task t12 = m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)); - Assert.assertTrue(sameTaskVars(t11, t12)); - Assert.assertTrue(hasTaskMonitor(t11)); - Assert.assertTrue(hasTaskMonitor(t12)); - - IntVar s13 = m.intVar(0, 10); - Task t13 = new Task(s13, m.intVar(d), m.offset(s13, d)); - IntVar s14 = m.intVar(0, 10); - Task t14 = m.taskVar(s14, m.intVar(d), m.offset(s14, d)); - Assert.assertTrue(sameTaskVars(t13, t14)); - Assert.assertFalse(hasTaskMonitor(t13)); - Assert.assertFalse(hasTaskMonitor(t14)); - } + shouldHaveMonitor = true; + creators = new TaskCreator[]{ + () -> new Task(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)), + () -> new OptionalTask(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)), + () -> new OptionalTask(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10), m.boolVar()), + () -> m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10)), + () -> m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10), false), + () -> m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10), true), + () -> m.taskVar(m.intVar(0, 10), m.intVar(1, 5), m.intVar(0, 10), m.boolVar()) + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); + + shouldHaveMonitor = false; + creators = new TaskCreator[]{ + () -> {IntVar s = m.intVar(0, 10); return new Task(s, m.intVar(d), m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(d), m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(d), m.offset(s, d), m.boolVar());}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(d), m.offset(s, d));}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(d), m.offset(s, d), false);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(d), m.offset(s, d), true);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(d), m.offset(s, d), m.boolVar());} + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); - @Test(groups = "1s", timeOut = 60000) - public void testingTaskConstructors() { - Model m = new Model(); - - /* Task specific constructors */ - specificToTask(m); - /* Constructing methods in common between Task and IVariableFactory */ - inCommon(m); - /* IVariableFactory specific constructors */ - specificToIVariableFactory(m); + // taskVar(IntVar s, IntVar d) + shouldHaveMonitor = true; + creators = new TaskCreator[]{ + () -> {IntVar s = m.intVar(0, 10); return new Task(s, m.intVar(1, 5));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(1, 5));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(1, 5), m.boolVar());}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(1, 5));}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(1, 5), false);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(1, 5), true);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(1, 5), m.boolVar());} + }; + testers = null; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); + + shouldHaveMonitor = false; + creators = new TaskCreator[]{ + () -> {IntVar s = m.intVar(0, 10); return new Task(s, m.intVar(2));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(2));}, + () -> {IntVar s = m.intVar(0, 10); return new OptionalTask(s, m.intVar(2), m.boolVar());}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(2));}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(2), false);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(2), true);}, + () -> {IntVar s = m.intVar(0, 10); return m.taskVar(s, m.intVar(2), m.boolVar());} + }; + testers = new TaskTester[]{ + t -> t.getEnd() instanceof IntAffineView + }; + testTaskVars(createTasks(creators), shouldHaveMonitor, testers); } - @Test(groups = "1s") public void testMonitor1() { Model model = new Model(); @@ -273,7 +365,7 @@ public void testMonitor1() { IntVar dur = model.intVar("dur", new int[]{1, 2, 4, 5}); IntVar last = model.intVar("last", 5, 6); IntVar IV390 = model.intVar("IV390", 6); - new Constraint("", new PropXplusYeqZ(first, dur, last)).post(); + model.arithm(first, "+", dur, "=", last).post(); new Task(first, dur, IV390); Solver s = model.getSolver(); s.setSearch(Search.inputOrderLBSearch(last)); // <- for the issue @@ -287,7 +379,7 @@ public void testMonitor2() { IntVar dur = new BitsetIntVarImpl("dur", new int[]{1, 2, 4, 5}, model); IntVar last = model.intVar("last", 5, 6); IntVar IV390 = model.intVar("IV390", 6); - new Constraint("", new PropXplusYeqZ(first, dur, last)).post(); + model.arithm(first, "+", dur, "=", last).post(); new Task(first, dur, IV390); Solver s = model.getSolver(); s.setSearch(Search.inputOrderLBSearch(last)); // <- for the issue @@ -301,11 +393,25 @@ public void testMonitorAndView() { IntVar dur = model.offset(model.intVar("dur", new int[]{1, 2, 4, 5}), 2); IntVar last = model.offset(model.intVar("last", 5, 6), 2); IntVar IV390 = model.offset(model.intVar("IV390", 6), 2); - new Constraint("", new PropXplusYeqZ(first, dur, last)).post(); + model.arithm(first, "+", dur, "=", last).post(); new Task(first, dur, IV390); Solver s = model.getSolver(); s.setSearch(Search.inputOrderLBSearch(last)); // <- for the issue Assert.assertTrue(s.solve()); } -} + @Test(groups = "1s", timeOut = 60000) + public void testScalarAndTaskBug1() { + int[] coeffs = new int[]{3, -2,-1,-2}; + IntVar[] vars = new IntVar[4]; + vars[0] = model.intVar(8, 9); + vars[1] = model.intVar(2, 4); + vars[2] = model.intVar(new int[]{1,2,4,5,6}); + vars[3] = model.intVar(3, 5); + model.scalar(vars, coeffs, "<=", 1).post(); + IntVar ee = model.intVar(4, 9); + new Task(vars[3], vars[2], ee); + model.getSolver().solve(); + Assert.assertEquals(model.getSolver().getSolutionCount(), 0); + } +} \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/solver/variables/ViewsTest.java b/solver/src/test/java/org/chocosolver/solver/variables/ViewsTest.java index 8186078e41..da56e7b349 100644 --- a/solver/src/test/java/org/chocosolver/solver/variables/ViewsTest.java +++ b/solver/src/test/java/org/chocosolver/solver/variables/ViewsTest.java @@ -14,7 +14,7 @@ import org.chocosolver.solver.constraints.nary.sum.PropScalar; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.search.strategy.Search; -import org.chocosolver.solver.search.strategy.selectors.values.IntDomainRandomBound; +import org.chocosolver.solver.search.strategy.selectors.values.IntDomainRandom; import org.chocosolver.solver.search.strategy.selectors.variables.Random; import org.chocosolver.util.iterators.DisposableValueIterator; import org.chocosolver.util.tools.ArrayUtils; @@ -386,7 +386,7 @@ public void test3() { IntVar az = ref.intVar("az", 0, 2, false); ref.absolute(az, z).post(); ref.getSolver().setSearch( - intVarSearch(new Random<>(seed), new IntDomainRandomBound(seed), x, y, az)); + intVarSearch(new Random<>(seed), new IntDomainRandom(seed, true), x, y, az)); } Model model = new Model(); { @@ -396,7 +396,7 @@ public void test3() { model.arithm(x, "-", y, "=", z).post(); IntVar az = model.abs(z); model.getSolver().setSearch( - intVarSearch(new Random<>(seed), new IntDomainRandomBound(seed), x, y, az)); + intVarSearch(new Random<>(seed), new IntDomainRandom(seed, true), x, y, az)); } check(ref, model, seed, true, true); } diff --git a/solver/src/test/java/org/chocosolver/solver/variables/impl/BoolVarImplTest.java b/solver/src/test/java/org/chocosolver/solver/variables/impl/BoolVarImplTest.java index 05b4648f71..3779c45cf9 100644 --- a/solver/src/test/java/org/chocosolver/solver/variables/impl/BoolVarImplTest.java +++ b/solver/src/test/java/org/chocosolver/solver/variables/impl/BoolVarImplTest.java @@ -20,9 +20,6 @@ import org.testng.Assert; import org.testng.annotations.Test; -import java.util.Iterator; -import java.util.NoSuchElementException; - /** *

      * Project: choco. @@ -190,18 +187,6 @@ public void testGetRangeIterator() throws Exception { Assert.assertFalse(rit.hasPrevious()); } - @Test(groups = "1s", timeOut = 60000) - public void testIterator() throws Exception { - setUp(); - Iterator iter = var.iterator(); - Assert.assertTrue(iter.hasNext()); - Assert.assertEquals(iter.next().intValue(), 0); - Assert.assertTrue(iter.hasNext()); - Assert.assertEquals(iter.next().intValue(), 1); - Assert.assertFalse(iter.hasNext()); - Assert.assertThrows(NoSuchElementException.class, () -> iter.next()); - } - @Test(groups = "1s", timeOut = 60000) public void testRemVals1() throws ContradictionException { Model model = new Model(); diff --git a/solver/src/test/java/org/chocosolver/solver/variables/impl/IntVarTest.java b/solver/src/test/java/org/chocosolver/solver/variables/impl/IntVarTest.java index 920c633c4c..3e656b2a49 100644 --- a/solver/src/test/java/org/chocosolver/solver/variables/impl/IntVarTest.java +++ b/solver/src/test/java/org/chocosolver/solver/variables/impl/IntVarTest.java @@ -9,15 +9,12 @@ */ package org.chocosolver.solver.variables.impl; -import java.util.Iterator; -import java.util.NoSuchElementException; import org.chocosolver.solver.Cause; import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableBitSet; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet; import org.chocosolver.util.objects.setDataStructures.iterable.IntIterableSet; -import org.testng.Assert; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -32,19 +29,6 @@ public abstract class IntVarTest { public abstract void setup(); - @Test(groups="1s", timeOut=60000) - public void testIterator() { - setup(); - int value = var.getLB()-1; - Iterator iter = var.iterator(); - while(value < var.getUB()) { - Assert.assertTrue(iter.hasNext()); - Assert.assertEquals(iter.next().intValue(), value+1); - value++; - } - Assert.assertThrows(NoSuchElementException.class, () -> iter.next()); - } - //------------------------------------ //------- Remove interval ------------ //------------------------------------ diff --git a/solver/src/test/java/org/chocosolver/solver/variables/view/integer/IntAffineViewTest.java b/solver/src/test/java/org/chocosolver/solver/variables/view/integer/IntAffineViewTest.java index 0235cde72f..5d28fa99b4 100644 --- a/solver/src/test/java/org/chocosolver/solver/variables/view/integer/IntAffineViewTest.java +++ b/solver/src/test/java/org/chocosolver/solver/variables/view/integer/IntAffineViewTest.java @@ -10,6 +10,9 @@ package org.chocosolver.solver.variables.view.integer; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Providers; +import org.chocosolver.solver.Settings; +import org.chocosolver.solver.variables.BoolVar; import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.iterators.DisposableRangeIterator; import org.chocosolver.util.iterators.DisposableValueIterator; @@ -214,4 +217,19 @@ public void testNegIntAffineView() { assertEquals(-8, y.getLB()); assertEquals(-6, y.getUB()); } + + @Test(groups = "1s", timeOut = 60000, dataProviderClass = Providers.class, dataProvider = "trueOrFalse") + public void testViewAndReification(boolean lcg) { + Model m = new Model(Settings.init().setLCG(lcg)); + IntVar x = m.intVar("x", -1, 6); + IntVar y = m.intView(1, x, -5); + BoolVar b = m.member(y, 0, 0).reify(); + m.member(x, 1,1).reifyWith(b.not()); + m.getSolver().setSearch(inputOrderLBSearch(x, y)); + while (m.getSolver().solve()) { +// System.out.printf("x=%d, y=%d, b=%b%n", x.getValue(), y.getValue(), b.getValue()); + }; + assertEquals(m.getSolver().getSolutionCount(), 2); + assertEquals(m.getSolver().getFailCount(), 2); + } } \ No newline at end of file diff --git a/solver/src/test/java/org/chocosolver/util/tools/PreProcessingTest.java b/solver/src/test/java/org/chocosolver/util/tools/PreProcessingTest.java index 352e5dfd18..09929e85c2 100644 --- a/solver/src/test/java/org/chocosolver/util/tools/PreProcessingTest.java +++ b/solver/src/test/java/org/chocosolver/util/tools/PreProcessingTest.java @@ -10,10 +10,14 @@ package org.chocosolver.util.tools; import org.chocosolver.solver.Model; +import org.chocosolver.solver.Solver; +import org.chocosolver.solver.exception.ContradictionException; import org.chocosolver.util.ProblemMaker; import org.testng.Assert; import org.testng.annotations.Test; +import java.util.Arrays; + /** *
      * @@ -30,4 +34,64 @@ public void test1() { model.getSolver().findAllSolutions(); Assert.assertEquals(model.getSolver().getSolutionCount(), 0); } + + @Test(groups = "1s", timeOut = 60000) + public void testDetectIntEqualities() { + Model model = ProblemMaker.makeNQueenWithBinaryConstraints(8); + Assert.assertEquals(model.getNbCstrs(), 84); + PreProcessing.detectIntInequalities(model); + Assert.assertEquals(model.getNbCstrs(), 59); // 57 + 2 implied constraints + model.getSolver().findAllSolutions(); + model.getSolver().printShortStatistics(); + Assert.assertEquals(model.getSolver().getSolutionCount(), 92); + } + + + @Test(groups = "1s") + public void testSACNQ() throws ContradictionException { + Model model = ProblemMaker.makeNQueenWithBinaryConstraints(4); + Solver solver = model.getSolver(); + long before = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(before, 16); + solver.propagate(); + PreProcessing.sac(model, 2000); + long after = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(after, 8); + } + + @Test(groups = "1s") + public void testSACGR() throws ContradictionException { + Model model = ProblemMaker.makeGolombRuler(6); + Solver solver = model.getSolver(); + long before = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(before, 2688); + solver.propagate(); + PreProcessing.sac(model, 2000); + long after = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(after, 2222); + } + + @Test(groups = "1s") + public void testSACBound() throws ContradictionException { + Model model = ProblemMaker.makeGolombRuler(6); + Solver solver = model.getSolver(); + long before = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(before, 2688); + solver.propagate(); + PreProcessing.sacBound(model, 2000); + long after = Arrays.stream(model.retrieveIntVars(true)) + .mapToLong(VariableUtils::domainCardinality) + .sum(); + Assert.assertEquals(after, 2340); + } } \ No newline at end of file