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.

Python Examples:

Python Example: Running the QBlade Library. Python Example: Definition of the QBladeLibrary Class.

Matlab Examples:

Matlab Example: Running the QBlade Library. Matlab Example: Definition of the QBladeLibrary Class.

Interface Function Definitions

Listing 138 : QBladeLibInclude.h
 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 functions void advanceController_at_num() and void 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.

Listing 139 : sampleScript.py
 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.

Listing 140 : QBladeLibrary.py
 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.

Listing 141 : sampleScript.m
 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.

Listing 142 : QBladeLibrary.m
  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