diff --git a/modules/inflowwind/python-lib/inflowwind_library.py b/modules/inflowwind/python-lib/inflowwind_library.py index 26e6eb599a..bc741443e8 100644 --- a/modules/inflowwind/python-lib/inflowwind_library.py +++ b/modules/inflowwind/python-lib/inflowwind_library.py @@ -52,6 +52,10 @@ 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 @@ -59,6 +63,9 @@ def __init__(self, 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) @@ -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 @@ -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 diff --git a/modules/inflowwind/src/IfW_C_Binding.f90 b/modules/inflowwind/src/IfW_C_Binding.f90 index 5c9c8ef568..8299d6010f 100644 --- a/modules/inflowwind/src/IfW_C_Binding.f90 +++ b/modules/inflowwind/src/IfW_C_Binding.f90 @@ -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 @@ -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 @@ -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 @@ -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 !=============================================================================================================== diff --git a/reg_tests/executeInflowwindPyRegressionCase.py b/reg_tests/executeInflowwindPyRegressionCase.py index 0f5bdca26e..6990257a48 100644 --- a/reg_tests/executeInflowwindPyRegressionCase.py +++ b/reg_tests/executeInflowwindPyRegressionCase.py @@ -101,7 +101,7 @@ ### Run inflowwind on the test case if not noExec: - caseInputFile = os.path.join(testBuildDirectory, "inflowWind_testDriver.py") + caseInputFile = os.path.join(testBuildDirectory, "py_ifw_driver.py") returnCode = openfastDrivers.runInflowwindDriverCase(caseInputFile, executable) if returnCode != 0: sys.exit(returnCode*10) diff --git a/reg_tests/r-test b/reg_tests/r-test index d641aeb7df..28cdf7ba6f 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit d641aeb7df530a0249b0679ac5de6829069c58c4 +Subproject commit 28cdf7ba6fcf36b6a97d17ad42b372a33281cdb6