Skip to content

post_process: extract s_write_field helper to collapse the 38 repeated fill->name->write->clear stanzas in s_save_data #1523

@sbryngelson

Description

@sbryngelson

s_save_data in src/post_process/m_start_up.fpp (lines 169–747, ~580 lines) writes every output variable with the same repeated scaffolding. The call s_write_variable_to_formatted_database_file(varname, t_step) appears 38 times, and nearly every one is wrapped in the identical "fill → name → write → clear" stanza:

q_sf(:,:,:) = q_cons_vf(i)%sf(x_beg:x_end, y_beg:y_end, z_beg:z_end)   ! fill the module scratch field
write (varname, '(A,I0)') 'alpha_rho', i                              ! build the name
call s_write_variable_to_formatted_database_file(varname, t_step)     ! write it
varname(:) = ' '                                                      ! clear the name buffer

That 4-line pattern repeats for densities, momenta, velocities, energies, species, stresses, B-fields, vorticity, etc. Two things make it worth tidying:

  • the q_sf(:,:,:) = …(x_beg:x_end, y_beg:y_end, z_beg:z_end) slice and the varname(:) = ' ' clear are identical every time and pure boilerplate;
  • the varname(:) = ' ' clear is easy to forget when adding a new output variable (a missed clear leaves stale characters in the name buffer).

Proposed helper

q_sf is a module-global scratch field (it's how s_write_variable_to_formatted_database_file receives the data — it reads q_sf directly, not via an argument). So a small private helper in m_start_up can wrap the common tail, with an optional source that handles the dominant "slice a scalar_field" case:

!> Fill q_sf from `src` (if given) over the interior, write it as `varname`, and clear varname.
!! @param varname  field name (set by caller); blanked on return
!! @param t_step   current time step
!! @param src      optional scalar_field to slice into q_sf; omit if q_sf is already filled
subroutine s_write_field(varname, t_step, src)
    character(LEN=name_len), intent(inout)       :: varname
    integer,                 intent(in)          :: t_step
    type(scalar_field),      intent(in), optional :: src

    if (present(src)) then
        q_sf(:,:,:) = src%sf(x_beg:x_end, y_beg:y_end, z_beg:z_end)
    end if
    call s_write_variable_to_formatted_database_file(varname, t_step)
    varname(:) = ' '
end subroutine s_write_field

Before → after

Common case (a scalar_field slice — the majority of the 38 sites), e.g. the partial densities:

! before (4 lines)
q_sf(:,:,:) = q_cons_vf(i)%sf(x_beg:x_end, y_beg:y_end, z_beg:z_end)
write (varname, '(A,I0)') 'alpha_rho', i
call s_write_variable_to_formatted_database_file(varname, t_step)
varname(:) = ' '

! after (2 lines)
write (varname, '(A,I0)') 'alpha_rho', i
call s_write_field(varname, t_step, q_cons_vf(i))

Derive-filled case (q_sf is filled in place by an s_derive_* call — ~6 sites such as vorticity, schlieren, flux limiter, specific-heat-ratio): just omit src:

! before
call s_derive_vorticity_component(i, q_prim_vf, q_sf)
write (varname, '(A,I0)') 'omega', i
call s_write_variable_to_formatted_database_file(varname, t_step)
varname(:) = ' '

! after
call s_derive_vorticity_component(i, q_prim_vf, q_sf)
write (varname, '(A,I0)') 'omega', i
call s_write_field(varname, t_step)

Variations to keep at the call site (don't over-unify)

  • Name building stays at the call site. It is genuinely variable — single names (write(varname,'(A)') 'rho'), indexed names ('(A,I0)') 'alpha_rho', i), and a few multi-branch if/else ladders (e.g. picking Bx/By/Bz). Don't try to fold name logic into the helper.
  • Irregular fills keep their explicit q_sf = … and use the no-src form. A handful of sources are not a plain scalar_field slice: rho_sf (a bare 3D array), q_T_sf%sf, and the real(ib_markers%sf(…), wp) cast for IB markers. For those, leave the explicit q_sf(:,:,:) = … line and call s_write_field(varname, t_step) — you still drop the varname(:) = ' ' clear and unify the write call.
  • Guards stay put. The surrounding if (… _wrt …) / cons_vars_wrt/prim_vars_wrt conditions are unchanged.

Net effect: ~38 four-line stanzas become two- or three-line calls, removing the duplicated slice expression and the forgettable clear, while the write order (which the golden output depends on) is untouched.

Golden-file safety & verification

Output-neutral by construction: the helper performs the exact same slice assignment, the same writer call, and the same clear, in the same order — so the database files are bit-identical. This is still output code, so verify with the post_process golden suite after the change: e.g. ./mfc.sh test -j 8 (or --only the relevant 1D/2D/3D feature tests), which exercise these write paths for the common variables, MHD (Bx/By/Bz), elasticity (stresses), species, vorticity, etc.

Scope / placement

  • Put s_write_field as a module-private routine in m_start_up (next to s_save_data, its only caller), or co-locate it in m_data_output beside s_write_variable_to_formatted_database_file. Either works since q_sf is module-global; the m_start_up-private option has the smallest blast radius.
  • post_process is CPU-only, so no GPU/GPU_* considerations.

Follow-on to #1520 (which lifts the bubble-moment block out of this same routine); the two can be done independently or together.


Filed from a repo-wide code-cleanliness review; verified against master @ 40dde5e.

Code references

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions