/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).ToArray()) { OutputStation chosenStation = null; switch (_config.OrderingRule) { case WorkloadOrderingRule.LowestOrderCount: chosenStation = Instance.OutputStations .Where(s => s.Active && s.FitsForReservation(order)) // There has to be sufficient capacity left at the station .OrderBy(s => s.CapacityInUse + s.CapacityReserved) // Choose the one with the least orders .ThenBy(s => Instance.Randomizer.NextDouble()) // Use a random tie-breaker .FirstOrDefault(); break; case WorkloadOrderingRule.HighestOrderCount: chosenStation = Instance.OutputStations .Where(s => s.Active && s.FitsForReservation(order)) // There has to be sufficient capacity left at the station .OrderByDescending(s => s.CapacityInUse + s.CapacityReserved) // Choose the one with the most orders .ThenBy(s => Instance.Randomizer.NextDouble()) // Use a random tie-breaker .FirstOrDefault(); break; default: throw new ArgumentException("Unknown ordering rule: " + _config.OrderingRule); } // If we found a station, assign the bundle to it if (chosenStation != null) { AllocateOrder(order, chosenStation); } } }
/// <summary> /// Called whenever a new Order has been assigned to an OutputStation. /// </summary> /// <param name="order">The Order which is assigned to the station.</param> /// <param name="station">The station the order is assigned to.</param> public void NewOrderAssignedToStation(Order order, OutputStation station) { // Connect request info with assigned station foreach (var request in order.Requests) { request.Assign(station); // If request is not yet assigned, update the info if (_availableExtractRequests.Contains(request)) { // Move request to available request list of station _availableExtractRequestsPerStation[station].Add(request); _availableExtractRequestsPerStationQueue[station].Remove(request); // Update demand _queuedDemand[request.Item]--; _assignedDemand[request.Item]++; } // Manage queue info _stationQueuedPerExtractRequest.Remove(request); } /* * station.StatCurrentlyOpenRequests = _availableExtractRequestsPerStation[station].Count; * station.StatCurrentlyOpenQueuedRequests = _availableExtractRequestsPerStationQueue[station].Count; */ }
/// <summary> /// Validates that the given combination is a valid assignment to the fast-lane slot of a station. /// </summary> /// <param name="s">The station.</param> /// <param name="o">The order.</param> /// <returns><code>true</code> if the assignment is valid, <code>false</code> otherwise.</returns> private bool ValidFastLaneAssignment(OutputStation s, Order o) { return(_nearestInboundPod[s] == null || _nearestInboundPod[s].GetDistance(s) > Instance.SettingConfig.Tolerance ? false : o.Requests.Any(r => r.State != Management.RequestState.Finished && r.Pod != null && r.Pod != _nearestInboundPod[s]) ? false : o.Requests.Where(r => r.State != Management.RequestState.Finished && r.Pod == null).GroupBy(r => r.Item) .All(i => _nearestInboundPod[s].CountAvailable(i.Key) >= i.Count()) ? true : false); }
/// <summary> /// Gets the number of robots currently assigned to the given station. Does only work for the balanced bot manager. For others it will only return 0. /// </summary> /// <param name="station">The station to get the number for.</param> /// <returns>The number of bots currently assigned to the station or always 0 if the bot manager does not support these assignments.</returns> internal int StatGetInfoBalancedBotsPerStation(OutputStation station) { return (Controller.BotManager is BalancedBotManager ? (Controller.BotManager as BalancedBotManager).GetAssignedBotCount(station) : Controller.BotManager is ConstantRatioBotManager ? (Controller.BotManager as ConstantRatioBotManager).GetAssignedBotCount(station) : 0); }
/// <summary> /// return the Distance between the bot and the station /// </summary> /// TODO Change the way to calculate Distance by different Tiers private double GetDistance(Bot bot, Object station) { double distance; if (station is InputStation) { InputStation inStation = station as InputStation; //Manhatten Distance distance = Math.Abs(inStation.GetInfoCenterX() - bot.GetInfoCenterX()) + Math.Abs(inStation.GetInfoCenterY() - bot.GetInfoCenterY()); if (inStation.GetInfoCurrentTier().GetInfoZ() != bot.GetInfoCurrentTier().GetInfoZ()) { distance += bot.GetInfoCurrentTier().GetInfoWidth(); distance += Math.Abs(inStation.GetInfoCurrentTier().GetInfoZ() - bot.GetInfoCurrentTier().GetInfoZ()); } } else if (station is OutputStation) { OutputStation outStation = station as OutputStation; //Manhatten Distance distance = Math.Abs(outStation.GetInfoCenterX() - bot.GetInfoCenterX()) + Math.Abs(outStation.GetInfoCenterY() - bot.GetInfoCenterY()); if (outStation.GetInfoCurrentTier().GetInfoZ() != bot.GetInfoCurrentTier().GetInfoZ()) { distance += bot.GetInfoCurrentTier().GetInfoWidth(); distance += Math.Abs(outStation.GetInfoCurrentTier().GetInfoZ() - bot.GetInfoCurrentTier().GetInfoZ()); } } else { distance = Int32.MaxValue; } return(distance); }
/// <summary> /// Notifies the instance that an order was completed in order to keep the statistics up-to-date. /// </summary> /// <param name="oStation">The corresponding station.</param> /// <param name="order">The order that was completed.</param> internal void NotifyOrderCompleted(Order order, OutputStation oStation) { // Store the number of handled items StatOverallOrdersHandled++; // Check whether the order was completed too late if (Controller.CurrentTime - order.DueTime > 0) { StatOverallOrdersLate++; } // Mark every item in the history with a timestamp //_statOrderHandlingTimestamps.Add( //new OrderHandledDatapoint(StatTime, oStation.ID, Controller.CurrentTime - order.TimeStamp, Controller.CurrentTime - order.TimeStampSubmit, Controller.CurrentTime - order.DueTime, Controller.CurrentTime - order.TimeStampSubmit, StatTime, StatTime, StatTime)); // Flush data points in case there are too many already if (_statOrderHandlingTimestamps.Count > STAT_MAX_DATA_POINTS) { StatFlushOrdersHandled(); } // Log turnover time _statOrderTurnoverTimes.Add(Controller.CurrentTime - order.TimeStamp); // Log throughput time _statOrderThroughputTimes.Add(Controller.CurrentTime - order.TimeStampSubmit); // Log lateness _statOrderLatenessTimes.Add(Controller.CurrentTime - order.DueTime); // log Time Queuing _statOrderTimeQueueing.Add(Controller.CurrentTime - order.TimeStampSubmit); _statOrderTotalTimeQueueing.Add(StatTime); _statOrderTaskTimeRest.Add(StatTime); // Raise the event OrderCompleted?.Invoke(order, oStation); }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).ToArray()) { OutputStation chosenStation = Instance.OutputStations // There has to be sufficient capacity left at the station .Where(s => s.Active && s.FitsForReservation(order)) // Choose the one with the most lines in common .OrderByDescending(s => s.AssignedOrders.Sum(other => other.Positions.Select(p => p.Key).Intersect(order.Positions.Select(p => p.Key)).Count())) // Use a tie breaker (especially for situations where stations are empty) .ThenBy(s => { switch (_config.TieBreaker) { case RelatedOrderBatchingTieBreaker.Random: return(Instance.Randomizer.NextDouble()); case RelatedOrderBatchingTieBreaker.LeastBusy: return(s.AssignedOrders.Count()); case RelatedOrderBatchingTieBreaker.MostBusy: return(-s.AssignedOrders.Count()); default: throw new ArgumentException("Unknown tie-breaker: " + _config.TieBreaker); } }) // The first one is the best .FirstOrDefault(); // If we found a station, assign the bundle to it if (chosenStation != null) { AllocateOrder(order, chosenStation); } } }
/// <summary> /// Notifies the instance that an order was allocated. /// </summary> internal void NotifyOrderAllocated(OutputStation oStation, Order order) { // Store the time the order was submitted to the system order.TimeStampSubmit = Controller.CurrentTime; // Raise the event OrderAllocated?.Invoke(oStation, order); }
/// <summary> /// Notifies the instance that a line was handled in order to keep the statistics up-to-date. /// </summary> /// <param name="oStation">The corresponding station.</param> /// <param name="item">The item that was picked.</param> /// <param name="lineCount">The number of items that have been picked for the line.</param> internal void NotifyLineHandled(OutputStation oStation, ItemDescription item, int lineCount) { // Store the number of handled lines StatOverallLinesHandled++; // Raise the event LinePicked?.Invoke(oStation, item, lineCount); }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).ToArray()) { OutputStation chosenStation = null; // Try to reuse the last station for this order if (_config.Recycle && _lastChosenStation != null && _lastChosenStation.Active && _lastChosenStation.FitsForReservation(order)) { // Last chosen station can be used for this order too chosenStation = _lastChosenStation; } else { // Choose a random station chosenStation = Instance.OutputStations .Where(s => s.Active && s.FitsForReservation(order)) // There has to be sufficient capacity left at the station .OrderBy(s => Instance.Randomizer.NextDouble()) // Choose a random one .FirstOrDefault(); _lastChosenStation = chosenStation; } // If we found a station, assign the bundle to it if (chosenStation != null) { AllocateOrder(order, chosenStation); } } }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).ToArray()) { // Check all pods for the maximum number of picks that can be done with them List <Pod> bestPods = new List <Pod>(); int bestPodPicks = -1; foreach (var pod in Instance.Pods.Where(p => !p.InUse)) { // Calculate picks that can potentially be done with the pod int picks = order.Positions.Sum(pos => Math.Min(pod.CountAvailable(pos.Key), pos.Value)); // Check whether we found even more possible picks with this pod if (bestPodPicks < picks) { bestPods.Clear(); bestPods.Add(pod); bestPodPicks = picks; } else { // Check whether the current pod belongs into the winner group if (bestPodPicks == picks) { bestPods.Add(pod); } } } // Choose station nearest to one of the best pods OutputStation chosenStation = null; double shortestDistance = double.PositiveInfinity; foreach (var station in Instance.OutputStations.Where(s => s.Active && s.FitsForReservation(order))) { foreach (var pod in bestPods) { double distance; switch (_config.DistanceRule) { case NearBestPodOrderBatchingDistanceRule.Euclid: distance = Distances.CalculateEuclid(pod, station, Instance.WrongTierPenaltyDistance); break; case NearBestPodOrderBatchingDistanceRule.Manhattan: distance = Distances.CalculateManhattan(pod, station, Instance.WrongTierPenaltyDistance); break; case NearBestPodOrderBatchingDistanceRule.ShortestPath: distance = Distances.CalculateShortestPathPodSafe(pod.Waypoint, station.Waypoint, Instance); break; default: throw new ArgumentException("Unknown distance rule: " + _config.DistanceRule); } if (distance < shortestDistance) { shortestDistance = distance; chosenStation = station; } } } // If we found a station, assign the bundle to it if (chosenStation != null) { AllocateOrder(order, chosenStation); } } }
/// <summary> /// Removes the request information belonging to the order. /// </summary> /// <param name="order">The order that was just completed.</param> /// <param name="station">The station at which the order was completed.</param> private void OrderCompleted(Order order, OutputStation station) { foreach (var r in order.Requests) { _stationQueuedPerExtractRequest.Remove(r); } _availableExtractRequestsPerOrder.Remove(order); }
private void PodHandled(Pod pod, InputStation iStation, OutputStation oStation) { // If the recycled pod was just handled at an input-station, do not assign any more bundles to it (we do not want to bring it back immediately after replenishing it) if (pod == _lastChosenPod && iStation != null) { _lastChosenPod = null; } }
/// <summary> /// Estimates the amount of time it will take before a new item can be delivered to the output-station. /// </summary> /// <param name="w">The waypoint of the output-station.</param> /// <param name="bot">The bot to consider.</param> /// <returns>The estimated time.</returns> public static double EstimateOutputStationWaitTime(Bot bot, Waypoint w) { OutputStation ws = w.OutputStation; // Assume the wait time is 2 * length of the bot there plus the time for all items double waitTime = ((ws.ItemTransferTime + 5 * 2 * bot.Radius) / bot.MaxVelocity) * w.BotCountOverall * w.BotCountOverall; // TODO this time estimate cannot hold when transferring multiple items at once return(waitTime); }
/// <summary> /// Checks whether a filling repositioning move is necessary for the given station. /// </summary> /// <param name="station">The station to check.</param> /// <returns><code>true</code> if filling repositioning should be done for the station, <code>false</code> otherwise. </returns> private bool NeedsFillingRepositioning(OutputStation station) { return // We need to relocate, if the number of used cache storage locations is smaller ... (_chacheStorageLocations[station].Count(w => w.Pod != null) < // ... than the number of cache storage locations overall minus the ones to keep free per cache _chacheStorageLocations[station].Count - _config.CacheClearing); }
private void PodHandled(Pod pod, InputStation iStation, OutputStation oStation) { // If the recycled pod was just handled at an input-station, do not assign any more bundles to it if (pod == _lastChosenPod && iStation != null) { _lastChosenPod = null; } }
private void PodHandled(Pod pod, InputStation iStation, OutputStation oStation) { // If the recycled pod was just handled at an input-station, do not assign any more bundles to it (we do not want to bring it back immediately after replenishing it) if (iStation != null && _lastChosenPodByClassReverse.ContainsKey(pod)) { _lastChosenPodByClass.Remove(_lastChosenPodByClassReverse[pod]); _lastChosenPodByClassReverse.Remove(pod); } }
/// <summary> /// Cleans up after a task was successfully executed. /// </summary> public override void Finish() { if (Requests.Any()) { throw new InvalidOperationException("An unfinished request cannot be marked as finished!"); } OutputStation.UnregisterInboundPod(ReservedPod); OutputStation.UnregisterExtractTask(this); }
/// <summary> /// Immediately submits the order to the station. /// </summary> /// <param name="order">The order that is going to be allocated.</param> /// <param name="station">The station the order is assigned to.</param> protected void AllocateOrder(Order order, OutputStation station) { // Update lists _pendingOrders.Remove(order); // Update intermediate capacity information station.RegisterOrder(order); // Submit the decision Instance.Controller.Allocator.Allocate(order, station); }
public void buildPickStation(int row, int column, directions d, List <Waypoint> bufferPaths, int activationOrderID) { OutputStation oStation = instance.CreateOutputStation( instance.RegisterOutputStationID(), tier, column + 0.5, row + 0.5, lc.StationRadius, lc.OStationCapacity, lc.ItemTransferTime, lc.ItemPickTime, activationOrderID); createTile_PickStation(row, column, d, oStation); Waypoint wp = tiles[row, column].wp; oStation.Queues[wp] = bufferPaths; }
public void createTile_PickStation(int row, int column, directions d, OutputStation oStation) { Waypoint wp = instance.CreateWaypoint(instance.RegisterWaypointID(), tier, oStation, true); if (tiles[row, column] != null) { throw new ArgumentException("trying to overwrite an existing waypoint!! At createTile_PickStation: tiles[row, column] != null"); } tiles[row, column] = new Tile(d, wp, waypointTypes.PickStation); }
/// <summary> /// Creates a new task. /// </summary> /// <param name="instance">The instance this task belongs to.</param> /// <param name="bot">The robot that shall execute the task.</param> /// <param name="reservedPod">The pod to use for executing the task.</param> /// <param name="outputStation">The output station to bring the pod to.</param> /// <param name="requests">The requests to handle with this task.</param> public ExtractTask(Instance instance, Bot bot, Pod reservedPod, OutputStation outputStation, List <ExtractRequest> requests) : base(instance, bot) { ReservedPod = reservedPod; OutputStation = outputStation; Requests = requests; foreach (var request in requests) { request.StatInjected = false; } }
/// <summary> /// Prepares everything for executing the task (claiming resources and similar). /// </summary> public override void Prepare() { Instance.ResourceManager.ClaimPod(ReservedPod, Bot, BotTaskType.Extract); OutputStation.RegisterInboundPod(ReservedPod); OutputStation.RegisterExtractTask(this); for (int i = 0; i < Requests.Count; i++) { Instance.ResourceManager.RemoveExtractRequest(Requests[i]); ReservedPod.RegisterItem(Requests[i].Item, Requests[i]); } }
/// <summary> /// Enqueues an extraction task. /// </summary> /// <param name="bot">The bot that shall execute the task.</param> /// <param name="station">The station at which the task will be executed.</param> /// <param name="pod">The pod used for the task.</param> /// <param name="requests">The requests to handle when executing the task.</param> protected void EnqueueExtract(Bot bot, OutputStation station, Pod pod, List <ExtractRequest> requests) { ExtractTask task = new ExtractTask(Instance, bot, pod, station, requests); task.Prepare(); if (_taskQueues[bot] != null) { _taskQueues[bot].Cancel(); } _taskQueues[bot] = task; _lastTaskEnqueued[bot] = task; }
/// <summary> /// Cleans up a cancelled task. /// </summary> public override void Cancel() { if (Bot.Pod == null) { Instance.ResourceManager.ReleasePod(ReservedPod); } OutputStation.UnregisterInboundPod(ReservedPod); OutputStation.UnregisterExtractTask(this); for (int i = 0; i < Requests.Count; i++) { Instance.ResourceManager.ReInsertExtractRequest(Requests[i]); ReservedPod.UnregisterItem(Requests[i].Item, Requests[i]); } }
/// <summary> /// Creates a new waypoint that serves as the handover point for an output station. /// </summary> /// <param name="id">The ID of the waypoint.</param> /// <param name="tier">The position (tier).</param> /// <param name="station">The station.</param> /// <param name="isQueueWaypoint">Indicates whether this waypoint is also a queue waypoint.</param> /// <returns>The newly created waypoint.</returns> public Waypoint CreateWaypoint(int id, Tier tier, OutputStation station, bool isQueueWaypoint) { Waypoint wp = new Waypoint(this) { ID = id, X = station.X, Y = station.Y, Radius = station.Radius, OutputStation = station, IsQueueWaypoint = isQueueWaypoint }; station.Waypoint = wp; tier.AddWaypoint(wp); Waypoints.Add(wp); WaypointGraph.Add(wp); _idToWaypoint[wp.ID] = wp; // Set volatile ID SetVolatileIDForWaypoint(wp); // Return return(wp); }
private double Score(ScoreFunctionStationOrder scoreFunction, OutputStation station, Order order) { double score; switch (scoreFunction) { case ScoreFunctionStationOrder.InboundPodsAvailablePicks: score = ScoreNormal(station, order); break; case ScoreFunctionStationOrder.InboundPodsAvailablePicksDepletePod: score = ScoreWeightedDepletePod(station, order); break; case ScoreFunctionStationOrder.Deadline: score = ScoreDeadline(station, order); break; case ScoreFunctionStationOrder.InboundPodsAvailablePicksNotDepletePod: score = ScoreWeightedOTW(station, order); break; default: throw new ArgumentException("Unknown score function: " + _config.ScoreFunctionStationOrder.ToString()); } return(score); }
/// <summary> /// Notifies the instance that items were handled in order to keep the statistics up-to-date. /// </summary> /// <param name="pod">The corresponding pod.</param> /// <param name="bot">The corresponding bot.</param> /// <param name="oStation">The corresponding station.</param> /// <param name="item">The item that was picked.</param> internal void NotifyItemHandled(Pod pod, Bot bot, OutputStation oStation, ItemDescription item) { // Store the number of handled items pod.StatItemsHandled++; StatOverallItemsHandled++; // Mark every item in the history with a timestamp _statItemHandlingTimestamps.Add(new ItemHandledDatapoint(Controller.CurrentTime - StatTimeStart, bot.ID, pod.ID, oStation.ID)); // Flush data points in case there are too many already if (_statItemHandlingTimestamps.Count > STAT_MAX_DATA_POINTS) { StatFlushItemsHandled(); } // Keep track of the maximal number of handled items if (StatMaxItemsHandledByPod < pod.StatItemsHandled) { StatMaxItemsHandledByPod = pod.StatItemsHandled; } // Raise the event ItemPicked?.Invoke(pod, bot, oStation, item); }
/// <summary> /// Allocates the order. /// </summary> /// <param name="order">The order to allocate.</param> /// <param name="station">The station to allocate the order to.</param> public void Allocate(Order order, OutputStation station) { // Indicate change at instance Instance.Changed = true; // Check whether decision is possible if (station.CapacityInUse + 1 > station.Capacity) { throw new InvalidOperationException("Allocating the bundle to the station would exceed its capacity!"); } // Hand over the order station.AssignOrder(order); // Add extraction request Instance.ResourceManager.NewOrderAssignedToStation(order, station); // Mark orders allocated Instance.Controller.OrderManager.SignalOrderAllocated(order, station); // Notify item manager Instance.ItemManager.NewOrderAssignedToStation(station, order); // Remove order from item manager (Instance.ItemManager as ItemManager).TakeAvailableOrder(order); }
/// <summary> /// Called whenever a new order shall be assigned to the order pool of a station. /// </summary> /// <param name="order">The order to assign to the order pool of the station.</param> /// <param name="station">The station to assign the order to.</param> public void NewOrderQueuedToStation(Order order, OutputStation station) { // Remember queue time order.TimeStampQueued = _instance.Controller.CurrentTime; // Remember station for all requests of the order foreach (var request in order.Requests) { _stationQueuedPerExtractRequest[request] = station; } // Update active requests foreach (var request in _availableExtractRequestsPerOrder[order]) { // Move not yet allocated requests to available requests of station _availableExtractRequestsPerStationQueue[station].Add(request); // Update demand _queuedDemand[request.Item]++; _backlogDemand[request.Item]--; } // Update statistics station.StatCurrentlyOpenQueuedRequests = _availableExtractRequestsPerStationQueue[station].Count; }