job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0a1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. job_shop_lib/__init__.py +16 -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} +9 -4
  4. job_shop_lib/_operation.py +95 -0
  5. job_shop_lib/{schedule.py → _schedule.py} +73 -54
  6. job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +13 -37
  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} +57 -18
  11. job_shop_lib/dispatching/__init__.py +45 -41
  12. job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +153 -80
  13. job_shop_lib/dispatching/_dispatcher_observer_config.py +54 -0
  14. job_shop_lib/dispatching/_factories.py +125 -0
  15. job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +4 -6
  16. job_shop_lib/dispatching/{pruning_functions.py → _ready_operation_filters.py} +6 -35
  17. job_shop_lib/dispatching/_unscheduled_operations_observer.py +69 -0
  18. job_shop_lib/dispatching/feature_observers/__init__.py +16 -10
  19. job_shop_lib/dispatching/feature_observers/{composite_feature_observer.py → _composite_feature_observer.py} +84 -2
  20. job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +6 -17
  21. job_shop_lib/dispatching/feature_observers/{earliest_start_time_observer.py → _earliest_start_time_observer.py} +114 -35
  22. job_shop_lib/dispatching/feature_observers/{factory.py → _factory.py} +31 -5
  23. job_shop_lib/dispatching/feature_observers/{feature_observer.py → _feature_observer.py} +59 -16
  24. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
  25. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +33 -0
  26. job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +1 -8
  27. job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
  28. job_shop_lib/dispatching/rules/__init__.py +51 -0
  29. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +82 -0
  30. job_shop_lib/dispatching/{dispatching_rule_solver.py → rules/_dispatching_rule_solver.py} +44 -15
  31. job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +74 -21
  32. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +69 -0
  33. job_shop_lib/dispatching/rules/_utils.py +127 -0
  34. job_shop_lib/exceptions.py +18 -0
  35. job_shop_lib/generation/__init__.py +2 -2
  36. job_shop_lib/generation/{general_instance_generator.py → _general_instance_generator.py} +26 -7
  37. job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +13 -3
  38. job_shop_lib/graphs/__init__.py +17 -6
  39. job_shop_lib/graphs/{job_shop_graph.py → _job_shop_graph.py} +81 -2
  40. job_shop_lib/graphs/{node.py → _node.py} +18 -12
  41. job_shop_lib/graphs/graph_updaters/__init__.py +13 -0
  42. job_shop_lib/graphs/graph_updaters/_graph_updater.py +59 -0
  43. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +154 -0
  44. job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
  45. job_shop_lib/reinforcement_learning/__init__.py +41 -0
  46. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +366 -0
  47. job_shop_lib/reinforcement_learning/_reward_observers.py +85 -0
  48. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +337 -0
  49. job_shop_lib/reinforcement_learning/_types_and_constants.py +61 -0
  50. job_shop_lib/reinforcement_learning/_utils.py +96 -0
  51. job_shop_lib/visualization/__init__.py +20 -4
  52. job_shop_lib/visualization/{agent_task_graph.py → _agent_task_graph.py} +28 -9
  53. job_shop_lib/visualization/_gantt_chart_creator.py +219 -0
  54. job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +388 -0
  55. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/METADATA +68 -44
  56. job_shop_lib-1.0.0a1.dist-info/RECORD +66 -0
  57. job_shop_lib/benchmarking/load_benchmark.py +0 -142
  58. job_shop_lib/cp_sat/__init__.py +0 -5
  59. job_shop_lib/dispatching/factories.py +0 -206
  60. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
  61. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
  62. job_shop_lib/generators/__init__.py +0 -8
  63. job_shop_lib/generators/basic_generator.py +0 -200
  64. job_shop_lib/generators/transformations.py +0 -164
  65. job_shop_lib/operation.py +0 -122
  66. job_shop_lib/visualization/create_gif.py +0 -209
  67. job_shop_lib-0.5.1.dist-info/RECORD +0 -52
  68. /job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +0 -0
  69. /job_shop_lib/generation/{transformations.py → _transformations.py} +0 -0
  70. /job_shop_lib/graphs/{build_agent_task_graph.py → _build_agent_task_graph.py} +0 -0
  71. /job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +0 -0
  72. /job_shop_lib/graphs/{constants.py → _constants.py} +0 -0
  73. /job_shop_lib/visualization/{disjunctive_graph.py → _disjunctive_graph.py} +0 -0
  74. /job_shop_lib/visualization/{gantt_chart.py → _gantt_chart.py} +0 -0
  75. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/LICENSE +0 -0
  76. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/WHEEL +0 -0
@@ -3,7 +3,9 @@
3
3
  import enum
4
4
 
5
5
  import numpy as np
6
+
6
7
  from job_shop_lib import ScheduledOperation
8
+ from job_shop_lib.exceptions import ValidationError
7
9
  from job_shop_lib.dispatching import Dispatcher, DispatcherObserver
8
10
 
9
11
 
@@ -16,22 +18,40 @@ class FeatureType(str, enum.Enum):
16
18
 
17
19
 
18
20
  class FeatureObserver(DispatcherObserver):
19
- """Base class for feature observers."""
21
+ """Base class for feature observers.
22
+
23
+ Feature observers are not singleton by default. This means that more than
24
+ one instance of the same feature observer type can be subscribed to the
25
+ dispatcher. This is useful when the first subscriber only observes a subset
26
+ of the features, and the second subscriber observes a different subset of
27
+ them. For example, the first subscriber could observe only the
28
+ operation-related features, while the second subscriber could observe the
29
+ jobs.
30
+ """
31
+
32
+ _is_singleton = False
33
+ _feature_size: dict[FeatureType, int] | int = 1
34
+ _supported_feature_types = list(FeatureType)
35
+
36
+ __slots__ = (
37
+ "features",
38
+ "feature_dimensions",
39
+ )
20
40
 
21
41
  def __init__(
22
42
  self,
23
43
  dispatcher: Dispatcher,
24
- feature_types: list[FeatureType] | FeatureType | None = None,
25
- feature_size: dict[FeatureType, int] | int = 1,
26
- is_singleton: bool = True,
44
+ *,
27
45
  subscribe: bool = True,
46
+ feature_types: list[FeatureType] | FeatureType | None = None,
28
47
  ):
29
- feature_types = self.get_feature_types_list(feature_types)
30
- if isinstance(feature_size, int):
48
+ feature_types = self._get_feature_types_list(feature_types)
49
+ if isinstance(self._feature_size, int):
31
50
  feature_size = {
32
- feature_type: feature_size for feature_type in feature_types
51
+ feature_type: self._feature_size
52
+ for feature_type in feature_types
33
53
  }
34
- super().__init__(dispatcher, is_singleton, subscribe)
54
+ super().__init__(dispatcher, subscribe=subscribe)
35
55
 
36
56
  number_of_entities = {
37
57
  FeatureType.OPERATIONS: dispatcher.instance.num_operations,
@@ -54,6 +74,21 @@ class FeatureObserver(DispatcherObserver):
54
74
  }
55
75
  self.initialize_features()
56
76
 
77
+ @property
78
+ def feature_size(self) -> dict[FeatureType, int]:
79
+ """Returns the size of the features."""
80
+ if isinstance(self._feature_size, int):
81
+ return {
82
+ feature_type: self._feature_size
83
+ for feature_type in self.features
84
+ }
85
+ return self._feature_size
86
+
87
+ @property
88
+ def supported_feature_types(self) -> list[FeatureType]:
89
+ """Returns the supported feature types."""
90
+ return self._supported_feature_types
91
+
57
92
  def initialize_features(self):
58
93
  """Initializes the features based on the current state of the
59
94
  dispatcher."""
@@ -61,16 +96,16 @@ class FeatureObserver(DispatcherObserver):
61
96
  def update(self, scheduled_operation: ScheduledOperation):
62
97
  """Updates the features based on the scheduled operation.
63
98
 
64
- By default, this method just calls `initialize_features`.
99
+ By default, this method just calls :meth:`initialize_features`.
65
100
 
66
101
  Args:
67
- scheduled_operation:
102
+ ScheduledOperation:
68
103
  The operation that has been scheduled.
69
104
  """
