open_stream Subroutine

public subroutine open_stream(diag, filename, deflate_level, px, py, i_start, j_start, nx_global, ny_global, nx_local, ny_local, nghost, output_precision, ierr)

Create the NetCDF file, define dims + vars for every currently-registered diag var, and bind the post-fire emit hook. Must be called AFTER all register calls land — vars registered later get no NetCDF binding and are not written.

deflate_level (0-9, default 0 = uncompressed, bit-identical) enables gzip compression on data vars; level 1 is usually optimal (3-5× compression, near-zero CPU).

The optional decomposition attrs (px, py, i_start, j_start, nx_global, ny_global, nx_local, ny_local, nghost) are written as global integer attributes when present, making the file mergeable by tools/merge_output.py. Absent => no attrs written; the file is bit-identical to the pre-MPI single-rank output.

ierr (P7 F1 fix): every internal nc_* call used to error stop unconditionally — a first-run create() against a fresh checkout with no ./output/ directory killed the whole host process from nc_create_file. When ierr is present, any failure (from nc_create_file itself through to the last attribute write) closes the partially-created file, logs + rings the specific NetCDF reason (via nc_check/fail), sets ierr = OCEAN_STATUS_ERR_IO, and returns with is_open left .false. — never error stops. Absent ierr preserves the legacy abort behaviour byte-for-byte.

Arguments

Type IntentOptional Attributes Name
class(ocean_diag_t), intent(inout) :: diag
character(len=*), intent(in) :: filename
integer, intent(in), optional :: deflate_level
integer, intent(in), optional :: px

Number of ranks in the x direction (process grid).

integer, intent(in), optional :: py

Number of ranks in the y direction (process grid).

integer, intent(in), optional :: i_start

1-based global column index of this rank’s first physical cell.

integer, intent(in), optional :: j_start

1-based global row index of this rank’s first physical cell.

integer, intent(in), optional :: nx_global

Total number of physical columns in the global domain.

integer, intent(in), optional :: ny_global

Total number of physical rows in the global domain.

integer, intent(in), optional :: nx_local

Number of physical columns on this rank (excluding ghost cells).

integer, intent(in), optional :: ny_local

Number of physical rows on this rank (excluding ghost cells).

integer, intent(in), optional :: nghost

Number of ghost cells on each side of the local domain.

character(len=*), intent(in), optional :: output_precision

"double" (default / absent) or "single" — element type of the DATA variables. Absent => NC_WP => byte-identical to the pre-knob writer. Time coordinate variables stay NC_WP regardless: they are a handful of values per frame and are routinely differenced.

integer, intent(out), optional :: ierr

Non-zero (OCEAN_STATUS_ERR_IO) on any NetCDF failure when present; absent behaves as today (error stop).


Calls

proc~~open_stream~~CallsGraph proc~open_stream open_stream proc~diag_io_ok diag_io_ok proc~open_stream->proc~diag_io_ok proc~diag_xtype_from_name diag_xtype_from_name proc~open_stream->proc~diag_xtype_from_name proc~nc_create_file nc_create_file proc~open_stream->proc~nc_create_file proc~nc_def_dim nc_def_dim proc~open_stream->proc~nc_def_dim proc~nc_def_var_3d nc_def_var_3d proc~open_stream->proc~nc_def_var_3d proc~nc_def_var_4d nc_def_var_4d proc~open_stream->proc~nc_def_var_4d proc~nc_enddef nc_enddef proc~open_stream->proc~nc_enddef proc~nc_put_att nc_put_att proc~open_stream->proc~nc_put_att proc~nc_put_att_global nc_put_att_global proc~open_stream->proc~nc_put_att_global proc~nc_put_att_global_int nc_put_att_global_int proc~open_stream->proc~nc_put_att_global_int proc~nc_put_att_real nc_put_att_real proc~open_stream->proc~nc_put_att_real proc~nc_put_att_real_r4 nc_put_att_real_r4 proc~open_stream->proc~nc_put_att_real_r4 proc~rdb_def_var_1d rdb_def_var_1d proc~open_stream->proc~rdb_def_var_1d proc~nc_close nc_close proc~diag_io_ok->proc~nc_close warning warning proc~diag_xtype_from_name->warning nf90_create nf90_create proc~nc_create_file->nf90_create proc~nc_check nc_check proc~nc_create_file->proc~nc_check nf90_def_dim nf90_def_dim proc~nc_def_dim->nf90_def_dim proc~nc_def_dim->proc~nc_check nf90_def_var nf90_def_var proc~nc_def_var_3d->nf90_def_var nf90_def_var_deflate nf90_def_var_deflate proc~nc_def_var_3d->nf90_def_var_deflate proc~nc_def_var_3d->proc~nc_check proc~nc_def_var_4d->nf90_def_var proc~nc_def_var_4d->nf90_def_var_deflate proc~nc_def_var_4d->proc~nc_check nf90_enddef nf90_enddef proc~nc_enddef->nf90_enddef proc~nc_enddef->proc~nc_check nf90_put_att nf90_put_att proc~nc_put_att->nf90_put_att proc~nc_put_att->proc~nc_check proc~nc_put_att_global->nf90_put_att proc~nc_put_att_global->proc~nc_check proc~nc_put_att_global_int->nf90_put_att proc~nc_put_att_global_int->proc~nc_check proc~nc_put_att_real->nf90_put_att proc~nc_put_att_real->proc~nc_check proc~nc_put_att_real_r4->nf90_put_att proc~nc_put_att_real_r4->proc~nc_check proc~rdb_def_var_1d->nf90_def_var proc~rdb_def_var_1d->nf90_def_var_deflate proc~rdb_def_var_1d->proc~nc_check nf90_strerror nf90_strerror proc~nc_check->nf90_strerror proc~fail fail proc~nc_check->proc~fail proc~nc_close->proc~nc_check nf90_close nf90_close proc~nc_close->nf90_close error error proc~fail->error proc~error_ring_push error_ring_push proc~fail->proc~error_ring_push

Called by

proc~~open_stream~~CalledByGraph proc~open_stream open_stream proc~engine_configure_diag engine_configure_diag proc~engine_configure_diag->proc~open_stream proc~engine_setup engine_setup proc~engine_setup->proc~engine_configure_diag proc~complete_ocean_create complete_ocean_create proc~complete_ocean_create->proc~engine_setup proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_setup proc~driver_validate driver_validate proc~driver_validate->proc~engine_setup proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean proc~rdb_ocean_create_finalize rdb_ocean_create_finalize proc~rdb_ocean_create_finalize->proc~complete_ocean_create proc~rdb_ocean_create_from_string rdb_ocean_create_from_string proc~rdb_ocean_create_from_string->proc~complete_ocean_create

Variables

Type Visibility Attributes Name Initial
integer, private :: dl
integer, private :: i
integer, private :: local_ierr
integer, private :: n1
integer, private :: n2
integer, private :: n3
integer, private :: n3_max
character(len=80), private :: time_dim_name
character(len=80), private :: z_dim_name

Source Code

   subroutine open_stream(diag, filename, deflate_level, &
                          px, py, i_start, j_start, &
                          nx_global, ny_global, nx_local, ny_local, nghost, &
                          output_precision, ierr)
      !! Create the NetCDF file, define dims + vars for every
      !! currently-registered diag var, and bind the post-fire emit hook.
      !! Must be called AFTER all `register` calls land — vars registered
      !! later get no NetCDF binding and are not written.
      !!
      !! `deflate_level` (0-9, default 0 = uncompressed, bit-identical)
      !! enables gzip compression on data vars; level 1 is usually
      !! optimal (3-5× compression, near-zero CPU).
      !!
      !! The optional decomposition attrs (`px`, `py`, `i_start`, `j_start`,
      !! `nx_global`, `ny_global`, `nx_local`, `ny_local`, `nghost`) are
      !! written as global integer attributes when present, making the file
      !! mergeable by `tools/merge_output.py`.  Absent => no attrs written;
      !! the file is bit-identical to the pre-MPI single-rank output.
      !!
      !! `ierr` (P7 F1 fix): every internal `nc_*` call used to `error
      !! stop` unconditionally — a first-run `create()` against a fresh
      !! checkout with no `./output/` directory killed the whole host
      !! process from `nc_create_file`. When `ierr` is present, any
      !! failure (from `nc_create_file` itself through to the last
      !! attribute write) closes the partially-created file, logs +
      !! rings the specific NetCDF reason (via `nc_check`/`fail`), sets
      !! `ierr = OCEAN_STATUS_ERR_IO`, and returns with `is_open` left
      !! `.false.` — never `error stop`s. Absent `ierr` preserves the
      !! legacy abort behaviour byte-for-byte.
      class(ocean_diag_t), intent(inout) :: diag
      character(len=*), intent(in) :: filename
      integer, intent(in), optional :: deflate_level
      integer, intent(in), optional :: px
         !! Number of ranks in the x direction (process grid).
      integer, intent(in), optional :: py
         !! Number of ranks in the y direction (process grid).
      integer, intent(in), optional :: i_start
         !! 1-based global column index of this rank's first physical cell.
      integer, intent(in), optional :: j_start
         !! 1-based global row index of this rank's first physical cell.
      integer, intent(in), optional :: nx_global
         !! Total number of physical columns in the global domain.
      integer, intent(in), optional :: ny_global
         !! Total number of physical rows in the global domain.
      integer, intent(in), optional :: nx_local
         !! Number of physical columns on this rank (excluding ghost cells).
      integer, intent(in), optional :: ny_local
         !! Number of physical rows on this rank (excluding ghost cells).
      integer, intent(in), optional :: nghost
         !! Number of ghost cells on each side of the local domain.
      character(len=*), intent(in), optional :: output_precision
         !! `"double"` (default / absent) or `"single"` — element type of
         !! the DATA variables.  Absent => `NC_WP` => byte-identical to the
         !! pre-knob writer.  Time coordinate variables stay `NC_WP`
         !! regardless: they are a handful of values per frame and are
         !! routinely differenced.
      integer, intent(out), optional :: ierr
         !! Non-zero (`OCEAN_STATUS_ERR_IO`) on any NetCDF failure when
         !! present; absent behaves as today (`error stop`).
      integer :: i, n1, n2, n3, dl, n3_max
      integer :: local_ierr

      character(len=80) :: time_dim_name, z_dim_name

      if (present(ierr)) ierr = OCEAN_STATUS_OK

      dl = 0
      if (present(deflate_level)) dl = max(0, min(9, deflate_level))

      if (diag%nc_stream%is_open) return
      diag%nc_stream%filename = filename

      diag%nc_stream%xtype = NC_WP
      if (present(output_precision)) then
         diag%nc_stream%xtype = diag_xtype_from_name(output_precision)
      end if

      call nc_create_file(filename, diag%nc_stream%ncid, ierr=local_ierr)
      if (.not. diag_io_ok(local_ierr, ierr)) return
      call nc_put_att_global(diag%nc_stream%ncid, "Conventions", "CF-1.8", ierr=local_ierr)
      if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      call nc_put_att_global(diag%nc_stream%ncid, "title", &
                             "Roundabout ocean-path diagnostics", ierr=local_ierr)
      if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return

      ! Decomposition window attrs — written only in multi-rank runs so
      ! single-rank files stay bit-identical.  The merge tool uses these to
      ! place each rank's physical-cell slice into the global array, trimming
      ! the `nghost` halo on each side before stitching.
      if (present(px)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "px", px, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(py)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "py", py, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(i_start)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "i_start", i_start, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(j_start)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "j_start", j_start, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(nx_global)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "nx_global", nx_global, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(ny_global)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "ny_global", ny_global, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(nx_local)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "nx_local", nx_local, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(ny_local)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "ny_local", ny_local, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if
      if (present(nghost)) then
         call nc_put_att_global_int(diag%nc_stream%ncid, "nghost", nghost, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if

      ! Spatial dims from var 1's output buffer (all vars share the
      ! same horizontal extent).
      if (diag%nvars > 0) then
         n1 = size(diag%vars(1)%output_buffer, 1)
         n2 = size(diag%vars(1)%output_buffer, 2)
         call nc_def_dim(diag%nc_stream%ncid, "x", n1, diag%nc_stream%x_dimid, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
         call nc_def_dim(diag%nc_stream%ncid, "y", n2, diag%nc_stream%y_dimid, ierr=local_ierr)
         if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
      end if

      do i = 1, diag%nvars
         associate (v => diag%vars(i))
            n3 = size(v%output_buffer, 3)

            ! Per-var time dim + coord var (unlimited).
            write (time_dim_name, "(A,A)") "time_", trim(v%name)
            call nc_def_dim(diag%nc_stream%ncid, trim(time_dim_name), &
                            nf90_unlimited, v%nc_time_dimid, ierr=local_ierr)
            if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            call rdb_def_var_1d(diag%nc_stream%ncid, trim(time_dim_name), &
                                v%nc_time_dimid, v%nc_time_varid, ierr=local_ierr)
            if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            call nc_put_att(diag%nc_stream%ncid, v%nc_time_varid, &
                            "units", "seconds since simulation start", ierr=local_ierr)
            if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            call nc_put_att(diag%nc_stream%ncid, v%nc_time_varid, &
                            "long_name", "time", ierr=local_ierr)
            if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return

            ! Data var: (x, y, time) for 2D; (x, y, z_<name>, time) for 3D.
            if (n3 == 1) then
               call nc_def_var_3d(diag%nc_stream%ncid, trim(v%name), &
                                  [diag%nc_stream%x_dimid, diag%nc_stream%y_dimid, &
                                   v%nc_time_dimid], v%nc_varid, &
                                  deflate_level=dl, xtype=diag%nc_stream%xtype, ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            else
               write (z_dim_name, "(A,A)") "z_", trim(v%name)
               call nc_def_dim(diag%nc_stream%ncid, trim(z_dim_name), &
                               n3, v%nc_z_dimid, ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
               call nc_def_var_4d(diag%nc_stream%ncid, trim(v%name), &
                                  [diag%nc_stream%x_dimid, diag%nc_stream%y_dimid, &
                                   v%nc_z_dimid, v%nc_time_dimid], v%nc_varid, &
                                  deflate_level=dl, xtype=diag%nc_stream%xtype, ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            end if

            if (len_trim(v%units) > 0) then
               call nc_put_att(diag%nc_stream%ncid, v%nc_varid, "units", trim(v%units), ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            end if
            if (len_trim(v%long_name) > 0) then
               call nc_put_att(diag%nc_stream%ncid, v%nc_varid, "long_name", trim(v%long_name), ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            end if
            if (len_trim(v%standard_name) > 0) then
               call nc_put_att(diag%nc_stream%ncid, v%nc_varid, &
                               "standard_name", trim(v%standard_name), ierr=local_ierr)
               if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
            end if
            if (v%has_missing) then
               ! Vanished (no-water) target cells carry the sentinel; advertise
               ! it both ways so CF-aware (_FillValue) and legacy
               ! (missing_value) tools mask below-bottom / dry cells.
               !
               ! NetCDF REJECTS a `_FillValue` whose type differs from the
               ! variable's, so the attribute has to follow `xtype`.  The
               ! real32 branch writes `real(DIAG_MISSING_VALUE, real32)` —
               ! the exact same value the data conversion produces for a
               ! sentinel cell, so masking still matches bit-for-bit.
               if (diag%nc_stream%xtype == NC_R4) then
                  call nc_put_att_real_r4(diag%nc_stream%ncid, v%nc_varid, &
                                          "_FillValue", real(DIAG_MISSING_VALUE, real32), ierr=local_ierr)
                  if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
                  call nc_put_att_real_r4(diag%nc_stream%ncid, v%nc_varid, &
                                          "missing_value", real(DIAG_MISSING_VALUE, real32), ierr=local_ierr)
                  if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
               else
                  call nc_put_att_real(diag%nc_stream%ncid, v%nc_varid, &
                                       "_FillValue", DIAG_MISSING_VALUE, ierr=local_ierr)
                  if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
                  call nc_put_att_real(diag%nc_stream%ncid, v%nc_varid, &
                                       "missing_value", DIAG_MISSING_VALUE, ierr=local_ierr)
                  if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return
               end if
            end if

            v%nc_time_index = 0
         end associate
      end do

      call nc_enddef(diag%nc_stream%ncid, ierr=local_ierr)
      if (.not. diag_io_ok(local_ierr, ierr, diag%nc_stream%ncid)) return

      ! Single-precision staging buffer.  One shared scratch covering the
      ! largest registered `output_buffer`: every var shares (n1, n2), so
      ! only the vertical extent varies and `n3_max` bounds them all.  This
      ! is why `open_stream` must run after every `register` — the same
      ! precondition the per-var NetCDF binding above already imposes.
      if (diag%nc_stream%xtype == NC_R4 .and. diag%nvars > 0) then
         n3_max = 1
         do i = 1, diag%nvars
            n3_max = max(n3_max, size(diag%vars(i)%output_buffer, 3))
         end do
         if (allocated(diag%nc_stream%stage)) deallocate (diag%nc_stream%stage)
         allocate (diag%nc_stream%stage(n1, n2, n3_max))
      end if

      diag%nc_stream%is_open = .true.
      diag%emit_post_fire => nc_emit_post_fire
   end subroutine open_stream