diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..ed9db46f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## Description and Purpose + +## Type of change +What types of change is it? +_Select the appropriate type(s) that describe this PR_ + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (non-backwards-compatible fix or feature) +- [ ] Code style update (formatting, renaming) +- [ ] Refactoring (no functional changes, no API changes) +- [ ] Documentation update +- [ ] Maintenance update +- [ ] Other (please describe) + +## Github issues addressed, if one exists + +## Examples/Testing, if applicable + + diff --git a/.github/workflows/CI_rosco.yml b/.github/workflows/CI_rosco.yml new file mode 100644 index 00000000..e20d87d9 --- /dev/null +++ b/.github/workflows/CI_rosco.yml @@ -0,0 +1,62 @@ +name: CI_rosco-toolbox + +# We run CI on push commits on all branches +on: [push, pull_request] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + name: Build (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: ["ubuntu-latest", "macOS-latest", "windows-latest"] + python-version: ["3.8"] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup environment + uses: conda-incubator/setup-miniconda@v2 + with: + miniconda-version: "latest" + channels: conda-forge, general + auto-update-conda: true + python-version: 3.8 + environment-file: environment.yml + + # Install compilers + - name: Add dependencies linux + if: false == contains( matrix.os, 'windows') + shell: pwsh + run: | + conda install -y compilers + + - name: Add dependencies windows + if: true == contains( matrix.os, 'windows') + shell: bash -l {0} + run: | + conda install -y m2w64-toolchain + + # Install ROSCO linux + - name: Compile ROSCO linux + if: false == contains( matrix.os, 'windows') + shell: pwsh + run: | + mkdir build + cd build + cmake .. + make install + + # Install ROSCO windows + - name: Compile ROSCO windows + if: true == contains( matrix.os, 'windows') + shell: pwsh + run: | + mkdir build + cd build + cmake .. -G "MinGW Makefiles" + make install + diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..bd96d34f --- /dev/null +++ b/environment.yml @@ -0,0 +1,3 @@ +channels: + - conda-forge + - defaults \ No newline at end of file diff --git a/src/Constants.f90 b/src/Constants.f90 index a205eaa5..766c6987 100644 --- a/src/Constants.f90 +++ b/src/Constants.f90 @@ -16,4 +16,5 @@ MODULE Constants REAL(8), PARAMETER :: PI = 3.14159265359 ! Mathematical constant pi INTEGER(4), PARAMETER :: NP_1 = 1 ! First rotational harmonic INTEGER(4), PARAMETER :: NP_2 = 2 ! Second rotational harmonic + CHARACTER(*), PARAMETER :: NewLine = ACHAR(10) ! The delimiter for New Lines [ Windows is CHAR(13)//CHAR(10); MAC is CHAR(13); Unix is CHAR(10) {CHAR(13)=\r is a line feed, CHAR(10)=\n is a new line}] END MODULE Constants \ No newline at end of file diff --git a/src/ControllerBlocks.f90 b/src/ControllerBlocks.f90 index d9613b92..00d2d338 100644 --- a/src/ControllerBlocks.f90 +++ b/src/ControllerBlocks.f90 @@ -31,6 +31,71 @@ MODULE ControllerBlocks IMPLICIT NONE CONTAINS +! ----------------------------------------------------------------------------------- + ! Calculate setpoints for primary control actions + SUBROUTINE ComputeVariablesSetpoints(CntrPar, LocalVar, objInst) + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances + USE Constants + ! Allocate variables + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(LocalVariables), INTENT(INOUT) :: LocalVar + TYPE(ObjectInstances), INTENT(INOUT) :: objInst + + REAL(8) :: VS_RefSpd ! Referece speed for variable speed torque controller, [rad/s] + REAL(8) :: PC_RefSpd ! Referece speed for pitch controller, [rad/s] + REAL(8) :: Omega_op ! Optimal TSR-tracking generator speed, [rad/s] + ! temp + ! REAL(8) :: VS_TSRop = 7.5 + + ! ----- Calculate yaw misalignment error ----- + LocalVar%Y_MErr = LocalVar%Y_M + CntrPar%Y_MErrSet ! Yaw-alignment error + + ! ----- Pitch controller speed and power error ----- + ! Implement setpoint smoothing + IF (LocalVar%SS_DelOmegaF < 0) THEN + PC_RefSpd = CntrPar%PC_RefSpd - LocalVar%SS_DelOmegaF + ELSE + PC_RefSpd = CntrPar%PC_RefSpd + ENDIF + + LocalVar%PC_SpdErr = PC_RefSpd - LocalVar%GenSpeedF ! Speed error + LocalVar%PC_PwrErr = CntrPar%VS_RtPwr - LocalVar%VS_GenPwr ! Power error + + ! ----- Torque controller reference errors ----- + ! Define VS reference generator speed [rad/s] + IF ((CntrPar%VS_ControlMode == 2) .OR. (CntrPar%VS_ControlMode == 3)) THEN + VS_RefSpd = (CntrPar%VS_TSRopt * LocalVar%We_Vw_F / CntrPar%WE_BladeRadius) * CntrPar%WE_GearboxRatio + VS_RefSpd = saturate(VS_RefSpd,CntrPar%VS_MinOMSpd, CntrPar%VS_RefSpd) + ELSE + VS_RefSpd = CntrPar%VS_RefSpd + ENDIF + + ! Implement setpoint smoothing + IF (LocalVar%SS_DelOmegaF > 0) THEN + VS_RefSpd = VS_RefSpd - LocalVar%SS_DelOmegaF + ENDIF + + ! Force zero torque in shutdown mode + IF (LocalVar%SD) THEN + VS_RefSpd = CntrPar%VS_MinOMSpd + ENDIF + + ! Force minimum rotor speed + VS_RefSpd = max(VS_RefSpd, CntrPar%VS_MinOmSpd) + + ! TSR-tracking reference error + IF ((CntrPar%VS_ControlMode == 2) .OR. (CntrPar%VS_ControlMode == 3)) THEN + LocalVar%VS_SpdErr = VS_RefSpd - LocalVar%GenSpeedF + ENDIF + + ! Define transition region setpoint errors + LocalVar%VS_SpdErrAr = VS_RefSpd - LocalVar%GenSpeedF ! Current speed error - Region 2.5 PI-control (Above Rated) + LocalVar%VS_SpdErrBr = CntrPar%VS_MinOMSpd - LocalVar%GenSpeedF ! Current speed error - Region 1.5 PI-control (Below Rated) + + ! Region 3 minimum pitch angle for state machine + LocalVar%VS_Rgn3Pitch = LocalVar%PC_MinPit + CntrPar%PC_Switch + + END SUBROUTINE ComputeVariablesSetpoints !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE StateMachine(CntrPar, LocalVar) ! State machine, determines the state of the wind turbine to specify the corresponding control actions @@ -101,11 +166,11 @@ SUBROUTINE StateMachine(CntrPar, LocalVar) END IF END SUBROUTINE StateMachine !------------------------------------------------------------------------------------------------------------------------------- - SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) + SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar, ErrVar) ! Wind Speed Estimator estimates wind speed at hub height. Currently implements two types of estimators ! WE_Mode = 0, Filter hub height wind speed as passed from servodyn using first order low pass filter with 1Hz cornering frequency ! WE_Mode = 1, Use Inversion and Inveriance filter as defined by Ortege et. al. - USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ObjectInstances, PerformanceData, DebugVariables + USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ObjectInstances, PerformanceData, DebugVariables, ErrorVariables IMPLICIT NONE ! Inputs @@ -114,6 +179,8 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) TYPE(ObjectInstances), INTENT(INOUT) :: objInst TYPE(PerformanceData), INTENT(INOUT) :: PerfData TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + ! Allocate Variables REAL(8) :: F_WECornerFreq ! Corner frequency (-3dB point) for first order low pass filter for measured hub height wind speed [Hz] @@ -143,6 +210,9 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) REAL(8) :: R_m ! Measurement noise covariance [(rad/s)^2] REAL(8), DIMENSION(3,1), SAVE :: B + + CHARACTER(*), PARAMETER :: RoutineName = 'WindSpeedEstimator' + ! ---- Debug Inputs ------ DebugVar%WE_b = LocalVar%PC_PitComTF*R2D DebugVar%WE_w = LocalVar%RotSpeedF @@ -152,7 +222,10 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) ! Inversion and Invariance Filter implementation IF (CntrPar%WE_Mode == 1) THEN - LocalVar%WE_VwIdot = CntrPar%WE_Gamma/CntrPar%WE_Jtot*(LocalVar%VS_LastGenTrq*CntrPar%WE_GearboxRatio - AeroDynTorque(LocalVar, CntrPar, PerfData)) + ! Compute AeroDynTorque + Tau_r = AeroDynTorque(LocalVar, CntrPar, PerfData, ErrVar) + + LocalVar%WE_VwIdot = CntrPar%WE_Gamma/CntrPar%WE_Jtot*(LocalVar%VS_LastGenTrq*CntrPar%WE_GearboxRatio - Tau_r) LocalVar%WE_VwI = LocalVar%WE_VwI + LocalVar%WE_VwIdot*LocalVar%DT LocalVar%WE_Vw = LocalVar%WE_VwI + CntrPar%WE_Gamma*LocalVar%RotSpeedF @@ -180,11 +253,13 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) ELSE ! Find estimated operating Cp and system pole - A_op = interp1d(CntrPar%WE_FOPoles_v,CntrPar%WE_FOPoles,v_h) + A_op = interp1d(CntrPar%WE_FOPoles_v,CntrPar%WE_FOPoles,v_h,ErrVar) ! TEST INTERP2D lambda = LocalVar%RotSpeed * CntrPar%WE_BladeRadius/v_h - Cp_op = interp2d(PerfData%Beta_vec,PerfData%TSR_vec,PerfData%Cp_mat, LocalVar%BlPitch(1)*R2D, lambda ) + + Cp_op = interp2d(PerfData%Beta_vec,PerfData%TSR_vec,PerfData%Cp_mat, LocalVar%BlPitch(1)*R2D, lambda, ErrVar) + Cp_op = max(0.0,Cp_op) ! Update Jacobian @@ -200,7 +275,7 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) Q(3,3) = (2.0**2.0)/600.0 ! Prediction update - Tau_r = AeroDynTorque(LocalVar,CntrPar,PerfData) + Tau_r = AeroDynTorque(LocalVar, CntrPar, PerfData, ErrVar) a = PI * v_m/(2.0*L) dxh(1,1) = 1.0/CntrPar%WE_Jtot * (Tau_r - CntrPar%WE_GearboxRatio * LocalVar%VS_LastGenTrqF) dxh(2,1) = -a*v_t @@ -236,6 +311,11 @@ SUBROUTINE WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) LocalVar%WE_Vw = LPFilter(LocalVar%HorWindV, LocalVar%DT, F_WECornerFreq, LocalVar%iStatus, .FALSE., objInst%instLPF) ENDIF + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + END SUBROUTINE WindSpeedEstimator !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE SetpointSmoother(LocalVar, CntrPar, objInst) @@ -255,7 +335,7 @@ SUBROUTINE SetpointSmoother(LocalVar, CntrPar, objInst) ! ------ Setpoint Smoothing ------ IF ( CntrPar%SS_Mode == 1) THEN ! Find setpoint shift amount - DelOmega = ((LocalVar%PC_PitComT - CntrPar%PC_MinPit)/0.524) * CntrPar%SS_VSGain - ((LocalVar%VS_GenPwr - LocalVar%VS_LastGenTrq))/CntrPar%VS_RtPwr * CntrPar%SS_PCGain ! Normalize to 30 degrees for now + DelOmega = ((LocalVar%PC_PitComT - LocalVar%PC_MinPit)/0.524) * CntrPar%SS_VSGain - ((CntrPar%VS_RtPwr - LocalVar%VS_LastGenPwr))/CntrPar%VS_RtPwr * CntrPar%SS_PCGain ! Normalize to 30 degrees for now DelOmega = DelOmega * CntrPar%PC_RefSpd ! Filter LocalVar%SS_DelOmegaF = LPFilter(DelOmega, LocalVar%DT, CntrPar%F_SSCornerFreq, LocalVar%iStatus, .FALSE., objInst%instLPF) @@ -265,20 +345,28 @@ SUBROUTINE SetpointSmoother(LocalVar, CntrPar, objInst) END SUBROUTINE SetpointSmoother !------------------------------------------------------------------------------------------------------------------------------- - REAL FUNCTION PitchSaturation(LocalVar, CntrPar, objInst, DebugVar) + REAL FUNCTION PitchSaturation(LocalVar, CntrPar, objInst, DebugVar, ErrVar) ! PitchSaturation defines a minimum blade pitch angle based on a lookup table provided by DISCON.IN ! SS_Mode = 0, No setpoint smoothing ! SS_Mode = 1, Implement pitch saturation - USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ObjectInstances, DebugVariables + USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ObjectInstances, DebugVariables, ErrorVariables IMPLICIT NONE ! Inputs TYPE(ControlParameters), INTENT(IN) :: CntrPar TYPE(LocalVariables), INTENT(INOUT) :: LocalVar TYPE(ObjectInstances), INTENT(INOUT) :: objInst TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + + CHARACTER(*), PARAMETER :: RoutineName = 'PitchSaturation' ! Define minimum blade pitch angle as a function of estimated wind speed - PitchSaturation = interp1d(CntrPar%PS_WindSpeeds, CntrPar%PS_BldPitchMin, LocalVar%WE_Vw_F) + PitchSaturation = interp1d(CntrPar%PS_WindSpeeds, CntrPar%PS_BldPitchMin, LocalVar%WE_Vw_F, ErrVar) + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF END FUNCTION PitchSaturation !------------------------------------------------------------------------------------------------------------------------------- diff --git a/src/Controllers.f90 b/src/Controllers.f90 index 48ddadc7..acb94765 100644 --- a/src/Controllers.f90 +++ b/src/Controllers.f90 @@ -31,7 +31,7 @@ MODULE Controllers CONTAINS !------------------------------------------------------------------------------------------------------------------------------- - SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) + SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) ! Blade pitch controller, generally maximizes rotor speed below rated (region 2) and regulates rotor speed above rated (region 3) ! PC_State = 0, fix blade pitch to fine pitch angle (PC_FinePit) ! PC_State = 1, is gain scheduled PI controller @@ -39,17 +39,21 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) ! Individual pitch control ! Tower fore-aft damping ! Sine excitation on pitch - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, DebugVariables + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, DebugVariables, ErrorVariables ! Inputs TYPE(ControlParameters), INTENT(INOUT) :: CntrPar TYPE(LocalVariables), INTENT(INOUT) :: LocalVar TYPE(ObjectInstances), INTENT(INOUT) :: objInst - TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(DebugVariables), INTENT(INOUT) :: DebugVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + ! Allocate Variables: - REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from the DLL controller. - INTEGER(4) :: K ! Index used for looping through blades. - REAL(8), Save :: PitComT_Last + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from the DLL controller. + INTEGER(4) :: K ! Index used for looping through blades. + REAL(8), Save :: PitComT_Last + + CHARACTER(*), PARAMETER :: RoutineName = 'PitchControl' ! ------- Blade Pitch Controller -------- ! Load PC State @@ -60,10 +64,10 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) END IF ! Compute (interpolate) the gains based on previously commanded blade pitch angles and lookup table: - LocalVar%PC_KP = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KP, LocalVar%PC_PitComTF) ! Proportional gain - LocalVar%PC_KI = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KI, LocalVar%PC_PitComTF) ! Integral gain - LocalVar%PC_KD = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KD, LocalVar%PC_PitComTF) ! Derivative gain - LocalVar%PC_TF = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_TF, LocalVar%PC_PitComTF) ! TF gains (derivative filter) !NJA - need to clarify + LocalVar%PC_KP = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KP, LocalVar%PC_PitComTF, ErrVar) ! Proportional gain + LocalVar%PC_KI = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KI, LocalVar%PC_PitComTF, ErrVar) ! Integral gain + LocalVar%PC_KD = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_KD, LocalVar%PC_PitComTF, ErrVar) ! Derivative gain + LocalVar%PC_TF = interp1d(CntrPar%PC_GS_angles, CntrPar%PC_GS_TF, LocalVar%PC_PitComTF, ErrVar) ! TF gains (derivative filter) !NJA - need to clarify ! Compute the collective pitch command associated with the proportional and integral gains: IF (LocalVar%iStatus == 0) THEN @@ -88,7 +92,7 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) ! Pitch Saturation IF (CntrPar%PS_Mode == 1) THEN - LocalVar%PC_MinPit = PitchSaturation(LocalVar,CntrPar,objInst,DebugVar) + LocalVar%PC_MinPit = PitchSaturation(LocalVar,CntrPar,objInst,DebugVar, ErrVar) LocalVar%PC_MinPit = max(LocalVar%PC_MinPit, CntrPar%PC_FinePit) ELSE LocalVar%PC_MinPit = CntrPar%PC_FinePit @@ -124,6 +128,12 @@ SUBROUTINE PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) avrSWAP(43) = LocalVar%PitCom(2) ! " avrSWAP(44) = LocalVar%PitCom(3) ! " avrSWAP(45) = LocalVar%PitCom(1) ! Use the command angle of blade 1 if using collective pitch + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + END SUBROUTINE PitchControl !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE VariableSpeedControl(avrSWAP, CntrPar, LocalVar, objInst) @@ -195,6 +205,7 @@ SUBROUTINE VariableSpeedControl(avrSWAP, CntrPar, LocalVar, objInst) ! Reset the value of LocalVar%VS_LastGenTrq to the current values: LocalVar%VS_LastGenTrq = LocalVar%GenTq + LocalVar%VS_LastGenPwr = LocalVar%VS_GenPwr ! Set the command generator torque (See Appendix A of Bladed User's Guide): avrSWAP(47) = MAX(0.0, LocalVar%VS_LastGenTrq) ! Demanded generator torque, prevent negatives. @@ -397,9 +408,9 @@ SUBROUTINE FlapControl(avrSWAP, CntrPar, LocalVar, objInst) LocalVar%Flp_Angle(2) = CntrPar%Flp_Angle LocalVar%Flp_Angle(3) = CntrPar%Flp_Angle ! Initialize filter - RootMOOP_F(1) = SecLPFilter(LocalVar%rootMOOP(1),LocalVar%DT, CntrPar%F_FlpCornerFreq, CntrPar%F_FlpDamping, LocalVar%iStatus, .FALSE.,objInst%instSecLPF) - RootMOOP_F(2) = SecLPFilter(LocalVar%rootMOOP(2),LocalVar%DT, CntrPar%F_FlpCornerFreq, CntrPar%F_FlpDamping, LocalVar%iStatus, .FALSE.,objInst%instSecLPF) - RootMOOP_F(3) = SecLPFilter(LocalVar%rootMOOP(3),LocalVar%DT, CntrPar%F_FlpCornerFreq, CntrPar%F_FlpDamping, LocalVar%iStatus, .FALSE.,objInst%instSecLPF) + RootMOOP_F(1) = SecLPFilter(LocalVar%rootMOOP(1),LocalVar%DT, CntrPar%F_FlpCornerFreq(1), CntrPar%F_FlpCornerFreq(2), LocalVar%iStatus, .FALSE.,objInst%instSecLPF) + RootMOOP_F(2) = SecLPFilter(LocalVar%rootMOOP(2),LocalVar%DT, CntrPar%F_FlpCornerFreq(1), CntrPar%F_FlpCornerFreq(2), LocalVar%iStatus, .FALSE.,objInst%instSecLPF) + RootMOOP_F(3) = SecLPFilter(LocalVar%rootMOOP(3),LocalVar%DT, CntrPar%F_FlpCornerFreq(1), CntrPar%F_FlpCornerFreq(2), LocalVar%iStatus, .FALSE.,objInst%instSecLPF) ! Initialize controller IF (CntrPar%Flp_Mode == 2) THEN LocalVar%Flp_Angle(K) = PIIController(RootMyb_VelErr(K), 0 - LocalVar%Flp_Angle(K), CntrPar%Flp_Kp, CntrPar%Flp_Ki, 0.05, -CntrPar%Flp_MaxPit , CntrPar%Flp_MaxPit , LocalVar%DT, 0.0, .TRUE., objInst%instPI) @@ -420,7 +431,7 @@ SUBROUTINE FlapControl(avrSWAP, CntrPar, LocalVar, objInst) ELSEIF (CntrPar%Flp_Mode == 2) THEN DO K = 1,LocalVar%NumBl ! LPF Blade root bending moment - RootMOOP_F(K) = SecLPFilter(LocalVar%rootMOOP(K),LocalVar%DT, CntrPar%F_FlpCornerFreq, CntrPar%F_FlpDamping, LocalVar%iStatus, .FALSE.,objInst%instSecLPF) + RootMOOP_F(K) = SecLPFilter(LocalVar%rootMOOP(K),LocalVar%DT, CntrPar%F_FlpCornerFreq(1), CntrPar%F_FlpCornerFreq(2), LocalVar%iStatus, .FALSE.,objInst%instSecLPF) ! Find derivative and derivative error of blade root bending moment RootMyb_Vel(K) = (RootMOOP_F(K) - RootMyb_Last(K))/LocalVar%DT diff --git a/src/DISCON.F90 b/src/DISCON.F90 index 7142e347..9ca37a12 100644 --- a/src/DISCON.F90 +++ b/src/DISCON.F90 @@ -54,6 +54,9 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME TYPE(ObjectInstances), SAVE :: objInst TYPE(PerformanceData), SAVE :: PerfData TYPE(DebugVariables), SAVE :: DebugVar +TYPE(ErrorVariables), SAVE :: ErrVar + +CHARACTER(*), PARAMETER :: RoutineName = 'ROSCO' RootName = TRANSFER(avcOUTNAME, RootName) !------------------------------------------------------------------------------------------------------------------------------ @@ -61,22 +64,30 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME !------------------------------------------------------------------------------------------------------------------------------ ! Read avrSWAP array into derived types/variables CALL ReadAvrSWAP(avrSWAP, LocalVar) -CALL SetParameters(avrSWAP, aviFAIL, accINFILE, ErrMsg, SIZE(avcMSG), CntrPar, LocalVar, objInst, PerfData) +CALL SetParameters(avrSWAP, accINFILE, SIZE(avcMSG), CntrPar, LocalVar, objInst, PerfData, ErrVar) CALL PreFilterMeasuredSignals(CntrPar, LocalVar, objInst) -IF ((LocalVar%iStatus >= 0) .AND. (aviFAIL >= 0)) THEN ! Only compute control calculations if no error has occurred and we are not on the last time step - CALL WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar) +IF ((LocalVar%iStatus >= 0) .AND. (ErrVar%aviFAIL >= 0)) THEN ! Only compute control calculations if no error has occurred and we are not on the last time step + CALL WindSpeedEstimator(LocalVar, CntrPar, objInst, PerfData, DebugVar, ErrVar) CALL ComputeVariablesSetpoints(CntrPar, LocalVar, objInst) CALL StateMachine(CntrPar, LocalVar) CALL SetpointSmoother(LocalVar, CntrPar, objInst) CALL ComputeVariablesSetpoints(CntrPar, LocalVar, objInst) CALL VariableSpeedControl(avrSWAP, CntrPar, LocalVar, objInst) - CALL PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar) + CALL PitchControl(avrSWAP, CntrPar, LocalVar, objInst, DebugVar, ErrVar) CALL YawRateControl(avrSWAP, CntrPar, LocalVar, objInst) CALL FlapControl(avrSWAP, CntrPar, LocalVar, objInst) CALL Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, SIZE(avcOUTNAME)) END IF -avcMSG = TRANSFER(TRIM(ErrMsg)//C_NULL_CHAR, avcMSG, SIZE(avcMSG)) +! Add RoutineName to error message +IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + print * , TRIM(ErrVar%ErrMsg) +ENDIF +ErrMsg = ErrVar%ErrMsg +avcMSG = TRANSFER(TRIM(ErrVar%ErrMsg)//C_NULL_CHAR, avcMSG, SIZE(avcMSG)) +aviFAIL = ErrVar%aviFAIL + RETURN END SUBROUTINE DISCON diff --git a/src/Filters.f90 b/src/Filters.f90 index d32e7f19..ad4d8a0a 100644 --- a/src/Filters.f90 +++ b/src/Filters.f90 @@ -276,12 +276,11 @@ SUBROUTINE PreFilterMeasuredSignals(CntrPar, LocalVar, objInst) IF (CntrPar%Fl_Mode == 1) THEN ! Force to start at 0 IF (LocalVar%iStatus == 0) THEN - LocalVar%NacIMU_FA_AccF = SecLPFilter(0., LocalVar%DT, CntrPar%F_FlCornerFreq, CntrPar%F_FlDamping, LocalVar%iStatus, .FALSE., objInst%instSecLPF) ! Fixed Damping + LocalVar%NacIMU_FA_AccF = SecLPFilter(0., LocalVar%DT, CntrPar%F_FlCornerFreq(1), CntrPar%F_FlCornerFreq(2), LocalVar%iStatus, .FALSE., objInst%instSecLPF) ! Fixed Damping ELSE - LocalVar%NacIMU_FA_AccF = SecLPFilter(LocalVar%NacIMU_FA_Acc, LocalVar%DT, CntrPar%F_FlCornerFreq, CntrPar%F_FlDamping, LocalVar%iStatus, .FALSE., objInst%instSecLPF) ! Fixed Damping + LocalVar%NacIMU_FA_AccF = SecLPFilter(LocalVar%NacIMU_FA_Acc, LocalVar%DT, CntrPar%F_FlCornerFreq(1), CntrPar%F_FlCornerFreq(2), LocalVar%iStatus, .FALSE., objInst%instSecLPF) ! Fixed Damping ENDIF LocalVar%NacIMU_FA_AccF = HPFilter(LocalVar%NacIMU_FA_AccF, LocalVar%DT, 0.0167, LocalVar%iStatus, .FALSE., objInst%instHPF) - ! LocalVar%NacIMU_FA_AccF = NotchFilterSlopes(LocalVar%NacIMU_FA_Acc, LocalVar%DT, CntrPar%F_FlCornerFreq, CntrPar%F_FlDamping, LocalVar%iStatus, .FALSE., objInst%instNotchSlopes) ! Fixed Damping IF (CntrPar%F_NotchType >= 2) THEN LocalVar%NACIMU_FA_AccF = NotchFilter(LocalVar%NacIMU_FA_AccF, LocalVar%DT, CntrPar%F_NotchCornerFreq, CntrPar%F_NotchBetaNumDen(1), CntrPar%F_NotchBetaNumDen(2), LocalVar%iStatus, .FALSE., objInst%instNotch) ! Fixed Damping diff --git a/src/Functions.f90 b/src/Functions.f90 index 9d0c330d..e0e3a70a 100644 --- a/src/Functions.f90 +++ b/src/Functions.f90 @@ -47,6 +47,7 @@ REAL FUNCTION saturate(inputValue, minValue, maxValue) saturate = MIN(MAX(inputValue,minValue), maxValue) END FUNCTION saturate + !------------------------------------------------------------------------------------------------------------------------------- REAL FUNCTION ratelimit(inputSignal, inputSignalPrev, minRate, maxRate, DT) ! Saturates inputValue. Makes sure it is not smaller than minValue and not larger than maxValue @@ -65,6 +66,7 @@ REAL FUNCTION ratelimit(inputSignal, inputSignalPrev, minRate, maxRate, DT) ratelimit = inputSignalPrev + rate*DT ! Saturate the overall command using the rate limit END FUNCTION ratelimit + !------------------------------------------------------------------------------------------------------------------------------- REAL FUNCTION PIController(error, kp, ki, minValue, maxValue, DT, I0, reset, inst) ! PI controller, with output saturation @@ -105,6 +107,7 @@ REAL FUNCTION PIController(error, kp, ki, minValue, maxValue, DT, I0, reset, ins inst = inst + 1 END FUNCTION PIController + !------------------------------------------------------------------------------------------------------------------------------- REAL(8) FUNCTION PIIController(error, error2, kp, ki, ki2, minValue, maxValue, DT, I0, reset, inst) ! PI controller, with output saturation. @@ -155,16 +158,44 @@ REAL(8) FUNCTION PIIController(error, error2, kp, ki, ki2, minValue, maxValue, D inst = inst + 1 END FUNCTION PIIController -!------------------------------------------------------------------------------------------------------------------------------- - REAL FUNCTION interp1d(xData, yData, xq) - ! interp1d 1-D interpolation (table lookup), xData should be monotonically increasing +!------------------------------------------------------------------------------------------------------------------------------- + REAL FUNCTION interp1d(xData, yData, xq, ErrVar) + ! interp1d 1-D interpolation (table lookup), xData should be strictly increasing + + USE ROSCO_Types, ONLY : ErrorVariables IMPLICIT NONE + ! Inputs REAL(8), DIMENSION(:), INTENT(IN) :: xData ! Provided x data (vector), to be interpolated REAL(8), DIMENSION(:), INTENT(IN) :: yData ! Provided y data (vector), to be interpolated REAL(8), INTENT(IN) :: xq ! x-value for which the y value has to be interpolated INTEGER(4) :: I ! Iteration index + + ! Error Catching + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + INTEGER(4) :: I_DIFF + + CHARACTER(*), PARAMETER :: RoutineName = 'interp1d' + + + ! Catch Errors + ! Are xData and yData the same size? + IF (SIZE(xData) .NE. SIZE(yData)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' xData and yData are not the same size' + WRITE(ErrVar%ErrMsg,"(A,I2,A,I2,A)") " SIZE(xData) =", SIZE(xData), & + ' and SIZE(yData) =', SIZE(yData),' are not the same' + END IF + + ! Is xData non decreasing + DO I_DIFF = 1, size(xData) - 1 + IF (xData(I_DIFF + 1) - xData(I_DIFF) <= 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' xData is not strictly increasing' + EXIT + END IF + END DO ! Interpolate IF (xq <= MINVAL(xData)) THEN @@ -181,10 +212,16 @@ REAL FUNCTION interp1d(xData, yData, xq) END IF END DO END IF + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF END FUNCTION interp1d + !------------------------------------------------------------------------------------------------------------------------------- - REAL FUNCTION interp2d(xData, yData, zData, xq, yq) + REAL FUNCTION interp2d(xData, yData, zData, xq, yq, ErrVar) ! interp2d 2-D interpolation (table lookup). Query done using bilinear interpolation. ! Note that the interpolated matrix with associated query vectors may be different than "standard", - zData should be formatted accordingly ! - xData follows the matrix from left to right @@ -196,13 +233,17 @@ REAL FUNCTION interp2d(xData, yData, zData, xq, yq) ! 5| d e f ! 6| g H i + USE ROSCO_Types, ONLY : ErrorVariables + IMPLICIT NONE + ! Inputs - REAL(8), DIMENSION(:), INTENT(IN) :: xData ! Provided x data (vector), to find query point (should be monotonically increasing) - REAL(8), DIMENSION(:), INTENT(IN) :: yData ! Provided y data (vector), to find query point (should be monotonically increasing) + REAL(8), DIMENSION(:), INTENT(IN) :: xData ! Provided x data (vector), to find query point (should be strictly increasing) + REAL(8), DIMENSION(:), INTENT(IN) :: yData ! Provided y data (vector), to find query point (should be strictly increasing) REAL(8), DIMENSION(:,:), INTENT(IN) :: zData ! Provided z data (vector), to be interpolated REAL(8), INTENT(IN) :: xq ! x-value for which the z value has to be interpolated REAL(8), INTENT(IN) :: yq ! y-value for which the z value has to be interpolated + ! Allocate variables INTEGER(4) :: i ! Iteration index & query index, x-direction INTEGER(4) :: ii ! Iteration index & second que . ry index, x-direction @@ -210,26 +251,66 @@ REAL FUNCTION interp2d(xData, yData, zData, xq, yq) INTEGER(4) :: jj ! Iteration index & second query index, y-direction REAL(8), DIMENSION(2,2) :: fQ ! zData value at query points for bilinear interpolation REAL(8), DIMENSION(1) :: fxy ! Interpolated z-data point to be returned - REAL(8) :: fxy1 ! zData value at query point for bilinear interpolation - REAL(8) :: fxy2 ! zData value at query point for bilinear interpolation + REAL(8) :: fxy1 ! zData value at query point for bilinear interpolation + REAL(8) :: fxy2 ! zData value at query point for bilinear interpolation + LOGICAL :: edge + + ! Error Catching + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + INTEGER(4) :: I_DIFF + + CHARACTER(*), PARAMETER :: RoutineName = 'interp2d' + ! Error catching + ! Are xData and zData(:,1) the same size? + IF (SIZE(xData) .NE. SIZE(zData,2)) THEN + ErrVar%aviFAIL = -1 + WRITE(ErrVar%ErrMsg,"(A,I4,A,I4,A)") " SIZE(xData) =", SIZE(xData), & + ' and SIZE(zData,1) =', SIZE(zData,2),' are not the same' + END IF + + ! Are yData and zData(1,:) the same size? + IF (SIZE(yData) .NE. SIZE(zData,1)) THEN + ErrVar%aviFAIL = -1 + WRITE(ErrVar%ErrMsg,"(A,I4,A,I4,A)") " SIZE(yData) =", SIZE(yData), & + ' and SIZE(zData,2) =', SIZE(zData,1),' are not the same' + END IF + + ! Is xData non decreasing + DO I_DIFF = 1, size(xData) - 1 + IF (xData(I_DIFF + 1) - xData(I_DIFF) <= 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' xData is not strictly increasing' + EXIT + END IF + END DO + + ! Is yData non decreasing + DO I_DIFF = 1, size(yData) - 1 + IF (yData(I_DIFF + 1) - yData(I_DIFF) <= 0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' yData is not strictly increasing' + EXIT + END IF + END DO + ! ---- Find corner indices surrounding desired interpolation point ----- ! x-direction IF (xq <= MINVAL(xData)) THEN ! On lower x-bound, just need to find zData(yq) j = 1 jj = 1 - interp2d = interp1d(yData,zData(:,j),yq) + interp2d = interp1d(yData,zData(:,j),yq,ErrVar) RETURN ELSEIF (xq >= MAXVAL(xData)) THEN ! On upper x-bound, just need to find zData(yq) j = size(xData) jj = size(xData) - interp2d = interp1d(yData,zData(:,j),yq) + interp2d = interp1d(yData,zData(:,j),yq,ErrVar) RETURN ELSE DO j = 1,size(xData) IF (xq == xData(j)) THEN ! On axis, just need 1d interpolation jj = j - interp2d = interp1d(yData,zData(:,j),yq) + interp2d = interp1d(yData,zData(:,j),yq,ErrVar) RETURN ELSEIF (xq < xData(j)) THEN jj = j @@ -244,18 +325,18 @@ REAL FUNCTION interp2d(xData, yData, zData, xq, yq) IF (yq <= MINVAL(yData)) THEN ! On lower y-bound, just need to find zData(xq) i = 1 ii = 1 - interp2d = interp1d(xData,zData(i,:),xq) + interp2d = interp1d(xData,zData(i,:),xq,ErrVar) RETURN ELSEIF (yq >= MAXVAL(yData)) THEN ! On upper y-bound, just need to find zData(xq) i = size(yData) ii = size(yData) - interp2d = interp1d(xData,zData(i,:),xq) + interp2d = interp1d(xData,zData(i,:),xq,ErrVar) RETURN ELSE DO i = 1,size(yData) IF (yq == yData(i)) THEN ! On axis, just need 1d interpolation ii = i - interp2d = interp1d(xData,zData(i,:),xq) + interp2d = interp1d(xData,zData(i,:),xq,ErrVar) RETURN ELSEIF (yq < yData(i)) THEN ii = i @@ -280,7 +361,13 @@ REAL FUNCTION interp2d(xData, yData, zData, xq, yq) interp2d = fxy(1) + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + END FUNCTION interp2d + !------------------------------------------------------------------------------------------------------------------------------- FUNCTION matinv3(A) RESULT(B) ! Performs a direct calculation of the inverse of a 3×3 matrix. @@ -305,6 +392,7 @@ FUNCTION matinv3(A) RESULT(B) B(2,3) = -detinv * (A(1,1)*A(2,3) - A(1,3)*A(2,1)) B(3,3) = +detinv * (A(1,1)*A(2,2) - A(1,2)*A(2,1)) END FUNCTION matinv3 + !------------------------------------------------------------------------------------------------------------------------------- FUNCTION identity(n) RESULT(A) ! Produces an identity matrix of size n x n @@ -326,6 +414,7 @@ FUNCTION identity(n) RESULT(A) ENDDO END FUNCTION identity + !------------------------------------------------------------------------------------------------------------------------------- REAL FUNCTION DFController(error, Kd, Tf, DT, inst) ! DF controller, with output saturation @@ -355,6 +444,7 @@ REAL FUNCTION DFController(error, Kd, Tf, DT, inst) errorLast(inst) = error DFControllerLast(inst) = DFController END FUNCTION DFController + !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE ColemanTransform(rootMOOP, aziAngle, nHarmonic, axTOut, axYOut) ! The Coleman or d-q axis transformation transforms the root out of plane bending moments of each turbine blade @@ -376,6 +466,7 @@ SUBROUTINE ColemanTransform(rootMOOP, aziAngle, nHarmonic, axTOut, axYOut) axYOut = 2.0/3.0 * (sin(nHarmonic*(aziAngle))*rootMOOP(1) + sin(nHarmonic*(aziAngle+phi2))*rootMOOP(2) + sin(nHarmonic*(aziAngle+phi3))*rootMOOP(3)) END SUBROUTINE ColemanTransform + !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE ColemanTransformInverse(axTIn, axYIn, aziAngle, nHarmonic, aziOffset, PitComIPC) ! The inverse Coleman or d-q axis transformation transforms the direct axis and quadrature axis @@ -398,6 +489,7 @@ SUBROUTINE ColemanTransformInverse(axTIn, axYIn, aziAngle, nHarmonic, aziOffset, PitComIPC(3) = cos(nHarmonic*(aziAngle+aziOffset+phi3))*axTIn + sin(nHarmonic*(aziAngle+aziOffset+phi3))*axYIn END SUBROUTINE ColemanTransformInverse + !------------------------------------------------------------------------------------------------------------------------------- REAL FUNCTION CPfunction(CP, lambda) ! Paremeterized Cp(lambda) function for a fixed pitch angle. Circumvents the need of importing a look-up table @@ -412,32 +504,44 @@ REAL FUNCTION CPfunction(CP, lambda) CPfunction = saturate(CPfunction, 0.001D0, 1.0D0) END FUNCTION CPfunction + !------------------------------------------------------------------------------------------------------------------------------- - REAL FUNCTION AeroDynTorque(LocalVar, CntrPar, PerfData) + REAL FUNCTION AeroDynTorque(LocalVar, CntrPar, PerfData, ErrVar) ! Function for computing the aerodynamic torque, divided by the effective rotor torque of the turbine, for use in wind speed estimation - USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, PerformanceData + USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, PerformanceData, ErrorVariables IMPLICIT NONE ! Inputs TYPE(ControlParameters), INTENT(IN) :: CntrPar TYPE(LocalVariables), INTENT(IN) :: LocalVar TYPE(PerformanceData), INTENT(IN) :: PerfData + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Local REAL(8) :: RotorArea REAL(8) :: Cp REAL(8) :: Lambda - + + CHARACTER(*), PARAMETER :: RoutineName = 'AeroDynTorque' + ! Find Torque RotorArea = PI*CntrPar%WE_BladeRadius**2 Lambda = LocalVar%RotSpeedF*CntrPar%WE_BladeRadius/LocalVar%WE_Vw - ! Cp = CPfunction(CntrPar%WE_CP, Lambda) - Cp = interp2d(PerfData%Beta_vec,PerfData%TSR_vec,PerfData%Cp_mat, LocalVar%PC_PitComT*R2D, Lambda) + + ! Compute Cp + Cp = interp2d(PerfData%Beta_vec,PerfData%TSR_vec,PerfData%Cp_mat, LocalVar%PC_PitComT*R2D, Lambda, ErrVar) + AeroDynTorque = 0.5*(CntrPar%WE_RhoAir*RotorArea)*(LocalVar%WE_Vw**3/LocalVar%RotSpeedF)*Cp AeroDynTorque = MAX(AeroDynTorque, 0.0) + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF END FUNCTION AeroDynTorque + !------------------------------------------------------------------------------------------------------------------------------- SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME) ! Debug routine, defines what gets printed to DEBUG.dbg if LoggingLevel = 1 @@ -548,6 +652,7 @@ SUBROUTINE Debug(LocalVar, CntrPar, DebugVar, avrSWAP, RootName, size_avcOUTNAME END IF END SUBROUTINE Debug + !------------------------------------------------------------------------------------------------------------------------------- FUNCTION QueryGitVersion() @@ -566,6 +671,7 @@ FUNCTION QueryGitVersion() RETURN END FUNCTION QueryGitVersion + !------------------------------------------------------------------------------------------------------------------------------- ! Copied from NWTC_IO.f90 !> This function returns a character string encoded with today's date in the form dd-mmm-ccyy. @@ -629,6 +735,7 @@ FUNCTION CurDate( ) RETURN END FUNCTION CurDate + !======================================================================= !> This function returns a character string encoded with the time in the form "hh:mm:ss". FUNCTION CurTime( ) @@ -651,8 +758,76 @@ FUNCTION CurTime( ) RETURN END FUNCTION CurTime + !======================================================================= +! This function checks whether an array is non-decreasing + LOGICAL Function NonDecreasing(Array) + + IMPLICIT NONE + REAL(8), DIMENSION(:) :: Array + INTEGER(4) :: I_DIFF + NonDecreasing = .TRUE. + ! Is Array non decreasing + DO I_DIFF = 1, size(Array) - 1 + IF (Array(I_DIFF + 1) - Array(I_DIFF) <= 0) THEN + NonDecreasing = .FALSE. + RETURN + END IF + END DO + + RETURN + END FUNCTION NonDecreasing + +!======================================================================= +!> This routine converts all the text in a string to upper case. + SUBROUTINE Conv2UC ( Str ) + + ! Argument declarations. + + CHARACTER(*), INTENT(INOUT) :: Str !< The string to be converted to UC (upper case). + + + ! Local declarations. + + INTEGER :: IC ! Character index + + + + DO IC=1,LEN_TRIM( Str ) + + IF ( ( Str(IC:IC) >= 'a' ).AND.( Str(IC:IC) <= 'z' ) ) THEN + Str(IC:IC) = CHAR( ICHAR( Str(IC:IC) ) - 32 ) + END IF + + END DO ! IC + + + RETURN + END SUBROUTINE Conv2UC + +!======================================================================= + !> This function returns a left-adjusted string representing the passed numeric value. + !! It eliminates trailing zeroes and even the decimal point if it is not a fraction. \n + !! Use Num2LStr (nwtc_io::num2lstr) instead of directly calling a specific routine in the generic interface. + FUNCTION Int2LStr ( Num ) + + CHARACTER(11) :: Int2LStr !< string representing input number. + + + ! Argument declarations. + + INTEGER, INTENT(IN) :: Num !< The number to convert to a left-justified string. + + + + WRITE (Int2LStr,'(I11)') Num + + Int2Lstr = ADJUSTL( Int2LStr ) + + + RETURN + END FUNCTION Int2LStr END MODULE Functions diff --git a/src/ROSCO_Types.f90 b/src/ROSCO_Types.f90 index ba2000da..97283b8f 100644 --- a/src/ROSCO_Types.f90 +++ b/src/ROSCO_Types.f90 @@ -19,8 +19,10 @@ MODULE ROSCO_Types ! Define Types +USE, INTRINSIC :: ISO_C_Binding IMPLICIT NONE + TYPE, PUBLIC :: ControlParameters INTEGER(4) :: LoggingLevel ! 0 = write no debug files, 1 = write standard output .dbg-file, 2 = write standard output .dbg-file and complete avrSWAP-array .dbg2-file @@ -31,10 +33,8 @@ MODULE ROSCO_Types REAL(8) :: F_NotchCornerFreq ! Natural frequency of the notch filter, [rad/s] REAL(8), DIMENSION(:), ALLOCATABLE :: F_NotchBetaNumDen ! These two notch damping values (numerator and denominator) determines the width and depth of the notch REAL(8) :: F_SSCornerFreq ! Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing} - REAL(8) :: F_FlCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s]. - REAL(8) :: F_FlDamping ! Damping constant in the first order low pass filter of the tower-top fore-aft motion for floating feedback control [-]. - REAL(8) :: F_FlpCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the blade root bending moment for flap control [rad/s]. - REAL(8) :: F_FlpDamping ! Damping constant in the first order low pass filter of the blade root bending moment for flap control[-]. + REAL(8), DIMENSION(:), ALLOCATABLE :: F_FlCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the tower-top fore-aft motion for floating feedback control [rad/s]. + REAL(8), DIMENSION(:), ALLOCATABLE :: F_FlpCornerFreq ! Corner frequency (-3dB point) in the second order low pass filter of the blade root bending moment for flap control [rad/s]. REAL(8) :: FA_HPFCornerFreq ! Corner frequency (-3dB point) in the high-pass filter on the fore-aft acceleration signal [rad/s] REAL(8) :: FA_IntSat ! Integrator saturation (maximum signal amplitude contrbution to pitch from FA damper), [rad] @@ -178,6 +178,7 @@ MODULE ROSCO_Types REAL(8) :: TestType ! Test variable, no use REAL(8) :: VS_MaxTq ! Maximum allowable generator torque [Nm]. REAL(8) :: VS_LastGenTrq ! Commanded electrical generator torque the last time the controller was called [Nm]. + REAL(8) :: VS_LastGenPwr ! Commanded electrical generator torque the last time the controller was called [Nm]. REAL(8) :: VS_MechGenPwr ! Mechanical power on the generator axis [W] REAL(8) :: VS_SpdErrAr ! Current speed error for region 2.5 PI controller (generator torque control) [rad/s]. REAL(8) :: VS_SpdErrBr ! Current speed error for region 1.5 PI controller (generator torque control) [rad/s]. @@ -231,4 +232,12 @@ MODULE ROSCO_Types END TYPE DebugVariables +TYPE, PUBLIC :: ErrorVariables + ! Error Catching + INTEGER(4) :: size_avcMSG + INTEGER(C_INT) :: aviFAIL ! A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message. + ! CHARACTER(:), ALLOCATABLE :: ErrMsg ! a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] + CHARACTER(:), ALLOCATABLE :: ErrMsg ! a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] +END TYPE ErrorVariables + END MODULE ROSCO_Types diff --git a/src/ReadSetParameters.f90 b/src/ReadSetParameters.f90 index c4ea668f..9ea406ff 100644 --- a/src/ReadSetParameters.f90 +++ b/src/ReadSetParameters.f90 @@ -12,7 +12,7 @@ ! Read and set the parameters used by the controller ! Submodules: -! Assert: Initial condition and input check +! CheckInputs: Initial condition and input check ! ComputeVariablesSetpoints: Compute setpoints used by controllers ! ReadAvrSWAP: Read AvrSWAP array ! ReadControlParameterFileSub: Read DISCON.IN input file @@ -23,274 +23,34 @@ MODULE ReadSetParameters USE, INTRINSIC :: ISO_C_Binding -USE Constants -USE Functions + USE Constants + USE Functions IMPLICIT NONE -CONTAINS - ! ----------------------------------------------------------------------------------- - ! Read all constant control parameters from DISCON.IN parameter file - SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size)!, accINFILE_size) - USE, INTRINSIC :: ISO_C_Binding - USE ROSCO_Types, ONLY : ControlParameters - - INTEGER(4) :: accINFILE_size ! size of DISCON input filename - CHARACTER(accINFILE_size), INTENT(IN) :: accINFILE(accINFILE_size) ! DISCON input filename - INTEGER(4), PARAMETER :: UnControllerParameters = 89 ! Unit number to open file - TYPE(ControlParameters), INTENT(INOUT) :: CntrPar ! Control parameter type - - - OPEN(unit=UnControllerParameters, file=accINFILE(1), status='old', action='read') - - !----------------------- HEADER ------------------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) - - !----------------------- DEBUG -------------------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%LoggingLevel - READ(UnControllerParameters, *) - - !----------------- CONTROLLER FLAGS --------------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%F_LPFType - READ(UnControllerParameters, *) CntrPar%F_NotchType - READ(UnControllerParameters, *) CntrPar%IPC_ControlMode - READ(UnControllerParameters, *) CntrPar%VS_ControlMode - READ(UnControllerParameters, *) CntrPar%PC_ControlMode - READ(UnControllerParameters, *) CntrPar%Y_ControlMode - READ(UnControllerParameters, *) CntrPar%SS_Mode - READ(UnControllerParameters, *) CntrPar%WE_Mode - READ(UnControllerParameters, *) CntrPar%PS_Mode - READ(UnControllerParameters, *) CntrPar%SD_Mode - READ(UnControllerParameters, *) CntrPar%FL_Mode - READ(UnControllerParameters, *) CntrPar%Flp_Mode - READ(UnControllerParameters, *) - - !----------------- FILTER CONSTANTS --------------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%F_LPFCornerFreq - READ(UnControllerParameters, *) CntrPar%F_LPFDamping - READ(UnControllerParameters, *) CntrPar%F_NotchCornerFreq - ALLOCATE(CntrPar%F_NotchBetaNumDen(2)) - READ(UnControllerParameters,*) CntrPar%F_NotchBetaNumDen - READ(UnControllerParameters,*) CntrPar%F_SSCornerFreq - READ(UnControllerParameters,*) CntrPar%F_FlCornerFreq, CntrPar%F_FlDamping - READ(UnControllerParameters,*) CntrPar%F_FlpCornerFreq, CntrPar%F_FlpDamping - READ(UnControllerParameters, *) - - !----------- BLADE PITCH CONTROLLER CONSTANTS ----------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%PC_GS_n - ALLOCATE(CntrPar%PC_GS_angles(CntrPar%PC_GS_n)) - READ(UnControllerParameters,*) CntrPar%PC_GS_angles - ALLOCATE(CntrPar%PC_GS_KP(CntrPar%PC_GS_n)) - READ(UnControllerParameters,*) CntrPar%PC_GS_KP - ALLOCATE(CntrPar%PC_GS_KI(CntrPar%PC_GS_n)) - READ(UnControllerParameters,*) CntrPar%PC_GS_KI - ALLOCATE(CntrPar%PC_GS_KD(CntrPar%PC_GS_n)) - READ(UnControllerParameters,*) CntrPar%PC_GS_KD - ALLOCATE(CntrPar%PC_GS_TF(CntrPar%PC_GS_n)) - READ(UnControllerParameters,*) CntrPar%PC_GS_TF - READ(UnControllerParameters, *) CntrPar%PC_MaxPit - READ(UnControllerParameters, *) CntrPar%PC_MinPit - READ(UnControllerParameters, *) CntrPar%PC_MaxRat - READ(UnControllerParameters, *) CntrPar%PC_MinRat - READ(UnControllerParameters, *) CntrPar%PC_RefSpd - READ(UnControllerParameters, *) CntrPar%PC_FinePit - READ(UnControllerParameters, *) CntrPar%PC_Switch - READ(UnControllerParameters, *) - - !------------------- IPC CONSTANTS ----------------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%IPC_IntSat - ALLOCATE(CntrPar%IPC_KI(2)) - READ(UnControllerParameters,*) CntrPar%IPC_KI - ALLOCATE(CntrPar%IPC_aziOffset(2)) - READ(UnControllerParameters,*) CntrPar%IPC_aziOffset - READ(UnControllerParameters, *) CntrPar%IPC_CornerFreqAct - READ(UnControllerParameters, *) - - !------------ VS TORQUE CONTROL CONSTANTS ---------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%VS_GenEff - READ(UnControllerParameters, *) CntrPar%VS_ArSatTq - READ(UnControllerParameters, *) CntrPar%VS_MaxRat - READ(UnControllerParameters, *) CntrPar%VS_MaxTq - READ(UnControllerParameters, *) CntrPar%VS_MinTq - READ(UnControllerParameters, *) CntrPar%VS_MinOMSpd - READ(UnControllerParameters, *) CntrPar%VS_Rgn2K - READ(UnControllerParameters, *) CntrPar%VS_RtPwr - READ(UnControllerParameters, *) CntrPar%VS_RtTq - READ(UnControllerParameters, *) CntrPar%VS_RefSpd - READ(UnControllerParameters, *) CntrPar%VS_n - ALLOCATE(CntrPar%VS_KP(CntrPar%VS_n)) - READ(UnControllerParameters,*) CntrPar%VS_KP - ALLOCATE(CntrPar%VS_KI(CntrPar%VS_n)) - READ(UnControllerParameters,*) CntrPar%VS_KI - READ(UnControllerParameters,*) CntrPar%VS_TSRopt - READ(UnControllerParameters, *) - - !------- Setpoint Smoother -------------------------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%SS_VSGain - READ(UnControllerParameters, *) CntrPar%SS_PCGain - READ(UnControllerParameters, *) - - !------------ WIND SPEED ESTIMATOR CONTANTS -------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%WE_BladeRadius - READ(UnControllerParameters, *) CntrPar%WE_CP_n - ALLOCATE(CntrPar%WE_CP(CntrPar%WE_CP_n)) - READ(UnControllerParameters, *) CntrPar%WE_CP - READ(UnControllerParameters, *) CntrPar%WE_Gamma - READ(UnControllerParameters, *) CntrPar%WE_GearboxRatio - READ(UnControllerParameters, *) CntrPar%WE_Jtot - READ(UnControllerParameters, *) CntrPar%WE_RhoAir - READ(UnControllerParameters, *) CntrPar%PerfFileName - ALLOCATE(CntrPar%PerfTableSize(2)) - READ(UnControllerParameters, *) CntrPar%PerfTableSize - READ(UnControllerParameters, *) CntrPar%WE_FOPoles_N - ALLOCATE(CntrPar%WE_FOPoles_v(CntrPar%WE_FOPoles_n)) - READ(UnControllerParameters, *) CntrPar%WE_FOPoles_v - ALLOCATE(CntrPar%WE_FOPoles(CntrPar%WE_FOPoles_n)) - READ(UnControllerParameters, *) CntrPar%WE_FOPoles - READ(UnControllerParameters, *) - - !-------------- YAW CONTROLLER CONSTANTS ----------------- - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%Y_ErrThresh - READ(UnControllerParameters, *) CntrPar%Y_IPC_IntSat - READ(UnControllerParameters, *) CntrPar%Y_IPC_n - ALLOCATE(CntrPar%Y_IPC_KP(CntrPar%Y_IPC_n)) - READ(UnControllerParameters,*) CntrPar%Y_IPC_KP - ALLOCATE(CntrPar%Y_IPC_KI(CntrPar%Y_IPC_n)) - READ(UnControllerParameters,*) CntrPar%Y_IPC_KI - READ(UnControllerParameters, *) CntrPar%Y_IPC_omegaLP - READ(UnControllerParameters, *) CntrPar%Y_IPC_zetaLP - READ(UnControllerParameters, *) CntrPar%Y_MErrSet - READ(UnControllerParameters, *) CntrPar%Y_omegaLPFast - READ(UnControllerParameters, *) CntrPar%Y_omegaLPSlow - READ(UnControllerParameters, *) CntrPar%Y_Rate - READ(UnControllerParameters, *) - - !------------ FORE-AFT TOWER DAMPER CONSTANTS ------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%FA_KI - READ(UnControllerParameters, *) CntrPar%FA_HPFCornerFreq - READ(UnControllerParameters, *) CntrPar%FA_IntSat - READ(UnControllerParameters, *) - - !------------ PEAK SHAVING ------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%PS_BldPitchMin_N - ALLOCATE(CntrPar%PS_WindSpeeds(CntrPar%PS_BldPitchMin_N)) - READ(UnControllerParameters, *) CntrPar%PS_WindSpeeds - ALLOCATE(CntrPar%PS_BldPitchMin(CntrPar%PS_BldPitchMin_N)) - READ(UnControllerParameters, *) CntrPar%PS_BldPitchMin - READ(UnControllerParameters, *) - - !------------ SHUTDOWN ------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%SD_MaxPit - READ(UnControllerParameters, *) CntrPar%SD_CornerFreq - READ(UnControllerParameters, *) - - !------------ FLOATING ------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%Fl_Kp - READ(UnControllerParameters, *) - - !------------ Flaps ------------ - READ(UnControllerParameters, *) - READ(UnControllerParameters, *) CntrPar%Flp_Angle - READ(UnControllerParameters, *) CntrPar%Flp_Kp - READ(UnControllerParameters, *) CntrPar%Flp_Ki - READ(UnControllerParameters, *) CntrPar%Flp_MaxPit - ! END OF INPUT FILE - - !------------------- CALCULATED CONSTANTS ----------------------- - CntrPar%PC_RtTq99 = CntrPar%VS_RtTq*0.99 - CntrPar%VS_MinOMTq = CntrPar%VS_Rgn2K*CntrPar%VS_MinOMSpd**2 - CntrPar%VS_MaxOMTq = CntrPar%VS_Rgn2K*CntrPar%VS_RefSpd**2 - CLOSE(UnControllerParameters) - - !------------------- HOUSEKEEPING ----------------------- - CntrPar%PerfFileName = TRIM(CntrPar%PerfFileName) - - END SUBROUTINE ReadControlParameterFileSub - ! ----------------------------------------------------------------------------------- - ! Calculate setpoints for primary control actions - SUBROUTINE ComputeVariablesSetpoints(CntrPar, LocalVar, objInst) - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances - USE Constants - ! Allocate variables - TYPE(ControlParameters), INTENT(INOUT) :: CntrPar - TYPE(LocalVariables), INTENT(INOUT) :: LocalVar - TYPE(ObjectInstances), INTENT(INOUT) :: objInst - - REAL(8) :: VS_RefSpd ! Referece speed for variable speed torque controller, [rad/s] - REAL(8) :: PC_RefSpd ! Referece speed for pitch controller, [rad/s] - REAL(8) :: Omega_op ! Optimal TSR-tracking generator speed, [rad/s] - ! temp - ! REAL(8) :: VS_TSRop = 7.5 - - ! ----- Calculate yaw misalignment error ----- - LocalVar%Y_MErr = LocalVar%Y_M + CntrPar%Y_MErrSet ! Yaw-alignment error - - ! ----- Pitch controller speed and power error ----- - ! Implement setpoint smoothing - IF (LocalVar%SS_DelOmegaF < 0) THEN - PC_RefSpd = CntrPar%PC_RefSpd - LocalVar%SS_DelOmegaF - ELSE - PC_RefSpd = CntrPar%PC_RefSpd - ENDIF - - LocalVar%PC_SpdErr = PC_RefSpd - LocalVar%GenSpeedF ! Speed error - LocalVar%PC_PwrErr = CntrPar%VS_RtPwr - LocalVar%VS_GenPwr ! Power error - - ! ----- Torque controller reference errors ----- - ! Define VS reference generator speed [rad/s] - IF ((CntrPar%VS_ControlMode == 2) .OR. (CntrPar%VS_ControlMode == 3)) THEN - VS_RefSpd = (CntrPar%VS_TSRopt * LocalVar%We_Vw_F / CntrPar%WE_BladeRadius) * CntrPar%WE_GearboxRatio - VS_RefSpd = saturate(VS_RefSpd,CntrPar%VS_MinOMSpd, CntrPar%VS_RefSpd) - ELSE - VS_RefSpd = CntrPar%VS_RefSpd - ENDIF - - ! Implement setpoint smoothing - IF (LocalVar%SS_DelOmegaF > 0) THEN - VS_RefSpd = VS_RefSpd - LocalVar%SS_DelOmegaF - ENDIF - - ! Force zero torque in shutdown mode - IF (LocalVar%SD) THEN - VS_RefSpd = CntrPar%VS_MinOMSpd - ENDIF + ! Global Variables + LOGICAL, PARAMETER :: DEBUG_PARSING = .FALSE. ! debug flag to output parsing information, set up Echo file later + + INTERFACE ParseInput ! Parses a character variable name and value from a string. + MODULE PROCEDURE ParseInput_Str ! Parses a character string from a string. + MODULE PROCEDURE ParseInput_Dbl ! Parses a double-precision REAL from a string. + MODULE PROCEDURE ParseInput_Int ! Parses an INTEGER from a string. + ! MODULE PROCEDURE ParseInput_Log ! Parses an LOGICAL from a string. + END INTERFACE - ! Force minimum rotor speed - VS_RefSpd = max(VS_RefSpd, CntrPar%VS_MinOmSpd) + INTERFACE ParseAry ! Parse an array of numbers from a string. + MODULE PROCEDURE ParseDbAry ! Parse an array of double-precision REAL values. + MODULE PROCEDURE ParseInAry ! Parse an array of whole numbers. + END INTERFACE - ! TSR-tracking reference error - IF ((CntrPar%VS_ControlMode == 2) .OR. (CntrPar%VS_ControlMode == 3)) THEN - LocalVar%VS_SpdErr = VS_RefSpd - LocalVar%GenSpeedF - ENDIF - ! Define transition region setpoint errors - LocalVar%VS_SpdErrAr = VS_RefSpd - LocalVar%GenSpeedF ! Current speed error - Region 2.5 PI-control (Above Rated) - LocalVar%VS_SpdErrBr = CntrPar%VS_MinOMSpd - LocalVar%GenSpeedF ! Current speed error - Region 1.5 PI-control (Below Rated) - - ! Region 3 minimum pitch angle for state machine - LocalVar%VS_Rgn3Pitch = LocalVar%PC_MinPit + CntrPar%PC_Switch - END SUBROUTINE ComputeVariablesSetpoints - ! ----------------------------------------------------------------------------------- +CONTAINS + ! ----------------------------------------------------------------------------------- ! Read avrSWAP array passed from ServoDyn SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) USE ROSCO_Types, ONLY : LocalVariables - + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. TYPE(LocalVariables), INTENT(INOUT) :: LocalVar @@ -313,7 +73,7 @@ SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) LocalVar%Azimuth = avrSWAP(60) LocalVar%NumBl = NINT(avrSWAP(61)) - ! --- NJA: usually feedback back the previous pitch command helps for numerical stability, sometimes it does not... + ! --- NJA: usually feedback back the previous pitch command helps for numerical stability, sometimes it does not... IF (LocalVar%iStatus == 0) THEN LocalVar%BlPitch(1) = avrSWAP(4) LocalVar%BlPitch(2) = avrSWAP(33) @@ -324,189 +84,35 @@ SUBROUTINE ReadAvrSWAP(avrSWAP, LocalVar) LocalVar%BlPitch(3) = LocalVar%PitCom(3) ENDIF - END SUBROUTINE ReadAvrSWAP - ! ----------------------------------------------------------------------------------- - ! Check for errors before any execution - SUBROUTINE Assert(LocalVar, CntrPar, avrSWAP, aviFAIL, ErrMsg, size_avcMSG) - USE, INTRINSIC :: ISO_C_Binding - USE ROSCO_Types, ONLY : LocalVariables, ControlParameters - - IMPLICIT NONE - - ! Inputs - TYPE(ControlParameters), INTENT(IN) :: CntrPar - TYPE(LocalVariables), INTENT(IN) :: LocalVar - INTEGER(4), INTENT(IN) :: size_avcMSG - REAL(C_FLOAT), INTENT(IN) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. - - ! Outputs - INTEGER(C_INT), INTENT(OUT) :: aviFAIL ! A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message. - CHARACTER(size_avcMSG-1), INTENT(OUT) :: ErrMsg ! a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] - - ! Local - - !.............................................................................................................................. - ! Check validity of input parameters: - !.............................................................................................................................. - - IF ((CntrPar%F_LPFType > 2.0) .OR. (CntrPar%F_LPFType < 1.0)) THEN - aviFAIL = -1 - ErrMsg = 'F_LPFType must be 1 or 2.' - ENDIF - - IF ((CntrPar%F_LPFDamping > 1.0) .OR. (CntrPar%F_LPFDamping < 0.0)) THEN - aviFAIL = -1 - ErrMsg = 'Filter damping coefficient must be between [0, 1]' - ENDIF - - IF (CntrPar%IPC_CornerFreqAct < 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Corner frequency of IPC actuator model must be positive, or set to 0 to disable.' - ENDIF - - IF (CntrPar%F_LPFCornerFreq <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'CornerFreq must be greater than zero.' - ENDIF - - IF ((CntrPar%IPC_ControlMode > 0) .AND. (CntrPar%Y_ControlMode > 1)) THEN - aviFAIL = -1 - ErrMsg = 'IPC control for load reductions and yaw-by-IPC cannot be activated simultaneously' - ENDIF - - IF (LocalVar%DT <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'DT must be greater than zero.' - ENDIF - - IF (CntrPar%VS_MaxRat <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'VS_MaxRat must be greater than zero.' - ENDIF - - IF (CntrPar%VS_RtTq < 0.0) THEN - aviFAIL = -1 - ErrMsg = 'VS_RtTq must not be negative.' - ENDIF - - IF (CntrPar%VS_Rgn2K < 0.0) THEN - aviFAIL = -1 - ErrMsg = 'VS_Rgn2K must not be negative.' - ENDIF - - IF (CntrPar%VS_MaxTq < CntrPar%VS_RtTq) THEN - aviFAIL = -1 - ErrMsg = 'VS_RtTq must not be greater than VS_MaxTq.' - ENDIF - - IF (CntrPar%VS_KP(1) > 0.0) THEN - aviFAIL = -1 - ErrMsg = 'VS_KP must be less than zero.' - ENDIF - - IF (CntrPar%VS_KI(1) > 0.0) THEN - aviFAIL = -1 - ErrMsg = 'VS_KI must be less than zero.' - ENDIF - - IF (CntrPar%PC_RefSpd <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'PC_RefSpd must be greater than zero.' - ENDIF - - IF (CntrPar%PC_MaxRat <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'PC_MaxRat must be greater than zero.' - ENDIF - - IF (CntrPar%PC_MinPit >= CntrPar%PC_MaxPit) THEN - aviFAIL = -1 - ErrMsg = 'PC_MinPit must be less than PC_MaxPit.' - ENDIF - - IF (CntrPar%IPC_KI(1) < 0.0) THEN - aviFAIL = -1 - ErrMsg = 'IPC_KI(1) must be zero or greater than zero.' - ENDIF - - IF (CntrPar%IPC_KI(2) < 0.0) THEN - aviFAIL = -1 - ErrMsg = 'IPC_KI(2) must be zero or greater than zero.' - ENDIF - - ! ---- Yaw Control ---- - IF (CntrPar%Y_ControlMode > 0) THEN - IF (CntrPar%Y_IPC_omegaLP <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Y_IPC_omegaLP must be greater than zero.' - ENDIF - - IF (CntrPar%Y_IPC_zetaLP <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Y_IPC_zetaLP must be greater than zero.' - ENDIF - - IF (CntrPar%Y_ErrThresh <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Y_ErrThresh must be greater than zero.' - ENDIF - - IF (CntrPar%Y_Rate <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'CntrPar%Y_Rate must be greater than zero.' - ENDIF - - IF (CntrPar%Y_omegaLPFast <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Y_omegaLPFast must be greater than zero.' - ENDIF - - IF (CntrPar%Y_omegaLPSlow <= 0.0) THEN - aviFAIL = -1 - ErrMsg = 'Y_omegaLPSlow must be greater than zero.' - ENDIF - ENDIF - - ! --- Floating Control --- - IF (CntrPar%Fl_Mode > 0) THEN - IF (CntrPar%F_NotchType <= 1 .OR. CntrPar%F_NotchCornerFreq == 0.0) THEN - aviFAIL = -1 - ErrMsg = 'F_NotchType and F_NotchCornerFreq must be specified for Fl_Mode greater than zero.' - ENDIF - ENDIF - - ! Abort if the user has not requested a pitch angle actuator (See Appendix A - ! of Bladed User's Guide): - IF (NINT(avrSWAP(10)) /= 0) THEN ! .TRUE. if a pitch angle actuator hasn't been requested - aviFAIL = -1 - ErrMsg = 'Pitch angle actuator not requested.' - ENDIF - - IF (NINT(avrSWAP(28)) == 0 .AND. ((CntrPar%IPC_ControlMode > 0) .OR. (CntrPar%Y_ControlMode > 1))) THEN - aviFAIL = -1 - ErrMsg = 'IPC enabled, but Ptch_Cntrl in ServoDyn has a value of 0. Set it to 1.' - ENDIF - - END SUBROUTINE Assert - ! ----------------------------------------------------------------------------------- + END SUBROUTINE ReadAvrSWAP +! ----------------------------------------------------------------------------------- ! Define parameters for control actions - SUBROUTINE SetParameters(avrSWAP, aviFAIL, accINFILE, ErrMsg, size_avcMSG, CntrPar, LocalVar, objInst, PerfData) - USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, PerformanceData + SUBROUTINE SetParameters(avrSWAP, accINFILE, size_avcMSG, CntrPar, LocalVar, objInst, PerfData, ErrVar) + + USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, PerformanceData, ErrorVariables INTEGER(4), INTENT(IN) :: size_avcMSG - TYPE(ControlParameters), INTENT(INOUT) :: CntrPar - TYPE(LocalVariables), INTENT(INOUT) :: LocalVar - TYPE(ObjectInstances), INTENT(INOUT) :: objInst - TYPE(PerformanceData), INTENT(INOUT) :: PerfData + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(LocalVariables), INTENT(INOUT) :: LocalVar + TYPE(ObjectInstances), INTENT(INOUT) :: objInst + TYPE(PerformanceData), INTENT(INOUT) :: PerfData + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar - REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. - INTEGER(C_INT), INTENT(OUT) :: aviFAIL ! A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message. + REAL(C_FLOAT), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. CHARACTER(KIND=C_CHAR), INTENT(IN) :: accINFILE(NINT(avrSWAP(50))) ! The name of the parameter input file - CHARACTER(size_avcMSG-1), INTENT(OUT) :: ErrMsg ! a Fortran version of the C string argument (not considered an array here) [subtract 1 for the C null-character] - INTEGER(4) :: K ! Index used for looping through blades. - CHARACTER(200) :: git_version - ! Set aviFAIL to 0 in each iteration: - aviFAIL = 0 + + INTEGER(4) :: K ! Index used for looping through blades. + CHARACTER(200) :: git_version + + CHARACTER(*), PARAMETER :: RoutineName = 'SetParameters' + + + + ! Error Catching Variables + ! Set ErrVar%aviFAIL to 0 in each iteration: + ErrVar%aviFAIL = 0 + ! ALLOCATE(ErrVar%ErrMsg(size_avcMSG-1)) + ErrVar%size_avcMSG = size_avcMSG ! Initialize all filter instance counters at 1 objInst%instLPF = 1 @@ -534,21 +140,9 @@ SUBROUTINE SetParameters(avrSWAP, aviFAIL, accINFILE, ErrMsg, size_avcMSG, CntrP IF (LocalVar%iStatus == 0) THEN ! .TRUE. if we're on the first call to the DLL ! Inform users that we are using this user-defined routine: - aviFAIL = 1 + ! ErrVar%aviFAIL = 1 git_version = QueryGitVersion() - ! ErrMsg = ' '//NEW_LINE('A')// & - ! '------------------------------------------------------------------------------'//NEW_LINE('A')// & - ! 'Running a controller implemented through NREL''s ROSCO Toolbox '//NEW_LINE('A')// & - ! 'A wind turbine controller framework for public use in the scientific field '//NEW_LINE('A')// & - ! 'Developed in collaboration: National Renewable Energy Laboratory '//NEW_LINE('A')// & - ! ' Delft University of Technology, The Netherlands '//NEW_LINE('A')// & - ! 'Primary development by (listed alphabetically): Nikhar J. Abbas '//NEW_LINE('A')// & - ! ' Sebastiaan P. Mulders '//NEW_LINE('A')// & - ! ' Jan-Willem van Wingerden '//NEW_LINE('A')// & - ! 'Visit our GitHub-page to contribute to this project: '//NEW_LINE('A')// & - ! 'https://github.com/NREL/ROSCO '//NEW_LINE('A')// & - ! '------------------------------------------------------------------------------' - ErrMsg = ' '//NEW_LINE('A')// & + PRINT *,' '//NEW_LINE('A')// & '------------------------------------------------------------------------------'//NEW_LINE('A')// & 'Running ROSCO-'//TRIM(git_version)//NEW_LINE('A')// & 'A wind turbine controller framework for public use in the scientific field '//NEW_LINE('A')// & @@ -556,10 +150,16 @@ SUBROUTINE SetParameters(avrSWAP, aviFAIL, accINFILE, ErrMsg, size_avcMSG, CntrP ' Delft University of Technology, The Netherlands '//NEW_LINE('A')// & '------------------------------------------------------------------------------' - CALL ReadControlParameterFileSub(CntrPar, accINFILE, NINT(avrSWAP(50))) + CALL ReadControlParameterFileSub(CntrPar, accINFILE, NINT(avrSWAP(50)),ErrVar) + ! If there's been an file reading error, don't continue + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + RETURN + ENDIF IF (CntrPar%WE_Mode > 0) THEN - CALL READCpFile(CntrPar,PerfData) + CALL READCpFile(CntrPar,PerfData,ErrVar) ENDIF ! Initialize testValue (debugging variable) LocalVar%TestType = 0 @@ -584,61 +184,1303 @@ SUBROUTINE SetParameters(avrSWAP, aviFAIL, accINFILE, ErrMsg, size_avcMSG, CntrP ENDIF LocalVar%VS_LastGenTrq = LocalVar%GenTq LocalVar%VS_MaxTq = CntrPar%VS_MaxTq + ! Check validity of input parameters: - CALL Assert(LocalVar, CntrPar, avrSWAP, aviFAIL, ErrMsg, size_avcMSG) + CALL CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG) + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF ENDIF END SUBROUTINE SetParameters ! ----------------------------------------------------------------------------------- ! Read all constant control parameters from DISCON.IN parameter file - SUBROUTINE ReadCpFile(CntrPar,PerfData) - USE ROSCO_Types, ONLY : PerformanceData, ControlParameters + SUBROUTINE ReadControlParameterFileSub(CntrPar, accINFILE, accINFILE_size,ErrVar)!, accINFILE_size) + USE, INTRINSIC :: ISO_C_Binding + USE ROSCO_Types, ONLY : ControlParameters, ErrorVariables + + INTEGER(4) :: accINFILE_size ! size of DISCON input filename + CHARACTER(accINFILE_size), INTENT(IN ) :: accINFILE(accINFILE_size) ! DISCON input filename + INTEGER(4), PARAMETER :: UnControllerParameters = 89 ! Unit number to open file + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar ! Control parameter type + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Control parameter type + + INTEGER(4) :: CurLine + + CHARACTER(*), PARAMETER :: RoutineName = 'ReadControlParameterFileSub' + + CurLine = 1 + + + OPEN(unit=UnControllerParameters, file=accINFILE(1), status='old', action='read') + + !----------------------- HEADER ------------------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !----------------------- DEBUG -------------------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + CALL ParseInput(UnControllerParameters,CurLine,'LoggingLevel',accINFILE(1),CntrPar%LoggingLevel,ErrVar) + + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !----------------- CONTROLLER FLAGS --------------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'F_LPFType',accINFILE(1),CntrPar%F_LPFType,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'F_NotchType',accINFILE(1),CntrPar%F_NotchType,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'IPC_ControlMode',accINFILE(1),CntrPar%IPC_ControlMode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_ControlMode',accINFILE(1),CntrPar%VS_ControlMode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_ControlMode',accINFILE(1),CntrPar%PC_ControlMode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_ControlMode',accINFILE(1),CntrPar%Y_ControlMode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'SS_Mode',accINFILE(1),CntrPar%SS_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'WE_Mode',accINFILE(1),CntrPar%WE_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PS_Mode',accINFILE(1),CntrPar%PS_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'SD_Mode',accINFILE(1),CntrPar%SD_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'FL_Mode',accINFILE(1),CntrPar%FL_Mode,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Flp_Mode',accINFILE(1),CntrPar%Flp_Mode,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !----------------- FILTER CONSTANTS --------------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'F_LPFCornerFreq',accINFILE(1),CntrPar%F_LPFCornerFreq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'F_LPFDamping',accINFILE(1),CntrPar%F_LPFDamping,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'F_NotchCornerFreq',accINFILE(1),CntrPar%F_NotchCornerFreq,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'F_NotchBetaNumDen', CntrPar%F_NotchBetaNumDen, 2, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'F_SSCornerFreq',accINFILE(1),CntrPar%F_SSCornerFreq,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'F_FlCornerFreq', CntrPar%F_FlCornerFreq, 2, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'F_FlpCornerFreq', CntrPar%F_FlpCornerFreq, 2, accINFILE(1), ErrVar ) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !----------- BLADE PITCH CONTROLLER CONSTANTS ----------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'PC_GS_n',accINFILE(1),CntrPar%PC_GS_n,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'PC_GS_angles', CntrPar%PC_GS_angles, CntrPar%PC_GS_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'PC_GS_KP', CntrPar%PC_GS_KP, CntrPar%PC_GS_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'PC_GS_KI', CntrPar%PC_GS_KI, CntrPar%PC_GS_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'PC_GS_KD', CntrPar%PC_GS_KD, CntrPar%PC_GS_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'PC_GS_TF', CntrPar%PC_GS_TF, CntrPar%PC_GS_n, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'PC_MaxPit',accINFILE(1),CntrPar%PC_MaxPit,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_MinPit',accINFILE(1),CntrPar%PC_MinPit,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_MaxRat',accINFILE(1),CntrPar%PC_MaxRat,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_MinRat',accINFILE(1),CntrPar%PC_MinRat,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_RefSpd',accINFILE(1),CntrPar%PC_RefSpd,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_FinePit',accINFILE(1),CntrPar%PC_FinePit,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PC_Switch',accINFILE(1),CntrPar%PC_Switch,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------------- IPC CONSTANTS ----------------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'IPC_IntSat',accINFILE(1),CntrPar%IPC_IntSat,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'IPC_KI', CntrPar%IPC_KI, 2, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'IPC_aziOffset', CntrPar%IPC_aziOffset, 2, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'IPC_CornerFreqAct',accINFILE(1),CntrPar%IPC_CornerFreqAct,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ VS TORQUE CONTROL CONSTANTS ---------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'VS_GenEff',accINFILE(1),CntrPar%VS_GenEff,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_ArSatTq',accINFILE(1),CntrPar%VS_ArSatTq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_MaxRat',accINFILE(1),CntrPar%VS_MaxRat,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_MaxTq',accINFILE(1),CntrPar%VS_MaxTq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_MinTq',accINFILE(1),CntrPar%VS_MinTq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_MinOMSpd',accINFILE(1),CntrPar%VS_MinOMSpd,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_Rgn2K',accINFILE(1),CntrPar%VS_Rgn2K,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_RtPwr',accINFILE(1),CntrPar%VS_RtPwr,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_RtTq',accINFILE(1),CntrPar%VS_RtTq,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_RefSpd',accINFILE(1),CntrPar%VS_RefSpd,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'VS_n',accINFILE(1),CntrPar%VS_n,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'VS_KP', CntrPar%VS_KP, CntrPar%VS_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'VS_KI', CntrPar%VS_KI, CntrPar%VS_n, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'VS_TSRopt',accINFILE(1),CntrPar%VS_TSRopt,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------- Setpoint Smoother -------------------------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'SS_VSGain',accINFILE(1),CntrPar%SS_VSGain,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'SS_PCGain',accINFILE(1),CntrPar%SS_PCGain,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ WIND SPEED ESTIMATOR CONTANTS -------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'WE_BladeRadius',accINFILE(1),CntrPar%WE_BladeRadius,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'WE_CP_n',accINFILE(1),CntrPar%WE_CP_n,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'WE_CP', CntrPar%WE_CP, CntrPar%WE_CP_n, accINFILE(1), ErrVar, .FALSE. ) + CALL ParseInput(UnControllerParameters,CurLine,'WE_Gamma',accINFILE(1),CntrPar%WE_Gamma,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'WE_GearboxRatio',accINFILE(1),CntrPar%WE_GearboxRatio,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'WE_Jtot',accINFILE(1),CntrPar%WE_Jtot,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'WE_RhoAir',accINFILE(1),CntrPar%WE_RhoAir,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'PerfFileName',accINFILE(1),CntrPar%PerfFileName,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'PerfTableSize', CntrPar%PerfTableSize, 2, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'WE_FOPoles_N',accINFILE(1),CntrPar%WE_FOPoles_N,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'WE_FOPoles_v', CntrPar%WE_FOPoles_v, CntrPar%WE_FOPoles_N, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'WE_FOPoles', CntrPar%WE_FOPoles, CntrPar%WE_FOPoles_N, accINFILE(1), ErrVar ) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !-------------- YAW CONTROLLER CONSTANTS ----------------- + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'Y_ErrThresh',accINFILE(1),CntrPar%Y_ErrThresh,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_IntSat',accINFILE(1),CntrPar%Y_IPC_IntSat,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_n',accINFILE(1),CntrPar%Y_IPC_n,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'Y_IPC_KP', CntrPar%Y_IPC_KP, CntrPar%Y_IPC_n, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'Y_IPC_KI', CntrPar%Y_IPC_KI, CntrPar%Y_IPC_n, accINFILE(1), ErrVar ) + CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_omegaLP',accINFILE(1),CntrPar%Y_IPC_omegaLP,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_IPC_zetaLP',accINFILE(1),CntrPar%Y_IPC_zetaLP,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_MErrSet',accINFILE(1),CntrPar%Y_MErrSet,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_omegaLPFast',accINFILE(1),CntrPar%Y_omegaLPFast,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_omegaLPSlow',accINFILE(1),CntrPar%Y_omegaLPSlow,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Y_Rate',accINFILE(1),CntrPar%Y_Rate,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ FORE-AFT TOWER DAMPER CONSTANTS ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'FA_KI',accINFILE(1),CntrPar%FA_KI,ErrVar) + ! Don't check this name until we make an API change + CALL ParseInput(UnControllerParameters,CurLine,'FA_HPFCornerFreq',accINFILE(1),CntrPar%FA_HPFCornerFreq,ErrVar,.FALSE.) + CALL ParseInput(UnControllerParameters,CurLine,'FA_IntSat',accINFILE(1),CntrPar%FA_IntSat,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ PEAK SHAVING ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'PS_BldPitchMin_N',accINFILE(1),CntrPar%PS_BldPitchMin_N,ErrVar) + CALL ParseAry(UnControllerParameters, CurLine, 'PS_WindSpeeds', CntrPar%PS_WindSpeeds, CntrPar%PS_BldPitchMin_N, accINFILE(1), ErrVar ) + CALL ParseAry(UnControllerParameters, CurLine, 'PS_BldPitchMin', CntrPar%PS_BldPitchMin, CntrPar%PS_BldPitchMin_N, accINFILE(1), ErrVar ) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ SHUTDOWN ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'SD_MaxPit',accINFILE(1),CntrPar%SD_MaxPit,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'SD_CornerFreq',accINFILE(1),CntrPar%SD_CornerFreq,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ FLOATING ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'Fl_Kp',accINFILE(1),CntrPar%Fl_Kp,ErrVar) + CALL ReadEmptyLine(UnControllerParameters,CurLine) + + !------------ Flaps ------------ + CALL ReadEmptyLine(UnControllerParameters,CurLine) + CALL ParseInput(UnControllerParameters,CurLine,'Flp_Angle',accINFILE(1),CntrPar%Flp_Angle,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Flp_Kp',accINFILE(1),CntrPar%Flp_Kp,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Flp_Ki',accINFILE(1),CntrPar%Flp_Ki,ErrVar) + CALL ParseInput(UnControllerParameters,CurLine,'Flp_MaxPit',accINFILE(1),CntrPar%Flp_MaxPit,ErrVar) + ! END OF INPUT FILE + + ! Close Input File + CLOSE(UnControllerParameters) + + + !------------------- CALCULATED CONSTANTS ----------------------- + CntrPar%PC_RtTq99 = CntrPar%VS_RtTq*0.99 + CntrPar%VS_MinOMTq = CntrPar%VS_Rgn2K*CntrPar%VS_MinOMSpd**2 + CntrPar%VS_MaxOMTq = CntrPar%VS_Rgn2K*CntrPar%VS_RefSpd**2 + + !------------------- HOUSEKEEPING ----------------------- + CntrPar%PerfFileName = TRIM(CntrPar%PerfFileName) + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + + + END SUBROUTINE ReadControlParameterFileSub + ! ----------------------------------------------------------------------------------- + ! Read all constant control parameters from DISCON.IN parameter file + SUBROUTINE ReadCpFile(CntrPar,PerfData, ErrVar) + USE ROSCO_Types, ONLY : PerformanceData, ControlParameters, ErrorVariables - INTEGER(4), PARAMETER :: UnPerfParameters = 89 - TYPE(PerformanceData), INTENT(INOUT) :: PerfData - TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(PerformanceData), INTENT(INOUT) :: PerfData + TYPE(ControlParameters), INTENT(INOUT) :: CntrPar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + ! Local variables - INTEGER(4) :: i ! iteration index + INTEGER(4), PARAMETER :: UnPerfParameters = 89 + INTEGER(4) :: i ! iteration index + + INTEGER(4) :: CurLine + CHARACTER(*), PARAMETER :: RoutineName = 'ReadCpFile' + REAL(8), DIMENSION(:), ALLOCATABLE :: TmpPerf + + CurLine = 1 + OPEN(unit=UnPerfParameters, file=TRIM(CntrPar%PerfFileName), status='old', action='read') ! Should put input file into DISCON.IN ! ----------------------- Axis Definitions ------------------------ - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - ALLOCATE(PerfData%Beta_vec(CntrPar%PerfTableSize(1))) - READ(UnPerfParameters, *) PerfData%Beta_vec - READ(UnPerfParameters, *) - ALLOCATE(PerfData%TSR_vec(CntrPar%PerfTableSize(2))) - READ(UnPerfParameters, *) PerfData%TSR_vec + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ParseAry(UnPerfParameters, CurLine, 'Pitch angle vector', PerfData%Beta_vec, CntrPar%PerfTableSize(1), TRIM(CntrPar%PerfFileName), ErrVar, .FALSE.) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ParseAry(UnPerfParameters, CurLine, 'TSR vector', PerfData%TSR_vec, CntrPar%PerfTableSize(2), TRIM(CntrPar%PerfFileName), ErrVar, .FALSE.) ! ----------------------- Read Cp, Ct, Cq, Tables ------------------------ - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) ! Input file should contains wind speed information here - unneeded for now - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) ! Input file should contains wind speed information here - unneeded for now + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) ALLOCATE(PerfData%Cp_mat(CntrPar%PerfTableSize(2),CntrPar%PerfTableSize(1))) DO i = 1,CntrPar%PerfTableSize(2) READ(UnPerfParameters, *) PerfData%Cp_mat(i,:) ! Read Cp table END DO - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) ALLOCATE(PerfData%Ct_mat(CntrPar%PerfTableSize(1),CntrPar%PerfTableSize(2))) DO i = 1,CntrPar%PerfTableSize(2) READ(UnPerfParameters, *) PerfData%Ct_mat(i,:) ! Read Ct table END DO - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) - READ(UnPerfParameters, *) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) + CALL ReadEmptyLine(UnPerfParameters,CurLine) ALLOCATE(PerfData%Cq_mat(CntrPar%PerfTableSize(1),CntrPar%PerfTableSize(2))) DO i = 1,CntrPar%PerfTableSize(2) READ(UnPerfParameters, *) PerfData%Cq_mat(i,:) ! Read Cq table END DO + + ! Add RoutineName to error message + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF END SUBROUTINE ReadCpFile + ! ----------------------------------------------------------------------------------- + ! Check for errors before any execution + SUBROUTINE CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG) + USE, INTRINSIC :: ISO_C_Binding + USE ROSCO_Types, ONLY : LocalVariables, ControlParameters, ErrorVariables + + IMPLICIT NONE + + ! Inputs + TYPE(ControlParameters), INTENT(IN ) :: CntrPar + TYPE(LocalVariables), INTENT(IN ) :: LocalVar + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar + INTEGER(4), INTENT(IN ) :: size_avcMSG + REAL(C_FLOAT), INTENT(IN ) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller. + + CHARACTER(*), PARAMETER :: RoutineName = 'CheckInputs' + ! Local + + !.............................................................................................................................. + ! Check validity of input parameters: + !.............................................................................................................................. + + !------- DEBUG ------------------------------------------------------------ + + ! LoggingLevel + IF ((CntrPar%LoggingLevel < 0) .OR. (CntrPar%LoggingLevel > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'LoggingLevel must be 0, 1, or 2.' + ENDIF + + !------- CONTROLLER FLAGS ------------------------------------------------- + + ! F_LPFType + IF ((CntrPar%F_LPFType < 1) .OR. (CntrPar%F_LPFType > 2)) THEN + ErrVar%aviFAIL = -1 + PRINT *, CntrPar%F_LPFType + ErrVar%ErrMsg = 'F_LPFType must be 1 or 2.' + ENDIF + + ! F_NotchType + IF ((CntrPar%F_NotchType < 0) .OR. (CntrPar%F_NotchType > 3)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_NotchType must be 0, 1, 2, or 3.' + ENDIF + + ! F_NotchType + IF ((CntrPar%F_NotchType < 0) .OR. (CntrPar%F_NotchType > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_NotchType must be 0, 1, or 2.' + ENDIF + + ! IPC_ControlMode + IF ((CntrPar%IPC_ControlMode < 0) .OR. (CntrPar%IPC_ControlMode > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'IPC_ControlMode must be 0, 1, or 2.' + ENDIF + + ! VS_ControlMode + IF ((CntrPar%VS_ControlMode < 0) .OR. (CntrPar%VS_ControlMode > 3)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_ControlMode must be 0, 1, 2, or 3.' + ENDIF + + ! PC_ControlMode + IF ((CntrPar%PC_ControlMode < 0) .OR. (CntrPar%PC_ControlMode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_ControlMode must be 0 or 1.' + ENDIF + + ! Y_ControlMode + IF ((CntrPar%Y_ControlMode < 0) .OR. (CntrPar%Y_ControlMode > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_ControlMode must be 0, 1 or 2.' + ENDIF + + IF ((CntrPar%IPC_ControlMode > 0) .AND. (CntrPar%Y_ControlMode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'IPC control for load reductions and yaw-by-IPC cannot be activated simultaneously' + ENDIF + + ! SS_Mode + IF ((CntrPar%SS_Mode < 0) .OR. (CntrPar%SS_Mode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'SS_Mode must be 0 or 1.' + ENDIF + + ! WE_Mode + IF ((CntrPar%WE_Mode < 0) .OR. (CntrPar%WE_Mode > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_Mode must be 0, 1, or 2.' + ENDIF + + ! PS_Mode + IF ((CntrPar%PS_Mode < 0) .OR. (CntrPar%PS_Mode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PS_Mode must be 0 or 1.' + ENDIF + + ! SD_Mode + IF ((CntrPar%SD_Mode < 0) .OR. (CntrPar%SD_Mode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'SD_Mode must be 0 or 1.' + ENDIF + + ! Fl_Mode + IF ((CntrPar%Fl_Mode < 0) .OR. (CntrPar%Fl_Mode > 1)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Fl_Mode must be 0 or 1.' + ENDIF + + ! Flp_Mode + IF ((CntrPar%Flp_Mode < 0) .OR. (CntrPar%Flp_Mode > 2)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Flp_Mode must be 0, 1, or 2.' + ENDIF + + !------- FILTERS ---------------------------------------------------------- + + ! F_LPFCornerFreq + IF (CntrPar%F_LPFCornerFreq <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_LPFCornerFreq must be greater than zero.' + ENDIF + + ! F_LPFDamping + IF (CntrPar%F_LPFType == 2) THEN + IF (CntrPar%F_LPFDamping <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_LPFDamping must be greater than zero.' + ENDIF + ENDIF + + ! Notch Filter Params + IF (CntrPar%F_NotchType > 0) THEN + + ! F_NotchCornerFreq + IF (CntrPar%F_NotchCornerFreq <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_NotchCornerFreq must be greater than zero.' + ENDIF + + ! F_NotchBetaNumDen(2) + IF (CntrPar%F_NotchBetaNumDen(2) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_NotchBetaNumDen(2) must be greater than zero.' + ENDIF + ENDIF + + ! F_SSCornerFreq + IF (CntrPar%F_SSCornerFreq <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_SSCornerFreq must be greater than zero.' + ENDIF + + ! F_FlCornerFreq(1) (frequency) + IF (CntrPar%F_FlCornerFreq(1) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_FlCornerFreq(1) must be greater than zero.' + ENDIF + + ! F_FlCornerFreq(2) (damping) + IF (CntrPar%F_FlCornerFreq(2) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_FlCornerFreq(2) must be greater than zero.' + ENDIF + + ! F_FlpCornerFreq(1) (frequency) + IF (CntrPar%F_FlpCornerFreq(1) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_FlpCornerFreq(1) must be greater than zero.' + ENDIF + + ! F_FlpCornerFreq(2) (damping) + IF (CntrPar%F_FlpCornerFreq(2) <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_FlpCornerFreq(2) must be greater than zero.' + ENDIF + + + !------- BLADE PITCH CONTROL ---------------------------------------------- + + ! PC_GS_n + IF (CntrPar%PC_GS_n <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_GS_n must be greater than 0' + ENDIF + + ! PC_GS_angles + IF (.NOT. NonDecreasing(CntrPar%PC_GS_angles)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_GS_angles must be non-decreasing' + ENDIF + + ! PC_GS_KP and PC_GS_KI + ! I'd like to throw warnings if these are positive + + ! PC_MinPit and PC_MaxPit + IF (CntrPar%PC_MinPit >= CntrPar%PC_MaxPit) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_MinPit must be less than PC_MaxPit.' + ENDIF + + ! PC_RefSpd + IF (CntrPar%PC_RefSpd <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_RefSpd must be greater than zero.' + ENDIF + + ! PC_MaxRat + IF (CntrPar%PC_MaxRat <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_MaxRat must be greater than zero.' + ENDIF + + ! PC_MinRat + IF (CntrPar%PC_MinRat >= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PC_MinRat must be less than zero.' + ENDIF + + !------- INDIVIDUAL PITCH CONTROL ----------------------------------------- + + IF (CntrPar%IPC_CornerFreqAct < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Corner frequency of IPC actuator model must be positive, or set to 0 to disable.' + ENDIF + + IF (CntrPar%IPC_KI(1) < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'IPC_KI(1) must be zero or greater than zero.' + ENDIF + + IF (CntrPar%IPC_KI(2) < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'IPC_KI(2) must be zero or greater than zero.' + ENDIF + + !------- VS TORQUE CONTROL ------------------------------------------------ + + IF (CntrPar%VS_MaxRat <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_MaxRat must be greater than zero.' + ENDIF + + + + ! VS_Rgn2K + IF (CntrPar%VS_Rgn2K < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_Rgn2K must not be negative.' + ENDIF + + ! VS_RtTq + IF (CntrPar%VS_MaxTq < CntrPar%VS_RtTq) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_RtTq must not be greater than VS_MaxTq.' + ENDIF + + ! VS_RtPwr + IF (CntrPar%VS_RtPwr < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_RtPwr must not be negative.' + ENDIF + + ! VS_RtTq + IF (CntrPar%VS_RtTq < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_RtTq must not be negative.' + ENDIF + + ! VS_KP + IF (CntrPar%VS_KP(1) > 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_KP must be less than zero.' + ENDIF + + ! VS_KI + IF (CntrPar%VS_KI(1) > 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_KI must be less than zero.' + ENDIF + + ! VS_TSRopt + IF (CntrPar%VS_TSRopt < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'VS_TSRopt must be greater than zero.' + ENDIF + + !------- SETPOINT SMOOTHER --------------------------------------------- + + ! SS_VSGain + IF (CntrPar%SS_VSGain < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'SS_VSGain must be greater than zero.' + ENDIF + + ! SS_PCGain + IF (CntrPar%SS_PCGain < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'SS_PCGain must be greater than zero.' + ENDIF + + !------- WIND SPEED ESTIMATOR --------------------------------------------- + + ! WE_BladeRadius + IF (CntrPar%WE_BladeRadius < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_BladeRadius must be greater than zero.' + ENDIF + + ! WE_GearboxRatio + IF (CntrPar%WE_GearboxRatio < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_GearboxRatio must be greater than zero.' + ENDIF + + ! WE_Jtot + IF (CntrPar%WE_Jtot < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_Jtot must be greater than zero.' + ENDIF + + ! WE_RhoAir + IF (CntrPar%WE_RhoAir < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_RhoAir must be greater than zero.' + ENDIF + + ! PerfTableSize(1) + IF (CntrPar%PerfTableSize(1) < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PerfTableSize(1) must be greater than zero.' + ENDIF + + ! PerfTableSize(2) + IF (CntrPar%PerfTableSize(2) < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PerfTableSize(2) must be greater than zero.' + ENDIF + + ! WE_FOPoles_N + IF (CntrPar%WE_FOPoles_N < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'WE_FOPoles_N must be greater than zero.' + ENDIF + + ! WE_FOPoles_v + IF (.NOT. NonDecreasing(CntrPar%WE_FOPoles_v)) THEN + ErrVar%aviFAIL = -1 + write(400,*) CntrPar%WE_FOPoles_v + ErrVar%ErrMsg = 'WE_FOPoles_v must be non-decreasing.' + ENDIF + + + + ! ---- Yaw Control ---- + IF (CntrPar%Y_ControlMode > 0) THEN + IF (CntrPar%Y_IPC_omegaLP <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_IPC_omegaLP must be greater than zero.' + ENDIF + + IF (CntrPar%Y_IPC_zetaLP <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_IPC_zetaLP must be greater than zero.' + ENDIF + + IF (CntrPar%Y_ErrThresh <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_ErrThresh must be greater than zero.' + ENDIF + + IF (CntrPar%Y_Rate <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'CntrPar%Y_Rate must be greater than zero.' + ENDIF + + IF (CntrPar%Y_omegaLPFast <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_omegaLPFast must be greater than zero.' + ENDIF + + IF (CntrPar%Y_omegaLPSlow <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Y_omegaLPSlow must be greater than zero.' + ENDIF + ENDIF + + !------- MINIMUM PITCH SATURATION ------------------------------------------- + IF (CntrPar%PS_Mode > 0) THEN + + ! PS_BldPitchMin_N + IF (CntrPar%PS_BldPitchMin_N < 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PS_BldPitchMin_N must be greater than zero.' + ENDIF + + ! PS_WindSpeeds + IF (.NOT. NonDecreasing(CntrPar%PS_WindSpeeds)) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'PS_WindSpeeds must be non-decreasing.' + ENDIF + + + ENDIF + + ! --- Floating Control --- + IF (CntrPar%Fl_Mode > 0) THEN + IF (CntrPar%F_NotchType <= 1 .OR. CntrPar%F_NotchCornerFreq == 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'F_NotchType and F_NotchCornerFreq must be specified for Fl_Mode greater than zero.' + ENDIF + ENDIF + + ! Abort if the user has not requested a pitch angle actuator (See Appendix A + ! of Bladed User's Guide): + IF (NINT(avrSWAP(10)) /= 0) THEN ! .TRUE. if a pitch angle actuator hasn't been requested + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'Pitch angle actuator not requested.' + ENDIF + + IF (NINT(avrSWAP(28)) == 0 .AND. ((CntrPar%IPC_ControlMode > 0) .OR. (CntrPar%Y_ControlMode > 1))) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'IPC enabled, but Ptch_Cntrl in ServoDyn has a value of 0. Set it to 1.' + ENDIF + + ! DT + IF (LocalVar%DT <= 0.0) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = 'DT must be greater than zero.' + ENDIF + + IF (ErrVar%aviFAIL < 0) THEN + ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg) + ENDIF + + END SUBROUTINE CheckInputs + + !======================================================================= + ! Parse integer input: read line, check that variable name is in line, handle errors + subroutine ParseInput_Int(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(4), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(20) :: Words (2) ! The two "words" parsed from the line + + INTEGER(4), INTENT(INOUT) :: Variable ! Variable + INTEGER(4) :: ErrStatLcl ! Error status local to this routine. + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + IF (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),*,IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Int + + !======================================================================= + ! Parse double input, this is a copy of ParseInput_Int and a change in the variable definitions + subroutine ParseInput_Dbl(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(4), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(20) :: Words (2) ! The two "words" parsed from the line + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + REAL(8), INTENT(INOUT) :: Variable ! Variable + INTEGER(4) :: ErrStatLcl ! Error status local to this routine. + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + IF (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),*,IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid INTEGER value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Dbl + + !======================================================================= + ! Parse string input, this is a copy of ParseInput_Int and a change in the variable definitions + subroutine ParseInput_Str(Un, CurLine, VarName, FileName, Variable, ErrVar, CheckName) + USE ROSCO_Types, ONLY : ErrorVariables + + CHARACTER(1024) :: Line + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + CHARACTER(*), INTENT(IN ) :: VarName ! Input file unit + CHARACTER(*), INTENT(IN ) :: FileName ! Input file unit + INTEGER(4), INTENT(INOUT) :: CurLine ! Current line of input + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + CHARACTER(200) :: Words (2) ! The two "words" parsed from the line + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + CHARACTER(*), INTENT(INOUT) :: Variable ! Variable + INTEGER(4) :: ErrStatLcl ! Error status local to this routine. + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Separate line string into 2 words + CALL GetWords ( Line, Words, 2 ) + + ! Debugging: show what's being read, turn into Echo later + if (DEBUG_PARSING) THEN + print *, 'Read: '//TRIM(Words(1))//' and '//TRIM(Words(2)),' on line ', CurLine + END IF + + ! Check that Variable Name is in Words + IF (CheckName_) THEN + CALL ChkParseData ( Words, VarName, FileName, CurLine, ErrVar ) + END IF + + ! IF We haven't failed already + IF (ErrVar%aviFAIL >= 0) THEN + + ! Read the variable + READ (Words(1),'(A)',IOSTAT=ErrStatLcl) Variable + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = NewLine//' >> A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The variable "'//TRIM( Words(2) )//'" was not assigned valid STRING value on line #' & + //TRIM( Int2LStr( CurLine ) )//'.'//NewLine//& + ' >> The text being parsed was :'//NewLine//' "'//TRIM( Line )//'"' + ENDIF + + ENDIF + + ! Increment line counter + CurLine = CurLine + 1 + END IF + + END subroutine ParseInput_Str + +!======================================================================= +!> This subroutine parses the specified line of text for AryLen REAL values. +!! Generate an error message if the value is the wrong type. +!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. + SUBROUTINE ParseDbAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) + + USE ROSCO_Types, ONLY : ErrorVariables + + ! Arguments declarations. + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. + + REAL(8), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. + + INTEGER(4), INTENT(INOUT) :: LineNum !< The number of the line to parse. + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. + + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + + ! Local declarations. + + CHARACTER(1024) :: Line + INTEGER(4) :: ErrStatLcl ! Error status local to this routine. + INTEGER(4) :: i + + CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. + CHARACTER(1024) :: Debug_String + CHARACTER(*), PARAMETER :: RoutineName = 'ParseDbAry' + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Allocate array and handle errors + ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + IF ( ALLOCATED(Ary) ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' + END IF + END IF + + ! Allocate words array + ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' + CALL Cleanup() + RETURN + ENDIF + + ! Separate line string into AryLen + 1 words, should include variable name + CALL GetWords ( Line, Words_Ary, AryLen + 1 ) + + ! Debug Output + IF (DEBUG_PARSING) THEN + Debug_String = '' + DO i = 1,AryLen+1 + Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) + IF (i < AryLen + 1) THEN + Debug_String = TRIM(Debug_String)//',' + END IF + END DO + print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum + END IF + + ! Check that Variable Name is at the end of Words, will also check length of array + IF (CheckName_) THEN + CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) + END IF + + ! Read array + READ (Line,*,IOSTAT=ErrStatLcl) Ary + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & + //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & + //' "'//TRIM( Line )//'"' + RETURN + CALL Cleanup() + ENDIF + + ! IF ( PRESENT(UnEc) ) THEN + ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) + ! END IF + + LineNum = LineNum + 1 + CALL Cleanup() + ENDIF + + RETURN + + !======================================================================= + CONTAINS + !======================================================================= + SUBROUTINE Cleanup ( ) + + ! This subroutine cleans up the parent routine before exiting. + + ! Deallocate the Words array if it had been allocated. + + IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) + + + RETURN + + END SUBROUTINE Cleanup + + END SUBROUTINE ParseDbAry + + !======================================================================= +!> This subroutine parses the specified line of text for AryLen INTEGER values. +!! Generate an error message if the value is the wrong type. +!! Use ParseAry (nwtc_io::parseary) instead of directly calling a specific routine in the generic interface. + SUBROUTINE ParseInAry ( Un, LineNum, AryName, Ary, AryLen, FileName, ErrVar, CheckName ) + + USE ROSCO_Types, ONLY : ErrorVariables + + ! Arguments declarations. + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + INTEGER, INTENT(IN ) :: AryLen !< The length of the array to parse. + + INTEGER(4), ALLOCATABLE, INTENT(INOUT) :: Ary(:) !< The array to receive the input values. + + INTEGER(4), INTENT(INOUT) :: LineNum !< The number of the line to parse. + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + CHARACTER(*), INTENT(IN ) :: AryName !< The array name we are trying to fill. + + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + LOGICAL, OPTIONAL, INTENT(IN ) :: CheckName + + ! Local declarations. + + CHARACTER(1024) :: Line + INTEGER(4) :: ErrStatLcl ! Error status local to this routine. + INTEGER(4) :: i + + CHARACTER(200), ALLOCATABLE :: Words_Ary (:) ! The array "words" parsed from the line. + CHARACTER(1024) :: Debug_String + CHARACTER(*), PARAMETER :: RoutineName = 'ParseInAry' + + LOGICAL :: CheckName_ + + ! Figure out if we're checking the name, default to .TRUE. + CheckName_ = .TRUE. + if (PRESENT(CheckName)) CheckName_ = CheckName + + ! If we've already failed, don't read anything + IF (ErrVar%aviFAIL >= 0) THEN + ! Read the whole line as a string + READ(Un, '(A)') Line + + ! Allocate array and handle errors + ALLOCATE ( Ary(AryLen) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + IF ( ALLOCATED(Ary) ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for the '//TRIM( AryName )//' array; array was already allocated.' + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Error allocating memory for '//TRIM(Int2LStr( AryLen ))//' characters in the '//TRIM( AryName )//' array.' + END IF + END IF + + ! Allocate words array + ALLOCATE ( Words_Ary( AryLen + 1 ) , STAT=ErrStatLcl ) + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':Fatal error allocating memory for the Words array.' + CALL Cleanup() + RETURN + ENDIF + + ! Separate line string into AryLen + 1 words, should include variable name + CALL GetWords ( Line, Words_Ary, AryLen + 1 ) + + ! Debug Output + IF (DEBUG_PARSING) THEN + Debug_String = '' + DO i = 1,AryLen+1 + Debug_String = TRIM(Debug_String)//TRIM(Words_Ary(i)) + IF (i < AryLen + 1) THEN + Debug_String = TRIM(Debug_String)//',' + END IF + END DO + print *, 'Read: '//TRIM(Debug_String)//' on line ', LineNum + END IF + + ! Check that Variable Name is at the end of Words, will also check length of array + IF (CheckName_) THEN + CALL ChkParseData ( Words_Ary(AryLen:AryLen+1), AryName, FileName, LineNum, ErrVar ) + END IF + + ! Read array + READ (Line,*,IOSTAT=ErrStatLcl) Ary + IF ( ErrStatLcl /= 0 ) THEN + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = RoutineName//':A fatal error occurred when parsing data from "' & + //TRIM( FileName )//'".'//NewLine// & + ' >> The "'//TRIM( AryName )//'" array was not assigned valid REAL values on line #' & + //TRIM( Int2LStr( LineNum ) )//'.'//NewLine//' >> The text being parsed was :'//NewLine & + //' "'//TRIM( Line )//'"' + RETURN + CALL Cleanup() + ENDIF + + ! IF ( PRESENT(UnEc) ) THEN + ! IF ( UnEc > 0 ) WRITE (UnEc,'(A)') TRIM( FileInfo%Lines(LineNum) ) + ! END IF + + LineNum = LineNum + 1 + CALL Cleanup() + ENDIF + + RETURN + + !======================================================================= + CONTAINS + !======================================================================= + SUBROUTINE Cleanup ( ) + + ! This subroutine cleans up the parent routine before exiting. + + ! Deallocate the Words array if it had been allocated. + + IF ( ALLOCATED( Words_Ary ) ) DEALLOCATE( Words_Ary ) + + + RETURN + + END SUBROUTINE Cleanup + +END SUBROUTINE ParseInAry + +!======================================================================= + !> This subroutine checks the data to be parsed to make sure it finds + !! the expected variable name and an associated value. +SUBROUTINE ChkParseData ( Words, ExpVarName, FileName, FileLineNum, ErrVar ) + + USE ROSCO_Types, ONLY : ErrorVariables + + + ! Arguments declarations. + TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar ! Current line of input + + INTEGER(4), INTENT(IN) :: FileLineNum !< The number of the line in the file being parsed. + INTEGER(4) :: NameIndx !< The index into the Words array that points to the variable name. + + CHARACTER(*), INTENT(IN) :: ExpVarName !< The expected variable name. + CHARACTER(*), INTENT(IN) :: Words (2) !< The two words to be parsed from the line. + + CHARACTER(*), INTENT(IN) :: FileName !< The name of the file being parsed. + + + ! Local declarations. + + CHARACTER(20) :: ExpUCVarName ! The uppercase version of ExpVarName. + CHARACTER(20) :: FndUCVarName ! The uppercase version of the word being tested. + + + + + ! Convert the found and expected names to uppercase. + + FndUCVarName = Words(1) + ExpUCVarName = ExpVarName + + CALL Conv2UC ( FndUCVarName ) + CALL Conv2UC ( ExpUCVarName ) + + ! See which word is the variable name. Generate an error if it is the first + + IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN + NameIndx = 1 + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & + //'".'//NewLine//' >> The variable "'//TRIM( Words(1) )//'" was not assigned a valid value on line #' & + //TRIM( Int2LStr( FileLineNum ) )//'.' + RETURN + ELSE + FndUCVarName = Words(2) + CALL Conv2UC ( FndUCVarName ) + IF ( TRIM( FndUCVarName ) == TRIM( ExpUCVarName ) ) THEN + NameIndx = 2 + ELSE + ErrVar%aviFAIL = -1 + ErrVar%ErrMsg = ' >> A fatal error occurred when parsing data from "'//TRIM( FileName ) & + //'".'//NewLine//' >> The variable "'//TRIM( ExpVarName )//'" was not assigned a valid value on line #' & + //TRIM( Int2LStr( FileLineNum ) )//'.' + RETURN + ENDIF + ENDIF + + +END SUBROUTINE ChkParseData + +!======================================================================= +subroutine ReadEmptyLine(Un,CurLine) + INTEGER(4), INTENT(IN ) :: Un ! Input file unit + INTEGER(4), INTENT(INOUT) :: CurLine ! Current line of input + + CHARACTER(1024) :: Line + + READ(Un, '(A)') Line + CurLine = CurLine + 1 + +END subroutine ReadEmptyLine + +!======================================================================= +!> This subroutine is used to get the NumWords "words" from a line of text. +!! It uses spaces, tabs, commas, semicolons, single quotes, and double quotes ("whitespace") +!! as word separators. If there aren't NumWords in the line, the remaining array elements will remain empty. +!! Use CountWords (nwtc_io::countwords) to count the number of words in a line. +SUBROUTINE GetWords ( Line, Words, NumWords ) + + ! Argument declarations. + + INTEGER, INTENT(IN) :: NumWords !< The number of words to look for. + + CHARACTER(*), INTENT(IN) :: Line !< The string to search. + CHARACTER(*), INTENT(OUT) :: Words(NumWords) !< The array of found words. + + + ! Local declarations. + + INTEGER :: Ch ! Character position within the string. + INTEGER :: IW ! Word index. + INTEGER :: NextWhite ! The location of the next whitespace in the string. + CHARACTER(1), PARAMETER :: Tab = CHAR( 9 ) + + + + ! Let's prefill the array with blanks. + + DO IW=1,NumWords + Words(IW) = ' ' + END DO ! IW + + + ! Let's make sure we have text on this line. + + IF ( LEN_TRIM( Line ) == 0 ) RETURN + + + ! Parse words separated by any combination of spaces, tabs, commas, + ! semicolons, single quotes, and double quotes ("whitespace"). + + Ch = 0 + IW = 0 + + DO + + NextWhite = SCAN( Line(Ch+1:) , ' ,!;''"'//Tab ) + + IF ( NextWhite > 1 ) THEN + + IW = IW + 1 + Words(IW) = Line(Ch+1:Ch+NextWhite-1) + + IF ( IW == NumWords ) EXIT + + Ch = Ch + NextWhite + + ELSE IF ( NextWhite == 1 ) THEN + + Ch = Ch + 1 + + CYCLE + + ELSE + + EXIT + + END IF + + END DO + + + RETURN +END SUBROUTINE GetWords + END MODULE ReadSetParameters