FF: Curl/Cartesian wake fixes and AWAE VTK wake-plane output - #3368
Open
andrew-platt wants to merge 61 commits into
Open
FF: Curl/Cartesian wake fixes and AWAE VTK wake-plane output#3368andrew-platt wants to merge 61 commits into
andrew-platt wants to merge 61 commits into
Conversation
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>
Replace the adjacent-only OOB merge loop with a collect-then-merge approach that checks all pairs of out-of-bounds planes for proximity. - Collect OOB plane indices into a scratch array (O(N) scan). - All-pairs proximity check among OOB planes only (O(nOOB^2), typically nOOB is very small). - Work backwards from highest index so ShiftWakePlanesDown does not invalidate lower-index merge targets. - Add AdjustOobIndices subroutine to maintain the OOB index list after each merge (remove entry, decrement indices above the dropped plane). This handles non-sequential OOB planes that arise when plane passing is allowed (e.g. drones or variable wind conditions). Co-authored-by: GitHub Copilot <copilot@github.com> Co-authored-by: Claude (Anthropic) <claude@anthropic.com>
Create modules/wakedynamics/tests/ following the same pattern used by InflowWind and the other modules (a wakedynamics_utest.F90 driver plus one file per test suite, registered in unit_tests/CMakeLists.txt as the wakedynamics_utest target). Move the two existing WD_TEST_* subroutines out of WakeDynamics.f90 and into proper testdrive unit tests: - WD_TEST_AddVelocityCurl -> test_addvelocitycurl.F90 (test_add_velocity_curl) - WD_TEST_Axi2Cart -> test_axisymmetric2cartesian.F90 (test_axisymmetric2cartesian_vel) These were previously dead code: public subroutines using print/STOP for assertions, never invoked from anywhere in the codebase. They are now real, runnable tests using testdrive's check(), matching the convention already used by inflowwind_utest, aerodyn_utest, etc. Since AddVelocityCurl and Axisymmetric2CartesianVel are private module procedures, expose them directly as public (replacing the WD_TEST_* public declarations) so the new test suites can call them, following the same pattern used by IfW_FlowField's public CalcAccel routines. Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ly out-of-bounds WD_UpdateStates declared its local `oobIdx` array as `0:MaxNumPlanes-1` (size MaxNumPlanes), but the OOB-collection loop can legitimately need to record up to `MaxNumPlanes` out-of-bounds plane indices (one entry per existing plane, if every plane simultaneously falls outside p%LowResBounds). This wrote one element past the end of the array. Fix: widen `oobIdx` to `0:MaxNumPlanes` (one extra unused slot). While adding a test that forces every plane out of bounds at once (exercising multiple simultaneous plane merges), a second, related bug surfaced in the OOB-pair merge loop: after AdjustOobIndices() shrinks nOOB and removes the merged entry, `iOOB` was never re-clamped, so a later loop pass could index oobIdx() with a stale value beyond the now-smaller nOOB, feeding a bad plane index into MergeWakePlanes / ShiftWakePlanesDown and causing an out-of-bounds write there too. Fixed by clamping `iOOB = min(iOOB, nOOB)` after AdjustOobIndices(). Add modules/wakedynamics/tests/test_wd_oobidx.F90: a regression test that initializes WakeDynamics (Polar wake model) with a small MaxNumPlanes, grows the plane count to the cap under steady inflow, then tightens p%LowResBounds so every existing plane is simultaneously out of bounds, and asserts WD_UpdateStates completes without error. Confirmed this test crashes with a stack array-bounds error before each of the two fixes above, and passes after both. Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Previously, once a turbine's tracked wake-plane count reached p%MaxNumPlanes, WD_UpdateStates re-issued the "exceeded the allowed number" ErrID_Warn on every subsequent timestep for the rest of the simulation (the clamp condition recurs every call once the plane count saturates at the cap), flooding logs with an unbounded number of identical warnings instead of a single, one-time notice. Add OtherState%MaxPlanesWarned (WakeDynamics_Registry.txt), initialized to .false. in WD_Init alongside the existing OtherState%firstPass one-time flag. WD_UpdateStates now gates the SetErrStat(ErrID_Warn, ...) call behind this flag and sets it once the warning has fired; the NumPlanes clamp itself remains unconditional every call. Since each turbine has its own WD module instance (and thus its own OtherState), a single scalar flag naturally yields "once per turbine" without needing per-turbine indexing. Add modules/wakedynamics/tests/test_wd_maxplanes_warning.F90: drives WD_UpdateStates well past a small MaxNumPlanes and asserts the warning message appears exactly once across all calls, while NumPlanes stays clamped at the cap throughout. Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The high-res search requested NumDT*n_high_low-1 time slices, but the largest high-res index actually read by AWAE_UpdateStates is (NumDT-1)*n_high_low (i_hl is forced to 0 on the final low-res step), i.e. (NumDT-1)*n_high_low+1 slices. For n_high_low=4 this over-requested by 2, so a run whose LES data exactly covered TMax was rejected for missing two directories it never reads. For n_high_low=1 -- legal, since DT_High need only divide DT_Low -- it under-requested by one, which is harmless today but becomes an out-of-bounds read once the directory index is looked up from a table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
amrex_find_subvols_c compared the directory index suffix against DirStartIndex as a string. AMReX pads plotfile indices to a *minimum* width and widens beyond it as needed, so once a run passes 99999 steps a six-digit index sorts before a five-digit one: "100030" < "27150". Every such directory was silently skipped and the run failed with a misleading count mismatch pointing at data that was present on disk. Parse the suffix to an integer and compare numerically. While here, harden the extraction: anchor the prefix match at position 0, require the suffix be all digits, and use stoll behind a try/catch. Previously a sibling directory such as ffboxes_1_00010_bak threw std::invalid_argument across an extern "C" boundary, terminating the process with no error message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReadWindAMReX formatted the directory index with a fixed-width I<DirIndexLen> edit descriptor, where DirIndexLen comes from len_trim(DirStartIndex). AMReX treats that width as a minimum and widens it as the step counter grows, so a run starting at "27150" and reaching index 100030 wrote '*****' and failed to open the directory. Format with I0 and left-pad to DirIndexLen, so the width matches amrex::Concatenate for any index magnitude. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add DirIndexLow(:) and DirIndexHigh(:) to AWAE_ParameterType, to hold the plotfile directory index for each low- and high-resolution time step. These replace the constant-stride assumption encoded in DirIndexDeltaLow/High, which cannot represent LES output written with a varying solver time step. Unused in this commit; populated and consumed in the following ones. The old scalars are left in place so this commit builds standalone. AWAE_Types.f90 regenerated with -DGENERATE_TYPES=ON. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… stride amrex_find_subvols_c scanned the sub-volume directories, read each plotfile header, validated that its time was a multiple of dt -- and then threw the result away, returning only a first index and a single stride. That stride was then required to be uniform across every directory. The stride is an artifact of how the LES advanced its solver, not a property of the data. AMR-Wind run with time.initial_dt = 0.025 until delay_time, then fixed_dt = 0.05, writes boxes every 0.1 s throughout -- uniform in time to 1e-10 s -- but the index stride steps from 4 to 2 at the transition, and the run was rejected. Return the full time step -> directory index table instead. Each directory is assigned the step round((header_time - start_time)/dt) and must land within tolerance of it; a step claimed by no directory or by two is fatal. This subsumes the old fmod filter, the count check and the stride check, and is strictly stronger than all three: missing files and duplicates are now detected exactly, and the residual test still skips deliberately decimated output. The matching tolerance is max(1e-6, 1e-9*(|t0|+|t|)). The error being absorbed is the drift a solver accumulates by summing its time step, which grows with absolute simulated time -- a precursor restarted at t = 3e4 s carries far more of it than one starting at zero -- so a fixed absolute tolerance is the wrong shape. The floor preserves the previous behavior for runs starting near t = 0. High-resolution sub-volumes are now compared to sub-volume 1 element by element rather than by stride alone, which catches a sub-volume whose whole sequence is offset from its siblings -- previously accepted, and silently supplying each turbine a different instant in time. ReadWindAMReX still builds names arithmetically in this commit; it is switched to the table next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ting it ReadWindAMReX rebuilt the directory name at every read as DirStartNum + Delta*n, which is only correct when the LES wrote its plotfiles at a constant step index stride. Read the index out of the table built during initialization instead. This is what makes variable-time-step LES output work end to end: with the old arithmetic, data whose stride changed part way through would resolve to a directory holding a *different* simulation time -- for a 4->2 stride change 1000 steps in, step 1001 wants t+100.1 s but the formula lands on t+100.2 s, and the error grows from there. Silently, with no failure. Also guard the lookup: an out-of-range step is fatal rather than clamped, since clamping would freeze the inflow for the tail of a run instead of reporting it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DirStartNum, DirIndexDeltaLow and DirIndexDeltaHigh existed only to reconstruct directory names arithmetically. With the index table in place they have no remaining consumer. AWAE_Types.f90 regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five tests covering the behavior the preceding commits changed:
variable_stride stride 4 -> 2 with a uniform 0.1 s output interval, using
header times that carry the drift seen in real data
wide_index five-digit start index followed by six-digit indices
missing_step a gap must name the step, not just a count
duplicate_step two directories on the same time must be rejected
out_of_tolerance a near miss must be reported as such
variable_stride and wide_index fail on rc-5.0.1 and pass here; the other three
cover conditions the old code either mis-reported or accepted silently.
Fixtures are copies of the existing plotfile directories with only the Header
time and level_steps rewritten -- 130 KB total. The binary Level_0 data is never
opened by amrex_find_subvols.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two changes to the search, both cost-only -- the directories selected are identical. Walk candidates in ascending index order and stop at the first one past the requested window. Simulation time rises with the step counter, so nothing after it can qualify. Previously the search read a header for every directory matching the prefix no matter how short the FAST.Farm run: a 100 s window over a precursor that had written 2200 low-res boxes read all 2200. Read the plotfile Header directly instead of constructing a PlotFileData, which also opens Level_0/Cell_H and builds a DistributionMapping to obtain a number already present in the text. Grid validation is unchanged and still runs on every directory -- it just takes its inputs from a cheaper source. The text parse is validated against amrex_read_header_c on the start directory before being trusted, and falls back to it on any parse failure, so a plotfile whose layout does not match is handled rather than mis-read. Measured on a 65-turbine dataset: the low-res search for a 100 s window went from ~7.6 minutes to 0.85 s. No OpenMP. The plan allowed for parallelising the scan, but after these two changes the measurement no longer justifies the complexity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The AMReX section stated that the directory for time step n is DirStartNum + n * Delta_index. That is no longer how the reader works, and describing a constant stride as part of the file-naming contract is what led a user to conclude their valid variable-time-step data was malformed. Replace with the actual rule -- directories are matched by the simulation time in their Header -- and state explicitly that the index stride may vary. Document the matching tolerance, the exactly-one-directory-per-time-step requirement, and the cross-sub-volume time consistency check. No input file changes: DirStartIndex keeps its meaning and no field is added, removed, or reinterpreted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
amrex_read_data_c called PlotFileData::get(level, varname) once per velocity component, which re-reads the whole level each time. The cost of a read is set by the number of boxes rather than the volume of data, so three passes cost three times as much. Read every component at once with get(level) and index the component off the Array4 instead. Measured against a low-resolution sub-volume written with 93456 boxes (a 528x472x24 grid at chunk_size_vec = 8 8 1): 63 s -> 16.5 s. On the high-resolution boxes, where per-box overhead dominates a 48 KB array, 55 ms -> 7 ms; that is the one that matters in aggregate, since FAST.Farm reads NumTurbines * (n_high_low + 1) high-resolution boxes per low-resolution step against a single low-resolution one. Output is unchanged: dumping both readers over a real 68 MB low-resolution box and a high-resolution box gives byte-identical results. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReadWindAMReX is where the 0-based directory index table is consumed and where
the index suffix is formatted, and neither had direct coverage. Four cases:
lookup step 1 of table [0, 6, 12] reads directory 00006, checked
against a direct read of that directory rather than just the
absence of an error
out_of_range a step past the table is fatal and names the available range;
it must never be clamped, which would silently freeze the
inflow for the rest of the run
wide_index DirIndexLen of five with index 100000 widens the suffix to six
digits and reads the right box (the subvolwide fixtures)
no_table an unpopulated table is reported as such rather than looked
up as zeros
Writing wide_index showed that pointing amrex_read_data at a directory that
does not exist aborts the process rather than returning an error: amrex::Error
calls abort() instead of throwing, so the try/catch in amrex_read_data_c never
fires. That is pre-existing and left alone here; the test was reworked to read
an existing fixture instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With AMREX_READER=ON and BUILD_TESTING=ON the amrex_reader_utest target failed
to link:
ld: crt1.o: in function `_start': undefined reference to `main'
Both of its sources are Fortran and its entry point is a Fortran program, but
awaelib transitively pulls in the C++ awaelib_c, so CMake resolves the link
language to CXX and the C++ driver's crt1.o then looks for a C main. Pin the
linker language to Fortran and pull in the C++ runtime explicitly with -cxxlib,
which is what the Fortran driver needs to resolve the AMReX and awaelib_c
symbols.
Every other unit test target in this file links with the Fortran driver
already; this one only differed because of the C++ dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-cxxlib is an Intel Fortran option; gfortran rejects it, so any GCC build with AMREX_READER=ON and BUILD_TESTING=ON failed at link. It is also unnecessary: with the link language pinned to Fortran the target links cleanly with ifx without it, which is how glue-codes/fast-farm handles the same awaelib -> awaelib_c C++ dependency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…laimed The scan walked candidates in ascending index order and stopped at the first directory whose header time fell past the requested window, on the assumption that time rises with the step counter. Within one run it does. But leftovers from an earlier run with a different time step share the index base and carry later times on lower indices, so a single stale directory could end the walk before the valid data that sorts after it -- and the run then failed with a spurious "no sub-volume directory was found for time step N" pointing the user at data that was on disk. A killed run restarted with a smaller initial_dt is exactly this case. Keep the early stop, but only once every step has been claimed. While any step is still missing, index order is not trusted to imply time order and the walk continues; a missing step is reported only after the full scan. Clean directories keep the same cost as before. The new subvolstale fixture has such a leftover (index 00002 at t = 5.0) ahead of the directories holding steps 1 and 2. It fails on the previous commit with the message above and passes here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…range The text-parse fast path accepted any ncomp >= 1, and amrex_read_data_c had no component check at all, so on the fast path only the starting directory was ever validated for ncomp == 3. A directory with fewer components then reached fab(i, j, k, ivar) with ivar past the FAB's component count: an out-of-bounds read yielding garbage velocities instead of an error. The text parser now returns false for anything but three components (which sends that directory to amrex_read_header_c, where it is reported), and amrex_read_data_c checks nComp() before reading. Also: - The parser cross-checks level_steps against the directory index it was asked about; a mismatch means the name does not describe the contents, so fall back to the authoritative reader rather than trust the text. - parse_dir_index deliberately uses stoll to cope with wide indices, but the result was narrowed to int unchecked. Both narrowing sites now fail with a clear message past the 32-bit range instead of wrapping. - The comment claiming the parser is safe to call from several threads is dropped; the AMReX read path is serial and nothing here relies on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
amrex_find_subvols set ErrStat = ErrID_None only inside the ENABLE_AMREX_LIB branch, so the #else branch called SetErrStat on undefined intent(out) dummies (SetErrStat does ErrStat = MAX(ErrStat, ...)). The two older shims never initialized them in either branch. Initialize in all three before the #ifdef. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The text-parse fast path only turns on when it agrees with amrex_read_header_c on the starting directory, and none of the existing fixtures satisfy that: they are hand-built subsets whose Header domain box (256x256x128) is much larger than the box array in Cell_H (3x4x5), so every test took the slow path and the parser had no coverage. Real Kynema/AMR-Wind sub-volume output is tiled -- one FAB spanning the domain box -- and the fast path does engage on it in production, which is where the measured speedup came from. Add a fixture with that real layout: three consecutive high-resolution snapshots as written by the sampler (headers, single FAB, three masked velocity components, 0.1 s apart), with the FAB payload replaced by a known field u = 1+i, v = 2+j, w = 3+k so the read can be checked exactly rather than for plausibility. Expose the parser to Fortran as amrex_parse_header_text and add four tests: the search and the read on the tiled fixture, agreement between the text parser and amrex_read_header on it, and the disagreement on the hand-built fixture that explains why those fall back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The AMReX section said every step 0..NumDT-1 needs a directory. That is the low-resolution count; each high-resolution domain needs steps 0 through (NumDT-1)*(DT_Low/DT_High). Also state that directories between required steps are ignored, so the LES may write sub-volumes more often than FAST.Farm reads them, provided the FAST.Farm step is a multiple of the output interval. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes for AMReX data handling for FAST.Farm
Follow-up to #33. Four issues found reviewing that work. Match the sub-volume prefix on the entry name, not the whole path. The scan was anchored with rfind(prefix, 0), but directory_iterator over "." hands back entries as "./name", so a WindDirPrefix carrying no directory of its own matched nothing beyond the starting directory and the run died with "no sub-volume directory was found for time step 1" with every directory present. Not reachable through FAST.Farm, which prepends PriPath (at least "./") to a relative WindDirPrefix, but it is reachable for any direct caller of amrex_find_subvols -- the unit tests among them. Check the grid in amrex_read_data against the caller's array. The reader writes the destination by grid index and had no idea how big it was, so a plotfile describing a larger grid wrote past the end of it. The sub-volume search does check this at initialization, but it may have taken the Header text fast path, which reads the domain box while the read takes the union of the box array -- the two agree on real sampler output and are only verified to agree on the starting directory. Also require the boxes to tile the grid: they are disjoint, so summing their volumes settles it, and anything less leaves part of the destination holding the previous step. Count claimed steps instead of rescanning the table for each directory past the window. The scan stops at the first such directory once the table is complete, so this cost nothing on a good dataset -- but when a step is genuinely missing it never stops, making the walk O(directories x steps) on exactly the path that was supposed to report the problem quickly. Say which tolerance a near miss was measured against. The tolerance grows with simulated time, so the value quoted in the message was not quite the one applied to the directory being reported. Also soften the "exactly one directory per step" claim in the docs and the routine comment: the walk stops early once the table is complete, so a stale duplicate sorting after the first directory past the window is never read. The check is best effort, not exhaustive. Tests: two added, both fail without the corresponding fix. All 21 AMReX reader unit tests pass, as do the other six utest suites, and FAST.Farm links. Assisted-by: Claude Code:claude-opus-5[1m] [Bash] [WebFetch]
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.
Ready to merge
Feature or improvement description
This PR addresses multiple bugs and adds VTK wake-plane visualization capabilities for FAST.Farm's Curl/Cartesian wake models in the AWAE and WakeDynamics modules.
Bug fixes:
Vx_wake2initialization at T=0 for Curl/Cartesian wake models — thefirstPassbranch inWD_CalcOutputcomputedy%Vx_wake(polar) but never populatedy%Vx_wake2, and the unconditional taper block overwrote it with zeros (update the solution before the convergence criteria control flow #32 taper fix also merged).numDFulltonumDBufffor curled wake.New VTK wake-plane visualization:
AWAE_vtkmodule (AWAE_vtk.f90), replacing inline routines inAWAE_CalcOutput.Write_Planes_WireFrame,PlaneCornershelper).Write_Planes_Data,Write_WakePlane_Data_File)..vtk.seriesfile writers for wireframes, wake-plane data, and disturbed-wind slices for ParaView time-series loading.Other changes:
MaxNumPlanescalculation for Cartesian/Curl models with larger safety factors..gitignorefor VS Code and Docker build configs.Impacted areas of the software
modules/awae/— NewAWAE_vtk.f90module; changes toAWAE.f90,AWAE_IO.f90,AWAE_Registry.txt,AWAE_Types.f90,CMakeLists.txtmodules/wakedynamics/—WakeDynamics.f90(initialization, taper, edge update fixes)glue-codes/fast-farm/—FAST_Farm_Subs.f90(MaxNumPlanes calculation)docs/— FAST.Farm plan (Rev26), wake extent documentationvs-build/modules/—AWAE.vfprojupdated for new source fileAdditional supporting information
The Curl and Cartesian wake models had several initialization and update issues that caused incorrect wake deficit values at startup and incorrect edge values during simulation. The VTK output refactoring moves wake visualization code into a dedicated module for maintainability and adds per-plane data output and ParaView
.vtk.seriesindex files for easier post-processing.Generative AI usage
Substantial portions of the VTK output code and bug fix analysis were developed with AI assistance:
This PR description was generated with AI assistance:
Test results, if applicable
No test result changes. No r-test updates required.