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 functionsvoid advanceController_at_num()
andvoid 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.
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
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