Source code for discrete_optimization.facility.transformations.to_binpack

#  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 Facility Location to BinPacking (reverse direction).

Note: This transformation DISCARDS facility setup costs and distances!
Assumes uniform facility costs (all equal).
"""

from typing import Optional

from discrete_optimization.binpack.problem import (
    BinPackProblem,
    BinPackSolution,
    ItemBinPack,
)
from discrete_optimization.facility.problem import FacilityProblem, FacilitySolution
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 FacilityToBinpackTransformation( ProblemTransformation[ FacilityProblem, FacilitySolution, BinPackProblem, BinPackSolution ] ): """Transform Facility Location to BinPacking (reverse direction). Mapping: - Customers (with demands) → Items (with weights) - Facility capacity → Bin capacity - Minimize facilities → Minimize bins - **Setup costs and distances DISCARDED** This transformation is ASYMMETRIC: - Forward (problem): LOSSY - setup costs, assignment costs, and heterogeneous capacities lost - Backward (solution): EXACT - bin assignments map to facility assignments Assumptions: - All facilities have SAME capacity (uses first facility's capacity) - Setup costs IGNORED (assumed uniform) - Distances/allocation costs IGNORED Use case: - Facility location without distance costs = bin packing - Capacitated facility location simplified to bin packing - Testing different solvers """
[docs] def get_forward_metadata(self) -> TransformationMetadata: """Metadata for forward problem transformation (Facility → BinPack). This direction is LOSSY: setup costs, assignment costs, and heterogeneous capacities lost. """ losses = [ InformationLoss( name="setup_costs", loss_type=LossType.OBJECTIVE, description="Facility setup costs (opening costs)", reason="BinPacking has no concept of setup/opening costs - all bins have implicit uniform cost", impact=LossImpact.MAJOR, workaround="Cannot be modeled in BinPacking. Use Facility solvers directly if setup costs matter.", ), InformationLoss( name="assignment_costs", loss_type=LossType.OBJECTIVE, description="Customer-to-facility assignment costs (distances)", reason="BinPacking has no concept of assignment costs - items have no location/distance", impact=LossImpact.MAJOR, workaround="Cannot be modeled in BinPacking. Use Facility solvers directly if distances matter.", ), InformationLoss( name="heterogeneous_capacities", loss_type=LossType.PARAMETER, description="Different facility capacities (if facilities have different capacities)", reason="BinPacking assumes uniform bin capacity. Transformation uses first facility's capacity.", impact=LossImpact.MODERATE, workaround="Verify all facilities have same capacity, or use multi-capacity bin packing variant.", ), ] return lossy_transformation( losses=losses, assumptions=[ "Setup costs are uniform or negligible", "Assignment costs (distances) are zero or negligible", "All facilities have same capacity (or only first facility capacity matters)", ], use_cases=[ "Facility location problem without distance costs (pure capacity allocation)", "Benchmarking BinPack solvers on facility-like problems", "When only capacity constraints matter, not costs", ], warnings=[ "Solutions may be suboptimal in original Facility problem due to ignored costs", "Verify that setup costs and assignment costs are truly negligible before using", "Check that all facilities have same capacity", ], )
[docs] def transform_problem(self, source_problem: FacilityProblem) -> BinPackProblem: """Transform Facility Location to BinPacking. Args: source_problem: Facility Location problem instance Returns: Equivalent BinPacking problem (ignoring distances/costs) """ # Map customers to items list_items = [ ItemBinPack(index=customer.index, weight=customer.demand) for customer in source_problem.customers ] # Use first facility's capacity (assume all facilities have same capacity) # This is a simplification - facility location can have heterogeneous capacities capacity_bin = int(source_problem.facilities[0].capacity) return BinPackProblem( list_items=list_items, capacity_bin=capacity_bin, incompatible_items=set(), )
[docs] def back_transform_solution( self, solution: BinPackSolution, source_problem: FacilityProblem ) -> FacilitySolution: """Transform BinPacking solution back to Facility Location solution. Args: solution: BinPacking solution source_problem: Original Facility Location problem Returns: Equivalent Facility Location solution """ # Direct mapping: bin allocation → facility allocation facility_for_customers = list(solution.allocation) return FacilitySolution( problem=source_problem, facility_for_customers=facility_for_customers, )
[docs] def forward_transform_solution( self, solution: FacilitySolution, target_problem: BinPackProblem ) -> Optional[BinPackSolution]: """Transform Facility solution to BinPacking solution (for warmstart). Args: solution: Facility Location solution target_problem: Target BinPacking problem Returns: Equivalent BinPacking solution for warmstart """ # Direct mapping: facility allocation → bin allocation allocation = list(solution.facility_for_customers) return BinPackSolution(problem=target_problem, allocation=allocation)