job-shop-lib 1.0.0a5__py3-none-any.whl → 1.0.0b1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. job_shop_lib/__init__.py +1 -1
  2. job_shop_lib/_job_shop_instance.py +34 -29
  3. job_shop_lib/_operation.py +4 -2
  4. job_shop_lib/_schedule.py +11 -11
  5. job_shop_lib/benchmarking/_load_benchmark.py +3 -3
  6. job_shop_lib/constraint_programming/_ortools_solver.py +6 -6
  7. job_shop_lib/dispatching/_dispatcher.py +19 -19
  8. job_shop_lib/dispatching/_dispatcher_observer_config.py +4 -4
  9. job_shop_lib/dispatching/_factories.py +4 -2
  10. job_shop_lib/dispatching/_history_observer.py +2 -1
  11. job_shop_lib/dispatching/_ready_operation_filters.py +19 -18
  12. job_shop_lib/dispatching/_unscheduled_operations_observer.py +4 -3
  13. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +7 -8
  14. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +3 -1
  15. job_shop_lib/dispatching/feature_observers/_factory.py +13 -14
  16. job_shop_lib/dispatching/feature_observers/_feature_observer.py +9 -8
  17. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +2 -1
  18. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +4 -2
  19. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +4 -2
  20. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +23 -15
  21. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -8
  22. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +4 -3
  23. job_shop_lib/dispatching/rules/_utils.py +9 -8
  24. job_shop_lib/generation/__init__.py +8 -0
  25. job_shop_lib/generation/_general_instance_generator.py +42 -64
  26. job_shop_lib/generation/_instance_generator.py +11 -7
  27. job_shop_lib/generation/_transformations.py +5 -4
  28. job_shop_lib/generation/_utils.py +124 -0
  29. job_shop_lib/graphs/__init__.py +7 -7
  30. job_shop_lib/graphs/{_build_agent_task_graph.py → _build_resource_task_graphs.py} +26 -24
  31. job_shop_lib/graphs/_job_shop_graph.py +17 -13
  32. job_shop_lib/graphs/_node.py +6 -4
  33. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +4 -2
  34. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +40 -20
  35. job_shop_lib/reinforcement_learning/_reward_observers.py +3 -1
  36. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +89 -22
  37. job_shop_lib/reinforcement_learning/_types_and_constants.py +1 -1
  38. job_shop_lib/reinforcement_learning/_utils.py +3 -3
  39. job_shop_lib/visualization/__init__.py +0 -60
  40. job_shop_lib/visualization/gantt/__init__.py +48 -0
  41. job_shop_lib/visualization/{_gantt_chart_creator.py → gantt/_gantt_chart_creator.py} +12 -12
  42. job_shop_lib/visualization/{_gantt_chart_video_and_gif_creation.py → gantt/_gantt_chart_video_and_gif_creation.py} +22 -22
  43. job_shop_lib/visualization/{_plot_gantt_chart.py → gantt/_plot_gantt_chart.py} +12 -13
  44. job_shop_lib/visualization/graphs/__init__.py +29 -0
  45. job_shop_lib/visualization/{_plot_disjunctive_graph.py → graphs/_plot_disjunctive_graph.py} +18 -16
  46. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  47. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/METADATA +17 -15
  48. job_shop_lib-1.0.0b1.dist-info/RECORD +69 -0
  49. job_shop_lib/visualization/_plot_agent_task_graph.py +0 -276
  50. job_shop_lib-1.0.0a5.dist-info/RECORD +0 -66
  51. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/LICENSE +0 -0
  52. {job_shop_lib-1.0.0a5.dist-info → job_shop_lib-1.0.0b1.dist-info}/WHEEL +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  from collections import defaultdict
4
4
  from collections.abc import Callable, Sequence
5
- from typing import Any
5
+ from typing import Any, Tuple, Dict, List, Optional, Type
6
6
  from copy import deepcopy
7
7
 
8
8
  import gymnasium as gym
@@ -16,7 +16,7 @@ from job_shop_lib.dispatching import (
16
16
  )
17
17
  from job_shop_lib.dispatching.feature_observers import FeatureObserverConfig
18
18
  from job_shop_lib.generation import InstanceGenerator
