1212import numpy as np
1313import pandas as pd
1414import logging
15+ import warnings
1516
1617
1718class 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