job-shop-lib 1.0.0a1__py3-none-any.whl → 1.0.0a2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/_operation.py +24 -20
- job_shop_lib/_schedule.py +14 -13
- job_shop_lib/_scheduled_operation.py +9 -11
- job_shop_lib/constraint_programming/_ortools_solver.py +31 -16
- job_shop_lib/dispatching/feature_observers/__init__.py +34 -2
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +54 -14
- job_shop_lib/dispatching/feature_observers/_duration_observer.py +15 -2
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +62 -10
- job_shop_lib/dispatching/feature_observers/_factory.py +5 -1
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +87 -16
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +32 -2
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +2 -2
- job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +9 -5
- job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +7 -2
- job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +1 -1
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +28 -24
- job_shop_lib/graphs/__init__.py +2 -0
- job_shop_lib/graphs/_build_agent_task_graph.py +2 -2
- job_shop_lib/graphs/_constants.py +18 -1
- job_shop_lib/graphs/_job_shop_graph.py +36 -20
- job_shop_lib/graphs/_node.py +60 -52
- job_shop_lib/graphs/graph_updaters/__init__.py +11 -1
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +1 -1
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a2.dist-info}/METADATA +1 -1
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a2.dist-info}/RECORD +27 -27
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a2.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a2.dist-info}/WHEEL +0 -0
@@ -15,8 +15,38 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
15
15
|
|
16
16
|
|
17
17
|
class IsCompletedObserver(FeatureObserver):
|
18
|
-
"""
|
19
|
-
machine, or job has been completed.
|
18
|
+
"""Adds a binary feature indicating whether each operation,
|
19
|
+
machine, or job has been completed.
|
20
|
+
|
21
|
+
An operation is considered completed if it has been scheduled and the
|
22
|
+
current time is greater than or equal to the sum of the operation's start
|
23
|
+
time and duration.
|
24
|
+
|
25
|
+
A machine or job is considered completed if all of its operations have been
|
26
|
+
completed.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
dispatcher:
|
30
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
|
31
|
+
feature_types:
|
32
|
+
A list of :class:`FeatureType` or a single :class:`FeatureType`
|
33
|
+
that specifies the types of features to observe. They must be a
|
34
|
+
subset of the class attribute :attr:`supported_feature_types`.
|
35
|
+
If ``None``, all supported feature types are tracked.
|
36
|
+
subscribe:
|
37
|
+
If ``True``, the observer is subscribed to the dispatcher upon
|
38
|
+
initialization. Otherwise, the observer must be subscribed later
|
39
|
+
or manually updated.
|
40
|
+
"""
|
41
|
+
|
42
|
+
__slots__ = {
|
43
|
+
"remaining_ops_per_machine": (
|
44
|
+
"The number of unscheduled operations per machine."
|
45
|
+
),
|
46
|
+
"remaining_ops_per_job": (
|
47
|
+
"The number of unscheduled operations per job."
|
48
|
+
),
|
49
|
+
}
|
20
50
|
|
21
51
|
def __init__(
|
22
52
|
self,
|
@@ -7,8 +7,8 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
7
7
|
|
8
8
|
|
9
9
|
class IsReadyObserver(FeatureObserver):
|
10
|
-
"""Feature creator that adds a binary feature indicating if the operation
|
11
|
-
is ready to be dispatched."""
|
10
|
+
"""Feature creator that adds a binary feature indicating if the operation,
|
11
|
+
machine or job is ready to be dispatched."""
|
12
12
|
|
13
13
|
def initialize_features(self):
|
14
14
|
self.set_features_to_zero()
|
@@ -8,14 +8,18 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class IsScheduledObserver(FeatureObserver):
|
11
|
-
"""
|
11
|
+
"""Updates features based on scheduling operations.
|
12
12
|
|
13
13
|
This observer tracks which operations have been scheduled and updates
|
14
|
-
feature matrices accordingly.
|
15
|
-
|
16
|
-
|
14
|
+
feature matrices accordingly.
|
15
|
+
|
16
|
+
It updates a feature in the
|
17
|
+
:meth:`FeatureType.OPERATIONS` matrix to indicate that an operation has
|
18
|
+
been scheduled.
|
19
|
+
|
20
|
+
Additionally, it counts the number of uncompleted but
|
17
21
|
scheduled operations for each machine and job, updating the respective
|
18
|
-
|
22
|
+
:meth:`FeatureType.MACHINES` and :meth:`FeatureType.JOBS` feature matrices.
|
19
23
|
"""
|
20
24
|
|
21
25
|
def update(self, scheduled_operation: ScheduledOperation):
|
@@ -8,10 +8,15 @@ from job_shop_lib.dispatching.feature_observers import (
|
|
8
8
|
|
9
9
|
|
10
10
|
class PositionInJobObserver(FeatureObserver):
|
11
|
-
"""
|
11
|
+
"""Adds a feature indicating the position of
|
12
12
|
operations in their respective jobs.
|
13
13
|
|
14
|
-
Positions are adjusted dynamically as operations are scheduled.
|
14
|
+
Positions are adjusted dynamically as operations are scheduled. In other
|
15
|
+
words, the position of an operation is the number of unscheduled operations
|
16
|
+
that precede it in the job.
|
17
|
+
|
18
|
+
It only supports the :meth:`~job_shop_lib.FeatureType.OPERATIONS` feature
|
19
|
+
type.
|
15
20
|
"""
|
16
21
|
|
17
22
|
_supported_feature_types = [FeatureType.OPERATIONS]
|
@@ -12,7 +12,7 @@ class RemainingOperationsObserver(FeatureObserver):
|
|
12
12
|
"""Adds a feature indicating the number of remaining operations for each
|
13
13
|
job and machine.
|
14
14
|
|
15
|
-
It does not support :
|
15
|
+
It does not support :meth:`FeatureType.OPERATIONS`.
|
16
16
|
"""
|
17
17
|
|
18
18
|
_supported_feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
|
@@ -40,7 +40,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
40
40
|
machine_chooser: (
|
41
41
|
str | Callable[[Dispatcher, Operation], int]
|
42
42
|
) = MachineChooserType.FIRST,
|
43
|
-
|
43
|
+
ready_operations_filter: (
|
44
44
|
str
|
45
45
|
| Callable[[Dispatcher, list[Operation]], list[Operation]]
|
46
46
|
| None
|
@@ -60,25 +60,25 @@ class DispatchingRuleSolver(BaseSolver):
|
|
60
60
|
of the machine chooser, a MachineChooser enum member, or a
|
61
61
|
callable that takes a dispatcher and an operation and returns
|
62
62
|
the machine id where the operation will be dispatched.
|
63
|
-
|
64
|
-
The
|
65
|
-
of the pruning function, a PruningFunction enum
|
66
|
-
callable that takes a dispatcher and a list of
|
67
|
-
returns a list of operations that should be
|
68
|
-
dispatching.
|
63
|
+
ready_operations_filter:
|
64
|
+
The ready operations filter to use. It can be a string with
|
65
|
+
the name of the pruning function, a PruningFunction enum
|
66
|
+
member, or a callable that takes a dispatcher and a list of
|
67
|
+
operations and returns a list of operations that should be
|
68
|
+
considered for dispatching.
|
69
69
|
"""
|
70
70
|
if isinstance(dispatching_rule, str):
|
71
71
|
dispatching_rule = dispatching_rule_factory(dispatching_rule)
|
72
72
|
if isinstance(machine_chooser, str):
|
73
73
|
machine_chooser = machine_chooser_factory(machine_chooser)
|
74
|
-
if isinstance(
|
75
|
-
|
76
|
-
|
74
|
+
if isinstance(ready_operations_filter, str):
|
75
|
+
ready_operations_filter = ready_operations_filter_factory(
|
76
|
+
ready_operations_filter
|
77
77
|
)
|
78
78
|
|
79
79
|
self.dispatching_rule = dispatching_rule
|
80
80
|
self.machine_chooser = machine_chooser
|
81
|
-
self.
|
81
|
+
self.ready_operations_filter = ready_operations_filter
|
82
82
|
|
83
83
|
def solve(
|
84
84
|
self, instance: JobShopInstance, dispatcher: Dispatcher | None = None
|
@@ -87,7 +87,7 @@ class DispatchingRuleSolver(BaseSolver):
|
|
87
87
|
dispatching rule algorithm."""
|
88
88
|
if dispatcher is None:
|
89
89
|
dispatcher = Dispatcher(
|
90
|
-
instance, ready_operations_filter=self.
|
90
|
+
instance, ready_operations_filter=self.ready_operations_filter
|
91
91
|
)
|
92
92
|
while not dispatcher.schedule.is_complete():
|
93
93
|
self.step(dispatcher)
|
@@ -110,19 +110,23 @@ class DispatchingRuleSolver(BaseSolver):
|
|
110
110
|
if __name__ == "__main__":
|
111
111
|
import time
|
112
112
|
import cProfile
|
113
|
-
import pstats
|
114
|
-
from io import StringIO
|
115
|
-
from job_shop_lib.benchmarking import
|
113
|
+
# import pstats
|
114
|
+
# from io import StringIO
|
115
|
+
from job_shop_lib.benchmarking import (
|
116
|
+
# load_benchmark_instance,
|
117
|
+
load_all_benchmark_instances,
|
118
|
+
)
|
116
119
|
|
117
120
|
# from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
|
118
121
|
# most_work_remaining_rule_2,
|
119
122
|
# )
|
120
123
|
|
121
|
-
ta_instances = [
|
122
|
-
|
123
|
-
]
|
124
|
+
# ta_instances = [
|
125
|
+
# load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
|
126
|
+
# ]
|
127
|
+
ta_instances = load_all_benchmark_instances().values()
|
124
128
|
solver = DispatchingRuleSolver(
|
125
|
-
dispatching_rule="most_work_remaining",
|
129
|
+
dispatching_rule="most_work_remaining", ready_operations_filter=None
|
126
130
|
)
|
127
131
|
|
128
132
|
start = time.perf_counter()
|
@@ -131,10 +135,10 @@ if __name__ == "__main__":
|
|
131
135
|
profiler = cProfile.Profile()
|
132
136
|
|
133
137
|
# Run the code under profiling
|
134
|
-
profiler.enable()
|
138
|
+
# profiler.enable()
|
135
139
|
for instance_ in ta_instances:
|
136
140
|
solver.solve(instance_)
|
137
|
-
profiler.disable()
|
141
|
+
# profiler.disable()
|
138
142
|
|
139
143
|
end = time.perf_counter()
|
140
144
|
|
@@ -142,7 +146,7 @@ if __name__ == "__main__":
|
|
142
146
|
print(f"Elapsed time: {end - start:.2f} seconds.")
|
143
147
|
|
144
148
|
# Print profiling results
|
145
|
-
s = StringIO()
|
146
|
-
ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
147
|
-
profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
149
|
+
# s = StringIO()
|
150
|
+
# ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
151
|
+
# profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
148
152
|
# print(s.getvalue())
|
job_shop_lib/graphs/__init__.py
CHANGED
@@ -13,8 +13,8 @@ job, and the global node is connected to all machine and job nodes.
|
|
13
13
|
|
14
14
|
References:
|
15
15
|
- Junyoung Park, Sanjar Bakhtiyar, and Jinkyoo Park. Schedulenet: Learn to
|
16
|
-
solve multi-agent scheduling problems with reinforcement learning. ArXiv,
|
17
|
-
abs/2106.03051, 2021.
|
16
|
+
solve multi-agent scheduling problems with reinforcement learning. ArXiv,
|
17
|
+
abs/2106.03051, 2021.
|
18
18
|
"""
|
19
19
|
|
20
20
|
import itertools
|
@@ -11,7 +11,24 @@ class EdgeType(enum.Enum):
|
|
11
11
|
|
12
12
|
|
13
13
|
class NodeType(enum.Enum):
|
14
|
-
"""Enumeration of node types.
|
14
|
+
"""Enumeration of node types.
|
15
|
+
|
16
|
+
Nodes of type :class:`NodeType.OPERATION`, :class:`NodeType.MACHINE`, and
|
17
|
+
:class:`NodeType.JOB` have specific attributes associated with them in the
|
18
|
+
:class:`Node` class.
|
19
|
+
|
20
|
+
On the other hand, Nodes of type :class:`NodeType.GLOBAL`,
|
21
|
+
:class:`NodeType.SOURCE`, and :class:`NodeType.SINK` are used to represent
|
22
|
+
different concepts in the graph and accesing them, but they do not have
|
23
|
+
specific attributes associated with them.
|
24
|
+
|
25
|
+
.. tip::
|
26
|
+
|
27
|
+
While uncommon, it can be useful to extend this enumeration with
|
28
|
+
additional node types. This can be achieved using
|
29
|
+
`aenum <https://github.com/ethanfurman/aenum>`_'s
|
30
|
+
``extend_enum`` (requires using Python 3.11+).
|
31
|
+
"""
|
15
32
|
|
16
33
|
OPERATION = enum.auto()
|
17
34
|
MACHINE = enum.auto()
|
@@ -13,10 +13,10 @@ NODE_ATTR = "node"
|
|
13
13
|
|
14
14
|
# pylint: disable=too-many-instance-attributes
|
15
15
|
class JobShopGraph:
|
16
|
-
"""
|
16
|
+
"""Represents a :class:`JobShopInstance` as a heterogeneous directed graph.
|
17
17
|
|
18
18
|
Provides a comprehensive graph-based representation of a job shop
|
19
|
-
scheduling problem, utilizing the
|
19
|
+
scheduling problem, utilizing the ``networkx`` library to model the complex
|
20
20
|
relationships between jobs, operations, and machines. This class transforms
|
21
21
|
the abstract scheduling problem into a directed graph, where various
|
22
22
|
entities (jobs, machines, and operations) are nodes, and the dependencies
|
@@ -25,25 +25,40 @@ class JobShopGraph:
|
|
25
25
|
This transformation allows for the application of graph algorithms
|
26
26
|
to analyze and solve scheduling problems.
|
27
27
|
|
28
|
-
|
28
|
+
Args:
|
29
29
|
instance:
|
30
|
-
The job shop instance
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
The job shop instance that the graph represents.
|
31
|
+
add_operation_nodes:
|
32
|
+
Whether to add nodes of type :class:`NodeType.OPERATION` to the
|
33
|
+
to the graph. If set to ``False``, the graph will be empty, and
|
34
|
+
operation nodes will need to be added manually.
|
35
35
|
"""
|
36
36
|
|
37
|
-
|
38
|
-
"""
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
""
|
37
|
+
__slots__ = {
|
38
|
+
"instance": "The job shop instance that the graph represents.",
|
39
|
+
"graph": (
|
40
|
+
"The directed graph representing the job shop, where nodes are "
|
41
|
+
"operations, machines, jobs, or abstract concepts like global, "
|
42
|
+
"source, and sink, with edges indicating dependencies."
|
43
|
+
),
|
44
|
+
"_nodes": "List of all nodes added to the graph.",
|
45
|
+
"_nodes_by_type": "Dictionary mapping node types to lists of nodes.",
|
46
|
+
"_nodes_by_machine": (
|
47
|
+
"List of lists mapping machine ids to operation nodes."
|
48
|
+
),
|
49
|
+
"_nodes_by_job": "List of lists mapping job ids to operation nodes.",
|
50
|
+
"_next_node_id": (
|
51
|
+
"The id to assign to the next node added to thegraph."
|
52
|
+
),
|
53
|
+
"removed_nodes": (
|
54
|
+
"List of boolean values indicating whether a node has been "
|
55
|
+
"removed from the graph."
|
56
|
+
),
|
57
|
+
}
|
58
|
+
|
59
|
+
def __init__(
|
60
|
+
self, instance: JobShopInstance, add_operation_nodes: bool = True
|
61
|
+
):
|
47
62
|
self.graph = nx.DiGraph()
|
48
63
|
self.instance = instance
|
49
64
|
|
@@ -59,7 +74,8 @@ class JobShopGraph:
|
|
59
74
|
]
|
60
75
|
self._next_node_id = 0
|
61
76
|
self.removed_nodes: list[bool] = []
|
62
|
-
|
77
|
+
if add_operation_nodes:
|
78
|
+
self.add_operation_nodes()
|
63
79
|
|
64
80
|
@property
|
65
81
|
def nodes(self) -> list[Node]:
|
@@ -103,7 +119,7 @@ class JobShopGraph:
|
|
103
119
|
"""Number of job nodes in the graph."""
|
104
120
|
return len(self._nodes_by_type[NodeType.JOB])
|
105
121
|
|
106
|
-
def
|
122
|
+
def add_operation_nodes(self) -> None:
|
107
123
|
"""Adds operation nodes to the graph."""
|
108
124
|
for job in self.instance.jobs:
|
109
125
|
for operation in job:
|
job_shop_lib/graphs/_node.py
CHANGED
@@ -9,7 +9,7 @@ from job_shop_lib.graphs._constants import NodeType
|
|
9
9
|
|
10
10
|
|
11
11
|
class Node:
|
12
|
-
"""
|
12
|
+
"""Represents a node in the :class:`JobShopGraph`.
|
13
13
|
|
14
14
|
A node is hashable by its id. The id is assigned when the node is added to
|
15
15
|
the graph. The id must be unique for each node in the graph, and should be
|
@@ -18,11 +18,15 @@ class Node:
|
|
18
18
|
Depending on the type of the node, it can have different attributes. The
|
19
19
|
following table shows the attributes of each type of node:
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
+----------------+---------------------+
|
22
|
+
| Node Type | Required Attribute |
|
23
|
+
+================+=====================+
|
24
|
+
| OPERATION | ``operation`` |
|
25
|
+
+----------------+---------------------+
|
26
|
+
| MACHINE | ``machine_id`` |
|
27
|
+
+----------------+---------------------+
|
28
|
+
| JOB | ``job_id`` |
|
29
|
+
+----------------+---------------------+
|
26
30
|
|
27
31
|
In terms of equality, two nodes are equal if they have the same id.
|
28
32
|
Additionally, one node is equal to an integer if the integer is equal to
|
@@ -31,25 +35,47 @@ class Node:
|
|
31
35
|
This allows for using the node as a key in a dictionary, at the same time
|
32
36
|
we can use its id to index that dictionary. Example:
|
33
37
|
|
34
|
-
|
35
|
-
node = Node(NodeType.SOURCE)
|
36
|
-
node.node_id = 1
|
37
|
-
graph = {node: "some value"}
|
38
|
-
print(graph[node]) # "some value"
|
39
|
-
print(graph[1]) # "some value"
|
40
|
-
```
|
38
|
+
.. code-block:: python
|
41
39
|
|
42
|
-
|
40
|
+
node = Node(NodeType.SOURCE)
|
41
|
+
node.node_id = 1
|
42
|
+
graph = {node: "some value"}
|
43
|
+
print(graph[node]) # "some value"
|
44
|
+
print(graph[1]) # "some value"
|
45
|
+
|
46
|
+
Args:
|
43
47
|
node_type:
|
44
|
-
The type of the node.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
The type of the node. See :class:`NodeType` for theavailable types.
|
49
|
+
operation:
|
50
|
+
The operation of the node. Required if ``node_type`` is
|
51
|
+
:attr:`NodeType.OPERATION`.
|
52
|
+
machine_id:
|
53
|
+
The id of the machine. Required if ``node_type`` is
|
54
|
+
:attr:`NodeType.MACHINE`.
|
55
|
+
job_id:
|
56
|
+
The id of the job. Required if ``node_type`` is
|
57
|
+
:attr:`NodeType.JOB`.
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
ValidationError:
|
61
|
+
If the ``node_type`` is :attr:`NodeType.OPERATION`,
|
62
|
+
:attr:`NodeType.MACHINE`, or :attr:`NodeType.JOB` and the
|
63
|
+
corresponding ``operation``, ``machine_id``, or ``job_id`` is
|
64
|
+
``None``, respectively.
|
65
|
+
|
50
66
|
"""
|
51
67
|
|
52
|
-
__slots__ =
|
68
|
+
__slots__ = {
|
69
|
+
"node_type": "The type of the node.",
|
70
|
+
"_node_id": "Unique identifier for the node.",
|
71
|
+
"_operation": (
|
72
|
+
"The operation associated with the node."
|
73
|
+
),
|
74
|
+
"_machine_id": (
|
75
|
+
"The machine ID associated with the node."
|
76
|
+
),
|
77
|
+
"_job_id": "The job ID associated with the node.",
|
78
|
+
}
|
53
79
|
|
54
80
|
def __init__(
|
55
81
|
self,
|
@@ -58,34 +84,6 @@ class Node:
|
|
58
84
|
machine_id: int | None = None,
|
59
85
|
job_id: int | None = None,
|
60
86
|
):
|
61
|
-
"""Initializes the node with the given attributes.
|
62
|
-
|
63
|
-
Args:
|
64
|
-
node_type:
|
65
|
-
The type of the node. It can be one of the following:
|
66
|
-
- NodeType.OPERATION
|
67
|
-
- NodeType.MACHINE
|
68
|
-
- NodeType.JOB
|
69
|
-
- NodeType.GLOBAL
|
70
|
-
...
|
71
|
-
operation:
|
72
|
-
The operation of the node. It should be provided if the
|
73
|
-
`node_type` is NodeType.OPERATION.
|
74
|
-
machine_id:
|
75
|
-
The id of the machine of the node. It should be provided if the
|
76
|
-
node_type is NodeType.MACHINE.
|
77
|
-
job_id:
|
78
|
-
The id of the job of the node. It should be provided if the
|
79
|
-
node_type is NodeType.JOB.
|
80
|
-
|
81
|
-
Raises:
|
82
|
-
ValidationError:
|
83
|
-
If the node_type is OPERATION and operation is None.
|
84
|
-
ValidationError:
|
85
|
-
If the node_type is MACHINE and machine_id is None.
|
86
|
-
ValidationError:
|
87
|
-
If the node_type is JOB and job_id is None.
|
88
|
-
"""
|
89
87
|
if node_type == NodeType.OPERATION and operation is None:
|
90
88
|
raise ValidationError("Operation node must have an operation.")
|
91
89
|
|
@@ -95,7 +93,7 @@ class Node:
|
|
95
93
|
if node_type == NodeType.JOB and job_id is None:
|
96
94
|
raise ValidationError("Job node must have a job_id.")
|
97
95
|
|
98
|
-
self.node_type = node_type
|
96
|
+
self.node_type: NodeType = node_type
|
99
97
|
self._node_id: int | None = None
|
100
98
|
|
101
99
|
self._operation = operation
|
@@ -119,7 +117,11 @@ class Node:
|
|
119
117
|
def operation(self) -> Operation:
|
120
118
|
"""Returns the operation of the node.
|
121
119
|
|
122
|
-
This property is mandatory for nodes of type
|
120
|
+
This property is mandatory for nodes of type
|
121
|
+
:class:`NodeType.OPERATION`.
|
122
|
+
|
123
|
+
Raises:
|
124
|
+
UninitializedAttributeError: If the node has no operation.
|
123
125
|
"""
|
124
126
|
if self._operation is None:
|
125
127
|
raise UninitializedAttributeError("Node has no operation.")
|
@@ -130,9 +132,12 @@ class Node:
|
|
130
132
|
"""Returns the `machine_id` of the node.
|
131
133
|
|
132
134
|
This property is mandatory for nodes of type `MACHINE`.
|
135
|
+
|
136
|
+
Raises:
|
137
|
+
UninitializedAttributeError: If the node has no ``machine_id``.
|
133
138
|
"""
|
134
139
|
if self._machine_id is None:
|
135
|
-
raise UninitializedAttributeError("Node has no
|
140
|
+
raise UninitializedAttributeError("Node has no ``machine_id``.")
|
136
141
|
return self._machine_id
|
137
142
|
|
138
143
|
@property
|
@@ -140,6 +145,9 @@ class Node:
|
|
140
145
|
"""Returns the `job_id` of the node.
|
141
146
|
|
142
147
|
This property is mandatory for nodes of type `JOB`.
|
148
|
+
|
149
|
+
Raises:
|
150
|
+
UninitializedAttributeError: If the node has no `job_id`.
|
143
151
|
"""
|
144
152
|
if self._job_id is None:
|
145
153
|
raise UninitializedAttributeError("Node has no `job_id`.")
|
@@ -1,5 +1,15 @@
|
|
1
1
|
"""Contains classes and functions for updating the graph representation of the
|
2
|
-
job shop scheduling problem.
|
2
|
+
job shop scheduling problem.
|
3
|
+
|
4
|
+
Currently, the following classes and utilities are available:
|
5
|
+
|
6
|
+
.. autosummary::
|
7
|
+
|
8
|
+
GraphUpdater
|
9
|
+
ResidualGraphUpdater
|
10
|
+
remove_completed_operations
|
11
|
+
|
12
|
+
"""
|
3
13
|
|
4
14
|
from ._graph_updater import GraphUpdater
|
5
15
|
from ._utils import remove_completed_operations
|
@@ -263,7 +263,7 @@ def create_gantt_chart_frames(
|
|
263
263
|
"""
|
264
264
|
if solver is not None and schedule_history is None:
|
265
265
|
dispatcher = Dispatcher(
|
266
|
-
instance, ready_operations_filter=solver.
|
266
|
+
instance, ready_operations_filter=solver.ready_operations_filter
|
267
267
|
)
|
268
268
|
history_tracker = HistoryObserver(dispatcher)
|
269
269
|
makespan = solver.solve(instance, dispatcher).makespan()
|
@@ -1,14 +1,14 @@
|
|
1
1
|
job_shop_lib/__init__.py,sha256=Ci5ipn-zciO88C5aX5Wx-UN8iBTbpde3dSSg02ZcfwM,597
|
2
2
|
job_shop_lib/_base_solver.py,sha256=p17XmtufNc9Y481cqZUT45pEkUmmW1HWG53dfhIBJH8,1363
|
3
3
|
job_shop_lib/_job_shop_instance.py,sha256=Q0ml3C36tmcBskBo8MyaZWILJPbdvHjJcvXzD_YfLsU,16475
|
4
|
-
job_shop_lib/_operation.py,sha256=
|
5
|
-
job_shop_lib/_schedule.py,sha256=
|
6
|
-
job_shop_lib/_scheduled_operation.py,sha256=
|
4
|
+
job_shop_lib/_operation.py,sha256=lJ4srQs82cYhUYuBxqkfKr9MuZoG0X2x0z3M1_QXkAs,3360
|
5
|
+
job_shop_lib/_schedule.py,sha256=H6PREUE9KCC0DtrQV8y9MB8S4sh3qnDwby5Y4WVDfzg,11333
|
6
|
+
job_shop_lib/_scheduled_operation.py,sha256=ptqYRW39EvG5tnk4DLQGFCOkbGTTYIQJhUMcuLSbdSA,2812
|
7
7
|
job_shop_lib/benchmarking/__init__.py,sha256=BYCrJUNr_uk2c0xIbDt07OnUMhQx8Dudkukx3TFWxgw,3271
|
8
8
|
job_shop_lib/benchmarking/_load_benchmark.py,sha256=-cgyx0Kn6uAc3KdGFSQb6eUVQjQggmpVKOH9qusNkXI,2930
|
9
9
|
job_shop_lib/benchmarking/benchmark_instances.json,sha256=F9EvyzFwVxiKAN6rQTsrMhsKstmyUmroyWduM7a00KQ,464841
|
10
10
|
job_shop_lib/constraint_programming/__init__.py,sha256=kKQRUxxS_nVFUdXGnf4bQOD9mqrXxZZWElS753A4YiA,454
|
11
|
-
job_shop_lib/constraint_programming/_ortools_solver.py,sha256=
|
11
|
+
job_shop_lib/constraint_programming/_ortools_solver.py,sha256=gRoImEBUa8_io-TzbSS-3f0CJ7UwacU5Lrz0bsDqibo,10462
|
12
12
|
job_shop_lib/dispatching/__init__.py,sha256=QV7qy-y0sSoKp_FslTm7sdqczYzpq0YctzKQ36l0ykg,1510
|
13
13
|
job_shop_lib/dispatching/_dispatcher.py,sha256=PCSBpYAF6QPXWrjwkBQXTxOdGdq6Y1Uqw8esQTW05TQ,21357
|
14
14
|
job_shop_lib/dispatching/_dispatcher_observer_config.py,sha256=l_lbaw9JJ5icVOmDAzAL6G5t6wG25bQLpRedN1bys8c,1932
|
@@ -16,20 +16,20 @@ job_shop_lib/dispatching/_factories.py,sha256=UAZLq7d_-puzMYteiAbbhkcW5ucKO-lo3b
|
|
16
16
|
job_shop_lib/dispatching/_history_observer.py,sha256=Vl8rQaxekUeEB-AyNxyC3c76zQakeh-rdri2iDnZvXw,610
|
17
17
|
job_shop_lib/dispatching/_ready_operation_filters.py,sha256=q8Xv4kp_2GsvEMC5mlTuJXivAz_b8bbrqo5sXaS3PJU,3110
|
18
18
|
job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=LNEzqOWqEf6fvtkQrDmDWFEhCfA75OgEtzdomzbxYII,2683
|
19
|
-
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=
|
20
|
-
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=
|
21
|
-
job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=
|
22
|
-
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=
|
23
|
-
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=
|
24
|
-
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=
|
25
|
-
job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=
|
26
|
-
job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=
|
27
|
-
job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py,sha256=
|
28
|
-
job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py,sha256=
|
29
|
-
job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha256=
|
19
|
+
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=EuJLvSpJpoXUK8A4UuC2k6Mpa293ZR3oCnnvYivIBtU,2240
|
20
|
+
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=DOS961MtWaDk2gxjOLA_75SyT6Nmn3IKuNtYO8odk8s,7938
|
21
|
+
job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=fbkUIVScF1iNjdVCYr1ImQm53TfahvVnGXhsRAsgdzY,4129
|
22
|
+
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=SOdXs-uzTzcLqOsmpbKvf-OGlGXOMVVJL9zgVVVDvF8,11442
|
23
|
+
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=b5YyzdnorijtWUNrYWs4sf-G17eDxw8oYrol1rzMN1Q,2919
|
24
|
+
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=crbqG1KrmUOfG4z7shHNzhUg7-uSP4_RWxyOi-RRWmE,8635
|
25
|
+
job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=wKmlASLjodztAB2ypTsi0XFLZ3h1ltzvsa9BpPrbksU,4581
|
26
|
+
job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=KYDUO3Zz1wgkClZ64i5-az6W-SFpi8rckAlv4Zjcii4,1260
|
27
|
+
job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py,sha256=OcuMUB9_By6ZMtX-1_3z-xaxGbP85a5Zv0ywAv7XxWQ,1491
|
28
|
+
job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py,sha256=WRknpQBKXs6h6cXLFJW7ZCvjtU8CPL-iXXNPw3g-mLE,1303
|
29
|
+
job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha256=5V87lCrJUabEe8AkTGXPu5yS8OGxeN8L3-xNyHmdmLs,1441
|
30
30
|
job_shop_lib/dispatching/rules/__init__.py,sha256=p1rkqf66L62uvAOM1ZxNV8xHoh7SuYjHi_8ZNBvPIjg,1450
|
31
31
|
job_shop_lib/dispatching/rules/_dispatching_rule_factory.py,sha256=5fNpv90fAoR6rcE6NeJOWiB7ir-FVnoONIhHtKJ9H0E,2904
|
32
|
-
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=
|
32
|
+
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=Nb3EPSIdnbeqKaIf5ufE2zxQsNpNkZxmYa0Eh1jnCnw,5537
|
33
33
|
job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=Wb9fQIfePvCJi4RqJ59UrRSnYufgQw5nQ_Am8M6-JOI,7569
|
34
34
|
job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=xsJ8nJwPDBi-sfLJRQF_BBQDbyXDfopD1U-efXffQAE,2331
|
35
35
|
job_shop_lib/dispatching/rules/_utils.py,sha256=X8vET2p1D3RyoB9mFfsfRgmilcTmxPssKYyJQ2zEt0Q,4605
|
@@ -38,13 +38,13 @@ job_shop_lib/generation/__init__.py,sha256=hUqjnE0bEoknuUwFoLUWjBH26qTTCGsJAW4gs
|
|
38
38
|
job_shop_lib/generation/_general_instance_generator.py,sha256=8DG70qT2TUTyPp-3Q1DHWo3DhtUvyB4Yo_u0eAa5CIc,7431
|
39
39
|
job_shop_lib/generation/_instance_generator.py,sha256=fPcbNoyk0t1JtJpBMiwk3SlyPkWYNkYS7-Vs8qH_eDM,4642
|
40
40
|
job_shop_lib/generation/_transformations.py,sha256=FI2qHrETATJUrQP3-RYhZAQ5boyEZ0CF2StDbacBej8,5290
|
41
|
-
job_shop_lib/graphs/__init__.py,sha256=
|
42
|
-
job_shop_lib/graphs/_build_agent_task_graph.py,sha256=
|
41
|
+
job_shop_lib/graphs/__init__.py,sha256=ALZNmvCi5XveMneVJ2VQpQ5msYwIqFWFloiwyFtJhAo,1709
|
42
|
+
job_shop_lib/graphs/_build_agent_task_graph.py,sha256=6mvWJ7fFD5CmxkTuXEwY7f_-qxjKdNgFmWk4a4mgiD8,7132
|
43
43
|
job_shop_lib/graphs/_build_disjunctive_graph.py,sha256=z1jiuTTaWPJZj-vSZdo064quGx4LEDKjtZIb1FieZW4,3705
|
44
|
-
job_shop_lib/graphs/_constants.py,sha256=
|
45
|
-
job_shop_lib/graphs/_job_shop_graph.py,sha256=
|
46
|
-
job_shop_lib/graphs/_node.py,sha256=
|
47
|
-
job_shop_lib/graphs/graph_updaters/__init__.py,sha256=
|
44
|
+
job_shop_lib/graphs/_constants.py,sha256=K-GeVvh_DTWpo1KOX1clmxWS_pkUJbq19yOBmrCVIxI,1086
|
45
|
+
job_shop_lib/graphs/_job_shop_graph.py,sha256=Fv0TOwtmjqdhH-A_TBH0wSzQkGgqTyc7vvEcfzbQiwA,10681
|
46
|
+
job_shop_lib/graphs/_node.py,sha256=hGgdnD9wlsTbkaDizFZMsxPXa2-m91iBNLu0vtkVbxw,6034
|
47
|
+
job_shop_lib/graphs/graph_updaters/__init__.py,sha256=UhnZL55e3cAv7hVetB6bRmIOn8BDhG2bsbrdRoHtxLY,516
|
48
48
|
job_shop_lib/graphs/graph_updaters/_graph_updater.py,sha256=H8PtBj4gv6y5wQKOstF2CSnLsFjO1YeVHpzvYK3vMRM,2053
|
49
49
|
job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py,sha256=kPuBmion70-GAQsyFal8gHylHvZSoBJae9eF8iGOkvA,6097
|
50
50
|
job_shop_lib/graphs/graph_updaters/_utils.py,sha256=X5YfwJA1CCgpm1r9C036Gal2CkDh2SSak7wl7TbdjHw,704
|
@@ -59,8 +59,8 @@ job_shop_lib/visualization/_agent_task_graph.py,sha256=AaBTD_S34WjrsZnL_iMAplR_f
|
|
59
59
|
job_shop_lib/visualization/_disjunctive_graph.py,sha256=pg4KG9BfQbnBPnXYgbyPGe0AuHSmhYqPeqWYAf_spWQ,5905
|
60
60
|
job_shop_lib/visualization/_gantt_chart.py,sha256=B9sn4XrEUqgQhRKju-1VUG5R67AZXRu7jbrtA8VcndU,4412
|
61
61
|
job_shop_lib/visualization/_gantt_chart_creator.py,sha256=qFhCfk3oC3uF7Mau3lrNhH-34sfHXvkqEXbsDzrIbBk,7721
|
62
|
-
job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py,sha256=
|
63
|
-
job_shop_lib-1.0.
|
64
|
-
job_shop_lib-1.0.
|
65
|
-
job_shop_lib-1.0.
|
66
|
-
job_shop_lib-1.0.
|
62
|
+
job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py,sha256=GQhQF2YzSRQT_DdUjJYHFKMZMjk_hPrYjQU5PpedNvs,13213
|
63
|
+
job_shop_lib-1.0.0a2.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
64
|
+
job_shop_lib-1.0.0a2.dist-info/METADATA,sha256=IVDNFeNtFCpjUzIGXkEn0uQ3aBgJLSLXHuJKEgpBajI,14810
|
65
|
+
job_shop_lib-1.0.0a2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
66
|
+
job_shop_lib-1.0.0a2.dist-info/RECORD,,
|