Source code for discrete_optimization.vrptw.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 VRPTW to VRP (lossy - drops 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 (
InformationLoss,
LossImpact,
LossType,
TransformationMetadata,
lossy_transformation,
)
from discrete_optimization.vrp.problem import (
Customer2D,
Customer2DVrpProblem,
VrpSolution,
)
from discrete_optimization.vrptw.problem import VRPTWProblem, VRPTWSolution
[docs]
class VrptwToVrpTransformation(
ProblemTransformation[
VRPTWProblem, VRPTWSolution, Customer2DVrpProblem, VrpSolution
]
):
"""Transform VRPTW to VRP (LOSSY transformation).
Mapping:
- Vehicle routes with time windows → Vehicle routes (no time constraints)
- Customer demands → Customer demands
- Vehicle capacities → Vehicle capacities
- Time windows → LOST
- Service times → LOST
This is a LOSSY transformation because:
- Time window constraints are dropped
- Service times are ignored
- Solutions from VRP may violate time windows in original VRPTW
Use cases:
- Get initial solutions from VRP solvers (faster, simpler)
- Benchmark VRP solvers on VRPTW instances (ignoring time)
- Analyze impact of time windows by comparing with unconstrained VRP
"""
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward transformation (VRPTW → VRP)."""
return lossy_transformation(
losses=[
InformationLoss(
name="time_windows",
loss_type=LossType.CONSTRAINT,
description="Customer time window constraints [earliest, latest]",
reason="VRP has no concept of time or temporal constraints",
impact=LossImpact.MAJOR,
workaround="Post-process VRP solution to check time window feasibility",
),
InformationLoss(
name="service_times",
loss_type=LossType.PARAMETER,
description="Service time at each customer location",
reason="VRP doesn't model service times",
impact=LossImpact.MODERATE,
workaround="Add service times to route evaluation post-hoc",
),
],
use_cases=[
"Get quick initial solutions from VRP solvers",
"Benchmark VRP algorithms on VRPTW instances",
"Analyze impact of time windows on routing decisions",
],
warnings=[
"VRP solutions may violate time windows",
"Service times are ignored in VRP optimization",
"Route feasibility must be verified against time constraints",
],
)
[docs]
def transform_problem(self, source_problem: VRPTWProblem) -> Customer2DVrpProblem:
"""Transform VRPTW to VRP.
Args:
source_problem: VRPTW problem instance
Returns:
VRP problem (time windows and service times dropped)
"""
# Extract 2D coordinates from distance matrix
# VRPTW uses distance_matrix, we need to create Customer2D objects
nb_nodes = source_problem.nb_nodes
# Reconstruct 2D coordinates using MDS (Multi-Dimensional Scaling) approximation
# For simplicity, we'll use a heuristic: place nodes on a grid
# In practice, you might need the original coordinates if available
customers = []
grid_size = int(np.ceil(np.sqrt(nb_nodes)))
for i in range(nb_nodes):
# Simple grid layout (can be improved with MDS)
x = (i % grid_size) * 100.0
y = (i // grid_size) * 100.0
demand = source_problem.demands[i]
customers.append(
Customer2D(
name=i,
demand=demand,
x=x,
y=y,
)
)
# Note: This is a heuristic layout. In practice, VRPTW problems
# often come from files that include coordinates. Users should
# provide coordinates if available.
# Vehicle parameters
vehicle_count = source_problem.nb_vehicles
vehicle_capacities = [source_problem.vehicle_capacity] * vehicle_count
# Depot: VRPTW depot_node becomes start/end index for all vehicles
depot = source_problem.depot_node
start_indexes = [depot] * vehicle_count
end_indexes = [depot] * vehicle_count
return Customer2DVrpProblem(
vehicle_count=vehicle_count,
vehicle_capacities=vehicle_capacities,
customer_count=nb_nodes,
customers=customers,
start_indexes=start_indexes,
end_indexes=end_indexes,
)
[docs]
def back_transform_solution(
self, solution: VrpSolution, source_problem: VRPTWProblem
) -> VRPTWSolution:
"""Transform VRP solution back to VRPTW solution.
Args:
solution: VRP solution
source_problem: Original VRPTW problem
Returns:
VRPTW solution (may violate time windows!)
Warning:
The returned solution may not satisfy time window constraints.
Use problem.satisfy() to check feasibility.
"""
# Extract routes from VRP solution
routes = [list(path) for path in solution.list_paths]
return VRPTWSolution(
problem=source_problem,
routes=routes,
)
[docs]
def forward_transform_solution(
self, solution: VRPTWSolution, target_problem: Customer2DVrpProblem
) -> Optional[VrpSolution]:
"""Transform VRPTW solution to VRP solution (for warmstart).
Args:
solution: VRPTW solution
target_problem: Target VRP problem
Returns:
Equivalent VRP solution
"""
# Convert VRPTW routes to VRP routes
list_paths = [list(route) for route in solution.routes]
# Start and end indices from target problem
list_start_index = list(target_problem.start_indexes)
list_end_index = list(target_problem.end_indexes)
return VrpSolution(
problem=target_problem,
list_start_index=list_start_index,
list_end_index=list_end_index,
list_paths=list_paths,
)