From cf75588243bb8a78de97a5ae76599e10e3bd22f9 Mon Sep 17 00:00:00 2001 From: Arthur Godet Date: Tue, 9 Jan 2024 18:01:13 +0100 Subject: [PATCH] Fix PropCount_AC wrt bounded domains (#1076) * fix PropCount_AC wrt bounded domains * fix propagation condition in PropCount_AC --- .../constraints/nary/count/PropCount_AC.java | 10 +- .../solver/constraints/nary/CountTest.java | 103 ++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/solver/src/main/java/org/chocosolver/solver/constraints/nary/count/PropCount_AC.java b/solver/src/main/java/org/chocosolver/solver/constraints/nary/count/PropCount_AC.java index d2ace5bf81..16a74c80df 100644 --- a/solver/src/main/java/org/chocosolver/solver/constraints/nary/count/PropCount_AC.java +++ b/solver/src/main/java/org/chocosolver/solver/constraints/nary/count/PropCount_AC.java @@ -76,7 +76,7 @@ public String toString() { st.append("..., "); } st.append("limit=").append(vars[vars.length - 1].getName()); - st.append(", value=").append(value).append(')'); + st.append(", value=").append(value).append(')'); return st.toString(); } @@ -112,15 +112,17 @@ public void propagate(int varIdx, int mask) throws ContradictionException { if (possibles.contains(varIdx)) { if (!vars[varIdx].contains(value)) { possibles.remove(varIdx); - filter(); + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); } else if (vars[varIdx].isInstantiated()) { possibles.remove(varIdx); mandatories.add(varIdx); - filter(); + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); + } else if (!vars[varIdx].hasEnumeratedDomain() && (vars[varIdx].getLB() == value || vars[varIdx].getUB() == value)) { + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); } } } else { - filter(); + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); } } diff --git a/solver/src/test/java/org/chocosolver/solver/constraints/nary/CountTest.java b/solver/src/test/java/org/chocosolver/solver/constraints/nary/CountTest.java index 5308ff9e0c..158bf8aec2 100644 --- a/solver/src/test/java/org/chocosolver/solver/constraints/nary/CountTest.java +++ b/solver/src/test/java/org/chocosolver/solver/constraints/nary/CountTest.java @@ -9,14 +9,17 @@ */ package org.chocosolver.solver.constraints.nary; +import org.chocosolver.solver.Cause; import org.chocosolver.solver.Settings; import org.chocosolver.solver.Model; import org.chocosolver.solver.Solver; import org.chocosolver.solver.constraints.Constraint; import org.chocosolver.solver.constraints.extension.Tuples; +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.DataProvider; import org.testng.annotations.Test; import java.util.LinkedList; @@ -30,6 +33,7 @@ import static org.chocosolver.solver.search.strategy.Search.randomSearch; import static org.chocosolver.util.tools.ArrayUtils.append; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; /** *
@@ -249,4 +253,103 @@ public Constraint getDecomposition(Model model, IntVar[] vs, IntVar occ, int val return model.sum(bs, "=", occ); } + @DataProvider(name = "testFilteringToZeroProvider") + public static Object[][] testFilteringToZeroProvider() { + return new Object[][] {{true}, {false}}; + } + + @Test(groups="1s", timeOut=60000, dataProvider = "testFilteringToZeroProvider") + public void testFilteringToZero(Boolean boundedDomain) { + Model model = new Model(); + IntVar[] vars = model.intVarArray("vars", 10, 0, 10, boundedDomain); + IntVar count = model.intVar("count", 0, 10); + model.count(4, vars, count).post(); + + try { + model.getSolver().propagate(); + for (int i = 0; i < vars.length; i++) { + Assert.assertEquals(11, vars[i].getDomainSize()); + } + Assert.assertEquals(11, count.getDomainSize()); + } catch (ContradictionException ex) { + fail(); + } + + try { + for (int i = 1; i < vars.length; i++) { + vars[i].updateLowerBound(5, Cause.Null); + } + model.getSolver().propagate(); + for (int i = 0; i < vars.length; i++) { + if (i == 0) { + Assert.assertEquals(11, vars[i].getDomainSize()); + } else { + Assert.assertEquals(6, vars[i].getDomainSize()); + } + } + Assert.assertEquals(0, count.getLB()); + Assert.assertEquals(1, count.getUB()); + } catch (ContradictionException ex) { + fail(); + } + + try { + count.updateUpperBound(0, Cause.Null); + model.getSolver().propagate(); + for (int i = 0; i < vars.length; i++) { + if (i == 0) { + if (vars[i].hasEnumeratedDomain()) { + Assert.assertEquals(10, vars[i].getDomainSize()); + Assert.assertFalse(vars[i].contains(4)); + } else { + Assert.assertEquals(11, vars[i].getDomainSize()); + Assert.assertTrue(vars[i].contains(4)); + } + } else { + Assert.assertEquals(6, vars[i].getDomainSize()); + Assert.assertFalse(vars[i].contains(4)); + } + } + Assert.assertTrue(count.isInstantiatedTo(0)); + } catch (ContradictionException ex) { + fail(); + } + + try { + // To test (in debug mode), that we do not enter in PropCount_AC.filter() + for (int j = 1; j < 4; j++) { + vars[0].updateLowerBound(j, Cause.Null); + model.getSolver().propagate(); + for (int i = 0; i < vars.length; i++) { + if (i == 0) { + if (vars[i].hasEnumeratedDomain()) { + Assert.assertEquals(10 - j, vars[i].getDomainSize()); + Assert.assertFalse(vars[i].contains(4)); + } else { + Assert.assertEquals(11 - j, vars[i].getDomainSize()); + Assert.assertTrue(vars[i].contains(4)); + } + } else { + Assert.assertEquals(6, vars[i].getDomainSize()); + Assert.assertFalse(vars[i].contains(4)); + } + } + Assert.assertTrue(count.isInstantiatedTo(0)); + } + } catch (ContradictionException ex) { + fail(); + } + + try { + vars[0].updateLowerBound(4, Cause.Null); + model.getSolver().propagate(); + for (int i = 0; i < vars.length; i++) { + Assert.assertEquals(6, vars[i].getDomainSize()); + Assert.assertFalse(vars[i].contains(4)); + } + Assert.assertTrue(count.isInstantiatedTo(0)); + } catch (ContradictionException ex) { + fail(); + } + } }