Skip to content

FAST.Farm slice output (terrain following, and arbitrary slice)#3392

Draft
andrew-platt wants to merge 36 commits into
OpenFAST:devfrom
andrew-platt:f/FF_sliceOutput
Draft

FAST.Farm slice output (terrain following, and arbitrary slice)#3392
andrew-platt wants to merge 36 commits into
OpenFAST:devfrom
andrew-platt:f/FF_sliceOutput

Conversation

@andrew-platt

@andrew-platt andrew-platt commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Not ready to merge. See merge order below

Co-authored-by: Regis Thedin registhedin@gmail.com - conceptual development and multi-round plan iteration

Feature or improvement description

Adds two new FAST.Farm VISUALIZATION output features requested by the "Large Wind Farm Task" (§2.8 and §2.9) and issue #2383. Both are gated by dedicated block headers so existing FAST.Farm input files remain unchanged.

  1. NumPlaneSlices — axis-aligned, extent-controlled planar sampling (Snippet 6 grammar from the plan). Each slice is defined by an origin(m), an axis-aligned normal, and two in-plane extent(m) values. The sample grid is fixed to the low-res spacing (dX_Low/dY_Low/dZ_Low), so no npoints column is required. Off-axis normals are a fatal input error whose message forward-references the future point-cloud extension.
  2. NumTerrainSlices — terrain-following point-cloud sampling from an STL surface or a plain-text point cloud, each swept along one or more Offsets(m) in an OffsetNormal direction (facet-normal by default for STL). Every sample point is trilinearly interpolated in the low-res disturbed wind field at each output step.

Both features write self-describing XML VTK output (.vts StructuredGrid for planar slices, .vtp PolyData for point clouds) plus a matching .series JSON sidecar for ParaView's built-in time-series loader. Out-of-domain sample nodes are masked with IEEE quiet NaN so ParaView clips them automatically.

Each feature has its own sampling period (WrPlaneDT, WrTerrainDT) that falls back to WrDisDT via DEFAULT, so users can sample the two feature streams at rates independent of the classic disturbed-wind output.

Feature 1 — three axis-aligned planes (conceptual and real output) fig1_arbslice_concept

The LESinflow_ArbSlice reg-test deck plants three slices: a horizontal hubXY plane (red, normal (0,0,1)), a cross-stream crossYZ plane through the turbine (green, normal (1,0,0)), and a stream-wise crossXZ plane (blue, normal (0,1,0)). All three intersect at the WT1 turbine.

fig2_arbslice_real

Above: horizontal hubXY plane at $t = 24$ s parsed directly from FAST.Farm.Plane.hubXY.008.vts. The plane is discretised at $121 \times 61$ nodes on the low-res spacing (dX_Low = dY_Low = 10 m); the wake deficit downstream of WT1 shows up as the dark low-velocity patch centred at $(1000, 1000)$.

Input file syntax (from the reg-test deck):

--- AXIS-ALIGNED PLANE SLICES (extent-controlled) --- [Feature 1, Snippet 6 grammar]
3                  NumPlaneSlices     - Number of axis-aligned extent-controlled planar slices (-) [0 to 99]
DEFAULT            WrPlaneDT          - Feature 1 sampling period (s); DEFAULT falls back to WrDisDT
SliceName  origin(m)      normal   extent1(m)  extent2(m)
(-)        (m,m,m)        (-)      (m)         (m)
"hubXY"    (700 700 95)   (0 0 1)  1200        600           ! 1200x600 XY plane at hub height
"crossYZ"  (1000 700 5)   (1 0 0)  600         300           ! 600x300 YZ plane through the turbine at x=1000
"crossXZ"  (700 1000 5)   (0 1 0)  1200        300           ! 1200x300 XZ plane through the turbine at y=1000
Feature 2 — STL mountain + hub cloud (conceptual and real output) fig3_terrainslice_concept

The LESinflow_TerrainSlice reg-test deck plants two slices:

  • "terr" — a 4-facet pyramid mountain read from mountain.stl (12 vertices, one per STL facet corner), swept along the per-facet normal to three offset sheets at 50/100/150 m. Total sample count: $3 \times 12 = 36$ points.
  • "hub" — a 9-point hand-authored point cloud read from hub_points.txt, one sheet at offset 0 with an explicit vertical OffsetNormal (0 0 1). Exercises the CSV-with-header parser path in ReadPointCloudFile.
fig4_terrainslice_real

Above: sampled velocity magnitude at $t = 24$ s parsed from FAST.Farm.TerrSlice.terr.008.vtp (green circles, 36 pts across three offset sheets) and FAST.Farm.TerrSlice.hub.008.vtp (blue squares, 9 pts at hub height). The underlying pyramid is drawn faint for scale.

Input file syntax (from the reg-test deck):

--- TERRAIN-FOLLOWING SAMPLING --- [Feature 2, PDF §2.8 grammar with SourceType flag]
2                  NumTerrainSlices   - Number of terrain-following point-cloud slices (-) [0 to 99]
DEFAULT            WrTerrainDT        - Feature 2 sampling period (s); DEFAULT falls back to WrDisDT
SliceName   Offsets(m)     OffsetNormal   SourceType   FileName
(-)         (m,list)       (-,-,-|dflt)   (STL|Point)  (quoted)
"terr"      50 100 150     default        STL          "mountain.stl"       ! sample above the mountain surface on 3 offset sheets
"hub"       0.0            (0 0 1)        Point        "hub_points.txt"     ! single sheet at hub height (offset 0)

Related issue, if one exists

Closes / addresses #2383 (parts pt. 1 = axis-aligned planar sampling, pt. 2 = terrain-following point cloud). The full design plan lives in FF_output_features_plan.md on the branch and cross-references the same issue.

Impacted areas of the software

Area File(s) Change
AWAE core modules/awae/src/AWAE.f90 New: ExtractSliceSub (bounded variant of ExtractSlice), ValidatePlaneSlices, EmitPlaneSlices, ReadSTLFile, ReadPointCloudFile, ValidateTerrainSlices, EmitTerrainSlices. Init calls both Validate* after existing slice validation; End closes both feature's .series sidecars.
AWAE I/O helpers modules/awae/src/AWAE_IO.f90 New: WriteVTK_PolyData (.vtp), WriteVTK_StructuredGrid_2D (.vts), streaming VTK_Series_Open / _Append / _Close for .series JSON sidecars.
AWAE registry modules/awae/src/AWAE_Registry.txt Flat-array fields on InputFileType / ParameterType / MiscVarType for both features (flat rather than array-of-derived-type — see design notes below).
AWAE types (generated) modules/awae/src/AWAE_Types.f90 Regenerated.
FAST.Farm glue glue-codes/fast-farm/src/FAST_Farm_IO.f90 New: ReadPlaneSlicesBlock and ReadTerrainSlicesBlock (both use backspace-and-return for missing headers, so pre-existing decks continue to parse unchanged).
Reg-tests reg_tests/CTestList.cmake Two new ff_regression entries — LESinflow_ArbSlice and LESinflow_TerrainSlice.
Reg-test decks reg_tests/r-test submodule bump to 64feb0011 New decks with baselines (see r-test PR below).
Docs docs/source/user/fast.farm/InputFiles.rst, OutputFiles.rst, ModelGuidance.rst, FutureWork.rst, api_change.rst New sub-sections describing input syntax, output naming (.Plane.<name>.<t>.vts, .TerrSlice.<name>.<t>.vtp, matching .series sidecars), NaN masking, and the independent sampling-rate story. FutureWork links back to #2383 for the arbitrary-orientation extension.

Additional supporting information

Commit layout on this branch (4 logical commits on top of b/FF_wake_start_noise):

Commit Subject
12af71a69 FAST.Farm: foundation for terrain-following and axis-aligned planar output
eff99bcdd FAST.Farm: Feature - axis-aligned planar sampling (NumPlaneSlices)
e7ace15fd FAST.Farm: Feature - terrain-following point-cloud sampling (NumTerrainSlices)
cdfb61065 FAST.Farm docs: document the new NumPlaneSlices / NumTerrainSlices blocks

Design notes worth calling out at review:

  • Flat arrays instead of AWAE_PlaneSliceType/AWAE_TerrainSliceType. An earlier draft used derived types for the per-slice state, but the generated per-element pack/unpack in FAST_Farm_Types.f90 blew past the 7.7 GiB container's memory ceiling during f951 code-gen. The switched design uses PlaneSliceOrigin(3,:) / PlaneSliceExtent1(:) / etc. plus a prefix-sum TerrainSliceOffsetIdx(:) into a packed TerrainSliceOffsetsPacked(:) — the ragged offsets stay ragged without paying the derived-type registry cost.
  • Duplicate-emit guard for t = 0. Farm_InitialCO calls AWAE_CalcOutput twice at $t=0$ (once for validation and once for the actual initial output). Both features carry a scalar m%LastPlaneSliceN / m%LastTerrainSliceN initialised to -1; the Emit* routines early-return when m%Last*N == n so the identical .vts/.vtp file isn't rewritten and the .series sidecar doesn't get a duplicate t=0 entry.
  • NaN masking for out-of-domain nodes. EmitPlaneSlices pre-fills the write buffer with ieee_value(0.0_SiKi, ieee_quiet_nan), then calls the bounded ExtractSliceSub for only the in-domain window [iLoE, iHiE] × [jLoE, jHiE]. EmitTerrainSlices NaN-fills any point whose $(x, y, z)$ falls outside [oXYZ, oXYZ + (nXYZ-1)·dXYZ]. ParaView masks these automatically.
  • Off-axis normals in Feature 1 are a fatal error (per plan §3.7): the message names the offending slice and forward-references the future arbitrary-orientation point-cloud extension (docs/source/user/fast.farm/FutureWork.rst).

Conceptual overlap with the existing AWAE_vtk .vtk.series writer. During the rebase onto b/FF_wake_start_noise (see "Test results" below) it became clear that this branch's AWAE_IO.f90::VTK_Series_Open/_Append/_Close helpers and the base branch's AWAE_vtk.f90::Write_DisWind_Series/Write_WakePlane_Series/Write_WireFrame_Series both emit ParaView .series JSON sidecars, but with materially different flow patterns:

AWAE_vtk (base branch) AWAE_IO (this PR)
Trigger Called once from AWAE_End after the run Streamed: Open at Init, Append each CalcOutput, Close at End
File extension .vtk.series (legacy VTK) .vts.series / .vtp.series (XML VTK)
Requires known step count? Yes — iterates 0..NumDT/WrDisSkp1 No — accretes as steps happen
Behaviour on abnormal termination Nothing written Whatever steps completed are still valid ParaView-loadable

No literal duplicate code and both routines coexist without collision on the merged branch, but a future cleanup PR could consolidate them into a single AWAE_vtk interface (streaming API for both) — kept out of this PR to hold the rebase minimal.

Generative AI usage

Substantial portions of this PR — implementation across all 4 phases, rebase conflict resolution onto b/FF_wake_start_noise, and reg-test deck authoring — were generated with AI assistance. Co-author trailers are on every commit.

Co-authored-by: Anthropic Claude Opus 4.7 claude@anthropic.com
Co-authored-by: Google Gemini 3.5 Flash gemini@google.com
Co-authored-by: GitHub Copilot copilot@github.com

Test results, if applicable

  • r-test branch merging required — bumps reg_tests/r-test submodule pointer from 14c0e913e (b/FF_wake_start_noise) to 64feb0011 (r-test f/FF_sliceOutput, 2 commits: 7be23c16e LESinflow_ArbSlice deck + 64feb0011 LESinflow_TerrainSlice deck). See companion r-test PR.

Full FAST.Farm regression suite in build-docker-double (double-precision, AMREX_READER=ON, RelWithDebInfo):

100% tests passed, 0 tests failed out of 9
Total Test time (real) = 126.99 sec

    Start 84: AMReX .................. Passed
    Start 85: TSinflow ............... Passed
    Start 86: LESinflow .............. Passed
    Start 87: LESinflow_ArbSlice ..... Passed   ← Feature 1 deck (new)
    Start 88: LESinflow_TerrainSlice . Passed   ← Feature 2 deck (new)
    Start 89: TSinflow_curl .......... Passed   ← b/'s updated wake-decay baseline still matches
    Start 90: ModAmb_3 ............... Passed
    Start 91: TSinflowADskSED ........ Passed
    Start 92: MD_Shared .............. Passed

Output verification. For each new deck, the per-step .vts/.vtp files were spot-checked by parsing the XML and confirming (a) node coordinates match the input origin + i·d_axis / Pts + Offset·OffsetNormal arithmetic to machine precision, (b) .series sidecars have exactly one entry per CalcOutput invocation with monotonically increasing time, and (c) no duplicate t = 0 entry from the double-call inside Farm_InitialCO.

Merge order

  • final release of 5.0.1
  • cross merge of 5.0.1 to dev
  • perform a check of code redundancy after merging dev to this branch
  • merge

andrew-platt and others added 30 commits May 12, 2026 13:07
Add Write_Planes_WireFrame and PlaneCorners helper to emit each
active wake plane's outline in the global frame using vtk_lines.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This is not complete, but contains some of the info.
The .vtk.series file is not working as expected yet.
The edge points on the wake planes were not getting updated.  The loop limits were one point short.
New guidance for calculating with larger safety factors, and different calculation for catesian+curl than polar.

Co-authored-by: Regis Thedin <registhedin@gmail.com>
WD: fix smooth tapering from `numDFull` to `numDBuff` for curled wake
- Create modules/awae/src/AWAE_vtk.f90 containing module AWAE_vtk with
  all wake-plane VTK output routines: PlaneAxes, PlaneCorners,
  Write_Planes_WireFrame, Write_Planes_Data, Write_WakePlane_Data_File,
  Write_WakePlane_Series, and Write_NullPlane.

- Remove those routines from AWAE_CalcOutput's internal contains block
  and convert them to standalone module subroutines with explicit
  arguments (p, u, m, n, t, Tstr) replacing host association.

- Move the per-step Write_WakePlane_Series call from AWAE_CalcOutput to
  AWAE_End, writing series files once at shutdown instead of every output
  step.  Merge the former two-routine pattern (Write_WakePlane_Series +
  Write_AllWakePlane_Series) into a single Write_WakePlane_Series(p, m).

- Extract the inline null-plane block into Write_NullPlane subroutine.

- Remove unused variables: FmtStr argument, PlaneNum, fileExists,
  rootDir from the former Write_WakePlane_Series.

- Add AWAE_vtk.f90 to CMakeLists.txt and vs-build project; add
  'use AWAE_vtk' to module AWAE.

Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com>
…s files

Add a new routine Write_WireFrame_Series(p) to AWAE_vtk that writes one
ParaView .vtk.series index file per turbine for the WakePlanesWireFrame
VTK outputs. Called once at shutdown from AWAE_End alongside the existing
Write_WakePlane_Series call.

Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com>
Add Write_DisWind_Series to AWAE_vtk module to generate ParaView
.vtk.series index files for Low-resolution disturbed-wind XY, YZ,
and XZ output slices.  The routine is called from AWAE_End for each
valid slice, matching the existing pattern for wake-plane and
wireframe series files.

Co-authored-by: GitHub Copilot <noreply@github.com>
…dels

In WD_CalcOutput, the firstPass branch computed y%Vx_wake (polar) via
NearWakeCorrection but never populated y%Vx_wake2 for Cartesian/Curl models.
Additionally, the unconditional Cartesian/Curl taper block overwrote y%Vx_wake2
with xd%Vx_wake2 (still zeros) on firstPass, discarding any values set earlier.

Changes:
- Add Axisymmetric2CartesianVx conversion in the firstPass block of WD_CalcOutput
  to populate y%Vx_wake2 for Curl/Cartesian models.
- Guard the Cartesian/Curl taper block with (.not. OtherState%firstPass) so it
  does not overwrite the firstPass initialization.
- Add Mod_Wake guard and NearWakeCorrection call in InitStatesWithInputs for
  Cartesian/Curl state initialization.
- Remove unused GridTol parameter in AWAE.f90.

Co-authored-by: andrew-platt <andy.platt@nrel.gov>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
I thought this was in the outer context, but apparently not.  Not sure how it was still compiling ok during testing.
- Changed planeDomainExit from a scalar to a 3-element array (one per
  spatial dimension) so a plane can be tracked as out-of-bounds in
  multiple dimensions simultaneously (e.g. exiting at a corner).
- Split boundary-check logic to handle X, Y, Z independently in a loop.
- Changed out-of-bounds velocity to reach target in one timestep (was 2).
- Updated Registry, Types, and allocation to match new 3D shape.

Co-authored-by: GitHub Copilot <noreply@github.com>
…rging

- Added y%wakePlaneDomainExit (ReKi, 3 x planes x turbines) to AWAE
  OutputType, populated from m%planeDomainExit after LowResGridCalcOutput.
- Added u%wakePlaneDomainExit (ReKi, 3 x planes) to WD InputType.
- Transfer in glue-code Transfer_AWAE_to_WD.
- Added out-of-bounds plane merging in WD_UpdateStates: consecutive
  planes that are both out of domain and within 2*dr of each other
  (Euclidean) are averaged and collapsed iteratively from highest to
  lowest index.
- Extracted common merge+shift logic into contained subroutine
  MergeWakePlanes(iKeep, iDrop), used by both overtake and OOB merging.
- Used ReKi for the input/output arrays (framework differentiability
  requirement); NINT used for comparisons in WD.

Co-authored-by: GitHub Copilot <noreply@github.com>
Change time declaration from real(ReKi) to real(DbKi) to match
amrex_read_header signature. Use _ReKi kind for gridSpacing, gridSize,
and bounds literal constants instead of _c_double.

Co-authored-by: GitHub Copilot <noreply@github.com>
…f tail truncation

Rewrite the buffer-exit plane-dropping logic so that each plane beyond
x_Buff is individually removed and higher-indexed planes are shifted
down.  This replaces the previous approach that simply decremented
NumPlanes (effectively dropping the last plane), which assumed the
highest-indexed plane had always travelled the furthest.

Extract the array-shifting loop from MergeWakePlanes into a new
ShiftWakePlanesDown subroutine shared by both MergeWakePlanes and the
buffer-drop loop.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude (Anthropic) <claude@anthropic.com>
andrew-platt and others added 5 commits July 9, 2026 17:02
Move the domain-exit check for wake plane merging from an AWAE-computed
per-plane flag (wakePlaneDomainExit) to a direct bounds test using the
low-resolution grid bounds stored as a WakeDynamics parameter.

- Add LowResBounds(3,2) to WD InitInputType and ParameterType
  (index 1 = XYZ dimension, index 2 = lower/upper bound).
- Populate LowResBounds in FAST_Farm_Subs from AWAE init outputs
  (oXYZ_low, dXYZ_low, nXYZ_low) before calling WD_Init.
- Add PlaneOutOfBounds() pure function in WD_UpdateStates that tests
  plane center position against p%LowResBounds directly.
- Remove wakePlaneDomainExit from WD InputType, AWAE OutputType,
  and the Transfer_AWAE_to_WD glue-code transfer.
- Remove dead LowRes%Center field from AWAE LRGParamType.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude (Anthropic) <claude@anthropic.com>
…utput

Adds three pieces of dead-code infrastructure that later commits wire into
the new VISUALIZATION features (NumPlaneSlices, NumTerrainSlices):

* modules/awae/src/AWAE.f90
    ExtractSliceSub - bounded variant of ExtractSlice that populates a
    subregion [i_lo:i_hi, j_lo:j_hi] of an axis-aligned slice. Used by
    the axis-aligned planar sampling feature (Feature 2).

* modules/awae/src/AWAE_IO.f90
    WriteVTK_PolyData - writes a single-piece VTK XML PolyData (.vtp)
    file for a point cloud with a 3-component vector field. NaN in the
    field is accepted (ParaView masks it). Used by the terrain-following
    feature (Feature 1).

    WriteVTK_StructuredGrid_2D - writes a 2-D VTK XML StructuredGrid
    (.vts) file with explicit points and a 3-component vector field.
    Used by Feature 2 when VTKFormat=xml.

    VTK_Series_Open / VTK_Series_Append / VTK_Series_Close - trio for
    incrementally writing a ParaView .series JSON sidecar so the two
    features can emit time-varying VTK series without buffering the
    full step list. Used by both Features 1 and 2.

None of the new symbols has a caller yet; all existing tests behave
identically. Verified with ctest -L fastfarm against the LESinflow deck
(passes) and the multi-turbine AMReX / ModAmb_3 / MD_Shared decks
(all pass). Three tests are still failing on tolerance drift because
the r-test gold baselines were captured with double-precision + OpenMP;
those pass on build-docker-double per the plan.

Also includes FF_output_features_plan.md, the multi-round design plan
for the two new output features, so future reviewers can see the
motivating requirements and locked-in design decisions.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Adds a new '--- AXIS-ALIGNED PLANE SLICES (extent-controlled) ---' block
to the FAST.Farm primary input file that lets the user emit an arbitrary
number of axis-aligned uniform-grid VTK slices, each with a
user-controlled 2-D extent placed anywhere in the low-res domain.
Grammar follows plan-file Snippet 6 (OpenFOAM-inspired origin + normal
+ extent1 + extent2). Output is XML VTK .vts + .vts.series so ParaView
can play the time series without buffering the full step list.

Registry-level additions (flat arrays rather than a derived slice type,
to keep the auto-generated pack/unpack code inside the compiler's memory
budget on this container):
  * AWAE_InputFileType: NumPlaneSlices, WrPlaneDT, PlaneSliceName/Origin/
    Normal/Extent1/Extent2
  * AWAE_ParameterType: NumPlaneSlices, WrPlaneSkp, PlaneSliceName,
    PlaneSliceOrigin, PlaneSliceNormalAxis/Axis1/Axis2/N1/N2/ILo/IHi/
    JLo/JHi/Valid/SeriesUn
  * AWAE_MiscVarType: PlaneSliceStepCount, LastPlaneSliceN (scalar guard
    against Farm_InitialCO calling AWAE_CalcOutput twice at t=0)

Code additions (all in modules/awae/src/AWAE.f90 and AWAE_IO.f90):
  * ValidatePlaneSlices - init-time normal check (fatal on off-axis with
    the exact plan §3.7 error text pointing to the future point-cloud
    feature), extent-positivity check, derives (Axis1, Axis2, N1, N2)
    from the low-res spacing, requested (i_lo, i_hi, j_lo, j_hi) index
    window (may be out-of-domain -> emit fills with NaN), and opens the
    .vts.series sidecar for each valid slice.
  * EmitPlaneSlices - per-step VTK emitter, called from AWAE_CalcOutput
    at every mod(n, p%WrPlaneSkp)==0. Pre-fills sample buffer with IEEE
    quiet NaN, then uses ExtractSliceSub (from the Phase 1 foundation)
    to fill only the in-domain window; writes .vts + appends to
    .vts.series.
  * VTK_Series_Close wiring in AWAE_End.
  * MKDIR condition extended to include NumPlaneSlices>0 so the vtk_ff/
    directory is created even when the classical NOutDisWind* slices
    are disabled.
  * One trivial format-string fix in VTK_Series_Append (had one too
    many 'A' items).

Parser (FAST_Farm_IO.f90):
  * ReadPlaneSlicesBlock - optional block reader with backspace-and-
    return path when the block header is absent (legacy decks stay
    inert). Tolerates parenthesised vectors and commas for the
    (origin), (normal) columns. Comment characters '!' and '#' allowed
    in slice rows.

Reg-test:
  * reg_tests/CTestList.cmake gains one ff_regression() line for
    LESinflow_ArbSlice. r-test submodule is bumped to the commit that
    contains the deck under
    glue-codes/fast-farm/LESinflow_ArbSlice/.
  * The deck's turbine physics and OpenFAST outputs are identical to
    the LESinflow deck by construction, so its baseline .out / .T1.outb
    are the LESinflow originals; only new VTK slice files are produced.

Verified with:
  cd build-docker-single-debug && ctest -L fastfarm --output-on-failure
  # 5 pass (LESinflow, LESinflow_ArbSlice, AMReX, ModAmb_3, MD_Shared)
  # 3 fail on the pre-existing single-precision-vs-double-precision
  # tolerance drift (TSinflow, TSinflow_curl, TSinflowADskSED) - these
  # were failing before Phase 1/2 and will pass in build-docker-double
  # per §10.2 of the plan.
The vts.series sidecar for the new deck is clean (no duplicate at t=0
thanks to the LastPlaneSliceN scalar guard).

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude <noreply@anthropic.com>
…inSlices)

