foxes 1.4__py3-none-any.whl → 1.5.1__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.
Potentially problematic release.
This version of foxes might be problematic. Click here for more details.
- docs/source/conf.py +1 -1
- examples/abl_states/run.py +58 -56
- examples/dyn_wakes/run.py +110 -118
- examples/field_data_nc/run.py +23 -21
- examples/multi_height/run.py +8 -6
- examples/scan_row/run.py +89 -87
- examples/sector_management/run.py +40 -38
- examples/states_lookup_table/run.py +6 -4
- examples/streamline_wakes/run.py +10 -8
- examples/timelines/run.py +100 -98
- examples/timeseries/run.py +71 -76
- examples/wind_rose/run.py +27 -25
- examples/yawed_wake/run.py +85 -81
- foxes/algorithms/downwind/downwind.py +5 -5
- foxes/algorithms/downwind/models/init_farm_data.py +58 -28
- foxes/algorithms/downwind/models/set_amb_farm_results.py +1 -1
- foxes/core/algorithm.py +6 -5
- foxes/core/data.py +75 -4
- foxes/core/data_calc_model.py +4 -2
- foxes/core/engine.py +33 -40
- foxes/core/farm_data_model.py +16 -13
- foxes/core/model.py +19 -1
- foxes/core/point_data_model.py +19 -14
- foxes/core/rotor_model.py +1 -0
- foxes/core/wake_deflection.py +3 -3
- foxes/data/states/point_cloud_100.nc +0 -0
- foxes/data/states/weibull_cloud_4.nc +0 -0
- foxes/data/states/weibull_grid.nc +0 -0
- foxes/engines/dask.py +3 -6
- foxes/engines/default.py +2 -2
- foxes/engines/numpy.py +11 -10
- foxes/engines/pool.py +21 -11
- foxes/engines/single.py +8 -6
- foxes/input/farm_layout/__init__.py +1 -0
- foxes/input/farm_layout/from_arrays.py +68 -0
- foxes/input/states/__init__.py +7 -1
- foxes/input/states/dataset_states.py +710 -0
- foxes/input/states/field_data.py +531 -0
- foxes/input/states/multi_height.py +2 -0
- foxes/input/states/one_point_flow.py +1 -0
- foxes/input/states/point_cloud_data.py +618 -0
- foxes/input/states/scan.py +2 -0
- foxes/input/states/single.py +2 -0
- foxes/input/states/states_table.py +13 -23
- foxes/input/states/weibull_sectors.py +182 -77
- foxes/input/states/wrg_states.py +1 -1
- foxes/input/yaml/dict.py +25 -24
- foxes/input/yaml/windio/read_attributes.py +40 -27
- foxes/input/yaml/windio/read_farm.py +12 -10
- foxes/input/yaml/windio/read_outputs.py +25 -15
- foxes/input/yaml/windio/read_site.py +121 -12
- foxes/input/yaml/windio/windio.py +22 -10
- foxes/input/yaml/yaml.py +1 -0
- foxes/models/model_book.py +16 -15
- foxes/models/rotor_models/__init__.py +1 -0
- foxes/models/rotor_models/centre.py +1 -1
- foxes/models/rotor_models/direct_infusion.py +241 -0
- foxes/models/turbine_models/calculator.py +16 -3
- foxes/models/turbine_models/kTI_model.py +1 -0
- foxes/models/turbine_models/lookup_table.py +2 -0
- foxes/models/turbine_models/power_mask.py +1 -0
- foxes/models/turbine_models/rotor_centre_calc.py +2 -0
- foxes/models/turbine_models/sector_management.py +1 -0
- foxes/models/turbine_models/set_farm_vars.py +3 -8
- foxes/models/turbine_models/table_factors.py +2 -0
- foxes/models/turbine_models/thrust2ct.py +1 -0
- foxes/models/turbine_models/yaw2yawm.py +2 -0
- foxes/models/turbine_models/yawm2yaw.py +2 -0
- foxes/models/turbine_types/PCt_file.py +2 -4
- foxes/models/turbine_types/PCt_from_two.py +1 -0
- foxes/models/turbine_types/__init__.py +1 -0
- foxes/models/turbine_types/calculator_type.py +123 -0
- foxes/models/turbine_types/null_type.py +1 -0
- foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -0
- foxes/models/turbine_types/wsti2PCt_from_two.py +3 -1
- foxes/output/farm_layout.py +2 -0
- foxes/output/farm_results_eval.py +4 -1
- foxes/output/flow_plots_2d/flow_plots.py +18 -0
- foxes/output/flow_plots_2d/get_fig.py +1 -0
- foxes/output/output.py +6 -1
- foxes/output/results_writer.py +1 -1
- foxes/output/rose_plot.py +10 -0
- foxes/output/rotor_point_plots.py +3 -0
- foxes/output/state_turbine_map.py +3 -0
- foxes/output/turbine_type_curves.py +3 -0
- foxes/utils/dict.py +46 -34
- foxes/utils/factory.py +2 -2
- foxes/utils/xarray_utils.py +20 -12
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/METADATA +32 -52
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/RECORD +94 -86
- foxes/input/states/field_data_nc.py +0 -833
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/WHEEL +0 -0
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/entry_points.txt +0 -0
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {foxes-1.4.dist-info → foxes-1.5.1.dist-info}/top_level.txt +0 -0
|
@@ -1,833 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import xarray as xr
|
|
4
|
-
from copy import copy
|
|
5
|
-
from scipy.interpolate import interpn
|
|
6
|
-
|
|
7
|
-
from foxes.core import States, get_engine
|
|
8
|
-
from foxes.utils import wd2uv, uv2wd, import_module
|
|
9
|
-
from foxes.data import STATES, StaticData
|
|
10
|
-
import foxes.variables as FV
|
|
11
|
-
import foxes.constants as FC
|
|
12
|
-
from foxes.config import config, get_input_path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _read_nc_file(
|
|
16
|
-
fpath,
|
|
17
|
-
coords,
|
|
18
|
-
vars,
|
|
19
|
-
nc_engine,
|
|
20
|
-
sel,
|
|
21
|
-
isel,
|
|
22
|
-
minimal,
|
|
23
|
-
):
|
|
24
|
-
"""Helper function for nc file reading"""
|
|
25
|
-
data = xr.open_dataset(fpath, engine=nc_engine)
|
|
26
|
-
for c in coords:
|
|
27
|
-
if c is not None and c not in data:
|
|
28
|
-
raise KeyError(
|
|
29
|
-
f"Missing coordinate '{c}' in file {fpath}, got: {list(data.coords.keys())}"
|
|
30
|
-
)
|
|
31
|
-
if minimal:
|
|
32
|
-
return data[coords[0]].to_numpy()
|
|
33
|
-
else:
|
|
34
|
-
data = data[vars]
|
|
35
|
-
data.attrs = {}
|
|
36
|
-
if isel is not None and len(isel):
|
|
37
|
-
data = data.isel(**isel)
|
|
38
|
-
if sel is not None and len(sel):
|
|
39
|
-
data = data.sel(**sel)
|
|
40
|
-
return data
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class FieldDataNC(States):
|
|
44
|
-
"""
|
|
45
|
-
Heterogeneous ambient states on a regular
|
|
46
|
-
horizontal grid in NetCDF format.
|
|
47
|
-
|
|
48
|
-
Attributes
|
|
49
|
-
----------
|
|
50
|
-
data_source: str or xarray.Dataset
|
|
51
|
-
The data or the file search pattern, should end with
|
|
52
|
-
suffix '.nc'. One or many files.
|
|
53
|
-
ovars: list of str
|
|
54
|
-
The output variables
|
|
55
|
-
var2ncvar: dict
|
|
56
|
-
Mapping from variable names to variable names
|
|
57
|
-
in the nc file
|
|
58
|
-
fixed_vars: dict
|
|
59
|
-
Uniform values for output variables, instead
|
|
60
|
-
of reading from data
|
|
61
|
-
states_coord: str
|
|
62
|
-
The states coordinate name in the data
|
|
63
|
-
x_coord: str
|
|
64
|
-
The x coordinate name in the data
|
|
65
|
-
y_coord: str
|
|
66
|
-
The y coordinate name in the data
|
|
67
|
-
h_coord: str
|
|
68
|
-
The height coordinate name in the data
|
|
69
|
-
load_mode: str
|
|
70
|
-
The load mode, choices: preload, lazy, fly.
|
|
71
|
-
preload loads all data during initialization,
|
|
72
|
-
lazy lazy-loads the data using dask, and fly
|
|
73
|
-
reads only states index and weights during initialization
|
|
74
|
-
and then opens the relevant files again within
|
|
75
|
-
the chunk calculation
|
|
76
|
-
weight_ncvar: str
|
|
77
|
-
Name of the weight data variable in the nc file(s)
|
|
78
|
-
bounds_error: bool
|
|
79
|
-
Flag for raising errors if bounds are exceeded
|
|
80
|
-
fill_value: number
|
|
81
|
-
Fill value in case of exceeding bounds, if no bounds error
|
|
82
|
-
time_format: str
|
|
83
|
-
The datetime parsing format string
|
|
84
|
-
interp_nans: bool
|
|
85
|
-
Linearly interpolate nan values
|
|
86
|
-
interpn_pars: dict, optional
|
|
87
|
-
Additional parameters for scipy.interpolate.interpn
|
|
88
|
-
bounds_extra_space: float or str
|
|
89
|
-
The extra space, either float in m,
|
|
90
|
-
or str for units of D, e.g. '2.5D'
|
|
91
|
-
|
|
92
|
-
:group: input.states
|
|
93
|
-
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
def __init__(
|
|
97
|
-
self,
|
|
98
|
-
data_source,
|
|
99
|
-
output_vars,
|
|
100
|
-
var2ncvar={},
|
|
101
|
-
fixed_vars={},
|
|
102
|
-
states_coord="Time",
|
|
103
|
-
x_coord="UTMX",
|
|
104
|
-
y_coord="UTMY",
|
|
105
|
-
h_coord="height",
|
|
106
|
-
load_mode="preload",
|
|
107
|
-
weight_ncvar=None,
|
|
108
|
-
time_format="%Y-%m-%d_%H:%M:%S",
|
|
109
|
-
sel=None,
|
|
110
|
-
isel=None,
|
|
111
|
-
interp_nans=False,
|
|
112
|
-
bounds_extra_space=1000,
|
|
113
|
-
**interpn_pars,
|
|
114
|
-
):
|
|
115
|
-
"""
|
|
116
|
-
Constructor.
|
|
117
|
-
|
|
118
|
-
Parameters
|
|
119
|
-
----------
|
|
120
|
-
data_source: str or xarray.Dataset
|
|
121
|
-
The data or the file search pattern, should end with
|
|
122
|
-
suffix '.nc'. One or many files.
|
|
123
|
-
output_vars: list of str
|
|
124
|
-
The output variables
|
|
125
|
-
var2ncvar: dict, optional
|
|
126
|
-
Mapping from variable names to variable names
|
|
127
|
-
in the nc file
|
|
128
|
-
fixed_vars: dict, optional
|
|
129
|
-
Uniform values for output variables, instead
|
|
130
|
-
of reading from data
|
|
131
|
-
states_coord: str
|
|
132
|
-
The states coordinate name in the data
|
|
133
|
-
x_coord: str
|
|
134
|
-
The x coordinate name in the data
|
|
135
|
-
y_coord: str
|
|
136
|
-
The y coordinate name in the data
|
|
137
|
-
h_coord: str, optional
|
|
138
|
-
The height coordinate name in the data
|
|
139
|
-
load_mode: str
|
|
140
|
-
The load mode, choices: preload, lazy, fly.
|
|
141
|
-
preload loads all data during initialization,
|
|
142
|
-
lazy lazy-loads the data using dask, and fly
|
|
143
|
-
reads only states index and weights during initialization
|
|
144
|
-
and then opens the relevant files again within
|
|
145
|
-
the chunk calculation
|
|
146
|
-
weight_ncvar: str, optional
|
|
147
|
-
Name of the weight data variable in the nc file(s)
|
|
148
|
-
time_format: str
|
|
149
|
-
The datetime parsing format string
|
|
150
|
-
sel: dict, optional
|
|
151
|
-
Subset selection via xr.Dataset.sel()
|
|
152
|
-
isel: dict, optional
|
|
153
|
-
Subset selection via xr.Dataset.isel()
|
|
154
|
-
interp_nans: bool
|
|
155
|
-
Linearly interpolate nan values
|
|
156
|
-
bounds_extra_space: float or str, optional
|
|
157
|
-
The extra space, either float in m,
|
|
158
|
-
or str for units of D, e.g. '2.5D'
|
|
159
|
-
interpn_pars: dict, optional
|
|
160
|
-
Additional parameters for scipy.interpolate.interpn
|
|
161
|
-
|
|
162
|
-
"""
|
|
163
|
-
super().__init__()
|
|
164
|
-
|
|
165
|
-
self.states_coord = states_coord
|
|
166
|
-
self.ovars = list(output_vars)
|
|
167
|
-
self.fixed_vars = fixed_vars
|
|
168
|
-
self.x_coord = x_coord
|
|
169
|
-
self.y_coord = y_coord
|
|
170
|
-
self.h_coord = h_coord
|
|
171
|
-
self.weight_ncvar = weight_ncvar
|
|
172
|
-
self.load_mode = load_mode
|
|
173
|
-
self.time_format = time_format
|
|
174
|
-
self.sel = sel if sel is not None else {}
|
|
175
|
-
self.isel = isel if isel is not None else {}
|
|
176
|
-
self.interpn_pars = interpn_pars
|
|
177
|
-
self.interp_nans = interp_nans
|
|
178
|
-
self.bounds_extra_space = bounds_extra_space
|
|
179
|
-
|
|
180
|
-
self.var2ncvar = {
|
|
181
|
-
v: var2ncvar.get(v, v) for v in output_vars if v not in fixed_vars
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
self._N = None
|
|
185
|
-
|
|
186
|
-
self.__data_source = data_source
|
|
187
|
-
self.__inds = None
|
|
188
|
-
|
|
189
|
-
@property
|
|
190
|
-
def data_source(self):
|
|
191
|
-
"""
|
|
192
|
-
The data source
|
|
193
|
-
|
|
194
|
-
Returns
|
|
195
|
-
-------
|
|
196
|
-
s: object
|
|
197
|
-
The data source
|
|
198
|
-
|
|
199
|
-
"""
|
|
200
|
-
if self.load_mode in ["preload", "fly"] and self.running:
|
|
201
|
-
raise ValueError(
|
|
202
|
-
f"States '{self.name}': Cannot access data_source while running for load mode '{self.load_mode}'"
|
|
203
|
-
)
|
|
204
|
-
return self.__data_source
|
|
205
|
-
|
|
206
|
-
def _get_data(self, ds, coords, verbosity):
|
|
207
|
-
"""
|
|
208
|
-
Helper function for data extraction
|
|
209
|
-
"""
|
|
210
|
-
for ci, c in enumerate(coords):
|
|
211
|
-
found = False
|
|
212
|
-
if c is not None:
|
|
213
|
-
for v in ds.data_vars.values():
|
|
214
|
-
if c in v.dims:
|
|
215
|
-
found = True
|
|
216
|
-
break
|
|
217
|
-
if not found:
|
|
218
|
-
coords[ci] = None
|
|
219
|
-
|
|
220
|
-
dlst = []
|
|
221
|
-
for c in coords:
|
|
222
|
-
if c is not None:
|
|
223
|
-
dlst.append(np.atleast_1d(ds[c].to_numpy()))
|
|
224
|
-
else:
|
|
225
|
-
dlst.append(np.array([0], dtype=config.dtype_double))
|
|
226
|
-
sts, h, y, x = dlst
|
|
227
|
-
n_sts, n_h, n_y, n_x = [len(u) for u in dlst]
|
|
228
|
-
|
|
229
|
-
cor_shxy = (self.states_coord, self.h_coord, self.x_coord, self.y_coord)
|
|
230
|
-
cor_shyx = (self.states_coord, self.h_coord, self.y_coord, self.x_coord)
|
|
231
|
-
cor_sxy = (self.states_coord, self.x_coord, self.y_coord)
|
|
232
|
-
cor_syx = (self.states_coord, self.y_coord, self.x_coord)
|
|
233
|
-
cor_sh = (self.states_coord, self.h_coord)
|
|
234
|
-
cor_s = (self.states_coord,)
|
|
235
|
-
vars_shyx = []
|
|
236
|
-
vars_syx = []
|
|
237
|
-
vars_sh = []
|
|
238
|
-
vars_s = []
|
|
239
|
-
for v, ncv in self.var2ncvar.items():
|
|
240
|
-
if ds[ncv].dims == cor_shyx or ds[ncv].dims == cor_shxy:
|
|
241
|
-
vars_shyx.append(v)
|
|
242
|
-
elif ds[ncv].dims == cor_syx or ds[ncv].dims == cor_sxy:
|
|
243
|
-
vars_syx.append(v)
|
|
244
|
-
elif ds[ncv].dims == cor_sh:
|
|
245
|
-
vars_sh.append(v)
|
|
246
|
-
elif ds[ncv].dims == cor_s:
|
|
247
|
-
vars_s.append(v)
|
|
248
|
-
else:
|
|
249
|
-
expc = [
|
|
250
|
-
c
|
|
251
|
-
for c in [cor_shxy, cor_shyx, cor_sxy, cor_syx, cor_sh, cor_s]
|
|
252
|
-
if None not in c
|
|
253
|
-
]
|
|
254
|
-
raise ValueError(
|
|
255
|
-
f"States '{self.name}': Wrong coordinates for variable '{ncv}': Found {ds[ncv].dims}, expecting one of {expc}"
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
data = np.zeros(
|
|
259
|
-
(n_sts, n_h, n_y, n_x, len(self.var2ncvar)), dtype=config.dtype_double
|
|
260
|
-
)
|
|
261
|
-
for v in vars_shyx:
|
|
262
|
-
ncv = self.var2ncvar[v]
|
|
263
|
-
if ds[ncv].dims == cor_shyx:
|
|
264
|
-
data[..., self._dkys[v]] = ds[ncv][:]
|
|
265
|
-
else:
|
|
266
|
-
data[..., self._dkys[v]] = np.swapaxes(ds[ncv].to_numpy(), 2, 3)
|
|
267
|
-
for v in vars_syx:
|
|
268
|
-
ncv = self.var2ncvar[v]
|
|
269
|
-
if ds[ncv].dims == cor_syx:
|
|
270
|
-
data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, None]
|
|
271
|
-
else:
|
|
272
|
-
data[..., self._dkys[v]] = np.swapaxes(ds[ncv].to_numpy(), 1, 2)[
|
|
273
|
-
:, None
|
|
274
|
-
]
|
|
275
|
-
for v in vars_sh:
|
|
276
|
-
ncv = self.var2ncvar[v]
|
|
277
|
-
data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, :, None, None]
|
|
278
|
-
for v in vars_s:
|
|
279
|
-
ncv = self.var2ncvar[v]
|
|
280
|
-
data[..., self._dkys[v]] = ds[ncv].to_numpy()[:, None, None, None]
|
|
281
|
-
if FV.WD in self.fixed_vars:
|
|
282
|
-
data[..., self._dkys[FV.WD]] = np.full(
|
|
283
|
-
(n_sts, n_h, n_y, n_x),
|
|
284
|
-
self.fixed_vars[FV.WD],
|
|
285
|
-
dtype=config.dtype_double,
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
weights = None
|
|
289
|
-
if self.weight_ncvar is not None:
|
|
290
|
-
if self.weight_ncvar not in ds.data_vars:
|
|
291
|
-
raise KeyError(
|
|
292
|
-
f"States '{self.name}': Missing weights variable '{self.weight_ncvar}' in data, found {sorted(list(ds.data_vars.keys()))}"
|
|
293
|
-
)
|
|
294
|
-
if ds[self.weight_ncvar].dims != (self.states_coord,):
|
|
295
|
-
raise ValueError(
|
|
296
|
-
f"States '{self.name}': Weights variable '{self.weight_ncvar}' has wrong dimensions. Expecting {(self.states_coord,)}, got {ds[self.weight_ncvar].dims}"
|
|
297
|
-
)
|
|
298
|
-
weights = ds[self.weight_ncvar].to_numpy()
|
|
299
|
-
|
|
300
|
-
if verbosity > 1:
|
|
301
|
-
print(f"\n{self.name}: Data ranges")
|
|
302
|
-
for v, i in self._dkys.items():
|
|
303
|
-
d = data[..., i]
|
|
304
|
-
nn = np.sum(np.isnan(d))
|
|
305
|
-
print(
|
|
306
|
-
f" {v}: {np.nanmin(d)} --> {np.nanmax(d)}, nans: {nn} ({100 * nn / len(d.flat):.2f}%)"
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
return sts, h, y, x, data, weights
|
|
310
|
-
|
|
311
|
-
def output_point_vars(self, algo):
|
|
312
|
-
"""
|
|
313
|
-
The variables which are being modified by the model.
|
|
314
|
-
|
|
315
|
-
Parameters
|
|
316
|
-
----------
|
|
317
|
-
algo: foxes.core.Algorithm
|
|
318
|
-
The calculation algorithm
|
|
319
|
-
|
|
320
|
-
Returns
|
|
321
|
-
-------
|
|
322
|
-
output_vars: list of str
|
|
323
|
-
The output variable names
|
|
324
|
-
|
|
325
|
-
"""
|
|
326
|
-
return self.ovars
|
|
327
|
-
|
|
328
|
-
def load_data(self, algo, verbosity=0):
|
|
329
|
-
"""
|
|
330
|
-
Load and/or create all model data that is subject to chunking.
|
|
331
|
-
|
|
332
|
-
Such data should not be stored under self, for memory reasons. The
|
|
333
|
-
data returned here will automatically be chunked and then provided
|
|
334
|
-
as part of the mdata object during calculations.
|
|
335
|
-
|
|
336
|
-
Parameters
|
|
337
|
-
----------
|
|
338
|
-
algo: foxes.core.Algorithm
|
|
339
|
-
The calculation algorithm
|
|
340
|
-
verbosity: int
|
|
341
|
-
The verbosity level, 0 = silent
|
|
342
|
-
|
|
343
|
-
Returns
|
|
344
|
-
-------
|
|
345
|
-
idata: dict
|
|
346
|
-
The dict has exactly two entries: `data_vars`,
|
|
347
|
-
a dict with entries `name_str -> (dim_tuple, data_ndarray)`;
|
|
348
|
-
and `coords`, a dict with entries `dim_name_str -> dim_array`
|
|
349
|
-
|
|
350
|
-
"""
|
|
351
|
-
|
|
352
|
-
# pre-load file reading:
|
|
353
|
-
coords = [self.states_coord, self.h_coord, self.y_coord, self.x_coord]
|
|
354
|
-
if not isinstance(self.data_source, xr.Dataset):
|
|
355
|
-
# check variables:
|
|
356
|
-
for v in self.ovars:
|
|
357
|
-
if v == FV.WEIGHT and self.weight_ncvar is None:
|
|
358
|
-
pass
|
|
359
|
-
elif v not in self.var2ncvar and v not in self.fixed_vars:
|
|
360
|
-
raise ValueError(
|
|
361
|
-
f"States '{self.name}': Variable '{v}' neither found in var2ncvar not in fixed_vars"
|
|
362
|
-
)
|
|
363
|
-
if (FV.WS in self.ovars and FV.WD not in self.ovars) or (
|
|
364
|
-
FV.WS not in self.ovars and FV.WD in self.ovars
|
|
365
|
-
):
|
|
366
|
-
raise KeyError(
|
|
367
|
-
f"States '{self.name}': Missing '{FV.WS}' or '{FV.WD}' in output variables {self.ovars}"
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
# check static data:
|
|
371
|
-
fpath = get_input_path(self.data_source)
|
|
372
|
-
if "*" not in str(self.data_source):
|
|
373
|
-
if not fpath.is_file():
|
|
374
|
-
fpath = StaticData().get_file_path(
|
|
375
|
-
STATES, fpath.name, check_raw=False
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
# find bounds:
|
|
379
|
-
if self.x_coord is not None and self.x_coord not in self.sel:
|
|
380
|
-
xy_min, xy_max = algo.farm.get_xy_bounds(
|
|
381
|
-
extra_space=self.bounds_extra_space, algo=algo
|
|
382
|
-
)
|
|
383
|
-
if verbosity > 0:
|
|
384
|
-
print(
|
|
385
|
-
f"States '{self.name}': Restricting to bounds {xy_min} - {xy_max}"
|
|
386
|
-
)
|
|
387
|
-
self.sel.update(
|
|
388
|
-
{
|
|
389
|
-
self.x_coord: slice(xy_min[0], xy_max[1]),
|
|
390
|
-
self.y_coord: slice(xy_min[1], xy_max[1]),
|
|
391
|
-
}
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
# read files:
|
|
395
|
-
if verbosity > 0:
|
|
396
|
-
if self.load_mode == "preload":
|
|
397
|
-
print(
|
|
398
|
-
f"States '{self.name}': Reading data from '{self.data_source}'"
|
|
399
|
-
)
|
|
400
|
-
elif self.load_mode == "lazy":
|
|
401
|
-
print(
|
|
402
|
-
f"States '{self.name}': Reading header from '{self.data_source}'"
|
|
403
|
-
)
|
|
404
|
-
else:
|
|
405
|
-
print(
|
|
406
|
-
f"States '{self.name}': Reading states from '{self.data_source}'"
|
|
407
|
-
)
|
|
408
|
-
files = sorted(list(fpath.resolve().parent.glob(fpath.name)))
|
|
409
|
-
vars = list(self.var2ncvar.values())
|
|
410
|
-
if self.weight_ncvar is not None:
|
|
411
|
-
vars += [self.weight_ncvar]
|
|
412
|
-
self.__data_source = get_engine().map(
|
|
413
|
-
_read_nc_file,
|
|
414
|
-
files,
|
|
415
|
-
coords=coords,
|
|
416
|
-
vars=vars,
|
|
417
|
-
nc_engine=config.nc_engine,
|
|
418
|
-
isel=self.isel,
|
|
419
|
-
sel=self.sel,
|
|
420
|
-
minimal=self.load_mode == "fly",
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
if self.load_mode in ["preload", "lazy"]:
|
|
424
|
-
if self.load_mode == "lazy":
|
|
425
|
-
try:
|
|
426
|
-
self.__data_source = [ds.chunk() for ds in self.__data_source]
|
|
427
|
-
except (ModuleNotFoundError, ValueError) as e:
|
|
428
|
-
import_module("dask")
|
|
429
|
-
raise e
|
|
430
|
-
self.__data_source = xr.concat(
|
|
431
|
-
self.__data_source,
|
|
432
|
-
dim=self.states_coord,
|
|
433
|
-
coords="minimal",
|
|
434
|
-
data_vars="minimal",
|
|
435
|
-
compat="equals",
|
|
436
|
-
join="exact",
|
|
437
|
-
combine_attrs="drop",
|
|
438
|
-
)
|
|
439
|
-
if self.load_mode == "preload":
|
|
440
|
-
self.__data_source.load()
|
|
441
|
-
self.__inds = self.__data_source[self.states_coord].to_numpy()
|
|
442
|
-
self._N = len(self.__inds)
|
|
443
|
-
|
|
444
|
-
elif self.load_mode == "fly":
|
|
445
|
-
self.__inds = self.__data_source
|
|
446
|
-
self.__data_source = fpath
|
|
447
|
-
self._files_maxi = {f: len(inds) for f, inds in zip(files, self.__inds)}
|
|
448
|
-
self.__inds = np.concatenate(self.__inds, axis=0)
|
|
449
|
-
self._N = len(self.__inds)
|
|
450
|
-
|
|
451
|
-
else:
|
|
452
|
-
raise KeyError(
|
|
453
|
-
f"States '{self.name}': Unknown load_mode '{self.load_mode}', choices: preload, lazy, fly"
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
if self.time_format is not None:
|
|
457
|
-
self.__inds = pd.to_datetime(
|
|
458
|
-
self.__inds, format=self.time_format
|
|
459
|
-
).to_numpy()
|
|
460
|
-
|
|
461
|
-
# given data is already Dataset:
|
|
462
|
-
else:
|
|
463
|
-
self.__inds = self.data_source[self.states_coord].to_numpy()
|
|
464
|
-
self._N = len(self.__inds)
|
|
465
|
-
|
|
466
|
-
# ensure WD and WS get the first two slots of data:
|
|
467
|
-
self._dkys = {}
|
|
468
|
-
if FV.WS in self.ovars:
|
|
469
|
-
self._dkys[FV.WD] = 0
|
|
470
|
-
if FV.WS in self.var2ncvar:
|
|
471
|
-
self._dkys[FV.WS] = 1
|
|
472
|
-
for v in self.var2ncvar:
|
|
473
|
-
if v not in self._dkys:
|
|
474
|
-
self._dkys[v] = len(self._dkys)
|
|
475
|
-
self._n_dvars = len(self._dkys)
|
|
476
|
-
|
|
477
|
-
idata = super().load_data(algo, verbosity)
|
|
478
|
-
|
|
479
|
-
if self.load_mode == "preload":
|
|
480
|
-
self.X = self.var(FV.X)
|
|
481
|
-
self.Y = self.var(FV.Y)
|
|
482
|
-
self.H = self.var(FV.H)
|
|
483
|
-
self.VARS = self.var("vars")
|
|
484
|
-
self.DATA = self.var("data")
|
|
485
|
-
self.WEIGHT = self.var(FV.WEIGHT)
|
|
486
|
-
|
|
487
|
-
__, h, y, x, data, weights = self._get_data(
|
|
488
|
-
self.data_source, coords, verbosity
|
|
489
|
-
)
|
|
490
|
-
self._prl_coords = coords
|
|
491
|
-
|
|
492
|
-
coos = (FC.STATE, self.H, self.Y, self.X, self.VARS)
|
|
493
|
-
data = (coos, data)
|
|
494
|
-
|
|
495
|
-
idata["coords"][self.H] = h
|
|
496
|
-
idata["coords"][self.Y] = y
|
|
497
|
-
idata["coords"][self.X] = x
|
|
498
|
-
idata["coords"][self.VARS] = list(self._dkys.keys())
|
|
499
|
-
idata["data_vars"][self.DATA] = data
|
|
500
|
-
if weights is not None:
|
|
501
|
-
idata["data_vars"][self.WEIGHT] = ((FC.STATE,), weights)
|
|
502
|
-
|
|
503
|
-
return idata
|
|
504
|
-
|
|
505
|
-
def set_running(
|
|
506
|
-
self,
|
|
507
|
-
algo,
|
|
508
|
-
data_stash,
|
|
509
|
-
sel=None,
|
|
510
|
-
isel=None,
|
|
511
|
-
verbosity=0,
|
|
512
|
-
):
|
|
513
|
-
"""
|
|
514
|
-
Sets this model status to running, and moves
|
|
515
|
-
all large data to stash.
|
|
516
|
-
|
|
517
|
-
The stashed data will be returned by the
|
|
518
|
-
unset_running() function after running calculations.
|
|
519
|
-
|
|
520
|
-
Parameters
|
|
521
|
-
----------
|
|
522
|
-
algo: foxes.core.Algorithm
|
|
523
|
-
The calculation algorithm
|
|
524
|
-
data_stash: dict
|
|
525
|
-
Large data stash, this function adds data here.
|
|
526
|
-
Key: model name. Value: dict, large model data
|
|
527
|
-
sel: dict, optional
|
|
528
|
-
The subset selection dictionary
|
|
529
|
-
isel: dict, optional
|
|
530
|
-
The index subset selection dictionary
|
|
531
|
-
verbosity: int
|
|
532
|
-
The verbosity level, 0 = silent
|
|
533
|
-
|
|
534
|
-
"""
|
|
535
|
-
super().set_running(algo, data_stash, sel, isel, verbosity)
|
|
536
|
-
|
|
537
|
-
data_stash[self.name] = dict(
|
|
538
|
-
inds=self.__inds,
|
|
539
|
-
)
|
|
540
|
-
del self.__inds
|
|
541
|
-
|
|
542
|
-
if self.load_mode == "preload":
|
|
543
|
-
data_stash[self.name]["data_source"] = self.__data_source
|
|
544
|
-
del self.__data_source
|
|
545
|
-
|
|
546
|
-
def unset_running(
|
|
547
|
-
self,
|
|
548
|
-
algo,
|
|
549
|
-
data_stash,
|
|
550
|
-
sel=None,
|
|
551
|
-
isel=None,
|
|
552
|
-
verbosity=0,
|
|
553
|
-
):
|
|
554
|
-
"""
|
|
555
|
-
Sets this model status to not running, recovering large data
|
|
556
|
-
from stash
|
|
557
|
-
|
|
558
|
-
Parameters
|
|
559
|
-
----------
|
|
560
|
-
algo: foxes.core.Algorithm
|
|
561
|
-
The calculation algorithm
|
|
562
|
-
data_stash: dict
|
|
563
|
-
Large data stash, this function adds data here.
|
|
564
|
-
Key: model name. Value: dict, large model data
|
|
565
|
-
sel: dict, optional
|
|
566
|
-
The subset selection dictionary
|
|
567
|
-
isel: dict, optional
|
|
568
|
-
The index subset selection dictionary
|
|
569
|
-
verbosity: int
|
|
570
|
-
The verbosity level, 0 = silent
|
|
571
|
-
|
|
572
|
-
"""
|
|
573
|
-
super().unset_running(algo, data_stash, sel, isel, verbosity)
|
|
574
|
-
|
|
575
|
-
data = data_stash[self.name]
|
|
576
|
-
self.__inds = data.pop("inds")
|
|
577
|
-
|
|
578
|
-
if self.load_mode == "preload":
|
|
579
|
-
self.__data_source = data.pop("data_source")
|
|
580
|
-
|
|
581
|
-
def size(self):
|
|
582
|
-
"""
|
|
583
|
-
The total number of states.
|
|
584
|
-
|
|
585
|
-
Returns
|
|
586
|
-
-------
|
|
587
|
-
int:
|
|
588
|
-
The total number of states
|
|
589
|
-
|
|
590
|
-
"""
|
|
591
|
-
return self._N
|
|
592
|
-
|
|
593
|
-
def index(self):
|
|
594
|
-
"""
|
|
595
|
-
The index list
|
|
596
|
-
|
|
597
|
-
Returns
|
|
598
|
-
-------
|
|
599
|
-
indices: array_like
|
|
600
|
-
The index labels of states, or None for default integers
|
|
601
|
-
|
|
602
|
-
"""
|
|
603
|
-
if self.running:
|
|
604
|
-
raise ValueError(f"States '{self.name}': Cannot access index while running")
|
|
605
|
-
return self.__inds
|
|
606
|
-
|
|
607
|
-
def calculate(self, algo, mdata, fdata, tdata):
|
|
608
|
-
"""
|
|
609
|
-
The main model calculation.
|
|
610
|
-
|
|
611
|
-
This function is executed on a single chunk of data,
|
|
612
|
-
all computations should be based on numpy arrays.
|
|
613
|
-
|
|
614
|
-
Parameters
|
|
615
|
-
----------
|
|
616
|
-
algo: foxes.core.Algorithm
|
|
617
|
-
The calculation algorithm
|
|
618
|
-
mdata: foxes.core.MData
|
|
619
|
-
The model data
|
|
620
|
-
fdata: foxes.core.FData
|
|
621
|
-
The farm data
|
|
622
|
-
tdata: foxes.core.TData
|
|
623
|
-
The target point data
|
|
624
|
-
|
|
625
|
-
Returns
|
|
626
|
-
-------
|
|
627
|
-
results: dict
|
|
628
|
-
The resulting data, keys: output variable str.
|
|
629
|
-
Values: numpy.ndarray with shape
|
|
630
|
-
(n_states, n_targets, n_tpoints)
|
|
631
|
-
|
|
632
|
-
"""
|
|
633
|
-
# prepare:
|
|
634
|
-
n_states = tdata.n_states
|
|
635
|
-
n_targets = tdata.n_targets
|
|
636
|
-
n_tpoints = tdata.n_tpoints
|
|
637
|
-
points = tdata[FC.TARGETS].reshape(n_states, n_targets * n_tpoints, 3)
|
|
638
|
-
n_pts = points.shape[1]
|
|
639
|
-
n_states = fdata.n_states
|
|
640
|
-
coords = [self.states_coord, self.h_coord, self.y_coord, self.x_coord]
|
|
641
|
-
|
|
642
|
-
# case preload:
|
|
643
|
-
if self.load_mode == "preload":
|
|
644
|
-
x = mdata[self.X]
|
|
645
|
-
y = mdata[self.Y]
|
|
646
|
-
h = mdata[self.H]
|
|
647
|
-
data = mdata[self.DATA].copy()
|
|
648
|
-
weights = mdata.get(self.WEIGHT, None)
|
|
649
|
-
coords = self._prl_coords
|
|
650
|
-
|
|
651
|
-
# case lazy:
|
|
652
|
-
elif self.load_mode == "lazy":
|
|
653
|
-
i0 = mdata.states_i0(counter=True)
|
|
654
|
-
s = slice(i0, i0 + n_states)
|
|
655
|
-
ds = self.data_source.isel({self.states_coord: s}).load()
|
|
656
|
-
__, h, y, x, data, weights = self._get_data(ds, coords, verbosity=0)
|
|
657
|
-
del ds
|
|
658
|
-
|
|
659
|
-
# case fly:
|
|
660
|
-
elif self.load_mode == "fly":
|
|
661
|
-
vars = list(self.var2ncvar.values())
|
|
662
|
-
if self.weight_ncvar is not None:
|
|
663
|
-
vars += [self.weight_ncvar]
|
|
664
|
-
|
|
665
|
-
i0 = mdata.states_i0(counter=True)
|
|
666
|
-
i1 = i0 + n_states
|
|
667
|
-
j0 = 0
|
|
668
|
-
data = []
|
|
669
|
-
for fpath, n in self._files_maxi.items():
|
|
670
|
-
if i0 < j0:
|
|
671
|
-
break
|
|
672
|
-
else:
|
|
673
|
-
j1 = j0 + n
|
|
674
|
-
if i0 < j1:
|
|
675
|
-
a = i0 - j0
|
|
676
|
-
b = min(i1, j1) - j0
|
|
677
|
-
isel = copy(self.isel)
|
|
678
|
-
isel[self.states_coord] = slice(a, b)
|
|
679
|
-
|
|
680
|
-
data.append(
|
|
681
|
-
_read_nc_file(
|
|
682
|
-
fpath,
|
|
683
|
-
coords=coords,
|
|
684
|
-
vars=vars,
|
|
685
|
-
nc_engine=config.nc_engine,
|
|
686
|
-
isel=isel,
|
|
687
|
-
sel=self.sel,
|
|
688
|
-
minimal=False,
|
|
689
|
-
)
|
|
690
|
-
)
|
|
691
|
-
|
|
692
|
-
i0 += b - a
|
|
693
|
-
j0 = j1
|
|
694
|
-
|
|
695
|
-
assert i0 == i1, (
|
|
696
|
-
f"States '{self.name}': Missing states for load_mode '{self.load_mode}': (i0, i1) = {(i0, i1)}"
|
|
697
|
-
)
|
|
698
|
-
|
|
699
|
-
data = xr.concat(data, dim=self.states_coord)
|
|
700
|
-
__, h, y, x, data, weights = self._get_data(data, coords, verbosity=0)
|
|
701
|
-
|
|
702
|
-
else:
|
|
703
|
-
raise KeyError(
|
|
704
|
-
f"States '{self.name}': Unknown load_mode '{self.load_mode}', choices: preload, lazy, fly"
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
n_h = len(h)
|
|
708
|
-
n_y = len(y)
|
|
709
|
-
n_x = len(x)
|
|
710
|
-
|
|
711
|
-
# translate WS, WD into U, V:
|
|
712
|
-
if FV.WD in self.ovars and FV.WS in self.ovars:
|
|
713
|
-
wd = data[..., self._dkys[FV.WD]]
|
|
714
|
-
ws = (
|
|
715
|
-
data[..., self._dkys[FV.WS]]
|
|
716
|
-
if FV.WS in self._dkys
|
|
717
|
-
else self.fixed_vars[FV.WS]
|
|
718
|
-
)
|
|
719
|
-
wdwsi = [self._dkys[FV.WD], self._dkys[FV.WS]]
|
|
720
|
-
data[..., wdwsi] = wd2uv(wd, ws, axis=-1)
|
|
721
|
-
del ws, wd
|
|
722
|
-
|
|
723
|
-
# prepare points:
|
|
724
|
-
sts = np.arange(n_states)
|
|
725
|
-
pts = np.append(
|
|
726
|
-
points, np.zeros((n_states, n_pts, 1), dtype=config.dtype_double), axis=2
|
|
727
|
-
)
|
|
728
|
-
pts[:, :, 3] = sts[:, None]
|
|
729
|
-
pts = pts.reshape(n_states * n_pts, 4)
|
|
730
|
-
pts = np.flip(pts, axis=1)
|
|
731
|
-
gvars = (sts, h, y, x)
|
|
732
|
-
|
|
733
|
-
# reset None coordinate data, since that should not be interpolated:
|
|
734
|
-
for i, (c, g) in enumerate(zip(coords, gvars)):
|
|
735
|
-
if c is None:
|
|
736
|
-
pts[..., i] = g[0]
|
|
737
|
-
|
|
738
|
-
# interpolate nan values:
|
|
739
|
-
if self.interp_nans and np.any(np.isnan(data)):
|
|
740
|
-
df = pd.DataFrame(
|
|
741
|
-
index=pd.MultiIndex.from_product(
|
|
742
|
-
gvars, names=["state", "height", "y", "x"]
|
|
743
|
-
),
|
|
744
|
-
data={
|
|
745
|
-
v: data[..., vi].reshape(n_states * n_h * n_y * n_x)
|
|
746
|
-
for v, vi in self._dkys.items()
|
|
747
|
-
},
|
|
748
|
-
)
|
|
749
|
-
df.interpolate(
|
|
750
|
-
axis=0, method="linear", limit_direction="forward", inplace=True
|
|
751
|
-
)
|
|
752
|
-
df.interpolate(
|
|
753
|
-
axis=0, method="linear", limit_direction="backward", inplace=True
|
|
754
|
-
)
|
|
755
|
-
data = df.to_numpy().reshape(n_states, n_h, n_y, n_x, self._n_dvars)
|
|
756
|
-
del df
|
|
757
|
-
|
|
758
|
-
# interpolate:
|
|
759
|
-
try:
|
|
760
|
-
ipars = dict(bounds_error=True, fill_value=None)
|
|
761
|
-
ipars.update(self.interpn_pars)
|
|
762
|
-
data = interpn(gvars, data, pts, **ipars).reshape(
|
|
763
|
-
n_states, n_pts, self._n_dvars
|
|
764
|
-
)
|
|
765
|
-
except ValueError as e:
|
|
766
|
-
print(f"\nStates '{self.name}': Interpolation error")
|
|
767
|
-
print("INPUT VARS: (state, heights, y, x)")
|
|
768
|
-
print(
|
|
769
|
-
"DATA BOUNDS:",
|
|
770
|
-
[float(np.min(d)) for d in gvars],
|
|
771
|
-
[float(np.max(d)) for d in gvars],
|
|
772
|
-
)
|
|
773
|
-
print(
|
|
774
|
-
"EVAL BOUNDS:",
|
|
775
|
-
[float(np.min(p)) for p in pts.T],
|
|
776
|
-
[float(np.max(p)) for p in pts.T],
|
|
777
|
-
)
|
|
778
|
-
print(
|
|
779
|
-
"\nMaybe you want to try the option 'bounds_error=False'? This will extrapolate the data.\n"
|
|
780
|
-
)
|
|
781
|
-
raise e
|
|
782
|
-
del pts, x, y, h, gvars
|
|
783
|
-
|
|
784
|
-
# interpolate nan values:
|
|
785
|
-
if self.interp_nans and np.any(np.isnan(data)):
|
|
786
|
-
df = pd.DataFrame(
|
|
787
|
-
index=pd.MultiIndex.from_product(
|
|
788
|
-
(sts, range(n_pts)), names=["state", "point"]
|
|
789
|
-
),
|
|
790
|
-
data={
|
|
791
|
-
v: data[:, :, vi].reshape(n_states * n_pts)
|
|
792
|
-
for v, vi in self._dkys.items()
|
|
793
|
-
},
|
|
794
|
-
)
|
|
795
|
-
df["x"] = points[:, :, 0].reshape(n_states * n_pts)
|
|
796
|
-
df["y"] = points[:, :, 1].reshape(n_states * n_pts)
|
|
797
|
-
df = df.reset_index().set_index(["state", "x", "y"])
|
|
798
|
-
df.interpolate(
|
|
799
|
-
axis=0, method="linear", limit_direction="forward", inplace=True
|
|
800
|
-
)
|
|
801
|
-
df.interpolate(
|
|
802
|
-
axis=0, method="linear", limit_direction="backward", inplace=True
|
|
803
|
-
)
|
|
804
|
-
df = df.reset_index().drop(["x", "y"], axis=1).set_index(["state", "point"])
|
|
805
|
-
data = df.to_numpy().reshape(n_states, n_pts, self._n_dvars)
|
|
806
|
-
del df
|
|
807
|
-
|
|
808
|
-
# set output:
|
|
809
|
-
out = {}
|
|
810
|
-
if FV.WD in self.ovars and FV.WS in self.ovars:
|
|
811
|
-
uv = data[..., wdwsi]
|
|
812
|
-
out[FV.WS] = np.linalg.norm(uv, axis=-1)
|
|
813
|
-
out[FV.WD] = uv2wd(uv, axis=-1)
|
|
814
|
-
del uv
|
|
815
|
-
for v in self.ovars:
|
|
816
|
-
if v != FV.WEIGHT and v not in out:
|
|
817
|
-
if v in self._dkys:
|
|
818
|
-
out[v] = data[..., self._dkys[v]]
|
|
819
|
-
elif v in self.fixed_vars:
|
|
820
|
-
out[v] = np.full(
|
|
821
|
-
(n_states, n_pts), self.fixed_vars[v], dtype=config.dtype_double
|
|
822
|
-
)
|
|
823
|
-
|
|
824
|
-
# add weights:
|
|
825
|
-
if weights is not None:
|
|
826
|
-
tdata[FV.WEIGHT] = weights[:, None, None]
|
|
827
|
-
else:
|
|
828
|
-
tdata[FV.WEIGHT] = np.full(
|
|
829
|
-
(mdata.n_states, 1, 1), 1 / self._N, dtype=config.dtype_double
|
|
830
|
-
)
|
|
831
|
-
tdata.dims[FV.WEIGHT] = (FC.STATE, FC.TARGET, FC.TPOINT)
|
|
832
|
-
|
|
833
|
-
return {v: d.reshape(n_states, n_targets, n_tpoints) for v, d in out.items()}
|