• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wind-python / windpowerlib / 362

6 Aug 2019 - 15:28 coverage: 99.627% (+0.02%) from 99.603%
362

Pull #63

travis-ci

web-flow
Adapt examples to show initialization of wind farm with total capacity
Pull Request #63: Features/add wind farm init option

48 of 48 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

534 of 536 relevant lines covered (99.63%)

2.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.92
/windpowerlib/wind_farm.py
1
"""
2
The ``wind_farm`` module contains the class WindFarm that implements
3
a wind farm in the windpowerlib and functions needed for the modelling of a
4
wind farm.
5

6
"""
7

8
__copyright__ = "Copyright oemof developer group"
3×
9
__license__ = "GPLv3"
3×
10

11
from windpowerlib import tools, power_curves, WindTurbine
3×
12
import numpy as np
3×
13
import pandas as pd
3×
14
import logging
3×
15
import warnings
3×
16

17

18
class WindFarm(object):
3×
19
    r"""
20
    Defines a standard set of wind farm attributes.
21

22
    Parameters
23
    ----------
24
    wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>` or list(dict)
25
        Wind turbines of wind farm. DataFrame/Dictionaries must have
26
        'wind_turbine' containing a :class:`~.wind_turbine.WindTurbine` object
27
        and either 'number_of_turbines' (number of wind turbines of the same
28
        turbine type in the wind farm, can be a float) or 'total_capacity'
29
        (installed capacity of wind turbines of the same turbine type in the
30
        wind farm) as columns/keys. See example below.
31
    efficiency : float or :pandas:`pandas.DataFrame<frame>` or None (optional)
32
        Efficiency of the wind farm. Provide as either constant (float) or
33
        power efficiency curve (pd.DataFrame) containing 'wind_speed' and
34
        'efficiency' columns with wind speeds in m/s and the corresponding
35
        dimensionless wind farm efficiency. Default: None.
36
    name : str (optional)
37
        Can be used as an identifier of the wind farm. Default: ''.
38

39
    Attributes
40
    ----------
41
    wind_turbine_fleet : list(dict)
42
        Wind turbines of wind farm. Dictionaries must have 'wind_turbine'
43
        (contains a :class:`~.wind_turbine.WindTurbine` object) and
44
        'number_of_turbines' (number of wind turbines of the same turbine type
45
        in the wind farm) as keys.
46
    efficiency : float or :pandas:`pandas.DataFrame<frame>` or None
47
        Efficiency of the wind farm. Either constant (float) power efficiency
48
        curve (pd.DataFrame) containing 'wind_speed' and 'efficiency'
49
        columns with wind speeds in m/s and the corresponding
50
        dimensionless wind farm efficiency. Default: None.
51
    name : str
52
        If set this is used as an identifier of the wind farm.
53
    hub_height : float
54
        The calculated mean hub height of the wind farm. See
55
        :py:func:`mean_hub_height` for more information.
56
    nominal_power : float
57
        The nominal power is the sum of the nominal power of all turbines in
58
        the wind farm in W.
59
    power_curve : :pandas:`pandas.DataFrame<frame>` or None
60
        The calculated power curve of the wind farm. See
61
        :py:func:`assign_power_curve` for more information.
62

63
    Examples
64
    --------
65
    >>> from windpowerlib import wind_farm
66
    >>> from windpowerlib import WindTurbine
67
    >>> import pandas as pd
68
    >>> enerconE126 = {
69
    ...    'hub_height': 135,
70
    ...    'rotor_diameter': 127,
71
    ...    'turbine_type': 'E-126/4200'}
72
    >>> e126 = WindTurbine(**enerconE126)
73
    >>> vestasV90 = {
74
    ...     'hub_height': 90,
75
    ...     'turbine_type': 'V90/2000',
76
    ...     'nominal_power': 2e6}
77
    >>> v90 = WindTurbine(**vestasV90)
78
    >>> # turbine fleet as DataFrame with number of turbines provided
79
    >>> wind_turbine_fleet = pd.DataFrame(
80
    ...     {'wind_turbine': [e126, v90],
81
    ...      'number_of_turbines': [6, 3]})
82
    >>> example_farm = wind_farm.WindFarm(wind_turbine_fleet)
83
    >>> print(example_farm.nominal_power)
84
    31200000.0
85
    >>> # turbine fleet as list with total capacity of each turbine type
86
    >>> # provided
87
    >>> example_farm_data = {
88
    ...    'name': 'example_farm',
89
    ...    'wind_turbine_fleet': [{'wind_turbine': e126,
90
    ...                            'total_capacity': 6 * 4.2e6},
91
    ...                           {'wind_turbine': v90,
92
    ...                            'total_capacity': 3 * 2e6}]}
93
    >>> example_farm = wind_farm.WindFarm(**example_farm_data)
94
    >>> print(example_farm.nominal_power)
95
    31200000.0
96

97
    """