70
105
  self.initialize_features()
71
106
 
72
107
  def reset(self):
73
- """Sets features to zero and calls to `initialize_features`."""
108
+ """Sets features to zero and calls to :meth:``initialize_features``."""
74
109
  self.set_features_to_zero()
75
110
  self.initialize_features()
76
111
 
@@ -88,21 +123,29 @@ class FeatureObserver(DispatcherObserver):
88
123
  continue
89
124
  self.features[feature_type][:] = 0.0
90
125
 
91
- @staticmethod
92
- def get_feature_types_list(
126
+ def _get_feature_types_list(
127
+ self,
93
128
  feature_types: list[FeatureType] | FeatureType | None,
94
129
  ) -> list[FeatureType]:
95
130
  """Returns a list of feature types.
96
131
 
97
132
  Args:
98
133
  feature_types:
99
- A list of feature types or a single feature type. If `None`,
134
+ A list of feature types or a single feature type. If ``None``,
100
135
  all feature types are returned.
101
136
  """
102
137
  if isinstance(feature_types, FeatureType):
103
- feature_types = [feature_types]
138
+ return [feature_types]
104
139
  if feature_types is None:
105
- feature_types = list(FeatureType)
140
+ return self._supported_feature_types
141
+
142
+ for feature_type in feature_types:
143
+ if feature_type not in self._supported_feature_types:
144
+ raise ValidationError(
145
+ f"Feature type {feature_type} is not supported."
146
+ " Supported feature types are: "
147
+ f"{self._supported_feature_types}"
148
+ )
106
149
  return feature_types
107
150
 
108
151
  def __str__(self):
@@ -0,0 +1,97 @@
1
+ """Home of the `IsCompletedObserver` class."""
2
+
3
+ import numpy as np
4
+
5
+ from job_shop_lib import ScheduledOperation
6
+ from job_shop_lib.dispatching import (
7
+ Dispatcher,
8
+ DispatcherObserver,
9
+ )
10
+ from job_shop_lib.dispatching.feature_observers import (
11
+ FeatureObserver,
12
+ FeatureType,
13
+ RemainingOperationsObserver,
14
+ )
15
+
16
+
17
+ class IsCompletedObserver(FeatureObserver):
18
+ """Observer that adds a binary feature indicating whether each operation,
19
+ machine, or job has been completed."""
20
+
21
+ def __init__(
22
+ self,
23
+ dispatcher: Dispatcher,
24
+ feature_types: list[FeatureType] | FeatureType | None = None,
25
+ subscribe: bool = True,
26
+ ):
27
+ feature_types = self._get_feature_types_list(feature_types)
28
+ self.remaining_ops_per_machine = np.zeros(
29
+ (dispatcher.instance.num_machines, 1), dtype=int
30
+ )
31
+ self.remaining_ops_per_job = np.zeros(
32
+ (dispatcher.instance.num_jobs, 1), dtype=int
33
+ )
34
+ super().__init__(
35
+ dispatcher,
36
+ feature_types=feature_types,
37
+ subscribe=subscribe,
38
+ )
39
+
40
+ def initialize_features(self):
41
+ def _has_same_features(observer: DispatcherObserver) -> bool:
42
+ if not isinstance(observer, RemainingOperationsObserver):
43
+ return False
44
+ return all(
45
+ feature_type in observer.features
46
+ for feature_type in remaining_ops_feature_types
47
+ )
48
+
49
+ self.set_features_to_zero()
50
+
51
+ remaining_ops_feature_types = [
52
+ feature_type
53
+ for feature_type in self.features.keys()
54
+ if feature_type != FeatureType.OPERATIONS
55
+ ]
56
+ remaining_ops_observer = self.dispatcher.create_or_get_observer(
57
+ RemainingOperationsObserver,
58
+ condition=_has_same_features,
59
+ feature_types=remaining_ops_feature_types,
60
+ )
61
+ if FeatureType.JOBS in self.features:
62
+ self.remaining_ops_per_job = remaining_ops_observer.features[
63
+ FeatureType.JOBS
64
+ ].copy()
65
+ if FeatureType.MACHINES in self.features:
66
+ self.remaining_ops_per_machine = remaining_ops_observer.features[
67
+ FeatureType.MACHINES
68
+ ].copy()
69
+
70
+ def reset(self):
71
+ self.initialize_features()
72
+
73
+ def update(self, scheduled_operation: ScheduledOperation):
74
+ if FeatureType.OPERATIONS in self.features:
75
+ completed_operations = [
76
+ op.operation_id
77
+ for op in self.dispatcher.completed_operations()
78
+ ]
79
+ self.features[FeatureType.OPERATIONS][completed_operations, 0] = 1
80
+ if FeatureType.MACHINES in self.features:
81
+ self.remaining_ops_per_machine[
82
+ scheduled_operation.operation.machines, 0
83
+ ] -= 1
84
+ is_completed = (
85
+ self.remaining_ops_per_machine[
86
+ scheduled_operation.operation.machines, 0
87
+ ]
88
+ == 0
89
+ )
90
+ self.features[FeatureType.MACHINES][
91
+ scheduled_operation.operation.machines, 0
92
+ ] = is_completed
93
+ if FeatureType.JOBS in self.features:
94
+ job_id = scheduled_operation.job_id
95
+ self.remaining_ops_per_job[job_id, 0] -= 1
96
+ is_completed = self.remaining_ops_per_job[job_id, 0] == 0
97
+ self.features[FeatureType.JOBS][job_id, 0] = is_completed
@@ -0,0 +1,33 @@
1
+ """Home of the `IsReadyObserver` class."""
2
+
3
+ from job_shop_lib.dispatching.feature_observers import (
4
+ FeatureObserver,
5
+ FeatureType,
6
+ )
7
+
8
+
9
+ class IsReadyObserver(FeatureObserver):
10
+ """Feature creator that adds a binary feature indicating if the operation
11
+ is ready to be dispatched."""
12
+
13
+ def initialize_features(self):
14
+ self.set_features_to_zero()
15
+ for feature_type, feature in self.features.items():
16
+ feature_ids = self._get_ready_feature_ids(feature_type)
17
+ feature[feature_ids, 0] = 1.0
18
+
19
+ def _get_ready_feature_ids(self, feature_type: FeatureType) -> list[int]:
20
+ if feature_type == FeatureType.OPERATIONS:
21
+ return self._get_ready_operations()
22
+ if feature_type == FeatureType.MACHINES:
23
+ return self.dispatcher.available_machines()
24
+ if feature_type == FeatureType.JOBS:
25
+ return self.dispatcher.available_jobs()
26
+ raise ValueError(f"Feature type {feature_type} is not supported.")
27
+
28
+ def reset(self):
29
+ self.initialize_features()
30
+
31
+ def _get_ready_operations(self) -> list[int]:
32
+ available_operations = self.dispatcher.ready_operations()
33
+ return [operation.operation_id for operation in available_operations]
@@ -1,6 +1,5 @@
1
1
  """Home of the `PositionInJobObserver` class."""
2
2
 
3
- from job_shop_lib.dispatching import Dispatcher
4
3
  from job_shop_lib import ScheduledOperation
5
4
  from job_shop_lib.dispatching.feature_observers import (
6
5
  FeatureObserver,
@@ -15,13 +14,7 @@ class PositionInJobObserver(FeatureObserver):
15
14
  Positions are adjusted dynamically as operations are scheduled.
16
15
  """
17
16
 
18
- def __init__(self, dispatcher: Dispatcher, subscribe: bool = True):
19
- super().__init__(
20
- dispatcher,
21
- feature_types=[FeatureType.OPERATIONS],
22
- feature_size=1,
23
- subscribe=subscribe,
24
- )
17
+ _supported_feature_types = [FeatureType.OPERATIONS]
25
18
 
26
19
  def initialize_features(self):
27
20
  for operation in self.dispatcher.unscheduled_operations():
@@ -1,7 +1,7 @@
1
1
  """Home of the `RemainingOperationsObserver` class."""
2
2
 
3
3
  from job_shop_lib import ScheduledOperation
4
- from job_shop_lib.dispatching import Dispatcher
4
+ from job_shop_lib.dispatching import UnscheduledOperationsObserver
5
5
  from job_shop_lib.dispatching.feature_observers import (
6
6
  FeatureObserver,
7
7
  FeatureType,
@@ -12,38 +12,20 @@ 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 FeatureType.OPERATIONS.
15
+ It does not support :class:`FeatureType.OPERATIONS`.
16
16
  """
17
17
 
18
- def __init__(
19
- self,
20
- dispatcher: Dispatcher,
21
- feature_types: list[FeatureType] | FeatureType | None = None,
22
- subscribe: bool = True,
23
- ):
24
- if feature_types is None:
25
- feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
26
-
27
- if (
28
- feature_types == FeatureType.OPERATIONS
29
- or FeatureType.OPERATIONS in feature_types
30
- ):
31
- raise ValueError("FeatureType.OPERATIONS is not supported.")
32
- super().__init__(
33
- dispatcher,
34
- feature_types=feature_types,
35
- feature_size=1,
36
- subscribe=subscribe,
37
- )
18
+ _supported_feature_types = [FeatureType.MACHINES, FeatureType.JOBS]
38
19
 
39
20
  def initialize_features(self):
40
- for operation in self.dispatcher.unscheduled_operations():
21
+ unscheduled_ops_observer = self.dispatcher.create_or_get_observer(
22
+ UnscheduledOperationsObserver
23
+ )
24
+ for operation in unscheduled_ops_observer.unscheduled_operations:
41
25
  if FeatureType.JOBS in self.features:
42
26
  self.features[FeatureType.JOBS][operation.job_id, 0] += 1
43
27
  if FeatureType.MACHINES in self.features:
44
- self.features[FeatureType.MACHINES][
45
- operation.machine_id, 0
46
- ] += 1
28
+ self.features[FeatureType.MACHINES][operation.machines, 0] += 1
47
29
 
48
30
  def update(self, scheduled_operation: ScheduledOperation):
49
31
  if FeatureType.JOBS in self.features:
@@ -0,0 +1,51 @@
1
+ """Contains the dispatching rules for the job shop scheduling problem."""
2
+
3
+ from ._dispatching_rules_functions import (
4
+ shortest_processing_time_rule,
5
+ first_come_first_served_rule,
6
+ most_work_remaining_rule,
7
+ most_operations_remaining_rule,
8
+ random_operation_rule,
9
+ score_based_rule,
10
+ score_based_rule_with_tie_breaker,
11
+ shortest_processing_time_score,
12
+ first_come_first_served_score,
13
+ MostWorkRemainingScorer,
14
+ most_operations_remaining_score,
15
+ random_score,
16
+ observer_based_most_work_remaining_rule,
17
+ )
18
+ from ._machine_chooser_factory import (
19
+ MachineChooserType,
20
+ MachineChooser,
21
+ machine_chooser_factory,
22
+ )
23
+
24
+ from ._dispatching_rule_factory import (
25
+ dispatching_rule_factory,
26
+ DispatchingRuleType,
27
+ )
28
+ from ._dispatching_rule_solver import DispatchingRuleSolver
29
+
30
+
31
+ __all__ = [
32
+ "shortest_processing_time_rule",
33
+ "first_come_first_served_rule",
34
+ "most_work_remaining_rule",
35
+ "most_operations_remaining_rule",
36
+ "random_operation_rule",
37
+ "score_based_rule",
38
+ "score_based_rule_with_tie_breaker",
39
+ "shortest_processing_time_score",
40
+ "first_come_first_served_score",
41
+ "MostWorkRemainingScorer",
42
+ "most_operations_remaining_score",
43
+ "random_score",
44
+ "dispatching_rule_factory",
45
+ "DispatchingRuleType",
46
+ "MachineChooserType",
47
+ "machine_chooser_factory",
48
+ "MachineChooser",
49
+ "DispatchingRuleSolver",
50
+ "observer_based_most_work_remaining_rule",
51
+ ]
@@ -0,0 +1,82 @@
1
+ """Contains factory functions for creating dispatching rules, machine choosers,
2
+ and pruning functions for the job shop scheduling problem.
3
+
4
+ The factory functions create and return the appropriate functions based on the
5
+ specified names or enums.
6
+ """
7
+
8
+ from enum import Enum
9
+ from collections.abc import Callable
10
+
11
+ from job_shop_lib import Operation
12
+ from job_shop_lib.exceptions import ValidationError
13
+ from job_shop_lib.dispatching import Dispatcher
14
+ from job_shop_lib.dispatching.rules import (
15
+ shortest_processing_time_rule,
16
+ first_come_first_served_rule,
17
+ most_operations_remaining_rule,
18
+ random_operation_rule,
19
+ most_work_remaining_rule,
20
+ )
21
+
22
+
23
+ class DispatchingRuleType(str, Enum):
24
+ """Enumeration of dispatching rules for the job shop scheduling problem."""
25
+
26
+ SHORTEST_PROCESSING_TIME = "shortest_processing_time"
27
+ FIRST_COME_FIRST_SERVED = "first_come_first_served"
28
+ MOST_WORK_REMAINING = "most_work_remaining"
29
+ MOST_OPERATIONS_REMAINING = "most_operations_remaining"
30
+ RANDOM = "random"
31
+
32
+
33
+ def dispatching_rule_factory(
34
+ dispatching_rule: str | DispatchingRuleType,
35
+ ) -> Callable[[Dispatcher], Operation]:
36
+ """Creates and returns a dispatching rule function based on the specified
37
+ dispatching rule name.
38
+
39
+ The dispatching rule function determines the order in which operations are
40
+ selected for execution based on certain criteria such as shortest
41
+ processing time, first come first served, etc.
42
+
43
+ Args:
44
+ dispatching_rule: The name of the dispatching rule to be used.
45
+ Supported values are 'shortest_processing_time',
46
+ 'first_come_first_served', 'most_work_remaining',
47
+ and 'random'.
48
+
49
+ Returns:
50
+ A function that takes a Dispatcher instance as input and returns an
51
+ Operation based on the specified dispatching rule.
52
+
53
+ Raises:
54
+ ValidationError:
55
+ If the dispatching_rule argument is not recognized or it is
56
+ not supported.
57
+ """
58
+ dispatching_rules: dict[
59
+ DispatchingRuleType,
60
+ Callable[[Dispatcher], Operation],
61
+ ] = {
62
+ DispatchingRuleType.SHORTEST_PROCESSING_TIME: (
63
+ shortest_processing_time_rule
64
+ ),
65
+ DispatchingRuleType.FIRST_COME_FIRST_SERVED: (
66
+ first_come_first_served_rule
67
+ ),
68
+ DispatchingRuleType.MOST_WORK_REMAINING: most_work_remaining_rule,
69
+ DispatchingRuleType.MOST_OPERATIONS_REMAINING: (
70
+ most_operations_remaining_rule
71
+ ),
72
+ DispatchingRuleType.RANDOM: random_operation_rule,
73
+ }
74
+
75
+ dispatching_rule = dispatching_rule.lower()
76
+ if dispatching_rule not in dispatching_rules:
77
+ raise ValidationError(
78
+ f"Dispatching rule {dispatching_rule} not recognized. Available "
79
+ f"dispatching rules: {', '.join(dispatching_rules)}."
80
+ )
81
+
82
+ return dispatching_rules[dispatching_rule] # type: ignore[index]
@@ -4,13 +4,15 @@ from collections.abc import Callable
4
4
 
5
5
  from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
6
6
  from job_shop_lib.dispatching import (
7
+ ready_operations_filter_factory,
8
+ Dispatcher,
9
+ ReadyOperationsFilterType,
10
+ )
11
+ from job_shop_lib.dispatching.rules import (
7
12
  dispatching_rule_factory,
8
13
  machine_chooser_factory,
9
- pruning_function_factory,
10
- DispatchingRule,
11
- MachineChooser,
12
- Dispatcher,
13
- PruningFunction,
14
+ DispatchingRuleType,
15
+ MachineChooserType,
14
16
  )
15
17
 
16
18
 
@@ -34,15 +36,15 @@ class DispatchingRuleSolver(BaseSolver):
34
36
  self,
35
37
  dispatching_rule: (
36
38
  str | Callable[[Dispatcher], Operation]
37
- ) = DispatchingRule.MOST_WORK_REMAINING,
39
+ ) = DispatchingRuleType.MOST_WORK_REMAINING,
38
40
  machine_chooser: (
39
41
  str | Callable[[Dispatcher, Operation], int]
40
- ) = MachineChooser.FIRST,
42
+ ) = MachineChooserType.FIRST,
41
43
  pruning_function: (
42
44
  str
43
45
  | Callable[[Dispatcher, list[Operation]], list[Operation]]
44
46
  | None
45
- ) = PruningFunction.DOMINATED_OPERATIONS,
47
+ ) = ReadyOperationsFilterType.DOMINATED_OPERATIONS,
46
48
  ):
