job-shop-lib 0.1.2__py3-none-any.whl → 0.2.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/generators/transformations.py +164 -0
- {job_shop_lib-0.1.2.dist-info → job_shop_lib-0.2.0.dist-info}/METADATA +30 -41
- {job_shop_lib-0.1.2.dist-info → job_shop_lib-0.2.0.dist-info}/RECORD +5 -4
- {job_shop_lib-0.1.2.dist-info → job_shop_lib-0.2.0.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.1.2.dist-info → job_shop_lib-0.2.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
"""Classes for generating transformed JobShopInstance objects."""
|
2
|
+
|
3
|
+
import abc
|
4
|
+
import copy
|
5
|
+
import random
|
6
|
+
|
7
|
+
from job_shop_lib import JobShopInstance, Operation
|
8
|
+
|
9
|
+
|
10
|
+
class Transformation(abc.ABC):
|
11
|
+
"""Base class for transformations applied to JobShopInstance objects."""
|
12
|
+
|
13
|
+
def __init__(self, suffix: str = ""):
|
14
|
+
self.suffix = suffix
|
15
|
+
self.counter = 0
|
16
|
+
|
17
|
+
@abc.abstractmethod
|
18
|
+
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
19
|
+
"""Applies the transformation to a given JobShopInstance.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
instance: The JobShopInstance to transform.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A new JobShopInstance with the transformation applied.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __call__(self, instance: JobShopInstance) -> JobShopInstance:
|
29
|
+
instance = self.apply(instance)
|
30
|
+
suffix = f"{self.suffix}_id={self.counter}"
|
31
|
+
instance.name += suffix
|
32
|
+
self.counter += 1
|
33
|
+
return instance
|
34
|
+
|
35
|
+
|
36
|
+
# pylint: disable=too-few-public-methods
|
37
|
+
class RemoveMachines(Transformation):
|
38
|
+
"""Removes operations associated with randomly selected machines until
|
39
|
+
there are exactly num_machines machines left."""
|
40
|
+
|
41
|
+
def __init__(self, num_machines: int, suffix: str | None = None):
|
42
|
+
if suffix is None:
|
43
|
+
suffix = f"_machines={num_machines}"
|
44
|
+
super().__init__(suffix=suffix)
|
45
|
+
self.num_machines = num_machines
|
46
|
+
|
47
|
+
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
48
|
+
if instance.num_machines <= self.num_machines:
|
49
|
+
return instance # No need to remove machines
|
50
|
+
|
51
|
+
# Select machine indices to keep
|
52
|
+
machines_to_keep = set(
|
53
|
+
random.sample(range(instance.num_machines), self.num_machines)
|
54
|
+
)
|
55
|
+
|
56
|
+
# Re-index machines
|
57
|
+
machine_reindex_map = {
|
58
|
+
old_id: new_id
|
59
|
+
for new_id, old_id in enumerate(sorted(machines_to_keep))
|
60
|
+
}
|
61
|
+
|
62
|
+
new_jobs = []
|
63
|
+
for job in instance.jobs:
|
64
|
+
# Keep operations whose machine_id is in machines_to_keep and
|
65
|
+
# re-index them
|
66
|
+
new_jobs.append(
|
67
|
+
[
|
68
|
+
Operation(machine_reindex_map[op.machine_id], op.duration)
|
69
|
+
for op in job
|
70
|
+
if op.machine_id in machines_to_keep
|
71
|
+
]
|
72
|
+
)
|
73
|
+
|
74
|
+
return JobShopInstance(new_jobs, instance.name)
|
75
|
+
|
76
|
+
|
77
|
+
# pylint: disable=too-few-public-methods
|
78
|
+
class AddDurationNoise(Transformation):
|
79
|
+
"""Adds uniform integer noise to operation durations."""
|
80
|
+
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
min_duration: int = 1,
|
84
|
+
max_duration: int = 100,
|
85
|
+
noise_level: int = 10,
|
86
|
+
suffix: str | None = None,
|
87
|
+
):
|
88
|
+
if suffix is None:
|
89
|
+
suffix = f"_noise={noise_level}"
|
90
|
+
super().__init__(suffix=suffix)
|
91
|
+
self.min_duration = min_duration
|
92
|
+
self.max_duration = max_duration
|
93
|
+
self.noise_level = noise_level
|
94
|
+
|
95
|
+
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
96
|
+
new_jobs = []
|
97
|
+
for job in instance.jobs:
|
98
|
+
new_job = []
|
99
|
+
for op in job:
|
100
|
+
noise = random.randint(-self.noise_level, self.noise_level)
|
101
|
+
new_duration = max(
|
102
|
+
self.min_duration,
|
103
|
+
min(self.max_duration, op.duration + noise),
|
104
|
+
)
|
105
|
+
|
106
|
+
new_job.append(Operation(op.machine_id, new_duration))
|
107
|
+
new_jobs.append(new_job)
|
108
|
+
|
109
|
+
return JobShopInstance(new_jobs, instance.name)
|
110
|
+
|
111
|
+
|
112
|
+
class RemoveJobs(Transformation):
|
113
|
+
"""Removes jobs randomly until the number of jobs is within a specified
|
114
|
+
range."""
|
115
|
+
|
116
|
+
def __init__(
|
117
|
+
self,
|
118
|
+
min_jobs: int,
|
119
|
+
max_jobs: int,
|
120
|
+
target_jobs: int | None = None,
|
121
|
+
suffix: str | None = None,
|
122
|
+
):
|
123
|
+
"""
|
124
|
+
Args:
|
125
|
+
min_jobs: The minimum number of jobs to remain in the instance.
|
126
|
+
max_jobs: The maximum number of jobs to remain in the instance.
|
127
|
+
target_jobs: If specified, the number of jobs to remain in the
|
128
|
+
instance. Overrides min_jobs and max_jobs.
|
129
|
+
"""
|
130
|
+
if suffix is None:
|
131
|
+
suffix = f"_jobs={min_jobs}-{max_jobs}"
|
132
|
+
super().__init__(suffix=suffix)
|
133
|
+
self.min_jobs = min_jobs
|
134
|
+
self.max_jobs = max_jobs
|
135
|
+
self.target_jobs = target_jobs
|
136
|
+
|
137
|
+
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
138
|
+
if self.target_jobs is None:
|
139
|
+
target_jobs = random.randint(self.min_jobs, self.max_jobs)
|
140
|
+
else:
|
141
|
+
target_jobs = self.target_jobs
|
142
|
+
new_jobs = copy.deepcopy(instance.jobs)
|
143
|
+
|
144
|
+
while len(new_jobs) > target_jobs:
|
145
|
+
new_jobs.pop(random.randint(0, len(new_jobs) - 1))
|
146
|
+
|
147
|
+
return JobShopInstance(new_jobs, instance.name)
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def remove_job(
|
151
|
+
instance: JobShopInstance, job_index: int
|
152
|
+
) -> JobShopInstance:
|
153
|
+
"""Removes a specific job from the instance.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
instance: The JobShopInstance from which to remove the job.
|
157
|
+
job_index: The index of the job to remove.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
A new JobShopInstance with the specified job removed.
|
161
|
+
"""
|
162
|
+
new_jobs = copy.deepcopy(instance.jobs)
|
163
|
+
new_jobs.pop(job_index)
|
164
|
+
return JobShopInstance(new_jobs, instance.name)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
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
|
@@ -16,7 +16,7 @@ Requires-Dist: imageio (>=2,<3)
|
|
16
16
|
Requires-Dist: matplotlib (>=3,<4)
|
17
17
|
Requires-Dist: networkx (>=3,<4)
|
18
18
|
Requires-Dist: ortools (>=9,<10)
|
19
|
-
Requires-Dist: pyarrow (>=
|
19
|
+
Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
|
20
20
|
Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
|
21
21
|
Description-Content-Type: text/markdown
|
22
22
|
|
@@ -33,13 +33,21 @@ Description-Content-Type: text/markdown
|
|
33
33
|
|
34
34
|
</div>
|
35
35
|
|
36
|
+
An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP) with a special focus on graph representations.
|
36
37
|
|
38
|
+
It provides intuitive data structures to represent instances and solutions, as well as solvers and visualization tools.
|
37
39
|
|
38
|
-
|
40
|
+
See the [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
|
41
|
+
|
42
|
+
## Installation
|
39
43
|
|
40
|
-
|
44
|
+
You can install the library from PyPI:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
pip install job-shop-lib
|
48
|
+
```
|
41
49
|
|
42
|
-
## Quick Start
|
50
|
+
## Quick Start :rocket:
|
43
51
|
|
44
52
|
### Create a Job Shop Instance
|
45
53
|
|
@@ -79,40 +87,22 @@ manually. The instances are stored in [benchmark_instances.json](job_shop_lib/be
|
|
79
87
|
|
80
88
|
The contributions to this benchmark dataset are as follows:
|
81
89
|
|
82
|
-
- `abz5-9`:
|
83
|
-
al. (1988).
|
90
|
+
- `abz5-9`: by Adams et al. (1988).
|
84
91
|
|
85
|
-
- `ft06`, `ft10`, `ft20`:
|
86
|
-
Fisher and Thompson, as detailed in their 1963 work.
|
92
|
+
- `ft06`, `ft10`, `ft20`: by Fisher and Thompson (1963).
|
87
93
|
|
88
|
-
- `la01-40`:
|
89
|
-
Lawrence, as referenced in his 1984 report.
|
94
|
+
- `la01-40`: by Lawrence (1984)
|
90
95
|
|
91
|
-
- `orb01-10`:
|
92
|
-
Cook, as seen in their 1991 study.
|
96
|
+
- `orb01-10`: by Applegate and Cook (1991).
|
93
97
|
|
94
|
-
- `swb01-20`:
|
95
|
-
Storer et al., as per their 1992 article.
|
98
|
+
- `swb01-20`: by Storer et al. (1992).
|
96
99
|
|
97
|
-
- `yn1-4`: Yamada and Nakano
|
98
|
-
in this group, as found in their 1992 paper.
|
100
|
+
- `yn1-4`: by Yamada and Nakano (1992).
|
99
101
|
|
100
|
-
- `ta01-80`:
|
101
|
-
made by Taillard, as documented in his 1993 paper.
|
102
|
+
- `ta01-80`: by Taillard (1993).
|
102
103
|
|
103
104
|
The metadata from these instances has been updated using data from:
|
104
|
-
|
105
|
-
Thomas Weise. jsspInstancesAndResults. Accessed in January 2024.
|
106
|
-
Available at: https://github.com/thomasWeise/jsspInstancesAndResults
|
107
|
-
|
108
|
-
It includes the following information:
|
109
|
-
- "optimum" (`int` | `None`): The optimal makespan for the instance.
|
110
|
-
- "lower_bound" (`int`): The best lower bound known for the makespan. If
|
111
|
-
optimality is known, it is equal to the optimum.
|
112
|
-
- "upper_bound" (`int`): The best upper bound known for the makespan. If
|
113
|
-
optimality is known, it is equal to the optimum.
|
114
|
-
- "reference" (`str`): The paper or source where the instance was first
|
115
|
-
introduced.
|
105
|
+
https://github.com/thomasWeise/jsspInstancesAndResults
|
116
106
|
|
117
107
|
```python
|
118
108
|
>>> ft06.metadata
|
@@ -124,7 +114,7 @@ It includes the following information:
|
|
124
114
|
|
125
115
|
### Generate a Random Instance
|
126
116
|
|
127
|
-
You can also generate a random instance with the `
|
117
|
+
You can also generate a random instance with the `BasicGenerator` class.
|
128
118
|
|
129
119
|
```python
|
130
120
|
from job_shop_lib.generators import BasicGenerator
|
@@ -138,7 +128,7 @@ random_instance = generator.generate()
|
|
138
128
|
This class can also work as an iterator to generate multiple instances:
|
139
129
|
|
140
130
|
```python
|
141
|
-
generator =
|
131
|
+
generator = BasicGenerator(iteration_limit=100, seed=42)
|
142
132
|
instances = []
|
143
133
|
for instance in generator:
|
144
134
|
instances.append(instance)
|
@@ -182,7 +172,7 @@ We can visualize the solution with a `DispatchingRuleSolver` as a gif:
|
|
182
172
|
|
183
173
|
```python
|
184
174
|
from job_shop_lib.visualization import create_gif, get_plot_function
|
185
|
-
from job_shop_lib.
|
175
|
+
from job_shop_lib.dispatching import DispatchingRuleSolver, DispatchingRule
|
186
176
|
|
187
177
|
plt.style.use("ggplot")
|
188
178
|
|
@@ -227,6 +217,9 @@ plt.show()
|
|
227
217
|

|
228
218
|
|
229
219
|
|
220
|
+
> [!WARNING]
|
221
|
+
> This plot function requires having the optional dependency [PyGraphViz](https://pygraphviz.github.io/) installed.
|
222
|
+
|
230
223
|
The `JobShopGraph` class provides easy access to the nodes, for example, to get all the nodes of a specific type:
|
231
224
|
|
232
225
|
```python
|
@@ -288,13 +281,9 @@ plt.show()
|
|
288
281
|
|
289
282
|
For more details, check the [examples](examples) folder.
|
290
283
|
|
291
|
-
## Installation
|
292
|
-
|
293
|
-
In the future, the library will be available on PyPI. For now, you can install it from the source code.
|
294
|
-
|
295
|
-
### For development
|
284
|
+
## Installation for development
|
296
285
|
|
297
|
-
|
286
|
+
### With Poetry
|
298
287
|
|
299
288
|
1. Clone the repository.
|
300
289
|
|
@@ -315,7 +304,7 @@ or equivalently:
|
|
315
304
|
make poetry_install_all
|
316
305
|
```
|
317
306
|
|
318
|
-
|
307
|
+
### With PyPI
|
319
308
|
|
320
309
|
If you don't want to use Poetry, you can install the library directly from the source code:
|
321
310
|
|
@@ -14,6 +14,7 @@ job_shop_lib/dispatching/pruning_functions.py,sha256=d94_uBHuESp4NSf_jBk1q8eBCfT
|
|
14
14
|
job_shop_lib/exceptions.py,sha256=0Wla1lK6E2u1o3t2hJj9hUwyoJ-1ebkXd42GdXFAhV0,899
|
15
15
|
job_shop_lib/generators/__init__.py,sha256=CrMExfhRbw_0TnYgJ1HwFmq13LEFYFU9wSFANmlSTSQ,154
|
16
16
|
job_shop_lib/generators/basic_generator.py,sha256=pbRDQWC2mnHU0dbc-T8wkdwVeJPlRn06nhFXuwKataQ,7533
|
17
|
+
job_shop_lib/generators/transformations.py,sha256=FI2qHrETATJUrQP3-RYhZAQ5boyEZ0CF2StDbacBej8,5290
|
17
18
|
job_shop_lib/graphs/__init__.py,sha256=mWyF0MypyYfvFhy2F93BJkFIVsxS_0ZqvPuc29B7TJg,1454
|
18
19
|
job_shop_lib/graphs/build_agent_task_graph.py,sha256=ktj-oNLUPmWHfL81EVyaoF4hXClWYfnN7oG2Nn4pOsg,7128
|
19
20
|
job_shop_lib/graphs/build_disjunctive_graph.py,sha256=IRMBtHw8aru5rYGz796-dc6QyaLJFh4LlPlN_BPSq5c,2877
|
@@ -29,7 +30,7 @@ job_shop_lib/visualization/agent_task_graph.py,sha256=G-c9eiawz6m9sdnDM1r-ZHz6K-
|
|
29
30
|
job_shop_lib/visualization/create_gif.py,sha256=3j339wjgGZKLOyMWGdVqVBQu4WFDUhyualHx8b3CJMQ,6382
|
30
31
|
job_shop_lib/visualization/disjunctive_graph.py,sha256=feiRAMxuG5CG2naO7I3HtcrSQw99yWxWzIGgZC_pxIs,5803
|
31
32
|
job_shop_lib/visualization/gantt_chart.py,sha256=OyBMBnjSsRC769qXimJ3IIQWlssgPfx-nlVeSeU5sWY,4415
|
32
|
-
job_shop_lib-0.
|
33
|
-
job_shop_lib-0.
|
34
|
-
job_shop_lib-0.
|
35
|
-
job_shop_lib-0.
|
33
|
+
job_shop_lib-0.2.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
34
|
+
job_shop_lib-0.2.0.dist-info/METADATA,sha256=KWTHYD4dNAAzSmNRUoATnMKKvwtBudbMwcI4yAnCiqQ,12624
|
35
|
+
job_shop_lib-0.2.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
36
|
+
job_shop_lib-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|