Beispiel #1
0
    def __init__(self,
                 names,
                 diameters,
                 hub_heights,
                 powerCtFunctions,
                 loadFunctions=None):
        """Initialize WindTurbines

        Parameters
        ----------
        names : array_like
            Wind turbine names
        diameters : array_like
            Diameter of wind turbines
        hub_heights : array_like
            Hub height of wind turbines
        powerCtFunctions : list of powerCtFunction objects
            Wind turbine ct functions; func(ws) -> ct
        """
        self._names = np.array(names)
        self._diameters = np.array(diameters)
        self._hub_heights = np.array(hub_heights)
        assert len(names) == len(diameters) == len(hub_heights) == len(
            powerCtFunctions)
        self.powerCtFunction = PowerCtFunctionList('type', powerCtFunctions)
Beispiel #2
0
def test_MultiPowerCtCurve():
    u_p, p = np.asarray(hornsrev1.power_curve).T.copy()
    ct = hornsrev1.ct_curve[:, 1]

    curve = PowerCtFunctionList('mode', [
        PowerCtTabular(ws=u_p, power=p, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p * 1.1, power_unit='w', ct=ct + .1)
    ])

    npt.assert_array_equal(sorted(curve.optional_inputs),
                           ['Air_density', 'tilt', 'yaw'])
    npt.assert_array_equal(list(curve.required_inputs), ['mode'])

    u = np.arange(0, 30, .1)
    p0, ct0 = curve(u, mode=0)
    p1, ct1 = curve(u, mode=1)

    npt.assert_array_almost_equal(p0, p1 / 1.1)
    npt.assert_array_almost_equal(ct0, ct1 - .1)

    # subset
    u = np.zeros((16, 360, 23)) + np.arange(3, 26)[na, na, :]
    mode_16 = (np.arange(16) > 7)
    mode_16_360 = np.broadcast_to(mode_16[:, na], (16, 360))
    mode_16_360_23 = np.broadcast_to(mode_16[:, na, na], (16, 360, 23))

    ref_p = np.array(
        np.broadcast_to(hornsrev1.power_curve[:, 1][na, na], (16, 360, 23)))
    ref_p[8:] *= 1.1

    for m in [mode_16, mode_16_360, mode_16_360_23]:
        p, ct = curve(u, mode=m)
        npt.assert_array_almost_equal(p, ref_p)
Beispiel #3
0
def test_time_series_operating_wrong_shape():
    from py_wake.wind_turbines.power_ct_functions import PowerCtFunctionList, PowerCtTabular
    d = np.load(os.path.dirname(examples.__file__) + "/data/time_series.npz")
    wd, ws, ws_std = [d[k][:6 * 24] for k in ['wd', 'ws', 'ws_std']]
    ws += 3
    t = np.arange(6 * 24)
    wt = V80()
    site = Hornsrev1Site()

    # replace powerCtFunction
    wt.powerCtFunction = PowerCtFunctionList(
        key='operating',
        powerCtFunction_lst=[
            PowerCtTabular(ws=[0, 100],
                           power=[0, 0],
                           power_unit='w',
                           ct=[0, 0]),  # 0=No power and ct
            wt.powerCtFunction
        ],  # 1=Normal operation
        default_value=1)
    wfm = NOJ(site, wt)
    x, y = site.initial_position.T
    operating = (t < 48) | (t > 72)
    with pytest.raises(
            ValueError,
            match=
            r"Argument, operating\(shape=\(1, 144\)\), has unsupported shape."
    ):
        wfm(x, y, ws=ws, wd=wd, time=t, operating=[operating])
Beispiel #4
0
def test_time_series_operating():
    from py_wake.wind_turbines.power_ct_functions import PowerCtFunctionList, PowerCtTabular
    d = np.load(os.path.dirname(examples.__file__) + "/data/time_series.npz")
    wd, ws, ws_std = [d[k][:6 * 24] for k in ['wd', 'ws', 'ws_std']]
    ws += 3
    t = np.arange(6 * 24)
    wt = V80()
    site = Hornsrev1Site()

    # replace powerCtFunction
    wt.powerCtFunction = PowerCtFunctionList(
        key='operating',
        powerCtFunction_lst=[
            PowerCtTabular(ws=[0, 100],
                           power=[0, 0],
                           power_unit='w',
                           ct=[0, 0]),  # 0=No power and ct
            wt.powerCtFunction
        ],  # 1=Normal operation
        default_value=1)
    wfm = NOJ(site, wt)
    x, y = site.initial_position.T
    operating = (t < 48) | (t > 72)
    sim_res = wfm(x, y, ws=ws, wd=wd, time=t, operating=operating)
    npt.assert_array_equal(sim_res.operating[0], operating)
    npt.assert_array_equal(sim_res.Power[:, operating == 0], 0)
    npt.assert_array_equal(sim_res.Power[:, operating != 0] > 0, True)

    operating = np.ones((80, 6 * 24))
    operating[1] = (t < 48) | (t > 72)
    sim_res = wfm(x, y, ws=ws, wd=wd, time=t, operating=operating)
    npt.assert_array_equal(sim_res.operating, operating)
    npt.assert_array_equal(sim_res.Power.values[operating == 0], 0)
    npt.assert_array_equal(sim_res.Power.values[operating != 0] > 0, True)
def test_MultiMultiPowerCtCurve_subset():
    u_p, p, ct = v80_upct.copy()

    curves = PowerCtFunctionList('mytype', [
        PowerCtFunctionList('mode', [
            PowerCtTabular(ws=u_p, power=p + 1, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 2, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 3, power_unit='w', ct=ct)
        ]),
        PowerCtFunctionList('mode', [
            PowerCtTabular(ws=u_p, power=p + 4, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 5, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 6, power_unit='w', ct=ct)
        ]),
    ])
    wfm = get_wfm(curves)
    ri, oi = wfm.windTurbines.function_inputs
    npt.assert_array_equal(ri, ['mode', 'mytype'])
    npt.assert_array_equal(oi, ['Air_density', 'tilt', 'yaw'])

    u = np.zeros((2, 3, 4)) + np.arange(3, 7)[na, na, :]
    type_2 = np.array([0, 1])
    type_2_3 = np.broadcast_to(type_2[:, na], (2, 3))
    type_2_3_4 = np.broadcast_to(type_2[:, na, na], (2, 3, 4))

    mode_2_3 = np.broadcast_to(np.array([0, 1, 2])[na, :], (2, 3))
    mode_2_3_4 = np.broadcast_to(mode_2_3[:, :, na], (2, 3, 4))

    ref_p = np.array(
        np.broadcast_to(hornsrev1.power_curve[:4, 1][na, na], (2, 3, 4)))
    ref_p[0, :] += np.array([1, 2, 3])[:, na]
    ref_p[1, :] += np.array([4, 5, 6])[:, na]

    for t in [type_2, type_2_3, type_2_3_4]:
        for m in [mode_2_3, mode_2_3_4]:
            sim_res = wfm([0, 1000], [0, 0],
                          wd=np.arange(3),
                          ws=np.arange(3, 7),
                          mode=m,
                          mytype=t)  # no wake effects
            p = sim_res.Power.values
            npt.assert_array_almost_equal(p, ref_p)
Beispiel #6
0
def test_MultiMultiPowerCtCurve_subset():
    u_p, p = np.asarray(hornsrev1.power_curve).T.copy()
    ct = hornsrev1.ct_curve[:, 1]

    curve = PowerCtFunctionList('type', [
        PowerCtFunctionList('mode', [
            PowerCtTabular(ws=u_p, power=p + 1, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 2, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 3, power_unit='w', ct=ct)
        ]),
        PowerCtFunctionList('mode', [
            PowerCtTabular(ws=u_p, power=p + 4, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 5, power_unit='w', ct=ct),
            PowerCtTabular(ws=u_p, power=p + 6, power_unit='w', ct=ct)
        ]),
    ])

    npt.assert_array_equal(
        sorted(curve.optional_inputs)[::-1], ['yaw', 'tilt', 'Air_density'])
    npt.assert_array_equal(
        sorted(curve.required_inputs)[::-1], ['type', 'mode'])

    u = np.zeros((2, 3, 4)) + np.arange(3, 7)[na, na, :]
    type_2 = np.array([0, 1])
    type_2_3 = np.broadcast_to(type_2[:, na], (2, 3))
    type_2_3_4 = np.broadcast_to(type_2[:, na, na], (2, 3, 4))

    mode_2_3 = np.broadcast_to(np.array([0, 1, 2])[na, :], (2, 3))
    mode_2_3_4 = np.broadcast_to(mode_2_3[:, :, na], (2, 3, 4))

    ref_p = np.array(
        np.broadcast_to(hornsrev1.power_curve[:4, 1][na, na], (2, 3, 4)))
    ref_p[0, :] += np.array([1, 2, 3])[:, na]
    ref_p[1, :] += np.array([4, 5, 6])[:, na]

    for t in [type_2, type_2_3, type_2_3_4]:
        for m in [mode_2_3, mode_2_3_4]:
            p, ct = curve(u, mode=m, type=t)
            npt.assert_array_almost_equal(p, ref_p)
Beispiel #7
0
def test_missing_input_PowerCtFunctionList():
    u_p, p = np.asarray(hornsrev1.power_curve).T.copy()
    ct = hornsrev1.ct_curve[:, 1]

    curve = PowerCtFunctionList('mode', [
        PowerCtTabular(ws=u_p, power=p + 1, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p + 2, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p + 3, power_unit='w', ct=ct)
    ])
    u = np.zeros((16, 360, 23)) + np.arange(3, 26)[na, na, :]
    with pytest.raises(
            KeyError,
            match="Argument, mode, required to calculate power and ct not found"
    ):
        curve(u)
def test_missing_input_PowerCtFunctionList():
    u_p, p, ct = v80_upct.copy()
    curve = PowerCtFunctionList('mode', [
        PowerCtTabular(ws=u_p, power=p + 1, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p + 2, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p + 3, power_unit='w', ct=ct)
    ])

    wfm = get_wfm(curve)
    ri, oi = wfm.windTurbines.function_inputs
    npt.assert_array_equal(ri, ['mode'])
    npt.assert_array_equal(oi, ['Air_density', 'tilt', 'yaw'])

    with pytest.raises(
            KeyError,
            match="Argument, mode, required to calculate power and ct not found"
    ):
        wfm([0], [0])
def test_MultiPowerCtCurve():
    u_p, p, ct = v80_upct.copy()

    curve = PowerCtFunctionList('mode', [
        PowerCtTabular(ws=u_p, power=p, power_unit='w', ct=ct),
        PowerCtTabular(ws=u_p, power=p * 1.1, power_unit='w', ct=ct + .1)
    ])

    u = np.arange(0, 30, .1)

    wfm = get_wfm(curve)
    ri, oi = wfm.windTurbines.function_inputs
    npt.assert_array_equal(ri, ['mode'])
    npt.assert_array_equal(oi, ['Air_density', 'tilt', 'yaw'])

    sim_res = wfm([0], [0], ws=u, wd=0, mode=0)
    p0, ct0 = sim_res.Power.squeeze().values, sim_res.CT.squeeze().values,
    sim_res = wfm([0], [0], ws=u, wd=0, mode=1)
    p1, ct1 = sim_res.Power.squeeze().values, sim_res.CT.squeeze().values,

    npt.assert_array_almost_equal(p0, p1 / 1.1)
    npt.assert_array_almost_equal(ct0, ct1 - .1)

    mode_16 = (np.arange(16) > 7)
    mode_16_360 = np.broadcast_to(mode_16[:, na], (16, 360))
    mode_16_360_23 = np.broadcast_to(mode_16[:, na, na], (16, 360, 23))

    ref_p = np.array(
        np.broadcast_to(hornsrev1.power_curve[:, 1][na, na], (16, 360, 23)))
    ref_p[8:] *= 1.1

    for m in [mode_16, mode_16_360, mode_16_360_23]:
        sim_res = wfm(np.arange(16) * 1e3, [0] * 16,
                      wd=np.arange(360) % 5,
                      mode=m)  # no wake effects
        p = sim_res.Power.values
        npt.assert_array_almost_equal(p, ref_p)