Adds a new '--- TERRAIN-FOLLOWING SAMPLING ---' block to the FAST.Farm
primary input file that lets the user emit an arbitrary number of point
clouds sampled from an STL surface or a plain-text/CSV point list, each
optionally lifted to multiple offset sheets along a user-provided (or
STL-per-facet) normal.  Output is XML VTK .vtp + .vtp.series so ParaView
can play the time series with correct NaN masking for points outside the
low-res domain.

Input grammar (pdf §2.8, with the SourceType/FileName column pair the
user requested during plan review):
  --- TERRAIN-FOLLOWING SAMPLING ---
  <NumTerrainSlices>          NumTerrainSlices [0..99]
  <WrTerrainDT | DEFAULT>     WrTerrainDT     (defaults to WrDisDT)
  SliceName   Offsets(m)  OffsetNormal   SourceType   FileName
  (-)         (m,list)    (-,-,-|dflt)   (STL|Point)  (quoted)
  "terr"      50 100 150  default        STL          "mountain.stl"
  "hub"       0.0         (0 0 1)        Point        "hub_points.txt"

Registry additions (flat/packed arrays -- same design as Feature 2 --
to keep the auto-generated pack/unpack code inside the compiler's memory
budget on this container):
  * AWAE_InputFileType: NumTerrainSlices, WrTerrainDT, TerrainSliceName,
    TerrainSliceSourceType (1=STL / 2=Point), TerrainSliceFileName,
    TerrainSliceOffsetNormal, TerrainSliceOffsetsPacked +
    TerrainSliceOffsetIdx (packed ragged offsets)
  * AWAE_ParameterType: NumTerrainSlices, WrTerrainSkp, TerrainSliceName,
    TerrainSliceValid, TerrainSliceSeriesUn, TerrainSliceNPtsTotal,
    TerrainSlicePtsIdx, TerrainSlicePtsFlat (packed per-slice point
    coordinates, all offsets embedded)
  * AWAE_MiscVarType: TerrainSliceStepCount (per-slice isFirst counter)
    + scalar LastTerrainSliceN guard against Farm_InitialCO's double
    t=0 CalcOutput

