job-shop-lib 1.0.0a2__py3-none-any.whl → 1.0.0a4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. job_shop_lib/_job_shop_instance.py +119 -55
  2. job_shop_lib/_operation.py +18 -7
  3. job_shop_lib/_schedule.py +13 -15
  4. job_shop_lib/_scheduled_operation.py +17 -18
  5. job_shop_lib/dispatching/__init__.py +4 -0
  6. job_shop_lib/dispatching/_dispatcher.py +36 -47
  7. job_shop_lib/dispatching/_dispatcher_observer_config.py +15 -2
  8. job_shop_lib/dispatching/_factories.py +10 -2
  9. job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
  10. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -1
  11. job_shop_lib/dispatching/feature_observers/_factory.py +21 -18
  12. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +1 -0
  13. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +1 -1
  14. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +44 -25
  15. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
  16. job_shop_lib/generation/_general_instance_generator.py +33 -34
  17. job_shop_lib/generation/_instance_generator.py +14 -17
  18. job_shop_lib/generation/_transformations.py +11 -8
  19. job_shop_lib/graphs/__init__.py +3 -0
  20. job_shop_lib/graphs/_build_disjunctive_graph.py +41 -3
  21. job_shop_lib/graphs/graph_updaters/_graph_updater.py +11 -13
  22. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +17 -20
  23. job_shop_lib/reinforcement_learning/__init__.py +16 -7
  24. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +69 -57
  25. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +43 -32
  26. job_shop_lib/reinforcement_learning/_types_and_constants.py +2 -2
  27. job_shop_lib/visualization/__init__.py +29 -10
  28. job_shop_lib/visualization/_gantt_chart_creator.py +122 -84
  29. job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +68 -37
  30. job_shop_lib/visualization/_plot_disjunctive_graph.py +382 -0
  31. job_shop_lib/visualization/{_gantt_chart.py → _plot_gantt_chart.py} +78 -14
  32. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/METADATA +15 -3
  33. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/RECORD +36 -36
  34. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/WHEEL +1 -1
  35. job_shop_lib/visualization/_disjunctive_graph.py +0 -210
  36. /job_shop_lib/visualization/{_agent_task_graph.py → _plot_agent_task_graph.py} +0 -0
  37. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/LICENSE +0 -0
@@ -15,49 +15,105 @@ from job_shop_lib import Operation
15
15
  class JobShopInstance:
16
16
  """Data structure to store a Job Shop Scheduling Problem instance.
17
17
 
18
- Additional attributes such as `num_jobs` or `num_machines` can be computed
19
- from the instance and are cached for performance if they require expensive
20
- computations.
18
+ Additional attributes such as ``num_machines`` or ``durations_matrix`` can
19
+ be computed from the instance and are cached for performance if they
20
+ require expensive computations.
21
+
22
+ Methods:
23
+
24
+ .. autosummary::
25
+ :nosignatures:
26
+
27
+ from_taillard_file
28
+ to_dict
29
+ from_matrices
30
+ set_operation_attributes
31
+
32
+ Properties:
33
+
34
+ .. autosummary::
35
+ :nosignatures:
36
+
37
+ num_jobs
38
+ num_machines
39
+ num_operations
40
+ is_flexible
41
+ durations_matrix
42
+ machines_matrix
43
+ durations_matrix_array
44
+ machines_matrix_array
45
+ operations_by_machine
46
+ max_duration
47
+ max_duration_per_job
48
+ max_duration_per_machine
49
+ job_durations
50
+ machine_loads
51
+ total_duration
21
52
 
22
53
  Attributes:
23
- jobs:
54
+ jobs (list[list[Operation]]):
24
55
  A list of lists of operations. Each list of operations represents
25
56
  a job, and the operations are ordered by their position in the job.
26
- The `job_id`, `position_in_job`, and `operation_id` attributes of
27
- the operations are set when the instance is created.
28
- name:
57
+ The ``job_id``, ``position_in_job``, and `operation_id` attributes
58
+ of the operations are set when the instance is created.
59
+ name (str):
29
60
  A string with the name of the instance.
30
- metadata:
61
+ metadata (dict[str, Any]):
31
62
  A dictionary with additional information about the instance.
63
+
64
+ Args:
65
+ jobs:
66
+ A list of lists of operations. Each list of operations
67
+ represents a job, and the operations are ordered by their
68
+ position in the job. The ``job_id``, ``position_in_job``, and
69
+ ``operation_id`` attributes of the operations are set when the
70
+ instance is created.
71
+ name:
72
+ A string with the name of the instance.
73
+ set_operation_attributes:
74
+ If True, the ``job_id``, ``position_in_job``, and ``operation_id``
75
+ attributes of the operations are set when the instance is created.
76
+ See :meth:`set_operation_attributes` for more information. Defaults
77
+ to True.
78
+ **metadata:
79
+ Additional information about the instance.
32
80
  """
33
81
 
34
82
  def __init__(
35
83
  self,
36
84
  jobs: list[list[Operation]],
37
85
  name: str = "JobShopInstance",
86
+ set_operation_attributes: bool = True,
38
87
  **metadata: Any,
39
88
  ):
40
- """Initializes the instance based on a list of lists of operations.
89
+ self.jobs: list[list[Operation]] = jobs
90
+ if set_operation_attributes:
91
+ self.set_operation_attributes()
92
+ self.name: str = name
93
+ self.metadata: dict[str, Any] = metadata
94
+
95
+ def set_operation_attributes(self):
96
+ """Sets the ``job_id``, ``position_in_job``, and ``operation_id``
97
+ attributes for each operation in the instance.
98
+
99
+ The ``job_id`` attribute is set to the id of the job to which the
100
+ operation belongs.
101
+
102
+ The ``position_in_job`` attribute is set to the
103
+ position of the operation in the job (starts from 0).
104
+
105
+ The ``operation_id`` attribute is set to a unique identifier for the
106
+ operation (starting from 0).
107
+
108
+ The formula to compute the ``operation_id`` in a job shop instance with
109
+ a fixed number of operations per job is:
110
+
111
+ .. code-block:: python
112
+
113
+ operation_id = job_id * num_operations_per_job + position_in_job
41
114
 
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
115
  """
54
- self.jobs = jobs
55
- self.set_operation_attributes()
56
- self.name = name
57
- self.metadata = metadata
58
116
 
59
- def set_operation_attributes(self):
60
- """Sets the job_id and position of each operation."""
61
117
  operation_id = 0
62
118
  for job_id, job in enumerate(self.jobs):
63
119
  for position, operation in enumerate(job):
@@ -92,8 +148,8 @@ class JobShopInstance:
92
148
  Additional information about the instance.
93
149
 
94
150
  Returns:
95
- A JobShopInstance object with the operations read from the file,
96
- and the name and metadata provided.
151
+ A :class:`JobShopInstance` object with the operations read from the
152
+ file, and the name and metadata provided.
97
153
  """
98
154
  with open(file_path, "r", encoding=encoding) as file:
99
155
  lines = file.readlines()
@@ -130,13 +186,17 @@ class JobShopInstance:
130
186
  like Taillard's.
131
187
 
132
188
  Returns:
133
- The returned dictionary has the following structure:
134
- {
135
- "name": self.name,
136
- "duration_matrix": self.durations_matrix,
137
- "machines_matrix": self.machines_matrix,
138
- "metadata": self.metadata,
139
- }
189
+ dict[str, Any]: The returned dictionary has the following
190
+ structure:
191
+
192
+ .. code-block:: python
193
+
194
+ {
195
+ "name": self.name,
196
+ "duration_matrix": self.durations_matrix,
197
+ "machines_matrix": self.machines_matrix,
198
+ "metadata": self.metadata,
199
+ }
140
200
  """
141
201
  return {
142
202
  "name": self.name,
@@ -153,7 +213,8 @@ class JobShopInstance:
153
213
  name: str = "JobShopInstance",
154
214
  metadata: dict[str, Any] | None = None,
155
215
  ) -> JobShopInstance:
156
- """Creates a JobShopInstance from duration and machines matrices.
216
+ """Creates a :class:`JobShopInstance` from duration and machines
217
+ matrices.
157
218
 
158
219
  Args:
159
220
  duration_matrix:
@@ -170,7 +231,7 @@ class JobShopInstance:
170
231
  A dictionary with additional information about the instance.
171
232
 
172
233
  Returns:
173
- A JobShopInstance object.
234
+ A :class:`JobShopInstance` object.
174
235
  """
175
236
  jobs: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]
176
237
 
@@ -222,7 +283,7 @@ class JobShopInstance:
222
283
 
223
284
  @functools.cached_property
224
285
  def is_flexible(self) -> bool:
225
- """Returns True if any operation has more than one machine."""
286
+ """Returns ``True`` if any operation has more than one machine."""
226
287
  return any(
227
288
  any(len(operation.machines) > 1 for operation in job)
228
289
  for job in self.jobs
@@ -232,12 +293,14 @@ class JobShopInstance:
232
293
  def durations_matrix(self) -> list[list[int]]:
233
294
  """Returns the duration matrix of the instance.
234
295
 
235
- The duration of the operation with `job_id` i and `position_in_job` j
236
- is stored in the i-th position of the j-th list of the returned matrix:
296
+ The duration of the operation with ``job_id`` i and ``position_in_job``
297
+ j is stored in the i-th position of the j-th list of the returned
298
+ matrix:
299
+
300
+ .. code-block:: python
301
+
302
+ duration = instance.durations_matrix[i][j]
237
303
 
238
- ```python
239
- duration = instance.durations_matrix[i][j]
240
- ```
241
304
  """
242
305
  return [[operation.duration for operation in job] for job in self.jobs]
243
306
 
@@ -254,9 +317,9 @@ class JobShopInstance:
254
317
  To access the machines of the operation with position i in the job
255
318
  with id j, the following code must be used:
256
319
 
257
- ```python
258
- machines = instance.machines_matrix[j][i]
259
- ```
320
+ .. code-block:: python
321
+
322
+ machines = instance.machines_matrix[j][i]
260
323
 
261
324
  """
262
325
  if self.is_flexible:
@@ -271,8 +334,9 @@ class JobShopInstance:
271
334
  def durations_matrix_array(self) -> NDArray[np.float32]:
272
335
  """Returns the duration matrix of the instance as a numpy array.
273
336
 
274
- The returned array has shape (num_jobs, max_num_operations_per_job).
275
- Non-existing operations are filled with np.nan.
337
+ The returned array has shape (``num_jobs``,
338
+ ``max_num_operations_per_job``).
339
+ Non-existing operations are filled with ``np.nan``.
276
340
 
277
341
  Example:
278
342
  >>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
@@ -288,9 +352,9 @@ class JobShopInstance:
288
352
  def machines_matrix_array(self) -> NDArray[np.float32]:
289
353
  """Returns the machines matrix of the instance as a numpy array.
290
354
 
291
- The returned array has shape (num_jobs, max_num_operations_per_job,
292
- max_num_machines_per_operation). Non-existing machines are filled with
293
- np.nan.
355
+ The returned array has shape (``num_jobs``,
356
+ ``max_num_operations_per_job``, ``max_num_machines_per_operation``).
357
+ Non-existing machines are filled with ``np.nan``.
294
358
 
295
359
  Example:
296
360
  >>> jobs = [
@@ -413,7 +477,7 @@ class JobShopInstance:
413
477
  def _fill_matrix_with_nans_2d(
414
478
  matrix: list[list[int]],
415
479
  ) -> NDArray[np.float32]:
416
- """Fills a matrix with np.nan values.
480
+ """Fills a matrix with ``np.nan`` values.
417
481
 
418
482
  Args:
419
483
  matrix:
@@ -421,7 +485,7 @@ class JobShopInstance:
421
485
 
422
486
  Returns:
423
487
  A numpy array with the same shape as the input matrix, filled with
424
- np.nan values.
488
+ ``np.nan`` values.
425
489
  """
426
490
  max_length = max(len(row) for row in matrix)
427
491
  squared_matrix = np.full(
@@ -435,7 +499,7 @@ class JobShopInstance:
435
499
  def _fill_matrix_with_nans_3d(
436
500
  matrix: list[list[list[int]]],
437
501
  ) -> NDArray[np.float32]:
438
- """Fills a 3D matrix with np.nan values.
502
+ """Fills a 3D matrix with ``np.nan`` values.
439
503
 
440
504
  Args:
441
505
  matrix:
@@ -443,7 +507,7 @@ class JobShopInstance:
443
507
 
444
508
  Returns:
445
509
  A numpy array with the same shape as the input matrix, filled with
446
- np.nan values.
510
+ ``np.nan`` values.
447
511
  """
448
512
  max_length = max(len(row) for row in matrix)
449
513
  max_inner_length = len(matrix[0][0])
@@ -42,17 +42,28 @@ class Operation:
42
42
  "The time it takes to perform the operation. Often referred"
43
43
  " to as the processing time."
44
44
  ),
45
- "job_id": "The id of the job the operation belongs to.",
46
- "position_in_job": "The index of the operation in the job.",
45
+ "job_id": (
46
+ "The id of the job the operation belongs to. Defaults to -1. "
47
+ "It is usually set by the :class:`JobShopInstance` class after "
48
+ "initialization."
49
+ ),
50
+ "position_in_job": (
51
+ "The index of the operation in the job. Defaults to -1. "
52
+ "It is usually set by the :class:`JobShopInstance` class after "
53
+ "initialization."
54
+ ),
47
55
  "operation_id": (
48
56
  "The id of the operation. This is unique within a "
49
- ":class:`JobShopInstance`."
57
+ ":class:`JobShopInstance`. Defaults to -1. It is usually set by "
58
+ "the :class:`JobShopInstance` class after initialization."
50
59
  ),
51
60
  }
52
61
 
53
62
  def __init__(self, machines: int | list[int], duration: int):
54
- self.machines = [machines] if isinstance(machines, int) else machines
55
- self.duration = duration
63
+ self.machines: list[int] = (
64
+ [machines] if isinstance(machines, int) else machines
65
+ )
66
+ self.duration: int = duration
56
67
 
57
68
  # Defined outside the class by the JobShopInstance class:
58
69
  self.job_id: int = -1
@@ -64,8 +75,8 @@ class Operation:
64
75
  """Returns the id of the machine associated with the operation.
65
76
 
66
77
  Raises:
67
- UninitializedAttributeError: If the operation has multiple machines
68
- in its list.
78
+ UninitializedAttributeError:
79
+ If the operation has multiple machines in its list.
69
80
  """
70
81
  if len(self.machines) > 1:
71
82
  raise UninitializedAttributeError(
job_shop_lib/_schedule.py CHANGED
@@ -25,6 +25,16 @@ class Schedule:
25
25
  is_complete
26
26
  add
27
27
  reset
28
+
29
+ Args:
30
+ instance:
31
+ The :class:`JobShopInstance` object that the schedule is for.
32
+ schedule:
33
+ A list of lists of :class:`ScheduledOperation` objects. Each
34
+ list represents the order of operations on a machine. If
35
+ not provided, the schedule is initialized as an empty schedule.
36
+ **metadata:
37
+ Additional information about the schedule.
28
38
  """
29
39
 
30
40
  __slots__ = {
@@ -46,28 +56,16 @@ class Schedule:
46
56
  self,
47
57
  instance: JobShopInstance,
48
58
  schedule: list[list[ScheduledOperation]] | None = None,
49
- **metadata,
59
+ **metadata: Any,
50
60
  ):
51
- """Initializes the object with the given instance and schedule.
52
-
53
- Args:
54
- instance:
55
- The :class:`JobShopInstance` object that the schedule is for.
56
- schedule:
57
- A list of lists of :class:`ScheduledOperation` objects. Each
58
- list represents the order of operations on a machine. If
59
- not provided, the schedule is initialized as an empty schedule.
60
- **metadata:
61
- Additional information about the schedule.
62
- """
63
61
  if schedule is None:
64
62
  schedule = [[] for _ in range(instance.num_machines)]
65
63
 
66
64
  Schedule.check_schedule(schedule)
67
65
 
68
- self.instance = instance
66
+ self.instance: JobShopInstance = instance
69
67
  self._schedule = schedule
70
- self.metadata = metadata
68
+ self.metadata: dict[str, Any] = metadata
71
69
 
72
70
  def __repr__(self) -> str:
73
71
  return str(self.schedule)
@@ -5,7 +5,21 @@ from job_shop_lib.exceptions import ValidationError
5
5
 
6
6
 
7
7
  class ScheduledOperation:
8
- """Data structure to store a scheduled operation."""
8
+ """Data structure to store a scheduled operation.
9
+
10
+ Args:
11
+ operation:
12
+ The :class:`Operation` object that is scheduled.
13
+ start_time:
14
+ The time at which the operation is scheduled to start.
15
+ machine_id:
16
+ The id of the machine on which the operation is scheduled.
17
+
18
+ Raises:
19
+ ValidationError:
20
+ If the given machine_id is not in the list of valid machines
21
+ for the operation.
22
+ """
9
23
 
10
24
  __slots__ = {
11
25
  "operation": "The :class:`Operation` object that is scheduled.",
@@ -16,23 +30,8 @@ class ScheduledOperation:
16
30
  }
17
31
 
18
32
  def __init__(self, operation: Operation, start_time: int, machine_id: int):
19
- """Initializes a new instance of the :class:`ScheduledOperation` class.
20
-
21
- Args:
22
- operation:
23
- The :class:`Operation` object that is scheduled.
24
- start_time:
25
- The time at which the operation is scheduled to start.
26
- machine_id:
27
- The id of the machine on which the operation is scheduled.
28
-
29
- Raises:
30
- ValidationError:
31
- If the given machine_id is not in the list of valid machines
32
- for the operation.
33
- """
34
- self.operation = operation
35
- self.start_time = start_time
33
+ self.operation: Operation = operation
34
+ self.start_time: int = start_time
36
35
  self._machine_id = machine_id
37
36
  self.machine_id = machine_id # Validate machine_id
38
37
 
@@ -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
  ]
@@ -29,6 +29,18 @@ class DispatcherObserver(abc.ABC):
29
29
  dispatcher:
30
30
  The :class:`Dispatcher` instance to observe.
31
31
 
32
+ Args:
33
+ dispatcher:
34
+ The :class:`Dispatcher` instance to observe.
35
+ subscribe:
36
+ If ``True``, automatically subscribes the observer to the
37
+ dispatcher when it is initialized. Defaults to ``True``.
38
+
39
+ Raises:
40
+ ValidationError: If ``is_singleton`` is ``True`` and an observer of the
41
+ same type already exists in the dispatcher's list of
42
+ subscribers.
43
+
32
44
  Example:
33
45
 
34
46
  .. code-block:: python
@@ -61,21 +73,6 @@ class DispatcherObserver(abc.ABC):
61
73
  *,
62
74
  subscribe: bool = True,
63
75
  ):
64
- """Initializes the observer with the :class:`Dispatcher` and subscribes
65
- to it.
66
-
67
- Args:
68
- dispatcher:
69
- The `Dispatcher` instance to observe.
70
- subscribe:
71
- If True, automatically subscribes the observer to the
72
- dispatcher.
73
-
74
- Raises:
75
- ValidationError: If ``is_singleton`` is True and an observer of the
76
- same type already exists in the dispatcher's list of
77
- subscribers.
78
- """
79
76
  if self._is_singleton and any(
80
77
  isinstance(observer, self.__class__)
81
78
  for observer in dispatcher.subscribers
@@ -156,26 +153,30 @@ class Dispatcher:
156
153
  responsible for scheduling the operations on the machines and keeping
157
154
  track of the next available time for each machine and job.
158
155
 
159
- Attributes:
156
+ Args:
160
157
  instance:
161
- The instance of the job shop problem to be scheduled.
162
- schedule:
163
- The schedule of operations on machines.
158
+ The instance of the job shop problem to be solved.
164
159
  ready_operations_filter:
165
- A function that filters out operations that are not ready to be
166
- scheduled.
160
+ A function that filters out operations that are not ready to
161
+ be scheduled. The function should take the dispatcher and a
162
+ list of operations as input and return a list of operations
163
+ that are ready to be scheduled. If ``None``, no filtering is
164
+ done.
167
165
  """
168
166
 
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
- )
167
+ __slots__ = {
168
+ "instance": "The instance of the job shop problem to be scheduled.",
169
+ "schedule": "The schedule of operations on machines.",
170
+ "_machine_next_available_time": "",
171
+ "_job_next_operation_index": "",
172
+ "_job_next_available_time": "",
173
+ "ready_operations_filter": (
174
+ "A function that filters out operations that are not ready to be "
175
+ "scheduled."
176
+ ),
177
+ "subscribers": "A list of observers subscribed to the dispatcher.",
178
+ "_cache": "A dictionary to cache the results of the cached methods.",
179
+ }
179
180
 
180
181
  def __init__(
181
182
  self,
@@ -184,18 +185,6 @@ class Dispatcher:
184
185
  Callable[[Dispatcher, list[Operation]], list[Operation]] | None
185
186
  ) = None,
186
187
  ) -> 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
188
 
200
189
  self.instance = instance
201
190
  self.schedule = Schedule(self.instance)
@@ -371,7 +360,7 @@ class Dispatcher:
371
360
  The current time is the minimum start time of the available
372
361
  operations.
373
362
  """
374
- available_operations = self.ready_operations()
363
+ available_operations = self.available_operations()
375
364
  current_time = self.min_start_time(available_operations)
376
365
  return current_time
377
366
 
@@ -387,7 +376,7 @@ class Dispatcher:
387
376
  return int(min_start_time)
388
377
 
389
378
  @_dispatcher_cache
390
- def ready_operations(self) -> list[Operation]:
379
+ def available_operations(self) -> list[Operation]:
391
380
  """Returns a list of available operations for processing, optionally
392
381
  filtering out operations using the filter function.
393
382
 
@@ -443,7 +432,7 @@ class Dispatcher:
443
432
  @_dispatcher_cache
444
433
  def available_machines(self) -> list[int]:
445
434
  """Returns the list of ready machines."""
446
- available_operations = self.ready_operations()
435
+ available_operations = self.available_operations()
447
436
  available_machines = set()
448
437
  for operation in available_operations:
449
438
  available_machines.update(operation.machines)
@@ -452,7 +441,7 @@ class Dispatcher:
452
441
  @_dispatcher_cache
453
442
  def available_jobs(self) -> list[int]:
454
443
  """Returns the list of ready jobs."""
455
- available_operations = self.ready_operations()
444
+ available_operations = self.available_operations()
456
445
  available_jobs = set(
457
446
  operation.job_id for operation in available_operations
458
447
  )
@@ -26,14 +26,21 @@ class DispatcherObserverConfig(Generic[T]):
26
26
  keyword arguments to pass to the dispatcher observer constructor while
27
27
  not containing the ``dispatcher`` argument.
28
28
 
29
- Attributes:
29
+ Args:
30
30
  class_type:
31
31
  Type of the class to be initialized. It can be the class type, an
32
32
  enum value, or a string. This is useful for the creation of
33
- DispatcherObserver instances from the factory functions.
33
+ :class:`~job_shop_lib.dispatching.DispatcherObserver` instances
34
+ from the factory functions.
34
35
  kwargs:
35
36
  Keyword arguments needed to initialize the class. It must not
36
37
  contain the ``dispatcher`` argument.
38
+
39
+ .. seealso::
40
+
41
+ - :class:`~job_shop_lib.dispatching.DispatcherObserver`
42
+ - :func:`job_shop_lib.dispatching.feature_observers.\\
43
+ feature_observer_factory`
37
44
  """
38
45
 
39
46
  # We use the type hint T, instead of ObserverType, to allow for string or
@@ -44,7 +51,13 @@ class DispatcherObserverConfig(Generic[T]):
44
51
  # This allows for the creation of a FeatureObserver instance
45
52
  # from the factory function.
46
53
  class_type: T
54
+ """Type of the class to be initialized. It can be the class type, an
55
+ enum value, or a string. This is useful for the creation of
56
+ :class:`DispatcherObserver` instances from the factory functions."""
57
+
47
58
  kwargs: dict[str, Any] = field(default_factory=dict)
59
+ """Keyword arguments needed to initialize the class. It must not
60
+ contain the ``dispatcher`` argument."""
48
61
 
49
62
  def __post_init__(self):
50
63
  if "dispatcher" in self.kwargs:
@@ -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(
@@ -47,13 +51,13 @@ def create_composite_operation_filter(
47
51
  'non_immediate_machines' or any Callable that takes a
48
52
  :class:`~job_shop_lib.dispatching.Dispatcher` instance and a list
49
53
  of :class:`~job_shop_lib.Operation` instances as input
50
- and returns a list of :class:`~job_shop_lib.Operation`instances.
54
+ and returns a list of :class:`~job_shop_lib.Operation` instances.
51
55
 
52
56
  Returns:
53
57
  A function that takes a :class:`~job_shop_lib.dispatching.Dispatcher`
54
58
  instance and a list of :class:`~job_shop_lib.Operation`
55
59
  instances as input and returns a list of
56
- :class:`~job_shop_lib.Operation`instances based on
60
+ :class:`~job_shop_lib.Operation` instances based on
57
61
  the specified list of filter strategies.
58
62
 
59
63
  Raises:
@@ -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: