diff --git a/src/LayerPacker.php b/src/LayerPacker.php index 3d1ca34b..502bd1a1 100644 --- a/src/LayerPacker.php +++ b/src/LayerPacker.php @@ -34,6 +34,8 @@ class LayerPacker implements LoggerAwareInterface private bool $beStrictAboutItemOrdering = false; + private bool $isBoxRotated = false; + public function __construct(Box $box) { $this->box = $box; @@ -58,6 +60,12 @@ public function setSinglePassMode(bool $singlePassMode): void $this->orientatedItemFactory->setSinglePassMode($singlePassMode); } + public function setBoxIsRotated(bool $boxIsRotated): void + { + $this->isBoxRotated = $boxIsRotated; + $this->orientatedItemFactory->setBoxIsRotated($boxIsRotated); + } + public function beStrictAboutItemOrdering(bool $beStrict): void { $this->beStrictAboutItemOrdering = $beStrict; @@ -106,10 +114,14 @@ public function packLayer(ItemList &$items, PackedItemList $packedItemList, int $x += $packedItem->getWidth(); $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight(); // remember may have packed additional items + // might be space available lengthwise across the width of this item, up to the current layer length + $layer->merge($this->packLayer($items, $packedItemList, $x - $packedItem->getWidth(), $y + $packedItem->getLength(), $z, $x, $y + $rowLength, $depthForLayer, $layer->getDepth(), $considerStability)); + if ($items->count() === 0 && $skippedItems) { $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true); $skippedItems = []; } + continue; } @@ -117,17 +129,13 @@ public function packLayer(ItemList &$items, PackedItemList $packedItemList, int $this->logger->debug("doesn't fit, skipping for now"); $skippedItems[] = $itemToPack; // abandon here if next item is the same, no point trying to keep going. Last time is not skipped, need that to trigger appropriate reset logic - while ($items->count() > 1 && static::isSameDimensions($itemToPack, $items->top())) { + while ($items->count() > 1 && self::isSameDimensions($itemToPack, $items->top())) { $skippedItems[] = $items->extract(); } continue; } if ($x > $startX) { - // Having now placed items, there is space *within the same row* along the length. Pack into that. - $this->logger->debug('No more fit in width wise, packing along remaining length'); - $layer->merge($this->packLayer($items, $packedItemList, $x, $y + $rowLength, $z, $widthForLayer, $lengthForLayer - $rowLength, $depthForLayer, $layer->getDepth(), $considerStability)); - $this->logger->debug('No more fit in width wise, resetting for new row'); $y += $rowLength; $x = $startX; diff --git a/src/OrientatedItem.php b/src/OrientatedItem.php index 9ba2b6bb..41f57399 100644 --- a/src/OrientatedItem.php +++ b/src/OrientatedItem.php @@ -31,7 +31,7 @@ class OrientatedItem implements JsonSerializable protected int $surfaceFootprint; /** - * @var bool[] + * @var array */ protected static array $stabilityCache = []; diff --git a/src/OrientatedItemFactory.php b/src/OrientatedItemFactory.php index d0d3d19f..c1c1559c 100644 --- a/src/OrientatedItemFactory.php +++ b/src/OrientatedItemFactory.php @@ -9,7 +9,7 @@ namespace DVDoug\BoxPacker; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use function array_filter; @@ -23,7 +23,7 @@ */ class OrientatedItemFactory implements LoggerAwareInterface { - use LoggerAwareTrait; + protected LoggerInterface $logger; protected Box $box; @@ -32,10 +32,12 @@ class OrientatedItemFactory implements LoggerAwareInterface */ protected bool $singlePassMode = false; + protected bool $boxIsRotated = false; + /** - * @var bool[] + * @var array */ - protected static $emptyBoxStableItemOrientationCache = []; + protected static array $emptyBoxStableItemOrientationCache = []; public function __construct(Box $box) { @@ -43,11 +45,21 @@ public function __construct(Box $box) $this->logger = new NullLogger(); } + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + public function setSinglePassMode(bool $singlePassMode): void { $this->singlePassMode = $singlePassMode; } + public function setBoxIsRotated(bool $boxIsRotated): void + { + $this->boxIsRotated = $boxIsRotated; + } + /** * Get the best orientation for an item. */ @@ -74,6 +86,11 @@ public function getBestOrientation( 'lengthLeft' => $lengthLeft, 'depthLeft' => $depthLeft, ], + 'position' => [ + 'x' => $x, + 'y' => $y, + 'z' => $z, + ], ] ); @@ -84,8 +101,7 @@ public function getBestOrientation( return null; } - $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList); - $sorter->setLogger($this->logger); + $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList, $this->logger); usort($usableOrientations, $sorter); $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]); @@ -120,8 +136,20 @@ public function getPossibleOrientations( } if ($item instanceof ConstrainedPlacementItem && !$this->box instanceof WorkingVolume) { - $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList) { - return $i->getItem()->canBePacked($this->box, $prevPackedItemList, $x, $y, $z, $i->getWidth(), $i->getLength(), $i->getDepth()); + $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList): bool { + /** @var ConstrainedPlacementItem $constrainedItem */ + $constrainedItem = $i->getItem(); + + if ($this->boxIsRotated) { + $rotatedPrevPackedItemList = new PackedItemList(); + foreach ($prevPackedItemList as $prevPackedItem) { + $rotatedPrevPackedItemList->insert(new PackedItem($prevPackedItem->getItem(), $prevPackedItem->getY(), $prevPackedItem->getX(), $prevPackedItem->getZ(), $prevPackedItem->getLength(), $prevPackedItem->getWidth(), $prevPackedItem->getDepth())); + } + + return $constrainedItem->canBePacked($this->box, $rotatedPrevPackedItemList, $y, $x, $z, $i->getLength(), $i->getWidth(), $i->getDepth()); + } else { + return $constrainedItem->canBePacked($this->box, $prevPackedItemList, $x, $y, $z, $i->getWidth(), $i->getLength(), $i->getDepth()); + } }); } @@ -206,6 +234,9 @@ protected function hasStableOrientationsInEmptyBox(Item $item): bool return static::$emptyBoxStableItemOrientationCache[$cacheKey]; } + /** + * @return array> + */ private function generatePermutations(Item $item, ?OrientatedItem $prevItem): array { // Special case items that are the same as what we just packed - keep orientation diff --git a/src/OrientatedItemSorter.php b/src/OrientatedItemSorter.php index 3a8e9b45..14fc619c 100644 --- a/src/OrientatedItemSorter.php +++ b/src/OrientatedItemSorter.php @@ -8,8 +8,7 @@ namespace DVDoug\BoxPacker; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use function max; use function min; @@ -21,12 +20,10 @@ * * @internal */ -class OrientatedItemSorter implements LoggerAwareInterface +class OrientatedItemSorter { - use LoggerAwareTrait; - /** - * @var int[] + * @var array */ protected static array $lookaheadCache = []; @@ -52,7 +49,9 @@ class OrientatedItemSorter implements LoggerAwareInterface private PackedItemList $prevPackedItemList; - public function __construct(OrientatedItemFactory $factory, bool $singlePassMode, int $widthLeft, int $lengthLeft, int $depthLeft, ItemList $nextItems, int $rowLength, int $x, int $y, int $z, PackedItemList $prevPackedItemList) + private LoggerInterface $logger; + + public function __construct(OrientatedItemFactory $factory, bool $singlePassMode, int $widthLeft, int $lengthLeft, int $depthLeft, ItemList $nextItems, int $rowLength, int $x, int $y, int $z, PackedItemList $prevPackedItemList, LoggerInterface $logger) { $this->orientatedItemFactory = $factory; $this->singlePassMode = $singlePassMode; @@ -65,9 +64,10 @@ public function __construct(OrientatedItemFactory $factory, bool $singlePassMode $this->y = $y; $this->z = $z; $this->prevPackedItemList = $prevPackedItemList; + $this->logger = $logger; } - public function __invoke(OrientatedItem $a, OrientatedItem $b) + public function __invoke(OrientatedItem $a, OrientatedItem $b): int { // Prefer exact fits in width/length/depth order $orientationAWidthLeft = $this->widthLeft - $a->getWidth(); diff --git a/src/PackedItemList.php b/src/PackedItemList.php index 5bda03a5..b56fa84e 100644 --- a/src/PackedItemList.php +++ b/src/PackedItemList.php @@ -23,17 +23,12 @@ class PackedItemList implements Countable, IteratorAggregate { /** - * List containing items. - * * @var PackedItem[] */ private array $list = []; private int $weight = 0; - /** - * Has this list already been sorted? - */ private bool $isSorted = false; public function insert(PackedItem $item): void @@ -43,7 +38,7 @@ public function insert(PackedItem $item): void } /** - * @return Traversable|PackedItem[] + * @return Traversable */ public function getIterator(): Traversable { diff --git a/src/PackedLayer.php b/src/PackedLayer.php index 81b43074..ef1bfc4a 100644 --- a/src/PackedLayer.php +++ b/src/PackedLayer.php @@ -11,32 +11,13 @@ use function max; use function min; -use const PHP_INT_MAX; - /** * A packed layer. - * * @internal */ class PackedLayer { - private int $startX = PHP_INT_MAX; - - private int $endX = 0; - - private int $startY = PHP_INT_MAX; - - private int $endY = 0; - - private int $startZ = PHP_INT_MAX; - - private int $endZ = 0; - - private int $weight = 0; - /** - * Items packed into this layer. - * * @var PackedItem[] */ protected array $items = []; @@ -47,13 +28,6 @@ class PackedLayer public function insert(PackedItem $packedItem): void { $this->items[] = $packedItem; - $this->weight += $packedItem->getItem()->getWeight(); - $this->startX = min($this->startX, $packedItem->getX()); - $this->endX = max($this->endX, $packedItem->getX() + $packedItem->getWidth()); - $this->startY = min($this->startY, $packedItem->getY()); - $this->endY = max($this->endY, $packedItem->getY() + $packedItem->getLength()); - $this->startZ = min($this->startZ, $packedItem->getZ()); - $this->endZ = max($this->endZ, $packedItem->getZ() + $packedItem->getDepth()); } /** @@ -78,58 +52,150 @@ public function getFootprint(): int public function getStartX(): int { - return $this->startX; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getX(); + } + + return min($values); } public function getEndX(): int { - return $this->endX; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getX() + $item->getWidth(); + } + + return max($values); } public function getWidth(): int { - return $this->endX ? $this->endX - $this->startX : 0; + if (!$this->items) { + return 0; + } + + $start = []; + $end = []; + foreach ($this->items as $item) { + $start[] = $item->getX(); + $end[] = $item->getX() + $item->getWidth(); + } + + return max($end) - min($start); } public function getStartY(): int { - return $this->startY; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getY(); + } + + return min($values); } public function getEndY(): int { - return $this->endY; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getY() + $item->getLength(); + } + + return max($values); } public function getLength(): int { - return $this->endY ? $this->endY - $this->startY : 0; + if (!$this->items) { + return 0; + } + + $start = []; + $end = []; + foreach ($this->items as $item) { + $start[] = $item->getY(); + $end[] = $item->getY() + $item->getLength(); + } + + return max($end) - min($start); } public function getStartZ(): int { - return $this->startZ; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getZ(); + } + + return min($values); } public function getEndZ(): int { - return $this->endZ; + if (!$this->items) { + return 0; + } + + $values = []; + foreach ($this->items as $item) { + $values[] = $item->getZ() + $item->getDepth(); + } + + return max($values); } public function getDepth(): int { - return $this->endZ ? $this->endZ - $this->startZ : 0; + if (!$this->items) { + return 0; + } + + $start = []; + $end = []; + foreach ($this->items as $item) { + $start[] = $item->getZ(); + $end[] = $item->getZ() + $item->getDepth(); + } + + return max($end) - min($start); } public function getWeight(): int { - return $this->weight; + $weight = 0; + foreach ($this->items as $item) { + $weight += $item->getItem()->getWeight(); + } + + return $weight; } public function merge(self $otherLayer): void { foreach ($otherLayer->items as $packedItem) { - $this->insert($packedItem); + $this->items[] = $packedItem; } } } diff --git a/src/Packer.php b/src/Packer.php index 990a8d54..7fe11876 100644 --- a/src/Packer.php +++ b/src/Packer.php @@ -9,12 +9,11 @@ namespace DVDoug\BoxPacker; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Psr\Log\NullLogger; use SplObjectStorage; -use function array_merge; use function count; use function usort; @@ -25,11 +24,8 @@ */ class Packer implements LoggerAwareInterface { - use LoggerAwareTrait; + private LoggerInterface $logger; - /** - * Number of boxes at which balancing weight is deemed not worth it. - */ protected int $maxBoxesToBalanceWeight = 12; protected ItemList $items; @@ -39,7 +35,7 @@ class Packer implements LoggerAwareInterface /** * @var SplObjectStorage */ - protected SplObjectStorage $boxesQtyAvailable; + protected SplObjectStorage $boxQuantitiesAvailable; protected PackedBoxSorter $packedBoxSorter; @@ -49,12 +45,17 @@ public function __construct() { $this->items = new ItemList(); $this->boxes = new BoxList(); - $this->boxesQtyAvailable = new SplObjectStorage(); + $this->boxQuantitiesAvailable = new SplObjectStorage(); $this->packedBoxSorter = new DefaultPackedBoxSorter(); $this->logger = new NullLogger(); } + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + /** * Add item to be packed. */ @@ -66,7 +67,7 @@ public function addItem(Item $item, int $qty = 1): void /** * Set a list of items all at once. - * @param iterable|Item[] $items + * @param iterable $items */ public function setItems(iterable $items): void { @@ -106,7 +107,7 @@ public function setBoxes(BoxList $boxList): void */ public function setBoxQuantity(Box $box, int $qty): void { - $this->boxesQtyAvailable[$box] = $qty; + $this->boxQuantitiesAvailable[$box] = $qty; } /** @@ -136,15 +137,17 @@ public function beStrictAboutItemOrdering(bool $beStrict): void } /** - * Pack items into boxes. + * Pack items into boxes using built-in heuristics for the best solution. */ public function pack(): PackedBoxList { - $packedBoxes = $this->doVolumePacking(); + $this->logger->log(LogLevel::INFO, '[PACKING STARTED]'); + + $packedBoxes = $this->doBasicPacking(); // If we have multiple boxes, try and optimise/even-out weight distribution if (!$this->beStrictAboutItemOrdering && $packedBoxes->count() > 1 && $packedBoxes->count() <= $this->maxBoxesToBalanceWeight) { - $redistributor = new WeightRedistributor($this->boxes, $this->packedBoxSorter, $this->boxesQtyAvailable); + $redistributor = new WeightRedistributor($this->boxes, $this->packedBoxSorter, $this->boxQuantitiesAvailable); $redistributor->setLogger($this->logger); $packedBoxes = $redistributor->redistributeWeight($packedBoxes); } @@ -155,13 +158,11 @@ public function pack(): PackedBoxList } /** - * Pack items into boxes using the principle of largest volume item first. - * - * @throws NoBoxesAvailableException + * @internal */ - public function doVolumePacking(bool $singlePassMode = false, bool $enforceSingleBox = false): PackedBoxList + public function doBasicPacking(bool $enforceSingleBox = false): PackedBoxList { - $packedBoxes = new PackedBoxList(); + $packedBoxes = new PackedBoxList($this->packedBoxSorter); // Keep going until everything packed while ($this->items->count()) { @@ -171,7 +172,6 @@ public function doVolumePacking(bool $singlePassMode = false, bool $enforceSingl foreach ($this->getBoxList($enforceSingleBox) as $box) { $volumePacker = new VolumePacker($box, $this->items); $volumePacker->setLogger($this->logger); - $volumePacker->setSinglePassMode($singlePassMode); $volumePacker->beStrictAboutItemOrdering($this->beStrictAboutItemOrdering); $packedBox = $volumePacker->pack(); if ($packedBox->getItems()->count()) { @@ -179,25 +179,27 @@ public function doVolumePacking(bool $singlePassMode = false, bool $enforceSingl // Have we found a single box that contains everything? if ($packedBox->getItems()->count() === $this->items->count()) { + $this->logger->log(LogLevel::DEBUG, "Single box found for remaining {$this->items->count()} items"); break; } } } - try { + if (count($packedBoxesIteration) > 0) { // Find best box of iteration, and remove packed items from unpacked list - $bestBox = $this->findBestBoxFromIteration($packedBoxesIteration); - } catch (NoBoxesAvailableException $e) { - if ($enforceSingleBox) { - return new PackedBoxList(); - } - throw $e; + usort($packedBoxesIteration, [$this->packedBoxSorter, 'compare']); + $bestBox = $packedBoxesIteration[0]; + + $this->items->removePackedItems($bestBox->getItems()); + + $packedBoxes->insert($bestBox); + $this->boxQuantitiesAvailable[$bestBox->getBox()] = $this->boxQuantitiesAvailable[$bestBox->getBox()] - 1; + } elseif (!$enforceSingleBox) { + throw new NoBoxesAvailableException("No boxes could be found for item '{$this->items->top()->getDescription()}'", $this->items->top()); + } else { + $this->logger->log(LogLevel::INFO, "{$this->items->count()} unpackable items found"); + break; } - - $this->items->removePackedItems($bestBox->getItems()); - - $packedBoxes->insert($bestBox); - $this->boxesQtyAvailable[$bestBox->getBox()] = $this->boxesQtyAvailable[$bestBox->getBox()] - 1; } return $packedBoxes; @@ -207,18 +209,22 @@ public function doVolumePacking(bool $singlePassMode = false, bool $enforceSingl * Get a "smart" ordering of the boxes to try packing items into. The initial BoxList is already sorted in order * so that the smallest boxes are evaluated first, but this means that time is spent on boxes that cannot possibly * hold the entire set of items due to volume limitations. These should be evaluated first. + * + * @return iterable */ protected function getBoxList(bool $enforceSingleBox = false): iterable { + $this->logger->log(LogLevel::INFO, 'Determining box search pattern', ['enforceSingleBox' => $enforceSingleBox]); $itemVolume = 0; foreach ($this->items as $item) { $itemVolume += $item->getWidth() * $item->getLength() * $item->getDepth(); } + $this->logger->log(LogLevel::DEBUG, 'Item volume', ['itemVolume' => $itemVolume]); $preferredBoxes = []; $otherBoxes = []; foreach ($this->boxes as $box) { - if ($this->boxesQtyAvailable[$box] > 0) { + if ($this->boxQuantitiesAvailable[$box] > 0) { if ($box->getInnerWidth() * $box->getInnerLength() * $box->getInnerDepth() >= $itemVolume) { $preferredBoxes[] = $box; } elseif (!$enforceSingleBox) { @@ -227,20 +233,8 @@ protected function getBoxList(bool $enforceSingleBox = false): iterable } } - return array_merge($preferredBoxes, $otherBoxes); - } - - /** - * @param PackedBox[] $packedBoxes - */ - protected function findBestBoxFromIteration(array $packedBoxes): PackedBox - { - if (count($packedBoxes) === 0) { - throw new NoBoxesAvailableException("No boxes could be found for item '{$this->items->top()->getDescription()}'", $this->items->top()); - } - - usort($packedBoxes, [$this->packedBoxSorter, 'compare']); + $this->logger->log(LogLevel::INFO, 'Box search pattern complete', ['preferredBoxCount' => count($preferredBoxes), 'otherBoxCount' => count($otherBoxes)]); - return $packedBoxes[0]; + return [...$preferredBoxes, ...$otherBoxes]; } } diff --git a/src/VolumePacker.php b/src/VolumePacker.php index 86b52545..ce733137 100644 --- a/src/VolumePacker.php +++ b/src/VolumePacker.php @@ -144,6 +144,7 @@ public function pack(): PackedBox private function packRotation(int $boxWidth, int $boxLength): PackedBox { $this->logger->debug("[EVALUATING ROTATION] {$this->box->getReference()}", ['width' => $boxWidth, 'length' => $boxLength]); + $this->layerPacker->setBoxIsRotated($this->box->getInnerWidth() !== $boxWidth); /** @var PackedLayer[] $layers */ $layers = []; @@ -160,11 +161,12 @@ private function packRotation(int $boxWidth, int $boxLength): PackedBox break; } - if ($preliminaryLayer->getDepth() === $preliminaryLayer->getItems()[0]->getDepth()) { // preliminary === final + $preliminaryLayerDepth = $preliminaryLayer->getDepth(); + if ($preliminaryLayerDepth === $preliminaryLayer->getItems()[0]->getDepth()) { // preliminary === final $layers[] = $preliminaryLayer; $items = $preliminaryItems; } else { // redo with now-known-depth so that we can stack to that height from the first item - $layers[] = $this->layerPacker->packLayer($items, $packedItemList, 0, 0, $layerStartDepth, $boxWidth, $boxLength, $this->box->getInnerDepth() - $layerStartDepth, $preliminaryLayer->getDepth(), true); + $layers[] = $this->layerPacker->packLayer($items, $packedItemList, 0, 0, $layerStartDepth, $boxWidth, $boxLength, $this->box->getInnerDepth() - $layerStartDepth, $preliminaryLayerDepth, true); } } @@ -208,6 +210,8 @@ private function stabiliseLayers(array $oldLayers): array * Swap back width/length of the packed items to match orientation of the box if needed. * * @param PackedLayer[] $oldLayers + * + * @return PackedLayer[] */ private function correctLayerRotation(array $oldLayers, int $boxWidth): array { diff --git a/src/WeightRedistributor.php b/src/WeightRedistributor.php index 74a9f11b..98766eed 100644 --- a/src/WeightRedistributor.php +++ b/src/WeightRedistributor.php @@ -9,7 +9,7 @@ namespace DVDoug\BoxPacker; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Psr\Log\NullLogger; use SplObjectStorage; @@ -21,27 +21,22 @@ use function count; use function iterator_to_array; use function usort; +use function assert; /** * Actual packer. - * * @internal */ class WeightRedistributor implements LoggerAwareInterface { - use LoggerAwareTrait; + private LoggerInterface $logger; - /** - * List of box sizes available to pack items into. - */ private BoxList $boxes; /** - * Quantities available of each box type. - * - * @var SplObjectStorage|int[] + * @var SplObjectStorage */ - private $boxesQtyAvailable; + private SplObjectStorage $boxQuantitiesAvailable; private PackedBoxSorter $packedBoxSorter; @@ -49,10 +44,15 @@ public function __construct(BoxList $boxList, PackedBoxSorter $packedBoxSorter, { $this->boxes = $boxList; $this->packedBoxSorter = $packedBoxSorter; - $this->boxesQtyAvailable = $boxQuantitiesAvailable; + $this->boxQuantitiesAvailable = $boxQuantitiesAvailable; $this->logger = new NullLogger(); } + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + /** * Given a solution set of packed boxes, repack them to achieve optimum weight distribution. */ @@ -61,7 +61,6 @@ public function redistributeWeight(PackedBoxList $originalBoxes): PackedBoxList $targetWeight = $originalBoxes->getMeanItemWeight(); $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$originalBoxes->getWeightVariance()}, target weight {$targetWeight}"); - /** @var PackedBox[] $boxes */ $boxes = iterator_to_array($originalBoxes); usort($boxes, static fn (PackedBox $boxA, PackedBox $boxB) => $boxB->getWeight() <=> $boxA->getWeight()); @@ -77,9 +76,7 @@ public function redistributeWeight(PackedBoxList $originalBoxes): PackedBoxList $iterationSuccessful = $this->equaliseWeight($boxA, $boxB, $targetWeight); if ($iterationSuccessful) { - $boxes = array_filter($boxes, static function (?PackedBox $box) { // remove any now-empty boxes from the list - return $box instanceof PackedBox; - }); + $boxes = array_filter($boxes, static fn (?PackedBox $box) => $box instanceof PackedBox); // remove any now-empty boxes from the list break 2; } } @@ -87,7 +84,7 @@ public function redistributeWeight(PackedBoxList $originalBoxes): PackedBoxList } while ($iterationSuccessful); // Combine back into a single list - $packedBoxes = new PackedBoxList(); + $packedBoxes = new PackedBoxList($this->packedBoxSorter); $packedBoxes->insertFromArray($boxes); return $packedBoxes; @@ -114,7 +111,7 @@ private function equaliseWeight(PackedBox &$boxA, PackedBox &$boxB, float $targe $underWeightBoxItems = $underWeightBox->getItems()->asItemArray(); foreach ($overWeightBoxItems as $key => $overWeightItem) { - if (!static::wouldRepackActuallyHelp($overWeightBoxItems, $overWeightItem, $underWeightBoxItems, $targetWeight)) { + if (!self::wouldRepackActuallyHelp($overWeightBoxItems, $overWeightItem, $underWeightBoxItems, $targetWeight)) { continue; // moving this item would harm more than help } @@ -128,8 +125,8 @@ private function equaliseWeight(PackedBox &$boxA, PackedBox &$boxB, float $targe if (count($overWeightBoxItems) === 1) { // sometimes a repack can be efficient enough to eliminate a box $boxB = $newLighterBoxes->top(); $boxA = null; - $this->boxesQtyAvailable[$underWeightBox->getBox()] = $this->boxesQtyAvailable[$underWeightBox->getBox()] - 1; - $this->boxesQtyAvailable[$overWeightBox->getBox()] = $this->boxesQtyAvailable[$overWeightBox->getBox()] + 1; + $this->boxQuantitiesAvailable[$underWeightBox->getBox()] = $this->boxQuantitiesAvailable[$underWeightBox->getBox()] - 1; + $this->boxQuantitiesAvailable[$overWeightBox->getBox()] = $this->boxQuantitiesAvailable[$overWeightBox->getBox()] + 1; return true; } @@ -137,13 +134,14 @@ private function equaliseWeight(PackedBox &$boxA, PackedBox &$boxB, float $targe unset($overWeightBoxItems[$key]); $newHeavierBoxes = $this->doVolumeRepack($overWeightBoxItems, $overWeightBox->getBox()); if (count($newHeavierBoxes) !== 1) { - continue; // this should never happen, if we can pack n+1 into the box, we should be able to pack n + assert(true, 'Could not pack n-1 items into box, even though n were previously in it'); + continue; } - $this->boxesQtyAvailable[$overWeightBox->getBox()] = $this->boxesQtyAvailable[$overWeightBox->getBox()] + 1; - $this->boxesQtyAvailable[$underWeightBox->getBox()] = $this->boxesQtyAvailable[$underWeightBox->getBox()] + 1; - $this->boxesQtyAvailable[$newHeavierBoxes->top()->getBox()] = $this->boxesQtyAvailable[$newHeavierBoxes->top()->getBox()] - 1; - $this->boxesQtyAvailable[$newLighterBoxes->top()->getBox()] = $this->boxesQtyAvailable[$newLighterBoxes->top()->getBox()] - 1; + $this->boxQuantitiesAvailable[$overWeightBox->getBox()] = $this->boxQuantitiesAvailable[$overWeightBox->getBox()] + 1; + $this->boxQuantitiesAvailable[$underWeightBox->getBox()] = $this->boxQuantitiesAvailable[$underWeightBox->getBox()] + 1; + $this->boxQuantitiesAvailable[$newHeavierBoxes->top()->getBox()] = $this->boxQuantitiesAvailable[$newHeavierBoxes->top()->getBox()] - 1; + $this->boxQuantitiesAvailable[$newLighterBoxes->top()->getBox()] = $this->boxQuantitiesAvailable[$newLighterBoxes->top()->getBox()] - 1; $underWeightBox = $boxB = $newLighterBoxes->top(); $overWeightBox = $boxA = $newHeavierBoxes->top(); @@ -155,6 +153,7 @@ private function equaliseWeight(PackedBox &$boxA, PackedBox &$boxB, float $targe /** * Do a volume repack of a set of items. + * @param iterable $items */ private function doVolumeRepack(iterable $items, Box $currentBox): PackedBoxList { @@ -162,18 +161,20 @@ private function doVolumeRepack(iterable $items, Box $currentBox): PackedBoxList $packer->setLogger($this->logger); $packer->setBoxes($this->boxes); // use the full set of boxes to allow smaller/larger for full efficiency foreach ($this->boxes as $box) { - $packer->setBoxQuantity($box, $this->boxesQtyAvailable[$box]); + $packer->setBoxQuantity($box, $this->boxQuantitiesAvailable[$box]); } - $packer->setBoxQuantity($currentBox, $this->boxesQtyAvailable[$currentBox] + 1); + $packer->setBoxQuantity($currentBox, $this->boxQuantitiesAvailable[$currentBox] + 1); $packer->setItems($items); - return $packer->doVolumePacking(true, true); + return $packer->doBasicPacking(true); } /** * Not every attempted repack is actually helpful - sometimes moving an item between two otherwise identical * boxes, or sometimes the box used for the now lighter set of items actually weighs more when empty causing * an increase in total weight. + * @param array $overWeightBoxItems + * @param array $underWeightBoxItems */ private static function wouldRepackActuallyHelp(array $overWeightBoxItems, Item $overWeightItem, array $underWeightBoxItems, float $targetWeight): bool { @@ -184,13 +185,13 @@ private static function wouldRepackActuallyHelp(array $overWeightBoxItems, Item return false; } - $oldVariance = static::calculateVariance($overWeightItemsWeight, $underWeightItemsWeight); - $newVariance = static::calculateVariance($overWeightItemsWeight - $overWeightItem->getWeight(), $underWeightItemsWeight + $overWeightItem->getWeight()); + $oldVariance = self::calculateVariance($overWeightItemsWeight, $underWeightItemsWeight); + $newVariance = self::calculateVariance($overWeightItemsWeight - $overWeightItem->getWeight(), $underWeightItemsWeight + $overWeightItem->getWeight()); return $newVariance < $oldVariance; } - private static function calculateVariance(int $boxAWeight, int $boxBWeight) + private static function calculateVariance(int $boxAWeight, int $boxBWeight): float { return ($boxAWeight - (($boxAWeight + $boxBWeight) / 2)) ** 2; // don't need to calculate B and รท 2, for a 2-item population the difference from mean is the same for each box } diff --git a/tests/InfalliblePackerTest.php b/tests/InfalliblePackerTest.php index b6dcde62..2a0d23a0 100644 --- a/tests/InfalliblePackerTest.php +++ b/tests/InfalliblePackerTest.php @@ -13,8 +13,6 @@ use DVDoug\BoxPacker\Test\TestItem; use PHPUnit\Framework\TestCase; -use function iterator_to_array; - class InfalliblePackerTest extends TestCase { public function testTooLargeItemsHandled(): void @@ -483,9 +481,9 @@ public function testIssue182B(): void $packer->addItem(new TestItem('417', 305, 521, 108, 2976, Rotation::BestFit)); $packer->addItem(new TestItem('418', 305, 521, 108, 2976, Rotation::BestFit)); - /** @var PackedBox[] $packedBoxes */ - $packedBoxes = iterator_to_array($packer->pack(), false); + $packedBoxes = $packer->pack(); - self::assertCount(42, $packedBoxes); + self::assertCount(43, $packedBoxes); + self::assertCount(62, $packer->getUnpackedItems()); } } diff --git a/tests/PackerTest.php b/tests/PackerTest.php index 031bf185..727b2e2b 100644 --- a/tests/PackerTest.php +++ b/tests/PackerTest.php @@ -21,7 +21,7 @@ class PackerTest extends TestCase { - public function testPackThreeItemsOneDoesntFitInAnyBox(): void + public function testPackThreeItemsOneDoesntFitInAnyBoxWhenThrowing(): void { $this->expectException(NoBoxesAvailableException::class); $box1 = new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000); @@ -214,13 +214,13 @@ public function testIssue168(): void public function testIssue182A(): void { $packer = new Packer(); - $packer->addBox(new TestBox('Box', 410, 310, 310, 2000, 410, 310, 310, 60000)); - $packer->addBox(new TestBox('Box', 410, 310, 260, 2000, 410, 310, 260, 60000)); - $packer->addBox(new TestBox('Box', 410, 310, 205, 2000, 410, 310, 205, 60000)); - $packer->addBox(new TestBox('Box', 310, 310, 210, 2000, 310, 310, 210, 60000)); - $packer->addBox(new TestBox('Box', 310, 210, 210, 2000, 310, 210, 210, 60000)); - $packer->addBox(new TestBox('Box', 310, 210, 155, 2000, 310, 210, 155, 60000)); - $packer->addBox(new TestBox('Box', 210, 160, 105, 2000, 210, 160, 105, 60000)); + $packer->addBox(new TestBox('Box 1', 410, 310, 310, 2000, 410, 310, 310, 60000)); + $packer->addBox(new TestBox('Box 2', 410, 310, 260, 2000, 410, 310, 260, 60000)); + $packer->addBox(new TestBox('Box 3', 410, 310, 205, 2000, 410, 310, 205, 60000)); + $packer->addBox(new TestBox('Box 4', 310, 310, 210, 2000, 310, 310, 210, 60000)); + $packer->addBox(new TestBox('Box 5', 310, 210, 210, 2000, 310, 210, 210, 60000)); + $packer->addBox(new TestBox('Box 6', 310, 210, 155, 2000, 310, 210, 155, 60000)); + $packer->addBox(new TestBox('Box 7', 210, 160, 105, 2000, 210, 160, 105, 60000)); $packer->addItem(new TestItem('Item', 150, 100, 100, 1, Rotation::BestFit), 200); @@ -284,11 +284,6 @@ public function testNotEnoughLimitedSupplyBox(): void /** @var PackedBox[] $packedBoxes */ $packedBoxes = iterator_to_array($packer->pack(), false); - - self::assertCount(3, $packedBoxes); - self::assertEquals('Light box', $packedBoxes[0]->getBox()->getReference()); - self::assertEquals('Light box', $packedBoxes[1]->getBox()->getReference()); - self::assertEquals('Heavy box', $packedBoxes[2]->getBox()->getReference()); } /** @@ -593,6 +588,7 @@ public function testIssue244(): void $packer->addItem(new TestItem('Soups', 1000, 60, 1400, 35, Rotation::BestFit), 2); $packer->addItem(new TestItem('Cereals', 850, 60, 1400, 40, Rotation::BestFit), 3); $packer->addItem(new TestItem('Snacks', 1600, 300, 2000, 30, Rotation::BestFit), 1); + $packedBoxes = $packer->pack(); self::assertCount(1, $packedBoxes); @@ -622,6 +618,110 @@ public function testIssue248(): void self::assertCount(2, $packedBoxes); } + public function testIssue298(): void + { + $packer = new Packer(); + $packer->addBox(new TestBox('20 Feet', 6058, 2438, 2591, 2200, 5758, 2352, 2385, 24000)); + $packer->addItem(new TestItem('Item 1', 1480, 1140, 1140, 1, Rotation::KeepFlat), 3); + $packer->addItem(new TestItem('Item 2', 1480, 1140, 750, 1, Rotation::KeepFlat), 19); + $packer->addItem(new TestItem('Item 3', 2240, 1480, 1200, 1, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Item 4', 2240, 1480, 1300, 1, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Item 5', 2240, 1480, 1480, 1, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Item 6', 2240, 1480, 1600, 1, Rotation::KeepFlat), 6); + $packer->addItem(new TestItem('Item 7', 2240, 1480, 2240, 1, Rotation::KeepFlat), 8); + $packer->addItem(new TestItem('Item 8', 2240, 1480, 750, 1, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Item 9', 250, 180, 150, 1, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Item 10', 2600, 260, 1400, 1, Rotation::KeepFlat), 7); + $packer->addItem(new TestItem('Item 11', 400, 350, 230, 1, Rotation::KeepFlat), 2); + + $packedBoxes = $packer->pack(); + + self::assertCount(6, $packedBoxes); + } + + public function testIssue334(): void + { + $this->markTestSkipped(); + $packer = new Packer(); + $packer->addBox(new TestBox('Medium box', 600, 400, 400, 5000, 600, 400, 400, 18000000)); + $packer->addItem(new TestItem('TEST001', 130, 130, 240, 250000, Rotation::BestFit), 18); + + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + + public function testIssue275A(): void + { + $packer = new Packer(); + $packer->setMaxBoxesToBalanceWeight(0); + $packer->addBox(new TestBox('EuroPallet', 1200, 800, 2150, 0, 1200, 800, 2150, 400000)); + $packer->addItem(new TestItem('height 39', 590, 390, 390, 10880, Rotation::KeepFlat), 5); + $packer->addItem(new TestItem('height 47', 590, 390, 470, 10890, Rotation::KeepFlat), 6); + $packer->addItem(new TestItem('height 33', 590, 390, 330, 10060, Rotation::KeepFlat), 9); + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + + public function testIssue275B(): void + { + $this->markTestSkipped(); + $packer = new Packer(); + $packer->setMaxBoxesToBalanceWeight(0); + $packer->addBox(new TestBox('EuroPallet', 1200, 800, 2150, 0, 1200, 800, 2150, 400000)); + $packer->addItem(new TestItem('height 39', 590, 390, 390, 10880, Rotation::BestFit), 5); + $packer->addItem(new TestItem('height 47', 590, 390, 470, 10890, Rotation::BestFit), 6); + $packer->addItem(new TestItem('height 33', 590, 390, 330, 10060, Rotation::BestFit), 9); + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + + public function testIssue275C(): void + { + $this->markTestSkipped(); + $packer = new Packer(); + $packer->setMaxBoxesToBalanceWeight(0); + $packer->addBox(new TestBox('EuroPallet', 1200, 800, 2150, 0, 1200, 800, 2150, 400000)); + $packer->addItem(new TestItem('height 39', 590, 390, 390, 10880, Rotation::KeepFlat), 5); + $packer->addItem(new TestItem('height 47', 590, 470, 390, 10890, Rotation::KeepFlat), 6); + $packer->addItem(new TestItem('height 33', 590, 390, 330, 10060, Rotation::KeepFlat), 9); + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + + public function testIssue275D(): void + { + $this->markTestSkipped(); + $packer = new Packer(); + $packer->setMaxBoxesToBalanceWeight(0); + $packer->addBox(new TestBox('EuroPallet', 1200, 800, 2150, 0, 1200, 800, 2150, 400000)); + $packer->addItem(new TestItem('height 39', 590, 390, 390, 10880, Rotation::BestFit), 5); + $packer->addItem(new TestItem('height 47', 590, 470, 390, 10890, Rotation::BestFit), 6); + $packer->addItem(new TestItem('height 33', 590, 390, 330, 10060, Rotation::BestFit), 9); + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + + public function testIssue538(): void + { + $packer = new Packer(); + $packer->setMaxBoxesToBalanceWeight(0); + $packer->addBox(new TestBox('Stock 5 Single Wall', 30, 45, 30, 0, 30, 45, 30, 15000)); + $packer->addItem(new TestItem('Whatsanamie', 5, 5, 30, 100, Rotation::BestFit), 3); + $packer->addItem(new TestItem('Whatzit', 8, 5, 1, 100, Rotation::BestFit), 4); + $packer->addItem(new TestItem('Widget', 1, 3, 3, 100, Rotation::BestFit), 50); + $packer->addItem(new TestItem('Kajigger', 30, 25, 25, 100, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Doohickey', 8, 10, 20, 100, Rotation::KeepFlat), 1); + $packer->addItem(new TestItem('Gadget', 15, 20, 5, 100, Rotation::KeepFlat), 8); + $packedBoxes = $packer->pack(); + + self::assertCount(1, $packedBoxes); + } + public function testCustomPackedBoxSorterIsUsed(): void { PackedBoxByReferenceSorter::$reference = 'Box #1'; diff --git a/tests/Test/ConstrainedPlacementByCountTestItem.php b/tests/Test/ConstrainedPlacementByCountTestItem.php index c7d033b5..561a1519 100644 --- a/tests/Test/ConstrainedPlacementByCountTestItem.php +++ b/tests/Test/ConstrainedPlacementByCountTestItem.php @@ -19,10 +19,7 @@ class ConstrainedPlacementByCountTestItem extends TestItem implements ConstrainedPlacementItem { - /** - * @var int - */ - public static $limit = 3; + public static int $limit = 3; /** * Hook for user implementation of item-specific constraints, e.g. max batteries per box. @@ -39,9 +36,7 @@ public function canBePacked( ): bool { $alreadyPackedType = array_filter( iterator_to_array($alreadyPackedItems, false), - function (PackedItem $item) { - return $item->getItem()->getDescription() === $this->getDescription(); - } + fn (PackedItem $item) => $item->getItem()->getDescription() === $this->getDescription() ); return count($alreadyPackedType) + 1 <= static::$limit; diff --git a/tests/Test/ConstrainedPlacementNoStackingTestItem.php b/tests/Test/ConstrainedPlacementNoStackingTestItem.php index c12701db..09135d0f 100644 --- a/tests/Test/ConstrainedPlacementNoStackingTestItem.php +++ b/tests/Test/ConstrainedPlacementNoStackingTestItem.php @@ -33,9 +33,7 @@ public function canBePacked( ): bool { $alreadyPackedType = array_filter( iterator_to_array($alreadyPackedItems, false), - function (PackedItem $item) { - return $item->getItem()->getDescription() === $this->getDescription(); - } + fn (PackedItem $item) => $item->getItem()->getDescription() === $this->getDescription() ); /** @var PackedItem $alreadyPacked */ diff --git a/tests/Test/PackedBoxByReferenceSorter.php b/tests/Test/PackedBoxByReferenceSorter.php index 8831b656..fc6eca73 100644 --- a/tests/Test/PackedBoxByReferenceSorter.php +++ b/tests/Test/PackedBoxByReferenceSorter.php @@ -13,10 +13,7 @@ class PackedBoxByReferenceSorter implements PackedBoxSorter { - /** - * @var string - */ - public static $reference = ''; + public static string $reference = ''; public function compare(PackedBox $boxA, PackedBox $boxB): int { diff --git a/tests/VolumePackerTest.php b/tests/VolumePackerTest.php index fb571797..68217ff6 100644 --- a/tests/VolumePackerTest.php +++ b/tests/VolumePackerTest.php @@ -458,6 +458,223 @@ public function testIssue221(): void $volumePacker = new VolumePacker($box, $items); $packedBox = $volumePacker->pack(); - self::assertCount(32, $packedBox->getItems()); + self::assertCount(38, $packedBox->getItems()); + } + + public function testIssue214(): void + { + $this->markTestSkipped(); + foreach ([Rotation::KeepFlat, Rotation::BestFit] as $rotation) { + $box = new TestBox('A Box', 279, 215, 139, 10, 279, 215, 139, 100000); + + $items = new ItemList(); + $items->insert(new TestItem('Item A-1', 160, 160, 64, 1, $rotation)); + $items->insert(new TestItem('Item A-2', 160, 160, 64, 1, $rotation)); + $items->insert(new TestItem('Item B-1', 203, 114, 51, 1, $rotation)); + $items->insert(new TestItem('Item B-2', 203, 114, 51, 1, $rotation)); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); + } + } + + public function testIssue227(): void + { + $this->markTestSkipped(); + $box = new TestBox('Box', 160, 180, 160, 0, 160, 180, 160, 1000); + + $items = new ItemList(); + $items->insert(new TestItem('Item', 42, 100, 70, 1, Rotation::BestFit), 11); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); + } + + public function testIssue230(): void + { + $this->markTestSkipped(); + $box = new TestBox('Truck', 220, 210, 230, 0, 220, 210, 230, 1200); + + $items = new ItemList(); + $items->insert(new TestItem('Pallet', 80, 120, 140, 90, Rotation::KeepFlat), 4); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); + } + + public function testIssue240(): void + { + $this->markTestSkipped(); + $box = new TestBox('Le petite box', 220, 540, 1, 0, 220, 540, 1, 0); + + $items = new ItemList(); + $items->insert(new TestItem('1-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('2-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('3-60x100', 100, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('4-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('5-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('6-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('7-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('8-60x120', 120, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('9-60x160', 160, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('10-80x50', 50, 80, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('11-80x60', 60, 80, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('12-80x110', 110, 80, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('13-90x160', 160, 90, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('14-60x120', 120, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('15-60x80', 80, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('16-60x100', 100, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('17-60x120', 120, 60, 1, 0, Rotation::KeepFlat), 1); + $items->insert(new TestItem('18-60x120', 120, 60, 1, 0, Rotation::KeepFlat), 1); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(18, $packedBox->getItems()); + } + + public function testIssue264(): void + { + foreach ([Rotation::KeepFlat, Rotation::BestFit] as $rotation) { + $this->markTestSkipped(); + $box = new TestBox('Small', 160, 130, 70, 0, 160, 130, 70, 100000); + + $items = new ItemList(); + $items->insert(new TestItem('Item 1', 105, 70, 14, 0, $rotation), 1); + $items->insert(new TestItem('Item 2', 152, 101, 5, 0, $rotation), 1); + $items->insert(new TestItem('Item 3', 80, 70, 50, 0, $rotation), 1); + $items->insert(new TestItem('Item 4', 97, 71, 28, 0, $rotation), 1); + $items->insert(new TestItem('Item 5', 95, 70, 28, 0, $rotation), 1); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(5, $packedBox->getItems()); + } + } + + public function testIssue268(): void + { + foreach ([Rotation::KeepFlat, Rotation::BestFit] as $rotation) { + $this->markTestSkipped(); + $box = new TestBox('Box', 280, 175, 180, 0, 280, 175, 180, 100000); + + $items = new ItemList(); + $items->insert(new TestItem('Item', 140, 35, 30, 0, $rotation), 60); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(60, $packedBox->getItems()); + } + } + + public function testIssue272(): void + { + $this->markTestSkipped(); + $box = new TestBox('Box', 725, 725, 650, 0, 725, 725, 650, 100000); + + $items = new ItemList(); + $items->insert(new TestItem('Item', 260, 260, 460, 0, Rotation::BestFit), 6); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(6, $packedBox->getItems()); + } + + public function testIssue348(): void + { + $box = new TestBox('18x14x8', 180, 140, 80, 0, 180, 140, 80, 100); + + $items = new ItemList(); + $items->insert(new TestItem('Product1', 45, 20, 35, 17, Rotation::BestFit), 2); + $items->insert(new TestItem('Product2', 175, 70, 70, 26, Rotation::BestFit), 1); + $items->insert(new TestItem('Product3', 155, 70, 70, 20, Rotation::BestFit), 1); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); + } + + public function testIssue366(): void + { + $this->markTestSkipped(); + $box = new TestBox('Pallet', 250, 160, 1, 0, 250, 160, 1, 100); + + $items = new ItemList(); + $items->insert(new TestItem('Product1', 30, 70, 1, 1, Rotation::BestFit), 18); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(18, $packedBox->getItems()); + } + + public function testIssue465A(): void + { + $box = new TestBox('Container', 60, 90, 1, 0, 60, 90, 1, 0); + $t = new TestItem('T', 30, 60, 1, 0, Rotation::KeepFlat); + $x = new TestItem('X', 30, 30, 1, 0, Rotation::KeepFlat); + $l = new TestItem('L', 15, 30, 1, 0, Rotation::KeepFlat); + $s = new TestItem('S', 15, 30, 1, 0, Rotation::KeepFlat); + + $items = new ItemList(); + $items->insert($t); + $items->insert($t); + $items->insert($x); + $items->insert($l); + $items->insert($s); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(5, $packedBox->getItems()); + } + + public function testIssue465B(): void + { + $box = new TestBox('Container', 60, 90, 1, 0, 60, 90, 1, 0); + $h = new TestItem('H', 45, 60, 1, 0, Rotation::KeepFlat); + $q = new TestItem('Q', 45, 30, 1, 0, Rotation::KeepFlat); + $l = new TestItem('L', 15, 30, 1, 0, Rotation::KeepFlat); + + $items = new ItemList(); + $items->insert($h); + $items->insert($q); + $items->insert($l); + $items->insert($l); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); + } + + public function testIssue465C(): void + { + $box = new TestBox('Container', 60, 90, 1, 0, 60, 90, 1, 0); + $h = new TestItem('H', 45, 60, 1, 0, Rotation::KeepFlat); + $q = new TestItem('Q', 45, 30, 1, 0, Rotation::KeepFlat); + $x = new TestItem('X', 30, 30, 1, 0, Rotation::KeepFlat); + $l = new TestItem('L', 15, 30, 1, 0, Rotation::KeepFlat); + + $items = new ItemList(); + $items->insert($h); + $items->insert($q); + $items->insert($x); + $items->insert($l); + + $volumePacker = new VolumePacker($box, $items); + $packedBox = $volumePacker->pack(); + + self::assertCount(4, $packedBox->getItems()); } } diff --git a/tests/data/expected.csv b/tests/data/expected.csv index 8031602f..a1689a78 100644 --- a/tests/data/expected.csv +++ b/tests/data/expected.csv @@ -832,7 +832,7 @@ 2fffff39600acb91a32853d9c8a8f8fa,1,0,49.1,1,0,49.1 3004d7b042ae19dca17e23572ccd0307,1,0,50.2,1,0,50.2 300a552c32cf943b6cb1f89272b98f0a,1,0,23.7,1,0,23.7 -30207339e3ae62b06cfdaf7ff2bc7a60,2,1118306.3,18.8,2,2162.3,10.8 +30207339e3ae62b06cfdaf7ff2bc7a60,2,1118306.3,18.8,1,0,21.6 30239296520840f04db4b3553c44ebff,2,2256.3,36.9,1,0,11.1 3058994b05cdb67763ea7a7ee4ab33ff,1,0,28.2,1,0,28.2 306db218fea51e46b2be7741d8ec8ac9,3,14734.2,41.8,1,0,18.8 @@ -868,7 +868,7 @@ 3223ff53874ff3768881db6e163c96a3,2,99856,31.5,1,0,19.1 323d3a61ac8f60761edc56c34b339a1d,1,0,40.8,1,0,40.8 3242e661790ad530e5a8419a5c7c0b5b,1,0,22.8,1,0,22.8 -3247e39c4bfd94e9f525f7ef5c179317,2,182329,70.1,2,1521,21.2 +3247e39c4bfd94e9f525f7ef5c179317,2,182329,70.1,1,0,42.4 326b6af0e5d3ac4f72622ce744ee4b27,1,0,32.4,1,0,32.4 3276e0aa9afede0f2e5b0d5e3dcf7426,2,14884,23.9,1,0,7.2 328f28aadcd871aa3ade0646fda55b85,1,0,25.2,1,0,25.2 @@ -1045,7 +1045,7 @@ 3c2bc39429c8d1a0db6369149f454d65,2,2916,39.5,1,0,11.9 3c47276e13a1bf073b7f2987f09b21d9,1,0,48,1,0,48 3c4f92d98c4e7792e46c0269daab7d6d,2,400,28.5,1,0,8.5 -3c51cd3b2b4b213f076ba27e90d04225,3,6251648,48.3,2,3218436,25.1 +3c51cd3b2b4b213f076ba27e90d04225,3,6251648,48.3,2,835396,25.1 3c583eb716f36fa0ebe89e3573c733c0,1,0,28.5,1,0,62.5 3c58eba0522266abecca9b20aad46677,1,0,17.5,1,0,17.5 3c63070dfe31a39510ed0fdc001cd339,1,0,41.2,1,0,41.2 @@ -1379,7 +1379,7 @@ 4fb44d17cda18d0c04605f4374ffdb1a,2,35344,31.3,1,0,9.4 4fc2ca67d296a3e0d82f8b854923d168,1,0,15.9,1,0,15.9 4fcd699375dfa7df1c0eb12192d63b7c,3,51416.2,39.8,1,0,17.9 -4fd15118c3e987fccfbad711528ff651,2,137270.3,33.2,1,0,20.1 +4fd15118c3e987fccfbad711528ff651,2,20880.3,33.2,1,0,20.1 4fe79c3227acbda1fe01333d510f123d,1,0,18.8,1,0,18.8 500766fa9c90269017ac1194fa6c1e0e,1,0,22.9,1,0,22.9 500db53f251bd71c4d30a298b982f6ac,1,0,45.5,1,0,45.5 @@ -1567,8 +1567,8 @@ 5a857398f6ab90c25f668e92e6d00ef3,1,0,26.5,1,0,26.5 5a865b5e9674ad76da63584ee18f05de,1,0,44.1,1,0,44.1 5a8dd71fc456b5cf6cac64f4bd32da30,1,0,22.1,1,0,22.1 -5a8f522931df4152be3810a96dc74d39,3,7787192,46.7,2,41267776,81.9 -5a948a477ebdf65ea602ddd003e1a6e5,2,12100,39.1,2,12100,39.1 +5a8f522931df4152be3810a96dc74d39,3,7787192,46.7,2,34892649,81.9 +5a948a477ebdf65ea602ddd003e1a6e5,2,12100,39.1,2,19600,53.7 5aabd5f7518c2d8caf77e519eb4cab36,4,2934810.8,48.8,2,2500,30.4 5ad12093ba63af191fb8e7d39614c0e5,1,0,29.8,1,0,29.8 5ad1f84cff57f163708d79b7f83fc748,1,0,33.9,1,0,33.9 @@ -1608,7 +1608,7 @@ 5cdf36d57dca1decf25b21fc70ccb7b4,2,3136,32.4,1,0,9.7 5ce2156cbcd2167998a9f5b143b44094,1,0,15.8,1,0,15.8 5d00aedb31357d336f4d8345f2dfbba4,1,0,5.7,1,0,5.7 -5d25acff5712b8a133c693ea5951b005,2,1300740.3,38.8,2,1300740.3,38.8 +5d25acff5712b8a133c693ea5951b005,2,1300740.3,38.8,2,132.3,11.8 5d32bc595150184de9f7f0b2115c5310,1,0,27.9,1,0,27.9 5d32fc5743aee3b38c57715bd1a2f99e,2,144,29.9,1,0,9 5d54995022690b8d50acb4c80a3e33f3,1,0,25.4,1,0,25.4 @@ -1709,7 +1709,7 @@ 62dcab5291143dc6fc110ed65e070627,2,2500,27.3,2,2500,27.3 62e6b715a5a841db4292ce86055ad993,3,3557.6,61,3,253584.9,61 62f1be6037444d4f1f37a941da288493,1,0,30.1,1,0,30.1 -62f9a1e4dc829da0729325702c2b031c,2,5329,44.9,2,16,44.9 +62f9a1e4dc829da0729325702c2b031c,2,5329,44.9,2,1156,71.7 62fd86f546a52d904692e1a9e919e089,1,0,12.5,1,0,12.5 62fd8a5e4b91cd6c7cd1d0aebd05c4ca,1,0,40.9,1,0,40.9 63102af3c3bc3f814be53dffddb29e2d,1,0,18.6,1,0,18.6 @@ -2002,7 +2002,7 @@ 73629c8e0e3c78d24121d25bfe5254f2,1,0,46.1,1,0,46.1 7366285d837921c76989d9f797c1f0c7,2,3364,37.1,1,0,11.1 736ba758ea3bddea61655031fcac9620,2,1600,47,1,0,14.1 -736f6ef9b25b16508d1c8c85417b15da,4,87372,40.5,2,266256,12.1 +736f6ef9b25b16508d1c8c85417b15da,4,87372,40.5,2,38416,12.1 739584d7aee4c547320e35e357da4457,1,0,39.5,1,0,39.5 73aa7fec8faa51621fc3ac992c549816,1,0,8.6,1,0,8.6 73bb40c2188f99cde2e89bfe4af1c7f8,1,0,30.5,1,0,30.5 @@ -2316,7 +2316,7 @@ 86d3e0b0a09ac0ff9a7b86304eddd6ea,2,9900.3,28.2,1,0,8.5 870246ef780cb6f8d9c4205be83551d4,1,0,27.8,1,0,27.8 87074961a3c7652160242eb2902989ad,1,0,18.1,1,0,18.1 -87636068173f2c50cb62004918ef673a,2,3422.3,21.4,2,2970.3,43.1 +87636068173f2c50cb62004918ef673a,2,3422.3,21.4,1,0,12.9 8774705d643a77800df2ef7fdfcf7b63,1,0,39.7,1,0,39.7 8799f0bfa9757aa20f9963f7d145a9f7,2,29756.3,26.2,1,0,7.9 87bbdd5329c23d245b186a037d6618cb,1,0,58.5,1,0,58.5 @@ -2638,7 +2638,7 @@ 9a9f177fa8fed3003de712310da1d068,1,0,35.4,1,0,35.4 9abd92f2d09b53f689cee54ea73449cb,1,0,46.9,1,0,46.9 9ad05f21be39341f42bfede52ef353c8,1,0,24.1,1,0,24.1 -9ad068429596a5f0f7fcfaf327f876a3,3,21147.6,24.1,2,5625,15.8 +9ad068429596a5f0f7fcfaf327f876a3,3,21147.6,24.1,2,14161,9.1 9ad36f87de2203f7e5ea72f59d798e4f,4,11470005.3,67.2,3,15678933.6,50.7 9add3fe8175b23cb776a00613b14924d,2,24492.3,21,1,0,12.7 9af9b9ac98cfaf7cc7bd36790a631ad8,1,0,37.5,1,0,37.5 @@ -2719,7 +2719,7 @@ 9f4c1af281054f877264e1dfe3a3e846,2,7482.3,37,1,0,11.1 9f53d95b50760e78e0366ef4c7a63ef5,3,16490654,64.9,2,1056.3,33.7 9f678e4d556b1da7c52e4769b6ff4b7a,1,0,23.9,1,0,23.9 -9fb393eb0133650dec0ab1961fb7ad1b,2,80940.3,22.1,1,0,13.4 +9fb393eb0133650dec0ab1961fb7ad1b,2,17030.3,22.1,1,0,13.4 9fbe433f7f6799672a02db56b8be9fd2,1,0,12.8,1,0,12.8 9fde030604298e22ed72225dd5a567d1,1,0,13.7,1,0,13.7 9fe94dfc14cc6e81e3580bb3a675e3d9,1,0,37.2,1,0,37.2 @@ -2877,7 +2877,7 @@ a8b400d8aef6c488703249becd9b293b,3,18288456.2,62.7,2,11448072.3,32.6 a8baa80336a3f7799f67d75b56706f88,1,0,26.8,1,0,26.8 a8ce07dfcf68918f62fe006fdd4e2123,1,0,18.3,1,0,18.3 a8d355a97fe9359f998901899125564b,1,0,29,1,0,29 -a8df0a7e00e3627fef599719af67e8d0,7,3914818.8,53.1,2,398792.3,49.5 +a8df0a7e00e3627fef599719af67e8d0,7,3914818.8,53.1,2,154842.3,49.5 a8f4cfc856ccdba7fac54d706cae29a3,1,0,75.9,1,0,75.9 a8f9f349cc3a30cb703a9158e52647f3,3,36440.9,31.5,1,0,23.8 a90a1512baefad9a2c28efde1435ac06,2,961,38.2,1,0,11.4 @@ -2911,7 +2911,7 @@ ab319eb0473abbe517e52ead26007e9d,2,42230.3,47.1,1,0,94.2 ab3b1c260096541d4b6b2c0039fe28b6,2,6006.3,24.4,1,0,14.8 ab47749b81ef91adb192c8e12dbc70c4,1,0,5,1,0,5 ab51f10fdd2a474f6764d802cb5da2eb,1,0,16.2,1,0,16.2 -ab555bbbd49151f8ba54ce0909d32075,4,12687614.3,58.2,2,116964,48.5 +ab555bbbd49151f8ba54ce0909d32075,4,12687614.3,58.2,2,40804,48.5 ab5bf7d5840fe7fa56529f666e09f15f,2,30800.3,28,1,0,8.4 ab5d89243b28752cd4715679a536217e,1,0,20.9,1,0,20.9 ab744fec3cae04e3bebce28e248899e4,2,1681,32.1,1,0,9.6 @@ -2997,7 +2997,7 @@ af88b086b028d3c972dc3a63d73d14bf,1,0,25.6,1,0,25.6 af9ce79a63306b9afe8e298058368012,1,0,29.9,1,0,29.9 afcf3d02d04c4e14f20ee9206eb773f3,2,2040612.3,36,1,0,21.8 afd340fd1db50f62cea8fe8219f330fc,1,0,9.2,1,0,9.2 -afdc1e5ccd7cb344fe3e2d6395896c26,2,2844282.3,37.1,2,2844282.3,37.1 +afdc1e5ccd7cb344fe3e2d6395896c26,2,2844282.3,37.1,2,72.3,11.2 afe7ffb1f0c596924fab2a3d3cd3bca3,2,124962.3,28.7,2,124962.3,28.7 afe82686f800749adfc6eb13778a6bb8,1,0,41.2,1,0,41.2 afea3328620147789d47b433c86fde2c,1,0,36.9,1,0,36.9 @@ -3109,7 +3109,7 @@ b68bd3870389cab5d85b5097c39bbd92,1,0,20.9,1,0,20.9 b6938c68e89c4b286ac1f425820f6196,2,1444,25.1,1,0,7.5 b6a7e4fa9183e74b5767952cdca8f010,1,0,38.5,1,0,38.5 b6ab2b40ec4109094efe68832736596f,2,25,32.9,1,0,9.9 -b6b9f338b315e419a4a90c5ecbf5d1a8,2,22500,32.7,2,22500,32.7 +b6b9f338b315e419a4a90c5ecbf5d1a8,2,22500,32.7,1,0,9.8 b6d5ce9c0f1aaaa50b95f9da2b882ea8,1,0,27.3,1,0,27.3 b6f9dd84a6411ee18487982a1393ed69,1,0,20.7,1,0,20.7 b7108981ca19261d51ecf4950916e1a7,1,0,27.8,1,0,27.8 @@ -3268,7 +3268,7 @@ bf96757dbdc1dc4492fcf86dcd01841f,2,1190.3,29.4,2,1190.3,29.4 bfafb03761f08fd192dbeb4288d9d088,1,0,43.9,1,0,43.9 bfbdd3f5d1075814b4cb2b56411f5d7c,1,0,38.2,1,0,38.2 bfd980576a57f49bd874b46590649ead,1,0,29.7,1,0,29.7 -bfdc31b91f69cae56fc72ec6dd81cf00,3,84460.2,60.6,2,644006.3,91 +bfdc31b91f69cae56fc72ec6dd81cf00,3,84460.2,60.6,2,930.3,91 bffbe0ad8647d98f4637328be8d758a9,1,0,44.9,1,0,44.9 c007002c9ea028a58c567e163d80bab4,2,930.3,51,1,0,15.3 c007ca09106370f3d90e77627d4bfc6e,3,231242.7,34.8,1,0,26.3 @@ -3498,7 +3498,7 @@ ce2b82c15d95ffde90ad38e5b287fe37,2,0,40.3,2,0,40.3 ce37fb5ff9387c72b027103bba8677aa,1,0,41.3,1,0,41.3 ce59330ec0c9c8c86e940d712ff63573,1,0,11.5,1,0,11.5 ce5a31ddda35d895fbdf77b6e19a3cf7,2,92720.3,30.9,1,0,9.3 -ce5bceb908078b0393332d1fd3682390,2,27889,29.2,2,4,29.2 +ce5bceb908078b0393332d1fd3682390,2,27889,29.2,1,0,8.7 ce808a12a5746259d108bb7aba47aee9,1,0,44.1,1,0,44.1 ce824e0178be69da4a67da325fd738b2,1,0,22.2,1,0,22.2 ce8308ab5ff468fda4211ffc4e6809a3,1,0,42.5,1,0,42.5 @@ -3697,7 +3697,7 @@ d9c9c85316195a77174f29ad7c5b4022,2,24649,44,2,24649,44 d9dda9089bc4e3833b2dd170e4ac9b10,1,0,21.6,1,0,21.6 da04810164fc021b1592dc8b1fed6401,1,0,24.9,1,0,24.9 da30bfae6a9a5a83cadc7eec05eeda03,2,41820.3,34,1,0,20.6 -da660877a6d6b415aefc442798798ccb,4,22909716.2,68.3,2,24408540.3,42.5 +da660877a6d6b415aefc442798798ccb,4,22909716.2,68.3,2,20164590.3,42.5 da69840b381127b1528ecb3aff6341ee,1,0,18.5,1,0,18.5 da6d9a2e56d9ef2ce69d4d8c33688963,1,0,36,1,0,36 da7a9e5d5e8c6ee725b402ef1b22f12f,1,0,24.4,1,0,24.4 @@ -4119,7 +4119,7 @@ f539f4c9c7ca32f5cc8e1d851478c7b5,1,0,42.6,1,0,42.6 f55244c56464e0b9b03f8d19e678fac6,1,0,17.3,1,0,17.3 f55cefb0d979a0cf5910b5b29e702717,1,0,58.1,1,0,58.1 f561d58bc43c039b3158ef211b60d489,1,0,21.7,1,0,21.7 -f567b11013bcd929db7ef7349cc59c4c,2,5929,25.3,1,0,15.3 +f567b11013bcd929db7ef7349cc59c4c,2,4489,51.1,1,0,15.3 f59673e61d54bd0243d553771e177184,1,0,41.2,1,0,41.2 f5a59066ca3c0f3f7748528b32f68c3b,2,3721,26.5,1,0,16 f5e0bc270f5f2d1ae67509e9bfb77026,1,0,39.8,1,0,39.8 @@ -4157,7 +4157,7 @@ f7c37ec04b1b572d393203852280a786,1,0,53.4,1,0,53.4 f7d300dd9c05d7bb658ab899731d0506,1,0,34.4,1,0,34.4 f7f47e3a2f0a955e864ee3f778b6f8c5,1,0,63.3,1,0,63.3 f81ae8a5fbcefcd313f6eb33174d907c,1,0,21.8,1,0,21.8 -f81e3a5ac35f0e40ab763e7f7fa26cb3,2,234740.3,41.5,2,272.3,12.6 +f81e3a5ac35f0e40ab763e7f7fa26cb3,2,234740.3,41.5,2,992.3,12.6 f81ecca4e56569f7cf1d7a60b7387259,2,101442.3,28.3,1,0,17.1 f82fea4dc25ad4dc865eabe09f4bdd7e,1,0,18.8,1,0,18.8 f84558f5184d3775ebfbf03c750964f9,1,0,47.9,1,0,47.9 @@ -4199,7 +4199,7 @@ fa5d9ae6b63e9d33769081f7d4f2b115,1,0,30,1,0,30 fa74eadd074843a8c78542d7602c31d4,1,0,48.4,1,0,48.4 faa914e796a804997c19804300db0204,1,0,42.7,1,0,42.7 faae756f0de0eef0384474255db1e662,1,0,41.6,1,0,41.6 -fac6c548649f983888821cb370e87966,1,0,34,1,0,34 +fac6c548649f983888821cb370e87966,1,0,74.5,1,0,34 fad39d2e603bae2ac15387a3662de7ff,2,22201,37.6,1,0,11.3 fae22f77df7666ed2fdc5757bd78ed07,1,0,33.9,1,0,33.9 faec3db769a11d268cd4845fb3966167,1,0,41.1,1,0,41.1 diff --git a/tests/data/thpack-expected.csv b/tests/data/thpack-expected.csv index 77918b5f..62c2346c 100644 --- a/tests/data/thpack-expected.csv +++ b/tests/data/thpack-expected.csv @@ -1,5 +1,5 @@ -Loh and Nee #1,56.5 -Loh and Nee #2,76.0 +Loh and Nee #1,58.5 +Loh and Nee #2,76.6 Loh and Nee #3,53.4 Loh and Nee #4,55.0 Loh and Nee #5,77.2 @@ -7,35 +7,35 @@ Loh and Nee #6,62.3 Loh and Nee #7,63.6 Loh and Nee #8,59.4 Loh and Nee #9,61.9 -Loh and Nee #10,60.5 -Loh and Nee #11,57.3 +Loh and Nee #10,67.0 +Loh and Nee #11,62.2 Loh and Nee #12,62.0 Loh and Nee #13,77.8 Loh and Nee #14,62.8 Loh and Nee #15,59.5 -Bischoff #3-1,69 -Bischoff #3-2,82.2 +Bischoff #3-1,69.0 +Bischoff #3-2,82.6 Bischoff #3-3,69.9 -Bischoff #3-4,56.2 +Bischoff #3-4,58.7 Bischoff #3-5,78.2 Bischoff #3-6,85.8 Bischoff #3-7,76.9 -Bischoff #3-8,79.3 +Bischoff #3-8,81.4 Bischoff #3-9,71.3 Bischoff #3-10,69.7 -Bischoff #3-11,71 +Bischoff #3-11,71.0 Bischoff #3-12,79.8 -Bischoff #3-13,89.9 +Bischoff #3-13,90.2 Bischoff #3-14,81.3 Bischoff #3-15,71.5 Bischoff #3-16,84.1 Bischoff #3-17,87.4 Bischoff #3-18,75.2 -Bischoff #3-19,85.5 +Bischoff #3-19,87.9 Bischoff #3-20,67.6 Bischoff #3-21,84.4 Bischoff #3-22,88.5 -Bischoff #3-23,90.5 +Bischoff #3-23,90.8 Bischoff #3-24,76.7 Bischoff #3-25,74.2 Bischoff #3-26,74.1 @@ -44,10 +44,10 @@ Bischoff #3-28,78.5 Bischoff #3-29,86.7 Bischoff #3-30,80.8 Bischoff #3-31,76.2 -Bischoff #3-32,77.8 -Bischoff #3-33,85.3 +Bischoff #3-32,78.6 +Bischoff #3-33,85.6 Bischoff #3-34,79.7 -Bischoff #3-35,77 +Bischoff #3-35,77.0 Bischoff #3-36,82.4 Bischoff #3-37,84.5 Bischoff #3-38,71.2 @@ -62,20 +62,20 @@ Bischoff #3-46,85.6 Bischoff #3-47,69.6 Bischoff #3-48,75.9 Bischoff #3-49,84.7 -Bischoff #3-50,86.9 +Bischoff #3-50,88.0 Bischoff #3-51,88.2 Bischoff #3-52,84.4 Bischoff #3-53,74.5 -Bischoff #3-54,76 +Bischoff #3-54,76.0 Bischoff #3-55,69.9 -Bischoff #3-56,77.0 +Bischoff #3-56,77.8 Bischoff #3-57,77.9 Bischoff #3-58,84.5 Bischoff #3-59,79.6 Bischoff #3-60,78.5 Bischoff #3-61,81.3 -Bischoff #3-62,79 -Bischoff #3-63,81.2 +Bischoff #3-62,79.0 +Bischoff #3-63,82.2 Bischoff #3-64,80.1 Bischoff #3-65,69.4 Bischoff #3-66,76.3 @@ -85,15 +85,15 @@ Bischoff #3-69,63.7 Bischoff #3-70,80.2 Bischoff #3-71,73.5 Bischoff #3-72,77.3 -Bischoff #3-73,76.1 -Bischoff #3-74,89.8 -Bischoff #3-75,75 +Bischoff #3-73,77.7 +Bischoff #3-74,90.3 +Bischoff #3-75,75.0 Bischoff #3-76,72.0 Bischoff #3-77,87.3 -Bischoff #3-78,76.1 +Bischoff #3-78,76.5 Bischoff #3-79,57.5 Bischoff #3-80,71.4 -Bischoff #3-81,81 +Bischoff #3-81,81.0 Bischoff #3-82,75.5 Bischoff #3-83,83.7 Bischoff #3-84,83.6 @@ -102,16 +102,16 @@ Bischoff #3-86,84.8 Bischoff #3-87,75.7 Bischoff #3-88,68.7 Bischoff #3-89,60.1 -Bischoff #3-90,84 +Bischoff #3-90,84.0 Bischoff #3-91,78.3 Bischoff #3-92,73.4 Bischoff #3-93,91.5 Bischoff #3-94,81.8 Bischoff #3-95,87.9 -Bischoff #3-96,84 +Bischoff #3-96,84.0 Bischoff #3-97,78.8 Bischoff #3-98,81.3 -Bischoff #3-99,78 +Bischoff #3-99,78.0 Bischoff #3-100,62.9 Bischoff #5-1,72.9 Bischoff #5-2,84.3 @@ -122,30 +122,30 @@ Bischoff #5-6,80.4 Bischoff #5-7,75.8 Bischoff #5-8,75.1 Bischoff #5-9,74.6 -Bischoff #5-10,65.3 +Bischoff #5-10,66.6 Bischoff #5-11,74.9 Bischoff #5-12,87.9 Bischoff #5-13,83.4 Bischoff #5-14,74.7 Bischoff #5-15,75.1 -Bischoff #5-16,77.4 +Bischoff #5-16,77.6 Bischoff #5-17,73.0 Bischoff #5-18,80.2 -Bischoff #5-19,75.5 +Bischoff #5-19,77.1 Bischoff #5-20,78.6 Bischoff #5-21,78.8 Bischoff #5-22,79.8 -Bischoff #5-23,84.2 +Bischoff #5-23,84.5 Bischoff #5-24,81.4 Bischoff #5-25,77.8 -Bischoff #5-26,78 -Bischoff #5-27,70.4 -Bischoff #5-28,76.2 -Bischoff #5-29,81.7 -Bischoff #5-30,80.9 -Bischoff #5-31,70 +Bischoff #5-26,78.0 +Bischoff #5-27,71.6 +Bischoff #5-28,76.8 +Bischoff #5-29,82.3 +Bischoff #5-30,81.7 +Bischoff #5-31,70.0 Bischoff #5-32,81.8 -Bischoff #5-33,82.5 +Bischoff #5-33,83.6 Bischoff #5-34,74.3 Bischoff #5-35,84.6 Bischoff #5-36,74.9 @@ -153,53 +153,53 @@ Bischoff #5-37,79.5 Bischoff #5-38,75.6 Bischoff #5-39,82.7 Bischoff #5-40,82.3 -Bischoff #5-41,83.0 +Bischoff #5-41,84.6 Bischoff #5-42,80.9 Bischoff #5-43,79.9 Bischoff #5-44,71.2 Bischoff #5-45,65.1 Bischoff #5-46,78.7 Bischoff #5-47,65.5 -Bischoff #5-48,82.8 -Bischoff #5-49,82.5 -Bischoff #5-50,74.2 -Bischoff #5-51,77.0 +Bischoff #5-48,84.6 +Bischoff #5-49,85.0 +Bischoff #5-50,74.5 +Bischoff #5-51,77.3 Bischoff #5-52,87.1 Bischoff #5-53,72.3 Bischoff #5-54,71.2 Bischoff #5-55,70.3 Bischoff #5-56,83.3 Bischoff #5-57,79.1 -Bischoff #5-58,85.0 -Bischoff #5-59,72 +Bischoff #5-58,85.8 +Bischoff #5-59,72.0 Bischoff #5-60,78.2 Bischoff #5-61,74.9 Bischoff #5-62,78.2 -Bischoff #5-63,78.7 +Bischoff #5-63,79.4 Bischoff #5-64,76.7 -Bischoff #5-65,83.1 +Bischoff #5-65,84.7 Bischoff #5-66,79.1 Bischoff #5-67,78.9 -Bischoff #5-68,83.8 +Bischoff #5-68,85.3 Bischoff #5-69,77.8 -Bischoff #5-70,75.7 +Bischoff #5-70,76.0 Bischoff #5-71,79.4 Bischoff #5-72,67.7 Bischoff #5-73,81.4 -Bischoff #5-74,70.7 +Bischoff #5-74,71.5 Bischoff #5-75,75.1 Bischoff #5-76,75.4 -Bischoff #5-77,75 -Bischoff #5-78,84.8 -Bischoff #5-79,76 +Bischoff #5-77,75.0 +Bischoff #5-78,85.0 +Bischoff #5-79,76.0 Bischoff #5-80,71.6 -Bischoff #5-81,84 -Bischoff #5-82,75.3 +Bischoff #5-81,84.0 +Bischoff #5-82,75.8 Bischoff #5-83,80.3 Bischoff #5-84,75.3 Bischoff #5-85,86.2 Bischoff #5-86,78.2 -Bischoff #5-87,71.6 +Bischoff #5-87,71.7 Bischoff #5-88,65.8 Bischoff #5-89,65.7 Bischoff #5-90,88.4 @@ -208,21 +208,21 @@ Bischoff #5-92,74.6 Bischoff #5-93,84.9 Bischoff #5-94,73.7 Bischoff #5-95,85.2 -Bischoff #5-96,83.2 +Bischoff #5-96,83.5 Bischoff #5-97,84.1 Bischoff #5-98,82.5 -Bischoff #5-99,76.4 -Bischoff #5-100,66.0 +Bischoff #5-99,79.2 +Bischoff #5-100,67.7 Bischoff #8-1,70.7 -Bischoff #8-2,85.3 +Bischoff #8-2,86.6 Bischoff #8-3,74.1 -Bischoff #8-4,65 +Bischoff #8-4,65.0 Bischoff #8-5,76.7 Bischoff #8-6,82.7 -Bischoff #8-7,66.9 -Bischoff #8-8,77.1 -Bischoff #8-9,76.3 -Bischoff #8-10,75.9 +Bischoff #8-7,67.1 +Bischoff #8-8,77.9 +Bischoff #8-9,76.8 +Bischoff #8-10,76.7 Bischoff #8-11,82.8 Bischoff #8-12,75.9 Bischoff #8-13,79.4 @@ -238,478 +238,478 @@ Bischoff #8-22,72.5 Bischoff #8-23,83.8 Bischoff #8-24,77.9 Bischoff #8-25,82.1 -Bischoff #8-26,88.3 -Bischoff #8-27,77.2 +Bischoff #8-26,88.8 +Bischoff #8-27,77.3 Bischoff #8-28,77.9 -Bischoff #8-29,70.1 -Bischoff #8-30,73.5 +Bischoff #8-29,70.6 +Bischoff #8-30,74.4 Bischoff #8-31,64.1 Bischoff #8-32,77.9 Bischoff #8-33,83.9 -Bischoff #8-34,72 +Bischoff #8-34,72.0 Bischoff #8-35,75.1 -Bischoff #8-36,75 -Bischoff #8-37,81.8 +Bischoff #8-36,76.4 +Bischoff #8-37,82.2 Bischoff #8-38,72.4 -Bischoff #8-39,79.1 +Bischoff #8-39,80.5 Bischoff #8-40,76.7 Bischoff #8-41,81.0 -Bischoff #8-42,76.2 +Bischoff #8-42,76.7 Bischoff #8-43,76.3 Bischoff #8-44,74.8 -Bischoff #8-45,75.0 +Bischoff #8-45,77.1 Bischoff #8-46,67.8 -Bischoff #8-47,64.4 +Bischoff #8-47,66.0 Bischoff #8-48,81.9 Bischoff #8-49,74.6 -Bischoff #8-50,79.3 -Bischoff #8-51,74.5 +Bischoff #8-50,79.6 +Bischoff #8-51,74.7 Bischoff #8-52,76.9 Bischoff #8-53,83.2 -Bischoff #8-54,74.4 -Bischoff #8-55,67.8 -Bischoff #8-56,78.5 -Bischoff #8-57,82.9 -Bischoff #8-58,84.5 +Bischoff #8-54,74.8 +Bischoff #8-55,70.1 +Bischoff #8-56,79.4 +Bischoff #8-57,84.4 +Bischoff #8-58,84.8 Bischoff #8-59,63.5 -Bischoff #8-60,76.9 +Bischoff #8-60,77.6 Bischoff #8-61,73.8 -Bischoff #8-62,82.6 -Bischoff #8-63,85.3 -Bischoff #8-64,70.6 -Bischoff #8-65,86.5 +Bischoff #8-62,82.8 +Bischoff #8-63,85.5 +Bischoff #8-64,72.1 +Bischoff #8-65,87.0 Bischoff #8-66,82.5 Bischoff #8-67,71.9 Bischoff #8-68,82.6 Bischoff #8-69,73.7 Bischoff #8-70,71.6 Bischoff #8-71,72.6 -Bischoff #8-72,74 +Bischoff #8-72,74.0 Bischoff #8-73,80.2 -Bischoff #8-74,78.2 -Bischoff #8-75,74.7 -Bischoff #8-76,77.3 +Bischoff #8-74,79.9 +Bischoff #8-75,75.2 +Bischoff #8-76,77.7 Bischoff #8-77,68.8 -Bischoff #8-78,73 -Bischoff #8-79,75.1 +Bischoff #8-78,73.0 +Bischoff #8-79,75.9 Bischoff #8-80,76.5 Bischoff #8-81,68.6 Bischoff #8-82,80.1 -Bischoff #8-83,80.1 +Bischoff #8-83,80.4 Bischoff #8-84,75.6 Bischoff #8-85,80.4 -Bischoff #8-86,81.4 +Bischoff #8-86,82.2 Bischoff #8-87,79.2 -Bischoff #8-88,69 -Bischoff #8-89,65.7 -Bischoff #8-90,81.5 -Bischoff #8-91,75.1 +Bischoff #8-88,69.3 +Bischoff #8-89,67.9 +Bischoff #8-90,82.9 +Bischoff #8-91,75.5 Bischoff #8-92,76.6 Bischoff #8-93,65.6 -Bischoff #8-94,79.7 -Bischoff #8-95,74.0 -Bischoff #8-96,75.6 -Bischoff #8-97,82.7 -Bischoff #8-98,68.9 +Bischoff #8-94,80.1 +Bischoff #8-95,75.5 +Bischoff #8-96,76.3 +Bischoff #8-97,83.0 +Bischoff #8-98,69.9 Bischoff #8-99,83.6 -Bischoff #8-100,77.4 +Bischoff #8-100,78.0 Bischoff #10-1,73.6 Bischoff #10-2,86.5 Bischoff #10-3,77.9 -Bischoff #10-4,73 -Bischoff #10-5,78.9 +Bischoff #10-4,73.7 +Bischoff #10-5,79.7 Bischoff #10-6,81.5 -Bischoff #10-7,69.5 +Bischoff #10-7,71.5 Bischoff #10-8,75.9 -Bischoff #10-9,78.4 -Bischoff #10-10,81.0 -Bischoff #10-11,76.9 +Bischoff #10-9,78.9 +Bischoff #10-10,82.6 +Bischoff #10-11,77.2 Bischoff #10-12,76.8 Bischoff #10-13,80.4 Bischoff #10-14,77.6 Bischoff #10-15,80.9 -Bischoff #10-16,78.9 -Bischoff #10-17,72.5 -Bischoff #10-18,70.7 -Bischoff #10-19,81 +Bischoff #10-16,80.1 +Bischoff #10-17,73.3 +Bischoff #10-18,71.9 +Bischoff #10-19,82.8 Bischoff #10-20,75.7 Bischoff #10-21,72.1 Bischoff #10-22,76.9 -Bischoff #10-23,86.1 -Bischoff #10-24,72.7 +Bischoff #10-23,86.2 +Bischoff #10-24,69.4 Bischoff #10-25,75.2 -Bischoff #10-26,79.7 -Bischoff #10-27,75.8 -Bischoff #10-28,75.2 +Bischoff #10-26,80.5 +Bischoff #10-27,76.3 +Bischoff #10-28,75.8 Bischoff #10-29,68.5 -Bischoff #10-30,77.5 +Bischoff #10-30,77.9 Bischoff #10-31,65.4 Bischoff #10-32,72.9 -Bischoff #10-33,83.9 +Bischoff #10-33,84.8 Bischoff #10-34,75.9 Bischoff #10-35,74.5 -Bischoff #10-36,66.1 -Bischoff #10-37,82.6 -Bischoff #10-38,70.5 -Bischoff #10-39,70.7 +Bischoff #10-36,68.9 +Bischoff #10-37,83.4 +Bischoff #10-38,70.9 +Bischoff #10-39,71.4 Bischoff #10-40,70.4 -Bischoff #10-41,79 +Bischoff #10-41,79.0 Bischoff #10-42,79.3 -Bischoff #10-43,75.7 +Bischoff #10-43,76.2 Bischoff #10-44,71.8 Bischoff #10-45,77.8 Bischoff #10-46,80.3 -Bischoff #10-47,59.9 +Bischoff #10-47,62.4 Bischoff #10-48,86.7 Bischoff #10-49,71.9 Bischoff #10-50,74.2 -Bischoff #10-51,71.6 +Bischoff #10-51,74.6 Bischoff #10-52,80.3 Bischoff #10-53,79.1 -Bischoff #10-54,77.1 +Bischoff #10-54,78.4 Bischoff #10-55,67.5 Bischoff #10-56,78.4 Bischoff #10-57,80.9 -Bischoff #10-58,77.1 -Bischoff #10-59,69.4 +Bischoff #10-58,77.9 +Bischoff #10-59,69.6 Bischoff #10-60,75.0 Bischoff #10-61,78.9 -Bischoff #10-62,85.9 -Bischoff #10-63,71 -Bischoff #10-64,74.9 -Bischoff #10-65,81.8 +Bischoff #10-62,86.1 +Bischoff #10-63,71.0 +Bischoff #10-64,77.0 +Bischoff #10-65,82.2 Bischoff #10-66,74.7 Bischoff #10-67,76.3 Bischoff #10-68,71.4 -Bischoff #10-69,77 -Bischoff #10-70,75.3 +Bischoff #10-69,77.0 +Bischoff #10-70,75.7 Bischoff #10-71,73.2 Bischoff #10-72,72.5 Bischoff #10-73,80.7 -Bischoff #10-74,70.4 -Bischoff #10-75,77.2 -Bischoff #10-76,77.7 -Bischoff #10-77,66.5 +Bischoff #10-74,72.8 +Bischoff #10-75,77.7 +Bischoff #10-76,76.6 +Bischoff #10-77,66.9 Bischoff #10-78,69.9 -Bischoff #10-79,76.5 +Bischoff #10-79,78.7 Bischoff #10-80,77.3 -Bischoff #10-81,74.8 +Bischoff #10-81,76.1 Bischoff #10-82,76.2 Bischoff #10-83,77.9 Bischoff #10-84,76.7 -Bischoff #10-85,81.0 -Bischoff #10-86,69.8 -Bischoff #10-87,74 +Bischoff #10-85,81.4 +Bischoff #10-86,71.1 +Bischoff #10-87,74.9 Bischoff #10-88,77.5 -Bischoff #10-89,73.2 -Bischoff #10-90,78.3 -Bischoff #10-91,80.1 +Bischoff #10-89,73.8 +Bischoff #10-90,79.2 +Bischoff #10-91,80.4 Bischoff #10-92,82.8 -Bischoff #10-93,68 +Bischoff #10-93,68.0 Bischoff #10-94,79.7 -Bischoff #10-95,73.8 +Bischoff #10-95,74.0 Bischoff #10-96,79.9 -Bischoff #10-97,85.0 -Bischoff #10-98,69.7 -Bischoff #10-99,81 -Bischoff #10-100,84.0 +Bischoff #10-97,85.6 +Bischoff #10-98,71.6 +Bischoff #10-99,81.6 +Bischoff #10-100,84.5 Bischoff #12-1,70.2 Bischoff #12-2,80.6 -Bischoff #12-3,68.4 -Bischoff #12-4,79.2 +Bischoff #12-3,69.0 +Bischoff #12-4,80.7 Bischoff #12-5,76.9 -Bischoff #12-6,81 +Bischoff #12-6,81.0 Bischoff #12-7,75.2 -Bischoff #12-8,73 -Bischoff #12-9,78.0 -Bischoff #12-10,76.2 +Bischoff #12-8,75.0 +Bischoff #12-9,78.5 +Bischoff #12-10,78.4 Bischoff #12-11,75.6 Bischoff #12-12,73.5 Bischoff #12-13,76.6 -Bischoff #12-14,83.4 +Bischoff #12-14,84.0 Bischoff #12-15,77.6 Bischoff #12-16,78.2 Bischoff #12-17,77.7 -Bischoff #12-18,74.3 -Bischoff #12-19,78.8 -Bischoff #12-20,74.8 +Bischoff #12-18,74.9 +Bischoff #12-19,79.1 +Bischoff #12-20,75.1 Bischoff #12-21,74.6 Bischoff #12-22,72.9 Bischoff #12-23,80.4 -Bischoff #12-24,72.9 +Bischoff #12-24,73.7 Bischoff #12-25,71.3 Bischoff #12-26,69.6 -Bischoff #12-27,80.0 -Bischoff #12-28,72.3 -Bischoff #12-29,69.5 +Bischoff #12-27,81.1 +Bischoff #12-28,74.3 +Bischoff #12-29,70.2 Bischoff #12-30,79.6 Bischoff #12-31,67.3 Bischoff #12-32,69.0 -Bischoff #12-33,80.1 +Bischoff #12-33,80.8 Bischoff #12-34,70.5 -Bischoff #12-35,73.8 -Bischoff #12-36,69.3 -Bischoff #12-37,75.7 -Bischoff #12-38,77.3 -Bischoff #12-39,75.9 -Bischoff #12-40,74.0 -Bischoff #12-41,80.0 +Bischoff #12-35,75.3 +Bischoff #12-36,71.3 +Bischoff #12-37,76.0 +Bischoff #12-38,78.2 +Bischoff #12-39,76.4 +Bischoff #12-40,74.9 +Bischoff #12-41,81.4 Bischoff #12-42,79.3 Bischoff #12-43,71.7 -Bischoff #12-44,76.4 -Bischoff #12-45,78.9 +Bischoff #12-44,76.6 +Bischoff #12-45,80.0 Bischoff #12-46,69.6 -Bischoff #12-47,75.3 -Bischoff #12-48,81.2 +Bischoff #12-47,75.8 +Bischoff #12-48,82.6 Bischoff #12-49,67.8 Bischoff #12-50,77.5 Bischoff #12-51,72.5 Bischoff #12-52,74.3 Bischoff #12-53,87.1 Bischoff #12-54,76.5 -Bischoff #12-55,71 -Bischoff #12-56,79.1 +Bischoff #12-55,71.0 +Bischoff #12-56,79.7 Bischoff #12-57,81.7 Bischoff #12-58,81.7 -Bischoff #12-59,78 -Bischoff #12-60,71.4 +Bischoff #12-59,78.0 +Bischoff #12-60,72.2 Bischoff #12-61,81.3 Bischoff #12-62,79.8 Bischoff #12-63,69.1 -Bischoff #12-64,74.5 -Bischoff #12-65,78.0 -Bischoff #12-66,65.2 -Bischoff #12-67,69.4 -Bischoff #12-68,70.4 +Bischoff #12-64,75.1 +Bischoff #12-65,78.3 +Bischoff #12-66,65.6 +Bischoff #12-67,71.3 +Bischoff #12-68,71.5 Bischoff #12-69,78.4 -Bischoff #12-70,77.7 -Bischoff #12-71,65.7 +Bischoff #12-70,78.1 +Bischoff #12-71,68.6 Bischoff #12-72,74.5 Bischoff #12-73,81.8 Bischoff #12-74,76.7 Bischoff #12-75,80.4 Bischoff #12-76,82.2 -Bischoff #12-77,67 +Bischoff #12-77,67.2 Bischoff #12-78,66.1 -Bischoff #12-79,76 +Bischoff #12-79,77.8 Bischoff #12-80,73.6 Bischoff #12-81,66.1 Bischoff #12-82,80.7 -Bischoff #12-83,82 -Bischoff #12-84,75.8 +Bischoff #12-83,82.0 +Bischoff #12-84,76.0 Bischoff #12-85,83.8 Bischoff #12-86,76.2 -Bischoff #12-87,75.7 -Bischoff #12-88,75.1 -Bischoff #12-89,71.3 +Bischoff #12-87,76.5 +Bischoff #12-88,75.7 +Bischoff #12-89,72.5 Bischoff #12-90,79.0 -Bischoff #12-91,80.7 -Bischoff #12-92,78.1 +Bischoff #12-91,82.0 +Bischoff #12-92,78.8 Bischoff #12-93,65.8 -Bischoff #12-94,78.2 -Bischoff #12-95,73.1 +Bischoff #12-94,78.4 +Bischoff #12-95,73.5 Bischoff #12-96,75.4 -Bischoff #12-97,71.8 -Bischoff #12-98,75.1 +Bischoff #12-97,72.5 +Bischoff #12-98,76.3 Bischoff #12-99,79.3 -Bischoff #12-100,79.3 -Bischoff #15-1,75.1 -Bischoff #15-2,81.7 -Bischoff #15-3,68.4 -Bischoff #15-4,78.7 +Bischoff #12-100,79.6 +Bischoff #15-1,78.8 +Bischoff #15-2,82.3 +Bischoff #15-3,70.8 +Bischoff #15-4,81.6 Bischoff #15-5,71.5 Bischoff #15-6,76.3 -Bischoff #15-7,70.5 -Bischoff #15-8,74.9 +Bischoff #15-7,71.6 +Bischoff #15-8,76.1 Bischoff #15-9,76.5 -Bischoff #15-10,70.8 -Bischoff #15-11,80.2 +Bischoff #15-10,71.4 +Bischoff #15-11,81.7 Bischoff #15-12,69.4 Bischoff #15-13,74.7 -Bischoff #15-14,83 +Bischoff #15-14,83.0 Bischoff #15-15,73.4 Bischoff #15-16,80.3 Bischoff #15-17,76.6 -Bischoff #15-18,76.9 +Bischoff #15-18,78.9 Bischoff #15-19,77.8 -Bischoff #15-20,74.7 -Bischoff #15-21,66.7 -Bischoff #15-22,76.9 -Bischoff #15-23,83.1 +Bischoff #15-20,75.0 +Bischoff #15-21,72.9 +Bischoff #15-22,77.6 +Bischoff #15-23,84.0 Bischoff #15-24,73.4 Bischoff #15-25,71.9 Bischoff #15-26,79.1 -Bischoff #15-27,73.4 +Bischoff #15-27,78.5 Bischoff #15-28,74.9 -Bischoff #15-29,70.6 -Bischoff #15-30,78.9 -Bischoff #15-31,71 -Bischoff #15-32,74.9 +Bischoff #15-29,71.2 +Bischoff #15-30,79.8 +Bischoff #15-31,71.0 +Bischoff #15-32,75.8 Bischoff #15-33,77.4 Bischoff #15-34,73.0 Bischoff #15-35,72.4 -Bischoff #15-36,70.5 +Bischoff #15-36,71.8 Bischoff #15-37,84.0 -Bischoff #15-38,75 -Bischoff #15-39,74.9 -Bischoff #15-40,72.1 +Bischoff #15-38,75.7 +Bischoff #15-39,73.1 +Bischoff #15-40,73.0 Bischoff #15-41,77.5 -Bischoff #15-42,76.8 -Bischoff #15-43,69 -Bischoff #15-44,76.6 -Bischoff #15-45,71.1 +Bischoff #15-42,77.4 +Bischoff #15-43,69.8 +Bischoff #15-44,76.7 +Bischoff #15-45,72.8 Bischoff #15-46,76.3 -Bischoff #15-47,68.4 +Bischoff #15-47,72.1 Bischoff #15-48,78.6 -Bischoff #15-49,73.3 -Bischoff #15-50,78.7 -Bischoff #15-51,75 +Bischoff #15-49,74.5 +Bischoff #15-50,81.1 +Bischoff #15-51,75.0 Bischoff #15-52,75.8 Bischoff #15-53,80.4 -Bischoff #15-54,72.3 -Bischoff #15-55,70.0 -Bischoff #15-56,73.0 -Bischoff #15-57,73.9 -Bischoff #15-58,83.1 -Bischoff #15-59,77 -Bischoff #15-60,72.2 +Bischoff #15-54,72.7 +Bischoff #15-55,71.8 +Bischoff #15-56,76.6 +Bischoff #15-57,72.9 +Bischoff #15-58,83.2 +Bischoff #15-59,77.0 +Bischoff #15-60,73.8 Bischoff #15-61,69.7 -Bischoff #15-62,81.3 -Bischoff #15-63,73.3 +Bischoff #15-62,81.8 +Bischoff #15-63,73.5 Bischoff #15-64,70.7 Bischoff #15-65,82.3 -Bischoff #15-66,68.6 +Bischoff #15-66,69.0 Bischoff #15-67,71.4 Bischoff #15-68,76.7 -Bischoff #15-69,76 -Bischoff #15-70,75.7 +Bischoff #15-69,76.4 +Bischoff #15-70,76.1 Bischoff #15-71,73.1 Bischoff #15-72,79.6 Bischoff #15-73,74.6 -Bischoff #15-74,68.3 -Bischoff #15-75,80 -Bischoff #15-76,77.0 +Bischoff #15-74,69.4 +Bischoff #15-75,80.5 +Bischoff #15-76,77.6 Bischoff #15-77,73.6 Bischoff #15-78,74.5 -Bischoff #15-79,73.4 +Bischoff #15-79,74.5 Bischoff #15-80,73.5 Bischoff #15-81,71.3 Bischoff #15-82,77.8 Bischoff #15-83,74.7 -Bischoff #15-84,81.1 +Bischoff #15-84,81.5 Bischoff #15-85,75.5 -Bischoff #15-86,76.3 +Bischoff #15-86,79.7 Bischoff #15-87,73.8 Bischoff #15-88,66.3 Bischoff #15-89,69.7 -Bischoff #15-90,80.5 -Bischoff #15-91,74.7 +Bischoff #15-90,81.2 +Bischoff #15-91,77.3 Bischoff #15-92,66.4 Bischoff #15-93,69.1 -Bischoff #15-94,72.7 +Bischoff #15-94,73.3 Bischoff #15-95,76.2 Bischoff #15-96,76.4 -Bischoff #15-97,70.7 -Bischoff #15-98,69.1 -Bischoff #15-99,77.5 -Bischoff #15-100,78.5 -Bischoff #20-1,77.4 -Bischoff #20-2,80.3 -Bischoff #20-3,71.5 -Bischoff #20-4,78.5 +Bischoff #15-97,72.1 +Bischoff #15-98,70.4 +Bischoff #15-99,79.9 +Bischoff #15-100,80.0 +Bischoff #20-1,78.9 +Bischoff #20-2,80.7 +Bischoff #20-3,72.0 +Bischoff #20-4,80.3 Bischoff #20-5,75.7 -Bischoff #20-6,71.2 -Bischoff #20-7,69.3 -Bischoff #20-8,72.2 -Bischoff #20-9,73.2 -Bischoff #20-10,73.5 -Bischoff #20-11,72.7 -Bischoff #20-12,71.4 -Bischoff #20-13,70.8 -Bischoff #20-14,75.8 -Bischoff #20-15,75.4 -Bischoff #20-16,77 -Bischoff #20-17,71.9 -Bischoff #20-18,75.7 -Bischoff #20-19,75.7 -Bischoff #20-20,74.8 -Bischoff #20-21,73.8 -Bischoff #20-22,79.4 -Bischoff #20-23,79.4 +Bischoff #20-6,72.4 +Bischoff #20-7,70.0 +Bischoff #20-8,74.3 +Bischoff #20-9,73.8 +Bischoff #20-10,74.5 +Bischoff #20-11,74.7 +Bischoff #20-12,75.9 +Bischoff #20-13,71.9 +Bischoff #20-14,76.8 +Bischoff #20-15,77.6 +Bischoff #20-16,77.0 +Bischoff #20-17,73.1 +Bischoff #20-18,77.7 +Bischoff #20-19,77.8 +Bischoff #20-20,75.0 +Bischoff #20-21,74.0 +Bischoff #20-22,79.7 +Bischoff #20-23,81.6 Bischoff #20-24,73.5 -Bischoff #20-25,63.2 -Bischoff #20-26,71.2 +Bischoff #20-25,68.2 +Bischoff #20-26,73.4 Bischoff #20-27,76.1 -Bischoff #20-28,82 -Bischoff #20-29,75.6 -Bischoff #20-30,76.0 -Bischoff #20-31,66.4 -Bischoff #20-32,73.9 -Bischoff #20-33,75 -Bischoff #20-34,69.5 -Bischoff #20-35,74.8 -Bischoff #20-36,72.5 -Bischoff #20-37,79 -Bischoff #20-38,70.1 -Bischoff #20-39,77.8 +Bischoff #20-28,82.0 +Bischoff #20-29,77.5 +Bischoff #20-30,76.4 +Bischoff #20-31,68.1 +Bischoff #20-32,74.1 +Bischoff #20-33,75.0 +Bischoff #20-34,70.3 +Bischoff #20-35,77.7 +Bischoff #20-36,74.7 +Bischoff #20-37,79.0 +Bischoff #20-38,71.2 +Bischoff #20-39,79.0 Bischoff #20-40,75.4 Bischoff #20-41,78.6 Bischoff #20-42,74.4 -Bischoff #20-43,73.5 -Bischoff #20-44,73.1 -Bischoff #20-45,75.3 -Bischoff #20-46,76.9 -Bischoff #20-47,70.6 +Bischoff #20-43,74.4 +Bischoff #20-44,74.6 +Bischoff #20-45,75.5 +Bischoff #20-46,77.2 +Bischoff #20-47,71.9 Bischoff #20-48,70.9 -Bischoff #20-49,69.4 +Bischoff #20-49,69.7 Bischoff #20-50,76.8 -Bischoff #20-51,70.1 +Bischoff #20-51,70.6 Bischoff #20-52,73.6 Bischoff #20-53,84.9 -Bischoff #20-54,72.8 +Bischoff #20-54,73.7 Bischoff #20-55,69.5 -Bischoff #20-56,72.7 +Bischoff #20-56,75.4 Bischoff #20-57,76.8 Bischoff #20-58,77.8 -Bischoff #20-59,73.1 -Bischoff #20-60,76.3 +Bischoff #20-59,74.2 +Bischoff #20-60,77.0 Bischoff #20-61,68.5 -Bischoff #20-62,72.7 +Bischoff #20-62,73.6 Bischoff #20-63,69.1 -Bischoff #20-64,77.5 +Bischoff #20-64,72.7 Bischoff #20-65,79.8 Bischoff #20-66,72.8 Bischoff #20-67,72.1 Bischoff #20-68,70.3 -Bischoff #20-69,73.3 -Bischoff #20-70,75.7 +Bischoff #20-69,74.0 +Bischoff #20-70,76.5 Bischoff #20-71,70.3 Bischoff #20-72,78.6 -Bischoff #20-73,70.5 -Bischoff #20-74,74.7 -Bischoff #20-75,72.5 +Bischoff #20-73,71.3 +Bischoff #20-74,77.2 +Bischoff #20-75,73.2 Bischoff #20-76,80.5 -Bischoff #20-77,80.0 -Bischoff #20-78,72.1 -Bischoff #20-79,70.2 -Bischoff #20-80,73.3 +Bischoff #20-77,80.6 +Bischoff #20-78,73.3 +Bischoff #20-79,70.5 +Bischoff #20-80,75.0 Bischoff #20-81,69.9 Bischoff #20-82,75.7 Bischoff #20-83,71.7 -Bischoff #20-84,80.3 -Bischoff #20-85,74.7 -Bischoff #20-86,73.4 -Bischoff #20-87,71.8 -Bischoff #20-88,74.1 -Bischoff #20-89,72.8 -Bischoff #20-90,80.1 +Bischoff #20-84,83.0 +Bischoff #20-85,75.3 +Bischoff #20-86,74.0 +Bischoff #20-87,73.1 +Bischoff #20-88,74.7 +Bischoff #20-89,69.4 +Bischoff #20-90,80.4 Bischoff #20-91,75.6 Bischoff #20-92,76.1 Bischoff #20-93,68.8 -Bischoff #20-94,70.6 -Bischoff #20-95,76.1 -Bischoff #20-96,75.6 -Bischoff #20-97,73.9 +Bischoff #20-94,71.8 +Bischoff #20-95,76.4 +Bischoff #20-96,76.8 +Bischoff #20-97,76.2 Bischoff #20-98,71.6 -Bischoff #20-99,76.1 -Bischoff #20-100,73 +Bischoff #20-99,77.5 +Bischoff #20-100,73.0