rtc-tools 2.7.3__py3-none-any.whl
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-2.7.3.dist-info/METADATA +53 -0
- rtc_tools-2.7.3.dist-info/RECORD +50 -0
- rtc_tools-2.7.3.dist-info/WHEEL +5 -0
- rtc_tools-2.7.3.dist-info/entry_points.txt +3 -0
- rtc_tools-2.7.3.dist-info/licenses/COPYING.LESSER +165 -0
- rtc_tools-2.7.3.dist-info/top_level.txt +1 -0
- rtctools/__init__.py +5 -0
- rtctools/_internal/__init__.py +0 -0
- rtctools/_internal/alias_tools.py +188 -0
- rtctools/_internal/caching.py +25 -0
- rtctools/_internal/casadi_helpers.py +99 -0
- rtctools/_internal/debug_check_helpers.py +41 -0
- rtctools/_version.py +21 -0
- rtctools/data/__init__.py +4 -0
- rtctools/data/csv.py +150 -0
- rtctools/data/interpolation/__init__.py +3 -0
- rtctools/data/interpolation/bspline.py +31 -0
- rtctools/data/interpolation/bspline1d.py +169 -0
- rtctools/data/interpolation/bspline2d.py +54 -0
- rtctools/data/netcdf.py +467 -0
- rtctools/data/pi.py +1236 -0
- rtctools/data/rtc.py +228 -0
- rtctools/data/storage.py +343 -0
- rtctools/optimization/__init__.py +0 -0
- rtctools/optimization/collocated_integrated_optimization_problem.py +3208 -0
- rtctools/optimization/control_tree_mixin.py +221 -0
- rtctools/optimization/csv_lookup_table_mixin.py +462 -0
- rtctools/optimization/csv_mixin.py +300 -0
- rtctools/optimization/goal_programming_mixin.py +769 -0
- rtctools/optimization/goal_programming_mixin_base.py +1094 -0
- rtctools/optimization/homotopy_mixin.py +165 -0
- rtctools/optimization/initial_state_estimation_mixin.py +89 -0
- rtctools/optimization/io_mixin.py +320 -0
- rtctools/optimization/linearization_mixin.py +33 -0
- rtctools/optimization/linearized_order_goal_programming_mixin.py +235 -0
- rtctools/optimization/min_abs_goal_programming_mixin.py +385 -0
- rtctools/optimization/modelica_mixin.py +482 -0
- rtctools/optimization/netcdf_mixin.py +177 -0
- rtctools/optimization/optimization_problem.py +1302 -0
- rtctools/optimization/pi_mixin.py +292 -0
- rtctools/optimization/planning_mixin.py +19 -0
- rtctools/optimization/single_pass_goal_programming_mixin.py +676 -0
- rtctools/optimization/timeseries.py +56 -0
- rtctools/rtctoolsapp.py +131 -0
- rtctools/simulation/__init__.py +0 -0
- rtctools/simulation/csv_mixin.py +171 -0
- rtctools/simulation/io_mixin.py +195 -0
- rtctools/simulation/pi_mixin.py +255 -0
- rtctools/simulation/simulation_problem.py +1293 -0
- rtctools/util.py +241 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import casadi as ca
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Timeseries:
|
|
8
|
+
"""
|
|
9
|
+
Time series object, bundling time stamps with values.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, times: np.ndarray, values: Union[float, np.ndarray, list, ca.DM]):
|
|
13
|
+
"""
|
|
14
|
+
Create a new time series object.
|
|
15
|
+
|
|
16
|
+
:param times: Iterable of time stamps.
|
|
17
|
+
:param values: Iterable of values.
|
|
18
|
+
"""
|
|
19
|
+
self.__times = times
|
|
20
|
+
|
|
21
|
+
if isinstance(values, ca.DM):
|
|
22
|
+
# Note that a ca.DM object has no __iter__ attribute, which we
|
|
23
|
+
# want it to have. We also want it to store it as a _flat_ array,
|
|
24
|
+
# not a 2-D column vector.
|
|
25
|
+
assert values.shape[0] == 1 or values.shape[1] == 1, "Only 1D ca.DM objects supported"
|
|
26
|
+
values = values.toarray().ravel()
|
|
27
|
+
elif isinstance(values, (np.ndarray, list)) and len(values) == 1:
|
|
28
|
+
values = values[0]
|
|
29
|
+
|
|
30
|
+
if hasattr(values, "__iter__"):
|
|
31
|
+
self.__values = np.array(values, dtype=np.float64, copy=True)
|
|
32
|
+
else:
|
|
33
|
+
self.__values = np.full_like(times, values, dtype=np.float64)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def times(self) -> np.ndarray:
|
|
37
|
+
"""
|
|
38
|
+
Array of time stamps.
|
|
39
|
+
"""
|
|
40
|
+
return self.__times
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def values(self) -> np.ndarray:
|
|
44
|
+
"""
|
|
45
|
+
Array of values.
|
|
46
|
+
"""
|
|
47
|
+
return self.__values
|
|
48
|
+
|
|
49
|
+
def __neg__(self) -> "Timeseries":
|
|
50
|
+
return self.__class__(self.times, -self.values)
|
|
51
|
+
|
|
52
|
+
def __repr__(self) -> str:
|
|
53
|
+
return "Timeseries({}, {})".format(self.__times, self.__values)
|
|
54
|
+
|
|
55
|
+
def __eq__(self, other: "Timeseries") -> bool:
|
|
56
|
+
return np.array_equal(self.times, other.times) and np.array_equal(self.values, other.values)
|
rtctools/rtctoolsapp.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Python 3.9's importlib.metadata does not support the "group" parameter to
|
|
9
|
+
# entry_points yet.
|
|
10
|
+
if sys.version_info < (3, 10):
|
|
11
|
+
import importlib_metadata
|
|
12
|
+
else:
|
|
13
|
+
from importlib import metadata as importlib_metadata
|
|
14
|
+
|
|
15
|
+
import rtctools
|
|
16
|
+
|
|
17
|
+
logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s")
|
|
18
|
+
logger = logging.getLogger("rtctools")
|
|
19
|
+
logger.setLevel(logging.INFO)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def copy_libraries(*args):
|
|
23
|
+
if not args:
|
|
24
|
+
args = sys.argv[1:]
|
|
25
|
+
|
|
26
|
+
if not args:
|
|
27
|
+
path = input("Folder to put the Modelica libraries: [.] ") or "."
|
|
28
|
+
else:
|
|
29
|
+
path = args[0]
|
|
30
|
+
|
|
31
|
+
if not os.path.exists(path):
|
|
32
|
+
sys.exit("Folder '{}' does not exist".format(path))
|
|
33
|
+
|
|
34
|
+
def _copytree(src, dst, symlinks=False, ignore=None):
|
|
35
|
+
if not os.path.exists(dst):
|
|
36
|
+
os.makedirs(dst)
|
|
37
|
+
for item in os.listdir(src):
|
|
38
|
+
s = os.path.join(src, item)
|
|
39
|
+
d = os.path.join(dst, item)
|
|
40
|
+
if os.path.isdir(s):
|
|
41
|
+
_copytree(s, d, symlinks, ignore)
|
|
42
|
+
else:
|
|
43
|
+
if not os.path.exists(d):
|
|
44
|
+
shutil.copy2(s, d)
|
|
45
|
+
elif Path(s).name.lower() == "package.mo":
|
|
46
|
+
# Pick the largest one, assuming that all plugin packages
|
|
47
|
+
# to not provide a meaningful package.mo
|
|
48
|
+
if os.stat(s).st_size > os.stat(d).st_size:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"Overwriting '{}' with '{}' as the latter is larger.".format(d, s)
|
|
51
|
+
)
|
|
52
|
+
os.remove(d)
|
|
53
|
+
shutil.copy2(s, d)
|
|
54
|
+
else:
|
|
55
|
+
logger.warning(
|
|
56
|
+
"Not copying '{}' to '{}' as the latter is larger.".format(s, d)
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
raise OSError("Could not combine two folders")
|
|
60
|
+
|
|
61
|
+
dst = Path(path)
|
|
62
|
+
|
|
63
|
+
library_folders = []
|
|
64
|
+
|
|
65
|
+
for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
|
|
66
|
+
if ep.name == "library_folder":
|
|
67
|
+
library_folders.append(Path(importlib.resources.files(ep.module).joinpath(ep.attr)))
|
|
68
|
+
|
|
69
|
+
tlds = {}
|
|
70
|
+
for lf in library_folders:
|
|
71
|
+
for x in lf.iterdir():
|
|
72
|
+
if x.is_dir():
|
|
73
|
+
tlds.setdefault(x.name, []).append(x)
|
|
74
|
+
|
|
75
|
+
for tld, paths in tlds.items():
|
|
76
|
+
if Path(tld).exists():
|
|
77
|
+
sys.exit("Library with name '{}'' already exists".format(tld))
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
for p in paths:
|
|
81
|
+
_copytree(p, dst / p.name)
|
|
82
|
+
except OSError:
|
|
83
|
+
sys.exit("Failed merging the libraries in package '{}'".format(tld))
|
|
84
|
+
|
|
85
|
+
sys.exit("Succesfully copied all library folders to '{}'".format(dst.resolve()))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def download_examples(*args):
|
|
89
|
+
if not args:
|
|
90
|
+
args = sys.argv[1:]
|
|
91
|
+
|
|
92
|
+
if not args:
|
|
93
|
+
path = input("Folder to download the examples to: [.] ") or "."
|
|
94
|
+
else:
|
|
95
|
+
path = args[0]
|
|
96
|
+
|
|
97
|
+
if not os.path.exists(path):
|
|
98
|
+
sys.exit("Folder '{}' does not exist".format(path))
|
|
99
|
+
|
|
100
|
+
path = Path(path)
|
|
101
|
+
|
|
102
|
+
import urllib.request
|
|
103
|
+
from urllib.error import HTTPError
|
|
104
|
+
from zipfile import ZipFile
|
|
105
|
+
|
|
106
|
+
version = rtctools.__version__
|
|
107
|
+
try:
|
|
108
|
+
url = "https://github.com/rtc-tools/rtc-tools/zipball/{}".format(version)
|
|
109
|
+
|
|
110
|
+
opener = urllib.request.build_opener()
|
|
111
|
+
urllib.request.install_opener(opener)
|
|
112
|
+
# The security warning can be dismissed as the url variable is hardcoded to a remote.
|
|
113
|
+
local_filename, _ = urllib.request.urlretrieve(url) # nosec
|
|
114
|
+
except HTTPError:
|
|
115
|
+
sys.exit("Could not found examples for RTC-Tools version {}.".format(version))
|
|
116
|
+
|
|
117
|
+
with ZipFile(local_filename, "r") as z:
|
|
118
|
+
target = path / "rtc-tools-examples"
|
|
119
|
+
zip_folder_name = next(x for x in z.namelist() if x.startswith("Deltares-rtc-tools-"))
|
|
120
|
+
prefix = "{}/examples/".format(zip_folder_name.rstrip("/"))
|
|
121
|
+
members = [x for x in z.namelist() if x.startswith(prefix)]
|
|
122
|
+
z.extractall(members=members)
|
|
123
|
+
shutil.move(prefix, target)
|
|
124
|
+
shutil.rmtree(zip_folder_name)
|
|
125
|
+
|
|
126
|
+
sys.exit("Succesfully downloaded the RTC-Tools examples to '{}'".format(target.resolve()))
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
os.remove(local_filename)
|
|
130
|
+
except OSError:
|
|
131
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
import rtctools.data.csv as csv
|
|
7
|
+
from rtctools._internal.caching import cached
|
|
8
|
+
from rtctools.simulation.io_mixin import IOMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("rtctools")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CSVMixin(IOMixin):
|
|
14
|
+
"""
|
|
15
|
+
Adds reading and writing of CSV timeseries and parameters to your simulation problem.
|
|
16
|
+
|
|
17
|
+
During preprocessing, files named ``timeseries_import.csv``, ``initial_state.csv``,
|
|
18
|
+
and ``parameters.csv`` are read from the ``input`` subfolder.
|
|
19
|
+
|
|
20
|
+
During postprocessing, a file named ``timeseries_export.csv`` is written to the ``output``
|
|
21
|
+
subfolder.
|
|
22
|
+
|
|
23
|
+
:cvar csv_delimiter: Column delimiter used in CSV files. Default is ``,``.
|
|
24
|
+
:cvar csv_validate_timeseries: Check consistency of timeseries. Default is ``True``.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
#: Column delimiter used in CSV files
|
|
28
|
+
csv_delimiter = ","
|
|
29
|
+
|
|
30
|
+
#: Check consistency of timeseries
|
|
31
|
+
csv_validate_timeseries = True
|
|
32
|
+
|
|
33
|
+
def __init__(self, **kwargs):
|
|
34
|
+
# Call parent class first for default behaviour.
|
|
35
|
+
super().__init__(**kwargs)
|
|
36
|
+
|
|
37
|
+
def read(self):
|
|
38
|
+
# Call parent class first for default behaviour.
|
|
39
|
+
super().read()
|
|
40
|
+
|
|
41
|
+
# Helper function to check if initial state array actually defines
|
|
42
|
+
# only the initial state
|
|
43
|
+
def check_initial_state_array(initial_state):
|
|
44
|
+
"""
|
|
45
|
+
Check length of initial state array, throw exception when larger than 1.
|
|
46
|
+
"""
|
|
47
|
+
if initial_state.shape:
|
|
48
|
+
raise Exception(
|
|
49
|
+
"CSVMixin: Initial state file {} contains more than one row of data. "
|
|
50
|
+
"Please remove the data row(s) that do not describe the initial "
|
|
51
|
+
"state.".format(os.path.join(self._input_folder, "initial_state.csv"))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Read CSV files
|
|
55
|
+
_timeseries = csv.load(
|
|
56
|
+
os.path.join(self._input_folder, self.timeseries_import_basename + ".csv"),
|
|
57
|
+
delimiter=self.csv_delimiter,
|
|
58
|
+
with_time=True,
|
|
59
|
+
)
|
|
60
|
+
self.__timeseries_times = _timeseries[_timeseries.dtype.names[0]]
|
|
61
|
+
|
|
62
|
+
self.io.reference_datetime = self.__timeseries_times[0]
|
|
63
|
+
|
|
64
|
+
for key in _timeseries.dtype.names[1:]:
|
|
65
|
+
self.io.set_timeseries(
|
|
66
|
+
key, self.__timeseries_times, np.asarray(_timeseries[key], dtype=np.float64)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
logger.debug("CSVMixin: Read timeseries.")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
_parameters = csv.load(
|
|
73
|
+
os.path.join(self._input_folder, "parameters.csv"), delimiter=self.csv_delimiter
|
|
74
|
+
)
|
|
75
|
+
for key in _parameters.dtype.names:
|
|
76
|
+
self.io.set_parameter(key, float(_parameters[key]))
|
|
77
|
+
logger.debug("CSVMixin: Read parameters.")
|
|
78
|
+
except IOError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
_initial_state = csv.load(
|
|
83
|
+
os.path.join(self._input_folder, "initial_state.csv"), delimiter=self.csv_delimiter
|
|
84
|
+
)
|
|
85
|
+
logger.debug("CSVMixin: Read initial state.")
|
|
86
|
+
check_initial_state_array(_initial_state)
|
|
87
|
+
self.__initial_state = {
|
|
88
|
+
key: float(_initial_state[key]) for key in _initial_state.dtype.names
|
|
89
|
+
}
|
|
90
|
+
except IOError:
|
|
91
|
+
self.__initial_state = {}
|
|
92
|
+
|
|
93
|
+
# Check for collisions in __initial_state and timeseries import (CSV)
|
|
94
|
+
for collision in set(self.__initial_state) & set(_timeseries.dtype.names[1:]):
|
|
95
|
+
if self.__initial_state[collision] == _timeseries[collision][0]:
|
|
96
|
+
continue
|
|
97
|
+
else:
|
|
98
|
+
logger.warning(
|
|
99
|
+
"CSVMixin: Entry {} in initial_state.csv conflicts with "
|
|
100
|
+
"timeseries_import.csv".format(collision)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Timestamp check
|
|
104
|
+
if self.csv_validate_timeseries:
|
|
105
|
+
times = self.__timeseries_times
|
|
106
|
+
for i in range(len(times) - 1):
|
|
107
|
+
if times[i] >= times[i + 1]:
|
|
108
|
+
raise Exception("CSVMixin: Time stamps must be strictly increasing.")
|
|
109
|
+
|
|
110
|
+
times = self.__timeseries_times
|
|
111
|
+
dt = times[1] - times[0]
|
|
112
|
+
|
|
113
|
+
# Check if the timeseries are truly equidistant
|
|
114
|
+
if self.csv_validate_timeseries:
|
|
115
|
+
for i in range(len(times) - 1):
|
|
116
|
+
if times[i + 1] - times[i] != dt:
|
|
117
|
+
raise Exception(
|
|
118
|
+
"CSVMixin: Expecting equidistant timeseries, the time step "
|
|
119
|
+
"towards {} is not the same as the time step(s) before. "
|
|
120
|
+
"Set equidistant=False if this is intended.".format(times[i + 1])
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def write(self):
|
|
124
|
+
# Call parent class first for default behaviour.
|
|
125
|
+
super().write()
|
|
126
|
+
|
|
127
|
+
times = self._simulation_times
|
|
128
|
+
|
|
129
|
+
# Write output
|
|
130
|
+
names = ["time"] + sorted(set(self._io_output_variables))
|
|
131
|
+
formats = ["O"] + (len(names) - 1) * ["f8"]
|
|
132
|
+
dtype = {"names": names, "formats": formats}
|
|
133
|
+
data = np.zeros(len(times), dtype=dtype)
|
|
134
|
+
data["time"] = self.io.sec_to_datetime(times, self.io.reference_datetime)
|
|
135
|
+
for variable in self._io_output_variables:
|
|
136
|
+
data[variable] = np.array(self._io_output[variable])
|
|
137
|
+
|
|
138
|
+
fname = os.path.join(self._output_folder, self.timeseries_export_basename + ".csv")
|
|
139
|
+
csv.save(fname, data, delimiter=self.csv_delimiter, with_time=True)
|
|
140
|
+
|
|
141
|
+
@cached
|
|
142
|
+
def initial_state(self):
|
|
143
|
+
"""
|
|
144
|
+
The initial state. Includes entries from parent classes and initial_state.csv
|
|
145
|
+
|
|
146
|
+
:returns: A dictionary of variable names and initial state (t0) values.
|
|
147
|
+
"""
|
|
148
|
+
# Call parent class first for default values.
|
|
149
|
+
initial_state = super().initial_state()
|
|
150
|
+
|
|
151
|
+
# Set of model vars that are allowed to have an initial state
|
|
152
|
+
valid_model_vars = set(self.get_state_variables()) | set(self.get_input_variables())
|
|
153
|
+
|
|
154
|
+
# Load initial states from __initial_state
|
|
155
|
+
for variable, value in self.__initial_state.items():
|
|
156
|
+
# Get the cannonical vars and signs
|
|
157
|
+
canonical_var, sign = self.alias_relation.canonical_signed(variable)
|
|
158
|
+
|
|
159
|
+
# Only store variables that are allowed to have an initial state
|
|
160
|
+
if canonical_var in valid_model_vars:
|
|
161
|
+
initial_state[canonical_var] = value * sign
|
|
162
|
+
|
|
163
|
+
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
164
|
+
logger.debug("CSVMixin: Read initial state {} = {}".format(variable, value))
|
|
165
|
+
else:
|
|
166
|
+
logger.warning(
|
|
167
|
+
"CSVMixin: In initial_state.csv, {} is not an input or state variable.".format(
|
|
168
|
+
variable
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
return initial_state
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
import logging
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from math import isfinite
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from rtctools._internal.alias_tools import AliasDict
|
|
9
|
+
from rtctools._internal.caching import cached
|
|
10
|
+
from rtctools.simulation.simulation_problem import SimulationProblem
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("rtctools")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IOMixin(SimulationProblem, metaclass=ABCMeta):
|
|
16
|
+
"""
|
|
17
|
+
Base class for all IO methods of optimization problems.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, **kwargs):
|
|
21
|
+
# Call parent class first for default behaviour.
|
|
22
|
+
super().__init__(**kwargs)
|
|
23
|
+
|
|
24
|
+
self._simulation_times = []
|
|
25
|
+
|
|
26
|
+
self.__first_update_call = True
|
|
27
|
+
|
|
28
|
+
def pre(self) -> None:
|
|
29
|
+
# Call read method to read all input
|
|
30
|
+
self.read()
|
|
31
|
+
|
|
32
|
+
self._simulation_times = []
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def read(self) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Reads input data from files, storing it in the internal data store through the various set
|
|
38
|
+
or add methods
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def post(self) -> None:
|
|
43
|
+
# Call write method to write all output
|
|
44
|
+
self.write()
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def write(self) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Writes output data to files, getting the data from the data store through the various get
|
|
50
|
+
methods
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def initialize(self, config_file=None):
|
|
55
|
+
# Set up experiment
|
|
56
|
+
timeseries_import_times = self.io.times_sec
|
|
57
|
+
self.__dt = timeseries_import_times[1] - timeseries_import_times[0]
|
|
58
|
+
self.setup_experiment(0, timeseries_import_times[-1], self.__dt)
|
|
59
|
+
|
|
60
|
+
parameter_variables = set(self.get_parameter_variables())
|
|
61
|
+
|
|
62
|
+
logger.debug("Model parameters are {}".format(parameter_variables))
|
|
63
|
+
|
|
64
|
+
for parameter, value in self.io.parameters().items():
|
|
65
|
+
if parameter in parameter_variables:
|
|
66
|
+
logger.debug("IOMixin: Setting parameter {} = {}".format(parameter, value))
|
|
67
|
+
self.set_var(parameter, value)
|
|
68
|
+
|
|
69
|
+
# Load input variable names
|
|
70
|
+
self.__input_variables = set(self.get_input_variables().keys())
|
|
71
|
+
|
|
72
|
+
# Set input values
|
|
73
|
+
t_idx = bisect.bisect_left(timeseries_import_times, 0.0)
|
|
74
|
+
self.__set_input_variables(t_idx)
|
|
75
|
+
|
|
76
|
+
logger.debug("Model inputs are {}".format(self.__input_variables))
|
|
77
|
+
|
|
78
|
+
# Set first timestep
|
|
79
|
+
self._simulation_times.append(self.get_current_time())
|
|
80
|
+
|
|
81
|
+
# Empty output
|
|
82
|
+
self._io_output_variables = self.get_output_variables()
|
|
83
|
+
self._io_output = AliasDict(self.alias_relation)
|
|
84
|
+
|
|
85
|
+
# Call super, which will also initialize the model itself
|
|
86
|
+
super().initialize(config_file)
|
|
87
|
+
|
|
88
|
+
# Extract consistent t0 values
|
|
89
|
+
for variable in self._io_output_variables:
|
|
90
|
+
self._io_output[variable] = [self.get_var(variable)]
|
|
91
|
+
|
|
92
|
+
def __set_input_variables(self, t_idx, use_cache=False):
|
|
93
|
+
if not use_cache:
|
|
94
|
+
self.__cache_loop_timeseries = {}
|
|
95
|
+
|
|
96
|
+
timeseries_names = set(self.io.get_timeseries_names(0))
|
|
97
|
+
for v in self.get_input_variables():
|
|
98
|
+
if v in timeseries_names:
|
|
99
|
+
_, values = self.io.get_timeseries_sec(v)
|
|
100
|
+
self.__cache_loop_timeseries[v] = values
|
|
101
|
+
|
|
102
|
+
for variable, values in self.__cache_loop_timeseries.items():
|
|
103
|
+
value = values[t_idx]
|
|
104
|
+
if isfinite(value):
|
|
105
|
+
self.set_var(variable, value)
|
|
106
|
+
else:
|
|
107
|
+
logger.debug(
|
|
108
|
+
"IOMixin: Found bad value {} at index [{}] "
|
|
109
|
+
"in timeseries aliased to input {}".format(value, t_idx, variable)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def update(self, dt):
|
|
113
|
+
# Time step
|
|
114
|
+
if dt < 0:
|
|
115
|
+
dt = self.__dt
|
|
116
|
+
|
|
117
|
+
# Current time stamp
|
|
118
|
+
t = self.get_current_time()
|
|
119
|
+
self._simulation_times.append(t + dt)
|
|
120
|
+
|
|
121
|
+
# Get current time index
|
|
122
|
+
t_idx = bisect.bisect_left(self.io.times_sec, t + dt)
|
|
123
|
+
|
|
124
|
+
# Set input values
|
|
125
|
+
self.__set_input_variables(t_idx, not self.__first_update_call)
|
|
126
|
+
|
|
127
|
+
# Call super
|
|
128
|
+
super().update(dt)
|
|
129
|
+
|
|
130
|
+
# Extract results
|
|
131
|
+
for variable, values in self._io_output.items():
|
|
132
|
+
values.append(self.get_var(variable))
|
|
133
|
+
|
|
134
|
+
self.__first_update_call = False
|
|
135
|
+
|
|
136
|
+
def extract_results(self):
|
|
137
|
+
"""
|
|
138
|
+
Extracts the results of output
|
|
139
|
+
|
|
140
|
+
:returns: An AliasDict of output variables and results array format.
|
|
141
|
+
"""
|
|
142
|
+
io_outputs_arrays = self._io_output.copy()
|
|
143
|
+
for k in io_outputs_arrays.keys():
|
|
144
|
+
io_outputs_arrays[k] = np.array(io_outputs_arrays[k])
|
|
145
|
+
|
|
146
|
+
return io_outputs_arrays
|
|
147
|
+
|
|
148
|
+
@cached
|
|
149
|
+
def parameters(self):
|
|
150
|
+
"""
|
|
151
|
+
Return a dictionary of parameters, including parameters in the input files files.
|
|
152
|
+
|
|
153
|
+
:returns: Dictionary of parameters
|
|
154
|
+
"""
|
|
155
|
+
# Call parent class first for default values.
|
|
156
|
+
parameters = super().parameters()
|
|
157
|
+
|
|
158
|
+
# Load parameters from input files (stored in internal data store)
|
|
159
|
+
for parameter_name, value in self.io.parameters().items():
|
|
160
|
+
parameters[parameter_name] = value
|
|
161
|
+
|
|
162
|
+
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
163
|
+
for parameter_name in self.io.parameters().keys():
|
|
164
|
+
logger.debug("IOMixin: Read parameter {}".format(parameter_name))
|
|
165
|
+
|
|
166
|
+
return parameters
|
|
167
|
+
|
|
168
|
+
def times(self, variable=None):
|
|
169
|
+
"""
|
|
170
|
+
Return a list of all the timesteps in seconds.
|
|
171
|
+
|
|
172
|
+
:param variable: Variable name.
|
|
173
|
+
|
|
174
|
+
:returns: List of all the timesteps in seconds.
|
|
175
|
+
"""
|
|
176
|
+
idx = bisect.bisect_left(self.io.datetimes, self.io.reference_datetime)
|
|
177
|
+
return self.io.times_sec[idx:]
|
|
178
|
+
|
|
179
|
+
def timeseries_at(self, variable, t):
|
|
180
|
+
"""
|
|
181
|
+
Return the value of a time series at the given time.
|
|
182
|
+
|
|
183
|
+
:param variable: Variable name.
|
|
184
|
+
:param t: Time.
|
|
185
|
+
|
|
186
|
+
:returns: The interpolated value of the time series.
|
|
187
|
+
|
|
188
|
+
:raises: KeyError
|
|
189
|
+
"""
|
|
190
|
+
timeseries_times_sec, values = self.io.get_timeseries_sec(variable)
|
|
191
|
+
t_idx = bisect.bisect_left(timeseries_times_sec, t)
|
|
192
|
+
if timeseries_times_sec[t_idx] == t:
|
|
193
|
+
return values[t_idx]
|
|
194
|
+
else:
|
|
195
|
+
return np.interp(t, timeseries_times_sec, values)
|