Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php declare(strict_types=1);
- namespace App\Services;
- use App\Factories\ContainerTypeFactory;
- use App\Factories\ItemTypeFactory;
- use App\Models\CLP\Container;
- use App\Models\CLP\Item;
- use App\Models\CLP\ItemLocation;
- use App\Models\CLP\ItemType;
- use App\Models\CLP\Solution;
- use Exception;
- use function DeepCopy\deep_copy;
- \set_time_limit(5000);
- /**
- * Class LNSService
- * @package App\Services
- */
- class LNSService
- {
- // epsilon is used to margin certain calculations
- /**
- * @var float
- */
- private $epsilon = 0.0001;
- /**
- * @var ItemType[]
- */
- public $itemTypeList;
- /**
- * @var
- */
- public $containerTypeList;
- /**
- * @var
- */
- public $solverOptions;
- /**
- * @var ContainerTypeFactory
- */
- private $containerTypeFactory;
- /**
- * @var ItemTypeFactory
- */
- private $itemTypeFactory;
- /**
- * @var
- */
- public $bestKnown;
- /**
- * @var SolutionService
- */
- private $solutionService;
- /**
- * @var
- */
- private $infeasibilityMessages;
- // TODO Added for debugging and performance measuring
- /**
- * @var
- */
- private $measurements;
- /**
- * @var
- */
- private $nextMeasurement;
- /**
- * @var
- */
- private $startedAt;
- /**
- * @var
- */
- private $iterations;
- /**
- * LNSService constructor.
- */
- public function __construct()
- {
- $this->containerTypeFactory = new ContainerTypeFactory();
- $this->itemTypeFactory = new ItemTypeFactory();
- $this->solutionService = new SolutionService();
- }
- /**
- * Main function that calculates the best solution via the LNS algorithm
- * - is dependant on itemTypeList and containerTypeList;
- *
- * @return array
- */
- public function CLPSolver()
- {
- // TODO this is just for measurements
- $this->startedAt = \microtime(true);
- $this->nextMeasurement = 500;
- $this->measurements = [];
- // Creates a new solution en initializes it.
- $incumbent = $this->solutionService->initialize($this->containerTypeList, $this->itemTypeList);
- $this->bestKnown = deep_copy($incumbent);
- // Sets start and end time to calculate how many iterations it can do
- $startTime = \time();
- $endTime = \time();
- // Check for any infeasibilities
- if ($this->getInfeasibilities() > 0) {
- // Throw an error
- return $this->infeasibilityMessages;
- }
- // Insertion sort of the containers with no random factor
- $this->sortContainers($incumbent, 0);
- foreach ($incumbent->containers as $containerIndex => $container) {
- // Calculates the order of rotation for each container
- $this->calculateRotationOrder($incumbent, $container);
- // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
- $incumbent = $this->packUnpackedItems($incumbent, $containerIndex);
- // Calculates the distance based on the amount of filled containers
- $this->calculateDispersion($incumbent);
- // Check if current solution is better then the best known solution
- $this->checkIfBestKnown($incumbent);
- }
- // Iteration counter, has informative purposes
- $iteration = 0;
- do {
- // 50 percent chance to continue with the best known solution
- // TODO This is just to make is consistent
- // if ((\mt_rand() / \mt_getrandmax()) < 0.5) {
- if (true) {
- $incumbent = deep_copy($this->bestKnown);
- }
- // Chance to empty a container or remove some items from a container. does this for each container
- foreach ($incumbent->containers as $containerIndex => $container) {
- $this->perturbSolution($incumbent, $containerIndex, (int)(1 - ($endTime - $startTime) / $this->solverOptions->CPU_time_limit));
- }
- // Insertion sorts the containers inside the solution with a 20 percent chance to do it randomly
- $this->sortContainers($incumbent, 0.2);
- $containerCount = \count($incumbent->containers);
- for ($i = 0; $i < $containerCount; $i++) {
- // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
- $incumbent = $this->packUnpackedItems($incumbent, $i);
- // Calculates the distance based on the amount of filled containers
- $this->calculateDispersion($incumbent);
- // Check if current solution is better then the best known solution
- $this->checkIfBestKnown($incumbent, true);
- }
- $iteration++;
- $endTime = \time();
- if ($endTime < $startTime - 0.01) {
- $this->solverOptions->CPU_time_limit -= (86400 - $startTime);
- $startTime = $endTime;
- }
- } while (($endTime - $startTime) < ($this->solverOptions->CPU_time_limit / 3));
- // Calculates the distance of the best known solution based on the measurements of items within the containers
- $this->calculateDistance($this->bestKnown);
- // Calculate how many containers are filled inside the best known solution
- $nonEmptyContainerCount = 0;
- foreach ($this->bestKnown->containers as $container) {
- if (\count($container->items) > 0) {
- $nonEmptyContainerCount++;
- }
- }
- foreach ($this->bestKnown->containers as $containerIndex => $container) {
- // Set current solution to the best known if the container contains items
- if (\count($container->items) > 0) {
- $incumbent = deep_copy($this->bestKnown);
- $startTime = \time();
- $endTime = \time();
- do {
- // Chance to empty a container or remove some items from a container. percentTimeLeft is calculates based on 2/3 of the cpu time and amount of filled containers
- $this->perturbSolution($incumbent, $containerIndex, 0.1 + 0.2 * (($endTime - $startTime) / (($this->solverOptions->CPU_time_limit * 0.666) / $nonEmptyContainerCount)));
- // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
- $incumbent = $this->packUnpackedItems($incumbent, $containerIndex);
- // Calculates the distance of the best known solution based on the measurements of items within the containers
- $this->calculateDistance($incumbent);
- // Check if current solution is better then the best known solution
- $this->checkIfBestKnown($incumbent, true);
- $endTime = \time();
- if ($endTime < $startTime - 0.01) {
- $this->solverOptions->CPU_time_limit -= (86400 - $startTime);
- $startTime = $endTime;
- }
- } while (($endTime - $startTime) < ($this->solverOptions->CPU_time_limit / 3));
- }
- }
- // Organizes the best known based on the loading order defined inside the solver options
- return $this->calculateLoadingOrder();
- }
- /**
- * Organizes the solution based on the loading order of the solver options
- * 1: standard
- * 2: lengthwise
- * 3: diagonal
- * 4: blockage free
- *
- * @return array
- */
- public function calculateLoadingOrder(): array
- {
- switch ($this->solverOptions->loading_order) {
- // lengthwise
- case 2:
- try {
- $this->organizeLengthwise();
- } catch (Exception $exception) {
- echo $exception;
- }
- break;
- // Diagonal
- case 3:
- try {
- $this->organizeDiagonal();
- } catch (Exception $exception) {
- echo $exception;
- }
- break;
- // Blockage free
- case 4:
- try {
- $this->organizeBlockageFree();
- } catch (Exception $exception) {
- echo $exception;
- }
- break;
- // Standard
- default:
- $this->organizeStandard();
- break;
- }
- // TODO Added iterations to response for debugging
- // return $this->bestKnown;
- return array($this->bestKnown, $this->iterations, $this->measurements);
- }
- /**
- * Organizes the solution lengthwise (Insertion sort)
- *
- * @throws Exception
- */
- public function organizeLengthwise(): void
- {
- foreach ($this->bestKnown->containers as $container) {
- $itemCount = \count($container->items);
- for ($j = 0; $j < $itemCount; $j++) {
- // Set the minimal position values to the container position values
- $minX = $container->width;
- $minY = $container->height;
- $minZ = $container->length;
- $selectedItemIndex = -1;
- for ($k = $j; $k < $itemCount; $k++) {
- // Check if the length of the item is the smallest
- if (($container->items[$k]->origin_z < $minZ - $this->epsilon) ||
- (($container->items[$k]->origin_z < $minZ + $this->epsilon) && ($container->items[$k]->origin_y < $minY - $this->epsilon)) ||
- (($container->items[$k]->origin_z < $minZ + $this->epsilon) && ($container->items[$k]->origin_y < $minY + $this->epsilon) && ($container->items[$k]->origin_x < $minX - $this->epsilon))) {
- $selectedItemIndex = $k;
- $minX = $container->items[$k]->origin_x;
- $minY = $container->items[$k]->origin_y;
- $minZ = $container->items[$k]->origin_z;
- }
- }
- // Swap the smallest item to the front of the list
- if ($selectedItemIndex > -1) {
- $swapItem = $container->items[$selectedItemIndex];
- $container->items[$selectedItemIndex] = deep_copy($container->items[$j]);
- $container->items[$j] = $swapItem;
- } else {
- // Loading order could not be constructed
- throw new Exception('Loading order could not be constructed');
- }
- $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
- }
- }
- }
- /**
- * Organizes the solution diagonal (Insertion sort)
- *
- * @throws Exception
- */
- public function organizeDiagonal(): void
- {
- foreach ($this->bestKnown->containers as $container) {
- $itemCount = \count($container->items);
- for ($j = 0; $j < $itemCount; $j++) {
- // Set the minimal position values to the container position values
- $minX = $container->width;
- $minY = $container->height;
- $minZ = $container->length;
- $selectedItemIndex = -1;
- for ($k = $j; $k < $itemCount; $k++) {
- // Check if the diagonal of an item is the smallest
- if (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ - $this->epsilon) ||
- (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ + $this->epsilon) && ($container->items[$k]->origin_z < $minZ - $this->epsilon)) ||
- (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ + $this->epsilon) && ($container->items[$k]->opposite_z < $minZ + $this->epsilon) && ($container->items[$k]->origin_y < $minY - $this->epsilon)) ||
- (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ + $this->epsilon) && ($container->items[$k]->opposite_z < $minZ + $this->epsilon) && ($container->items[$k]->opposite_y < $minY + $this->epsilon) && ($container->items[$k]->origin_x < $minX - $this->epsilon))) {
- $selectedItemIndex = $k;
- $minX = $container->items[$k]->origin_x;
- $minY = $container->items[$k]->origin_y;
- $minZ = $container->items[$k]->origin_z;
- }
- }
- // Swap the smallest item to the front of the list
- if ($selectedItemIndex > -1) {
- $swapItem = $container->items[$selectedItemIndex];
- $container->items[$selectedItemIndex] = $container->items[$j];
- $container->items[$j] = $swapItem;
- } else {
- // Loading order could not be constructed
- throw new Exception('Loading order could not be constructed');
- }
- $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
- }
- }
- }
- /**
- * Organizes the solution blockage free
- *
- * @throws Exception
- */
- public function organizeBlockageFree(): void
- {
- foreach ($this->bestKnown->containers as $container) {
- $lastSelectedItemType = 0;
- $itemCount = \count($container->items);
- for ($j = 0; $j < $itemCount; $j++) {
- $selectedItemIndex = -1;
- $maxX = -1;
- $maxY = -1;
- $maxZ = -1;
- for ($k = $itemCount - 1 - $j; $k >= 0; $k--) {
- $blocked = false;
- for ($m = 0; $m < $itemCount - 1 - $j; $m++) {
- if ($container->items[$k]->opposite_y < $container->items[$m]->origin_y + $this->epsilon) {
- $blocked = $this->checkIntersection($container, $m, $k);
- }
- if ($container->items[$k]->opposite_z < $container->items[$m]->origin_z + $this->epsilon) {
- $blocked = $this->checkIntersection($container, $m, $k);
- }
- }
- if ($blocked) {
- if ($selectedItemIndex === -1) {
- $selectedItemIndex = $k;
- $maxX = $container->items[$k]->opposite_x;
- $maxY = $container->items[$k]->opposite_y;
- $maxZ = $container->items[$k]->opposite_z;
- } else {
- if ($container->items[$selectedItemIndex]->item_type !== $lastSelectedItemType && $container->items[$k]->item_type === $lastSelectedItemType) {
- $selectedItemIndex = $k;
- $maxX = $container->items[$k]->opposite_x;
- $maxY = $container->items[$k]->opposite_y;
- $maxZ = $container->items[$k]->opposite_z;
- } elseif ($container->items[$selectedItemIndex]->item_type !== $lastSelectedItemType && $container->items[$k]->item_type !== $lastSelectedItemType) {
- if (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z > $maxY + $maxZ + $this->epsilon) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z > $maxZ + $this->epsilon)) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z >= $maxZ - $this->epsilon) && ($container->items[$k]->opposite_y > $maxY + $this->epsilon)) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z >= $maxZ - $this->epsilon) && ($container->items[$k]->opposite_y >= $maxY - $this->epsilon) && ($container->items[$k]->opposite_x > $maxX))) {
- $selectedItemIndex = $k;
- $maxX = $container->items[$k]->opposite_x;
- $maxY = $container->items[$k]->opposite_y;
- $maxZ = $container->items[$k]->opposite_z;
- }
- } elseif ($container->items[$selectedItemIndex]->item_type === $lastSelectedItemType && $container->items[$k]->item_type === $lastSelectedItemType) {
- if (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z > $maxY + $maxZ + $this->epsilon) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z > $maxZ + $this->epsilon)) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z >= $maxZ - $this->epsilon) && ($container->items[$k]->opposite_y > $maxY + $this->epsilon)) ||
- (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z >= $maxZ - $this->epsilon) && ($container->items[$k]->opposite_y >= $maxY - $this->epsilon) && ($container->items[$k]->opposite_x > $maxX))) {
- $selectedItemIndex = $k;
- $maxX = $container->items[$k]->opposite_x;
- $maxY = $container->items[$k]->opposite_y;
- $maxZ = $container->items[$k]->opposite_z;
- }
- }
- }
- }
- }
- if ($selectedItemIndex > -1) {
- $lastSelectedItemType = $container->items[$selectedItemIndex]->item_type;
- $swapItem = $container->items[$selectedItemIndex];
- $container->items[$selectedItemIndex] = $container->items[$itemCount + 1 - $j];
- $container->items[$itemCount + 1 - $j] = $swapItem;
- } else {
- // Loading order could not be constructed
- throw new Exception('Loading order could not be constructed');
- }
- $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
- }
- }
- }
- /**
- * The standard way of organizing the items in the container
- * The algorithm will not take the blockage, diagonal or lengthwise options into consideration
- */
- public function organizeStandard(): void
- {
- foreach ($this->bestKnown->containers as $container) {
- $itemCount = \count($container->items);
- for ($j = 0; $j < $itemCount; $j++) {
- for ($k = 0; $k < $itemCount - $j; $k++) {
- $blocked = false;
- $blockingItem = 0;
- for ($m = $k - 1; $m > 0; $m--) {
- // Check if previous item is able to intersect another item based on the y position (height)
- if ($container->items[$k]->opposite_y < $container->items[$m]->origin_y + $this->epsilon) {
- // Check if the 2 items are intersecting each other
- $blocked = $this->checkIntersection($container, $m, $k);
- if ($blocked) {
- $blockingItem = $m;
- }
- }
- // Check if previous item is able to intersect another item based on the z position (length)
- if ($container->items[$k]->opposite_z < $container->items[$m]->origin_z + $this->epsilon) {
- // Check if the 2 items are intersecting each other
- $blocked = $this->checkIntersection($container, $m, $k);
- if ($blocked) {
- $blockingItem = $m;
- }
- }
- }
- // Swap blocked item
- if ($blocked) {
- $swapItem = $container->items[$k];
- for ($m = $k; $m < $blockingItem + 1; $m--) {
- $container->items[$m] = deep_copy($container->items[$m - 1]);
- }
- $container->items[$blockingItem] = $swapItem;
- }
- }
- $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
- }
- }
- }
- /**
- * Checks if 2 items are intersecting each other
- *
- * @param Container $container
- * @param int $m
- * @param int $k
- * @return bool
- */
- public function checkIntersection(Container $container, int $m, int $k): bool
- {
- $blocked = false;
- $intersectionRight = $container->items[$m]->opposite_x;
- if ($intersectionRight > $container->items[$k]->opposite_x) {
- $intersectionRight = $container->items[$k]->opposite_x;
- }
- $intersectionLeft = $container->items[$m]->origin_x;
- if ($intersectionLeft < $container->items[$k]->origin_x) {
- $intersectionLeft = $container->items[$k]->origin_x;
- }
- $intersectionTop = $container->items[$m]->opposite_z;
- if ($intersectionTop > $container->items[$k]->opposite_z) {
- $intersectionTop = $container->items[$k]->opposite_z;
- }
- $intersectionBottom = $container->items[$m]->origin_z;
- if ($intersectionBottom < $container->items[$k]->origin_z) {
- $intersectionBottom = $container->items[$k]->origin_z;
- }
- if (($intersectionRight > $intersectionLeft + $this->epsilon) && ($intersectionTop > $intersectionBottom + $this->epsilon)) {
- $blocked = true;
- }
- return $blocked;
- }
- /**
- * Packs items based on the unpacked item count within the solution.
- * - Fills the containers based on the position within the container array inside the solution.
- * - continue will be false if the item doesn't fit inside the container
- *
- * Checks if the solution is feasible.
- *
- * @param Solution $solution
- * @param int $containerIndex
- * @return Solution
- */
- public function packUnpackedItems(Solution $solution, int $containerIndex): Solution
- {
- $itemTypeCount = \count($this->itemTypeList);
- // Keeps adding items to the container if possible and needed
- for ($j = 0; $j < $itemTypeCount; $j++) {
- $continue = true;
- while (($solution->unpacked_item_count[$solution->item_type_order[$j]] > 0) && $continue) {
- $continue = $this->addItemToContainer($solution, $containerIndex, $solution->item_type_order[$j], 1);
- }
- }
- // Checks if all the mandatory items are packed inside the containers of the solution
- $solution->feasible = true;
- for ($j = 0; $j < $itemTypeCount; $j++) {
- if ($solution->unpacked_item_count[$j] > 0 && $this->itemTypeList[$j]->mandatory === 1) {
- $solution->feasible = false;
- }
- }
- return $solution;
- }
- /**
- * Check if the solution given is better than the best known solution
- * - Able to pass distance check boolean, if distance needs to be checked
- *
- * @param Solution $solution
- * @param bool $distanceCheck
- */
- public function checkIfBestKnown(Solution $solution, bool $distanceCheck = false): void
- {
- // Checks if the solution has a better feasibility, volume, net profit and total volume
- if (($solution->feasible && !$this->bestKnown->feasible) ||
- (!$solution->feasible && !$this->bestKnown->feasible && $solution->total_volume > $this->bestKnown->total_volume + $this->epsilon) ||
- ($solution->feasible && $this->bestKnown->feasible && $solution->net_profit > $this->bestKnown->net_profit + $this->epsilon) ||
- ($solution->feasible && $this->bestKnown->feasible && ($solution->net_profit > $this->bestKnown->net_profit - $this->epsilon) && ($solution->total_volume < $this->bestKnown->total_volume - $this->epsilon))) {
- $this->bestKnown = deep_copy($solution);
- // Checks if the solution has a better distance, total x moment and total yz moment
- } elseif ($distanceCheck &&
- ($solution->feasible && $this->bestKnown->feasible && ($solution->net_profit > $this->bestKnown->net_profit - $this->epsilon) && ($solution->total_volume < $this->bestKnown->total_volume + $this->epsilon) && ($solution->total_distance < $this->bestKnown->total_distance - $this->epsilon)) ||
- ($solution->feasible && $this->bestKnown->feasible && ($solution->net_profit > $this->bestKnown->net_profit - $this->epsilon) && ($solution->total_volume < $this->bestKnown->total_volume + $this->epsilon) && ($solution->total_distance < $this->bestKnown->total_distance + $this->epsilon) && ($solution->total_x_moment < $this->bestKnown->total_x_moment - $this->epsilon)) ||
- ($solution->feasible && $this->bestKnown->feasible && ($solution->net_profit > $this->bestKnown->net_profit - $this->epsilon) && ($solution->total_volume < $this->bestKnown->total_volume + $this->epsilon) && ($solution->total_distance < $this->bestKnown->total_distance + $this->epsilon) && ($solution->total_x_moment < $this->bestKnown->total_x_moment + $this->epsilon) && ($solution->total_yz_moment < $this->bestKnown->total_yz_moment - $this->epsilon))) {
- $this->bestKnown = deep_copy($solution);
- }
- $this->iterations++;
- if ($this->iterations === $this->nextMeasurement) {
- array_push($this->measurements, array("iterations" => $this->iterations, "time" => \microtime(true) - $this->startedAt));
- $this->nextMeasurement += 500;
- }
- }
- /**
- * Calculates the rotation order based on sort criterion calculations
- *
- * @param Solution $solution
- * @param Container $container
- */
- public function calculateRotationOrder(Solution $solution, Container $container): void
- {
- foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
- $sortCriterion = 0;
- $selectedRotation = 0;
- $calculatedSortCriterion = ((int)($container->width / $itemType->width) * $itemType->width) * ((int)($container->height / $itemType->height) * $itemType->height);
- if ($sortCriterion < $calculatedSortCriterion) {
- $sortCriterion = $calculatedSortCriterion;
- $selectedRotation = 1;
- }
- $calculatedSortCriterion = ((int)($container->width / $itemType->length) * $itemType->length) * ((int)($container->height / $itemType->height) * $itemType->height);
- if ($sortCriterion < $calculatedSortCriterion) {
- $sortCriterion = $calculatedSortCriterion;
- $selectedRotation = 2;
- }
- $calculatedSortCriterion = ((int)($container->width / $itemType->width) * $itemType->width) * ((int)($container->height / $itemType->length) * $itemType->length);
- if ($itemType->xy_rotatable && $sortCriterion < $calculatedSortCriterion) {
- $sortCriterion = $calculatedSortCriterion;
- $selectedRotation = 3;
- }
- $calculatedSortCriterion = ((int)($container->width / $itemType->height) * $itemType->height) * ((int)($container->height / $itemType->length) * $itemType->length);
- if ($itemType->xy_rotatable && $sortCriterion < $calculatedSortCriterion) {
- $sortCriterion = $calculatedSortCriterion;
- $selectedRotation = 4;
- }
- $calculatedSortCriterion = ((int)($container->width / $itemType->height) * $itemType->height) * ((int)($container->height / $itemType->width) * $itemType->width);
- if ($itemType->yz_rotatable && $sortCriterion < $calculatedSortCriterion) {
- $sortCriterion = $calculatedSortCriterion;
- $selectedRotation = 5;
- }
- $calculatedSortCriterion = ((int)($container->width / $itemType->length) * $itemType->length) * ((int)($container->height / $itemType->width) * $itemType->width);
- if ($itemType->yz_rotatable && $sortCriterion < $calculatedSortCriterion) {
- $selectedRotation = 6;
- }
- if ($selectedRotation === 0) {
- $selectedRotation = 1;
- }
- $solution->rotation_order[$itemTypeIndex][0] = $selectedRotation;
- $solution->rotation_order[$itemTypeIndex][$selectedRotation - 1] = 1;
- }
- }
- /**
- * Set options for the solver (CPUTimeLimit and itemSortCriterion)
- *
- * @param array $options
- */
- public function setSolverOptions(array $options): void
- {
- $this->solverOptions = (object)$options;
- }
- /**
- * Fills the item type array and calculates values of item type.
- *
- * @param array $itemTypes
- */
- public function setItemTypeData(array $itemTypes): void
- {
- $this->itemTypeList = [];
- for ($i = 0; $i < \count($itemTypes); $i++) {
- $itemType = $this->itemTypeFactory->createSolutionType($itemTypes[$i], $this->solverOptions);
- \array_push($this->itemTypeList, $itemType);
- }
- $this->itemTypeList = $this->sortItems($this->itemTypeList);
- }
- /**
- * Fills the container type array and calculates the volume capacity
- *
- * @param array $containerTypes
- */
- public function setContainerData(array $containerTypes): void
- {
- $this->containerTypeList = [];
- $containerTypesCount = \count($containerTypes);
- for ($i = 0; $i < $containerTypesCount; $i++) {
- $containerType = $this->containerTypeFactory->createSolutionType($containerTypes[$i]);
- \array_push($this->containerTypeList, $containerType);
- }
- }
- /**
- * Sorts the item type list based on sort criterion and profit/volume margin
- *
- * @param array $itemTypes
- * @return array
- */
- public function sortItems(array $itemTypes)
- {
- $itemTypesCount = \count($itemTypes);
- if ($itemTypesCount > 0) {
- for ($i = 0; $i < $itemTypesCount; $i++) {
- for ($j = $itemTypesCount - 1; $j >= 1; $j--) {
- $currentItemType = $itemTypes[$j];
- $previousItemType = $itemTypes[$j - 1];
- if (
- ($currentItemType->mandatory > $previousItemType->mandatory) ||
- (($currentItemType->mandatory === 1) && ($previousItemType->mandatory === 1) && ($currentItemType->sort_criterion > $previousItemType->sort_criterion)) ||
- (($currentItemType->mandatory === 0) && ($previousItemType->mandatory === 0) && (($currentItemType->profit / $currentItemType->volume) > ($previousItemType->profit / $previousItemType->volume)))
- ) {
- $itemTypes[$j] = $previousItemType;
- $itemTypes[$j - 1] = $currentItemType;
- }
- }
- }
- }
- return $itemTypes;
- }
- /**
- * Insertion sort of the containers within a solution
- * - Random reorder probability defines the chance to reorder randomly (0.2 === 20%)
- *
- * @param Solution $solution
- * @param float $randomReorderProbability
- */
- public function sortContainers(Solution &$solution, float $randomReorderProbability): void
- {
- $containerCount = \count($solution->containers);
- // Check if random doesn't fall under the random reorder probability
- // TODO This is just to make is consistent
- // if ($this->getRandom() < 1 - $randomReorderProbability) {
- if (true) {
- for ($i = 0; $i < $containerCount; $i++) {
- $candidateIndex = $i;
- $maxMandatory = $solution->containers[$i]->mandatory;
- $maxVolumePacked = $solution->containers[$i]->volume_packed;
- $minRatio = $solution->containers[$i]->cost / $solution->containers[$i]->volume_capacity;
- for ($j = $i + 1; $j < $containerCount; $j++) {
- $currentContainer = $solution->containers[$j];
- // check which container has the greatest volume packed and ratio
- if (
- ($currentContainer->mandatory > $maxMandatory) ||
- (($currentContainer->mandatory === $maxMandatory) && ($currentContainer->volume_packed > $maxVolumePacked + $this->epsilon)) ||
- (($currentContainer->mandatory === 0) && ($maxMandatory === 0) && $currentContainer->volume_packed > $maxVolumePacked - $this->epsilon) && (($currentContainer->cost / $currentContainer->volume_capacity) < $minRatio)
- ) {
- $candidateIndex = $j;
- $maxMandatory = $currentContainer->mandatory;
- $maxVolumePacked = $currentContainer->volume_packed;
- $minRatio = $currentContainer->cost / $currentContainer->volume_capacity;
- }
- }
- // Swap the containers
- if ($candidateIndex !== $i) {
- $swapContainer = $solution->containers[$candidateIndex];
- $solution->containers[$candidateIndex] = $solution->containers[$i];
- $solution->containers[$i] = $swapContainer;
- }
- }
- } else {
- // Reorder the containers based on a random index
- for ($i = 0; $i < $containerCount; $i++) {
- $candidateIndex = \rand($i, $containerCount - 1);
- if ($candidateIndex !== $i) {
- $swapContainer = $solution->containers[$candidateIndex];
- $solution->containers[$candidateIndex] = $solution->containers[$i];
- $solution->containers[$i] = $swapContainer;
- }
- }
- }
- }
- /**
- * Checks for any infeasibilities
- * - Checks if the total volume of items fits into the total volume of containers
- * - Checks if the total weight of items fits into the total weight of containers
- * - Checks if each item fits into the container based on its height, length and width
- *
- * @return int $count
- */
- public function getInfeasibilities(): int
- {
- $count = 0;
- $volumeCapacityRequired = 0;
- $volumeCapacityAvailable = 0;
- $weightCapacityRequired = 0;
- $weightCapacityAvailable = 0;
- $maxWidth = 0;
- $maxHeight = 0;
- $maxLength = 0;
- // Calculates the volume and weight needed
- foreach ($this->itemTypeList as $itemType) {
- if ($itemType->mandatory === 1) {
- $volumeCapacityRequired += ($itemType->volume * $itemType->number_requested);
- $weightCapacityRequired += ($itemType->weight * $itemType->number_requested);
- }
- }
- // Calculates the volume and weight available and calculates the max width, length and height
- foreach ($this->containerTypeList as $containerType) {
- if ($containerType->mandatory >= 0) {
- $volumeCapacityAvailable += ($containerType->volume_capacity * $containerType->number_available);
- $weightCapacityAvailable += ($containerType->weight_capacity * $containerType->number_available);
- if ($containerType->width > $maxWidth) {
- $maxWidth = $containerType->width;
- }
- if ($containerType->height > $maxHeight) {
- $maxHeight = $containerType->height;
- }
- if ($containerType->length > $maxLength) {
- $maxLength = $containerType->length;
- }
- }
- }
- // Checks if volume needed exceeds volume available
- if ($volumeCapacityRequired > $volumeCapacityAvailable + $this->epsilon) {
- $count++;
- $this->infeasibilityMessages[] = 'The amount of available volume is not enough to pack the mandatory items.';
- }
- // Checks if weight needed exceeds weight available
- if ($weightCapacityRequired > $weightCapacityAvailable + $this->epsilon) {
- $count++;
- $this->infeasibilityMessages[] = 'The amount of available weight capacity is not enough to pack the mandatory items.';
- }
- // Checks if each item fits into the container
- foreach ($this->itemTypeList as $itemType) {
- if ($itemType->mandatory === 1 && $itemType->xy_rotatable === false && $itemType->yz_rotatable === false && (($itemType->width > $maxWidth + $this->epsilon) || ($itemType->height > $maxHeight + $this->epsilon) || ($itemType->length > $maxLength + $this->epsilon))) {
- $count++;
- $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too large to fit into any container.';
- }
- if ($itemType->mandatory === 1 && ($itemType->width > $maxWidth + $this->epsilon) && ($itemType->width > $maxHeight + $this->epsilon) && ($itemType->width > $maxLength + $this->epsilon)) {
- $count++;
- $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too wide to fit into any container.';
- }
- if ($itemType->mandatory === 1 && ($itemType->height > $maxWidth + $this->epsilon) && ($itemType->height > $maxHeight + $this->epsilon) && ($itemType->height > $maxLength + $this->epsilon)) {
- $count++;
- $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too tall to fit into any container.';
- }
- if ($itemType->mandatory === 1 && ($itemType->length > $maxWidth + $this->epsilon) && ($itemType->length > $maxHeight + $this->epsilon) && ($itemType->length > $maxLength + $this->epsilon)) {
- $count++;
- $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too long to fit into any container.';
- }
- }
- return $count;
- }
- /**
- * Adds an item to the container within a solution
- *
- * @param Solution $solution
- * @param int $containerIndex
- * @param int $itemTypeIndex
- * @param int $addType
- * @return bool
- */
- public function addItemToContainer(Solution $solution, int $containerIndex, int $itemTypeIndex, int $addType): bool
- {
- $currentContainer = $solution->containers[$containerIndex];
- $currentItemType = $this->itemTypeList[$itemTypeIndex];
- $minX = $currentContainer->width + 1;
- $minY = $currentContainer->height + 1;
- $minZ = $currentContainer->length + 1;
- $candidatePosition = -1;
- $containerItemCount = \count($currentContainer->items);
- // Checks if the item is able to fit into the current container based on volume
- if (($currentContainer->volume_packed + $currentItemType->volume) > $currentContainer->volume_capacity) {
- goto addItemToContainerFinish;
- }
- // Checks if the item is able to fit into the current container based on weight
- if (($currentContainer->weight_packed + $currentItemType->weight) > $currentContainer->weight_capacity) {
- goto addItemToContainerFinish;
- }
- // Checks each rotation, based on the rotation order of the solution
- for ($i = 0; $i < 6; $i++) {
- $rotationIndex = $i;
- if ($candidatePosition !== -1) {
- goto addItemToContainerFinish;
- }
- $currentRotation = $solution->rotation_order[$itemTypeIndex][$rotationIndex];
- // Forbidden rotations
- if ((($currentRotation === 3) || ($currentRotation === 4)) && ($currentItemType->xy_rotatable === false)) {
- continue;
- }
- if ((($currentRotation === 5) || ($currentRotation === 6)) && ($currentItemType->yz_rotatable === false)) {
- continue;
- }
- // Symmetry breaking
- if (($currentRotation === 2) && (\abs($currentItemType->width - $currentItemType->length) < $this->epsilon)) {
- continue;
- }
- if (($currentRotation === 4) && (\abs($currentItemType->width - $currentItemType->height) < $this->epsilon)) {
- continue;
- }
- if (($currentRotation === 6) && (\abs($currentItemType->height - $currentItemType->length) < $this->epsilon)) {
- continue;
- }
- // TODO if everything works, maybe this can be improved as well
- // Loops through each addition point to find the next gap to put a new item
- for ($j = 0; $j < $currentContainer->addition_point_count; $j++) {
- if (\count($currentContainer->addition_points) - 1 >= $j) {
- $additionPoint = $currentContainer->addition_points[$j];
- $originX = $additionPoint->origin_x;
- $originY = $additionPoint->origin_y;
- $originZ = $additionPoint->origin_z;
- } else {
- $originX = 0;
- $originY = 0;
- $originZ = 0;
- }
- // set opposite positions based on the rotation which is calculated above
- switch ($currentRotation) {
- case 1:
- $oppositeX = $originX + $currentItemType->width;
- $oppositeY = $originY + $currentItemType->height;
- $oppositeZ = $originZ + $currentItemType->length;
- break;
- case 2:
- $oppositeX = $originX + $currentItemType->length;
- $oppositeY = $originY + $currentItemType->height;
- $oppositeZ = $originZ + $currentItemType->width;
- break;
- case 3:
- $oppositeX = $originX + $currentItemType->width;
- $oppositeY = $originY + $currentItemType->length;
- $oppositeZ = $originZ + $currentItemType->height;
- break;
- case 4:
- $oppositeX = $originX + $currentItemType->height;
- $oppositeY = $originY + $currentItemType->length;
- $oppositeZ = $originZ + $currentItemType->width;
- break;
- case 5:
- $oppositeX = $originX + $currentItemType->height;
- $oppositeY = $originY + $currentItemType->width;
- $oppositeZ = $originZ + $currentItemType->length;
- break;
- case 6:
- $oppositeX = $originX + $currentItemType->length;
- $oppositeY = $originY + $currentItemType->width;
- $oppositeZ = $originZ + $currentItemType->height;
- break;
- default:
- $oppositeX = 0;
- $oppositeY = 0;
- $oppositeZ = 0;
- break;
- }
- // Check the feasibility of all four corners, w.r.t. to the other items
- if (($oppositeX > $currentContainer->width + $this->epsilon) || ($oppositeY > $currentContainer->height + $this->epsilon) || ($oppositeZ > $currentContainer->length + $this->epsilon)) {
- continue;
- }
- // Check if there is conflict with other items within the container
- foreach ($currentContainer->items as $item) {
- if (
- ($oppositeX < $item->origin_x + $this->epsilon) ||
- ($item->opposite_x < $originX + $this->epsilon) ||
- ($oppositeY < $item->origin_y + $this->epsilon) ||
- ($item->opposite_y < $originY + $this->epsilon) ||
- ($oppositeZ < $item->origin_z + $this->epsilon) ||
- ($item->opposite_z < $originZ + $this->epsilon)
- ) {
- // No conflict
- } else {
- // Conflict
- continue 2 ;
- }
- }
- // Support
- if ($originY < $this->epsilon) {
- $support = true;
- } else {
- $areaSupported = 0;
- $support = false;
- for ($k = $containerItemCount - 1; $k >= 0; $k--) {
- $item = $currentContainer->items[$k];
- if (\abs($originY - $item->opposite_y) < $this->epsilon) {
- // Check for intersection
- $intersectionRight = $oppositeX;
- if ($intersectionRight > $item->opposite_x) {
- $intersectionRight = $item->opposite_x;
- }
- $intersectionLeft = $originX;
- if ($intersectionLeft < $item->origin_x) {
- $intersectionLeft = $item->origin_x;
- }
- $intersectionTop = $oppositeZ;
- if ($intersectionTop > $item->opposite_z) {
- $intersectionTop = $item->opposite_z;
- }
- $intersectionBottom = $originZ;
- if ($intersectionBottom < $item->origin_z) {
- $intersectionBottom = $item->origin_z;
- }
- if (($intersectionRight > $intersectionLeft) && ($intersectionTop > $intersectionBottom)) {
- $areaSupported += (($intersectionRight - $intersectionLeft) * ($intersectionTop - $intersectionBottom));
- if ($areaSupported > (($oppositeX - $originX) * ($oppositeZ - $originZ)) - $this->epsilon) {
- $support = true;
- break;
- }
- }
- }
- }
- }
- if (!$support) {
- continue;
- }
- // No conflicts at this point
- if (
- ($originZ < $minZ) ||
- (($originZ <= $minZ + $this->epsilon) && ($originY < $minY)) ||
- (($originZ <= $minZ + $this->epsilon) && ($originY <= $minY + $this->epsilon) && ($originX < $minX))
- ) {
- $minX = $originX;
- $minY = $originY;
- $minZ = $originZ;
- $candidatePosition = $j;
- $candidateRotation = $currentRotation;
- }
- }
- }
- // Portion of the function where the item gets added to the container
- addItemToContainerFinish :
- // If the candidate position is equal to -1 return
- if ($candidatePosition === -1) {
- return false;
- } else {
- $newItem = new Item();
- $newItem->item_type = $itemTypeIndex;
- if (\count($currentContainer->addition_points) - 1 >= $candidatePosition) {
- $newItem->origin_x = $currentContainer->addition_points[$candidatePosition]->origin_x;
- $newItem->origin_y = $currentContainer->addition_points[$candidatePosition]->origin_y;
- $newItem->origin_z = $currentContainer->addition_points[$candidatePosition]->origin_z;
- } else {
- $newItem->origin_x = 0;
- $newItem->origin_y = 0;
- $newItem->origin_z = 0;
- }
- $newItem->rotation = $candidateRotation;
- $newItem->mandatory = $currentItemType->mandatory;
- // Based on the resulted rotation set the opposite position
- switch ($candidateRotation) {
- case 1:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->width;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->height;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->length;
- break;
- case 2:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->length;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->height;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->width;
- break;
- case 3:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->width;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->length;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->height;
- break;
- case 4:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->height;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->length;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->width;
- break;
- case 5:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->height;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->width;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->length;
- break;
- case 6:
- $newItem->opposite_x = $newItem->origin_x + $currentItemType->length;
- $newItem->opposite_y = $newItem->origin_y + $currentItemType->width;
- $newItem->opposite_z = $newItem->origin_z + $currentItemType->height;
- break;
- }
- \array_push($solution->containers[$containerIndex]->items, $newItem);
- // Update the volume and weight of the container
- $currentContainer->volume_packed += $currentItemType->volume;
- $currentContainer->weight_packed += $currentItemType->weight;
- if ($addType === 2) {
- $currentContainer->repack_item_count[$itemTypeIndex] = $currentContainer->repack_item_count[$itemTypeIndex] - 1;
- }
- // Update the addition points
- for ($i = $candidatePosition; $i < $currentContainer->addition_point_count - 1; $i++) {
- $currentContainer->addition_points[$i] = clone($currentContainer->addition_points[$i + 1]);
- }
- $currentContainer->addition_point_count = $currentContainer->addition_point_count - 1;
- $containerItemCount = \count($currentContainer->items);
- // Add a new addition point to the container based on the last item
- if (($currentContainer->items[$containerItemCount - 1]->opposite_x < $currentContainer->width - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->origin_y < $currentContainer->height - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->origin_z < $currentContainer->length - $this->epsilon)) {
- $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
- $additionPoint = new ItemLocation();
- $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->opposite_x;
- $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->origin_y;
- $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->origin_z;
- if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
- \array_push($currentContainer->addition_points, $additionPoint);
- } else {
- $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
- }
- }
- // Add a new addition point to the container based on the last item
- if (($currentContainer->items[$containerItemCount - 1]->origin_x < $currentContainer->width - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->opposite_y < $currentContainer->height - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->origin_z < $currentContainer->length - $this->epsilon)) {
- $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
- $additionPoint = new ItemLocation();
- $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->origin_x;
- $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->opposite_y;
- $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->origin_z;
- if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
- \array_push($currentContainer->addition_points, $additionPoint);
- } else {
- $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
- }
- }
- // Add a new addition point to the container based on the last item
- if (($currentContainer->items[$containerItemCount - 1]->origin_x < $currentContainer->width - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->origin_y < $currentContainer->height - $this->epsilon) &&
- ($currentContainer->items[$containerItemCount - 1]->opposite_z < $currentContainer->length - $this->epsilon)) {
- $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
- $additionPoint = new ItemLocation();
- $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->origin_x;
- $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->origin_y;
- $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->opposite_z;
- if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
- \array_push($currentContainer->addition_points, $additionPoint);
- } else {
- $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
- }
- }
- // Update the profit
- if (\count($currentContainer->items) === 1) {
- $solution->net_profit += ($currentItemType->profit - $solution->containers[$containerIndex]->cost);
- } else {
- $solution->net_profit += $currentItemType->profit;
- }
- // Update the volume per container and the total volume
- $solution->total_volume += $currentItemType->volume;
- $solution->total_weight += $currentItemType->weight;
- // Update the unpacked items
- if ($addType === 1) {
- $solution->unpacked_item_count[$itemTypeIndex] = $solution->unpacked_item_count[$itemTypeIndex] - 1;
- }
- return true;
- }
- }
- /**
- * Returns the total amount of items need to be packed
- *
- * @return int
- */
- public function getTotalNumberOfItems(): int
- {
- $sum = 0;
- foreach ($this->itemTypeList as $key => $value) {
- if (isset($value->number_requested)) {
- $sum += $value->number_requested;
- }
- }
- return $sum;
- }
- /**
- * Returns the total amount of containers that can be used
- *
- * @return int
- */
- public function getTotalNumberOfMandatoryContainers(): int
- {
- $sum = 0;
- foreach ($this->containerTypeList as $key => $value) {
- if (isset($value->number_available) && isset($value->mandatory)) {
- if ($value->mandatory >= 0) {
- $sum += $value->number_available;
- }
- }
- }
- return $sum;
- }
- /**
- * Calculates the distance based on the amount of filled containers
- *
- * @param Solution $solution
- */
- public function calculateDispersion(Solution $solution): void
- {
- $solution->total_distance = 0;
- foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
- $container_count = 0;
- foreach ($solution->containers as $container) {
- $itemFlag = false;
- // Check if the container
- foreach ($container->items as $item) {
- if ($item->item_type === $itemTypeIndex) {
- $itemFlag = true;
- }
- }
- if ($itemFlag) {
- $container_count++;
- }
- }
- $solution->total_distance += $container_count * $container_count;
- }
- }
- /**
- * Chance to empty a container or remove some items from a container
- *
- * @param Solution $solution
- * @param int $containerIndex
- * @param float $percentTimeLeft
- */
- private function perturbSolution(Solution $solution, int $containerIndex, float $percentTimeLeft): void
- {
- $currentContainer = $solution->containers[$containerIndex];
- $containerEmptyingProbability = 0.05 + 0.15 * $percentTimeLeft;
- $itemRemovalProbability = 0.05 + 0.15 * $percentTimeLeft;
- if (\count($currentContainer->items) > 0) {
- // TODO This is just to make is consistent
- // if ((\mt_rand() / \mt_getrandmax()) < $containerEmptyingProbability) {
- if (true) {
- foreach ($currentContainer->items as $item) {
- $solution->unpacked_item_count[$item->item_type]++;
- $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
- }
- $solution->net_profit += $currentContainer->cost;
- $solution->total_volume -= $currentContainer->volume_packed;
- $solution->total_weight -= $currentContainer->weight_packed;
- $currentContainer->items = [];
- $currentContainer->volume_packed = 0;
- $currentContainer->weight_packed = 0;
- $currentContainer->addition_point_count = 1;
- $currentContainer->addition_points = [];
- } else {
- $repackFlag = false;
- if ((\mt_rand() / \mt_getrandmax()) < 0.3) {
- foreach ($currentContainer->items as $itemIndex => $item) {
- if ((!$solution->feasible && $item->mandatory === 0) ||
- ((\mt_rand() / \mt_getrandmax()) < $itemRemovalProbability)) {
- $solution->unpacked_item_count[$item->item_type]++;
- $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
- $item->item_type = -1;
- $repackFlag = true;
- }
- }
- } elseif ((\mt_rand() / \mt_getrandmax()) < 0.3) {
- $maxZ = 0;
- foreach ($currentContainer->items as $item) {
- if ($maxZ < $item->opposite_z) {
- $maxZ = $item->opposite_z;
- }
- }
- $maxZ = $maxZ * (0.1 + (0.5 * $percentTimeLeft * (\mt_rand() / \mt_getrandmax())));
- foreach ($currentContainer->items as $itemIndex => $item) {
- if ((!$solution->feasible && $item->mandatory === 0) || $item->opposite_z < $maxZ) {
- $solution->unpacked_item_count[$item->item_type]++;
- $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
- $item->item_type = -1;
- $repackFlag = true;
- }
- }
- } else {
- $maxZ = 0;
- foreach ($currentContainer->items as $item) {
- if ($maxZ < $item->opposite_z) {
- $maxZ = $item->opposite_z;
- }
- }
- $maxZ = $maxZ * (0.6 - (0.5 * $percentTimeLeft * (\mt_rand() / \mt_getrandmax())));
- foreach ($currentContainer->items as $itemIndex => $item) {
- if ((!$solution->feasible && $item->mandatory === 0) || $item->opposite_z > $maxZ) {
- $solution->unpacked_item_count[$item->item_type]++;
- $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
- $item->item_type = -1;
- $repackFlag = true;
- }
- }
- }
- if ($repackFlag) {
- foreach ($currentContainer->items as $item) {
- if ($item->item_type > -1) {
- $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
- }
- }
- $solution->net_profit += $currentContainer->cost;
- $solution->total_volume -= $currentContainer->volume_packed;
- $solution->total_weight -= $currentContainer->weight_packed;
- foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
- $currentContainer->repack_item_count[$itemTypeIndex] = 0;
- }
- foreach ($currentContainer->items as $itemIndex => $item) {
- if ($item->item_type > -1) {
- $currentContainer->repack_item_count[$item->item_type]++;
- }
- }
- $currentContainer->items = [];
- $currentContainer->volume_packed = 0;
- $currentContainer->weight_packed = 0;
- $currentContainer->addition_point_count = 1;
- $currentContainer->addition_points = [];
- foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
- $continue = true;
- while (($currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]] > 0) && $continue) {
- $continue = $this->addItemToContainer($solution, $containerIndex, $solution->item_type_order[$itemTypeIndex], 2);
- }
- $solution->unpacked_item_count[$solution->item_type_order[$itemTypeIndex]] += $currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]];
- $currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]] = 0;
- }
- }
- }
- }
- $itemTypeCount = \count($this->itemTypeList);
- for ($i = 0; $i < $itemTypeCount; $i++) {
- for ($j = 0; $j < 6; $j++) {
- // TODO This is just to make is consistent
- // $k = (int)(5 - $j + 1) * (\mt_rand() / \mt_getrandmax()) + $j;
- $k = (int)(5 - $j + 1) * 0.26302986092075 + $j;
- $swapLong = $solution->rotation_order[$i][$j];
- $solution->rotation_order[$i][$j] = $solution->rotation_order[$i][$k];
- $solution->rotation_order[$i][$k] = $swapLong;
- }
- }
- for ($i = 0; $i < $itemTypeCount; $i++) {
- // TODO This is just to make is consistent
- // $j = \rand($i, $itemTypeCount - 1);
- $j = $itemTypeCount - 1;
- $swapLong = $solution->item_type_order[$i];
- $solution->item_type_order[$i] = $solution->item_type_order[$j];
- $solution->item_type_order[$j] = $swapLong;
- }
- }
- /**
- * Calculates the total distance of a container based on the distance of items within the containers
- *
- * @param Solution $solution
- */
- public function calculateDistance(Solution $solution): void
- {
- $solution->total_distance = 0;
- $solution->total_x_moment = 0;
- $solution->total_yz_moment = 0;
- foreach ($solution->containers as $container) {
- $itemCount = \count($container->items);
- $maxZ = 0;
- for ($i = 0; $i < $itemCount; $i++) {
- for ($j = $i + 1; $j < $itemCount; $j++) {
- if ($container->items[$i]->item_type === $container->items[$j]->item_type) {
- $solution->total_distance += \abs($container->items[$i]->opposite_x + $container->items[$i]->origin_x - $container->items[$j]->opposite_x - $container->items[$j]->origin_x) + \abs($container->items[$i]->opposite_y + $container->items[$i]->origin_y - $container->items[$j]->opposite_y - $container->items[$j]->origin_y) + \abs($container->items[$i]->opposite_z + $container->items[$i]->origin_z - $container->items[$j]->opposite_z - $container->items[$j]->origin_z);
- }
- }
- if ($maxZ < $container->items[$i]->opposite_z) {
- $maxZ = $container->items[$i]->opposite_z;
- }
- $solution->total_distance += $container->items[$i]->opposite_z;
- }
- $solution->total_distance += ($itemCount * $itemCount * $maxZ);
- }
- }
- /**
- * Returns a random float between 0 and 1
- *
- * @return float
- */
- public function getRandom(): float
- {
- return \mt_rand() / \mt_getrandmax();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement