Source code for discrete_optimization.gpdp.transformations.from_tsp
# 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 GPDP (General Pickup and Delivery Problem)."""
from typing import Optional
from discrete_optimization.generic_tools.transformation.problem_transformation import (
ProblemTransformation,
)
from discrete_optimization.generic_tools.transformation.transformation_metadata import (
TransformationMetadata,
subset_transformation,
)
from discrete_optimization.gpdp.problem import GpdpProblem, GpdpSolution
from discrete_optimization.tsp.problem import Point2DTspProblem, TspSolution
[docs]
class TspToGpdpTransformation(
ProblemTransformation[Point2DTspProblem, TspSolution, GpdpProblem, GpdpSolution]
):
"""Transform TSP to GPDP.
TSP is a special case of GPDP with a single vehicle and no capacity constraints.
"""
def __init__(self, compute_graph: bool = False):
self.compute_graph = compute_graph
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
return subset_transformation(
use_cases=["Use GPDP solvers for TSP problems"],
assumptions=["Single vehicle", "No capacity constraints"],
)
[docs]
def transform_problem(self, source_problem: Point2DTspProblem) -> GpdpProblem:
"""Transform TSP to GPDP using the existing ProxyClass implementation."""
# Reuse the existing ProxyClass method (cleaned up version)
from discrete_optimization.gpdp.problem import ProxyClass
return ProxyClass.from_tsp_to_gpdp(
source_problem, compute_graph=self.compute_graph
)
[docs]
def back_transform_solution(
self, solution: GpdpSolution, source_problem: Point2DTspProblem
) -> TspSolution:
"""Transform GPDP solution back to TSP solution."""
# Extract the single vehicle's trajectory
trajectory = solution.trajectories[0] if 0 in solution.trajectories else []
# Filter to get only customer nodes (exclude origin/target)
permutation = [
node
for node in trajectory
if node not in {0, len(source_problem.list_points) - 1}
]
return TspSolution(problem=source_problem, permutation=permutation)
[docs]
def forward_transform_solution(
self, solution: TspSolution, target_problem: GpdpProblem
) -> Optional[GpdpSolution]:
"""Transform TSP solution to GPDP solution."""
# Build trajectory from TSP permutation
trajectories = {
0: [target_problem.origin_vehicle[0]]
+ list(solution.permutation)
+ [target_problem.target_vehicle[0]]
}
# Compute times
times = {}
current_time = 0.0
traj = trajectories[0]
for i, node in enumerate(traj):
times[node] = current_time
if i < len(traj) - 1:
next_node = traj[i + 1]
current_time += target_problem.distance_delta[node][next_node]
return GpdpSolution(
problem=target_problem,
trajectories=trajectories,
times=times,
resource_evolution={},
)