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
@@ -0,0 +1,108 @@
1
+ """Home of the `ResidualGraphUpdater` class."""
2
+
3
+ from job_shop_lib import ScheduledOperation
4
+ from job_shop_lib.graphs.graph_updaters import (
5
+ ResidualGraphUpdater,
6
+ )
7
+ from job_shop_lib.exceptions import ValidationError
8
+
9
+
10
+ class DisjunctiveGraphUpdater(ResidualGraphUpdater):
11
+ """Updates the graph based on the completed operations.
12
+
13
+ This observer updates the graph by removing the completed
14
+ operation, machine and job nodes from the graph. It subscribes to the
15
+ :class:`~job_shop_lib.dispatching.feature_observers.IsCompletedObserver`
16
+ to determine which operations, machines and jobs have been completed.
17
+
18
+ After an operation is dispatched, one of two disjunctive arcs that
19
+ connected it with the previous operation is dropped. Similarly, the
20
+ disjunctive arcs associated with the previous scheduled operation are
21
+ removed.
22
+
23
+ Attributes:
24
+ remove_completed_machine_nodes:
25
+ If ``True``, removes completed machine nodes from the graph.
26
+ remove_completed_job_nodes:
27
+ If ``True``, removes completed job nodes from the graph.
28
+
29
+ Args:
30
+ dispatcher:
31
+ The dispatcher instance to observe.
32
+ job_shop_graph:
33
+ The job shop graph to update.
34
+ subscribe:
35
+ If ``True``, automatically subscribes the observer to the
36
+ dispatcher. Defaults to ``True``.
37
+ remove_completed_machine_nodes:
38
+ If ``True``, removes completed machine nodes from the graph.
39
+ Defaults to ``True``.
40
+ remove_completed_job_nodes:
41
+ If ``True``, removes completed job nodes from the graph.
42
+ Defaults to ``True``.
43
+ """
44
+
45
+ def update(self, scheduled_operation: ScheduledOperation) -> None:
46
+ """Updates the disjunctive graph.
47
+
48
+ After an operation is dispatched, one of two arcs that connected it
49
+ with the previous operation is dropped. Similarly, the disjunctive
50
+ arcs associated with the previous scheduled operation are removed.
51
+
52
+ Args:
53
+ scheduled_operation:
54
+ The scheduled operation that was dispatched.
55
+ """
56
+ super().update(scheduled_operation)
57
+ machine_schedule = self.dispatcher.schedule.schedule[
58
+ scheduled_operation.machine_id
59
+ ]
60
+ if len(machine_schedule) <= 1:
61
+ return
62
+
63
+ previous_scheduled_operation = machine_schedule[-2]
64
+
65
+ # Remove the disjunctive arcs between the scheduled operation and the
66
+ # previous operation
67
+ scheduled_operation_node = self.job_shop_graph.nodes[
68
+ scheduled_operation.operation.operation_id
69
+ ]
70
+ if (
71
+ scheduled_operation_node.operation
72
+ is not scheduled_operation.operation
73
+ ):
74
+ raise ValidationError(
75
+ "Scheduled operation node does not match scheduled operation."
76
+ "Make sure that the operation nodes have been the first to be "
77
+ "added to the graph. This method assumes that the operation id"
78
+ " and node id are the same."
79
+ )
80
+ scheduled_id = scheduled_operation_node.node_id
81
+ assert scheduled_id == scheduled_operation.operation.operation_id
82
+ previous_id = previous_scheduled_operation.operation.operation_id
83
+ if self.job_shop_graph.is_removed(
84
+ previous_id
85
+ ) or self.job_shop_graph.is_removed(scheduled_id):
86
+ return
87
+ self.job_shop_graph.graph.remove_edge(scheduled_id, previous_id)
88
+
89
+ # Now, remove all the disjunctive edges between the previous scheduled
90
+ # operation and the other operations in the machine schedule
91
+ operations_with_same_machine = (
92
+ self.dispatcher.instance.operations_by_machine[
93
+ scheduled_operation.machine_id
94
+ ]
95
+ )
96
+ already_scheduled_operations = {
97
+ scheduled_op.operation.operation_id
98
+ for scheduled_op in machine_schedule
99
+ }
100
+ for operation in operations_with_same_machine:
101
+ if operation.operation_id in already_scheduled_operations:
102
+ continue
103
+ self.job_shop_graph.graph.remove_edge(
104
+ previous_id, operation.operation_id
105
+ )
106
+ self.job_shop_graph.graph.remove_edge(
107
+ operation.operation_id, previous_id
108
+ )
@@ -0,0 +1,57 @@
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
+ Args:
28
+ dispatcher:
29
+ The dispatcher instance to observe.
30
+ job_shop_graph:
31
+ The job shop graph to update.
32
+ subscribe:
33
+ Whether to subscribe to the dispatcher. If ``True``, the
34
+ observer will subscribe to the dispatcher when it is
35
+ initialized. If ``False``, the observer will not subscribe
36
+ to the dispatcher.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ dispatcher: Dispatcher,
42
+ job_shop_graph: JobShopGraph,
43
+ *,
44
+ subscribe: bool = True,
45
+ ):
46
+ super().__init__(dispatcher, subscribe=subscribe)
47
+ self.initial_job_shop_graph = deepcopy(job_shop_graph)
48
+ self.job_shop_graph = job_shop_graph
49
+
50
+ def reset(self) -> None:
51
+ """Resets the job shop graph."""
52
+ self.job_shop_graph = deepcopy(self.initial_job_shop_graph)
53
+
54
+ @abstractmethod
55
+ def update(self, scheduled_operation: ScheduledOperation) -> None:
56
+ """Updates the job shop graph after an operation has been
57
+ dispatched."""
@@ -0,0 +1,155 @@
1
+ """Home of the `ResidualGraphUpdater` class."""
2
+
3
+ from typing import Optional, List
4
+
5
+ from job_shop_lib import ScheduledOperation
6
+ from job_shop_lib.exceptions import UninitializedAttributeError
7
+ from job_shop_lib.graphs import NodeType, JobShopGraph
8
+ from job_shop_lib.graphs.graph_updaters import (
9
+ GraphUpdater,
10
+ remove_completed_operations,
11
+ )
12
+ from job_shop_lib.dispatching import Dispatcher
13
+ from job_shop_lib.dispatching.feature_observers import (
14
+ IsCompletedObserver,
15
+ FeatureType,
16
+ )
17
+ from job_shop_lib.dispatching import DispatcherObserver
18
+
19
+
20
+ class ResidualGraphUpdater(GraphUpdater):
21
+ """Updates the residual graph based on the completed operations.
22
+
23
+ This observer updates the residual graph by removing the completed
24
+ operation, machine and job nodes from the graph. It subscribes to the
25
+ :class:`~job_shop_lib.dispatching.feature_observers.IsCompletedObserver`
26
+ to determine which operations, machines and jobs have been completed.
27
+
28
+ Attributes:
29
+ remove_completed_machine_nodes:
30
+ If ``True``, removes completed machine nodes from the graph.
31
+ remove_completed_job_nodes:
32
+ If ``True``, removes completed job nodes from the graph.
33
+
34
+ Args:
35
+ dispatcher:
36
+ The dispatcher instance to observe.
37
+ job_shop_graph:
38
+ The job shop graph to update.
39
+ subscribe:
40
+ If ``True``, automatically subscribes the observer to the
41
+ dispatcher. Defaults to ``True``.
42
+ remove_completed_machine_nodes:
43
+ If ``True``, removes completed machine nodes from the graph.
44
+ Defaults to ``True``.
45
+ remove_completed_job_nodes:
46
+ If ``True``, removes completed job nodes from the graph.
47
+ Defaults to ``True``.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ dispatcher: Dispatcher,
53
+ job_shop_graph: JobShopGraph,
54
+ *,
55
+ subscribe: bool = True,
56
+ remove_completed_machine_nodes: bool = True,
57
+ remove_completed_job_nodes: bool = True,
58
+ ):
59
+ self._is_completed_observer: Optional[IsCompletedObserver] = None
60
+ self.remove_completed_job_nodes = remove_completed_job_nodes
61
+ self.remove_completed_machine_nodes = remove_completed_machine_nodes
62
+ self._initialize_is_completed_observer_attribute(dispatcher)
63
+
64
+ # It is important to initialize the `IsCompletedObserver` before
65
+ # calling the parent class constructor to ensure the observer is
66
+ # updated before the `update` method of this class is called.
67
+ super().__init__(
68
+ dispatcher,
69
+ job_shop_graph,
70
+ subscribe=subscribe,
71
+ )
72
+
73
+ def _initialize_is_completed_observer_attribute(
74
+ self, dispatcher: Dispatcher
75
+ ):
76
+ def has_all_features(observer: DispatcherObserver) -> bool:
77
+ if not isinstance(observer, IsCompletedObserver):
78
+ return False # Make the type checker happy.
79
+
80
+ for feature_type in feature_types:
81
+ if feature_type not in observer.features.keys():
82
+ return False
83
+ return True
84
+
85
+ feature_types: List[FeatureType] = []
86
+ if self.remove_completed_machine_nodes:
87
+ feature_types.append(FeatureType.MACHINES)
88
+ if self.remove_completed_job_nodes:
89
+ feature_types.append(FeatureType.JOBS)
90
+ if feature_types:
91
+ self._is_completed_observer = dispatcher.create_or_get_observer(
92
+ IsCompletedObserver,
93
+ condition=has_all_features,
94
+ feature_types=feature_types,
95
+ )
96
+
97
+ @property
98
+ def is_completed_observer(self) -> IsCompletedObserver:
99
+ """Returns the :class:`~job_shop_lib.dispatching.feature_observers.
100
+ IsCompletedObserver` instance."""
101
+
102
+ if self._is_completed_observer is None:
103
+ raise UninitializedAttributeError(
104
+ "The `is_completed_observer` attribute has not been "
105
+ "initialized. Set `remove_completed_machine_nodes` or "
106
+ "remove_completed_job_nodes` to True when initializing the "
107
+ "ResidualGraphUpdater."
108
+ )
109
+ return self._is_completed_observer
110
+
111
+ def update(self, scheduled_operation: ScheduledOperation) -> None:
112
+ """Updates the residual graph based on the completed operations."""
113
+ remove_completed_operations(
114
+ self.job_shop_graph,
115
+ completed_operations=(
116
+ op.operation for op in self.dispatcher.completed_operations()
117
+ ),
118
+ )
119
+ graph_has_machine_nodes = bool(
120
+ self.job_shop_graph.nodes_by_type[NodeType.MACHINE]
121
+ )
122
+ if self.remove_completed_machine_nodes and graph_has_machine_nodes:
123
+ self._remove_completed_machine_nodes()
124
+
125
+ graph_has_machine_nodes = bool(
126
+ self.job_shop_graph.nodes_by_type[NodeType.JOB]
127
+ )
128
+ if self.remove_completed_job_nodes and graph_has_machine_nodes:
129
+ self._remove_completed_job_nodes()
130
+
131
+ def _remove_completed_machine_nodes(self):
132
+ """Removes the completed machine nodes from the graph if they are
133
+ not already removed."""
134
+
135
+ for machine_id, is_completed in enumerate(
136
+ self.is_completed_observer.features[FeatureType.MACHINES].flatten()
137
+ ):
138
+ if is_completed == 1 and not self.job_shop_graph.is_removed(
139
+ machine_node := self.job_shop_graph.get_machine_node(
140
+ machine_id
141
+ )
142
+ ):
143
+ self.job_shop_graph.remove_node(machine_node.node_id)
144
+
145
+ def _remove_completed_job_nodes(self):
146
+ """Removes the completed job nodes from the graph if they are not
147
+ already removed."""
148
+
149
+ for job_id, is_completed in enumerate(
150
+ self.is_completed_observer.features[FeatureType.JOBS]
151
+ ):
152
+ if is_completed == 1 and not self.job_shop_graph.is_removed(
153
+ job_node := self.job_shop_graph.get_job_node(job_id)
154
+ ):
155
+ self.job_shop_graph.remove_node(job_node.node_id)
@@ -0,0 +1,25 @@
1
+ """Contains utility functions for updating the job shop graph."""
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
+ job_shop_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)
job_shop_lib/py.typed ADDED
File without changes
@@ -0,0 +1,68 @@
1
+ """Contains reinforcement learning components.
2
+
3
+
4
+ .. autosummary::
5
+
6
+ SingleJobShopGraphEnv
7
+ MultiJobShopGraphEnv
8
+ ObservationDict
9
+ ObservationSpaceKey
10
+ RewardObserver
11
+ MakespanReward
12
+ IdleTimeReward
13
+ RenderConfig
14
+ add_padding
15
+ create_edge_type_dict
16
+ map_values
17
+ get_optimal_actions
18
+ ResourceTaskGraphObservation
19
+ ResourceTaskGraphObservationDict
20
+
21
+ """
22
+
23
+ from job_shop_lib.reinforcement_learning._types_and_constants import (
24
+ ObservationSpaceKey,
25
+ RenderConfig,
26
+ ObservationDict,
27
+ )
28
+
29
+ from job_shop_lib.reinforcement_learning._reward_observers import (
30
+ RewardObserver,
31
+ MakespanReward,
32
+ IdleTimeReward,
33
+ )
34
+
35
+ from job_shop_lib.reinforcement_learning._utils import (
36
+ add_padding,
37
+ create_edge_type_dict,
38
+ map_values,
39
+ get_optimal_actions,
40
+ )
41
+
42
+ from job_shop_lib.reinforcement_learning._single_job_shop_graph_env import (
43
+ SingleJobShopGraphEnv,
44
+ )
45
+ from job_shop_lib.reinforcement_learning._multi_job_shop_graph_env import (
46
+ MultiJobShopGraphEnv,
47
+ )
48
+ from ._resource_task_graph_observation import (
49
+ ResourceTaskGraphObservation, ResourceTaskGraphObservationDict
50
+ )
51
+
52
+
53
+ __all__ = [
54
+ "ObservationSpaceKey",
55
+ "RewardObserver",
56
+ "MakespanReward",
57
+ "IdleTimeReward",
58
+ "SingleJobShopGraphEnv",
59
+ "RenderConfig",
60
+ "ObservationDict",
61
+ "add_padding",
62
+ "MultiJobShopGraphEnv",
63
+ "create_edge_type_dict",
64
+ "ResourceTaskGraphObservation",
65
+ "map_values",
66
+ "ResourceTaskGraphObservationDict",
67
+ "get_optimal_actions",
68
+ ]