Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output the potential phi on the macroparticles #4599

Merged
merged 30 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
96c0643
Output the potential phi on the macroparticles
RemiLehe Jan 10, 2024
9670ecc
Merge branch 'development' into output_particle_phi
RemiLehe Mar 4, 2024
abc846d
Merge branch 'development' into output_particle_phi
RemiLehe Mar 5, 2024
776d106
Fix compilation
RemiLehe Mar 5, 2024
40d88aa
Cleaner code by using `getParticlePosition`
RemiLehe Mar 5, 2024
9e29cf4
Add include statement
RemiLehe Mar 5, 2024
c502717
Update const-ness
RemiLehe Mar 5, 2024
cbe8172
Update code so that `phi` can be calculated
RemiLehe Mar 8, 2024
e260a0a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 8, 2024
cc9caf7
Better boundary error message
RemiLehe Mar 14, 2024
10cf0fe
Merge branch 'development' into output_particle_phi
RemiLehe Mar 14, 2024
d123e91
Define function to define tiles, for a NamedComponentParticleContainer
RemiLehe Mar 15, 2024
e0df6e1
Call parent class in WarpXParticleContainer
RemiLehe Mar 15, 2024
288a771
Call `defineAllParticleTiles` in diagnostics
RemiLehe Mar 15, 2024
f9f84b5
Merge branch 'development' into output_particle_phi
RemiLehe Mar 19, 2024
b429847
Merge branch 'development' into output_particle_phi
RemiLehe Apr 2, 2024
8493d01
Fix compilation issue
RemiLehe Apr 3, 2024
317aa16
Fix compilation for fixed precision
RemiLehe Apr 3, 2024
423414e
Merge branch 'development' into output_particle_phi
RemiLehe Apr 8, 2024
06dbe71
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 8, 2024
82181ca
Add automated test
RemiLehe Apr 8, 2024
7c3f679
Fix analysis script
RemiLehe Apr 8, 2024
21d8058
Merge branch 'output_particle_phi' of github.com:RemiLehe/WarpX into …
RemiLehe Apr 8, 2024
3b5b1b7
Update test so that they work correctly in RZ
RemiLehe Apr 8, 2024
f540222
Update automated test
RemiLehe Apr 8, 2024
b79720d
Update implementation of `phi` on the particles
RemiLehe Apr 8, 2024
f977aa6
Update test so as to trigger output of `phi`
RemiLehe Apr 8, 2024
a918c69
Update documentation
RemiLehe Apr 8, 2024
d8ce701
Update Source/Diagnostics/ParticleDiag/ParticleDiag.H
RemiLehe Apr 8, 2024
a43a65f
Apply suggestions from code review
RemiLehe Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Docs/source/usage/parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2676,7 +2676,8 @@ In-situ capabilities can be used by turning on Sensei or Ascent (provided they a
* ``<diag_name>.<species_name>.variables`` (list of `strings` separated by spaces, optional)
List of particle quantities to write to output.
Choices are ``w`` for the particle weight and ``ux`` ``uy`` ``uz`` for the particle momenta.
By default, all particle quantities are written.
When using the lab-frame electrostatic solver, ``phi`` (electrostatic potential, on the macroparticles) is also available.
By default, all particle quantities (except ``phi``) are written.
If ``<diag_name>.<species_name>.variables = none``, no particle data are written, except for particle positions, which are always included.

* ``<diag_name>.<species_name>.random_fraction`` (`float`) optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import numpy as np
import yt
from openpmd_viewer import OpenPMDTimeSeries
from scipy.constants import c
from scipy.optimize import fsolve

sys.path.insert(1, '../../../../warpx/Regression/Checksum/')
Expand Down Expand Up @@ -125,7 +127,6 @@ def calculate_error(E_axis, xmin, dx, nx):

return L2_error


L2_error_x = calculate_error(Ex_axis, xmin, dx, nx)
L2_error_y = calculate_error(Ey_axis, ymin, dy, ny)
L2_error_z = calculate_error(Ez_axis, zmin, dz, nz)
Expand All @@ -137,6 +138,21 @@ def calculate_error(E_axis, xmin, dx, nx):
assert L2_error_y < 0.05
assert L2_error_z < 0.05

# Check conservation of energy
def return_energies(iteration):
ux, uy, uz, phi, m, q, w = ts.get_particle(['ux', 'uy', 'uz', 'phi', 'mass', 'charge', 'w'], iteration=iteration)
E_kinetic = (w*m*c**2 * (np.sqrt(1 + ux**2 + uy**2 + uz**2) - 1)).sum()
E_potential = 0.5*(w*q*phi).sum() # potential energy of particles in their own space-charge field: includes factor 1/2
return E_kinetic, E_potential
ts = OpenPMDTimeSeries('./diags/diag2')
if 'phi' in ts.avail_record_components['electron']:
# phi is only available when this script is run with the labframe poisson solver
print('Checking conservation of energy')
Ek_i, Ep_i = return_energies(0)
Ek_f, Ep_f = return_energies(30)
assert Ep_f < 0.7*Ep_i # Check that potential energy changes significantly
assert abs( (Ek_i + Ep_i) - (Ek_f + Ep_f) ) < 0.003 * (Ek_i + Ep_i) # Check conservation of energy

# Checksum regression analysis
test_name = os.path.split(os.getcwd())[1]
checksumAPI.evaluate_checksum(test_name, filename)
8 changes: 7 additions & 1 deletion Examples/Tests/electrostatic_sphere/inputs_3d
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ electron.profile = parse_density_function
electron.density_function(x,y,z) = "(x*x + y*y + z*z < R0*R0)*n0"
electron.momentum_distribution_type = at_rest

diagnostics.diags_names = diag1
diagnostics.diags_names = diag1 diag2

diag1.intervals = 30
diag1.diag_type = Full
diag1.fields_to_plot = Ex Ey Ez rho

diag2.intervals = 30
diag2.diag_type = Full
diag2.fields_to_plot = none
diag2.format = openpmd
9 changes: 8 additions & 1 deletion Examples/Tests/electrostatic_sphere/inputs_rz
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ electron.profile = parse_density_function
electron.density_function(x,y,z) = "(x*x + y*y + z*z < R0*R0)*n0"
electron.momentum_distribution_type = at_rest

diagnostics.diags_names = diag1
diagnostics.diags_names = diag1 diag2

diag1.intervals = 30
diag1.diag_type = Full
diag1.fields_to_plot = Er Et Ez rho

diag2.intervals = 30
diag2.diag_type = Full
diag2.fields_to_plot = none
diag2.electron.variables = ux uy uz w phi
diag2.format = openpmd
2 changes: 1 addition & 1 deletion Regression/WarpX-tests.ini
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ analysisRoutine = Examples/Tests/electrostatic_sphere_eb/analysis_rz.py
[ElectrostaticSphereLabFrame]
buildDir = .
inputFile = Examples/Tests/electrostatic_sphere/inputs_3d
runtime_params = warpx.do_electrostatic=labframe
runtime_params = warpx.do_electrostatic=labframe diag2.electron.variables=ux uy uz w phi
dim = 3
addToCompileString =
cmakeSetupOpts = -DWarpX_DIMS=3
Expand Down
1 change: 1 addition & 0 deletions Source/Diagnostics/ParticleDiag/ParticleDiag.H
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public:
[[nodiscard]] PinnedMemoryParticleContainer* getPinnedParticleContainer() const { return m_pinned_pc; }
[[nodiscard]] std::string getSpeciesName() const { return m_name; }
amrex::Vector<int> m_plot_flags;
bool m_plot_phi = false; // Whether to plot the potential phi on the particles
RemiLehe marked this conversation as resolved.
Show resolved Hide resolved

bool m_do_random_filter = false;
bool m_do_uniform_filter = false;
Expand Down
19 changes: 13 additions & 6 deletions Source/Diagnostics/ParticleDiag/ParticleDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ ParticleDiag::ParticleDiag(
if (variables[0] != "none"){
const std::map<std::string, int> existing_variable_names = pc->getParticleComps();
for (const auto& var : variables){
const auto search = existing_variable_names.find(var);
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
search != existing_variable_names.end(),
"variables argument '" + var
+"' is not an existing attribute for this species");
m_plot_flags[existing_variable_names.at(var)] = 1;
if (var == "phi") {
// User requests phi on particle. This is *not* part of the variables that
// the particle container carries, and is only added to particles during output.
// Therefore, this case needs to be treated specifically.
m_plot_phi = true;
} else {
const auto search = existing_variable_names.find(var);
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
search != existing_variable_names.end(),
"variables argument '" + var
+"' is not an existing attribute for this species");
m_plot_flags[existing_variable_names.at(var)] = 1;
}
}
}
}
Expand Down
118 changes: 73 additions & 45 deletions Source/Diagnostics/WarpXOpenPMD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,48 +548,6 @@ for (unsigned i = 0, n = particle_diags.size(); i < n; ++i) {
pinned_pc->make_alike<amrex::PinnedArenaAllocator>() :
pc->make_alike<amrex::PinnedArenaAllocator>();

// names of amrex::Real and int particle attributes in SoA data
Copy link
Member Author

@RemiLehe RemiLehe Apr 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to move this code further down, so that it comes after tmp.AddRealComp("phi");

(This is because the code below reads tmp.getParticleRuntimeComps(); and sets the output flags accordingly.)

amrex::Vector<std::string> real_names;
amrex::Vector<std::string> int_names;
amrex::Vector<int> int_flags;
amrex::Vector<int> real_flags;

// see openPMD ED-PIC extension for namings
// note: an underscore separates the record name from its component
// for non-scalar records
// note: in RZ, we reconstruct x,y,z positions from r,z,theta in WarpX
#if !defined (WARPX_DIM_1D_Z)
real_names.push_back("position_x");
#endif
#if defined (WARPX_DIM_3D) || defined(WARPX_DIM_RZ)
real_names.push_back("position_y");
#endif
real_names.push_back("position_z");
real_names.push_back("weighting");
real_names.push_back("momentum_x");
real_names.push_back("momentum_y");
real_names.push_back("momentum_z");

// get the names of the real comps
real_names.resize(tmp.NumRealComps());
auto runtime_rnames = tmp.getParticleRuntimeComps();
for (auto const& x : runtime_rnames)
{
real_names[x.second+PIdx::nattribs] = detail::snakeToCamel(x.first);
}
// plot any "extra" fields by default
real_flags = particle_diags[i].m_plot_flags;
real_flags.resize(tmp.NumRealComps(), 1);
// and the names
int_names.resize(tmp.NumIntComps());
auto runtime_inames = tmp.getParticleRuntimeiComps();
for (auto const& x : runtime_inames)
{
int_names[x.second+0] = detail::snakeToCamel(x.first);
}
// plot by default
int_flags.resize(tmp.NumIntComps(), 1);

const auto mass = pc->AmIA<PhysicalSpecies::photon>() ? PhysConst::m_e : pc->getMass();
RandomFilter const random_filter(particle_diags[i].m_do_random_filter,
particle_diags[i].m_random_fraction);
Expand Down Expand Up @@ -631,6 +589,77 @@ for (unsigned i = 0, n = particle_diags.size(); i < n; ++i) {
particlesConvertUnits(ConvertDirection::SI_to_WarpX, pc, mass);
}

// Gather the electrostatic potential (phi) on the macroparticles
if ( particle_diags[i].m_plot_phi ) {
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
Comment on lines +592 to +594
Copy link
Member

@ax3l ax3l Apr 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this if block a function call please so it does separate out nicely and does not make this sub-function longer? I would call it add_phi_component or so and also declare it in ParticleIO.H?

(WarpX::electrostatic_solver_id == ElectrostaticSolverAlgo::LabFrame) ||
(WarpX::electrostatic_solver_id == ElectrostaticSolverAlgo::LabFrameElectroMagnetostatic),
"Output of the electrostatic potential (phi) on the particles was requested, "
"but this is only available for `warpx.do_electrostatic=labframe` or `labframe-electromagnetostatic`.");
// Using pinned PC indicates that the particles are not written at the same physical time (i.e. PIC iteration)
// that they were collected. This happens for diagnostics that use buffering (e.g. BackTransformed, BoundaryScraping).
// Here `phi` is gathered at the iteration when particles are written (not collected) and is thus mismatched.
// To avoid confusion, we raise an error in this case.
WARPX_ALWAYS_ASSERT_WITH_MESSAGE(
use_pinned_pc == false,
"Output of the electrostatic potential (phi) on the particles was requested, "
"but this is only available with `diag_type = Full`.");
tmp.AddRealComp("phi");
tmp.defineAllParticleTiles();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should not be needed anymore as of AMReX 24.04 :)

