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.
Files changed (52) hide show
  1. job_shop_lib/__init__.py +1 -1
  2. job_shop_lib/_job_shop_instance.py +34 -29
  3. job_shop_lib/_operation.py +4 -2
  4. job_shop_lib/_schedule.py +11 -11
  5. job_shop_lib/benchmarking/_load_benchmark.py +3 -3
  6. job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
  7. job_shop_lib/dispatching/_dispatcher.py +19 -19
  8. job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
  9. job_shop_lib/dispatching/_factories.py +4 -2
  10. job_shop_lib/dispatching/_history_observer.py +2 -1
  11. job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
  12. job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
  13. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
  14. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
  15. job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
  16. job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
  17. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
  18. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
  19. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
  20. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +23 -15
  21. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
  22. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
  23. job_shop_lib/dispatching/rules/_utils.py +9 -8
  24. job_shop_lib/generation/__init__.py +8 -0
  25. job_shop_lib/generation/_general_instance_generator.py +42 -64
  26. job_shop_lib/generation/_instance_generator.py +11 -7
  27. job_shop_lib/generation/_transformations.py +5 -4
  28. job_shop_lib/generation/_utils.py +124 -0
  29. job_shop_lib/graphs/__init__.py +7 -7
  30. job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
  31. job_shop_lib/graphs/_job_shop_graph.py +17 -13
  32. job_shop_lib/graphs/_node.py +6 -4
  33. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
  34. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
  35. job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
  36. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
  37. job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
  38. job_shop_lib/reinforcement_learning/_utils.py +3 -3
  39. job_shop_lib/visualization/__init__.py +0 -60
  40. job_shop_lib/visualization/gantt/__init__.py +48 -0
  41. job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
  42. job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
  43. job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
  44. job_shop_lib/visualization/graphs/__init__.py +29 -0
  45. job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
  46. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  47. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +17 -15
  48. job_shop_lib-1.0.0b1.dist-info/RECORD +69 -0
  49. job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
  50. job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
  51. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
  52. {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
@@ -19,7 +19,7 @@ from job_shop_lib._schedule import Schedule
19
19
  from job_shop_lib._base_solver import BaseSolver, Solver
20
20
 
21
21
 
22
- __version__ = "1.0.0-a.5"
22
+ __version__ = "1.0.0-b.1"
23
23
 
24
24
  __all__ = [
25
25
  "Operation",
@@ -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 (list[list[Operation]]):
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 (dict[str, Any]):
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: list[list[Operation]],
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: list[list[Operation]] = 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: dict[str, Any] = 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 | str | bytes,
128
+ file_path: Union[os.PathLike, str, bytes],
129
129
  encoding: str = "utf-8",
130
130
  comment_symbol: str = "#",
131
- name: str | None = None,
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) -> dict[str, Any]:
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
- dict[str, Any]: The returned dictionary has the following
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: list[list[int]],
212
- machines_matrix: list[list[list[int]]] | list[list[int]],
211
+ duration_matrix: List[List[int]],
212
+ machines_matrix: List[List[List[int]]] | List[List[int]],
213
213
  name: str = "JobShopInstance",
214
- metadata: dict[str, Any] | None = None,
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: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]
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) -> list[list[int]]:
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) -> list[list[list[int]]] | list[list[int]]:
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., 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(machines=[0, 1], 2), Operation(machines=1, 3)],
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
- # list[list[list[int]]] here
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
- # list[list[int]] here
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) -> list[list[Operation]]:
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: list[list[Operation]] = [
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) -> list[float]:
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) -> list[int]:
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) -> list[int]:
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) -> list[int]:
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: list[list[int]],
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: list[list[list[int]]],
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()
@@ -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 | list[int], duration: int):
63
- self.machines: list[int] = (
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: list[list[ScheduledOperation]] | None = None,
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: dict[str, Any] = 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) -> list[list[ScheduledOperation]]:
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: list[list[ScheduledOperation]]):
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: list[list[int]] = []
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: dict[str, Any] | JobShopInstance,
121
- job_sequences: list[list[int]],
122
- metadata: dict[str, Any] | None = None,
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: list[list[int]],
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: list[list[ScheduledOperation]]):
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() -> dict[str, JobShopInstance]:
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() -> dict[str, dict[str, Any]]:
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: dict[Operation, tuple[IntVar, IntVar]] = {}
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: dict[str, Any]
155
+ self, instance: JobShopInstance, metadata: Dict[str, Any]
156
156
  ) -> Schedule:
157
157
  """Creates a Schedule object from the solution."""
158
- operations_start: dict[Operation, int] = {
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: list[list[ScheduledOperation]] = [
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: list[list[tuple[tuple[IntVar, IntVar], int]]] = [
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: list[ScheduledOperation] = []
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, list[Operation]], list[Operation]] | None
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: list[DispatcherObserver] = []
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) -> list[int]:
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) -> list[int]:
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) -> list[int]:
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 | None = None
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: type[ObserverType],
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: list[Operation]) -> int:
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) -> list[Operation]:
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) -> list[Operation]:
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) -> list[Operation]:
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) -> list[Operation]:
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) -> list[int]:
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) -> list[int]:
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) -> set[Operation]:
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) -> list[Operation]:
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) -> list[ScheduledOperation]:
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(slots=True, frozen=True)
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
- # type[FeatureObserver] | FeatureObserverType | str
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: dict[str, Any] = field(default_factory=dict)
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 | str | ReadyOperationsFilterType
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 | ReadyOperationsFilterType | ReadyOperationsFilter,
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: list[ScheduledOperation] = []
13
+ self.history: List[ScheduledOperation] = []
13
14
 
14
15
  def update(self, scheduled_operation: ScheduledOperation):
15
16
  self.history.append(scheduled_operation)