job-shop-lib 1.0.0b5__tar.gz → 1.0.1__tar.gz
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-1.0.0b5 → job_shop_lib-1.0.1}/PKG-INFO +4 -10
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/README.md +2 -8
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/__init__.py +1 -1
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/_operation.py +9 -3
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/_scheduled_operation.py +3 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_dispatcher.py +6 -13
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_factories.py +3 -3
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_optimal_operations_observer.py +0 -2
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_ready_operation_filters.py +4 -4
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +10 -5
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_factory.py +8 -3
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_feature_observer.py +1 -1
- job_shop_lib-1.0.1/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +1 -1
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/_machine_chooser_factory.py +3 -2
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/__init__.py +2 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_build_resource_task_graphs.py +1 -1
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_job_shop_graph.py +38 -19
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/graph_updaters/__init__.py +3 -0
- job_shop_lib-1.0.1/job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +3 -1
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/graph_updaters/_utils.py +2 -2
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/__init__.py +4 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +1 -1
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +102 -24
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +11 -2
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_types_and_constants.py +11 -10
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_utils.py +29 -0
- job_shop_lib-1.0.1/job_shop_lib/visualization/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +5 -2
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +53 -19
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/pyproject.toml +3 -3
- job_shop_lib-1.0.0b5/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +0 -129
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/LICENSE +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/_base_solver.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/_job_shop_instance.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/_schedule.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/benchmarking/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/benchmarking/_load_benchmark.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/benchmarking/benchmark_instances.json +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/constraint_programming/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/constraint_programming/_ortools_solver.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_dispatcher_observer_config.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_history_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_unscheduled_operations_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_duration_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/rules/_utils.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/exceptions.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/generation/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/generation/_general_instance_generator.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/generation/_instance_generator.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/generation/_transformations.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/generation/_utils.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_build_disjunctive_graph.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_constants.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_node.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/graph_updaters/_graph_updater.py +0 -0
- /job_shop_lib-1.0.0b5/job_shop_lib/visualization/__init__.py → /job_shop_lib-1.0.1/job_shop_lib/py.typed +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/reinforcement_learning/_reward_observers.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/gantt/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/gantt/_gantt_chart_creator.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/gantt/_plot_gantt_chart.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/graphs/__init__.py +0 -0
- {job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.1
|
4
4
|
Summary: An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)
|
5
5
|
License: MIT
|
6
6
|
Author: Pabloo22
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
14
|
Provides-Extra: pygraphviz
|
15
|
-
Requires-Dist: gymnasium (>=0.
|
15
|
+
Requires-Dist: gymnasium (>=1.0.0,<2.0.0)
|
16
16
|
Requires-Dist: imageio[ffmpeg] (>=2.34.1,<3.0.0)
|
17
17
|
Requires-Dist: matplotlib (>=3,<4)
|
18
18
|
Requires-Dist: networkx (>=3,<4)
|
@@ -48,7 +48,7 @@ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more
|
|
48
48
|
|
49
49
|
JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
|
50
50
|
|
51
|
-
You can install the latest stable version
|
51
|
+
You can install the latest stable version using `pip`:
|
52
52
|
|
53
53
|
```bash
|
54
54
|
pip install job-shop-lib
|
@@ -57,13 +57,7 @@ pip install job-shop-lib
|
|
57
57
|
See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
|
58
58
|
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
```bash
|
63
|
-
pip install job-shop-lib==1.0.0b5
|
64
|
-
```
|
65
|
-
|
66
|
-
Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiarize yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward.
|
60
|
+
There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward. See see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed) for the latest changes.
|
67
61
|
|
68
62
|
<!-- end installation -->
|
69
63
|
|
@@ -24,7 +24,7 @@ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more
|
|
24
24
|
|
25
25
|
JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
|
26
26
|
|
27
|
-
You can install the latest stable version
|
27
|
+
You can install the latest stable version using `pip`:
|
28
28
|
|
29
29
|
```bash
|
30
30
|
pip install job-shop-lib
|
@@ -33,13 +33,7 @@ pip install job-shop-lib
|
|
33
33
|
See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
|
34
34
|
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
```bash
|
39
|
-
pip install job-shop-lib==1.0.0b5
|
40
|
-
```
|
41
|
-
|
42
|
-
Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiarize yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward.
|
36
|
+
There is a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/) for versions 1.0.0a3 and onward. See see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed) for the latest changes.
|
43
37
|
|
44
38
|
<!-- end installation -->
|
45
39
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
from typing import Union, List
|
6
6
|
|
7
|
-
from job_shop_lib.exceptions import
|
7
|
+
from job_shop_lib.exceptions import ValidationError
|
8
8
|
|
9
9
|
|
10
10
|
class Operation:
|
@@ -81,8 +81,14 @@ class Operation:
|
|
81
81
|
If the operation has multiple machines in its list.
|
82
82
|
"""
|
83
83
|
if len(self.machines) > 1:
|
84
|
-
raise
|
85
|
-
"Operation has multiple machines."
|
84
|
+
raise ValidationError(
|
85
|
+
"Operation has multiple machines. The `machine_id` property "
|
86
|
+
"should only be used when working with a classic JSSP "
|
87
|
+
"instance. This error prevents silent bugs. To handle "
|
88
|
+
"operations with more machines you have to use the machines "
|
89
|
+
"attribute. If you get this error using `job_shop_lib` "
|
90
|
+
"objects, it means that that object does not support "
|
91
|
+
"operations with multiple machines yet."
|
86
92
|
)
|
87
93
|
return self.machines[0]
|
88
94
|
|
@@ -336,8 +336,7 @@ class Dispatcher:
|
|
336
336
|
The operation to be scheduled.
|
337
337
|
machine_id:
|
338
338
|
The id of the machine on which the operation is to be
|
339
|
-
scheduled.
|
340
|
-
next available time for the operation on any machine.
|
339
|
+
scheduled.
|
341
340
|
"""
|
342
341
|
return max(
|
343
342
|
self._machine_next_available_time[machine_id],
|
@@ -459,12 +458,11 @@ class Dispatcher:
|
|
459
458
|
return unscheduled_operations
|
460
459
|
|
461
460
|
@_dispatcher_cache
|
462
|
-
def scheduled_operations(self) -> List[
|
461
|
+
def scheduled_operations(self) -> List[ScheduledOperation]:
|
463
462
|
"""Returns the list of operations that have been scheduled."""
|
464
463
|
scheduled_operations = []
|
465
|
-
for
|
466
|
-
|
467
|
-
scheduled_operations.extend(operations)
|
464
|
+
for machine_schedule in self.schedule.schedule:
|
465
|
+
scheduled_operations.extend(machine_schedule)
|
468
466
|
return scheduled_operations
|
469
467
|
|
470
468
|
@_dispatcher_cache
|
@@ -532,19 +530,14 @@ class Dispatcher:
|
|
532
530
|
return scheduled_operation.end_time - adjusted_start_time
|
533
531
|
|
534
532
|
@_dispatcher_cache
|
535
|
-
def completed_operations(self) -> Set[
|
533
|
+
def completed_operations(self) -> Set[ScheduledOperation]:
|
536
534
|
"""Returns the set of operations that have been completed.
|
537
535
|
|
538
536
|
This method returns the operations that have been scheduled and the
|
539
537
|
current time is greater than or equal to the end time of the operation.
|
540
538
|
"""
|
541
539
|
scheduled_operations = set(self.scheduled_operations())
|
542
|
-
ongoing_operations = set(
|
543
|
-
map(
|
544
|
-
lambda scheduled_op: scheduled_op.operation,
|
545
|
-
self.ongoing_operations(),
|
546
|
-
)
|
547
|
-
)
|
540
|
+
ongoing_operations = set(self.ongoing_operations())
|
548
541
|
completed_operations = scheduled_operations - ongoing_operations
|
549
542
|
return completed_operations
|
550
543
|
|
@@ -95,9 +95,9 @@ def ready_operations_filter_factory(
|
|
95
95
|
|
96
96
|
Args:
|
97
97
|
filter_name:
|
98
|
-
The name of the filter function to be used.
|
99
|
-
|
100
|
-
|
98
|
+
The name of the filter function to be used. See
|
99
|
+
:class:`ReadyOperationsFilterType` for supported values.
|
100
|
+
Alternatively, a custom filter function can be passed directly.
|
101
101
|
|
102
102
|
Returns:
|
103
103
|
A function that takes a :class:`~job_shop_lib.dispatching.Dispatcher`
|
{job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_optimal_operations_observer.py
RENAMED
@@ -20,8 +20,6 @@ class OptimalOperationsObserver(DispatcherObserver):
|
|
20
20
|
based on the reference schedule.
|
21
21
|
reference_schedule: The reference schedule used to determine optimal
|
22
22
|
operations.
|
23
|
-
_operation_to_scheduled: Dictionary mapping operations to their
|
24
|
-
scheduled versions in the reference schedule.
|
25
23
|
|
26
24
|
Args:
|
27
25
|
dispatcher: The dispatcher instance to observe.
|
{job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/_ready_operation_filters.py
RENAMED
@@ -153,16 +153,16 @@ def _get_min_machine_end_times(
|
|
153
153
|
|
154
154
|
|
155
155
|
def _get_immediate_machines(
|
156
|
-
|
156
|
+
dispatcher: Dispatcher, available_operations: List[Operation]
|
157
157
|
) -> List[bool]:
|
158
158
|
"""Returns the machine ids of the machines that have at least one
|
159
159
|
operation with the lowest start time (i.e. the start time)."""
|
160
|
-
working_machines = [False] *
|
160
|
+
working_machines = [False] * dispatcher.instance.num_machines
|
161
161
|
# We can't use the current_time directly because it will cause
|
162
162
|
# an infinite loop.
|
163
|
-
current_time =
|
163
|
+
current_time = dispatcher.min_start_time(available_operations)
|
164
164
|
for op in available_operations:
|
165
165
|
for machine_id in op.machines:
|
166
|
-
if
|
166
|
+
if dispatcher.start_time(op, machine_id) == current_time:
|
167
167
|
working_machines[machine_id] = True
|
168
168
|
return working_machines
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from collections.abc import Sequence
|
5
|
-
from typing import List, Dict, Union, Optional
|
5
|
+
from typing import List, Dict, Union, Optional, Type
|
6
6
|
# The Self type can be imported directly from Python’s typing module in
|
7
7
|
# version 3.11 and beyond. We use the typing_extensions module to support
|
8
8
|
# python >=3.8
|
@@ -18,6 +18,7 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
18
18
|
FeatureType,
|
19
19
|
FeatureObserverConfig,
|
20
20
|
feature_observer_factory,
|
21
|
+
FeatureObserverType,
|
21
22
|
)
|
22
23
|
|
23
24
|
|
@@ -104,7 +105,14 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
104
105
|
def from_feature_observer_configs(
|
105
106
|
cls,
|
106
107
|
dispatcher: Dispatcher,
|
107
|
-
feature_observer_configs: Sequence[
|
108
|
+
feature_observer_configs: Sequence[
|
109
|
+
Union[
|
110
|
+
str,
|
111
|
+
FeatureObserverType,
|
112
|
+
Type[FeatureObserver],
|
113
|
+
FeatureObserverConfig,
|
114
|
+
],
|
115
|
+
],
|
108
116
|
subscribe: bool = True,
|
109
117
|
) -> Self:
|
110
118
|
"""Creates the composite feature observer.
|
@@ -178,9 +186,6 @@ if __name__ == "__main__":
|
|
178
186
|
import time
|
179
187
|
from job_shop_lib.benchmarking import load_benchmark_instance
|
180
188
|
from job_shop_lib.dispatching.rules import DispatchingRuleSolver
|
181
|
-
from job_shop_lib.dispatching.feature_observers import (
|
182
|
-
FeatureObserverType,
|
183
|
-
)
|
184
189
|
|
185
190
|
ta80 = load_benchmark_instance("ta80")
|
186
191
|
|
{job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/dispatching/feature_observers/_factory.py
RENAMED
@@ -39,9 +39,14 @@ class FeatureObserverType(str, Enum):
|
|
39
39
|
# FeatureObserverConfig = DispatcherObserverConfig[
|
40
40
|
# Type[FeatureObserver] | FeatureObserverType | str
|
41
41
|
# ]
|
42
|
-
FeatureObserverConfig = DispatcherObserverConfig[
|
43
|
-
|
44
|
-
]
|
42
|
+
# FeatureObserverConfig = DispatcherObserverConfig[
|
43
|
+
# Union[Type[FeatureObserver], FeatureObserverType, str]
|
44
|
+
# ]
|
45
|
+
FeatureObserverConfig = (
|
46
|
+
DispatcherObserverConfig[Type[FeatureObserver]]
|
47
|
+
| DispatcherObserverConfig[FeatureObserverType]
|
48
|
+
| DispatcherObserverConfig[str]
|
49
|
+
)
|
45
50
|
|
46
51
|
|
47
52
|
def feature_observer_factory(
|
@@ -36,7 +36,7 @@ class FeatureObserver(DispatcherObserver):
|
|
36
36
|
individually. Furthermore, machine learning models can be trained on these
|
37
37
|
arrays to predict the best dispatching decisions.
|
38
38
|
|
39
|
-
Arrays use the data type ``np.float32``.
|
39
|
+
Arrays use the data type ``np.float32``.
|
40
40
|
|
41
41
|
New :class:`FeatureObservers` must inherit from this class, and re-define
|
42
42
|
the class attributes ``_singleton`` (defualt ), ``_feature_size``
|
@@ -0,0 +1,97 @@
|
|
1
|
+
"""Home of the `IsCompletedObserver` class."""
|
2
|
+
|
3
|
+
from typing import Optional, Union, List
|
4
|
+
import numpy as np
|
5
|
+
|
6
|
+
from job_shop_lib import ScheduledOperation
|
7
|
+
from job_shop_lib.dispatching import Dispatcher
|
8
|
+
from job_shop_lib.dispatching.feature_observers import (
|
9
|
+
FeatureObserver,
|
10
|
+
FeatureType,
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class IsCompletedObserver(FeatureObserver):
|
15
|
+
"""Adds a binary feature indicating whether each operation,
|
16
|
+
machine, or job has been completed.
|
17
|
+
|
18
|
+
An operation is considered completed if it has been scheduled and the
|
19
|
+
current time is greater than or equal to the sum of the operation's start
|
20
|
+
time and duration.
|
21
|
+
|
22
|
+
A machine or job is considered completed if all of its operations have been
|
23
|
+
completed.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
dispatcher:
|
27
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
28
|
+
feature_types:
|
29
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
30
|
+
that specifies the types of features to observe. They must be a
|
31
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
32
|
+
If ``None``, all supported feature types are tracked.
|
33
|
+
subscribe:
|
34
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
35
|
+
initialization. Otherwise, the observer must be subscribed later
|
36
|
+
or manually updated.
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
dispatcher: Dispatcher,
|
42
|
+
*,
|
43
|
+
feature_types: Optional[Union[List[FeatureType], FeatureType]] = None,
|
44
|
+
subscribe: bool = True,
|
45
|
+
):
|
46
|
+
feature_types = self._get_feature_types_list(feature_types)
|
47
|
+
self._num_of_operations_per_machine = np.array(
|
48
|
+
[
|
49
|
+
len(operations_by_machine)
|
50
|
+
for operations_by_machine in (
|
51
|
+
dispatcher.instance.operations_by_machine
|
52
|
+
)
|
53
|
+
]
|
54
|
+
)
|
55
|
+
self._num_of_operations_per_job = np.array(
|
56
|
+
[len(job) for job in dispatcher.instance.jobs]
|
57
|
+
)
|
58
|
+
super().__init__(
|
59
|
+
dispatcher,
|
60
|
+
feature_types=feature_types,
|
61
|
+
subscribe=subscribe,
|
62
|
+
)
|
63
|
+
|
64
|
+
def initialize_features(self):
|
65
|
+
if FeatureType.OPERATIONS in self.features:
|
66
|
+
completed_operations = [
|
67
|
+
op.operation.operation_id
|
68
|
+
for op in self.dispatcher.completed_operations()
|
69
|
+
]
|
70
|
+
self.features[FeatureType.OPERATIONS][completed_operations, 0] = 1
|
71
|
+
if FeatureType.MACHINES in self.features:
|
72
|
+
num_completed_ops_per_machine = np.zeros(
|
73
|
+
len(self._num_of_operations_per_machine)
|
74
|
+
)
|
75
|
+
for op in self.dispatcher.completed_operations():
|
76
|
+
for machine_id in op.operation.machines:
|
77
|
+
num_completed_ops_per_machine[machine_id] += 1
|
78
|
+
self.features[FeatureType.MACHINES][:, 0] = (
|
79
|
+
num_completed_ops_per_machine
|
80
|
+
== self._num_of_operations_per_machine
|
81
|
+
).astype(np.float32)
|
82
|
+
if FeatureType.JOBS in self.features:
|
83
|
+
num_completed_ops_per_job = np.zeros(
|
84
|
+
len(self._num_of_operations_per_job)
|
85
|
+
)
|
86
|
+
for op in self.dispatcher.completed_operations():
|
87
|
+
num_completed_ops_per_job[op.operation.job_id] += 1
|
88
|
+
self.features[FeatureType.JOBS][:, 0] = (
|
89
|
+
num_completed_ops_per_job
|
90
|
+
== self._num_of_operations_per_job
|
91
|
+
).astype(np.float32)
|
92
|
+
|
93
|
+
def reset(self):
|
94
|
+
self.set_features_to_zero()
|
95
|
+
|
96
|
+
def update(self, scheduled_operation: ScheduledOperation):
|
97
|
+
self.initialize_features()
|
@@ -33,7 +33,7 @@ class DispatchingRuleType(str, Enum):
|
|
33
33
|
|
34
34
|
|
35
35
|
def dispatching_rule_factory(
|
36
|
-
dispatching_rule: Union[str, DispatchingRuleType,
|
36
|
+
dispatching_rule: Union[str, DispatchingRuleType],
|
37
37
|
) -> Callable[[Dispatcher], Operation]:
|
38
38
|
"""Creates and returns a dispatching rule function based on the specified
|
39
39
|
dispatching rule name.
|
@@ -47,8 +47,9 @@ def machine_chooser_factory(
|
|
47
47
|
machine chooser strategy.
|
48
48
|
|
49
49
|
Raises:
|
50
|
-
|
51
|
-
not
|
50
|
+
ValidationError:
|
51
|
+
If the ``machine_chooser`` argument is not recognized or
|
52
|
+
is not supported.
|
52
53
|
"""
|
53
54
|
machine_choosers: Dict[str, Callable[[Dispatcher, Operation], int]] = {
|
54
55
|
MachineChooserType.FIRST: lambda _, operation: operation.machines[0],
|
@@ -38,6 +38,7 @@ from job_shop_lib.graphs._build_resource_task_graphs import (
|
|
38
38
|
add_global_node,
|
39
39
|
add_machine_global_edges,
|
40
40
|
add_job_global_edges,
|
41
|
+
add_job_job_edges,
|
41
42
|
)
|
42
43
|
|
43
44
|
|
@@ -65,4 +66,5 @@ __all__ = [
|
|
65
66
|
"add_machine_global_edges",
|
66
67
|
"add_job_global_edges",
|
67
68
|
"build_solved_disjunctive_graph",
|
69
|
+
"add_job_job_edges",
|
68
70
|
]
|
{job_shop_lib-1.0.0b5 → job_shop_lib-1.0.1}/job_shop_lib/graphs/_build_resource_task_graphs.py
RENAMED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Contains helper functions to build the resource-task graphs from a job shop
|
2
2
|
instance.
|
3
3
|
|
4
|
-
The
|
4
|
+
The resource-task graph (originally called agent-task graph) was introduced by
|
5
5
|
Junyoung Park et al. (2021).
|
6
6
|
In contrast to the disjunctive graph, instead of connecting operations that
|
7
7
|
share the same resources directly by disjunctive edges, operation nodes are
|
@@ -132,15 +132,13 @@ class JobShopGraph:
|
|
132
132
|
|
133
133
|
This method assigns a unique identifier to the node, adds it to the
|
134
134
|
graph, and updates the nodes list and the nodes_by_type dictionary. If
|
135
|
-
the node is of type
|
136
|
-
|
135
|
+
the node is of type :class:`NodeType.OPERATION`, it also updates
|
136
|
+
``nodes_by_job`` and ``nodes_by_machine`` based on the operation's
|
137
|
+
job id and machine ids.
|
137
138
|
|
138
139
|
Args:
|
139
|
-
node_for_adding
|
140
|
-
|
141
|
-
Raises:
|
142
|
-
ValueError: If the node type is unsupported or if required
|
143
|
-
attributes for the node type are missing.
|
140
|
+
node_for_adding:
|
141
|
+
The node to be added to the graph.
|
144
142
|
|
145
143
|
Note:
|
146
144
|
This method directly modifies the graph attribute as well as
|
@@ -171,17 +169,25 @@ class JobShopGraph:
|
|
171
169
|
) -> None:
|
172
170
|
"""Adds an edge to the graph.
|
173
171
|
|
172
|
+
It automatically determines the edge type based on the source and
|
173
|
+
destination nodes unless explicitly provided in the ``attr`` argument
|
174
|
+
via the ``type`` key. The edge type is a tuple of strings:
|
175
|
+
``(source_node_type, "to", destination_node_type)``.
|
176
|
+
|
174
177
|
Args:
|
175
|
-
u_of_edge:
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
178
|
+
u_of_edge:
|
179
|
+
The source node of the edge. If it is a :class:`Node`, its
|
180
|
+
``node_id`` is used as the source. Otherwise, it is assumed to
|
181
|
+
be the ``node_id`` of the source.
|
182
|
+
v_of_edge:
|
183
|
+
The destination node of the edge. If it is a :class:`Node`,
|
184
|
+
its ``node_id`` is used as the destination. Otherwise, it
|
185
|
+
is assumed to be the ``node_id`` of the destination.
|
186
|
+
**attr:
|
187
|
+
Additional attributes to be added to the edge.
|
182
188
|
|
183
189
|
Raises:
|
184
|
-
ValidationError: If
|
190
|
+
ValidationError: If ``u_of_edge`` or ``v_of_edge`` are not in the
|
185
191
|
graph.
|
186
192
|
"""
|
187
193
|
if isinstance(u_of_edge, Node):
|
@@ -192,18 +198,30 @@ class JobShopGraph:
|
|
192
198
|
raise ValidationError(
|
193
199
|
"`u_of_edge` and `v_of_edge` must be in the graph."
|
194
200
|
)
|
195
|
-
|
201
|
+
edge_type = attr.pop("type", None)
|
202
|
+
if edge_type is None:
|
203
|
+
u_node = self.nodes[u_of_edge]
|
204
|
+
v_node = self.nodes[v_of_edge]
|
205
|
+
edge_type = (
|
206
|
+
u_node.node_type.name.lower(),
|
207
|
+
"to",
|
208
|
+
v_node.node_type.name.lower(),
|
209
|
+
)
|
210
|
+
self.graph.add_edge(u_of_edge, v_of_edge, type=edge_type, **attr)
|
196
211
|
|
197
212
|
def remove_node(self, node_id: int) -> None:
|
198
213
|
"""Removes a node from the graph and the isolated nodes that result
|
199
214
|
from the removal.
|
200
215
|
|
201
216
|
Args:
|
202
|
-
node_id:
|
217
|
+
node_id:
|
218
|
+
The id of the node to remove.
|
203
219
|
"""
|
204
220
|
self.graph.remove_node(node_id)
|
205
221
|
self.removed_nodes[node_id] = True
|
206
222
|
|
223
|
+
def remove_isolated_nodes(self) -> None:
|
224
|
+
"""Removes isolated nodes from the graph."""
|
207
225
|
isolated_nodes = list(nx.isolates(self.graph))
|
208
226
|
for isolated_node in isolated_nodes:
|
209
227
|
self.removed_nodes[isolated_node] = True
|
@@ -214,9 +232,10 @@ class JobShopGraph:
|
|
214
232
|
"""Returns whether the node is removed from the graph.
|
215
233
|
|
216
234
|
Args:
|
217
|
-
node:
|
235
|
+
node:
|
236
|
+
The node to check. If it is a ``Node``, its `node_id` is used
|
218
237
|
as the node to check. Otherwise, it is assumed to be the
|
219
|
-
|
238
|
+
``node_id`` of the node to check.
|
220
239
|
"""
|
221
240
|
if isinstance(node, Node):
|
222
241
|
node = node.node_id
|
@@ -7,6 +7,7 @@ Currently, the following classes and utilities are available:
|
|
7
7
|
|
8
8
|
GraphUpdater
|
9
9
|
ResidualGraphUpdater
|
10
|
+
DisjunctiveGraphUpdater
|
10
11
|
remove_completed_operations
|
11
12
|
|
12
13
|
"""
|
@@ -14,10 +15,12 @@ Currently, the following classes and utilities are available:
|
|
14
15
|
from ._graph_updater import GraphUpdater
|
15
16
|
from ._utils import remove_completed_operations
|
16
17
|
from ._residual_graph_updater import ResidualGraphUpdater
|
18
|
+
from ._disjunctive_graph_updater import DisjunctiveGraphUpdater
|
17
19
|
|
18
20
|
|
19
21
|
__all__ = [
|
20
22
|
"GraphUpdater",
|
21
23
|
"remove_completed_operations",
|
22
24
|
"ResidualGraphUpdater",
|
25
|
+
"DisjunctiveGraphUpdater",
|
23
26
|
]
|
@@ -0,0 +1,108 @@
|
|
1
|
+
"""Home of the `ResidualGraphUpdater` class."""
|
2
|
+
|
3
|
+
from job_shop_lib import ScheduledOperation
|
4
|
+
from job_shop_lib.graphs.graph_updaters import (
|
5
|
+
ResidualGraphUpdater,
|
6
|
+
)
|
7
|
+
from job_shop_lib.exceptions import ValidationError
|
8
|
+
|
9
|
+
|
10
|
+
class DisjunctiveGraphUpdater(ResidualGraphUpdater):
|
11
|
+
"""Updates the graph based on the completed operations.
|
12
|
+
|
13
|
+
This observer updates the graph by removing the completed
|
14
|
+
operation, machine and job nodes from the graph. It subscribes to the
|
15
|
+
:class:`~job_shop_lib.dispatching.feature_observers.IsCompletedObserver`
|
16
|
+
to determine which operations, machines and jobs have been completed.
|
17
|
+
|
18
|
+
After an operation is dispatched, one of two disjunctive arcs that
|
19
|
+
connected it with the previous operation is dropped. Similarly, the
|
20
|
+
disjunctive arcs associated with the previous scheduled operation are
|
21
|
+
removed.
|
22
|
+
|
23
|
+
Attributes:
|
24
|
+
remove_completed_machine_nodes:
|
25
|
+
If ``True``, removes completed machine nodes from the graph.
|
26
|
+
remove_completed_job_nodes:
|
27
|
+
If ``True``, removes completed job nodes from the graph.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
dispatcher:
|
31
|
+
The dispatcher instance to observe.
|
32
|
+
job_shop_graph:
|
33
|
+
The job shop graph to update.
|
34
|
+
subscribe:
|
35
|
+
If ``True``, automatically subscribes the observer to the
|
36
|
+
dispatcher. Defaults to ``True``.
|
37
|
+
remove_completed_machine_nodes:
|
38
|
+
If ``True``, removes completed machine nodes from the graph.
|
39
|
+
Defaults to ``True``.
|
40
|
+
remove_completed_job_nodes:
|
41
|
+
If ``True``, removes completed job nodes from the graph.
|
42
|
+
Defaults to ``True``.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def update(self, scheduled_operation: ScheduledOperation) -> None:
|
46
|
+
"""Updates the disjunctive graph.
|
47
|
+
|
48
|
+
After an operation is dispatched, one of two arcs that connected it
|
49
|
+
with the previous operation is dropped. Similarly, the disjunctive
|
50
|
+
arcs associated with the previous scheduled operation are removed.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
scheduled_operation:
|
54
|
+
The scheduled operation that was dispatched.
|
55
|
+
"""
|
56
|
+
super().update(scheduled_operation)
|
57
|
+
machine_schedule = self.dispatcher.schedule.schedule[
|
58
|
+
scheduled_operation.machine_id
|
59
|
+
]
|
60
|
+
if len(machine_schedule) <= 1:
|
61
|
+
return
|
62
|
+
|
63
|
+
previous_scheduled_operation = machine_schedule[-2]
|
64
|
+
|
65
|
+
# Remove the disjunctive arcs between the scheduled operation and the
|
66
|
+
# previous operation
|
67
|
+
scheduled_operation_node = self.job_shop_graph.nodes[
|
68
|
+
scheduled_operation.operation.operation_id
|
69
|
+
]
|
70
|
+
if (
|
71
|
+
scheduled_operation_node.operation
|
72
|
+
is not scheduled_operation.operation
|
73
|
+
):
|
74
|
+
raise ValidationError(
|
75
|
+
"Scheduled operation node does not match scheduled operation."
|
76
|
+
"Make sure that the operation nodes have been the first to be "
|
77
|
+
"added to the graph. This method assumes that the operation id"
|
78
|
+
" and node id are the same."
|
79
|
+
)
|
80
|
+
scheduled_id = scheduled_operation_node.node_id
|
81
|
+
assert scheduled_id == scheduled_operation.operation.operation_id
|
82
|
+
previous_id = previous_scheduled_operation.operation.operation_id
|
83
|
+
if self.job_shop_graph.is_removed(
|
84
|
+
previous_id
|
85
|
+
) or self.job_shop_graph.is_removed(scheduled_id):
|
86
|
+
return
|
87
|
+
self.job_shop_graph.graph.remove_edge(scheduled_id, previous_id)
|
88
|
+
|
89
|
+
# Now, remove all the disjunctive edges between the previous scheduled
|
90
|
+
# operation and the other operations in the machine schedule
|
91
|
+
operations_with_same_machine = (
|
92
|
+
self.dispatcher.instance.operations_by_machine[
|
93
|
+
scheduled_operation.machine_id
|
94
|
+
]
|
95
|
+
)
|
96
|
+
already_scheduled_operations = {
|
97
|
+
scheduled_op.operation.operation_id
|
98
|
+
for scheduled_op in machine_schedule
|
99
|
+
}
|
100
|
+
for operation in operations_with_same_machine:
|
101
|
+
if operation.operation_id in already_scheduled_operations:
|
102
|
+
continue
|
103
|
+
self.job_shop_graph.graph.remove_edge(
|
104
|
+
previous_id, operation.operation_id
|
105
|
+
)
|
106
|
+
self.job_shop_graph.graph.remove_edge(
|
107
|
+
operation.operation_id, previous_id
|
108
|
+
)
|