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.
@@ -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.1.2
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 (>=14.0.0,<15.0.0)
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
- An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP) with a special focus on graph representations.
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
- It provides intuitive data structures to represent instances and solutions, as well as solvers and visualization tools:
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`: This subset, comprising five instances, was introduced by Adams et
83
- al. (1988).
90
+ - `abz5-9`: by Adams et al. (1988).
84
91
 
85
- - `ft06`, `ft10`, `ft20`: These three instances are attributed to the work of
86
- Fisher and Thompson, as detailed in their 1963 work.
92
+ - `ft06`, `ft10`, `ft20`: by Fisher and Thompson (1963).
87
93
 
88
- - `la01-40`: A collection of forty instances, this group was contributed by
89
- Lawrence, as referenced in his 1984 report.
94
+ - `la01-40`: by Lawrence (1984)
90
95
 
91
- - `orb01-10`: Ten instances in this category were provided by Applegate and
92
- Cook, as seen in their 1991 study.
96
+ - `orb01-10`: by Applegate and Cook (1991).
93
97
 
94
- - `swb01-20`: This segment, encompassing twenty instances, was contributed by
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 are credited with the addition of four instances
98
- in this group, as found in their 1992 paper.
100
+ - `yn1-4`: by Yamada and Nakano (1992).
99
101
 
100
- - `ta01-80`: The largest contribution, consisting of eighty instances, was
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 `InstanceGenerator` class.
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 = InstanceGenerator(iteration_limit=100, seed=42)
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.solvers import DispatchingRuleSolver, DispatchingRule
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
  ![Example Disjunctive Graph](images/example_disjunctive_graph.png)
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
- #### With Poetry
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
- #### With PyPI
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.1.2.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
33
- job_shop_lib-0.1.2.dist-info/METADATA,sha256=PAX6P-PWImnC9molUuAeYnLkufbB_rkq1aGtX1GSV3U,13467
34
- job_shop_lib-0.1.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
35
- job_shop_lib-0.1.2.dist-info/RECORD,,
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,,