Skip to content

Commit 10b298e

Browse files
authored
Implementing sparse matrix representation of Jacobian and using PETSc preconditioners (BLAST-WarpX#6184)
Implemented a `MatrixPC` preconditioner that constructs the sparse matrix representing the Jacobian for the implicit/semi-implicit time integrators. Currently, this PC provides no native matrix solvers; it relies on PETSc's preconditioners and matrix solvers. In the PETSc interfaces for the JFNK algorithm, added the option of using matrix-based preconditioners. In this PR, the matrix comprises the Maxwell's equation terms (discretized curl-curl) and the diagonal element of the mass matrix (for the current term). Other changes: + Implemented a wrapper for PETSc's SNES module to solve nonlinear systems; it is an alternative to the native Newton solver. + Streamlined the PETSc interface by creating a `PETScSolver_impl` base class that contains everything common to the linear and nonlinear solvers. `KSP_impl` and `SNES_impl` now inherit from it. Minor changes: + Moved PETSc initialization and finalizing calls to `main.cpp` to allow for the use of command line inputs. + Simplified initializing the nonlinear solver based on user input by removing `nlsolver_type_str` and directly using `AMREX_ENUM` types. CI test will be added in a later PR.
1 parent e5d1269 commit 10b298e

File tree

16 files changed

+1835
-161
lines changed

16 files changed

+1835
-161
lines changed

Source/FieldSolver/ImplicitSolvers/ImplicitSolver.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -443,18 +443,29 @@ void ImplicitSolver::ComputeJfromMassMatrices (const bool a_J_from_MM_only)
443443
void ImplicitSolver::parseNonlinearSolverParams ( const amrex::ParmParse& pp )
444444
{
445445

446-
std::string nlsolver_type_str;
447-
pp.get("nonlinear_solver", nlsolver_type_str);
446+
pp.get("nonlinear_solver", m_nlsolver_type);
448447

449-
if (nlsolver_type_str=="picard") {
450-
m_nlsolver_type = NonlinearSolverType::Picard;
448+
if (m_nlsolver_type == NonlinearSolverType::picard) {
449+
450+
// Picard
451451
m_nlsolver = std::make_unique<PicardSolver<WarpXSolverVec,ImplicitSolver>>();
452452
m_max_particle_iterations = 1;
453453
m_particle_tolerance = 0.0;
454+
454455
}
455-
else if (nlsolver_type_str=="newton") {
456-
m_nlsolver_type = NonlinearSolverType::Newton;
457-
m_nlsolver = std::make_unique<NewtonSolver<WarpXSolverVec,ImplicitSolver>>();
456+
else if ( (m_nlsolver_type == NonlinearSolverType::newton)
457+
|| (m_nlsolver_type == NonlinearSolverType::petsc_snes) ) {
458+
459+
// JFNK solvers
460+
if (m_nlsolver_type == NonlinearSolverType::newton) {
461+
m_nlsolver = std::make_unique<NewtonSolver<WarpXSolverVec,ImplicitSolver>>();
462+
} else {
463+
#ifdef AMREX_USE_PETSC
464+
m_nlsolver = std::make_unique<PETScSNES<WarpXSolverVec,ImplicitSolver>>();
465+
#else
466+
WARPX_ABORT_WITH_MESSAGE("ImplicitSolver::parseNonlinearSolverParams(): must compile with PETSc to use petsc_snes (AMREX_USE_PETSC must be defined)");
467+
#endif
468+
}
458469
pp.query("max_particle_iterations", m_max_particle_iterations);
459470
pp.query("particle_tolerance", m_particle_tolerance);
460471
pp.query("particle_suborbits", m_particle_suborbits);
@@ -846,7 +857,7 @@ void ImplicitSolver::PrintBaseImplicitSolverParameters () const
846857
amrex::Print() << "use particle suborbits: " << (m_particle_suborbits ? "true":"false") << "\n";
847858
amrex::Print() << "print unconverged particle details: " << (m_print_unconverged_particle_details ? "true":"false") << "\n";
848859
amrex::Print() << "Nonlinear solver type: " << amrex::getEnumNameString(m_nlsolver_type) << "\n";
849-
if (m_nlsolver_type==NonlinearSolverType::Newton) {
860+
if (m_nlsolver_type==NonlinearSolverType::newton) {
850861
amrex::Print() << "use mass matrices: " << (m_use_mass_matrices ? "true":"false") << "\n";
851862
if (m_use_mass_matrices) {
852863
amrex::Print() << " for jacobian calc: " << (m_use_mass_matrices_jacobian ? "true":"false") << "\n";

Source/FieldSolver/ImplicitSolvers/WarpXSolverDOF.H

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ class WarpX;
2323
*/
2424
struct WarpXSolverDOF
2525
{
26-
ablastr::fields::MultiLevelVectorField m_array;
27-
ablastr::fields::MultiLevelScalarField m_scalar;
26+
amrex::Vector<std::array<std::unique_ptr<amrex::iMultiFab>,3>> m_array;
27+
amrex::Vector<std::array<std::unique_ptr<amrex::iMultiFab>,3>> m_array_lhs;
28+
amrex::Vector<std::unique_ptr<amrex::iMultiFab>> m_scalar;
29+
amrex::Vector<std::unique_ptr<amrex::iMultiFab>> m_scalar_lhs;
2830

2931
warpx::fields::FieldType m_array_type = warpx::fields::FieldType::None;
3032
warpx::fields::FieldType m_scalar_type = warpx::fields::FieldType::None;

Source/FieldSolver/ImplicitSolvers/WarpXSolverDOF.cpp

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
4141

4242
m_array.resize(a_num_amr_levels);
4343
m_scalar.resize(a_num_amr_levels);
44+
m_array_lhs.resize(a_num_amr_levels);
45+
m_scalar_lhs.resize(a_num_amr_levels);
4446

4547
amrex::Long offset = 0;
4648
m_nDoFs_l = 0;
@@ -56,10 +58,10 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
5658
const ablastr::fields::VectorField this_array = a_WarpX->m_fields.get_alldirs(a_vector_type_name, lev);
5759
for (int n = 0; n < 3; n++) {
5860
auto ncomp = this_array[n]->nComp();
59-
m_array[lev][n] = new amrex::MultiFab( this_array[n]->boxArray(),
60-
this_array[n]->DistributionMap(),
61-
2*ncomp, // {local, global} for each comp
62-
amrex::IntVect::TheUnitVector() );
61+
m_array[lev][n] = std::make_unique<amrex::iMultiFab>(this_array[n]->boxArray(),
62+
this_array[n]->DistributionMap(),
63+
2*ncomp, // {local, global} for each comp
64+
this_array[n]->nGrowVect() );
6365
m_nDoFs_g += this_array[n]->boxArray().numPts()*ncomp;
6466

6567
m_array[lev][n]->setVal(-1.0);
@@ -70,9 +72,10 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
7072
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
7173
{
7274
for (int v = 0; v < ncomp; v++) {
73-
dof_arr(i,j,k,2*v) = (amrex::Real) bx.index(amrex::IntVect(AMREX_D_DECL(i, j, k))) * ncomp
74-
+ (amrex::Real) offset_mf
75-
+ (amrex::Real) offset;
75+
dof_arr(i,j,k,2*v) = bx.index(amrex::IntVect(AMREX_D_DECL(i, j, k))) * ncomp
76+
+ v
77+
+ offset_mf
78+
+ offset;
7679
}
7780
});
7881
offset_mf += bx.numPts()*ncomp;
@@ -94,10 +97,10 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
9497
for (int lev = 0; lev < a_num_amr_levels; ++lev) {
9598
const amrex::MultiFab* this_mf = a_WarpX->m_fields.get(a_scalar_type_name,lev);
9699
auto ncomp = this_mf->nComp();
97-
m_scalar[lev] = new amrex::MultiFab( this_mf->boxArray(),
98-
this_mf->DistributionMap(),
99-
2*ncomp, // {local, global} for each comp
100-
amrex::IntVect::TheUnitVector() );
100+
m_scalar[lev] = std::make_unique<amrex::iMultiFab>(this_mf->boxArray(),
101+
this_mf->DistributionMap(),
102+
2*ncomp, // {local, global} for each comp
103+
this_mf->nGrowVect() );
101104
m_nDoFs_g += this_mf->boxArray().numPts()*ncomp;
102105

103106
m_scalar[lev]->setVal(-1.0);
@@ -108,9 +111,10 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
108111
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
109112
{
110113
for (int v = 0; v < ncomp; v++) {
111-
dof_arr(i,j,k,2*v) = (amrex::Real) bx.index(amrex::IntVect(AMREX_D_DECL(i, j, k))) * ncomp
112-
+ (amrex::Real) offset_mf
113-
+ (amrex::Real) offset;
114+
dof_arr(i,j,k,2*v) = bx.index(amrex::IntVect(AMREX_D_DECL(i, j, k))) * ncomp
115+
+ v
116+
+ offset_mf
117+
+ offset;
114118
}
115119
});
116120
offset_mf += bx.numPts()*ncomp;
@@ -147,7 +151,7 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
147151
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
148152
{
149153
for (int v = 0; v < ncomp; v++) {
150-
dof_arr(i,j,k,2*v+1) = dof_arr(i,j,k,2*v) + (amrex::Real) offset_global;
154+
dof_arr(i,j,k,2*v+1) = dof_arr(i,j,k,2*v) + offset_global;
151155
}
152156
});
153157
}
@@ -164,12 +168,39 @@ void WarpXSolverDOF::Define ( WarpX* const a_WarpX,
164168
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
165169
{
166170
for (int v = 0; v < ncomp; v++) {
167-
dof_arr(i,j,k,2*v+1) = dof_arr(i,j,k,2*v) + (amrex::Real) offset_global;
171+
dof_arr(i,j,k,2*v+1) = dof_arr(i,j,k,2*v) + offset_global;
168172
}
169173
});
170174
}
171175
}
172176
}
173177

178+
if (m_array_type != FieldType::None) {
179+
for (int lev = 0; lev < a_num_amr_levels; ++lev) {
180+
const auto& geom = a_WarpX->Geom(lev);
181+
for (int n = 0; n < 3; n++) {
182+
m_array_lhs[lev][n] = std::make_unique<amrex::iMultiFab>(m_array[lev][n]->boxArray(),
183+
m_array[lev][n]->DistributionMap(),
184+
m_array[lev][n]->nComp(),
185+
0 );
186+
amrex::iMultiFab::Copy(*m_array_lhs[lev][n], *m_array[lev][n], 0, 0, m_array[lev][n]->nComp(), 0);
187+
m_array[lev][n]->FillBoundary(geom.periodicity());
188+
// do NOT call FillBoundary() on m_array_lhs
189+
}
190+
}
191+
}
192+
if (m_scalar_type != FieldType::None) {
193+
for (int lev = 0; lev < a_num_amr_levels; ++lev) {
194+
m_scalar_lhs[lev] = std::make_unique<amrex::iMultiFab>(m_scalar[lev]->boxArray(),
195+
m_scalar[lev]->DistributionMap(),
196+
m_scalar[lev]->nComp(),
197+
0 );
198+
amrex::iMultiFab::Copy(*m_scalar_lhs[lev], *m_scalar[lev], 0, 0, m_scalar[lev]->nComp(), 0);
199+
const auto& geom = a_WarpX->Geom(lev);
200+
m_scalar[lev]->FillBoundary(geom.periodicity());
201+
// do NOT call FillBoundary() on m_scalar_lhs
202+
}
203+
}
204+
174205
amrex::Print() << "Defined DOF object for linear solves (total DOFs = " << m_nDoFs_g << ").\n";
175206
}

Source/FieldSolver/ImplicitSolvers/WarpXSolverVec.H

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,13 @@ public:
310310
void copyTo (amrex::Real* const) const;
311311

312312
// return WarpX pointer
313-
inline auto getWarpX () const { return m_WarpX; }
313+
[[nodiscard]] auto getWarpX () const { return m_WarpX; }
314314

315315
// return the number of AMR levels
316-
inline auto numAMRLevels () const { return m_num_amr_levels; }
316+
[[nodiscard]] auto numAMRLevels () const { return m_num_amr_levels; }
317+
318+
// return DOFs object pointer
319+
inline const auto& getDOFsObject () const { return m_dofs; }
317320

318321
private:
319322

Source/FieldSolver/ImplicitSolvers/WarpXSolverVec.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void WarpXSolverVec::Define ( WarpX* a_WarpX,
105105
if (m_dofs == nullptr) {
106106
m_dofs = std::make_unique<WarpXSolverDOF>();
107107
m_dofs->Define(m_WarpX, m_num_amr_levels, m_vector_type_name, m_scalar_type_name);
108+
amrex::ExecOnFinalize([p=&m_dofs] () { p->reset(); });
108109
}
109110

110111
m_is_defined = true;
@@ -158,7 +159,7 @@ void WarpXSolverVec::copyFrom ( const amrex::Real* const a_arr)
158159
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
159160
{
160161
for (int v = 0; v < ncomp; v++) {
161-
int dof = (int) dof_arr(i,j,k,2*v); // local
162+
int dof = dof_arr(i,j,k,2*v); // local
162163
data_arr(i,j,k,v) = a_arr[dof];
163164
}
164165
});
@@ -174,7 +175,7 @@ void WarpXSolverVec::copyFrom ( const amrex::Real* const a_arr)
174175
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
175176
{
176177
for (int v = 0; v < ncomp; v++) {
177-
int dof = (int) dof_arr(i,j,k,2*v); // local
178+
int dof = dof_arr(i,j,k,2*v); // local
178179
data_arr(i,j,k,v) = a_arr[dof];
179180
}
180181
});
@@ -203,7 +204,7 @@ void WarpXSolverVec::copyTo ( amrex::Real* const a_arr) const
203204
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
204205
{
205206
for (int v = 0; v < ncomp; v++) {
206-
int dof = (int) dof_arr(i,j,k,2*v); // local
207+
int dof = dof_arr(i,j,k,2*v); // local
207208
a_arr[dof] = data_arr(i,j,k,v);
208209
}
209210
});
@@ -219,7 +220,7 @@ void WarpXSolverVec::copyTo ( amrex::Real* const a_arr) const
219220
ParallelFor( bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
220221
{
221222
for (int v = 0; v < ncomp; v++) {
222-
int dof = (int) dof_arr(i,j,k,2*v); // local
223+
int dof = dof_arr(i,j,k,2*v); // local
223224
a_arr[dof] = data_arr(i,j,k,v);
224225
}
225226
});

Source/Initialization/WarpXInit.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
#include <ablastr/parallelization/MPIInitHelpers.H>
1818
#include <ablastr/warn_manager/WarnManager.H>
1919

20+
#ifdef AMREX_USE_PETSC
21+
#include <petscsys.h>
22+
#endif
23+
24+
2025
#include <optional>
2126
#include <string>
2227

@@ -25,10 +30,19 @@ void warpx::initialization::initialize_external_libraries(int argc, char* argv[]
2530
ablastr::parallelization::mpi_init(argc, argv);
2631
warpx::initialization::amrex_init(argc, argv);
2732
ablastr::math::anyfft::setup();
33+
#ifdef AMREX_USE_PETSC
34+
PETSC_COMM_WORLD = amrex::ParallelContext::CommunicatorSub();
35+
PetscInitialize(&argc, &argv, nullptr, "WarpX with PETSc");
36+
amrex::Print() << "Initialized PETSc.\n";
37+
#endif
2838
}
2939

3040
void warpx::initialization::finalize_external_libraries ()
3141
{
42+
#ifdef AMREX_USE_PETSC
43+
PetscFinalize();
44+
amrex::Print() << "Finalized PETSc.\n";
45+
#endif
3246
ablastr::math::anyfft::cleanup();
3347
amrex::Finalize();
3448
ablastr::parallelization::mpi_finalize();

Source/NonlinearSolvers/JacobianFunctionMF.H

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "LinearFunction.H"
1111
#include "CurlCurlMLMGPC.H"
12+
#include "MatrixPC.H"
1213
#include "JacobiPC.H"
1314
#include "Utils/TextMsg.H"
1415
#include <AMReX_Config.H>
@@ -56,6 +57,18 @@ class JacobianFunctionMF : public LinearFunction<T,Ops>
5657
if (m_usePreCond) { m_preCond->Update(a_X); }
5758
}
5859

60+
inline
61+
void getPCMatrix( amrex::Gpu::DeviceVector<int>& a_ridx_g,
62+
amrex::Gpu::DeviceVector<int>& a_nnz,
63+
amrex::Gpu::DeviceVector<int>& a_cidx_g,
64+
amrex::Gpu::DeviceVector<RT>& a_aij,
65+
int& a_n, int& a_ncols_max ) override
66+
{
67+
AMREX_ALWAYS_ASSERT(m_usePreCond);
68+
m_preCond->getPCMatrix(a_ridx_g, a_nnz, a_cidx_g, a_aij, a_n, a_ncols_max);
69+
}
70+
71+
5972
T makeVecLHS () const override;
6073
T makeVecRHS () const override;
6174

@@ -153,17 +166,9 @@ void JacobianFunctionMF<T,Ops>::define ( const T& a_U,
153166
m_preCond = std::make_unique<CurlCurlMLMGPC<T,Ops>>();
154167
} else if (m_pc_type == PreconditionerType::pc_jacobi) {
155168
m_preCond = std::make_unique<JacobiPC<T,Ops>>();
156-
} else if (m_pc_type == PreconditionerType::pc_petsc) {
157-
#ifdef AMREX_USE_PETSC
158-
WARPX_ABORT_WITH_MESSAGE("JacobianFunctionMF::Define(): pc_petsc not yet implemented");
159-
#else
160-
WARPX_ABORT_WITH_MESSAGE("JacobianFunctionMF::Define(): must compile with PETSc to use pc_petsc (AMREX_USE_PETSC must be defined)");
161-
#endif
162169
} else {
163-
std::stringstream convergenceMsg;
164-
convergenceMsg << "JacobianFunctionMF::define(): " << amrex::getEnumNameString(m_pc_type)
165-
<< " is not a valid preconditioner type.";
166-
WARPX_ABORT_WITH_MESSAGE(convergenceMsg.str());
170+
m_preCond = std::make_unique<MatrixPC<T,Ops>>();
171+
m_preCond->setName(amrex::getEnumNameString(m_pc_type));
167172
}
168173
m_preCond->Define(a_U, a_ops);
169174
}

Source/NonlinearSolvers/LinearFunction.H

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ class LinearFunction
4343
//! update preconditioner
4444
virtual void updatePreCondMat ( const T& a_X ) = 0;
4545

46+
//! get sparse matrix representation of preconditioner
47+
virtual void getPCMatrix( amrex::Gpu::DeviceVector<int>&,
48+
amrex::Gpu::DeviceVector<int>&,
49+
amrex::Gpu::DeviceVector<int>&,
50+
amrex::Gpu::DeviceVector<RT>&,
51+
int&, int& ) = 0;
52+
4653
//! create a new vector given a defined vector
4754
inline void create ( T& a_Z, const T& a_U )
4855
{

0 commit comments

Comments
 (0)