Skip to content

Commit

Permalink
Merge 36f1b7e into df33d42
Browse files Browse the repository at this point in the history
  • Loading branch information
uvchik committed Sep 4, 2019
2 parents df33d42 + 36f1b7e commit 597955f
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 85 deletions.
2 changes: 1 addition & 1 deletion doc/conf.py
Expand Up @@ -157,7 +157,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = []

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down
6 changes: 3 additions & 3 deletions doc/getting_started.rst
Expand Up @@ -55,9 +55,9 @@ The basic usage of the windpowerlib is shown in the ModelChain example that is a
To run the example you need the example weather and turbine data used:

* :download:`Example weather data file <../example/weather.csv>`
* :download:`Example power curve data file <../example/data/example_power_curves.csv>`
* :download:`Example power coefficient curve data file <../example/data/example_power_coefficient_curves.csv>`
* :download:`Example nominal power data file <../example/data/example_turbine_data.csv>`
* :download:`Example power curve data file <../example/data/power_curves.csv>`
* :download:`Example power coefficient curve data file <../example/data/power_coefficient_curves.csv>`
* :download:`Example nominal power data file <../example/data/turbine_data.csv>`

Furthermore, you have to install the windpowerlib and to run the notebook you also need to install `notebook` using pip3. To launch jupyter notebook type ``jupyter notebook`` in the terminal.
This will open a browser window. Navigate to the directory containing the notebook to open it. See the jupyter notebook quick start guide for more information on `how to install <http://jupyter-notebook-beginner-guide.readthedocs.io/en/latest/install.html>`_ and
Expand Down
16 changes: 14 additions & 2 deletions doc/modules.rst
Expand Up @@ -63,14 +63,26 @@ Wind turbine data
Functions and methods to obtain the nominal power as well as
power curve or power coefficient curve needed by the :py:class:`~.wind_turbine.WindTurbine` class.


.. autosummary::
:toctree: temp/

wind_turbine.get_turbine_data_from_file
wind_turbine.load_turbine_data_from_oedb
wind_turbine.get_turbine_types

.. _create_input_types_label:

Data Container
=====================

Create data container to be used as an input in classes und functions.

.. autosummary::
:toctree: temp/

wind_turbine.WindTurbineGroup
wind_turbine.WindTurbine.to_group

.. _wind_farm_label:

Wind farm calculations
Expand Down Expand Up @@ -233,4 +245,4 @@ TurbineClusterModelChain example
The ``turbine_cluster_modelchain_example`` consists of the following functions
as well as it uses functions of the ``modelchain_example``.

.. include:: example_2.rst
.. include:: example_2.rst
8 changes: 0 additions & 8 deletions doc/temp/windpowerlib.wind_farm.WindFarm.rst

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion example/turbine_cluster_modelchain_example.ipynb
Expand Up @@ -176,7 +176,7 @@
"\n",
"The TurbineClusterModelChain is a class that provides all necessary steps to calculate the power output of a wind farm or wind turbine cluster. \n",
"\n",
"Like the ModelChain (see [basic example](modelchain_example.ipynb)) you can use the TurbineClusterModelChain with default parameters as shown in this example for the wind farm or specify custom parameters as done here for the cluster.\n",
"Like the ModelChain (see [basic example](modelchain_example_notebook.ipynb)) you can use the TurbineClusterModelChain with default parameters as shown in this example for the wind farm or specify custom parameters as done here for the cluster.\n",
"If you use the 'run_model' method first the aggregated power curve and the mean hub height of the wind farm/cluster is calculated, then inherited functions of the ModelChain are used to calculate the wind speed and density (if necessary) at hub height. After that, depending on the parameters, wake losses are applied and at last the power output is calculated."
]
},
Expand Down
23 changes: 18 additions & 5 deletions tests/test_wind_farm.py
Expand Up @@ -48,10 +48,11 @@ def test_initialization_dataframe(self):

def test_initialization_1(self):
"""test catching error when wind_turbine_fleet not provided as list"""
msg = 'Wind turbine fleet not provided properly.'
msg = 'Wind turbine must be provided as WindTurbine object'
with pytest.raises(ValueError, match=msg):
WindFarm(wind_turbine_fleet=[{'wind_turbine': 'turbine',
'number_of_turbines': 2}, 'dummy'])
WindFarm(wind_turbine_fleet={'wind_turbine': 'turbine',
'number_of_turbines': 2},
name='dummy')

def test_initialization_2(self):
"""test catching error when WindTurbine in wind_turbine_fleet
Expand All @@ -71,7 +72,7 @@ def test_initialization_3(self):
WindTurbine(**self.test_turbine_2)],
'number_of_turbines': [3, 2]})
msg = 'Missing wind_turbine key/column in wind_turbine_fleet'
with pytest.raises(ValueError, match=msg):
with pytest.raises(KeyError, match=msg):
WindFarm(wind_turbine_fleet=wind_turbine_fleet)

def test_initialization_4(self, recwarn):
Expand Down Expand Up @@ -144,4 +145,16 @@ def test_repr(self):
'number_of_turbines': 2}]
assert 'E-126/4200' in repr(WindFarm(wind_turbine_fleet=test_fleet))


def test_aggregation_of_power_curve_with_missing_power_curve(self):
"""Test WindFarm.assign_power_curve() with missing power_curve."""
wt1 = WindTurbine(**self.test_turbine)
wt1.power_curve = None
wind_turbine_fleet = [
{'wind_turbine': wt1,
'number_of_turbines': 3},
{'wind_turbine': WindTurbine(**self.test_turbine_2),
'number_of_turbines': 2}]
windfarm = WindFarm(wind_turbine_fleet=wind_turbine_fleet)
msg = 'For an aggregated wind farm power curve each wind'
with pytest.raises(ValueError, match=msg):
windfarm.assign_power_curve()
37 changes: 33 additions & 4 deletions tests/test_wind_turbine.py
Expand Up @@ -10,18 +10,22 @@
from windpowerlib.tools import WindpowerlibUserWarning

from windpowerlib.wind_turbine import (get_turbine_data_from_file, WindTurbine,
get_turbine_types,
get_turbine_types, WindTurbineGroup,
load_turbine_data_from_oedb)


class TestWindTurbine:

@classmethod
def setup_class(cls):
"""Setup default values"""
cls.source = os.path.join(os.path.dirname(__file__), '../example/data')

def test_warning(self, recwarn):
source = os.path.join(os.path.dirname(__file__), '../example/data')
test_turbine_data = {'hub_height': 100,
'rotor_diameter': 80,
'turbine_type': 'turbine_not_in_file',
'path': source}
'path': self.source}
assert(WindTurbine(**test_turbine_data).power_curve is None)
assert recwarn.pop(WindpowerlibUserWarning)

Expand All @@ -42,6 +46,7 @@ def test_get_turbine_types(self, capsys):

def test_wrong_url_load_turbine_data(self):
"""Load turbine data from oedb."""

with pytest.raises(ConnectionError,
match="Database connection not successful"):
load_turbine_data_from_oedb('wrong_schema')
Expand All @@ -67,4 +72,28 @@ def test_power_coefficient_curve_is_of_wrong_type(self):
'turbine_type': 'test_type',
'power_coefficient_curve': 'string'}
with pytest.raises(TypeError):
WindTurbine(**test_turbine_data)
WindTurbine(**test_turbine_data)

def test_to_group_method(self):
example_turbine = {
'hub_height': 100,
'rotor_diameter': 70,
'turbine_type': 'DUMMY 3',
'path': self.source}
e_t_1 = WindTurbine(**example_turbine)
assert(isinstance(e_t_1.to_group(), WindTurbineGroup))
assert(e_t_1.to_group(5).number_of_turbines == 5)
assert(e_t_1.to_group(number_turbines=5).number_of_turbines == 5)
assert(e_t_1.to_group(total_capacity=3e6).number_of_turbines == 2.0)

def test_wrongly_defined_to_group_method(self):
example_turbine = {
'hub_height': 100,
'rotor_diameter': 70,
'turbine_type': 'DUMMY 3',
'path': self.source}
e_t_1 = WindTurbine(**example_turbine)
with pytest.raises(ValueError,
match="The 'number' and the 'total_capacity' "
"parameter are mutually exclusive."):
e_t_1.to_group(5, 3000)
73 changes: 38 additions & 35 deletions windpowerlib/wind_farm.py
Expand Up @@ -21,13 +21,23 @@ class WindFarm(object):
Parameters
----------
wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>` or list(dict)
Wind turbines of wind farm. DataFrame/Dictionaries must have
'wind_turbine' containing a :class:`~.wind_turbine.WindTurbine` object
and either 'number_of_turbines' (number of wind turbines of the same
turbine type in the wind farm, can be a float) or 'total_capacity'
wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>` or list()
Wind turbines of the wind farm.
1. DataFrame must have 'wind_turbine' containing a
:class:`~.wind_turbine.WindTurbine` object and either
'number_of_turbines' (number of wind turbines of the same turbine type
in the wind farm, can be a float) or 'total_capacity'
(installed capacity of wind turbines of the same turbine type in the
wind farm) as columns/keys. See example below.
2. A list of :class:`~windpowerlib.wind_turbine.WindTurbineGroup`
objects. A WindTurbineGroup can be created from a
:class:`~windpowerlib.wind_turbine.WindTurbine` using the
:func:`~windpowerlib.wind_turbine.WindTurbine.to_group` method.
3. It is still possible to use a list of dict (see example) but we
recommend to use one of the other options above.
efficiency : float or :pandas:`pandas.DataFrame<frame>` or None (optional)
Efficiency of the wind farm. Provide as either constant (float) or
power efficiency curve (pd.DataFrame) containing 'wind_speed' and
Expand All @@ -38,11 +48,11 @@ class WindFarm(object):
Attributes
----------
wind_turbine_fleet : list(dict)
Wind turbines of wind farm. Dictionaries must have 'wind_turbine'
wind_turbine_fleet : :pandas:`pandas.DataFrame<frame>`
Wind turbines of wind farm. DataFrame must have 'wind_turbine'
(contains a :class:`~.wind_turbine.WindTurbine` object) and
'number_of_turbines' (number of wind turbines of the same turbine type
in the wind farm) as keys.
in the wind farm) as columns.
efficiency : float or :pandas:`pandas.DataFrame<frame>` or None
Efficiency of the wind farm. Either constant (float) power efficiency
curve (pd.DataFrame) containing 'wind_speed' and 'efficiency'
Expand All @@ -53,9 +63,6 @@ class WindFarm(object):
hub_height : float
The calculated mean hub height of the wind farm. See
:py:func:`mean_hub_height` for more information.
nominal_power : float
The nominal power is the sum of the nominal power of all turbines in
the wind farm in W.
power_curve : :pandas:`pandas.DataFrame<frame>` or None
The calculated power curve of the wind farm. See
:py:func:`assign_power_curve` for more information.
Expand All @@ -75,25 +82,31 @@ class WindFarm(object):
... 'turbine_type': 'V90/2000',
... 'nominal_power': 2e6}
>>> v90 = WindTurbine(**vestasV90)
>>> # turbine fleet as DataFrame with number of turbines provided
>>> # turbine fleet as DataFrame
>>> wind_turbine_fleet = pd.DataFrame(
... {'wind_turbine': [e126, v90],
... 'number_of_turbines': [6, 3]})
>>> example_farm = wind_farm.WindFarm(wind_turbine_fleet)
... 'number_of_turbines': [6, None],
... 'total_capacity': [None, 3 * 2e6]})
>>> example_farm = wind_farm.WindFarm(wind_turbine_fleet, name='my_farm')
>>> print(example_farm.nominal_power)
31200000.0
>>> # turbine fleet as a list of WindTurbineGroup objects using the
>>> # 'to_group' method.
>>> wind_turbine_fleet = [e126.to_group(6),
... v90.to_group(total_capacity=3 * 2e6)]
>>> example_farm = wind_farm.WindFarm(wind_turbine_fleet, name='my_farm')
>>> print(example_farm.nominal_power)
31200000.0
>>> # turbine fleet as list with total capacity of each turbine type
>>> # provided
>>> # turbine fleet as list of dictionaries (not recommended)
>>> example_farm_data = {
... 'name': 'example_farm',
... 'name': 'my_farm',
... 'wind_turbine_fleet': [{'wind_turbine': e126,
... 'total_capacity': 6 * 4.2e6},
... 'number_of_turbines': 6},
... {'wind_turbine': v90,
... 'total_capacity': 3 * 2e6}]}
>>> example_farm = wind_farm.WindFarm(**example_farm_data)
>>> print(example_farm.nominal_power)
31200000.0
"""

