Advertisement
Guest User

Count uit loops gehaald

a guest
Sep 19th, 2019
292
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 67.50 KB | None | 0 0
  1. <?php declare(strict_types=1);
  2.  
  3.  
  4. namespace App\Services;
  5.  
  6. use App\Factories\ContainerTypeFactory;
  7. use App\Factories\ItemTypeFactory;
  8. use App\Models\CLP\Container;
  9. use App\Models\CLP\Item;
  10. use App\Models\CLP\ItemLocation;
  11. use App\Models\CLP\ItemType;
  12. use App\Models\CLP\Solution;
  13. use Exception;
  14. use function DeepCopy\deep_copy;
  15.  
  16. \set_time_limit(5000);
  17.  
  18.  
  19. /**
  20.  * Class LNSService
  21.  * @package App\Services
  22.  */
  23. class LNSService
  24. {
  25.     // epsilon is used to margin certain calculations
  26.     /**
  27.      * @var float
  28.      */
  29.     private $epsilon = 0.0001;
  30.     /**
  31.      * @var ItemType[]
  32.      */
  33.     public $itemTypeList;
  34.     /**
  35.      * @var
  36.      */
  37.     public $containerTypeList;
  38.     /**
  39.      * @var
  40.      */
  41.     public $solverOptions;
  42.     /**
  43.      * @var ContainerTypeFactory
  44.      */
  45.     private $containerTypeFactory;
  46.     /**
  47.      * @var ItemTypeFactory
  48.      */
  49.     private $itemTypeFactory;
  50.     /**
  51.      * @var
  52.      */
  53.     public $bestKnown;
  54.     /**
  55.      * @var SolutionService
  56.      */
  57.     private $solutionService;
  58.     /**
  59.      * @var
  60.      */
  61.     private $infeasibilityMessages;
  62.  
  63.  
  64.     // TODO Added for debugging and performance measuring
  65.     /**
  66.      * @var
  67.      */
  68.     private $measurements;
  69.     /**
  70.      * @var
  71.      */
  72.     private $nextMeasurement;
  73.     /**
  74.      * @var
  75.      */
  76.     private $startedAt;
  77.     /**
  78.      * @var
  79.      */
  80.     private $iterations;
  81.  
  82.     /**
  83.      * LNSService constructor.
  84.      */
  85.     public function __construct()
  86.     {
  87.         $this->containerTypeFactory = new ContainerTypeFactory();
  88.         $this->itemTypeFactory = new ItemTypeFactory();
  89.         $this->solutionService = new SolutionService();
  90.     }
  91.  
  92.     /**
  93.      * Main function that calculates the best solution via the LNS algorithm
  94.      * - is dependant on itemTypeList and containerTypeList;
  95.      *
  96.      * @return array
  97.      */
  98.     public function CLPSolver()
  99.     {
  100.         // TODO this is just for measurements
  101.         $this->startedAt = \microtime(true);
  102.         $this->nextMeasurement = 500;
  103.         $this->measurements = [];
  104.  
  105.         // Creates a new solution en initializes it.
  106.         $incumbent = $this->solutionService->initialize($this->containerTypeList, $this->itemTypeList);
  107.         $this->bestKnown = deep_copy($incumbent);
  108.  
  109.         // Sets start and end time to calculate how many iterations it can do
  110.         $startTime = \time();
  111.         $endTime = \time();
  112.  
  113.         // Check for any infeasibilities
  114.         if ($this->getInfeasibilities() > 0) {
  115.             // Throw an error
  116.             return $this->infeasibilityMessages;
  117.         }
  118.  
  119.         // Insertion sort of the containers with no random factor
  120.         $this->sortContainers($incumbent, 0);
  121.  
  122.         foreach ($incumbent->containers as $containerIndex => $container) {
  123.  
  124.             // Calculates the order of rotation for each container
  125.             $this->calculateRotationOrder($incumbent, $container);
  126.  
  127.             // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
  128.             $incumbent = $this->packUnpackedItems($incumbent, $containerIndex);
  129.  
  130.             // Calculates the distance based on the amount of filled containers
  131.             $this->calculateDispersion($incumbent);
  132.  
  133.  
  134.             // Check if current solution is better then the best known solution
  135.             $this->checkIfBestKnown($incumbent);
  136.         }
  137.  
  138.         // Iteration counter, has informative purposes
  139.         $iteration = 0;
  140.  
  141.         do {
  142.             // 50 percent chance to continue with the best known solution
  143.             // TODO This is just to make is consistent
  144. //            if ((\mt_rand() / \mt_getrandmax()) < 0.5) {
  145.             if (true) {
  146.                 $incumbent = deep_copy($this->bestKnown);
  147.             }
  148.  
  149.             // Chance to empty a container or remove some items from a container. does this for each container
  150.             foreach ($incumbent->containers as $containerIndex => $container) {
  151.                 $this->perturbSolution($incumbent, $containerIndex, (int)(1 - ($endTime - $startTime) / $this->solverOptions->CPU_time_limit));
  152.             }
  153.  
  154.             // Insertion sorts the containers inside the solution with a 20 percent chance to do it randomly
  155.             $this->sortContainers($incumbent, 0.2);
  156.  
  157.  
  158.             $containerCount = \count($incumbent->containers);
  159.             for ($i = 0; $i < $containerCount; $i++) {
  160.                 // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
  161.                 $incumbent = $this->packUnpackedItems($incumbent, $i);
  162.  
  163.                 // Calculates the distance based on the amount of filled containers
  164.                 $this->calculateDispersion($incumbent);
  165.  
  166.                 // Check if current solution is better then the best known solution
  167.                 $this->checkIfBestKnown($incumbent, true);
  168.             }
  169.  
  170.             $iteration++;
  171.             $endTime = \time();
  172.  
  173.             if ($endTime < $startTime - 0.01) {
  174.                 $this->solverOptions->CPU_time_limit -= (86400 - $startTime);
  175.                 $startTime = $endTime;
  176.             }
  177.         } while (($endTime - $startTime) < ($this->solverOptions->CPU_time_limit / 3));
  178.  
  179.         // Calculates the distance of the best known solution based on the measurements of items within the containers
  180.         $this->calculateDistance($this->bestKnown);
  181.  
  182.         // Calculate how many containers are filled inside the best known solution
  183.         $nonEmptyContainerCount = 0;
  184.         foreach ($this->bestKnown->containers as $container) {
  185.             if (\count($container->items) > 0) {
  186.                 $nonEmptyContainerCount++;
  187.             }
  188.         }
  189.  
  190.         foreach ($this->bestKnown->containers as $containerIndex => $container) {
  191.             // Set current solution to the best known if the container contains items
  192.             if (\count($container->items) > 0) {
  193.                 $incumbent = deep_copy($this->bestKnown);
  194.  
  195.                 $startTime = \time();
  196.                 $endTime = \time();
  197.  
  198.                 do {
  199.                     // 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
  200.                     $this->perturbSolution($incumbent, $containerIndex, 0.1 + 0.2 * (($endTime - $startTime) / (($this->solverOptions->CPU_time_limit * 0.666) / $nonEmptyContainerCount)));
  201.  
  202.                     // Add all the items from the unpacked item list to the containers within the solution and check if solution is feasible
  203.                     $incumbent = $this->packUnpackedItems($incumbent, $containerIndex);
  204.  
  205.                     // Calculates the distance of the best known solution based on the measurements of items within the containers
  206.                     $this->calculateDistance($incumbent);
  207.  
  208.                     // Check if current solution is better then the best known solution
  209.                     $this->checkIfBestKnown($incumbent, true);
  210.  
  211.                     $endTime = \time();
  212.  
  213.                     if ($endTime < $startTime - 0.01) {
  214.                         $this->solverOptions->CPU_time_limit -= (86400 - $startTime);
  215.                         $startTime = $endTime;
  216.                     }
  217.                 } while (($endTime - $startTime) < ($this->solverOptions->CPU_time_limit / 3));
  218.             }
  219.         }
  220.         // Organizes the best known based on the loading order defined inside the solver options
  221.         return $this->calculateLoadingOrder();
  222.     }
  223.  
  224.     /**
  225.      * Organizes the solution based on the loading order of the solver options
  226.      * 1: standard
  227.      * 2: lengthwise
  228.      * 3: diagonal
  229.      * 4: blockage free
  230.      *
  231.      * @return array
  232.      */
  233.     public function calculateLoadingOrder(): array
  234.     {
  235.         switch ($this->solverOptions->loading_order) {
  236.             // lengthwise
  237.             case 2:
  238.                 try {
  239.                     $this->organizeLengthwise();
  240.                 } catch (Exception $exception) {
  241.                     echo $exception;
  242.                 }
  243.                 break;
  244.             // Diagonal
  245.             case 3:
  246.                 try {
  247.                     $this->organizeDiagonal();
  248.                 } catch (Exception $exception) {
  249.                     echo $exception;
  250.                 }
  251.                 break;
  252.             // Blockage free
  253.             case 4:
  254.                 try {
  255.                     $this->organizeBlockageFree();
  256.                 } catch (Exception $exception) {
  257.                     echo $exception;
  258.                 }
  259.                 break;
  260.             // Standard
  261.             default:
  262.                 $this->organizeStandard();
  263.                 break;
  264.         }
  265.         // TODO Added iterations to response for debugging
  266. //        return $this->bestKnown;
  267.         return array($this->bestKnown, $this->iterations, $this->measurements);
  268.     }
  269.  
  270.     /**
  271.      * Organizes the solution lengthwise (Insertion sort)
  272.      *
  273.      * @throws Exception
  274.      */
  275.     public function organizeLengthwise(): void
  276.     {
  277.         foreach ($this->bestKnown->containers as $container) {
  278.             $itemCount = \count($container->items);
  279.             for ($j = 0; $j < $itemCount; $j++) {
  280.                 // Set the minimal position values to the container position values
  281.                 $minX = $container->width;
  282.                 $minY = $container->height;
  283.                 $minZ = $container->length;
  284.  
  285.                 $selectedItemIndex = -1;
  286.                 for ($k = $j; $k < $itemCount; $k++) {
  287.                     // Check if the length of the item is the smallest
  288.                     if (($container->items[$k]->origin_z < $minZ - $this->epsilon) ||
  289.                         (($container->items[$k]->origin_z < $minZ + $this->epsilon) && ($container->items[$k]->origin_y < $minY - $this->epsilon)) ||
  290.                         (($container->items[$k]->origin_z < $minZ + $this->epsilon) && ($container->items[$k]->origin_y < $minY + $this->epsilon) && ($container->items[$k]->origin_x < $minX - $this->epsilon))) {
  291.                         $selectedItemIndex = $k;
  292.  
  293.                         $minX = $container->items[$k]->origin_x;
  294.                         $minY = $container->items[$k]->origin_y;
  295.                         $minZ = $container->items[$k]->origin_z;
  296.                     }
  297.                 }
  298.  
  299.                 // Swap the smallest item to the front of the list
  300.                 if ($selectedItemIndex > -1) {
  301.                     $swapItem = $container->items[$selectedItemIndex];
  302.                     $container->items[$selectedItemIndex] = deep_copy($container->items[$j]);
  303.                     $container->items[$j] = $swapItem;
  304.                 } else {
  305.                     // Loading order could not be constructed
  306.                     throw new Exception('Loading order could not be constructed');
  307.                 }
  308.  
  309.                 $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
  310.             }
  311.         }
  312.     }
  313.  
  314.     /**
  315.      * Organizes the solution diagonal (Insertion sort)
  316.      *
  317.      * @throws Exception
  318.      */
  319.     public function organizeDiagonal(): void
  320.     {
  321.         foreach ($this->bestKnown->containers as $container) {
  322.             $itemCount = \count($container->items);
  323.             for ($j = 0; $j < $itemCount; $j++) {
  324.                 // Set the minimal position values to the container position values
  325.                 $minX = $container->width;
  326.                 $minY = $container->height;
  327.                 $minZ = $container->length;
  328.  
  329.                 $selectedItemIndex = -1;
  330.  
  331.                 for ($k = $j; $k < $itemCount; $k++) {
  332.                     // Check if the diagonal of an item is the smallest
  333.                     if (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ - $this->epsilon) ||
  334.                         (($container->items[$k]->origin_y + $container->items[$k]->origin_z < $minY + $minZ + $this->epsilon) && ($container->items[$k]->origin_z < $minZ - $this->epsilon)) ||
  335.                         (($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)) ||
  336.                         (($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))) {
  337.                         $selectedItemIndex = $k;
  338.  
  339.                         $minX = $container->items[$k]->origin_x;
  340.                         $minY = $container->items[$k]->origin_y;
  341.                         $minZ = $container->items[$k]->origin_z;
  342.                     }
  343.                 }
  344.  
  345.                 // Swap the smallest item to the front of the list
  346.                 if ($selectedItemIndex > -1) {
  347.                     $swapItem = $container->items[$selectedItemIndex];
  348.                     $container->items[$selectedItemIndex] = $container->items[$j];
  349.                     $container->items[$j] = $swapItem;
  350.                 } else {
  351.                     // Loading order could not be constructed
  352.                     throw new Exception('Loading order could not be constructed');
  353.                 }
  354.                 $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
  355.             }
  356.         }
  357.     }
  358.  
  359.     /**
  360.      * Organizes the solution blockage free
  361.      *
  362.      * @throws Exception
  363.      */
  364.     public function organizeBlockageFree(): void
  365.     {
  366.         foreach ($this->bestKnown->containers as $container) {
  367.             $lastSelectedItemType = 0;
  368.             $itemCount = \count($container->items);
  369.             for ($j = 0; $j < $itemCount; $j++) {
  370.                 $selectedItemIndex = -1;
  371.                 $maxX = -1;
  372.                 $maxY = -1;
  373.                 $maxZ = -1;
  374.  
  375.                 for ($k = $itemCount - 1 - $j; $k >= 0; $k--) {
  376.                     $blocked = false;
  377.                     for ($m = 0; $m < $itemCount - 1 - $j; $m++) {
  378.                         if ($container->items[$k]->opposite_y < $container->items[$m]->origin_y + $this->epsilon) {
  379.                             $blocked = $this->checkIntersection($container, $m, $k);
  380.                         }
  381.  
  382.                         if ($container->items[$k]->opposite_z < $container->items[$m]->origin_z + $this->epsilon) {
  383.                             $blocked = $this->checkIntersection($container, $m, $k);
  384.                         }
  385.                     }
  386.  
  387.                     if ($blocked) {
  388.                         if ($selectedItemIndex === -1) {
  389.                             $selectedItemIndex = $k;
  390.  
  391.                             $maxX = $container->items[$k]->opposite_x;
  392.                             $maxY = $container->items[$k]->opposite_y;
  393.                             $maxZ = $container->items[$k]->opposite_z;
  394.                         } else {
  395.                             if ($container->items[$selectedItemIndex]->item_type !== $lastSelectedItemType && $container->items[$k]->item_type === $lastSelectedItemType) {
  396.                                 $selectedItemIndex = $k;
  397.  
  398.                                 $maxX = $container->items[$k]->opposite_x;
  399.                                 $maxY = $container->items[$k]->opposite_y;
  400.                                 $maxZ = $container->items[$k]->opposite_z;
  401.                             } elseif ($container->items[$selectedItemIndex]->item_type !== $lastSelectedItemType && $container->items[$k]->item_type !== $lastSelectedItemType) {
  402.                                 if (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z > $maxY + $maxZ + $this->epsilon) ||
  403.                                     (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z > $maxZ + $this->epsilon)) ||
  404.                                     (($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)) ||
  405.                                     (($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))) {
  406.                                     $selectedItemIndex = $k;
  407.  
  408.                                     $maxX = $container->items[$k]->opposite_x;
  409.                                     $maxY = $container->items[$k]->opposite_y;
  410.                                     $maxZ = $container->items[$k]->opposite_z;
  411.                                 }
  412.                             } elseif ($container->items[$selectedItemIndex]->item_type === $lastSelectedItemType && $container->items[$k]->item_type === $lastSelectedItemType) {
  413.                                 if (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z > $maxY + $maxZ + $this->epsilon) ||
  414.                                     (($container->items[$k]->opposite_y + $container->items[$k]->opposite_z >= $maxY + $maxZ - $this->epsilon) && ($container->items[$k]->opposite_z > $maxZ + $this->epsilon)) ||
  415.                                     (($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)) ||
  416.                                     (($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))) {
  417.                                     $selectedItemIndex = $k;
  418.  
  419.                                     $maxX = $container->items[$k]->opposite_x;
  420.                                     $maxY = $container->items[$k]->opposite_y;
  421.                                     $maxZ = $container->items[$k]->opposite_z;
  422.                                 }
  423.                             }
  424.                         }
  425.                     }
  426.                 }
  427.                 if ($selectedItemIndex > -1) {
  428.                     $lastSelectedItemType = $container->items[$selectedItemIndex]->item_type;
  429.                     $swapItem = $container->items[$selectedItemIndex];
  430.                     $container->items[$selectedItemIndex] = $container->items[$itemCount + 1 - $j];
  431.                     $container->items[$itemCount + 1 - $j] = $swapItem;
  432.                 } else {
  433.                     // Loading order could not be constructed
  434.                     throw new Exception('Loading order could not be constructed');
  435.                 }
  436.  
  437.                 $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
  438.             }
  439.         }
  440.     }
  441.  
  442.     /**
  443.      * The standard way of organizing the items in the container
  444.      * The algorithm will not take the blockage, diagonal or lengthwise options into consideration
  445.      */
  446.     public function organizeStandard(): void
  447.     {
  448.         foreach ($this->bestKnown->containers as $container) {
  449.             $itemCount = \count($container->items);
  450.             for ($j = 0; $j < $itemCount; $j++) {
  451.                 for ($k = 0; $k < $itemCount - $j; $k++) {
  452.                     $blocked = false;
  453.                     $blockingItem = 0;
  454.                     for ($m = $k - 1; $m > 0; $m--) {
  455.                         // Check if previous item is able to intersect another item based on the y position (height)
  456.                         if ($container->items[$k]->opposite_y < $container->items[$m]->origin_y + $this->epsilon) {
  457.                             // Check if the 2 items are intersecting each other
  458.                             $blocked = $this->checkIntersection($container, $m, $k);
  459.  
  460.                             if ($blocked) {
  461.                                 $blockingItem = $m;
  462.                             }
  463.                         }
  464.  
  465.                         // Check if previous item is able to intersect another item based on the z position (length)
  466.                         if ($container->items[$k]->opposite_z < $container->items[$m]->origin_z + $this->epsilon) {
  467.                             // Check if the 2 items are intersecting each other
  468.                             $blocked = $this->checkIntersection($container, $m, $k);
  469.  
  470.                             if ($blocked) {
  471.                                 $blockingItem = $m;
  472.                             }
  473.                         }
  474.                     }
  475.  
  476.                     // Swap blocked item
  477.                     if ($blocked) {
  478.                         $swapItem = $container->items[$k];
  479.  
  480.                         for ($m = $k; $m < $blockingItem + 1; $m--) {
  481.                             $container->items[$m] = deep_copy($container->items[$m - 1]);
  482.                         }
  483.  
  484.                         $container->items[$blockingItem] = $swapItem;
  485.                     }
  486.                 }
  487.                 $container->items[$j]->type_id = $this->itemTypeList[$container->items[$j]->item_type]->type_id;
  488.             }
  489.         }
  490.     }
  491.  
  492.     /**
  493.      * Checks if 2 items are intersecting each other
  494.      *
  495.      * @param Container $container
  496.      * @param int $m
  497.      * @param int $k
  498.      * @return bool
  499.      */
  500.     public function checkIntersection(Container $container, int $m, int $k): bool
  501.     {
  502.         $blocked = false;
  503.         $intersectionRight = $container->items[$m]->opposite_x;
  504.         if ($intersectionRight > $container->items[$k]->opposite_x) {
  505.             $intersectionRight = $container->items[$k]->opposite_x;
  506.         }
  507.  
  508.         $intersectionLeft = $container->items[$m]->origin_x;
  509.         if ($intersectionLeft < $container->items[$k]->origin_x) {
  510.             $intersectionLeft = $container->items[$k]->origin_x;
  511.         }
  512.  
  513.         $intersectionTop = $container->items[$m]->opposite_z;
  514.         if ($intersectionTop > $container->items[$k]->opposite_z) {
  515.             $intersectionTop = $container->items[$k]->opposite_z;
  516.         }
  517.  
  518.         $intersectionBottom = $container->items[$m]->origin_z;
  519.         if ($intersectionBottom < $container->items[$k]->origin_z) {
  520.             $intersectionBottom = $container->items[$k]->origin_z;
  521.         }
  522.  
  523.         if (($intersectionRight > $intersectionLeft + $this->epsilon) && ($intersectionTop > $intersectionBottom + $this->epsilon)) {
  524.             $blocked = true;
  525.         }
  526.  
  527.         return $blocked;
  528.     }
  529.  
  530.     /**
  531.      * Packs items based on the unpacked item count within the solution.
  532.      * - Fills the containers based on the position within the container array inside the solution.
  533.      * - continue will be false if the item doesn't fit inside the container
  534.      *
  535.      * Checks if the solution is feasible.
  536.      *
  537.      * @param Solution $solution
  538.      * @param int $containerIndex
  539.      * @return Solution
  540.      */
  541.     public function packUnpackedItems(Solution $solution, int $containerIndex): Solution
  542.     {
  543.         $itemTypeCount = \count($this->itemTypeList);
  544.  
  545.         // Keeps adding items to the container if possible and needed
  546.         for ($j = 0; $j < $itemTypeCount; $j++) {
  547.             $continue = true;
  548.             while (($solution->unpacked_item_count[$solution->item_type_order[$j]] > 0) && $continue) {
  549.                 $continue = $this->addItemToContainer($solution, $containerIndex, $solution->item_type_order[$j], 1);
  550.             }
  551.         }
  552.  
  553.         // Checks if all the mandatory items are packed inside the containers of the solution
  554.         $solution->feasible = true;
  555.         for ($j = 0; $j < $itemTypeCount; $j++) {
  556.             if ($solution->unpacked_item_count[$j] > 0 && $this->itemTypeList[$j]->mandatory === 1) {
  557.                 $solution->feasible = false;
  558.             }
  559.         }
  560.  
  561.         return $solution;
  562.     }
  563.  
  564.     /**
  565.      * Check if the solution given is better than the best known solution
  566.      * - Able to pass distance check boolean, if distance needs to be checked
  567.      *
  568.      * @param Solution $solution
  569.      * @param bool $distanceCheck
  570.      */
  571.     public function checkIfBestKnown(Solution $solution, bool $distanceCheck = false): void
  572.     {
  573.         // Checks if the solution has a better feasibility, volume, net profit and total volume
  574.         if (($solution->feasible && !$this->bestKnown->feasible) ||
  575.             (!$solution->feasible && !$this->bestKnown->feasible && $solution->total_volume > $this->bestKnown->total_volume + $this->epsilon) ||
  576.             ($solution->feasible && $this->bestKnown->feasible && $solution->net_profit > $this->bestKnown->net_profit + $this->epsilon) ||
  577.             ($solution->feasible && $this->bestKnown->feasible && ($solution->net_profit > $this->bestKnown->net_profit - $this->epsilon) && ($solution->total_volume < $this->bestKnown->total_volume - $this->epsilon))) {
  578.             $this->bestKnown = deep_copy($solution);
  579.             // Checks if the solution has a better distance, total x moment and total yz moment
  580.         } elseif ($distanceCheck &&
  581.             ($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)) ||
  582.             ($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)) ||
  583.             ($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))) {
  584.             $this->bestKnown = deep_copy($solution);
  585.         }
  586.         $this->iterations++;
  587.  
  588.         if ($this->iterations === $this->nextMeasurement) {
  589.             array_push($this->measurements, array("iterations" => $this->iterations, "time" => \microtime(true) - $this->startedAt));
  590.             $this->nextMeasurement += 500;
  591.         }
  592.     }
  593.  
  594.     /**
  595.      * Calculates the rotation order based on sort criterion calculations
  596.      *
  597.      * @param Solution $solution
  598.      * @param Container $container
  599.      */
  600.     public function calculateRotationOrder(Solution $solution, Container $container): void
  601.     {
  602.         foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
  603.             $sortCriterion = 0;
  604.             $selectedRotation = 0;
  605.  
  606.             $calculatedSortCriterion = ((int)($container->width / $itemType->width) * $itemType->width) * ((int)($container->height / $itemType->height) * $itemType->height);
  607.             if ($sortCriterion < $calculatedSortCriterion) {
  608.                 $sortCriterion = $calculatedSortCriterion;
  609.                 $selectedRotation = 1;
  610.             }
  611.             $calculatedSortCriterion = ((int)($container->width / $itemType->length) * $itemType->length) * ((int)($container->height / $itemType->height) * $itemType->height);
  612.             if ($sortCriterion < $calculatedSortCriterion) {
  613.                 $sortCriterion = $calculatedSortCriterion;
  614.                 $selectedRotation = 2;
  615.             }
  616.             $calculatedSortCriterion = ((int)($container->width / $itemType->width) * $itemType->width) * ((int)($container->height / $itemType->length) * $itemType->length);
  617.             if ($itemType->xy_rotatable && $sortCriterion < $calculatedSortCriterion) {
  618.                 $sortCriterion = $calculatedSortCriterion;
  619.                 $selectedRotation = 3;
  620.             }
  621.             $calculatedSortCriterion = ((int)($container->width / $itemType->height) * $itemType->height) * ((int)($container->height / $itemType->length) * $itemType->length);
  622.             if ($itemType->xy_rotatable && $sortCriterion < $calculatedSortCriterion) {
  623.                 $sortCriterion = $calculatedSortCriterion;
  624.                 $selectedRotation = 4;
  625.             }
  626.             $calculatedSortCriterion = ((int)($container->width / $itemType->height) * $itemType->height) * ((int)($container->height / $itemType->width) * $itemType->width);
  627.             if ($itemType->yz_rotatable && $sortCriterion < $calculatedSortCriterion) {
  628.                 $sortCriterion = $calculatedSortCriterion;
  629.                 $selectedRotation = 5;
  630.             }
  631.             $calculatedSortCriterion = ((int)($container->width / $itemType->length) * $itemType->length) * ((int)($container->height / $itemType->width) * $itemType->width);
  632.             if ($itemType->yz_rotatable && $sortCriterion < $calculatedSortCriterion) {
  633.                 $selectedRotation = 6;
  634.             }
  635.  
  636.             if ($selectedRotation === 0) {
  637.                 $selectedRotation = 1;
  638.             }
  639.  
  640.             $solution->rotation_order[$itemTypeIndex][0] = $selectedRotation;
  641.             $solution->rotation_order[$itemTypeIndex][$selectedRotation - 1] = 1;
  642.         }
  643.     }
  644.  
  645.     /**
  646.      * Set options for the solver (CPUTimeLimit and itemSortCriterion)
  647.      *
  648.      * @param array $options
  649.      */
  650.     public function setSolverOptions(array $options): void
  651.     {
  652.         $this->solverOptions = (object)$options;
  653.     }
  654.  
  655.     /**
  656.      * Fills the item type array and calculates values of item type.
  657.      *
  658.      * @param array $itemTypes
  659.      */
  660.     public function setItemTypeData(array $itemTypes): void
  661.     {
  662.         $this->itemTypeList = [];
  663.         for ($i = 0; $i < \count($itemTypes); $i++) {
  664.             $itemType = $this->itemTypeFactory->createSolutionType($itemTypes[$i], $this->solverOptions);
  665.             \array_push($this->itemTypeList, $itemType);
  666.         }
  667.         $this->itemTypeList = $this->sortItems($this->itemTypeList);
  668.     }
  669.  
  670.     /**
  671.      * Fills the container type array and calculates the volume capacity
  672.      *
  673.      * @param array $containerTypes
  674.      */
  675.     public function setContainerData(array $containerTypes): void
  676.     {
  677.         $this->containerTypeList = [];
  678.         $containerTypesCount = \count($containerTypes);
  679.         for ($i = 0; $i < $containerTypesCount; $i++) {
  680.             $containerType = $this->containerTypeFactory->createSolutionType($containerTypes[$i]);
  681.             \array_push($this->containerTypeList, $containerType);
  682.         }
  683.     }
  684.  
  685.     /**
  686.      * Sorts the item type list based on sort criterion and profit/volume margin
  687.      *
  688.      * @param array $itemTypes
  689.      * @return array
  690.      */
  691.     public function sortItems(array $itemTypes)
  692.     {
  693.         $itemTypesCount = \count($itemTypes);
  694.         if ($itemTypesCount > 0) {
  695.             for ($i = 0; $i < $itemTypesCount; $i++) {
  696.                 for ($j = $itemTypesCount - 1; $j >= 1; $j--) {
  697.                     $currentItemType = $itemTypes[$j];
  698.                     $previousItemType = $itemTypes[$j - 1];
  699.                     if (
  700.                         ($currentItemType->mandatory > $previousItemType->mandatory) ||
  701.                         (($currentItemType->mandatory === 1) && ($previousItemType->mandatory === 1) && ($currentItemType->sort_criterion > $previousItemType->sort_criterion)) ||
  702.                         (($currentItemType->mandatory === 0) && ($previousItemType->mandatory === 0) && (($currentItemType->profit / $currentItemType->volume) > ($previousItemType->profit / $previousItemType->volume)))
  703.                     ) {
  704.                         $itemTypes[$j] = $previousItemType;
  705.                         $itemTypes[$j - 1] = $currentItemType;
  706.                     }
  707.                 }
  708.             }
  709.         }
  710.         return $itemTypes;
  711.     }
  712.  
  713.     /**
  714.      * Insertion sort of the containers within a solution
  715.      * - Random reorder probability defines the chance to reorder randomly (0.2 === 20%)
  716.      *
  717.      * @param Solution $solution
  718.      * @param float $randomReorderProbability
  719.      */
  720.     public function sortContainers(Solution &$solution, float $randomReorderProbability): void
  721.     {
  722.         $containerCount = \count($solution->containers);
  723.  
  724.         // Check if random doesn't fall under the random reorder probability
  725.         // TODO This is just to make is consistent
  726. //        if ($this->getRandom() < 1 - $randomReorderProbability) {
  727.         if (true) {
  728.             for ($i = 0; $i < $containerCount; $i++) {
  729.                 $candidateIndex = $i;
  730.                 $maxMandatory = $solution->containers[$i]->mandatory;
  731.                 $maxVolumePacked = $solution->containers[$i]->volume_packed;
  732.                 $minRatio = $solution->containers[$i]->cost / $solution->containers[$i]->volume_capacity;
  733.  
  734.                 for ($j = $i + 1; $j < $containerCount; $j++) {
  735.                     $currentContainer = $solution->containers[$j];
  736.                     // check which container has the greatest volume packed and ratio
  737.  
  738.                     if (
  739.                         ($currentContainer->mandatory > $maxMandatory) ||
  740.                         (($currentContainer->mandatory === $maxMandatory) && ($currentContainer->volume_packed > $maxVolumePacked + $this->epsilon)) ||
  741.                         (($currentContainer->mandatory === 0) && ($maxMandatory === 0) && $currentContainer->volume_packed > $maxVolumePacked - $this->epsilon) && (($currentContainer->cost / $currentContainer->volume_capacity) < $minRatio)
  742.                     ) {
  743.                         $candidateIndex = $j;
  744.                         $maxMandatory = $currentContainer->mandatory;
  745.                         $maxVolumePacked = $currentContainer->volume_packed;
  746.                         $minRatio = $currentContainer->cost / $currentContainer->volume_capacity;
  747.                     }
  748.                 }
  749.  
  750.                 // Swap the containers
  751.                 if ($candidateIndex !== $i) {
  752.                     $swapContainer = $solution->containers[$candidateIndex];
  753.                     $solution->containers[$candidateIndex] = $solution->containers[$i];
  754.                     $solution->containers[$i] = $swapContainer;
  755.                 }
  756.             }
  757.         } else {
  758.             // Reorder the containers based on a random index
  759.             for ($i = 0; $i < $containerCount; $i++) {
  760.                 $candidateIndex = \rand($i, $containerCount - 1);
  761.  
  762.                 if ($candidateIndex !== $i) {
  763.                     $swapContainer = $solution->containers[$candidateIndex];
  764.                     $solution->containers[$candidateIndex] = $solution->containers[$i];
  765.                     $solution->containers[$i] = $swapContainer;
  766.                 }
  767.             }
  768.         }
  769.     }
  770.  
  771.     /**
  772.      * Checks for any infeasibilities
  773.      * - Checks if the total volume of items fits into the total volume of containers
  774.      * - Checks if the total weight of items fits into the total weight of containers
  775.      * - Checks if each item fits into the container based on its height, length and width
  776.      *
  777.      * @return int $count
  778.      */
  779.     public function getInfeasibilities(): int
  780.     {
  781.         $count = 0;
  782.  
  783.         $volumeCapacityRequired = 0;
  784.         $volumeCapacityAvailable = 0;
  785.  
  786.         $weightCapacityRequired = 0;
  787.         $weightCapacityAvailable = 0;
  788.  
  789.         $maxWidth = 0;
  790.         $maxHeight = 0;
  791.         $maxLength = 0;
  792.  
  793.         // Calculates the volume and weight needed
  794.         foreach ($this->itemTypeList as $itemType) {
  795.             if ($itemType->mandatory === 1) {
  796.                 $volumeCapacityRequired += ($itemType->volume * $itemType->number_requested);
  797.                 $weightCapacityRequired += ($itemType->weight * $itemType->number_requested);
  798.             }
  799.         }
  800.  
  801.         // Calculates the volume and weight available and calculates the max width, length and height
  802.         foreach ($this->containerTypeList as $containerType) {
  803.             if ($containerType->mandatory >= 0) {
  804.                 $volumeCapacityAvailable += ($containerType->volume_capacity * $containerType->number_available);
  805.                 $weightCapacityAvailable += ($containerType->weight_capacity * $containerType->number_available);
  806.  
  807.                 if ($containerType->width > $maxWidth) {
  808.                     $maxWidth = $containerType->width;
  809.                 }
  810.                 if ($containerType->height > $maxHeight) {
  811.                     $maxHeight = $containerType->height;
  812.                 }
  813.                 if ($containerType->length > $maxLength) {
  814.                     $maxLength = $containerType->length;
  815.                 }
  816.             }
  817.         }
  818.  
  819.         // Checks if volume needed exceeds volume available
  820.         if ($volumeCapacityRequired > $volumeCapacityAvailable + $this->epsilon) {
  821.             $count++;
  822.             $this->infeasibilityMessages[] = 'The amount of available volume is not enough to pack the mandatory items.';
  823.         }
  824.  
  825.         // Checks if weight needed exceeds weight available
  826.         if ($weightCapacityRequired > $weightCapacityAvailable + $this->epsilon) {
  827.             $count++;
  828.             $this->infeasibilityMessages[] = 'The amount of available weight capacity is not enough to pack the mandatory items.';
  829.         }
  830.  
  831.         // Checks if each item fits into the container
  832.         foreach ($this->itemTypeList as $itemType) {
  833.             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))) {
  834.                 $count++;
  835.                 $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too large to fit into any container.';
  836.             }
  837.             if ($itemType->mandatory === 1 && ($itemType->width > $maxWidth + $this->epsilon) && ($itemType->width > $maxHeight + $this->epsilon) && ($itemType->width > $maxLength + $this->epsilon)) {
  838.                 $count++;
  839.                 $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too wide to fit into any container.';
  840.             }
  841.             if ($itemType->mandatory === 1 && ($itemType->height > $maxWidth + $this->epsilon) && ($itemType->height > $maxHeight + $this->epsilon) && ($itemType->height > $maxLength + $this->epsilon)) {
  842.                 $count++;
  843.                 $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too tall to fit into any container.';
  844.             }
  845.             if ($itemType->mandatory === 1 && ($itemType->length > $maxWidth + $this->epsilon) && ($itemType->length > $maxHeight + $this->epsilon) && ($itemType->length > $maxLength + $this->epsilon)) {
  846.                 $count++;
  847.                 $this->infeasibilityMessages[] = 'Item type ' & $itemType->type_id & ' is too long to fit into any container.';
  848.             }
  849.         }
  850.  
  851.         return $count;
  852.     }
  853.  
  854.     /**
  855.      * Adds an item to the container within a solution
  856.      *
  857.      * @param Solution $solution
  858.      * @param int $containerIndex
  859.      * @param int $itemTypeIndex
  860.      * @param int $addType
  861.      * @return bool
  862.      */
  863.     public function addItemToContainer(Solution $solution, int $containerIndex, int $itemTypeIndex, int $addType): bool
  864.     {
  865.         $currentContainer = $solution->containers[$containerIndex];
  866.         $currentItemType = $this->itemTypeList[$itemTypeIndex];
  867.         $minX = $currentContainer->width + 1;
  868.         $minY = $currentContainer->height + 1;
  869.         $minZ = $currentContainer->length + 1;
  870.  
  871.         $candidatePosition = -1;
  872.  
  873.         $containerItemCount = \count($currentContainer->items);
  874.  
  875.         // Checks if the item is able to fit into the current container based on volume
  876.         if (($currentContainer->volume_packed + $currentItemType->volume) > $currentContainer->volume_capacity) {
  877.             goto addItemToContainerFinish;
  878.         }
  879.  
  880.         // Checks if the item is able to fit into the current container based on weight
  881.         if (($currentContainer->weight_packed + $currentItemType->weight) > $currentContainer->weight_capacity) {
  882.             goto addItemToContainerFinish;
  883.         }
  884.  
  885.         // Checks each rotation, based on the rotation order of the solution
  886.         for ($i = 0; $i < 6; $i++) {
  887.             $rotationIndex = $i;
  888.             if ($candidatePosition !== -1) {
  889.                 goto addItemToContainerFinish;
  890.             }
  891.  
  892.             $currentRotation = $solution->rotation_order[$itemTypeIndex][$rotationIndex];
  893.  
  894.             // Forbidden rotations
  895.             if ((($currentRotation === 3) || ($currentRotation === 4)) && ($currentItemType->xy_rotatable === false)) {
  896.                 continue;
  897.             }
  898.  
  899.             if ((($currentRotation === 5) || ($currentRotation === 6)) && ($currentItemType->yz_rotatable === false)) {
  900.                 continue;
  901.             }
  902.  
  903.             // Symmetry breaking
  904.             if (($currentRotation === 2) && (\abs($currentItemType->width - $currentItemType->length) < $this->epsilon)) {
  905.                 continue;
  906.             }
  907.  
  908.             if (($currentRotation === 4) && (\abs($currentItemType->width - $currentItemType->height) < $this->epsilon)) {
  909.                 continue;
  910.             }
  911.  
  912.             if (($currentRotation === 6) && (\abs($currentItemType->height - $currentItemType->length) < $this->epsilon)) {
  913.                 continue;
  914.             }
  915.  
  916.  
  917.             // TODO if everything works, maybe this can be improved as well
  918.             // Loops through each addition point to find the next gap to put a new item
  919.             for ($j = 0; $j < $currentContainer->addition_point_count; $j++) {
  920.                 if (\count($currentContainer->addition_points) - 1 >= $j) {
  921.                     $additionPoint = $currentContainer->addition_points[$j];
  922.                     $originX = $additionPoint->origin_x;
  923.                     $originY = $additionPoint->origin_y;
  924.                     $originZ = $additionPoint->origin_z;
  925.                 } else {
  926.                     $originX = 0;
  927.                     $originY = 0;
  928.                     $originZ = 0;
  929.                 }
  930.  
  931.                 // set opposite positions based on the rotation which is calculated above
  932.                 switch ($currentRotation) {
  933.                     case 1:
  934.                         $oppositeX = $originX + $currentItemType->width;
  935.                         $oppositeY = $originY + $currentItemType->height;
  936.                         $oppositeZ = $originZ + $currentItemType->length;
  937.                         break;
  938.                     case 2:
  939.                         $oppositeX = $originX + $currentItemType->length;
  940.                         $oppositeY = $originY + $currentItemType->height;
  941.                         $oppositeZ = $originZ + $currentItemType->width;
  942.                         break;
  943.                     case 3:
  944.                         $oppositeX = $originX + $currentItemType->width;
  945.                         $oppositeY = $originY + $currentItemType->length;
  946.                         $oppositeZ = $originZ + $currentItemType->height;
  947.                         break;
  948.                     case 4:
  949.                         $oppositeX = $originX + $currentItemType->height;
  950.                         $oppositeY = $originY + $currentItemType->length;
  951.                         $oppositeZ = $originZ + $currentItemType->width;
  952.                         break;
  953.                     case 5:
  954.                         $oppositeX = $originX + $currentItemType->height;
  955.                         $oppositeY = $originY + $currentItemType->width;
  956.                         $oppositeZ = $originZ + $currentItemType->length;
  957.                         break;
  958.                     case 6:
  959.                         $oppositeX = $originX + $currentItemType->length;
  960.                         $oppositeY = $originY + $currentItemType->width;
  961.                         $oppositeZ = $originZ + $currentItemType->height;
  962.                         break;
  963.                     default:
  964.                         $oppositeX = 0;
  965.                         $oppositeY = 0;
  966.                         $oppositeZ = 0;
  967.                         break;
  968.                 }
  969.  
  970.                 // Check the feasibility of all four corners, w.r.t. to the other items
  971.                 if (($oppositeX > $currentContainer->width + $this->epsilon) || ($oppositeY > $currentContainer->height + $this->epsilon) || ($oppositeZ > $currentContainer->length + $this->epsilon)) {
  972.                     continue;
  973.                 }
  974.  
  975.                 // Check if there is conflict with other items within the container
  976.                 foreach ($currentContainer->items as $item) {
  977.                     if (
  978.                         ($oppositeX < $item->origin_x + $this->epsilon) ||
  979.                         ($item->opposite_x < $originX + $this->epsilon) ||
  980.                         ($oppositeY < $item->origin_y + $this->epsilon) ||
  981.                         ($item->opposite_y < $originY + $this->epsilon) ||
  982.                         ($oppositeZ < $item->origin_z + $this->epsilon) ||
  983.                         ($item->opposite_z < $originZ + $this->epsilon)
  984.                     ) {
  985.                         // No conflict
  986.                     } else {
  987.                         // Conflict
  988.                         continue 2 ;
  989.                     }
  990.                 }
  991.  
  992.                 // Support
  993.                 if ($originY < $this->epsilon) {
  994.                     $support = true;
  995.                 } else {
  996.                     $areaSupported = 0;
  997.                     $support = false;
  998.                     for ($k = $containerItemCount - 1; $k >= 0; $k--) {
  999.                         $item = $currentContainer->items[$k];
  1000.                         if (\abs($originY - $item->opposite_y) < $this->epsilon) {
  1001.                             // Check for intersection
  1002.                             $intersectionRight = $oppositeX;
  1003.                             if ($intersectionRight > $item->opposite_x) {
  1004.                                 $intersectionRight = $item->opposite_x;
  1005.                             }
  1006.  
  1007.                             $intersectionLeft = $originX;
  1008.                             if ($intersectionLeft < $item->origin_x) {
  1009.                                 $intersectionLeft = $item->origin_x;
  1010.                             }
  1011.  
  1012.                             $intersectionTop = $oppositeZ;
  1013.                             if ($intersectionTop > $item->opposite_z) {
  1014.                                 $intersectionTop = $item->opposite_z;
  1015.                             }
  1016.  
  1017.                             $intersectionBottom = $originZ;
  1018.                             if ($intersectionBottom < $item->origin_z) {
  1019.                                 $intersectionBottom = $item->origin_z;
  1020.                             }
  1021.  
  1022.                             if (($intersectionRight > $intersectionLeft) && ($intersectionTop > $intersectionBottom)) {
  1023.                                 $areaSupported += (($intersectionRight - $intersectionLeft) * ($intersectionTop - $intersectionBottom));
  1024.                                 if ($areaSupported > (($oppositeX - $originX) * ($oppositeZ - $originZ)) - $this->epsilon) {
  1025.                                     $support = true;
  1026.                                     break;
  1027.                                 }
  1028.                             }
  1029.                         }
  1030.                     }
  1031.                 }
  1032.  
  1033.                 if (!$support) {
  1034.                     continue;
  1035.                 }
  1036.  
  1037.                 // No conflicts at this point
  1038.                 if (
  1039.                     ($originZ < $minZ) ||
  1040.                     (($originZ <= $minZ + $this->epsilon) && ($originY < $minY)) ||
  1041.                     (($originZ <= $minZ + $this->epsilon) && ($originY <= $minY + $this->epsilon) && ($originX < $minX))
  1042.                 ) {
  1043.                     $minX = $originX;
  1044.                     $minY = $originY;
  1045.                     $minZ = $originZ;
  1046.                     $candidatePosition = $j;
  1047.                     $candidateRotation = $currentRotation;
  1048.                 }
  1049.             }
  1050.         }
  1051.  
  1052.         // Portion of the function where the item gets added to the container
  1053.         addItemToContainerFinish :
  1054.  
  1055.         // If the candidate position is equal to -1 return
  1056.         if ($candidatePosition === -1) {
  1057.             return false;
  1058.         } else {
  1059.             $newItem = new Item();
  1060.             $newItem->item_type = $itemTypeIndex;
  1061.             if (\count($currentContainer->addition_points) - 1 >= $candidatePosition) {
  1062.                 $newItem->origin_x = $currentContainer->addition_points[$candidatePosition]->origin_x;
  1063.                 $newItem->origin_y = $currentContainer->addition_points[$candidatePosition]->origin_y;
  1064.                 $newItem->origin_z = $currentContainer->addition_points[$candidatePosition]->origin_z;
  1065.             } else {
  1066.                 $newItem->origin_x = 0;
  1067.                 $newItem->origin_y = 0;
  1068.                 $newItem->origin_z = 0;
  1069.             }
  1070.             $newItem->rotation = $candidateRotation;
  1071.             $newItem->mandatory = $currentItemType->mandatory;
  1072.  
  1073.             // Based on the resulted rotation set the opposite position
  1074.             switch ($candidateRotation) {
  1075.                 case 1:
  1076.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->width;
  1077.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->height;
  1078.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->length;
  1079.                     break;
  1080.                 case 2:
  1081.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->length;
  1082.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->height;
  1083.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->width;
  1084.                     break;
  1085.                 case 3:
  1086.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->width;
  1087.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->length;
  1088.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->height;
  1089.                     break;
  1090.                 case 4:
  1091.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->height;
  1092.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->length;
  1093.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->width;
  1094.                     break;
  1095.                 case 5:
  1096.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->height;
  1097.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->width;
  1098.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->length;
  1099.                     break;
  1100.                 case 6:
  1101.                     $newItem->opposite_x = $newItem->origin_x + $currentItemType->length;
  1102.                     $newItem->opposite_y = $newItem->origin_y + $currentItemType->width;
  1103.                     $newItem->opposite_z = $newItem->origin_z + $currentItemType->height;
  1104.                     break;
  1105.             }
  1106.  
  1107.             \array_push($solution->containers[$containerIndex]->items, $newItem);
  1108.  
  1109.             // Update the volume and weight of the container
  1110.             $currentContainer->volume_packed += $currentItemType->volume;
  1111.             $currentContainer->weight_packed += $currentItemType->weight;
  1112.  
  1113.             if ($addType === 2) {
  1114.                 $currentContainer->repack_item_count[$itemTypeIndex] = $currentContainer->repack_item_count[$itemTypeIndex] - 1;
  1115.             }
  1116.  
  1117.             // Update the addition points
  1118.             for ($i = $candidatePosition; $i < $currentContainer->addition_point_count - 1; $i++) {
  1119.                 $currentContainer->addition_points[$i] = clone($currentContainer->addition_points[$i + 1]);
  1120.             }
  1121.  
  1122.             $currentContainer->addition_point_count = $currentContainer->addition_point_count - 1;
  1123.  
  1124.             $containerItemCount = \count($currentContainer->items);
  1125.             // Add a new addition point to the container based on the last item
  1126.             if (($currentContainer->items[$containerItemCount - 1]->opposite_x < $currentContainer->width - $this->epsilon) &&
  1127.                 ($currentContainer->items[$containerItemCount - 1]->origin_y < $currentContainer->height - $this->epsilon) &&
  1128.                 ($currentContainer->items[$containerItemCount - 1]->origin_z < $currentContainer->length - $this->epsilon)) {
  1129.                 $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
  1130.                 $additionPoint = new ItemLocation();
  1131.                 $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->opposite_x;
  1132.                 $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->origin_y;
  1133.                 $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->origin_z;
  1134.                 if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
  1135.                     \array_push($currentContainer->addition_points, $additionPoint);
  1136.                 } else {
  1137.                     $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
  1138.                 }
  1139.             }
  1140.  
  1141.             // Add a new addition point to the container based on the last item
  1142.             if (($currentContainer->items[$containerItemCount - 1]->origin_x < $currentContainer->width - $this->epsilon) &&
  1143.                 ($currentContainer->items[$containerItemCount - 1]->opposite_y < $currentContainer->height - $this->epsilon) &&
  1144.                 ($currentContainer->items[$containerItemCount - 1]->origin_z < $currentContainer->length - $this->epsilon)) {
  1145.                 $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
  1146.                 $additionPoint = new ItemLocation();
  1147.                 $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->origin_x;
  1148.                 $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->opposite_y;
  1149.                 $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->origin_z;
  1150.                 if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
  1151.                     \array_push($currentContainer->addition_points, $additionPoint);
  1152.                 } else {
  1153.                     $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
  1154.                 }
  1155.             }
  1156.  
  1157.             // Add a new addition point to the container based on the last item
  1158.             if (($currentContainer->items[$containerItemCount - 1]->origin_x < $currentContainer->width - $this->epsilon) &&
  1159.                 ($currentContainer->items[$containerItemCount - 1]->origin_y < $currentContainer->height - $this->epsilon) &&
  1160.                 ($currentContainer->items[$containerItemCount - 1]->opposite_z < $currentContainer->length - $this->epsilon)) {
  1161.                 $currentContainer->addition_point_count = $currentContainer->addition_point_count + 1;
  1162.                 $additionPoint = new ItemLocation();
  1163.                 $additionPoint->origin_x = $currentContainer->items[$containerItemCount - 1]->origin_x;
  1164.                 $additionPoint->origin_y = $currentContainer->items[$containerItemCount - 1]->origin_y;
  1165.                 $additionPoint->origin_z = $currentContainer->items[$containerItemCount - 1]->opposite_z;
  1166.                 if (\count($currentContainer->addition_points) < $currentContainer->addition_point_count) {
  1167.                     \array_push($currentContainer->addition_points, $additionPoint);
  1168.                 } else {
  1169.                     $currentContainer->addition_points[$currentContainer->addition_point_count - 1] = $additionPoint;
  1170.                 }
  1171.             }
  1172.  
  1173.             // Update the profit
  1174.             if (\count($currentContainer->items) === 1) {
  1175.                 $solution->net_profit += ($currentItemType->profit - $solution->containers[$containerIndex]->cost);
  1176.             } else {
  1177.                 $solution->net_profit += $currentItemType->profit;
  1178.             }
  1179.  
  1180.             // Update the volume per container and the total volume
  1181.             $solution->total_volume += $currentItemType->volume;
  1182.             $solution->total_weight += $currentItemType->weight;
  1183.  
  1184.             // Update the unpacked items
  1185.             if ($addType === 1) {
  1186.                 $solution->unpacked_item_count[$itemTypeIndex] = $solution->unpacked_item_count[$itemTypeIndex] - 1;
  1187.             }
  1188.             return true;
  1189.         }
  1190.     }
  1191.  
  1192.     /**
  1193.      * Returns the total amount of items need to be packed
  1194.      *
  1195.      * @return int
  1196.      */
  1197.     public function getTotalNumberOfItems(): int
  1198.     {
  1199.         $sum = 0;
  1200.         foreach ($this->itemTypeList as $key => $value) {
  1201.             if (isset($value->number_requested)) {
  1202.                 $sum += $value->number_requested;
  1203.             }
  1204.         }
  1205.         return $sum;
  1206.     }
  1207.  
  1208.     /**
  1209.      * Returns the total amount of containers that can be used
  1210.      *
  1211.      * @return int
  1212.      */
  1213.     public function getTotalNumberOfMandatoryContainers(): int
  1214.     {
  1215.         $sum = 0;
  1216.         foreach ($this->containerTypeList as $key => $value) {
  1217.             if (isset($value->number_available) && isset($value->mandatory)) {
  1218.                 if ($value->mandatory >= 0) {
  1219.                     $sum += $value->number_available;
  1220.                 }
  1221.             }
  1222.         }
  1223.         return $sum;
  1224.     }
  1225.  
  1226.     /**
  1227.      * Calculates the distance based on the amount of filled containers
  1228.      *
  1229.      * @param Solution $solution
  1230.      */
  1231.     public function calculateDispersion(Solution $solution): void
  1232.     {
  1233.         $solution->total_distance = 0;
  1234.  
  1235.         foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
  1236.             $container_count = 0;
  1237.             foreach ($solution->containers as $container) {
  1238.                 $itemFlag = false;
  1239.                 // Check if the container
  1240.                 foreach ($container->items as $item) {
  1241.                     if ($item->item_type === $itemTypeIndex) {
  1242.                         $itemFlag = true;
  1243.                     }
  1244.                 }
  1245.                 if ($itemFlag) {
  1246.                     $container_count++;
  1247.                 }
  1248.             }
  1249.             $solution->total_distance += $container_count * $container_count;
  1250.         }
  1251.     }
  1252.  
  1253.     /**
  1254.      * Chance to empty a container or remove some items from a container
  1255.      *
  1256.      * @param Solution $solution
  1257.      * @param int $containerIndex
  1258.      * @param float $percentTimeLeft
  1259.      */
  1260.     private function perturbSolution(Solution $solution, int $containerIndex, float $percentTimeLeft): void
  1261.     {
  1262.         $currentContainer = $solution->containers[$containerIndex];
  1263.  
  1264.         $containerEmptyingProbability = 0.05 + 0.15 * $percentTimeLeft;
  1265.         $itemRemovalProbability = 0.05 + 0.15 * $percentTimeLeft;
  1266.  
  1267.         if (\count($currentContainer->items) > 0) {
  1268.             // TODO This is just to make is consistent
  1269. //            if ((\mt_rand() / \mt_getrandmax()) < $containerEmptyingProbability) {
  1270.             if (true) {
  1271.                 foreach ($currentContainer->items as $item) {
  1272.                     $solution->unpacked_item_count[$item->item_type]++;
  1273.                     $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
  1274.                 }
  1275.  
  1276.                 $solution->net_profit += $currentContainer->cost;
  1277.                 $solution->total_volume -= $currentContainer->volume_packed;
  1278.                 $solution->total_weight -= $currentContainer->weight_packed;
  1279.  
  1280.                 $currentContainer->items = [];
  1281.                 $currentContainer->volume_packed = 0;
  1282.                 $currentContainer->weight_packed = 0;
  1283.                 $currentContainer->addition_point_count = 1;
  1284.                 $currentContainer->addition_points = [];
  1285.             } else {
  1286.                 $repackFlag = false;
  1287.                 if ((\mt_rand() / \mt_getrandmax()) < 0.3) {
  1288.                     foreach ($currentContainer->items as $itemIndex => $item) {
  1289.                         if ((!$solution->feasible && $item->mandatory === 0) ||
  1290.                             ((\mt_rand() / \mt_getrandmax()) < $itemRemovalProbability)) {
  1291.                             $solution->unpacked_item_count[$item->item_type]++;
  1292.                             $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
  1293.                             $item->item_type = -1;
  1294.                             $repackFlag = true;
  1295.                         }
  1296.                     }
  1297.                 } elseif ((\mt_rand() / \mt_getrandmax()) < 0.3) {
  1298.                     $maxZ = 0;
  1299.                     foreach ($currentContainer->items as $item) {
  1300.                         if ($maxZ < $item->opposite_z) {
  1301.                             $maxZ = $item->opposite_z;
  1302.                         }
  1303.                     }
  1304.  
  1305.                     $maxZ = $maxZ * (0.1 + (0.5 * $percentTimeLeft * (\mt_rand() / \mt_getrandmax())));
  1306.  
  1307.                     foreach ($currentContainer->items as $itemIndex => $item) {
  1308.                         if ((!$solution->feasible && $item->mandatory === 0) || $item->opposite_z < $maxZ) {
  1309.                             $solution->unpacked_item_count[$item->item_type]++;
  1310.                             $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
  1311.                             $item->item_type = -1;
  1312.                             $repackFlag = true;
  1313.                         }
  1314.                     }
  1315.                 } else {
  1316.                     $maxZ = 0;
  1317.                     foreach ($currentContainer->items as $item) {
  1318.                         if ($maxZ < $item->opposite_z) {
  1319.                             $maxZ = $item->opposite_z;
  1320.                         }
  1321.                     }
  1322.  
  1323.                     $maxZ = $maxZ * (0.6 - (0.5 * $percentTimeLeft * (\mt_rand() / \mt_getrandmax())));
  1324.                     foreach ($currentContainer->items as $itemIndex => $item) {
  1325.                         if ((!$solution->feasible && $item->mandatory === 0) || $item->opposite_z > $maxZ) {
  1326.                             $solution->unpacked_item_count[$item->item_type]++;
  1327.                             $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
  1328.                             $item->item_type = -1;
  1329.                             $repackFlag = true;
  1330.                         }
  1331.                     }
  1332.                 }
  1333.  
  1334.                 if ($repackFlag) {
  1335.                     foreach ($currentContainer->items as $item) {
  1336.                         if ($item->item_type > -1) {
  1337.                             $solution->net_profit -= $this->itemTypeList[$item->item_type]->profit;
  1338.                         }
  1339.                     }
  1340.  
  1341.                     $solution->net_profit += $currentContainer->cost;
  1342.                     $solution->total_volume -= $currentContainer->volume_packed;
  1343.                     $solution->total_weight -= $currentContainer->weight_packed;
  1344.  
  1345.                     foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
  1346.                         $currentContainer->repack_item_count[$itemTypeIndex] = 0;
  1347.                     }
  1348.  
  1349.                     foreach ($currentContainer->items as $itemIndex => $item) {
  1350.                         if ($item->item_type > -1) {
  1351.                             $currentContainer->repack_item_count[$item->item_type]++;
  1352.                         }
  1353.                     }
  1354.  
  1355.                     $currentContainer->items = [];
  1356.                     $currentContainer->volume_packed = 0;
  1357.                     $currentContainer->weight_packed = 0;
  1358.                     $currentContainer->addition_point_count = 1;
  1359.                     $currentContainer->addition_points = [];
  1360.  
  1361.                     foreach ($this->itemTypeList as $itemTypeIndex => $itemType) {
  1362.                         $continue = true;
  1363.                         while (($currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]] > 0) && $continue) {
  1364.                             $continue = $this->addItemToContainer($solution, $containerIndex, $solution->item_type_order[$itemTypeIndex], 2);
  1365.                         }
  1366.  
  1367.                         $solution->unpacked_item_count[$solution->item_type_order[$itemTypeIndex]] += $currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]];
  1368.                         $currentContainer->repack_item_count[$solution->item_type_order[$itemTypeIndex]] = 0;
  1369.                     }
  1370.                 }
  1371.             }
  1372.         }
  1373.  
  1374.         $itemTypeCount = \count($this->itemTypeList);
  1375.         for ($i = 0; $i < $itemTypeCount; $i++) {
  1376.             for ($j = 0; $j < 6; $j++) {
  1377.                 // TODO This is just to make is consistent
  1378. //                $k = (int)(5 - $j + 1) * (\mt_rand() / \mt_getrandmax()) + $j;
  1379.                 $k = (int)(5 - $j + 1) * 0.26302986092075 + $j;
  1380.  
  1381.                 $swapLong = $solution->rotation_order[$i][$j];
  1382.                 $solution->rotation_order[$i][$j] = $solution->rotation_order[$i][$k];
  1383.                 $solution->rotation_order[$i][$k] = $swapLong;
  1384.             }
  1385.         }
  1386.  
  1387.         for ($i = 0; $i < $itemTypeCount; $i++) {
  1388.             // TODO This is just to make is consistent
  1389. //            $j = \rand($i, $itemTypeCount - 1);
  1390.             $j = $itemTypeCount - 1;
  1391.  
  1392.             $swapLong = $solution->item_type_order[$i];
  1393.             $solution->item_type_order[$i] = $solution->item_type_order[$j];
  1394.             $solution->item_type_order[$j] = $swapLong;
  1395.         }
  1396.     }
  1397.  
  1398.     /**
  1399.      * Calculates the total distance of a container based on the distance of items within the containers
  1400.      *
  1401.      * @param Solution $solution
  1402.      */
  1403.     public function calculateDistance(Solution $solution): void
  1404.     {
  1405.         $solution->total_distance = 0;
  1406.         $solution->total_x_moment = 0;
  1407.         $solution->total_yz_moment = 0;
  1408.  
  1409.         foreach ($solution->containers as $container) {
  1410.             $itemCount = \count($container->items);
  1411.             $maxZ = 0;
  1412.             for ($i = 0; $i < $itemCount; $i++) {
  1413.                 for ($j = $i + 1; $j < $itemCount; $j++) {
  1414.                     if ($container->items[$i]->item_type === $container->items[$j]->item_type) {
  1415.                         $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);
  1416.                     }
  1417.                 }
  1418.  
  1419.                 if ($maxZ < $container->items[$i]->opposite_z) {
  1420.                     $maxZ = $container->items[$i]->opposite_z;
  1421.                 }
  1422.  
  1423.                 $solution->total_distance += $container->items[$i]->opposite_z;
  1424.             }
  1425.  
  1426.             $solution->total_distance += ($itemCount * $itemCount * $maxZ);
  1427.         }
  1428.     }
  1429.  
  1430.     /**
  1431.      * Returns a random float between 0 and 1
  1432.      *
  1433.      * @return float
  1434.      */
  1435.     public function getRandom(): float
  1436.     {
  1437.         return \mt_rand() / \mt_getrandmax();
  1438.     }
  1439. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement