job-shop-lib 1.0.0a2__py3-none-any.whl → 1.0.0a4__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/_job_shop_instance.py +119 -55
- job_shop_lib/_operation.py +18 -7
- job_shop_lib/_schedule.py +13 -15
- job_shop_lib/_scheduled_operation.py +17 -18
- job_shop_lib/dispatching/__init__.py +4 -0
- job_shop_lib/dispatching/_dispatcher.py +36 -47
- job_shop_lib/dispatching/_dispatcher_observer_config.py +15 -2
- job_shop_lib/dispatching/_factories.py +10 -2
- job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -1
- job_shop_lib/dispatching/feature_observers/_factory.py +21 -18
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +1 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +1 -1
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +44 -25
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
- job_shop_lib/generation/_general_instance_generator.py +33 -34
- job_shop_lib/generation/_instance_generator.py +14 -17
- job_shop_lib/generation/_transformations.py +11 -8
- job_shop_lib/graphs/__init__.py +3 -0
- job_shop_lib/graphs/_build_disjunctive_graph.py +41 -3
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +11 -13
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +17 -20
- job_shop_lib/reinforcement_learning/__init__.py +16 -7
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +69 -57
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +43 -32
- job_shop_lib/reinforcement_learning/_types_and_constants.py +2 -2
- job_shop_lib/visualization/__init__.py +29 -10
- job_shop_lib/visualization/_gantt_chart_creator.py +122 -84
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +68 -37
- job_shop_lib/visualization/_plot_disjunctive_graph.py +382 -0
- job_shop_lib/visualization/{_gantt_chart.py → _plot_gantt_chart.py} +78 -14
- {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/METADATA +15 -3
- {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/RECORD +36 -36
- {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/WHEEL +1 -1
- job_shop_lib/visualization/_disjunctive_graph.py +0 -210
- /job_shop_lib/visualization/{_agent_task_graph.py → _plot_agent_task_graph.py} +0 -0
- {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/LICENSE +0 -0
@@ -15,49 +15,105 @@ from job_shop_lib import Operation
|
|
15
15
|
class JobShopInstance:
|
16
16
|
"""Data structure to store a Job Shop Scheduling Problem instance.
|
17
17
|
|
18
|
-
Additional attributes such as
|
19
|
-
from the instance and are cached for performance if they
|
20
|
-
computations.
|
18
|
+
Additional attributes such as ``num_machines`` or ``durations_matrix`` can
|
19
|
+
be computed from the instance and are cached for performance if they
|
20
|
+
require expensive computations.
|
21
|
+
|
22
|
+
Methods:
|
23
|
+
|
24
|
+
.. autosummary::
|
25
|
+
:nosignatures:
|
26
|
+
|
27
|
+
from_taillard_file
|
28
|
+
to_dict
|
29
|
+
from_matrices
|
30
|
+
set_operation_attributes
|
31
|
+
|
32
|
+
Properties:
|
33
|
+
|
34
|
+
.. autosummary::
|
35
|
+
:nosignatures:
|
36
|
+
|
37
|
+
num_jobs
|
38
|
+
num_machines
|
39
|
+
num_operations
|
40
|
+
is_flexible
|
41
|
+
durations_matrix
|
42
|
+
machines_matrix
|
43
|
+
durations_matrix_array
|
44
|
+
machines_matrix_array
|
45
|
+
operations_by_machine
|
46
|
+
max_duration
|
47
|
+
max_duration_per_job
|
48
|
+
max_duration_per_machine
|
49
|
+
job_durations
|
50
|
+
machine_loads
|
51
|
+
total_duration
|
21
52
|
|
22
53
|
Attributes:
|
23
|
-
jobs:
|
54
|
+
jobs (list[list[Operation]]):
|
24
55
|
A list of lists of operations. Each list of operations represents
|
25
56
|
a job, and the operations are ordered by their position in the job.
|
26
|
-
The
|
27
|
-
the operations are set when the instance is created.
|
28
|
-
name:
|
57
|
+
The ``job_id``, ``position_in_job``, and `operation_id` attributes
|
58
|
+
of the operations are set when the instance is created.
|
59
|
+
name (str):
|
29
60
|
A string with the name of the instance.
|
30
|
-
metadata:
|
61
|
+
metadata (dict[str, Any]):
|
31
62
|
A dictionary with additional information about the instance.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
jobs:
|
66
|
+
A list of lists of operations. Each list of operations
|
67
|
+
represents a job, and the operations are ordered by their
|
68
|
+
position in the job. The ``job_id``, ``position_in_job``, and
|
69
|
+
``operation_id`` attributes of the operations are set when the
|
70
|
+
instance is created.
|
71
|
+
name:
|
72
|
+
A string with the name of the instance.
|
73
|
+
set_operation_attributes:
|
74
|
+
If True, the ``job_id``, ``position_in_job``, and ``operation_id``
|
75
|
+
attributes of the operations are set when the instance is created.
|
76
|
+
See :meth:`set_operation_attributes` for more information. Defaults
|
77
|
+
to True.
|
78
|
+
**metadata:
|
79
|
+
Additional information about the instance.
|
32
80
|
"""
|
33
81
|
|
34
82
|
def __init__(
|
35
83
|
self,
|
36
84
|
jobs: list[list[Operation]],
|
37
85
|
name: str = "JobShopInstance",
|
86
|
+
set_operation_attributes: bool = True,
|
38
87
|
**metadata: Any,
|
39
88
|
):
|
40
|
-
|
89
|
+
self.jobs: list[list[Operation]] = jobs
|
90
|
+
if set_operation_attributes:
|
91
|
+
self.set_operation_attributes()
|
92
|
+
self.name: str = name
|
93
|
+
self.metadata: dict[str, Any] = metadata
|
94
|
+
|
95
|
+
def set_operation_attributes(self):
|
96
|
+
"""Sets the ``job_id``, ``position_in_job``, and ``operation_id``
|
97
|
+
attributes for each operation in the instance.
|
98
|
+
|
99
|
+
The ``job_id`` attribute is set to the id of the job to which the
|
100
|
+
operation belongs.
|
101
|
+
|
102
|
+
The ``position_in_job`` attribute is set to the
|
103
|
+
position of the operation in the job (starts from 0).
|
104
|
+
|
105
|
+
The ``operation_id`` attribute is set to a unique identifier for the
|
106
|
+
operation (starting from 0).
|
107
|
+
|
108
|
+
The formula to compute the ``operation_id`` in a job shop instance with
|
109
|
+
a fixed number of operations per job is:
|
110
|
+
|
111
|
+
.. code-block:: python
|
112
|
+
|
113
|
+
operation_id = job_id * num_operations_per_job + position_in_job
|
41
114
|
|
42
|
-
Args:
|
43
|
-
jobs:
|
44
|
-
A list of lists of operations. Each list of operations
|
45
|
-
represents a job, and the operations are ordered by their
|
46
|
-
position in the job. The `job_id`, `position_in_job`, and
|
47
|
-
`operation_id` attributes of the operations are set when the
|
48
|
-
instance is created.
|
49
|
-
name:
|
50
|
-
A string with the name of the instance.
|
51
|
-
**metadata:
|
52
|
-
Additional information about the instance.
|
53
115
|
"""
|
54
|
-
self.jobs = jobs
|
55
|
-
self.set_operation_attributes()
|
56
|
-
self.name = name
|
57
|
-
self.metadata = metadata
|
58
116
|
|
59
|
-
def set_operation_attributes(self):
|
60
|
-
"""Sets the job_id and position of each operation."""
|
61
117
|
operation_id = 0
|
62
118
|
for job_id, job in enumerate(self.jobs):
|
63
119
|
for position, operation in enumerate(job):
|
@@ -92,8 +148,8 @@ class JobShopInstance:
|
|
92
148
|
Additional information about the instance.
|
93
149
|
|
94
150
|
Returns:
|
95
|
-
A JobShopInstance object with the operations read from the
|
96
|
-
and the name and metadata provided.
|
151
|
+
A :class:`JobShopInstance` object with the operations read from the
|
152
|
+
file, and the name and metadata provided.
|
97
153
|
"""
|
98
154
|
with open(file_path, "r", encoding=encoding) as file:
|
99
155
|
lines = file.readlines()
|
@@ -130,13 +186,17 @@ class JobShopInstance:
|
|
130
186
|
like Taillard's.
|
131
187
|
|
132
188
|
Returns:
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
189
|
+
dict[str, Any]: The returned dictionary has the following
|
190
|
+
structure:
|
191
|
+
|
192
|
+
.. code-block:: python
|
193
|
+
|
194
|
+
{
|
195
|
+
"name": self.name,
|
196
|
+
"duration_matrix": self.durations_matrix,
|
197
|
+
"machines_matrix": self.machines_matrix,
|
198
|
+
"metadata": self.metadata,
|
199
|
+
}
|
140
200
|
"""
|
141
201
|
return {
|
142
202
|
"name": self.name,
|
@@ -153,7 +213,8 @@ class JobShopInstance:
|
|
153
213
|
name: str = "JobShopInstance",
|
154
214
|
metadata: dict[str, Any] | None = None,
|
155
215
|
) -> JobShopInstance:
|
156
|
-
"""Creates a JobShopInstance from duration and machines
|
216
|
+
"""Creates a :class:`JobShopInstance` from duration and machines
|
217
|
+
matrices.
|
157
218
|
|
158
219
|
Args:
|
159
220
|
duration_matrix:
|
@@ -170,7 +231,7 @@ class JobShopInstance:
|
|
170
231
|
A dictionary with additional information about the instance.
|
171
232
|
|
172
233
|
Returns:
|
173
|
-
A JobShopInstance object.
|
234
|
+
A :class:`JobShopInstance` object.
|
174
235
|
"""
|
175
236
|
jobs: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]
|
176
237
|
|
@@ -222,7 +283,7 @@ class JobShopInstance:
|
|
222
283
|
|
223
284
|
@functools.cached_property
|
224
285
|
def is_flexible(self) -> bool:
|
225
|
-
"""Returns True if any operation has more than one machine."""
|
286
|
+
"""Returns ``True`` if any operation has more than one machine."""
|
226
287
|
return any(
|
227
288
|
any(len(operation.machines) > 1 for operation in job)
|
228
289
|
for job in self.jobs
|
@@ -232,12 +293,14 @@ class JobShopInstance:
|
|
232
293
|
def durations_matrix(self) -> list[list[int]]:
|
233
294
|
"""Returns the duration matrix of the instance.
|
234
295
|
|
235
|
-
The duration of the operation with
|
236
|
-
is stored in the i-th position of the j-th list of the returned
|
296
|
+
The duration of the operation with ``job_id`` i and ``position_in_job``
|
297
|
+
j is stored in the i-th position of the j-th list of the returned
|
298
|
+
matrix:
|
299
|
+
|
300
|
+
.. code-block:: python
|
301
|
+
|
302
|
+
duration = instance.durations_matrix[i][j]
|
237
303
|
|
238
|
-
```python
|
239
|
-
duration = instance.durations_matrix[i][j]
|
240
|
-
```
|
241
304
|
"""
|
242
305
|
return [[operation.duration for operation in job] for job in self.jobs]
|
243
306
|
|
@@ -254,9 +317,9 @@ class JobShopInstance:
|
|
254
317
|
To access the machines of the operation with position i in the job
|
255
318
|
with id j, the following code must be used:
|
256
319
|
|
257
|
-
|
258
|
-
|
259
|
-
|
320
|
+
.. code-block:: python
|
321
|
+
|
322
|
+
machines = instance.machines_matrix[j][i]
|
260
323
|
|
261
324
|
"""
|
262
325
|
if self.is_flexible:
|
@@ -271,8 +334,9 @@ class JobShopInstance:
|
|
271
334
|
def durations_matrix_array(self) -> NDArray[np.float32]:
|
272
335
|
"""Returns the duration matrix of the instance as a numpy array.
|
273
336
|
|
274
|
-
The returned array has shape (num_jobs
|
275
|
-
|
337
|
+
The returned array has shape (``num_jobs``,
|
338
|
+
``max_num_operations_per_job``).
|
339
|
+
Non-existing operations are filled with ``np.nan``.
|
276
340
|
|
277
341
|
Example:
|
278
342
|
>>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
|
@@ -288,9 +352,9 @@ class JobShopInstance:
|
|
288
352
|
def machines_matrix_array(self) -> NDArray[np.float32]:
|
289
353
|
"""Returns the machines matrix of the instance as a numpy array.
|
290
354
|
|
291
|
-
The returned array has shape (num_jobs
|
292
|
-
max_num_machines_per_operation).
|
293
|
-
np.nan
|
355
|
+
The returned array has shape (``num_jobs``,
|
356
|
+
``max_num_operations_per_job``, ``max_num_machines_per_operation``).
|
357
|
+
Non-existing machines are filled with ``np.nan``.
|
294
358
|
|
295
359
|
Example:
|
296
360
|
>>> jobs = [
|
@@ -413,7 +477,7 @@ class JobShopInstance:
|
|
413
477
|
def _fill_matrix_with_nans_2d(
|
414
478
|
matrix: list[list[int]],
|
415
479
|
) -> NDArray[np.float32]:
|
416
|
-
"""Fills a matrix with np.nan values.
|
480
|
+
"""Fills a matrix with ``np.nan`` values.
|
417
481
|
|
418
482
|
Args:
|
419
483
|
matrix:
|
@@ -421,7 +485,7 @@ class JobShopInstance:
|
|
421
485
|
|
422
486
|
Returns:
|
423
487
|
A numpy array with the same shape as the input matrix, filled with
|
424
|
-
np.nan values.
|
488
|
+
``np.nan`` values.
|
425
489
|
"""
|
426
490
|
max_length = max(len(row) for row in matrix)
|
427
491
|
squared_matrix = np.full(
|
@@ -435,7 +499,7 @@ class JobShopInstance:
|
|
435
499
|
def _fill_matrix_with_nans_3d(
|
436
500
|
matrix: list[list[list[int]]],
|
437
501
|
) -> NDArray[np.float32]:
|
438
|
-
"""Fills a 3D matrix with np.nan values.
|
502
|
+
"""Fills a 3D matrix with ``np.nan`` values.
|
439
503
|
|
440
504
|
Args:
|
441
505
|
matrix:
|
@@ -443,7 +507,7 @@ class JobShopInstance:
|
|
443
507
|
|
444
508
|
Returns:
|
445
509
|
A numpy array with the same shape as the input matrix, filled with
|
446
|
-
np.nan values.
|
510
|
+
``np.nan`` values.
|
447
511
|
"""
|
448
512
|
max_length = max(len(row) for row in matrix)
|
449
513
|
max_inner_length = len(matrix[0][0])
|
job_shop_lib/_operation.py
CHANGED
@@ -42,17 +42,28 @@ class Operation:
|
|
42
42
|
"The time it takes to perform the operation. Often referred"
|
43
43
|
" to as the processing time."
|
44
44
|
),
|
45
|
-
"job_id":
|
46
|
-
|
45
|
+
"job_id": (
|
46
|
+
"The id of the job the operation belongs to. Defaults to -1. "
|
47
|
+
"It is usually set by the :class:`JobShopInstance` class after "
|
48
|
+
"initialization."
|
49
|
+
),
|
50
|
+
"position_in_job": (
|
51
|
+
"The index of the operation in the job. Defaults to -1. "
|
52
|
+
"It is usually set by the :class:`JobShopInstance` class after "
|
53
|
+
"initialization."
|
54
|
+
),
|
47
55
|
"operation_id": (
|
48
56
|
"The id of the operation. This is unique within a "
|
49
|
-
":class:`JobShopInstance`."
|
57
|
+
":class:`JobShopInstance`. Defaults to -1. It is usually set by "
|
58
|
+
"the :class:`JobShopInstance` class after initialization."
|
50
59
|
),
|
51
60
|
}
|
52
61
|
|
53
62
|
def __init__(self, machines: int | list[int], duration: int):
|
54
|
-
self.machines
|
55
|
-
|
63
|
+
self.machines: list[int] = (
|
64
|
+
[machines] if isinstance(machines, int) else machines
|
65
|
+
)
|
66
|
+
self.duration: int = duration
|
56
67
|
|
57
68
|
# Defined outside the class by the JobShopInstance class:
|
58
69
|
self.job_id: int = -1
|
@@ -64,8 +75,8 @@ class Operation:
|
|
64
75
|
"""Returns the id of the machine associated with the operation.
|
65
76
|
|
66
77
|
Raises:
|
67
|
-
UninitializedAttributeError:
|
68
|
-
|
78
|
+
UninitializedAttributeError:
|
79
|
+
If the operation has multiple machines in its list.
|
69
80
|
"""
|
70
81
|
if len(self.machines) > 1:
|
71
82
|
raise UninitializedAttributeError(
|
job_shop_lib/_schedule.py
CHANGED
@@ -25,6 +25,16 @@ class Schedule:
|
|
25
25
|
is_complete
|
26
26
|
add
|
27
27
|
reset
|
28
|
+
|
29
|
+
Args:
|
30
|
+
instance:
|
31
|
+
The :class:`JobShopInstance` object that the schedule is for.
|
32
|
+
schedule:
|
33
|
+
A list of lists of :class:`ScheduledOperation` objects. Each
|
34
|
+
list represents the order of operations on a machine. If
|
35
|
+
not provided, the schedule is initialized as an empty schedule.
|
36
|
+
**metadata:
|
37
|
+
Additional information about the schedule.
|
28
38
|
"""
|
29
39
|
|
30
40
|
__slots__ = {
|
@@ -46,28 +56,16 @@ class Schedule:
|
|
46
56
|
self,
|
47
57
|
instance: JobShopInstance,
|
48
58
|
schedule: list[list[ScheduledOperation]] | None = None,
|
49
|
-
**metadata,
|
59
|
+
**metadata: Any,
|
50
60
|
):
|
51
|
-
"""Initializes the object with the given instance and schedule.
|
52
|
-
|
53
|
-
Args:
|
54
|
-
instance:
|
55
|
-
The :class:`JobShopInstance` object that the schedule is for.
|
56
|
-
schedule:
|
57
|
-
A list of lists of :class:`ScheduledOperation` objects. Each
|
58
|
-
list represents the order of operations on a machine. If
|
59
|
-
not provided, the schedule is initialized as an empty schedule.
|
60
|
-
**metadata:
|
61
|
-
Additional information about the schedule.
|
62
|
-
"""
|
63
61
|
if schedule is None:
|
64
62
|
schedule = [[] for _ in range(instance.num_machines)]
|
65
63
|
|
66
64
|
Schedule.check_schedule(schedule)
|
67
65
|
|
68
|
-
self.instance = instance
|
66
|
+
self.instance: JobShopInstance = instance
|
69
67
|
self._schedule = schedule
|
70
|
-
self.metadata = metadata
|
68
|
+
self.metadata: dict[str, Any] = metadata
|
71
69
|
|
72
70
|
def __repr__(self) -> str:
|
73
71
|
return str(self.schedule)
|
@@ -5,7 +5,21 @@ from job_shop_lib.exceptions import ValidationError
|
|
5
5
|
|
6
6
|
|
7
7
|
class ScheduledOperation:
|
8
|
-
"""Data structure to store a scheduled operation.
|
8
|
+
"""Data structure to store a scheduled operation.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
operation:
|
12
|
+
The :class:`Operation` object that is scheduled.
|
13
|
+
start_time:
|
14
|
+
The time at which the operation is scheduled to start.
|
15
|
+
machine_id:
|
16
|
+
The id of the machine on which the operation is scheduled.
|
17
|
+
|
18
|
+
Raises:
|
19
|
+
ValidationError:
|
20
|
+
If the given machine_id is not in the list of valid machines
|
21
|
+
for the operation.
|
22
|
+
"""
|
9
23
|
|
10
24
|
__slots__ = {
|
11
25
|
"operation": "The :class:`Operation` object that is scheduled.",
|
@@ -16,23 +30,8 @@ class ScheduledOperation:
|
|
16
30
|
}
|
17
31
|
|
18
32
|
def __init__(self, operation: Operation, start_time: int, machine_id: int):
|
19
|
-
|
20
|
-
|
21
|
-
Args:
|
22
|
-
operation:
|
23
|
-
The :class:`Operation` object that is scheduled.
|
24
|
-
start_time:
|
25
|
-
The time at which the operation is scheduled to start.
|
26
|
-
machine_id:
|
27
|
-
The id of the machine on which the operation is scheduled.
|
28
|
-
|
29
|
-
Raises:
|
30
|
-
ValidationError:
|
31
|
-
If the given machine_id is not in the list of valid machines
|
32
|
-
for the operation.
|
33
|
-
"""
|
34
|
-
self.operation = operation
|
35
|
-
self.start_time = start_time
|
33
|
+
self.operation: Operation = operation
|
34
|
+
self.start_time: int = start_time
|
36
35
|
self._machine_id = machine_id
|
37
36
|
self.machine_id = machine_id # Validate machine_id
|
38
37
|
|
@@ -32,6 +32,8 @@ from ._ready_operation_filters import (
|
|
32
32
|
filter_dominated_operations,
|
33
33
|
filter_non_immediate_machines,
|
34
34
|
ReadyOperationsFilter,
|
35
|
+
filter_non_idle_machines,
|
36
|
+
filter_non_immediate_operations,
|
35
37
|
)
|
36
38
|
from ._dispatcher_observer_config import DispatcherObserverConfig
|
37
39
|
from ._factories import (
|
@@ -53,4 +55,6 @@ __all__ = [
|
|
53
55
|
"DispatcherObserverConfig",
|
54
56
|
"UnscheduledOperationsObserver",
|
55
57
|
"ReadyOperationsFilter",
|
58
|
+
"filter_non_idle_machines",
|
59
|
+
"filter_non_immediate_operations",
|
56
60
|
]
|
@@ -29,6 +29,18 @@ class DispatcherObserver(abc.ABC):
|
|
29
29
|
dispatcher:
|
30
30
|
The :class:`Dispatcher` instance to observe.
|
31
31
|
|
32
|
+
Args:
|
33
|
+
dispatcher:
|
34
|
+
The :class:`Dispatcher` instance to observe.
|
35
|
+
subscribe:
|
36
|
+
If ``True``, automatically subscribes the observer to the
|
37
|
+
dispatcher when it is initialized. Defaults to ``True``.
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
ValidationError: If ``is_singleton`` is ``True`` and an observer of the
|
41
|
+
same type already exists in the dispatcher's list of
|
42
|
+
subscribers.
|
43
|
+
|
32
44
|
Example:
|
33
45
|
|
34
46
|
.. code-block:: python
|
@@ -61,21 +73,6 @@ class DispatcherObserver(abc.ABC):
|
|
61
73
|
*,
|
62
74
|
subscribe: bool = True,
|
63
75
|
):
|
64
|
-
"""Initializes the observer with the :class:`Dispatcher` and subscribes
|
65
|
-
to it.
|
66
|
-
|
67
|
-
Args:
|
68
|
-
dispatcher:
|
69
|
-
The `Dispatcher` instance to observe.
|
70
|
-
subscribe:
|
71
|
-
If True, automatically subscribes the observer to the
|
72
|
-
dispatcher.
|
73
|
-
|
74
|
-
Raises:
|
75
|
-
ValidationError: If ``is_singleton`` is True and an observer of the
|
76
|
-
same type already exists in the dispatcher's list of
|
77
|
-
subscribers.
|
78
|
-
"""
|
79
76
|
if self._is_singleton and any(
|
80
77
|
isinstance(observer, self.__class__)
|
81
78
|
for observer in dispatcher.subscribers
|
@@ -156,26 +153,30 @@ class Dispatcher:
|
|
156
153
|
responsible for scheduling the operations on the machines and keeping
|
157
154
|
track of the next available time for each machine and job.
|
158
155
|
|
159
|
-
|
156
|
+
Args:
|
160
157
|
instance:
|
161
|
-
The instance of the job shop problem to be
|
162
|
-
schedule:
|
163
|
-
The schedule of operations on machines.
|
158
|
+
The instance of the job shop problem to be solved.
|
164
159
|
ready_operations_filter:
|
165
|
-
A function that filters out operations that are not ready to
|
166
|
-
scheduled.
|
160
|
+
A function that filters out operations that are not ready to
|
161
|
+
be scheduled. The function should take the dispatcher and a
|
162
|
+
list of operations as input and return a list of operations
|
163
|
+
that are ready to be scheduled. If ``None``, no filtering is
|
164
|
+
done.
|
167
165
|
"""
|
168
166
|
|
169
|
-
__slots__ =
|
170
|
-
"instance",
|
171
|
-
"schedule",
|
172
|
-
"_machine_next_available_time",
|
173
|
-
"_job_next_operation_index",
|
174
|
-
"_job_next_available_time",
|
175
|
-
"ready_operations_filter"
|
176
|
-
|
177
|
-
|
178
|
-
|
167
|
+
__slots__ = {
|
168
|
+
"instance": "The instance of the job shop problem to be scheduled.",
|
169
|
+
"schedule": "The schedule of operations on machines.",
|
170
|
+
"_machine_next_available_time": "",
|
171
|
+
"_job_next_operation_index": "",
|
172
|
+
"_job_next_available_time": "",
|
173
|
+
"ready_operations_filter": (
|
174
|
+
"A function that filters out operations that are not ready to be "
|
175
|
+
"scheduled."
|
176
|
+
),
|
177
|
+
"subscribers": "A list of observers subscribed to the dispatcher.",
|
178
|
+
"_cache": "A dictionary to cache the results of the cached methods.",
|
179
|
+
}
|
179
180
|
|
180
181
|
def __init__(
|
181
182
|
self,
|
@@ -184,18 +185,6 @@ class Dispatcher:
|
|
184
185
|
Callable[[Dispatcher, list[Operation]], list[Operation]] | None
|
185
186
|
) = None,
|
186
187
|
) -> None:
|
187
|
-
"""Initializes the object with the given instance.
|
188
|
-
|
189
|
-
Args:
|
190
|
-
instance:
|
191
|
-
The instance of the job shop problem to be solved.
|
192
|
-
ready_operations_filter:
|
193
|
-
A function that filters out operations that are not ready to
|
194
|
-
be scheduled. The function should take the dispatcher and a
|
195
|
-
list of operations as input and return a list of operations
|
196
|
-
that are ready to be scheduled. If ``None``, no filtering is
|
197
|
-
done.
|
198
|
-
"""
|
199
188
|
|
200
189
|
self.instance = instance
|
201
190
|
self.schedule = Schedule(self.instance)
|
@@ -371,7 +360,7 @@ class Dispatcher:
|
|
371
360
|
The current time is the minimum start time of the available
|
372
361
|
operations.
|
373
362
|
"""
|
374
|
-
available_operations = self.
|
363
|
+
available_operations = self.available_operations()
|
375
364
|
current_time = self.min_start_time(available_operations)
|
376
365
|
return current_time
|
377
366
|
|
@@ -387,7 +376,7 @@ class Dispatcher:
|
|
387
376
|
return int(min_start_time)
|
388
377
|
|
389
378
|
@_dispatcher_cache
|
390
|
-
def
|
379
|
+
def available_operations(self) -> list[Operation]:
|
391
380
|
"""Returns a list of available operations for processing, optionally
|
392
381
|
filtering out operations using the filter function.
|
393
382
|
|
@@ -443,7 +432,7 @@ class Dispatcher:
|
|
443
432
|
@_dispatcher_cache
|
444
433
|
def available_machines(self) -> list[int]:
|
445
434
|
"""Returns the list of ready machines."""
|
446
|
-
available_operations = self.
|
435
|
+
available_operations = self.available_operations()
|
447
436
|
available_machines = set()
|
448
437
|
for operation in available_operations:
|
449
438
|
available_machines.update(operation.machines)
|
@@ -452,7 +441,7 @@ class Dispatcher:
|
|
452
441
|
@_dispatcher_cache
|
453
442
|
def available_jobs(self) -> list[int]:
|
454
443
|
"""Returns the list of ready jobs."""
|
455
|
-
available_operations = self.
|
444
|
+
available_operations = self.available_operations()
|
456
445
|
available_jobs = set(
|
457
446
|
operation.job_id for operation in available_operations
|
458
447
|
)
|
@@ -26,14 +26,21 @@ class DispatcherObserverConfig(Generic[T]):
|
|
26
26
|
keyword arguments to pass to the dispatcher observer constructor while
|
27
27
|
not containing the ``dispatcher`` argument.
|
28
28
|
|
29
|
-
|
29
|
+
Args:
|
30
30
|
class_type:
|
31
31
|
Type of the class to be initialized. It can be the class type, an
|
32
32
|
enum value, or a string. This is useful for the creation of
|
33
|
-
DispatcherObserver instances
|
33
|
+
:class:`~job_shop_lib.dispatching.DispatcherObserver` instances
|
34
|
+
from the factory functions.
|
34
35
|
kwargs:
|
35
36
|
Keyword arguments needed to initialize the class. It must not
|
36
37
|
contain the ``dispatcher`` argument.
|
38
|
+
|
39
|
+
.. seealso::
|
40
|
+
|
41
|
+
- :class:`~job_shop_lib.dispatching.DispatcherObserver`
|
42
|
+
- :func:`job_shop_lib.dispatching.feature_observers.\\
|
43
|
+
feature_observer_factory`
|
37
44
|
"""
|
38
45
|
|
39
46
|
# We use the type hint T, instead of ObserverType, to allow for string or
|
@@ -44,7 +51,13 @@ class DispatcherObserverConfig(Generic[T]):
|
|
44
51
|
# This allows for the creation of a FeatureObserver instance
|
45
52
|
# from the factory function.
|
46
53
|
class_type: T
|
54
|
+
"""Type of the class to be initialized. It can be the class type, an
|
55
|
+
enum value, or a string. This is useful for the creation of
|
56
|
+
:class:`DispatcherObserver` instances from the factory functions."""
|
57
|
+
|
47
58
|
kwargs: dict[str, Any] = field(default_factory=dict)
|
59
|
+
"""Keyword arguments needed to initialize the class. It must not
|
60
|
+
contain the ``dispatcher`` argument."""
|
48
61
|
|
49
62
|
def __post_init__(self):
|
50
63
|
if "dispatcher" in self.kwargs:
|
@@ -13,6 +13,8 @@ from job_shop_lib.dispatching import (
|
|
13
13
|
Dispatcher,
|
14
14
|
filter_dominated_operations,
|
15
15
|
filter_non_immediate_machines,
|
16
|
+
filter_non_idle_machines,
|
17
|
+
filter_non_immediate_operations,
|
16
18
|
ReadyOperationsFilter,
|
17
19
|
)
|
18
20
|
|
@@ -27,6 +29,8 @@ class ReadyOperationsFilterType(str, Enum):
|
|
27
29
|
|
28
30
|
DOMINATED_OPERATIONS = "dominated_operations"
|
29
31
|
NON_IMMEDIATE_MACHINES = "non_immediate_machines"
|
32
|
+
NON_IDLE_MACHINES = "non_idle_machines"
|
33
|
+
NON_IMMEDIATE_OPERATIONS = "non_immediate_operations"
|
30
34
|
|
31
35
|
|
32
36
|
def create_composite_operation_filter(
|
@@ -47,13 +51,13 @@ def create_composite_operation_filter(
|
|
47
51
|
'non_immediate_machines' or any Callable that takes a
|
48
52
|
:class:`~job_shop_lib.dispatching.Dispatcher` instance and a list
|
49
53
|
of :class:`~job_shop_lib.Operation` instances as input
|
50
|
-
and returns a list of :class:`~job_shop_lib.Operation`instances.
|
54
|
+
and returns a list of :class:`~job_shop_lib.Operation` instances.
|
51
55
|
|
52
56
|
Returns:
|
53
57
|
A function that takes a :class:`~job_shop_lib.dispatching.Dispatcher`
|
54
58
|
instance and a list of :class:`~job_shop_lib.Operation`
|
55
59
|
instances as input and returns a list of
|
56
|
-
:class:`~job_shop_lib.Operation`instances based on
|
60
|
+
:class:`~job_shop_lib.Operation` instances based on
|
57
61
|
the specified list of filter strategies.
|
58
62
|
|
59
63
|
Raises:
|
@@ -114,6 +118,10 @@ def ready_operations_filter_factory(
|
|
114
118
|
ReadyOperationsFilterType.NON_IMMEDIATE_MACHINES: (
|
115
119
|
filter_non_immediate_machines
|
116
120
|
),
|
121
|
+
ReadyOperationsFilterType.NON_IDLE_MACHINES: filter_non_idle_machines,
|
122
|
+
ReadyOperationsFilterType.NON_IMMEDIATE_OPERATIONS: (
|
123
|
+
filter_non_immediate_operations
|
124
|
+
),
|
117
125
|
}
|
118
126
|
|
119
127
|
if filter_name not in filtering_strategies:
|