Source code for discrete_optimization.rcpsp.transformations.to_multiskill

#  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 RCPSP to RCPSP Multiskill."""

from __future__ import annotations

from discrete_optimization.generic_tools.transformation.problem_transformation import (
    ProblemTransformation,
)
from discrete_optimization.rcpsp.problem import RcpspProblem
from discrete_optimization.rcpsp.solution import RcpspSolution
from discrete_optimization.rcpsp_multiskill.problem import (
    MultiskillRcpspProblem,
    MultiskillRcpspSolution,
)


[docs] class RcpspToMultiskillTransformation( ProblemTransformation[ RcpspProblem, RcpspSolution, MultiskillRcpspProblem, MultiskillRcpspSolution ] ): """Transform RCPSP to RCPSPMultiskill. Mapping strategy: - RCPSP resources map directly to MultiskillRCPSP cumulative resources - No employees or skills needed (MultiskillRCPSP can work with just resources) - Same mode details and precedence constraints Example: # >>> problem = RcpspProblem( # ... resources={"R1": 5, "R2": 2}, # ... ... # ... ) # >>> transformation = RcpspToMultiskillTransformation() # >>> ms_problem = transformation.transform_problem(problem) # >>> # ms_problem uses resources R1, R2 directly (no fake employees) """
[docs] def transform_problem(self, source_problem: RcpspProblem) -> MultiskillRcpspProblem: """Convert RCPSP to Multiskill RCPSP. Args: source_problem: Original RCPSP problem Returns: Equivalent MultiskillRcpspProblem """ rcpsp = source_problem # Use resources directly (no skills/employees needed) resources_set = set(rcpsp.resources_list) skills_set = set() # Empty skills employees = {} # No employees # Resources availability (convert int to list if needed) resources_availability = {} for r in rcpsp.resources_list: if isinstance(rcpsp.resources[r], int): resources_availability[r] = [rcpsp.resources[r]] * rcpsp.horizon else: resources_availability[r] = list(rcpsp.resources[r]) return MultiskillRcpspProblem( skills_set=skills_set, # Empty resources_set=resources_set, non_renewable_resources=set(rcpsp.non_renewable_resources), resources_availability=resources_availability, employees=employees, # Empty mode_details=rcpsp.mode_details, # Same modes successors=rcpsp.successors, # Same precedence horizon=rcpsp.horizon, source_task=rcpsp.source_task, sink_task=rcpsp.sink_task, )
[docs] def back_transform_solution( self, solution: MultiskillRcpspSolution, source_problem: RcpspProblem ) -> RcpspSolution: """Convert Multiskill solution back to RCPSP solution. We only need the schedule (start times) and modes. Employee assignments are not relevant (empty in this transformation). Args: solution: Solution in multiskill problem space source_problem: Original RCPSP problem Returns: Corresponding RCPSP solution """ # Extract schedule (convert to RCPSP format if needed) rcpsp_schedule = {} for task, details in solution.schedule.items(): if isinstance(details, dict): # Already in correct format rcpsp_schedule[task] = { "start_time": details["start_time"], "end_time": details["end_time"], } else: # Handle other formats if necessary rcpsp_schedule[task] = details # Extract modes (convert dict to list in correct order) rcpsp_modes = [ solution.modes[task] for task in source_problem.tasks_list_non_dummy ] return RcpspSolution( problem=source_problem, rcpsp_schedule=rcpsp_schedule, rcpsp_modes=rcpsp_modes, )
[docs] def forward_transform_solution( self, solution: RcpspSolution, target_problem: MultiskillRcpspProblem ) -> MultiskillRcpspSolution: """Convert RCPSP solution to Multiskill solution. Since we don't use employees, this is straightforward. Args: solution: Solution in RCPSP problem space target_problem: Transformed multiskill problem Returns: Corresponding multiskill solution (without employee assignments) """ # Extract schedule and modes from RCPSP solution schedule = solution.rcpsp_schedule modes = { task: solution.rcpsp_modes[target_problem.index_task_non_dummy[task]] for task in target_problem.tasks_list_non_dummy } # Add source and sink with their modes modes[target_problem.source_task] = 1 modes[target_problem.sink_task] = 1 # No employee assignments needed employee_usage = {} return MultiskillRcpspSolution( problem=target_problem, modes=modes, schedule=schedule, employee_usage=employee_usage, )