Skip to content

Commit 862a8ac

Browse files
committed
ruff format new periodic constraints and related tests see #142
1 parent 2fae158 commit 862a8ac

File tree

4 files changed

+108
-82
lines changed

4 files changed

+108
-82
lines changed

processscheduler/resource_constraint.py

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ class ResourcePeriodicallyUnavailable(ResourceConstraint):
222222
offset: int = 0
223223
end: Union[int, None] = None
224224

225-
226225
def __init__(self, **data):
227226
"""
228227
Initialize a ResourceUnavailable constraint.
@@ -254,8 +253,10 @@ def __init__(self, **data):
254253
duration = end_task_i - start_task_i
255254
conds = [
256255
z3.Xor(
257-
(start_task_i - self.offset) % self.period >= interval_upper_bound,
258-
(start_task_i - self.offset) % self.period + duration <= interval_lower_bound
256+
(start_task_i - self.offset) % self.period
257+
>= interval_upper_bound,
258+
(start_task_i - self.offset) % self.period + duration
259+
<= interval_lower_bound,
259260
)
260261
]
261262

@@ -315,46 +316,51 @@ def __init__(self, **data) -> None:
315316

316317
is_interruptible = isinstance(task, VariableDurationTask)
317318

318-
for interval_lower_bound, interval_upper_bound in self.list_of_time_intervals:
319-
overlap_condition = z3.Not(z3.Xor(
320-
start_task_i >= interval_upper_bound,
321-
end_task_i <= interval_lower_bound
322-
))
319+
for (
320+
interval_lower_bound,
321+
interval_upper_bound,
322+
) in self.list_of_time_intervals:
323+
overlap_condition = z3.Not(
324+
z3.Xor(
325+
start_task_i >= interval_upper_bound,
326+
end_task_i <= interval_lower_bound,
327+
)
328+
)
323329
overlap = z3.If(
324330
overlap_condition,
325331
interval_upper_bound - interval_lower_bound,
326-
0
332+
0,
327333
)
328334
overlaps.append(overlap)
329335

330336
if is_interruptible:
331337
# just make sure that the task does not start or end within the time interval...
332338
# TODO: account for zero-duration?
333-
conds.extend([
334-
z3.Xor(
335-
start_task_i <= interval_lower_bound,
336-
start_task_i >= interval_upper_bound
337-
),
338-
z3.Xor(
339-
end_task_i <= interval_lower_bound,
340-
end_task_i >= interval_upper_bound
341-
)
342-
])
339+
conds.extend(
340+
[
341+
z3.Xor(
342+
start_task_i <= interval_lower_bound,
343+
start_task_i >= interval_upper_bound,
344+
),
345+
z3.Xor(
346+
end_task_i <= interval_lower_bound,
347+
end_task_i >= interval_upper_bound,
348+
),
349+
]
350+
)
343351
else:
344352
# ...otherwise make sure the task does not overlap with the time interval
345353
conds.append(
346354
z3.Xor(
347355
start_task_i >= interval_upper_bound,
348-
end_task_i <= interval_lower_bound
356+
end_task_i <= interval_lower_bound,
349357
)
350358
)
351359

352360
if is_interruptible:
353361
# add assertions for task duration based on the total count of overlapped periods
354362
total_overlap = z3.Sum(*overlaps)
355-
conds.append(
356-
task._duration >= task.min_duration + total_overlap
357-
)
363+
conds.append(task._duration >= task.min_duration + total_overlap)
358364
if task.max_duration is not None:
359365
conds.append(
360366
task._duration <= task.max_duration + total_overlap
@@ -432,74 +438,84 @@ def __init__(self, **data) -> None:
432438
folded_start_task_i = (start_task_i - self.offset) % self.period
433439
folded_end_task_i = (end_task_i - self.offset) % self.period
434440

435-
for interval_lower_bound, interval_upper_bound in self.list_of_time_intervals:
441+
for (
442+
interval_lower_bound,
443+
interval_upper_bound,
444+
) in self.list_of_time_intervals:
436445
# intervals need to be defined in one period
437446
if interval_upper_bound > self.period:
438-
raise AssertionError(f"interval ({interval_lower_bound}, {interval_upper_bound}) exceeds period {self.period}")
447+
raise AssertionError(
448+
f"interval ({interval_lower_bound}, {interval_upper_bound}) exceeds period {self.period}"
449+
)
439450

440451
# if true, the folded task overlaps with the time interval in the first period
441-
crossing_condition = z3.Not(z3.Xor(
442-
# folded task is completely before the first time interval
443-
z3.And(
444-
folded_start_task_i <= interval_lower_bound,
445-
folded_start_task_i + duration % self.period <= interval_lower_bound,
446-
),
447-
# folded task is completely between the first and second time interval
448-
z3.And(
449-
folded_start_task_i >= interval_upper_bound,
450-
folded_start_task_i + duration % self.period <= interval_lower_bound + self.period,
452+
crossing_condition = z3.Not(
453+
z3.Xor(
454+
# folded task is completely before the first time interval
455+
z3.And(
456+
folded_start_task_i <= interval_lower_bound,
457+
folded_start_task_i + duration % self.period
458+
<= interval_lower_bound,
459+
),
460+
# folded task is completely between the first and second time interval
461+
z3.And(
462+
folded_start_task_i >= interval_upper_bound,
463+
folded_start_task_i + duration % self.period
464+
<= interval_lower_bound + self.period,
465+
),
451466
)
452-
))
467+
)
453468

454469
# if true, the task overlaps with at least one time interval
455470
overlap_condition = z3.Or(
456471
crossing_condition,
457472
# task does not fit between two intervals
458-
duration > interval_lower_bound + self.period - interval_upper_bound
473+
duration
474+
> interval_lower_bound + self.period - interval_upper_bound,
459475
)
460476

461477
# adjust the number of crossed time intervals
462478
crossings = z3.If(
463479
crossing_condition,
464480
duration / self.period + 1,
465-
duration / self.period
481+
duration / self.period,
466482
)
467483
# calculate the total overlap for this particular time interval
468484
overlap = z3.If(
469485
overlap_condition,
470486
(interval_upper_bound - interval_lower_bound) * crossings,
471-
0
487+
0,
472488
)
473489
overlaps.append(overlap)
474490

475491
if is_interruptible:
476492
# just make sure that the task does not start or end within one of the time intervals...
477493
# TODO: account for zero-duration?
478-
conds.extend([
479-
z3.Xor(
480-
folded_start_task_i <= interval_lower_bound,
481-
folded_start_task_i >= interval_upper_bound
482-
),
483-
z3.Xor(
484-
folded_end_task_i <= interval_lower_bound,
485-
folded_end_task_i >= interval_upper_bound
486-
)
487-
])
494+
conds.extend(
495+
[
496+
z3.Xor(
497+
folded_start_task_i <= interval_lower_bound,
498+
folded_start_task_i >= interval_upper_bound,
499+
),
500+
z3.Xor(
501+
folded_end_task_i <= interval_lower_bound,
502+
folded_end_task_i >= interval_upper_bound,
503+
),
504+
]
505+
)
488506
else:
489507
# ...otherwise make sure the task does not overlap with any of time intervals
490508
conds.append(
491509
z3.Xor(
492510
folded_start_task_i >= interval_upper_bound,
493-
folded_start_task_i + duration <= interval_lower_bound
511+
folded_start_task_i + duration <= interval_lower_bound,
494512
)
495513
)
496514

497515
if is_interruptible:
498516
# add assertions for task duration based on the total count of overlapped periods
499517
total_overlap = z3.Sum(*overlaps)
500-
conds.append(
501-
task._duration >= task.min_duration + total_overlap
502-
)
518+
conds.append(task._duration >= task.min_duration + total_overlap)
503519
if task.max_duration is not None:
504520
conds.append(
505521
task._duration <= task.max_duration + total_overlap

test/test_resource_interrupted.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ def test_resource_interrupted_fixed_duration() -> None:
3636
assert solution.tasks[task_2.name].start == 8
3737
assert solution.tasks[task_2.name].end == 12
3838

39+
3940
def test_resource_interrupted_variable_duration() -> None:
4041
pb = ps.SchedulingProblem(name="variable_duration")
4142
task_1 = ps.VariableDurationTask(name="task1", min_duration=3)
4243
task_2 = ps.FixedDurationTask(name="task2", duration=4)
43-
ps.TaskStartAt(task=task_1, value=0) # pin to have a more stable outcome
44+
ps.TaskStartAt(task=task_1, value=0) # pin to have a more stable outcome
4445
worker_1 = ps.Worker(name="Worker1")
4546
task_1.add_required_resource(worker_1)
4647
task_2.add_required_resource(worker_1)

test/test_resource_periodically_interrupted.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ def test_resource_periodically_interrupted_fixed_duration() -> None:
2626
task_1.add_required_resource(worker_1)
2727
task_2.add_required_resource(worker_1)
2828
ps.ResourcePeriodicallyInterrupted(
29-
resource=worker_1,
30-
list_of_time_intervals=[(1, 2), (4, 5)],
31-
period=10
29+
resource=worker_1, list_of_time_intervals=[(1, 2), (4, 5)], period=10
3230
)
3331

3432
ps.ObjectiveMinimizeMakespan()
@@ -45,14 +43,12 @@ def test_resource_periodically_interrupted_variable_duration() -> None:
4543
pb = ps.SchedulingProblem(name="variable_duration")
4644
task_1 = ps.VariableDurationTask(name="task1", min_duration=3)
4745
task_2 = ps.FixedDurationTask(name="task2", duration=4)
48-
ps.TaskStartAt(task=task_1, value=0) # pin to have a more stable outcome
46+
ps.TaskStartAt(task=task_1, value=0) # pin to have a more stable outcome
4947
worker_1 = ps.Worker(name="Worker1")
5048
task_1.add_required_resource(worker_1)
5149
task_2.add_required_resource(worker_1)
5250
ps.ResourcePeriodicallyInterrupted(
53-
resource=worker_1,
54-
list_of_time_intervals=[(1, 2), (4, 5)],
55-
period=10
51+
resource=worker_1, list_of_time_intervals=[(1, 2), (4, 5)], period=10
5652
)
5753

5854
ps.ObjectiveMinimizeMakespan()
@@ -69,11 +65,15 @@ def test_resource_periodically_interrupted_assignment_assertion() -> None:
6965
ps.SchedulingProblem(name="assignment_assertion")
7066
worker_1 = ps.Worker(name="Worker1")
7167
with pytest.raises(AssertionError):
72-
ps.ResourcePeriodicallyInterrupted(resource=worker_1, list_of_time_intervals=[(1, 3)], period=6)
68+
ps.ResourcePeriodicallyInterrupted(
69+
resource=worker_1, list_of_time_intervals=[(1, 3)], period=6
70+
)
7371

7472

7573
def test_resource_periodically_interrupted_period_assertion() -> None:
7674
ps.SchedulingProblem(name="period_assertion")
7775
worker_1 = ps.Worker(name="Worker1")
7876
with pytest.raises(AssertionError):
79-
ps.ResourcePeriodicallyInterrupted(resource=worker_1, list_of_time_intervals=[(1, 2), (3, 5)], period=4)
77+
ps.ResourcePeriodicallyInterrupted(
78+
resource=worker_1, list_of_time_intervals=[(1, 2), (3, 5)], period=4
79+
)

test/test_resource_periodically_unavailable.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ def test_resource_periodically_unavailable_1() -> None:
2424
task_1 = ps.FixedDurationTask(name="task1", duration=3)
2525
worker_1 = ps.Worker(name="Worker1")
2626
task_1.add_required_resource(worker_1)
27-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(1, 3), (6, 8)], period=10)
27+
ps.ResourcePeriodicallyUnavailable(
28+
resource=worker_1, list_of_time_intervals=[(1, 3), (6, 8)], period=10
29+
)
2830

2931
solver = ps.SchedulingSolver(problem=pb)
3032
solution = solver.solve()
@@ -40,8 +42,12 @@ def test_resource_periodically_unavailable_2() -> None:
4042
task_1.add_required_resource(worker_1)
4143
# difference with the first one: build 2 constraints
4244
# merged using a and_
43-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(1, 3)], period=10)
44-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(6, 8)], period=10)
45+
ps.ResourcePeriodicallyUnavailable(
46+
resource=worker_1, list_of_time_intervals=[(1, 3)], period=10
47+
)
48+
ps.ResourcePeriodicallyUnavailable(
49+
resource=worker_1, list_of_time_intervals=[(6, 8)], period=10
50+
)
4551

4652
# that should not change the problem solution
4753
solver = ps.SchedulingSolver(problem=pb)
@@ -59,8 +65,12 @@ def test_resource_periodically_unavailable_3() -> None:
5965
# difference with the previous ones: too much unavailability,
6066
# so possible solution
6167
# merged using a and_
62-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(1, 3)], period=10)
63-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(5, 8)], period=10)
68+
ps.ResourcePeriodicallyUnavailable(
69+
resource=worker_1, list_of_time_intervals=[(1, 3)], period=10
70+
)
71+
ps.ResourcePeriodicallyUnavailable(
72+
resource=worker_1, list_of_time_intervals=[(5, 8)], period=10
73+
)
6474

6575
# that should not change the problem solution
6676
solver = ps.SchedulingSolver(problem=pb)
@@ -72,7 +82,9 @@ def test_resource_periodically_unavailable_4() -> None:
7282
ps.SchedulingProblem(name="ResourcePeriodicallyUnavailable4", horizon=10)
7383
worker_1 = ps.Worker(name="Worker1")
7484
with pytest.raises(AssertionError):
75-
ps.ResourcePeriodicallyUnavailable(resource=worker_1, list_of_time_intervals=[(1, 3)], period=10)
85+
ps.ResourcePeriodicallyUnavailable(
86+
resource=worker_1, list_of_time_intervals=[(1, 3)], period=10
87+
)
7688

7789

7890
def test_resource_periodically_unavailable_5() -> None:
@@ -86,9 +98,7 @@ def test_resource_periodically_unavailable_5() -> None:
8698
for task in (task_1, task_2, task_3):
8799
task.add_required_resource(worker_1)
88100
ps.ResourcePeriodicallyUnavailable(
89-
resource=worker_1,
90-
list_of_time_intervals=[(3, 5)],
91-
period=5
101+
resource=worker_1, list_of_time_intervals=[(3, 5)], period=5
92102
)
93103

94104
solver = ps.SchedulingSolver(problem=pb)
@@ -117,29 +127,30 @@ def test_resource_periodically_unavailable_6() -> None:
117127
ps.ResourcePeriodicallyUnavailable(
118128
resource=worker_1,
119129
list_of_time_intervals=[(2, 4)],
120-
offset=2, # shift interval to (4, 6)
130+
offset=2, # shift interval to (4, 6)
121131
start=3, # end_task_i <= start, so it must be set to the task duration
122-
end=14, # unavailability interval at (14, 16), but it should be ignored
123-
period=5
132+
end=14, # unavailability interval at (14, 16), but it should be ignored
133+
period=5,
124134
)
125135
ps.ResourceUnavailable(
126136
resource=worker_1,
127-
list_of_time_intervals=[(3, 4)] # leaving task_1 starting at 0 as only option
137+
list_of_time_intervals=[(3, 4)], # leaving task_1 starting at 0 as only option
128138
)
129139

130-
ps.ObjectiveMinimizeMakespan() # ensure dense packing
140+
ps.ObjectiveMinimizeMakespan() # ensure dense packing
131141
solver = ps.SchedulingSolver(problem=pb)
132142
solution = solver.solve()
133143
assert solution
134144
assert solution.tasks[task_1.name].start == 0 # unavailability at (0, 1) ignored
135145
assert solution.tasks[task_1.name].end == 3
136146
assert solution.tasks[task_2.name].start == 6
137-
assert solution.tasks[task_2.name].end == 9 # expected unavailability
147+
assert solution.tasks[task_2.name].end == 9 # expected unavailability
138148
assert solution.tasks[task_3.name].start == 11
139149
assert solution.tasks[task_3.name].end == 14
140-
assert solution.tasks[task_4.name].start == 14 # unavailability at (14, 16) ignored
150+
assert solution.tasks[task_4.name].start == 14 # unavailability at (14, 16) ignored
141151
assert solution.tasks[task_4.name].end == 17
142152

153+
143154
def test_resource_periodically_unavailable_7() -> None:
144155
pb = ps.SchedulingProblem(name="ResourcePeriodicallyUnavailable3")
145156
task_1 = ps.FixedDurationTask(name="task1", duration=2)
@@ -151,9 +162,7 @@ def test_resource_periodically_unavailable_7() -> None:
151162
for task in (task_1, task_2, task_3):
152163
task.add_required_resource(worker_1)
153164
ps.ResourcePeriodicallyUnavailable(
154-
resource=worker_1,
155-
list_of_time_intervals=[(1, 2), (3, 4)],
156-
period=5
165+
resource=worker_1, list_of_time_intervals=[(1, 2), (3, 4)], period=5
157166
)
158167

159168
ps.ObjectiveMinimizeMakespan()

0 commit comments

Comments
 (0)