def __init__(self, wind_turbine_fleet, efficiency=None, name='', **kwargs):
Expand All @@ -120,10 +133,7 @@ def check_and_complete_wind_turbine_fleet(self):
"""
# convert list to dataframe if necessary
if isinstance(self.wind_turbine_fleet, list):
try:
self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)
except:
raise ValueError("Wind turbine fleet not provided properly.")
self.wind_turbine_fleet = pd.DataFrame(self.wind_turbine_fleet)

# check wind turbines
try:
Expand All @@ -133,8 +143,8 @@ def check_and_complete_wind_turbine_fleet(self):
'Wind turbine must be provided as WindTurbine object '
'but was provided as {}.'.format(type(turbine)))
except KeyError:
raise ValueError('Missing wind_turbine key/column in '
'wind_turbine_fleet parameter.')
raise KeyError('Missing wind_turbine key/column in '
'wind_turbine_fleet parameter.')

# add columns for number of turbines and total capacity if they don't
# yet exist
Expand All @@ -159,7 +169,7 @@ def check_and_complete_wind_turbine_fleet(self):
else:
self.wind_turbine_fleet.loc[ix, 'number_of_turbines'] = \
number_of_turbines
except:
except TypeError:
raise ValueError(msg.format(row['wind_turbine']))

# calculate total capacity if necessary and check that total capacity
Expand All @@ -170,7 +180,7 @@ def check_and_complete_wind_turbine_fleet(self):
self.wind_turbine_fleet.loc[ix, 'total_capacity'] = \
row['number_of_turbines'] * \
row['wind_turbine'].nominal_power
except:
except TypeError:
raise ValueError(
'Total capacity of turbines of type {turbine} cannot '
'be deduced. Please check if the nominal power of the '
Expand Down Expand Up @@ -201,14 +211,7 @@ def __repr__(self):
@property
def nominal_power(self):
r"""
The nominal power of the wind farm.
See :attr:`~.wind_farm.WindFarm.nominal_power` for further information.
Parameters
-----------
nominal_power : float
Nominal power of the wind farm in W.
The nominal power is the sum of the nominal power of all turbines.
Returns
-------
Expand Down

0 comments on commit 597955f

Please sign in to comment.