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 modeling the floater, the cosimulation could also model the drivetrain, controller or generator in a more sophisticated 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 SIL 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 SIL interface and the simulation loop, when running the SIL 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()
Quick Start with the SIL Interface in Python
To test the SIL interface in Python you can simply start the python script sampleScript.py, which you find in the folder PythonInterface of the QBlade directory. This script imports the QBlade library, loads a turbine and simulation definition by from a QBlade project file (.qpr) and then runs a simulation loop for 500 timesteps while printing out some results and finally saving the finished simulation as a new project file. This samples are just meant as a quick demo on how to interface with QBlade in Python and does not serve any other particular purpose. Adapt as needed.
Interface Function Definitions
1#all variables and return values are c data types
2
3void setLibraryPath(char *str);
4
5void createInstance(int clDevice, int groupSize);
6void loadProject(char *str);
7void loadSimDefinition(char *str);
8void initializeSimulation();
9void runFullSimulation();
10
11void advanceController_at_num(double *vars, int num);
12bool advanceTurbineSimulation();
13
14void storeProject(char *str);
15void exportResults(int type, char *filepath, char* filename, char *filter);
16void closeInstance();
17void setLogFile(char *str);
18
19void loadTurbulentWindBinary(char *str);
20void 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);
21
22void setPowerLawWind(double windspeed, double horAngle, double vertAngle, double shearExponent, double referenceHeight);
23void setDebugInfo(bool isDebug);
24void setUseOpenCl(bool isOpenCl);
25void setAutoClearTemp(bool enabled);
26
27void setGranularDebug(bool dStr, bool dSim, bool dTurb, bool dCont, bool dSer);
28void setTimestepSize(double timestep);
29void setRPMPrescribeType_at_num(int type, int num);
30void setRPM_at_num(double rpm, int num);
31void setRampupTime(double time);
32void setInitialConditions_at_num(double yaw, double pitch, double azimuth, double rpm, int num);
33void setTurbinePosition_at_num(double x, double y, double z, double rotx, double roty, double rotz, int num);
34void setControlVars_at_num(double *vars, int num);
35void setExternalAction(char *action, char *id, double val, double pos, char *dir, bool isLocal, int num);
36void setMooringStiffness(double EA, double neutralStrain, int cabID, int num);
37
38void getWindspeed(double posx, double posy, double posz, double *velocity);
39void getWindspeedArray(double *posx, double *posy, double *posz, double *velx, double *vely, double *velz, int arraySize);
40void getTowerBottomLoads_at_num(double *loads, int num);
41void getTurbineOperation_at_num(double *vars, int num);
42void getWaveVelAccElev(double posx, double posy, double posz, double *vel, double *acc, double *elev);
43double getCustomData_at_num(char *str, double pos, int num);
44double getCustomSimulationTimeData(char *str);
Interface 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 setLibraryPath(char *atr)This function sets the location of the QBlade dll or shared object so that the QBlade instance knows about its location. This function must be called first so that the QBlade instance knows about the location of associated binaries (XFoil, TurbSim) and possibly license files.
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 simulation definition from a QBlade project (.qpr) 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. If the QBlade project contains one or more simulation definitions, the first simulation definition of the project file (in alphabetic order) is loaded into the SIL interface.
void loadSimDefinition(char *str)This function loads a simulation definition (.sim) file into the QBlade instance. The (.sim) files are ASCII files and any aspect of the simulation can be changed by modifying or preprocessing (.sim) files. 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 advances the controller shared library that is assigned to the selected turbine (argument num). When calling this function the controller outputs (gen. torque, blade pitch, etc.) are automatically applied to the turbine (no need to call
void setControlVars_at_num(double *vars, int num = 0)). The controller outputs are also returned in the vars array, and can be processed further:vars[0] = generator torque [Nm]
vars[1] = yaw angle [deg]
vars[2] = pitch BLD_1 [deg]
vars[3] = pitch BLD_2 [deg]
vars[4] = pitch BLD_3 [deg]
bool advanceTurbineSimulation()This function advances the turbine simulation for all turbines by one (time) step. Returns true if the step was successful.
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 exportResults(int type, char *filepath, char* filename, char *filter)This function saves the results of the current simulation to a file in the chosen format
type: specifies the export format: 0: QBlade ASCII; 1: HAWC2 ASCII; 2: HAWC2 BINARY; 3: OpenFAST BINARY
filepath: sets the folder in which the export file(s) will be stored
filename: sets the name of the export file, do not add an extension. The extension will be added automatically, based on the chosen export format type
filter: allows to point to a global result filter file (see Global Export Filter), specify full fill path and extension
void setLogFile(char *str)This functions sets the path to a log file that will be created to store the debug output. This is helpful when accessing the SIL interface from a tool that does not display standard output.
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 setGranularDebug(bool dStr, bool dSim, bool dTurb, bool dCont, bool dSer)This function enables a granular debug output.
dStr: enable structural model debug info
dSim: enable simulation debug info
dTurb: enable turbine debug info
dCont: enable controller debug info
dSer: enable serializer debug info
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 setRPM_at_num(double rpm, int num = 0)This function can be used to change the prescribed rpm for a turbine.
The parameter rpm sets the rotational rate.
The parameter num specifies the turbine instance for which the rpm is set.
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 to 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 are overwritten (in this way the control actions can also be 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 BLD_1 [deg];
vars[3] = pitch BLD_2 [deg];
vars[4] = pitch BLD_3 [deg];
void setExternalAction(char *action, char *id, double val, double pos, char *dir, bool isLocal, int num)This is a general purpose function that can be used to apply an external action to the simulated turbine.
The action can be of different types, defined by the parameter action. All of these actions are not accumulated and are reset at every timestep, or in other words if, for example, a constant mass should be assigned to the turbine it needs to be assigned with this function at every timestep or it will automatically be reset to zero. The different types are:
ADDMASS: adds mass to a location, in [kg]
ADDFORCE: adds a force to a location, in [N]
ADDTORQUE: adds a torque to a location, in [Nm]
SETLENGTH: sets the delta Length of a cable, in [m]
SETAFC: sets the state of an AFC element [-]
SETTORQUE: sets the generator torque, in [Nm]
SETYAW: sets the yaw angle, in [rad]
SETPITCH: sets the pitch angle for BLD_X, in [rad]
SETBRAKE: sets the brake activation (binary) [0-off, 1-on]
POSOFFSET : applies a position offset, in [m]
ROTOFFSET : applies a rotation offset, in [rad]
Some actions are applied to a certain location ID, indicated by the parameter id, the different locations are:
CAB_<X>: applies the action to the guycable with ID <X>. Actions on cables are: SETLENGTH, ADDMASS, ADDFORCE
MOO_<X>: applies the action to the mooring line with ID <X>. Actions on moorings are: SETLENGTH, ADDMASS, ADDFORCE
TWR: applies the action to the tower. Actions on the tower are: ADDFORCE, ADDTORQUE, ADDMASS
TRQ: applies the action to the torquetube. Actions on the torquetube are: ADDFORCE, ADDTORQUE, ADDMASS
BLD_<X>: applies the action to blade <X>. Actions on the blades are: ADDFORCE, ADDTORQUE, ADDMASS
STR_<X>_<Y>: applies the action to strut <X> of blade <Y>. Actions on the struts are: ADDFORCE, ADDTORQUE, ADDMASS
AFC_<X>_<Y>: applies the action to AFC <X> of blade <Y>. Actions on the AFC elements are: SETAFC
SUB_<X>: applies the action to the substructure element with ID <X>. Actions on the substructure elements are: ADDFORCE, ADDTORQUE, ADDMASS
JNT_<X>: applies the action to the substructure joint with ID <X>. Actions on the substructure joints are: ADDFORCE, ADDTORQUE, ADDMASS
HUB: applies the action to the free LSS hub node. Actions on the hub node are: ADDFORCE, ADDTORQUE, ADDMASS
HUBFIXED: applies the action to the fixed non-rotating hub node. Actions on the hub node are: ADDFORCE, ADDTORQUE, ADDMASS, POSOFFSET, ROTOFFSET
NAC: applies the action to the nacelle node, located at the tower top, yawing. Actions on the nacelle node are: ADDFORCE, ADDTORQUE, ADDMASS, POSOFFSET, ROTOFFSET
The remaining parameters are used to further define the action that is applied, their coordinate systems, etc.
The parameter val specifies the mass [kg], torque [Nm], force [N], delta length [m] or AFC state [-].
The parameter pos sets the normalized position [0-1] at which the mass, force or torque is applied. Only has an effect on elements, not on nodes.
The parameter dir specifies the direction along which the force or torque is applied, options are “X”, “Y”, “Z”.
The parameter isLocal specifies sets whether the direction is defined in global or local (element or node) coordinates.
The parameter num specifies the turbine instance to which the action is applied, if num is set to -1, the action is performed on the global mooring system.
void setMooringStiffness(double EA, double neutralStrain, int cabID, int num)This function can be used to dynamically adjust the stiffness (EA value) and neutral strain of a mooring line. In this way a nonlinear stiffness-strain relationship can be implemented.
The parameter EA: the longitudinal stiffness value [N/m]
The parameter neutralStrain: adjusts the cable’s rest length to be force-free at this strain, based on the initial length in the MOORMEMBERS table.
The parameter cabID: the cable id
The parameter num: the turbine instance for which the mooring line properties are changed, use -1 to apply this to a cable from the global mooringSystem
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 getWindspeedArray(double *posx, double *posy, double *posz, double *velx, double *vely, double *velz, int arraySize)This function can be called to get the current windspeed for an array of positions
posx = double array of position x-components
posy = double array of position y-components
posz = double array of position z-components
velx = double array of velocity x-components evaluated at the pos array
vely = double array of velocity y-components evaluated at the pos array
velz = double array of velocity z-components evaluated at the pos array
arraySize = the size of the pos and velocity arrays
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 getWaveVelAccElev(double posx, double posy, double posz, double *vel, double *acc, double *elev)This function can be used to obtain the current wave velocity, acceleration and elevation at an arbitrary position.
posx = global x position of query location
posy = global y position of query location
posz = global z position of query location
vel = double array that returns the velocity (size 3)
acc = double array that returns the acceleration (size 3)
elev = double pointer that returns the elevation
void getTurbineOperation_at_num(double *vars, int num = 0)This function returns useful turbine operational parameters from the selected turbine (argument num). Typically, this data is used to feed the logic of a supervisory wind turbine controller. 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 BLD_1 [deg]
vars[5] = pitch BLD_2 [deg]
vars[6] = pitch BLD_3 [deg]
vars[7] = oop blade root bending moment BLD_1 [Nm]
vars[8] = oop blade root bending moment BLD_2 [Nm]
vars[9] = oop blade root bending moment BLD_3 [Nm]
vars[10] = ip blade root bending moment BLD_1 [Nm]
vars[11] = ip blade root bending moment BLD_2 [Nm]
vars[12] = ip blade root bending moment BLD_3 [Nm]
vars[13] = tor blade root bending moment BLD_1 [Nm]
vars[14] = tor blade root bending moment BLD_2 [Nm]
vars[15] = tor blade root bending moment BLD_3 [Nm]
vars[16] = oop tip deflection BLD_1 [m]
vars[17] = oop tip deflection BLD_2 [m]
vars[18] = oop tip deflection BLD_3 [m]
vars[19] = ip tip deflection BLD_1 [m]
vars[20] = ip tip deflection BLD_2 [m]
vars[21] = ip tip deflection BLD_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 turbine simulation variable in QBlade. Specify the data name as is would appear in any QBlade graph as a char pointer. If you are requesting an aerodynamic ‘at section’ variable, for instance ‘Angle of Attack at 0.25c (at section) BLD_1 [deg]’ you can specify the normalized position along the blade length using the ‘pos’ variable. As an example, to get the AoA at 85% blade length from turbine 0, you would call the function the following way:
getCustomData_at_num("Angle of Attack at 0.25c (at section) BLD_1 [deg]", 0.85,0). Choosing num == -1 allows to extract any values that are stored in the Simulation Time Graph (such as for the global mooring system).double getCustomSimulationTimeData(char *str)This function can be used to access the current value from an arbitrary simulation time graph variable in QBlade.
void setAutoClearTemp(bool enabled)This function allows to disable the automatic deletion of the TEMP folder, in which temporary controller libraries are files are stored. This automatic deletion can be problematic in case of multi-instanced SIL libraries, hence thhis function can deactivate it.
Python Example: Running the QBlade Library
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 directory dll_directory is searched for shared library files. If a shared library file is found, the library is loaded by creating an object QBLIB, by calling the QBladeLibrary function that handles the library import. After the object QBLIB has been created 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.
1import os
2import sys
3from ctypes import *
4from QBladeLibrary import QBladeLibrary
5
6# Define the directory where the QBlade library is located
7dll_directory = "../"
8
9# On Windows systems, we update the PATH environment variable to include the QBlade directory.
10# This ensures that the required SSL libraries (e.g., libssl and libcrypto) are properly located and loaded.
11# If experiencing issues with this DLL in a Windows Python environment see:
12# https://docs.qblade.org/src/license/license_files.html#resolving-openssl-issues-on-windows
13if os.name == 'nt': # 'nt' indicates Windows
14 os.environ["PATH"] = os.path.abspath(dll_directory) + ";" + os.environ.get("PATH", "")
15
16# Search the directory below for library files matching the pattern QBlade*.dll or QBlade*.so
17dll_files = [f for f in os.listdir(dll_directory) if 'QBlade' in f and ('.dll' in f or '.so' in f)]
18
19# Check if any matching files are found
20if not dll_files:
21 print('No matching QBlade*.dll or QBlade*.so files found in the specified directory:',os.path.abspath(dll_directory))
22 sys.exit(1) # Exit the script with a non-zero status to indicate an error
23
24# Use the first matching file
25dll_file_path = os.path.join(dll_directory, dll_files[0])
26
27# Display the selected shared library file
28print(f'Using shared library file: {dll_file_path}')
29
30# Create an object of the class 'QBladeLibrary' that contains the API
31QBLADE = QBladeLibrary(dll_file_path)
32
33# Creation of a QBlade instance from the library
34QBLADE.createInstance(1,32)
35
36# Loading a project or sim-file, in this case the DTU_10MW_Demo project or simulation definition file
37#QBLADE.loadSimDefinition(b"./DTU_10MW_Demo.sim") #uncomment this line to load a simulation definition file
38QBLADE.loadProject(b"./NREL_5MW_Sample.qpr")
39
40# Initializing the sim and ramp-up phase, call before starting the simulation loop
41QBLADE.initializeSimulation()
42
43# We will run the simulation for 500 steps before storing the results
44number_of_timesteps = 500
45
46# Start of the simulation loop
47for i in range(number_of_timesteps):
48
49 #advance the simulation
50 success = QBLADE.advanceTurbineSimulation()
51
52 # Check if the simulation step was successful
53 if not success: # If success is False, exit the loop
54 print(f"Simulation failed at timestep {i}. Exiting loop.")
55 break
56
57 # Assign the c-type double array 'loads' with length [6], initialized with zeros
58 loads = (c_double * 6)(0,0,0,0,0,0)
59 # Retrieve the tower loads and store the in the array 'loads' by calling the function getTowerBottomLoads_at_num()
60 QBLADE.getTowerBottomLoads_at_num(loads,0)
61
62 # Uncomment the next line to try changing the position of the turbine dynamically
63 #QBLADE.setTurbinePosition_at_num(-0.2*i,0,0,0,i*0.1,i*0.1,0)
64
65 # 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
66 rpm = QBLADE.getCustomData_at_num(b"Rotational Speed [rpm]",0,0)
67 time = QBLADE.getCustomData_at_num(b"Time [s]",0,0) #example how to extract the variable 'Time' by name from the simulation
68 AoA = QBLADE.getCustomData_at_num(b"Angle of Attack at 0.25c (at section) BLD_1 [deg]",0.85,0) #example how to extract the variable 'Angle of Attack' by name at 85% blade length from the simulation
69
70 # Example how to extract a 3 length double array with the x,y,z windspeed components at a global position of x=-50,Y=0,Z=100m from the simulation
71 windspeed = (c_double * 3)(0,0,0)
72 QBLADE.getWindspeed(-50,0,100,windspeed)
73
74 # Assign the c-type double array 'ctr_vars' with length [5], initialized with zeros
75 ctr_vars = (c_double * 5)(0);
76 # Advance the turbine controller and store the controller signals in the array 'ctr_vars'
77 QBLADE.advanceController_at_num(ctr_vars,0)
78
79 # Pass the controller signals in 'ctr_vars' to the turbine by calling setControlVars_at_num(ctr_vars,0)
80 QBLADE.setControlVars_at_num(ctr_vars,0)
81
82 # Print out a few of the recorded data, in this case torque, tower bottom force along z (weight force) and rpm
83 print("Time:","{:3.2f}".format(time)," Windspeed:","{:2.2f}".format(windspeed[0])," Torque:","{:1.4e}".format(ctr_vars[0])," RPM:","{:2.2f}".format(rpm)," Pitch:","{:2.2f}".format(ctr_vars[2])," AoA at 85%:","{:2.2f}".format(AoA))
84
85# The simulation loop ends here after all 'number_of_timesteps have been evaluated
86
87# Storing the finished simulation in a project as NREL_5MW_Sample_completed, you can open this file to view the results of the simulation inside QBlade's GUI
88QBLADE.storeProject(b"./NREL_5MW_Sample_completed.qpr")
89
90# Storing the simulation results in QBlade ASCII format in the file NREL_5MW_Sample_results.txt
91QBLADE.exportResults(0,b"./",b"NREL_5MW_Sample_results",b"")
92
93# Unloading the qblade library
94QBLADE.unload()
Python Example: Definition of the QBladeLibrary Class
The script QBladeLibrary.py defines the class QBladeLibrary and loads the shared object. This script is just a suggestion on how to interface with the QBlade Library in Python and certainly there are more efficient ways of how to do this.
1from ctypes import *
2from typing import Dict, Any
3
4class QBladeLibrary:
5 def __init__(self, shared_lib_path: str):
6 """Initialize and load the QBlade shared library."""
7 self.lib_path = shared_lib_path
8 self.lib = None
9
10 # Define all functions with argument types and return types
11 self.functions: Dict[str, Dict[str, Any]] = {
12 "createInstance": {"argtypes": [c_int, c_int], "restype": c_void_p},
13 "closeInstance": {"argtypes": None, "restype": c_void_p},
14 "loadProject": {"argtypes": [c_char_p], "restype": c_void_p},
15 "loadSimDefinition": {"argtypes": [c_char_p], "restype": c_void_p},
16 "setOmpNumThreads": {"argtypes": [c_int], "restype": c_void_p},
17 "getCustomData_at_num": {"argtypes": [c_char_p, c_double, c_int], "restype": c_double},
18 "getCustomSimulationTimeData": {"argtypes": [c_char_p], "restype": c_double},
19 "getWindspeed": {"argtypes": [c_double, c_double, c_double, POINTER(c_double * 3)], "restype": c_void_p},
20 "getWindspeedArray": {"argtypes": [POINTER(c_double), POINTER(c_double), POINTER(c_double),POINTER(c_double), POINTER(c_double), POINTER(c_double), c_int],"restype": c_void_p,},
21 "storeProject": {"argtypes": [c_char_p], "restype": c_void_p},
22 "exportResults": {"argtypes": [c_int, c_char_p, c_char_p, c_char_p], "restype": c_void_p},
23 "setLibraryPath": {"argtypes": [c_char_p], "restype": c_void_p},
24 "setLogFile": {"argtypes": [c_char_p], "restype": c_void_p},
25 "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,],"restype": c_void_p,},
26 "setExternalAction": {"argtypes": [c_char_p, c_char_p, c_double, c_double, c_char_p, c_bool, c_int],"restype": c_void_p,},
27 "setMooringStiffness": {"argtypes": [c_double, c_double, c_int, c_int], "restype": c_void_p},
28 "loadTurbulentWindBinary": {"argtypes": [c_char_p], "restype": c_void_p},
29 "setTimestepSize": {"argtypes": [c_double], "restype": c_void_p},
30 "setInitialConditions_at_num": {"argtypes": [c_double, c_double, c_double, c_double, c_int],"restype": c_void_p,},
31 "setRPMPrescribeType_at_num": {"argtypes": [c_int, c_int], "restype": c_void_p},
32 "setRPM_at_num": {"argtypes": [c_double, c_int], "restype": c_void_p},
33 "setRampupTime": {"argtypes": [c_double], "restype": c_void_p},
34 "setTurbinePosition_at_num": {"argtypes": [c_double, c_double, c_double, c_double, c_double, c_double, c_int],"restype": c_void_p,},
35 "getTowerBottomLoads_at_num": {"argtypes": [POINTER(c_double * 6), c_int], "restype": c_void_p},
36 "initializeSimulation": {"argtypes": None, "restype": c_void_p},
37 "advanceTurbineSimulation": {"argtypes": None, "restype": c_bool},
38 "advanceController_at_num": {"argtypes": [POINTER(c_double * 5), c_int], "restype": c_void_p},
39 "setDebugInfo": {"argtypes": [c_bool], "restype": c_void_p},
40 "setUseOpenCl": {"argtypes": [c_bool], "restype": c_void_p},
41 "setGranularDebug": {"argtypes": [c_bool, c_bool, c_bool, c_bool, c_bool], "restype": c_void_p},
42 "setControlVars_at_num": {"argtypes": [POINTER(c_double * 5), c_int], "restype": c_void_p},
43 "getTurbineOperation_at_num": {"argtypes": [POINTER(c_double * 41), c_int], "restype": c_void_p},
44 "setPowerLawWind": {"argtypes": [c_double, c_double, c_double, c_double, c_double], "restype": c_void_p},
45 "runFullSimulation": {"argtypes": None, "restype": c_void_p},
46 "setAutoClearTemp": {"argtypes": [c_bool], "restype": c_void_p},
47 }
48
49 # Automatically load the library
50 self.load_library()
51
52 def load_library(self):
53 """Load the shared library and dynamically bind all functions."""
54 try:
55 self.lib = CDLL(self.lib_path)
56 print(f"Successfully loaded library from: {self.lib_path}")
57 except Exception as e:
58 raise RuntimeError(f"Could not load the library at {self.lib_path}: {e}")
59
60 # Bind functions dynamically
61 for func_name, config in self.functions.items():
62 try:
63 func = getattr(self.lib, func_name)
64 func.argtypes = config.get("argtypes")
65 func.restype = config.get("restype")
66 setattr(self, func_name, func) # Bind the function to the instance
67 except AttributeError as e:
68 raise RuntimeError(f"Failed to bind function '{func_name}': {e}")
69
70 # Call setLibraryPath after the library is loaded
71 try:
72 self.setLibraryPath(self.lib_path.encode('utf-8'))
73 print(f"Library path set to: {self.lib_path}")
74 except Exception as e:
75 raise RuntimeError(f"Failed to set library path: {e}")
76
77 def unload(self):
78
79 # Close the QBlade instance if it exists
80 try:
81 self.closeInstance()
82 print("QBlade instance closed.")
83 except Exception as e:
84 print(f"Warning: Failed to close QBlade instance: {e}")
85
86 # Clean up resources and unload the library
87 if self.lib:
88 del self.lib
89 self.lib = None
90 print("Library unloaded successfully.")
Matlab Example: Running the QBlade Library
This is an example for using the QBlade library within Matlab. It reproduces the Python example above. An object of the class QBladeLibrary, that contains the library interface is created and a simple simulation loop is started.
1%%
2clear all
3close all
4clc
5
6% Search the directory below for library files matching the pattern QBlade*.dll or QBlade*.so
7libSearchDirectory = '../';
8sharedLibFiles = dir(fullfile(libSearchDirectory, '*QBlade*'));
9sharedLibFiles = sharedLibFiles(contains({sharedLibFiles.name}, {'.dll', '.so'}));
10
11if isempty(sharedLibFiles)
12 fprintf('No matching QBlade*.dll files or QBlade*.so found in the specified directory.');
13 return;
14end
15
16% Use the first matching dll file and print out its name
17sharedLibFilePath = fullfile(libSearchDirectory, sharedLibFiles(1).name);
18fprintf('Using DLL file: %s\n', sharedLibFilePath);
19
20% Create an object of the class 'QBladeLibrary' that contains the API
21QBLADE = QBladeLibrary(sharedLibFilePath);
22
23QBLADE.createInstance(1,32);
24
25% Since matlab is unable to display the console output from the library, we
26% store the output in a log file
27QBLADE.setLogFile(fullfile('.', 'LogFile.txt'))
28
29QBLADE.loadProject('NREL_5MW_Sample.qpr')
30
31QBLADE.initializeSimulation()
32
33number_of_timesteps = 500;
34
35f = waitbar(0,'Initializing Simulation') ;
36
37for i = 1:1:number_of_timesteps
38
39 % Advance the simulation
40 success = QBLADE.advanceTurbineSimulation();
41
42 % Check if the simulation step was successful
43 if ~success
44 fprintf('Simulation failed at timestep %d. Exiting loop.\n', i);
45 break; % Exit the loop
46 end
47
48 % Assign the c-type double array 'loads' with length [6], initialized with zeros
49 loads = libpointer('doublePtr',zeros(6,1));
50 % Retrieve the tower loads and store them in the array 'loads' by calling the function getTowerBottomLoads_at_num()
51 QBLADE.getTowerBottomLoads_at_num(loads,0);
52 % De-referencing the 'loads' pointer and accessing its first value
53 loads.Value(1);
54
55 % Uncomment the next line to try changing the position of the turbine dynamically
56 %QBLADE.setTurbinePosition_at_num(-0.2*i,0,0,0,i*0.1,i*0.1,0)
57
58 % 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
59 rpm = QBLADE.getCustomData_at_num('Rotational Speed [rpm]',0,0);
60 t = QBLADE.getCustomData_at_num('Time [s]',0,0); %example how to extract the variable 'Time' by name from the simulation
61 AoA = QBLADE.getCustomData_at_num('Angle of Attack at 0.25c (at section) BLD_1 [deg]',0.85,0); %example how to extract the variable 'Angle of Attack' by name at 85% blade length from the simulation
62
63 % Example how to extract a 3 length double array with the x,y,z windspeed components at a global position of x=-50,Y=0,Z=100m from the simulation
64 windspeed = libpointer('doublePtr',zeros(3,1));
65 QBLADE.getWindspeed(-50,0,100,windspeed);
66
67 % Assign the c-type double array 'ctr_vars' with length [5], initialized with zeros
68 ctr_vars = libpointer('doublePtr',zeros(5,1));
69 % Advance the turbine controller and store the controller signals in the array 'ctr_vars'
70 QBLADE.advanceController_at_num(ctr_vars,0)
71
72 % Pass the controller signals in 'ctr_vars' to the turbine by calling setControlVars_at_num(ctr_vars,0)
73 QBLADE.setControlVars_at_num(ctr_vars,0)
74
75 fprintf('Time: %3.2f Windspeed: %2.2f Torque: %1.4e RPM: %2.2f Pitch: %2.2f AoA at 85%%: %2.2f\n',t,windspeed.Value(1),ctr_vars.Value(1),rpm,ctr_vars.Value(3),AoA);
76
77 waitbar(i/number_of_timesteps,f,'QBlade Simulation Running')
78
79end
80
81close(f)
82
83% Storing the finished simulation in a project as NREL_5MW_Sample_completed, you can open this file to view the results of the simulation inside QBlade's GUI
84QBLADE.storeProject('./NREL_5MW_Sample_completed.qpr')
85
86% Storing the simulation results in QBlade ASCII format in the file NREL_5MW_Sample_results.txt
87QBLADE.exportResults(0,'./','NREL_5MW_Sample_Results','')
88
89% Closing the instance of the shared library, if this fail it can lead to unexpected behavior
90QBLADE.closeInstance()
91
92% Unloading the shared library
93QBLADE.unload()
Matlab Example: Definition of the QBladeLibrary Class
This code shows how the class QBladeLibrary is defined in the Matlab environment. To load the library, a header file QBladeLibInclude.h is required that contains the C-type Interface Function Definitions of the QBlade shared object.
1classdef QBladeLibrary
2 properties
3 lib % DLL handle
4 end
5
6 methods
7 % Constructor
8 function obj = QBladeLibrary(dllPath)
9 % Validate DLL path
10 if ~isfile(dllPath)
11 error('QBladeLibrary:InvalidPath', 'The specified DLL path does not exist: %s', dllPath);
12 end
13
14 % Check if the library is already loaded
15 if libisloaded('QBLIB')
16 disp('Library "QBLIB" is already loaded. Unloading it first...');
17 unloadlibrary('QBLIB');
18 end
19
20 % Attempt to load the library
21 try
22 obj.lib = loadlibrary(dllPath, 'QBladeLibInclude.h', 'alias', 'QBLIB');
23 calllib('QBLIB', 'setLibraryPath', dllPath);
24 disp('Library loaded and path set successfully.');
25 catch ME
26 error('QBladeLibrary:LoadError', 'Failed to load library: %s\n%s', dllPath, ME.message);
27 end
28 end
29
30 % Destructor
31 function unload(obj)
32 % Unload Library
33 if libisloaded('QBLIB') % Check if the library is loaded
34 try
35 % Unload the library
36 unloadlibrary('QBLIB');
37 disp('Library unloaded successfully.');
38 catch ME
39 warning('Error during library unloading: %s', ME.message);
40 end
41 else
42 disp('Library is not loaded. No action taken.');
43 end
44 end
45
46 % Function to call library function
47 function createInstance(obj,clDevice,groupSize)
48 calllib('QBLIB', 'createInstance', clDevice, groupSize);
49 end
50
51 function loadProject(obj,str)
52 calllib('QBLIB', 'loadProject', str);
53 end
54
55 function loadSimDefinition(obj,str)
56 calllib('QBLIB', 'loadSimDefinition', str);
57 end
58
59 function setOmpNumThreads(obj,num)
60 calllib('QBLIB', 'setOmpNumThreads', num);
61 end
62
63 function initializeSimulation(obj)
64 calllib('QBLIB', 'initializeSimulation');
65 end
66
67 function runFullSimulation(obj)
68 calllib('QBLIB', 'runFullSimulation');
69 end
70
71 function advanceController_at_num(obj,vars,num)
72 calllib('QBLIB', 'advanceController_at_num', vars, num);
73 end
74
75 function success = advanceTurbineSimulation(obj)
76 success = calllib('QBLIB', 'advanceTurbineSimulation');
77 end
78
79 function storeProject(obj,str)
80 calllib('QBLIB', 'storeProject',str);
81 end
82
83 function exportResults(obj, type, filepath, filename, filter)
84 calllib('QBLIB', 'exportResults',type, filepath, filename, filter);
85 end
86
87 function closeInstance(obj)
88 calllib('QBLIB', 'closeInstance');
89 end
90
91 function setLogFile(obj,str)
92 calllib('QBLIB', 'setLogFile',str);
93 end
94
95 function loadTurbulentWindBinary(obj,str)
96 calllib('QBLIB', 'loadTurbulentWindBinary', str);
97 end
98
99 function addTurbulentWind(obj,windspeed, refheight, hubheight, dimensions, gridPoints, length, dT, turbulenceClass, turbulenceType, seed, vertInf, horInf, removeFiles)
100 calllib('QBLIB', 'addTurbulentWind', windspeed, refheight, hubheight, dimensions, gridPoints, length, dT, turbulenceClass, turbulenceType, seed, vertInf, horInf, removeFiles);
101 end
102
103 function setPowerLawWind(obj,windspeed,horAngle,vertAngle,shearExponent,referenceHeight)
104 calllib('QBLIB', 'setPowerLawWind',windspeed,horAngle,vertAngle,shearExponent,referenceHeight);
105 end
106
107 function setDebugInfo(obj,isDebug)
108 calllib('QBLIB', 'setDebugInfo', isDebug);
109 end
110
111 function setUseOpenCl(obj,isOpenCl)
112 calllib('QBLIB', 'setUseOpenCl', isOpenCl);
113 end
114
115 function setGranularDebug(obj,dStr,dSim,dTurb,dCont,dSer)
116 calllib('QBLIB', 'setGranularDebug',dStr,dSim,dTurb,dCont,dSer);
117 end
118
119 function setTimestepSize(obj,timestep)
120 calllib('QBLIB', 'setTimestepSize', timestep);
121 end
122
123 function setRPMPrescribeType_at_num(obj,type,num)
124 calllib('QBLIB', 'setRPMPrescribeType_at_num',type,num);
125 end
126
127 function setRPM_at_num(obj,rpm,num)
128 calllib('QBLIB', 'setRPM_at_num',rpm,num);
129 end
130
131 function setRampupTime(obj,time)
132 calllib('QBLIB', 'setRampupTime',time);
133 end
134
135 function setInitialConditions_at_num(obj,yaw,pitch,azimuth,rpm,num)
136 calllib('QBLIB', 'setInitialConditions_at_num',yaw,pitch,azimuth,rpm,num);
137 end
138
139 function setTurbinePosition_at_num(obj,x,y,z,xrot,yrot,zrot,num)
140 calllib('QBLIB', 'setTurbinePosition_at_num',x,y,z,xrot,yrot,zrot,num);
141 end
142
143 function setControlVars_at_num(obj,vars,num)
144 calllib('QBLIB', 'setControlVars_at_num',vars,num);
145 end
146
147 function setExternalAction(obj,action,id,val,pos,dir,isLocal,num)
148 calllib('QBLIB', 'setExternalAction',action,id,val,pos,dir,isLocal,num);
149 end
150
151 function setMooringStiffness(obj,EA,id,num)
152 calllib('QBLIB', 'setMooringStiffness',EA,neutralStrain,id,num);
153 end
154
155 function getWindspeed(obj,x,y,z,velocity)
156 calllib('QBLIB', 'getWindspeed',x,y,z,velocity);
157 end
158
159 function getWindspeedArray(obj,posx,posy,posz,velx,vely,velz,arraySize)
160 calllib('QBLIB', 'getWindspeedArray',posx,posy,posz,velx,vely,velz,arraySize);
161 end
162
163 function getTowerBottomLoads_at_num(obj,loads,num)
164 calllib('QBLIB', 'getTowerBottomLoads_at_num',loads,num);
165 end
166
167 function getTurbineOperation_at_num(obj,vars,num)
168 calllib('QBLIB', 'getTurbineOperation_at_num',vars,num);
169 end
170
171 function output = getCustomData_at_num(obj,var,i,j)
172 output = calllib('QBLIB','getCustomData_at_num',var,i,j);
173 end
174
175 function output = getCustomSimulationTimeData(obj,var)
176 output = calllib('QBLIB','getCustomSimulationTimeData',var);
177 end
178
179 function output = setAutoClearTemp(obj,var)
180 output = calllib('QBLIB','setAutoClearTemp',var);
181 end
182
183 end
184end