Skip to content

Commit

Permalink
Merge pull request #2582 from andrew-platt/f/IfW_C_mods_coupling
Browse files Browse the repository at this point in the history
Update InflowWind_c_bindings interface
  • Loading branch information
andrew-platt authored Dec 23, 2024
2 parents abf5938 + aae85d8 commit bc6c481
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 177 deletions.
46 changes: 31 additions & 15 deletions modules/inflowwind/python-lib/inflowwind_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,20 @@ class InflowWindLib(CDLL):
# here.
error_msg_c_len = 1025

# NOTE: the length of the name used for any output file written by the
# IfW Fortran code is 1025.
default_str_c_len = 1025

def __init__(self, library_path):
super().__init__(library_path)
self.library_path = library_path

self._initialize_routines()
self.ended = False # For error handling at end

# Input file handling
self.IfWinputPass = 1 # Assume passing of input file as a string

# Create buffers for class data
self.abort_error_level = 4
self.error_status_c = c_int(0)
Expand All @@ -84,18 +91,27 @@ def __init__(self, library_path):

self.numChannels = 0 # Number of channels returned

# flags
self.debuglevel = 0 # 0-4 levels

# OutRootName
# If HD writes a file (echo, summary, or other), use this for the
# root of the file name.
self.outRootName = "Output_ifwlib_default"


