Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a Fixed Duration Interruptible Task #143

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions processscheduler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
ZeroDurationTask,
FixedDurationTask,
VariableDurationTask,
FixedDurationInterruptibleTask,
)
from processscheduler.constraint import *
from processscheduler.task_constraint import *
Expand Down
25 changes: 19 additions & 6 deletions processscheduler/resource_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
from processscheduler.resource import Worker, CumulativeWorker, SelectWorkers
from processscheduler.constraint import ResourceConstraint
from processscheduler.util import sort_no_duplicates
from processscheduler.task import VariableDurationTask
from processscheduler.task import (
VariableDurationTask,
FixedDurationInterruptibleTask,
)


class WorkLoad(ResourceConstraint):
Expand Down Expand Up @@ -242,7 +245,7 @@ def __init__(self, **data):
if isinstance(self.resource, Worker):
workers = [self.resource]
elif isinstance(self.resource, CumulativeWorker):
workers = self.resource.cumulative_workers
workers = self.resource._cumulative_workers

resource_assigned = False

Expand Down Expand Up @@ -315,6 +318,9 @@ def __init__(self, **data) -> None:
overlaps = []

is_interruptible = isinstance(task, VariableDurationTask)
is_fixed_interruptible = isinstance(
task, FixedDurationInterruptibleTask
)

for (
interval_lower_bound,
Expand All @@ -335,7 +341,6 @@ def __init__(self, **data) -> None:

if is_interruptible:
# just make sure that the task does not start or end within the time interval...
# TODO: account for zero-duration?
conds.extend(
[
z3.Xor(
Expand Down Expand Up @@ -365,6 +370,9 @@ def __init__(self, **data) -> None:
conds.append(
task._duration <= task.max_duration + total_overlap
)
elif is_fixed_interruptible:
total_overlap = z3.Sum(*overlaps)
conds.append(task._overlap == total_overlap)

# TODO: remove AND, as the solver does that anyways?
self.set_z3_assertions(z3.And(*conds))
Expand Down Expand Up @@ -421,7 +429,7 @@ def __init__(self, **data) -> None:
if isinstance(self.resource, Worker):
workers = [self.resource]
elif isinstance(self.resource, CumulativeWorker):
workers = self.resource.cumulative_workers
workers = self.resource._cumulative_workers

resource_assigned = False

Expand All @@ -433,6 +441,9 @@ def __init__(self, **data) -> None:

# check if the task allows variable duration
is_interruptible = isinstance(task, VariableDurationTask)
is_fixed_interruptible = isinstance(
task, FixedDurationInterruptibleTask
)

duration = end_task_i - start_task_i
folded_start_task_i = (start_task_i - self.offset) % self.period
Expand Down Expand Up @@ -488,9 +499,8 @@ def __init__(self, **data) -> None:
)
overlaps.append(overlap)

if is_interruptible:
if is_interruptible or is_fixed_interruptible:
# just make sure that the task does not start or end within one of the time intervals...
# TODO: account for zero-duration?
conds.extend(
[
z3.Xor(
Expand Down Expand Up @@ -520,6 +530,9 @@ def __init__(self, **data) -> None:
conds.append(
task._duration <= task.max_duration + total_overlap
)
elif is_fixed_interruptible:
total_overlap = z3.Sum(*overlaps)
conds.append(task._overlap == total_overlap)

# TODO: add AND only of mask is set?
core = z3.And(*conds)
Expand Down
23 changes: 21 additions & 2 deletions processscheduler/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ def __init__(self, **data) -> None:
super().__init__(**data)

# workers required to process the task
self._required_resources = [] # type: List[Union[Worker, CumulativeWorker, SelectWorkers]]
self._required_resources = (
[]
) # type: List[Union[Worker, CumulativeWorker, SelectWorkers]]

# z3 Int variables
self._start = z3.Int(f"{self.name}_start") # type: z3.ArithRef
Expand All @@ -88,7 +90,9 @@ def __init__(self, **data) -> None:
raise AssertionError("No active problem. First create a SchedulingProblem")
# the task_number is an integer that is incremented each time
# a task is created. The first task has number 1, the second number 2 etc.
self._task_number = processscheduler.base.active_problem.add_task(self) # type: int
self._task_number = processscheduler.base.active_problem.add_task(
self
) # type: int

# the release date
if self.release_date is not None:
Expand Down Expand Up @@ -268,6 +272,21 @@ def __init__(self, **data) -> None:
self.set_assertions(assertions)


class FixedDurationInterruptibleTask(Task):
duration: PositiveInt

def __init__(self, **data) -> None:
super().__init__(**data)
self._overlap = z3.Int(f"{self.name}_overlap")
assertions = [
self._end - self._start == self.duration + self._overlap,
self._start >= 0,
self._overlap >= 0,
]

self.set_assertions(assertions)


class VariableDurationTask(Task):
"""The duration can take any value, computed by the solver."""

Expand Down
Loading
Loading