# 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.
import logging
from typing import Generic
from discrete_optimization.generic_tasks_tools.base import Task
from discrete_optimization.generic_tasks_tools.enums import StartOrEnd
from discrete_optimization.generic_tasks_tools.scheduling import (
SchedulingProblem,
SchedulingSolution,
)
logger = logging.getLogger(__name__)
[docs]
class TimewindowProblem(SchedulingProblem[Task], Generic[Task]):
"""Class for problem having time windows between tasks."""
[docs]
def get_task_start_or_end_lower_bound(
self, task: Task, start_or_end: StartOrEnd
) -> int:
"""Get a lower bound on start or end of a given task as specified by the problem.
For tighter computed bounds, see `GenericSchedulingProblem.get_tight_task_start_or_end_lower_bound()` and
`GenericSchedulingProblem.compute_task_bounds()`.
Default implementation: 0
Args:
task:
start_or_end:
Returns:
"""
return 0
[docs]
def get_task_start_or_end_upper_bound(
self, task: Task, start_or_end: StartOrEnd
) -> int:
"""Get an upper bound on start or end of a given task as specified by the problem.
For tighter computed bounds, see `GenericSchedulingProblem.get_tight_task_start_or_end_upper_bound()` and
`GenericSchedulingProblem.compute_task_bounds()`.
Default implementation: makespan upper bound
Args:
task:
start_or_end:
Returns:
"""
return self.get_makespan_upper_bound()
[docs]
def get_makespan_lower_bound(self) -> int:
"""Get a lower bound on global makespan.
Time windows on last tasks can be used to get a better makespan lower bound.
"""
return max(
self.get_task_start_or_end_lower_bound(
task=task, start_or_end=StartOrEnd.END
)
for task in self.get_last_tasks()
)
[docs]
class TimewindowSolution(SchedulingSolution[Task], Generic[Task]):
"""Class for solution of problems having time windows between tasks."""
problem: TimewindowProblem[Task]
[docs]
def check_time_windows(self) -> bool:
"""check whether time windows are respected."""
for task in self.problem.tasks_list:
start = self.get_start_time(task)
if start < self.problem.get_task_start_or_end_lower_bound(
task=task, start_or_end=StartOrEnd.START
) or start > self.problem.get_task_start_or_end_upper_bound(
task=task, start_or_end=StartOrEnd.START
):
logger.debug(f"Window for start of {task} is not respected.")
return False
end = self.get_end_time(task)
if end < self.problem.get_task_start_or_end_lower_bound(
task=task, start_or_end=StartOrEnd.END
) or end > self.problem.get_task_start_or_end_upper_bound(
task=task, start_or_end=StartOrEnd.END
):
logger.debug(f"Window for end of {task} is not respected.")
return False
return True