Output along ship tracks / mooring curtains#925
Merged
Conversation
63b4c1f to
2935b62
Compare
Emit a 2D (depth x along-track) NetCDF curtain of any 3D node-based
FESOM field along a user-supplied polyline, at a user-supplied cadence.
Multiple tracks can run simultaneously and write to separate files.
Config (set in namelist.io's &nml_tracks block, or via XIOS XML
overrides in context_fesom.xml for XIOS-coupled runs):
ltracks master switch (default off)
track_files semicolon-separated list of CSV paths
track_vars semicolon-separated tracer per track (temp|salt)
track_names semicolon-separated names (default track1..N)
track_resolution_km densification spacing (default 5 km)
track_output_freq shared XIOS cadence ('1h', '3h', '1d', '1mo', ...)
For each track, io_tracks_register_xios runs the pipeline:
1. parse the CSV polyline vertices on rank 0
2. slerp each great-circle segment at track_resolution_km
3. MPI_MINLOC nearest-neighbour: per rank scans strictly-owned mesh
nodes; reduce picks the global owner with tie-break by lowest rank
4. collapse consecutive duplicate gids
5. build a sparse per-rank list (sorted by global position)
6. register a runtime XIOS domain (track_<name>), grid
(grid_3d_track_<name>), field (<var>_track_<name>) and a <file>
inside the shared "fesom_tracks" file_group
io_tracks_send packs the configured tracer at owned mesh nodes and
calls xios_send_field every timestep; XIOS gates the actual write at
freq_op. Output file pattern: <var>_track_<name>.fesom_<year>-<year>.nc
with shape (time, cell, nz).
Files:
src/io_tracks.F90 new module
src/io_xios.F90 xios_getvar() for the six config variables
plus io_tracks_register_xios() call before
xios_close_context_definition
src/io_meandata.F90 &nml_tracks namelist + ltracks added to
&nml_general
src/fesom_module.F90 io_tracks_send() call in the time loop
2935b62 to
a048c90
Compare
Collaborator
Author
|
Putting this on draft while reworking to be in line with tripyview logic. |
Collaborator
Author
Replaces the snap-to-nearest-mesh-node curtain sampler with a
geometry-aware pipeline ported from tripyview/sub_transect.py. For each
polyline sub-segment we find the mesh edges it crosses, then interpolate
each crossed edge's two endpoint values via V*(1-t)+V*t at the actual
intersection parameter t. Output samples lie on the line — no more
mesh-resolution-dependent zig-zag.
Changes:
* src/mod_tracks_geometry.F90 (new, ~750 LOC pure Fortran). Public
transect_t + analyse_transect: bbox + polar widening, signed-distance
line-edge intersection, alternating up/down-section path with the
triangle's edge_cross_dxdy used directly (1:2 LEFT, 3:4 RIGHT), and
cumulative great-circle distance. No MPI / XIOS deps.
* src/io_tracks.F90 rewrite around the new geometry kernel:
- rank 0 calls gather_nod/_elem/_edge to assemble the global mesh
once at init (same primitives io_mesh_info.F90 uses);
- rank 0 runs analyse_transect; result broadcast to all ranks;
- per-rank lid_n1/lid_n2 strict-own lookups built via a direct g2l
table from myList_nod2D(1:myDim_nod2D);
- sampling kernel: weighted contributions + weight-sum buffers
(both MPI_Allreduced); rescale-by-weight so wet/dry edges fall
back to the surviving endpoint instead of a half-amplitude
partial sum; NaN where neither endpoint contributes;
- per-level wet/dry mask via mesh%nlevels_nod2D(lid)-1;
- round-robin m -> rank assignment for XIOS write ownership.
* src/io_meandata.F90 / src/io_xios.F90: drop the now-meaningless
track_resolution_km knob from the namelist binding and XML override
block (sampling cadence is set by the mesh, not the user).
* src/fesom_module.F90: pass mesh to io_tracks_send (needed for the
per-level wet/dry mask).
io_xios_init's partit is now intent(inout) because the new gather path
requires it; matches the existing io_mesh_info pattern.
Verified by a 1-day CORE2 smoke run across four tracks (Atlantic->Fram
plus the three Polarstern MOSAiC ice-camp windows). Crossed-edge counts
log cleanly (242 / 187 / 60 / 24); curtains have no streaky-bottom
artifact from partial-wet edges.
Collaborator
Author
Collaborator
Author
Step 3 of the geometry-aware redesign: add support for u, v, and a
volume-transport diagnostic 'vflx' on the path-side (P-sized) XIOS
domain. Both temp/salt at edge crossings (node-based, M-sized) and u/v
at path triangles (element-based, P-sized) can now be requested in the
same track_vars list; per-track domain dispatch picks the right grid.
Sampling kernel (send_one_track_elem):
* Reads dynamics%uv at strict-owned path elements (built via a
global->local hash from myInd_elem2D_shrinked).
* Rotates (u, v) from the mesh frame to geographic at each path
centroid via vector_r2g, gated on rotated_grid .or. force_rotation.
* Per-level wet/dry mask via mesh%nlevels(eid) - 1.
* MPI_Allreduce on both value and contribution-count buffers; element
ownership is unique, so the count is 0/1 and post-reduce mask sets
NaN where no rank contributed (boundary slot or dry).
vflx formula matches tripyview's calc_transect_Xtransp do_nveccs=True
(sub_transect.py:1371-1382):
Vflx_cell = (u * |path_dy| * nvec_x + v * |path_dx| * nvec_y) * helem
Cross-checked against tripyview on the same year-1 monthly-mean u/v from
this run: tripyview reports +149.27 Sv mean ACC throughflow across a
2-waypoint Drake Passage line; io_tracks vflx 3-hourly aggregated to
year-1 mean = +150.2 Sv (agreement < 1 Sv). The earlier formula
"u*dy + v*dx" without the section-normal projection gave -20 Sv, both
wrong sign and wrong magnitude; that was a real bug in this commit's
predecessor, now fixed.
Other changes:
* track_t extended with path-side state: P, path_ei, path_dx/dy,
path_lon/lat, path_nvec_x/y, lid_elem, nL_p, my_p, i_index_p.
* track_t carries a var_kind flag (1 = node, 2 = elem) plus uv_comp
(1 = u, 2 = v, 3 = vflx) to select the sampler and output domain.
* broadcast_transect_min now also distributes path_ei, path_dx/dy,
path_centroid_xy and path_nvec_cs for element-based tracks.
* register_xios_objects_for_track dispatches: node-based ->
track_<name> domain at M cells; element-based -> path_<name>
domain at P cells; ni_glo and i_index sized accordingly.
* io_tracks_send threads dynamics%uv through; fesom_module updated
accordingly.
* 'u', 'v', 'vflx' accepted as track_vars values alongside temp/salt.
Tested with a 7-track config (temp/u/v on a transect plus 3 mosaic temp
plus vflx on Drake) across CORE2 LR. Element-based u/v curtain shows
the expected EGC/NwAC signatures on the Atlantic->Fram section. Year-1
Drake ACC = +150 Sv with the right 30-day spin-up plateau.
Performance: the path-side machinery doubles per-track broadcast cost
(~negligible, <100 kB extra) and adds one extra MPI_Allreduce per send.
Node-based temp/salt tracks pay nothing for the new code.
Collaborator
Author
Status of the geometry-aware redesignWhat works
What doesn't (yet)
|
Extend the element-based path-side sampler with two derived transport
diagnostics:
* hflx -> per-cell heat transport in PW, as
vflx_cell * T_at_element * rho0 * cp * 1e-15
* sflx -> per-cell salinity transport in kg/s, as
vflx_cell * S_at_element * rho0 / 1000
where vflx_cell is the same volume-transport formula already used by
the vflx variant (matching tripyview's do_nveccs=True). T/S at the
element centroid are averaged from the three vertex node values
(tracers%data(1) or (2)); halo node tracers are kept current by FESOM's
exchange, so the average works for any path element this rank strict-
owns regardless of which side of the partition its corner nodes lie on.
Wired through:
* tracers passed into send_one_track_elem alongside dynamics/mesh
* 'hflx', 'sflx' parsed as var_kind=2 with uv_comp=4, 5
* tracer_idx=1 for hflx, 2 for sflx; same vector_r2g rotation and
wet/dry mask as vflx
* supported track_vars list updated in the parser error message
No extra MPI traffic vs vflx (one Allreduce per send). A single Drake
Passage CSV with track_vars='vflx;hflx;sflx' simultaneously gives
volume, heat and salinity transport at the same path.
abf9c42 to
e863155
Compare
…pts stub
Add or tighten subroutine headers across io_tracks.F90 and
mod_tracks_geometry.F90 following the convention that lines up with
MODFLOW / CESM / JULES / fortran-lang style guides:
* every routine carries a short purpose line
* public-API routines get the longer treatment with units, invariants,
and side effects
* internal helpers with self-documenting argument names drop the
in/out arg listings -- the signature already says it
Remove the insert_landpts stub from mod_tracks_geometry. It was a port
target for tripyview's _do_insert_landpts (synthetic NaN slots between
two consecutive boundary edges when a polyline crosses a continent) but
was always a no-op return. The within-segment boundary case is handled
by push_ghost at build_path time; the cross-continent case is not
needed for any test transect we run. Left a one-line comment in
analyse_transect noting the missing feature.
Drop the v1-narrative leftover in the io_tracks module header.
For multi-segment polylines, the vflx / hflx / sflx formula needs the
local sub-segment normal at each path step, not the whole-transect
normal. Previously analyse_transect overwrote path_nvec_cs with a
single n_vec_tot for every path step, which gave correct results for
single-sub-segment polylines (Drake matched tripyview within < 1 Sv on
1-year monthly mean) but produced per-cell errors on bent polylines.
Four coordinated changes match tripyview's sub_transect.py line-for-line:
* calc_csect_vec gains the conditional flip from tripyview:437-440
-- going N (dy > 0) or W (dx < 0) gets the clockwise rotation,
everything else the counter-clockwise rotation. seg%n_vec was
previously computed unconditionally then never read.
* subseg_t gains a path_nvec_cs(2, P) buffer.
* build_path allocates that buffer and fills every path step inside
the sub-segment with the sub-segment's own n_vec, matching
tripyview:855-857.
* concat_subtransects vstacks the per-sub-segment buffers into
transect%path_nvec_cs, matching tripyview:386, 402.
* analyse_transect drops the n_vec_tot overwrite at the end.
The whole-transect sign-flip on path_dx / path_dy (gated on n_vec_tot)
stays, matching tripyview:875. Diagnosis and validation plan in
runtime/fesom-2.7/meltpond_diag/path_nvec_cs_bug_analysis.md.
Collaborator
Author
|
@patrickscholz Ready for review. |
patrickscholz
approved these changes
Jun 3, 2026
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.




I wrote FESOM 2D curtain output. It works by providing a list of lat lon in a csv file. FESOM the computes the greatcircle connections. It desifies those lines and for each subsection picks the nearest neighbour fesom nodal center. Here is a first example of an track / curtained i picked with 7 coordiantes:
One year of hourly test outputs at: /work/bb1469/a270092/runtime/awiesm3-develop/test_track_11/outdata/fesom
I have only tested the output via XIOS. Not without it.