! 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 mnrc_mpi module.

!> Wrapper functions for MPI.
!!
!! All of these functions can be called irrespective of whether MPI
!! support was compiled in or not. If MPI support is not enabled then
!! they do the obvious trivial thing (normally nothing).
!!
!! These functions are copied from PartMC for use in MONARCH when PartMC
!! support is not compiled in.
module monarch_mpi
! MONARCH always has MPI
#define MONARCH_USE_MPI

  use monarch_constants,                    only : mint, mflt, mdbl
  use monarch_util

#ifdef MONARCH_USE_MPI
  use mpi
#endif

contains

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

  !> Whether MPI support is compiled in.
  logical function mnrc_mpi_support()

#ifdef MONARCH_USE_MPI
    mnrc_mpi_support = .true.
#else
    mnrc_mpi_support = .false.
#endif

  end function mnrc_mpi_support

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

  !> Dies if \c ierr is not ok.
  subroutine mnrc_mpi_check_ierr(ierr)

    !> MPI status code.
    integer(kind=mint), intent(in) :: ierr

#ifdef MONARCH_USE_MPI
    if (ierr /= MPI_SUCCESS) then
       call mnrc_mpi_abort(1)
    end if
#endif

  end subroutine mnrc_mpi_check_ierr

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

  !> Initialize MPI.
  subroutine mnrc_mpi_init()

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

    call mpi_init(ierr)
    call mnrc_mpi_check_ierr(ierr)
    call mnrc_mpi_test(MPI_COMM_WORLD)
#endif

  end subroutine mnrc_mpi_init

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

  !> Abort the program.
  subroutine mnrc_mpi_abort(status)

    !> Status flag to abort with.
    integer(kind=mint), intent(in) :: status

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

    call mpi_abort(MPI_COMM_WORLD, status, ierr)
#else
    call die(status)
#endif

  end subroutine mnrc_mpi_abort

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

  !> Shut down MPI.
  subroutine mnrc_mpi_finalize()

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

    call mpi_finalize(ierr)
    call mnrc_mpi_check_ierr(ierr)