19
- from job_shop_lib.graphs import JobShopGraph, build_agent_task_graph
19
+ from job_shop_lib.graphs import JobShopGraph, build_resource_task_graph
20
20
  from job_shop_lib.graphs.graph_updaters import (
21
21
  GraphUpdater,
22
22
  ResidualGraphUpdater,
@@ -160,18 +160,18 @@ class MultiJobShopGraphEnv(gym.Env):
160
160
  feature_observer_configs: Sequence[FeatureObserverConfig],
161
161
  graph_initializer: Callable[
162
162
  [JobShopInstance], JobShopGraph
163
- ] = build_agent_task_graph,
163
+ ] = build_resource_task_graph,
164
164
  graph_updater_config: DispatcherObserverConfig[
165
- type[GraphUpdater]
165
+ Type[GraphUpdater]
166
166
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
167
167
  ready_operations_filter: Callable[
168
- [Dispatcher, list[Operation]], list[Operation]
168
+ [Dispatcher, List[Operation]], List[Operation]
169
169
  ] = filter_dominated_operations,
170
170
  reward_function_config: DispatcherObserverConfig[
171
- type[RewardObserver]
171
+ Type[RewardObserver]
172
172
  ] = DispatcherObserverConfig(class_type=MakespanReward),
173
- render_mode: str | None = None,
174
- render_config: RenderConfig | None = None,
173
+ render_mode: Optional[str] = None,
174
+ render_config: Optional[RenderConfig] = None,
175
175
  use_padding: bool = True,
176
176
  ) -> None:
177
177
  super().__init__()
@@ -226,7 +226,7 @@ class MultiJobShopGraphEnv(gym.Env):
226
226
  @property
227
227
  def ready_operations_filter(
228
228
  self,
229
- ) -> Callable[[Dispatcher, list[Operation]], list[Operation]] | None:
229
+ ) -> Optional[Callable[[Dispatcher, List[Operation]], List[Operation]]]:
230
230
  """Returns the current ready operations filter."""
231
231
  return (
232
232
  self.single_job_shop_graph_env.dispatcher.ready_operations_filter
@@ -236,7 +236,7 @@ class MultiJobShopGraphEnv(gym.Env):
236
236
  def ready_operations_filter(
237
237
  self,
238
238
  pruning_function: Callable[
239
- [Dispatcher, list[Operation]], list[Operation]
239
+ [Dispatcher, List[Operation]], List[Operation]
240
240
  ],
241
241
  ) -> None:
242
242
  """Sets the ready operations filter."""
@@ -267,9 +267,9 @@ class MultiJobShopGraphEnv(gym.Env):
267
267
  def reset(
268
268
  self,
269
269
  *,
270
- seed: int | None = None,
271
- options: dict[str, Any] | None = None,
272
- ) -> tuple[ObservationDict, dict]:
270
+ seed: Optional[int] = None,
271
+ options: Dict[str, Any] | None = None,
272
+ ) -> Tuple[ObservationDict, Dict[str, Any]]:
273
273
  """Resets the environment and returns the initial observation.
274
274
 
275
275
  Args:
@@ -303,8 +303,8 @@ class MultiJobShopGraphEnv(gym.Env):
303
303
  return obs, info
304
304
 
305
305
  def step(
306
- self, action: tuple[int, int]
307
- ) -> tuple[ObservationDict, float, bool, bool, dict]:
306
+ self, action: Tuple[int, int]
307
+ ) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
308
308
  """Takes a step in the environment.
309
309
 
310
310
  Args:
@@ -322,9 +322,10 @@ class MultiJobShopGraphEnv(gym.Env):
322
322
  - Whether the environment is done.
323
323
  - Whether the episode was truncated (always False).
324
324
  - A dictionary with additional information. The dictionary
325
- contains the following keys: ``"feature_names"``, The names of
326
- the features in the observation; ``"available_operations"``, the
327
- operations that are ready to be scheduled.
325
+ contains the following keys: "feature_names", the names of the
326
+ features in the observation; and "available_operations_with_ids",
327
+ a list of available actions in the form of (operation_id,
328
+ machine_id, job_id).
328
329
  """
329
330
  obs, reward, done, truncated, info = (
330
331
  self.single_job_shop_graph_env.step(action)
@@ -355,7 +356,7 @@ class MultiJobShopGraphEnv(gym.Env):
355
356
  input_shape: (num_machines, num_features)
356
357
  output_shape: (max_num_machines, num_features) (padded with -1)
357
358
  """
