New Feature: Adding input file verification to OpenFAST, FAST.Farm and drivers.#3379
Draft
mayankchetan wants to merge 30 commits into
Draft
New Feature: Adding input file verification to OpenFAST, FAST.Farm and drivers.#3379mayankchetan wants to merge 30 commits into
mayankchetan wants to merge 30 commits into
Conversation
…put mode Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…ror handling Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…failure paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…lable sticky Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
… report Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…ulation) Adds reg_tests/executeCheckInputTest.py (stdlib-only assertion runner) and of_checkinput registrations in CTestList.cmake: 3 positive cases (AOC_WSt, 5MW_Land_BD_Init, AWT_YFix_WSt) plus one negative case that seeds an ElastoDyn blade-file error and an InflowWind WindType error into a copy of AOC_WSt and asserts both are reported in one run. Fixtures are generated at test time by copying pristine r-test cases (and corrupting the copy for the negative case) -- nothing is written back into the r-test submodule. The runner additionally scans copied input files for "../<name>/" sibling-directory references (e.g. AOC_WSt -> ../AOC, 5MW_Land_BD_Init -> ../5MW_Baseline) and copies those siblings alongside the case directory, since r-test cases are not self-contained. The two 5MW DLL-controller cases from the original plan were substituted with controller-free equivalents (5MW_Land_BD_Init, AWT_YFix_WSt) because the reference DISCON controllers are built by a separate CMake target not compiled in this tree; see CTestList.cmake comments for details. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
… structure Replace simple whitespace-stripping approach with structure-preserving comparison that joins hard-wrapped console lines per summary entry before matching, preventing false positives from messages straddling unrelated lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…th exit code - SED's case-default block in FAST_InitializeAll now mirrors the ElastoDyn failure-path guard: on a failed SED_Init, stub HubPtMotion/NacelleMotion/ PlatformPtMesh (committed, neutral) and mark the enabled downstream consumers (InflowWind/AeroDyn/AeroDisk/ServoDyn) 'unavailable', matching what ED already did. Downstream reads of SED%y%BladeRootMotion(k) (AeroDyn, ServoDyn) and SrvD's SED BlPitchInit copy are additionally guarded against unallocated/uncommitted/undersized data, closing the segfault gap where a corrupted SED deck could crash -CheckInput instead of being reported. - NWTC_CheckInput's CkIn_CloseReport now derives overall_status from CkIn_ExitCode instead of NumErrors, so a fatal collected with an empty message (which does not increment NumErrors but does mark its component CkIn_St_Failed) can no longer make the report disagree with the process exit code. Covered by new unit test test_empty_message_fatal. - Adds CTest checkinput_SED_error: corrupts SED's NumBl to 0 so SED_Init fails before committing its output meshes, and asserts SED is attributed, the process exits 1 with status failed, and it does not crash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
- FAST_CheckInput_T: correct the comment describing what reaches the outer ErrStat>=AbortErrLev check (module Init failures take the collect path, not FAST_Init's); drop the unused FIAStat, discarding CkIn_ComponentStatus's return value inline instead. - Add one-line invariant comments at the unconditional ED-mesh guard sites (IfW, AeroDyn, SubDyn), and note where the else-branch is now unreachable in practice because the corresponding case-default block stubs the mesh -- kept as defense-in-depth for other uncommitted-mesh causes. - Set CurrentComponent = 'ExternalInflow' at the top of the ExtInfw init block so a failure there is attributed to it instead of whatever module's label was last set. - CkIn_OpenReport resets collector%UnYaml to -1 when OpenFOutFile fails, so a failed report doesn't leave a stale unit number behind. - of_checkinput now applies the same TO_NATIVE_PATH/backslash-escaping path hygiene the regression() function already uses for its path arguments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the P1-P4 driver pattern into TurbSim.f90: -CheckInput now dispatches into a real read -> validate -> grid -> profiles -> summary check instead of exiting via the blanket NormStop before reading anything. Failures are intercepted at the existing CheckError chokepoint and attributed to one of five stages (Input, Validation, Grid, Profiles, Summary); a passing run reports all five and exits 0 with <Root>.verify.yaml written. TS_ValidateInput's non-fatal warnings are captured into the Validation component instead of being printed and discarded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Farm_Initialize gains an optional CheckInputCollectorType collector (its presence is the mode, mirroring FAST_Subs's -CheckInput wiring); the patched Failed() collects-and-continues per component instead of aborting. Since FAST.Farm's components are strongly sequential (AWAE feeds WD, WD feeds turbines) and there's no stub infrastructure for farm-level data, a failed component marks all downstream components 'skipped' rather than attempting them against garbage state -- a deliberate deviation from the attempt- everything default used elsewhere. Farm_InitFAST's existing per-turbine loop already runs every wrapped turbine before returning, so a single turbine's init failure still surfaces every other turbine's status, with 'T<n>:'- prefixed messages identifying which turbine failed. New Farm_CheckInput driver dispatches from FAST_Farm.f90's -CheckInput flag, runs Farm_Initialize with the collector, tears down via FARM_End (verified safe on partially-initialized state across several seeded failure modes), and exits with a report-derived code via CkIn_DriverFinish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…itialized on init success Farm_CheckInput previously called FARM_End (teardown) before finalizing the -CheckInput report, so a crash in FARM_End on partially-initialized state would lose an otherwise-completed check's report (yaml stuck on the crashed placeholder, no console summary). Reordered to mirror FAST_CheckInput_T (FAST_Subs.f90): write the summary, close the report, capture the exit code, THEN tear down, THEN exit. Also audited every IsInitialized = .true. assignment in Farm_Initialize and its init helpers. Under -CheckInput's collect-and-continue Failed(), if(Failed()) return no longer returns on failure, so farm%AWAE%IsInitialized was being set even when AWAE_Init failed -- gated it on success captured before Failed() can mask it. Also found (and fixed) a related pre-existing bug in the WakeDynamics and FAST-wrapper per-turbine init loops, where IsInitialized was set unconditionally before checking that turbine's own init status; harmless previously since a failure aborted the process before FARM_End ran, but newly reachable now that Farm_CheckInput always calls FARM_End regardless of outcome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the shared P1-P4 driver pattern into HydroDyn_DriverCode.f90 and SeaState_DriverCode.f90, replacing the blanket NormStop() false-pass with a real read -> init -> validate sequence attributed to Driver/SeaState/ HydroDyn stages and a <Root>.driver.verify.yaml report. HydroDyn: two interval-mismatch checks called HD_DvrEnd() directly, bypassing the CheckError() chokepoint the brief's recon described -- routed both through CheckError() instead (behaviorally identical for the normal path) so every fatal gets the CkIn_DriverFail interception. Guarded the InitOutputFile call (which unconditionally opens <OutRootName>.out) on CheckInputMode so a passing check run leaves no compute artifact behind. SeaState: no shared CheckError -- interception added at the top of SeaSt_DvrCleanup, gated on ErrStat >= AbortErrLev since that routine is also the unconditional success-teardown path. Guarded WaveElevGrid_Output (a genuine compute artifact) on CheckInputMode as well. Verified via 4 smokes (HD/SeaState x positive/seeded-negative) against reg_tests/r-test cases, and confirmed the normal (non-check) path is bit-identical to pre-patch output via a stash-and-rebuild diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the shared P1-P4 driver pattern into AeroDyn_Driver.f90/ AeroDyn_Driver_Subs.f90 and AeroAcoustics_Driver.f90/ AeroAcoustics_Driver_Subs.f90. AeroDyn: Dvr_SimData/AllData are Registry-generated, so rather than regenerating the registry, the -CheckInput flag (detected inside Dvr_Init, where CheckArgs already lives) is threaded to the main program via a new optional out-argument on Dvr_Init, into a program-level CheckInputMode in AeroDyn_Driver.f90. Dvr_InitCase gets the same optional argument (in) to gate its own output-file/VTK-reference side effects. Under -CheckInput the main program still runs Dvr_Init + Dvr_InitCase for every case in a combined-case deck (validating all of them) but never Dvr_TimeStep, attributing each case to 'Case1'..'CaseN' (or 'Case' for a single-case deck) and reporting all of them once the case loop completes without a fatal. AeroAcoustics: straight P1-P4 across Driver/AirfoilInfo/AeroAcoustics stages; Init_AAmodule's internal output-file open is gated the same way as AeroDyn's. Verified via 4 AeroDyn smokes (single-case and combined-case x positive/seeded-negative) against reg_tests/r-test, plus a stash/rebuild bit-identical diff confirming the normal (non-check) path is unaffected. No AeroAcoustics r-test case exists; covered instead by a missing-file run and a hand-built minimal deck seeding a missing airfoil path, both exercising the full chokepoint/report pipeline -- no positive all-components-pass smoke was built (see task report for the gap). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the shared P1-P4 driver pattern into MoorDyn_Driver.f90, attributing checks to four stages: Driver (ReadDriverInputFile), SeaState (optional SeaSt_Init, gated not_used when no SeaStateInputFile), MoorDyn (MD_Init + pre-loop compute setup), and Motions (the prescribed-motion input-file processing block, gated not_used when InputsMod != 1). Handles the re-fetch quirk: the raw get_command_argument(1, ...) re-fetch bypasses CheckArgs' parse, so under CheckInputMode it is overridden with the CheckArgs-parsed MD_InitInp%FileName (one gated assignment) so both "<file> -checkinput" and "-checkinput <file>" resolve the driver file correctly; the non-check path is untouched. AbortIfFailed() already folds ErrStat2/ErrMsg2 into ErrStat/ErrMsg (taking whichever is more severe) before aborting, so the CkIn_DriverFail interception is placed right after that merge and reports ErrStat/ErrMsg directly. Verified via 3 smokes (SeaState-enabled positive, Motions-enabled positive, seeded-negative with a garbage line-type name) against reg_tests/r-test cases, both argument orders under check mode, and confirmed the normal (non-check) path is bit-identical to pre-patch output via a stash-and-rebuild diff (only the human-readable timestamp line differs). Note: MD_Init unconditionally opens <RootName>.MD.out inside MoorDyn.f90 (not the driver) and writes one t=0 row before the driver reaches P4 -- left ungated since fixing it is out of this task's driver-only scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the shared P1-P4 driver pattern into the five custom-parser (non-CheckArgs) drivers: InflowWind, AeroDisk, Simplified-ElastoDyn (SED), SoilDyn, and OrcaFlexInterface -- each with its own ParseArg CHECKINPUT branch, a CheckInput logical on the driver Flags type, a default, and a help-text line. InflowWind: IfWDriver_Flags is Registry-generated, so the field was added to InflowWind_Driver_Registry.txt and InflowWind_Driver_Types.f90 was regenerated manually via the openfast_registry binary (git diff: 4 lines, all CheckInput-related). P4 fires before the WindGrid/Points/FFT compute-setup block; the HAWC/Bladed/VTK/Uniform format-conversion writes are also gated under CheckInputMode since they are file-writing side effects independent of validation. AeroDisk and SED: the direct -adsk/-sed file mode never populates CaseTime/CaseData (only the driver-input-file parse path does), so the time-step-default logic crashes on an unallocated array in that mode -- a pre-existing bug in both, out of scope to fix. Under -CheckInput this is intercepted before it's reached, failing the 'Driver' component with a clear message instead. SoilDyn: RootName is normally derived only post-init; derived it early (lexically, from the already-final SlDIptFileName) purely to name the -CheckInput report. The SlD_Init call site is an inline duplicate of CheckErr (doesn't call it) -- gave it its own interception, since the REDWIN DLL loads inside SlD_Init and a missing/broken DLL must surface as a failed component rather than crash. OrcaFlexInterface: no shared CheckErr/CheckCallErr chokepoint -- gated each pre-P4 inline abort site individually, with the critical one at Orca_Init (where the OrcaFlex DLL dlopens). Verified via 2 smokes per driver (positive + negative) against reg_tests/r-test cases where they exist (InflowWind, AeroDisk, SED); SoilDyn and OrcaFlex have no r-test coverage, so minimal decks were hand-built from each module's file-parser source. OrcaFlex has no DLL on this machine, so only the negative (DLL-missing) path is exercisable; documented rather than faked. Re-verified normal (non-check) runs are unaffected for all four drivers with real decks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
CkIn_CloseReport previously emitted a severe "Report file is not open" for any collector whose report was never opened, including driver failures that happen before RootName is known (e.g. AeroDisk/SED direct-file mode under -CheckInput). That left exit 1 with no verify.yaml at all -- the machine-readable contract broke exactly when input was most broken. Now, when the report was never opened, CkIn_CloseReport opens a last-resort checkinput.verify.yaml in the CWD, backfills every already-collected component, and falls through to the normal trailer. The already-opened-then- closed case still gets the severe (that is a caller bug). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Wires the shared P1-P4 driver pattern into Driver_Beam.f90 and UnsteadyAero_Driver.f90 -- the last two "bare-args" drivers with no prior flag dispatch at all (raw GET_COMMAND_ARGUMENT / a bare argument-count check), so both needed a small hand-rolled command-line scan rather than reusing CheckArgs. BeamDyn: attributes checks to 'Driver' (BD_ReadDvrFile) and 'BeamDyn' (BD_Init + mesh setup, including quasi-static init, which is part of init per the plan's explicit decision). P4 cuts before the t=0 BD_CalcOutput -- that's compute, not part of the check, unlike MoorDyn where a t=0 write slipped in. Gates the output-file open (Dvr_InitializeOutputFile) and both VTK-reference write blocks under CheckInputMode. UnsteadyAero: attributes checks to 'Driver' (ReadDriverInputFile + Dvr_SetParameters), 'LinDyn' (LD_Init, SimMod 3 only; not_used otherwise), and 'UnsteadyAero' (UA_Init including airfoil polars via driverInputsToUAInitData). Replaces the "count>1 -> help+exit" dispatch, which today makes "file -checkinput" a false pass (miscounted as too many args, prints help, exits 0) -- closes that gap while preserving the same help+exit contract for any other unrecognized flag or a genuine second non-flag argument. Dvr_InitializeOutputs (the P4 anchor) was checked and does no file I/O, so nothing needed gating there. Verified via smokes against reg_tests/r-test cases: BeamDyn positive (both arg orders) + seeded negative (missing blade file); UnsteadyAero positive for both SimMod=1 and SimMod=3 (both arg orders) + seeded negative (missing airfoil file), plus confirmed the old "file -checkinput" false pass is gone while unrelated unknown-flag/too-many-arg behavior is unchanged. Both drivers' normal (non-check) runs are bit-identical to pre-patch output via stash-and-rebuild comparison (BeamDyn .out diff, UnsteadyAero .outb MD5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…odule drivers Generalizes executeCheckInputTest.py (--input to bypass the *.fst glob for driver decks named .dvr/.inp/.fstf; --report-glob for <root>.driver.verify.yaml; --exe-args pass-through) and adds a driver_checkinput() CMake function alongside of_checkinput() for cases living outside reg_tests/r-test/glue-codes/openfast. Also fixes a latent bug in the console/YAML parity check surfaced by BeamDyn's negative case: a message containing a quoted path was unescaped only within its truncated [:60] slice, which can leave a lone backslash with no matching quote when the slice boundary falls mid-escape. Now unescapes the full message before truncating. New registrations (12, alongside Plan 1's original 5): - checkinput_turbsim / checkinput_turbsim_bad - checkinput_fastfarm / checkinput_fastfarm_turbine_error (guarded by BUILD_FASTFARM, matching the existing ff_regression() registrations) - checkinput_addriver / checkinput_addriver_bad - checkinput_moordyn / checkinput_moordyn_bad - checkinput_beamdyn_driver / checkinput_beamdyn_driver_bad - checkinput_inflowwind / checkinput_inflowwind_bad Each corruption regex was verified by running executeCheckInputTest.py by hand against the built executable before being wired into CTest. Not covered by CTest here (smoke-tested only, in their own Plan-2 task reports): aeroacoustics_driver, seastate_driver, hydrodyn_driver, aerodisk_driver, sed_driver, soildyn_driver, orca_driver, unsteadyaero_driver. No r-test case exists for aeroacoustics_driver; the others were left for a future pass to keep this task's diff bounded to the brief's explicit minimum set -- logged here rather than silently capped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Document the availability of -CheckInput flag across all user-facing executables (openfast, FAST.Farm, turbsim, and 11 single-module drivers), report naming conventions, excluded executables, and DLL-loading caveats. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…oorDyn check before t0 compute Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
…us sweep A 173-case corpus sweep of `-CheckInput` against reg_tests/r-test/glue-codes/openfast found two reproducible SIGSEGVs in FAST_InitializeAll's attempt-everything collect-and-continue path: 1. 5MW_Land_AeroMap: ServoDyn's input file is genuinely absent from this deck dir, so SrvD_Init fails and Failed() collects+continues instead of returning. ~80 lines later, the "Initialize external inputs for first step" block unconditionally reads SrvD%Input(INPUT_CURR,1)%ExternalBlPitchCom -- an allocatable array SrvD_Init never reaches allocating -- and segfaults on SIZE() of an unallocated array. Fixed by gating that whole block on the array actually being allocated. 2. 5MW_OC3Mnpl_Sld_REDWIN: a four-module failure cascade (ElastoDyn/SeaState/SoilDyn/ HydroDyn) ending in a segfault during output setup. SoilDyn.f90's SlD_Init has a REDWIN-DLL-load call with no accompanying "if (Failed()) return", so when REDWIN fails to load, the stale Fatal ErrStat trips the *next*, otherwise-successful AllocAry(WriteOutputHdr,...)'s Failed() check, returning before AllocAry(WriteOutputUnt,...) runs -- leaving WriteOutputHdr allocated but WriteOutputUnt not. FAST_InitOutput derived numOuts(Module_SlD) from WriteOutputHdr alone and then indexed WriteOutputUnt(i) too. Fixed by requiring both arrays allocated before trusting SoilDyn has any outputs. Added two CTest negatives (checkinput_aeromap_srvd_missing, checkinput_redwin_cascade) via of_checkinput() using the decks as-shipped (no corruption needed -- they fail on their own). Full report: .superpowers/sdd/sweep-crash-fix-report.md (gitignored, not part of this commit). Verified: ctest -R checkinput 19/19, ctest -R nwtc_library_utest 1/1 (13/13 sub-tests), happy-path re-check (AOC_WSt, 5MW_Land_DLL_WTurb with the built DISCON controller, hd_5MW_OC3Spar_DLL_WTurb_WavesIrr hydrodyn driver case) all still PASSED. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaXZQch1V1FSfKxuKSRM1R
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feature or improvement description
New
-CheckInputcommand-line option that verifies an input deck without running a simulation:The check runs the full initialization path of every module enabled by the input file — reading and
validating all module input files, resolving file references, building meshes and airfoil tables,
loading controller/interface libraries, and running the glue code's cross-module consistency checks
and mesh-mapping setup — then exits before any time marching. Three outputs:
INPUT CHECK SUMMARY(per-module status, every error and warning withits source);
<RootName>.verify.yamlreport, written incrementally (a file without thetrailing
overall_status:block indicates the check itself crashed — a deliberate livenesscontract for scripted callers);
0(deck valid; warnings allowed) or1(one or more fatal input errors).Unlike a normal run, which stops at the first fatal error,
-CheckInputusesattempt-everything error accumulation in the
openfastglue: when one module's init fails, theerror is recorded and every remaining module is still attempted (with guarded/stubbed interface data
where a failed upstream module would otherwise cause invalid dereferences, disclosed in the report as
status
unavailable). A deck with several independent problems is fully diagnosed in oneinvocation — the motivating use case is scripted and agent-driven workflows that generate decks
programmatically, where today each error costs a full simulation launch. It also replaces the common
TMax = DT"one-timestep" validation hack.The flag is available on every user-facing executable:
openfastFAST.FarmT<n>:attribution; downstream farm steps after a failure reported asskippedturbsim, 12 module drivers (aerodyn,aeroacoustics,hydrodyn,seastate,moordyn,inflowwind,aerodisk,sed,soildyn,orca,beamdyn,unsteadyaero).driver.verify.yaml)servodyn_driver(hardcoded test harness; deck is a code literal),feam_driver(no CLI),openfast_registry, C++ glue (dryRunalready exists in the C++ API)Prior art: the C++ API's
dryRunflag; LS-DYNAmcheck=1; OpenFOAMcheckMesh.Related issue, if one exists
No existing issue or PR requests an input-check mode for the Fortran CLI.
Impacted areas of the software
NWTC_CheckInput.f90(error collector, console summary writer,incremental YAML report writer, exit-code helper, driver-side helpers) + unit tests
(fortran-lang test-drive, 13 tests).
CHECKINPUTflag case + unit test.FAST_Prog.f90,FAST_Subs.f90,FAST_Registry.txt/generatedFAST_Types.f90): dispatch branch, newFAST_CheckInput_Tdriver routine, aCheckInputModeparameter, collect-and-continue behavior in
FAST_InitializeAll's containedFailed()(gated onan optional collector argument), dereference guards/stub meshes for failed-module continuation,
per-module report labels.
FAST_Farm.f90,FAST_Farm_Subs.f90): dispatch +Farm_CheckInput,collector threading through
Farm_Initialize. Also fixes three latentIsInitialized-set-on-failed-init bugs (AWAE, per-turbine WD and FWrap instances) that would have had
FARM_Endteardown failed instances.
TurbSim.f90) and the 12 driver programs listed above (main files + their argumentparsers;
InflowWind_Driver_Registry.txtregenerated for one new flag field).reg_tests/executeCheckInputTest.py(fixtures generated at test time by copying + corrupting pristine r-test cases — the r-test
submodule is not modified) and 19 CTest cases under the
checkinputlabel.docs/source/working.rst(usage, report contract, availability table,exclusions, DLL caveat, non-goals).
No changes to any module's physics or
Validate*routines. All error detection reuses theexisting validators; the feature only changes what happens after they report.
Backward compatibility: without the flag, behavior is unchanged. All new code paths are gated on
the
CheckInputModeparameter (default false) or the presence of the optional collector argument(never passed by existing callers, including the C bindings / Simulink / AeroMap paths). The few
ungated
allocated()/%committedguards are unreachable-when-false on normal runs (failure abortsbefore those lines). Verified empirically: baseline regression tests pass (below) and byte-identical
output spot-checks were done per driver during development.
Additional supporting information
r-test corpus (173 cases) through
-CheckInputsurfaced two latent state-consistency flaws inexisting code that fail-fast behavior had been masking; both are guarded in this PR and locked in
as regression tests:
SrvD%Input(...)%ExternalBlPitchComunconditionally after ServoDyn init (neverallocated if
SrvD_Initfails early);SlD_Init's REDWIN setup sets a fatalErrStatwithout an immediate return, so a subsequentsuccessful allocation's error check trips on the stale fatal, leaving
WriteOutputHdrallocatedbut
WriteOutputUntnot;FAST_InitOutputtrusted the header array alone. (The missingearly-return in
SoilDyn.f90itself is a candidate for a small separate fix; this PR onlyhardens the glue, per its no-module-changes rule.)
(
ad_BAR_OLAF/ad_driver_Advanced.dvr: missingnumCases;ad_Kite_OLAF/ad_driver_8figure.dvr:missing
analysisType). Not addressed here — can open r-test issues if useful.convergence, NaN growth) are out of scope by design; TurbSim's pre-existing output-writability
probe leaves a 0-byte
.wndplaceholder during a check; MoorDyn's module-level.MD.outheaderis still written (module scope); modules whose init loads an external DLL (SoilDyn REDWIN,
OrcaFlex) report a missing DLL as that component's failure — deliberate, since the DLL is part of
the deck/environment contract.
Generative AI usage
Substantial portions of this feature were developed with Anthropic Claude (Claude Code), under human
direction for all design decisions. Specifically, a multi-agent workflow was used: Claude Fable 5
orchestrated the work (design/spec interview with the human author, implementation planning, and
adjudication of review findings); Claude Sonnet agents performed the code implementation, the
per-task independent code reviews, and the codebase reconnaissance; Claude Opus performed the two
whole-branch final reviews; Claude Haiku handled small fully-specified tasks (documentation,
single-file test fixes). Every task was gated by an independent review pass (spec compliance + code
quality) before proceeding, and all changes are backed by the test evidence below.
Co-authored-by: Anthropic Claude claude@anthropic.com
This PR heavily relies on OpenFAST Agentic Kit (OAK) workflows.
Test results, if applicable
All on macOS (arm64), gfortran 15.2, RelWithDebInfo, double precision:
Unit tests:
nwtc_library_utest13/13 (newNWTC_CheckInputsuite);versioninfo_utest15/15(including the new flag case; no regressions).
New feature tests:
ctest -R checkinput— 19/19. Includes the multi-error acceptance test (twoindependent module errors seeded, both must be reported in one run), failed-module cascade and
segfault-regression cases, FAST.Farm per-turbine attribution, and per-executable rollout cases.
These tests are baseline-free (exit code + report assertions only) and generate corrupted fixtures
at test time.
Full r-test corpus sweep through
-CheckInput(ad hoc, not CI): 173/173 cases complete withoutcrashing — 168 pass, 3 fail cleanly with correct reports (1 macOS AMReX build limitation, the 2
malformed r-test
.dvrinputs noted above), 0 crashes.Baseline physics regression (unchanged-behavior check):
AWT_YFix_WSt,AOC_WSt,5MW_Land_DLL_WTurb— all pass channel-by-channel against stored baselines on the featurebranch (reference DISCON controllers built via
regression_test_controllers).r-test branch merging required — not required: no files in the r-test submodule are added
or modified; all new test fixtures are generated at test time from pristine copies.
please squash PR before merging