! Copyright 2008-2021 Earth Sciences Department, BSC-CNS
!
! This file is part of MONARCH.
!
! MONARCH is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! MONARCH is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with MONARCH. If not, see <http://www.gnu.org/licenses/>.

!> \file
!> The monarch_diagnostics_t object and related functions

!> \page using_diagnostics Using diagnostics
!!
!! \c monarch_diagnostics can be used to retrieve basic information
!! about the current state of a model domain. Diagnostics that apply to an
!! entire vertical column may be available from the \c monarch_column whose
!! usage is described \ref using_column_diagnostics "here". To use a
!! \c monarch_diagnostics::monarch_diagnostics_t object, first attach it to
!! an instance of the \c module_solver_internal_state::solver_internal_state
!! type that contains the current model state, and an
!! \ref module_aero_rep "aerosol representation" that describes the current
!! MONARCH aerosol scheme:
!! \code{.f90}
!!
!!   type(solver_internal_state), target :: int_state
!!   type(monarch_aerosol_representation_ptr) :: aero_rep_ptr
!!   type(monarch_diagnostics_t) :: diag
!!
!!   ! (The solver_internal_state is typically passed through the ESMF
!!   !  import/export states of the solver_grid_component and is
!!   !  available in the SOLVER_RUN subroutine.)
!!
!!   ! (There is also an aerosol representation variable set up in the
!!   !  the SOLVER_RUN subroutine, but it can be created as follows:)
!!   aero_rep_ptr%val => monarch_aero_rep_my_cool_aero_scheme_t( int_state )
!!
!!   ! Attach the diagnostics object to the solver internal state
!!   call diag%attach( int_state, aero_rep_ptr%val )
!!
!! \endcode
!! After the diagnostics object is attached, get some state properties like
!! this:
!! \code{.f90}
!!
!!   real(kind=mdbl)    :: utc
!!   integer(kind=mint) :: mnth
!!
!!   utc = diag%mid_time_step_UTC__s( )
!!   mnth = diag%current_month( )
!!
!! \endcode
!! For properties that are location-specific (grid-cell, column, surface,
!! etc.) the \c monarch_util::monarch_point_t is available.
!! \code{.f90}
!!
!!   type(monarch_point_t) :: cell
!!
!!   ! Do this once to set the internal boundaries of the point object
!!   cell = diag%new_point( )
!!
!! \endcode
!! Now that the point is set up, specify a location by setting the spatial
!! indices:
!! \code{.f90}
!!
!!   call cell%set_WE_idx(       2  )
!!   call cell%set_SN_idx(       52 )
!!   call cell%set_vertical_idx( 12 )
!!
!! \endcode
!! Only the indices needed to determine a state property will be used. If, for
!! example, you're only interested in total column properties, you only need
!! to set the two horizontal indices. Also, the diagnostics object figures out
!! the state array dimensions internally, so the point indices always
!! increase going West -> East, South -> North, and surface -> highest layer.
!!
!! Now we're ready to get location-specific properties:
!! \code{.f90}
!!
!!   real(kind=mdbl) :: P, T, RH
!!
!!   T  = diag%temperature__K( cell )
!!   P  = diag%mid_layer_pressure__Pa( cell )
!!   RH = diag%relative_humidity__dec( cell )
!!
!! \endcode
!! To keep things fast, functions in \c monarch_diagnostics are kept small with
!! the expectation that the compiler can be asked to in-line the functions
!! directly into the calling routines. Also, to avoid the introduction of
!! unintended inefficiency, no \c monarch_diagnostics functions include loops
!! through multiple grid cells. For diagnostics whose calculation requires
!! information about the state of each grid cell in a vertical column, the
!! \c monarch_column is available, and described
!! \ref using_column_diagnostics "here".
!!
!! Instructions for adding new diagnostics can be found
!! \ref adding_diagnostics "here".

!> \page adding_diagnostics Adding a new diagnostic
!!
!! This page describes how to add a new diagnostic to the
!! \c monarch_diagnostics::monarch_diagnostics_t type. These instructions
!! should be followed for simple diagnostic calculations that do not require
!! iteration over, e.g., multiple grid cells. For more intensive calculations
!! that are performed for an entire vertical column, the \c monarch_column is
!! available. Instructions describing the addition of column-based diagnostics
!! can be found \ref adding_column_diagnostics "here".
!!
!! ## Naming ##
!!
!! Any name may be used for new diagnostics, but it is recommended to follow
!! the general format \c property_name__units() where the
!! full name of the property is used as the function name followed by two
!! underscores and the units (unless the property is unitless). Avoid using
!! names that may have several meanings depending on the context (e.g.,
!! alpha, deltaT, shape_param, paul). Properties should always be returned in
!! SI units.
!!
!! ## Adding a diagnostic ##
!!
!! The \c monarch_diagnostics::monarch_diagnostics_t type is defined in the
!! \c monarch_diagnostics module, and is followed by the function definitions
!! for each diagnostic.
!!
!! Diagnostics can be added by performing the following steps:
!!
!!   1. Add the name of your new diagnostic to the
!!      \c monarch_diagnostics::monarch_diagnostics_t type's bound procedures:
!!      \code{.f90}
!!        !> Model state diagnostics
!!        type monarch_diagnostics_t
!!          ...
!!        contains
!!          ...
!!          !> Calculates the Sbornak-Nylund tension parameter [m2 s-1]
!!          procedure :: sbornak_nylund_tension__m2s
!!          ...
!!        end type monarch_diagnostics_t
!!      \endcode
!!      The order of the type-bound procedures is arbitrary, but they are
!!      roughly grouped by the domain they apply to.
!!
!!   2. Add the function to calculate your diagnostic to the module functions:
!!
!!     - The \c module_SOLVER_INTERNAL_STATE::solver_internal_state
!!       that describes the current state of the model is available from the
!!       \c monarch_diagnostics::monarch_diagnostics_t object. All members of
!!       the \c solver_internal_state as well as other monarch_diagnostics
!!       functions are available to your function.
!!
!!     - If your diagnostic does not apply to a specific model location, it
!!       can accept only a \c monarch_diagnostics::monarch_diagnostics_t
!!       as input:
!!       \code{.f90}
!!         !> Calculates the Sbornak-Nylund tension parameter [m2 s-1]
!!         function sbornak_nyland_tension__m2s( this ) result( tension )
!!
!!           !> Tension parameter [m2 s-1]
!!           real(kind=mdbl) :: tension
!!           !> Diagnostics object
!!           class(monarch_diagnostics_t), intent(in) :: diag
!!
!!           tension = this%mid_timestep_GMT__s( ) * &                       ! <-- diagnostic function
!!                     this%int_state%NTSD                                   ! <-- solver_internal_state variable
!!
!!         end function sbornak_nyland_tension__m2s
!!       \endcode
!!
!!     - If your diagnostic applies to a specific location (e.g., a grid cell,
!!       column, surface, etc.), it can also accept a
!!       \c monarch_util::monarch_point_t object that will contain indices
!!       for the requested location. The indicies can be used to determine
!!       \c solver_internal_state array indices or can be passed to other
!!       \c monarch_diagnostics functions.
!!       \code{.f90}
!!         !> Calculates the Sbornak-Nylund tension parameter [m2 s-1]
!!         function sbornak_nyland_tension__m2s( this, indices )             &
!!             result( tension )
!!
!!           !> Tension parameter [m2 s-1]
!!           real(kind=mdbl) :: tension
!!           !> Diagnostics object
!!           class(monarch_diagnostics_t), intent(in) :: this
!!           !> Grid cell indices
!!           type(monarch_point_t), intent(in) :: indices
!!
!!           tension = this%mid_timestep_GMT__s( )                         * & ! <-- diagnostic function
!!                     this%rain_mixing_ratio__kgkg( indices )             + & ! <-- location-specific diagnostic function
!!                     this%int_state%NTSD                                 / & ! <-- solver_internal_state variable
!!                     this%int_state%T( indices%WE_idx( ),                  & ! <-- solver_internal_state array
!!                                       indices%SN_idx( ),                  &
!!                                       indices%flipped_vertical_idx( ) )
!!
!!         end function sbornak_nyland_tension__m2s
!!       \endcode
!!       The tracer indices available from the
!!       \c monarch_util::monarch_point_t type are:
!!       \code{.f90}
!!         indices%WE_idx( )               ! horizontal index from West -> East
!!         indices%SN_idx( )               ! horizontal index from South -> North
!!         indices%veritcal_idx( )         ! vertical index from the surface layer -> top layer
!!         indices%flipped_vertical_idx( ) ! vertical index from top layer -> surface layer
!!       \endcode
!!
!!   3. Enjoy your new diagnostic! It can be used as described
!!      \ref using_diagnostics "here".

!> Diagnostic functions for SOLVER_INTERNAL_STATE instances.
!!
!! Provides information related to the current model state. See
!! \ref using_diagnostics "here" for details.
module monarch_diagnostics

  use module_solver_internal_state,   only : solver_internal_state
  use monarch_aerosol_representation, only : monarch_aerosol_representation_t
  use monarch_constants,              only : mint, mflt, mdbl

  implicit none
  private

  public :: monarch_diagnostics_t

  !> Model state diagnostics
  type monarch_diagnostics_t
  private
    !> Instance of the SOLVER_INTERNAL_STATE to use for calculations
    type(solver_internal_state), pointer :: int_state => null( )
    !> Current model aerosol representation
    class(monarch_aerosol_representation_t), pointer :: aero_rep => null( )
    !> Number of vertical layers
    !! TODO Remove once always available from solver internal state
    integer(kind=mint) :: num_vertical_layers
  contains
    !> Attach to a SOLVER_INTERNAL_STATE
    procedure :: attach
#ifdef DO_PARTMC
    !> Allocate a new species map
    procedure :: new_species_map
#endif

    ! Model properties
    !> Get the set of aerosol groups used in the model
    procedure :: aerosol_groups
    !> Get a new set of indices
    procedure :: new_point
    !> Get a new dimension object
    procedure :: new_dimensions
    !> Get the MPI process id
    procedure :: mpi_process_id
    !> Get the MPI communicator for computation processes
    procedure :: mpi_computation_comm
    !> Get the file unit for the logging
    procedure :: log_file_unit
    !> Get the file unit for error logging
    procedure :: error_file_unit
    !> Get the integration dimensions for the current task
    procedure :: task_integration_dimensions
    !> Get the memory dimensions for the current task
    procedure :: task_memory_dimensions
    !> Get the overall domain dimensions( all tasks )
    procedure :: total_domain_dimensions
    !> Get the total domain dimensions excluding ghost cells
    !! (In regional runs this is the same as total_domain_dimensions
    procedure :: total_real_domain_dimensions
    !> Determine whether this is a global model run
    procedure :: is_global_run
    !> Determine whether chemistry is included in the model run
    procedure :: is_chemistry_on
    !> Determines whether PartMC should be used for solving the chemistry
    procedure :: use_partmc_for_chemistry
    !> Get the starting index for chemical species on the tracer array
    !! (Aerosol and gas-phase species)
    !! TODO Update once PartMC-phlex can carve out it's own space in TRACERS
    procedure :: starting_chem_tracer_id
    !> Get the ending index for chemical species on the tracer array
    !! (Aerosol and gas-phase species)
    !! TODO Update once PartMC-phlex can carve out it's own space in TRACERS
    procedure :: ending_chem_tracer_id
    !> Get the number of meteorology time steps taken since the model start
    procedure :: number_meteorology_time_steps
    !> Determine whether advection should be calculated at this time step
    procedure :: do_advection_now
    !> Determine whether physics should be calculated at this time step
    procedure :: do_physics_now
    !> Determine whether chemistry should be calculated at this time step
    procedure :: do_chemistry_now
    !> Determine whether precipitation should be calculated at this time step
    procedure :: do_precipitation_now
    !> Determine wheter radiation should be calculated at this time step
    procedure :: do_radiation_now
    !> Get the meteorology time step size [s]
    procedure :: meteorology_time_step__s
    !> Get the physics time step size [s]
    procedure :: physics_time_step__s
    !> Get the chemistry time step size [s]
    procedure :: chem_time_step__s
    !> Get the pressure at the top of the highest layer [Pa]
    procedure :: top_of_domain_pressure__Pa

    ! Domain properties
    !> Get the Julian day of the year as a decimal [day]
    procedure :: julian_day
    !> Get the current year
    procedure :: current_year
    !> Get the current month (1...12)
    procedure :: current_month
    !> Get the current UTC hour (0...23)
    procedure :: current_UTC_hour
    !> Get the mid-timestep UTC [s]
    procedure :: mid_time_step_UTC__s
    !> Get the time that has elapsed since chemistry started [s]
    procedure :: time_since_chemistry_start__s

    ! Grid-cell properties
    !> Get the temperature of a grid cell [K]
    procedure :: temperature__K
    !> Get the pressure at the top of a grid cell [Pa]
    procedure :: top_of_layer_pressure__Pa
    !> Get the pressure at the bottom of a grid cell [Pa]
    procedure :: bottom_of_layer_pressure__Pa
    !> Get the mid-layer pressure of a grid cell [Pa]
    procedure :: mid_layer_pressure__Pa
    !> Get the air density in a grid cell [kg m-3]
    procedure :: air_density__kgm3
    !> Get the number density of air in a grid cell [mol m-3]
    procedure :: air_density__molm3
    !> Get the cloud water mass mixing raio [kg kg-1]
    procedure :: cloud_water_mixing_ratio__kgkg
    !> Get the cloud ice mass mixing raio [kg kg-1]
    procedure :: cloud_ice_mixing_ratio__kgkg
    !> Get the rain mass mixing raio [kg kg-1]
    procedure :: rain_mixing_ratio__kgkg
    !> Get the snow mass mixing raio [kg kg-1]
    procedure :: snow_mixing_ratio__kgkg
    !> Get the graupel mass mixing raio [kg kg-1]
    procedure :: graupel_mixing_ratio__kgkg
    !> Get the specific humidity of a grid cell [kg kg-1]
    procedure :: specific_humidity__kgkg
    !> Get the water vapor in a grid cell [ppm]
    procedure :: water_vapor__ppmv
    !> Get the relative humidity of a grid cell [unitless fraction]
    procedure :: relative_humidity__dec
    !> Determine whether it is raining in a grid cell
    procedure :: is_raining
    !> Get the dry aerosol mass concentration in a grid cell [kg m-3]
    procedure :: aerosol_dry_mass_conc__kgm3
    !> Get the aerosol extinction in a grid cell [m-1]
    procedure :: aerosol_extinction__m
#ifdef DO_PARTMC
    !> Get the phlex-state of a grid cell
    procedure :: get_phlex_state
#endif

    ! Surface properties
    !> Get the surface albedo
    procedure :: surface_albedo
    !> Get the skin temperature for a vertical column [K]
    procedure :: skin_temperature__K
    !> Temperature at 2-m altitude [K]
    procedure :: temperature_at_2m__K
    !> Get the downward shortwave radiation at the surface of a vertical
    !! column [W m-2]
    procedure :: surface_downward_shortwave_radiation__Wm2
    !> Get the USGS land-use type for a vertical column
    procedure :: USGS_land_use_type_id
    !> Get the friction velocity at the surface [m s-1]
    procedure :: friction_velocity__ms
    !> Get the roughness length [m]
    procedure :: roughness_length__m
    !> Get the mixed layer height [m]
    procedure :: mixed_layer_height__m
    !> Get the accumulated precipitation [m]
    procedure :: accumulated_precip__m
    !> Get the convective precipitation [m]
    procedure :: convective_precip__m
    !> Get the relative humidity at 2m [decimal fraction]
    procedure :: relative_humidity_2m__dec

    ! Column properties
    !> Get the latitude of a column [decimal degrees]
    procedure :: latitude__deg
    !> Get the longitude of a column [decimal degrees]
    procedure :: longitude__deg
    !> Get the reciprocal of the Monin-Obukhov length [m-1]
    procedure :: recip_Monin_Obukhov_length__m
    !> Get the column's pressure sigma scaling [Pa]
    procedure :: pressure_sigma_scaling__Pa
    !> Get the surface geopotential [m2 s-2]
    procedure :: surface_geopotential__m2s2
    !> Get the liquid water scale height [m]
    procedure :: liquid_water_scale_height__m
    !> Get the longitude-based local time [s]
    procedure :: longitude_based_local_time__s
    !> Get the cloud fraction [unitless]
    procedure :: cloud_fraction__dec

    ! Vertical layer properties
    !> Get the pressure sigma layer thickness( sigma range ) [unitless]
    procedure :: pressure_sigma_layer_thickness
    !> Get the pressure layer thickness( pressure range ) [Pa]
    procedure :: pressure_layer_thickness__Pa

    ! Bulk copy functions
    !> Copy the accumulated precipitation array [m]
    procedure :: copy_accumulated_precip__m
    !> Copy the convective precipitation array [m]
    procedure :: copy_convective_precip__m

    ! Helper functions (not based on model state)
    !> Get the number of days in a year
    procedure :: days_per_year
    !> Get the number of days in a month
    procedure :: days_per_month

    ! Initialization only functions
    ! These can only be called by the primary task
    !> Get a gas-phase species tracer id
    procedure :: get_gas_species_tracer_id

  end type monarch_diagnostics_t

contains

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Attach to a SOLVER_INTERNAL_STATE instance
  !!
  !! Before performing calculations, instances of monarch_diagnostics_t must
  !! attach to a SOLVER_INTERNAL_STATE object.
  subroutine attach( this, int_state, aero_rep, num_vertical_layers )

    use monarch_util,                       only : assert_msg

    !> Diagnostic object
    class(monarch_diagnostics_t), intent(inout) :: this
    !> Solver internal state
    type(solver_internal_state), target, intent(in) :: int_state
    !> Aerosol representation being used
    class(monarch_aerosol_representation_t), target, intent(inout) :: aero_rep
    !> Number of vertical layers
    !! (During initialization, the number of vertical layers is not available
    !!  from the solver_internal_state, so it needs to set explicitly.)
    !! FIXME Can we set LM when IDS, IDE, etc. are set?
    integer(kind=mint), optional, intent(in) :: num_vertical_layers

    this%int_state => int_state
    this%aero_rep  => aero_rep
    if( present( num_vertical_layers ) ) then
      this%num_vertical_layers = num_vertical_layers
    else
      call assert_msg( 407991066, associated( int_state%LM ),                &
                       "If the number of vertical layers is not yet set "//  &
                       "in the solver internal state, it must be passed "//  &
                       "explicitly to the diagnostics object" )
      this%num_vertical_layers = int_state%LM
    end if

  end subroutine attach

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#ifdef DO_PARTMC
  !> Allocate space for a new species map, and return a pointer to it
  function new_species_map( this )

    use monarch_species_map,                only : monarch_species_map_t
    use monarch_util,                       only : assert_msg

    !> New species map
    type(monarch_species_map_t), pointer :: new_species_map
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    call assert_msg( 632134691, associated( this%int_state ), "Trying to "// &
                     "get a species map from an unattached diagnostics "//   &
                     "object." )
    call assert_msg( 232529085, this%int_state%do_partmc, "Trying to get "// &
                     "get a species map without PartMC chemistry turned on." )

    new_species_map => monarch_species_map_t( )

  end function new_species_map
#endif
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the set of aerosol groups used in the model
  function aerosol_groups( this )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_group_collection_t

    !> Pointer to a new set of aerosol groups
    class(monarch_aerosol_group_collection_t), pointer :: aerosol_groups
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    aerosol_groups => this%aero_rep%groups( )

  end function aerosol_groups

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get a new set of indices
  function new_point( this )

    use monarch_util,                       only : monarch_point_t

    !> New point object
    type(monarch_point_t) :: new_point
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    new_point = monarch_point_t( this%num_vertical_layers )

  end function new_point

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get a new dimension object
  function new_dimensions( this )

    use monarch_util,                       only : monarch_dimensions_t

    !> New dimension object
    type(monarch_dimensions_t) :: new_dimensions
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    new_dimensions%lower = this%new_point( )
    new_dimensions%upper = this%new_point( )

  end function new_dimensions

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the MPI process id
  pure function mpi_process_id( this )

    !> MPI process id
    integer(kind=mint) :: mpi_process_id
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    mpi_process_id = this%int_state%MYPE

  end function mpi_process_id

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the MPI communicator for computation processes
  !! FIXME Is this right? Or is this for the integration and write tasks?
  pure function mpi_computation_comm( this )

    !> MPI Communicator
    integer(kind=mint) :: mpi_computation_comm
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    mpi_computation_comm = this%int_state%MPI_COMM_COMP

  end function mpi_computation_comm

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the log file unit
  pure function log_file_unit( this )

    !> Log file unit
    integer(kind=mint) :: log_file_unit
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    ! FIXME Check this is the right place to output to
    log_file_unit = 0

  end function log_file_unit

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the error file unit
  pure function error_file_unit( this )

    !> Error file unit
    integer(kind=mint) :: error_file_unit
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    ! FIXME Check this is the right place to output to
    error_file_unit = 0

  end function error_file_unit

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the integration dimensions for the current task
  function task_integration_dimensions( this ) result( dims )

    use monarch_util,                       only : monarch_dimensions_t

    !> Task integration dimensions
    type(monarch_dimensions_t) :: dims
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      dims = this%new_dimensions( )

      call dims%lower%set_WE_idx( this%int_state%ITS )
      call dims%lower%set_SN_idx( this%int_state%JTS )
      call dims%lower%set_vertical_idx( 1 )
      call dims%upper%set_WE_idx( this%int_state%ITE )
      call dims%upper%set_SN_idx( this%int_state%JTE )
      call dims%upper%set_vertical_idx( this%num_vertical_layers )

  end function task_integration_dimensions

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the memory dimensions for the current task
  function task_memory_dimensions( this ) result( dims )

    use monarch_util,                       only : monarch_dimensions_t

    !> Task memory dimensions
    type(monarch_dimensions_t) :: dims
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      dims = this%new_dimensions( )

      call dims%lower%set_WE_idx( this%int_state%IMS )
      call dims%lower%set_SN_idx( this%int_state%JMS )
      call dims%lower%set_vertical_idx( 1 )
      call dims%upper%set_WE_idx( this%int_state%IME )
      call dims%upper%set_SN_idx( this%int_state%JME )
      call dims%upper%set_vertical_idx( this%num_vertical_layers )

  end function task_memory_dimensions

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the overall domain dimensions( all tasks )
  function total_domain_dimensions( this ) result( dims )

    use monarch_util,                       only : monarch_dimensions_t

    !> Total domain dimensions
    type(monarch_dimensions_t) :: dims
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      dims = this%new_dimensions( )

      call dims%lower%set_WE_idx( this%int_state%IDS )
      call dims%lower%set_SN_idx( this%int_state%JDS )
      call dims%lower%set_vertical_idx( 1 )
      call dims%upper%set_WE_idx( this%int_state%IDE )
      call dims%upper%set_SN_idx( this%int_state%JDE )
      call dims%upper%set_vertical_idx( this%num_vertical_layers )

  end function total_domain_dimensions

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the total domain dimensions excluding ghost cells
  !! (In regional runs this is the same as total_domain_dimensions)
  function total_real_domain_dimensions( this ) result( dims )

    use monarch_util,                       only : monarch_dimensions_t

    !> Total real domain dimensions
    type(monarch_dimensions_t) :: dims
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      dims = this%new_dimensions( )

      call dims%lower%set_WE_idx( this%int_state%IDS + 1 )
      call dims%lower%set_SN_idx( this%int_state%JDS + 1 )
      call dims%lower%set_vertical_idx( 1 )
      call dims%upper%set_WE_idx( this%int_state%IDE - 1)
      call dims%upper%set_SN_idx( this%int_state%JDE - 1)
      call dims%upper%set_vertical_idx( this%num_vertical_layers )

  end function total_real_domain_dimensions

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether this is a global model run
  pure function is_global_run( this )

    !> Flag indicating whether this is a global run
    logical :: is_global_run
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      is_global_run = this%int_state%GLOBAL

  end function is_global_run

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether chemistry is included in the model run
  pure function is_chemistry_on( this )

    !> Flag indicating whether chemistry is turned on
    logical :: is_chemistry_on
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    is_chemistry_on = this%int_state%LCHEMON

  end function is_chemistry_on

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether PartMC should be used to solve the chemistry
  pure function use_partmc_for_chemistry( this )

    !> Flag indicating whether this is a global run
    logical :: use_partmc_for_chemistry
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

      use_partmc_for_chemistry = this%int_state%do_partmc

  end function use_partmc_for_chemistry

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the starting index for chemical species on the tracer array
  !! (Aerosol and gas-phase species)
  !! TODO Update once PartMC-phlex can carve out it's own space in TRACERS
  pure function starting_chem_tracer_id( this ) result( id )

    !> Tracer id
    integer(kind=mint) :: id
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    id = this%int_state%INDX_AERO_START

  end function starting_chem_tracer_id

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the ending index for chemical species on the tracer array
  !! (Aerosol and gas-phase species)
  !! TODO Update once PartMC-phlex can carve out it's own space in TRACERS
  pure function ending_chem_tracer_id( this ) result( id )

    !> Tracer id
    integer(kind=mint) :: id
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    id = this%int_state%INDX_GAS_END

  end function ending_chem_tracer_id

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the current Julian day of the year [day]
  !!
  !! The day is calculated as a decimal where 12Z 2 January --> 1.5
  pure function julian_day( this )

    !> Julian day of the year [day]
    real(kind=mdbl) :: julian_day
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    integer(kind=mint) :: curr_year, i_month
    integer(kind=mint), dimension( 12 ) :: days_per_month

    ! Update the days per month based on the year
    curr_year = mod( this%int_state%START_YEAR, 4 )
    if( curr_year.eq.0 ) then
      days_per_month = (/31,29,31,30,31,30,31,31,30,31,30,31/)
    else
      days_per_month = (/31,28,31,30,31,30,31,31,30,31,30,31/)
    endif

    ! Julian day at beginning of model run at midnight
    julian_day = 0.0
    do i_month = 1, 12
      if( i_month.eq.this%int_state%START_MONTH ) exit
      julian_day = julian_day + days_per_month( i_month )
    end do
    julian_day = julian_day + this%int_state%START_DAY - 1.0

    ! Add the starting time of day of the model run
    julian_day = julian_day +                                                &
                 ( this%int_state%START_HOUR +                               &
                   ( this%int_state%START_MINUTE +                           &
                     this%int_state%START_SECOND / 60.0                      &
                   ) / 60.0                                                  &
                 ) / 24.0

    ! Add the number of days since the start of the model run
    julian_day = julian_day + this%int_state%NTSD *                          & ! <-- number of steps taken( NTSD ) *
                          this%int_state%DT / 60.0 / 60.0 / 24.0               !     stepsize( DT; sec ) + start day

    ! Remove whole years from julian day
    do while( .true. )
      if( mod( curr_year,4 ) .eq.0 ) then
        if( julian_day.lt.365.0 ) return
        julian_day = julian_day - 365.0
      else
        if( julian_day.lt.364.0 ) return
        julian_day = julian_day - 364.0
      end if
      curr_year = curr_year + 1
    end do

  end function julian_day

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the current year
  pure function current_year( this )

    !> Current year
    integer(kind=mint) :: current_year
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    integer(kind=mint) :: jul_day, days_per_year

    jul_day      = this%julian_day( )
    current_year = this%int_state%START_YEAR

    do while( .true. )
      days_per_year = this%days_per_year( current_year )
      if( jul_day .le. days_per_year) return
      jul_day = jul_day - days_per_year
      current_year = current_year + 1
    end do

  end function current_year

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the current month (1...12)
  function current_month( this )

    use monarch_util,                       only : die_msg

    !> Current month as an integer
    integer(kind=mint) :: current_month
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    integer(kind=mint) :: jul_day, curr_year
    integer(kind=mint), dimension( 12 ) :: days_per_month

    ! Update the days per month based on the year
    curr_year = mod( this%int_state%START_YEAR, 4 )
    if( curr_year.eq.0 ) then
      days_per_month = (/31,29,31,30,31,30,31,31,30,31,30,31/)
    else
      days_per_month = (/31,28,31,30,31,30,31,31,30,31,30,31/)
    endif

    jul_day = this%julian_day( )

    do current_month = 1, 12
      jul_day = jul_day - days_per_month( current_month )
      if( jul_day .le. 0 ) return
    end do

    call die_msg( 445014853, "Internal error." )

  end function current_month

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the current UTC hour (0...23)
  pure function current_UTC_hour( this )

    !> Current UTC hour as an integer
    integer(kind=mint) :: current_UTC_hour
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    current_UTC_hour = mod( floor( this%mid_time_step_UTC__s( )/3600.0 ), 24 )

  end function current_UTC_hour

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the mid-timestep UTC [s]
  pure function mid_time_step_UTC__s( this ) result( utc_time )

    !> Current UTC at mid-timestep [s]
    real(kind=mdbl) :: utc_time
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    utc_time = real( this%int_state%START_HOUR, kind=mdbl ) * 3600.0 +       & ! <-- Starting hour [hr] -> [s]
               real( this%int_state%START_MINUTE, kind=mdbl ) * 60.0 +       & ! <-- Starting minute [min] -> [s]
               real( this%int_state%START_SECOND, kind=mdbl ) +              & ! <-- Starting second [s]
               this%int_state%NTSD *                                         & ! <-- Number of time steps taken
               this%int_state%DT   +                                         & ! <-- step size [s]
               this%int_state%DT / 2.0                                         ! <-- half a step [s]

    utc_time = mod( utc_time, 86400.0 )                                        ! <-- time since midnight [s]

  end function mid_time_step_UTC__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the time that has elapsed since chemistry started [s]
  pure function time_since_chemistry_start__s( this ) result( elapsed_time )

    !> Time since chemistry started [s]
    real(kind=mdbl) :: elapsed_time
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    elapsed_time = this%int_state%NTSD *                                     & ! <-- Number of time steps taken
                   this%int_state%DT          +                              & ! <-- step size [s]
                   this%int_state%DT / 2.0    -                              & ! <-- half a step [s]
                   real( this%int_state%NHRS_SPINUP, kind=mdbl ) * 3600.0      ! <-- spinup time [hr] -> [s]

  end function time_since_chemistry_start__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the number of meteorology time steps taken since the model started
  pure function number_meteorology_time_steps( this ) result( n_steps )

    !> Number of steps taken
    integer(kind=mint) :: n_steps
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    n_steps = this%int_state%NTSD

  end function number_meteorology_time_steps

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether advection should be calculated at this time step
  pure function do_advection_now( this )

    !> Whether to do advection at this time step
    logical :: do_advection_now
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    do_advection_now = mod( this%int_state%NTSD,                             &
                            this%int_state%IDTAD_CHEM ) .eq. 0

  end function do_advection_now

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether physics should be calculated at this time step
  pure function do_physics_now( this )

    !> Whether to do physics at this time step
    logical :: do_physics_now
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    do_physics_now = mod( this%int_state%NTSD, this%int_state%NPHS ) .eq. 0

  end function do_physics_now

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether chemistry should be calculated at this time step
  !!
  !! This is currently based on the physics timestep.
  pure function do_chemistry_now( this )

    !> Whether to do chemistry at this time step
    logical :: do_chemistry_now
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    do_chemistry_now = mod( this%int_state%NTSD, this%int_state%NPHS ) .eq. 0

  end function do_chemistry_now

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether precipitation should be calculated at this time step
  pure function do_precipitation_now( this )

    !> Whether to do precipitation at this time step
    logical :: do_precipitation_now
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    do_precipitation_now = mod( this%int_state%NTSD,                         &
                                this%int_state%NPRECIP ) .eq. 0

  end function do_precipitation_now

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether radiation should be calculated at this time step
  pure function do_radiation_now( this )

    !> Whether to do radiation at this time step
    logical :: do_radiation_now
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    do_radiation_now = mod( this%int_state%NTSD, this%int_state%NPHOT ) .eq. 0

  end function do_radiation_now

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the meteorology time step [s]
  pure function meteorology_time_step__s( this ) result( time_step )

    !> Time step [s]
    real(kind=mdbl) :: time_step
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    ! FIXME Is this right???
    time_step = this%int_state%DT

  end function meteorology_time_step__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the physics time step [s]
  pure function physics_time_step__s( this ) result( time_step )

    !> Time step [s]
    real(kind=mdbl) :: time_step
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    ! FIXME Is this right???
    time_step = this%int_state%DT * this%int_state%NPHS

  end function physics_time_step__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the chemistry time step [s]
  pure function chem_time_step__s( this ) result( time_step )

    !> Time step [s]
    real(kind=mdbl) :: time_step
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this

    ! FIXME Is this right???
    time_step = this%int_state%DT * this%int_state%NPHS

  end function chem_time_step__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the hydrostatic pressure at the top of the highest layer [Pa]
  pure function top_of_domain_pressure__Pa( this ) result( press )

    !> Pressure at the top of the domain [Pa]
    real(kind=mdbl) :: press
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this

    press = this%int_state%PT

  end function top_of_domain_pressure__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the latitude of a column [decimal degrees]
  !! FIXME Ask Oriol if this is real or rotated (we think it's real)
  pure function latitude__deg( this, indices ) result( lat )

    use module_constants,                   only : radians_to_degrees
    use monarch_util,                       only : monarch_point_t

    !> Latitude [decimal degrees]
    real(kind=mdbl) :: lat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    lat = this%int_state%GLAT( indices%WE_idx( ),                                 &
                               indices%SN_idx( ) ) * radians_to_degrees

  end function latitude__deg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the longitude of a column [decimal degrees]
  !! FIXME Ask Oriol if this is real or rotated (we think it's real)
  pure function longitude__deg( this, indices ) result( long )

    use module_constants,                   only : radians_to_degrees
    use monarch_util,                       only : monarch_point_t

    !> Longitude [decimal degrees]
    real(kind=mdbl) :: long
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    long = this%int_state%GLON( indices%WE_idx( ),                                &
                                indices%SN_idx( ) ) * radians_to_degrees

  end function longitude__deg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the surface albedo at the base of a column [unitless fraction]
  pure function surface_albedo( this, indices ) result( surf_alb )

    use monarch_util,                       only : monarch_point_t

    !> Surface albedo [unitless fraction]
    real(kind=mdbl) :: surf_alb
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    ! TODO Fast-J was using a constant value of 0.055. Test the sensitivity
    ! using the albedo from nmmb relative to using the constant.
    ! surf_alb = 0.055
    surf_alb = this%int_state%ALBEDO( indices%WE_idx( ), indices%SN_idx( ) )

  end function surface_albedo

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the temperature of a grid cell [K]
  pure function temperature__K( this, indices ) result( temp )

    use monarch_util,                       only : monarch_point_t

    !> Temperature [k]
    real(kind=mdbl) :: temp
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    temp = this%int_state%T( indices%WE_idx( ),                              &
                             indices%SN_idx( ),                              &
                             indices%flipped_vertical_idx( ) )

  end function temperature__K

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the hydrostatic pressure at the top of the grid cell [Pa]
  !!
  !! The hydrostatic pressure is calculated according to Eq. 2.0 in
  !! \cite Janjic2012 :
  !!
  !! \f[
  !!   \pi(x,y,s,t) = \pi_T + \sigma_1(s)\Pi + \sigma_2(s)\mu(x,y,t),
  !! \f]
  !!
  !! where \f$\Pi\f$ is the constant depth of the hydrostatic pressure
  !! coordinate layer at the top of the model atmosphere, \f$\sigma_1\f$ is
  !! zero at the top and the bottom of the model atmosphere, and
  !! \f$\sigma_2\f$ increases from 0 to 1 from top to bottom.
  !!
  !! When the arbitrary level \f$s\f$ refers to the top of veritcal layer
  !! \f$k\f$ assuming that \f$k=1\f$ refers to the lowest vertical layer,
  !! \f$k=\mbox{LM}\f$ refers to the highest vertical layer,
  !! \f$k_{flip} = \mbox{LM} - k + 1\f$, and \f$i\f$ and
  !! \f$j\f$ are the horizontal coordinates, the hydrostatic pressure
  !! \f$pi_{ijk}\f$ at the interface is calculated according to
  !!
  !! \f{equation}{
  !!   \pi_{ijk} = \mbox{SG2}(k_{flip}) * \mbox{PD}(i,j) + \mbox{PSG1}(k_{flip}) \\
  !!   \mbox{SG2}(k_{flip}) = \sigma_2(s) \\
  !!   \mbox{PD}(i,j) = \mu(x,y,t) \\
  !!   \mbox{PSG1}(k_{flip}) = \pi_T + \sigma_1(s)\Pi
  !! \f}
  pure function top_of_layer_pressure__Pa( this, indices )                   &
      result( top_press )

    use monarch_util,                       only : monarch_point_t

    !> Top-of-cell pressure [Pa]
    real(kind=mdbl) :: top_press
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    top_press =                                                              &
        this%int_state%SG2( indices%flipped_vertical_idx( ) )              * & ! <-- top-of-cell sigma - sigma range [unitless]
        this%int_state%PD( indices%WE_idx( ), indices%SN_idx( ) )          + & ! <-- sigma scaling [Pa]
        this%int_state%PSG1( indices%flipped_vertical_idx( ) )                 ! <-- top-of-cell pressure - pressure range [Pa]

  end function top_of_layer_pressure__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the pressure at the bottom of the grid cell [Pa]
  !!
  !! The hydrostatic pressure is calculated according to Eq. 2.0 in
  !! \cite Janjic2012 :
  !!
  !! \f[
  !!   \pi(x,y,s,t) = \pi_T + \sigma_1(s)\Pi + \sigma_2(s)\mu(x,y,t),
  !! \f]
  !!
  !! where \f$\Pi\f$ is the constant depth of the hydrostatic pressure
  !! coordinate layer at the top of the model atmosphere, \f$\sigma_1\f$ is
  !! zero at the top and the bottom of the model atmosphere, and
  !! \f$\sigma_2\f$ increases from 0 to 1 from top to bottom.
  !!
  !! When the arbitrary level \f$s\f$ refers to the bottom of veritcal layer
  !! \f$k\f$ assuming that \f$k=1\f$ refers to the lowest vertical layer,
  !! \f$k=\mbox{LM}\f$ refers to the highest vertical layer,
  !! \f$k_{flip} = \mbox{LM} - k + 1\f$, and \f$i\f$ and
  !! \f$j\f$ are the horizontal coordinates, the hydrostatic pressure
  !! \f$pi_{ijk}\f$ at the interface is calculated according to
  !!
  !! \f{equation}{
  !!   \pi_{ijk} = \mbox{SG2}(k_{flip} + 1) * \mbox{PD}(i,j) + \mbox{PSG1}(k_{flip} + 1) \\
  !!   \mbox{SG2}(k_{flip} + 1) = \sigma_2(s) \\
  !!   \mbox{PD}(i,j) = \mu(x,y,t) \\
  !!   \mbox{PSG1}(k_{flip} + 1) = \pi_T + \sigma_1(s)\Pi
  !! \f}
  pure function bottom_of_layer_pressure__Pa( this, indices )                &
      result( bottom_press )

    use monarch_util,                       only : monarch_point_t

    !> Bottom-of-cell pressure [Pa]
    real(kind=mdbl) :: bottom_press
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    bottom_press =                                                           &
        this%int_state%SG2( indices%flipped_vertical_idx( ) + 1 )          * & ! <-- bottom-of-cell sigma - sigma range [unitless]
        this%int_state%PD( indices%WE_idx( ), indices%SN_idx( ) )          + & ! <-- sigma scaling [Pa]
        this%int_state%PSG1( indices%flipped_vertical_idx( ) + 1 )             ! <-- bottom-of-cell pressure - pressure range [Pa]

  end function bottom_of_layer_pressure__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the mid-layer pressure of a grid cell [Pa]
  !!
  !! The hydrostatic pressure is calculated according to Eq. 2.0 in
  !! \cite Janjic2012 :
  !!
  !! \f[
  !!   \pi(x,y,s,t) = \pi_T + \sigma_1(s)\Pi + \sigma_2(s)\mu(x,y,t),
  !! \f]
  !!
  !! where \f$\Pi\f$ is the constant depth of the hydrostatic pressure
  !! coordinate layer at the top of the model atmosphere, \f$\sigma_1\f$ is
  !! zero at the top and the bottom of the model atmosphere, and
  !! \f$\sigma_2\f$ increases from 0 to 1 from top to bottom.
  !!
  !! When the arbitrary level \f$s\f$ refers to the middle of veritcal layer
  !! \f$k\f$ assuming that \f$k=1\f$ refers to the lowest vertical layer,
  !! \f$k=\mbox{LM}\f$ refers to the highest vertical layer,
  !! \f$k_{flip} = \mbox{LM} - k + 1\f$, and \f$i\f$ and
  !! \f$j\f$ are the horizontal coordinates, the hydrostatic pressure
  !! \f$pi_{ijk}\f$ at the interface is calculated according to
  !!
  !! \f{equation}{
  !!   \pi_{ijk} = \mbox{SGML2}(k_{flip}) * \mbox{PD}(i,j) + \mbox{PSGML1}(k_{flip}) \\
  !!   \mbox{SGML2}(k_{flip}) = \sigma_2(s) \\
  !!   \mbox{PD}(i,j) = \mu(x,y,t) \\
  !!   \mbox{PSGML1}(k_{flip}) = \pi_T + \sigma_1(s)\Pi
  !! \f}
  pure function mid_layer_pressure__Pa( this, indices )                      &
      result( mid_level_press )

    use monarch_util,                       only : monarch_point_t

    !> Mid-Level pressure [Pa]
    real(kind=mdbl) :: mid_level_press
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    mid_level_press =                                                        &
        this%int_state%SGML2( indices%flipped_vertical_idx( ) )            * & ! <-- mid-layer sigma - sigma range [unitless]
        this%int_state%PD( indices%WE_idx( ), indices%SN_idx( ) )          + & ! <-- sigma scaling [Pa]
        this%int_state%PSGML1( indices%flipped_vertical_idx( ) )               ! <-- pressure at mid-layer - pressure range [Pa]

  end function mid_layer_pressure__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the air density in a grid cell [kg m-3]
  pure function air_density__kgm3( this, indices ) result( air_density )

    use module_constants,                   only : R_D                       & ! <-- dry-air gas constant [J kg-1 K-1]
                                                  ,P608                        ! <-- water vapor scaling factor for virtual temp
    use monarch_util,                       only : monarch_point_t

    !> Air density [kg m-3]
    real(kind=mdbl) :: air_density
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    real(kind=mdbl) :: spec_humid, temp, mid_layer_press

    spec_humid      = this%specific_humidity__kgkg( indices )
    temp            = this%temperature__K( indices )
    mid_layer_press = this%mid_layer_pressure__Pa( indices )

    air_density     = mid_layer_press /                                      &
                      ( R_D * temp *( 1.0 + P608 * spec_humid )  )

  end function air_density__kgm3

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the air density in a grid cell [mol m-3]
  pure function air_density__molm3( this, indices ) result( air_density )

    use monarch_constants,                  only : const
    use monarch_util,                       only : monarch_point_t

    !> Air density [mol m-3]
    real(kind=mdbl) :: air_density
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    real(kind=mdbl) :: temp, mid_layer_press

    temp            = this%temperature__K( indices )
    mid_layer_press = this%mid_layer_pressure__Pa( indices )

    air_density     = mid_layer_press /                                      &
                      ( temp * const%gas_const__JmolK )

  end function air_density__molm3

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the cloud water mass mixing raio [kg kg-1]
  pure function cloud_water_mixing_ratio__kgkg( this, indices )              &
      result( mix_rat )

    use monarch_util,                       only : monarch_point_t

    !> Cloud water mass mixing ratio
    real(kind=mdbl) :: mix_rat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell coordinates
    type(monarch_point_t), intent(in) :: indices

    if( this%int_state%P_QC .gt. 0 ) then
      mix_rat = max(                                                         &
              this%int_state%TRACERS( indices%WE_idx( ),                     &
                                      indices%SN_idx( ),                     &
                                      indices%flipped_vertical_idx( ),       &
                                      this%int_state%INDX_WATER_START +      &
                                          this%int_state%P_QC - 1 ) , 0.0 )
     else
       mix_rat = 0.0
     end if

  end function cloud_water_mixing_ratio__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the cloud ice mass mixing raio [kg kg-1]
  pure function cloud_ice_mixing_ratio__kgkg( this, indices )                &
      result( mix_rat )

    use monarch_util,                       only : monarch_point_t

    !> Cloud ice mass mixing ratio
    real(kind=mdbl) :: mix_rat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell coordinates
    type(monarch_point_t), intent(in) :: indices

    if( this%int_state%P_QI .gt. 0 ) then
      mix_rat = max(                                                         &
              this%int_state%TRACERS( indices%WE_idx( ),                     &
                                      indices%SN_idx( ),                     &
                                      indices%flipped_vertical_idx( ),       &
                                      this%int_state%INDX_WATER_START +      &
                                          this%int_state%P_QI - 1 ) , 0.0 )
     else
       mix_rat = 0.0
     end if

  end function cloud_ice_mixing_ratio__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the rain mass mixing raio [kg kg-1]
  pure function rain_mixing_ratio__kgkg( this, indices ) result( mix_rat )

    use monarch_util,                       only : monarch_point_t

    !> Rain mass mixing ratio
    real(kind=mdbl) :: mix_rat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell coordinates
    type(monarch_point_t), intent(in) :: indices

    if( this%int_state%P_QR .gt. 0 ) then
      mix_rat = max(                                                         &
              this%int_state%TRACERS( indices%WE_idx( ),                     &
                                      indices%SN_idx( ),                     &
                                      indices%flipped_vertical_idx( ),       &
                                      this%int_state%INDX_WATER_START +      &
                                          this%int_state%P_QR - 1 ) , 0.0 )
     else
       mix_rat = 0.0
     end if

  end function rain_mixing_ratio__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the snow mass mixing raio [kg kg-1]
  pure function snow_mixing_ratio__kgkg( this, indices ) result( mix_rat )

    use monarch_util,                       only : monarch_point_t

    !> Snow mass mixing ratio
    real(kind=mdbl) :: mix_rat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell coordinates
    type(monarch_point_t), intent(in) :: indices

    if( this%int_state%P_QS .gt. 0 ) then
      mix_rat = max(                                                         &
              this%int_state%TRACERS( indices%WE_idx( ),                     &
                                      indices%SN_idx( ),                     &
                                      indices%flipped_vertical_idx( ),       &
                                      this%int_state%INDX_WATER_START +      &
                                          this%int_state%P_QS - 1 ) , 0.0 )
     else
       mix_rat = 0.0
     end if

  end function snow_mixing_ratio__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the graupel mass mixing raio [kg kg-1]
  pure function graupel_mixing_ratio__kgkg( this, indices ) result( mix_rat )

    use monarch_util,                       only : monarch_point_t

    !> Graupel mixing ratio
    real(kind=mdbl) :: mix_rat
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell coordinates
    type(monarch_point_t), intent(in) :: indices

    if( this%int_state%P_QG .gt. 0 ) then
      mix_rat = max(                                                         &
              this%int_state%TRACERS( indices%WE_idx( ),                     &
                                      indices%SN_idx( ),                     &
                                      indices%flipped_vertical_idx( ),       &
                                      this%int_state%INDX_WATER_START +      &
                                          this%int_state%P_QG - 1 ) , 0.0 )
     else
       mix_rat = 0.0
     end if

  end function graupel_mixing_ratio__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the specific humidity of a grid cell [kg kg-1]
  pure function specific_humidity__kgkg( this, indices ) result( spec_humid )

    use module_constants,                   only : EPSQ                        ! <-- minimum value for specific humidity [kg kg-1]
    use monarch_util,                       only : monarch_point_t

    !> Specific humidity [kg kg-1]
    real(kind=mdbl) :: spec_humid
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    spec_humid =                                                             &
        max( this%int_state%Q( indices%WE_idx( ), indices%SN_idx( ),         &
                               indices%flipped_vertical_idx( ) ) , EPSQ )

  end function specific_humidity__kgkg

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the water vapor in a grid cell [ppmv]
  pure function water_vapor__ppmv( this, indices ) result( water_vapor )

    use monarch_constants,                  only : const
    use monarch_util,                       only : monarch_point_t

    !> Water vapor [ppm]
    real(kind=mdbl) :: water_vapor
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    real(kind=mdbl) :: spec_humid

    spec_humid      = this%specific_humidity__kgkg( indices )

    ! Convert specific humidity to ppmmv
    !( kg_H2O/kg_air ) *( kg_air/mol_air ) /( kg_H2O/mol_H2O ) * 10^6 ppmv
    water_vapor = spec_humid / ( 1.0 + spec_humid )                        * &
                  const%molec_weight_dry_air__kgmol                        / &
                  const%molec_weight_water__kgmol * 1.0e6

  end function water_vapor__ppmv

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the relative humidity of a grid cell [unitless fraction]
  !!
  !! TODO Add reference for this calculation
  pure function relative_humidity__dec( this, indices ) result( rel_humid )

    use monarch_util,                       only : monarch_point_t

    !> Relative humidity [unitless fraction]
    real(kind=mdbl) :: rel_humid
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    real(kind=mdbl) :: spec_humid, temp, mid_layer_press

    spec_humid      = this%specific_humidity__kgkg( indices )
    temp            = this%temperature__K( indices )
    mid_layer_press = this%mid_layer_pressure__Pa( indices )

    rel_humid       = max( 0.001, min( 0.9995,                               &
                      ( spec_humid /( 1.0 - spec_humid )  ) /                &
                      ( 3.80 * exp( 17.27            *                       &
                                    ( temp - 273.0 ) /                       &
                                    ( temp - 36.0 )  ) /                     &
                        ( 0.01 * mid_layer_press )  )  )  )

  end function relative_humidity__dec

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Determine whether it is raining in a grid cell
  pure function is_raining( this, indices )

    use monarch_util,                       only : monarch_point_t

    !> Flag indicating rain
    logical :: is_raining
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices

    is_raining = this%rain_mixing_ratio__kgkg( indices ) > 1.0e-12

  end function is_raining

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the dry aerosol mass concentration in a grid cell [kg m-3]
  !! If an aerosol group is included, results are calculated for the group,
  !! otherwise the total aerosol dry mass is calculated.
  function aerosol_dry_mass_conc__kgm3( this, indices, aero_group )     &
      result( dry_mass )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_group_collection_t
    use monarch_util,                       only : monarch_point_t

    !> Aerosol dry mass in a grid cell [kg m-3]
    real(kind=mdbl) :: dry_mass
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices
    !> Aerosol group
    class(monarch_aerosol_group_collection_t), optional, intent(in) ::       &
        aero_group

    if( present( aero_group ) ) then
      dry_mass = this%aero_rep%dry_mass_conc__kgm3( indices, aero_group )
    else
      dry_mass = this%aero_rep%dry_mass_conc__kgm3( indices )
    end if

  end function aerosol_dry_mass_conc__kgm3

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the aerosol extinction at 550 nm in a grid cell [m-1]
  !! If an aerosol group is included, results are calculated for the group,
  !! otherwise the total aerosol extinction is calculated.
  function aerosol_extinction__m( this, indices, aero_group )                &
      result( extinct )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_group_collection_t
    use monarch_util,                       only : monarch_point_t

    !> Aerosol extinction [m-1]
    real(kind=mdbl) :: extinct
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid-cell indices
    type(monarch_point_t), intent(in) :: indices
    !> Aerosol group
    class(monarch_aerosol_group_collection_t), optional, intent(in) ::       &
        aero_group

    ! If an aerosol group was passed, calculate extinction for that group
    ! (i.e., dust, sea salt, black carbon, etc.) ...
    if( present( aero_group ) ) then
      extinct = this%aero_rep%extinction__m(                                 &
                                indices,                                     &
                                this%relative_humidity__dec( indices ),      &
                                aero_group )

    ! ... otherwise calculate total aerosol extinction in the cell
    else
      extinct = this%aero_rep%extinction__m(                                 &
                                indices,                                     &
                                this%relative_humidity__dec( indices ) )
    end if

  end function aerosol_extinction__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#ifdef DO_PARTMC
  !> Get the phlex-state of a grid cell
  subroutine get_phlex_state( this, indices, species_map, phlex_state )

    use monarch_species_map,                only : monarch_species_map_t
    use monarch_util,                       only : monarch_point_t, assert_msg
    use pmc_phlex_state,                    only : phlex_state_t

    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Grid cell indices
    type(monarch_point_t), intent(in) :: indices
    !> PartMC-Phlex <-> MONARCH species map
    type(monarch_species_map_t), intent(in) :: species_map
    !> Phlex-state
    type(phlex_state_t), intent(inout) :: phlex_state

    integer(kind=mint) :: i_spec, phlex_id
    real(kind=mdbl) :: kgkg_to_ugm3

    call assert_msg( 712318545, associated( this%int_state ), "Trying to "// &
                     "get a phlex_state_t from an unattached diagnostics "// &
                     "object." )
    call assert_msg( 480964309, this%int_state%do_partmc, "Trying to get "// &
                     "a phlex_state_t without PartMC chemistry turned on." )

    ! Calculate the conversion from [kg kg-1] to [ug m-3] for bulk water
    kgkg_to_ugm3 = this%air_density__kgm3( indices ) * 1.0d9

    ! Update the environmental conditions
    phlex_state%env_state%temp     = this%temperature__K( indices )
    phlex_state%env_state%pressure = this%mid_layer_pressure__Pa( indices )

    ! Update the species concentrations for the specified grid cell using the
    ! species map
    call species_map%update_phlex_state( phlex_state,                        &
          this%int_state%TRACERS( indices%WE_idx( ) , indices%SN_idx( ),     &
                                  indices%flipped_vertical_idx( ) , : ),     &
          this%air_density__molm3( indices ) )

    ! Update the water-vapor species in PartMC-phlex
    phlex_state%state_var( species_map%get_phlex_water_vapor_id( ) ) =       &
        this%water_vapor__ppmv( indices )

    ! Update the cloud water species in PartMC-phlex, if one exists
    phlex_id = species_map%get_phlex_cloud_water_id( )
    if( phlex_id .gt. 0 ) phlex_state%state_var( phlex_id ) =                &
        this%cloud_water_mixing_ratio__kgkg( indices ) * kgkg_to_ugm3

    ! Update the rain water species in PartMC-phlex, if one exists
    phlex_id = species_map%get_phlex_rain_water_id( )
    if( phlex_id .gt. 0 ) phlex_state%state_var( phlex_id ) =                &
        this%rain_mixing_ratio__kgkg( indices ) * kgkg_to_ugm3

    ! Update the cloud ice species in PartMC-phlex, if one exists
    phlex_id = species_map%get_phlex_cloud_ice_id( )
    if( phlex_id .gt. 0 ) phlex_state%state_var( phlex_id ) =                &
        this%cloud_ice_mixing_ratio__kgkg( indices ) * kgkg_to_ugm3

    ! Update the graupel species in PartMC-phlex, if one exists
    phlex_id = species_map%get_phlex_graupel_id( )
    if( phlex_id .gt. 0 ) phlex_state%state_var( phlex_id ) =                &
        this%graupel_mixing_ratio__kgkg( indices ) * kgkg_to_ugm3

    ! Update the snow species in PartMC-phlex, if one exists
    phlex_id = species_map%get_phlex_snow_id( )
    if( phlex_id .gt. 0 ) phlex_state%state_var( phlex_id ) =                &
        this%snow_mixing_ratio__kgkg( indices ) * kgkg_to_ugm3

  end subroutine get_phlex_state
#endif
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the skin temperature for a vertical column
  pure function skin_temperature__K( this, indices ) result( skin_temp )

    use monarch_util,                       only : monarch_point_t

    !> Skin temperature [K]
    real(kind=mdbl) :: skin_temp
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    skin_temp = this%int_state%TSKIN( indices%WE_idx( ), indices%SN_idx( ) )

  end function skin_temperature__K

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the temperature at 2-m height [K]
  pure function temperature_at_2m__K( this, indices ) result( temp )

    use monarch_util,                       only : monarch_point_t

    !> Temperature at 2-m [K]
    real(kind=mdbl) :: temp
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    temp = this%int_state%T2( indices%WE_idx( ), indices%SN_idx( ) )

  end function temperature_at_2m__K

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the shortwave radiation at the surface of a vertical column
  pure function surface_downward_shortwave_radiation__Wm2( this, indices )   &
      result( rad )

    use monarch_util,                       only : monarch_point_t

    !> Surface shortwave radiation [W m-2]
    real(kind=mdbl) :: rad
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    rad = this%int_state%RSWIN( indices%WE_idx( ), indices%SN_idx( ) )

  end function surface_downward_shortwave_radiation__Wm2

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the USGS land-use type id
  !!
  !!   |  USGS type | Description                        | Wesely type |
  !!   | ---------- | ---------------------------------- | ----------- |
  !!   |   1        | Urban and built-up land            |  1          |
  !!   |   2        | Dryland cropland and pasture       |  2          |
  !!   |   3        | Irrigated cropland and pasture     |  2          |
  !!   |   4        | Mix. dry/irrg. cropland and pasture|  2          |
  !!   |   5        | Cropland/grassland mosaic          |  2          |
  !!   |   6        | Cropland/woodland mosaic           |  4          |
  !!   |   7        | Grassland                          |  3          |
  !!   |   8        | Shrubland                          |  3          |
  !!   |   9        | Mixed shrubland/grassland          |  3          |
  !!   |  10        | Savanna                            |  3, always summer |
  !!   |  11        | Deciduous broadleaf forest         |  4          |
  !!   |  12        | Deciduous needleleaf forest        |  5, autumn and winter modi |
  !!   |  13        | Evergreen broadleaf forest         |  4, always summer |
  !!   |  14        | Evergreen needleleaf forest        |  5          |
  !!   |  15        | Mixed Forest                       |  6          |
  !!   |  16        | Water Bodies                       |  7          |
  !!   |  17        | Herbaceous wetland                 |  9          |
  !!   |  18        | Wooded wetland                     |  6          |
  !!   |  19        | Barren or sparsely vegetated       |  8          |
  !!   |  20        | Herbaceous Tundra                  |  9          |
  !!   |  21        | Wooded Tundra                      |  6          |
  !!   |  22        | Mixed Tundra                       |  6          |
  !!   |  23        | Bare Ground Tundra                 |  8          |
  !!   |  24        | Snow or Ice                        |  -, always winter |
  !!   |  25        | No data                            |  8          |
  pure function USGS_land_use_type_id( this, indices )                       &
      result( land_use_type )

    use monarch_util,                       only : monarch_point_t

    !> USGS land-use type id
    integer(kind=mint) :: land_use_type
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    land_use_type = this%int_state%IVGTYP( indices%WE_idx( ),                &
                                           indices%SN_idx( ) )

  end function USGS_land_use_type_id

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the friction velocity at the surface [m s-1]
  pure function friction_velocity__ms( this, indices ) result( fric_vel )

    use monarch_util,                       only : monarch_point_t

    !> Friction velocity at the surface [m s-1]
    real(kind=mdbl) :: fric_vel
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    fric_vel = this%int_state%USTAR( indices%WE_idx( ), indices%SN_idx( ) )

  end function friction_velocity__ms

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the roughness length [m]
  pure function roughness_length__m( this, indices ) result( rough_len )

    use monarch_util,                       only : monarch_point_t

    !> Roughness length [m]
    real(kind=mdbl) :: rough_len
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    rough_len = this%int_state%Z0( indices%WE_idx( ), indices%SN_idx( ) )

  end function roughness_length__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the mixed layer height [m]
  pure function mixed_layer_height__m( this, indices ) result( height )

    use monarch_util,                       only : monarch_point_t

    !> Mixed layer height [m]
    real(kind=mdbl) :: height
    !> Diagnostic object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    height = this%int_state%MIXHT( indices%WE_idx( ), indices%SN_idx( ) )

  end function mixed_layer_height__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the accumulated precipitation [m]
  pure function accumulated_precip__m( this, indices ) result( precip )

    use monarch_util,                       only : monarch_point_t

    !> Accumulated precipitation [m]
    real(kind=mdbl) :: precip
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    precip = this%int_state%ACPREC( indices%WE_idx( ), indices%SN_idx( ) )

  end function accumulated_precip__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the convective precipitation [m]
  pure function convective_precip__m( this, indices ) result( precip )

    use monarch_util,                       only : monarch_point_t

    !> Convective precipitation [m]
    real(kind=mdbl) :: precip
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    ! FIXME from the code before the call to CLDCHEM_DRIVER, it looks like
    !       this is accumulated convective precipitation. Check, and if it is
    !       rename the function here and in pmc_last_time_step for clarity. If
    !       not, fix the calculation in pmc_wet_deposition
    precip = this%int_state%CUPREC( indices%WE_idx( ), indices%SN_idx( ) )

  end function convective_precip__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the relative humidity at 2m [decimal fraction]
  function relative_humidity_2m__dec( this, indices ) result( rel_hum )

    use monarch_util,                       only : monarch_point_t

    !> Relative humidity at 2m [decimal fraction]
    real(kind=mdbl) :: rel_hum
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    rel_hum = this%int_state%RH02( indices%WE_idx( ), indices%SN_idx( ) )

  end function relative_humidity_2m__dec

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the reciprocal of the Monin-Obukhov length [m-1]
  pure function recip_Monin_Obukhov_length__m( this, indices )               &
      result( rmol )

    use monarch_util,                       only : monarch_point_t

    !> Reciprocal of the Monin-Obukhov length [m-1]
    real(kind=mdbl) :: rmol
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    rmol = this%int_state%RMOL( indices%WE_idx( ), indices%SN_idx( ) )

  end function recip_Monin_Obukhov_length__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the column's pressure sigma scaling [Pa]
  pure function pressure_sigma_scaling__Pa( this, indices ) result( sigma )

    use monarch_util,                       only : monarch_point_t

    !> Pressure sigma scaling for the column [Pa]
    real(kind=mdbl) :: sigma
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    sigma = this%int_state%PD( indices%WE_idx( ), indices%SN_idx( ) )

  end function pressure_sigma_scaling__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the surface geopotential [m2 s-2]
  pure function surface_geopotential__m2s2( this, indices ) result( geopot )

    use monarch_util,                       only : monarch_point_t

    !> Surface geopotential [m2 s-2]
    real(kind=mdbl) :: geopot
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    geopot = this%int_state%FIS( indices%WE_idx( ), indices%SN_idx( ) )

  end function surface_geopotential__m2s2

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the liquid water scale height [m]
  !!
  !! Based on approximations in NCAR's CCM2 for calculating 'typical' liquid
  !! cloud water paths.
  pure function liquid_water_scale_height__m( this, indices ) result( height )

    use monarch_util,                       only : monarch_point_t

    !> Liquid water scale height [m]
    real(kind=mdbl) :: height
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    height = 1080.0 + 2000.0 * cos( this%latitude__deg( indices ) *          &
                                    0.017454329 )

  end function liquid_water_scale_height__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the longitude-based local time [s]
  !!
  !! Compute a local time based on UTC and longitude. For each degree of
  !! longitude east 4 min are added. The addition of 1 day (86400 s) and use
  !! of Modulo(1 day) are done to avoid negative times and account for date
  !! changes.
  !!
  !! Ref: http://wiki.esipfed.org/index.php/Indirect_forcing
  pure function longitude_based_local_time__s( this, indices )             &
      result( time )

    use monarch_util,                            only : monarch_point_t

    !> Longitude-based local time [s]
    real(kind=mdbl) :: time
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    time = mod(this%mid_time_step_UTC__s( ) + 240.0 *                        &
           this%longitude__deg( indices ) + 86400.0, 86400.0)

  end function longitude_based_local_time__s

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the cloud fraction [unitless fraction]
  !!
  pure function cloud_fraction__dec( this, indices )             &
      result( cloud_frac )

    use monarch_util,                            only : monarch_point_t

    !> cloud fraction [unitless fraction]
    real(kind=mdbl) :: cloud_frac
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Column indices
    type(monarch_point_t), intent(in) :: indices

    cloud_frac = this%int_state%CLDFRA( indices%WE_idx( ), indices%SN_idx( ), & 
                                        indices%flipped_vertical_idx( ) )

  end function cloud_fraction__dec

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the pressure sigma layer thickness( sigma range ) [unitless]
  !! TODO Add more description for this value
  pure function pressure_sigma_layer_thickness( this, indices )              &
      result( thickness )

    use monarch_util,                       only : monarch_point_t

    !> Pressure sigma layer thickness [unitless]
    real(kind=mdbl) :: thickness
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Layer index
    type(monarch_point_t), intent(in) :: indices

    thickness = this%int_state%DSG2( indices%flipped_vertical_idx( ) )

  end function pressure_sigma_layer_thickness

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the pressure layer thickness( pressure range ) [Pa]
  !! TODO Add more description for this value
  pure function pressure_layer_thickness__Pa( this, indices )                &
      result( thickness )

    use monarch_util,                       only : monarch_point_t

    !> Pressure sigma layer thickness [unitless]
    real(kind=mdbl) :: thickness
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Layer index
    type(monarch_point_t), intent(in) :: indices

    thickness = this%int_state%PDSG1( indices%flipped_vertical_idx( ) )

  end function pressure_layer_thickness__Pa

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Copy the accumulated precipitation array [m]
  subroutine copy_accumulated_precip__m( this, copy_to_array, dimensions )

    use monarch_util,                       only : monarch_dimensions_t

    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Array to copy values to
    real(kind=mdbl), allocatable :: copy_to_array( :,: )
    !> Dimensions to copy over
    type(monarch_dimensions_t), intent(in) :: dimensions

    integer(kind=mint) :: WE_idx, SN_idx

    forall( WE_idx = dimensions%lower%WE_idx( ) :                            &
                     dimensions%upper%WE_idx( ) )
      forall( SN_idx = dimensions%lower%SN_idx( ) :                          &
                       dimensions%upper%SN_idx( ) )
        copy_to_array( WE_idx, SN_idx ) =                                    &
            this%int_state%ACPREC( WE_idx, SN_idx )
      end forall
    end forall

  end subroutine copy_accumulated_precip__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Copy the convective precipitation array [m]
  subroutine copy_convective_precip__m( this, copy_to_array, dimensions )

    use monarch_util,                       only : monarch_dimensions_t

    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Array to copy values to
    real(kind=mdbl), allocatable :: copy_to_array( :,: )
    !> Dimensions to copy over
    type(monarch_dimensions_t), intent(in) :: dimensions

    integer(kind=mint) :: WE_idx, SN_idx

    forall( WE_idx = dimensions%lower%WE_idx( ) :                            &
                     dimensions%upper%WE_idx( ) )
      forall( SN_idx = dimensions%lower%SN_idx( ) :                          &
                       dimensions%upper%SN_idx( ) )
        copy_to_array( WE_idx, SN_idx ) =                                    &
            this%int_state%CUPREC( WE_idx, SN_idx )
      end forall
    end forall

  end subroutine copy_convective_precip__m

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the number of days in a year
  pure function days_per_year( this, year )

    !> Days in the year
    integer(kind=mint) :: days_per_year
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Year to get the number of days for
    integer(kind=mint), intent(in) :: year

    if( mod( year, 4 ) .eq. 0 ) then
      days_per_year = 365
    else
      days_per_year = 364
    end if

  end function days_per_year

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the number of days in a month
  pure function days_per_month( this, year, month )

    !> Days in the month
    integer(kind=mint) :: days_per_month
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Year to find month in
    integer(kind=mint), intent(in) :: year
    !> Month to get days for
    integer(kind=mint), intent(in) :: month

    integer(kind=mint), dimension( 12 ) :: days_per_month_array

    ! Update the days per month based on the year
    if( mod( year, 4 ) .eq. 0 ) then
      days_per_month_array = (/31,29,31,30,31,30,31,31,30,31,30,31/)
    else
      days_per_month_array = (/31,28,31,30,31,30,31,31,30,31,30,31/)
    endif

    days_per_month = days_per_month_array( month )

  end function days_per_month

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get a gas-phase species tracer id
  !!
  !! This should be called only during initialization, and can only be called
  !! by the primary task.
  !! Returns the tracer id if the species exists; returns 0 otherwise
  function get_gas_species_tracer_id( this, species_name )                   &
      result( tracer_id )

    use module_BSC_CHEM_DATA,                    only : GC_SPC,              &
                                                 CHEM_INDICES_ARE_INITIALIZED
    use monarch_util,                            only : die_msg, assert_msg

    !> Tracer id
    integer(kind=mint) :: tracer_id
    !> Diagnostics object
    class(monarch_diagnostics_t), intent(in) :: this
    !> Species name to look up
    character(len=*) :: species_name

    integer(kind=mint) :: i_spec

    tracer_id = 0

    !> \bug Update for PartMC once it sets its own TRACERS ids
    call assert_msg( 263976350, CHEM_INDICES_ARE_INITIALIZED,                &
                     "Cannot look up species ids before they have been "//   &
                     "initialized." )

    do i_spec = 1, size( GC_SPC )
      if( trim( species_name ) .eq. trim( GC_SPC( i_spec ) ) ) then
        tracer_id = i_spec + this%int_state%INDX_GAS_START - 1
        return
      end if
    end do

  end function get_gas_species_tracer_id

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

end module monarch_diagnostics
