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
@@ -1,7 +1,11 @@
1
1
  """Home of the `Node` class."""
2
2
 
3
- from job_shop_lib import Operation, JobShopLibError
4
- from job_shop_lib.graphs.constants import NodeType
3
+ from job_shop_lib import Operation
4
+ from job_shop_lib.exceptions import (
5
+ UninitializedAttributeError,
6
+ ValidationError,
7
+ )
8
+ from job_shop_lib.graphs._constants import NodeType
5
9
 
6
10
 
7
11
  class Node:
@@ -75,21 +79,21 @@ class Node:
75
79
  node_type is NodeType.JOB.
76
80
 
77
81
  Raises:
78
- JobShopLibError:
82
+ ValidationError:
79
83
  If the node_type is OPERATION and operation is None.
80
- JobShopLibError:
84
+ ValidationError:
81
85
  If the node_type is MACHINE and machine_id is None.
82
- JobShopLibError:
86
+ ValidationError:
83
87
  If the node_type is JOB and job_id is None.
84
88
  """
85
89
  if node_type == NodeType.OPERATION and operation is None:
86
- raise JobShopLibError("Operation node must have an operation.")
90
+ raise ValidationError("Operation node must have an operation.")
87
91
 
88
92
  if node_type == NodeType.MACHINE and machine_id is None:
89
- raise JobShopLibError("Machine node must have a machine_id.")
93
+ raise ValidationError("Machine node must have a machine_id.")
90
94
 
91
95
  if node_type == NodeType.JOB and job_id is None:
92
- raise JobShopLibError("Job node must have a job_id.")
96
+ raise ValidationError("Job node must have a job_id.")
93
97
 
94
98
  self.node_type = node_type
95
99
  self._node_id: int | None = None
@@ -102,7 +106,9 @@ class Node:
102
106
  def node_id(self) -> int:
103
107
  """Returns a unique identifier for the node."""
104
108
  if self._node_id is None:
105
- raise JobShopLibError("Node has not been assigned an id.")
109
+ raise UninitializedAttributeError(
110
+ "Node has not been assigned an id."
111
+ )
106
112
  return self._node_id
107
113
 
108
114
  @node_id.setter
@@ -116,7 +122,7 @@ class Node:
116
122
  This property is mandatory for nodes of type `OPERATION`.
117
123
  """
118
124
  if self._operation is None:
119
- raise JobShopLibError("Node has no operation.")
125
+ raise UninitializedAttributeError("Node has no operation.")
120
126
  return self._operation
121
127
 
122
128
  @property
@@ -126,7 +132,7 @@ class Node:
126
132
  This property is mandatory for nodes of type `MACHINE`.
127
133
  """
128
134
  if self._machine_id is None:
129
- raise JobShopLibError("Node has no `machine_id`.")
135
+ raise UninitializedAttributeError("Node has no `machine_id`.")
130
136
  return self._machine_id
131
137
 
132
138
  @property
@@ -136,7 +142,7 @@ class Node:
136
142
  This property is mandatory for nodes of type `JOB`.
137
143
  """
138
144
  if self._job_id is None:
139
- raise JobShopLibError("Node has no `job_id`.")
145
+ raise UninitializedAttributeError("Node has no `job_id`.")
140
146
  return self._job_id
141
147
 
142
148
  def __hash__(self) -> int:
@@ -0,0 +1,13 @@
1
+ """Contains classes and functions for updating the graph representation of the
2
+ job shop scheduling problem."""
3
+
4
+ from ._graph_updater import GraphUpdater
5
+ from ._utils import remove_completed_operations
6
+ from ._residual_graph_updater import ResidualGraphUpdater
7
+
8
+
9
+ __all__ = [
10
+ "GraphUpdater",
11
+ "remove_completed_operations",
12
+ "ResidualGraphUpdater",
13
+ ]
@@ -0,0 +1,59 @@
1
+ """Home of the `GraphUpdater` class."""
2
+
3
+ from abc import abstractmethod
4
+ from copy import deepcopy
5
+
6
+ from job_shop_lib import ScheduledOperation
7
+ from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
8
+ from job_shop_lib.graphs import JobShopGraph
9
+
10
+
11
+ class GraphUpdater(DispatcherObserver):
12
+ """Observer that builds and updates a job shop graph.
13
+
14
+ This observer uses a provided graph builder function to initialize the
15
+ job shop graph and a graph updater function to update the graph after
16
+ each scheduled operation.
17
+
18
+ Attributes:
19
+ initial_job_shop_graph:
20
+ The initial job shop graph. This is a copy of the graph that was
21
+ received when the observer was created. It is used to reset the
22
+ graph to its initial state.
23
+ job_shop_graph:
24
+ The current job shop graph. This is the graph that is updated
25
+ after each scheduled operation.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ dispatcher: Dispatcher,
31
+ job_shop_graph: JobShopGraph,
32
+ *,
33
+ subscribe: bool = True,
34
+ ):
35
+ """Initializes the class.
36
+
37
+ Args:
38
+ dispatcher:
39
+ The dispatcher instance to observe.
40
+ job_shop_graph:
41
+ The job shop graph to update.
42
+ subscribe:
43
+ Whether to subscribe to the dispatcher. If ``True``, the
44
+ observer will subscribe to the dispatcher when it is
45
+ initialized. If ``False``, the observer will not subscribe
46
+ to the dispatcher.
47
+ """
48
+ super().__init__(dispatcher, subscribe=subscribe)
49
+ self.initial_job_shop_graph = deepcopy(job_shop_graph)
50
+ self.job_shop_graph = job_shop_graph
51
+
52
+ def reset(self) -> None:
53
+ """Resets the job shop graph."""
54
+ self.job_shop_graph = deepcopy(self.initial_job_shop_graph)
55
+
56
+ @abstractmethod
57
+ def update(self, scheduled_operation: ScheduledOperation) -> None:
58
+ """Updates the job shop graph after an operation has been
59
+ dispatched."""
@@ -0,0 +1,154 @@
1
+ """Home of the `ResidualGraphUpdater` class."""
2
+
3
+ from job_shop_lib import ScheduledOperation
4
+ from job_shop_lib.exceptions import UninitializedAttributeError
5
+ from job_shop_lib.graphs import NodeType, JobShopGraph
6
+ from job_shop_lib.graphs.graph_updaters import (
7
+ GraphUpdater,
8
+ remove_completed_operations,
9
+ )
10
+ from job_shop_lib.dispatching import Dispatcher
11
+ from job_shop_lib.dispatching.feature_observers import (
12
+ IsCompletedObserver,
13
+ FeatureType,
14
+ )
15
+ from job_shop_lib.dispatching import DispatcherObserver
16
+
17
+
18
+ class ResidualGraphUpdater(GraphUpdater):
19
+ """Updates the residual graph based on the completed operations.
20
+
21
+ This observer updates the residual graph by removing the completed
22
+ operation, machine and job nodes from the graph. It subscribes to the
23
+ :class:`~job_shop_lib.dispatching.feature_observers.IsCompletedObserver`
24
+ to determine which operations, machines and jobs have been completed.
25
+
26
+ Attributes:
27
+ remove_completed_machine_nodes:
28
+ If True, removes completed machine nodes from the graph.
29
+ remove_completed_job_nodes:
30
+ If True, removes completed job nodes from the graph.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ dispatcher: Dispatcher,
36
+ job_shop_graph: JobShopGraph,
37
+ *,
38
+ subscribe: bool = True,
39
+ remove_completed_machine_nodes: bool = True,
40
+ remove_completed_job_nodes: bool = True,
41
+ ):
42
+ """Initializes the residual graph updater.
43
+
44
+ Args:
45
+ dispatcher:
46
+ The dispatcher instance to observe.
47
+ job_shop_graph:
48
+ The job shop graph to update.
49
+ subscribe:
50
+ If True, automatically subscribes the observer to the
51
+ dispatcher. Defaults to True.
52
+ remove_completed_machine_nodes:
53
+ If True, removes completed machine nodes from the graph.
54
+ Defaults to True.
55
+ remove_completed_job_nodes:
56
+ If True, removes completed job nodes from the graph.
57
+ Defaults to True.
58
+ """
59
+
60
+ self._is_completed_observer: None | IsCompletedObserver = None
61
+ self.remove_completed_job_nodes = remove_completed_job_nodes
62
+ self.remove_completed_machine_nodes = remove_completed_machine_nodes
63
+ self._initialize_is_completed_observer_attribute(dispatcher)
64
+
65
+ # It is important to initialize the `IsCompletedObserver` before
66
+ # calling the parent class constructor to ensure the observer is
67
+ # updated before the `update` method of this class is called.
68
+ super().__init__(
69
+ dispatcher,
70
+ job_shop_graph,
71
+ subscribe=subscribe,
72
+ )
73
+
74
+ def _initialize_is_completed_observer_attribute(
75
+ self, dispatcher: Dispatcher
76
+ ):
77
+ def has_all_features(observer: DispatcherObserver) -> bool:
78
+ if not isinstance(observer, IsCompletedObserver):
79
+ return False # Make the type checker happy.
80
+
81
+ for feature_type in feature_types:
82
+ if feature_type not in observer.features.keys():
83
+ return False
84
+ return True
85
+
86
+ feature_types: list[FeatureType] = []
87
+ if self.remove_completed_machine_nodes:
88
+ feature_types.append(FeatureType.MACHINES)
89
+ if self.remove_completed_job_nodes:
90
+ feature_types.append(FeatureType.JOBS)
91
+ if feature_types:
92
+ self._is_completed_observer = dispatcher.create_or_get_observer(
93
+ IsCompletedObserver,
94
+ condition=has_all_features,
95
+ feature_types=feature_types,
96
+ )
97
+
98
+ @property
99
+ def is_completed_observer(self) -> IsCompletedObserver:
100
+ """Returns the :class:`~job_shop_lib.dispatching.feature_observers.
101
+ IsCompletedObserver` instance."""
102
+
103
+ if self._is_completed_observer is None:
104
+ raise UninitializedAttributeError(
105
+ "The `is_completed_observer` attribute has not been "
106
+ "initialized. Set `remove_completed_machine_nodes` or "
107
+ "remove_completed_job_nodes` to True when initializing the "
108
+ "ResidualGraphUpdater."
109
+ )
110
+ return self._is_completed_observer
111
+
112
+ def update(self, scheduled_operation: ScheduledOperation) -> None:
113
+ """Updates the residual graph based on the completed operations."""
114
+ remove_completed_operations(
115
+ self.job_shop_graph,
116
+ completed_operations=self.dispatcher.completed_operations(),
117
+ )
118
+ graph_has_machine_nodes = bool(
119
+ self.job_shop_graph.nodes_by_type[NodeType.MACHINE]
120
+ )
121
+ if self.remove_completed_machine_nodes and graph_has_machine_nodes:
122
+ self._remove_completed_machine_nodes()
123
+
124
+ graph_has_machine_nodes = bool(
125
+ self.job_shop_graph.nodes_by_type[NodeType.JOB]
126
+ )
127
+ if self.remove_completed_job_nodes and graph_has_machine_nodes:
128
+ self._remove_completed_job_nodes()
129
+
130
+ def _remove_completed_machine_nodes(self):
131
+ """Removes the completed machine nodes from the graph if they are
132
+ not already removed."""
133
+
134
+ for machine_id, is_completed in enumerate(
135
+ self.is_completed_observer.features[FeatureType.MACHINES].flatten()
136
+ ):
137
+ if is_completed == 1 and not self.job_shop_graph.is_removed(
138
+ machine_node := self.job_shop_graph.get_machine_node(
139
+ machine_id
140
+ )
141
+ ):
142
+ self.job_shop_graph.remove_node(machine_node.node_id)
143
+
144
+ def _remove_completed_job_nodes(self):
145
+ """Removes the completed job nodes from the graph if they are not
146
+ already removed."""
147
+
148
+ for job_id, is_completed in enumerate(
149
+ self.is_completed_observer.features[FeatureType.JOBS]
150
+ ):
151
+ if is_completed == 1 and not self.job_shop_graph.is_removed(
152
+ job_node := self.job_shop_graph.get_job_node(job_id)
153
+ ):
154
+ self.job_shop_graph.remove_node(job_node.node_id)
@@ -0,0 +1,25 @@
1
+ """Contains grah updater functions to update """
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from job_shop_lib import Operation
6
+ from job_shop_lib.graphs import JobShopGraph
7
+
8
+
9
+ def remove_completed_operations(
10
+ job_shop_graph: JobShopGraph,
11
+ completed_operations: Iterable[Operation],
12
+ ) -> None:
13
+ """Removes the operation node of the scheduled operation from the graph.
14
+
15
+ Args:
16
+ graph:
17
+ The job shop graph to update.
18
+ dispatcher:
19
+ The dispatcher instance.
20
+ """
21
+ for operation in completed_operations:
22
+ node_id = operation.operation_id
23
+ if job_shop_graph.removed_nodes[node_id]:
24
+ continue
25
+ job_shop_graph.remove_node(node_id)
@@ -0,0 +1,41 @@
1
+ """Package for reinforcement learning components."""
2
+
3
+ from job_shop_lib.reinforcement_learning._types_and_constants import (
4
+ ObservationSpaceKey,
5
+ RenderConfig,
6
+ ObservationDict,
7
+ GanttChartWrapperConfig,
8
+ GifConfig,
9
+ VideoConfig,
10
+ )
11
+
12
+ from job_shop_lib.reinforcement_learning._reward_observers import (
13
+ RewardObserver,
14
+ MakespanReward,
15
+ IdleTimeReward,
16
+ )
17
+
18
+ from job_shop_lib.reinforcement_learning._utils import add_padding
19
+
20
+ from job_shop_lib.reinforcement_learning._single_job_shop_graph_env import (
21
+ SingleJobShopGraphEnv,
22
+ )
23
+ from job_shop_lib.reinforcement_learning._multi_job_shop_graph_env import (
24
+ MultiJobShopGraphEnv,
25
+ )
26
+
27
+
28
+ __all__ = [
29
+ "ObservationSpaceKey",
30
+ "RewardObserver",
31
+ "MakespanReward",
32
+ "IdleTimeReward",
33
+ "GanttChartWrapperConfig",
34
+ "GifConfig",
35
+ "VideoConfig",
36
+ "SingleJobShopGraphEnv",
37
+ "RenderConfig",
38
+ "ObservationDict",
39
+ "add_padding",
40
+ "MultiJobShopGraphEnv",
41
+ ]