job-shop-lib 1.0.0a3__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 +104 -38
- job_shop_lib/_operation.py +12 -3
- job_shop_lib/_schedule.py +10 -12
- job_shop_lib/_scheduled_operation.py +15 -16
- job_shop_lib/dispatching/_dispatcher.py +12 -15
- job_shop_lib/dispatching/_dispatcher_observer_config.py +15 -2
- job_shop_lib/dispatching/_factories.py +2 -2
- 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/rules/_dispatching_rule_solver.py +1 -1
- 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 +42 -31
- job_shop_lib/reinforcement_learning/_types_and_constants.py +2 -2
- job_shop_lib/visualization/__init__.py +24 -5
- job_shop_lib/visualization/_gantt_chart_creator.py +118 -80
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +15 -11
- job_shop_lib/visualization/_plot_disjunctive_graph.py +382 -0
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/METADATA +5 -5
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/RECORD +31 -31
- 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.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a3.dist-info → job_shop_lib-1.0.0a4.dist-info}/WHEEL +0 -0
@@ -15,16 +15,47 @@ 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
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.
|
57
|
+
The ``job_id``, ``position_in_job``, and `operation_id` attributes
|
58
|
+
of the operations are set when the instance is created.
|
28
59
|
name (str):
|
29
60
|
A string with the name of the instance.
|
30
61
|
metadata (dict[str, Any]):
|
@@ -34,11 +65,16 @@ class JobShopInstance:
|
|
34
65
|
jobs:
|
35
66
|
A list of lists of operations. Each list of operations
|
36
67
|
represents a job, and the operations are ordered by their
|
37
|
-
position in the job. The
|
38
|
-
|
68
|
+
position in the job. The ``job_id``, ``position_in_job``, and
|
69
|
+
``operation_id`` attributes of the operations are set when the
|
39
70
|
instance is created.
|
40
71
|
name:
|
41
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.
|
42
78
|
**metadata:
|
43
79
|
Additional information about the instance.
|
44
80
|
"""
|
@@ -47,15 +83,37 @@ class JobShopInstance:
|
|
47
83
|
self,
|
48
84
|
jobs: list[list[Operation]],
|
49
85
|
name: str = "JobShopInstance",
|
86
|
+
set_operation_attributes: bool = True,
|
50
87
|
**metadata: Any,
|
51
88
|
):
|
52
89
|
self.jobs: list[list[Operation]] = jobs
|
53
|
-
|
90
|
+
if set_operation_attributes:
|
91
|
+
self.set_operation_attributes()
|
54
92
|
self.name: str = name
|
55
93
|
self.metadata: dict[str, Any] = metadata
|
56
94
|
|
57
95
|
def set_operation_attributes(self):
|
58
|
-
"""Sets the job_id and
|
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
|
114
|
+
|
115
|
+
"""
|
116
|
+
|
59
117
|
operation_id = 0
|
60
118
|
for job_id, job in enumerate(self.jobs):
|
61
119
|
for position, operation in enumerate(job):
|
@@ -90,8 +148,8 @@ class JobShopInstance:
|
|
90
148
|
Additional information about the instance.
|
91
149
|
|
92
150
|
Returns:
|
93
|
-
A JobShopInstance object with the operations read from the
|
94
|
-
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.
|
95
153
|
"""
|
96
154
|
with open(file_path, "r", encoding=encoding) as file:
|
97
155
|
lines = file.readlines()
|
@@ -128,13 +186,17 @@ class JobShopInstance:
|
|
128
186
|
like Taillard's.
|
129
187
|
|
130
188
|
Returns:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
+
}
|
138
200
|
"""
|
139
201
|
return {
|
140
202
|
"name": self.name,
|
@@ -151,7 +213,8 @@ class JobShopInstance:
|
|
151
213
|
name: str = "JobShopInstance",
|
152
214
|
metadata: dict[str, Any] | None = None,
|
153
215
|
) -> JobShopInstance:
|
154
|
-
"""Creates a JobShopInstance from duration and machines
|
216
|
+
"""Creates a :class:`JobShopInstance` from duration and machines
|
217
|
+
matrices.
|
155
218
|
|
156
219
|
Args:
|
157
220
|
duration_matrix:
|
@@ -168,7 +231,7 @@ class JobShopInstance:
|
|
168
231
|
A dictionary with additional information about the instance.
|
169
232
|
|
170
233
|
Returns:
|
171
|
-
A JobShopInstance object.
|
234
|
+
A :class:`JobShopInstance` object.
|
172
235
|
"""
|
173
236
|
jobs: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]
|
174
237
|
|
@@ -220,7 +283,7 @@ class JobShopInstance:
|
|
220
283
|
|
221
284
|
@functools.cached_property
|
222
285
|
def is_flexible(self) -> bool:
|
223
|
-
"""Returns True if any operation has more than one machine."""
|
286
|
+
"""Returns ``True`` if any operation has more than one machine."""
|
224
287
|
return any(
|
225
288
|
any(len(operation.machines) > 1 for operation in job)
|
226
289
|
for job in self.jobs
|
@@ -230,12 +293,14 @@ class JobShopInstance:
|
|
230
293
|
def durations_matrix(self) -> list[list[int]]:
|
231
294
|
"""Returns the duration matrix of the instance.
|
232
295
|
|
233
|
-
The duration of the operation with
|
234
|
-
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]
|
235
303
|
|
236
|
-
```python
|
237
|
-
duration = instance.durations_matrix[i][j]
|
238
|
-
```
|
239
304
|
"""
|
240
305
|
return [[operation.duration for operation in job] for job in self.jobs]
|
241
306
|
|
@@ -252,9 +317,9 @@ class JobShopInstance:
|
|
252
317
|
To access the machines of the operation with position i in the job
|
253
318
|
with id j, the following code must be used:
|
254
319
|
|
255
|
-
|
256
|
-
|
257
|
-
|
320
|
+
.. code-block:: python
|
321
|
+
|
322
|
+
machines = instance.machines_matrix[j][i]
|
258
323
|
|
259
324
|
"""
|
260
325
|
if self.is_flexible:
|
@@ -269,8 +334,9 @@ class JobShopInstance:
|
|
269
334
|
def durations_matrix_array(self) -> NDArray[np.float32]:
|
270
335
|
"""Returns the duration matrix of the instance as a numpy array.
|
271
336
|
|
272
|
-
The returned array has shape (num_jobs
|
273
|
-
|
337
|
+
The returned array has shape (``num_jobs``,
|
338
|
+
``max_num_operations_per_job``).
|
339
|
+
Non-existing operations are filled with ``np.nan``.
|
274
340
|
|
275
341
|
Example:
|
276
342
|
>>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
|
@@ -286,9 +352,9 @@ class JobShopInstance:
|
|
286
352
|
def machines_matrix_array(self) -> NDArray[np.float32]:
|
287
353
|
"""Returns the machines matrix of the instance as a numpy array.
|
288
354
|
|
289
|
-
The returned array has shape (num_jobs
|
290
|
-
max_num_machines_per_operation).
|
291
|
-
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``.
|
292
358
|
|
293
359
|
Example:
|
294
360
|
>>> jobs = [
|
@@ -411,7 +477,7 @@ class JobShopInstance:
|
|
411
477
|
def _fill_matrix_with_nans_2d(
|
412
478
|
matrix: list[list[int]],
|
413
479
|
) -> NDArray[np.float32]:
|
414
|
-
"""Fills a matrix with np.nan values.
|
480
|
+
"""Fills a matrix with ``np.nan`` values.
|
415
481
|
|
416
482
|
Args:
|
417
483
|
matrix:
|
@@ -419,7 +485,7 @@ class JobShopInstance:
|
|
419
485
|
|
420
486
|
Returns:
|
421
487
|
A numpy array with the same shape as the input matrix, filled with
|
422
|
-
np.nan values.
|
488
|
+
``np.nan`` values.
|
423
489
|
"""
|
424
490
|
max_length = max(len(row) for row in matrix)
|
425
491
|
squared_matrix = np.full(
|
@@ -433,7 +499,7 @@ class JobShopInstance:
|
|
433
499
|
def _fill_matrix_with_nans_3d(
|
434
500
|
matrix: list[list[list[int]]],
|
435
501
|
) -> NDArray[np.float32]:
|
436
|
-
"""Fills a 3D matrix with np.nan values.
|
502
|
+
"""Fills a 3D matrix with ``np.nan`` values.
|
437
503
|
|
438
504
|
Args:
|
439
505
|
matrix:
|
@@ -441,7 +507,7 @@ class JobShopInstance:
|
|
441
507
|
|
442
508
|
Returns:
|
443
509
|
A numpy array with the same shape as the input matrix, filled with
|
444
|
-
np.nan values.
|
510
|
+
``np.nan`` values.
|
445
511
|
"""
|
446
512
|
max_length = max(len(row) for row in matrix)
|
447
513
|
max_inner_length = len(matrix[0][0])
|
job_shop_lib/_operation.py
CHANGED
@@ -42,11 +42,20 @@ 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
|
|
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__ = {
|
@@ -48,18 +58,6 @@ class Schedule:
|
|
48
58
|
schedule: list[list[ScheduledOperation]] | None = None,
|
49
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
|
|
@@ -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,21 +30,6 @@ class ScheduledOperation:
|
|
16
30
|
}
|
17
31
|
|
18
32
|
def __init__(self, operation: Operation, start_time: int, machine_id: int):
|
19
|
-
"""Initializes a new instance of the :class:`ScheduledOperation` class.
|
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
33
|
self.operation: Operation = operation
|
35
34
|
self.start_time: int = start_time
|
36
35
|
self._machine_id = machine_id
|
@@ -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
|
@@ -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:
|
@@ -51,13 +51,13 @@ def create_composite_operation_filter(
|
|
51
51
|
'non_immediate_machines' or any Callable that takes a
|
52
52
|
:class:`~job_shop_lib.dispatching.Dispatcher` instance and a list
|
53
53
|
of :class:`~job_shop_lib.Operation` instances as input
|
54
|
-
and returns a list of :class:`~job_shop_lib.Operation`instances.
|
54
|
+
and returns a list of :class:`~job_shop_lib.Operation` instances.
|
55
55
|
|
56
56
|
Returns:
|
57
57
|
A function that takes a :class:`~job_shop_lib.dispatching.Dispatcher`
|
58
58
|
instance and a list of :class:`~job_shop_lib.Operation`
|
59
59
|
instances as input and returns a list of
|
60
|
-
:class:`~job_shop_lib.Operation`instances based on
|
60
|
+
:class:`~job_shop_lib.Operation` instances based on
|
61
61
|
the specified list of filter strategies.
|
62
62
|
|
63
63
|
Raises:
|
@@ -193,7 +193,6 @@ if __name__ == "__main__":
|
|
193
193
|
dispatcher=dispatcher_,
|
194
194
|
)
|
195
195
|
for observer_type in feature_observer_types_
|
196
|
-
if not observer_type == FeatureObserverType.COMPOSITE
|
197
196
|
# and not FeatureObserverType.EARLIEST_START_TIME
|
198
197
|
]
|
199
198
|
composite_observer_ = CompositeFeatureObserver(
|
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Contains factory functions for creating
|
1
|
+
"""Contains factory functions for creating :class:`FeatureObserver`s."""
|
2
2
|
|
3
3
|
from enum import Enum
|
4
4
|
|
@@ -18,8 +18,12 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
18
18
|
class FeatureObserverType(str, Enum):
|
19
19
|
"""Enumeration of the different feature observers.
|
20
20
|
|
21
|
-
Each
|
22
|
-
to create the
|
21
|
+
Each :class:`FeatureObserver` is associated with a string value that can be
|
22
|
+
used to create the :class:`FeatureObserver` using the factory function.
|
23
|
+
|
24
|
+
It does not include the :class:`CompositeFeatureObserver` class since this
|
25
|
+
observer is often managed separately from the others. For example, a
|
26
|
+
common use case is to create a list of feature observers and pass them to
|
23
27
|
"""
|
24
28
|
|
25
29
|
IS_READY = "is_ready"
|
@@ -29,7 +33,6 @@ class FeatureObserverType(str, Enum):
|
|
29
33
|
POSITION_IN_JOB = "position_in_job"
|
30
34
|
REMAINING_OPERATIONS = "remaining_operations"
|
31
35
|
IS_COMPLETED = "is_completed"
|
32
|
-
COMPOSITE = "composite"
|
33
36
|
|
34
37
|
|
35
38
|
# FeatureObserverConfig = DispatcherObserverConfig[
|
@@ -43,7 +46,7 @@ FeatureObserverConfig = (
|
|
43
46
|
|
44
47
|
|
45
48
|
def feature_observer_factory(
|
46
|
-
|
49
|
+
feature_observer_type: (
|
47
50
|
str
|
48
51
|
| FeatureObserverType
|
49
52
|
| type[FeatureObserver]
|
@@ -51,29 +54,29 @@ def feature_observer_factory(
|
|
51
54
|
),
|
52
55
|
**kwargs,
|
53
56
|
) -> FeatureObserver:
|
54
|
-
"""Creates and returns a
|
55
|
-
|
57
|
+
"""Creates and returns a :class:`FeatureObserver` based on the specified
|
58
|
+
:class:`FeatureObserver` type.
|
56
59
|
|
57
60
|
Args:
|
58
61
|
feature_creator_type:
|
59
|
-
The type of
|
62
|
+
The type of :class:`FeatureObserver` to create.
|
60
63
|
**kwargs:
|
61
|
-
Additional keyword arguments to pass to the
|
62
|
-
|
64
|
+
Additional keyword arguments to pass to the
|
65
|
+
:class:`FeatureObserver` constructor.
|
63
66
|
|
64
67
|
Returns:
|
65
|
-
A
|
68
|
+
A :class:`FeatureObserver` instance.
|
66
69
|
"""
|
67
|
-
if isinstance(
|
70
|
+
if isinstance(feature_observer_type, DispatcherObserverConfig):
|
68
71
|
return feature_observer_factory(
|
69
|
-
|
70
|
-
**
|
72
|
+
feature_observer_type.class_type,
|
73
|
+
**feature_observer_type.kwargs,
|
71
74
|
**kwargs,
|
72
75
|
)
|
73
76
|
# if the instance is of type type[FeatureObserver] we can just
|
74
77
|
# call the object constructor with the keyword arguments
|
75
|
-
if isinstance(
|
76
|
-
return
|
78
|
+
if isinstance(feature_observer_type, type):
|
79
|
+
return feature_observer_type(**kwargs)
|
77
80
|
|
78
81
|
mapping: dict[FeatureObserverType, type[FeatureObserver]] = {
|
79
82
|
FeatureObserverType.IS_READY: IsReadyObserver,
|
@@ -84,5 +87,5 @@ def feature_observer_factory(
|
|
84
87
|
FeatureObserverType.REMAINING_OPERATIONS: RemainingOperationsObserver,
|
85
88
|
FeatureObserverType.IS_COMPLETED: IsCompletedObserver,
|
86
89
|
}
|
87
|
-
|
88
|
-
return
|
90
|
+
feature_observer = mapping[feature_observer_type] # type: ignore[index]
|
91
|
+
return feature_observer(**kwargs)
|
@@ -55,7 +55,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
55
55
|
- a list with names or actual ready operations filters to be used.
|
56
56
|
If a list is provided, a composite filter will be created
|
57
57
|
using the specified filters.
|
58
|
-
|
58
|
+
|
59
59
|
.. seealso::
|
60
60
|
- :func:`job_shop_lib.dispatching.rules.dispatching_rule_factory`
|
61
61
|
- :func:`job_shop_lib.dispatching.rules.machine_chooser_factory`
|