! 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_column_t object and related functions

!> \page using_column_diagnostics Using column diagnostics
!!
!! A \c monarch_column describes the current state of a vertical column
!! in MONARCH. Unlike \c monarch_diagnostics, the \c monarch_column does have
!! some internal memory, which the \c monarch_column::update function can be
!! used to update for a new location. \c monarch_column::monarch_column_t
!! objects should be passed to functions that need access to column
!! diagnostics, to avoid repeated calls to \c monarch_column::update() for
!! the same horizontal location and time step. Instructions for adding new
!! \c monarch_column diagnostics can be found
!! \ref adding_column_diagnostics "here".
!!
!! A subroutine that uses a column object is generally structured as
!! follows:
!! \code{.f90}
!!   subroutine my_subroutine( column )
!!
!!     !> Column object
!!     !! (This should always be intent-in, as the calling routine will have
!!     !!  already called column%update( ) for the current location.)
!!     type(monarch_column_t), intent(in) :: column
!!
!!     ...
!!     ! Use column diagnostics as needed
!!     ...
!!
!!   end subroutine my_subroutine
!! \endcode
!! Within your function or subroutine there are several ways to use a
!! \c monarch_column::monarch_column_t object:
!!
!!   - Access arrays of column properties. Array indices always start at 1
!!     near the surface and increase with height. Grid-cell average and
!!     mid-layer properties have indices \f$i \in 1...n\f$ where \f$n\f$ is
!!     the number of vertical layers in the model. Layer interface properties
!!     start at the surface and go to the top of the model domain with
!!     indices \f$i \in 1...n+1\f$.
!!     \code{.f90}
!!       real(kind=mdbl) :: temp, press
!!
!!       temp  = column%temperature__K( 2 )
!!       press = column%mid_layer_pressure__Pa( 2 )
!!     \endcode
!!
!!   - Use column functions to calculate column properties.
!!     \code{.f90}
!!       real(kind=mdbl) :: total_column_snow, total_column_rain
!!
!!       total_column_snow = column%snow__kgm2( )
!!       total_column_rain = column%rain__kgm2( )
!!     \endcode
!!
!!   - Get a set of indices preloaded with the horizontal coordinates of the
!!     column, for use with \c monarch_diagnostics functions. You can even use
!!     the internal \c monarch_diagnostics::monarch_diagnostics_t object in
!!     your \c monarch_column::monarch_column_t object.
!!     \code{.f90}
!!       type(monarch_point_t) :: indices
!!       real(kind=mdbl) :: surf_alb, lat, lon, rh
!!
!!       indices  = column%get_indices( )
!!
!!       ! Get some properties that only depend on the horizontal location
!!       surf_alb = column%diag%surface_albedo( indices )
!!       lat      = column%diag%latitude__deg( indices )
!!       lon      = column%diag%longitude__deg( indices )
!!
!!       ! Get a property for a specific cell in the column
!!       call indices%set_vertical_idx( 4 )
!!       rh = column%diag%relative_humidity__dec( indices )
!!     \endcode

!> \page adding_column_diagnostics Adding new column diagnostics
!!
!! A \c monarch_column describes the current state of a vertical column
!! in MONARCH. This document describes how to add new diagnostics to the
!! \c monarch_column::monarch_column_t type.
!!
!! ## 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.
!!
!! Diagnostics in the \c monarch_column take one of several forms, which are
!! described below.
!!
!! ## Wrapper functions ##
!!
!! These functions essentially just return properties for the column provided
!! by \c monarch_diagnostics functions. The following steps can be used to
!! add a new wrapper function:
!!
!!   1. Add the new funcion name to the \c monarch_column::monarch_column_t
!!      type's bound procedures:
!!      \code{.f90}
!!        !> MONARCH column state
!!        type monarch_column_t
!!          ...
!!        contains
!!          ...
!!          !> Get the surface albedo at the base of the column [unitless]
!!          procedure :: surface_albedo
!!          ...
!!        end type monarch_column_t
!!      \endcode
!!
!!   2. Add the function to the \c monarch_column functions:
!!      \code{.f90}
!!        !> Get the surface albedo at the base of the column [unitless]
!!        function surface_albedo( this )
!!
!!          !> Surface albedo [unitless]
!!          real(kind=mdbl) :: surface_albedo
!!          !> Column state
!!          class(monarch_column_t), intent(in) :: this
!!
!!          ! Use the internal indices object to get the property from
!!          ! the monarch_diagnostics_t object. NOTE only the horizontal
!!          ! indices are set, so no layer-specific digagnostics
!!          ! can be retrieved.
!!          surface_albedo = this%diag%surface_albedo( this%indices )
!!
!!        end function surface_albedo
!!      \endcode
!!
!! ## Call-time calculations ##
!!
!! Functions that perform a calculation for the column when they are called
!! can be added using the following steps:
!!
!!   1. Add the new funcion name to the \c monarch_column::monarch_column_t
!!      type's bound procedures:
!!      \code{.f90}
!!        !> MONARCH column state
!!        type monarch_column_t
!!          ...
!!        contains
!!          ...
!!          !> Get the average pressure for the column [Pa]
!!          procedure :: average_pressure__Pa
!!          ...
!!        end type monarch_column_t
!!      \endcode
!!
!!   2. Add the function to the \c monarch_column functions:
!!      \code{.f90}
!!        !> Get the average pressure for the column [Pa]
!!        function average_pressure__Pa( this ) result( pressure )
!!
!!          !> Average column pressure [Pa]
!!          real(kind=mdbl) :: pressure
!!          !> Column state
!!          class(monarch_column_t), intent(in) :: this
!!
!!          ! To iterate through the layer, create a local set of indices
!!          type(monarch_point_t) :: indices
!!          integer(kind=mint) :: i_layer
!!
!!          indices = this%get_indices( )
!!          pressure = 0.0
!!          do i_layer = 1, this%n_layers
!!            call indices%set_vertical_idx( i_layer )
!!            pressure = pressure + this%diag%mid_layer_pressure__Pa( indices )
!!          end do
!!          pressure = pressure / this%n_layers
!!
!!        end function average_pressure__Pa
!!      \endcode

!> State of a vertical column
module monarch_column

  use monarch_constants,                    only : mint, mflt, mdbl
  use monarch_diagnostics,                  only : monarch_diagnostics_t
  use monarch_util,                         only : monarch_point_t
#ifdef DO_PARTMC
  use monarch_species_map,                  only : monarch_species_map_t
  use pmc_phlex_state,                      only : phlex_state_ptr
