Skip to content

Commit 350b568

Browse files
authored
Implement particle suborbits for the implicit solver (BLAST-WarpX#5969)
In the implicit solver, a Picard iteration is done for the particle advance. Occasionally, there will be a few particles that fail to converge (for example reaching a bistable state). A solution is to do sub-orbits during the advance, dividing the time step into smaller steps, with the Picard iteration done on each substep. This PR implements this scheme. The PR is ready for merging accept for adding a CI test. A full test of this would require a full implicit simulation with large time steps (where the need for the sub-orbits arises). A CI test could be added at a later point when the implicit capability is fully implemented. Merge first: - [x] BLAST-WarpX#6021 - [x] BLAST-WarpX#6116 - [x] BLAST-WarpX#6120
1 parent 603bc57 commit 350b568

File tree

12 files changed

+816
-44
lines changed

12 files changed

+816
-44
lines changed

Docs/source/usage/parameters.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ Overall simulation parameters
149149

150150
- ``implicit_evolve.max_particle_iterations`` (`integer`, default: 21)
151151
- ``implicit_evolve.particle_tolerance`` (`float`, default: 1.e-10)
152+
- ``implicit_evolve.particle_suborbits`` (`bool`, default: false)
153+
- ``implicit_evolve.print_unconverged_particle_details`` (`bool`, default: false)
152154

153155
- ``implicit_evolve.use_mass_matrices_jacobian`` (`bool`, default: false).
154156
When `true`, the plasma current density is computed using the mass matrices during the linear stage of PS-JFNK, replacing direct particle calculations. This can enable large speed ups for simulations with many particles.

Source/FieldSolver/ImplicitSolvers/ImplicitOptions.H

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using namespace amrex::literals;
77
struct ImplicitOptions {
88
int max_particle_iterations = 21;
99
amrex::ParticleReal particle_tolerance = 1.0e-10_prt;
10+
bool print_unconverged_particle_details = false;
1011
bool deposit_mass_matrices = false;
1112
bool linear_stage_of_jfnk = false;
1213
int nonlinear_iteration = 0;

Source/FieldSolver/ImplicitSolvers/ImplicitSolver.H

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public:
5454

5555
void CreateParticleAttributes () const;
5656

57+
bool DoParticleSuborbits () { return m_particle_suborbits; }
58+
5759
/**
5860
* \brief Advance fields and particles by one time step using the specified implicit algorithm
5961
*/
@@ -148,6 +150,18 @@ protected:
148150
*/
149151
int m_max_particle_iterations = 21;
150152

153+
/**
154+
* \brief whether to use suborbits for particles that fail to converge in
155+
* m_max_particle_iterations iterations
156+
*/
157+
bool m_particle_suborbits = false;
158+
159+
/**
160+
* \brief whether the details of unconverged particles are printed out during
161+
* the particle evolve loops
162+
*/
163+
bool m_print_unconverged_particle_details = false;
164+
151165
/**
152166
* \brief Whether to use mass matrices for the implicit solver
153167
*/

Source/FieldSolver/ImplicitSolvers/ImplicitSolver.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ void ImplicitSolver::CreateParticleAttributes () const
2727
pc->AddRealComp("ux_n", comm);
2828
pc->AddRealComp("uy_n", comm);
2929
pc->AddRealComp("uz_n", comm);
30+
31+
if (m_particle_suborbits) {
32+
pc->AddIntComp("nsuborbits", comm);
33+
}
3034
}
3135
}
3236

@@ -429,7 +433,9 @@ void ImplicitSolver::parseNonlinearSolverParams ( const amrex::ParmParse& pp )
429433
m_nlsolver_type = NonlinearSolverType::Newton;
430434
m_nlsolver = std::make_unique<NewtonSolver<WarpXSolverVec,ImplicitSolver>>();
431435
pp.query("max_particle_iterations", m_max_particle_iterations);
436+
pp.query("particle_suborbits", m_particle_suborbits);
432437
pp.query("particle_tolerance", m_particle_tolerance);
438+
pp.query("print_unconverged_particle_details", m_print_unconverged_particle_details);
433439
pp.query("use_mass_matrices_jacobian", m_use_mass_matrices_jacobian);
434440
pp.query("use_mass_matrices_pc", m_use_mass_matrices_pc);
435441
if (m_use_mass_matrices_jacobian || m_use_mass_matrices_pc) {

Source/FieldSolver/ImplicitSolvers/ThetaImplicitEM.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ void ThetaImplicitEM::PrintParameters () const
7373
amrex::Print() << "Time-bias parameter theta: " << m_theta << "\n";
7474
amrex::Print() << "max particle iterations: " << m_max_particle_iterations << "\n";
7575
amrex::Print() << "particle tolerance: " << m_particle_tolerance << "\n";
76+
amrex::Print() << "use particle suborbits: " << (m_particle_suborbits ? "true":"false") << "\n";
7677
if (m_nlsolver_type==NonlinearSolverType::Picard) {
7778
amrex::Print() << "Nonlinear solver type: Picard\n";
7879
}

Source/FieldSolver/ImplicitSolvers/WarpXImplicitOps.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ WarpX::SaveParticlesAtImplicitStepStart ( )
169169
amrex::ParticleReal* uy_n = pti.GetAttribs("uy_n").dataPtr();
170170
amrex::ParticleReal* uz_n = pti.GetAttribs("uz_n").dataPtr();
171171

172+
// Check if nsuborbits is present, and if so it is set to 1
173+
int *nsuborbits = (pc->HasiAttrib("nsuborbits") ? pti.GetiAttribs("nsuborbits").dataPtr() : nullptr);
174+
172175
const long np = pti.numParticles();
173176

174177
amrex::ParallelFor( np, [=] AMREX_GPU_DEVICE (long ip)
@@ -190,6 +193,10 @@ WarpX::SaveParticlesAtImplicitStepStart ( )
190193
uy_n[ip] = uy[ip];
191194
uz_n[ip] = uz[ip];
192195

196+
if (nsuborbits) {
197+
nsuborbits[ip] = 1;
198+
}
199+
193200
});
194201

195202
}

Source/Particles/Deposition/CurrentDeposition.H

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,10 @@ void doVillasenorDepositionShapeNImplicit ([[maybe_unused]]const amrex::Particle
22672267
np_to_deposit,
22682268
[=] AMREX_GPU_DEVICE (long const ip) {
22692269

2270+
// Skip particles with zero weight.
2271+
// This should only be the case for particles that will be suborbited.
2272+
if (wp[ip] == 0.) { return; }
2273+
22702274
#if !defined(WARPX_DIM_3D)
22712275
constexpr amrex::ParticleReal inv_c2 = 1._prt/(PhysConst::c*PhysConst::c);
22722276

Source/Particles/Deposition/MassMatricesDeposition.H

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,11 @@ void doVillasenorJandSigmaDeposition ( [[maybe_unused]] const amrex::ParticleRea
16301630
amrex::ParallelFor(
16311631
np_to_deposit,
16321632
[=] AMREX_GPU_DEVICE (long const ip) {
1633+
1634+
// Skip particles with zero weight.
1635+
// This should only be the case for particles that will be suborbited.
1636+
if (wp[ip] == 0.) { return; }
1637+
16331638
amrex::ParticleReal xp_nph, yp_nph, zp_nph;
16341639
GetPosition(ip, xp_nph, yp_nph, zp_nph);
16351640

Source/Particles/PhysicalParticleContainer.H

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,31 @@ public:
135135
long np_to_push,
136136
int lev, int gather_lev,
137137
amrex::Real dt, ScaleFields scaleFields,
138+
long & num_unconverged_particles,
139+
amrex::Gpu::DeviceVector<long> & unconverged_indices,
140+
amrex::Gpu::DeviceVector<amrex::ParticleReal> & saved_weights,
138141
DtType a_dt_type=DtType::Full);
139142

143+
void ImplicitPushXPSubOrbits (WarpXParIter& pti, ablastr::fields::MultiFabRegister& fields,
144+
amrex::FArrayBox const * exfab,
145+
amrex::FArrayBox const * eyfab,
146+
amrex::FArrayBox const * ezfab,
147+
amrex::FArrayBox const * bxfab,
148+
amrex::FArrayBox const * byfab,
149+
amrex::FArrayBox const * bzfab,
150+
ImplicitOptions const * implicit_options,
151+
amrex::IntVect ngEB,
152+
amrex::MultiFab * const jx,
153+
amrex::MultiFab * const jy,
154+
amrex::MultiFab * const jz,
155+
long offset,
156+
int lev, int gather_lev,
157+
amrex::Real dt, ScaleFields scaleFields,
158+
bool skip_deposition,
159+
long num_unconverged_particles,
160+
amrex::Gpu::DeviceVector<long> & unconverged_indices,
161+
amrex::Gpu::DeviceVector<amrex::ParticleReal> & saved_weights);
162+
140163
void PushP (int lev, amrex::Real dt,
141164
const amrex::MultiFab& Ex,
142165
const amrex::MultiFab& Ey,

Source/Particles/PhysicalParticleContainer.cpp

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,12 @@ PhysicalParticleContainer::Evolve (ablastr::fields::MultiFabRegister& fields,
547547

548548
int e_is_nodal = Ex.is_nodal() and Ey.is_nodal() and Ez.is_nodal();
549549

550+
// Temporary data used in the implicit advance
551+
amrex::Gpu::DeviceVector<long> unconverged_indices;
552+
amrex::Gpu::DeviceVector<amrex::ParticleReal> saved_weights;
553+
long num_unconverged_particles = 0;
554+
long num_unconverged_particles_c = 0;
555+
550556
//
551557
// Gather and push for particles not in the buffer
552558
//
@@ -559,11 +565,14 @@ PhysicalParticleContainer::Evolve (ablastr::fields::MultiFabRegister& fields,
559565
Ex.nGrowVect(), e_is_nodal,
560566
0, np_to_push, lev, gather_lev, dt, ScaleFields(false), a_dt_type);
561567
} else if (push_type == PushType::Implicit) {
568+
long const offset = 0;
562569
ImplicitPushXP(pti, exfab, eyfab, ezfab,
563570
bxfab, byfab, bzfab,
564571
implicit_options,
565572
Ex.nGrowVect(),
566-
0, np_to_push, lev, gather_lev, dt, ScaleFields(false), a_dt_type);
573+
offset, np_to_push, lev, gather_lev, dt, ScaleFields(false),
574+
num_unconverged_particles, unconverged_indices, saved_weights,
575+
a_dt_type);
567576
}
568577

569578
if (np_gather < np)
@@ -614,7 +623,9 @@ PhysicalParticleContainer::Evolve (ablastr::fields::MultiFabRegister& fields,
614623
implicit_options,
615624
cEx.nGrowVect(),
616625
nfine_gather, np-nfine_gather,
617-
lev, lev-1, dt, ScaleFields(false), a_dt_type);
626+
lev, lev-1, dt, ScaleFields(false),
627+
num_unconverged_particles_c, unconverged_indices, saved_weights,
628+
a_dt_type);
618629
}
619630
}
620631

@@ -662,7 +673,54 @@ PhysicalParticleContainer::Evolve (ablastr::fields::MultiFabRegister& fields,
662673
np_to_deposit, np-np_to_deposit, thread_num,
663674
lev, lev-1, dt, relative_time, push_type);
664675
}
665-
} // end of "if electrostatic_solver_id == ElectrostaticSolverAlgo::None"
676+
} // end of "if skip_deposition"
677+
678+
if (push_type == PushType::Implicit) {
679+
if (num_unconverged_particles > 0) {
680+
amrex::MultiFab * jx = fields.get(current_fp_string, Direction{0}, lev);
681+
amrex::MultiFab * jy = fields.get(current_fp_string, Direction{1}, lev);
682+
amrex::MultiFab * jz = fields.get(current_fp_string, Direction{2}, lev);
683+
long const offset = 0;
684+
ImplicitPushXPSubOrbits(pti, fields, exfab, eyfab, ezfab,
685+
bxfab, byfab, bzfab,
686+
implicit_options,
687+
Ex.nGrowVect(),
688+
jx, jy, jz,
689+
offset, lev, gather_lev, dt, ScaleFields(false), skip_deposition,
690+
num_unconverged_particles, unconverged_indices, saved_weights);
691+
}
692+
if (num_unconverged_particles_c > 0) {
693+
694+
amrex::MultiFab & cEx = *fields.get(FieldType::Efield_cax, Direction{0}, lev);
695+
amrex::MultiFab & cEy = *fields.get(FieldType::Efield_cax, Direction{1}, lev);
696+
amrex::MultiFab & cEz = *fields.get(FieldType::Efield_cax, Direction{2}, lev);
697+
amrex::MultiFab & cBx = *fields.get(FieldType::Bfield_cax, Direction{0}, lev);
698+
amrex::MultiFab & cBy = *fields.get(FieldType::Bfield_cax, Direction{1}, lev);
699+
amrex::MultiFab & cBz = *fields.get(FieldType::Bfield_cax, Direction{2}, lev);
700+
701+
// Data on the grid
702+
FArrayBox const* cexfab = &cEx[pti];
703+
FArrayBox const* ceyfab = &cEy[pti];
704+
FArrayBox const* cezfab = &cEz[pti];
705+
FArrayBox const* cbxfab = &cBx[pti];
706+
FArrayBox const* cbyfab = &cBy[pti];
707+
FArrayBox const* cbzfab = &cBz[pti];
708+
709+
amrex::MultiFab * cjx = fields.get(FieldType::current_buf, Direction{0}, lev);
710+
amrex::MultiFab * cjy = fields.get(FieldType::current_buf, Direction{1}, lev);
711+
amrex::MultiFab * cjz = fields.get(FieldType::current_buf, Direction{2}, lev);
712+
713+
long const offset = num_unconverged_particles;
714+
ImplicitPushXPSubOrbits(pti, fields, cexfab, ceyfab, cezfab,
715+
cbxfab, cbyfab, cbzfab,
716+
implicit_options,
717+
cEx.nGrowVect(),
718+
cjx, cjy, cjz,
719+
offset, lev, lev-1, dt, ScaleFields(false), skip_deposition,
720+
num_unconverged_particles_c, unconverged_indices, saved_weights);
721+
}
722+
}
723+
666724
} // end of "if do_not_push"
667725

668726
if (has_rho && ! skip_deposition && ! do_not_deposit) {

0 commit comments

Comments
 (0)