Skip to content

Commit 2bc6c8d

Browse files
committed
Allow providing wind turbine fleet as dataframe
1 parent b62448c commit 2bc6c8d

File tree

1 file changed

+124
-85
lines changed

1 file changed

+124
-85
lines changed

windpowerlib/wind_farm.py

Lines changed: 124 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import numpy as np
1313
import pandas as pd
1414
import logging
15+
import warnings
1516

1617

1718
class WindFarm(object):
@@ -20,13 +21,13 @@ class WindFarm(object):
2021
2122
Parameters
2223
----------
23-
wind_turbine_fleet : list(dict)
24-
Wind turbines of wind farm. Dictionaries must have 'wind_turbine'
25-
(contains a :class:`~.wind_turbine.WindTurbine` object) and
26-
either 'number_of_turbines' (number of wind turbines of the same
27-
turbine type in the wind farm) or 'total_capacity' (installed capacity
28-
of wind turbines of the same turbine type in the wind farm) as keys.
29-
See example below.
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.
3031
efficiency : float or :pandas:`pandas.DataFrame<frame>` or None (optional)
3132
Efficiency of the wind farm. Provide as either constant (float) or
3233
power efficiency curve (pd.DataFrame) containing 'wind_speed' and
@@ -62,25 +63,37 @@ class WindFarm(object):
6263
Examples
6364
--------
6465
>>> from windpowerlib import wind_farm
65-
>>> from windpowerlib import wind_turbine
66+
>>> from windpowerlib import WindTurbine
67+
>>> import pandas as pd
6668
>>> enerconE126 = {
6769
... 'hub_height': 135,
6870
... 'rotor_diameter': 127,
6971
... 'turbine_type': 'E-126/4200'}
70-
>>> e126 = wind_turbine.WindTurbine(**enerconE126)
71-
>>> example_farm_data = {
72-
... 'name': 'example_farm',
73-
... 'wind_turbine_fleet': [{'wind_turbine': e126,
74-
... 'number_of_turbines': 6}]}
75-
>>> example_farm = wind_farm.WindFarm(**example_farm_data)
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)
7683
>>> print(example_farm.nominal_power)
77-
25200000.0
84+
31200000.0
85+
>>> # turbine fleet as list with total capacity of each turbine type
86+
>>> # provided
7887
>>> example_farm_data = {
88+
... 'name': 'example_farm',
7989
... 'wind_turbine_fleet': [{'wind_turbine': e126,
80-
... 'total_capacity': 8.4e6}]}
90+
... 'total_capacity': 6 * 4.2e6},
91+
... {'wind_turbine': v90,
92+
... 'total_capacity': 3 * 2e6}]}
8193
>>> example_farm = wind_farm.WindFarm(**example_farm_data)
8294
>>> print(example_farm.nominal_power)
83-
8400000.0
95+
31200000.0
96+
8497
"""
8598

8699
def __init__(self, wind_turbine_fleet, efficiency=None, name='', **kwargs):
@@ -93,52 +106,97 @@ def __init__(self, wind_turbine_fleet, efficiency=None, name='', **kwargs):
93106
self._nominal_power = None
94107
self.power_curve = None
95108

96-
# check integrity of given wind turbine fleet
97-
for turbine_type in self.wind_turbine_fleet:
98-
turbine_keys = list(turbine_type.keys())
99-
# check wind turbine
100-
if 'wind_turbine' in turbine_keys:
101-
if not isinstance(turbine_type['wind_turbine'], WindTurbine):
109+
self.check_and_complete_wind_turbine_fleet()
110+
111+
def check_and_complete_wind_turbine_fleet(self):
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):
123+
try:
124+
self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)
125+
except ValueError:
126+
raise ValueError("Wind turbine fleet not provided properly.")
127+
128+
# check wind turbines
129+
try:
130+
for turbine in self.wind_turbine_fleet['wind_turbine']:
131+
if not isinstance(turbine, WindTurbine):
102132
raise ValueError(
103133
'Wind turbine must be provided as WindTurbine object '
104-
'but was provided as {}.'.format(
105-
type(turbine_type['wind_turbine'])))
106-
else:
107-
raise ValueError('Missing wind_turbine key in wind turbine '
108-
'fleet entry {}.'.format(turbine_type))
109-
# check if number of turbines is provided
110-
if not 'number_of_turbines' in turbine_keys:
111-
if 'total_capacity' in turbine_keys:
112-
try:
113-
turbine_type['number_of_turbines'] = \
114-
turbine_type['total_capacity'] / \
115-
turbine_type['wind_turbine'].nominal_power
116-
except:
117-
raise ValueError(
118-
'Number of turbines of type {turbine} can not be '
119-
'deduced from total capacity. Please either '
120-
'provide `number_of_turbines` in the turbine '
121-
'fleet definition or set the nominal power of the '
122-
'wind turbine.'.format(
123-
turbine=turbine_type['wind_turbine']))
134+
'but was provided as {}.'.format(type(turbine)))
135+
except KeyError:
136+
raise ValueError('Missing wind_turbine key/column in '
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:
142+
self.wind_turbine_fleet['number_of_turbines'] = np.nan
143+
if 'total_capacity' not in self.wind_turbine_fleet.columns:
144+
self.wind_turbine_fleet['total_capacity'] = np.nan
145+
146+
# calculate number of turbines if necessary
147+
number_turbines_not_provided = self.wind_turbine_fleet[
148+
self.wind_turbine_fleet['number_of_turbines'].isnull()]
149+
for ix, row in number_turbines_not_provided.iterrows():
150+
msg = 'Number of turbines of type {0} can not be deduced ' \
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:
155+
number_of_turbines = row['total_capacity'] / \
156+
row['wind_turbine'].nominal_power
157+
if np.isnan(number_of_turbines):
158+
raise ValueError(msg.format(row['wind_turbine']))
124159
else:
160+
self.wind_turbine_fleet.loc[ix, 'number_of_turbines'] = \
161+
number_of_turbines
162+
except:
163+
raise ValueError(msg.format(row['wind_turbine']))
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():
168+
if np.isnan(row['total_capacity']):
169+
try:
170+
self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
171+
row['number_of_turbines'] * \
172+
row['wind_turbine'].nominal_power
173+
except:
125174
raise ValueError(
126-
'Please provide `number_of_turbines` or '
127-
'`total_capacity` for wind turbine {} in wind farm '
128-
'definition.'.format(turbine_type['wind_turbine']))
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'] == (
181+
row['number_of_turbines'] *
182+
row['wind_turbine'].nominal_power):
183+
self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
184+
row['number_of_turbines'] * \
185+
row['wind_turbine'].nominal_power
186+
msg = (
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']),
191+
tools.WindpowerlibUserWarning)
129192

130193
def __repr__(self):
131-
132194
if self.name is not '':
133-
repr = 'Wind farm: {name}'.format(name=self.name)
195+
return 'Wind farm: {name}'.format(name=self.name)
134196
else:
135-
info = []
136-
for turbine_dict in self.wind_turbine_fleet:
137-
info.append(r"{number}x {type}".format(
138-
number=turbine_dict['number_of_turbines'],
139-
type=turbine_dict['wind_turbine']))
140-
repr = r'Wind farm with: {info}'.format(info=info)
141-
return repr
197+
return 'Wind farm with turbine fleet: [number, type]\n {}'.format(
198+
self.wind_turbine_fleet.loc[
199+
:, ['number_of_turbines', 'wind_turbine']].values)
142200

143201
@property
144202
def nominal_power(self):
@@ -159,7 +217,7 @@ def nominal_power(self):
159217
160218
"""
161219
if not self._nominal_power:
162-
self.nominal_power = self.get_installed_power()
220+
self.nominal_power = self.wind_turbine_fleet.total_capacity.sum()
163221
return self._nominal_power
164222

165223
@nominal_power.setter
@@ -203,29 +261,11 @@ def mean_hub_height(self):
203261
204262
"""
205263
self.hub_height = np.exp(
206-
sum(np.log(wind_dict['wind_turbine'].hub_height) *
207-
wind_dict['wind_turbine'].nominal_power *
208-
wind_dict['number_of_turbines']
209-
for wind_dict in self.wind_turbine_fleet) /
210-
self.get_installed_power())
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)
211267
return self
212268

213-
def get_installed_power(self):
214-
r"""
215-
Calculates :py:attr:`~nominal_power` of the wind farm.
216-
217-
Returns
218-
-------
219-
float
220-
Nominal power of the wind farm in W. See :py:attr:`~nominal_power`
221-
for further information.
222-
223-
"""
224-
return sum(
225-
wind_dict['wind_turbine'].nominal_power *
226-
wind_dict['number_of_turbines']
227-
for wind_dict in self.wind_turbine_fleet)
228-
229269
def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
230270
smoothing=False, block_width=0.5,
231271
standard_deviation_method='turbulence_intensity',
@@ -280,15 +320,15 @@ def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
280320
281321
"""
282322
# Check if all wind turbines have a power curve as attribute
283-
for item in self.wind_turbine_fleet:
284-
if item['wind_turbine'].power_curve is None:
323+
for turbine in self.wind_turbine_fleet['wind_turbine']:
324+
if turbine.power_curve is None:
285325
raise ValueError("For an aggregated wind farm power curve " +
286326
"each wind turbine needs a power curve " +
287327
"but `power_curve` of '{}' is None.".format(
288-
item['wind_turbine']))
328+
turbine))
289329
# Initialize data frame for power curve values
290330
df = pd.DataFrame()
291-
for turbine_type_dict in self.wind_turbine_fleet:
331+
for ix, row in self.wind_turbine_fleet.iterrows():
292332
# Check if needed parameters are available and/or assign them
293333
if smoothing:
294334
if (standard_deviation_method == 'turbulence_intensity' and
@@ -298,7 +338,7 @@ def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
298338
# Calculate turbulence intensity and write to kwargs
299339
turbulence_intensity = (
300340
tools.estimate_turbulence_intensity(
301-
turbine_type_dict['wind_turbine'].hub_height,
341+
row['wind_turbine'].hub_height,
302342
kwargs['roughness_length']))
303343
kwargs['turbulence_intensity'] = turbulence_intensity
304344
else:
@@ -308,8 +348,7 @@ def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
308348
"`standard_deviation_method` if " +
309349
"`turbulence_intensity` is not given")
310350
# Get original power curve
311-
power_curve = pd.DataFrame(
312-
turbine_type_dict['wind_turbine'].power_curve)
351+
power_curve = pd.DataFrame(row['wind_turbine'].power_curve)
313352
# Editions to the power curves before the summation
314353
if smoothing and smoothing_order == 'turbine_power_curves':
315354
power_curve = power_curves.smooth_power_curve(
@@ -335,7 +374,7 @@ def assign_power_curve(self, wake_losses_model='wind_farm_efficiency',
335374
# (multiplied by turbine amount)
336375
df = pd.concat(
337376
[df, pd.DataFrame(power_curve.set_index(['wind_speed']) *
338-
turbine_type_dict['number_of_turbines'])], axis=1)
377+
row['number_of_turbines'])], axis=1)
339378
# Aggregate all power curves
340379
wind_farm_power_curve = pd.DataFrame(
341380
df.interpolate(method='index').sum(axis=1))

0 commit comments

Comments
 (0)