Skip to content
Snippets Groups Projects
fuga.py 12.7 KiB
Newer Older
Mads M. Pedersen's avatar
Mads M. Pedersen committed
from numpy import newaxis as na
import xarray as xr
from py_wake.deficit_models.deficit_model import WakeDeficitModel, BlockageDeficitModel
Mads M. Pedersen's avatar
Mads M. Pedersen committed
from py_wake.superposition_models import LinearSum
from py_wake.utils.fuga_utils import FugaUtils, LUTInterpolator
Mads M. Pedersen's avatar
Mads M. Pedersen committed
from py_wake.wind_farm_models.engineering_models import PropagateDownwind, All2AllIterative
from scipy.interpolate import RectBivariateSpline
from py_wake.utils import fuga_utils
from py_wake.utils.grid_interpolator import GridInterpolator
Mads M. Pedersen's avatar
Mads M. Pedersen committed
class FugaDeficit(WakeDeficitModel, BlockageDeficitModel, FugaUtils):
    def __init__(self, LUT_path=tfp + 'fuga/2MW/Z0=0.03000000Zi=00401Zeta0=0.00E+00.nc', remove_wriggles=False,
                 method='linear', rotorAvgModel=None, groundModel=None):
        """
        Parameters
        ----------
        LUT_path : str
            Path to folder containing 'CaseData.bin', input parameter file (*.par) and loop-up tables
        remove_wriggles : bool
            The current Fuga loop-up tables have significan wriggles.
            If True, all deficit values after the first zero crossing (when going from the center line
            and out in the lateral direction) is set to zero.
            This means that all speed-up regions are also removed
        """
        BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)
        FugaUtils.__init__(self, LUT_path, on_mismatch='input_par')
        self.remove_wriggles = remove_wriggles
        x, y, z, du = self.load()
        err_msg = "Method must be 'linear' or 'spline'. Spline is supports only height level only"
        assert method == 'linear' or (method == 'spline' and len(z) == 1), err_msg

        if method == 'linear':
            self.lut_interpolator = LUTInterpolator(x, y, z, du)
        else:
            du_interpolator = RectBivariateSpline(x, y, du[0].T)

            def interp(xyz):
                x, y, z = xyz
                assert np.all(z == self.z[0]), f'LUT table contains z={self.z} only'
                return du_interpolator.ev(x, y)
            self.lut_interpolator = interp
Mads M. Pedersen's avatar
Mads M. Pedersen committed
    def load(self):
        du = self.init_lut(self.load_luts(['UL'])[0], self.zHub, smooth2zero_x=None, smooth2zero_y=None,
                           remove_wriggles=self.remove_wriggles)
        return self.x, self.y, self.z, du
Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def interpolate(self, x, y, z):
        # self.grid_interplator(np.array([zyx.flatten() for zyx in [z, y, x]]).T, check_bounds=False).reshape(x.shape)
Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, z_ijlk, D_src_il, **_):
        self.mdu_ijlk = self.interpolate(dw_ijlk, cabs(hcw_ijlk), z_ijlk)
Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def calc_deficit(self, WS_ilk, WS_eff_ilk, dw_ijlk, hcw_ijlk, z_ijlk, ct_ilk, D_src_il, **kwargs):
            self._calc_layout_terms(dw_ijlk, hcw_ijlk, z_ijlk, D_src_il, **kwargs)
        return self.mdu_ijlk * (ct_ilk * WS_eff_ilk**2 / WS_ilk)[:, na]
Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def wake_radius(self, D_src_il, dw_ijlk, **_):
        return np.zeros_like(dw_ijlk) + D_src_il[:, na, :, na]
Mads M. Pedersen's avatar
Mads M. Pedersen committed

Mads M. Pedersen's avatar
Mads M. Pedersen committed
class FugaYawDeficit(FugaDeficit):

    def __init__(self, LUT_path=tfp + 'fuga/2MW/Z0=0.00408599Zi=00400Zeta0=0.00E+00.nc',
                 remove_wriggles=False, method='linear', rotorAvgModel=None, groundModel=None):
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        """
        Parameters
        ----------
        LUT_path : str
            Path to folder containing 'CaseData.bin', input parameter file (*.par) and loop-up tables
        remove_wriggles : bool
            The current Fuga loop-up tables have significan wriggles.
            If True, all deficit values after the first zero crossing (when going from the center line
            and out in the lateral direction) is set to zero.
            This means that all speed-up regions are also removed
        """
        BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        FugaUtils.__init__(self, LUT_path, on_mismatch='input_par')
        self.remove_wriggles = remove_wriggles
        x, y, z, dUL = self.load()

        mdUT = self.load_luts(['UT'])[0]
        dUT = np.array(mdUT, dtype=np.float32) * self.zeta0_factor(self.zHub)