47
49
  """Initializes the solver with the given dispatching rule, machine
48
50
  chooser and pruning function.
@@ -70,7 +72,9 @@ class DispatchingRuleSolver(BaseSolver):
70
72
  if isinstance(machine_chooser, str):
71
73
  machine_chooser = machine_chooser_factory(machine_chooser)
72
74
  if isinstance(pruning_function, str):
73
- pruning_function = pruning_function_factory(pruning_function)
75
+ pruning_function = ready_operations_filter_factory(
76
+ pruning_function
77
+ )
74
78
 
75
79
  self.dispatching_rule = dispatching_rule
76
80
  self.machine_chooser = machine_chooser
@@ -83,7 +87,7 @@ class DispatchingRuleSolver(BaseSolver):
83
87
  dispatching rule algorithm."""
84
88
  if dispatcher is None:
85
89
  dispatcher = Dispatcher(
86
- instance, pruning_function=self.pruning_function
90
+ instance, ready_operations_filter=self.pruning_function
87
91
  )
88
92
  while not dispatcher.schedule.is_complete():
89
93
  self.step(dispatcher)
@@ -105,15 +109,40 @@ class DispatchingRuleSolver(BaseSolver):
105
109
 
106
110
  if __name__ == "__main__":
107
111
  import time
112
+ import cProfile
113
+ import pstats
114
+ from io import StringIO
108
115
  from job_shop_lib.benchmarking import load_benchmark_instance