New code (modules/awae/src/AWAE.f90):
  * ReadSTLFile - reads ASCII or binary STL and returns per-vertex
    coordinates + facet normals (the same normal is replicated across
    the three vertices of each facet). Auto-detects ASCII vs binary
    from the header. No de-duplication (deliberate: keeps output
    faithful to the source mesh).
  * ReadPointCloudFile - reads plain-text or comma-delimited x y z per
    line; tolerates leading comment (# or !) rows and one optional
    header row (any line that fails numeric parsing before the first
    data row is silently skipped).  Fails fast on a malformed row
    once data has started, per plan §2.2.1.
  * ValidateTerrainSlices - reads each slice's file, expands with the
    per-slice offsets (using the STL per-facet normal when
    OffsetNormal=0 sentinel, else the global vector), packs the
    resulting points into p%TerrainSlicePtsFlat, opens each valid
    slice's .vtp.series sidecar, emits an ErrID_Info size advisory
    above 10 M sample locations (per plan §2.2.2 no-hard-cap policy),
    and emits an ErrID_Warn describing the fraction of the slice that
    will be NaN-filled when it straddles the domain boundary.
  * EmitTerrainSlices - per-step trilinear-samples m%Vdist_low_full at
    every pre-computed point (NaN for out-of-domain), writes a .vtp
    per slice, appends to the .vtp.series sidecar.
  * MKDIR condition extended: vtk_ff/ is now created when either
    NumPlaneSlices or NumTerrainSlices > 0, in addition to the
    original legacy triggers.
  * VTK_Series_Close wiring added to AWAE_End.

Parser (glue-codes/fast-farm/src/FAST_Farm_IO.f90):
  * ReadTerrainSlicesBlock - same optional-block pattern as
    ReadPlaneSlicesBlock (backspaces on missing header for legacy
    decks). Extracts the two quoted strings (name, filename) in order,
    strips ( ) , delimiters, then tokenises the remaining row into
    (offsets, [normal|"default"], SourceType). The variable-length
    offsets list is inferred from the token count.

Reg-test:
  * reg_tests/CTestList.cmake gains one ff_regression() line for
    LESinflow_TerrainSlice. r-test submodule bumped to the commit
    that contains the deck (glue-codes/fast-farm/LESinflow_TerrainSlice/)
    together with two small hand-authored source files:
      - mountain.stl (ASCII, 4-facet pyramid centred on the turbine)
      - hub_points.txt (9-point CSV with header, exercises both
        delimiter paths)
  * Deck exercises both SourceType paths, both OffsetNormal modes
    (per-facet default vs explicit vector), and the ragged-offsets
    packed layout (3 offsets on one slice, 1 on the other).

Verified with:
  cd build-docker-single-debug && ctest -L fastfarm --output-on-failure
  # 6 pass (LESinflow, LESinflow_ArbSlice, LESinflow_TerrainSlice,
  #         AMReX, ModAmb_3, MD_Shared)
  # 3 fail on the pre-existing single-precision-vs-double-precision
  # tolerance drift (TSinflow, TSinflow_curl, TSinflowADskSED) -
  # unchanged from Phase 1/2 and will pass in build-docker-double
  # per §10.2 of the plan.

Sample output for the mountain slice at t=0 verified: 3*(3*4)=36
points per timestep, coordinates match hand-computed vertex+offset*normal
for each facet.  Series sidecar is clean (no duplicate at t=0 thanks
to the LastTerrainSliceN scalar guard).

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ocks

Adds the user-facing documentation for the two new VISUALIZATION
sub-sections introduced by commits 77abd9298 (Feature 2, axis-aligned
extent-controlled planar sampling) and 8037df4ea (Feature 3,
terrain-following point-cloud sampling) on branch f/FF_sliceOutput.

Touched files:

* docs/source/user/fast.farm/InputFiles.rst
    Two new subsections after the classic '--- VISUALIZATION ---'
    block: 'Axis-Aligned Plane Slices (extent-controlled)' and
    'Terrain-Following Sampling', each with the block grammar as a
    definition list (avoids RST simple-table alignment pitfalls),
    one worked example, and the semantics of the point-cloud file
    format (whitespace/CSV, comments, optional header row, no de-dup).

* docs/source/user/fast.farm/OutputFiles.rst
    Two new subsections describing the '.vts' + '.vts.series' and
    '.vtp' + '.vtp.series' outputs, the NaN-masking convention for
    out-of-domain nodes, and the independent WrPlaneDT / WrTerrainDT
    sampling rates.

* docs/source/user/fast.farm/ModelGuidance.rst
    New 'Choosing a Visualization Slice Output' section that guides
    users among the three paths (classic NOutDisWind*, NumPlaneSlices,
    NumTerrainSlices) and their independent sampling rates.

* docs/source/user/fast.farm/FutureWork.rst
    New bullet on extending NumPlaneSlices to accept arbitrary
    non-axis-aligned planes, referencing issue OpenFAST#2383 - so the fatal
    error text emitted by ValidatePlaneSlices for off-axis normals has
    a matching documented forward pointer.

* docs/source/user/api_change.rst
    New v5.0.x-to-next-release entry describing the two new optional
    VISUALIZATION sub-sections, cross-referenced to the new subsections
    in InputFiles.rst and the guidance section in ModelGuidance.rst.

Verified: docutils parses every touched file with only the pre-existing
Sphinx-role warnings (numref, cite, eq, download, ref) - my additions
introduce no new RST syntax errors.  Full Sphinx build wasn't run in
this container (Sphinx 5.3 in the Python 3.13 env needs an imghdr
compat shim that isn't installed), but that's an environment issue
unrelated to these edits.

Removed the development plan for these features.

Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Claude <noreply@anthropic.com>
@andrew-platt

Copy link
Copy Markdown
Collaborator Author

Note: as posted there are a lot of merge conflicts. This was developed off of branch b/FF_wake_start_noise which has not been merged to rc-5.0.1 release at the time of posting.

@andrew-platt
andrew-platt marked this pull request as draft July 17, 2026 06:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request extends FAST.Farm’s visualization/output capabilities by adding new slice-output options (extent-controlled axis-aligned planar sampling and terrain-following point-cloud sampling) with ParaView-friendly XML VTK outputs and streamed .series time-index sidecars, while also updating wake/VTK infrastructure and regression coverage to support and validate the new outputs.

Changes:

  • Adds new FAST.Farm VISUALIZATION blocks for NumPlaneSlices and NumTerrainSlices, including parsing, runtime state, and XML VTK output (.vts / .vtp) with streamed .series sidecars.
  • Introduces/updates AWAE VTK writer infrastructure (new AWAE_vtk.f90, new XML writers and .series helpers in AWAE_IO.f90, updated generated types/registry).
  • Updates FAST.Farm regression test registration and documentation for the new input/output features.

Reviewed changes

Copilot reviewed 20 out of 24 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
vs-build/modules/AWAE.vfproj Adds AWAE_vtk.f90 to the Visual Fortran project sources.
reg_tests/CTestList.cmake Registers two new FAST.Farm regression tests for the new slice-output features.
modules/wakedynamics/src/WakeDynamics.f90 Updates wake-plane lifecycle/merging/removal logic and initializes additional state fields.
modules/wakedynamics/src/WakeDynamics_Types.f90 Adds LowResBounds to init/parameter types and updates pack/unpack/copy routines.
modules/wakedynamics/src/WakeDynamics_Registry.txt Declares LowResBounds in the WakeDynamics registry for code generation.
modules/awae/tests/test_AMReX_reader.f90 Adjusts test scalar kinds/constants for time and grid calculations.
modules/awae/src/AWAE_vtk.f90 Adds AWAE VTK output module (wake plane files + .vtk.series generation).
modules/awae/src/AWAE_Types.f90 Regenerated AWAE types/pack/unpack to include new slice feature fields and updated VTK root naming.
modules/awae/src/AWAE_Registry.txt Extends AWAE registry with new slice inputs/runtime state and clarifies misc/param field semantics.
modules/awae/src/AWAE_IO.f90 Adds XML VTK writers (.vtp, .vts) and streamed .series helpers; updates disturbed-wind output root naming.
modules/awae/CMakeLists.txt Adds AWAE_vtk.f90 to the AWAE static library build.
glue-codes/fast-farm/src/FAST_Farm_Subs.f90 Adjusts MaxNumPlanes logic and passes low-res domain bounds into WakeDynamics init input.
glue-codes/fast-farm/src/FAST_Farm_IO.f90 Adds optional parsing blocks for plane slices and terrain slices (backspace-and-return behavior for legacy decks).
docs/source/user/fast.farm/OutputFiles.rst Documents new .vts/.vtp outputs and .series sidecars for the added slice features.
docs/source/user/fast.farm/ModelGuidance.rst Adds guidance on choosing between classic slices, extent-controlled planes, and terrain-following point clouds.
docs/source/user/fast.farm/InputFiles.rst Documents the new VISUALIZATION sub-block grammars and point-cloud file format.
docs/source/user/fast.farm/FutureWork.rst Notes future extension toward arbitrary-orientation planar sampling.
docs/source/user/api_change.rst Records the new optional FAST.Farm input blocks as an API/input change.
.gitignore Ignores common devcontainer/VSCodedocker workspace files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +322 to +326
if (m%WakeVTK_StartN(np_wp, nt_wp) > out_idx) then
EntryName = trim(p%OutFileFFvtkWakeNullData)
else
EntryName = trim(baseName)//"."//trim(VTKprefix)//"."//trim(TstrOut)//".vtk"
endif
Comment on lines +1606 to +1615
! The row has (up to) TWO double-quoted strings: the slice name (first)
! and the filename (second/last). Extract in order.
call ExtractQuoted( work, sliceName )
if (len_trim(sliceName) == 0) then
call ExtractBareToken( work, sliceName )
end if
if (len_trim(sliceName) == 0) then
err = ErrID_Fatal; msg = 'Missing slice name.'
return
end if
Comment on lines +1651 to +1656
tokCount = tokCount + 1
offList(tokCount) = rval
if (tokCount > size(offList)) then
err = ErrID_Fatal; msg = 'Too many offsets on a single line.'
return
end if
write(Un,'(A)') '<!-- '//trim(descr)//' -->'
write(Un,'(A)') '<VTKFile type="PolyData" version="0.1" byte_order="LittleEndian">'
write(Un,'(A)') ' <PolyData>'
write(Un,'(A,I0,A)') ' <Piece NumberOfPoints="', N, '" NumberOfVerts="1" NumberOfLines="0" NumberOfStrips="0" NumberOfPolys="0">'
Comment on lines +167 to +169
write(Un,'(A)') ' <DataArray type="Int64" Name="offsets" format="ascii">'
write(Un,'(1X,I0)') N
write(Un,'(A)') ' </DataArray>'
@andrew-platt

andrew-platt commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Todo (@andrew-platt)

  • manually check diffs commit-by-commit for sanity using my knowledge.

Fixes compile/lint warnings from type and argument shadowing on the
rebased branch:
- Rename local dummy argument 'name' to 'nameOut' in FAST_Farm_IO.f90
  helpers (ExtractQuotedName, ExtractQuoted) to avoid masking parent scope variables.
- Rename local variable 'nan' to 'nan_val' in AWAE.f90 handlers
  (EmitPlaneSlices, EmitTerrainSlices) to resolve shadowing of the module-level constant from NWTC_Library.

Co-authored-by: Google Gemini 3.5 Flash <gemini@google.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
@jjonkman

Copy link
Copy Markdown
Collaborator

Thanks for adding these new visualization features! Just a few comments:

  • I would think WrPlaneDT and WrTerrainDT should DEFAULT back to DT_Low or DT_Low-VTK, just like WrDisDT does. Basing the DEFAULTs of the new visualization time step outputs on the old WrDisDT is confusing, especially given that I assume you'd want to use one or the other visualization features, not both at the same time.
  • For the new plane slices, the normal must be X, Y or Z; would it be better simply to specify that directly, rather than as a normal vector? Or perhaps this is a placeholder for a future generic plane slice? If the normal is kept, must it be normalized to unity or will the normalization be calculated internally?
  • For the new plane slices, are you linearly interpolating the data between planes if the selected plane is not aligned with the low-resolution grid? (This is how the existing plane slice visualization works.)
  • For the new terrain slices, I'm not sure I understand how the STL option is useful. I would think it would be more useful if the STL file is specified as Z-elevations across an XY plane of points, that is Z = Function(X,Y), and then Z-offsets would be user specified such that the outputs would be located at the set of points defined by Function(X,Y) + Offset. This would make the most sense if you wanted to output a terrain-following flow field rather than an XY slice at a fixed Z elevation. This option wouldn't work if the STL file has mulitple Z-elevations at the same X,Y point, but I doubt we'll consider terrain that complex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants