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.
- 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 +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 +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.3.dist-info}/METADATA +1 -1
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/RECORD +46 -46
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.2.dist-info → job_shop_lib-1.0.3.dist-info}/WHEEL +0 -0
@@ -1,7 +1,4 @@
|
|
1
|
-
|
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
|
-
|
79
|
+
# pylint: disable=too-many-arguments
|
80
|
+
def __init__( # pylint: disable=too-many-positional-arguments
|
83
81
|
self,
|
84
|
-
num_jobs:
|
85
|
-
num_machines:
|
86
|
-
duration_range:
|
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:
|
87
|
+
machines_per_operation: int | tuple[int, int] = 1,
|
90
88
|
name_suffix: str = "classic_generated_instance",
|
91
|
-
seed:
|
92
|
-
iteration_limit:
|
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
|
-
|
118
|
-
|
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:
|
131
|
-
num_machines:
|
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 =
|
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 =
|
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
|
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:
|
56
|
-
num_machines:
|
57
|
-
duration_range:
|
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:
|
60
|
-
iteration_limit:
|
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:
|
82
|
-
num_machines:
|
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:
|
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:
|
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:
|
132
|
-
suffix:
|
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,
|
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
|
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 =
|
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
|
-
|
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:
|
67
|
-
self._nodes_by_type:
|
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:
|
69
|
+
self._nodes_by_machine: list[list[Node]] = [
|
71
70
|
[] for _ in range(instance.num_machines)
|
72
71
|
]
|
73
|
-
self._nodes_by_job:
|
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:
|
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) ->
|
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) ->
|
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) ->
|
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) ->
|
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:
|
167
|
-
v_of_edge:
|
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:
|
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) ->
|
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
|
|
job_shop_lib/graphs/_node.py
CHANGED
@@ -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
|
-
|
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:
|
86
|
-
machine_id:
|
87
|
-
job_id:
|
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:
|
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:
|
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:
|
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,
|
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
|
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
|
-
|
165
|
+
type[GraphUpdater]
|
166
166
|
] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
|
167
167
|
ready_operations_filter: Callable[
|
168
|
-
[Dispatcher,
|
168
|
+
[Dispatcher, list[Operation]], list[Operation]
|
169
169
|
] = filter_dominated_operations,
|
170
170
|
reward_function_config: DispatcherObserverConfig[
|
171
|
-
|
171
|
+
type[RewardObserver]
|
172
172
|
] = DispatcherObserverConfig(class_type=MakespanReward),
|
173
|
-
render_mode:
|
174
|
-
render_config:
|
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
|
-
) ->
|
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,
|
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:
|
271
|
-
options:
|
272
|
-
) ->
|
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:
|
307
|
-
) ->
|
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:
|
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) ->
|
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) ->
|
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:
|
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:
|
21
|
+
self.rewards: list[float] = []
|
24
22
|
|
25
23
|
@property
|
26
24
|
def last_reward(self) -> float:
|