#endif

  implicit none
  private

  public :: monarch_column_t

  !> MONARCH column state
  !!
  !! State of a column of vertical cells in MONARCH.
  !! Arrays are ordered starting at the surface.
  type monarch_column_t
    !> Number of vertical layers
    integer(kind=mint) :: n_layers
    !> Diagnostic functions
    type(monarch_diagnostics_t) :: diag
    !> Column indices
    type(monarch_point_t) :: indices
    !> Top layer use in transport FIXME Is this right???
    !! 1 <= top_transport_layer <= n_layers
    integer(kind=mint) :: top_transport_layer
    !> Number of layers in the troposhere
    !! 1 <= num_tropospheric_layers <= num_model_layers
    integer(kind=mint) :: num_tropospheric_layers
    !> Julian day [decimal days] where 12Z 2 January --> 1.5
    real(kind=mdbl) :: julian_day
    !> Current time of day in UTC at the middle of the time step [s]
    real(kind=mdbl) :: mid_time_step_UTC__s
    !> Temperature [K] 1...n_layers
    real(kind=mdbl), allocatable :: temperature__K( : )
    !> Pressure at layer interfaces [Pa]
    !! 1( bottom of lowest layer ) ...n_layers+1( top of highest layer )
    real(kind=mdbl), allocatable :: btw_layer_pressure__Pa( : )
    !> Mid-layer pressure [Pa]
    !! 1...n_layers
    real(kind=mdbl), allocatable :: mid_layer_pressure__Pa( : )
    !> Hydrostatic pressure change with height per layer [Pa]
    real(kind=mdbl), allocatable :: pressure_change__Pa( : )
    !> Height above sea level at layer interfaces [m] FIXME Is this right???
    !! 1( bottom of lowest layer ) ...n_layers+1( top of highest layer )
    real(kind=mdbl), allocatable :: btw_layer_height__m( : )
    !> Layer thickness [m] 1...n_layers
    real(kind=mdbl), allocatable :: layer_thickness__m( : )
    !> Air density [kg m-3] 1...n_layers
    real(kind=mdbl), allocatable :: air_density__kgm3( : )
    !> Cloud water mixing ratio [kg kg-1] 1...n_layers
    real(kind=mdbl), allocatable :: cloud_water_mix_rat__kgkg( : )
    !> Relative humidity [unitless fraction] 1...n_layers
    real(kind=mdbl), allocatable :: relative_humidity__dec( : )
    !> Cloud liquid water path [kg m2]
    !! ('typical' liquid cloudwater path for non-convective clouds based on
    !!  approximations in NCAR's CCM2)
    real(kind=mdbl), allocatable :: cloud_liquid_water_path__kgm2( : )
#ifdef DO_PARTMC
    !> PartMC <-> MONARCH species map
    type(monarch_species_map_t), pointer :: species_map => null( )
    !> Phlex state for each grid cell in the column
    type(phlex_state_ptr), allocatable :: phlex_state( : )
#endif
    !> Working array for aerosol extinction [m-1]
    real(kind=mdbl), allocatable, private :: working_extinction__m( : )
  contains
    !> Update the state for a specified column
    procedure :: update
    !> Get a indices object loaded with the column indices
    procedure :: get_indices

#ifdef DO_PARTMC
    !> Get the phlex state of a grid cell in the column
    procedure :: get_phlex_state
    !> Save the phlex states of the column to the model state object
    procedure :: save_phlex_states
#endif

    ! Column properties

    !> Get the latitude of the column [decimal degrees]
    procedure :: latitude__deg
    !> Get the longitude of the column [decimal degrees]
    procedure :: longitude__deg
    !> Get the recipropcal of the Monin-Obukhov length [m-1]
    procedure :: recip_Monin_Obukhov_length__m
    !> Get the roughness length [m]
    procedure :: roughness_length__m
    !> Get the mixed layer height [m]
    procedure :: mixed_layer_height__m
    !> Relative humidity at 2m [decimal fraction]
    procedure :: relative_humidity_2m__dec
    !> Total column cloud water [kg m-2]
    procedure :: cloud_water__kgm2
    !> Total column rain water [kg m-2]
    procedure :: rain__kgm2
    !> Total column graupel [kg m-2]
    procedure :: graupel__kgm2
    !> Total column snow [kg m-2]
    procedure :: snow__kgm2
    !> Total column ice [kg m-2]
    procedure :: ice__kgm2
    !> Aerosol optical depth [unitless]
    procedure :: aerosol_optical_depth
    !> Clear-sky probe [unitless]
    procedure :: clear_sky_probe

    ! Surface-level properties

    !> Get the surface albedo [unitless fraction]
    procedure :: surface_albedo
    !> Get the surface pressure [Pa]
    procedure :: surface_pressure__Pa
    !> Get the skin temperature [K]
    procedure :: skin_temperature__K
    !> Get the downward shortwave radiation at the surface [W m-2]
    procedure :: surface_downward_shortwave_radiation__Wm2
    !> Get the USGS land-use type id
    procedure :: USGS_land_use_type_id
    !> Get the friction velocity at the surface [m s-1]
    procedure :: friction_velocity__ms
    !> Get the convective precipitation for the current time step [m]
    procedure :: convective_precip__m
    !> Get the non-convective precipitation for the current time step [m]
    procedure :: non_convective_precip__m

    ! Layer-specific properties
    !> Get a set of grid cell indices
    procedure :: grid_cell_indices
    !> Get the specific humidity in a grid cell
    procedure :: specific_humidity__kgkg
    !> Determine whether it is raining in a grid cell
    procedure :: is_raining
    !> Get the aerosol mass loading [kg m-2]
    procedure :: aerosol_mass_loading__kgm2
    !> Get the mid-layer height above sea-level [m]
    procedure :: mid_layer_height__m
    !> Get the cloud optical thickness within a layer [unitless]
    procedure :: cloud_optical_thickness

    ! Debugging functions
    !> Validate the contents of a monarch_column_t object
    procedure :: validate_contents

    ! Private functions
    !> Calculate the lowest layer in the tropopause
    procedure, private :: calc_tropopause_layer
  end type monarch_column_t

  !> Constructor
  interface monarch_column_t
    procedure :: constructor_no_partmc
#ifdef DO_PARTMC
    procedure :: constructor_partmc
#endif
  end interface monarch_column_t

  !> Check that a value falls within a specified range
  interface check_value
    module procedure :: check_real, check_int
  end interface check_value

contains

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

  !> Constructor for monarch_column_t when not using PartMC-Phlex
  !!
  !! Each PartMC<->MONARCH interface should have one column object that it can
  !! use while solving the chemistry
  function constructor_no_partmc( diag ) result( new_obj )
    use monarch_util,                       only : monarch_dimensions_t,     &
                                                   assert_msg

    !> New column object
    type(monarch_column_t), pointer :: new_obj
    !> Diagnostics object attached to internal state with model dimensions
    type(monarch_diagnostics_t), intent(in) :: diag

    integer(kind=mint) :: i_state
    type(monarch_dimensions_t) :: model_domain

    call assert_msg( 370577095, .not. ( diag%use_partmc_for_chemistry( )     &
                                        .and. diag%is_chemistry_on( ) ),     &
                     "Called wrong monarch_column_t constructor." )

    allocate( new_obj )

    model_domain     = diag%total_domain_dimensions( )
    new_obj%n_layers = model_domain%upper%vertical_idx( )

    allocate( new_obj%temperature__K(             new_obj%n_layers     ) )
    allocate( new_obj%btw_layer_pressure__Pa(     new_obj%n_layers + 1 ) )
    allocate( new_obj%mid_layer_pressure__Pa(     new_obj%n_layers     ) )
    allocate( new_obj%pressure_change__Pa(        new_obj%n_layers     ) )
    allocate( new_obj%btw_layer_height__m(        new_obj%n_layers + 1 ) )
    allocate( new_obj%layer_thickness__m(         new_obj%n_layers     ) )
    allocate( new_obj%air_density__kgm3(          new_obj%n_layers     ) )
    allocate( new_obj%cloud_water_mix_rat__kgkg(  new_obj%n_layers     ) )
    allocate( new_obj%relative_humidity__dec(     new_obj%n_layers     ) )
    allocate( new_obj%cloud_liquid_water_path__kgm2( new_obj%n_layers  ) )
    allocate( new_obj%working_extinction__m(      new_obj%n_layers     ) )

  end function constructor_no_partmc

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#ifdef DO_PARTMC
  !> Constructor for monarch_column_t when using PartMC-Phlex
  !!
  !! Each PartMC<->MONARCH interface should have one column object that it can
  !! use while solving the chemistry
  function constructor_partmc( phlex_core, species_map, diag )               &
      result( new_obj )

    use pmc_phlex_core,                     only : phlex_core_t
    use monarch_util,                       only : monarch_dimensions_t,     &
                                                   assert_msg

    !> New column object
    type(monarch_column_t), pointer :: new_obj
    !> Phlex core - used to generate phlex states
    type(phlex_core_t), intent(in) :: phlex_core
    !> PartMC-Phlex <-> MONARCH species map
    type(monarch_species_map_t), target, intent(in) :: species_map
    !> Diagnostics object attached to internal state with model dimensions
    type(monarch_diagnostics_t), intent(in) :: diag

    integer(kind=mint) :: i_state
    type(monarch_dimensions_t) :: model_domain

    call assert_msg( 373483573, diag%use_partmc_for_chemistry( ) .and.       &
                                diag%is_chemistry_on( ),                     &
                     "Called wrong monarch_column_t constructor." )

    allocate( new_obj )

    model_domain     = diag%total_domain_dimensions( )
    new_obj%n_layers = model_domain%upper%vertical_idx( )

    allocate( new_obj%temperature__K(             new_obj%n_layers     ) )
    allocate( new_obj%btw_layer_pressure__Pa(     new_obj%n_layers + 1 ) )
    allocate( new_obj%mid_layer_pressure__Pa(     new_obj%n_layers     ) )
    allocate( new_obj%pressure_change__Pa(        new_obj%n_layers     ) )
    allocate( new_obj%btw_layer_height__m(        new_obj%n_layers + 1 ) )
    allocate( new_obj%layer_thickness__m(         new_obj%n_layers     ) )
    allocate( new_obj%air_density__kgm3(          new_obj%n_layers     ) )
    allocate( new_obj%cloud_water_mix_rat__kgkg(  new_obj%n_layers     ) )
    allocate( new_obj%relative_humidity__dec(     new_obj%n_layers     ) )
    allocate( new_obj%cloud_liquid_water_path__kgm2( new_obj%n_layers  ) )
    if( diag%use_partmc_for_chemistry( ) .and.                               &
        diag%is_chemistry_on( ) ) then
      new_obj%species_map => species_map
      allocate( new_obj%phlex_state( new_obj%n_layers )  )
      do i_state = 1, new_obj%n_layers
        new_obj%phlex_state( i_state )%val => phlex_core%new_state( )
      end do
    end if
    allocate( new_obj%working_extinction__m(      new_obj%n_layers     ) )

  end function constructor_partmc
#endif
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Update the state for a specified column
  subroutine update( this, diag, indices )

    use module_constants                          , only : g                 & ! <-- gravitational acceleration [m s-2]
                                                           ,EPSQ               ! <-- minimum value for specific humidity [kg kg-1]

    !> Column state
    class(monarch_column_t), intent(inout) :: this
    !> Diagnostic object attached to current model state
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Indices for the column
    type(monarch_point_t), intent(in) :: indices

    real(kind=mdbl) :: press_diff
    real(kind=mflt) :: cloud_water
    real(kind=mdbl) :: water_scale_height
    real(kind=mdbl) :: curr_water_fac
    real(kind=mdbl) :: last_water_fac = 0.0
    real(kind=mdbl) :: cloud_rh
    integer(kind=mint) :: i_layer

    ! Save the column coordinates
    this%indices = indices

    ! Update the diagnostics object
    this%diag = diag

    ! Get the current julian day [decimal days]
    this%julian_day = this%diag%julian_day( )

    ! Get the time of day in UTC at mid-timestep [s]
    this%mid_time_step_UTC__s = this%diag%mid_time_step_UTC__s( )

    ! Get the column's sigma scaling [Pa]
    ! Pressures are determined using the sigma coordinate up to a certain
    ! layer( sigma range ) and by the hydrostatic pressure coordinate above
    ! that( pressure range ) TODO Is this right?
    press_diff = diag%pressure_sigma_scaling__Pa( this%indices )

    call this%indices%set_vertical_idx( this%n_layers )

    ! Save the pressure at the top of the highest layer [Pa]
    ! this%btw_layer_pressure__Pa( this%n_layers + 1 ) = &
    !     diag%top_of_layer_pressure__Pa( this%indices )
    ! FIXME Why are these different?
    this%btw_layer_pressure__Pa( this%n_layers + 1 ) = &
        diag%top_of_domain_pressure__Pa( )

    ! Get the liquid water scale height [m]
    water_scale_height = diag%liquid_water_scale_height__m( this%indices )

    ! Move from the top of the atmoshere down to the surface
    ! TODO Rework to remove unneeded allocatable arrays in the derived type
    do i_layer = this%n_layers, 1, -1

      ! Set the vertical index
      call this%indices%set_vertical_idx( i_layer )

      ! Save the temperature [K]
      this%temperature__K( i_layer ) = diag%temperature__K( this%indices )

      ! Save the mid-layer pressure [Pa]
      this%mid_layer_pressure__Pa( i_layer ) = &
          this%diag%mid_layer_pressure__Pa( this%indices )

      ! Save the air density [kg m-3]
      this%air_density__kgm3( i_layer ) = &
          this%diag%air_density__kgm3( this%indices )

      ! Save the relative humidity [unitless fraction]
      this%relative_humidity__dec( i_layer ) = &
          this%diag%relative_humidity__dec( this%indices )

      ! Save the cloud water mixing ratio [kg kg-1]
      ! FIXME Why is there a factor of 1/0.622?
      ! FIXME This is for the photolysis calculations, but appears to get set
      ! to zero before it's used
      cloud_water = diag%cloud_water_mixing_ratio__kgkg( this%indices )
      this%cloud_water_mix_rat__kgkg( i_layer ) = amax1( cloud_water,        &
                                                         EPSQ ) / 0.622d0

      ! Save the pressure change within this layer [Pa]
      this%pressure_change__Pa( i_layer ) =                                  &
          diag%pressure_sigma_layer_thickness( this%indices )              * & ! <-- sigma layer thickness - sigma range [unitless]
          press_diff                                                       + & ! <-- sigma scaling [Pa]
          diag%pressure_layer_thickness__Pa( this%indices )                    ! <-- pressure layer thickness - pressure range [Pa]

      ! Save the pressure at the bottom of this layer [Pa]
      ! this%btw_layer_pressure__Pa( i_layer ) =                               &
      !    diag%bottom_of_layer_pressure__Pa( this%indices )
      ! FIXME Why are these different?
      this%btw_layer_pressure__Pa( i_layer ) =                               &
          this%btw_layer_pressure__Pa( i_layer + 1 )                       + &
          this%pressure_change__Pa( i_layer )

      ! Save the thickness of the layer [m]
      this%layer_thickness__m( i_layer ) =                                   &
                ( this%btw_layer_pressure__Pa( i_layer )                   - & ! <-- pressure at the bottom of the layer [Pa]
                  this%btw_layer_pressure__Pa( i_layer + 1 )  )            / & ! <-- pressure at the top of the layer [Pa]
                ( this%air_density__kgm3( i_layer ) * g )                      ! <-- air density [kg m-3] * gravity [m s-2]

#ifdef DO_PARTMC
      if( diag%use_partmc_for_chemistry( ) .and.                             &
          diag%is_chemistry_on( ) ) then
        ! Update the phlex states for the column
        call diag%get_phlex_state( this%indices,                             & !<-- Current grid cell indices
                                   this%species_map,                         & !<-- PartMC-Phlex <-> MONARCH species map
                                   this%phlex_state( i_layer )%val  )          !<-- Phlex state returned
      end if
#endif
    end do

    ! Set the between layer heights above sea level [m]
    this%btw_layer_height__m( 1 ) =                                          &
          diag%surface_geopotential__m2s2( this%indices )                  / & ! <-- surface geopotential [m2 s-2]
          g                                                                    ! <-- acceleration by gravity [m s-2]
    do i_layer = 2, this%n_layers + 1
      this%btw_layer_height__m( i_layer )                                  = &
          this%btw_layer_height__m( i_layer - 1 )                          + & ! <-- between layer height of last layer [m]
          this%layer_thickness__m( i_layer - 1 )                               ! <-- thickness of last layer [m]
    end do

    ! Calculate the cloud liquid water path [kg/m2]
    ! ('typical' liquid cloudwater path for non-convective clouds based on
    !  approximations in NCAR's CCM2)
    last_water_fac = exp( -this%btw_layer_height__m( this%n_layers + 1 )   / & !<-- exponential factor used in calculating
                          water_scale_height )                                 !    liquid water path
    do i_layer = this%n_layers, 1, -1
      cloud_rh = ( max( min( this%relative_humidity__dec( i_layer ), 1.0d0 ),& !<-- estimate a relative cloud density
                              0.8d0 ) - 0.8d0 ) / 0.2d0                        !    FIXME is this the right description for this term?
      if( cloud_rh .lt. 0.01d0 ) cloud_rh = 0.0d0
      curr_water_fac =                                                       & !<-- exponential factor used in calculating
          exp( -this%btw_layer_height__m( i_layer ) / water_scale_height )     !    liquid water path
      this%cloud_liquid_water_path__kgm2( i_layer )                        = &
          0.00018d0                                                        * & !<-- reference liquid water conc. [kg/m3]
          cloud_rh                                                         * & !<-- fractional cloud estimate [decimal fraction]
          water_scale_height                                               * & !<-- liquid water scale height [m]
          ( curr_water_fac - last_water_fac )                                  !<-- diff in exp factor at  top and bottom of cell
      last_water_fac = curr_water_fac
    end do

    ! Set the top-level chemistry and transport layers
    this%num_tropospheric_layers = this%calc_tropopause_layer( )
    this%top_transport_layer     = this%num_tropospheric_layers

  end subroutine update

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

  !> Get an indices object loaded with the column indices
  function get_indices( this )

    !> Column indices
    type(monarch_point_t) :: get_indices
    !> Column state
    class(monarch_column_t), intent(in) :: this

    get_indices = this%indices

  end function get_indices

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

    use pmc_phlex_state,                    only : phlex_state_t
    use monarch_util,                       only : assert, assert_msg

    !> Phlex state of the grid cell
    type(phlex_state_t), pointer :: phlex_state
    !> Column state
    class(monarch_column_t), intent(inout) :: this
    !> Grid cell indices
    type(monarch_point_t), intent(in) :: indices

    call assert_msg( 358595242, this%diag%use_partmc_for_chemistry( ),       &
                     "Trying to get a phlex_state_t object when PartMC "//   &
                     "chemistry is not turned on." )
    call assert_msg( 815639837, this%diag%is_chemistry_on( ),                &
                     "Trying to get a phlex_state_t object when "//          &
                     "chemistry is not turned on." )
    call assert( 224218725, allocated( this%phlex_state ) )

    phlex_state => this%phlex_state( indices%vertical_idx( ) )%val

  end function get_phlex_state
#endif
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#ifdef DO_PARTMC
  !> Save the column phlex states to the model state object
  subroutine save_phlex_states( this, state_changer )

    use monarch_state_changer,              only : monarch_state_changer_t
    use monarch_util,                       only : assert, assert_msg

    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> State changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

    type(monarch_point_t) :: indices
    integer(kind=mint) :: i_layer

    call assert_msg( 910110648, this%diag%use_partmc_for_chemistry( ),       &
                     "Trying to save a phlex_state_t object when PartMC "//  &
                     "chemistry is not turned on." )
    call assert_msg( 307074659, this%diag%is_chemistry_on( ),                &
                     "Trying to save a phlex_state_t object when "//         &
                     "chemistry is not turned on." )
    call assert( 620917853, allocated( this%phlex_state ) )

    indices = this%indices
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )
      call state_changer%save_phlex_state(                                   &
                                  indices,                                   &
                                  this%phlex_state( i_layer )%val,           &
                                  this%diag%air_density__molm3( indices ) )
    end do

  end subroutine save_phlex_states
#endif
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Get the latitude of the column [decimal degrees]
  pure function latitude__deg( this ) result( lat )

    !> Column latitude [decimal degrees]
    real(kind=mdbl) :: lat
    !> Column state
    class(monarch_column_t), intent(in) :: this

    lat = this%diag%latitude__deg( this%indices )

  end function latitude__deg

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

  !> Get the longitude of the column [decimal degrees]
  pure function longitude__deg( this ) result( long )

    !> Column longitude [decimal degrees]
    real(kind=mdbl) :: long
    !> Column state
    class(monarch_column_t), intent(in) :: this

    long = this%diag%longitude__deg( this%indices )

  end function longitude__deg

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

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

    !> Reciprocal of the Monin-Obukhov length [m-1]
    real(kind=mdbl) :: rmol
    !> Column state
    class(monarch_column_t), intent(in) :: this

    rmol = this%diag%recip_Monin_Obukhov_length__m( this%indices )

  end function recip_Monin_Obukhov_length__m

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

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

    !> Roughness length [m]
    real(kind=mdbl) :: rough_len
    !> Column state
    class(monarch_column_t), intent(in) :: this

    rough_len = this%diag%roughness_length__m( this%indices )

  end function roughness_length__m

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

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

    !> Mixed layer height [m]
    real(kind=mdbl) :: height
    !> Column state
    class(monarch_column_t), intent(in) :: this

    height = this%diag%mixed_layer_height__m( this%indices )

  end function mixed_layer_height__m

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

  !> Relative humidity at 2m [decimal fraction]
  function relative_humidity_2m__dec( this ) result( rel_hum )

    !> Relative humidity at 2m [decimal fraction]
    real(kind=mdbl) :: rel_hum
    !> Column state
    class(monarch_column_t), intent(in) :: this

    rel_hum = this%diag%relative_humidity_2m__dec( this%indices )

  end function relative_humidity_2m__dec

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

  !> Total column cloud water [kg m-2]
  !!
  !! This value can be multiplied by the column surface area to get total
  !! cloud water in units of [kg].
  pure function cloud_water__kgm2( this ) result( cloud_water )

    !> Total column cloud water [kg m-2]
    real(kind=mdbl) :: cloud_water
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer

    cloud_water = real( 0.0, kind=mdbl )
    do i_layer = 1, this%n_layers
      cloud_water = cloud_water                               +              &
                    this%cloud_water_mix_rat__kgkg( i_layer ) *              &
                    this%air_density__kgm3( i_layer )         *              &
                    this%layer_thickness__m( i_layer )
    end do

  end function cloud_water__kgm2

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

  !> Total column rain water [kg m-2]
  !!
  !! This value can be multiplied by the column surface area to get total
  !! rain water in units of [kg].
  function rain__kgm2( this ) result( rain )

    !> Total column rain water [kg m-2]
    real(kind=mdbl) :: rain
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer
    type(monarch_point_t) :: indices

    indices = this%indices
    rain = real( 0.0, kind=mdbl )
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )
      rain = rain                                         +                  &
             this%diag%rain_mixing_ratio__kgkg( indices ) *                  &
             this%air_density__kgm3( i_layer )            *                  &
             this%layer_thickness__m( i_layer )
    end do

  end function rain__kgm2

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

  !> Total column graupel [kg m-2]
  !!
  !! This value can be multiplied by the column surface area to get total
  !! graupel in units of [kg].
  function graupel__kgm2( this ) result( graupel )

    !> Total column graupel [kg m-2]
    real(kind=mdbl) :: graupel
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer
    type(monarch_point_t) :: indices

    indices = this%indices
    graupel = real( 0.0, kind=mdbl )
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )
      graupel = graupel                                         +            &
                this%diag%graupel_mixing_ratio__kgkg( indices ) *            &
                this%air_density__kgm3( i_layer )               *            &
                this%layer_thickness__m( i_layer )
    end do

  end function graupel__kgm2

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

  !> Total column snow [kg m-2]
  !!
  !! This value can be multiplied by the column surface area to get total
  !! snow in units of [kg].
  function snow__kgm2( this ) result( snow )

    !> Total column snow [kg m-2]
    real(kind=mdbl) :: snow
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer
    type(monarch_point_t) :: indices

    indices = this%indices
    snow = real( 0.0, kind=mdbl )
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )
      snow = snow                                         +                  &
             this%diag%snow_mixing_ratio__kgkg( indices ) *                  &
             this%air_density__kgm3( i_layer )            *                  &
             this%layer_thickness__m( i_layer )
    end do

  end function snow__kgm2

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

  !> Total column ice [kg m-2]
  !!
  !! This value can be multiplied by the column surface area to get total
  !! ice in units of [kg].
  function ice__kgm2( this ) result( ice )

    !> Total column ice [kg m-2]
    real(kind=mdbl) :: ice
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer
    type(monarch_point_t) :: indices

    indices = this%indices
    ice = real( 0.0, kind=mdbl )
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )
      ice = ice                                               +              &
            this%diag%cloud_ice_mixing_ratio__kgkg( indices ) *              &
            this%air_density__kgm3( i_layer )                 *              &
            this%layer_thickness__m( i_layer )
    end do

  end function ice__kgm2

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

  !> Aerosol optical depth
  !!
  !! When an extinction array is included, it will be populated with the
  !! aerosol extinction for each vertical cell in the column
  function aerosol_optical_depth( this, group, extinction__m ) result( aod )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_group_collection_t

    !> Aerosol optical depth
    real(kind=mdbl) :: aod
    !> Column state
    class(monarch_column_t), intent(inout) :: this
    !> Aerosol group
    !! If not included, the total extinction and optical depth will be
    !! calculated.
    class(monarch_aerosol_group_collection_t), optional, intent(in) :: group
    !> Aerosol extinction for each cell in the column [m-1]
    real(kind=mdbl), allocatable, optional, intent(inout) :: extinction__m(:)

    integer(kind=mint) :: i_layer
    type(monarch_point_t) :: indices

    ! Loop through the layers, calculating extinction in each vertical cell
    ! and summing the contributions to AOD
    indices = this%indices
    aod = 0
    do i_layer = 1, this%n_layers
      call indices%set_vertical_idx( i_layer )

      ! If this is for a specific aerosol group (i.e., dust, sea salt, etc.)
      ! get the extinciton for only the requested group ...
      if( present( group ) ) then
        this%working_extinction__m( i_layer ) =                              &
            this%diag%aerosol_extinction__m( indices, group )

      ! ... otherwise get the total aerosol extinction in the cell
      else
        this%working_extinction__m( i_layer ) =                              &
            this%diag%aerosol_extinction__m( indices )
      end if

      ! Add the contribution from this cell to the aod
      aod = aod + this%working_extinction__m( i_layer )                    * &
                  this%layer_thickness__m( i_layer )
    end do

    ! If an extinction array was passed, fill it with the extinction values
    ! for each vertical layer
    if( present( extinction__m ) )                                           &
        extinction__m( : ) = this%working_extinction__m( : )

  end function aerosol_optical_depth

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

  !> Get the clear-sky probe from cloud fraction and coin-flipping [unitless fraction]
  function clear_sky_probe( this ) result( cskyprob )

    !> Clear-sky probe [unitless]
    real(kind=mdbl) :: cskyprob
    !> Column state 
    class(monarch_column_t), intent(in) :: this 
 
    integer(kind=mint)     :: i_layer
    type(monarch_point_t)  :: indices
    
    logical, save   :: firstrandom
    integer, dimension (33) :: rseed = (/ 1, 2, 3, 4, 5, 6, 7, 8, 9,10, &
                                          11,12,13,14,15,16,17,18,19,20, &
                                          21,22,23,24,25,26,27,28,29,30, &
                                          31,32,33/)

    firstrandom = .true. 
    if (firstrandom) then
        call random_seed (put=rseed)
        firstrandom=.false.
    end if 

    indices = this%indices 
    cskyprob = 1.
    do i_layer = 1, this%n_layers
       call indices%set_vertical_idx( i_layer )
       cskyprob = cskyprob*(1.-this%diag%cloud_fraction__dec( indices ) )
    end do 

  end function clear_sky_probe

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

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

    !> Column surface albedo [unitless fraction]
    real(kind=mdbl) :: surf_alb
    !> Column state
    class(monarch_column_t), intent(in) :: this

    surf_alb = this%diag%surface_albedo( this%indices )

  end function surface_albedo

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

  !> Get the surface pressure [Pa]
  pure function surface_pressure__Pa( this ) result( surf_press )

    !> Column surface pressure [Pa]
    real(kind=mdbl) :: surf_press
    !> Column state
    class(monarch_column_t), intent(in) :: this

    surf_press = this%btw_layer_pressure__Pa( 1 )

  end function surface_pressure__Pa

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

  !> Get the skin temperature [K]
  pure function skin_temperature__K( this ) result( skin_temp )

    !> Skin temperature [K]
    real(kind=mdbl) :: skin_temp
    !> Column state
    class(monarch_column_t), intent(in) :: this

    skin_temp = this%diag%skin_temperature__K( this%indices )

  end function skin_temperature__K

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

  !> Get the downward shortwave radiation at the surface [W m-2]
  pure function surface_downward_shortwave_radiation__Wm2( this ) result( rad )

    !> Surface shortwave radiation [W m-2]
    real(kind=mdbl) :: rad
    !> Column state
    class(monarch_column_t), intent(in) :: this

    rad = this%diag%surface_downward_shortwave_radiation__Wm2( this%indices )

  end function surface_downward_shortwave_radiation__Wm2

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

  !> Get the USGS land-use type ID for a vertical column
  !!     Land use types:
  !!     USGS type                                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 ) result( land_use_type )

    !> USGS land-use type id
    integer(kind=mint) :: land_use_type
    !> Column state
    class(monarch_column_t), intent(in) :: this

    land_use_type = this%diag%USGS_land_use_type_id( this%indices )

  end function USGS_land_use_type_id

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

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

    !> Friction velocity at the surface [m s-1]
    real(kind=mdbl) :: fric_vel
    !> Column state
    class(monarch_column_t), intent(in) :: this

    fric_vel = this%diag%friction_velocity__ms( this%indices )

  end function friction_velocity__ms

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

  !> Get the convective precipitation for the current time step [m]
  pure function convective_precip__m( this, last_time_step ) result( precip )

    use monarch_last_time_step,             only : monarch_last_time_step_t

    !> Convective precipitation for this time step [m]
    real(kind=mdbl) :: precip
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Values from the last time step
    type(monarch_last_time_step_t), intent(in) :: last_time_step

    real(kind=mdbl) :: acc_conv, last_acc_conv

    acc_conv      = this%diag%convective_precip__m( this%indices )
    last_acc_conv = last_time_step%convective_precip__m( this%indices )

    precip        = max( acc_conv - last_acc_conv, 0.0 )

  end function convective_precip__m

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

  !> Get the non-convective precipitation for the current time step [m]
  pure function non_convective_precip__m( this, last_time_step )             &
      result( precip )

    use monarch_last_time_step,             only : monarch_last_time_step_t

    !> Non-convective precipitation for this time step [m]
    real(kind=mdbl) :: precip
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Values from the last time step
    type(monarch_last_time_step_t), intent(in) :: last_time_step

    real(kind=mdbl) :: acc, last_acc, acc_conv, last_acc_conv

    acc           = this%diag%accumulated_precip__m( this%indices )
    last_acc      = last_time_step%accumulated_precip__m( this%indices )
    acc_conv      = this%diag%convective_precip__m( this%indices )
    last_acc_conv = last_time_step%convective_precip__m( this%indices )

    precip        = max( ( acc - last_acc ) -                                &
                         ( acc_conv - last_acc_conv ), 0.0 )

  end function non_convective_precip__m

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

  !> Get a set of grid cell indices
  function grid_cell_indices( this, layer_idx ) result( indices )

    !> Grid cell indices
    type(monarch_point_t) :: indices
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Layer index
    integer(kind=mint), intent(in) :: layer_idx

    indices = this%indices
    call indices%set_vertical_idx( layer_idx )

  end function grid_cell_indices

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

  !> Get the specific humidity in a grid cell [kg kg-1]
  function specific_humidity__kgkg( this, layer_idx )                        &
      result( spec_humid )

    !> Specific humidity [kg kg-1]
    real(kind=mdbl) :: spec_humid
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Layer index
    integer(kind=mint), intent(in) :: layer_idx

    type(monarch_point_t) :: indices

    indices = this%indices
    call indices%set_vertical_idx( layer_idx )
    spec_humid = this%diag%specific_humidity__kgkg( indices )

  end function specific_humidity__kgkg

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

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

    !> Flag indicating rain
    logical :: is_raining
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Layer index
    integer(kind=mint), intent(in) :: layer_idx

    type(monarch_point_t) :: indices

    indices = this%indices
    call indices%set_vertical_idx( layer_idx )
    is_raining = this%diag%is_raining( indices )

  end function is_raining

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

  !> Aerosol mass loading [kg m-2]
  function aerosol_mass_loading__kgm2( this, group ) result( loading )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_group_collection_t

    !> Aerosol mass loading [kg m-2]
    real(kind=mdbl) :: loading
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Aerosol group to get loading for
    !! If not included, total aerosol mass loading is calculated
    class(monarch_aerosol_group_collection_t), optional, intent(in) :: group

    type(monarch_point_t) :: indices
    integer(kind=mint) :: i_layer

    indices = this%indices
    if( present( group ) ) then
      do i_layer = 1, this%n_layers
        call indices%set_vertical_idx( i_layer )
        loading = loading                                                  + &
                  this%diag%aerosol_dry_mass_conc__kgm3( indices, group )  * &
                  this%layer_thickness__m( i_layer )
      end do
    else
      do i_layer = 1, this%n_layers
        call indices%set_vertical_idx( i_layer )
        loading = loading                                                  + &
                  this%diag%aerosol_dry_mass_conc__kgm3( indices )         * &
                  this%layer_thickness__m( i_layer )
      end do
    end if

  end function aerosol_mass_loading__kgm2

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

  !> Get the mid-layer height about sea level [m]
  !! FIXME Is this the right definition?
  function mid_layer_height__m( this, layer_idx ) result( height )

    !> Mid-layer height above sea level [m]
    real(kind=mdbl) :: height
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Layer index
    integer(kind=mint), intent(in) :: layer_idx

    height = this%btw_layer_height__m( layer_idx )                         + &
             0.5 * this%layer_thickness__m( layer_idx )

  end function mid_layer_height__m

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

  !> Get the cloud optical thickness within a layer [unitless] 1...n_layers
  !!
  !! Cloud optical thickness (COT) is currently calculated using fractional
  !! cloudiness based on the relative humidity. Parameters used to calculate
  !! 'typical' liquid cloudwater path values for non-convective clouds based
  !! on approximations in NCAR's CCM2.
  !!
  !! TODO: include full equation for COT
  !!
  !! The approximation used here assumes cloud droplets have a radius \f$r\f$
  !! of 1.0e-5 m, and the density of water \f$\rho\f$ is 1000 kg/m^3. Then, the
  !! COT \f$\tau\f$ is estimated as:
  !! \f[
  !!   \tau = \frac{3 r}{2 \rho} L
  !! \f]
  !! where \f$L\f$ is the cloud liquid water path [kg/m^2].
  pure function cloud_optical_thickness( this, layer_idx ) result( thickness )

    !> Cloud optical thickness in this layer [unitless]
    real(kind=mdbl) :: thickness
    !> Column state
    class(monarch_column_t), intent(in) :: this
    !> Layer index
    integer(kind=mint), intent(in) :: layer_idx

    real(kind=mdbl), parameter :: prefactor = 150.0d0                          !<-- Prefactor in approximation of COT [m^2/kg]
#ifdef NOT_FINISHED
    real(kind=mdbl) :: cloud_liq_water_path

    ! Calculate the cloud liquid water path from the cloud water mixing ratio
    ! and the cloud fraction. TODO finish when cloud fraction is available
    cloud_liq_water_path =                                                   & !<-- cloud liquid water path [kg m-2]
        this%cloud_water_mixing_ratio__kgkg( layer_idx )                   * & !<-- cloud water mixing ratio [kg kg-1]
        this%air_density__kgm3(              layer_idx )                   * & !<-- air density [kg m-3]
        this%cloud_fraction(                 layer_idx )                   * & !<-- cloud fraction [unitless] FIXME needs created
        this%layer_thickness__m(             layer_idx )                     & !<-- layer thickness [m]
#endif

    ! FIXME Ask Oriol if this is right
    thickness = max( prefactor                                             * &
                     this%cloud_liquid_water_path__kgm2( layer_idx ),        &
                     real( 0.0, kind=mdbl ) )

  end function cloud_optical_thickness

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

  !> Validate the contents of a monarch_column_t object
  !! This should only be used for debugging
  function validate_contents( this ) result( passed )

    use monarch_util,                            only : to_string

    !> Flag indicating valid contents
    logical :: passed
    !> Column state
    class(monarch_column_t), intent(in) :: this

    integer(kind=mint) :: i_layer
    real(kind=mdbl)    :: diff_comp

    passed = .true.

    passed = passed .and. check_value( this%top_transport_layer,             &
          1, this%n_layers, "top transport layer" )
    passed = passed .and. check_value( this%num_tropospheric_layers,         &
          1, this%top_transport_layer, "number of tropospheric layers" )
    passed = passed .and. check_value( this%julian_day,                      &
          1.d-10, 365.0d0, "julian day" )
    passed = passed .and. check_value( this%mid_time_step_UTC__s,            &
          1.d-10, 3600.0d0, "mid-timestep UTC" )
    do i_layer = 1, this%n_layers
      passed = passed .and. check_value(                                     &
            this%temperature__K( i_layer ),                                  &
            1.0d-3, 1.0d3, "temperature "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%btw_layer_pressure__Pa( i_layer ),                          &
            this%btw_layer_pressure__Pa( i_layer + 1 ), 2.0d6,               &
            "bottom of layer pressure "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%btw_layer_pressure__Pa( i_layer + 1 ),                      &
            1.0d-3, this%btw_layer_pressure__Pa( i_layer ),                  &
            "top of layer pressure "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%mid_layer_pressure__Pa( i_layer ),                          &
            this%btw_layer_pressure__Pa( i_layer + 1 ),                      &
            this%btw_layer_pressure__Pa( i_layer ),                          &
            "mid-layer pressure "//to_string( i_layer ) )
      diff_comp = this%btw_layer_pressure__Pa( i_layer ) -                   &
                  this%btw_layer_pressure__Pa( i_layer + 1 )
      passed = passed .and. check_value(                                     &
            this%pressure_change__Pa( i_layer ),                             &
            diff_comp - 1.0d-3, diff_comp + 1.0d-3,                          &
            "pressure change in layer "//to_string(i_layer ) )
      passed = passed .and. check_value(                                     &
            this%btw_layer_height__m( i_layer ),                             &
            0.0d0, this%btw_layer_height__m( i_layer + 1 ),                  &
            "height of bottom of layer "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%btw_layer_height__m( i_layer + 1 ),                         &
            this%btw_layer_height__m( i_layer ), 1.0d6,                      &
            "height of top of layer "//to_string( i_layer ) )
      diff_comp = this%btw_layer_height__m( i_layer + 1 ) -                  &
                  this%btw_layer_height__m( i_layer )
      passed = passed .and. check_value(                                     &
            this%layer_thickness__m( i_layer ),                              &
            diff_comp - 1.0d-3, diff_comp + 1.0d-3,                          &
            "layer thickness "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%air_density__kgm3( i_layer ),                               &
            1d-10, 1.0d1, "air density "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%cloud_water_mix_rat__kgkg( i_layer ),                       &
            0.0d0, 1.0d0, "cloud water mixing ratio "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%relative_humidity__dec( i_layer ),                          &
            0.0d0, 1.1d0, "relative humidity "//to_string( i_layer ) )
      passed = passed .and. check_value(                                     &
            this%cloud_optical_thickness( i_layer ),                         &
            0.0d0, 5000.0d0, "cloud optical depth "//to_string( i_layer ) )
    end do

  end function validate_contents

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

  !> Calculate the index of the lowest layer in the tropopause
  !!
  !! The lowest occurance of a three-layer mean lapse rate that is less than
  !! or equal to a critical lapse rates is used to identify the tropopause.
  !! The critical lapse rate is 0.002 K/m, in accordance with the WMO
  !! definition of a tropopause. A maximum tropopause pressure of 5e4 Pa is
  !! enforced. Layer indices start at 1 nearest the surface and increase
  !! with altitude.
  function calc_tropopause_layer( this ) result( trop_layer )

    !> Tropopause layer index 1( surface layer ) ...n_layers( top layer )
    integer(kind=mint) :: trop_layer
    !> Column state
    class(monarch_column_t), intent(in) :: this

    ! Critical lapse rate( K/m ) and minimum pressure( Pa ) for tropopause
    real(kind=mdbl), parameter :: crit_lapse_rate = 0.002,                     &
                                min_pressure    = 5.0e4

    integer(kind=mint) :: i_layer
    real(kind=mdbl) :: pressure, delta_temp, delta_height, lapse_rate,         &
                     last_3_layer_height, curr_three_layer_height

    ! Assume a default tropopause layer near the top of column
    trop_layer = this%n_layers - 2

    ! FIXME For now, the tropopause location is not used and there are some
    ! discrepancies between the stated methodology and the original MONARCH
    ! code, as well as some errors in the calculation. Find an appropriate
    ! method for finding the top layer to do chemistry in and finish the
    ! function
    if( .true. ) return

    ! Start near the surface and work up until the conditions are met
    do i_layer = 2, this%n_layers-3

      pressure     = this%mid_layer_pressure__Pa( i_layer )

      ! Wait until the minimum pressure is reached
      if( pressure .ge. min_pressure ) cycle

      delta_temp   = this%temperature__K( i_layer+1 ) -                      &
                     this%temperature__K( i_layer )
      delta_height = 0.5 *( this%btw_layer_height__m( i_layer+1 ) -          &
                             this%btw_layer_height__m( i_layer-1 )  )
      lapse_rate   = - delta_temp / delta_height

      ! Wait until the first-single layer lapse rate is below the threshhold
      if( lapse_rate .ge. crit_lapse_rate ) cycle

      ! Find the first 3-layer average lapse rate that meets the criteria

      ! FIXME Finish

    end do

  end function calc_tropopause_layer

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

  !> Check that a given value is within a specified range
  function check_real( val, min_val, max_val, fail_msg )

    use monarch_util,                              only : to_string

    !> Flag indicating whether value is within the specified range
    logical :: check_real
    !> Value to check
    real(kind=mdbl), intent(in) :: val
    !> Minimum value
    real(kind=mdbl), intent(in) :: min_val
    !> Maximum value
    real(kind=mdbl), intent(in) :: max_val
    !> Failure message
    character(len=*) :: fail_msg

    if( val.lt.min_val .or. val.gt.max_val ) then
      write(*,*) fail_msg//"; value: "//to_string( val )//                   &
                 " out of expected range: "//to_string( min_val )//" to "//  &
                 to_string( max_val )
      check_real = .false.
    else
      check_real = .true.
    end if

  end function check_real

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

  !> Check that a given value is within a specified range
  function check_int( val, min_val, max_val, fail_msg )

    use monarch_util,                               only : to_string

    !> Flag indicating whether value is within the specified range
    logical :: check_int
    !> Value to check
    integer(kind=mint), intent(in) :: val
    !> Minimum value
    integer(kind=mint), intent(in) :: min_val
    !> Maximum value
    integer(kind=mint), intent(in) :: max_val
    !> Failure message
    character(len=*) :: fail_msg

    if( val.lt.min_val .or. val.gt.max_val ) then
      write(*,*) fail_msg//"; value: "//to_string( val )//                   &
                 " out of expected range: "//to_string( min_val )//" to "//  &
                 to_string( max_val )
      check_int = .false.
    else
      check_int = .true.
    end if

  end function check_int

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

end module monarch_column
