diff options
Diffstat (limited to 'Source')
29 files changed, 1861 insertions, 90 deletions
diff --git a/Source/Diagnostics/FlushFormats/FlushFormatOpenPMD.cpp b/Source/Diagnostics/FlushFormats/FlushFormatOpenPMD.cpp index 8af12308e..c3fd8e522 100644 --- a/Source/Diagnostics/FlushFormats/FlushFormatOpenPMD.cpp +++ b/Source/Diagnostics/FlushFormats/FlushFormatOpenPMD.cpp @@ -43,7 +43,7 @@ FlushFormatOpenPMD::FlushFormatOpenPMD (const std::string& diag_name) ( openPMD::IterationEncoding::groupBased != encoding ) ) { std::string warnMsg = diag_name+" Unable to support BTD with streaming. Using GroupBased "; - amrex::Warning(warnMsg); + WarpX::GetInstance().RecordWarning("Diagnostics", warnMsg); encoding = openPMD::IterationEncoding::groupBased; } } diff --git a/Source/Diagnostics/FlushFormats/FlushFormatPlotfile.cpp b/Source/Diagnostics/FlushFormats/FlushFormatPlotfile.cpp index 08230ac1a..62fea2dd8 100644 --- a/Source/Diagnostics/FlushFormats/FlushFormatPlotfile.cpp +++ b/Source/Diagnostics/FlushFormats/FlushFormatPlotfile.cpp @@ -522,14 +522,16 @@ FlushFormatPlotfile::WriteAllRawFields( WriteRawMF( warpx.getBfield_fp(lev, 2), dm, raw_pltname, default_level_prefix, "Bz_fp", lev, plot_raw_fields_guards); if (plot_raw_F) { if (warpx.get_pointer_F_fp(lev) == nullptr) { - amrex::Warning("The user requested to write raw F data, but F_fp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw F data, but F_fp was not allocated"); } else { WriteRawMF(warpx.getF_fp(lev), dm, raw_pltname, default_level_prefix, "F_fp", lev, plot_raw_fields_guards); } } if (plot_raw_rho) { if (warpx.get_pointer_rho_fp(lev) == nullptr) { - amrex::Warning("The user requested to write raw rho data, but rho_fp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw rho data, but rho_fp was not allocated"); } else { // Use the component 1 of `rho_fp`, i.e. rho_new for time synchronization // If nComp > 1, this is the upper half of the list of components. @@ -579,9 +581,11 @@ FlushFormatPlotfile::WriteAllRawFields( dm, raw_pltname, default_level_prefix, lev, plot_raw_fields_guards); if (plot_raw_F) { if (warpx.get_pointer_F_fp(lev) == nullptr) { - amrex::Warning("The user requested to write raw F data, but F_fp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw F data, but F_fp was not allocated"); } else if (warpx.get_pointer_F_cp(lev) == nullptr) { - amrex::Warning("The user requested to write raw F data, but F_cp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw F data, but F_cp was not allocated"); } else { WriteCoarseScalar("F", warpx.get_pointer_F_cp(lev), warpx.get_pointer_F_fp(lev), dm, raw_pltname, default_level_prefix, lev, plot_raw_fields_guards, 0); @@ -589,9 +593,11 @@ FlushFormatPlotfile::WriteAllRawFields( } if (plot_raw_rho) { if (warpx.get_pointer_rho_fp(lev) == nullptr) { - amrex::Warning("The user requested to write raw rho data, but rho_fp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw rho data, but rho_fp was not allocated"); } else if (warpx.get_pointer_rho_cp(lev) == nullptr) { - amrex::Warning("The user requested to write raw rho data, but rho_cp was not allocated"); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The user requested to write raw rho data, but rho_cp was not allocated"); } else { // Use the component 1 of `rho_cp`, i.e. rho_new for time synchronization WriteCoarseScalar("rho", warpx.get_pointer_rho_cp(lev), warpx.get_pointer_rho_fp(lev), diff --git a/Source/Diagnostics/SliceDiagnostic.cpp b/Source/Diagnostics/SliceDiagnostic.cpp index 185c560ae..8a018e7e5 100644 --- a/Source/Diagnostics/SliceDiagnostic.cpp +++ b/Source/Diagnostics/SliceDiagnostic.cpp @@ -35,6 +35,7 @@ #include <cmath> #include <memory> +#include <sstream> using namespace amrex; @@ -124,7 +125,8 @@ CreateSlice( const MultiFab& mf, const Vector<Geometry> &dom_geom, } } if (configuration_dim==1) { - amrex::Warning("The slice configuration is 1D and cannot be visualized using yt."); + WarpX::GetInstance().RecordWarning("Diagnostics", + "The slice configuration is 1D and cannot be visualized using yt."); } // Slice generation with index type inheritance // @@ -278,17 +280,23 @@ CheckSliceInput( const RealBox real_box, RealBox &slice_cc_nd_box, // Modify lo if input is out of bounds // if ( slice_realbox.lo(idim) < real_box.lo(idim) ) { slice_realbox.setLo( idim, real_box.lo(idim)); - amrex::Print() << " slice lo is out of bounds. " << - " Modified it in dimension " << idim << - " to be aligned with the domain box\n"; + std::stringstream warnMsg; + warnMsg << " slice lo is out of bounds. " << + " Modified it in dimension " << idim << + " to be aligned with the domain box."; + WarpX::GetInstance().RecordWarning("Diagnostics", + warnMsg.str(), WarnPriority::low); } // Modify hi if input in out od bounds // if ( slice_realbox.hi(idim) > real_box.hi(idim) ) { slice_realbox.setHi( idim, real_box.hi(idim)); - amrex::Print() << " slice hi is out of bounds." << - " Modified it in dimension " << idim << - " to be aligned with the domain box\n"; + std::stringstream warnMsg; + warnMsg << " slice hi is out of bounds. " << + " Modified it in dimension " << idim << + " to be aligned with the domain box."; + WarpX::GetInstance().RecordWarning("Diagnostics", + warnMsg.str(), WarnPriority::low); } // Factor to ensure index values computation depending on index type // @@ -372,11 +380,12 @@ CheckSliceInput( const RealBox real_box, RealBox &slice_cc_nd_box, } if ( (hi_new - lo_new) == 0 ){ - amrex::Print() << " Diagnostic Warning :: "; - amrex::Print() << " Coarsening ratio "; - amrex::Print() << slice_cr_ratio[idim] << " in dim "<< idim; - amrex::Print() << "is leading to zero cells for slice."; - amrex::Print() << " Thus reducing cr_ratio by half.\n"; + std::stringstream warnMsg; + warnMsg << " Coarsening ratio " << slice_cr_ratio[idim] << " in dim "<< idim << + "is leading to zero cells for slice." << " Thus reducing cr_ratio by half.\n"; + + WarpX::GetInstance().RecordWarning("Diagnostics", + warnMsg.str()); slice_cr_ratio[idim] = slice_cr_ratio[idim]/2; modify_cr = true; diff --git a/Source/Diagnostics/WarpXOpenPMD.cpp b/Source/Diagnostics/WarpXOpenPMD.cpp index 2a1a18bd1..d1c45abda 100644 --- a/Source/Diagnostics/WarpXOpenPMD.cpp +++ b/Source/Diagnostics/WarpXOpenPMD.cpp @@ -397,10 +397,10 @@ void WarpXOpenPMDPlot::SetStep (int ts, const std::string& dirPrefix, int file_m if (m_CurrentStep >= ts) { // note m_Series is reset in Init(), so using m_Series->iterations.contains(ts) is only able to check the // last written step in m_Series's life time, but not other earlier written steps by other m_Series - std::string warnMsg = - " Warning from openPMD writer: Already written iteration:" + std::to_string(ts); - std::cout << warnMsg << std::endl; - amrex::Warning(warnMsg); + WarpX::GetInstance().RecordWarning("Diagnostics", + " Warning from openPMD writer: Already written iteration:" + + std::to_string(ts) + ); } } diff --git a/Source/Evolve/WarpXEvolve.cpp b/Source/Evolve/WarpXEvolve.cpp index b6dadd8a1..caffe11d7 100644 --- a/Source/Evolve/WarpXEvolve.cpp +++ b/Source/Evolve/WarpXEvolve.cpp @@ -326,6 +326,7 @@ WarpX::Evolve (int numsteps) if (!early_params_checked) { amrex::Print() << "\n"; // better: conditional \n based on return value amrex::ParmParse().QueryUnusedInputs(); + this->PrintGlobalWarnings("FIRST STEP"); //Print the warning list right after the first step. early_params_checked = true; } diff --git a/Source/FieldSolver/FiniteDifferenceSolver/MacroscopicProperties/MacroscopicProperties.cpp b/Source/FieldSolver/FiniteDifferenceSolver/MacroscopicProperties/MacroscopicProperties.cpp index 7166eb99d..577fe9de7 100644 --- a/Source/FieldSolver/FiniteDifferenceSolver/MacroscopicProperties/MacroscopicProperties.cpp +++ b/Source/FieldSolver/FiniteDifferenceSolver/MacroscopicProperties/MacroscopicProperties.cpp @@ -19,6 +19,7 @@ #include <AMReX_BaseFwd.H> #include <memory> +#include <sstream> using namespace amrex; @@ -90,7 +91,11 @@ MacroscopicProperties::ReadParameters () sigma_specified = true; } if (!sigma_specified) { - amrex::Print() << "WARNING: Material conductivity is not specified. Using default vacuum value of " << m_sigma << " in the simulation\n"; + std::stringstream warnMsg; + warnMsg << "Material conductivity is not specified. Using default vacuum value of " << + m_sigma << " in the simulation."; + WarpX::GetInstance().RecordWarning("Macroscopic properties", + warnMsg.str()); } // initialization of sigma (conductivity) with parser if (m_sigma_s == "parse_sigma_function") { @@ -109,7 +114,11 @@ MacroscopicProperties::ReadParameters () epsilon_specified = true; } if (!epsilon_specified) { - amrex::Print() << "WARNING: Material permittivity is not specified. Using default vacuum value of " << m_epsilon << " in the simulation\n"; + std::stringstream warnMsg; + warnMsg << "Material permittivity is not specified. Using default vacuum value of " << + m_epsilon << " in the simulation."; + WarpX::GetInstance().RecordWarning("Macroscopic properties", + warnMsg.str()); } // initialization of epsilon (permittivity) with parser @@ -130,7 +139,11 @@ MacroscopicProperties::ReadParameters () mu_specified = true; } if (!mu_specified) { - amrex::Print() << "WARNING: Material permittivity is not specified. Using default vacuum value of " << m_mu << " in the simulation\n"; + std::stringstream warnMsg; + warnMsg << "Material permittivity is not specified. Using default vacuum value of " << + m_mu << " in the simulation."; + WarpX::GetInstance().RecordWarning("Macroscopic properties", + warnMsg.str()); } // initialization of mu (permeability) with parser diff --git a/Source/FieldSolver/SpectralSolver/SpectralFieldDataRZ.cpp b/Source/FieldSolver/SpectralSolver/SpectralFieldDataRZ.cpp index 9604ccb44..a44ecb47e 100644 --- a/Source/FieldSolver/SpectralSolver/SpectralFieldDataRZ.cpp +++ b/Source/FieldSolver/SpectralSolver/SpectralFieldDataRZ.cpp @@ -86,7 +86,8 @@ SpectralFieldDataRZ::SpectralFieldDataRZ (const int lev, result = cufftPlanMany(&forward_plan[mfi], 1, fft_length, inembed, istride, idist, onembed, ostride, odist, cufft_type, batch); if (result != CUFFT_SUCCESS) { - amrex::AllPrint() << " cufftPlanMany failed! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "cufftPlanMany failed!", WarnPriority::high); } // The backward plane is the same as the forward since the direction is passed when executed. #elif defined(AMREX_USE_HIP) @@ -114,7 +115,8 @@ SpectralFieldDataRZ::SpectralFieldDataRZ (const int lev, grid_size[0], // number of transforms description); if (result != rocfft_status_success) { - amrex::AllPrint() << " rocfft_plan_create failed! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "rocfft_plan_create failed!\n", WarnPriority::high); } result = rocfft_plan_create(&(backward_plan[mfi]), @@ -129,12 +131,14 @@ SpectralFieldDataRZ::SpectralFieldDataRZ (const int lev, grid_size[0], // number of transforms description); if (result != rocfft_status_success) { - amrex::AllPrint() << " rocfft_plan_create failed! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "rocfft_plan_create failed!\n", WarnPriority::high); } result = rocfft_plan_description_destroy(description); if (result != rocfft_status_success) { - amrex::AllPrint() << " rocfft_plan_description_destroy failed! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "rocfft_plan_description_destroy failed!\n", WarnPriority::high); } #else // Create FFTW plans. @@ -240,7 +244,8 @@ SpectralFieldDataRZ::FABZForwardTransform (amrex::MFIter const & mfi, amrex::Box reinterpret_cast<AnyFFT::Complex*>(tmpSpectralField[mfi].dataPtr(mode)), // Complex *out CUFFT_FORWARD); if (result != CUFFT_SUCCESS) { - amrex::AllPrint() << " forward transform using cufftExecZ2Z failed ! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "forward transform using cufftExecZ2Z failed!", WarnPriority::high); } } #elif defined(AMREX_USE_HIP) @@ -257,7 +262,8 @@ SpectralFieldDataRZ::FABZForwardTransform (amrex::MFIter const & mfi, amrex::Box void* out_array[] = {(void*)(tmpSpectralField[mfi].dataPtr(mode))}; result = rocfft_execute(forward_plan[mfi], in_array, out_array, execinfo); if (result != rocfft_status_success) { - amrex::AllPrint() << " forward transform using rocfft_execute failed ! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "forward transform using rocfft_execute failed!", WarnPriority::high); } } @@ -349,7 +355,8 @@ SpectralFieldDataRZ::FABZBackwardTransform (amrex::MFIter const & mfi, amrex::Bo reinterpret_cast<AnyFFT::Complex*>(tempHTransformed[mfi].dataPtr(mode)), // Complex *out CUFFT_INVERSE); if (result != CUFFT_SUCCESS) { - amrex::AllPrint() << " backwardtransform using cufftExecZ2Z failed ! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "backwardtransform using cufftExecZ2Z failed!", WarnPriority::high); } } #elif defined(AMREX_USE_HIP) @@ -366,7 +373,8 @@ SpectralFieldDataRZ::FABZBackwardTransform (amrex::MFIter const & mfi, amrex::Bo void* out_array[] = {(void*)(tempHTransformed[mfi].dataPtr(mode))}; result = rocfft_execute(backward_plan[mfi], in_array, out_array, execinfo); if (result != rocfft_status_success) { - amrex::AllPrint() << " forward transform using rocfft_execute failed ! \n"; + WarpX::GetInstance().RecordWarning("Spectral solver", + "forward transform using rocfft_execute failed!", WarnPriority::high); } } diff --git a/Source/Initialization/PlasmaInjector.cpp b/Source/Initialization/PlasmaInjector.cpp index 1e4b632c0..572267d0e 100644 --- a/Source/Initialization/PlasmaInjector.cpp +++ b/Source/Initialization/PlasmaInjector.cpp @@ -131,9 +131,11 @@ PlasmaInjector::PlasmaInjector (int ispecies, const std::string& name) bool mass_is_specified = queryWithParser(pp_species_name, "mass", mass); if ( charge_is_specified && species_is_specified ){ - amrex::Print() << "WARNING: Both '" << species_name << ".charge' and " - << species_name << ".species_type' are specified\n'" - << species_name << ".charge' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + species_name + ".charge' and " + + species_name + ".species_type' are specified.\n" + + species_name + ".charge' will take precedence.\n"); + } if (!charge_is_specified && !species_is_specified && injection_style != "external_file"){ // external file will throw own assertions below if charge cannot be found @@ -141,9 +143,10 @@ PlasmaInjector::PlasmaInjector (int ispecies, const std::string& name) } if ( mass_is_specified && species_is_specified ){ - amrex::Print() << "WARNING: Both '" << species_name << ".mass' and " - << species_name << ".species_type' are specified\n'" - << species_name << ".mass' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + species_name + ".mass' and " + + species_name + ".species_type' are specified.\n" + + species_name + ".mass' will take precedence.\n"); } if (!mass_is_specified && !species_is_specified && injection_style != "external_file"){ // external file will throw own assertions below if mass cannot be found @@ -343,14 +346,16 @@ PlasmaInjector::PlasmaInjector (int ispecies, const std::string& name) "'" + ps_name + ".species_type' in your input file!\n"); if (charge_is_specified) { - amrex::Print() << "WARNING: Both '" << ps_name << ".charge' and '" - << ps_name << ".injection_file' specify a charge.\n'" - << ps_name << ".charge' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + ps_name + ".charge' and '" + + ps_name + ".injection_file' specify a charge.\n'" + + ps_name + ".charge' will take precedence.\n"); } else if (species_is_specified) { - amrex::Print() << "WARNING: Both '" << ps_name << ".species_type' and '" - << ps_name << ".injection_file' specify a charge.\n'" - << ps_name << ".species_type' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + ps_name + ".species_type' and '" + + ps_name + ".injection_file' specify a charge.\n'" + + ps_name + ".species_type' will take precedence.\n"); } else { // TODO: Add ASSERT_WITH_MESSAGE to test if charge is a constant record @@ -360,14 +365,16 @@ PlasmaInjector::PlasmaInjector (int ispecies, const std::string& name) charge = p_q * charge_unit; } if (mass_is_specified) { - amrex::Print() << "WARNING: Both '" << ps_name << ".mass' and '" - << ps_name << ".injection_file' specify a mass.\n'" - << ps_name << ".mass' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + ps_name + ".mass' and '" + + ps_name + ".injection_file' specify a charge.\n'" + + ps_name + ".mass' will take precedence.\n"); } else if (species_is_specified) { - amrex::Print() << "WARNING: Both '" << ps_name << ".species_type' and '" - << ps_name << ".injection_file' specify a mass.\n'" - << ps_name << ".species_type' will take precedence.\n"; + WarpX::GetInstance().RecordWarning("Species", + "Both '" + ps_name + ".species_type' and '" + + ps_name + ".injection_file' specify a mass.\n'" + + ps_name + ".species_type' will take precedence.\n"); } else { // TODO: Add ASSERT_WITH_MESSAGE to test if mass is a constant record diff --git a/Source/Initialization/WarpXInitData.cpp b/Source/Initialization/WarpXInitData.cpp index d8b6cdd16..98c4b448c 100644 --- a/Source/Initialization/WarpXInitData.cpp +++ b/Source/Initialization/WarpXInitData.cpp @@ -62,7 +62,7 @@ #include <string> #include <utility> #include <vector> - +#include <sstream> using namespace amrex; @@ -757,8 +757,9 @@ WarpX::PerformanceHints () for (int ilev = 0; ilev <= finestLevel(); ++ilev) { total_nboxes += boxArray(ilev).size(); } - if (ParallelDescriptor::NProcs() > total_nboxes) - amrex::Print() << "\n[Warning] [Performance] Too many resources / too little work!\n" + if (ParallelDescriptor::NProcs() > total_nboxes){ + std::stringstream warnMsg; + warnMsg << "Too many resources / too little work!\n" << " It looks like you requested more compute resources than " << "there are total number of boxes of cells available (" << total_nboxes << "). " @@ -773,6 +774,9 @@ WarpX::PerformanceHints () << " More information:\n" << " https://warpx.readthedocs.io/en/latest/running_cpp/parallelization.html\n"; + WarpX::GetInstance().RecordWarning("Performance", warnMsg.str(), WarnPriority::high); + } + // TODO: warn if some ranks have disproportionally more work than all others // tricky: it can be ok to assign "vacuum" boxes to some ranks w/o slowing down // all other ranks; we need to measure this with our load-balancing diff --git a/Source/Laser/LaserProfilesImpl/LaserProfileFromTXYEFile.cpp b/Source/Laser/LaserProfilesImpl/LaserProfileFromTXYEFile.cpp index b9b31ec7e..b99a9e0a3 100644 --- a/Source/Laser/LaserProfilesImpl/LaserProfileFromTXYEFile.cpp +++ b/Source/Laser/LaserProfilesImpl/LaserProfileFromTXYEFile.cpp @@ -8,6 +8,7 @@ #include "Utils/WarpXUtil.H" #include "Utils/WarpX_Complex.H" +#include "WarpX.H" #include <AMReX.H> #include <AMReX_Algorithm.H> @@ -45,8 +46,10 @@ WarpXLaserProfiles::FromTXYEFileLaserProfile::init ( { if (!std::numeric_limits< double >::is_iec559) { - Print() << R"(Warning: double does not comply with IEEE 754: bad - things will happen parsing the X, Y and T profiles for the laser!)"; + WarpX::GetInstance().RecordWarning("Laser", + "(Double does not comply with IEEE 754: bad" + "things will happen parsing the X, Y and T profiles for the laser!)", + WarnPriority::high); } // Parse the TXYE file diff --git a/Source/Particles/LaserParticleContainer.cpp b/Source/Particles/LaserParticleContainer.cpp index 1f5a7e8d0..1790f3019 100644 --- a/Source/Particles/LaserParticleContainer.cpp +++ b/Source/Particles/LaserParticleContainer.cpp @@ -116,7 +116,9 @@ LaserParticleContainer::LaserParticleContainer (AmrCore* amr_core, int ispecies, pp_laser_name.query("min_particles_per_mode", m_min_particles_per_mode); if (m_e_max == amrex::Real(0.)){ - amrex::Print() << m_laser_name << " with zero amplitude disabled.\n"; + WarpX::GetInstance().RecordWarning("Laser", + m_laser_name + " with zero amplitude disabled.", + WarnPriority::low); m_enabled = false; return; // Disable laser if amplitude is 0 } @@ -290,7 +292,9 @@ LaserParticleContainer::InitData () InitData(maxLevel()); if(!do_continuous_injection && (TotalNumberOfParticles() == 0)){ - amrex::Print() << "WARNING: laser antenna is completely out of the simulation box !!!\n"; + WarpX::GetInstance().RecordWarning("Laser", + "The antenna is completely out of the simulation box for laser " + m_laser_name, + WarnPriority::high); m_enabled = false; // Disable laser if antenna is completely out of the simulation box } } @@ -662,8 +666,10 @@ LaserParticleContainer::ComputeWeightMobility (Real Sx, Real Sy) // calculated antenna particle velocities may exceed c, which can cause a segfault. constexpr Real warning_tol = 0.1_rt; if (m_wavelength < std::min(Sx,Sy)*warning_tol){ - amrex::Warning("WARNING: laser wavelength seems to be much smaller than the grid size." - " This may cause a segmentation fault"); + WarpX::GetInstance().RecordWarning("Laser", + "Laser wavelength seems to be much smaller than the grid size." + " This may cause a segmentation fault", + WarnPriority::high); } } diff --git a/Source/Particles/MultiParticleContainer.cpp b/Source/Particles/MultiParticleContainer.cpp index 6cf890821..b877708f9 100644 --- a/Source/Particles/MultiParticleContainer.cpp +++ b/Source/Particles/MultiParticleContainer.cpp @@ -1007,8 +1007,9 @@ void MultiParticleContainer::InitQuantumSync () m_quantum_sync_photon_creation_energy_threshold = temp; } else{ - amrex::Print() << "Using default value (2*me*c^2)" << - " for photon energy creation threshold \n" ; + WarpX::GetInstance().RecordWarning("QED", + "Using default value (2*me*c^2) for photon energy creation threshold", + WarnPriority::low); } // qs_minimum_chi_part is the minimum chi parameter to be @@ -1024,7 +1025,9 @@ void MultiParticleContainer::InitQuantumSync () } if(lookup_table_mode == "generate"){ - amrex::Print() << "Quantum Synchrotron table will be generated. \n" ; + WarpX::GetInstance().RecordWarning("QED", + "A new Quantum Synchrotron table will be generated.", + WarnPriority::low); #ifndef WARPX_QED_TABLE_GEN amrex::Error("Error: Compile with QED_TABLE_GEN=TRUE to enable table generation!\n"); #else @@ -1032,9 +1035,11 @@ void MultiParticleContainer::InitQuantumSync () #endif } else if(lookup_table_mode == "load"){ - amrex::Print() << "Quantum Synchrotron table will be read from file. \n" ; std::string load_table_name; pp_qed_qs.query("load_table_from", load_table_name); + WarpX::GetInstance().RecordWarning("QED", + "The Quantum Synchrotron table will be read from the file: " + load_table_name, + WarnPriority::low); if(load_table_name.empty()){ amrex::Abort("Quantum Synchrotron table name should be provided"); } @@ -1045,7 +1050,10 @@ void MultiParticleContainer::InitQuantumSync () qs_minimum_chi_part); } else if(lookup_table_mode == "builtin"){ - amrex::Print() << "Built-in Quantum Synchrotron table will be used. \n" ; + WarpX::GetInstance().RecordWarning("QED", + "The built-in Quantum Synchrotron table will be used." + "This low resolution table is intended for testing purposes only.", + WarnPriority::medium); m_shr_p_qs_engine->init_builtin_tables(qs_minimum_chi_part); } else{ @@ -1075,7 +1083,9 @@ void MultiParticleContainer::InitBreitWheeler () } if(lookup_table_mode == "generate"){ - amrex::Print() << "Breit Wheeler table will be generated. \n" ; + WarpX::GetInstance().RecordWarning("QED", + "A new Breit Wheeler table will be generated.", + WarnPriority::low); #ifndef WARPX_QED_TABLE_GEN amrex::Error("Error: Compile with QED_TABLE_GEN=TRUE to enable table generation!\n"); #else @@ -1083,9 +1093,11 @@ void MultiParticleContainer::InitBreitWheeler () #endif } else if(lookup_table_mode == "load"){ - amrex::Print() << "Breit Wheeler table will be read from file. \n" ; std::string load_table_name; pp_qed_bw.query("load_table_from", load_table_name); + WarpX::GetInstance().RecordWarning("QED", + "The Breit Wheeler table will be read from the file:" + load_table_name, + WarnPriority::low); if(load_table_name.empty()){ amrex::Abort("Breit Wheeler table name should be provided"); } @@ -1096,7 +1108,10 @@ void MultiParticleContainer::InitBreitWheeler () table_data, bw_minimum_chi_part); } else if(lookup_table_mode == "builtin"){ - amrex::Print() << "Built-in Breit Wheeler table will be used. \n" ; + WarpX::GetInstance().RecordWarning("QED", + "The built-in Breit Wheeler table will be used." + "This low resolution table is intended for testing purposes only.", + WarnPriority::medium); m_shr_p_bw_engine->init_builtin_tables(bw_minimum_chi_part); } else{ diff --git a/Source/Particles/PhysicalParticleContainer.cpp b/Source/Particles/PhysicalParticleContainer.cpp index d6238528f..ea7287bac 100644 --- a/Source/Particles/PhysicalParticleContainer.cpp +++ b/Source/Particles/PhysicalParticleContainer.cpp @@ -101,6 +101,7 @@ #include <string> #include <utility> #include <vector> +#include <sstream> using namespace amrex; @@ -533,9 +534,11 @@ PhysicalParticleContainer::AddPlasmaFromFile(ParticleReal q_tot, if (q_tot != 0.0) { weight = std::abs(q_tot) / ( std::abs(charge) * ParticleReal(npart) ); if (ps.contains("weighting")) { - Print() << "WARNING: Both '" << ps_name << ".q_tot' and '" + std::stringstream ss; + ss << "Both '" << ps_name << ".q_tot' and '" << ps_name << ".injection_file' specify a total charge.\n'" - << ps_name << ".q_tot' will take precedence.\n"; + << ps_name << ".q_tot' will take precedence."; + WarpX::GetInstance().RecordWarning("Species", ss.str()); } } // ED-PIC extension? @@ -570,7 +573,9 @@ PhysicalParticleContainer::AddPlasmaFromFile(ParticleReal q_tot, } auto const np = particle_z.size(); if (np < npart) { - Print() << "WARNING: Simulation box doesn't cover all particles\n"; + WarpX::GetInstance().RecordWarning("Species", + "Simulation box doesn't cover all particles", + WarnPriority::high); } } // IO Processor auto const np = particle_z.size(); @@ -2517,8 +2522,10 @@ PhysicalParticleContainer::InitIonizationModule () if (!do_field_ionization) return; ParmParse pp_species_name(species_name); if (charge != PhysConst::q_e){ - amrex::Warning( - "charge != q_e for ionizable species: overriding user value and setting charge = q_e."); + WarpX::GetInstance().RecordWarning("Species", + "charge != q_e for ionizable species '" + + species_name + "':" + + "overriding user value and setting charge = q_e."); charge = PhysConst::q_e; } queryWithParser(pp_species_name, "ionization_initial_level", ionization_initial_level); diff --git a/Source/Particles/Resampling/LevelingThinning.cpp b/Source/Particles/Resampling/LevelingThinning.cpp index 33ad87630..716b0d2ab 100644 --- a/Source/Particles/Resampling/LevelingThinning.cpp +++ b/Source/Particles/Resampling/LevelingThinning.cpp @@ -9,6 +9,7 @@ #include "Particles/WarpXParticleContainer.H" #include "Utils/ParticleUtils.H" #include "Utils/WarpXUtil.H" +#include "WarpX.H" #include <AMReX.H> #include <AMReX_BLassert.H> @@ -36,8 +37,10 @@ LevelingThinning::LevelingThinning (const std::string species_name) "Resampling target ratio should be strictly greater than 0"); if (m_target_ratio <= 1._rt) { - amrex::Warning("WARNING: target ratio for leveling thinning is smaller or equal to one." - " It is possible that no particle will be removed during resampling"); + WarpX::GetInstance().RecordWarning("Species", + "For species '" + species_name + "' " + + "target ratio for leveling thinning is smaller or equal to one." + + "It is possible that no particle will be removed during resampling"); } queryWithParser(pp_species_name, "resampling_algorithm_min_ppc", m_min_ppc); diff --git a/Source/Utils/CMakeLists.txt b/Source/Utils/CMakeLists.txt index 40d3a2c31..26982fb91 100644 --- a/Source/Utils/CMakeLists.txt +++ b/Source/Utils/CMakeLists.txt @@ -7,8 +7,11 @@ target_sources(WarpX MPIInitHelpers.cpp ParticleUtils.cpp RelativeCellPosition.cpp + WarnManager.cpp WarpXAlgorithmSelection.cpp WarpXMovingWindow.cpp WarpXTagging.cpp WarpXUtil.cpp ) + +add_subdirectory(MsgLogger) diff --git a/Source/Utils/MPIInitHelpers.cpp b/Source/Utils/MPIInitHelpers.cpp index d4b049d7c..da9409e31 100644 --- a/Source/Utils/MPIInitHelpers.cpp +++ b/Source/Utils/MPIInitHelpers.cpp @@ -6,6 +6,8 @@ */ #include "MPIInitHelpers.H" +#include "WarpX.H" + #include <AMReX_Config.H> #include <AMReX_ParallelDescriptor.H> #include <AMReX_Print.H> @@ -16,6 +18,7 @@ #include <string> #include <utility> +#include <sstream> namespace utils { @@ -47,16 +50,21 @@ namespace utils auto const thread_provided = mpi_thread_levels.second; auto mtn = amrex::ParallelDescriptor::mpi_level_to_string; - if( thread_provided < thread_required ) - amrex::Print() << "WARNING: Provided MPI thread safety level (" + std::stringstream ss; + if( thread_provided < thread_required ){ + ss << "WARNING: Provided MPI thread safety level (" << mtn(thread_provided) << ") is LOWER than requested " << mtn(thread_required) << "). This might lead to undefined " << "results in asynchronous operations (e.g. async_io)."; - if( thread_provided > thread_required ) - amrex::Print() << "NOTE: Provided MPI thread safety level (" + WarpX::GetInstance().RecordWarning("MPI", ss.str(), WarnPriority::high); + } + if( thread_provided > thread_required ){ + ss << "NOTE: Provided MPI thread safety level (" << mtn(thread_provided) << ") is stricter than requested " << mtn(thread_required) << "). This might reduce multi-node " << "communication performance."; + WarpX::GetInstance().RecordWarning("MPI", ss.str()); + } #else amrex::ignore_unused(mpi_thread_levels); #endif diff --git a/Source/Utils/Make.package b/Source/Utils/Make.package index 3b1ff6a84..f1b31104c 100644 --- a/Source/Utils/Make.package +++ b/Source/Utils/Make.package @@ -7,7 +7,10 @@ CEXE_sources += CoarsenMR.cpp CEXE_sources += Interpolate.cpp CEXE_sources += IntervalsParser.cpp CEXE_sources += MPIInitHelpers.cpp +CEXE_sources += WarnManager.cpp CEXE_sources += RelativeCellPosition.cpp CEXE_sources += ParticleUtils.cpp VPATH_LOCATIONS += $(WARPX_HOME)/Source/Utils + +include $(WARPX_HOME)/Source/Utils/MsgLogger/Make.package diff --git a/Source/Utils/MsgLogger/CMakeLists.txt b/Source/Utils/MsgLogger/CMakeLists.txt new file mode 100644 index 000000000..bd167d0e4 --- /dev/null +++ b/Source/Utils/MsgLogger/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(WarpX + PRIVATE + MsgLogger.cpp +) diff --git a/Source/Utils/MsgLogger/Make.package b/Source/Utils/MsgLogger/Make.package new file mode 100644 index 000000000..e02476222 --- /dev/null +++ b/Source/Utils/MsgLogger/Make.package @@ -0,0 +1,3 @@ +CEXE_sources += MsgLogger.cpp + +VPATH_LOCATIONS += $(WARPX_HOME)/Source/Utils/MsgLogger diff --git a/Source/Utils/MsgLogger/MsgLogger.H b/Source/Utils/MsgLogger/MsgLogger.H new file mode 100644 index 000000000..ca55289b2 --- /dev/null +++ b/Source/Utils/MsgLogger/MsgLogger.H @@ -0,0 +1,293 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#ifndef WARPX_MSG_LOGGER_H_ +#define WARPX_MSG_LOGGER_H_ + +#include <AMReX.H> + +#include <cstdint> +#include <map> +#include <string> +#include <utility> +#include <vector> + +namespace Utils{ +namespace MsgLogger{ + + /** Priority is recorded together with messages. It influences + * the display order and the appearance of a message. + */ + enum class Priority + { + /** Low priority message */ + low, + /** Medium priority message */ + medium, + /** High priority message */ + high + }; + + /** + * \brief This function converts a Priority into the corresponding + * string (e.g, Priority::low --> "low") + * + * @param[in] priority the priority + * @return the corresponding string + */ + std::string PriorityToString(const Priority& priority); + + /** + * \brief This function converts a string into the corresponding + * priority (e.g, "low" --> Priority::low) + * + * @param[in] priority_string the priority string + * @return the corresponding priority + */ + Priority StringToPriority(const std::string& priority_string); + + /** + * This struct represents a message, which is composed by + * a topic, a text and a priority. It also provides methods for + * serialization and deserialization. + */ + struct Msg + { + std::string topic /*! The message topic*/; + std::string text /*! The message text*/; + Priority priority /*! The priority of the message*/; + + /** + * \brief This function returns a byte representation of the struct + * + * @return a byte vector + */ + std::vector<char> serialize() const; + + /** + * \brief This function generates a Msg struct from a byte vector + * + * @param[in] it iterator of a byte array + * @return a Msg struct + */ + static Msg deserialize(std::vector<char>::const_iterator& it); + + /** + * \brief Same as static Msg deserialize(std::vector<char>::const_iterator& it) + * but accepting an rvalue as an argument + * + * @param[in] it iterator of a byte array + * @return a Msg struct + */ + static Msg deserialize(std::vector<char>::const_iterator&& it); + }; + + /** + * This struct represents a message with counter, which is composed + * by a message and a counter. The latter is intended to store the + * number of times a message is recorded. The struct also provides + * methods for serialization and deserialization. + */ + struct MsgWithCounter + { + Msg msg /*! A message*/; + std::int64_t counter /*! The counter*/; + + /** + * \brief This function returns a byte representation of the struct + * + * @return a byte vector + */ + std::vector<char> serialize() const; + + /** + * \brief This function generates a MsgWithCounter struct from a byte vector + * + * @param[in] it iterator of a byte array + * @return a MsgWithCounter struct + */ + static MsgWithCounter deserialize(std::vector<char>::const_iterator& it); + + /** + * \brief Same as static Msg MsgWithCounter(std::vector<char>::const_iterator& it) + * but accepting an rvalue as an argument + * + * @param[in] it iterator of a byte array + * @return a MsgWithCounter struct + */ + static MsgWithCounter deserialize(std::vector<char>::const_iterator&& it); + }; + + /** + * This struct represents a message with counter and ranks, which is + * composed by a message with counter, a bool flag and a std::vector<int>. + * The bool flag is used to store if a message is emitted by all the ranks. + * The std::vector<int> is used to store the affected ranks + * (note: when we switch to C++17, should we consider variants?). + * The struct also provides methods for serialization and deserialization. + */ + struct MsgWithCounterAndRanks + { + MsgWithCounter msg_with_counter /*! A message with counter*/; + bool all_ranks /*! Flag to store if message is emitted by all ranks*/; + std::vector<int> ranks /*! Affected ranks*/; + + /** + * \brief This function returns a byte representation of the struct + * + * @return a byte vector + */ + std::vector<char> serialize() const; + + /** + * \brief This function generates a MsgWithCounterAndRanks struct from a byte vector + * + * @param[in] it iterator of a byte array + * @return a MsgWithCounterAndRanks struct + */ + static MsgWithCounterAndRanks deserialize(std::vector<char>::const_iterator& it); + + /** + * \brief Same as static Msg MsgWithCounterAndRanks(std::vector<char>::const_iterator& it) + * but accepting an rvalue as an argument + * + * @param[in] it iterator of a byte array + * @return a MsgWithCounterAndRanks struct + */ + static MsgWithCounterAndRanks deserialize(std::vector<char>::const_iterator&& it); + }; + + /** + * \brief This implements the < operator for Msg. + * Warning messages are first ordered by priority (warning: high < medium < low + * to give precedence to higher priorities), then by topic (alphabetically), + * and finally by text (alphabetically). + * + * @param[in] l a Msg + * @param[in] r a Msg + * @return true if l<r, false otherwise + */ + constexpr bool operator<(const Msg& l, const Msg& r) + { + return + (l.priority > r.priority) || + ((l.priority == r.priority) && (l.topic < r.topic)) || + ((l.priority == r.priority) && (l.topic == r.topic) && (l.text < r.text)); + } + + /** + * This class is responsible for storing messages and merging messages + * collected by different processes. + */ + class Logger + { + public: + + /** + * \brief The constructor. + */ + Logger(); + + /** + * \brief This function records a message + * + * @param[in] msg a Msg struct + */ + void record_msg(Msg msg); + + /** + * \brief This function returns a vector containing the recorded messages + * + * @return a vector of the recorded messages + */ + std::vector<Msg> get_msgs() const; + + /** + * \brief This function returns a vector containing the recorded messages + * with the corresponding counters + * + * @return a vector of the recorded messages with counters + */ + std::vector<MsgWithCounter> get_msgs_with_counter() const; + + /** + * \brief This collective function generates a vector containing the messages + * with counters and emitting ranks by gathering data from + * all the ranks + * + * @return a vector of messages with counters and ranks if I/O rank, an empty vector otherwise + */ + std::vector<MsgWithCounterAndRanks> + collective_gather_msgs_with_counter_and_ranks() const; + + private: + + /** + * \brief This function implements the trivial special case of + * collective_gather_msgs_with_counter_and_ranks when there is only one rank. + * + * @return a vector of messages with counters and ranks + */ + std::vector<MsgWithCounterAndRanks> + one_rank_gather_msgs_with_counter_and_ranks() const; + +#ifdef AMREX_USE_MPI + /** + * \brief This collective function finds the rank having the + * most messages and how many messages this rank has. The + * rank having the most messages is designated as "gather rank". + * + * @param[in] how_many_msgs the number of messages that the current rank has + * @return a pair containing the ID of the "gather rank" and its number of messages + */ + std::pair<int, int> find_gather_rank_and_its_msgs( + int how_many_msgs) const; + + /** + * \brief This function uses data gathered on the "gather rank" to generate + * a vector of messages with global counters and emitting rank lists + * + * @param[in] my_msg_map messages and counters of the current rank (as a map) + * @param[in] all_data a byte array containing all the data gathered on the gather rank + * @param[in] displacements a vector of displacements to access data corresponding to a given rank in all_data + * @param[in] gather_rank the ID of the "gather rank" + * @return if gather_rank==m_rank a vector of messages with global counters and emitting rank lists, dummy data otherwise + */ + std::vector<MsgWithCounterAndRanks> + compute_msgs_with_counter_and_ranks( + const std::map<Msg,std::int64_t>& my_msg_map, + const std::vector<char>& all_data, + const std::vector<int>& displacements, + const int gather_rank + ) const; + + + /** + * \brief If the gather_rank is not the I/O rank, this function sends msgs_with_counter_and_ranks + * to the I/O rank. This function uses point-to-point communications. + * + * @param[in] msgs_with_counter_and_ranks a vector of messages with counters and ranks + * @param[in] gather_rank the ID of the "gather rank" + */ + void + swap_with_io_rank( + std::vector<MsgWithCounterAndRanks>& msgs_with_counter_and_ranks, + int gather_rank) const; + +#endif + + int m_rank = 0 /*! MPI rank of the current process*/; + int m_num_procs = 0 /*! Number of MPI ranks*/; + int m_io_rank = 0 /*! Rank of the I/O process*/; + bool m_am_i_io = false /*! Flag to store if the process is responsible for I/O*/; + + std::map<Msg, std::int64_t> m_messages /*! This stores a map to associate warning messages with the corresponding counters*/; + }; +} +} + +#endif //WARPX_MSG_LOGGER_H_ diff --git a/Source/Utils/MsgLogger/MsgLogger.cpp b/Source/Utils/MsgLogger/MsgLogger.cpp new file mode 100644 index 000000000..c1c1f9324 --- /dev/null +++ b/Source/Utils/MsgLogger/MsgLogger.cpp @@ -0,0 +1,653 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#include "MsgLogger.H" + +#include "MsgLoggerSerialization.H" + +#ifdef AMREX_USE_MPI +# include <AMReX_ParallelDescriptor.H> +#endif +#include <AMReX_Print.H> + +#include <iostream> +#include <sstream> +#include <numeric> + +using namespace Utils::MsgLogger; + +#ifdef AMREX_USE_MPI +// Helper functions used only in this source file +namespace +{ + /** + * \brief This collective function returns the messages of the "gather rank" + * as a byte array. + * + * @param[in] my_msgs the messages of the current rank + * @param[in] gather_rank the ID of the "gather rank" + * @param[in] my_rank the ID of the current rank + * @return the messages of the "gather rank" as a byte array + */ + std::vector<char> + get_serialized_gather_rank_msgs( + const std::vector<Msg>& my_msgs, + const int gather_rank, + const int my_rank); + + /** + * \brief This function generates data to send back to the "gather rank" + * + * @param[in] serialized_gather_rank_msgs the serialized messages of the gather rank + * @param[in] gather_rank_how_many_msgs number of messages of the "gather rank" + * @param[in] my_msg_map messages and counters of the current rank (as a map) + * @param[in] is_gather_rank true if the rank is the "gather rank", false otherwise + * @return a byte array to send back to the "gather rank" (or a dummy vector in case is_gather_rank is true) + */ + std::vector<char> + compute_package_for_gather_rank( + const std::vector<char>& serialized_gather_rank_msgs, + const std::int64_t gather_rank_how_many_msgs, + const std::map<Msg, std::int64_t>& my_msg_map, + const bool is_gather_rank + ); + + /** + * \brief This collective function gathers data generated with compute_package_for_gather_rank + * to the gather rank. + * If my_rank != gather_rank the function returns dummy data. Otherwise the function returns + * a pair containing: + * 1) a byte array containing info on messages seen by other ranks + * 2) a vector of displacements to access data corresponding to a given rank + * + * @param[in] package_for_gather_rank a byte array generated by compute_package_for_gather_rank + * @param[in] gather_rank the ID of the "gather rank" + * @param[in] my_rank the ID of the current rank + * @return (see function description) + */ + std::pair<std::vector<char>, std::vector<int>> + gather_all_data( + const std::vector<char>& package_for_gather_rank, + const int gather_rank, const int my_rank); + + /** + * \brief This function converts a vector of Msg struct into a byte array + * + * @param[in] msgs the vector of Msg struct + * @return a byte array + */ + std::vector<char> serialize_msgs( + const std::vector<Msg>& msgs); + + /** + * \brief This function converts a byte array into a vector of Msg struct + * + * @param[in] serialized the byte array + * @return a vector of Msg struct + */ + std::vector<Msg> deserialize_msgs( + const std::vector<char>& serialized); +} +#endif + +std::string Utils::MsgLogger::PriorityToString(const Priority& priority) +{ + if(priority == Priority::high) + return "high"; + else if (priority == Priority::medium) + return "medium"; + else + return "low"; +} + +Priority Utils::MsgLogger::StringToPriority(const std::string& priority_string) +{ + if(priority_string == "high") + return Priority::high; + else if (priority_string == "medium") + return Priority::medium; + else if (priority_string == "low") + return Priority::low; + else + amrex::Abort( + "Priority string '" + priority_string + "' not recognized"); + + //this silences a "non-void function does not return a value in all control paths" warning + return Priority::low; +} + +std::vector<char> Msg::serialize() const +{ + std::vector<char> serialized_msg; + + put_in(this->topic, serialized_msg); + put_in(this->text, serialized_msg); + const int int_priority = static_cast<int>(this->priority); + put_in(int_priority, serialized_msg); + + return serialized_msg; +} + +Msg Msg::deserialize (std::vector<char>::const_iterator& it) +{ + Msg msg; + + msg.topic = get_out<std::string> (it); + msg.text = get_out<std::string> (it); + msg.priority = static_cast<Priority> (get_out<int> (it)); + + return msg; +} + +Msg Msg::deserialize (std::vector<char>::const_iterator&& it) +{ + return Msg::deserialize(it); +} + +std::vector<char> MsgWithCounter::serialize() const +{ + std::vector<char> serialized_msg_with_counter; + + put_in_vec(msg.serialize(), serialized_msg_with_counter); + put_in(this->counter, serialized_msg_with_counter); + + return serialized_msg_with_counter; +} + +MsgWithCounter MsgWithCounter::deserialize (std::vector<char>::const_iterator& it) +{ + MsgWithCounter msg_with_counter; + + const auto vec = get_out_vec<char>(it); + auto iit = vec.begin(); + msg_with_counter.msg = Msg::deserialize(iit); + msg_with_counter.counter = get_out<std::int64_t> (it); + + return msg_with_counter; +} + +MsgWithCounter MsgWithCounter::deserialize (std::vector<char>::const_iterator&& it) +{ + return MsgWithCounter::deserialize(it); +} + +std::vector<char> MsgWithCounterAndRanks::serialize() const +{ + std::vector<char> serialized_msg_with_counter_and_ranks; + + put_in_vec(this->msg_with_counter.serialize(), serialized_msg_with_counter_and_ranks); + put_in(this->all_ranks, serialized_msg_with_counter_and_ranks); + put_in_vec(this->ranks, serialized_msg_with_counter_and_ranks); + + return serialized_msg_with_counter_and_ranks; +} + +MsgWithCounterAndRanks +MsgWithCounterAndRanks::deserialize (std::vector<char>::const_iterator& it) +{ + MsgWithCounterAndRanks msg_with_counter_and_ranks; + + const auto vec = get_out_vec<char>(it); + auto iit = vec.begin(); + msg_with_counter_and_ranks.msg_with_counter = MsgWithCounter::deserialize(iit); + msg_with_counter_and_ranks.all_ranks = get_out<bool>(it); + msg_with_counter_and_ranks.ranks = get_out_vec<int>(it); + + return msg_with_counter_and_ranks; +} + +MsgWithCounterAndRanks +MsgWithCounterAndRanks::deserialize (std::vector<char>::const_iterator&& it) +{ + return MsgWithCounterAndRanks::deserialize(it); +} + +Logger::Logger(){ + m_rank = amrex::ParallelDescriptor::MyProc(); + m_num_procs = amrex::ParallelDescriptor::NProcs(); + m_io_rank = amrex::ParallelDescriptor::IOProcessorNumber(); + m_am_i_io = (m_rank == m_io_rank); +} + +void Logger::record_msg(Msg msg) +{ + m_messages[msg]++; +} + +std::vector<Msg> Logger::get_msgs() const +{ + auto res = std::vector<Msg>{}; + + for (const auto& msg_w_counter : m_messages) + res.emplace_back(msg_w_counter.first); + + return res; +} + +std::vector<MsgWithCounter> Logger::get_msgs_with_counter() const +{ + auto res = std::vector<MsgWithCounter>{}; + + for (const auto& msg : m_messages) + res.emplace_back(MsgWithCounter{msg.first, msg.second}); + + return res; +} + +std::vector<MsgWithCounterAndRanks> +Logger::collective_gather_msgs_with_counter_and_ranks() const +{ + +#ifdef AMREX_USE_MPI + + // Trivial case of only one rank + if (m_num_procs == 1) + return one_rank_gather_msgs_with_counter_and_ranks(); + + // Find out who is the "gather rank" and how many messages it has + const auto my_msgs = get_msgs(); + const auto how_many_msgs = my_msgs.size(); + int gather_rank = 0; + std::int64_t gather_rank_how_many_msgs = 0; + std::tie(gather_rank, gather_rank_how_many_msgs) = + find_gather_rank_and_its_msgs(how_many_msgs); + + // If the "gather rank" has zero messages there are no messages at all + if(gather_rank_how_many_msgs == 0) + return std::vector<MsgWithCounterAndRanks>{}; + + // All the ranks receive the msgs of the "gather rank" as a byte array + const auto serialized_gather_rank_msgs = + ::get_serialized_gather_rank_msgs(my_msgs, gather_rank, m_rank); + + // Each rank assembles a message to send back to the "gather rank" + const bool is_gather_rank = (gather_rank == m_rank); + const auto package_for_gather_rank = + ::compute_package_for_gather_rank( + serialized_gather_rank_msgs, + gather_rank_how_many_msgs, + m_messages, is_gather_rank); + + // Send back all the data to the "gather rank" + auto all_data = std::vector<char>{}; + auto displacements = std::vector<int>{}; + std::tie(all_data, displacements) = + ::gather_all_data( + package_for_gather_rank, + gather_rank, m_rank); + + // Use the gathered data to generate (on the "gather rank") a vector of all the + // messages seen by all the ranks with the corresponding counters and + // emitting rank lists. + auto msgs_with_counter_and_ranks = + compute_msgs_with_counter_and_ranks( + m_messages, + all_data, + displacements, + gather_rank); + + // If the current rank is not the I/O rank, send msgs_with_counter_and_ranks + // to the I/O rank + swap_with_io_rank( + msgs_with_counter_and_ranks, + gather_rank); + + return msgs_with_counter_and_ranks; +#else + return one_rank_gather_msgs_with_counter_and_ranks(); +#endif +} + +std::vector<MsgWithCounterAndRanks> +Logger::one_rank_gather_msgs_with_counter_and_ranks() const +{ + std::vector<MsgWithCounterAndRanks> res; + for (const auto& el : m_messages) + { + res.emplace_back( + MsgWithCounterAndRanks{ + MsgWithCounter{el.first, el.second}, + true, + std::vector<int>{m_rank}}); + } + return res; +} + +#ifdef AMREX_USE_MPI + +std::pair<int,int> Logger::find_gather_rank_and_its_msgs(int how_many_msgs) const +{ + int max_items = 0; + int max_rank = 0; + + const auto num_msg = + amrex::ParallelDescriptor::Gather(how_many_msgs, m_io_rank); + + if (m_am_i_io){ + const auto it_max = std::max_element(num_msg.begin(), num_msg.end()); + max_items = *it_max; + + //In case of an "ex aequo" the I/O rank should be the gather rank + max_rank = (max_items == how_many_msgs) ? + m_io_rank : it_max - num_msg.begin(); + } + + auto package = std::array<int,2>{max_rank, max_items}; + amrex::ParallelDescriptor::Bcast(package.data(), 2, m_io_rank); + + return std::make_pair(package[0], package[1]); +} + +std::vector<MsgWithCounterAndRanks> +Logger::compute_msgs_with_counter_and_ranks( + const std::map<Msg,std::int64_t>& my_msg_map, + const std::vector<char>& all_data, + const std::vector<int>& displacements, + const int gather_rank) const +{ + if(m_rank != gather_rank) return std::vector<MsgWithCounterAndRanks>{}; + + std::vector<MsgWithCounterAndRanks> msgs_with_counter_and_ranks; + + // Put messages of the gather rank in msgs_with_counter_and_ranks + for (const auto& el : my_msg_map) + { + msgs_with_counter_and_ranks.emplace_back( + MsgWithCounterAndRanks{ + MsgWithCounter{el.first, el.second}, + false, + std::vector<int>{m_rank}}); + } + + // We need a temporary map + std::map<Msg, MsgWithCounterAndRanks> tmap; + +#ifdef AMREX_USE_OMP + #pragma omp parallel for +#endif + for(int rr = 0; rr < m_num_procs; ++rr){ //for each rank + if(rr == gather_rank) // (skip gather_rank) + continue; + + // get counters generated by rank rr + auto it = all_data.begin() + displacements[rr]; + const auto counters_rr = get_out_vec<std::int64_t>(it); + + //for each counter from rank rr + std::int64_t c = 0; + for (const auto& counter : counters_rr){ +#ifdef AMREX_USE_OMP + #pragma omp atomic +#endif + msgs_with_counter_and_ranks[c].msg_with_counter.counter += + counter; //update corresponding global counter + + //and add rank to rank list if it has emitted the message + if (counter > 0){ +#ifdef AMREX_USE_OMP + #pragma omp critical +#endif + { + msgs_with_counter_and_ranks[c].ranks.push_back(rr); + } + } + c++; + } + + // for each additional message coming from rank rr + const auto how_many_additional_msgs_with_counter = get_out<int>(it); + for(int i = 0; i < how_many_additional_msgs_with_counter; ++i){ + + //deserialize the message + const auto serialized_msg_with_counter = get_out_vec<char>(it); + auto msg_with_counter = + MsgWithCounter::deserialize(serialized_msg_with_counter.begin()); + + //and eventually add it to the temporary map +#ifdef AMREX_USE_OMP + #pragma omp critical +#endif + { + if (tmap.find(msg_with_counter.msg) == tmap.end()){ + const auto msg_with_counter_and_ranks = + MsgWithCounterAndRanks{ + msg_with_counter, + false, + std::vector<int>{rr} + }; + tmap[msg_with_counter.msg] = msg_with_counter_and_ranks; + } + else{ + tmap[msg_with_counter.msg].msg_with_counter.counter += + msg_with_counter.counter; + tmap[msg_with_counter.msg].ranks.push_back(rr); + } + } + } + } + + // Check if messages emitted by "gather rank" are actually emitted by all ranks + const auto ssize = static_cast<int>(msgs_with_counter_and_ranks.size()); + for (int i = 0; i < ssize; ++i){ + const auto how_many = + static_cast<int>(msgs_with_counter_and_ranks[i].ranks.size()); + if(how_many == m_num_procs){ + msgs_with_counter_and_ranks[i].all_ranks = true; + // trick to force free memory + std::vector<int>{}.swap(msgs_with_counter_and_ranks[i].ranks); + } + } + + // Add elements from the temporary map + for(const auto& el : tmap){ + msgs_with_counter_and_ranks.push_back(el.second); + } + + // Sort affected ranks lists + for(auto& el : msgs_with_counter_and_ranks){ + std::sort(el.ranks.begin(), el.ranks.end()); + } + + return msgs_with_counter_and_ranks; +} + +void Logger::swap_with_io_rank( + std::vector<MsgWithCounterAndRanks>& msgs_with_counter_and_ranks, + int gather_rank) const +{ + if (gather_rank != m_io_rank){ + if(m_rank == gather_rank){ + auto package = std::vector<char>{}; + for (const auto& el: msgs_with_counter_and_ranks) + put_in_vec<char>(el.serialize(), package); + + auto package_size = static_cast<int>(package.size()); + amrex::ParallelDescriptor::Send(&package_size, 1, m_io_rank, 0); + amrex::ParallelDescriptor::Send(package, m_io_rank, 1); + int list_size = static_cast<int>(msgs_with_counter_and_ranks.size()); + amrex::ParallelDescriptor::Send(&list_size, 1, m_io_rank, 2); + } + else if (m_rank == m_io_rank){ + int vec_size = 0; + amrex::ParallelDescriptor::Recv(&vec_size, 1, gather_rank, 0); + std::vector<char> package(vec_size); + amrex::ParallelDescriptor::Recv(package, gather_rank, 1); + int list_size = 0; + amrex::ParallelDescriptor::Recv(&list_size, 1, gather_rank, 2); + auto it = package.cbegin(); + for (int i = 0; i < list_size; ++i){ + const auto vec = get_out_vec<char>(it); + msgs_with_counter_and_ranks.emplace_back( + MsgWithCounterAndRanks::deserialize(vec.begin()) + ); + } + } + } +} + +namespace +{ +std::vector<char> +get_serialized_gather_rank_msgs( + const std::vector<Msg>& my_msgs, + const int gather_rank, + const int my_rank) +{ + const bool is_gather_rank = (my_rank == gather_rank); + + auto serialized_gather_rank_msgs = std::vector<char>{}; + int size_serialized_gather_rank_msgs = 0; + + if (is_gather_rank){ + serialized_gather_rank_msgs = ::serialize_msgs(my_msgs); + size_serialized_gather_rank_msgs = static_cast<int>( + serialized_gather_rank_msgs.size()); + } + + amrex::ParallelDescriptor::Bcast( + &size_serialized_gather_rank_msgs, 1, gather_rank); + + if (!is_gather_rank) + serialized_gather_rank_msgs.resize( + size_serialized_gather_rank_msgs); + + amrex::ParallelDescriptor::Bcast( + serialized_gather_rank_msgs.data(), + size_serialized_gather_rank_msgs, gather_rank); + + return serialized_gather_rank_msgs; +} + +std::vector<char> +compute_package_for_gather_rank( + const std::vector<char>& serialized_gather_rank_msgs, + const std::int64_t gather_rank_how_many_msgs, + const std::map<Msg, std::int64_t>& my_msg_map, + const bool is_gather_rank) +{ + if(!is_gather_rank){ + auto package = std::vector<char>{}; + + //generates a copy of the message map + auto msgs_to_send = std::map<Msg, std::int64_t>{my_msg_map}; + + // For each message of the "gather rank" store how many times + // the message has been emitted by the current ranks. + const auto gather_rank_msgs = + ::deserialize_msgs(serialized_gather_rank_msgs); + std::vector<std::int64_t> gather_rank_msg_counters(gather_rank_how_many_msgs); + std::int64_t counter = 0; + for (const auto& msg : gather_rank_msgs){ + const auto pp = msgs_to_send.find(msg); + if (pp != msgs_to_send.end()){ + gather_rank_msg_counters[counter] += pp->second; + // Remove messages already seen by "gather rank" from + // the messages to send back + msgs_to_send.erase(msg); + } + counter++; + } + put_in_vec(gather_rank_msg_counters, package); + + // Add the additional messages seen by the current rank to the package + put_in(static_cast<int>(msgs_to_send.size()), package); + for (const auto& el : msgs_to_send) + put_in_vec<char>( + MsgWithCounter{el.first, el.second}.serialize(), package); + + return package; + } + + return std::vector<char>{}; +} + +std::pair<std::vector<char>, std::vector<int>> +gather_all_data( + const std::vector<char>& package_for_gather_rank, + const int gather_rank, const int my_rank) +{ + auto package_lengths = std::vector<int>{}; + auto all_data = std::vector<char>{}; + auto displacements = std::vector<int>{}; + + if(gather_rank != my_rank){ + amrex::ParallelDescriptor::Gather( + static_cast<int>(package_for_gather_rank.size()), gather_rank); + amrex::ParallelDescriptor::Gatherv( + package_for_gather_rank.data(), + package_for_gather_rank.size(), + all_data.data(), + package_lengths, + displacements, + gather_rank); + } + else{ + const int zero_size = 0; + package_lengths = + amrex::ParallelDescriptor::Gather(zero_size, gather_rank); + + // Compute displacements + // Given (n1, n2, n3, n4, ..., n_n) we need (0, n1, n1+n2, n1+n2+n3, ...), + // but partial_sum gives us (n1,n1+n2, n1+n2+n3, n1+n2+n3+n4, ...). + // Rotating this last vector by one is just shifting: (n1+n2+n3+n4+...,n1, n1+n2, n1+n2+n3, ...). + // Then we just need to replace the first element with zero: (0,n1, n1+n2, n1+n2+n3, ...). + displacements.resize(package_lengths.size()); + std::partial_sum(package_lengths.begin(), package_lengths.end(), + displacements.begin()); + const auto total_sum = displacements.back(); + std::rotate(displacements.rbegin(), + displacements.rbegin()+1, + displacements.rend()); + displacements[0] = 0; + + all_data.resize(total_sum); + + amrex::ParallelDescriptor::Gatherv( + static_cast<char*>(nullptr), + 0, + all_data.data(), + package_lengths, + displacements, + gather_rank); + } + return std::make_pair(all_data, displacements); +} + +std::vector<char> serialize_msgs( + const std::vector<Msg>& msgs) +{ + auto serialized = std::vector<char>{}; + + const auto how_many = static_cast<int> (msgs.size()); + put_in (how_many, serialized); + + for (auto msg : msgs){ + put_in_vec(msg.serialize(), serialized); + } + return serialized; +} + +std::vector<Msg> deserialize_msgs( + const std::vector<char>& serialized) +{ + auto it = serialized.begin(); + + const auto how_many = get_out<int>(it); + auto msgs = std::vector<Msg>{}; + msgs.reserve(how_many); + + for (int i = 0; i < how_many; ++i){ + const auto vv = get_out_vec<char>(it); + msgs.emplace_back(Msg::deserialize(vv.begin())); + } + + return msgs; +} +} + +#endif + diff --git a/Source/Utils/MsgLogger/MsgLoggerSerialization.H b/Source/Utils/MsgLogger/MsgLoggerSerialization.H new file mode 100644 index 000000000..fba8bb0d1 --- /dev/null +++ b/Source/Utils/MsgLogger/MsgLoggerSerialization.H @@ -0,0 +1,189 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#ifndef WARPX_MSG_LOGGER_SERIALIZATION_H_ +#define WARPX_MSG_LOGGER_SERIALIZATION_H_ + +#include <algorithm> +#include <array> +#include <cstring> +#include <string> +#include <type_traits> +#include <vector> + +namespace Utils{ +namespace MsgLogger{ + + /** + * This function transforms a variable of type T into a vector of chars holding its + * byte representation and it appends this vector at the end of an + * existing vector of chars. T must be either a trivially copyable type or an std::string + * (see specialization) + * + * @tparam T the variable type + * @param[in] val a variable of type T to be serialized + * @param[in, out] vec a reference to the vector to which the byte representation of val is appended + */ + template <typename T> + void put_in(const T& val, std::vector<char>& vec) + { + static_assert(std::is_trivially_copyable<T>(), + "Cannot serialize non-trivally copyable types, except std::string."); + + const auto* ptr_val = reinterpret_cast<const char*>(&val); + vec.insert(vec.end(), ptr_val, ptr_val+sizeof(T)); + } + + /** + * This function transforms a string into a vector of chars holding its + * byte representation and it appends this vector at the end of an + * existing vector of chars (specialization of put_in<T>). + * + * @param[in] val a std::string to be serialized + * @param[in, out] vec a reference to the vector to which the byte representation of val is appended + */ + template <> + inline void put_in<std::string> (const std::string& val, std::vector<char>& vec) + { + const char* c_str = val.c_str(); + const auto length = static_cast<int>(val.size()); + + put_in(length, vec); + vec.insert(vec.end(), c_str, c_str+length); + } + + /** + * This function transforms an std::vector<T> into a vector of chars holding its + * byte representation and it appends this vector at the end of an + * existing vector of chars. T must be either a trivially copyable type or an std::string. + * A specialization exists in case val is a vector of chars. + * + * @tparam T the variable type + * @param[in] val a variable of type T to be serialized + * @param[in, out] vec a reference to the vector to which the byte representation of val is appended + */ + template <typename T> + inline void put_in_vec (const std::vector<T>& val, std::vector<char>& vec) + { + static_assert(std::is_trivially_copyable<T>() || std::is_same<T,std::string>(), + "Cannot serialize vectors of non-trivally copyable types" + ", except vectors of std::string."); + + put_in(static_cast<int>(val.size()), vec); + for (const auto& el : val) + put_in(el, vec); + } + + /** + * This function transforms an std::vector<char> into a vector of chars holding its + * byte representation and it appends this vector at the end of an + * existing vector of chars (specialization of put_in_vec<T>). + * + * @tparam T the variable type + * @param[in] val a variable of type T to be serialized + * @param[in, out] vec a reference to the vector to which the byte representation of val is appended + */ + template <> + inline void put_in_vec <char> (const std::vector<char>& val, std::vector<char>& vec) + { + put_in(static_cast<int>(val.size()), vec); + vec.insert(vec.end(), val.begin(), val.end()); + } + + /** + * This function extracts a variable of type T from a byte vector, at the position + * given by a std::vector<char> iterator. The iterator is then advanced according to + * the number of bytes read from the byte vector. T must be either a trivially copyable type + * or an std::string (see specialization below). + * + * @tparam T the variable type (must be trivially copyable) + * @param[in, out] it the iterator to a byte vector + * @return the variable extracted from the byte array + */ + template<typename T> + T get_out(std::vector<char>::const_iterator& it) + { + static_assert(std::is_trivially_copyable<T>(), + "Cannot extract non-trivally copyable types from char vectors," + " with the exception of std::string."); + + auto temp = std::array<char, sizeof(T)>{}; + std::copy(it, it + sizeof(T), temp.begin()); + it += sizeof(T); + T res; + std::memcpy(&res, temp.data(), sizeof(T)); + + return res; + } + + /** + * This function extracts an std::string from a byte vector, at the position + * given by a std::vector<char> iterator. The iterator is then advanced according to + * the number of bytes read from the byte vector. This is a specialization of + * get_out<T> + * + * @param[in, out] it the iterator to a byte vector + * @return the std::string extracted from the byte array + */ + template<> + inline std::string get_out<std::string> (std::vector<char>::const_iterator& it) + { + const auto length = get_out<int> (it); + const auto str = std::string{it, it+length}; + it += length; + + return str; + } + + /** + * This function extracts an std::vector<T> from a byte vector, at the position + * given by a std::vector<char> iterator. The iterator is then advanced according to + * the number of bytes read from the byte vector. T must be either a trivially copyable type + * or an std::string. + * + * @tparam T the variable type (must be trivially copyable) + * @param[in, out] it the iterator to a byte vector + * @return the variable extracted from the byte array + */ + template<typename T> + inline std::vector<T> get_out_vec (std::vector<char>::const_iterator& it) + { + static_assert(std::is_trivially_copyable<T>() || std::is_same<T,std::string>(), + "Cannot extract non-trivally copyable types from char vectors," + " with the exception of std::string."); + + const auto length = get_out<int> (it); + std::vector<T> res(length); + for (int i = 0; i < length; ++i) + res[i] = get_out<T>(it); + + return res; + } + + /** + * This function extracts an std::vector<char> from a byte vector, at the position + * given by a std::vector<char> iterator. The iterator is then advanced according to + * the number of bytes read from the byte vector. This is a specialization of get_out_vec<T>. + * + * @param[in, out] it the iterator to a byte vector + * @return the variable extracted from the byte array + */ + template<> + inline std::vector<char> get_out_vec<char> (std::vector<char>::const_iterator& it) + { + const auto length = get_out<int> (it); + std::vector<char> res(length); + std::copy(it, it+length, res.begin()); + it += length; + + return res; + } + +} +} + +#endif //WARPX_MSG_LOGGER_SERIALIZATION_H_ diff --git a/Source/Utils/MsgLogger/MsgLogger_fwd.H b/Source/Utils/MsgLogger/MsgLogger_fwd.H new file mode 100644 index 000000000..626348670 --- /dev/null +++ b/Source/Utils/MsgLogger/MsgLogger_fwd.H @@ -0,0 +1,24 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#ifndef WARPX_MSG_LOGGER_FWD_H +#define WARPX_MSG_LOGGER_FWD_H + +namespace Utils{ +namespace MsgLogger{ + + enum class Priority; + + struct Msg; + struct MsgWithCounter; + struct MsgWithCounterAndRanks; + + class Logger; +} +} + +#endif //WARPX_MSG_LOGGER_FWD_H diff --git a/Source/Utils/WarnManager.H b/Source/Utils/WarnManager.H new file mode 100644 index 000000000..9a360ac83 --- /dev/null +++ b/Source/Utils/WarnManager.H @@ -0,0 +1,135 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#ifndef WARPX_WARN_MANAGER_H_ +#define WARPX_WARN_MANAGER_H_ + +#include "WarnManager_fwd.H" + +#include "MsgLogger/MsgLogger_fwd.H" + +#include <AMReX_ParmParse.H> + +#include <memory> +#include <string> +#include <vector> + +namespace Utils +{ + /** + * The class WarnManager manages warning messages in WarpX, + * providing methods to record warnings, and print warning + * lists. + */ + class WarnManager + { + public: + + /** + * The constructor. + */ + WarnManager(); + + /** + * \brief This function records a warning message. + * + * @param[in] topic a string to identify the topic of the warning (e.g., "parallelization", "pbc", "particles"...) + * @param[in] text the text of the warning message + * @param[in] priority priority of the warning message ("medium" by default) + */ + void record_warning( + std::string topic, + std::string text, + MsgLogger::Priority priority); + + /** + * \brief This function prints all the warning messages collected on the present MPI rank + * (i.e., this is not a collective call). This function is mainly intended for debug purposes. + * + * @param[in] when a string to mark when the warnings are printed out (it appears in the warning list) + * @return a string containing the "local" warning list + */ + std::string print_local_warnings( + const std::string& when) const; + + /** + * \brief This function prints all the warning messages collected by all the MPI ranks + * (i.e., this is a collective call). Only the I/O rank prints the message. + * + * @param[in] when a string to mark when the warnings are printed out (it appears in the warning list) + * @return a string containing the "global" warning list + */ + std::string print_global_warnings( + const std::string& when) const; + + /** + * \brief This function reads warning messages from the inputfile. It is intended for + * debug&testing purposes + * + * @param[in, out] params the inputfile parser + */ + void debug_read_warnings_from_input(amrex::ParmParse& params); + + static const int warn_line_size = 80 /*! Maximum line length to be used in formatting warning list*/; + static const int warn_tab_size = 5 /*! Tabulation size to be used in formatting warning list*/; + + private: + + /** + * \brief This function generates the header of the warning messages list + * + * @param[in] when a string to mark when the warnings are printed out (it appears in the warning list) + * @param[in] line_size maximum line length to be used in formatting warning list + * @param[in] is_global flag: true if the header is for a global warning list, false otherwise + * @return a string containing the header of the warning list + */ + std::string get_header( + const std::string& when, + const int line_size, + const bool is_global) const; + + /** + * \brief This function generates a string for a single entry of the warning list + * for a MessageWithCounter struct (i.e., a warning message paired with a counter storing + * how many times the warning has been raised) + * + * @param[in] msg_with_counter a MessageWithCounter + * @return a string containing the warning message + */ + std::string print_warn_msg( + const MsgLogger::MsgWithCounter& msg_with_counter) const; + + /** + * \brief This function generates a string for a single entry of the warning list + * for a MsgWithCounterAndRanks struct (i.e., a warning message paired with a counter storing + * how many times the warning has been raised and info on which ranks have raised the warning) + * + * @param[in] msg_with_counter_and_ranks a MsgWithCounterAndRanks + * @return a string containing the warning message + */ + std::string print_warn_msg( + const MsgLogger::MsgWithCounterAndRanks& msg_with_counter_and_ranks) const; + + /** + * \brief This function formats each line of a warning message text + * + * @param[in] msg the warning message text + * @param[in] line_size maximum line length to be used in formatting warning list + * @param[in] tab_size tabulation size to be used in formatting warning list + * @return a string containing the formatted warning message text + */ + std::string msg_formatter( + const std::string& msg, + const int line_size, + const int tab_size) const; + + int m_rank = 0 /*! MPI rank (appears in the warning list)*/; + std::unique_ptr<MsgLogger::Logger> m_p_logger /*! The Logger stores all the warning messages*/; + }; +} + +#endif //WARPX_WARN_MANAGER_H_ diff --git a/Source/Utils/WarnManager.cpp b/Source/Utils/WarnManager.cpp new file mode 100644 index 000000000..c3b8d3159 --- /dev/null +++ b/Source/Utils/WarnManager.cpp @@ -0,0 +1,239 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#include "WarnManager.H" + +#include "MsgLogger/MsgLogger.H" + +#include <AMReX_ParallelDescriptor.H> + +#include <algorithm> +#include <sstream> + +using namespace Utils; +using namespace Utils::MsgLogger; + +WarnManager::WarnManager(){ + m_rank = amrex::ParallelDescriptor::MyProc(); + m_p_logger = std::make_unique<Logger>(); +} + +void WarnManager::record_warning( + std::string topic, + std::string text, + Priority priority) +{ + m_p_logger->record_msg(Msg{topic, text, priority}); +} + +std::string WarnManager::print_local_warnings(const std::string& when) const +{ + auto all_warnings = m_p_logger->get_msgs_with_counter(); + std::sort(all_warnings.begin(), all_warnings.end(), + [](const auto& a, const auto& b){return a.msg < b.msg;}); + + std::stringstream ss; + + ss << "\n" << get_header(when, warn_line_size, false); + + if(all_warnings.size() == 0){ + ss << "* No recorded warnings.\n"; + } + else{ + for(const auto& warn_msg : all_warnings){ + ss << print_warn_msg(warn_msg); + ss << "*\n"; + } + } + + ss << std::string(warn_line_size, '*') << "\n\n" ; + + return ss.str(); +} + +std::string WarnManager::print_global_warnings(const std::string& when) const +{ + auto all_warnings = + m_p_logger->collective_gather_msgs_with_counter_and_ranks(); + + if(m_rank != amrex::ParallelDescriptor::IOProcessorNumber()) + return "[see I/O rank message]"; + + std::sort(all_warnings.begin(), all_warnings.end(), + [](const auto& a, const auto& b){ + return a.msg_with_counter.msg < b.msg_with_counter.msg;}); + + std::stringstream ss; + + ss << "\n" << get_header(when, warn_line_size, true); + + if(all_warnings.size() == 0){ + ss << "* No recorded warnings.\n"; + } + else{ + for(const auto& warn_msg : all_warnings){ + ss << print_warn_msg(warn_msg); + ss << "*\n"; + } + } + + ss << std::string(warn_line_size, '*') << "\n\n" ; + + return ss.str(); +} + +void WarnManager::debug_read_warnings_from_input(amrex::ParmParse& params) +{ + std::vector<std::string> warnings; + params.queryarr("test_warnings", warnings); + + for (const auto& warn : warnings){ + amrex::ParmParse pp_warn(warn); + + std::string topic; + pp_warn.query("topic", topic); + + std::string msg; + pp_warn.query("msg", msg); + + std::string spriority; + pp_warn.query("priority", spriority); + Priority priority = StringToPriority(spriority); + + int all_involved = 0; + pp_warn.query("all_involved", all_involved); + if(all_involved){ + this->record_warning(topic, msg, priority); + } + else{ + std::vector<int> who_involved; + pp_warn.queryarr("who_involved", who_involved); + if(std::find (who_involved.begin(), who_involved.end(), m_rank) + != who_involved.end()){ + this->record_warning(topic, msg, priority); + } + } + } + +} + +std::string WarnManager::get_header( + const std::string& when, + const int line_size, + const bool is_global) const +{ + const std::string warn_header{"**** WARNINGS "}; + + std::stringstream ss; + + ss << warn_header << + std::string(line_size - static_cast<int>(warn_header.length()), '*') << "\n" ; + + if(is_global){ + ss << "* GLOBAL warning list after " << " [ " << when << " ]\n*\n"; + } + else{ + auto const mpi_rank = amrex::ParallelDescriptor::MyProc(); + ss << "* LOCAL" << " ( rank # " << mpi_rank << " ) " + << " warning list after " << when << "\n*\n"; + } + + return ss.str(); +} + +std::string WarnManager::print_warn_msg( + const MsgLogger::MsgWithCounter& msg_with_counter) const +{ + std::stringstream ss; + ss << "* --> "; + if (msg_with_counter.msg.priority == MsgLogger::Priority::high) + ss << "[!!!]"; + else if (msg_with_counter.msg.priority == MsgLogger::Priority::medium) + ss << "[!! ]"; + else if (msg_with_counter.msg.priority == MsgLogger::Priority::low) + ss << "[! ]"; + else + ss << "[???]"; + + ss << " [" + msg_with_counter.msg.topic << "] "; + + if(msg_with_counter.counter == 2) + ss << "[raised twice]\n"; + else if(msg_with_counter.counter == 1) + ss << "[raised once]\n"; + else + ss << "[raised " << msg_with_counter.counter << " times]\n"; + + ss << msg_formatter(msg_with_counter.msg.text, warn_line_size, warn_tab_size); + + return ss.str(); +} + +std::string WarnManager::print_warn_msg( + const MsgLogger::MsgWithCounterAndRanks& msg_with_counter_and_ranks) const +{ + std::stringstream ss; + ss << this->print_warn_msg(msg_with_counter_and_ranks.msg_with_counter); + + std::string raised_by = "@ Raised by: "; + if (!msg_with_counter_and_ranks.all_ranks){ + for (const auto rr : msg_with_counter_and_ranks.ranks) + raised_by += " " + std::to_string(rr); + } + else{ + raised_by += "ALL\n"; + } + ss << msg_formatter(raised_by, warn_line_size, warn_tab_size); + + return ss.str(); +} + +std::string +WarnManager::msg_formatter( + const std::string& msg, + const int line_size, + const int tab_size) const +{ + const auto prefix = "*" + std::string(tab_size, ' '); + const auto prefix_length = static_cast<int>(prefix.length()); + + std::stringstream ss_out; + std::stringstream ss_msg{msg}; + + std::string line; + std::string word; + + while(std::getline(ss_msg, line,'\n')){ + ss_out << prefix; + + std::stringstream ss_line{line}; + int counter = prefix_length; + + while (ss_line >> word){ + const auto wlen = static_cast<int>(word.length()); + + if(counter == prefix_length){ + ss_out << word; + counter += wlen; + } + else{ + if (counter + wlen < line_size){ + ss_out << " " << word; + counter += (wlen+1); + } + else{ + ss_out << "\n" << prefix << word; + counter = prefix_length + wlen; + } + } + } + + ss_out << '\n'; + } + + return ss_out.str(); +} diff --git a/Source/Utils/WarnManager_fwd.H b/Source/Utils/WarnManager_fwd.H new file mode 100644 index 000000000..e12cf7910 --- /dev/null +++ b/Source/Utils/WarnManager_fwd.H @@ -0,0 +1,16 @@ +/* Copyright 2021 Luca Fedeli + * + * This file is part of WarpX. + * + * License: BSD-3-Clause-LBNL + */ + +#ifndef WARPX_WARN_MANAGER_FWD_H +#define WARPX_WARN_MANAGER_FWD_H + +namespace Utils +{ + class WarnManager; +} + +#endif //WARPX_WARN_MANAGER_FWD_H diff --git a/Source/WarpX.H b/Source/WarpX.H index 588648c69..0ba6e9001 100644 --- a/Source/WarpX.H +++ b/Source/WarpX.H @@ -35,6 +35,7 @@ #include "Particles/MultiParticleContainer_fwd.H" #include "Particles/WarpXParticleContainer_fwd.H" #include "Utils/IntervalsParser.H" +#include "Utils/WarnManager_fwd.H" #include "Utils/WarpXAlgorithmSelection.H" #include <AMReX.H> @@ -74,6 +75,27 @@ enum struct PatchType : int coarse }; +/** WarnPriority is recorded together with warning messages. It influences + * the display order and the appearance of a warning message. + * This enum class mirrors Utils::MsgLogger::Priority. +*/ +enum class WarnPriority +{ + /** Low priority warning: + * essentially an informative message + */ + low, + /** Medium priority warning: + * a bug or a performance issue may affect the simulation + */ + medium, + /** High priority warning: + * a very serious bug or performance issue + * almost certainly affects the simulation + */ + high +}; + class WarpX : public amrex::AmrCore { @@ -92,6 +114,34 @@ public: int Verbose () const { return verbose; } + /** + * \brief This function records a warning message. + * RecordWarning is thread safe: it can be used within OpenMP parallel loops. + * + * @param[in] topic a string to identify the topic of the warning (e.g., "parallelization", "pbc", "particles"...) + * @param[in] text the text of the warning message + * @param[in] priority priority of the warning message ("medium" by default) + */ + void RecordWarning( + std::string topic, + std::string text, + WarnPriority priority = WarnPriority::medium); + + /** + * \brief This function prints all the warning messages collected on the present MPI rank + * (i.e., this is not a collective call). This function is mainly intended for debug purposes. + * + * @param[in] when a string to mark when the warnings are printed out (it appears in the warning list) + */ + void PrintLocalWarnings(const std::string& when); + + /** + * \brief This function prints all the warning messages collected by all the MPI ranks + * (i.e., this is a collective call). Only the I/O rank prints the message. + * + * @param[in] when a string to mark when the warnings are printed out (it appears in the warning list) + */ + void PrintGlobalWarnings(const std::string& when); void InitData (); @@ -938,6 +988,11 @@ private: # endif #endif + // Warning manager: it allows recording and printing error messages + std::unique_ptr<Utils::WarnManager> m_p_warn_manager; + // Flag to control if WarpX has to emit a warning message as soon as a warning is recorded + bool m_always_warn_immediately = false; + amrex::Vector<int> istep; // which step? amrex::Vector<int> nsubsteps; // how many substeps on each level? diff --git a/Source/WarpX.cpp b/Source/WarpX.cpp index 7f620e515..acc6e5844 100644 --- a/Source/WarpX.cpp +++ b/Source/WarpX.cpp @@ -29,8 +29,11 @@ #include "Filter/NCIGodfreyFilter.H" #include "Particles/MultiParticleContainer.H" #include "Particles/ParticleBoundaryBuffer.H" +#include "Utils/MsgLogger/MsgLogger.H" +#include "Utils/WarnManager.H" #include "Utils/WarpXAlgorithmSelection.H" #include "Utils/WarpXConst.H" +#include "Utils/WarpXProfilerWrapper.H" #include "Utils/WarpXUtil.H" #ifdef AMREX_USE_SENSEI_INSITU @@ -216,6 +219,9 @@ WarpX::ResetInstance () WarpX::WarpX () { m_instance = this; + + m_p_warn_manager = std::make_unique<Utils::WarnManager>(); + ReadParameters(); BackwardCompatibility(); @@ -410,6 +416,51 @@ WarpX::~WarpX () } void +WarpX::RecordWarning( + std::string topic, + std::string text, + WarnPriority priority) +{ + WARPX_PROFILE("WarpX::RecordWarning"); + + auto msg_priority = Utils::MsgLogger::Priority::high; + if(priority == WarnPriority::low) + msg_priority = Utils::MsgLogger::Priority::low; + else if(priority == WarnPriority::medium) + msg_priority = Utils::MsgLogger::Priority::medium; + + if(m_always_warn_immediately){ + amrex::Warning( + "!!!!!! WARNING: [" + + std::string(Utils::MsgLogger::PriorityToString(msg_priority)) + + "][" + topic + "] " + text); + } + +#ifdef AMREX_USE_OMP + #pragma omp critical +#endif + { + m_p_warn_manager->record_warning(topic, text, msg_priority); + } +} + +void +WarpX::PrintLocalWarnings(const std::string& when) +{ + WARPX_PROFILE("WarpX::PrintLocalWarnings"); + const auto warn_string = m_p_warn_manager->print_local_warnings(when); + amrex::AllPrint() << warn_string; +} + +void +WarpX::PrintGlobalWarnings(const std::string& when) +{ + WARPX_PROFILE("WarpX::PrintGlobalWarnings"); + const auto warn_string = m_p_warn_manager->print_global_warnings(when); + amrex::Print() << warn_string; +} + +void WarpX::ReadParameters () { { @@ -433,6 +484,11 @@ WarpX::ReadParameters () { ParmParse pp_warpx("warpx"); + //"Synthetic" warning messages may be injected in the Warning Manager via + // inputfile for debug&testing purposes. + m_p_warn_manager->debug_read_warnings_from_input(pp_warpx); + pp_warpx.query("always_warn_immediately", m_always_warn_immediately); + std::vector<int> numprocs_in; queryArrWithParser(pp_warpx, "numprocs", numprocs_in, 0, AMREX_SPACEDIM); if (not numprocs_in.empty()) { @@ -620,10 +676,9 @@ WarpX::ReadParameters () // (see https://github.com/ECP-WarpX/WarpX/issues/1943) if (use_filter) { - amrex::Print() << "\nWARNING:" - << "\nFilter currently not working with FDTD solver in RZ geometry:" - << "\nwe recommend setting warpx.use_filter = 0 in the input file.\n" - << std::endl; + this->RecordWarning("Filter", + "Filter currently not working with FDTD solver in RZ geometry." + "We recommend setting warpx.use_filter = 0 in the input file."); } } #endif @@ -904,9 +959,10 @@ WarpX::ReadParameters () if ((maxLevel() > 0) && (particle_shape > 1) && (do_pml_j_damping == 1)) { - amrex::Warning("\nWARNING: When algo.particle_shape > 1," - " some numerical artifact will be present at the interface between coarse and fine patch." - "\nWe recommend setting algo.particle_shape = 1 in order to avoid this issue"); + this->RecordWarning("Particles", + "When algo.particle_shape > 1," + "some numerical artifact will be present at the interface between coarse and fine patch." + "We recommend setting algo.particle_shape = 1 in order to avoid this issue"); } // default sort interval for particles if species or lasers vector is not empty @@ -1279,17 +1335,23 @@ WarpX::BackwardCompatibility () ParmParse pp_particles("particles"); int nspecies; if (pp_particles.query("nspecies", nspecies)){ - amrex::Print()<<"particles.nspecies is ignored. Just use particles.species_names please.\n"; + this->RecordWarning("Species", + "particles.nspecies is ignored. Just use particles.species_names please.", + WarnPriority::low); } ParmParse pp_collisions("collisions"); int ncollisions; if (pp_collisions.query("ncollisions", ncollisions)){ - amrex::Print()<<"collisions.ncollisions is ignored. Just use particles.collision_names please.\n"; + this->RecordWarning("Collisions", + "collisions.ncollisions is ignored. Just use particles.collision_names please.", + WarnPriority::low); } ParmParse pp_lasers("lasers"); int nlasers; if (pp_lasers.query("nlasers", nlasers)){ - amrex::Print()<<"lasers.nlasers is ignored. Just use lasers.names please.\n"; + this->RecordWarning("Laser", + "lasers.nlasers is ignored. Just use lasers.names please.", + WarnPriority::low); } } diff --git a/Source/main.cpp b/Source/main.cpp index 751a63fb0..185746a4d 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -66,6 +66,8 @@ int main(int argc, char* argv[]) warpx.Evolve(); + warpx.PrintGlobalWarnings("THE END"); //Print warning messages at the end of the simulation + if (warpx.Verbose()) { auto end_total = static_cast<Real>(amrex::second()) - strt_total; ParallelDescriptor::ReduceRealMax(end_total, ParallelDescriptor::IOProcessorNumber()); |