config.f90 Source File


This file depends on

sourcefile~~config.f90~~EfferentGraph sourcefile~config.f90 config.f90 sourcefile~common.f90~3 common.f90 sourcefile~config.f90->sourcefile~common.f90~3 sourcefile~mpi.f90 mpi.f90 sourcefile~common.f90~3->sourcefile~mpi.f90

Files dependent on this one

sourcefile~~config.f90~~AfferentGraph sourcefile~config.f90 config.f90 sourcefile~abl.f90 abl.f90 sourcefile~abl.f90->sourcefile~config.f90 sourcefile~les.f90 les.f90 sourcefile~abl.f90->sourcefile~les.f90 sourcefile~abl.f90~2 abl.f90 sourcefile~abl.f90~2->sourcefile~config.f90 sourcefile~abl.f90~2->sourcefile~abl.f90 sourcefile~base_case.f90 base_case.f90 sourcefile~abl.f90~2->sourcefile~base_case.f90 sourcefile~diagnostics.f90 diagnostics.f90 sourcefile~abl.f90~2->sourcefile~diagnostics.f90 sourcefile~base_case.f90->sourcefile~config.f90 sourcefile~solver.f90 solver.f90 sourcefile~base_case.f90->sourcefile~solver.f90 sourcefile~io_manager.f90 io_manager.f90 sourcefile~base_case.f90->sourcefile~io_manager.f90 sourcefile~monitoring.f90 monitoring.f90 sourcefile~base_case.f90->sourcefile~monitoring.f90 sourcefile~postprocess.f90 postprocess.f90 sourcefile~base_case.f90->sourcefile~postprocess.f90 sourcefile~channel.f90 channel.f90 sourcefile~channel.f90->sourcefile~config.f90 sourcefile~channel.f90->sourcefile~base_case.f90 sourcefile~channel.f90->sourcefile~solver.f90 sourcefile~checkpoint_manager.f90 checkpoint_manager.f90 sourcefile~checkpoint_manager.f90->sourcefile~config.f90 sourcefile~checkpoint_manager.f90->sourcefile~solver.f90 sourcefile~stats.f90 stats.f90 sourcefile~checkpoint_manager.f90->sourcefile~stats.f90 sourcefile~io_field_utils.f90 io_field_utils.f90 sourcefile~checkpoint_manager.f90->sourcefile~io_field_utils.f90 sourcefile~cylinder.f90 cylinder.f90 sourcefile~cylinder.f90->sourcefile~config.f90 sourcefile~cylinder.f90->sourcefile~base_case.f90 sourcefile~cylinder.f90->sourcefile~solver.f90 sourcefile~diagnostics.f90->sourcefile~config.f90 sourcefile~diagnostics.f90->sourcefile~les.f90 sourcefile~les.f90->sourcefile~config.f90 sourcefile~snapshot_manager.f90 snapshot_manager.f90 sourcefile~snapshot_manager.f90->sourcefile~config.f90 sourcefile~snapshot_manager.f90->sourcefile~solver.f90 sourcefile~snapshot_manager.f90->sourcefile~io_field_utils.f90 sourcefile~solver.f90->sourcefile~config.f90 sourcefile~solver.f90->sourcefile~les.f90 sourcefile~stats.f90->sourcefile~config.f90 sourcefile~stats.f90->sourcefile~solver.f90 sourcefile~xcompact.f90 xcompact.f90 sourcefile~xcompact.f90->sourcefile~config.f90 sourcefile~xcompact.f90->sourcefile~abl.f90~2 sourcefile~xcompact.f90->sourcefile~base_case.f90 sourcefile~xcompact.f90->sourcefile~channel.f90 sourcefile~xcompact.f90->sourcefile~cylinder.f90 sourcefile~generic.f90 generic.f90 sourcefile~xcompact.f90->sourcefile~generic.f90 sourcefile~tgv.f90 tgv.f90 sourcefile~xcompact.f90->sourcefile~tgv.f90 sourcefile~generic.f90->sourcefile~base_case.f90 sourcefile~generic.f90->sourcefile~solver.f90 sourcefile~io_field_utils.f90->sourcefile~solver.f90 sourcefile~io_manager.f90->sourcefile~checkpoint_manager.f90 sourcefile~io_manager.f90->sourcefile~snapshot_manager.f90 sourcefile~io_manager.f90->sourcefile~solver.f90 sourcefile~io_manager.f90->sourcefile~stats.f90 sourcefile~monitoring.f90->sourcefile~solver.f90 sourcefile~postprocess.f90->sourcefile~solver.f90 sourcefile~tgv.f90->sourcefile~base_case.f90 sourcefile~tgv.f90->sourcefile~solver.f90

