engine_step_finalize Subroutine

public subroutine engine_step_finalize(engine, dt, t, ierr)

Second half of one outer step: derive Q_heat/Q_salt from the surface-flux component set (no-op unless &ocean_forcing_nml enable_components) and, when diagnostics are configured, run one ocean_diag_t%step. Call immediately after engine_step and, when sea ice is enabled, engine_step_ice too — see engine_step’s docstring for why this is a separate call.

Arguments

Type IntentOptional Attributes Name
type(ocean_engine_t), intent(inout) :: engine
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in) :: t

Simulation time at the START of the step this finalizes (the same t passed to the matching engine_step call) — t + dt is what reaches the diag manager, matching driver_run_ocean’s post-advance t_current.

integer, intent(out), optional :: ierr

Calls

proc~~engine_step_finalize~~CallsGraph proc~engine_step_finalize engine_step_finalize proc~ocean_cavity_flux_step ocean_cavity_flux_step proc~engine_step_finalize->proc~ocean_cavity_flux_step proc~ocean_diag_step ocean_diag_t%ocean_diag_step proc~engine_step_finalize->proc~ocean_diag_step proc~ocean_dyn_is_thermo_step ocean_dyn_t%ocean_dyn_is_thermo_step proc~engine_step_finalize->proc~ocean_dyn_is_thermo_step proc~ocean_surface_flux_assemble ocean_surface_flux_assemble proc~engine_step_finalize->proc~ocean_surface_flux_assemble proc~ocean_surface_stress_set_shelf_from_ustar ocean_surface_stress_set_shelf_from_ustar proc~engine_step_finalize->proc~ocean_surface_stress_set_shelf_from_ustar proc~cavity_far_field_impl cavity_far_field_impl proc~ocean_cavity_flux_step->proc~cavity_far_field_impl proc~cavity_flux_fill_impl cavity_flux_fill_impl proc~ocean_cavity_flux_step->proc~cavity_flux_fill_impl proc~cavity_melt_columns_2d cavity_melt_columns_2d proc~ocean_cavity_flux_step->proc~cavity_melt_columns_2d proc~cavity_melt_status_is_fatal cavity_melt_status_is_fatal proc~ocean_cavity_flux_step->proc~cavity_melt_status_is_fatal proc~cavity_status_counts_impl cavity_status_counts_impl proc~ocean_cavity_flux_step->proc~cavity_status_counts_impl proc~fail fail proc~ocean_cavity_flux_step->proc~fail to_string to_string proc~ocean_cavity_flux_step->to_string warning warning proc~ocean_cavity_flux_step->warning info info proc~ocean_diag_step->info proc~diag_field_stats diag_field_stats proc~ocean_diag_step->proc~diag_field_stats proc~fill_and_remap fill_and_remap proc~ocean_diag_step->proc~fill_and_remap proc~finalise_accumulator finalise_accumulator proc~ocean_diag_step->proc~finalise_accumulator proc~fold_sample fold_sample proc~ocean_diag_step->proc~fold_sample proc~reset_accumulator reset_accumulator proc~ocean_diag_step->proc~reset_accumulator proc~ocean_surfflux_assemble_cover_impl ocean_surfflux_assemble_cover_impl proc~ocean_surface_flux_assemble->proc~ocean_surfflux_assemble_cover_impl proc~ocean_surfflux_assemble_impl ocean_surfflux_assemble_impl proc~ocean_surface_flux_assemble->proc~ocean_surfflux_assemble_impl local local proc~cavity_far_field_impl->local proc~cavity_melt_columns_2d->local proc~cavity_melt_point_gamma_f cavity_melt_point_gamma_f proc~cavity_melt_columns_2d->proc~cavity_melt_point_gamma_f proc~cavity_ustar cavity_ustar proc~cavity_melt_columns_2d->proc~cavity_ustar reduce reduce proc~cavity_status_counts_impl->reduce proc~diag_reduce_stats diag_reduce_stats proc~diag_field_stats->proc~diag_reduce_stats error error proc~fail->error proc~error_ring_push error_ring_push proc~fail->proc~error_ring_push proc~finalise_copy_impl finalise_copy_impl proc~fill_and_remap->proc~finalise_copy_impl proc~finalise_accumulator->proc~finalise_copy_impl proc~finalise_scale_impl finalise_scale_impl proc~finalise_accumulator->proc~finalise_scale_impl proc~fold_sample_masked_impl fold_sample_masked_impl proc~fold_sample->proc~fold_sample_masked_impl proc~fold_sample_unmasked_impl fold_sample_unmasked_impl proc~fold_sample->proc~fold_sample_unmasked_impl proc~ocean_surfflux_assemble_cover_impl->local proc~ocean_surfflux_assemble_impl->local proc~fill_buffer_impl fill_buffer_impl proc~reset_accumulator->proc~fill_buffer_impl proc~cavity_solve_melt_f cavity_solve_melt_f proc~cavity_melt_point_gamma_f->proc~cavity_solve_melt_f proc~diag_reduce_stats->reduce proc~fold_sample_masked_impl->local proc~cavity_heat_fluxes cavity_heat_fluxes proc~cavity_solve_melt_f->proc~cavity_heat_fluxes proc~cavity_law_is_implicit cavity_law_is_implicit proc~cavity_solve_melt_f->proc~cavity_law_is_implicit proc~cavity_obukhov_length cavity_obukhov_length proc~cavity_solve_melt_f->proc~cavity_obukhov_length proc~cavity_outer_residual cavity_outer_residual proc~cavity_solve_melt_f->proc~cavity_outer_residual proc~cavity_safe_state cavity_safe_state proc~cavity_solve_melt_f->proc~cavity_safe_state proc~cavity_solution_reset cavity_solution_reset proc~cavity_solve_melt_f->proc~cavity_solution_reset proc~cavity_state_at_x cavity_state_at_x proc~cavity_solve_melt_f->proc~cavity_state_at_x proc~eos_freezing_point eos_freezing_point proc~cavity_solve_melt_f->proc~eos_freezing_point

