Source code for discrete_optimization.tsptw.transformations.to_vrptw
# 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 TSPTW to VRPTW.
TSP with Time Windows is a special case of VRP with Time Windows:
- Single vehicle
- No capacity constraints (zero demands)
- Time windows for each customer
- 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.tsptw.problem import TSPTWProblem, TSPTWSolution
from discrete_optimization.vrptw.problem import VRPTWProblem, VRPTWSolution
[docs]
class TsptwToVrptwTransformation(
ProblemTransformation[TSPTWProblem, TSPTWSolution, VRPTWProblem, VRPTWSolution]
):
"""Transform TSPTW to VRPTW.
Mapping:
- Single tour → Single vehicle route
- Time windows → Customer time windows
- All customers must be visited → All customers served
- No capacity constraints → Zero demands + infinite capacity
- Depot with time window → VRPTW depot with time window
This transformation is EXACT in both directions:
- TSPTW is exactly VRPTW with 1 vehicle and no capacity constraints
- All TSPTW constraints (time windows) are preserved in VRPTW
- Solution mapping is exact both ways
"""
def __init__(self, default_service_time: float = 0.0):
"""Initialize transformation.
Args:
default_service_time: Service time for each customer (default: 0.0).
Note: In TSPTW, service time is often included in the
distance matrix. Set to 0.0 if already included in distances.
"""
self.default_service_time = default_service_time
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward problem transformation (TSPTW → VRPTW).
This direction is EXACT: TSPTW is exactly VRPTW with 1 vehicle, no capacity,
and time windows.
"""
return exact_transformation(
use_cases=[
"Use VRPTW solvers to solve TSPTW problems",
"TSPTW is exactly VRPTW with 1 vehicle and infinite capacity",
"All TSPTW constraints (time windows) preserved in VRPTW",
"Time windows constrain arrival/service times at each customer",
]
)
[docs]
def transform_problem(self, source_problem: TSPTWProblem) -> VRPTWProblem:
"""Transform TSPTW to VRPTW.
Args:
source_problem: TSPTW problem instance
Returns:
Equivalent VRPTW problem with 1 vehicle and no capacity constraints
"""
nb_nodes = source_problem.nb_nodes
# Use existing distance matrix from TSPTW
distance_matrix = source_problem.distance_matrix.copy()
# Time windows from TSPTW
time_windows = list(source_problem.time_windows)
# Service times: zero or default (TSPTW often includes service in distance)
service_times = [self.default_service_time for _ in range(nb_nodes)]
# Demands: zero (no capacity constraints in TSPTW)
demands = [0.0 for _ in range(nb_nodes)]
# Single vehicle with infinite capacity
nb_vehicles = 1
vehicle_capacity = float("inf")
# Depot node from TSPTW
depot_node = source_problem.depot_node
return VRPTWProblem(
nb_vehicles=nb_vehicles,
vehicle_capacity=vehicle_capacity,
nb_nodes=nb_nodes,
distance_matrix=distance_matrix,
time_windows=time_windows,
service_times=service_times,
demands=demands,
depot_node=depot_node,
)
[docs]
def back_transform_solution(
self, solution: VRPTWSolution, source_problem: TSPTWProblem
) -> TSPTWSolution:
"""Transform VRPTW solution back to TSPTW solution.
Args:
solution: VRPTW solution (should have 1 vehicle)
source_problem: Original TSPTW problem
Returns:
Equivalent TSPTW solution
"""
# Extract the single vehicle route
if not solution.routes or len(solution.routes) == 0:
# Empty solution - use customer order
permutation = list(source_problem.customers)
else:
# Get first route (single vehicle)
route = solution.routes[0]
# Filter out depot if present (should not be in TSPTW permutation)
permutation = [node for node in route if node != source_problem.depot_node]
return TSPTWSolution(
problem=source_problem,
permutation=permutation,
)
[docs]
def forward_transform_solution(
self, solution: TSPTWSolution, target_problem: VRPTWProblem
) -> Optional[VRPTWSolution]:
"""Transform TSPTW solution to VRPTW solution (for warmstart).
Args:
solution: TSPTW solution
target_problem: Target VRPTW problem
Returns:
Equivalent VRPTW solution with single vehicle route
"""
# TSPTW permutation becomes the single vehicle route
routes = [list(solution.permutation)]
return VRPTWSolution(
problem=target_problem,
routes=routes,
)