rtc-tools-interface 0.7.0b3__tar.gz → 0.7.1__tar.gz

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 (48) hide show
  1. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/PKG-INFO +1 -1
  2. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/PKG-INFO +1 -1
  3. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/SOURCES.txt +6 -0
  4. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/_version.py +3 -3
  5. rtc-tools-interface-0.7.1/rtctools_interface/plotting/plot_tools.py +306 -0
  6. rtc-tools-interface-0.7.1/rtctools_interface/plotting/subplot_classes.py +304 -0
  7. rtc-tools-interface-0.7.1/rtctools_interface/simulation/base_simulation_problem.py +31 -0
  8. rtc-tools-interface-0.7.1/rtctools_interface/simulation/plot_mixin.py +27 -0
  9. rtc-tools-interface-0.7.1/tests/simulation/__init__.py +0 -0
  10. rtc-tools-interface-0.7.1/tests/utils/__init__.py +0 -0
  11. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/COPYING.LESSER +0 -0
  12. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/README.md +0 -0
  13. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/dependency_links.txt +0 -0
  14. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/requires.txt +0 -0
  15. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/top_level.txt +0 -0
  16. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/__init__.py +0 -0
  17. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/__init__.py +0 -0
  18. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/base_goal.py +0 -0
  19. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/base_optimization_problem.py +0 -0
  20. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_generator_mixin.py +0 -0
  21. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_performance_metrics.py +0 -0
  22. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_table_schema.py +0 -0
  23. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/helpers/__init__.py +0 -0
  24. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/helpers/statistics_mixin.py +0 -0
  25. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/plot_goals_mixin.py +0 -0
  26. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/plot_mixin.py +0 -0
  27. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/read_goals.py +0 -0
  28. {rtc-tools-interface-0.7.0b3/rtctools_interface/utils → rtc-tools-interface-0.7.1/rtctools_interface/plotting}/__init__.py +0 -0
  29. {rtc-tools-interface-0.7.0b3/tests → rtc-tools-interface-0.7.1/rtctools_interface/simulation}/__init__.py +0 -0
  30. {rtc-tools-interface-0.7.0b3/tests/optimization → rtc-tools-interface-0.7.1/rtctools_interface/utils}/__init__.py +0 -0
  31. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/plot_table_schema.py +0 -0
  32. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/read_goals_mixin.py +0 -0
  33. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/read_plot_table.py +0 -0
  34. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/results_collection.py +0 -0
  35. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/serialization.py +0 -0
  36. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/type_definitions.py +0 -0
  37. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/setup.cfg +0 -0
  38. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/setup.py +0 -0
  39. {rtc-tools-interface-0.7.0b3/tests/simulation → rtc-tools-interface-0.7.1/tests}/__init__.py +0 -0
  40. {rtc-tools-interface-0.7.0b3/tests/utils → rtc-tools-interface-0.7.1/tests/optimization}/__init__.py +0 -0
  41. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_base_optimization_problem.py +0 -0
  42. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_passing_goals_directly.py +0 -0
  43. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_plot_goals_mixin.py +0 -0
  44. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_read_goals.py +0 -0
  45. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/simulation/test_base_simulation_problem.py +0 -0
  46. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/simulation/test_plot_mixin.py +0 -0
  47. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/utils/get_test.py +0 -0
  48. {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rtc-tools-interface
3
- Version: 0.7.0b3
3
+ Version: 0.7.1
4
4
  Summary: Toolbox for user interfaces for RTC-Tools
5
5
  Home-page: UNKNOWN
6
6
  Author: Deltares
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rtc-tools-interface
3
- Version: 0.7.0b3
3
+ Version: 0.7.1
4
4
  Summary: Toolbox for user interfaces for RTC-Tools
5
5
  Home-page: UNKNOWN
6
6
  Author: Deltares
@@ -21,6 +21,12 @@ rtctools_interface/optimization/plot_mixin.py
21
21
  rtctools_interface/optimization/read_goals.py
22
22
  rtctools_interface/optimization/helpers/__init__.py
23
23
  rtctools_interface/optimization/helpers/statistics_mixin.py
24
+ rtctools_interface/plotting/__init__.py
25
+ rtctools_interface/plotting/plot_tools.py
26
+ rtctools_interface/plotting/subplot_classes.py
27
+ rtctools_interface/simulation/__init__.py
28
+ rtctools_interface/simulation/base_simulation_problem.py
29
+ rtctools_interface/simulation/plot_mixin.py
24
30
  rtctools_interface/utils/__init__.py
25
31
  rtctools_interface/utils/plot_table_schema.py
26
32
  rtctools_interface/utils/read_goals_mixin.py
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-11-28T16:07:57+0000",
11
+ "date": "2023-12-22T13:22:49+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "499d6e416027fec2a9b6dd1cc4d7249b30a41463",
15
- "version": "0.7.0b3"
14
+ "full-revisionid": "5d3d1b69fbc76a7bf4fd384003bfa601bb820486",
15
+ "version": "0.7.1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,306 @@
1
+ """Functions to create plots."""
2
+ from io import StringIO
3
+ import logging
4
+ import math
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any, Dict, Optional, Union
8
+ import matplotlib
9
+
10
+ import matplotlib.pyplot as plt
11
+ from plotly.subplots import make_subplots
12
+
13
+ from rtctools_interface.plotting.subplot_classes import (
14
+ COMPARISON_RUN_SUFFIX,
15
+ SubplotMatplotlib,
16
+ SubplotPlotly,
17
+ )
18
+ from rtctools_interface.utils.type_definitions import GoalConfig, IntermediateResult, PlotDataAndConfig
19
+
20
+ logger = logging.getLogger("rtctools")
21
+
22
+
23
+ def get_row_col_number(i_plot, n_rows, n_cols, row_first=False):
24
+ """Get row and col number given a plot number."""
25
+ if row_first:
26
+ i_r = math.ceil((i_plot + 1) / n_cols) - 1
27
+ i_c = i_plot - i_r * n_cols
28
+ else: # Count along column direction first.
29
+ i_c = math.ceil((i_plot + 1) / n_rows) - 1
30
+ i_r = i_plot - i_c * n_rows
31
+ return i_c, i_r
32
+
33
+
34
+ def get_subplot_axis(i_plot, n_rows, n_cols, axs):
35
+ """Determine the row and column index and returns the corresponding subplot object."""
36
+ i_c, i_r = get_row_col_number(i_plot, n_rows, n_cols)
37
+ subplot = axs[i_r, i_c]
38
+ return subplot
39
+
40
+
41
+ def get_file_write_path(output_folder: Union[str, Path], file_name="figure"):
42
+ """Get path to to file."""
43
+ new_output_folder = Path(output_folder) / "goal_figures"
44
+ os.makedirs(new_output_folder, exist_ok=True)
45
+ return os.path.join(new_output_folder, file_name)
46
+
47
+
48
+ def get_file_name(priority: int, final_result: bool):
49
+ """Get the file name for the figure to be written."""
50
+ if final_result:
51
+ file_name = "final_results"
52
+ else:
53
+ file_name = "after_priority_{}".format(priority)
54
+ return file_name
55
+
56
+
57
+ def save_fig_as_png(fig, output_folder, priority, final_result) -> matplotlib.figure.Figure:
58
+ """Save matplotlib figure to output folder."""
59
+ file_name = get_file_name(priority, final_result)
60
+ figure_path = get_file_write_path(output_folder, file_name)
61
+ fig.savefig(figure_path + ".png")
62
+ return fig
63
+
64
+
65
+ def save_fig_as_html(fig, output_folder, priority, final_result) -> dict:
66
+ """Save plotly figure as html"""
67
+ file_name = get_file_name(priority, final_result)
68
+ figure_path = get_file_write_path(output_folder, file_name)
69
+ fig.write_html(figure_path + ".html")
70
+ return fig
71
+
72
+
73
+ def get_goal(subplot_config, base_goals) -> Union[GoalConfig, None]:
74
+ """Find the goal belonging to a subplot. Only checks for goals as specified in the goal table."""
75
+ for goal in base_goals:
76
+ if goal.get("goal_id") == subplot_config.id:
77
+ return goal
78
+ return None
79
+
80
+
81
+ def save_fig_as_stringio(fig):
82
+ """Save figure as stringio in self."""
83
+ svg_data = StringIO()
84
+ fig.savefig(svg_data, format="svg")
85
+ return svg_data
86
+
87
+
88
+ def save_figure(fig, save_plot_to, output_folder, priority, final_result) -> Union[StringIO, matplotlib.figure.Figure]:
89
+ """Save figure."""
90
+ if save_plot_to == "image":
91
+ return save_fig_as_png(fig, output_folder, priority, final_result)
92
+ if save_plot_to == "stringio":
93
+ return save_fig_as_stringio(fig)
94
+ raise ValueError("Unsupported method of saving the plot results.")
95
+
96
+
97
+ def add_buttons_to_plotly(plotly_figure):
98
+ """Add buttons to the plotly figure.
99
+
100
+ Currently only a button to select whether previous results should also be visible"""
101
+
102
+ def comparison_run(name):
103
+ """Returns bool indicating whether the trace corresponds to a comparison run."""
104
+ return COMPARISON_RUN_SUFFIX in name
105
+
106
+ all_names = [True for _ in plotly_figure.data]
107
+ hide_comparison = [not comparison_run(trace.name) for trace in plotly_figure.data]
108
+ buttons = [
109
+ {
110
+ "label": "Show results from previous run",
111
+ "method": "update",
112
+ "args": [{"visible": all_names}],
113
+ },
114
+ {
115
+ "label": "Hide results from previous run",
116
+ "method": "update",
117
+ "args": [{"visible": hide_comparison}],
118
+ },
119
+ ]
120
+
121
+ # Add the buttons to the layout
122
+ plotly_figure.update_layout(
123
+ updatemenus=[
124
+ {
125
+ "buttons": buttons,
126
+ "x": 1.0,
127
+ "y": 1.0,
128
+ "xanchor": "left",
129
+ "yanchor": "bottom",
130
+ "pad": {"r": 2, "t": 2, "l": 20, "b": 10},
131
+ }
132
+ ]
133
+ )
134
+
135
+
136
+ def check_empty_plot_table(plot_config):
137
+ """Chech whether there are any elements in the plot table."""
138
+ if len(plot_config) == 0:
139
+ logger.info("Nothing to plot." + " Are there any goals that are active and described in the plot_table?")
140
+ return True
141
+ return False
142
+
143
+
144
+ def get_main_title(final_result: bool, result_dict):
145
+ """Generate main title."""
146
+ if final_result:
147
+ main_title = "Final results"
148
+ else:
149
+ main_title = "Results after optimizing until priority {}".format(result_dict["priority"])
150
+ return main_title
151
+
152
+
153
+ def create_matplotlib_figure(
154
+ result_dict, results_prev, current_run: PlotDataAndConfig, final_result=False
155
+ ) -> Union[StringIO, matplotlib.figure.Figure]:
156
+ # pylint: disable=too-many-locals
157
+ """Creates a figure with a subplot for each row in the plot_table."""
158
+ used_colors: list = []
159
+ results = result_dict["timeseries_data"]
160
+ plot_config = current_run["plot_options"]["plot_config"]
161
+ plot_max_rows = current_run["plot_options"]["plot_max_rows"]
162
+ if check_empty_plot_table(plot_config):
163
+ return None
164
+
165
+ # Initalize figure
166
+ n_cols = math.ceil(len(plot_config) / plot_max_rows)
167
+ n_rows = math.ceil(len(plot_config) / n_cols)
168
+ fig, axs = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(n_cols * 9, n_rows * 3), dpi=80, squeeze=False)
169
+ main_title = get_main_title(final_result, result_dict)
170
+ fig.suptitle(main_title, fontsize=14)
171
+ i_plot = -1
172
+
173
+ base_goals = current_run["prio_independent_data"]["base_goals"]
174
+ # Add subplot for each row in the plot_table
175
+ for subplot_config in plot_config:
176
+ i_plot += 1
177
+ axis = get_subplot_axis(i_plot, n_rows, n_cols, axs)
178
+ goal = get_goal(subplot_config, base_goals)
179
+ subplot = SubplotMatplotlib(
180
+ axis,
181
+ subplot_config,
182
+ goal,
183
+ results,
184
+ results_prev,
185
+ current_run["prio_independent_data"],
186
+ used_colors,
187
+ )
188
+ subplot.plot()
189
+
190
+ fig.tight_layout()
191
+ return save_figure(
192
+ fig,
193
+ current_run["plot_options"]["save_plot_to"],
194
+ current_run["plot_options"]["output_folder"],
195
+ result_dict["priority"],
196
+ final_result,
197
+ )
198
+
199
+
200
+ def set_plotly_layout(plotly_figure, final_result, result_dict, results_compare):
201
+ """Set the layout for the plotly figure."""
202
+ main_title = get_main_title(final_result, result_dict)
203
+ plotly_figure.update_layout(title_text=main_title)
204
+ scale_factor = 0.8
205
+ plotly_figure.update_layout(
206
+ font={"size": scale_factor * 12},
207
+ title_font={"size": scale_factor * 16},
208
+ )
209
+ plotly_figure.update_traces(hovertemplate="%{y}")
210
+ plotly_figure.update_layout(hovermode="x")
211
+ plotly_figure.update_annotations(font_size=scale_factor * 14)
212
+ if results_compare:
213
+ add_buttons_to_plotly(plotly_figure)
214
+ plotly_figure.update_traces(marker={"size": 5})
215
+
216
+
217
+ def create_plotly_figure(
218
+ result_dict: IntermediateResult,
219
+ results_prev: Optional[IntermediateResult],
220
+ current_run: PlotDataAndConfig,
221
+ final_result=False,
222
+ results_compare: Optional[IntermediateResult] = None,
223
+ ) -> Any:
224
+ # pylint: disable=too-many-locals
225
+ """Creates a figure with a subplot for each row in the plot_table."""
226
+ plot_config = current_run["plot_options"]["plot_config"]
227
+ if check_empty_plot_table(plot_config):
228
+ return None
229
+
230
+ n_cols = math.ceil(len(plot_config) / current_run["plot_options"]["plot_max_rows"])
231
+ n_rows = math.ceil(len(plot_config) / n_cols)
232
+ i_plot = -1
233
+ plotly_figure = make_subplots(rows=n_rows, cols=n_cols, subplot_titles=len(plot_config) * [" "], shared_xaxes=True)
234
+ base_goals = current_run["prio_independent_data"]["base_goals"]
235
+
236
+ # Add subplot for each row in the plot_table
237
+ used_colors: list = []
238
+ for subplot_config in plot_config:
239
+ i_plot += 1
240
+ i_c, i_r = get_row_col_number(i_plot, n_rows, n_cols, row_first=True)
241
+ goal = get_goal(subplot_config, base_goals)
242
+ subplot = SubplotPlotly(
243
+ subplot_config,
244
+ goal,
245
+ result_dict["timeseries_data"],
246
+ results_prev,
247
+ current_run["prio_independent_data"],
248
+ used_colors,
249
+ results_compare,
250
+ plotly_figure,
251
+ i_r + 1,
252
+ i_c + 1,
253
+ i_plot,
254
+ )
255
+ subplot.plot()
256
+
257
+ set_plotly_layout(plotly_figure, final_result, result_dict, results_compare)
258
+
259
+ return save_fig_as_html(
260
+ plotly_figure, current_run["plot_options"]["output_folder"], result_dict["priority"], final_result
261
+ )
262
+
263
+
264
+ def create_plot_each_priority(current_run: PlotDataAndConfig, plotting_library: str = "plotly") -> Dict[int, Any]:
265
+ """Create all plots for one optimization run, for each priority one seperate plot."""
266
+ intermediate_results = current_run["intermediate_results"]
267
+ plot_results = {}
268
+ for intermediate_result_prev, intermediate_result in zip([None] + intermediate_results[:-1], intermediate_results):
269
+ priority = intermediate_result["priority"]
270
+ if plotting_library == "plotly":
271
+ plot_results[priority] = create_plotly_figure(intermediate_result, intermediate_result_prev, current_run)
272
+ elif plotting_library == "matplotlib":
273
+ plot_results[priority] = create_matplotlib_figure(
274
+ intermediate_result, intermediate_result_prev, current_run
275
+ )
276
+ else:
277
+ raise ValueError("Invalid plotting library.")
278
+ return plot_results
279
+
280
+
281
+ def create_plot_final_results(
282
+ current_run: PlotDataAndConfig,
283
+ previous_run: Optional[PlotDataAndConfig] = None,
284
+ output_folder=None,
285
+ plotting_library: str = "plotly",
286
+ ) -> Dict[str, Union[StringIO, matplotlib.figure.Figure]]:
287
+ """Create a plot for the final results."""
288
+ current_final_result = sorted(current_run["intermediate_results"], key=lambda x: x["priority"])[-1]
289
+ if previous_run:
290
+ previous_final_result = sorted(previous_run["intermediate_results"], key=lambda x: x["priority"])[-1]
291
+ else:
292
+ previous_final_result = None
293
+
294
+ if output_folder:
295
+ current_run["plot_options"]["output_folder"] = output_folder
296
+
297
+ if plotting_library == "plotly":
298
+ final_results_plot = create_plotly_figure(
299
+ current_final_result, None, current_run, final_result=True, results_compare=previous_final_result
300
+ )
301
+ elif plotting_library == "matplotlib":
302
+ final_results_plot = create_matplotlib_figure(current_final_result, None, current_run, final_result=True)
303
+ else:
304
+ raise ValueError("Invalid plotting library.")
305
+ result_name = "final_results"
306
+ return {result_name: final_results_plot}
@@ -0,0 +1,304 @@
1
+ """Classes for plotting, either using plotly or matplotlib. The classes generate one subplot in the full figure.."""
2
+ from abc import abstractmethod, ABC
3
+ import logging
4
+ import random
5
+ from typing import Any, Dict, Optional
6
+
7
+ import matplotlib.dates as mdates
8
+ import matplotlib.ticker as mtick
9
+ import plotly.graph_objects as go
10
+
11
+ import numpy as np
12
+ from rtctools_interface.utils.plot_table_schema import PlotTableRow
13
+ from rtctools_interface.utils.type_definitions import GoalConfig, IntermediateResult, PrioIndependentData
14
+
15
+ logger = logging.getLogger("rtctools")
16
+
17
+ COMPARISON_RUN_SUFFIX = " (previous run)"
18
+
19
+
20
+ def get_timedeltas(times):
21
+ """Get delta_t for each timestep."""
22
+ return [np.nan] + [times[i] - times[i - 1] for i in range(1, len(times))]
23
+
24
+
25
+ def generate_unique_color(used_colors):
26
+ """Get a color. Adds the new color to used_colors."""
27
+ color_palette = [
28
+ "#1f77b4",
29
+ "#ff7f0e",
30
+ "#2ca02c",
31
+ "#d62728",
32
+ "#9467bd",
33
+ "#8c564b",
34
+ "#e377c2",
35
+ "#7f7f7f",
36
+ "#bcbd22",
37
+ "#17becf",
38
+ ]
39
+
40
+ available_colors = [color for color in color_palette if color not in used_colors]
41
+
42
+ if available_colors:
43
+ new_color = available_colors[0]
44
+ else: # Generate a new color, may be similar to the existing colors.
45
+ new_color = "#{:02x}{:02x}{:02x}".format(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
46
+ used_colors.append(new_color)
47
+ return new_color
48
+
49
+
50
+ class SubplotBase(ABC):
51
+ """Base class for creating subplots."""
52
+
53
+ def __init__(
54
+ self,
55
+ subplot_config: PlotTableRow,
56
+ goal: Optional[GoalConfig],
57
+ results: Dict[str, Any],
58
+ results_prev: Optional[IntermediateResult],
59
+ prio_independent_data: PrioIndependentData,
60
+ used_colors,
61
+ results_compare: Optional[IntermediateResult] = None,
62
+ ):
63
+ self.config: PlotTableRow = subplot_config
64
+ self.goal = goal
65
+ self.function_nominal = self.goal["function_nominal"] if self.goal else 1
66
+ self.results = results
67
+ self.results_prev = results_prev
68
+ self.results_compare = results_compare
69
+ self.datetimes = prio_independent_data["io_datetimes"]
70
+ self.time_deltas = get_timedeltas(prio_independent_data["times"])
71
+ self.used_colors = used_colors
72
+
73
+ if self.goal:
74
+ self.rate_of_change = self.goal.get("goal_type") in ["range_rate_of_change"]
75
+ if self.goal.get("goal_type") in ["range", "range_rate_of_change"]:
76
+ self.target_min, self.target_max = self.goal["target_min_series"], self.goal["target_max_series"]
77
+ else:
78
+ self.target_min, self.target_max = None, None
79
+ else:
80
+ self.rate_of_change = False
81
+
82
+ if "custom_title" in self.config.__dict__ and isinstance(self.config.custom_title, str):
83
+ self.subplot_title = self.config.custom_title
84
+ elif self.config.specified_in == "goal_generator" and self.goal:
85
+ self.subplot_title = "Goal for {} (active from priority {})".format(
86
+ self.goal["state"], self.goal["priority"]
87
+ )
88
+ else:
89
+ self.subplot_title = ""
90
+
91
+ def get_differences(self, timeseries):
92
+ """Get rate of change timeseries for input timeseries, relative to the function nominal."""
93
+ timeseries = list(timeseries)
94
+ return [
95
+ (st - st_prev) / dt / self.function_nominal * 100
96
+ for st, st_prev, dt in zip(timeseries, [np.nan] + timeseries[:-1], self.time_deltas)
97
+ ]
98
+
99
+ def plot_with_comparison(self, label, state_name, linestyle=None, linewidth=None):
100
+ """Plot the state both for the recent run and the comparison run."""
101
+ timeseries_data = self.results[state_name]
102
+ color = generate_unique_color(self.used_colors)
103
+ self.plot_timeseries(label, timeseries_data, color=color, linestyle=linestyle, linewidth=linewidth)
104
+ if self.results_compare and state_name in self.results_compare["timeseries_data"]:
105
+ timeseries_data = self.results_compare["timeseries_data"][state_name]
106
+ label += COMPARISON_RUN_SUFFIX
107
+ self.plot_timeseries(label, timeseries_data, linestyle="dotted", color=color, linewidth=linewidth)
108
+
109
+ def plot_with_previous(self, label, state_name, linestyle=None, linewidth=None):
110
+ """Add line with the results for a particular state. If the results for the previous
111
+ priority are availab, also add a (gray) line with those."""
112
+ self.plot_with_comparison(label, state_name, linestyle=linestyle, linewidth=linewidth)
113
+
114
+ if self.results_prev:
115
+ timeseries_data = self.results_prev["timeseries_data"][state_name]
116
+ label += " (at previous priority optimization)"
117
+ self.plot_timeseries(
118
+ label,
119
+ timeseries_data,
120
+ color="gray",
121
+ linestyle="dotted",
122
+ )
123
+
124
+ def plot_additional_variables(self):
125
+ """Plot the additional variables defined in the plot_table"""
126
+ for var in self.config.variables_style_1:
127
+ self.plot_with_comparison(var, var)
128
+ for var in self.config.variables_style_2:
129
+ self.plot_with_comparison(var, var, linestyle="solid", linewidth="0.5")
130
+ for var in self.config.variables_with_previous_result:
131
+ self.plot_with_previous(var, var)
132
+
133
+ def plot(self):
134
+ """Plot the data in the subplot and format."""
135
+ if self.config.specified_in == "goal_generator" and self.goal:
136
+ self.plot_with_previous(self.goal["state"], self.goal["state"])
137
+ self.plot_additional_variables()
138
+ if (
139
+ self.config.specified_in == "goal_generator"
140
+ and self.goal
141
+ and self.goal["goal_type"]
142
+ in [
143
+ "range",
144
+ "range_rate_of_change",
145
+ ]
146
+ ):
147
+ self.add_ranges()
148
+ self.format_subplot()
149
+
150
+ def add_ranges(self):
151
+ """Add lines for the lower and upper target."""
152
+ if np.array_equal(self.target_min, self.target_max, equal_nan=True):
153
+ self.plot_dashed_line(self.datetimes, self.target_min, "Target", "r")
154
+ else:
155
+ if not (isinstance(self.target_max, float) or np.isnan(self.target_max).any()):
156
+ self.plot_dashed_line(self.datetimes, self.target_max, "Target max", "r")
157
+ if not (isinstance(self.target_min, float) or np.isnan(self.target_min).any()):
158
+ self.plot_dashed_line(self.datetimes, self.target_min, "Target min", "r")
159
+
160
+ def plot_timeseries(self, label, timeseries_data, color=None, linewidth=None, linestyle=None):
161
+ """Plot a timeseries with the given style.
162
+ If subplot is of rate_of_change type, the difference series will be plotted."""
163
+ if self.rate_of_change:
164
+ label = "Rate of Change of " + label
165
+ series_to_plot = self.get_differences(timeseries_data)
166
+ else:
167
+ series_to_plot = timeseries_data
168
+
169
+ self.plot_line(self.datetimes, series_to_plot, label, color, linewidth, linestyle)
170
+
171
+ @abstractmethod
172
+ def plot_line(self, xarray, yarray, label, color=None, linewidth=None, linestyle=None):
173
+ """Given the input and output array, add a line plot to the subplot."""
174
+
175
+ @abstractmethod
176
+ def plot_dashed_line(self, xarray, yarray, label, color):
177
+ """Given the input and output array, add dashed line plot to the subplot."""
178
+
179
+ @abstractmethod
180
+ def format_subplot(self):
181
+ """Format the current subplot."""
182
+
183
+
184
+ class SubplotMatplotlib(SubplotBase):
185
+ """Class for creating subplots using matplotlib. Expects an axis object
186
+ which refers to that subplot."""
187
+
188
+ def __init__(
189
+ self,
190
+ axis,
191
+ subplot_config: PlotTableRow,
192
+ goal: Optional[GoalConfig],
193
+ results: Dict[str, Any],
194
+ results_prev: Optional[IntermediateResult],
195
+ prio_independent_data: PrioIndependentData,
196
+ used_colors,
197
+ ):
198
+ super().__init__(subplot_config, goal, results, results_prev, prio_independent_data, used_colors)
199
+ self.axis = axis
200
+
201
+ def plot_dashed_line(self, xarray, yarray, label, color="red"):
202
+ """Given the input and output array, add dashed line plot to the subplot."""
203
+ self.axis.plot(xarray, yarray, "--", label=label, color=color)
204
+
205
+ def plot_line(self, xarray, yarray, label, color=None, linewidth=None, linestyle=None):
206
+ self.axis.plot(xarray, yarray, label=label, color=color, linewidth=linewidth, linestyle=linestyle)
207
+
208
+ def format_subplot(self):
209
+ """Format the current axis and set legend and title."""
210
+ # Format y-axis
211
+ self.axis.set_ylabel(self.config.y_axis_title)
212
+ self.axis.legend()
213
+ # Set title
214
+ self.axis.set_title(self.subplot_title)
215
+ # Format x-axis
216
+ data_format_str = "%d%b%H"
217
+ date_format = mdates.DateFormatter(data_format_str)
218
+ self.axis.xaxis.set_major_formatter(date_format)
219
+ self.axis.set_xlabel("Time")
220
+ # Format y-axis for rate-of-change-goals
221
+ if self.rate_of_change:
222
+ self.axis.yaxis.set_major_formatter(mtick.PercentFormatter())
223
+ # Add grid lines
224
+ self.axis.grid(which="both", axis="x")
225
+
226
+
227
+ class SubplotPlotly(SubplotBase):
228
+ # As this class is still work in progress...
229
+ """Class for creating subplots using plotly. Expects to be part of
230
+ a figure object with subplots."""
231
+
232
+ def __init__(
233
+ self,
234
+ subplot_config: PlotTableRow,
235
+ goal: Optional[GoalConfig],
236
+ results: Dict[str, Any],
237
+ results_prev: Optional[IntermediateResult],
238
+ prio_independent_data: PrioIndependentData,
239
+ used_colors,
240
+ results_compare: Optional[IntermediateResult] = None,
241
+ figure=None,
242
+ row_num=0,
243
+ col_num=0,
244
+ i_plot=None,
245
+ ):
246
+ super().__init__(
247
+ subplot_config, goal, results, results_prev, prio_independent_data, used_colors, results_compare
248
+ )
249
+ self.row_num = row_num
250
+ self.col_num = col_num
251
+ self.use_plotly = True
252
+ self.figure = figure
253
+ self.i_plot = i_plot
254
+
255
+ def map_color_code(self, color):
256
+ """Map a color code to a plotly supported color code."""
257
+ color_mapping = {"r": "red"}
258
+ return color_mapping.get(color, color)
259
+
260
+ def plot_dashed_line(self, xarray, yarray, label, color="red"):
261
+ """Given the input and output array, add dashed line plot to the subplot."""
262
+ self.figure.add_trace(
263
+ go.Scatter(
264
+ legendgroup=self.i_plot,
265
+ x=xarray,
266
+ y=yarray,
267
+ name=label,
268
+ line={"color": self.map_color_code(color), "dash": "dot"},
269
+ ),
270
+ row=self.row_num,
271
+ col=self.col_num,
272
+ )
273
+
274
+ def plot_line(self, xarray, yarray, label, color=None, linewidth=None, linestyle=None):
275
+ linewidth = float(linewidth) * 1.3 if linewidth else linewidth
276
+ linestyle = "dot" if linestyle == "dotted" else linestyle
277
+ self.figure.add_trace(
278
+ go.Scatter(
279
+ legendgroup=self.i_plot,
280
+ legendgrouptitle_text=self.subplot_title,
281
+ x=xarray,
282
+ y=yarray,
283
+ name=label,
284
+ line={"width": linewidth, "dash": linestyle, "color": color},
285
+ ),
286
+ row=self.row_num,
287
+ col=self.col_num,
288
+ )
289
+
290
+ def format_subplot(self):
291
+ """Format the current axis and set legend and title."""
292
+ # Format y-axis
293
+ self.figure.update_yaxes(title_text=self.config.y_axis_title, row=self.row_num, col=self.col_num)
294
+ # Set title
295
+ self.figure.layout.annotations[self.i_plot]["text"] = self.subplot_title
296
+ # Format x-axis
297
+ data_format_str = "%d%b%H"
298
+ self.figure.update_xaxes(tickformat=data_format_str, row=self.row_num, col=self.col_num)
299
+ # Format y-axis for rate-of-change-goals
300
+ if self.rate_of_change:
301
+ self.figure.update_yaxes(tickformat=".1", row=self.row_num, col=self.col_num)
302
+ # Add grid lines
303
+ self.figure.update_xaxes(showgrid=True, row=self.row_num, col=self.col_num, gridwidth=1, gridcolor="gray")
304
+ self.figure.update_xaxes(showticklabels=True, row=self.row_num, col=self.col_num)
@@ -0,0 +1,31 @@
1
+ """Module for a basic simulation problem."""
2
+ from rtctools.simulation.csv_mixin import CSVMixin
3
+ from rtctools.simulation.simulation_problem import SimulationProblem
4
+
5
+
6
+ class BaseSimulationProblem(
7
+ CSVMixin,
8
+ SimulationProblem,
9
+ ):
10
+ # Ignore too many ancestors, since the use of mixin classes is how rtc-tools is set up.
11
+ # pylint: disable=too-many-ancestors
12
+ """
13
+ Basic simulation problem for a given state.
14
+
15
+ :cvar goal_table_file:
16
+ path to csv file containing a list of goals.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ **kwargs,
22
+ ):
23
+ super().__init__(**kwargs)
24
+
25
+ def update(self, dt):
26
+ self.set_var("u", 1)
27
+ super().update(dt)
28
+
29
+ def initialize(self):
30
+ self.set_var("u", 1)
31
+ super().initialize()
@@ -0,0 +1,27 @@
1
+ """Mixin to store all required data for plotting. Can also call the plot function."""
2
+ import logging
3
+
4
+ from rtctools_interface.plotting.plot_tools import create_plot_final_results
5
+ from rtctools_interface.utils.results_collection import PlottingBaseMixin
6
+
7
+ logger = logging.getLogger("rtctools")
8
+
9
+
10
+ class PlotMixin(PlottingBaseMixin):
11
+ """
12
+ Class for plotting results based on the plot_table.
13
+ """
14
+
15
+ optimization_problem = False
16
+
17
+ def post(self):
18
+ """Tasks after optimizing."""
19
+ super().post()
20
+
21
+ timeseries_data = self.collect_timeseries_data(self.custom_variables)
22
+ self.intermediate_results.append({"timeseries_data": timeseries_data, "priority": 0})
23
+ current_run = self.create_plot_data_and_config([])
24
+ self._store_current_results(self._cache_folder, current_run)
25
+
26
+ if self.plot_final_results:
27
+ create_plot_final_results(current_run, self._previous_run, plotting_library=self.plotting_library)
File without changes
File without changes