Forum

Important Notice for New User Registrations

To combat an increasing number of spam and bot registrations, we now manually approve all new user registrations. While this may cause a delay until your account is approved, this step is essential to ensure the quality and security of this forum.

To help us verify your registration as legitimate, please use a clear name as user name or an official email address (such as a work, university, or similar address). If you’re concerned that we may not recognize your registration as non-spam, feel free to email us at with a request to approve your username.

Please or Register to create posts and topics.

The structural model diverged when a bladed-type controller was added

PreviousPage 2 of 3Next

Hello David,

Thank you again for your reply. I tried to run simulations in the SIL interface, what I saw is that only the very first value (after the ramp-up, first time-step) of the generator torque is correct. After that the vars variable only contain nan. I will try to smooth the generator torque value considering the first value I get. Do you have an idea of what could cause these NaN values ? How can i do a smooth ramp-up using setControlVars_at_num between two values A and B and let the controller takes the lead after that ?

Thank you in advance,

Best regards,

Hugo

Uploaded files:
  • You need to login to have access to uploads.

Hi Hugo,

if the ctr_vars array contains NaN values after calling advanceController_at_num(ctr_vars, 0), it suggests that your custom controller is returning these NaN values. This is likely to cause the simulation to crash.

To generate a ramp or interpolate between two values before passing control variables to the turbine, you can modify the content of the ctr_vars array as needed. For example, you could calculate a ramp based on the timestep or another factor, and then assign these values to the ctr_vars array before passing it to the turbine using setControlVars_at_num(ctr_vars, 0).

BR,

David

Hello David,

Thank you for your reply.

That’s what I thought about the NaN but what I don’t understand is that we have previously checked the operation of this DLL library and it seems to be working and not returning any NaN. Could this be due to a different definition of the types allowed in ctvars and the types returned by the DLL function? Also, does the value supplied at the first time step after calling the advanceController_at_num function come from the controller or from initialization by QBlade? Because it corresponds to what we expect the controller to supply as an order-of-magnitude Torque value.

Concerning the ramp-up, I’ve created one but it doesn’t affect the controller’s operation.

Thanks in advance for your reply,

Best regards,

Hugo

Hi Hugo,

the BLADED DLL interface is used exactly as in the Interface Definition Document, provided by DNV. The swap array that is passed to the controller is automatically populatd by QBlade, based on the chosen controller interface. After the controller is advanced some of the values returned by the controller are then sorted in to the ctr_vars array, again based on the chosen controller interface.

The content of the ctr_vars array corresponds to the values that the controller returns in its swap array at the positions for torque, blade pitch and yaw:

  • ctr_vars [0] = generator torque [Nm]
  • ctr_vars [1] = yaw angle [deg]
  • ctr_vars [2] = pitch blade 1 [deg]
  • ctr_vars [3] = pitch blade 2 [deg]
  • ctr_vars [4] = pitch blade 3 [deg]

After calling the advanceController function (also for the first timestep) all values in ctr_vars are values returned by the controller. I dont know what exactly your are outputting in your script, but if its the contents of the ctr_vars array, right after the advanceController() function is called, these values definitely come from the controller library.

I would suggest for debugging you could add some debug outputs to your custom controller, to see what could be causing the returned NaN value.

BR,

David

Hello David,

First of all, thanks again for your answers and your investment in this problem. I think the issue appears when the simulation returns data to the controller after the first iteration. Is it a problem of conversion between units? Rpm to rad/s for example? In my case, the controller takes the generator rotation speed as input.

Thank you in advance,

Best regards,

Hugo

Hi Hugo,

check out this definition document of the BLADED interface:

https://myworkspace.dnv.com/download/public/renewables/bladed/docs/Bladed%20user-defined%20controllers%20in%20versions%20prior%20to%20version%204.4.pdf

From page 5 on this document lists the content of the exchange array. All positions marked as “Dataflow: in” are provided by QBlade in the designated units. I think that adding some standard debug outputs to the controller library to find out exactly whats going on would lead to a quick solution.

BR,

David

 

Hi David,

I’m taking the liberty of writing another message because, having just returned from vacation, the problem persists. After adding some debugs to the controller, here’s the output: DISCON
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.inRot Speed 1.047813
Measured Torque 83893.546875
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.in
calcOutputController SINGLETASKING
-1DISCON
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.inRot Speed nan
Measured Torque -nan(ind)
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.in
calcOutputController SINGLETASKING
-1DISCON
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.inRot Speed nan
Measured Torque -nan(ind)
QBladeCE_2.0.7\TEMP\5138e98c-9fa7-4a8e-ab8b-a3c3993f8b99\discon.in
calcOutputController SINGLETASKING

It looks like the first iteration goes well, then the DISCON function istatus is NoK. Do you have any idea what’s causing the problem?

Thanks again for all your answers,

Hugo

 

Hi Hugo,

I’m having difficulty identifying the problem from this debug output, and I’m also not sure what exactly is being printed to the console.