358
- padding_value: dict[str, float | bool] = defaultdict(lambda: -1)
359
+ padding_value: Dict[str, float | bool] = defaultdict(lambda: -1)
359
360
  padding_value[ObservationSpaceKey.REMOVED_NODES.value] = True
360
361
  for key, value in observation.items():
361
362
  if not isinstance(value, np.ndarray): # Make mypy happy
@@ -368,7 +369,7 @@ class MultiJobShopGraphEnv(gym.Env):
368
369
  )
369
370
  return observation
370
371
 
371
- def _get_output_shape(self, key: str) -> tuple[int, ...]:
372
+ def _get_output_shape(self, key: str) -> Tuple[int, ...]:
372
373
  """Returns the output shape of the observation space key."""
373
374
  output_shape = self.observation_space[key].shape
374
375
  assert output_shape is not None # Make mypy happy
@@ -376,3 +377,22 @@ class MultiJobShopGraphEnv(gym.Env):
376
377
 
377
378
  def render(self) -> None:
378
379
  self.single_job_shop_graph_env.render()
380
+
381
+ def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
382
+ """Returns a list of available actions in the form of
383
+ (operation_id, machine_id, job_id)."""
384
+ return self.single_job_shop_graph_env.get_available_actions_with_ids()
385
+
386
+ def validate_action(self, action: Tuple[int, int]) -> None:
387
+ """Validates the action.
388
+
389
+ Args:
390
+ action:
391
+ The action to validate. The action is a tuple of two integers
392
+ (job_id, machine_id): the job ID and the machine ID in which
393
+ to schedule the operation.
394
+
395
+ Raises:
396
+ ValidationError: If the action is invalid.
397
+ """
398
+ self.single_job_shop_graph_env.validate_action(action)
@@ -1,6 +1,8 @@
1
1
  """Rewards functions are defined as `DispatcherObervers` and are used to
2
2
  calculate the reward for a given state."""
3
3
 
4
+ from typing import List
5
+
4
6
  from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
5
7
  from job_shop_lib import ScheduledOperation
6
8
 
@@ -18,7 +20,7 @@ class RewardObserver(DispatcherObserver):
18
20
  self, dispatcher: Dispatcher, *, subscribe: bool = True
19
21
  ) -> None:
20
22
  super().__init__(dispatcher, subscribe=subscribe)
21
- self.rewards: list[float] = []
23
+ self.rewards: List[float] = []
22
24
 
23
25
  @property
24
26
  def last_reward(self) -> float:
@@ -2,7 +2,7 @@
2
2
 
3
3
  from copy import deepcopy
4
4
  from collections.abc import Callable, Sequence
5
- from typing import Any
5
+ from typing import Any, Dict, Tuple, List, Optional, Type
6
6
 
7
7
  import matplotlib.pyplot as plt
8
8
  import gymnasium as gym
@@ -15,6 +15,7 @@ from job_shop_lib.graphs.graph_updaters import (
15
15
  GraphUpdater,
16
16
  ResidualGraphUpdater,
17
17
  )
