Source code for floris.core.wake_velocity.gauss


from typing import Any, Dict

import numexpr as ne
import numpy as np
from attrs import define, field

from floris.core import (
    BaseModel,
    Farm,
    FlowField,
    Grid,
    Turbine,
)
from floris.utilities import (
    cosd,
    sind,
    tand,
)


[docs] @define class GaussVelocityDeficit(BaseModel): alpha: float = field(default=0.58) beta: float = field(default=0.077) ka: float = field(default=0.38) kb: float = field(default=0.004)
[docs] def prepare_function( self, grid: Grid, flow_field: FlowField, ) -> Dict[str, Any]: kwargs = { "x": grid.x_sorted, "y": grid.y_sorted, "z": grid.z_sorted, "u_initial": flow_field.u_initial_sorted, "wind_veer": flow_field.wind_veer } return kwargs
# @profile
[docs] def function( self, x_i: np.ndarray, y_i: np.ndarray, z_i: np.ndarray, axial_induction_i: np.ndarray, deflection_field_i: np.ndarray, yaw_angle_i: np.ndarray, turbulence_intensity_i: np.ndarray, ct_i: np.ndarray, hub_height_i: float, rotor_diameter_i: np.ndarray, # enforces the use of the below as keyword arguments and adherence to the # unpacking of the results from prepare_function() *, x: np.ndarray, y: np.ndarray, z: np.ndarray, u_initial: np.ndarray, wind_veer: float, ) -> None: # yaw_angle is all turbine yaw angles for each wind speed # Extract and broadcast only the current turbine yaw setting # for all wind speeds # Opposite sign convention in this model yaw_angle = -1 * yaw_angle_i # Initialize the velocity deficit uR = u_initial * ct_i / (2.0 * (1 - np.sqrt(1 - ct_i))) u0 = u_initial * np.sqrt(1 - ct_i) # Initial lateral bounds sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0)) sigma_y0 = sigma_z0 * cosd(yaw_angle) * cosd(wind_veer) # Compute the bounds of the near and far wake regions and a mask # Start of the near wake xR = x_i # Start of the far wake x0 = np.ones_like(u_initial) x0 *= rotor_diameter_i * cosd(yaw_angle) * (1 + np.sqrt(1 - ct_i) ) x0 /= np.sqrt(2) * ( 4 * self.alpha * turbulence_intensity_i + 2 * self.beta * (1 - np.sqrt(1 - ct_i) ) ) x0 += x_i # Initialize the velocity deficit array velocity_deficit = np.zeros_like(u_initial) # Masks # When we have only an inequality, the current turbine may be applied its own # wake in cases where numerical precision cause in incorrect comparison. We've # applied a small bump to avoid this. "0.1" is arbitrary but it is a small, non # zero value. # This mask defines the near wake; keeps the areas downstream of xR and upstream of x0 near_wake_mask = (x > xR + 0.1) * (x < x0) far_wake_mask = (x >= x0) # Compute the velocity deficit in the NEAR WAKE region # ONLY If there are points within the near wake boundary # TODO: for the TurbineGrid, do we need to do this near wake calculation at all? # same question for any grid with a resolution larger than the near wake region if np.sum(near_wake_mask): # Calculate the wake expansion # This is a linear ramp from 0 to 1 from the start of the near wake to the start # of the far wake. near_wake_ramp_up = (x - xR) / (x0 - xR) # Another linear ramp, but positive upstream of the far wake and negative in the # far wake; 0 at the start of the far wake near_wake_ramp_down = (x0 - x) / (x0 - xR) # near_wake_ramp_down = -1 * (near_wake_ramp_up - 1) # : this is equivalent, right? sigma_y = near_wake_ramp_down * 0.501 * rotor_diameter_i * np.sqrt(ct_i / 2.0) sigma_y += near_wake_ramp_up * sigma_y0 sigma_y *= (x >= xR) sigma_y += np.ones_like(sigma_y) * (x < xR) * 0.5 * rotor_diameter_i sigma_z = near_wake_ramp_down * 0.501 * rotor_diameter_i * np.sqrt(ct_i / 2.0) sigma_z += near_wake_ramp_up * sigma_z0 sigma_z *= (x >= xR) sigma_z += np.ones_like(sigma_z) * (x < xR) * 0.5 * rotor_diameter_i r, C = rC( wind_veer, sigma_y, sigma_z, y, y_i, deflection_field_i, z, hub_height_i, ct_i, yaw_angle, rotor_diameter_i, ) near_wake_deficit = gaussian_function(C, r, 1, np.sqrt(0.5)) near_wake_deficit *= near_wake_mask velocity_deficit += near_wake_deficit # Compute the velocity deficit in the FAR WAKE region if np.sum(far_wake_mask): # Wake expansion in the lateral (y) and the vertical (z) ky = self.ka * turbulence_intensity_i + self.kb # wake expansion parameters kz = self.ka * turbulence_intensity_i + self.kb # wake expansion parameters sigma_y = (ky * (x - x0) + sigma_y0) * far_wake_mask + sigma_y0 * (x < x0) sigma_z = (kz * (x - x0) + sigma_z0) * far_wake_mask + sigma_z0 * (x < x0) r, C = rC( wind_veer, sigma_y, sigma_z, y, y_i, deflection_field_i, z, hub_height_i, ct_i, yaw_angle, rotor_diameter_i, ) far_wake_deficit = gaussian_function(C, r, 1, np.sqrt(0.5)) far_wake_deficit *= far_wake_mask velocity_deficit += far_wake_deficit return velocity_deficit
# @profile
[docs] def rC(wind_veer, sigma_y, sigma_z, y, y_i, delta, z, HH, Ct, yaw, D): ## original # a = cosd(wind_veer) ** 2 / (2 * sigma_y ** 2) + sind(wind_veer) ** 2 / (2 * sigma_z ** 2) # b = -sind(2 * wind_veer) / (4 * sigma_y ** 2) + sind(2 * wind_veer) / (4 * sigma_z ** 2) # c = sind(wind_veer) ** 2 / (2 * sigma_y ** 2) + cosd(wind_veer) ** 2 / (2 * sigma_z ** 2) # r = ( # a * (y - y_i - delta) ** 2 # - 2 * b * (y - y_i - delta) * (z - HH) # + c * (z - HH) ** 2 # ) # C = 1 - np.sqrt(np.clip(1 - (Ct * cosd(yaw) / (8.0 * sigma_y * sigma_z / D ** 2)), 0.0, 1.0)) ## Precalculate some parts # twox_sigmay_2 = 2 * sigma_y ** 2 # twox_sigmaz_2 = 2 * sigma_z ** 2 # a = cosd(wind_veer) ** 2 / (twox_sigmay_2) + sind(wind_veer) ** 2 / (twox_sigmaz_2) # b = -sind(2 * wind_veer) / (2 * twox_sigmay_2) + sind(2 * wind_veer) / (2 * twox_sigmaz_2) # c = sind(wind_veer) ** 2 / (twox_sigmay_2) + cosd(wind_veer) ** 2 / (twox_sigmaz_2) # delta_y = y - y_i - delta # delta_z = z - HH # r = (a * (delta_y ** 2) - 2 * b * (delta_y) * (delta_z) + c * (delta_z ** 2)) # C = 1 - np.sqrt(np.clip(1 - (Ct * cosd(yaw) / (8.0 * sigma_y * sigma_z / (D * D))), 0.0, 1.0)) ## Numexpr wind_veer = np.deg2rad(wind_veer) a = ne.evaluate( "cos(wind_veer) ** 2 / (2 * sigma_y ** 2) + sin(wind_veer) ** 2 / (2 * sigma_z ** 2)" ) b = ne.evaluate( "-sin(2 * wind_veer) / (4 * sigma_y ** 2) + sin(2 * wind_veer) / (4 * sigma_z ** 2)" ) c = ne.evaluate( "sin(wind_veer) ** 2 / (2 * sigma_y ** 2) + cos(wind_veer) ** 2 / (2 * sigma_z ** 2)" ) r = ne.evaluate( "a * ((y - y_i - delta) ** 2) - 2 * b * (y - y_i - delta) * (z - HH) + c * ((z - HH) ** 2)" ) d = np.clip(1 - (Ct * cosd(yaw) / ( 8.0 * sigma_y * sigma_z / (D * D) )), 0.0, 1.0) C = ne.evaluate("1 - sqrt(d)") return r, C
[docs] def mask_upstream_wake(mesh_y_rotated, x_coord_rotated, y_coord_rotated, turbine_yaw): yR = mesh_y_rotated - y_coord_rotated xR = yR * tand(turbine_yaw) + x_coord_rotated return xR, yR
[docs] def gaussian_function(C, r, n, sigma): result = ne.evaluate("C * exp(-1 * r ** n / (2 * sigma ** 2))") return result