Source Code

module m_config
  !! Contains all the namelist configurations used in x3d2
  use iso_fortran_env, only: stderr => error_unit

  use m_common

  implicit none

  integer, parameter :: n_species_max = 99

  !! Maximum number of additional snapshot output fields accepted from the
  !! input file. Some entries, such as ``species``, can expand to multiple
  !! written snapshot fields.
  integer, parameter :: MAX_OUTPUT_FIELDS = 10

  type, abstract :: base_config_t
    !! All config types have a method read to initialise their data
  contains
    procedure(read), deferred :: read
  end type base_config_t

  type, extends(base_config_t) :: domain_config_t
    character(len=30) :: flow_case_name
    real(dp) :: L_global(3)
    integer :: dims_global(3), nproc_dir(3)
    character(len=20) :: BC_x(2), BC_y(2), BC_z(2)
    character(len=20) :: stretching(3)
    real(dp) :: beta(3)
  contains
    procedure :: read => read_domain_nml
  end type domain_config_t

  type, extends(base_config_t) :: solver_config_t
    real(dp) :: Re, dt
    logical :: ibm_on
    real(dp), dimension(:), allocatable :: pr_species
    integer :: n_iters, n_output, n_species
    logical :: lowmem_transeq, lowmem_fft
    logical :: spatial_filter          !! explicit low-pass filter on velocity
    real(dp) :: filter_alpha           !! filter parameter, -0.5 < alpha < 0.5
    character(3) :: poisson_solver_type, time_intg
    character(30) :: der1st_scheme, der2nd_scheme, &
                     interpl_scheme, stagder_scheme
  contains
    procedure :: read => read_solver_nml
  end type solver_config_t

  type, extends(base_config_t) :: les_config_t
    !! Configuration for explicit sub-grid-scale modelling.
    character(len=20) :: model = 'none'
    real(dp) :: smagorinsky_constant = 0.14_dp
    logical :: wall_damping = .false.
    real(dp) :: wall_damping_n = 3._dp
  contains
    procedure :: read => read_les_nml
  end type les_config_t

  type, extends(base_config_t) :: channel_config_t
    real(dp) :: omega_rot
    real(dp) :: init_noise(3)
    real(dp) :: inlet_noise(3)
    logical :: rotation
    integer :: n_rotate
  contains
    procedure :: read => read_channel_nml
  end type channel_config_t

  type, extends(base_config_t) :: cylinder_config_t
    real(dp) :: init_noise(3)
    real(dp) :: inlet_noise(3)
  contains
    procedure :: read => read_cylinder_nml
  end type cylinder_config_t

  type, extends(base_config_t) :: abl_config_t
    real(dp) :: z0 = 0.1_dp            !! aerodynamic roughness length
    real(dp) :: u_star = 0._dp         !! friction velocity
    real(dp) :: delta = 1._dp          !! boundary-layer depth
    real(dp) :: kappa = 0.41_dp        !! von Karman constant
    real(dp) :: dsampling = 3._dp      !! wall-model sampling height, in dy
    real(dp) :: u_geo(3) = 0._dp       !! geostrophic wind UG
    real(dp) :: coriolis_freq = 0._dp  !! Coriolis frequency f
    real(dp) :: init_noise(3) = 0._dp
    !> Seed for the initial noise; 0 draws one from the clock
    integer :: seed = 0
    real(dp) :: u_bulk = 0._dp         !! target bulk velocity (mass_conserve)
    integer :: profile_start_iter = -1 !! first profile sample (-1 = disabled)
    character(len=256) :: profile_file = 'abl_profile.csv'
    logical :: pressure_gradient = .false.
    logical :: coriolis = .false.
    logical :: mass_conserve = .false.
    logical :: damping = .false.
  contains
    procedure :: read => read_abl_nml
  end type abl_config_t

  type, extends(base_config_t) :: stats_config_t
    integer :: initstat = 0          !! iteration to start accumulating (0 = disabled)
    integer :: istatfreq = 1         !! accumulate every N steps
    integer :: istatout = 0          !! write stats every N steps (0 = disabled)
    character(len=256) :: stats_prefix = "statistics"
  contains
    procedure :: read => read_stats_nml
  end type stats_config_t

  type, extends(base_config_t) :: checkpoint_config_t
    integer :: checkpoint_freq = 0                         !! Frequency of checkpointing (0 = off)
    integer :: snapshot_freq = 0                           !! Frequency of snapshots (0 = off)
    logical :: keep_checkpoint = .true.                    !! If false, only keep latest checkpoint
    character(len=256) :: checkpoint_prefix = "checkpoint"
    character(len=256) :: snapshot_prefix = "snapshot"
    logical :: restart_from_checkpoint = .false.
    character(len=256) :: restart_file = ""
    integer, dimension(3) :: output_stride = [2, 2, 2]     !! Spatial stride for snapshot output
    logical :: snapshot_sp = .false.                       !! if true, snapshot in single precision
    character(len=32) :: output_fields(MAX_OUTPUT_FIELDS) = '' !! additional snapshot output fields
  contains
    procedure :: read => read_checkpoint_nml
  end type checkpoint_config_t

  abstract interface
    subroutine read(self, nml_file, nml_string) !&
      !! Assigns the member variables either from a file or text source.
      !!
      !! nml_file can be an absolute or relative path
      !! nml_string is a character string that contains the namelist.
      !! For example, nml_string="&foobar_nml foo=0, bar='this'/"
      import :: base_config_t

      class(base_config_t) :: self
      character(*), optional, intent(in) :: nml_file
      character(*), optional, intent(in) :: nml_string
    end subroutine read
  end interface

contains

  subroutine read_domain_nml(self, nml_file, nml_string)
    implicit none

    class(domain_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit

    character(len=20) :: flow_case_name
    real(dp), dimension(3) :: L_global
    integer, dimension(3) :: dims_global
    integer, dimension(3) :: nproc_dir
    character(len=20) :: BC_x(2), BC_y(2), BC_z(2)
    character(len=20) :: stretching(3) = ['uniform', 'uniform', 'uniform']
    real(dp), dimension(3) :: beta

    namelist /domain_settings/ flow_case_name, L_global, dims_global, &
      nproc_dir, BC_x, BC_y, BC_z, stretching, beta

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading domain config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file)
      read (unit, nml=domain_settings)
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=domain_settings)
    else
      error stop 'Reading domain config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%flow_case_name = flow_case_name
    self%L_global = L_global
    self%dims_global = dims_global
    self%nproc_dir = nproc_dir
    self%BC_x = BC_x
    self%BC_y = BC_y
    self%BC_z = BC_z
    self%stretching = stretching
    self%beta = beta

  end subroutine read_domain_nml

  subroutine read_solver_nml(self, nml_file, nml_string)
    implicit none

    class(solver_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit

    real(dp) :: Re, dt
    logical :: ibm_on = .false.
    real(dp), dimension(n_species_max) :: pr_species = 1._dp
    integer :: n_iters, n_output, n_species = 0
    !> triggers the low memory implementations
    logical :: lowmem_transeq = .false., lowmem_fft = .false.
    !! Explicit low-pass filter on the velocity, off by default. Needed by
    !! wall-modelled ABL runs, where the 2*dx mode the compact schemes cannot
    !! dissipate otherwise breaks momentum conservation in the convection.
    logical :: spatial_filter = .false.
    !! Incompact3d's value. Smaller alpha is far more dissipative: at 0.40 the
    !! filter damps every resolved wavenumber about ten times harder per
    !! application than at 0.49, which over a long run scrubs out the smallest
    !! resolved eddies. The filter's system is only marginally diagonally
    !! dominant here, so it is solved with Thomas rather than the distributed
    !! algorithm wherever the direction is not decomposed.
    real(dp) :: filter_alpha = 0.49_dp
    character(3) :: time_intg
    character(3) :: poisson_solver_type = 'FFT'
    character(30) :: der1st_scheme = 'compact6', der2nd_scheme = 'compact6', &
                     interpl_scheme = 'classic', stagder_scheme = 'compact6'

    namelist /solver_params/ Re, dt, n_iters, n_output, poisson_solver_type, &
      spatial_filter, filter_alpha, &
      n_species, pr_species, lowmem_transeq, lowmem_fft, &
      time_intg, der1st_scheme, der2nd_scheme, interpl_scheme, &
      stagder_scheme, ibm_on

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading solver config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file)
      read (unit, nml=solver_params)
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=solver_params)
    else
      error stop 'Reading solver config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%Re = Re
    self%dt = dt
    self%n_iters = n_iters
    self%n_output = n_output
    self%spatial_filter = spatial_filter
    self%filter_alpha = filter_alpha
    self%ibm_on = ibm_on
    self%n_species = n_species
    if (n_species > 0) self%pr_species = pr_species(1:n_species)
    self%lowmem_transeq = lowmem_transeq
    self%lowmem_fft = lowmem_fft
    self%poisson_solver_type = poisson_solver_type
    self%time_intg = time_intg
    self%der1st_scheme = der1st_scheme
    self%der2nd_scheme = der2nd_scheme
    self%interpl_scheme = interpl_scheme
    self%stagder_scheme = stagder_scheme

  end subroutine read_solver_nml

  subroutine read_les_nml(self, nml_file, nml_string)
    class(les_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit, ierr
    character(len=20) :: model
    real(dp) :: smagorinsky_constant, wall_damping_n
    logical :: wall_damping

    namelist /les_params/ model, smagorinsky_constant, wall_damping, &
      wall_damping_n

    model = self%model
    smagorinsky_constant = self%smagorinsky_constant
    wall_damping = self%wall_damping
    wall_damping_n = self%wall_damping_n

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading LES config failed! &
                 & Provide only a file name or source.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file, iostat=ierr)
      if (ierr /= 0) error stop 'Opening LES config file failed.'
      read (unit, nml=les_params, iostat=ierr)
      close (unit)
      ! Existing input files may omit this optional namelist. End-of-file
      ! therefore selects the defaults, while a malformed block remains fatal.
      if (ierr > 0) error stop 'Reading LES config failed.'
    else if (present(nml_string)) then
      read (nml_string, nml=les_params)
    else
      error stop 'Reading LES config failed! Provide a file name or source.'
    end if

    select case (trim(model))
    case ('none', 'smagorinsky')
    case default
      error stop 'Unknown LES model. Use "none" or "smagorinsky".'
    end select
    if (smagorinsky_constant <= 0._dp) &
      error stop 'smagorinsky_constant must be positive.'
    if (wall_damping_n <= 0._dp) &
      error stop 'wall_damping_n must be positive.'

    self%model = trim(model)
    self%smagorinsky_constant = smagorinsky_constant
    self%wall_damping = wall_damping
    self%wall_damping_n = wall_damping_n
  end subroutine read_les_nml

  subroutine read_channel_nml(self, nml_file, nml_string)
    implicit none

    class(channel_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit

    real(dp) :: init_noise(3)
    real(dp) :: inlet_noise(3)
    real(dp) :: omega_rot
    logical :: rotation
    integer :: n_rotate

    namelist /channel_nml/ init_noise, inlet_noise, &
      rotation, omega_rot, n_rotate

    ! Default to no noise if the namelist omits these entries.
    init_noise = 0._dp
    inlet_noise = 0._dp

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading channel config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file)
      read (unit, nml=channel_nml)
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=channel_nml)
    else
      error stop 'Reading channel config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%init_noise = init_noise
    self%inlet_noise = inlet_noise
    self%rotation = rotation
    self%omega_rot = omega_rot
    self%n_rotate = n_rotate

  end subroutine read_channel_nml

  subroutine read_cylinder_nml(self, nml_file, nml_string)
    implicit none

    class(cylinder_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit

    real(dp) :: init_noise(3)
    real(dp) :: inlet_noise(3)

    namelist /cylinder_nml/ init_noise, inlet_noise

    ! Default to no noise if the namelist omits these entries.
    init_noise = 0._dp
    inlet_noise = 0._dp

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading cylinder config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file)
      read (unit, nml=cylinder_nml)
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=cylinder_nml)
    else
      error stop 'Reading cylinder config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%init_noise = init_noise
    self%inlet_noise = inlet_noise

  end subroutine read_cylinder_nml

  subroutine read_abl_nml(self, nml_file, nml_string)
    implicit none

    class(abl_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit

    real(dp) :: z0, u_star, delta, kappa, dsampling
    real(dp) :: u_geo(3), coriolis_freq, init_noise(3), u_bulk
    integer :: profile_start_iter, seed
    character(len=256) :: profile_file
    logical :: pressure_gradient, coriolis, mass_conserve, damping

    namelist /abl_nml/ z0, u_star, delta, kappa, dsampling, &
      u_geo, coriolis_freq, &
      init_noise, seed, u_bulk, profile_start_iter, profile_file, &
      pressure_gradient, coriolis, mass_conserve, damping

    ! Defaults
    z0 = 0.1_dp
    u_star = 0._dp
    delta = 1._dp
    kappa = 0.41_dp
    dsampling = 3._dp
    u_geo = 0._dp
    coriolis_freq = 0._dp
    init_noise = 0._dp
    seed = 0
    u_bulk = 0._dp
    profile_start_iter = -1
    profile_file = 'abl_profile.csv'
    pressure_gradient = .false.
    coriolis = .false.
    mass_conserve = .false.
    damping = .false.

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading ABL config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file)
      read (unit, nml=abl_nml)
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=abl_nml)
    else
      error stop 'Reading ABL config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%z0 = z0
    self%u_star = u_star
    self%delta = delta
    self%kappa = kappa
    self%dsampling = dsampling
    self%u_geo = u_geo
    self%coriolis_freq = coriolis_freq
    self%init_noise = init_noise
    self%seed = seed
    self%u_bulk = u_bulk
    self%profile_start_iter = profile_start_iter
    self%profile_file = trim(profile_file)
    self%pressure_gradient = pressure_gradient
    self%coriolis = coriolis
    self%mass_conserve = mass_conserve
    self%damping = damping

    call validate_abl_config(self)

  end subroutine read_abl_nml

  subroutine validate_abl_config(config)
    !! Validate relationships between ABL namelist values in one place.
    type(abl_config_t), intent(in) :: config

    ! Damping is a sponge rather than a driving mechanism.
    if (.not. (config%pressure_gradient .or. config%coriolis .or. &
               config%mass_conserve)) &
      error stop 'ABL config error: enable pressure_gradient, coriolis, &
                 &or mass_conserve.'

    if (config%z0 <= 0._dp .or. config%delta <= config%z0 .or. &
        config%kappa <= 0._dp) &
      error stop 'ABL config error: require delta > z0 > 0 and kappa > 0.'

    if (any(config%init_noise < 0._dp)) &
      error stop 'ABL config error: init_noise must not be negative.'
    if (config%seed < 0) &
      error stop 'ABL config error: seed must not be negative.'

    if (config%profile_start_iter < -1) &
      error stop 'ABL config error: profile_start_iter must be -1 or greater.'
    if (config%profile_start_iter >= 0 .and. &
        len_trim(config%profile_file) == 0) &
      error stop 'ABL config error: profile_file must not be empty.'
  end subroutine validate_abl_config

  subroutine read_checkpoint_nml(self, nml_file, nml_string)
    implicit none

    class(checkpoint_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit, ierr

    integer :: checkpoint_freq = 0
    integer :: snapshot_freq = 0
    logical :: keep_checkpoint = .false.
    character(len=256) :: checkpoint_prefix = "checkpoint"
    character(len=256) :: snapshot_prefix = "snapshot"
    logical :: restart_from_checkpoint = .false.
    character(len=256) :: restart_file = ""
    integer, dimension(3) :: output_stride = [1, 1, 1]
    logical :: snapshot_sp = .false.
    character(len=32) :: output_fields(MAX_OUTPUT_FIELDS) = ''

    namelist /checkpoint_params/ checkpoint_freq, snapshot_freq, &
      keep_checkpoint, checkpoint_prefix, snapshot_prefix, &
      restart_from_checkpoint, restart_file, output_stride, snapshot_sp, &
      output_fields
    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading checkpoint config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file, iostat=ierr)
      if (ierr == 0) then
        read (unit, nml=checkpoint_params, iostat=ierr)

        if (ierr /= 0 .and. ierr /= -1) &
          print *, 'WARNING: Error in checkpoint_params namelist, &
          & using defaults'
      end if
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=checkpoint_params)
    else
      error stop 'Reading checkpoint config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%checkpoint_freq = checkpoint_freq
    self%snapshot_freq = snapshot_freq
    self%keep_checkpoint = keep_checkpoint
    self%checkpoint_prefix = checkpoint_prefix
    self%snapshot_prefix = snapshot_prefix
    self%restart_from_checkpoint = restart_from_checkpoint
    self%restart_file = restart_file
    self%output_stride = output_stride
    self%snapshot_sp = snapshot_sp
    self%output_fields = output_fields
  end subroutine read_checkpoint_nml

  subroutine read_stats_nml(self, nml_file, nml_string)
    implicit none

    class(stats_config_t) :: self
    character(*), optional, intent(in) :: nml_file
    character(*), optional, intent(in) :: nml_string

    integer :: unit, ierr

    integer :: initstat = 0
    integer :: istatfreq = 1
    integer :: istatout = 0
    character(len=256) :: stats_prefix = "statistics"

    namelist /stats_params/ initstat, istatfreq, istatout, stats_prefix

    if (present(nml_file) .and. present(nml_string)) then
      error stop 'Reading stats config failed! &
                 &Provide only a file name or source, not both.'
    else if (present(nml_file)) then
      open (newunit=unit, file=nml_file, iostat=ierr)
      if (ierr == 0) then
        read (unit, nml=stats_params, iostat=ierr)
        if (ierr /= 0 .and. ierr /= -1) &
          print *, 'WARNING: Error in stats_params namelist, &
          & using defaults'
      end if
      close (unit)
    else if (present(nml_string)) then
      read (nml_string, nml=stats_params)
    else
      error stop 'Reading stats config failed! &
                 &Provide at least one of the following: file name or source'
    end if

    self%initstat = initstat
    self%istatfreq = istatfreq
    self%istatout = istatout
    self%stats_prefix = stats_prefix
  end subroutine read_stats_nml

  pure logical function has_output_field(config, name)
    !! Check whether a field name is present in the output_fields list.
    type(checkpoint_config_t), intent(in) :: config
    character(*), intent(in) :: name

    has_output_field = any(config%output_fields == name)
  end function has_output_field

end module m_config