subroutine ocean_vcoord_init(this, grid, nz_ml)
!! Allocate every per-column array the slot owns: `dsig`,
!! `z_ref_global`, `target_h`, `z_ref`. All sized once at init —
!! grids don't resize. Host allocations only; `enter_data` ships
!! them to the device.
class(ocean_vcoord_t), intent(inout) :: this
type(hgrid_t), intent(in) :: grid
integer, intent(in), optional :: nz_ml
integer :: nz_local, k
nz_local = 1
if (present(nz_ml)) nz_local = nz_ml
if (nz_local < 1) nz_local = 1
this%nx_total = grid%nx_total
this%ny_total = grid%ny_total
this%nz_ml = nz_local
allocate (this%dsig(nz_local))
do k = 1, nz_local
this%dsig(k) = 1.0_wp/real(nz_local, wp)
end do
! Default reference z-interfaces: uniform 0..1 normalised. The
! ZSIGMA / ZSTAR_SIGMA branches that consume this expect absolute
! metre values from the namelist parser; the normalised default
! is only useful for VCOORD_SIGMA (which ignores it)
! and for unit tests that pre-populate before running.
allocate (this%z_ref_global(0:nz_local))
do k = 0, nz_local
this%z_ref_global(k) = real(k, wp)/real(nz_local, wp)
end do
allocate (this%target_h(grid%nx_total, grid%ny_total, nz_local), source=0.0_wp)
! Per-column z* reference table — sized but not populated. Callers
! invoke `build_zref_full(h_bed)` once at setup to fill it. Until
! then, ZSTAR_FULL's `compute_target_h` walks a column of zeros,
! which the formula safely degrades to an all-h_min vanishing-
! layer column (the dry / shallow degenerate case).
allocate (this%z_ref(grid%nx_total, grid%ny_total, 0:nz_local), source=0.0_wp)
! Geopotential depth of the column top. Zero = the `z = 0` datum;
! `configure_ocean_cavity` overwrites it with the static ice draft
! when a cavity is configured. Allocated unconditionally so the
! Z_FIXED kernel never sees a placeholder-sized array.
allocate (this%z_top(grid%nx_total, grid%ny_total), source=0.0_wp)
! `VCOORD_Z_FIXED` stretched nominal profile — allocated
! unconditionally (zeros) so the target kernel's explicit-shape
! dummies never see a placeholder; filled by
! `ocean_vcoord_set_z_fixed_profile` only when a profile is set.
allocate (this%z_fixed_zi(0:nz_local), source=0.0_wp)
allocate (this%z_fixed_dz(nz_local), source=0.0_wp)
! Isopycnal target densities — sized `0:nz_ml`, populated by the
! setup wiring only when `coord_type == VCOORD_RHO`. Default is a
! benign monotone ramp (1020..1030 kg/m³) so the slot is always
! valid; the setup path overwrites it from the namelist.
allocate (this%rho_target(0:nz_local))
do k = 0, nz_local
this%rho_target(k) = 1020.0_wp + 10.0_wp*real(k, wp)/real(nz_local, wp)
end do
! ALE remap scratch — persistent so the remap step doesn't
! allocate fresh host buffers per call (the DCs there were
! reading device-resident `ms%h_layer` and writing into freshly-
! allocated host arrays, forcing per-iteration H↔D transfers
! that dominated wallclock in production runs).
allocate (this%remap_total_h(grid%nx_total, grid%ny_total), source=0.0_wp)
allocate (this%remap_h_ref(grid%nx_total, grid%ny_total), source=0.0_wp)
allocate (this%remap_h_old(grid%nx_total, grid%ny_total, nz_local), source=0.0_wp)
allocate (this%remap_conc_t(grid%nx_total, grid%ny_total, nz_local), source=0.0_wp)
allocate (this%remap_conc_s(grid%nx_total, grid%ny_total, nz_local), source=0.0_wp)
this%is_init = .true.
end subroutine ocean_vcoord_init