Skip to content
Snippets Groups Projects
rankinehalfbody.py 5.15 KiB
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()