From 46a19cc1c09804f688f2a68167ac7c519f567f4d Mon Sep 17 00:00:00 2001 From: Fabio Tardivo Date: Sun, 15 Sep 2024 01:56:17 -0600 Subject: [PATCH 1/3] 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 206b78fbe07fdabc5dc164af5abe0dbf901c723b Mon Sep 17 00:00:00 2001 From: Fabio Tardivo Date: Sun, 15 Sep 2024 01:56:25 -0600 Subject: [PATCH 2/3] 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 899234a8871e795188661427e37c18e74e73373b Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 7 Jul 2026 10:23:39 +0200 Subject: [PATCH 3/3] Integrate bin-packing DFF propagation --- changelog.in | 11 ++ 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, 293 insertions(+), 281 deletions(-) diff --git a/changelog.in b/changelog.in index 451776aa77..c20924b2be 100755 --- a/changelog.in +++ b/changelog.in @@ -103,6 +103,17 @@ Fix float power and nth-root tests with shared variables by initializing their exponent fields and rejecting nth-root test assignments whose source interval is not fully non-negative. +[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 -