From a2212ecdc4864a1da992f4d011d28edb3a650131 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Wed, 8 Apr 2026 16:09:50 -0600 Subject: [PATCH 1/5] Implement under-relaxation for the tight-coupling iterative solver to improve numerical stability --- modules/nwtc-library/src/ModVar.f90 | 15 +++++++++++---- modules/openfast-library/src/FAST_Registry.txt | 1 + modules/openfast-library/src/FAST_Solver.f90 | 7 +++++-- modules/openfast-library/src/FAST_Subs.f90 | 8 ++++++++ modules/openfast-library/src/FAST_Types.f90 | 4 ++++ modules/openfast-library/src/Glue_Registry.txt | 1 + modules/openfast-library/src/Glue_Types.f90 | 4 ++++ 7 files changed, 34 insertions(+), 6 deletions(-) diff --git a/modules/nwtc-library/src/ModVar.f90 b/modules/nwtc-library/src/ModVar.f90 index b8cadfecdf..ecb464acf5 100644 --- a/modules/nwtc-library/src/ModVar.f90 +++ b/modules/nwtc-library/src/ModVar.f90 @@ -787,15 +787,22 @@ subroutine MV_ExtrapInterp(VarAry, y, tin, y_out, tin_out, ErrStat, ErrMsg) end subroutine -subroutine MV_AddDelta(VarAry, DeltaAry, DataAry) +subroutine MV_AddDelta(VarAry, DeltaAry, DataAry, RelaxIn) type(ModVarType), intent(in) :: VarAry(:) ! Array of variables real(R8Ki), intent(in) :: DeltaAry(:) ! Array of delta values real(R8Ki), intent(inout) :: DataAry(:) ! Array to be modified + real(R8Ki), intent(in),optional :: RelaxIn integer(IntKi) :: i, j, k - real(R8Ki) :: quat_base(3), quat_delta(3), rvec(3), dcm(3, 3) + real(R8Ki) :: quat_base(3), quat_delta(3), rvec(3), dcm(3, 3), Relax integer(IntKi) :: ErrStat2 character(ErrMsgLen) :: ErrMsg2 + if (present(RelaxIn)) then + Relax = RelaxIn + else + Relax = 1.0_R8Ki + endif + ! Loop through variables do i = 1, size(VarAry) associate (iLoc => VarAry(i)%iLoc) @@ -812,7 +819,7 @@ subroutine MV_AddDelta(VarAry, DeltaAry, DataAry) quat_base = DataAry(k:k + 2) ! Get rotation vector delta - rvec = DeltaAry(k:k + 2) + rvec = Relax*DeltaAry(k:k + 2) if (UseSmallRotAngles) then call SmllRotTrans('linearization perturbation', rvec(1), rvec(2), rvec(3), dcm, ErrStat=ErrStat2, ErrMsg=ErrMsg2) @@ -829,7 +836,7 @@ subroutine MV_AddDelta(VarAry, DeltaAry, DataAry) end do case default - DataAry(iLoc(1):iLoc(2)) = DataAry(iLoc(1):iLoc(2)) + DeltaAry(iLoc(1):iLoc(2)) + DataAry(iLoc(1):iLoc(2)) = DataAry(iLoc(1):iLoc(2)) + Relax*DeltaAry(iLoc(1):iLoc(2)) end select end associate end do diff --git a/modules/openfast-library/src/FAST_Registry.txt b/modules/openfast-library/src/FAST_Registry.txt index 5aa331a16b..61195d8efc 100644 --- a/modules/openfast-library/src/FAST_Registry.txt +++ b/modules/openfast-library/src/FAST_Registry.txt @@ -129,6 +129,7 @@ typedef ^ FAST_ParameterType IntKi ModCoupling - - - "Module coupling type {1=lo typedef ^ FAST_ParameterType DbKi RhoInf - - - "Numerical damping parameter for tight coupling generalized-alpha integrator (-) [0.0 to 1.0]" - typedef ^ FAST_ParameterType DbKi ConvTol - - - "Convergence iteration error tolerance for tight coupling generalized alpha integrator (-)" - typedef ^ FAST_ParameterType IntKi MaxConvIter - - - "Maximum number of convergence iterations for tight coupling generalized alpha integrator (-)" - +typedef ^ FAST_ParameterType DbKi Relax - 1.0_DbKi - "Under-relaxation factor for the iterative solver (-) [>0 and <=1]" - # Data for Jacobians: typedef ^ FAST_ParameterType DbKi DT_Ujac - - - "Time between when we need to re-calculate these Jacobians" s typedef ^ FAST_ParameterType Reki UJacSclFact - - - "Scaling factor used to get similar magnitudes between accelerations, forces, and moments in Jacobians" - diff --git a/modules/openfast-library/src/FAST_Solver.f90 b/modules/openfast-library/src/FAST_Solver.f90 index dd5b1a672e..134e514f1b 100644 --- a/modules/openfast-library/src/FAST_Solver.f90 +++ b/modules/openfast-library/src/FAST_Solver.f90 @@ -64,6 +64,9 @@ subroutine FAST_SolverInit(p_FAST, p, m, GlueModData, GlueModMaps, Turbine, ErrS ! Convergence tolerance p%ConvTol = p_FAST%ConvTol + ! Under-relaxation factor + p%Relax = p_FAST%Relax + ! Solver time step p%h = p_FAST%DT @@ -1562,7 +1565,7 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps !---------------------------------------------------------------------- ! Add change in inputs - if (p%iJU(1) > 0) call MV_AddDelta(m%Mod%Vars%u, m%XB(p%iJU(1):p%iJU(2), 1), m%Mod%Lin%u) + if (p%iJU(1) > 0) call MV_AddDelta(m%Mod%Vars%u, m%XB(p%iJU(1):p%iJU(2), 1), m%Mod%Lin%u, RelaxIn=p%Relax) !---------------------------------------------------------------------- ! TC and Option 1: Transfer updated states and inputs to modules @@ -1831,7 +1834,7 @@ subroutine FAST_CalcOutputsAndSolveForInputs(p, m, GlueModData, GlueModMaps, Thi !------------------------------------------------------------------------- ! Add change in inputs - call MV_AddDelta(m%Mod%Vars%u, m%IO_X(:, 1), m%Mod%Lin%u) + call MV_AddDelta(m%Mod%Vars%u, m%IO_X(:, 1), m%Mod%Lin%u, RelaxIn=p%Relax) ! Transfer updated TC and Option 1 inputs to modules do i = 1, size(m%Mod%ModData) diff --git a/modules/openfast-library/src/FAST_Subs.f90 b/modules/openfast-library/src/FAST_Subs.f90 index a023a7d4e5..2a0dfe1612 100644 --- a/modules/openfast-library/src/FAST_Subs.f90 +++ b/modules/openfast-library/src/FAST_Subs.f90 @@ -1982,6 +1982,10 @@ SUBROUTINE ValidateInputData(p, m_FAST, ErrStat, ErrMsg) CALL SetErrStat( ErrID_Fatal, 'MaxIter must be at least 1.', ErrStat, ErrMsg, RoutineName ) END IF + IF ( (p%Relax <= 0.0_DbKi) .or. (p%Relax > 1.0_DbKi) ) THEN + CALL SetErrStat( ErrID_Fatal, 'Relax must be positive and less than or equal to 1.', ErrStat, ErrMsg, RoutineName ) + END IF + ! Check that InputFileData%OutFmt is a valid format specifier and will fit over the column headings CALL ChkRealFmtStr( p%OutFmt, 'OutFmt', p%FmtWidth, ErrStat2, ErrMsg2 ) call SetErrStat(ErrStat2, ErrMsg2, ErrStat, ErrMsg, RoutineName) @@ -2838,6 +2842,10 @@ SUBROUTINE FAST_ReadPrimaryFile( InputFile, p, m_FAST, OverrideAbortErrLev, ErrS "for tight coupling generalized alpha integrator (-)", ErrStat2, ErrMsg2, UnEc) if (Failed()) return + ! Relax - Under-relaxation factor for the iterative solver (-) [>0 and <=1] + CALL ReadVar( UnIn, InputFile, p%Relax, "Relax", "Under-relaxation factor for the iterative solver (-) [>0 and <=1]", ErrStat2, ErrMsg2, UnEc) + if (Failed()) return + ! DT_UJac - Time between calls to get Jacobians (s) CALL ReadVar( UnIn, InputFile, p%DT_UJac, "DT_UJac", "Time between calls to get Jacobians (s)", ErrStat2, ErrMsg2, UnEc) if (Failed()) return diff --git a/modules/openfast-library/src/FAST_Types.f90 b/modules/openfast-library/src/FAST_Types.f90 index ac56b2841a..3db1ace6cc 100644 --- a/modules/openfast-library/src/FAST_Types.f90 +++ b/modules/openfast-library/src/FAST_Types.f90 @@ -145,6 +145,7 @@ MODULE FAST_Types REAL(DbKi) :: RhoInf = 0.0_R8Ki !< Numerical damping parameter for tight coupling generalized-alpha integrator (-) [0.0 to 1.0] [-] REAL(DbKi) :: ConvTol = 0.0_R8Ki !< Convergence iteration error tolerance for tight coupling generalized alpha integrator (-) [-] INTEGER(IntKi) :: MaxConvIter = 0_IntKi !< Maximum number of convergence iterations for tight coupling generalized alpha integrator (-) [-] + REAL(DbKi) :: Relax = 1.0_DbKi !< Under-relaxation factor for the iterative solver (-) [>0 and <=1] [-] REAL(DbKi) :: DT_Ujac = 0.0_R8Ki !< Time between when we need to re-calculate these Jacobians [s] REAL(ReKi) :: UJacSclFact = 0.0_ReKi !< Scaling factor used to get similar magnitudes between accelerations, forces, and moments in Jacobians [-] INTEGER(IntKi) , DIMENSION(1:9) :: SizeJac_Opt1 = 0_IntKi !< (1)=size of matrix; (2)=size of ED portion; (3)=size of SD portion [2 meshes]; (4)=size of HD portion; (5)=size of BD portion blade 1; (6)=size of BD portion blade 2; (7)=size of BD portion blade 3; (8)=size of Orca portion; (9)=size of ExtPtfm portion; [-] @@ -1162,6 +1163,7 @@ subroutine FAST_CopyParam(SrcParamData, DstParamData, CtrlCode, ErrStat, ErrMsg) DstParamData%RhoInf = SrcParamData%RhoInf DstParamData%ConvTol = SrcParamData%ConvTol DstParamData%MaxConvIter = SrcParamData%MaxConvIter + DstParamData%Relax = SrcParamData%Relax DstParamData%DT_Ujac = SrcParamData%DT_Ujac DstParamData%UJacSclFact = SrcParamData%UJacSclFact DstParamData%SizeJac_Opt1 = SrcParamData%SizeJac_Opt1 @@ -1391,6 +1393,7 @@ subroutine FAST_PackParam(RF, Indata) call RegPack(RF, InData%RhoInf) call RegPack(RF, InData%ConvTol) call RegPack(RF, InData%MaxConvIter) + call RegPack(RF, InData%Relax) call RegPack(RF, InData%DT_Ujac) call RegPack(RF, InData%UJacSclFact) call RegPack(RF, InData%SizeJac_Opt1) @@ -1513,6 +1516,7 @@ subroutine FAST_UnPackParam(RF, OutData) call RegUnpack(RF, OutData%RhoInf); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%ConvTol); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%MaxConvIter); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%Relax); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%DT_Ujac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%UJacSclFact); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%SizeJac_Opt1); if (RegCheckErr(RF, RoutineName)) return diff --git a/modules/openfast-library/src/Glue_Registry.txt b/modules/openfast-library/src/Glue_Registry.txt index c098ee5537..28c6dbc1c8 100644 --- a/modules/openfast-library/src/Glue_Registry.txt +++ b/modules/openfast-library/src/Glue_Registry.txt @@ -74,6 +74,7 @@ typedef ^ ^ R8Ki ConvTol - - - typedef ^ ^ IntKi ModCoupling - - - "Module coupling method {1=loose; 2=tight with fixed Jacobian updates (DT_UJac); 3=tight with automatic Jacobian updates}" - typedef ^ ^ IntKi NumCrctn - - - "" - typedef ^ ^ IntKi MaxConvIter - - - "" - +typedef ^ ^ R8Ki Relax - 1.0_R8Ki - "Under-relaxation factor for the iterative solver" - typedef ^ ^ IntKi NIter_UJac - - - "Number of solution iterations between updating the Jacobian" - typedef ^ ^ IntKi NStep_UJac - - - "Number of global time steps between updating the Jacobian" - typedef ^ ^ R8Ki Scale_UJac - - - "Jacobian load scaling factor" - diff --git a/modules/openfast-library/src/Glue_Types.f90 b/modules/openfast-library/src/Glue_Types.f90 index cdbfd4dc5b..b471121cda 100644 --- a/modules/openfast-library/src/Glue_Types.f90 +++ b/modules/openfast-library/src/Glue_Types.f90 @@ -101,6 +101,7 @@ MODULE Glue_Types INTEGER(IntKi) :: ModCoupling = 0_IntKi !< Module coupling method {1=loose; 2=tight with fixed Jacobian updates (DT_UJac); 3=tight with automatic Jacobian updates} [-] INTEGER(IntKi) :: NumCrctn = 0_IntKi !< [-] INTEGER(IntKi) :: MaxConvIter = 0_IntKi !< [-] + REAL(R8Ki) :: Relax = 1.0_R8Ki !< Under-relaxation factor for the iterative solver [-] INTEGER(IntKi) :: NIter_UJac = 0_IntKi !< Number of solution iterations between updating the Jacobian [-] INTEGER(IntKi) :: NStep_UJac = 0_IntKi !< Number of global time steps between updating the Jacobian [-] REAL(R8Ki) :: Scale_UJac = 0.0_R8Ki !< Jacobian load scaling factor [-] @@ -742,6 +743,7 @@ subroutine Glue_CopyTCParam(SrcTCParamData, DstTCParamData, CtrlCode, ErrStat, E DstTCParamData%ModCoupling = SrcTCParamData%ModCoupling DstTCParamData%NumCrctn = SrcTCParamData%NumCrctn DstTCParamData%MaxConvIter = SrcTCParamData%MaxConvIter + DstTCParamData%Relax = SrcTCParamData%Relax DstTCParamData%NIter_UJac = SrcTCParamData%NIter_UJac DstTCParamData%NStep_UJac = SrcTCParamData%NStep_UJac DstTCParamData%Scale_UJac = SrcTCParamData%Scale_UJac @@ -863,6 +865,7 @@ subroutine Glue_PackTCParam(RF, Indata) call RegPack(RF, InData%ModCoupling) call RegPack(RF, InData%NumCrctn) call RegPack(RF, InData%MaxConvIter) + call RegPack(RF, InData%Relax) call RegPack(RF, InData%NIter_UJac) call RegPack(RF, InData%NStep_UJac) call RegPack(RF, InData%Scale_UJac) @@ -909,6 +912,7 @@ subroutine Glue_UnPackTCParam(RF, OutData) call RegUnpack(RF, OutData%ModCoupling); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NumCrctn); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%MaxConvIter); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%Relax); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NIter_UJac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NStep_UJac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%Scale_UJac); if (RegCheckErr(RF, RoutineName)) return From ccef055a242c9358255084507035461bccb52b4b Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Thu, 9 Apr 2026 15:53:45 -0600 Subject: [PATCH 2/5] Added a simple adaptive under-relaxation feature based on change in residual between iterations --- .../openfast-library/src/FAST_Registry.txt | 3 +- modules/openfast-library/src/FAST_Solver.f90 | 43 ++++++++++++++++--- modules/openfast-library/src/FAST_Subs.f90 | 19 +++++--- modules/openfast-library/src/FAST_Types.f90 | 12 ++++-- .../openfast-library/src/Glue_Registry.txt | 3 +- modules/openfast-library/src/Glue_Types.f90 | 12 ++++-- 6 files changed, 71 insertions(+), 21 deletions(-) diff --git a/modules/openfast-library/src/FAST_Registry.txt b/modules/openfast-library/src/FAST_Registry.txt index 61195d8efc..fd3a13b961 100644 --- a/modules/openfast-library/src/FAST_Registry.txt +++ b/modules/openfast-library/src/FAST_Registry.txt @@ -129,7 +129,8 @@ typedef ^ FAST_ParameterType IntKi ModCoupling - - - "Module coupling type {1=lo typedef ^ FAST_ParameterType DbKi RhoInf - - - "Numerical damping parameter for tight coupling generalized-alpha integrator (-) [0.0 to 1.0]" - typedef ^ FAST_ParameterType DbKi ConvTol - - - "Convergence iteration error tolerance for tight coupling generalized alpha integrator (-)" - typedef ^ FAST_ParameterType IntKi MaxConvIter - - - "Maximum number of convergence iterations for tight coupling generalized alpha integrator (-)" - -typedef ^ FAST_ParameterType DbKi Relax - 1.0_DbKi - "Under-relaxation factor for the iterative solver (-) [>0 and <=1]" - +typedef ^ FAST_ParameterType logical AutoRelax - .true. - "Adaptive under-relaxation (flag)" - +typedef ^ FAST_ParameterType DbKi RelaxFactor - 0.7_DbKi - "Constant or initial under-relaxation factor for the tight-coupling iterative solver (-) [>0 and <=1]" - # Data for Jacobians: typedef ^ FAST_ParameterType DbKi DT_Ujac - - - "Time between when we need to re-calculate these Jacobians" s typedef ^ FAST_ParameterType Reki UJacSclFact - - - "Scaling factor used to get similar magnitudes between accelerations, forces, and moments in Jacobians" - diff --git a/modules/openfast-library/src/FAST_Solver.f90 b/modules/openfast-library/src/FAST_Solver.f90 index 134e514f1b..21547e4c59 100644 --- a/modules/openfast-library/src/FAST_Solver.f90 +++ b/modules/openfast-library/src/FAST_Solver.f90 @@ -64,8 +64,11 @@ subroutine FAST_SolverInit(p_FAST, p, m, GlueModData, GlueModMaps, Turbine, ErrS ! Convergence tolerance p%ConvTol = p_FAST%ConvTol + ! Adaptive under-relaxation + p%AutoRelax = p_FAST%AutoRelax + ! Under-relaxation factor - p%Relax = p_FAST%Relax + p%RelaxFactor = p_FAST%RelaxFactor ! Solver time step p%h = p_FAST%DT @@ -1212,7 +1215,7 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps logical, parameter :: IsSolve = .true. integer(IntKi) :: ConvIter, CorrIter, TotalIter integer(IntKi) :: NumUJac, NumCorrections - real(R8Ki) :: ConvError + real(R8Ki) :: ConvError, ConvErrorLast real(DbKi) :: t_global_next ! next simulation time (m_FAST%t_global + p_FAST%dt) integer(IntKi) :: n_t_global_next ! n_t_global + 1 integer(IntKi) :: i, j, k @@ -1220,6 +1223,7 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps integer(IntKi) :: ConvUJac ! Jacobian updated for convergence integer(IntKi) :: MaxConvUJac ! Max times Jacobian can be updated for convergence real(R8Ki) :: RotDiff(3, 3) + real(R8Ki) :: RelaxFactor ErrStat = ErrID_None ErrMsg = '' @@ -1401,6 +1405,7 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps !------------------------------------------------------------------------- ! Loop through convergence iterations + RelaxFactor = p%RelaxFactor do ConvIter = 0, p%MaxConvIter ! Increment total number of convergence iterations in step @@ -1549,7 +1554,19 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps ! If at least one convergence iteration has been done and ! the RHS norm is less than convergence tolerance, exit loop - if ((ConvIter > 0) .and. (ConvError < p%ConvTol)) exit + if (ConvIter == 0) then + ConvErrorLast = ConvError + else + if (ConvError < p%ConvTol) exit + if (p%AutoRelax) then + if (ConvError 0) m%XB(p%iJL(1):p%iJL(2), 1) = m%XB(p%iJL(1):p%iJL(2), 1)*p%Scale_UJac @@ -1565,7 +1582,7 @@ subroutine FAST_SolverStep(n_t_global, t_initial, p, m, GlueModData, GlueModMaps !---------------------------------------------------------------------- ! Add change in inputs - if (p%iJU(1) > 0) call MV_AddDelta(m%Mod%Vars%u, m%XB(p%iJU(1):p%iJU(2), 1), m%Mod%Lin%u, RelaxIn=p%Relax) + if (p%iJU(1) > 0) call MV_AddDelta(m%Mod%Vars%u, m%XB(p%iJU(1):p%iJU(2), 1), m%Mod%Lin%u, RelaxIn=RelaxFactor) !---------------------------------------------------------------------- ! TC and Option 1: Transfer updated states and inputs to modules @@ -1642,6 +1659,7 @@ subroutine FAST_CalcOutputsAndSolveForInputs(p, m, GlueModData, GlueModMaps, Thi integer(IntKi) :: ErrStat2 character(ErrMsgLen) :: ErrMsg2 integer(IntKi) :: i + real(R8Ki) :: RelaxFactor, ConvErrorLast ErrStat = ErrID_None ErrMsg = '' @@ -1708,7 +1726,7 @@ subroutine FAST_CalcOutputsAndSolveForInputs(p, m, GlueModData, GlueModMaps, Thi !---------------------------------------------------------------------------- ! Convergence Iterations !---------------------------------------------------------------------------- - + RelaxFactor = p%RelaxFactor ! Loop through convergence iterations do ConvIter = 0, p%MaxConvIter @@ -1826,6 +1844,19 @@ subroutine FAST_CalcOutputsAndSolveForInputs(p, m, GlueModData, GlueModMaps, Thi exit end if + if (ConvIter == 0) then + ConvErrorLast = ConvError + else + if (p%AutoRelax) then + if (ConvError 0) m%IO_X(p%iUL(1):p%iUL(2), 1) = m%IO_X(p%iUL(1):p%iUL(2), 1)*p%Scale_UJac @@ -1834,7 +1865,7 @@ subroutine FAST_CalcOutputsAndSolveForInputs(p, m, GlueModData, GlueModMaps, Thi !------------------------------------------------------------------------- ! Add change in inputs - call MV_AddDelta(m%Mod%Vars%u, m%IO_X(:, 1), m%Mod%Lin%u, RelaxIn=p%Relax) + call MV_AddDelta(m%Mod%Vars%u, m%IO_X(:, 1), m%Mod%Lin%u, RelaxIn=RelaxFactor) ! Transfer updated TC and Option 1 inputs to modules do i = 1, size(m%Mod%ModData) diff --git a/modules/openfast-library/src/FAST_Subs.f90 b/modules/openfast-library/src/FAST_Subs.f90 index 2a0dfe1612..ff1c236262 100644 --- a/modules/openfast-library/src/FAST_Subs.f90 +++ b/modules/openfast-library/src/FAST_Subs.f90 @@ -1982,8 +1982,8 @@ SUBROUTINE ValidateInputData(p, m_FAST, ErrStat, ErrMsg) CALL SetErrStat( ErrID_Fatal, 'MaxIter must be at least 1.', ErrStat, ErrMsg, RoutineName ) END IF - IF ( (p%Relax <= 0.0_DbKi) .or. (p%Relax > 1.0_DbKi) ) THEN - CALL SetErrStat( ErrID_Fatal, 'Relax must be positive and less than or equal to 1.', ErrStat, ErrMsg, RoutineName ) + IF ( (p%RelaxFactor <= 0.0_DbKi) .or. (p%RelaxFactor > 1.0_DbKi) ) THEN + CALL SetErrStat( ErrID_Fatal, 'RelaxFactor must be positive and less than or equal to 1.', ErrStat, ErrMsg, RoutineName ) END IF ! Check that InputFileData%OutFmt is a valid format specifier and will fit over the column headings @@ -2841,11 +2841,20 @@ SUBROUTINE FAST_ReadPrimaryFile( InputFile, p, m_FAST, OverrideAbortErrLev, ErrS CALL ReadVar( UnIn, InputFile, p%MaxConvIter, "MaxConvIter", "Maximum number of convergence iterations "//& "for tight coupling generalized alpha integrator (-)", ErrStat2, ErrMsg2, UnEc) if (Failed()) return - - ! Relax - Under-relaxation factor for the iterative solver (-) [>0 and <=1] - CALL ReadVar( UnIn, InputFile, p%Relax, "Relax", "Under-relaxation factor for the iterative solver (-) [>0 and <=1]", ErrStat2, ErrMsg2, UnEc) + + ! AutoRelax - Adaptive under-relaxation (flag) + CALL ReadVarWDefault( UnIn, InputFile, p%AutoRelax, "AutoRelax", "Adaptive under-relaxation (flag)", .true., ErrStat2, ErrMsg2, UnEc) if (Failed()) return + ! RelaxFactor - Constant or initial under-relaxation factor for the iterative solver (-) [>0 and <=1] + if (p%AutoRelax) then + CALL ReadVarWDefault( UnIn, InputFile, p%RelaxFactor, "RelaxFactor", "Constant or initial under-relaxation factor for the iterative solver (-) [>0 and <=1]", 0.3_R8Ki, ErrStat2, ErrMsg2, UnEc) + if (Failed()) return + else + CALL ReadVarWDefault( UnIn, InputFile, p%RelaxFactor, "RelaxFactor", "Constant or initial under-relaxation factor for the iterative solver (-) [>0 and <=1]", 0.7_R8Ki, ErrStat2, ErrMsg2, UnEc) + if (Failed()) return + endif + ! DT_UJac - Time between calls to get Jacobians (s) CALL ReadVar( UnIn, InputFile, p%DT_UJac, "DT_UJac", "Time between calls to get Jacobians (s)", ErrStat2, ErrMsg2, UnEc) if (Failed()) return diff --git a/modules/openfast-library/src/FAST_Types.f90 b/modules/openfast-library/src/FAST_Types.f90 index 3db1ace6cc..543823a9bd 100644 --- a/modules/openfast-library/src/FAST_Types.f90 +++ b/modules/openfast-library/src/FAST_Types.f90 @@ -145,7 +145,8 @@ MODULE FAST_Types REAL(DbKi) :: RhoInf = 0.0_R8Ki !< Numerical damping parameter for tight coupling generalized-alpha integrator (-) [0.0 to 1.0] [-] REAL(DbKi) :: ConvTol = 0.0_R8Ki !< Convergence iteration error tolerance for tight coupling generalized alpha integrator (-) [-] INTEGER(IntKi) :: MaxConvIter = 0_IntKi !< Maximum number of convergence iterations for tight coupling generalized alpha integrator (-) [-] - REAL(DbKi) :: Relax = 1.0_DbKi !< Under-relaxation factor for the iterative solver (-) [>0 and <=1] [-] + LOGICAL :: AutoRelax = .true. !< Adaptive under-relaxation (flag) [-] + REAL(DbKi) :: RelaxFactor = 0.7_DbKi !< Constant or initial under-relaxation factor for the tight-coupling iterative solver (-) [>0 and <=1] [-] REAL(DbKi) :: DT_Ujac = 0.0_R8Ki !< Time between when we need to re-calculate these Jacobians [s] REAL(ReKi) :: UJacSclFact = 0.0_ReKi !< Scaling factor used to get similar magnitudes between accelerations, forces, and moments in Jacobians [-] INTEGER(IntKi) , DIMENSION(1:9) :: SizeJac_Opt1 = 0_IntKi !< (1)=size of matrix; (2)=size of ED portion; (3)=size of SD portion [2 meshes]; (4)=size of HD portion; (5)=size of BD portion blade 1; (6)=size of BD portion blade 2; (7)=size of BD portion blade 3; (8)=size of Orca portion; (9)=size of ExtPtfm portion; [-] @@ -1163,7 +1164,8 @@ subroutine FAST_CopyParam(SrcParamData, DstParamData, CtrlCode, ErrStat, ErrMsg) DstParamData%RhoInf = SrcParamData%RhoInf DstParamData%ConvTol = SrcParamData%ConvTol DstParamData%MaxConvIter = SrcParamData%MaxConvIter - DstParamData%Relax = SrcParamData%Relax + DstParamData%AutoRelax = SrcParamData%AutoRelax + DstParamData%RelaxFactor = SrcParamData%RelaxFactor DstParamData%DT_Ujac = SrcParamData%DT_Ujac DstParamData%UJacSclFact = SrcParamData%UJacSclFact DstParamData%SizeJac_Opt1 = SrcParamData%SizeJac_Opt1 @@ -1393,7 +1395,8 @@ subroutine FAST_PackParam(RF, Indata) call RegPack(RF, InData%RhoInf) call RegPack(RF, InData%ConvTol) call RegPack(RF, InData%MaxConvIter) - call RegPack(RF, InData%Relax) + call RegPack(RF, InData%AutoRelax) + call RegPack(RF, InData%RelaxFactor) call RegPack(RF, InData%DT_Ujac) call RegPack(RF, InData%UJacSclFact) call RegPack(RF, InData%SizeJac_Opt1) @@ -1516,7 +1519,8 @@ subroutine FAST_UnPackParam(RF, OutData) call RegUnpack(RF, OutData%RhoInf); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%ConvTol); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%MaxConvIter); if (RegCheckErr(RF, RoutineName)) return - call RegUnpack(RF, OutData%Relax); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%AutoRelax); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%RelaxFactor); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%DT_Ujac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%UJacSclFact); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%SizeJac_Opt1); if (RegCheckErr(RF, RoutineName)) return diff --git a/modules/openfast-library/src/Glue_Registry.txt b/modules/openfast-library/src/Glue_Registry.txt index 28c6dbc1c8..6fd9e2bcb5 100644 --- a/modules/openfast-library/src/Glue_Registry.txt +++ b/modules/openfast-library/src/Glue_Registry.txt @@ -74,7 +74,8 @@ typedef ^ ^ R8Ki ConvTol - - - typedef ^ ^ IntKi ModCoupling - - - "Module coupling method {1=loose; 2=tight with fixed Jacobian updates (DT_UJac); 3=tight with automatic Jacobian updates}" - typedef ^ ^ IntKi NumCrctn - - - "" - typedef ^ ^ IntKi MaxConvIter - - - "" - -typedef ^ ^ R8Ki Relax - 1.0_R8Ki - "Under-relaxation factor for the iterative solver" - +typedef ^ ^ logical AutoRelax - .true. - "Adaptive under-relaxation (flag)" - +typedef ^ ^ R8Ki RelaxFactor - 0.7_R8Ki - "Constant or initial under-relaxation factor for the tight-coupling iterative solver" - typedef ^ ^ IntKi NIter_UJac - - - "Number of solution iterations between updating the Jacobian" - typedef ^ ^ IntKi NStep_UJac - - - "Number of global time steps between updating the Jacobian" - typedef ^ ^ R8Ki Scale_UJac - - - "Jacobian load scaling factor" - diff --git a/modules/openfast-library/src/Glue_Types.f90 b/modules/openfast-library/src/Glue_Types.f90 index b471121cda..4677803f48 100644 --- a/modules/openfast-library/src/Glue_Types.f90 +++ b/modules/openfast-library/src/Glue_Types.f90 @@ -101,7 +101,8 @@ MODULE Glue_Types INTEGER(IntKi) :: ModCoupling = 0_IntKi !< Module coupling method {1=loose; 2=tight with fixed Jacobian updates (DT_UJac); 3=tight with automatic Jacobian updates} [-] INTEGER(IntKi) :: NumCrctn = 0_IntKi !< [-] INTEGER(IntKi) :: MaxConvIter = 0_IntKi !< [-] - REAL(R8Ki) :: Relax = 1.0_R8Ki !< Under-relaxation factor for the iterative solver [-] + LOGICAL :: AutoRelax = .true. !< Adaptive under-relaxation (flag) [-] + REAL(R8Ki) :: RelaxFactor = 0.7_R8Ki !< Constant or initial under-relaxation factor for the tight-coupling iterative solver [-] INTEGER(IntKi) :: NIter_UJac = 0_IntKi !< Number of solution iterations between updating the Jacobian [-] INTEGER(IntKi) :: NStep_UJac = 0_IntKi !< Number of global time steps between updating the Jacobian [-] REAL(R8Ki) :: Scale_UJac = 0.0_R8Ki !< Jacobian load scaling factor [-] @@ -743,7 +744,8 @@ subroutine Glue_CopyTCParam(SrcTCParamData, DstTCParamData, CtrlCode, ErrStat, E DstTCParamData%ModCoupling = SrcTCParamData%ModCoupling DstTCParamData%NumCrctn = SrcTCParamData%NumCrctn DstTCParamData%MaxConvIter = SrcTCParamData%MaxConvIter - DstTCParamData%Relax = SrcTCParamData%Relax + DstTCParamData%AutoRelax = SrcTCParamData%AutoRelax + DstTCParamData%RelaxFactor = SrcTCParamData%RelaxFactor DstTCParamData%NIter_UJac = SrcTCParamData%NIter_UJac DstTCParamData%NStep_UJac = SrcTCParamData%NStep_UJac DstTCParamData%Scale_UJac = SrcTCParamData%Scale_UJac @@ -865,7 +867,8 @@ subroutine Glue_PackTCParam(RF, Indata) call RegPack(RF, InData%ModCoupling) call RegPack(RF, InData%NumCrctn) call RegPack(RF, InData%MaxConvIter) - call RegPack(RF, InData%Relax) + call RegPack(RF, InData%AutoRelax) + call RegPack(RF, InData%RelaxFactor) call RegPack(RF, InData%NIter_UJac) call RegPack(RF, InData%NStep_UJac) call RegPack(RF, InData%Scale_UJac) @@ -912,7 +915,8 @@ subroutine Glue_UnPackTCParam(RF, OutData) call RegUnpack(RF, OutData%ModCoupling); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NumCrctn); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%MaxConvIter); if (RegCheckErr(RF, RoutineName)) return - call RegUnpack(RF, OutData%Relax); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%AutoRelax); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%RelaxFactor); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NIter_UJac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%NStep_UJac); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%Scale_UJac); if (RegCheckErr(RF, RoutineName)) return From 7557ad4ec3051c54fe7262bfbb66057b88125c68 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 10 Apr 2026 12:46:39 -0600 Subject: [PATCH 3/5] Update openfast_io with the new inputs for under-relaxation --- openfast_io/openfast_io/FAST_reader.py | 2 ++ openfast_io/openfast_io/FAST_writer.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py index 07f7d0d2ed..a675f78059 100644 --- a/openfast_io/openfast_io/FAST_reader.py +++ b/openfast_io/openfast_io/FAST_reader.py @@ -342,6 +342,8 @@ def read_MainInput(self): self.fst_vt['Fst']['RhoInf'] = float_read(f.readline().split()[0]) self.fst_vt['Fst']['ConvTol'] = float_read(f.readline().split()[0]) self.fst_vt['Fst']['MaxConvIter'] = int(f.readline().split()[0]) + self.fst_vt['Fst']['AutoRelax'] = bool_read(f.readline().split()[0]) + self.fst_vt['Fst']['RelaxFactor'] = float_read(f.readline().split()[0]) self.fst_vt['Fst']['DT_UJac'] = float_read(f.readline().split()[0]) self.fst_vt['Fst']['UJacSclFact'] = float_read(f.readline().split()[0]) diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py index e9998d6fbf..29b3e487c6 100644 --- a/openfast_io/openfast_io/FAST_writer.py +++ b/openfast_io/openfast_io/FAST_writer.py @@ -294,6 +294,8 @@ def write_MainInput(self): f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['RhoInf'], 'RhoInf', '- Convergence iteration error tolerance for tight coupling generalized alpha integrator (-)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['ConvTol'], 'ConvTol', '- Maximum number of convergence iterations for tight coupling generalized alpha integrator (-)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['MaxConvIter'], 'MaxConvIter', '- Number of correction iterations (-) {0=explicit calculation, i.e., no corrections}\n')) + f.write('{!s:<22} {:<11} {:}'.format(self.fst_vt['Fst']['AutoRelax'], 'AutoRelax', '- Adaptive under-relaxation for the tight-coupling iterative solver (flag) [default=true]\n')) + f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['RelaxFactor'], 'RelaxFactor', '- Constant or initial (if AutoRelax) under-relaxation factor for the tight-coupling iterative solver (-) [>0 and <=1; default=0.7 if AutoRelax=false; default=0.3 if AutoRelax=true]\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['DT_UJac'], 'DT_UJac', '- Time between calls to get Jacobians (s)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['Fst']['UJacSclFact'], 'UJacSclFact', '- Scaling factor used in Jacobians (-)\n')) f.write('---------------------- FEATURE SWITCHES AND FLAGS ------------------------------\n') From b93237f0ed76e024b345c128d2cc1a1d740b0818 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 10 Apr 2026 13:29:29 -0600 Subject: [PATCH 4/5] Update user docs with the new inputs for under-relaxation --- docs/source/user/api_change.rst | 14 +++++++++++++ docs/source/user/glue-code/solver.rst | 29 ++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/source/user/api_change.rst b/docs/source/user/api_change.rst index 9a2087ff80..aeeeb81700 100644 --- a/docs/source/user/api_change.rst +++ b/docs/source/user/api_change.rst @@ -9,6 +9,20 @@ The changes are tabulated according to the module input file, line number, and f The line number corresponds to the resulting line number after all changes are implemented. Thus, be sure to implement each in order so that subsequent line numbers are correct. +OpenFAST v5.0.x to OpenFAST v5.1.0 +----------------------------------- + +Under-relaxation is introduced for the tight-coupling iterative solver to improve numerical stability, requiring two new inputs in the main OpenFAST input file. + +============================================= ======== ==================== ========================================================================================================================================================================================================================================================================================================== +Added in OpenFAST `5.1.0` +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Module Line Flag Name Example Value +============================================= ======== ==================== ========================================================================================================================================================================================================================================================================================================== +OpenFAST 14 AutoRelax default AutoRelax - Adaptive under-relaxation for the tight-coupling iterative solver (flag) [default=true] +OpenFAST 15 RelaxFactor default RelaxFactor - Constant or initial (if AutoRelax) under-relaxation factor for the tight-coupling iterative solver (-) [>0 and <=1; default=0.7 if AutoRelax=false; default=0.3 if AutoRelax=true] +============================================= ======== ==================== ========================================================================================================================================================================================================================================================================================================== + OpenFAST v4.2.x to OpenFAST v5.0.0 ----------------------------------- diff --git a/docs/source/user/glue-code/solver.rst b/docs/source/user/glue-code/solver.rst index 62bf2a6256..828e0d6649 100644 --- a/docs/source/user/glue-code/solver.rst +++ b/docs/source/user/glue-code/solver.rst @@ -50,6 +50,12 @@ All solver parameters are set in the main OpenFAST input file maximum damping (first-order accurate). Typical value: **0.9**. Reducing ``RhoInf`` below 1 damps high-frequency numerical noise at the cost of slightly reduced accuracy. + * - ``ConvTol`` + - real + - Convergence tolerance. The iteration stops when the average + `L2`-norm of the Newton update vector falls below this value. + Typical value: ``1.0e-4``. Tighter tolerances increase + computational cost but may be needed for stiff problems. * - ``MaxConvIter`` - integer - Maximum number of Newton convergence iterations per time step before the @@ -57,12 +63,25 @@ All solver parameters are set in the main OpenFAST input file With ``ModCoupling=2`` or ``1``, a fatal error is issued on failure; with ``ModCoupling=3`` the Jacobian is rebuilt first and the step is retried before a warning is emitted. - * - ``ConvTol`` + * - ``AutoRelax`` + - bool + - Adaptive under-relaxation for the tight-coupling iterative solver. + If set to ``true``, OpenFAST will only use the user provided + ``RelaxFactor`` in the first iteration of each predictor or corrector + step. In subsequent iterations, OpenFAST will increase the relaxation + factor from the previous iteration by a factor of 1.2 if converging + (residual decreasing) or halve the relaxation factor if diverging + (residual increasing). The relaxation factor is bounded between a + minimum of 0.01 and a maximum of 0.8. If set to ``false``, OpenFAST + will use a constant relaxation factor given by ``RelaxFactor``. + Default is true. + * - ``RelaxFactor`` - real - - Convergence tolerance. The iteration stops when the average - `L2`-norm of the Newton update vector falls below this value. - Typical value: ``1.0e-4``. Tighter tolerances increase - computational cost but may be needed for stiff problems. + - Constant (if ``AutoRelax`` is ``False``) or initial (if ``AutoRelax`` + is ``True``) under-relaxation factor for the tight-coupling iterative + solver. Must be a positive number less than or equal to **1.0**. If + ``AutoRelax`` is ``False``, default value is **0.7**. If ``AutoRelax`` + is ``True``, default value is **0.3**. * - ``DT_UJac`` - real - Time interval (seconds) between Jacobian rebuilds when From 0a8984ca50dc8e2f2807e155082f032e25e5942e Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 10 Apr 2026 13:32:35 -0600 Subject: [PATCH 5/5] Update r-test pointer --- reg_tests/r-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reg_tests/r-test b/reg_tests/r-test index e007bb70ad..7acd904925 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit e007bb70ad4277860f655d2d92f48282fce8683b +Subproject commit 7acd90492526592e676fc4f3f8fa114bc78b421c