import numpy as np from numpy import newaxis as na from py_wake.deficit_models import DeficitModel from py_wake.deficit_models import BlockageDeficitModel from py_wake.ground_models.ground_models import NoGround from py_wake.utils.gradients import hypot class RankineHalfBody(BlockageDeficitModel): """ A simple induction model using a Rankine Half Body to represent the induction introduced by a wind turbine. The source strength is determined enforcing 1D momentum balance at the rotor disc. References: [1] B Gribben, G Hawkes - A potential flow model for wind turbine induction and wind farm blockage - Technical Paper, Frazer-Nash Consultancy, 2019 """ args4deficit = ['WS_ilk', 'D_src_il', 'dw_ijlk', 'cw_ijlk', 'ct_ilk'] def __init__(self, limiter=1e-10, exclude_wake=True, superpositionModel=None, groundModel=NoGround(), upstream_only=False): DeficitModel.__init__(self, groundModel=groundModel) BlockageDeficitModel.__init__(self, upstream_only=upstream_only, superpositionModel=superpositionModel) # coefficients for BEM approximation by Madsen (1997) self.a0p = np.array([0.2460, 0.0586, 0.0883]) # limiter to avoid singularities self.limiter = limiter # if used in a wind farm simulation, set deficit in wake region to # zero, as here the wake model is active self.exclude_wake = exclude_wake def a0(self, ct_ilk): """ BEM axial induction approximation by Madsen (1997). """ # Evaluate with Horner's rule. # a0_ilk = self.a0p[2] * ct_ilk**3 + self.a0p[1] * ct_ilk**2 + self.a0p[0] * ct_ilk a0_ilk = ct_ilk * (self.a0p[0] + ct_ilk * (self.a0p[1] + ct_ilk * self.a0p[2])) return a0_ilk def outside_body(self, WS_ilk, a0_ilk, R_il, dw_ijlk, cw_ijlk, r_ijlk): """ Find all points lying outside Rankine Half Body, stagnation line given on p.3 """ cos_ijlk = dw_ijlk / r_ijlk # avoid division by zero f_ilk = a0_ilk * R_il[:, :, na] f_ilk[f_ilk == 0.] = np.inf # replaced sin**2 and m of expression given in [1] val = cos_ijlk - 1 / f_ilk[:, na] * cw_ijlk**2 iout = val <= -1. return iout def calc_deficit(self, WS_ilk, D_src_il, dw_ijlk, cw_ijlk, ct_ilk, **_): # source strength as given on p.7 a0_ilk = self.a0(ct_ilk) R_il = D_src_il / 2. m_ilk = 2. * WS_ilk * a0_ilk * np.pi * R_il[:, :, na]**2 # radial distance r_ijlk = hypot(dw_ijlk, cw_ijlk) # find points lying outside RHB, the only ones to be computed # remove singularities r_ijlk[2 * r_ijlk / D_src_il[:, na, :, na] < self.limiter] = np.inf iout = self.outside_body(WS_ilk, a0_ilk, R_il, dw_ijlk, cw_ijlk, r_ijlk) # deficit, p.3 equation for u, negative to get deficit deficit_ijlk = -m_ilk[:, na] / (4 * np.pi) * dw_ijlk / r_ijlk**3 * (iout) if self.exclude_wake: # indices on rotor plane and in wake region iw = ((dw_ijlk / R_il[:, na, :, na] >= -self.limiter) & (np.abs(cw_ijlk) <= R_il[:, na, :, na])) * np.full(deficit_ijlk.shape, True) deficit_ijlk[iw] = 0. # Close to the rotor the induced velocities become unphysical and are # limited to the induction in the rotor plane estimated by BEM. ilim = deficit_ijlk > (WS_ilk * self.a0(ct_ilk))[:, na] deficit_ijlk[ilim] = ((WS_ilk * self.a0(ct_ilk))[:, na] * np.sign(deficit_ijlk))[ilim] return deficit_ijlk def main(): if __name__ == '__main__': from py_wake.examples.data.iea37._iea37 import IEA37Site from py_wake.examples.data.iea37._iea37 import IEA37_WindTurbines from py_wake.superposition_models import LinearSum from py_wake.wind_farm_models import All2AllIterative from py_wake.deficit_models.no_wake import NoWakeDeficit import matplotlib.pyplot as plt # setup site, turbines and wind farm model site = IEA37Site(16) x, y = site.initial_position.T windTurbines = IEA37_WindTurbines() rhb = RankineHalfBody() plt.figure() noj_rhb = All2AllIterative(site, windTurbines, wake_deficitModel=NoWakeDeficit(), superpositionModel=LinearSum(), blockage_deficitModel=rhb) flow_map = noj_rhb(x=[0], y=[0], wd=[270], ws=[10]).flow_map() clevels = np.array([.6, .7, .8, .9, .95, .98, .99, .995, .998, .999, 1., 1.01, 1.02]) * 10. flow_map.plot_wake_map(levels=clevels) plt.title('Rankine Half Body') plt.ylabel("Crosswind distance [y/R]") plt.xlabel("Downwind distance [x/R]") plt.show() # run wind farm simulation sim_res = noj_rhb(x, y, wd=[0, 30, 45, 60, 90], ws=[5, 10, 15]) # calculate AEP aep = sim_res.aep().sum() # plot wake map plt.figure() print(noj_rhb) flow_map = sim_res.flow_map(wd=0, ws=10) flow_map.plot_wake_map(levels=clevels, plot_colorbar=False) plt.title('Rankine Half Body, AEP: %.3f GWh' % aep) plt.show() main()