def _initialize_routines(self):
"""
Initialize the Python handles to necessary routines in the InflowWind library.
"""
self.IfW_C_Init.argtypes = [
POINTER(c_int), # IfW input file passed as string
POINTER(c_char_p), # input file string
POINTER(c_int), # input file string length
POINTER(c_char_p), # uniform file string
POINTER(c_int), # uniform file string length
POINTER(c_char), # OutRootName
POINTER(c_int), # numWindPts
POINTER(c_double), # dt
POINTER(c_int), # debuglevel
POINTER(c_int), # number of channels
POINTER(c_char), # output channel names
POINTER(c_char), # output channel units
Expand All @@ -121,29 +137,29 @@ def _initialize_routines(self):
self.IfW_C_End.restype = c_int


def ifw_init(self, input_string_array, uniform_string_array):
def ifw_init(self, IfW_input_string_array):
"""
Call the initialization routine in the InflowWind library.
"""

# Set up inputs: Pass single NULL joined string
input_string = '\x00'.join(input_string_array)
input_string = input_string.encode('utf-8')
input_string_length = len(input_string)

uniform_string = '\x00'.join(uniform_string_array)
uniform_string = uniform_string.encode('utf-8')
uniform_string_length = len(uniform_string)

IfW_input_string = '\x00'.join(IfW_input_string_array)
IfW_input_string = IfW_input_string.encode('utf-8')
IfW_input_string_length = len(IfW_input_string)

# Rootname for ADI output files (echo etc).
_outRootName_c = create_string_buffer((self.outRootName.ljust(self.default_str_c_len)).encode('utf-8'))

self._numChannels_c = c_int(0)

self.IfW_C_Init(
c_char_p(input_string), # IN: input file string
byref(c_int(input_string_length)), # IN: input file string length
c_char_p(uniform_string), # IN: uniform file string
byref(c_int(uniform_string_length)), # IN: uniform file string length
byref(c_int(self.IfWinputPass)), # IN: IfW input file is passed
c_char_p(IfW_input_string), # IN: input file string
byref(c_int(IfW_input_string_length)), # IN: input file string length
_outRootName_c, # IN: rootname for ADI file writing
byref(c_int(self.numWindPts)), # IN: number of wind points
byref(c_double(self.dt)), # IN: time step (dt)
byref(c_int(self.debuglevel)), # IN: debuglevel
byref(self._numChannels_c), # OUT: number of channels
self._channel_names_c, # OUT: output channel names as c_char
self._channel_units_c, # OUT: output channel units as c_char
Expand Down
194 changes: 137 additions & 57 deletions modules/inflowwind/src/IfW_C_Binding.f90
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,39 @@ MODULE InflowWind_C_BINDING
PUBLIC :: IfW_C_CalcOutput
PUBLIC :: IfW_C_End

!------------------------------------------------------------------------------------
! Version info for display
type(ProgDesc), parameter :: version = ProgDesc( 'InflowWind library', '', '' )

! Accessible to all routines inside module
TYPE(InflowWind_InputType) , SAVE :: InputData !< Inputs to InflowWind
TYPE(InflowWind_InitInputType) , SAVE :: InitInp
TYPE(InflowWind_InitOutputType) , SAVE :: InitOutData !< Initial output data -- Names, units, and version info.
TYPE(InflowWind_ParameterType) , SAVE :: p !< Parameters
TYPE(InflowWind_ContinuousStateType) , SAVE :: ContStates !< Initial continuous states
TYPE(InflowWind_DiscreteStateType) , SAVE :: DiscStates !< Initial discrete states
TYPE(InflowWind_ConstraintStateType) , SAVE :: ConstrStates !< Constraint states at Time
TYPE(InflowWind_OtherStateType) , SAVE :: OtherStates !< Initial other/optimization states
TYPE(InflowWind_OutputType) , SAVE :: y !< Initial output (outputs are not calculated; only the output mesh is initialized)
TYPE(InflowWind_MiscVarType) , SAVE :: m !< Misc variables for optimization (not copied in glue code)

! This must exactly match the value in the Python interface. We are not using the variable 'ErrMsgLen'
! so that we avoid issues if ErrMsgLen changes in the NWTC Library. If the value of ErrMsgLen does change
! in the NWTC Library, ErrMsgLen_C (and the equivalent value in the Python interface) can be updated
! to be equivalent to ErrMsgLen + 1, but the logic exists to correctly handle different lengths of the strings
integer(IntKi), parameter :: ErrMsgLen_C=1025 ! Numerical equivalent of ErrMsgLen + 1
!------------------------------------------------------------------------------------
! Debugging: DebugLevel -- passed at PreInit
! 0 - none
! 1 - some summary info
! 2 - above + all position/orientation info
! 3 - above + input files (if direct passed)
! 4 - above + meshes
integer(IntKi) :: DebugLevel = 0

!------------------------------------------------------------------------------------
! Primary IfW data derived types
type(InflowWind_InputType) :: InputData !< Inputs to InflowWind
type(InflowWind_InitInputType) :: InitInp
type(InflowWind_InitOutputType) :: InitOutData !< Initial output data -- Names, units, and version info.
type(InflowWind_ParameterType) :: p !< Parameters
type(InflowWind_ContinuousStateType) :: ContStates !< Initial continuous states
type(InflowWind_DiscreteStateType) :: DiscStates !< Initial discrete states
type(InflowWind_ConstraintStateType) :: ConstrStates !< Constraint states at Time
type(InflowWind_OtherStateType) :: OtherStates !< Initial other/optimization states
type(InflowWind_OutputType) :: y !< Initial output (outputs are not calculated; only the output mesh is initialized)
type(InflowWind_MiscVarType) :: m !< Misc variables for optimization (not copied in glue code)

!------------------------------------------------------------------------------------
! Error handling
! This must exactly match the value in the python-lib. If ErrMsgLen changes at
! some point in the nwtc-library, this should be updated, but the logic exists
! to correctly handle different lengths of the strings
integer(IntKi), parameter :: ErrMsgLen_C = 1025
integer(IntKi), parameter :: IntfStrLen = 1025 ! length of other strings through the C interface



Expand All @@ -78,35 +91,39 @@ end subroutine SetErr
!===============================================================================================================
!--------------------------------------------- IFW INIT --------------------------------------------------------
!===============================================================================================================
SUBROUTINE IfW_C_Init(InputFileString_C, InputFileStringLength_C, InputUniformString_C, InputUniformStringLength_C, NumWindPts_C, DT_C, NumChannels_C, OutputChannelNames_C, OutputChannelUnits_C, ErrStat_C, ErrMsg_C) BIND (C, NAME='IfW_C_Init')
SUBROUTINE IfW_C_Init(IfWinputFilePassed, IfWinputFileString_C, IfWinputFileStringLength_C, OutRootName_C, &
NumWindPts_C, DT_C, DebugLevel_in, NumChannels_C, OutputChannelNames_C, OutputChannelUnits_C, &
ErrStat_C, ErrMsg_C) BIND (C, NAME='IfW_C_Init')
IMPLICIT NONE
#ifndef IMPLICIT_DLLEXPORT
!DEC$ ATTRIBUTES DLLEXPORT :: IfW_C_Init
!GCC$ ATTRIBUTES DLLEXPORT :: IfW_C_Init
#endif
TYPE(C_PTR) , INTENT(IN ) :: InputFileString_C
INTEGER(C_INT) , INTENT(IN ) :: InputFileStringLength_C
TYPE(C_PTR) , INTENT(IN ) :: InputUniformString_C
INTEGER(C_INT) , INTENT(IN ) :: InputUniformStringLength_C
INTEGER(C_INT) , INTENT(IN ) :: NumWindPts_C
REAL(C_DOUBLE) , INTENT(IN ) :: DT_C
INTEGER(C_INT) , INTENT( OUT) :: NumChannels_C
CHARACTER(KIND=C_CHAR) , INTENT( OUT) :: OutputChannelNames_C(ChanLen*MaxOutPts+1)
CHARACTER(KIND=C_CHAR) , INTENT( OUT) :: OutputChannelUnits_C(ChanLen*MaxOutPts+1)
INTEGER(C_INT) , INTENT( OUT) :: ErrStat_C
CHARACTER(KIND=C_CHAR) , INTENT( OUT) :: ErrMsg_C(ErrMsgLen_C)

! Local Variables
CHARACTER(kind=C_char, len=InputFileStringLength_C), POINTER :: InputFileString !< Input file as a single string with NULL chracter separating lines
CHARACTER(kind=C_char, len=InputUniformStringLength_C), POINTER :: UniformFileString !< Input file as a single string with NULL chracter separating lines -- Uniform wind file

REAL(DbKi) :: TimeInterval
INTEGER :: ErrStat !< aggregated error message
CHARACTER(ErrMsgLen) :: ErrMsg !< aggregated error message
INTEGER :: ErrStat2 !< temporary error status from a call
CHARACTER(ErrMsgLen) :: ErrMsg2 !< temporary error message from a call
INTEGER :: i,j,k
character(*), parameter :: RoutineName = 'IfW_C_Init' !< for error handling
integer(c_int), intent(in ) :: IfWinputFilePassed !< Write VTK outputs [0: none, 1: init only, 2: animation]
type(c_ptr), intent(in ) :: IfWinputFileString_C !< Input file as a single string with lines deliniated by C_NULL_CHAR
integer(c_int), intent(in ) :: IfWinputFileStringLength_C !< lenght of the input file string
character(kind=c_char), intent(in ) :: OutRootName_C(IntfStrLen) !< Root name to use for echo files and other
integer(c_int), intent(in ) :: NumWindPts_C
real(c_double), intent(in ) :: DT_C
integer(c_int), intent(in ) :: DebugLevel_in
integer(c_int), intent( out) :: NumChannels_C
character(kind=c_char), intent( out) :: OutputChannelNames_C(ChanLen*MaxOutPts+1)
character(kind=c_char), intent( out) :: OutputChannelUnits_C(ChanLen*MaxOutPts+1)
integer(c_int), intent( out) :: ErrStat_C
character(kind=c_char), intent( out) :: ErrMsg_C(ErrMsgLen_C)

! local variables
character(IntfStrLen) :: OutRootName !< Root name to use for echo files and other
character(IntfStrLen) :: TmpFileName !< Temporary file name if not passing AD or IfW input file contents directly
character(kind=c_char, len=IfWinputFileStringLength_C), pointer :: IfWinputFileString !< Input file as a single string with NULL chracter separating lines

real(DbKi) :: TimeInterval
integer :: ErrStat !< aggregated error message
character(ErrMsgLen) :: ErrMsg !< aggregated error message
integer :: ErrStat2 !< temporary error status from a call
character(ErrMsgLen) :: ErrMsg2 !< temporary error message from a call
integer :: i,j,k
character(*), parameter :: RoutineName = 'IfW_C_Init' !< for error handling

! Initialize error handling
ErrStat = ErrID_None
Expand All @@ -116,26 +133,65 @@ SUBROUTINE IfW_C_Init(InputFileString_C, InputFileStringLength_C, InputUniformSt
CALL DispCopyrightLicense( version%Name )
CALL DispCompileRuntimeInfo( version%Name )


! interface debugging
DebugLevel = int(DebugLevel_in,IntKi)

! Input files
OutRootName = TRANSFER( OutRootName_C, OutRootName )
i = INDEX(OutRootName,C_NULL_CHAR) - 1 ! if this has a c null character at the end...
if ( i > 0 ) OutRootName = OutRootName(1:I) ! remove it

! if non-zero, show all passed data here. Then check valid values
if (DebugLevel /= 0_IntKi) then
call WrScr(" Interface debugging level "//trim(Num2Lstr(DebugLevel))//" requested.")
call ShowPassedData()
endif
! check valid debug level
if (DebugLevel < 0_IntKi) then
ErrStat2 = ErrID_Fatal
ErrMsg2 = "Interface debug level must be 0 or greater"//NewLine// &
" 0 - none"//NewLine// &
" 1 - some summary info and variables passed through interface"//NewLine// &
" 2 - above + all position/orientation info"//NewLine// &
" 3 - above + input files (if direct passed)"//NewLine// &
" 4 - above + meshes"
if (Failed()) return;
endif

! For debugging the interface:
if (DebugLevel > 0) then
call ShowPassedData()
endif

! Get fortran pointer to C_NULL_CHAR deliniated input file as a string
CALL C_F_pointer(InputFileString_C, InputFileString)
CALL C_F_pointer(InputUniformString_C, UniformFileString)

! Store string-inputs as type FileInfoType within InflowWind_InitInputType
CALL InitFileInfo(InputFileString, InitInp%PassedFileInfo, ErrStat2, ErrMsg2); if (Failed()) return
InitInp%FilePassingMethod = 1_IntKi ! read file and pass as FileInfoType structure

! store Uniform File strings if they are non-zero sized
if (len(UniformFileString) > 1) then
CALL InitFileInfo(UniformFileString, InitInp%WindType2Info, ErrStat2, ErrMsg2); if (Failed()) return
InitInp%WindType2UseInputFile = .FALSE.
else ! Default to reading from disk
InitInp%WindType2UseInputFile = .TRUE.
CALL C_F_pointer(IfWinputFileString_C, IfWinputFileString)

! Format IfW input file contents
if (IfWinputFilePassed==1_c_int) then
InitInp%FilePassingMethod = 1_IntKi ! Don't try to read an input -- use passed data instead (blades and AF tables not passed) using FileInfoType
InitInp%InputFileName = "passed_ifw_file" ! not actually used
call InitFileInfo(IfWinputFileString, InitInp%PassedFileInfo, ErrStat2, ErrMsg2); if (Failed()) return
else
InitInp%FilePassingMethod = 0_IntKi ! Read input info from a primary input file
i = min(IntfStrLen,IfWinputFileStringLength_C)
TmpFileName = ''
TmpFileName(1:i) = IfWinputFileString(1:i)
i = INDEX(TmpFileName,C_NULL_CHAR) - 1 ! if this has a c null character at the end...
if ( i > 0 ) TmpFileName = TmpFileName(1:I) ! remove it
InitInp%InputFileName = TmpFileName
endif

! For diagnostic purposes, the following can be used to display the contents
! of the InFileInfo data structure.
! CU is the screen -- system dependent.
if (DebugLevel >= 3) then
if (IfWinputFilePassed==1_c_int) call Print_FileInfo_Struct( CU, InitInp%PassedFileInfo )
endif

! Set other inputs for calling InflowWind_Init
InitInp%NumWindPoints = NumWindPts_C
InitInp%InputFileName = "passed_ifw_file" ! dummy
InitInp%RootName = "ifwRoot" ! used for making echo files
InitInp%NumWindPoints = int(NumWindPts_C, IntKi)
InitInp%RootName = OutRootName ! used for making echo files
TimeInterval = REAL(DT_C, DbKi)

! Call the main subroutine InflowWind_Init - only need InitInp and TimeInterval as inputs, the rest are set by InflowWind_Init
Expand Down Expand Up @@ -174,6 +230,30 @@ logical function Failed()
end function Failed
subroutine Cleanup() ! NOTE: we are ignoring any error reporting from here
end subroutine Cleanup

!> This subroutine prints out all the variables that are passed in. Use this only
!! for debugging the interface on the Fortran side.
subroutine ShowPassedData()
character(1) :: TmpFlag
integer :: i,j
call WrSCr("")
call WrScr("-----------------------------------------------------------")
call WrScr("Interface debugging: Variables passed in through interface")
call WrScr(" ADI_C_Init")
call WrScr(" --------------------------------------------------------")
call WrScr(" FileInfo")
TmpFlag="F"; if (IfWinputFilePassed==1_c_int) TmpFlag="T"
call WrScr(" IfWinputFilePassed_C "//TmpFlag )
call WrScr(" IfWinputFileString_C (ptr addr)"//trim(Num2LStr(LOC(IfWinputFileString_C))) )
call WrScr(" IfWinputFileStringLength_C "//trim(Num2LStr( IfWinputFileStringLength_C )) )
call WrScr(" OutRootName "//trim(OutRootName) )
call WrScr(" Input variables")
call WrScr(" NumWindPts_C "//trim(Num2LStr( NumWindPts_C)) )
call WrScr(" Time variables")
call WrScr(" DT_C "//trim(Num2LStr( DT_C )) )
call WrScr("-----------------------------------------------------------")
end subroutine ShowPassedData

END SUBROUTINE IfW_C_Init

!===============================================================================================================
Expand Down
Loading

0 comments on commit bc6c481

Please sign in to comment.