A bit of imports to render the notebook
In [1]:
%load_ext autoreload
%autoreload 2
import numpy as np
from IPython.display import HTML, Latex, Markdown, Pretty
We will plot with the Plot.ly library
In [2]:
from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
from plotly.graph_objs import *
print __version__ # requires version >= 1.9.0
init_notebook_mode() # run at the start of every ipython notebook to use plotly.offline
# this injects the plotly.js source files into the notebook
WindIO is a package that can read .yml files describing the plants and turbines. It's hopefully going to be integrated in FUSED-Wind in a close future.
In [3]:
from windIO.Plant import WTLayout
We have 3 offshore plants to play with. You can change the plant by running the corresponding cell before running the rest of the notebook.
In [4]:
filename = 'middelgrunden.yml'
In [5]:
filename = 'lillgrund.yml'
In [6]:
filename = 'hornsrev.yml'
In [7]:
wtl = WTLayout(filename)
The WindIO YML format is a human readable file format. It is meant to be easy to edit as a collaborative document. We plan to also include standards to add external data files for the mode data-heavy I/O needs.
In [8]:
!cat $filename|head
In [9]:
wtl.plot_location()
In [10]:
wtl.plot_layout()
In [11]:
wtl.WT01
Out[11]:
In [12]:
from fusedwake.WindFarm import WindFarm
In [13]:
wf = WindFarm(yml=filename)
The WindFarm object representation returns in Jupyter notebooks a little summary of what the wind farm is about
In [14]:
wf
Out[14]:
In [15]:
iplot({'data':[{'x':wf.WT.pc[:,0],
'y':wf.WT.pc[:,1]}],
'layout':{'xaxis':{'title':'Wind Speed [m/s]'},
'yaxis':{'title': 'Power [kW]'},
'title':'The {} Power Curve'.format(wf.WT.turbine_type)}})
Right now the WindIO WTLayout object is monkey-patched inside the WindFarm object. We might integrate it more tightly inside the WindFarm class later on.
The GCL model
In [16]:
from fusedwake.gcl import GCL
gcl = GCL(WF=wf, TI=0.1, z0=0.0001, NG=8, sup='lin', inflow='log')
The NOJ model
In [17]:
from fusedwake.noj import NOJ
noj = NOJ(WF=wf, kj=0.04)
The Gaussian shape wake model from Bastankhah & Porté-Agel$^*$
$^*$ Bastankhah, M., & Porté-Agel, F. (2014). **A new analytical model for wind-turbine wakes**. *Renewable Energy, 70, 116-123*.
In [18]:
from fusedwake.gau import GAU
gau = GAU(WF=wf, ks=0.04)
The GCL class is wrapper class for different python and fortran functions. When the __init__ function is called, you can pass as parameters all the inputs you want. Typically that would be the inputs you are not planning to change afterwards.
In [19]:
# Inputs
WS=10.0 # m/s
WD=270 # deg
version = 'fort_gcl' #
# Run the models
gcl(WS=WS, WD=WD, version='fort_gcl')
noj(WS=WS, WD=WD, version='fort_noj')
gau(WS=WS, WD=WD)
Out[19]:
The models can be run by calling the instance directly. It returns itself so you can also call it like that to calculate the total power produced by the plant:
In [20]:
gcl(WS=WS, WD=WD).p_wt.sum()
Out[20]:
In [21]:
noj(WS=WS, WD=WD).p_wt.sum()
Out[21]:
In [22]:
gau(WS=WS, WD=WD).p_wt.sum()
Out[22]:
Once it has been run, all the inputs and outputs of the model are stored as object variable in the gcl instance.
In [23]:
iplot({'data':[{'x': [wt.name for wt in wf.WT],
'y': noj(version=v).p_wt, 'type': 'scatter',
'name':v} for v in noj.versions ] +
[{'x': [wt.name for wt in wf.WT],
'y': gcl(version=v).p_wt, 'type': 'scatter',
'name':v} for v in gcl.versions] +
[{'x': [wt.name for wt in wf.WT],
'y': gau(version=v).p_wt, 'type': 'scatter',
'name':v} for v in gau.versions],
'layout':{'xaxis':{'title':'Turbine Name'},
'yaxis':{'title':'Power [W]'},
'title': '%s Power at WD=%2.1f deg and WS=%2.1f m/s'%(wf.name, WD, WS)}})
Latex("""The total production of %s in this flow case is:
$P_{GCL}(u=%2.1f, \\theta=%3.1f)=%8.2f$ MW,
$P_{NOJ}(u=%2.1f, \\theta=%3.1f)=%8.2f$ MW"""%(
wf.name, WS, WD, gcl.p_wt.sum()/1.0E6, WS, WD, noj.p_wt.sum()/1.0E6))
Out[23]:
In [24]:
iplot({'data':[{'x': [wt.name for wt in wf.WT],
'y': noj(version=v).u_wt, 'type': 'scatter',
'name':v} for v in noj.versions ]+
[{'x': [wt.name for wt in wf.WT],
'y': gcl(version=v).u_wt, 'type': 'scatter',
'name':v} for v in gcl.versions] +
[{'x': [wt.name for wt in wf.WT],
'y': gau(version=v).u_wt, 'type': 'scatter',
'name':v} for v in gau.versions],
'layout':{'xaxis':{'title':'Turbine Name'},
'yaxis':{'title':'AVG Wind Speed [m/s]'},
'title': '%s Average wind speed over the rotor at WD=%2.1f deg and WS=%2.1f m/s'%(wf.name, WD, WS)}})
There are several versions of the GCLarsen model implemented in the FUSED-Wake. They are all accessible through the GCL wrapper class using the same I/Os.
In [25]:
Markdown('The different version s of GCL currently available are: `{}`'.format('`, `'.join(gcl.versions)))
Out[25]:
Checkout the docs online to see what is the difference.
In [26]:
powers = [gcl(version=v).p_wt.sum() for v in gcl.versions] + [noj(version=v).p_wt.sum() for v in noj.versions] + [gau(version=v).p_wt.sum() for v in gau.versions]
# The plot
iplot({'data':[{'x': gcl.versions+noj.versions+gau.versions,
'y':powers, 'type':'bar'}],
'layout':{'xaxis':{'title':'GCL version'},
'yaxis':{'title':'Total power [W]',
'range':[min(powers)*0.99, max(powers)*1.01]},
'title':'Difference between the power predicted by different implementations of GCL and NOJ'}})
# A bit of text
Markdown("""The different versions of GCL, NOJ and GAU give different results for the
{WF.name} case at WS $={WS: .1f}$ m/s, WD $={WD: .1f} ^o$:""".format(**gcl.__dict__)
+ "\n * " + "\n * ".join(["**{0}**: P = {1: .3f} MW".format(v, p/1E6) for v, p in zip(gcl.versions + noj.versions + gau.versions, powers)]))
Out[26]:
In [27]:
[min(powers), max(powers)]
Out[27]:
The GCL has been developed using several different algorithms, there was a bug found in the original implementaation of the GCL model in Matlab, that was translated to python (version py0). It was then fixed in version py1. However, it's not clear what is the difference between the py1 version and the fort0 version.
In [28]:
WDs = range(0,360,1)
# The plot
iplot({'data':[{'x': WDs,
'y':[gcl(WS=8.0, version=v, WD=wd).p_wt.sum() for wd in WDs],
'name': v} for v in gcl.versions]+
[{'x': WDs,
'y':[noj(WS=8.0, version=v, WD=wd).p_wt.sum() for wd in WDs],
'name': v} for v in noj.versions] +
[{'x': WDs,
'y':[gau(WS=8.0, version=v, WD=wd).p_wt.sum() for wd in WDs],
'name': v} for v in gau.versions],
'layout':{'xaxis':{'title':'Wind Direction [deg]'},
'yaxis':{'title':'Total power [W]'},
'title':'Difference between the power predicted by different implementations of GCL'}})
In [29]:
NGs = range(4,9)
# The plot
iplot({'data':[{'x': NGs,
'y':[gcl(NG=NG, version=v).p_wt.sum() for NG in NGs],
'type':'bar',
'name':v} for v in gcl.versions],
'layout':{'xaxis':{'title':'Total power [W]'},
'yaxis':{'title':'Number of points in the Gauss averaging [-]'},
'title':'Different number of points to average wind speed over the rotor'}})
In [ ]:
In [ ]: