diff --git a/makefiles/Makefile.cpp.mk b/makefiles/Makefile.cpp.mk index 8e9af03bc0..e15a67377c 100644 --- a/makefiles/Makefile.cpp.mk +++ b/makefiles/Makefile.cpp.mk @@ -419,6 +419,7 @@ test_cpp_constraint_solver_samples: \ rcpp_vrp_pickup_delivery_fifo \ rcpp_vrp_pickup_delivery_lifo \ rcpp_vrp_resources \ + rcpp_vrp_solution_callback \ rcpp_vrp_starts_ends \ rcpp_vrp_time_windows \ rcpp_vrp_with_time_limit diff --git a/makefiles/Makefile.dotnet.mk b/makefiles/Makefile.dotnet.mk index 4f0c7a045f..850e54786c 100644 --- a/makefiles/Makefile.dotnet.mk +++ b/makefiles/Makefile.dotnet.mk @@ -325,6 +325,7 @@ test_dotnet_constraint_solver_samples: \ rdotnet_VrpPickupDeliveryFifo \ rdotnet_VrpPickupDeliveryLifo \ rdotnet_VrpResources \ + rdotnet_VrpSolutionCallback \ rdotnet_VrpStartsEnds \ rdotnet_VrpTimeWindows \ rdotnet_VrpWithTimeLimit diff --git a/makefiles/Makefile.java.mk b/makefiles/Makefile.java.mk index 2ea2936a83..974910c45a 100644 --- a/makefiles/Makefile.java.mk +++ b/makefiles/Makefile.java.mk @@ -304,6 +304,7 @@ test_java_constraint_solver_samples: \ rjava_VrpPickupDeliveryFifo \ rjava_VrpPickupDeliveryLifo \ rjava_VrpResources \ + rjava_VrpSolutionCallback \ rjava_VrpStartsEnds \ rjava_VrpTimeWindows \ rjava_VrpWithTimeLimit diff --git a/makefiles/Makefile.python.mk b/makefiles/Makefile.python.mk index bd0615fc79..c674fc82cd 100644 --- a/makefiles/Makefile.python.mk +++ b/makefiles/Makefile.python.mk @@ -138,6 +138,7 @@ test_python_constraint_solver_samples: \ rpy_vrp_pickup_delivery_fifo \ rpy_vrp_pickup_delivery_lifo \ rpy_vrp_resources \ + rpy_vrp_solution_callback \ rpy_vrp_starts_ends \ rpy_vrp_time_windows \ rpy_vrp_tokens \ diff --git a/ortools/constraint_solver/samples/VrpSolutionCallback.cs b/ortools/constraint_solver/samples/VrpSolutionCallback.cs new file mode 100644 index 0000000000..2c92c3dcaa --- /dev/null +++ b/ortools/constraint_solver/samples/VrpSolutionCallback.cs @@ -0,0 +1,200 @@ +// Copyright 2010-2022 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. + +// [START program] +// [START import] +using System; +using System.Collections.Generic; +using Google.OrTools.ConstraintSolver; +using Google.Protobuf.WellKnownTypes; // Duration +// [END import] + +/// +/// Minimal Vehicle Routing Problem (VRP). +/// The search stop after improving the solution 15 times or after 5 seconds. +/// +public class VrpSolutionCallback +{ + // [START data_model] + class DataModel + { + public long[,] DistanceMatrix = { + { 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662 }, + { 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210 }, + { 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754 }, + { 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358 }, + { 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244 }, + { 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708 }, + { 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480 }, + { 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856 }, + { 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514 }, + { 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468 }, + { 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354 }, + { 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844 }, + { 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730 }, + { 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536 }, + { 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194 }, + { 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798 }, + { 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0 } + }; + public int VehicleNumber = 4; + public int Depot = 0; + }; + // [END data_model] + + // [START solution_callback_printer] + /// + /// Print the solution. + /// + static void printSolution(ref RoutingIndexManager routingManager, ref RoutingModel routingModel) + { + Console.WriteLine($"Solution objective: {routingModel.CostVar().Value()}:"); + // Inspect solution. + long totalDistance = 0; + for (int i = 0; i < routingManager.GetNumberOfVehicles(); ++i) + { + Console.WriteLine("################"); + Console.WriteLine($"Route for Vehicle {i}:"); + long routeDistance = 0; + long index = routingModel.Start(i); + while (routingModel.IsEnd(index) == false) + { + Console.Write($" {routingManager.IndexToNode(index)} ->"); + long previousIndex = index; + index = routingModel.NextVar(index).Value(); + routeDistance += routingModel.GetArcCostForVehicle(previousIndex, index, 0); + } + Console.WriteLine($" {routingManager.IndexToNode(index)}"); + Console.WriteLine($"Distance of the route: {routeDistance}m"); + totalDistance += routeDistance; + } + Console.WriteLine($"Total Distance of all routes: {totalDistance}m"); + } + // [END solution_callback_printer] + + // [START solution_callback] + class SolutionCallback { + public long[] objectives; + private RoutingIndexManager routingManager; + private RoutingModel routingModel; + private long maxSolution; + private long counter; + + public SolutionCallback( + ref RoutingIndexManager manager, + ref RoutingModel routing, + in long limit) + { + routingManager = manager; + routingModel = routing;; + maxSolution = limit; + counter = 0; + objectives = new long[maxSolution]; + } + + public void Run() { + long objective = routingModel.CostVar().Value(); + if(counter == 0 || objective < objectives[counter-1]) { + objectives[counter] = objective; + printSolution(ref routingManager, ref routingModel); + counter++; + } + if(counter >= maxSolution) { + routingModel.solver().FinishCurrentSearch(); + } + } + }; + // [END solution_callback] + + public static void Main(String[] args) + { + // Instantiate the data problem. + // [START data] + DataModel data = new DataModel(); + // [END data] + + // Create Routing Index Manager + // [START index_manager] + RoutingIndexManager routingManager = + new RoutingIndexManager(data.DistanceMatrix.GetLength(0), data.VehicleNumber, data.Depot); + // [END index_manager] + + // Create Routing Model. + // [START routing_model] + RoutingModel routingModel = new RoutingModel(routingManager); + // [END routing_model] + + // Create and register a transit callback. + // [START transit_callback] + int transitCallbackIndex = routingModel.RegisterTransitCallback( + (long fromIndex, long toIndex) => + { + // Convert from routing variable Index to + // distance matrix NodeIndex. + var fromNode = routingManager.IndexToNode(fromIndex); + var toNode = routingManager.IndexToNode(toIndex); + return data.DistanceMatrix[fromNode, toNode]; + }); + // [END transit_callback] + + // Define cost of each arc. + // [START arc_cost] + routingModel.SetArcCostEvaluatorOfAllVehicles(transitCallbackIndex); + // [END arc_cost] + + // Add Distance constraint. + // [START distance_constraint] + routingModel.AddDimension( + transitCallbackIndex, + 0, // no slack + 3000, // vehicle maximum travel distance + true, // start cumul to zero + "Distance"); + RoutingDimension distanceDimension = routingModel.GetMutableDimension("Distance"); + distanceDimension.SetGlobalSpanCostCoefficient(100); + // [END distance_constraint] + + // Attach a solution callback. + // [START attach_callback] + SolutionCallback solutionCallback = new SolutionCallback(ref routingManager, ref routingModel, /*max_solution=*/15); + routingModel.AddAtSolutionCallback(solutionCallback.Run); + // [END attach_callback] + + // Setting first solution heuristic. + // [START parameters] + RoutingSearchParameters searchParameters = + operations_research_constraint_solver.DefaultRoutingSearchParameters(); + searchParameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.PathCheapestArc; + searchParameters.LocalSearchMetaheuristic = LocalSearchMetaheuristic.Types.Value.GuidedLocalSearch; + searchParameters.TimeLimit = new Duration { Seconds = 5 }; + // [END parameters] + + // Solve the problem. + // [START solve] + Assignment solution = routingModel.SolveWithParameters(searchParameters); + // [END solve] + + // Print solution on console. + // [START print_solution] + if(solution != null) + { + Console.WriteLine($"Best objective: {solutionCallback.objectives[^1]}"); + } + else + { + Console.WriteLine("No solution found !"); + } + // [END print_solution] + } +} +// [END program] diff --git a/ortools/constraint_solver/samples/VrpSolutionCallback.java b/ortools/constraint_solver/samples/VrpSolutionCallback.java new file mode 100644 index 0000000000..c3bcd909ef --- /dev/null +++ b/ortools/constraint_solver/samples/VrpSolutionCallback.java @@ -0,0 +1,200 @@ +// Copyright 2010-2022 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. + +// [START program] +package com.google.ortools.constraintsolver.samples; +// [START import] +import com.google.ortools.Loader; +import com.google.ortools.constraintsolver.Assignment; +import com.google.ortools.constraintsolver.FirstSolutionStrategy; +import com.google.ortools.constraintsolver.LocalSearchMetaheuristic; +import com.google.ortools.constraintsolver.RoutingDimension; +import com.google.ortools.constraintsolver.RoutingIndexManager; +import com.google.ortools.constraintsolver.RoutingModel; +import com.google.ortools.constraintsolver.RoutingSearchParameters; +import com.google.ortools.constraintsolver.main; +import com.google.protobuf.Duration; +import java.util.logging.Logger; +// [END import] + +/** Minimal VRP.*/ +public class VrpSolutionCallback { + private static final Logger logger = Logger.getLogger(VrpSolutionCallback.class.getName()); + + // [START data_model] + static class DataModel { + public final long[][] distanceMatrix = { + {0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662}, + {548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210}, + {776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754}, + {696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358}, + {582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244}, + {274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708}, + {502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480}, + {194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856}, + {308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514}, + {194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468}, + {536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354}, + {502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844}, + {388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730}, + {354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536}, + {468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194}, + {776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798}, + {662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0}, + }; + public final int vehicleNumber = 4; + public final int depot = 0; + } + // [END data_model] + + // [START solution_callback_printer] + /// @brief Print the solution. + static void printSolution(RoutingIndexManager routingManager, RoutingModel routingModel) { + // Solution cost. + logger.info("################"); + logger.info("Solution objective : " + routingModel.costVar().value()); + // Inspect solution. + long totalDistance = 0; + for (int i = 0; i < routingManager.getNumberOfVehicles(); ++i) { + logger.info("Route for Vehicle " + i + ":"); + long routeDistance = 0; + long index = routingModel.start(i); + String route = ""; + while (!routingModel.isEnd(index)) { + route += routingManager.indexToNode(index) + " -> "; + long previousIndex = index; + index = routingModel.nextVar(index).value(); + routeDistance += routingModel.getArcCostForVehicle(previousIndex, index, i); + } + logger.info(route + routingManager.indexToNode(index)); + logger.info("Distance of the route: " + routeDistance + "m"); + totalDistance += routeDistance; + } + logger.info("Total distance of all routes: " + totalDistance + "m"); + } + // [END solution_callback_printer] + + // [START solution_callback] + static class SolutionCallback implements Runnable { + public final long[] objectives; + private final RoutingIndexManager routingManager; + private final RoutingModel routingModel; + private final int maxSolution; + private int counter; + + public SolutionCallback( + final RoutingIndexManager manager, + final RoutingModel routing, + int limit) + { + routingManager = manager; + routingModel = routing; + maxSolution = limit; + counter = 0; + objectives = new long[maxSolution]; + } + + @Override + public void run() { + long objective = routingModel.costVar().value(); + if(counter == 0 || objective < objectives[counter-1]) { + objectives[counter] = objective; + printSolution(routingManager, routingModel); + counter++; + } + if(counter >= maxSolution) { + routingModel.solver().finishCurrentSearch(); + } + } + }; + // [END solution_callback] + + public static void main(String[] args) throws Exception { + Loader.loadNativeLibraries(); + // Instantiate the data problem. + // [START data] + final DataModel data = new DataModel(); + // [END data] + + // Create Routing Index Manager + // [START index_manager] + final RoutingIndexManager routingManager = + new RoutingIndexManager(data.distanceMatrix.length, data.vehicleNumber, data.depot); + // [END index_manager] + + // Create Routing Model. + // [START routing_model] + final RoutingModel routingModel = new RoutingModel(routingManager); + // [END routing_model] + + // Create and register a transit callback. + // [START transit_callback] + final int transitCallbackIndex = + routingModel.registerTransitCallback((long fromIndex, long toIndex) -> { + // Convert from routing variable Index to user NodeIndex. + int fromNode = routingManager.indexToNode(fromIndex); + int toNode = routingManager.indexToNode(toIndex); + return data.distanceMatrix[fromNode][toNode]; + }); + // [END transit_callback] + + // Define cost of each arc. + // [START arc_cost] + routingModel.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex); + // [END arc_cost] + + // Add Distance constraint. + // [START distance_constraint] + routingModel.addDimension( + transitCallbackIndex, + 0, // no slack + 3000, // vehicle maximum travel distance + true, // start cumul to zero + "Distance"); + RoutingDimension distanceDimension = routingModel.getMutableDimension("Distance"); + distanceDimension.setGlobalSpanCostCoefficient(100); + // [END distance_constraint] + + // Attach a solution callback. + // [START attach_callback] + final SolutionCallback solutionCallback = new SolutionCallback(routingManager, routingModel, 15); + routingModel.addAtSolutionCallback(solutionCallback); + // [END attach_callback] + + // Setting first solution heuristic. + // [START parameters] + RoutingSearchParameters searchParameters = + main.defaultRoutingSearchParameters() + .toBuilder() + .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .setLocalSearchMetaheuristic(LocalSearchMetaheuristic.Value.GUIDED_LOCAL_SEARCH) + .setTimeLimit(Duration.newBuilder().setSeconds(5).build()) + .build(); + // [END parameters] + + // Solve the problem. + // [START solve] + Assignment solution = routingModel.solveWithParameters(searchParameters); + // [END solve] + + // Print solution on console. + // [START print_solution] + if(solution != null) { + logger.info("Best objective: " + solutionCallback.objectives[solutionCallback.objectives.length - 1]); + } else { + logger.info("No solution found !"); + } + // [END print_solution] + } +} +// [END program] diff --git a/ortools/constraint_solver/samples/vrp_solution_callback.cc b/ortools/constraint_solver/samples/vrp_solution_callback.cc new file mode 100644 index 0000000000..3499d72866 --- /dev/null +++ b/ortools/constraint_solver/samples/vrp_solution_callback.cc @@ -0,0 +1,218 @@ +// Copyright 2010-2022 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. + +// [START program] +// [START import] +#include +#include +#include +#include +#include + +#include "ortools/constraint_solver/routing.h" +#include "ortools/constraint_solver/routing_enums.pb.h" +#include "ortools/constraint_solver/routing_index_manager.h" +#include "ortools/constraint_solver/routing_parameters.h" +// [END import] + +namespace operations_research { +// [START data_model] +struct DataModel { + const std::vector> distance_matrix{ + {0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, + 776, 662}, + {548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, + 1016, 868, 1210}, + {776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, + 788, 1552, 754}, + {696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, + 1164, 560, 1358}, + {582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, + 1050, 674, 1244}, + {274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, + 1050, 708}, + {502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, + 1278, 480}, + {194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, + 742, 856}, + {308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, + 1084, 514}, + {194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, + 810, 468}, + {536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, + 388, 1152, 354}, + {502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, + 650, 274, 844}, + {388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, + 388, 730}, + {354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, + 422, 536}, + {468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, + 0, 764, 194}, + {776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, + 422, 764, 0, 798}, + {662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, + 194, 798, 0}, + }; + const int num_vehicles = 4; + const RoutingIndexManager::NodeIndex depot{0}; +}; +// [END data_model] + +//! @brief Print the solution. +//! @param[in] routing_manager Index manager used. +//! @param[in] routing_model Routing solver used. +// [START solution_callback_printer] +void print_solution( + const RoutingIndexManager& routing_manager, + const RoutingModel& routing_model) { + LOG(INFO) << "################"; + LOG(INFO) << "Solution objective: " << routing_model.CostVar()->Value(); + int64_t total_distance{0}; + for (int vehicle_id = 0; vehicle_id < routing_manager.num_vehicles(); ++vehicle_id) { + int64_t index = routing_model.Start(vehicle_id); + LOG(INFO) << "Route for Vehicle " << vehicle_id << ":"; + int64_t route_distance{0}; + std::stringstream route; + while (routing_model.IsEnd(index) == false) { + route << " " << routing_manager.IndexToNode(index).value() << " ->"; + int64_t previous_index = index; + index = routing_model.NextVar(index)->Value(); + route_distance += routing_model.GetArcCostForVehicle(previous_index, index, + int64_t{vehicle_id}); + } + LOG(INFO) << route.str() << routing_manager.IndexToNode(index).value(); + LOG(INFO) << "Distance of the route: " << route_distance << "m"; + total_distance += route_distance; + } + LOG(INFO) << "Total distance of all routes: " << total_distance << "m"; +} +// [END solution_callback_printer] + +// [START solution_callback] +struct SolutionCallback { + const RoutingIndexManager& routing_manager; + const RoutingModel& routing_model; + const int64_t max_solution; + + SolutionCallback( + const RoutingIndexManager& manager, + const RoutingModel& model, + const int64_t max_solution): + routing_manager(manager), + routing_model(model), + max_solution(max_solution) { + objectives.reserve(max_solution); + }; + ~SolutionCallback() = default; + + void Run() { + int64_t objective = routing_model.CostVar()->Value(); + if(objectives.empty() || objective < objectives.back()) { + objectives.push_back(objective); + print_solution(routing_manager, routing_model); + counter++; + } + if(counter >= max_solution) { + routing_model.solver()->FinishCurrentSearch(); + } + } + + std::vector objectives = {}; + + private: + int64_t counter = 0; +}; +// [END solution_callback] + +void VrpSolutionCallback() { + // Instantiate the data problem. + // [START data] + DataModel data; + // [END data] + + // Create Routing Index Manager. + // [START index_manager] + RoutingIndexManager routing_manager(data.distance_matrix.size(), data.num_vehicles, + data.depot); + // [END index_manager] + + // Create Routing Model. + // [START routing_model] + RoutingModel routing_model(routing_manager); + // [END routing_model] + + // Create and register a transit callback. + // [START transit_callback] + const int transit_callback_index = routing_model.RegisterTransitCallback( + [&data, &routing_manager](int64_t from_index, int64_t to_index) -> int64_t { + // Convert from routing variable Index to distance matrix NodeIndex. + auto from_node = routing_manager.IndexToNode(from_index).value(); + auto to_node = routing_manager.IndexToNode(to_index).value(); + return data.distance_matrix[from_node][to_node]; + }); + // [END transit_callback] + + // Define cost of each arc. + // [START arc_cost] + routing_model.SetArcCostEvaluatorOfAllVehicles(transit_callback_index); + // [END arc_cost] + + // Add Distance constraint. + // [START distance_constraint] + routing_model.AddDimension( + transit_callback_index, + 0, // no slack + 3000, // vehicle maximum travel distance + true, // start cumul to zero + "Distance"); + routing_model.GetMutableDimension("Distance")->SetGlobalSpanCostCoefficient(100); + // [END distance_constraint] + + // Attach a solution callback. + // [START attach_callback] + SolutionCallback solution_callback{routing_manager, routing_model, /*max_solution=*/15}; + routing_model.AddAtSolutionCallback(std::bind(&SolutionCallback::Run, &solution_callback)); + // [END attach_callback] + + // Setting first solution heuristic. + // [START parameters] + RoutingSearchParameters search_parameters = DefaultRoutingSearchParameters(); + search_parameters.set_first_solution_strategy( + FirstSolutionStrategy::PATH_CHEAPEST_ARC); + search_parameters.set_local_search_metaheuristic( + LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH); + search_parameters.mutable_time_limit()->set_seconds(5); + // [END parameters] + + // Solve the problem. + // [START solve] + const Assignment* solution = routing_model.SolveWithParameters(search_parameters); + // [END solve] + + // Print solution on console. + // [START print_solution] + if (solution != nullptr) { + LOG(INFO) << "Best objectives: " << std::to_string(solution_callback.objectives.back()); + } else { + LOG(INFO) << "No solution found."; + } + // [END print_solution] +} +} // namespace operations_research + +int main(int argc, char** argv) { + operations_research::VrpSolutionCallback(); + return EXIT_SUCCESS; +} +// [END program] diff --git a/ortools/constraint_solver/samples/vrp_solution_callback.py b/ortools/constraint_solver/samples/vrp_solution_callback.py new file mode 100755 index 0000000000..6fa9e15db8 --- /dev/null +++ b/ortools/constraint_solver/samples/vrp_solution_callback.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# Copyright 2010-2022 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. + +# [START program] +"""Simple Vehicles Routing Problem (VRP). + + This is a sample using the routing library python wrapper to solve a VRP + problem. + + The solver stop after improving its solution 15 times or after 5 seconds. + + Distances are in meters. +""" + +# [START import] +from ortools.constraint_solver import routing_enums_pb2 +from ortools.constraint_solver import pywrapcp +# [END import] + + +# [START data_model] +def create_data_model(): + """Stores the data for the problem.""" + data = {} + data['distance_matrix'] = [ + [ + 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, + 468, 776, 662 + ], + [ + 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, + 1016, 868, 1210 + ], + [ + 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, + 1130, 788, 1552, 754 + ], + [ + 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, + 1164, 560, 1358 + ], + [ + 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, + 1050, 674, 1244 + ], + [ + 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, + 514, 1050, 708 + ], + [ + 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, + 514, 1278, 480 + ], + [ + 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, + 662, 742, 856 + ], + [ + 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, + 320, 1084, 514 + ], + [ + 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, + 274, 810, 468 + ], + [ + 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, + 730, 388, 1152, 354 + ], + [ + 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, + 308, 650, 274, 844 + ], + [ + 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, + 536, 388, 730 + ], + [ + 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, + 342, 422, 536 + ], + [ + 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, + 342, 0, 764, 194 + ], + [ + 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, + 388, 422, 764, 0, 798 + ], + [ + 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, + 536, 194, 798, 0 + ], + ] + data['num_vehicles'] = 4 + data['depot'] = 0 + return data + # [END data_model] + + +# [START solution_callback_printer] +def print_solution( + routing_manager: pywrapcp.RoutingIndexManager, + routing_model: pywrapcp.RoutingModel): + """Prints solution on console.""" + print('################') + print(f'Solution objective: {routing_model.CostVar().Value()}') + total_distance = 0 + for vehicle_id in range(routing_manager.GetNumberOfVehicles()): + index = routing_model.Start(vehicle_id) + plan_output = f'Route for vehicle {vehicle_id}:\n' + route_distance = 0 + while not routing_model.IsEnd(index): + plan_output += f' {routing_manager.IndexToNode(index)} ->' + previous_index = index + index = routing_model.NextVar(index).Value() + route_distance += routing_model.GetArcCostForVehicle( + previous_index, index, vehicle_id) + plan_output += f' {routing_manager.IndexToNode(index)}\n' + plan_output += f'Distance of the route: {route_distance}m\n' + print(plan_output) + total_distance += route_distance + print(f'Total Distance of all routes: {total_distance}m') +# [END solution_callback_printer] + +# [START solution_callback] +def create_routing_monitor( + routing_manager: pywrapcp.RoutingIndexManager, + routing_model: pywrapcp.RoutingModel, + max_solution: int) -> callable: + class RoutingMonitor: + def __init__(self, + manager: pywrapcp.RoutingIndexManager, + model: pywrapcp.RoutingModel, + limit: int): + self._routing_manager = manager + self._routing_model = model + self._counter = 0 + self._counter_limit = limit + self.objectives = [] + + def __call__(self): + objective = int(self._routing_model.CostVar().Value()) + if not self.objectives or objective < self.objectives[-1]: + self.objectives.append(objective) + print_solution(self._routing_manager, self._routing_model) + self._counter += 1 + if self._counter > self._counter_limit: + self._routing_model.solver().FinishCurrentSearch() + return RoutingMonitor(routing_manager, routing_model, max_solution) +# [END solution_callback] + + +def main(): + """Entry point of the program.""" + # Instantiate the data problem. + # [START data] + data = create_data_model() + # [END data] + + # Create the routing index manager. + # [START index_manager] + manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), + data['num_vehicles'], data['depot']) + # [END index_manager] + + # Create Routing Model. + # [START routing_model] + routing_model = pywrapcp.RoutingModel(manager) + # [END routing_model] + + # Create and register a transit callback. + # [START transit_callback] + def distance_callback(from_index, to_index): + """Returns the distance between the two nodes.""" + # Convert from routing variable Index to distance matrix NodeIndex. + from_node = manager.IndexToNode(from_index) + to_node = manager.IndexToNode(to_index) + return data['distance_matrix'][from_node][to_node] + + transit_callback_index = routing_model.RegisterTransitCallback(distance_callback) + # [END transit_callback] + + # Define cost of each arc. + # [START arc_cost] + routing_model.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) + # [END arc_cost] + + # Add Distance constraint. + # [START distance_constraint] + dimension_name = 'Distance' + routing_model.AddDimension( + transit_callback_index, + 0, # no slack + 3000, # vehicle maximum travel distance + True, # start cumul to zero + dimension_name) + distance_dimension = routing_model.GetDimensionOrDie(dimension_name) + distance_dimension.SetGlobalSpanCostCoefficient(100) + # [END distance_constraint] + + # Attach a solution callback. + # [START attach_callback] + routing_monitor = create_routing_monitor(manager, routing_model, max_solution=15) + routing_model.AddAtSolutionCallback(routing_monitor) + # [END attach_callback] + + # Setting first solution heuristic. + # [START parameters] + search_parameters = pywrapcp.DefaultRoutingSearchParameters() + search_parameters.first_solution_strategy = ( + routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) + search_parameters.local_search_metaheuristic = ( + routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) + search_parameters.time_limit.FromSeconds(5) + # [END parameters] + + # Solve the problem. + # [START solve] + solution = routing_model.SolveWithParameters(search_parameters) + # [END solve] + + # Print solution on console. + # [START print_solution] + if solution: + print(f'Best objective: {routing_monitor.objectives[-1]}') + else: + print('No solution found !') + # [END print_solution] + + +if __name__ == '__main__': + main() +# [END program]