Exemple #1
0
        private void AddTimeWindowConstrains(WorkData data, RoutingIndexManager manager, RoutingModel routing)
        {
            // Create and register a transit callback
            int transitCallbackIndex = routing.RegisterTransitCallback(
                (long fromIndex, long toIndex) =>
            {
                int fromNode = manager.IndexToNode(fromIndex);
                int toNode   = manager.IndexToNode(toIndex);
                return(data.DurationMatrix[fromNode, toNode] + data.ServiceTimes[fromNode]);
            });

            routing.AddDimension(transitCallbackIndex, 120, MAX_VEHICLE_ROUTING_TIME, false, "Time");

            // Add time window constraints for each location except depot
            RoutingDimension timeDimension = routing.GetDimensionOrDie("Time");

            for (int i = 1; i < data.TimeWindows.GetLength(0); ++i)
            {
                long index = manager.NodeToIndex(i);
                timeDimension.CumulVar(index).SetRange(data.TimeWindows[i][0], data.TimeWindows[i][1]);
            }
            // Add time window constraints for each vehicle start node
            for (int i = 0; i < data.VehiclesAmount; ++i)
            {
                long index = routing.Start(i);
                timeDimension.CumulVar(index).SetRange(data.TimeWindows[0][0], data.TimeWindows[0][1]);

                routing.AddVariableMinimizedByFinalizer(timeDimension.CumulVar(routing.Start(i)));
                routing.AddVariableMinimizedByFinalizer(timeDimension.CumulVar(routing.End(i)));
            }
        }
        private IList <Summary> GetSummaries(FileInput input, RoutingIndexManager manager, RoutingModel routing, Assignment solution)
        {
            int vehiclesAmount = input.Vehicles.Count();
            IDictionary <int, string> vehiclesNames = input.Vehicles.ToDictionary(k => k.Id - 1, v => v.Name);

            List <Summary> summaries = new List <Summary>();

            RoutingDimension capacityDimension = routing.GetDimensionOrDie("Capacity");
            RoutingDimension timeDimension     = routing.GetMutableDimension("Time");

            for (int vehicleIndex = 0; vehicleIndex < vehiclesAmount; ++vehicleIndex)
            {
                long load           = 0;
                long routeDistance  = 0;
                long routeIndex     = routing.Start(vehicleIndex);
                int  numberOfVisits = 0;

                while (routing.IsEnd(routeIndex) == false)
                {
                    long previousVehicleIndex = routeIndex;

                    load          += solution.Value(capacityDimension.CumulVar(routeIndex));
                    routeDistance += routing.GetArcCostForVehicle(previousVehicleIndex, routeIndex, 0);
                    ++numberOfVisits;

                    routeIndex = solution.Value(routing.NextVar(routeIndex));
                }

                load += solution.Value(capacityDimension.CumulVar(routeIndex));

                long vehicleSpentTime = solution.Min(timeDimension.CumulVar(routeIndex));
                summaries.Add(new Summary
                {
                    VehicleName    = vehiclesNames[vehicleIndex],
                    Load           = (int)load,
                    Distance       = (int)routeDistance,
                    Time           = (int)vehicleSpentTime,
                    NumberOfVisits = numberOfVisits
                });
            }

            return(summaries);
        }
Exemple #3
0
        private static void AddTimeDimension(FeatureCollection input, RoutingModel routing, NodeEvaluator2 evaluator)
        {
            var speed = 10 * 1000 / 3600; // 10 km/h

            routing.AddDimension(new ServiceTimeEvaluator(evaluator, speed), 24 * 3600, 24 * 3600, true, "Time");
            var timeDimension = routing.GetDimensionOrDie("Time");
            var timeWindow    = 3600 * 3;

            for (int i = 0; i < input.Features.Count; i++)
            {
                var feature = input.Features[i];
                if (feature.Properties.ContainsKey("start"))
                {
                    int start = Convert.ToInt32(feature.Properties["start"]),
                        end   = start + timeWindow;
                    timeDimension.CumulVar(routing.NodeToIndex(i)).SetRange(start, end);
                }
            }
        }
        private IList <Totals> GetTotals(FileInput input, RoutingIndexManager manager, RoutingModel routing, Assignment solution)
        {
            int vehiclesAmount = input.Vehicles.Count();

            List <Totals> totals = new List <Totals>();

            RoutingDimension capacityDimension = routing.GetDimensionOrDie("Capacity");
            RoutingDimension timeDimension     = routing.GetMutableDimension("Time");

            long totalLoad     = 0;
            long totalTime     = 0;
            long totalDistance = 0;

            for (int vehicleIndex = 0; vehicleIndex < vehiclesAmount; ++vehicleIndex)
            {
                long load          = 0;
                long routeDistance = 0;
                long routeIndex    = routing.Start(vehicleIndex);

                while (routing.IsEnd(routeIndex) == false)
                {
                    long previousRouteIndex = routeIndex;

                    load          += solution.Value(capacityDimension.CumulVar(routeIndex));
                    routeDistance += routing.GetArcCostForVehicle(previousRouteIndex, routeIndex, 0);

                    routeIndex = solution.Value(routing.NextVar(routeIndex));
                }

                load += solution.Value(capacityDimension.CumulVar(routeIndex));

                totalLoad     += load;
                totalTime     += solution.Min(timeDimension.CumulVar(routeIndex));
                totalDistance += routeDistance;
            }

            totals.Add(new Totals {
                Load = (int)totalLoad, Distance = (int)totalDistance, Time = (int)totalTime
            });

            return(totals);
        }
        private IList <Itineraries> GetItineraries(FileInput input, RoutingIndexManager manager, RoutingModel routing, Assignment solution)
        {
            int      vehiclesAmount = input.Vehicles.Count();
            DateTime minDate        = input.Locations.Select(l => l.From).Min();
            IDictionary <int, string> vehiclesNames = input.Vehicles.ToDictionary(k => k.Id - 1, v => v.Name);

            List <Itineraries> itineraries = new List <Itineraries>();

            RoutingDimension capacityDimension = routing.GetDimensionOrDie("Capacity");
            RoutingDimension timeDimension     = routing.GetMutableDimension("Time");

            for (int vehicleIndex = 0; vehicleIndex < vehiclesAmount; ++vehicleIndex)
            {
                long load       = 0;
                long routeIndex = routing.Start(vehicleIndex);

                while (routing.IsEnd(routeIndex) == false)
                {
                    long previousRouteIndex = routeIndex;

                    load += solution.Value(capacityDimension.CumulVar(routeIndex));

                    long   distance = routing.GetArcCostForVehicle(previousRouteIndex, routeIndex, 0);
                    IntVar timeVar  = timeDimension.CumulVar(routeIndex);

                    itineraries.Add(new Itineraries
                    {
                        VehicleName = vehiclesNames[vehicleIndex],
                        Load        = (int)load,
                        Distance    = (int)distance,
                        From        = minDate.AddMinutes(solution.Min(timeVar)),
                        To          = minDate.AddMinutes(solution.Max(timeVar))
                    });

                    routeIndex = solution.Value(routing.NextVar(routeIndex));
                }
            }

            return(itineraries);
        }
