Source code for discrete_optimization.vrp.transformations.to_top
# 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 Team Orienteering Problem (TOP)."""
from typing import Optional
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.top.problem import (
CustomerTop2D,
TeamOrienteeringProblem2D,
)
from discrete_optimization.vrp.problem import (
Customer2DVrpProblem,
VrpSolution,
)
[docs]
class VrpToTopTransformation(
ProblemTransformation[
Customer2DVrpProblem, VrpSolution, TeamOrienteeringProblem2D, VrpSolution
]
):
"""Transform VRP (2D) to Team Orienteering Problem (TOP).
Mapping:
- VRP customers → TOP customers with rewards
- VRP vehicle routes → TOP tours
- VRP capacity constraints → Dropped (TOP has no capacity)
- Add max_length_tours constraint
- Objective: minimize total distance → maximize total reward
Note: This transformation assigns uniform rewards (reward=1) to all customers.
Users can modify rewards after transformation if needed.
"""
def __init__(self, max_length_tours: Optional[float] = None, reward_function=None):
"""Initialize transformation.
Args:
max_length_tours: Maximum length for each tour (default: computed from problem)
reward_function: Function to compute reward from Customer2D (default: uniform reward=1)
"""
self.max_length_tours = max_length_tours
self.reward_function = reward_function or (lambda customer: 1.0)
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward transformation (VRP → TOP)."""
return lossy_transformation(
losses=[
InformationLoss(
name="heterogeneous_capacity",
loss_type=LossType.CONSTRAINT,
description="top cant model yet varying capacity per vehicle",
reason="",
impact=LossImpact.MAJOR,
)
],
use_cases=[
"Use TOP solvers for VRP problems",
"VRP can be modeled as TOP with uniform rewards",
"Reframe VRP as reward maximization problem",
],
)
[docs]
def transform_problem(
self, source_problem: Customer2DVrpProblem
) -> TeamOrienteeringProblem2D:
"""Transform VRP to TOP.
Args:
source_problem: VRP problem instance (must be 2D)
Returns:
Equivalent TOP problem with rewards
"""
# Determine max_length_tours if not provided
if self.max_length_tours is None:
# Estimate: sum of all pairwise distances / number of vehicles
total_distance_estimate = 0.0
nb_customers = source_problem.customer_count
for i in range(nb_customers):
for j in range(i + 1, nb_customers):
total_distance_estimate += source_problem.evaluate_function_indexes(
i, j
)
max_length = (
total_distance_estimate / source_problem.vehicle_count
if source_problem.vehicle_count > 0
else total_distance_estimate
)
else:
max_length = self.max_length_tours
# Create TOP customers with rewards
top_customers = [
CustomerTop2D(
name=customer.name,
reward=self.reward_function(customer),
x=customer.x,
y=customer.y,
)
for customer in source_problem.customers
]
return TeamOrienteeringProblem2D(
vehicle_count=source_problem.vehicle_count,
max_length_tours=max_length,
customer_count=source_problem.customer_count,
customers=top_customers,
start_indexes=list(source_problem.start_indexes),
end_indexes=list(source_problem.end_indexes),
)
[docs]
def back_transform_solution(
self, solution: VrpSolution, source_problem: Customer2DVrpProblem
) -> VrpSolution:
"""Transform TOP solution back to VRP solution.
Args:
solution: TOP solution (uses VrpSolution representation)
source_problem: Original VRP problem
Returns:
Equivalent VRP solution
"""
# TOP and VRP use the same solution representation (VrpSolution)
# Just need to change the problem reference
return VrpSolution(
problem=source_problem,
list_start_index=solution.list_start_index,
list_end_index=solution.list_end_index,
list_paths=solution.list_paths,
lengths=solution.lengths,
length=solution.length,
capacities=solution.capacities,
)
[docs]
def forward_transform_solution(
self, solution: VrpSolution, target_problem: TeamOrienteeringProblem2D
) -> Optional[VrpSolution]:
"""Transform VRP solution to TOP solution (for warmstart).
Args:
solution: VRP solution
target_problem: Target TOP problem
Returns:
Equivalent TOP solution
"""
# Both use VrpSolution representation, just change problem reference
return VrpSolution(
problem=target_problem,
list_start_index=solution.list_start_index,
list_end_index=solution.list_end_index,
list_paths=solution.list_paths,
lengths=solution.lengths,
length=solution.length,
capacities=solution.capacities,
)