Beispiel #10
0
    def from_WAsP_wtg(wtg_file, default_mode=0, power_unit='W'):
        """ Parse the one/multiple .wtg file(s) (xml) to initilize an
        WindTurbines object.

        Parameters
        ----------
        wtg_file : string or a list of string
            A string denoting the .wtg file, which is exported from WAsP.

        Returns
        -------
        an object of WindTurbines.

        Note: it is assumed that the power_unit inside multiple .wtg files is the same, i.e., power_unit.
        """
        if isinstance(wtg_file, (list, tuple)):
            return WindTurbine.from_WindTurbine_lst(
                [WindTurbines.from_WAsP_wtg(f) for f in wtg_file])

        cut_ins = []
        cut_outs = []

        tree = ET.parse(wtg_file)
        root = tree.getroot()
        # Reading data from wtg_file
        name = root.attrib['Description']
        diameter = float(root.attrib['RotorDiameter'])
        hub_height = float(root.find('SuggestedHeights').find('Height').text)

        performance_tables = list(root.iter('PerformanceTable'))

        def fmt(v):
            try:
                return int(v)
            except (ValueError, TypeError):
                try:
                    return float(v)
                except (ValueError, TypeError):
                    return v

        wt_data = [{
            k: fmt(perftab.attrib.get(k, None))
            for k in performance_tables[0].attrib
        } for perftab in performance_tables]

        for i, perftab in enumerate(performance_tables):
            wt_data[i].update({
                k:
                float(perftab.find('StartStopStrategy').attrib.get(k, None))
                for k in perftab.find('StartStopStrategy').attrib
            })
            wt_data[i].update({
                k: np.array([
                    dp.attrib.get(k, np.nan)
                    for dp in perftab.iter('DataPoint')
                ],
                            dtype=float)
                for k in list(perftab.iter('DataPoint'))[0].attrib
            })
            wt_data[i]['ct_idle'] = wt_data[i]['ThrustCoEfficient'][-1]

        power_ct_funcs = PowerCtFunctionList(
            'mode', [
                PowerCtTabular(wt['WindSpeed'],
                               wt['PowerOutput'],
                               power_unit,
                               wt['ThrustCoEfficient'],
                               ws_cutin=wt['LowSpeedCutIn'],
                               ws_cutout=wt['HighSpeedCutOut'],
                               ct_idle=wt['ct_idle'],
                               additional_models=[]) for wt in wt_data
            ],
            default_value=default_mode,
            additional_models=[SimpleYawModel()])

        char_data_tables = [
            np.array([pct.ws_tab, pct.power_ct_tab[0], pct.power_ct_tab[1]]).T
            for pct in power_ct_funcs.windTurbineFunction_lst
        ]

        wts = WindTurbine(name=name,
                          diameter=diameter,
                          hub_height=hub_height,
                          powerCtFunction=power_ct_funcs)
        wts.wt_data = wt_data
        wts.upct_tables = char_data_tables
        wts.cut_in = cut_ins
        wts.cut_out = cut_outs
        return wts
Beispiel #11
0
class WindTurbines():
    """Set of multiple type wind turbines"""
    def __new__(cls, *args, **kwargs):
        from py_wake.wind_turbines.wind_turbines_deprecated import DeprecatedWindTurbines
        if cls != WindTurbines:
            return super(WindTurbines, cls).__new__(cls)
        try:
            inspect.getcallargs(DeprecatedWindTurbines.__init__, None, *args,
                                **kwargs)
            warnings.warn(
                """WindTurbines(names, diameters, hub_heights, ct_funcs, power_funcs, power_unit=None) is deprecated.
Use WindTurbines(names, diameters, hub_heights, power_ct_funcs) instead""",
                DeprecationWarning,
                stacklevel=2)
            return DeprecatedWindTurbines(*args, **kwargs)
        except TypeError:
            return super(WindTurbines, cls).__new__(cls)

    def __init__(self,
                 names,
                 diameters,
                 hub_heights,
                 powerCtFunctions,
                 loadFunctions=None):
        """Initialize WindTurbines

        Parameters
        ----------
        names : array_like
            Wind turbine names
        diameters : array_like
            Diameter of wind turbines
        hub_heights : array_like
            Hub height of wind turbines
        powerCtFunctions : list of powerCtFunction objects
            Wind turbine ct functions; func(ws) -> ct
        """
        self._names = np.array(names)
        self._diameters = np.array(diameters)
        self._hub_heights = np.array(hub_heights)
        assert len(names) == len(diameters) == len(hub_heights) == len(
            powerCtFunctions)
        self.powerCtFunction = PowerCtFunctionList('type', powerCtFunctions)


