main Program

Uses

  • program~~main~8~~UsesGraph program~main~8 main module~leapblock leapBlock program~main~8->module~leapblock module~leapeulerian leapEulerian program~main~8->module~leapeulerian module~leapkinds leapKinds program~main~8->module~leapkinds module~leapparallel leapParallel program~main~8->module~leapparallel module~leapparser leapParser program~main~8->module~leapparser module~leapblock->module~leapkinds module~leapblock->module~leapparallel iso_fortran_env iso_fortran_env module~leapblock->iso_fortran_env module~leapio_hdf5 leapIO_hdf5 module~leapblock->module~leapio_hdf5 mpi_f08 mpi_f08 module~leapblock->mpi_f08 module~leapeulerian->module~leapblock module~leapeulerian->module~leapkinds module~leapeulerian->module~leapparallel module~leapeulerian->iso_fortran_env module~leapio leapIO module~leapeulerian->module~leapio module~leaputils leapUtils module~leapeulerian->module~leaputils module~leapeulerian->mpi_f08 module~leapkinds->iso_fortran_env module~leapparallel->module~leapkinds module~leapparallel->iso_fortran_env module~leapparallel->mpi_f08 module~leapparser->module~leapkinds module~leapparser->iso_fortran_env module~leapcli leapCli module~leapparser->module~leapcli module~leapcli->module~leapkinds module~leapio->module~leapio_hdf5 module~leapio_h5hut leapIO_h5hut module~leapio->module~leapio_h5hut module~leapio_silo leapIO_silo module~leapio->module~leapio_silo module~leapio_xdmf leapIO_xdmf module~leapio->module~leapio_xdmf module~leapio_hdf5->module~leapkinds module~leapio_hdf5->module~leapparallel module~leapio_hdf5->module~leaputils hdf5 hdf5 module~leapio_hdf5->hdf5 module~leaputils->module~leapkinds module~leapio_h5hut->module~leapkinds module~leapio_h5hut->module~leapparallel module~leapio_h5hut->module~leapio_hdf5 module~leapio_silo->module~leapkinds module~leapio_silo->module~leapparallel module~leapio_silo->module~leaputils module~leapio_silo->mpi_f08 module~leapio_xdmf->module~leapkinds module~leapio_xdmf->module~leaputils

Solver: CDIFS

Description: Generates synthetic homogeneous isotropic turbulence using the following energy spectrum: E(k) = A k^4 exp(-2(k/kp)^2) where A is an amplitude determined from the box size and target Taylor-microscale Reynolds number, and kp is the peak wavenumber in the box (associated by default to mode 4).

NOTE: Due to FFTW constraints, this initialization must be performed in serial.

References: R. S. Rogallo, Numerical experiments in homogeneous turbulence, NASA STI/Recon Technical Report N 81, 31508 (1981).

Rosales, C. & Meneveau, C. Linear forcing in numerical simulations of isotropic turbulence: Physical space implementations and convergence properties. Physics of Fluids 17, 095106 (2005).



Calls

