OpenFAST: Linearization of NREL 5MW Onshore Turbine

Dear Mohamed,

What input perturbation have you applied to the linear model? Does the output from your linear model match that of the nonlinear model when no input perturbations to the linear model are specified, such that all states, state derivatives, and input perturbations are zero? This should be the case because the output perturbations from the linear model are then zero, and when the periodic OP is added the perturbation, the result should match the nonlinear model.

Best regards,

Dear Jason

Thank you for your explanation.
However, I found less deviation between the linearized model and the nonlinear model, although I set all perturbations to zeros. I add the periodic OPs that are retrieved from the linearization txt file (*.lin). The operating conditions of the nonlinear model are the same as the linearization point. Moreover, I made another simulation with accounting the periodicity of A, B, C, and D matrices, but, it gives me the same results.
The Blade RootMFlp1 VS Simulation time ( kN.m VS seconds).
The yellow line for the nonlinear model. The blue line for the linear model.

The connection diagram is shown below

Regards
M.Fekry

Dear Mohamed,

I still have not fully understood your Simulink model, but if the perturbations zero, then the linear and nonlinear model should agree if the OP from the linear model is derived from the conditions of the nonlinear model. Are you adding the periodic OP to perturbation (which are all zeros for now) when comparing the linear and nonlinear models, including inputs, states, and outputs?

Best regards,

Dear Dr. Jason

The connection diagram of the Simulink is shown below:


The A, B, C, D matrices are calculated by azimuth averaged in the non-rotating frame using MBC toolbox.
I added the operating states only as shown in the diagram above. All perturbations are zero. So, I can expect that the linear and nonlinear responses should be almost similar.
The tower fore-aft displacement and generator speed are agreed well in this case as shown below:
The generator speed rpm VS simulation time S.

The tower fore-aft displacement m VS simulation time S.

Continuing from above

However, The RootMFlp1, RootMFlp2, and RootMFlp3 have some deviation as shown below:
RootMFlp1 kN.m VS Time S


Also, I compared the corresponding states and I found them agreed well as shown below:
Blade flap-wise displacement m VS Time S

Blade flap-wise velocity m VS Time S

Are there any precautions should I take when dealing with C and D matrices?

Regards
M.Fekry

Dear Mohamed,

Are the perturbed outputs from the linear model (dy = Cdx + Ddu) all zero? From your Simulink model (Linear Model Diagram.jpg), it still looks like you are adding Xop to dx before multiplying it by C.

And where is the periodic OP of the blade flapwise outputs added to the perturbed outputs from the linear model, i.e., y = yop(Azimuth) + dy, after transformation of dy from the nonrotating to the rotating frame? Given that dy in your case should be zero (both rotating and nonrotating), the blade flapwise output from the linear model should just be the OP value, which should match the nonlinear model.

Best regards,

Dr. Jason
Thank you very much for your explanation
it works now.
RootMFlp1 kN.m VS Time S


Regards
M.Fekry

Dear Jonkman,
First, thank your help on topic “Obtaining sensitivity of aerodynamic power to rotor-collective blade pitch”, I have successfully gotten the data. My version is OPENFAST 2.4.
However, I have some questions.
First, what is the difference between AD RtAeroPwr and ED RotPwr. Maybe the GenEff 94.4 in ServoDyn? I compared them in linearization files. They are not the relation about GenEff.
Second, I looked at the sensitivity of them to rotor-collective blade pith. For ED RotPwr, it is -5.904E+03. For AD RtAeroPwr, it is -5.134E+07. Owing to their units, one is KW, the other is W, so I think the sensitivity of them should differ 10^3, but it is nearly 10^4 now. I am a litter puzzled.
Could you please help me?

Best regards!

Dear Dezheng.Zhu,

A similar question was asked and answered in the following forum topic: FAST_v8.15 OutList parameters - #2 by Jason.Jonkman.

If the generator or drivetrain DOFs are enabled in ElastoDyn, I would expect large differences in the derivatives calculated from a linearization analysis between RotPwr from ElastoDyn and RtAeroPwr from AeroDyn because the former is effected by rotor acceleration/deceleration while the later is not. If the generator and drivetrain DOFs are disabled in ElastoDyn, then I would expect quite good agreement between the two.

Best regards,

Dear @Jason.Jonkman,

I am trying to linearize the onshore 5 MW reference wind turbine at a steady wind speed of 23 m/s. I have been facing some problems and I followed a debbuging process, that I will be explaining, and I would be glad if you could share your thoughts about it. The configuration I have been using can be found in this link:

First, I was receving this warning message:

"_NREL5MW_LINParametric/../5MW_Baseline/NRELOffshrBsline5MW_AeroDyn_blade.dat".
 AD_Init:Init_BEMTmodule:BEMT_Init:UA_Init:UA_SetParameters:UA_ValidateAFI:File
 "_NREL5MW_LINParametric/../5MW_Baseline/Airfoils/DU40_A17.dat":
The separation function f_st does  not reach 1; max(f_st)=0.56668. 
Check the calculation or provide f_st in the input file.

I have seen a similar issue here, and the proposed solutions are to either change the airfoil data or use a different UAMod. I got this message for all the speeds (3-25 m/s), but at the end the of the simulation I was seeing “OpenFAST terminated normally”. However, it was only when the OP was 23 m/s or higher (24 or 25 m/s) that I couldn’t apply the MBC3 to the lin files. I was getting this error message in MATLAB:

Unable to perform assignment because the size of the left side is 438-by-438 and 
the size of the right side is 314-by-438.

I checked the issue and realized that the A matrix was being truncated at the 314 state variable (AD x4 blade 1, node 18, -), where it row was full of NaN. I first solved this issue by setting UAEndRad = 0.85 instead of 1, but I was still getting the first warning message.

Then, I started checking the results with UA_Mod = 5 and got the following warning messages:

AD_Init:Init_BEMTmodule:BEMT_Init:UA_Init:UA_SetParameters:UA_ValidateAFI:File
“_NREL5MW_LINParametric/../5MW_Baseline/Airfoils/DU40_A17.dat”:UA cutout parameter should be at a value where the separation function is 0; separation function is 1.29902E-02.

UA_SetParameters:UA_ValidateAFI:File
“_NREL5MW_LINParametric/../5MW_Baseline/Airfoils/DU35_A17.dat”:UA cutout parameter should be at a value where the separation function is 0; separation function is 1.43151E-02.

UA_SetParameters:UA_ValidateAFI:File
“_NREL5MW_LINParametric/../5MW_Baseline/Airfoils/DU30_A17.dat”:UA cutout parameter should be at a value where the separation function is 0; separation function is 1.06945E-03.

This warnings made me change the parameter UAStartRad, to avoid having UA on those airfoils. After trying some values I finally set it to 0.45. With UAStartRad = 0.45 I am able to run the linearizations without any of those warning messages and then I can apply the MBC3 without any problems.

SUMMARY:

UA_Mod = 4
UAStartRad = 0
UAEndRad = 1

“The separation function f_st does not reach 1” warning message and I can’t apply MBC3 because the matrix A gets truncated.

UA_Mod = 4
UAStartRad = 0
UAEndRad = 0.85

“The separation function f_st does not reach 1” warning message still appearing, but I can apply MBC3.

UA_Mod = 5
UAStartRad = 0
UAEndRad = 1

Second warning messages appeared, and I can apply MBC3.

UA_Mod = 5
UAStartRad = 0.45
UAEndRad = 1

No warning messages, and I can apply MBC3.

UA_Mod = 4
UAStartRad = 0.45
UAEndRad = 1

No warning messages, and I can’t apply MBC3 because the matrix A gets truncated.

Best regards,
Ignacio López

Dear @Ignacio.Lopez,

Can you clarify when you are receiving warning messages–is it during the steady-state trim solution or during the linearization process?

I see that your model is quite sophisticated, with many DOFs enabled in ElastoDyn, as well as dynamic inflow and UA enabled in AeroDyn. Do you have issues in a simpler model without aerodynamic states, e.g., DBEMT_Mod = -1 and UAMod = 0?

Best regards,

Dear @Jason.Jonkman,

I’m receiving the warning messages during the steady-state trim solution. They appear just after the simulation begins and it happens when Linearize = True and False

