Skip to content
Snippets Groups Projects
rathmann.py 6.95 KiB
import numpy as np
from numpy import newaxis as na
from py_wake.deficit_models import BlockageDeficitModel


class Rathmann(BlockageDeficitModel):
    """
    Ole Sten Rathmann (DTU) developed in 2020 an approximation to the vortex
    cylinder solution (E. Branlard and M. Gaunaa, 2014). In speed it is
    comparable to the vortex dipole method, whilst giving a flow-field
    nearly identical to the vortex cylinder model for x/R < -1. Its centreline
    deficit is identical to the vortex cylinder model, whilst using a radial shape
    function that depends on the opening of the vortex cylinder seen from
    a point upstream. To simulate the speed-up downstream the deficit is mirrored in the
    rotor plane with a sign change.

    """

    args4deficit = ['WS_ilk', 'D_src_il', 'dw_ijlk', 'cw_ijlk', 'ct_ilk']

    def __init__(self, sct=1.0, limiter=1e-10, exclude_wake=True):
        BlockageDeficitModel.__init__(self)
        # coefficients for BEM approximation by Madsen (1997)
        self.a0p = np.array([0.2460, 0.0586, 0.0883])
        # limiter to avoid singularities
        self.limiter = limiter
        # coefficient for scaling the effective forcing
        self.sct = sct
        # 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).
        """
        a0_ilk = self.a0p[2] * ct_ilk**3 + self.a0p[1] * ct_ilk**2 + self.a0p[0] * ct_ilk
        return a0_ilk

    def dmu(self, xi_ijlk):
        """
        Centreline deficit shape function. Same as for the vortex cylinder model.
        """
        dmu_ijlk = 1 + xi_ijlk / np.sqrt(1 + xi_ijlk**2)
        return dmu_ijlk

    def G(self, xi_ijlk, rho_ijlk):
        """
        Radial shape function, that relies on the opening angles of the cylinder seen
        from the point (xi,rho). It is an approximation of the vortex cylinder behaviour.
        """
        # horizontal angle
        sin2a = 2 * xi_ijlk / np.sqrt((xi_ijlk**2 + (rho_ijlk - 1)**2) * (xi_ijlk**2 + (rho_ijlk + 1)**2))
        # get sin(alpha) from sin(2*alpha)
        sina = np.sqrt((1 - np.sqrt(1 - sin2a**2)) / 2)
        # vertical angle
        sinb = 1 / np.sqrt(xi_ijlk**2 + rho_ijlk**2 + 1)
        # normalise by the value along the centreline (rho=0)
        norm = 1 / (1 + xi_ijlk**2)
        G_ijlk = sina * sinb / norm

        return G_ijlk

    def calc_deficit(self, WS_ilk, D_src_il, dw_ijlk, cw_ijlk, ct_ilk, **_):
        """
        The deficit is determined from a streamwise and radial shape function, whereas
        the strength is given vom vortex and BEM theory.
        """
        # Ensure dw and cw have the correct shape
        if (cw_ijlk.shape[3] != ct_ilk.shape[2]):
            cw_ijlk = np.repeat(cw_ijlk, ct_ilk.shape[2], axis=3)
            dw_ijlk = np.repeat(dw_ijlk, ct_ilk.shape[2], axis=3)
        R_il = (D_src_il / 2)
        # circulation/strength of vortex dipole Eq. (1) in [1]
        gammat_ilk = WS_ilk * 2. * self.a0(ct_ilk * self.sct)
        # determine dimensionless radial and streamwise coordinates
        rho_ijlk = cw_ijlk / R_il[:, na, :, na]
        xi_ijlk = dw_ijlk / R_il[:, na, :, na]
        # mirror the bahaviour in the rotor-plane
        xi_ijlk[xi_ijlk > 0] = -xi_ijlk[xi_ijlk > 0]
        # centerline shape function
        dmu_ijlk = self.dmu(xi_ijlk)
        # radial shape function
        G_ijlk = self.G(xi_ijlk, rho_ijlk)
        # deficit
        deficit_ijlk = gammat_ilk[:, na] / 2. * dmu_ijlk * G_ijlk
        # turn deficiti into speed-up downstream
        deficit_ijlk[dw_ijlk > 0] = -deficit_ijlk[dw_ijlk > 0]

        if self.exclude_wake:
            # indices 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.

        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
        from py_wake.deficit_models.vortexcylinder import VortexCylinder
        from py_wake.deficit_models.vortexdipole import VortexDipole
        import matplotlib.pyplot as plt
        from py_wake import HorizontalGrid
        from timeit import default_timer as timer

        # setup site, turbines and wind farm model
        site = IEA37Site(16)
        x, y = site.initial_position.T
        windTurbines = IEA37_WindTurbines()
        d = windTurbines.diameter()
        ra = Rathmann()
        grid = HorizontalGrid(x=np.linspace(-6, 6, 100) * d, y=np.linspace(0, 4, 100) * d)

        noj_ra = All2AllIterative(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
                                  superpositionModel=LinearSum(), blockage_deficitModel=ra)
        noj_vc = All2AllIterative(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
                                  superpositionModel=LinearSum(), blockage_deficitModel=VortexCylinder())
        noj_vd = All2AllIterative(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
                                  superpositionModel=LinearSum(), blockage_deficitModel=VortexDipole())
        t1 = timer()
        flow_map = noj_ra(x=[0], y=[0], wd=[270], ws=[10]).flow_map(grid=grid)
        t2 = timer()
        flow_map_vc = noj_vc(x=[0], y=[0], wd=[270], ws=[10]).flow_map(grid=grid)
        t3 = timer()
        flow_map_vd = noj_vd(x=[0], y=[0], wd=[270], ws=[10]).flow_map(grid=grid)
        t4 = timer()
        print(t2 - t1, t3 - t2, t4 - t3)

        plt.figure()
        clevels = np.array([.6, .7, .8, .9, .95, .98, .99, .995, .998, .999, 1., 1.005, 1.01, 1.02, 1.05]) * 10.
        flow_map.plot_wake_map(levels=clevels)
        plt.contour(flow_map.x, flow_map.y, flow_map.WS_eff[:, :, 0, -1, 0], levels=clevels, colors='k', linewidths=1)
        plt.contour(flow_map.x, flow_map.y, flow_map_vc.WS_eff[:, :, 0, -1, 0], levels=clevels, colors='r', linewidths=1, linestyles='dashed')
        plt.contour(flow_map.x, flow_map.y, flow_map_vd.WS_eff[:, :, 0, -1, 0], levels=clevels, colors='b', linewidths=1, linestyles='dotted')
        plt.title('Rathmann')
        plt.ylabel("Crosswind distance [y/R]")
        plt.xlabel("Downwind distance [x/R]")
        plt.show()

        # run wind farm simulation
        sim_res = noj_ra(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_ra)
        flow_map = sim_res.flow_map(wd=0, ws=10)
        flow_map.plot_wake_map(levels=clevels, plot_colorbar=False)
        plt.title('Rathmann model, AEP: %.3f GWh' % aep)
        plt.show()


main()