I believe the main task is to determine where the NaN value first appears. To achieve this, you could set up debug outputs as follows:

  1. In the DISCON function of your controller, print the entire contents of the swap array, as populated by QBlade, before it is processed.
  2. In the DISCON function of your controller, print the entire contents of the swap array, as populated by the controller, before it is returned to the QBlade simulation.

If the NaN value first appears in output (1), it indicates that QBlade is returning a NaN value. If the NaN value first appears in output (2), it suggests that the call to DISCON is somehow returning a NaN value.

After identifying where the NaN value first appears, you will have already narrowed down the root cause for further analysis. From there, you can focus on the specific function or segment of code that is introducing the NaN value.

Best regards,

David

Hi David,

Thanks again for your answers.

I did several tests this morning because I can now generate DLLs myself, which was not the case until now, it makes the process much easier. After adding some debugs, I think the problem comes from reading the .in file that sets the controller parameters. On the first iteration, the parameters are correctly readed and no NaN file is contained in the avrSwap matrix. Parameters reading at time step 1 is shown below:

Reading the .in file
avrSwap[119] = 0.310000
avrSwap[120] = 0.5400000
avrSwap[121] = 0.900000
avrSwap[122] = 1.200000
avrSwap[123] = 30.000000
avrSwap[124] = 10.000000
avrSwap[125] = 10.000000
avrSwap[126] = 50000.000000
avrSwap[127] = 700.000000
avrSwap[128] = 400.000000
avrSwap[129] = 100.000000
avrSwap[130] = 1.670000
avrSwap[131] = 8.91000
avrSwap[132] = 4.100000

And the parameters of interest at the controller output:

Reading avrSwap output
avrSwap[41] = 0.000000
avrSwap[42] = 0.000000
avrSwap[43] = 0.000000
avrSwap[44] = 0.000000
avrSwap[45] = 0.000000
avrSwap[46] = 36209.425781
avrSwap[47] = 0.000000
avrSwap[48] = 1025.000000

At the second time step, the reading of the input parameters resumes the .in file, but here is the result of the reading :

Reading the .in file
avrSwap[119] = 0.000000
avrSwap[120] = 0.000000
avrSwap[121] = 0.000000
avrSwap[122] = 0.000000
avrSwap[123] = 0.000000
avrSwap[124] = 0.000000
avrSwap[125] = 0.000000
avrSwap[126] = 0.000000
avrSwap[127] = 0.000000
avrSwap[128] = 0.000000
avrSwap[129] = 0.000000
avrSwap[130] = 0.000000
avrSwap[131] = 0.000000
avrSwap[132] = 0.000000

And necessarily the output matrix, the result of a division by zero:

Reading avrSwap output
avrSwap[41] = 0.000000
avrSwap[42] = 0.000000
avrSwap[43] = 0.000000
avrSwap[44] = 0.000000
avrSwap[45] = 0.000000
avrSwap[46] = -nan(ind)
avrSwap[47] = 0.000000
avrSwap[48] = 1025.000000

Then I tried manually redefining the .in parameters at each time step. This isn’t a definitive solution, as it makes the parameters complex to modify between several simulations, but this way the calculation works. The problem probably lies in the calls to the .in file. Do you have any idea what might be causing the problem? I’ve checked, the address used to call the .in file doesn’t seem to change between calls.

If you like, I can generate a fake controller trying to reproduce the problem and send you the integral file allowing you to generate the dll and launch the .qpr?

Thank you in advance for your reply,

Hugo

Hello Hugo,

the issue likely arises because the contents of the .in file are being read multiple times, at each timestep.

As I understand it—and as handled by other DISCON controllers such as ROSCO—the .in file should only be read once to initialize the controller parameters. Since these parameters (and the content of the .in file) do not change, subsequent readings of the file at later timesteps are redundant and should be avoided.

This behavior is reflected in the source code:

QString curDir = QDir::currentPath();
if (firstCall) QDir::setCurrent(g_tempPath+QDir::separator()+uuid);
Discon((float *)avrSWAP, (int *)aviFAIL, (char *) accINFILE, (char *) avcOUTNAME, (char *) avcMSG);
if (firstCall) QDir::setCurrent(curDir);

As you can see, only during the first call to the DISCON function (when firstCall is true) does the QBlade code change the current directory to the /TEMP directory containing the controller library and its .in parameter files. This change is necessary for the controller library to locate the input file. However, this directory change is not performed during subsequent calls, which is likely why reading the .in file a second time fails. To resolve this, you could store the necessary values during the first call and avoid reading the file again in subsequent calls within your controller implementation.

The reason why QBlade changes the local directory only once is to prevent issues like race conditions that could occur if QDir::setCurrent is called by multiple threads simultaneously. This approach aligns with the behavior of all DISCON controllers that I am aware of, which read the parameter files only once to minimize I/O operations.

I hope this clarifies the issue, and I am happy to discuss whether it is a general consensus that the .in file should only be read during the first call to DISCON.

Best regards,

David

PreviousPage 2 of 3Next

Scroll to Top