From 51979ecb85608d80bce45ee8f7db601618b3d81b Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Tue, 1 Sep 2026 17:09:22 -0600 Subject: [PATCH 01/11] OLAF/FVW: add selectable compact-C2 particle kernel (RegFunctionPart) Item 1: true finite-support compact-C2 particle regularization kernel with kernel-aware treecode near-core floor. - FVW_BiotSavart.f90: PART_REG_C2=1.6 param; PartRegFloorFactor(RegFunction); rewrite idRegCompact case in ui_part_nograd_11 as (1-rho^2)^2 blob with the 1.6 core-equivalence factor baked into the kernel (RegParam/seeding unchanged). - FVW_VortexTools.f90: particle tree near-core floor uses PartRegFloorFactor; segment tree floor unchanged. Item 2: make idRegCompact selectable via a new RegFunctionPart input so the particle kernel can be A/B tested against the exponential kernel. - FVW_Registry.txt / FVW_Types.f90: new RegFunctionPart field in Param and InputFile types (Types regenerated). - FVW_IO.f90: read RegFunctionPart (default Exponential) plus validation. - FVW.f90: copy RegFunctionPart into parameters. - FVW_Subs.f90: route RegFunctionPart through SegmentsToPartWrap and the particle-velocity call sites, replacing the previously forced idRegExp. Default RegFunctionPart=Exponential preserves existing behavior. Core spreading is unchanged (it acts upstream on filament cores). Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus --- modules/aerodyn/src/FVW.f90 | 1 + modules/aerodyn/src/FVW_BiotSavart.f90 | 31 +++++++++++++++++++++---- modules/aerodyn/src/FVW_IO.f90 | 2 ++ modules/aerodyn/src/FVW_Registry.txt | 2 ++ modules/aerodyn/src/FVW_Subs.f90 | 16 ++++++------- modules/aerodyn/src/FVW_Types.f90 | 8 +++++++ modules/aerodyn/src/FVW_VortexTools.f90 | 4 ++-- 7 files changed, 48 insertions(+), 16 deletions(-) diff --git a/modules/aerodyn/src/FVW.f90 b/modules/aerodyn/src/FVW.f90 index 6bc12eb3ef..ad3ba42202 100644 --- a/modules/aerodyn/src/FVW.f90 +++ b/modules/aerodyn/src/FVW.f90 @@ -456,6 +456,7 @@ SUBROUTINE FVW_SetParametersFromInputFile( InputFileData, p, ErrStat, ErrMsg ) p%FWShedVorticity = InputFileData%FWShedVorticity p%DiffusionMethod = InputFileData%DiffusionMethod p%RegFunction = InputFileData%RegFunction + p%RegFunctionPart = InputFileData%RegFunctionPart p%RegDeterMethod = InputFileData%RegDeterMethod p%WakeRegMethod = InputFileData%WakeRegMethod p%WakeRegParam = InputFileData%WakeRegParam diff --git a/modules/aerodyn/src/FVW_BiotSavart.f90 b/modules/aerodyn/src/FVW_BiotSavart.f90 index d6db453b6f..372a8fc279 100644 --- a/modules/aerodyn/src/FVW_BiotSavart.f90 +++ b/modules/aerodyn/src/FVW_BiotSavart.f90 @@ -12,6 +12,7 @@ module FVW_BiotSavart real(ReKi),parameter :: MIN_EXP_VALUE=-10.0_ReKi real(ReKi),parameter :: PART_REG_NRAD = 2.0_ReKi !< Particle exp mollifier treated as 1 beyond this many core radii (matches 2*rc far-field multipole floor) real(ReKi),parameter :: PART_REG_CUT3 = PART_REG_NRAD**3 !< Corresponding (r/rc)^3 cutoff + real(ReKi),parameter :: PART_REG_C2 = 1.6_ReKi !< Compact-C2 support = PART_REG_C2*RegParam (exp-equivalent core); also the exact multipole floor for that kernel real(ReKi),parameter :: MINDENOM=0.0_ReKi ! real(ReKi),parameter :: MINDENOM=1e-15_ReKi real(ReKi),parameter :: MINNORM=1e-4 @@ -31,6 +32,19 @@ module FVW_BiotSavart contains +!> Multipole floor factor for the particle kernels: number of core radii beyond which the +!! regularized kernel equals the singular 1/r^3, so the far-field multipole expansion is valid. +pure function PartRegFloorFactor(RegFunction) result(f) + integer(IntKi), intent(in) :: RegFunction + real(ReKi) :: f + select case (RegFunction) + case (idRegCompact) ! Truly compact: exactly singular beyond rc = PART_REG_C2*RegParam + f = PART_REG_C2 + case default ! Exponential (and none): conservative 2*rc floor + f = PART_REG_NRAD + end select +end function PartRegFloorFactor + !> Induced velocity from one segment at one control points subroutine ui_seg_11(DeltaPa, DeltaPb, SegGamma, RegFunction, RegParam1, Uind) @@ -376,7 +390,8 @@ subroutine ui_part_nograd_11(DeltaP, Alpha, RegFunction, RegParam, Ui) real(ReKi),dimension(3) :: C !< Cross product of Alpha and r real(ReKi) :: E !< Exponential poart for the mollifider real(ReKi) :: r3_inv !< - real(ReKi) :: r2, r3, rc3!< |r|^2, |r|^3, RegParam^3 (reused to avoid recomputing ** intrinsics) + real(ReKi) :: r2, r3, rc3!< |r|^2, |r|^3, core^3 (reused to avoid recomputing ** intrinsics) + real(ReKi) :: rc2, t !< compact-C2 core^2 and (r/rc)^2 real(ReKi) :: rDeltaP !< norm , distance between point and particle real(ReKi) :: ScalarPart !< the part containing the inverse of the distance, but not 4pi, Mollifier r2 = DeltaP(1)**2+ DeltaP(2)**2+ DeltaP(3)**2 @@ -402,10 +417,16 @@ subroutine ui_part_nograd_11(DeltaP, Alpha, RegFunction, RegParam, Ui) E = exp(-r3/rc3) ScalarPart = (1._ReKi-E)*r3_inv*fourpi_inv endif - case (idRegCompact) ! Compact support - rc3 = RegParam*RegParam*RegParam - r3_inv = 1._ReKi/sqrt(rc3*rc3+r3*r3) - ScalarPart = r3_inv*fourpi_inv + case (idRegCompact) ! Truly compact C2 core: exactly singular for r>=rc, rc=PART_REG_C2*RegParam + rc2 = (PART_REG_C2*RegParam)**2 + if (r2 >= rc2) then ! outside core: kernel is exactly the singular 1/r^3 + r3_inv = 1._ReKi/r3 + ScalarPart = r3_inv*fourpi_inv + else ! inside core: C2 polynomial mollifier + t = r2/rc2 + rc3 = rc2*PART_REG_C2*RegParam + ScalarPart = (35._ReKi + t*(-42._ReKi + 15._ReKi*t))*fourpi_inv/(8._ReKi*rc3) + endif case default print*,'[ERROR] Wrong regularization function for particles',RegFunction STOP diff --git a/modules/aerodyn/src/FVW_IO.f90 b/modules/aerodyn/src/FVW_IO.f90 index a7c252bf04..40b03d1654 100644 --- a/modules/aerodyn/src/FVW_IO.f90 +++ b/modules/aerodyn/src/FVW_IO.f90 @@ -60,6 +60,7 @@ SUBROUTINE FVW_ReadInputFile( FileName, p, m, Inp, ErrStat, ErrMsg ) CALL ReadVarWDefault(UnIn,FileName,Inp%DiffusionMethod ,'DiffusionMethod' ,'',idDiffusionNone , ErrStat2,ErrMsg2); if(Failed())return CALL ReadVarWDefault(UnIn,FileName,Inp%RegDeterMethod ,'RegDeterMethod' ,'',idRegDeterConstant, ErrStat2,ErrMsg2); if(Failed())return CALL ReadVarWDefault(UnIn,FileName,Inp%RegFunction ,'RegFunction' ,'',idRegVatistas , ErrStat2,ErrMsg2); if(Failed())return + CALL ReadVarWDefault(UnIn,FileName,Inp%RegFunctionPart ,'RegFunctionPart' ,'',idRegExp , ErrStat2,ErrMsg2); if(Failed())return CALL ReadVarWDefault(UnIn,FileName,Inp%WakeRegMethod ,'WakeRegMethod' ,'',idRegAge , ErrStat2,ErrMsg2); if(Failed())return CALL ReadVar (UnIn,FileName,Inp%WakeRegParam ,'WakeRegParam' ,'' , ErrStat2,ErrMsg2); if(Failed())return CALL ReadVar (UnIn,FileName,Inp%WingRegParam ,'WingRegParam' ,'' , ErrStat2,ErrMsg2); if(Failed())return @@ -190,6 +191,7 @@ SUBROUTINE FVW_ReadInputFile( FileName, p, m, Inp, ErrStat, ErrMsg ) if (Check(.not.(ANY(idDiffusionVALID==Inp%DiffusionMethod)) , 'Diffusion method (DiffusionMethod) not implemented: '//trim(Num2LStr(Inp%DiffusionMethod)))) return if (Check(.not.(ANY(idRegDeterVALID ==Inp%RegDeterMethod)) , 'Regularization determination method (RegDeterMethod) not yet implemented: '//trim(Num2LStr(Inp%RegDeterMethod)))) return if (Check(.not.(ANY(idRegVALID ==Inp%RegFunction )), 'Regularization function (RegFunction) not implemented: '//trim(Num2LStr(Inp%RegFunction)))) return + if (Check(.not.(ANY(idRegPartVALID ==Inp%RegFunctionPart)), 'Particle regularization function (RegFunctionPart) not implemented: '//trim(Num2LStr(Inp%RegFunctionPart)))) return if (Check(.not.(ANY(idRegMethodVALID==Inp%WakeRegMethod)), 'Wake regularization method (WakeRegMethod) not implemented: '//trim(Num2LStr(Inp%WakeRegMethod)))) return if (Check(.not.(ANY(idShearVALID ==Inp%ShearModel )), 'Shear model (ShearModel) not valid: '//trim(Num2LStr(Inp%ShearModel)))) return if (Check(.not.(ANY(idVelocityVALID ==Inp%VelocityMethod(1))), 'Velocity method (VelocityMethod(1)) not valid: '//trim(Num2LStr(Inp%VelocityMethod(1))))) return diff --git a/modules/aerodyn/src/FVW_Registry.txt b/modules/aerodyn/src/FVW_Registry.txt index 2d9ce90812..72968d627f 100644 --- a/modules/aerodyn/src/FVW_Registry.txt +++ b/modules/aerodyn/src/FVW_Registry.txt @@ -116,6 +116,7 @@ typedef ^ ^ IntKi typedef ^ ^ ReKi CoreSpreadEddyVisc - - - "Eddy viscosity used in the core spreading method" typedef ^ ^ IntKi RegDeterMethod - - - "Regularization determinatino method (manual, automatic)" - typedef ^ ^ IntKi RegFunction - - - "Type of regularizaion function (LambOseen, Vatistas, see FVW_BiotSavart)" - +typedef ^ ^ IntKi RegFunctionPart - - - "Type of particle regularization function (None, Exp, Compact; see FVW_BiotSavart)" - typedef ^ ^ IntKi WakeRegMethod - - - "Method for regularization (constant, stretching, age, etc.)" - typedef ^ ^ ReKi WakeRegParam - - - "Initial value of the regularization parameter" typedef ^ ^ ReKi WingRegParam - - - "Regularization parameter of the wing" @@ -334,6 +335,7 @@ typedef ^ ^ IntKi typedef ^ ^ ReKi CoreSpreadEddyVisc - - - "Eddy viscosity used in the core spreading method" typedef ^ ^ IntKi RegDeterMethod - - - "Regularization determinatino method (manual, automatic)" - typedef ^ ^ IntKi RegFunction - - - "Type of regularizaion function (LambOseen, Vatistas, see FVW_BiotSavart)" - +typedef ^ ^ IntKi RegFunctionPart - - - "Type of particle regularization function (None, Exp, Compact; see FVW_BiotSavart)" - typedef ^ ^ IntKi WakeRegMethod - - - "Method for regularization (constant, stretching, age, etc.)" - typedef ^ ^ ReKi WakeRegParam - - - "Factor used in the regularization " typedef ^ ^ ReKi WingRegParam - - - "Factor used in the regularization " diff --git a/modules/aerodyn/src/FVW_Subs.f90 b/modules/aerodyn/src/FVW_Subs.f90 index 7da341ad64..ad30d5dded 100644 --- a/modules/aerodyn/src/FVW_Subs.f90 +++ b/modules/aerodyn/src/FVW_Subs.f90 @@ -843,7 +843,7 @@ subroutine FVW_InitMiscVarsPostParam( p, m, ErrStat, ErrMsg ) call AllocAry( m%Part%Alpha , 3, nPart, 'PartAlpha' , ErrStat2, ErrMsg2 ); if(Failed())return; m%Part%Alpha = -999999_ReKi; call AllocAry( m%Part%RegParam, nPart, 'PartEpsilon', ErrStat2, ErrMsg2 ); if(Failed())return; m%Part%RegParam= -999999_ReKi; m%Part%nAct = -1 ! Active particles - m%Part%RegFunction = p%RegFunction + m%Part%RegFunction = p%RegFunctionPart endif ! TODO Figure out Uind, CPs needed for grid @@ -1184,11 +1184,11 @@ subroutine InducedVelocitiesAll_OnGrid(g, p, x, m, ErrStat, ErrMsg) end subroutine InducedVelocitiesAll_OnGrid !> Wrapper to setup part from set of segments -subroutine SegmentsToPartWrap(Sgmt, nSeg, PartPerSegment, RegFunction, Part, allocPart) +subroutine SegmentsToPartWrap(Sgmt, nSeg, PartPerSegment, RegFunctionPart, Part, allocPart) type(T_Sgmt), intent(in ) :: Sgmt !< Segments integer(IntKi), intent(in ) :: nSeg !< Number of segments to use (might not use all of them) integer(IntKi), intent(in ) :: PartPerSegment !< Number of particles per segment - integer(IntKi), intent(in ) :: RegFunction !< Regularization function + integer(IntKi), intent(in ) :: RegFunctionPart !< Particle regularization function (None, Exp, Compact) type(T_Part), intent(inout) :: Part !< Particles logical, intent(in ) :: allocPart !< allocate particles integer(IntKi) :: iHeadP @@ -1219,9 +1219,7 @@ subroutine SegmentsToPartWrap(Sgmt, nSeg, PartPerSegment, RegFunction, Part, all Part%nAct = nPart ! TODO add iHeadPart if particles already present call SegmentsToPart(Sgmt%Points, Sgmt%Connct, Sgmt%Gamma, Sgmt%Epsilon, 1, nSeg, PartPerSegment, Part%P, Part%Alpha, Part%RegParam, iHeadP) - if (RegFunction/=idRegNone) then - Part%RegFunction = idRegExp ! TODO need to find a good equivalence and potentially adapt Epsilon in SegmentsToPart - endif + Part%RegFunction = RegFunctionPart ! TODO Epsilon equivalence between segment and particle cores may still need tuning in SegmentsToPart if (DEV_VERSION) then call find_nan_2D(Part%P(:,1:nPart) , 'SegmentsToPartWrap Part%P') call find_nan_2D(Part%Alpha(:,1:nPart), 'SegmentsToPartWrap Part%Alpha') @@ -1263,7 +1261,7 @@ subroutine InducedVelocitiesAll_Init(p, x, m, Sgmt, Part, Tree, Panl, ErrStat, E ! --- Convert to particles if needed if ((p%VelocityMethod(iVel)==idVelocityTreePart) .or. (p%VelocityMethod(iVel)==idVelocityPart)) then - call SegmentsToPartWrap(Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunction, Part, allocPart=allocPart) + call SegmentsToPartWrap(Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunctionPart, Part, allocPart=allocPart) endif ! --- Grow tree if needed @@ -1551,7 +1549,7 @@ subroutine LiftingLineInducedVelocities(p, x, InductionAtCP, iDepthStart, m, Err call ui_seg( 1, nCPs, CPs, 1, nSeg, m%Sgmt%Points, m%Sgmt%Connct, m%Sgmt%Gamma, m%Sgmt%RegFunction, m%Sgmt%Epsilon, Uind) else if (p%VelocityMethod(iVel) == idVelocityPart) then - call SegmentsToPartWrap(m%Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunction, m%Part, allocPart=.false.) + call SegmentsToPartWrap(m%Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunctionPart, m%Part, allocPart=.false.) call ui_part_nograd(nCPs, CPs, m%Part%nAct, m%Part%P, m%Part%Alpha, m%Part%RegFunction, m%Part%RegParam, Uind) !deallocate(Part%P, Part%Alpha, Part%RegParam) @@ -1561,7 +1559,7 @@ subroutine LiftingLineInducedVelocities(p, x, InductionAtCP, iDepthStart, m, Err call cut_tree(Tree) else if (p%VelocityMethod(iVel) == idVelocityTreePart) then - call SegmentsToPartWrap(m%Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunction, m%Part, allocPart=.false.) + call SegmentsToPartWrap(m%Sgmt, nSeg, p%PartPerSegment(iVel), p%RegFunctionPart, m%Part, allocPart=.false.) call grow_tree_part(Tree, m%Part%nAct, m%Part%P, m%Part%Alpha, m%Part%RegFunction, m%Part%RegParam, 0) call ui_tree_part(Tree, nCPs, CPs, p%TreeBranchFactor(iVel), DistanceDirect, Uind, ErrStat, ErrMsg) !deallocate(Part%P, Part%Alpha, Part%RegParam) diff --git a/modules/aerodyn/src/FVW_Types.f90 b/modules/aerodyn/src/FVW_Types.f90 index 9f0b0b1aa7..e8d100f37b 100644 --- a/modules/aerodyn/src/FVW_Types.f90 +++ b/modules/aerodyn/src/FVW_Types.f90 @@ -157,6 +157,7 @@ MODULE FVW_Types REAL(ReKi) :: CoreSpreadEddyVisc = 0.0_ReKi !< Eddy viscosity used in the core spreading method [-] INTEGER(IntKi) :: RegDeterMethod = 0_IntKi !< Regularization determinatino method (manual, automatic) [-] INTEGER(IntKi) :: RegFunction = 0_IntKi !< Type of regularizaion function (LambOseen, Vatistas, see FVW_BiotSavart) [-] + INTEGER(IntKi) :: RegFunctionPart = 0_IntKi !< Type of particle regularization function (None, Exp, Compact; see FVW_BiotSavart) [-] INTEGER(IntKi) :: WakeRegMethod = 0_IntKi !< Method for regularization (constant, stretching, age, etc.) [-] REAL(ReKi) :: WakeRegParam = 0.0_ReKi !< Initial value of the regularization parameter [-] REAL(ReKi) :: WingRegParam = 0.0_ReKi !< Regularization parameter of the wing [-] @@ -385,6 +386,7 @@ MODULE FVW_Types REAL(ReKi) :: CoreSpreadEddyVisc = 0.0_ReKi !< Eddy viscosity used in the core spreading method [-] INTEGER(IntKi) :: RegDeterMethod = 0_IntKi !< Regularization determinatino method (manual, automatic) [-] INTEGER(IntKi) :: RegFunction = 0_IntKi !< Type of regularizaion function (LambOseen, Vatistas, see FVW_BiotSavart) [-] + INTEGER(IntKi) :: RegFunctionPart = 0_IntKi !< Type of particle regularization function (None, Exp, Compact; see FVW_BiotSavart) [-] INTEGER(IntKi) :: WakeRegMethod = 0_IntKi !< Method for regularization (constant, stretching, age, etc.) [-] REAL(ReKi) :: WakeRegParam = 0.0_ReKi !< Factor used in the regularization [-] REAL(ReKi) :: WingRegParam = 0.0_ReKi !< Factor used in the regularization [-] @@ -1541,6 +1543,7 @@ subroutine FVW_CopyParam(SrcParamData, DstParamData, CtrlCode, ErrStat, ErrMsg) DstParamData%CoreSpreadEddyVisc = SrcParamData%CoreSpreadEddyVisc DstParamData%RegDeterMethod = SrcParamData%RegDeterMethod DstParamData%RegFunction = SrcParamData%RegFunction + DstParamData%RegFunctionPart = SrcParamData%RegFunctionPart DstParamData%WakeRegMethod = SrcParamData%WakeRegMethod DstParamData%WakeRegParam = SrcParamData%WakeRegParam DstParamData%WingRegParam = SrcParamData%WingRegParam @@ -1641,6 +1644,7 @@ subroutine FVW_PackParam(RF, Indata) call RegPack(RF, InData%CoreSpreadEddyVisc) call RegPack(RF, InData%RegDeterMethod) call RegPack(RF, InData%RegFunction) + call RegPack(RF, InData%RegFunctionPart) call RegPack(RF, InData%WakeRegMethod) call RegPack(RF, InData%WakeRegParam) call RegPack(RF, InData%WingRegParam) @@ -1719,6 +1723,7 @@ subroutine FVW_UnPackParam(RF, OutData) call RegUnpack(RF, OutData%CoreSpreadEddyVisc); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%RegDeterMethod); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%RegFunction); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%RegFunctionPart); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WakeRegMethod); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WakeRegParam); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WingRegParam); if (RegCheckErr(RF, RoutineName)) return @@ -4232,6 +4237,7 @@ subroutine FVW_CopyInputFile(SrcInputFileData, DstInputFileData, CtrlCode, ErrSt DstInputFileData%CoreSpreadEddyVisc = SrcInputFileData%CoreSpreadEddyVisc DstInputFileData%RegDeterMethod = SrcInputFileData%RegDeterMethod DstInputFileData%RegFunction = SrcInputFileData%RegFunction + DstInputFileData%RegFunctionPart = SrcInputFileData%RegFunctionPart DstInputFileData%WakeRegMethod = SrcInputFileData%WakeRegMethod DstInputFileData%WakeRegParam = SrcInputFileData%WakeRegParam DstInputFileData%WingRegParam = SrcInputFileData%WingRegParam @@ -4281,6 +4287,7 @@ subroutine FVW_PackInputFile(RF, Indata) call RegPack(RF, InData%CoreSpreadEddyVisc) call RegPack(RF, InData%RegDeterMethod) call RegPack(RF, InData%RegFunction) + call RegPack(RF, InData%RegFunctionPart) call RegPack(RF, InData%WakeRegMethod) call RegPack(RF, InData%WakeRegParam) call RegPack(RF, InData%WingRegParam) @@ -4322,6 +4329,7 @@ subroutine FVW_UnPackInputFile(RF, OutData) call RegUnpack(RF, OutData%CoreSpreadEddyVisc); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%RegDeterMethod); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%RegFunction); if (RegCheckErr(RF, RoutineName)) return + call RegUnpack(RF, OutData%RegFunctionPart); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WakeRegMethod); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WakeRegParam); if (RegCheckErr(RF, RoutineName)) return call RegUnpack(RF, OutData%WingRegParam); if (RegCheckErr(RF, RoutineName)) return diff --git a/modules/aerodyn/src/FVW_VortexTools.f90 b/modules/aerodyn/src/FVW_VortexTools.f90 index d2a1b9b935..a00d60d93b 100644 --- a/modules/aerodyn/src/FVW_VortexTools.f90 +++ b/modules/aerodyn/src/FVW_VortexTools.f90 @@ -1453,7 +1453,7 @@ end subroutine cut_tree_segment_parallel ! --- Velocity computation ! -------------------------------------------------------------------------------- subroutine ui_tree_part(Tree, icp_end, CPs, BranchFactor, DistanceDirect, Uind, ErrStat, ErrMsg) - use FVW_BiotSavart, only: ui_part_nograd_11 + use FVW_BiotSavart, only: ui_part_nograd_11, PartRegFloorFactor type(T_Tree), target, intent(inout) :: Tree !< integer, intent(in ) :: icp_end !< Number of CPs to use Date: Wed, 2 Sep 2026 13:16:56 -0600 Subject: [PATCH 02/11] Update example OLAF input file in docs with the new RegFunctionPart input and update r-test pointer --- .../source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat | 1 + reg_tests/r-test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat b/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat index 4fdfb0ad97..08f340e2c8 100644 --- a/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat +++ b/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat @@ -23,6 +23,7 @@ default FWShedVorticity - Include shed vorticity in the far wake {default: fa default DiffusionMethod - Diffusion method to account for viscous effects {0: None, 1: Core Spreading, "default": 0} 2 RegDeterMethod - Method to determine the regularization parameters {0: Constant, 1: Optimized, 2: Chord-scaled, 3: dr-scaled, default: 0 } default RegFunction - Viscous diffusion function {0: None, 1: Rankine, 2: LambOseen, 3: Vatistas, 4: Denominator, "default": 3} (switch) +1 RegFunctionPart - Particle regularization function {0: None, 1: Exponential, 2: Compact, "default": 1} (switch) default WakeRegMethod - Wake regularization method {1: Constant, 2: Stretching, 3: Age, default: 1} (switch) 0.25 WakeRegFactor - Wake regularization factor (m or -) 0.25 WingRegFactor - Wing regularization factor (m or -) diff --git a/reg_tests/r-test b/reg_tests/r-test index f8d1d131de..e14f28eec0 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit f8d1d131debad662dffa749bf37b440f8320095f +Subproject commit e14f28eec0d58e2b4bd44f17094c41bc1aaabed2 From cd288e5ca5a49bec9ba820102289e9666c7748d4 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Wed, 2 Sep 2026 16:19:10 -0600 Subject: [PATCH 03/11] Update openfast_io with the new RegFunctionPart input for OLAF --- openfast_io/openfast_io/FAST_reader.py | 1 + openfast_io/openfast_io/FAST_writer.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openfast_io/openfast_io/FAST_reader.py b/openfast_io/openfast_io/FAST_reader.py index 07f7d0d2ed..a5a76c41ec 100644 --- a/openfast_io/openfast_io/FAST_reader.py +++ b/openfast_io/openfast_io/FAST_reader.py @@ -1468,6 +1468,7 @@ def read_AeroDynOLAF(self, olaf_filename): self.fst_vt['AeroDyn']['OLAF']['DiffusionMethod'] = int_read(f.readline().split()[0]) self.fst_vt['AeroDyn']['OLAF']['RegDeterMethod'] = int_read(f.readline().split()[0]) self.fst_vt['AeroDyn']['OLAF']['RegFunction'] = int_read(f.readline().split()[0]) + self.fst_vt['AeroDyn']['OLAF']['RegFunctionPart'] = int_read(f.readline().split()[0]) self.fst_vt['AeroDyn']['OLAF']['WakeRegMethod'] = int_read(f.readline().split()[0]) self.fst_vt['AeroDyn']['OLAF']['WakeRegFactor'] = float(f.readline().split()[0]) self.fst_vt['AeroDyn']['OLAF']['WingRegFactor'] = float(f.readline().split()[0]) diff --git a/openfast_io/openfast_io/FAST_writer.py b/openfast_io/openfast_io/FAST_writer.py index ec10ed7414..1285957548 100644 --- a/openfast_io/openfast_io/FAST_writer.py +++ b/openfast_io/openfast_io/FAST_writer.py @@ -1342,7 +1342,8 @@ def write_OLAF(self): f.write('------------------- WAKE REGULARIZATIONS AND DIFFUSION -----------------------------------------\n') f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['DiffusionMethod'], 'DiffusionMethod','- Diffusion method to account for viscous effects {0: None, 1: Core Spreading, "default": 0}\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['RegDeterMethod'], 'RegDeterMethod','- Method to determine the regularization parameters {0: Manual, 1: Optimized, 2: Chord, 3: Span, default: 0 }\n')) - f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['RegFunction'], 'RegFunction','- Viscous diffusion function {0: None, 1: Rankine, 2: LambOseen, 3: Vatistas, 4: Denominator, "default": 3} (switch)\n')) + f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['RegFunction'], 'RegFunction','- Segment regularization function {0: None, 1: Rankine, 2: LambOseen, 3: Vatistas, 4: Denominator, "default": 3} (switch)\n')) + f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['RegFunctionPart'], 'RegFunctionPart','- Particle regularization function {0: None, 1: Exponential, 2: Compact, "default": 1} [only if VelocityMethod=2,3] (switch)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['WakeRegMethod'], 'WakeRegMethod','- Wake regularization method {1: Constant, 2: Stretching, 3: Age, default: 3} (switch)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['WakeRegFactor'], 'WakeRegFactor','- Wake regularization factor (m)\n')) f.write('{:<22} {:<11} {:}'.format(self.fst_vt['AeroDyn']['OLAF']['WingRegFactor'], 'WingRegFactor','- Wing regularization factor (m)\n')) From 0bfa408344055b22b780219cb6f9f6dd15f83cbf Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Wed, 2 Sep 2026 16:43:33 -0600 Subject: [PATCH 04/11] Update OLAF regularization function input comments --- .../user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat | 4 ++-- reg_tests/r-test | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat b/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat index 08f340e2c8..2763378554 100644 --- a/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat +++ b/docs/source/user/aerodyn-olaf/ExampleFiles/ExampleFile--OLAF.dat @@ -22,8 +22,8 @@ default FWShedVorticity - Include shed vorticity in the far wake {default: fa ------------------- WAKE REGULARIZATIONS AND DIFFUSION ----------------------------------------- default DiffusionMethod - Diffusion method to account for viscous effects {0: None, 1: Core Spreading, "default": 0} 2 RegDeterMethod - Method to determine the regularization parameters {0: Constant, 1: Optimized, 2: Chord-scaled, 3: dr-scaled, default: 0 } -default RegFunction - Viscous diffusion function {0: None, 1: Rankine, 2: LambOseen, 3: Vatistas, 4: Denominator, "default": 3} (switch) -1 RegFunctionPart - Particle regularization function {0: None, 1: Exponential, 2: Compact, "default": 1} (switch) +default RegFunction - Segment regularization function {0: None, 1: Rankine, 2: LambOseen, 3: Vatistas, 4: Denominator, "default": 3} (switch) +default RegFunctionPart - Particle regularization function {0: None, 1: Exponential, 2: Compact, "default": 1} [only if VelocityMethod=2,3] (switch) default WakeRegMethod - Wake regularization method {1: Constant, 2: Stretching, 3: Age, default: 1} (switch) 0.25 WakeRegFactor - Wake regularization factor (m or -) 0.25 WingRegFactor - Wing regularization factor (m or -) diff --git a/reg_tests/r-test b/reg_tests/r-test index d9d4f687ac..4e4e95d225 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit d9d4f687acf4fe94ceae9ce709b7c6678bfc8b32 +Subproject commit 4e4e95d2257cc8d2c1ca003481f3b5dbb72144c6 From 312b54a64b08f087a291bb445998f18b790c5311 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Wed, 9 Sep 2026 18:24:44 -0600 Subject: [PATCH 05/11] docs(OLAF): document particle regularization functions Clarify the existing regularization functions apply to vortex segments, and add a Particle regularization functions section covering the segment-to-particle conversion, the regularized point-vortex kernel, and the exponential and compact-support functions (RegFunctionPart). Also document the RegFunctionPart input in the OLAF input file reference. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus --- docs/source/user/aerodyn-olaf/InputFiles.rst | 18 ++- docs/source/user/aerodyn-olaf/OLAFTheory.rst | 113 ++++++++++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/docs/source/user/aerodyn-olaf/InputFiles.rst b/docs/source/user/aerodyn-olaf/InputFiles.rst index afc81f7b85..19c97b188e 100644 --- a/docs/source/user/aerodyn-olaf/InputFiles.rst +++ b/docs/source/user/aerodyn-olaf/InputFiles.rst @@ -178,13 +178,29 @@ See :numref:`Guidelines-OLAF` for recommendations on setting up this parameter. **RegFunction** [switch] specifies the regularization function used to remove -the singularity of the vortex elements, as specified in +the singularity of the vortex elements/segments, as specified in :numref:`sec:vortconv`. There are five options: 1) no correction *[0]*, 2) the Rankine method *[1]*, 3) the Lamb-Oseen method *[2]*, 4) the Vatistas method *[3]*, and 5) the denominator offset method *[4]*. The functions are given in :numref:`sec:RegularizationFunction`. The default option is *[3]*. +**RegFunctionPart** [switch] specifies the regularization function used for the +vortex particles, which are used when a particle-based velocity method is +selected (*VelocityMethod* = *[2,3]*). There are three options: 1) no +correction *[0]*, 2) the exponential method *[1]*, and 3) the compact-support +method *[2]*. The functions are given in +:numref:`sec:RegularizationFunctionPart`. +The compact-support option *[2]* has a finite support radius beyond which the +kernel reverts to the exact singular kernel; with the tree-accelerated particle +method (*VelocityMethod* = *[2]*) this allows more particle interactions to be +handled by the far-field multipole approximation, so it can be significantly +faster than the exponential option depending on the case (wake size, particle +count, and regularization parameter). +The compact-support kernel is also purely polynomial and avoids evaluating a +transcendental (exponential) function, which can further reduce cost. +The default option is *[1]*. + **WakeRegMethod** [switch] specifies the method of determining viscous core radius (i.e., the regularization parameter). There are three options: 1) constant *[1]*, 2) stretching *[2]*, and 3) age *[3]*. The methods are diff --git a/docs/source/user/aerodyn-olaf/OLAFTheory.rst b/docs/source/user/aerodyn-olaf/OLAFTheory.rst index ac21622da4..e358136bb1 100644 --- a/docs/source/user/aerodyn-olaf/OLAFTheory.rst +++ b/docs/source/user/aerodyn-olaf/OLAFTheory.rst @@ -489,8 +489,16 @@ refinement of this option will be considered in the future. .. _sec:RegularizationFunction: -Implemented regularization functions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Segment regularization functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The regularization functions described in this section apply to the vortex +*segments* used to represent both the bound (blade) vorticity and the wake +vorticity, i.e., they regularize the segment Biot-Savart kernel of +Eq. :eq:`eq:BiotSavartSegment`. They are selected with the input +**RegFunction**. The corresponding regularization functions used by the +vortex-particle representation of the wake are described separately in +:numref:`sec:RegularizationFunctionPart`. Several regularization functions have been developed (:cite:`olaf-Rankine58_1,olaf-Scully75_1,olaf-Vatistas91_1`). At present, five @@ -562,7 +570,108 @@ Here, the singularity is removed by introducing an additive factor in the denominator of Eq. :eq:`eq:BiotSavartSegment`, proportional to the filament length :math:`r_0`. In this case, :math:`F_\nu=1`. This method is found in the work of van Garrel (:cite:`olaf-Garrel03_1`). +.. _sec:RegularizationFunctionPart: + +Particle regularization functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The regularization functions of :numref:`sec:RegularizationFunction` apply to +the vortex segments. When a particle-based velocity method is selected +(**VelocityMethod=[3]**, the direct vortex-particle method, or +**VelocityMethod=[2]**, the tree-accelerated particle method), the wake segments +are converted into vortex particles and a separate set of regularization +functions applies. The particle regularization function is selected with the +input **RegFunctionPart**. +Segment-to-particle conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Each wake segment of circulation :math:`\Gamma` and length vector +:math:`\vec{l}=\vec{x}_2-\vec{x}_1` is divided into :math:`n_p` +(**PartPerSegment**) equally spaced particles. The intensity of a particle +:math:`\vec{\alpha}` (the vorticity integrated over the volume represented by +the particle, :math:`\vec{\alpha}=\vec{\omega}\,dV`) is obtained from the parent +segment as + +.. math:: + \vec{\alpha} = \frac{\Gamma\,\vec{l}}{n_p} + :label: eq:SegToPart + +so that the :math:`n_p` particles of a segment sum to the total segment +vorticity :math:`\Gamma\,\vec{l}`. The particles are placed at the centers of +the :math:`n_p` sub-segments, and each particle inherits the regularization +parameter (core radius :math:`r_c`) of its parent segment. + +Particle velocity kernel +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The velocity induced at a point :math:`\vec{x}` by a vortex particle of +intensity :math:`\vec{\alpha}` located at :math:`\vec{x}_p` is the regularized +point-vortex kernel + +.. math:: + \vec{v}(\vec{x}) = \frac{1}{4\pi}\, g(\bar{r})\, + \frac{\vec{\alpha}\times\vec{r}}{r^3} + ,\qquad \vec{r}=\vec{x}-\vec{x}_p,\quad r=|\vec{r}|,\quad \bar{r}=\frac{r}{r_c} + :label: eq:BiotSavartParticle + +where :math:`g` is the particle regularization factor (the particle-method +analog of :math:`F_\nu`) and :math:`r_c` is the particle core radius. Away from +the particle center the induced velocity decays as :math:`1/r^2`. If no +correction is used (**RegFunctionPart=[0]**), :math:`g=1` and the singular +point-vortex kernel is recovered. + +Exponential +^^^^^^^^^^^ + +If the exponential method is used (**RegFunctionPart=[1]**, the default), the +regularization factor is + +.. math:: + g(\bar{r}) = 1 - \exp\!\left(-\bar{r}^{\,3}\right) + :label: eq:PartExp + +For :math:`\bar{r} > 2` (beyond two core radii) the exponential term is +negligible and :math:`g` is set to :math:`1`. + +Compact support +^^^^^^^^^^^^^^^^ + +If the compact-support method is used (**RegFunctionPart=[2]**), the kernel is +*exactly* singular (:math:`g=1`) beyond a finite support radius +:math:`r_c'=1.6\,r_c`, and inside the support it is given by a polynomial +mollifier + +.. math:: + g(\bar{r}') = \begin{cases} + \dfrac{35\,\bar{r}'^{3} - 42\,\bar{r}'^{5} + 15\,\bar{r}'^{7}}{8} + & 0 \le \bar{r}' < 1 \\[2mm] + 1 & \bar{r}' \ge 1 + \end{cases} + ,\qquad \bar{r}' = \frac{r}{r_c'} = \frac{r}{1.6\,r_c} + :label: eq:PartCompact + +The polynomial is smooth (:math:`C^\infty`) inside the support; the overall +regularization factor is globally :math:`C^2`, its regularity being limited by +the match to the singular kernel at the support boundary, where :math:`g=1`, +:math:`g'=0`, and :math:`g''=0` but :math:`g'''\neq 0` at :math:`\bar{r}'=1`. At +the center the mollifier vanishes as :math:`\bar{r}'^{3}` +(:math:`g=g'=g''=0`), so the induced velocity is regular there. The +support-radius factor :math:`1.6` makes the compact kernel roughly equivalent +to the exponential kernel with the same :math:`r_c`. It matches the peak +tangential (swirl) velocity *magnitude* of the two kernels, while being a +reasonable compromise across three possible equivalence criteria: + +.. math:: + \frac{r_c'}{r_c} \approx \begin{cases} + 1.54 & \text{radius of peak tangential velocity} \\ + 1.60 & \text{peak tangential velocity magnitude} \\ + 1.65 & \text{second moment of the vorticity distribution} + \end{cases} + +Because the kernel is exactly equal to the singular kernel beyond :math:`r_c'`, +the tree-accelerated particle method can apply an exact far-field cutoff at +that radius. .. _sec:corerad: Time Evolution of the Regularization Parameter–Core Spreading Method From 090073f93e39f5458ad97f34cdc6ffd63019069b Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Wed, 9 Sep 2026 18:55:18 -0600 Subject: [PATCH 06/11] fix(OLAF): skip core-based tree floor for unregularized particles PartRegFloorFactor now returns 0 for idRegNone since the singular point-vortex kernel has no core region; accuracy is governed solely by the Barnes-Hut opening criterion. Avoids inflating the direct-evaluation region unnecessarily. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus --- modules/aerodyn/src/FVW_BiotSavart.f90 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/aerodyn/src/FVW_BiotSavart.f90 b/modules/aerodyn/src/FVW_BiotSavart.f90 index 372a8fc279..bf93782203 100644 --- a/modules/aerodyn/src/FVW_BiotSavart.f90 +++ b/modules/aerodyn/src/FVW_BiotSavart.f90 @@ -38,9 +38,11 @@ pure function PartRegFloorFactor(RegFunction) result(f) integer(IntKi), intent(in) :: RegFunction real(ReKi) :: f select case (RegFunction) + case (idRegNone) ! Unregularized kernel: no extra core-based floor needed + f = 0.0_ReKi case (idRegCompact) ! Truly compact: exactly singular beyond rc = PART_REG_C2*RegParam f = PART_REG_C2 - case default ! Exponential (and none): conservative 2*rc floor + case default ! Exponential: conservative 2*rc floor f = PART_REG_NRAD end select end function PartRegFloorFactor From 08b7687eaef4fe676d48365bc60fc1c716ee3b26 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 25 Sep 2026 10:02:23 -0600 Subject: [PATCH 07/11] 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 4e4e95d225..5d9114ec75 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit 4e4e95d2257cc8d2c1ca003481f3b5dbb72144c6 +Subproject commit 5d9114ec7572d2391536ad2ed0e65d0582d4aeb4 From 841b6d2a79f58437d59e8fe04a2e23f5ee50b129 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 25 Sep 2026 10:51:53 -0600 Subject: [PATCH 08/11] Update api_change.rst with the new OLAF RegFunctionPart input --- docs/source/user/api_change.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/user/api_change.rst b/docs/source/user/api_change.rst index f877be8949..2171853a28 100644 --- a/docs/source/user/api_change.rst +++ b/docs/source/user/api_change.rst @@ -36,6 +36,7 @@ AeroDyn \* ==== AeroDyn \* NumGSMembers 0 NumGSMembers - Number of general support members (-) AeroDyn \* GSMemberID GSMJointID1 GSMJointID2 GSMDia1 GSMDia2 GSMCd1 GSMCd2 GSMTI1 GSMTI2 GSMDiv AeroDyn \* (-) (-) (-) (m) (m) (-) (-) (-) (-) (m) +OLAF 26 RegFunctionPart 2 RegFunctionPart - Particle regularization function {0: None, 1: Exponential, 2: Compact, "default": 1} [only if VelocityMethod=2,3] (switch) ============================================= ======== ==================== ========================================================================================================================================================================================================================================================================================================== OpenFAST v4.2.x to OpenFAST v5.0.0 From 9a1d0d22dd564617de89bfe3af6660c20fe7404d Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Fri, 25 Sep 2026 11:10:36 -0600 Subject: [PATCH 09/11] Register the new OLAF r-test with RegFunctionPart=2 and bump r-test pointer --- reg_tests/CTestList.cmake | 1 + reg_tests/r-test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/reg_tests/CTestList.cmake b/reg_tests/CTestList.cmake index 8bb6bbfef0..75ee500f81 100644 --- a/reg_tests/CTestList.cmake +++ b/reg_tests/CTestList.cmake @@ -460,6 +460,7 @@ ad_regression("ad_5MW_GSPotent" "aerodyn;bem;GS") ad_regression("ad_AWT_GSShadow" "aerodyn;bem;GS") ad_regression("ad_BAR_CombinedCases" "aerodyn;bem") # NOTE: doing BAR at the end to avoid copy errors ad_regression("ad_BAR_OLAF" "aerodyn;bem") +ad_regression("ad_BAR_OLAF_RegFunctionPart2" "aerodyn;bem") ad_regression("ad_BAR_SineMotion" "aerodyn;bem") ad_regression("ad_BAR_SineMotion_UA4_DBEMT3" "aerodyn;bem") ad_regression("ad_BAR_RNAMotion" "aerodyn;bem") diff --git a/reg_tests/r-test b/reg_tests/r-test index 5d9114ec75..e773db8bbf 160000 --- a/reg_tests/r-test +++ b/reg_tests/r-test @@ -1 +1 @@ -Subproject commit 5d9114ec7572d2391536ad2ed0e65d0582d4aeb4 +Subproject commit e773db8bbf04208641791de8171a4607d1997a2a From 6ef1b51fb18d992d2eda539307d46a573398ab10 Mon Sep 17 00:00:00 2001 From: andrew-platt Date: Fri, 25 Sep 2026 13:40:44 -0600 Subject: [PATCH 10/11] docs(OLAF): add missing blank lines before RST section targets The `sec:RegularizationFunctionPart` and `sec:corerad` hyperlink targets each abutted the preceding paragraph, so docutils absorbed them into that paragraph instead of registering them. This silently broke six :numref: references: two to the new particle-regularization section (InputFiles.rst:193, OLAFTheory.rst:501) and four to the pre-existing sec:corerad label (InputFiles.rst:207, InputFiles.rst:224, OLAFTheory.rst:528, OLAFTheory.rst:777). Verified by parsing the file before and after: both labels are absent from the document name index before the change and registered as targets after it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/source/user/aerodyn-olaf/OLAFTheory.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/user/aerodyn-olaf/OLAFTheory.rst b/docs/source/user/aerodyn-olaf/OLAFTheory.rst index e358136bb1..1379f4e626 100644 --- a/docs/source/user/aerodyn-olaf/OLAFTheory.rst +++ b/docs/source/user/aerodyn-olaf/OLAFTheory.rst @@ -570,6 +570,7 @@ Here, the singularity is removed by introducing an additive factor in the denominator of Eq. :eq:`eq:BiotSavartSegment`, proportional to the filament length :math:`r_0`. In this case, :math:`F_\nu=1`. This method is found in the work of van Garrel (:cite:`olaf-Garrel03_1`). + .. _sec:RegularizationFunctionPart: Particle regularization functions @@ -672,6 +673,7 @@ reasonable compromise across three possible equivalence criteria: Because the kernel is exactly equal to the singular kernel beyond :math:`r_c'`, the tree-accelerated particle method can apply an exact far-field cutoff at that radius. + .. _sec:corerad: Time Evolution of the Regularization Parameter–Core Spreading Method From f591771b7013dbe5b0ffeb8a661bdcbc9b4b0de1 Mon Sep 17 00:00:00 2001 From: andrew-platt Date: Fri, 25 Sep 2026 13:46:00 -0600 Subject: [PATCH 11/11] docs(OLAF): document the RegFunctionPart particle-kernel behavior change The api_change table already lists the new RegFunctionPart input, but not the behavior change that comes with decoupling the particle kernel from RegFunction. The default RegFunctionPart=1 reproduces the previous exponential particle kernel for any RegFunction greater than 0, so those decks are unaffected. Decks with RegFunction=0 previously got an unregularized particle kernel and now get the exponential one unless RegFunctionPart=0 is also set; and with RegFunctionPart=0 the particle-tree far-field cutoff is no longer padded by the particle core radius, so VelocityMethod=2 results still shift. Co-Authored-By: Claude Opus 5 (1M context) --- docs/source/user/api_change.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/user/api_change.rst b/docs/source/user/api_change.rst index 2171853a28..97040f95bd 100644 --- a/docs/source/user/api_change.rst +++ b/docs/source/user/api_change.rst @@ -16,6 +16,8 @@ Under-relaxation is introduced for the tight-coupling iterative solver to improv The generalized support-structure (GS) influence model was added to AeroDyn. This introduces three new switches (``GSPotent``, ``GSShadow``, ``GSAero``) after ``TwrAero`` in the AeroDyn primary input file, and two new sections (``General support structure joints`` and ``General support structure members``) after the ``Tower Influence and Aerodynamics`` section. The two new sections are required even when the GS model is disabled (set ``NumGSJoints`` and ``NumGSMembers`` to 0, keeping the table header lines). +OLAF now selects the regularization function for the vortex particles separately from the vortex segments. The new ``RegFunctionPart`` input on line 26 of the OLAF input file controls the particle kernel, and ``RegFunction`` now applies only to the segments. Previously the particle kernel was inferred from ``RegFunction``: any regularized value gave an exponential particle kernel, while ``RegFunction=0`` gave an unregularized one. The default ``RegFunctionPart=1`` reproduces the exponential kernel, so results are unchanged for decks with ``RegFunction`` greater than 0. Decks with ``RegFunction=0`` will change: the particles are now regularized with the exponential kernel unless ``RegFunctionPart=0`` is also set. Setting ``RegFunctionPart=0`` restores an unregularized particle kernel, but with ``VelocityMethod=2`` the results will still differ from previous versions because the particle-tree far-field cutoff is no longer padded by the particle core radius when the particle kernel is unregularized. + ============================================= ======== ==================== ========================================================================================================================================================================================================================================================================================================== Added in OpenFAST `5.1.0` --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------