rtc-tools-interface 0.9.0a1__tar.gz → 0.9.0a2__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.9.0a1 → rtc-tools-interface-0.9.0a2}/PKG-INFO +1 -1
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/PKG-INFO +1 -1
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/requires.txt +3 -3
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/_version.py +3 -3
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/results_construction.py +17 -4
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/runner.py +3 -2
- rtc-tools-interface-0.9.0a2/rtctools_interface/closed_loop/time_series_handler.py +234 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/base_goal.py +1 -1
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/setup.py +8 -1
- rtc-tools-interface-0.9.0a1/rtctools_interface/closed_loop/time_series_handler.py +0 -449
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/COPYING.LESSER +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/README.md +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/SOURCES.txt +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/dependency_links.txt +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/top_level.txt +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/config.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/optimization_ranges.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/base_optimization_problem.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/goal_generator_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/goal_performance_metrics.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/goal_table_schema.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/helpers/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/helpers/statistics_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/plot_goals_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/plot_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/optimization/read_goals.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/plotting/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/plotting/plot_tools.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/plotting/subplot_classes.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/simulation/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/simulation/base_simulation_problem.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/simulation/plot_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/plot_table_schema.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/read_goals_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/read_plot_table.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/results_collection.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/serialization.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/type_definitions.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/setup.cfg +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/closed_loop/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/closed_loop/test_optimization_ranges.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/closed_loop/test_read_xml.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/closed_loop/test_run_optization_problem_closed_loop.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/test_base_optimization_problem.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/test_passing_goals_directly.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/test_plot_goals_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/test_read_goals.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/simulation/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/simulation/test_base_simulation_problem.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/simulation/test_plot_mixin.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/utils/__init__.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/utils/get_test.py +0 -0
- {rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/versioneer.py +0 -0
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-08-01T10:56:38+0200",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.9.
|
|
14
|
+
"full-revisionid": "ef695c81333aa6aca2b0d62cc2b86ff937294803",
|
|
15
|
+
"version": "0.9.0a2"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -10,6 +10,18 @@ from rtctools.data import pi
|
|
|
10
10
|
logger = logging.getLogger("rtctools")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _get_variables_from_pi(data_config: rtc.DataConfig, timeseries: pi.Timeseries):
|
|
14
|
+
"""Get all variables of a PI timeseries that are in the data configuration."""
|
|
15
|
+
variables = []
|
|
16
|
+
for var, _ in timeseries.items():
|
|
17
|
+
try:
|
|
18
|
+
data_config.pi_variable_ids(var)
|
|
19
|
+
variables.append(var)
|
|
20
|
+
except KeyError:
|
|
21
|
+
pass
|
|
22
|
+
return variables
|
|
23
|
+
|
|
24
|
+
|
|
13
25
|
def combine_xml_exports(output_base_path: Path, original_input_timeseries_path: Path, write_csv_out: bool = False):
|
|
14
26
|
"""Combine the xml exports of multiple periods into a single xml file."""
|
|
15
27
|
logger.info("Combining XML exports.")
|
|
@@ -24,9 +36,10 @@ def combine_xml_exports(output_base_path: Path, original_input_timeseries_path:
|
|
|
24
36
|
ts_export = pi.Timeseries(
|
|
25
37
|
data_config=dataconfig, folder=output_base_path / "period_0", basename="timeseries_export", binary=False
|
|
26
38
|
) # Use the first timeseries export as a starting point for the combined timeseries export.
|
|
27
|
-
ts_export._Timeseries__path_xml = os.path.join(output_base_path.parent, "timeseries_export.xml")
|
|
28
39
|
ts_export.resize(orig_start_datetime, orig_end_datetime)
|
|
29
40
|
|
|
41
|
+
variables = _get_variables_from_pi(data_config=dataconfig, timeseries=ts_export)
|
|
42
|
+
|
|
30
43
|
i = 0
|
|
31
44
|
while os.path.isfile(os.path.join(output_base_path, f"period_{i}", "timeseries_export.xml")):
|
|
32
45
|
ts_export_step = pi.Timeseries(
|
|
@@ -38,7 +51,7 @@ def combine_xml_exports(output_base_path: Path, original_input_timeseries_path:
|
|
|
38
51
|
all_times = ts_import_orig.times # Workaround to map indices to times, as ts_export does
|
|
39
52
|
# not contain all times. TODO Check whether the assumption that these times map to
|
|
40
53
|
# the correct indices for ts_export always holds.
|
|
41
|
-
for loc_par in
|
|
54
|
+
for loc_par in variables:
|
|
42
55
|
try:
|
|
43
56
|
current_values = ts_export.get(loc_par)
|
|
44
57
|
new_values = ts_export_step.get(loc_par)
|
|
@@ -60,12 +73,12 @@ def combine_xml_exports(output_base_path: Path, original_input_timeseries_path:
|
|
|
60
73
|
combined_values[start_new_data_index : start_new_data_index + len(new_values)] = new_values # noqa
|
|
61
74
|
ts_export.set(loc_par, combined_values)
|
|
62
75
|
i += 1
|
|
63
|
-
ts_export.write()
|
|
76
|
+
ts_export.write(output_folder=output_base_path.parent, output_filename="timeseries_export")
|
|
64
77
|
|
|
65
78
|
if write_csv_out:
|
|
66
79
|
data = pd.DataFrame()
|
|
67
80
|
data["date"] = all_times
|
|
68
|
-
for timeseries_id in
|
|
81
|
+
for timeseries_id in variables:
|
|
69
82
|
try:
|
|
70
83
|
values = ts_export.get(timeseries_id)
|
|
71
84
|
except KeyError:
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/runner.py
RENAMED
|
@@ -115,10 +115,10 @@ def run_optimization_problem_closed_loop(
|
|
|
115
115
|
if not any((isinstance(h, DiagHandler) for h in logger.handlers)):
|
|
116
116
|
diag_handler = DiagHandler(original_output_folder)
|
|
117
117
|
logger.addHandler(diag_handler)
|
|
118
|
+
logger.setLevel(log_level)
|
|
118
119
|
|
|
119
120
|
if issubclass(optimization_problem_class, PIMixin):
|
|
120
121
|
original_import = XMLTimeSeriesFile(original_input_folder)
|
|
121
|
-
original_import.set_reference_data(copy.deepcopy(original_import))
|
|
122
122
|
elif issubclass(optimization_problem_class, CSVMixin):
|
|
123
123
|
original_import = CSVTimeSeriesFile(original_input_folder)
|
|
124
124
|
else:
|
|
@@ -154,6 +154,7 @@ def run_optimization_problem_closed_loop(
|
|
|
154
154
|
modelling_period_input_folder_i = modelling_period_input_folder / modelling_period_name
|
|
155
155
|
write_input_folder(modelling_period_input_folder_i, original_input_folder, timeseries_import)
|
|
156
156
|
|
|
157
|
+
logger.info(f"Run optimization for period {i}: {(str(start_time), str(end_time))}.")
|
|
157
158
|
result = run_optimization_problem(
|
|
158
159
|
optimization_problem_class,
|
|
159
160
|
base_folder,
|
|
@@ -163,7 +164,7 @@ def run_optimization_problem_closed_loop(
|
|
|
163
164
|
output_folder=modelling_period_output_folder_i,
|
|
164
165
|
**kwargs,
|
|
165
166
|
)
|
|
166
|
-
logger.info(f"Finished optimization
|
|
167
|
+
logger.info(f"Finished optimization for period {i}: {(str(start_time), str(end_time))}.")
|
|
167
168
|
|
|
168
169
|
results_previous_run = {key: result.extract_results().get(key) for key in variables_in_import}
|
|
169
170
|
previous_run_datetimes = result.io.datetimes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
import datetime
|
|
3
|
+
import logging
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from rtctools.data import pi
|
|
12
|
+
from rtctools.data import rtc
|
|
13
|
+
from rtctools.data import csv
|
|
14
|
+
|
|
15
|
+
ns = {"fews": "http://www.wldelft.nl/fews", "pi": "http://www.wldelft.nl/fews/PI"}
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("rtctools")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TimeSeriesHandler(ABC):
|
|
21
|
+
"""ABC for handling timeseries data."""
|
|
22
|
+
|
|
23
|
+
# The forecast date determines at which date the optimization starts.
|
|
24
|
+
forecast_date: Optional[datetime.datetime] = None
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def read(self, file_name: str) -> None:
|
|
28
|
+
"""Read the timeseries."""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime) -> None:
|
|
32
|
+
"""Select a time range from the timeseries data. Removes data outside the interval.
|
|
33
|
+
The specified range is inclusive on both sides."""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def write(self, file_path: Path) -> None:
|
|
37
|
+
"""Write the timeseries data to a file."""
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def get_timestep(self) -> datetime.timedelta:
|
|
41
|
+
"""Get the timestep of the timeseries data."""
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def get_datetimes(self) -> List[datetime.datetime]:
|
|
45
|
+
"""Get the dates of the timeseries."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def get_datetime_range(self) -> Tuple[datetime.datetime, datetime.datetime]:
|
|
49
|
+
"""Get the date range of the timeseries data (min, max)."""
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def get_all_internal_ids(self) -> List[str]:
|
|
53
|
+
"""Get all internal id's of the timeseries data."""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def set_initial_value(self, internal_id: str, value: float) -> None:
|
|
57
|
+
"""Set the initial value of a variable in the timeseries data."""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def is_set(self, internal_id: str) -> bool:
|
|
61
|
+
"""Check whether the variable exists in the timeseries data and whether it has a least one non-nan value"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CSVTimeSeriesFile(TimeSeriesHandler):
|
|
65
|
+
"""Timeseries handler for csv files."""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
input_folder: Path,
|
|
70
|
+
timeseries_import_basename: str = "timeseries_import",
|
|
71
|
+
csv_delimiter=",",
|
|
72
|
+
initial_state_base_name: str = "initial_state",
|
|
73
|
+
):
|
|
74
|
+
self.data = pd.DataFrame()
|
|
75
|
+
self.input_folder = input_folder
|
|
76
|
+
self.csv_delimiter = csv_delimiter
|
|
77
|
+
self.data_col = None
|
|
78
|
+
self.initial_state = None
|
|
79
|
+
self.read(timeseries_import_basename, initial_state_base_name)
|
|
80
|
+
|
|
81
|
+
def read(self, file_name: str, initial_state_base_name=None):
|
|
82
|
+
timeseries = csv.load(
|
|
83
|
+
(self.input_folder / file_name).with_suffix(".csv"),
|
|
84
|
+
delimiter=self.csv_delimiter,
|
|
85
|
+
with_time=True,
|
|
86
|
+
)
|
|
87
|
+
self.data = pd.DataFrame(timeseries)
|
|
88
|
+
if self.data is not None:
|
|
89
|
+
self.date_col = self.data.columns[0]
|
|
90
|
+
self.forecast_date = self.data[self.date_col].iloc[0]
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError("No data to read.")
|
|
93
|
+
if initial_state_base_name is not None:
|
|
94
|
+
initial_state_file = self.input_folder / initial_state_base_name
|
|
95
|
+
if initial_state_file.with_suffix(".csv").exists():
|
|
96
|
+
initial_state = csv.load(
|
|
97
|
+
initial_state_file.with_suffix(".csv"),
|
|
98
|
+
delimiter=self.csv_delimiter,
|
|
99
|
+
with_time=False,
|
|
100
|
+
)
|
|
101
|
+
self.initial_state: Optional[dict] = {
|
|
102
|
+
field: float(initial_state[field]) for field in initial_state.dtype.names
|
|
103
|
+
}
|
|
104
|
+
else:
|
|
105
|
+
self.initial_state = None
|
|
106
|
+
|
|
107
|
+
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime):
|
|
108
|
+
mask = (self.data[self.date_col] >= start_date) & (self.data[self.date_col] <= end_date)
|
|
109
|
+
self.data = self.data.loc[mask]
|
|
110
|
+
self.forecast_date = start_date
|
|
111
|
+
|
|
112
|
+
def write(self, file_path: Path):
|
|
113
|
+
self.write_timeseries(file_path)
|
|
114
|
+
self.write_initial_state(file_path)
|
|
115
|
+
|
|
116
|
+
def write_timeseries(self, file_path: Path, file_name: str = "timeseries_import"):
|
|
117
|
+
self.data.to_csv(
|
|
118
|
+
(file_path / file_name).with_suffix(".csv"),
|
|
119
|
+
index=False,
|
|
120
|
+
date_format="%Y-%m-%d %H:%M:%S",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def write_initial_state(self, file_path: Path, file_name: str = "initial_state"):
|
|
124
|
+
if self.initial_state is not None:
|
|
125
|
+
initial_state = pd.DataFrame(self.initial_state, index=[0])
|
|
126
|
+
initial_state.to_csv((file_path / file_name).with_suffix(".csv"), header=True, index=False)
|
|
127
|
+
|
|
128
|
+
def get_timestep(self):
|
|
129
|
+
return self.data[self.date_col].diff().min()
|
|
130
|
+
|
|
131
|
+
def get_datetimes(self):
|
|
132
|
+
return self.data[self.date_col].to_list()
|
|
133
|
+
|
|
134
|
+
def get_datetime_range(self):
|
|
135
|
+
return self.data[self.date_col].min(), self.data[self.date_col].max()
|
|
136
|
+
|
|
137
|
+
def get_all_internal_ids(self):
|
|
138
|
+
ids = list(self.data.columns[1:])
|
|
139
|
+
if self.initial_state is not None:
|
|
140
|
+
ids.extend(list(self.initial_state.keys()))
|
|
141
|
+
return ids
|
|
142
|
+
|
|
143
|
+
def set_initial_value(self, internal_id, value):
|
|
144
|
+
if self.initial_state is None or internal_id not in self.initial_state:
|
|
145
|
+
self.data[internal_id].iloc[0] = value
|
|
146
|
+
else:
|
|
147
|
+
self.initial_state[internal_id] = value
|
|
148
|
+
|
|
149
|
+
def is_set(self, internal_id):
|
|
150
|
+
val_is_set = False
|
|
151
|
+
if internal_id in self.data.columns:
|
|
152
|
+
val_is_set = not self.data[internal_id].isna().all()
|
|
153
|
+
if self.initial_state is not None and internal_id in self.initial_state:
|
|
154
|
+
val_is_set = False
|
|
155
|
+
return val_is_set
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class XMLTimeSeriesFile(TimeSeriesHandler):
|
|
159
|
+
""" "Timeseries handler for xml files"""
|
|
160
|
+
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
input_folder: Path,
|
|
164
|
+
timeseries_import_basename: str = "timeseries_import",
|
|
165
|
+
):
|
|
166
|
+
self.input_folder = input_folder
|
|
167
|
+
self.pi_binary_timeseries = False
|
|
168
|
+
self.pi_validate_timeseries = True
|
|
169
|
+
self.data_config = None
|
|
170
|
+
self.pi_timeseries = None
|
|
171
|
+
self.read(timeseries_import_basename)
|
|
172
|
+
|
|
173
|
+
def read(self, file_name: str):
|
|
174
|
+
"""Read the timeseries data from a file."""
|
|
175
|
+
timeseries_import_basename = file_name
|
|
176
|
+
self.data_config = rtc.DataConfig(self.input_folder)
|
|
177
|
+
self.pi_timeseries = pi.Timeseries(
|
|
178
|
+
self.data_config,
|
|
179
|
+
self.input_folder,
|
|
180
|
+
timeseries_import_basename,
|
|
181
|
+
binary=self.pi_binary_timeseries,
|
|
182
|
+
pi_validate_times=self.pi_validate_timeseries,
|
|
183
|
+
)
|
|
184
|
+
self.forecast_date = self.pi_timeseries.forecast_datetime
|
|
185
|
+
|
|
186
|
+
def is_set(self, internal_id):
|
|
187
|
+
"""Check whether the variable exists in the timeseries data and whether it has a value at at least
|
|
188
|
+
one of time steps."""
|
|
189
|
+
try:
|
|
190
|
+
var: np.ndarray = self.pi_timeseries.get(variable=internal_id)
|
|
191
|
+
return not np.isnan(var).all()
|
|
192
|
+
except KeyError:
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def _is_in_dataconfig(self, internal_id):
|
|
196
|
+
"""Check if an internal id is in the data configuration."""
|
|
197
|
+
try:
|
|
198
|
+
self.data_config.pi_variable_ids(internal_id)
|
|
199
|
+
return True
|
|
200
|
+
except KeyError:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def get_all_internal_ids(self):
|
|
204
|
+
"""Get all internal id's of the timeseries data. Only returns the id's that are also in the dataconfig."""
|
|
205
|
+
all_ids = [var for var, _ in self.pi_timeseries.items() if self._is_in_dataconfig(var)]
|
|
206
|
+
return all_ids
|
|
207
|
+
|
|
208
|
+
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime):
|
|
209
|
+
times = self.pi_timeseries.times
|
|
210
|
+
i_start = bisect.bisect_left(times, start_date)
|
|
211
|
+
i_end = bisect.bisect_right(times, end_date) - 1
|
|
212
|
+
self.pi_timeseries.resize(start_datetime=times[i_start], end_datetime=times[i_end])
|
|
213
|
+
self.pi_timeseries.times = times[i_start:i_end + 1]
|
|
214
|
+
self.pi_timeseries.forecast_datetime = times[i_start]
|
|
215
|
+
self.forecast_date = times[i_start]
|
|
216
|
+
|
|
217
|
+
def write(self, file_path: Path, file_name: str = "timeseries_import"):
|
|
218
|
+
self.pi_timeseries.write(output_folder=file_path, output_filename=file_name)
|
|
219
|
+
|
|
220
|
+
def get_datetimes(self):
|
|
221
|
+
"""Get the dates of all timeseries data."""
|
|
222
|
+
return self.pi_timeseries.times
|
|
223
|
+
|
|
224
|
+
def get_datetime_range(self):
|
|
225
|
+
"""Get the date range of the timeseries data, minimum and maximum over all series"""
|
|
226
|
+
times = self.pi_timeseries.times
|
|
227
|
+
return times[0], times[-1]
|
|
228
|
+
|
|
229
|
+
def get_timestep(self):
|
|
230
|
+
"""Get the timestep of the timeseries data, raise error if different stepsizes"""
|
|
231
|
+
return self.pi_timeseries.dt
|
|
232
|
+
|
|
233
|
+
def set_initial_value(self, internal_id: str, value: float):
|
|
234
|
+
self.pi_timeseries.set(internal_id, [value])
|
|
@@ -147,7 +147,7 @@ class BaseGoal(Goal):
|
|
|
147
147
|
if not np.isfinite(self.function_nominal):
|
|
148
148
|
if isinstance(self.function_range, (list, tuple)):
|
|
149
149
|
if np.all(np.isfinite(self.function_range)):
|
|
150
|
-
self.function_nominal = abs(self.function_range[0]) + abs(self.function_range[1]) / 2
|
|
150
|
+
self.function_nominal = (abs(self.function_range[0]) + abs(self.function_range[1])) / 2
|
|
151
151
|
return
|
|
152
152
|
self.function_nominal = 1.0
|
|
153
153
|
logger.warning("Function nominal for goal with id '%s' not specified, nominal is set to 1.0", self.goal_id)
|
|
@@ -10,7 +10,14 @@ setup(
|
|
|
10
10
|
packages=find_packages("."),
|
|
11
11
|
author="Deltares",
|
|
12
12
|
description="Toolbox for user interfaces for RTC-Tools",
|
|
13
|
-
install_requires=[
|
|
13
|
+
install_requires=[
|
|
14
|
+
"matplotlib",
|
|
15
|
+
"numpy",
|
|
16
|
+
"pandas",
|
|
17
|
+
"plotly",
|
|
18
|
+
"pydantic",
|
|
19
|
+
"rtc-tools >= 2.7.0a3",
|
|
20
|
+
],
|
|
14
21
|
tests_require=["pytest", "pytest-runner"],
|
|
15
22
|
python_requires=">=3.9",
|
|
16
23
|
cmdclass=versioneer.get_cmdclass(),
|
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
import copy
|
|
2
|
-
import datetime
|
|
3
|
-
import logging
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
import xml.etree.ElementTree as ET
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import List, Optional, Tuple
|
|
8
|
-
import pandas as pd
|
|
9
|
-
from rtctools.data import pi
|
|
10
|
-
from rtctools.data import rtc
|
|
11
|
-
from rtctools.data import csv
|
|
12
|
-
|
|
13
|
-
ns = {"fews": "http://www.wldelft.nl/fews", "pi": "http://www.wldelft.nl/fews/PI"}
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger("rtctools")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class TimeSeriesHandler(ABC):
|
|
19
|
-
"""ABC for handling timeseries data."""
|
|
20
|
-
|
|
21
|
-
# The forecast date determines at which date the optimization starts.
|
|
22
|
-
forecast_date: Optional[datetime.datetime] = None
|
|
23
|
-
|
|
24
|
-
@abstractmethod
|
|
25
|
-
def read(self, file_name: str) -> None:
|
|
26
|
-
"""Read the timeseries."""
|
|
27
|
-
|
|
28
|
-
@abstractmethod
|
|
29
|
-
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime) -> None:
|
|
30
|
-
"""Select a time range from the timeseries data. Removes data outside the interval.
|
|
31
|
-
The specified range is inclusive on both sides."""
|
|
32
|
-
|
|
33
|
-
@abstractmethod
|
|
34
|
-
def write(self, file_path: Path) -> None:
|
|
35
|
-
"""Write the timeseries data to a file."""
|
|
36
|
-
|
|
37
|
-
def set_reference_data(self, reference_data: "TimeSeriesHandler"):
|
|
38
|
-
"""Set the reference TimeSeriesHandler. Only relevant for XMLTimeSeriesFile.
|
|
39
|
-
Required when setting initial values for variables that are not in the modelling period data range."""
|
|
40
|
-
|
|
41
|
-
@abstractmethod
|
|
42
|
-
def get_timestep(self) -> datetime.timedelta:
|
|
43
|
-
"""Get the timestep of the timeseries data."""
|
|
44
|
-
|
|
45
|
-
@abstractmethod
|
|
46
|
-
def get_datetimes(self) -> List[datetime.datetime]:
|
|
47
|
-
"""Get the dates of the timeseries."""
|
|
48
|
-
|
|
49
|
-
@abstractmethod
|
|
50
|
-
def get_datetime_range(self) -> Tuple[datetime.datetime, datetime.datetime]:
|
|
51
|
-
"""Get the date range of the timeseries data (min, max)."""
|
|
52
|
-
|
|
53
|
-
@abstractmethod
|
|
54
|
-
def get_all_internal_ids(self) -> List[str]:
|
|
55
|
-
"""Get all internal id's of the timeseries data."""
|
|
56
|
-
|
|
57
|
-
@abstractmethod
|
|
58
|
-
def set_initial_value(self, internal_id: str, value: float) -> None:
|
|
59
|
-
"""Set the initial value of a variable in the timeseries data."""
|
|
60
|
-
|
|
61
|
-
@abstractmethod
|
|
62
|
-
def is_set(self, internal_id: str) -> bool:
|
|
63
|
-
"""Check whether the variable exists in the timeseries data and whether it has a least one non-nan value"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class CSVTimeSeriesFile(TimeSeriesHandler):
|
|
67
|
-
"""Timeseries handler for csv files."""
|
|
68
|
-
|
|
69
|
-
def __init__(
|
|
70
|
-
self,
|
|
71
|
-
input_folder: Path,
|
|
72
|
-
timeseries_import_basename: str = "timeseries_import",
|
|
73
|
-
csv_delimiter=",",
|
|
74
|
-
initial_state_base_name: str = "initial_state",
|
|
75
|
-
):
|
|
76
|
-
self.data = pd.DataFrame()
|
|
77
|
-
self.input_folder = input_folder
|
|
78
|
-
self.csv_delimiter = csv_delimiter
|
|
79
|
-
self.read(timeseries_import_basename, initial_state_base_name)
|
|
80
|
-
|
|
81
|
-
def read(self, file_name: str, initial_state_base_name=None):
|
|
82
|
-
timeseries = csv.load(
|
|
83
|
-
(self.input_folder / file_name).with_suffix(".csv"),
|
|
84
|
-
delimiter=self.csv_delimiter,
|
|
85
|
-
with_time=True,
|
|
86
|
-
)
|
|
87
|
-
self.data = pd.DataFrame(timeseries)
|
|
88
|
-
if self.data is not None:
|
|
89
|
-
self.date_col = self.data.columns[0]
|
|
90
|
-
self.forecast_date = self.data[self.date_col].iloc[0]
|
|
91
|
-
else:
|
|
92
|
-
raise ValueError("No data to read.")
|
|
93
|
-
if initial_state_base_name is not None:
|
|
94
|
-
initial_state_file = self.input_folder / initial_state_base_name
|
|
95
|
-
if initial_state_file.with_suffix(".csv").exists():
|
|
96
|
-
initial_state = csv.load(
|
|
97
|
-
initial_state_file.with_suffix(".csv"),
|
|
98
|
-
delimiter=self.csv_delimiter,
|
|
99
|
-
with_time=False,
|
|
100
|
-
)
|
|
101
|
-
self.initial_state: Optional[dict] = {
|
|
102
|
-
field: float(initial_state[field]) for field in initial_state.dtype.names
|
|
103
|
-
}
|
|
104
|
-
else:
|
|
105
|
-
self.initial_state = None
|
|
106
|
-
|
|
107
|
-
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime):
|
|
108
|
-
mask = (self.data[self.date_col] >= start_date) & (self.data[self.date_col] <= end_date)
|
|
109
|
-
self.data = self.data.loc[mask]
|
|
110
|
-
self.forecast_date = start_date
|
|
111
|
-
|
|
112
|
-
def write(self, file_path: Path):
|
|
113
|
-
self.write_timeseries(file_path)
|
|
114
|
-
self.write_initial_state(file_path)
|
|
115
|
-
|
|
116
|
-
def write_timeseries(self, file_path: Path, file_name: str = "timeseries_import"):
|
|
117
|
-
self.data.to_csv(
|
|
118
|
-
(file_path / file_name).with_suffix(".csv"),
|
|
119
|
-
index=False,
|
|
120
|
-
date_format="%Y-%m-%d %H:%M:%S",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
def write_initial_state(self, file_path: Path, file_name: str = "initial_state"):
|
|
124
|
-
if self.initial_state is not None:
|
|
125
|
-
initial_state = pd.DataFrame(self.initial_state, index=[0])
|
|
126
|
-
initial_state.to_csv((file_path / file_name).with_suffix(".csv"), header=True, index=False)
|
|
127
|
-
|
|
128
|
-
def get_timestep(self):
|
|
129
|
-
return self.data[self.date_col].diff().min()
|
|
130
|
-
|
|
131
|
-
def get_datetimes(self):
|
|
132
|
-
return self.data[self.date_col].to_list()
|
|
133
|
-
|
|
134
|
-
def get_datetime_range(self):
|
|
135
|
-
return self.data[self.date_col].min(), self.data[self.date_col].max()
|
|
136
|
-
|
|
137
|
-
def get_all_internal_ids(self):
|
|
138
|
-
ids = list(self.data.columns[1:])
|
|
139
|
-
if self.initial_state is not None:
|
|
140
|
-
ids.extend(list(self.initial_state.keys()))
|
|
141
|
-
return ids
|
|
142
|
-
|
|
143
|
-
def set_initial_value(self, internal_id, value):
|
|
144
|
-
if self.initial_state is None or internal_id not in self.initial_state:
|
|
145
|
-
self.data[internal_id].iloc[0] = value
|
|
146
|
-
else:
|
|
147
|
-
self.initial_state[internal_id] = value
|
|
148
|
-
|
|
149
|
-
def is_set(self, internal_id):
|
|
150
|
-
val_is_set = False
|
|
151
|
-
if internal_id in self.data.columns:
|
|
152
|
-
val_is_set = not self.data[internal_id].isna().all()
|
|
153
|
-
if self.initial_state is not None and internal_id in self.initial_state:
|
|
154
|
-
val_is_set = False
|
|
155
|
-
return val_is_set
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
class XMLTimeSeriesFile(TimeSeriesHandler):
|
|
159
|
-
""" "Timeseries handler for xml files"""
|
|
160
|
-
|
|
161
|
-
# Whether the timeseries data has a forecast date in the header.
|
|
162
|
-
forecast_date_in_header = False
|
|
163
|
-
|
|
164
|
-
def __parse_date_time(self, el):
|
|
165
|
-
return datetime.datetime.strptime(el.get("date") + " " + el.get("time"), "%Y-%m-%d %H:%M:%S")
|
|
166
|
-
|
|
167
|
-
def __init__(
|
|
168
|
-
self,
|
|
169
|
-
input_folder: Path,
|
|
170
|
-
timeseries_import_basename: str = "timeseries_import",
|
|
171
|
-
):
|
|
172
|
-
self.input_folder = input_folder
|
|
173
|
-
self.pi_binary_timeseries = False
|
|
174
|
-
self.pi_validate_timeseries = True
|
|
175
|
-
self.read(timeseries_import_basename)
|
|
176
|
-
|
|
177
|
-
def read(self, file_name: str):
|
|
178
|
-
"""Read the timeseries data from a file."""
|
|
179
|
-
timeseries_import_basename = file_name
|
|
180
|
-
self.data_config = rtc.DataConfig(self.input_folder)
|
|
181
|
-
self.pi_timeseries = pi.Timeseries(
|
|
182
|
-
self.data_config,
|
|
183
|
-
self.input_folder,
|
|
184
|
-
timeseries_import_basename,
|
|
185
|
-
binary=self.pi_binary_timeseries,
|
|
186
|
-
pi_validate_times=self.pi_validate_timeseries,
|
|
187
|
-
)
|
|
188
|
-
tree = self.pi_timeseries._Timeseries__tree
|
|
189
|
-
self.root = tree.getroot()
|
|
190
|
-
if self.root is None:
|
|
191
|
-
raise ValueError("No data to read.")
|
|
192
|
-
self.set_forecast_date()
|
|
193
|
-
|
|
194
|
-
def set_forecast_date(self):
|
|
195
|
-
"""Set the internal attribute `forecast_date` of the timeseries data.
|
|
196
|
-
|
|
197
|
-
The forecast date is set to the first event date of the first series
|
|
198
|
-
if no forecast date is present in the header."""
|
|
199
|
-
first_series = self.root.find("pi:series", ns)
|
|
200
|
-
first_header = first_series.find("pi:header", ns)
|
|
201
|
-
forecast_date_element = first_header.find("pi:forecastDate", ns)
|
|
202
|
-
if forecast_date_element is not None:
|
|
203
|
-
self.forecast_date = self.__parse_date_time(forecast_date_element)
|
|
204
|
-
self.forecast_date_in_header = True
|
|
205
|
-
else:
|
|
206
|
-
first_event = first_series.find("pi:event", ns)
|
|
207
|
-
self.forecast_date = self.__parse_date_time(first_event)
|
|
208
|
-
self.forecast_date_in_header = False
|
|
209
|
-
|
|
210
|
-
def is_set(self, internal_id):
|
|
211
|
-
"""Check whether the variable exists in the timeseries data and whether it has a value at at least
|
|
212
|
-
one of time steps."""
|
|
213
|
-
location_id, parameter_id, qualifier_id = self.get_external_id_from_internal_id(internal_id)
|
|
214
|
-
for series in self.root.findall("pi:series", ns):
|
|
215
|
-
if (
|
|
216
|
-
location_id,
|
|
217
|
-
parameter_id,
|
|
218
|
-
qualifier_id,
|
|
219
|
-
) == self.get_external_id_from_series(series):
|
|
220
|
-
events = series.findall("pi:event", ns)
|
|
221
|
-
for event in events:
|
|
222
|
-
if event.get("value") is not None:
|
|
223
|
-
return True
|
|
224
|
-
return False
|
|
225
|
-
|
|
226
|
-
def get_internal_id(self, series):
|
|
227
|
-
"""Get the internal id of a series element."""
|
|
228
|
-
pi_header = series.find("pi:header", ns)
|
|
229
|
-
return self.data_config.variable(pi_header)
|
|
230
|
-
|
|
231
|
-
def get_external_id_from_internal_id(self, internal_id):
|
|
232
|
-
"""Get the external id of a series element. Returns a tuple with three elements: location, parameter
|
|
233
|
-
and qualifier ID's."""
|
|
234
|
-
return self.data_config.pi_variable_ids(internal_id)
|
|
235
|
-
|
|
236
|
-
def get_all_internal_ids(self):
|
|
237
|
-
"""Get all internal id's of the timeseries data. Only returns the id's that are also in the dataconfig."""
|
|
238
|
-
all_ids = [self.get_internal_id(series) for series in self.root.findall("pi:series", ns)]
|
|
239
|
-
return [id for id in all_ids if ":" not in id] # Variables that contain ":" are not in the dataconfig.
|
|
240
|
-
|
|
241
|
-
def set_new_forecast_date(self, forecast_date: datetime.datetime):
|
|
242
|
-
for series in self.root.findall("pi:series", ns):
|
|
243
|
-
header = series.find("pi:header", ns)
|
|
244
|
-
start_date, end_date = self.get_single_date_range_from_series(series)
|
|
245
|
-
if start_date <= forecast_date <= end_date:
|
|
246
|
-
header.find("pi:forecastDate", ns).attrib = {
|
|
247
|
-
"date": forecast_date.strftime("%Y-%m-%d"),
|
|
248
|
-
"time": forecast_date.strftime("%H:%M:%S"),
|
|
249
|
-
}
|
|
250
|
-
else:
|
|
251
|
-
raise ValueError("Forecast date is not within the date range of the timeseries data.")
|
|
252
|
-
|
|
253
|
-
def select_time_range(self, start_date: datetime.datetime, end_date: datetime.datetime):
|
|
254
|
-
assert isinstance(start_date, datetime.datetime) and isinstance(
|
|
255
|
-
end_date, datetime.datetime
|
|
256
|
-
), "Dates must be datetime objects"
|
|
257
|
-
for series in self.root.findall("pi:series", ns):
|
|
258
|
-
new_start_date, new_end_date = datetime.datetime.max, datetime.datetime.min
|
|
259
|
-
events = series.findall("pi:event", ns)
|
|
260
|
-
for event in events:
|
|
261
|
-
event_datetime = self.__parse_date_time(event)
|
|
262
|
-
if event_datetime < start_date or event_datetime > end_date:
|
|
263
|
-
series.remove(event)
|
|
264
|
-
continue
|
|
265
|
-
if event_datetime < new_start_date:
|
|
266
|
-
new_start_date = event_datetime
|
|
267
|
-
if event_datetime > new_end_date:
|
|
268
|
-
new_end_date = event_datetime
|
|
269
|
-
start_date_attrib = {
|
|
270
|
-
"date": new_start_date.strftime("%Y-%m-%d"),
|
|
271
|
-
"time": new_start_date.strftime("%H:%M:%S"),
|
|
272
|
-
}
|
|
273
|
-
end_date_attrib = {
|
|
274
|
-
"date": new_end_date.strftime("%Y-%m-%d"),
|
|
275
|
-
"time": new_end_date.strftime("%H:%M:%S"),
|
|
276
|
-
}
|
|
277
|
-
series.find("pi:header", ns).find("pi:startDate", ns).attrib = start_date_attrib
|
|
278
|
-
series.find("pi:header", ns).find("pi:endDate", ns).attrib = end_date_attrib
|
|
279
|
-
if self.forecast_date_in_header:
|
|
280
|
-
series.find("pi:header", ns).find("pi:forecastDate", ns).attrib = start_date_attrib
|
|
281
|
-
if not series.findall("pi:event", ns):
|
|
282
|
-
self.root.remove(series)
|
|
283
|
-
logger.warning("Removed series with no events.")
|
|
284
|
-
self.set_forecast_date()
|
|
285
|
-
|
|
286
|
-
def write(self, file_path: Path, file_name: str = "timeseries_import"):
|
|
287
|
-
tree = ET.ElementTree(self.root)
|
|
288
|
-
tree.write((file_path / file_name).with_suffix(".xml"))
|
|
289
|
-
|
|
290
|
-
def get_external_id_from_series(self, series) -> Tuple[str, str, List[str]]:
|
|
291
|
-
header = series.find("pi:header", ns)
|
|
292
|
-
locationId = header.find("pi:locationId", ns).text
|
|
293
|
-
parameterId = header.find("pi:parameterId", ns).text
|
|
294
|
-
qualifier_ids = []
|
|
295
|
-
qualifiers_els = header.findall("pi:qualifierId", ns)
|
|
296
|
-
for qualifier in qualifiers_els:
|
|
297
|
-
qualifier_ids.append(qualifier.text)
|
|
298
|
-
return locationId, parameterId, qualifier_ids
|
|
299
|
-
|
|
300
|
-
def get_single_date_range(self, locationId, paramterId, qualifier_ids):
|
|
301
|
-
"""Get the date range of the timeseries data for a single location and parameter"""
|
|
302
|
-
min_date = datetime.datetime.max
|
|
303
|
-
max_date = datetime.datetime.min
|
|
304
|
-
for series in self.root.findall("pi:series", ns):
|
|
305
|
-
if (
|
|
306
|
-
locationId,
|
|
307
|
-
paramterId,
|
|
308
|
-
qualifier_ids,
|
|
309
|
-
) == self.get_external_id_from_series(series):
|
|
310
|
-
events = series.findall("pi:event", ns)
|
|
311
|
-
for event in events:
|
|
312
|
-
event_datetime = self.__parse_date_time(event)
|
|
313
|
-
if event_datetime < min_date:
|
|
314
|
-
min_date = event_datetime
|
|
315
|
-
if event_datetime > max_date:
|
|
316
|
-
max_date = event_datetime
|
|
317
|
-
if min_date == datetime.datetime.max:
|
|
318
|
-
raise ValueError(
|
|
319
|
-
"No data for locationId {}, parameterId {} and qualifierIds {}".format(
|
|
320
|
-
locationId, paramterId, qualifier_ids
|
|
321
|
-
)
|
|
322
|
-
)
|
|
323
|
-
return min_date, max_date
|
|
324
|
-
|
|
325
|
-
def get_single_date_range_from_series(self, series):
|
|
326
|
-
(
|
|
327
|
-
location_id,
|
|
328
|
-
parameter_id,
|
|
329
|
-
qualifier_ids,
|
|
330
|
-
) = self.get_external_id_from_series(series)
|
|
331
|
-
return self.get_single_date_range(location_id, parameter_id, qualifier_ids)
|
|
332
|
-
|
|
333
|
-
def get_datetimes(self):
|
|
334
|
-
"""Get the dates of all timeseries data."""
|
|
335
|
-
datetimes = set()
|
|
336
|
-
for series in self.root.findall("pi:series", ns):
|
|
337
|
-
events = series.findall("pi:event", ns)
|
|
338
|
-
for event in events:
|
|
339
|
-
event_datetime = self.__parse_date_time(event)
|
|
340
|
-
datetimes.add(event_datetime)
|
|
341
|
-
datetimes = list(datetimes)
|
|
342
|
-
datetimes.sort()
|
|
343
|
-
return datetimes
|
|
344
|
-
|
|
345
|
-
def get_datetime_range(self):
|
|
346
|
-
"""Get the date range of the timeseries data, minimum and maximum over all series"""
|
|
347
|
-
min_date = datetime.datetime.max
|
|
348
|
-
max_date = datetime.datetime.min
|
|
349
|
-
for series in self.root.findall("pi:series", ns):
|
|
350
|
-
events = series.findall("pi:event", ns)
|
|
351
|
-
for event in events:
|
|
352
|
-
event_datetime = self.__parse_date_time(event)
|
|
353
|
-
if event_datetime < min_date:
|
|
354
|
-
min_date = event_datetime
|
|
355
|
-
if event_datetime > max_date:
|
|
356
|
-
max_date = event_datetime
|
|
357
|
-
return min_date, max_date
|
|
358
|
-
|
|
359
|
-
def get_timestep(self):
|
|
360
|
-
"""Get the timestep of the timeseries data, raise error if different stepsizes"""
|
|
361
|
-
timestep = None
|
|
362
|
-
for series in self.root.findall("pi:series", ns):
|
|
363
|
-
events = series.findall("pi:event", ns)
|
|
364
|
-
for i, event in enumerate(events):
|
|
365
|
-
if i == 0:
|
|
366
|
-
continue
|
|
367
|
-
event_datetime = self.__parse_date_time(event)
|
|
368
|
-
previous_event_datetime = self.__parse_date_time(events[i - 1])
|
|
369
|
-
if timestep is None:
|
|
370
|
-
timestep = event_datetime - previous_event_datetime
|
|
371
|
-
elif timestep != event_datetime - previous_event_datetime:
|
|
372
|
-
raise ValueError("Different timesteps in timeseries data.")
|
|
373
|
-
return timestep
|
|
374
|
-
|
|
375
|
-
def set_reference_data(self, reference_data):
|
|
376
|
-
"""Set the the reference XMLTimeSeriesFile object to use for creating new series."""
|
|
377
|
-
self._reference_data = reference_data
|
|
378
|
-
|
|
379
|
-
def get_series(
|
|
380
|
-
self,
|
|
381
|
-
location_id: str,
|
|
382
|
-
parameter_id: str,
|
|
383
|
-
qualifier_ids: List[str],
|
|
384
|
-
from_reference=False,
|
|
385
|
-
) -> ET.Element:
|
|
386
|
-
"""Get the XML series element for a location and parameter.
|
|
387
|
-
If from_reference is True, the series is taken from the reference data."""
|
|
388
|
-
if from_reference:
|
|
389
|
-
if hasattr(self, "_reference_data"):
|
|
390
|
-
root = self._reference_data.root
|
|
391
|
-
else:
|
|
392
|
-
raise ValueError("No reference data available.")
|
|
393
|
-
elif hasattr(self, "root") and self.root is not None:
|
|
394
|
-
root = self.root
|
|
395
|
-
else:
|
|
396
|
-
raise ValueError("No data to select from, use read first.")
|
|
397
|
-
for series in root.findall("pi:series", ns):
|
|
398
|
-
if (
|
|
399
|
-
location_id,
|
|
400
|
-
parameter_id,
|
|
401
|
-
qualifier_ids,
|
|
402
|
-
) == self.get_external_id_from_series(series):
|
|
403
|
-
return series
|
|
404
|
-
raise ValueError("No series found for locationId {} and parameterId {}".format(location_id, parameter_id))
|
|
405
|
-
|
|
406
|
-
def set_initial_value(self, internal_id: str, value: float):
|
|
407
|
-
location_id, parameter_id, qualifier_ids = self.get_external_id_from_internal_id(internal_id)
|
|
408
|
-
for series in self.root.findall("pi:series", ns):
|
|
409
|
-
if (
|
|
410
|
-
location_id,
|
|
411
|
-
parameter_id,
|
|
412
|
-
qualifier_ids,
|
|
413
|
-
) == self.get_external_id_from_series(series):
|
|
414
|
-
logger.info(
|
|
415
|
-
"Overwriting initial value for locationId {} and parameterId {} and qualifier ids {}".format(
|
|
416
|
-
location_id, parameter_id, qualifier_ids
|
|
417
|
-
)
|
|
418
|
-
)
|
|
419
|
-
event = series.find("pi:event", ns)
|
|
420
|
-
event.attrib["value"] = str(value)
|
|
421
|
-
return
|
|
422
|
-
else:
|
|
423
|
-
# if no series found, create a new series with just one event (based on the reference data)
|
|
424
|
-
reference_series = self.get_series(location_id, parameter_id, qualifier_ids, from_reference=True)
|
|
425
|
-
new_series = copy.deepcopy(reference_series)
|
|
426
|
-
start_date, end_date = self.get_datetime_range()
|
|
427
|
-
first_event = new_series.find("pi:event", ns)
|
|
428
|
-
if first_event is None:
|
|
429
|
-
raise ValueError("No event found in reference data.")
|
|
430
|
-
first_event.attrib["value"] = str(value)
|
|
431
|
-
first_event.attrib["date"] = start_date.strftime("%Y-%m-%d")
|
|
432
|
-
first_event.attrib["time"] = start_date.strftime("%H:%M:%S")
|
|
433
|
-
header = new_series.find("pi:header", ns)
|
|
434
|
-
if header is None:
|
|
435
|
-
raise ValueError("No header found in reference data.")
|
|
436
|
-
header.find("pi:startDate", ns).attrib = {
|
|
437
|
-
"date": start_date.strftime("%Y-%m-%d"),
|
|
438
|
-
"time": start_date.strftime("%H:%M:%S"),
|
|
439
|
-
}
|
|
440
|
-
header.find("pi:endDate", ns).attrib = {
|
|
441
|
-
"date": end_date.strftime("%Y-%m-%d"),
|
|
442
|
-
"time": end_date.strftime("%H:%M:%S"),
|
|
443
|
-
}
|
|
444
|
-
if self.forecast_date_in_header:
|
|
445
|
-
header.find("pi:forecastDate", ns).attrib = {
|
|
446
|
-
"date": start_date.strftime("%Y-%m-%d"),
|
|
447
|
-
"time": start_date.strftime("%H:%M:%S"),
|
|
448
|
-
}
|
|
449
|
-
self.root.append(new_series)
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtc_tools_interface.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/closed_loop/config.py
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
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/plotting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/rtctools_interface/utils/__init__.py
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
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/closed_loop/test_read_xml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/optimization/test_read_goals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rtc-tools-interface-0.9.0a1 → rtc-tools-interface-0.9.0a2}/tests/simulation/test_plot_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|