WarpX
PoissonSolver.H
Go to the documentation of this file.
1 /* Copyright 2019-2022 Axel Huebl, Remi Lehe
2  *
3  * This file is part of WarpX.
4  *
5  * License: BSD-3-Clause-LBNL
6  */
7 #ifndef ABLASTR_POISSON_SOLVER_H
8 #define ABLASTR_POISSON_SOLVER_H
9 
10 #include <ablastr/constant.H>
12 #include <ablastr/utils/Enums.H>
13 #include <ablastr/utils/TextMsg.H>
18 
19 #if defined(ABLASTR_USE_FFT) && defined(WARPX_DIM_3D)
21 #endif
22 
23 #include <AMReX_Array.H>
24 #include <AMReX_Array4.H>
25 #include <AMReX_BLassert.H>
26 #include <AMReX_Box.H>
27 #include <AMReX_BoxArray.H>
28 #include <AMReX_Config.H>
30 #include <AMReX_FArrayBox.H>
31 #include <AMReX_FabArray.H>
32 #include <AMReX_Geometry.H>
33 #include <AMReX_GpuControl.H>
34 #include <AMReX_GpuLaunch.H>
35 #include <AMReX_GpuQualifiers.H>
36 #include <AMReX_IndexType.H>
37 #include <AMReX_IntVect.H>
38 #include <AMReX_LO_BCTYPES.H>
39 #include <AMReX_MFIter.H>
40 #include <AMReX_MFInterp_C.H>
41 #include <AMReX_MLMG.H>
42 #include <AMReX_MLLinOp.H>
44 #include <AMReX_MultiFab.H>
45 #include <AMReX_Parser.H>
46 #include <AMReX_REAL.H>
47 #include <AMReX_SPACE.H>
48 #include <AMReX_Vector.H>
49 #if defined(AMREX_USE_EB) || defined(WARPX_DIM_RZ)
51 #endif
52 #ifdef AMREX_USE_EB
53 # include <AMReX_EBFabFactory.H>
54 #endif
55 
56 #include <array>
57 #include <optional>
58 
59 
60 namespace ablastr::fields {
61 
94 template<
95  typename T_BoundaryHandler,
96  typename T_PostPhiCalculationFunctor = std::nullopt_t,
97  typename T_FArrayBoxFactory = void
98 >
99 void
102  std::array<amrex::Real, 3> const beta,
103  amrex::Real const relative_tolerance,
104  amrex::Real absolute_tolerance,
105  int const max_iters,
106  int const verbosity,
107  amrex::Vector<amrex::Geometry> const& geom,
109  amrex::Vector<amrex::BoxArray> const& grids,
110  utils::enums::GridType grid_type,
111  T_BoundaryHandler const boundary_handler,
112  bool is_solver_igf_on_lev0,
113  bool const do_single_precision_comms = false,
114  std::optional<amrex::Vector<amrex::IntVect> > rel_ref_ratio = std::nullopt,
115  [[maybe_unused]] T_PostPhiCalculationFunctor post_phi_calculation = std::nullopt,
116  [[maybe_unused]] std::optional<amrex::Real const> current_time = std::nullopt, // only used for EB
117  [[maybe_unused]] std::optional<amrex::Vector<T_FArrayBoxFactory const *> > eb_farray_box_factory = std::nullopt // only used for EB
118 )
119 {
120  using namespace amrex::literals;
121 
122  ABLASTR_PROFILE("computePhi");
123 
124  if (!rel_ref_ratio.has_value()) {
126  "rel_ref_ratio must be set if mesh-refinement is used");
127  rel_ref_ratio = amrex::Vector<amrex::IntVect>{{amrex::IntVect(AMREX_D_DECL(1, 1, 1))}};
128  }
129 
130  auto const finest_level = static_cast<int>(rho.size() - 1);
131 
132  // determine if rho is zero everywhere
133  amrex::Real max_norm_b = 0.0;
134  for (int lev=0; lev<=finest_level; lev++) {
135  max_norm_b = amrex::max(max_norm_b, rho[lev]->norm0());
136  }
138 
139  const bool always_use_bnorm = (max_norm_b > 0);
140  if (!always_use_bnorm) {
141  if (absolute_tolerance == 0.0) { absolute_tolerance = amrex::Real(1e-6); }
143  "ElectrostaticSolver",
144  "Max norm of rho is 0",
146  );
147  }
148 
149 #if !(defined(AMREX_USE_EB) || defined(WARPX_DIM_RZ))
150  amrex::LPInfo info;
151 #else
152  const amrex::LPInfo info;
153 #endif
154 
155  for (int lev=0; lev<=finest_level; lev++) {
156  // Set the value of beta
158 #if defined(WARPX_DIM_1D_Z)
159  {{ beta[2] }}; // beta_x and beta_z
160 #elif defined(WARPX_DIM_XZ) || defined(WARPX_DIM_RZ)
161  {{ beta[0], beta[2] }}; // beta_x and beta_z
162 #else
163  {{ beta[0], beta[1], beta[2] }};
164 #endif
165 
166 #if !defined(ABLASTR_USE_FFT)
167  ABLASTR_ALWAYS_ASSERT_WITH_MESSAGE( !is_solver_igf_on_lev0,
168  "Must compile with FFT support to use the IGF solver!");
169 #endif
170 
171 #if !defined(WARPX_DIM_3D)
172  ABLASTR_ALWAYS_ASSERT_WITH_MESSAGE( !is_solver_igf_on_lev0,
173  "The FFT Poisson solver is currently only implemented for 3D!");
174 #endif
175 
176 #if (defined(ABLASTR_USE_FFT) && defined(WARPX_DIM_3D))
177  // Use the Integrated Green Function solver (FFT) on the coarsest level if it was selected
178  if(is_solver_igf_on_lev0 && lev==0){
180  {AMREX_D_DECL(geom[lev].CellSize(0)/std::sqrt(1._rt-beta_solver[0]*beta_solver[0]),
181  geom[lev].CellSize(1)/std::sqrt(1._rt-beta_solver[1]*beta_solver[1]),
182  geom[lev].CellSize(2)/std::sqrt(1._rt-beta_solver[2]*beta_solver[2]))};
183  if ( max_norm_b == 0 ) {
184  phi[lev]->setVal(0);
185  } else {
186  computePhiIGF( *rho[lev], *phi[lev], dx_igf, grids[lev] );
187  }
188  continue;
189  }
190 #endif
191 
192  // Use the Multigrid (MLMG) solver if selected or on refined patches
193  // but first scale rho appropriately
194  using namespace ablastr::constant::SI;
195  rho[lev]->mult(-1._rt/ep0); // TODO: when do we "un-multiply" this? We need to document this side-effect!
196 
197 #if !(defined(AMREX_USE_EB) || defined(WARPX_DIM_RZ))
198  // Determine whether to use semi-coarsening
200  {AMREX_D_DECL(geom[lev].CellSize(0)/std::sqrt(1._rt-beta_solver[0]*beta_solver[0]),
201  geom[lev].CellSize(1)/std::sqrt(1._rt-beta_solver[1]*beta_solver[1]),
202  geom[lev].CellSize(2)/std::sqrt(1._rt-beta_solver[2]*beta_solver[2]))};
203  int max_semicoarsening_level = 0;
204  int semicoarsening_direction = -1;
205  const auto min_dir = static_cast<int>(std::distance(dx_scaled.begin(),
206  std::min_element(dx_scaled.begin(),dx_scaled.end())));
207  const auto max_dir = static_cast<int>(std::distance(dx_scaled.begin(),
208  std::max_element(dx_scaled.begin(),dx_scaled.end())));
209  if (dx_scaled[max_dir] > dx_scaled[min_dir]) {
210  semicoarsening_direction = max_dir;
211  max_semicoarsening_level = static_cast<int>
212  (std::log2(dx_scaled[max_dir]/dx_scaled[min_dir]));
213  }
214  if (max_semicoarsening_level > 0) {
215  info.setSemicoarsening(true);
216  info.setMaxSemicoarseningLevel(max_semicoarsening_level);
217  info.setSemicoarseningDirection(semicoarsening_direction);
218  }
219 #endif
220 
221 #if defined(AMREX_USE_EB) || defined(WARPX_DIM_RZ)
222  // In the presence of EB or RZ: the solver assumes that the beam is
223  // propagating along one of the axes of the grid, i.e. that only *one*
224  // of the components of `beta` is non-negligible.
225  amrex::MLEBNodeFDLaplacian linop( {geom[lev]}, {grids[lev]}, {dmap[lev]}, info
226 #if defined(AMREX_USE_EB)
227  , {eb_farray_box_factory.value()[lev]}
228 #endif
229  );
230 
231  // Note: this assumes that the beam is propagating along
232  // one of the axes of the grid, i.e. that only *one* of the
233  // components of `beta` is non-negligible. // we use this
234 #if defined(WARPX_DIM_RZ)
235  linop.setSigma({0._rt, 1._rt-beta_solver[1]*beta_solver[1]});
236 #else
237  linop.setSigma({AMREX_D_DECL(
238  1._rt-beta_solver[0]*beta_solver[0],
239  1._rt-beta_solver[1]*beta_solver[1],
240  1._rt-beta_solver[2]*beta_solver[2])});
241 #endif
242 
243 #if defined(AMREX_USE_EB)
244  // if the EB potential only depends on time, the potential can be passed
245  // as a float instead of a callable
246  if (boundary_handler.phi_EB_only_t) {
247  linop.setEBDirichlet(boundary_handler.potential_eb_t(current_time.value()));
248  }
249  else
250  linop.setEBDirichlet(boundary_handler.getPhiEB(current_time.value()));
251 #endif
252 #else
253  // In the absence of EB and RZ: use a more generic solver
254  // that can handle beams propagating in any direction
255  amrex::MLNodeTensorLaplacian linop( {geom[lev]}, {grids[lev]},
256  {dmap[lev]}, info );
257  linop.setBeta( beta_solver ); // for the non-axis-aligned solver
258 #endif
259 
260  // Solve the Poisson equation
261  linop.setDomainBC( boundary_handler.lobc, boundary_handler.hibc );
262 #ifdef WARPX_DIM_RZ
263  linop.setRZ(true);
264 #endif
265  amrex::MLMG mlmg(linop); // actual solver defined here
266  mlmg.setVerbose(verbosity);
267  mlmg.setMaxIter(max_iters);
268  mlmg.setAlwaysUseBNorm(always_use_bnorm);
269  if (grid_type == utils::enums::GridType::Collocated) {
270  // In this case, computeE needs to use ghost nodes data. So we
271  // ask MLMG to fill BC for us after it solves the problem.
272  mlmg.setFinalFillBC(true);
273  }
274 
275  // Solve Poisson equation at lev
276  mlmg.solve( {phi[lev]}, {rho[lev]},
277  relative_tolerance, absolute_tolerance );
278 
279  // needed for solving the levels by levels:
280  // - coarser level is initial guess for finer level
281  // - coarser level provides boundary values for finer level patch
282  // Interpolation from phi[lev] to phi[lev+1]
283  // (This provides both the boundary conditions and initial guess for phi[lev+1])
284  if (lev < finest_level) {
285 
286  // Allocate phi_cp for lev+1
287  amrex::BoxArray ba = phi[lev+1]->boxArray();
288  const amrex::IntVect& refratio = rel_ref_ratio.value()[lev];
289  ba.coarsen(refratio);
290  const int ncomp = linop.getNComp();
291  const int ng = (grid_type == utils::enums::GridType::Collocated) ? 1 : 0;
292  amrex::MultiFab phi_cp(ba, phi[lev+1]->DistributionMap(), ncomp, ng);
293  if (ng > 0) {
294  // Set all values outside the domain to zero
295  phi_cp.setDomainBndry(0.0_rt, geom[lev]);
296  }
297 
298  // Copy from phi[lev] to phi_cp (in parallel)
299  const amrex::Periodicity& crse_period = geom[lev].periodicity();
300 
302  phi_cp,
303  *phi[lev],
304  0,
305  0,
306  1,
307  amrex::IntVect(0),
308  amrex::IntVect(ng),
309  do_single_precision_comms,
310  crse_period
311  );
312 
313  // Local interpolation from phi_cp to phi[lev+1]
314 #ifdef AMREX_USE_OMP
315 #pragma omp parallel if (amrex::Gpu::notInLaunchRegion())
316 #endif
317  for (amrex::MFIter mfi(*phi[lev + 1], amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi) {
318  amrex::Array4<amrex::Real> const phi_fp_arr = phi[lev + 1]->array(mfi);
319  amrex::Array4<amrex::Real const> const phi_cp_arr = phi_cp.array(mfi);
320 
321  details::PoissonInterpCPtoFP const interp(phi_fp_arr, phi_cp_arr, refratio);
322 
323  amrex::Box const& b = mfi.growntilebox(ng);
325  }
326 
327  }
328 
329  // Run additional operations, such as calculation of the E field for embedded boundaries
330  if constexpr (!std::is_same_v<T_PostPhiCalculationFunctor, std::nullopt_t>) {
331  if (post_phi_calculation.has_value()) {
332  post_phi_calculation.value()(mlmg, lev);
333  }
334  }
335 
336  } // loop over lev(els)
337 }
338 
339 } // namespace ablastr::fields
340 
341 #endif // ABLASTR_POISSON_SOLVER_H
#define AMREX_D_DECL(a, b, c)
#define ABLASTR_PROFILE(fname)
Definition: ProfilerWrapper.H:13
#define ABLASTR_ALWAYS_ASSERT_WITH_MESSAGE(EX, MSG)
Definition: TextMsg.H:75
BoxArray & coarsen(int refinement_ratio)
void setDomainBndry(value_type val, const Geometry &geom)
Array4< typename FabArray< FArrayBox >::value_type const > array(const MFIter &mfi) const noexcept
void setSigma(Array< Real, AMREX_SPACEDIM > const &a_sigma) noexcept
void setVerbose(int v) noexcept
void setAlwaysUseBNorm(int flag) noexcept
void setFinalFillBC(int flag) noexcept
RT solve(const Vector< AMF * > &a_sol, const Vector< AMF const * > &a_rhs, RT a_tol_rel, RT a_tol_abs, const char *checkpoint_file=nullptr)
void setMaxIter(int n) noexcept
void setBeta(Array< Real, AMREX_SPACEDIM > const &a_beta) noexcept
Long size() const noexcept
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void interp(int j, int k, int l, amrex::Array4< amrex::Real > const &fine, amrex::Array4< amrex::Real const > const &crse, const amrex::IntVect r_ratio, IntVect const &type) noexcept
Definition: Interpolate_K.H:9
Definition: constant.H:40
static constexpr auto ep0
vacuum permittivity: dielectric permittivity of vacuum [F/m]
Definition: constant.H:46
Definition: IntegratedGreenFunctionSolver.cpp:33
void computePhiIGF(amrex::MultiFab const &rho, amrex::MultiFab &phi, std::array< amrex::Real, 3 > const &cell_size, amrex::BoxArray const &ba)
Compute the electrostatic potential using the Integrated Green Function method as in http://dx....
Definition: IntegratedGreenFunctionSolver.cpp:36
void computePhi(amrex::Vector< amrex::MultiFab * > const &rho, amrex::Vector< amrex::MultiFab * > &phi, std::array< amrex::Real, 3 > const beta, amrex::Real const relative_tolerance, amrex::Real absolute_tolerance, int const max_iters, int const verbosity, amrex::Vector< amrex::Geometry > const &geom, amrex::Vector< amrex::DistributionMapping > const &dmap, amrex::Vector< amrex::BoxArray > const &grids, utils::enums::GridType grid_type, T_BoundaryHandler const boundary_handler, bool is_solver_igf_on_lev0, bool const do_single_precision_comms=false, std::optional< amrex::Vector< amrex::IntVect > > rel_ref_ratio=std::nullopt, [[maybe_unused]] T_PostPhiCalculationFunctor post_phi_calculation=std::nullopt, [[maybe_unused]] std::optional< amrex::Real const > current_time=std::nullopt, [[maybe_unused]] std::optional< amrex::Vector< T_FArrayBoxFactory const * > > eb_farray_box_factory=std::nullopt)
Definition: PoissonSolver.H:100
void ParallelCopy(amrex::MultiFab &dst, const amrex::MultiFab &src, int src_comp, int dst_comp, int num_comp, const amrex::IntVect &src_nghost, const amrex::IntVect &dst_nghost, bool do_single_precision_comms, const amrex::Periodicity &period, amrex::FabArrayBase::CpOp op)
Definition: Communication.cpp:28
GridType
Definition: Enums.H:17
void WMRecordWarning(const std::string &topic, const std::string &text, const WarnPriority &priority=WarnPriority::medium)
Helper function to abbreviate the call to WarnManager::GetInstance().RecordWarning (recording a warni...
Definition: WarnManager.cpp:318
void ReduceRealMax(Vector< std::reference_wrapper< Real > > const &)
DistributionMapping const & DistributionMap(FabArrayBase const &fa)
AMREX_GPU_HOST_DEVICE constexpr AMREX_FORCE_INLINE const T & max(const T &a, const T &b) noexcept
IntVectND< AMREX_SPACEDIM > IntVect
bool TilingIfNotGPU() noexcept
std::enable_if_t< std::is_integral_v< T > > ParallelFor(TypeList< CTOs... >, std::array< int, sizeof...(CTOs)> const &runtime_options, T N, F &&f)
std::array< T, N > Array
beta
Definition: stencil.py:434
LPInfo & setSemicoarsening(bool x) noexcept
LPInfo & setMaxSemicoarseningLevel(int n) noexcept
LPInfo & setSemicoarseningDirection(int n) noexcept
int verbosity()