Software in Loop (SIL) Overview

The Software in Loop interface in QBlade provides an easy way of controlling the whole simulation loop of a wind turbine and enable cosimulation within other software frameworks or scripting languages, such as Python or Matlab. To enable this functionality QBlade is compiled as a Dynamic Link Library (.dll, Windows) or as a Shared Object (.so, Unix) and the relevant functionality is exported into an interface.

Through the different functions that are exported the user can explicitly import QBlade projects (.qpr) or Simulation Definition Files (.sim) and then progress the simulation incrementally in time by calling the advanceTurbineSimulation() function. At every timestep it is possible to inquire any variable of the simulation and control various aspects of the simulation in response, such as changing the inflow conditions, changing the position and orientation of the turbine or controlling the various control actuators (pitch, yaw, generator torque) of the wind turbine. A possible application for the SIL interface is a cosimulation, where the turbine floater can be modeled within a specialized software that is coupled with QBlade through force/position intercommunication. Instead of modelling the floater, the cosimulation could also model the drivetrain, controller or generator in a more sphisticated way. Another application is controller development, where the controller can run in a scripting language (such as Simulink), receiving custom signals from the simulation and controlling the turbine actuators in response. When running a multi-turbine simulation within the SLI interface the user may control each simulated turbine individually, enabling the modeling of global wind park controllers.

In general, the high level overview of the SLI interface and the simulation loop, when running the SLI in an external language, looks as follows:

loadLibrary()
createInstance()
loadProject()
initializeSimulation()

#start of the simulation loop

for i in range(end):

        ...do something...
        advanceTurbineSimulation()

#end of the simulation loop

storeProject()
closeInstance()

Interface Function Definitions

void createInstance(int clDevice = 0, int groupSize = 32)
void loadProject(char *str)
void loadSimDefinition(char *str)
void initializeSimulation()
void runFullSimulation()

void advanceController_at_num(double *vars, int num = 0)
void advanceTurbineSimulation()

void storeProject(char *str)
void closeInstance()

void loadTurbulentWindBinary(char *str)
void addTurbulentWind(double windspeed, double refheight, double hubheight, double dimensions, int gridPoints, double length, double dT, char *turbulenceClass, char *turbulenceType, int seed, double vertInf, double horInf, bool removeFiles = false)

void setPowerLawWind(double windspeed, double horAngle, double vertAngle, double shearExponent, double referenceHeight)
void setDebugInfo(bool isDebug)
void setLibraryPath(char *str)
void setTimestepSize(double timestep)
void setRPMPrescribeType_at_num(int type, int num = 0)
void setRampupTime(double time)
void setInitialConditions_at_num(double yaw, double pitch, double azimuth, double rpm, int num = 0)
void setTurbinePosition_at_num(double x, double y, double z, double rotx, double roty, double rotz, int num = 0)
void setControlVars_at_num(double *vars, int num = 0)

void getWindspeed(double x, double y, double z, double *velocity)
void getTowerBottomLoads_at_num(double *loads, int num)
void getTurbineOperation_at_num(double *vars, int num = 0)
double getCustomData_at_num(char *str, double pos = 0, int num = 0)

Function Documentation

In the following, the functionality that is exported from the QBlade dll or shared object is described and the function arguments and return types are given. ALl functions with the appendix _at_num affect the turbine specified by the argument num - this has only an effect for multi turbine simulations.

void createInstance(int clDevice = 0, int groupSize = 32)

This function creates a new instance of QBlade. The OpenCL device and the OpenCL group-size can both be specified in the arguments. Calling this function is mandatory!

void loadProject(char *str)

This function loads a qblade (.qpr) project into the QBlade instance. The file location has to be passed as a char pointer. File names can be passed as absolute or as relative paths.

void loadSimDefinition(char *str)

This function loads a simulation definition (.sim) file into the QBlade instance. The file location has to be passed as a char pointer. File names can be passed as absolute or as relative paths.

void initializeSimulation()

This function initializes the simulation, e.g. the simulation is reset and structural ramp-up is carried out.

