Source code for discrete_optimization.alb.salbp.transformations.to_facility
# 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 SALBP to Facility Location.
Creates a facility problem with uniform facilities and zero distances.
"""
from typing import Optional
from discrete_optimization.alb.salbp.problem import SalbpProblem, SalbpSolution
from discrete_optimization.facility.problem import (
Customer,
Facility,
FacilityProblem,
FacilitySolution,
Point,
)
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,
)
[docs]
class SalbpToFacilityTransformation(
ProblemTransformation[
SalbpProblem, SalbpSolution, FacilityProblem, FacilitySolution
]
):
"""Transform SALBP to Facility Location.
Mapping:
- Tasks → Customers (demand = task_time)
- Stations → Facilities (capacity = cycle_time)
- Minimize stations → Minimize facilities
- Precedence DISCARDED (Facility has no precedence concept)
This transformation is ASYMMETRIC:
- Forward (problem): LOSSY - precedence constraints lost
- Backward (solution): EXACT - facility assignments map to station assignments
Creates:
- Uniform facilities (all same capacity = cycle_time, zero setup cost)
- Zero distances between customers and facilities
- This makes it equivalent to pure capacity allocation
"""
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward problem transformation (SALBP → Facility).
This direction is LOSSY: precedence constraints cannot be represented.
"""
losses = [
InformationLoss(
name="precedence_constraints",
loss_type=LossType.CONSTRAINT,
description="Task precedence constraints (task i must come before task j)",
reason="Facility Location has no concept of ordering or precedence between customers",
impact=LossImpact.MAJOR,
workaround="Use SALBP→RCPSP transformation which preserves precedence constraints",
)
]
return lossy_transformation(
losses=losses,
assumptions=[
"No task precedence constraints (or safe to ignore)",
"Pure capacity allocation problem",
],
use_cases=[
"SALBP without precedence constraints (equivalent to bin packing / facility location)",
"Benchmarking Facility solvers on balancing problems without precedence",
],
warnings=[
"Solutions may violate precedence constraints if present in source problem",
"Verify that source problem has no precedence before using this transformation",
],
)
[docs]
def transform_problem(self, source_problem: SalbpProblem) -> FacilityProblem:
"""Transform SALBP to Facility Location.
Args:
source_problem: SALBP problem instance
Returns:
Equivalent Facility Location problem
"""
# Create customers from tasks
customers = [
Customer(
index=task,
demand=float(source_problem.task_times[task]),
location=Point(x=0.0, y=0.0), # Dummy location
)
for task in source_problem.tasks
]
# Create facilities (one per possible station)
# Maximum stations = number of tasks (worst case)
max_facilities = source_problem.nb_tasks
facilities = [
Facility(
index=i,
setup_cost=0.0, # Uniform cost (minimize count)
capacity=float(source_problem.cycle_time),
location=Point(x=0.0, y=0.0), # Dummy location
)
for i in range(max_facilities)
]
# Create a concrete FacilityProblem subclass
# We need to implement evaluate_customer_facility
class SalbpDerivedFacilityProblem(FacilityProblem):
def evaluate_customer_facility(
self, facility: Facility, customer: Customer
) -> float:
# Zero distance (pure capacity allocation)
return 0.0
return SalbpDerivedFacilityProblem(
facility_count=len(facilities),
customer_count=len(customers),
facilities=facilities,
customers=customers,
)
[docs]
def back_transform_solution(
self, solution: FacilitySolution, source_problem: SalbpProblem
) -> SalbpSolution:
"""Transform Facility Location solution back to SALBP solution.
Args:
solution: Facility Location solution
source_problem: Original SALBP problem
Returns:
Equivalent SALBP solution
"""
# Direct mapping: facility allocation → station allocation
allocation_to_station = list(solution.facility_for_customers)
return SalbpSolution(
problem=source_problem,
allocation_to_station=allocation_to_station,
)
[docs]
def forward_transform_solution(
self, solution: SalbpSolution, target_problem: FacilityProblem
) -> Optional[FacilitySolution]:
"""Transform SALBP solution to Facility Location solution (for warmstart).
Args:
solution: SALBP solution
target_problem: Target Facility Location problem
Returns:
Equivalent Facility Location solution for warmstart
"""
# Direct mapping: station allocation → facility allocation
facility_for_customers = list(solution.allocation_to_station)
return FacilitySolution(
problem=target_problem,
facility_for_customers=facility_for_customers,
)