// Copyright 2010-2018 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Collections.Generic; using Google.OrTools.ConstraintSolver; /// /// Sample showing how to model and solve a capacitated vehicle routing /// problem with time windows using the swig-wrapped version of the vehicle /// routing library in src/constraint_solver. /// public class CapacitatedVehicleRoutingProblemWithTimeWindows { /// /// A position on the map with (x, y) coordinates. /// class Position { public Position() { this.x_ = 0; this.y_ = 0; } public Position(int x, int y) { this.x_ = x; this.y_ = y; } public int x_; public int y_; } /// /// A time window with start/end data. /// class TimeWindow { public TimeWindow() { this.start_ = -1; this.end_ = -1; } public TimeWindow(int start, int end) { this.start_ = start; this.end_ = end; } public int start_; public int end_; } /// /// Manhattan distance implemented as a callback. It uses an array of /// positions and computes the Manhattan distance between the two /// positions of two different indices. /// class Manhattan { public Manhattan( RoutingIndexManager manager, Position[] locations, int coefficient) { this.manager_ = manager; this.locations_ = locations; this.coefficient_ = coefficient; } public long Call(long first_index, long second_index) { if (first_index >= locations_.Length || second_index >= locations_.Length) { return 0; } int first_node = manager_.IndexToNode(first_index); int second_node = manager_.IndexToNode(second_index); return (Math.Abs(locations_[first_node].x_ - locations_[second_node].x_) + Math.Abs(locations_[first_node].y_ - locations_[second_node].y_)) * coefficient_; } private readonly RoutingIndexManager manager_; private readonly Position[] locations_; private readonly int coefficient_; }; /// /// A callback that computes the volume of a demand stored in an /// integer array. /// class Demand { public Demand( RoutingIndexManager manager, int[] order_demands) { this.manager_ = manager; this.order_demands_ = order_demands; } public long Call(long index) { if (index < order_demands_.Length) { int node = manager_.IndexToNode(index); return order_demands_[node]; } return 0; } private readonly RoutingIndexManager manager_; private readonly int[] order_demands_; }; /// Locations representing either an order location or a vehicle route /// start/end. private Position[] locations_; /// Quantity to be picked up for each order. private int[] order_demands_; /// Time window in which each order must be performed. private TimeWindow[] order_time_windows_; /// Penalty cost "paid" for dropping an order. private int[] order_penalties_; /// Capacity of the vehicles. private int vehicle_capacity_ = 0; /// Latest time at which each vehicle must end its tour. private int[] vehicle_end_time_; /// Cost per unit of distance of each vehicle. private int[] vehicle_cost_coefficients_; /// Vehicle start and end indices. They have to be implemented as int[] due /// to the available SWIG-ed interface. private int[] vehicle_starts_; private int[] vehicle_ends_; /// Random number generator to produce data. private Random random_generator = new Random(0xBEEF); /// /// Constructs a capacitated vehicle routing problem with time windows. /// private CapacitatedVehicleRoutingProblemWithTimeWindows() {} /// /// Creates order data. Location of the order is random, as well /// as its demand (quantity), time window and penalty. /// /// /// number of orders to build. /// maximum x coordinate in which orders are located. /// /// maximum y coordinate in which orders are located. /// /// maximum quantity of a demand. /// maximum starting time of the order time /// window. /// duration of the order time window. /// /// minimum pernalty cost if order is dropped. /// /// maximum pernalty cost if order is dropped. /// private void BuildOrders(int number_of_orders, int number_of_vehicles, int x_max, int y_max, int demand_max, int time_window_max, int time_window_width, int penalty_min, int penalty_max) { Console.WriteLine("Building orders."); locations_ = new Position[number_of_orders + 2 * number_of_vehicles]; order_demands_ = new int[number_of_orders]; order_time_windows_ = new TimeWindow[number_of_orders]; order_penalties_ = new int[number_of_orders]; for (int order = 0; order < number_of_orders; ++order) { locations_[order] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1)); order_demands_[order] = random_generator.Next(demand_max + 1); int time_window_start = random_generator.Next(time_window_max + 1); order_time_windows_[order] = new TimeWindow(time_window_start, time_window_start + time_window_width); order_penalties_[order] = random_generator.Next(penalty_max - penalty_min + 1) + penalty_min; } } /// /// Creates fleet data. Vehicle starting and ending locations are /// random, as well as vehicle costs per distance unit. /// /// /// number of orders /// number of vehicles /// maximum x coordinate in which orders are located. /// /// maximum y coordinate in which orders are located. /// /// latest end time of a tour of a vehicle. /// capacity of a vehicle. /// maximum cost per distance unit of a /// vehicle (minimum is 1) private void BuildFleet(int number_of_orders, int number_of_vehicles, int x_max, int y_max, int end_time, int capacity, int cost_coefficient_max) { Console.WriteLine("Building fleet."); vehicle_capacity_ = capacity; vehicle_starts_ = new int[number_of_vehicles]; vehicle_ends_ = new int[number_of_vehicles]; vehicle_end_time_ = new int[number_of_vehicles]; vehicle_cost_coefficients_ = new int[number_of_vehicles]; for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle) { int index = 2 * vehicle + number_of_orders; vehicle_starts_[vehicle] = index; locations_[index] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1)); vehicle_ends_[vehicle] = index + 1; locations_[index + 1] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1)); vehicle_end_time_[vehicle] = end_time; vehicle_cost_coefficients_[vehicle] = random_generator.Next(cost_coefficient_max) + 1; } } /// /// Solves the current routing problem. /// 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); } } public static void Main(String[] args) { CapacitatedVehicleRoutingProblemWithTimeWindows problem = new CapacitatedVehicleRoutingProblemWithTimeWindows(); int x_max = 20; int y_max = 20; int demand_max = 3; int time_window_max = 24 * 60; int time_window_width = 4 * 60; int penalty_min = 50; int penalty_max = 100; int end_time = 24 * 60; int cost_coefficient_max = 3; int orders = 100; int vehicles = 20; int capacity = 50; problem.BuildOrders(orders, vehicles, x_max, y_max, demand_max, time_window_max, time_window_width, penalty_min, penalty_max); problem.BuildFleet(orders, vehicles, x_max, y_max, end_time, capacity, cost_coefficient_max); problem.Solve(orders, vehicles); } }