diff options
author | 2022-03-21 21:47:34 -0700 | |
---|---|---|
committer | 2022-03-21 21:47:34 -0700 | |
commit | be559b9988e90ffb7b8b18ef68cf942c4e3f36df (patch) | |
tree | 346d1064e36ec49162307a8f687260db4e5f5e1b /Source/Diagnostics/WarpXOpenPMD.cpp | |
parent | af55efab7afb1fbaaa84dcf561342957fc3e71f0 (diff) | |
download | WarpX-be559b9988e90ffb7b8b18ef68cf942c4e3f36df.tar.gz WarpX-be559b9988e90ffb7b8b18ef68cf942c4e3f36df.tar.zst WarpX-be559b9988e90ffb7b8b18ef68cf942c4e3f36df.zip |
openPMD: Handle Zero Particles Well (#2980)
* openPMD: Handle Zero Particles Well
When a time step for output encounters zero particles in a species,
then we still want to dump them as "empty" species in openPMD. That
simplifies post-processing a lot and we have the mechanisms in
openPMD for it :)
* openPMD: Emtpy Particle Writes
Write empty records for iterations (steps or lab steps for BTD,
respectively) without particles in a species.
* Re-order: ED-PIC & Constant Particle Records
- set attributes once
- set constant records once
- clean up into appropriate functions
* Enable BTD for ADIOS :)
Works now as well :tada:
* Fix lingo in comments (Reva)
Thank you!! :)
Co-authored-by: Revathi Jambunathan <41089244+RevathiJambunathan@users.noreply.github.com>
Co-authored-by: Revathi Jambunathan <41089244+RevathiJambunathan@users.noreply.github.com>
Diffstat (limited to 'Source/Diagnostics/WarpXOpenPMD.cpp')
-rw-r--r-- | Source/Diagnostics/WarpXOpenPMD.cpp | 421 |
1 files changed, 237 insertions, 184 deletions
diff --git a/Source/Diagnostics/WarpXOpenPMD.cpp b/Source/Diagnostics/WarpXOpenPMD.cpp index a08227779..f94325a72 100644 --- a/Source/Diagnostics/WarpXOpenPMD.cpp +++ b/Source/Diagnostics/WarpXOpenPMD.cpp @@ -541,7 +541,7 @@ WarpXOpenPMDPlot::Init (openPMD::Access access, bool isBTD) void WarpXOpenPMDPlot::WriteOpenPMDParticles (const amrex::Vector<ParticleDiag>& particle_diags, - const bool isBTD, const amrex::Vector<int>& totalParticlesFlushedAlready) + const bool isBTD, const bool isLastBTDFlush, const amrex::Vector<int>& totalParticlesFlushedAlready) { WARPX_PROFILE("WarpXOpenPMDPlot::WriteOpenPMDParticles()"); @@ -633,7 +633,8 @@ WarpXOpenPMDPlot::WriteOpenPMDParticles (const amrex::Vector<ParticleDiag>& part int_flags, real_names, int_names, pc->getCharge(), pc->getMass(), - isBTD, totalParticlesFlushedAlready[i] + isBTD, isLastBTDFlush, + totalParticlesFlushedAlready[i] ); } else { DumpToFile(&tmp, @@ -643,7 +644,8 @@ WarpXOpenPMDPlot::WriteOpenPMDParticles (const amrex::Vector<ParticleDiag>& part int_flags, real_names, int_names, pc->getCharge(), pc->getMass(), - isBTD, 0 + isBTD, isLastBTDFlush, + 0 ); } } @@ -662,161 +664,156 @@ WarpXOpenPMDPlot::DumpToFile (ParticleContainer* pc, const amrex::Vector<std::string>& real_comp_names, const amrex::Vector<std::string>& int_comp_names, amrex::ParticleReal const charge, - amrex::ParticleReal const mass, const bool isBTD, - int ParticleFlushOffset) -{ - WARPX_ALWAYS_ASSERT_WITH_MESSAGE(m_Series != nullptr, "openPMD: series must be initialized"); - - AMREX_ALWAYS_ASSERT(write_real_comp.size() == pc->NumRealComps()); - AMREX_ALWAYS_ASSERT( write_int_comp.size() == pc->NumIntComps()); - AMREX_ALWAYS_ASSERT(real_comp_names.size() == pc->NumRealComps()); - AMREX_ALWAYS_ASSERT( int_comp_names.size() == pc->NumIntComps()); - - WarpXParticleCounter counter(pc); - if (counter.GetTotalNumParticles() == 0) return; - openPMD::Iteration currIteration = GetIteration(iteration, isBTD); - - openPMD::ParticleSpecies currSpecies = currIteration.particles[name]; - // meta data for ED-PIC extension - currSpecies.setAttribute( "particleShape", double( WarpX::noz ) ); - // TODO allow this per direction in the openPMD standard, ED-PIC extension? - currSpecies.setAttribute( "particleShapes", [](){ - return std::vector< double >{ -#if AMREX_SPACEDIM>=2 - double(WarpX::nox), -#endif -#if defined(WARPX_DIM_3D) - double(WarpX::noy), -#endif - double(WarpX::noz) - }; - }() ); - currSpecies.setAttribute( "particlePush", [](){ - switch( WarpX::particle_pusher_algo ) { - case ParticlePusherAlgo::Boris : return "Boris"; - case ParticlePusherAlgo::Vay : return "Vay"; - case ParticlePusherAlgo::HigueraCary : return "HigueraCary"; - default: return "other"; - } - }() ); - currSpecies.setAttribute( "particleInterpolation", [](){ - switch( WarpX::field_gathering_algo ) { - case GatheringAlgo::EnergyConserving : return "energyConserving"; - case GatheringAlgo::MomentumConserving : return "momentumConserving"; - default: return "other"; - } - }() ); - currSpecies.setAttribute( "particleSmoothing", "none" ); - currSpecies.setAttribute( "currentDeposition", [](){ - switch( WarpX::current_deposition_algo ) { - case CurrentDepositionAlgo::Esirkepov : return "Esirkepov"; - case CurrentDepositionAlgo::Vay : return "Vay"; - default: return "directMorseNielson"; - } - }() ); + amrex::ParticleReal const mass, + const bool isBTD, + const bool isLastBTDFlush, + int ParticleFlushOffset) { + WARPX_ALWAYS_ASSERT_WITH_MESSAGE(m_Series != nullptr, "openPMD: series must be initialized"); + + AMREX_ALWAYS_ASSERT(write_real_comp.size() == pc->NumRealComps()); + AMREX_ALWAYS_ASSERT(write_int_comp.size() == pc->NumIntComps()); + AMREX_ALWAYS_ASSERT(real_comp_names.size() == pc->NumRealComps()); + AMREX_ALWAYS_ASSERT(int_comp_names.size() == pc->NumIntComps()); + + WarpXParticleCounter counter(pc); + auto const num_dump_particles = counter.GetTotalNumParticles(); + + openPMD::Iteration currIteration = GetIteration(iteration, isBTD); + openPMD::ParticleSpecies currSpecies = currIteration.particles[name]; + + // prepare data structures the first time BTD has non-zero particles + // we set some of them to zero extent, so we need to time that well + bool const is_first_flush_with_particles = num_dump_particles > 0 && ParticleFlushOffset == 0; + // BTD: we flush multiple times to the same lab step and thus need to resize + // our declared particle output sizes + bool const is_resizing_flush = num_dump_particles > 0 && ParticleFlushOffset > 0; + // write structure & declare particles in this (lab) step empty: + // if not BTD, then this is the only (and last) time we flush to this step + // if BTD, then we may do this multiple times until it is the last BTD flush + bool const is_last_flush_to_step = !isBTD || (isBTD && isLastBTDFlush); + // well, even in BTD we have to recognize that some lab stations may have no + // particles - so we mark them empty at the end of station reconstruction + bool const is_last_flush_and_never_particles = + is_last_flush_to_step && num_dump_particles == 0 && ParticleFlushOffset == 0; - // - // define positions & offsets - // - const unsigned long long NewParticleVectorSize = counter.GetTotalNumParticles() + ParticleFlushOffset; - m_doParticleSetUp = false; - if (counter.GetTotalNumParticles() > 0 and ParticleFlushOffset == 0) { - // This will trigger meta-data flush for particles the first-time non-zero number of particles are flushed. - m_doParticleSetUp = true; - } - SetupPos(currSpecies, NewParticleVectorSize, charge, mass, isBTD); - SetupRealProperties(pc, currSpecies, write_real_comp, real_comp_names, write_int_comp, int_comp_names, NewParticleVectorSize, isBTD); - // open files from all processors, in case some will not contribute below - m_Series->flush(); - for (auto currentLevel = 0; currentLevel <= pc->finestLevel(); currentLevel++) - { - uint64_t offset = static_cast<uint64_t>( counter.m_ParticleOffsetAtRank[currentLevel] ); - // For BTD, the offset include the number of particles already flushed - if (isBTD) offset += ParticleFlushOffset; - for (ParticleIter pti(*pc, currentLevel); pti.isValid(); ++pti) { - auto const numParticleOnTile = pti.numParticles(); - uint64_t const numParticleOnTile64 = static_cast<uint64_t>( numParticleOnTile ); - - // Do not call storeChunk() with zero-sized particle tiles: - // https://github.com/openPMD/openPMD-api/issues/1147 - if (numParticleOnTile == 0) continue; - - // get position and particle ID from aos - // note: this implementation iterates the AoS 4x... - // if we flush late as we do now, we can also copy out the data in one go - const auto& aos = pti.GetArrayOfStructs(); // size = numParticlesOnTile - { - // Save positions - auto const positionComponents = detail::getParticlePositionComponentLabels(); + // + // prepare structure and meta-data + // + + // define positions & offset structure + const unsigned long long NewParticleVectorSize = num_dump_particles + ParticleFlushOffset; + // we will set up empty particles unless it's BTD, where we might add some in a following buffer dump + // during this setup, we mark some particle properties as constant and potentially zero-sized + bool doParticleSetup = true; + if (isBTD) + doParticleSetup = is_first_flush_with_particles || is_last_flush_and_never_particles; + + // this setup stage also implicitly calls "makeEmpty" if needed (i.e., is_last_flush_and_never_particles) + // for BTD, we call this multiple times as we may resize in subsequent dumps if number of particles in the buffer > 0 + if (doParticleSetup || is_resizing_flush) { + SetupPos(currSpecies, NewParticleVectorSize, isBTD); + SetupRealProperties(pc, currSpecies, write_real_comp, real_comp_names, write_int_comp, int_comp_names, + NewParticleVectorSize, isBTD); + } + + if (is_last_flush_to_step) { + SetConstParticleRecordsEDPIC(currSpecies, NewParticleVectorSize, charge, mass); + } + + // open files from all processors, in case some will not contribute below + m_Series->flush(); + + // dump individual particles + for (auto currentLevel = 0; currentLevel <= pc->finestLevel(); currentLevel++) { + uint64_t offset = static_cast<uint64_t>( counter.m_ParticleOffsetAtRank[currentLevel] ); + // For BTD, the offset include the number of particles already flushed + if (isBTD) offset += ParticleFlushOffset; + for (ParticleIter pti(*pc, currentLevel); pti.isValid(); ++pti) { + auto const numParticleOnTile = pti.numParticles(); + uint64_t const numParticleOnTile64 = static_cast<uint64_t>( numParticleOnTile ); + + // Do not call storeChunk() with zero-sized particle tiles: + // https://github.com/openPMD/openPMD-api/issues/1147 + if (numParticleOnTile == 0) continue; + + // get position and particle ID from aos + // note: this implementation iterates the AoS 4x... + // if we flush late as we do now, we can also copy out the data in one go + const auto &aos = pti.GetArrayOfStructs(); // size = numParticlesOnTile + { + // Save positions + auto const positionComponents = detail::getParticlePositionComponentLabels(); #if defined(WARPX_DIM_RZ) - { - std::shared_ptr<amrex::ParticleReal> z( - new amrex::ParticleReal[numParticleOnTile], - [](amrex::ParticleReal const *p) { delete[] p; } - ); - for (auto i = 0; i < numParticleOnTile; i++) - z.get()[i] = aos[i].pos(1); // {0: "r", 1: "z"} - std::string const positionComponent = "z"; - currSpecies["position"]["z"].storeChunk(z, {offset}, {numParticleOnTile64}); - } + { + std::shared_ptr<amrex::ParticleReal> z( + new amrex::ParticleReal[numParticleOnTile], + [](amrex::ParticleReal const *p) { delete[] p; } + ); + for (auto i = 0; i < numParticleOnTile; i++) + z.get()[i] = aos[i].pos(1); // {0: "r", 1: "z"} + std::string const positionComponent = "z"; + currSpecies["position"]["z"].storeChunk(z, {offset}, {numParticleOnTile64}); + } - // reconstruct x and y from polar coordinates r, theta - auto const& soa = pti.GetStructOfArrays(); - amrex::ParticleReal const* theta = soa.GetRealData(PIdx::theta).dataPtr(); - WARPX_ALWAYS_ASSERT_WITH_MESSAGE(theta != nullptr, "openPMD: invalid theta pointer."); - WARPX_ALWAYS_ASSERT_WITH_MESSAGE(int(soa.GetRealData(PIdx::theta).size()) == numParticleOnTile, - "openPMD: theta and tile size do not match"); - { - std::shared_ptr< amrex::ParticleReal > x( - new amrex::ParticleReal[numParticleOnTile], - [](amrex::ParticleReal const *p){ delete[] p; } - ); - std::shared_ptr< amrex::ParticleReal > y( - new amrex::ParticleReal[numParticleOnTile], - [](amrex::ParticleReal const *p){ delete[] p; } - ); - for (auto i=0; i<numParticleOnTile; i++) { - auto const r = aos[i].pos(0); // {0: "r", 1: "z"} - x.get()[i] = r * std::cos(theta[i]); - y.get()[i] = r * std::sin(theta[i]); - } - currSpecies["position"]["x"].storeChunk(x, {offset}, {numParticleOnTile64}); - currSpecies["position"]["y"].storeChunk(y, {offset}, {numParticleOnTile64}); - } + // reconstruct x and y from polar coordinates r, theta + auto const& soa = pti.GetStructOfArrays(); + amrex::ParticleReal const* theta = soa.GetRealData(PIdx::theta).dataPtr(); + WARPX_ALWAYS_ASSERT_WITH_MESSAGE(theta != nullptr, "openPMD: invalid theta pointer."); + WARPX_ALWAYS_ASSERT_WITH_MESSAGE(int(soa.GetRealData(PIdx::theta).size()) == numParticleOnTile, + "openPMD: theta and tile size do not match"); + { + std::shared_ptr< amrex::ParticleReal > x( + new amrex::ParticleReal[numParticleOnTile], + [](amrex::ParticleReal const *p){ delete[] p; } + ); + std::shared_ptr< amrex::ParticleReal > y( + new amrex::ParticleReal[numParticleOnTile], + [](amrex::ParticleReal const *p){ delete[] p; } + ); + for (auto i=0; i<numParticleOnTile; i++) { + auto const r = aos[i].pos(0); // {0: "r", 1: "z"} + x.get()[i] = r * std::cos(theta[i]); + y.get()[i] = r * std::sin(theta[i]); + } + currSpecies["position"]["x"].storeChunk(x, {offset}, {numParticleOnTile64}); + currSpecies["position"]["y"].storeChunk(y, {offset}, {numParticleOnTile64}); + } #else - for (auto currDim = 0; currDim < AMREX_SPACEDIM; currDim++) { - std::shared_ptr< amrex::ParticleReal > curr( - new amrex::ParticleReal[numParticleOnTile], - [](amrex::ParticleReal const *p){ delete[] p; } - ); - for (auto i=0; i<numParticleOnTile; i++) { - curr.get()[i] = aos[i].pos(currDim); + for (auto currDim = 0; currDim < AMREX_SPACEDIM; currDim++) { + std::shared_ptr<amrex::ParticleReal> curr( + new amrex::ParticleReal[numParticleOnTile], + [](amrex::ParticleReal const *p) { delete[] p; } + ); + for (auto i = 0; i < numParticleOnTile; i++) { + curr.get()[i] = aos[i].pos(currDim); + } + std::string const positionComponent = positionComponents[currDim]; + currSpecies["position"][positionComponent].storeChunk(curr, {offset}, + {numParticleOnTile64}); } - std::string const positionComponent = positionComponents[currDim]; - currSpecies["position"][positionComponent].storeChunk(curr, {offset}, {numParticleOnTile64}); - } #endif - // save particle ID after converting it to a globally unique ID - std::shared_ptr< uint64_t > ids( - new uint64_t[numParticleOnTile], - [](uint64_t const *p){ delete[] p; } - ); - for (auto i=0; i<numParticleOnTile; i++) { - ids.get()[i] = WarpXUtilIO::localIDtoGlobal( aos[i].id(), aos[i].cpu() ); - } - auto const scalar = openPMD::RecordComponent::SCALAR; - currSpecies["id"][scalar].storeChunk(ids, {offset}, {numParticleOnTile64}); - } - // save "extra" particle properties in AoS and SoA - SaveRealProperty(pti, - currSpecies, - offset, - write_real_comp, real_comp_names, - write_int_comp, int_comp_names); - - offset += numParticleOnTile64; - } + // save particle ID after converting it to a globally unique ID + std::shared_ptr<uint64_t> ids( + new uint64_t[numParticleOnTile], + [](uint64_t const *p) { delete[] p; } + ); + for (auto i = 0; i < numParticleOnTile; i++) { + ids.get()[i] = WarpXUtilIO::localIDtoGlobal(aos[i].id(), aos[i].cpu()); + } + auto const scalar = openPMD::RecordComponent::SCALAR; + currSpecies["id"][scalar].storeChunk(ids, {offset}, {numParticleOnTile64}); + + } + // save "extra" particle properties in AoS and SoA + SaveRealProperty(pti, + currSpecies, + offset, + write_real_comp, real_comp_names, + write_int_comp, int_comp_names); + + offset += numParticleOnTile64; + } } m_Series->flush(); } @@ -856,9 +853,6 @@ WarpXOpenPMDPlot::SetupRealProperties (ParticleContainer const * pc, } } - // attributes need to be set only the first time BTD flush is called for a snapshot - if (isBTD and m_doParticleSetUp == false) return; - std::set< std::string > addedRecords; // add meta-data per record only once for (auto idx=0; idx<pc->NumRealComps(); idx++) { auto ii = ParticleContainer::NStructReal + idx; // jump over extra AoS names @@ -976,51 +970,110 @@ void WarpXOpenPMDPlot::SetupPos ( openPMD::ParticleSpecies& currSpecies, const unsigned long long& np, - amrex::ParticleReal const charge, - amrex::ParticleReal const mass, bool const isBTD) { std::string options = "{}"; if (isBTD) options = "{ \"resizable\": true }"; - auto realType= openPMD::Dataset(openPMD::determineDatatype<amrex::ParticleReal>(), {np}, options); + auto realType = openPMD::Dataset(openPMD::determineDatatype<amrex::ParticleReal>(), {np}, options); auto idType = openPMD::Dataset(openPMD::determineDatatype< uint64_t >(), {np}, options); auto const positionComponents = detail::getParticlePositionComponentLabels(); for( auto const& comp : positionComponents ) { - currSpecies["positionOffset"][comp].resetDataset( realType ); currSpecies["position"][comp].resetDataset( realType ); } auto const scalar = openPMD::RecordComponent::SCALAR; currSpecies["id"][scalar].resetDataset( idType ); - currSpecies["charge"][scalar].resetDataset( realType ); - currSpecies["mass"][scalar].resetDataset( realType ); +} - if (isBTD and m_doParticleSetUp == false) return; - // make constant - for( auto const& comp : positionComponents ) { - currSpecies["positionOffset"][comp].makeConstant( 0. ); - } - currSpecies["charge"][scalar].makeConstant( charge ); - currSpecies["mass"][scalar].makeConstant( mass ); +void +WarpXOpenPMDPlot::SetConstParticleRecordsEDPIC ( + openPMD::ParticleSpecies& currSpecies, + const unsigned long long& np, + amrex::ParticleReal const charge, + amrex::ParticleReal const mass) +{ + auto realType = openPMD::Dataset(openPMD::determineDatatype<amrex::ParticleReal>(), {np}); - // meta data - currSpecies["position"].setUnitDimension( detail::getUnitDimension("position") ); - currSpecies["positionOffset"].setUnitDimension( detail::getUnitDimension("positionOffset") ); - currSpecies["charge"].setUnitDimension( detail::getUnitDimension("charge") ); - currSpecies["mass"].setUnitDimension( detail::getUnitDimension("mass") ); - - // meta data for ED-PIC extension - currSpecies["position"].setAttribute( "macroWeighted", 0u ); - currSpecies["position"].setAttribute( "weightingPower", 0.0 ); - currSpecies["positionOffset"].setAttribute( "macroWeighted", 0u ); - currSpecies["positionOffset"].setAttribute( "weightingPower", 0.0 ); - currSpecies["id"].setAttribute( "macroWeighted", 0u ); - currSpecies["id"].setAttribute( "weightingPower", 0.0 ); - currSpecies["charge"].setAttribute( "macroWeighted", 0u ); - currSpecies["charge"].setAttribute( "weightingPower", 1.0 ); - currSpecies["mass"].setAttribute( "macroWeighted", 0u ); - currSpecies["mass"].setAttribute( "weightingPower", 1.0 ); + auto const positionComponents = detail::getParticlePositionComponentLabels(); + for( auto const& comp : positionComponents ) { + currSpecies["positionOffset"][comp].resetDataset( realType ); + } + + // make constant + using namespace amrex::literals; + auto const scalar = openPMD::RecordComponent::SCALAR; + for( auto const& comp : positionComponents ) { + currSpecies["positionOffset"][comp].makeConstant( 0._prt ); + } + currSpecies["charge"][scalar].makeConstant( charge ); + currSpecies["mass"][scalar].makeConstant( mass ); + + // meta data + currSpecies["position"].setUnitDimension( detail::getUnitDimension("position") ); + currSpecies["positionOffset"].setUnitDimension( detail::getUnitDimension("positionOffset") ); + currSpecies["charge"].setUnitDimension( detail::getUnitDimension("charge") ); + currSpecies["mass"].setUnitDimension( detail::getUnitDimension("mass") ); + + // meta data for ED-PIC extension + currSpecies["position"].setAttribute( "macroWeighted", 0u ); + currSpecies["position"].setAttribute( "weightingPower", 0.0 ); + currSpecies["positionOffset"].setAttribute( "macroWeighted", 0u ); + currSpecies["positionOffset"].setAttribute( "weightingPower", 0.0 ); + currSpecies["id"].setAttribute( "macroWeighted", 0u ); + currSpecies["id"].setAttribute( "weightingPower", 0.0 ); + currSpecies["charge"].setAttribute( "macroWeighted", 0u ); + currSpecies["charge"].setAttribute( "weightingPower", 1.0 ); + currSpecies["mass"].setAttribute( "macroWeighted", 0u ); + currSpecies["mass"].setAttribute( "weightingPower", 1.0 ); + + // more ED-PIC attributes + currSpecies.setAttribute("particleShape", double(WarpX::noz)); + // TODO allow this per direction in the openPMD standard, ED-PIC extension? + currSpecies.setAttribute("particleShapes", []() { + return std::vector<double>{ +#if AMREX_SPACEDIM >= 2 + double(WarpX::nox), +#endif +#if defined(WARPX_DIM_3D) + double(WarpX::noy), +#endif + double(WarpX::noz) + }; + }()); + currSpecies.setAttribute("particlePush", []() { + switch (WarpX::particle_pusher_algo) { + case ParticlePusherAlgo::Boris : + return "Boris"; + case ParticlePusherAlgo::Vay : + return "Vay"; + case ParticlePusherAlgo::HigueraCary : + return "HigueraCary"; + default: + return "other"; + } + }()); + currSpecies.setAttribute("particleInterpolation", []() { + switch (WarpX::field_gathering_algo) { + case GatheringAlgo::EnergyConserving : + return "energyConserving"; + case GatheringAlgo::MomentumConserving : + return "momentumConserving"; + default: + return "other"; + } + }()); + currSpecies.setAttribute("particleSmoothing", "none"); + currSpecies.setAttribute("currentDeposition", []() { + switch (WarpX::current_deposition_algo) { + case CurrentDepositionAlgo::Esirkepov : + return "Esirkepov"; + case CurrentDepositionAlgo::Vay : + return "Vay"; + default: + return "directMorseNielson"; + } + }()); } |