job-shop-lib 1.0.0a5__py3-none-any.whl → 1.0.0b1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/__init__.py +1 -1
- job_shop_lib/_job_shop_instance.py +34 -29
- job_shop_lib/_operation.py +4 -2
- job_shop_lib/_schedule.py +11 -11
- 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 +4 -4
- job_shop_lib/dispatching/_factories.py +4 -2
- job_shop_lib/dispatching/_history_observer.py +2 -1
- job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
- job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +23 -15
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
- job_shop_lib/dispatching/rules/_utils.py +9 -8
- job_shop_lib/generation/__init__.py +8 -0
- job_shop_lib/generation/_general_instance_generator.py +42 -64
- job_shop_lib/generation/_instance_generator.py +11 -7
- job_shop_lib/generation/_transformations.py +5 -4
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +7 -7
- job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
- job_shop_lib/graphs/_job_shop_graph.py +17 -13
- job_shop_lib/graphs/_node.py +6 -4
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
- job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
- job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
- job_shop_lib/reinforcement_learning/_utils.py +3 -3
- job_shop_lib/visualization/__init__.py +0 -60
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
- job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
- job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +17 -15
- job_shop_lib-1.0.0b1.dist-info/RECORD +69 -0
- job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
- job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.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, List, Union, Dict
|
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: os.PathLike
|
128
|
+
file_path: Union[os.PathLike, str, bytes],
|
129
129
|
encoding: str = "utf-8",
|
130
130
|
comment_symbol: str = "#",
|
131
|
-
name: str
|
131
|
+
name: Union[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) -> Union[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
|
@@ -342,7 +342,7 @@ class JobShopInstance:
|
|
342
342
|
>>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
|
343
343
|
>>> instance = JobShopInstance(jobs)
|
344
344
|
>>> instance.durations_matrix_array
|
345
|
-
array([[ 2.,
|
345
|
+
array([[ 2., 3.],
|
346
346
|
[ 4., nan]], dtype=float32)
|
347
347
|
"""
|
348
348
|
duration_matrix = self.durations_matrix
|
@@ -358,8 +358,7 @@ class JobShopInstance:
|
|
358
358
|
|
359
359
|
Example:
|
360
360
|
>>> jobs = [
|
361
|
-
... [Operation(
|
362
|
-
... [Operation(machines=0, 6)],
|
361
|
+
... [Operation([0, 1], 2), Operation(1, 3)], [Operation(0, 6)]
|
363
362
|
... ]
|
364
363
|
>>> instance = JobShopInstance(jobs)
|
365
364
|
>>> instance.machines_matrix_array
|
@@ -372,25 +371,25 @@ class JobShopInstance:
|
|
372
371
|
machines_matrix = self.machines_matrix
|
373
372
|
if self.is_flexible:
|
374
373
|
# False positive from mypy, the type of machines_matrix is
|
375
|
-
#
|
374
|
+
# List[List[List[int]]] here
|
376
375
|
return self._fill_matrix_with_nans_3d(
|
377
376
|
machines_matrix # type: ignore[arg-type]
|
378
377
|
)
|
379
378
|
|
380
379
|
# False positive from mypy, the type of machines_matrix is
|
381
|
-
#
|
380
|
+
# List[List[int]] here
|
382
381
|
return self._fill_matrix_with_nans_2d(
|
383
382
|
machines_matrix # type: ignore[arg-type]
|
384
383
|
)
|
385
384
|
|
386
385
|
@functools.cached_property
|
387
|
-
def operations_by_machine(self) ->
|
386
|
+
def operations_by_machine(self) -> List[List[Operation]]:
|
388
387
|
"""Returns a list of lists of operations.
|
389
388
|
|
390
389
|
The i-th list contains the operations that can be processed in the
|
391
390
|
machine with id i.
|
392
391
|
"""
|
393
|
-
operations_by_machine:
|
392
|
+
operations_by_machine: List[List[Operation]] = [
|
394
393
|
[] for _ in range(self.num_machines)
|
395
394
|
]
|
396
395
|
for job in self.jobs:
|
@@ -410,7 +409,7 @@ class JobShopInstance:
|
|
410
409
|
)
|
411
410
|
|
412
411
|
@functools.cached_property
|
413
|
-
def max_duration_per_job(self) ->
|
412
|
+
def max_duration_per_job(self) -> List[float]:
|
414
413
|
"""Returns the maximum duration of each job in the instance.
|
415
414
|
|
416
415
|
The maximum duration of the job with id i is stored in the i-th
|
@@ -421,7 +420,7 @@ class JobShopInstance:
|
|
421
420
|
return [max(op.duration for op in job) for job in self.jobs]
|
422
421
|
|
423
422
|
@functools.cached_property
|
424
|
-
def max_duration_per_machine(self) ->
|
423
|
+
def max_duration_per_machine(self) -> List[int]:
|
425
424
|
"""Returns the maximum duration of each machine in the instance.
|
426
425
|
|
427
426
|
The maximum duration of the machine with id i is stored in the i-th
|
@@ -440,7 +439,7 @@ class JobShopInstance:
|
|
440
439
|
return max_duration_per_machine
|
441
440
|
|
442
441
|
@functools.cached_property
|
443
|
-
def job_durations(self) ->
|
442
|
+
def job_durations(self) -> List[int]:
|
444
443
|
"""Returns a list with the duration of each job in the instance.
|
445
444
|
|
446
445
|
The duration of a job is the sum of the durations of its operations.
|
@@ -451,7 +450,7 @@ class JobShopInstance:
|
|
451
450
|
return [sum(op.duration for op in job) for job in self.jobs]
|
452
451
|
|
453
452
|
@functools.cached_property
|
454
|
-
def machine_loads(self) ->
|
453
|
+
def machine_loads(self) -> List[int]:
|
455
454
|
"""Returns the total machine load of each machine in the instance.
|
456
455
|
|
457
456
|
The total machine load of a machine is the sum of the durations of the
|
@@ -475,7 +474,7 @@ class JobShopInstance:
|
|
475
474
|
|
476
475
|
@staticmethod
|
477
476
|
def _fill_matrix_with_nans_2d(
|
478
|
-
matrix:
|
477
|
+
matrix: List[List[int]],
|
479
478
|
) -> NDArray[np.float32]:
|
480
479
|
"""Fills a matrix with ``np.nan`` values.
|
481
480
|
|
@@ -497,7 +496,7 @@ class JobShopInstance:
|
|
497
496
|
|
498
497
|
@staticmethod
|
499
498
|
def _fill_matrix_with_nans_3d(
|
500
|
-
matrix:
|
499
|
+
matrix: List[List[List[int]]],
|
501
500
|
) -> NDArray[np.float32]:
|
502
501
|
"""Fills a 3D matrix with ``np.nan`` values.
|
503
502
|
|
@@ -523,3 +522,9 @@ class JobShopInstance:
|
|
523
522
|
for j, inner_row in enumerate(row):
|
524
523
|
squared_matrix[i, j, : len(inner_row)] = inner_row
|
525
524
|
return squared_matrix
|
525
|
+
|
526
|
+
|
527
|
+
if __name__ == "__main__":
|
528
|
+
import doctest
|
529
|
+
|
530
|
+
doctest.testmod()
|
job_shop_lib/_operation.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
from typing import Union, List
|
6
|
+
|
5
7
|
from job_shop_lib.exceptions import UninitializedAttributeError
|
6
8
|
|
7
9
|
|
@@ -59,8 +61,8 @@ class Operation:
|
|
59
61
|
),
|
60
62
|
}
|
61
63
|
|
62
|
-
def __init__(self, machines: int
|
63
|
-
self.machines:
|
64
|
+
def __init__(self, machines: Union[int, List[int]], duration: int):
|
65
|
+
self.machines: List[int] = (
|
64
66
|
[machines] if isinstance(machines, int) else machines
|
65
67
|
)
|
66
68
|
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, List, Union, Dict, Optional
|
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: Optional[List[List[ScheduledOperation]]] = 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,9 +117,9 @@ class Schedule:
|
|
117
117
|
|
118
118
|
@staticmethod
|
119
119
|
def from_dict(
|
120
|
-
instance:
|
121
|
-
job_sequences:
|
122
|
-
metadata:
|
120
|
+
instance: Union[Dict[str, Any], JobShopInstance],
|
121
|
+
job_sequences: List[List[int]],
|
122
|
+
metadata: Optional[Dict[str, Any]] = None,
|
123
123
|
) -> Schedule:
|
124
124
|
"""Creates a schedule from a dictionary representation."""
|
125
125
|
if isinstance(instance, dict):
|
@@ -131,7 +131,7 @@ class Schedule:
|
|
131
131
|
@staticmethod
|
132
132
|
def from_job_sequences(
|
133
133
|
instance: JobShopInstance,
|
134
|
-
job_sequences:
|
134
|
+
job_sequences: List[List[int]],
|
135
135
|
) -> Schedule:
|
136
136
|
"""Creates an active schedule from a list of job sequences.
|
137
137
|
|
@@ -240,7 +240,7 @@ class Schedule:
|
|
240
240
|
return previous_operation.end_time <= scheduled_operation.start_time
|
241
241
|
|
242
242
|
@staticmethod
|
243
|
-
def check_schedule(schedule:
|
243
|
+
def check_schedule(schedule: List[List[ScheduledOperation]]):
|
244
244
|
"""Checks if a schedule is valid and raises a
|
245
245
|
:class:`~exceptions.ValidationError` if it is not.
|
246
246
|
|
@@ -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, Dict
|
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, Dict, List, Tuple
|
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, List, Optional, Type, Set
|
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
|
-
Callable[[Dispatcher,
|
215
|
+
Optional[Callable[[Dispatcher, List[Operation]], List[Operation]]]
|
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: int
|
270
|
+
self, operation: Operation, machine_id: Optional[int] = None
|
271
271
|
) -> None:
|
272
272
|
"""Schedules the given operation on the given machine.
|
273
273
|
|
@@ -363,7 +363,7 @@ class Dispatcher:
|
|
363
363
|
|
364
364
|
def create_or_get_observer(
|
365
365
|
self,
|
366
|
-
observer:
|
366
|
+
observer: Type[ObserverType],
|
367
367
|
condition: Callable[[DispatcherObserver], bool] = lambda _: True,
|
368
368
|
**kwargs,
|
369
369
|
) -> ObserverType:
|
@@ -402,7 +402,7 @@ class Dispatcher:
|
|
402
402
|
current_time = self.min_start_time(available_operations)
|
403
403
|
return current_time
|
404
404
|
|
405
|
-
def min_start_time(self, operations:
|
405
|
+
def min_start_time(self, operations: List[Operation]) -> int:
|
406
406
|
"""Returns the minimum start time of the available operations."""
|
407
407
|
if not operations:
|
408
408
|
return self.schedule.makespan()
|
@@ -414,7 +414,7 @@ class Dispatcher:
|
|
414
414
|
return int(min_start_time)
|
415
415
|
|
416
416
|
@_dispatcher_cache
|
417
|
-
def available_operations(self) ->
|
417
|
+
def available_operations(self) -> List[Operation]:
|
418
418
|
"""Returns a list of available operations for processing, optionally
|
419
419
|
filtering out operations using the filter function.
|
420
420
|
|
@@ -434,7 +434,7 @@ class Dispatcher:
|
|
434
434
|
return available_operations
|
435
435
|
|
436
436
|
@_dispatcher_cache
|
437
|
-
def raw_ready_operations(self) ->
|
437
|
+
def raw_ready_operations(self) -> List[Operation]:
|
438
438
|
"""Returns a list of available operations for processing without
|
439
439
|
applying the filter function.
|
440
440
|
|
@@ -450,7 +450,7 @@ class Dispatcher:
|
|
450
450
|
return available_operations
|
451
451
|
|
452
452
|
@_dispatcher_cache
|
453
|
-
def unscheduled_operations(self) ->
|
453
|
+
def unscheduled_operations(self) -> List[Operation]:
|
454
454
|
"""Returns the list of operations that have not been scheduled."""
|
455
455
|
unscheduled_operations = []
|
456
456
|
for job_id, next_position in enumerate(self._job_next_operation_index):
|
@@ -459,7 +459,7 @@ class Dispatcher:
|
|
459
459
|
return unscheduled_operations
|
460
460
|
|
461
461
|
@_dispatcher_cache
|
462
|
-
def scheduled_operations(self) ->
|
462
|
+
def scheduled_operations(self) -> List[Operation]:
|
463
463
|
"""Returns the list of operations that have been scheduled."""
|
464
464
|
scheduled_operations = []
|
465
465
|
for job_id, next_position in enumerate(self._job_next_operation_index):
|
@@ -468,7 +468,7 @@ class Dispatcher:
|
|
468
468
|
return scheduled_operations
|
469
469
|
|
470
470
|
@_dispatcher_cache
|
471
|
-
def available_machines(self) ->
|
471
|
+
def available_machines(self) -> List[int]:
|
472
472
|
"""Returns the list of ready machines."""
|
473
473
|
available_operations = self.available_operations()
|
474
474
|
available_machines = set()
|
@@ -477,7 +477,7 @@ class Dispatcher:
|
|
477
477
|
return list(available_machines)
|
478
478
|
|
479
479
|
@_dispatcher_cache
|
480
|
-
def available_jobs(self) ->
|
480
|
+
def available_jobs(self) -> List[int]:
|
481
481
|
"""Returns the list of ready jobs."""
|
482
482
|
available_operations = self.available_operations()
|
483
483
|
available_jobs = set(
|
@@ -532,7 +532,7 @@ class Dispatcher:
|
|
532
532
|
return scheduled_operation.end_time - adjusted_start_time
|
533
533
|
|
534
534
|
@_dispatcher_cache
|
535
|
-
def completed_operations(self) ->
|
535
|
+
def completed_operations(self) -> Set[Operation]:
|
536
536
|
"""Returns the set of operations that have been completed.
|
537
537
|
|
538
538
|
This method returns the operations that have been scheduled and the
|
@@ -549,7 +549,7 @@ class Dispatcher:
|
|
549
549
|
return completed_operations
|
550
550
|
|
551
551
|
@_dispatcher_cache
|
552
|
-
def uncompleted_operations(self) ->
|
552
|
+
def uncompleted_operations(self) -> List[Operation]:
|
553
553
|
"""Returns the list of operations that have not been completed yet.
|
554
554
|
|
555
555
|
This method checks for operations that either haven't been scheduled
|
@@ -568,7 +568,7 @@ class Dispatcher:
|
|
568
568
|
return uncompleted_operations
|
569
569
|
|
570
570
|
@_dispatcher_cache
|
571
|
-
def ongoing_operations(self) ->
|
571
|
+
def ongoing_operations(self) -> List[ScheduledOperation]:
|
572
572
|
"""Returns the list of operations that are currently being processed.
|
573
573
|
|
574
574
|
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, Dict
|
9
9
|
|
10
10
|
from dataclasses import dataclass, field
|
11
11
|
|
@@ -17,7 +17,7 @@ from job_shop_lib.exceptions import ValidationError
|
|
17
17
|
T = TypeVar("T")
|
18
18
|
|
19
19
|
|
20
|
-
@dataclass(
|
20
|
+
@dataclass(frozen=True)
|
21
21
|
class DispatcherObserverConfig(Generic[T]):
|
22
22
|
"""Configuration for initializing any type of class.
|
23
23
|
|
@@ -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,11 +4,13 @@ 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
|
7
8
|
from enum import Enum
|
8
9
|
from collections.abc import Iterable
|
9
10
|
|
10
11
|
from job_shop_lib import Operation
|
11
12
|
from job_shop_lib.exceptions import ValidationError
|
13
|
+
|
12
14
|
from job_shop_lib.dispatching import (
|
13
15
|
Dispatcher,
|
14
16
|
filter_dominated_operations,
|
@@ -35,7 +37,7 @@ class ReadyOperationsFilterType(str, Enum):
|
|
35
37
|
|
36
38
|
def create_composite_operation_filter(
|
37
39
|
ready_operations_filters: Iterable[
|
38
|
-
ReadyOperationsFilter
|
40
|
+
Union[ReadyOperationsFilter, str, ReadyOperationsFilterType]
|
39
41
|
],
|
40
42
|
) -> ReadyOperationsFilter:
|
41
43
|
"""Creates and returns a :class:`ReadyOperationsFilter` function by
|
@@ -83,7 +85,7 @@ def create_composite_operation_filter(
|
|
83
85
|
|
84
86
|
|
85
87
|
def ready_operations_filter_factory(
|
86
|
-
filter_name: str
|
88
|
+
filter_name: Union[str, ReadyOperationsFilterType, ReadyOperationsFilter]
|
87
89
|
) -> ReadyOperationsFilter:
|
88
90
|
"""Creates and returns a filter function based on the specified
|
89
91
|
filter strategy name.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Home of the `HistoryObserver` class."""
|
2
2
|
|
3
|
+
from typing import List
|
3
4
|
from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
|
4
5
|
from job_shop_lib import ScheduledOperation
|
5
6
|
|
@@ -9,7 +10,7 @@ class HistoryObserver(DispatcherObserver):
|
|
9
10
|
|
10
11
|
def __init__(self, dispatcher: Dispatcher, *, subscribe: bool = True):
|
11
12
|
super().__init__(dispatcher, subscribe=subscribe)
|
12
|
-
self.history:
|
13
|
+
self.history: List[ScheduledOperation] = []
|
13
14
|
|
14
15
|
def update(self, scheduled_operation: ScheduledOperation):
|
15
16
|
self.history.append(scheduled_operation)
|