Skip to content

Refactor OBC segment tracer reservoir initialization - #1139

Open
herrwang0 wants to merge 7 commits into
NOAA-GFDL:dev/gfdlfrom
herrwang0:refactor-obc-res-init-part1
Open

Refactor OBC segment tracer reservoir initialization#1139
herrwang0 wants to merge 7 commits into
NOAA-GFDL:dev/gfdlfrom
herrwang0:refactor-obc-res-init-part1

Conversation

@herrwang0

@herrwang0 herrwang0 commented Jun 26, 2026

Copy link
Copy Markdown

Summary

This PR aims to consolidate and clarify the initialization of OBC segment tracer reservoirs (%tres) in initialize_MOM.

The logic was previously spread across MOM_initialize_state / MOM_initialize_OBCs and initialize_MOM, with the new-run vs restart and OBC_RESERVOIR_INIT_BUG cases handled implicitly through scattered if-branches, which relies heavily on the is_initialized flag, and a %tres -> %t -> %tres round-trip.

This PR covers mostly some preparatory work by reorder calls. The Mechanism section details the overarching logic.

The refactor results in modest reduction in line count (net -101 lines across 3 files), and better code readability for future maintenance (I think). There will be a second part that better streamlines the segment reservoir initialization and update.

Mechanism

The fundamental complication during initialization (and during the run) is the back and forth copy between per-segment tracer reservoir %tres and global restart array OBC%tres_[xy]. The copy during the run was partially addressed in #1128 . This PR is the first step to addresses the initialization part.

OBC tracer reservoirs exist in two parallel representations:

  • Per-segment reservoirssegment%tr_Reg%Tr(m)%tres (tracer). This is what the dynamics actually
    reads and evolves.
  • Global restart arraysOBC%tres_x/y. These exist only as a buffer to be written to, and read back from the restart file.

The overarching logic that motivates this cleanup:

  1. Only the per-segment reservoir matters at run time. Every use of the reservoirs goes through the per-segment arrays; the global arrays are never used by the dynamics.
  2. The global restart array's only job is to repopulate the per-segment reservoir on a restart.
  3. On a restart run, the per-segment reservoir should be set exclusively from the restart array (copy_OBC_*_reservoirs: global → segment).
  4. On a new run, the global restart array does not need to be populated from the per-segment reservoir at initialization.
  5. OBC_RESERVOIR_INIT_BUG selects how the per-segment T/S reservoir is first populated: with the bug enabled (True) it is initialized from the interior field, and with the bug disabled (False) from the external (prescribed boundary) data.

Commits

2d6f8f1: Rename MOM_initialize_OBCs to initialize_user_OBCs and hoist the non-user steps into initialize_MOM
834ca9b: Rename initialize_OBC_segment_reservoirs to initialize_OBC_tracer_reservoirs and drop its dead thickness block
157851e: Drop the %tres->%t round-trip and %t fallback in setup_OBC_tracer_reservoirs on the reservoir-init-bug route
3edb4ef: Split the reservoir setup/copy block into explicit new-run vs restart branches
2e7ea73: Remove redundant is_initialized guards in fill_temp_salt_segments and fill_obgc_segments
e2e5eda: Reorder OBC init so the unconditional dynamics fill precedes reservoir seeding; drop redundant mask checksums
45dbbc5: Remove the now-unused is_initialized flag from OBC tracer reservoirs

Comments to the reviewers
Because this a somewhat voluminous PR, each commit is designed to change just one thing. It is probably easier to go through the commits one by one. For each commit, I added some explanatory comments on where codes are removed, which hopefully makes the review easier.

There is no answer change. The PR is tested in loop_current test (OBC_RESERVOIR_INIT_BUG=T/F, DIABATIC_FIRST=T/F, new/restart) and ESMG user cases.

Various Claude models are used to double check code logics, and draft commit/PR messages.

@herrwang0 herrwang0 added the refactor Code cleanup with no changes in functionality or results label Jun 26, 2026
Comment thread src/core/MOM.F90 Outdated
Comment thread src/core/MOM.F90 Outdated
! 1) It is still before remapping.
! 2) For new runs, restart file are irrelevant.
! 3) For restart runs, segment tracer reservoirs are assigned the value from OBC%tres_x/y in a call to
! copy_OBC_tracer_reservoirs below in line 3735.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not confident that this call to fill_temp_salt_segments() can be moved after everything else inside of initialize_state(), nor that OBC_RESERVOIR_INIT_BUG can be eliminated altogether. This certainly was not the case when OBC_RESERVOIR_INIT_BUG was first added. There may have been subsequent refactoring that eliminated the buggy case, but it is not obvious to me what refactoring that would have been. Should I be concerned that this could be an instance of A.I. hallucination?

@herrwang0 herrwang0 Jun 27, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Should I be concerned that this could be an instance of A.I. hallucination?

No, I am not at the stage of trusting AI to do refactor at this scale, especially for MOM6. The core of this PR is all me from earlier this year.

