rdb_ocean_restart_io.F90 Source File

NetCDF read/write of the ocean restart registry.


This file depends on

sourcefile~~rdb_ocean_restart_io.f90~~EfferentGraph sourcefile~rdb_ocean_restart_io.f90 rdb_ocean_restart_io.F90 sourcefile~rdb_constants.f90 rdb_constants.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_decomp.f90 rdb_decomp.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_error_ring.f90 rdb_error_ring.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_io_netcdf.f90 rdb_io_netcdf.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_restart.f90 rdb_ocean_restart.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_ocean_restart.f90 sourcefile~rdb_ocean_status.f90 rdb_ocean_status.F90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_config.f90 rdb_config.F90 sourcefile~rdb_decomp.f90->sourcefile~rdb_config.f90 sourcefile~rdb_io_netcdf.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_io_netcdf.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_restart.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_config.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_config.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_coriolis_adv.f90 rdb_coriolis_adv.F90 sourcefile~rdb_config.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_eos.f90 rdb_eos.F90 sourcefile~rdb_config.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_enthalpy.f90 rdb_ice_enthalpy.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_init.f90 rdb_ice_init.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ice_init.f90 sourcefile~rdb_nml_schema.f90 rdb_nml_schema.F90 sourcefile~rdb_config.f90->sourcefile~rdb_nml_schema.f90 sourcefile~rdb_ocean_bottom_drag.f90 rdb_ocean_bottom_drag.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_boundary_types.f90 rdb_ocean_boundary_types.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_cavity.f90 rdb_ocean_cavity.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_cavity.f90 sourcefile~rdb_ocean_cavity_melt.f90 rdb_ocean_cavity_melt.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_cavity_melt.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90 rdb_ocean_horizontal_viscosity.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_lateral_mix.f90 rdb_ocean_lateral_mix.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_pressure_force.f90 rdb_ocean_pressure_force.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_pseudo_salt.f90 rdb_ocean_pseudo_salt.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_pseudo_salt.f90 sourcefile~rdb_ocean_surface_flux.f90 rdb_ocean_surface_flux.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_tidal_mixing.f90 rdb_ocean_tidal_mixing.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_tidal_mixing.f90 sourcefile~rdb_ocean_top_drag.f90 rdb_ocean_top_drag.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_ocean_vmix.f90 rdb_ocean_vmix.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_vmix.f90 sourcefile~rdb_recon_weno.f90 rdb_recon_weno.F90 sourcefile~rdb_config.f90->sourcefile~rdb_recon_weno.f90 sourcefile~rdb_vcoord.f90 rdb_vcoord.F90 sourcefile~rdb_config.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_state.f90 rdb_barotropic_state.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_barotropic_state.f90 sourcefile~rdb_grid.f90 rdb_grid.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_mem_report.f90 rdb_mem_report.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_multilayer_state.f90 rdb_multilayer_state.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_metrics.f90 rdb_ocean_metrics.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_porous.f90 rdb_ocean_porous.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_ocean_porous.f90 sourcefile~rdb_scratch_3d.f90 rdb_scratch_3d.F90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_enthalpy.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_column.f90 rdb_ice_column.F90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_state.f90 rdb_ice_state.F90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_nml_schema.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_nml_schema.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_tide_astro.f90 rdb_ocean_tide_astro.F90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_ocean_tide_astro.f90 sourcefile~rdb_ocean_cavity.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_cavity.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_cavity_melt.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_cavity_melt.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90 rdb_ocean_pgf_reconstruct.F90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_ocean_pgf_reconstruct.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_surface_stress.f90 rdb_ocean_surface_stress.F90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_recon_weno.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_recon_weno.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_vcoord.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_grid.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_mass.f90 rdb_ice_mass.F90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_mass.f90 sourcefile~rdb_ice_optics.f90 rdb_ice_optics.F90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_optics.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_mem_report.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_efp.f90 rdb_efp.F90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_tracer.f90 rdb_tracer.F90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_bipolar.f90 rdb_ocean_bipolar.F90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_bipolar.f90 sourcefile~rdb_ocean_fold.f90 rdb_ocean_fold.F90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_porous.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_tide_astro.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_scratch_3d.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_scratch_3d.f90->sourcefile~rdb_mem_report.f90

Files dependent on this one

sourcefile~~rdb_ocean_restart_io.f90~~AfferentGraph sourcefile~rdb_ocean_restart_io.f90 rdb_ocean_restart_io.F90 sourcefile~rdb_ocean_state.f90 rdb_ocean_state.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_restart_io.f90 sourcefile~rdb_driver.f90 rdb_driver.F90 sourcefile~rdb_driver.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_engine.f90 rdb_ocean_engine.F90 sourcefile~rdb_driver.f90->sourcefile~rdb_ocean_engine.f90 sourcefile~rdb_handle.f90 rdb_handle.F90 sourcefile~rdb_handle.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_handle.f90->sourcefile~rdb_ocean_engine.f90 sourcefile~rdb_ocean_diag_derived.f90 rdb_ocean_diag_derived.F90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_diag_fills.f90 rdb_ocean_diag_fills.F90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag_derived.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_setup.f90 rdb_ocean_setup.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_setup.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_api.f90 rdb_ocean_api.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_handle.f90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_diag_derived.f90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_engine.f90

Source Code

!! NetCDF read/write of the ocean restart registry.
module rdb_ocean_restart_io
   !! Walks a `restart_registry_t` and writes/reads each registered field
   !! as a FULL local array (interior + ghosts) to/from a per-rank NetCDF
   !! file, alongside the scalar checkpoint metadata (time, step
   !! counters), the decomposition attributes that gate a same-decomp
   !! resume, and the grid/vcoord/tracer metadata that gates a same-schema
   !! resume.
   !!
   !! Durability: the write goes to `<filename>.tmp`, then a POSIX
   !! `rename(2)` atomically swaps it into place — a crash mid-write
   !! leaves the previous checkpoint intact.
   use, intrinsic :: iso_c_binding, only: c_char, c_int, c_null_char, c_ptr, c_loc
   use rdb_constants, only: wp
   use rdb_decomp, only: decomp_t
   use rdb_ocean_restart, only: restart_registry_t, RESTART_SCHEMA_VERSION
   use rdb_io_netcdf, only: nc_check, nc_create_file, nc_open_read, nc_close, &
                            nc_def_dim, nc_enddef, nc_put_att_global, &
                            nc_put_att_global_int, nc_get_att_int, &
                            NC_WP
   use netcdf, only: nf90_def_var, nf90_put_var, nf90_get_var, nf90_inq_varid, &
                     nf90_double, nf90_int, nf90_noerr
   use pic_logger, only: logger => global_logger
   use pic_strings, only: to_string
   use rdb_error_ring, only: error_ring_push
   use rdb_ocean_status, only: OCEAN_STATUS_OK, OCEAN_STATUS_ERR_IO
   implicit none
   private

   public :: ocean_restart_write_local
   public :: ocean_restart_read_local
   public :: ocean_restart_check_decomp
   public :: ocean_restart_metadata_t

   type :: ocean_restart_metadata_t
      !! Grid/coordinate/tracer fingerprint validated on read.  Distinct
      !! from the decomposition metadata (px/py/dims) so a file built with
      !! a different vertical resolution, ghost width, vcoord, or tracer
      !! set is rejected loudly rather than silently mis-mapped.
      integer :: nz_ml = 0
         !! Multilayer layer count.
      integer :: nghost = 0
         !! Ghost width.
      integer :: vcoord_type = 0
         !! Vertical-coordinate enum (VCOORD_*).
      character(len=32) :: vcoord_name = ""
         !! Human-readable vcoord tag (for the mismatch message).
      integer :: n_tracers = 0
         !! Registered tracer count.
      integer :: idx_salinity = 0
         !! tracers(:) index of salinity (0 = none).
      integer :: idx_temperature = 0
         !! tracers(:) index of temperature (0 = none).
   end type ocean_restart_metadata_t

