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.
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/PKG-INFO +1 -1
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/PKG-INFO +1 -1
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/SOURCES.txt +6 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/_version.py +3 -3
- rtc-tools-interface-0.7.1/rtctools_interface/plotting/plot_tools.py +306 -0
- rtc-tools-interface-0.7.1/rtctools_interface/plotting/subplot_classes.py +304 -0
- rtc-tools-interface-0.7.1/rtctools_interface/simulation/base_simulation_problem.py +31 -0
- rtc-tools-interface-0.7.1/rtctools_interface/simulation/plot_mixin.py +27 -0
- rtc-tools-interface-0.7.1/tests/simulation/__init__.py +0 -0
- rtc-tools-interface-0.7.1/tests/utils/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/COPYING.LESSER +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/README.md +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/dependency_links.txt +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/requires.txt +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/top_level.txt +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/base_goal.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/base_optimization_problem.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_generator_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_performance_metrics.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/goal_table_schema.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/helpers/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/helpers/statistics_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/plot_goals_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/plot_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/optimization/read_goals.py +0 -0
- {rtc-tools-interface-0.7.0b3/rtctools_interface/utils → rtc-tools-interface-0.7.1/rtctools_interface/plotting}/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3/tests → rtc-tools-interface-0.7.1/rtctools_interface/simulation}/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3/tests/optimization → rtc-tools-interface-0.7.1/rtctools_interface/utils}/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/plot_table_schema.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/read_goals_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/read_plot_table.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/results_collection.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/serialization.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/type_definitions.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/setup.cfg +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/setup.py +0 -0
- {rtc-tools-interface-0.7.0b3/tests/simulation → rtc-tools-interface-0.7.1/tests}/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3/tests/utils → rtc-tools-interface-0.7.1/tests/optimization}/__init__.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_base_optimization_problem.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_passing_goals_directly.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_plot_goals_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_read_goals.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/simulation/test_base_simulation_problem.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/simulation/test_plot_mixin.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/utils/get_test.py +0 -0
- {rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/versioneer.py +0 -0
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/SOURCES.txt
RENAMED
|
@@ -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
|
+
"date": "2023-12-22T13:22:49+0000",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.7.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/requires.txt
RENAMED
|
File without changes
|
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtc_tools_interface.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/rtctools_interface/utils/serialization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.7.0b3/tests/simulation → rtc-tools-interface-0.7.1/tests}/__init__.py
RENAMED
|
File without changes
|
{rtc-tools-interface-0.7.0b3/tests/utils → rtc-tools-interface-0.7.1/tests/optimization}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/optimization/test_read_goals.py
RENAMED
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.7.0b3 → rtc-tools-interface-0.7.1}/tests/simulation/test_plot_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|