RemiLehe marked this conversation as resolved.
Show resolved Hide resolved
int const phi_index = tmp.getParticleComps().at("phi");
auto& warpx = WarpX::GetInstance();
#ifdef AMREX_USE_OMP
#pragma omp parallel if (amrex::Gpu::notInLaunchRegion())
#endif
for (int lev=0; lev<=warpx.finestLevel(); lev++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (int lev=0; lev<=warpx.finestLevel(); lev++) {
for (int lev=0; lev<=tmp.finestLevel(); lev++) {

const amrex::Geometry& geom = warpx.Geom(lev);
auto plo = geom.ProbLoArray();
auto dxi = geom.InvCellSizeArray();
amrex::MultiFab const& phi = warpx.getField( FieldType::phi_fp, lev, 0 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For less overhead, we should add a helper that returns an mf on all levels. -> follow-up pr

storeFieldOnParticles( tmp, plo, dxi, phi, phi_index, lev );
}
}

// names of amrex::Real and int particle attributes in SoA data
amrex::Vector<std::string> real_names;
amrex::Vector<std::string> int_names;
amrex::Vector<int> int_flags;
amrex::Vector<int> real_flags;
// see openPMD ED-PIC extension for namings
// note: an underscore separates the record name from its component
// for non-scalar records
// note: in RZ, we reconstruct x,y,z positions from r,z,theta in WarpX
#if !defined (WARPX_DIM_1D_Z)
real_names.push_back("position_x");
#endif
#if defined (WARPX_DIM_3D) || defined(WARPX_DIM_RZ)
real_names.push_back("position_y");
#endif
real_names.push_back("position_z");
real_names.push_back("weighting");
real_names.push_back("momentum_x");
real_names.push_back("momentum_y");
real_names.push_back("momentum_z");
// get the names of the real comps
real_names.resize(tmp.NumRealComps());
auto runtime_rnames = tmp.getParticleRuntimeComps();
for (auto const& x : runtime_rnames)
{
real_names[x.second+PIdx::nattribs] = detail::snakeToCamel(x.first);
}
// plot any "extra" fields by default
real_flags = particle_diags[i].m_plot_flags;
real_flags.resize(tmp.NumRealComps(), 1);
// and the names
int_names.resize(tmp.NumIntComps());
auto runtime_inames = tmp.getParticleRuntimeiComps();
for (auto const& x : runtime_inames)
{
int_names[x.second+0] = detail::snakeToCamel(x.first);
}
// plot by default
int_flags.resize(tmp.NumIntComps(), 1);

// real_names contains a list of all real particle attributes.
// real_flags is 1 or 0, whether quantity is dumped or not.
DumpToFile(&tmp,
Expand All @@ -640,9 +669,8 @@ for (unsigned i = 0, n = particle_diags.size(); i < n; ++i) {
int_flags,
real_names, int_names,
pc->getCharge(), pc->getMass(),
isBTD, isLastBTDFlush
);
}
isBTD, isLastBTDFlush);
}
}

void
Expand Down
45 changes: 45 additions & 0 deletions Source/Particles/ParticleIO.H
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#define PARTICLEIO_H_

#include "Particles/WarpXParticleContainer.H"
#include "Particles/Pusher/GetAndSetPosition.H"
#include "ablastr/particles/NodalFieldGather.H"
RemiLehe marked this conversation as resolved.
Show resolved Hide resolved

#include <AMReX_AmrParticles.H>
#include <AMReX_ParIter.H>
Expand Down Expand Up @@ -77,4 +79,47 @@ particlesConvertUnits (ConvertDirection convert_direction, T_ParticleContainer*
}
}

/** Gathers the field from a MultiFab to the macroparticles
* and stores it into a runtime component of the particle container
*
* @tparam T_ParticleContainer a WarpX particle container or AmrParticleContainer
* @param tmp the particle container on which to store the gathered field
* @param plo the coordinates of the low bound of the box, along each dimension
* @param dxi the inverse of the cell size, along each dimension
* @param phi a multifab that contains the field to be gathered
* @param phi_index the index of the runtime component where to store the gathered field
* @param lev the MR level
*/
template< typename T_ParticleContainer >
void
storeFieldOnParticles ( T_ParticleContainer& tmp,
amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& plo,
amrex::GpuArray<amrex::Real,AMREX_SPACEDIM> const& dxi,
amrex::MultiFab const& phi,
int const phi_index, int const lev ) {

using PinnedParIter = typename T_ParticleContainer::ParIterType;

for (PinnedParIter pti(tmp, lev); pti.isValid(); ++pti) {

auto phi_grid = phi[pti].array();
const auto getPosition = GetParticlePosition<PIdx>(pti);
amrex::ParticleReal* phi_particle_arr = pti.GetStructOfArrays().GetRealData(phi_index).dataPtr();

// Loop over the particles and update their position
amrex::ParallelFor( pti.numParticles(),
[=] AMREX_GPU_DEVICE (long ip) {

amrex::ParticleReal xp, yp, zp;
getPosition(ip, xp, yp, zp);
int i, j, k;
amrex::Real W[AMREX_SPACEDIM][2];
ablastr::particles::compute_weights(xp, yp, zp, plo, dxi, i, j, k, W);
amrex::Real const phi_value = ablastr::particles::interp_field_nodal(i, j, k, W, phi_grid);
phi_particle_arr[ip] = phi_value;
}
);
}
}

#endif /* PARTICLEIO_H_ */
Loading