job-shop-lib 1.0.0a1__py3-none-any.whl → 1.0.0a3__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 +18 -20
  2. job_shop_lib/_operation.py +30 -24
  3. job_shop_lib/_schedule.py +17 -16
  4. job_shop_lib/_scheduled_operation.py +10 -12
  5. job_shop_lib/constraint_programming/_ortools_solver.py +31 -16
  6. job_shop_lib/dispatching/__init__.py +4 -0
  7. job_shop_lib/dispatching/_dispatcher.py +24 -32
  8. job_shop_lib/dispatching/_factories.py +8 -0
  9. job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
  10. job_shop_lib/dispatching/feature_observers/__init__.py +34 -2
  11. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +54 -14
  12. job_shop_lib/dispatching/feature_observers/_duration_observer.py +15 -2
  13. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +62 -10
  14. job_shop_lib/dispatching/feature_observers/_factory.py +5 -1
  15. job_shop_lib/dispatching/feature_observers/_feature_observer.py +87 -16
  16. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +32 -2
  17. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +3 -3
  18. job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +9 -5
  19. job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +7 -2
  20. job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +1 -1
  21. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +66 -43
  22. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
  23. job_shop_lib/graphs/__init__.py +2 -0
  24. job_shop_lib/graphs/_build_agent_task_graph.py +2 -2
  25. job_shop_lib/graphs/_constants.py +18 -1
  26. job_shop_lib/graphs/_job_shop_graph.py +36 -20
  27. job_shop_lib/graphs/_node.py +60 -52
  28. job_shop_lib/graphs/graph_updaters/__init__.py +11 -1
  29. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +1 -1
  30. job_shop_lib/visualization/__init__.py +5 -5
  31. job_shop_lib/visualization/_gantt_chart_creator.py +5 -5
  32. job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +63 -36
  33. job_shop_lib/visualization/{_gantt_chart.py → _plot_gantt_chart.py} +78 -14
  34. {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/METADATA +15 -3
  35. {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/RECORD +37 -37
  36. {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/WHEEL +1 -1
  37. {job_shop_lib-1.0.0a1.dist-info → job_shop_lib-1.0.0a3.dist-info}/LICENSE +0 -0
@@ -12,7 +12,7 @@ from job_shop_lib.dispatching.feature_observers import (
12
12
  class DurationObserver(FeatureObserver):
13
13
  """Measures the remaining duration of operations, machines, and jobs.
14
14
 
15
- The duration of an :class:`Operation` is:
15
+ The duration of an :class:`~job_shop_lib.Operation` is:
16
16
  - if the operation has not been scheduled, it is the duration of the
17
17
  operation.
18
18
  - if the operation has been scheduled, it is the remaining duration of
@@ -22,8 +22,21 @@ class DurationObserver(FeatureObserver):
22
22
  manually if needed. We do not update the duration of completed
23
23
  operations to save computation time.
24
24
 
25
- The duration of a Machine or Job is the sum of the durations of the
25
+ The duration of a machine or job is the sum of the durations of the
26
26
  unscheduled operations that belong to the machine or job.
27
+
28
+ Args:
29
+ dispatcher:
30
+ The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
31
+ subscribe:
32
+ If ``True``, the observer is subscribed to the dispatcher upon
33
+ initialization. Otherwise, the observer must be subscribed later
34
+ or manually updated.
35
+ feature_types:
36
+ A list of :class:`FeatureType` or a single :class:`FeatureType`
37
+ that specifies the types of features to observe. They must be a
38
+ subset of the class attribute :attr:`supported_feature_types`.
39
+ If ``None``, all supported feature types are tracked.
27
40
  """
28
41
 
29
42
  def initialize_features(self):
@@ -1,6 +1,7 @@
1
1
  """Home of the `EarliestStartTimeObserver` class."""
2
2
 
3
3
  import numpy as np
4
+ from numpy.typing import NDArray
4
5
 
5
6
  from job_shop_lib.dispatching import Dispatcher
6
7
  from job_shop_lib.dispatching.feature_observers import (
@@ -12,14 +13,62 @@ from job_shop_lib import ScheduledOperation
12
13
 
13
14
  class EarliestStartTimeObserver(FeatureObserver):
14
15
  """Observer that adds a feature indicating the earliest start time of
15
- each operation, machine, and job in the graph."""
16
+ each operation, machine, and job in the graph.
16
17
 
17
- __slots__ = (
18
- "earliest_start_times",
19
- "_job_ids",
20
- "_positions",
21
- "machine_ids",
22
- )
18
+ The earliest start time of an operation refers to the earliest time at
19
+ which the operation could potentially start without violating any
20
+ constraints. This time is normalized by the current time (i.e., the
21
+ difference between the earliest start time and the current time).
22
+
23
+ The earliest start time of a machine is the earliest start time of the
24
+ next operation that can be scheduled on that machine.
25
+
26
+ Finally, the earliest start time of a job is the earliest start time of the
27
+ next operation in the job.
28
+
29
+ Args:
30
+ dispatcher:
31
+ The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
32
+ subscribe:
33
+ If ``True``, the observer is subscribed to the dispatcher upon
34
+ initialization. Otherwise, the observer must be subscribed later
35
+ or manually updated.
36
+ feature_types:
37
+ A list of :class:`FeatureType` or a single :class:`FeatureType`
38
+ that specifies the types of features to observe. They must be a
39
+ subset of the class attribute :attr:`supported_feature_types`.
40
+ If ``None``, all supported feature types are tracked.
41
+ """
42
+
43
+ __slots__ = {
44
+ "earliest_start_times": (
45
+ "A 2D numpy array with the earliest start "
46
+ "times of each operation. The array has "
47
+ "shape (``num_jobs``, ``max_operations_per_job``). "
48
+ "The value at index (i, j) is the earliest start "
49
+ "time of the j-th operation in the i-th job. "
50
+ "If a job has fewer than the maximum number of "
51
+ "operations in a job, the remaining values are "
52
+ "set to ``np.nan``. Similarly to "
53
+ ":class:`~job_shop_lib.JobShopInstance`'s "
54
+ ":meth:`~job_shop_lib.JobShopInstance.durations_matrix_array` "
55
+ "method."
56
+ ),
57
+ "_job_ids": (
58
+ "An array that stores the job IDs for each operation in the "
59
+ "dispatcher's instance. The array has shape "
60
+ "(``num_machines``, ``max_operations_per_machine``)."
61
+ ),
62
+ "_positions": (
63
+ "An array that stores the positions of each operation in their "
64
+ "respective jobs. The array has shape "
65
+ "(``num_machines``, ``max_operations_per_machine``)."
66
+ ),
67
+ "_is_regular_instance": (
68
+ "Whether the dispatcher's instance is a regular "
69
+ "instance, where each job has the same number of operations."
70
+ ),
71
+ }
23
72
 
24
73
  def __init__(
25
74
  self,
@@ -32,9 +81,9 @@ class EarliestStartTimeObserver(FeatureObserver):
32
81
  # Earliest start times initialization
33
82
  # -------------------------------
34
83
  squared_duration_matrix = dispatcher.instance.durations_matrix_array
35
- self.earliest_start_times = np.hstack(
84
+ self.earliest_start_times: NDArray[np.float32] = np.hstack(
36
85
  (
37
- np.zeros((squared_duration_matrix.shape[0], 1)),
86
+ np.zeros((squared_duration_matrix.shape[0], 1), dtype=float),
38
87
  np.cumsum(squared_duration_matrix[:, :-1], axis=1),
39
88
  )
40
89
  )
@@ -70,7 +119,7 @@ class EarliestStartTimeObserver(FeatureObserver):
70
119
 
71
120
  def update(self, scheduled_operation: ScheduledOperation):
72
121
  """Recomputes the earliest start times and calls the
73
- `initialize_features` method.
122
+ ``initialize_features`` method.
74
123
 
75
124
  The earliest start times is computed as the cumulative sum of the
76
125
  previous unscheduled operations in the job plus the maximum of the
@@ -78,6 +127,9 @@ class EarliestStartTimeObserver(FeatureObserver):
78
127
  time of the machine(s) the operation is assigned.
79
128
 
80
129
  After that, we substract the current time.
130
+
131
+ Args:
132
+ scheduled_operation: The operation that has been scheduled.
81
133
  """
82
134
  # We compute the gap that the current scheduled operation could be
83
135
  # adding to each job.
@@ -16,7 +16,11 @@ from job_shop_lib.dispatching.feature_observers import (
16
16
 
17
17
 
18
18
  class FeatureObserverType(str, Enum):
19
- """Enumeration of the different feature observers."""
19
+ """Enumeration of the different feature observers.
20
+
21
+ Each feature observer is associated with a string value that can be used
22
+ to create the feature observer using the factory function.
23
+ """
20
24
 
21
25
  IS_READY = "is_ready"
22
26
  EARLIEST_START_TIME = "earliest_start_time"
@@ -20,6 +20,27 @@ class FeatureType(str, enum.Enum):
20
20
  class FeatureObserver(DispatcherObserver):
21
21
  """Base class for feature observers.
22
22
 
23
+ A :class:`FeatureObserver` is a
24
+ a subclass of :class:`~job_shop_lib.dispatching.DispatcherObserver` that
25
+ observes features related to operations, machines, or jobs in the
26
+ :class:`~job_shop_lib.dispatching.Dispatcher`.
27
+
28
+ Attributes are stored in numpy arrays with a shape of (``num_entities``,
29
+ ``feature_size``), where ``num_entities`` is the number of entities being
30
+ observed (e.g., operations, machines, or jobs) and ``feature_size`` is the
31
+ number of values being observed for each entity.
32
+
33
+ The advantage of using arrays is that they can be easily updated in a
34
+ vectorized manner, which is more efficient than updating each attribute
35
+ individually. Furthermore, machine learning models can be trained on these
36
+ arrays to predict the best dispatching decisions.
37
+
38
+ Arrays use the data type ``np.float32``. This is because most machine
39
+
40
+ New :class:`FeatureObservers` must inherit from this class, and re-define
41
+ the class attributes ``_singleton`` (defualt ), ``_feature_size``
42
+ (default 1) and ``_supported_feature_types`` (default all feature types).
43
+
23
44
  Feature observers are not singleton by default. This means that more than
24
45
  one instance of the same feature observer type can be subscribed to the
25
46
  dispatcher. This is useful when the first subscriber only observes a subset
@@ -27,16 +48,36 @@ class FeatureObserver(DispatcherObserver):
27
48
  them. For example, the first subscriber could observe only the
28
49
  operation-related features, while the second subscriber could observe the
29
50
  jobs.
51
+
52
+ Args:
53
+ dispatcher:
54
+ The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
55
+ subscribe:
56
+ If ``True``, the observer is subscribed to the dispatcher upon
57
+ initialization. Otherwise, the observer must be subscribed later
58
+ or manually updated.
59
+ feature_types:
60
+ A list of :class:`FeatureType` or a single :class:`FeatureType`
61
+ that specifies the types of features to observe. They must be a
62
+ subset of the class attribute :attr:`supported_feature_types`.
63
+ If ``None``, all supported feature types are tracked.
30
64
  """
31
65
 
32
66
  _is_singleton = False
33
- _feature_size: dict[FeatureType, int] | int = 1
67
+ _feature_sizes: dict[FeatureType, int] | int = 1
34
68
  _supported_feature_types = list(FeatureType)
35
69
 
36
- __slots__ = (
37
- "features",
38
- "feature_dimensions",
39
- )
70
+ __slots__ = {
71
+ "features": (
72
+ "A dictionary of numpy arrays with the features. "
73
+ "Each key is a :class:`FeatureType` and each value is a numpy "
74
+ "array with the features. The array has shape (``num_entities``, "
75
+ "``feature_size``), where ``num_entities`` is the number of "
76
+ "entities being observed (e.g., operations, machines, or jobs) and"
77
+ " ``feature_size`` is the number of values being observed for each"
78
+ " entity."
79
+ )
80
+ }
40
81
 
41
82
  def __init__(
42
83
  self,
@@ -46,9 +87,9 @@ class FeatureObserver(DispatcherObserver):
46
87
  feature_types: list[FeatureType] | FeatureType | None = None,
47
88
  ):
48
89
  feature_types = self._get_feature_types_list(feature_types)
49
- if isinstance(self._feature_size, int):
90
+ if isinstance(self._feature_sizes, int):
50
91
  feature_size = {
51
- feature_type: self._feature_size
92
+ feature_type: self._feature_sizes
52
93
  for feature_type in feature_types
53
94
  }
54
95
  super().__init__(dispatcher, subscribe=subscribe)
@@ -58,7 +99,7 @@ class FeatureObserver(DispatcherObserver):
58
99
  FeatureType.MACHINES: dispatcher.instance.num_machines,
59
100
  FeatureType.JOBS: dispatcher.instance.num_jobs,
60
101
  }
61
- self.feature_dimensions = {
102
+ feature_dimensions = {
62
103
  feature_type: (
63
104
  number_of_entities[feature_type],
64
105
  feature_size[feature_type],
@@ -67,7 +108,7 @@ class FeatureObserver(DispatcherObserver):
67
108
  }
68
109
  self.features = {
69
110
  feature_type: np.zeros(
70
- self.feature_dimensions[feature_type],
111
+ feature_dimensions[feature_type],
71
112
  dtype=np.float32,
72
113
  )
73
114
  for feature_type in feature_types
@@ -75,23 +116,42 @@ class FeatureObserver(DispatcherObserver):
75
116
  self.initialize_features()
76
117
 
77
118
  @property
78
- def feature_size(self) -> dict[FeatureType, int]:
79
- """Returns the size of the features."""
80
- if isinstance(self._feature_size, int):
119
+ def feature_sizes(self) -> dict[FeatureType, int]:
120
+ """Returns the size of the features.
121
+
122
+ The size of the features is the number of values being observed for
123
+ each entity. This corresponds to the second dimension of each array.
124
+
125
+ This number is typically one (e.g. measuring the duration
126
+ of each operation), but some feature observers like the
127
+ :class:`CompositeFeatureObserver` may track more than one value.
128
+ """
129
+ if isinstance(self._feature_sizes, int):
81
130
  return {
82
- feature_type: self._feature_size
131
+ feature_type: self._feature_sizes
83
132
  for feature_type in self.features
84
133
  }
85
- return self._feature_size
134
+ return self._feature_sizes
86
135
 
87
136
  @property
88
137
  def supported_feature_types(self) -> list[FeatureType]:
89
138
  """Returns the supported feature types."""
90
139
  return self._supported_feature_types
91
140
 
141
+ @property
142
+ def feature_dimensions(self) -> dict[FeatureType, tuple[int, int]]:
143
+ """A dictionary containing the shape of each :class:`FeatureType`."""
144
+ feature_dimensions = {}
145
+ for feature_type, array in self.features.items():
146
+ feature_dimensions[feature_type] = array.shape
147
+ return feature_dimensions # type: ignore[return-value]
148
+
92
149
  def initialize_features(self):
93
150
  """Initializes the features based on the current state of the
94
- dispatcher."""
151
+ dispatcher.
152
+
153
+ This method is automatically called after initializing the observer.
154
+ """
95
155
 
96
156
  def update(self, scheduled_operation: ScheduledOperation):
97
157
  """Updates the features based on the scheduled operation.
@@ -112,7 +172,18 @@ class FeatureObserver(DispatcherObserver):
112
172
  def set_features_to_zero(
113
173
  self, exclude: FeatureType | list[FeatureType] | None = None
114
174
  ):
115
- """Sets features to zero."""
175
+ """Sets all features to zero except for the ones specified in
176
+ ``exclude``.
177
+
178
+ Setting a feature to zero means that all values in the feature array
179
+ are set to this value.
180
+
181
+ Args:
182
+ exclude:
183
+ A single :class:`FeatureType` or a list of :class:`FeatureType`
184
+ that specifies the features that should not be set to zero. If
185
+ ``None``, all currently used features are set to zero.
186
+ """
116
187
  if exclude is None:
117
188
  exclude = []
118
189
  if isinstance(exclude, FeatureType):
@@ -15,8 +15,38 @@ from job_shop_lib.dispatching.feature_observers import (
15
15
 
16
16
 
17
17
  class IsCompletedObserver(FeatureObserver):
18
- """Observer that adds a binary feature indicating whether each operation,
19
- machine, or job has been completed."""
18
+ """Adds a binary feature indicating whether each operation,
19
+ machine, or job has been completed.
20
+
21
+ An operation is considered completed if it has been scheduled and the
22
+ current time is greater than or equal to the sum of the operation's start
23
+ time and duration.
24
+
25
+ A machine or job is considered completed if all of its operations have been
26
+ completed.
27
+
28
+ Args:
29
+ dispatcher:
30
+ The :class:`~job_shop_lib.dispatching.Dispatcher` to observe.
31
+ feature_types:
32
+ A list of :class:`FeatureType` or a single :class:`FeatureType`
33
+ that specifies the types of features to observe. They must be a
34
+ subset of the class attribute :attr:`supported_feature_types`.
35
+ If ``None``, all supported feature types are tracked.
36
+ subscribe:
37
+ If ``True``, the observer is subscribed to the dispatcher upon
38
+ initialization. Otherwise, the observer must be subscribed later
39
+ or manually updated.
40
+ """
41
+
42
+ __slots__ = {
43
+ "remaining_ops_per_machine": (
44
+ "The number of unscheduled operations per machine."
45
+ ),
46
+ "remaining_ops_per_job": (
47
+ "The number of unscheduled operations per job."
48
+ ),
49
+ }
20
50
 
21
51
  def __init__(
22
52
  self,
@@ -7,8 +7,8 @@ from job_shop_lib.dispatching.feature_observers import (
7
7
 
8
8
 
9
9
  class IsReadyObserver(FeatureObserver):
10
- """Feature creator that adds a binary feature indicating if the operation
11
- is ready to be dispatched."""
10
+ """Feature creator that adds a binary feature indicating if the operation,
11
+ machine or job is ready to be dispatched."""
12
12
 
13
13
  def initialize_features(self):
14
14
  self.set_features_to_zero()
@@ -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]
@@ -8,14 +8,18 @@ from job_shop_lib.dispatching.feature_observers import (
8
8
 
9
9
 
10
10
  class IsScheduledObserver(FeatureObserver):
11
- """Observer that updates features based on scheduling operations.
11
+ """Updates features based on scheduling operations.
12
12
 
13
13
  This observer tracks which operations have been scheduled and updates
14
- feature matrices accordingly. It updates a feature in the
15
- `FeatureType.OPERATIONS` matrix to indicate that an operation has been
16
- scheduled. Additionally, it counts the number of uncompleted but
14
+ feature matrices accordingly.
15
+
16
+ It updates a feature in the
17
+ :meth:`FeatureType.OPERATIONS` matrix to indicate that an operation has
18
+ been scheduled.
19
+
20
+ Additionally, it counts the number of uncompleted but
17
21
  scheduled operations for each machine and job, updating the respective
18
- `FeatureType.MACHINES` and `FeatureType.JOBS` feature matrices.
22
+ :meth:`FeatureType.MACHINES` and :meth:`FeatureType.JOBS` feature matrices.
19
23
  """
20
24
 
21
25
  def update(self, scheduled_operation: ScheduledOperation):
@@ -8,10 +8,15 @@ from job_shop_lib.dispatching.feature_observers import (
8
8
 
9
9
 
10
10
  class PositionInJobObserver(FeatureObserver):
11
- """Observer that adds a feature indicating the position of
11
+ """Adds a feature indicating the position of
12
12
  operations in their respective jobs.
13
13
 
14
- Positions are adjusted dynamically as operations are scheduled.
14
+ Positions are adjusted dynamically as operations are scheduled. In other
15
+ words, the position of an operation is the number of unscheduled operations
16
+ that precede it in the job.
17
+
18
+ It only supports the :meth:`~job_shop_lib.FeatureType.OPERATIONS` feature
19
+ type.
15
20
  """
16
21
 
17
22
  _supported_feature_types = [FeatureType.OPERATIONS]
@@ -12,7 +12,7 @@ class RemainingOperationsObserver(FeatureObserver):
12
12
  """Adds a feature indicating the number of remaining operations for each
13
13
  job and machine.
14
14
 
15
- It does not support :class:`FeatureType.OPERATIONS`.
15
+ It does not support :meth:`FeatureType.OPERATIONS`.
16
16
  """
17
17
 
18
18
  _supported_feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
@@ -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__(
@@ -40,45 +71,33 @@ class DispatchingRuleSolver(BaseSolver):
40
71
  machine_chooser: (
41
72
  str | Callable[[Dispatcher, Operation], int]
42
73
  ) = MachineChooserType.FIRST,
43
- pruning_function: (
44
- str
45
- | Callable[[Dispatcher, list[Operation]], list[Operation]]
74
+ ready_operations_filter: (
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
- pruning_function:
64
- The pruning function to use. It can be a string with the name
65
- of the pruning function, a PruningFunction enum member, or a
66
- callable that takes a dispatcher and a list of operations and
67
- returns a list of operations that should be considered for
68
- 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):
73
88
  machine_chooser = machine_chooser_factory(machine_chooser)
74
- if isinstance(pruning_function, str):
75
- pruning_function = ready_operations_filter_factory(
76
- pruning_function
89
+ if isinstance(ready_operations_filter, str):
90
+ ready_operations_filter = ready_operations_filter_factory(
91
+ ready_operations_filter
92
+ )
93
+ if isinstance(ready_operations_filter, Iterable):
94
+ ready_operations_filter = create_composite_operation_filter(
95
+ ready_operations_filter
77
96
  )
78
97
 
79
98
  self.dispatching_rule = dispatching_rule
80
99
  self.machine_chooser = machine_chooser
81
- self.pruning_function = pruning_function
100
+ self.ready_operations_filter = ready_operations_filter
82
101
 
83
102
  def solve(
84
103
  self, instance: JobShopInstance, dispatcher: Dispatcher | None = None
@@ -87,7 +106,7 @@ class DispatchingRuleSolver(BaseSolver):
87
106
  dispatching rule algorithm."""
88
107
  if dispatcher is None:
89
108
  dispatcher = Dispatcher(
90
- instance, ready_operations_filter=self.pruning_function
109
+ instance, ready_operations_filter=self.ready_operations_filter
91
110
  )
92
111
  while not dispatcher.schedule.is_complete():
93
112
  self.step(dispatcher)
@@ -110,19 +129,23 @@ class DispatchingRuleSolver(BaseSolver):
110
129
  if __name__ == "__main__":
111
130
  import time
112
131
  import cProfile
113
- import pstats
114
- from io import StringIO
115
- from job_shop_lib.benchmarking import load_benchmark_instance
132
+ # import pstats
133
+ # from io import StringIO
134
+ from job_shop_lib.benchmarking import (
135
+ # load_benchmark_instance,
136
+ load_all_benchmark_instances,
137
+ )
116
138
 
117
139
  # from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
118
140
  # most_work_remaining_rule_2,
119
141
  # )
120
142
 
121
- ta_instances = [
122
- load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
123
- ]
143
+ # ta_instances = [
144
+ # load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
145
+ # ]
146
+ ta_instances = load_all_benchmark_instances().values()
124
147
  solver = DispatchingRuleSolver(
125
- dispatching_rule="most_work_remaining", pruning_function=None
148
+ dispatching_rule="most_work_remaining", ready_operations_filter=None
126
149
  )
127
150
 
128
151
  start = time.perf_counter()
@@ -131,10 +154,10 @@ if __name__ == "__main__":
131
154
  profiler = cProfile.Profile()
132
155
 
133
156
  # Run the code under profiling
134
- profiler.enable()
157
+ # profiler.enable()
135
158
  for instance_ in ta_instances:
136
159
  solver.solve(instance_)
137
- profiler.disable()
160
+ # profiler.disable()
138
161
 
139
162
  end = time.perf_counter()
140
163
 
@@ -142,7 +165,7 @@ if __name__ == "__main__":
142
165
  print(f"Elapsed time: {end - start:.2f} seconds.")
143
166
 
144
167
  # Print profiling results
145
- s = StringIO()
146
- ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
147
- profiler.print_stats("cumtime") # Print top 20 time-consuming functions
168
+ # s = StringIO()
169
+ # ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
170
+ # profiler.print_stats("cumtime") # Print top 20 time-consuming functions
148
171
  # print(s.getvalue())