Source code for discrete_optimization.singlebatch.transformations.to_ovensched
# 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 SingleBatch to OvenSched.
SingleBatch is a special case of OvenSched with:
- 1 machine
- 1 attribute (all jobs have same type)
- No setup times/costs
- No time windows
- Fixed processing times (min_duration = max_duration)
"""
from typing import Optional
from discrete_optimization.generic_tools.transformation.problem_transformation import (
ProblemTransformation,
)
from discrete_optimization.generic_tools.transformation.transformation_metadata import (
TransformationMetadata,
subset_transformation,
)
from discrete_optimization.ovensched.problem import (
MachineData,
OvenSchedulingProblem,
OvenSchedulingSolution,
ScheduleInfo,
TaskData,
)
from discrete_optimization.singlebatch.problem import (
BatchProcessingSolution,
SingleBatchProcessingProblem,
)
[docs]
class SinglebatchToOvenschedTransformation(
ProblemTransformation[
SingleBatchProcessingProblem,
BatchProcessingSolution,
OvenSchedulingProblem,
OvenSchedulingSolution,
]
):
"""Transform SingleBatch problem to OvenSched problem.
SingleBatch is a special case of OvenSched, so this is a SUBSET transformation (exact).
Mapping:
- Single machine → 1 machine in OvenSched
- Jobs → Tasks with:
- Single attribute (all tasks have attribute=0)
- min_duration = max_duration = processing_time
- size → size
- earliest_start = 0
- latest_end = large value (no deadline)
- eligible_machines = {0} (only the single machine)
- Capacity → machine capacity
- No setup times → all setup_times[i][j] = 0
- No setup costs → all setup_costs[i][j] = 0
- Machine availability → [(0, large_value)] (always available)
This is an EXACT transformation:
- Forward: Every SingleBatch problem is a valid OvenSched problem
- Backward: Solutions map directly (batch assignments are preserved)
"""
[docs]
def get_forward_metadata(self) -> TransformationMetadata:
"""Metadata for forward problem transformation (SingleBatch → OvenSched).
This is a SUBSET transformation: SingleBatch ⊂ OvenSched.
"""
return subset_transformation(
use_cases=[
"Use OvenSched solvers on SingleBatch problems",
"Benchmark OvenSched solvers on simpler instances",
"Prototype on SingleBatch before tackling full OvenSched",
],
assumptions=[
"SingleBatch is a special case of OvenSched with restrictions",
"1 machine, 1 attribute, no setup times/costs, no time windows",
],
)
[docs]
def transform_problem(
self, source_problem: SingleBatchProcessingProblem
) -> OvenSchedulingProblem:
"""Transform SingleBatch to OvenSched.
Args:
source_problem: SingleBatch problem instance
Returns:
Equivalent OvenSched problem with 1 machine and 1 attribute
"""
n_jobs = source_problem.nb_jobs
n_machines = 1 # Single machine
# All tasks have the same attribute (0)
single_attribute = 0
# Large value for unbounded time windows
large_time = source_problem.get_makespan_upper_bound() * 2
# Create task data
tasks_data = []
for i, job in enumerate(source_problem.jobs):
tasks_data.append(
TaskData(
attribute=single_attribute,
min_duration=job.processing_time,
max_duration=job.processing_time,
earliest_start=0,
latest_end=large_time,
eligible_machines={0}, # Only machine 0
size=job.size,
)
)
# Create machine data
machines_data = [
MachineData(
capacity=source_problem.capacity,
initial_attribute=single_attribute,
availability=[(0, large_time)], # Always available
)
]
# Setup times and costs (all zeros since no setup)
# Need 1x1 matrix for the single attribute
num_attributes = 1
setup_times = [[0] * num_attributes for _ in range(num_attributes)]
setup_costs = [[0] * num_attributes for _ in range(num_attributes)]
return OvenSchedulingProblem(
n_jobs=n_jobs,
n_machines=n_machines,
tasks_data=tasks_data,
machines_data=machines_data,
setup_costs=setup_costs,
setup_times=setup_times,
)
[docs]
def back_transform_solution(
self,
solution: OvenSchedulingSolution,
source_problem: SingleBatchProcessingProblem,
) -> BatchProcessingSolution:
"""Transform OvenSched solution back to SingleBatch solution.
Args:
solution: OvenSched solution
source_problem: Original SingleBatch problem
Returns:
Equivalent SingleBatch solution
"""
# Extract batches from machine 0
machine_0_schedule = solution.schedule_per_machine.get(0, [])
# Create job_to_batch mapping
job_to_batch = [0] * source_problem.nb_jobs
for batch_idx, schedule_info in enumerate(machine_0_schedule):
for task_id in schedule_info.tasks:
job_to_batch[task_id] = batch_idx
# Create schedule_batch for SingleBatch
schedule_batch = []
for schedule_info in machine_0_schedule:
schedule_batch.append((schedule_info.start_time, schedule_info.end_time))
return BatchProcessingSolution(
problem=source_problem,
job_to_batch=job_to_batch,
schedule_batch=schedule_batch,
)
[docs]
def forward_transform_solution(
self,
solution: BatchProcessingSolution,
target_problem: OvenSchedulingProblem,
) -> Optional[OvenSchedulingSolution]:
"""Transform SingleBatch solution to OvenSched solution (for warmstart).
Args:
solution: SingleBatch solution
target_problem: Target OvenSched problem
Returns:
Equivalent OvenSched solution for warmstart
"""
# Group jobs by batch
batches_dict = {}
for job_idx, batch_id in enumerate(solution.job_to_batch):
if batch_id not in batches_dict:
batches_dict[batch_id] = set()
batches_dict[batch_id].add(job_idx)
# Create schedule for machine 0
schedule_infos = []
for batch_id in sorted(batches_dict.keys()):
tasks_in_batch = batches_dict[batch_id]
# Get batch timing from solution
if batch_id < len(solution.schedule_batch):
start_time, end_time = solution.schedule_batch[batch_id]
else:
# Fallback if schedule_batch not available
start_time = 0
end_time = max(
solution.problem.jobs[t].processing_time for t in tasks_in_batch
)
schedule_infos.append(
ScheduleInfo(
tasks=tasks_in_batch,
task_attribute=0, # Single attribute
start_time=start_time,
end_time=end_time,
machine_batch_index=(0, batch_id),
)
)
schedule_per_machine = {0: schedule_infos}
return OvenSchedulingSolution(
problem=target_problem,
schedule_per_machine=schedule_per_machine,
)