98

99
    def __init__(self, wind_turbine_fleet, efficiency=None, name='', **kwargs):
3×
100

101
        self.wind_turbine_fleet = wind_turbine_fleet
3×
102
        self.efficiency = efficiency
3×
103
        self.name = name
3×
104

105
        self.hub_height = None
3×
106
        self._nominal_power = None
3×
107
        self.power_curve = None
3×
108

109
        self.check_and_complete_wind_turbine_fleet()
3×
110

111
    def check_and_complete_wind_turbine_fleet(self):
3×
112
        """
113
        Function to check wind turbine fleet user input.
114

115
        Besides checking if all necessary parameters to fully define the wind
116
        turbine fleet are provided, this function also fills in the
117
        number of turbines or total capacity of each turbine type and checks
118
        if they are consistent.
119

120
        """
121
        # convert list to dataframe if necessary
122
        if isinstance(self.wind_turbine_fleet, list):
3×
123
            try:
3×
124
                self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)
3×
125
            except:
3×
126
                raise ValueError("Wind turbine fleet not provided properly.")
3×
127

128
        # check wind turbines
129
        try:
3×
130
            for turbine in self.wind_turbine_fleet['wind_turbine']:
3×
131
                if not isinstance(turbine, WindTurbine):
3×
132
                    raise ValueError(
3×
133
                        'Wind turbine must be provided as WindTurbine object '
134
                        'but was provided as {}.'.format(type(turbine)))
135
        except KeyError:
3×
136
            raise ValueError('Missing wind_turbine key/column in '
3×
137
                             'wind_turbine_fleet parameter.')
138

139
        # add columns for number of turbines and total capacity if they don't
140
        # yet exist
141
        if 'number_of_turbines' not in self.wind_turbine_fleet.columns:
3×
142
            self.wind_turbine_fleet['number_of_turbines'] = np.nan
3×
143
        if 'total_capacity' not in self.wind_turbine_fleet.columns:
3×
144
            self.wind_turbine_fleet['total_capacity'] = np.nan
3×
145

146
        # calculate number of turbines if necessary
147
        number_turbines_not_provided = self.wind_turbine_fleet[
3×
148
            self.wind_turbine_fleet['number_of_turbines'].isnull()]
149
        for ix, row in number_turbines_not_provided.iterrows():
3×
150
            msg = 'Number of turbines of type {0} can not be deduced ' \
3×
151
                  'from total capacity. Please either provide ' \
152
                  '`number_of_turbines` in the turbine fleet definition or ' \
153
                  'set the nominal power of the wind turbine.'
154
            try:
3×
155
                number_of_turbines = row['total_capacity'] / \
3×
156
                    row['wind_turbine'].nominal_power
157
                if np.isnan(number_of_turbines):
3×
158
                    raise ValueError(msg.format(row['wind_turbine']))
3×
159
                else:
160
                    self.wind_turbine_fleet.loc[ix, 'number_of_turbines'] = \
3×
161
                        number_of_turbines
162
            except:
3×
163
                raise ValueError(msg.format(row['wind_turbine']))
3×
164

165
        # calculate total capacity if necessary and check that total capacity
166
        # and number of turbines is consistent if both are provided
167
        for ix, row in self.wind_turbine_fleet.iterrows():
3×
168
            if np.isnan(row['total_capacity']):
3×
169
                try:
3×
170
                    self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
3×
171
                        row['number_of_turbines'] * \
172
                        row['wind_turbine'].nominal_power
173
                except:
3×
174
                    raise ValueError(
3×
175
                        'Total capacity of turbines of type {turbine} cannot '
176
                        'be deduced. Please check if the nominal power of the '
177
                        'wind turbine is set.'.format(
178
                            turbine=row['wind_turbine']))
179
            else:
180
                if not row['total_capacity'] == (
3×
181
                        row['number_of_turbines'] *
182
                        row['wind_turbine'].nominal_power):
183
                    self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
3×
184
                        row['number_of_turbines'] * \
185
                        row['wind_turbine'].nominal_power
186
                    msg = (
3×
187
                        'The provided total capacity of WindTurbine {0} has '
188
                        'been overwritten as it was not consistent with the '
189
                        'number of turbines provided for this type.')
190
                    warnings.warn(msg.format(row['wind_turbine']),
3×
191
                                  tools.WindpowerlibUserWarning)
192

193
    def __repr__(self):
3×
194
        if self.name is not '':
3×
195
            return 'Wind farm: {name}'.format(name=self.name)
3×
196
        else:
197
            return 'Wind farm with turbine fleet: [number, type]\n {}'.format(
3×
198
                self.wind_turbine_fleet.loc[
199
                    :, ['number_of_turbines', 'wind_turbine']].values)
200

201
    @property
3×
202
    def nominal_power(self):
203
        r"""
204
        The nominal power of the wind farm.
205

206
        See :attr:`~.wind_farm.WindFarm.nominal_power` for further information.
207

208
        Parameters
209
        -----------
210
        nominal_power : float
211
            Nominal power of the wind farm in W.
212

213
        Returns
214
        -------
215
        float
216
            Nominal power of the wind farm in W.
217

218
        """
219
        if not self._nominal_power:
3×
220
            self.nominal_power = self.wind_turbine_fleet.total_capacity.sum()
3×
221
        return self._nominal_power
3×
222

223
    @nominal_power.setter
3×
224
    def nominal_power(self, nominal_power):
225
        self._nominal_power = nominal_power
3×
226

227
    def mean_hub_height(self):
3×
228
        r"""
229
        Calculates the mean hub height of the wind farm.
230

231
        The mean hub height of a wind farm is necessary for power output
232
        calculations with an aggregated wind farm power curve containing wind
233
        turbines with different hub heights. Hub heights of wind turbines with
234
        higher nominal power weigh more than others.
235
        After the calculations the mean hub height is assigned to the attribute
236
        :py:attr:`~hub_height`.
237

238
        Returns
239
        -------
240
        :class:`~.wind_farm.WindFarm`
241
            self
242

243
        Notes
244
        -----
245
        The following equation is used [1]_:
246

247
        .. math:: h_{WF} = e^{\sum\limits_{k}{ln(h_{WT,k})}
248
                           \frac{P_{N,k}}{\sum\limits_{k}{P_{N,k}}}}
249

250
        with:
251
            :math:`h_{WF}`: mean hub height of wind farm,
252
            :math:`h_{WT,k}`: hub height of the k-th wind turbine of a wind
253
            farm, :math:`P_{N,k}`: nominal power of the k-th wind turbine
254

255
        References
256
        ----------
257
        .. [1]  Knorr, K.: "Modellierung von raum-zeitlichen Eigenschaften der
258
                 Windenergieeinspeisung f��r wetterdatenbasierte
259
                 Windleistungssimulationen". Universit��t Kassel, Diss., 2016,
260
                 p. 35
261

262
        """
263
        self.hub_height = np.exp(
3×
264
            sum(np.log(row['wind_turbine'].hub_height) * row['total_capacity']
265
                for ix, row in self.wind_turbine_fleet.iterrows()) /
266
            self.nominal_power)
267
        return self
3×
268

269
    def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
3×
270
                           smoothing=False, block_width=0.5,
271
                           standard_deviation_method='turbulence_intensity',
272
                           smoothing_order='wind_farm_power_curves',
273
                           turbulence_intensity=None, **kwargs):
274
        r"""
275
        Calculates the power curve of a wind farm.
276

277
        The wind farm power curve is calculated by aggregating the power curves
278
        of all wind turbines in the wind farm. Depending on the parameters the
279
        power curves are smoothed (before or after the aggregation) and/or a
280
        wind farm efficiency (power efficiency curve or constant efficiency) is
281
        applied after the aggregation.
282
        After the calculations the power curve is assigned to the attribute
283
        :py:attr:`~power_curve`.
284

285
        Parameters
286
        ----------
287
        wake_losses_model : str
288
            Defines the method for taking wake losses within the farm into
289
            consideration. Options: 'wind_farm_efficiency' or None.
290
            Default: 'wind_farm_efficiency'.
291
        smoothing : bool
292
            If True the power curves will be smoothed before or after the
293
            aggregation of power curves depending on `smoothing_order`.
294
            Default: False.
295
        block_width : float
296
            Width between the wind speeds in the sum of the equation in
297
            :py:func:`~.power_curves.smooth_power_curve`. Default: 0.5.
298
        standard_deviation_method : str
299
            Method for calculating the standard deviation for the Gauss
300
            distribution. Options: 'turbulence_intensity',
301
            'Staffell_Pfenninger'. Default: 'turbulence_intensity'.
302
        smoothing_order : str
303
            Defines when the smoothing takes place if `smoothing` is True.
304
            Options: 'turbine_power_curves' (to the single turbine power
305
            curves), 'wind_farm_power_curves'.
306
            Default: 'wind_farm_power_curves'.
307
        turbulence_intensity : float
308
            Turbulence intensity at hub height of the wind farm for power curve
309
            smoothing with 'turbulence_intensity' method. Can be calculated
310
            from `roughness_length` instead. Default: None.
311
        roughness_length : float (optional)
312
            Roughness length. If `standard_deviation_method` is
313
            'turbulence_intensity' and `turbulence_intensity` is not given
314
            the turbulence intensity is calculated via the roughness length.
315

316
        Returns
317
        -------
318
        :class:`~.wind_farm.WindFarm`
319
            self
320

321
        """
322
        # Check if all wind turbines have a power curve as attribute
323
        for turbine in self.wind_turbine_fleet['wind_turbine']:
3×
324
            if turbine.power_curve is None:
3×
UNCOV
325
                raise ValueError("For an aggregated wind farm power curve " +
!
326
                                 "each wind turbine needs a power curve " +
327
                                 "but `power_curve` of '{}' is None.".format(
328
                                     turbine))
329
        # Initialize data frame for power curve values
330
        df = pd.DataFrame()
3×
331
        for ix, row in self.wind_turbine_fleet.iterrows():
3×
332
            # Check if needed parameters are available and/or assign them
333
            if smoothing:
3×
334
                if (standard_deviation_method == 'turbulence_intensity' and
3×
335
                        turbulence_intensity is None):
336
                    if 'roughness_length' in kwargs and \
3×
337
                            kwargs['roughness_length'] is not None:
338
                        # Calculate turbulence intensity and write to kwargs
339
                        turbulence_intensity = (
3×
340
                            tools.estimate_turbulence_intensity(
341
                                row['wind_turbine'].hub_height,
342
                                kwargs['roughness_length']))
343
                        kwargs['turbulence_intensity'] = turbulence_intensity
3×
344
                    else:
345
                        raise ValueError(
3×
346
                            "`roughness_length` must be defined for using " +
347
                            "'turbulence_intensity' as " +
348
                            "`standard_deviation_method` if " +
349
                            "`turbulence_intensity` is not given")
350
            # Get original power curve
351
            power_curve = pd.DataFrame(row['wind_turbine'].power_curve)
3×
352
            # Editions to the power curves before the summation
353
            if smoothing and smoothing_order == 'turbine_power_curves':
3×
354
                power_curve = power_curves.smooth_power_curve(
3×
355
                    power_curve['wind_speed'], power_curve['value'],
356
                    standard_deviation_method=standard_deviation_method,
357
                    block_width=block_width, **kwargs)
358
            else:
359
                # Add value zero to start and end of curve as otherwise
360
                # problems can occur during the aggregation
361
                if power_curve.iloc[0]['wind_speed'] != 0.0:
3×
362
                    power_curve = pd.concat(
3×
363
                        [pd.DataFrame(data={
364
                            'value': [0.0], 'wind_speed': [0.0]}),
365
                         power_curve], sort=True)
366
                if power_curve.iloc[-1]['value'] != 0.0:
3×
367
                    power_curve = pd.concat(
3×
368
                        [power_curve, pd.DataFrame(data={
369
                            'value': [0.0], 'wind_speed': [
370
                                power_curve['wind_speed'].loc[
371
                                    power_curve.index[-1]] + 0.5]})],
372
                        sort=True)
373
            # Add power curves of all turbine types to data frame
374
            # (multiplied by turbine amount)
375
            df = pd.concat(
3×
376
                [df, pd.DataFrame(power_curve.set_index(['wind_speed']) *
377
                 row['number_of_turbines'])], axis=1)
378
        # Aggregate all power curves
379
        wind_farm_power_curve = pd.DataFrame(
3×
380
            df.interpolate(method='index').sum(axis=1))
381
        wind_farm_power_curve.columns = ['value']
3×
382
        wind_farm_power_curve.reset_index('wind_speed', inplace=True)
3×
383
        # Apply power curve smoothing and consideration of wake losses
384
        # after the summation
385
        if smoothing and smoothing_order == 'wind_farm_power_curves':
3×
386
            wind_farm_power_curve = power_curves.smooth_power_curve(
3×
387
                wind_farm_power_curve['wind_speed'],
388
                wind_farm_power_curve['value'],
389
                standard_deviation_method=standard_deviation_method,
390
                block_width=block_width, **kwargs)
391
        if wake_losses_model == 'wind_farm_efficiency':
3×
392
            if self.efficiency is not None:
3×
393
                wind_farm_power_curve = (
3×
394
                    power_curves.wake_losses_to_power_curve(
395
                        wind_farm_power_curve['wind_speed'].values,
396
                        wind_farm_power_curve['value'].values,
397
                        wind_farm_efficiency=self.efficiency))
398
            else:
399
                logging.info("`wake_losses_model` is {} but wind farm ".format(
!
400
                    wake_losses_model) + "efficiency is NOT taken into "
401
                                         "account as it is None.")
402
        self.power_curve = wind_farm_power_curve
3×
403
        return self
3×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc