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.
Files changed (52) hide show
  1. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/PKG-INFO +1 -1
  2. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/README.md +16 -12
  3. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/PKG-INFO +1 -1
  4. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/SOURCES.txt +19 -10
  5. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/_version.py +3 -3
  6. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/base_goal.py +15 -8
  7. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_generator_mixin.py +8 -17
  8. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_performance_metrics.py +13 -9
  9. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/goal_table_schema.py +1 -1
  10. rtc-tools-interface-0.7.0/rtctools_interface/optimization/helpers/statistics_mixin.py +68 -0
  11. rtc-tools-interface-0.7.0/rtctools_interface/optimization/plot_goals_mixin.py +14 -0
  12. rtc-tools-interface-0.7.0/rtctools_interface/optimization/plot_mixin.py +46 -0
  13. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/read_goals.py +2 -2
  14. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/plot_tools.py +13 -14
  15. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/subplot_classes.py +39 -43
  16. rtc-tools-interface-0.7.0/rtctools_interface/simulation/base_simulation_problem.py +31 -0
  17. rtc-tools-interface-0.7.0/rtctools_interface/simulation/plot_mixin.py +27 -0
  18. rtc-tools-interface-0.7.0/rtctools_interface/utils/read_goals_mixin.py +31 -0
  19. rtc-tools-interface-0.7.0/rtctools_interface/utils/read_plot_table.py +48 -0
  20. rtc-tools-interface-0.7.0/rtctools_interface/utils/results_collection.py +188 -0
  21. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/helpers → rtc-tools-interface-0.7.0/rtctools_interface/utils}/serialization.py +11 -24
  22. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface/utils}/type_definitions.py +8 -21
  23. rtc-tools-interface-0.7.0/tests/optimization/__init__.py +0 -0
  24. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_base_optimization_problem.py +2 -2
  25. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_passing_goals_directly.py +2 -2
  26. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_plot_goals_mixin.py +5 -5
  27. rtc-tools-interface-0.7.0/tests/simulation/__init__.py +0 -0
  28. rtc-tools-interface-0.7.0/tests/simulation/test_base_simulation_problem.py +27 -0
  29. rtc-tools-interface-0.7.0/tests/simulation/test_plot_mixin.py +54 -0
  30. rtc-tools-interface-0.7.0/tests/utils/__init__.py +0 -0
  31. {rtc-tools-interface-0.6.0b2/tests/optimization → rtc-tools-interface-0.7.0/tests/utils}/get_test.py +9 -6
  32. rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/helpers/statistics_mixin.py +0 -80
  33. rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/plot_and_goal_schema.py +0 -32
  34. rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/plot_goals_mixin.py +0 -179
  35. rtc-tools-interface-0.6.0b2/rtctools_interface/optimization/read_plot_table.py +0 -102
  36. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/COPYING.LESSER +0 -0
  37. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/dependency_links.txt +0 -0
  38. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/requires.txt +0 -0
  39. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtc_tools_interface.egg-info/top_level.txt +0 -0
  40. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/__init__.py +0 -0
  41. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/__init__.py +0 -0
  42. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/base_optimization_problem.py +0 -0
  43. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/rtctools_interface/optimization/helpers/__init__.py +0 -0
  44. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface}/plotting/__init__.py +0 -0
  45. {rtc-tools-interface-0.6.0b2/rtctools_interface/utils → rtc-tools-interface-0.7.0/rtctools_interface/simulation}/__init__.py +0 -0
  46. {rtc-tools-interface-0.6.0b2/tests → rtc-tools-interface-0.7.0/rtctools_interface/utils}/__init__.py +0 -0
  47. {rtc-tools-interface-0.6.0b2/rtctools_interface/optimization → rtc-tools-interface-0.7.0/rtctools_interface/utils}/plot_table_schema.py +0 -0
  48. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/setup.cfg +0 -0
  49. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/setup.py +0 -0
  50. {rtc-tools-interface-0.6.0b2/tests/optimization → rtc-tools-interface-0.7.0/tests}/__init__.py +0 -0
  51. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/tests/optimization/test_read_goals.py +0 -0
  52. {rtc-tools-interface-0.6.0b2 → rtc-tools-interface-0.7.0}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rtc-tools-interface
3
- Version: 0.6.0b2
3
+ Version: 0.7.0
4
4
  Summary: Toolbox for user interfaces for RTC-Tools
5
5
  Home-page: UNKNOWN
6
6
  Author: Deltares
@@ -110,28 +110,32 @@ The calculated metrics are:
110
110
 
111
111
 
112
112
  ## Automatic plotting of results
113
- With the `PlotGoalsMixin` one can easily make plots of the results of rtc-tools. To utilize this functionality, import the mixin as follows:
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.plot_goals_mixin import PlotGoalsMixin
115
+ from rtctools_interface.optimization.plot_mixin import PlotMixin
116
116
  ```
117
- Then, add the `PlotGoalsMixin` to your optimization problem class. The PlotGoalsMixin can create a plot after each priority and/or a plot with the final results only.
118
- By default, the `PlotGoalsMixin` 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.
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 PlotGoalsMixin 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.
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
- - 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.
124
- - The `final_results` plot will show the result from the **previous optimization run**. This allows for comparing results from different scenario's (like input timeseries or differently parametrized goals). 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.
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 PlotGoalsMixin:
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 PlotGoalsMixin
133
- 1. Plots based on goals in the goal_generator table
134
- 2. Plots of arbitrary states, for example ones being optimized in a goal defined in Python.
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rtc-tools-interface
3
- Version: 0.6.0b2
3
+ Version: 0.7.0
4
4
  Summary: Toolbox for user interfaces for RTC-Tools
5
5
  Home-page: UNKNOWN
6
6
  Author: Deltares
@@ -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/plot_table_schema.py
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/optimization/plotting/__init__.py
29
- rtctools_interface/optimization/plotting/plot_tools.py
30
- rtctools_interface/optimization/plotting/subplot_classes.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
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-13T16:01:56+0100",
11
+ "date": "2023-12-19T14:41:26+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "3f4ef7c3896bbc5f0b95b5e15be2a69e56f9c03a",
15
- "version": "0.6.0b2"
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.optimization.type_definitions import GoalConfig
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.optimization.read_goals import read_goals
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
- self.goals_to_generate = kwargs.get("goals_to_generate", [])
38
- self.read_from = kwargs.get("read_goals_from", "csv_table")
39
- if not hasattr(self, "goal_table_file"):
40
- self.goal_table_file = os.path.join(self._input_folder, "goal_table.csv")
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
- targets = self.collect_range_target_values(goal_generator_goals)
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.optimization.type_definitions import TargetDict
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 = sum(np.abs(np.where(timeseries < targets["target_min"], timeseries - targets["target_min"], 0)))
86
- above_target = sum(np.abs(np.where(timeseries > targets["target_max"], timeseries - targets["target_max"], 0)))
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)
@@ -103,7 +103,7 @@ PATH_GOALS = {
103
103
  "range": RangeGoalModel,
104
104
  "range_rate_of_change": RangeRateOfChangeGoalModel,
105
105
  }
106
- NON_PATH_GOALS = {}
106
+ NON_PATH_GOALS: dict = {}
107
107
  GOAL_TYPES = PATH_GOALS | NON_PATH_GOALS
108
108
  TARGET_DATA_TYPES = [
109
109
  "value",
@@ -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 plot_config in goals_to_generate:
58
- if not isinstance(plot_config, BaseGoalModel):
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.optimization.base_goal import BaseGoal
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.optimization.type_definitions import IntermediateResult, PlotDataAndConfig
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, all_goals) -> Union[BaseGoal, None]:
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 all_goals:
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 = "Result after optimizing for all priorities"
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["extract_result"]
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
- all_goals = current_run["prio_independent_data"]["all_goals"]
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, all_goals)
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
- all_goals = current_run["prio_independent_data"]["all_goals"]
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, all_goals)
241
+ goal = get_goal(subplot_config, base_goals)
243
242
  subplot = SubplotPlotly(
244
243
  subplot_config,
245
244
  goal,
246
- result_dict["extract_result"],
245
+ result_dict["timeseries_data"],
247
246
  results_prev,
248
247
  current_run["prio_independent_data"],
249
248
  used_colors,