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

!> Original fixed aerosol representation used in MONARCH
module monarch_aero_rep_fixed
! MONARCH always has MPI
#define MONARCH_USE_MPI

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

  implicit none
  private

  public :: monarch_aero_rep_fixed_t, monarch_aero_groups_fixed_t

  !> Aerosol group names
  integer(kind=mint), parameter :: AERO_GROUP_UNKNOWN = 0
  integer(kind=mint), parameter :: AERO_GROUP_ASH     = 1
  integer(kind=mint), parameter :: AERO_GROUP_DUST    = 2
  integer(kind=mint), parameter :: AERO_GROUP_SALT    = 3
  integer(kind=mint), parameter :: AERO_GROUP_OM      = 4
  integer(kind=mint), parameter :: AERO_GROUP_BC      = 5
  integer(kind=mint), parameter :: AERO_GROUP_SO4     = 6
  integer(kind=mint), parameter :: AERO_GROUP_NO3     = 7
  integer(kind=mint), parameter :: AERO_GROUP_NH4     = 8
  integer(kind=mint), parameter :: AERO_GROUP_UNSPC   = 9

  !> Aerosol distribution types
  integer(kind=mint), parameter :: AERO_DIST_UNKNOWN  = 0
  integer(kind=mint), parameter :: AERO_DIST_BIN      = 1
  integer(kind=mint), parameter :: AERO_DIST_MODE     = 2

  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  !!! Private, internal-use derived types !!!
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Extinction parameters by relative humidity
  type :: extinct_rel_humid_map_t
    !> Single-particle extinction efficiency at 550 nm
    !! FIXME Is this right?
    real(kind=mflt) :: ext_eff_550_nm = 0.0
    !> FIXME What is this?
    real(kind=mflt) :: d2wf = 1.0 ! FIXME rename
    !> Lower cutoff for relative humidity as a decimal [0...1]
    real(kind=mflt) :: min_rel_humid = 0.0
    !> Wet-to-dry mean volume ratio (sulfate case)
    real(kind=mflt) :: wet_to_dry_mean_volume_ratio = 0.0
    !> Wet effective radius [m] (sulfate case)
    real(kind=mflt) :: wet_effective_radius__m = 0.0
  end type extinct_rel_humid_map_t

  !> An individual bin or mode
  type :: fixed_division_t
    !> Number-to-dry-mass ratio [# kg-3]
    real(kind=mdbl) :: number_to_dry_mass_ratio = 0.0
    !> Effective radius [m]
    !! TODO doulble-check units (seems to be meters based on input data values)
    real(kind=mflt) :: effective_radius__m = 0.0
    !> Density [kg m-3]
    !! TODO doulble-check units (seems to be kg m-3 based on input data values)
    real(kind=mflt) :: density__kgm3 = 0.0
    !> A map of extinction parameters to relative humidity bins
    type(extinct_rel_humid_map_t), allocatable :: extinct_param( : )
  contains
    !> Print out the configuration of the division
    procedure :: print => div_print
  end type fixed_division_t

  !> A fixed distribution
  type :: fixed_dist_t
    !> Aerosol composition type (dust, sea salt, etc.)
    integer(kind=mint) :: composition_type = 0
    !> Aerosol distribution type (bins or mode)
    integer(kind=mint) :: dist_type = 0
    !> Beginning index of the distribution on tracer array
    integer(kind=mint) :: tracer_id = 0
    !> Divisions within the distribution (bins or species)
    type(fixed_division_t), allocatable :: div( : )
  contains
    !> Print out the configuration of the distribution
    procedure :: print => dist_print
  end type fixed_dist_t

  !> A set of flags for individual bins/modes for use in groups
  type :: group_dist_t
    !> Flag inidicating whether a bin/mode is select in the group
    logical, allocatable :: is_selected( : )
  end type group_dist_t

  !!!!!!!!!!!!!!!!!!!!!!!!!!!!
  !!! Public derived types !!!
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Fixed-dimension aerosol representation
  !!
  !! Original MONARCH aerosol representation with hard-coded dimensions
  type, extends(monarch_aerosol_representation_t) :: monarch_aero_rep_fixed_t
  private
    !> Pointer to the internal state
    type(solver_internal_state), pointer :: int_state => null()
    !> Number of aerosol groups (dust, sea salt, black carbon, etc.)
    integer(kind=mint) :: num_groups
    !> Working set of groups (dust, sea salt, black carbon, etc.)
    type(monarch_aero_groups_fixed_t), pointer :: all_groups => null()
    !> Group distribution
    !! A set of at least one bin/mode forming a distribution for each group
    type(fixed_dist_t), allocatable :: dist( : )
  contains
    !> Update the tracer ids for aerosol species based on the values in
    !! the solver internal state
    procedure :: update_tracer_ids
    !> Get a collection of pointers to the aerosol groups in this
    !! representation (dust, sea salt, black carbon, etc.)
    procedure :: groups
    !> Get a collection of pointers to the aerosol groups in this
    !! representation as the extending type
    procedure :: groups_fixed
    !> Get a set of the aerosol group names
    procedure :: group_names
    !> Check if an aerosol group exists in the representation
    procedure :: is_valid_group
    !> Get the number of bins/modes available in a particular group
    procedure :: num_bins
    procedure :: num_modes => num_bins
    !> Get the number of aerosol particles [# m-3]
    procedure :: num_particles__m3
    !> Get the dry aerosol mass concentration [kg m-3]
    procedure :: dry_mass_conc__kgm3
    !> Get the aerosol extinction [m-1]
    procedure :: extinction__m
    !> Determine the number of bytes required to pack the given object
    procedure :: pack_size
    !> Pack the given object to the buffer, advancing position
    procedure :: bin_pack
    !> Unpack the given object from the buffer, advancing position
    procedure :: bin_unpack
    !> Print out the configuration of the aerosol representation
    procedure :: print => do_print
    !> Get a group name by id
    procedure, private :: group_name
    !> Get a group id by name
    procedure, private :: group_id
  end type monarch_aero_rep_fixed_t

  !> Aerosol representation constructor
  interface monarch_aero_rep_fixed_t
    procedure :: constructor
  end interface monarch_aero_rep_fixed_t

  !> Group collection for the fixed-dimension aerosol representation
  !! In the fixed representation the groups are externally mixed, typically
  !! source-specific groups: dust, sea salt, black carbon, etc.
  !! Each group is made up of one or more bins/modes that are referenced by
  !! sequential indices.
  type, extends(monarch_aerosol_group_collection_t) ::                       &
      monarch_aero_groups_fixed_t
  private
    !> Pointer to the aerosol representation
    type(monarch_aero_rep_fixed_t), pointer :: aero_rep => null( )
    !> Flags indicating what aerosol types and which of their bins/modes are
    !! incuded in the group
    type(group_dist_t), allocatable :: dist( : )
  contains
    !> Select all groups (and all bins/modes within each group)
    procedure :: select_all
    !> Deselect all groups
    procedure :: deselect_all
    !> Select all bins within a group by name
    procedure :: select_group
    !> Deselect all bins within a group by name
    procedure :: deselect_group
    !> Select a bin/mode within a group
    procedure :: select_bin
    procedure :: select_mode => select_bin
    !> Deselect a bin/mode within a group
    procedure :: deselect_bin
    procedure :: deselect_mode => deselect_bin
    !> Duplicate the group collection in its current state
    procedure :: duplicate
    !> Determine the number of bytes required to pack the given object
    procedure :: pack_size => group_pack_size
    !> Pack the given object to the buffer, advancing position
    procedure :: bin_pack => group_bin_pack
    !> Unpack the given object from the buffer, advancing position
    procedure :: bin_unpack => group_bin_unpack
  end type monarch_aero_groups_fixed_t

  !> Group constructor
  interface monarch_aero_groups_fixed_t
    procedure :: group_constructor
  end interface monarch_aero_groups_fixed_t

contains

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

  !> Constructor for the fixed-dimension aerosol representation
  function constructor( int_state ) result( new_obj )

    use module_bsc_constants_chem
    use monarch_util,                       only : die_msg

    !> Pointer to the newly allocated aerosol representation
    type(monarch_aero_rep_fixed_t), pointer :: new_obj
    !> Solver internal state
    !! (Not needed for object being unpacked from a buffer)
    type(solver_internal_state), target, optional, intent(in) :: int_state

    integer(kind=mint) :: i_group, i_div

    allocate( new_obj )

    ! Continue only for objects being created from the solver internal state
    if( .not.present( int_state ) ) return
    new_obj%int_state => int_state

    ! Set up the dimensions of the aerosol representation
    ! FIXME Make sure this is right

    ! Count the number of groups
    i_group = 0
    if( int_state%NUM_ASH    .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_DUST   .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_SALT   .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_OM     .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_BC     .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_SO4    .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_NO3    .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_NH4    .gt. 0 ) i_group = i_group + 1
    if( int_state%NUM_UNSPC  .gt. 0 ) i_group = i_group + 1

    new_obj%num_groups = i_group
    allocate( new_obj%dist( i_group ) )

    ! Assign the types, tracer ids and number of divisions
    i_group     = 0
    if( int_state%NUM_ASH .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_ASH
      new_obj%dist( i_group )%dist_type        = AERO_DIST_BIN
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_ASH_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_ASH ) )
      ! No extinction values provided for ash bins
      do i_div = 1, int_state%NUM_ASH
        allocate( new_obj%dist( i_group )%div( i_div )%extinct_param( 0 ) )
      end do
    end if
    if( int_state%NUM_DUST .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_DUST
      new_obj%dist( i_group )%dist_type        = AERO_DIST_BIN
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_DUST_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_DUST ) )
      ! No RH dependence for dust bin extinction
      do i_div = 1, int_state%NUM_DUST
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 1 ) )
        div%extinct_param( 1 )%min_rel_humid   = 0.0
!        div%extinct_param( 1 )%ext_eff_550_nm  = DUST_QEXT_550( i_div )
        if( int_state%DEXT .eq. 0 ) then
          div%extinct_param( 1 )%ext_eff_550_nm  = DUST_QEXT_550( i_div )
        else if( int_state%DEXT .eq. 1 ) then
          div%extinct_param( 1 )%ext_eff_550_nm  = DUST_QEXT_550_tri( i_div )
        else
          call die_msg( 123958673, "Invalid DEXT option specified" )
        end if
        div%extinct_param( 1 )%d2wf            = 1.0
        div%effective_radius__m                = R_DUST_E( i_div )
        div%density__kgm3                      = RHO_DUST( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_SALT .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_SALT
      new_obj%dist( i_group )%dist_type        = AERO_DIST_BIN
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_SALT_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_SALT ) )
      do i_div = 1, int_state%NUM_SALT
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 7 ) )
        div%extinct_param( 1 )%min_rel_humid  = 0.0
        div%extinct_param( 1 )%ext_eff_550_nm = SALT_QEXT_550( i_div      )
        div%extinct_param( 1 )%d2wf           = 1.0
        div%extinct_param( 2 )%min_rel_humid  = 0.5
        div%extinct_param( 2 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 8  )
        div%extinct_param( 2 )%d2wf           = 1.6
        div%extinct_param( 3 )%min_rel_humid  = 0.7
        div%extinct_param( 3 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 16 )
        div%extinct_param( 3 )%d2wf           = 1.8
        div%extinct_param( 4 )%min_rel_humid  = 0.8
        div%extinct_param( 4 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 24 )
        div%extinct_param( 4 )%d2wf           = 2.0
        div%extinct_param( 5 )%min_rel_humid  = 0.9
        div%extinct_param( 5 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 32 )
        div%extinct_param( 5 )%d2wf           = 2.4
        div%extinct_param( 6 )%min_rel_humid  = 0.95
        div%extinct_param( 6 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 40 )
        div%extinct_param( 6 )%d2wf           = 2.9
        div%extinct_param( 7 )%min_rel_humid  = 0.99
        div%extinct_param( 7 )%ext_eff_550_nm = SALT_QEXT_550( i_div + 48 )
        div%extinct_param( 7 )%d2wf           = 4.8
        div%effective_radius__m               = R_SALT_E( i_div )
        div%density__kgm3                     = RHO_SALT( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_OM .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_OM
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_OM_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_OM ) )
      do i_div = 1, int_state%NUM_OM
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        ! (1/1.6 factor is to account for OC:OM)
        ! hydrophobic modes
        if( i_div .eq. 1 .or. i_div .eq. 3 ) then
          allocate( div%extinct_param( 1 ) )
          div%extinct_param( 1 )%min_rel_humid  = 0.0
          div%extinct_param( 1 )%ext_eff_550_nm = OC_QEXT_550( 1 ) / 1.6
          div%extinct_param( 1 )%d2wf           = 1.0
        ! hydrophilic modes
        else
          allocate( div%extinct_param( 7 ) )
          div%extinct_param( : )%ext_eff_550_nm = OC_QEXT_550( : ) / 1.6
          div%extinct_param( 1 )%min_rel_humid  = 0.0
          div%extinct_param( 1 )%d2wf           = 1.0
          div%extinct_param( 2 )%min_rel_humid  = 0.5
          div%extinct_param( 2 )%d2wf           = 1.2
          div%extinct_param( 3 )%min_rel_humid  = 0.7
          div%extinct_param( 3 )%d2wf           = 1.4
          div%extinct_param( 4 )%min_rel_humid  = 0.8
          div%extinct_param( 4 )%d2wf           = 1.5
          div%extinct_param( 5 )%min_rel_humid  = 0.9
          div%extinct_param( 5 )%d2wf           = 1.6
          div%extinct_param( 6 )%min_rel_humid  = 0.95
          div%extinct_param( 6 )%d2wf           = 1.8
          div%extinct_param( 7 )%min_rel_humid  = 0.99
          div%extinct_param( 7 )%d2wf           = 2.2
        end if
        div%effective_radius__m                 = R_OM_E( i_div )
        div%density__kgm3                       = RHO_OM( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_BC .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_BC
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_BC_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_BC ) )
      do i_div = 1, int_state%NUM_BC
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        ! hydrophobic mode
        if( i_div .ne. 2 ) then
          allocate( div%extinct_param( 1 ) )
          div%extinct_param( 1 )%min_rel_humid  = 0.0
          div%extinct_param( 1 )%ext_eff_550_nm = BC_QEXT_550( 1 )
          div%extinct_param( 1 )%d2wf = 1.0
        ! hydrophilic mode
        else
          allocate( div%extinct_param( 7 ) )
          div%extinct_param( : )%ext_eff_550_nm = BC_QEXT_550( : )
          div%extinct_param( 1 )%min_rel_humid  = 0.0
          div%extinct_param( 1 )%d2wf           = 1.0
          div%extinct_param( 2 )%min_rel_humid  = 0.5
          div%extinct_param( 2 )%d2wf           = 1.0
          div%extinct_param( 3 )%min_rel_humid  = 0.7
          div%extinct_param( 3 )%d2wf           = 1.0
          div%extinct_param( 4 )%min_rel_humid  = 0.8
          div%extinct_param( 4 )%d2wf           = 1.2
          div%extinct_param( 5 )%min_rel_humid  = 0.9
          div%extinct_param( 5 )%d2wf           = 1.4
          div%extinct_param( 6 )%min_rel_humid  = 0.95
          div%extinct_param( 6 )%d2wf           = 1.5
          div%extinct_param( 7 )%min_rel_humid  = 0.99
          div%extinct_param( 7 )%d2wf           = 1.9
        end if
        div%effective_radius__m                 = R_BC_E( i_div )
        div%density__kgm3                       = RHO_BC( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_SO4 .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_SO4
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_SO4_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_SO4 ) )
      ! Special treatment for sulfate
      do i_div = 1, int_state%NUM_SO4
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 15 ) )
        div%extinct_param( : )%min_rel_humid                = surh( : )
        div%extinct_param( : )%ext_eff_550_nm               = suqt( : )
        div%extinct_param( : )%wet_to_dry_mean_volume_ratio = suwr( : )
        div%extinct_param( : )%wet_effective_radius__m      = suwf( : )
        div%effective_radius__m                             = R_SO4_E( i_div )
        div%density__kgm3                                   = RHO_SO4( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_NO3 .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_NO3
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_NO3_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_NO3 ) )
      ! No RH dependence for nitrate extinction
      do i_div = 1, int_state%NUM_NO3
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 1 ) )
        div%extinct_param( 1 )%min_rel_humid   = 0.0
        div%extinct_param( 1 )%ext_eff_550_nm  = NI_QEXT_550( i_div )
        div%extinct_param( 1 )%d2wf            = 1.0
        div%effective_radius__m                = R_NO3_E( i_div )
        div%density__kgm3                      = RHO_NO3( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_NH4 .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_NH4
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_NH4_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_NH4 ) )
      ! No RH dependence for ammonium extinction
      do i_div = 1, int_state%NUM_NH4
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 1 ) )
        div%extinct_param( 1 )%min_rel_humid   = 0.0
        div%extinct_param( 1 )%ext_eff_550_nm  = NH4_QEXT_550( i_div )
        div%extinct_param( 1 )%d2wf            = 1.0
        div%effective_radius__m                = R_NH4_E( i_div )
        div%density__kgm3                      = RHO_NH4( i_div )
        end associate
      end do
    end if
    if( int_state%NUM_UNSPC .gt. 0 ) then
      i_group = i_group + 1
      new_obj%dist( i_group )%composition_type = AERO_GROUP_UNSPC
      new_obj%dist( i_group )%dist_type        = AERO_DIST_MODE
      new_obj%dist( i_group )%tracer_id        = int_state%INDX_UNSPC_START
      allocate( new_obj%dist( i_group )%div( int_state%NUM_UNSPC ) )
      ! FIXME See if this is the right way to handle coarse and fine bins
      do i_div = 1, int_state%NUM_UNSPC, 2
        ! fine aerosol
        associate( div => new_obj%dist( i_group )%div( i_div ) )
        allocate( div%extinct_param( 1 ) )
        div%extinct_param( 1 )%min_rel_humid   = 0.0
        div%extinct_param( 1 )%ext_eff_550_nm  = UN_QEXT_550( i_div )
        div%extinct_param( 1 )%d2wf            = 1.0
        div%effective_radius__m                = R_UNSPC_E( i_div )
        div%density__kgm3                      = RHO_UNSPC( i_div )
        end associate
        ! coarse aerosol
        associate( div => new_obj%dist( i_group )%div( i_div + 1 ) )
        allocate( div%extinct_param( 1 ) )
        div%extinct_param( 1 )%min_rel_humid   = 0.0
        div%extinct_param( 1 )%ext_eff_550_nm  = UN_QEXT_550( i_div + 1 )
        div%extinct_param( 1 )%d2wf            = 1.0
        div%effective_radius__m                = R_UNSPC_E( i_div + 1 )
        div%density__kgm3                      = RHO_UNSPC( i_div + 1 )
        end associate
      end do
    end if

    ! FIXME Set bin/mode number-to-mass ratios

    ! Get a group collection with all the aerosol groups for internal use
    new_obj%all_groups => new_obj%groups_fixed( )
    call new_obj%all_groups%select_all( )

  end function constructor

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

  !> Update the tracers ids based on the values in the solver internal state
  subroutine update_tracer_ids( this )

    use monarch_util,                       only : die_msg, assert

    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(inout) :: this

    integer(kind=mint) :: i_dist

    call assert( 286401845, associated( this%int_state ) )

    do i_dist = 1, size( this%dist )
      select case( this%dist( i_dist )%composition_type )
      case( AERO_GROUP_ASH     )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_ASH_START
      case( AERO_GROUP_DUST    )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_DUST_START
      case( AERO_GROUP_SALT    )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_SALT_START
      case( AERO_GROUP_OM      )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_OM_START
      case( AERO_GROUP_BC      )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_BC_START
      case( AERO_GROUP_SO4     )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_SO4_START
      case( AERO_GROUP_NO3     )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_NO3_START
      case( AERO_GROUP_NH4     )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_NH4_START
      case( AERO_GROUP_UNSPC   )
        this%dist( i_dist )%tracer_id = this%int_state%INDX_UNSPC_START
      case default
        call die_msg( 530738878, "Unknown aerosol composition" )
      end select
    end do

  end subroutine update_tracer_ids

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

  !> Get a collection of aerosol groups for the fixed representation as an
  !! abstract monarch_aerosol_group_collection_t pointer
  function groups( this )

    !> Fixed-dimension aerosol group collection
    class(monarch_aerosol_group_collection_t), pointer :: groups
    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), target, intent(in) :: this

    groups => this%groups_fixed( )

  end function groups

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

  !> Get a collection of aerosol groups for the fixed representation
  !!
  !! By default, the groups are unselected.
  function groups_fixed( this ) result( groups )

    !> Fixed-dimension aerosol group collection
    type(monarch_aero_groups_fixed_t), pointer :: groups
    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), target, intent(in) :: this

    integer(kind=mint) :: i_dist

    allocate( groups )
    allocate( groups%dist( this%num_groups ) )
    do i_dist = 1, this%num_groups
      allocate( groups%dist( i_dist )%is_selected(                           &
                                          size( this%dist( i_dist )%div ) ) )
      groups%dist( i_dist )%is_selected( : ) = .false.
    end do
    groups%aero_rep => this

  end function groups_fixed

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

  !> Return a list of group names that are present in the representation
  function group_names( this )

    use monarch_util,                            only : string_t

    !> Set of group names that are present
    type(string_t), allocatable :: group_names( : )
    !> Aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this

    integer(kind=mint) :: i_dist

    allocate( group_names( this%num_groups ) )

    do i_dist = 1, this%num_groups
      group_names( i_dist )%string = this%group_name( i_dist )
    end do

  end function group_names

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

  !> Check if an aerosol group exists in the representation
  function is_valid_group( this, group_name )

    !> Flag indicating whether a group is present in the representation
    logical :: is_valid_group
    !> Aerosol group collection
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name

    is_valid_group = this%group_id( group_name ) .ne. -1

  end function is_valid_group

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

  !> Get the number of bins or modes available in a group by name
  function num_bins( this, group_name )

    !> Number of bins or modes
    integer(kind=mint) :: num_bins
    !> Aerosol group collection
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name

    integer(kind=mint) :: i_dist

    i_dist = this%group_id( group_name )
    if( i_dist .eq. -1 ) then
      num_bins = 0
    else
      num_bins = size( this%dist( i_dist )%div )
    end if

  end function num_bins

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

  !> Get the aerosol number concentration [# m-3]
  function num_particles__m3( this, cell, group )

    use monarch_util,                       only : monarch_point_t, die_msg

    !> Number of particles [# m-3]
    real(kind=mdbl) :: num_particles__m3
    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Indices for the cell to get the number concentration for
    type(monarch_point_t), intent(in) :: cell
    !> Fixed-dimension aerosol group collection
    class(monarch_aerosol_group_collection_t), target, optional,             &
        intent(in) :: group

    integer(kind=mint) :: i_dist, i_div, min_div, max_div, min_grp, max_grp
    type(monarch_aero_groups_fixed_t), pointer :: grp

    ! Get a pointer to the group (if one was passed). Otherwise, point to the
    ! local iterator to cycle across all groups
    if( present( group ) ) then
      select type( group )
        type is( monarch_aero_groups_fixed_t )
          grp => group
        class default
          call die_msg( 523542442, "Aerosol group type mismatch" )
      end select
    else
      grp => this%all_groups
    end if

    ! Cycle through aerosol groups (dust, sea salt, black carbon, etc.)
    num_particles__m3 = 0.0
    do i_dist = 1, size( grp%dist )
      associate( dist => this%dist( i_dist ) )
      do i_div = 1, size( grp%dist( i_dist )%is_selected )
        if( .not. grp%dist( i_dist )%is_selected( i_div ) ) cycle
        num_particles__m3 = num_particles__m3                             +  &
            this%int_state%TRACERS(                                          &
                cell%WE_idx( ),                                              &
                cell%SN_idx( ),                                              &
                cell%flipped_vertical_idx( ),                                &
                dist%tracer_id + i_div - 1 )                               * &
            dist%div( i_div )%number_to_dry_mass_ratio
      end do
      end associate
    end do

  end function num_particles__m3

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

  !> Get the dry aerosol mass concentration [kg m-3]
  function dry_mass_conc__kgm3( this, cell, group )

    use monarch_util,                       only : monarch_point_t, die_msg

    !> Dry mass concentration [kg m-3]
    real(kind=mdbl) :: dry_mass_conc__kgm3
    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Indices for the cell to get the mass concentration for
    type(monarch_point_t), intent(in) :: cell
    !> Fixed-dimension aerosol group collection
    class(monarch_aerosol_group_collection_t), target, optional,             &
        intent(in) :: group

    integer(kind=mint) :: i_dist, i_div, min_div, max_div, min_grp, max_grp
    type(monarch_aero_groups_fixed_t), pointer :: grp

    ! Get a pointer to the group (if one was passed). Otherwise, point to the
    ! local iterator to cycle across all groups
    if( present( group ) ) then
      select type( group )
        type is( monarch_aero_groups_fixed_t )
          grp => group
        class default
          call die_msg( 909712956, "Aerosol group type mismatch" )
      end select
    else
      grp => this%all_groups
    end if

    ! Cycle through aerosol groups (dust, sea salt, black carbon, etc.)
    dry_mass_conc__kgm3 = 0.0
    do i_dist = 1, size( grp%dist )
      associate( dist => this%dist( i_dist ) )
      do i_div = 1, size( grp%dist( i_dist )%is_selected )
        if( .not. grp%dist( i_dist )%is_selected( i_div ) ) cycle
        dry_mass_conc__kgm3 = dry_mass_conc__kgm3 +                          &
            this%int_state%TRACERS(                                          &
                cell%WE_idx( ),                                              &
                cell%SN_idx( ),                                              &
                cell%flipped_vertical_idx( ),                                &
                dist%tracer_id + i_div - 1 )
      end do
      end associate
    end do

  end function dry_mass_conc__kgm3

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

  !> Calculate the aerosol extinction [m-1]
  !! If an aerosol group in passed, return the extinction for that group
  !! (i.e., dust, sea salt, black carbon, bin 3 of sea salt, etc.), otherwise
  !! return the total aerosol extinction in the cell
  !!
  !! Based on Chin et al., J. Atmos. Sci., 59, 461--483, 2002
  !! FIXME Chin seems to refer to this value as the aerosol optical thickness
  function extinction__m( this, cell, rel_humid, group )

    use monarch_util,                       only : monarch_point_t, die_msg

    !> Get the aerosol extinction [m-1]
    real(kind=mdbl) :: extinction__m
    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Indices for the grid cell
    type(monarch_point_t), intent(in) :: cell
    !> Relative humidity as a decimal value [0.0...1.0]
    real(kind=mdbl), intent(in) :: rel_humid
    !> Fixed-dimension aerosol group collection
    class(monarch_aerosol_group_collection_t), target, optional,             &
        intent(in) :: group

    integer(kind=mint) :: i_dist, i_div, i_rh, min_div, max_div, min_grp,   &
                          max_grp
    type(monarch_aero_groups_fixed_t), pointer :: grp

    ! mass extinction efficiency [m3 kg-1] (beta in Chin et al., eq. 3)
    real(kind=mflt) :: mass_ext_eff
    ! set of extinct parameters
    type(extinct_rel_humid_map_t) :: params

    ! Get a pointer to the group (if one was passed). Otherwise, point to the
    ! local iterator to cycle across all groups
    if( present( group ) ) then
      select type( group )
        type is( monarch_aero_groups_fixed_t )
          grp => group
        class default
          call die_msg( 399242244, "Aerosol group type mismatch" )
      end select
    else
      grp => this%all_groups
    end if

    ! Cycle through aerosol groups (dust, sea salt, black carbon, etc.)
    extinction__m = 0.0
    do i_dist = 1, size( grp%dist )

      ! Cycle through each bin/mode of the distribution
      associate( dist => this%dist( i_dist ) )
      do i_div = 1, size( grp%dist( i_dist )%is_selected )
        if( .not. grp%dist( i_dist )%is_selected( i_div ) ) cycle

        ! Special treatment for sulfate
        if( dist%composition_type .eq. AERO_GROUP_SO4 ) then

          ! Interpolate values between discreet RH values
          params = interpolate_extinct_params(                               &
                                         dist%div( i_div )%extinct_param,    &
                                         rel_humid )

          mass_ext_eff = 0.75 * params%ext_eff_550_nm                      * &
                         params%wet_to_dry_mean_volume_ratio               / &
                         ( params%wet_effective_radius__m                  * &
                           dist%div( i_div )%density__kgm3 )

        ! Non-sulfate cases
        else
          i_rh = get_extinct_params_by_rel_humid(                            &
                                           dist%div( i_div )%extinct_param,  &
                                           rel_humid )
          if( i_rh .gt. 0 ) then
            mass_ext_eff = 0.75                                            * &
                dist%div( i_div )%extinct_param( i_rh )%ext_eff_550_nm     * &
                dist%div( i_div )%extinct_param( i_rh )%d2wf**2            / &
                ( dist%div( i_div )%effective_radius__m                    * &
                  dist%div( i_div )%density__kgm3 )
          else
            mass_ext_eff = 0.0
          end if
        end if

        ! Calculate the extinction
        extinction__m = extinction__m                                      + &
            this%int_state%TRACERS(                                          &
                cell%WE_idx( ),                                              &
                cell%SN_idx( ),                                              &
                cell%flipped_vertical_idx( ),                                &
                dist%tracer_id + i_div - 1 ) * mass_ext_eff

      end do
      end associate
    end do

  end function extinction__m

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

  !> Determine the number of bytes required to pack the given object
  integer(kind=mint) function pack_size( this, comm )

    use monarch_mpi,                        only : mnrc_mpi_pack_size_integer
    use monarch_util,                       only : assert

    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: i_group

    ! Right now aerosol representations are only passed to write tasks, so
    ! parameters needed only by forcast tasks are not passed.
    ! TODO pass everything
    call assert( 397021781, associated( this%all_groups ) )
    pack_size =                                                              &
        mnrc_mpi_pack_size_integer( this%num_groups, comm )                + &
        this%all_groups%pack_size( comm )

  end function pack_size

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

  !> Pack the given object to the buffer, advancing position
  subroutine bin_pack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                        only : mnrc_mpi_pack_integer
    use monarch_util,                       only : assert
#endif

    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position

    prev_position = pos
    call mnrc_mpi_pack_integer( buffer, pos, this%num_groups, comm )
    call this%all_groups%bin_pack( buffer, pos, comm )
    call assert( 257093834, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_pack

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

  !> Unpack the given object from the buffer, advancing position
  subroutine bin_unpack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                        only : mnrc_mpi_unpack_integer
    use monarch_util,                       only : assert
#endif

    !> Fixed-dimension aerosol representation
    class(monarch_aero_rep_fixed_t), intent(inout) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position

    call assert( 582067016, .not. associated( this%all_groups ) )
    allocate( this%all_groups )
    prev_position = pos
    call mnrc_mpi_unpack_integer( buffer, pos, this%num_groups, comm )
    call this%all_groups%bin_unpack( buffer, pos, comm )
    call assert( 350712780, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_unpack

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

  !> Print out the configuration of the aerosol representation
  subroutine do_print( this, file_unit )

    !> Aerosol representation
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> File unit
    integer(kind=mint), intent(in) :: file_unit

    type(monarch_aero_groups_fixed_t), pointer :: groups
    integer(kind=mint) :: i_dist
    character(len=*), parameter :: fmt_blank_line = "('')"

    groups => this%groups_fixed( )

    write(file_unit,fmt_blank_line)
    write(file_unit,*) "**************************************"
    write(file_unit,*) "*** MONARCH Aerosol Representation ***"
    write(file_unit,*) "**************************************"
    write(file_unit,fmt_blank_line)
    write(file_unit,*) "Number of aerosol groups", this%num_groups
    do i_dist = 1, size( groups%dist )
      write(file_unit,fmt_blank_line)
      write(file_unit,*) "  *** Group ", this%group_name( i_dist )," ***"
      call this%dist( i_dist )%print( file_unit )
    end do
    write(file_unit,fmt_blank_line)
    write(file_unit,*) "******************************************"
    write(file_unit,*) "*** End MONARCH Aerosol Representation ***"
    write(file_unit,*) "******************************************"
    write(file_unit,fmt_blank_line)

    deallocate( groups )

  end subroutine do_print

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

  !> Get a group name by id
  function group_name( this, group_id )

    !> Group name
    character(len=:), allocatable :: group_name
    !> Aerosol group collection
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Index of the group in the aerosol representation
    integer(kind=mint), intent(in) :: group_id

    select case( this%dist( group_id )%composition_type )
      case( AERO_GROUP_ASH )
        group_name = "ASH"
      case( AERO_GROUP_DUST )
        group_name = "DUST"
      case( AERO_GROUP_SALT )
        group_name = "SALT"
      case( AERO_GROUP_OM )
        group_name = "OM"
      case( AERO_GROUP_BC )
        group_name = "BC"
      case( AERO_GROUP_SO4 )
        group_name = "SO4"
      case( AERO_GROUP_NO3 )
        group_name = "NO3"
      case( AERO_GROUP_NH4 )
        group_name = "NH4"
      case( AERO_GROUP_UNSPC )
        group_name = "UNSPC"
      case default
        group_name = "UNKNOWN"
    end select

  end function group_name

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

  !> Get a group id by name
  !!
  !! If the name is not found, an id of -1 is returned
  function group_id( this, group_name )

    !> Group id
    integer(kind=mint) :: group_id
    !> Aerosol group collection
    class(monarch_aero_rep_fixed_t), intent(in) :: this
    !> Group name
    character(len=*), intent(in) :: group_name

    integer(kind=mint) :: i_dist

    group_id = -1

    do i_dist = 1, size( this%dist )
      if( this%group_name( i_dist ) .eq. trim( group_name ) ) then
        group_id = i_dist
        return
      end if
    end do

  end function group_id

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

  !> Print out the configuration of the aerosol representation
  subroutine dist_print( this, file_unit )

    !> Aerosol representation
    class(fixed_dist_t), intent(in) :: this
    !> File unit
    integer(kind=mint), intent(in) :: file_unit

    integer(kind=mint) :: i_div
    character(len=10) :: label

    if( this%dist_type .eq. AERO_DIST_BIN ) then
      write(file_unit,*) "  Distribution type: BINS"
      label = "Bin       "
    else if( this%dist_type .eq. AERO_DIST_MODE ) then
      write(file_unit,*) "  Distribution type: MODES"
      label = "Mode      "
    else
      write(file_unit,*) "  Distribution type: UNKNOWN"
      label = "Division  "
    end if
    write(file_unit,*) "  Beginning index in TRACERS:", this%tracer_id

    do i_div = 1, size( this%div )
      write(file_unit,*) "  ** ",trim( label )," ",i_div,"**"
      call this%div( i_div )%print( file_unit )
    end do

  end subroutine dist_print

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

  !> Print out the configuration of the aerosol representation
  subroutine div_print( this, file_unit )

    !> Aerosol representation
    class(fixed_division_t), intent(in) :: this
    !> File unit
    integer(kind=mint), intent(in) :: file_unit

    character(len=*), parameter :: fmt1 = "(5x,'| ',A13,' | ',A13,' | ',"//  &
                               "A13,' | ',A13,' | ',A13,' |')"
    character(len=*), parameter :: fmt2 = "(5x,'| ',ES13.3,' | ',ES13.3,"//  &
                               "' | ',ES13.3,' | ',ES13.3,' | ',ES13.3,' |')"
    character(len=*), parameter :: fmt3 = "(5x,A21,' ',ES13.3,' ',A8)"
    integer(kind=mint) :: i_rh

    write(file_unit,fmt3) "Number:Dry-Mass ratio",                           &
                          this%number_to_dry_mass_ratio, "[# kg-3]"
    write(file_unit,fmt3) "Effective radius     ", this%effective_radius__m, &
                          "[m]     "
    write(file_unit,fmt3) "Density              ", this%density__kgm3,       &
                          "[kg m-3]"
    write(file_unit,*) "    Extinction parameters by relative humidity"
    write(file_unit,fmt1) "Min Rel Humid", "Ext Eff 550nm", "D2WF         ", &
                          "Wet:Dry Volum", "Wet Eff Rad  "
    write(file_unit,fmt1) "   [0...1]   ", " [unitless]  ", " [unitless]  ", &
                          " [unitless]  ", "     [m]     "
    do i_rh = 1, size( this%extinct_param )
      write(file_unit,fmt2) this%extinct_param( i_rh )%min_rel_humid,        &
                    this%extinct_param( i_rh )%ext_eff_550_nm,               &
                    this%extinct_param( i_rh )%d2wf,                         &
                    this%extinct_param( i_rh )%wet_to_dry_mean_volume_ratio, &
                    this%extinct_param( i_rh )%wet_effective_radius__m
    end do

  end subroutine div_print

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

  !> Constructor for the monarch_aero_groups_fixed_t type
  function group_constructor( aero_rep_fixed ) result( groups )

    use monarch_util,                       only : die_msg

    !> Pointer to the new group collection
    class(monarch_aerosol_group_collection_t), pointer :: groups
    !> Fixed-dimension aerosol representation
    class(monarch_aerosol_representation_t), intent(in) :: aero_rep_fixed

    select type( aero_rep_fixed )
      type is( monarch_aero_rep_fixed_t )
        groups => aero_rep_fixed%groups( )
      class default
        call die_msg( 267349281, "Mismatched aerosol representation" )
    end select

  end function group_constructor

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

  !> Select all aerosol groups (with all their bins/modes)
  subroutine select_all( this )

    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this

    integer(kind=mint) :: i_dist

    do i_dist = 1, size( this%dist )
      this%dist( i_dist )%is_selected( : ) = .true.
    end do

  end subroutine select_all

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

  !> Deselect all aerosol groups (with all their bins/modes)
  subroutine deselect_all( this )

    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this

    integer(kind=mint) :: i_dist

    do i_dist = 1, size( this%dist )
      this%dist( i_dist )%is_selected( : ) = .false.
    end do

  end subroutine deselect_all

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

  !> Select all bins/modes for an aerosol group by name
  function select_group( this, group_name ) result( found )

    !> Flag indicating whether the group was selected
    logical :: found
    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name

    integer(kind=mint) :: i_dist

    found = .false.
    i_dist = this%aero_rep%group_id( group_name )
    if( i_dist .ne. -1 ) then
      this%dist( i_dist )%is_selected( : ) = .true.
      found = .true.
    end if

  end function select_group

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

  !> Deselect all bins/modes for an aerosol group by name
  function deselect_group( this, group_name ) result( found )

    !> Flag indicating whether the group was deselected
    logical :: found
    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name

    integer(kind=mint) :: i_dist

    found = .false.
    i_dist = this%aero_rep%group_id( group_name )
    if( i_dist .ne. -1 ) then
      this%dist( i_dist )%is_selected( : ) = .false.
      found = .true.
    end if

  end function deselect_group

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

  !> Select one bin/mode for an aerosol group by name
  function select_bin( this, group_name, i_bin ) result( found )

    !> Flag indicating whether the bin was selected
    logical :: found
    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name
    !> Bin index
    integer(kind=mint) :: i_bin

    integer(kind=mint) :: i_dist

    found = .false.
    i_dist = this%aero_rep%group_id( group_name )
    if( i_dist .ne. -1 ) then
      if( i_bin .gt. 0 .and.                                                 &
          i_bin .le. size( this%dist( i_dist )%is_selected ) ) then
        this%dist( i_dist )%is_selected( i_bin ) = .true.
        found = .true.
      end if
    end if

  end function select_bin

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

  !> Deselect one bin/mode for an aerosol group by name
  function deselect_bin( this, group_name, i_bin ) result( found )

    !> Flag indicating whether the bin was deselected
    logical :: found
    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this
    !> Name of the group
    character(len=*), intent(in) :: group_name
    !> Bin index
    integer(kind=mint) :: i_bin

    integer(kind=mint) :: i_dist

    found = .false.
    i_dist = this%aero_rep%group_id( group_name )
    if( i_dist .ne. -1 ) then
      if( i_bin .gt. 0 .and.                                                 &
          i_bin .le. size( this%dist( i_dist )%is_selected ) ) then
        this%dist( i_dist )%is_selected( i_bin ) = .false.
        found = .true.
      end if
    end if

  end function deselect_bin

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

  !> Duplicate the aerosol collection in its current state
  function duplicate( this ) result( new_obj )

    !> New aerosol group collection
    class(monarch_aerosol_group_collection_t), pointer :: new_obj
    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(in) :: this

    allocate( new_obj, source = this )

  end function duplicate

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

  !> Determine the number of bytes required to pack the given object
  integer(kind=mint) function group_pack_size( this, comm )

    use monarch_mpi,                  only : mnrc_mpi_pack_size_integer,     &
                                             mnrc_mpi_pack_size_logical_array
    use monarch_util,                 only : assert_msg

    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: i_dist

    call assert_msg( 268999059, allocated( this%dist ),                      &
             "Trying to get pack size of an uninitialized aerosol group" )

    group_pack_size = mnrc_mpi_pack_size_integer( size( this%dist ), comm )
    do i_dist = 1, size( this%dist )
      group_pack_size = group_pack_size                                    + &
        mnrc_mpi_pack_size_logical_array(                                    &
            this%dist( i_dist )%is_selected, comm )
    end do

  end function group_pack_size

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

  !> Pack the given object to the buffer, advancing position
  subroutine group_bin_pack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                     only : mnrc_mpi_pack_integer,       &
                                                mnrc_mpi_pack_logical_array
    use monarch_util,                    only : assert, assert_msg
#endif

    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(in) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_dist

    call assert_msg( 208254022, allocated( this%dist ),                      &
                     "Trying to pack an uninitialized aerosol group" )

    prev_position = pos
    call mnrc_mpi_pack_integer(       buffer, pos, size( this%dist ), comm )
    do i_dist = 1, size( this%dist )
      call mnrc_mpi_pack_logical_array( buffer, pos,                         &
                                       this%dist( i_dist )%is_selected, comm )
    end do
    call assert( 625888196, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine group_bin_pack

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

  !> Unpack the given object from the buffer, advancing position
  subroutine group_bin_unpack( this, buffer, pos, comm )
#ifdef MONARCH_USE_MPI
    use monarch_mpi,                     only : mnrc_mpi_unpack_integer,     &
                                                mnrc_mpi_unpack_logical_array
#endif
    use monarch_util,                    only : assert, die_msg

    !> Aerosol group collection
    class(monarch_aero_groups_fixed_t), intent(inout) :: this
    !> Memory buffer
    character, intent(inout) :: buffer( : )
    !> Current buffer position
    integer(kind=mint), intent(inout) :: pos
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_dist, n_dist

    prev_position = pos
    call mnrc_mpi_unpack_integer(       buffer, pos, n_dist, comm )
    allocate( this%dist( n_dist ) )
    do i_dist = 1, size( this%dist )
      call mnrc_mpi_unpack_logical_array( buffer, pos,                       &
                                       this%dist( i_dist )%is_selected, comm )
    end do
    call assert( 716148369, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine group_bin_unpack

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

  !> Get the index of a set of extinction parameters for a given relative
  !! humidity
  !! Returns zero on empty set or a relative humidity below the minimum value
  !! in the set.
  function get_extinct_params_by_rel_humid( param_set, rel_humid )           &
      result( param_id )

    !> Index in parameter set to use
    integer(kind=mint) :: param_id
    !> Parameter set
    type(extinct_rel_humid_map_t), allocatable, intent(in) :: param_set( : )
    !> Relative humidity as a decimal [0...1]
    real(kind=mdbl), intent(in) :: rel_humid

    integer(kind=mint) :: i_rh

    param_id = 0
    do i_rh = 1, size( param_set )
      if( rel_humid .ge. param_set( i_rh )%min_rel_humid ) then
        param_id = i_rh
      else
        return
      end if
    end do

  end function get_extinct_params_by_rel_humid

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

  !> Calculate a set of extinction parameters for a given relative humidity
  !! by interpolation
  function interpolate_extinct_params( param_set, rel_humid ) result( params )

    !> Interpolated xtinction parameters
    type(extinct_rel_humid_map_t) :: params
    !> Parameter set
    type(extinct_rel_humid_map_t), allocatable, intent(in) :: param_set( : )
    !> Relative humidity as a decimal [0...1]
    real(kind=mdbl), intent(in) :: rel_humid

    real(kind=mdbl) :: scale_factor
    integer(kind=mint) :: i_rh

    i_rh = get_extinct_params_by_rel_humid( param_set, rel_humid )

    ! No scaling in highest relative humidity bin
    if( i_rh .eq. size( param_set ) ) then
      params = param_set( i_rh )
      return
    end if

    scale_factor = ( rel_humid - param_set( i_rh )%min_rel_humid )         / &
                   ( param_set( i_rh + 1 )%min_rel_humid                   - &
                     param_set( i_rh     )%min_rel_humid )

    params%ext_eff_550_nm =                                                  &
                   param_set( i_rh     )%ext_eff_550_nm                    + &
                   scale_factor                                            * &
                   ( param_set( i_rh + 1 )%ext_eff_550_nm                  - &
                     param_set( i_rh     )%ext_eff_550_nm )

    params%d2wf =                                                            &
                   param_set( i_rh     )%d2wf                              + &
                   scale_factor                                            * &
                   ( param_set( i_rh + 1 )%d2wf                            - &
                     param_set( i_rh     )%d2wf )

    params%min_rel_humid =                                                   &
                   param_set( i_rh     )%min_rel_humid                     + &
                   scale_factor                                            * &
                   ( param_set( i_rh + 1 )%min_rel_humid                   - &
                     param_set( i_rh     )%min_rel_humid )

    params%wet_to_dry_mean_volume_ratio =                                    &
                   param_set( i_rh     )%wet_to_dry_mean_volume_ratio      + &
                   scale_factor                                            * &
                   ( param_set( i_rh + 1 )%wet_to_dry_mean_volume_ratio    - &
                     param_set( i_rh     )%wet_to_dry_mean_volume_ratio )

    params%wet_effective_radius__m =                                         &
                   param_set( i_rh     )%wet_effective_radius__m           + &
                   scale_factor                                            * &
                   ( param_set( i_rh + 1 )%wet_effective_radius__m         - &
                     param_set( i_rh     )%wet_effective_radius__m )

  end function interpolate_extinct_params

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

end module monarch_aero_rep_fixed
