aboutsummaryrefslogtreecommitdiff
path: root/Source/FieldSolver/SpectralSolver
diff options
context:
space:
mode:
Diffstat (limited to 'Source/FieldSolver/SpectralSolver')
-rw-r--r--Source/FieldSolver/SpectralSolver/Make.package8
-rw-r--r--Source/FieldSolver/SpectralSolver/PsatdSolver.H26
-rw-r--r--Source/FieldSolver/SpectralSolver/PsatdSolver.cpp185
-rw-r--r--Source/FieldSolver/SpectralSolver/SpectralData.H43
-rw-r--r--Source/FieldSolver/SpectralSolver/SpectralData.cpp137
-rw-r--r--Source/FieldSolver/SpectralSolver/WarpXComplex.H26
6 files changed, 425 insertions, 0 deletions
diff --git a/Source/FieldSolver/SpectralSolver/Make.package b/Source/FieldSolver/SpectralSolver/Make.package
new file mode 100644
index 000000000..a6d088038
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/Make.package
@@ -0,0 +1,8 @@
+CEXE_headers += WarpXComplex.H
+CEXE_headers += SpectralData.H
+CEXE_sources += SpectralData.cpp
+CEXE_headers += PsatdSolver.H
+CEXE_sources += PsatdSolver.cpp
+
+INCLUDE_LOCATIONS += $(WARPX_HOME)/Source/FieldSolver/SpectralSolver
+VPATH_LOCATIONS += $(WARPX_HOME)/Source/FieldSolver/SpectralSolver \ No newline at end of file
diff --git a/Source/FieldSolver/SpectralSolver/PsatdSolver.H b/Source/FieldSolver/SpectralSolver/PsatdSolver.H
new file mode 100644
index 000000000..a2bf7e84b
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/PsatdSolver.H
@@ -0,0 +1,26 @@
+#ifndef WARPX_PSATD_SOLVER_H_
+#define WARPX_PSATD_SOLVER_H_
+
+#include <AMREX_Real.H>
+#include <WarpXComplex.H>
+#include <SpectralData.H>
+
+using namespace amrex;
+using namespace Gpu;
+
+using SpectralVector = LayoutData<ManagedVector<Real>>
+
+class PsatdSolver
+{
+ using SpectralCoefficients = FabArray<BaseFab<Real>>
+
+ public:
+ PsatdSolver( const BoxArray& ba, const DistributionMapping& dm, const Real* dx );
+ void pushSpectralFields( SpectralData& f ) const;
+
+ private:
+ SpectralVector kx, ky, kz;
+ SpectralCoefficients C_coef, S_ck_coef, X1_coef, X2_coef, X3_coef;
+};
+
+#endif // WARPX_PSATD_SOLVER_H_
diff --git a/Source/FieldSolver/SpectralSolver/PsatdSolver.cpp b/Source/FieldSolver/SpectralSolver/PsatdSolver.cpp
new file mode 100644
index 000000000..2b18f7a83
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/PsatdSolver.cpp
@@ -0,0 +1,185 @@
+#include <PsatdSolver.H>
+#include <WarpXConst.H>
+#include <cmath>
+
+using namespace amrex;
+using namespace Gpu;
+
+/*
+ * ba: BoxArray for spectral space
+ * dm: DistributionMapping for spectral space
+ */
+PsatdSolver::PsatdSolver( const BoxArray& ba, const DistributionMapping& dm,
+ const Real* dx, const Real dt )
+{
+ // Allocate the 1D vectors
+ kx = SpectralVector( ba, dm );
+ ky = SpectralVector( ba, dm );
+ kz = SpectralVector( ba, dm );
+ for ( MFIter mfi(ba, dm); mfi.isValid(); ++mfi ){
+ Box bx = ba[mfi];
+ AllocateAndFillKvector( kx[mfi], bx, dx, 0 )
+ AllocateAndFillKvector( ky[mfi], bx, dx, 1 )
+ AllocateAndFillKvector( kz[mfi], bx, dx, 2 )
+ }
+
+ // Allocate the arrays of coefficients
+ C_coef = SpectralMatrix( ba, dm, 1, 0 );
+ S_ck_coef = SpectralMatrix( ba, dm, 1, 0 );
+ X1_coef = SpectralMatrix( ba, dm, 1, 0 );
+ X2_coef = SpectralMatrix( ba, dm, 1, 0 );
+ X3_coef = SpectralMatrix( ba, dm, 1, 0 );
+
+ // Fill them with the right values:
+ // Loop over boxes
+ for ( MFIter mfi(ba, dm); mfi.isValid(); ++mfi ){
+
+ const Box& bx = mfi.box();
+
+ // Extract pointers for the k vectors
+ const Real* kx = kx[mfi].dataPtr();
+ const Real* ky = ky[mfi].dataPtr();
+ const Real* kz = kz[mfi].dataPtr();
+ // Extract arrays for the coefficients
+ Array4<Real> C = C_coef[mfi].array();
+ Array4<Real> S_ck = S_ck_coef[mfi].array();
+ Array4<Real> X1 = X1_coef[mfi].array();
+ Array4<Real> X2 = X2_coef[mfi].array();
+ Array4<Real> X3 = X3_coef[mfi].array();
+
+ // Loop over indices within one box
+ ParallelFor( bx,
+ [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
+ {
+ // Calculate norm of vector
+ const Real k_norm = std::sqrt( kx[i]*kx[i] + ky[j]*ky[j] + kz[k]*kz[k] );
+
+ // Calculate coefficients
+ constexpr Real c = PhysConst::c;
+ constexpr Real ep0 = PhysConst::ep0;
+ if ( k_norm != 0 ){
+ C(i,j,k) = std::cos( c*k_norm*dt );
+ S_ck(i,j,k) = std::sin( c*k_norm*dt )/( c*k_norm );
+ X1(i,j,k) = (1. - C(i,j,k))/(ep0 * c*c * k_norm*k_norm);
+ X2(i,j,k) = (1. - S_ck(i,j,k)/dt )/(ep0 * k_norm*k_norm);
+ X3(i,j,k) = (C(i,j,k) - S_ck(i,j,k)/dt )/(ep0 * k_norm*k_norm);
+ } else { // Handle k_norm = 0, by using the analytical limit
+ C(i,j,k) = 1.;
+ S_ck(i,j,k) = dt;
+ X1(i,j,k) = 0.5 * dt*dt / ep0;
+ X2(i,j,k) = c*c * dt*dt / (6.*ep0);
+ X3(i,j,k) = - c*c * dt*dt / (3.*ep0);
+ }
+ });
+ }
+}
+
+void
+PsatdSolver::pushSpectralFields( SpectralFields& f ) const{
+
+ // Loop over boxes
+ for ( MFIter mfi(f.Ex); mfi.isValid(); ++mfi ){
+
+ const Box& bx = mfi.box();
+
+ // Extract arrays for the fields to be updated
+ Array4<Complex> Ex_arr = f.Ex[mfi].array();
+ Array4<Complex> Ey_arr = f.Ey[mfi].array();
+ Array4<Complex> Ez_arr = f.Ez[mfi].array();
+ Array4<Complex> Bx_arr = f.Bx[mfi].array();
+ Array4<Complex> By_arr = f.By[mfi].array();
+ Array4<Complex> Bz_arr = f.Bz[mfi].array();
+ // Extract arrays for J
+ const Array4<Complex> Jx_arr = f.Jx[mfi].array();
+ const Array4<Complex> Jy_arr = f.Jy[mfi].array();
+ const Array4<Complex> Jz_arr = f.Jz[mfi].array();
+ const Array4<Complex> rho_old_arr = f.rho_old[mfi].array();
+ const Array4<Complex> rho_new_arr = f.rho_new[mfi].array();
+ // Extract arrays for the coefficients
+ const Array4<Real> C_arr = C_coef[mfi].array();
+ const Array4<Real> S_ck_arr = S_ck_coef[mfi].array();
+ const Array4<Real> inv_k2_arr =
+ // Extract pointers for the k vectors
+ const Real* kx_arr = kx[mfi].dataPtr();
+ const Real* ky_arr = ky[mfi].dataPtr();
+ const Real* kz_arr = kz[mfi].dataPtr();
+
+ // Loop over indices within one box
+ ParallelFor( bx,
+ [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept
+ {
+ // Record old values of the fields to be updated
+ const Complex Ex_old = Ex_arr(i,j,k);
+ const Complex Ey_old = Ey_arr(i,j,k);
+ const Complex Ez_old = Ez_arr(i,j,k);
+ const Complex Bx_old = Bx_arr(i,j,k);
+ const Complex By_old = By_arr(i,j,k);
+ const Complex Bz_old = Bz_arr(i,j,k);
+ // k vector values, and coefficients
+ const Real kx = kx_arr[i];
+ const Real ky = ky_arr[j];
+ const Real kz = kz_arr[k];
+ constexpr Real c2 = PhysConst::c*PhysConst::c;
+ constexpr Real inv_ep0 = 1./PhysConst::ep0;
+ constexpr Complex I = Complex{0,1};
+ const Real C = C_arr(i,j,k);
+ const Real S_ck = S_ck_arr(i,j,k);
+ const Real X1 = X1_arr(i,j,k);
+ const Real X2 = X2_arr(i,j,k);
+ const Real X3 = X3_arr(i,j,k);
+ // Short cut for the values of J and rho
+ const Complex Jx = Jx_arr(i,j,k);
+ const Complex Jy = Jy_arr(i,j,k);
+ const Complex Jz = Jz_arr(i,j,k);
+
+ // Update E (see WarpX online documentation: theory section)
+ Ex_arr(i,j,k) = C*Ex_old
+ + S_ck*( c2*I*(ky*Bz_old - kz*By_old) - inv_ep0*Jx )
+ - I*( X2*rho_new - X3*rho_old )*kx;
+ Ey_arr(i,j,k) = C*Ey_old
+ + S_ck*( c2*I*(kz*Bx_old - kx*Bz_old) - inv_ep0*Jy )
+ - I*( X2*rho_new - X3*rho_old )*ky;
+ Ez_arr(i,j,k) = C*Ez_old
+ + S_ck*( c2*I*(kx*By_old - ky*Bx_old) - inv_ep0*Jz )
+ - I*( X2*rho_new - X3*rho_old )*kz;
+ // Update B (see WarpX online documentation: theory section)
+ Bx_arr(i,j,k) = C*Bx_old
+ - S_ck*I*(ky*Ez_old - kz*Ey_old)
+ + X1*I*(ky*Jz_old - kz*Jy_old);
+ By_arr(i,j,k) = C*By_old
+ - S_ck*I*(kz*Ex_old - kx*Ez_old)
+ + X1*I*(kz*Jx_old - kx*Jz_old);
+ Bz_arr(i,j,k) = C*Bz_old
+ - S_ck*I*(kx*Ey_old - ky*Ex_old)
+ + X1*I*(kx*Jy_old - ky*Jx_old);
+ });
+ }
+}
+
+AllocateAndFillKvector( ManagedVector<Real>& k, const Box& bx, const Real* dx, const int i_dim )
+{
+ // Alllocate k to the right size
+ int N = bx.length( i_dim );
+ k.resize( N );
+
+ // Fill the k vector
+ const Real PI = std::atan(1.0)*4;
+ const Real dk = 2*PI/(N*dx[i_dim]);
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE( bx.smallEnd(i_dim) == 0,
+ "Expected box to start at 0, in spectral space.");
+ AMREX_ALWAYS_ASSERT_WITH_MESSAGE( bx.bigEnd(i_dim) == N-1,
+ "Expected different box end index in spectral space.");
+ // Fill positive values of k (FFT conventions: first half is positive)
+ for (int i=0; i<(N+1)/2; i++ ){
+ k[i] = i*dk;
+ }
+ // Fill negative values of k (FFT conventions: second half is negative)
+ for (int i=(N+1)/2, i<N; i++){
+ k[i] = (N-i)*dk;
+ }
+ // TODO: This should be quite different for the hybrid spectral code:
+ // In that case we should take into consideration the actual indices of the box
+ // and distinguish the size of the local box and that of the global FFT
+ // TODO: For real-to-complex,
+
+}
diff --git a/Source/FieldSolver/SpectralSolver/SpectralData.H b/Source/FieldSolver/SpectralSolver/SpectralData.H
new file mode 100644
index 000000000..e294287c5
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/SpectralData.H
@@ -0,0 +1,43 @@
+#ifndef WARPX_PSATD_DATA_H_
+#define WARPX_PSATD_DATA_H_
+
+#include <WarpXComplex.H>
+#include <AMReX_MultiFab.H>
+
+using namespace amrex;
+
+// Declare spectral types
+using SpectralField = FabArray<BaseFab<Complex>>;
+
+/* Fields that will be stored in spectral space */
+struct SpectralFieldIndex{
+ enum { Ex=0, Ey, Ez, Bx, By, Bz, Jx, Jy, Jz, rho_old, rho_new };
+};
+
+/* \brief Class that stores the fields in spectral space
+ * and performs the spectral transforms to/from real space
+ */
+class SpectralData
+{
+#ifdef AMREX_USE_GPU
+// Add cuFFT-specific code
+#else
+ using FFTplans = LayoutData<fftw_plan>;
+#endif
+
+ public:
+ SpectralData( const BoxArray& realspace_ba,
+ const BoxArray& spectralspace_ba,
+ const DistributionMapping& dm );
+ ~SpectralData();
+ void ForwardTransform( const MultiFab& mf, const int field_index );
+ void InverseTransform( MultiFab& mf, const int field_index );
+
+ private:
+ SpectralField Ex, Ey, Ez, Bx, By, Bz, Jx, Jy, Jz, rho_old, rho_new;
+ SpectralField tmpRealField, tmpSpectralField; // Store fields before/after transform
+ FFTplans forward_plan, inverse_plan;
+ SpectralField& getSpectralField( const int field_index );
+};
+
+#endif // WARPX_PSATD_DATA_H_
diff --git a/Source/FieldSolver/SpectralSolver/SpectralData.cpp b/Source/FieldSolver/SpectralSolver/SpectralData.cpp
new file mode 100644
index 000000000..316849064
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/SpectralData.cpp
@@ -0,0 +1,137 @@
+#include <WarpXComplex.H>
+#include <AMReX_MultiFab.H>
+
+using namespace amrex;
+
+SpectralData::SpectralData( const BoxArray& realspace_ba,
+ const BoxArray& spectralspace_ba,
+ const DistributionMapping& dm )
+{
+ // Allocate the arrays that contain the fields in spectral space
+ Ex = SpectralField(spectralspace_ba, dm, 1, 0);
+ Ey = SpectralField(spectralspace_ba, dm, 1, 0);
+ Ez = SpectralField(spectralspace_ba, dm, 1, 0);
+ Bx = SpectralField(spectralspace_ba, dm, 1, 0);
+ By = SpectralField(spectralspace_ba, dm, 1, 0);
+ Bz = SpectralField(spectralspace_ba, dm, 1, 0);
+ Jx = SpectralField(spectralspace_ba, dm, 1, 0);
+ Jy = SpectralField(spectralspace_ba, dm, 1, 0);
+ Jz = SpectralField(spectralspace_ba, dm, 1, 0);
+ rho_old = SpectralField(spectralspace_ba, dm, 1, 0);
+ rho_new = SpectralField(spectralspace_ba, dm, 1, 0);
+
+ // Allocate temporary arrays - over different boxarrays
+ tmpRealField = SpectralField(realspace_ba, dm, 1, 0);
+ tmpSpectralField = SpectralField(spectralspace_ba, dm, 1, 0);
+
+ // Allocate and initialize the FFT plans
+ forward_plan = FFTplans(spectralspace_ba, dm);
+ inverse_plan = FFTplans(spectralspace_ba, dm);
+ for ( MFIter mfi(spectralspace_ba, dm); mfi.isValid(); ++mfi ){
+ Box bx = spectralspace_ba[mfi];
+#ifdef AMREX_USE_GPU
+ // Add cuFFT-specific code
+#else
+ // Create FFTW plans
+ forward_plan[mfi] = fftw_plan_dft_3d(
+ // Swap dimensions: AMReX data is Fortran-order, but FFTW is C-order
+ bx.length(2), bx.length(1), bx.length(0),
+ reinterpret_cast<fftw_complex*>( tmpRealField[mfi].dataPtr() ),
+ reinterpret_cast<fftw_complex*>( tmpSpectralField[mfi].dataPtr() ),
+ FFTW_FORWARD, FFTW_ESTIMATE );
+ inverse_plan[mfi] = fftw_plan_dft_3d(
+ // Swap dimensions: AMReX data is Fortran-order, but FFTW is C-order
+ bx.length(2), bx.length(1), bx.length(0),
+ reinterpret_cast<fftw_complex*>( tmpSpectralField[mfi].dataPtr() ),
+ reinterpret_cast<fftw_complex*>( tmpRealField[mfi].dataPtr() ),
+ FFTW_BACKWARD, FFTW_ESTIMATE );
+ // TODO: Add 2D code
+ // TODO: Do real-to-complex transform instead of complex-to-complex
+ // TODO: Let the user decide whether to use FFTW_ESTIMATE or FFTW_MEASURE
+#endif
+ }
+}
+
+
+SpectralData::~SpectralData()
+{
+ for ( MFIter mfi(tmpRealField); mfi.isValid(); ++mfi ){
+#ifdef AMREX_USE_GPU
+ // Add cuFFT-specific code
+#else
+ // Destroy FFTW plans
+ fftw_destroy_plan( forward_plan[mfi] );
+ fftw_destroy_plan( inverse_plan[mfi] );
+#endif
+ }
+}
+
+/* TODO: Documentation
+ * Example: ForwardTransform( Efield_cp[0], SpectralFieldIndex::Ex )
+ */
+void
+SpectralData::ForwardTransform( const MultiFab& mf, const int field_index )
+{
+ // Loop over boxes
+ for ( MFIter mfi(mf); mfi.isValid(); ++mfi ){
+
+ // Copy the real-space field `mf` to the temporary field `tmpRealField`
+ // This ensures that all fields have the same number of points
+ // before the Fourier transform.
+ // As a consequence, the copy discards the *last* point of `mf`
+ // in any direction that has *nodal* index type.
+ {
+ Box bx = mf[mfi].box();
+ const Box realspace_bx = bx.enclosedCells(); // discards last point in each nodal direction
+ AMREX_ALWAYS_ASSERT( realspace_bx == tmpRealField[mfi].box() );
+ Array4<const Real> mf_arr = mf[mfi].array();
+ Array4<Complex> tmp_arr = tmpRealField[mfi].array();
+ ParallelFor( realspace_bx,
+ [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
+ tmp_arr(i,j,k) = mf_arr(i,j,k);
+ });
+ }
+
+ // Perform Fourier transform from `tmpRealField` to `tmpSpectralField`
+#ifdef AMREX_USE_GPU
+ // Add cuFFT-specific code ; make sure that this is done on the same
+ // GPU stream as the above copy
+#else
+ fftw_execute( forward_plan[mfi] );
+#endif
+
+ // Copy the spectral-space field `tmpSpectralField` to the appropriate field
+ // (specified by the input argument field_index )
+ {
+ SpectralField& field = getSpectralField( field_index );
+ Array4<Complex> field_arr = field[mfi].array();
+ Array4<const Complex> tmp_arr = tmpSpectralField[mfi].array();
+ const Box spectralspace_bx = tmpSpectralField[mfi].box();
+ ParallelFor( spectralspace_bx,
+ [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
+ field_arr(i,j,k) = tmp_arr(i,j,k);
+ });
+ }
+ }
+}
+
+
+SpectralField&
+SpectralData::getSpectralField( const int field_index )
+{
+ switch(field_index)
+ {
+ case SpectralFieldIndex::Ex : return Ex;
+ case SpectralFieldIndex::Ey : return Ey;
+ case SpectralFieldIndex::Ez : return Ez;
+ case SpectralFieldIndex::Bx : return Bx;
+ case SpectralFieldIndex::By : return By;
+ case SpectralFieldIndex::Bz : return Bz;
+ case SpectralFieldIndex::Jx : return Jx;
+ case SpectralFieldIndex::Jy : return Jy;
+ case SpectralFieldIndex::Jz : return Jz;
+ case SpectralFieldIndex::rho_old : return rho_old;
+ case SpectralFieldIndex::rho_new : return rho_new;
+ default : return tmpSpectralField; // For synthax; should not occur in practice
+ }
+}
diff --git a/Source/FieldSolver/SpectralSolver/WarpXComplex.H b/Source/FieldSolver/SpectralSolver/WarpXComplex.H
new file mode 100644
index 000000000..8e2b3977f
--- /dev/null
+++ b/Source/FieldSolver/SpectralSolver/WarpXComplex.H
@@ -0,0 +1,26 @@
+#ifndef WARPX_COMPLEX_H_
+#define WARPX_COMPLEX_H_
+
+#include <AMReX_REAL.H>
+
+// Define complex type on GPU/CPU
+#ifdef AMREX_USE_GPU
+
+#include <thrust/complex.h>
+#include <cufft.h>
+using Complex = thrust::complex<amrex::Real>;
+static_assert( sizeof(Complex) == sizeof(cuDoubleComplex),
+ "The complex types in WarpX and cuFFT do not match.");
+
+#else
+
+#include <complex>
+#include <fftw3.h>
+using Complex = std::complex<amrex::Real>;
+static_assert( sizeof(Complex) == sizeof(fftw_complex),
+ "The complex types in WarpX and FFTW do not match.");
+
+#endif
+static_assert(sizeof(Complex) == sizeof(amrex::Real[2]), "Unexpected complex type.");
+
+#endif //WARPX_COMPLEX_H_