#endif

  end subroutine mnrc_mpi_finalize

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

  !> Synchronize all processes.
  subroutine mnrc_mpi_barrier(comm)

    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_barrier(comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#endif

  end subroutine mnrc_mpi_barrier

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

  !> Returns the rank of the current process.
  integer(kind=mint) function mnrc_mpi_rank(comm)

    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: rank, ierr

    call mpi_comm_rank(comm, rank, ierr)
    call mnrc_mpi_check_ierr(ierr)
    mnrc_mpi_rank = rank
#else
    mnrc_mpi_rank = 0
#endif

  end function mnrc_mpi_rank

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

  !> Returns the total number of processes.
  integer(kind=mint) function mnrc_mpi_size(comm)

    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: size, ierr

    call mpi_comm_size(MPI_COMM_WORLD, size, ierr)
    call mnrc_mpi_check_ierr(ierr)
    mnrc_mpi_size = size
#else
    mnrc_mpi_size = 1
#endif

  end function mnrc_mpi_size

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

  !> Perform basic sanity checks on send/receive.
  subroutine mnrc_mpi_test(comm)

    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    real(kind=mdbl), parameter :: test_double = 2.718281828459d0
    real(kind=mflt), parameter :: test_float = 2.718281828459
    complex(kind=mdbl), parameter :: test_complex &
         = (0.707106781187d0, 1.4142135624d0)
    logical, parameter :: test_logical = .true.
    type(string_t) :: test_string
    integer(kind=mint), parameter :: test_integer = 314159

    character, allocatable :: buffer(:)
    integer(kind=mint) :: buffer_size, max_buffer_size, position
    real(kind=mdbl) :: send_double, recv_double
    real(kind=mflt) :: send_float, recv_float
    complex(kind=mdbl) :: send_complex, recv_complex
    logical :: send_logical, recv_logical
    type(string_t) :: send_string, recv_string
    integer(kind=mint) :: send_integer, recv_integer
    real(kind=mdbl), allocatable :: send_double_array(:)
    real(kind=mdbl), allocatable :: recv_double_array(:)
    real(kind=mflt), allocatable :: send_float_array(:)
    real(kind=mflt), allocatable :: recv_float_array(:)

    test_string%string = "a truth universally acknowledged"
    if (mnrc_mpi_rank(comm) == 0) then
       send_double = test_double
       send_float = send_float
       send_complex = test_complex
       send_logical = test_logical
       send_string = test_string
       send_integer = test_integer
       allocate(send_double_array(2))
       send_double_array(1) = real(test_complex)
       send_double_array(2) = aimag(test_complex)
       allocate(send_float_array(2))
       send_float_array(1) = real(test_complex, kind=mflt)
       send_float_array(2) = real(aimag(test_complex), kind=mflt)

       max_buffer_size = 0
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_integer(send_integer, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_double(send_double, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_float(send_float, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_complex(send_complex, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_logical(send_logical, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_string_t(send_string, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_double_array(send_double_array, comm)
       max_buffer_size = max_buffer_size &
            + mnrc_mpi_pack_size_float_array(send_float_array, comm)

       allocate(buffer(max_buffer_size))

       position = 0
       call mnrc_mpi_pack_double(buffer, position, send_double, comm)
       call mnrc_mpi_pack_float(buffer, position, send_float, comm)
       call mnrc_mpi_pack_complex(buffer, position, send_complex, comm)
       call mnrc_mpi_pack_logical(buffer, position, send_logical, comm)
       call mnrc_mpi_pack_string_t(buffer, position, send_string, comm)
       call mnrc_mpi_pack_integer(buffer, position, send_integer, comm)
       call mnrc_mpi_pack_double_array(buffer, position, send_double_array, comm)
       call mnrc_mpi_pack_float_array(buffer, position, send_float_array, comm)
       call assert_msg(350740830, position <= max_buffer_size, &
            "MPI test failure: pack position " &
            // trim(to_string(position)) &
            // " greater than max_buffer_size " &
            // trim(to_string(max_buffer_size)))
       buffer_size = position ! might be less than we allocated
    end if

    call mnrc_mpi_bcast_integer(buffer_size, comm)

    if (mnrc_mpi_rank(comm) /= 0) then
       allocate(buffer(buffer_size))
    end if

    call mnrc_mpi_bcast_packed(buffer, comm)

    if (mnrc_mpi_rank(comm) /= 0) then
       position = 0
       call mnrc_mpi_unpack_double(buffer, position, recv_double, comm)
       call mnrc_mpi_unpack_float(buffer, position, recv_float, comm)
       call mnrc_mpi_unpack_complex(buffer, position, recv_complex, comm)
       call mnrc_mpi_unpack_logical(buffer, position, recv_logical, comm)
       call mnrc_mpi_unpack_string_t(buffer, position, recv_string, comm)
       call mnrc_mpi_unpack_integer(buffer, position, recv_integer, comm)
       call mnrc_mpi_unpack_double_array(buffer, position, recv_double_array, comm)
       call mnrc_mpi_unpack_float_array(buffer, position, recv_float_array, comm)
       call assert_msg(787677020, position == buffer_size, &
            "MPI test failure: unpack position " &
            // trim(to_string(position)) &
            // " not equal to buffer_size " &
            // trim(to_string(buffer_size)))
    end if

    deallocate(buffer)

    if (mnrc_mpi_rank(comm) /= 0) then
       call assert_msg(567548916, recv_double == test_double, &
            "MPI test failure: double recv " &
            // trim(to_string(recv_double)) &
            // " not equal to " &
            // trim(to_string(test_double)))
       call assert_msg(271985748, recv_float == test_float, &
            "MPI test failure: float recv " &
            // trim(to_string(recv_float)) &
            // " not equal to " &
            // trim(to_string(test_float)))
       call assert_msg(653908509, recv_complex == test_complex, &
            "MPI test failure: complex recv " &
            // trim(to_string(recv_complex)) &
            // " not equal to " &
            // trim(to_string(test_complex)))
       call assert_msg(307746296, recv_logical .eqv. test_logical, &
            "MPI test failure: logical recv " &
            // trim(logical_to_string(recv_logical)) &
            // " not equal to " &
            // trim(logical_to_string(test_logical)))
       call assert_msg(155693492, recv_string == test_string, &
            "MPI test failure: string recv '" &
            // trim(recv_string%string) &
            // "' not equal to '" &
            // trim(test_string%string) // "'")
       call assert_msg(875699427, recv_integer == test_integer, &
            "MPI test failure: integer(kind=mint) recv " &
            // trim(to_string(recv_integer)) &
            // " not equal to " &
            // trim(to_string(test_integer)))
       call assert_msg(326982363, size(recv_double_array) == 2, &
            "MPI test failure: real array recv size " &
            // trim(to_string(size(recv_double_array))) &
            // " not equal to 2")
       call assert_msg(744394323, &
            recv_double_array(1) == real(test_complex), &
            "MPI test failure: double array recv index 1 " &
            // trim(to_string(recv_double_array(1))) &
            // " not equal to " &
            // trim(to_string(real(test_complex))))
       call assert_msg(858902527, &
            recv_double_array(2) == aimag(test_complex), &
            "MPI test failure: double array recv index 2 " &
            // trim(to_string(recv_double_array(2))) &
            // " not equal to " &
            // trim(to_string(aimag(test_complex))))
       call assert_msg(144326777, &
            recv_float_array(1) == real(test_complex, kind=mflt), &
            "MPI test failure: float array recv index 1 " &
            // trim(to_string(recv_float_array(1))) &
            // " not equal to " &
            // trim(to_string(real(test_complex, kind=mflt))))
       call assert_msg(775738914, &
            recv_float_array(2) == real(aimag(test_complex), kind=mflt), &
            "MPI test failure: float array recv index 2 " &
            // trim(to_string(recv_float_array(2))) &
            // " not equal to " &
            // trim(to_string(real(aimag(test_complex), kind=mflt))))
    end if
#endif

  end subroutine mnrc_mpi_test

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

  !> Broadcast the given value from process 0 to all other processes.
  subroutine mnrc_mpi_bcast_integer(val, comm)

    !> Value to broadcast.
    integer(kind=mint), intent(inout) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: root, ierr

    root = 0 ! source of data to broadcast
    call mpi_bcast(val, 1, MPI_INTEGER, root, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#endif

  end subroutine mnrc_mpi_bcast_integer

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

  !> Broadcast the given value from process 0 to all other processes.
  subroutine mnrc_mpi_bcast_string(val, comm)

    !> Value to broadcast.
    character(len=*), intent(inout) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: root, ierr

    root = 0 ! source of data to broadcast
    call mpi_bcast(val, len(val), MPI_CHARACTER, root, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#endif

  end subroutine mnrc_mpi_bcast_string

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

  !> Broadcast the given value from process 0 to all other processes.
  subroutine mnrc_mpi_bcast_packed(val, comm)

    !> Value to broadcast.
    character, intent(inout) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: root, ierr

    root = 0 ! source of data to broadcast
    call mpi_bcast(val, size(val), MPI_CHARACTER, root, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#endif

  end subroutine mnrc_mpi_bcast_packed

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_integer(val, comm)

    !> Value to pack.
    integer(kind=mint), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr

#ifdef MONARCH_USE_MPI

    root = 0 ! source of data to broadcast
    call mpi_pack_size(1, MPI_INTEGER, comm, &
         mnrc_mpi_pack_size_integer, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    mnrc_mpi_pack_size_integer = 0
#endif

  end function mnrc_mpi_pack_size_integer

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_float(val, comm)

    !> Value to pack.
    real(kind=mflt), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr

#ifdef MONARCH_USE_MPI

    call mpi_pack_size(1, MPI_REAL, comm, &
         mnrc_mpi_pack_size_float, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    mnrc_mpi_pack_size_float = 0
#endif

  end function mnrc_mpi_pack_size_float

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_double(val, comm)

    !> Value to pack.
    real(kind=mdbl), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr

#ifdef MONARCH_USE_MPI

    call mpi_pack_size(1, MPI_DOUBLE_PRECISION, comm, &
         mnrc_mpi_pack_size_double, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    mnrc_mpi_pack_size_double = 0
#endif

  end function mnrc_mpi_pack_size_double

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_string_t(val, comm)

    !> Value to pack.
    type(string_t), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr, str_size
    logical :: is_allocated

#ifdef MONARCH_USE_MPI

    str_size = 0
    is_allocated = allocated( val%string )
    mnrc_mpi_pack_size_string_t =                                            &
        mnrc_mpi_pack_size_logical( is_allocated, comm )
    if( is_allocated ) then
      mnrc_mpi_pack_size_string_t = mnrc_mpi_pack_size_string_t &
           + mnrc_mpi_pack_size_integer(len_trim(val%string), comm)
      call mpi_pack_size(len_trim(val%string), MPI_CHARACTER, comm, &
           str_size, ierr)
      call mnrc_mpi_check_ierr(ierr)
      mnrc_mpi_pack_size_string_t = mnrc_mpi_pack_size_string_t + str_size
    end if
#else
    mnrc_mpi_pack_size_string_t = 0
#endif

  end function mnrc_mpi_pack_size_string_t

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_logical(val, comm)

    !> Value to pack.
    logical, intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr

#ifdef MONARCH_USE_MPI

    call mpi_pack_size(1, MPI_LOGICAL, comm, &
         mnrc_mpi_pack_size_logical, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    mnrc_mpi_pack_size_logical = 0
#endif

  end function mnrc_mpi_pack_size_logical

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_complex(val, comm)

    !> Value to pack.
    complex(kind=mdbl), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: ierr

#ifdef MONARCH_USE_MPI

    call mpi_pack_size(1, MPI_DOUBLE_COMPLEX, comm, &
         mnrc_mpi_pack_size_complex, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    mnrc_mpi_pack_size_complex = 0
#endif

  end function mnrc_mpi_pack_size_complex

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_integer_array(val, comm)

    !> Value to pack.
    integer(kind=mint), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_INTEGER, comm,                      &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_integer_array = total_size

  end function mnrc_mpi_pack_size_integer_array

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_float_array(val, comm)

    !> Value to pack.
    real(kind=mflt), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_REAL, comm,                         &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_float_array = total_size

  end function mnrc_mpi_pack_size_float_array

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_double_array(val, comm)

    !> Value to pack.
    real(kind=mdbl), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_DOUBLE_PRECISION, comm,             &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_double_array = total_size

  end function mnrc_mpi_pack_size_double_array

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_string_t_array(val, comm)

    !> Value to pack.
    type(string_t), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: i, total_size

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    is_allocated = allocated(val)
    total_size = mnrc_mpi_pack_size_logical(is_allocated, comm)
    if (is_allocated) then
       total_size = total_size + mnrc_mpi_pack_size_integer(size(val), comm)
       do i = 1,size(val)
          total_size = total_size                                            &
               + mnrc_mpi_pack_size_string_t(val(i), comm)
       end do
    end if
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_string_t_array = total_size

  end function mnrc_mpi_pack_size_string_t_array

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_logical_array(val, comm)

    !> Value to pack.
    logical, allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_LOGICAL, comm,                      &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_logical_array = total_size

  end function mnrc_mpi_pack_size_logical_array

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_float_array_2d(val, comm)

    !> Value to pack.
    real(kind=mflt), allocatable, intent(in) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_REAL, comm,                         &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val,1), comm)                  &
            + mnrc_mpi_pack_size_integer(size(val,2), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_float_array_2d = total_size

  end function mnrc_mpi_pack_size_float_array_2d

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

  !> Determines the number of bytes required to pack the given value.
  integer(kind=mint) function mnrc_mpi_pack_size_double_array_2d(val, comm)

    !> Value to pack.
    real(kind=mdbl), allocatable, intent(in) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

    integer(kind=mint) :: total_size, ierr

#ifdef MONARCH_USE_MPI
    logical :: is_allocated

    total_size = 0
    is_allocated = allocated(val)
    if (is_allocated) then
       call mpi_pack_size(size(val), MPI_DOUBLE_PRECISION, comm,             &
            total_size, ierr)
       call mnrc_mpi_check_ierr(ierr)
       total_size = total_size                                               &
            + mnrc_mpi_pack_size_integer(size(val,1), comm)                  &
            + mnrc_mpi_pack_size_integer(size(val,2), comm)
    end if
    total_size = total_size                                                  &
         + mnrc_mpi_pack_size_logical(is_allocated, comm)
#else
    total_size = 0
#endif

    mnrc_mpi_pack_size_double_array_2d = total_size

  end function mnrc_mpi_pack_size_double_array_2d

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_integer(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    integer(kind=mint), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_pack(val, 1, MPI_INTEGER, buffer, size(buffer),                 &
         position, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(913495993, position - prev_position <=                       &
             mnrc_mpi_pack_size_integer(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_integer

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_float(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_pack(val, 1, MPI_REAL, buffer, size(buffer),        &
         position, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(138348649, position - prev_position <=                       &
             mnrc_mpi_pack_size_float(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_float

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_double(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_pack(val, 1, MPI_DOUBLE_PRECISION, buffer, size(buffer),        &
         position, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(395354132, position - prev_position <=                       &
             mnrc_mpi_pack_size_double(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_double

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_string_t(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    type(string_t), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, length, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated( val%string )
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if( is_allocated ) then
      length = len_trim( val%string )
      call mnrc_mpi_pack_integer(buffer, position, length, comm)
      call mpi_pack(val%string, length, MPI_CHARACTER, buffer, size(buffer), &
           position, comm, ierr)
      call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(607212018, position - prev_position <=                       &
             mnrc_mpi_pack_size_string_t(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_string_t

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_logical(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    logical, intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_pack(val, 1, MPI_LOGICAL, buffer, size(buffer),                 &
         position, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(104535200, position - prev_position <=                       &
             mnrc_mpi_pack_size_logical(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_logical

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_complex(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    complex(kind=mdbl), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_pack(val, 1, MPI_DOUBLE_COMPLEX, buffer, size(buffer),          &
         position, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(640416372, position - prev_position <=                       &
             mnrc_mpi_pack_size_complex(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_complex

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_integer_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    integer(kind=mint), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n = size(val)
       call mnrc_mpi_pack_integer(buffer, position, n, comm)
       call mpi_pack(val, n, MPI_INTEGER, buffer, size(buffer),              &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(698601296, position - prev_position <=                       &
             mnrc_mpi_pack_size_integer_array(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_integer_array

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_float_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n = size(val)
       call mnrc_mpi_pack_integer(buffer, position, n, comm)
       call mpi_pack(val, n, MPI_REAL, buffer, size(buffer),     &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(763043240, &
         position - prev_position <=                                         &
             mnrc_mpi_pack_size_float_array(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_float_array

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_double_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n = size(val)
       call mnrc_mpi_pack_integer(buffer, position, n, comm)
       call mpi_pack(val, n, MPI_DOUBLE_PRECISION, buffer, size(buffer),     &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(825718791, position - prev_position <=                       &
             mnrc_mpi_pack_size_double_array(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_double_array

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_string_t_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    type(string_t), allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i, n
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n = size(val)
       call mnrc_mpi_pack_integer(buffer, position, n, comm)
       do i = 1,n
          call mnrc_mpi_pack_string_t(buffer, position, val(i), comm)
       end do
    end if
    call assert(442079868, position - prev_position <=                       &
             mnrc_mpi_pack_size_string_t_array(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_string_t_array

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_logical_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    logical, allocatable, intent(in) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n = size(val)
       call mnrc_mpi_pack_integer(buffer, position, n, comm)
       call mpi_pack(val, n, MPI_LOGICAL, buffer, size(buffer),              &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(259786369, position - prev_position <=                       &
             mnrc_mpi_pack_size_logical_array(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_logical_array

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_float_array_2d(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), allocatable, intent(in) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n1, n2, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n1 = size(val, 1)
       n2 = size(val, 2)
       call mnrc_mpi_pack_integer(buffer, position, n1, comm)
       call mnrc_mpi_pack_integer(buffer, position, n2, comm)
       call mpi_pack(val, n1*n2, MPI_REAL, buffer, size(buffer), &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(422729432, position - prev_position <=                       &
             mnrc_mpi_pack_size_float_array_2d(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_float_array_2d

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

  !> Packs the given value into the buffer, advancing position.
  subroutine mnrc_mpi_pack_double_array_2d(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), allocatable, intent(in) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n1, n2, ierr
    logical :: is_allocated

    prev_position = position
    is_allocated = allocated(val)
    call mnrc_mpi_pack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       n1 = size(val, 1)
       n2 = size(val, 2)
       call mnrc_mpi_pack_integer(buffer, position, n1, comm)
       call mnrc_mpi_pack_integer(buffer, position, n2, comm)
       call mpi_pack(val, n1*n2, MPI_DOUBLE_PRECISION, buffer, size(buffer), &
            position, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(567349745, position - prev_position <=                       &
             mnrc_mpi_pack_size_double_array_2d(val, comm) )
#endif

  end subroutine mnrc_mpi_pack_double_array_2d

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_integer(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    integer(kind=mint), intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_unpack(buffer, size(buffer), position, val, 1, MPI_INTEGER, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(890243339, &
         position - prev_position <= mnrc_mpi_pack_size_integer(val, comm) )
#else
    val = 0
#endif

  end subroutine mnrc_mpi_unpack_integer

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_float(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_unpack(buffer, size(buffer), position, val, 1, &
         MPI_REAL, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(417465125, &
         position - prev_position <= mnrc_mpi_pack_size_float(val, comm) )
#else
    val = real(0.0, kind=mflt)
#endif

  end subroutine mnrc_mpi_unpack_float

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_double(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_unpack(buffer, size(buffer), position, val, 1, &
         MPI_DOUBLE_PRECISION, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(570771632, &
         position - prev_position <= mnrc_mpi_pack_size_double(val, comm) )
#else
    val = real(0.0, kind=mdbl)
#endif

  end subroutine mnrc_mpi_unpack_double

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_string_t(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    type(string_t), intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, length, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if( is_allocated ) then
      call mnrc_mpi_unpack_integer(buffer, position, length, comm)
      allocate( character(length) :: val%string )
      call mpi_unpack(buffer, size(buffer), position, val%string, length, &
           MPI_CHARACTER, comm, ierr)
      call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(503378058, position - prev_position <=                       &
             mnrc_mpi_pack_size_string_t(val, comm) )
#else
    val%string = ""
#endif

  end subroutine mnrc_mpi_unpack_string_t

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_logical(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    logical, intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_unpack(buffer, size(buffer), position, val, 1, MPI_LOGICAL, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(694750528, position - prev_position <=                       &
             mnrc_mpi_pack_size_logical(val, comm) )
#else
    val = .false.
#endif

  end subroutine mnrc_mpi_unpack_logical

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_complex(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    complex(kind=mdbl), intent(out) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    prev_position = position
    call mpi_unpack(buffer, size(buffer), position, val, 1, &
         MPI_DOUBLE_COMPLEX, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    call assert(969672634, position - prev_position <=                       &
             mnrc_mpi_pack_size_complex(val, comm) )
#else
    val = cmplx(0)
#endif

  end subroutine mnrc_mpi_unpack_complex

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_integer_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    integer(kind=mint), allocatable, intent(inout) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n, comm)
       allocate(val(n))
       call mpi_unpack(buffer, size(buffer), position, val, n, MPI_INTEGER, &
            comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(565840919, position - prev_position <=                       &
             mnrc_mpi_pack_size_integer_array(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_integer_array

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_float_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), allocatable, intent(inout) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n, comm)
       allocate(val(n))
       call mpi_unpack(buffer, size(buffer), position, val, n, &
            MPI_REAL, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(359626566, position - prev_position <=                       &
             mnrc_mpi_pack_size_float_array(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_float_array

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_double_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), allocatable, intent(inout) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n, comm)
       allocate(val(n))
       call mpi_unpack(buffer, size(buffer), position, val, n, &
            MPI_DOUBLE_PRECISION, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(782875761, position - prev_position <=                       &
             mnrc_mpi_pack_size_double_array(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_double_array

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_string_t_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    type(string_t), allocatable, intent(out) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, i, n
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n, comm)
       allocate(val(n))
       do i = 1,n
          call mnrc_mpi_unpack_string_t(buffer, position, val(i), comm)
       end do
    end if
    call assert(270469725, position - prev_position <=                       &
             mnrc_mpi_pack_size_string_t_array(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_string_t_array

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_logical_array(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    logical, allocatable, intent(inout) :: val(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n, comm)
       allocate(val(n))
       call mpi_unpack(buffer, size(buffer), position, val, n, &
            MPI_LOGICAL, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(540356084, position - prev_position <=                       &
             mnrc_mpi_pack_size_logical_array(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_logical_array

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_float_array_2d(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mflt), allocatable, intent(inout) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n1, n2, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n1, comm)
       call mnrc_mpi_unpack_integer(buffer, position, n2, comm)
       allocate(val(n1,n2))
       call mpi_unpack(buffer, size(buffer), position, val, n1*n2, &
            MPI_REAL, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(366796407, position - prev_position &
         <= mnrc_mpi_pack_size_float_array_2d(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_float_array_2d

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

  !> Unpacks the given value from the buffer, advancing position.
  subroutine mnrc_mpi_unpack_double_array_2d(buffer, position, val, comm)

    !> Memory buffer.
    character, intent(inout) :: buffer(:)
    !> Current buffer position.
    integer(kind=mint), intent(inout) :: position
    !> Value to pack.
    real(kind=mdbl), allocatable, intent(inout) :: val(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: prev_position, n1, n2, ierr
    logical :: is_allocated

    prev_position = position
    call mnrc_mpi_unpack_logical(buffer, position, is_allocated, comm)
    if (allocated(val)) deallocate(val)
    if (is_allocated) then
       call mnrc_mpi_unpack_integer(buffer, position, n1, comm)
       call mnrc_mpi_unpack_integer(buffer, position, n2, comm)
       allocate(val(n1,n2))
       call mpi_unpack(buffer, size(buffer), position, val, n1*n2, &
            MPI_DOUBLE_PRECISION, comm, ierr)
       call mnrc_mpi_check_ierr(ierr)
    end if
    call assert(781681739, position - prev_position &
         <= mnrc_mpi_pack_size_double_array_2d(val, comm) )
#endif

  end subroutine mnrc_mpi_unpack_double_array_2d

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

  !> Computes the average of val across all processes, storing the
  !> result in val_avg on the root process.
  subroutine mnrc_mpi_reduce_avg_double(val, val_avg, comm)

    !> Value to average.
    real(kind=mdbl), intent(in) :: val
    !> Result.
    real(kind=mdbl), intent(out) :: val_avg
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_reduce(val, val_avg, 1, MPI_DOUBLE_PRECISION, MPI_SUM, 0, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    if (mnrc_mpi_rank(comm) == 0) then
       val_avg = val_avg / real(mnrc_mpi_size(comm), kind=mdbl)
    end if
#else
    val_avg = val
#endif

  end subroutine mnrc_mpi_reduce_avg_double

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

  !> Transfer the value between the given processes.
  subroutine mnrc_mpi_transfer_double(from_val, to_val, from_proc, to_proc,  &
      comm)

    !> Value to send.
    real(kind=mdbl), intent(in) :: from_val
    !> Variable to send to.
    real(kind=mdbl), intent(out) :: to_val
    !> Process to send from.
    integer(kind=mint), intent(in) :: from_proc
    !> Process to send to.
    integer(kind=mint), intent(in) :: to_proc
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: rank, ierr, status(MPI_STATUS_SIZE)

    rank = mnrc_mpi_rank(comm)
    if (from_proc == to_proc) then
       if (rank == from_proc) then
          to_val = from_val
       end if
    else
       if (rank == from_proc) then
          call mpi_send(from_val, 1, MPI_DOUBLE_PRECISION, to_proc, &
               208020430, comm, ierr)
          call mnrc_mpi_check_ierr(ierr)
       elseif (rank == to_proc) then
          call mpi_recv(to_val, 1, MPI_DOUBLE_PRECISION, from_proc, &
               208020430, comm, status, ierr)
          call mnrc_mpi_check_ierr(ierr)
       end if
    end if
#else
    to_val = from_val
#endif

  end subroutine mnrc_mpi_transfer_double

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

  !> Transfer the value between the given processes.
  subroutine mnrc_mpi_transfer_integer(from_val, to_val, from_proc, to_proc, &
      comm)

    !> Value to send.
    integer(kind=mint), intent(in) :: from_val
    !> Variable to send to.
    integer(kind=mint), intent(out) :: to_val
    !> Process to send from.
    integer(kind=mint), intent(in) :: from_proc
    !> Process to send to.
    integer(kind=mint), intent(in) :: to_proc
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: rank, ierr, status(MPI_STATUS_SIZE)

    rank = mnrc_mpi_rank(comm)
    if (from_proc == to_proc) then
       if (rank == from_proc) then
          to_val = from_val
       end if
    else
       if (rank == from_proc) then
          call mpi_send(from_val, 1, MPI_INTEGER, to_proc, &
               208020430, comm, ierr)
          call mnrc_mpi_check_ierr(ierr)
       elseif (rank == to_proc) then
          call mpi_recv(to_val, 1, MPI_INTEGER, from_proc, &
               208020430, comm, status, ierr)
          call mnrc_mpi_check_ierr(ierr)
       end if
    end if
#else
    to_val = from_val
#endif

  end subroutine mnrc_mpi_transfer_integer

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

  !> Computes the sum of \c val across all processes, storing the
  !> result in \c val_sum on the root process.
  subroutine mnrc_mpi_reduce_sum_integer(val, val_sum, comm)

    !> Value to sum.
    integer(kind=mint), intent(in) :: val
    !> Result.
    integer(kind=mint), intent(out) :: val_sum
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_reduce(val, val_sum, 1, MPI_INTEGER, MPI_SUM, 0, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_sum = val
#endif

  end subroutine mnrc_mpi_reduce_sum_integer

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

  !> Computes the sum of \c val across all processes, storing the
  !> result in \c val_sum on all processes.
  subroutine mnrc_mpi_allreduce_sum_integer(val, val_sum, comm)

    !> Value to sum.
    integer(kind=mint), intent(in) :: val
    !> Result.
    integer(kind=mint), intent(out) :: val_sum
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_sum, 1, MPI_INTEGER, MPI_SUM, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_sum = val
#endif

  end subroutine mnrc_mpi_allreduce_sum_integer

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

  !> Computes the average of val across all processes, storing the
  !> result in val_avg on the root process.
  subroutine mnrc_mpi_reduce_avg_double_array(val, val_avg, comm)

    !> Value to average.
    real(kind=mdbl), intent(in) :: val(:)
    !> Result.
    real(kind=mdbl), intent(out) :: val_avg(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call assert(915136121, size(val) == size(val_avg))
    call mpi_reduce(val, val_avg, size(val), MPI_DOUBLE_PRECISION, &
         MPI_SUM, 0, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    if (mnrc_mpi_rank(comm) == 0) then
       val_avg = val_avg / real(mnrc_mpi_size(comm), kind=mdbl)
    end if
#else
    val_avg = val
#endif

  end subroutine mnrc_mpi_reduce_avg_double_array

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

  !> Computes the average of val across all processes, storing the
  !> result in val_avg on the root process.
  subroutine mnrc_mpi_reduce_avg_double_array_2d(val, val_avg, comm)

    !> Value to average.
    real(kind=mdbl), intent(in) :: val(:,:)
    !> Result.
    real(kind=mdbl), intent(out) :: val_avg(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call assert(131229046, size(val,1) == size(val_avg,1))
    call assert(992122167, size(val,2) == size(val_avg,2))
    call mpi_reduce(val, val_avg, size(val), MPI_DOUBLE_PRECISION, &
         MPI_SUM, 0, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    if (mnrc_mpi_rank(comm) == 0) then
       val_avg = val_avg / real(mnrc_mpi_size(comm), kind=mdbl)
    end if
#else
    val_avg = val
#endif

  end subroutine mnrc_mpi_reduce_avg_double_array_2d

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

  !> Computes the average of val across all processes, storing the
  !> result in val_avg on all processes.
  subroutine mnrc_mpi_allreduce_average_double(val, val_avg, comm)

    !> Value to average.
    real(kind=mdbl), intent(in) :: val
    !> Result.
    real(kind=mdbl), intent(out) :: val_avg
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_avg, 1, MPI_DOUBLE_PRECISION, MPI_SUM, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    val_avg = val_avg / real(mnrc_mpi_size(comm), kind=mdbl)
#else
    val_avg = val
#endif

  end subroutine mnrc_mpi_allreduce_average_double

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

  !> Computes the average of val across all processes, storing the
  !> result in val_avg on all processes.
  subroutine mnrc_mpi_allreduce_average_double_array(val, val_avg, comm)

    !> Value to average.
    real(kind=mdbl), intent(in) :: val(:)
    !> Result.
    real(kind=mdbl), intent(out) :: val_avg(:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call assert(948533359, size(val) == size(val_avg))
    call mpi_allreduce(val, val_avg, size(val), MPI_DOUBLE_PRECISION, &
         MPI_SUM, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    val_avg = val_avg / real(mnrc_mpi_size(comm), kind=mdbl)
#else
    val_avg = val
#endif

  end subroutine mnrc_mpi_allreduce_average_double_array

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

  !> Computes the minimum of val across all processes, storing the
  !> result in val_min on all processes.
  subroutine mnrc_mpi_allreduce_min_integer(val, val_min, comm)

    !> Value to minimize.
    integer(kind=mint), intent(in) :: val
    !> Result.
    integer(kind=mint), intent(out) :: val_min
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_min, 1, MPI_INTEGER, MPI_MIN, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_min = val
#endif

  end subroutine mnrc_mpi_allreduce_min_integer

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

  !> Computes the maximum of val across all processes, storing the
  !> result in val_max on all processes.
  subroutine mnrc_mpi_allreduce_max_integer(val, val_max, comm)

    !> Value to maximize.
    integer(kind=mint), intent(in) :: val
    !> Result.
    integer(kind=mint), intent(out) :: val_max
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_max, 1, MPI_INTEGER, MPI_MAX, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_max = val
#endif

  end subroutine mnrc_mpi_allreduce_max_integer

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

  !> Computes the minimum of val across all processes, storing the
  !> result in val_min on all processes.
  subroutine mnrc_mpi_allreduce_min_double(val, val_min, comm)

    !> Value to minimize.
    real(kind=mdbl), intent(in) :: val
    !> Result.
    real(kind=mdbl), intent(out) :: val_min
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_min, 1, MPI_DOUBLE_PRECISION, MPI_MIN, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_min = val
#endif

  end subroutine mnrc_mpi_allreduce_min_double

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

  !> Computes the maximum of val across all processes, storing the
  !> result in val_max on all processes.
  subroutine mnrc_mpi_allreduce_max_double(val, val_max, comm)

    !> Value to maximize.
    real(kind=mdbl), intent(in) :: val
    !> Result.
    real(kind=mdbl), intent(out) :: val_max
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_allreduce(val, val_max, 1, MPI_DOUBLE_PRECISION, MPI_MAX, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    val_max = val
#endif

  end subroutine mnrc_mpi_allreduce_max_double

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

  !> Returns whether all processors have the same value.
  logical function mnrc_mpi_allequal_integer(val, comm)

    !> Value to compare.
    integer(kind=mint), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: min_val, max_val

    call mnrc_mpi_allreduce_min_integer(val, min_val, comm)
    call mnrc_mpi_allreduce_max_integer(val, max_val, comm)
    if (min_val == max_val) then
       mnrc_mpi_allequal_integer = .true.
    else
       mnrc_mpi_allequal_integer = .false.
    end if
#else
    mnrc_mpi_allequal_integer = .true.
#endif

  end function mnrc_mpi_allequal_integer

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

  !> Returns whether all processors have the same value.
  logical function mnrc_mpi_allequal_double(val, comm)

    !> Value to compare.
    real(kind=mdbl), intent(in) :: val
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    real(kind=mdbl) :: min_val, max_val

    call mnrc_mpi_allreduce_min_double(val, min_val, comm)
    call mnrc_mpi_allreduce_max_double(val, max_val, comm)
    if (min_val == max_val) then
       mnrc_mpi_allequal_double = .true.
    else
       mnrc_mpi_allequal_double = .false.
    end if
#else
    mnrc_mpi_allequal_double = .true.
#endif

  end function mnrc_mpi_allequal_double

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

  !> Does an all-to-all transfer of integer(kind=mint)s.
  subroutine mnrc_mpi_alltoall_integer(send, recv, comm)

    !> Values to send (must be one per process).
    integer(kind=mint), intent(in) :: send(:)
    !> Values to receive (must be one per process).
    integer(kind=mint), intent(out) :: recv(size(send))
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

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

    call mpi_alltoall(send, 1, MPI_INTEGER, recv, 1, MPI_INTEGER, &
         comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
#else
    recv = send
#endif

  end subroutine mnrc_mpi_alltoall_integer

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

  !> Does an allgather of integer(kind=mint) arrays (must be the same size on all
  !> processes).
  subroutine mnrc_mpi_allgather_integer_array(send, recv, comm)

    !> Values to send on each process.
    integer(kind=mint), intent(in) :: send(:)
    !> Values to receive (will be the same on all processes.
    integer(kind=mint), intent(out) :: recv(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: n_proc, n_bin, n_data, ierr
    integer(kind=mint), allocatable :: send_buf(:), recv_buf(:)

    n_proc = mnrc_mpi_size(comm)
    n_data = size(send, 1)
    call assert(353005542, all(shape(recv) == (/n_data, n_proc/)))

    ! use a new send_buf to make sure the memory is contiguous
    allocate(send_buf(n_data))
    allocate(recv_buf(n_data * n_proc))
    send_buf = send
    call mpi_allgather(send_buf, n_data, MPI_INTEGER, &
         recv_buf, n_data, MPI_INTEGER, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    recv = reshape(recv_buf, (/n_data, n_proc/))
    deallocate(send_buf)
    deallocate(recv_buf)
#else
    recv(:, 1) = send
#endif

  end subroutine mnrc_mpi_allgather_integer_array

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

  !> Does an allgather of real arrays (must be the same size on all
  !> processes).
  subroutine mnrc_mpi_allgather_double_array(send, recv, comm)

    !> Values to send on each process.
    real(kind=mdbl), intent(in) :: send(:)
    !> Values to receive (will be the same on all processes.
    real(kind=mdbl), intent(out) :: recv(:,:)
    !> MPI communicator
    integer(kind=mint), intent(in) :: comm

#ifdef MONARCH_USE_MPI
    integer(kind=mint) :: n_proc, n_bin, n_data, ierr
    real(kind=mdbl), allocatable :: send_buf(:), recv_buf(:)

    n_proc = mnrc_mpi_size(comm)
    n_data = size(send, 1)
    call assert(291000580, all(shape(recv) == (/n_data, n_proc/)))

    ! use a new send_buf to make sure the memory is contiguous
    allocate(send_buf(n_data))
    allocate(recv_buf(n_data * n_proc))
    send_buf = send
    call mpi_allgather(send_buf, n_data, MPI_DOUBLE_PRECISION, &
         recv_buf, n_data, MPI_DOUBLE_PRECISION, comm, ierr)
    call mnrc_mpi_check_ierr(ierr)
    recv = reshape(recv_buf, (/n_data, n_proc/))
    deallocate(send_buf)
    deallocate(recv_buf)
#else
    recv(:, 1) = send
#endif

  end subroutine mnrc_mpi_allgather_double_array

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
end module monarch_mpi
