! -*- mode: f90; coding: utf-8 -*-
!-------------------------------------------------------------------------
! Copyright (c) 2019 SPMODEL Development Group. All rights reserved.
!
! 履歴  2019/03/19 竹広真一
!       2019/10/09 佐々木洋平
!
!-------------------------------------------------------------------------
!> @copyright Copyright (C) SPMODEL Development Group, 2019.
!>            License is MIT/X11. see [COPYRIGHT](@ref COPYRIGHT) in detail
!> @author Shin-ichi Takehiro, Youhei SASAKI
!> @brief
!> @ref w_mpi_module の下部モジュール: スペクトル解析
!>
!> @warning
!> 本モジュールを直接呼ぶ事は想定されていないことに注意されたい．
!> ユーザは w_mpi_module を呼ぶこと．
!>
module w_mpi_module_spectrum
  use mpi
  use dc_types, only: DP
  use w_mpi_module_base, only: nm=>nn, nm_l, nc, icom

  implicit none
  private
  !> エネルギースペクトル(水平全波数 n, 帯状波数 m 空間)
  public nm_EnergyFromStreamfunc_w
  !> エネルギースペクトル(水平全波数 n 空間)
  public n_EnergyFromStreamfunc_w
  !> エンストロフィースペクトル(水平全波数 n, 帯状波数 m 空間)
  public nm_EnstrophyFromStreamfunc_w
  !> エンストロフィースペクトル(水平全波数 n 空間)
  public n_EnstrophyFromStreamfunc_w
  !> 欠損値. 初期値は -999.000
  public w_spectrum_VMiss
  real(DP) :: w_spectrum_VMiss = -999.000

contains

  !---------------------------------------------------------------------
  !> 流線関数のスペクトルデータからエネルギーの球面調和函数成分
  !> (スペクトル)を計算する(1 層用).
  !>
  !>  * 全波数 n, 帯状波数 m の流線関数のスペクトル成分
  !>    \f$ψ(n,m)\f$ から, エネルギースペクトルは
  !>    \f$(1/2)n(n+1)|ψ(n,m)|^2\f$ と計算される.
  !>
  !>  * 全てのエネルギースペクトル成分の和に\f$4π\f$をかけたものが
  !>    球面上での全エネルギーに等しい.
  !>
  !>  * スペクトルの (n,m) 成分は全波数 n, 東西波数 m の実部成分,
  !>    (n,-m) 成分は全波数 n, 東西波数 m の虚部成分が格納されている.
  !>
  !>  * データの存在しない全波数 n, 帯状波数 m の配列には欠損値が格納される.
  !>    欠損値の値はモジュール変数
  !>    [w_spectrum_VMiss](@ref w_spectrum_vmiss) によって設定できる
  !>    (初期値は -999.0)
  !>
  function nm_EnergyFromStreamfunc_w(w_Strfunc)
    !> 流線関数(スペクトルデータ)
    real(DP), intent(in)             :: w_Strfunc(nc)
    !> エネルギースペクトル(水平全波数 n, 帯状波数 m 空間)
    real(DP), dimension(0:nm,-nm:nm) :: nm_EnergyFromStreamfunc_w

    real(DP), dimension(0:nm,-nm:nm) :: nm_Work
    integer :: l, n, m, nm_ary(2)
    integer :: ierr

    nm_work = 0.0D0
    do l=1,int(nc)
      nm_ary = nm_l(l)
      n = nm_ary(1)
      m = abs(nm_ary(2))
      if ( n .ge. 0 ) then
        if ( m == 0 ) then
          nm_Work(n,0) = nm_Work(n,0) &
            & + 0.5D0 * n*(n+1) * w_Strfunc(l)**2
        else
          nm_Work(n,m) = nm_Work(n,m) &
            & + 0.5D0 * n*(n+1) * w_Strfunc(l)**2
          nm_Work(n,-m) = nm_Work(n,-m) &
            & + 0.5D0 * n*(n+1) * w_Strfunc(l)**2
        endif
      endif
    enddo

    CALL MPI_ALLREDUCE(nm_Work,                          &
      & nm_EnergyFromStreamfunc_w, int((nm+1)*(2*nm+1)), &
      & MPI_REAL8,MPI_SUM, int(icom), IERR)

    do m=1,int(nm)
      do n=0,m-1
        nm_EnergyFromStreamfunc_w(n,m)  = w_spectrum_Vmiss
        nm_EnergyFromStreamfunc_w(n,-m) = w_spectrum_Vmiss
      end do
    end do

  end function nm_EnergyFromStreamfunc_w

  !---------------------------------------------------------------------
  !> 流線関数のスペクトルデータから各全波数のエネルギー成分(スペクトル)を
  !> 計算する(1 層用).
  !>
  !>  * 全波数 n の流線関数のスペクトル成分 \f$ψ(n,m)\f$ から
  !>    エネルギースペクトルは
  !>    \f[ \sum_{m=-nm}^{nm}(1/2)n(n+1)|ψ(n,m)|^2\f]
  !>    と計算される.
  !>
  !>  * 全てのエネルギースペクトル成分の和に \f$4π\f$ をかけたものが
  !>    球面上での全エネルギーに等しい.
  !>
  function n_EnergyFromStreamfunc_w(w_Strfunc)
    !> 流線関数(スペクトルデータ)
    real(DP), intent(in)      :: w_Strfunc(nc)
    !> エネルギースペクトル (水平全波数 n 空間)
    real(DP), dimension(0:nm) :: n_EnergyFromStreamfunc_w

    real(DP), dimension(0:nm) :: n_Work

    integer  :: l, n, m, nm_ary(2)
    real(DP) :: factor
    integer :: ierr

    n_work = 0.0D0
    do l=1,int(nc)
      nm_ary = nm_l(l)
      n = nm_ary(1)
      m = abs(nm_ary(2))
      if ( n .ge. 0 ) then
        if ( m == 0 ) then
          factor = 1.0D0
        else
          factor = 2.0D0
        endif
        n_Work(n) = n_Work(n) &
          + factor * 0.5D0 * n*(n+1) * w_Strfunc(l)**2
      endif
    enddo

    CALL MPI_ALLREDUCE(n_Work,               &
      & n_EnergyFromStreamfunc_w, int(nm+1), &
      & MPI_REAL8, MPI_SUM, int(icom), IERR)

  end function n_EnergyFromStreamfunc_w

  !---------------------------------------------------------------------
  !> 流線関数のスペクトルデータからエンストロフィーの球面調和函数成分
  !> (スペクトル)を計算する(1 層用).
  !>
  !> * 全波数 n, 帯状波数 m の流線関数のスペクトル成分 \f$ψ(n,m)\f$ から
  !>   エンストロフィースペクトルは
  !>   \f$(1/2)n^2(n+1)^2|ψ(n,m)|^2\f$と計算される.
  !>
  !> * 全てのエンストロフィースペクトル成分の和に \f$4π/R^2\f$ をかけた
  !>   ものが球面上での全エンストロフィーに等しい.
  !>   ここで \f$R\f$ は球面の半径である.
  !>
  !> * データの存在しない全波数 n, 帯状波数 m の配列には欠損値が格納される.
  !>   欠損値の値はモジュール変数 w_spectrum_VMiss によって設定できる
  !>   (初期値は -999.0)
  !>
  function nm_EnstrophyFromStreamfunc_w(w_Strfunc)
    !> 流線関数(スペクトルデータ)
    real(DP), intent(in)   :: w_Strfunc(nc)
    !> エンストロフィースペクトル (水平全波数 n, 帯状波数 m 空間)
    real(DP), dimension(0:nm,-nm:nm) :: nm_EnstrophyFromStreamfunc_w

    real(DP), dimension(0:nm,-nm:nm) :: nm_Work
    integer :: l, n, m, nm_ary(2)
    integer :: ierr

    nm_work = 0.0D0
    do l=1,int(nc)
      nm_ary = nm_l(l)
      n = nm_ary(1)
      m = abs(nm_ary(2))
      if ( n .ge. 0 ) then
        if ( m == 0 ) then
          nm_Work(n,0) = nm_Work(n,0) &
            +  0.5D0 * n**2 * (n+1)**2 * w_Strfunc(l)**2
        else
          nm_Work(n,m) = nm_Work(n,m) &
            +  0.5D0 * n**2 * (n+1)**2 * w_Strfunc(l)**2
          nm_Work(n,-m) = nm_Work(n,-m) &
            +  0.5D0 * n**2 * (n+1)**2 * w_Strfunc(l)**2
        endif
      endif
    enddo

    CALL MPI_ALLREDUCE(nm_Work,                            &
      & nm_EnstrophyFromStreamfunc_w,int((nm+1)*(2*nm+1)), &
      MPI_REAL8, MPI_SUM, int(icom), IERR)

    do m=1,int(nm)
      do n=0,m-1
        nm_EnstrophyFromStreamfunc_w(n,m)  = w_spectrum_Vmiss
        nm_EnstrophyFromStreamfunc_w(n,-m) = w_spectrum_Vmiss
      enddo
    enddo

  end function nm_EnstrophyFromStreamfunc_w

  !---------------------------------------------------------------------
  !> 流線関数のスペクトルデータから各全波数のエネルギー成分(スペクトル)を
  !> 計算する(1 層用)
  !>
  !> * 全波数 n の流線関数のスペクトル成分ψ(n,m) からエンストロフィー
  !>   スペクトルは
  !>   \f[\sum_{m=-nm}^{nm} (1/2)n^2(n+1)^2|ψ(n,m)|^2\f] と計算される.
  !>
  !> * 全てのエネルギースペクトル成分の和に \f$4π/R^2\f$ をかけたものが
  !>   球面上での全エンストフィーに等しい.
  !>   ここで \f$R\f$ は球面の半径である.
  !>
  function n_EnstrophyFromStreamfunc_w(w_Strfunc)
    !> 流線関数(スペクトルデータ)
    real(DP), intent(in)      :: w_Strfunc(nc)
    !> エンストロフィースペクトル(水平全波数 n 空間)
    real(DP), dimension(0:nm) :: n_EnstrophyFromStreamfunc_w

    real(DP), dimension(0:nm) :: n_Work

    integer :: l, n, m, nm_ary(2)
    real(DP) :: factor

    integer :: ierr

    n_work = 0.0D0
    do l=1,int(nc)
      nm_ary = nm_l(l)
      n = nm_ary(1)
      m = abs(nm_ary(2))
      if ( n .ge. 0 ) then
        if ( m == 0 ) then
          factor = 1.0D0
        else
          factor = 2.0D0
        endif
        n_Work(n) = n_Work(n) &
          + factor * 0.5D0 * n**2 * (n+1)**2 * w_Strfunc(l)**2
      endif
    enddo

    CALL MPI_ALLREDUCE(n_Work,                 &
      & n_EnstrophyFromStreamfunc_w,int(nm+1), &
      & MPI_REAL8, MPI_SUM, int(icom), IERR)

  end function n_EnstrophyFromStreamfunc_w

end module w_mpi_module_spectrum