program~~main~8~~CallsGraph program~main~8 main proc~block_obj_final block_obj%block_obj_Final program~main~8->proc~block_obj_final proc~parser_obj_init parser_obj%parser_obj_init program~main~8->proc~parser_obj_init proc~parser_obj_parsefile parser_obj%parser_obj_ParseFile program~main~8->proc~parser_obj_parsefile proc~setupcasebcs~8 SetUpCaseBCS program~main~8->proc~setupcasebcs~8 proc~setupcaseblock~8 SetUpCaseBlock program~main~8->proc~setupcaseblock~8 proc~setupcasefields~4 SetUpCaseFields program~main~8->proc~setupcasefields~4 mpi_type_free mpi_type_free proc~block_obj_final->mpi_type_free proc~axis_obj_final axis_obj%axis_obj_Final proc~block_obj_final->proc~axis_obj_final proc~cli_obj_get cli_obj%cli_obj_Get proc~parser_obj_parsefile->proc~cli_obj_get proc~parser_obj_parseline parser_obj%parser_obj_ParseLine proc~parser_obj_parsefile->proc~parser_obj_parseline proc~bc_set_final bc_set%bc_set_Final proc~setupcasebcs~8->proc~bc_set_final proc~bc_set_init bc_set%bc_set_Init proc~setupcasebcs~8->proc~bc_set_init none~get parser_obj%Get proc~setupcaseblock~8->none~get none~initialize~10 block_obj%Initialize proc~setupcaseblock~8->none~initialize~10 proc~block_obj_partition block_obj%block_obj_Partition proc~setupcaseblock~8->proc~block_obj_partition proc~block_obj_setperiodicity block_obj%block_obj_SetPeriodicity proc~setupcaseblock~8->proc~block_obj_setperiodicity proc~block_obj_setupuniformgrid block_obj%block_obj_SetupUniformGrid proc~setupcaseblock~8->proc~block_obj_setupuniformgrid proc~setupcasefields~4->none~get proc~buildfouriermodes BuildFourierModes proc~setupcasefields~4->proc~buildfouriermodes proc~eulerian_set_add eulerian_set%eulerian_set_Add proc~setupcasefields~4->proc~eulerian_set_add proc~eulerian_set_final eulerian_set%eulerian_set_Final proc~setupcasefields~4->proc~eulerian_set_final proc~eulerian_set_init eulerian_set%eulerian_set_Init proc~setupcasefields~4->proc~eulerian_set_init proc~eulerian_set_setwritefilename eulerian_set%eulerian_set_SetWriteFileName proc~setupcasefields~4->proc~eulerian_set_setwritefilename proc~invertfouriermodes InvertFourierModes proc~setupcasefields~4->proc~invertfouriermodes proc~parser_obj_read0d parser_obj%parser_obj_read0D none~get->proc~parser_obj_read0d proc~parser_obj_read1d parser_obj%parser_obj_read1D none~get->proc~parser_obj_read1d proc~block_obj_init block_obj%block_obj_Init none~initialize~10->proc~block_obj_init proc~block_obj_init2 block_obj%block_obj_Init2 none~initialize~10->proc~block_obj_init2 proc~region_obj_final region_obj%region_obj_Final proc~bc_set_final->proc~region_obj_final proc~hashtbl_obj_init hashtbl_obj%hashtbl_obj_Init proc~bc_set_init->proc~hashtbl_obj_init proc~block_obj_partition->proc~axis_obj_final proc~axis_obj_init axis_obj%axis_obj_Init proc~block_obj_partition->proc~axis_obj_init proc~block_obj_setconveniencepointers block_obj%block_obj_SetConveniencePointers proc~block_obj_partition->proc~block_obj_setconveniencepointers proc~block_obj_setupmpitypes block_obj%block_obj_SetupMPITypes proc~block_obj_partition->proc~block_obj_setupmpitypes proc~block_obj_subdivideblock block_obj%block_obj_SubDivideBlock proc~block_obj_partition->proc~block_obj_subdivideblock proc~block_obj_updategridghostcells block_obj%block_obj_UpdateGridGhostCells proc~block_obj_partition->proc~block_obj_updategridghostcells proc~block_obj_updatemidpoints block_obj%block_obj_UpdateMidPoints proc~block_obj_partition->proc~block_obj_updatemidpoints proc~block_obj_updatespacing block_obj%block_obj_UpdateSpacing proc~block_obj_partition->proc~block_obj_updatespacing proc~parallel_obj_topology parallel_obj%parallel_obj_Topology proc~block_obj_partition->proc~parallel_obj_topology proc~block_obj_setupuniformgrid->proc~axis_obj_init proc~block_obj_setupuniformgrid->proc~block_obj_setconveniencepointers proc~block_obj_setupuniformgrid->proc~block_obj_setupmpitypes proc~block_obj_setupuniformgrid->proc~block_obj_updategridghostcells proc~block_obj_setupuniformgrid->proc~block_obj_updatemidpoints proc~block_obj_setupuniformgrid->proc~block_obj_updatespacing proc~getenergyspectrum GetEnergySpectrum proc~buildfouriermodes->proc~getenergyspectrum proc~eulerian_obj_init eulerian_obj_base%eulerian_obj_Init proc~eulerian_set_add->proc~eulerian_obj_init proc~hashtbl_obj_hashstring hashtbl_obj%hashtbl_obj_HashString proc~eulerian_set_add->proc~hashtbl_obj_hashstring proc~hashtbl_obj_put hashtbl_obj%hashtbl_obj_Put proc~eulerian_set_add->proc~hashtbl_obj_put proc~eulerian_obj_final eulerian_obj_base%eulerian_obj_Final proc~eulerian_set_final->proc~eulerian_obj_final proc~eulerian_set_init->proc~hashtbl_obj_init dfftw_destroy_plan dfftw_destroy_plan proc~invertfouriermodes->dfftw_destroy_plan proc~parser_obj_addentry parser_obj%parser_obj_AddEntry proc~parser_obj_parseline->proc~parser_obj_addentry proc~parser_obj_fetchlabelid parser_obj%parser_obj_FetchLabelID proc~parser_obj_parseline->proc~parser_obj_fetchlabelid proc~parser_obj_reformatline parser_obj%parser_obj_ReformatLine proc~parser_obj_parseline->proc~parser_obj_reformatline proc~block_obj_init2->proc~block_obj_setupuniformgrid proc~block_obj_setupmpitypes->mpi_type_free mpi_type_commit mpi_type_commit proc~block_obj_setupmpitypes->mpi_type_commit mpi_type_vector mpi_type_vector proc~block_obj_setupmpitypes->mpi_type_vector mpi_irecv mpi_irecv proc~block_obj_updategridghostcells->mpi_irecv mpi_isend mpi_isend proc~block_obj_updategridghostcells->mpi_isend mpi_wait mpi_wait proc~block_obj_updategridghostcells->mpi_wait mpi_waitall mpi_waitall proc~block_obj_updategridghostcells->mpi_waitall proc~block_obj_updateextents block_obj%block_obj_UpdateExtents proc~block_obj_updategridghostcells->proc~block_obj_updateextents proc~sllist_obj_put sllist_obj%sllist_obj_Put proc~hashtbl_obj_put->proc~sllist_obj_put mpi_cart_coords mpi_cart_coords proc~parallel_obj_topology->mpi_cart_coords mpi_cart_create mpi_cart_create proc~parallel_obj_topology->mpi_cart_create mpi_cart_rank mpi_cart_rank proc~parallel_obj_topology->mpi_cart_rank mpi_cart_shift mpi_cart_shift proc~parallel_obj_topology->mpi_cart_shift mpi_comm_rank mpi_comm_rank proc~parallel_obj_topology->mpi_comm_rank mpi_dims_create mpi_dims_create proc~parallel_obj_topology->mpi_dims_create proc~parser_obj_read0d->proc~parser_obj_fetchlabelid none~assigndefault parser_obj%AssignDefault proc~parser_obj_read0d->none~assigndefault proc~parser_obj_read1d->proc~parser_obj_fetchlabelid proc~parser_obj_read1d->none~assigndefault proc~hashtbl_obj_final hashtbl_obj%hashtbl_obj_Final proc~region_obj_final->proc~hashtbl_obj_final proc~parser_obj_assigndefault0d parser_obj%parser_obj_AssignDefault0D none~assigndefault->proc~parser_obj_assigndefault0d proc~parser_obj_assigndefault1d parser_obj%parser_obj_AssignDefault1D none~assigndefault->proc~parser_obj_assigndefault1d proc~block_obj_updateextents->proc~axis_obj_init proc~sllist_obj_put->proc~sllist_obj_put

Variables

Type Attributes Name Initial
integer :: Ng(3)

Global number of grid points in each direction

type(block_obj) :: block

Block object manages the Cartesian grid

type(parallel_obj) :: parallel

Utility that handles parallel (MPI) functions

type(parser_obj) :: parser

Utility that parses input files


Functions

function GetEnergySpectrum(kmag, kp, A) result(res)

Computes E(k)

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: kmag

Wavevector magnitude

real(kind=wp), intent(in) :: kp

Peak

real(kind=wp), intent(in) :: A

Spectrum amplitude

Return Value real(kind=wp)

Result


Subroutines

subroutine BuildFourierModes(Vrms, peak_mode, Nk, Vk)

Builds the Fourier modes, scaled with the provided energy spectrum. Note that Since the FFT of a real field satisfies the Hermitian symmetry, one needs to describe only half of the wavenumber space. Direction 1 is truncated in half to conform with FFTW.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: Vrms

Wavevector magnitude

integer, intent(in) :: peak_mode

Peak

integer, intent(out) :: Nk(3)

Size of fourier modes

complex(kind=wp), intent(out), allocatable :: Vk(:,:,:,:)

Fourier modes

subroutine InvertFourierModes(Nk, Vk, V)

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: Nk(3)

Size of fourier modes

complex(kind=wp), intent(in) :: Vk(:,:,:,:)

Fourier modes

type(eulerian_obj_r), intent(inout) :: V(3)

subroutine SetUpCaseBCS()

Defines boundary conditions.

Arguments

None

subroutine SetUpCaseBlock()

Builds and writes block file.

Arguments

None

subroutine SetUpCaseFields()

Builds and writes initial fields.

Arguments

None

Source Code

program main
  !>--------------------------------------------------------------------------
  ! Program: Homogeneous Isotropic Turbulence
  ! Author: Mohamed Houssem Kasbaoui
  !
  ! Solver: CDIFS
  !
  ! Description: Generates synthetic homogeneous isotropic turbulence using
  ! the following energy spectrum:
  !     E(k) = A k^4 exp(-2(k/kp)^2)
  ! where A is an amplitude determined from the box size and target
  ! Taylor-microscale Reynolds number, and kp is the peak wavenumber in
  ! the box (associated by default to mode 4).
  !
  ! NOTE: Due to FFTW constraints, this initialization must be performed
  ! in serial.
  !
  ! References:
  ! R. S. Rogallo, Numerical experiments in homogeneous turbulence,
  ! NASA STI/Recon Technical Report N 81, 31508 (1981).
  !
  ! Rosales, C. & Meneveau, C. Linear forcing in numerical simulations of
  ! isotropic turbulence: Physical space implementations and convergence
  ! properties. Physics of Fluids 17, 095106 (2005).
  ! --------------------------------------------------------------------------
  use leapKinds
  use leapParser
  use leapParallel
  use leapBlock
  use leapEulerian
  implicit none
  type(parallel_obj) :: parallel                                               !! Utility that handles parallel (MPI) functions
  type(parser_obj)   :: parser                                                 !! Utility that parses input files
  type(block_obj)    :: block                                                  !! Block object manages the Cartesian grid
  integer            :: Ng(3)                                                  !! Global number of grid points in each direction


  ! Initialize parser
  call parser%Initialize()

  ! Parse input file
  call parser%ParseFile()

  ! Initialize parallel environment
  call parallel%Initialize()

  if (parallel%nproc.ne.1) &
    call parallel%Stop("HIT initialization must be done in serial")

  ! Set the block info
  call SetUpCaseBlock()

  ! Set the initial fields
  call SetUpCaseFields()

  ! Set boundary conditions
  call SetUpCaseBCS()

  ! Free up data
  call block%Finalize()
  call parser%Finalize()
  call parallel%Finalize()
  contains
    subroutine SetUpCaseBlock()
      !> Builds and writes block file.
      implicit none
      ! Work variables
      character(str64) :: filename
      real(wp)         :: L(3)
      integer          :: ngc
      integer          :: Nb(3)
      real(wp)         :: xlo(3)
      real(wp)         :: xhi(3)
      integer          :: ilo(3)
      integer          :: ihi(3)

      ! Get info from parser
      call parser%Get("Block file",  filename)
      call parser%Get("Domain size", L       )
      call parser%Get("Grid points", Ng      )
      call parser%Get("Ghost cells", ngc     )
      call parser%Get("Partition",   Nb      )

      ! Domain extents
      xlo=-0.5_wp*L ; xhi= 0.5_wp*L
      ilo=[1,1,1] ; ihi=Ng

      ! Initialize the main block
      call block%Initialize(ngc,parallel)

      ! Setup the domain periodicity
      call block%SetPeriodicity([.true.,.true.,.true.])

      ! Create a uniform block
      call block%SetupUniformGrid(xlo,xhi,ilo,ihi)

      ! Partition block for parallel initializations
      call block%Partition(Nb)

      ! Write block to disk
      call block%Write(filename)

      return
    end subroutine SetUpCaseBlock
    subroutine SetUpCaseFields()
      !> Builds and writes initial fields.
      use iso_fortran_env, only : stdout => output_unit
      implicit none
      ! Work variables
      type(Eulerian_set)   :: fields
      type(eulerian_obj_r) :: V(3)
      type(eulerian_obj_r) :: P
      character(str64)     :: filename
      real(wp)             :: rho
      real(wp)             :: mu
      real(wp)             :: nu
      real(wp)             :: dissipation_rate
      real(wp)             :: Re_lambda
      integer              :: peak_mode
      real(wp)             :: ell
      real(wp)             :: L(3)
      integer              :: Nk(3)
      complex(wp),          &
               allocatable :: Vk(:,:,:,:)
      real(wp)             :: lambda
      real(wp)             :: Vrms
      real(wp)             :: eta_k
      real(wp)             :: dV
      integer              :: i,j,k
      real(wp), parameter  :: C=0.19_wp

      ! Get info from parser
      call parser%Get("Fields IC file",   filename               )
      call parser%Get("Fluid density",    rho                    )
      call parser%Get("Fluid viscosity",  mu                     )
      call parser%Get("Reynolds number",  Re_lambda              )
      call parser%Get("Peak mode",        peak_mode, default = 4 )

      ! Initialize fields container
      call fields%Initialize(block,parallel)

      ! Add fields to container (this will allocate data)
      call fields%Add('V1', 1, V(1))
      call fields%Add('V2', 2, V(2))
      call fields%Add('V3', 3, V(3))
      call fields%Add('P',  0, P   )

      ! Kinematic viscosity
      nu = mu/rho

      ! Domain size
      L = block%pmax - block%pmin

      ! Integral length scale based on Rosales & Meneveau, Physics of Fluids, 2005
      ell = C*minval(L)

      ! Dissipation rate
      dissipation_rate = (nu/15.0_wp)**3*Re_lambda**6/ell**4

      ! Velocity fluctuation rms
      Vrms = (dissipation_rate*ell)**(1.0_wp/3.0_wp)

      call BuildFourierModes(Vrms,peak_mode,Nk,Vk)
      call InvertFourierModes(Nk,Vk,V)

      deallocate(Vk)

      ! Rescale
      Vrms = 0.0_wp
      do k=block%lo(3),block%hi(3)
        do j=block%lo(2),block%hi(2)
          do i=block%lo(1),block%hi(1)
            dV = (block%xm(i+1)-block%xm(i)) &
               * (block%ym(j+1)-block%ym(j)) &
               * (block%zm(k+1)-block%zm(k))
            Vrms = Vrms +(V(1)%cell(i,j,k)**2 &
                        + V(2)%cell(i,j,k)**2 &
                        + V(3)%cell(i,j,k)**2)*dV
          end do
        end do
      end do
      Vrms = sqrt((1.0_wp/3.0_wp)/(L(1)*L(2)*L(3))*Vrms)

      V(1)%cell = V(1)%cell* sqrt(nu*Re_lambda/ sqrt(15.0_wp*nu/dissipation_rate))/Vrms
      V(2)%cell = V(2)%cell* sqrt(nu*Re_lambda/ sqrt(15.0_wp*nu/dissipation_rate))/Vrms
      V(3)%cell = V(3)%cell* sqrt(nu*Re_lambda/ sqrt(15.0_wp*nu/dissipation_rate))/Vrms

      ! Compute urms
      Vrms = 0.0_wp
      do k=block%lo(3),block%hi(3)
        do j=block%lo(2),block%hi(2)
          do i=block%lo(1),block%hi(1)
            dV = (block%xm(i+1)-block%xm(i)) &
               * (block%ym(j+1)-block%ym(j)) &
               * (block%zm(k+1)-block%zm(k))
            Vrms = Vrms +(V(1)%cell(i,j,k)**2 &
                        + V(2)%cell(i,j,k)**2 &
                        + V(3)%cell(i,j,k)**2)*dV
          end do
        end do
      end do
      Vrms = sqrt((1.0_wp/3.0_wp)/(L(1)*L(2)*L(3))*Vrms)

      lambda    = sqrt(15.0_wp*nu/dissipation_rate)*Vrms
      Re_lambda = lambda*Vrms/nu
      eta_k     = (nu**0.75_wp)/(dissipation_rate**0.25_wp)

      write(stdout,*) "Taylor Microscale Reynolds : ", Re_lambda
      write(stdout,*) "Velocity fluctuation rms   : ", Vrms
      write(stdout,*) "Taylor microscale (lambda) : ", lambda
      write(stdout,*) "Kolomogorov length (eta_k) : ", eta_k
      write(stdout,*) "Dissipation rate           : ", dissipation_rate
      write(stdout,*) "Eddy turounover time       : ", Vrms**2/dissipation_rate
      write(stdout,*) "HIT linear forcing         : ", dissipation_rate/(3.0_wp*Vrms**2)
      write(stdout,*) "Resolution (kmax*eta_k)    : ", 4.0_wp*atan(1.0_wp)*minval(Ng/L)*eta_k


      ! Write data to disk
      call fields%SetWriteFileName(filename)
      call fields%Write(0,0.0_wp)

      ! Clear data
      call fields%Finalize()

      return
    end subroutine SetUpCaseFields
    subroutine SetUpCaseBCS()
      !> Defines boundary conditions.
      use leapBC
      implicit none
      ! Work variables
      type(bc_set) :: bcs

      ! Initialize utility that handles boundary conditions
      call bcs%Initialize(block,parallel)

      ! Fully-periodic, nothing to do

      ! Write boundary conditions
      call bcs%Write(0,0.0_wp)

      ! Clear data
      call bcs%Finalize()

      return
    end subroutine SetUpCaseBCS
    function GetEnergySpectrum(kmag,kp,A) result (res)
      !> Computes E(k)
      implicit none
      real(wp), intent(in) :: kmag                                             !! Wavevector magnitude
      real(wp), intent(in) :: kp                                               !! Peak
      real(wp), intent(in) :: A                                                !! Spectrum amplitude
      real(wp)             :: res                                              !! Result

      res = A*(kmag**4)*exp(-2.0_wp*(kmag/kp)**2)

      return
    end function GetEnergySpectrum
    subroutine BuildFourierModes(Vrms,peak_mode,Nk,Vk)
      !> Builds the Fourier modes, scaled with the provided
      ! energy spectrum.
      ! Note that Since the FFT of a real field satisfies the Hermitian
      ! symmetry, one needs to describe only half of the wavenumber space.
      ! Direction 1 is truncated in half to conform with FFTW.
      implicit none
      real(wp),     intent(in)  :: Vrms                                        !! Wavevector magnitude
      integer,      intent(in)  :: peak_mode                                   !! Peak
      integer,      intent(out) :: Nk(3)                                       !! Size of fourier modes
      complex(wp), allocatable,  &
                    intent(out) :: Vk(:,:,:,:)                                 !! Fourier modes
      ! Work variables
      real(wp)             :: A
      real(wp)             :: kp
      real(wp)             :: Ek
      real(wp)             :: kmag
      real(wp)             :: phi
      real(wp)             :: kappa(3)
      real(wp)             :: rand(3)
      real(wp)             :: dk(3)
      integer              :: mi,mj,mk
      real(wp),  parameter :: Pi = 4.0_wp*atan(1.0_wp)
      complex(wp),          &
                 parameter :: ii = (0.0_wp,1.0_wp)

      ! Infinitesimal wavenumbers
      dk = 2.0_wp*Pi/(block%pmax-block%pmin)

      ! Peak wavenumber
      kp = real(peak_mode,wp)*maxval(dk)

      ! Spectrum prefactor
      A = 16.0_wp/sqrt(0.5_wp*Pi)*Vrms**2/kp**5

      ! Number of fourier modes in each direction
      Nk = [Ng(1)/2+1, Ng(2), Ng(3)]

      allocate(Vk(3,Nk(1),Nk(2),Nk(3)),mold=(0.0_wp,0.0_wp))

      ! Build Fourier modes following Rogallo's method
      do mk=1,Nk(3)

        ! Wavenumber in 3-dir
        kappa(3) = real(mk-1,wp)*dk(3)
        if (mk.gt.Ng(3)/2+1) kappa(3) = -real(Ng(3)-(mk-1),wp)*dk(3)

        do mj=1,Nk(2)

          ! Wavenumber in 2-dir
          kappa(2) = real(mj-1,wp)*dk(2)
          if (mj.gt.Ng(2)/2+1) kappa(2) = -real(Ng(2)-(mj-1),wp)*dk(2)

          do mi=1,Nk(1)

            ! Wavenumber in 1-dir
            kappa(1) = real(mi-1,wp)*dk(1)

            ! Wavevector magnitude
            kmag=norm2(kappa)

            if (kmag.gt.1.0e-10_wp) then
              ! Energy at this shell
              Ek = GetEnergySpectrum(kmag,kp,A)

              ! Generate a random phase
              call random_number(phi)
              phi    = 2.0_wp*Pi*phi

              ! Generate a complex vector with a random amplitude and the previous random phase
              call random_number(rand)
              Vk(:,mi,mj,mk) = (rand - 0.5_wp) * exp(ii * phi)

              ! Make it orthogonal to the wavevector to satisfy continuity
              Vk(:,mi,mj,mk) = Vk(:,mi,mj,mk) - (1.0_wp/kmag**2)*dot_product(kappa,Vk(:,mi,mj,mk))*kappa

              ! Rescale to match target energy spectrum
              Vk(:,mi,mj,mk) = Vk(:,mi,mj,mk) * sqrt(Ek / (4.0_wp * pi * kmag**2))

            else
              Vk(:,mi,mj,mk) = (0.0_wp,0.0_wp)
            end if

          end do
        end do
      end do

      return
    end subroutine BuildFourierModes
    subroutine InvertFourierModes(Nk,Vk,V)
      ! Computes velocities in physical space from fourier modes.
      use, intrinsic :: iso_c_binding
      implicit none
      integer,      intent(in)    :: Nk(3)                                     !! Size of fourier modes
      complex(wp),  intent(in)    :: Vk(:,:,:,:)                               !! Fourier modes
      type(eulerian_obj_r),        &
                    intent(inout) :: V(3)
      ! Work variables
      type(C_PTR)             :: plan       = C_NULL_PTR
      type(C_PTR)             :: p_in       = C_NULL_PTR
      type(C_PTR)             :: p_out      = C_NULL_PTR
      complex(C_DOUBLE_COMPLEX),  &
                      pointer :: in(:,:,:)  => null()
      real(C_DOUBLE), pointer :: out(:,:,:) => null()
      integer                 :: mi,mj,mk
      integer                 :: i,j,k,dir

      include 'fftw3.f03'

      ! Allocate memory using FFTW to ensure SIMD allignment
      ! Note that the output must include +2 padding cells in the
      ! third dimension. This is a requirement from FFTW.
      p_in  = fftw_alloc_complex(int(Nk(1)*Nk(2)*Nk(3),         C_SIZE_T))
      p_out = fftw_alloc_real(   int(2*(Ng(1)/2+1)*Ng(2)*Ng(3), C_SIZE_T))

      ! Convert C pointers to Fortran arrays
      call c_f_pointer(p_in,  in,  [Nk(1),Nk(2),Nk(3)])
      call c_f_pointer(p_out, out, [Ng(1),Ng(2),Ng(3)])

      ! Create plan - dimensions must be inverted per FFTW.
      plan = fftw_plan_dft_c2r_3d(Ng(3), Ng(2), Ng(1), in, out, FFTW_MEASURE)


      do dir=1,3

        do mk=1,Nk(3)
          do mj=1,Nk(2)
            do mi=1,Nk(1)
               in(mi,mj,mk) = cmplx(Vk(dir,mi,mj,mk),kind=C_DOUBLE_COMPLEX)
            end do
          end do
        end do

        ! Execute plan (build inverse)
        call fftw_execute_dft_c2r(plan, in, out)

        ! Normalize (FFTW does not automatically normalize)
        do k=1,Ng(3)
          do j=1,Ng(2)
            do i=1,Ng(1)
              V(dir)%cell(i,j,k) = real(out(i,j,k),wp)/real(Ng(1)*Ng(2)*Ng(3),wp)
            end do
          end do
        end do

      end do

      ! Clean up
      call dfftw_destroy_plan(plan)
      call fftw_free(p_out)
      call fftw_free(p_in)

      return
    end subroutine InvertFourierModes
end program main