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

!> Handles output of variables used to debug MONARCH
module monarch_output_diag_debug
! MONARCH always has MPI
#define MONARCH_USE_MPI

  use monarch_aerosol_representation_pointer, only :                         &
                                         monarch_aerosol_group_collection_ptr
  use monarch_constants,                      only : mint, mflt, mdbl
  use monarch_state_changer,                  only : monarch_state_array_ptr
  use monarch_output_diagnostics,             only :                         &
                                         monarch_output_diagnostics_t
  use monarch_util,                           only : string_t

  implicit none
  private

  public :: monarch_output_diag_debug_t

  !> Output debugging properties
  type, extends(monarch_output_diagnostics_t) ::                             &
      monarch_output_diag_debug_t
  private
    !> Aerosol groups to output optical properties for
    class(monarch_aerosol_group_collection_ptr), allocatable ::              &
        aero_groups( : )
    !> Set of pointers to state arrays holding the diagnostics
    type(monarch_state_array_ptr), allocatable :: array_ptr( : )
    !> Set of gas-phase species names to output
    type(string_t), allocatable :: gas_spec_names( : )
    !> Tracer ids for gas-phase species to output
    integer(kind=mint), allocatable :: gas_spec_tracer_ids( : )
  contains
    !> Register space in the model state for the diagnostics
    procedure :: register
    !> Update output diagnostic once all MONARCH ids have been initialized
    procedure :: update
    !> Perform column-level calculations
    procedure :: calculate_column
    !> Perform grid cell-level calculations
    procedure :: calculate_grid_cell
    !> Determine the number of bytes required to pack the given object
    procedure :: pack_size
    !> Pack the given object onto the buffer, advancing position
    procedure :: bin_pack
    !> Unpack the given object from the buffer, advancing position
    procedure :: bin_unpack
    !> Output the diagnostics
    procedure :: output
    !> Print out the configuration of the output diagnostic
    procedure :: print => do_print
  end type monarch_output_diag_debug_t

  !> Constructor for the debugging diagnostics
  interface monarch_output_diag_debug_t
    procedure :: constructor
  end interface monarch_output_diag_debug_t

contains
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  !> Constructor for the debugging diagnostics
  function constructor( diag, aero_rep ) result( new_obj )

    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_representation_t
    use monarch_aero_rep_fixed,             only : monarch_aero_rep_fixed_t, &
                                                   monarch_aero_groups_fixed_t
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_util,                       only : assert_msg, die_msg, assert

    !> New output diagnostics object
    type(monarch_output_diag_debug_t), pointer :: new_obj
    !> Diagnostics object
    !! (Do not include when unpacking object from an mpi buffer)
    type(monarch_diagnostics_t), optional, intent(in) :: diag
    !> Aerosol representation
    !! (Do not include when unpacking object from an mpi buffer)
    class(monarch_aerosol_representation_t), optional, intent(in) :: aero_rep

    type(monarch_aero_groups_fixed_t), pointer :: aero_group
    integer(kind=mint) :: i_group, n_groups, i_bin

    allocate( new_obj )

    ! Return an empty object for unpacking from an mpi buffer
    if( .not. present( diag ) ) return

    ! Find the dust bins
    select type( aero_rep )
      type is( monarch_aero_rep_fixed_t )

        ! Get a group object to work with
        aero_group => aero_rep%groups_fixed( )

        ! We're only interested in dust (of course)
        n_groups = aero_rep%num_bins( "DUST" )

        call assert_msg( 151172633, n_groups .gt. 0,                         &
                         "Trying to output debug diagnostics for DUST "//    &
                         "when there is no DUST" )

        allocate( new_obj%aero_groups( n_groups ) )

        ! Save the group indices for DUST
        do i_bin = 1, n_groups
          call aero_group%deselect_all( )
          call assert( 404840422, aero_group%select_bin( "DUST", i_bin ) )
          new_obj%aero_groups( i_bin )%val => aero_group%duplicate( )
        end do

        deallocate( aero_group )

      class default
        call die_msg( 583688993,  "Unsupported aerosol group type in "//     &
                   "debugging diagnostics setup" )
    end select

    ! Send a dummy set of tracers for now, and update once the tracer ids
    ! have been initialized
    allocate( new_obj%gas_spec_names(      1 ) )
    allocate( new_obj%gas_spec_tracer_ids( 1 ) )
    new_obj%gas_spec_names( 1 )%string = "DUMMY"
    new_obj%gas_spec_tracer_ids( 1 )   = 1

  end function constructor

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

  !> Register space in the model state for the diagnostics
  subroutine register( this, diag, state_changer )

    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_state_changer,              only : monarch_state_changer_t
    use monarch_util,                       only : string_t, to_string

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(inout) :: this
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

    type(string_t), allocatable :: bin_labels( : )
    integer(kind=mint) :: i_bin, n_arrays
    integer(kind=mint) :: i_photo, i_dry_dep, i_res_cloud, i_sub_grid,       &
                          i_vert_mix
    type(monarch_state_array_ptr), pointer :: photo_rates, dry_dep_velocity, &
                                              res_cloud_loss, sub_grid_loss, &
                                              vert_mix

    ! Set up the dust bin labels
    allocate( bin_labels( size( this%aero_groups ) ) )
    do i_bin = 1, size( bin_labels )
      bin_labels( i_bin )%string = "DUST_"//trim( to_string( i_bin ) )
    end do

    n_arrays = 4

    ! Find the photolysis rate array if one exists
    photo_rates => state_changer%find_state_array( "photolysis_rates" )
    if( associated( photo_rates ) ) then
      n_arrays = n_arrays + 1
      i_photo = n_arrays
    else
      i_photo = 0
    end if

    ! Find the dry deposition rate array if one exists
    dry_dep_velocity => state_changer%find_state_array(                      &
                                                 "dry_deposition_velocity" )
    if( associated( dry_dep_velocity ) ) then
      n_arrays = n_arrays + 1
      i_dry_dep = n_arrays
    else
      i_dry_dep = 0
    end if

    ! Find the resolved cloud loss rate array if one exists
    res_cloud_loss => state_changer%find_state_array(                        &
                                                 "resolved_cloud_loss_rate" )
    if( associated( res_cloud_loss ) ) then
      n_arrays = n_arrays + 1
      i_res_cloud = n_arrays
    else
      i_res_cloud = 0
    end if

    ! Find the sub-grid scale cloud loss rate array if one exists
    sub_grid_loss => state_changer%find_state_array(                         &
                                                 "sub_grid_cloud_loss_rate" )
    if( associated( sub_grid_loss ) ) then
      n_arrays = n_arrays + 1
      i_sub_grid = n_arrays
    else
      i_sub_grid = 0
    end if

    ! Find the vertical mixing rate array if one exists
    vert_mix => state_changer%find_state_array(                              &
                                                 "vertical_mixing_rate" )
    if( associated( vert_mix ) ) then
      n_arrays = n_arrays + 1
      i_vert_mix = n_arrays
    else
      i_vert_mix = 0
    end if

    ! Set up arrays for debugging outputs
    allocate( this%array_ptr( n_arrays ) )

    ! Register an array to hold vertical dust conc. per bin
    this%array_ptr( 1 ) = state_changer%register_per_cell_real(              &
                                              "dust_conc",                   & !<-- unique name for array
                                              .true.,                        & !<-- output to history file
                                              size( this%aero_groups ),      & !<-- number of array elements (if > 1)
                                              bin_labels )                     !<-- label for each element
    call this%array_ptr( 1 )%set_netcdf_long_name( "Dust dry mass conc." )
    call this%array_ptr( 1 )%set_netcdf_units( "kg m-3" )
    call this%array_ptr( 1 )%set_netcdf_standard_name(                       &
         "mass_concentration_of_dust_dry_aerosol_in_air" )

    ! Register an array to hold the bottom-of-layer height [m]
    this%array_ptr( 2 ) = state_changer%register_per_cell_real(              &
                                              "bottom_of_layer_height",      & !<-- unique name for the array
                                              .true. )                         !<-- output to history file
    call this%array_ptr( 2 )%set_netcdf_long_name( "Bottom of layer height" )
    call this%array_ptr( 2 )%set_netcdf_units( "m" )

    ! Register an array to hold cloud optical thickness [unitless]
    this%array_ptr( 3 ) = state_changer%register_per_cell_real(              &
                                              "cloud_optical_thickness",     & !<-- unique name for the array
                                              .true. )                         !<-- output to history file
    call this%array_ptr( 3 )%set_netcdf_long_name( "Cloud optical thickness" )
    call this%array_ptr( 3 )%set_netcdf_units( "unitless" )

    ! Attach to the TRACERS array to output selected gas-phase species
    ! concentrations [ppm]
    if( size( this%gas_spec_names ) .gt. 0 ) then
      this%array_ptr( 4 ) = state_changer%register_tracers_like(             &
                                              "TRACERS",                     & !<-- unique name for the array
                                              .true.,                        & !<-- output to history file
                                              size( this%gas_spec_names ),   & !<-- number of array elements (if > 1)
                                              this%gas_spec_names,           & !<-- label for each element
                                              this%gas_spec_tracer_ids )       !<-- map to externally owned array elements
      call this%array_ptr( 4 )%set_netcdf_short_name(                        &
                                         "gas_species_conc" )
      call this%array_ptr( 4 )%set_netcdf_long_name(                         &
                                         "Gas-phase species concentrations" )
      call this%array_ptr( 4 )%set_netcdf_units( "ppm" )
    end if

    ! Include the emission rates, if it has been registered by FastJ
    if( i_photo .gt. 0 ) then
      this%array_ptr( i_photo ) = photo_rates
      deallocate( photo_rates )
    end if

    ! Include the dry deposition velocities, if they have been registered
    if( i_dry_dep .gt. 0 ) then
      this%array_ptr( i_dry_dep ) = dry_dep_velocity
      deallocate( dry_dep_velocity )
    end if

    ! Include the resolved cloud loss rates, if they have been registered
    if( i_res_cloud .gt. 0 ) then
      this%array_ptr( i_res_cloud ) = res_cloud_loss
      deallocate( res_cloud_loss )
    end if

    ! Include the sub-grid scale cloud loss rates, if they have been registered
    if( i_sub_grid .gt. 0 ) then
      this%array_ptr( i_sub_grid ) = sub_grid_loss
      deallocate( sub_grid_loss )
    end if

    ! Include the vertical mixing rates, if they have been registered
    if( i_vert_mix .gt. 0 ) then
      this%array_ptr( i_vert_mix ) = vert_mix
      deallocate( vert_mix )
    end if

  end subroutine register

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

  !> Update output diagnostics once all MONARCH ids have been initialized
  subroutine update( this, diag )

    use monarch_diagnostics,                only : monarch_diagnostics_t

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(inout) :: this
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag

    integer(kind=mint) :: n_tracers, i_spec, tracer_id, i_tracer
    type(string_t), dimension( 4 ) :: spec_names

    spec_names( 1 )%string = "O3"
    spec_names( 2 )%string = "NH3"
    spec_names( 3 )%string = "NO3"
    spec_names( 4 )%string = "SO2"

    ! See which tracers are present
    n_tracers = 0
    do i_spec = 1, size( spec_names )
      if( diag%get_gas_species_tracer_id(                                    &
              spec_names( i_spec )%string ) .gt. 0 ) n_tracers = n_tracers + 1
    end do

    ! Assemble the tracer names and ids
    if( allocated( this%gas_spec_names      ) )                              &
                                       deallocate( this%gas_spec_names )
    if( allocated( this%gas_spec_tracer_ids ) )                              &
                                       deallocate( this%gas_spec_tracer_ids )
    allocate( this%gas_spec_names(      n_tracers ) )
    allocate( this%gas_spec_tracer_ids( n_tracers ) )
    i_tracer = 1
    do i_spec = 1, size( spec_names )
      tracer_id = diag%get_gas_species_tracer_id( spec_names(                &
                                                      i_spec )%string )
      if( tracer_id .gt. 0 ) then
        this%gas_spec_names( i_tracer )%string = spec_names( i_spec )%string
        this%gas_spec_tracer_ids(   i_tracer ) = tracer_id
        i_tracer = i_tracer + 1
      end if
    end do

    ! Update the property map
    call this%array_ptr( 6 )%update_property_map( this%gas_spec_names,       &
                                                  this%gas_spec_tracer_ids )

  end subroutine update

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

  !> Perform column-level calculations
  subroutine calculate_column( this, column, state_changer )

    use monarch_column,                     only : monarch_column_t
    use monarch_state_changer,              only : monarch_state_changer_t

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(inout) :: this
    !> Column state
    type(monarch_column_t), intent(inout) :: column
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

    ! Nothing to be done here

  end subroutine calculate_column

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

  !> Perform grid cell-level calculations
  subroutine calculate_grid_cell( this, cell, diag, column, state_changer )

    use monarch_column,                     only : monarch_column_t
    use monarch_diagnostics,                only : monarch_diagnostics_t
    use monarch_state_changer,              only : monarch_state_changer_t
    use monarch_util,                       only : monarch_point_t

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(inout) :: this
    !> Cell indices
    type(monarch_point_t), intent(in) :: cell
    !> Diagnostics object
    type(monarch_diagnostics_t), intent(in) :: diag
    !> Column object
    type(monarch_column_t), intent(in) :: column
    !> Model state changer
    type(monarch_state_changer_t), intent(inout) :: state_changer

    integer(kind=mint) :: i_group

    ! Save the dry mass concentration for each dust bin [kg m-3]
    do i_group = 1, size( this%aero_groups )
      call state_changer%save_property( this%array_ptr( 1 ),                 & !<-- aerosol dry mass state array
                     cell,                                                   & !<-- grid cell indices
                     i_group,                                                & !<-- dust bin
                     real( diag%aerosol_dry_mass_conc__kgm3(                 & !<-- aerosol dry mass concentration [kg m-3]
                           cell, this%aero_groups( i_group )%val ),          &
                           kind=mflt ) )
    end do

    ! Save the bottom-of-layer height [m]
    call state_changer%save_property( this%array_ptr( 4 ),                   & !<-- layer height state array
                    cell,                                                    & !<-- grid cell indices
                    real( column%btw_layer_height__m( cell%vertical_idx( ) ),&
                           kind=mflt ) )                                       !<-- bottom-of-layer height [m]

    ! Save the cloud optical thickness [unitless]
    call state_changer%save_property( this%array_ptr( 5 ),                   & !<-- cloud optical thickness array
                    cell,                                                    & !<-- grid cell indices
                    real( column%cloud_optical_thickness(                    &
                           cell%vertical_idx( ) ), kind=mflt ) )               !<-- cloud optical thickess [unitless]

  end subroutine calculate_grid_cell

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

  !> Output the diagnostics
  subroutine output( this, write_int_state, array_name, first_output )

    use module_WRITE_INTERNAL_STATE,        only : write_internal_state

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(inout) :: this
    !> Write internal state
    type(write_internal_state), intent(in) :: write_int_state
    !> Name of array that is available for output
    character(len=*), intent(in) :: array_name
    !> Flag indicating whether the state arrays need to be defined in the
    !! NetCDF output file
    logical, intent(in) :: first_output

    integer(kind=mint) :: i_array

    ! See if the array is one that this output diagnostic registered
    do i_array = 1, size( this%array_ptr )
      call this%array_ptr( i_array )%output_to_netcdf( write_int_state,      &
                                                       array_name,           &
                                                       first_output )
    end do

  end subroutine output

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

  !> 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,        &
                                          mnrc_mpi_pack_size_string_t_array, &
                                          mnrc_mpi_pack_size_integer_array

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(in) :: this
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: i_group, i_array

    pack_size =                                                            + &
        mnrc_mpi_pack_size_integer( 1, comm )                              + &
        mnrc_mpi_pack_size_integer( 1, comm )                              + &
        mnrc_mpi_pack_size_string_t_array(  this%gas_spec_names, comm )    + &
        mnrc_mpi_pack_size_integer_array( this%gas_spec_tracer_ids, comm )
    do i_group = 1, size( this%aero_groups )
      pack_size = pack_size + this%aero_groups( i_group )%pack_size( comm )
    end do
    do i_array = 1, size( this%array_ptr )
      pack_size = pack_size + this%array_ptr( i_array )%pack_size( comm )
    end do

  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,             &
                                          mnrc_mpi_pack_string_t_array,      &
                                          mnrc_mpi_pack_integer_array
    use monarch_util,              only : assert
#endif

    !> Output diagnostics
    class(monarch_output_diag_debug_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_group, i_array

    prev_position = pos
    call mnrc_mpi_pack_integer( buffer, pos, size( this%aero_groups ), comm )
    call mnrc_mpi_pack_integer( buffer, pos, size( this%array_ptr   ), comm )
    call mnrc_mpi_pack_string_t_array( buffer, pos,                          &
                                             this%gas_spec_names     , comm )
    call mnrc_mpi_pack_integer_array(  buffer, pos,                          &
                                             this%gas_spec_tracer_ids, comm )
    do i_group = 1, size( this%aero_groups )
      call this%aero_groups( i_group )%bin_pack( buffer, pos, comm )
    end do
    do i_array = 1, size( this%array_ptr )
      call this%array_ptr( i_array )%bin_pack( buffer, pos, comm )
    end do
    call assert( 495194779, &
         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, aero_rep )
    use monarch_aerosol_representation,     only :                           &
                                            monarch_aerosol_representation_t
#ifdef MONARCH_USE_MPI
    use monarch_mpi,               only : mnrc_mpi_unpack_integer,           &
                                          mnrc_mpi_unpack_string_t_array,    &
                                          mnrc_mpi_unpack_integer_array
    use monarch_util,              only : assert
#endif

    !> Output diagnostics
    class(monarch_output_diag_debug_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
    !> Aerosol representation
    class(monarch_aerosol_representation_t), target, intent(in) :: aero_rep

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i_group, n_groups, i_array, n_arrays

    call assert( 154880971, .not. allocated( this%aero_groups ) )
    call assert( 551580099, .not. allocated( this%array_ptr   ) )

    prev_position = pos
    call mnrc_mpi_unpack_integer( buffer, pos, n_groups, comm )
    call mnrc_mpi_unpack_integer( buffer, pos, n_arrays, comm )
    call mnrc_mpi_unpack_string_t_array( buffer, pos,                        &
                                             this%gas_spec_names     , comm )
    call mnrc_mpi_unpack_integer_array(  buffer, pos,                        &
                                             this%gas_spec_tracer_ids, comm )
    allocate( this%aero_groups( n_groups ) )
    allocate( this%array_ptr(   n_arrays ) )
    do i_group = 1, n_groups
      call this%aero_groups( i_group )%bin_unpack( buffer, pos, comm,        &
                                                   aero_rep )
    end do
    do i_array = 1, n_arrays
      call this%array_ptr( i_array )%bin_unpack( buffer, pos, comm )
    end do
    call assert( 267199316, &
         pos - prev_position <= this%pack_size( comm ) )
#endif

  end subroutine bin_unpack

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

  !> Print out the configuration of the output diagnostic
  subroutine do_print( this, file_unit )

    use monarch_util,                            only : to_string

    !> Output diagnostics
    class(monarch_output_diag_debug_t), intent(in) :: this
    !> File unit
    integer(kind=mint), intent(in) :: file_unit

    integer(kind=mint) :: i_group

    write(file_unit,*) "  *** Diagnostic Output for Debugging MONARCH ***"
    write(file_unit,*) "  Bottom-of-layer height [m] saved to:         "//   &
                       "bottom_of_layer_height"
    write(file_unit,*) "  Aerosol dry mass conc. [kgm-3] saved to:     "//   &
                       "dust_conc"
    write(file_unit,*) "  Cloud optical thickness [unitless] saved to: "//   &
                       "cloud_optical_thickness"
    write(file_unit,*) "  | Index | Label"
    do i_group = 1, size( this%aero_groups )
      write(file_unit,"('   | ',I5,' |',A25)") i_group,                      &
                      "DUST_"//trim( to_string( i_group ) )
    end do
    write(file_unit,*) "  Gas-phase species conc. [ppm] saved to:    "//     &
                       "gas_species_conc"
    write(file_unit,*) "  | Index | gas-phase species"
    do i_group = 1, size( this%gas_spec_names )
      write(file_unit,"('   | ',I5,' | ',A25)")                              &
                      this%gas_spec_tracer_ids( i_group ),                   &
                      this%gas_spec_names( i_group )%string
    end do

  end subroutine do_print

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

end module monarch_output_diag_debug
