Source code for discrete_optimization.shop.transformations.to_generic_scheduling

#  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 CommonShopProblem (JSP/FJSP/OSP) to GenericSchedulingImpl."""

from typing import Optional

from discrete_optimization.generic_tasks_tools.generic_scheduling_impl import (
    GenericSchedulingImplProblem,
    GenericSchedulingImplSolution,
)
from discrete_optimization.generic_tasks_tools.generic_scheduling_utils import (
    RawSolution,
    TaskVariable,
)
from discrete_optimization.generic_tools.transformation.problem_transformation import (
    ProblemTransformation,
)
from discrete_optimization.shop.base import AnyShopSolution, CommonShopProblem, Task


[docs] class ShopToGenericSchedulingTransformation( ProblemTransformation[ CommonShopProblem, AnyShopSolution, GenericSchedulingImplProblem, GenericSchedulingImplSolution, ] ): """Transform CommonShopProblem to GenericSchedulingImplProblem. This transformation works for JSP, FJSP, and OSP problems: - JSP: Single mode per task (one recipe per subjob) - FJSP: Multiple modes per task (multiple recipe options per subjob) - OSP: Single mode per task, no precedence constraints Mapping: - Tasks: (job_index, subjob_index) tuples - Modes: Recipe options for each subjob - Cumulative resources: Machines (capacity 1 each) - Precedence: From problem's get_precedence_constraints() - No-overlap: Tasks within same job (from get_no_overlap()) """ def __init__(self): """Initialize transformation.""" pass
[docs] def transform_problem( self, source_problem: CommonShopProblem ) -> GenericSchedulingImplProblem: """Transform CommonShopProblem to GenericSchedulingImplProblem. Args: source_problem: CommonShopProblem instance (JSP/FJSP/OSP) Returns: Equivalent GenericSchedulingImplProblem """ # Build durations_per_mode: task -> mode -> duration durations_per_mode: dict[Task, dict[int, int]] = {} for job_idx, job in enumerate(source_problem.list_jobs): for subjob_idx, subjob in enumerate(job.subjobs): task = (job_idx, subjob_idx) durations_per_mode[task] = {} # Each recipe option becomes a mode for mode_idx, recipe in enumerate(subjob.recipes): durations_per_mode[task][mode_idx] = recipe.processing_time # Build resource_consumptions: task -> mode -> resource -> consumption resource_consumptions: dict[Task, dict[int, dict[str, int]]] = {} for job_idx, job in enumerate(source_problem.list_jobs): for subjob_idx, subjob in enumerate(job.subjobs): task = (job_idx, subjob_idx) resource_consumptions[task] = {} # Each recipe option consumes the corresponding machine for mode_idx, recipe in enumerate(subjob.recipes): machine_resource = f"M{recipe.machine_index}" resource_consumptions[task][mode_idx] = {machine_resource: 1} # Build non_skill_cumulative_resources: machines with capacity 1 non_skill_cumulative_resources = { f"M{machine}": 1 for machine in range(source_problem.n_machines) } # Get successors from problem's precedence constraints successors = source_problem.get_precedence_constraints() # Get no-overlap sets (tasks within same job) no_overlap_sets = source_problem.get_no_overlap() return GenericSchedulingImplProblem( horizon=source_problem.horizon, durations_per_mode=durations_per_mode, resource_consumptions=resource_consumptions, successors=successors, non_skill_cumulative_resources=non_skill_cumulative_resources, no_overlap_sets=no_overlap_sets, )
[docs] def back_transform_solution( self, solution: GenericSchedulingImplSolution, source_problem: CommonShopProblem, ) -> AnyShopSolution: """Transform GenericSchedulingImplSolution back to CommonShopProblem solution. Args: solution: GenericSchedulingImplSolution source_problem: Original CommonShopProblem Returns: Equivalent CommonShopProblem solution """ # Build shop schedule from GenericSchedulingImplSolution shop_schedule = [[None] * len(job.subjobs) for job in source_problem.list_jobs] machine_index = [[None] * len(job.subjobs) for job in source_problem.list_jobs] for job_idx, job in enumerate(source_problem.list_jobs): for subjob_idx in range(len(job.subjobs)): task = (job_idx, subjob_idx) start = solution.get_start_time(task) end = solution.get_end_time(task) mode = solution.get_mode(task) # Get machine from the chosen mode/recipe machine_id = job.subjobs[subjob_idx].recipes[mode].machine_index shop_schedule[job_idx][subjob_idx] = (start, end) machine_index[job_idx][subjob_idx] = machine_id return AnyShopSolution( problem=source_problem, schedule=shop_schedule, machine_index=machine_index, )
[docs] def forward_transform_solution( self, solution: AnyShopSolution, target_problem: GenericSchedulingImplProblem, ) -> Optional[GenericSchedulingImplSolution]: """Transform CommonShopProblem solution to GenericSchedulingImplSolution. Args: solution: CommonShopProblem solution target_problem: Target GenericSchedulingImplProblem Returns: Equivalent GenericSchedulingImplSolution for warm-start """ # Build task_variables dict task_variables: dict[Task, TaskVariable] = {} for job_idx, job_schedule in enumerate(solution.schedule): for subjob_idx, (start, end) in enumerate(job_schedule): task = (job_idx, subjob_idx) mode = solution.get_mode(task) task_variables[task] = TaskVariable( start=start, end=end, mode=mode, allocated={}, # No unary resources in shop problems ) raw_sol = RawSolution(task_variables=task_variables) return GenericSchedulingImplSolution(problem=target_problem, raw_sol=raw_sol)