#         if loadFunctions:
#             self.loadfunction =

    @property
    def function_inputs(self):
        ri, oi = self.powerCtFunction.required_inputs, self.powerCtFunction.optional_inputs
        if hasattr(self, 'loadFunction'):
            ri += self.loadFunction.required_inputs
            oi += self.loadFunction.optional_inputs
        return ri, oi

    def _info(self, var, type):
        return var[np.asarray(type, int)]

    def hub_height(self, type=0):
        """Hub height of the specified type(s) of wind turbines"""
        return self._info(self._hub_heights, type)

    def diameter(self, type=0):
        """Rotor diameter of the specified type(s) of wind turbines"""
        return self._info(self._diameters, type)

    def name(self, type=0):
        """Name of the specified type(s) of wind turbines"""
        return self._info(self._names, type)

    def power(self, ws, **kwargs):
        """Power in watt

        Parameters
        ----------
        ws : array_like
            Wind speed
        kwargs : keyword arguments
            required and optional inputs
        """
        return self.powerCtFunction(ws, run_only=0, **kwargs)

    def ct(self, ws, **kwargs):
        """Thrust coefficient

        Parameters
        ----------
        ws : array_like
            Wind speed
        kwargs : keyword arguments
            required and optional inputs
        """
        return self.powerCtFunction(ws, run_only=1, **kwargs)

    def power_ct(self, ws, **kwargs):
        return [self.power(ws, **kwargs), self.ct(ws, **kwargs)]

    def loads(self, ws, **kwargs):
        return self.loadFunction(ws, **kwargs)

    def types(self):
        return np.arange(len(self._names))

    def get_defaults(self, N, type_i=0, h_i=None, d_i=None):
        """
        Parameters
        ----------
        N : int
            number of turbines
        type_i : array_like or None, optional
            Turbine type. If None, all turbines is type 0
        h_i : array_like or None, optional
            hub heights. If None: default hub heights (set in WindTurbines)
        d_i : array_lie or None, optional
            Rotor diameter. If None: default diameter (set in WindTurbines)
        """
        type_i = np.zeros(N, dtype=int) + type_i
        if h_i is None:
            h_i = self.hub_height(type_i)
        elif isinstance(h_i, (int, float)):
            h_i = np.zeros(N) + h_i
        if d_i is None:
            d_i = self.diameter(type_i)
        elif isinstance(d_i, (int, float)):
            d_i = np.zeros(N) + d_i
        return np.asarray(h_i), np.asarray(d_i)

    def enable_autograd(self):
        self.powerCtFunction.enable_autograd()

    def plot_xy(self,
                x,
                y,
                types=None,
                wd=None,
                yaw=0,
                tilt=0,
                normalize_with=1,
                ax=None):
        """Plot wind farm layout including type name and diameter

        Parameters
        ----------
        x : array_like
            x position of wind turbines
        y : array_like
            y position of wind turbines
        types : int or array_like
            type of the wind turbines
        wd : int, float, array_like or None
            - if int, float or array_like: wd is assumed to be the wind direction(s) and a line\
            indicating the perpendicular rotor is plotted.
            - if None: An circle indicating the rotor diameter is plotted
        ax : pyplot or matplotlib axes object, default None

        """
        import matplotlib.pyplot as plt
        if types is None:
            types = np.zeros_like(x)
        if ax is None:
            ax = plt.gca()
        markers = np.array(list("213v^<>o48spP*hH+xXDd|_"))
        colors = ['gray', 'r', 'g', 'k'] * 5

        from matplotlib.patches import Circle
        assert len(x) == len(y)
        types = (np.zeros_like(x) + types).astype(
            int)  # ensure same length as x
        yaw = np.zeros_like(x) + yaw
        tilt = np.zeros_like(x) + tilt

        x, y, D = [
            np.asarray(v) / normalize_with
            for v in [x, y, self.diameter(types)]
        ]
        R = D / 2
        for i, (x_, y_, r, t, yaw_,
                tilt_) in enumerate(zip(x, y, R, types, yaw, tilt)):
            if wd is None or len(np.atleast_1d(wd)) > 1:
                circle = Circle((x_, y_), r, ec=colors[t], fc="None")
                ax.add_artist(circle)
                ax.plot(
                    x_,
                    y_,
                    'None',
                )
            else:
                for wd_ in np.atleast_1d(wd):
                    circle = Ellipse((x_, y_),
                                     2 * r * np.sin(np.deg2rad(tilt_)),
                                     2 * r,
                                     angle=90 - wd_ + yaw_,
                                     ec=colors[t],
                                     fc="None")
                    ax.add_artist(circle)

        for t, m, c in zip(np.unique(types), markers, colors):
            # ax.plot(np.asarray(x)[types == t], np.asarray(y)[types == t], '%sk' % m, label=self._names[int(t)])
            ax.plot([], [], '2', color=colors[t], label=self._names[int(t)])

        for i, (x_, y_, r) in enumerate(zip(x, y, R)):
            ax.annotate(i, (x_ + r, y_ + r), fontsize=7)
        ax.legend(loc=1)
        ax.axis('equal')

    def plot_yz(self,
                y,
                z=None,
                h=None,
                types=None,
                wd=270,
                yaw=0,
                tilt=0,
                normalize_with=1,
                ax=None):
        """Plot wind farm layout in yz-plane including type name and diameter

        Parameters
        ----------
        y : array_like
            y position of wind turbines
        types : int or array_like
            type of the wind turbines
        wd : int, float, array_like or None
            - if int, float or array_like: wd is assumed to be the wind direction(s) and a line\
            indicating the perpendicular rotor is plotted.
            - if None: An circle indicating the rotor diameter is plotted
        ax : pyplot or matplotlib axes object, default None

        """
        import matplotlib.pyplot as plt
        if z is None:
            z = np.zeros_like(y)
        if types is None:
            types = np.zeros_like(y).astype(int)
        else:
            types = (np.zeros_like(y) + types).astype(
                int)  # ensure same length as x
        if h is None:
            h = np.zeros_like(y) + self.hub_height(types)
        else:
            h = np.zeros_like(y) + h

        if ax is None:
            ax = plt.gca()
        markers = np.array(list("213v^<>o48spP*hH+xXDd|_"))
        colors = ['gray', 'k', 'r', 'g', 'k'] * 5

        from matplotlib.patches import Circle

        yaw = np.zeros_like(y) + yaw
        tilt = np.zeros_like(y) + tilt
        y, z, h, D = [
            v / normalize_with
            for v in [y, z, h, self.diameter(types)]
        ]
        for i, (y_, z_, h_, d, t, yaw_,
                tilt_) in enumerate(zip(y, z, h, D, types, yaw, tilt)):
            circle = Ellipse((y_, h_ + z_),
                             d * np.sin(np.deg2rad(wd - yaw_)),
                             d,
                             angle=-tilt_,
                             ec=colors[t],
                             fc="None")
            ax.add_artist(circle)
            ax.plot([y_, y_], [z_, z_ + h_], 'k')
            ax.plot(y_, h_, 'None')

        for t, m, c in zip(np.unique(types), markers, colors):
            ax.plot([], [], '2', color=c, label=self._names[int(t)])

        for i, (y_, z_, h_, d) in enumerate(zip(y, z, h, D)):
            ax.annotate(i, (y_ + d / 2, z_ + h_ + d / 2), fontsize=7)
        ax.legend(loc=1)
        ax.axis('equal')

    def plot(self,
             x,
             y,
             type=None,
             wd=None,
             yaw=0,
             tilt=0,
             normalize_with=1,
             ax=None):
        return self.plot_xy(x, y, type, wd, yaw, tilt, normalize_with, ax)

    @staticmethod
    def from_WindTurbine_lst(wt_lst):
        """Generate a WindTurbines object from a list of (Onetype)WindTurbines

        Parameters
        ----------
        wt_lst : array_like
            list of (OneType)WindTurbines
        """
        def get(att):
            lst = []
            for wt in wt_lst:
                lst.extend(getattr(wt, att))
            return lst

        return WindTurbines(
            *[get(n) for n in ['_names', '_diameters', '_hub_heights']] +
            [[getattr(wt, 'powerCtFunction') for wt in wt_lst]])

    @staticmethod
    def from_WindTurbines(wt_lst):
        from py_wake.wind_turbines.wind_turbines_deprecated import DeprecatedWindTurbines
        assert not any([
            isinstance(wt, DeprecatedWindTurbines) for wt in wt_lst
        ]), "from_WindTurbines no longer supports DeprecatedWindTurbines"
        warnings.simplefilter('default', DeprecationWarning)
        warnings.warn(
            """WindTurbines.from_WindTurbines is deprecated. Use WindTurbines.from_WindTurbine_lst instead""",
            DeprecationWarning,
            stacklevel=2)

        return WindTurbines.from_WindTurbine_lst(wt_lst)

    @staticmethod
    def from_WAsP_wtg(wtg_file, default_mode=0, power_unit='W'):
        """ Parse the one/multiple .wtg file(s) (xml) to initilize an
        WindTurbines object.

        Parameters
        ----------
        wtg_file : string or a list of string
            A string denoting the .wtg file, which is exported from WAsP.

        Returns
        -------
        an object of WindTurbines.

        Note: it is assumed that the power_unit inside multiple .wtg files is the same, i.e., power_unit.
        """
        if isinstance(wtg_file, (list, tuple)):
            return WindTurbine.from_WindTurbine_lst(
                [WindTurbines.from_WAsP_wtg(f) for f in wtg_file])

        cut_ins = []
        cut_outs = []

        tree = ET.parse(wtg_file)
        root = tree.getroot()
        # Reading data from wtg_file
        name = root.attrib['Description']
        diameter = float(root.attrib['RotorDiameter'])
        hub_height = float(root.find('SuggestedHeights').find('Height').text)

        performance_tables = list(root.iter('PerformanceTable'))

        def fmt(v):
            try:
                return int(v)
            except (ValueError, TypeError):
                try:
                    return float(v)
                except (ValueError, TypeError):
                    return v

        wt_data = [{
            k: fmt(perftab.attrib.get(k, None))
            for k in performance_tables[0].attrib
        } for perftab in performance_tables]

        for i, perftab in enumerate(performance_tables):
            wt_data[i].update({
                k:
                float(perftab.find('StartStopStrategy').attrib.get(k, None))
                for k in perftab.find('StartStopStrategy').attrib
            })
            wt_data[i].update({
                k: np.array([
                    dp.attrib.get(k, np.nan)
                    for dp in perftab.iter('DataPoint')
                ],
                            dtype=float)
                for k in list(perftab.iter('DataPoint'))[0].attrib
            })
            wt_data[i]['ct_idle'] = wt_data[i]['ThrustCoEfficient'][-1]

        power_ct_funcs = PowerCtFunctionList(
            'mode', [
                PowerCtTabular(wt['WindSpeed'],
                               wt['PowerOutput'],
                               power_unit,
                               wt['ThrustCoEfficient'],
                               ws_cutin=wt['LowSpeedCutIn'],
                               ws_cutout=wt['HighSpeedCutOut'],
                               ct_idle=wt['ct_idle'],
                               additional_models=[]) for wt in wt_data
            ],
            default_value=default_mode,
            additional_models=[SimpleYawModel()])

        char_data_tables = [
            np.array([pct.ws_tab, pct.power_ct_tab[0], pct.power_ct_tab[1]]).T
            for pct in power_ct_funcs.windTurbineFunction_lst
        ]

        wts = WindTurbine(name=name,
                          diameter=diameter,
                          hub_height=hub_height,
                          powerCtFunction=power_ct_funcs)
        wts.wt_data = wt_data
        wts.upct_tables = char_data_tables
        wts.cut_in = cut_ins
        wts.cut_out = cut_outs
        return wts
     ], [1.21851, 0., 0., -0.05454545]),
     ('PowerCtTabular_spline', V80(method='spline'), [
         69723.7929, 158087.18190692, 324012.9604669, 156598.55856862
     ], [8.176490e-01, -3.31076624e-05, -7.19353708e-03, -1.66006862e-01]),
     ('PowerCtTabular_kW',
      get_wt(PowerCtTabular(
          v80_upct[0], v80_upct[1] / 1000, 'kW', v80_upct[2])), [
              66600.00000931, 178000.00001444, 344999.99973923, 91999.99994598
          ], [0.818, 0.001, -0.014, -0.3]),
     ('PowerCtFunctionList',
      get_wt(
          PowerCtFunctionList('mode', [
              PowerCtTabular(ws=v80_upct[0],
                             power=v80_upct[1],
                             power_unit='w',
                             ct=v80_upct[2]),
              PowerCtTabular(ws=v80_upct[0],
                             power=v80_upct[1] * 1.1,
                             power_unit='w',
                             ct=v80_upct[2] + .1)
          ])), [73260., 195800., 379499.9998, 101199.9999
                ], [0.818, 0.001, -0.014, -0.3])])
@pytest.mark.parametrize('grad_method', [autograd, cs, fd])
def test_gradients(case, wt, dpdu_ref, dctdu_ref, grad_method):

    ws_pts = np.array([3.1, 6., 9., 12.])
    if isinstance(dpdu_ref, FunctionType):
        dpdu_ref, dctdu_ref = dpdu_ref(ws_pts), dctdu_ref(ws_pts)

    with use_autograd_in([
            WindTurbines, iea37_reader, power_ct_functions,
            wind_turbines_deprecated