Source code for discrete_optimization.tsp.transformations.to_tsptw

#  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 TSPTW.

TSP is a special case of TSPTW with relaxed (infinite) time windows.
"""

from typing import Optional

import numpy as np

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.tsptw.problem import TSPTWProblem, TSPTWSolution


[docs] class TspToTsptwTransformation( ProblemTransformation[TspProblem, TspSolution, TSPTWProblem, TSPTWSolution] ): """Transform TSP to TSPTW. Mapping: - Tour → Tour with relaxed time windows - No time constraints → Wide time windows [0, horizon] - Depot → Depot with wide time window - Distance matrix → Distance matrix (preserved) This transformation is EXACT: - TSP is exactly TSPTW with relaxed (unconstrained) time windows - All TSP constraints are preserved - Solution quality is identical """ def __init__(self, horizon: Optional[int] = None): """Initialize transformation. Args: horizon: Maximum time for time windows. If None, computed as sum of all distances (very conservative). """ self.horizon = horizon
[docs] def get_forward_metadata(self) -> TransformationMetadata: """Metadata for forward problem transformation (TSP → TSPTW). This direction is EXACT: TSP is exactly TSPTW with wide time windows. """ return exact_transformation( use_cases=[ "Use TSPTW solvers to solve TSP problems", "TSP is exactly TSPTW with relaxed time windows [0, horizon]", "All TSP constraints preserved in TSPTW", "No time window constraints in TSP → wide windows in TSPTW", ] )
[docs] def transform_problem(self, source_problem: TspProblem) -> TSPTWProblem: """Transform TSP to TSPTW. Args: source_problem: TSP problem instance Returns: Equivalent TSPTW problem with relaxed time windows """ # Build distance matrix (TSP uses evaluate_function_indexes) nb_nodes = source_problem.node_count distance_matrix = np.zeros((nb_nodes, nb_nodes)) for i in range(nb_nodes): for j in range(nb_nodes): if i != j: distance_matrix[i, j] = source_problem.evaluate_function_indexes( i, j ) # Determine horizon if not provided if self.horizon is None: # Very conservative: sum of all distances horizon = int(np.sum(distance_matrix) + 1000) else: horizon = self.horizon # Wide time windows for all nodes (no constraints) time_windows = [(0, horizon) for _ in range(nb_nodes)] # Depot from TSP depot_node = source_problem.start_index return TSPTWProblem( nb_nodes=nb_nodes, distance_matrix=distance_matrix, time_windows=time_windows, depot_node=depot_node, )
[docs] def back_transform_solution( self, solution: TSPTWSolution, source_problem: TspProblem ) -> TspSolution: """Transform TSPTW solution back to TSP solution. Args: solution: TSPTW solution source_problem: Original TSP problem Returns: Equivalent TSP solution """ return TspSolution( problem=source_problem, permutation=list(solution.permutation), start_index=source_problem.start_index, end_index=source_problem.end_index, )
[docs] def forward_transform_solution( self, solution: TspSolution, target_problem: TSPTWProblem ) -> Optional[TSPTWSolution]: """Transform TSP solution to TSPTW solution (for warmstart). Args: solution: TSP solution target_problem: Target TSPTW problem Returns: Equivalent TSPTW solution """ return TSPTWSolution( problem=target_problem, permutation=list(solution.permutation), )