From 4c136b1450b056e63967a08e5e4e7f9aefa6ca87 Mon Sep 17 00:00:00 2001 From: Arthur Godet Date: Mon, 9 Oct 2023 20:41:47 +0200 Subject: [PATCH 1/2] make propagation stops sooner if time limit is met (fix issue #1062) --- .../java/org/chocosolver/solver/Solver.java | 23 +++++++++++-- .../solver/propagation/PropagationEngine.java | 3 ++ .../org/chocosolver/solver/LimitsTest.java | 32 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/solver/src/main/java/org/chocosolver/solver/Solver.java b/solver/src/main/java/org/chocosolver/solver/Solver.java index 41097dfa63..ad82c3fcee 100644 --- a/solver/src/main/java/org/chocosolver/solver/Solver.java +++ b/solver/src/main/java/org/chocosolver/solver/Solver.java @@ -21,6 +21,7 @@ import org.chocosolver.solver.propagation.PropagationEngine; import org.chocosolver.solver.search.SearchState; import org.chocosolver.solver.search.limits.ICounter; +import org.chocosolver.solver.search.limits.TimeCounter; import org.chocosolver.solver.search.loop.Reporting; import org.chocosolver.solver.search.loop.learn.Learn; import org.chocosolver.solver.search.loop.learn.LearnNothing; @@ -169,6 +170,8 @@ public enum Action { */ protected List criteria; + protected TimeCounter timeCounter; + /** * Indicates if the default search loop is in use (set to true in that case). */ @@ -1007,11 +1010,18 @@ public boolean hasEndedUnexpectedly() { return mMeasures.getSearchState() == SearchState.KILLED; } + /** + * @return true if the time limit is met. + */ + public boolean isTimeLimitMet() { + return timeCounter.isMet(); + } + /** * @return true if the search loops encountered at least one of the stop criteria declared. */ public boolean isStopCriterionMet() { - boolean ismet = false; + boolean ismet = isTimeLimitMet(); for (int i = 0; i < criteria.size() && !ismet; i++) { ismet = criteria.get(i).isMet(); } @@ -1311,7 +1321,16 @@ public void removeHints() { */ public void addStopCriterion(Criterion... criterion) { if (criterion != null) { - Collections.addAll(criteria, criterion); + for (int i = 0; i < criterion.length; i++) { + if (criterion[i] instanceof TimeCounter) { + TimeCounter tc = (TimeCounter) criterion[i]; + if (timeCounter == null || timeCounter.getLimitValue() > tc.getLimitValue()) { + timeCounter = tc; + } + } else { + criteria.add(criterion[i]); + } + } } } 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 7b94aff6d9..f115cbdb63 100644 --- a/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java +++ b/solver/src/main/java/org/chocosolver/solver/propagation/PropagationEngine.java @@ -189,6 +189,9 @@ public void propagate() throws ContradictionException { manageModifications(); for (int i = nextNotEmpty(); i > -1; i = nextNotEmpty()) { assert !pro_queue[i].isEmpty() : "try to pop a propagator from an empty queue"; + if (model.getSolver().isTimeLimitMet()) { + return; + } lastProp = pro_queue[i].pollFirst(); if (pro_queue[i].isEmpty()) { notEmpty &= ~(1 << i); diff --git a/solver/src/test/java/org/chocosolver/solver/LimitsTest.java b/solver/src/test/java/org/chocosolver/solver/LimitsTest.java index 92986894e6..607416eb56 100644 --- a/solver/src/test/java/org/chocosolver/solver/LimitsTest.java +++ b/solver/src/test/java/org/chocosolver/solver/LimitsTest.java @@ -9,12 +9,15 @@ */ package org.chocosolver.solver; +import org.chocosolver.solver.exception.SolverException; +import org.chocosolver.solver.variables.IntVar; import org.chocosolver.util.tools.TimeUtils; import org.testng.Assert; import org.testng.annotations.Test; import static org.chocosolver.util.ProblemMaker.makeNQueenWithBinaryConstraints; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; /** @@ -35,6 +38,35 @@ public void testTime() { assertTrue(tl - (tl * 5 / 100) <= tc && tc <= tl + (tl * 5 / 100), tl + " vs. " + tc); } + @Test(groups="1s", timeOut=60000) + public void testTime2() throws SolverException { + Model model = new Model(); + + IntVar v7 = model.intVar("@v7", IntVar.MIN_INT_BOUND, IntVar.MAX_INT_BOUND, true); + + model.post( + model.arithm(v7, ">", v7), + model.arithm(v7, "<", v7) + ); + // TODO : such a simple case should be detected within the constraint declaration + // TODO : this test might need to be changed if better model analysis is done during model declaration + + Solver solver = model.getSolver(); + solver.limitTime(250); + + long start = System.currentTimeMillis(); + boolean solved = solver.solve(); + long took = System.currentTimeMillis() - start; + + assertFalse(solved); + assertEquals(solver.getNodeCount(), 1); + assertEquals(solver.getBackTrackCount(), 0); + assertEquals(solver.getFailCount(), 0); + assertEquals(solver.getSolutionCount(), 0); + assertTrue(solver.isStopCriterionMet()); + assertTrue(took <= 1000); // less than 1 second + } + @Test(groups="1s", timeOut=60000) public void testNode() { Model s = makeNQueenWithBinaryConstraints(12); From b512e4dee15188d1455583ef4d8a0d67f7673d5c Mon Sep 17 00:00:00 2001 From: Arthur Godet Date: Mon, 9 Oct 2023 20:48:42 +0200 Subject: [PATCH 2/2] fix NullPointerException wrt TimeCounter in Solver --- solver/src/main/java/org/chocosolver/solver/Solver.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/solver/src/main/java/org/chocosolver/solver/Solver.java b/solver/src/main/java/org/chocosolver/solver/Solver.java index ad82c3fcee..0c2859e8b7 100644 --- a/solver/src/main/java/org/chocosolver/solver/Solver.java +++ b/solver/src/main/java/org/chocosolver/solver/Solver.java @@ -1014,7 +1014,10 @@ public boolean hasEndedUnexpectedly() { * @return true if the time limit is met. */ public boolean isTimeLimitMet() { - return timeCounter.isMet(); + if (timeCounter != null) { + return timeCounter.isMet(); + } + return false; } /**