18
+ from job_shop_lib.exceptions import ValidationError
18
19
  from job_shop_lib.dispatching import (
19
20
  Dispatcher,
20
21
  filter_dominated_operations,
@@ -24,7 +25,7 @@ from job_shop_lib.dispatching.feature_observers import (
24
25
  FeatureObserverConfig,
25
26
  CompositeFeatureObserver,
26
27
  )
27
- from job_shop_lib.visualization import GanttChartCreator
28
+ from job_shop_lib.visualization.gantt import GanttChartCreator
28
29
  from job_shop_lib.reinforcement_learning import (
29
30
  RewardObserver,
30
31
  MakespanReward,
@@ -138,16 +139,16 @@ class SingleJobShopGraphEnv(gym.Env):
138
139
  job_shop_graph: JobShopGraph,
139
140
  feature_observer_configs: Sequence[FeatureObserverConfig],
140
141
  reward_function_config: DispatcherObserverConfig[
141
- type[RewardObserver]
142
+ Type[RewardObserver]
142
143
  ] = DispatcherObserverConfig(class_type=MakespanReward),
143
144
  graph_updater_config: DispatcherObserverConfig[
144
- type[GraphUpdater]
145
+ Type[GraphUpdater]
145
146
  ] = DispatcherObserverConfig(class_type=ResidualGraphUpdater),
146
- ready_operations_filter: (
147
- Callable[[Dispatcher, list[Operation]], list[Operation]] | None
148
- ) = filter_dominated_operations,
149
- render_mode: str | None = None,
150
- render_config: RenderConfig | None = None,
147
+ ready_operations_filter: Optional[
148
+ Callable[[Dispatcher, List[Operation]], List[Operation]]
149
+ ] = filter_dominated_operations,
150
+ render_mode: Optional[str] = None,
151
+ render_config: Optional[RenderConfig] = None,
151
152
  use_padding: bool = True,
152
153
  ) -> None:
153
154
  super().__init__()
@@ -195,10 +196,26 @@ class SingleJobShopGraphEnv(gym.Env):
195
196
  """Returns the job shop graph."""
196
197
  return self.graph_updater.job_shop_graph
197
198
 
199
+ def current_makespan(self) -> int:
200
+ """Returns current makespan of partial schedule."""
201
+ return self.dispatcher.schedule.makespan()
202
+
203
+ def machine_utilization(self) -> NDArray[np.float32]:
204
+ """Returns utilization percentage for each machine."""
205
+ total_time = max(1, self.current_makespan()) # Avoid division by zero
206
+ machine_busy_time = np.zeros(self.instance.num_machines)
207
+
208
+ for m_id, m_schedule in enumerate(self.dispatcher.schedule.schedule):
209
+ machine_busy_time[m_id] = sum(
210
+ op.operation.duration for op in m_schedule
211
+ )
212
+
213
+ return machine_busy_time / total_time
214
+
198
215
  def _get_observation_space(self) -> gym.spaces.Dict:
199
216
  """Returns the observation space dictionary."""
200
217
  num_edges = self.job_shop_graph.num_edges
201
- dict_space: dict[str, gym.Space] = {
218
+ dict_space: Dict[str, gym.Space] = {
202
219
  ObservationSpaceKey.REMOVED_NODES.value: gym.spaces.MultiBinary(
203
220
  len(self.job_shop_graph.nodes)
204
221
  ),
@@ -224,18 +241,23 @@ class SingleJobShopGraphEnv(gym.Env):
224
241
  def reset(
225
242
  self,
226
243
  *,
227
- seed: int | None = None,
228
- options: dict[str, Any] | None = None,
229
- ) -> tuple[ObservationDict, dict]:
244
+ seed: Optional[int] = None,
245
+ options: Optional[Dict[str, Any]] = None,
246
+ ) -> Tuple[ObservationDict, dict]:
230
247
  """Resets the environment."""
231
248
  super().reset(seed=seed, options=options)
232
249
  self.dispatcher.reset()
233
250
  obs = self.get_observation()
234
- return obs, {}
251
+ return obs, {
252
+ "feature_names": self.composite_observer.column_names,
253
+ "available_operations_with_ids": (
254
+ self.get_available_actions_with_ids()
255
+ ),
256
+ }
235
257
 
236
258
  def step(
237
- self, action: tuple[int, int]
238
- ) -> tuple[ObservationDict, float, bool, bool, dict[str, Any]]:
259
+ self, action: Tuple[int, int]
260
+ ) -> Tuple[ObservationDict, float, bool, bool, Dict[str, Any]]:
239
261
  """Takes a step in the environment.
240
262
 
241
263
  Args:
@@ -254,9 +276,9 @@ class SingleJobShopGraphEnv(gym.Env):
254
276
  - Whether the episode was truncated (always False).
255
277
  - A dictionary with additional information. The dictionary
256
278
  contains the following keys: "feature_names", the names of the
257
- features in the observation; "available_operations", the
258
- operations that are ready to be scheduled.
259
-
279
+ features in the observation; and "available_operations_with_ids",
280
+ a list of available actions in the form of (operation_id,
281
+ machine_id, job_id).
260
282
  """
261
283
  job_id, machine_id = action
262
284
  operation = self.dispatcher.next_operation(job_id)
@@ -269,9 +291,11 @@ class SingleJobShopGraphEnv(gym.Env):
269
291
  reward = self.reward_function.last_reward
270
292
  done = self.dispatcher.schedule.is_complete()
271
293
  truncated = False
272
- info: dict[str, Any] = {
294
+ info: Dict[str, Any] = {
273
295
  "feature_names": self.composite_observer.column_names,
274
- "available_operations": self.dispatcher.available_operations(),
296
+ "available_operations_with_ids": (
297
+ self.get_available_actions_with_ids()
298
+ ),
275
299
  }
276
300
  return obs, reward, done, truncated, info
277
301
 
@@ -322,6 +346,49 @@ class SingleJobShopGraphEnv(gym.Env):
322
346
  elif self.render_mode == "save_gif":
323
347
  self.gantt_chart_creator.create_gif()
324
348
 
349
+ def get_available_actions_with_ids(self) -> List[Tuple[int, int, int]]:
350
+ """Returns a list of available actions in the form of
351
+ (operation_id, machine_id, job_id)."""
352
+ available_operations = self.dispatcher.available_operations()
353
+ available_operations_with_ids = []
354
+ for operation in available_operations:
355
+ job_id = operation.job_id
356
+ operation_id = operation.operation_id
357
+ for machine_id in operation.machines:
358
+ available_operations_with_ids.append(
359
+ (operation_id, machine_id, job_id)
360
+ )
361
+ return available_operations_with_ids
362
+
363
+ def validate_action(self, action: Tuple[int, int]) -> None:
364
+ """Validates that the action is legal in the current state.
365
+
366
+ Args:
367
+ action:
368
+ The action to validate. The action is a tuple of two integers
369
+ (job_id, machine_id).
370
+
371
+ Raises:
372
+ ValidationError: If the action is invalid.
373
+ """
374
+ job_id, machine_id = action
375
+ if not 0 <= job_id < self.instance.num_jobs:
376
+ raise ValidationError(f"Invalid job_id {job_id}")
377
+
378
+ if not -1 <= machine_id < self.instance.num_machines:
379
+ raise ValidationError(f"Invalid machine_id {machine_id}")
380
+
381
+ # Check if job has operations left
382
+ job = self.instance.jobs[job_id]
383
+ if self.dispatcher.job_next_operation_index[job_id] >= len(job):
384
+ raise ValidationError(f"Job {job_id} has no operations left")
385
+
386
+ next_operation = self.dispatcher.next_operation(job_id)
387
+ if machine_id == -1 and len(next_operation.machines) > 1:
388
+ raise ValidationError(
389
+ f"Operation {next_operation} requires a machine_id"
390
+ )
391
+
325
392
 
326
393
  if __name__ == "__main__":
327
394
  from job_shop_lib.dispatching.feature_observers import (
@@ -333,7 +400,7 @@ if __name__ == "__main__":
333
400
 
334
401
  instance = load_benchmark_instance("ft06")
335
402
  job_shop_graph_ = build_disjunctive_graph(instance)
336
- feature_observer_configs_ = [
403
+ feature_observer_configs_: List[DispatcherObserverConfig] = [
337
404
  DispatcherObserverConfig(
338
405
  FeatureObserverType.IS_READY,
339
406
  kwargs={"feature_types": [FeatureType.JOBS]},
@@ -7,7 +7,7 @@ from typing import TypedDict
7
7
  import numpy as np
8
8
 
9
9
  from job_shop_lib.dispatching.feature_observers import FeatureType
10
- from job_shop_lib.visualization import (
10
+ from job_shop_lib.visualization.gantt import (
11
11
  PartialGanttChartPlotterConfig,
12
12
  GifConfig,
13
13
  VideoConfig,
@@ -1,6 +1,6 @@
1
1
  """Utility functions for reinforcement learning."""
2
2
 
3
- from typing import TypeVar, Any
3
+ from typing import TypeVar, Any, Tuple, Optional, Type
4
4
 
5
5
  import numpy as np
6
6
  from numpy.typing import NDArray
@@ -12,9 +12,9 @@ T = TypeVar("T", bound=np.number)
12
12
 
13
13
  def add_padding(
14
14
  array: NDArray[Any],
15
- output_shape: tuple[int, ...],
15
+ output_shape: Tuple[int, ...],
16
16
  padding_value: float = -1,
17
- dtype: type[T] | None = None,
17
+ dtype: Optional[Type[T]] = None,
18
18
  ) -> NDArray[T]:
19
19
  """Adds padding to the array.
20
20
 
@@ -1,60 +0,0 @@
1
- """Contains functions and classes for visualizing job shop scheduling problems.
2
-
3
- .. autosummary::
4
-
5
- plot_gantt_chart
6
- get_partial_gantt_chart_plotter
7
- PartialGanttChartPlotter
8
- create_gantt_chart_video
9
- create_gantt_chart_gif
10
- plot_disjunctive_graph
11
- plot_agent_task_graph
12
- GanttChartCreator
13
- GifConfig
14
- VideoConfig
15
-
16
- """
17
-
18
- from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
19
- from job_shop_lib.visualization._gantt_chart_video_and_gif_creation import (
20
- create_gantt_chart_gif,
21
- create_gantt_chart_video,
22
- create_gantt_chart_frames,
23
- get_partial_gantt_chart_plotter,
24
- create_video_from_frames,
25
- create_gif_from_frames,
26
- PartialGanttChartPlotter,
27
- )
28
- from job_shop_lib.visualization._plot_disjunctive_graph import (
29
- plot_disjunctive_graph,
30
- duration_labeler,
31
- )
32
- from job_shop_lib.visualization._plot_agent_task_graph import (
33
- plot_agent_task_graph,
34
- three_columns_layout,
35
- )
36
- from job_shop_lib.visualization._gantt_chart_creator import (
37
- GanttChartCreator,
38
- PartialGanttChartPlotterConfig,
39
- GifConfig,
40
- VideoConfig,
41
- )
42
-
43
- __all__ = [
44
- "plot_gantt_chart",
45
- "create_gantt_chart_video",
46
- "create_gantt_chart_gif",
47
- "create_gantt_chart_frames",
48
- "get_partial_gantt_chart_plotter",
49
- "create_gif_from_frames",
50
- "create_video_from_frames",
51
- "plot_disjunctive_graph",
52
- "plot_agent_task_graph",
53
- "three_columns_layout",
54
- "GanttChartCreator",
55
- "PartialGanttChartPlotterConfig",
56
- "GifConfig",
57
- "VideoConfig",
58
- "PartialGanttChartPlotter",
59
- "duration_labeler",
60
- ]
@@ -0,0 +1,48 @@
1
+ """Contains functions and classes for visualizing job shop scheduling problems.
2
+
3
+ .. autosummary::
4
+
5
+ plot_gantt_chart
6
+ get_partial_gantt_chart_plotter
7
+ PartialGanttChartPlotter
8
+ create_gantt_chart_video
9
+ create_gantt_chart_gif
10
+ GanttChartCreator
11
+ GifConfig
12
+ VideoConfig
13
+ PartialGanttChartPlotterConfig
14
+
15
+ """
16
+
17
+ from ._plot_gantt_chart import plot_gantt_chart
18
+ from ._gantt_chart_video_and_gif_creation import (
19
+ create_gantt_chart_gif,
20
+ create_gantt_chart_video,
21
+ create_gantt_chart_frames,
22
+ get_partial_gantt_chart_plotter,
23
+ create_video_from_frames,
24
+ create_gif_from_frames,
25
+ PartialGanttChartPlotter,
26
+ )
27
+
28
+ from ._gantt_chart_creator import (
29
+ GanttChartCreator,
30
+ PartialGanttChartPlotterConfig,
31
+ GifConfig,
32
+ VideoConfig,
33
+ )
34
+
35
+ __all__ = [
36
+ "plot_gantt_chart",
37
+ "create_gantt_chart_video",
38
+ "create_gantt_chart_gif",
39
+ "create_gantt_chart_frames",
40
+ "get_partial_gantt_chart_plotter",
41
+ "create_gif_from_frames",
42
+ "create_video_from_frames",
43
+ "GanttChartCreator",
44
+ "PartialGanttChartPlotterConfig",
45
+ "GifConfig",
46
+ "VideoConfig",
47
+ "PartialGanttChartPlotter",
48
+ ]
@@ -1,13 +1,13 @@
1
1
  """Home of the `GanttChartCreator` class and its configuration types."""
2
2
 
3
- from typing import TypedDict
3
+ from typing import TypedDict, Optional
4
4
  import matplotlib.pyplot as plt
5
5
 
6
6
  from job_shop_lib.dispatching import (
7
7
  Dispatcher,
8
8
  HistoryObserver,
9
9
  )
10
- from job_shop_lib.visualization import (
10
+ from job_shop_lib.visualization.gantt import (
11
11
  create_gantt_chart_video,
12
12
  get_partial_gantt_chart_plotter,
13
13
  create_gantt_chart_gif,
@@ -24,7 +24,7 @@ class PartialGanttChartPlotterConfig(TypedDict, total=False):
24
24
  - :func:`get_partial_gantt_chart_plotter`
25
25
  """
26
26
 
27
- title: str | None
27
+ title: Optional[str]
28
28
  """The title of the Gantt chart."""
29
29
 
30
30
  cmap: str
@@ -43,7 +43,7 @@ class GifConfig(TypedDict, total=False):
43
43
  :func:`create_gantt_chart_gif`
44
44
  """
45
45
 
46
- gif_path: str | None
46
+ gif_path: Optional[str]
47
47
  """The path to save the GIF. It must end with '.gif'."""
48
48
 
49
49
  fps: int
@@ -52,7 +52,7 @@ class GifConfig(TypedDict, total=False):
52
52
  remove_frames: bool
53
53
  """Whether to remove the frames after creating the GIF."""
54
54
 
55
- frames_dir: str | None
55
+ frames_dir: Optional[str]
56
56
  """The directory to store the frames."""
57
57
 
58
58
  plot_current_time: bool
@@ -68,7 +68,7 @@ class VideoConfig(TypedDict, total=False):
68
68
  :func:`create_gantt_chart_video`
69
69
  """
70
70
 
71
- video_path: str | None
71
+ video_path: Optional[str]
72
72
  """The path to save the video. It must end with a valid video extension
73
73
  (e.g., '.mp4')."""
74
74
 
@@ -78,7 +78,7 @@ class VideoConfig(TypedDict, total=False):
78
78
  remove_frames: bool
79
79
  """Whether to remove the frames after creating the video."""
80
80
 
81
- frames_dir: str | None
81
+ frames_dir: Optional[str]
82
82
  """The directory to store the frames."""
83
83
 
84
84
  plot_current_time: bool
@@ -164,11 +164,11 @@ class GanttChartCreator:
164
164
  def __init__(
165
165
  self,
166
166
  dispatcher: Dispatcher,
167
- partial_gantt_chart_plotter_config: (
168
- PartialGanttChartPlotterConfig | None
169
- ) = None,
170
- gif_config: GifConfig | None = None,
171
- video_config: VideoConfig | None = None,
167
+ partial_gantt_chart_plotter_config: Optional[
168
+ PartialGanttChartPlotterConfig
169
+ ] = None,
170
+ gif_config: Optional[GifConfig] = None,
171
+ video_config: Optional[VideoConfig] = None,
172
172
  ):
173
173
  if gif_config is None:
174
174
  gif_config = {}
@@ -3,7 +3,7 @@
3
3
  import os
4
4
  import pathlib
5
5
  import shutil
6
- from typing import Sequence, Protocol
6
+ from typing import Sequence, Protocol, Optional, List
7
7
 
8
8
  import imageio
9
9
  import matplotlib.pyplot as plt
@@ -22,7 +22,7 @@ from job_shop_lib.dispatching import (
22
22
  HistoryObserver,
23
23
  )
24
24
  from job_shop_lib.dispatching.rules import DispatchingRuleSolver
25
- from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
25
+ from job_shop_lib.visualization.gantt._plot_gantt_chart import plot_gantt_chart
26
26
 
27
27
 
28
28
  # This class serves as a more meaningful type hint than simply:
@@ -43,9 +43,9 @@ class PartialGanttChartPlotter(Protocol):
43
43
  def __call__(
44
44
  self,
45
45
  schedule: Schedule,
46
- makespan: int | None = None,
47
- available_operations: list[Operation] | None = None,
48
- current_time: int | None = None,
46
+ makespan: Optional[int] = None,
47
+ available_operations: Optional[List[Operation]] = None,
48
+ current_time: Optional[int] = None,
49
49
  ) -> Figure:
50
50
  """Plots a Gantt chart for an unfinished schedule.
51
51
 
@@ -65,7 +65,7 @@ class PartialGanttChartPlotter(Protocol):
65
65
 
66
66
 
67
67
  def get_partial_gantt_chart_plotter(
68
- title: str | None = None,
68
+ title: Optional[str] = None,
69
69
  cmap: str = "viridis",
70
70
  show_available_operations: bool = False,
71
71
  ) -> PartialGanttChartPlotter:
@@ -92,9 +92,9 @@ def get_partial_gantt_chart_plotter(
92
92
 
93
93
  def plot_function(
94
94
  schedule: Schedule,
95
- makespan: int | None = None,
96
- available_operations: list | None = None,
97
- current_time: int | None = None,
95
+ makespan: Optional[int] = None,
96
+ available_operations: Optional[List[Operation]] = None,
97
+ current_time: Optional[int] = None,
98
98
  ) -> Figure:
99
99
  fig, ax = plot_gantt_chart(
100
100
  schedule, title=title, cmap_name=cmap, xlim=makespan
@@ -133,14 +133,14 @@ def get_partial_gantt_chart_plotter(
133
133
  # pylint: disable=too-many-arguments
134
134
  def create_gantt_chart_gif(
135
135
  instance: JobShopInstance,
136
- gif_path: str | None = None,
137
- solver: DispatchingRuleSolver | None = None,
138
- plot_function: PartialGanttChartPlotter | None = None,
136
+ gif_path: Optional[str] = None,
137
+ solver: Optional[DispatchingRuleSolver] = None,
138
+ plot_function: Optional[PartialGanttChartPlotter] = None,
139
139
  fps: int = 1,
140
140
  remove_frames: bool = True,
141
- frames_dir: str | None = None,
141
+ frames_dir: Optional[str] = None,
142
142
  plot_current_time: bool = True,
143
- schedule_history: Sequence[ScheduledOperation] | None = None,
143
+ schedule_history: Optional[Sequence[ScheduledOperation]] = None,
144
144
  ) -> None:
145
145
  """Creates a GIF of the schedule being built.
146
146
 
@@ -202,14 +202,14 @@ def create_gantt_chart_gif(
202
202
  # pylint: disable=too-many-arguments
203
203
  def create_gantt_chart_video(
204
204
  instance: JobShopInstance,
205
- video_path: str | None = None,
206
- solver: DispatchingRuleSolver | None = None,
207
- plot_function: PartialGanttChartPlotter | None = None,
205
+ video_path: Optional[str] = None,
206
+ solver: Optional[DispatchingRuleSolver] = None,
207
+ plot_function: Optional[PartialGanttChartPlotter] = None,
208
208
  fps: int = 1,
209
209
  remove_frames: bool = True,
210
- frames_dir: str | None = None,
210
+ frames_dir: Optional[str] = None,
211
211
  plot_current_time: bool = True,
212
- schedule_history: Sequence[ScheduledOperation] | None = None,
212
+ schedule_history: Optional[Sequence[ScheduledOperation]] = None,
213
213
  ) -> None:
214
214
  """Creates a video of the schedule being built.
215
215
 
@@ -268,10 +268,10 @@ def create_gantt_chart_video(
268
268
  def create_gantt_chart_frames(
269
269
  frames_dir: str,
270
270
  instance: JobShopInstance,
271
- solver: DispatchingRuleSolver | None,
271
+ solver: Optional[DispatchingRuleSolver],
272
272
  plot_function: PartialGanttChartPlotter,
273
273
  plot_current_time: bool = True,
274
- schedule_history: Sequence[ScheduledOperation] | None = None,
274
+ schedule_history: Optional[Sequence[ScheduledOperation]] = None,
275
275
  ) -> None:
276
276
  """Creates frames of the Gantt chart for the schedule being built.
277
277
 
@@ -411,7 +411,7 @@ def resize_image_to_macro_block(
411
411
  return image
412
412
 
413
413
 
414
- def _load_images(frames_dir: str) -> list:
414
+ def _load_images(frames_dir: str) -> List:
415
415
  frames = [
416
416
  os.path.join(frames_dir, frame)
417
417
  for frame in sorted(os.listdir(frames_dir))