job-shop-lib 1.1.2__tar.gz → 1.2.0__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.
Files changed (73) hide show
  1. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/PKG-INFO +87 -18
  2. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/README.md +86 -17
  3. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/__init__.py +1 -1
  4. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/__init__.py +3 -0
  5. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_start_time_calculators.py +49 -0
  6. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_feature_observer.py +3 -0
  7. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +106 -11
  8. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/generation/_instance_generator.py +3 -3
  9. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/pyproject.toml +2 -2
  10. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/LICENSE +0 -0
  11. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/_base_solver.py +0 -0
  12. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/_job_shop_instance.py +0 -0
  13. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/_operation.py +0 -0
  14. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/_schedule.py +0 -0
  15. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/_scheduled_operation.py +0 -0
  16. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/benchmarking/__init__.py +0 -0
  17. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/benchmarking/_load_benchmark.py +0 -0
  18. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/benchmarking/benchmark_instances.json +0 -0
  19. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/constraint_programming/__init__.py +0 -0
  20. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/constraint_programming/_ortools_solver.py +0 -0
  21. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_dispatcher.py +0 -0
  22. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_dispatcher_observer_config.py +0 -0
  23. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_factories.py +0 -0
  24. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_history_observer.py +0 -0
  25. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_optimal_operations_observer.py +0 -0
  26. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_ready_operation_filters.py +0 -0
  27. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/_unscheduled_operations_observer.py +0 -0
  28. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/__init__.py +0 -0
  29. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -0
  30. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_duration_observer.py +0 -0
  31. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +0 -0
  32. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_factory.py +0 -0
  33. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +0 -0
  34. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +0 -0
  35. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +0 -0
  36. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +0 -0
  37. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +0 -0
  38. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/__init__.py +0 -0
  39. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +0 -0
  40. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +0 -0
  41. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/_machine_chooser_factory.py +0 -0
  42. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/dispatching/rules/_utils.py +0 -0
  43. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/exceptions.py +0 -0
  44. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/generation/__init__.py +0 -0
  45. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/generation/_general_instance_generator.py +0 -0
  46. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/generation/_utils.py +0 -0
  47. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/__init__.py +0 -0
  48. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/_build_disjunctive_graph.py +0 -0
  49. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/_build_resource_task_graphs.py +0 -0
  50. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/_constants.py +0 -0
  51. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/_job_shop_graph.py +0 -0
  52. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/_node.py +0 -0
  53. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/graph_updaters/__init__.py +0 -0
  54. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +0 -0
  55. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/graph_updaters/_graph_updater.py +0 -0
  56. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +0 -0
  57. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/graphs/graph_updaters/_utils.py +0 -0
  58. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/py.typed +0 -0
  59. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/__init__.py +0 -0
  60. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +0 -0
  61. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +0 -0
  62. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_reward_observers.py +0 -0
  63. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +0 -0
  64. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_types_and_constants.py +0 -0
  65. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/reinforcement_learning/_utils.py +0 -0
  66. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/__init__.py +0 -0
  67. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/gantt/__init__.py +0 -0
  68. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/gantt/_gantt_chart_creator.py +0 -0
  69. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +0 -0
  70. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/gantt/_plot_gantt_chart.py +0 -0
  71. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/graphs/__init__.py +0 -0
  72. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +0 -0
  73. {job_shop_lib-1.1.2 → job_shop_lib-1.2.0}/job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.1.2
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
- For more details, check the [examples](examples) folder.
351
+ ### Gymnasium Environments
334
352
 
335
- ## Installation for development
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
- 1. Clone the repository.
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
- ```bash
342
- git clone https://github.com/Pabloo22/job_shop_lib.git
343
- cd job_shop_lib
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
- ```bash
349
- pip install poetry
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
- 3. Install dependencies:
353
- ```bash
354
- make poetry_install_all
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
- <!-- end installation development -->
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
 
@@ -61,6 +61,24 @@ pip install job-shop-lib
61
61
 
62
62
  <!-- end key features -->
63
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
+
64
82
  ## Some Examples :rocket:
65
83
 
66
84
  ### Create a Job Shop Instance
@@ -305,31 +323,82 @@ plt.show()
305
323
 
306
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`).
307
325
 
308
- For more details, check the [examples](examples) folder.
326
+ ### Gymnasium Environments
309
327
 
310
- ## Installation for development
328
+ <div align="center">
329
+ <img src="docs/source/images/rl_diagram.png">
330
+ </div>
331
+ <br>
311
332
 
312
- <!-- start installation development -->
313
333
 
314
- 1. Clone the repository.
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).
315
335
 
316
- ```bash
317
- git clone https://github.com/Pabloo22/job_shop_lib.git
318
- cd job_shop_lib
319
- ```
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
+ )
320
371
 
321
- 2. Install [poetry](https://python-poetry.org/docs/) if you don't have it already:
322
372
 
323
- ```bash
324
- pip install poetry
325
- ```
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)
326
380
 
327
- 3. Install dependencies:
328
- ```bash
329
- make poetry_install_all
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()
330
398
  ```
331
399
 
332
- <!-- end installation development -->
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.
333
402
 
334
403
  ## License :scroll:
335
404
 
@@ -366,4 +435,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
366
435
  - E. Taillard, "Benchmarks for basic scheduling problems," European
367
436
  Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
368
437
 
369
- - 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.
@@ -19,7 +19,7 @@ from job_shop_lib._schedule import Schedule
19
19
  from job_shop_lib._base_solver import BaseSolver, Solver
20
20
 
21
21
 
22
- __version__ = "1.1.2"
22
+ __version__ = "1.2.0"
23
23
 
24
24
  __all__ = [
25
25
  "Operation",
@@ -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
@@ -92,6 +92,9 @@ class FeatureObserver(DispatcherObserver):
92
92
  feature_type: self._feature_sizes
93
93
  for feature_type in feature_types
94
94
  }
95
+ else:
96
+ feature_size = self._feature_sizes
97
+
95
98
  super().__init__(dispatcher, subscribe=subscribe)
96
99
 
97
100
  number_of_entities = {
@@ -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: A function that takes a Dispatcher instance as input
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: A list of scoring functions that take a Dispatcher
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 instead
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 abc
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(abc.ABC):
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
- @abc.abstractmethod
78
+ @abstractmethod
79
79
  def generate(
80
80
  self,
81
81
  num_jobs: int | None = None,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "job-shop-lib"
3
- version = "1.1.2"
3
+ version = "1.2.0"
4
4
  description = "An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)"
5
5
  authors = ["Pabloo22 <pablete.arino@gmail.com>"]
6
6
  license = "MIT"
@@ -64,7 +64,7 @@ ipython = "^8.31.0"
64
64
  pygraphviz = ["pygraphviz"]
65
65
 
66
66
  [build-system]
67
- requires = ["poetry-core"]
67
+ requires = ["poetry-core==1.8.2"] # Enforces specific Poetry version
68
68
  build-backend = "poetry.core.masonry.api"
69
69
 
70
70
  [tool.black]
File without changes