Exemple #6
0
        // <summary>
        // Получаем предложение по оптимальному расположению адресов в указанном маршруте.
        // Рачет идет с учетом окон доставки. Но естественно без любых ограничений по весу и прочему.
        // </summary>
        // <returns>Предолженый маршрут</returns>
        // <param name="route">Первоначальный маршрутный лист, чтобы взять адреса.</param>
        public ProposedRoute RebuidOneRoute(RouteList route)
        {
            var trip = new PossibleTrip(route);

            logger.Info("Подготавливаем заказы...");
            PerformanceHelper.StartMeasurement($"Строим маршрут");

            List <CalculatedOrder> calculatedOrders = new List <CalculatedOrder>();

            foreach (var address in route.Addresses)
            {
                if (address.Order.DeliveryPoint.Longitude == null || address.Order.DeliveryPoint.Latitude == null)
                {
                    continue;
                }

                calculatedOrders.Add(new CalculatedOrder(address.Order, null));
            }
            Nodes = calculatedOrders.ToArray();

            distanceCalculator = new ExtDistanceCalculator(DistanceProvider.Osrm, Nodes.Select(x => x.Order.DeliveryPoint).ToArray(), StatisticsTxtAction);

            PerformanceHelper.AddTimePoint(logger, $"Подготовка заказов");

            logger.Info("Создаем модель...");
            RoutingModel routing = new RoutingModel(Nodes.Length + 1, 1, 0);

            int horizon = 24 * 3600;

            routing.AddDimension(new CallbackTime(Nodes, trip, distanceCalculator), 3 * 3600, horizon, false, "Time");
            var time_dimension = routing.GetDimensionOrDie("Time");

            var cumulTimeOnEnd   = routing.CumulVar(routing.End(0), "Time");
            var cumulTimeOnBegin = routing.CumulVar(routing.Start(0), "Time");

            if (route.Shift != null)
            {
                var shift = route.Shift;
                cumulTimeOnEnd.SetMax((long)shift.EndTime.TotalSeconds);
                cumulTimeOnBegin.SetMin((long)shift.StartTime.TotalSeconds);
            }

            routing.SetArcCostEvaluatorOfVehicle(new CallbackDistance(Nodes, distanceCalculator), 0);

            for (int ix = 0; ix < Nodes.Length; ix++)
            {
                var startWindow = Nodes[ix].Order.DeliverySchedule.From.TotalSeconds;
                var endWindow   = Nodes[ix].Order.DeliverySchedule.To.TotalSeconds - trip.Driver.TimeCorrection(Nodes[ix].Order.CalculateTimeOnPoint(route.Forwarder != null));
                if (endWindow < startWindow)
                {
                    logger.Warn("Время разгрузки на точке, не помещается в диапазон времени доставки. {0}-{1}", Nodes[ix].Order.DeliverySchedule.From, Nodes[ix].Order.DeliverySchedule.To);
                    endWindow = startWindow;
                }
                time_dimension.CumulVar(ix + 1).SetRange((long)startWindow, (long)endWindow);
                routing.AddDisjunction(new int[] { ix + 1 }, MaxDistanceAddressPenalty);
            }

            RoutingSearchParameters search_parameters = RoutingModel.DefaultSearchParameters();

            search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.ParallelCheapestInsertion;

            search_parameters.TimeLimitMs = MaxTimeSeconds * 1000;
            //search_parameters.FingerprintArcCostEvaluators = true;
            //search_parameters.OptimizationStep = 100;

            var solver = routing.solver();

            PerformanceHelper.AddTimePoint(logger, $"Настроили оптимизацию");
            logger.Info("Закрываем модель...");
            logger.Info("Рассчет расстояний между точками...");
            routing.CloseModelWithParameters(search_parameters);
            distanceCalculator.FlushCache();
            var lastSolution = solver.MakeLastSolutionCollector();

            lastSolution.AddObjective(routing.CostVar());
            routing.AddSearchMonitor(lastSolution);
            routing.AddSearchMonitor(new CallbackMonitor(solver, StatisticsTxtAction, lastSolution));

            PerformanceHelper.AddTimePoint(logger, $"Закрыли модель");
            logger.Info("Поиск решения...");

            Assignment solution = routing.SolveWithParameters(search_parameters);

            PerformanceHelper.AddTimePoint(logger, $"Получили решение.");
            logger.Info("Готово. Заполняем.");
            Console.WriteLine("Status = {0}", routing.Status());
            ProposedRoute proposedRoute = null;

            if (solution != null)
            {
                // Solution cost.
                Console.WriteLine("Cost = {0}", solution.ObjectiveValue());
                time_dimension = routing.GetDimensionOrDie("Time");

                int route_number = 0;

                proposedRoute = new ProposedRoute(null);
                long first_node  = routing.Start(route_number);
                long second_node = solution.Value(routing.NextVar(first_node));                 // Пропускаем первый узел, так как это наша база.
                proposedRoute.RouteCost = routing.GetCost(first_node, second_node, route_number);

                while (!routing.IsEnd(second_node))
                {
                    var time_var = time_dimension.CumulVar(second_node);
                    var rPoint   = new ProposedRoutePoint(
                        TimeSpan.FromSeconds(solution.Min(time_var)),
                        TimeSpan.FromSeconds(solution.Max(time_var)),
                        Nodes[second_node - 1].Order
                        );
                    rPoint.DebugMaxMin = string.Format("\n({0},{1})[{3}-{4}]-{2}",
                                                       new DateTime().AddSeconds(solution.Min(time_var)).ToShortTimeString(),
                                                       new DateTime().AddSeconds(solution.Max(time_var)).ToShortTimeString(),
                                                       second_node,
                                                       rPoint.Order.DeliverySchedule.From.ToString("hh\\:mm"),
                                                       rPoint.Order.DeliverySchedule.To.ToString("hh\\:mm")
                                                       );
                    proposedRoute.Orders.Add(rPoint);

                    first_node               = second_node;
                    second_node              = solution.Value(routing.NextVar(first_node));
                    proposedRoute.RouteCost += routing.GetCost(first_node, second_node, route_number);
                }
            }

            PerformanceHelper.Main.PrintAllPoints(logger);

            if (distanceCalculator.ErrorWays.Count > 0)
            {
                logger.Debug("Ошибок получения расстояний {0}", distanceCalculator.ErrorWays.Count);
                var uniqueFrom = distanceCalculator.ErrorWays.Select(x => x.FromHash).Distinct().ToList();
                var uniqueTo   = distanceCalculator.ErrorWays.Select(x => x.ToHash).Distinct().ToList();
                logger.Debug("Уникальных точек: отправки = {0}, прибытия = {1}", uniqueFrom.Count, uniqueTo.Count);
                logger.Debug("Проблемные точки отправки:\n{0}",
                             string.Join("; ", distanceCalculator.ErrorWays
                                         .GroupBy(x => x.FromHash)
                                         .Where(x => x.Count() > (uniqueTo.Count / 2))
                                         .Select(x => CachedDistance.GetText(x.Key)))
                             );
                logger.Debug("Проблемные точки прибытия:\n{0}",
                             string.Join("; ", distanceCalculator.ErrorWays
                                         .GroupBy(x => x.ToHash)
                                         .Where(x => x.Count() > (uniqueFrom.Count / 2))
                                         .Select(x => CachedDistance.GetText(x.Key)))
                             );
            }
            distanceCalculator.Dispose();
            return(proposedRoute);
        }
