/// <summary> /// Estimates the time it takes to travel from one waypoint to another. /// </summary> /// <param name="from">The start node.</param> /// <param name="to">The destination node.</param> /// <returns>The estimated time it takes to travel between the nodes.</returns> private double EstimateTravelTime(TimeWaypoint from, Waypoint to) { // Init, if not done yet if (_timeGraph == null) { _timeGraph = new TimeGraph(_instance); } // --> Calculate time needed for driving the given distance double travelTime = Distances.CalculateEuclid(from.OriginalWaypoint, to, _instance.WrongTierPenaltyDistance) / _timeGraph.Speed; // Check whether we reached the goal already, thus, eliminating the need for turning if (from.OriginalWaypoint == to) { // No turning necessary - only consider time for driving return(travelTime); } else { // In addition to the drive time also consider the effort for turning return // --> Calculate time needed for turning towards new orientation (Math.Abs(Circle.GetOrientationDifference(from.Orientation, Circle.GetOrientation(from.OriginalWaypoint.X, from.OriginalWaypoint.Y, to.X, to.Y))) / TimeGraph.PI2 * _timeGraph.TurnSpeed + // --> Add time for driving travelTime); } }
/// <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> /// Calculates the time to move from one time-waypoint to another. /// </summary> /// <param name="from">The start node.</param> /// <param name="to">The destination node.</param> /// <returns>The time for traveling from one node to the other.</returns> public double GetTimeNeededToTravel(TimeWaypoint from, TimeWaypoint to) { return // --> Calculate time needed for turning towards new orientation (Math.Abs(Circle.GetOrientationDifference(from.Orientation, to.Orientation)) / PI2 * TurnSpeed + // --> Calculate time needed for driving the given distance Distances.CalculateEuclid(from.OriginalWaypoint, to.OriginalWaypoint, Instance.WrongTierPenaltyDistance) / Speed); }
/// <summary> /// Initializes this manager. /// </summary> private void Init() { // --> Init shared component or ensure consistency Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // --> Store information about the station to which a cache storage location belongs _cacheStations = new VolatileIDDictionary <Waypoint, OutputStation>( Instance.SharedControlElements.StoragePartitioner.CachePartitions.SelectMany(kvp => kvp.Value.Select(wp => new VolatileKeyValuePair <Waypoint, OutputStation>(wp, kvp.Key))).ToList()); // --> Init scoring List <Func <double> > scorers = new List <Func <double> >(); // First select cache or not depending on decision scorers.Add(() => { // Check whether the current location belongs to the current station's cache and the pod does make the cut according to the cache score if (_currentCacheable && _currentStorageLocation.InfoTagCache == ZoneType.Cache && _cacheStations[_currentStorageLocation] == _currentStation) { // It fits, reward it return(-1); } // Check whether the pod shouldn't be cached and the storage location does not belong to a cache else if (!_currentCacheable && _currentStorageLocation.InfoTagCache != ZoneType.Cache) { // It fits, reward it return(-1); } // Check whether the storage location belongs to a foreign station's cache else if (_currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Heavily penalize using foreign cache storage locations return(1); } else { // It does not fit, penalize it return(0); } }); // Then select the nearest one scorers.Add(() => { switch (_config.PodDisposeRule) { case CacheStorageLocationSelectionRule.Euclid: return(Distances.CalculateEuclid(_currentPodLocation, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.Manhattan: return(Distances.CalculateManhattan(_currentPodLocation, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.ShortestPath: return(Distances.CalculateShortestPathPodSafe(_currentPodLocation, _currentStorageLocation, Instance)); case CacheStorageLocationSelectionRule.ShortestTime: return(Distances.CalculateShortestTimePathPodSafe(_currentPodLocation, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }); // Instantiate best candidate assessment _bestCandidateSelector = new BestCandidateSelector(false, scorers.ToArray()); }
/// <summary> /// Estimates the time for traveling using the manhattan metric. /// </summary> /// <param name="from">The from part of the trip.</param> /// <param name="to">The to part of the trip.</param> /// <param name="instance">The instance.</param> /// <returns>The time needed to for conducting the trip according to the manhattan metric.</returns> public double EstimateShortestPathEuclid(Circle from, Circle to, Instance instance) { // Init, if not done yet if (_timeGraph == null) { _timeGraph = new TimeGraph(_instance); } return // Use full manhattan distance to estimate travel time (Distances.CalculateEuclid(from, to, instance.WrongTierPenaltyDistance) / _timeGraph.Speed); }
/// <summary> /// Decides the waypoint to use when storing a pod. This call is measured by the timing done. /// </summary> /// <param name="pod">The pod to store.</param> /// <returns>The waypoint to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { double minDistance = double.PositiveInfinity; Waypoint bestStorageLocation = null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Calculate the distance double distance; switch (_config.PodDisposeRule) { case StationBasedPodStorageLocationDisposeRule.Euclid: distance = (_config.OutputStationMode ? Instance.OutputStations.Cast <Circle>() : Instance.InputStations.Cast <Circle>()) .Min(station => Distances.CalculateEuclid(station, storageLocation, Instance.WrongTierPenaltyDistance)); break; case StationBasedPodStorageLocationDisposeRule.Manhattan: distance = (_config.OutputStationMode ? Instance.OutputStations.Cast <Circle>() : Instance.InputStations.Cast <Circle>()) .Min(station => Distances.CalculateManhattan(station, storageLocation, Instance.WrongTierPenaltyDistance)); break; case StationBasedPodStorageLocationDisposeRule.ShortestPath: distance = (_config.OutputStationMode ? Instance.OutputStations.Select(s => s.Waypoint) : Instance.InputStations.Select(s => s.Waypoint)) .Min(station => Distances.CalculateShortestPathPodSafe(storageLocation, station, Instance)); break; case StationBasedPodStorageLocationDisposeRule.ShortestTime: distance = (_config.OutputStationMode ? Instance.OutputStations.Select(s => s.Waypoint) : Instance.InputStations.Select(s => s.Waypoint)) .Min(station => Distances.CalculateShortestTimePathPodSafe(storageLocation, station, Instance)); break; default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } // Update minimum if (distance < minDistance) { minDistance = distance; bestStorageLocation = storageLocation; } } // Check success if (bestStorageLocation == null) { throw new InvalidOperationException("There was no suitable storage location for the pod: " + pod.ToString()); } // Return it return(bestStorageLocation); }
/// <summary> /// This is called to decide about potentially pending bundles. /// 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 DecideAboutPendingBundles() { foreach (var bundle in _bundleToStation.Keys.ToArray()) { // Find a pod Pod chosenPod = Instance.Pods .Where(b => b.FitsForReservation(bundle)) .OrderBy(b => b.InUse ? Instance.WrongTierPenaltyDistance + Distances.CalculateEuclid(_bundleToStation[bundle], b, Instance.WrongTierPenaltyDistance) : Distances.CalculateEuclid(_bundleToStation[bundle], b, Instance.WrongTierPenaltyDistance)) .FirstOrDefault(); // If we found a pod, assign the bundle to it if (chosenPod != null) { AddToReadyList(bundle, chosenPod); _bundleToStation.Remove(bundle); } } }
/// <summary> /// Returns a suitable storage location for the given pod. /// </summary> /// <param name="pod">The pod to fetch a storage location for.</param> /// <returns>The storage location to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { double minDistance = double.PositiveInfinity; Waypoint bestStorageLocation = null; Waypoint podLocation = // Get current waypoint of pod, if we want to estimate a path _config.PodDisposeRule == NearestPodStorageLocationDisposeRule.ShortestPath || _config.PodDisposeRule == NearestPodStorageLocationDisposeRule.ShortestTime ? Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y) : null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Calculate the distance double distance; switch (_config.PodDisposeRule) { case NearestPodStorageLocationDisposeRule.Euclid: distance = Distances.CalculateEuclid(pod, storageLocation, Instance.WrongTierPenaltyDistance); break; case NearestPodStorageLocationDisposeRule.Manhattan: distance = Distances.CalculateManhattan(pod, storageLocation, Instance.WrongTierPenaltyDistance); break; case NearestPodStorageLocationDisposeRule.ShortestPath: distance = Distances.CalculateShortestPathPodSafe(podLocation, storageLocation, Instance); break; case NearestPodStorageLocationDisposeRule.ShortestTime: distance = Distances.CalculateShortestTimePathPodSafe(podLocation, storageLocation, Instance); break; default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } // Update minimum if (distance < minDistance) { minDistance = distance; bestStorageLocation = storageLocation; } } // Check success if (bestStorageLocation == null) { throw new InvalidOperationException("There was no suitable storage location for the pod: " + pod.ToString()); } // Return it return(bestStorageLocation); }
/// <summary> /// Returns a suitable storage location for the given pod. /// </summary> /// <param name="pod">The pod to fetch a storage location for.</param> /// <returns>The storage location to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { // Ensure init if (_bestCandidateSelector == null) { Init(); } // Get output station this pod is coming from (it has to be the nearest one) _currentStation = Instance.OutputStations.ArgMin(s => Distances.CalculateEuclid(s, pod, Instance.WrongTierPenaltyDistance)); // Get current pod location (pod has to be at station's position right now) _currentPodLocation = _currentStation.Waypoint; // Determine whether the pod should be put in the cache double cacheUtilityValue = Instance.SharedControlElements.StoragePartitioner.GetCacheValue(pod, _currentStation, _config.WeightSpeed, _config.WeightUtility); double cacheFill = (double)Instance.SharedControlElements.StoragePartitioner.CachePartitions[_currentStation].Count(c => c.Pod != null) / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_currentStation].Count; _currentCacheable = ((1 - cacheFill) * _config.WeightCacheFill + cacheUtilityValue * _config.WeightCacheUtility) / 2.0 > _config.PodCacheableThreshold; // Get best storage location _bestCandidateSelector.Recycle(); Waypoint bestStorageLocation = null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Update current candidate _currentStorageLocation = storageLocation; // Assess new candidate if (_bestCandidateSelector.Reassess()) { bestStorageLocation = _currentStorageLocation; } } // Return it return(bestStorageLocation); }
/// <summary> /// Initializes this manager. /// </summary> private void Init() { // Remember initialization _initialized = true; // Fetch additional settings if (Instance.Controller.PodStorageManager is TurnoverPodStorageManager) { _storageLocationClassRule = (Instance.ControllerConfig.PodStorageConfig as TurnoverPodStorageConfiguration).StorageLocationClassRule; } // Initialize ordered storage location list _storageLocationsOrdered = Instance.Waypoints .Where(wp => wp.PodStorageLocation) .OrderBy(wp => Instance.OutputStations.Min(o => { double value = 0; switch (_storageLocationClassRule) { case TurnoverPodStorageLocationClassRule.OutputStationDistanceEuclidean: value = Distances.CalculateEuclid(wp, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceManhattan: value = Distances.CalculateManhattan(wp, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestPath: value = Distances.CalculateShortestPathPodSafe(wp, o.Waypoint, Instance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestTime: value = Distances.CalculateShortestTimePathPodSafe(wp, o.Waypoint, Instance); break; default: throw new ArgumentException("Unknown storage location rule: " + _storageLocationClassRule); } return(value); })) .ToList(); // Allocate storage locations to classes _classStorageLocations = new List <Waypoint> [_classCount]; List <Waypoint> tempPodsOrdered = _storageLocationsOrdered.ToList(); for (int i = 0; i < _classCount; i++) { if (i < _classCount - 1) { _classStorageLocations[i] = tempPodsOrdered.Take((int)(_classBorders[i] * _storageLocationsOrdered.Count)).ToList(); tempPodsOrdered.RemoveRange(0, _classStorageLocations[i].Count); } else { _classStorageLocations[i] = tempPodsOrdered; } } // Log this reallocation Instance.LogInfo("Allocated storage locations - classes: " + string.Join(";", Enumerable.Range(0, _classCount).Select(c => c + ":" + _classStorageLocations[c].Count))); // Reallocate items and pods for the first time ReallocatePods(); // Reallocate items and pods for the first time ReallocateItems(); }
private void Init() { // --> Init shared component or ensure consistency Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // --> Init meta info _cacheTargetCounts = new VolatileIDDictionary <OutputStation, int>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, int>(s, (int)(_config.TargetFillCache * Instance.SharedControlElements.StoragePartitioner.CachePartitions[s].Count))).ToList()); _cachePodCounts = new VolatileIDDictionary <OutputStation, int>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, int>(s, 0)).ToList()); _storageLocationStations = new VolatileIDDictionary <Waypoint, OutputStation>( Instance.Waypoints.Select(w => new VolatileKeyValuePair <Waypoint, OutputStation>(w, null)).ToList()); foreach (var station in Instance.OutputStations) { foreach (var wp in Instance.SharedControlElements.StoragePartitioner.CachePartitions[station]) { _storageLocationStations[wp] = station; } } _cacheableInfoPerStation = new VolatileIDDictionary <OutputStation, bool>( Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, bool>(s, false)).ToList()); // --> Init move scoring List <Func <double> > scorers = new List <Func <double> >(); // First try to keep the move on the same tier, if desired (this heavily influences the possible moves) if (_config.PreferSameTierMoves) { scorers.Add(() => { return(_currentPod.Tier == _currentBot.Tier && _currentStorageLocation.Tier == _currentBot.Tier ? 1 : 0); }); } // Then decide about move type scorers.Add(() => { if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Drop-off -> Normal return(2); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Drop-off -> Cache return(2); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Cache -> Normal return(1); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.None && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Normal -> Cache return(1); } else { throw new InvalidOperationException("Forbidden move: " + _currentPod.Waypoint.InfoTagCache + " -> " + _currentStorageLocation.InfoTagCache); } }); // Then prefer moves keeping cache at targeted level scorers.Add(() => { // Move types should now be separated, i.e. if there is a drop-off clearing move there is no 'normal' move, respectively, if there is no drop-off clearing move there are only 'normal' moves if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Cache -> Normal return // Current cache level minus ((double)_cachePodCounts[_storageLocationStations[_currentPod.Waypoint]] / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_storageLocationStations[_currentPod.Waypoint]].Count - // targeted cache level _config.TargetFillCache); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.None && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Normal -> Cache return // Targeted cache level minus (_config.TargetFillCache - // current cache level (double)_cachePodCounts[_storageLocationStations[_currentStorageLocation]] / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_storageLocationStations[_currentStorageLocation]].Count); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.None) { // Drop-off -> Normal return (_cacheableAtAll ? 0 : 1); } else if (_currentPod.Waypoint.InfoTagCache == ZoneType.Dropoff && _currentStorageLocation.InfoTagCache == ZoneType.Cache) { // Drop-off -> Cache return (_cacheableInfoPerStation[_storageLocationStations[_currentStorageLocation]] ? 1 : 0); } else { throw new InvalidOperationException("Forbidden move: " + _currentPod.Waypoint.InfoTagCache + " -> " + _currentStorageLocation.InfoTagCache); } }); // Then prefer moves that make the greatest contribution in keeping the cache hot (depending on settings) scorers.Add(() => { if (_currentPod.Waypoint.InfoTagCache == ZoneType.Cache) // Cache clearing move { return(1 - Instance.ElementMetaInfoTracker.GetPodCombinedScore(_currentPod, _config.WeightSpeed, _config.WeightUtility)); } else if (_currentStorageLocation.InfoTagCache == ZoneType.Cache) // Cache filling move { return(Instance.ElementMetaInfoTracker.GetPodCombinedScore(_currentPod, _config.WeightSpeed, _config.WeightUtility)); } else { return(0); // Drop-off clearing -> highest priority } }); // Then prefer the shortest moves scorers.Add(() => { switch (_config.PodDisposeRule) { case CacheStorageLocationSelectionRule.Euclid: return(-Distances.CalculateEuclid(_currentPod.Waypoint, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.Manhattan: return(-Distances.CalculateManhattan(_currentPod.Waypoint, _currentStorageLocation, Instance.WrongTierPenaltyDistance)); case CacheStorageLocationSelectionRule.ShortestPath: return(-Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); case CacheStorageLocationSelectionRule.ShortestTime: return(-Distances.CalculateShortestTimePathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }); // Then use randomness scorers.Add(() => { return(Instance.Randomizer.NextDouble()); }); // Instantiate best candidate assessment _bestCandidateSelector = new BestCandidateSelector(true, scorers.ToArray()); }
/// <summary> /// Updates the allocation of robots to station, if stations became active or inactive in the meantime. /// </summary> public void UpdateRobotAllocation(bool firstAllocation) { // --> Allocate bots int pickBots = (int)(Instance.Bots.Count * _config.PickBotRatio); int replenishmentBots = Instance.Bots.Count - pickBots; // Check active stations List <InputStation> activeInputStations = Instance.InputStations.Where(s => firstAllocation || s.Active).ToList(); List <OutputStation> activeOutputStations = Instance.OutputStations.Where(s => firstAllocation || s.Active).ToList(); // Check whether something changed since last time IEnumerable <Circle> currentlyActiveStations = activeInputStations.Cast <Circle>().Concat(activeOutputStations); // If it's the first allocation, remember the 'last' active stations for next time if (_lastActiveStations == null) { _lastActiveStations = new HashSet <Circle>(activeInputStations.Cast <Circle>().Concat(activeOutputStations)); } // If nothing changed, don't do anything else if (currentlyActiveStations.All(s => _lastActiveStations.Contains(s)) && currentlyActiveStations.Count() == _lastActiveStations.Count) { return; } // If there is no active station at all, ignore the update else if (!activeInputStations.Any(s => s.Active) && !activeOutputStations.Any(s => s.Active)) { return; } // Update the new 'last' active stations for next time else { _lastActiveStations = new HashSet <Circle>(activeInputStations.Cast <Circle>().Concat(activeOutputStations)); } // If there is a 'fractional' bot, add it to the lower number if (replenishmentBots + pickBots < Instance.Bots.Count) { if (replenishmentBots < pickBots) { replenishmentBots += Instance.Bots.Count - replenishmentBots - pickBots; } else { pickBots += Instance.Bots.Count - replenishmentBots - pickBots; } } // Order active stations by their preference for assigning bots activeInputStations = // Order active input stations by their distance to the middle of their tier (prefer stations more far away, because others are more likely to share bots) activeInputStations.OrderByDescending(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)).ToList(); activeOutputStations = // Order active output stations by their distance to the middle of their tier (prefer stations more far away, because others are more likely to share bots) activeOutputStations.OrderByDescending(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)).ToList(); // Obtain goal numbers per station Dictionary <Circle, int> stationBotGoals = _stations.ToDictionary(k => k, v => 0); for (int i = 0; replenishmentBots > 0; i = (i + 1) % activeInputStations.Count) { if (stationBotGoals.ContainsKey(activeInputStations[i])) { stationBotGoals[activeInputStations[i]]++; } else { stationBotGoals[activeInputStations[i]] = 1; } replenishmentBots--; } for (int i = 0; pickBots > 0; i = (i + 1) % activeOutputStations.Count) { if (stationBotGoals.ContainsKey(activeOutputStations[i])) { stationBotGoals[activeOutputStations[i]]++; } else { stationBotGoals[activeOutputStations[i]] = 1; } pickBots--; } // Keep on reassigning until all stations meet their goal List <Tuple <Bot, Circle, Circle> > reassignments = null; Dictionary <Circle, int> previousAssignment = null; while (_stations.Any(s => stationBotGoals[s] != _stationBots[s].Count)) { // Remember current assignment counts if (previousAssignment == null) { previousAssignment = _stationBots.ToDictionary(k => k.Key, v => v.Value.Count); } // Get next switching partners Bot bestBot = null; Circle receivingStation = null; double bestValue = double.PositiveInfinity; bool unassignedBot = true; // Check all station not yet at their goal foreach (var station in _stations.Where(s => _stationBots[s].Count < stationBotGoals[s])) { // Check all bots that are assigned to a station with an overflow of bots foreach (var bot in _unassignedBots.Concat(_stationBots.Where(s => _stationBots[s.Key].Count > stationBotGoals[s.Key]).SelectMany(s => s.Value))) { // See whether the bot is nearer to the station than the previous ones double distance = (bot.TargetWaypoint != null ? bot.TargetWaypoint.GetDistance(station) : Distances.CalculateEuclid(bot, station, Instance.WrongTierPenaltyDistance)); if (distance < bestValue) { // Set new best pair bestValue = distance; bestBot = bot; receivingStation = station; unassignedBot = _unassignedBots.Contains(bot); } } } // Store the switch if (reassignments != null) { reassignments.Add(new Tuple <Bot, Circle, Circle>(bestBot, unassignedBot ? null : _botStations[bestBot], receivingStation)); } else { reassignments = new List <Tuple <Bot, Circle, Circle> >() { new Tuple <Bot, Circle, Circle>(bestBot, unassignedBot ? null : _botStations[bestBot], receivingStation) } }; // Perform the switch if (unassignedBot) { _unassignedBots.Remove(bestBot); } else { _stationBots[_botStations[bestBot]].Remove(bestBot); } _botStations[bestBot] = receivingStation; _stationBots[receivingStation].Add(bestBot); } // Log reassignments if (reassignments != null) { Instance.LogVerbose("Reassigning " + reassignments.Count + " bots:"); Instance.LogVerbose("ID: " + string.Join(",", Instance.InputStations.Select(s => "I" + s.ID).Concat(Instance.OutputStations.Select(s => "O" + s.ID)).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Work: " + string.Join(",", Instance.InputStations.Select(s => (s.ItemBundles.Any() ? s.ItemBundles.Count().ToString() : " ")).Concat( Instance.OutputStations.Select(s => (s.AssignedOrders.Any() ? s.AssignedOrders.Count().ToString() : " "))).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Bots: " + string.Join(",", Instance.InputStations.Select(s => _stationBots[s].Count.ToString()).Concat( Instance.OutputStations.Select(s => _stationBots[s].Count.ToString())).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Was: " + string.Join(",", Instance.InputStations.Select(s => previousAssignment[s].ToString()).Concat( Instance.OutputStations.Select(s => previousAssignment[s].ToString())).Select(s => s.PadLeft(3)))); } }
/// <summary> /// Creates a new instance of this controller. /// </summary> /// <param name="instance">The instance this controller belongs to.</param> public BalancedBotManager(Instance instance) : base(instance) { // Save config and instance Instance = instance; _config = instance.ControllerConfig.TaskAllocationConfig as BalancedTaskAllocationConfiguration; // Build list of bots used for station work _bots = instance.Bots // Take all bots that shall be used for the dynamic station assignment .Take((int)Math.Ceiling((_config.WeightInputStations + _config.WeightOutputStations) / (_config.WeightInputStations + _config.WeightOutputStations + _config.WeightRepositioning) * instance.Bots.Count)) .ToList(); // Use all remaining bots for repositioning _repositioningBots = instance.Bots.Except(_bots).ToHashSet(); // Keep expended search radius within limits, if same tier is preferred if (_config.ExtendedSearchRadius > instance.WrongTierPenaltyDistance && _config.PreferSameTier) { _config.ExtendedSearchRadius = instance.WrongTierPenaltyDistance; } _stations = instance.InputStations.Cast <Circle>().Concat(instance.OutputStations).ToList(); _singleStationLists = _stations.ToDictionary(k => k, v => new List <Circle>() { v }); _allStationsList = _stations.ToDictionary(k => k, v => _stations.OrderBy(s => Distances.CalculateEuclid(v, s, instance.WrongTierPenaltyDistance)).ThenByDescending(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)).ToList()); _stationBots = _stations.ToDictionary(k => k, v => new HashSet <Bot>()); foreach (var bot in _bots) { _unassignedBots.Add(bot); } // Perform first allocation ReallocateBots(true); // Keep track of the stations the bots are currently working for _workerStations = instance.Bots.ToDictionary(k => k, v => _botStations.ContainsKey(v) ? _botStations[v] : null); _stationWorkerCount = _stations.ToDictionary(k => k, v => _workerStations.Count(s => s.Value == v)); // Do not immediately reallocate again _nextReallocation = instance.Controller != null ? instance.Controller.CurrentTime + _config.BotReallocationTimeout : _config.BotReallocationTimeout; }
/// <summary> /// Generates a complete configuration for the simple item generator. /// </summary> /// <param name="preConfig">The pre-configuration defining characteristics of the actual configuration.</param> /// <returns>The complete configuration.</returns> public static SimpleItemGeneratorConfiguration GenerateSimpleItemConfiguration(SimpleItemGeneratorPreConfiguration preConfig) { // Init SimpleItemGeneratorConfiguration config = new SimpleItemGeneratorConfiguration() { DefaultWeight = preConfig.DefaultWeight, DefaultCoWeight = preConfig.DefaultCoWeight, ProbToUseCoWeight = preConfig.ProbToUseCoWeight }; RandomizerSimple randomizer = new RandomizerSimple(0); List <SimpleItemDescription> itemDescriptions = new List <SimpleItemDescription>(); List <Tuple <SimpleItemDescription, double> > itemDescriptionWeights = new List <Tuple <SimpleItemDescription, double> >(); List <Tuple <SimpleItemDescription, SimpleItemDescription, double> > itemDescriptionCoWeights = new List <Tuple <SimpleItemDescription, SimpleItemDescription, double> >(); // Add comment config.Description = string.Join(",", typeof(SimpleItemGeneratorPreConfiguration).GetFields().Select(f => { string fieldValue; if (f.GetValue(preConfig) is double) { fieldValue = ((double)f.GetValue(preConfig)).ToString(IOConstants.FORMATTER); } else { fieldValue = f.GetValue(preConfig).ToString(); } return(f.Name + "=" + fieldValue); })); // Generate a set of item-descriptions for (int i = 0; i < preConfig.ItemDescriptionCount; i++) { // Generate next item SimpleItemDescription description = new SimpleItemDescription(null) { ID = i }; // Randomly weight the item double itemDescriptionWeight = 0; switch (preConfig.WeightDistributionType) { case ItemDescriptionWeightDistributionType.Normal: itemDescriptionWeight = randomizer.NextNormalDouble(preConfig.ItemWeightMu, preConfig.ItemWeightSigma, preConfig.ItemWeightLB, preConfig.ItemWeightUB); break; case ItemDescriptionWeightDistributionType.Uniform: itemDescriptionWeight = randomizer.NextDouble(preConfig.ItemWeightLB, preConfig.ItemWeightUB); break; default: throw new ArgumentException("Unknown distribution: " + preConfig.WeightDistributionType); } description.Weight = itemDescriptionWeight; // Randomly determine bundle size of the item if (preConfig.SupplyBundleSize) { int itemDescriptionBundleSize = 0; switch (preConfig.BundleSizeDistributionType) { case ItemDescriptionBundleSizeDistributionType.Normal: itemDescriptionBundleSize = randomizer.NextNormalInt(preConfig.BundleSizeMu, preConfig.BundleSizeSigma, preConfig.BundleSizeLB, preConfig.BundleSizeUB); break; case ItemDescriptionBundleSizeDistributionType.Uniform: itemDescriptionBundleSize = randomizer.NextInt(preConfig.BundleSizeLB, preConfig.BundleSizeUB + 1); break; default: throw new ArgumentException("Unknown distribution: " + preConfig.BundleSizeDistributionType); } description.BundleSize = itemDescriptionBundleSize; } // Add a random hue value to distinguish the item from others description.Hue = randomizer.NextDouble(360); // Add it itemDescriptions.Add(description); // Set a weight for the probability of the item double weight = 0; switch (preConfig.ProbWeightDistributionType) { case ItemDescriptionProbabilityWeightDistributionType.Constant: weight = preConfig.ProbabilityWeightConstant; break; case ItemDescriptionProbabilityWeightDistributionType.Uniform: weight = randomizer.NextDouble(preConfig.ProbabilityWeightUniformMin, preConfig.ProbabilityWeightUniformMax); break; case ItemDescriptionProbabilityWeightDistributionType.Normal: weight = randomizer.NextNormalDouble(preConfig.ProbabilityWeightNormalMu, preConfig.ProbabilityWeightNormalSigma, preConfig.ProbabilityWeightLB, preConfig.ProbabilityWeightUB); break; case ItemDescriptionProbabilityWeightDistributionType.Exponential: weight = randomizer.NextExponentialDouble(preConfig.ProbabilityWeightExpLambda, preConfig.ProbabilityWeightLB, preConfig.ProbabilityWeightUB); break; case ItemDescriptionProbabilityWeightDistributionType.Gamma: weight = randomizer.NextGammaDouble(preConfig.ProbabilityWeightGammaK, preConfig.ProbabilityWeightGammaTheta, preConfig.ProbabilityWeightLB, preConfig.ProbabilityWeightUB); break; default: throw new ArgumentException("Unknown distribution: " + preConfig.ProbWeightDistributionType); } itemDescriptionWeights.Add(new Tuple <SimpleItemDescription, double>(description, weight)); } // Equally distribute items over two-dimensional space Dictionary <SimpleItemDescription, Tuple <double, double> > itemDescriptionPosition = new Dictionary <SimpleItemDescription, Tuple <double, double> >(); foreach (var description in itemDescriptions) { itemDescriptionPosition[description] = new Tuple <double, double>(randomizer.NextDouble(), randomizer.NextDouble()); } // Plot the distribution for reference GnuPlotter.Plot2DPoints( "itemdistribution", new List <Tuple <string, IEnumerable <Tuple <double, double> > > >() { new Tuple <string, IEnumerable <Tuple <double, double> > >("Item locations in 2D", itemDescriptionPosition.Values) }, "item distribution for co-probability emulation"); // Set conditional weights double maxDistance = Distances.CalculateEuclid(0, 0, 1, 1); foreach (var description in itemDescriptions.OrderBy(d => randomizer.NextDouble()).Take((int)(itemDescriptions.Count * preConfig.GivenCoWeights))) { foreach (var otherDescription in itemDescriptions.OrderBy(d => randomizer.NextDouble()).Take((int)(itemDescriptions.Count * preConfig.GivenCoWeights))) { itemDescriptionCoWeights.Add(new Tuple <SimpleItemDescription, SimpleItemDescription, double>( description, otherDescription, maxDistance - Distances.CalculateEuclid( itemDescriptionPosition[description].Item1, itemDescriptionPosition[description].Item2, itemDescriptionPosition[otherDescription].Item1, itemDescriptionPosition[otherDescription].Item2))); } } // Submit all config.ItemDescriptions = itemDescriptions.Select(d => new Skvp <int, double>() { Key = d.ID, Value = d.Hue }).ToList(); config.ItemDescriptionWeights = itemDescriptions.Select(d => new Skvp <int, double>() { Key = d.ID, Value = d.Weight }).ToList(); if (preConfig.SupplyBundleSize) { config.ItemDescriptionBundleSizes = itemDescriptions.Select(d => new Skvp <int, int>() { Key = d.ID, Value = d.BundleSize }).ToList(); } config.ItemWeights = itemDescriptionWeights.Select(d => new Skvp <int, double>() { Key = d.Item1.ID, Value = d.Item2 }).ToList(); config.ItemCoWeights = itemDescriptionCoWeights.Select(d => new Skkvt <int, int, double>() { Key1 = d.Item1.ID, Key2 = d.Item2.ID, Value = d.Item3 }).ToList(); // Name it config.Name = config.GetMetaInfoBasedName(); // Return it return(config); }
/// <summary> /// This is called to decide about potentially pending bundles. /// 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 DecideAboutPendingBundles() { if (_config.BreakBatches) { // Go through list of not assigned bundles for (int i = 0; i < _itemBundles.Count; i++) { // Check which pod was used to store the bundle ItemBundle bundle = _itemBundles[i]; Pod podForBundle = _bundleToPod[bundle]; // See whether we already have a station in memory for this pod InputStation chosenStation; if (_lastChosenStations.ContainsKey(podForBundle)) { // See whether we can assign the new bundle to that station if (_lastChosenStations[podForBundle].FitsForReservation(bundle)) { chosenStation = _lastChosenStations[podForBundle]; } else { // The bundle won't fit the station - try a station close by chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderBy(s => Distances.CalculateEuclid(_lastChosenStations[podForBundle], s, Instance.WrongTierPenaltyDistance)) // Start with the nearest station .FirstOrDefault(); // Return the first one or null if none available } } else { // We don't know this pod - select a new station switch (_config.FirstStationRule) { case SamePodFirstStationRule.Emptiest: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderBy(s => (s.CapacityInUse + s.CapacityReserved) / s.Capacity) // Pick the emptiest one .FirstOrDefault(); // Return the first one or null if none available break; case SamePodFirstStationRule.Fullest: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderByDescending(s => (s.CapacityInUse + s.CapacityReserved) / s.Capacity) // Pick the fullest one .FirstOrDefault(); // Return the first one or null if none available break; case SamePodFirstStationRule.LeastBusy: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderBy(s => s.ItemBundles.Count()) // Pick the one with the fewest bundles .FirstOrDefault(); // Return the first one or null if none available break; case SamePodFirstStationRule.MostBusy: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderByDescending(s => s.ItemBundles.Count()) // Pick the one with the most bundles .FirstOrDefault(); // Return the first one or null if none available break; case SamePodFirstStationRule.Random: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderBy(s => Instance.Randomizer.NextDouble()) // Pick a random one .FirstOrDefault(); // Return the first one or null if none available break; case SamePodFirstStationRule.DistanceEuclid: chosenStation = Instance.InputStations .Where(s => s.Active && s.FitsForReservation(bundle)) // There has to be sufficient capacity left at the station and the station needs to be active .OrderBy(s => Distances.CalculateEuclid(podForBundle, s, Instance.WrongTierPenaltyDistance)) // Pick the nearest one .FirstOrDefault(); // Return the first one or null if none available break; default: throw new ArgumentException("Unknown first station rule: " + _config.FirstStationRule); } } // If we found a station, assign the bundle to it if (chosenStation != null) { AddToReadyList(bundle, chosenStation); _bundleToPod.Remove(bundle); _itemBundles.RemoveAt(0); i--; _lastChosenStations[podForBundle] = chosenStation; } } } else { // Assign batches in the order they arrive in List <Pod> removedBatches = null; foreach (var batch in _podBundles.OrderBy(p => _waitingTime[p.Key])) { double batchSize = batch.Value.Sum(b => b.BundleWeight); // Choose a suitable station InputStation chosenStation = Instance.InputStations // Only active stations where the complete bundle fits .Where(s => s.Active && batchSize <= s.RemainingCapacity) // Only stations that are located on the same tier as the pod (if desired) .Where(s => s.Tier == batch.Key.Tier) // Find the best station according to the chosen rule .ArgMin(s => { switch (_config.FirstStationRule) { case SamePodFirstStationRule.Emptiest: return((s.CapacityInUse + s.CapacityReserved) / s.Capacity); case SamePodFirstStationRule.Fullest: return(1 - ((s.CapacityInUse + s.CapacityReserved) / s.Capacity)); case SamePodFirstStationRule.LeastBusy: return(s.ItemBundles.Count()); case SamePodFirstStationRule.MostBusy: return(-s.ItemBundles.Count()); case SamePodFirstStationRule.Random: return(Instance.Randomizer.NextDouble()); case SamePodFirstStationRule.DistanceEuclid: return(Distances.CalculateEuclid(batch.Key, s, Instance.WrongTierPenaltyDistance)); default: throw new ArgumentException("Unknown rule: " + _config.FirstStationRule); } }); // Check whether there was a suitable station at all if (chosenStation != null) { // Submit the decision foreach (var bundle in batch.Value) { AddToReadyList(bundle, chosenStation); } // Clean up _lastChosenStations[batch.Key] = chosenStation; if (removedBatches == null) { removedBatches = new List <Pod>(); } removedBatches.Add(batch.Key); } else { // If FCFS applies, we cannot assign further batches until this one gets assigned if (_config.FCFS) { break; } } } // Actually remove the batches if (removedBatches != null) { foreach (var batch in removedBatches) { _podBundles.Remove(batch); } } } }
/// <summary> /// Reallocates the pods to different classes. /// </summary> private void ReallocatePods() { // Sort pods by their current average frequency _podsOrdered = Instance.Pods .OrderBy(p => p.ItemDescriptionsContained.Any() ? p.ItemDescriptionsContained.Average(item => item.OrderCount) : 0) .ThenBy(p => Instance.OutputStations.Min(o => { double value = 0; switch (_storageLocationClassRule) { case TurnoverPodStorageLocationClassRule.OutputStationDistanceEuclidean: value = Distances.CalculateEuclid(p, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceManhattan: value = Distances.CalculateManhattan(p, o, Instance.WrongTierPenaltyDistance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestPath: value = Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(p.Tier, p.X, p.Y), o.Waypoint, Instance); break; case TurnoverPodStorageLocationClassRule.OutputStationDistanceShortestTime: value = Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(p.Tier, p.X, p.Y), o.Waypoint, Instance); break; default: throw new ArgumentException("Unknown storage location rule: " + _storageLocationClassRule); } return(value); })) .ToList(); // Determine the shares of the capacity of the storage location classes double overallCapacity = _classStorageLocations.Sum(l => l.Count); Dictionary <int, double> classStorageCapacityShares = new Dictionary <int, double>(); for (int i = 0; i < _classCount; i++) { classStorageCapacityShares[i] = _classStorageLocations[i].Count / overallCapacity; } // Group pods to classes _podClasses = new Dictionary <Pod, int>(); _classStoragePods = Enumerable.Range(0, _classCount).Select(i => new List <Pod>()).ToArray(); int currentClass = 0; int podCount = 0; double aggregatedRelativeCapacity = classStorageCapacityShares[0]; foreach (var pod in _podsOrdered) { podCount++; // See whether the pod still fits in the current storage location area if ((double)podCount / _podsOrdered.Count > aggregatedRelativeCapacity) { // Update virtual capacity currentClass++; if (currentClass < _classCount) { aggregatedRelativeCapacity += classStorageCapacityShares[currentClass]; } } // Assign the pod to the class _podClasses[pod] = currentClass; _classStoragePods[currentClass].Add(pod); // Mark the pods pod.InfoTagPodStorageType = (_classCount - currentClass - 1.0) / (_classCount - 1.0); pod.InfoTagPodStorageInfo = "Class" + currentClass; } // Log this reallocation Instance.LogInfo("Reallocated pods - classes: " + string.Join(";", Enumerable.Range(0, _classCount).Select(c => c + ":" + _classStoragePods[c].Count))); // Mark this re-allocation _lastReallocation = Instance.Controller.CurrentTime; }
/// <summary> /// Reallocates the bots between the stations. /// </summary> /// <param name="overrideActivity">Indicates whether the stations activity will be ignored.</param> private void ReallocateBots(bool overrideActivity) { // Check active stations List <InputStation> activeInputStations = Instance.InputStations.Where(s => overrideActivity || s.ItemBundles.Any()).ToList(); List <OutputStation> activeOutputStations = Instance.OutputStations.Where(s => overrideActivity || s.AssignedOrders.Any()).ToList(); // Check whether something changed since last time IEnumerable <Circle> currentlyActiveStations = activeInputStations.Cast <Circle>().Concat(activeOutputStations); if (_lastActiveStations == null) { _lastActiveStations = new HashSet <Circle>(activeInputStations.Cast <Circle>().Concat(activeOutputStations)); } else if (currentlyActiveStations.All(s => _lastActiveStations.Contains(s)) && currentlyActiveStations.Count() == _lastActiveStations.Count) { return; } else { _lastActiveStations = new HashSet <Circle>(activeInputStations.Cast <Circle>().Concat(activeOutputStations)); } // Defining stations as active when there are available requests is too volatile, but maybe consider the request count somehow in the future? //List<InputStation> activeInputStations = Instance.ResourceManager.AvailableStoreRequests.Select(r => r.Station).Distinct().ToList(); //List<OutputStation> activeOutputStations = Instance.ResourceManager.AvailableExtractRequests.Select(r => r.Station).Distinct().ToList(); // Get count if input / output bots double activeWeightInputStations = _config.WeightInputStations * ((double)activeInputStations.Count / Instance.InputStations.Count); double activeWeightOutputStations = _config.WeightOutputStations * ((double)activeOutputStations.Count / Instance.OutputStations.Count); // Handle the extreme case of no active stations if (activeWeightInputStations == 0 && activeWeightOutputStations == 0) { // Set all bots to unassigned and quit foreach (var bot in _bots) { _botStations[bot] = null; _unassignedBots.Add(bot); } foreach (var station in _stations) { _stationBots[station].Clear(); } return; } // Divide input and output station bots int inputStationBots = Convert.ToInt32(Math.Floor(_bots.Count * (activeWeightInputStations / (activeWeightInputStations + activeWeightOutputStations)))); int outputStationBots = Convert.ToInt32(Math.Floor(_bots.Count * (activeWeightOutputStations / (activeWeightInputStations + activeWeightOutputStations)))); // If there is a 'fractional' bot, add it to the lower number if (inputStationBots + outputStationBots < _bots.Count) { if (inputStationBots < outputStationBots) { inputStationBots += _bots.Count - inputStationBots - outputStationBots; } else { outputStationBots += _bots.Count - inputStationBots - outputStationBots; } } // Order active stations by their preference for assigning bots activeInputStations = // Order active input stations by their distance to the middle of their tier (prefer stations more far away, because others are more likely to share bots) activeInputStations.OrderByDescending(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)).ToList(); activeOutputStations = // Order active output stations by their distance to the middle of their tier (prefer stations more far away, because others are more likely to share bots) activeOutputStations.OrderByDescending(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)).ToList(); // Obtain goal numbers per station Dictionary <Circle, int> stationBotGoals = _stations.ToDictionary(k => k, v => 0); for (int i = 0; inputStationBots > 0; i = (i + 1) % activeInputStations.Count) { if (stationBotGoals.ContainsKey(activeInputStations[i])) { stationBotGoals[activeInputStations[i]]++; } else { stationBotGoals[activeInputStations[i]] = 1; } inputStationBots--; } for (int i = 0; outputStationBots > 0; i = (i + 1) % activeOutputStations.Count) { if (stationBotGoals.ContainsKey(activeOutputStations[i])) { stationBotGoals[activeOutputStations[i]]++; } else { stationBotGoals[activeOutputStations[i]] = 1; } outputStationBots--; } // Limit bots per station int overflowBots = 0; foreach (var station in _stations) { if (stationBotGoals[station] > _config.BotsPerStationLimit) { overflowBots += stationBotGoals[station] - _config.BotsPerStationLimit; stationBotGoals[station] = _config.BotsPerStationLimit; } } // Distribute overflow bots across other stations, if possible while (overflowBots > 0 && stationBotGoals.Any(s => s.Value < _config.BotsPerStationLimit)) { // Select a station at the border of its tier (stations nearer to the middle should share bots more easily) Circle receivingStation = _stations.Where(s => stationBotGoals[s] < _config.BotsPerStationLimit).ArgMax(s => s.GetDistance(s.Tier.Length / 2.0, s.Tier.Width / 2.0)); stationBotGoals[receivingStation]++; overflowBots--; } // Keep on reassigning until all stations meet their goal List <Tuple <Bot, Circle, Circle> > reassignments = null; Dictionary <Circle, int> previousAssignment = null; while (_stations.Any(s => stationBotGoals[s] != _stationBots[s].Count)) { // Remember current assignment counts if (previousAssignment == null) { previousAssignment = _stationBots.ToDictionary(k => k.Key, v => v.Value.Count); } // Get next switching partners Bot bestBot = null; Circle receivingStation = null; double bestValue = double.PositiveInfinity; bool unassignedBot = true; // Check all station not yet at their goal foreach (var station in _stations.Where(s => _stationBots[s].Count < stationBotGoals[s])) { // Check all unassigned bots or bots that are assigned to a station with an overflow of bots foreach (var bot in _unassignedBots.Concat(_stationBots.Where(s => _stationBots[s.Key].Count > stationBotGoals[s.Key]).SelectMany(s => s.Value))) { // See whether the bot is nearer to the station than the previous ones double distance = (bot.TargetWaypoint != null ? bot.TargetWaypoint.GetDistance(station) : Distances.CalculateEuclid(bot, station, Instance.WrongTierPenaltyDistance)); if (distance < bestValue) { // Set new best pair bestValue = distance; bestBot = bot; receivingStation = station; unassignedBot = _unassignedBots.Contains(bot); } } } // Store the switch if (reassignments != null) { reassignments.Add(new Tuple <Bot, Circle, Circle>(bestBot, unassignedBot ? null : _botStations[bestBot], receivingStation)); } else { reassignments = new List <Tuple <Bot, Circle, Circle> >() { new Tuple <Bot, Circle, Circle>(bestBot, unassignedBot ? null : _botStations[bestBot], receivingStation) } }; // Perform the switch if (unassignedBot) { _unassignedBots.Remove(bestBot); } else { _stationBots[_botStations[bestBot]].Remove(bestBot); } _botStations[bestBot] = receivingStation; _stationBots[receivingStation].Add(bestBot); } // Log reassignments if (reassignments != null) { Instance.LogVerbose("Reassigning " + reassignments.Count + " bots:"); Instance.LogVerbose("ID: " + string.Join(",", Instance.InputStations.Select(s => "I" + s.ID).Concat(Instance.OutputStations.Select(s => "O" + s.ID)).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Work: " + string.Join(",", Instance.InputStations.Select(s => (s.ItemBundles.Any() ? s.ItemBundles.Count().ToString() : " ")).Concat( Instance.OutputStations.Select(s => (s.AssignedOrders.Any() ? s.AssignedOrders.Count().ToString() : " "))).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Bots: " + string.Join(",", Instance.InputStations.Select(s => _stationBots[s].Count.ToString()).Concat( Instance.OutputStations.Select(s => _stationBots[s].Count.ToString())).Select(s => s.PadLeft(3)))); Instance.LogVerbose("Was: " + string.Join(",", Instance.InputStations.Select(s => previousAssignment[s].ToString()).Concat( Instance.OutputStations.Select(s => previousAssignment[s].ToString())).Select(s => s.PadLeft(3)))); } }
/// <summary> /// Chooses the storage location to use for the given pod. /// </summary> /// <param name="pod">The pod to store.</param> /// <returns>The storage location to use for the pod.</returns> private Waypoint ChooseStorageLocation(Pod pod) { // Get the storage class the pod should end up in int desiredStorageClass = _classManager.DetermineStorageClass(pod); // Try to allocate the pod to its storage class - if not possible try neighboring classes int currentClassTriedLow = desiredStorageClass; int currentClassTriedHigh = desiredStorageClass; Waypoint chosenStorageLocation = null; while (true) { // Try the less frequent class first if (currentClassTriedLow < _classManager.ClassCount) { chosenStorageLocation = _classManager.GetClassStorageLocations(currentClassTriedLow) .Where(wp => !Instance.ResourceManager.IsStorageLocationClaimed(wp)) // Only use not occupied ones .OrderBy(wp => { switch (_config.PodDisposeRule) { case TurnoverPodStorageLocationDisposeRule.NearestEuclid: return(Distances.CalculateEuclid(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestManhattan: return(Distances.CalculateManhattan(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestPath: return(Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestTime: return(Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.OStationNearestEuclid: return(Instance.OutputStations.Min(s => Distances.CalculateEuclid(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestManhattan: return(Instance.OutputStations.Min(s => Distances.CalculateManhattan(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestPath: return(Instance.OutputStations.Min(s => Distances.CalculateShortestPathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestTime: return(Instance.OutputStations.Min(s => Distances.CalculateShortestTimePathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.Random: return(wp.Instance.Randomizer.NextDouble()); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }) // Order the remaining ones by the given rule .FirstOrDefault(); // Use the first one } // Check whether we found a suitable pod of this class if (chosenStorageLocation != null) { break; } // Try the higher frequent class next if (currentClassTriedHigh >= 0 && currentClassTriedHigh != currentClassTriedLow) { chosenStorageLocation = _classManager.GetClassStorageLocations(currentClassTriedHigh) .Where(wp => !Instance.ResourceManager.IsStorageLocationClaimed(wp)) // Only use not occupied ones .OrderBy(wp => { switch (_config.PodDisposeRule) { case TurnoverPodStorageLocationDisposeRule.NearestEuclid: return(Distances.CalculateEuclid(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestManhattan: return(Distances.CalculateManhattan(wp, pod, Instance.WrongTierPenaltyDistance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestPath: return(Distances.CalculateShortestPathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.NearestShortestTime: return(Distances.CalculateShortestTimePathPodSafe(Instance.WaypointGraph.GetClosestWaypoint(pod.Tier, pod.X, pod.Y), wp, Instance)); case TurnoverPodStorageLocationDisposeRule.OStationNearestEuclid: return(Instance.OutputStations.Min(s => Distances.CalculateEuclid(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestManhattan: return(Instance.OutputStations.Min(s => Distances.CalculateManhattan(wp, s, Instance.WrongTierPenaltyDistance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestPath: return(Instance.OutputStations.Min(s => Distances.CalculateShortestPathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.OStationNearestShortestTime: return(Instance.OutputStations.Min(s => Distances.CalculateShortestTimePathPodSafe(wp, s.Waypoint, Instance))); case TurnoverPodStorageLocationDisposeRule.Random: return(wp.Instance.Randomizer.NextDouble()); default: throw new ArgumentException("Unknown pod dispose rule: " + _config.PodDisposeRule); } }) // Order the remaining ones by the given rule .FirstOrDefault(); // Use the first one } // Check whether we found a suitable pod of this class if (chosenStorageLocation != null) { break; } // Update the class indeces to check next currentClassTriedLow++; currentClassTriedHigh--; // Check index correctness if (currentClassTriedHigh < 0 && currentClassTriedLow >= _classManager.ClassCount) { throw new InvalidOperationException("There was no storage location available!"); } } // Return the chosen one return(chosenStorageLocation); }