New FUSEDWake

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


/opt/conda/lib/python3.5/site-packages/matplotlib/font_manager.py:273: UserWarning:

Matplotlib is building the font cache using fc-list. This may take a moment.

/opt/conda/lib/python3.5/site-packages/matplotlib/font_manager.py:273: UserWarning:

Matplotlib is building the font cache using fc-list. This may take a moment.

1.12.9

Playing with WindIO

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

Load the layout using WindIO

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 YML file standard

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.

The header of the file you selected


In [8]:
!cat $filename|head


layout:
  - name: WT01
    row: 1
    position: [423974, 6151447]
    turbine_type: V80
  - name: WT02
    row: 2
    position: [424042, 6150891]
    turbine_type: V80
  - name: WT03

Plot the geographic location of the wind farm


In [9]:
wtl.plot_location()


Plotting the layout


In [10]:
wtl.plot_layout()



In [11]:
wtl.WT01


Out[11]:
{'name': 'WT01',
 'position': [423974, 6151447],
 'row': 1,
 'turbine_type': 'V80'}

Playing with FUSED-Wake


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

Plotting the power curve


In [14]:
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)}})


Running a flow case

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 [15]:
from fusedwake.gcl.interface import GCL
gcl = GCL(WF=wf, TI=0.1)

The NOJ model


In [16]:
from fusedwake.noj.interface 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 [17]:
from fusedwake.gau.interface 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 [18]:
# 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, version='fort_gau_s')


Out[18]:
<fusedwake.gau.interface.GAU at 0x7fd4c5363cf8>

In [19]:
WS_cases=np.arange(4,6)
WD_cases=np.arange(0,360,20)
WS_ms,WD_ms=np.meshgrid(WS_cases,WD_cases)
WS=WS_ms.flatten()
WD=WD_ms.flatten()
TI=0.1*np.ones_like(WD)

In [20]:
%%timeit
gcl(WF=wf, WS=WS, WD=WD, TI=TI, version='fort_gcl')


1 loop, best of 3: 2.08 s per loop

In [21]:
WS_ms,WD_ms=np.meshgrid(WS_cases,WD_cases)
WS=WS_ms.reshape(-1,1)*np.ones([1,wf.nWT])
WS=WS+np.random.normal(0,0.5,size=WS.shape)
WD=WD_ms.reshape(-1,1)*np.ones([1,wf.nWT])
WD=WD+np.random.normal(0,3,size=WS.shape)
TI=0.1*np.ones_like(WD)

In [22]:
%%timeit
gcl(WF=wf, WS=WS, WD=WD, TI=TI, version='fort_gclm')


1 loop, best of 3: 2.11 s per loop

In [23]:
noj(WS=WS, WD=WD, version='fort_noj')


Out[23]:
<fusedwake.noj.interface.NOJ at 0x7fd4c535c1d0>

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 [22]:
gcl(WS=WS, WD=WD).p_wt.sum()


Out[22]:
53223432.296349496

In [23]:
noj(WS=WS, WD=WD).p_wt.sum()


Out[23]:
48669770.342685707

In [24]:
gau(WS=WS, WD=WD).p_wt.sum()


Out[24]:
55126814.187098593

Once it has been run, all the inputs and outputs of the model are stored as object variable in the gcl instance.


In [25]:
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(WS=WS,WD=WD,TI=0.1,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[25]:
The total production of Horns Rev in this flow case is: $P_{GCL}(u=10.0, \theta=270.0)= 53.22$ MW, $P_{NOJ}(u=10.0, \theta=270.0)= 48.67$ MW

In [26]:
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(WS=WS,WD=WD,TI=0.1,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 [27]:
Markdown('The different version s of GCL currently available are: `{}`'.format('`, `'.join(gcl.versions)))


Out[27]:

The different version s of GCL currently available are: fort_gcl, fort_gclm, py_gcl_v0, py_gcl_v1

Checkout the docs online to see what is the difference.


In [28]:
powers = [gcl(WS=WS,WD=WD,TI=0.1,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[0]: .1f}$ m/s, WD $={WD[0]: .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[28]:

The different versions of GCL, NOJ and GAU give different results for the Horns Rev case at WS $= 10.0$ m/s, WD $= 270.0 ^o$:

  • fort_gcl: P = 53.223 MW
  • fort_gclm: P = 53.223 MW
  • py_gcl_v0: P = 53.223 MW
  • py_gcl_v1: P = 53.223 MW
  • fort_mod_noj: P = 39.593 MW
  • fort_mod_noj_av: P = 39.593 MW
  • fort_mod_noj_s: P = 39.593 MW
  • fort_noj: P = 48.670 MW
  • fort_noj_av: P = 48.670 MW
  • fort_noj_s: P = 48.670 MW
  • fort_gau: P = 55.127 MW
  • fort_gau_av: P = 55.127 MW
  • fort_gau_s: P = 55.127 MW

In [29]:
[min(powers), max(powers)]


Out[29]:
[39593323.445963286, 55126814.187098593]

The GCL has been developed using several different algorithms.


In [ ]:
WDs = range(0,360,1)
# The plot
iplot({'data':[{'x': WDs, 
                'y':[gcl(WS=8.0, version=v, WD=wd, TI=0.1).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 [ ]:


In [ ]: