/* Copyright 2019-2020 Maxence Thevenet * * This file is part of WarpX. * * License: BSD-3-Clause-LBNL */ #include "GuardCellManager.H" #ifndef WARPX_DIM_RZ # include "FieldSolver/FiniteDifferenceSolver/FiniteDifferenceAlgorithms/CartesianYeeAlgorithm.H" # include "FieldSolver/FiniteDifferenceSolver/FiniteDifferenceAlgorithms/CartesianNodalAlgorithm.H" # include "FieldSolver/FiniteDifferenceSolver/FiniteDifferenceAlgorithms/CartesianCKCAlgorithm.H" #else # include "FieldSolver/FiniteDifferenceSolver/FiniteDifferenceAlgorithms/CylindricalYeeAlgorithm.H" #endif #include "Filter/NCIGodfreyFilter.H" #include "Utils/Parser/ParserUtils.H" #include "Utils/TextMsg.H" #include "Utils/WarpXAlgorithmSelection.H" #include "Utils/WarpXConst.H" #include #include #include #include #include #include using namespace amrex; void guardCellManager::Init ( const amrex::Real dt, const amrex::RealVect dx, const bool do_subcycling, const bool do_fdtd_nci_corr, const short grid_type, const bool do_moving_window, const int moving_window_dir, const int nox, const int nox_fft, const int noy_fft, const int noz_fft, const int nci_corr_stencil, const int electromagnetic_solver_id, const int max_level, const amrex::Vector v_galilean, const amrex::Vector v_comoving, const bool safe_guard_cells, const int do_multi_J, const bool fft_do_time_averaging, const bool do_pml, const int do_pml_in_domain, const int pml_ncell, const amrex::Vector& ref_ratios, const bool use_filter, const amrex::IntVect& bilinear_filter_stencil_length) { // When using subcycling, the particles on the fine level perform two pushes // before being redistributed ; therefore, we need one extra guard cell // (the particles may move by 2*c*dt) int ngx_tmp = (max_level > 0 && do_subcycling == 1) ? nox+1 : nox; int ngy_tmp = (max_level > 0 && do_subcycling == 1) ? nox+1 : nox; int ngz_tmp = (max_level > 0 && do_subcycling == 1) ? nox+1 : nox; const bool galilean = (v_galilean[0] != 0. || v_galilean[1] != 0. || v_galilean[2] != 0.); const bool comoving = (v_comoving[0] != 0. || v_comoving[1] != 0. || v_comoving[2] != 0.); // Add one guard cell in the case of the Galilean or comoving algorithms if (galilean || comoving) { ngx_tmp += 1; ngy_tmp += 1; ngz_tmp += 1; } // Ex, Ey, Ez, Bx, By, and Bz have the same number of ghost cells. // jx, jy, jz and rho have the same number of ghost cells. // E and B have the same number of ghost cells as j and rho if NCI filter is not used, // but different number of ghost cells in z-direction if NCI filter is used. // The number of cells should be even, in order to easily perform the // interpolation from coarse grid to fine grid. int ngx = (ngx_tmp % 2) ? ngx_tmp+1 : ngx_tmp; // Always even number int ngy = (ngy_tmp % 2) ? ngy_tmp+1 : ngy_tmp; // Always even number const int ngz_nonci = (ngz_tmp % 2) ? ngz_tmp+1 : ngz_tmp; // Always even number int ngz; if (do_fdtd_nci_corr) { const int ng = ngz_tmp + nci_corr_stencil; ngz = (ng % 2) ? ng+1 : ng; } else { ngz = ngz_nonci; } // J is only interpolated from fine to coarse (not coarse to fine) // and therefore does not need to be even. int ngJx = ngx_tmp; int ngJy = ngy_tmp; int ngJz = ngz_tmp; // When calling the moving window (with one level of refinement), we shift // the fine grid by a number of cells equal to the ref_ratio in the moving // window direction. if (do_moving_window) { WARPX_ALWAYS_ASSERT_WITH_MESSAGE(ref_ratios.size() <= 1, "The number of grow cells for the moving window currently assumes 2 levels max."); const int nlevs = ref_ratios.size()+1; const int max_r = (nlevs > 1) ? ref_ratios[0][moving_window_dir] : 2; ngx = std::max(ngx,max_r); ngy = std::max(ngy,max_r); ngz = std::max(ngz,max_r); ngJx = std::max(ngJx,max_r); ngJy = std::max(ngJy,max_r); ngJz = std::max(ngJz,max_r); } #if defined(WARPX_DIM_3D) ng_alloc_EB = IntVect(ngx,ngy,ngz); ng_alloc_J = IntVect(ngJx,ngJy,ngJz); #elif defined(WARPX_DIM_XZ) || defined(WARPX_DIM_RZ) ng_alloc_EB = IntVect(ngx,ngz); ng_alloc_J = IntVect(ngJx,ngJz); #elif defined(WARPX_DIM_1D_Z) ng_alloc_EB = IntVect(ngz); ng_alloc_J = IntVect(ngJz); #endif // TODO Adding one cell for rho should not be necessary, given that the number of guard cells // now takes into account the time step (see code block below). However, this does seem to be // necessary in order to avoid some remaining instances of out-of-bound array access in // simulations with large time steps (revealed by building WarpX with BOUND_CHECK = TRUE). ng_alloc_Rho = ng_alloc_J+1; // Electromagnetic simulations: account for change in particle positions within half a time step // for current deposition and within one time step for charge deposition (since rho is needed // both at the beginning and at the end of the PIC iteration). // For the hybrid-PIC solver, the same number of guard cells are used as for // the electrostatic solver. if (electromagnetic_solver_id != ElectromagneticSolverAlgo::None && electromagnetic_solver_id != ElectromagneticSolverAlgo::HybridPIC) { for (int i = 0; i < AMREX_SPACEDIM; i++) { amrex::Real dt_Rho = dt; amrex::Real dt_J = 0.5_rt*dt; if (do_multi_J) { // With multi_J + time averaging, particles can move during 2*dt per PIC cycle. if (fft_do_time_averaging){ dt_Rho = 2._rt*dt; dt_J = 2._rt*dt; } // With multi_J but without time averaging, particles can move during dt per PIC // cycle for the current deposition as well. else { dt_J = dt; } } ng_alloc_Rho[i] += static_cast(std::ceil(PhysConst::c * dt_Rho / dx[i])); ng_alloc_J[i] += static_cast(std::ceil(PhysConst::c * dt_J / dx[i])); } } // Number of guard cells for local deposition of J and rho ng_depos_J = ng_alloc_J; ng_depos_rho = ng_alloc_Rho; if (use_filter) { ng_alloc_J += bilinear_filter_stencil_length - amrex::IntVect(1); } // After pushing particle int ng_alloc_F_int = (do_moving_window) ? 2 : 0; // CKC solver requires one additional guard cell if (electromagnetic_solver_id == ElectromagneticSolverAlgo::CKC) ng_alloc_F_int = std::max( ng_alloc_F_int, 1 ); ng_alloc_F = IntVect(AMREX_D_DECL(ng_alloc_F_int, ng_alloc_F_int, ng_alloc_F_int)); // Used if warpx.do_divb_cleaning = 1 int ng_alloc_G_int = (do_moving_window) ? 2 : 1; // TODO Does the CKC solver require one additional guard cell (as for F)? ng_alloc_G = IntVect(AMREX_D_DECL(ng_alloc_G_int, ng_alloc_G_int, ng_alloc_G_int)); if (electromagnetic_solver_id == ElectromagneticSolverAlgo::PSATD) { // The number of guard cells should be enough to contain the stencil of the FFT solver. // // Here, this number (ngFFT) is determined empirically to be the order of the solver or // half the order of the solver, depending on other various numerical parameters. // // With the standard PSATD algorithm, simulations on staggered grids usually work fine // with a number of guard cells equal to half the number of guard cells that would be // used on nodal grids, in all directions x, y and z. // // On the other hand, with the Galilean PSATD or averaged Galilean PSATD algorithms, // with a Galilean coordinate transformation directed only in z, it seems more robust // to set the same number of guard cells in z, irrespective of whether the simulation // runs on nodal grids or staggered grids (typically with centering of fields and/or // currents in the latter case). This does not seem to be necessary in x and y, // where it still seems fine to set half the number of guard cells of the nodal case. int ngFFt_x = (grid_type == GridType::Collocated) ? nox_fft : nox_fft / 2; int ngFFt_y = (grid_type == GridType::Collocated) ? noy_fft : noy_fft / 2; int ngFFt_z = (grid_type == GridType::Collocated || galilean) ? noz_fft : noz_fft / 2; const ParmParse pp_psatd("psatd"); utils::parser::queryWithParser(pp_psatd, "nx_guard", ngFFt_x); utils::parser::queryWithParser(pp_psatd, "ny_guard", ngFFt_y); utils::parser::queryWithParser(pp_psatd, "nz_guard", ngFFt_z); #if defined(WARPX_DIM_3D) IntVect ngFFT = IntVect(ngFFt_x, ngFFt_y, ngFFt_z); #elif defined(WARPX_DIM_XZ) || defined(WARPX_DIM_RZ) IntVect ngFFT = IntVect(ngFFt_x, ngFFt_z); #elif defined(WARPX_DIM_1D_Z) IntVect ngFFT = IntVect(ngFFt_z); #endif #ifdef WARPX_DIM_RZ if (do_pml) { if (!do_pml_in_domain) { ngFFT[0] = std::max(ngFFT[0], pml_ncell); } } #else amrex::ignore_unused(do_pml, do_pml_in_domain, pml_ncell); #endif // All boxes should have the same number of guard cells, to avoid temporary parallel copies: // thus we take the maximum of the required number of guard cells over all available fields. for (int i_dim = 0; i_dim < AMREX_SPACEDIM; i_dim++) { int ng_required = ngFFT[i_dim]; // Get the max ng_required = std::max(ng_required, ng_alloc_EB[i_dim]); ng_required = std::max(ng_required, ng_alloc_J[i_dim]); ng_required = std::max(ng_required, ng_alloc_Rho[i_dim]); ng_required = std::max(ng_required, ng_alloc_F[i_dim]); // Set the guard cells to this max ng_alloc_EB[i_dim] = ng_required; ng_alloc_J[i_dim] = ng_required; ng_alloc_F[i_dim] = ng_required; ng_alloc_Rho[i_dim] = ng_required; ng_alloc_F_int = ng_required; ng_alloc_G_int = ng_required; } ng_alloc_F = IntVect(AMREX_D_DECL(ng_alloc_F_int, ng_alloc_F_int, ng_alloc_F_int)); ng_alloc_G = IntVect(AMREX_D_DECL(ng_alloc_G_int, ng_alloc_G_int, ng_alloc_G_int)); } // Compute number of cells required for Field Solver if (electromagnetic_solver_id == ElectromagneticSolverAlgo::PSATD) { ng_FieldSolver = ng_alloc_EB; ng_FieldSolverF = ng_alloc_EB; ng_FieldSolverG = ng_alloc_EB; } #ifdef WARPX_DIM_RZ else if (electromagnetic_solver_id == ElectromagneticSolverAlgo::None || electromagnetic_solver_id == ElectromagneticSolverAlgo::Yee || electromagnetic_solver_id == ElectromagneticSolverAlgo::HybridPIC ) { ng_FieldSolver = CylindricalYeeAlgorithm::GetMaxGuardCell(); ng_FieldSolverF = CylindricalYeeAlgorithm::GetMaxGuardCell(); ng_FieldSolverG = CylindricalYeeAlgorithm::GetMaxGuardCell(); } #else else { if (grid_type == GridType::Collocated) { ng_FieldSolver = CartesianNodalAlgorithm::GetMaxGuardCell(); ng_FieldSolverF = CartesianNodalAlgorithm::GetMaxGuardCell(); ng_FieldSolverG = CartesianNodalAlgorithm::GetMaxGuardCell(); } else if (electromagnetic_solver_id == ElectromagneticSolverAlgo::None || electromagnetic_solver_id == ElectromagneticSolverAlgo::Yee || electromagnetic_solver_id == ElectromagneticSolverAlgo::ECT || electromagnetic_solver_id == ElectromagneticSolverAlgo::HybridPIC ) { ng_FieldSolver = CartesianYeeAlgorithm::GetMaxGuardCell(); ng_FieldSolverF = CartesianYeeAlgorithm::GetMaxGuardCell(); ng_FieldSolverG = CartesianYeeAlgorithm::GetMaxGuardCell(); } else if (electromagnetic_solver_id == ElectromagneticSolverAlgo::CKC) { ng_FieldSolver = CartesianCKCAlgorithm::GetMaxGuardCell(); ng_FieldSolverF = CartesianCKCAlgorithm::GetMaxGuardCell(); ng_FieldSolverG = CartesianCKCAlgorithm::GetMaxGuardCell(); } } #endif // Number of guard cells is the max of that determined by particle shape factor and // the stencil used in the field solve ng_alloc_EB.max( ng_FieldSolver ); ng_alloc_F.max( ng_FieldSolverF ); ng_alloc_G.max( ng_FieldSolverG ); if (do_moving_window && electromagnetic_solver_id == ElectromagneticSolverAlgo::PSATD) { ng_afterPushPSATD = ng_alloc_EB; } if (safe_guard_cells){ // Run in safe mode: exchange all allocated guard cells at each // call of FillBoundary ng_FieldSolver = ng_alloc_EB; ng_FieldSolverF = ng_alloc_F; ng_FieldSolverG = ng_alloc_G; ng_FieldGather = ng_alloc_EB; ng_UpdateAux = ng_alloc_EB; ng_afterPushPSATD = ng_alloc_EB; if (do_moving_window){ ng_MovingWindow = ng_alloc_EB; } } else { // Compute number of cells required for Field Gather: // When increasing the shape by order by one, support of the shape // factor grows symmetrically by half a cell on each side. So every // +2 orders, one touches one more cell point. int const FGcell = (nox + 1) / 2; // integer division IntVect ng_FieldGather_noNCI = IntVect(AMREX_D_DECL(FGcell,FGcell,FGcell)); ng_FieldGather_noNCI = ng_FieldGather_noNCI.min(ng_alloc_EB); // If NCI filter, add guard cells in the z direction IntVect ng_NCIFilter = IntVect::TheZeroVector(); if (do_fdtd_nci_corr) ng_NCIFilter[WARPX_ZINDEX] = NCIGodfreyFilter::m_stencil_width; // Note: communications of guard cells for bilinear filter are handled // separately. ng_FieldGather = ng_FieldGather_noNCI + ng_NCIFilter; // Guard cells for auxiliary grid. // Not sure why there is a 2* here... ng_UpdateAux = 2*ng_FieldGather_noNCI + ng_NCIFilter; // Make sure we do not exchange more guard cells than allocated. ng_FieldGather = ng_FieldGather.min(ng_alloc_EB); ng_UpdateAux = ng_UpdateAux.min(ng_alloc_EB); // Only FillBoundary(ng_FieldGather) is called between consecutive // field solves. So ng_FieldGather must have enough cells // for the field solve too. ng_FieldGather = ng_FieldGather.max(ng_FieldSolver); if (do_moving_window){ ng_MovingWindow[moving_window_dir] = 1; } } }