Mads M. Pedersen's avatar
Mads M. Pedersen committed
        dU = np.concatenate([dUL[:, :, :, na], dUT[:, :, :, na]], 3)
        err_msg = "Method must be 'linear' or 'spline'. Spline is supports only height level only"
        assert method == 'linear' or (method == 'spline' and len(z) == 1), err_msg

        if method == 'linear':
            self.lut_interpolator = LUTInterpolator(x, y, z, dU)
        else:
            UL_interpolator = RectBivariateSpline(x, y, dU[0, :, :, 0].T)
            UT_interpolator = RectBivariateSpline(x, y, dU[0, :, :, 1].T)

            def interp(xyz):
                x, y, z = xyz
                assert np.all(z == self.z[0]), f'LUT table contains z={self.z} only'
                return np.moveaxis([UL_interpolator.ev(x, y), UT_interpolator.ev(x, y)], 0, -1)
            self.lut_interpolator = interp
Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, z_ijlk, D_src_il, **_):
        self.mdu_ijlk = (self.interpolate(dw_ijlk, cabs(hcw_ijlk), z_ijlk))

    def calc_deficit_downwind(self, WS_ilk, WS_eff_ilk, dw_ijlk, hcw_ijlk,
                              z_ijlk, ct_ilk, D_src_il, yaw_ilk, **_):
Mads M. Pedersen's avatar
Mads M. Pedersen committed

        mdUL_ijlk, mdUT_ijlk = np.moveaxis(self.interpolate(
            dw_ijlk, cabs(hcw_ijlk), z_ijlk), -1, 0)
        mdUT_ijlk = np.negative(mdUT_ijlk, out=mdUT_ijlk, where=hcw_ijlk < 0)  # UT is antisymmetric
        theta_ilk = np.deg2rad(yaw_ilk)
        mdu_ijlk = (mdUL_ijlk * np.cos(theta_ilk)[:, na] - mdUT_ijlk * np.sin(theta_ilk)[:, na])
        # avoid wake on itself
        mdu_ijlk *= ~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na]))
Mads M. Pedersen's avatar
Mads M. Pedersen committed

        return mdu_ijlk * (ct_ilk * WS_eff_ilk**2 / WS_ilk)[:, na]

    def calc_deficit(self, **kwargs):
        # fuga result is already downwind
        return self.calc_deficit_downwind(**kwargs)

Mads M. Pedersen's avatar
Mads M. Pedersen committed

    def __init__(self, LUT_path, site, windTurbines,
Mads M. Pedersen's avatar
Mads M. Pedersen committed
                 rotorAvgModel=None, deflectionModel=None, turbulenceModel=None, remove_wriggles=False):
        """
        Parameters
        ----------
        LUT_path : str
            path to look up tables
        site : Site
            Site object
        windTurbines : WindTurbines
            WindTurbines object representing the wake generating wind turbines
        rotorAvgModel : RotorAvgModel, optional
            Model defining one or more points at the down stream rotors to
            calculate the rotor average wind speeds from.\n
            if None, default, the wind speed at the rotor center is used
        deflectionModel : DeflectionModel
            Model describing the deflection of the wake due to yaw misalignment, sheared inflow, etc.
        turbulenceModel : TurbulenceModel
            Model describing the amount of added turbulence in the wake
        """
        PropagateDownwind.__init__(self, site, windTurbines,
                                   wake_deficitModel=FugaDeficit(LUT_path, remove_wriggles=remove_wriggles),
                                   rotorAvgModel=rotorAvgModel, superpositionModel=LinearSum(),
                                   deflectionModel=deflectionModel, turbulenceModel=turbulenceModel)


class FugaBlockage(All2AllIterative):
Mads M. Pedersen's avatar
Mads M. Pedersen committed
    def __init__(self, LUT_path, site, windTurbines, rotorAvgModel=None,
                 deflectionModel=None, turbulenceModel=None, convergence_tolerance=1e-6, remove_wriggles=False):
        """
        Parameters
        ----------
        LUT_path : str
            path to look up tables
        site : Site
            Site object
        windTurbines : WindTurbines
            WindTurbines object representing the wake generating wind turbines
        rotorAvgModel : RotorAvgModel, optional
            Model defining one or more points at the down stream rotors to
            calculate the rotor average wind speeds from.\n
            if None, default, the wind speed at the rotor center is used
        deflectionModel : DeflectionModel
            Model describing the deflection of the wake due to yaw misalignment, sheared inflow, etc.
        turbulenceModel : TurbulenceModel
            Model describing the amount of added turbulence in the wake
        """
        fuga_deficit = FugaDeficit(LUT_path, remove_wriggles=remove_wriggles)
        All2AllIterative.__init__(self, site, windTurbines, wake_deficitModel=fuga_deficit,
                                  rotorAvgModel=rotorAvgModel, superpositionModel=LinearSum(),
                                  deflectionModel=deflectionModel, blockage_deficitModel=fuga_deficit,
                                  turbulenceModel=turbulenceModel, convergence_tolerance=convergence_tolerance)
class list_indexer:
    def __init__(self, lst):
        self.lst = lst

    def __call__(self, x):
        return np.searchsorted(self.lst, np.minimum(x, self.lst[-1]))


class FugaMultiLUTDeficit(FugaDeficit):
    def __init__(self, LUT_path_lst=tfp + 'fuga/*.nc', remove_wriggles=False,
                 method='linear', rotorAvgModel=None, groundModel=None):
        BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)

        import glob

        def open_dataset(f):
            ds = xr.open_dataset(f).transpose('z', 'y', 'x')
            ds['TI'] = fuga_utils.ti(ds.z0, ds.hubheight)
            return ds

        if isinstance(LUT_path_lst, str):
            ds_lst = [open_dataset(f) for f in glob.glob(LUT_path_lst)]
        x_lst, y_lst, z_lst = [ds_lst[0][k].values for k in 'xyz']
        assert np.all([np.all(ds.x == ds_lst[0].x) for ds in ds_lst])
        assert np.all([np.all(ds.y == ds_lst[0].y) for ds in ds_lst])
        assert np.all([np.all(ds.z == ds_lst[0].z) for ds in ds_lst])
        assert np.all([np.all(ds.z0 == ds_lst[0].z0) for ds in ds_lst])
        assert np.all([np.all(ds.zeta0 == ds_lst[0].zeta0) for ds in ds_lst])

        self.x = ds_lst[0].x.values
        self.y = ds_lst[0].y.values
        self.z = ds_lst[0].z.values
        self.z0 = ds_lst[0].z0.item()
        self.zeta0 = ds_lst[0].zeta0.item()

        data = np.concatenate([self.init_lut(ds.UL.values, ds.hubheight.item(), remove_wriggles=remove_wriggles)[na]
        self.interpolator = GridInterpolator([i_lst, z_lst, y_lst, x_lst], data,
                                             method=['nearest', 'linear', 'linear', 'linear'])

        d_lst = np.sort(np.unique([ds.diameter.item() for ds in ds_lst]))
        h_lst = np.sort(np.unique([ds.hubheight.item() for ds in ds_lst]))
        # ti_lst = np.sort(np.unique([ds.TI.item() for ds in ds_lst]))
        d_index, h_index = [list_indexer(lst) for lst in [d_lst, h_lst]]
        # ti_searcher =

        index_arr = np.full((len(d_lst), len(h_lst)), -1)
            index_arr[d_index(ds.diameter.item()), h_index(ds.hubheight.item())] = i
        self.index_arr = index_arr
        self.d_index, self.h_index = d_index, h_index

    def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, z_ijlk, D_src_il, h_ilk, **kwargs):
        i_ilk = self.index_arr[self.d_index(D_src_il)[:, :, na], self.h_index(h_ilk)]
        i_ijlk = np.broadcast_to(i_ilk[:, na, :], dw_ijlk.shape)
        xp = np.array([i_ijlk, z_ijlk, cabs(hcw_ijlk), dw_ijlk])
        self.mdu_ijlk = self.interpolator(xp.reshape((4, -1)).T, bounds='limit').reshape(dw_ijlk.shape)

        self.mdu_ijlk *= ~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na]))  # avoid wake on itself


Mads M. Pedersen's avatar
Mads M. Pedersen committed
def main():
    if __name__ == '__main__':
        from py_wake.examples.data.iea37._iea37 import IEA37Site
        from py_wake.examples.data.iea37._iea37 import IEA37_WindTurbines
        site = IEA37Site(16)
        x, y = site.initial_position.T
        windTurbines = IEA37_WindTurbines()
        path = tfp + 'fuga/2MW/Z0=0.03000000Zi=00401Zeta0=0.00E+00.nc'
        for wf_model in [Fuga(path, site, windTurbines),
                         FugaBlockage(path, site, windTurbines)]:
            plt.figure()
            print(wf_model)
            aep = sim_res.aep().sum()

            # plot wake map
            flow_map = sim_res.flow_map(wd=30, ws=9.8)
            flow_map.plot_wake_map()
            flow_map.plot_windturbines()
            plt.title('AEP: %.2f GWh' % aep)