void runFullSimulation()

This function runs all timesteps for all turbines of the simulation as defined in the simulation object. This is equivalent to pressing the Start Simulation button in QBlade`s GUI. This function needs to be called after void initializeSimulation(). When calling this function it is not possible to interact with the simulation before it is finished. To interact with the simulation you need to create your own simulation loop and call the functions void advanceController_at_num() and void advanceTurbineSimulation() at every timestep.

void advanceController_at_num(double *vars, int num = 0)

This function advancess the controller dll of the selected turbine (argument num). The controller outputs are automatically applied to the turbine actuators and to the generator. The controller ouputs are also returned in the vars array:

  • vars[0] = generator torque [Nm]

  • vars[1] = yaw angle [deg]

  • vars[2] = pitch blade 1 [deg]

  • vars[3] = pitch blade 2 [deg]

  • vars[4] = pitch blade 3 [deg]

void advanceTurbineSimulation()

This function advances the turbine simulation for all turbines and finishes the timestep.

void storeProject(char *str)

This functions stores a project file. The file location has to be passed as a char pointer. File names can be passed as absolute or as relative paths.

void closeInstance()

This function closes the instance of QBlade and frees the memory.

void loadTurbulentWindBinary(char *str)

This function allows to load a turbulent windfield that is stored in binary format. The file location has to be passed as a char pointer. File names can be passed as absolute or as relative paths.

void addTurbulentWind(double windspeed, double refheight, double hubheight, double dimensions, int gridPoints,double length, double dT, char *turbulenceClass, char *turbulenceType, int seed, double vertInf, double horInf, bool removeFiles = false)

This function allows to define and add a turbulent windfield (using TurbSim) to the simulation. If a turbulent windfield is used the function setPowerLawWind() has no effect. It uses the following parameters:

  • windspeed: the mean windspeed at the reference height [m/s]

  • refheight: the reference height [m]

  • hubheight: the hubheight, more specifically the height of the windfield center [m]

  • dimensions: the y- and z- dimensions of the windfield in meters [m]

  • length: the simulated length of the windfield in seconds [s]

  • dT: the temporal resolution of the windfield [s]

  • turbulenceClass: the turbulence class, can be “A”, “B” or “C”

  • turbulenceType: the turbulence type, can be “NTM”, “ETM”, “xEWM1” or “xEWM50” - where x is the turbine class (1,2 or 3)

  • seed: the random seed for the turbulent windfield

  • vertInf: vertical inflow angle in degrees [deg]

  • horInf: horizontal inflow angle in degrees [deg]

void setPowerLawWind(double windspeed, double horAngle, double vertAngle, double shearExponent, double referenceHeight)

This function can be called before or at any time after the simulation has been initialized with initializeSimulation() to statically or dynamically change the inflow conditions. It defines a power law wind profile (https://en.wikipedia.org/wiki/Wind_profile_power_law) and its inflow direction. The arguments for this function are:

  • windspeed: constant windspeed in m/s [m/s]

  • horAngle: the horizontal inflow angle in degrees [deg]

  • vertAngle: the vertical inflow angle in degrees [deg]

  • shearExponent: this is the exponent for the power law boundary layer profile, if this is set to 0 the windspeed is constant with height [-]

  • referenceHeight: this is the height at which the velocity in the boundary layer is the defined windspeed, usually set to the hubheight [m]

  • exemplary call: addTurbulentWind(12,115,115,220,20,60,0.1,”A”,”NTM”,1000000,0,0);

void setDebugInfo(bool isDebug)

This function enables the debug output if set to true.

void setLibraryPath(char *atr)

This function sets the location of the QBlade dll or shared object so that the QBlade instance knows about its location. Calling this function is mandatory so that the QBlade instance knows about the location of associated binaries (XFoil, TurbSim) and possibly license files.

void setTimestepSize(double timestep)

This function can be used to set the timestep size (in [s]) if the user wants to change this value from the project or simulation definition file. It needs to be called before initializeSimulation().

void setRPMPrescribeType_at_num(int type, int num = 0)

This function can be used to change the rpm prescribe type. It needs to be called before initializeSimulation().

  • 0 - RPM prescribed during ramp-up only

  • 1 - RPM prescribed for the whole simulation

  • 3 - no prescribed RPM

void setRampupTime(double time)

This function can be used to change the ramp-up time from the value specified in the project or simulation file, call before initializeSimulation().

void setInitialConditions_at_num(double yaw, double pitch, double azimuth, double rpm, int num = 0)

This function may be used to set the turbine initial yaw [deg], collective pitch [deg], azimuthal angle [deg] and initial rotSpeed [rpm] to a value different than specified in the QBlade project or simulation input file. It needs to be called before initializeSimulation().

void setTurbinePosition_at_num(double x, double y, double z, double rotx, double roty, double rotz, int num = 0)

This function sets the turbine tower bottom x, y and z position [m], and xrot, yrot zrot rotation [deg]. It can be called before initializeSimulation() if the turbine position should be offset initially or during the simulation loop if it should be changed dynamically, for example during cosimulation with a hydrodynamics software that models the floater.

void setControlVars_at_num(double *vars, int num = 0)

This function applies the control actions of the selected turbine (argument num) for torque, pitch and yaw angle. If it is called after the function advanceController() the control actions from the controller can be overwritten (or modified). The following data needs to be passed in the array vars.

  • vars[0] = generator torque [Nm];

  • vars[1] = yaw angle [deg];

  • vars[2] = pitch blade 1 [deg];

  • vars[3] = pitch blade 2 [deg];

  • vars[4] = pitch blade 3 [deg];

void getWindspeed(double x, double y, double z, double *velocity)

This function can be called to get the current windspeed at the chosen position (x,y,z), returns the windspeed vector in the double pointer velocity.

  • velocity[0] = x-component [m/s];

  • velocity[1] = y-component [m/s];

  • velocity[2] = z-component [m/s];

void getTowerBottomLoads_at_num(double *loads, int num)

This function can be used to obtain the loads at the bottom of the tower. The main purpose of this is to be used in conjunction with the setTurbinePosition_at_num() function for force/position cosimilation with a hydrodynamics solver that is modeling the floater.

void getTurbineOperation_at_num(double *vars, int num = 0)

This function returns typically useful turbine operational parameters of the selected turbine (argument num). The data is returned in the array vars which has the following content:

  • vars[0] = rotational speed [rad/s]

  • vars[1] = power [W]

  • vars[2] = Abs HH wind velocity [m/s]

  • vars[3] = yaw angle [deg]

  • vars[4] = pitch blade 1 [deg]

  • vars[5] = pitch blade 2 [deg]

  • vars[6] = pitch blade 3 [deg]

  • vars[7] = oop blade root bending moment blade 1 [Nm]

  • vars[8] = oop blade root bending moment blade 2 [Nm]

  • vars[9] = oop blade root bending moment blade 3 [Nm]

  • vars[10] = ip blade root bending moment blade 1 [Nm]

  • vars[11] = ip blade root bending moment blade 2 [Nm]

  • vars[12] = ip blade root bending moment blade 3 [Nm]

  • vars[13] = tor blade root bending moment blade 1 [Nm]

  • vars[14] = tor blade root bending moment blade 2 [Nm]

  • vars[15] = tor blade root bending moment blade 3 [Nm]

  • vars[16] = oop tip deflection blade 1 [m]

  • vars[17] = oop tip deflection blade 2 [m]

  • vars[18] = oop tip deflection blade 3 [m]

  • vars[19] = ip tip deflection blade 1 [m]

  • vars[20] = ip tip deflection blade 2 [m]

  • vars[21] = ip tip deflection blade 3 [m]

  • vars[22] = tower top acceleration in global X [m/s^2]

  • vars[23] = tower top acceleration in global Y [m/s^2]

  • vars[24] = tower top acceleration in global Z [m/s^2]

  • vars[25] = tower top fore aft acceleration [m/s^2]

  • vars[26] = tower top side side acceleration [m/s^2]

  • vars[27] = tower top X position [m]

  • vars[28] = tower top Y position [m]

  • vars[29] = tower bottom force along global X [Nm]

  • vars[30] = tower bottom force along global Y [Nm]

  • vars[31] = tower bottom force along global Z [Nm]

  • vars[32] = tower bottom bending moment along global X [Nm]

  • vars[33] = tower bottom bending moment along global Y [Nm]

  • vars[34] = tower bottom bending moment along global Z [Nm]

  • vars[35] = current time [s]

  • vars[36] = azimuthal position of the LSS [deg]

  • vars[37] = azimuthal position of the HSS [deg]

  • vars[38] = HSS torque [Nm]

  • vars[39] = wind speed at hub height [m/s]

  • vars[40] = HH wind velocity x [m/s]

  • vars[41] = HH wind velocity y [m/s]

  • vars[42] = HH wind velocity z [m/s]

double getCustomData_at_num(char *str, double pos = 0, int num = 0)

This function can be used to access the current value from an arbitrary simulation variable in QBlade. Specify the data name as is would appear in any QBlade graph as a char pointer.

Sample Script Running the SLI in Python

The following code example (sampleScript.py) is an example for a light weight Python script that utilizes the QBlade SIL interface. There are many ways to improve this, e.g. the library could be loaded into multiple separate processes for parallelization and sophisticated algorithms could be implemented instead of using a standard controller. This exemplary script only uses a small amount of the functionality that is exported by the QBlade library for purely illustrative purposes.

In this Python example script the library is loaded by calling the script QBladeLIBImport.py, which handles the library import. After QBladeLIBImport.py has been imported (import QBladeLIBImport as QBLIB) and the QBlade library has been loaded :code:` QBLIB.loadLibrary(“./QBladeCE_2.0.5.2.dll”)` any function of the QBlade library can be accessed by calling QBLIB.function_XYZ(). All lines of code that are needed to load the QBlade library into python are highlighted in the example below.

After the QBlade library has been loaded a simulation object is imported and a simulation is started over 500 timesteps. During the simulation loop different data is obtained from the turbine simulation. The turbine controller that is defined in the simulation object is advanced and its signals are passed to the turbine actuators. After the simulation loop has finished the simulation is stored into a project file, for later inspection, and the library is unloaded from python.

Listing 1 : sampleScript.py
 1from ctypes import *
 2import QBladeLibImport as QBLIB
 3
 4#loading the QBlade DLL from the folder below the location of sampleScript.py, if calling this script not from the script folder directly you need to use an absolute path instead!
 5QBLIB.loadLibrary("../QBladeCE_2.0.5.2.dll")
 6
 7#creation of a QBlade instance from the DLL
 8QBLIB.createInstance(1,32)
 9
10#loading a project or sim-file, in this case the DTU_10MW_Demo project or simulation definition file
11#QBLIB.loadSimDefinition(b"./DTU_10MW_Demo.sim") #uncomment this line to load a simulation definition file
12QBLIB.loadProject(b"./NREL_5MW_Sample.qpr")
13
14#initializing the sim and ramp-up phase, call before starting the simulation loop
15QBLIB.initializeSimulation()
16
17#we will run the simulation for 500 steps before storing the results
18number_of_timesteps = 500
19
20#start of the simulation loop
21for i in range(number_of_timesteps):
22
23        #assign the c-type double array 'loads' with length [6], initialized with zeros
24        loads = (c_double * 6)(0)
25        #retrieve the tower loads and store the in the array 'loads' by calling the function getTowerBottomLoads_at_num()
26        QBLIB.getTowerBottomLoads_at_num(loads,0)
27
28        #uncomment the next line to try changing the position of the turbine dynamically
29        #QBLIB.setTurbinePosition_at_num(-0.2*i,0,0,0,i*0.1,i*0.1,0)
30
31        #example how to extract a variable by name from the simulation, call as often as needed with different variable names, extracting rpm and time in the lines below
32        rpm = QBLIB.getCustomData_at_num(b"Rotational Speed [rpm]",0,0)
33        time = QBLIB.getCustomData_at_num(b"Time [s]",c_double(0), c_int(0)) #example how to extract a variable by name from the simulation
34
35        #assign the c-type double array 'ctr_vars' with length [6], initialized with zeros
36        ctr_vars = (c_double * 5)(0);
37        #advance the turbine controller and store the controller signals in the array 'ctr_vars'
38        QBLIB.advanceController_at_num(ctr_vars,0)
39
40        #passthe controller signals in 'ctr_vars' to the turbine by calling setControlVars_at_num(ctr_vars,0)
41        QBLIB.setControlVars_at_num(ctr_vars,0)
42
43        #print out a few of the recorded data, in this case torque, tower bottom force along z (weight force) and rpm
44        print("Time:","{:3.2f}".format(time),"  Torque:","{:1.4e}".format(ctr_vars[0]),"    RPM:","{:2.2f}".format(rpm),"   Pitch:","{:2.2f}".format(ctr_vars[2]))
45
46
47        #advance the simulation
48        QBLIB.advanceTurbineSimulation()
49
50#the simulation loop ends here after all 'number_of_timesteps have been evaluated
51
52#storing the finished simulation in a project as DTU_10MW_Demo_finished.qpr, you can open this file to view the results of the simulation inside QBlade's GUI
53QBLIB.storeProject(b"./NREL_5MW_Sample_completed.qpr")
54
55#closing the QBlade instance to free memory
56QBLIB.closeInstance()
57
58#unloading the QBlade library
59del QBLIB.QB_LIB

Sample Script Loading the SLI in Python

The script QBladeLibImport.py which loads the QBlade library into Python and imports its functionality is shown below. Since the QBlade library is loaded upon calling the function loadLibrary() defined in the script, the imported library functions are defined as global, to make them available outside of the function scope of loadLibrary(). This script is imported into the

Listing 2 : QBladeLibImport.py
  1from ctypes import *
  2
  3#this is the first function that is called in sampleScript.py to load the QBlade library
  4def loadLibrary(shared_lib_path):
  5
  6        global QB_LIB
  7
  8        try:
  9                #loading the library into python here, using ctypes
 10                QB_LIB = CDLL(shared_lib_path)
 11                print("Successfully loaded ", QB_LIB)
 12        except Exception as e:
 13                print(e)
 14
 15        #setting the shared_lib_path, so that the library knows about its location!
 16        QB_LIB.setLibraryPath(shared_lib_path.encode('utf-8'))
 17
 18        #the imported functions are defined below
 19
 20        global loadProject
 21        loadProject = QB_LIB.loadProject
 22        loadProject.argtype = c_char_p
 23        loadProject.restype = c_void_p
 24
 25        global loadSimDefinition
 26        loadSimDefinition = QB_LIB.loadSimDefinition
 27        loadSimDefinition.argtype = c_char_p
 28        loadSimDefinition.restype = c_void_p
 29
 30        global getCustomData_at_num
 31        getCustomData_at_num = QB_LIB.getCustomData_at_num
 32        getCustomData_at_num.argtypes = [c_char_p, c_double, c_int]
 33        getCustomData_at_num.restype = c_double
 34
 35        global getWindspeed
 36        getWindspeed = QB_LIB.getWindspeed
 37        getWindspeed.argtypes = [c_double, c_double, c_double, c_double * 3]
 38        getWindspeed.restype = c_void_p
 39
 40        global storeProject
 41        storeProject = QB_LIB.storeProject
 42        storeProject.argtype = c_char_p
 43        storeProject.restype = c_void_p
 44
 45        global setLibraryPath
 46        setLibraryPath = QB_LIB.createInstance
 47        setLibraryPath.argtype = c_char_p
 48        setLibraryPath.restype = c_void_p
 49
 50        global createInstance
 51        createInstance = QB_LIB.createInstance
 52        createInstance.argtypes = [c_int, c_int]
 53        createInstance.restype = c_void_p
 54
 55        global closeInstance
 56        closeInstance = QB_LIB.closeInstance
 57        closeInstance.restype = c_void_p
 58
 59        global addTurbulentWind
 60        addTurbulentWind = QB_LIB.addTurbulentWind
 61        addTurbulentWind.argtypes = [c_double, c_double, c_double, c_double, c_int, c_double, c_double, c_char_p, c_char_p, c_int, c_double, c_double, c_bool]
 62        addTurbulentWind.restype = c_void_p
 63
 64        global loadTurbulentWindBinary
 65        loadTurbulentWindBinary = QB_LIB.loadTurbulentWindBinary
 66        loadTurbulentWindBinary.argtype = c_char_p
 67        loadTurbulentWindBinary.restype = c_void_p
 68
 69        global setTimestepSize
 70        setTimestepSize = QB_LIB.setTimestepSize
 71        setTimestepSize.argtype = c_double
 72        setTimestepSize.restype = c_void_p
 73
 74        global setInitialConditions_at_num
 75        setInitialConditions_at_num = QB_LIB.setInitialConditions_at_num
 76        setInitialConditions_at_num.argtypes = [c_double, c_double, c_double, c_double, c_int]
 77        setInitialConditions_at_num.restype = c_void_p
 78
 79        global setRPMPrescribeType_at_num
 80        setRPMPrescribeType_at_num = QB_LIB.setRPMPrescribeType_at_num
 81        setRPMPrescribeType_at_num.argtypes = [c_int, c_int]
 82        setRPMPrescribeType_at_num.restype = c_void_p
 83
 84        global setRampupTime
 85        setRampupTime = QB_LIB.setRampupTime
 86        setRampupTime.argtype = c_double
 87        setRampupTime.restype = c_void_p
 88
 89        global setTurbinePosition_at_num
 90        setTurbinePosition_at_num = QB_LIB.setTurbinePosition_at_num
 91        setTurbinePosition_at_num.argtypes = [c_double, c_double, c_double, c_double, c_double, c_double, c_int]
 92        setTurbinePosition_at_num.restype = c_void_p
 93
 94        global getTowerBottomLoads_at_num
 95        getTowerBottomLoads_at_num = QB_LIB.getTowerBottomLoads_at_num
 96        getTowerBottomLoads_at_num.argtypes = [c_double * 6, c_int]
 97        getTowerBottomLoads_at_num.restype = c_void_p
 98
 99        global initializeSimulation
100        initializeSimulation = QB_LIB.initializeSimulation
101        initializeSimulation.restype = c_void_p
102
103        global advanceTurbineSimulation
104        advanceTurbineSimulation = QB_LIB.advanceTurbineSimulation
105        advanceTurbineSimulation.restype = c_void_p
106
107        global advanceController_at_num
108        advanceController_at_num = QB_LIB.advanceController_at_num
109        advanceController_at_num.argtypes = [c_double * 5, c_int]
110        advanceController_at_num.restype = c_void_p
111
112        global setDebugInfo
113        setDebugInfo = QB_LIB.setDebugInfo
114        setDebugInfo.argtype = c_bool
115        setDebugInfo.restype = c_void_p
116
117        global setControlVars_at_num
118        setControlVars_at_num = QB_LIB.setControlVars_at_num
119        setControlVars_at_num.argtypes = [c_double * 5, c_int]
120        setControlVars_at_num.restype = c_void_p
121
122        global getTurbineOperation_at_num
123        getTurbineOperation_at_num = QB_LIB.getTurbineOperation_at_num
124        getTurbineOperation_at_num.argtypes = [c_double * 41, c_int]
125        getTurbineOperation_at_num.restype = c_void_p
126
127        global setPowerLawWind
128        setPowerLawWind = QB_LIB.setPowerLawWind
129        setPowerLawWind.argtypes = [c_double, c_double, c_double, c_double, c_double]
130        setPowerLawWind.restype = c_void_p
131
132        global runFullSimulation
133        runFullSimulation = QB_LIB.runFullSimulation
134        runFullSimulation.restype = c_void_p