SeaState: On-demand block partitioning of wave kinematics to reduce memory (WvKinBlockMod)#3388
SeaState: On-demand block partitioning of wave kinematics to reduce memory (WvKinBlockMod)#3388mayankchetan wants to merge 17 commits into
Conversation
…ts (parsing only, behavior unchanged)
New required lines in the SPATIAL DISCRETIZATION section of the SeaState
primary input file, after NZ:
WvKinBlockMod (switch) 0: precompute full domain (current behavior),
1: on-demand block partitioning
WvKinBlockSize (m) target XY block edge length, DEFAULT=100
WvKinBlockFreeT (s) idle time before a block is freed, <=0 never,
DEFAULT=600
Parsed, validated (mode in {0,1}; size>0; fatal with WaveMod=6), and
mirrored into SeaSt_WaveFieldType as immutable config. No behavior
change: mode 1 does nothing yet. openfast_io reader/writer and user
docs updated; r-test submodule bumped to the companion branch with all
SeaState decks carrying the new lines.
Gate: seastate/hydrodyn/glue regression subset matches the clean-binary
baseline inventory; all SeaState module channel outputs byte-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGPxtJHRToYcregeghmrvn
…ematics block store (no behavior change) Adds SeaSt_WaveBlockType / SeaSt_WaveBlockStoreType to the SeaSt_WaveField registry, an owning &WaveBlockStore in the SeaState MiscVarType, and a non-owning *BlockStore pointer in SeaSt_WaveFieldType (associated only when WvKinBlockMod=1, wired before Waves_Init). Generation seeds for later on-demand block population: - Per-frequency seeds (WaveNmbrArr, OmegaIArr, MCFCArr) are captured inside VariousWaves_Init's frequency loop from the exact values it computes, guarded by ASSOCIATED(BlockStore) so WvKinBlockMod=0 executes nothing new. Recomputing them in another compilation unit is NOT bit-identical: moving WaveDispRel/WaveNumber to a new module changed gfortran inlining/FMA contraction and drifted seastate_wavemod5 outputs by 1 ulp (caught by the byte-identity gate, confirmed by bisect). - Grid z levels and the steady current profile are captured post-init by WaveKinKernel_CaptureGridSeeds (pure copies). New module SeaSt_WaveKinKernel.f90 also carries private verbatim copies of the COSH/SINH kinematics helpers so the future per-column kernel compiles in one unit with them, mirroring Waves.f90's current codegen. Gate: seastate + glue regression subsets match the baseline inventory; all 19 SeaState module outputs byte-identical to the pre-change snapshot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGPxtJHRToYcregeghmrvn
… shared by mode 0 and mode 1 VariousWaves_Init no longer assembles/IFFTs the per-point volume kinematics itself: WaveKinKernel_ComputeColumns (module Waves, PUBLIC) computes first- order dynamic pressure / velocity / acceleration (+MacCamy-Fuchs scaling, +steady current, above-SWL/below-seabed zeroing, periodic last-step wrap) for an arbitrary rectangle of grid columns, writing into caller-provided column-local arrays. Mode 0 calls it over the full grid; mode 1 block population (upcoming) will call it per block — one code path, so mode 1 results are bit-identical to mode 0 by construction. Generation seeds (wave numbers, intrinsic frequencies, MCF coefficients, direction cosines, grid coordinates, current profile) are captured inside VariousWaves_Init from the exact values it computes, into the block store (mode 1) or a routine-local store (mode 0). The full-size per-point frequency-domain temporaries (7-10 complex arrays of NStepWave2 x NPoints) are gone — the kernel uses per-column 1D work arrays, removing the init memory spike. Numerical note (user signed off): the restructured loop contracts FMAs differently than the old code, shifting mode-0 outputs at single-precision last-ulp scale vs dev (max ~1.0e-5 of channel RMS, 7/11 seastate cases; all regression tests pass). Verified NOT a transcription bug: expressions are verbatim, seeds are exact captures, and hosting the kernel in a separate module vs in Waves.f90 makes no difference. Accepted in exchange for a single shared generation path; the binding equivalence bar is now mode 1 == mode 0 bit-for-bit. The interim SeaSt_WaveKinKernel.f90 module (added in the previous commit) is removed — the kernel must live in Waves.f90 anyway, and its dispersion- helper copies are unnecessary there. Gate: seastate subset 11/11; glue subset green except the known pre-existing MHK_RM1_Floating_MR_Linear platform drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGPxtJHRToYcregeghmrvn
…l shared by mode 0 and mode 1 Waves2_Init no longer computes the second-order volume kinematics: the new WaveKinKernel_AddSecondOrderColumns (module Waves2, PUBLIC) adds difference-QTF and/or sum-QTF velocity / acceleration / dynamic-pressure corrections into caller-provided column-local arrays for an arbitrary rectangle of grid columns. Mode 0 (SeaState.f90) calls it over the full grid right after the first-order fill; mode 1 block population (upcoming) will call it per block — one code path, so mode 1 second-order results are bit-identical to mode 0 by construction. Waves2_Init retains the input checks and the WaveElev2 surface correction. Seeds: Waves2_CaptureKernelSeeds copies gravity + the QTF mode flags into the block store (mode 1) or a routine-local store (mode 0); grid coordinates are reused from the Task-3 capture when present. SeaSt_WaveBlockStoreType gains a Gravity field. Per the Task-2 same-compilation-unit rule, the kernel lives IN Waves2.f90 and carries private verbatim copies of the transfer functions (TransFuncB/D, k_nm_+/-, COSHNumOvrCOSHDen); Waves2_Init keeps the ones its elevation routines use. The full-size Waves2 InitOutput temporaries (WaveVel2D/S, WaveAcc2D/S, WaveDynP2D/S — up to six extra field-size arrays at init) and the AddArrays_4D/5D step in SeaState.f90 are gone; Waves2_InitOutputType is removed from the registry. Additions are performed per element in the same order and rounding as before (2*x is exact; sum term combined as T1 + 2*T2 in SiKi before the add; the wrapped last time step keeps the historical undoubled-diff quirk). Gate: 5MW_OC3Mnpl_DLL_WTurb_WavesIrr (+_Restart) — the discriminating WvDiffQTF+WvSumQTF cases — have 60/61 output channels byte-identical to the pre-change binary, including every hydro load channel driven by the volume kinematics. Only Wave1Elev (WaveElev2 surface correction, math untouched) shifts by 1 single-precision ulp in ~half the samples — the same gfortran-arm64 recompilation drift documented and signed off in the Task 3 commit. OC6_phaseII is WaveMod=0 (second order trivially zero): identical. Seastate module subset 11/11 with all outputs byte-identical; offshore glue subset green except the known pre-existing MHK_RM1_Floating_MR_Linear platform drift. New byte reference for the three Waves2 glue cases lives in baselines-task4/ (workspace-local). Also folds in the vs-build project-file removal of SeaSt_WaveKinKernel.f90 that the previous commit missed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGPxtJHRToYcregeghmrvn
…kMod=1) The feature goes live: with WvKinBlockMod=1 the full-domain volume arrays (WaveDynP/WaveVel/WaveAcc/WaveAccMCF) are never allocated. The XY grid is partitioned into blocks of whole grid cells (WvKinBlockSize snapped to cells, >=8 cells, full Z and time extent); a block is allocated and filled on the first wave-kinematics query that lands in it, by the same two column kernels that fill the full domain in mode 0 -- so block contents, and therefore all outputs, are bit-identical to mode 0 by construction. Key correction to the planned design: the volume interpolation stencil is 4 points per dimension (GridInterpSetup4D, Indx = floor(p)+[-1,0,1,2], cubic in the interior; all four indices are read even where edge weights are zero). Blocks therefore store the point planes [c0-1 .. c1+2] around their cell range (three shared planes between neighbors), and the owning block is derived from the stencil's base cell m%Indx(2,dim), which guarantees every stencil point is in-block. New in SeaSt_WaveField.f90 (which now uses modules Waves and Waves2 for the kernels -- acyclic): - WaveField_BlockStore_Init: block layout from the 4D grid params, logged. - WaveField_EnsureBlock: double-checked populate inside !$OMP CRITICAL(SeaSt_BlockPop) (first-order kernel, then second-order when QTF terms are enabled), population/peak-resident counters, one log line per population, last-access stamping (eviction comes next). - WaveField_InterpVol: the single reader of volume data. All 16 accessor read clusters route through it; in mode 1 it shifts a local copy of the interpolation misc indices to block-local and interpolates the block arrays. - WaveField_HasMCF: replaces ALLOCATED(WaveAccMCF) guards, which are wrong in mode 1 where the full-domain MCF array never exists (also fixes the guard on the extrapolation-stretching MCF surface correction, which keyed on the volume array). Mode-1 init skips the volume allocations (Initial_InitOut_Arrays), the full-domain MCF allocation and the full-grid kernel calls (Waves.f90), and the full-grid second-order add (SeaState.f90). Still water (WaveMod=0) and full-field kinematics file output (WrWvKinMod=2) force mode 0 with a notice; WaveMod=6 remains a validation error. Gate: mode 0 untouched -- seastate module outputs 22/22 byte-identical to the Task-3 reference, 5MW_OC3Mnpl outb data-identical to the Task-4 reference. Mode-1 twins bit-identical to their mode-0 siblings (output diffs confined to the timestamp header): seastate_1 (single-block AND forced 3x3 multi-block), seastate_WaveMod7_WaveStMod2 (extrapolation stretching), hd_MCF_WaveStMod2 (MacCamy-Fuchs + stretching), 5MW_OC3Mnpl_DLL_WTurb_WavesIrr (diff+sum QTF populated per block), 5MW_OC4Semi_WSt_WavesWN (2x2 layout, one 269 MB block resident instead of the full domain). Regression subset green except the known pre-existing MHK_RM1_Floating_MR_Linear platform drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGPxtJHRToYcregeghmrvn
Add WaveField_SweepBlocks: deallocates any populated on-demand wave block that has gone longer than WvKinBlockFreeT seconds of simulation time without an access, reclaiming its memory until the query returns. Freed blocks are regenerated deterministically by the same seed-driven kernels as the initial population, so eviction never changes results. Triggers, both under the SeaSt_BlockPop critical section so eviction never races population: (1) after each block population, since resident memory just grew; (2) a throttled sweep on any access, at most once per max(WvKinBlockFreeT, 60 s). The fresh block's LastAccess is stamped before the population-triggered sweep so it can never evict itself. WvKinBlockFreeT <= 0 disables freeing. Verified: HydroDyn driver, translating monopile (WaveDisp=1) sweeping a fine 10-block grid, triangle surge -90..+90..-90 over 60 s. Eviction on (FreeT=5): 18 populations / 15 evictions with repopulation of previously-freed blocks; eviction off (FreeT=-1): 10 populations / 0 evictions. Output byte-identical between the two (max abs diff 0.0, all channels) — deterministic-regeneration proof. seastate module suite 11/11 pass (mode 0 untouched by construction). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Print, in both modes at the end of SeaSt_Init: the volume grid dimensions and component count, the full-domain volume footprint (what mode 0 allocates / mode 1 serves on demand), the eager surface-array footprint, and — in on-demand mode — the block layout with the per-full-block size and the init-time populated count (0, since blocks are lazy). Volume bytes are computed from the grid dimensions (so the figure is correct in mode 1 where the arrays are never allocated); surface bytes are summed from the allocated eager arrays via storage_size()/8*size(). Self-contained in a BLOCK construct (local decls, no new routine); output-only, so channel results are unchanged. seastate module suite 11/11 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the "guidance added in the future" placeholder with a measured sizing section backed by block-size x free-time sweeps on 5MW OC4-Semi and MHK_RM1_Floating glue-code cases. Key measured facts documented: - Block halo (3-plane stencil overlap) makes small blocks counterproductive: size 50 m split the MHK footprint into 3 blocks and used ~17% MORE memory than the full-domain default. - size 100-200 m gives 1 resident block at parity-to-modest savings (OC4-Semi: -5% peak at the 100 m default). - WvKinBlockFreeT has no effect for station-keeping platforms (static query footprint never goes idle); eviction only helps a migrating query. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… blocks) WvKinBlockMod=1 blocks previously partitioned only X/Y and stored the full Z extent per block. Extend the same constant-cell tiling machinery to Z, using the mean Z spacing to snap the single WvKinBlockSize input to a cube edge for all three axes. The shared first- and second-order per-column kernels gain iPtZ0/nPtZ sub-range parameters; mode-0 callers still pass the full Z range so mode 0 stays byte-identical, and mode 1 fills each block through the same kernels over a Z sub-range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bookkeeping only (no FP code touched): distinguishes never-populated blocks from evicted ones for the upcoming block VTK export. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One legacy-VTK rectilinear grid per frame, one cell per block, BlockLife cell scalar: -1 never populated, 0 evicted, (0,1] normalized time-to-evict (pinned at 1 when eviction is disabled). Sets mvtk%nData explicitly because vtk_dataset_rectilinear never sets it and CELL_DATA needs the cell count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Optional trailing driver-file line (absent => off); writes the block partition VTK every N driver steps via WaveField_WriteBlockVTK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Called from WrVTK_Surfaces next to the wave-surface writer, same frame numbering; internally a no-op unless WvKinBlockMod=1, so no new inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R1VH35n3rgDRwB14o4eGwQ
Replaces the stale "Target XY edge length" phrasing (pre-Z-partition) in the registry files, generated types, input-parser comment, openfast_io writer, and docs appendix; bumps r-test to the matching deck-comment update (d4f6532). Comment/doc strings only, no code change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P66f1hCsQru66dxcmEwPMm
jjonkman
left a comment
There was a problem hiding this comment.
Super cool that AI made this PR come together very quickly!
See a couple comments below.
In the conversation, what does "mode 1" mean in the following?:
Guards: WaveMod = 0 and WrWvKinMod = 2 force mode 0; WaveMod = 6
(externally-supplied full kinematics) with mode 1 is a fatal input error.
|
|
||
| **NZ** sets the number of grid points in the vertical *Z*-direction from *Z* = (\ **MSL2SWL** − **Z_Depth**\ ) to *Z* = **MSL2SWL**\ . The distribution of grid points in the *Z*-direction is not uniform. It instead follows a cosine distribution: *Z*\ [\ *n*\ ] = **Z_Depth**\ (cos(\ *n*\ ·d\ *θ*\ )–1), where *n* = 0,1,…,\ **NZ**\ -1 and d\ *θ* = *π*\ /(2(\ **NZ**\ -1)). This distribution places more grid points near the free surface. **NZ** must be greater than or equal to 2. | ||
|
|
||
| **WvKinBlockMod** selects whether the wave-kinematics volume-data grid is precomputed over its full domain (**WvKinBlockMod** = 0, the default/current behavior) or partitioned into on-demand cube blocks (**WvKinBlockMod** = 1). When on-demand block partitioning is enabled, the wave-kinematics grid is divided into blocks partitioned in *X*, *Y*, and *Z*; each block is computed and stored only once wave kinematics are first requested at a point inside it. This reduces initialization time and memory use for domains that are much larger than the region actually queried during the simulation. Results with **WvKinBlockMod** = 1 are identical to those with **WvKinBlockMod** = 0 to within solver precision. |
There was a problem hiding this comment.
Why is WvKinBlockMod made a switch? Are you expecting additional options in the future? If not, this could be a TRUE/FALSE flag.
There was a problem hiding this comment.
mode 1 is referring to WvKinBlockMod=1, but you are right, I should just change that to a TRUE/FALSE flag
|
This looks very cool, @mayankchetan! Just a couple of questions based on the visualization:
But when the simulation advances, the blocks for the mooring lines seem to not be active anymore. Again, I'm not sure if this is related to the visualization or the actual behavior. |
So,
|
|
Thanks for the feedback, @mayankchetan. One small follow-up question based on my understanding of the settings used: |
There was a problem hiding this comment.
Pull request overview
This PR adds an optional on-demand block-partitioning mode for SeaState wave-kinematics volume data to reduce initialization memory by lazily populating (and optionally evicting) kinematics blocks, while preserving the existing full-domain precompute mode.
Changes:
- Introduces
WvKinBlockMod/WvKinBlockSize/WvKinBlockFreeTinputs and wiring for on-demand wave-kinematics blocks in SeaState (including restart packing/unpacking support). - Refactors shared wave-kinematics generation into per-column kernels reused by both full-domain precompute (mode 0) and block population (mode 1), including second-order additions.
- Adds optional wave-block VTK visualization output (SeaState driver and OpenFAST glue hook) and documents the new inputs/behavior.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| openfast_io/openfast_io/FAST_writer.py | Writes the three new SeaState spatial-discretization inputs. |
| openfast_io/openfast_io/FAST_reader.py | Reads the three new SeaState spatial-discretization inputs. |
| modules/seastate/src/Waves2.txt | Updates registry text to reflect removal of full-size init-time temporaries. |
| modules/seastate/src/Waves2_Types.f90 | Removes the Waves2_InitOutputType and associated pack/copy/destroy routines. |
| modules/seastate/src/Waves.f90 | Adds shared first-order column kernel and refactors init to capture seeds + call kernel for mode 0. |
| modules/seastate/src/UserWaves.f90 | Avoids allocating full-domain volume arrays when block mode is active. |
| modules/seastate/src/SeaState.txt | Adds new input fields and misc-var block-store pointer to the registry definitions. |
| modules/seastate/src/SeaState.f90 | Wires block store ownership, guards, seed capture, second-order integration, and memory reporting. |
| modules/seastate/src/SeaState_Types.f90 | Adds new input fields and registry pack/unpack/copy support for the block store pointer. |
| modules/seastate/src/SeaState_Input.f90 | Parses/validates the new inputs and stores them into p%WaveField. |
| modules/seastate/src/SeaState_DriverCode.f90 | Adds optional driver-only WrBlockVTK trailing input and writes block VTK frames. |
| modules/seastate/src/SeaSt_WaveField.txt | Adds block/block-store type definitions and new WaveField fields for block mode. |
| modules/seastate/src/SeaSt_WaveField.f90 | Implements block layout, population/eviction, VTK export, and unified interpolation access. |
| modules/seastate/src/SeaSt_WaveField_Types.f90 | Adds registry-generated types and pack/unpack/copy/destroy support for blocks/store. |
| modules/openfast-library/src/FAST_Subs.f90 | Hooks block VTK writing into existing WrVTK=2 surface output path. |
| LICENSE | Updates the Apache-2.0 appendix boilerplate copyright placeholder line. |
| docs/source/user/seastate/input_files.rst | Documents new inputs, sizing guidance, and VTK visualization behavior. |
| docs/source/user/seastate/appendix.rst | Updates the SeaState input-file structure example to include new inputs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| identification within third-party archives. | ||
|
|
||
| Copyright {yyyy} {name of copyright owner} | ||
| Copyright 2025 NREL |
|
|
||
| **NZ** sets the number of grid points in the vertical *Z*-direction from *Z* = (\ **MSL2SWL** − **Z_Depth**\ ) to *Z* = **MSL2SWL**\ . The distribution of grid points in the *Z*-direction is not uniform. It instead follows a cosine distribution: *Z*\ [\ *n*\ ] = **Z_Depth**\ (cos(\ *n*\ ·d\ *θ*\ )–1), where *n* = 0,1,…,\ **NZ**\ -1 and d\ *θ* = *π*\ /(2(\ **NZ**\ -1)). This distribution places more grid points near the free surface. **NZ** must be greater than or equal to 2. | ||
|
|
||
| **WvKinBlockMod** selects whether the wave-kinematics volume-data grid is precomputed over its full domain (**WvKinBlockMod** = 0, the default/current behavior) or partitioned into on-demand cube blocks (**WvKinBlockMod** = 1). When on-demand block partitioning is enabled, the wave-kinematics grid is divided into blocks partitioned in *X*, *Y*, and *Z*; each block is computed and stored only once wave kinematics are first requested at a point inside it. This reduces initialization time and memory use for domains that are much larger than the region actually queried during the simulation. Results with **WvKinBlockMod** = 1 are identical to those with **WvKinBlockMod** = 0 to within solver precision. |
| call RegUnpack(RF, OutData%WvKinBlockMod); if (RegCheckErr(RF, RoutineName)) return | ||
| call RegUnpack(RF, OutData%WvKinBlockSize); if (RegCheckErr(RF, RoutineName)) return | ||
| call RegUnpack(RF, OutData%WvKinBlockFreeT); if (RegCheckErr(RF, RoutineName)) return | ||
| if (associated(OutData%BlockStore)) deallocate(OutData%BlockStore) |







