job-shop-lib 0.5.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

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 (93) hide show
  1. job_shop_lib/__init__.py +19 -8
  2. job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
  3. job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
  4. job_shop_lib/_operation.py +118 -0
  5. job_shop_lib/{schedule.py → _schedule.py} +102 -84
  6. job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
  7. job_shop_lib/benchmarking/__init__.py +66 -43
  8. job_shop_lib/benchmarking/_load_benchmark.py +88 -0
  9. job_shop_lib/constraint_programming/__init__.py +13 -0
  10. job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +77 -22
  11. job_shop_lib/dispatching/__init__.py +51 -42
  12. job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
  13. job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
  14. job_shop_lib/dispatching/_factories.py +135 -0
  15. job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
  16. job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
  17. job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
  18. job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
  19. job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
  20. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
  21. job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
  22. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
  23. job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
  24. job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
  25. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
  26. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
  27. job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
  28. job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
  29. job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
  30. job_shop_lib/dispatching/rules/__init__.py +87 -0
  31. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
  32. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
  33. job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
  34. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
  35. job_shop_lib/dispatching/rules/_utils.py +128 -0
  36. job_shop_lib/exceptions.py +18 -0
  37. job_shop_lib/generation/__init__.py +19 -0
  38. job_shop_lib/generation/_general_instance_generator.py +165 -0
  39. job_shop_lib/generation/_instance_generator.py +133 -0
  40. job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
  41. job_shop_lib/generation/_utils.py +124 -0
  42. job_shop_lib/graphs/__init__.py +30 -12
  43. job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
  44. job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
  45. job_shop_lib/graphs/_constants.py +38 -0
  46. job_shop_lib/graphs/_job_shop_graph.py +320 -0
  47. job_shop_lib/graphs/_node.py +182 -0
  48. job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
  49. job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
  50. job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
  51. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
  52. job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
  53. job_shop_lib/py.typed +0 -0
  54. job_shop_lib/reinforcement_learning/__init__.py +68 -0
  55. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
  56. job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
  57. job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
  58. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
  59. job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
  60. job_shop_lib/reinforcement_learning/_utils.py +199 -0
  61. job_shop_lib/visualization/__init__.py +0 -25
  62. job_shop_lib/visualization/gantt/__init__.py +48 -0
  63. job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
  64. job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
  65. job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
  66. job_shop_lib/visualization/graphs/__init__.py +29 -0
  67. job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
  68. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  69. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
  70. job_shop_lib-1.0.0.dist-info/RECORD +73 -0
  71. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
  72. job_shop_lib/benchmarking/load_benchmark.py +0 -142
  73. job_shop_lib/cp_sat/__init__.py +0 -5
  74. job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
  75. job_shop_lib/dispatching/factories.py +0 -206
  76. job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
  77. job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
  78. job_shop_lib/dispatching/feature_observers/factory.py +0 -58
  79. job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
  80. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
  81. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
  82. job_shop_lib/dispatching/pruning_functions.py +0 -116
  83. job_shop_lib/generators/__init__.py +0 -7
  84. job_shop_lib/generators/basic_generator.py +0 -197
  85. job_shop_lib/graphs/constants.py +0 -21
  86. job_shop_lib/graphs/job_shop_graph.py +0 -202
  87. job_shop_lib/graphs/node.py +0 -166
  88. job_shop_lib/operation.py +0 -122
  89. job_shop_lib/visualization/agent_task_graph.py +0 -257
  90. job_shop_lib/visualization/create_gif.py +0 -209
  91. job_shop_lib/visualization/disjunctive_graph.py +0 -210
  92. job_shop_lib-0.5.0.dist-info/RECORD +0 -48
  93. {job_shop_lib-0.5.0.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -1,156 +0,0 @@
1
- """Home of the `EarliestStartTimeObserver` class."""
2
-
3
- import numpy as np
4
-
5
- from job_shop_lib.dispatching import Dispatcher
6
- from job_shop_lib.dispatching.feature_observers import (
7
- FeatureObserver,
8
- FeatureType,
9
- )
10
- from job_shop_lib.scheduled_operation import ScheduledOperation
11
-
12
-
13
- class EarliestStartTimeObserver(FeatureObserver):
14
- """Observer that adds a feature indicating the earliest start time of
15
- each operation, machine, and job in the graph."""
16
-
17
- def __init__(
18
- self,
19
- dispatcher: Dispatcher,
20
- feature_types: list[FeatureType] | FeatureType | None = None,
21
- subscribe: bool = True,
22
- ):
23
-
24
- # Earliest start times initialization
25
- # -------------------------------
26
- squared_duration_matrix = dispatcher.instance.durations_matrix_array
27
- self.earliest_start_times = np.hstack(
28
- (
29
- np.zeros((squared_duration_matrix.shape[0], 1)),
30
- np.cumsum(squared_duration_matrix[:, :-1], axis=1),
31
- )
32
- )
33
- self.earliest_start_times[np.isnan(squared_duration_matrix)] = np.nan
34
- # -------------------------------
35
- super().__init__(
36
- dispatcher, feature_types, feature_size=1, subscribe=subscribe
37
- )
38
-
39
- def update(self, scheduled_operation: ScheduledOperation):
40
- """Recomputes the earliest start times and calls the
41
- `initialize_features` method.
42
-
43
- The earliest start times is computed as the cumulative sum of the
44
- previous unscheduled operations in the job plus the maximum of the
45
- completion time of the last scheduled operation and the next available
46
- time of the machine(s) the operation is assigned.
47
-
48
- After that, we substract the current time.
49
- """
50
- # We compute the gap that the current scheduled operation could be
51
- # adding to each job.
52
- job_id = scheduled_operation.job_id
53
- next_operation_idx = self.dispatcher.job_next_operation_index[job_id]
54
- if next_operation_idx < len(self.dispatcher.instance.jobs[job_id]):
55
- old_start_time = self.earliest_start_times[
56
- job_id, next_operation_idx
57
- ]
58
- next_operation = self.dispatcher.instance.jobs[job_id][
59
- next_operation_idx
60
- ]
61
- new_start_time = max(
62
- scheduled_operation.end_time,
63
- old_start_time,
64
- self.dispatcher.earliest_start_time(next_operation),
65
- )
66
- gap = new_start_time - old_start_time
67
- self.earliest_start_times[job_id, next_operation_idx:] += gap
68
-
69
- # Now, we compute the gap that could be introduced by the new
70
- # next_available_time of the machine.
71
- operations_by_machine = self.dispatcher.instance.operations_by_machine
72
- for operation in operations_by_machine[scheduled_operation.machine_id]:
73
- if self.dispatcher.is_scheduled(operation):
74
- continue
75
- old_start_time = self.earliest_start_times[
76
- operation.job_id, operation.position_in_job
77
- ]
78
- new_start_time = max(old_start_time, scheduled_operation.end_time)
79
- gap = new_start_time - old_start_time
80
- self.earliest_start_times[
81
- operation.job_id, operation.position_in_job :
82
- ] += gap
83
-
84
- self.initialize_features()
85
-
86
- def initialize_features(self):
87
- """Initializes the features based on the current state of the
88
- dispatcher."""
89
- mapping = {
90
- FeatureType.OPERATIONS: self._update_operation_features,
91
- FeatureType.MACHINES: self._update_machine_features,
92
- FeatureType.JOBS: self._update_job_features,
93
- }
94
- for feature_type in self.features:
95
- mapping[feature_type]()
96
-
97
- def _update_operation_features(self):
98
- """Ravels the 2D array into a 1D array"""
99
- current_time = self.dispatcher.current_time()
100
- next_index = 0
101
- for job_id, operations in enumerate(self.dispatcher.instance.jobs):
102
- self.features[FeatureType.OPERATIONS][
103
- next_index : next_index + len(operations), 0
104
- ] = (
105
- self.earliest_start_times[job_id, : len(operations)]
106
- - current_time
107
- )
108
- next_index += len(operations)
109
-
110
- def _update_machine_features(self):
111
- """Picks the minimum start time of all operations that can be scheduled
112
- on that machine"""
113
- current_time = self.dispatcher.current_time()
114
- operations_by_machine = self.dispatcher.instance.operations_by_machine
115
- for machine_id, operations in enumerate(operations_by_machine):
116
- min_earliest_start_time = min(
117
- (
118
- self.earliest_start_times[
119
- operation.job_id, operation.position_in_job
120
- ]
121
- for operation in operations
122
- if not self.dispatcher.is_scheduled(operation)
123
- ),
124
- default=0,
125
- )
126
- self.features[FeatureType.MACHINES][machine_id, 0] = (
127
- min_earliest_start_time - current_time
128
- )
129
-
130
- def _update_job_features(self):
131
- """Picks the earliest start time of the next operation in the job"""
132
- current_time = self.dispatcher.current_time()
133
- for job_id, next_operation_idx in enumerate(
134
- self.dispatcher.job_next_operation_index
135
- ):
136
- job_length = len(self.dispatcher.instance.jobs[job_id])
137
- if next_operation_idx == job_length:
138
- continue
139
- self.features[FeatureType.JOBS][job_id, 0] = (
140
- self.earliest_start_times[job_id, next_operation_idx]
141
- - current_time
142
- )
143
-
144
-
145
- if __name__ == "__main__":
146
- squared_durations_matrix = np.array([[1, 1, 7], [5, 1, 1], [1, 3, 2]])
147
- # Add a zeros column to the left of the matrix
148
- cumulative_durations = np.hstack(
149
- (
150
- np.zeros((squared_durations_matrix.shape[0], 1)),
151
- squared_durations_matrix[:, :-1],
152
- )
153
- )
154
- # Set to nan the values that are not available
155
- cumulative_durations[np.isnan(squared_durations_matrix)] = np.nan
156
- print(cumulative_durations)
@@ -1,58 +0,0 @@
1
- """Contains factory functions for creating node feature encoders."""
2
-
3
- from enum import Enum
4
-
5
- from job_shop_lib.dispatching.feature_observers import (
6
- IsReadyObserver,
7
- EarliestStartTimeObserver,
8
- FeatureObserver,
9
- DurationObserver,
10
- IsScheduledObserver,
11
- PositionInJobObserver,
12
- RemainingOperationsObserver,
13
- IsCompletedObserver,
14
- )
15
-
16
-
17
- class FeatureObserverType(str, Enum):
18
- """Enumeration of node feature creator types for the job shop scheduling
19
- problem."""
20
-
21
- IS_READY = "is_ready"
22
- EARLIEST_START_TIME = "earliest_start_time"
23
- DURATION = "duration"
24
- IS_SCHEDULED = "is_scheduled"
25
- POSITION_IN_JOB = "position_in_job"
26
- REMAINING_OPERATIONS = "remaining_operations"
27
- IS_COMPLETED = "is_completed"
28
- COMPOSITE = "composite"
29
-
30
-
31
- def feature_observer_factory(
32
- node_feature_creator_type: str | FeatureObserverType,
33
- **kwargs,
34
- ) -> FeatureObserver:
35
- """Creates and returns a node feature creator based on the specified
36
- node feature creator type.
37
-
38
- Args:
39
- node_feature_creator_type:
40
- The type of node feature creator to create.
41
- **kwargs:
42
- Additional keyword arguments to pass to the node
43
- feature creator constructor.
44
-
45
- Returns:
46
- A node feature creator instance.
47
- """
48
- mapping: dict[FeatureObserverType, type[FeatureObserver]] = {
49
- FeatureObserverType.IS_READY: IsReadyObserver,
50
- FeatureObserverType.EARLIEST_START_TIME: EarliestStartTimeObserver,
51
- FeatureObserverType.DURATION: DurationObserver,
52
- FeatureObserverType.IS_SCHEDULED: IsScheduledObserver,
53
- FeatureObserverType.POSITION_IN_JOB: PositionInJobObserver,
54
- FeatureObserverType.REMAINING_OPERATIONS: RemainingOperationsObserver,
55
- FeatureObserverType.IS_COMPLETED: IsCompletedObserver,
56
- }
57
- feature_creator = mapping[node_feature_creator_type] # type: ignore[index]
58
- return feature_creator(**kwargs)
@@ -1,113 +0,0 @@
1
- """Home of the `FeatureObserver` class and `FeatureType` enum."""
2
-
3
- import enum
4
-
5
- import numpy as np
6
- from job_shop_lib import ScheduledOperation
7
- from job_shop_lib.dispatching import Dispatcher, DispatcherObserver
8
-
9
-
10
- class FeatureType(str, enum.Enum):
11
- """Types of features that can be extracted."""
12
-
13
- OPERATIONS = "operations"
14
- MACHINES = "machines"
15
- JOBS = "jobs"
16
-
17
-
18
- class FeatureObserver(DispatcherObserver):
19
- """Base class for feature observers."""
20
-
21
- def __init__(
22
- self,
23
- dispatcher: Dispatcher,
24
- feature_types: list[FeatureType] | FeatureType | None = None,
25
- feature_size: dict[FeatureType, int] | int = 1,
26
- is_singleton: bool = True,
27
- subscribe: bool = True,
28
- ):
29
- feature_types = self.get_feature_types_list(feature_types)
30
- if isinstance(feature_size, int):
31
- feature_size = {
32
- feature_type: feature_size for feature_type in feature_types
33
- }
34
- super().__init__(dispatcher, is_singleton, subscribe)
35
-
36
- number_of_entities = {
37
- FeatureType.OPERATIONS: dispatcher.instance.num_operations,
38
- FeatureType.MACHINES: dispatcher.instance.num_machines,
39
- FeatureType.JOBS: dispatcher.instance.num_jobs,
40
- }
41
- self.feature_dimensions = {
42
- feature_type: (
43
- number_of_entities[feature_type],
44
- feature_size[feature_type],
45
- )
46
- for feature_type in feature_types
47
- }
48
- self.features = {
49
- feature_type: np.zeros(
50
- self.feature_dimensions[feature_type],
51
- dtype=np.float32,
52
- )
53
- for feature_type in feature_types
54
- }
55
- self.initialize_features()
56
-
57
- def initialize_features(self):
58
- """Initializes the features based on the current state of the
59
- dispatcher."""
60
-
61
- def update(self, scheduled_operation: ScheduledOperation):
62
- """Updates the features based on the scheduled operation.
63
-
64
- By default, this method just calls `initialize_features`.
65
-
66
- Args:
67
- scheduled_operation:
68
- The operation that has been scheduled.
69
- """
70
- self.initialize_features()
71
-
72
- def reset(self):
73
- """Sets features to zero and calls to `initialize_features`."""
74
- self.set_features_to_zero()
75
- self.initialize_features()
76
-
77
- def set_features_to_zero(
78
- self, exclude: FeatureType | list[FeatureType] | None = None
79
- ):
80
- """Sets features to zero."""
81
- if exclude is None:
82
- exclude = []
83
- if isinstance(exclude, FeatureType):
84
- exclude = [exclude]
85
-
86
- for feature_type in self.features:
87
- if feature_type in exclude:
88
- continue
89
- self.features[feature_type][:] = 0.0
90
-
91
- @staticmethod
92
- def get_feature_types_list(
93
- feature_types: list[FeatureType] | FeatureType | None,
94
- ) -> list[FeatureType]:
95
- """Returns a list of feature types.
96
-
97
- Args:
98
- feature_types:
99
- A list of feature types or a single feature type. If `None`,
100
- all feature types are returned.
101
- """
102
- if isinstance(feature_types, FeatureType):
103
- feature_types = [feature_types]
104
- if feature_types is None:
105
- feature_types = list(FeatureType)
106
- return feature_types
107
-
108
- def __str__(self):
109
- out = [self.__class__.__name__, ":\n"]
110
- out.append("-" * len(out[0]))
111
- for feature_type, feature in self.features.items():
112
- out.append(f"\n{feature_type.value}:\n{feature}")
113
- return "".join(out)
@@ -1,98 +0,0 @@
1
- """Home of the `IsCompletedObserver` class."""
2
-
3
- from typing import Iterable
4
-
5
- import numpy as np
6
-
7
- from job_shop_lib import ScheduledOperation
8
- from job_shop_lib.dispatching import Dispatcher
9
- from job_shop_lib.dispatching.feature_observers import (
10
- FeatureObserver,
11
- FeatureType,
12
- RemainingOperationsObserver,
13
- )
14
-
15
-
16
- class IsCompletedObserver(FeatureObserver):
17
- """Observer that adds a binary feature indicating whether each operation,
18
- machine, or job has been completed."""
19
-
20
- def __init__(
21
- self,
22
- dispatcher: Dispatcher,
23
- feature_types: list[FeatureType] | FeatureType | None = None,
24
- ):
25
- feature_types = self.get_feature_types_list(feature_types)
26
- self.remaining_ops_per_machine = np.zeros(
27
- (dispatcher.instance.num_machines, 1), dtype=int
28
- )
29
- self.remaining_ops_per_job = np.zeros(
30
- (dispatcher.instance.num_jobs, 1), dtype=int
31
- )
32
- super().__init__(dispatcher, feature_types, feature_size=1)
33
-
34
- def initialize_features(self):
35
- self._initialize_remaining_operations()
36
-
37
- def update(self, scheduled_operation: ScheduledOperation):
38
- if FeatureType.OPERATIONS in self.features:
39
- # operation_id = scheduled_operation.operation.operation_id
40
- # self.features[FeatureType.OPERATIONS][operation_id, 0] = 1
41
- completed_operations = [
42
- op.operation_id
43
- for op in self.dispatcher.completed_operations()
44
- ]
45
- self.features[FeatureType.OPERATIONS][completed_operations, 0] = 1
46
- if FeatureType.MACHINES in self.features:
47
- machine_id = scheduled_operation.machine_id
48
- self.remaining_ops_per_machine[machine_id, 0] -= 1
49
- is_completed = self.remaining_ops_per_machine[machine_id, 0] == 0
50
- self.features[FeatureType.MACHINES][machine_id, 0] = is_completed
51
- if FeatureType.JOBS in self.features:
52
- job_id = scheduled_operation.job_id
53
- self.remaining_ops_per_job[job_id, 0] -= 1
54
- is_completed = self.remaining_ops_per_job[job_id, 0] == 0
55
- self.features[FeatureType.JOBS][job_id, 0] = is_completed
56
-
57
- def _initialize_remaining_operations(self):
58
- remaining_ops_observer = self._get_remaining_operations_observer(
59
- self.dispatcher, self.features
60
- )
61
- if remaining_ops_observer is not None:
62
- if FeatureType.JOBS in self.features:
63
- self.remaining_ops_per_job = remaining_ops_observer.features[
64
- FeatureType.JOBS
65
- ].copy()
66
- if FeatureType.MACHINES in self.features:
67
- self.remaining_ops_per_machine = (
68
- remaining_ops_observer.features[
69
- FeatureType.MACHINES
70
- ].copy()
71
- )
72
- return
73
-
74
- # If there is no remaining operations observer, we need to
75
- # compute the remaining operations ourselves.
76
- # We iterate over all operations using scheduled_operations
77
- # instead of uncompleted_operations, because in this case
78
- # they will output the same operations, and the former is slightly
79
- # more efficient.
80
- for operation in self.dispatcher.unscheduled_operations():
81
- if FeatureType.JOBS in self.features:
82
- self.remaining_ops_per_job[operation.job_id, 0] += 1
83
- if FeatureType.MACHINES in self.features:
84
- self.remaining_ops_per_machine[operation.machine_id, 0] += 1
85
-
86
- def _get_remaining_operations_observer(
87
- self, dispatcher: Dispatcher, feature_types: Iterable[FeatureType]
88
- ) -> RemainingOperationsObserver | None:
89
- for observer in dispatcher.subscribers:
90
- if not isinstance(observer, RemainingOperationsObserver):
91
- continue
92
- has_same_features = all(
93
- feature_type in observer.features
94
- for feature_type in feature_types
95
- )
96
- if has_same_features:
97
- return observer
98
- return None
@@ -1,40 +0,0 @@
1
- """Home of the `IsReadyObserver` class."""
2
-
3
- from job_shop_lib.dispatching import Dispatcher
4
- from job_shop_lib.dispatching.feature_observers import (
5
- FeatureObserver,
6
- FeatureType,
7
- )
8
-
9
-
10
- class IsReadyObserver(FeatureObserver):
11
- """Feature creator that adds a binary feature indicating if the operation
12
- is ready to be dispatched."""
13
-
14
- def __init__(
15
- self,
16
- dispatcher: Dispatcher,
17
- feature_types: list[FeatureType] | FeatureType | None = None,
18
- subscribe: bool = True,
19
- ):
20
- super().__init__(
21
- dispatcher, feature_types, feature_size=1, subscribe=subscribe
22
- )
23
-
24
- def initialize_features(self):
25
- self.set_features_to_zero()
26
- for feature_type, feature in self.features.items():
27
- node_ids = self._get_ready_nodes(feature_type)
28
- feature[node_ids, 0] = 1.0
29
-
30
- def _get_ready_nodes(self, feature_type: FeatureType) -> list[int]:
31
- mapping = {
32
- FeatureType.OPERATIONS: self._get_ready_operation_nodes,
33
- FeatureType.MACHINES: self.dispatcher.available_machines,
34
- FeatureType.JOBS: self.dispatcher.available_jobs,
35
- }
36
- return mapping[feature_type]()
37
-
38
- def _get_ready_operation_nodes(self) -> list[int]:
39
- available_operations = self.dispatcher.available_operations()
40
- return [operation.operation_id for operation in available_operations]
@@ -1,116 +0,0 @@
1
- """Contains functions to prune (filter) operations.
2
-
3
- This functions are used by the `Dispatcher` class to reduce the
4
- amount of available operations to choose from.
5
- """
6
-
7
- from collections.abc import Callable, Iterable
8
-
9
- from job_shop_lib import Operation
10
- from job_shop_lib.dispatching import Dispatcher
11
-
12
-
13
- def create_composite_pruning_function(
14
- pruning_functions: Iterable[
15
- Callable[[Dispatcher, list[Operation]], list[Operation]]
16
- ],
17
- ) -> Callable[[Dispatcher, list[Operation]], list[Operation]]:
18
- """Creates and returns a composite pruning strategy function based on the
19
- specified list of pruning strategies.
20
- The composite pruning strategy function filters out operations based on
21
- the specified list of pruning strategies.
22
- Args:
23
- pruning_strategies:
24
- A list of pruning strategies to be used. Supported values are
25
- 'dominated_operations' and 'non_immediate_machines'.
26
- Returns:
27
- A function that takes a Dispatcher instance and a list of Operation
28
- instances as input and returns a list of Operation instances based on
29
- the specified list of pruning strategies.
30
- Raises:
31
- ValueError: If any of the pruning strategies in the list are not
32
- recognized or are not supported.
33
- """
34
-
35
- def composite_pruning_function(
36
- dispatcher: Dispatcher, operations: list[Operation]
37
- ) -> list[Operation]:
38
- pruned_operations = operations
39
- for pruning_function in pruning_functions:
40
- pruned_operations = pruning_function(dispatcher, pruned_operations)
41
-
42
- return pruned_operations
43
-
44
- return composite_pruning_function
45
-
46
-
47
- def prune_dominated_operations(
48
- dispatcher: Dispatcher, operations: list[Operation]
49
- ) -> list[Operation]:
50
- """Filters out all the operations that are dominated.
51
- An operation is dominated if there is another operation that ends before
52
- it starts on the same machine.
53
- """
54
-
55
- min_machine_end_times = _get_min_machine_end_times(dispatcher, operations)
56
-
57
- non_dominated_operations: list[Operation] = []
58
- for operation in operations:
59
- # One benchmark instance has an operation with duration 0
60
- if operation.duration == 0:
61
- return [operation]
62
- for machine_id in operation.machines:
63
- start_time = dispatcher.start_time(operation, machine_id)
64
- is_dominated = start_time >= min_machine_end_times[machine_id]
65
- if not is_dominated:
66
- non_dominated_operations.append(operation)
67
- break
68
-
69
- return non_dominated_operations
70
-
71
-
72
- def prune_non_immediate_machines(
73
- dispatcher: Dispatcher, operations: list[Operation]
74
- ) -> list[Operation]:
75
- """Filters out all the operations associated with machines which earliest
76
- operation is not the current time."""
77
-
78
- is_immediate_machine = _get_immediate_machines(dispatcher, operations)
79
- non_dominated_operations: list[Operation] = []
80
- for operation in operations:
81
- if any(
82
- is_immediate_machine[machine_id]
83
- for machine_id in operation.machines
84
- ):
85
- non_dominated_operations.append(operation)
86
-
87
- return non_dominated_operations
88
-
89
-
90
- def _get_min_machine_end_times(
91
- dispatcher: Dispatcher, available_operations: list[Operation]
92
- ) -> list[int | float]:
93
- end_times_per_machine = [float("inf")] * dispatcher.instance.num_machines
94
- for op in available_operations:
95
- for machine_id in op.machines:
96
- start_time = dispatcher.start_time(op, machine_id)
97
- end_times_per_machine[machine_id] = min(
98
- end_times_per_machine[machine_id], start_time + op.duration
99
- )
100
- return end_times_per_machine
101
-
102
-
103
- def _get_immediate_machines(
104
- self: Dispatcher, available_operations: list[Operation]
105
- ) -> list[bool]:
106
- """Returns the machine ids of the machines that have at least one
107
- operation with the lowest start time (i.e. the start time)."""
108
- working_machines = [False] * self.instance.num_machines
109
- # We can't use the current_time directly because it will cause
110
- # an infinite loop.
111
- current_time = self.min_start_time(available_operations)
112
- for op in available_operations:
113
- for machine_id in op.machines:
114
- if self.start_time(op, machine_id) == current_time:
115
- working_machines[machine_id] = True
116
- return working_machines
@@ -1,7 +0,0 @@
1
- """Package for generating job shop instances."""
2
-
3
- from job_shop_lib.generators.basic_generator import BasicGenerator
4
-
5
- __all__ = [
6
- "BasicGenerator",
7
- ]