Variable airfoils using the MulTabLoc USER function

Hello,

I’m using Fast v8.08 and I would like to activate the Aerodyn MulTabLoc function. Since I want to control the airfoil selection for some blade elements actively I chose the MulTabLoc “USER” option.
I had a look at the source code but I could not figure out on my own how I have to modify the source code to provide a control signal (eg from a dll controller). Also I did not find where I have to fill in my own routine how it is mentioned in the AeroDyn manual on page 13 (“Use USER only if you supply your own algorithm
in the dynamics code to move between airfoil tables during a Simulation.”).

Could you please give me a few hints for the necessary modifications.

Thank you in advance.

Niklas

Hello, Niklas.

As far as I know, you are the first person who has asked about this feature in FAST v8; it’s never been tested so hopefully this won’t be a painful exercise.

In FAST_IO.f90, there is a subroutine called AD_InputSolve(). At the bottom of that routine is a comment: [code]
!-------------------------------------------------------------------------------------------------
! If using MulTabLoc feature, set it here:
!-------------------------------------------------------------------------------------------------

! u_AD%MulTabLoc(:,:slight_smile: = ???

[/code]
After that comment is where you should set the MulTabLoc values that you want as input to AeroDyn. These values are stored in u_AD%MulTabLoc(:,:), where dimension 1 is the element number and dimension 2 is the blade number. Make sure you set all the values in the matrix (it is initialized to 0, which means you don’t use the feature).

If you set these inputs, it should be logically equivalent to what was done in FAST v7 and even FAST v6.

I hope that helps. I have never actually used this feature in any version of FAST, but have been told it worked as expected in FAST v7.

Hello Bonnie,
thank you for the quick answer, it was very helpful.
I modified the code at the spot you mentioned and after compiling I was thereby able to let AeroDyn take a second airfoil table I provided in the airfoil file.
For testing this I used some hard coded values in the code.

Now I am having difficulties with the next step. I want to use an active control signal from a bladed style dll to switch between the airfoil tables. Therefore I activated six entries in the dll_data%avrSWAP() array, three entries for inputs (Fill_avrSWAP) and three entries for outputs (Retrieve_avrSWAP), eg:
dll_data%avrSWAP(66) = dll_data%XY(1) etc.

My problem is that I don’t know how to lead the signal to the AD_InputSolve() routine which is called in SUBROUTINE SolveOption2. I think I have to include the input signal in “ED_Output(1)”. But I could not find where ED_Output is defined.
Having a look from the controller point of view and following the routines it looked like the dll signal is passed via SUBROUTINE BladedInterface_CalcOutput and SUBROUTINE SrvD_CalcOutput to SUBROUTINE SolveOption2 included in OtherState and OtherSt_SrvD. Could you tell me where I have to make the Connection between the two signals in SUBROUTINE SolveOption2? Or should it be somewhere in SolvingOption1?

I am looking forward to your answer.
Cheers
Niklas

I’m not sure I fully understand what you’re doing, but here are the basics:

To get data out of ServoDyn, you need to add variables to ServoDyn_OutputType. For this, you would add lines to end of ServoDyn_Registry.txt:

typedef ^ OutputType ReKi MyNewOutputs {3} - - "Array of 3 new outputs from dll" I assume you’ve already done this for the BladedDLLType if you have a field XY (dll_data%XY(1)).

The Visual Studio project in the FAST archive will (should) run the FAST registry when you’ve changed ServoDyn_Registry.txt. It will then create a new ServoDyn_Types.f90 file with your new variable. There is something strange in Visual Studio that sometimes requires you to compile twice after you change a registry input file. It may not be a problem, but be aware that it may happen (it acts like it’s using the wrong time stamp on the *_Types.f90 files so it doesn’t recompile them when they get modified during a custom build step).

In ServoDyn.f90, routine SrvD_CalcOutput(), you will need to populate the y%MyNewOutputs variable with the data from OtherStates%dll_data%XY (or whatever you called the new variable). You need to do this somewhere after the call to BladedInterface_CalcOutput().

The FAST glue code will take the ServoDyn output and set the AeroDyn input. Because AD_InputSolve() doesn’t currently contain the y_SrvD variable, you will need to add it to the subroutine arguments. In FAST_Prog.f90, change the call to AD_InputSolve in SolveOption2 to be this:

CALL AD_InputSolve( AD_Input(1), ED_Output(1), y_SrvD, MeshMapData, ErrStat, ErrMsg )

In FAST_IO.f90, change the AD_InputSolve routine to look like this: SUBROUTINE AD_InputSolve( u_AD, y_ED, y_SrvD, MeshMapData, ErrStat, ErrMsg ) and add

TYPE(SrvD_OutputType), INTENT(IN) :: y_SrvD ! The outputs of the ServoDyn module in the section of code after the “Passed variables” comment at the top of the routine.

Now you’ve got data from the DLL in the AD_InputSolve routine, and you can use y_SrvD%MyNewOutputs to set u_AD%MulTabLoc.

Good Luck!

Thanks a lot Bonnie!
I finally got it to work with your description.

Hi Bonnie,
now I’d like to integrate a “sensor” into Fast and receive the Information in the dll. In my case the sensor is supposed to be the local wind normal to the rotor plane for a specific blade element (VNWind in the Aerodyn AD_CalcOutput subroutine).
I was able to add this component as a Fast Output sensor to have a look on it after a full Simulation. For a reason I don’t know I was not able to feed it to the ServoDyn module and receive it in the dll.

Here is what I did so far:

In subroutine CalcOutput i added these lines in the corresponding loops to store the wind

o%SensorWind(1,IElement,IBlade) = VNWind
y%SensorWind(:,IElement,IBlade) = o%SensorWind(:,IElement,IBlade)

In the AeroDyn registry I added
typedef ^ OtherStateType Reki SensorWind {2}{59}{3} #indices: Wind, ielm, iblade
typedef ^ OutputType ReKi SensorWind {2}{59}{3} - -

In ElastoDyn.f90 I added the following line in a loop
OtherState%AllOuts( SensorF( runningIndex) )= u%SensorWind(1,J,K)

I also followed the instruction on how to add a sensor to the Output and it worked, I can see my signals in the Fast output.

But when I try to add the sensor to the output for ServoDyn it seems that the signals get lost somewhere but I could not figure out where.
I added the following lines:

In the ElastoDyn.f90 after “!Control outputs for Bladed DLL:” :
y%SensorF(J) = OtherState%AllOuts( SensorF( J ) )

In the ElastoDyn_Registry.txt:
typedef ^ OutputType ReKi SensorF {45} - - “sensor signal”

In the ServoDyn_Registry.txt:
typedef ^ OutputType ReKi SensorF {45} - - “SensorF”
typedef ^ InputType ReKi SensorF {45} - - “SensorF”
typedef ^ BladedDLLType ReKi SensorF {45} - - "Sensor Position
(I think the definition as a BladedDLLType is not necessary)

From my understanding this should be passed through to ServoDyn like other sensors (e.g. y%RootMyc).
But if try to use u%SensorF( I ) in SUBROUTINE Fill_avrSWAP to fill the Swap Array:

dll_data%avrSWAP( 84 + I ) = u%SensorF( I )

the value I receive in the dll seems to be Zero. My interface works properly. I receive the value in the dll correctly, if I set

dll_data%avrSWAP( 84 + I ) = 8.

Do you have any idea where the missing link is or could you please describe how you would lead a Signal from AeroDyn to the ServoDyn Bladed dll Interface?
Thank you for your time.
Cheers
Niklas

Let me see if I understand what you’re trying to do in terms of the interface:

  1. pass wind velocities, SensorWind, from AeroDyn (InflowWind) to ElastoDyn (ElastoDyn uses this new input to create a new output)
  2. pass a new output, SensorF, from ElasoDyn to ServoDyn so the controller DLL in ServoDyn can use it

I haven’t spent a lot of time looking at all the details you shared, but it seems to me you have the basic idea of how to pass outputs from a module and how to pass inputs into a module. If you’re passing values on each blade and element, I’d normally recommend using a mesh data structure… but for this version of ElasoDyn and AeroDyn it’s still okay if you don’t (and less complicated for me to explain). I am a little confused about the ServoDyn output you’ve added, but it doesn’t seem to be doing any harm.

The part you are missing is the role of the glue code, which performs an input-output solve… the outputs of one or more modules are used to compute the inputs for other modules. For the modules you are using, this is pretty simple:

u_ED%SensorWind = y_AD%SensorWind (this is in FAST_IO.f90/ED_InputSolve() )
u_SrvD%SensorF = y_ED%SensorF (this is in FAST_IO.f90/SrvD_InputSolve())

Just make sure you put these lines in the appropriate IF statements (depending on which module is used) and check the indices on the arrays to make sure they’re consistent between modules.

Hi Bonnie,
yes, that solved the problem.
Thank you
Niklas

Hello Bonnie,

Thanks for the helpful discussion here.
I tested the MulTabLoc feature with a hard coded Table location and it works.
However, not like Niklas, I plan to use the simulink +FAST_Sfunc to control my flap angle, similar to Keshan did in this poster
[url]Instructions for Compiling FAST - #121 by Keshan.He]

I have one question regarding to the “NumAdditionalInputs” parameter.
I have total three additional inputs, corresponding to the flap angle of each flap of each blade.
So I expect I should set NumAdditionalInputs to 3.
And I change the FAST_library code like this:

subroutine FAST_SetExternalInputs(NumInputs_c, InputAry, m_FAST)

   USE, INTRINSIC :: ISO_C_Binding
   USE FAST_Types
!   USE FAST_Data, only: NumFixedInputs
   
   IMPLICIT  NONE

   INTEGER(C_INT),         INTENT(IN   ) :: NumInputs_c      
   REAL(C_DOUBLE),         INTENT(IN   ) :: InputAry(NumInputs_c)                   ! Inputs from Simulink
   TYPE(FAST_MiscVarType), INTENT(INOUT) :: m_FAST                                  ! Miscellaneous variables
   
         ! set the inputs from external code here...
         ! transfer inputs from Simulink to FAST
      IF ( NumInputs_c < NumFixedInputs ) RETURN ! This is an error
          
           
      m_FAST%ExternInput%GenTrq      = InputAry(1)
      m_FAST%ExternInput%ElecPwr     = InputAry(2)
      m_FAST%ExternInput%YawPosCom   = InputAry(3)
      m_FAST%ExternInput%YawRateCom  = InputAry(4)
      m_FAST%ExternInput%BlPitchCom  = InputAry(5:7)
      m_FAST%ExternInput%HSSBrFrac   = InputAry(8)
      
     
            
      IF ( NumInputs_c > NumFixedInputs ) THEN  ! NumFixedInputs is the fixed number of inputs
         IF ( NumInputs_c == NumFixedInputs + 3 ) &
             !Xiao Sun: Set NumAdditionalInput to 3
             ! Pass three flap angle input to AeroDyn
             m_FAST%ExternInput%FlapCtrl   = InputAry(9:11)
             m_FAST%ExternInput%LidarFocus = InputAry(9:11)
      END IF   
      
end subroutine FAST_SetExternalInputs

However, in your reply to Keshan, you said the NumAdditionalInputs can be set to [3, COM11_STU].
What does that mean? Does that mean InputAry(1) can be a array that contains three flap angle values?

Hi, Xiao.

Sorry for the confusion. The footnote on NumAdditionalInputs in the FAST v8.12 documentation says: “For flexibility reasons (i.e., so that FAST_SFunc.c doesn’t have to be recompiled for customizations to FAST_Library.f90), this third parameter can also be input as an array with no more than 11 entries. If this parameter is an array, the first element of the array is NumAdditionalInputs.” In the post you reference, I was calling the entire array NumAdditionalInputs.

Normally, setting NumAdditionalInputs = 3 causes the code to simulate a CW lidar, whose measurement is set in an output channel called “WindMeas1”. When NumAdditionalInputs = 3, the code expects this third parameter to contain an array of initialization data for the lidar module:
S-function parameters: FAST_InputFileName, TMax, [3, UseLidar, LidRadialV]

If you want to set NumAdditionalInputs to 3, I would set UseLidar=-1 to disable the lidar module without changing more source code. Your code will run a lot slower if it’s calling the lidar module each time. LidRadialV should be 0.

If you don’t have any other initialization data, you don’t need to change this array, but you may want to initialize m_FAST%ExternInput%FlapCtrl with it. For instance, you may want to set that third parameter to [3, UseLidar, LidRadialV, FlapCtrl1, FlapCtrl2, FlapCtrl3] where FlapCtrl1, FlapCtrl2, and FlapCtrl3 are initial values for m_FAST%ExternInput%FlapCtrl. Then in FAST_Library.f90, you could initialize these values in the FAST_Sizes() routine, something like this:

m_FAST%ExternInput%FlapCtrl = InitInpAry(3:5) It may not be necessary to do this, but you have a smoother initialization with these values.

Hi Bonnie,

Thanks for the clarification. It is truly helpful.

Regarding for the initialization of the flap position, I did it this way since it may be easy to change m_FAST%ExternInput%FlapCtrl without inserting m_FAST in the FAST_Sizes() function.
How do you think about this?

  1. populate ExternInitData%FlapPos (FAST_ExternIntType in the FAST_Type.f90)
! =========  FAST_ExternInitType  =======
! Xiao Sun: Add initial viarable for flap?
  TYPE, PUBLIC :: FAST_ExternInitType
    REAL(DbKi)  :: Tmax = -1      ! External code specified Tmax [s]
    INTEGER(IntKi)  :: SensorType = SensorType_None      ! lidar sensor type, which should not be pulsed at the moment; this input should be replaced with a section in the InflowWind input file [-]
    LOGICAL  :: LidRadialVel      ! TRUE => return radial component, FALSE => return 'x' direction estimate [-]
    INTEGER(IntKi)  :: TurbineID      ! ID number for turbine (used to create output file naming convention) [-]
    REAL(ReKi) , DIMENSION(1:3)  :: TurbinePos      ! Initial position of turbine base (origin used in future for graphics) [m]
    INTEGER(IntKi)  :: NumSCin      ! number of controller inputs [from supercontroller] [-]
    INTEGER(IntKi)  :: NumSCout      ! number of controller outputs [to supercontroller] [-]
    ! Xiao Sun: add flap
    REAL(ReKi) , DIMENSION(1:3)  :: FlapPos      ! Initial position of flap  [deg]
  END TYPE FAST_ExternInitType
! =======================
  1. import the initial value of FlapPos: ExternInitData%FlapPos(i) = InitInpAry(i+2) (FAST_Sizes in FAST_library.f90)
   
      ! initialize variables:   
   n_t_global = 0
   
   ExternInitData%TMax       = TMax
   ExternInitData%TurbineID  = -1        ! we're not going to use this to simulate a wind farm
   ExternInitData%TurbinePos = 0.0_ReKi  ! turbine position is at the origin
   ExternInitData%NumSCin = 0
   ExternInitData%NumSCout = 0
   ExternInitData%SensorType = NINT(InitInpAry(1))
   
   
   IF ( NINT(InitInpAry(2)) == 1 ) THEN
      ExternInitData%LidRadialVel = .true.
   ELSE
      ExternInitData%LidRadialVel = .false.
   END IF
   
    !Xiao Sun: add flap
    do i = 1, 3
    ExternInitData%FlapPos(i) = InitInpAry(i+2) 
    end do
    !-------------
   
   CALL FAST_InitializeAll_T( t_initial, 1_IntKi, Turbine, ErrStat, ErrMsg, InputFileName, ExternInitData )
                  
  
  1. Initialize these values in the FAST_InitializeAll(FAST_Subs.f90)
 ! -------------------------------------------------------------------------
   ! other misc variables initialized here:
   ! -------------------------------------------------------------------------
      
   m_FAST%t_global   = t_initial
         
   ! Initialize external inputs for first step  
   if ( p_FAST%CompServo == MODULE_SrvD ) then      
      m_FAST%ExternInput%GenTrq     = SrvD%Input(1)%ExternalGenTrq !0.0_ReKi
      m_FAST%ExternInput%ElecPwr    = SrvD%Input(1)%ExternalElecPwr
      m_FAST%ExternInput%YawPosCom  = SrvD%Input(1)%ExternalYawPosCom
      m_FAST%ExternInput%YawRateCom = SrvD%Input(1)%ExternalYawRateCom
      m_FAST%ExternInput%HSSBrFrac  = SrvD%Input(1)%ExternalHSSBrFrac
      
      do i=1,SIZE(SrvD%Input(1)%ExternalBlPitchCom)
         m_FAST%ExternInput%BlPitchCom(i) = SrvD%Input(1)%ExternalBlPitchCom(i)
      end do   
   end if
   
   m_FAST%ExternInput%LidarFocus = 1.0_ReKi  ! make this non-zero (until we add the initial position in the InflowWind input file)
   ! Xiao Sun: add initialization for flap 
   IF (PRESENT(ExternInitData)) THEN
       do i=1,3
             m_FAST%ExternInput%FlapCtrl(i) = ExternInitData%FlapPos(i)
       end do 
    END IF
   !------------

Right now, I have difficulties to run the simulink openloop sample with my new compiled FAST_Sfunc.mexw64.
The FAST_Sfunc.mexw64 was compiled with gcc compiler in matlab and modified FAST_library_x64.dll.
However, when I was running the case, the matlab crashed and give me some error messages.
Can you give me some suggestions?

	This segmentation violation occurred while executing the 
	S-function 'FAST_SFunc' in block 'O'.
	A common cause of this segmentation violation is an incorrect
	input port direct feedthrough setting. Each input port of the
	S-function that is read (accessed) in mdlOutputs and/or
	mdlGetTimeOfNextVarHit must specify that it needs its input
	signal in these routines by setting direct feedthrough for
	these input ports.
	Another cause can be incorrect memory accesses which occur
	when your code accesses beyond the end of an array. For example
	if you access input port 5 and in mdlInitializeSizes specify
	that you only have 4 input ports.
	To debug your C-MEX S-function, you can enable diagnostics
	by compiling the S-function source with the -g flag, e.g.,
	  mex -g sfunction_name.c
	-------------------------------------------------------------

Error using Run_OpenLoop (line 31)
Error while obtaining sizes from MEX S-function 'FAST_SFunc' in 'OpenLoop/FAST Nonlinear Wind
Turbine/S-Function'.

Caused by:
    Error using Run_OpenLoop (line 31)
    FatalException

Wth the modifications you have done, you should not have to recompile the FAST_SFunc.mexw32 or FAST_SFunc.mexw64 files. What you do need to recompile is the FAST_Library_Win32.dll or FAST_Library_x64.dll file.

If you have compiled the FAST_Library dll and are still getting the error with the OpenLoop example, you need to isolate the problem: is it coming from the Simulink model or from your changes to the FAST source code?

Have you changed the OpenLoop example at all? If NumAdditionalInputs = 0, this shouldn’t have any effect on the code. You may want to try to compile the standalone FAST executable with your changes and see if it still runs (and be careful, because if you added variables to types without using the FAST registry, your changes will be lost if the FAST registry runs). Or, if you want to debug the dll directly, you could also compile the FAST_Prog.c file to link with your FAST_Library (compiled with SysIVF.f90 instead of SysMatlab.f90–there are compiling configurations for OpFM that have this set up already). The errors you get from running in a C driver like FAST_Prog.c are much more helpful than what Matlab tells you.

If you modified the S-function parameters so that Matlab/FAST thinks there are additional inputs, make sure you’ve modified Mux1 to accept another input block and that the input block returns 3 values.

Hi Mr.Xiao,
I am trying to enable flapcntrl as per your post but when i compile i get the following error as in buildlog kindly help me in rectifying it :
buildlog.txt (18.9 KB)

hi Dr.bonnie ,
i am trying to enable flapcntrl as Xiao did in his post Variable airfoils using the MulTabLoc USER function - #13 by Srinivasa.Sudharsan, but when i compile i get the errors a in the buildlog. kindly help in rectifying it if you can, thank you!!
buildlog.txt (18.9 KB)

Srinivasa,

The error message indicates that you are missing a variable declaration in your FAST_Types.f90 file. I would recommend that you regenerate that file. If you modify the FAST_Registry.txt file in the FAST_Project VS file, it should automatically regenerate the file; otherwise, open a command prompt, change to the /Compiling/VisualStudio directory and type RunRegistry FAST

Hi Dr.Bonnie:
thank you very much for your fast reply!! I did as you said after running RunRegistry also i get the following errors which restricts the generation of FAST_Library.dll which is need for simulink. kindly hep me to rectify these errors since i not well versed in fortran language…
xiao test1.txt (15.3 KB)

hi Dr.Bonnie;
i have successfully compiled FAST8.12 with active flap control. But can we linearize FAST with the multitab flap angle as input like pitch angle???

Dear Srinivasa,

Linearization within FAST v8 was not offered until the release of FAST v8.16, which happened recently. However, the linearization functionality does not support linearization with the MulTabLoc feature, as MulTabLoc is supported in AeroDyn v14, but not AeroDyn v15 (and the linearization functionality only applies to AeroDyn v15). You would have to customize AeroDyn v15 and the glue-code linearization routines to get such functionality.

Best regards,

Dear Bonnie
I’ve tried to use multabloc feature on FAST_Farm, but I think there are a lot of changes in source codes in comparison with FAST. By the way, I want to actively control the flaps using dll. would you please give me some hints.

Best regards
Majid

Dear @Majid.Ebr,

AeroDyn v14 supported 1D interpolation (AoA only) and 2D interpolation (AoA and Re or control) based on the MulTabLoc option. But FAST.Farm is only compatible with OpenFAST models that use AeroDyn v15. We intend to deprecate AeroDyn v14 soon…

AeroDyn v15 supports 1D interpolation (AoA only), 2D interpolation (AoA and Re), and 3D interpolation (AoA, Re, and control) through the AFTabMod option. Active control of the airfoil data specified through AFTabMod option of AeroDyn v15 is available through the AfCmode option of ServoDyn.

Best regards,