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
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.interpolate import (
|
|
3
|
+
LinearNDInterpolator,
|
|
4
|
+
NearestNDInterpolator,
|
|
5
|
+
RBFInterpolator,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from foxes.config import config
|
|
9
|
+
from foxes.utils import wd2uv, uv2wd, weibull_weights
|
|
10
|
+
import foxes.variables as FV
|
|
11
|
+
import foxes.constants as FC
|
|
12
|
+
|
|
13
|
+
from .dataset_states import DatasetStates
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PointCloudData(DatasetStates):
|
|
17
|
+
"""
|
|
18
|
+
Inflow data with point cloud support.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
states_coord: str
|
|
23
|
+
The states coordinate name in the data
|
|
24
|
+
point_coord: str
|
|
25
|
+
The point coordinate name in the data
|
|
26
|
+
x_ncvar: str
|
|
27
|
+
The x variable name in the data
|
|
28
|
+
y_ncvar: str
|
|
29
|
+
The y variable name in the data
|
|
30
|
+
h_ncvar: str, optional
|
|
31
|
+
The height variable name in the data
|
|
32
|
+
weight_ncvar: str, optional
|
|
33
|
+
The name of the weights variable in the data
|
|
34
|
+
interp_method: str
|
|
35
|
+
The interpolation method, "linear", "nearest" or "radialBasisFunction"
|
|
36
|
+
interp_fallback_nearest: bool
|
|
37
|
+
If True, use nearest neighbor interpolation if the
|
|
38
|
+
interpolation method fails.
|
|
39
|
+
interp_pars: dict
|
|
40
|
+
Additional arguments for the interpolation
|
|
41
|
+
|
|
42
|
+
:group: input.states
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
*args,
|
|
49
|
+
states_coord="Time",
|
|
50
|
+
point_coord="point",
|
|
51
|
+
x_ncvar="x",
|
|
52
|
+
y_ncvar="y",
|
|
53
|
+
h_ncvar=None,
|
|
54
|
+
weight_ncvar=None,
|
|
55
|
+
interp_method="linear",
|
|
56
|
+
interp_fallback_nearest=False,
|
|
57
|
+
interp_pars={},
|
|
58
|
+
**kwargs,
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
Constructor.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
args: tuple, optional
|
|
66
|
+
Arguments for the base class
|
|
67
|
+
states_coord: str
|
|
68
|
+
The states coordinate name in the data
|
|
69
|
+
point_coord: str
|
|
70
|
+
The point coordinate name in the data
|
|
71
|
+
x_ncvar: str
|
|
72
|
+
The x variable name in the data
|
|
73
|
+
y_ncvar: str
|
|
74
|
+
The y variable name in the data
|
|
75
|
+
h_ncvar: str, optional
|
|
76
|
+
The height variable name in the data
|
|
77
|
+
weight_ncvar: str, optional
|
|
78
|
+
The name of the weights variable in the data
|
|
79
|
+
interp_method: str
|
|
80
|
+
The interpolation method, "linear", "nearest" or "radialBasisFunction"
|
|
81
|
+
interp_fallback_nearest: bool
|
|
82
|
+
If True, use nearest neighbor interpolation if the
|
|
83
|
+
interpolation method fails.
|
|
84
|
+
interp_pars: dict
|
|
85
|
+
Additional arguments for the interpolation
|
|
86
|
+
kwargs: dict, optional
|
|
87
|
+
Additional parameters for the base class
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
super().__init__(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
self.states_coord = states_coord
|
|
93
|
+
self.point_coord = point_coord
|
|
94
|
+
self.x_ncvar = x_ncvar
|
|
95
|
+
self.y_ncvar = y_ncvar
|
|
96
|
+
self.h_ncvar = h_ncvar
|
|
97
|
+
self.weight_ncvar = weight_ncvar
|
|
98
|
+
self.interp_method = interp_method
|
|
99
|
+
self.interp_pars = interp_pars
|
|
100
|
+
self.interp_fallback_nearest = interp_fallback_nearest
|
|
101
|
+
|
|
102
|
+
self.variables = [FV.X, FV.Y]
|
|
103
|
+
self.variables += [v for v in self.ovars if v not in self.fixed_vars]
|
|
104
|
+
self.var2ncvar[FV.X] = x_ncvar
|
|
105
|
+
self.var2ncvar[FV.Y] = y_ncvar
|
|
106
|
+
if weight_ncvar is not None:
|
|
107
|
+
self.var2ncvar[FV.WEIGHT] = weight_ncvar
|
|
108
|
+
self.variables.append(FV.WEIGHT)
|
|
109
|
+
elif FV.WEIGHT in self.var2ncvar:
|
|
110
|
+
raise KeyError(
|
|
111
|
+
f"States '{self.name}': Cannot have '{FV.WEIGHT}' in var2ncvar, use weight_ncvar instead"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self._n_pt = None
|
|
115
|
+
self._n_wd = None
|
|
116
|
+
self._n_ws = None
|
|
117
|
+
|
|
118
|
+
if FV.WS not in self.ovars:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"States '{self.name}': Expecting output variable '{FV.WS}', got {self.ovars}"
|
|
121
|
+
)
|
|
122
|
+
if FV.WD not in self.ovars:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"States '{self.name}': Expecting output variable '{FV.WD}', got {self.ovars}"
|
|
125
|
+
)
|
|
126
|
+
for v in [FV.WEIBULL_A, FV.WEIBULL_k, FV.WEIGHT]:
|
|
127
|
+
if v in self.ovars:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"States '{self.name}': Cannot have '{v}' as output variable"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self._cmap = {
|
|
133
|
+
FC.STATE: self.states_coord,
|
|
134
|
+
FC.POINT: self.point_coord,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
def __repr__(self):
|
|
138
|
+
return f"{type(self).__name__}(n_pt={self._n_pt}, n_wd={self._n_wd}, n_ws={self._n_ws})"
|
|
139
|
+
|
|
140
|
+
def _read_ds(self, ds, cmap, variables, verbosity=0):
|
|
141
|
+
"""
|
|
142
|
+
Helper function for _get_data, extracts data from the original Dataset.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
ds: xarray.Dataset
|
|
147
|
+
The Dataset to read data from
|
|
148
|
+
cmap: dict
|
|
149
|
+
A mapping from foxes variable names to Dataset dimension names
|
|
150
|
+
variables: list of str
|
|
151
|
+
The variables to extract from the Dataset
|
|
152
|
+
verbosity: int
|
|
153
|
+
The verbosity level, 0 = silent
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
coords: dict
|
|
158
|
+
keys: Foxes variable names, values: 1D coordinate value arrays
|
|
159
|
+
data: dict
|
|
160
|
+
The extracted data, keys are variable names,
|
|
161
|
+
values are tuples (dims, data_array)
|
|
162
|
+
where dims is a tuple of dimension names and
|
|
163
|
+
data_array is a numpy.ndarray with the data values
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
coords, data = super()._read_ds(ds, cmap, variables, verbosity)
|
|
167
|
+
|
|
168
|
+
assert FV.X in data and FV.Y in data, (
|
|
169
|
+
f"States '{self.name}': Expecting variables '{FV.X}' and '{FV.Y}' in data, found {list(data.keys())}"
|
|
170
|
+
)
|
|
171
|
+
assert data[FV.X][0] == (FC.POINT,), (
|
|
172
|
+
f"States '{self.name}': Expecting variable '{FV.X}' to have dimensions '({FC.POINT},)', got {data[FV.X][0]}"
|
|
173
|
+
)
|
|
174
|
+
assert data[FV.Y][0] == (FC.POINT,), (
|
|
175
|
+
f"States '{self.name}': Expecting variable '{FV.Y}' to have dimensions '({FC.POINT},)', got {data[FV.Y][0]}"
|
|
176
|
+
)
|
|
177
|
+
if FV.H in data:
|
|
178
|
+
assert data[FV.H][0] == (FC.POINT,), (
|
|
179
|
+
f"States '{self.name}': Expecting variable '{FV.H}' to have dimensions '({FC.POINT},)', got {data[FV.H][0]}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
points = [data.pop(FV.X)[1], data.pop(FV.Y)[1]]
|
|
183
|
+
if FV.H in data:
|
|
184
|
+
points.append(data.pop(FV.H)[1])
|
|
185
|
+
coords[FC.POINT] = np.stack(points, axis=-1)
|
|
186
|
+
|
|
187
|
+
return coords, data
|
|
188
|
+
|
|
189
|
+
def load_data(self, algo, verbosity=0):
|
|
190
|
+
"""
|
|
191
|
+
Load and/or create all model data that is subject to chunking.
|
|
192
|
+
|
|
193
|
+
Such data should not be stored under self, for memory reasons. The
|
|
194
|
+
data returned here will automatically be chunked and then provided
|
|
195
|
+
as part of the mdata object during calculations.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
algo: foxes.core.Algorithm
|
|
200
|
+
The calculation algorithm
|
|
201
|
+
verbosity: int
|
|
202
|
+
The verbosity level, 0 = silent
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
idata: dict
|
|
207
|
+
The dict has exactly two entries: `data_vars`,
|
|
208
|
+
a dict with entries `name_str -> (dim_tuple, data_ndarray)`;
|
|
209
|
+
and `coords`, a dict with entries `dim_name_str -> dim_array`
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
return super().load_data(
|
|
213
|
+
algo,
|
|
214
|
+
cmap=self._cmap,
|
|
215
|
+
variables=self.variables,
|
|
216
|
+
bounds_extra_space=None,
|
|
217
|
+
verbosity=verbosity,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def calculate(self, algo, mdata, fdata, tdata):
|
|
221
|
+
"""
|
|
222
|
+
The main model calculation.
|
|
223
|
+
|
|
224
|
+
This function is executed on a single chunk of data,
|
|
225
|
+
all computations should be based on numpy arrays.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
algo: foxes.core.Algorithm
|
|
230
|
+
The calculation algorithm
|
|
231
|
+
mdata: foxes.core.MData
|
|
232
|
+
The model data
|
|
233
|
+
fdata: foxes.core.FData
|
|
234
|
+
The farm data
|
|
235
|
+
tdata: foxes.core.TData
|
|
236
|
+
The target point data
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
results: dict
|
|
241
|
+
The resulting data, keys: output variable str.
|
|
242
|
+
Values: numpy.ndarray with shape
|
|
243
|
+
(n_states, n_targets, n_tpoints)
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
# prepare
|
|
247
|
+
self.ensure_output_vars(algo, tdata)
|
|
248
|
+
n_states = tdata.n_states
|
|
249
|
+
n_targets = tdata.n_targets
|
|
250
|
+
n_tpoints = tdata.n_tpoints
|
|
251
|
+
n_states = fdata.n_states
|
|
252
|
+
n_pts = n_states * n_targets * n_tpoints
|
|
253
|
+
coords = [self.states_coord, self.point_coord]
|
|
254
|
+
|
|
255
|
+
# get data for calculation
|
|
256
|
+
coords, data, weights = self.get_calc_data(mdata, self._cmap, self.variables)
|
|
257
|
+
coords[FC.STATE] = np.arange(n_states, dtype=config.dtype_int)
|
|
258
|
+
|
|
259
|
+
# interpolate data to points:
|
|
260
|
+
out = {}
|
|
261
|
+
for dims, (vrs, d) in data.items():
|
|
262
|
+
# prepare
|
|
263
|
+
n_vrs = len(vrs)
|
|
264
|
+
qts = coords[FC.POINT]
|
|
265
|
+
n_qts, n_dms = qts.shape
|
|
266
|
+
idims = dims[:-1]
|
|
267
|
+
|
|
268
|
+
if idims == (FC.STATE,):
|
|
269
|
+
for i, v in enumerate(vrs):
|
|
270
|
+
if v in self.ovars:
|
|
271
|
+
out[v] = np.zeros(
|
|
272
|
+
(n_states, n_targets, n_tpoints), dtype=config.dtype_double
|
|
273
|
+
)
|
|
274
|
+
out[v][:] = d[:, None, None, i]
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
elif idims == (FC.POINT,):
|
|
278
|
+
# prepare grid data
|
|
279
|
+
gts = qts
|
|
280
|
+
n_gts = n_qts
|
|
281
|
+
|
|
282
|
+
# prepare evaluation points
|
|
283
|
+
pts = tdata[FC.TARGETS][..., :n_dms].reshape(n_pts, n_dms)
|
|
284
|
+
|
|
285
|
+
elif idims == (FC.STATE, FC.POINT):
|
|
286
|
+
# prepare grid data, add state index to last axis
|
|
287
|
+
gts = np.zeros((n_qts, n_states, n_dms + 1), dtype=config.dtype_double)
|
|
288
|
+
gts[..., :n_dms] = qts[:, None, :]
|
|
289
|
+
gts[..., n_dms] = np.arange(n_states)[None, :]
|
|
290
|
+
n_gts = n_qts * n_states
|
|
291
|
+
gts = gts.reshape(n_gts, n_dms + 1)
|
|
292
|
+
|
|
293
|
+
# reorder data, first to shape (n_qts, n_states, n_vars),
|
|
294
|
+
# then to (n_gts, n_vrs)
|
|
295
|
+
d = np.swapaxes(d, 0, 1)
|
|
296
|
+
d = d.reshape(n_gts, n_vrs)
|
|
297
|
+
|
|
298
|
+
# prepare evaluation points, add state index to last axis
|
|
299
|
+
pts = np.zeros(
|
|
300
|
+
(n_states, tdata.n_targets, tdata.n_tpoints, n_dms + 1),
|
|
301
|
+
dtype=config.dtype_double,
|
|
302
|
+
)
|
|
303
|
+
pts[..., :n_dms] = tdata[FC.TARGETS][..., :n_dms]
|
|
304
|
+
pts[..., n_dms] = np.arange(n_states)[:, None, None]
|
|
305
|
+
pts = pts.reshape(n_pts, n_dms + 1)
|
|
306
|
+
|
|
307
|
+
else:
|
|
308
|
+
raise ValueError(
|
|
309
|
+
f"States '{self.name}': Unsupported dimensions {dims} for variables {vrs}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# translate (WD, WS) to (U, V):
|
|
313
|
+
if FV.WD in vrs or FV.WS in vrs:
|
|
314
|
+
assert FV.WD in vrs and (FV.WS in vrs or FV.WS in self.fixed_vars), (
|
|
315
|
+
f"States '{self.name}': Missing '{FV.WD}' or '{FV.WS}' in data variables {vrs} for dimensions {dims}"
|
|
316
|
+
)
|
|
317
|
+
iwd = vrs.index(FV.WD)
|
|
318
|
+
iws = vrs.index(FV.WS)
|
|
319
|
+
ws = d[..., iws] if FV.WS in vrs else self.fixed_vars[FV.WS]
|
|
320
|
+
d[..., [iwd, iws]] = wd2uv(d[..., iwd], ws, axis=-1)
|
|
321
|
+
del ws
|
|
322
|
+
|
|
323
|
+
# create interpolator
|
|
324
|
+
if self.interp_method == "linear":
|
|
325
|
+
interp = LinearNDInterpolator(gts, d, **self.interp_pars)
|
|
326
|
+
elif self.interp_method == "nearest":
|
|
327
|
+
interp = NearestNDInterpolator(gts, d, **self.interp_pars)
|
|
328
|
+
elif self.interp_method == "radialBasisFunction":
|
|
329
|
+
pars = {"neighbors": 10}
|
|
330
|
+
pars.update(self.interp_pars)
|
|
331
|
+
interp = RBFInterpolator(gts, d, **pars)
|
|
332
|
+
else:
|
|
333
|
+
raise NotImplementedError(
|
|
334
|
+
f"States '{self.name}': Interpolation method '{self.interp_method}' not implemented, choices are: 'linear', 'nearest', 'radialBasisFunction'"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# run interpolation
|
|
338
|
+
ires = interp(pts)
|
|
339
|
+
del interp
|
|
340
|
+
|
|
341
|
+
# check for error:
|
|
342
|
+
sel = np.any(np.isnan(ires), axis=-1)
|
|
343
|
+
if np.any(sel):
|
|
344
|
+
i = np.where(sel)[0]
|
|
345
|
+
if self.interp_fallback_nearest:
|
|
346
|
+
interp = NearestNDInterpolator(gts, d)
|
|
347
|
+
pts = pts[i]
|
|
348
|
+
ires[i] = interp(pts)
|
|
349
|
+
del interp
|
|
350
|
+
else:
|
|
351
|
+
p = pts[i[0], :n_dms]
|
|
352
|
+
qmin = np.min(qts[:, :n_dms], axis=0)
|
|
353
|
+
qmax = np.max(qts[:, :n_dms], axis=0)
|
|
354
|
+
raise ValueError(
|
|
355
|
+
f"States '{self.name}': Interpolation method '{self.interp_method}' failed for {np.sum(sel)} points, e.g. for point {p}, outside of bounds {qmin} - {qmax}"
|
|
356
|
+
)
|
|
357
|
+
del pts, gts, d
|
|
358
|
+
|
|
359
|
+
# translate (U, V) into (WD, WS):
|
|
360
|
+
if FV.WD in vrs:
|
|
361
|
+
uv = ires[..., [iwd, iws]]
|
|
362
|
+
ires[..., iwd] = uv2wd(uv)
|
|
363
|
+
ires[..., iws] = np.linalg.norm(uv, axis=-1)
|
|
364
|
+
del uv
|
|
365
|
+
|
|
366
|
+
# set output:
|
|
367
|
+
for i, v in enumerate(vrs):
|
|
368
|
+
out[v] = ires[..., i].reshape(n_states, n_targets, n_tpoints)
|
|
369
|
+
del ires
|
|
370
|
+
|
|
371
|
+
# set fixed variables:
|
|
372
|
+
for v, d in self.fixed_vars.items():
|
|
373
|
+
out[v] = np.full(
|
|
374
|
+
(n_states, n_targets, n_tpoints), d, dtype=config.dtype_double
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# add weights:
|
|
378
|
+
if weights is not None:
|
|
379
|
+
tdata[FV.WEIGHT] = weights[:, None, None]
|
|
380
|
+
elif FV.WEIGHT in out:
|
|
381
|
+
tdata[FV.WEIGHT] = out.pop(FV.WEIGHT)
|
|
382
|
+
else:
|
|
383
|
+
tdata[FV.WEIGHT] = np.full(
|
|
384
|
+
(n_states, 1, 1), 1 / self._N, dtype=config.dtype_double
|
|
385
|
+
)
|
|
386
|
+
tdata.dims[FV.WEIGHT] = (FC.STATE, FC.TARGET, FC.TPOINT)
|
|
387
|
+
|
|
388
|
+
return {v: out[v] for v in self.ovars}
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class WeibullPointCloud(PointCloudData):
|
|
392
|
+
"""
|
|
393
|
+
Weibull sectors at point cloud support, e.g., at turbine locations.
|
|
394
|
+
|
|
395
|
+
Attributes
|
|
396
|
+
----------
|
|
397
|
+
wd_coord: str
|
|
398
|
+
The wind direction coordinate name
|
|
399
|
+
ws_coord: str
|
|
400
|
+
The wind speed coordinate name, if wind speed bin
|
|
401
|
+
centres are in data, else None
|
|
402
|
+
ws_bins: numpy.ndarray
|
|
403
|
+
The wind speed bins, including
|
|
404
|
+
lower and upper bounds, shape: (n_ws_bins+1,)
|
|
405
|
+
|
|
406
|
+
:group: input.states
|
|
407
|
+
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
def __init__(
|
|
411
|
+
self,
|
|
412
|
+
*args,
|
|
413
|
+
wd_coord,
|
|
414
|
+
ws_coord=None,
|
|
415
|
+
ws_bins=None,
|
|
416
|
+
**kwargs,
|
|
417
|
+
):
|
|
418
|
+
"""
|
|
419
|
+
Constructor.
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
args: tuple, optional
|
|
424
|
+
Positional arguments for the base class
|
|
425
|
+
wd_coord: str
|
|
426
|
+
The wind direction coordinate name
|
|
427
|
+
ws_coord: str, optional
|
|
428
|
+
The wind speed coordinate name, if wind speed bin
|
|
429
|
+
centres are in data
|
|
430
|
+
ws_bins: list of float, optional
|
|
431
|
+
The wind speed bins, including
|
|
432
|
+
lower and upper bounds
|
|
433
|
+
kwargs: dict, optional
|
|
434
|
+
Keyword arguments for the base class
|
|
435
|
+
|
|
436
|
+
"""
|
|
437
|
+
super().__init__(
|
|
438
|
+
*args,
|
|
439
|
+
states_coord=wd_coord,
|
|
440
|
+
time_format=None,
|
|
441
|
+
load_mode="preload",
|
|
442
|
+
**kwargs,
|
|
443
|
+
)
|
|
444
|
+
self.wd_coord = wd_coord
|
|
445
|
+
self.ws_coord = ws_coord
|
|
446
|
+
self.ws_bins = None if ws_bins is None else np.sort(np.asarray(ws_bins))
|
|
447
|
+
|
|
448
|
+
assert ws_coord is not None or ws_bins is not None, (
|
|
449
|
+
f"States '{self.name}': Expecting either ws_coord or ws_bins"
|
|
450
|
+
)
|
|
451
|
+
assert ws_coord is None or ws_bins is None, (
|
|
452
|
+
f"States '{self.name}': Expecting either ws_coord or ws_bins, not both"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if FV.WD not in self.ovars:
|
|
456
|
+
raise ValueError(
|
|
457
|
+
f"States '{self.name}': Expecting output variable '{FV.WD}', got {self.ovars}"
|
|
458
|
+
)
|
|
459
|
+
for v in [FV.WEIBULL_A, FV.WEIBULL_k, FV.WEIGHT]:
|
|
460
|
+
if v in self.ovars:
|
|
461
|
+
raise ValueError(
|
|
462
|
+
f"States '{self.name}': Cannot have '{v}' as output variable"
|
|
463
|
+
)
|
|
464
|
+
if v not in self.variables:
|
|
465
|
+
self.variables.append(v)
|
|
466
|
+
|
|
467
|
+
for v in [FV.WS, FV.WD]:
|
|
468
|
+
if v in self.variables:
|
|
469
|
+
self.variables.remove(v)
|
|
470
|
+
|
|
471
|
+
self._n_wd = None
|
|
472
|
+
self._n_ws = None
|
|
473
|
+
|
|
474
|
+
def __repr__(self):
|
|
475
|
+
return f"{type(self).__name__}(n_wd={self._n_wd}, n_ws={self._n_ws})"
|
|
476
|
+
|
|
477
|
+
def _read_ds(self, ds, cmap, variables, verbosity=0):
|
|
478
|
+
"""
|
|
479
|
+
Helper function for _get_data, extracts data from the original Dataset.
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
ds: xarray.Dataset
|
|
484
|
+
The Dataset to read data from
|
|
485
|
+
cmap: dict
|
|
486
|
+
A mapping from foxes variable names to Dataset dimension names
|
|
487
|
+
variables: list of str
|
|
488
|
+
The variables to extract from the Dataset
|
|
489
|
+
verbosity: int
|
|
490
|
+
The verbosity level, 0 = silent
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
coords: dict
|
|
495
|
+
keys: Foxes variable names, values: 1D coordinate value arrays
|
|
496
|
+
data: dict
|
|
497
|
+
The extracted data, keys are variable names,
|
|
498
|
+
values are tuples (dims, data_array)
|
|
499
|
+
where dims is a tuple of dimension names and
|
|
500
|
+
data_array is a numpy.ndarray with the data values
|
|
501
|
+
|
|
502
|
+
"""
|
|
503
|
+
# read data, using wd_coord as state coordinate
|
|
504
|
+
hcmap = cmap.copy()
|
|
505
|
+
if self.ws_coord is not None:
|
|
506
|
+
hcmap = {FV.WS: self.ws_coord, **cmap}
|
|
507
|
+
coords, data0 = super()._read_ds(ds, hcmap, variables, verbosity)
|
|
508
|
+
wd = coords.pop(FC.STATE)
|
|
509
|
+
wss = coords.pop(FV.WS, None)
|
|
510
|
+
|
|
511
|
+
# replace state by wd coordinate
|
|
512
|
+
data0 = {
|
|
513
|
+
v: (tuple({FC.STATE: FV.WD}.get(c, c) for c in dims), d)
|
|
514
|
+
for v, (dims, d) in data0.items()
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
# check weights
|
|
518
|
+
if FV.WEIGHT not in data0:
|
|
519
|
+
raise KeyError(
|
|
520
|
+
f"States '{self.name}': Missing weights variable '{FV.WEIGHT}' in data, found {sorted(list(data0.keys()))}"
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
dims = data0[FV.WEIGHT][0]
|
|
524
|
+
if FV.WD not in dims:
|
|
525
|
+
raise KeyError(
|
|
526
|
+
f"States '{self.name}': Expecting weights variable '{FV.WEIGHT}' to contain dimension '{FV.WD}', got {dims}"
|
|
527
|
+
)
|
|
528
|
+
if FV.WS in dims:
|
|
529
|
+
raise KeyError(
|
|
530
|
+
f"States '{self.name}': Expecting weights variable '{FV.WEIGHT}' to not contain dimension '{FV.WS}', got {dims}"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
# construct wind speed bins and bin deltas
|
|
534
|
+
assert FV.WS not in data0, (
|
|
535
|
+
f"States '{self.name}': Cannot have '{FV.WS}' in data, found variables {list(data0.keys())}"
|
|
536
|
+
)
|
|
537
|
+
if self.ws_bins is not None:
|
|
538
|
+
wsb = self.ws_bins
|
|
539
|
+
wss = 0.5 * (wsb[:-1] + wsb[1:])
|
|
540
|
+
elif wss is not None:
|
|
541
|
+
wsb = np.zeros((len(wss) + 1,), dtype=config.dtype_double)
|
|
542
|
+
wsb[1:-1] = 0.5 * (wss[1:] + wss[:-1])
|
|
543
|
+
wsb[0] = wss[0] - 0.5 * wsb[1]
|
|
544
|
+
wsb[-1] = wss[-1] + 0.5 * wsb[-2]
|
|
545
|
+
self.ws_bins = wsb
|
|
546
|
+
else:
|
|
547
|
+
raise ValueError(
|
|
548
|
+
f"States '{self.name}': Expecting ws_bins argument, or '{self.ws_coord}' among data coordinates, got {list(ds.coords.keys())}"
|
|
549
|
+
)
|
|
550
|
+
wsd = wsb[1:] - wsb[:-1]
|
|
551
|
+
n_ws = len(wss)
|
|
552
|
+
n_wd = len(wd)
|
|
553
|
+
del wsb
|
|
554
|
+
|
|
555
|
+
# calculate Weibull weights
|
|
556
|
+
dms = [FV.WS, FV.WD]
|
|
557
|
+
shp = [n_ws, n_wd]
|
|
558
|
+
for v in [FV.WEIBULL_A, FV.WEIBULL_k]:
|
|
559
|
+
if FC.POINT in data0[v][0]:
|
|
560
|
+
dms.append(FC.POINT)
|
|
561
|
+
shp.append(data0[v][1].shape[data0[v][0].index(FC.POINT)])
|
|
562
|
+
break
|
|
563
|
+
dms = tuple(dms)
|
|
564
|
+
shp = tuple(shp)
|
|
565
|
+
if data0[FV.WEIGHT][0] == dms:
|
|
566
|
+
w = data0.pop(FV.WEIGHT)[1]
|
|
567
|
+
else:
|
|
568
|
+
s_w = tuple([np.s_[:] if c in data0[FV.WEIGHT][0] else None for c in dms])
|
|
569
|
+
w = np.zeros(shp, dtype=config.dtype_double)
|
|
570
|
+
w[:] = data0.pop(FV.WEIGHT)[1][s_w]
|
|
571
|
+
s_ws = tuple([np.s_[:], None] + [None] * (len(dms) - 2))
|
|
572
|
+
s_A = tuple([np.s_[:] if c in data0[FV.WEIBULL_A][0] else None for c in dms])
|
|
573
|
+
s_k = tuple([np.s_[:] if c in data0[FV.WEIBULL_A][0] else None for c in dms])
|
|
574
|
+
data0[FV.WEIGHT] = (
|
|
575
|
+
dms,
|
|
576
|
+
w
|
|
577
|
+
* weibull_weights(
|
|
578
|
+
ws=wss[s_ws],
|
|
579
|
+
ws_deltas=wsd[s_ws],
|
|
580
|
+
A=data0.pop(FV.WEIBULL_A)[1][s_A],
|
|
581
|
+
k=data0.pop(FV.WEIBULL_k)[1][s_k],
|
|
582
|
+
),
|
|
583
|
+
)
|
|
584
|
+
del w, s_ws, s_A, s_k
|
|
585
|
+
|
|
586
|
+
# translate binned data to states
|
|
587
|
+
self._N = n_ws * n_wd
|
|
588
|
+
self._inds = None
|
|
589
|
+
data = {
|
|
590
|
+
FV.WS: np.zeros((n_ws, n_wd), dtype=config.dtype_double),
|
|
591
|
+
FV.WD: np.zeros((n_ws, n_wd), dtype=config.dtype_double),
|
|
592
|
+
}
|
|
593
|
+
data[FV.WS][:] = wss[:, None]
|
|
594
|
+
data[FV.WD][:] = wd[None, :]
|
|
595
|
+
data[FV.WS] = ((FC.STATE,), data[FV.WS].reshape(self._N))
|
|
596
|
+
data[FV.WD] = ((FC.STATE,), data[FV.WD].reshape(self._N))
|
|
597
|
+
for v in list(data0.keys()):
|
|
598
|
+
dims, d = data0.pop(v)
|
|
599
|
+
if len(dims) >= 2 and dims[:2] == (FV.WS, FV.WD):
|
|
600
|
+
dms = tuple([FC.STATE] + list(dims[2:]))
|
|
601
|
+
shp = [self._N] + list(d.shape[2:])
|
|
602
|
+
data[v] = (dms, d.reshape(shp))
|
|
603
|
+
elif dims[0] == FV.WD:
|
|
604
|
+
dms = tuple([FC.STATE] + list(dims[1:]))
|
|
605
|
+
shp = [n_ws] + list(d.shape)
|
|
606
|
+
data[v] = np.zeros(shp, dtype=config.dtype_double)
|
|
607
|
+
data[v][:] = d[None, ...]
|
|
608
|
+
data[v] = (dms, data[v].reshape([self._N] + shp[2:]))
|
|
609
|
+
elif dims[0] == FV.WS:
|
|
610
|
+
dms = tuple([FC.STATE] + list(dims[1:]))
|
|
611
|
+
shp = [n_ws, n_wd] + list(d.shape[2:])
|
|
612
|
+
data[v] = np.zeros(shp, dtype=config.dtype_double)
|
|
613
|
+
data[v][:] = d[:, None, ...]
|
|
614
|
+
data[v] = (dms, data[v].reshape([self._N] + shp[2:]))
|
|
615
|
+
else:
|
|
616
|
+
data[v] = (dims, d)
|
|
617
|
+
|
|
618
|
+
return coords, data
|
foxes/input/states/scan.py
CHANGED
foxes/input/states/single.py
CHANGED