OpenFAST

 Copyright (C) 2026 National Renewable Energy Laboratory
 Copyright (C) 2026 Envision Energy USA LTD

 This program is licensed under Apache License Version 2.0 and comes with ABSOLUTELY NO WARRANTY.
 See the "LICENSE" file distributed with this software for details.
 **************************************************************************************************

 OpenFAST-v4.2.0
 Compile Info:
  - Compiler: GCC version 14.3.0
  - Architecture: 64 bit
  - Precision: single
  - OpenMP: No
  - Date: Jan 28 2026
  - Time: 21:04:40
 Execution Info:
  - Date: 06/08/2026
  - Time: 10:29:00+0200

 OpenFAST input file heading:
     FAST                   Certification - Test #18: NREL 5.0 MW Baseline Wind Turbine (Onshore)

  Running ElastoDyn.
 Nodal outputs section of ElastoDyn input file not found or improperly formatted.
  Running InflowWind.
  Running AeroDyn.
    AeroDyn: projMod: 2
    Projection: Polar, BEM: polar (3D)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 1, blade 1)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 2, blade 1)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 3, blade 1)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 4, blade 1)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 1, blade 2)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 2, blade 2)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 3, blade 2)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 4, blade 2)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 1, blade 3)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 2, blade 3)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 3, blade 3)
 Warning: Turning off Unsteady Aerodynamics because polar has constant data. (node 4, blade 3)
  Running ServoDyn.

 FAST_InitializeAll:ED_Init:ED_ReadInput:ReadBladeInputs:ReadBladeFile:The ElastoDyn Blade file,
 ws23_test/../5MW_Baseline/NRELOffshrBsline5MW_Blade.dat, DISTRIBUTED BLADE PROPERTIES table
 contains the PitchAxis column.  This column is unused and will be removed in future releases
 FAST_InitializeAll:AD_Init:ReadInputFiles:Blade1:ReadBladeInputs:BlCrvAng will be calculated and
 overwrite the values specified in blade file
 "ws23_test/../5MW_Baseline/NRELOffshrBsline5MW_AeroDyn_blade.dat".
 ReadInputFiles:Blade2:ReadBladeInputs:BlCrvAng will be calculated and overwrite the values
 specified in blade file "ws23_test/../5MW_Baseline/NRELOffshrBsline5MW_AeroDyn_blade.dat".
 ReadInputFiles:Blade3:ReadBladeInputs:BlCrvAng will be calculated and overwrite the values
 specified in blade file "ws23_test/../5MW_Baseline/NRELOffshrBsline5MW_AeroDyn_blade.dat".
 AD_Init:Init_BEMTmodule:BEMT_Init:UA_Init:UA_SetParameters:UA_ValidateAFI:File
 "ws23_test/../5MW_Baseline/Airfoils/DU40_A17.dat":The separation function f_st does not reach 1;
 max(f_st)=0.56668. Check the calculation or provide f_st in the input file.


  Time: 0 of 600 seconds.
 Time: 15 of 600 seconds.  Estimated final completion at 10:32:07 (in 0.002 days).

With a simpler model without aerodynamic states (DBEMT_Mod = -1 and UAMod = 0) I don’t have any problems. I have also tried DBEMT_Mod = 3 and UAMod = 0, since DBEMT_Mod = 3 is the recommend practice according to the OpenFAST Documentation, and -1 is useful for simplified linearizations only. The moment I try to activate any unsteady aerodynamics (UAMod = 4 or 5) I get those warning messages. Reducing the number of DOFs doesn’t change anything.

Best regards,

Dear @Jason.Jonkman,

Since I need to continue with my work, is it acceptable/common practice to linearize the 5 MW wind turbine without UA (UA_Mod = 0)?

I have made a little research about the first warning:

"_NREL5MW_LINParametric/../5MW_Baseline/NRELOffshrBsline5MW_AeroDyn_blade.dat".
 AD_Init:Init_BEMTmodule:BEMT_Init:UA_Init:UA_SetParameters:UA_ValidateAFI:File
 "_NREL5MW_LINParametric/../5MW_Baseline/Airfoils/DU40_A17.dat":
The separation function f_st does  not reach 1; max(f_st)=0.56668.  
Check the calculation or provide f_st in the input file.

I’d like to share a couple of things:

  1. I have checked the DU40_A17 airfoil with the Polar.py script from the openfast_toolbox and the separation function does reach 1.
