From fd9e885b2595b7434c12d054355b4c278ef9108c Mon Sep 17 00:00:00 2001
From: mmpe <mmpe@dtu.dk>
Date: Wed, 18 Aug 2021 09:09:46 +0200
Subject: [PATCH] replace nummerical ct calculation with cubic solution from
 plaa

---
 py_wake/examples/data/dtu10mw/_dtu10mw.py          |  3 ++-
 .../test_generic_wind_turbines.py                  | 14 +++++++++-----
 py_wake/utils/generic_power_ct_curves.py           | 11 +++++++++--
 py_wake/wind_turbines/generic_wind_turbines.py     |  4 +++-
 4 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/py_wake/examples/data/dtu10mw/_dtu10mw.py b/py_wake/examples/data/dtu10mw/_dtu10mw.py
index 9e5d4e804..1888e62cc 100644
--- a/py_wake/examples/data/dtu10mw/_dtu10mw.py
+++ b/py_wake/examples/data/dtu10mw/_dtu10mw.py
@@ -64,7 +64,8 @@ class DTU10MW(WindTurbine):
             'DTU10MW',
             diameter=178.3,
             hub_height=119,
-            powerCtFunction=PowerCtTabular(u, p * 1000, 'w', ct_curve[:, 1], method=method))
+            powerCtFunction=PowerCtTabular(u, p * 1000, 'w', ct_curve[:, 1], ws_cutin=4, ws_cutout=25,
+                                           ct_idle=0.059, method=method))
 
 
 DTU10WM_RWT = DTU10MW
diff --git a/py_wake/tests/test_windturbines/test_generic_wind_turbines.py b/py_wake/tests/test_windturbines/test_generic_wind_turbines.py
index 0dec1eb35..7c08f0ffd 100644
--- a/py_wake/tests/test_windturbines/test_generic_wind_turbines.py
+++ b/py_wake/tests/test_windturbines/test_generic_wind_turbines.py
@@ -12,12 +12,14 @@ from py_wake.site.xrsite import XRSite
 
 
 def test_GenericWindTurbine():
-    for ref, ti, p_tol, ct_tol in [(V80(), .1, 0.03, .16),
-                                   (WindTurbine.from_WAsP_wtg(wtg_path + "Vestas V112-3.0 MW.wtg"), .05, 0.035, .07),
-                                   (DTU10MW(), .05, 0.06, .13)]:
+    for ref, ti, cut_in, cut_out, p_tol, ct_tol in [(V80(), .1, 4, None, 0.03, .14),
+                                                    (WindTurbine.from_WAsP_wtg(wtg_path +
+                                                                               "Vestas V112-3.0 MW.wtg"), .05, 3, 25, 0.035, .07),
+                                                    (DTU10MW(), .05, 4, 25, 0.06, .12)]:
+
         power_norm = ref.power(np.arange(10, 20)).max()
         wt = GenericWindTurbine('Generic', ref.diameter(), ref.hub_height(), power_norm / 1e3,
-                                turbulence_intensity=ti, ws_cutin=None)
+                                turbulence_intensity=ti, ws_cutin=cut_in, ws_cutout=cut_out)
 
         if 0:
             u = np.arange(0, 30, .1)
@@ -27,10 +29,12 @@ def test_GenericWindTurbine():
             plt.plot(u, ref.power(u) / 1e6, label=ref.name())
 
             plt.ylabel('Power [MW]')
-            plt.legend()
+            plt.legend(loc='center left')
             ax = plt.twinx()
             ax.plot(u, ct, '--')
             ax.plot(u, ref.ct(u), '--')
+
+            ax.set_ylim([0, 1])
             plt.ylabel('Ct')
             plt.show()
 
diff --git a/py_wake/utils/generic_power_ct_curves.py b/py_wake/utils/generic_power_ct_curves.py
index 92fd866ee..d78bcb57d 100644
--- a/py_wake/utils/generic_power_ct_curves.py
+++ b/py_wake/utils/generic_power_ct_curves.py
@@ -63,17 +63,24 @@ def standard_power_ct_curve(power_norm, diameter, turbulence_intensity=.1,
     p_gear = p_gen / (1 - generator_loss)
     p_aero = (p_gear + gear_loss_const * power_norm) / (1 - gear_loss_var)
     cp = p_aero * 1000 / (.5 * rho * area * wsp_lst ** 3)
+    # cp is too high at low ws due to constant loss, so limit to 16/27
     cp = np.minimum(cp, 16 / 27)
 
     def cp2ct(cp):
-        a = np.array([newton(lambda a, cp=cp:4 * a * (1 - a)**2 - cp, .1) for cp in cp])
+        # solve cp = 4 * a * (1 - a)**2 for a
+        y = 27.0 / 16.0 * cp
+        a = 2.0 / 3.0 * (1 - np.cos(np.arctan2(2 * np.sqrt(y * (1.0 - y)), 1 - 2 * y) / 3.0))
         return 4 * a * (1 - a)
 
     ct_lst = cp2ct(cp)
 
+    # scale ct, such that the constant region (~cut-in to rated) equals <constant_ct>
+    # First part (~0-2m/s) is constant at 8/9 due to cp limit and must be disregarded
     ct_below_lim = np.where(ct_lst < 8 / 9 - 1e-6)[0][0]
+    # find index of most constant ct after the disregarded 8/9 region
     constant_ct_idx = ct_below_lim + np.argmin(np.abs(np.diff(ct_lst[ct_below_lim:])))
-    if ct_lst[constant_ct_idx] < cp2ct([max_cp]):
+    if ct_lst[constant_ct_idx] < cp2ct(max_cp):
+        # if TI is high, then there is no constant region
         constant_ct_idx = 0
     f = constant_ct / ct_lst[constant_ct_idx]
     ct_lst = np.minimum(8 / 9, ct_lst * f)
diff --git a/py_wake/wind_turbines/generic_wind_turbines.py b/py_wake/wind_turbines/generic_wind_turbines.py
index bc310fa27..c85c80883 100644
--- a/py_wake/wind_turbines/generic_wind_turbines.py
+++ b/py_wake/wind_turbines/generic_wind_turbines.py
@@ -9,7 +9,7 @@ class GenericWindTurbine(WindTurbine):
                  air_density=1.225, max_cp=.49, constant_ct=.8,
                  gear_loss_const=.01, gear_loss_var=.014, generator_loss=0.03, converter_loss=.03,
                  ws_lst=np.arange(.1, 30, .1), ws_cutin=None,
-                 ws_cutout=None, power_idle=0, ct_idle=0, method='linear',
+                 ws_cutout=None, power_idle=0, ct_idle=None, method='linear',
                  additional_models=[SimpleYawModel()]):
         """Wind turbine with generic standard power curve based on max_cp, rated power and losses.
         Ct is computed from the basic 1d momentum theory
@@ -64,6 +64,8 @@ class GenericWindTurbine(WindTurbine):
             u, p, ct = [v[u >= ws_cutin] for v in [u, p, ct]]
         if ws_cutout is not None:
             u, p, ct = [v[u <= ws_cutout] for v in [u, p, ct]]
+        if ct_idle is None:
+            ct_idle = ct[-1]
         powerCtFunction = PowerCtTabular(u, p * 1000, 'w', ct, ws_cutin=ws_cutin, ws_cutout=ws_cutout,
                                          power_idle=power_idle, ct_idle=ct_idle, method=method,
                                          additional_models=additional_models)
-- 
GitLab