Skip to content

Commit 94fe7e5

Browse files
authored
Improve various docstrings (#1171)
* Update references to floris.simulation and add docstring to get_defaults * Improve docstrings for user-level functionality * Update args docstring * Formatting * Update docstring * Fix WETO software hyperlink * Fix documentation for cut planes and add documentation for visualizations * Add Katic reference * Switch from r to r_squared to be explicit and avoid confusion * Another reference to Katic * Add notes regarding CH initial exponent * Spelling fix * Square image, update email addresses * Update todo note
1 parent 541151a commit 94fe7e5

14 files changed

Lines changed: 192 additions & 101 deletions

File tree

docs/dev_guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ git commit -m "Update so and so"
174174
175175
In order to maintain a level of confidence in the software, FLORIS is expected
176176
to maintain a reasonable level of test coverage. To that end, unit
177-
tests for a the low-level code in the `floris.simulation` package are included.
177+
tests for a the low-level code in the `floris.core` package are included.
178178
179179
The full testing suite can by executed by running the command ``pytest`` from
180180
the highest directory in the repository. A testing-only class is included
@@ -404,7 +404,7 @@ def function(
404404
```
405405
406406
Some models require a special grid and/or solver, and that mapping happens in
407-
[floris.simulation.Floris](https://github.com/NatLabRockies/floris/blob/main/floris/simulation/floris.py#L145).
407+
[floris.core.core.Core](https://github.com/NatLabRockies/floris/blob/main/floris/core/core.py).
408408
Generally, a specific kind of solver requires one or a number of specific grid-types.
409409
For example, `full_flow_sequential_solver` requires either `FlowFieldGrid` or
410410
`FlowFieldPlanarGrid`.

docs/docs_image.png

56.6 KB
Loading

docs/references.bib

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ @Article{annoni2018analysis
146146

147147
@article{crespo1996turbulence,
148148
title={Turbulence characteristics in wind-turbine wakes},
149-
author={Crespo, A and Hernandez, J},
149+
author={Crespo, A. and Hernández, J.},
150150
journal={Journal of wind engineering and industrial aerodynamics},
151151
volume={61},
152152
number={1},
@@ -369,3 +369,36 @@ @techreport{zahle_IEA22MW_2024
369369
author = {Zahle, Frederik and Barlas, Athanasios and Loenbaek, Kenneth and Bortolotti, Pietro and Zalkind, Daniel and Wang, Lu and Labuschagne, Casper and Sethuraman, Latha and Barter, Garrett},
370370
year = {2024},
371371
}
372+
373+
@article{fleming_sr_2022,
374+
title = {Serial-Refine Method for Fast Wake-Steering Yaw Optimization},
375+
volume = {2265},
376+
issn = {1742-6588, 1742-6596},
377+
url = {https://iopscience.iop.org/article/10.1088/1742-6596/2265/3/032109},
378+
doi = {10.1088/1742-6596/2265/3/032109},
379+
number = {3},
380+
journal = {Journal of Physics: Conference Series (TORQUE)},
381+
author = {Fleming, Paul A. and Stanley, Andrew P. J. and Bay, Christopher J. and King, Jennifer and Simley, Eric and Doekemeijer, Bart M. and Mudafort, Rafael},
382+
year = {2022},
383+
pages = {032109},
384+
}
385+
386+
@inproceedings{katic_sos_1986,
387+
address = {Rome, Italy},
388+
title = {A simple model for cluster efficiency},
389+
volume = {1},
390+
author = {Katic, I and Højstrup, J and Jensen, N O},
391+
year = {1986},
392+
pages = {407--410},
393+
}
394+
395+
@article{zehtabiyan_rezaie_CH_2023,
396+
title = {A short note on turbulence characteristics in wind-turbine wakes},
397+
volume = {240},
398+
issn = {0167-6105},
399+
doi = {10.1016/j.jweia.2023.105504},
400+
journal = {Journal of Wind Engineering and Industrial Aerodynamics},
401+
author = {Zehtabiyan-Rezaie, Navid and Abkar, Mahdi},
402+
year = {2023},
403+
pages = {105504},
404+
}

docs/wake_models.ipynb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@
213213
"\n",
214214
"CrespoHernandez is a wake-turbulence model that is used to compute additional variability introduced\n",
215215
"to the flow field by operation of a wind turbine. Implementation of the model follows the original\n",
216-
"formulation and limitations outlined in {cite:t}`crespo1996turbulence`."
216+
"formulation and limitations outlined in {cite:t}`crespo1996turbulence`.\n",
217+
"\n",
218+
"The default parameter values used in FLORIS for CrespoHernandez differ from those reported in {cite:t}`crespo1996turbulence` following subsequent calibration. However, {cite:t}`zehtabiyan_rezaie_CH_2023` argue that the sign of certain parameters are not physically consistent (and also misreported in subsequent literature). See the `CrespoHernandez` class docstring for more details."
217219
]
218220
},
219221
{
@@ -347,8 +349,7 @@
347349
"source": [
348350
"### Sum of Squares Freestream Superposition (SOSFS)\n",
349351
"\n",
350-
"This model combines the wakes via a sum of squares of the new wake to add and the existing\n",
351-
"flow field."
352+
"This model combines the wakes via a sum of squares of the new wake to add and the existing flow field. For more information, refer to :cite:`katic_sos_1986`."
352353
]
353354
},
354355
{

examples/examples_turbine/001_reference_turbines.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
"""Example: Check turbine power curves
1+
"""Example: Reference turbines
22
3-
For each turbine in the turbine library, make a small figure showing that its power
4-
curve and power loss to yaw are reasonable and reasonably smooth
3+
For each reference wind turbine in the turbine library, make a small figure
4+
showing its power and thrust coefficient curves and demonstrate its power loss
5+
to yaw.
56
"""
67

78

floris/core/wake_combination/sosfs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ class SOSFS(BaseModel):
1010
"""
1111
SOSFS uses sum of squares freestream superposition to combine the
1212
wake velocity deficits to the base flow field.
13+
14+
For more information, refer to :cite:`katic_sos_1986`.
1315
"""
1416

1517
def prepare_function(self) -> dict:

floris/core/wake_deflection/gauss.py

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -297,29 +297,31 @@ def wake_added_yaw(
297297
# top vortex
298298
# NOTE: this is the top of the grid, not the top of the rotor
299299
zT = z_i - (HH + D / 2) + NUM_EPS # distance from the top of the grid
300-
rT = ne.evaluate("yLocs ** 2 + zT ** 2") # TODO: This is (-) in the paper
300+
# NOTE: This is (-) in the paper, but (+) is consistent with the
301+
# Martínez-Tossas et al. (2019) source.
302+
rT_squared = ne.evaluate("yLocs ** 2 + zT ** 2")
301303
# This looks like spanwise decay;
302304
# it defines the vortex profile in the spanwise directions
303-
core_shape = ne.evaluate("1 - exp(-rT / (eps ** 2))")
304-
v_top = ne.evaluate("(Gamma_top * zT) / (2 * pi * rT) * core_shape")
305+
core_shape = ne.evaluate("1 - exp(-rT_squared / (eps ** 2))")
306+
v_top = ne.evaluate("(Gamma_top * zT) / (2 * pi * rT_squared) * core_shape")
305307
v_top = np.mean( v_top, axis=(2,3) )
306308
# w_top = (-1 * Gamma_top * yLocs) / (2 * pi * rT) * core_shape * decay
307309

308310
# bottom vortex
309311
zB = z_i - (HH - D / 2) + NUM_EPS
310-
rB = ne.evaluate("yLocs ** 2 + zB ** 2")
311-
core_shape = ne.evaluate("1 - exp(-rB / (eps ** 2))")
312-
v_bottom = ne.evaluate("(Gamma_bottom * zB) / (2 * pi * rB) * core_shape")
312+
rB_squared = ne.evaluate("yLocs ** 2 + zB ** 2")
313+
core_shape = ne.evaluate("1 - exp(-rB_squared / (eps ** 2))")
314+
v_bottom = ne.evaluate("(Gamma_bottom * zB) / (2 * pi * rB_squared) * core_shape")
313315
v_bottom = np.mean( v_bottom, axis=(2,3) )
314316
# w_bottom = (-1 * Gamma_bottom * yLocs) / (2 * pi * rB) * core_shape * decay
315317

316318
# wake rotation vortex
317319
zC = z_i - HH + NUM_EPS
318-
rC = ne.evaluate("yLocs ** 2 + zC ** 2")
319-
core_shape = ne.evaluate("1 - exp(-rC / (eps ** 2))")
320-
v_core = ne.evaluate("(Gamma_wake_rotation * zC) / (2 * pi * rC) * core_shape")
320+
rC_squared = ne.evaluate("yLocs ** 2 + zC ** 2")
321+
core_shape = ne.evaluate("1 - exp(-rC_squared / (eps ** 2))")
322+
v_core = ne.evaluate("(Gamma_wake_rotation * zC) / (2 * pi * rC_squared) * core_shape")
321323
v_core = np.mean( v_core, axis=(2,3) )
322-
# w_core = (-1 * Gamma_wake_rotation * yLocs) / (2 * pi * rC) * core_shape * decay
324+
# w_core = (-1 * Gamma_wake_rotation * yLocs) / (2 * pi * rC_squared) * core_shape * decay
323325

324326
# Cap the effective yaw values between -45 and 45 degrees
325327
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
@@ -398,51 +400,59 @@ def calculate_transverse_velocity(
398400

399401
# top vortex
400402
zT = z - (HH + D / 2) + NUM_EPS
401-
rT = ne.evaluate("yLocs ** 2 + zT ** 2") # TODO: This is - in the paper
403+
# NOTE: This is (-) in the paper, but (+) is consistent with the
404+
# Martínez-Tossas et al. (2019) source.
405+
rT_squared = ne.evaluate("yLocs ** 2 + zT ** 2")
402406
# This looks like spanwise decay;
403407
# it defines the vortex profile in the spanwise directions
404-
core_shape = ne.evaluate("1 - exp(-rT / (eps ** 2))")
405-
V1 = ne.evaluate("(Gamma_top * zT) / (2 * pi * rT) * core_shape * decay")
406-
W1 = ne.evaluate("(-1 * Gamma_top * yLocs) / (2 * pi * rT) * core_shape * decay")
408+
core_shape = ne.evaluate("1 - exp(-rT_squared / (eps ** 2))")
409+
V1 = ne.evaluate("(Gamma_top * zT) / (2 * pi * rT_squared) * core_shape * decay")
410+
W1 = ne.evaluate("(-1 * Gamma_top * yLocs) / (2 * pi * rT_squared) * core_shape * decay")
407411

408412
# bottom vortex
409413
zB = z - (HH - D / 2) + NUM_EPS
410-
rB = ne.evaluate("yLocs ** 2 + zB ** 2")
411-
core_shape = ne.evaluate("1 - exp(-rB / (eps ** 2))")
412-
V2 = ne.evaluate("(Gamma_bottom * zB) / (2 * pi * rB) * core_shape * decay")
413-
W2 = ne.evaluate("(-1 * Gamma_bottom * yLocs) / (2 * pi * rB) * core_shape * decay")
414+
rB_squared = ne.evaluate("yLocs ** 2 + zB ** 2")
415+
core_shape = ne.evaluate("1 - exp(-rB_squared / (eps ** 2))")
416+
V2 = ne.evaluate("(Gamma_bottom * zB) / (2 * pi * rB_squared) * core_shape * decay")
417+
W2 = ne.evaluate("(-1 * Gamma_bottom * yLocs) / (2 * pi * rB_squared) * core_shape * decay")
414418

415419
# wake rotation vortex
416420
zC = z - HH + NUM_EPS
417-
rC = ne.evaluate("yLocs ** 2 + zC ** 2")
418-
core_shape = ne.evaluate("1 - exp(-rC / (eps ** 2))")
419-
V5 = ne.evaluate("(Gamma_wake_rotation * zC) / (2 * pi * rC) * core_shape * decay")
420-
W5 = ne.evaluate("(-1 * Gamma_wake_rotation * yLocs) / (2 * pi * rC) * core_shape * decay")
421+
rC_squared = ne.evaluate("yLocs ** 2 + zC ** 2")
422+
core_shape = ne.evaluate("1 - exp(-rC_squared / (eps ** 2))")
423+
V5 = ne.evaluate("(Gamma_wake_rotation * zC) / (2 * pi * rC_squared) * core_shape * decay")
424+
W5 = ne.evaluate(
425+
"(-1 * Gamma_wake_rotation * yLocs) / (2 * pi * rC_squared) * core_shape * decay"
426+
)
421427

422428
### Boundary condition - ground mirror vortex
423429

424430
# top vortex - ground
425431
zTb = z + (HH + D / 2) + NUM_EPS
426-
rTb = ne.evaluate("yLocs ** 2 + zTb ** 2")
432+
rTb_squared = ne.evaluate("yLocs ** 2 + zTb ** 2")
427433
# This looks like spanwise decay;
428434
# it defines the vortex profile in the spanwise directions
429-
core_shape = ne.evaluate("1 - exp(-rTb / (eps ** 2))")
430-
V3 = ne.evaluate("(-1 * Gamma_top * zTb) / (2 * pi * rTb) * core_shape * decay")
431-
W3 = ne.evaluate("(Gamma_top * yLocs) / (2 * pi * rTb) * core_shape * decay")
435+
core_shape = ne.evaluate("1 - exp(-rTb_squared / (eps ** 2))")
436+
V3 = ne.evaluate("(-1 * Gamma_top * zTb) / (2 * pi * rTb_squared) * core_shape * decay")
437+
W3 = ne.evaluate("(Gamma_top * yLocs) / (2 * pi * rTb_squared) * core_shape * decay")
432438

433439
# bottom vortex - ground
434440
zBb = z + (HH - D / 2) + NUM_EPS
435-
rBb = ne.evaluate("yLocs ** 2 + zBb ** 2")
436-
core_shape = ne.evaluate("1 - exp(-rBb / (eps ** 2))")
437-
V4 = ne.evaluate("(-1 * Gamma_bottom * zBb) / (2 * pi * rBb) * core_shape * decay")
438-
W4 = ne.evaluate("(Gamma_bottom * yLocs) / (2 * pi * rBb) * core_shape * decay")
441+
rBb_squared = ne.evaluate("yLocs ** 2 + zBb ** 2")
442+
core_shape = ne.evaluate("1 - exp(-rBb_squared / (eps ** 2))")
443+
V4 = ne.evaluate("(-1 * Gamma_bottom * zBb) / (2 * pi * rBb_squared) * core_shape * decay")
444+
W4 = ne.evaluate("(Gamma_bottom * yLocs) / (2 * pi * rBb_squared) * core_shape * decay")
439445

440446
# wake rotation vortex - ground effect
441447
zCb = z + HH + NUM_EPS
442-
rCb = ne.evaluate("yLocs ** 2 + zCb ** 2")
443-
core_shape = ne.evaluate("1 - exp(-rCb / (eps ** 2))")
444-
V6 = ne.evaluate("(-1 * Gamma_wake_rotation * zCb) / (2 * pi * rCb) * core_shape * decay")
445-
W6 = ne.evaluate("(Gamma_wake_rotation * yLocs) / (2 * pi * rCb) * core_shape * decay")
448+
rCb_squared = ne.evaluate("yLocs ** 2 + zCb ** 2")
449+
core_shape = ne.evaluate("1 - exp(-rCb_squared / (eps ** 2))")
450+
V6 = ne.evaluate(
451+
"(-1 * Gamma_wake_rotation * zCb) / (2 * pi * rCb_squared) * core_shape * decay"
452+
)
453+
W6 = ne.evaluate(
454+
"(Gamma_wake_rotation * yLocs) / (2 * pi * rCb_squared) * core_shape * decay"
455+
)
446456

447457
# total spanwise velocity
448458
V = V1 + V2 + V3 + V4 + V5 + V6

floris/core/wake_turbulence/crespo_hernandez.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,29 @@ class CrespoHernandez(BaseModel):
2323
turbine. Implementation of the model follows the original formulation and
2424
limitations outlined in :cite:`cht-crespo1996turbulence`.
2525
26+
Note: The values for default parameters provided here differ from those in
27+
:cite:`cht-crespo1996turbulence. Following their recommendations, the
28+
default parameters would instead be:
29+
- initial: -0.0325*
30+
- constant: 0.73
31+
- ai: 0.8325
32+
- downstream: -0.32
33+
* The "initial" parameter is given as -0.0325 in :cite:`cht-crespo1996turbulence`,
34+
but the negative exponent is not clear in the scans of the paper found on the internet,
35+
and several subsequent paper cite the exponent as positive (0.0325). This discrepancy
36+
is noted in :cite:`zehtabiyan_rezaie_CH_2023`. Moreover, :cite:`zehtabiyan_rezaie_CH_2023`
37+
argues that positive values for this exponent are not representative of the physical
38+
phenomena occurring. For more details, see https://github.com/NREL/floris/issues/773.
39+
Nonetheless, the default value here is set to 0.1 for consistency with previous
40+
FLORIS versions. The default value may be updated in a future release.
41+
2642
Args:
2743
parameter_dictionary (dict): Model-specific parameters.
2844
Default values are used when a parameter is not included
2945
in `parameter_dictionary`. Possible key-value pairs include:
3046
31-
- **initial** (*float*): The initial ambient turbulence
32-
intensity, expressed as a decimal fraction.
47+
- **initial** (*float*): The exponent on the initial ambient
48+
turbulence intensity.
3349
- **constant** (*float*): The constant used to scale the
3450
wake-added turbulence intensity.
3551
- **ai** (*float*): The axial induction factor exponent used

floris/core/wake_velocity/gauss.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def function(
132132
sigma_z *= (x >= xR)
133133
sigma_z += np.ones_like(sigma_z) * (x < xR) * 0.5 * rotor_diameter_i
134134

135-
r, C = rC(
135+
r_squared, C = rC(
136136
wind_veer,
137137
sigma_y,
138138
sigma_z,
@@ -146,7 +146,7 @@ def function(
146146
rotor_diameter_i,
147147
)
148148

149-
near_wake_deficit = gaussian_function(C, r, 1, np.sqrt(0.5))
149+
near_wake_deficit = gaussian_function(C, r_squared, 1, np.sqrt(0.5))
150150
near_wake_deficit *= near_wake_mask
151151

152152
velocity_deficit += near_wake_deficit
@@ -160,7 +160,7 @@ def function(
160160
sigma_y = (ky * (x - x0) + sigma_y0) * far_wake_mask + sigma_y0 * (x < x0)
161161
sigma_z = (kz * (x - x0) + sigma_z0) * far_wake_mask + sigma_z0 * (x < x0)
162162

163-
r, C = rC(
163+
r_squared, C = rC(
164164
wind_veer,
165165
sigma_y,
166166
sigma_z,
@@ -174,7 +174,7 @@ def function(
174174
rotor_diameter_i,
175175
)
176176

177-
far_wake_deficit = gaussian_function(C, r, 1, np.sqrt(0.5))
177+
far_wake_deficit = gaussian_function(C, r_squared, 1, np.sqrt(0.5))
178178
far_wake_deficit *= far_wake_mask
179179

180180
velocity_deficit += far_wake_deficit
@@ -189,7 +189,7 @@ def rC(wind_veer, sigma_y, sigma_z, y, y_i, delta, z, HH, Ct, yaw, D):
189189
# a = cosd(wind_veer) ** 2 / (2 * sigma_y ** 2) + sind(wind_veer) ** 2 / (2 * sigma_z ** 2)
190190
# b = -sind(2 * wind_veer) / (4 * sigma_y ** 2) + sind(2 * wind_veer) / (4 * sigma_z ** 2)
191191
# c = sind(wind_veer) ** 2 / (2 * sigma_y ** 2) + cosd(wind_veer) ** 2 / (2 * sigma_z ** 2)
192-
# r = (
192+
# r_squared = (
193193
# a * (y - y_i - delta) ** 2
194194
# - 2 * b * (y - y_i - delta) * (z - HH)
195195
# + c * (z - HH) ** 2
@@ -204,7 +204,7 @@ def rC(wind_veer, sigma_y, sigma_z, y, y_i, delta, z, HH, Ct, yaw, D):
204204
# c = sind(wind_veer) ** 2 / (twox_sigmay_2) + cosd(wind_veer) ** 2 / (twox_sigmaz_2)
205205
# delta_y = y - y_i - delta
206206
# delta_z = z - HH
207-
# r = (a * (delta_y ** 2) - 2 * b * (delta_y) * (delta_z) + c * (delta_z ** 2))
207+
# r_squared = (a * (delta_y ** 2) - 2 * b * (delta_y) * (delta_z) + c * (delta_z ** 2))
208208
# C = 1 - np.sqrt(np.clip(1 - (Ct * cosd(yaw) / (8.0 * sigma_y * sigma_z / (D * D))), 0.0, 1.0))
209209

210210
## Numexpr
@@ -218,12 +218,12 @@ def rC(wind_veer, sigma_y, sigma_z, y, y_i, delta, z, HH, Ct, yaw, D):
218218
c = ne.evaluate(
219219
"sin(wind_veer) ** 2 / (2 * sigma_y ** 2) + cos(wind_veer) ** 2 / (2 * sigma_z ** 2)"
220220
)
221-
r = ne.evaluate(
221+
r_squared = ne.evaluate(
222222
"a * ((y - y_i - delta) ** 2) - 2 * b * (y - y_i - delta) * (z - HH) + c * ((z - HH) ** 2)"
223223
)
224224
d = np.clip(1 - (Ct * cosd(yaw) / ( 8.0 * sigma_y * sigma_z / (D * D) )), 0.0, 1.0)
225225
C = ne.evaluate("1 - sqrt(d)")
226-
return r, C
226+
return r_squared, C
227227

228228

229229
def mask_upstream_wake(mesh_y_rotated, x_coord_rotated, y_coord_rotated, turbine_yaw):
@@ -232,6 +232,6 @@ def mask_upstream_wake(mesh_y_rotated, x_coord_rotated, y_coord_rotated, turbine
232232
return xR, yR
233233

234234

235-
def gaussian_function(C, r, n, sigma):
236-
result = ne.evaluate("C * exp(-1 * r ** n / (2 * sigma ** 2))")
235+
def gaussian_function(C, r_squared, n, sigma):
236+
result = ne.evaluate("C * exp(-1 * r_squared ** n / (2 * sigma ** 2))")
237237
return result

0 commit comments

Comments
 (0)