Source code for discrete_optimization.tsp.transformations.to_vrp

#  Copyright (c) 2026 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.

"""Transformation from TSP to VRP.

This transformation is EXACT: TSP is a special case of VRP with 1 vehicle,
infinite capacity (or zero demands), and all customers must be visited.
"""

from typing import Optional

from discrete_optimization.generic_tools.transformation.problem_transformation import (
    ProblemTransformation,
)
from discrete_optimization.generic_tools.transformation.transformation_metadata import (
    TransformationMetadata,
    exact_transformation,
)
from discrete_optimization.tsp.problem import TspProblem, TspSolution
from discrete_optimization.vrp.problem import BasicCustomer, VrpProblem, VrpSolution


[docs] class TspToVrpTransformation( ProblemTransformation[TspProblem, TspSolution, VrpProblem, VrpSolution] ): """Transform TSP to VRP. Mapping: - Single tour → Single vehicle route - All nodes must be visited → All customers served - No capacity constraints → Infinite capacity (or zero demands) - Start/end depot → VRP start/end depot This transformation is EXACT: - TSP is exactly VRP with 1 vehicle and no capacity constraints - All TSP constraints are preserved in VRP formulation Solution mapping is exact in both directions: - TSP tour → VRP route for vehicle 0 - VRP route (single vehicle) → TSP tour """
[docs] def get_forward_metadata(self) -> TransformationMetadata: """Metadata for forward problem transformation (TSP → VRP). This direction is EXACT: TSP is a perfect subset of VRP. """ return exact_transformation( use_cases=[ "Use VRP solvers to solve TSP problems", "TSP is exactly VRP with 1 vehicle and infinite capacity", "All TSP constraints preserved in VRP", ] )
[docs] def transform_problem(self, source_problem: TspProblem) -> VrpProblem: """Transform TSP to VRP. Args: source_problem: TSP problem instance Returns: Equivalent VRP problem with 1 vehicle """ # In VRP, we need ALL nodes including the depot # VRP uses indices 0..node_count-1 # We create customers for ALL TSP nodes (including depot) customers = [ BasicCustomer(name=i, demand=0.0) # Zero demand = infinite capacity for i in range(source_problem.node_count) ] # Create concrete VRP problem class # We need to implement the abstract methods class TspDerivedVrpProblem(VrpProblem): def __init__(self, tsp_problem: TspProblem, *args, **kwargs): super().__init__(*args, **kwargs) self.tsp_problem = tsp_problem def evaluate_function(self, var_vrp: VrpSolution): # Convert VRP solution to TSP solution and evaluate if not var_vrp.list_paths or len(var_vrp.list_paths[0]) == 0: return [[]], [0.0], float("inf"), [0.0] # Extract the single route - VRP node indices = TSP node indices vrp_route = var_vrp.list_paths[0] # VRP route contains node indices that directly correspond to TSP nodes # Just use them as the permutation tsp_permutation = vrp_route # Build TSP solution tsp_solution = TspSolution( problem=self.tsp_problem, permutation=tsp_permutation, start_index=var_vrp.list_start_index[0], end_index=var_vrp.list_end_index[0], ) # Evaluate in TSP self.tsp_problem.evaluate(tsp_solution) # Return in VRP format # lengths should be list[list[float]] - one list per vehicle lengths = [tsp_solution.lengths] if tsp_solution.lengths else [[]] obj = tsp_solution.length if tsp_solution.length else 0.0 return lengths, [obj], obj, [0.0] # Zero capacity used def evaluate_function_indexes(self, index_1: int, index_2: int) -> float: return self.tsp_problem.evaluate_function_indexes(index_1, index_2) return TspDerivedVrpProblem( tsp_problem=source_problem, customers=customers, vehicle_count=1, vehicle_capacities=[float("inf")], # Infinite capacity customer_count=source_problem.node_count, # ALL nodes including depot start_indexes=[source_problem.start_index], end_indexes=[source_problem.end_index], )
[docs] def back_transform_solution( self, solution: VrpSolution, source_problem: TspProblem ) -> TspSolution: """Transform VRP solution back to TSP solution. Args: solution: VRP solution (should have 1 vehicle) source_problem: Original TSP problem Returns: Equivalent TSP solution """ # Extract the single vehicle route if not solution.list_paths or len(solution.list_paths) == 0: # Empty solution permutation = list(source_problem.ind_in_permutation) else: # VRP node indices directly correspond to TSP node indices # Just use them as the permutation permutation = solution.list_paths[0] return TspSolution( problem=source_problem, permutation=permutation, start_index=source_problem.start_index, end_index=source_problem.end_index, )
[docs] def forward_transform_solution( self, solution: TspSolution, target_problem: VrpProblem ) -> Optional[VrpSolution]: """Transform TSP solution to VRP solution (for warmstart). Args: solution: TSP solution target_problem: Target VRP problem Returns: Equivalent VRP solution with single vehicle route """ # VRP node indices directly correspond to TSP node indices # Just use the TSP permutation as the VRP route return VrpSolution( problem=target_problem, list_start_index=[solution.start_index], list_end_index=[solution.end_index], list_paths=[solution.permutation], )