From b5c2782ba5935f0ac3e478767b73954e1d3e9174 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 13 Jul 2026 16:23:51 +0200 Subject: [PATCH 1/2] Fix GCC solution loss during recomputation --- Makefile.in | 1 + changelog.in | 10 +++++ cmake/GecodeSources.cmake | 1 + gecode/int/gcc/dom-sup.hpp | 4 +- test/flatzinc/issue166.cpp | 75 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 test/flatzinc/issue166.cpp diff --git a/Makefile.in b/Makefile.in index ac07d4e96b..2762b72a40 100755 --- a/Makefile.in +++ b/Makefile.in @@ -1141,6 +1141,7 @@ FLATZINCTESTSRC0 = \ test/flatzinc/empty_domain_2.cpp \ test/flatzinc/int_set_as_type1.cpp \ test/flatzinc/int_set_as_type2.cpp \ + test/flatzinc/issue166.cpp \ test/flatzinc/jobshop.cpp \ test/flatzinc/no_warn_empty_domain.cpp \ test/flatzinc/output_test.cpp \ diff --git a/changelog.in b/changelog.in index a97b2d6660..b7c1bbd315 100755 --- a/changelog.in +++ b/changelog.in @@ -75,6 +75,16 @@ This release modernizes the Gecode build infrastructure, adds a first-class CMake package for downstream consumers, refreshes the autoconf build path, and updates CI coverage for current platforms. +[ENTRY] +Module: int +What: bug +Rank: major +Issue: 166 +Thanks: Mats Carlsson +[DESCRIPTION] +Fix domain-level global cardinality propagation losing solutions when search +recomputation applies several branch choices before propagation. + [ENTRY] Module: flatzinc What: new diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index 40a2ffddd9..088aa12457 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -293,6 +293,7 @@ set(GECODE_TEST_SOURCES test/flatzinc/golomb.cpp test/flatzinc/int_set_as_type1.cpp test/flatzinc/int_set_as_type2.cpp + test/flatzinc/issue166.cpp test/flatzinc/jobshop.cpp test/flatzinc/jobshop2x2.cpp test/flatzinc/knights.cpp diff --git a/gecode/int/gcc/dom-sup.hpp b/gecode/int/gcc/dom-sup.hpp index bbdca883b6..b32eb2d4f8 100644 --- a/gecode/int/gcc/dom-sup.hpp +++ b/gecode/int/gcc/dom-sup.hpp @@ -1417,7 +1417,8 @@ namespace Gecode { namespace Int { namespace GCC { ValNode* v = vars[i]->first()->getVal(); vars[i]->first()->free(bc); GECODE_ME_CHECK(x[i].eq(home, v->val)); - v->inc(); + if (bc == UBC) + v->inc(); } for (int i = n_val; i--; ) { @@ -1766,4 +1767,3 @@ namespace Gecode { namespace Int { namespace GCC { // STATISTICS: int-prop - diff --git a/test/flatzinc/issue166.cpp b/test/flatzinc/issue166.cpp new file mode 100644 index 0000000000..1856e9b441 --- /dev/null +++ b/test/flatzinc/issue166.cpp @@ -0,0 +1,75 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "test/flatzinc.hh" + +namespace Test { namespace FlatZinc { + + namespace { + /// Check that recomputation does not lose a solution + bool check(const std::string& output) { + const std::string separator = "----------\n"; + int solutions = 0; + for (std::string::size_type p = 0; + (p = output.find(separator,p)) != std::string::npos; + p += separator.size()) + solutions++; + return (solutions == 11) && + (output.find("A = 3;\nB = 1;\nC = 3;\nD = 1;\nE = 2;\nG = 3;\n") + != std::string::npos); + } + + /// Helper class to create and register test + class Create { + public: + /// Perform creation and registration + Create(void) { + (void) new FlatZincTest("Issue166", +"predicate gecode_global_cardinality(array [int] of var int: x, array [int] of int: cover, array [int] of var int: counts);\n\ +var 1..3: A :: output_var;\n\ +var 1..3: B :: output_var;\n\ +var 1..3: C :: output_var;\n\ +var 1..2: D :: output_var;\n\ +var 2..3: E :: output_var;\n\ +var 2..3: G :: output_var;\n\ +constraint gecode_global_cardinality([3,A,D,G,E],[1,2,3],[1,B,C]) :: domain;\n\ +solve :: int_search([D,A,E,B],input_order,indomain_random,complete) satisfy;\n", + "", true, {"-a", "-r", "0", "-c-d", "8"}, check); + } + }; + + Create c; + } + +}} + +// STATISTICS: test-flatzinc From ce31afdd5459d4d378aae8ea8b66800b506acf02 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Mon, 13 Jul 2026 21:44:42 +0200 Subject: [PATCH 2/2] Complete GCC solution-loss fix --- changelog.in | 5 +++-- gecode/int/gcc/dom-sup.hpp | 3 ++- test/flatzinc/issue166.cpp | 35 ++++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/changelog.in b/changelog.in index b7c1bbd315..e584560f51 100755 --- a/changelog.in +++ b/changelog.in @@ -82,8 +82,9 @@ Rank: major Issue: 166 Thanks: Mats Carlsson [DESCRIPTION] -Fix domain-level global cardinality propagation losing solutions when search -recomputation applies several branch choices before propagation. +Fix domain-level global cardinality propagation losing solutions by counting +assigned variables twice and retaining stale conflict marks when cardinality +bounds shrink. [ENTRY] Module: flatzinc diff --git a/gecode/int/gcc/dom-sup.hpp b/gecode/int/gcc/dom-sup.hpp index b32eb2d4f8..fba2584cf9 100644 --- a/gecode/int/gcc/dom-sup.hpp +++ b/gecode/int/gcc/dom-sup.hpp @@ -1268,7 +1268,8 @@ namespace Gecode { namespace Int { namespace GCC { v->maxlow(k[i].max() - (inc_lbc)); if (v->kmin() == v->kmax()) v->cap(LBC,k[i].max() - (inc_lbc)); - v->card_conflict(rm); + int matched = inc_ubc - v->kcount(); + v->card_conflict(std::min(rm, matched)); } } } diff --git a/test/flatzinc/issue166.cpp b/test/flatzinc/issue166.cpp index 1856e9b441..8af10b6fce 100644 --- a/test/flatzinc/issue166.cpp +++ b/test/flatzinc/issue166.cpp @@ -35,17 +35,17 @@ namespace Test { namespace FlatZinc { namespace { - /// Check that recomputation does not lose a solution - bool check(const std::string& output) { + /// Check that recomputation does not lose solutions + bool check(const std::string& output, int expected, + const std::string& representative) { const std::string separator = "----------\n"; int solutions = 0; for (std::string::size_type p = 0; (p = output.find(separator,p)) != std::string::npos; p += separator.size()) solutions++; - return (solutions == 11) && - (output.find("A = 3;\nB = 1;\nC = 3;\nD = 1;\nE = 2;\nG = 3;\n") - != std::string::npos); + return (solutions == expected) && + (output.find(representative) != std::string::npos); } /// Helper class to create and register test @@ -53,7 +53,24 @@ namespace Test { namespace FlatZinc { public: /// Perform creation and registration Create(void) { - (void) new FlatZincTest("Issue166", + (void) new FlatZincTest("Issue166::Original", +"predicate gecode_global_cardinality(array [int] of var int: x, array [int] of int: cover, array [int] of var int: counts);\n\ +var {1,3}: A :: output_var;\n\ +var 2..3: B :: output_var;\n\ +var 2..3: C :: output_var;\n\ +var 2..3: D :: output_var;\n\ +var 2..5: E :: output_var;\n\ +var 1..4: F :: output_var;\n\ +var 2..3: G :: output_var;\n\ +var 2..3: H :: output_var;\n\ +constraint gecode_global_cardinality([A,D,1,3,3,C,1,H,B],[1,2,3],[G,F,E]) :: domain;\n\ +solve :: int_search([A,B,C,D,E,F,G,H],anti_first_fail,indomain_min,complete) satisfy;\n", + "", true, {"-a"}, [] (const std::string& output) { + return check(output, 26, + "A = 1;\nB = 2;\nC = 3;\nD = 3;\nE = 5;\n" + "F = 1;\nG = 3;\nH = 3;\n"); + }); + (void) new FlatZincTest("Issue166::Minimized", "predicate gecode_global_cardinality(array [int] of var int: x, array [int] of int: cover, array [int] of var int: counts);\n\ var 1..3: A :: output_var;\n\ var 1..3: B :: output_var;\n\ @@ -63,7 +80,11 @@ var 2..3: E :: output_var;\n\ var 2..3: G :: output_var;\n\ constraint gecode_global_cardinality([3,A,D,G,E],[1,2,3],[1,B,C]) :: domain;\n\ solve :: int_search([D,A,E,B],input_order,indomain_random,complete) satisfy;\n", - "", true, {"-a", "-r", "0", "-c-d", "8"}, check); + "", true, {"-a", "-r", "0", "-c-d", "8"}, + [] (const std::string& output) { + return check(output, 11, + "A = 3;\nB = 1;\nC = 3;\nD = 1;\nE = 2;\nG = 3;\n"); + }); } };