Called by

proc~~engine_step_finalize~~CalledByGraph proc~engine_step_finalize engine_step_finalize proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_step_finalize proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step_finalize proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Source Code

   subroutine engine_step_finalize(engine, dt, t, ierr)
      !! Second half of one outer step: derive `Q_heat`/`Q_salt` from
      !! the surface-flux component set (no-op unless
      !! `&ocean_forcing_nml enable_components`) and, when diagnostics
      !! are configured, run one `ocean_diag_t%step`. Call immediately
      !! after `engine_step` and, when sea ice is enabled,
      !! `engine_step_ice` too — see `engine_step`'s docstring for why
      !! this is a separate call.
      type(ocean_engine_t), intent(inout) :: engine
      real(wp), intent(in) :: dt
      real(wp), intent(in) :: t
         !! Simulation time at the START of the step this finalizes
         !! (the same `t` passed to the matching `engine_step` call) —
         !! `t + dt` is what reaches the diag manager, matching
         !! `driver_run_ocean`'s post-advance `t_current`.
      integer, intent(out), optional :: ierr

      if (present(ierr)) ierr = OCEAN_STATUS_OK

      ! Ice-shelf basal melt (P2b): solve the three-equation interface on
      ! every covered column and fill the OWNED heat_cavity/salt_cavity
      ! components.  MUST precede the assembler, which folds them into
      ! Q_heat/Q_salt.  Same thermo cadence, and a no-op (immediate
      ! return) when &ocean_cavity_melt_nml enable=.false.  Cavity x sea
      ! ice is refused at configure, so the ordering against
      ! engine_step_ice's fillers is not a live question.
      call ocean_cavity_flux_step(engine%grid, engine%state%cavity_flux, &
                                  engine%state%metrics, engine%state%multilayer, &
                                  engine%state%eos, engine%state%surface_flux, &
                                  active=engine%state%dyn%enable_thermodynamics &
                                  .and. engine%state%dyn%is_thermo_step())

      ! Phase 4b — the MELT-ONLY fallback for the boundary-layer `u_*`.
      !
      ! When `&ocean_tdrag_nml` is on, the RK2 stage drivers publish
      ! `ss%stress_shelf` from the top drag's own `stress_top`, in-stage
      ! and unlagged, and this branch stays out of the way.  When it is
      ! OFF but basal melt is on, nothing else would give KPP/EPBL an
      ! under-ice `u_*` at all — they would mix a covered column on the
      ! masked (exactly zero) wind stress.  The melt slot already solved
      ! for a friction velocity with the SAME `C_d` (the one-drag-
      ! coefficient rule: `&ocean_cavity_melt_nml cdrag_top` must equal
      ! `&ocean_tdrag_nml cd`), so re-deriving `|tau_top| = rho_0*u_*^2`
      ! from it is the consistent answer, not a second drag law.
      !
      ! HONEST LIMIT: `cav%ustar` is refreshed at the THERMO cadence, at
      ! the END of the outer step, so this path reaches the boundary-layer
      ! schemes ONE OUTER STEP LATE.  The top-drag path has no such lag.
      ! `cav%ustar` is exactly 0 on every uncovered column, so the
      ! published field keeps `stress_shelf`'s "zero off the cover"
      ! invariant.
      !
      ! A CALL, not an inline `do concurrent`, and the reason is a GPU
      ! rule rather than a style preference.  A `do concurrent` written
      ! here would reference `engine%state%surface_stress%stress_shelf`,
      ! i.e. it would walk the ENGINE, and `ocean_engine_t` is not a
      ! mapped object -- only `engine%state` is.  nvfortran then emits a
      ! data clause for the whole `engine` and aborts at run time with
      ! "variable in data clause is partially present on the device:
      ! name=engine".  Measured, cc70, 2026-09-20: it did exactly that.
      ! Host-dereferencing the two component arrays AT the call site and
      ! handing them to a flat explicit-shape kernel is the fix -- the
      ! outer-shim + flat-impl pattern.
      !
      ! CLAUDE.md's "write a host-gated pass INLINE" rule does not apply:
      ! that rule exists because an escaping state array pessimises the
      ! OTHER `do concurrent` loops in the calling routine, and
      ! `engine_step_finalize` has none -- it is a four-call orchestrator.
      !
      ! Placeholder safety is STRUCTURAL, not a runtime branch: the gate
      ! is `cavity_flux%enable`, and the melt slot allocates `ustar` at
      ! `(nx, ny)` exactly when that is set (`(1,1)` otherwise), while
      ! `stress_shelf` is always full size.  So the explicit-shape dummies
      ! below can only ever be reached with matching, full-size actuals.
      if (engine%state%cavity_flux%enable .and. .not. engine%state%tdrag%enable) then
         call ocean_surface_stress_set_shelf_from_ustar( &
            engine%state%surface_stress%stress_shelf, &
            engine%state%cavity_flux%ustar, &
            engine%state%surface_stress%rho0, &
            size(engine%state%surface_stress%stress_shelf, 1), &
            size(engine%state%surface_stress%stress_shelf, 2))
      end if

      ! Ice-shelf cover: the assembler is where the atmospheric bands and
      ! the cavity's own heat_cavity/salt_cavity are still separable, so
      ! it is where `1 - cover_frac` is applied — and applying it there
      ! (rather than at apply time) is also what makes the `Q_heat` /
      ! `Q_salt` that KPP and EPBL read for `B_0` the MASKED values.
      ! Cavity off ⇒ the original call, byte-identical.
      if (engine%state%metrics%use_cavity) then
         call ocean_surface_flux_assemble(engine%grid, engine%state%surface_flux, &
                                          engine%state%multilayer, &
                                          active=engine%state%dyn%enable_thermodynamics &
                                          .and. engine%state%dyn%is_thermo_step(), &
                                          cover_frac=engine%state%metrics%cover_frac)
      else
         call ocean_surface_flux_assemble(engine%grid, engine%state%surface_flux, &
                                          engine%state%multilayer, &
                                          active=engine%state%dyn%enable_thermodynamics &
                                          .and. engine%state%dyn%is_thermo_step())
      end if

      if (engine%diag_enabled) then
         call engine%state%diag%step(engine%state, dt, t + dt)
      end if
   end subroutine engine_step_finalize