foxes 0.6.1__py3-none-any.whl → 0.7__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.
- foxes/VERSION +1 -1
- foxes/algorithms/downwind/downwind.py +131 -65
- foxes/algorithms/downwind/models/__init__.py +2 -1
- foxes/algorithms/downwind/models/farm_wakes_calc.py +87 -55
- foxes/algorithms/downwind/models/init_farm_data.py +134 -0
- foxes/algorithms/downwind/models/point_wakes_calc.py +54 -65
- foxes/algorithms/downwind/models/{calc_order.py → reorder_farm_output.py} +28 -26
- foxes/algorithms/iterative/iterative.py +100 -51
- foxes/algorithms/iterative/models/convergence.py +3 -3
- foxes/algorithms/iterative/models/farm_wakes_calc.py +55 -48
- foxes/algorithms/sequential/models/seq_state.py +7 -6
- foxes/algorithms/sequential/sequential.py +81 -44
- foxes/constants.py +33 -18
- foxes/core/__init__.py +2 -2
- foxes/core/algorithm.py +31 -12
- foxes/core/data.py +335 -41
- foxes/core/data_calc_model.py +27 -23
- foxes/core/farm_controller.py +27 -28
- foxes/core/farm_data_model.py +26 -4
- foxes/core/model.py +186 -129
- foxes/core/partial_wakes_model.py +84 -81
- foxes/core/point_data_model.py +51 -18
- foxes/core/rotor_model.py +59 -77
- foxes/core/states.py +6 -6
- foxes/core/turbine_model.py +4 -4
- foxes/core/turbine_type.py +24 -0
- foxes/core/vertical_profile.py +3 -3
- foxes/core/wake_frame.py +91 -50
- foxes/core/wake_model.py +74 -43
- foxes/core/wake_superposition.py +29 -26
- foxes/input/farm_layout/__init__.py +1 -0
- foxes/input/farm_layout/from_random.py +49 -0
- foxes/input/states/__init__.py +1 -1
- foxes/input/states/create/__init__.py +1 -0
- foxes/input/states/create/random_abl_states.py +6 -2
- foxes/input/states/create/random_timeseries.py +56 -0
- foxes/input/states/field_data_nc.py +12 -8
- foxes/input/states/multi_height.py +24 -14
- foxes/input/states/scan_ws.py +13 -17
- foxes/input/states/single.py +28 -20
- foxes/input/states/states_table.py +22 -18
- foxes/models/axial_induction_models/betz.py +1 -1
- foxes/models/farm_models/turbine2farm.py +2 -2
- foxes/models/model_book.py +40 -14
- foxes/models/partial_wakes/__init__.py +2 -2
- foxes/models/partial_wakes/axiwake.py +73 -200
- foxes/models/partial_wakes/centre.py +40 -0
- foxes/models/partial_wakes/grid.py +7 -63
- foxes/models/partial_wakes/rotor_points.py +53 -147
- foxes/models/partial_wakes/segregated.py +158 -0
- foxes/models/partial_wakes/top_hat.py +88 -196
- foxes/models/point_models/set_uniform_data.py +4 -4
- foxes/models/point_models/tke2ti.py +4 -4
- foxes/models/point_models/wake_deltas.py +4 -4
- foxes/models/rotor_models/centre.py +15 -19
- foxes/models/rotor_models/grid.py +2 -1
- foxes/models/rotor_models/levels.py +2 -1
- foxes/models/turbine_models/__init__.py +0 -1
- foxes/models/turbine_models/calculator.py +11 -7
- foxes/models/turbine_models/kTI_model.py +13 -11
- foxes/models/turbine_models/lookup_table.py +22 -9
- foxes/models/turbine_models/power_mask.py +81 -51
- foxes/models/turbine_models/rotor_centre_calc.py +17 -20
- foxes/models/turbine_models/sector_management.py +5 -6
- foxes/models/turbine_models/set_farm_vars.py +49 -20
- foxes/models/turbine_models/table_factors.py +5 -5
- foxes/models/turbine_models/thrust2ct.py +9 -5
- foxes/models/turbine_models/yaw2yawm.py +7 -13
- foxes/models/turbine_models/yawm2yaw.py +7 -11
- foxes/models/turbine_types/PCt_file.py +84 -3
- foxes/models/turbine_types/PCt_from_two.py +7 -3
- foxes/models/turbine_types/null_type.py +2 -2
- foxes/models/turbine_types/wsrho2PCt_from_two.py +2 -2
- foxes/models/turbine_types/wsti2PCt_from_two.py +6 -2
- foxes/models/wake_frames/farm_order.py +26 -22
- foxes/models/wake_frames/rotor_wd.py +32 -31
- foxes/models/wake_frames/seq_dynamic_wakes.py +112 -64
- foxes/models/wake_frames/streamlines.py +51 -47
- foxes/models/wake_frames/timelines.py +59 -47
- foxes/models/wake_frames/yawed_wakes.py +63 -40
- foxes/models/wake_models/axisymmetric.py +31 -35
- foxes/models/wake_models/dist_sliced.py +50 -56
- foxes/models/wake_models/gaussian.py +33 -35
- foxes/models/wake_models/induction/rankine_half_body.py +79 -87
- foxes/models/wake_models/induction/rathmann.py +56 -63
- foxes/models/wake_models/induction/self_similar.py +59 -62
- foxes/models/wake_models/ti/crespo_hernandez.py +83 -74
- foxes/models/wake_models/ti/iec_ti.py +65 -75
- foxes/models/wake_models/top_hat.py +60 -69
- foxes/models/wake_models/wake_mirror.py +49 -54
- foxes/models/wake_models/wind/bastankhah14.py +44 -66
- foxes/models/wake_models/wind/bastankhah16.py +84 -111
- foxes/models/wake_models/wind/jensen.py +67 -89
- foxes/models/wake_models/wind/turbopark.py +93 -133
- foxes/models/wake_superpositions/ti_linear.py +33 -27
- foxes/models/wake_superpositions/ti_max.py +33 -27
- foxes/models/wake_superpositions/ti_pow.py +35 -27
- foxes/models/wake_superpositions/ti_quadratic.py +33 -27
- foxes/models/wake_superpositions/ws_linear.py +39 -32
- foxes/models/wake_superpositions/ws_max.py +40 -33
- foxes/models/wake_superpositions/ws_pow.py +39 -32
- foxes/models/wake_superpositions/ws_product.py +35 -28
- foxes/models/wake_superpositions/ws_quadratic.py +39 -32
- foxes/opt/constraints/min_dist.py +1 -1
- foxes/opt/objectives/farm_vars.py +1 -1
- foxes/opt/problems/layout/farm_layout.py +38 -97
- foxes/output/__init__.py +1 -0
- foxes/output/farm_results_eval.py +1 -1
- foxes/output/flow_plots_2d/flow_plots.py +2 -0
- foxes/output/flow_plots_2d/get_fig.py +2 -0
- foxes/output/grids.py +1 -1
- foxes/output/rose_plot.py +3 -3
- foxes/output/rotor_point_plots.py +117 -0
- foxes/output/turbine_type_curves.py +2 -2
- foxes/utils/__init__.py +2 -1
- foxes/utils/load.py +29 -0
- foxes/utils/random_xy.py +56 -0
- foxes/utils/runners/runners.py +13 -1
- foxes/utils/windrose_plot.py +1 -1
- foxes/variables.py +10 -0
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/METADATA +13 -7
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/RECORD +126 -122
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/WHEEL +1 -1
- foxes/models/partial_wakes/distsliced.py +0 -322
- foxes/models/partial_wakes/mapped.py +0 -252
- foxes/models/turbine_models/set_XYHD.py +0 -130
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/LICENSE +0 -0
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/top_level.txt +0 -0
- {foxes-0.6.1.dist-info → foxes-0.7.dist-info}/zip-safe +0 -0
foxes/core/data.py
CHANGED
|
@@ -7,10 +7,8 @@ import foxes.constants as FC
|
|
|
7
7
|
|
|
8
8
|
class Data(Dict):
|
|
9
9
|
"""
|
|
10
|
-
Container for data and
|
|
11
|
-
|
|
12
|
-
Used during the calculation of single chunks,
|
|
13
|
-
usually for numpy data (not xarray data).
|
|
10
|
+
Container for numpy array data and
|
|
11
|
+
the associated meta data.
|
|
14
12
|
|
|
15
13
|
Attributes
|
|
16
14
|
----------
|
|
@@ -53,9 +51,9 @@ class Data(Dict):
|
|
|
53
51
|
|
|
54
52
|
self.sizes = {}
|
|
55
53
|
for v, d in data.items():
|
|
56
|
-
self.
|
|
54
|
+
self._run_entry_checks(v, d, dims[v])
|
|
57
55
|
|
|
58
|
-
self.
|
|
56
|
+
self._auto_update()
|
|
59
57
|
|
|
60
58
|
@property
|
|
61
59
|
def n_states(self):
|
|
@@ -83,19 +81,6 @@ class Data(Dict):
|
|
|
83
81
|
"""
|
|
84
82
|
return self.sizes[FC.TURBINE] if FC.TURBINE in self.sizes else None
|
|
85
83
|
|
|
86
|
-
@property
|
|
87
|
-
def n_points(self):
|
|
88
|
-
"""
|
|
89
|
-
The number of points
|
|
90
|
-
|
|
91
|
-
Returns
|
|
92
|
-
-------
|
|
93
|
-
int:
|
|
94
|
-
The number of points
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
return self.sizes[FC.POINT] if FC.POINT in self.sizes else None
|
|
98
|
-
|
|
99
84
|
def states_i0(self, counter=False, algo=None):
|
|
100
85
|
"""
|
|
101
86
|
Get the state counter for first state in chunk
|
|
@@ -123,7 +108,8 @@ class Data(Dict):
|
|
|
123
108
|
else:
|
|
124
109
|
return self[FC.STATE][0]
|
|
125
110
|
|
|
126
|
-
def
|
|
111
|
+
def _auto_update(self):
|
|
112
|
+
"""Checks and operations after data changes"""
|
|
127
113
|
data = self
|
|
128
114
|
dims = self.dims
|
|
129
115
|
|
|
@@ -132,25 +118,19 @@ class Data(Dict):
|
|
|
132
118
|
and FV.X in data
|
|
133
119
|
and FV.Y in data
|
|
134
120
|
and FV.H in data
|
|
135
|
-
and dims[FV.X] ==
|
|
136
|
-
and dims[FV.
|
|
137
|
-
and dims[FV.H] == (FC.STATE, FC.TURBINE)
|
|
121
|
+
and dims[FV.X] == dims[FV.Y]
|
|
122
|
+
and dims[FV.X] == dims[FV.H]
|
|
138
123
|
):
|
|
139
|
-
self[FV.TXYH] = np.
|
|
140
|
-
(self.n_states, self.n_turbines, 3), dtype=FC.DTYPE
|
|
141
|
-
)
|
|
124
|
+
self[FV.TXYH] = np.stack([self[FV.X], self[FV.Y], self[FV.H]], axis=-1)
|
|
142
125
|
|
|
143
|
-
self[FV.
|
|
144
|
-
self[FV.
|
|
145
|
-
self[FV.
|
|
126
|
+
self[FV.X] = self[FV.TXYH][..., 0]
|
|
127
|
+
self[FV.Y] = self[FV.TXYH][..., 1]
|
|
128
|
+
self[FV.H] = self[FV.TXYH][..., 2]
|
|
146
129
|
|
|
147
|
-
self[FV.
|
|
148
|
-
self[FV.Y] = self[FV.TXYH][:, :, 1]
|
|
149
|
-
self[FV.H] = self[FV.TXYH][:, :, 2]
|
|
130
|
+
self.dims[FV.TXYH] = tuple(list(dims[FV.X]) + [FC.XYH])
|
|
150
131
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def __run_entry_checks(self, name, data, dims):
|
|
132
|
+
def _run_entry_checks(self, name, data, dims):
|
|
133
|
+
"""Run entry checks on new data"""
|
|
154
134
|
# remove axes of size 1, added by dask for extra loop dimensions:
|
|
155
135
|
if dims is not None:
|
|
156
136
|
if len(dims) != len(data.shape):
|
|
@@ -182,8 +162,270 @@ class Data(Dict):
|
|
|
182
162
|
"""
|
|
183
163
|
self[name] = data
|
|
184
164
|
self.dims[name] = dims
|
|
185
|
-
self.
|
|
186
|
-
self.
|
|
165
|
+
self._run_entry_checks(name, data, dims)
|
|
166
|
+
self._auto_update()
|
|
167
|
+
|
|
168
|
+
def get_slice(self, s, dim_map={}, name=None, keep=True):
|
|
169
|
+
"""
|
|
170
|
+
Get a slice of data.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
s: slice
|
|
175
|
+
The slice
|
|
176
|
+
dim_map: dict
|
|
177
|
+
Mapping from original to new dimensions.
|
|
178
|
+
If not found, same dimensions are assumed.
|
|
179
|
+
name: str, optional
|
|
180
|
+
The name of the data object
|
|
181
|
+
keep: bool
|
|
182
|
+
Keep non-matching fields as they are, else
|
|
183
|
+
throw them out
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
data: Data
|
|
188
|
+
The new data object, containing slices
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
data = {}
|
|
192
|
+
dims = {}
|
|
193
|
+
for v in self.keys():
|
|
194
|
+
try:
|
|
195
|
+
d = self.dims[v]
|
|
196
|
+
data[v] = self[v][s]
|
|
197
|
+
dims[v] = dim_map.get(d, d)
|
|
198
|
+
except IndexError:
|
|
199
|
+
if keep:
|
|
200
|
+
data[v] = self[v]
|
|
201
|
+
dims[v] = self.dims[v]
|
|
202
|
+
if name is None:
|
|
203
|
+
name = self.name
|
|
204
|
+
return type(self)(data, dims, loop_dims=self.loop_dims, name=name)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class MData(Data):
|
|
208
|
+
"""
|
|
209
|
+
Container for foxes model data.
|
|
210
|
+
|
|
211
|
+
:group: core
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def __init__(self, *args, name="mdata", **kwargs):
|
|
216
|
+
"""
|
|
217
|
+
Constructor
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
args: tuple, optional
|
|
222
|
+
Arguments for the base class
|
|
223
|
+
name: str
|
|
224
|
+
The data name
|
|
225
|
+
kwargs: dict, optional
|
|
226
|
+
Arguments for the base class
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
super().__init__(*args, name=name, **kwargs)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class FData(Data):
|
|
233
|
+
"""
|
|
234
|
+
Container for foxes farm data.
|
|
235
|
+
|
|
236
|
+
Each farm data entry has (n_states, n_turbines) shape,
|
|
237
|
+
except the dimensions.
|
|
238
|
+
|
|
239
|
+
:group: core
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def __init__(self, *args, name="fdata", **kwargs):
|
|
244
|
+
"""
|
|
245
|
+
Constructor
|
|
246
|
+
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
args: tuple, optional
|
|
250
|
+
Arguments for the base class
|
|
251
|
+
name: str
|
|
252
|
+
The data name
|
|
253
|
+
kwargs: dict, optional
|
|
254
|
+
Arguments for the base class
|
|
255
|
+
|
|
256
|
+
"""
|
|
257
|
+
super().__init__(*args, name=name, **kwargs)
|
|
258
|
+
|
|
259
|
+
def _run_entry_checks(self, name, data, dims):
|
|
260
|
+
"""Run entry checks on new data"""
|
|
261
|
+
super()._run_entry_checks(name, data, dims)
|
|
262
|
+
data = self[name]
|
|
263
|
+
dims = self.dims[name]
|
|
264
|
+
|
|
265
|
+
if name not in self.sizes and name not in FC.TNAME:
|
|
266
|
+
dms = (FC.STATE, FC.TURBINE)
|
|
267
|
+
shp = (self.n_states, self.n_turbines)
|
|
268
|
+
if len(data.shape) < 2:
|
|
269
|
+
raise ValueError(
|
|
270
|
+
f"FData '{self.name}': Invalid shape for '{name}', expecting {shp}, got {data.shape}"
|
|
271
|
+
)
|
|
272
|
+
if len(dims) < 2 or dims[:2] != dms:
|
|
273
|
+
raise ValueError(
|
|
274
|
+
f"FData '{self.name}': Invalid dims for '{name}', expecting {dms}, got {dims}"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _auto_update(self):
|
|
278
|
+
"""Checks and operations after data changes"""
|
|
279
|
+
super()._auto_update()
|
|
280
|
+
if len(self):
|
|
281
|
+
for x in [FC.STATE, FC.TURBINE]:
|
|
282
|
+
if x not in self.sizes:
|
|
283
|
+
raise KeyError(
|
|
284
|
+
f"FData '{self.name}': Missing '{x}' in sizes, got {sorted(list(self.sizes.keys()))}"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class TData(Data):
|
|
289
|
+
"""
|
|
290
|
+
Container for foxes target data.
|
|
291
|
+
|
|
292
|
+
Each target consists of a fixed number of
|
|
293
|
+
target points.
|
|
294
|
+
|
|
295
|
+
:group: core
|
|
296
|
+
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, *args, name="tdata", **kwargs):
|
|
300
|
+
"""
|
|
301
|
+
Constructor
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
args: tuple, optional
|
|
306
|
+
Arguments for the base class
|
|
307
|
+
name: str
|
|
308
|
+
The data name
|
|
309
|
+
kwargs: dict, optional
|
|
310
|
+
Arguments for the base class
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
super().__init__(*args, name=name, **kwargs)
|
|
314
|
+
|
|
315
|
+
def _run_entry_checks(self, name, data, dims):
|
|
316
|
+
"""Run entry checks on new data"""
|
|
317
|
+
super()._run_entry_checks(name, data, dims)
|
|
318
|
+
data = self[name]
|
|
319
|
+
dims = self.dims[name]
|
|
320
|
+
|
|
321
|
+
if name == FC.TARGETS:
|
|
322
|
+
dms = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
|
|
323
|
+
shp = (self.n_states, self.n_targets, self.n_tpoints, 3)
|
|
324
|
+
if dims != dms:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"TData '{self.name}': Invalid dims of {FC.TARGETS}, expecting {dms}, got {dims}"
|
|
327
|
+
)
|
|
328
|
+
if data.shape != shp:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
f"TData '{self.name}': Invalid shape of {FC.TARGETS}, expecting {shp}, got {data.shape}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
elif name == FC.TWEIGHTS:
|
|
334
|
+
dms = (FC.TPOINT,)
|
|
335
|
+
shp = (self.n_tpoints,)
|
|
336
|
+
if dims != dms:
|
|
337
|
+
raise ValueError(
|
|
338
|
+
f"TData '{self.name}': Invalid dims of {FC.TWEIGHTS}, expecting {dms}, got {dims}"
|
|
339
|
+
)
|
|
340
|
+
if data.shape != shp:
|
|
341
|
+
raise ValueError(
|
|
342
|
+
f"TData '{self.name}': Invalid shape of {FC.TWEIGHTS}, expecting {shp}, got {data.shape}"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
elif FC.TARGETS not in self:
|
|
346
|
+
raise KeyError(
|
|
347
|
+
f"TData '{self.name}': Missing '{FC.TARGETS}' before adding '{name}'"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
elif FC.TWEIGHTS not in self:
|
|
351
|
+
raise KeyError(
|
|
352
|
+
f"TData '{self.name}': Missing '{FC.TWEIGHTS}' before adding '{name}'"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
elif name not in self.sizes:
|
|
356
|
+
dms = (FC.STATE, FC.TARGET, FC.TPOINT)
|
|
357
|
+
shp = (self.n_states, self.n_targets, self.n_tpoints)
|
|
358
|
+
if len(data.shape) < 3:
|
|
359
|
+
raise ValueError(
|
|
360
|
+
f"TData '{self.name}': Invalid shape for '{name}', expecting {shp}, got {data.shape}"
|
|
361
|
+
)
|
|
362
|
+
if len(dims) < 3 or dims[:3] != dms:
|
|
363
|
+
raise ValueError(
|
|
364
|
+
f"TData '{self.name}': Invalid dims for '{name}', expecting {dms}, got {dims}"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def _auto_update(self):
|
|
368
|
+
"""Checks and operations after data changes"""
|
|
369
|
+
super()._auto_update()
|
|
370
|
+
if len(self):
|
|
371
|
+
for x in [FC.TARGETS, FC.TWEIGHTS]:
|
|
372
|
+
if x not in self:
|
|
373
|
+
raise KeyError(
|
|
374
|
+
f"TData '{self.name}': Missing '{x}' in data, got {sorted(list(self.keys()))}"
|
|
375
|
+
)
|
|
376
|
+
if x not in self.dims:
|
|
377
|
+
raise KeyError(
|
|
378
|
+
f"TData '{self.name}': Missing '{x}' in dims, got {sorted(list(self.dims.keys()))}"
|
|
379
|
+
)
|
|
380
|
+
for x in [FC.STATE, FC.TARGET, FC.TPOINT]:
|
|
381
|
+
if x not in self.sizes:
|
|
382
|
+
raise KeyError(
|
|
383
|
+
f"TData '{self.name}': Missing '{x}' in sizes, got {sorted(list(self.sizes.keys()))}"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def n_targets(self):
|
|
388
|
+
"""
|
|
389
|
+
The number of targets
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
int:
|
|
394
|
+
The number of targets
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
return self.sizes[FC.TARGET]
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def n_tpoints(self):
|
|
401
|
+
"""
|
|
402
|
+
The number of points per target
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
int:
|
|
407
|
+
The number of points per target
|
|
408
|
+
|
|
409
|
+
"""
|
|
410
|
+
return self.sizes[FC.TPOINT]
|
|
411
|
+
|
|
412
|
+
def tpoint_mean(self, variable):
|
|
413
|
+
"""
|
|
414
|
+
Take the mean over target points
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
variable: str
|
|
419
|
+
The variable name
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
data: numpy.ndarray
|
|
424
|
+
The reduced array, shape:
|
|
425
|
+
(n_states, n_targets, ...)
|
|
426
|
+
|
|
427
|
+
"""
|
|
428
|
+
return np.einsum("stp...,p->st...", self[variable], self[FC.TWEIGHTS])
|
|
187
429
|
|
|
188
430
|
@classmethod
|
|
189
431
|
def from_points(
|
|
@@ -191,7 +433,8 @@ class Data(Dict):
|
|
|
191
433
|
points,
|
|
192
434
|
data={},
|
|
193
435
|
dims={},
|
|
194
|
-
name="
|
|
436
|
+
name="tdata",
|
|
437
|
+
**kwargs,
|
|
195
438
|
):
|
|
196
439
|
"""
|
|
197
440
|
Create from points
|
|
@@ -207,6 +450,8 @@ class Data(Dict):
|
|
|
207
450
|
of data keys
|
|
208
451
|
name: str
|
|
209
452
|
The data container name
|
|
453
|
+
kwargs: dict, optional
|
|
454
|
+
Additional parameters for the constructor
|
|
210
455
|
|
|
211
456
|
Returns
|
|
212
457
|
-------
|
|
@@ -218,6 +463,55 @@ class Data(Dict):
|
|
|
218
463
|
raise ValueError(
|
|
219
464
|
f"Expecting points shape (n_states, n_points, 3), got {points.shape}"
|
|
220
465
|
)
|
|
221
|
-
data[FC.
|
|
222
|
-
dims[FC.
|
|
223
|
-
|
|
466
|
+
data[FC.TARGETS] = points[:, :, None, :]
|
|
467
|
+
dims[FC.TARGETS] = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
|
|
468
|
+
data[FC.TWEIGHTS] = np.array([1], dtype=FC.DTYPE)
|
|
469
|
+
dims[FC.TWEIGHTS] = (FC.TPOINT,)
|
|
470
|
+
return cls(data, dims, [FC.STATE, FC.TARGET], name=name, **kwargs)
|
|
471
|
+
|
|
472
|
+
@classmethod
|
|
473
|
+
def from_tpoints(
|
|
474
|
+
cls,
|
|
475
|
+
tpoints,
|
|
476
|
+
tweights,
|
|
477
|
+
data={},
|
|
478
|
+
dims={},
|
|
479
|
+
name="tdata",
|
|
480
|
+
**kwargs,
|
|
481
|
+
):
|
|
482
|
+
"""
|
|
483
|
+
Create from points at targets
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
tpoints: np.ndarray
|
|
488
|
+
The points at targets, shape:
|
|
489
|
+
(n_states, n_targets, n_tpoints, 3)
|
|
490
|
+
tweights: np.ndarray, optional
|
|
491
|
+
The target point weights, shape:
|
|
492
|
+
(n_tpoints,)
|
|
493
|
+
data: dict
|
|
494
|
+
The initial data to be stored
|
|
495
|
+
dims: dict
|
|
496
|
+
The dimensions tuples, same or subset
|
|
497
|
+
of data keys
|
|
498
|
+
name: str
|
|
499
|
+
The data container name
|
|
500
|
+
kwargs: dict, optional
|
|
501
|
+
Additional parameters for the constructor
|
|
502
|
+
|
|
503
|
+
Returns
|
|
504
|
+
-------
|
|
505
|
+
pdata: Data
|
|
506
|
+
The data object
|
|
507
|
+
|
|
508
|
+
"""
|
|
509
|
+
if len(tpoints.shape) != 4 or tpoints.shape[3] != 3:
|
|
510
|
+
raise ValueError(
|
|
511
|
+
f"Expecting tpoints shape (n_states, n_targets, n_tpoints, 3), got {tpoints.shape}"
|
|
512
|
+
)
|
|
513
|
+
data[FC.TARGETS] = tpoints
|
|
514
|
+
dims[FC.TARGETS] = (FC.STATE, FC.TARGET, FC.TPOINT, FC.XYH)
|
|
515
|
+
data[FC.TWEIGHTS] = tweights
|
|
516
|
+
dims[FC.TWEIGHTS] = (FC.TPOINT,)
|
|
517
|
+
return cls(data, dims, [FC.STATE], name=name, **kwargs)
|
foxes/core/data_calc_model.py
CHANGED
|
@@ -5,7 +5,7 @@ from dask.distributed import progress
|
|
|
5
5
|
from dask.diagnostics import ProgressBar
|
|
6
6
|
|
|
7
7
|
from .model import Model
|
|
8
|
-
from .data import
|
|
8
|
+
from .data import MData, FData, TData
|
|
9
9
|
from foxes.utils.runners import DaskRunner
|
|
10
10
|
import foxes.constants as FC
|
|
11
11
|
import foxes.variables as FV
|
|
@@ -73,7 +73,6 @@ class DataCalcModel(Model):
|
|
|
73
73
|
"""
|
|
74
74
|
Wrapper that mitigates between apply_ufunc and `calculate`.
|
|
75
75
|
"""
|
|
76
|
-
|
|
77
76
|
n_prev = len(init_vars)
|
|
78
77
|
if n_prev:
|
|
79
78
|
prev = ldata[:n_prev]
|
|
@@ -81,13 +80,23 @@ class DataCalcModel(Model):
|
|
|
81
80
|
|
|
82
81
|
# reconstruct original data:
|
|
83
82
|
data = []
|
|
84
|
-
for hvars in dvars:
|
|
83
|
+
for i, hvars in enumerate(dvars):
|
|
85
84
|
v2l = {v: lvars.index(v) for v in hvars if v in lvars}
|
|
86
85
|
v2e = {v: evars.index(v) for v in hvars if v in evars}
|
|
87
86
|
|
|
88
87
|
hdata = {v: ldata[v2l[v]] if v in v2l else edata[v2e[v]] for v in hvars}
|
|
89
88
|
hdims = {v: ldims[v2l[v]] if v in v2l else edims[v2e[v]] for v in hvars}
|
|
90
|
-
|
|
89
|
+
|
|
90
|
+
if i == 0:
|
|
91
|
+
data.append(MData(data=hdata, dims=hdims, loop_dims=loop_dims))
|
|
92
|
+
elif i == 1:
|
|
93
|
+
data.append(FData(data=hdata, dims=hdims, loop_dims=loop_dims))
|
|
94
|
+
elif i == 2:
|
|
95
|
+
data.append(TData(data=hdata, dims=hdims, loop_dims=loop_dims))
|
|
96
|
+
else:
|
|
97
|
+
raise NotImplementedError(
|
|
98
|
+
f"Not more than 3 data sets implemented, found {len(dvars)}"
|
|
99
|
+
)
|
|
91
100
|
|
|
92
101
|
del hdata, hdims, v2l, v2e
|
|
93
102
|
|
|
@@ -102,7 +111,7 @@ class DataCalcModel(Model):
|
|
|
102
111
|
raise ValueError(f"Model '{self.name}': Failed to find loop dimension")
|
|
103
112
|
|
|
104
113
|
# add zero output data arrays:
|
|
105
|
-
odims = {v: out_dims for v in out_vars}
|
|
114
|
+
odims = {v: tuple(out_dims) for v in out_vars}
|
|
106
115
|
odata = {
|
|
107
116
|
v: (
|
|
108
117
|
np.full(oshape, np.nan, dtype=FC.DTYPE)
|
|
@@ -112,35 +121,30 @@ class DataCalcModel(Model):
|
|
|
112
121
|
for v in out_vars
|
|
113
122
|
if v not in data[-1]
|
|
114
123
|
}
|
|
115
|
-
|
|
116
|
-
n_prev
|
|
117
|
-
and FV.TXYH not in odata
|
|
118
|
-
and FV.X in odata
|
|
119
|
-
and FV.X in odata
|
|
120
|
-
and FV.Y in odata
|
|
121
|
-
and FV.H in odata
|
|
122
|
-
):
|
|
123
|
-
txyh = np.zeros((data[0].n_states, data[0].n_turbines, 3), dtype=FC.DTYPE)
|
|
124
|
-
txyh[..., 0] = odata[FV.X]
|
|
125
|
-
txyh[..., 1] = odata[FV.Y]
|
|
126
|
-
txyh[..., 2] = odata[FV.H]
|
|
127
|
-
odata[FV.TXYH] = txyh
|
|
128
|
-
odims[FV.TXYH] = (FC.STATE, FC.TURBINE, FC.XYH)
|
|
129
|
-
del txyh
|
|
124
|
+
|
|
130
125
|
if len(data) == 1:
|
|
131
|
-
data.append(
|
|
126
|
+
data.append(FData(odata, odims, loop_dims))
|
|
132
127
|
else:
|
|
133
128
|
odata.update(data[-1])
|
|
134
129
|
odims.update(data[-1].dims)
|
|
135
|
-
|
|
130
|
+
if len(data) == 2:
|
|
131
|
+
data[-1] = FData(odata, odims, loop_dims)
|
|
132
|
+
else:
|
|
133
|
+
data[-1] = TData(odata, odims, loop_dims)
|
|
136
134
|
del odims, odata
|
|
137
135
|
|
|
138
|
-
# link chunk state indices from mdata to fdata and
|
|
136
|
+
# link chunk state indices from mdata to fdata and tdata:
|
|
139
137
|
if FC.STATE in data[0]:
|
|
140
138
|
for d in data[1:]:
|
|
141
139
|
d[FC.STATE] = data[0][FC.STATE]
|
|
142
140
|
|
|
141
|
+
# link weights from mdata to fdata:
|
|
142
|
+
if FV.WEIGHT in data[0]:
|
|
143
|
+
data[1][FV.WEIGHT] = data[0][FV.WEIGHT]
|
|
144
|
+
data[1].dims[FV.WEIGHT] = data[0].dims[FV.WEIGHT]
|
|
145
|
+
|
|
143
146
|
# run model calculation:
|
|
147
|
+
self.ensure_variables(algo, *data)
|
|
144
148
|
results = self.calculate(algo, *data, **calc_pars)
|
|
145
149
|
|
|
146
150
|
# replace missing results by first input data with matching shape:
|
foxes/core/farm_controller.py
CHANGED
|
@@ -16,9 +16,6 @@ class FarmController(FarmDataModel):
|
|
|
16
16
|
The turbine type of each turbine
|
|
17
17
|
turbine_model_names: list of str
|
|
18
18
|
Names of all turbine models found in the farm
|
|
19
|
-
turbine_model_sels: numpy.ndarray of bool
|
|
20
|
-
Selection flags for all turbine models,
|
|
21
|
-
shape: (n_states, n_turbines, n_models)
|
|
22
19
|
pre_rotor_models: foxes.core.FarmDataModelList
|
|
23
20
|
The turbine models with pre-rotor flag
|
|
24
21
|
post_rotor_models: foxes.core.FarmDataModelList
|
|
@@ -46,7 +43,6 @@ class FarmController(FarmDataModel):
|
|
|
46
43
|
|
|
47
44
|
self.turbine_types = None
|
|
48
45
|
self.turbine_model_names = None
|
|
49
|
-
self.turbine_model_sels = None
|
|
50
46
|
self.pre_rotor_models = None
|
|
51
47
|
self.post_rotor_models = None
|
|
52
48
|
|
|
@@ -223,29 +219,32 @@ class FarmController(FarmDataModel):
|
|
|
223
219
|
algo, pre_rotor=False, models=postr_models
|
|
224
220
|
)
|
|
225
221
|
tmsels = tmsels_pre + tmsels_post
|
|
222
|
+
self._tmall = [np.all(t) for t in tmsels]
|
|
226
223
|
self.turbine_model_names = mnames_pre + mnames_post
|
|
227
224
|
if len(self.turbine_model_names):
|
|
228
|
-
self.
|
|
225
|
+
self._tmsels = np.stack(tmsels, axis=2)
|
|
229
226
|
else:
|
|
230
227
|
raise ValueError(f"Controller '{self.name}': No turbine model found.")
|
|
231
228
|
|
|
232
|
-
def __get_pars(self, algo, models, ptype, mdata=None,
|
|
229
|
+
def __get_pars(self, algo, models, ptype, mdata=None, downwind_index=None):
|
|
233
230
|
"""
|
|
234
231
|
Private helper function for gathering model parameters.
|
|
235
232
|
"""
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
233
|
+
pars = []
|
|
234
|
+
for m in models:
|
|
235
|
+
mi = self.turbine_model_names.index(m.name)
|
|
236
|
+
if self._tmall[mi]:
|
|
237
|
+
s = np.s_[:, :] if downwind_index is None else np.s_[:, downwind_index]
|
|
238
|
+
else:
|
|
239
|
+
if downwind_index is None:
|
|
240
|
+
s = mdata[FC.TMODEL_SELS][:, :, mi]
|
|
241
|
+
else:
|
|
242
|
+
s = np.s_[
|
|
243
|
+
mdata[FC.TMODEL_SELS][:, downwind_index, mi], downwind_index
|
|
244
|
+
]
|
|
245
|
+
pars.append({"st_sel": s})
|
|
247
246
|
if m.name in self.pars:
|
|
248
|
-
pars[
|
|
247
|
+
pars[-1].update(self.pars[m.name][ptype])
|
|
249
248
|
|
|
250
249
|
return pars
|
|
251
250
|
|
|
@@ -288,11 +287,14 @@ class FarmController(FarmDataModel):
|
|
|
288
287
|
|
|
289
288
|
"""
|
|
290
289
|
idata = super().load_data(algo, verbosity)
|
|
290
|
+
|
|
291
291
|
idata["coords"][FC.TMODELS] = self.turbine_model_names
|
|
292
292
|
idata["data_vars"][FC.TMODEL_SELS] = (
|
|
293
293
|
(FC.STATE, FC.TURBINE, FC.TMODELS),
|
|
294
|
-
self.
|
|
294
|
+
self._tmsels,
|
|
295
295
|
)
|
|
296
|
+
self._tmsels = None
|
|
297
|
+
|
|
296
298
|
return idata
|
|
297
299
|
|
|
298
300
|
def output_farm_vars(self, algo):
|
|
@@ -317,7 +319,7 @@ class FarmController(FarmDataModel):
|
|
|
317
319
|
)
|
|
318
320
|
)
|
|
319
321
|
|
|
320
|
-
def calculate(self, algo, mdata, fdata, pre_rotor,
|
|
322
|
+
def calculate(self, algo, mdata, fdata, pre_rotor, downwind_index=None):
|
|
321
323
|
""" "
|
|
322
324
|
The main model calculation.
|
|
323
325
|
|
|
@@ -328,16 +330,15 @@ class FarmController(FarmDataModel):
|
|
|
328
330
|
----------
|
|
329
331
|
algo: foxes.core.Algorithm
|
|
330
332
|
The calculation algorithm
|
|
331
|
-
mdata: foxes.core.
|
|
333
|
+
mdata: foxes.core.MData
|
|
332
334
|
The model data
|
|
333
|
-
fdata: foxes.core.
|
|
335
|
+
fdata: foxes.core.FData
|
|
334
336
|
The farm data
|
|
335
337
|
pre_rotor: bool
|
|
336
338
|
Flag for running pre-rotor or post-rotor
|
|
337
339
|
models
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
(n_states, n_turbines). None for all.
|
|
340
|
+
downwind_index: int, optional
|
|
341
|
+
The index in the downwind order
|
|
341
342
|
|
|
342
343
|
Returns
|
|
343
344
|
-------
|
|
@@ -347,9 +348,8 @@ class FarmController(FarmDataModel):
|
|
|
347
348
|
|
|
348
349
|
"""
|
|
349
350
|
s = self.pre_rotor_models if pre_rotor else self.post_rotor_models
|
|
350
|
-
pars = self.__get_pars(algo, s.models, "calc", mdata,
|
|
351
|
+
pars = self.__get_pars(algo, s.models, "calc", mdata, downwind_index)
|
|
351
352
|
res = s.calculate(algo, mdata, fdata, parameters=pars)
|
|
352
|
-
self.turbine_model_sels = mdata[FC.TMODEL_SELS]
|
|
353
353
|
return res
|
|
354
354
|
|
|
355
355
|
def finalize(self, algo, verbosity=0):
|
|
@@ -366,4 +366,3 @@ class FarmController(FarmDataModel):
|
|
|
366
366
|
"""
|
|
367
367
|
super().finalize(algo, verbosity)
|
|
368
368
|
self.turbine_model_names = None
|
|
369
|
-
self.turbine_model_sels = None
|