SeaState: on-demand wave-kinematics block partitioning (
WvKinBlockMod)This PR is ready to be merged.
Feature or improvement description
Adds an optional on-demand block partitioning mode for the SeaState
wave-kinematics volume arrays (velocity, acceleration, dynamic pressure, and —
when enabled — the MCF acceleration). Today SeaState precomputes these arrays
over the entire wave grid at initialization and holds them resident for the
whole simulation. For grids that are large relative to the region a structure
actually visits, most of that memory is never touched.
With
WvKinBlockMod = 1, the grid is divided into cube blocks partitioned inX, Y, and Z. Each block is computed the first time wave kinematics are
requested at a point inside it, and (optionally) freed after a configurable idle time.
Evicted blocks are transparently and deterministically recomputed on next
access.
Physical behavior is unchanged by default:
WvKinBlockMod = 0(full-domainprecompute) is byte-identical to the previous code. Note that the three new
input lines are required additions to the SeaState input file
(
WvKinBlockModhas no parser default; the other two acceptDEFAULT) — thecompanion r-test submodule bump adds them to every SeaState deck.
seastate_blocks_2x2.mp4
New inputs (SeaState SPATIAL DISCRETIZATION section)
WvKinBlockModWvKinBlockSizeWvKinBlockMod = 1WvKinBlockFreeTWvKinBlockMod = 1Guards:
WaveMod = 0andWrWvKinMod = 2force mode 0;WaveMod = 6(externally-supplied full kinematics) with mode 1 is a fatal input error.
Block VTK visualization
Adds an optional
<root>.SeaSt.WaveBlocks.<frame>.vtkoutput series so theon-demand block partition can be watched evolve during a run:
WrVTK = 2animation output — no new.fst/SeaState input is added. Thestandalone SeaState driver gets one new optional trailing input,
WrBlockVTK(write interval in steps; 0/absent = off, preservingcompatibility with every existing driver input file).
BlockLifecell scalar is-1for a block never populated,
0for an evicted block, and(0, 1]for aresident block (normalized time remaining before eviction, pinned at
1when
WvKinBlockFreeT <= 0).EverPopulatedflag is packed/unpacked viathe registry, and the non-owning
BlockStorealias on theWaveFieldisre-established on restart, so a restarted run resumes with the correct
never/evicted/active block states.
Related issue, if one exists
None.
Impacted areas of the software
modules/seastate/src/):SeaSt_WaveField.f90/.txt,SeaState.f90/.txt,SeaState_Input.f90,SeaState_DriverCode.f90,Waves.f90,Waves2.f90/.txt,UserWaves.f90, registry-generated*_Types.f90.modules/openfast-library/src/FAST_Subs.f90): the guardedblock-VTK call in
WrVTK_Surfaces.openfast_io/openfast_io/FAST_reader.py,FAST_writer.py):read/write the three new inputs.
docs/source/user/seastate/input_files.rst,appendix.rst.(branch
f/seastate-block-partitionon the r-test fork).Additional supporting information
Design
column computations were extracted into
WaveKinKernel_ComputeColumns(Waves.f90) and
WaveKinKernel_AddSecondOrderColumns(Waves2.f90). Mode 0(full-grid loop) and mode 1 (per-block fill) call the same kernels, so the
two modes agree to solver precision and evicted blocks regenerate
bit-identically. Kernels depend only on captured seeds and immutable grid
data — no run-time mutable state, no date/random. The kernels take
iPtZ0/nPtZZ sub-range arguments; mode 0 always passes the full Zrange, so its loop structure (and output) is unchanged by the Z
partitioning.
MiscVar(m%WaveBlockStore); theWaveFieldcarries a non-owning pointer. Blocks partition grid cells inall three axes and store a halo of three extra grid planes per axis (one
below, two above the block's cells) to satisfy the 4-point cubic
interpolation stencil.
WaveField_SweepBlocksfrees idle blocks under an!$OMP CRITICAL(SeaSt_BlockPop)guard; the just-accessed block is timestampedbefore any sweep so it can never evict itself.
block resident memory at the end of initialization.
Measured guidance (added to the user docs)
Block-size × free-time sweeps on 5MW OC4-Semi and MHK_RM1_Floating:
queried footprint. For station-keeping platforms (static footprint), savings
come from block size, not eviction.
WvKinBlockSize= 50 m split the MHK footprint across 3 blocks and used ~17% more peak
memory than the full-domain default. 100–200 m used a single block at
parity-to-modest savings (OC4-Semi: −5% at the 100 m default).
WvKinBlockFreeThas no effect for station-keeping platforms (footprintnever idles); it matters only for a query that migrates across the grid.
Generative AI usage
Substantial portions of this feature were developed with Anthropic Claude
(Claude Code); all design decisions were made and approved by the human
author. A multi-agent workflow was used: Claude Fable 5 orchestrated (design
interview, implementation planning, adjudication of review findings), with
implementation and per-task independent code reviews delegated to smaller
Claude models, and two independent whole-branch reviews performed by Claude
Opus. 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. Development relied heavily on the OpenFAST Agent Kit (OAK) —
OpenFAST-specific sub-agents and MCP tooling for reading/editing/running
cases, searching the docs/code corpus, and navigating the Fortran source.
Co-authored-by: Anthropic Claude claude@anthropic.com
Test results, if applicable
Mode-0 byte-identity (the primary gate). All 11 SeaState module regression
cases produce byte-identical
.SeaSt.outdata vs the post-kernel-refactorbaseline. This gate was re-run and re-passed 11/11 after each later structural
change (the Z/cube-block partitioning and the block VTK export). The one-time
kernel extraction introduces a floating-point reassociation drift of ≤ 1 ULP of
the printed output precision (max relative ≈ 8×10⁻⁷, well under the r-test
tolerance of ~2×10⁻⁵) vs the pre-refactor snapshot — documented for
transparency.
Mode 0 ≡ Mode 1. Re-verified on the final code (after the Z/cube-block
change): nine twin pairs across five cases — seastate_1 (3×3×2 forced cube
layout), seastate_wavemod5 (3×3×2), seastate_WaveMod7_WaveStMod2,
hd_MCF_WaveStMod2 (1×1×4 pure-Z stack, MacCamy-Fuchs array per block), and
5MW_OC3Mnpl_DLL_WTurb_WavesIrr (second-order QTFs filled over true Z
sub-ranges, 1×1×2) plus 5MW_OC4Semi_WSt_WavesWN under the full glue stack —
all bit-identical to the mode-0 twin (binary channel data max abs diff
0.0; text outputs differ only in run-timestamp header lines). Wave kinematics
were also compared directly: a 9-probe twin (
NWaveKin = 9, probes placedto sample every XY block column and both Z layers of a 3×3×2 partition) shows
all 63 kinematics output channels (
FVelJ*,FAccJ*,FDynPJ) exactly equalbetween the two modes at every output step. Deterministic
regeneration after eviction: a 1000 s drifting-platform run with 166
evictions/repopulations is payload-byte-identical to its eviction-disabled
twin. Earlier smoke coverage (py_seastate_1 binding, 5MW_OC3Mnpl restart,
eviction-on/off) also passed byte-identical.
Block VTK verified end-to-end. A drifting-platform run on 5MW OC4-Semi
(
CompMooring = 0, HydroDynWaveDisp = 1so the kinematics footprint followsthe drift,
TMax = 300,WvKinBlockFreeT = 30) produced a 301-frame seriesshowing all three states (
never/evicted/active); a pinned run(
WvKinBlockFreeT = -1) showed onlynever/activewith every resident blockexactly at
1.0; the standalone-driver trigger was exercised onseastate_wr_kin1(needsNWaveKin > 0) with well-formed frames every 5steps. Three additional 1000 s OC4-Semi drifting-platform visualization runs
(mode 0 / eviction-off /
WvKinBlockFreeT = 60) confirmed the full block lifecycle in the exported series: block counts growing with the drift, the eviction
countdown, and 15 blocks evicted by the end of the eviction-enabled run.
Full regression suite.
ctest -j 8(196 tests). 7 failures, all triaged asunrelated to this feature:
MHK_RM1_Floating_MR_LinearDamped_Beam_Rotated,5MW_Land_BD_Linear,IEA22MW_ModalDampingopenfast_io_librarypytestnot installedpy_wavetank_test1nptdmsnot installed5MW_OC4Jckt_DLL_WTurb_WavesIrr_MGrowthIndependent whole-branch code review (Claude Opus): no Critical or Important
findings. All six binding invariants (mode-0 byte-identity, mode 0 ≡ mode 1,
deterministic regeneration, halo correctness, guards, eviction/ownership
safety) verified by source trace. Three Minor, non-blocking observations
recorded.
r-test branch merging required
(
f/seastate-block-partitionon the r-test fork — adds the three new inputlines to all SeaState decks)
please squash commits before merge