As for OBC_RESERVOIR_INIT_BUG, I tested with loop_current with both True and False, and the PR reproduces old answers for both. And OBC_RESERVOIR_INIT_BUG=True and OBC_RESERVOIR_INIT_BUG=False do give different answers.

Is there an old case that I should take notice? I can add it to my test suites.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The problem occurs in cases that use ALE remapping inside of MOM_initialize_state() in the if (useALE) then block. The temperature is initialized from one of the initilize_temp_salt_...() routines, and then the reservoirs are initialized before the ALE remapping. By moving the call to fill_temp_salt_segments() outside of MOM_initialize_state(), it is only the remapped values that are available to initialize the tracer reservoirs. When I added the OBC_RESERVOIR_INIT_BUG flag in MOM_initialize_state(), I did not see any alternatives just because this particular state of temperatures and salinity is only around very briefly. When we have our consortium-wide call to change defaults, we would ordinarily be changing the default for OBC_RESERVOIR_INIT_BUG to False, but I might see whether we can jump straight to obsoleting it, which would solve all of the problems this bug creates.

I think that this strange initialization behavior that might cause problems with the changes in this PR is found in the loop_current test case.

As you do note, if the model is being restarted from a restart file, there is no problem.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think I found one useALE block within MOM_initialize_state (called after fill_temp_salt_segments) that could potentially be the issue. And its execution depends on parameter REGRID_ACCELERATE_INIT.

  1. REGRID_ACCELERATE_INIT is never logged.
  2. REGRID_ACCELERATE_INIT is False in loop_current, which makes me wonder if this is really the culprit ...
  3. If REGRID_ACCELERATE_INIT=True, I got an error from loop_current restart...

if (useALE) then
call get_param(PF, mdl, "REGRID_ACCELERATE_INIT", regrid_accelerate, &
"If true, runs REGRID_ACCELERATE_ITERATIONS iterations of the regridding "//&
"algorithm to push the initial grid to be consistent with the initial "//&
"condition. Useful only for state-based and iterative coordinates.", &
default=.false., do_not_log=just_read)
if (regrid_accelerate) then
call get_param(PF, mdl, "REGRID_ACCELERATE_ITERATIONS", regrid_iterations, &
"The number of regridding iterations to perform to generate "//&
"an initial grid that is consistent with the initial conditions.", &
default=1, do_not_log=just_read)
call get_param(PF, mdl, "DT", dt, "Timestep", &
units="s", scale=US%s_to_T, fail_if_missing=.true.)
if (new_sim .and. debug) &
call hchksum(h, "Pre-ALE_regrid: h ", G%HI, haloshift=1, unscale=GV%H_to_MKS)
! In this call, OBC_for_remap is only used for the directions of OBCs when setting thicknesses at
! velocity points.
call ALE_regrid_accelerated(ALE_CSp, G, GV, US, h, tv, regrid_iterations, u, v, OBC_for_remap, &
tracer_Reg, dt=dt, initial=.true.)
endif
endif

@theresa-cordero
theresa-cordero self-requested a review June 29, 2026 18:43
@Hallberg-NOAA

Copy link
Copy Markdown
Member

