job-shop-lib 1.0.0a2__tar.gz → 1.0.0a3__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 (66) hide show
  1. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/PKG-INFO +15 -3
  2. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/README.md +14 -2
  3. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/_job_shop_instance.py +18 -20
  4. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/_operation.py +6 -4
  5. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/_schedule.py +3 -3
  6. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/_scheduled_operation.py +2 -2
  7. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/__init__.py +4 -0
  8. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_dispatcher.py +24 -32
  9. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_factories.py +8 -0
  10. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
  11. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +1 -1
  12. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +44 -25
  13. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
  14. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +1 -1
  15. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/__init__.py +5 -5
  16. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_gantt_chart_creator.py +5 -5
  17. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +62 -35
  18. job_shop_lib-1.0.0a2/job_shop_lib/visualization/_gantt_chart.py → job_shop_lib-1.0.0a3/job_shop_lib/visualization/_plot_gantt_chart.py +78 -14
  19. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/pyproject.toml +1 -1
  20. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/LICENSE +0 -0
  21. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/__init__.py +0 -0
  22. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/_base_solver.py +0 -0
  23. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/__init__.py +0 -0
  24. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/_load_benchmark.py +0 -0
  25. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/benchmark_instances.json +0 -0
  26. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/constraint_programming/__init__.py +0 -0
  27. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/constraint_programming/_ortools_solver.py +0 -0
  28. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_dispatcher_observer_config.py +0 -0
  29. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_history_observer.py +0 -0
  30. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_unscheduled_operations_observer.py +0 -0
  31. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/__init__.py +0 -0
  32. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -0
  33. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_duration_observer.py +0 -0
  34. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +0 -0
  35. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_factory.py +0 -0
  36. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_feature_observer.py +0 -0
  37. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +0 -0
  38. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +0 -0
  39. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +0 -0
  40. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +0 -0
  41. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/__init__.py +0 -0
  42. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +0 -0
  43. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_machine_chooser_factory.py +0 -0
  44. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_utils.py +0 -0
  45. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/exceptions.py +0 -0
  46. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/__init__.py +0 -0
  47. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_general_instance_generator.py +0 -0
  48. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_instance_generator.py +0 -0
  49. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_transformations.py +0 -0
  50. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/__init__.py +0 -0
  51. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_build_agent_task_graph.py +0 -0
  52. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_build_disjunctive_graph.py +0 -0
  53. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_constants.py +0 -0
  54. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_job_shop_graph.py +0 -0
  55. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_node.py +0 -0
  56. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/__init__.py +0 -0
  57. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_graph_updater.py +0 -0
  58. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +0 -0
  59. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_utils.py +0 -0
  60. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/__init__.py +0 -0
  61. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +0 -0
  62. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_reward_observers.py +0 -0
  63. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_types_and_constants.py +0 -0
  64. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_utils.py +0 -0
  65. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_agent_task_graph.py +0 -0
  66. {job_shop_lib-1.0.0a2 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_disjunctive_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.0.0a2
3
+ Version: 1.0.0a3
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
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
29
29
  <h1>JobShopLib</h1>
30
30
 
31
31
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
32
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
32
33
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
33
34
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
34
35
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -39,7 +40,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S
39
40
 
40
41
  It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
41
42
 
42
- See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
43
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
43
44
 
44
45
  ## Installation :package:
45
46
 
@@ -47,12 +48,23 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
47
48
 
48
49
  JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
49
50
 
50
- You can install the latest version using `pip`:
51
+ You can install the latest stable version (version 0.5.1) using `pip`:
51
52
 
52
53
  ```bash
53
54
  pip install job-shop-lib
54
55
  ```
55
56
 
57
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
58
+
59
+
60
+ Version 1.0.0 is currently in beta stage and can be installed with:
61
+
62
+ ```bash
63
+ pip install job-shop-lib==1.0.0a3
64
+ ```
65
+
66
+ Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiare yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
67
+
56
68
  <!-- end installation -->
57
69
 
58
70
  <!-- key features -->
@@ -5,6 +5,7 @@
5
5
  <h1>JobShopLib</h1>
6
6
 
7
7
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
8
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
8
9
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
9
10
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
10
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -15,7 +16,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S
15
16
 
16
17
  It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
17
18
 
18
- See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
19
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
19
20
 
20
21
  ## Installation :package:
21
22
 
@@ -23,12 +24,23 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
23
24
 
24
25
  JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
25
26
 
26
- You can install the latest version using `pip`:
27
+ You can install the latest stable version (version 0.5.1) using `pip`:
27
28
 
28
29
  ```bash
29
30
  pip install job-shop-lib
30
31
  ```
31
32
 
33
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
34
+
35
+
36
+ Version 1.0.0 is currently in beta stage and can be installed with:
37
+
38
+ ```bash
39
+ pip install job-shop-lib==1.0.0a3
40
+ ```
41
+
42
+ Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiare yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
43
+
32
44
  <!-- end installation -->
33
45
 
34
46
  <!-- key features -->
@@ -20,15 +20,27 @@ class JobShopInstance:
20
20
  computations.
21
21
 
22
22
  Attributes:
23
- jobs:
23
+ jobs (list[list[Operation]]):
24
24
  A list of lists of operations. Each list of operations represents
25
25
  a job, and the operations are ordered by their position in the job.
26
26
  The `job_id`, `position_in_job`, and `operation_id` attributes of
27
27
  the operations are set when the instance is created.
28
- name:
28
+ name (str):
29
29
  A string with the name of the instance.
30
- metadata:
30
+ metadata (dict[str, Any]):
31
31
  A dictionary with additional information about the instance.
32
+
33
+ Args:
34
+ jobs:
35
+ A list of lists of operations. Each list of operations
36
+ represents a job, and the operations are ordered by their
37
+ position in the job. The `job_id`, `position_in_job`, and
38
+ `operation_id` attributes of the operations are set when the
39
+ instance is created.
40
+ name:
41
+ A string with the name of the instance.
42
+ **metadata:
43
+ Additional information about the instance.
32
44
  """
33
45
 
34
46
  def __init__(
@@ -37,24 +49,10 @@ class JobShopInstance:
37
49
  name: str = "JobShopInstance",
38
50
  **metadata: Any,
39
51
  ):
40
- """Initializes the instance based on a list of lists of operations.
41
-
42
- Args:
43
- jobs:
44
- A list of lists of operations. Each list of operations
45
- represents a job, and the operations are ordered by their
46
- position in the job. The `job_id`, `position_in_job`, and
47
- `operation_id` attributes of the operations are set when the
48
- instance is created.
49
- name:
50
- A string with the name of the instance.
51
- **metadata:
52
- Additional information about the instance.
53
- """
54
- self.jobs = jobs
52
+ self.jobs: list[list[Operation]] = jobs
55
53
  self.set_operation_attributes()
56
- self.name = name
57
- self.metadata = metadata
54
+ self.name: str = name
55
+ self.metadata: dict[str, Any] = metadata
58
56
 
59
57
  def set_operation_attributes(self):
60
58
  """Sets the job_id and position of each operation."""
@@ -51,8 +51,10 @@ class Operation:
51
51
  }
52
52
 
53
53
  def __init__(self, machines: int | list[int], duration: int):
54
- self.machines = [machines] if isinstance(machines, int) else machines
55
- self.duration = duration
54
+ self.machines: list[int] = (
55
+ [machines] if isinstance(machines, int) else machines
56
+ )
57
+ self.duration: int = duration
56
58
 
57
59
  # Defined outside the class by the JobShopInstance class:
58
60
  self.job_id: int = -1
@@ -64,8 +66,8 @@ class Operation:
64
66
  """Returns the id of the machine associated with the operation.
65
67
 
66
68
  Raises:
67
- UninitializedAttributeError: If the operation has multiple machines
68
- in its list.
69
+ UninitializedAttributeError:
70
+ If the operation has multiple machines in its list.
69
71
  """
70
72
  if len(self.machines) > 1:
71
73
  raise UninitializedAttributeError(
@@ -46,7 +46,7 @@ class Schedule:
46
46
  self,
47
47
  instance: JobShopInstance,
48
48
  schedule: list[list[ScheduledOperation]] | None = None,
49
- **metadata,
49
+ **metadata: Any,
50
50
  ):
51
51
  """Initializes the object with the given instance and schedule.
52
52
 
@@ -65,9 +65,9 @@ class Schedule:
65
65
 
66
66
  Schedule.check_schedule(schedule)
67
67
 
68
- self.instance = instance
68
+ self.instance: JobShopInstance = instance
69
69
  self._schedule = schedule
70
- self.metadata = metadata
70
+ self.metadata: dict[str, Any] = metadata
71
71
 
72
72
  def __repr__(self) -> str:
73
73
  return str(self.schedule)
@@ -31,8 +31,8 @@ class ScheduledOperation:
31
31
  If the given machine_id is not in the list of valid machines
32
32
  for the operation.
33
33
  """
34
- self.operation = operation
35
- self.start_time = start_time
34
+ self.operation: Operation = operation
35
+ self.start_time: int = start_time
36
36
  self._machine_id = machine_id
37
37
  self.machine_id = machine_id # Validate machine_id
38
38
 
@@ -32,6 +32,8 @@ from ._ready_operation_filters import (
32
32
  filter_dominated_operations,
33
33
  filter_non_immediate_machines,
34
34
  ReadyOperationsFilter,
35
+ filter_non_idle_machines,
36
+ filter_non_immediate_operations,
35
37
  )
36
38
  from ._dispatcher_observer_config import DispatcherObserverConfig
37
39
  from ._factories import (
@@ -53,4 +55,6 @@ __all__ = [
53
55
  "DispatcherObserverConfig",
54
56
  "UnscheduledOperationsObserver",
55
57
  "ReadyOperationsFilter",
58
+ "filter_non_idle_machines",
59
+ "filter_non_immediate_operations",
56
60
  ]
@@ -156,26 +156,30 @@ class Dispatcher:
156
156
  responsible for scheduling the operations on the machines and keeping
157
157
  track of the next available time for each machine and job.
158
158
 
159
- Attributes:
159
+ Args:
160
160
  instance:
161
- The instance of the job shop problem to be scheduled.
162
- schedule:
163
- The schedule of operations on machines.
161
+ The instance of the job shop problem to be solved.
164
162
  ready_operations_filter:
165
- A function that filters out operations that are not ready to be
166
- scheduled.
163
+ A function that filters out operations that are not ready to
164
+ be scheduled. The function should take the dispatcher and a
165
+ list of operations as input and return a list of operations
166
+ that are ready to be scheduled. If ``None``, no filtering is
167
+ done.
167
168
  """
168
169
 
169
- __slots__ = (
170
- "instance",
171
- "schedule",
172
- "_machine_next_available_time",
173
- "_job_next_operation_index",
174
- "_job_next_available_time",
175
- "ready_operations_filter",
176
- "subscribers",
177
- "_cache",
178
- )
170
+ __slots__ = {
171
+ "instance": "The instance of the job shop problem to be scheduled.",
172
+ "schedule": "The schedule of operations on machines.",
173
+ "_machine_next_available_time": "",
174
+ "_job_next_operation_index": "",
175
+ "_job_next_available_time": "",
176
+ "ready_operations_filter": (
177
+ "A function that filters out operations that are not ready to be "
178
+ "scheduled."
179
+ ),
180
+ "subscribers": "A list of observers subscribed to the dispatcher.",
181
+ "_cache": "A dictionary to cache the results of the cached methods.",
182
+ }
179
183
 
180
184
  def __init__(
181
185
  self,
@@ -184,18 +188,6 @@ class Dispatcher:
184
188
  Callable[[Dispatcher, list[Operation]], list[Operation]] | None
185
189
  ) = None,
186
190
  ) -> None:
187
- """Initializes the object with the given instance.
188
-
189
- Args:
190
- instance:
191
- The instance of the job shop problem to be solved.
192
- ready_operations_filter:
193
- A function that filters out operations that are not ready to
194
- be scheduled. The function should take the dispatcher and a
195
- list of operations as input and return a list of operations
196
- that are ready to be scheduled. If ``None``, no filtering is
197
- done.
198
- """
199
191
 
200
192
  self.instance = instance
201
193
  self.schedule = Schedule(self.instance)
@@ -371,7 +363,7 @@ class Dispatcher:
371
363
  The current time is the minimum start time of the available
372
364
  operations.
373
365
  """
374
- available_operations = self.ready_operations()
366
+ available_operations = self.available_operations()
375
367
  current_time = self.min_start_time(available_operations)
376
368
  return current_time
377
369
 
@@ -387,7 +379,7 @@ class Dispatcher:
387
379
  return int(min_start_time)
388
380
 
389
381
  @_dispatcher_cache
390
- def ready_operations(self) -> list[Operation]:
382
+ def available_operations(self) -> list[Operation]:
391
383
  """Returns a list of available operations for processing, optionally
392
384
  filtering out operations using the filter function.
393
385
 
@@ -443,7 +435,7 @@ class Dispatcher:
443
435
  @_dispatcher_cache
444
436
  def available_machines(self) -> list[int]:
445
437
  """Returns the list of ready machines."""
446
- available_operations = self.ready_operations()
438
+ available_operations = self.available_operations()
447
439
  available_machines = set()
448
440
  for operation in available_operations:
449
441
  available_machines.update(operation.machines)
@@ -452,7 +444,7 @@ class Dispatcher:
452
444
  @_dispatcher_cache
453
445
  def available_jobs(self) -> list[int]:
454
446
  """Returns the list of ready jobs."""
455
- available_operations = self.ready_operations()
447
+ available_operations = self.available_operations()
456
448
  available_jobs = set(
457
449
  operation.job_id for operation in available_operations
458
450
  )
@@ -13,6 +13,8 @@ from job_shop_lib.dispatching import (
13
13
  Dispatcher,
14
14
  filter_dominated_operations,
15
15
  filter_non_immediate_machines,
16
+ filter_non_idle_machines,
17
+ filter_non_immediate_operations,
16
18
  ReadyOperationsFilter,
17
19
  )
18
20
 
@@ -27,6 +29,8 @@ class ReadyOperationsFilterType(str, Enum):
27
29
 
28
30
  DOMINATED_OPERATIONS = "dominated_operations"
29
31
  NON_IMMEDIATE_MACHINES = "non_immediate_machines"
32
+ NON_IDLE_MACHINES = "non_idle_machines"
33
+ NON_IMMEDIATE_OPERATIONS = "non_immediate_operations"
30
34
 
31
35
 
32
36
  def create_composite_operation_filter(
@@ -114,6 +118,10 @@ def ready_operations_filter_factory(
114
118
  ReadyOperationsFilterType.NON_IMMEDIATE_MACHINES: (
115
119
  filter_non_immediate_machines
116
120
  ),
121
+ ReadyOperationsFilterType.NON_IDLE_MACHINES: filter_non_idle_machines,
122
+ ReadyOperationsFilterType.NON_IMMEDIATE_OPERATIONS: (
123
+ filter_non_immediate_operations
124
+ ),
117
125
  }
118
126
 
119
127
  if filter_name not in filtering_strategies:
@@ -15,6 +15,86 @@ ReadyOperationsFilter = Callable[
15
15
  ]
16
16
 
17
17
 
18
+ def filter_non_idle_machines(
19
+ dispatcher: Dispatcher, operations: list[Operation]
20
+ ) -> list[Operation]:
21
+ """Filters out all the operations associated with non-idle machines.
22
+
23
+ A machine is considered idle if there are no ongoing operations
24
+ currently scheduled on it. This filter removes operations that are
25
+ associated with machines that are busy (i.e., have at least one
26
+ uncompleted operation).
27
+
28
+ Utilizes :meth:``Dispatcher.ongoing_operations()`` to determine machine
29
+ statuses.
30
+
31
+ Args:
32
+ dispatcher: The dispatcher object.
33
+ operations: The list of operations to filter.
34
+
35
+ Returns:
36
+ The list of operations that are associated with idle machines.
37
+ """
38
+ current_time = dispatcher.min_start_time(operations)
39
+ non_idle_machines = _get_non_idle_machines(dispatcher, current_time)
40
+
41
+ # Filter operations to keep those that are associated with at least one
42
+ # idle machine
43
+ filtered_operations: list[Operation] = []
44
+ for operation in operations:
45
+ if all(
46
+ machine_id in non_idle_machines
47
+ for machine_id in operation.machines
48
+ ):
49
+ continue
50
+ filtered_operations.append(operation)
51
+
52
+ return filtered_operations
53
+
54
+
55
+ def _get_non_idle_machines(
56
+ dispatcher: Dispatcher, current_time: int
57
+ ) -> set[int]:
58
+ """Returns the set of machine ids that are currently busy (i.e., have at
59
+ least one uncompleted operation)."""
60
+
61
+ non_idle_machines = set()
62
+ for machine_schedule in dispatcher.schedule.schedule:
63
+ for scheduled_operation in reversed(machine_schedule):
64
+ is_completed = scheduled_operation.end_time <= current_time
65
+ if is_completed:
66
+ break
67
+ non_idle_machines.add(scheduled_operation.machine_id)
68
+
69
+ return non_idle_machines
70
+
71
+
72
+ def filter_non_immediate_operations(
73
+ dispatcher: Dispatcher, operations: list[Operation]
74
+ ) -> list[Operation]:
75
+ """Filters out all the operations that can't start immediately.
76
+
77
+ An operation can start immediately if its earliest start time is the
78
+ current time.
79
+
80
+ The current time is determined by the minimum start time of the
81
+ operations.
82
+
83
+ Args:
84
+ dispatcher: The dispatcher object.
85
+ operations: The list of operations to filter.
86
+ """
87
+
88
+ min_start_time = dispatcher.min_start_time(operations)
89
+ immediate_operations: list[Operation] = []
90
+ for operation in operations:
91
+ start_time = dispatcher.earliest_start_time(operation)
92
+ if start_time == min_start_time:
93
+ immediate_operations.append(operation)
94
+
95
+ return immediate_operations
96
+
97
+
18
98
  def filter_dominated_operations(
19
99
  dispatcher: Dispatcher, operations: list[Operation]
20
100
  ) -> list[Operation]:
@@ -29,5 +29,5 @@ class IsReadyObserver(FeatureObserver):
29
29
  self.initialize_features()
30
30
 
31
31
  def _get_ready_operations(self) -> list[int]:
32
- available_operations = self.dispatcher.ready_operations()
32
+ available_operations = self.dispatcher.available_operations()
33
33
  return [operation.operation_id for operation in available_operations]
@@ -1,12 +1,14 @@
1
1
  """Home of the `DispatchingRuleSolver` class."""
2
2
 
3
- from collections.abc import Callable
3
+ from collections.abc import Callable, Iterable
4
4
 
5
5
  from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
6
6
  from job_shop_lib.dispatching import (
7
7
  ready_operations_filter_factory,
8
8
  Dispatcher,
9
9
  ReadyOperationsFilterType,
10
+ ReadyOperationsFilter,
11
+ create_composite_operation_filter,
10
12
  )
11
13
  from job_shop_lib.dispatching.rules import (
12
14
  dispatching_rule_factory,
@@ -30,6 +32,35 @@ class DispatchingRuleSolver(BaseSolver):
30
32
  pruning_function:
31
33
  The pruning function to use. It is used to initialize the
32
34
  dispatcher object internally when calling the solve method.
35
+
36
+ Args:
37
+ dispatching_rule:
38
+ The dispatching rule to use. It can be a string with the name
39
+ of the dispatching rule, a class`DispatchingRuleType` enum member,
40
+ or a callable that takes a dispatcher and returns the operation to
41
+ be dispatched next.
42
+ machine_chooser:
43
+ The machine chooser to use. It can be a string with the name
44
+ of the machine chooser, a :class:`MachineChooserType` member, or a
45
+ callable that takes a dispatcher and an operation and returns
46
+ the machine id where the operation will be dispatched.
47
+ ready_operations_filter:
48
+ The ready operations filter to use. It can be either:
49
+
50
+ - a string with the name of the pruning function
51
+ - a :class`ReadyOperationsFilterType` enum member.
52
+ - a callable that takes a dispatcher and a list of operations
53
+ and returns a list of operations that should be considered
54
+ for dispatching,
55
+ - a list with names or actual ready operations filters to be used.
56
+ If a list is provided, a composite filter will be created
57
+ using the specified filters.
58
+
59
+ .. seealso::
60
+ - :func:`job_shop_lib.dispatching.rules.dispatching_rule_factory`
61
+ - :func:`job_shop_lib.dispatching.rules.machine_chooser_factory`
62
+ - :func:`~job_shop_lib.dispatching.ready_operations_filter_factory`
63
+ - :func:`~job_shop_lib.dispatching.create_composite_operation_filter`
33
64
  """
34
65
 
35
66
  def __init__(
@@ -41,32 +72,16 @@ class DispatchingRuleSolver(BaseSolver):
41
72
  str | Callable[[Dispatcher, Operation], int]
42
73
  ) = MachineChooserType.FIRST,
43
74
  ready_operations_filter: (
44
- str
45
- | Callable[[Dispatcher, list[Operation]], list[Operation]]
75
+ Iterable[ReadyOperationsFilter | str | ReadyOperationsFilterType]
76
+ | str
77
+ | ReadyOperationsFilterType
78
+ | ReadyOperationsFilter
46
79
  | None
47
- ) = ReadyOperationsFilterType.DOMINATED_OPERATIONS,
80
+ ) = (
81
+ ReadyOperationsFilterType.DOMINATED_OPERATIONS,
82
+ ReadyOperationsFilterType.NON_IDLE_MACHINES,
83
+ ),
48
84
  ):
49
- """Initializes the solver with the given dispatching rule, machine
50
- chooser and pruning function.
51
-
52
- Args:
53
- dispatching_rule:
54
- The dispatching rule to use. It can be a string with the name
55
- of the dispatching rule, a DispatchingRule enum member, or a
56
- callable that takes a dispatcher and returns the operation to
57
- be dispatched next.
58
- machine_chooser:
59
- The machine chooser to use. It can be a string with the name
60
- of the machine chooser, a MachineChooser enum member, or a
61
- callable that takes a dispatcher and an operation and returns
62
- the machine id where the operation will be dispatched.
63
- ready_operations_filter:
64
- The ready operations filter to use. It can be a string with
65
- the name of the pruning function, a PruningFunction enum
66
- member, or a callable that takes a dispatcher and a list of
67
- operations and returns a list of operations that should be
68
- considered for dispatching.
69
- """
70
85
  if isinstance(dispatching_rule, str):
71
86
  dispatching_rule = dispatching_rule_factory(dispatching_rule)
72
87
  if isinstance(machine_chooser, str):
@@ -75,6 +90,10 @@ class DispatchingRuleSolver(BaseSolver):
75
90
  ready_operations_filter = ready_operations_filter_factory(
76
91
  ready_operations_filter
77
92
  )
93
+ if isinstance(ready_operations_filter, Iterable):
94
+ ready_operations_filter = create_composite_operation_filter(
95
+ ready_operations_filter
96
+ )
78
97
 
79
98
  self.dispatching_rule = dispatching_rule
80
99
  self.machine_chooser = machine_chooser
@@ -21,7 +21,7 @@ from job_shop_lib.dispatching.feature_observers import (
21
21
  def shortest_processing_time_rule(dispatcher: Dispatcher) -> Operation:
22
22
  """Dispatches the operation with the shortest duration."""
23
23
  return min(
24
- dispatcher.ready_operations(),
24
+ dispatcher.available_operations(),
25
25
  key=lambda operation: operation.duration,
26
26
  )
27
27
 
@@ -29,7 +29,7 @@ def shortest_processing_time_rule(dispatcher: Dispatcher) -> Operation:
29
29
  def first_come_first_served_rule(dispatcher: Dispatcher) -> Operation:
30
30
  """Dispatches the operation with the lowest position in job."""
31
31
  return min(
32
- dispatcher.ready_operations(),
32
+ dispatcher.available_operations(),
33
33
  key=lambda operation: operation.position_in_job,
34
34
  )
35
35
 
@@ -41,7 +41,7 @@ def most_work_remaining_rule(dispatcher: Dispatcher) -> Operation:
41
41
  job_remaining_work[operation.job_id] += operation.duration
42
42
 
43
43
  return max(
44
- dispatcher.ready_operations(),
44
+ dispatcher.available_operations(),
45
45
  key=lambda operation: job_remaining_work[operation.job_id],
46
46
  )
47
47
 
@@ -53,14 +53,14 @@ def most_operations_remaining_rule(dispatcher: Dispatcher) -> Operation:
53
53
  job_remaining_operations[operation.job_id] += 1
54
54
 
55
55
  return max(
56
- dispatcher.ready_operations(),
56
+ dispatcher.available_operations(),
57
57
  key=lambda operation: job_remaining_operations[operation.job_id],
58
58
  )
59
59
 
60
60
 
61
61
  def random_operation_rule(dispatcher: Dispatcher) -> Operation:
62
62
  """Dispatches a random operation."""
63
- return random.choice(dispatcher.ready_operations())
63
+ return random.choice(dispatcher.available_operations())
64
64
 
65
65
 
66
66
  def score_based_rule(
@@ -80,7 +80,7 @@ def score_based_rule(
80
80
  def rule(dispatcher: Dispatcher) -> Operation:
81
81
  scores = score_function(dispatcher)
82
82
  return max(
83
- dispatcher.ready_operations(),
83
+ dispatcher.available_operations(),
84
84
  key=lambda operation: scores[operation.job_id],
85
85
  )
86
86
 
@@ -102,7 +102,7 @@ def score_based_rule_with_tie_breaker(
102
102
  """
103
103
 
104
104
  def rule(dispatcher: Dispatcher) -> Operation:
105
- candidates = dispatcher.ready_operations()
105
+ candidates = dispatcher.available_operations()
106
106
  for scoring_function in score_functions:
107
107
  scores = scoring_function(dispatcher)
108
108
  best_score = max(scores)
@@ -126,7 +126,7 @@ def shortest_processing_time_score(dispatcher: Dispatcher) -> list[int]:
126
126
  """Scores each job based on the duration of the next operation."""
127
127
  num_jobs = dispatcher.instance.num_jobs
128
128
  scores = [0] * num_jobs
129
- for operation in dispatcher.ready_operations():
129
+ for operation in dispatcher.available_operations():
130
130
  scores[operation.job_id] = -operation.duration
131
131
  return scores
132
132
 
@@ -135,7 +135,7 @@ def first_come_first_served_score(dispatcher: Dispatcher) -> list[int]:
135
135
  """Scores each job based on the position of the next operation."""
136
136
  num_jobs = dispatcher.instance.num_jobs
137
137
  scores = [0] * num_jobs
138
- for operation in dispatcher.ready_operations():
138
+ for operation in dispatcher.available_operations():
139
139
  scores[operation.job_id] = operation.operation_id
140
140
  return scores
141
141
 
@@ -260,7 +260,7 @@ class SingleJobShopGraphEnv(gym.Env):
260
260
  truncated = False
261
261
  info: dict[str, Any] = {
262
262
  "feature_names": self.composite_observer.column_names,
263
- "available_operations": self.dispatcher.ready_operations(),
263
+ "available_operations": self.dispatcher.available_operations(),
264
264
  }
265
265
  return obs, reward, done, truncated, info
266
266