contains

   subroutine ocean_restart_write_local(filename, reg, decomp, meta, t, step, &
                                        outer_step_count, ierr)
      !! Write a per-rank ocean restart file from the registry durably
      !! (to `<filename>.tmp` then POSIX-rename).  Device sync
      !! (`!$acc update self`) on the device-mapped arrays must already
      !! have been done by the caller — this is pure host NetCDF I/O.
      !!
      !! `ierr` (P7, same treatment as `rdb_ocean_diag_netcdf`'s
      !! `open_stream`): every internal `nc_*` call, plus the final
      !! atomic rename, used to `error stop` unconditionally. When
      !! `ierr` is present, any failure closes/discards the partial
      !! `.tmp` file, sets `ierr = OCEAN_STATUS_ERR_IO`, and returns
      !! rather than aborting. Absent `ierr` preserves the legacy abort
      !! behaviour byte-for-byte.
      character(len=*), intent(in) :: filename
      type(restart_registry_t), intent(in) :: reg
      type(decomp_t), intent(in) :: decomp
      type(ocean_restart_metadata_t), intent(in) :: meta
      real(wp), intent(in) :: t
      integer, intent(in) :: step
      integer, intent(in) :: outer_step_count
      integer, intent(out), optional :: ierr
         !! Non-zero (`OCEAN_STATUS_ERR_IO`) on any NetCDF or rename
         !! failure when present; absent behaves as today (`error stop`).

      character(len=:), allocatable :: tmpname
      integer :: ncid, e
      integer :: time_varid, step_varid, osc_varid
      integer, allocatable :: varids(:)
      character(len=32) :: dimname_x, dimname_y, dimname_z
      integer :: dx_id, dy_id, dz_id
      integer :: dimids2(2), dimids3(3)
      integer :: rc, local_ierr
      character(len=:), allocatable :: msg

      if (present(ierr)) ierr = OCEAN_STATUS_OK

      tmpname = trim(filename)//".tmp"
      call logger%info("Writing ocean restart file: "//trim(filename)// &
                       " ("//to_string(reg%n)//" fields)")

      call nc_create_file(tmpname, ncid, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr)) return
      allocate (varids(reg%n))

      ! One x/y/z dim trio per entry index.  The registry mixes layer
      ! (nk=nz) and interface (nk=nz+1) fields; per-entry dims are simple
      ! and the file is tiny so the duplication costs nothing.
      do e = 1, reg%n
         if (reg%entries(e)%rank == 0) cycle  ! host scalars: no dims
         write (dimname_x, "(A,I0)") "x", e
         write (dimname_y, "(A,I0)") "y", e
         call nc_def_dim(ncid, trim(dimname_x), reg%entries(e)%nx_phys, dx_id, ierr=local_ierr)
         if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
         call nc_def_dim(ncid, trim(dimname_y), reg%entries(e)%ny_phys, dy_id, ierr=local_ierr)
         if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
         if (reg%entries(e)%rank == 2) then
            dimids2 = [dx_id, dy_id]
            call nc_check(nf90_def_var(ncid, trim(reg%entries(e)%tag), NC_WP, &
                                       dimids2, varids(e)), &
                          "defining restart var "//trim(reg%entries(e)%tag), local_ierr)
            if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
         else
            write (dimname_z, "(A,I0)") "z", e
            call nc_def_dim(ncid, trim(dimname_z), reg%entries(e)%nk, dz_id, ierr=local_ierr)
            if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
            dimids3 = [dx_id, dy_id, dz_id]
            call nc_check(nf90_def_var(ncid, trim(reg%entries(e)%tag), NC_WP, &
                                       dimids3, varids(e)), &
                          "defining restart var "//trim(reg%entries(e)%tag), local_ierr)
            if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
         end if
      end do

      ! Host scalars (rank-0 persistent state) as NetCDF scalar vars.
      do e = 1, reg%n
         if (reg%entries(e)%rank /= 0) cycle
         call nc_check(nf90_def_var(ncid, trim(reg%entries(e)%tag), nf90_double, &
                                    varids(e)), &
                       "defining restart scalar "//trim(reg%entries(e)%tag), local_ierr)
         if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      end do

      ! Scalar metadata (rank-0 globals).
      call nc_check(nf90_def_var(ncid, "time", nf90_double, time_varid), &
                    "defining restart time", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_check(nf90_def_var(ncid, "n_steps", nf90_int, step_varid), &
                    "defining restart n_steps", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_check(nf90_def_var(ncid, "outer_step_count", nf90_int, osc_varid), &
                    "defining restart outer_step_count", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return

      ! Decomposition + schema metadata (validated on read).
      call nc_put_att_global_int(ncid, "schema_version", RESTART_SCHEMA_VERSION, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "px", decomp%px, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "py", decomp%py, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "i_start", decomp%i_start, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "j_start", decomp%j_start, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "nx_global", decomp%nx_global, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "ny_global", decomp%ny_global, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "nx_local", decomp%nx_local, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "ny_local", decomp%ny_local, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return

      ! Grid / vcoord / tracer metadata (validated on read — review #7).
      call nc_put_att_global_int(ncid, "nz_ml", meta%nz_ml, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "nghost", meta%nghost, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "vcoord_type", meta%vcoord_type, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global(ncid, "vcoord_name", trim(meta%vcoord_name), ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "n_tracers", meta%n_tracers, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "idx_salinity", meta%idx_salinity, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_put_att_global_int(ncid, "idx_temperature", meta%idx_temperature, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return

      call nc_enddef(ncid, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return

      ! Write each FULL local array (interior + ghosts) / scalar.
      do e = 1, reg%n
         associate (en => reg%entries(e))
            select case (en%rank)
            case (0)
               call nc_check(nf90_put_var(ncid, varids(e), en%p0), &
                             "writing restart scalar "//trim(en%tag), local_ierr)
            case (2)
               call nc_check(nf90_put_var(ncid, varids(e), &
                                          en%p2(en%ng + 1:en%ng + en%nx_phys, en%ng + 1:en%ng + en%ny_phys)), &
                             "writing restart "//trim(en%tag), local_ierr)
            case default
               call nc_check(nf90_put_var(ncid, varids(e), &
                                          en%p3(en%ng + 1:en%ng + en%nx_phys, en%ng + 1:en%ng + en%ny_phys, 1:en%nk)), &
                             "writing restart "//trim(en%tag), local_ierr)
            end select
         end associate
         if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      end do

      call nc_check(nf90_put_var(ncid, time_varid, t), "writing restart time", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_check(nf90_put_var(ncid, step_varid, step), "writing restart n_steps", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return
      call nc_check(nf90_put_var(ncid, osc_varid, outer_step_count), &
                    "writing restart outer_step_count", local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr, ncid)) return

      call nc_close(ncid, ierr=local_ierr)
      if (.not. restart_io_ok(local_ierr, ierr)) return
      deallocate (varids)

      ! Atomic swap: rename the complete tmp file over the target.  A
      ! crash before this point leaves the previous checkpoint untouched.
      rc = posix_rename(tmpname, trim(filename))
      if (rc /= 0) then
         msg = "Failed to rename restart "//trim(tmpname)//" -> "// &
               trim(filename)//" (rc="//to_string(rc)//")"
         call error_ring_push(msg)
         call logger%error(msg)
         if (present(ierr)) then
            ierr = OCEAN_STATUS_ERR_IO
            return
         end if
         error stop "ocean_restart_write_local: rename failed"
      end if
      call logger%info("Ocean restart written at t = "//to_string(t)//" s")
   end subroutine ocean_restart_write_local

   function restart_io_ok(local_ierr, ierr, ncid) result(ok)
      !! Translate a raw `nc_check`-style status (0 = ok) from one of the
      !! `nc_*` calls in `ocean_restart_write_local` into the caller's
      !! `ierr` contract. Mirrors `rdb_bathymetry`'s `bathy_io_ok` /
      !! `rdb_ocean_diag_netcdf`'s `diag_io_ok` (P0.1 F1 / P7 F1): closes
      !! `ncid` (when given) on failure so a mid-write error does not
      !! leak the file handle, sets `ierr = OCEAN_STATUS_ERR_IO` when
      !! present, or `error stop`s with the generic `nc_check` text when
      !! `ierr` is absent (byte-identical legacy behaviour).
      integer, intent(in) :: local_ierr
      integer, intent(out), optional :: ierr
      integer, intent(in), optional :: ncid
      logical :: ok

      integer :: discard_ierr

      ok = (local_ierr == 0)
      if (ok) return

      if (present(ierr)) then
         if (present(ncid)) call nc_close(ncid, ierr=discard_ierr)
         ierr = OCEAN_STATUS_ERR_IO
         return
      end if

      error stop "NetCDF operation failed"
   end function restart_io_ok

   integer function posix_rename(oldpath, newpath) result(rc)
      !! Atomically rename `oldpath` -> `newpath` via libc `rename(2)`.
      !! Returns 0 on success, -1 on error.
      character(len=*), intent(in) :: oldpath, newpath
      ! Null-terminated C strings as `target` locals so `c_loc` can hand
      ! libc `rename` a pointer to each (c_ptr by value sidesteps
      ! assumed-size dummies).
      character(kind=c_char), allocatable, target :: c_old(:), c_new(:)
      interface
         function c_rename(old, new) bind(C, name="rename") result(r)
            import :: c_ptr, c_int
            implicit none
            type(c_ptr), value, intent(in) :: old
            type(c_ptr), value, intent(in) :: new
            integer(c_int) :: r
         end function c_rename
      end interface
      c_old = string_to_c(oldpath)
      c_new = string_to_c(newpath)
      rc = int(c_rename(c_loc(c_old), c_loc(c_new)))
   end function posix_rename

   pure function string_to_c(s) result(c)
      !! Pack a Fortran string into a null-terminated c_char array.
      character(len=*), intent(in) :: s
      character(kind=c_char), allocatable :: c(:)
      integer :: i, n
      n = len_trim(s)
      allocate (c(n + 1))
      do i = 1, n
         c(i) = s(i:i)
      end do
      c(n + 1) = c_null_char
   end function string_to_c

   subroutine ocean_restart_check_decomp(filename, decomp, ierr, meta)
      !! Validate schema + decomposition (+ optional grid/vcoord/tracer)
      !! metadata in `filename` against the live `decomp`/`meta`.
      !! `ierr` = 0 on match; 1 = schema, 2 = decomposition,
      !! 3 = grid/vcoord/tracer mismatch.
      character(len=*), intent(in) :: filename
      type(decomp_t), intent(in) :: decomp
      integer, intent(out) :: ierr
      type(ocean_restart_metadata_t), intent(in), optional :: meta

      integer :: ncid, file_schema, fpx, fpy, fnxg, fnyg, fnxl, fnyl
      integer :: fis, fjs
      integer :: fnz, fng, fvc, fntr, fidxs, fidxt
      character(len=:), allocatable :: msg

      ierr = 0
      call nc_open_read(filename, ncid)
      call nc_get_att_int(ncid, "schema_version", file_schema)
      if (file_schema /= RESTART_SCHEMA_VERSION) then
         msg = "Restart schema mismatch: file v"//to_string(file_schema)// &
               " but build expects v"//to_string(RESTART_SCHEMA_VERSION)
         call error_ring_push(msg)
         call logger%error(msg)
         ierr = 1
         call nc_close(ncid)
         return
      end if
      call nc_get_att_int(ncid, "px", fpx)
      call nc_get_att_int(ncid, "py", fpy)
      call nc_get_att_int(ncid, "i_start", fis)
      call nc_get_att_int(ncid, "j_start", fjs)
      call nc_get_att_int(ncid, "nx_global", fnxg)
      call nc_get_att_int(ncid, "ny_global", fnyg)
      call nc_get_att_int(ncid, "nx_local", fnxl)
      call nc_get_att_int(ncid, "ny_local", fnyl)

      if (fpx /= decomp%px .or. fpy /= decomp%py .or. &
          fis /= decomp%i_start .or. fjs /= decomp%j_start .or. &
          fnxg /= decomp%nx_global .or. fnyg /= decomp%ny_global .or. &
          fnxl /= decomp%nx_local .or. fnyl /= decomp%ny_local) then
         msg = "Restart decomposition mismatch: file px="// &
               to_string(fpx)//" py="//to_string(fpy)// &
               " i_start="//to_string(fis)//" j_start="//to_string(fjs)// &
               " nx_local="//to_string(fnxl)//" ny_local="//to_string(fnyl)// &
               " but run has px="//to_string(decomp%px)// &
               " py="//to_string(decomp%py)// &
               " i_start="//to_string(decomp%i_start)// &
               " j_start="//to_string(decomp%j_start)// &
               " nx_local="//to_string(decomp%nx_local)// &
               " ny_local="//to_string(decomp%ny_local)// &
               ".  File: "//trim(filename)// &
               ".  Same-decomp resume only; use the offline "// &
               "redistribute tool to change rank count."
         call error_ring_push(msg)
         call logger%error(msg)
         ierr = 2
         call nc_close(ncid)
         return
      end if

      if (present(meta)) then
         call nc_get_att_int(ncid, "nz_ml", fnz)
         call nc_get_att_int(ncid, "nghost", fng)
         call nc_get_att_int(ncid, "vcoord_type", fvc)
         call nc_get_att_int(ncid, "n_tracers", fntr)
         call nc_get_att_int(ncid, "idx_salinity", fidxs)
         call nc_get_att_int(ncid, "idx_temperature", fidxt)
         if (fnz /= meta%nz_ml .or. fng /= meta%nghost .or. &
             fvc /= meta%vcoord_type .or. fntr /= meta%n_tracers .or. &
             fidxs /= meta%idx_salinity .or. fidxt /= meta%idx_temperature) then
            msg = "Restart grid/vcoord/tracer mismatch: file nz_ml="// &
                  to_string(fnz)//" nghost="//to_string(fng)// &
                  " vcoord_type="//to_string(fvc)// &
                  " n_tracers="//to_string(fntr)// &
                  " idx_S="//to_string(fidxs)//" idx_T="//to_string(fidxt)// &
                  " but run has nz_ml="//to_string(meta%nz_ml)// &
                  " nghost="//to_string(meta%nghost)// &
                  " vcoord_type="//to_string(meta%vcoord_type)// &
                  " ("//trim(meta%vcoord_name)//")"// &
                  " n_tracers="//to_string(meta%n_tracers)// &
                  " idx_S="//to_string(meta%idx_salinity)// &
                  " idx_T="//to_string(meta%idx_temperature)// &
                  ".  File: "//trim(filename)
            call error_ring_push(msg)
            call logger%error(msg)
            ierr = 3
         end if
      end if
      call nc_close(ncid)
   end subroutine ocean_restart_check_decomp

   subroutine ocean_restart_read_local(filename, reg, decomp, meta, t, step, &
                                       outer_step_count, ierr)
      !! Read a per-rank ocean restart into the registry's host arrays
      !! (FULL local extent) + return the scalar metadata.  Validates
      !! decomp + grid/vcoord/tracer metadata first and error-stops on
      !! mismatch unless `ierr` is present.  A REQUIRED field absent from
      !! the file is FATAL; only `optional` entries warn-and-seed.  Must
      !! run BEFORE `ocean_state_enter_data` so the H->D copy carries the
      !! restored values up.
      character(len=*), intent(in) :: filename
      type(restart_registry_t), intent(inout) :: reg
      type(decomp_t), intent(in) :: decomp
      type(ocean_restart_metadata_t), intent(in) :: meta
      real(wp), intent(out) :: t
      integer, intent(out) :: step
      integer, intent(out) :: outer_step_count
      integer, intent(out), optional :: ierr

      integer :: ncid, e, varid, status, check_err
      character(len=:), allocatable :: msg

      t = 0.0_wp
      step = 0
      outer_step_count = 0
      if (present(ierr)) ierr = 0

      call logger%info("Reading ocean restart file: "//trim(filename))

      call ocean_restart_check_decomp(filename, decomp, check_err, meta=meta)
      if (check_err /= 0) then
         if (present(ierr)) then
            ierr = check_err
            return
         end if
         error stop "ocean_restart_read_local: restart metadata mismatch (see log)"
      end if

      call nc_open_read(filename, ncid)
      do e = 1, reg%n
         associate (en => reg%entries(e))
            status = nf90_inq_varid(ncid, trim(en%tag), varid)
            if (status /= nf90_noerr) then
               if (en%optional) then
                  call logger%warning("Restart file lacks optional field "// &
                                      trim(en%tag)//"; leaving it at its seeded value")
                  cycle
               end if
               msg = "Restart file "//trim(filename)// &
                     " is missing REQUIRED field "//trim(en%tag)// &
                     "; refusing to cold-start half the state."
               call error_ring_push(msg)
               call logger%error(msg)
               call nc_close(ncid)
               error stop "ocean_restart_read_local: missing required field"
            end if
            en%found = .true.
            select case (en%rank)
            case (0)
               call nc_check(nf90_get_var(ncid, varid, en%p0), &
                             "reading restart scalar "//trim(en%tag))
            case (2)
               call nc_check(nf90_get_var(ncid, varid, &
                                          en%p2(en%ng + 1:en%ng + en%nx_phys, en%ng + 1:en%ng + en%ny_phys)), &
                             "reading restart "//trim(en%tag))
            case default
               call nc_check(nf90_get_var(ncid, varid, &
                                          en%p3(en%ng + 1:en%ng + en%nx_phys, en%ng + 1:en%ng + en%ny_phys, 1:en%nk)), &
                             "reading restart "//trim(en%tag))
            end select
         end associate
      end do

      call nc_get_varid(ncid, "time", varid)
      call nc_check(nf90_get_var(ncid, varid, t), "reading restart time")
      call nc_get_varid(ncid, "n_steps", varid)
      call nc_check(nf90_get_var(ncid, varid, step), "reading restart n_steps")
      call nc_get_varid(ncid, "outer_step_count", varid)
      call nc_check(nf90_get_var(ncid, varid, outer_step_count), &
                    "reading restart outer_step_count")
      call nc_close(ncid)

      call logger%info("Ocean restart loaded: t = "//to_string(t)//" s, step = "// &
                       to_string(step))
   end subroutine ocean_restart_read_local

   subroutine nc_get_varid(ncid, name, varid)
      integer, intent(in) :: ncid
      character(len=*), intent(in) :: name
      integer, intent(out) :: varid
      call nc_check(nf90_inq_varid(ncid, name, varid), "looking up restart var "//name)
   end subroutine nc_get_varid

end module rdb_ocean_restart_io