Exemple #7
0
        // <summary>
        // Метод создаем маршруты на день основываясь на данных всесенных в поля <c>Routes</c>, <c>Orders</c>,
        // <c>Drivers</c> и <c>Forwarders</c>.
        // </summary>
        public void CreateRoutes(TimeSpan drvStartTime, TimeSpan drvEndTime)
        {
            WarningMessages.Clear();
            ProposedRoutes.Clear();             //Очищаем сразу, так как можем выйти из метода ранее.

            logger.Info("Подготавливаем заказы...");
            PerformanceHelper.StartMeasurement($"Строим оптимальные маршруты");

            // Создаем список поездок всех водителей. Тут перебираем всех водителей с машинами
            // и создаем поездки для них, в зависимости от выбранного режима работы.
            var trips = Drivers.Where(x => x.Car != null)
                        .OrderBy(x => x.PriorityAtDay)
                        .SelectMany(drv => drv.DaySchedule != null
                                                                                                ? drv.DaySchedule.Shifts.Where(s => s.StartTime >= drvStartTime && s.StartTime < drvEndTime)
                                    .Select(shift => new PossibleTrip(drv, shift))
                                                                                                                                                : new[] { new PossibleTrip(drv, null) }
                                    )
                        .ToList();

            // Стыкуем уже созданные маршрутные листы с возможными поездками, на основании водителя и смены.
            // Если уже созданный маршрут не найден в поездках, то создаем поездку для него.
            foreach (var existRoute in Routes)
            {
                var trip = trips.FirstOrDefault(x => x.Driver == existRoute.Driver && x.Shift == existRoute.Shift);
                if (trip != null)
                {
                    trip.OldRoute = existRoute;
                }
                else
                {
                    trips.Add(new PossibleTrip(existRoute));
                }
                //Проверяем все ли заказы из МЛ присутствуют в списке заказов. Если их нет. Добавляем.
                foreach (var address in existRoute.Addresses)
                {
                    if (Orders.All(x => x.Id != address.Order.Id))
                    {
                        Orders.Add(address.Order);
                    }
                }
            }

            var possibleRoutes = trips.ToArray();

            if (!possibleRoutes.Any())
            {
                AddWarning("Для построения маршрутов, нет водителей.");
                return;
            }

            TestCars(possibleRoutes);

            var                    areas            = UoW.GetAll <District>().Where(x => x.DistrictsSet.Status == DistrictsSetStatus.Active).ToList();
            List <District>        unusedDistricts  = new List <District>();
            List <CalculatedOrder> calculatedOrders = new List <CalculatedOrder>();

            // Перебираем все заказы, исключаем те которые без координат, определяем для каждого заказа район
            // на основании координат. И создавая экземпляр <c>CalculatedOrder</c>, происходит подсчет сумарной
            // информации о заказе. Всего бутылей, вес и прочее.
            foreach (var order in Orders)
            {
                if (order.DeliveryPoint.Longitude == null || order.DeliveryPoint.Latitude == null)
                {
                    continue;
                }
                var point = new Point((double)order.DeliveryPoint.Latitude.Value, (double)order.DeliveryPoint.Longitude.Value);
                var area  = areas.Find(x => x.DistrictBorder.Contains(point));
                if (area != null)
                {
                    var oldRoute = Routes.FirstOrDefault(r => r.Addresses.Any(a => a.Order.Id == order.Id));
                    if (oldRoute != null)
                    {
                        calculatedOrders.Add(new CalculatedOrder(order, area, false, oldRoute));
                    }
                    else if (possibleRoutes.SelectMany(x => x.Districts).Any(x => x.District.Id == area.Id))
                    {
                        var cOrder = new CalculatedOrder(order, area);
                        //if(possibleRoutes.Any(r => r.GeographicGroup.Id == cOrder.ShippingBase.Id))//убрать, если в автоформировании должны учавствовать заказы из всех частей города вне зависимости от того какие части города выбраны в диалоге
                        calculatedOrders.Add(cOrder);
                    }
                    else if (!unusedDistricts.Contains(area))
                    {
                        unusedDistricts.Add(area);
                    }
                }
            }
            Nodes = calculatedOrders.ToArray();
            if (unusedDistricts.Any())
            {
                AddWarning("Районы без водителей: {0}", string.Join(", ", unusedDistricts.Select(x => x.DistrictName)));
            }

            // Создаем калькулятор расчета расстояний. Он сразу запрашивает уже имеющиеся расстояния из кеша
            // и в фоновом режиме начинает считать недостающую матрицу.
            distanceCalculator = new ExtDistanceCalculator(DistanceProvider.Osrm, Nodes.Select(x => x.Order.DeliveryPoint).ToArray(), StatisticsTxtAction);

            logger.Info("Развозка по {0} районам.", calculatedOrders.Select(x => x.District).Distinct().Count());
            PerformanceHelper.AddTimePoint(logger, $"Подготовка заказов");

            // Пред запуском оптимизации мы должны создать модель и внести в нее все необходимые данные.
            logger.Info("Создаем модель...");
            RoutingModel routing = new RoutingModel(Nodes.Length + 1, possibleRoutes.Length, 0);

            // Создаем измерение со временем на маршруте.
            // <c>horizon</c> - ограничивает максимально допустимое значение диапазона, чтобы не уйти за границы суток;
            // <c>maxWaitTime</c> - Максимальное время ожидания водителя. То есть водитель закончил разгрузку следующий
            // адрес в маршруте у него не должен быть позже чем на 4 часа ожидания.
            int horizon        = 24 * 3600;
            int maxWaitTime    = 4 * 3600;
            var timeEvaluators = possibleRoutes.Select(x => new CallbackTime(Nodes, x, distanceCalculator)).ToArray();

            routing.AddDimensionWithVehicleTransits(timeEvaluators, maxWaitTime, horizon, false, "Time");
            var time_dimension = routing.GetDimensionOrDie("Time");

            // Ниже заполняем все измерения для учета бутылей, веса, адресов, объема.
            var bottlesCapacity = possibleRoutes.Select(x => (long)x.Car.MaxBottles).ToArray();

            routing.AddDimensionWithVehicleCapacity(new CallbackBottles(Nodes), 0, bottlesCapacity, true, "Bottles");

            var weightCapacity = possibleRoutes.Select(x => (long)x.Car.MaxWeight).ToArray();

            routing.AddDimensionWithVehicleCapacity(new CallbackWeight(Nodes), 0, weightCapacity, true, "Weight");

            var volumeCapacity = possibleRoutes.Select(x => (long)(x.Car.MaxVolume * 1000)).ToArray();

            routing.AddDimensionWithVehicleCapacity(new CallbackVolume(Nodes), 0, volumeCapacity, true, "Volume");

            var addressCapacity = possibleRoutes.Select(x => (long)(x.Driver.MaxRouteAddresses)).ToArray();

            routing.AddDimensionWithVehicleCapacity(new CallbackAddressCount(Nodes.Length), 0, addressCapacity, true, "AddressCount");

            var bottlesDimension = routing.GetDimensionOrDie("Bottles");
            var addressDimension = routing.GetDimensionOrDie("AddressCount");

            for (int ix = 0; ix < possibleRoutes.Length; ix++)
            {
                // Устанавливаем функцию получения стоимости маршрута.
                routing.SetArcCostEvaluatorOfVehicle(new CallbackDistanceDistrict(Nodes, possibleRoutes[ix], distanceCalculator), ix);

                // Добавляем фиксированный штраф за принадлежность водителя.
                if (possibleRoutes[ix].Driver.DriverType.HasValue)
                {
                    routing.SetFixedCostOfVehicle(((int)possibleRoutes[ix].Driver.DriverType) * DriverPriorityPenalty, ix);
                }
                else
                {
                    routing.SetFixedCostOfVehicle(DriverPriorityPenalty * 3, ix);
                }

                var cumulTimeOnEnd   = routing.CumulVar(routing.End(ix), "Time");
                var cumulTimeOnBegin = routing.CumulVar(routing.Start(ix), "Time");

                // Устанавливаем минимальные(мягкие) границы для измерений. При значениях меньше минимальных, маршрут все таки принимается,
                // но вносятся некоторые штрафные очки на последнюю точку маршрута.
                //bottlesDimension.SetEndCumulVarSoftLowerBound(ix, possibleRoutes[ix].Car.MinBottles, MinBottlesInRoutePenalty);
                //addressDimension.SetEndCumulVarSoftLowerBound(ix, possibleRoutes[ix].Driver.MinRouteAddresses, MinAddressesInRoutePenalty);

                // Устанавливаем диапазон времени для движения по маршруту в зависимости от выбраной смены,
                // день, вечер и с учетом досрочного завершения водителем работы.
                if (possibleRoutes[ix].Shift != null)
                {
                    var shift   = possibleRoutes[ix].Shift;
                    var endTime = possibleRoutes[ix].EarlyEnd.HasValue
                                                                                                        ? Math.Min(shift.EndTime.TotalSeconds, possibleRoutes[ix].EarlyEnd.Value.TotalSeconds)
                                                                                                        : shift.EndTime.TotalSeconds;
                    cumulTimeOnEnd.SetMax((long)endTime);
                    cumulTimeOnBegin.SetMin((long)shift.StartTime.TotalSeconds);
                }
                else if (possibleRoutes[ix].EarlyEnd.HasValue)                //Устанавливаем время окончания рабочего дня у водителя.
                {
                    cumulTimeOnEnd.SetMax((long)possibleRoutes[ix].EarlyEnd.Value.TotalSeconds);
                }
            }

            for (int ix = 0; ix < Nodes.Length; ix++)
            {
                // Проставляем на каждый адрес окно времени приезда.
                var startWindow = Nodes[ix].Order.DeliverySchedule.From.TotalSeconds;
                var endWindow   = Nodes[ix].Order.DeliverySchedule.To.TotalSeconds - Nodes[ix].Order.CalculateTimeOnPoint(false);               //FIXME Внимание здесь задаем время без экспедитора и без учета скорости водителя. Это не правильно, но другого варианта я придумать не смог.
                if (endWindow < startWindow)
                {
                    AddWarning("Время разгрузки на {2}, не помещается в диапазон времени доставки. {0}-{1}", Nodes[ix].Order.DeliverySchedule.From, Nodes[ix].Order.DeliverySchedule.To, Nodes[ix].Order.DeliveryPoint.ShortAddress);
                    endWindow = startWindow;
                }
                time_dimension.CumulVar(ix + 1).SetRange((long)startWindow, (long)endWindow);
                // Добавляем абсолютно все заказы в дизюкцию. Если бы заказы небыли вдобавлены в отдельные дизьюкции
                // то при не возможность доставить хоть один заказ. Все решение бы считаль не верным. Добавление каждого заказа
                // в отдельную дизьюкцию, позволяет механизму не вести какой то и заказов, и все таки формировать решение с недовезенными
                // заказами. Дизьюкция работает так. Он говорит, если хотя бы один заказ в этой группе(дизьюкции) доставлен,
                // то все хорошо, иначе штраф. Так как у нас в кадой дизьюкции по одному заказу. Мы получаем опциональную доставку каждого заказа.
                routing.AddDisjunction(new int[] { ix + 1 }, MaxDistanceAddressPenalty);
            }

            logger.Debug("Nodes.Length = {0}", Nodes.Length);
            logger.Debug("routing.Nodes() = {0}", routing.Nodes());
            logger.Debug("GetNumberOfDisjunctions = {0}", routing.GetNumberOfDisjunctions());

            RoutingSearchParameters search_parameters = RoutingModel.DefaultSearchParameters();

            // Setting first solution heuristic (cheapest addition).
            // Указывается стратегия первоначального заполнения. Опытным путем было вычислено, что именно при
            // стратегиях вставки маршруты получаются с набором точек более близких к друг другу. То есть в большей
            // степени облачком. Что воспринималось человеком как более отпимальное. В отличии от большенства других
            // стратегий в которых маршруты, формируюся скорее по лентами ведущими через все обезжаемые раоны. То есть водители
            // чаще имели пересечения маршутов.
            search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.ParallelCheapestInsertion;

            search_parameters.TimeLimitMs = MaxTimeSeconds * 1000;
            // Отключаем внутреннего кеширования расчитанных значений. Опытным путем было проверено, что включение этого значения.
            // Значительно(на несколько секунд) увеличивает время закрытия модели и сокращает иногда не значительно время расчета оптимизаций.
            // И в принцепе становится целесообразно только на количествах заказов 300-400. При количестве заказов менее 200
            // влючение отпечатков значений. Не уменьшало, а увеличивало общее время расчета. А при большом количестве заказов
            // время расчета уменьшалось не значительно.
            //search_parameters.FingerprintArcCostEvaluators = false;

            search_parameters.FingerprintArcCostEvaluators = true;

            //search_parameters.OptimizationStep = 100;

            var solver = routing.solver();

            PerformanceHelper.AddTimePoint(logger, $"Настроили оптимизацию");
            logger.Info("Закрываем модель...");

            if (
                WarningMessages.Any() && !interactiveService.Question(
                    string.Join("\n", WarningMessages.Select(x => "⚠ " + x)),
                    "При построении транспортной модели обнаружены следующие проблемы:\n{0}\nПродолжить?"
                    )
                )
            {
                distanceCalculator.Canceled = true;
                distanceCalculator.Dispose();
                return;
            }

            logger.Info("Рассчет расстояний между точками...");
            routing.CloseModelWithParameters(search_parameters);
#if DEBUG
            PrintMatrixCount(distanceCalculator.matrixcount);
#endif
            //Записывем возможно не схраненый кеш в базу.
            distanceCalculator.FlushCache();
            //Попытка хоть как то ослеживать что происходит в момент построения. Возможно не очень правильная.
            //Пришлось создавать 2 монитора.
            var lastSolution = solver.MakeLastSolutionCollector();
            lastSolution.AddObjective(routing.CostVar());
            routing.AddSearchMonitor(lastSolution);
            routing.AddSearchMonitor(new CallbackMonitor(solver, StatisticsTxtAction, lastSolution));

            PerformanceHelper.AddTimePoint(logger, $"Закрыли модель");
            logger.Info("Поиск решения...");

            Assignment solution = routing.SolveWithParameters(search_parameters);
            PerformanceHelper.AddTimePoint(logger, $"Получили решение.");
            logger.Info("Готово. Заполняем.");
#if DEBUG
            PrintMatrixCount(distanceCalculator.matrixcount);
#endif
            Console.WriteLine("Status = {0}", routing.Status());
            if (solution != null)
            {
                // Solution cost.
                Console.WriteLine("Cost = {0}", solution.ObjectiveValue());
                time_dimension = routing.GetDimensionOrDie("Time");

                //Читаем полученные маршруты.
                for (int route_number = 0; route_number < routing.Vehicles(); route_number++)
                {
                    var  route       = new ProposedRoute(possibleRoutes[route_number]);
                    long first_node  = routing.Start(route_number);
                    long second_node = solution.Value(routing.NextVar(first_node));                     // Пропускаем первый узел, так как это наша база.
                    route.RouteCost = routing.GetCost(first_node, second_node, route_number);

                    while (!routing.IsEnd(second_node))
                    {
                        var time_var = time_dimension.CumulVar(second_node);
                        var rPoint   = new ProposedRoutePoint(
                            TimeSpan.FromSeconds(solution.Min(time_var)),
                            TimeSpan.FromSeconds(solution.Max(time_var)),
                            Nodes[second_node - 1].Order
                            );
                        rPoint.DebugMaxMin = string.Format("\n({0},{1})[{3}-{4}]-{2} Cost:{5}",
                                                           new DateTime().AddSeconds(solution.Min(time_var)).ToShortTimeString(),
                                                           new DateTime().AddSeconds(solution.Max(time_var)).ToShortTimeString(),
                                                           second_node,
                                                           rPoint.Order.DeliverySchedule.From.ToString("hh\\:mm"),
                                                           rPoint.Order.DeliverySchedule.To.ToString("hh\\:mm"),
                                                           routing.GetCost(first_node, second_node, route_number)
                                                           );
                        route.Orders.Add(rPoint);

                        first_node       = second_node;
                        second_node      = solution.Value(routing.NextVar(first_node));
                        route.RouteCost += routing.GetCost(first_node, second_node, route_number);
                    }

                    if (route.Orders.Count > 0)
                    {
                        ProposedRoutes.Add(route);
                        logger.Debug("Маршрут {0}: {1}",
                                     route.Trip.Driver.ShortName,
                                     string.Join(" -> ", route.Orders.Select(x => x.DebugMaxMin))
                                     );
                    }
                    else
                    {
                        logger.Debug("Маршрут {0}: пустой", route.Trip.Driver.ShortName);
                    }
                }
            }

#if DEBUG
            logger.Debug("SGoToBase:{0}", string.Join(", ", CallbackDistanceDistrict.SGoToBase.Select(x => $"{x.Key.Driver.ShortName}={x.Value}")));
            logger.Debug("SFromExistPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SFromExistPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}")));
            logger.Debug("SUnlikeDistrictPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SUnlikeDistrictPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}")));
            logger.Debug("SLargusPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SLargusPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}")));
#endif

            if (ProposedRoutes.Count > 0)
            {
                logger.Info($"Предложено {ProposedRoutes.Count} маршрутов.");
            }
            PerformanceHelper.Main.PrintAllPoints(logger);

            if (distanceCalculator.ErrorWays.Any())
            {
                logger.Debug("Ошибок получения расстояний {0}", distanceCalculator.ErrorWays.Count);
                var uniqueFrom = distanceCalculator.ErrorWays.Select(x => x.FromHash).Distinct().ToList();
                var uniqueTo   = distanceCalculator.ErrorWays.Select(x => x.ToHash).Distinct().ToList();
                logger.Debug("Уникальных точек: отправки = {0}, прибытия = {1}", uniqueFrom.Count, uniqueTo.Count);
                logger.Debug("Проблемные точки отправки:\n{0}",
                             string.Join("; ", distanceCalculator.ErrorWays
                                         .GroupBy(x => x.FromHash)
                                         .Where(x => x.Count() > (uniqueTo.Count / 2))
                                         .Select(x => CachedDistance.GetText(x.Key)))
                             );
                logger.Debug("Проблемные точки прибытия:\n{0}",
                             string.Join("; ", distanceCalculator.ErrorWays
                                         .GroupBy(x => x.ToHash)
                                         .Where(x => x.Count() > (uniqueFrom.Count / 2))
                                         .Select(x => CachedDistance.GetText(x.Key)))
                             );
            }
        }
