job-shop-lib 1.0.2__py3-none-any.whl → 1.0.3__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.
Files changed (46) hide show
  1. job_shop_lib/__init__.py +1 -1
  2. job_shop_lib/_job_shop_instance.py +26 -26
  3. job_shop_lib/_operation.py +2 -4
  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 +3 -3
  9. job_shop_lib/dispatching/_factories.py +2 -3
  10. job_shop_lib/dispatching/_history_observer.py +1 -2
  11. job_shop_lib/dispatching/_optimal_operations_observer.py +3 -4
  12. job_shop_lib/dispatching/_ready_operation_filters.py +18 -19
  13. job_shop_lib/dispatching/_unscheduled_operations_observer.py +3 -2
  14. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +10 -12
  15. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +1 -3
  16. job_shop_lib/dispatching/feature_observers/_factory.py +11 -12
  17. job_shop_lib/dispatching/feature_observers/_feature_observer.py +8 -9
  18. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -4
  19. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +2 -4
  20. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +2 -4
  21. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +15 -20
  22. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -10
  23. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +2 -3
  24. job_shop_lib/dispatching/rules/_utils.py +7 -8
  25. job_shop_lib/generation/_general_instance_generator.py +22 -20
  26. job_shop_lib/generation/_instance_generator.py +8 -8
  27. job_shop_lib/generation/_transformations.py +4 -5
  28. job_shop_lib/generation/_utils.py +16 -8
  29. job_shop_lib/graphs/_job_shop_graph.py +13 -14
  30. job_shop_lib/graphs/_node.py +6 -12
  31. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +2 -4
  32. job_shop_lib/reinforcement_learning/__init__.py +2 -1
  33. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +17 -17
  34. job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +1 -1
  35. job_shop_lib/reinforcement_learning/_reward_observers.py +1 -3
  36. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +22 -24
  37. job_shop_lib/reinforcement_learning/_utils.py +2 -2
  38. job_shop_lib/visualization/gantt/_gantt_chart_creator.py +11 -11
  39. job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +21 -21
  40. job_shop_lib/visualization/gantt/_plot_gantt_chart.py +12 -14
  41. job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +15 -17
  42. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +22 -24
  43. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/METADATA +1 -1
  44. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/RECORD +46 -46
  45. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/LICENSE +0 -0
  46. {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/WHEEL +0 -0
@@ -1,7 +1,4 @@
1
- """Home of the `BasicGenerator` class."""
2
-
3
- from typing import Optional, Tuple, Union
4
- import random
1
+ import numpy as np
5
2
 
6
3
  from job_shop_lib import JobShopInstance
7
4
  from job_shop_lib.exceptions import ValidationError
@@ -79,17 +76,18 @@ class GeneralInstanceGenerator(InstanceGenerator):
79
76
  Maximum number of instances to generate in iteration mode.
80
77
  """
81
78
 
82
- def __init__( # pylint: disable=too-many-arguments
79
+ # pylint: disable=too-many-arguments
80
+ def __init__( # pylint: disable=too-many-positional-arguments
83
81
  self,
84
- num_jobs: Union[int, Tuple[int, int]] = (10, 20),
85
- num_machines: Union[int, Tuple[int, int]] = (5, 10),
86
- duration_range: Tuple[int, int] = (1, 99),
82
+ num_jobs: int | tuple[int, int] = (10, 20),
83
+ num_machines: int | tuple[int, int] = (5, 10),
84
+ duration_range: tuple[int, int] = (1, 99),
87
85
  allow_less_jobs_than_machines: bool = True,
88
86
  allow_recirculation: bool = False,
89
- machines_per_operation: Union[int, Tuple[int, int]] = 1,
87
+ machines_per_operation: int | tuple[int, int] = 1,
90
88
  name_suffix: str = "classic_generated_instance",
91
- seed: Optional[int] = None,
92
- iteration_limit: Optional[int] = None,
89
+ seed: int | None = None,
90
+ iteration_limit: int | None = None,
93
91
  ):
94
92
  super().__init__(
95
93
  num_jobs=num_jobs,
@@ -114,8 +112,8 @@ class GeneralInstanceGenerator(InstanceGenerator):
114
112
  self.allow_recirculation = allow_recirculation
115
113
  self.name_suffix = name_suffix
116
114
 
117
- if seed is not None:
118
- random.seed(seed)
115
+ # Create a dedicated random number generator instance
116
+ self.rng = np.random.default_rng(seed)
119
117
 
120
118
  def __repr__(self) -> str:
121
119
  return (
@@ -127,17 +125,21 @@ class GeneralInstanceGenerator(InstanceGenerator):
127
125
 
128
126
  def generate(
129
127
  self,
130
- num_jobs: Optional[int] = None,
131
- num_machines: Optional[int] = None,
128
+ num_jobs: int | None = None,
129
+ num_machines: int | None = None,
132
130
  ) -> JobShopInstance:
133
131
  if num_jobs is None:
134
- num_jobs = random.randint(*self.num_jobs_range)
132
+ num_jobs = self.rng.integers(
133
+ self.num_jobs_range[0], self.num_jobs_range[1] + 1
134
+ )
135
135
 
136
136
  if num_machines is None:
137
137
  min_num_machines, max_num_machines = self.num_machines_range
138
138
  if not self.allow_less_jobs_than_machines:
139
139
  min_num_machines = min(num_jobs, max_num_machines)
140
- num_machines = random.randint(min_num_machines, max_num_machines)
140
+ num_machines = self.rng.integers(
141
+ min_num_machines, max_num_machines + 1
142
+ )
141
143
  elif (
142
144
  not self.allow_less_jobs_than_machines and num_jobs < num_machines
143
145
  ):
@@ -147,15 +149,15 @@ class GeneralInstanceGenerator(InstanceGenerator):
147
149
  )
148
150
 
149
151
  duration_matrix = generate_duration_matrix(
150
- num_jobs, num_machines, self.duration_range
152
+ num_jobs, num_machines, self.duration_range, self.rng
151
153
  )
152
154
  if self.allow_recirculation:
153
155
  machine_matrix = generate_machine_matrix_with_recirculation(
154
- num_jobs, num_machines
156
+ num_jobs, num_machines, self.rng
155
157
  )
156
158
  else:
157
159
  machine_matrix = generate_machine_matrix_without_recirculation(
158
- num_jobs, num_machines
160
+ num_jobs, num_machines, self.rng
159
161
  )
160
162
 
161
163
  return JobShopInstance.from_matrices(
@@ -3,7 +3,7 @@
3
3
  import abc
4
4
 
5
5
  import random
6
- from typing import Iterator, Optional, Tuple, Union
6
+ from typing import Iterator
7
7
 
8
8
  from job_shop_lib import JobShopInstance
9
9
  from job_shop_lib.exceptions import UninitializedAttributeError
@@ -52,12 +52,12 @@ class InstanceGenerator(abc.ABC):
52
52
 
53
53
  def __init__( # pylint: disable=too-many-arguments
54
54
  self,
55
- num_jobs: Union[int, Tuple[int, int]] = (10, 20),
56
- num_machines: Union[int, Tuple[int, int]] = (5, 10),
57
- duration_range: Tuple[int, int] = (1, 99),
55
+ num_jobs: int | tuple[int, int] = (10, 20),
56
+ num_machines: int | tuple[int, int] = (5, 10),
57
+ duration_range: tuple[int, int] = (1, 99),
58
58
  name_suffix: str = "generated_instance",
59
- seed: Optional[int] = None,
60
- iteration_limit: Optional[int] = None,
59
+ seed: int | None = None,
60
+ iteration_limit: int | None = None,
61
61
  ):
62
62
  if isinstance(num_jobs, int):
63
63
  num_jobs = (num_jobs, num_jobs)
@@ -78,8 +78,8 @@ class InstanceGenerator(abc.ABC):
78
78
  @abc.abstractmethod
79
79
  def generate(
80
80
  self,
81
- num_jobs: Optional[int] = None,
82
- num_machines: Optional[int] = None,
81
+ num_jobs: int | None = None,
82
+ num_machines: int | None = None,
83
83
  ) -> JobShopInstance:
84
84
  """Generates a single job shop instance
85
85
 
@@ -3,7 +3,6 @@
3
3
  import abc
4
4
  import copy
5
5
  import random
6
- from typing import Optional
7
6
 
8
7
  from job_shop_lib import JobShopInstance, Operation
9
8
 
@@ -39,7 +38,7 @@ class RemoveMachines(Transformation):
39
38
  """Removes operations associated with randomly selected machines until
40
39
  there are exactly num_machines machines left."""
41
40
 
42
- def __init__(self, num_machines: int, suffix: Optional[str] = None):
41
+ def __init__(self, num_machines: int, suffix: str | None = None):
43
42
  if suffix is None:
44
43
  suffix = f"_machines={num_machines}"
45
44
  super().__init__(suffix=suffix)
@@ -84,7 +83,7 @@ class AddDurationNoise(Transformation):
84
83
  min_duration: int = 1,
85
84
  max_duration: int = 100,
86
85
  noise_level: int = 10,
87
- suffix: Optional[str] = None,
86
+ suffix: str | None = None,
88
87
  ):
89
88
  if suffix is None:
90
89
  suffix = f"_noise={noise_level}"
@@ -128,8 +127,8 @@ class RemoveJobs(Transformation):
128
127
  self,
129
128
  min_jobs: int,
130
129
  max_jobs: int,
131
- target_jobs: Optional[int] = None,
132
- suffix: Optional[str] = None,
130
+ target_jobs: int | None = None,
131
+ suffix: str | None = None,
133
132
  ):
134
133
  if suffix is None:
135
134
  suffix = f"_jobs={min_jobs}-{max_jobs}"
@@ -1,4 +1,3 @@
1
- from typing import Tuple
2
1
  import numpy as np
3
2
  from numpy.typing import NDArray
4
3
 
@@ -6,7 +5,10 @@ from job_shop_lib.exceptions import ValidationError
6
5
 
7
6
 
8
7
  def generate_duration_matrix(
9
- num_jobs: int, num_machines: int, duration_range: Tuple[int, int]
8
+ num_jobs: int,
9
+ num_machines: int,
10
+ duration_range: tuple[int, int],
11
+ rng: np.random.Generator | None = None,
10
12
  ) -> NDArray[np.int32]:
11
13
  """Generates a duration matrix.
12
14
 
@@ -14,10 +16,12 @@ def generate_duration_matrix(
14
16
  num_jobs: The number of jobs.
15
17
  num_machines: The number of machines.
16
18
  duration_range: The range of the duration values.
19
+ rng: A numpy random number generator.
17
20
 
18
21
  Returns:
19
22
  A duration matrix with shape (num_jobs, num_machines).
20
23
  """
24
+ rng = rng or np.random.default_rng()
21
25
  if duration_range[0] > duration_range[1]:
22
26
  raise ValidationError(
23
27
  "The lower bound of the duration range must be less than or equal "
@@ -28,7 +32,7 @@ def generate_duration_matrix(
28
32
  if num_machines <= 0:
29
33
  raise ValidationError("The number of machines must be greater than 0.")
30
34
 
31
- return np.random.randint(
35
+ return rng.integers(
32
36
  duration_range[0],
33
37
  duration_range[1] + 1,
34
38
  size=(num_jobs, num_machines),
@@ -37,26 +41,28 @@ def generate_duration_matrix(
37
41
 
38
42
 
39
43
  def generate_machine_matrix_with_recirculation(
40
- num_jobs: int, num_machines: int
44
+ num_jobs: int, num_machines: int, rng: np.random.Generator | None = None
41
45
  ) -> NDArray[np.int32]:
42
46
  """Generate a machine matrix with recirculation.
43
47
 
44
48
  Args:
45
49
  num_jobs: The number of jobs.
46
50
  num_machines: The number of machines.
51
+ rng: A numpy random number generator.
47
52
 
48
53
  Returns:
49
54
  A machine matrix with recirculation with shape (num_machines,
50
55
  num_jobs).
51
56
  """
57
+ rng = rng or np.random.default_rng()
52
58
  if num_jobs <= 0:
53
59
  raise ValidationError("The number of jobs must be greater than 0.")
54
60
  if num_machines <= 0:
55
61
  raise ValidationError("The number of machines must be greater than 0.")
56
62
  num_machines_is_correct = False
57
63
  while not num_machines_is_correct:
58
- machine_matrix: np.ndarray = np.random.randint(
59
- 0, num_machines, size=(num_machines, num_jobs)
64
+ machine_matrix: np.ndarray = rng.integers(
65
+ 0, num_machines, size=(num_machines, num_jobs), dtype=np.int32
60
66
  )
61
67
  num_machines_is_correct = (
62
68
  len(np.unique(machine_matrix)) == num_machines
@@ -66,17 +72,19 @@ def generate_machine_matrix_with_recirculation(
66
72
 
67
73
 
68
74
  def generate_machine_matrix_without_recirculation(
69
- num_jobs: int, num_machines: int
75
+ num_jobs: int, num_machines: int, rng: np.random.Generator | None = None
70
76
  ) -> NDArray[np.int32]:
71
77
  """Generate a machine matrix without recirculation.
72
78
 
73
79
  Args:
74
80
  num_jobs: The number of jobs.
75
81
  num_machines: The number of machines.
82
+ rng: A numpy random number generator.
76
83
 
77
84
  Returns:
78
85
  A machine matrix without recirculation.
79
86
  """
87
+ rng = rng or np.random.default_rng()
80
88
  if num_jobs <= 0:
81
89
  raise ValidationError("The number of jobs must be greater than 0.")
82
90
  if num_machines <= 0:
@@ -91,7 +99,7 @@ def generate_machine_matrix_without_recirculation(
91
99
  )
92
100
  # Shuffle the columns:
93
101
  machine_matrix = np.apply_along_axis(
94
- np.random.permutation, 1, machine_matrix
102
+ rng.permutation, 1, machine_matrix
95
103
  )
96
104
  return machine_matrix
97
105
 
@@ -1,6 +1,5 @@
1
1
  """Home of the `JobShopGraph` class."""
2
2
 
3
- from typing import List, Union, Dict
4
3
  import collections
5
4
  import networkx as nx
6
5
 
@@ -63,23 +62,23 @@ class JobShopGraph:
63
62
  self.graph = nx.DiGraph()
64
63
  self.instance = instance
65
64
 
66
- self._nodes: List[Node] = []
67
- self._nodes_by_type: Dict[NodeType, List[Node]] = (
65
+ self._nodes: list[Node] = []
66
+ self._nodes_by_type: dict[NodeType, list[Node]] = (
68
67
  collections.defaultdict(list)
69
68
  )
70
- self._nodes_by_machine: List[List[Node]] = [
69
+ self._nodes_by_machine: list[list[Node]] = [
71
70
  [] for _ in range(instance.num_machines)
72
71
  ]
73
- self._nodes_by_job: List[List[Node]] = [
72
+ self._nodes_by_job: list[list[Node]] = [
74
73
  [] for _ in range(instance.num_jobs)
75
74
  ]
76
75
  self._next_node_id = 0
77
- self.removed_nodes: List[bool] = []
76
+ self.removed_nodes: list[bool] = []
78
77
  if add_operation_nodes:
79
78
  self.add_operation_nodes()
80
79
 
81
80
  @property
82
- def nodes(self) -> List[Node]:
81
+ def nodes(self) -> list[Node]:
83
82
  """List of all nodes added to the graph.
84
83
 
85
84
  It may contain nodes that have been removed from the graph.
@@ -87,7 +86,7 @@ class JobShopGraph:
87
86
  return self._nodes
88
87
 
89
88
  @property
90
- def nodes_by_type(self) -> Dict[NodeType, List[Node]]:
89
+ def nodes_by_type(self) -> dict[NodeType, list[Node]]:
91
90
  """Dictionary mapping node types to lists of nodes.
92
91
 
93
92
  It may contain nodes that have been removed from the graph.
@@ -95,7 +94,7 @@ class JobShopGraph:
95
94
  return self._nodes_by_type
96
95
 
97
96
  @property
98
- def nodes_by_machine(self) -> List[List[Node]]:
97
+ def nodes_by_machine(self) -> list[list[Node]]:
99
98
  """List of lists mapping machine ids to operation nodes.
100
99
 
101
100
  It may contain nodes that have been removed from the graph.
@@ -103,7 +102,7 @@ class JobShopGraph:
103
102
  return self._nodes_by_machine
104
103
 
105
104
  @property
106
- def nodes_by_job(self) -> List[List[Node]]:
105
+ def nodes_by_job(self) -> list[list[Node]]:
107
106
  """List of lists mapping job ids to operation nodes.
108
107
 
109
108
  It may contain nodes that have been removed from the graph.
@@ -163,8 +162,8 @@ class JobShopGraph:
163
162
 
164
163
  def add_edge(
165
164
  self,
166
- u_of_edge: Union[Node, int],
167
- v_of_edge: Union[Node, int],
165
+ u_of_edge: Node | int,
166
+ v_of_edge: Node | int,
168
167
  **attr,
169
168
  ) -> None:
170
169
  """Adds an edge to the graph.
@@ -228,7 +227,7 @@ class JobShopGraph:
228
227
 
229
228
  self.graph.remove_nodes_from(isolated_nodes)
230
229
 
231
- def is_removed(self, node: Union[int, Node]) -> bool:
230
+ def is_removed(self, node: int | Node) -> bool:
232
231
  """Returns whether the node is removed from the graph.
233
232
 
234
233
  Args:
@@ -241,7 +240,7 @@ class JobShopGraph:
241
240
  node = node.node_id
242
241
  return self.removed_nodes[node]
243
242
 
244
- def non_removed_nodes(self) -> List[Node]:
243
+ def non_removed_nodes(self) -> list[Node]:
245
244
  """Returns the nodes that are not removed from the graph."""
246
245
  return [node for node in self._nodes if not self.is_removed(node)]
247
246
 
@@ -1,7 +1,5 @@
1
1
  """Home of the `Node` class."""
2
2
 
3
- from typing import Optional
4
-
5
3
  from job_shop_lib import Operation
6
4
  from job_shop_lib.exceptions import (
7
5
  UninitializedAttributeError,
@@ -70,21 +68,17 @@ class Node:
70
68
  __slots__ = {
71
69
  "node_type": "The type of the node.",
72
70
  "_node_id": "Unique identifier for the node.",
73
- "_operation": (
74
- "The operation associated with the node."
75
- ),
76
- "_machine_id": (
77
- "The machine ID associated with the node."
78
- ),
71
+ "_operation": ("The operation associated with the node."),
72
+ "_machine_id": ("The machine ID associated with the node."),
79
73
  "_job_id": "The job ID associated with the node.",
80
74
  }
81
75
 
82
76
  def __init__(
83
77
  self,
84
78
  node_type: NodeType,
85
- operation: Optional[Operation] = None,
86
- machine_id: Optional[int] = None,
87
- job_id: Optional[int] = None,
79
+ operation: Operation | None = None,
80
+ machine_id: int | None = None,
81
+ job_id: int | None = None,
88
82
  ):
89
83
  if node_type == NodeType.OPERATION and operation is None:
90
84
  raise ValidationError("Operation node must have an operation.")
@@ -96,7 +90,7 @@ class Node:
96
90
  raise ValidationError("Job node must have a job_id.")
97
91
 
98
92
  self.node_type: NodeType = node_type
99
- self._node_id: Optional[int] = None
93
+ self._node_id: int | None = None
100
94
 
101
95
  self._operation = operation
102
96
  self._machine_id = machine_id
@@ -1,7 +1,5 @@
1
1
  """Home of the `ResidualGraphUpdater` class."""
2
2
 
3
- from typing import Optional, List
4
-
5
3
  from job_shop_lib import ScheduledOperation
6
4
  from job_shop_lib.exceptions import UninitializedAttributeError
7
5
  from job_shop_lib.graphs import NodeType, JobShopGraph
@@ -56,7 +54,7 @@ class ResidualGraphUpdater(GraphUpdater):
56
54
  remove_completed_machine_nodes: bool = True,
57
55
  remove_completed_job_nodes: bool = True,
58
56
  ):
59
- self._is_completed_observer: Optional[IsCompletedObserver] = None
57
+ self._is_completed_observer: IsCompletedObserver | None = None
60
58
  self.remove_completed_job_nodes = remove_completed_job_nodes
61
59
  self.remove_completed_machine_nodes = remove_completed_machine_nodes
62
60
  self._initialize_is_completed_observer_attribute(dispatcher)
@@ -82,7 +80,7 @@ class ResidualGraphUpdater(GraphUpdater):
82
80
  return False
83
81
  return True
84
82
 
85
- feature_types: List[FeatureType] = []
83
+ feature_types: list[FeatureType] = []
86
84
  if self.remove_completed_machine_nodes:
87
85
  feature_types.append(FeatureType.MACHINES)
88
86
  if self.remove_completed_job_nodes:
@@ -48,7 +48,8 @@ from job_shop_lib.reinforcement_learning._multi_job_shop_graph_env import (
48
48
  MultiJobShopGraphEnv,
49
49
  )
50
50
  from ._resource_task_graph_observation import (
51
- ResourceTaskGraphObservation, ResourceTaskGraphObservationDict
51
+ ResourceTaskGraphObservation,
52
+ ResourceTaskGraphObservationDict,
52
53
  )
53
54
 
54
55
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from collections import defaultdict
4
4
  from collections.abc import Callable, Sequence
5
- from typing import Any, Tuple, Dict, List, Optional, Type
5
+ from typing import Any
6
6
  from copy import deepcopy
7
7
 
8
8
  import gymnasium as gym
@@ -162,16 +162,16 @@ class MultiJobShopGraphEnv(gym.Env):
162
162
  [JobShopInstance], JobShopGraph
163
163
  ] = build_resource_task_graph,
164
164
  graph_updater_config: DispatcherObserverConfig[
165
- Type[GraphUpdater]
165
+ type[GraphUpdater]
166
166
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
167
167
  ready_operations_filter: Callable[
168
- [Dispatcher, List[Operation]], List[Operation]
168
+ [Dispatcher, list[Operation]], list[Operation]
169
169
  ] = filter_dominated_operations,
170
170
  reward_function_config: DispatcherObserverConfig[
171
- Type[RewardObserver]
171
+ type[RewardObserver]
172
172
  ] = DispatcherObserverConfig(class_type=MakespanReward),
173
- render_mode: Optional[str] = None,
174
- render_config: Optional[RenderConfig] = None,
173
+ render_mode: str | None = None,
174
+ render_config: RenderConfig | None = None,
175
175
  use_padding: bool = True,
176
176
  ) -> None:
177
177
  super().__init__()
@@ -226,7 +226,7 @@ class MultiJobShopGraphEnv(gym.Env):
226
226
  @property
227
227
  def ready_operations_filter(
228
228
  self,
229
- ) -> Optional[Callable[[Dispatcher, List[Operation]], List[Operation]]]:
229
+ ) -> Callable[[Dispatcher, list[Operation]], list[Operation]] | None:
230
230
  """Returns the current ready operations filter."""
231
231
  return (
232
232
  self.single_job_shop_graph_env.dispatcher.ready_operations_filter
@@ -236,7 +236,7 @@ class MultiJobShopGraphEnv(gym.Env):
236
236
  def ready_operations_filter(
237
237
  self,
238
238
  ready_operations_filter: Callable[
239
- [Dispatcher, List[Operation]], List[Operation]
239
+ [Dispatcher, list[Operation]], list[Operation]
240
240
  ],
241
241
  ) -> None:
242
242
  """Sets the ready operations filter."""
@@ -267,9 +267,9 @@ class MultiJobShopGraphEnv(gym.Env):
267
267
  def reset(
268
268
  self,
269
269
  *,
270
- seed: Optional[int] = None,
271
- options: Dict[str, Any] | None = None,
272
- ) -> Tuple[ObservationDict, Dict[str, Any]]:
270
+ seed: int | None = None,
271
+ options: dict[str, Any] | None = None,
272
+ ) -> tuple[ObservationDict, dict[str, Any]]:
273
273
  """Resets the environment and returns the initial observation.
274
274
 
275
275
  Args:
@@ -303,8 +303,8 @@ class MultiJobShopGraphEnv(gym.Env):
303
303
  return obs, info
304
304
 
305
305
  def step(
306
- self, action: Tuple[int, int]
307
- ) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
306
+ self, action: tuple[int, int]
307
+ ) -> tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
308
308
  """Takes a step in the environment.
309
309
 
310
310
  Args:
@@ -356,7 +356,7 @@ class MultiJobShopGraphEnv(gym.Env):
356
356
  input_shape: (num_machines, num_features)
357
357
  output_shape: (max_num_machines, num_features) (padded with -1)
358
358
  """
359
- padding_value: Dict[str, float | bool] = defaultdict(lambda: -1)
359
+ padding_value: dict[str, float | bool] = defaultdict(lambda: -1)
360
360
  padding_value[ObservationSpaceKey.REMOVED_NODES.value] = True
361
361
  for key, value in observation.items():
362
362
  if not isinstance(value, np.ndarray): # Make mypy happy
@@ -369,7 +369,7 @@ class MultiJobShopGraphEnv(gym.Env):
369
369
  )
370
370
  return observation
371
371
 
372
- def _get_output_shape(self, key: str) -> Tuple[int, ...]:
372
+ def _get_output_shape(self, key: str) -> tuple[int, ...]:
373
373
  """Returns the output shape of the observation space key."""
374
374
  output_shape = self.observation_space[key].shape
375
375
  assert output_shape is not None # Make mypy happy
@@ -378,12 +378,12 @@ class MultiJobShopGraphEnv(gym.Env):
378
378
  def render(self) -> None:
379
379
  self.single_job_shop_graph_env.render()
380
380
 
381
- def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
381
+ def get_available_actions_with_ids(self) -> list[tuple[int, int, int]]:
382
382
  """Returns a list of available actions in the form of
383
383
  (operation_id, machine_id, job_id)."""
384
384
  return self.single_job_shop_graph_env.get_available_actions_with_ids()
385
385
 
386
- def validate_action(self, action: Tuple[int, int]) -> None:
386
+ def validate_action(self, action: tuple[int, int]) -> None:
387
387
  """Validates the action.
388
388
 
389
389
  Args:
@@ -245,7 +245,7 @@ class ResourceTaskGraphObservation(ObservationWrapper, Generic[EnvType]):
245
245
 
246
246
  @staticmethod
247
247
  def _get_start_from_zero_mappings(
248
- original_indices_dict: dict[str, NDArray[np.int32]]
248
+ original_indices_dict: dict[str, NDArray[np.int32]],
249
249
  ) -> dict[str, dict[int, int]]:
250
250
  mappings = {}
251
251
  for key, indices in original_indices_dict.items():
@@ -1,8 +1,6 @@
1
1
  """Rewards functions are defined as `DispatcherObervers` and are used to
2
2
  calculate the reward for a given state."""
3
3
 
4
- from typing import List
5
-
6
4
  from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
7
5
  from job_shop_lib import ScheduledOperation
8
6
 
@@ -20,7 +18,7 @@ class RewardObserver(DispatcherObserver):
20
18
  self, dispatcher: Dispatcher, *, subscribe: bool = True
21
19
  ) -> None:
22
20
  super().__init__(dispatcher, subscribe=subscribe)
23
- self.rewards: List[float] = []
21
+ self.rewards: list[float] = []
24
22
 
25
23
  @property
26
24
  def last_reward(self) -> float: