/* * Copyright (c) Fabien Hermenier * * This file is part of Entropy. * * Entropy is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Entropy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Entropy. If not, see <http://www.gnu.org/licenses/>. */ package entropy.plan.choco.constraint.pack; import gnu.trove.TIntArrayList; import gnu.trove.TIntProcedure; import java.util.Arrays; import choco.cp.solver.constraints.global.pack.IPackSConstraint; import choco.kernel.common.logging.ChocoLogging; import choco.kernel.common.opres.nosum.NoSumList; 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.memory.IStateIntVector; import choco.kernel.solver.ContradictionException; import choco.kernel.solver.SolverException; import choco.kernel.solver.constraints.set.AbstractLargeSetIntSConstraint; import choco.kernel.solver.variables.integer.IntDomainVar; import choco.kernel.solver.variables.set.SetVar; /** * A simplified version of {@link choco.cp.solver.constraints.global.pack.PackSConstraint}. Required and candidate * loads are computed incrementally while some coding tips speeds up computation. The constraint does not * however support all the options in {@link choco.cp.solver.constraints.global.pack.PackSConstraint} * * @author Fabien Hermenier * @see choco.cp.solver.constraints.global.pack.PackSConstraint */ public class SimpleBinPacking extends AbstractLargeSetIntSConstraint implements IPackSConstraint, CustomPack { public final SimpleBinPackingFiltering filtering; protected final BoundNumberOfBins bounds; private IStateIntVector availableBins; /** * The constant size of each item. */ protected final int[] iSizes; /** * The loads of the bins. */ protected final IntDomainVar[] loads; /** * The bin of each item. */ protected final IntDomainVar[] bins; private SetVar[] bSets; private IStateInt[] bCLoads; private IStateInt[] bRLoads; private IEnvironment env; public SimpleBinPacking(IEnvironment environment, SetVar[] itemSets, IntDomainVar[] loads, IntDomainVar[] sizes, IntDomainVar[] bins, IntDomainVar nbNonEmpty) { super(ArrayUtils.append(loads, sizes, bins, new IntDomainVar[]{nbNonEmpty}), itemSets); this.loads = loads; this.env = environment; iSizes = new int[sizes.length]; for (int i = 0; i < sizes.length; i++) { iSizes[i] = sizes[i].getVal(); } this.bSets = itemSets; this.bCLoads = new IStateInt[bSets.length]; this.bRLoads = new IStateInt[bSets.length]; this.bins = bins; this.bounds = new BoundNumberOfBins(); filtering = new SimpleBinPackingFiltering(this); availableBins = environment.makeBipartiteIntList(ArrayUtils.zeroToN(getNbBins())); } @Override public void fireAvailableBins() { final DisposableIntIterator iter = availableBins.getIterator(); while (iter.hasNext()) { final int b = iter.next(); if (svars[b].isInstantiated()) { iter.remove(); } } iter.dispose(); } @Override public final IStateIntVector getAvailableBins() { return availableBins; } public final int getRequiredSpace(int bin) { return bRLoads[bin].get(); } @Override public final int getRemainingSpace(int bin) { return loads[bin].getSup() - getRequiredSpace(bin); } protected final boolean isSetEvent(final int varIdx) { return varIdx < svars.length; } protected final boolean isItemEvent(final int varIdx) { final int a = 2 * getNbBins() + getNbItems(); final int b = a + getNbItems(); return varIdx >= a && varIdx < b; } protected final int getItemIndex(final int varIdx) { return varIdx - 2 * getNbBins() - getNbItems(); } @Override public final IntDomainVar[] getBins() { return bins; } public IStateInt getRLoad(int bIdx) { return bRLoads[bIdx]; } public IStateInt getCLoad(int bIdx) { return bCLoads[bIdx]; } //****************************************************************// //********* Filtering interface **********************************// //****************************************************************// @Override public final int getNbBins() { return svars.length; } @Override public final int getNbItems() { return iSizes.length; } @Override public final IntDomainVar[] getLoads() { return loads; } @Override public final int[] getSizes() { return null; } public final int[] getISizes() { return iSizes; } public SetVar getSetBin(int bIdx) { return bSets[bIdx]; } @Override public final NoSumList getStatus(int bin) { return null; } @Override public final boolean pack(int item, int bin) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " pack(" + item + ", " + bin + ")"); boolean ret = false; if (svars[bin].getDomain().getEnveloppeDomain().contains(item) && svars[bin].addToKernel(item, this, false)) { ret = true; bRLoads[bin].add(iSizes[item]); bCLoads[bin].add(-1 * iSizes[item]); } if (svars[bin].isInDomainKernel(item) && bins[item].fastCanBeInstantiatedTo(bin)) { //Update the cLoads & rLoads of the bin the item is packed in /* final DisposableIntIterator iter = bins[item].getDomain().getIterator(); //remove from other env try { while (iter.hasNext()) { final int b = iter.next(); if (bin != b) { //ChocoLogging.getSearchLogger().finest("Remove " + item + " from env of " + b); if (svars[b].remFromEnveloppe(item, this, false)) { bCLoads[b].add(-1 * iSizes[item]); } } } } finally { iter.dispose(); } */ //remove from other env for (int b = bins[item].getInf(); b <= bins[item].getSup(); b = bins[item].getNextDomainValue(b)) { if (bin != b) { //ChocoLogging.getSearchLogger().finest("Remove " + item + " from env of " + b); if (svars[b].remFromEnveloppe(item, this, false)) { bCLoads[b].add(-1 * iSizes[item]); } } } } else { this.fail(); } bins[item].instantiate(bin, this, true); return ret; } public final boolean simplePack(int item, int bin) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " simplePack(" + item + ", " + bin + ")"); boolean ret = false; if (svars[bin].getDomain().getEnveloppeDomain().contains(item) && svars[bin].addToKernel(item, this, true)) { ret = true; } if (svars[bin].isInDomainKernel(item) && bins[item].fastCanBeInstantiatedTo(bin)) { //Update the cLoads & rLoads of the bin the item is packed in /* final DisposableIntIterator iter = bins[item].getDomain().getIterator(); //remove from other env try { while (iter.hasNext()) { final int b = iter.next(); if (bin != b) { //ChocoLogging.getSearchLogger().finest("Remove " + item + " from env of " + b); ret |= svars[b].remFromEnveloppe(item, this, true); } } } finally { iter.dispose(); } */ //remove from other env for (int b = bins[item].getInf(); b <= bins[item].getSup(); b = bins[item].getNextDomainValue(b)) { if (bin != b) { //ChocoLogging.getSearchLogger().finest("Remove " + item + " from env of " + b); ret |= svars[b].remFromEnveloppe(item, this, true); } } } else { this.fail(); } bins[item].instantiate(bin, this, true); return ret; } @Override public final boolean remove(int item, int bin) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " remove(" + item + ", " + bin + ")"); boolean res = svars[bin].remFromEnveloppe(item, this, false); if (res) { bCLoads[bin].add(-1 * iSizes[item]); } bins[item].removeVal(bin, this, true); if (bins[item].isInstantiated()) { final int b = bins[item].getVal(); if (svars[b].addToKernel(item, this, false)) { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " remove may " + item + " add to the kernel of " + b); bRLoads[b].add(iSizes[item]); bCLoads[b].add(-1 * iSizes[item]); } } return res; } public final boolean simpleRemove(int item, int bin) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " simpleRemove(" + item + ", " + bin + ")"); boolean res = svars[bin].remFromEnveloppe(item, this, true); bins[item].removeVal(bin, this, true); if (bins[item].isInstantiated()) { final int b = bins[item].getVal(); if (svars[b].addToKernel(item, this, true)) { //ChocoLogging.getSearchLogger().finest(svars[bin].pretty() + " remove may " + item + " add to the kernel of " + b); } } return res; } @Override public final boolean updateInfLoad(int bin, int load) throws ContradictionException { return loads[bin].updateInf(load, this, true); } @Override public final boolean updateNbNonEmpty(int min, int max) throws ContradictionException { boolean res = false; final int idx = ivars.length - 1; ivars[idx].updateInf(min, this, true); ivars[idx].updateSup(max, this, true); return res; } @Override public final boolean updateSupLoad(int bin, int load) throws ContradictionException { return loads[bin].updateSup(load, this, true); } //****************************************************************// //********* Events *******************************************// //****************************************************************// @Override public boolean isConsistent() { // really no idea. wait and propagate return false; } protected final void checkBounds(int item) throws ContradictionException { bins[item].updateInf(0, this, true); bins[item].updateSup(svars.length - 1, this, true); } protected final void checkEnveloppes() throws ContradictionException { for (int bin = 0; bin < svars.length; bin++) { int inf; // check if envelope is empty, to avoid infinite loop while ((inf = svars[bin].getEnveloppeInf()) < 0 && svars[bin].remFromEnveloppe(inf, this, true)) { //bCLoads[bin].add(-1 * iSizes[inf]); //assert(bCLoads[bin].get() >= 0); } int sup; // check if envelope is empty, to avoid infinite loop while ((sup = svars[bin].getEnveloppeSup()) > bins.length - 1 && svars[bin].remFromEnveloppe(sup, this, true)) { //bCLoads[bin].add(-1 * iSizes[sup]); //assert(bCLoads[bin].get() >= 0); } } } @Override public void awake() throws ContradictionException { //initial channeling checkEnveloppes(); for (int item = 0; item < bins.length; item++) { checkBounds(item); if (bins[item].isInstantiated()) { //the item is packed final int b0 = bins[item].getVal(); svars[b0].addToKernel(item, this, false); for (int b = 0; b < b0; b++) { svars[b].remFromEnveloppe(item, this, false); } for (int b = b0 + 1; b < svars.length; b++) { svars[b].remFromEnveloppe(item, this, false); } } else { for (int bin = 0; bin < svars.length; bin++) { if (svars[bin].isInDomainEnveloppe(item)) { //item could be packed here if (svars[bin].isInDomainKernel(item)) { //item is packed bins[item].instantiate(bin, this, false); } else if (!bins[item].fastCanBeInstantiatedTo(bin)) { //in fact, channeling fails svars[bin].remFromEnveloppe(item, this, false); } //channeling ok envelope-domain } else { //otherwise remove from domain bins[item].removeVal(bin, this, false); } } } } //Initial r & c load DisposableIntIterator iterK = null; DisposableIntIterator iterE = null; try { for (int i = 0; i < bSets.length; i++) { SetVar s = bSets[i]; int r = 0; iterK = s.getDomain().getKernelIterator(); while (iterK.hasNext()) { r += iSizes[iterK.next()]; } int c = 0; iterE = s.getDomain().getOpenDomainIterator(); while (iterE.hasNext()) { c += iSizes[iterE.next()]; } bRLoads[i] = env.makeInt(r); bCLoads[i] = env.makeInt(c); assert filtering.checkLoadConsistency(i); } } finally { if (iterK != null) { iterK.dispose(); } if (iterE != null) { iterE.dispose(); } } /* for (int i = 0; i < svars.length; i++) { ChocoLogging.getSearchLogger().finest(svars[i].pretty()); }*/ } protected void checkDeltaDomain(int item) throws ContradictionException { final DisposableIntIterator iter = bins[item].getDomain().getDeltaIterator(); if (iter.hasNext()) { try { while (iter.hasNext()) { final int b = iter.next(); svars[b].remFromEnveloppe(item, this, true); } } finally { iter.dispose(); } } else { throw new SolverException("empty delta domain for bin " + item); } } @Override public void awakeOnBounds(int varIndex) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(pretty() +" awakeOnBounds(" + varIndex + ")"); if (isItemEvent(varIndex)) { final int item = getItemIndex(varIndex); //the item is not packed //so, we can safely remove from other envelopes checkDeltaDomain(item); } this.constAwake(false); } @Override public void awakeOnInf(int varIdx) throws ContradictionException { awakeOnBounds(varIdx); } @Override public void awakeOnInst(int varIdx) throws ContradictionException { if (isSetEvent(varIdx)) { //ChocoLogging.getSearchLogger().finest(svars[varIdx].pretty() + " awakeOnSetInst(" + svars[varIdx].pretty() + ") r=" + bRLoads[varIdx].get() + " c=" + bCLoads[varIdx].get()); DisposableIntIterator iter = svars[varIdx].getDomain().getKernelIterator(); try { while (iter.hasNext()) { final int item = iter.next(); if (!bins[item].isInstantiated()) { simplePack(item, varIdx); } } } finally { iter.dispose(); } iter = svars[varIdx].getDomain().getEnveloppeDomain().getDeltaIterator(); try { while (iter.hasNext()) { final int item = iter.next(); if (bins[item].fastCanBeInstantiatedTo(varIdx)) { simpleRemove(item, varIdx); } } } finally { iter.dispose(); } } else if (isItemEvent(varIdx)) { final int item = getItemIndex(varIdx); //ChocoLogging.getSearchLogger().finest(bins[item].pretty() +" awakeOnBinInst(" +bins[item].getVal()+ ")"); final int b = bins[item].getVal(); checkDeltaDomain(item); simplePack(item, b); } constAwake(false); } @Override public void awakeOnKer(int varIdx, int x) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[varIdx].pretty() +" awakeOnKer: +item " + x + " (" + bRLoads[varIdx].get() + "+" + iSizes[x] + ")"); this.bRLoads[varIdx].add(iSizes[x]); this.bCLoads[varIdx].add(-1 * iSizes[x]); simplePack(x, varIdx); //ChocoLogging.getSearchLogger().finest(svars[varIdx].pretty() +" awakeOnKer: r=" + bRLoads[varIdx].get()); this.constAwake(false); } @Override public void awakeOnEnv(int varIdx, int x) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(svars[varIdx].pretty() + " awakeOnEnv: -item " + x + " (" + bCLoads[varIdx].get() + "-" + iSizes[x] + ")"); bCLoads[varIdx].add(-1 * iSizes[x]); bins[x].removeVal(varIdx, this, true); //if the item is packed, update variables if (bins[x].isInstantiated()) { final int b = bins[x].getVal(); svars[b].addToKernel(x, this, true); } this.constAwake(false); } @Override public void awakeOnRem(int varIdx, int val) throws ContradictionException { if (isItemEvent(varIdx)) { int item = getItemIndex(varIdx); //ChocoLogging.getSearchLogger().finest(bins[item].pretty() + " awakeOnRem(" + item + ", " + val + ")"); svars[val].remFromEnveloppe(item, this, true); if (bins[item].isInstantiated()) { final int b = bins[item].getVal(); svars[b].addToKernel(item, this, true); } } this.constAwake(false); } @Override public void awakeOnSup(int varIdx) throws ContradictionException { //ChocoLogging.getSearchLogger().finest(pretty() + " awakeOnSup(" + varIdx + ")"); awakeOnBounds(varIdx); } @Override public void propagate() throws ContradictionException { do { //ChocoLogging.getSearchLogger().finest("propagate " + pretty()); filtering.propagate(); if (!bounds.computeBounds(false)) { fail(); } } while (updateNbNonEmpty(bounds.getMinimumNumberOfBins(), bounds.getMaximumNumberOfBins())); } @Override public boolean isSatisfied() { int[] l = new int[loads.length]; int[] c = new int[loads.length]; for (int i = 0; i < bins.length; i++) { final int b = bins[i].getVal(); if (!svars[b].isInDomainKernel(i)) { ChocoLogging.getBranchingLogger().warning("Bad channeling for " + svars[b] + " should contains " + i); return false; //check channeling } l[b] += iSizes[i]; c[b]++; } int nbb = 0; for (int i = 0; i < loads.length; i++) { if (svars[i].getCard().getVal() != c[i]) { ChocoLogging.getBranchingLogger().warning("card of set " + i + " = " + svars[i].getCard().getVal() + " expected=" + c[i]); return false; //check cardinality } if (loads[i].getVal() != l[i]) { ChocoLogging.getBranchingLogger().warning("Load of " + i + " = " + loads[i].getVal() + " expected=" + l[i]); return false; //check load } if (c[i] != 0) { nbb++; } } boolean b = ivars[ivars.length - 1].getVal() == nbb; if (!b) { ChocoLogging.getBranchingLogger().warning("Bad number of bins: " + ivars[ivars.length - 1].getVal() + " but want " + nbb); } return b;//check number of bins } protected final class BoundNumberOfBins { private final int[] remainingSpace; private final TIntArrayList itemsMLB; protected int capacityMLB; private final TIntArrayList binsMLB; private int sizeIMLB; private int totalSizeCLB; private final TIntArrayList binsCLB; protected int nbEmpty; protected int nbSome; protected int nbFull; protected int nbNewCLB; private final TIntProcedure minimumNumberOfNewBins = new TIntProcedure() { @Override public boolean execute(int arg0) { nbNewCLB++; if (totalSizeCLB <= arg0) { return false; } totalSizeCLB -= arg0; return true; } }; public BoundNumberOfBins() { itemsMLB = new TIntArrayList(getNbBins() + getNbItems()); binsMLB = new TIntArrayList(getNbBins()); binsCLB = new TIntArrayList(getNbBins()); remainingSpace = new int[getNbBins()]; } public void reset() { Arrays.fill(remainingSpace, 0); itemsMLB.resetQuick(); capacityMLB = 0; binsMLB.resetQuick(); totalSizeCLB = 0; binsCLB.resetQuick(); nbEmpty = 0; nbSome = 0; nbFull = 0; nbNewCLB = 0; } /** * add unpacked items (MLB) compute their total size (CLB). */ private void handleItems() { final int n = getNbItems(); for (int i = 0; i < n; i++) { final int iSize = iSizes[i]; if (bins[i].isInstantiated()) { remainingSpace[bins[i].getVal()] -= iSize; } else { totalSizeCLB += iSize; itemsMLB.add(iSize); } } sizeIMLB = itemsMLB.size(); } /** * compute the remaining space in each bin and the cardinality of sets (empty, partially filled, full) */ private void handleBins() { final int n = getNbBins(); //compute the number of empty, partially filled and closed bins //also compute the remaining space in each open bins for (int b = 0; b < n; b++) { if (svars[b].isInstantiated()) { //we ignore closed bins if (loads[b].isInstantiatedTo(0)) { nbEmpty++; } else { nbFull++; } } else { //the bins is used by the modified lower bound binsMLB.add(b); remainingSpace[b] += loads[b].getSup(); capacityMLB = Math.max(capacityMLB, remainingSpace[b]); if (svars[b].getKernelDomainSize() > 0) { //partially filled nbSome++; totalSizeCLB -= remainingSpace[b]; //fill partially filled bin before empty ones } else { //still empty binsCLB.add(remainingSpace[b]); //record empty bins to fill them later } } } } private void computeMinimumNumberOfNewBins() { binsCLB.sort(); binsCLB.forEachDescending(minimumNumberOfNewBins); } /** * @param useDDFF do we use advanced and costly bounding procedure for a feasibility test. * @return <code>false</code> if the current state is infeasible. */ public boolean computeBounds(boolean useDDFF) { reset(); //the order of the following calls is important handleItems(); handleBins(); if (!itemsMLB.isEmpty()) { //if( sizeMLB < maximumNumberOfNewBins.get() ) maximumNumberOfNewBins.set(sizeMLB); //there is unpacked items if (totalSizeCLB > 0) { //compute an estimation of the minimal number of additional bins. if (binsCLB.isEmpty()) { return false; //no more available bins for remaining unpacked items } computeMinimumNumberOfNewBins(); } if (getMinimumNumberOfBins() > ivars[ivars.length - 1].getSup()) { return false; //the continuous bound prove infeasibility } } return true; } public int getMaximumNumberOfBins() { return Math.min(getNbBins() - nbEmpty, nbFull + nbSome + sizeIMLB); } public int getMinimumNumberOfBins() { return nbFull + nbSome + nbNewCLB; } } public String prettyEnvelop(int bIdx) { DisposableIntIterator ite = svars[bIdx].getDomain().getOpenDomainIterator(); StringBuilder b = new StringBuilder("{"); while (ite.hasNext()) { int i = ite.next(); b.append(i); if (ite.hasNext()) { b.append(", "); } } ite.dispose(); b.append("}"); return b.toString(); } public String prettyKernel(int bIdx) { DisposableIntIterator ite = svars[bIdx].getDomain().getKernelIterator(); StringBuilder b = new StringBuilder("{"); while (ite.hasNext()) { int i = ite.next(); b.append(i); if (ite.hasNext()) { b.append(", "); } } ite.dispose(); b.append("}"); return b.toString(); } @Override public IStateBitSet getCandidates(int bin) { return null; } }