!--
!----------------------------------------------------------------------
!     Copyright (c) 2024-2025 Shin-ichi Takehiro. All rights reserved.
!----------------------------------------------------------------------
!表題  w_zonal_deriv_module
!
!  spml/w_zonal_module_deriv モジュールは球面上での 2 次元
!  流体運動を球面調和函数を用いたスペクトル法によって数値計算する
!  ための モジュール w_zonal_module_ の下部モジュールであり, 
!  スペクトル法の微分計算のための Fortran90 関数を提供する. 
!
!  内部で ISPACK3 の LXPACK の Fortran サブルーチンを呼んでいる. 
!  スペクトルデータおよび格子点データの格納方法や変換の詳しい計算法に
!  ついては ISPACK3/LXPACK のマニュアルを参照されたい.
!
!  このモジュールを使うためには前もって w_base_initial を呼んで
!  切断波数, 格子点数の設定をしておく必要がある. 
!
!
!履歴  
!      2024/02/15  竹広真一  w_zonal_module_deriv より改造
!      2025/11/27  竹広真一  w_base_initial interface を変更
!
!      制限
!         ・変換する格子点データ, スペクトルデータの配列の大きさは決めうち
!
!++
module w_zonal_module_deriv
  !
  != w_zonal_module_deriv
  !
  !== 概要
  !
  !  spml/w_zonal_module_deriv モジュールは球面上での 2 次元
  !  流体運動を球面調和函数を用いたスペクトル法によって数値計算する
  !  ための モジュール w_zonal_module_ の下部モジュールであり, 
  !  スペクトル法の微分計算のための Fortran90 関数を提供する. 
  !
  !  内部で ISPACK3 の LXPACK の Fortran サブルーチンを呼んでいる. 
  !  スペクトルデータおよび格子点データの格納方法や変換の詳しい計算法に
  !  ついては ISPACK3/LXPACK のマニュアルを参照されたい.
  !
  !  このモジュールを使うためには前もって w_deriv_initial を呼んで
  !  切断波数, 格子点数の設定をしておく必要がある. 
  !
  use dc_message, only : MessageNotify
  use w_zonal_module_base, only : im, jm, nm=>nn, mm, l_nm, &
                                  w_base_Initial, xy_w, w_xy, w_DLon_w
  implicit none

  real(8), allocatable  :: D(:)
  ! ラプラシアン演算用配列
  !
  ! スペクトルデータのラプラシアンを計算するための係数
  ! 配列のサイズは(nm-m+1)
  !

  real(8), allocatable  :: rn(:,:)            
  ! ラプラシアン演算用配列(w_module と互換性を保つため)
  !
  ! スペクトルデータのラプラシアンを計算するための係数
  ! 配列のサイズは(nm+1, 2)
  !
  ! r(L,1) には L 番目の格納位置のスペクトルに対するラプラシアン計算の
  ! 係数 -n(n+1) の値が格納されている.
  !

  logical               :: w_zonal_deriv_initialize=.false.   ! 初期化フラッグ

  private

  public w_deriv_Initial                      ! 初期化
  public w_deriv_Finalize                     ! 終了処理

  public w_Lapla_w, w_LaplaInv_w              ! ラプラシアンと逆演算
  public xy_GradLon_w, xy_GradLat_w           ! 勾配型微分
  public w_DivLon_xy, w_DivLat_xy             ! 発散型微分
  public w_Div_xy_xy                          ! 発散型微分
!!$  public w_Jacobian_w_w                       ! ヤコビアン
  public xy_GradLambda_w, xy_GradMu_w         ! 勾配型微分(λ,μ座標)
  public w_DivLambda_xy, w_DivMu_xy           ! 発散型微分(λ,μ座標)
  public rn                                   ! ラプラシアン演算用配列

  save D, rn

contains

  !--------------- 初期化 -----------------
  subroutine w_deriv_initial
    !
    ! スペクトル微分計算に必要となる作業領域を設定する. 
    !
    ! 他の関数を呼ぶ前に, 最初にこのサブルーチンを呼んで
    ! 初期設定をしなければならない. 
    !
    ! このサブルーチンを単独で用いるのでなく, 
    ! 上位サブルーチン w_Initial を使用すること.
    !
    integer :: n

    w_zonal_deriv_initialize=.true.

    allocate(D(nm+1))
    allocate(rn(nm+1,2))

    do n=0,nm
       D(l_nm(n,0)) = -n*(n+1)
       rn(l_nm(n,0),1) = -n*(n+1)
       if ( n /= 0 ) then
          rn(l_nm(n,0),2) = -1.0D0/(n*(n+1))
       else
          rn(l_nm(n,0),2) = 0.0D0
       end if
    end do

    call MessageNotify('M','w_deriv_initial',&
         'w_zonal_module_deriv (2025/11/27) is initialized')

  end subroutine w_deriv_initial

  !--------------- 微分計算 -----------------
  function w_Lapla_w(w_data)
    !
    ! 入力スペクトルデータにラプラシアン
    !
    !    ▽^2 = 1/cos^2φ・∂^2/∂λ^2 + 1/cosφ・∂/∂φ(cosφ∂/∂φ)
    !
    ! を作用する(1 層用).
    !
    ! スペクトルデータのラプラシアンとは, 対応する格子点データに
    ! ラプラシアンを作用させたデータのスペクトル変換のことである. 
    !
    real(8)              :: w_Lapla_w(nm+1)
    !(out) 入力スペクトルデータのラプラシアン

    real(8), intent(in)  :: w_data(nm+1)
    !(in) 入力スペクトルデータ

    w_Lapla_w = D*w_Data

  end function w_Lapla_w

  function w_LaplaInv_w(w_data)
    !
    ! 入力スペクトルデータに逆ラプラシアン
    !
    !    ▽^{-2}
    !      =[1/cos^2φ・∂^2/∂λ^2 + 1/cosφ・∂/∂φ(cosφ∂/∂φ)]^{-1}
    !
    ! を作用する(1 層用).
    !
    ! スペクトルデータの逆ラプラシアンとは, 対応する格子点データに
    ! 逆ラプラシアンを作用させたデータのスペクトル変換のことである. 
    !
    real(8)              :: w_LaplaInv_w(nm+1)
    !(out) スペクトルデータの逆ラプラシアン

    real(8), intent(in)  :: w_data(nm+1)
    !(in) 入力スペクトルデータ

    w_LaplaInv_w = rn(:,2)*w_data

  end function w_LaplaInv_w

  function xy_GradLon_w(w_data)
    !
    ! スペクトルデータに勾配型経度微分 1/cosφ・∂/∂λ を
    ! 作用させた格子点データを返す(1 層用).
    !
    real(8)              :: xy_GradLon_w(0:im-1,1:jm)
    !(out) スペクトルデータを勾配型経度微分した格子点データ

    real(8), intent(in)  :: w_data(2*(nm+1))
    !(in) 入力スペクトルデータ

    xy_GradLon_w = xy_w(w_data,ipow=1,iflag=-1)

  end function xy_GradLon_w

  function xy_GradLat_w(w_data)
    !
    ! スペクトルデータに勾配型緯度微分 ∂/∂φ を作用させて
    ! 格子点データに変換して返す(1 層用).
    !
    real(8)              :: xy_GradLat_w(0:im-1,1:jm)
    !(out) スペクトルデータを勾配型緯度微分した格子点データ

    real(8), intent(in)  :: w_data(nm+1)
    !(in) 入力スペクトルデータ

    xy_GradLat_w = xy_w(w_data,ipow=1,iflag=1)

  end function xy_GradLat_w

  function w_DivLon_xy(xy_data)
    !
    ! 格子点データに発散型経度微分 1/cosφ・∂/∂λ を作用させて
    ! スペクトルデータに変換して返す(1 層用).
    !
    real(8)              :: w_DivLon_xy(nm+1)
    !(out) 格子点データを発散型経度微分したスペクトルデータ
    real(8), intent(in)  :: xy_data(0:im-1,1:jm)
    !(in) 入力格子点データ

    w_DivLon_xy = w_xy(xy_data,ipow=1,iflag=-1)

  end function w_DivLon_xy

  function w_DivLat_xy(xy_data)
    !
    ! 格子点データに発散型緯度微分 1/cosφ・∂(f cosφ)/∂φ を作用させて
    ! スペクトルデータに変換して返す(1 層用).
    !
    real(8)              :: w_DivLat_xy(nm+1)
    !(out) 格子点データを発散型緯度微分したスペクトルデータ

    real(8), intent(in)  :: xy_data(0:im-1,1:jm)
    !(in) 入力格子点データ

    w_DivLat_xy = w_xy(xy_data,ipow=1,iflag=1)

  end function w_DivLat_xy

  function w_Div_xy_xy(xy_u,xy_v)
    !
    ! 2 つの入力格子点データをベクトル成分とする発散を計算し, 
    ! スペクトルデータとして返す(1 層用).
    !
    real(8)              :: w_Div_xy_xy(nm+1)
    !(out) 2 つの入力格子点データをベクトル成分とする発散のスペクトルデータ

    real(8), intent(in)  :: xy_u(0:im-1,1:jm)
    !(in) ベクトル経度成分の格子点データ

    real(8), intent(in)  :: xy_v(0:im-1,1:jm)
    !(in) ベクトル緯度成分の格子点データ

    w_Div_xy_xy = w_Divlon_xy(xy_u) + w_Divlat_xy(xy_v)

  end function w_Div_xy_xy

!!$    function w_Jacobian_w_w(w_a,w_b)
!!$      ! 2 つのスペクトルデータにヤコビアン
!!$      !
!!$      !   J(f,g) = ∂f/∂λ・∂g/∂μ - ∂g/∂λ・∂f/∂μ
!!$      !          = ∂f/∂λ・1/cosφ・∂g/∂φ
!!$      !             - ∂g/∂λ・1/cosφ・∂f/∂φ
!!$      !
!!$      ! を作用させる(1 層用).
!!$
!!$      real(8)             :: w_Jacobian_w_w(2*(nm+1))
!!$      !(out) 2 つのスペクトルデータのヤコビアン
!!$
!!$      real(8), intent(in) :: w_a(2*(nm+1))
!!$      !(in) 1つ目の入力スペクトルデータ
!!$      
!!$      real(8), intent(in) :: w_b(2*(nm+1))
!!$      !(in) 2つ目の入力スペクトルデータ
!!$
!!$      w_Jacobian_w_w = w_xy( &
!!$                  xy_w(w_DLon_w(w_a))*xy_w(w_b,ipow=2,iflag=1) &
!!$                - xy_w(w_DLon_w(w_b))*xy_w(w_a,ipow=2,iflag=1) )
!!$
!!$    end function w_Jacobian_w_w

  !--------------- 微分計算 (λ,μ座標系用) -----------------
  function xy_GradLambda_w(w_data)
    !
    ! スペクトルデータに勾配型経度微分 ∂/∂λ を作用する(1 層用).
    !
    real(8)              :: xy_GradLambda_w(0:im-1,1:jm)
    !(out) スペクトルデータを勾配型経度微分した格子点データ

    real(8), intent(in)  :: w_data(nm+1)
    !(in) 入力スペクトルデータ

    xy_GradLambda_w = xy_w(w_data,ipow=0,iflag=-1)

  end function xy_GradLambda_w

  function xy_GradMu_w(w_data)
    !
    ! スペクトルデータに勾配型緯度微分 (1-μ^2)∂/∂μ  (μ=sinφ)
    ! を作用させて格子点データに変換して返す(1 層用).
    !
    real(8)              :: xy_GradMu_w(0:im-1,1:jm)
    !(out) スペクトルデータを勾配型緯度微分した格子点データ

    real(8), intent(in)  :: w_data(nm+1)
    !(in) 入力スペクトルデータ

    xy_GradMu_w = xy_w(w_data,ipow=0,iflag=1)

  end function xy_GradMu_w

  function w_DivLambda_xy(xy_data)
    !
    ! 格子点データに発散型経度微分 1/(1-μ^2)・∂/∂λ (μ=sinφ) 
    ! を作用させてスペクトルデータに変換して返す(1 層用).
    !
    real(8)              :: w_DivLambda_xy(nm+1)
    !(out) 格子点データを発散型経度微分したスペクトルデータ

    real(8), intent(in)  :: xy_data(0:im-1,1:jm)
    !(in) 入力格子点データ

    w_DivLambda_xy = w_xy(xy_data,ipow=2,iflag=-1)

  end function w_DivLambda_xy

  function w_DivMu_xy(xy_data)
    !
    ! 格子点データに発散型緯度微分 ∂/∂μ (μ=sinφ)を作用させて
    ! スペクトルデータに変換して返す(1 層用).
    !
    real(8)              :: w_DivMu_xy(nm+1)
    !(out) 格子点データを発散型緯度微分したスペクトルデータ

    real(8), intent(in)  :: xy_data(0:im-1,1:jm)
    !(in) 入力格子点データ

    w_DivMu_xy = w_xy(xy_data,ipow=2,iflag=1)

  end function w_DivMu_xy

  !--------------- 終了処理 -----------------
  subroutine w_deriv_finalize
    !
    ! モジュールの終了処理(割り付け配列の解放)をおこなう. 
    !
    ! このサブルーチンを単独で用いるのでなく, 
    ! 上位サブルーチン w_Finalize を使用すること.
    !
    if ( .not. w_zonal_deriv_initialize ) then
       call MessageNotify('W','w_deriv_Finalize',&
            'w_zonal_module_deriv_ not initialized yet')
       return
    endif

    deallocate(D)           ! ラプラシアン演算用配列
    deallocate(rn)          ! ラプラシアン演算用配列

    w_zonal_deriv_initialize = .false.

    call MessageNotify('M','w_deriv_Finalize',&
         'w_zonal_module_deriv (2025/11/27) is finalized')

  end subroutine w_deriv_finalize

end module w_zonal_module_deriv