Exemple #8
0
    /// <summary>
    ///   Solves the current routing problem.
    /// </summary>
    private void Solve(int number_of_orders, int number_of_vehicles)
    {
        Console.WriteLine("Creating model with " + number_of_orders + " orders and " + number_of_vehicles +
                          " vehicles.");
        // Finalizing model
        int number_of_locations = locations_.Length;

        RoutingIndexManager manager =
            new RoutingIndexManager(number_of_locations, number_of_vehicles, vehicle_starts_, vehicle_ends_);
        RoutingModel model = new RoutingModel(manager);

        // Setting up dimensions
        const int big_number         = 100000;
        Manhattan manhattan_callback = new Manhattan(manager, locations_, 1);

        model.AddDimension(model.RegisterTransitCallback(manhattan_callback.Call), big_number, big_number, false,
                           "time");
        RoutingDimension time_dimension = model.GetDimensionOrDie("time");

        Demand demand_callback = new Demand(manager, order_demands_);

        model.AddDimension(model.RegisterUnaryTransitCallback(demand_callback.Call), 0, vehicle_capacity_, true,
                           "capacity");
        RoutingDimension capacity_dimension = model.GetDimensionOrDie("capacity");

        // Setting up vehicles
        Manhattan[] cost_callbacks = new Manhattan[number_of_vehicles];
        for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle)
        {
            int       cost_coefficient        = vehicle_cost_coefficients_[vehicle];
            Manhattan manhattan_cost_callback = new Manhattan(manager, locations_, cost_coefficient);
            cost_callbacks[vehicle] = manhattan_cost_callback;
            int manhattan_cost_index = model.RegisterTransitCallback(manhattan_cost_callback.Call);
            model.SetArcCostEvaluatorOfVehicle(manhattan_cost_index, vehicle);
            time_dimension.CumulVar(model.End(vehicle)).SetMax(vehicle_end_time_[vehicle]);
        }

        // Setting up orders
        for (int order = 0; order < number_of_orders; ++order)
        {
            time_dimension.CumulVar(order).SetRange(order_time_windows_[order].start_, order_time_windows_[order].end_);
            long[] orders = { manager.NodeToIndex(order) };
            model.AddDisjunction(orders, order_penalties_[order]);
        }

        // Solving
        RoutingSearchParameters search_parameters =
            operations_research_constraint_solver.DefaultRoutingSearchParameters();

        search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.AllUnperformed;

        Console.WriteLine("Search...");
        Assignment solution = model.SolveWithParameters(search_parameters);

        if (solution != null)
        {
            String output = "Total cost: " + solution.ObjectiveValue() + "\n";
            // Dropped orders
            String dropped = "";
            for (int order = 0; order < number_of_orders; ++order)
            {
                if (solution.Value(model.NextVar(order)) == order)
                {
                    dropped += " " + order;
                }
            }
            if (dropped.Length > 0)
            {
                output += "Dropped orders:" + dropped + "\n";
            }
            // Routes
            for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle)
            {
                String route = "Vehicle " + vehicle + ": ";
                long   order = model.Start(vehicle);
                if (model.IsEnd(solution.Value(model.NextVar(order))))
                {
                    route += "Empty";
                }
                else
                {
                    for (; !model.IsEnd(order); order = solution.Value(model.NextVar(order)))
                    {
                        IntVar local_load = capacity_dimension.CumulVar(order);
                        IntVar local_time = time_dimension.CumulVar(order);
                        route += order + " Load(" + solution.Value(local_load) + ") " + "Time(" +
                                 solution.Min(local_time) + ", " + solution.Max(local_time) + ") -> ";
                    }
                    IntVar load = capacity_dimension.CumulVar(order);
                    IntVar time = time_dimension.CumulVar(order);
                    route += order + " Load(" + solution.Value(load) + ") " + "Time(" + solution.Min(time) + ", " +
                             solution.Max(time) + ")";
                }
                output += route + "\n";
            }
            Console.WriteLine(output);
        }
    }
        /// <summary>
        /// requires data.SantaIds
        /// requires data.Visits
        /// requires data.HomeIndex
        /// requires data.Unavailable
        /// requires data.Start
        /// requires data.End
        /// </summary>
        public static OptimizationResult Solve(RoutingData data, long timeLimitMilliseconds, ITimeWindowStrategy strategy)
        {
            if (false ||
                data.SantaIds == null ||
                data.Visits == null ||
                data.Unavailable == null ||
                data.SantaStartIndex == null ||
                data.SantaEndIndex == null
                )
            {
                throw new ArgumentNullException();
            }

            var model = new RoutingModel(data.Visits.Length, data.SantaIds.Length, data.SantaStartIndex, data.SantaEndIndex);

            // setting up dimensions
            var maxTime      = GetMaxTime(data);
            var timeCallback = new TimeEvaluator(data);

            model.AddDimension(timeCallback, 0, maxTime, false, DimensionTime);
            var lengthCallback = new TimeEvaluator(data);

            model.AddDimension(lengthCallback, 0, maxTime, true, DimensionLength);

            // set additional cost of longest day
            {
                var dim = model.GetDimensionOrDie(DimensionLength);
                dim.SetGlobalSpanCostCoefficient(data.Cost.CostLongestDayPerHour);
            }

            // dimensions for breaks
            var breakCallbacks  = new List <BreakEvaluator>();
            var breakDimensions = new List <string>();

            for (int santa = 0; santa < data.NumberOfSantas; santa++)
            {
                var maxBreaks = GetNumberOfBreaks(data, santa);
                if (maxBreaks == 0)
                {
                    // no breaks
                    continue;
                }

                var evaluator = new BreakEvaluator(data, santa);
                var dimension = GetSantaBreakDimension(santa);
                model.AddDimension(evaluator, 0, maxBreaks, true, dimension);
                breakCallbacks.Add(evaluator);
                breakDimensions.Add(dimension);
            }

            // setting up santas (=vehicles)
            var costCallbacks = new NodeEvaluator2[data.NumberOfSantas];

            for (int santa = 0; santa < data.NumberOfSantas; santa++)
            {
                // must be a new instance per santa
                NodeEvaluator2 costCallback = data.Input.IsAdditionalSanta(data.SantaIds[santa])
                    ? new CostEvaluator(data, data.Cost.CostWorkPerHour + data.Cost.CostAdditionalSantaPerHour, data.Cost.CostAdditionalSanta)
                    : new CostEvaluator(data, data.Cost.CostWorkPerHour, 0);
                costCallbacks[santa] = costCallback;
                model.SetVehicleCost(santa, costCallback);

                // limit time per santa
                var day   = data.GetDayFromSanta(santa);
                var start = GetDayStart(data, day);
                var end   = GetDayEnd(data, day);
                model.CumulVar(model.End(santa), DimensionTime).SetRange(start, end);
                model.CumulVar(model.Start(santa), DimensionTime).SetRange(start, end);

                // avoid visiting breaks of other santas
                var breakDimension = GetSantaBreakDimension(santa);
                foreach (var dimension in breakDimensions.Except(new[] { breakDimension }))
                {
                    model.CumulVar(model.End(santa), dimension).SetMax(0);
                }
            }

            // setting up visits (=orders)
            for (int visit = 0; visit < data.NumberOfVisits; ++visit)
            {
                var cumulTimeVar = model.CumulVar(visit, DimensionTime);
                cumulTimeVar.SetRange(data.OverallStart, data.OverallEnd);
                model.AddDisjunction(new[] { visit }, data.Cost.CostNotVisitedVisit);

                // add desired / unavailable according to strategy
                var timeDimension = model.GetDimensionOrDie(DimensionTime);
                strategy.AddConstraints(data, model, cumulTimeVar, timeDimension, visit);
            }

            // Solving
            var searchParameters = RoutingModel.DefaultSearchParameters();

            searchParameters.FirstSolutionStrategy    = FirstSolutionStrategy.Types.Value.Automatic; // maybe try AllUnperformed or PathCheapestArc
            searchParameters.LocalSearchMetaheuristic = LocalSearchMetaheuristic.Types.Value.GuidedLocalSearch;
            searchParameters.TimeLimitMs = timeLimitMilliseconds;

            var solution = model.SolveWithParameters(searchParameters);

            // protect callbacks from the GC
            GC.KeepAlive(timeCallback);
            GC.KeepAlive(lengthCallback);
            foreach (var costCallback in costCallbacks)
            {
                GC.KeepAlive(costCallback);
            }
            foreach (var breakCallback in breakCallbacks)
            {
                GC.KeepAlive(breakCallback);
            }

            Debug.WriteLine($"obj={solution?.ObjectiveValue()}");

            return(CreateResult(data, model, solution));
        }