109
116
 
110
- ta_instances = []
111
- for i in range(1, 81):
112
- ta_instances.append(load_benchmark_instance(f"ta{i:02d}"))
113
- solver = DispatchingRuleSolver(dispatching_rule="most_work_remaining")
114
- # cProfile.run("for instance in ta_instances: solver.solve(instance)")
117
+ # from job_shop_lib.dispatching.rules._dispatching_rules_functions import (
118
+ # most_work_remaining_rule_2,
119
+ # )
120
+
121
+ ta_instances = [
122
+ load_benchmark_instance(f"ta{i:02d}") for i in range(1, 81)
123
+ ]
124
+ solver = DispatchingRuleSolver(
125
+ dispatching_rule="most_work_remaining", pruning_function=None
126
+ )
127
+
115
128
  start = time.perf_counter()
129
+
130
+ # Create a Profile object
131
+ profiler = cProfile.Profile()
132
+
133
+ # Run the code under profiling
134
+ profiler.enable()
116
135
  for instance_ in ta_instances:
117
136
  solver.solve(instance_)
137
+ profiler.disable()
138
+
118
139
  end = time.perf_counter()
140
+
141
+ # Print elapsed time
119
142
  print(f"Elapsed time: {end - start:.2f} seconds.")
143
+
144
+ # 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
148
+ # print(s.getvalue())