job-shop-lib 1.0.2__py3-none-any.whl → 1.0.4__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.
- job_shop_lib/__init__.py +1 -1
- job_shop_lib/_job_shop_instance.py +26 -26
- job_shop_lib/_operation.py +2 -4
- job_shop_lib/_schedule.py +29 -13
- job_shop_lib/benchmarking/_load_benchmark.py +3 -3
- job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
- job_shop_lib/dispatching/_dispatcher.py +19 -19
- job_shop_lib/dispatching/_dispatcher_observer_config.py +3 -3
- job_shop_lib/dispatching/_factories.py +2 -3
- job_shop_lib/dispatching/_history_observer.py +1 -2
- job_shop_lib/dispatching/_optimal_operations_observer.py +3 -4
- job_shop_lib/dispatching/_ready_operation_filters.py +18 -19
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +3 -2
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +10 -12
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +1 -3
- job_shop_lib/dispatching/feature_observers/_factory.py +11 -12
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +8 -9
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -4
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +2 -4
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +2 -4
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +15 -20
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -10
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +2 -3
- job_shop_lib/dispatching/rules/_utils.py +7 -8
- job_shop_lib/generation/_general_instance_generator.py +22 -20
- job_shop_lib/generation/_instance_generator.py +8 -8
- job_shop_lib/generation/_transformations.py +4 -5
- job_shop_lib/generation/_utils.py +16 -8
- job_shop_lib/graphs/_job_shop_graph.py +13 -14
- job_shop_lib/graphs/_node.py +6 -12
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +2 -4
- job_shop_lib/reinforcement_learning/__init__.py +2 -1
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +17 -17
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +1 -1
- job_shop_lib/reinforcement_learning/_reward_observers.py +1 -3
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +22 -24
- job_shop_lib/reinforcement_learning/_utils.py +2 -2
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +11 -11
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +21 -21
- job_shop_lib/visualization/gantt/_plot_gantt_chart.py +12 -14
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +15 -17
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +22 -24
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.4.dist-info}/METADATA +1 -1
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.4.dist-info}/RECORD +46 -46
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.4.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.4.dist-info}/WHEEL +0 -0
job_shop_lib/__init__.py
CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import os
|
6
6
|
import functools
|
7
|
-
from typing import Any
|
7
|
+
from typing import Any
|
8
8
|
|
9
9
|
import numpy as np
|
10
10
|
from numpy.typing import NDArray
|
@@ -51,14 +51,14 @@ class JobShopInstance:
|
|
51
51
|
total_duration
|
52
52
|
|
53
53
|
Attributes:
|
54
|
-
jobs (
|
54
|
+
jobs (list[list[Operation]]):
|
55
55
|
A list of lists of operations. Each list of operations represents
|
56
56
|
a job, and the operations are ordered by their position in the job.
|
57
57
|
The ``job_id``, ``position_in_job``, and ``operation_id``
|
58
58
|
attributes of the operations are set when the instance is created.
|
59
59
|
name (str):
|
60
60
|
A string with the name of the instance.
|
61
|
-
metadata (
|
61
|
+
metadata (dict[str, Any]):
|
62
62
|
A dictionary with additional information about the instance.
|
63
63
|
|
64
64
|
Args:
|
@@ -81,16 +81,16 @@ class JobShopInstance:
|
|
81
81
|
|
82
82
|
def __init__(
|
83
83
|
self,
|
84
|
-
jobs:
|
84
|
+
jobs: list[list[Operation]],
|
85
85
|
name: str = "JobShopInstance",
|
86
86
|
set_operation_attributes: bool = True,
|
87
87
|
**metadata: Any,
|
88
88
|
):
|
89
|
-
self.jobs:
|
89
|
+
self.jobs: list[list[Operation]] = jobs
|
90
90
|
if set_operation_attributes:
|
91
91
|
self.set_operation_attributes()
|
92
92
|
self.name: str = name
|
93
|
-
self.metadata:
|
93
|
+
self.metadata: dict[str, Any] = metadata
|
94
94
|
|
95
95
|
def set_operation_attributes(self):
|
96
96
|
"""Sets the ``job_id``, ``position_in_job``, and ``operation_id``
|
@@ -125,10 +125,10 @@ class JobShopInstance:
|
|
125
125
|
@classmethod
|
126
126
|
def from_taillard_file(
|
127
127
|
cls,
|
128
|
-
file_path:
|
128
|
+
file_path: os.PathLike | str | bytes,
|
129
129
|
encoding: str = "utf-8",
|
130
130
|
comment_symbol: str = "#",
|
131
|
-
name:
|
131
|
+
name: str | None = None,
|
132
132
|
**metadata: Any,
|
133
133
|
) -> JobShopInstance:
|
134
134
|
"""Creates a JobShopInstance from a file following Taillard's format.
|
@@ -178,7 +178,7 @@ class JobShopInstance:
|
|
178
178
|
name = name.split(".")[0]
|
179
179
|
return cls(jobs=jobs, name=name, **metadata)
|
180
180
|
|
181
|
-
def to_dict(self) ->
|
181
|
+
def to_dict(self) -> dict[str, Any]:
|
182
182
|
"""Returns a dictionary representation of the instance.
|
183
183
|
|
184
184
|
This representation is useful for saving the instance to a JSON file,
|
@@ -186,7 +186,7 @@ class JobShopInstance:
|
|
186
186
|
like Taillard's.
|
187
187
|
|
188
188
|
Returns:
|
189
|
-
|
189
|
+
dict[str, Any]: The returned dictionary has the following
|
190
190
|
structure:
|
191
191
|
|
192
192
|
.. code-block:: python
|
@@ -208,10 +208,10 @@ class JobShopInstance:
|
|
208
208
|
@classmethod
|
209
209
|
def from_matrices(
|
210
210
|
cls,
|
211
|
-
duration_matrix:
|
212
|
-
machines_matrix:
|
211
|
+
duration_matrix: list[list[int]],
|
212
|
+
machines_matrix: list[list[list[int]]] | list[list[int]],
|
213
213
|
name: str = "JobShopInstance",
|
214
|
-
metadata:
|
214
|
+
metadata: dict[str, Any] | None = None,
|
215
215
|
) -> JobShopInstance:
|
216
216
|
"""Creates a :class:`JobShopInstance` from duration and machines
|
217
217
|
matrices.
|
@@ -233,7 +233,7 @@ class JobShopInstance:
|
|
233
233
|
Returns:
|
234
234
|
A :class:`JobShopInstance` object.
|
235
235
|
"""
|
236
|
-
jobs:
|
236
|
+
jobs: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]
|
237
237
|
|
238
238
|
num_jobs = len(duration_matrix)
|
239
239
|
for job_id in range(num_jobs):
|
@@ -290,7 +290,7 @@ class JobShopInstance:
|
|
290
290
|
)
|
291
291
|
|
292
292
|
@functools.cached_property
|
293
|
-
def durations_matrix(self) ->
|
293
|
+
def durations_matrix(self) -> list[list[int]]:
|
294
294
|
"""Returns the duration matrix of the instance.
|
295
295
|
|
296
296
|
The duration of the operation with ``job_id`` i and ``position_in_job``
|
@@ -305,7 +305,7 @@ class JobShopInstance:
|
|
305
305
|
return [[operation.duration for operation in job] for job in self.jobs]
|
306
306
|
|
307
307
|
@functools.cached_property
|
308
|
-
def machines_matrix(self) ->
|
308
|
+
def machines_matrix(self) -> list[list[list[int]]] | list[list[int]]:
|
309
309
|
"""Returns the machines matrix of the instance.
|
310
310
|
|
311
311
|
If the instance is flexible (i.e., if any operation has more than one
|
@@ -371,25 +371,25 @@ class JobShopInstance:
|
|
371
371
|
machines_matrix = self.machines_matrix
|
372
372
|
if self.is_flexible:
|
373
373
|
# False positive from mypy, the type of machines_matrix is
|
374
|
-
#
|
374
|
+
# list[list[list[int]]] here
|
375
375
|
return self._fill_matrix_with_nans_3d(
|
376
376
|
machines_matrix # type: ignore[arg-type]
|
377
377
|
)
|
378
378
|
|
379
379
|
# False positive from mypy, the type of machines_matrix is
|
380
|
-
#
|
380
|
+
# list[list[int]] here
|
381
381
|
return self._fill_matrix_with_nans_2d(
|
382
382
|
machines_matrix # type: ignore[arg-type]
|
383
383
|
)
|
384
384
|
|
385
385
|
@functools.cached_property
|
386
|
-
def operations_by_machine(self) ->
|
386
|
+
def operations_by_machine(self) -> list[list[Operation]]:
|
387
387
|
"""Returns a list of lists of operations.
|
388
388
|
|
389
389
|
The i-th list contains the operations that can be processed in the
|
390
390
|
machine with id i.
|
391
391
|
"""
|
392
|
-
operations_by_machine:
|
392
|
+
operations_by_machine: list[list[Operation]] = [
|
393
393
|
[] for _ in range(self.num_machines)
|
394
394
|
]
|
395
395
|
for job in self.jobs:
|
@@ -409,7 +409,7 @@ class JobShopInstance:
|
|
409
409
|
)
|
410
410
|
|
411
411
|
@functools.cached_property
|
412
|
-
def max_duration_per_job(self) ->
|
412
|
+
def max_duration_per_job(self) -> list[float]:
|
413
413
|
"""Returns the maximum duration of each job in the instance.
|
414
414
|
|
415
415
|
The maximum duration of the job with id i is stored in the i-th
|
@@ -420,7 +420,7 @@ class JobShopInstance:
|
|
420
420
|
return [max(op.duration for op in job) for job in self.jobs]
|
421
421
|
|
422
422
|
@functools.cached_property
|
423
|
-
def max_duration_per_machine(self) ->
|
423
|
+
def max_duration_per_machine(self) -> list[int]:
|
424
424
|
"""Returns the maximum duration of each machine in the instance.
|
425
425
|
|
426
426
|
The maximum duration of the machine with id i is stored in the i-th
|
@@ -439,7 +439,7 @@ class JobShopInstance:
|
|
439
439
|
return max_duration_per_machine
|
440
440
|
|
441
441
|
@functools.cached_property
|
442
|
-
def job_durations(self) ->
|
442
|
+
def job_durations(self) -> list[int]:
|
443
443
|
"""Returns a list with the duration of each job in the instance.
|
444
444
|
|
445
445
|
The duration of a job is the sum of the durations of its operations.
|
@@ -450,7 +450,7 @@ class JobShopInstance:
|
|
450
450
|
return [sum(op.duration for op in job) for job in self.jobs]
|
451
451
|
|
452
452
|
@functools.cached_property
|
453
|
-
def machine_loads(self) ->
|
453
|
+
def machine_loads(self) -> list[int]:
|
454
454
|
"""Returns the total machine load of each machine in the instance.
|
455
455
|
|
456
456
|
The total machine load of a machine is the sum of the durations of the
|
@@ -474,7 +474,7 @@ class JobShopInstance:
|
|
474
474
|
|
475
475
|
@staticmethod
|
476
476
|
def _fill_matrix_with_nans_2d(
|
477
|
-
matrix:
|
477
|
+
matrix: list[list[int]],
|
478
478
|
) -> NDArray[np.float32]:
|
479
479
|
"""Fills a matrix with ``np.nan`` values.
|
480
480
|
|
@@ -496,7 +496,7 @@ class JobShopInstance:
|
|
496
496
|
|
497
497
|
@staticmethod
|
498
498
|
def _fill_matrix_with_nans_3d(
|
499
|
-
matrix:
|
499
|
+
matrix: list[list[list[int]]],
|
500
500
|
) -> NDArray[np.float32]:
|
501
501
|
"""Fills a 3D matrix with ``np.nan`` values.
|
502
502
|
|
job_shop_lib/_operation.py
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Union, List
|
6
|
-
|
7
5
|
from job_shop_lib.exceptions import ValidationError
|
8
6
|
|
9
7
|
|
@@ -61,8 +59,8 @@ class Operation:
|
|
61
59
|
),
|
62
60
|
}
|
63
61
|
|
64
|
-
def __init__(self, machines:
|
65
|
-
self.machines:
|
62
|
+
def __init__(self, machines: int | list[int], duration: int):
|
63
|
+
self.machines: list[int] = (
|
66
64
|
[machines] if isinstance(machines, int) else machines
|
67
65
|
)
|
68
66
|
self.duration: int = duration
|
job_shop_lib/_schedule.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any
|
6
6
|
from collections import deque
|
7
7
|
|
8
8
|
from job_shop_lib import ScheduledOperation, JobShopInstance
|
@@ -55,7 +55,7 @@ class Schedule:
|
|
55
55
|
def __init__(
|
56
56
|
self,
|
57
57
|
instance: JobShopInstance,
|
58
|
-
schedule:
|
58
|
+
schedule: list[list[ScheduledOperation]] | None = None,
|
59
59
|
**metadata: Any,
|
60
60
|
):
|
61
61
|
if schedule is None:
|
@@ -65,19 +65,19 @@ class Schedule:
|
|
65
65
|
|
66
66
|
self.instance: JobShopInstance = instance
|
67
67
|
self._schedule = schedule
|
68
|
-
self.metadata:
|
68
|
+
self.metadata: dict[str, Any] = metadata
|
69
69
|
|
70
70
|
def __repr__(self) -> str:
|
71
71
|
return str(self.schedule)
|
72
72
|
|
73
73
|
@property
|
74
|
-
def schedule(self) ->
|
74
|
+
def schedule(self) -> list[list[ScheduledOperation]]:
|
75
75
|
"""A list of lists of :class:`ScheduledOperation` objects. Each list
|
76
76
|
represents the order of operations on a machine."""
|
77
77
|
return self._schedule
|
78
78
|
|
79
79
|
@schedule.setter
|
80
|
-
def schedule(self, new_schedule:
|
80
|
+
def schedule(self, new_schedule: list[list[ScheduledOperation]]):
|
81
81
|
Schedule.check_schedule(new_schedule)
|
82
82
|
self._schedule = new_schedule
|
83
83
|
|
@@ -103,7 +103,7 @@ class Schedule:
|
|
103
103
|
- **"metadata"**: A dictionary with additional information
|
104
104
|
about the schedule.
|
105
105
|
"""
|
106
|
-
job_sequences:
|
106
|
+
job_sequences: list[list[int]] = []
|
107
107
|
for machine_schedule in self.schedule:
|
108
108
|
job_sequences.append(
|
109
109
|
[operation.job_id for operation in machine_schedule]
|
@@ -117,11 +117,27 @@ class Schedule:
|
|
117
117
|
|
118
118
|
@staticmethod
|
119
119
|
def from_dict(
|
120
|
-
instance:
|
121
|
-
job_sequences:
|
122
|
-
metadata:
|
120
|
+
instance: dict[str, Any] | JobShopInstance,
|
121
|
+
job_sequences: list[list[int]],
|
122
|
+
metadata: dict[str, Any] | None = None,
|
123
123
|
) -> Schedule:
|
124
|
-
"""Creates a schedule from a dictionary representation.
|
124
|
+
"""Creates a schedule from a dictionary representation.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
instance:
|
128
|
+
The instance to create the schedule for. Can be a dictionary
|
129
|
+
representation of a :class:`JobShopInstance` or a
|
130
|
+
:class:`JobShopInstance` object.
|
131
|
+
job_sequences:
|
132
|
+
A list of lists of job ids. Each list of job ids represents the
|
133
|
+
order of operations on the machine. The machine that the list
|
134
|
+
corresponds to is determined by the index of the list.
|
135
|
+
metadata:
|
136
|
+
A dictionary with additional information about the schedule.
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
A :class:`Schedule` object with the given job sequences.
|
140
|
+
"""
|
125
141
|
if isinstance(instance, dict):
|
126
142
|
instance = JobShopInstance.from_matrices(**instance)
|
127
143
|
schedule = Schedule.from_job_sequences(instance, job_sequences)
|
@@ -131,7 +147,7 @@ class Schedule:
|
|
131
147
|
@staticmethod
|
132
148
|
def from_job_sequences(
|
133
149
|
instance: JobShopInstance,
|
134
|
-
job_sequences:
|
150
|
+
job_sequences: list[list[int]],
|
135
151
|
) -> Schedule:
|
136
152
|
"""Creates an active schedule from a list of job sequences.
|
137
153
|
|
@@ -157,7 +173,7 @@ class Schedule:
|
|
157
173
|
dispatcher.reset()
|
158
174
|
raw_solution_deques = [deque(job_ids) for job_ids in job_sequences]
|
159
175
|
|
160
|
-
while
|
176
|
+
while any(job_seq for job_seq in raw_solution_deques):
|
161
177
|
at_least_one_operation_scheduled = False
|
162
178
|
for machine_id, job_ids in enumerate(raw_solution_deques):
|
163
179
|
if not job_ids:
|
@@ -240,7 +256,7 @@ class Schedule:
|
|
240
256
|
return previous_operation.end_time <= scheduled_operation.start_time
|
241
257
|
|
242
258
|
@staticmethod
|
243
|
-
def check_schedule(schedule:
|
259
|
+
def check_schedule(schedule: list[list[ScheduledOperation]]):
|
244
260
|
"""Checks if a schedule is valid and raises a
|
245
261
|
:class:`~exceptions.ValidationError` if it is not.
|
246
262
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Contains functions to load benchmark instances from a JSON file."""
|
2
2
|
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
import functools
|
6
6
|
import json
|
@@ -10,7 +10,7 @@ from job_shop_lib import JobShopInstance
|
|
10
10
|
|
11
11
|
|
12
12
|
@functools.cache
|
13
|
-
def load_all_benchmark_instances() ->
|
13
|
+
def load_all_benchmark_instances() -> dict[str, JobShopInstance]:
|
14
14
|
"""Loads all benchmark instances available.
|
15
15
|
|
16
16
|
Returns:
|
@@ -48,7 +48,7 @@ def load_benchmark_instance(name: str) -> JobShopInstance:
|
|
48
48
|
|
49
49
|
|
50
50
|
@functools.cache
|
51
|
-
def load_benchmark_json() ->
|
51
|
+
def load_benchmark_json() -> dict[str, dict[str, Any]]:
|
52
52
|
"""Loads the raw JSON file containing the benchmark instances.
|
53
53
|
|
54
54
|
Results are cached to avoid reading the file multiple times.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from typing import Any
|
5
|
+
from typing import Any
|
6
6
|
import time
|
7
7
|
|
8
8
|
from ortools.sat.python import cp_model
|
@@ -62,7 +62,7 @@ class ORToolsSolver(BaseSolver):
|
|
62
62
|
self._makespan: cp_model.IntVar | None = None
|
63
63
|
self.model = cp_model.CpModel()
|
64
64
|
self.solver = cp_model.CpSolver()
|
65
|
-
self._operations_start:
|
65
|
+
self._operations_start: dict[Operation, tuple[IntVar, IntVar]] = {}
|
66
66
|
|
67
67
|
def __call__(self, instance: JobShopInstance) -> Schedule:
|
68
68
|
"""Equivalent to calling the :meth:`~ORToolsSolver.solve` method.
|
@@ -152,15 +152,15 @@ class ORToolsSolver(BaseSolver):
|
|
152
152
|
self._set_objective(instance)
|
153
153
|
|
154
154
|
def _create_schedule(
|
155
|
-
self, instance: JobShopInstance, metadata:
|
155
|
+
self, instance: JobShopInstance, metadata: dict[str, Any]
|
156
156
|
) -> Schedule:
|
157
157
|
"""Creates a Schedule object from the solution."""
|
158
|
-
operations_start:
|
158
|
+
operations_start: dict[Operation, int] = {
|
159
159
|
operation: self.solver.Value(start_var)
|
160
160
|
for operation, (start_var, _) in self._operations_start.items()
|
161
161
|
}
|
162
162
|
|
163
|
-
unsorted_schedule:
|
163
|
+
unsorted_schedule: list[list[ScheduledOperation]] = [
|
164
164
|
[] for _ in range(instance.num_machines)
|
165
165
|
]
|
166
166
|
for operation, start_time in operations_start.items():
|
@@ -235,7 +235,7 @@ class ORToolsSolver(BaseSolver):
|
|
235
235
|
each machine."""
|
236
236
|
|
237
237
|
# Create interval variables for each operation on each machine
|
238
|
-
machines_operations:
|
238
|
+
machines_operations: list[list[tuple[tuple[IntVar, IntVar], int]]] = [
|
239
239
|
[] for _ in range(instance.num_machines)
|
240
240
|
]
|
241
241
|
for job in instance.jobs:
|
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import abc
|
6
|
-
from typing import Any, TypeVar
|
6
|
+
from typing import Any, TypeVar
|
7
7
|
from collections.abc import Callable
|
8
8
|
from functools import wraps
|
9
9
|
|
@@ -52,7 +52,7 @@ class DispatcherObserver(abc.ABC):
|
|
52
52
|
class HistoryObserver(DispatcherObserver):
|
53
53
|
def __init__(self, dispatcher: Dispatcher):
|
54
54
|
super().__init__(dispatcher)
|
55
|
-
self.history:
|
55
|
+
self.history: list[ScheduledOperation] = []
|
56
56
|
|
57
57
|
def update(self, scheduled_operation: ScheduledOperation):
|
58
58
|
self.history.append(scheduled_operation)
|
@@ -212,14 +212,14 @@ class Dispatcher:
|
|
212
212
|
self,
|
213
213
|
instance: JobShopInstance,
|
214
214
|
ready_operations_filter: (
|
215
|
-
|
215
|
+
Callable[[Dispatcher, list[Operation]], list[Operation]] | None
|
216
216
|
) = None,
|
217
217
|
) -> None:
|
218
218
|
|
219
219
|
self.instance = instance
|
220
220
|
self.schedule = Schedule(self.instance)
|
221
221
|
self.ready_operations_filter = ready_operations_filter
|
222
|
-
self.subscribers:
|
222
|
+
self.subscribers: list[DispatcherObserver] = []
|
223
223
|
|
224
224
|
self._machine_next_available_time = [0] * self.instance.num_machines
|
225
225
|
self._job_next_operation_index = [0] * self.instance.num_jobs
|
@@ -233,18 +233,18 @@ class Dispatcher:
|
|
233
233
|
return str(self)
|
234
234
|
|
235
235
|
@property
|
236
|
-
def machine_next_available_time(self) ->
|
236
|
+
def machine_next_available_time(self) -> list[int]:
|
237
237
|
"""Returns the next available time for each machine."""
|
238
238
|
return self._machine_next_available_time
|
239
239
|
|
240
240
|
@property
|
241
|
-
def job_next_operation_index(self) ->
|
241
|
+
def job_next_operation_index(self) -> list[int]:
|
242
242
|
"""Returns the index of the next operation to be scheduled for each
|
243
243
|
job."""
|
244
244
|
return self._job_next_operation_index
|
245
245
|
|
246
246
|
@property
|
247
|
-
def job_next_available_time(self) ->
|
247
|
+
def job_next_available_time(self) -> list[int]:
|
248
248
|
"""Returns the next available time for each job."""
|
249
249
|
return self._job_next_available_time
|
250
250
|
|
@@ -267,7 +267,7 @@ class Dispatcher:
|
|
267
267
|
subscriber.reset()
|
268
268
|
|
269
269
|
def dispatch(
|
270
|
-
self, operation: Operation, machine_id:
|
270
|
+
self, operation: Operation, machine_id: int | None = None
|
271
271
|
) -> None:
|
272
272
|
"""Schedules the given operation on the given machine.
|
273
273
|
|
@@ -362,7 +362,7 @@ class Dispatcher:
|
|
362
362
|
|
363
363
|
def create_or_get_observer(
|
364
364
|
self,
|
365
|
-
observer:
|
365
|
+
observer: type[ObserverType],
|
366
366
|
condition: Callable[[DispatcherObserver], bool] = lambda _: True,
|
367
367
|
**kwargs,
|
368
368
|
) -> ObserverType:
|
@@ -401,7 +401,7 @@ class Dispatcher:
|
|
401
401
|
current_time = self.min_start_time(available_operations)
|
402
402
|
return current_time
|
403
403
|
|
404
|
-
def min_start_time(self, operations:
|
404
|
+
def min_start_time(self, operations: list[Operation]) -> int:
|
405
405
|
"""Returns the minimum start time of the available operations."""
|
406
406
|
if not operations:
|
407
407
|
return self.schedule.makespan()
|
@@ -413,7 +413,7 @@ class Dispatcher:
|
|
413
413
|
return int(min_start_time)
|
414
414
|
|
415
415
|
@_dispatcher_cache
|
416
|
-
def available_operations(self) ->
|
416
|
+
def available_operations(self) -> list[Operation]:
|
417
417
|
"""Returns a list of available operations for processing, optionally
|
418
418
|
filtering out operations using the filter function.
|
419
419
|
|
@@ -433,7 +433,7 @@ class Dispatcher:
|
|
433
433
|
return available_operations
|
434
434
|
|
435
435
|
@_dispatcher_cache
|
436
|
-
def raw_ready_operations(self) ->
|
436
|
+
def raw_ready_operations(self) -> list[Operation]:
|
437
437
|
"""Returns a list of available operations for processing without
|
438
438
|
applying the filter function.
|
439
439
|
|
@@ -449,7 +449,7 @@ class Dispatcher:
|
|
449
449
|
return available_operations
|
450
450
|
|
451
451
|
@_dispatcher_cache
|
452
|
-
def unscheduled_operations(self) ->
|
452
|
+
def unscheduled_operations(self) -> list[Operation]:
|
453
453
|
"""Returns the list of operations that have not been scheduled."""
|
454
454
|
unscheduled_operations = []
|
455
455
|
for job_id, next_position in enumerate(self._job_next_operation_index):
|
@@ -458,7 +458,7 @@ class Dispatcher:
|
|
458
458
|
return unscheduled_operations
|
459
459
|
|
460
460
|
@_dispatcher_cache
|
461
|
-
def scheduled_operations(self) ->
|
461
|
+
def scheduled_operations(self) -> list[ScheduledOperation]:
|
462
462
|
"""Returns the list of operations that have been scheduled."""
|
463
463
|
scheduled_operations = []
|
464
464
|
for machine_schedule in self.schedule.schedule:
|
@@ -466,7 +466,7 @@ class Dispatcher:
|
|
466
466
|
return scheduled_operations
|
467
467
|
|
468
468
|
@_dispatcher_cache
|
469
|
-
def available_machines(self) ->
|
469
|
+
def available_machines(self) -> list[int]:
|
470
470
|
"""Returns the list of ready machines."""
|
471
471
|
available_operations = self.available_operations()
|
472
472
|
available_machines = set()
|
@@ -475,7 +475,7 @@ class Dispatcher:
|
|
475
475
|
return list(available_machines)
|
476
476
|
|
477
477
|
@_dispatcher_cache
|
478
|
-
def available_jobs(self) ->
|
478
|
+
def available_jobs(self) -> list[int]:
|
479
479
|
"""Returns the list of ready jobs."""
|
480
480
|
available_operations = self.available_operations()
|
481
481
|
available_jobs = set(
|
@@ -530,7 +530,7 @@ class Dispatcher:
|
|
530
530
|
return scheduled_operation.end_time - adjusted_start_time
|
531
531
|
|
532
532
|
@_dispatcher_cache
|
533
|
-
def completed_operations(self) ->
|
533
|
+
def completed_operations(self) -> set[ScheduledOperation]:
|
534
534
|
"""Returns the set of operations that have been completed.
|
535
535
|
|
536
536
|
This method returns the operations that have been scheduled and the
|
@@ -542,7 +542,7 @@ class Dispatcher:
|
|
542
542
|
return completed_operations
|
543
543
|
|
544
544
|
@_dispatcher_cache
|
545
|
-
def uncompleted_operations(self) ->
|
545
|
+
def uncompleted_operations(self) -> list[Operation]:
|
546
546
|
"""Returns the list of operations that have not been completed yet.
|
547
547
|
|
548
548
|
This method checks for operations that either haven't been scheduled
|
@@ -561,7 +561,7 @@ class Dispatcher:
|
|
561
561
|
return uncompleted_operations
|
562
562
|
|
563
563
|
@_dispatcher_cache
|
564
|
-
def ongoing_operations(self) ->
|
564
|
+
def ongoing_operations(self) -> list[ScheduledOperation]:
|
565
565
|
"""Returns the list of operations that are currently being processed.
|
566
566
|
|
567
567
|
This method returns the operations that have been scheduled and are
|
@@ -5,7 +5,7 @@ The factory functions create and return the appropriate functions based on the
|
|
5
5
|
specified names or enums.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import TypeVar, Generic, Any
|
8
|
+
from typing import TypeVar, Generic, Any
|
9
9
|
|
10
10
|
from dataclasses import dataclass, field
|
11
11
|
|
@@ -46,7 +46,7 @@ class DispatcherObserverConfig(Generic[T]):
|
|
46
46
|
# We use the type hint T, instead of ObserverType, to allow for string or
|
47
47
|
# specific Enum values to be passed as the type argument. For example:
|
48
48
|
# FeatureObserverConfig = DispatcherObserverConfig[
|
49
|
-
#
|
49
|
+
# type[FeatureObserver] | FeatureObserverType | str
|
50
50
|
# ]
|
51
51
|
# This allows for the creation of a FeatureObserver instance
|
52
52
|
# from the factory function.
|
@@ -55,7 +55,7 @@ class DispatcherObserverConfig(Generic[T]):
|
|
55
55
|
enum value, or a string. This is useful for the creation of
|
56
56
|
:class:`DispatcherObserver` instances from the factory functions."""
|
57
57
|
|
58
|
-
kwargs:
|
58
|
+
kwargs: dict[str, Any] = field(default_factory=dict)
|
59
59
|
"""Keyword arguments needed to initialize the class. It must not
|
60
60
|
contain the ``dispatcher`` argument."""
|
61
61
|
|
@@ -4,7 +4,6 @@ The factory functions create and return the appropriate functions based on the
|
|
4
4
|
specified names or enums.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from typing import Union
|
8
7
|
from enum import Enum
|
9
8
|
from collections.abc import Iterable
|
10
9
|
|
@@ -37,7 +36,7 @@ class ReadyOperationsFilterType(str, Enum):
|
|
37
36
|
|
38
37
|
def create_composite_operation_filter(
|
39
38
|
ready_operations_filters: Iterable[
|
40
|
-
|
39
|
+
ReadyOperationsFilter | str | ReadyOperationsFilterType
|
41
40
|
],
|
42
41
|
) -> ReadyOperationsFilter:
|
43
42
|
"""Creates and returns a :class:`ReadyOperationsFilter` function by
|
@@ -85,7 +84,7 @@ def create_composite_operation_filter(
|
|
85
84
|
|
86
85
|
|
87
86
|
def ready_operations_filter_factory(
|
88
|
-
filter_name:
|
87
|
+
filter_name: str | ReadyOperationsFilterType | ReadyOperationsFilter,
|
89
88
|
) -> ReadyOperationsFilter:
|
90
89
|
"""Creates and returns a filter function based on the specified
|
91
90
|
filter strategy name.
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Home of the `HistoryObserver` class."""
|
2
2
|
|
3
|
-
from typing import List
|
4
3
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
5
4
|
from job_shop_lib import ScheduledOperation
|
6
5
|
|
@@ -10,7 +9,7 @@ class HistoryObserver(DispatcherObserver):
|
|
10
9
|
|
11
10
|
def __init__(self, dispatcher: Dispatcher, *, subscribe: bool = True):
|
12
11
|
super().__init__(dispatcher, subscribe=subscribe)
|
13
|
-
self.history:
|
12
|
+
self.history: list[ScheduledOperation] = []
|
14
13
|
|
15
14
|
def update(self, scheduled_operation: ScheduledOperation):
|
16
15
|
self.history.append(scheduled_operation)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
"""Home of the `OptimalOperationsObserver` class."""
|
2
2
|
|
3
|
-
from typing import List, Set, Dict
|
4
3
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
5
4
|
from job_shop_lib import Schedule, Operation, ScheduledOperation
|
6
5
|
from job_shop_lib.exceptions import ValidationError
|
@@ -53,9 +52,9 @@ class OptimalOperationsObserver(DispatcherObserver):
|
|
53
52
|
)
|
54
53
|
|
55
54
|
self.reference_schedule = reference_schedule
|
56
|
-
self.optimal_available:
|
57
|
-
self._operation_to_scheduled:
|
58
|
-
self._machine_next_operation_index:
|
55
|
+
self.optimal_available: set[Operation] = set()
|
56
|
+
self._operation_to_scheduled: dict[Operation, ScheduledOperation] = {}
|
57
|
+
self._machine_next_operation_index: list[int] = [0] * len(
|
59
58
|
reference_schedule.schedule
|
60
59
|
)
|
61
60
|
|