Source code for foxes.models.wake_frames.yawed_wakes

import numpy as np

from foxes.core import WakeFrame, WakeK, TData
from foxes.models.wake_models.wind.bastankhah16 import (
    Bastankhah2016Model,
    Bastankhah2016,
)
from foxes.config import config
import foxes.variables as FV
import foxes.constants as FC

from .rotor_wd import RotorWD


[docs] class YawedWakes(WakeFrame): """ Bend the wakes for yawed turbines, based on the Bastankhah 2016 wake model Notes ----- Reference: "Experimental and theoretical study of wind turbine wakes in yawed conditions" Majid Bastankhah, Fernando Porté-Agel https://doi.org/10.1017/jfm.2016.595 Attributes ---------- model: Bastankhah2016Model The model for computing common data model_pars: dict Model parameters YAWM: float The yaw misalignment YAWM. If not given here it will be searched in the farm data. base_frame: foxes.core.WakeFrame The wake frame from which to start :group: models.wake_frames """
[docs] def __init__( self, base_frame=RotorWD(), alpha=0.58, beta=0.07, induction="Madsen", max_length_km=30, **wake_k, ): """ Constructor. Parameters ---------- base_frame: foxes.core.WakeFrame The wake frame from which to start alpha: float model parameter used to determine onset of far wake region, if not found in wake model beta: float model parameter used to determine onset of far wake region, if not found in wake model induction: foxes.core.AxialInductionModel or str The induction model, if not found in wake model wake_k: dict, optional Parameters for the WakeK class, if not found in wake model max_length_km: float The maximal wake length in km """ super().__init__(max_length_km=max_length_km) self.base_frame = base_frame self.model = None self.alpha = alpha self.beta = beta self.induction = induction self.wake_k = None self._wake_k_pars = wake_k setattr(self, FV.YAWM, 0.0)
[docs] def __repr__(self): s = f"{type(self).__name__}(" s += self.wake_k.repr() if self.wake_k is not None else "" s += ")" return s
[docs] def sub_models(self): """ List of all sub-models Returns ------- smdls: list of foxes.core.Model Names of all sub models """ return [self.wake_k, self.base_frame, self.model]
[docs] def initialize(self, algo, verbosity=0, force=False): """ Initializes the model. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm verbosity: int The verbosity level, 0 = silent force: bool Overwrite existing data """ if not self.initialized: for w in algo.wake_models.values(): if isinstance(w, Bastankhah2016): if not w.initialized: w.initialize(algo, verbosity, force) self.model = w.model self.wake_k = w.wake_k break if self.model is None: self.model = Bastankhah2016Model( alpha=self.alpha, beta=self.beta, induction=self.induction ) if self.wake_k is None: wake_k = WakeK(**self._wake_k_pars) if not wake_k.all_none: self.wake_k = wake_k else: for w in algo.wake_models.values(): if hasattr(w, "wake_k"): self.wake_k = w.wake_k break super().initialize(algo, verbosity, force)
[docs] def calc_order(self, algo, mdata, fdata): """ Calculates the order of turbine evaluation. This function is executed on a single chunk of data, all computations should be based on numpy arrays. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm mdata: foxes.core.MData The model data fdata: foxes.core.FData The farm data Returns ------- order: numpy.ndarray The turbine order, shape: (n_states, n_turbines) """ return self.base_frame.calc_order(algo, mdata, fdata)
def _update_y(self, algo, mdata, fdata, tdata, downwind_index, x, y): """ Helper function for y deflection """ # get gamma: gamma = self.get_data( FV.YAWM, FC.STATE_TARGET, lookup="wfs", algo=algo, fdata=fdata, tdata=tdata, upcast=True, downwind_index=downwind_index, accept_nan=False, ) gamma = gamma * np.pi / 180 # get k: k = self.wake_k( FC.STATE_TARGET, lookup_ti="f", lookup_k="sf", algo=algo, fdata=fdata, tdata=tdata, upcast=True, downwind_index=downwind_index, accept_nan=False, ) # run model calculation: self.model.calc_data(algo, mdata, fdata, tdata, downwind_index, x, gamma, k) # select targets: st_sel = self.model.get_data(Bastankhah2016Model.ST_SEL, mdata) if np.any(st_sel): # prepare: n_st_sel = np.sum(st_sel) ydef = np.zeros((n_st_sel,), dtype=config.dtype_double) # collect data: near = self.model.get_data(Bastankhah2016Model.NEAR, mdata) far = ~near # near wake: if np.any(near): # collect data: delta = self.model.get_data(Bastankhah2016Model.DELTA_NEAR, mdata) # set deflection: ydef[near] = delta # far wake: if np.any(far): # collect data: delta = self.model.get_data(Bastankhah2016Model.DELTA_FAR, mdata) # set deflection: ydef[far] = delta # apply deflection: y[st_sel] -= ydef
[docs] def get_wake_coos( self, algo, mdata, fdata, tdata, downwind_index, ): """ Calculate wake coordinates of rotor points. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm mdata: foxes.core.MData The model data fdata: foxes.core.FData The farm data tdata: foxes.core.TData The target point data downwind_index: int The index of the wake causing turbine in the downwind order Returns ------- wake_coos: numpy.ndarray The wake frame coordinates of the evaluation points, shape: (n_states, n_targets, n_tpoints, 3) """ # get unyawed results: xyz = self.base_frame.get_wake_coos( algo, mdata, fdata, tdata, downwind_index, ) # take rotor average: xy = np.einsum("stpd,p->std", xyz[..., :2], tdata[FC.TWEIGHTS]) x = xy[:, :, 0] y = xy[:, :, 1] # apply deflection: self._update_y(algo, mdata, fdata, tdata, downwind_index, x, y) xyz[..., 1] = y[:, :, None] return xyz
[docs] def get_centreline_points(self, algo, mdata, fdata, downwind_index, x): """ Gets the points along the centreline for given values of x. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm mdata: foxes.core.MData The model data fdata: foxes.core.FData The farm data downwind_index: int The index in the downwind order x: numpy.ndarray The wake frame x coordinates, shape: (n_states, n_points) Returns ------- points: numpy.ndarray The centreline points, shape: (n_states, n_points, 3) """ points = self.base_frame.get_centreline_points( algo, mdata, fdata, downwind_index, x ) tdata = TData.from_points(points) nx = np.zeros_like(points) nx[:, 0] = points[:, 1] - points[:, 0] nx[:, -1] = points[:, -1] - points[:, -2] nx[:, 1:-1] = 0.5 * (points[:, 1:-1] - points[:, :-2]) + 0.5 * ( points[:, 2:] - points[:, 1:-1] ) nx /= np.linalg.norm(nx, axis=-1)[:, :, None] nz = np.zeros_like(nx) nz[:, :, 2] = 1 ny = np.cross(nz, nx, axis=-1) del nx, nz y = np.zeros_like(x) self._update_y(algo, mdata, fdata, tdata, downwind_index, x, y) points += y[:, :, None] * ny return points