Unconstrained turbulence

The basic function for all turbulence generation is the gen_turb function, which can be used both with and without constraining time series. In this notebook, we will show it being used without any constraining time series. We will generate a turbulence box that follows IEC 61400-1 Ed. 3 specifications.

This notebook demonstrates the following:

To specify custom functions for the mean wind speed, turbulence standard deviation, or power spectrum as a function of \(y\) and \(z\), please see the related example in the sidebar.

Preliminaries: importing functions

We begin by importing the necessary functions so we can use them.

[1]:
%matplotlib inline

import matplotlib.pyplot as plt  # matplotlib for some plotting
import numpy as np  # numeric python functions
import pandas as pd  # need this to load our data from the csv files

from pyconturb import gen_turb, gen_spat_grid  # generate turbulence, useful helper
from pyconturb.sig_models import iec_sig  # IEC 61400-1 turbulence std dev
from pyconturb.spectral_models import kaimal_spectrum  # Kaimal spectrum
from pyconturb.wind_profiles import constant_profile, power_profile  # wind-speed profile functions

from _nb_utils import plot_slice

Defining the simulation points manually

The only required input to the gen_turb function is a Pandas dataframe that specifies the xyz locations of the simulation points to be simulated and the turbulence components to be simulated at each point. This dataframe is referred to as the spatial dataframe (spat_df), and it must have the following rows: * k (turbulence component, 0=\(u\), 1=\(v\) and 2=\(w\)), * x (along-wind location of the simulation point; positive is downwind), * y (lateral location of the simulation point; positive is to the left looking downwind) and * z (vertical location of the simulation point; positive is upwards).

The columns may be named for the user’s convenience, but PyConTurb does not require it.

Note that the x-value will often be 0 for standard simulations in a y-z plane that is parallel to the incoming wind.

As an example, consider the following spatial dataframe:

[2]:
spat_df = pd.DataFrame([[0, 1, 2],
                        [0, 0, 0],
                        [0, 0, -10],
                        [15, 15, 30]], index=['k', 'x', 'y', 'z'],
                      columns=['pointA_u', 'pointA_v', 'pointB_w'])  # column names are optional
spat_df  # let's look at it
[2]:
pointA_u pointA_v pointB_w
k 0 1 2
x 0 0 0
y 0 0 -10
z 15 15 30

This spatial dataframe will tell PyConTurb to simulate the \(u\) and \(v\) components at Point A, located at \((0, 0, 15)\), and the \(w\) component at Point B, located at \((0, -10, 30)\). We can run a quick simulation to see the result.

[3]:
turb_df = gen_turb(spat_df, T=60, dt=0.5, u_ref=10)
turb_df.head()  # show just the first few row
[3]:
u_p0 v_p0 w_p1
0.0 6.276168 -2.602351 -1.412486
0.5 7.105002 -0.019472 -0.998736
1.0 9.171079 1.110533 1.092972
1.5 7.740324 -0.351597 1.077588
2.0 6.194271 -1.021853 1.357559

The output is another Pandas dataframe, this one referred to as the turbulence dataframe. Each column of the dataframe corresponds to a different column in spat_df, and the index of the dataframe (i.e., the rows) are the time steps. As expected, the generated turb_df has the \(u\) and \(v\) components at Point A (renamed to 0) and the \(w\) component at Point B (renamed to 1).

Notes:
* Due to the underlying Fourier transformations used in the simulation method, the turbulence box is assumed to wrap after \(T\) seconds. In other words, \(u(t) = u(t+T)\).
* PyConTurb renames columns using information in spat_df. A column name u_p0 indicates the longitudinal component at Point 0. PyConTurb numbers the points by unique xyz locations, going from left to right. In other words, the leftmost unique xyz coordinate is Point 0, the next unique xyz location is Point 1, etc.

Here is a quick demo of some visualization/filtering abilities.

[4]:
turb_df.u_p0.plot();  # plot the u_p0 channel
../_images/notebooks_1_unconstr_time_series_9_0.png
[5]:
turb_df[(turb_df.index > 10) & ((turb_df.index < 30))].w_p1.max()  # max of w_p0 between 10 and 30 seconds
[5]:
2.4960612399067545
[6]:
turb_df.describe()  # show statistics for the turbulence dataframe
[6]:
u_p0 v_p0 w_p1
count 120.000000 1.200000e+02 1.200000e+02
mean 6.988271 -1.480297e-17 -7.401487e-18
std 2.096000 1.676800e+00 1.048000e+00
min 2.363388 -3.043431e+00 -2.594625e+00
25% 5.261389 -1.188543e+00 -6.965065e-01
50% 6.850676 -1.911416e-01 -2.081711e-01
75% 8.434220 1.283614e+00 7.343218e-01
max 11.486009 4.135347e+00 2.658873e+00
[7]:
turb_df.std()/turb_df.mean().u_p0  # turbulence intensities
[7]:
u_p0    0.299931
v_p0    0.239945
w_p1    0.149966
dtype: float64

Easily defining a y-z simulation grid

In most of our applications, our turbulence box is a regular, rectangular grid, and we want to simulate the same component(s) at each point. In this case, the built-in function gen_spat_grid is useful for generating a spatial grid. The function takes a 1D array of the lateral points you want to simulate and a 1D array of the vertical points you want to simulate. By default, the point numbering will place Point 0 at the most-negative \(y\) and \(z\) locations. The numbering will then proceed vertically before proceeding laterally.

Here are a few examples of using gen_spat_grid.

[8]:
y = [-10, 10]  # two lateral locations
z = [90]  # one vertical location
gen_spat_grid(y, z, comps=[0, 2])  # only want longitudinal + vertical components
[8]:
u_p0 w_p0 u_p1 w_p1
k 0 2 0 2
x 0 0 0 0
y -10 -10 10 10
z 90 90 90 90
[9]:
y = np.linspace(-50, 50, 11)  # 11 lateral points from -50 to 50 (center @ 0)
z = np.linspace(40, 160, 13)  # 13 vertical points from 40 to 160 (center @ 100)
spat_df = gen_spat_grid(y, z)  # if `comps` not passed in, assumes all 3 components are wanted
spat_df.head()  # look at the first few rows
[9]:
u_p0 v_p0 w_p0 u_p1 v_p1 w_p1 u_p2 v_p2 w_p2 u_p3 ... w_p139 u_p140 v_p140 w_p140 u_p141 v_p141 w_p141 u_p142 v_p142 w_p142
k 0.0 1.0 2.0 0.0 1.0 2.0 0.0 1.0 2.0 0.0 ... 2.0 0.0 1.0 2.0 0.0 1.0 2.0 0.0 1.0 2.0
x 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
y -50.0 -50.0 -50.0 -50.0 -50.0 -50.0 -50.0 -50.0 -50.0 -50.0 ... 50.0 50.0 50.0 50.0 50.0 50.0 50.0 50.0 50.0 50.0
z 40.0 40.0 40.0 50.0 50.0 50.0 60.0 60.0 60.0 70.0 ... 130.0 140.0 140.0 140.0 150.0 150.0 150.0 160.0 160.0 160.0

4 rows × 429 columns

This spatial dataframe can easily be passed into gen_turb to simulate unconstrained turbulence at the specified grid.

[10]:
turb_df = gen_turb(spat_df, T=10, dt=2, u_ref=10)

We can use the matplotlib function imshow to visualize slices of the turbulence at certain times.

[11]:
t = 0
ax = plot_slice(spat_df, turb_df, val=t)
ax.set_title(f'Turbulence slice at t = {t}');
../_images/notebooks_1_unconstr_time_series_19_0.png

Using built-in profile functions

Until this point, we have used the default parameters in gen_turb, which has hidden from view many things. Something that many users may find useful (or want to change), is how the values for the mean wind speed, turbulence standard deviation, and power spectra change with \(y\)- and \(z\)-coordinates. These functions are collectively called the profile functions.

We start by using Jupyter’s built-in help function to get the list of optional arguments for gen_turb. (Note that this list does not include keyword arguments for any subfunctions.)

[12]:
?gen_turb

The profile functions are wsp_func, sig_func and spec_func. If no values are passed in for these functions (which is what we had in the earlier examples), they will by default take the IEC 61400-1 values for an unconstrainted simulation. More details on the profile functions can be found in their respective documentation sections.

Below are examples of how you manually specify the profile functions if you don’t want the IEC defaults.

[13]:
y, z = [-10, 0, 10], [70, 80, 90]  # first define our turbulence grid locations
spat_df = gen_spat_grid(y, z)

Wind speed profile

The values for the profiles functions can be placed in a single keyword-argument dictionary, like in kwargs below. The necessary arguments for each profile function are listed in the function’s documentation function.

[14]:
# specify mean wind speed profile function (constant_profile or power_profile)
wsp_func = constant_profile
# define simulation arguments
kwargs = {'u_ref': 6, 'z_ref': 80, 'T': 60, 'dt': 1}
# simulate turbulence
turb_df = gen_turb(spat_df, wsp_func=wsp_func, **kwargs)
# plot the spatial variation of the mean wind speed
ax = plot_slice(spat_df, turb_df, val='mean')
ax.set_title(f'Mean of turbulence box');
../_images/notebooks_1_unconstr_time_series_25_0.png

Turbulence standard deviation profile

The main keyword argument for the IEC standard deviation is the turbulence class, turb_class.

Note that the standard deviation will not match the theory at all spatial points due to the spatial correlation procedure inherent withing the KSEC method.

[15]:
# specify standard deviation profile function
sig_func = iec_sig
# define simulation arguments
kwargs = {'turb_class': 'C', 'T': 60, 'dt': 1, 'u_ref': 10}
# simulate turbulence
turb_df = gen_turb(spat_df, sig_func=sig_func, **kwargs)
# plot the spatial variation of the mean wind speed
ax = plot_slice(spat_df, turb_df, val='std')
ax.set_title(f'Std. dev. of turbulence box');
../_images/notebooks_1_unconstr_time_series_27_0.png

Power spectrum

Specify the power spectrum profile using the spec_func input argument.

[16]:
# specify power spectrum profile function
spec_func = kaimal_spectrum
# define simulation arguments
kwargs = {'u_ref': 12, 'T': 60, 'dt': 1, 'u_ref': 10}
# simulate turbulence
turb_df = gen_turb(spat_df, spec_func=spec_func, **kwargs)  # no plot, just demo

Specifying custom profile functions

To specify your own functions for the mean wind speed, turbulence standard deviation, or power spectrum as a function of \(y\) and \(z\), please see the related example in the sidebar.