foxes 1.3__py3-none-any.whl → 1.4__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 +3 -3
- examples/abl_states/run.py +2 -2
- examples/compare_rotors_pwakes/run.py +1 -1
- examples/compare_wakes/run.py +1 -2
- examples/dyn_wakes/run.py +29 -6
- examples/induction/run.py +3 -3
- examples/multi_height/run.py +1 -1
- examples/power_mask/run.py +2 -2
- examples/quickstart/run.py +0 -1
- examples/random_timeseries/run.py +3 -4
- examples/scan_row/run.py +3 -3
- examples/sequential/run.py +33 -10
- examples/single_state/run.py +3 -4
- examples/states_lookup_table/run.py +3 -3
- examples/streamline_wakes/run.py +27 -4
- examples/tab_file/run.py +3 -3
- examples/timelines/run.py +29 -5
- examples/timeseries/run.py +3 -3
- examples/timeseries_slurm/run.py +3 -3
- examples/wind_rose/run.py +3 -3
- examples/yawed_wake/run.py +16 -8
- foxes/__init__.py +21 -17
- foxes/algorithms/__init__.py +6 -6
- foxes/algorithms/downwind/__init__.py +2 -2
- foxes/algorithms/downwind/downwind.py +44 -12
- foxes/algorithms/downwind/models/__init__.py +6 -6
- foxes/algorithms/downwind/models/farm_wakes_calc.py +11 -9
- foxes/algorithms/downwind/models/init_farm_data.py +0 -1
- foxes/algorithms/downwind/models/point_wakes_calc.py +7 -13
- foxes/algorithms/downwind/models/set_amb_point_results.py +6 -6
- foxes/algorithms/iterative/__init__.py +7 -3
- foxes/algorithms/iterative/iterative.py +1 -2
- foxes/algorithms/iterative/models/__init__.py +7 -3
- foxes/algorithms/iterative/models/farm_wakes_calc.py +9 -5
- foxes/algorithms/sequential/__init__.py +3 -3
- foxes/algorithms/sequential/models/__init__.py +2 -2
- foxes/algorithms/sequential/sequential.py +3 -4
- foxes/config/__init__.py +5 -1
- foxes/constants.py +16 -0
- foxes/core/__init__.py +45 -22
- foxes/core/algorithm.py +0 -1
- foxes/core/data.py +19 -18
- foxes/core/engine.py +9 -13
- foxes/core/farm_controller.py +2 -2
- foxes/core/ground_model.py +4 -13
- foxes/core/model.py +5 -5
- foxes/core/partial_wakes_model.py +147 -10
- foxes/core/point_data_model.py +2 -3
- foxes/core/rotor_model.py +3 -3
- foxes/core/states.py +2 -3
- foxes/core/turbine.py +2 -1
- foxes/core/wake_deflection.py +130 -0
- foxes/core/wake_model.py +222 -9
- foxes/core/wake_superposition.py +122 -4
- foxes/core/wind_farm.py +6 -6
- foxes/data/__init__.py +7 -2
- foxes/data/states/weibull_sectors_12.csv +13 -0
- foxes/data/states/weibull_sectors_12.nc +0 -0
- foxes/engines/__init__.py +14 -15
- foxes/engines/dask.py +39 -14
- foxes/engines/numpy.py +0 -3
- foxes/input/__init__.py +3 -3
- foxes/input/farm_layout/__init__.py +8 -8
- foxes/input/farm_layout/from_csv.py +1 -1
- foxes/input/farm_layout/ring.py +0 -1
- foxes/input/states/__init__.py +22 -12
- foxes/input/states/create/__init__.py +3 -2
- foxes/input/states/field_data_nc.py +10 -24
- foxes/input/states/multi_height.py +9 -6
- foxes/input/states/one_point_flow.py +0 -4
- foxes/input/states/single.py +1 -1
- foxes/input/states/states_table.py +10 -7
- foxes/input/states/weibull_sectors.py +225 -0
- foxes/input/states/wrg_states.py +7 -5
- foxes/input/yaml/__init__.py +9 -3
- foxes/input/yaml/dict.py +19 -19
- foxes/input/yaml/windio/__init__.py +10 -5
- foxes/input/yaml/windio/read_attributes.py +2 -2
- foxes/input/yaml/windio/read_farm.py +5 -5
- foxes/input/yaml/windio/read_fields.py +4 -2
- foxes/input/yaml/windio/read_site.py +52 -0
- foxes/input/yaml/windio/windio.py +1 -1
- foxes/models/__init__.py +15 -14
- foxes/models/axial_induction/__init__.py +2 -2
- foxes/models/farm_controllers/__init__.py +1 -1
- foxes/models/farm_models/__init__.py +1 -1
- foxes/models/ground_models/__init__.py +3 -2
- foxes/models/ground_models/wake_mirror.py +3 -3
- foxes/models/model_book.py +175 -49
- foxes/models/partial_wakes/__init__.py +6 -6
- foxes/models/partial_wakes/axiwake.py +30 -5
- foxes/models/partial_wakes/centre.py +47 -0
- foxes/models/partial_wakes/rotor_points.py +41 -11
- foxes/models/partial_wakes/segregated.py +2 -25
- foxes/models/partial_wakes/top_hat.py +27 -2
- foxes/models/point_models/__init__.py +4 -4
- foxes/models/rotor_models/__init__.py +3 -3
- foxes/models/turbine_models/__init__.py +11 -11
- foxes/models/turbine_models/set_farm_vars.py +0 -1
- foxes/models/turbine_types/PCt_file.py +0 -2
- foxes/models/turbine_types/PCt_from_two.py +0 -2
- foxes/models/turbine_types/__init__.py +9 -9
- foxes/models/vertical_profiles/__init__.py +7 -7
- foxes/models/wake_deflections/__init__.py +3 -0
- foxes/models/{wake_frames/yawed_wakes.py → wake_deflections/bastankhah2016.py} +32 -111
- foxes/models/wake_deflections/jimenez.py +277 -0
- foxes/models/wake_deflections/no_deflection.py +94 -0
- foxes/models/wake_frames/__init__.py +6 -7
- foxes/models/wake_frames/dynamic_wakes.py +12 -3
- foxes/models/wake_frames/rotor_wd.py +3 -1
- foxes/models/wake_frames/seq_dynamic_wakes.py +41 -7
- foxes/models/wake_frames/streamlines.py +8 -6
- foxes/models/wake_frames/timelines.py +9 -3
- foxes/models/wake_models/__init__.py +7 -7
- foxes/models/wake_models/dist_sliced.py +50 -84
- foxes/models/wake_models/gaussian.py +20 -0
- foxes/models/wake_models/induction/__init__.py +5 -5
- foxes/models/wake_models/induction/rankine_half_body.py +30 -71
- foxes/models/wake_models/induction/rathmann.py +65 -64
- foxes/models/wake_models/induction/self_similar.py +65 -68
- foxes/models/wake_models/induction/self_similar2020.py +0 -3
- foxes/models/wake_models/induction/vortex_sheet.py +71 -75
- foxes/models/wake_models/ti/__init__.py +2 -2
- foxes/models/wake_models/ti/crespo_hernandez.py +5 -3
- foxes/models/wake_models/ti/iec_ti.py +6 -4
- foxes/models/wake_models/top_hat.py +58 -7
- foxes/models/wake_models/wind/__init__.py +6 -4
- foxes/models/wake_models/wind/bastankhah14.py +25 -7
- foxes/models/wake_models/wind/bastankhah16.py +35 -3
- foxes/models/wake_models/wind/jensen.py +15 -2
- foxes/models/wake_models/wind/turbopark.py +28 -2
- foxes/models/wake_superpositions/__init__.py +18 -9
- foxes/models/wake_superpositions/ti_linear.py +4 -4
- foxes/models/wake_superpositions/ti_max.py +4 -4
- foxes/models/wake_superpositions/ti_pow.py +4 -4
- foxes/models/wake_superpositions/ti_quadratic.py +4 -4
- foxes/models/wake_superpositions/wind_vector.py +257 -0
- foxes/models/wake_superpositions/ws_linear.py +9 -10
- foxes/models/wake_superpositions/ws_max.py +8 -8
- foxes/models/wake_superpositions/ws_pow.py +8 -8
- foxes/models/wake_superpositions/ws_product.py +4 -4
- foxes/models/wake_superpositions/ws_quadratic.py +8 -8
- foxes/output/__init__.py +21 -19
- foxes/output/farm_layout.py +2 -2
- foxes/output/farm_results_eval.py +15 -15
- foxes/output/flow_plots_2d/__init__.py +2 -2
- foxes/output/flow_plots_2d/get_fig.py +4 -2
- foxes/output/rose_plot.py +3 -3
- foxes/output/seq_plugins/__init__.py +2 -2
- foxes/output/seq_plugins/seq_flow_ani_plugin.py +0 -3
- foxes/output/seq_plugins/seq_wake_debug_plugin.py +0 -1
- foxes/output/turbine_type_curves.py +7 -8
- foxes/utils/__init__.py +37 -19
- foxes/utils/abl/__init__.py +4 -4
- foxes/utils/cubic_roots.py +1 -1
- foxes/utils/data_book.py +4 -3
- foxes/utils/dict.py +3 -3
- foxes/utils/exec_python.py +5 -5
- foxes/utils/factory.py +1 -3
- foxes/utils/geom2d/__init__.py +7 -5
- foxes/utils/geopandas_utils.py +2 -2
- foxes/utils/pandas_utils.py +4 -3
- foxes/utils/tab_files.py +0 -1
- foxes/utils/weibull.py +28 -0
- foxes/utils/wrg_utils.py +3 -1
- foxes/utils/xarray_utils.py +9 -2
- foxes/variables.py +67 -9
- {foxes-1.3.dist-info → foxes-1.4.dist-info}/METADATA +6 -15
- foxes-1.4.dist-info/RECORD +320 -0
- {foxes-1.3.dist-info → foxes-1.4.dist-info}/WHEEL +1 -1
- tests/1_verification/flappy_0_6/PCt_files/flappy/run.py +2 -3
- tests/1_verification/flappy_0_6/PCt_files/test_PCt_files.py +1 -1
- tests/1_verification/flappy_0_6/abl_states/flappy/run.py +0 -1
- tests/1_verification/flappy_0_6/partial_top_hat/flappy/run.py +0 -1
- tests/1_verification/flappy_0_6/partial_top_hat/test_partial_top_hat.py +0 -2
- tests/1_verification/flappy_0_6/row_Jensen_linear_centre/test_row_Jensen_linear_centre.py +0 -1
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat/test_row_Jensen_linear_tophat.py +0 -1
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2005/test_row_Jensen_linear_tophat_IECTI_2005.py +0 -1
- tests/1_verification/flappy_0_6/row_Jensen_linear_tophat_IECTI2019/test_row_Jensen_linear_tophat_IECTI_2019.py +0 -1
- tests/1_verification/flappy_0_6/row_Jensen_quadratic_centre/test_row_Jensen_quadratic_centre.py +0 -1
- tests/1_verification/flappy_0_6_2/grid_rotors/flappy/run.py +0 -2
- tests/1_verification/flappy_0_6_2/row_Bastankhah_Crespo/test_row_Bastankhah_Crespo.py +0 -1
- tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/flappy/run.py +0 -1
- tests/1_verification/flappy_0_6_2/row_Bastankhah_linear_centre/test_row_Bastankhah_linear_centre.py +0 -1
- foxes/output/round.py +0 -10
- foxes/utils/pandas_helpers.py +0 -178
- foxes-1.3.dist-info/RECORD +0 -313
- {foxes-1.3.dist-info → foxes-1.4.dist-info}/entry_points.txt +0 -0
- {foxes-1.3.dist-info → foxes-1.4.dist-info/licenses}/LICENSE +0 -0
- {foxes-1.3.dist-info → foxes-1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from foxes.core.wake_deflection import WakeDeflection
|
|
4
|
+
from foxes.algorithms import Sequential
|
|
5
|
+
import foxes.constants as FC
|
|
6
|
+
import foxes.variables as FV
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JimenezDeflection(WakeDeflection):
|
|
10
|
+
"""
|
|
11
|
+
Yawed rotor wake defection according to the Jimenez model
|
|
12
|
+
|
|
13
|
+
Notes
|
|
14
|
+
-----
|
|
15
|
+
Reference:
|
|
16
|
+
Jiménez, Á., Crespo, A. and Migoya, E. (2010), Application of a LES technique to characterize
|
|
17
|
+
the wake deflection of a wind turbine in yaw. Wind Energ., 13: 559-572. doi:10.1002/we.380
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
rotate: bool
|
|
23
|
+
If True, rotate local wind vector at evaluation points.
|
|
24
|
+
If False, multiply wind speed with cos(angle) instead.
|
|
25
|
+
If None, do not modify the wind vector, only the path.
|
|
26
|
+
beta: float
|
|
27
|
+
The beta coefficient of the Jimenez model
|
|
28
|
+
step_x: float
|
|
29
|
+
The x step in m for integration
|
|
30
|
+
|
|
31
|
+
:group: models.wake_deflections
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, rotate=True, beta=0.1, step_x=10.0):
|
|
36
|
+
"""
|
|
37
|
+
Constructor.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
rotate: bool, optional
|
|
42
|
+
If True, rotate local wind vector at evaluation points.
|
|
43
|
+
If False, multiply wind speed with cos(angle) instead.
|
|
44
|
+
If None, do not modify the wind vector, only the path.
|
|
45
|
+
beta: float
|
|
46
|
+
The beta coefficient of the Jimenez model
|
|
47
|
+
step_x: float
|
|
48
|
+
The x step in m for integration
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
super().__init__()
|
|
52
|
+
self.rotate = rotate
|
|
53
|
+
self.beta = beta
|
|
54
|
+
self.step_x = step_x
|
|
55
|
+
|
|
56
|
+
def __repr__(self):
|
|
57
|
+
s = f"{type(self).__name__}("
|
|
58
|
+
s += f"rotate={self.rotate}, beta={self.beta}, step_x={self.step_x}"
|
|
59
|
+
s += ")"
|
|
60
|
+
return s
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def has_uv(self):
|
|
64
|
+
"""
|
|
65
|
+
This model uses wind vector data
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
hasuv: bool
|
|
70
|
+
Flag for wind vector data
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
return self.rotate is not None and self.rotate
|
|
74
|
+
|
|
75
|
+
def calc_deflection(
|
|
76
|
+
self,
|
|
77
|
+
algo,
|
|
78
|
+
mdata,
|
|
79
|
+
fdata,
|
|
80
|
+
tdata,
|
|
81
|
+
downwind_index,
|
|
82
|
+
coos,
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Calculates the wake deflection.
|
|
86
|
+
|
|
87
|
+
This function optionally adds FC.WDEFL_ROT_ANGLE or
|
|
88
|
+
FC.WDEFL_DWS_FACTOR to the tdata.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
algo: foxes.core.Algorithm
|
|
93
|
+
The calculation algorithm
|
|
94
|
+
mdata: foxes.core.MData
|
|
95
|
+
The model data
|
|
96
|
+
fdata: foxes.core.FData
|
|
97
|
+
The farm data
|
|
98
|
+
tdata: foxes.core.TData
|
|
99
|
+
The target point data
|
|
100
|
+
downwind_index: int
|
|
101
|
+
The index of the wake causing turbine
|
|
102
|
+
in the downwind order
|
|
103
|
+
coos: numpy.ndarray
|
|
104
|
+
The wake frame coordinates of the evaluation
|
|
105
|
+
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
coos: numpy.ndarray
|
|
110
|
+
The wake frame coordinates of the evaluation
|
|
111
|
+
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
if FV.YAWM not in fdata:
|
|
116
|
+
return coos
|
|
117
|
+
|
|
118
|
+
# take rotor average:
|
|
119
|
+
xyz = np.einsum("stpd,p->std", coos, tdata[FC.TWEIGHTS])
|
|
120
|
+
x = xyz[:, :, 0]
|
|
121
|
+
y = xyz[:, :, 1]
|
|
122
|
+
z = xyz[:, :, 2]
|
|
123
|
+
|
|
124
|
+
# get ct:
|
|
125
|
+
ct = self.get_data(
|
|
126
|
+
FV.CT,
|
|
127
|
+
FC.STATE_TARGET,
|
|
128
|
+
lookup="w",
|
|
129
|
+
algo=algo,
|
|
130
|
+
fdata=fdata,
|
|
131
|
+
tdata=tdata,
|
|
132
|
+
downwind_index=downwind_index,
|
|
133
|
+
upcast=True,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# get gamma:
|
|
137
|
+
gamma = self.get_data(
|
|
138
|
+
FV.YAWM,
|
|
139
|
+
FC.STATE_TARGET,
|
|
140
|
+
lookup="w",
|
|
141
|
+
algo=algo,
|
|
142
|
+
fdata=fdata,
|
|
143
|
+
tdata=tdata,
|
|
144
|
+
downwind_index=downwind_index,
|
|
145
|
+
upcast=True,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
sel = (x > 1e-8) & (x < 1e10) & (ct > 1e-8) & (np.abs(gamma) > 1e-8)
|
|
149
|
+
delwd = np.zeros_like(coos[..., 0])
|
|
150
|
+
n_sel = np.sum(sel)
|
|
151
|
+
if n_sel > 0:
|
|
152
|
+
# apply selection:
|
|
153
|
+
gamma = np.deg2rad(gamma[sel])
|
|
154
|
+
ct = ct[sel]
|
|
155
|
+
x = x[sel]
|
|
156
|
+
|
|
157
|
+
# get rotor diameter:
|
|
158
|
+
D = self.get_data(
|
|
159
|
+
FV.D,
|
|
160
|
+
FC.STATE_TARGET,
|
|
161
|
+
lookup="w",
|
|
162
|
+
algo=algo,
|
|
163
|
+
fdata=fdata,
|
|
164
|
+
tdata=tdata,
|
|
165
|
+
downwind_index=downwind_index,
|
|
166
|
+
upcast=True,
|
|
167
|
+
selection=sel,
|
|
168
|
+
)[:, None]
|
|
169
|
+
|
|
170
|
+
# define x path:
|
|
171
|
+
xmax = np.max(x)
|
|
172
|
+
n_x = int(xmax / self.step_x)
|
|
173
|
+
if xmax > n_x * self.step_x:
|
|
174
|
+
n_x += 1
|
|
175
|
+
delx = np.arange(n_x + 1) * self.step_x
|
|
176
|
+
delx = np.minimum(delx[None, :], x[:, None])
|
|
177
|
+
dx = delx[:, 1:] - delx[:, :-1]
|
|
178
|
+
delx = delx[:, :-1]
|
|
179
|
+
|
|
180
|
+
# integrate deflection of y along the x path:
|
|
181
|
+
alpha0 = (
|
|
182
|
+
-(np.cos(gamma[:, None]) ** 2)
|
|
183
|
+
* np.sin(gamma[:, None])
|
|
184
|
+
* ct[:, None]
|
|
185
|
+
/ 2
|
|
186
|
+
)
|
|
187
|
+
y[sel] += np.sum(
|
|
188
|
+
np.tan(alpha0 / (1 + self.beta * delx / D) ** 2) * dx, axis=-1
|
|
189
|
+
)
|
|
190
|
+
del delx, dx
|
|
191
|
+
coos[..., 1] = y[:, :, None]
|
|
192
|
+
|
|
193
|
+
# calculate wind vector modification at evaluation points:
|
|
194
|
+
if self.rotate is not None:
|
|
195
|
+
# delta wd at evaluation points, if within wake radius:
|
|
196
|
+
r2 = (y[sel, None] ** 2 + z[sel, None] ** 2) / D**2
|
|
197
|
+
WD2 = (1 + self.beta * x[:, None] / D) ** 2
|
|
198
|
+
delwd[sel] = np.where(r2 <= WD2 / 4, alpha0 / WD2, 0)
|
|
199
|
+
|
|
200
|
+
if self.rotate:
|
|
201
|
+
tdata[FC.WDEFL_ROT_ANGLE] = np.rad2deg(delwd)
|
|
202
|
+
else:
|
|
203
|
+
tdata[FC.WDEFL_DWS_FACTOR] = np.cos(delwd)
|
|
204
|
+
|
|
205
|
+
return coos
|
|
206
|
+
|
|
207
|
+
def get_yaw_alpha_seq(
|
|
208
|
+
self,
|
|
209
|
+
algo,
|
|
210
|
+
mdata,
|
|
211
|
+
fdata,
|
|
212
|
+
tdata,
|
|
213
|
+
downwind_index,
|
|
214
|
+
x,
|
|
215
|
+
):
|
|
216
|
+
"""
|
|
217
|
+
Computes sequential wind vector rotation angles.
|
|
218
|
+
|
|
219
|
+
Wind vector rotation angles are computed at the
|
|
220
|
+
current trace points due to a yawed rotor
|
|
221
|
+
for sequential runs.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
algo: foxes.core.Algorithm
|
|
226
|
+
The calculation algorithm
|
|
227
|
+
mdata: foxes.core.MData
|
|
228
|
+
The model data
|
|
229
|
+
fdata: foxes.core.FData
|
|
230
|
+
The farm data
|
|
231
|
+
tdata: foxes.core.TData
|
|
232
|
+
The target point data
|
|
233
|
+
downwind_index: int
|
|
234
|
+
The index of the wake causing turbine
|
|
235
|
+
in the downwind order
|
|
236
|
+
x: numpy.ndarray
|
|
237
|
+
The distance from the wake causing rotor
|
|
238
|
+
for the first n_times subsequent time steps,
|
|
239
|
+
shape: (n_times,)
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
alpha: numpy.ndarray
|
|
244
|
+
The delta WD result at the x locations,
|
|
245
|
+
shape: (n_times,)
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
assert isinstance(algo, Sequential), (
|
|
249
|
+
f"Wake deflection '{self.name}' requires Sequential algorithm, got '{type(algo).__name__}'"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
n_times = len(x)
|
|
253
|
+
|
|
254
|
+
def _get_data(var):
|
|
255
|
+
data = algo.farm_results_downwind[var].to_numpy()[:n_times, downwind_index]
|
|
256
|
+
data[-1] = fdata[var][0, downwind_index]
|
|
257
|
+
return data
|
|
258
|
+
|
|
259
|
+
gamma = _get_data(FV.YAWM)
|
|
260
|
+
ct = _get_data(FV.CT)
|
|
261
|
+
alpha = np.zeros_like(gamma)
|
|
262
|
+
|
|
263
|
+
sel = (ct > 1e-8) & (np.abs(gamma) > 1e-8)
|
|
264
|
+
if np.any(sel):
|
|
265
|
+
D = _get_data(FV.D)[sel]
|
|
266
|
+
gamma = np.deg2rad(gamma[sel])
|
|
267
|
+
ct = ct[sel]
|
|
268
|
+
|
|
269
|
+
alpha[sel] = np.rad2deg(
|
|
270
|
+
-(np.cos(gamma) ** 2)
|
|
271
|
+
* np.sin(gamma)
|
|
272
|
+
* ct
|
|
273
|
+
/ 2
|
|
274
|
+
/ (1 + self.beta * x / D) ** 2
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return alpha
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from foxes.core.wake_deflection import WakeDeflection
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NoDeflection(WakeDeflection):
|
|
5
|
+
"""
|
|
6
|
+
Switch of wake deflection
|
|
7
|
+
|
|
8
|
+
:group: models.wake_deflections
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def calc_deflection(
|
|
13
|
+
self,
|
|
14
|
+
algo,
|
|
15
|
+
mdata,
|
|
16
|
+
fdata,
|
|
17
|
+
tdata,
|
|
18
|
+
downwind_index,
|
|
19
|
+
coos,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Calculates the wake deflection.
|
|
23
|
+
|
|
24
|
+
This function optionally adds FC.WDEFL_ROT_ANGLE or
|
|
25
|
+
FC.WDEFL_DWS_FACTOR to the tdata.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
algo: foxes.core.Algorithm
|
|
30
|
+
The calculation algorithm
|
|
31
|
+
mdata: foxes.core.MData
|
|
32
|
+
The model data
|
|
33
|
+
fdata: foxes.core.FData
|
|
34
|
+
The farm data
|
|
35
|
+
tdata: foxes.core.TData
|
|
36
|
+
The target point data
|
|
37
|
+
downwind_index: int
|
|
38
|
+
The index of the wake causing turbine
|
|
39
|
+
in the downwind order
|
|
40
|
+
coos: numpy.ndarray
|
|
41
|
+
The wake frame coordinates of the evaluation
|
|
42
|
+
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
coos: numpy.ndarray
|
|
47
|
+
The wake frame coordinates of the evaluation
|
|
48
|
+
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return coos
|
|
52
|
+
|
|
53
|
+
def get_yaw_alpha_seq(
|
|
54
|
+
self,
|
|
55
|
+
algo,
|
|
56
|
+
mdata,
|
|
57
|
+
fdata,
|
|
58
|
+
tdata,
|
|
59
|
+
downwind_index,
|
|
60
|
+
x,
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Computes sequential wind vector rotation angles.
|
|
64
|
+
|
|
65
|
+
Wind vector rotation angles are computed at the
|
|
66
|
+
current trace points due to a yawed rotor
|
|
67
|
+
for sequential runs.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
algo: foxes.core.Algorithm
|
|
72
|
+
The calculation algorithm
|
|
73
|
+
mdata: foxes.core.MData
|
|
74
|
+
The model data
|
|
75
|
+
fdata: foxes.core.FData
|
|
76
|
+
The farm data
|
|
77
|
+
tdata: foxes.core.TData
|
|
78
|
+
The target point data
|
|
79
|
+
downwind_index: int
|
|
80
|
+
The index of the wake causing turbine
|
|
81
|
+
in the downwind order
|
|
82
|
+
x: numpy.ndarray
|
|
83
|
+
The distance from the wake causing rotor
|
|
84
|
+
for the first n_times subsequent time steps,
|
|
85
|
+
shape: (n_times,)
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
alpha: numpy.ndarray
|
|
90
|
+
The delta WD result at the x locations,
|
|
91
|
+
shape: (n_times,)
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
return None
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
Wake frame models.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .rotor_wd import RotorWD
|
|
6
|
-
from .streamlines import Streamlines2D
|
|
7
|
-
from .timelines import Timelines
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from .dynamic_wakes import DynamicWakes
|
|
5
|
+
from .rotor_wd import RotorWD as RotorWD
|
|
6
|
+
from .streamlines import Streamlines2D as Streamlines2D
|
|
7
|
+
from .timelines import Timelines as Timelines
|
|
8
|
+
from .farm_order import FarmOrder as FarmOrder
|
|
9
|
+
from .seq_dynamic_wakes import SeqDynamicWakes as SeqDynamicWakes
|
|
10
|
+
from .dynamic_wakes import DynamicWakes as DynamicWakes
|
|
@@ -161,7 +161,10 @@ class DynamicWakes(WakeFrame):
|
|
|
161
161
|
for v in algo.states.output_point_vars(algo)
|
|
162
162
|
}
|
|
163
163
|
key = f"{self.DATA}_{downwind_index}"
|
|
164
|
-
|
|
164
|
+
|
|
165
|
+
def ukey_fun(fr, to):
|
|
166
|
+
"""helper function to create update key"""
|
|
167
|
+
return f"{self.UPDATE}_dw{downwind_index}_from_{fr}_to_{to}"
|
|
165
168
|
|
|
166
169
|
# compute wakes that start within this chunk: x, y, z, length
|
|
167
170
|
data = algo.get_from_chunk_store(name=key, mdata=mdata, error=False)
|
|
@@ -274,7 +277,6 @@ class DynamicWakes(WakeFrame):
|
|
|
274
277
|
# compute single state wake propagation:
|
|
275
278
|
isnan0 = np.isnan(hdata)
|
|
276
279
|
for si in range(n_states):
|
|
277
|
-
|
|
278
280
|
s = slice(si, si + 1, None)
|
|
279
281
|
hmdata = mdata.get_slice(FC.STATE, s)
|
|
280
282
|
hfdata = fdata.get_slice(FC.STATE, s)
|
|
@@ -429,4 +431,11 @@ class DynamicWakes(WakeFrame):
|
|
|
429
431
|
(FC.STATE, FC.TARGET, FC.TPOINT),
|
|
430
432
|
)
|
|
431
433
|
|
|
432
|
-
return
|
|
434
|
+
return algo.wake_deflection.calc_deflection(
|
|
435
|
+
algo,
|
|
436
|
+
mdata,
|
|
437
|
+
fdata,
|
|
438
|
+
tdata,
|
|
439
|
+
downwind_index,
|
|
440
|
+
wcoos.reshape(n_states, n_targets, n_tpoints, 3),
|
|
441
|
+
)
|
|
@@ -116,7 +116,9 @@ class RotorWD(WakeFrame):
|
|
|
116
116
|
|
|
117
117
|
coos = np.einsum("stpd,sad->stpa", delta, nax)
|
|
118
118
|
|
|
119
|
-
return
|
|
119
|
+
return algo.wake_deflection.calc_deflection(
|
|
120
|
+
algo, mdata, fdata, tdata, downwind_index, coos
|
|
121
|
+
)
|
|
120
122
|
|
|
121
123
|
def get_centreline_points(self, algo, mdata, fdata, downwind_index, x):
|
|
122
124
|
"""
|
|
@@ -23,12 +23,14 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
23
23
|
dt_min: float, optional
|
|
24
24
|
The delta t value in minutes,
|
|
25
25
|
if not from timeseries data
|
|
26
|
+
induction: foxes.core.AxialInductionModel
|
|
27
|
+
The induction model
|
|
26
28
|
|
|
27
29
|
:group: models.wake_frames.sequential
|
|
28
30
|
|
|
29
31
|
"""
|
|
30
32
|
|
|
31
|
-
def __init__(self, cl_ipars={}, dt_min=None, **kwargs):
|
|
33
|
+
def __init__(self, cl_ipars={}, dt_min=None, induction="Madsen", **kwargs):
|
|
32
34
|
"""
|
|
33
35
|
Constructor.
|
|
34
36
|
|
|
@@ -40,6 +42,8 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
40
42
|
dt_min: float, optional
|
|
41
43
|
The delta t value in minutes,
|
|
42
44
|
if not from timeseries data
|
|
45
|
+
induction: foxes.core.AxialInductionModel or str
|
|
46
|
+
The induction model
|
|
43
47
|
kwargs: dict, optional
|
|
44
48
|
Additional parameters for the base class
|
|
45
49
|
|
|
@@ -47,9 +51,25 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
47
51
|
super().__init__(**kwargs)
|
|
48
52
|
self.cl_ipars = cl_ipars
|
|
49
53
|
self.dt_min = dt_min
|
|
54
|
+
self.induction = induction
|
|
50
55
|
|
|
51
56
|
def __repr__(self):
|
|
52
|
-
|
|
57
|
+
iname = (
|
|
58
|
+
self.induction if isinstance(self.induction, str) else self.induction.name
|
|
59
|
+
)
|
|
60
|
+
return f"{type(self).__name__}(dt_min={self.dt_min}, induction={iname})"
|
|
61
|
+
|
|
62
|
+
def sub_models(self):
|
|
63
|
+
"""
|
|
64
|
+
List of all sub-models
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
smdls: list of foxes.core.Model
|
|
69
|
+
All sub models
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
return [self.induction]
|
|
53
73
|
|
|
54
74
|
def initialize(self, algo, verbosity=0):
|
|
55
75
|
"""
|
|
@@ -63,7 +83,11 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
63
83
|
The verbosity level, 0 = silent
|
|
64
84
|
|
|
65
85
|
"""
|
|
86
|
+
if isinstance(self.induction, str):
|
|
87
|
+
self.induction = algo.mbook.axial_induction[self.induction]
|
|
88
|
+
|
|
66
89
|
super().initialize(algo, verbosity)
|
|
90
|
+
|
|
67
91
|
if not isinstance(algo, Sequential):
|
|
68
92
|
raise TypeError(
|
|
69
93
|
f"Incompatible algorithm type {type(algo).__name__}, expecting {Sequential.__name__}"
|
|
@@ -168,7 +192,6 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
168
192
|
N = counter + 1
|
|
169
193
|
|
|
170
194
|
if np.isnan(self._traces_l[counter, downwind_index]):
|
|
171
|
-
|
|
172
195
|
# new wake starts at turbine:
|
|
173
196
|
self._traces_p[counter, downwind_index][:] = fdata[FV.TXYH][
|
|
174
197
|
0, downwind_index
|
|
@@ -191,10 +214,21 @@ class SeqDynamicWakes(FarmOrder):
|
|
|
191
214
|
points=self._traces_p[None, :N, downwind_index], variables=svrs
|
|
192
215
|
)
|
|
193
216
|
res = algo.states.calculate(algo, mdata, fdata, hpdata)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
wd = res[FV.WD][0, :, 0]
|
|
218
|
+
if FV.YAWM in fdata:
|
|
219
|
+
wddef = algo.wake_deflection.get_yaw_alpha_seq(
|
|
220
|
+
algo,
|
|
221
|
+
mdata,
|
|
222
|
+
fdata,
|
|
223
|
+
hpdata,
|
|
224
|
+
downwind_index,
|
|
225
|
+
self._traces_l[:N, downwind_index],
|
|
226
|
+
)
|
|
227
|
+
if wddef is not None:
|
|
228
|
+
wd += wddef
|
|
229
|
+
del wddef
|
|
230
|
+
self._traces_v[:N, downwind_index, :2] = wd2uv(wd, res[FV.WS][0, :, 0])
|
|
231
|
+
del hpdata, res, svrs, wd
|
|
198
232
|
|
|
199
233
|
# find nearest wake point:
|
|
200
234
|
dists = cdist(points[0], self._traces_p[:N, downwind_index])
|
|
@@ -67,7 +67,6 @@ class Streamlines2D(WakeFrame):
|
|
|
67
67
|
# calc data: x, y, z, wd
|
|
68
68
|
data = np.zeros((n_states, n_turbines, N, 4), dtype=config.dtype_double)
|
|
69
69
|
for i in range(N):
|
|
70
|
-
|
|
71
70
|
# set streamline start point data (rotor centre):
|
|
72
71
|
if i == 0:
|
|
73
72
|
data[:, :, i, :3] = fdata[FV.TXYH]
|
|
@@ -75,7 +74,6 @@ class Streamlines2D(WakeFrame):
|
|
|
75
74
|
|
|
76
75
|
# compute next step:
|
|
77
76
|
else:
|
|
78
|
-
|
|
79
77
|
# calculate next point:
|
|
80
78
|
xyz = data[:, :, i - 1, :3]
|
|
81
79
|
n = wd2uv(data[:, :, i - 1, 3])
|
|
@@ -198,9 +196,9 @@ class Streamlines2D(WakeFrame):
|
|
|
198
196
|
# n_states, n_turbines_source, n_turbines_target
|
|
199
197
|
coosx = np.zeros((n_states, n_turbines, n_turbines), dtype=config.dtype_double)
|
|
200
198
|
for ti in range(n_turbines):
|
|
201
|
-
coosx[:, ti, :] = self.
|
|
202
|
-
|
|
203
|
-
]
|
|
199
|
+
coosx[:, ti, :] = self._calc_coos(
|
|
200
|
+
algo, mdata, fdata, tdata[FC.TARGETS], ti
|
|
201
|
+
)[:, :, 0, 0]
|
|
204
202
|
|
|
205
203
|
# derive turbine order:
|
|
206
204
|
# TODO: Remove loop over states
|
|
@@ -242,7 +240,11 @@ class Streamlines2D(WakeFrame):
|
|
|
242
240
|
points, shape: (n_states, n_targets, n_tpoints, 3)
|
|
243
241
|
|
|
244
242
|
"""
|
|
245
|
-
|
|
243
|
+
coos = self._calc_coos(algo, mdata, fdata, tdata[FC.TARGETS], downwind_index)
|
|
244
|
+
|
|
245
|
+
return algo.wake_deflection.calc_deflection(
|
|
246
|
+
algo, mdata, fdata, tdata, downwind_index, coos
|
|
247
|
+
)
|
|
246
248
|
|
|
247
249
|
def get_centreline_points(self, algo, mdata, fdata, downwind_index, x):
|
|
248
250
|
"""
|
|
@@ -92,7 +92,7 @@ class Timelines(WakeFrame):
|
|
|
92
92
|
del mdict, mdims, data
|
|
93
93
|
|
|
94
94
|
# prepare fdata:
|
|
95
|
-
fdata = FData(
|
|
95
|
+
fdata = FData()
|
|
96
96
|
|
|
97
97
|
# prepare tdata:
|
|
98
98
|
n_states = states.size()
|
|
@@ -107,7 +107,6 @@ class Timelines(WakeFrame):
|
|
|
107
107
|
self.timelines_data = {"dxy": (("height", FC.STATE, "dir"), [])}
|
|
108
108
|
weight_data = None
|
|
109
109
|
for h in heights:
|
|
110
|
-
|
|
111
110
|
if verbosity > 0:
|
|
112
111
|
print(f" Height: {h} m")
|
|
113
112
|
|
|
@@ -430,7 +429,14 @@ class Timelines(WakeFrame):
|
|
|
430
429
|
(FC.STATE, FC.TARGET, FC.TPOINT),
|
|
431
430
|
)
|
|
432
431
|
|
|
433
|
-
return
|
|
432
|
+
return algo.wake_deflection.calc_deflection(
|
|
433
|
+
algo,
|
|
434
|
+
mdata,
|
|
435
|
+
fdata,
|
|
436
|
+
tdata,
|
|
437
|
+
downwind_index,
|
|
438
|
+
wcoos.reshape(n_states, n_targets, n_tpoints, 3),
|
|
439
|
+
)
|
|
434
440
|
|
|
435
441
|
def get_centreline_points(self, algo, mdata, fdata, downwind_index, x):
|
|
436
442
|
"""
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
Wake models.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .dist_sliced import DistSlicedWakeModel
|
|
6
|
-
from .axisymmetric import AxisymmetricWakeModel
|
|
7
|
-
from .top_hat import TopHatWakeModel
|
|
8
|
-
from .gaussian import GaussianWakeModel
|
|
5
|
+
from .dist_sliced import DistSlicedWakeModel as DistSlicedWakeModel
|
|
6
|
+
from .axisymmetric import AxisymmetricWakeModel as AxisymmetricWakeModel
|
|
7
|
+
from .top_hat import TopHatWakeModel as TopHatWakeModel
|
|
8
|
+
from .gaussian import GaussianWakeModel as GaussianWakeModel
|
|
9
9
|
|
|
10
|
-
from . import wind
|
|
11
|
-
from . import ti
|
|
12
|
-
from . import induction
|
|
10
|
+
from . import wind as wind
|
|
11
|
+
from . import ti as ti
|
|
12
|
+
from . import induction as induction
|