Source code for discrete_optimization.binpack.transformations.to_salbp

#  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 BinPack to SALBP (Assembly Line Balancing)."""

from typing import Optional

from discrete_optimization.alb.base.problem import TaskData
from discrete_optimization.alb.salbp.problem import SalbpProblem, SalbpSolution
from discrete_optimization.binpack.problem import BinPackProblem, BinPackSolution
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 BinpackToSalbpTransformation( ProblemTransformation[BinPackProblem, BinPackSolution, SalbpProblem, SalbpSolution] ): """Transform BinPack problem to SALBP (Assembly Line Balancing) problem. Mapping: - Items (with weights) → Tasks (with processing times) - Bin capacity → Station cycle time - Minimize bins → Minimize stations - No precedence constraints (empty list) This transformation is INCOMPLETE IN BOTH DIRECTIONS: - Forward (problem): LOSSY - incompatibility constraints cannot be represented in SALBP - Backward (solution): LOSSY - precedence constraints from SALBP cannot be verified in BinPack Only use when: - BinPack has NO incompatibility constraints AND - SALBP has NO precedence constraints - In this case, both problems are equivalent (pure capacity allocation) """
[docs] def get_forward_metadata(self) -> TransformationMetadata: """Metadata for forward problem transformation (BinPack → SALBP). This direction is LOSSY: incompatibility constraints cannot be represented. """ losses = [ InformationLoss( name="incompatibility_constraints", loss_type=LossType.CONSTRAINT, description="Item incompatibility constraints (items that cannot be in same bin)", reason="SALBP has no concept of task incompatibility or conflict constraints", impact=LossImpact.MAJOR, workaround="Use BinPack→RCPSP transformation which models incompatibility with virtual resources, " "or pre-filter incompatible items before transformation", ) ] return lossy_transformation( losses=losses, assumptions=[ "No item incompatibility constraints (or safe to ignore)", "Pure capacity allocation problem", ], use_cases=[ "Bin packing without incompatibility constraints", "Benchmarking SALBP solvers on packing problems", "Exploring assembly line balancing formulation", ], warnings=[ "Solutions may violate incompatibility constraints if present in source problem", "Verify solution feasibility in original BinPack problem after solving", ], )
[docs] def transform_problem(self, source_problem: BinPackProblem) -> SalbpProblem: """Transform BinPack to SALBP. Args: source_problem: BinPack problem instance Returns: Equivalent SALBP problem """ # Map items to tasks with processing times = weights cycle_time = int(source_problem.capacity_bin) # Create TaskData for each item tasks_data = [ TaskData(task_id=item.index, processing_time=int(item.weight)) for item in source_problem.list_items ] # No precedence constraints (bin packing has no ordering constraints) # Incompatibility constraints are LOST (documented in forward_metadata) precedences = [] return SalbpProblem( tasks_data=tasks_data, cycle_time=cycle_time, precedences=precedences, )
[docs] def back_transform_solution( self, solution: SalbpSolution, source_problem: BinPackProblem ) -> BinPackSolution: """Transform SALBP solution back to BinPack solution. Args: solution: SALBP solution (allocation_to_station) source_problem: Original BinPack problem Returns: Equivalent BinPack solution """ # Direct mapping: station allocation → bin allocation (EXACT) # allocation_to_station[i] gives the station for task i # This directly maps to allocation[i] = bin for item i allocation = list(solution.allocation_to_station) return BinPackSolution(problem=source_problem, allocation=allocation)
[docs] def forward_transform_solution( self, solution: BinPackSolution, target_problem: SalbpProblem ) -> Optional[SalbpSolution]: """Transform BinPack solution to SALBP solution (for warmstart). Args: solution: BinPack solution target_problem: Target SALBP problem Returns: Equivalent SALBP solution for warmstart """ # Direct mapping: bin allocation → station allocation (EXACT) allocation_to_station = list(solution.allocation) return SalbpSolution( problem=target_problem, allocation_to_station=allocation_to_station )