I have added a new PR (#1142) that should help resolve some of the concerns regarding this PR, in particular by offering a short path to obsoleting those aspects of the bugs currently recreated by OBC_RESERVOIR_INIT_BUG that this PR is moving to eliminate.

@herrwang0
herrwang0 marked this pull request as draft July 2, 2026 19:41
@herrwang0
herrwang0 force-pushed the refactor-obc-res-init-part1 branch from e065251 to 45dbbc5 Compare July 8, 2026 17:56
@herrwang0
herrwang0 marked this pull request as ready for review July 8, 2026 17:57
@herrwang0 herrwang0 changed the title Refactor OBC segment tracer reservoir initialization (Part 1) Refactor OBC segment tracer reservoir initialization Jul 8, 2026
* Rename MOM_initialize_OBCs to initialize_user_OBCs and narrow it to
only set up OBC_USER_CONFIG.  Its h, Time and restart_CS arguments are
therefore dropped.
* Faithfully move the remaining steps into the caller initialize_MOM.
This includes the a few debug calls that have nothing to do with user
OBC, a call to fill_thickness_segments, and most importantly, the
OBC_RESERVOIR_INIT_BUG tracer reservoir setup
(setup_OBC_tracer_reservoirs / set_initialized_OBC_tracer_reservoirs /
fill_temp_salt_segments), which will undergo further refactor in the
next few commits.
* Reformat the parameter description for OBC_USER_CONFIG into an
aligned list.
Remove the thickness-reservoir initialization block from
initialize_OBC_segment_reservoirs and rename the routine to
initialize_OBC_tracer_reservoirs to reflect that it now handles
only tracer reservoirs.

The removed block set segment%h_Reg%h_res from the external
thickness, which is repeating fill_thickness_segments.  There is
currently no true external thickness value ever assigned.  Besides,
this block of code only works when segment%h_Reg%is_initialized was
false. That condition is never reached, as fill_thickness_segments is
called earlier, which initializes h_res and sets is_initialized=.true.
Simplify OBC tracer-reservoir initialization in initialize_MOM by
eliminating the special-case handling that existed only for the
OBC_RESERVOIR_INIT_BUG=True new-run path:

- Guard the later initialize_OBC_tracer_reservoirs call with
  (((.not. OBC_reservoir_init_bug) .or. CS%diabatic_first) .and.
  is_new_run(restart_CSp)) so that on the bug route %tres set by
  fill_temp_salt_segments is preserved (not overwritten by %t),
  letting the %tres -> OBC%tres_[xy] save be deferred to the
  setup_OBC_tracer_reservoirs call later in initialize_MOM.

- In setup_OBC_tracer_reservoirs, drop the "OBC%tres_[xy] = %t" branch
  taken when a segment reservoir was not yet is_initialized. The
  per-segment reservoir %tres is now unmodified by
  initialize_OBC_tracer_reservoirs in the OBC_RESERVOIR_INIT_BUG=True
  route, so the call to setup_OBC_tracer_reservoirs can be postponed
  and combined with a later call. The OBC%tres_[xy] = %t is also no
  longer needed.

- Remove the early bug-route calls that ran right after
  fill_temp_salt_segments: setup_OBC_tracer_reservoirs and
  set_initialized_OBC_tracer_reservoirs.

- Delete the now-unused public subroutine
  set_initialized_OBC_tracer_reservoirs and its export.

With these changes %tres is no longer round-tripped through %t on
the bug route, so the early setup and the %t fallback are redundant.
Restructure the OBC reservoir block that follows
tracer_flow_control_init in initialize_MOM into two mutually
exclusive branches selected by is_new_run(restart_CSp):

Previously all of these ran every time, relying on query_initialized
inside the setup_* routines and on an explicit if-guard before
copy_OBC_tracer_reservoirs to sort out the new-run vs restart cases.
The restructure makes the branching explicit, improves readability.

* new run: save segment %tres / %h_res into the global restart arrays
OBC%tres_[xy] / OBC%h_res_[xy]
    * setup_OBC_tracer_reservoirs
    * setup_OBC_thickness_reservoirs

* restart: restore OBC%tres_[xy] / OBC%h_res_[xy] read from the restart
file back into the segment reservoirs
    * open_boundary_halo_update
    * copy_OBC_radiation_coefs
    * copy_OBC_tracer_reservoirs
    * copy_OBC_thickness_reservoirs

OBC halo update clock init is moved from open_boundary_halo_update to
open_boundary_config.
fill_temp_salt_segments is only called before
initialize_OBC_tracer_reservoirs, which is the sole place that sets
segment%tr_Reg%Tr(:)%is_initialized to .true. So when
fill_temp_salt_segments is called, the is_initialized flag is always
.false., so the "if (.not. is_initialized)" guards is redundant.

Similar for fill_obgc_segments, the only place BGC tracers'
is_initialized flag is switched to true is in
initialize_OBC_tracer_reservoirs, which never happens because
update_OBC_seg_data=.false. during initialization.

As in the previous commit, assigning BGC tracers' OBC%tres_[xy] can be
deferred to a call to setup_OBC_tracer_reservoirs later in
initialize_MOM.
Reorganize the open boundary condition setup in initialize_MOM simplify
call sequence and improve readability. The new order is

1. Fill segment dynamics fields (unconditional):
  * calc_derived_thermo,
  * read_OBC_dynamics_data,
  * update_OBC_dynamics_data,
  * fill_thickness_segments.
2. Initialize T/S tracer reservoirs (new runs only)
  * First fill with interior values via fill_temp_salt_segments
  * Then the external read/update and initialize_OBC_tracer_reservoirs.
3. Initialize_user_OBCs, which can later override the reservoirs.

The dynamics fill is moved ahead of the reservoir seeding and kept
unconditional because its outputs are required regardless of the
reservoir-init bug flags: the continuity calls in initialize_dyn_*
consume the segment transports, read_OBC_tracer_data needs the
segment%dz set by update_OBC_dynamics_data, and the update provides
the one-time fill relied on when OBC_VALUE_UPDATE_BUG is true.

Also remove the debug mask checksums after initialize_user_OBCs
(mask2dT, mask2dC[uv], mask2dBu) and the now-unused qchksum import.
There are no calls after the last mask chksum in MOM_initialize_fixed,
so these debugs are not needed.
The is_initialized flag is now only read by
initialize_OBC_tracer_reservoirs, and the only purpose is to skip
%tres = %t copy for tracers whose reservoir value had been set
explicitly via register_segment_tracer(..., OBC_scalar=...) in user
cases.

That guard is no longer needed after initialize_user_OBCs is now called
after initialize_OBC_tracer_reservoirs, so any user-specified reservoir
value is applied last and cannot be overwritten.

The h_Reg (thickness reservoir) is_initialized flag is untouched.
@Hallberg-NOAA
Hallberg-NOAA force-pushed the refactor-obc-res-init-part1 branch from 45dbbc5 to 99d40af Compare September 3, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code cleanup with no changes in functionality or results

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants