pycsp3-scheduling 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycsp3_scheduling/__init__.py +220 -0
- pycsp3_scheduling/constraints/__init__.py +87 -0
- pycsp3_scheduling/constraints/_pycsp3.py +701 -0
- pycsp3_scheduling/constraints/cumulative.py +227 -0
- pycsp3_scheduling/constraints/grouping.py +382 -0
- pycsp3_scheduling/constraints/precedence.py +376 -0
- pycsp3_scheduling/constraints/sequence.py +814 -0
- pycsp3_scheduling/expressions/__init__.py +80 -0
- pycsp3_scheduling/expressions/element.py +313 -0
- pycsp3_scheduling/expressions/interval_expr.py +495 -0
- pycsp3_scheduling/expressions/sequence_expr.py +865 -0
- pycsp3_scheduling/functions/__init__.py +111 -0
- pycsp3_scheduling/functions/cumul_functions.py +891 -0
- pycsp3_scheduling/functions/state_functions.py +494 -0
- pycsp3_scheduling/interop.py +356 -0
- pycsp3_scheduling/output/__init__.py +13 -0
- pycsp3_scheduling/solvers/__init__.py +14 -0
- pycsp3_scheduling/solvers/adapters/__init__.py +7 -0
- pycsp3_scheduling/variables/__init__.py +45 -0
- pycsp3_scheduling/variables/interval.py +450 -0
- pycsp3_scheduling/variables/sequence.py +244 -0
- pycsp3_scheduling/visu.py +1315 -0
- pycsp3_scheduling-0.2.1.dist-info/METADATA +234 -0
- pycsp3_scheduling-0.2.1.dist-info/RECORD +26 -0
- pycsp3_scheduling-0.2.1.dist-info/WHEEL +4 -0
- pycsp3_scheduling-0.2.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Precedence constraints for interval variables.
|
|
3
|
+
|
|
4
|
+
This module provides two families of precedence constraints:
|
|
5
|
+
|
|
6
|
+
1. **Exact timing constraints** (equality):
|
|
7
|
+
- start_at_start(a, b, delay): start(b) == start(a) + delay
|
|
8
|
+
- start_at_end(a, b, delay): start(b) == end(a) + delay
|
|
9
|
+
- end_at_start(a, b, delay): end(a) == start(b) + delay
|
|
10
|
+
- end_at_end(a, b, delay): end(b) == end(a) + delay
|
|
11
|
+
|
|
12
|
+
2. **Before constraints** (inequality):
|
|
13
|
+
- start_before_start(a, b, delay): start(b) >= start(a) + delay
|
|
14
|
+
- start_before_end(a, b, delay): end(b) >= start(a) + delay
|
|
15
|
+
- end_before_start(a, b, delay): start(b) >= end(a) + delay
|
|
16
|
+
- end_before_end(a, b, delay): end(b) >= end(a) + delay
|
|
17
|
+
|
|
18
|
+
All constraints return pycsp3 Node objects that can be used with satisfy().
|
|
19
|
+
|
|
20
|
+
Absent interval semantics:
|
|
21
|
+
- When both intervals are present: constraint is enforced
|
|
22
|
+
- When either interval is absent: constraint is trivially satisfied
|
|
23
|
+
(Note: optional intervals not yet implemented)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from pycsp3_scheduling.constraints._pycsp3 import length_value, start_var
|
|
29
|
+
from pycsp3_scheduling.variables.interval import IntervalVar
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _validate_precedence_args(
|
|
33
|
+
a: IntervalVar, b: IntervalVar, delay: int, func_name: str
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Validate arguments for precedence constraint functions."""
|
|
36
|
+
if not isinstance(a, IntervalVar) or not isinstance(b, IntervalVar):
|
|
37
|
+
raise TypeError(f"{func_name} expects IntervalVar inputs")
|
|
38
|
+
if not isinstance(delay, int):
|
|
39
|
+
raise TypeError("delay must be an int")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_node_builders():
|
|
43
|
+
"""Import and return pycsp3 Node building utilities."""
|
|
44
|
+
from pycsp3.classes.nodes import Node, TypeNode
|
|
45
|
+
|
|
46
|
+
return Node, TypeNode
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# =============================================================================
|
|
50
|
+
# Exact Timing Constraints (Equality)
|
|
51
|
+
# =============================================================================
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def start_at_start(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
55
|
+
"""
|
|
56
|
+
Enforce that interval b starts exactly when interval a starts (plus delay).
|
|
57
|
+
|
|
58
|
+
Semantics (when both present):
|
|
59
|
+
start(b) == start(a) + delay
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
a: First interval variable.
|
|
63
|
+
b: Second interval variable.
|
|
64
|
+
delay: Time delay between start of a and start of b. Default 0.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A pycsp3 Node representing the constraint.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
74
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
75
|
+
>>> satisfy(start_at_start(task1, task2)) # Start together
|
|
76
|
+
>>> satisfy(start_at_start(task1, task2, delay=5)) # task2 starts 5 after task1
|
|
77
|
+
"""
|
|
78
|
+
_validate_precedence_args(a, b, delay, "start_at_start")
|
|
79
|
+
Node, TypeNode = _get_node_builders()
|
|
80
|
+
|
|
81
|
+
start_a = start_var(a)
|
|
82
|
+
start_b = start_var(b)
|
|
83
|
+
|
|
84
|
+
# start(b) == start(a) + delay
|
|
85
|
+
if delay:
|
|
86
|
+
rhs = Node.build(TypeNode.ADD, start_a, delay)
|
|
87
|
+
else:
|
|
88
|
+
rhs = start_a
|
|
89
|
+
return Node.build(TypeNode.EQ, start_b, rhs)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def start_at_end(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
93
|
+
"""
|
|
94
|
+
Enforce that interval b starts exactly when interval a ends (plus delay).
|
|
95
|
+
|
|
96
|
+
Semantics (when both present):
|
|
97
|
+
start(b) == end(a) + delay
|
|
98
|
+
start(b) == start(a) + length(a) + delay
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
a: First interval variable.
|
|
102
|
+
b: Second interval variable.
|
|
103
|
+
delay: Time delay between end of a and start of b. Default 0.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A pycsp3 Node representing the constraint.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
113
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
114
|
+
>>> satisfy(start_at_end(task1, task2)) # task2 starts exactly when task1 ends
|
|
115
|
+
"""
|
|
116
|
+
_validate_precedence_args(a, b, delay, "start_at_end")
|
|
117
|
+
Node, TypeNode = _get_node_builders()
|
|
118
|
+
|
|
119
|
+
start_a = start_var(a)
|
|
120
|
+
start_b = start_var(b)
|
|
121
|
+
length_a = length_value(a)
|
|
122
|
+
|
|
123
|
+
# start(b) == start(a) + length(a) + delay
|
|
124
|
+
rhs = Node.build(TypeNode.ADD, start_a, length_a)
|
|
125
|
+
if delay:
|
|
126
|
+
rhs = Node.build(TypeNode.ADD, rhs, delay)
|
|
127
|
+
return Node.build(TypeNode.EQ, start_b, rhs)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def end_at_start(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
131
|
+
"""
|
|
132
|
+
Enforce that interval a ends exactly when interval b starts (plus delay).
|
|
133
|
+
|
|
134
|
+
Semantics (when both present):
|
|
135
|
+
end(a) == start(b) + delay
|
|
136
|
+
start(a) + length(a) == start(b) + delay
|
|
137
|
+
|
|
138
|
+
Note: This is equivalent to start_at_end(b, a, -delay) but expressed
|
|
139
|
+
from a's perspective.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
a: First interval variable.
|
|
143
|
+
b: Second interval variable.
|
|
144
|
+
delay: Time delay between end of a and start of b. Default 0.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
A pycsp3 Node representing the constraint.
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
154
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
155
|
+
>>> satisfy(end_at_start(task1, task2)) # task1 ends exactly when task2 starts
|
|
156
|
+
"""
|
|
157
|
+
_validate_precedence_args(a, b, delay, "end_at_start")
|
|
158
|
+
Node, TypeNode = _get_node_builders()
|
|
159
|
+
|
|
160
|
+
start_a = start_var(a)
|
|
161
|
+
start_b = start_var(b)
|
|
162
|
+
length_a = length_value(a)
|
|
163
|
+
|
|
164
|
+
# end(a) == start(b) + delay
|
|
165
|
+
# start(a) + length(a) == start(b) + delay
|
|
166
|
+
lhs = Node.build(TypeNode.ADD, start_a, length_a)
|
|
167
|
+
if delay:
|
|
168
|
+
rhs = Node.build(TypeNode.ADD, start_b, delay)
|
|
169
|
+
else:
|
|
170
|
+
rhs = start_b
|
|
171
|
+
return Node.build(TypeNode.EQ, lhs, rhs)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def end_at_end(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
175
|
+
"""
|
|
176
|
+
Enforce that interval b ends exactly when interval a ends (plus delay).
|
|
177
|
+
|
|
178
|
+
Semantics (when both present):
|
|
179
|
+
end(b) == end(a) + delay
|
|
180
|
+
start(b) + length(b) == start(a) + length(a) + delay
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
a: First interval variable.
|
|
184
|
+
b: Second interval variable.
|
|
185
|
+
delay: Time delay between end of a and end of b. Default 0.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
A pycsp3 Node representing the constraint.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
195
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
196
|
+
>>> satisfy(end_at_end(task1, task2)) # Both end at the same time
|
|
197
|
+
"""
|
|
198
|
+
_validate_precedence_args(a, b, delay, "end_at_end")
|
|
199
|
+
Node, TypeNode = _get_node_builders()
|
|
200
|
+
|
|
201
|
+
start_a = start_var(a)
|
|
202
|
+
start_b = start_var(b)
|
|
203
|
+
length_a = length_value(a)
|
|
204
|
+
length_b = length_value(b)
|
|
205
|
+
|
|
206
|
+
# end(b) == end(a) + delay
|
|
207
|
+
# start(b) + length(b) == start(a) + length(a) + delay
|
|
208
|
+
lhs = Node.build(TypeNode.ADD, start_b, length_b)
|
|
209
|
+
rhs = Node.build(TypeNode.ADD, start_a, length_a)
|
|
210
|
+
if delay:
|
|
211
|
+
rhs = Node.build(TypeNode.ADD, rhs, delay)
|
|
212
|
+
return Node.build(TypeNode.EQ, lhs, rhs)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# =============================================================================
|
|
216
|
+
# Before Constraints (Inequality)
|
|
217
|
+
# =============================================================================
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def start_before_start(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
221
|
+
"""
|
|
222
|
+
Enforce that interval b cannot start before interval a starts (plus delay).
|
|
223
|
+
|
|
224
|
+
Semantics (when both present):
|
|
225
|
+
start(b) >= start(a) + delay
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
a: First interval variable.
|
|
229
|
+
b: Second interval variable.
|
|
230
|
+
delay: Minimum time between start of a and start of b. Default 0.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
A pycsp3 Node representing the constraint.
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
240
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
241
|
+
>>> satisfy(start_before_start(task1, task2)) # task2 starts after task1 starts
|
|
242
|
+
"""
|
|
243
|
+
_validate_precedence_args(a, b, delay, "start_before_start")
|
|
244
|
+
Node, TypeNode = _get_node_builders()
|
|
245
|
+
|
|
246
|
+
start_a = start_var(a)
|
|
247
|
+
start_b = start_var(b)
|
|
248
|
+
|
|
249
|
+
# start(b) >= start(a) + delay => start(a) + delay <= start(b)
|
|
250
|
+
if delay:
|
|
251
|
+
lhs = Node.build(TypeNode.ADD, start_a, delay)
|
|
252
|
+
else:
|
|
253
|
+
lhs = start_a
|
|
254
|
+
return Node.build(TypeNode.LE, lhs, start_b)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def start_before_end(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
258
|
+
"""
|
|
259
|
+
Enforce that interval b cannot end before interval a starts (plus delay).
|
|
260
|
+
|
|
261
|
+
Semantics (when both present):
|
|
262
|
+
end(b) >= start(a) + delay
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
a: First interval variable.
|
|
266
|
+
b: Second interval variable.
|
|
267
|
+
delay: Minimum time between start of a and end of b. Default 0.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
A pycsp3 Node representing the constraint.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
277
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
278
|
+
>>> satisfy(start_before_end(task1, task2)) # task2 ends after task1 starts
|
|
279
|
+
"""
|
|
280
|
+
_validate_precedence_args(a, b, delay, "start_before_end")
|
|
281
|
+
Node, TypeNode = _get_node_builders()
|
|
282
|
+
|
|
283
|
+
start_a = start_var(a)
|
|
284
|
+
start_b = start_var(b)
|
|
285
|
+
length_b = length_value(b)
|
|
286
|
+
|
|
287
|
+
# end(b) >= start(a) + delay
|
|
288
|
+
# start(b) + length(b) >= start(a) + delay
|
|
289
|
+
# start(a) + delay <= start(b) + length(b)
|
|
290
|
+
if delay:
|
|
291
|
+
lhs = Node.build(TypeNode.ADD, start_a, delay)
|
|
292
|
+
else:
|
|
293
|
+
lhs = start_a
|
|
294
|
+
rhs = Node.build(TypeNode.ADD, start_b, length_b)
|
|
295
|
+
return Node.build(TypeNode.LE, lhs, rhs)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def end_before_start(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
299
|
+
"""
|
|
300
|
+
Enforce that interval a ends before interval b starts.
|
|
301
|
+
|
|
302
|
+
This is the classic precedence constraint used in job-shop scheduling.
|
|
303
|
+
|
|
304
|
+
Semantics (when both present):
|
|
305
|
+
start(b) >= end(a) + delay
|
|
306
|
+
start(b) >= start(a) + length(a) + delay
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
a: First interval variable (predecessor).
|
|
310
|
+
b: Second interval variable (successor).
|
|
311
|
+
delay: Minimum time between end of a and start of b. Default 0.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
A pycsp3 Node representing the constraint.
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
318
|
+
|
|
319
|
+
Example:
|
|
320
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
321
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
322
|
+
>>> satisfy(end_before_start(task1, task2)) # task2 starts after task1 ends
|
|
323
|
+
"""
|
|
324
|
+
_validate_precedence_args(a, b, delay, "end_before_start")
|
|
325
|
+
Node, TypeNode = _get_node_builders()
|
|
326
|
+
|
|
327
|
+
start_a = start_var(a)
|
|
328
|
+
start_b = start_var(b)
|
|
329
|
+
length_a = length_value(a)
|
|
330
|
+
|
|
331
|
+
# start(b) >= start(a) + length(a) + delay
|
|
332
|
+
lhs = Node.build(TypeNode.ADD, start_a, length_a)
|
|
333
|
+
if delay:
|
|
334
|
+
lhs = Node.build(TypeNode.ADD, lhs, delay)
|
|
335
|
+
return Node.build(TypeNode.LE, lhs, start_b)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def end_before_end(a: IntervalVar, b: IntervalVar, delay: int = 0):
|
|
339
|
+
"""
|
|
340
|
+
Enforce that interval b cannot end before interval a ends (plus delay).
|
|
341
|
+
|
|
342
|
+
Semantics (when both present):
|
|
343
|
+
end(b) >= end(a) + delay
|
|
344
|
+
start(b) + length(b) >= start(a) + length(a) + delay
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
a: First interval variable.
|
|
348
|
+
b: Second interval variable.
|
|
349
|
+
delay: Minimum time between end of a and end of b. Default 0.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
A pycsp3 Node representing the constraint.
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
TypeError: If inputs are not IntervalVar or delay is not int.
|
|
356
|
+
|
|
357
|
+
Example:
|
|
358
|
+
>>> task1 = IntervalVar(size=10, name="task1")
|
|
359
|
+
>>> task2 = IntervalVar(size=5, name="task2")
|
|
360
|
+
>>> satisfy(end_before_end(task1, task2)) # task2 ends after task1 ends
|
|
361
|
+
"""
|
|
362
|
+
_validate_precedence_args(a, b, delay, "end_before_end")
|
|
363
|
+
Node, TypeNode = _get_node_builders()
|
|
364
|
+
|
|
365
|
+
start_a = start_var(a)
|
|
366
|
+
start_b = start_var(b)
|
|
367
|
+
length_a = length_value(a)
|
|
368
|
+
length_b = length_value(b)
|
|
369
|
+
|
|
370
|
+
# end(b) >= end(a) + delay
|
|
371
|
+
# start(b) + length(b) >= start(a) + length(a) + delay
|
|
372
|
+
lhs = Node.build(TypeNode.ADD, start_a, length_a)
|
|
373
|
+
if delay:
|
|
374
|
+
lhs = Node.build(TypeNode.ADD, lhs, delay)
|
|
375
|
+
rhs = Node.build(TypeNode.ADD, start_b, length_b)
|
|
376
|
+
return Node.build(TypeNode.LE, lhs, rhs)
|