package entropy.plan.choco.constraint.pack;/* * Created by IntelliJ IDEA. * User: sofdem - sophie.demassey{at}mines-nantes.fr * Date: 15/08/11 - 01:28 */ import java.util.BitSet; import choco.cp.solver.variables.integer.IntVarEvent; import choco.kernel.common.logging.ChocoLogging; import choco.kernel.common.util.iterators.DisposableIntIterator; import choco.kernel.common.util.tools.ArrayUtils; import choco.kernel.memory.IEnvironment; import choco.kernel.memory.IStateBitSet; import choco.kernel.memory.IStateInt; import choco.kernel.solver.ContradictionException; import choco.kernel.solver.constraints.integer.AbstractLargeIntSConstraint; import choco.kernel.solver.variables.integer.IntDomainVar; /** * @author Sophie Demassey */ public class FastMultiBinPacking extends AbstractLargeIntSConstraint implements CustomPack { private IEnvironment env; /** * The bin assigned to each item [I]. */ protected final IntDomainVar[] bins; /** * The constant size of each item on each dimension [DxI]. */ protected final int[][] iSizes; /** * The sum of the item sizes on each dimension [D]. */ private final long[] sumISizes; /** * The load of each bin on each dimension [DxB]. */ protected final IntDomainVar[][] loads; /** * The candidate items for each bin (possible but not required assignments) [B]. */ private IStateBitSet[] candidates; /** * The total size of the candidate + required items for each bin on each dimension [DxB]. */ private IStateInt[][] bTLoads; /** * The total size of the required items for each bin on each dimension [DxB]. */ private IStateInt[][] bRLoads; /** * The sum of the bin load LBs on each dimension [D]. */ private IStateInt[] sumLoadInf; /** * The sum of the bin load UBs on each dimension [D]. */ private IStateInt[] sumLoadSup; /** * The remaining available bins (having candidate items). */ private IStateBitSet availableBins; /** * nb of dimensions. */ private final int nbDims; /** * nb of bins. */ private final int nbBins; /** * constructor of the FastBinPacking global constraint * @param environment the solver environment * @param loads array of nbDims x nbBins variables, each figuring the total size of the items assigned to it, usually initialized to [0, capacity] * @param sizes array of nbDim x nbItems CONSTANTS ordered in decreasing order on the first dimension, each figuring the size of i-th item * @param bins array of nbItems variables, each figuring the possible bins an item can be assigned to, usually initialized to [0, nbBins-1] */ public FastMultiBinPacking(IEnvironment environment, IntDomainVar[][] loads, int[][] sizes, IntDomainVar[] bins) { super(ArrayUtils.append(bins, ArrayUtils.flatten(loads))); this.nbDims = sizes.length; this.nbBins = loads[0].length; this.loads = loads; this.env = environment; this.iSizes = sizes; this.sumISizes = new long[nbDims]; for (int d=0; d<nbDims; d++) { long sum = 0; for (int i = 0; i < sizes[d].length; i++) { sum += iSizes[d][i]; } sumISizes[d] = sum; } this.bTLoads = new IStateInt[nbDims][nbBins]; this.bRLoads = new IStateInt[nbDims][nbBins]; this.sumLoadInf = new IStateInt[nbDims]; this.sumLoadSup = new IStateInt[nbDims]; this.bins = bins; } @Override public final int getRemainingSpace(int bin) { throw new UnsupportedOperationException("the dimension must be specified."); } public final int getRemainingSpace(int dim, int bin) { return loads[dim][bin].getSup() - bRLoads[dim][bin].get(); } @Override public IStateBitSet getCandidates(int bin) { return candidates[bin]; } //****************************************************************// //********* Events ***********************************************// //****************************************************************// @Override public int getFilteredEventMask(int idx) { if (idx<bins.length) { return IntVarEvent.REMVAL_MASK; } return IntVarEvent.BOUNDS_MASK; } @Override public boolean isSatisfied(int[] tuple) { int[][] l = new int[nbDims][nbBins]; int[] c = new int[nbBins]; for (int i = 0; i < bins.length; i++) { final int b = tuple[i]; for (int d=0; d < nbDims; d++) { l[d][b] += iSizes[d][i]; } c[b]++; } int shift = bins.length; for (int d=0; d < nbDims; d++) { for (int i = 0; i < nbBins; i++) { if (tuple[i+shift] != l[d][i]) { ChocoLogging.getBranchingLogger().warning("Bad load of " + i + " in dim " + d + " = " + tuple[i+shift] + " expected =" + l[d][i]); return false; } } shift += nbBins; } return true; } @Override /** * initialize the internal data: availableBins, candidates, binRequiredLoads, binTotalLoads, sumLoadInf, sumLoadSup * shrink the item-to-bins assignment variables: 0 <= bins[i] <= nbBins * shrink the bin load variables: binRequiredLoad <= binLoad <= binTotalLoad */ public void awake() throws ContradictionException { availableBins = env.makeBitSet(nbBins); candidates = new IStateBitSet[nbBins]; for (int b = 0; b < nbBins; b++) { candidates[b] = env.makeBitSet(bins.length); } int[][] rLoads = new int[nbDims][nbBins]; int[][] cLoads = new int[nbDims][nbBins]; for (int i = 0; i < bins.length; i++) { bins[i].updateInf(0, this, false); bins[i].updateSup(nbBins - 1, this, false); if (bins[i].isInstantiated()) { for (int d=0; d < nbDims; d++) { rLoads[d][bins[i].getVal()] += iSizes[d][i]; } } else { DisposableIntIterator it = bins[i].getDomain().getIterator(); try { while (it.hasNext()) { int b = it.next(); candidates[b].set(i); for (int d=0; d < nbDims; d++) { cLoads[d][b] += iSizes[d][i]; } } } finally { it.dispose(); } } } for (int d=0; d < nbDims; d++) { int sumLoadInf = 0; int sumLoadSup = 0; for (int b = 0; b < nbBins; b++) { bRLoads[d][b] = env.makeInt(rLoads[d][b]); bTLoads[d][b] = env.makeInt(rLoads[d][b]+cLoads[d][b]); loads[d][b].updateInf(rLoads[d][b], this, false); sumLoadInf += loads[d][b].getInf(); loads[d][b].updateSup(rLoads[d][b]+cLoads[d][b], this, false); sumLoadSup += loads[d][b].getSup(); if (!candidates[b].isEmpty() && d==0) { availableBins.set(b); } } this.sumLoadInf[d] = env.makeInt(sumLoadInf); this.sumLoadSup[d] = env.makeInt(sumLoadSup); } assert checkLoadConsistency() && checkCandidatesConsistency(); propagate(); } @Override /** * propagate 1) globally: sumItemSizes == sumBinLoads 2) on each bin: sumAssignedItemSizes == binLoad * rule 1: if sumSizes > sumBinLoadSups then fail * rule 2, for each bin: sumItemSizes - sumOtherBinSups <= binLoad <= sumItemSizes - sumOtherBinInfs * rule 3, for each bin: binRequiredLoad <= binLoad <= binTotalLoad * rule 4, for each bin and candidate item: if binRequiredLoad + itemSize > binLoadSup then remove item from bin * rule 5, for each bin and candidate item: if binTotalLoad - itemSize < binLoadInf then pack item into bin */ public void propagate() throws ContradictionException { //ChocoLogging.getSearchLogger().finest("propagate " + pretty()); boolean noFixPoint = true; while (noFixPoint) { noFixPoint = false; for (int d=0; d<nbDims; d++) { if (sumISizes[d] > sumLoadSup[d].get()) { fail(); } } for (int b = availableBins.nextSetBit(0); b >= 0; b = availableBins.nextSetBit(b+1)) { for (int d=0; d<nbDims; d++) { noFixPoint |= loadInfFiltering(d, b, Math.max(bRLoads[d][b].get(), (int) sumISizes[d] - sumLoadSup[d].get() + loads[d][b].getSup())); noFixPoint |= loadSupFiltering(d, b, Math.min(bTLoads[d][b].get(), (int) sumISizes[d] - sumLoadInf[d].get() + loads[d][b].getInf())); } noFixPoint |= propagateMultiKnapsack(b); } } assert checkLoadConsistency() && checkCandidatesConsistency(); } @Override /** * delayed propagation of the bound updates of a bin load variable */ public void awakeOnBounds(int varIdx) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(vars[varIdx].pretty() + " awakeOnBounds"); constAwake(false); } @Override /** * propagate the removal of an item-to-bins assignment variable: * 1) update the candidate and check to decrease the load UB of each removed bins: binLoad <= binTotalLoad * 2) if item is assigned: update the required and check to increase the load LB of the bin: binLoad >= binRequiredLoad * @throws ContradictionException on the load variables */ public void awakeOnRemovals(int iIdx, DisposableIntIterator deltaDomain) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(vars[iIdx].pretty() + " awakeOnRem"); if (iIdx < bins.length) { try { while (deltaDomain.hasNext()) { int bin = deltaDomain.next(); if (updateRemoveItemFromBin(iIdx, bin)) { loadSupFiltering(bin, bTLoads); } } } finally { deltaDomain.dispose(); } if (vars[iIdx].isInstantiated()) { int bin = vars[iIdx].getVal(); if (updatePackItemToBin(iIdx, bin)) { loadInfFiltering(bin, bRLoads); } } } this.constAwake(false); } //****************************************************************// //********* VARIABLE FILTERING ***********************************// //****************************************************************// /** * update the internal data corresponding to the assignment of an item to a bin: * remove the item from the candidate list of the bin and balance its size from the candidate to the required load of the bin * @param item item index * @param bin bin index * @return true if the update had not already been performed */ private boolean updatePackItemToBin(int item, int bin) { if (candidates[bin].get(item)) { candidates[bin].clear(item); if (candidates[bin].isEmpty()) { availableBins.clear(bin); } for (int d=0; d<nbDims; d++) { bRLoads[d][bin].add(iSizes[d][item]); } return true; } return false; } /** * update the internal data corresponding to the removal of an item from a bin: * remove the item from the candidate list of the bin and reduce the candidate load of the bin * @param item item index * @param bin bin index * @return true if the update had not already been performed */ private boolean updateRemoveItemFromBin(int item, int bin) { if (candidates[bin].get(item)) { candidates[bin].clear(item); if (candidates[bin].isEmpty()) { availableBins.clear(bin); } for (int d=0; d<nbDims; d++) { bTLoads[d][bin].add(-1 * iSizes[d][item]); } return true; } return false; } /** * increase the LB of the bin load and the sum of the bin load LBs * @param bin bin index * @param newLoads new LB of the bin load * @return {@code true} if LB is increased in at least one dimension. * @throws ContradictionException on the load[bin] variable */ private boolean loadInfFiltering(int bin, IStateInt[][] newLoads) throws ContradictionException { boolean ret = false; for (int d=0; d<nbDims; d++) { ret |= loadInfFiltering(d, bin, newLoads[d][bin].get()); } return ret; } /** * increase the LB of the bin load and the sum of the bin load LBs * @param dim dimension index * @param bin bin index * @param newLoadInf new LB of the bin load * @return {@code true} if LB is increased. * @throws ContradictionException on the load[bin] variable */ private boolean loadInfFiltering(int dim, int bin, int newLoadInf) throws ContradictionException { int inc = newLoadInf-loads[dim][bin].getInf(); if (inc>0) { loads[dim][bin].updateInf(newLoadInf, this, false); sumLoadInf[dim].add(inc); return true; } return false; } /** * decrease the UB of the bin load and the sum of the bin load UBs * @param bin bin index * @param newLoads new UB of the bin load * @return {@code true} if UB is decreased in at least one dimension. * @throws ContradictionException on the load[bin] variable */ private boolean loadSupFiltering(int bin, IStateInt[][] newLoads) throws ContradictionException { boolean ret = false; for (int d=0; d<nbDims; d++) { ret |= loadSupFiltering(d, bin, newLoads[d][bin].get()); } return ret; } /** * decrease the UB of the bin load and the sum of the bin load UBs * @param dim dimension index * @param bin bin index * @param newLoadSup new UB of the bin load * @return {@code true} if UB is decreased. * @throws ContradictionException on the load[bin] variable */ private boolean loadSupFiltering(int dim, int bin, int newLoadSup) throws ContradictionException { int dec = newLoadSup - loads[dim][bin].getSup(); if (dec<0) { loads[dim][bin].updateSup(newLoadSup, this, false); sumLoadSup[dim].add(dec); return true; } return false; } /** * propagate the knapsack constraint on a given bin: * 1) remove the candidate items bigger than the remaining free space (when binRequiredLoad + itemSize > binLoadSup) * 2) pack the candidate items necessary to reach the load LB (when binTotalLoad - itemSize < binLoadInf). * the loads are also filtered within this constraint (rather in the propagate loop) because considered bins are eventually became unavailable * @param bin bin index * @return {@code true} if at least one item is removed or packed. * @throws ContradictionException on the bins or loads variables */ private boolean propagateMultiKnapsack(int bin) throws ContradictionException { int d; int item; boolean ret = false, up=true; for (item = candidates[bin].nextSetBit(0); item >= 0 && up; item = candidates[bin].nextSetBit(item + 1)) { up = false; for (d=0; d<nbDims && (iSizes[d][item] + bRLoads[d][bin].get() <= loads[d][bin].getSup()); d++); if (d<nbDims && updateRemoveItemFromBin(item, bin)) { bins[item].removeVal(bin, this, false); loadSupFiltering(bin, bTLoads); if (bins[item].isInstantiated()) { int b = bins[item].getVal(); updatePackItemToBin(item, b); loadInfFiltering(b, bRLoads); } up = true; } for (d=0; d<nbDims && (bTLoads[d][bin].get() - iSizes[d][item] >= loads[d][bin].getInf()); d++); if (d<nbDims && updatePackItemToBin(item, bin)) { DisposableIntIterator domain = bins[item].getDomain().getIterator(); try { while (domain.hasNext()) { int b = domain.next(); if (b != bin) { updateRemoveItemFromBin(item, b); loadSupFiltering(b, bTLoads); } } } finally { domain.dispose(); } bins[item].instantiate(bin, this, false); loadInfFiltering(bin, bRLoads); up = true; } ret |= up; } return ret; } //****************************************************************// //********* Checkers *********************************************// //****************************************************************// /** * Check the consistency of the required and candidate loads with regards to the assignment variables: * for each bin: sumAssignedItemSizes == binRequiredLoad, sumPossibleItemSizes == binTotalLoad * rule 3, for each bin: binRequiredLoad <= binLoad <= binTotalLoad * @return {@code false} if not consistent. */ private boolean checkLoadConsistency() { boolean check = true; for (int d=0; d<nbDims; d++) { int[] rs = new int[nbBins]; int[] cs = new int[nbBins]; for (int i = 0; i < bins.length; i++) { if (bins[i].isInstantiated()) { rs[bins[i].getVal()] += iSizes[d][i]; } else { DisposableIntIterator it = bins[i].getDomain().getIterator(); try { while (it.hasNext()) { int bin = it.next(); cs[bin] += iSizes[d][i]; } } finally { it.dispose(); } } } int sumLoadInf = 0; int sumLoadSup = 0; for (int b = 0; b < rs.length; b++) { if (rs[b] != bRLoads[d][b].get()) { ChocoLogging.getBranchingLogger().warning(loads[d][b].pretty() + " required=" + bRLoads[d][b].get() + " expected=" + rs[b]); check = false; } if (rs[b]+cs[b] != bTLoads[d][b].get()) { ChocoLogging.getBranchingLogger().warning(loads[d][b].pretty() + " total=" + bTLoads[d][b].get() + " expected=" + (rs[b]+cs[b])); check = false; } if (loads[d][b].getInf() < rs[b]) { ChocoLogging.getBranchingLogger().warning(loads[d][b].pretty() + " LB expected >=" + rs[b]); check = false; } if (loads[d][b].getSup() > rs[b]+cs[b]) { ChocoLogging.getBranchingLogger().warning(loads[d][b].pretty() + " UB expected <=" + (rs[b]+cs[b])); check = false; } sumLoadInf += loads[d][b].getInf(); sumLoadSup += loads[d][b].getSup(); } if (this.sumLoadInf[d].get() != sumLoadInf) { ChocoLogging.getBranchingLogger().warning("Sum Load LB = " + this.sumLoadInf[d].get() + " expected =" + sumLoadInf); check = false; } if (this.sumLoadSup[d].get() != sumLoadSup) { ChocoLogging.getBranchingLogger().warning("Sum Load UB = " + this.sumLoadSup[d].get() + " expected =" + sumLoadSup); check = false; } } ChocoLogging.flushLogs(); return check; } /** * Check that the candidate lists are aligned with the assignment variables: * item is in candidates[bin] iff bin is in bins[item] * @return {@code false} if not consistent. */ private boolean checkCandidatesConsistency() { BitSet[] bs = new BitSet[nbBins]; for (int bin = 0; bin < nbBins; bin++) { bs[bin] = new BitSet(iSizes.length); } for (int i = 0; i < bins.length; i++) { if (!bins[i].isInstantiated()) { DisposableIntIterator it = bins[i].getDomain().getIterator(); try { while (it.hasNext()) { int bin = it.next(); bs[bin].set(i); } } finally { it.dispose(); } } } for (int b = 0; b < nbBins; b++) { for (int i = 0; i < bs[b].size(); i++) { if (bs[b].get(i) != candidates[b].get(i)) { ChocoLogging.getBranchingLogger().warning("candidate i '" + i + "' for bin '" + b + ": " + candidates[b].get(i) + " expected: " + bs[b].get(i)); ChocoLogging.flushLogs(); return false; } } } return true; } /** * print the list of candidate items for a given bin * @param bin bin index * @return list of item indices, between braces, separated by spaces */ public String prettyCandidates(int bin) { StringBuilder s = new StringBuilder("{"); for (int i = candidates[bin].nextSetBit(0); i >= 0; i = candidates[bin].nextSetBit(i + 1)) { s.append(i); s.append(' '); } s.append('}'); return s.toString(); } }