foxes 1.2.5__py3-none-any.whl → 1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of foxes might be problematic. Click here for more details.
- examples/quickstart/run.py +17 -0
- foxes/__init__.py +1 -1
- foxes/algorithms/downwind/downwind.py +9 -15
- foxes/algorithms/downwind/models/farm_wakes_calc.py +13 -7
- foxes/algorithms/downwind/models/init_farm_data.py +4 -4
- foxes/algorithms/downwind/models/reorder_farm_output.py +5 -1
- foxes/algorithms/downwind/models/set_amb_point_results.py +1 -1
- foxes/algorithms/iterative/models/farm_wakes_calc.py +6 -3
- foxes/algorithms/sequential/models/seq_state.py +0 -18
- foxes/algorithms/sequential/sequential.py +5 -18
- foxes/constants.py +6 -0
- foxes/core/data.py +44 -18
- foxes/core/engine.py +19 -1
- foxes/core/farm_data_model.py +1 -0
- foxes/core/rotor_model.py +42 -38
- foxes/core/states.py +2 -47
- foxes/input/states/__init__.py +1 -0
- foxes/input/states/field_data_nc.py +39 -61
- foxes/input/states/multi_height.py +31 -54
- foxes/input/states/one_point_flow.py +22 -21
- foxes/input/states/scan.py +6 -19
- foxes/input/states/single.py +5 -17
- foxes/input/states/states_table.py +15 -37
- foxes/input/states/wrg_states.py +148 -36
- foxes/models/partial_wakes/rotor_points.py +8 -2
- foxes/models/partial_wakes/segregated.py +9 -4
- foxes/models/rotor_models/centre.py +6 -4
- foxes/models/wake_frames/seq_dynamic_wakes.py +5 -2
- foxes/models/wake_frames/timelines.py +10 -0
- foxes/output/farm_layout.py +12 -4
- foxes/output/farm_results_eval.py +36 -12
- foxes/output/rose_plot.py +20 -2
- foxes/output/slice_data.py +16 -19
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/METADATA +10 -8
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/RECORD +52 -51
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/WHEEL +1 -1
- tests/0_consistency/iterative/test_iterative.py +2 -3
- tests/0_consistency/partial_wakes/test_partial_wakes.py +2 -2
- tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +48 -56
- tests/1_verification/flappy_0_6/abl_states/test_abl_states.py +33 -36
- tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +3 -2
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +3 -3
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +3 -3
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +3 -3
- tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +3 -3
- tests/1_verification/flappy_0_6_2/grid_rotors/test_grid_rotors.py +3 -3
- tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +3 -2
- tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +3 -3
- tests/3_examples/test_examples.py +3 -2
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/LICENSE +0 -0
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/entry_points.txt +0 -0
- {foxes-1.2.5.dist-info → foxes-1.3.dist-info}/top_level.txt +0 -0
foxes/input/states/wrg_states.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
from scipy.interpolate import interpn
|
|
2
3
|
|
|
3
4
|
from foxes.core.states import States
|
|
4
5
|
from foxes.config import config, get_input_path
|
|
@@ -25,6 +26,8 @@ class WRGStates(States):
|
|
|
25
26
|
bounds_extra_space: float or str
|
|
26
27
|
The extra space, either float in m,
|
|
27
28
|
or str for units of D, e.g. '2.5D'
|
|
29
|
+
interpn_pars: dict
|
|
30
|
+
Additional parameters for scipy.interpolate.interpn
|
|
28
31
|
|
|
29
32
|
:group: input.states
|
|
30
33
|
|
|
@@ -36,7 +39,7 @@ class WRGStates(States):
|
|
|
36
39
|
ws_bins,
|
|
37
40
|
fixed_vars={},
|
|
38
41
|
bounds_extra_space="1D",
|
|
39
|
-
**
|
|
42
|
+
**interpn_pars,
|
|
40
43
|
):
|
|
41
44
|
"""
|
|
42
45
|
Constructor
|
|
@@ -54,15 +57,16 @@ class WRGStates(States):
|
|
|
54
57
|
bounds_extra_space: float or str, optional
|
|
55
58
|
The extra space, either float in m,
|
|
56
59
|
or str for units of D, e.g. '2.5D'
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
interpn_pars: dict, optional
|
|
61
|
+
Additional parameters for scipy.interpolate.interpn
|
|
59
62
|
|
|
60
63
|
"""
|
|
61
|
-
super().__init__(
|
|
64
|
+
super().__init__()
|
|
62
65
|
self.wrg_fname = wrg_fname
|
|
63
66
|
self.ws_bins = np.asarray(ws_bins)
|
|
64
67
|
self.fixed_vars = fixed_vars
|
|
65
68
|
self.bounds_extra_space = bounds_extra_space
|
|
69
|
+
self.interpn_pars = interpn_pars
|
|
66
70
|
|
|
67
71
|
def load_data(self, algo, verbosity=0):
|
|
68
72
|
"""
|
|
@@ -100,11 +104,14 @@ class WRGStates(States):
|
|
|
100
104
|
elif verbosity:
|
|
101
105
|
print(f"States '{self.name}': Reading file {fpath}")
|
|
102
106
|
wrg = ReaderWRG(fpath)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
p0 = np.array([wrg.x0, wrg.y0], dtype=config.dtype_double)
|
|
108
|
+
nx = wrg.nx
|
|
109
|
+
ny = wrg.ny
|
|
110
|
+
ns = wrg.n_sectors
|
|
111
|
+
res = wrg.resolution
|
|
112
|
+
p1 = p0 + np.array([nx * res, ny * res])
|
|
113
|
+
if verbosity > 0:
|
|
114
|
+
print(f"States '{self.name}': Data bounds {p0} - {p1}")
|
|
108
115
|
|
|
109
116
|
# find bounds:
|
|
110
117
|
if self.bounds_extra_space is not None:
|
|
@@ -112,48 +119,56 @@ class WRGStates(States):
|
|
|
112
119
|
extra_space=self.bounds_extra_space, algo=algo
|
|
113
120
|
)
|
|
114
121
|
if verbosity > 0:
|
|
115
|
-
print(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
xy_min -= self._p0
|
|
119
|
-
xy_max -= self._p0
|
|
120
|
-
ij_min = np.asarray(xy_min / self._res, dtype=config.dtype_int)
|
|
121
|
-
ij_max = np.asarray(xy_max / self._res, dtype=config.dtype_int) + 1
|
|
122
|
+
print(f"States '{self.name}': Farm bounds {xy_min} - {xy_max}")
|
|
123
|
+
ij_min = np.asarray((xy_min - p0) / res, dtype=config.dtype_int)
|
|
124
|
+
ij_max = np.asarray((xy_max - p0) / res, dtype=config.dtype_int) + 1
|
|
122
125
|
sx = slice(ij_min[0], ij_max[0])
|
|
123
126
|
sy = slice(ij_min[1], ij_max[1])
|
|
124
127
|
else:
|
|
125
128
|
sx = np.s_[:]
|
|
126
129
|
sy = np.s_[:]
|
|
130
|
+
self._x = p0[0] + np.arange(nx) * res
|
|
131
|
+
self._x = self._x[sx]
|
|
132
|
+
self._y = p0[1] + np.arange(ny) * res
|
|
133
|
+
self._y = self._y[sy]
|
|
134
|
+
if len(self._x) < 2 or len(self._y) < 2:
|
|
135
|
+
raise ValueError(f"No overlap between data bounds and farm bounds")
|
|
136
|
+
p0[0] = np.min(self._x)
|
|
137
|
+
p0[1] = np.min(self._y)
|
|
138
|
+
p1[0] = np.max(self._x)
|
|
139
|
+
p1[1] = np.max(self._y)
|
|
140
|
+
if verbosity > 0:
|
|
141
|
+
print(f"States '{self.name}': New bounds {p0} - {p1}")
|
|
127
142
|
|
|
128
143
|
# store data:
|
|
129
144
|
A = []
|
|
130
145
|
k = []
|
|
131
|
-
|
|
132
|
-
for s in range(
|
|
133
|
-
A.append(wrg.data[f"
|
|
134
|
-
k.append(wrg.data[f"
|
|
135
|
-
|
|
136
|
-
wrg.data[f"fs_{s}"].to_numpy().reshape(self._ny, self._nx)[sy, sx]
|
|
137
|
-
)
|
|
146
|
+
f = []
|
|
147
|
+
for s in range(ns):
|
|
148
|
+
A.append(wrg.data[f"As_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
|
|
149
|
+
k.append(wrg.data[f"Ks_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
|
|
150
|
+
f.append(wrg.data[f"fs_{s}"].to_numpy().reshape(ny, nx)[sy, sx])
|
|
138
151
|
del wrg
|
|
139
152
|
A = np.stack(A, axis=0).T
|
|
140
153
|
k = np.stack(k, axis=0).T
|
|
141
|
-
|
|
142
|
-
self._data = np.stack([A, k,
|
|
154
|
+
f = np.stack(f, axis=0).T
|
|
155
|
+
self._data = np.stack([A, k, f], axis=-1) # (x, y, wd, AKfs)
|
|
143
156
|
|
|
144
157
|
# store ws and wd:
|
|
145
|
-
self.
|
|
146
|
-
self.
|
|
158
|
+
self.VARS = self.var("VARS")
|
|
159
|
+
self.DATA = self.var("DATA")
|
|
160
|
+
self._wds = np.arange(0.0, 360.0, 360 / ns)
|
|
147
161
|
self._wsd = self.ws_bins[1:] - self.ws_bins[:-1]
|
|
148
162
|
self._wss = 0.5 * (self.ws_bins[:-1] + self.ws_bins[1:])
|
|
149
|
-
self._N = len(self._wss) *
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
self._N = len(self._wss) * ns
|
|
164
|
+
data = np.zeros((len(self._wss), ns, 3), dtype=config.dtype_double)
|
|
165
|
+
data[..., 0] = self._wss[:, None]
|
|
166
|
+
data[..., 1] = self._wds[None, :]
|
|
167
|
+
data[..., 2] = self._wsd[:, None]
|
|
168
|
+
data = data.reshape(self._N, 3)
|
|
154
169
|
idata = super().load_data(algo, verbosity)
|
|
155
|
-
idata
|
|
156
|
-
idata
|
|
170
|
+
idata["coords"][self.VARS] = ["ws", "wd", "dws"]
|
|
171
|
+
idata["data_vars"][self.DATA] = ((FC.STATE, self.VARS), data)
|
|
157
172
|
|
|
158
173
|
return idata
|
|
159
174
|
|
|
@@ -184,6 +199,103 @@ class WRGStates(States):
|
|
|
184
199
|
The output variable names
|
|
185
200
|
|
|
186
201
|
"""
|
|
187
|
-
ovars = set([FV.WS, FV.WD
|
|
188
|
-
ovars.update(self.fixed_vars.
|
|
202
|
+
ovars = set([FV.WS, FV.WD])
|
|
203
|
+
ovars.update(self.fixed_vars.keys())
|
|
189
204
|
return list(ovars)
|
|
205
|
+
|
|
206
|
+
def calculate(self, algo, mdata, fdata, tdata):
|
|
207
|
+
"""
|
|
208
|
+
The main model calculation.
|
|
209
|
+
|
|
210
|
+
This function is executed on a single chunk of data,
|
|
211
|
+
all computations should be based on numpy arrays.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
algo: foxes.core.Algorithm
|
|
216
|
+
The calculation algorithm
|
|
217
|
+
mdata: foxes.core.MData
|
|
218
|
+
The model data
|
|
219
|
+
fdata: foxes.core.FData
|
|
220
|
+
The farm data
|
|
221
|
+
tdata: foxes.core.TData
|
|
222
|
+
The target point data
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
results: dict
|
|
227
|
+
The resulting data, keys: output variable str.
|
|
228
|
+
Values: numpy.ndarray with shape
|
|
229
|
+
(n_states, n_targets, n_tpoints)
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
# prepare:
|
|
234
|
+
n_states = tdata.n_states
|
|
235
|
+
n_targets = tdata.n_targets
|
|
236
|
+
n_tpoints = tdata.n_tpoints
|
|
237
|
+
n_pts = n_states * n_targets * n_tpoints
|
|
238
|
+
points = tdata[FC.TARGETS]
|
|
239
|
+
ws = mdata[self.DATA][:, 0]
|
|
240
|
+
wd = mdata[self.DATA][:, 1]
|
|
241
|
+
wsd = mdata[self.DATA][:, 2]
|
|
242
|
+
|
|
243
|
+
out = {}
|
|
244
|
+
|
|
245
|
+
out[FV.WS] = tdata[FV.WS]
|
|
246
|
+
out[FV.WS][:] = ws[:, None, None]
|
|
247
|
+
|
|
248
|
+
out[FV.WD] = tdata[FV.WD]
|
|
249
|
+
out[FV.WD][:] = wd[:, None, None]
|
|
250
|
+
|
|
251
|
+
for v, d in self.fixed_vars.items():
|
|
252
|
+
out[v] = tdata[v]
|
|
253
|
+
out[v][:] = d
|
|
254
|
+
|
|
255
|
+
# interpolate A, k, f from x, y, wd
|
|
256
|
+
z = points[..., 2].copy()
|
|
257
|
+
points[..., 2] = wd[:, None, None]
|
|
258
|
+
pts = points.reshape(n_pts, 3)
|
|
259
|
+
gvars = (self._x, self._y, self._wds)
|
|
260
|
+
try:
|
|
261
|
+
ipars = dict(bounds_error=True, fill_value=None)
|
|
262
|
+
ipars.update(self.interpn_pars)
|
|
263
|
+
data = interpn(gvars, self._data, pts, **ipars).reshape(
|
|
264
|
+
n_states, n_targets, n_tpoints, 3
|
|
265
|
+
)
|
|
266
|
+
except ValueError as e:
|
|
267
|
+
print(f"\nStates '{self.name}': Interpolation error")
|
|
268
|
+
print("INPUT VARS: (x, y, wd)")
|
|
269
|
+
print(
|
|
270
|
+
"DATA BOUNDS:",
|
|
271
|
+
[float(np.min(d)) for d in gvars],
|
|
272
|
+
[float(np.max(d)) for d in gvars],
|
|
273
|
+
)
|
|
274
|
+
print(
|
|
275
|
+
"EVAL BOUNDS:",
|
|
276
|
+
[float(np.min(p)) for p in pts.T],
|
|
277
|
+
[float(np.max(p)) for p in pts.T],
|
|
278
|
+
)
|
|
279
|
+
print(
|
|
280
|
+
"\nMaybe you want to try the option 'bounds_error=False'? This will extrapolate the data.\n"
|
|
281
|
+
)
|
|
282
|
+
raise e
|
|
283
|
+
|
|
284
|
+
A = data[..., 0]
|
|
285
|
+
k = data[..., 1]
|
|
286
|
+
f = data[..., 2]
|
|
287
|
+
points[..., 2] = z
|
|
288
|
+
del data, gvars, pts, z, wd
|
|
289
|
+
|
|
290
|
+
tdata.add(
|
|
291
|
+
FV.WEIGHT,
|
|
292
|
+
f,
|
|
293
|
+
dims=(FC.STATE, FC.TARGET, FC.TPOINT),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
wsA = out[FV.WS] / A
|
|
297
|
+
tdata[FV.WEIGHT] *= wsd[:, None, None] * (
|
|
298
|
+
k / A * wsA ** (k - 1) * np.exp(-(wsA**k))
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return out
|
|
@@ -91,8 +91,14 @@ class RotorPoints(PartialWakesModel):
|
|
|
91
91
|
of shape (n_states, n_rotor_points)
|
|
92
92
|
|
|
93
93
|
"""
|
|
94
|
-
ares = {
|
|
95
|
-
|
|
94
|
+
ares = {
|
|
95
|
+
v: d[:, downwind_index, None] if d.shape[1] > 1 else d[:, 0, None]
|
|
96
|
+
for v, d in amb_res.items()
|
|
97
|
+
}
|
|
98
|
+
wdel = {
|
|
99
|
+
v: d[:, downwind_index, None].copy() if d.shape[1] > 1 else d[:, 0, None]
|
|
100
|
+
for v, d in wake_deltas.items()
|
|
101
|
+
}
|
|
96
102
|
wmodel.finalize_wake_deltas(algo, mdata, fdata, ares, wdel)
|
|
97
103
|
|
|
98
104
|
return {v: d[:, 0] for v, d in wdel.items()}
|
|
@@ -140,16 +140,21 @@ class PartialSegregated(PartialWakesModel):
|
|
|
140
140
|
wdel = {v: d[:, downwind_index, None].copy() for v, d in wake_deltas.items()}
|
|
141
141
|
|
|
142
142
|
if n_rotor_points == tdata.n_tpoints:
|
|
143
|
-
ares = {
|
|
143
|
+
ares = {
|
|
144
|
+
v: d[:, downwind_index, None] if d.shape[1] > 1 else d[:, 0, None]
|
|
145
|
+
for v, d in amb_res.items()
|
|
146
|
+
}
|
|
144
147
|
else:
|
|
145
148
|
ares = {}
|
|
146
149
|
for v, d in amb_res.items():
|
|
147
150
|
ares[v] = np.zeros(
|
|
148
151
|
(n_states, 1, tdata.n_tpoints), dtype=config.dtype_double
|
|
149
152
|
)
|
|
150
|
-
ares[v][:] = np.einsum(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
ares[v][:] = np.einsum(
|
|
154
|
+
"sp,p->s",
|
|
155
|
+
d[:, downwind_index] if d.shape[1] > 1 else d[:, 0],
|
|
156
|
+
rpoint_weights,
|
|
157
|
+
)[:, None, None]
|
|
153
158
|
|
|
154
159
|
wmodel.finalize_wake_deltas(algo, mdata, fdata, ares, wdel)
|
|
155
160
|
|
|
@@ -89,7 +89,7 @@ class CentreRotor(RotorModel):
|
|
|
89
89
|
mdata,
|
|
90
90
|
fdata,
|
|
91
91
|
rpoint_results,
|
|
92
|
-
|
|
92
|
+
rpoint_weights,
|
|
93
93
|
downwind_index=None,
|
|
94
94
|
copy_to_ambient=False,
|
|
95
95
|
):
|
|
@@ -124,9 +124,9 @@ class CentreRotor(RotorModel):
|
|
|
124
124
|
variables after calculation
|
|
125
125
|
|
|
126
126
|
"""
|
|
127
|
-
if len(
|
|
127
|
+
if len(rpoint_weights) > 1:
|
|
128
128
|
return super().eval_rpoint_results(
|
|
129
|
-
algo, mdata, fdata, rpoint_results,
|
|
129
|
+
algo, mdata, fdata, rpoint_results, rpoint_weights, downwind_index
|
|
130
130
|
)
|
|
131
131
|
|
|
132
132
|
n_states = mdata.n_states
|
|
@@ -192,7 +192,9 @@ class CentreRotor(RotorModel):
|
|
|
192
192
|
del uvp
|
|
193
193
|
|
|
194
194
|
for v in self.calc_vars:
|
|
195
|
-
if v not in vdone
|
|
195
|
+
if v not in vdone and (
|
|
196
|
+
fdata[v].shape[1] > 1 or downwind_index is None or downwind_index == 0
|
|
197
|
+
):
|
|
196
198
|
res = rpoint_results[v][:, :, 0]
|
|
197
199
|
self._set_res(fdata, v, res, downwind_index)
|
|
198
200
|
del res
|
|
@@ -186,12 +186,15 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
186
186
|
|
|
187
187
|
# compute wind vectors at wake traces:
|
|
188
188
|
# TODO: dz from U_z is missing here
|
|
189
|
-
|
|
189
|
+
svrs = algo.states.output_point_vars(algo)
|
|
190
|
+
hpdata = TData.from_points(
|
|
191
|
+
points=self._traces_p[None, :N, downwind_index], variables=svrs
|
|
192
|
+
)
|
|
190
193
|
res = algo.states.calculate(algo, mdata, fdata, hpdata)
|
|
191
194
|
self._traces_v[:N, downwind_index, :2] = wd2uv(
|
|
192
195
|
res[FV.WD][0, :, 0], res[FV.WS][0, :, 0]
|
|
193
196
|
)
|
|
194
|
-
del hpdata, res
|
|
197
|
+
del hpdata, res, svrs
|
|
195
198
|
|
|
196
199
|
# find nearest wake point:
|
|
197
200
|
dists = cdist(points[0], self._traces_p[:N, downwind_index])
|
|
@@ -105,6 +105,7 @@ class Timelines(WakeFrame):
|
|
|
105
105
|
|
|
106
106
|
# calculate all heights:
|
|
107
107
|
self.timelines_data = {"dxy": (("height", FC.STATE, "dir"), [])}
|
|
108
|
+
weight_data = None
|
|
108
109
|
for h in heights:
|
|
109
110
|
|
|
110
111
|
if verbosity > 0:
|
|
@@ -118,6 +119,13 @@ class Timelines(WakeFrame):
|
|
|
118
119
|
)
|
|
119
120
|
|
|
120
121
|
res = states.calculate(algo, mdata, fdata, tdata)
|
|
122
|
+
|
|
123
|
+
if weight_data is None:
|
|
124
|
+
weight_data = ((FC.STATE,), tdata[FV.WEIGHT][:, 0, 0])
|
|
125
|
+
elif not np.all(tdata[FV.WEIGHT] == weight_data[1]):
|
|
126
|
+
raise AssertionError(
|
|
127
|
+
f"States '{self.name}': weight data mismatch between heights"
|
|
128
|
+
)
|
|
121
129
|
del tdata
|
|
122
130
|
|
|
123
131
|
uv = wd2uv(res[FV.WD], res[FV.WS])[:, 0, 0, :2]
|
|
@@ -163,6 +171,8 @@ class Timelines(WakeFrame):
|
|
|
163
171
|
},
|
|
164
172
|
)
|
|
165
173
|
|
|
174
|
+
return weight_data
|
|
175
|
+
|
|
166
176
|
def initialize(self, algo, verbosity=0):
|
|
167
177
|
"""
|
|
168
178
|
Initializes the model.
|
foxes/output/farm_layout.py
CHANGED
|
@@ -5,8 +5,9 @@ import matplotlib.pyplot as plt
|
|
|
5
5
|
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
6
6
|
|
|
7
7
|
from foxes.config import config
|
|
8
|
-
import foxes.variables as FV
|
|
9
8
|
from foxes.output.output import Output
|
|
9
|
+
import foxes.variables as FV
|
|
10
|
+
import foxes.constants as FC
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class FarmLayoutOutput(Output):
|
|
@@ -229,9 +230,16 @@ class FarmLayoutOutput(Output):
|
|
|
229
230
|
if self.fres is None:
|
|
230
231
|
raise ValueError(f"Missing farm_results for color_by '{color_by}'")
|
|
231
232
|
if color_by[:5] == "mean_":
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
weights = self.fres[FV.WEIGHT]
|
|
234
|
+
if weights.dims == (FC.STATE,):
|
|
235
|
+
wx = "s"
|
|
236
|
+
elif weights.dims == (FC.STATE, FC.TURBINE):
|
|
237
|
+
wx = "st"
|
|
238
|
+
else:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
f"Unsupported dimensions for '{FV.WEIGHT}': Expecting '{(FC.STATE,)}' or '{(FC.STATE, FC.TURBINE)}', got '{weights.dims}'"
|
|
241
|
+
)
|
|
242
|
+
kw["c"] = np.einsum(f"st,{wx}->t", self.fres[color_by[5:]], weights)
|
|
235
243
|
elif color_by[:4] == "sum_":
|
|
236
244
|
kw["c"] = np.sum(self.fres[color_by[4:]], axis=0)
|
|
237
245
|
elif color_by[:4] == "min_":
|
|
@@ -76,22 +76,46 @@ class FarmResultsEval(Output):
|
|
|
76
76
|
nas = np.zeros_like(fields[-1], dtype=bool)
|
|
77
77
|
nas = nas | np.isnan(fields[-1])
|
|
78
78
|
|
|
79
|
-
inds = ["st" for
|
|
80
|
-
|
|
79
|
+
inds = ["st" for __ in fields]
|
|
80
|
+
if self.results[FV.WEIGHT].dims == (FC.STATE,):
|
|
81
|
+
inds += ["s"]
|
|
82
|
+
|
|
83
|
+
if np.any(nas):
|
|
84
|
+
sel = ~np.any(nas, axis=1)
|
|
85
|
+
fields = [f[sel] for f in fields]
|
|
86
|
+
|
|
87
|
+
weights0 = self.results[FV.WEIGHT].to_numpy()
|
|
88
|
+
w0 = np.sum(weights0)
|
|
89
|
+
weights = weights0[sel]
|
|
90
|
+
w1 = np.sum(weights)
|
|
91
|
+
weights *= w0 / w1
|
|
92
|
+
fields.append(weights)
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
fields.append(self.results[FV.WEIGHT].to_numpy())
|
|
96
|
+
|
|
97
|
+
elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
|
|
98
|
+
inds += ["st"]
|
|
81
99
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
if np.any(nas):
|
|
101
|
+
sel = ~np.any(nas, axis=1)
|
|
102
|
+
fields = [f[sel] for f in fields]
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
weights0 = self.results[FV.WEIGHT].to_numpy()
|
|
105
|
+
w0 = np.sum(weights0, axis=0)[None, :]
|
|
106
|
+
weights = weights0[sel]
|
|
107
|
+
w1 = np.sum(weights, axis=0)[None, :]
|
|
108
|
+
weights *= w0 / w1
|
|
109
|
+
fields.append(weights)
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
fields.append(self.results[FV.WEIGHT].to_numpy())
|
|
92
113
|
|
|
93
114
|
else:
|
|
94
|
-
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Expecting '{FV.WEIGHT}' variable with dimensions {(FC.STATE,)} or {(FC.STATE, FC.TURBINE)}, got {self.results[FV.WEIGHT].dims}"
|
|
117
|
+
)
|
|
118
|
+
expr = ",".join(inds) + "->" + rhs
|
|
95
119
|
|
|
96
120
|
return np.einsum(expr, *fields)
|
|
97
121
|
|
foxes/output/rose_plot.py
CHANGED
|
@@ -151,9 +151,19 @@ class RosePlotOutput(Output):
|
|
|
151
151
|
The plot data
|
|
152
152
|
|
|
153
153
|
"""
|
|
154
|
+
if self.results[FV.WEIGHT].dims == (FC.STATE,):
|
|
155
|
+
w = self.results[FV.WEIGHT].to_numpy()
|
|
156
|
+
elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
|
|
157
|
+
w = self.results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
158
|
+
elif self.results[FV.WEIGHT].dims == (FC.STATE, FC.POINT):
|
|
159
|
+
w = self.results[FV.WEIGHT].to_numpy()[:, point]
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Wrong dimensions for '{FV.WEIGHT}'. Expecting {(FC.STATE,)}, {(FC.STATE, FC.TURBINE)} or {(FC.STATE, FC.POINT)}, got {self.results[FV.WEIGHT].dims}"
|
|
163
|
+
)
|
|
164
|
+
|
|
154
165
|
if add_inf:
|
|
155
166
|
ws_bins = list(ws_bins) + [np.inf]
|
|
156
|
-
w = self.results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
157
167
|
t = turbine if self._rtype == FC.TURBINE else point
|
|
158
168
|
ws = self.results[ws_var].to_numpy()[:, t]
|
|
159
169
|
wd = self.results[wd_var].to_numpy()[:, t].copy()
|
|
@@ -449,8 +459,16 @@ class WindRoseBinPlot(Output):
|
|
|
449
459
|
The plot data
|
|
450
460
|
|
|
451
461
|
"""
|
|
462
|
+
if self.farm_results[FV.WEIGHT].dims == (FC.STATE,):
|
|
463
|
+
w = self.farm_results[FV.WEIGHT].to_numpy()
|
|
464
|
+
elif self.farm_results[FV.WEIGHT].dims == (FC.STATE, FC.TURBINE):
|
|
465
|
+
w = self.farm_results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
466
|
+
else:
|
|
467
|
+
raise ValueError(
|
|
468
|
+
f"Wrong dimensions for '{FV.WEIGHT}'. Expecting {(FC.STATE,)} or {(FC.STATE, FC.TURBINE)}, got {self.farm_results[FV.WEIGHT].dims}"
|
|
469
|
+
)
|
|
470
|
+
|
|
452
471
|
var = self.farm_results[variable].to_numpy()[:, turbine]
|
|
453
|
-
w = self.farm_results[FV.WEIGHT].to_numpy()[:, turbine]
|
|
454
472
|
ws = self.farm_results[ws_var].to_numpy()[:, turbine]
|
|
455
473
|
wd = self.farm_results[wd_var].to_numpy()[:, turbine].copy()
|
|
456
474
|
wd_delta = 360 / wd_sectors
|
foxes/output/slice_data.py
CHANGED
|
@@ -123,7 +123,6 @@ class SliceData(Output):
|
|
|
123
123
|
label_map,
|
|
124
124
|
vmin,
|
|
125
125
|
vmax,
|
|
126
|
-
weight_turbine,
|
|
127
126
|
to_file,
|
|
128
127
|
write_pars,
|
|
129
128
|
ret_states,
|
|
@@ -147,12 +146,22 @@ class SliceData(Output):
|
|
|
147
146
|
del g_pts
|
|
148
147
|
|
|
149
148
|
# take mean over states:
|
|
150
|
-
weights =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
weights = point_results[FV.WEIGHT].to_numpy()
|
|
150
|
+
if point_results[FV.WEIGHT].dims == (FC.STATE,):
|
|
151
|
+
data = {
|
|
152
|
+
v: np.einsum("s,sp->p", weights, point_results[v].to_numpy())
|
|
153
|
+
for v in variables
|
|
154
|
+
}
|
|
155
|
+
elif point_results[FV.WEIGHT].dims == (FC.STATE, FC.POINT):
|
|
156
|
+
data = {
|
|
157
|
+
v: np.einsum("sp,sp->p", weights, point_results[v].to_numpy())
|
|
158
|
+
for v in variables
|
|
159
|
+
}
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Wrong dimensions for '{FV.WEIGHT}': Expecting {(FC.STATE,)} or {(FC.STATE, FC.POINT)}, got {point_results[FV.WEIGHT].dims}"
|
|
163
|
+
)
|
|
164
|
+
del point_results, weights
|
|
156
165
|
|
|
157
166
|
# apply data modification:
|
|
158
167
|
a_pos, b_pos, c_pos, data = self._data_mod(
|
|
@@ -207,7 +216,6 @@ class SliceData(Output):
|
|
|
207
216
|
vmax={},
|
|
208
217
|
states_sel=None,
|
|
209
218
|
states_isel=None,
|
|
210
|
-
weight_turbine=0,
|
|
211
219
|
to_file=None,
|
|
212
220
|
write_pars={},
|
|
213
221
|
ret_states=False,
|
|
@@ -260,8 +268,6 @@ class SliceData(Output):
|
|
|
260
268
|
Reduce to selected states
|
|
261
269
|
states_isel: list, optional
|
|
262
270
|
Reduce to the selected states indices
|
|
263
|
-
weight_turbine: int, optional
|
|
264
|
-
Index of the turbine from which to take the weight
|
|
265
271
|
to_file: str, optional
|
|
266
272
|
Write data to this file name
|
|
267
273
|
write_pars: dict
|
|
@@ -314,7 +320,6 @@ class SliceData(Output):
|
|
|
314
320
|
label_map,
|
|
315
321
|
vmin,
|
|
316
322
|
vmax,
|
|
317
|
-
weight_turbine,
|
|
318
323
|
to_file,
|
|
319
324
|
write_pars,
|
|
320
325
|
ret_states,
|
|
@@ -352,7 +357,6 @@ class SliceData(Output):
|
|
|
352
357
|
vmax={},
|
|
353
358
|
states_sel=None,
|
|
354
359
|
states_isel=None,
|
|
355
|
-
weight_turbine=0,
|
|
356
360
|
to_file=None,
|
|
357
361
|
write_pars={},
|
|
358
362
|
ret_states=False,
|
|
@@ -407,8 +411,6 @@ class SliceData(Output):
|
|
|
407
411
|
Reduce to selected states
|
|
408
412
|
states_isel: list, optional
|
|
409
413
|
Reduce to the selected states indices
|
|
410
|
-
weight_turbine: int, optional
|
|
411
|
-
Index of the turbine from which to take the weight
|
|
412
414
|
to_file: str, optional
|
|
413
415
|
Write data to this file name
|
|
414
416
|
write_pars: dict
|
|
@@ -463,7 +465,6 @@ class SliceData(Output):
|
|
|
463
465
|
label_map,
|
|
464
466
|
vmin,
|
|
465
467
|
vmax,
|
|
466
|
-
weight_turbine,
|
|
467
468
|
to_file,
|
|
468
469
|
write_pars,
|
|
469
470
|
ret_states,
|
|
@@ -501,7 +502,6 @@ class SliceData(Output):
|
|
|
501
502
|
vmax={},
|
|
502
503
|
states_sel=None,
|
|
503
504
|
states_isel=None,
|
|
504
|
-
weight_turbine=0,
|
|
505
505
|
to_file=None,
|
|
506
506
|
write_pars={},
|
|
507
507
|
ret_states=False,
|
|
@@ -556,8 +556,6 @@ class SliceData(Output):
|
|
|
556
556
|
Reduce to selected states
|
|
557
557
|
states_isel: list, optional
|
|
558
558
|
Reduce to the selected states indices
|
|
559
|
-
weight_turbine: int, optional
|
|
560
|
-
Index of the turbine from which to take the weight
|
|
561
559
|
to_file: str, optional
|
|
562
560
|
Write data to this file name
|
|
563
561
|
write_pars: dict
|
|
@@ -612,7 +610,6 @@ class SliceData(Output):
|
|
|
612
610
|
label_map,
|
|
613
611
|
vmin,
|
|
614
612
|
vmax,
|
|
615
|
-
weight_turbine,
|
|
616
613
|
to_file,
|
|
617
614
|
write_pars,
|
|
618
615
|
ret_states,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: foxes
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3
|
|
4
4
|
Summary: Farm Optimization and eXtended yield Evaluation Software
|
|
5
5
|
Author: Jonas Schulte
|
|
6
6
|
Maintainer: Jonas Schulte
|
|
@@ -191,15 +191,17 @@ For detailed examples of how to run _foxes_, check the `examples` and `notebooks
|
|
|
191
191
|
```python
|
|
192
192
|
import foxes
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
if __name__ == "__main__":
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
foxes.input.farm_layout.add_from_file(farm, "test_farm_67.csv", turbine_models=["NREL5MW"])
|
|
196
|
+
states = foxes.input.states.Timeseries("timeseries_3000.csv.gz", ["WS", "WD","TI","RHO"])
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
farm = foxes.WindFarm()
|
|
199
|
+
foxes.input.farm_layout.add_from_file(farm, "test_farm_67.csv", turbine_models=["NREL5MW"])
|
|
201
200
|
|
|
202
|
-
|
|
201
|
+
algo = foxes.algorithms.Downwind(farm, states, ["Jensen_linear_k007"])
|
|
202
|
+
farm_results = algo.calc_farm()
|
|
203
|
+
|
|
204
|
+
print(farm_results)
|
|
203
205
|
```
|
|
204
206
|
|
|
205
207
|
## Testing
|