/* Copyright 2023 Remi Lehe * * This file is part of WarpX. * * License: BSD-3-Clause-LBNL */ #include "ChargeOnEB.H" #include "Diagnostics/ReducedDiags/ReducedDiags.H" #include "Utils/TextMsg.H" #include "Utils/WarpXConst.H" #include "Utils/Parser/ParserUtils.H" #include "WarpX.H" #include #include #include #include #include #include #include #include #include #include using namespace amrex; // constructor ChargeOnEB::ChargeOnEB (std::string rd_name) : ReducedDiags{rd_name} { // Only 3D is working for now #if !(defined WARPX_DIM_3D) WARPX_ALWAYS_ASSERT_WITH_MESSAGE(false, "ChargeOnEB reduced diagnostics only works in 3D"); #endif #if !(defined AMREX_USE_EB) WARPX_ALWAYS_ASSERT_WITH_MESSAGE(false, "ChargeOnEB reduced diagnostics only works when compiling with EB support"); #endif // resize data array m_data.resize(1, 0.0_rt); // Read optional weighting std::string buf; const amrex::ParmParse pp_rd_name(rd_name); m_do_parser_weighting = pp_rd_name.query("weighting_function(x,y,z)", buf); if (m_do_parser_weighting) { std::string weighting_string; utils::parser::Store_parserString( pp_rd_name,"weighting_function(x,y,z)", weighting_string); m_parser_weighting = std::make_unique( utils::parser::makeParser(weighting_string,{"x","y","z"})); } if (ParallelDescriptor::IOProcessor()) { if ( m_write_header ) { // open file std::ofstream ofs{m_path + m_rd_name + "." + m_extension, std::ofstream::out}; // write header row int c = 0; ofs << "#"; ofs << "[" << c++ << "]step()"; ofs << m_sep; ofs << "[" << c++ << "]time(s)"; ofs << m_sep; ofs << "[" << c++ << "]Charge (C)"; ofs << std::endl; // close file ofs.close(); } } } // end constructor // function that computes the charge at the surface of the EB void ChargeOnEB::ComputeDiags (const int step) { // Judge whether the diags should be done if (!m_intervals.contains(step+1)) { return; } #if ((defined WARPX_DIM_3D) && (defined AMREX_USE_EB)) // get a reference to WarpX instance auto & warpx = WarpX::GetInstance(); // Only compute the integral on level 0 int const lev = 0; // get MultiFab data at lev const amrex::MultiFab & Ex = warpx.getEfield_fp(lev,0); const amrex::MultiFab & Ey = warpx.getEfield_fp(lev,1); const amrex::MultiFab & Ez = warpx.getEfield_fp(lev,2); // get EB structures amrex::EBFArrayBoxFactory const& eb_box_factory = warpx.fieldEBFactory(lev); amrex::FabArray const& eb_flag = eb_box_factory.getMultiEBCellFlagFab(); amrex::MultiCutFab const& eb_bnd_cent = eb_box_factory.getBndryCent(); amrex::MultiCutFab const& eb_bnd_normal = eb_box_factory.getBndryNormal(); amrex::Array eb_area_fraction = eb_box_factory.getAreaFrac(); // get surface integration element const amrex::GpuArray dx = warpx.Geom(lev).CellSizeArray(); amrex::Real const dSx = dx[1]*dx[2]; amrex::Real const dSy = dx[2]*dx[0]; amrex::Real const dSz = dx[0]*dx[1]; // Required for parser const amrex::RealBox& real_box = warpx.Geom(lev).ProbDomain(); const bool do_parser_weighting = m_do_parser_weighting; auto fun_weightingparser = utils::parser::compileParser<3>(m_parser_weighting.get()); // Integral to calculate amrex::Gpu::Buffer surface_integral({0.0_rt}); amrex::Real* surface_integral_pointer = surface_integral.data(); // Loop over boxes #ifdef AMREX_USE_OMP #pragma omp parallel if (amrex::Gpu::notInLaunchRegion()) #endif for (amrex::MFIter mfi(Ex, TilingIfNotGPU()); mfi.isValid(); ++mfi) { const amrex::Box & box = mfi.tilebox( amrex::IntVect::TheCellVector() ); // Skip boxes that do not intersect with the embedded boundary // (i.e. either fully covered or fully regular) amrex::FabType fab_type = eb_flag[mfi].getType(box); if (fab_type == amrex::FabType::regular) continue; if (fab_type == amrex::FabType::covered) continue; // Extract data for electric field const amrex::Array4 & Ex_arr = Ex.array(mfi); const amrex::Array4 & Ey_arr = Ey.array(mfi); const amrex::Array4 & Ez_arr = Ez.array(mfi); // Extract data for EB auto const& eb_flag_arr = eb_flag.array(mfi); const amrex::Array4 & eb_bnd_normal_arr = eb_bnd_normal.array(mfi); const amrex::Array4 & eb_bnd_cent_arr = eb_bnd_cent.array(mfi); const amrex::Array4 & dSx_fraction_arr = eb_area_fraction[0]->array(mfi); const amrex::Array4 & dSy_fraction_arr = eb_area_fraction[1]->array(mfi); const amrex::Array4 & dSz_fraction_arr = eb_area_fraction[2]->array(mfi); amrex::ParallelFor( box, [=] AMREX_GPU_DEVICE (int i, int j, int k) { // Only cells that are partially covered do contribute to the integral if (eb_flag_arr(i,j,k).isRegular() || eb_flag_arr(i,j,k).isCovered()) return; // Find nodal point which is outside of the EB // (eb_normal points towards the *interior* of the EB) int const i_n = (eb_bnd_normal_arr(i,j,k,0) > 0)? i : i+1; int const j_n = (eb_bnd_normal_arr(i,j,k,1) > 0)? j : j+1; int const k_n = (eb_bnd_normal_arr(i,j,k,2) > 0)? k : k+1; // Find cell-centered point which is outside of the EB // (eb_normal points towards the *interior* of the EB) int i_c = i; if ((eb_bnd_normal_arr(i,j,k,0)>0) && (eb_bnd_cent_arr(i,j,k,0)<=0)) i_c -= 1; if ((eb_bnd_normal_arr(i,j,k,0)<0) && (eb_bnd_cent_arr(i,j,k,0)>=0)) i_c += 1; int j_c = j; if ((eb_bnd_normal_arr(i,j,k,1)>0) && (eb_bnd_cent_arr(i,j,k,1)<=0)) j_c -= 1; if ((eb_bnd_normal_arr(i,j,k,1)<0) && (eb_bnd_cent_arr(i,j,k,1)>=0)) j_c += 1; int k_c = k; if ((eb_bnd_normal_arr(i,j,k,2)>0) && (eb_bnd_cent_arr(i,j,k,2)<=0)) k_c -= 1; if ((eb_bnd_normal_arr(i,j,k,2)<0) && (eb_bnd_cent_arr(i,j,k,2)>=0)) k_c += 1; // Compute contribution to the surface integral $\int dS \cdot E$) amrex::Real local_integral_contribution = 0; local_integral_contribution += Ex_arr(i_c,j_n,k_n)*dSx*(dSx_fraction_arr(i+1,j,k)-dSx_fraction_arr(i,j,k)); local_integral_contribution += Ey_arr(i_n,j_c,k_n)*dSy*(dSy_fraction_arr(i,j+1,k)-dSy_fraction_arr(i,j,k)); local_integral_contribution += Ez_arr(i_n,j_n,k_c)*dSz*(dSz_fraction_arr(i,j,k+1)-dSz_fraction_arr(i,j,k)); // Add weighting if requested by user if (do_parser_weighting) { // Get the 3D position of the centroid of surface element amrex::Real x = (i + 0.5_rt + eb_bnd_cent_arr(i,j,k,0))*dx[0] + real_box.lo(0); amrex::Real y = (j + 0.5_rt + eb_bnd_cent_arr(i,j,k,1))*dx[1] + real_box.lo(1); amrex::Real z = (k + 0.5_rt + eb_bnd_cent_arr(i,j,k,2))*dx[2] + real_box.lo(2); // Apply weighting local_integral_contribution *= fun_weightingparser(x, y, z); } // Given that only a tiny fraction of the cells have a non-zero contribution // (the ones that intersect with the EB), it is not clear whether ReduceOpSum // or AtomicAdd would be faster. However, the implementation with AtomicAdd is easier. amrex::HostDevice::Atomic::Add( surface_integral_pointer, local_integral_contribution ); }); } // Reduce across MPI ranks surface_integral.copyToHost(); amrex::Real surface_integral_value = *(surface_integral.hostData()); amrex::ParallelDescriptor::ReduceRealSum( surface_integral_value ); // save data m_data[0] = PhysConst::ep0 * surface_integral_value; #endif } // end void ChargeOnEB::ComputeDiags