job-shop-lib 1.0.0a2__py3-none-any.whl → 1.0.0a4__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 (37) hide show
  1. job_shop_lib/_job_shop_instance.py +119 -55
  2. job_shop_lib/_operation.py +18 -7
  3. job_shop_lib/_schedule.py +13 -15
  4. job_shop_lib/_scheduled_operation.py +17 -18
  5. job_shop_lib/dispatching/__init__.py +4 -0
  6. job_shop_lib/dispatching/_dispatcher.py +36 -47
  7. job_shop_lib/dispatching/_dispatcher_observer_config.py +15 -2
  8. job_shop_lib/dispatching/_factories.py +10 -2
  9. job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
  10. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +0 -1
  11. job_shop_lib/dispatching/feature_observers/_factory.py +21 -18
  12. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +1 -0
  13. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +1 -1
  14. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +44 -25
  15. job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
  16. job_shop_lib/generation/_general_instance_generator.py +33 -34
  17. job_shop_lib/generation/_instance_generator.py +14 -17
  18. job_shop_lib/generation/_transformations.py +11 -8
  19. job_shop_lib/graphs/__init__.py +3 -0
  20. job_shop_lib/graphs/_build_disjunctive_graph.py +41 -3
  21. job_shop_lib/graphs/graph_updaters/_graph_updater.py +11 -13
  22. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +17 -20
  23. job_shop_lib/reinforcement_learning/__init__.py +16 -7
  24. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +69 -57
  25. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +43 -32
  26. job_shop_lib/reinforcement_learning/_types_and_constants.py +2 -2
  27. job_shop_lib/visualization/__init__.py +29 -10
  28. job_shop_lib/visualization/_gantt_chart_creator.py +122 -84
  29. job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +68 -37
  30. job_shop_lib/visualization/_plot_disjunctive_graph.py +382 -0
  31. job_shop_lib/visualization/{_gantt_chart.py → _plot_gantt_chart.py} +78 -14
  32. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/METADATA +15 -3
  33. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/RECORD +36 -36
  34. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/WHEEL +1 -1
  35. job_shop_lib/visualization/_disjunctive_graph.py +0 -210
  36. /job_shop_lib/visualization/{_agent_task_graph.py → _plot_agent_task_graph.py} +0 -0
  37. {job_shop_lib-1.0.0a2.dist-info → job_shop_lib-1.0.0a4.dist-info}/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
1
  """Home of the `GanttChartCreator` class and its configuration types."""
2
2
 
3
3
  from typing import TypedDict
4
-
5
4
  import matplotlib.pyplot as plt
6
5
 
7
6
  from job_shop_lib.dispatching import (
@@ -10,62 +9,92 @@ from job_shop_lib.dispatching import (
10
9
  )
11
10
  from job_shop_lib.visualization import (
12
11
  create_gantt_chart_video,
13
- plot_gantt_chart_wrapper,
14
- create_gif,
12
+ get_partial_gantt_chart_plotter,
13
+ create_gantt_chart_gif,
15
14
  )
16
15
 
17
16
 
18
- class GanttChartWrapperConfig(TypedDict, total=False):
19
- """Configuration for creating the plot function with the
20
- `plot_gantt_chart_wrapper` function."""
17
+ class PartialGanttChartPlotterConfig(TypedDict, total=False):
18
+ """A dictionary with the configuration for creating the
19
+ :class:`PartialGanttChartPlotter` function.
20
+
21
+ .. seealso::
22
+
23
+ - :class:`PartialGanttChartPlotter`
24
+ - :func:`get_partial_gantt_chart_plotter`
25
+ """
21
26
 
22
27
  title: str | None
28
+ """The title of the Gantt chart."""
29
+
23
30
  cmap: str
31
+ """The colormap to use in the Gantt chart."""
32
+
24
33
  show_available_operations: bool
34
+ """Whether to show available operations in each step."""
25
35
 
26
36
 
27
- # We can't use Required here because it's not available in Python 3.10
28
- class _GifConfigRequired(TypedDict):
29
- """Required configuration for creating the GIF."""
37
+ class GifConfig(TypedDict, total=False):
38
+ """A dictionary with the configuration for creating the GIF using the
39
+ :func:`create_gantt_chart_gif` function.
30
40
 
31
- gif_path: str | None
41
+ .. seealso::
32
42
 
43
+ :func:`create_gantt_chart_gif`
44
+ """
33
45
 
34
- class _GifConfigOptional(TypedDict, total=False):
35
- """Optional configuration for creating the GIF."""
46
+ gif_path: str | None
47
+ """The path to save the GIF. It must end with '.gif'."""
36
48
 
37
49
  fps: int
50
+ """The frames per second of the GIF. Defaults to 1."""
51
+
38
52
  remove_frames: bool
39
- frames_dir: str | None
40
- plot_current_time: bool
53
+ """Whether to remove the frames after creating the GIF."""
41
54
 
55
+ frames_dir: str | None
56
+ """The directory to store the frames."""
42
57
 
43
- class GifConfig(_GifConfigRequired, _GifConfigOptional):
44
- """Configuration for creating the GIF using the `create_gannt_chart_video`
45
- function."""
58
+ plot_current_time: bool
59
+ """Whether to plot the current time in the Gantt chart."""
46
60
 
47
61
 
48
62
  class VideoConfig(TypedDict, total=False):
49
63
  """Configuration for creating the video using the
50
- `create_gannt_chart_video` function."""
64
+ :func:`create_gantt_chart_video` function.
65
+
66
+ .. seealso::
67
+
68
+ :func:`create_gantt_chart_video`
69
+ """
51
70
 
52
71
  video_path: str | None
72
+ """The path to save the video. It must end with a valid video extension
73
+ (e.g., '.mp4')."""
74
+
53
75
  fps: int
76
+ """The frames per second of the video. Defaults to 1."""
77
+
54
78
  remove_frames: bool
79
+ """Whether to remove the frames after creating the video."""
80
+
55
81
  frames_dir: str | None
82
+ """The directory to store the frames."""
83
+
56
84
  plot_current_time: bool
85
+ """Whether to plot the current time in the Gantt chart."""
57
86
 
58
87
 
59
88
  class GanttChartCreator:
60
89
  """Facade class that centralizes the creation of Gantt charts, videos
61
90
  and GIFs.
62
91
 
63
- It leverages a `HistoryObserver` to keep track of the operations being
64
- scheduled and provides methods to plot the current state
65
- of the schedule as a Gantt chart and to create a GIF that shows the
66
- evolution of the schedule over time.
92
+ It leverages a :class:`HistoryObserver` to keep track of the operations
93
+ being scheduled and provides methods to plot the current state
94
+ of the schedule as a Gantt chart and to create a GIF or video that shows
95
+ the evolution of the schedule over time.
67
96
 
68
- It adds a new `HistoryObserver` to the dispatcher if it does
97
+ It adds a new :class:`HistoryObserver` to the dispatcher if it does
69
98
  not have one already. Otherwise, it uses the observer already present.
70
99
 
71
100
  Attributes:
@@ -81,72 +110,80 @@ class GanttChartCreator:
81
110
  Configuration for creating the video.
82
111
  plot_function:
83
112
  The function used to plot the Gantt chart when creating the GIF
84
- or video. Created using the `plot_gantt_chart_wrapper` function.
113
+ or video. Created using the :func:`get_partial_gantt_chart_plotter`
114
+ function.
115
+
116
+ Args:
117
+ dispatcher:
118
+ The :class:`Dispatcher` class that will be tracked using a
119
+ :class:`HistoryObserver`.
120
+ partial_gantt_chart_plotter_config:
121
+ Configuration for the Gantt chart wrapper function through the
122
+ :class:`PartialGanttChartPlotterConfig` class. Defaults to
123
+ ``None``. Valid keys are:
124
+
125
+ - title: The title of the Gantt chart.
126
+ - cmap: The name of the colormap to use.
127
+ - show_available_operations: Whether to show available
128
+ operations in each step.
129
+
130
+ If ``title`` or ``cmap`` are not provided here and
131
+ ``infer_gantt_chart_config`` is set to ``True``, the values from
132
+ ``gantt_chart_config`` will be used if they are present.
133
+
134
+ .. seealso::
135
+
136
+ - :class:`PartialGanttChartPlotterConfig`
137
+ - :func:`get_partial_gantt_chart_plotter`
138
+ - :class:`PartialGanttChartPlotter`
139
+
140
+ gif_config:
141
+ Configuration for creating the GIF. Defaults to ``None``.
142
+ Valid keys are:
143
+
144
+ - gif_path: The path to save the GIF.
145
+ - fps: The frames per second of the GIF.
146
+ - remove_frames: Whether to remove the frames after creating
147
+ the GIF.
148
+ - frames_dir: The directory to store the frames.
149
+ - plot_current_time: Whether to plot the current time in the
150
+ Gantt chart.
151
+ video_config:
152
+ Configuration for creating the video. Defaults to ``None``.
153
+ Valid keys are:
154
+
155
+ - video_path: The path to save the video.
156
+ - fps: The frames per second of the video.
157
+ - remove_frames: Whether to remove the frames after creating
158
+ the video.
159
+ - frames_dir: The directory to store the frames.
160
+ - plot_current_time: Whether to plot the current time in the
161
+ Gantt chart.
85
162
  """
86
163
 
87
164
  def __init__(
88
165
  self,
89
166
  dispatcher: Dispatcher,
90
- gantt_chart_wrapper_config: GanttChartWrapperConfig | None = None,
167
+ partial_gantt_chart_plotter_config: (
168
+ PartialGanttChartPlotterConfig | None
169
+ ) = None,
91
170
  gif_config: GifConfig | None = None,
92
171
  video_config: VideoConfig | None = None,
93
172
  ):
94
- """Initializes the GanttChartCreator with the given configurations
95
- and history observer.
96
-
97
- This class adds a new `HistoryObserver` to the dispatcher if it does
98
- not have one already. Otherwise, it uses the observer already present.
99
-
100
- Args:
101
- dispatcher:
102
- The `Dispatcher` class that will be tracked using a
103
- `HistoryObserver`.
104
- gantt_chart_wrapper_config:
105
- Configuration for the Gantt chart wrapper function. Valid keys
106
- are:
107
- - title: The title of the Gantt chart.
108
- - cmap: The name of the colormap to use.
109
- - show_available_operations: Whether to show available
110
- operations in each step.
111
-
112
- If `title` or `cmap` are not provided here and
113
- `infer_gantt_chart_config` is set to True, the values from
114
- `gantt_chart_config` will be used if they are present.
115
- gif_config:
116
- Configuration for creating the GIF. Defaults to None.
117
- Valid keys are:
118
- - gif_path: The path to save the GIF.
119
- - fps: The frames per second of the GIF.
120
- - remove_frames: Whether to remove the frames after creating
121
- the GIF.
122
- - frames_dir: The directory to store the frames.
123
- - plot_current_time: Whether to plot the current time in the
124
- Gantt chart.
125
- video_config:
126
- Configuration for creating the video. Defaults to None.
127
- Valid keys are:
128
- - video_path: The path to save the video.
129
- - fps: The frames per second of the video.
130
- - remove_frames: Whether to remove the frames after creating
131
- the video.
132
- - frames_dir: The directory to store the frames.
133
- - plot_current_time: Whether to plot the current time in the
134
- Gantt chart.
135
- """
136
173
  if gif_config is None:
137
- gif_config = {"gif_path": None}
138
- if gantt_chart_wrapper_config is None:
139
- gantt_chart_wrapper_config = {}
174
+ gif_config = {}
175
+ if partial_gantt_chart_plotter_config is None:
176
+ partial_gantt_chart_plotter_config = {}
140
177
  if video_config is None:
141
178
  video_config = {}
142
179
 
143
180
  self.gif_config = gif_config
144
- self.gannt_chart_wrapper_config = gantt_chart_wrapper_config
181
+ self.gannt_chart_wrapper_config = partial_gantt_chart_plotter_config
145
182
  self.video_config = video_config
146
183
  self.history_observer: HistoryObserver = (
147
184
  dispatcher.create_or_get_observer(HistoryObserver)
148
185
  )
149
- self.plot_function = plot_gantt_chart_wrapper(
186
+ self.partial_gantt_chart_plotter = get_partial_gantt_chart_plotter(
150
187
  **self.gannt_chart_wrapper_config
151
188
  )
152
189
 
@@ -169,37 +206,38 @@ class GanttChartCreator:
169
206
  """Plots the current Gantt chart of the schedule.
170
207
 
171
208
  Returns:
172
- tuple[plt.Figure, plt.Axes]:
173
- The figure and axes of the plotted Gantt chart.
209
+ The figure of the plotted Gantt chart.
174
210
  """
175
- return self.plot_function(
211
+ a = self.partial_gantt_chart_plotter(
176
212
  self.schedule,
177
213
  None,
178
- self.dispatcher.ready_operations(),
214
+ self.dispatcher.available_operations(),
179
215
  self.dispatcher.current_time(),
180
216
  )
217
+ return a
181
218
 
182
219
  def create_gif(self) -> None:
183
220
  """Creates a GIF of the schedule being built using the recorded
184
221
  history.
185
222
 
186
223
  This method uses the history of scheduled operations recorded by the
187
- `HistoryTracker` to create a GIF that shows the progression of the
188
- scheduling process.
224
+ :class:`HistoryTracker` to create a GIF that shows the progression of
225
+ the scheduling process.
189
226
 
190
227
  The GIF creation process involves:
228
+
191
229
  - Using the history of scheduled operations to generate frames for
192
230
  each step of the schedule.
193
231
  - Creating a GIF from these frames.
194
232
  - Optionally, removing the frames after the GIF is created.
195
233
 
196
234
  The configuration for the GIF creation can be customized through the
197
- `gif_config` attribute.
235
+ ``gif_config`` attribute.
198
236
  """
199
- create_gif(
237
+ create_gantt_chart_gif(
200
238
  instance=self.history_observer.dispatcher.instance,
201
239
  schedule_history=self.history_observer.history,
202
- plot_function=self.plot_function,
240
+ plot_function=self.partial_gantt_chart_plotter,
203
241
  **self.gif_config
204
242
  )
205
243
 
@@ -208,12 +246,12 @@ class GanttChartCreator:
208
246
  history.
209
247
 
210
248
  This method uses the history of scheduled operations recorded by the
211
- `HistoryTracker` to create a video that shows the progression of the
212
- scheduling process.
249
+ :class:`HistoryTracker` to create a video that shows the progression
250
+ of the scheduling process.
213
251
  """
214
252
  create_gantt_chart_video(
215
253
  instance=self.history_observer.dispatcher.instance,
216
254
  schedule_history=self.history_observer.history,
217
- plot_function=self.plot_function,
255
+ plot_function=self.partial_gantt_chart_plotter,
218
256
  **self.video_config
219
257
  )
@@ -3,8 +3,7 @@
3
3
  import os
4
4
  import pathlib
5
5
  import shutil
6
- from collections.abc import Callable
7
- from typing import Sequence
6
+ from typing import Sequence, Protocol
8
7
 
9
8
  import imageio
10
9
  import matplotlib.pyplot as plt
@@ -23,19 +22,53 @@ from job_shop_lib.dispatching import (
23
22
  HistoryObserver,
24
23
  )
25
24
  from job_shop_lib.dispatching.rules import DispatchingRuleSolver
26
- from job_shop_lib.visualization._gantt_chart import plot_gantt_chart
27
-
28
-
29
- PlotFunction = Callable[
30
- [Schedule, int | None, list[Operation] | None, int | None], Figure
31
- ]
32
-
25
+ from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
26
+
27
+
28
+ # This class serves as a more meaningful type hint than simply:
29
+ # PlotFunction = Callable[
30
+ # [Schedule, int | None, list[Operation] | None, int | None], Figure
31
+ # ]
32
+ # That's why it doesn't have more methods or attributes. It is a protocol
33
+ # for functions, not for classes.
34
+ # pylint: disable=too-few-public-methods
35
+ class PartialGanttChartPlotter(Protocol):
36
+ """A protocol for a function that plots an uncompleted Gantt chart
37
+ for a schedule.
38
+
39
+ This kind of functions are created using the
40
+ :func:`plot_gantt_chart_wrapper` function.
41
+ """
33
42
 
34
- def plot_gantt_chart_wrapper(
43
+ def __call__(
44
+ self,
45
+ schedule: Schedule,
46
+ makespan: int | None = None,
47
+ available_operations: list[Operation] | None = None,
48
+ current_time: int | None = None,
49
+ ) -> Figure:
50
+ """Plots a Gantt chart for an unfinished schedule.
51
+
52
+ Args:
53
+ schedule:
54
+ The schedule to plot.
55
+ makespan:
56
+ The makespan of the schedule if known. Can be used to fix
57
+ the x-axis limits.
58
+ available_operations:
59
+ A list of available operations. If ``None``,
60
+ the available operations are not shown.
61
+ current_time:
62
+ The current time in the schedule. If provided, a red
63
+ vertical line is plotted at this time.
64
+ """
65
+
66
+
67
+ def get_partial_gantt_chart_plotter(
35
68
  title: str | None = None,
36
69
  cmap: str = "viridis",
37
70
  show_available_operations: bool = False,
38
- ) -> PlotFunction:
71
+ ) -> PartialGanttChartPlotter:
39
72
  """Returns a function that plots a Gantt chart for an unfinished schedule.
40
73
 
41
74
  Args:
@@ -47,12 +80,13 @@ def plot_gantt_chart_wrapper(
47
80
  Returns:
48
81
  A function that plots a Gantt chart for a schedule. The function takes
49
82
  the following arguments:
83
+
50
84
  - schedule: The schedule to plot.
51
85
  - makespan: The makespan of the schedule.
52
86
  - available_operations: A list of available operations. If None,
53
- the available operations are not shown.
87
+ the available operations are not shown.
54
88
  - current_time: The current time in the schedule. If provided, a
55
- red vertical line is plotted at this time.
89
+ red vertical line is plotted at this time.
56
90
 
57
91
  """
58
92
 
@@ -97,33 +131,30 @@ def plot_gantt_chart_wrapper(
97
131
  # Most of the arguments are optional with default values. There is no way to
98
132
  # reduce the number of arguments without losing functionality.
99
133
  # pylint: disable=too-many-arguments
100
- def create_gif(
101
- gif_path: str | None,
134
+ def create_gantt_chart_gif(
102
135
  instance: JobShopInstance,
136
+ gif_path: str | None = None,
103
137
  solver: DispatchingRuleSolver | None = None,
104
- plot_function: PlotFunction | None = None,
138
+ plot_function: PartialGanttChartPlotter | None = None,
105
139
  fps: int = 1,
106
140
  remove_frames: bool = True,
107
141
  frames_dir: str | None = None,
108
142
  plot_current_time: bool = True,
109
143
  schedule_history: Sequence[ScheduledOperation] | None = None,
110
144
  ) -> None:
111
- """Creates a GIF of the schedule being built by the given solver.
112
-
113
- Deprecated since version 0.6.0: Use `create_gif_or_video` instead.
145
+ """Creates a GIF of the schedule being built.
114
146
 
115
147
  Args:
116
- gif_path:
117
- The path to save the GIF file. It should end with ".gif". If not
118
- provided, the name of the instance is used. It will be made an
119
- optional argument in version 1.0.0.
120
148
  instance:
121
149
  The instance of the job shop problem to be scheduled.
150
+ gif_path:
151
+ The path to save the GIF file. It should end with ".gif". If not
152
+ provided, the name of the instance is used.
122
153
  solver:
123
154
  The dispatching rule solver to use. If not provided, the history
124
155
  of scheduled operations should be provided.
125
156
  plot_function:
126
- A function that plots a Gantt chart for a schedule. It
157
+ A :class:`PlotFunction` that plots a Gantt chart for a schedule. It
127
158
  should take a `Schedule` object and the makespan of the schedule as
128
159
  input and return a `Figure` object. If not provided, a default
129
160
  function is used.
@@ -133,7 +164,7 @@ def create_gif(
133
164
  Whether to remove the frames after creating the GIF.
134
165
  frames_dir:
135
166
  The directory to save the frames in. If not provided,
136
- `gif_path.replace(".gif", "") + "_frames"` is used.
167
+ ``gif_path.replace(".gif", "") + "_frames"`` is used.
137
168
  plot_current_time:
138
169
  Whether to plot a vertical line at the current time.
139
170
  schedule_history:
@@ -144,7 +175,7 @@ def create_gif(
144
175
  gif_path = f"{instance.name}_gantt_chart.gif"
145
176
 
146
177
  if plot_function is None:
147
- plot_function = plot_gantt_chart_wrapper()
178
+ plot_function = get_partial_gantt_chart_plotter()
148
179
 
149
180
  if frames_dir is None:
150
181
  # Use the name of the GIF file as the directory name
@@ -173,14 +204,14 @@ def create_gantt_chart_video(
173
204
  instance: JobShopInstance,
174
205
  video_path: str | None = None,
175
206
  solver: DispatchingRuleSolver | None = None,
176
- plot_function: PlotFunction | None = None,
207
+ plot_function: PartialGanttChartPlotter | None = None,
177
208
  fps: int = 1,
178
209
  remove_frames: bool = True,
179
210
  frames_dir: str | None = None,
180
211
  plot_current_time: bool = True,
181
212
  schedule_history: Sequence[ScheduledOperation] | None = None,
182
213
  ) -> None:
183
- """Creates a GIF of the schedule being built by the given solver.
214
+ """Creates a video of the schedule being built.
184
215
 
185
216
  Args:
186
217
  instance:
@@ -192,16 +223,16 @@ def create_gantt_chart_video(
192
223
  of scheduled operations should be provided.
193
224
  plot_function:
194
225
  A function that plots a Gantt chart for a schedule. It
195
- should take a `Schedule` object and the makespan of the schedule as
196
- input and return a `Figure` object. If not provided, a default
197
- function is used.
226
+ should take a :class:`Schedule` object and the makespan of the
227
+ schedule as input and return a ``Figure`` object. If not provided,
228
+ a default function is used.
198
229
  fps:
199
- The number of frames per second in the GIF.
230
+ The number of frames per second in the video.
200
231
  remove_frames:
201
- Whether to remove the frames after creating the GIF.
232
+ Whether to remove the frames after creating the video.
202
233
  frames_dir:
203
234
  The directory to save the frames in. If not provided,
204
- `name_without_the_extension` + "_frames"` is used.
235
+ ``name_without_the_extension + "_frames"`` is used.
205
236
  plot_current_time:
206
237
  Whether to plot a vertical line at the current time.
207
238
  schedule_history:
@@ -212,7 +243,7 @@ def create_gantt_chart_video(
212
243
  video_path = f"{instance.name}_gantt_chart.mp4"
213
244
 
214
245
  if plot_function is None:
215
- plot_function = plot_gantt_chart_wrapper()
246
+ plot_function = get_partial_gantt_chart_plotter()
216
247
 
217
248
  if frames_dir is None:
218
249
  extension = video_path.split(".")[-1]
@@ -238,7 +269,7 @@ def create_gantt_chart_frames(
238
269
  frames_dir: str,
239
270
  instance: JobShopInstance,
240
271
  solver: DispatchingRuleSolver | None,
241
- plot_function: PlotFunction,
272
+ plot_function: PartialGanttChartPlotter,
242
273
  plot_current_time: bool = True,
243
274
  schedule_history: Sequence[ScheduledOperation] | None = None,
244
275
  ) -> None:
@@ -295,7 +326,7 @@ def create_gantt_chart_frames(
295
326
  fig = plot_function(
296
327
  dispatcher.schedule,
297
328
  makespan,
298
- dispatcher.ready_operations(),
329
+ dispatcher.available_operations(),
299
330
  current_time,
300
331
  )
301
332
  _save_frame(fig, frames_dir, i)