All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.skjolberg.packing.BruteForcePackager Maven / Gradle / Ivy

There is a newer version: 1.2.5
Show newest version
package com.github.skjolberg.packing;

import com.github.skjolberg.packing.impl.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;


/**
 * Fit boxes into container, i.e. perform bin packing to a single container.
 * 

* This attempts a brute force approach, which is very demanding in terms of resources. For use in scenarios with 'few' * boxes, where the complexity of a 'few' can be measured for a specific set of boxes and containers using {@linkplain * PermutationRotationIterator#countPermutations()} * {@linkplain PermutationRotationIterator#countRotations()}. *

* Thread-safe implementation. The input Boxes can be used by multiple threads at a time. */ public class BruteForcePackager extends Packager { /** * Constructor * * @param containers list of containers * @param rotate3D whether boxes can be rotated in all three directions (two directions otherwise) * @param binarySearch if true, the packager attempts to find the best box given a binary search. Upon finding a * container that can hold the boxes, given time, it also tries to find a better match. */ public BruteForcePackager(List containers, boolean rotate3D, boolean binarySearch) { super(containers, rotate3D, binarySearch); } /** * Constructor * * @param containers list of containers */ public BruteForcePackager(List containers) { this(containers, true, true); } public BruteForceResult pack(List placements, Container container, PermutationRotationIterator rotator, long deadline) { return pack(placements, container, rotator, deadLinePredicate(deadline)); } public BruteForceResult pack(List placements, Container container, PermutationRotationIterator rotator, long deadline, AtomicBoolean interrupt) { return pack(placements, container, rotator, () -> deadlineReached(deadline) || interrupt.get()); } public BruteForceResult pack(List placements, Container container, PermutationRotationIterator rotator, BooleanSupplier interrupt) { Container holder = new Container(container); BruteForceResult result = new BruteForceResult(rotator, placements, holder); // iterator over all permutations do { if (interrupt.getAsBoolean()) { return null; } // iterator over all rotations do { int count = pack(placements, holder, rotator, holder, 0, interrupt); if (count == Integer.MIN_VALUE) { return null; // timeout } else { holder.clear(); if (count == placements.size()) { if (accept()) { result.setCount(count); result.setState(rotator.getState()); return result; } } else if (count > 0) { // continue search, but see if this is the best fit so far if (count > result.getCount()) { result.setCount(count); result.setState(rotator.getState()); } } } } while (rotator.nextRotation()); } while (rotator.nextPermutation()); return result; } public static int pack(List placements, Dimension container, PermutationRotationIterator rotator, long deadline, Container holder, int index) { return pack(placements, container, rotator, holder, index, deadLinePredicate(deadline)); } public static int pack(List placements, Dimension container, PermutationRotationIterator rotator, long deadline, Container holder, int index, AtomicBoolean interrupt) { return pack(placements, container, rotator, holder, index, () -> deadlineReached(deadline) || interrupt.get()); } public static int pack(List placements, Dimension container, PermutationRotationIterator rotator, Container holder, int index, BooleanSupplier interrupt) { if (placements.isEmpty()) { return -1; } Dimension remainingSpace = container; while (index < rotator.length()) { if (interrupt.getAsBoolean()) { // fit2d below might have returned due to deadline return Integer.MIN_VALUE; } Box box = rotator.get(index); if (box.getWeight() > holder.getFreeWeight()) { return index; } if (!remainingSpace.canHold2D(box)) { return index; } Placement placement = placements.get(index); Space levelSpace = placement.getSpace(); levelSpace.width = remainingSpace.getWidth(); levelSpace.depth = remainingSpace.getDepth(); // the LAFF allocates a height per level equal to the given the current box, // but here allow for use of all of the remaining space; the selection of boxes is fixed // the result will be constrained to actual use. levelSpace.height = remainingSpace.getHeight(); placement.setBox(box); levelSpace.setX(0); levelSpace.setY(0); levelSpace.setZ(holder.getStackHeight()); levelSpace.setParent(null); levelSpace.getRemainder().setParent(null); holder.addLevel(); index = fit2D(rotator, index + 1, placements, holder, placement, interrupt); // update remaining space remainingSpace = holder.getFreeSpace(); } return index; } protected boolean accept() { return true; } private static int fit2D(PermutationRotationIterator rotator, int index, List placements, Container holder, Placement usedSpace, BooleanSupplier interrupt) { // add used space box now // there is up to 2 possible free spaces holder.add(usedSpace); if (index >= rotator.length()) { return index; } if (interrupt.getAsBoolean()) { return -1; } Box nextBox = rotator.get(index); if (nextBox.getWeight() > holder.getFreeWeight()) { return index; } Placement nextPlacement = placements.get(index); nextPlacement.setBox(nextBox); boolean room = isFreeSpace(usedSpace.getSpace(), usedSpace.getBox(), nextPlacement); if (room) { index++; // the correct space dimensions is copied into the next placement // attempt to fit in the remaining (usually smaller) space first // stack in the 'sibling' space - the space left over between the used box and the selected free space if (index < rotator.length()) { Space remainder = nextPlacement.getSpace().getRemainder(); if (remainder.nonEmpty()) { Box box = rotator.get(index); if (box.getWeight() <= holder.getFreeWeight()) { if (box.fitsInside3D(remainder)) { Placement placement = placements.get(index); placement.setBox(box); index++; placement.getSpace().copyFrom(remainder); placement.getSpace().setParent(remainder); placement.getSpace().getRemainder().setParent(remainder); index = fit2D(rotator, index, placements, holder, placement, interrupt); if(index == -1) { return -1; } } } } } // fit the next box in the selected free space // double check that there is still free weight if (holder.getFreeWeight() >= nextBox.getWeight()) { index = fit2D(rotator, index, placements, holder, nextPlacement, interrupt); if(index == -1) { return -1; } } } else { // no additional boxes can be placed along the level floor } // also use the space above the placed box, if any. if (index < rotator.length()) { if(usedSpace.getSpace().getHeight() > usedSpace.getBox().getHeight()) { // so there is some free room; between the used space and the level height nextBox = rotator.get(index); if (nextBox.getWeight() > holder.getFreeWeight()) { return index; } // the level by level approach is somewhat crude, but at least some of the inefficiency // can be avoided this way Space free = usedSpace.getSpace(); Box used = usedSpace.getBox(); Space above; if(!room) { // full width / depth above = new Space( free.getWidth(), free.getDepth(), free.getHeight() - used.getHeight(), free.getX(), free.getY(), free.getZ() + used.getHeight() ); } else { // just directly above the used space // TODO possible include the sibling space if no box was fitted there above = new Space( used.getWidth(), used.getDepth(), free.getHeight() - used.getHeight(), free.getX(), free.getY(), free.getZ() + used.getHeight() ); } Placement placement = placements.get(index); Space levelSpace = placement.getSpace(); levelSpace.copyFrom(above); placement.setBox(nextBox); levelSpace.setParent(usedSpace.getSpace()); levelSpace.getRemainder().setParent(usedSpace.getSpace()); if (above.fitsInside3D(nextBox)) { index++; index = fit2D(rotator, index, placements, holder, nextPlacement, interrupt); } } } return index; } private static boolean isFreeSpace(Space freeSpace, Box used, Placement target) { // Two free spaces, on each rotation of the used space. // Height is always the same, used box is assumed within free space height. // First: // ........................ ........................ ............. // . . . . . . // . . . . . . // . A . . A . . . // . . . . . . // . B . . . . B . // ............ . ........................ . . // . . . . . // . . . . . // ........................ ............. // // So there is always a 'big' and a 'small' leftover area (the small is not shown). if (freeSpace.getWidth() >= used.getWidth() && freeSpace.getDepth() >= used.getDepth()) { // if B is empty, then it is sufficient to work with A and the other way around int b = (freeSpace.getWidth() - used.getWidth()) * freeSpace.getDepth(); int a = freeSpace.getWidth() * (freeSpace.getDepth() - used.getDepth()); // pick the one with largest footprint. if (b >= a) { if (b > 0 && b(freeSpace, used, target)) { return true; } return a > 0 && a(freeSpace, used, target); } else { if (a > 0 && a(freeSpace, used, target)) { return true; } return b > 0 && b(freeSpace, used, target); } } return false; } private static boolean a(Space freeSpace, Box used, Placement target) { if (target.getBox().fitsInside3D(freeSpace.getWidth(), freeSpace.getDepth() - used.getDepth(), freeSpace.getHeight())) { target.getSpace().copyFrom( freeSpace.getWidth(), freeSpace.getDepth() - used.getDepth(), freeSpace.getHeight(), freeSpace.getX(), freeSpace.getY() + used.depth, freeSpace.getZ() ); target.getSpace().getRemainder().copyFrom( freeSpace.getWidth() - used.getWidth(), used.getDepth(), freeSpace.getHeight(), freeSpace.getX() + used.getWidth(), freeSpace.getY(), freeSpace.getZ() ); target.getSpace().setParent(freeSpace); target.getSpace().getRemainder().setParent(freeSpace); return true; } return false; } private static boolean b(Space freeSpace, Box used, Placement target) { if (target.getBox().fitsInside3D(freeSpace.getWidth() - used.getWidth(), freeSpace.getDepth(), freeSpace.getHeight())) { // we have a winner target.getSpace().copyFrom( freeSpace.getWidth() - used.getWidth(), freeSpace.getDepth(), freeSpace.getHeight(), freeSpace.getX() + used.getWidth(), freeSpace.getY(), freeSpace.getZ() ); target.getSpace().getRemainder().copyFrom( used.getWidth(), freeSpace.getDepth() - used.getDepth(), freeSpace.getHeight(), freeSpace.getX(), freeSpace.getY() + used.getDepth(), freeSpace.getZ() ); target.getSpace().setParent(freeSpace); target.getSpace().getRemainder().setParent(freeSpace); return true; } return false; } @Override protected Adapter adapter() { // instead of placing boxes, work with placements // this very much reduces the number of objects created // performance gain is something like 25% over the box-centric approach return new Adapter() { private List placements; private PermutationRotationIterator[] iterators; private List containers; @Override public PackResult attempt(int i, BooleanSupplier interrupt) { return BruteForcePackager.this.pack(placements, containers.get(i), iterators[i], interrupt); } @Override public void initialize(List boxes, List containers) { this.containers = containers; PermutationRotation[] rotations = PermutationRotationIterator.toRotationMatrix(boxes, rotate3D); int count = 0; for (PermutationRotation permutationRotation : rotations) { count += permutationRotation.getCount(); } placements = getPlacements(count); iterators = new PermutationRotationIterator[containers.size()]; for (int i = 0; i < containers.size(); i++) { iterators[i] = new PermutationRotationIterator(containers.get(i), rotations); } } @Override public Container accepted(PackResult result) { BruteForceResult bruteForceResult = (BruteForceResult) result; Container container = bruteForceResult.getContainer(); if (bruteForceResult.isRemainder()) { int[] permutations = bruteForceResult.getRotator().getPermutations(); List p = new ArrayList<>(bruteForceResult.getCount()); for (int i = 0; i < bruteForceResult.getCount(); i++) { p.add(permutations[i]); } for (PermutationRotationIterator it : iterators) { if (it == bruteForceResult.getRotator()) { it.removePermutations(bruteForceResult.getCount()); } else { it.removePermutations(p); } } placements = placements.subList(bruteForceResult.getCount(), this.placements.size()); } else { placements = Collections.emptyList(); } return container; } @Override public boolean hasMore(PackResult result) { BruteForceResult bruteForceResult = (BruteForceResult) result; return placements.size() > bruteForceResult.getCount(); } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy