From 758a1c85840087af24567e608a60bc0a8891d00d Mon Sep 17 00:00:00 2001
From: mmpe <mmpe@dtu.dk>
Date: Tue, 8 Dec 2020 11:43:48 +0100
Subject: [PATCH] Allow different rotor average model for turbulence

---
 py_wake/deficit_models/no_wake.py             |  4 ++
 .../test_deficit_models.py                    |  5 +-
 py_wake/tests/test_rotor_avg_models.py        |  6 +-
 .../test_turbulence_models.py                 | 67 ++++++++++++++++++-
 py_wake/turbulence_models/crespo.py           |  4 +-
 py_wake/turbulence_models/gcl.py              |  4 +-
 py_wake/turbulence_models/stf.py              |  5 +-
 py_wake/turbulence_models/turbulence_model.py |  3 +-
 .../wind_farm_models/engineering_models.py    | 11 +--
 9 files changed, 92 insertions(+), 17 deletions(-)

diff --git a/py_wake/deficit_models/no_wake.py b/py_wake/deficit_models/no_wake.py
index 2d09f86a8..77025dc4b 100644
--- a/py_wake/deficit_models/no_wake.py
+++ b/py_wake/deficit_models/no_wake.py
@@ -1,7 +1,11 @@
 from py_wake.deficit_models import DeficitModel
 from numpy import newaxis as na
+import numpy as np
 
 
 class NoWakeDeficit(DeficitModel):
     def calc_deficit(self, WS_ilk, dw_ijlk, **_):
         return (WS_ilk)[:, na] * (dw_ijlk > 0) * 0
+
+    def wake_radius(self, dw_ijlk, **_):
+        return np.zeros_like(dw_ijlk)
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 ba6a02a33..d7a1ab6a1 100644
--- a/py_wake/tests/test_deficit_models/test_deficit_models.py
+++ b/py_wake/tests/test_deficit_models/test_deficit_models.py
@@ -28,6 +28,7 @@ from py_wake.turbulence_models.gcl import GCLTurbulence
 from py_wake.wind_farm_models.engineering_models import PropagateDownwind, All2AllIterative
 from py_wake.turbulence_models.stf import STF2017TurbulenceModel
 from py_wake.examples.data.hornsrev1 import Hornsrev1Site
+from py_wake.deficit_models.selfsimilarity import SelfSimilarityDeficit
 
 
 class GCLLocalDeficit(GCLDeficit):
@@ -229,9 +230,9 @@ def test_wake_radius_not_implemented():
     site = IEA37Site(16)
     x, y = site.initial_position.T
     windTurbines = IEA37_WindTurbines()
-    wfm = PropagateDownwind(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
+    wfm = PropagateDownwind(site, windTurbines, wake_deficitModel=SelfSimilarityDeficit(),
                             turbulenceModel=GCLTurbulence())
-    with pytest.raises(NotImplementedError, match="wake_radius not implemented for NoWakeDeficit"):
+    with pytest.raises(NotImplementedError, match="wake_radius not implemented for SelfSimilarityDeficit"):
         wfm(x, y)
 
 
diff --git a/py_wake/tests/test_rotor_avg_models.py b/py_wake/tests/test_rotor_avg_models.py
index 272820464..4ac04a986 100644
--- a/py_wake/tests/test_rotor_avg_models.py
+++ b/py_wake/tests/test_rotor_avg_models.py
@@ -29,7 +29,11 @@ def test_RotorGridAvg_deficit():
             ('RotorGrid3', EqGridRotorAvg(3), 7.633415167369133),
             ('RotorGrid4', EqGridRotorAvg(4), 7.710215921858325),
             ('RotorGrid100', EqGridRotorAvg(100), 7.820762402628349),
-            ('RotorGQGrid_4,3', GQGridRotorAvg(4, 3), 7.826105012683896)]:
+            ('RotorGQGrid_4,3', GQGridRotorAvg(4, 3), 7.826105012683896),
+            ('RotorCGI4', CGIRotorAvg(4), 7.848406907726826),
+            ('RotorCGI4', CGIRotorAvg(7), 7.819900693605533),
+            ('RotorCGI4', CGIRotorAvg(9), 7.82149363932618),
+            ('RotorCGI4', CGIRotorAvg(21), 7.821558905416136)]:
 
         # test with PropagateDownwind
         wfm = IEA37SimpleBastankhahGaussian(site,
diff --git a/py_wake/tests/test_turbulence_models/test_turbulence_models.py b/py_wake/tests/test_turbulence_models/test_turbulence_models.py
index 7a03dc21d..4fc6908ce 100644
--- a/py_wake/tests/test_turbulence_models/test_turbulence_models.py
+++ b/py_wake/tests/test_turbulence_models/test_turbulence_models.py
@@ -18,12 +18,15 @@ from py_wake.wind_farm_models.engineering_models import PropagateDownwind, All2A
 from py_wake.turbulence_models.gcl import GCLTurbulence
 import matplotlib.pyplot as plt
 from py_wake.turbulence_models.crespo import CrespoHernandez
-from py_wake.deficit_models.gaussian import BastankhahGaussian, IEA37SimpleBastankhahGaussianDeficit
+from py_wake.deficit_models.gaussian import BastankhahGaussian, IEA37SimpleBastankhahGaussianDeficit,\
+    IEA37SimpleBastankhahGaussian
 import xarray as xr
 import os
 import pkgutil
 import inspect
 from py_wake.examples.data.hornsrev1 import Hornsrev1Site
+from py_wake.rotor_avg_models.rotor_avg_model import RotorCenter, EqGridRotorAvg, GQGridRotorAvg, CGIRotorAvg
+from py_wake.deficit_models.gcl import GCLDeficit
 
 
 def get_all_turbulence_models():
@@ -35,7 +38,7 @@ def get_all_turbulence_models():
             v = _module.__dict__[n]
             if inspect.isclass(v) and issubclass(v, TurbulenceModel) and \
                     v not in [TurbulenceModel]:
-                all_turbulence_modules.append(v())
+                all_turbulence_modules.append(v)
     return all_turbulence_modules
 
 
@@ -146,6 +149,64 @@ def test_own_turbulence_is_zero(turbulenceModel):
     site = Hornsrev1Site()
     windTurbines = IEA37_WindTurbines()
     wf_model = All2AllIterative(site, windTurbines, wake_deficitModel=IEA37SimpleBastankhahGaussianDeficit(),
-                                turbulenceModel=turbulenceModel)
+                                turbulenceModel=turbulenceModel())
     sim_res = wf_model([0], [0])
     npt.assert_array_equal(sim_res.TI_eff, sim_res.TI.broadcast_like(sim_res.TI_eff))
+
+
+def test_RotorAvg_deficit():
+    site = IEA37Site(16)
+    windTurbines = IEA37_WindTurbines()
+    wfm = IEA37SimpleBastankhahGaussian(site,
+                                        windTurbines,
+                                        turbulenceModel=STF2017TurbulenceModel())
+    flow_map = wfm([0, 500], [0, 0], wd=270, ws=10).flow_map(HorizontalGrid(x=[500], y=np.arange(-100, 100)))
+    plt.plot(flow_map.Y[:, 0], flow_map.TI_eff_xylk[:, 0, 0, 0])
+    R = windTurbines.diameter() / 2
+
+    for name, rotorAvgModel, ref1 in [
+            ('None', None, 0.22292190804089568),
+            ('RotorCenter', RotorCenter(), 0.22292190804089568),
+            ('RotorGrid100', EqGridRotorAvg(100), 0.1989725533174574),
+            ('RotorGQGrid_4,3', GQGridRotorAvg(4, 3), 0.19874837617113356),
+            ('RotorCGI4', CGIRotorAvg(4), 0.19822024411411204),
+            ('RotorCGI4', CGIRotorAvg(21), 0.1989414764606653)]:
+
+        # test with PropagateDownwind
+        wfm = IEA37SimpleBastankhahGaussian(site,
+                                            windTurbines,
+                                            turbulenceModel=STF2017TurbulenceModel(rotorAvgModel=rotorAvgModel))
+        sim_res = wfm([0, 500], [0, 0], wd=270, ws=10)
+        npt.assert_almost_equal(sim_res.TI_eff_ilk[1, 0, 0], ref1, err_msg=name)
+
+        # test with All2AllIterative
+        wfm = All2AllIterative(site, windTurbines,
+                               IEA37SimpleBastankhahGaussianDeficit(),
+                               turbulenceModel=STF2017TurbulenceModel(rotorAvgModel=rotorAvgModel),
+                               superpositionModel=SquaredSum())
+        sim_res = wfm([0, 500], [0, 0], wd=270, ws=10)
+        npt.assert_almost_equal(sim_res.TI_eff_ilk[1, 0, 0], ref1)
+
+        plt.plot([-R, R], [sim_res.WS_eff_ilk[1, 0, 0]] * 2, label=name)
+    if 0:
+        plt.legend()
+        plt.show()
+    plt.close()
+
+
+@pytest.mark.parametrize('WFM', [All2AllIterative, PropagateDownwind])
+@pytest.mark.parametrize('turbulenceModel', get_all_turbulence_models())
+def test_with_all_turbulence_models(WFM, turbulenceModel):
+    site = IEA37Site(16)
+    windTurbines = IEA37_WindTurbines()
+
+    wfm = WFM(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
+              rotorAvgModel=CGIRotorAvg(4),
+              superpositionModel=LinearSum(),
+              turbulenceModel=turbulenceModel())
+
+    wfm2 = WFM(site, windTurbines, wake_deficitModel=NoWakeDeficit(),
+               superpositionModel=LinearSum(),
+               turbulenceModel=turbulenceModel(rotorAvgModel=CGIRotorAvg(4)))
+    kwargs = {'x': [0, 0, 500, 500], 'y': [0, 500, 0, 500], 'wd': [0], 'ws': [8]}
+    npt.assert_array_equal(wfm(**kwargs).TI_eff, wfm2(**kwargs).TI_eff, turbulenceModel.__name__)
diff --git a/py_wake/turbulence_models/crespo.py b/py_wake/turbulence_models/crespo.py
index 5ffc01519..02e50b3b5 100644
--- a/py_wake/turbulence_models/crespo.py
+++ b/py_wake/turbulence_models/crespo.py
@@ -14,8 +14,8 @@ class CrespoHernandez(TurbulenceModel, AreaOverlappingFactor):
     """
     args4addturb = ['dw_ijlk', 'cw_ijlk', 'D_src_il', 'ct_ilk', 'TI_ilk', 'D_dst_ijl', 'wake_radius_ijlk']
 
-    def __init__(self, addedTurbulenceSuperpositionModel=SqrMaxSum()):
-        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel)
+    def __init__(self, addedTurbulenceSuperpositionModel=SqrMaxSum(), **kwargs):
+        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel, **kwargs)
 
     def calc_added_turbulence(self, dw_ijlk, cw_ijlk, D_src_il, ct_ilk, TI_ilk, D_dst_ijl, wake_radius_ijlk, **_):
         """ Calculate the added turbulence intensity at locations specified by
diff --git a/py_wake/turbulence_models/gcl.py b/py_wake/turbulence_models/gcl.py
index 56c8724fe..b7fc558bb 100644
--- a/py_wake/turbulence_models/gcl.py
+++ b/py_wake/turbulence_models/gcl.py
@@ -15,8 +15,8 @@ class GCLTurbulence(TurbulenceModel, AreaOverlappingFactor):
     """
     args4addturb = ['D_src_il', 'dw_ijlk', 'ct_ilk', 'D_dst_ijl', 'cw_ijlk', 'wake_radius_ijlk']
 
-    def __init__(self, addedTurbulenceSuperpositionModel=SqrMaxSum()):
-        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel)
+    def __init__(self, addedTurbulenceSuperpositionModel=SqrMaxSum(), **kwargs):
+        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel, **kwargs)
 
     def calc_added_turbulence(self, dw_ijlk, D_src_il, ct_ilk, wake_radius_ijlk,
                               D_dst_ijl, cw_ijlk, **_):
diff --git a/py_wake/turbulence_models/stf.py b/py_wake/turbulence_models/stf.py
index d01f9fc49..23f5329d4 100644
--- a/py_wake/turbulence_models/stf.py
+++ b/py_wake/turbulence_models/stf.py
@@ -8,8 +8,9 @@ class STF2017TurbulenceModel(TurbulenceModel):
 
     args4addturb = ['dw_ijlk', 'cw_ijlk', 'D_src_il', 'ct_ilk', 'TI_ilk']
 
-    def __init__(self, addedTurbulenceSuperpositionModel=LinearSum()):
-        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel)
+    def __init__(self, addedTurbulenceSuperpositionModel=LinearSum(),
+                 **kwargs):
+        TurbulenceModel.__init__(self, addedTurbulenceSuperpositionModel, **kwargs)
 
     def weight(self, dw_ijlk, cw_ijlk, D_src_il):
         # The weight is given by the exponential term in Eq 3.16 and accounts
diff --git a/py_wake/turbulence_models/turbulence_model.py b/py_wake/turbulence_models/turbulence_model.py
index d6a035b21..7c39f8f4f 100644
--- a/py_wake/turbulence_models/turbulence_model.py
+++ b/py_wake/turbulence_models/turbulence_model.py
@@ -4,9 +4,10 @@ from abc import abstractmethod
 
 class TurbulenceModel():
 
-    def __init__(self, addedTurbulenceSuperpositionModel):
+    def __init__(self, addedTurbulenceSuperpositionModel, rotorAvgModel=None):
         assert isinstance(addedTurbulenceSuperpositionModel, AddedTurbulenceSuperpositionModel)
         self.addedTurbulenceSuperpositionModel = addedTurbulenceSuperpositionModel
+        self.rotorAvgModel = rotorAvgModel
 
     @abstractmethod
     def calc_added_turbulence(self):
diff --git a/py_wake/wind_farm_models/engineering_models.py b/py_wake/wind_farm_models/engineering_models.py
index 4f5baffed..082c362d1 100644
--- a/py_wake/wind_farm_models/engineering_models.py
+++ b/py_wake/wind_farm_models/engineering_models.py
@@ -86,7 +86,10 @@ class EngineeringWindFarmModel(WindFarmModel):
             self.args4deficit = set(self.args4deficit) | set(self.groundModel.args4deficit)
         self.args4all = set(self.args4deficit)
         if self.turbulenceModel:
-            self.args4addturb = set(self.turbulenceModel.args4addturb) | set(self.rotorAvgModel.args4rotor_avg_deficit)
+            if self.turbulenceModel.rotorAvgModel is None:
+                self.turbulenceModel.rotorAvgModel = rotorAvgModel
+            self.args4addturb = set(self.turbulenceModel.args4addturb) | set(
+                self.turbulenceModel.rotorAvgModel.args4rotor_avg_deficit)
             self.args4all = self.args4all | set(self.turbulenceModel.args4addturb)
         if self.deflectionModel:
             self.args4all = self.args4all | set(self.deflectionModel.args4deflection)
@@ -494,8 +497,8 @@ class PropagateDownwind(EngineeringWindFarmModel):
                                  if k != "dw_ijlk"}
 
                     # Calculate added turbulence
-                    add_turb_nk[n_dw] = self.rotorAvgModel(self.turbulenceModel.calc_added_turbulence,
-                                                           dw_ijlk=dw_ijlk, **turb_args)
+                    add_turb_nk[n_dw] = self.turbulenceModel.rotorAvgModel(self.turbulenceModel.calc_added_turbulence,
+                                                                           dw_ijlk=dw_ijlk, **turb_args)
 
         WS_eff_jlk, power_jlk, ct_jlk = np.array(WS_eff_mk), np.array(power_jlk), np.array(ct_jlk)
 
@@ -596,7 +599,7 @@ class All2AllIterative(EngineeringWindFarmModel):
             # Calculate effective wind speed
             WS_eff_ilk = self.superpositionModel.calc_effective_WS(lw.WS_ilk, deficit_iilk)
             if self.turbulenceModel:
-                add_turb_ijlk = self.rotorAvgModel(self.turbulenceModel.calc_added_turbulence, **args)
+                add_turb_ijlk = self.turbulenceModel.rotorAvgModel(self.turbulenceModel.calc_added_turbulence, **args)
                 TI_eff_ilk = self.turbulenceModel.calc_effective_TI(lw.TI_ilk, add_turb_ijlk)
 
             # Check if converged
-- 
GitLab