! -*- mode: f90; coding: utf-8 -*-
! ----------------------------------------------------------------------------
! Copyright (c) 2019-2026 SPMODEL Development Group. All rights reserved.
! ----------------------------------------------------------------------------
!> @author Shin-ichi Takehiro, Youhei SASAKI
!> @copyright Copyright (C) SPMODEL Development Group, 2019-2026
!>     License is MIT/X11. see [COPYRIGHT](@ref COPYRIGHT) in detail
!> @brief 1 次元周期境界: 実フーリエ変換
!>
!> @details
!> 1 次元周期境界条件の下での流体運動を
!> 実フーリエ変換によるスペクトル法で数値計算するための
!> Fortran90 関数を提供する.
!> 2 次元データの 1 次元に関して同時にスペクトル計算を実行するための
!> 関数も提供しており, 2, 3 次元領域での計算のベースも提供する.
!>
!> このモジュールは内部で ISPACK3/FXPACK のサブルーチンを呼んでいる.
!> スペクトルデータの格納方法については ISPACK3/FXPACK
!> と異なっているので以下のコメントに注意されたい.
!>
!> ## 関数・変数の名前と型について
!>
!> ### 命名法
!>
!> * 関数名の先頭 (`e_`, `g_`, `ae_`, `ag_`) は, 返す値の形を示している.
!> * `e_`  :: スペクトルデータ,
!> * `g_`  :: 1 次元格子点データ,
!> * `ae_` :: 1 次元スペクトルデータが複数並んだ 2 次元データ,
!> * `ag_` :: 1 次元格子点データが複数並んだ 2 次元データ.
!>
!> * 関数名の間の文字列(`Dx`)は, その関数の作用を表している.
!>
!> * 関数名の最後 (`_e`, `_ae`, `_g`, `_ag`) は,
!> 入力変数の形スペクトルデータおよび格子点データであることを示している.
!> * `_e`  :: スペクトルデータ
!> * `_g`  :: 1 次元格子点データ
!> * `_ae` :: 1 次元スペクトルデータが複数並んだ 2 次元データ
!> * `_ag` :: 1 次元格子点データが複数並んだ 2 次元データ
!>
!> ### 各データの種類の説明
!>
!> * 以下 `DP  = kind(1.0D)` である
!>
!> * `g` : 1 次元格子点データ.
!> * 変数の種類と次元は `real(DP), dimension(0:im-1)`.
!> * `im` は X 座標の格子点数であり, サブルーチン ae_initial() にて
!> あらかじめ設定しておく.
!>
!> * `e` : スペクトルデータ.
!> * 変数の種類と次元は `real(DP), dimension(-km:km)`.
!> * `km` は X 方向の最大波数であり, サブルーチン ae_initial() にて
!> あらかじめ設定しておく.
!> * スペクトルデータの格納のされ方は
!> `0:km` までが \f$\cos(kx)\f$ の係数,
!> `-km:-1` が  \f$sin(kx)\f$ の係数となっている.
!>
!> * `ag` : 1 次元(X)格子点データの並んだ 2 次元データ.
!> * 変数の種類と次元は `real(DP), dimension(:,0:im-1)`.
!> 第 2 次元が X 方向を表す.
!>
!> * `ae` : 1 次元スペクトルデータの並んだ 2 次元データ.
!> * 変数の種類と次元は `real(DP), dimension(:,-km:km)`.
!> 第 2 次元がスペクトルを表す.
!>
!> * `g_` で始まる関数が返す値は 1 次元格子点データに同じ.
!>
!> * `e_` で始まる関数が返す値はスペクトルデータに同じ.
!>
!> * `ag_` で始まる関数が返す値は 1 次元格子点データの並んだ
!> 2 次元データに同じ.
!>
!> * `ae_` で始まる関数が返す値は 1 次元スペクトルデータの並んだ
!> 2 次元データに同じ.
!>
!> * スペクトルデータに対する微分等の作用とは, 対応する格子点データに
!> 微分などを作用させたデータをスペクトル変換したもののことである.
!>
module ae_module
  use dc_message, only: MessageNotify
  use dc_types, only: DP
  use spml_utils, only: pi
  implicit none

  private
  public ae_Initial
  public ae_Initialize
  public ae_Finalize
  public ag_ae
  public ae_ag
  public g_e
  public e_g
  public ae_Dx_ae
  public e_Dx_e
  public a_Int_ag
  public Int_g
  public a_Avr_ag
  public Avr_g
  public Interpolate_e
  public a_Interpolate_ae
  public g_X
  public g_X_Weight

  ! --------------------------------------------------------------------------
  ! for backward compatibility
  interface ae_Initialize
    module procedure ae_Initialize_I4
    module procedure ae_Initialize_I8
  end interface ae_Initialize

  !> 格子点座標(X)を格納した1次元配列.
  real(DP), allocatable :: g_X(:)
  !> 重み座標を格納した 1 次元配列. X方向の格子点の間隔が格納してある.
  real(DP), allocatable :: g_X_Weight(:)

  integer(8) :: im     !> 格子点の数
  integer(8) :: km     !> 切断波数
  real(DP) :: xl       !> 領域の大きさ
  integer(8), allocatable :: it(:)
  real(DP), allocatable :: t(:)
  logical :: ae_Initialized = .false.

  save im, km, it, t, xl, g_X, g_X_Weight

  !> 作業領域/Workspace: ae_ag
  real(DP), allocatable, dimension(:, :) :: ag_work_ae_ag
  integer(8) :: ae_ag_im = -1_8, ae_ag_nm = -1_8
  save :: ag_work_ae_ag, ae_ag_im
  !$omp threadprivate(ag_work_ae_ag, ae_ag_im, ae_ag_nm)
  !> 作業領域: g_e
  real(DP), allocatable, dimension(:, :) :: ae_work_g_e
  real(DP), allocatable, dimension(:, :) :: ag_work_g_e
  integer(8) :: g_e_km = -1_8, g_e_im = -1_8
  save :: ae_work_g_e, ag_work_g_e, g_e_km, g_e_im
  !$omp threadprivate(ag_work_g_e, ae_work_g_e, g_e_km, g_e_im)
  !> 作業領域: e_g
  real(DP), allocatable, dimension(:, :) :: ag_work_e_g
  real(DP), allocatable, dimension(:, :) :: ae_work_e_g
  integer(8) :: e_g_km = -1_8, e_g_im = -1_8
  save :: ag_work_e_g, ae_work_e_g, e_g_im
  !$omp threadprivate(ag_work_e_g, ae_work_e_g, e_g_im)
  !> 作業領域: e_Dx_e
  real(DP), allocatable, dimension(:, :) :: ae_work_e_Dx_e
  integer(8) :: e_Dx_e_km = -1_8
  save :: ae_work_e_Dx_e, e_Dx_e_km
  !$omp threadprivate(ae_work_e_Dx_e, e_Dx_e_km)

contains
  ! --------------------------------------------------------------------------
  !> 関数名の変更
  subroutine ae_Initial(i, k, xmin, xmax)
    !> 格子点の数
    integer, intent(in) :: i
    !> 切断波数
    integer, intent(in) :: k
    !> X座標下端
    real(DP), intent(in) :: xmin
    !> X座標上端
    real(DP), intent(in) :: xmax

    call MessageNotify('W', 'ae_Initial', &
      & 'ae_Initial is deprecated; will be removed; use ae_Initialize.')

    call ae_Initialize_I8(int(i, kind(im)), int(k, kind(km)), xmin, xmax)

  end subroutine ae_Initial

  ! --------------------------------------------------------------------------
  !> 内部で利用する波数, 格子点数を Integer(8) としたため,
  !> 後方互換性を維持するために用意したインターフェース
  !> * Integer 用
  subroutine ae_Initialize_I4(i, k, xmin, xmax)
    !> 格子点の数
    integer, intent(in) :: i
    !> 切断波数
    integer, intent(in) :: k
    !> X座標下端
    real(DP), intent(in) :: xmin
    !> X座標上端
    real(DP), intent(in) :: xmax

    call MessageNotify('W', 'ae_Initialize', &
      & 'INTEGER(4) im/km deprecated; will be removed; use INTEGER(8).')

    call ae_Initialize_I8(int(i, kind(im)), int(k, kind(km)), xmin, xmax)

  end subroutine ae_Initialize_I4

  !-----------------------------------------------------------------------
  !> スペクトル変換の格子点数, 波数, 領域の大きさを設定する.
  !>
  !> 他の関数や変数を呼ぶ前に, 最初にこのサブルーチンを呼んで
  !> 初期設定をしなければならない.
  !>
  subroutine ae_Initialize_I8(i, k, xmin, xmax)
    !> 格子点の数
    integer(8), intent(in) :: i
    !> 切断波数
    integer(8), intent(in) :: k
    !> X座標下端
    real(DP), intent(in) :: xmin
    !> X座標上端
    real(DP), intent(in) :: xmax
    integer(8) :: ii

    im = i
    km = k
    xl = xmax - xmin

    if (im <= 0 .or. km <= 0) then
      call MessageNotify('E', 'ae_Initial', &
        & 'Number of grid points and waves should be positive')
    elseif (mod(im, 2_8) /= 0) then
      call MessageNotify('E', 'ae_Initial', &
        & 'Number of grid points should be even')
    elseif (km > im / 2) then
      call MessageNotify('E', 'ae_Initial', &
        & 'KM shoud be less equal to IM/2')
    end if

    allocate (it(im / 2_8), t(im * 3_8 / 2_8))

    call FXRINI(im, it, t)

    allocate (g_x(0:im - 1))
    do ii = 0, im - 1
      g_X(ii) = xmin + (xl / im) * ii
    end do

    allocate (g_x_Weight(0:im - 1))
    g_X_Weight = xl / im

    ae_Initialized = .true.

    call MessageNotify(&
      & 'M', 'ae_initial', 'ae_module (2026/01/30) is initialized')

  end subroutine ae_Initialize_I8

  !-----------------------------------------------------------------------
  !> モジュールの終了処理を行なう.
  !>
  subroutine ae_Finalize
    use omp_lib, only: omp_in_parallel
    if (allocated(it)) deallocate (it)
    if (allocated(t)) deallocate (t)
    if (allocated(g_X)) deallocate (g_X)
    if (allocated(g_X_Weight)) deallocate (g_X_Weight)

    if (omp_in_parallel()) then
      call ae_Finalize_tls()
    else
      !$omp parallel
      call ae_Finalize_tls()
      !$omp end parallel
    end if

    ae_Initialized = .false.

    call MessageNotify(&
      & 'M', 'ae_finalize', 'ae_module (2026/01/30) is finalized')
  end subroutine ae_Finalize
  ! --------------------------------------------------------------------------
  subroutine ae_Finalize_tls
    if (allocated(ag_work_ae_ag)) deallocate (ag_work_ae_ag)
    ae_ag_im = -1_8
    ae_ag_nm = -1_8
    if (allocated(ag_work_g_e)) deallocate (ag_work_g_e)
    if (allocated(ae_work_g_e)) deallocate (ae_work_g_e)
    g_e_km = -1_8
    g_e_im = -1_8
    if (allocated(ag_work_e_g)) deallocate (ag_work_e_g)
    if (allocated(ae_work_e_g)) deallocate (ae_work_e_g)
    e_g_km = -1_8
    e_g_im = -1_8
    if (allocated(ae_work_e_Dx_e)) deallocate (ae_work_e_Dx_e)
    e_Dx_e_km = -1_8
  end subroutine ae_Finalize_tls

  !-----------------------------------------------------------------------
  !> スペクトルデータから格子点データへ逆変換する(2 次元データ用)
  !>
  !> スペクトル逆変換の定義は以下のとおり:
  !> * スペクトルデータ: \f$e_k \, (k=-km,...,km)\f$
  !> * 格子点データ:     \f$g_j \, (j=-0,...,im-1)\f$
  !> \f{align*}{
  !>  g_j = e_0
  !>      + 2\sum_{k=1}^{km}e_k\cos(2\pi jk/im) - e_{-k}\sin(2\pi jk/im))
  !>                                               \quad (j=0,1,...,im-1).
  !> \f}
  function ag_ae(ae)
    !> スペクトルデータ
    real(DP), dimension(:, -km:), intent(in) :: ae
    !> 格子点データ
    real(DP), allocatable, dimension(:, :) :: ag_ae

    integer(8) :: n, nm, k, kmax

    if (size(ae, 2) < 2_8 * km + 1_8) then
      call MessageNotify('E', 'ag_ae', &
        & 'The Fourier dimension of input data too small.')
    elseif (size(ae, 2) > 2_8 * km + 1_8) then
      call MessageNotify('W', 'ag_ae', &
        & 'The Fourier dimension of input data too large.')
    end if
    nm = size(ae, 1)
    allocate (ag_ae(nm, 0:im - 1))

    ag_ae = 0.0_DP
    ag_ae(:, 0) = ae(:, 0)

    kmax = min(km, im / 2_8 - 1_8)
    do k = 1, kmax
      !$omp simd
      do n = 1, nm
        ag_ae(n, 2 * k) = ae(n, k)
        ag_ae(n, 2 * k + 1) = ae(n, -k)
      end do
    end do
    if (km == im / 2_8) then
      !$omp simd
      do n = 1, nm
        ag_ae(n, 1) = ae(n, km)
      end do
    end if

    call FXRTBA(nm, im, ag_ae, it, t)

  end function ag_ae

  ! ------------------------------------------------------------------
  !> スペクトルデータから格子点データへ逆変換する(1 次元データ用)
  !>
  !>  * スペクトルデータ: \f$e_k \, (k=-km,...,km)\f$
  !>  * 格子点データ:  \f$g_j \, (j=-0,...,im-1)\f$
  !> \f{align*}{
  !>  g_j = e_0
  !>      + 2\sum_{k=1}^{km}(e_k\cos(2\pi jk/im)
  !>      - e_{-k}\sin(2\pi jk/im))   \quad (j=0,1,...,im-1).
  !> \f}
  function g_e(e_data)
    !> スペクトルデータ
    real(DP), dimension(-km:km), intent(in) :: e_data
    !> 格子点データ
    real(DP), allocatable, dimension(:) :: g_e

    if (.not. allocated(ae_work_g_e) .or. g_e_km /= km) then
      if (allocated(ae_work_g_e)) deallocate (ae_work_g_e)
      allocate (ae_work_g_e(1, -km:km))
      g_e_km = km
    end if

    if (.not. allocated(ag_work_g_e) .or. g_e_im /= im) then
      if (allocated(ag_work_g_e)) deallocate (ag_work_g_e)
      allocate (ag_work_g_e(1, 0:im - 1))
      g_e_im = im
    end if

    allocate (g_e(0:im - 1))
    ae_work_g_e(1, :) = e_data
    ag_work_g_e = ag_ae(ae_work_g_e)
    g_e = ag_work_g_e(1, :)

  end function g_e

  !-----------------------------------------------------------------------
  !> 格子点データからスペクトルデータへ正変換する(2 次元データ用)
  !>
  !> スペクトル正変換の定義は以下のとおり:
  !> * 格子点データ     \f$g_j \, (j=-0,...,im-1)\f$
  !> * スペクトルデータ \f$e_k \, (k=-km,...,km)\f$
  !> \f{align*}{
  !>   e_0 & = (1/im)\sum_{j=0}^{im-1} g_j \\
  !>   e_k & = (1/im)\sum_{j=0}^{im-1} g_j \cos(2\pi jk/im)
  !>          \quad (k=1,2,...,km) \\
  !>   e_{-k} & = - (1/im)\sum_{j=0}^{im-1} g_j \sin(2\pi jk/im)
  !>          \quad (k=1,2,...,km)
  !> \f}
  function ae_ag(ag)
    !> 格子点データ
    real(DP), dimension(:, :), intent(in) :: ag
    !> スペクトルデータ
    real(DP), allocatable, dimension(:, :) :: ae_ag
    integer(8) :: n, nm, k, kmax

    if (size(ag, 2) < im) then
      call MessageNotify('E', 'ae_ag', &
        & 'The Grid points of input data too small.')
    elseif (size(ag, 2) > im) then
      call MessageNotify('W', 'ae_ag', &
        & 'The Grid points of input data too large.')
    end if

    nm = size(ag, 1)
    if (.not. allocated(ag_work_ae_ag)  &
      & .or. ae_ag_nm /= nm             &
      & .or. ae_ag_im /= im) then
      if (allocated(ag_work_ae_ag)) deallocate (ag_work_ae_ag)
      allocate (ag_work_ae_ag(nm, 0:im - 1))
      ae_ag_nm = nm
      ae_ag_nm = im
    end if

    allocate (ae_ag(nm, -km:km))
    ag_work_ae_ag = ag

    call FXRTFA(nm, im, ag_work_ae_ag, it, t)
    kmax = min(km, im / 2_8 - 1_8)
    do k = 1, kmax
      !$omp simd
      do n = 1, nm
        ae_ag(n, k) = ag_work_ae_ag(n, 2 * k)
        ae_ag(n, -k) = ag_work_ae_ag(n, 2 * k + 1)
      end do
    end do
    ae_ag(:, 0) = ag_work_ae_ag(:, 0)
    if (km == im / 2_8) then
      !$omp simd
      do n = 1, nm
        ae_ag(n, km) = ag_work_ae_ag(n, 1)
      end do
    end if

  end function ae_ag

  !-----------------------------------------------------------------------
  !> 格子点データからスペクトルデータへ正変換する(1 次元データ用)
  !>
  !> スペクトル正変換の定義は以下のとおり:
  !> * 格子点データ:     \f$g_j \, (j=-0,...,im-1)\f$
  !> * スペクトルデータ: \f$e_k \, (k=-km,...,km)\f$
  !> \f{align*}{
  !>  e_0    & = (1/im)\sum_{j=0}^{im-1} g_j \\
  !>  e_k    & = (1/im)\sum_{j=0}^{im-1} g_j \cos(2\pi jk/im)
  !>             \quad (k=1,2,...,km) \\
  !>  e_{-k} & = - (1/im)\sum_{j=0}^{im-1} g_j \sin(2\pi jk/im)
  !>              \quad (k=1,2,...,km)
  !> \f}
  function e_g(g_data)
    !> 格子点データ
    real(DP), dimension(0:im - 1), intent(in) :: g_data
    !> スペクトルデータ
    real(DP), allocatable, dimension(:) :: e_g

    if (.not. allocated(ag_work_e_g) .or. e_g_im /= im) then
      if (allocated(ag_work_e_g)) deallocate (ag_work_e_g)
      allocate (ag_work_e_g(1, 0:im - 1))
      e_g_im = im
    end if
    if (.not. allocated(ag_work_e_g) .or. e_g_km /= km) then
      if (allocated(ae_work_e_g)) deallocate (ae_work_e_g)
      allocate (ae_work_e_g(1, -km:km))
    end if

    allocate (e_g(-km:km))

    ag_work_e_g(1, :) = g_data
    ae_work_e_g = ae_ag(ag_work_e_g)
    e_g = ae_work_e_g(1, :)

  end function e_g

  !-----------------------------------------------------------------------
  !> 入力スペクトルデータに X 微分を作用する(2 次元データ).
  !>
  !> スペクトルデータの X 微分とは, 対応する格子点データに X 微分を
  !> 作用させたデータのスペクトル変換のことである.
  function ae_Dx_ae(ae_data)
    !> 入力スペクトルデータ
    real(DP), dimension(:, -km:), intent(in) :: ae_data
    !> 入力スペクトルデータの X 微分
    real(DP), allocatable, dimension(:, :) :: ae_Dx_ae

    integer(8) :: k
    real(DP) :: coef

    if (size(ae_data, 2) < 2_8 * km + 1_8) then
      call MessageNotify('W', 'ae_Dx_ae', &
                         'The Fourier dimension of input data too small.')
    elseif (size(ae_data, 2) > 2_8 * km + 1_8) then
      call MessageNotify('W', 'ae_Dx_ae', &
                         'The Fourier dimension of input data too large.')
    end if
    allocate (ae_Dx_ae(size(ae_data, 1), -km:km))
    coef = 2.0_DP * PI * (1 / xl)

    do k = -km, km
      ae_Dx_ae(:, k) = -(coef * k) * ae_data(:, -k)
    end do

  end function ae_Dx_ae

  !-----------------------------------------------------------------------
  !> 入力スペクトルデータに X 微分を作用する(1 次元データ).
  !>
  !> スペクトルデータの X 微分とは, 対応する格子点データに X 微分を
  !> 作用させたデータのスペクトル変換のことである.
  function e_Dx_e(e_data)
    !> 入力スペクトルデータ
    real(DP), dimension(-km:km), intent(in) :: e_data
    !> 入力スペクトルデータの X 微分
    real(DP), allocatable, dimension(:) :: e_Dx_e

    if (.not. allocated(ae_work_e_Dx_e) .or. e_Dx_e_km /= km) then
      if (allocated(ae_work_e_Dx_e)) deallocate (ae_work_e_Dx_e)
      allocate (ae_work_e_Dx_e(1, -km:km))
      e_Dx_e_km = km
    end if

    allocate (e_Dx_e(-km:km))
    ae_work_e_Dx_e(1, :) = e_data
    ae_work_e_Dx_e = ae_Dx_ae(ae_work_e_Dx_e)
    e_Dx_e = ae_work_e_Dx_e(1, :)

  end function e_Dx_e

  !-----------------------------------------------------------------------
  !> 1 次元格子点データが並んだ 2 次元配列の積分
  function a_Int_ag(ag)
    !> 格子点データ
    real(DP), dimension(:, 0:), intent(in) :: ag
    !> 積分結果
    real(DP), allocatable, dimension(:) :: a_Int_ag

    integer(8) :: i, n, nm
    real(DP) :: wi

    if (size(ag, 2) < im) then
      call MessageNotify('E', 'ae_Int_ag', &
                         'The Grid points of input data too small.')
    elseif (size(ag, 2) > im) then
      call MessageNotify('W', 'ae_Int_ag', &
                         'The Grid points of input data too large.')
    end if

    nm = size(ag, 1)
    allocate (a_Int_ag(nm))
    a_Int_ag = 0.0_DP
    do i = 0, im - 1
      wi = g_X_Weight(i)
      !$omp simd
      do n = 1, nm
        a_Int_ag(n) = a_Int_ag(n) + ag(n, i) * wi
      end do
    end do

  end function a_Int_ag

  !-----------------------------------------------------------------------
  !> 1 次元格子点データの積分
  function Int_g(g)
    !> 格子点データ
    real(DP), dimension(0:im - 1), intent(in) :: g
    !> 積分結果
    real(DP) :: Int_g

    Int_g = sum(g * g_X_Weight)

  end function Int_g

  !-----------------------------------------------------------------------
  !> 1 次元格子点データが並んだ 2 次元配列の平均
  function a_Avr_ag(ag)
    !> 格子点データ
    real(DP), dimension(:, 0:), intent(in) :: ag
    !> 平均結果
    real(DP), allocatable, dimension(:) :: a_Avr_ag

    allocate (a_Avr_ag(size(ag, 1)))

    a_Avr_ag = a_Int_ag(ag) / sum(g_X_Weight)

  end function a_Avr_ag

  !-----------------------------------------------------------------------
  !> 1 次元格子点データの平均
  function Avr_g(g)
    !> 格子点データ
    real(DP), dimension(0:im - 1), intent(in) :: g
    !> 平均値
    real(DP) :: Avr_g

    Avr_g = Int_g(g) / sum(g_X_Weight)

  end function Avr_g

  !-----------------------------------------------------------------------
  !> 補間計算(1次元)
  function Interpolate_e(e_data, xval)
    !> 入力スペクトルデータ
    real(DP), dimension(-km:km), intent(IN) :: e_data
    !> 補間する点の座標
    real(DP), intent(in) :: xval
    !> 補間した結果の値
    real(DP) :: Interpolate_e

    integer(8) :: k
    real(DP) :: coef, ck, sk

    coef = 2.0_DP * PI * (1 / xl) * xval
    Interpolate_e = e_data(0)

    !$omp simd reduction(+:Interpolate_e)
    do k = 1, km
      ck = cos(coef * k)
      sk = sin(coef * k)
      Interpolate_e = Interpolate_e        &
        &  + 2.0_DP * (e_data(k) * ck  &
        &               - e_data(-k) * sk)
    end do

  end function Interpolate_e

  !-----------------------------------------------------------------------
  !> 補間計算(2次元)
  function a_Interpolate_ae(ae_data, xval)
    !> 入力スペクトルデータ
    real(DP), dimension(:, -km:), intent(IN) :: ae_data
    !> 補間する点の座標
    real(DP), intent(in) :: xval
    !> 補間した結果の値
    real(DP), allocatable, dimension(:) :: a_Interpolate_ae

    integer(8) :: k, i, imax
    real(DP) :: coef, ck, sk

    imax = size(ae_data, 1)
    allocate (a_Interpolate_ae(imax))
    coef = 2.0_DP * PI * (1 / xl) * xval

    a_Interpolate_ae = ae_data(:, 0)
    do k = 1, km
      ck = cos(coef * k)
      sk = sin(coef * k)
      !$omp simd
      do i = 1, imax
        a_Interpolate_ae(i) = a_Interpolate_ae(i)  &
          &  + 2.0_DP * (ae_data(i, k) * ck     &
          &               - ae_data(i, -k) * sk)
      end do
    end do

  end function a_Interpolate_ae

end module ae_module
