Source code for discrete_optimization.tsp.solvers.ortools_routing
"""Simple travelling salesman problem between cities."""
# Copyright (c) 2022 AIRBUS and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import print_function
import logging
from typing import Any, Optional
import numpy as np
from ortools.constraint_solver import pywrapcp, routing_enums_pb2
from discrete_optimization.generic_tools.do_problem import ParamsObjectiveFunction
from discrete_optimization.generic_tools.do_solver import ResultStorage
from discrete_optimization.tsp.problem import TspProblem, TspSolution
from discrete_optimization.tsp.solvers import TspSolver
from discrete_optimization.tsp.utils import build_matrice_distance
logger = logging.getLogger(__name__)
[docs]
class ORtoolsTspSolver(TspSolver):
def __init__(
self,
problem: TspProblem,
params_objective_function: Optional[ParamsObjectiveFunction] = None,
**kwargs: Any,
):
super().__init__(
problem=problem, params_objective_function=params_objective_function
)
self.node_count = self.problem.node_count
self.list_points = self.problem.list_points
self.start_index = self.problem.start_index
self.end_index = self.problem.end_index
self.routing: pywrapcp.RoutingModel = None
self.manager: pywrapcp.RoutingIndexManager = None
self.search_parameters = None
[docs]
def init_model(self, **kwargs: Any) -> None:
# Create the routing index manager.
if self.node_count < 1000:
matrix = build_matrice_distance(
self.node_count,
method=self.problem.evaluate_function_indexes,
)
distance_matrix = 10**6 * matrix.astype(np.int_)
def distance_callback(from_index: int, to_index: int) -> int:
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return distance_matrix[from_node, to_node]
else:
def distance_callback(from_index: int, to_index: int) -> int:
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(
10**6
* self.problem.evaluate_function_indexes(
self.list_points[from_node], self.list_points[to_node]
)
)
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(
self.node_count, 1, [self.start_index], [self.end_index]
)
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
# Define cost of each arc.
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Setting first solution heuristic.
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.seconds = 30
self.manager = manager
self.routing = routing
self.search_parameters = search_parameters
[docs]
def solve(self, time_limit: Optional[int] = 100, **kwargs: Any) -> ResultStorage:
"""Prints solution on console."""
if self.routing is None:
self.init_model(**kwargs)
self.search_parameters.time_limit.seconds = int(time_limit)
solution = self.routing.SolveWithParameters(self.search_parameters)
logger.debug(f"Objective: {solution.ObjectiveValue()} miles")
index = self.routing.Start(0)
index_real = self.manager.IndexToNode(index)
sol = [index_real]
route_distance = 0
while not self.routing.IsEnd(index):
previous_index = index
index = solution.Value(self.routing.NextVar(index))
index_real = self.manager.IndexToNode(index)
sol += [index_real]
route_distance += self.routing.GetArcCostForVehicle(
previous_index, index, 0
)
variableTsp = TspSolution(
problem=self.problem,
start_index=self.problem.start_index,
end_index=self.problem.end_index,
permutation=sol[1:-1],
lengths=None,
length=None,
)
fitness = self.aggreg_from_sol(variableTsp)
return self.create_result_storage(
[(variableTsp, fitness)],
)