From 05d184eef71babc6e6867aa4f70a2e8c525cc72d Mon Sep 17 00:00:00 2001 From: Fabio Tardivo Date: Sun, 15 Sep 2024 01:56:17 -0600 Subject: [PATCH 1/6] Add capacity method to DynamicArray --- gecode/support/dynamic-array.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gecode/support/dynamic-array.hpp b/gecode/support/dynamic-array.hpp index 1adfbe0d58..25583bf35e 100644 --- a/gecode/support/dynamic-array.hpp +++ b/gecode/support/dynamic-array.hpp @@ -75,6 +75,9 @@ namespace Gecode { namespace Support { /// Cast in to pointer of type \a T operator T*(void); + + /// Return the capacity of the array + int capacity(void) const; }; @@ -154,6 +157,12 @@ namespace Gecode { namespace Support { return x; } + template + forceinline int + DynamicArray::capacity() const { + return n; + } + }} // STATISTICS: support-any From 9d0d6bf04a3c985f903a596890903ec220c32a9e Mon Sep 17 00:00:00 2001 From: Fabio Tardivo Date: Sun, 15 Sep 2024 01:56:25 -0600 Subject: [PATCH 2/6] Add DFFs based lower bounds --- gecode/int/bin-packing.hh | 31 ++++ gecode/int/bin-packing/propagate.cpp | 233 +++++++++++++++++++-------- gecode/int/bin-packing/propagate.hpp | 179 ++++++++++++++++++++ 3 files changed, 380 insertions(+), 63 deletions(-) diff --git a/gecode/int/bin-packing.hh b/gecode/int/bin-packing.hh index 909e00be62..f5b2e41ca1 100755 --- a/gecode/int/bin-packing.hh +++ b/gecode/int/bin-packing.hh @@ -127,12 +127,18 @@ namespace Gecode { namespace Int { namespace BinPacking { int operator [](int i) const; }; + /// Range of lambda values + struct LambdaRange {int min; int max;}; + + /// Integer Dynamic Array + using IntDynamicArray = Support::DynamicArray; /** * \brief Bin-packing propagator * * The algorithm is taken from: * Paul Shaw. A Constraint for Bin Packing. CP 2004. + * Tardivo et al. CP for Bin Packing with Multi-Core and GPUs. CP 2024. * * Requires \code #include \endcode * @@ -175,6 +181,31 @@ namespace Gecode { namespace Int { namespace BinPacking { virtual Actor* copy(Space& home); /// Destructor virtual size_t dispose(Space& home); + /// Reductions + static int const nReductions = 3; + static void calcReductions(const ViewArray& bs, const ViewArray& l, IntDynamicArray & weightsBaseReduction, int & capacityBaseReduction, IntDynamicArray & deltaReductions); + /// Dual Feasible Functions + static int fCCM1(int w, int l, int c); + static int fMT(int w, int l, int c); + static int fBJ1(int w, int l, int c); + static int fVB2Base(int w, int l, int c); + static int fVB2(int w, int l, int c); + static int fFS1(int w, int l, int c); + static int fRAD2Base(int w, int l, int c); + static int fRAD2(int w, int l, int c); + static int const nLambdaSamples = 256; + static LambdaRange lCCM1(int c); + static LambdaRange lMT(int c); + static LambdaRange lBJ1(int c); + static LambdaRange lVB2(int c); + static LambdaRange lFS1(int c); + static LambdaRange lRAD2(int c); + static LambdaRange sanitizeLambdaRange(LambdaRange lambda, int nWeights, int maxWeight); + /// Lower bound + template + static int calcDffLowerboundSingleLambda(const IntDynamicArray & weights, int capacity, int lambda); + template + static int calcDffLowerbound(const IntDynamicArray & weights, int capacity, int nNotZeroWeights, int maxWeight, bool sanitize = false); }; diff --git a/gecode/int/bin-packing/propagate.cpp b/gecode/int/bin-packing/propagate.cpp index 9425fdd460..c5e14755da 100755 --- a/gecode/int/bin-packing/propagate.cpp +++ b/gecode/int/bin-packing/propagate.cpp @@ -278,77 +278,136 @@ namespace Gecode { namespace Int { namespace BinPacking { // Perform lower bound checking if (n > 0) { - // Find capacity estimate (we start from bs[0] as it might be - // not packable, actually (will be detected later anyway)! - int c = bs[0].size(); - for (int j=0; j(c+1); + // Count how many items have a certain size (bucket sort) + int* n_s = region.alloc(c+1); - for (int i=0; i l[j].max()) { - n_s[c - l[j].max()]++; nm++; - } + // Only count positive remaining bin loads + for (int j=0; j l[j].max()) { + n_s[c - l[j].max()]++; nm++; + } - // Sizes of items and remaining bin loads - int* s = region.alloc(nm); + // Sizes of items and remaining bin loads + int* s = region.alloc(nm); - // Setup sorted sizes - { - int k=0; - for (int i=c+1; i--; ) - for (int j=n_s[i]; j--; ) - s[k++]=i; - assert(k == nm); - } + // Setup sorted sizes + { + int k=0; + for (int i=c+1; i--; ) + for (int j=n_s[i]; j--; ) + s[k++]=i; + assert(k == nm); + } - // Items in N1 are from 0 ... n1 - 1 - int n1 = 0; - // Items in N2 are from n1 ... n12 - 1, we count elements in N1 and N2 - int n12 = 0; - // Items in N3 are from n12 ... n3 - 1 - int n3 = 0; - // Free space in N2 - int f2 = 0; - // Total size of items in N3 - int s3 = 0; - - // Initialize n12 and f2 - for (; (n12 < nm) && (s[n12] > c/2); n12++) - f2 += c - s[n12]; - - // Initialize n3 and s3 - for (n3 = n12; n3 < nm; n3++) - s3 += s[n3]; - - // Compute lower bounds - for (int k=0; k<=c/2; k++) { - // Make N1 larger by adding elements and N2 smaller - for (; (n1 < nm) && (s[n1] > c-k); n1++) - f2 -= c - s[n1]; - assert(n1 <= n12); - // Make N3 smaller by removing elements - for (; (s[n3-1] < k) && (n3 > n12); n3--) - s3 -= s[n3-1]; - // Overspill - int o = (s3 > f2) ? ((s3 - f2 + c - 1) / c) : 0; - if (n12 + o > m) - return ES_FAILED; + // Items in N1 are from 0 ... n1 - 1 + int n1 = 0; + // Items in N2 are from n1 ... n12 - 1, we count elements in N1 and N2 + int n12 = 0; + // Items in N3 are from n12 ... n3 - 1 + int n3 = 0; + // Free space in N2 + int f2 = 0; + // Total size of items in N3 + int s3 = 0; + + // Initialize n12 and f2 + for (; (n12 < nm) && (s[n12] > c/2); n12++) + f2 += c - s[n12]; + + // Initialize n3 and s3 + for (n3 = n12; n3 < nm; n3++) + s3 += s[n3]; + + // Compute lower bounds + for (int k=0; k<=c/2; k++) { + // Make N1 larger by adding elements and N2 smaller + for (; (n1 < nm) && (s[n1] > c-k); n1++) + f2 -= c - s[n1]; + assert(n1 <= n12); + // Make N3 smaller by removing elements + for (; (s[n3-1] < k) && (n3 > n12); n3--) + s3 -= s[n3-1]; + // Overspill + int o = (s3 > f2) ? ((s3 - f2 + c - 1) / c) : 0; + if (n12 + o > m) + return ES_FAILED; + } + } + else + { + // Allocate auxiliary data + int nBins = l.size(); + int nWeightsReductions = nBins + bs.size(); + IntDynamicArray weightsBaseReduction(region, nWeightsReductions); + IntDynamicArray weightsCurrentReduction(region, nWeightsReductions); + IntDynamicArray deltaReductions(region, nReductions); + + // Initialize reductions + int capacityBaseReduction = 0; + calcReductions(bs, l, weightsBaseReduction, capacityBaseReduction, deltaReductions); + + for (int rIdx = 0; rIdx < nReductions; rIdx += 1) + { + // Calculate reduction parameters + int & delta = deltaReductions[rIdx]; + int capacity = capacityBaseReduction + delta; + + if (capacity > 0) + { + // Calculate reduction + int nNotZeroWeights = 0; + int maxWeight = 0; + IntDynamicArray & weights = weightsCurrentReduction; + for (int wIdx = 0; wIdx < nWeightsReductions; wIdx += 1) + { + int weight = weightsBaseReduction[wIdx] + (wIdx < nBins ? delta : 0); + nNotZeroWeights += weight != 0; + maxWeight = std::max(maxWeight, weight); + weights[wIdx] = weight; + } + + // Check lowerbounds + int lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); + if (lowerbound > nBins) return ES_FAILED; + + lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); + if (lowerbound > nBins) return ES_FAILED; + + lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); + if (lowerbound > nBins) return ES_FAILED; + + lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight, true); + if (lowerbound > nBins) return ES_FAILED; + + lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight, true); + if (lowerbound > nBins) return ES_FAILED; + + lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); + if (lowerbound > nBins) return ES_FAILED; + } + } } region.free(); } @@ -394,6 +453,54 @@ namespace Gecode { namespace Int { namespace BinPacking { } } + void + Pack::calcReductions(const ViewArray& bs, const ViewArray& l, IntDynamicArray & weightsBaseReduction, int & capacityBaseReduction, IntDynamicArray & deltaReductions) + { + // Reset values + int nWeightsReductions = weightsBaseReduction.capacity(); + for (int wIdx = 0; wIdx < nWeightsReductions; wIdx += 1) + { + weightsBaseReduction[wIdx] = 0; + } + + // R0 + int nBins = l.size(); + int nItems = bs.size(); + capacityBaseReduction = 0; + for (int bIdx = 0; bIdx < nBins; bIdx += 1) + { + capacityBaseReduction = std::max(capacityBaseReduction, l[bIdx].max()); + } + for (int bIdx = 0; bIdx < nBins; bIdx += 1) + { + weightsBaseReduction[bIdx] = capacityBaseReduction - l[bIdx].max(); + } + for (int iIdx = 0; iIdx < nItems; iIdx += 1) + { + bool iAssigned = bs[iIdx].bin().assigned(); + int iWeight = bs[iIdx].size(); + if (iAssigned) + { + int bIdx = bs[iIdx].bin().val(); + weightsBaseReduction[bIdx] += iWeight; + } + else + { + weightsBaseReduction[nBins + iIdx] = iWeight; + } + } + + // RMin, RMax + int smallestVirtualWeight = std::numeric_limits::max(); + for (auto bIdx = 0; bIdx < nBins; bIdx += 1) + { + smallestVirtualWeight = std::min(smallestVirtualWeight, weightsBaseReduction[bIdx]); + } + deltaReductions[0] = -smallestVirtualWeight; + deltaReductions[1] = 0; + deltaReductions[2] = capacityBaseReduction - 2 * smallestVirtualWeight + 1; + } + }}} // STATISTICS: int-prop diff --git a/gecode/int/bin-packing/propagate.hpp b/gecode/int/bin-packing/propagate.hpp index 36241a3a0d..ab09e0b7d3 100755 --- a/gecode/int/bin-packing/propagate.hpp +++ b/gecode/int/bin-packing/propagate.hpp @@ -208,6 +208,185 @@ namespace Gecode { namespace Int { namespace BinPacking { return nosum(s, a, b, ap, bp); } + forceinline int + Pack::fCCM1(int w, int l, int c) + { + // Conditions + int const c0 = 2 * w > c; // x > c / 2 + int const c1 = 2 * w == c; // x == c / 2 + int const c2 = 2 * w < c; // x < c / 2 + + // Values + int const v0 = 2 * ((c / l) - ((c - w) / l)); + int const v1 = c / l; + int const v2 = 2 * (w / l); + + return (c0 * v0) + (c1 * v1) + (c2 * v2); + } + + forceinline int + Pack::fMT(int w, int l, int c) + { + // Conditions + int const c0 = w < l; + int const c1 = l <= w and w <= c - l; + int const c2 = c - l < w; + + // Values + int const v0 = 0; + int const v1 = w; + int const v2 = c; + + return (c0 * v0) + (c1 * v1) + (c2 * v2); + } + + forceinline int + Pack::fBJ1(int w, int l, int c) + { + // Auxiliary values + int const p = l - (c % l); + + // Conditions + int const c0 = w % l <= c % l; + int const c1 = w % l > c % l; + + // Values + int const v0 = (w / l) * p; + int const v1 = (w / l) * p + (w % l) - (c % l); + + return (c0 * v0) + (c1 * v1); + } + + forceinline int + Pack::fVB2Base(int w, int l, int c) + { + int v = ceil_div_pp(l * w, c); + return v > 0 ? v - 1 : 0; + } + + forceinline int + Pack::fVB2(int w, int l, int c) + { + int const c0 = 2 * w > c; + int const c1 = 2 * w == c; + int const c2 = 2 * w < c; + + int const t0 = fVB2Base(c, l, c); + int const t1 = fVB2Base(w, l, c); + int const t2 = fVB2Base(c - w, l, c); + + int const v0 = 2 * t0 - 2 * t2; + int const v1 = t0; + int const v2 = 2 * t1; + + return (c0 * v0) + (c1 * v1) + (c2 * v2); + } + + forceinline int + Pack::fFS1(int w, int l, int c) + { + // Conditions + int c0 = w * (l + 1) % c == 0; + int c1 = w * (l + 1) % c != 0; + + // Values + int v0 = w * l; + int v1 = ((w * (l + 1)) / c) * c; + + return (c0 * v0) + (c1 * v1); + } + + forceinline int + Pack::fRAD2Base(int w, int l, int c) + { + // Conditions + int const c0 = w < l; + int const c1 = l <= w and w <= c - 2 * l; + int const c2 = c - 2 * l < w and w < 2 * l; + + // Values + int const v0 = 0; + int const v1 = c / 3; + int const v2 = c / 2; + + return (c0 * v0) + (c1 * v1) + (c2 * v2); + } + + forceinline int + Pack::fRAD2(int w, int l, int c) + { + // Conditions + int const c0 = w < 2 * l; + int const c1 = 2 * l <= w; + + // Values + int const v0 = fRAD2Base(w, l, c); + int const v1 = c - fRAD2Base(c - w, l, c); + + return (c0 * v0) + (c1 * v1); + } + + forceinline LambdaRange Pack::lCCM1(int c) {return {1, c / 2};}; + + forceinline LambdaRange Pack::lMT(int c) {return {0, c / 2};}; // The 0 value is included to calculate the L0 bound + + forceinline LambdaRange Pack::lBJ1(int c) {return {1, c};} + + forceinline LambdaRange Pack::lVB2(int c) {return {2, c};}; + + forceinline LambdaRange Pack::lFS1(int c) {return {1, 100};}; + + forceinline LambdaRange Pack::lRAD2(int c) {return {c / 4 + 1, c / 3};}; + + forceinline LambdaRange + Pack::sanitizeLambdaRange(LambdaRange lambdaRange, int nNotZeroWeights, int maxWeight) + { + if (nNotZeroWeights * maxWeight != 0) + { + int lMax = std::min(std::numeric_limits::max() / (nNotZeroWeights * maxWeight), lambdaRange.max); + return {lambdaRange.min, lMax}; + } + else + { + return {0,-1}; + } + } + + template + forceinline int + Pack::calcDffLowerboundSingleLambda(const IntDynamicArray& weights, int capacity, int lambda) + { + // Transform and sum the weights + int sumTransformedWeights = 0; + int nWeights = weights.capacity(); + for (int wIdx = 0; wIdx < nWeights; wIdx += 1) + { + sumTransformedWeights += f(weights[wIdx], lambda, capacity); + } + + // Transform the capacity + int transformedCapacity = f(capacity, lambda, capacity); + + // Lowerbound + return ceil_div_pp(sumTransformedWeights, transformedCapacity); + } + + template + forceinline int + Pack::calcDffLowerbound(const IntDynamicArray& weights, int capacity, int nNotZeroWeights, int maxWeight, bool sanitize) + { + int fLowerbound = 0; + LambdaRange lambdaRange = l(capacity); + lambdaRange = sanitize ? sanitizeLambdaRange(lambdaRange, nNotZeroWeights, maxWeight) : lambdaRange; + int lStep = ceil_div_pp(lambdaRange.max - lambdaRange.min + 1, nLambdaSamples + 1); + for (int lambda = lambdaRange.min + lStep; lambda < lambdaRange.max; lambda += lStep) + { + int lowerbound = calcDffLowerboundSingleLambda(weights, capacity, lambda); + fLowerbound = std::max(fLowerbound, lowerbound); + } + return fLowerbound; + } + }}} // STATISTICS: int-prop From e49b8d71938bda63ccf7fb095e850ba8731f7b76 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 7 Jul 2026 10:23:39 +0200 Subject: [PATCH 3/6] Integrate bin-packing DFF propagation --- changelog.in | 12 ++ gecode/int/bin-packing.hh | 61 +++--- gecode/int/bin-packing/propagate.cpp | 271 +++++++++++---------------- gecode/int/bin-packing/propagate.hpp | 188 +++++++++++-------- gecode/support/dynamic-array.hpp | 9 - test/int/bin-packing.cpp | 34 +++- 6 files changed, 294 insertions(+), 281 deletions(-) diff --git a/changelog.in b/changelog.in index 900229276c..aacd98578f 100755 --- a/changelog.in +++ b/changelog.in @@ -152,6 +152,7 @@ initializing their exponent fields and rejecting nth-root test assignments whose source interval is not fully non-negative. [ENTRY] +<<<<<<< HEAD Module: flatzinc What: bug Rank: minor @@ -257,6 +258,17 @@ The new fault-injection tests exercise clone, dispose-notice, IntSet, MiniModel, and heap-allocation failure paths that are otherwise hard to reproduce reliably. +[ENTRY] +Module: int +What: performance +Rank: minor +Issue: 202 +Thanks: Fabio Tardivo +[DESCRIPTION] +Strengthen bin-packing propagation with lower bounds based on +dual-feasible functions, improving early detection of infeasible +packing states. + [ENTRY] Module: other What: change diff --git a/gecode/int/bin-packing.hh b/gecode/int/bin-packing.hh index f5b2e41ca1..54be725a98 100755 --- a/gecode/int/bin-packing.hh +++ b/gecode/int/bin-packing.hh @@ -4,9 +4,11 @@ * Christian Schulte * * Contributing authors: + * Fabio Tardivo * Stefano Gualandi * * Copyright: + * Fabio Tardivo, 2024 * Stefano Gualandi, 2013 * Christian Schulte, 2010 * @@ -127,11 +129,11 @@ namespace Gecode { namespace Int { namespace BinPacking { int operator [](int i) const; }; - /// Range of lambda values - struct LambdaRange {int min; int max;}; - - /// Integer Dynamic Array - using IntDynamicArray = Support::DynamicArray; + /// Range of lambda values + struct LambdaRange { + int min; + int max; + }; /** * \brief Bin-packing propagator @@ -182,30 +184,39 @@ namespace Gecode { namespace Int { namespace BinPacking { /// Destructor virtual size_t dispose(Space& home); /// Reductions - static int const nReductions = 3; - static void calcReductions(const ViewArray& bs, const ViewArray& l, IntDynamicArray & weightsBaseReduction, int & capacityBaseReduction, IntDynamicArray & deltaReductions); + static int const n_reductions = 3; + static void calc_reductions(const ViewArray& bs, + const ViewArray& l, + int* weights_base_reduction, + int& capacity_base_reduction, + int* delta_reductions); /// Dual Feasible Functions - static int fCCM1(int w, int l, int c); - static int fMT(int w, int l, int c); - static int fBJ1(int w, int l, int c); - static int fVB2Base(int w, int l, int c); - static int fVB2(int w, int l, int c); - static int fFS1(int w, int l, int c); - static int fRAD2Base(int w, int l, int c); - static int fRAD2(int w, int l, int c); - static int const nLambdaSamples = 256; - static LambdaRange lCCM1(int c); - static LambdaRange lMT(int c); - static LambdaRange lBJ1(int c); - static LambdaRange lVB2(int c); - static LambdaRange lFS1(int c); - static LambdaRange lRAD2(int c); - static LambdaRange sanitizeLambdaRange(LambdaRange lambda, int nWeights, int maxWeight); + static int f_ccm1(int w, int l, int c); + static int f_mt(int w, int l, int c); + static int f_bj1(int w, int l, int c); + static int f_vb2_base(int w, int l, int c); + static int f_vb2(int w, int l, int c); + static int f_fs1(int w, int l, int c); + static int f_rad2_base(int w, int l, int c); + static int f_rad2(int w, int l, int c); + static int const n_lambda_samples = 256; + static LambdaRange l_ccm1(int c); + static LambdaRange l_mt(int c); + static LambdaRange l_bj1(int c); + static LambdaRange l_vb2(int c); + static LambdaRange l_fs1(int c); + static LambdaRange l_rad2(int c); + static LambdaRange sanitize_lambda_range(LambdaRange lambda, + int n_weights, int max_weight); /// Lower bound template - static int calcDffLowerboundSingleLambda(const IntDynamicArray & weights, int capacity, int lambda); + static int calc_dff_lower_bound_single_lambda(const int* weights, + int n_weights, + int capacity, int lambda); template - static int calcDffLowerbound(const IntDynamicArray & weights, int capacity, int nNotZeroWeights, int maxWeight, bool sanitize = false); + static int calc_dff_lower_bound(const int* weights, int n_weights, + int capacity, int n_not_zero_weights, + int max_weight, bool sanitize = false); }; diff --git a/gecode/int/bin-packing/propagate.cpp b/gecode/int/bin-packing/propagate.cpp index c5e14755da..43c1beb6a2 100755 --- a/gecode/int/bin-packing/propagate.cpp +++ b/gecode/int/bin-packing/propagate.cpp @@ -3,7 +3,11 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Fabio Tardivo + * * Copyright: + * Fabio Tardivo, 2024 * Christian Schulte, 2010 * * This file is part of Gecode, the generic constraint @@ -278,134 +282,81 @@ namespace Gecode { namespace Int { namespace BinPacking { // Perform lower bound checking if (n > 0) { - bool useDffLowerbounds = true; - if (not useDffLowerbounds) - { - - // Find capacity estimate (we start from bs[0] as it might be - // not packable, actually (will be detected later anyway)! - int c = bs[0].size(); - for (int j=0; j(c+1); - - for (int i=0; i l[j].max()) { - n_s[c - l[j].max()]++; nm++; - } - - // Sizes of items and remaining bin loads - int* s = region.alloc(nm); - - // Setup sorted sizes - { - int k=0; - for (int i=c+1; i--; ) - for (int j=n_s[i]; j--; ) - s[k++]=i; - assert(k == nm); - } - - // Items in N1 are from 0 ... n1 - 1 - int n1 = 0; - // Items in N2 are from n1 ... n12 - 1, we count elements in N1 and N2 - int n12 = 0; - // Items in N3 are from n12 ... n3 - 1 - int n3 = 0; - // Free space in N2 - int f2 = 0; - // Total size of items in N3 - int s3 = 0; - - // Initialize n12 and f2 - for (; (n12 < nm) && (s[n12] > c/2); n12++) - f2 += c - s[n12]; - - // Initialize n3 and s3 - for (n3 = n12; n3 < nm; n3++) - s3 += s[n3]; - - // Compute lower bounds - for (int k=0; k<=c/2; k++) { - // Make N1 larger by adding elements and N2 smaller - for (; (n1 < nm) && (s[n1] > c-k); n1++) - f2 -= c - s[n1]; - assert(n1 <= n12); - // Make N3 smaller by removing elements - for (; (s[n3-1] < k) && (n3 > n12); n3--) - s3 -= s[n3-1]; - // Overspill - int o = (s3 > f2) ? ((s3 - f2 + c - 1) / c) : 0; - if (n12 + o > m) - return ES_FAILED; - } - } - else { // Allocate auxiliary data - int nBins = l.size(); - int nWeightsReductions = nBins + bs.size(); - IntDynamicArray weightsBaseReduction(region, nWeightsReductions); - IntDynamicArray weightsCurrentReduction(region, nWeightsReductions); - IntDynamicArray deltaReductions(region, nReductions); + int n_bins = l.size(); + int n_weights_reductions = n_bins + bs.size(); + int* weights_base_reduction = + region.alloc(n_weights_reductions); + int* weights_current_reduction = + region.alloc(n_weights_reductions); + int* delta_reductions = region.alloc(n_reductions); // Initialize reductions - int capacityBaseReduction = 0; - calcReductions(bs, l, weightsBaseReduction, capacityBaseReduction, deltaReductions); + int capacity_base_reduction = 0; + calc_reductions(bs, l, weights_base_reduction, + capacity_base_reduction, delta_reductions); - for (int rIdx = 0; rIdx < nReductions; rIdx += 1) - { + for (int r_idx = 0; r_idx < n_reductions; r_idx += 1) { // Calculate reduction parameters - int & delta = deltaReductions[rIdx]; - int capacity = capacityBaseReduction + delta; + int& delta = delta_reductions[r_idx]; + int capacity = capacity_base_reduction + delta; - if (capacity > 0) - { + if (capacity > 0) { // Calculate reduction - int nNotZeroWeights = 0; - int maxWeight = 0; - IntDynamicArray & weights = weightsCurrentReduction; - for (int wIdx = 0; wIdx < nWeightsReductions; wIdx += 1) - { - int weight = weightsBaseReduction[wIdx] + (wIdx < nBins ? delta : 0); - nNotZeroWeights += weight != 0; - maxWeight = std::max(maxWeight, weight); - weights[wIdx] = weight; + int n_not_zero_weights = 0; + int max_weight = 0; + int* weights = weights_current_reduction; + for (int w_idx = 0; w_idx < n_weights_reductions; w_idx += 1) { + int weight = + weights_base_reduction[w_idx] + (w_idx < n_bins ? delta : 0); + n_not_zero_weights += weight != 0; + max_weight = std::max(max_weight, weight); + weights[w_idx] = weight; } - // Check lowerbounds - int lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); - if (lowerbound > nBins) return ES_FAILED; - - lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); - if (lowerbound > nBins) return ES_FAILED; - - lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); - if (lowerbound > nBins) return ES_FAILED; - - lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight, true); - if (lowerbound > nBins) return ES_FAILED; - - lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight, true); - if (lowerbound > nBins) return ES_FAILED; - - lowerbound = calcDffLowerbound(weights, capacity, nNotZeroWeights, maxWeight); - if (lowerbound > nBins) return ES_FAILED; + // Check lower bounds + int lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight, true); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight, true); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; } } } @@ -453,55 +404,49 @@ namespace Gecode { namespace Int { namespace BinPacking { } } - void - Pack::calcReductions(const ViewArray& bs, const ViewArray& l, IntDynamicArray & weightsBaseReduction, int & capacityBaseReduction, IntDynamicArray & deltaReductions) - { - // Reset values - int nWeightsReductions = weightsBaseReduction.capacity(); - for (int wIdx = 0; wIdx < nWeightsReductions; wIdx += 1) - { - weightsBaseReduction[wIdx] = 0; - } - - // R0 - int nBins = l.size(); - int nItems = bs.size(); - capacityBaseReduction = 0; - for (int bIdx = 0; bIdx < nBins; bIdx += 1) - { - capacityBaseReduction = std::max(capacityBaseReduction, l[bIdx].max()); - } - for (int bIdx = 0; bIdx < nBins; bIdx += 1) - { - weightsBaseReduction[bIdx] = capacityBaseReduction - l[bIdx].max(); - } - for (int iIdx = 0; iIdx < nItems; iIdx += 1) - { - bool iAssigned = bs[iIdx].bin().assigned(); - int iWeight = bs[iIdx].size(); - if (iAssigned) - { - int bIdx = bs[iIdx].bin().val(); - weightsBaseReduction[bIdx] += iWeight; - } - else - { - weightsBaseReduction[nBins + iIdx] = iWeight; - } + void + Pack::calc_reductions(const ViewArray& bs, + const ViewArray& l, + int* weights_base_reduction, + int& capacity_base_reduction, + int* delta_reductions) { + // Reset values + int n_weights_reductions = l.size() + bs.size(); + for (int w_idx = 0; w_idx < n_weights_reductions; w_idx += 1) + weights_base_reduction[w_idx] = 0; + + // R0 + int n_bins = l.size(); + int n_items = bs.size(); + capacity_base_reduction = 0; + for (int b_idx = 0; b_idx < n_bins; b_idx += 1) + capacity_base_reduction = + std::max(capacity_base_reduction, l[b_idx].max()); + for (int b_idx = 0; b_idx < n_bins; b_idx += 1) + weights_base_reduction[b_idx] = + capacity_base_reduction - l[b_idx].max(); + for (int i_idx = 0; i_idx < n_items; i_idx += 1) { + bool i_assigned = bs[i_idx].bin().assigned(); + int i_weight = bs[i_idx].size(); + if (i_assigned) { + int b_idx = bs[i_idx].bin().val(); + weights_base_reduction[b_idx] += i_weight; + } else { + weights_base_reduction[n_bins + i_idx] = i_weight; } + } - // RMin, RMax - int smallestVirtualWeight = std::numeric_limits::max(); - for (auto bIdx = 0; bIdx < nBins; bIdx += 1) - { - smallestVirtualWeight = std::min(smallestVirtualWeight, weightsBaseReduction[bIdx]); - } - deltaReductions[0] = -smallestVirtualWeight; - deltaReductions[1] = 0; - deltaReductions[2] = capacityBaseReduction - 2 * smallestVirtualWeight + 1; + // RMin, RMax + int smallest_virtual_weight = std::numeric_limits::max(); + for (int b_idx = 0; b_idx < n_bins; b_idx += 1) + smallest_virtual_weight = + std::min(smallest_virtual_weight, weights_base_reduction[b_idx]); + delta_reductions[0] = -smallest_virtual_weight; + delta_reductions[1] = 0; + delta_reductions[2] = + capacity_base_reduction - 2 * smallest_virtual_weight + 1; } }}} // STATISTICS: int-prop - diff --git a/gecode/int/bin-packing/propagate.hpp b/gecode/int/bin-packing/propagate.hpp index ab09e0b7d3..1c6e41eb98 100755 --- a/gecode/int/bin-packing/propagate.hpp +++ b/gecode/int/bin-packing/propagate.hpp @@ -3,7 +3,11 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Fabio Tardivo + * * Copyright: + * Fabio Tardivo, 2024 * Christian Schulte, 2010 * * This file is part of Gecode, the generic constraint @@ -208,9 +212,8 @@ namespace Gecode { namespace Int { namespace BinPacking { return nosum(s, a, b, ap, bp); } - forceinline int - Pack::fCCM1(int w, int l, int c) - { + forceinline int + Pack::f_ccm1(int w, int l, int c) { // Conditions int const c0 = 2 * w > c; // x > c / 2 int const c1 = 2 * w == c; // x == c / 2 @@ -224,12 +227,11 @@ namespace Gecode { namespace Int { namespace BinPacking { return (c0 * v0) + (c1 * v1) + (c2 * v2); } - forceinline int - Pack::fMT(int w, int l, int c) - { + forceinline int + Pack::f_mt(int w, int l, int c) { // Conditions int const c0 = w < l; - int const c1 = l <= w and w <= c - l; + int const c1 = (l <= w) && (w <= c - l); int const c2 = c - l < w; // Values @@ -240,9 +242,8 @@ namespace Gecode { namespace Int { namespace BinPacking { return (c0 * v0) + (c1 * v1) + (c2 * v2); } - forceinline int - Pack::fBJ1(int w, int l, int c) - { + forceinline int + Pack::f_bj1(int w, int l, int c) { // Auxiliary values int const p = l - (c % l); @@ -257,23 +258,21 @@ namespace Gecode { namespace Int { namespace BinPacking { return (c0 * v0) + (c1 * v1); } - forceinline int - Pack::fVB2Base(int w, int l, int c) - { - int v = ceil_div_pp(l * w, c); - return v > 0 ? v - 1 : 0; + forceinline int + Pack::f_vb2_base(int w, int l, int c) { + int v = ceil_div_pp(l * w, c); + return v > 0 ? v - 1 : 0; } - forceinline int - Pack::fVB2(int w, int l, int c) - { + forceinline int + Pack::f_vb2(int w, int l, int c) { int const c0 = 2 * w > c; int const c1 = 2 * w == c; int const c2 = 2 * w < c; - int const t0 = fVB2Base(c, l, c); - int const t1 = fVB2Base(w, l, c); - int const t2 = fVB2Base(c - w, l, c); + int const t0 = f_vb2_base(c, l, c); + int const t1 = f_vb2_base(w, l, c); + int const t2 = f_vb2_base(c - w, l, c); int const v0 = 2 * t0 - 2 * t2; int const v1 = t0; @@ -282,27 +281,25 @@ namespace Gecode { namespace Int { namespace BinPacking { return (c0 * v0) + (c1 * v1) + (c2 * v2); } - forceinline int - Pack::fFS1(int w, int l, int c) - { + forceinline int + Pack::f_fs1(int w, int l, int c) { // Conditions - int c0 = w * (l + 1) % c == 0; - int c1 = w * (l + 1) % c != 0; + int const c0 = w * (l + 1) % c == 0; + int const c1 = w * (l + 1) % c != 0; // Values - int v0 = w * l; - int v1 = ((w * (l + 1)) / c) * c; + int const v0 = w * l; + int const v1 = ((w * (l + 1)) / c) * c; return (c0 * v0) + (c1 * v1); } - forceinline int - Pack::fRAD2Base(int w, int l, int c) - { + forceinline int + Pack::f_rad2_base(int w, int l, int c) { // Conditions int const c0 = w < l; - int const c1 = l <= w and w <= c - 2 * l; - int const c2 = c - 2 * l < w and w < 2 * l; + int const c1 = (l <= w) && (w <= c - 2 * l); + int const c2 = (c - 2 * l < w) && (w < 2 * l); // Values int const v0 = 0; @@ -312,82 +309,107 @@ namespace Gecode { namespace Int { namespace BinPacking { return (c0 * v0) + (c1 * v1) + (c2 * v2); } - forceinline int - Pack::fRAD2(int w, int l, int c) - { + forceinline int + Pack::f_rad2(int w, int l, int c) { // Conditions int const c0 = w < 2 * l; int const c1 = 2 * l <= w; // Values - int const v0 = fRAD2Base(w, l, c); - int const v1 = c - fRAD2Base(c - w, l, c); + int const v0 = f_rad2_base(w, l, c); + int const v1 = c - f_rad2_base(c - w, l, c); return (c0 * v0) + (c1 * v1); } - forceinline LambdaRange Pack::lCCM1(int c) {return {1, c / 2};}; + forceinline LambdaRange + Pack::l_ccm1(int c) { + return {1, c / 2}; + } - forceinline LambdaRange Pack::lMT(int c) {return {0, c / 2};}; // The 0 value is included to calculate the L0 bound + forceinline LambdaRange + Pack::l_mt(int c) { + return {0, c / 2}; + } - forceinline LambdaRange Pack::lBJ1(int c) {return {1, c};} + forceinline LambdaRange + Pack::l_bj1(int c) { + return {1, c}; + } - forceinline LambdaRange Pack::lVB2(int c) {return {2, c};}; + forceinline LambdaRange + Pack::l_vb2(int c) { + return {2, c}; + } - forceinline LambdaRange Pack::lFS1(int c) {return {1, 100};}; + forceinline LambdaRange + Pack::l_fs1(int) { + return {1, 100}; + } - forceinline LambdaRange Pack::lRAD2(int c) {return {c / 4 + 1, c / 3};}; + forceinline LambdaRange + Pack::l_rad2(int c) { + return {c / 4 + 1, c / 3}; + } - forceinline LambdaRange - Pack::sanitizeLambdaRange(LambdaRange lambdaRange, int nNotZeroWeights, int maxWeight) - { - if (nNotZeroWeights * maxWeight != 0) - { - int lMax = std::min(std::numeric_limits::max() / (nNotZeroWeights * maxWeight), lambdaRange.max); - return {lambdaRange.min, lMax}; - } - else - { - return {0,-1}; + forceinline LambdaRange + Pack::sanitize_lambda_range(LambdaRange lambda_range, + int n_not_zero_weights, int max_weight) { + long long int n = static_cast(n_not_zero_weights) * + static_cast(max_weight); + if (n != 0) { + int l_max = + std::min(static_cast(std::numeric_limits::max() / n), + lambda_range.max); + return {lambda_range.min, l_max}; + } else { + return {0, -1}; } } template - forceinline int - Pack::calcDffLowerboundSingleLambda(const IntDynamicArray& weights, int capacity, int lambda) - { - // Transform and sum the weights - int sumTransformedWeights = 0; - int nWeights = weights.capacity(); - for (int wIdx = 0; wIdx < nWeights; wIdx += 1) - { - sumTransformedWeights += f(weights[wIdx], lambda, capacity); - } + forceinline int + Pack::calc_dff_lower_bound_single_lambda(const int* weights, int n_weights, + int capacity, int lambda) { + // Transform and sum the weights + int sum_transformed_weights = 0; + for (int w_idx = 0; w_idx < n_weights; w_idx += 1) + sum_transformed_weights += f(weights[w_idx], lambda, capacity); - // Transform the capacity - int transformedCapacity = f(capacity, lambda, capacity); + // Transform the capacity + int transformed_capacity = f(capacity, lambda, capacity); - // Lowerbound - return ceil_div_pp(sumTransformedWeights, transformedCapacity); + // Lower bound + return ceil_div_pp(sum_transformed_weights, transformed_capacity); } template - forceinline int - Pack::calcDffLowerbound(const IntDynamicArray& weights, int capacity, int nNotZeroWeights, int maxWeight, bool sanitize) - { - int fLowerbound = 0; - LambdaRange lambdaRange = l(capacity); - lambdaRange = sanitize ? sanitizeLambdaRange(lambdaRange, nNotZeroWeights, maxWeight) : lambdaRange; - int lStep = ceil_div_pp(lambdaRange.max - lambdaRange.min + 1, nLambdaSamples + 1); - for (int lambda = lambdaRange.min + lStep; lambda < lambdaRange.max; lambda += lStep) - { - int lowerbound = calcDffLowerboundSingleLambda(weights, capacity, lambda); - fLowerbound = std::max(fLowerbound, lowerbound); - } - return fLowerbound; + forceinline int + Pack::calc_dff_lower_bound(const int* weights, int n_weights, + int capacity, int n_not_zero_weights, + int max_weight, bool sanitize) { + LambdaRange lambda_range = l(capacity); + lambda_range = + sanitize ? sanitize_lambda_range(lambda_range, n_not_zero_weights, + max_weight) : + lambda_range; + if (lambda_range.min >= lambda_range.max) + return 0; + + int lower_bound = 0; + int l_step = + ceil_div_pp(lambda_range.max - lambda_range.min + 1, + n_lambda_samples + 1); + for (int lambda = lambda_range.min + l_step; + lambda < lambda_range.max; lambda += l_step) { + int cur_lower_bound = + calc_dff_lower_bound_single_lambda(weights, n_weights, + capacity, lambda); + lower_bound = std::max(lower_bound, cur_lower_bound); + } + return lower_bound; } }}} // STATISTICS: int-prop - diff --git a/gecode/support/dynamic-array.hpp b/gecode/support/dynamic-array.hpp index 25583bf35e..1adfbe0d58 100644 --- a/gecode/support/dynamic-array.hpp +++ b/gecode/support/dynamic-array.hpp @@ -75,9 +75,6 @@ namespace Gecode { namespace Support { /// Cast in to pointer of type \a T operator T*(void); - - /// Return the capacity of the array - int capacity(void) const; }; @@ -157,12 +154,6 @@ namespace Gecode { namespace Support { return x; } - template - forceinline int - DynamicArray::capacity() const { - return n; - } - }} // STATISTICS: support-any diff --git a/test/int/bin-packing.cpp b/test/int/bin-packing.cpp index dcf78ada03..f35ca8cd28 100755 --- a/test/int/bin-packing.cpp +++ b/test/int/bin-packing.cpp @@ -309,6 +309,37 @@ namespace Test { namespace Int { } }; + /// Test that DFF lower bounds detect infeasible packing + class DFFLowerBound : public Base { + protected: + /// Simple test space class + class TestSpace : public Gecode::Space { + public: + /// Constructor + TestSpace(void) {} + /// Copy function + virtual Gecode::Space* copy(void) { + return nullptr; + } + }; + public: + /// Constructor + DFFLowerBound(void) + : Base("Int::BinPacking::DFFLowerBound") {} + /// Run the actual test + virtual bool run(void) { + using namespace Gecode; + TestSpace* home = new TestSpace; + IntVarArgs l(*home, 3, 0, 5); + IntVarArgs b(*home, 6, 0, 2); + IntArgs s({4,2,2,2,2,2}); + binpacking(*home, l, b, s); + bool failed = home->status() == SS_FAILED; + delete home; + return failed; + } + }; + /// Help class to create and register tests class Create { public: @@ -392,6 +423,8 @@ namespace Test { namespace Int { (void) new CliqueMBPT(c48); (void) new CliqueMBPT(c49); } + + (void) new DFFLowerBound; } }; @@ -405,4 +438,3 @@ namespace Test { namespace Int { // STATISTICS: test-int - From 0b6faf050e3e26f0e2a8c06500bcc8493a05450e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Thu, 9 Jul 2026 21:47:32 +0200 Subject: [PATCH 4/6] Avoid overflow in bin-packing DFF bounds --- changelog.in | 1 - gecode/int/bin-packing/propagate.cpp | 11 ++++-- gecode/int/bin-packing/propagate.hpp | 23 ++++++----- test/int/bin-packing.cpp | 57 ++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/changelog.in b/changelog.in index aacd98578f..d44fc9620a 100755 --- a/changelog.in +++ b/changelog.in @@ -152,7 +152,6 @@ initializing their exponent fields and rejecting nth-root test assignments whose source interval is not fully non-negative. [ENTRY] -<<<<<<< HEAD Module: flatzinc What: bug Rank: minor diff --git a/gecode/int/bin-packing/propagate.cpp b/gecode/int/bin-packing/propagate.cpp index 43c1beb6a2..09263917e4 100755 --- a/gecode/int/bin-packing/propagate.cpp +++ b/gecode/int/bin-packing/propagate.cpp @@ -300,9 +300,12 @@ namespace Gecode { namespace Int { namespace BinPacking { for (int r_idx = 0; r_idx < n_reductions; r_idx += 1) { // Calculate reduction parameters int& delta = delta_reductions[r_idx]; - int capacity = capacity_base_reduction + delta; + long long int capacity_ll = + static_cast(capacity_base_reduction) + delta; - if (capacity > 0) { + if ((capacity_ll > 0) && + (capacity_ll <= std::numeric_limits::max())) { + int capacity = static_cast(capacity_ll); // Calculate reduction int n_not_zero_weights = 0; int max_weight = 0; @@ -443,8 +446,8 @@ namespace Gecode { namespace Int { namespace BinPacking { std::min(smallest_virtual_weight, weights_base_reduction[b_idx]); delta_reductions[0] = -smallest_virtual_weight; delta_reductions[1] = 0; - delta_reductions[2] = - capacity_base_reduction - 2 * smallest_virtual_weight + 1; + delta_reductions[2] = capacity_base_reduction - + smallest_virtual_weight - smallest_virtual_weight + 1; } }}} diff --git a/gecode/int/bin-packing/propagate.hpp b/gecode/int/bin-packing/propagate.hpp index 1c6e41eb98..87e0ce2edf 100755 --- a/gecode/int/bin-packing/propagate.hpp +++ b/gecode/int/bin-packing/propagate.hpp @@ -215,9 +215,9 @@ namespace Gecode { namespace Int { namespace BinPacking { forceinline int Pack::f_ccm1(int w, int l, int c) { // Conditions - int const c0 = 2 * w > c; // x > c / 2 - int const c1 = 2 * w == c; // x == c / 2 - int const c2 = 2 * w < c; // x < c / 2 + int const c0 = w > c - w; // x > c / 2 + int const c1 = w == c - w; // x == c / 2 + int const c2 = w < c - w; // x < c / 2 // Values int const v0 = 2 * ((c / l) - ((c - w) / l)); @@ -266,9 +266,9 @@ namespace Gecode { namespace Int { namespace BinPacking { forceinline int Pack::f_vb2(int w, int l, int c) { - int const c0 = 2 * w > c; - int const c1 = 2 * w == c; - int const c2 = 2 * w < c; + int const c0 = w > c - w; + int const c1 = w == c - w; + int const c2 = w < c - w; int const t0 = f_vb2_base(c, l, c); int const t1 = f_vb2_base(w, l, c); @@ -372,7 +372,7 @@ namespace Gecode { namespace Int { namespace BinPacking { Pack::calc_dff_lower_bound_single_lambda(const int* weights, int n_weights, int capacity, int lambda) { // Transform and sum the weights - int sum_transformed_weights = 0; + long long int sum_transformed_weights = 0; for (int w_idx = 0; w_idx < n_weights; w_idx += 1) sum_transformed_weights += f(weights[w_idx], lambda, capacity); @@ -380,7 +380,9 @@ namespace Gecode { namespace Int { namespace BinPacking { int transformed_capacity = f(capacity, lambda, capacity); // Lower bound - return ceil_div_pp(sum_transformed_weights, transformed_capacity); + return static_cast + (ceil_div_pp(sum_transformed_weights, + static_cast(transformed_capacity))); } template @@ -401,11 +403,14 @@ namespace Gecode { namespace Int { namespace BinPacking { ceil_div_pp(lambda_range.max - lambda_range.min + 1, n_lambda_samples + 1); for (int lambda = lambda_range.min + l_step; - lambda < lambda_range.max; lambda += l_step) { + lambda < lambda_range.max; ) { int cur_lower_bound = calc_dff_lower_bound_single_lambda(weights, n_weights, capacity, lambda); lower_bound = std::max(lower_bound, cur_lower_bound); + if (lambda > lambda_range.max - l_step) + break; + lambda += l_step; } return lower_bound; } diff --git a/test/int/bin-packing.cpp b/test/int/bin-packing.cpp index f35ca8cd28..f0e2c16856 100755 --- a/test/int/bin-packing.cpp +++ b/test/int/bin-packing.cpp @@ -340,6 +340,62 @@ namespace Test { namespace Int { } }; + /// Test DFF arithmetic at the largest supported integer value + class DFFLargeWeights : public Base { + protected: + /// Simple test space class + class TestSpace : public Gecode::Space { + public: + /// Constructor + TestSpace(void) {} + /// Copy function + virtual Gecode::Space* copy(void) { + return nullptr; + } + }; + public: + /// Constructor + DFFLargeWeights(void) + : Base("Int::BinPacking::DFFLargeWeights") {} + /// Run the actual test + virtual bool run(void) { + using namespace Gecode; + { + TestSpace* home = new TestSpace; + IntVarArgs l(*home, 2, 0, Gecode::Int::Limits::max); + IntVarArgs b(*home, 1, 0, 1); + IntArgs s({Gecode::Int::Limits::max}); + binpacking(*home, l, b, s); + bool failed = home->status() == SS_FAILED; + delete home; + if (failed) + return false; + } + { + TestSpace* home = new TestSpace; + int c = Gecode::Int::Limits::max / 2; + IntVarArgs l(*home, 2, 0, c); + IntVarArgs b(*home, 1, 0, 1); + IntArgs s({c}); + binpacking(*home, l, b, s); + bool failed = home->status() == SS_FAILED; + delete home; + if (failed) + return false; + } + { + TestSpace* home = new TestSpace; + IntVarArgs l(*home, 2, 0, 1100000000); + IntVarArgs b(*home, 2, 0, 1); + IntArgs s({1000000000,1000000000}); + binpacking(*home, l, b, s); + bool failed = home->status() == SS_FAILED; + delete home; + return !failed; + } + } + }; + /// Help class to create and register tests class Create { public: @@ -425,6 +481,7 @@ namespace Test { namespace Int { } (void) new DFFLowerBound; + (void) new DFFLargeWeights; } }; From 1c16777224974b67161d61983eae4d215517df08 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 20:26:38 +0200 Subject: [PATCH 5/6] Add tiered bin-packing propagation levels --- gecode/driver/options.cpp | 11 +++-- gecode/int.hh | 20 +++++++- gecode/int/bin-packing.cpp | 8 ++-- gecode/int/bin-packing.hh | 12 +++-- gecode/int/bin-packing/propagate.cpp | 69 ++++++++++++++++------------ gecode/int/bin-packing/propagate.hpp | 7 +-- test/int/bin-packing.cpp | 47 +++++++++++++++++++ 7 files changed, 130 insertions(+), 44 deletions(-) diff --git a/gecode/driver/options.cpp b/gecode/driver/options.cpp index bf03411a10..5c6c1fbf87 100755 --- a/gecode/driver/options.cpp +++ b/gecode/driver/options.cpp @@ -329,6 +329,7 @@ namespace Gecode { else if (!strncmp("dom",a,e)) { b = IPL_DOM; } else if (!strncmp("basic",a,e)) { m |= IPL_BASIC; } else if (!strncmp("advanced",a,e)) { m |= IPL_ADVANCED; } + else if (!strncmp("full",a,e)) { m |= IPL_FULL; } else { std::cerr << "Wrong argument \"" << a << "\" for option \"" << iopt << "\"" @@ -350,7 +351,7 @@ namespace Gecode { IplOption::help(void) { using namespace std; cerr << '\t' << iopt - << " (def,val,bnd,dom,basic,advanced)" << endl + << " (def,val,bnd,dom,basic,advanced,full)" << endl << "\t\tdefault: "; switch (vbd(cur)) { case IPL_DEF: cerr << "def"; break; @@ -359,8 +360,12 @@ namespace Gecode { case IPL_DOM: cerr << "dom"; break; default: GECODE_NEVER; } - if (cur & IPL_BASIC) cerr << ",basic"; - if (cur & IPL_ADVANCED) cerr << ",advanced"; + if ((cur & IPL_FULL) == IPL_FULL) + cerr << ",full"; + else { + if (cur & IPL_BASIC) cerr << ",basic"; + if (cur & IPL_ADVANCED) cerr << ",advanced"; + } cerr << endl << "\t\t" << exp << endl; } diff --git a/gecode/int.hh b/gecode/int.hh index 301eb0ddc4..ff4961cb5e 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -1015,6 +1015,7 @@ namespace Gecode { IPL_BASIC = 4, ///< Use basic propagation algorithm IPL_ADVANCED = 8, ///< Use advanced propagation algorithm IPL_BASIC_ADVANCED = IPL_BASIC | IPL_ADVANCED, ///< Use both + IPL_FULL = IPL_BASIC_ADVANCED, ///< Use full propagation IPL_BITS_ = 4 ///< Number of bits required (internal) }; @@ -3147,7 +3148,16 @@ namespace Gecode { * for each \f$i\f$ with \f$0\leq i<|b|\f$ the constraint * \f$0\leq b_i<|l|\f$ holds. * - * The propagation follows: Paul Shaw. A Constraint for Bin Packing. CP 2004. + * Basic and knapsack propagation are based on: + * Paul Shaw. A Constraint for Bin Packing. CP 2004. + * The lower-bound phase uses dual-feasible functions described in: + * Tardivo et al. CP for Bin Packing with Multi-Core and GPUs. CP 2024. + * The propagation level \a ipl controls the amount of filtering: + * - \a IPL_BASIC performs basic load and item filtering only. + * - \a IPL_ADVANCED performs basic filtering, knapsack filtering using + * NoSum, and a small DFF portfolio (CCM1 and MT). + * - \a IPL_FULL performs all of the above and the complete DFF portfolio. + * It is the default (\a IPL_DEF). * * Throws the following exceptions: * - Of type Int::ArgumentSizeMismatch if \a b and \a s are not of @@ -3191,6 +3201,14 @@ namespace Gecode { * number of items due to the Bron-Kerbosch algorithm used for finding * the maximal conflict item sets. * + * The propagation level \a ipl controls the filtering performed by each + * per-dimension bin-packing propagator: + * - \a IPL_BASIC performs basic load and item filtering only. + * - \a IPL_ADVANCED performs basic filtering, knapsack filtering using + * NoSum, and a small DFF portfolio (CCM1 and MT). + * - \a IPL_FULL performs all of the above and the complete DFF portfolio. + * It is the default (\a IPL_DEF). + * * Throws the following exceptions: * - Of type Int::ArgumentSizeMismatch if any of the following properties * is violated: \f$|b|=n\f$, \f$|l|=m\cdot d\f$, \f$|s|=n\cdot d\f$, diff --git a/gecode/int/bin-packing.cpp b/gecode/int/bin-packing.cpp index c9776e6999..9a5844299f 100755 --- a/gecode/int/bin-packing.cpp +++ b/gecode/int/bin-packing.cpp @@ -41,7 +41,7 @@ namespace Gecode { binpacking(Home home, const IntVarArgs& l, const IntVarArgs& b, const IntArgs& s, - IntPropLevel) { + IntPropLevel ipl) { using namespace Int; if (same(l,b)) throw ArgumentSame("Int::binpacking"); @@ -59,14 +59,14 @@ namespace Gecode { for (int i=0; i \endcode @@ -152,17 +154,21 @@ namespace Gecode { namespace Int { namespace BinPacking { ViewArray l; /// Items with bin and size ViewArray bs; + /// Propagation level + IntPropLevel ipl; /// Total size of all items int t; /// Constructor for posting - Pack(Home home, ViewArray& l, ViewArray& bs); + Pack(Home home, ViewArray& l, ViewArray& bs, + IntPropLevel ipl); /// Constructor for cloning \a p Pack(Space& home, Pack& p); public: /// Post propagator for loads \a l and items \a bs GECODE_INT_EXPORT static ExecStatus post(Home home, - ViewArray& l, ViewArray& bs); + ViewArray& l, ViewArray& bs, + IntPropLevel ipl=IPL_DEF); /// Detect non-existence of sums in \a a .. \a b template bool nosum(const SizeSet& s, int a, int b, int& ap, int& bp); diff --git a/gecode/int/bin-packing/propagate.cpp b/gecode/int/bin-packing/propagate.cpp index 09263917e4..f22ce2cd11 100755 --- a/gecode/int/bin-packing/propagate.cpp +++ b/gecode/int/bin-packing/propagate.cpp @@ -213,6 +213,10 @@ namespace Gecode { namespace Int { namespace BinPacking { region.free(); } + // Stop after basic propagation unless stronger filtering is requested. + if (ba(ipl) == IPL_BASIC) + return ES_NOFIX; + // Only if the propagator is at fixpoint here, continue with the more // expensive stage for propagation. if (IntView::me(modeventdelta()) != ME_INT_NONE) @@ -291,6 +295,7 @@ namespace Gecode { namespace Int { namespace BinPacking { int* weights_current_reduction = region.alloc(n_weights_reductions); int* delta_reductions = region.alloc(n_reductions); + bool full = ba(ipl) != IPL_ADVANCED; // Initialize reductions int capacity_base_reduction = 0; @@ -318,7 +323,7 @@ namespace Gecode { namespace Int { namespace BinPacking { weights[w_idx] = weight; } - // Check lower bounds + // Check the small DFF portfolio used by advanced propagation. int lower_bound = calc_dff_lower_bound (weights, n_weights_reductions, capacity, @@ -333,33 +338,36 @@ namespace Gecode { namespace Int { namespace BinPacking { if (lower_bound > n_bins) return ES_FAILED; - lower_bound = - calc_dff_lower_bound - (weights, n_weights_reductions, capacity, - n_not_zero_weights, max_weight); - if (lower_bound > n_bins) - return ES_FAILED; - - lower_bound = - calc_dff_lower_bound - (weights, n_weights_reductions, capacity, - n_not_zero_weights, max_weight, true); - if (lower_bound > n_bins) - return ES_FAILED; - - lower_bound = - calc_dff_lower_bound - (weights, n_weights_reductions, capacity, - n_not_zero_weights, max_weight, true); - if (lower_bound > n_bins) - return ES_FAILED; - - lower_bound = - calc_dff_lower_bound - (weights, n_weights_reductions, capacity, - n_not_zero_weights, max_weight); - if (lower_bound > n_bins) - return ES_FAILED; + if (full) { + // Check the remaining DFFs for full propagation. + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight, true); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight, true); + if (lower_bound > n_bins) + return ES_FAILED; + + lower_bound = + calc_dff_lower_bound + (weights, n_weights_reductions, capacity, + n_not_zero_weights, max_weight); + if (lower_bound > n_bins) + return ES_FAILED; + } } } } @@ -370,7 +378,8 @@ namespace Gecode { namespace Int { namespace BinPacking { } ExecStatus - Pack::post(Home home, ViewArray& l, ViewArray& bs) { + Pack::post(Home home, ViewArray& l, ViewArray& bs, + IntPropLevel ipl) { // Sort according to size Support::quicksort(&bs[0], bs.size()); // Total size of items @@ -402,7 +411,7 @@ namespace Gecode { namespace Int { namespace BinPacking { GECODE_ME_CHECK(l[j].gq(home,0)); GECODE_ME_CHECK(l[j].lq(home,s)); } - (void) new (home) Pack(home,l,bs); + (void) new (home) Pack(home,l,bs,ipl); return ES_OK; } } diff --git a/gecode/int/bin-packing/propagate.hpp b/gecode/int/bin-packing/propagate.hpp index 87e0ce2edf..e548fe3a43 100755 --- a/gecode/int/bin-packing/propagate.hpp +++ b/gecode/int/bin-packing/propagate.hpp @@ -151,8 +151,9 @@ namespace Gecode { namespace Int { namespace BinPacking { */ forceinline - Pack::Pack(Home home, ViewArray& l0, ViewArray& bs0) - : Propagator(home), l(l0), bs(bs0), t(0) { + Pack::Pack(Home home, ViewArray& l0, ViewArray& bs0, + IntPropLevel ipl0) + : Propagator(home), l(l0), bs(bs0), ipl(ipl0), t(0) { l.subscribe(home,*this,PC_INT_BND); bs.subscribe(home,*this,PC_INT_DOM); for (int i=0; istatus() == SS_FAILED; + delete home; + if (failed != expected_failed[i]) + return false; + } + return true; + } + public: + /// Constructor + PropagationLevels(void) + : Base("Int::BinPacking::PropagationLevels") {} + /// Run the actual test + virtual bool run(void) { + using namespace Gecode; + bool const advanced_failed[] = {false, true, true}; + bool const full_failed[] = {false, false, true}; + return + check_case(3, 5, IntArgs({4,2,2,2,2,2}), advanced_failed) && + check_case(5, 35, + IntArgs({32,30,24,21,19,11,10,9,8,5,3}), + full_failed); + } + }; + /// Test DFF arithmetic at the largest supported integer value class DFFLargeWeights : public Base { protected: @@ -481,6 +527,7 @@ namespace Test { namespace Int { } (void) new DFFLowerBound; + (void) new PropagationLevels; (void) new DFFLargeWeights; } }; From c17ceb2c5579e6c6e921ac0c934eb5ba6437f3aa Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Sat, 11 Jul 2026 21:04:52 +0200 Subject: [PATCH 6/6] Make advanced bin-packing propagation the default --- gecode/int.hh | 8 ++++---- gecode/int/bin-packing/propagate.hpp | 3 ++- test/int/bin-packing.cpp | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/gecode/int.hh b/gecode/int.hh index ff4961cb5e..05769055f6 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -3155,9 +3155,9 @@ namespace Gecode { * The propagation level \a ipl controls the amount of filtering: * - \a IPL_BASIC performs basic load and item filtering only. * - \a IPL_ADVANCED performs basic filtering, knapsack filtering using - * NoSum, and a small DFF portfolio (CCM1 and MT). + * NoSum, and a small DFF portfolio (CCM1 and MT). It is the default + * (\a IPL_DEF). * - \a IPL_FULL performs all of the above and the complete DFF portfolio. - * It is the default (\a IPL_DEF). * * Throws the following exceptions: * - Of type Int::ArgumentSizeMismatch if \a b and \a s are not of @@ -3205,9 +3205,9 @@ namespace Gecode { * per-dimension bin-packing propagator: * - \a IPL_BASIC performs basic load and item filtering only. * - \a IPL_ADVANCED performs basic filtering, knapsack filtering using - * NoSum, and a small DFF portfolio (CCM1 and MT). + * NoSum, and a small DFF portfolio (CCM1 and MT). It is the default + * (\a IPL_DEF). * - \a IPL_FULL performs all of the above and the complete DFF portfolio. - * It is the default (\a IPL_DEF). * * Throws the following exceptions: * - Of type Int::ArgumentSizeMismatch if any of the following properties diff --git a/gecode/int/bin-packing/propagate.hpp b/gecode/int/bin-packing/propagate.hpp index e548fe3a43..9cf3d4cdd5 100755 --- a/gecode/int/bin-packing/propagate.hpp +++ b/gecode/int/bin-packing/propagate.hpp @@ -153,7 +153,8 @@ namespace Gecode { namespace Int { namespace BinPacking { forceinline Pack::Pack(Home home, ViewArray& l0, ViewArray& bs0, IntPropLevel ipl0) - : Propagator(home), l(l0), bs(bs0), ipl(ipl0), t(0) { + : Propagator(home), l(l0), bs(bs0), + ipl(ba(ipl0) == IPL_DEF ? IPL_ADVANCED : ipl0), t(0) { l.subscribe(home,*this,PC_INT_BND); bs.subscribe(home,*this,PC_INT_DOM); for (int i=0; istatus() == SS_FAILED; delete home; return failed; @@ -353,11 +353,11 @@ namespace Test { namespace Int { }; /// Check one propagation-level test case bool check_case(int n_bins, int capacity, const Gecode::IntArgs& sizes, - const bool expected_failed[3]) const { + const bool expected_failed[4]) const { using namespace Gecode; IntPropLevel const levels[] = - {IPL_BASIC, IPL_ADVANCED, IPL_FULL}; - for (unsigned int i = 0; i < 3; i += 1) { + {IPL_BASIC, IPL_DEF, IPL_ADVANCED, IPL_FULL}; + for (unsigned int i = 0; i < 4; i += 1) { TestSpace* home = new TestSpace; IntVarArgs l(*home, n_bins, 0, capacity); IntVarArgs b(*home, sizes.size(), 0, n_bins-1); @@ -376,8 +376,8 @@ namespace Test { namespace Int { /// Run the actual test virtual bool run(void) { using namespace Gecode; - bool const advanced_failed[] = {false, true, true}; - bool const full_failed[] = {false, false, true}; + bool const advanced_failed[] = {false, true, true, true}; + bool const full_failed[] = {false, false, false, true}; return check_case(3, 5, IntArgs({4,2,2,2,2,2}), advanced_failed) && check_case(5, 35,