/** * @param RideNodeList $rideNodeList */ public function addRideNodeList(RideNodeList $rideNodeList) { $this->totalDistance += $rideNodeList->getTotalDistance(); $this->totalEmptyRideTime += $rideNodeList->getTotalEmptyRideTime(); $this->totalEmptyRideDistance += $rideNodeList->getTotalEmptyRideDistance(); $this->rideNodeLists[] = $rideNodeList; }
/** * Enforce operational constraints for ride nodes and node lists: * * constraint 1: vehicle capacity (seats, wheelchairs) not exceeded * constraint 2: compatibility passenger(s) and vehicle (true) * constraint 3: compatibility passenger(s) and driver (true) * * return true if anything is wrong (taboo, sometimes also called tabu) * * @param RideNodeList $rideNodeList * @param RideNode $rideNode * @return bool */ public function isTaboo(RideNodeList $rideNodeList, RideNode $rideNode) { $vehicle = $rideNodeList->getVehicle(); if (null !== $vehicle) { // constraint 1: vehicle capacity (resources) $vehicleCat = $vehicle->getCategory(); if ($rideNode->passengers > $vehicleCat->getAmountOfSeats() or $rideNode->wheelChairs > $vehicleCat->getAmountOfWheelChairs()) { return true; } // constraint 2: compatibility of vehicle category and passenger(s) $contradicting = $rideNode->contradictingVehicleCategories; if (null !== $contradicting) { if (array_key_exists($vehicleCat->getId(), $contradicting)) { return true; } } } // todo constraint 3: compatibility passenger(s) and driver tested here... return false; }
/** * @param RideNode $feasibleNode * @param RideNodeList $rideNodeList * @throws \Exception * @return mixed */ private function checkIfNodeIsFeasibleForNodeList(RideNode $feasibleNode, RideNodeList $rideNodeList) { /** @var RideNode $listNode */ $listNode = $rideNodeList->getFirstNode(); $counter = 0; while (null !== $listNode) { if ($this->checkOverlappingNodes($feasibleNode, $listNode)) { // overlapping nodes return RideDTO::MAYBE; } elseif ($feasibleNode->endMinute < $listNode->startMinute) { // feasible node is left $rideNodeList->addRideNodeBeforeRideNode($feasibleNode, $listNode); return $this->checkEmptyRidesInNodeList($rideNodeList, $feasibleNode); } elseif ($listNode === $rideNodeList->getLastNode()) { // feasible node is right of last node $rideNodeList->addRideNodeAfterRideNode($feasibleNode, $listNode); return $this->checkEmptyRidesInNodeList($rideNodeList, $feasibleNode); } else { // feasible node is in between two nodes, check next node $listNode = $listNode->nextNode; $counter = $this->incrementAndTestLoopCounter($counter); } } return RideDTO::YES; // vehicle driving pool is empty }
/** * @param $rideNodes RideNode[] * @return RideConfiguration */ private function buildGenericLeastDurationConfiguration($rideNodes) { $rideConfiguration = new RideConfiguration($this->drivingPools); $workRideNodes = $rideNodes; //always loop all available pools - but stop when no missions are left so it is //possible to have empty rideNodeLists (no driver/vehicle needed for these) //the amount of created rideNodeLists will give the amount of used vehicles foreach ($this->drivingPools as $drivingPool) { if (count($workRideNodes) < 1) { break; } $rideNodeList = new RideNodeList($drivingPool); //set first node on start $rideNodeList->addRideNode(array_shift($workRideNodes)); $poolExtraMinutesPerRide = 0; if ($drivingPool->hasAssociatedDriver()) { $poolExtraMinutesPerRide += $drivingPool->getDriver()->getExtraMinutes(); } $stillFeasible = true; while ($stillFeasible) { $actualNode = $rideNodeList->getActualRideNode(); $bestNode = null; $bestNodeKey = null; $bestEmptyRide = null; $actualDuration = -1; //check all nodes in workSet for feasible and best distance foreach ($workRideNodes as $compareNodeKey => $compareNode) { $emptyRide = $this->adjacenceMatrix[$actualNode->getRideNodeHashId()][$compareNode->getRideNodeHashId()]; //not feasible, get next node if ($emptyRide === -1) { continue; } if ($this->isTaboo($rideNodeList, $compareNode)) { continue; } // feasible $feasibleTimeForNextNode = $actualNode->endMinute + $emptyRide->duration + DispositionVariables::ARRIVAL_BEFORE_PICKUP + $poolExtraMinutesPerRide; //feasible time for node + emptyRide -> to next node if ($feasibleTimeForNextNode <= $compareNode->startMinute) { if ($actualDuration === -1) { $bestNode = $compareNode; $bestNodeKey = $compareNodeKey; $bestEmptyRide = $emptyRide; $actualDuration = $emptyRide->duration; } if ($emptyRide->duration < $actualDuration) { $bestNode = $compareNode; $bestNodeKey = $compareNodeKey; $bestEmptyRide = $emptyRide; $actualDuration = $emptyRide->duration; } } } if ($bestNode && $bestEmptyRide) { $rideNodeList->addRideNode($bestNode); $rideNodeList->addRideNode($bestEmptyRide); unset($workRideNodes[$bestNodeKey]); } else { $stillFeasible = false; } } $rideConfiguration->addRideNodeList($rideNodeList); } $rideConfiguration->setNotFeasibleNodes($workRideNodes); $rideConfiguration->removeEmptyRideNodeLists(); return $rideConfiguration; }
/** * build rideConfiguration in this builder and returns it * * @return RideConfiguration */ public function createConfigurationFromExistingMissions() { foreach ($this->drivingPools as $pool) { $rideNodeList = new RideNodeList($pool); if ($pool->hasAssociatedDrivingMissions()) { $nodes = $this->createRideNodesFromDrivingMissions($pool->getDrivingMissions()); $this->sortNodesByStartMinute($nodes); foreach ($nodes as $node) { $rideNodeList->addRideNode($node); } } $this->rideConfiguration->addRideNodeList($rideNodeList); } return $this->rideConfiguration; }