job-shop-lib 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/dispatching/dispatcher.py +8 -15
- job_shop_lib/operation.py +4 -4
- job_shop_lib/schedule.py +96 -1
- job_shop_lib/scheduled_operation.py +17 -5
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.4.0.dist-info}/METADATA +1 -1
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.4.0.dist-info}/RECORD +8 -8
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.4.0.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.3.0.dist-info → job_shop_lib-0.4.0.dist-info}/WHEEL +0 -0
@@ -7,6 +7,7 @@ from typing import Any
|
|
7
7
|
from collections.abc import Callable
|
8
8
|
from collections import deque
|
9
9
|
from functools import wraps
|
10
|
+
from warnings import warn
|
10
11
|
|
11
12
|
from job_shop_lib import (
|
12
13
|
JobShopInstance,
|
@@ -155,21 +156,13 @@ class Dispatcher:
|
|
155
156
|
def create_schedule_from_raw_solution(
|
156
157
|
cls, instance: JobShopInstance, raw_solution: list[list[Operation]]
|
157
158
|
) -> Schedule:
|
158
|
-
"""
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
The instance of the job shop problem to be solved.
|
166
|
-
raw_solution:
|
167
|
-
A list of lists of operations, where each list represents the
|
168
|
-
order of operations for a machine.
|
169
|
-
|
170
|
-
Returns:
|
171
|
-
A Schedule object representing the solution.
|
172
|
-
"""
|
159
|
+
"""Deprecated method, use `Schedule.from_job_sequences` instead."""
|
160
|
+
warn(
|
161
|
+
"Dispatcher.create_schedule_from_raw_solution is deprecated. "
|
162
|
+
"Use Schedule.from_job_sequences instead. It will be removed in "
|
163
|
+
"version 1.0.0.",
|
164
|
+
DeprecationWarning,
|
165
|
+
)
|
173
166
|
dispatcher = cls(instance)
|
174
167
|
dispatcher.reset()
|
175
168
|
raw_solution_deques = [
|
job_shop_lib/operation.py
CHANGED
@@ -107,10 +107,10 @@ class Operation:
|
|
107
107
|
def __hash__(self) -> int:
|
108
108
|
return hash(self.operation_id)
|
109
109
|
|
110
|
-
def __eq__(self,
|
111
|
-
if isinstance(
|
112
|
-
return
|
113
|
-
return
|
110
|
+
def __eq__(self, value: object) -> bool:
|
111
|
+
if not isinstance(value, Operation):
|
112
|
+
return False
|
113
|
+
return self.__slots__ == value.__slots__
|
114
114
|
|
115
115
|
def __repr__(self) -> str:
|
116
116
|
machines = (
|
job_shop_lib/schedule.py
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
"""Home of the `Schedule` class."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import Any
|
6
|
+
from collections import deque
|
7
|
+
|
8
|
+
from job_shop_lib import ScheduledOperation, JobShopInstance, JobShopLibError
|
4
9
|
|
5
10
|
|
6
11
|
class Schedule:
|
@@ -71,6 +76,96 @@ class Schedule:
|
|
71
76
|
"""Returns the number of operations that have been scheduled."""
|
72
77
|
return sum(len(machine_schedule) for machine_schedule in self.schedule)
|
73
78
|
|
79
|
+
def to_dict(self) -> dict:
|
80
|
+
"""Returns a dictionary representation of the schedule.
|
81
|
+
|
82
|
+
This representation is useful for saving the instance to a JSON file.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
A dictionary representation of the schedule with the following
|
86
|
+
keys:
|
87
|
+
- "instance": A dictionary representation of the instance.
|
88
|
+
- "job_sequences": A list of lists of job ids. Each list of job
|
89
|
+
ids represents the order of operations on the machine. The
|
90
|
+
machine that the list corresponds to is determined by the
|
91
|
+
index of the list.
|
92
|
+
- "metadata": A dictionary with additional information about
|
93
|
+
the schedule.
|
94
|
+
"""
|
95
|
+
job_sequences: list[list[int]] = []
|
96
|
+
for machine_schedule in self.schedule:
|
97
|
+
job_sequences.append(
|
98
|
+
[operation.job_id for operation in machine_schedule]
|
99
|
+
)
|
100
|
+
|
101
|
+
return {
|
102
|
+
"instance": self.instance.to_dict(),
|
103
|
+
"job_sequences": job_sequences,
|
104
|
+
"metadata": self.metadata,
|
105
|
+
}
|
106
|
+
|
107
|
+
@staticmethod
|
108
|
+
def from_dict(
|
109
|
+
instance: dict[str, Any] | JobShopInstance,
|
110
|
+
job_sequences: list[list[int]],
|
111
|
+
metadata: dict[str, Any] | None = None,
|
112
|
+
) -> Schedule:
|
113
|
+
"""Creates a schedule from a dictionary representation."""
|
114
|
+
if isinstance(instance, dict):
|
115
|
+
instance = JobShopInstance.from_matrices(**instance)
|
116
|
+
schedule = Schedule.from_job_sequences(instance, job_sequences)
|
117
|
+
schedule.metadata = metadata if metadata is not None else {}
|
118
|
+
return schedule
|
119
|
+
|
120
|
+
@staticmethod
|
121
|
+
def from_job_sequences(
|
122
|
+
instance: JobShopInstance,
|
123
|
+
job_sequences: list[list[int]],
|
124
|
+
) -> Schedule:
|
125
|
+
"""Creates an active schedule from a list of job sequences.
|
126
|
+
|
127
|
+
An active schedule is the optimal schedule for the given job sequences.
|
128
|
+
In other words, it is not possible to construct another schedule,
|
129
|
+
through changes in the order of processing on the machines, with at
|
130
|
+
least one operation finishing earlier and no operation finishing later.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
instance:
|
134
|
+
The `JobShopInstance` object that the schedule is for.
|
135
|
+
job_sequences:
|
136
|
+
A list of lists of job ids. Each list of job ids represents the
|
137
|
+
order of operations on the machine. The machine that the list
|
138
|
+
corresponds to is determined by the index of the list.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
A `Schedule` object with the given job sequences.
|
142
|
+
"""
|
143
|
+
from job_shop_lib.dispatching import Dispatcher
|
144
|
+
|
145
|
+
dispatcher = Dispatcher(instance)
|
146
|
+
dispatcher.reset()
|
147
|
+
raw_solution_deques = [deque(job_ids) for job_ids in job_sequences]
|
148
|
+
|
149
|
+
while not dispatcher.schedule.is_complete():
|
150
|
+
at_least_one_operation_scheduled = False
|
151
|
+
for machine_id, job_ids in enumerate(raw_solution_deques):
|
152
|
+
if not job_ids:
|
153
|
+
continue
|
154
|
+
job_id = job_ids[0]
|
155
|
+
operation_index = dispatcher.job_next_operation_index[job_id]
|
156
|
+
operation = instance.jobs[job_id][operation_index]
|
157
|
+
is_ready = dispatcher.is_operation_ready(operation)
|
158
|
+
if is_ready and machine_id in operation.machines:
|
159
|
+
dispatcher.dispatch(operation, machine_id)
|
160
|
+
job_ids.popleft()
|
161
|
+
at_least_one_operation_scheduled = True
|
162
|
+
|
163
|
+
if not at_least_one_operation_scheduled:
|
164
|
+
raise JobShopLibError(
|
165
|
+
"Invalid job sequences. No valid operation to schedule."
|
166
|
+
)
|
167
|
+
return dispatcher.schedule
|
168
|
+
|
74
169
|
def reset(self):
|
75
170
|
"""Resets the schedule to an empty state."""
|
76
171
|
self.schedule = [[] for _ in range(self.instance.num_machines)]
|
@@ -1,6 +1,8 @@
|
|
1
1
|
"""Home of the `ScheduledOperation` class."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from warnings import warn
|
4
|
+
|
5
|
+
from job_shop_lib import Operation, JobShopLibError
|
4
6
|
|
5
7
|
|
6
8
|
class ScheduledOperation:
|
@@ -47,7 +49,7 @@ class ScheduledOperation:
|
|
47
49
|
@machine_id.setter
|
48
50
|
def machine_id(self, value: int):
|
49
51
|
if value not in self.operation.machines:
|
50
|
-
raise
|
52
|
+
raise JobShopLibError(
|
51
53
|
f"Operation cannot be scheduled on machine {value}. "
|
52
54
|
f"Valid machines are {self.operation.machines}."
|
53
55
|
)
|
@@ -62,18 +64,28 @@ class ScheduledOperation:
|
|
62
64
|
"""
|
63
65
|
|
64
66
|
if self.operation.job_id is None:
|
65
|
-
raise
|
67
|
+
raise JobShopLibError("Operation has no job_id.")
|
66
68
|
return self.operation.job_id
|
67
69
|
|
68
70
|
@property
|
69
71
|
def position(self) -> int:
|
72
|
+
"""Deprecated. Use `position_in_job` instead."""
|
73
|
+
warn(
|
74
|
+
"The `position` attribute is deprecated. Use `position_in_job` "
|
75
|
+
"instead. It will be removed in version 1.0.0.",
|
76
|
+
DeprecationWarning,
|
77
|
+
)
|
78
|
+
return self.position_in_job
|
79
|
+
|
80
|
+
@property
|
81
|
+
def position_in_job(self) -> int:
|
70
82
|
"""Returns the position (starting at zero) of the operation in the job.
|
71
83
|
|
72
84
|
Raises:
|
73
85
|
ValueError: If the operation has no position_in_job.
|
74
86
|
"""
|
75
87
|
if self.operation.position_in_job is None:
|
76
|
-
raise
|
88
|
+
raise JobShopLibError("Operation has no position.")
|
77
89
|
return self.operation.position_in_job
|
78
90
|
|
79
91
|
@property
|
@@ -91,7 +103,7 @@ class ScheduledOperation:
|
|
91
103
|
if not isinstance(value, ScheduledOperation):
|
92
104
|
return False
|
93
105
|
return (
|
94
|
-
self.operation
|
106
|
+
self.operation == value.operation
|
95
107
|
and self.start_time == value.start_time
|
96
108
|
and self.machine_id == value.machine_id
|
97
109
|
)
|
@@ -6,7 +6,7 @@ job_shop_lib/benchmarking/load_benchmark.py,sha256=CjiSALutgWcfD-SDU6w9WO3udvPVp
|
|
6
6
|
job_shop_lib/cp_sat/__init__.py,sha256=DqrF9IewFMkVB5BhFOHhlJvG6w6BW4ecxBXySunGLoU,97
|
7
7
|
job_shop_lib/cp_sat/ortools_solver.py,sha256=zsISUQy0dQvn7bmUsAQBCe-V92CFskJHkSfngSP4KSg,8130
|
8
8
|
job_shop_lib/dispatching/__init__.py,sha256=xk6NjndZ4-EH5G_fGSEX4LQEXL53TRYn5dKEb5uFggI,1568
|
9
|
-
job_shop_lib/dispatching/dispatcher.py,sha256=
|
9
|
+
job_shop_lib/dispatching/dispatcher.py,sha256=2rzvmn6EQz2gIS8tP2tUPJ34uUq7SZzJubHmrjw_qV8,12394
|
10
10
|
job_shop_lib/dispatching/dispatching_rule_solver.py,sha256=fbNfSclH6Jw1F-QGY1oxAj9wm2hHhJHGnsF2HateXX8,4669
|
11
11
|
job_shop_lib/dispatching/dispatching_rules.py,sha256=SIDkPx_1uTkM0loEqGMqotLBBSaGi1gH0WS85GXrT_I,5557
|
12
12
|
job_shop_lib/dispatching/factories.py,sha256=ldyIbz3QuLuDkrqbgJXV6YoM6AV6CKyHu8z4hXLG2Vo,7267
|
@@ -23,15 +23,15 @@ job_shop_lib/graphs/constants.py,sha256=dqPF--okue5sF70Iv-YR14QKFx4pxPwT2dL1Rh5j
|
|
23
23
|
job_shop_lib/graphs/job_shop_graph.py,sha256=B0buqcg7US6UvIRWsoY8_FwqzPa_nVjnBu7hPIrygUo,7404
|
24
24
|
job_shop_lib/graphs/node.py,sha256=FrSndtvqgRbN69jIcU6q1TkBh-LOGg8sxxYjDZqCcf4,5613
|
25
25
|
job_shop_lib/job_shop_instance.py,sha256=ZB0NOcTvGSq0zmmxiDceaC0DH9ljpJXD0hfKOmP0jcE,12801
|
26
|
-
job_shop_lib/operation.py,sha256=
|
27
|
-
job_shop_lib/schedule.py,sha256=
|
28
|
-
job_shop_lib/scheduled_operation.py,sha256=
|
26
|
+
job_shop_lib/operation.py,sha256=S61x0xgu09JLwrRp7syd1P2psbl0ByGuK_hHoHp4ng8,3916
|
27
|
+
job_shop_lib/schedule.py,sha256=aODGwMv9slFIqOTCz2hF_EIpXhddz8-iAH5gSzGO5G8,10393
|
28
|
+
job_shop_lib/scheduled_operation.py,sha256=qzXzat1dQBbQ-sLyoG1iXbF9eWbdFeZDFjhAFVavHPk,3526
|
29
29
|
job_shop_lib/visualization/__init__.py,sha256=Kxjk3ERYXPAHR72nkD92gFdJltSLA2kxLZrlZzZJS8o,693
|
30
30
|
job_shop_lib/visualization/agent_task_graph.py,sha256=G-c9eiawz6m9sdnDM1r-ZHz6K-gYDIAreHpb6pkYE7w,8284
|
31
31
|
job_shop_lib/visualization/create_gif.py,sha256=KrwMpSYvSCsL5Ld3taiNHSl_QDrODLpqM-MKQG_C2oU,6674
|
32
32
|
job_shop_lib/visualization/disjunctive_graph.py,sha256=pg4KG9BfQbnBPnXYgbyPGe0AuHSmhYqPeqWYAf_spWQ,5905
|
33
33
|
job_shop_lib/visualization/gantt_chart.py,sha256=OyBMBnjSsRC769qXimJ3IIQWlssgPfx-nlVeSeU5sWY,4415
|
34
|
-
job_shop_lib-0.
|
35
|
-
job_shop_lib-0.
|
36
|
-
job_shop_lib-0.
|
37
|
-
job_shop_lib-0.
|
34
|
+
job_shop_lib-0.4.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
35
|
+
job_shop_lib-0.4.0.dist-info/METADATA,sha256=nWD2fekWRXglydO3tihH7tLHvYVhILm_TASf-Yn82qA,12624
|
36
|
+
job_shop_lib-0.4.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
37
|
+
job_shop_lib-0.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|