rtc-tools-interface 0.6.0b2__tar.gz → 0.7.0__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.6.0b2 → rtc-tools-interface-0.7.0}/PKG-INFO +1 -1
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/README.md +16 -12
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/PKG-INFO +1 -1
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/SOURCES.txt +19 -10
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/_version.py +3 -3
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/base_goal.py +15 -8
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_generator_mixin.py +8 -17
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_performance_metrics.py +13 -9
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_table_schema.py +1 -1
- rtc-tools-interface-0.7.0/rtctools_interface/optimization/helpers/statistics_mixin.py +68 -0
- rtc-tools-interface-0.7.0/rtctools_interface/optimization/plot_goals_mixin.py +14 -0
- rtc-tools-interface-0.7.0/rtctools_interface/optimization/plot_mixin.py +46 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/read_goals.py +2 -2
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/plot_tools.py +13 -14
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/subplot_classes.py +39 -43
- rtc-tools-interface-0.7.0/rtctools_interface/simulation/base_simulation_problem.py +31 -0
- rtc-tools-interface-0.7.0/rtctools_interface/simulation/plot_mixin.py +27 -0
- rtc-tools-interface-0.7.0/rtctools_interface/utils/read_goals_mixin.py +31 -0
- rtc-tools-interface-0.7.0/rtctools_interface/utils/read_plot_table.py +48 -0
- rtc-tools-interface-0.7.0/rtctools_interface/utils/results_collection.py +188 -0
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/helpers → rtc-tools-interface-0.7.0/rtctools_interface/utils}/serialization.py +11 -24
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface/utils}/type_definitions.py +8 -21
- rtc-tools-interface-0.7.0/tests/optimization/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_base_optimization_problem.py +2 -2
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_passing_goals_directly.py +2 -2
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_plot_goals_mixin.py +5 -5
- rtc-tools-interface-0.7.0/tests/simulation/__init__.py +0 -0
- rtc-tools-interface-0.7.0/tests/simulation/test_base_simulation_problem.py +27 -0
- rtc-tools-interface-0.7.0/tests/simulation/test_plot_mixin.py +54 -0
- rtc-tools-interface-0.7.0/tests/utils/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2/tests/optimization → rtc-tools-interface-0.7.0/tests/utils}/get_test.py +9 -6
- rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/helpers/statistics_mixin.py +0 -80
- rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/plot_and_goal_schema.py +0 -32
- rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/plot_goals_mixin.py +0 -179
- rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/read_plot_table.py +0 -102
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/COPYING.LESSER +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/dependency_links.txt +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/requires.txt +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/top_level.txt +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/base_optimization_problem.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/helpers/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2/rtctools_interface/utils → rtc-tools-interface-0.7.0/rtctools_interface/simulation}/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2/tests → rtc-tools-interface-0.7.0/rtctools_interface/utils}/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface/utils}/plot_table_schema.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/setup.cfg +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/setup.py +0 -0
- {rtc-tools-interface-0.6.0b2/tests/optimization → rtc-tools-interface-0.7.0/tests}/__init__.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_read_goals.py +0 -0
- {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/versioneer.py +0 -0
|
@@ -110,28 +110,32 @@ The calculated metrics are:
|
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
## Automatic plotting of results
|
|
113
|
-
With the `
|
|
113
|
+
With the `PlotMixin` one can easily make plots of the results of rtc-tools. This functionality can be used both for optimization and simulation problems. For optimization problems, use:
|
|
114
114
|
```python
|
|
115
|
-
from rtctools_interface.optimization.
|
|
115
|
+
from rtctools_interface.optimization.plot_mixin import PlotMixin
|
|
116
116
|
```
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
and for simulation problems use:
|
|
118
|
+
```python
|
|
119
|
+
from rtctools_interface.simulation.plot_mixin import PlotMixin
|
|
120
|
+
```
|
|
121
|
+
Then, add the `PlotMixin` to your optimization/simulation problem class. For optimization problems, the PlotMixin can create a plot after each priority and/or a plot with the final results only.
|
|
122
|
+
By default, the `PlotMixin` will make both. This can be changed by setting the class variables `plot_results_each_priority` and `plot_final_results` to either `True` or `False` in your problem class.
|
|
119
123
|
|
|
120
|
-
Furthermore, the
|
|
124
|
+
Furthermore, the PlotMixin can either create `Plotly` plots and `matplotlib` plots. The `matplotlib` plots will be exported as `png`, the Plotly figures as `html`. By default, `Plotly` is used. To change this, pass the keyword-argument `plotting_library="matplotlib"` to the `run_optimization_problem` function.
|
|
121
125
|
|
|
122
126
|
### Comparing results from different runs
|
|
123
|
-
-
|
|
124
|
-
- The `final_results` plot will show the result from the **previous
|
|
127
|
+
- In optimization mode, the plots for a particular priority will contain line segments with the results from the previous priority result. This makes it easy to see what changed from priority to priority.
|
|
128
|
+
- The `final_results` plot will show the result from the **previous run**. This allows for comparing results from different scenario's (like input timeseries or changes to the model). Note that is not possible to change the number of goals between two comparison runs. This feature currently only works with Plotly plots, where a dropdown is available to hide the previous results.
|
|
125
129
|
### Configuration variables
|
|
126
|
-
The following class variables can be set to change the behaviour of the
|
|
130
|
+
The following class variables can be set to change the behaviour of the PlotMixin:
|
|
127
131
|
- `plot_max_rows`: an integer number for the maximum number of rows (default is 4). The number of columns will be derived from that.
|
|
128
|
-
- `plot_results_each_priority`: boolean indicating whether the plots for each priority should be generated and saved. Default is True.
|
|
132
|
+
- `plot_results_each_priority`: Only for optimization: boolean indicating whether the plots for each priority should be generated and saved. Default is True.
|
|
129
133
|
- `plot_table_file`: path to plot table csv file. Default is `input\plot_table.csv`.
|
|
130
134
|
|
|
131
135
|
### Specifying the plot table
|
|
132
|
-
There are two types of plots that can be made with the
|
|
133
|
-
1. Plots
|
|
134
|
-
2. Plots
|
|
136
|
+
There are two types of plots that can be made with the PlotMixin
|
|
137
|
+
1. Plots of arbitrary states, for example ones being optimized in a goal defined in Python.
|
|
138
|
+
2. Plots based on goals in the goal_generator table (only applicable to optimization problems).
|
|
135
139
|
|
|
136
140
|
To add a plot for a goal in the `goal_generator` table, one should add a row to the `plot_table` with an `id` equal to the id of the goal in the `goal_generator` to be plotted. The `specified_in` field should be set to `goal_generator`. Rows of the `plot_table` with `specified_in`=`goal_generator` but with an `id` that does not occcur in the `goal_table`, are ignored.
|
|
137
141
|
|
{rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/SOURCES.txt
RENAMED
|
@@ -16,23 +16,32 @@ rtctools_interface/optimization/base_optimization_problem.py
|
|
|
16
16
|
rtctools_interface/optimization/goal_generator_mixin.py
|
|
17
17
|
rtctools_interface/optimization/goal_performance_metrics.py
|
|
18
18
|
rtctools_interface/optimization/goal_table_schema.py
|
|
19
|
-
rtctools_interface/optimization/plot_and_goal_schema.py
|
|
20
19
|
rtctools_interface/optimization/plot_goals_mixin.py
|
|
21
|
-
rtctools_interface/optimization/
|
|
20
|
+
rtctools_interface/optimization/plot_mixin.py
|
|
22
21
|
rtctools_interface/optimization/read_goals.py
|
|
23
|
-
rtctools_interface/optimization/read_plot_table.py
|
|
24
|
-
rtctools_interface/optimization/type_definitions.py
|
|
25
22
|
rtctools_interface/optimization/helpers/__init__.py
|
|
26
|
-
rtctools_interface/optimization/helpers/serialization.py
|
|
27
23
|
rtctools_interface/optimization/helpers/statistics_mixin.py
|
|
28
|
-
rtctools_interface/
|
|
29
|
-
rtctools_interface/
|
|
30
|
-
rtctools_interface/
|
|
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
|
|
31
30
|
rtctools_interface/utils/__init__.py
|
|
31
|
+
rtctools_interface/utils/plot_table_schema.py
|
|
32
|
+
rtctools_interface/utils/read_goals_mixin.py
|
|
33
|
+
rtctools_interface/utils/read_plot_table.py
|
|
34
|
+
rtctools_interface/utils/results_collection.py
|
|
35
|
+
rtctools_interface/utils/serialization.py
|
|
36
|
+
rtctools_interface/utils/type_definitions.py
|
|
32
37
|
tests/__init__.py
|
|
33
38
|
tests/optimization/__init__.py
|
|
34
|
-
tests/optimization/get_test.py
|
|
35
39
|
tests/optimization/test_base_optimization_problem.py
|
|
36
40
|
tests/optimization/test_passing_goals_directly.py
|
|
37
41
|
tests/optimization/test_plot_goals_mixin.py
|
|
38
|
-
tests/optimization/test_read_goals.py
|
|
42
|
+
tests/optimization/test_read_goals.py
|
|
43
|
+
tests/simulation/__init__.py
|
|
44
|
+
tests/simulation/test_base_simulation_problem.py
|
|
45
|
+
tests/simulation/test_plot_mixin.py
|
|
46
|
+
tests/utils/__init__.py
|
|
47
|
+
tests/utils/get_test.py
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2023-
|
|
11
|
+
"date": "2023-12-19T14:41:26+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "4873e97ef18dfc53c55550cf1c3d12f32aa6d368",
|
|
15
|
+
"version": "0.7.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -8,7 +8,7 @@ from rtctools.optimization.optimization_problem import OptimizationProblem
|
|
|
8
8
|
from rtctools.optimization.timeseries import Timeseries
|
|
9
9
|
|
|
10
10
|
from rtctools_interface.optimization.goal_table_schema import GOAL_TYPES, TARGET_DATA_TYPES
|
|
11
|
-
from rtctools_interface.
|
|
11
|
+
from rtctools_interface.utils.type_definitions import GoalConfig
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger("rtctools")
|
|
@@ -48,6 +48,7 @@ class BaseGoal(Goal):
|
|
|
48
48
|
):
|
|
49
49
|
self.goal_id = goal_id
|
|
50
50
|
self.state = state
|
|
51
|
+
self.target_data_type = target_data_type
|
|
51
52
|
self._set_goal_type(goal_type)
|
|
52
53
|
if goal_type in ["range", "range_rate_of_change"]:
|
|
53
54
|
self._set_function_bounds(
|
|
@@ -59,7 +60,6 @@ class BaseGoal(Goal):
|
|
|
59
60
|
if goal_type in ["range", "range_rate_of_change"]:
|
|
60
61
|
self._set_target_bounds(
|
|
61
62
|
optimization_problem=optimization_problem,
|
|
62
|
-
target_data_type=target_data_type,
|
|
63
63
|
target_min=target_min,
|
|
64
64
|
target_max=target_max,
|
|
65
65
|
)
|
|
@@ -155,7 +155,6 @@ class BaseGoal(Goal):
|
|
|
155
155
|
def _set_target_bounds(
|
|
156
156
|
self,
|
|
157
157
|
optimization_problem: OptimizationProblem,
|
|
158
|
-
target_data_type="value",
|
|
159
158
|
target_min=np.nan,
|
|
160
159
|
target_max=np.nan,
|
|
161
160
|
):
|
|
@@ -193,19 +192,21 @@ class BaseGoal(Goal):
|
|
|
193
192
|
elif np.isnan(target_min):
|
|
194
193
|
self.target_min = np.nan
|
|
195
194
|
|
|
196
|
-
if target_data_type not in TARGET_DATA_TYPES:
|
|
195
|
+
if self.target_data_type not in TARGET_DATA_TYPES:
|
|
197
196
|
raise ValueError(f"target_data_type should be one of {TARGET_DATA_TYPES}.")
|
|
198
197
|
|
|
199
|
-
if self.goal_type == "range_rate_of_change" and target_data_type != "value":
|
|
198
|
+
if self.goal_type == "range_rate_of_change" and self.target_data_type != "value":
|
|
200
199
|
raise ValueError("For range_rate_of_change goal only the `value` target type is supported.")
|
|
201
200
|
|
|
202
|
-
if target_data_type == "value":
|
|
201
|
+
if self.target_data_type == "value":
|
|
203
202
|
set_value_target()
|
|
204
|
-
elif target_data_type == "parameter":
|
|
203
|
+
elif self.target_data_type == "parameter":
|
|
205
204
|
set_parameter_target()
|
|
206
|
-
elif target_data_type == "timeseries":
|
|
205
|
+
elif self.target_data_type == "timeseries":
|
|
207
206
|
set_timeseries_target()
|
|
208
207
|
|
|
208
|
+
self._target_dict = optimization_problem.collect_range_target_values_from_basegoal(self)
|
|
209
|
+
|
|
209
210
|
def get_goal_config(self) -> GoalConfig:
|
|
210
211
|
"""
|
|
211
212
|
Serialize the goal configuration into a dictionary.
|
|
@@ -217,6 +218,8 @@ class BaseGoal(Goal):
|
|
|
217
218
|
"function_min": self.function_range[0] if np.isfinite(self.function_range[0]) else None,
|
|
218
219
|
"function_max": self.function_range[1] if np.isfinite(self.function_range[1]) else None,
|
|
219
220
|
"function_nominal": self.function_nominal if np.isfinite(self.function_nominal) else None,
|
|
221
|
+
"target_min_series": None,
|
|
222
|
+
"target_max_series": None,
|
|
220
223
|
"target_min": self.target_min,
|
|
221
224
|
"target_max": self.target_max,
|
|
222
225
|
"priority": self.priority,
|
|
@@ -227,4 +230,8 @@ class BaseGoal(Goal):
|
|
|
227
230
|
goal_config["target_min"] = self.target_min.values
|
|
228
231
|
if isinstance(self.target_max, Timeseries):
|
|
229
232
|
goal_config["target_max"] = self.target_max.values
|
|
233
|
+
|
|
234
|
+
if hasattr(self, "_target_dict"):
|
|
235
|
+
goal_config["target_min_series"] = self._target_dict["target_min"]
|
|
236
|
+
goal_config["target_max_series"] = self._target_dict["target_max"]
|
|
230
237
|
return goal_config
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Dict, Union
|
|
4
4
|
import logging
|
|
5
|
-
import os
|
|
6
5
|
import pandas as pd
|
|
7
6
|
|
|
8
7
|
from rtctools_interface.optimization.base_goal import BaseGoal
|
|
9
8
|
from rtctools_interface.optimization.goal_performance_metrics import get_performance_metrics
|
|
10
9
|
from rtctools_interface.optimization.helpers.statistics_mixin import StatisticsMixin
|
|
11
|
-
from rtctools_interface.
|
|
10
|
+
from rtctools_interface.utils.read_goals_mixin import ReadGoalsMixin
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger("rtctools")
|
|
14
13
|
|
|
@@ -21,7 +20,7 @@ def write_performance_metrics(performance_metrics: Dict[str, pd.DataFrame], outp
|
|
|
21
20
|
performance_metric_table.to_csv(output_path / f"{goal_id}.csv")
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
class GoalGeneratorMixin(StatisticsMixin):
|
|
23
|
+
class GoalGeneratorMixin(ReadGoalsMixin, StatisticsMixin):
|
|
25
24
|
# TODO: remove pylint disable below once we have more public functions.
|
|
26
25
|
# pylint: disable=too-few-public-methods
|
|
27
26
|
"""Add path goals as specified in the goal_table.
|
|
@@ -34,19 +33,10 @@ class GoalGeneratorMixin(StatisticsMixin):
|
|
|
34
33
|
|
|
35
34
|
def __init__(self, **kwargs):
|
|
36
35
|
super().__init__(**kwargs)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
self._goal_generator_path_goals = read_goals(
|
|
43
|
-
self.goal_table_file, path_goal=True, read_from=self.read_from, goals_to_generate=self.goals_to_generate
|
|
44
|
-
)
|
|
45
|
-
self._goal_generator_non_path_goals = read_goals(
|
|
46
|
-
self.goal_table_file, path_goal=False, read_from=self.read_from, goals_to_generate=self.goals_to_generate
|
|
47
|
-
)
|
|
48
|
-
self._all_goal_generator_goals = self._goal_generator_path_goals + self._goal_generator_non_path_goals
|
|
49
|
-
|
|
36
|
+
if not hasattr(self, "_all_goal_generator_goals"):
|
|
37
|
+
goals_to_generate = kwargs.get("goals_to_generate", [])
|
|
38
|
+
read_from = kwargs.get("read_goals_from", "csv_table")
|
|
39
|
+
self.load_goals(read_from, goals_to_generate)
|
|
50
40
|
if self.calculate_performance_metrics:
|
|
51
41
|
# A dataframe for each goal defined by the goal generator
|
|
52
42
|
self._performance_metrics = {}
|
|
@@ -73,7 +63,8 @@ class GoalGeneratorMixin(StatisticsMixin):
|
|
|
73
63
|
"""Calculate and store performance metrics."""
|
|
74
64
|
results = self.extract_results()
|
|
75
65
|
goal_generator_goals = self._all_goal_generator_goals
|
|
76
|
-
|
|
66
|
+
all_base_goals = [goal for goal in self.goals() + self.path_goals() if isinstance(goal, BaseGoal)]
|
|
67
|
+
targets = self.collect_range_target_values(all_base_goals)
|
|
77
68
|
for goal in goal_generator_goals:
|
|
78
69
|
next_row = get_performance_metrics(results, goal, targets.get(str(goal.goal_id)))
|
|
79
70
|
if next_row is not None:
|
|
@@ -12,7 +12,7 @@ from rtctools_interface.optimization.goal_table_schema import (
|
|
|
12
12
|
RangeGoalModel,
|
|
13
13
|
RangeRateOfChangeGoalModel,
|
|
14
14
|
)
|
|
15
|
-
from rtctools_interface.
|
|
15
|
+
from rtctools_interface.utils.type_definitions import TargetDict
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger("rtctools")
|
|
@@ -63,33 +63,37 @@ def performance_metrics_minmaximization(results: Dict[str, np.ndarray], goal: Mi
|
|
|
63
63
|
|
|
64
64
|
def get_range_percentual_exceedance(
|
|
65
65
|
timeseries: np.ndarray, goal: RangeGoalModel, targets: TargetDict
|
|
66
|
-
) -> Optional[dict[str, float]]:
|
|
66
|
+
) -> Optional[dict[str, Optional[float]]]:
|
|
67
67
|
"""Calculate percentage of timesteps in which target is exceeded"""
|
|
68
68
|
if goal.goal_type not in ["range", "range_rate_of_change"]:
|
|
69
69
|
below_target = None
|
|
70
70
|
above_target = None
|
|
71
71
|
else:
|
|
72
|
-
below_target = sum(np.where(timeseries + ABS_TOL < targets["target_min"], 1, 0)) / len(timeseries)
|
|
73
|
-
above_target = sum(np.where(timeseries - ABS_TOL > targets["target_max"], 1, 0)) / len(timeseries)
|
|
72
|
+
below_target = float(sum(np.where(timeseries + ABS_TOL < targets["target_min"], 1, 0)) / len(timeseries))
|
|
73
|
+
above_target = float(sum(np.where(timeseries - ABS_TOL > targets["target_max"], 1, 0)) / len(timeseries))
|
|
74
74
|
return {"perc_below_target": below_target, "perc_above_target": above_target}
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def get_range_total_exceedance(
|
|
78
78
|
timeseries: np.ndarray, goal: RangeGoalModel, targets: TargetDict
|
|
79
|
-
) -> Optional[dict[str, float]]:
|
|
79
|
+
) -> Optional[dict[str, Optional[float]]]:
|
|
80
80
|
"""Calculate sum of absolute exceedances of the target"""
|
|
81
81
|
if goal.goal_type not in ["range", "range_rate_of_change"]:
|
|
82
82
|
below_target = None
|
|
83
83
|
above_target = None
|
|
84
84
|
else:
|
|
85
|
-
below_target =
|
|
86
|
-
|
|
85
|
+
below_target = float(
|
|
86
|
+
sum(np.abs(np.where(timeseries < targets["target_min"], timeseries - targets["target_min"], 0)))
|
|
87
|
+
)
|
|
88
|
+
above_target = float(
|
|
89
|
+
sum(np.abs(np.where(timeseries > targets["target_max"], timeseries - targets["target_max"], 0)))
|
|
90
|
+
)
|
|
87
91
|
return {"sum_below_target": below_target, "sum_above_target": above_target}
|
|
88
92
|
|
|
89
93
|
|
|
90
94
|
def performance_metrics_range(results: Dict[str, np.ndarray], goal: RangeGoalModel, targets: TargetDict) -> pd.Series:
|
|
91
95
|
"""Get all relevant statistics for a range goal."""
|
|
92
|
-
metrics = {}
|
|
96
|
+
metrics: dict = {}
|
|
93
97
|
state_timeseries = results[goal.state]
|
|
94
98
|
metrics = metrics | get_basic_metrics(state_timeseries)
|
|
95
99
|
metrics = metrics | get_range_percentual_exceedance(state_timeseries, goal, targets)
|
|
@@ -101,7 +105,7 @@ def performance_metrics_rangerateofchange(
|
|
|
101
105
|
results: Dict[str, np.ndarray], goal: RangeGoalModel, _targets: TargetDict
|
|
102
106
|
) -> pd.Series:
|
|
103
107
|
"""Get all relevant statistics for a range-rate-of-change goal."""
|
|
104
|
-
metrics = {}
|
|
108
|
+
metrics: dict[str, Optional[float]] = {}
|
|
105
109
|
state_timeseries = results[goal.state]
|
|
106
110
|
metrics = metrics | get_basic_metrics(state_timeseries)
|
|
107
111
|
return pd.Series(metrics)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Base class/mixin for with some methods for retrieving particular data/stats for goals and plotting. """
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, List, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from rtctools_interface.optimization.base_goal import BaseGoal
|
|
8
|
+
from rtctools_interface.utils.type_definitions import TargetDict
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("rtctools")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StatisticsMixin:
|
|
14
|
+
# TODO: remove pylint disable below once we have more public functions.
|
|
15
|
+
# pylint: disable=too-few-public-methods
|
|
16
|
+
"""A mixin class providing methods for collecting data and statistics from optimization results,
|
|
17
|
+
useful for solution performance analysis."""
|
|
18
|
+
|
|
19
|
+
def collect_range_target_values(
|
|
20
|
+
self,
|
|
21
|
+
base_goals: List[BaseGoal],
|
|
22
|
+
) -> Dict[str, TargetDict]:
|
|
23
|
+
"""For the goals with targets, collect the actual timeseries with these targets."""
|
|
24
|
+
target_series: Dict[str, TargetDict] = {}
|
|
25
|
+
for goal in base_goals:
|
|
26
|
+
if goal.goal_type in ["range", "range_rate_of_change"]:
|
|
27
|
+
target_dict = self.collect_range_target_values_from_basegoal(goal)
|
|
28
|
+
target_series[str(goal.goal_id)] = target_dict
|
|
29
|
+
return target_series
|
|
30
|
+
|
|
31
|
+
def collect_range_target_values_from_basegoal(self, goal: BaseGoal) -> TargetDict:
|
|
32
|
+
"""Collect the target timeseries for a single basegoal."""
|
|
33
|
+
t = self.times()
|
|
34
|
+
|
|
35
|
+
def get_parameter_ranges(goal) -> Tuple[np.ndarray, np.ndarray]:
|
|
36
|
+
target_min = np.full_like(t, 1) * float(goal.target_min)
|
|
37
|
+
target_max = np.full_like(t, 1) * float(goal.target_max)
|
|
38
|
+
return target_min, target_max
|
|
39
|
+
|
|
40
|
+
def get_value_ranges(goal) -> Tuple[np.ndarray, np.ndarray]:
|
|
41
|
+
target_min = np.full_like(t, 1) * float(goal.target_min)
|
|
42
|
+
target_max = np.full_like(t, 1) * float(goal.target_max)
|
|
43
|
+
return target_min, target_max
|
|
44
|
+
|
|
45
|
+
def get_timeseries_ranges(goal) -> Tuple[np.ndarray, np.ndarray]:
|
|
46
|
+
try:
|
|
47
|
+
target_min = goal.target_min.values
|
|
48
|
+
except AttributeError:
|
|
49
|
+
target_min = goal.target_min
|
|
50
|
+
try:
|
|
51
|
+
target_max = goal.target_max.values
|
|
52
|
+
except AttributeError:
|
|
53
|
+
target_max = goal.target_max
|
|
54
|
+
return target_min, target_max
|
|
55
|
+
|
|
56
|
+
if goal.goal_type in ["range", "range_rate_of_change"]:
|
|
57
|
+
if goal.target_data_type == "parameter":
|
|
58
|
+
target_min, target_max = get_parameter_ranges(goal)
|
|
59
|
+
elif goal.target_data_type == "value":
|
|
60
|
+
target_min, target_max = get_value_ranges(goal)
|
|
61
|
+
elif goal.target_data_type == "timeseries":
|
|
62
|
+
target_min, target_max = get_timeseries_ranges(goal)
|
|
63
|
+
else:
|
|
64
|
+
message = "Target type {} not known for goal {}.".format(goal.target_data_type, goal.goal_id)
|
|
65
|
+
logger.error(message)
|
|
66
|
+
raise ValueError(message)
|
|
67
|
+
target_dict: TargetDict = {"target_min": target_min, "target_max": target_max}
|
|
68
|
+
return target_dict
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Deprecated, use PlotMixin."""
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
from rtctools_interface.optimization.plot_mixin import PlotMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PlotGoalsMixin(PlotMixin):
|
|
8
|
+
"""
|
|
9
|
+
Deprecated class, use PlotMixin instead.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
warnings.warn("PlotGoalsMixin is deprecated, use PlotMixin instead", FutureWarning, stacklevel=1)
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Mixin to store all required data for plotting. Can also call the plot function."""
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from rtctools_interface.optimization.helpers.statistics_mixin import StatisticsMixin
|
|
5
|
+
from rtctools_interface.plotting.plot_tools import create_plot_each_priority, create_plot_final_results
|
|
6
|
+
from rtctools_interface.optimization.base_goal import BaseGoal
|
|
7
|
+
from rtctools_interface.utils.results_collection import PlottingBaseMixin
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("rtctools")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PlotMixin(PlottingBaseMixin, StatisticsMixin):
|
|
13
|
+
"""
|
|
14
|
+
Class for plotting results.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
optimization_problem = True
|
|
18
|
+
|
|
19
|
+
def priority_completed(self, priority: int) -> None:
|
|
20
|
+
"""Store priority-dependent results required for plotting."""
|
|
21
|
+
timeseries_data = self.collect_timeseries_data(list(set(self.custom_variables + self.state_variables)))
|
|
22
|
+
to_store = {"timeseries_data": timeseries_data, "priority": priority}
|
|
23
|
+
self.intermediate_results.append(to_store)
|
|
24
|
+
super().priority_completed(priority)
|
|
25
|
+
|
|
26
|
+
def post(self):
|
|
27
|
+
"""Tasks after optimizing. Creates a plot for for each priority."""
|
|
28
|
+
super().post()
|
|
29
|
+
|
|
30
|
+
if self.solver_stats["success"]:
|
|
31
|
+
base_goals = [
|
|
32
|
+
goal.get_goal_config() for goal in self.goals() + self.path_goals() if isinstance(goal, BaseGoal)
|
|
33
|
+
]
|
|
34
|
+
current_run = self.create_plot_data_and_config(base_goals)
|
|
35
|
+
# Cache results, such that in a next run they can be used for comparison
|
|
36
|
+
self._store_current_results(self._cache_folder, current_run)
|
|
37
|
+
|
|
38
|
+
# Create the plots
|
|
39
|
+
plot_data = {}
|
|
40
|
+
if self.plot_results_each_priority:
|
|
41
|
+
plot_data = plot_data | create_plot_each_priority(current_run, plotting_library=self.plotting_library)
|
|
42
|
+
|
|
43
|
+
if self.plot_final_results:
|
|
44
|
+
plot_data = plot_data | create_plot_final_results(
|
|
45
|
+
current_run, self._previous_run, plotting_library=self.plotting_library
|
|
46
|
+
)
|
|
@@ -54,8 +54,8 @@ def read_goals_from_list(
|
|
|
54
54
|
"""Read goals from a list. Validates whether the goals are of correct type."""
|
|
55
55
|
if not isinstance(goals_to_generate, list):
|
|
56
56
|
raise TypeError(f"Pass a list of goal elements, not a {type(goals_to_generate)}")
|
|
57
|
-
for
|
|
58
|
-
if not isinstance(
|
|
57
|
+
for base_goal in goals_to_generate:
|
|
58
|
+
if not isinstance(base_goal, BaseGoalModel):
|
|
59
59
|
raise TypeError("Each element in the list of goals to generate should be a child of BaseGoalModel")
|
|
60
60
|
active_goals = []
|
|
61
61
|
for goal in goals_to_generate:
|
|
@@ -10,13 +10,12 @@ import matplotlib
|
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
11
|
from plotly.subplots import make_subplots
|
|
12
12
|
|
|
13
|
-
from rtctools_interface.
|
|
14
|
-
from rtctools_interface.optimization.plotting.subplot_classes import (
|
|
13
|
+
from rtctools_interface.plotting.subplot_classes import (
|
|
15
14
|
COMPARISON_RUN_SUFFIX,
|
|
16
15
|
SubplotMatplotlib,
|
|
17
16
|
SubplotPlotly,
|
|
18
17
|
)
|
|
19
|
-
from rtctools_interface.
|
|
18
|
+
from rtctools_interface.utils.type_definitions import GoalConfig, IntermediateResult, PlotDataAndConfig
|
|
20
19
|
|
|
21
20
|
logger = logging.getLogger("rtctools")
|
|
22
21
|
|
|
@@ -71,9 +70,9 @@ def save_fig_as_html(fig, output_folder, priority, final_result) -> dict:
|
|
|
71
70
|
return fig
|
|
72
71
|
|
|
73
72
|
|
|
74
|
-
def get_goal(subplot_config,
|
|
73
|
+
def get_goal(subplot_config, base_goals) -> Union[GoalConfig, None]:
|
|
75
74
|
"""Find the goal belonging to a subplot. Only checks for goals as specified in the goal table."""
|
|
76
|
-
for goal in
|
|
75
|
+
for goal in base_goals:
|
|
77
76
|
if goal.get("goal_id") == subplot_config.id:
|
|
78
77
|
return goal
|
|
79
78
|
return None
|
|
@@ -145,7 +144,7 @@ def check_empty_plot_table(plot_config):
|
|
|
145
144
|
def get_main_title(final_result: bool, result_dict):
|
|
146
145
|
"""Generate main title."""
|
|
147
146
|
if final_result:
|
|
148
|
-
main_title = "
|
|
147
|
+
main_title = "Final results"
|
|
149
148
|
else:
|
|
150
149
|
main_title = "Results after optimizing until priority {}".format(result_dict["priority"])
|
|
151
150
|
return main_title
|
|
@@ -156,8 +155,8 @@ def create_matplotlib_figure(
|
|
|
156
155
|
) -> Union[StringIO, matplotlib.figure.Figure]:
|
|
157
156
|
# pylint: disable=too-many-locals
|
|
158
157
|
"""Creates a figure with a subplot for each row in the plot_table."""
|
|
159
|
-
used_colors = []
|
|
160
|
-
results = result_dict["
|
|
158
|
+
used_colors: list = []
|
|
159
|
+
results = result_dict["timeseries_data"]
|
|
161
160
|
plot_config = current_run["plot_options"]["plot_config"]
|
|
162
161
|
plot_max_rows = current_run["plot_options"]["plot_max_rows"]
|
|
163
162
|
if check_empty_plot_table(plot_config):
|
|
@@ -171,12 +170,12 @@ def create_matplotlib_figure(
|
|
|
171
170
|
fig.suptitle(main_title, fontsize=14)
|
|
172
171
|
i_plot = -1
|
|
173
172
|
|
|
174
|
-
|
|
173
|
+
base_goals = current_run["prio_independent_data"]["base_goals"]
|
|
175
174
|
# Add subplot for each row in the plot_table
|
|
176
175
|
for subplot_config in plot_config:
|
|
177
176
|
i_plot += 1
|
|
178
177
|
axis = get_subplot_axis(i_plot, n_rows, n_cols, axs)
|
|
179
|
-
goal = get_goal(subplot_config,
|
|
178
|
+
goal = get_goal(subplot_config, base_goals)
|
|
180
179
|
subplot = SubplotMatplotlib(
|
|
181
180
|
axis,
|
|
182
181
|
subplot_config,
|
|
@@ -232,18 +231,18 @@ def create_plotly_figure(
|
|
|
232
231
|
n_rows = math.ceil(len(plot_config) / n_cols)
|
|
233
232
|
i_plot = -1
|
|
234
233
|
plotly_figure = make_subplots(rows=n_rows, cols=n_cols, subplot_titles=len(plot_config) * [" "], shared_xaxes=True)
|
|
235
|
-
|
|
234
|
+
base_goals = current_run["prio_independent_data"]["base_goals"]
|
|
236
235
|
|
|
237
236
|
# Add subplot for each row in the plot_table
|
|
238
|
-
used_colors = []
|
|
237
|
+
used_colors: list = []
|
|
239
238
|
for subplot_config in plot_config:
|
|
240
239
|
i_plot += 1
|
|
241
240
|
i_c, i_r = get_row_col_number(i_plot, n_rows, n_cols, row_first=True)
|
|
242
|
-
goal = get_goal(subplot_config,
|
|
241
|
+
goal = get_goal(subplot_config, base_goals)
|
|
243
242
|
subplot = SubplotPlotly(
|
|
244
243
|
subplot_config,
|
|
245
244
|
goal,
|
|
246
|
-
result_dict["
|
|
245
|
+
result_dict["timeseries_data"],
|
|
247
246
|
results_prev,
|
|
248
247
|
current_run["prio_independent_data"],
|
|
249
248
|
used_colors,
|