Source code for discrete_optimization.vrp.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 VRP to VRPTW."""
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.vrp.problem import (
Customer2DVrpProblem,
VrpProblem,
VrpSolution,
)
from discrete_optimization.vrptw.problem import VRPTWProblem, VRPTWSolution
[docs]
class VrpToVrptwTransformation(
ProblemTransformation[VrpProblem, VrpSolution, VRPTWProblem, VRPTWSolution]
):
"""Transform VRP to VRPTW (Vehicle Routing with Time Windows).
Mapping:
- Vehicle routes → Vehicle routes with time windows
- Customer demands → Customer demands
- Vehicle capacities → Vehicle capacities
- Add relaxed time windows: [0, horizon] for all customers
- Add zero service times
VRP is a special case of VRPTW with relaxed (unconstrained) time windows.
This transformation is EXACT in both directions when time windows are wide enough.
"""
def __init__(
self, horizon: Optional[int] = None, default_service_time: float = 0.0
):
"""Initialize transformation.
Args:
horizon: Maximum time for time windows (default: computed from problem)
default_service_time: Service time for each customer (default: 0.0)
"""
self.horizon = horizon
self.default_service_time = default_service_time
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward problem transformation (VRP → VRPTW).
This direction is EXACT: VRP is exactly VRPTW with relaxed time windows.
"""
return exact_transformation(
use_cases=[
"Use VRPTW solvers to solve VRP problems",
"VRP is exactly VRPTW with wide time windows [0, horizon]",
"All VRP constraints preserved in VRPTW",
]
)
[docs]
def transform_problem(self, source_problem: VrpProblem) -> VRPTWProblem:
"""Transform VRP to VRPTW.
Args:
source_problem: VRP problem instance
Returns:
Equivalent VRPTW problem with relaxed time windows
"""
# Determine horizon if not provided
if self.horizon is None:
# Estimate: assume average speed = 1, total distance upper bound
horizon = source_problem.customer_count * 1000
else:
horizon = self.horizon
# Build distance matrix
# Check if we have 2D customers for distance calculation
if isinstance(source_problem, Customer2DVrpProblem):
# Build distance matrix from 2D coordinates
nb_nodes = source_problem.customer_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)
)
else:
# For non-2D problems, we need to build a distance matrix
# This is abstract in VrpProblem, so we estimate or use unit distances
nb_nodes = source_problem.customer_count
distance_matrix = np.ones((nb_nodes, nb_nodes))
np.fill_diagonal(distance_matrix, 0)
# Time windows: relaxed for all nodes [0, horizon]
time_windows = [(0, horizon) for _ in range(source_problem.customer_count)]
# Service times: zero or default
service_times = [
self.default_service_time for _ in range(source_problem.customer_count)
]
# Demands from VRP customers
demands = [customer.demand for customer in source_problem.customers]
# Depot: use first start index
depot_node = source_problem.start_indexes[0]
# Vehicle capacity: use first vehicle capacity (assume homogeneous fleet)
vehicle_capacity = source_problem.vehicle_capacities[0]
return VRPTWProblem(
nb_vehicles=source_problem.vehicle_count,
vehicle_capacity=vehicle_capacity,
nb_nodes=source_problem.customer_count,
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: VrpProblem
) -> VrpSolution:
"""Transform VRPTW solution back to VRP solution.
Args:
solution: VRPTW solution
source_problem: Original VRP problem
Returns:
Equivalent VRP solution
"""
# Extract routes from VRPTW solution
list_paths = [list(route) for route in solution.routes]
# Start and end indices from source problem
list_start_index = list(source_problem.start_indexes)
list_end_index = list(source_problem.end_indexes)
return VrpSolution(
problem=source_problem,
list_start_index=list_start_index,
list_end_index=list_end_index,
list_paths=list_paths,
)
[docs]
def forward_transform_solution(
self, solution: VrpSolution, target_problem: VRPTWProblem
) -> Optional[VRPTWSolution]:
"""Transform VRP solution to VRPTW solution (for warmstart).
Args:
solution: VRP solution
target_problem: Target VRPTW problem
Returns:
Equivalent VRPTW solution
"""
# Convert VRP routes to VRPTW routes
routes = [list(path) for path in solution.list_paths]
return VRPTWSolution(
problem=target_problem,
routes=routes,
)