include/pops/coupling/schur/source/condensed_schur_source_stepper.hpp Source FileΒΆ

adc_cpp: include/pops/coupling/schur/source/condensed_schur_source_stepper.hpp Source File
adc_cpp 0.3.0
Model-free C++23 core for coupled hyperbolic-elliptic systems on adaptive (AMR) meshes, with MPI and GPU (Kokkos) backends
condensed_schur_source_stepper.hpp
Go to the documentation of this file.
1#pragma once
2
5#include <pops/coupling/schur/core/schur_condensation.hpp> // ElectrostaticLorentzCondensation (builder #124)
6#include <pops/coupling/schur/core/schur_source_kernels.hpp> // shared geometry-free kernels + validate_krylov_params (#263)
12#include <pops/numerics/elliptic/mg/geometric_mg.hpp> // operator + preconditioner (#120)
13#include <pops/numerics/elliptic/linear/krylov_solver.hpp> // TensorKrylovSolver (BiCGStab, #122)
14#include <pops/numerics/linalg/lorentz_eliminator.hpp> // closed-form B^{-1} (#118)
15
16#include <stdexcept>
17
75
76namespace pops {
77
78namespace detail {
79
93 int c_rho, c_mx, c_my;
94 POPS_HD void operator()(int i, int j) const {
95 const Real gx = (phi(i + 1, j, 0) - phi(i - 1, j, 0)) * half_idx; // d_x phi^{n+theta}
96 const Real gy = (phi(i, j + 1, 0) - phi(i, j - 1, 0)) * half_idy; // d_y phi^{n+theta}
97 const Real rhsx = vx(i, j, 0) - th_dt * gx; // (v^n - theta dt grad phi)_x
98 const Real rhsy = vy(i, j, 0) - th_dt * gy;
99 const LorentzEliminator le(th_dt, Real(1), bz(i, j, 0)); // w = th_dt * B_z
100 Real nx, ny;
101 le.apply_Binv(rhsx, rhsy, nx, ny); // v^{n+theta} = B^{-1}(v^n - theta dt grad phi)
102 nvx(i, j, 0) = nx;
103 nvy(i, j, 0) = ny;
104 const Real rho = st(i, j, c_rho); // rho^n (frozen in the source)
105 st(i, j, c_mx) = rho * nx; // mom^{n+theta} = rho^n v^{n+theta}
106 st(i, j, c_my) = rho * ny;
107 }
108};
109
110// The 5 geometry-free source kernels (extrapolate-scalar/velocity, energy, extract-velocity, copy-Bz)
111// live in <pops/coupling/schur/schur_source_kernels.hpp> and are shared with the polar / AMR steppers (#263).
112// Only SchurReconstructKernel stays local: it carries the discretization-specific centered gradient.
113
114} // namespace detail
115
127 public:
133 CondensedSchurSourceStepper(const VariableSet& vars, const Geometry& geom, const BoxArray& ba,
134 const BCRec& bcPhi, Real alpha, int n_precond_vcycles = 1)
136 vars, vars.index_of(VariableRole::Density), vars.index_of(VariableRole::MomentumX),
137 vars.index_of(VariableRole::MomentumY), vars.index_of(VariableRole::Energy), geom, ba,
138 bcPhi, alpha, n_precond_vcycles) {}
139
146 CondensedSchurSourceStepper(const VariableSet& vars, int c_rho, int c_mx, int c_my, int c_E,
147 const Geometry& geom, const BoxArray& ba, const BCRec& bcPhi,
148 Real alpha, int n_precond_vcycles = 1)
149 : vars_(vars),
150 c_rho_(c_rho),
151 c_mx_(c_mx),
152 c_my_(c_my),
153 c_E_(c_E),
154 alpha_(alpha),
155 geom_(geom),
156 bcPhi_(bcPhi),
157 n_precond_(n_precond_vcycles),
158 ba_(ba),
159 dm_(ba.size(), n_ranks()),
160 // FULL operator (BiCGStab matvec) + SYMMETRIC preconditioner (without cross terms).
161 op_(geom, ba, bcPhi),
162 precond_(geom, ba, bcPhi),
163 // reused buffers (allocated ONCE):
164 eps_x_(ba, dm_, 1, 1),
165 eps_y_(ba, dm_, 1, 1),
166 a_xy_(ba, dm_, 1, 1),
167 a_yx_(ba, dm_, 1, 1),
168 rhs_schur_(ba, dm_, 1, 0),
169 bz_(ba, dm_, 1, 1),
170 vx_n_(ba, dm_, 1, 0),
171 vy_n_(ba, dm_, 1, 0),
172 vx_t_(ba, dm_, 1, 0),
173 vy_t_(ba, dm_, 1, 0),
174 phi_n_(ba, dm_, 1, 1),
175 kry_(op_, precond_, n_precond_) {
176 if (c_rho_ < 0 || c_mx_ < 0 || c_my_ < 0)
177 throw std::runtime_error(
178 "CondensedSchurSourceStepper: the fluid block must expose the Density, MomentumX "
179 "and MomentumY roles (VariableSet.roles populated).");
180 }
181
183 bool has_energy() const { return c_E_ >= 0; }
184
190 void step(MultiFab& state, MultiFab& phi, const MultiFab& bz_field, int c_bz, Real theta,
191 Real dt) {
192 const Real th_dt = theta * dt;
193
194 // -1) freeze phi^n (for the final extrapolation; op_'s phi() will be overwritten by the solve).
195 copy_comp0(phi_n_, phi);
196
197 // 0) extract v^n = (mx, my)/rho and copy B_z into the internal buffer (1 ghost filled afterwards).
198 for (int li = 0; li < state.local_size(); ++li) {
199 const ConstArray4 s = state.fab(li).const_array();
200 for_each_cell(state.box(li),
201 detail::ExtractVelocityKernel{s, vx_n_.fab(li).array(), vy_n_.fab(li).array(),
202 c_rho_, c_mx_, c_my_});
203 for_each_cell(bz_.box(li), detail::CopyBzKernel{bz_field.fab(li).const_array(),
204 bz_.fab(li).array(), c_bz});
205 }
206 const BCRec ebc = coeff_bc(bcPhi_);
207 fill_ghosts(bz_, geom_.domain, ebc); // B_z read at (i,j) only here, but 1 ghost = MPI-clean
208
209 // 1) ASSEMBLE A_op = I + c rho B^{-1} + condensed RHS (#124). phi gets its ghosts filled (BC).
210 ElectrostaticLorentzCondensation builder(vars_, alpha_, theta, dt);
211 builder.assemble_operator(state, bz_, geom_, bcPhi_, eps_x_, eps_y_, a_xy_, a_yx_);
212 builder.assemble_rhs(phi, state, bz_, geom_, bcPhi_, rhs_schur_);
213
214 // 2) configure the FULL operator (op_) and the SYMMETRIC preconditioner (precond_), then
215 // SOLVE L_int(phi) = -rhs_schur (cf. the sign convention of the header) by BiCGStab.
216 op_.set_epsilon_anisotropic(eps_x_, eps_y_);
217 op_.set_cross_terms(a_xy_, a_yx_);
218 precond_.set_epsilon_anisotropic(eps_x_, eps_y_); // symmetric part: cross terms DROPPED.
219 // warm start: phi^n -> op_.phi(); rhs = -rhs_schur.
220 copy_comp0(op_.phi(), phi);
221 negate_into(op_.rhs(), rhs_schur_);
222 // kry_ is a persistent MEMBER (its ~10 MultiFab are allocated ONCE at construction, not
223 // per call): solve() recomputes the entirety of its state per solve (prepare_solve, r0, directions),
224 // so the result is BIT-IDENTICAL to the old per-call construction of a local TensorKrylovSolver here.
225 // Tolerances configurable via set_krylov (audit 2026-06); defaults = historical 1e-10/400.
226 last_result_ = kry_.solve(krylov_tol_, krylov_max_iters_);
227 copy_comp0(phi, op_.phi()); // phi <- phi^{n+theta}
228
229 // 3) RECONSTRUCT v^{n+theta} = B^{-1}(v^n - theta dt grad phi^{n+theta}); mom = rho v.
230 device_fence();
231 fill_ghosts(phi, geom_.domain, bcPhi_); // centered grad reads phi(i+-1), phi(j+-1)
232 const Real half_idx = Real(1) / (Real(2) * geom_.dx());
233 const Real half_idy = Real(1) / (Real(2) * geom_.dy());
234 for (int li = 0; li < state.local_size(); ++li) {
236 state.box(li),
238 phi.fab(li).const_array(), vx_n_.fab(li).const_array(), vy_n_.fab(li).const_array(),
239 bz_.fab(li).const_array(), state.fab(li).array(), vx_t_.fab(li).array(),
240 vy_t_.fab(li).array(), th_dt, half_idx, half_idy, c_rho_, c_mx_, c_my_});
241 }
242 // vx_t_/vy_t_ now carry v^{n+theta}.
243
244 // 5) EXTRAPOLATE phi and v from the theta-stage to the full step: f^{n+1} = f^n + (1/theta)(f^{n+theta}-f^n).
245 // theta = 1 -> identity. phi^n is frozen in phi_n_ (step -1), v^n in vx_n_/vy_n_.
246 const Real inv_theta = Real(1) / theta;
247 for (int li = 0; li < phi.local_size(); ++li)
249 phi_n_.fab(li).const_array(), phi.fab(li).array(), inv_theta});
250 // v + mom: linear extrapolation, recompose mom = rho v^{n+1}. vx_t_/vy_t_ then carry v^{n+1}.
251 for (int li = 0; li < state.local_size(); ++li)
253 vx_n_.fab(li).const_array(), vy_n_.fab(li).const_array(),
254 vx_t_.fab(li).array(), vy_t_.fab(li).array(),
255 state.fab(li).array(), inv_theta, c_rho_, c_mx_, c_my_});
256
257 // 4) ENERGY (if role present): E^{n+1} = E^n + (1/2) rho (|v^{n+1}|^2 - |v^n|^2).
258 if (c_E_ >= 0)
259 for (int li = 0; li < state.local_size(); ++li)
261 vx_n_.fab(li).const_array(), vy_n_.fab(li).const_array(),
262 vx_t_.fab(li).const_array(), vy_t_.fab(li).const_array(),
263 state.fab(li).array(), c_rho_, c_E_});
264
265 // 6) FILL the ghosts of the state and of the potential (MPI halos / physical BC) before returning control.
266 device_fence();
267 fill_ghosts(state, geom_.domain, bcU_default());
268 fill_ghosts(phi, geom_.domain, bcPhi_);
269 }
270
272 const KrylovResult& last_solve() const { return last_result_; }
273
277 void set_krylov(Real tol, int max_iters) {
278 detail::validate_krylov_params(tol, max_iters, "CondensedSchurSourceStepper::set_krylov");
279 krylov_tol_ = tol;
280 krylov_max_iters_ = max_iters;
281 }
282
283 int density_comp() const { return c_rho_; }
284 int momentum_x_comp() const { return c_mx_; }
285 int momentum_y_comp() const { return c_my_; }
286 int energy_comp() const { return c_E_; }
287
288 private:
292 BCRec bcU_default() const { return coeff_bc(bcPhi_); }
293
295 static BCRec coeff_bc(const BCRec& bc) {
296 auto fo = [](BCType t) { return t == BCType::Periodic ? t : BCType::Foextrap; };
297 BCRec b;
298 b.xlo = fo(bc.xlo);
299 b.xhi = fo(bc.xhi);
300 b.ylo = fo(bc.ylo);
301 b.yhi = fo(bc.yhi);
302 return b;
303 }
304
306 void copy_comp0(MultiFab& dst, const MultiFab& src) {
307 for (int li = 0; li < dst.local_size(); ++li)
308 for_each_cell(dst.box(li),
309 detail::CopyComp0Kernel{dst.fab(li).array(), src.fab(li).const_array()});
310 }
311
313 void negate_into(MultiFab& dst, const MultiFab& src) {
314 for (int li = 0; li < dst.local_size(); ++li)
315 for_each_cell(dst.box(li),
316 detail::NegateKernel{src.fab(li).const_array(), dst.fab(li).array()});
317 }
318
319 VariableSet vars_;
320 int c_rho_, c_mx_, c_my_, c_E_;
321 Real alpha_;
322 Geometry geom_;
323 BCRec bcPhi_;
324 int n_precond_;
325 BoxArray ba_;
326 DistributionMapping dm_;
327 GeometricMG op_, precond_;
328 MultiFab eps_x_, eps_y_, a_xy_, a_yx_;
329 MultiFab rhs_schur_;
330 MultiFab bz_;
331 MultiFab vx_n_, vy_n_;
332 MultiFab vx_t_, vy_t_;
333 MultiFab
334 phi_n_;
335 KrylovResult last_result_;
336 Real krylov_tol_ = Real(1e-10);
337 int krylov_max_iters_ = 400;
338 // PERSISTENT Krylov solver (BiCGStab + MG precond). Allocates its buffers (r/rhat/p/v/s/t/phat/
339 // shat + BC offsets) ONCE; step() reuses kry_ without reallocation. MUST be declared AFTER
340 // op_/precond_/n_precond_ (which it references): init order (after them) and destruction order (before them)
341 // correct. Bit-identical to the old per-call construction (solve() reinitializes its entire state).
342 TensorKrylovSolver kry_;
343};
344
345} // namespace pops
Ordered list of boxes tiling a level.
Definition box_array.hpp:22
Schur-condensed SOURCE STAGE, STANDALONE (transport frozen), GENERIC over any fluid block that expose...
Definition condensed_schur_source_stepper.hpp:126
void set_krylov(Real tol, int max_iters)
Tolerance / iteration budget of the stage Krylov solve (BiCGStab).
Definition condensed_schur_source_stepper.hpp:277
int density_comp() const
Definition condensed_schur_source_stepper.hpp:283
CondensedSchurSourceStepper(const VariableSet &vars, const Geometry &geom, const BoxArray &ba, const BCRec &bcPhi, Real alpha, int n_precond_vcycles=1)
vars: descriptor of the fluid block; MUST expose Density / MomentumX / MomentumY (Energy optional).
Definition condensed_schur_source_stepper.hpp:133
int momentum_x_comp() const
Definition condensed_schur_source_stepper.hpp:284
CondensedSchurSourceStepper(const VariableSet &vars, int c_rho, int c_mx, int c_my, int c_E, const Geometry &geom, const BoxArray &ba, const BCRec &bcPhi, Real alpha, int n_precond_vcycles=1)
Variant with EXPLICIT COMPONENTS (audit 2026-06, wave 2: roles/fields carried in the ABI).
Definition condensed_schur_source_stepper.hpp:146
bool has_energy() const
true if the model carries an Energy role (energy update active).
Definition condensed_schur_source_stepper.hpp:183
int energy_comp() const
Definition condensed_schur_source_stepper.hpp:286
const KrylovResult & last_solve() const
Diagnostic of the last solve (BiCGStab iterations, relative residual, convergence).
Definition condensed_schur_source_stepper.hpp:272
void step(MultiFab &state, MultiFab &phi, const MultiFab &bz_field, int c_bz, Real theta, Real dt)
Condensed SOURCE STAGE, IN-PLACE on state and phi.
Definition condensed_schur_source_stepper.hpp:190
int momentum_y_comp() const
Definition condensed_schur_source_stepper.hpp:285
GENERIC builder of the condensed source stage for the electrostatic + Lorentz source (kind="electrost...
Definition schur_condensation.hpp:153
void assemble_operator(const MultiFab &state, const MultiFab &bz, const Geometry &geom, const BCRec &bc, MultiFab &eps_x, MultiFab &eps_y, MultiFab &a_xy, MultiFab &a_yx) const
Assembles ONLY the coefficients of the tensor operator A_op = I + c rho B^{-1} into MultiFab eps_x/ep...
Definition schur_condensation.hpp:185
void assemble_rhs(MultiFab &phi_n, const MultiFab &state, const MultiFab &bz, const Geometry &geom, const BCRec &bc, MultiFab &rhs) const
Assembles ONLY the condensed right-hand side -Lap phi^n - theta dt alpha div(rho B^{-1} v^n).
Definition schur_condensation.hpp:216
ConstArray4 const_array() const
READ handle (POD device-copyable) over this Fab. Valid as long as the Fab lives.
Definition fab2d.hpp:96
MultiFab & rhs()
Definition geometric_mg.hpp:222
MultiFab & phi()
Definition geometric_mg.hpp:221
void set_epsilon_anisotropic(std::function< Real(Real, Real)> eps_x_fn, std::function< Real(Real, Real)> eps_y_fn)
Definition geometric_mg.hpp:274
void set_cross_terms(std::function< Real(Real, Real)> a_xy_fn, std::function< Real(Real, Real)> a_yx_fn)
Definition geometric_mg.hpp:329
Field distributed over a level: decomposition (BoxArray) + distribution (DistributionMapping) + ncomp...
Definition multifab.hpp:33
Fab2D & fab(int li)
Local fab at index li (0 <= li < local_size()), for writing.
Definition multifab.hpp:67
const Box2D & box(int li) const
VALID box of local fab li.
Definition multifab.hpp:71
int local_size() const
Number of fabs OWNED by this rank (bound on local indices).
Definition multifab.hpp:65
void solve()
Definition krylov_solver.hpp:92
for_each_cell and reductions: the parallelism SEAM over the cells of a Box2D; sync_host / sync_device...
GeometricMG: in-house geometric multigrid (V-cycle) for the elliptic operator, Gauss-Seidel smoother ...
Geometry: index-space (Box2D) <-> Cartesian physical-space mapping; PolarGeometry: SIBLING for a glob...
TensorKrylovSolver: MATRIX-FREE Krylov solver (BiCGStab) with MG preconditioning, for the FULL-TENSOR...
LorentzEliminator: 2x2 operator B of the Schur scheme for implicit velocity elimination.
MultiFab arithmetic (saxpy, lincomb, norm_inf, dot) over VALID cells.
MultiFab: a field DISTRIBUTED over a level (equivalent of AMReX's MultiFab).
void validate_krylov_params(Real tol, int max_iters, const char *who)
Validates the Krylov tolerance / iteration budget shared by the Schur source steppers (historical con...
Definition schur_source_kernels.hpp:103
Definition amr_hierarchy.hpp:29
void for_each_cell(const Box2D &b, F f)
Applies f to EACH cell (i, j) of box b (bounds inclusive), via Kokkos::parallel_for (Serial / OpenMP ...
Definition for_each.hpp:138
double Real
Definition types.hpp:30
int n_ranks()
Definition comm.hpp:139
void device_fence()
Device barrier: waits for in-flight kernels to finish before a HOST access to unified memory.
Definition kokkos_env.hpp:43
VariableRole
PHYSICAL role of a component.
Definition variables.hpp:27
BCType
Boundary condition type for a face: Periodic (handled by fill_boundary), Foextrap (zero gradient,...
Definition physical_bc.hpp:25
void fill_ghosts(MultiFab &mf, const Box2D &domain, const BCRec &bc)
COMPLETE ghost filling: fill_boundary (interior + periodic, periodicity deduced from bc) THEN fill_ph...
Definition physical_bc.hpp:227
PHYSICAL boundary conditions at the domain edge (BCType, BCRec, fill_physical_bc, fill_ghosts).
BUILDER (NOT solver) of the Schur-condensed source stage of the implicit source coupling potential / ...
Geometry-INDEPENDENT device kernels shared by the Schur SOURCE-STAGE steppers: the Cartesian condense...
WRITE POD handle (raw pointer + strides) over a Fab2D buffer, indexed by (i, j, c) IN GLOBAL INDICES ...
Definition fab2d.hpp:29
Boundary conditions for the FOUR faces of the domain (type + associated Dirichlet value).
Definition physical_bc.hpp:29
READ-only handle (const counterpart of Array4): same layout and same contract (POD device-copyable,...
Definition fab2d.hpp:44
Cartesian geometry of a level: index domain + physical bounds [xlo, xhi] x [ylo, yhi].
Definition geometry.hpp:20
POPS_HD Real dy() const
Grid spacing in y (= (yhi - ylo) / domain.ny()). POPS_HD.
Definition geometry.hpp:33
POPS_HD Real dx() const
Grid spacing in x (= (xhi - xlo) / domain.nx()). POPS_HD.
Definition geometry.hpp:31
Box2D domain
Definition geometry.hpp:21
Outcome of a Krylov solve: iterations performed, final relative residual, convergence flag.
Definition krylov_result.hpp:16
LorentzEliminator: operator B = [[1,-w],[w,1]] and its analytic inverse.
Definition lorentz_eliminator.hpp:55
POPS_HD void apply_Binv(Real vx, Real vy, Real &vxp, Real &vyp) const
apply_Binv: applies B^{-1} = (1/det)*[[1,w],[-w,1]] to (vx, vy), writes (vxp, vyp)....
Definition lorentz_eliminator.hpp:73
A model's variable set: kind (cons/prim), names, size, canonical roles (optional, parallel to names; ...
Definition variables.hpp:58
Copies the B_z field (aux channel) into an internal scalar MultiFab (0 ghost is enough,...
Definition schur_source_kernels.hpp:93
Extracts the velocity v = (mx, my) / rho from the state (Density / MomentumX / MomentumY roles) into ...
Definition schur_source_kernels.hpp:79
Energy update: E^{n+1} = E^n + (1/2) rho^n (|v^{n+1}|^2 - |v^n|^2).
Definition schur_source_kernels.hpp:62
Linear extrapolation of a SCALAR field from the theta-stage to the full step: f^{n+1} = f^n + (1/thet...
Definition schur_source_kernels.hpp:32
Linear extrapolation of the VELOCITY (vx, vy) from the theta-stage to the full step,...
Definition schur_source_kernels.hpp:43
Reconstructs v^{n+theta} = B^{-1}(v^n - theta dt grad phi^{n+theta}) and writes mom = rho^n v^{n+thet...
Definition condensed_schur_source_stepper.hpp:85
ConstArray4 phi
phi^{n+theta} (ghosts filled: centered grad reads i+-1, j+-1)
Definition condensed_schur_source_stepper.hpp:86
int c_mx
Definition condensed_schur_source_stepper.hpp:93
ConstArray4 vy
v^n (component 0 of their MultiFab: velocity, NOT momentum)
Definition condensed_schur_source_stepper.hpp:87
Real half_idx
Definition condensed_schur_source_stepper.hpp:92
ConstArray4 vx
Definition condensed_schur_source_stepper.hpp:87
Array4 st
fluid state (WRITE mx, my; READ rho)
Definition condensed_schur_source_stepper.hpp:89
POPS_HD void operator()(int i, int j) const
Definition condensed_schur_source_stepper.hpp:94
int c_rho
Definition condensed_schur_source_stepper.hpp:93
Array4 nvy
output: v^{n+theta} (component 0) for the energy / the diagnostic
Definition condensed_schur_source_stepper.hpp:90
ConstArray4 bz
B_z field at the center.
Definition condensed_schur_source_stepper.hpp:88
Real half_idy
1/(2 dx), 1/(2 dy) (centered gradient)
Definition condensed_schur_source_stepper.hpp:92
Array4 nvx
Definition condensed_schur_source_stepper.hpp:90
Real th_dt
theta * dt (w = th_dt * B_z, and gradient factor)
Definition condensed_schur_source_stepper.hpp:91
int c_my
Density / MomentumX / MomentumY components.
Definition condensed_schur_source_stepper.hpp:93
Base scalar types and the POPS_HD macro (host+device portability).
#define POPS_HD
Definition types.hpp:25
Descriptor of a model's variables (Vars).