Fix InflowWind Grid3D cubic velocity interpolation NaN bug - #3428
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes InflowWind Grid3D cubic-in-time velocity interpolation behavior so callers that request velocity-only (no acceleration output) no longer receive NaN/Inf wind velocities, and corrects the Grid3D acceleration precompute guard to use the number of time steps rather than the tower-grid count.
Changes:
- Ensure
Grid3DField_GetCellpopulatesAccCellwheneverVelInterpCubicis enabled, independent of whetherAccelUVWis allocated by the caller. - Fix
IfW_Grid3DField_CalcAccelearly-return condition to checkG3D%NSteps < 3(time steps) instead ofG3D%NTGrids < 3(tower grid points). - Add and register new InflowWind unit-test coverage for the cubic-velocity-only bug path.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| unit_tests/CMakeLists.txt | Adds the new Grid3D unit test source to the InflowWind unit test target. |
| modules/inflowwind/tests/test_grid3d_field.F90 | Introduces a new unit test module exercising the cubic velocity interpolation path. |
| modules/inflowwind/tests/inflowwind_utest.F90 | Registers the new Grid3D test suite in the InflowWind unit test runner. |
| modules/inflowwind/src/IfW_FlowField.f90 | Fixes the Grid3D cubic interpolation path selection and corrects the CalcAccel guard condition. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
IfW_FlowField_GetVelAcc only populated the local AccCell array when the caller requested acceleration output (OutputAccel). But the cubic Hermite velocity formula in Grid3DField_GetVelAccCubic depends on AccCell for its tangent terms regardless of whether acceleration is separately requested. Callers that use VelInterpCubic=True but only want velocity (e.g. AWAE's ambient-wind sampling) therefore computed velocity from uninitialized memory, corrupting every returned value. Also fixes IfW_Grid3DField_CalcAccel, which checked G3D%NTGrids (tower grid point count) instead of G3D%NSteps (time step count) to decide whether to compute real cubic-spline time derivatives, forcing G3D%Acc to zero for every case without a tower file regardless of how many time steps were available. Adds unit tests (test_grid3d_field.F90) that reproduce both defects in isolation and verify the fix. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
f6aaeb6 to
2942295
Compare
|
| Channel | Max |diff| | Mean |diff| | Old max magnitude | New max magnitude |
|---|---|---|---|---|
T, X, Y, Z, U, V, W |
0.0 | 0.0 | — | — |
UA |
10.16 | 1.30 | 5.37 | 13.57 |
VA |
11.97 | 1.39 | 5.57 | 15.50 |
WA |
9.26 | 1.28 | 7.97 | 12.63 |
The new accelerations are ~2-3x larger in typical magnitude, consistent with the old code systematically biasing derivatives toward zero rather than a small numeric drift.
…3d-cubic-interp-nan
50613f4 to
cada939
Compare
IfW_FlowField_GetVelAcc only populated the local AccCell array when the caller requested acceleration output (OutputAccel). But the cubic Hermite velocity formula in Grid3DField_GetVelAccCubic depends on AccCell for its tangent terms regardless of whether acceleration is separately requested. Callers that use VelInterpCubic=True but only want velocity (e.g. AWAE's ambient-wind sampling) therefore computed velocity from uninitialized memory, corrupting every returned value. Also fixes IfW_Grid3DField_CalcAccel, which checked G3D%NTGrids (tower grid point count) instead of G3D%NSteps (time step count) to decide whether to compute real cubic-spline time derivatives, forcing G3D%Acc to zero for every case without a tower file regardless of how many time steps were available. Adds unit tests (test_grid3d_field.F90) that reproduce both defects in isolation and verify the fix. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…an' into bugfix/ifw-grid3d-cubic-interp-nan
…NSteps) IfW_Grid3DField_CalcAccel's tower-grid branch checked G3D%NTGrids < 3 to decide whether to zero out tower acceleration, but the actual time-derivative calculation needs G3D%NSteps >= 3 (already guaranteed by the earlier NSteps<3 early return). This mirrors the NTGrids/NSteps mix-up fixed elsewhere in this PR, and incorrectly zeroed valid tower acceleration whenever a wind file had only 1 or 2 tower grid points. Removed the erroneous guard; the tower acceleration loop needs no minimum tower height count. Added test_grid3d_calcaccel_few_tower_points to cover this case. Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
modules/inflowwind/src/IfW_FlowField.f90:85
- The AccFieldValid guard now also triggers when FF%VelInterpCubic is enabled even if AccelUVW is not allocated, but the fatal message still says "Accel output requested". This is misleading for the cubic-velocity/no-accel caller case (the one this PR fixes). Update the message to mention cubic velocity interpolation (or more generally that an acceleration field is required).
! Cubic velocity interpolation also requires a valid acceleration field, since its
! formula uses the derivative data even when acceleration output is not requested.
if ((OutputAccel .or. FF%VelInterpCubic) .and. .not. FF%AccFieldValid) then
call SetErrStat(ErrID_Fatal, "Accel output requested, but accel field is not valid", &
ErrStat, ErrMsg, RoutineName)
…calculated before
Ready to merge
Feature or improvement description
This is a bug fix targeting
rc-5.0.1, not a feature. It fixes long-standing defects in InflowWind'sGrid3Dflow field (TurbSim/Bladed/HAWC full-field wind files,WindType=3and similar) that produce NaN wind velocities wheneverVelInterpCubic = Trueis set in the InflowWind input file and the calling code queries velocity without also requesting acceleration output. FAST.Farm's AWAE module is exactly such a caller: it samples ambient wind velocity for its high-resolution grid without asking for acceleration, so any FAST.Farm case withVelInterpCubic = Truereturns a 100%-NaN ambient wind field and fails immediately at t=0 with:Root cause
IfW_FlowField_GetVelAcc(inmodules/inflowwind/src/IfW_FlowField.f90) determines whether the caller wants acceleration output from whether it allocated the optionalAccelUVWoutput array:OutputAccel = allocated(AccelUVW)For the
Grid3D_FieldTypecase, this flag (combined withVelInterpCubic) selects one of four interpolation code paths. WhenVelInterpCubic = TrueandOutputAccel = False("cubic velocity, no acceleration"),Grid3DField_GetCellis called withCalcAccel = OutputAccel = .false., which causes it to skip populating the localAccCell(8,3)array entirely — it is only filledif (CalcAccel) then ....The velocity is then computed by
Grid3DField_GetVelAccCubic, which implements a cubic Hermite spline in time. Critically, the Hermite value formula (not just the optional acceleration output) depends onAccCell, since a proper cubic-in-time interpolant needs derivative (tangent) information at both time endpoints to compute the interpolated value itself:where
PPis derived fromAccCell. SinceAccCellis a local, stack-allocated array that is never initialized in this code path,PPis computed from garbage memory, corruptingVelocityfor every single query — confirmed via debugger to containInfin every element for this test case, while the companionVelCell(actual wind data) was fully valid. This explains why the reported failure showed a 100%-NaN velocity array with a 100%-valid position array: the defect is purely a function of which interpolation-flag combination is selected, not of any particular spatial location.Two further, related defects were found in the same area, both stemming from
IfW_Grid3DField_CalcAccel(which precomputes the time-derivative arraysG3D%Acc/G3D%AccTowerneeded for cubic interpolation) confusing a spatial grid-point count with the temporal step count needed by the cubic-spline-in-time derivative calculation:G3D%NTGrids < 3(NTGrids= number of tower grid points) instead ofG3D%NSteps < 3(number of time steps):NTGrids = 0), this unconditionally forcedG3D%Accto zero and skipped the real cubic-spline derivative calculation — regardless of how many time steps were actually available (4004 in the reported case, 300 in theifw_BoxExceedreg test). This doesn't itself produce NaN (zero is well-defined), but it silently defeats cubic-in-time interpolation whenever no tower file is present, which is likely most cases that useVelInterpCubic = True.G3D%NTGrids < 3to decide whether to computeG3D%AccTower, even though each tower height's time-derivative is computed independently and has no minimum spatial-count requirement (the temporalNSteps < 3case is already handled by the earlier guard). This incorrectly zeroed valid tower acceleration whenever a wind file had only 1 or 2 tower grid points.Both original defects are pre-existing in
rc-5.0.1(and earlier) — reproduced and confirmed on an unmodifiedrc-5.0.1checkout with identical input files, so this is not a regression from any other branch. It is essentially untested in the existing test suite because every other reg-test input file in the repository setsVelInterpCubic = False; the reported case is the only one usingTrue.Fix
IfW_FlowField_GetVelAcc: passOutputAccel .or. FF%VelInterpCubicas theCalcAccelargument toGrid3DField_GetCell, soAccCellis always populated whenever cubic-in-time interpolation is active, independent of whether the caller also wants acceleration returned. Also widened the existingAccFieldValidguard to fire wheneverOutputAccel .or. FF%VelInterpCubicis true but the acceleration field hasn't actually been computed, so a misconfigured caller gets a clear fatal error instead of dereferencing an unallocated array.IfW_Grid3DField_CalcAccel: changed the main-grid early-return guard fromG3D%NTGrids < 3toG3D%NSteps < 3, so real time derivatives are computed whenever enough time samples exist, regardless of whether a tower file is present. This early-return path now also allocates and zeroesG3D%AccTower(when a tower grid is present) so downstream tower interpolation always has a valid array to reference.IfW_Grid3DField_CalcAccel: removed the erroneousG3D%NTGrids < 3guard in the tower-grid branch entirely —G3D%NSteps >= 3is already guaranteed by fix DWM Driver #2's early return, and there's no minimum tower-height-count requirement for the per-height time derivative.Related issue, if one exists
None filed yet; found during investigation of a reported FAST.Farm
FARM_InitialCOfatal error ("rotor plane has left the low-resolution domain") on a 2-turbine wind-tunnel test case.Impacted areas of the software
modules/inflowwind/src/IfW_FlowField.f90(IfW_FlowField_GetVelAcc,IfW_Grid3DField_CalcAccel)Grid3Dflow field type (TurbSim, Bladed, HAWC full-field wind,WindType = 3, 4, 5, 7) withVelInterpCubic = True: FAST.Farm (AWAE ambient-wind sampling and per-turbine InflowWind instances), OpenFAST, InflowWind driver, AeroDyn driver.VelInterpCubic = Falselinear-interpolation path.Additional supporting information
Diagnosed via GDB by tracing a NaN observed in AeroDyn's
RotInflow%Blade%InflowVelbackwards throughFASTWrapper, AWAE's ambient-wind fill, and into InflowWind'sGrid3Dcubic interpolation, whereAccCellwas directly observed to containInfin all elements at the point of use. Confirmed the same failure reproduces on an unmodifiedrc-5.0.1build with AMReX disabled, ruling out any connection to unrelated work-in-progress changes on another branch (including a separately-identified, unrelatedWakeDynamicsarray-bounds issue, which is not part of this PR).Generative AI usage
Root-cause investigation (GDB tracing, source review), the code fixes, and the new unit tests were developed with substantial assistance from AI coding agents (GitHub Copilot chat agent, running Claude Sonnet 5), including responding to GitHub Copilot's automated PR review comments across two review rounds.
Co-authored-by: GitHub Copilot 175728472+Copilot@users.noreply.github.com
Co-authored-by: Claude Sonnet 5 noreply@anthropic.com
Reviewed by: Google Gemini gemini@google.com
Testing
modules/inflowwind/tests/test_grid3d_field.F90with three new unit tests, registered ininflowwind_utest:test_grid3d_cubic_vel_only— builds a minimal syntheticGrid3Dfield with a spatially-uniform, linear-in-time velocity ramp andVelInterpCubic = True; queries velocity both with and without requesting acceleration output. Fails today with "not finite (NaN/Inf)" without fix AeroDyn14 Driver #1; passes with it.test_grid3d_calcaccel_no_tower— callsIfW_Grid3DField_CalcAcceldirectly on a field with no tower grid and checks that the computed derivative recovers the known true value. Fails today (derivative forced to zero) without fix DWM Driver #2; passes with it.test_grid3d_calcaccel_few_tower_points— callsIfW_Grid3DField_CalcAccelon a field with only 2 tower grid points and checks that the tower acceleration is actually computed rather than zeroed. Fails today without fix DLLEXPORT directives in FAST_Library #3; passes with it.inflowwind_utestsuite passes with no regressions in any pre-existing test.FAST.Farmwith the fix and reran the original, unmodified reported test case (VelInterpCubic = True): the instant t=0FARM_InitialCOfatal error no longer occurs; the simulation progresses normally through simulated time.ifw_BoxExceedregression baseline updated to reflect corrected (previously erroneously-zeroed) acceleration values; other InflowWind regression tests unaffected.ad_MHK_RM1_Fixed(RtFldMyg),MHK_RM1_Floating(B2FldMx,RtFldFzg,RtFldMxh), andMHK_RM1_Floating_MR(R2HbMbx,R2HbMby). All three setVelInterpCubic = Falsebut are MHK cases, whereOutputAccelis always forcedTrue— soIfW_Grid3DField_CalcAccelhas always run for them, independent of fix AeroDyn14 Driver #1. All three also use a TurbSim wind file with no tower extension (WrADTWR = False, i.e.NTGrids = 0), which is exactly the condition fix DWM Driver #2 addresses: previouslyG3D%Accwas forced to zero for the whole run regardless of the ~700 available time steps, silently feeding zero fluid acceleration into AeroDyn's MHK added-mass/inertial force terms (InflowAcc→aFBTemp/aFTTemp). No other regression cases are affected.ifw_BoxExceedbaseline)