Alpha (deg) | f_st value
    -180.00 |         0.00000
    -175.00 |         0.00000
    -170.00 |         0.00000
    -160.00 |         0.00000
    -155.00 |         0.00000
    -150.00 |         0.00000
    -145.00 |         0.00000
    -140.00 |         0.00000
    -135.00 |         0.00000
    -130.00 |         0.00000
    -125.00 |         0.00000
    -120.00 |         0.00000
    -115.00 |         0.00000
    -110.00 |         0.00000
    -105.00 |         0.00000
    -100.00 |         0.00000
     -95.00 |         0.00000
     -90.00 |         0.00000
     -85.00 |         0.00000
     -80.00 |         0.00000
     -75.00 |         0.00000
     -70.00 |         0.00000
     -65.00 |         0.00000
     -60.00 |         0.00000
     -55.00 |         0.00000
     -50.00 |         0.00000
     -45.00 |         0.00000
     -40.00 |         0.00000
     -35.00 |         0.00000
     -30.00 |         0.00567
     -25.00 |         0.02153
     -24.00 |         0.02613
     -23.00 |         0.03136
     -22.00 |         0.03696
     -21.00 |         0.04364
     -20.00 |         0.05083
     -19.00 |         0.05854
     -18.00 |         0.06577
     -17.00 |         0.07273
     -16.00 |         0.07858
     -15.00 |         0.08353
     -14.00 |         0.08720
     -13.00 |         0.08996
     -12.00 |         0.09048
     -11.00 |         0.08897
     -10.00 |         0.08461
      -8.00 |         0.06342
      -6.00 |         0.03483
      -5.50 |         0.03010
      -5.00 |         0.03171
      -4.50 |         0.09058
      -4.00 |         0.22258
      -3.50 |         0.04765
      -3.00 |         0.04765
      -2.50 |         0.00245
      -2.00 |         0.19593
      -1.50 |         0.48028
      -1.00 |         0.03907
      -0.50 |         0.00066
       0.00 |         0.08165
       0.50 |         0.23703
       1.00 |         0.39765
       1.50 |         0.53284
       2.00 |         0.64278
       2.50 |         0.72181
       3.00 |         0.77822
       3.50 |         0.82424
       4.00 |         0.86429
       4.50 |         0.90184
       5.00 |         0.93281
       5.50 |         0.95612
       6.00 |         0.97697
       6.50 |         0.98994
       7.00 |         0.99614
       7.50 |         1.00000
       8.00 |         0.99849
       8.50 |         0.99071
       9.00 |         0.97748
       9.50 |         0.96679
      10.00 |         0.94155
      10.50 |         0.91431
      11.00 |         0.88024
      11.50 |         0.84759
      12.00 |         0.81740
      12.50 |         0.78605
      13.00 |         0.75481
      13.50 |         0.73194
      14.50 |         0.68965
      15.00 |         0.67281
      15.50 |         0.64805
      16.00 |         0.62568
      16.50 |         0.60384
      17.00 |         0.58168
      17.50 |         0.56314
      18.00 |         0.54714
      19.00 |         0.51205
      19.50 |         0.49591
      20.50 |         0.46545
      21.00 |         0.44920
      22.00 |         0.41679
      23.00 |         0.38590
      24.00 |         0.35642
      25.00 |         0.32825
      26.00 |         0.30180
      28.00 |         0.25386
      30.00 |         0.21299
      32.00 |         0.17920
      35.00 |         0.13770
      40.00 |         0.07859
      45.00 |         0.03430
      50.00 |         0.00756
      55.00 |         0.00019
      60.00 |         0.00000
      65.00 |         0.00000
      70.00 |         0.00000
      75.00 |         0.00000
      80.00 |         0.00000
      85.00 |         0.00000
      90.00 |         0.00000
      95.00 |         0.00000
     100.00 |         0.00000
     105.00 |         0.00000
     110.00 |         0.00000
     115.00 |         0.00000
     120.00 |         0.00000
     125.00 |         0.00000
     130.00 |         0.00000
     135.00 |         0.00000
     140.00 |         0.00000
     145.00 |         0.00000
     150.00 |         0.00000
     155.00 |         0.00000
     160.00 |         0.00000
     170.00 |         0.00000
     175.00 |         0.00000
     180.00 |         0.00000
  1. The aforementioned Python script is throwing this warning:
    [WARN] More than 20% error between estimated slope (0.1078) and the slope around alpha0 (0.0400). The window for the slope search ([-10.0 17.0]) is likely wrong.

  2. I have also checked the following scripts: DU21_A17, DU25_A17, DU30_A17 and DU35_A17. With any of them I get that warning.

  3. After plotting some data of the DU40_A17 airfoil I have come up with a possible reason: there is to much noise near the alpha0 region, and that’s why the script is having trouble to estimate the slope. What do you think?

Best regards,

Dear Ignacio,

I’ve only looked at the end of the thread.

First step: look at how OpenFAST compute fs:
What I would do if I were to receive such a warning (about the separation function not reaching 1) would be to run each of your airfoil polars through the Unsteady Aerodynamic Driver of OpenFAST, setting WriteAFITables to true.
See an example here:

The UA Driver will produce a .out file that you can read like a regular OpenFAST output file (for instance in pyDatView).

The separation function should be part of the outputs. You’ll be able to see how OpenFAST computed this separation function and what might create the problem.

Ideally, we would like the separation funciton to be equal to 1 throughout the linear region.
Unfortunately, when OpenFAST compute thee parameters on the fly, it is highly sensitive to how the Cl curve looks like.

More advanced: Provide fs, or Cl,alpha. or modify the polar.
I believe there is a way to provide your own fs (separation function) in the polar input file of OpenFAST (though I have never done it, I would have to check the source code).

There might also be a way to provide Cl,alpha directly in the input file (sorry I cannot check this right now without looking at the source code).

Or, what I’ve done in the past, is I have slightly adjusted the Cl curve to force it to be linear close to alpha0. Ideally, if OpenFAST finds/uses the Cl,alpha that correponds to the linear region of Cl, then you’ll get a fs exactly equal to 1 in the linear region.

This might sound hacky, but UAMod 4 and 6 will not perform very well without a clean Fs.

Keep us in touch,

Emmanuel

Dear @Emmanuel.Branlard,

Thank you for your feedback.

I’ve been working on it and this is what I can tell you:

  1. First of all, I recently migrated from OpenFAST-v4.2.0 to OpenFAST-v5.0.0 and when replicating the cases that generated the warning about the separation function not reaching 1 (see the attached file from this post OpenFAST: Linearization of NREL 5MW Onshore Turbine - #30 by Ignacio.Lopez) I no longer see the warnings, i.e. OpenFAST is no longer complaining about f_st not reaching 1. Is the warning outputed somewhere else or this problem has already being solved in OpenFAST-v5.0.0?

  2. As you said I set up the Unsteady Aerodynamic Driver and ran each of the airfoil polars through it. The results are linked in the file below.

I have made a comparison between the f_st function calculated with the UA_driver and Polar.py. I have also added a graph with the raw table Cl, Cl,inv and Cl,fs generated also by Polar.py.
The only airfoil separation function that fails to reach 1 is the DU40_A17, when calculated with the UA_driver. The rest of the airfoils separation functions reach 1. With the Polar.py script all the airfoils separation functions also reach 1 at least once.

  1. Checking the data generated in the UA2.Coefs.1.out file I found that, for DU40_A17, the Cl,lin curve is not matching the Cl curve in the linear region, just like this:


    Does this tell you something about what the root of the problem is?
    If somehow I could make the Cl,lin curve calculated the by the UA_driver match the Cl,lin curve calculated by the Polar.py, would the separation function be 1, at least once?

  2. For the DU40_A17.dat file, I have also tried to add my own separation function (the one created by the Polar.py, which reaches 1). I have looked at the source code and found the following lines in the AirfoilInfo.f90:

subroutine AFI_ComputeAirfoilCoefs1D( AOA, p, AFI_interp, errStat, errMsg, TableNum )
(...)
   if ( p%ColUAf > 0 ) then
      AFI_interp%f_st          = IntAFCoefs(p%ColUAf)   ! separation function
      AFI_interp%fullySeparate = IntAFCoefs(p%ColUAf+1) ! fully separated cn or cl
      AFI_interp%fullyAttached = IntAFCoefs(p%ColUAf+2) ! fully attached cn or cl
   else
      AFI_interp%f_st          = 0.0_ReKi
      AFI_interp%fullySeparate = 0.0_ReKi
      AFI_interp%fullyAttached = 0.0_ReKi
   end if

ColUAf   ! column for UA f_st (based on Cl or cn)

Unfortunatley, I added the three columns to my DU40_A17.dat file but the driver doesn’t read them.

Best regards,

Hi,

Thank you for doing all of this.

  1. I’m not sure if things have changed for v5.0, but if you used the UA driver with version 5.0, then you will likely see whether f_st is indeed still not reaching 1 in this versin.

  2. There are indeed some inconsistencies in how Polar.py and OpenFAST calculate f_st, which is unfortunate.

  3. Thank you for diving into the individual polars with the driver. I believe it highlights the root cause of the problem. This polar doesn’t display a clean linear region (or has two Cl=0 points very close to each other) and OpenFAST has trouble detecting the linear region. For f_st to reach one, the linear and regular Cl should be equal (or intersect).

  4. Thanks for diving into the code! It seems there is a way to provide them, but maybe p%ColUAf is not positive? If you can’t find a way, I can try to dive into the code.

Emmanuel