job-shop-lib 1.1.2__py3-none-any.whl → 1.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/__init__.py +1 -1
- job_shop_lib/dispatching/__init__.py +3 -0
- job_shop_lib/dispatching/_start_time_calculators.py +49 -0
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +3 -0
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +106 -11
- job_shop_lib/generation/_instance_generator.py +3 -3
- {job_shop_lib-1.1.2.dist-info → job_shop_lib-1.2.0.dist-info}/METADATA +87 -18
- {job_shop_lib-1.1.2.dist-info → job_shop_lib-1.2.0.dist-info}/RECORD +10 -10
- {job_shop_lib-1.1.2.dist-info → job_shop_lib-1.2.0.dist-info}/LICENSE +0 -0
- {job_shop_lib-1.1.2.dist-info → job_shop_lib-1.2.0.dist-info}/WHEEL +0 -0
job_shop_lib/__init__.py
CHANGED
@@ -24,6 +24,7 @@ Problem step-by-step.
|
|
24
24
|
get_matrix_setup_time_calculator
|
25
25
|
get_breakdown_calculator
|
26
26
|
get_job_dependent_setup_calculator
|
27
|
+
get_arrival_calculator
|
27
28
|
|
28
29
|
Dispatching refers to the decision-making process of selecting which job
|
29
30
|
should be processed next on a particular machine when that machine becomes
|
@@ -53,6 +54,7 @@ from ._start_time_calculators import (
|
|
53
54
|
get_breakdown_calculator,
|
54
55
|
get_job_dependent_setup_calculator,
|
55
56
|
get_matrix_setup_time_calculator,
|
57
|
+
get_arrival_calculator,
|
56
58
|
)
|
57
59
|
from ._dispatcher_observer_config import DispatcherObserverConfig
|
58
60
|
from ._factories import (
|
@@ -84,4 +86,5 @@ __all__ = [
|
|
84
86
|
"get_matrix_setup_time_calculator",
|
85
87
|
"get_breakdown_calculator",
|
86
88
|
"get_job_dependent_setup_calculator",
|
89
|
+
"get_arrival_calculator",
|
87
90
|
]
|
@@ -193,3 +193,52 @@ def get_job_dependent_setup_calculator(
|
|
193
193
|
return default_start + setup_time
|
194
194
|
|
195
195
|
return calculator
|
196
|
+
|
197
|
+
|
198
|
+
def get_arrival_calculator(
|
199
|
+
arrival_times: Sequence[Sequence[int]] | NDArray[np.integer] | None = None,
|
200
|
+
) -> StartTimeCalculator:
|
201
|
+
"""Returns a start time calculator that respects operation arrival times.
|
202
|
+
|
203
|
+
This calculator uses a predefined matrix of arrival times to
|
204
|
+
ensure that no operation begins before its specified arrival
|
205
|
+
time. If the ``arrival_times`` matrix isn't provided directly,
|
206
|
+
the calculator attempts to retrieve it from the dispatcher's
|
207
|
+
instance metadata using the key ``"arrival_times_matrix"``.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
arrival_times:
|
211
|
+
A 2D matrix where ``arrival_times[i][j]`` is the
|
212
|
+
arrival time for the operation at index ``j`` of
|
213
|
+
job ``i``. If ``None``, the calculator will
|
214
|
+
attempt to retrieve it from the dispatcher metadata.
|
215
|
+
|
216
|
+
Returns:
|
217
|
+
A start time calculator function that uses the arrival times.
|
218
|
+
|
219
|
+
Example:
|
220
|
+
>>> arrival_calc = get_arrival_calculator([[0, 2], [1, 0]])
|
221
|
+
>>> dispatcher = Dispatcher(
|
222
|
+
... instance, start_time_calculator=arrival_calc
|
223
|
+
... )
|
224
|
+
"""
|
225
|
+
|
226
|
+
def calculator(
|
227
|
+
dispatcher: Dispatcher, operation: Operation, machine_id: int
|
228
|
+
) -> int:
|
229
|
+
default_start_time = no_setup_time_calculator(
|
230
|
+
dispatcher, operation, machine_id
|
231
|
+
)
|
232
|
+
arrival_matrix = arrival_times
|
233
|
+
if arrival_matrix is None:
|
234
|
+
arrival_matrix = dispatcher.instance.metadata.get(
|
235
|
+
"arrival_times_matrix"
|
236
|
+
)
|
237
|
+
if arrival_matrix is None:
|
238
|
+
return default_start_time
|
239
|
+
operation_arrival_time = arrival_matrix[operation.job_id][
|
240
|
+
operation.position_in_job
|
241
|
+
]
|
242
|
+
return max(default_start_time, operation_arrival_time)
|
243
|
+
|
244
|
+
return calculator
|
@@ -35,7 +35,23 @@ def first_come_first_served_rule(dispatcher: Dispatcher) -> Operation:
|
|
35
35
|
|
36
36
|
|
37
37
|
def most_work_remaining_rule(dispatcher: Dispatcher) -> Operation:
|
38
|
-
"""Dispatches the operation which job has the most remaining work.
|
38
|
+
"""Dispatches the operation which job has the most remaining work.
|
39
|
+
|
40
|
+
The remaining work of a job is defined as the sum of the durations of
|
41
|
+
all unscheduled operations in that job. The operation with the highest
|
42
|
+
remaining work is selected for dispatching. Note that uncompleted
|
43
|
+
but scheduled operations are not considered in this rule, only
|
44
|
+
unscheduled operations are taken into account.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
dispatcher:
|
48
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
49
|
+
containing the job shop instance and the current state of the
|
50
|
+
schedule.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
The operation that belongs to the job with the most remaining work.
|
54
|
+
"""
|
39
55
|
job_remaining_work = [0] * dispatcher.instance.num_jobs
|
40
56
|
for operation in dispatcher.unscheduled_operations():
|
41
57
|
job_remaining_work[operation.job_id] += operation.duration
|
@@ -47,7 +63,23 @@ def most_work_remaining_rule(dispatcher: Dispatcher) -> Operation:
|
|
47
63
|
|
48
64
|
|
49
65
|
def most_operations_remaining_rule(dispatcher: Dispatcher) -> Operation:
|
50
|
-
"""Dispatches the operation which job has the most remaining operations.
|
66
|
+
"""Dispatches the operation which job has the most remaining operations.
|
67
|
+
|
68
|
+
The remaining operations of a job are defined as the number of
|
69
|
+
uncompleted operations in that job. The operation with the highest
|
70
|
+
number of remaining operations is selected for dispatching. Note that
|
71
|
+
uncompleted but scheduled operations are considered in this rule.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
dispatcher:
|
75
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
76
|
+
containing the job shop instance and the current state of the
|
77
|
+
schedule.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
The operation that belongs to the job with the most remaining
|
81
|
+
operations.
|
82
|
+
"""
|
51
83
|
job_remaining_operations = [0] * dispatcher.instance.num_jobs
|
52
84
|
for operation in dispatcher.uncompleted_operations():
|
53
85
|
job_remaining_operations[operation.job_id] += 1
|
@@ -69,7 +101,9 @@ def score_based_rule(
|
|
69
101
|
"""Creates a dispatching rule based on a scoring function.
|
70
102
|
|
71
103
|
Args:
|
72
|
-
score_function:
|
104
|
+
score_function:
|
105
|
+
A function that takes a
|
106
|
+
:class:`~job_shop_lib.dispatching.Dispatcher` instance as input
|
73
107
|
and returns a list of scores for each job.
|
74
108
|
|
75
109
|
Returns:
|
@@ -97,7 +131,9 @@ def score_based_rule_with_tie_breaker(
|
|
97
131
|
still a tie, the third scoring function is used, and so on.
|
98
132
|
|
99
133
|
Args:
|
100
|
-
score_functions
|
134
|
+
score_functions
|
135
|
+
A list of scoring functions that take a
|
136
|
+
:class:`~job_shop_lib.dispatching.Dispatcher`
|
101
137
|
instance as input and return a list of scores for each job.
|
102
138
|
"""
|
103
139
|
|
@@ -123,7 +159,21 @@ def score_based_rule_with_tie_breaker(
|
|
123
159
|
|
124
160
|
|
125
161
|
def shortest_processing_time_score(dispatcher: Dispatcher) -> list[int]:
|
126
|
-
"""Scores each job based on the duration of the next operation.
|
162
|
+
"""Scores each job based on the duration of the next operation.
|
163
|
+
|
164
|
+
The score is the negative duration of the next operation in each job.
|
165
|
+
This means that jobs with shorter next operations will have higher scores.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
dispatcher:
|
169
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
170
|
+
containing the job shop instance and the current state of the
|
171
|
+
schedule.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
A list of scores for each job, where the score is the negative
|
175
|
+
duration of the next operation in that job.
|
176
|
+
"""
|
127
177
|
num_jobs = dispatcher.instance.num_jobs
|
128
178
|
scores = [0] * num_jobs
|
129
179
|
for operation in dispatcher.available_operations():
|
@@ -132,11 +182,26 @@ def shortest_processing_time_score(dispatcher: Dispatcher) -> list[int]:
|
|
132
182
|
|
133
183
|
|
134
184
|
def first_come_first_served_score(dispatcher: Dispatcher) -> list[int]:
|
135
|
-
"""Scores each job based on the position of the next operation.
|
185
|
+
"""Scores each job based on the position of the next operation.
|
186
|
+
|
187
|
+
The score is the negative position of the next operation in each job.
|
188
|
+
This means that jobs with operations that are earlier in the job will have
|
189
|
+
higher scores.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
dispatcher:
|
193
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
194
|
+
containing the job shop instance and the current state of the
|
195
|
+
schedule.
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
A list of scores for each job, where the score is the negative
|
199
|
+
position of the next operation in that job.
|
200
|
+
"""
|
136
201
|
num_jobs = dispatcher.instance.num_jobs
|
137
202
|
scores = [0] * num_jobs
|
138
203
|
for operation in dispatcher.available_operations():
|
139
|
-
scores[operation.job_id] = operation.operation_id
|
204
|
+
scores[operation.job_id] = -operation.operation_id
|
140
205
|
return scores
|
141
206
|
|
142
207
|
|
@@ -145,8 +210,8 @@ class MostWorkRemainingScorer: # pylint: disable=too-few-public-methods
|
|
145
210
|
|
146
211
|
This class is conceptually a function: it can be called with a
|
147
212
|
:class:`~job_shop_lib.dispatching.Dispatcher` instance as input, and it
|
148
|
-
returns a list of scores for each job. The reason for using a class
|
149
|
-
of a function is to cache the observers that are created for each
|
213
|
+
returns a list of scores for each job. The reason for using a class
|
214
|
+
instead of a function is to cache the observers that are created for each
|
150
215
|
dispatcher instance. This way, the observers do not have to be retrieved
|
151
216
|
every time the function is called.
|
152
217
|
|
@@ -195,10 +260,26 @@ class MostWorkRemainingScorer: # pylint: disable=too-few-public-methods
|
|
195
260
|
observer_based_most_work_remaining_rule = score_based_rule(
|
196
261
|
MostWorkRemainingScorer()
|
197
262
|
)
|
263
|
+
"""Dispatching rule that uses the :class:`MostWorkRemainingScorer` to select
|
264
|
+
the next operation."""
|
198
265
|
|
199
266
|
|
200
267
|
def most_operations_remaining_score(dispatcher: Dispatcher) -> list[int]:
|
201
|
-
"""Scores each job based on the remaining operations in the job.
|
268
|
+
"""Scores each job based on the remaining operations in the job.
|
269
|
+
|
270
|
+
The score is the number of uncompleted operations in each job. This means
|
271
|
+
that jobs with more uncompleted operations will have higher scores.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
dispatcher:
|
275
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
276
|
+
containing the job shop instance and the current state of the
|
277
|
+
schedule.
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
A list of scores for each job, where the score is the number of
|
281
|
+
uncompleted operations in that job.
|
282
|
+
"""
|
202
283
|
num_jobs = dispatcher.instance.num_jobs
|
203
284
|
scores = [0] * num_jobs
|
204
285
|
for operation in dispatcher.uncompleted_operations():
|
@@ -207,7 +288,21 @@ def most_operations_remaining_score(dispatcher: Dispatcher) -> list[int]:
|
|
207
288
|
|
208
289
|
|
209
290
|
def random_score(dispatcher: Dispatcher) -> list[int]:
|
210
|
-
"""Scores each job randomly.
|
291
|
+
"""Scores each job randomly.
|
292
|
+
|
293
|
+
This function generates a random score for each job in the job shop
|
294
|
+
instance. The scores are integers between 0 and 100, inclusive.
|
295
|
+
|
296
|
+
Args:
|
297
|
+
dispatcher:
|
298
|
+
The :class:`~job_shop_lib.dispatching.Dispatcher` instance
|
299
|
+
containing the job shop instance and the current state of the
|
300
|
+
schedule.
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
A list of random scores for each job, where each score is an integer
|
304
|
+
between 0 and 100.
|
305
|
+
"""
|
211
306
|
return [
|
212
307
|
random.randint(0, 100) for _ in range(dispatcher.instance.num_jobs)
|
213
308
|
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Home of the `InstanceGenerator` class."""
|
2
2
|
|
3
|
-
import
|
3
|
+
from abc import ABC, abstractmethod
|
4
4
|
|
5
5
|
import random
|
6
6
|
from typing import Iterator
|
@@ -9,7 +9,7 @@ from job_shop_lib import JobShopInstance
|
|
9
9
|
from job_shop_lib.exceptions import UninitializedAttributeError
|
10
10
|
|
11
11
|
|
12
|
-
class InstanceGenerator(
|
12
|
+
class InstanceGenerator(ABC):
|
13
13
|
"""Common interface for all generators.
|
14
14
|
|
15
15
|
The class supports both single instance generation and iteration over
|
@@ -75,7 +75,7 @@ class InstanceGenerator(abc.ABC):
|
|
75
75
|
self._current_iteration = 0
|
76
76
|
self._iteration_limit = iteration_limit
|
77
77
|
|
78
|
-
@
|
78
|
+
@abstractmethod
|
79
79
|
def generate(
|
80
80
|
self,
|
81
81
|
num_jobs: int | None = None,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.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
|
@@ -86,6 +86,24 @@ pip install job-shop-lib
|
|
86
86
|
|
87
87
|
<!-- end key features -->
|
88
88
|
|
89
|
+
## Publication :scroll:
|
90
|
+
|
91
|
+
For an in-depth explanation of the library (v1.0.0), including its design, features, reinforcement learning environments, and some experiments, please refer to my [Bachelor's thesis](https://www.arxiv.org/abs/2506.13781).
|
92
|
+
|
93
|
+
You can also cite the library using the following BibTeX entry:
|
94
|
+
|
95
|
+
```bibtex
|
96
|
+
@misc{arino2025jobshoplib,
|
97
|
+
title={Solving the Job Shop Scheduling Problem with Graph Neural Networks: A Customizable Reinforcement Learning Environment},
|
98
|
+
author={Pablo Ariño Fernández},
|
99
|
+
year={2025},
|
100
|
+
eprint={2506.13781},
|
101
|
+
archivePrefix={arXiv},
|
102
|
+
primaryClass={cs.LG},
|
103
|
+
url={https://arxiv.org/abs/2506.13781},
|
104
|
+
}
|
105
|
+
```
|
106
|
+
|
89
107
|
## Some Examples :rocket:
|
90
108
|
|
91
109
|
### Create a Job Shop Instance
|
@@ -330,31 +348,82 @@ plt.show()
|
|
330
348
|
|
331
349
|
The library generalizes this graph by allowing the addition of job nodes and a global one (see `build_resource_task_graph_with_jobs` and `build_resource_task_graph`).
|
332
350
|
|
333
|
-
|
351
|
+
### Gymnasium Environments
|
334
352
|
|
335
|
-
|
353
|
+
<div align="center">
|
354
|
+
<img src="docs/source/images/rl_diagram.png">
|
355
|
+
</div>
|
356
|
+
<br>
|
336
357
|
|
337
|
-
<!-- start installation development -->
|
338
358
|
|
339
|
-
|
359
|
+
The `SingleJobShopGraphEnv` allows to learn from a single job shop instance, while the `MultiJobShopGraphEnv` generates a new instance at each reset. For an in-depth explanation of the environments see chapter 7 of my [Bachelor's thesis](https://www.arxiv.org/abs/2506.13781).
|
340
360
|
|
341
|
-
```
|
342
|
-
|
343
|
-
|
344
|
-
|
361
|
+
```python
|
362
|
+
from IPython.display import clear_output
|
363
|
+
|
364
|
+
from job_shop_lib.reinforcement_learning import (
|
365
|
+
# MakespanReward,
|
366
|
+
SingleJobShopGraphEnv,
|
367
|
+
ObservationSpaceKey,
|
368
|
+
IdleTimeReward,
|
369
|
+
ObservationDict,
|
370
|
+
)
|
371
|
+
from job_shop_lib.dispatching.feature_observers import (
|
372
|
+
FeatureObserverType,
|
373
|
+
FeatureType,
|
374
|
+
)
|
375
|
+
from job_shop_lib.dispatching import DispatcherObserverConfig
|
376
|
+
|
377
|
+
|
378
|
+
instance = load_benchmark_instance("ft06")
|
379
|
+
job_shop_graph = build_disjunctive_graph(instance)
|
380
|
+
feature_observer_configs = [
|
381
|
+
DispatcherObserverConfig(
|
382
|
+
FeatureObserverType.IS_READY,
|
383
|
+
kwargs={"feature_types": [FeatureType.JOBS]},
|
384
|
+
)
|
385
|
+
]
|
386
|
+
|
387
|
+
env = SingleJobShopGraphEnv(
|
388
|
+
job_shop_graph=job_shop_graph,
|
389
|
+
feature_observer_configs=feature_observer_configs,
|
390
|
+
reward_function_config=DispatcherObserverConfig(IdleTimeReward),
|
391
|
+
render_mode="human", # Try "save_video"
|
392
|
+
render_config={
|
393
|
+
"video_config": {"fps": 4}
|
394
|
+
}
|
395
|
+
)
|
345
396
|
|
346
|
-
2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
|
347
397
|
|
348
|
-
|
349
|
-
|
350
|
-
|
398
|
+
def random_action(observation: ObservationDict) -> tuple[int, int]:
|
399
|
+
ready_jobs = []
|
400
|
+
for job_id, is_ready in enumerate(
|
401
|
+
observation[ObservationSpaceKey.JOBS.value].ravel()
|
402
|
+
):
|
403
|
+
if is_ready == 1.0:
|
404
|
+
ready_jobs.append(job_id)
|
351
405
|
|
352
|
-
|
353
|
-
|
354
|
-
|
406
|
+
job_id = random.choice(ready_jobs)
|
407
|
+
machine_id = -1 # We can use -1 if each operation can only be scheduled
|
408
|
+
# on one machine.
|
409
|
+
return (job_id, machine_id)
|
410
|
+
|
411
|
+
|
412
|
+
done = False
|
413
|
+
obs, _ = env.reset()
|
414
|
+
while not done:
|
415
|
+
action = random_action(obs)
|
416
|
+
obs, reward, done, *_ = env.step(action)
|
417
|
+
if env.render_mode == "human":
|
418
|
+
env.render()
|
419
|
+
clear_output(wait=True)
|
420
|
+
|
421
|
+
if env.render_mode == "save_video" or env.render_mode == "save_gif":
|
422
|
+
env.render()
|
355
423
|
```
|
356
424
|
|
357
|
-
|
425
|
+
## Contributing :handshake:
|
426
|
+
Any contribution is welcome, whether it's a small bug or documentation fix or a new feature! See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to contribute to this project.
|
358
427
|
|
359
428
|
## License :scroll:
|
360
429
|
|
@@ -391,5 +460,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
391
460
|
- E. Taillard, "Benchmarks for basic scheduling problems," European
|
392
461
|
Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
|
393
462
|
|
394
|
-
- Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
|
463
|
+
- Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
|
395
464
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
job_shop_lib/__init__.py,sha256=
|
1
|
+
job_shop_lib/__init__.py,sha256=yXJFP7NB9jntS26eZXQCVjeCM45KPFcc2FS1PbTCLm8,639
|
2
2
|
job_shop_lib/_base_solver.py,sha256=p17XmtufNc9Y481cqZUT45pEkUmmW1HWG53dfhIBJH8,1363
|
3
3
|
job_shop_lib/_job_shop_instance.py,sha256=FkMBy9Yb8cNEGswI9vlN3Wh4mhtEX-QuDbKvSYUOXcM,18361
|
4
4
|
job_shop_lib/_operation.py,sha256=lwCjgXwWlgESFuV3Yh4SCVofPGCd3hJU4vnK7peREac,4235
|
@@ -9,21 +9,21 @@ job_shop_lib/benchmarking/_load_benchmark.py,sha256=-cgyx0Kn6uAc3KdGFSQb6eUVQjQg
|
|
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
11
|
job_shop_lib/constraint_programming/_ortools_solver.py,sha256=oMPeA2VHoYX1ZvmygQ8kYew40ITLAQATmM4OhgVFuXM,10482
|
12
|
-
job_shop_lib/dispatching/__init__.py,sha256=
|
12
|
+
job_shop_lib/dispatching/__init__.py,sha256=gbgY1_lhergmXaDa-VYVUmxMpOKzYko0ONREVAt_QPc,2643
|
13
13
|
job_shop_lib/dispatching/_dispatcher.py,sha256=KnV_Kry3Ie81WbKhdpRQtOMsuFDNCuh5Kp2ZnelM-R8,23835
|
14
14
|
job_shop_lib/dispatching/_dispatcher_observer_config.py,sha256=QF2d3rJWwmvutQBAkKxzQ1toJs6eMelT404LGS2z9HQ,2467
|
15
15
|
job_shop_lib/dispatching/_factories.py,sha256=j3MhIwVXiq-B8JMit72ObvXSa2sdgWNhUD86gghL6Gg,4689
|
16
16
|
job_shop_lib/dispatching/_history_observer.py,sha256=Vl8rQaxekUeEB-AyNxyC3c76zQakeh-rdri2iDnZvXw,610
|
17
17
|
job_shop_lib/dispatching/_optimal_operations_observer.py,sha256=2EYxevjpeGMP3do-m0ZmtmjIjmNcxrWOSKzN_bW37gQ,4247
|
18
18
|
job_shop_lib/dispatching/_ready_operation_filters.py,sha256=brhmhoyyoZ98wAEEfneZC-CD-aw9SerZHGMB1DpK8HY,5749
|
19
|
-
job_shop_lib/dispatching/_start_time_calculators.py,sha256=
|
19
|
+
job_shop_lib/dispatching/_start_time_calculators.py,sha256=N4kz3c4TmXbyFsY6ctxruYK2ucnjSVXWNMhvsUWFuDg,8192
|
20
20
|
job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=0he-j4OlvqtXAJZD5x1nuBnUKqZUfftVx9NT3CVxPyg,2708
|
21
21
|
job_shop_lib/dispatching/feature_observers/__init__.py,sha256=EuJLvSpJpoXUK8A4UuC2k6Mpa293ZR3oCnnvYivIBtU,2240
|
22
22
|
job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=tpvqTLIcNmbYROSFT62LiUZ_tI4fHWL_qCULKK43BU4,6429
|
23
23
|
job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=fbkUIVScF1iNjdVCYr1ImQm53TfahvVnGXhsRAsgdzY,4129
|
24
24
|
job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=AQIjVp7VRDnb5GuYZlLUwk-xiXSqbsxJW-Ji7NjLoAw,11452
|
25
25
|
job_shop_lib/dispatching/feature_observers/_factory.py,sha256=NyXYK5A1hXsYEeEqngwVRNAFkevY95DglheeqyfFv8s,3217
|
26
|
-
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=
|
26
|
+
job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=qbgtMUicQ5FWS-Ql4Izjsj4QrevfOGlWzoJ0JlVSLH0,8668
|
27
27
|
job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=EYJOyWL8ApUElLucoHnFlt0g2Ior_1yO7Q8V3FU_Qog,3576
|
28
28
|
job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=wy_pA-1wmnzVjhq92mdsT2JJHYbfsm79mcMgSgYUCOs,1264
|
29
29
|
job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py,sha256=OcuMUB9_By6ZMtX-1_3z-xaxGbP85a5Zv0ywAv7XxWQ,1491
|
@@ -32,13 +32,13 @@ job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha
|
|
32
32
|
job_shop_lib/dispatching/rules/__init__.py,sha256=0Nn9FBVmxVYeDeLsd7g7WkmKFBYJqOIDzArbqsC7FAI,2187
|
33
33
|
job_shop_lib/dispatching/rules/_dispatching_rule_factory.py,sha256=5fNpv90fAoR6rcE6NeJOWiB7ir-FVnoONIhHtKJ9H0E,2904
|
34
34
|
job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=1_canC1lXZATrQCZaHOY3JOLmTuT6U0Z_QWzgTOLwqI,5917
|
35
|
-
job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=
|
35
|
+
job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=Yk40aKePBHHiMO6aTFeyJd1-khsDPhqit2WCOaByCfw,10998
|
36
36
|
job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=CJ74ujgWXgG8cuULWY6VJkD_b3arTcOjTNLZJTAf8xE,2346
|
37
37
|
job_shop_lib/dispatching/rules/_utils.py,sha256=m5qw4qyfaIvVrkmv51nuhreizr98-cg8AJKt2VTd48w,4603
|
38
38
|
job_shop_lib/exceptions.py,sha256=ARzpoZJCvRIvOesCiqqFSRxkv6w9WwEXx0aBP-l2IKA,1597
|
39
39
|
job_shop_lib/generation/__init__.py,sha256=QaWwuBfBNnOiG0OPiP_CV_flBu9dX7r2o_HwL47tREM,822
|
40
40
|
job_shop_lib/generation/_general_instance_generator.py,sha256=b_tnyP4H_buoN7b6lKQRLvDkeZDdys0mpqS3thB5-SQ,6544
|
41
|
-
job_shop_lib/generation/_instance_generator.py,sha256=
|
41
|
+
job_shop_lib/generation/_instance_generator.py,sha256=doN6WySyI0k7wz3aKy_e6hj6t7WV3dNzve3YmTFShas,4584
|
42
42
|
job_shop_lib/generation/_utils.py,sha256=TYBGt4Zjw94l6ukIjXBVAK3lmrrZXdyzyq_r1DMlL-E,3986
|
43
43
|
job_shop_lib/graphs/__init__.py,sha256=wlYIiXTuZRE6Kx3K0RpPUoZikzoegBuN2hcdqMODtGk,2433
|
44
44
|
job_shop_lib/graphs/_build_disjunctive_graph.py,sha256=UbUYdeQaaeEqLchcKJGHEFGl4wElfGLb1o_R-u8wqnA,5120
|
@@ -67,7 +67,7 @@ job_shop_lib/visualization/gantt/_plot_gantt_chart.py,sha256=_4UGUTRuIw0tLzsJD9G
|
|
67
67
|
job_shop_lib/visualization/graphs/__init__.py,sha256=HUWzfgQLeklNROtjnxeJX_FIySo_baTXO6klx0zUVpQ,630
|
68
68
|
job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py,sha256=L9_ZGgvCFpGc2rTOdZESdtydFQqShjqedimIOhqZx6Y,16209
|
69
69
|
job_shop_lib/visualization/graphs/_plot_resource_task_graph.py,sha256=nkkdZ-9_OBevw72Frecwzv1y3WyhGZ9r9lz0y9MXvZ8,13192
|
70
|
-
job_shop_lib-1.
|
71
|
-
job_shop_lib-1.
|
72
|
-
job_shop_lib-1.
|
73
|
-
job_shop_lib-1.
|
70
|
+
job_shop_lib-1.2.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
|
71
|
+
job_shop_lib-1.2.0.dist-info/METADATA,sha256=jn2o3HTpGzqzkYdagJfNM-IVheqnLQ6SEUnQQt69sYc,19130
|
72
|
+
job_shop_lib-1.2.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
73
|
+
job_shop_lib-1.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|