Exemple #10
0
        public IList <string> GetShortestOptimizedRoute([FromBody] GeographicViewModel geographicVM)
        {
            // double[] latitude = { 13.121329, 13.065150, 13.024346, 13.027691, 12.913887, 12.915754, 12.962431, 12.890461, 12.907220, 12.954234, 13.026996, 13.041044, 13.001573 };
            //double[] longitude = { 80.029049, 80.128613, 79.909573, 80.259762, 80.253067, 80.192041, 80.253839, 80.097198, 80.142088, 80.188437, 80.107756, 80.234957, 80.257616 };
            string[] city_names = { "Thirunindravur", "Thiruverkadu", "Senkadu", "Milapore", "VGP Golden", "Medavakkam", "Palavakkam", "Vandalur", "Selaiyur", "Kelkattalai", "Mangadu", "TNagar", "Adyar" };

            double[] latitudeandLongitude;
            var      sCoordlatitudeandLongitude = new List <string>();
            var      finallatitudeandLongitude  = new List <string>();

            distanceArray = new double[geographicVM.Latitude.Length, geographicVM.Longitude.Length];
            var k = 0;

            for (int i = 0; i < geographicVM.Latitude.Length; i++)
            {
                for (int j = 0; j < geographicVM.Longitude.Length; j++)
                {
                    var sCoord = new GeoCoordinate(geographicVM.Latitude[i], geographicVM.Longitude[i]);
                    var eCoord = new GeoCoordinate(geographicVM.Latitude[j], geographicVM.Longitude[j]);
                    if (i == j)
                    {
                        if (i % 2 == 0)
                        {
                            sCoordlatitudeandLongitude.Add(sCoord.ToString() + ",D" + "," + k++);
                        }
                        else
                        {
                            sCoordlatitudeandLongitude.Add(sCoord.ToString() + ",P" + "," + k);
                        }
                    }

                    var text = sCoord.GetDistanceTo(eCoord) / 1609.344;
                    distanceArray[i, j] = Math.Round(text);
                }
            }
            double[,] costs = distanceArray;

            int          num_locations = city_names.Length;
            RoutingModel routingModel  = new RoutingModel(geographicVM.Latitude.Length, 1, 0);

            Solver solver = routingModel.solver();


            string rank_name = "rank";

            routingModel.AddConstantDimension(1, geographicVM.Latitude.Length, true, rank_name);
            var rank_dimension = routingModel.GetDimensionOrDie(rank_name);

            //Constraint MinneapolisBeforeNewYork = solver.MakeLess(routingModel.CumulVar(highPriorityVarIndex, rank_name), routingModel.CumulVar(lowPriorityVarIndex, rank_name));

            /* Later needs to be worked on the Constraint for the  Multiple Pickups before Delivery */
            //Constraint test = routingModel.CumulVar(3, rank_name) < routingModel.CumulVar(13, rank_name);
            //solver.Add(test);


            RoutingSearchParameters search_parameters = RoutingModel.DefaultSearchParameters();

            search_parameters.FirstSolutionStrategy =
                FirstSolutionStrategy.Types.Value.LocalCheapestArc;
            NodeEvaluator2 cost_between_nodes = new Cost(costs);

            routingModel.SetArcCostEvaluatorOfAllVehicles(cost_between_nodes); //oder SetVehicleCost wenn Fahrzeuge unterschiedliche Kostenmatrix haben

            StringBuilder route = new StringBuilder();

            Assignment assignment = routingModel.SolveWithParameters(search_parameters);

            if (assignment != null)
            {
                Console.WriteLine("Total distance: " + assignment.ObjectiveValue().ToString() + "miles");

                long index = routingModel.Start(0); //vehicle 0

                route.Append("Route: ");

                do
                {
                    //route.Append(city_names[routingModel.IndexToNode(index)] + " -> ");
                    finallatitudeandLongitude.Add(sCoordlatitudeandLongitude[routingModel.IndexToNode(index)]);
                    index = assignment.Value(routingModel.NextVar(index));
                }while (!routingModel.IsEnd(index));
                // route.Append(city_names[routingModel.IndexToNode(index)]);
                finallatitudeandLongitude.Add(sCoordlatitudeandLongitude[routingModel.IndexToNode(index)]);
            }


            return(finallatitudeandLongitude);
        }