job-shop-lib 0.3.0__py3-none-any.whl → 0.4.0__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/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
|