Source code for discrete_optimization.workforce.scheduling.transformations.to_rcpsp
# 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 Workforce Scheduling to RCPSP."""
from typing import Optional
from discrete_optimization.generic_tools.transformation.problem_transformation import (
ProblemTransformation,
)
from discrete_optimization.generic_tools.transformation.transformation_metadata import (
TransformationMetadata,
exact_transformation,
)
from discrete_optimization.rcpsp.problem import RcpspProblem
from discrete_optimization.rcpsp.solution import RcpspSolution
from discrete_optimization.workforce.scheduling.problem import (
AllocSchedulingProblem,
AllocSchedulingSolution,
transform_alloc_solution_to_rcpsp_solution,
transform_rcpsp_solution_to_alloc_solution,
transform_to_multimode_rcpsp,
)
[docs]
class WorkforceSchedulingToRcpspTransformation(
ProblemTransformation[
AllocSchedulingProblem,
AllocSchedulingSolution,
RcpspProblem,
RcpspSolution,
]
):
"""Transform Workforce Scheduling to RCPSP (Resource-Constrained Project Scheduling).
Mapping:
- Tasks → RCPSP tasks
- Teams → Modes for each task
- Team availability calendars → Resource calendars
- Task duration → Task duration in each mode
- Precedence constraints → RCPSP successors
- Time windows → Start/end time windows
This transformation is EXACT:
- All workforce scheduling constraints are preserved in RCPSP formulation
- Team assignment becomes mode selection in RCPSP
"""
def __init__(
self,
build_calendar: bool = True,
add_window_time_constraint: bool = True,
add_additional_constraint: bool = True,
):
"""Initialize transformation.
Args:
build_calendar: Build resource calendars from team availability
add_window_time_constraint: Add time window constraints to RCPSP
add_additional_constraint: Add special constraints (pair mode constraints)
"""
self.build_calendar = build_calendar
self.add_window_time_constraint = add_window_time_constraint
self.add_additional_constraint = add_additional_constraint
# Will store the mode-to-team mapping after transformation
self.ac_mode_to_team: dict = {}
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward problem transformation (WorkforceScheduling → RCPSP).
This direction is EXACT: all workforce constraints map to RCPSP constraints.
"""
return exact_transformation(
use_cases=[
"Use RCPSP solvers to solve workforce scheduling problems",
"Team assignment becomes mode selection in RCPSP",
"All precedence and time window constraints preserved",
]
)
[docs]
def transform_problem(self, source_problem: AllocSchedulingProblem) -> RcpspProblem:
"""Transform Workforce Scheduling to RCPSP.
Args:
source_problem: AllocSchedulingProblem instance
Returns:
Equivalent RCPSP problem
"""
# Use the existing transformation function
rcpsp_problem, ac_mode_to_team = transform_to_multimode_rcpsp(
problem=source_problem,
build_calendar=self.build_calendar,
add_window_time_constraint=self.add_window_time_constraint,
add_additional_constraint=self.add_additional_constraint,
)
# Store the mapping for solution back-transformation
self.ac_mode_to_team = ac_mode_to_team
return rcpsp_problem
[docs]
def back_transform_solution(
self, solution: RcpspSolution, source_problem: AllocSchedulingProblem
) -> AllocSchedulingSolution:
"""Transform RCPSP solution back to Workforce Scheduling solution.
Args:
solution: RCPSP solution
source_problem: Original AllocSchedulingProblem
Returns:
Equivalent AllocSchedulingSolution
"""
# Use the existing solution transformation function
return transform_rcpsp_solution_to_alloc_solution(
rcpsp_solution=solution,
rcpsp_problem=solution.problem,
ac_mode_to_team=self.ac_mode_to_team,
alloc_scheduling_problem=source_problem,
)
[docs]
def forward_transform_solution(
self, solution: AllocSchedulingSolution, target_problem: RcpspProblem
) -> Optional[RcpspSolution]:
"""Transform Workforce Scheduling solution to RCPSP solution (for warmstart).
Args:
solution: AllocSchedulingSolution
target_problem: Target RCPSP problem
Returns:
Equivalent RCPSP solution for warmstart
"""
# Use the existing solution transformation function
return transform_alloc_solution_to_rcpsp_solution(
alloc_solution=solution,
rcpsp_problem=target_problem,
ac_mode_to_team=self.ac_mode_to_team,
alloc_scheduling_problem=solution.problem,
)