job-shop-lib 1.1.1__tar.gz → 1.1.3__tar.gz
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-1.1.1 → job_shop_lib-1.1.3}/PKG-INFO +90 -19
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/README.md +87 -17
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/__init__.py +1 -1
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/_job_shop_instance.py +5 -5
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/_schedule.py +2 -2
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_dispatcher.py +12 -7
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_optimal_operations_observer.py +6 -3
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +11 -46
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +2 -2
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_factory.py +2 -4
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_feature_observer.py +3 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +0 -46
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +106 -11
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/generation/_general_instance_generator.py +6 -4
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/_build_resource_task_graphs.py +1 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/_job_shop_graph.py +2 -2
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +4 -3
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +5 -3
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +23 -29
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +6 -4
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +18 -11
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +1 -1
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/pyproject.toml +8 -3
- job_shop_lib-1.1.1/job_shop_lib/generation/_transformations.py +0 -167
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/LICENSE +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/_base_solver.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/_operation.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/_scheduled_operation.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/benchmarking/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/benchmarking/_load_benchmark.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/benchmarking/benchmark_instances.json +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/constraint_programming/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/constraint_programming/_ortools_solver.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_dispatcher_observer_config.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_factories.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_history_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_ready_operation_filters.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_start_time_calculators.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_unscheduled_operations_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_duration_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_machine_chooser_factory.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_utils.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/exceptions.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/generation/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/generation/_instance_generator.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/generation/_utils.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/_build_disjunctive_graph.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/_constants.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/_node.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/graph_updaters/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/graph_updaters/_graph_updater.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/graphs/graph_updaters/_utils.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/py.typed +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_reward_observers.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_types_and_constants.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/reinforcement_learning/_utils.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/gantt/__init__.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/gantt/_gantt_chart_creator.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/gantt/_plot_gantt_chart.py +0 -0
- {job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/visualization/graphs/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: job-shop-lib
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.3
|
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
|
@@ -17,7 +17,8 @@ Requires-Dist: imageio[ffmpeg] (>=2.34.1,<3.0.0)
|
|
17
17
|
Requires-Dist: matplotlib (>=3,<4)
|
18
18
|
Requires-Dist: networkx (>=3,<4)
|
19
19
|
Requires-Dist: numpy (>=1.26.4,<3.0.0)
|
20
|
-
Requires-Dist: ortools (>=9.9,<10.0)
|
20
|
+
Requires-Dist: ortools (>=9.9,<10.0) ; sys_platform != "darwin"
|
21
|
+
Requires-Dist: ortools (>=9.9,<9.13) ; sys_platform == "darwin"
|
21
22
|
Requires-Dist: pyarrow (>=15,<21)
|
22
23
|
Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
|
23
24
|
Description-Content-Type: text/markdown
|
@@ -30,6 +31,7 @@ Description-Content-Type: text/markdown
|
|
30
31
|
|
31
32
|
[](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
|
32
33
|
[](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
|
34
|
+
[](https://codecov.io/gh/Pabloo22/job_shop_lib)
|
33
35
|

|
34
36
|
[](https://github.com/psf/black)
|
35
37
|
[](https://opensource.org/licenses/MIT)
|
@@ -84,6 +86,24 @@ pip install job-shop-lib
|
|
84
86
|
|
85
87
|
<!-- end key features -->
|
86
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
|
+
|
87
107
|
## Some Examples :rocket:
|
88
108
|
|
89
109
|
### Create a Job Shop Instance
|
@@ -328,31 +348,82 @@ plt.show()
|
|
328
348
|
|
329
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`).
|
330
350
|
|
331
|
-
|
351
|
+
### Gymnasium Environments
|
332
352
|
|
333
|
-
|
353
|
+
<div align="center">
|
354
|
+
<img src="docs/source/images/rl_diagram.png">
|
355
|
+
</div>
|
356
|
+
<br>
|
334
357
|
|
335
|
-
<!-- start installation development -->
|
336
358
|
|
337
|
-
|
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).
|
338
360
|
|
339
|
-
```
|
340
|
-
|
341
|
-
|
342
|
-
|
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
|
+
)
|
343
396
|
|
344
|
-
2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
|
345
397
|
|
346
|
-
|
347
|
-
|
348
|
-
|
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)
|
349
405
|
|
350
|
-
|
351
|
-
|
352
|
-
|
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()
|
353
423
|
```
|
354
424
|
|
355
|
-
|
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.
|
356
427
|
|
357
428
|
## License :scroll:
|
358
429
|
|
@@ -389,5 +460,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
389
460
|
- E. Taillard, "Benchmarks for basic scheduling problems," European
|
390
461
|
Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
|
391
462
|
|
392
|
-
- 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.
|
393
464
|
|
@@ -6,6 +6,7 @@
|
|
6
6
|
|
7
7
|
[](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
|
8
8
|
[](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
|
9
|
+
[](https://codecov.io/gh/Pabloo22/job_shop_lib)
|
9
10
|

|
10
11
|
[](https://github.com/psf/black)
|
11
12
|
[](https://opensource.org/licenses/MIT)
|
@@ -60,6 +61,24 @@ pip install job-shop-lib
|
|
60
61
|
|
61
62
|
<!-- end key features -->
|
62
63
|
|
64
|
+
## Publication :scroll:
|
65
|
+
|
66
|
+
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).
|
67
|
+
|
68
|
+
You can also cite the library using the following BibTeX entry:
|
69
|
+
|
70
|
+
```bibtex
|
71
|
+
@misc{arino2025jobshoplib,
|
72
|
+
title={Solving the Job Shop Scheduling Problem with Graph Neural Networks: A Customizable Reinforcement Learning Environment},
|
73
|
+
author={Pablo Ariño Fernández},
|
74
|
+
year={2025},
|
75
|
+
eprint={2506.13781},
|
76
|
+
archivePrefix={arXiv},
|
77
|
+
primaryClass={cs.LG},
|
78
|
+
url={https://arxiv.org/abs/2506.13781},
|
79
|
+
}
|
80
|
+
```
|
81
|
+
|
63
82
|
## Some Examples :rocket:
|
64
83
|
|
65
84
|
### Create a Job Shop Instance
|
@@ -304,31 +323,82 @@ plt.show()
|
|
304
323
|
|
305
324
|
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`).
|
306
325
|
|
307
|
-
|
326
|
+
### Gymnasium Environments
|
308
327
|
|
309
|
-
|
328
|
+
<div align="center">
|
329
|
+
<img src="docs/source/images/rl_diagram.png">
|
330
|
+
</div>
|
331
|
+
<br>
|
310
332
|
|
311
|
-
<!-- start installation development -->
|
312
333
|
|
313
|
-
|
334
|
+
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).
|
314
335
|
|
315
|
-
```
|
316
|
-
|
317
|
-
|
318
|
-
|
336
|
+
```python
|
337
|
+
from IPython.display import clear_output
|
338
|
+
|
339
|
+
from job_shop_lib.reinforcement_learning import (
|
340
|
+
# MakespanReward,
|
341
|
+
SingleJobShopGraphEnv,
|
342
|
+
ObservationSpaceKey,
|
343
|
+
IdleTimeReward,
|
344
|
+
ObservationDict,
|
345
|
+
)
|
346
|
+
from job_shop_lib.dispatching.feature_observers import (
|
347
|
+
FeatureObserverType,
|
348
|
+
FeatureType,
|
349
|
+
)
|
350
|
+
from job_shop_lib.dispatching import DispatcherObserverConfig
|
351
|
+
|
352
|
+
|
353
|
+
instance = load_benchmark_instance("ft06")
|
354
|
+
job_shop_graph = build_disjunctive_graph(instance)
|
355
|
+
feature_observer_configs = [
|
356
|
+
DispatcherObserverConfig(
|
357
|
+
FeatureObserverType.IS_READY,
|
358
|
+
kwargs={"feature_types": [FeatureType.JOBS]},
|
359
|
+
)
|
360
|
+
]
|
361
|
+
|
362
|
+
env = SingleJobShopGraphEnv(
|
363
|
+
job_shop_graph=job_shop_graph,
|
364
|
+
feature_observer_configs=feature_observer_configs,
|
365
|
+
reward_function_config=DispatcherObserverConfig(IdleTimeReward),
|
366
|
+
render_mode="human", # Try "save_video"
|
367
|
+
render_config={
|
368
|
+
"video_config": {"fps": 4}
|
369
|
+
}
|
370
|
+
)
|
319
371
|
|
320
|
-
2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
|
321
372
|
|
322
|
-
|
323
|
-
|
324
|
-
|
373
|
+
def random_action(observation: ObservationDict) -> tuple[int, int]:
|
374
|
+
ready_jobs = []
|
375
|
+
for job_id, is_ready in enumerate(
|
376
|
+
observation[ObservationSpaceKey.JOBS.value].ravel()
|
377
|
+
):
|
378
|
+
if is_ready == 1.0:
|
379
|
+
ready_jobs.append(job_id)
|
325
380
|
|
326
|
-
|
327
|
-
|
328
|
-
|
381
|
+
job_id = random.choice(ready_jobs)
|
382
|
+
machine_id = -1 # We can use -1 if each operation can only be scheduled
|
383
|
+
# on one machine.
|
384
|
+
return (job_id, machine_id)
|
385
|
+
|
386
|
+
|
387
|
+
done = False
|
388
|
+
obs, _ = env.reset()
|
389
|
+
while not done:
|
390
|
+
action = random_action(obs)
|
391
|
+
obs, reward, done, *_ = env.step(action)
|
392
|
+
if env.render_mode == "human":
|
393
|
+
env.render()
|
394
|
+
clear_output(wait=True)
|
395
|
+
|
396
|
+
if env.render_mode == "save_video" or env.render_mode == "save_gif":
|
397
|
+
env.render()
|
329
398
|
```
|
330
399
|
|
331
|
-
|
400
|
+
## Contributing :handshake:
|
401
|
+
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.
|
332
402
|
|
333
403
|
## License :scroll:
|
334
404
|
|
@@ -365,4 +435,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
365
435
|
- E. Taillard, "Benchmarks for basic scheduling problems," European
|
366
436
|
Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
|
367
437
|
|
368
|
-
- Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
|
438
|
+
- Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
|
@@ -13,7 +13,7 @@ from job_shop_lib import Operation
|
|
13
13
|
|
14
14
|
|
15
15
|
class JobShopInstance:
|
16
|
-
"""Data structure to store a Job Shop Scheduling Problem instance.
|
16
|
+
r"""Data structure to store a Job Shop Scheduling Problem instance.
|
17
17
|
|
18
18
|
Additional attributes such as ``num_machines`` or ``durations_matrix`` can
|
19
19
|
be computed from the instance and are cached for performance since they
|
@@ -75,7 +75,7 @@ class JobShopInstance:
|
|
75
75
|
attributes of the operations are set when the instance is created.
|
76
76
|
See :meth:`set_operation_attributes` for more information. Defaults
|
77
77
|
to True.
|
78
|
-
|
78
|
+
\**metadata:
|
79
79
|
Additional information about the instance.
|
80
80
|
"""
|
81
81
|
|
@@ -131,7 +131,7 @@ class JobShopInstance:
|
|
131
131
|
name: str | None = None,
|
132
132
|
**metadata: Any,
|
133
133
|
) -> JobShopInstance:
|
134
|
-
"""Creates a JobShopInstance from a file following Taillard's format.
|
134
|
+
r"""Creates a JobShopInstance from a file following Taillard's format.
|
135
135
|
|
136
136
|
Args:
|
137
137
|
file_path:
|
@@ -144,7 +144,7 @@ class JobShopInstance:
|
|
144
144
|
name:
|
145
145
|
A string with the name of the instance. If not provided, the
|
146
146
|
name of the instance is set to the name of the file.
|
147
|
-
|
147
|
+
\**metadata:
|
148
148
|
Additional information about the instance.
|
149
149
|
|
150
150
|
Returns:
|
@@ -221,7 +221,7 @@ class JobShopInstance:
|
|
221
221
|
A list of lists of integers. The i-th list contains the
|
222
222
|
durations of the operations of the job with id i.
|
223
223
|
machines_matrix:
|
224
|
-
|
224
|
+
A list of lists of lists of integers if the
|
225
225
|
instance is flexible, or a list of lists of integers if the
|
226
226
|
instance is not flexible. The i-th list contains the machines
|
227
227
|
in which the operations of the job with id i can be processed.
|
@@ -10,7 +10,7 @@ from job_shop_lib.exceptions import ValidationError
|
|
10
10
|
|
11
11
|
|
12
12
|
class Schedule:
|
13
|
-
"""Data structure to store a complete or partial solution for a particular
|
13
|
+
r"""Data structure to store a complete or partial solution for a particular
|
14
14
|
:class:`JobShopInstance`.
|
15
15
|
|
16
16
|
A schedule is a list of lists of :class:`ScheduledOperation` objects. Each
|
@@ -33,7 +33,7 @@ class Schedule:
|
|
33
33
|
A list of lists of :class:`ScheduledOperation` objects. Each
|
34
34
|
list represents the order of operations on a machine. If
|
35
35
|
not provided, the schedule is initialized as an empty schedule.
|
36
|
-
|
36
|
+
\**metadata:
|
37
37
|
Additional information about the schedule.
|
38
38
|
"""
|
39
39
|
|
@@ -30,9 +30,12 @@ def no_setup_time_calculator(
|
|
30
30
|
operation belongs.
|
31
31
|
|
32
32
|
Args:
|
33
|
-
dispatcher:
|
34
|
-
|
35
|
-
|
33
|
+
dispatcher:
|
34
|
+
The dispatcher instance.
|
35
|
+
operation:
|
36
|
+
The operation to be scheduled.
|
37
|
+
machine_id:
|
38
|
+
The id of the machine on which the operation is to be
|
36
39
|
scheduled.
|
37
40
|
|
38
41
|
Returns:
|
@@ -329,8 +332,10 @@ class Dispatcher:
|
|
329
332
|
:attr:`~job_shop_lib.Operation.machine_id` attribute is used.
|
330
333
|
|
331
334
|
Raises:
|
332
|
-
ValidationError:
|
333
|
-
|
335
|
+
ValidationError:
|
336
|
+
If the operation is not ready to be scheduled.
|
337
|
+
UninitializedAttributeError:
|
338
|
+
If the operation has multiple
|
334
339
|
machines in its list and no ``machine_id`` is provided.
|
335
340
|
"""
|
336
341
|
|
@@ -408,7 +413,7 @@ class Dispatcher:
|
|
408
413
|
condition: Callable[[DispatcherObserver], bool] = lambda _: True,
|
409
414
|
**kwargs,
|
410
415
|
) -> ObserverType:
|
411
|
-
"""Creates a new observer of the specified type or returns an existing
|
416
|
+
r"""Creates a new observer of the specified type or returns an existing
|
412
417
|
observer of the same type if it already exists in the dispatcher's list
|
413
418
|
of observers.
|
414
419
|
|
@@ -419,7 +424,7 @@ class Dispatcher:
|
|
419
424
|
A function that takes an observer and returns True if it is
|
420
425
|
the observer to be retrieved. By default, it returns True for
|
421
426
|
all observers.
|
422
|
-
|
427
|
+
\**kwargs:
|
423
428
|
Additional keyword arguments to be passed to the observer's
|
424
429
|
constructor.
|
425
430
|
"""
|
{job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/_optimal_operations_observer.py
RENAMED
@@ -21,10 +21,13 @@ class OptimalOperationsObserver(DispatcherObserver):
|
|
21
21
|
operations.
|
22
22
|
|
23
23
|
Args:
|
24
|
-
dispatcher:
|
25
|
-
|
24
|
+
dispatcher:
|
25
|
+
The dispatcher instance to observe.
|
26
|
+
reference_schedule:
|
27
|
+
A complete schedule that represents the optimal
|
26
28
|
or reference solution.
|
27
|
-
subscribe:
|
29
|
+
subscribe:
|
30
|
+
If True, automatically subscribes to the dispatcher.
|
28
31
|
|
29
32
|
Raises:
|
30
33
|
ValidationError: If the reference schedule is incomplete or if it
|
@@ -11,7 +11,6 @@ import numpy as np
|
|
11
11
|
from numpy.typing import NDArray
|
12
12
|
import pandas as pd
|
13
13
|
|
14
|
-
from job_shop_lib.exceptions import ValidationError
|
15
14
|
from job_shop_lib.dispatching import Dispatcher
|
16
15
|
from job_shop_lib.dispatching.feature_observers import (
|
17
16
|
FeatureObserver,
|
@@ -86,19 +85,11 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
86
85
|
for observer in dispatcher.subscribers
|
87
86
|
if isinstance(observer, FeatureObserver)
|
88
87
|
]
|
89
|
-
feature_types = self._get_feature_types_list(feature_types)
|
90
|
-
for observer in feature_observers:
|
91
|
-
if not set(observer.features.keys()).issubset(set(feature_types)):
|
92
|
-
raise ValidationError(
|
93
|
-
"The feature types observed by the feature observer "
|
94
|
-
f"{observer.__class__.__name__} are not a subset of the "
|
95
|
-
"feature types specified in the CompositeFeatureObserver."
|
96
|
-
f"Observer feature types: {observer.features.keys()}"
|
97
|
-
f"Composite feature types: {feature_types}"
|
98
|
-
)
|
99
88
|
self.feature_observers = feature_observers
|
100
89
|
self.column_names: dict[FeatureType, list[str]] = defaultdict(list)
|
101
|
-
super().__init__(
|
90
|
+
super().__init__(
|
91
|
+
dispatcher, subscribe=subscribe, feature_types=feature_types
|
92
|
+
)
|
102
93
|
self._set_column_names()
|
103
94
|
|
104
95
|
@classmethod
|
@@ -148,12 +139,17 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
148
139
|
list
|
149
140
|
)
|
150
141
|
for observer in self.feature_observers:
|
151
|
-
for feature_type
|
142
|
+
for feature_type in self.supported_feature_types:
|
143
|
+
feature_matrix = observer.features.get(feature_type)
|
144
|
+
if feature_matrix is None:
|
145
|
+
continue
|
152
146
|
features[feature_type].append(feature_matrix)
|
153
147
|
|
154
148
|
self.features = {
|
155
|
-
feature_type: np.concatenate(
|
156
|
-
|
149
|
+
feature_type: np.concatenate(
|
150
|
+
feature_matrices, axis=1 # type: ignore[misc]
|
151
|
+
)
|
152
|
+
for feature_type, feature_matrices in features.items()
|
157
153
|
}
|
158
154
|
|
159
155
|
def _set_column_names(self):
|
@@ -177,34 +173,3 @@ class CompositeFeatureObserver(FeatureObserver):
|
|
177
173
|
out.append(f"{feature_type.value}:")
|
178
174
|
out.append(dataframe.to_string())
|
179
175
|
return "\n".join(out)
|
180
|
-
|
181
|
-
|
182
|
-
if __name__ == "__main__":
|
183
|
-
# from cProfile import Profile
|
184
|
-
import time
|
185
|
-
from job_shop_lib.benchmarking import load_benchmark_instance
|
186
|
-
from job_shop_lib.dispatching.rules import DispatchingRuleSolver
|
187
|
-
|
188
|
-
ta80 = load_benchmark_instance("ta80")
|
189
|
-
|
190
|
-
dispatcher_ = Dispatcher(ta80)
|
191
|
-
feature_observer_types_ = list(FeatureObserverType)
|
192
|
-
feature_observers_ = [
|
193
|
-
feature_observer_factory(
|
194
|
-
observer_type,
|
195
|
-
dispatcher=dispatcher_,
|
196
|
-
)
|
197
|
-
for observer_type in feature_observer_types_
|
198
|
-
# and not FeatureObserverType.EARLIEST_START_TIME
|
199
|
-
]
|
200
|
-
composite_observer_ = CompositeFeatureObserver(
|
201
|
-
dispatcher_, feature_observers=feature_observers_
|
202
|
-
)
|
203
|
-
solver = DispatchingRuleSolver(dispatching_rule="random")
|
204
|
-
# profiler = Profile()
|
205
|
-
# profiler.runcall(solver.solve, dispatcher_.instance, dispatcher_)
|
206
|
-
# profiler.print_stats("cumtime")
|
207
|
-
start = time.perf_counter()
|
208
|
-
solver.solve(dispatcher_.instance, dispatcher_)
|
209
|
-
end = time.perf_counter()
|
210
|
-
print(f"Time: {end - start}")
|
@@ -93,8 +93,8 @@ class EarliestStartTimeObserver(FeatureObserver):
|
|
93
93
|
# Cache:
|
94
94
|
operations_by_machine = dispatcher.instance.operations_by_machine
|
95
95
|
self._is_regular_instance = all(
|
96
|
-
len(
|
97
|
-
for
|
96
|
+
len(machine_ops) == len(operations_by_machine[0])
|
97
|
+
for machine_ops in operations_by_machine
|
98
98
|
)
|
99
99
|
if self._is_regular_instance:
|
100
100
|
self._job_ids = np.array(
|
{job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/feature_observers/_factory.py
RENAMED
@@ -1,5 +1,3 @@
|
|
1
|
-
"""Contains factory functions for creating :class:`FeatureObserver`s."""
|
2
|
-
|
3
1
|
from enum import Enum
|
4
2
|
|
5
3
|
from job_shop_lib.dispatching import DispatcherObserverConfig
|
@@ -57,13 +55,13 @@ def feature_observer_factory(
|
|
57
55
|
),
|
58
56
|
**kwargs,
|
59
57
|
) -> FeatureObserver:
|
60
|
-
"""Creates and returns a :class:`FeatureObserver` based on the specified
|
58
|
+
r"""Creates and returns a :class:`FeatureObserver` based on the specified
|
61
59
|
:class:`FeatureObserver` type.
|
62
60
|
|
63
61
|
Args:
|
64
62
|
feature_creator_type:
|
65
63
|
The type of :class:`FeatureObserver` to create.
|
66
|
-
|
64
|
+
\*\*kwargs:
|
67
65
|
Additional keyword arguments to pass to the
|
68
66
|
:class:`FeatureObserver` constructor.
|
69
67
|
|
{job_shop_lib-1.1.1 → job_shop_lib-1.1.3}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py
RENAMED
@@ -148,49 +148,3 @@ class DispatchingRuleSolver(BaseSolver):
|
|
148
148
|
selected_operation = self.dispatching_rule(dispatcher)
|
149
149
|
machine_id = self.machine_chooser(dispatcher, selected_operation)
|
150
150
|
dispatcher.dispatch(selected_operation, machine_id)
|
151
|
-
|
152
|
-
|
153
|
-
if __name__ == "__main__":
|
154
|
-
import time
|
155
|
-
import cProfile
|
156
|
-
|
157
|
-
# import pstats
|
158
|
-
# from io import StringIO
|
159
|
-
from job_shop_lib.benchmarking import (
|
160
|
-
# load_benchmark_instance,
|
161
|
-
load_all_benchmark_instances,
|
162
|
-
)
|
163
|
-
|
164
|
-
# from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
|
165
|
-
# most_work_remaining_rule_2,
|
166
|
-
# )
|
167
|
-
|
168
|
-
# ta_instances = [
|
169
|
-
# load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
|
170
|
-
# ]
|
171
|
-
ta_instances = load_all_benchmark_instances().values()
|
172
|
-
solver = DispatchingRuleSolver(
|
173
|
-
dispatching_rule="most_work_remaining", ready_operations_filter=None
|
174
|
-
)
|
175
|
-
|
176
|
-
start = time.perf_counter()
|
177
|
-
|
178
|
-
# Create a Profile object
|
179
|
-
profiler = cProfile.Profile()
|
180
|
-
|
181
|
-
# Run the code under profiling
|
182
|
-
# profiler.enable()
|
183
|
-
for instance_ in ta_instances:
|
184
|
-
solver.solve(instance_)
|
185
|
-
# profiler.disable()
|
186
|
-
|
187
|
-
end = time.perf_counter()
|
188
|
-
|
189
|
-
# Print elapsed time
|
190
|
-
print(f"Elapsed time: {end - start:.2f} seconds.")
|
191
|
-
|
192
|
-
# Print profiling results
|
193
|
-
# s = StringIO()
|
194
|
-
# ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
|
195
|
-
# profiler.print_stats("cumtime") # Print top 20 time-consuming functions
|
196
|
-
# print(s.getvalue())
|