From f58e8bf7719b107e73bfe86284adccb4a71a0447 Mon Sep 17 00:00:00 2001 From: "Mads M. Pedersen" <mmpe@dtu.dk> Date: Thu, 6 Mar 2025 12:03:09 +0000 Subject: [PATCH] fix fuga wake radius (before it was fixed to D) + switch bounds from limit to check in Fuga + tests --- py_wake/deficit_models/fuga.py | 51 +++++++++++++++---- .../test_blockage_models.py | 3 +- .../test_deficit_models.py | 4 +- .../tests/test_deficit_models/test_fuga.py | 44 +++++++++++++++- 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/py_wake/deficit_models/fuga.py b/py_wake/deficit_models/fuga.py index 952da611d..8b8af5ecc 100644 --- a/py_wake/deficit_models/fuga.py +++ b/py_wake/deficit_models/fuga.py @@ -19,7 +19,7 @@ import os class Fuga(PropagateDownwind, DeprecatedModel): def __init__(self, LUT_path, site, windTurbines, rotorAvgModel=None, deflectionModel=None, turbulenceModel=None, - smooth2zero_x=None, smooth2zero_y=None, remove_wriggles=False): + bounds='limit', smooth2zero_x=None, smooth2zero_y=None, remove_wriggles=False): """ Parameters ---------- @@ -39,7 +39,7 @@ class Fuga(PropagateDownwind, DeprecatedModel): Model describing the amount of added turbulence in the wake """ PropagateDownwind.__init__(self, site, windTurbines, - wake_deficitModel=FugaDeficit(LUT_path, + wake_deficitModel=FugaDeficit(LUT_path, bounds=bounds, smooth2zero_x=smooth2zero_x, smooth2zero_y=smooth2zero_y, remove_wriggles=remove_wriggles), @@ -50,7 +50,7 @@ class Fuga(PropagateDownwind, DeprecatedModel): class FugaBlockage(All2AllIterative, DeprecatedModel): def __init__(self, LUT_path, site, windTurbines, rotorAvgModel=None, - deflectionModel=None, turbulenceModel=None, convergence_tolerance=1e-6, remove_wriggles=False): + deflectionModel=None, turbulenceModel=None, convergence_tolerance=1e-6, bounds='limit', remove_wriggles=False): """ Parameters ---------- @@ -69,7 +69,7 @@ class FugaBlockage(All2AllIterative, DeprecatedModel): turbulenceModel : TurbulenceModel Model describing the amount of added turbulence in the wake """ - fuga_deficit = FugaDeficit(LUT_path, remove_wriggles=remove_wriggles) + fuga_deficit = FugaDeficit(LUT_path, bounds=bounds, remove_wriggles=remove_wriggles) All2AllIterative.__init__(self, site, windTurbines, wake_deficitModel=fuga_deficit, rotorAvgModel=rotorAvgModel, superpositionModel=LinearSum(), deflectionModel=deflectionModel, blockage_deficitModel=fuga_deficit, @@ -135,9 +135,33 @@ class FugaMultiLUTDeficit(XRLUTDeficitModel): dims = ['d_h', 'zeta0', 'zi', 'z0'] return dims - def wake_radius(self, D_src_il, dw_ijlk, **_): - # Set at twice the source radius for now - return np.zeros_like(dw_ijlk) + D_src_il[:, na, :, na] + def wake_radius(self, dw_ijlk, D_src_il, h_ilk, TI_ilk, hcw_ijlk=None, z_ijlk=None, **kwargs): + z_ijlk = h_ilk[:, na] + lim = self.da.y.max().item() + + def get_mdu(hcw_ijlk): + hcw_ijlk = np.nan_to_num(np.clip(hcw_ijlk, -lim, lim), nan=lim) + return self._calc_mdu(D_src_il=D_src_il, dw_ijlk=dw_ijlk, + hcw_ijlk=hcw_ijlk, h_ilk=h_ilk, z_ijlk=z_ijlk, TI_ilk=TI_ilk, **kwargs) + mdu_target = get_mdu(dw_ijlk * 0) * np.exp(-2) # corresponding to 2 sigma + + def get_err(hcw): + return get_mdu(hcw) - mdu_target + + def get_wake_radius(wake_radius_ijlk): + # Newton Raphson + for _ in range(4): + err = get_err(wake_radius_ijlk) + derr = get_err(wake_radius_ijlk + 1) - err + wake_radius_ijlk = wake_radius_ijlk - err / derr + return np.abs(wake_radius_ijlk) + wake_radius_ijlk = get_wake_radius(D_src_il[:, na, :, na]) # diameter as initial guess + + if np.any(kwargs.get('yaw_ilk', [0])) and 'UT' in self.da.variable_names: + # mean of positive and negative side + wake_radius_ijlk = (wake_radius_ijlk + get_wake_radius(-D_src_il[:, na, :, na])) + + return wake_radius_ijlk def calc_deficit(self, WS_ilk, WS_eff_ilk, dw_ijlk, hcw_ijlk, z_ijlk, ct_ilk, D_src_il, **kwargs): # bypass XRLUTDeficitModel.calc_deficit @@ -145,19 +169,24 @@ class FugaMultiLUTDeficit(XRLUTDeficitModel): self._calc_layout_terms(dw_ijlk=dw_ijlk, hcw_ijlk=hcw_ijlk, z_ijlk=z_ijlk, D_src_il=D_src_il, **kwargs) return self.mdu_ijlk * (ct_ilk * WS_eff_ilk**2 / WS_ilk)[:, na] - def _calc_layout_terms(self, **kwargs): + def _calc_mdu(self, **kwargs): # 0 = UL variables0 = np.reshape(0, np.ones_like(kwargs['dw_ijlk'].shape).astype(int)) - self.mdu_ijlk = XRLUTDeficitModel.calc_deficit(self, **kwargs, variables=variables0) + mdu_ijlk = XRLUTDeficitModel.calc_deficit(self, **kwargs, variables=variables0) if 'yaw_ilk' in kwargs: theta_yaw_ijlk = gradients.deg2rad(kwargs['yaw_ilk'])[:, na] if list(self.da.variable_names) == ['UL', 'UT']: # 1 = UT mdUT_ijlk = XRLUTDeficitModel.calc_deficit(self, **kwargs, variables=variables0 + 1) mdUT_ijlk = np.negative(mdUT_ijlk, out=mdUT_ijlk, where=kwargs['hcw_ijlk'] >= 0) - self.mdu_ijlk = self.mdu_ijlk * np.cos(theta_yaw_ijlk) + np.sin(theta_yaw_ijlk) * mdUT_ijlk + mdu_ijlk = mdu_ijlk * np.cos(theta_yaw_ijlk) + np.sin(theta_yaw_ijlk) * mdUT_ijlk else: - self.mdu_ijlk *= np.cos(theta_yaw_ijlk) + mdu_ijlk *= np.cos(theta_yaw_ijlk) + return mdu_ijlk + + def _calc_layout_terms(self, **kwargs): + self.mdu_ijlk = self._calc_mdu(**kwargs) + return self.mdu_ijlk def get_input(self, D_src_il, TI_ilk, h_ilk, dw_ijlk, hcw_ijlk, z_ijlk, **kwargs): user = {'zeta0': lambda: kwargs['zeta0_ilk'][:, na], diff --git a/py_wake/tests/test_deficit_models/test_blockage_models.py b/py_wake/tests/test_deficit_models/test_blockage_models.py index 50f767250..3db379c4c 100644 --- a/py_wake/tests/test_deficit_models/test_blockage_models.py +++ b/py_wake/tests/test_deficit_models/test_blockage_models.py @@ -189,8 +189,7 @@ def test_All2AllIterative_all_blockage_DeficitModels_with_RotorAvg(deficitModel) sim_res = wf_model([0, 500, 1000, 1500], [0, 0, 0, 0], yaw=0, wd=270, ws=10) if 0: - sim_res.flow_map( - XYGrid(x=np.linspace(-200, 2000, 100))).plot_wake_map() + sim_res.flow_map(XYGrid(x=np.linspace(-200, 2000, 100))).plot_wake_map() plt.show() diff --git a/py_wake/tests/test_deficit_models/test_deficit_models.py b/py_wake/tests/test_deficit_models/test_deficit_models.py index d3839d3c9..cdb9a3b75 100644 --- a/py_wake/tests/test_deficit_models/test_deficit_models.py +++ b/py_wake/tests/test_deficit_models/test_deficit_models.py @@ -254,7 +254,8 @@ def test_deficitModel_wake_map(deficitModel, ref): (TurboNOJDeficit(), [99.024477, 61.553917, 123.107833, 92.439673, 97.034049]), (BastankhahGaussianDeficit(), [83.336286, 57.895893, 115.791786, 75.266662, 83.336286]), (IEA37SimpleBastankhahGaussianDeficit(), [103.166178, 67.810839, 135.621678, 103.166178, 103.166178]), - (FugaDeficit(LUT_path=tfp + 'fuga/2MW/Z0=0.00408599Zi=00400Zeta0=0.00E+00.nc'), [100, 50, 100, 100, 100]), + (FugaDeficit(LUT_path=tfp + 'fuga/2MW/Z0=0.00408599Zi=00400Zeta0=0.00E+00.nc'), + [88.044245, 88.044245, 110.523363, 88.044245, 88.044245]), (GCLDeficit(), [156.949964, 97.763333, 195.526667, 113.225695, 111.340236]), (GCLLocalDeficit(), [156.949964, 97.763333, 195.526667, 113.225695, 111.340236]), (ZongGaussianDeficit(eps_coeff=0.35), [91.15734, 66.228381, 132.456762, 94.90156, 79.198215]), @@ -272,6 +273,7 @@ def test_wake_radius(deficitModel, wake_radius_ref): D_src_il=np.reshape([100, 50, 100, 100, 100], (5, 1)), dw_ijlk=np.reshape([500, 500, 1000, 500, 500], (5, 1, 1, 1)), WS_ilk=np.reshape([10, 10, 10, 10, 10], (5, 1, 1)), + h_ilk=np.reshape([70, 70, 70, 70, 70], (5, 1, 1)), ct_ilk=np.reshape([.8, .8, .8, .4, .8], (5, 1, 1)), TI_ilk=np.reshape([.1, .1, .1, .1, .05], (5, 1, 1)), TI_eff_ilk=np.reshape([.1, .1, .1, .1, .05], (5, 1, 1)))[:, 0, 0, 0], diff --git a/py_wake/tests/test_deficit_models/test_fuga.py b/py_wake/tests/test_deficit_models/test_fuga.py index 6d9c074dc..b8bf4b8fa 100644 --- a/py_wake/tests/test_deficit_models/test_fuga.py +++ b/py_wake/tests/test_deficit_models/test_fuga.py @@ -18,7 +18,9 @@ from py_wake.wind_turbines._wind_turbines import WindTurbine, WindTurbines from py_wake.utils.profiling import timeit import warnings from py_wake.utils import fuga_utils -from py_wake.utils.fuga_utils import FugaXRLUT, FugaUtils +from py_wake.utils.fuga_utils import FugaUtils +from numpy import newaxis as na +from scipy.optimize._minpack_py import curve_fit def test_fuga(): @@ -469,6 +471,46 @@ def test_verify_FugaMultiLUT(): npt.assert_array_almost_equal(fm.WS_eff.sel(time=t).squeeze(), fm_ref.WS_eff.squeeze(), 5) +@pytest.mark.parametrize('yaw', [0, 30, 60]) +def test_fuga_wake_radius(yaw): + wts = HornsrevV80() + + path = tfp + 'fuga/2MW/Z0=0.00408599Zi=00400Zeta0=0.00E+00.nc' + site = UniformSite() + wfm = Fuga(path, site, wts) + sim_res = wfm([0], [0], wd=270, ws=10, yaw=yaw) + + y = np.linspace(-300, 300, 500) + du = 10 - sim_res.flow_map(XYGrid(x=500, y=y)).WS_eff.squeeze() + wake_radius = wfm.wake_deficitModel.wake_radius(dw_ijlk=np.array([500])[na, :, na, na], D_src_il=wts.diameter([0])[:, na], + h_ilk=wts.hub_height([0])[:, na, na], + TI_ilk=np.array([0.1])[:, na, na]).squeeze() + + def gauss(x, *p): + A, mu, sigma = p + return A * np.exp(-(x - mu)**2 / (2. * sigma**2)) + + # p0 is the initial guess for the fitting coefficients (A, mu and sigma above) + coeff, var_matrix = curve_fit(gauss, y, du, p0=[1, 0, 1]) + A, mu, sigma = coeff + if 0: + x = np.linspace(-200, 1200) + sim_res.flow_map(XYGrid(x=x)).plot_wake_map() + wake_radius_map = wfm.wake_deficitModel.wake_radius(dw_ijlk=x[na, :, na, na], D_src_il=wts.diameter([0])[:, na], + h_ilk=wts.hub_height([0])[:, na, na], + TI_ilk=np.array([0.1])[:, na, na]).squeeze() + plt.plot(x, wake_radius_map) + plt.figure() + plt.plot(y, du, label='Fuga') + plt.axvline(wake_radius, label='wake radius') + plt.plot(y, gauss(y, *coeff), label=f'Gauss({coeff})') + plt.axvline(sigma * 2, ls='--', color='r', label='2*sigma') + plt.legend() + plt.show() + npt.assert_allclose(sigma * 2, wake_radius, rtol=0.02) + + plt.show() + # def cmp_fuga_with_colonel(): # from py_wake.aep_calculator import AEPCalculator # -- GitLab