Source code for discrete_optimization.alb.salbp.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 SALBP to BinPacking (reverse direction).

Note: This transformation DISCARDS precedence constraints!
Only use when precedence can be safely ignored or doesn't exist.
"""

from typing import Optional

from discrete_optimization.alb.salbp.problem import SalbpProblem, SalbpSolution
from discrete_optimization.binpack.problem import (
    BinPackProblem,
    BinPackSolution,
    ItemBinPack,
)
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 SalbpToBinpackTransformation( ProblemTransformation[SalbpProblem, SalbpSolution, BinPackProblem, BinPackSolution] ): """Transform SALBP to BinPacking (reverse direction). Mapping: - Tasks (with processing times) → Items (with weights) - Station cycle time → Bin capacity - Minimize stations → Minimize bins - **Precedence constraints DISCARDED** This transformation is INCOMPLETE IN BOTH DIRECTIONS: - Forward (problem): LOSSY - precedence constraints cannot be represented in BinPack - Backward (solution): LOSSY - incompatibility constraints from BinPack cannot be verified in SALBP Only use when: - SALBP has NO precedence constraints AND - BinPack has NO incompatibility constraints - In this case, both problems are equivalent (pure capacity allocation) Warning: This transformation LOSES precedence information! Solutions from BinPack solvers may violate precedence if present in SALBP. """
[docs] def get_forward_metadata(self) -> TransformationMetadata: """Metadata for forward problem transformation (SALBP → BinPack). 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="BinPacking has no concept of ordering or precedence between items", impact=LossImpact.MAJOR, workaround="Use SALBP→RCPSP transformation which preserves precedence constraints, " "or verify that precedence list is empty before transformation", ) ] 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)", "Testing BinPack solvers on SALBP instances 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) -> BinPackProblem: """Transform SALBP to BinPacking. Args: source_problem: SALBP problem instance Returns: Equivalent BinPacking problem (without precedence) """ # Map tasks to items list_items = [ ItemBinPack(index=task, weight=float(source_problem.task_times[task])) for task in source_problem.tasks ] # Capacity = cycle time capacity_bin = int(source_problem.cycle_time) # Precedence constraints are LOST (documented in forward_metadata) # BinPacking has no precedence concept return BinPackProblem( list_items=list_items, capacity_bin=capacity_bin, incompatible_items=set(), # No incompatibility in base SALBP )
[docs] def back_transform_solution( self, solution: BinPackSolution, source_problem: SalbpProblem ) -> SalbpSolution: """Transform BinPacking solution back to SALBP solution. Args: solution: BinPacking solution source_problem: Original SALBP problem Returns: Equivalent SALBP solution """ # Direct mapping: bin allocation → station allocation (EXACT) allocation_to_station = list(solution.allocation) return SalbpSolution( problem=source_problem, allocation_to_station=allocation_to_station, )
[docs] def forward_transform_solution( self, solution: SalbpSolution, target_problem: BinPackProblem ) -> Optional[BinPackSolution]: """Transform SALBP solution to BinPacking solution (for warmstart). Args: solution: SALBP solution target_problem: Target BinPacking problem Returns: Equivalent BinPacking solution for warmstart """ # Direct mapping: station allocation → bin allocation (EXACT) allocation = list(solution.allocation_to_station) return BinPackSolution(problem=target_problem, allocation=allocation)