chombo-discharge
Loading...
Searching...
No Matches
CD_ItoSolverImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
14#ifndef CD_ITOSOLVERIMPLEM_H
15#define CD_ITOSOLVERIMPLEM_H
16
17// Std includes
18#include <chrono>
19
20// Chombo includes
21#include <EBAlias.H>
22#include <PolyGeom.H>
23
24// Our includes
25#include <CD_ItoSolver.H>
26#include <CD_DataOps.H>
27#include <CD_MirrorDeposition.H>
28#include <CD_ParallelOps.H>
29#include <CD_ParticleOps.H>
30#include <CD_Random.H>
31#include <CD_EBParticleMesh.H>
32#include <CD_NamespaceHeader.H>
33
34inline RealVect
36{
37 // TLDR: We draw a random number from a Gaussian distribution for each coordinate, and truncate the distribution at
38 // m_normalDistributionTruncation.
39
40 auto sign = [](const Real& a) -> Real {
41 return (a > 0) - (a < 0);
42 };
43
44 RealVect r = RealVect::Zero;
45 for (int i = 0; i < SpaceDim; i++) {
46 r[i] = Random::getNormal01();
47
48 r[i] = sign(r[i]) * std::min(std::abs(r[i]), m_normalDistributionTruncation);
49 }
50
51 return r;
52}
53
54template <typename P, typename Traits>
55void
56ItoSolver::depositWeight(EBAMRCellData& a_phi,
57 const ParticleContainer<P, Traits>& a_particles,
58 const DepositionType a_deposition,
59 const CoarseFineDeposition a_coarseFineDeposition) const
60{
61 CH_TIME("ItoSolver::depositWeight");
62 if (m_verbosity > 5) {
63 pout() << m_name + "::depositWeight" << endl;
64 }
65
66 CH_assert(a_phi[0]->nComp() == 1);
67 CH_assert(a_phi.getRealm() == m_realm);
68 CH_assert(a_particles.getRealm() == m_realm);
69 CH_assert(!a_particles.isOrganizedByCell());
70
71 // Deposit the weight column onto the mesh (resets a_phi internally), then redistribute. Redistribution belongs
72 // here because it decides what the valid-cell values are; coarsening and ghost filling are the caller's, see
73 // coarsenAndFillGhosts().
74 m_amr
75 ->depositWeight(a_phi, m_realm, m_phase, a_particles, a_deposition, a_coarseFineDeposition, m_irregularDeposition);
76
78 this->mirrorPass(a_phi,
79 a_particles,
80 a_deposition,
81 a_coarseFineDeposition,
82 [](const ParticleSoA<P, Traits>& a_leaf, const std::size_t a_index) -> Real {
83 return a_leaf.weight(a_index);
84 });
85 }
86
87 this->redistributeAMR(a_phi);
88}
89
90template <typename Gather>
91void
92ItoSolver::depositGatheredNGP(LevelData<EBCellFAB>& a_output,
93 const ParticleContainer<ItoParticle>& a_particles,
94 const int a_level,
95 Gather a_gather) const noexcept
96{
97 CH_TIME("ItoSolver::depositGatheredNGP");
98 if (m_verbosity > 5) {
99 pout() << m_name + "::depositGatheredNGP" << endl;
100 }
101
102 CH_assert(a_level >= 0);
103 CH_assert(a_level <= m_amr->getFinestLevel());
104
105 const ProblemDomain& domain = m_amr->getDomains()[a_level];
106 const DisjointBoxLayout& dbl = m_amr->getGrids(a_particles.getRealm())[a_level];
107 const DataIterator& dit = dbl.dataIterator();
108 const EBISLayout& ebisl = m_amr->getEBISLayout(a_particles.getRealm(), m_phase)[a_level];
109 const Real dx = m_amr->getDx()[a_level];
110 const RealVect probLo = m_amr->getProbLo();
111
112 CH_assert(a_output.disjointBoxLayout() == dbl);
113
114 const int nbox = dit.size();
115
116#pragma omp parallel for schedule(runtime)
117 for (int mybox = 0; mybox < nbox; mybox++) {
118 const DataIndex& din = dit[mybox];
119
120 const Box cellBox = dbl[din];
121 const EBISBox& ebisbox = ebisl[din];
122
123 EBParticleMesh particleMesh(domain, cellBox, ebisbox, dx * RealVect::Unit, probLo);
124
125 EBCellFAB& output = a_output[din];
126 const ParticleSoA<ItoParticle>& leaf = a_particles[a_level][din];
127
128 // The per-patch deposit INCREMENTS, so start from a clean slate.
129 output.setVal(0.0);
130
131 particleMesh
132 .depositGathered(output, 0, leaf, DepositionType::NGP, 1.0, true, [&](const std::size_t a_i, Real* a_out) {
133 a_out[0] = a_gather(leaf, a_i);
134 });
135 }
136}
137
138template <typename Gather>
139void
140ItoSolver::depositGathered(EBAMRCellData& a_phi,
141 const ParticleContainer<ItoParticle>& a_particles,
142 const DepositionType a_deposition,
143 const CoarseFineDeposition a_coarseFineDeposition,
144 Gather a_gather) const
145{
146 CH_TIME("ItoSolver::depositGathered");
147 if (m_verbosity > 5) {
148 pout() << m_name + "::depositGathered" << endl;
149 }
150
151 CH_assert(a_phi[0]->nComp() == 1);
152 CH_assert(!a_particles.isOrganizedByCell());
153
154 // Deposit the gathered quantity onto the mesh (resets a_phi internally), then redistribute. Coarsening and ghost
155 // filling are the caller's, see coarsenAndFillGhosts().
156 m_amr->depositGathered(a_phi,
157 m_realm,
158 m_phase,
159 a_particles,
160 a_deposition,
161 a_coarseFineDeposition,
163 a_gather);
164
166 this->mirrorPass(a_phi, a_particles, a_deposition, a_coarseFineDeposition, a_gather);
167 }
168
169 // A no-op unless the selector is one of the two redistributing ones, which Mirror is not -- the enum is what makes
170 // that combination unrepresentable rather than merely discouraged.
171 this->redistributeAMR(a_phi);
172}
173
174template <typename P, typename Traits, typename Strength>
175void
176ItoSolver::mirrorPass(EBAMRCellData& a_phi,
177 const ParticleContainer<P, Traits>& a_particles,
178 const DepositionType a_deposition,
179 const CoarseFineDeposition a_coarseFineDeposition,
180 const Strength a_strength) const
181{
182 CH_TIME("ItoSolver::mirrorPass");
183 if (m_verbosity > 5) {
184 pout() << m_name + "::mirrorPass" << endl;
185 }
186
187 const Vector<int>& hasCutCells = m_amr->getMirrorHasCutCells(m_realm, m_phase);
188
189 // The early-out has to be a GLOBAL decision, not a per-rank one: remap() below is collective, so a rank that
190 // skipped it while another entered it deadlocks rather than misbehaves. getMirrorHasCutCells is all-reduced at
191 // regrid precisely so this test is safe to make independently on every rank.
192 bool anyCutCells = false;
193 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
194 anyCutCells = anyCutCells || (hasCutCells[lvl] != 0);
195 }
196
197 if (!anyCutCells) {
198 return;
199 }
200
201 const EBAMRCellData& surfaceData = m_amr->getMirrorSurfaceData(m_realm, m_phase);
202 const RealVect probLo = m_amr->getProbLo();
203 const Real maxJacobian = m_amr->getMirrorMaxJacobian();
204 const Real minDenom = m_amr->getMirrorMinDenominator();
205
206 // Cleared at the START of the pass, so that a pass which aborts partway cannot leak its images into the next
207 // deposit.
209
210 // Diagnostics. Counted unconditionally because it is a handful of increments, but reduced and printed only when
211 // asked for: the reduction is a collective and this pass runs on every deposit of every species, so doing it
212 // always would put a global synchronization in the hot path. m_verbosity is parsed from the same input on every
213 // rank, so gating a collective on it is safe.
214 //
215 // These matter more than they look. A refused image still deposits, with J = 1, which is the FLAT mirror -- and a
216 // flat mirror on a curved surface is biased, not neutral. So a run whose images are nearly all refused has quietly
217 // degraded to a worse model rather than failed, and without these counters it looks identical to a healthy one.
218 long long numImages = 0;
219 long long numNoData = 0;
220 long long numInside = 0;
221 long long numSmallDenom = 0;
222 long long numLargeJac = 0;
223 long long numNonPositiveJ = 0;
224
225 for (int lvl = 0; lvl <= m_amr->getFinestLevel(); lvl++) {
226 const DisjointBoxLayout& dbl = m_amr->getGrids(m_realm)[lvl];
227 const DataIterator& dit = dbl.dataIterator();
228 const Real dx = m_amr->getDx()[lvl];
229
230 const int nbox = dit.size();
231
232 // Safe to thread over patches: each iteration reads its own surface FAB and particle leaf and appends only to
233 // m_mirrorImages[lvl][din], which is that patch's own container. The counters are reduced rather than shared.
234#pragma omp parallel for schedule(runtime) \
235 reduction(+ : numImages, numNoData, numInside, numSmallDenom, numLargeJac, numNonPositiveJ)
236 for (int mybox = 0; mybox < nbox; mybox++) {
237 const DataIndex& din = dit[mybox];
238
239 const EBCellFAB& surf = (*surfaceData[lvl])[din];
240 const BaseFab<Real>& surfReg = surf.getSingleValuedFAB();
241
242 const ParticleSoA<P, Traits>& leaf = a_particles[lvl][din];
243 ParticleSoA<NoPayload>& images = m_mirrorImages[lvl][din];
244
245 const std::size_t numParticles = leaf.size();
246
247 for (std::size_t i = 0; i < numParticles; i++) {
248 const RealVect pos = leaf.position(i);
249 const IntVect iv = ParticleOps::getParticleCellIndex(pos, probLo, dx);
250
251 // Outside the band the surface data was never written, and a particle there has nothing to reflect across.
252 if (surfReg(iv, MirrorDeposition::compStatus) < 0.5) {
253 numNoData++;
254
255 continue;
256 }
257
258 // reflect() takes a contiguous array. A BaseFab stores the component index LAST, so &surfReg(iv, 0) would
259 // walk into the next cell rather than the next component.
260 Real surfaceComponents[MirrorDeposition::numComp];
261 for (int c = 0; c < MirrorDeposition::numComp; c++) {
262 surfaceComponents[c] = surfReg(iv, c);
263 }
264
265 RealVect image;
266 Real jacobian = 1.0;
267 Real d = 0.0;
269
270 // A refused image is still deposited, with J = 1. Dropping it would remove the correction in exactly the
271 // cells where the correction is largest.
272 MirrorDeposition::reflect(pos, surfaceComponents, maxJacobian, minDenom, image, jacobian, d, refusal);
273
274 switch (refusal) {
276 numSmallDenom++;
277
278 break;
279 }
281 numLargeJac++;
282
283 break;
284 }
286 numNonPositiveJ++;
287
288 break;
289 }
291 break;
292 }
293 }
294
295 // A particle at or inside the surface has no image to add: the even extension is defined on the fluid side.
296 if (d <= 0.0) {
297 numInside++;
298
299 continue;
300 }
301
302 numImages++;
303
304 images.append(image, a_strength(leaf, i) * jacobian);
305 }
306 }
307 }
308
309 // The ORDINARY remap. A level-preserving one was evaluated and refuted in both directions -- pinning an image to
310 // the coarse level puts it outside the coarse-fine band where it would simply be discarded.
312
313 // Native, because the images are the cut-cell treatment; asking for another one here would apply it twice.
314 m_amr->depositWeight(m_mirrorScratch,
315 m_realm,
316 m_phase,
318 a_deposition,
319 a_coarseFineDeposition,
321
322 // No covered-cell reset. Every image is inside the solid by construction -- that is what reflection means -- and
323 // the density is only defined where kappa > 0. The deposit's contract is that the field is correct where it is
324 // read.
325 DataOps::incr(a_phi, m_mirrorScratch, 1.0);
326
327 if (m_verbosity > 5) {
328 numImages = ParallelOps::sum(numImages);
329 numNoData = ParallelOps::sum(numNoData);
330 numInside = ParallelOps::sum(numInside);
331 numSmallDenom = ParallelOps::sum(numSmallDenom);
332 numLargeJac = ParallelOps::sum(numLargeJac);
333 numNonPositiveJ = ParallelOps::sum(numNonPositiveJ);
334
335 pout() << m_name + "::mirrorPass - " << numImages << " images; skipped " << numNoData << " outside the band, "
336 << numInside << " at d <= 0; refused (J = 1, the FLAT mirror) " << numSmallDenom << " small denominator, "
337 << numLargeJac << " large Jacobian, " << numNonPositiveJ << " non-positive Jacobian" << endl;
338 }
339}
340
341#include <CD_NamespaceFooter.H>
342
343#endif
CoarseFineDeposition
Coarse-fine deposition types (see CD_EBAMRParticleMesh for how these are handled).
Definition CD_CoarseFineDeposition.H:28
Agglomeration of useful data operations.
DepositionType
Deposition types.
Definition CD_DepositionType.H:24
Single-patch ParticleSoA deposit/interpolate onto an embedded-boundary mesh.
@ Mirror
Even extension of the density about the embedded boundary: deposit each particle's cloud and the clou...
@ Native
Deposit as-is, with no cut-cell treatment at all.
Declaration of solver class for Ito diffusion.
Namespace containing the geometry used by mirrored cut-cell deposition.
Agglomeration of basic MPI reductions.
Declaration of a static class containing some common useful particle routines that would otherwise be...
File containing some useful static methods related to random number generation.
static void incr(MFAMRCellData &a_lhs, const MFAMRCellData &a_rhs, const Real a_scale) noexcept
Function which increments data in the form a_lhs = a_lhs + a_rhs*a_scale for all components.
Definition CD_DataOps.cpp:881
Deposits/interpolates ParticleSoA leaves on a single patch, with embedded-boundary (cut-cell) awarene...
Definition CD_EBParticleMesh.H:71
void depositGathered(EBCellFAB &a_meshData, const int a_comp, const ParticleSoA< P, Traits > &a_particles, const DepositionType a_depositionType, const Real a_widthScale, const bool a_forceIrregNGP, GatherFunc &&a_gather) const
Deposit a custom per-particle scalar (computed by a_gather) onto mesh component a_comp.
Definition CD_EBParticleMesh.H:263
ParticleContainer< NoPayload > m_mirrorImages
Reflected images of the band particles, for IrregularDeposition::Mirror.
Definition CD_ItoSolver.H:1623
EBAMRCellData m_mirrorScratch
Mesh scratch the mirror images deposit into, before being added to the real field.
Definition CD_ItoSolver.H:1634
void mirrorPass(EBAMRCellData &a_phi, const ParticleContainer< P, Traits > &a_particles, const DepositionType a_deposition, const CoarseFineDeposition a_coarseFineDeposition, const Strength a_strength) const
Add the mirrored contribution of the band particles to an already-deposited field.
Definition CD_ItoSolverImplem.H:176
std::string m_realm
Realm where this solve lives.
Definition CD_ItoSolver.H:1390
void depositWeight(EBAMRCellData &a_phi, const ParticleContainer< P, Traits > &a_particles, DepositionType a_deposition, CoarseFineDeposition a_coarseFineDeposition) const
Deposit the SoA weight column on the mesh (kappa-conservative + redistribution).
Definition CD_ItoSolverImplem.H:56
std::string m_name
Solver name.
Definition CD_ItoSolver.H:1415
IrregularDeposition m_irregularDeposition
How the cut cells are treated when depositing.
Definition CD_ItoSolver.H:1460
RealVect randomGaussian() const
Draw a random N-dimensional Gaussian number from a normal distribution with zero with and unit standa...
Definition CD_ItoSolverImplem.H:35
int m_verbosity
Verbosity level for this solver.
Definition CD_ItoSolver.H:1438
void depositGathered(EBAMRCellData &a_phi, const ParticleContainer< ItoParticle > &a_particles, DepositionType a_deposition, CoarseFineDeposition a_coarseFineDeposition, Gather a_gather) const
Deposit a gathered per-particle quantity on the mesh (kappa-conservative + redistribution).
Definition CD_ItoSolverImplem.H:140
Real m_normalDistributionTruncation
Truncation value for normal distribution.
Definition CD_ItoSolver.H:1428
RefCountedPtr< AmrMesh > m_amr
AMR; needed for grid stuff.
Definition CD_ItoSolver.H:1400
void depositGatheredNGP(LevelData< EBCellFAB > &a_output, const ParticleContainer< ItoParticle > &a_particles, int a_level, Gather a_gather) const noexcept
Do an NGP deposit of a gathered per-particle quantity on a specific grid level. Used for IO.
Definition CD_ItoSolverImplem.H:92
virtual void redistributeAMR(EBAMRCellData &a_phi) const
Redistribute mass in an AMR context.
Definition CD_ItoSolver.cpp:2240
phase::which_phase m_phase
Phase where this solver lives.
Definition CD_ItoSolver.H:1410
AMR-hierarchy container of computational particles, stored per patch in Struct-of-Arrays form.
Definition CD_ParticleContainer.H:123
void clearParticles()
Drop all valid particles on every level (keeps each leaf's arena capacity).
Definition CD_ParticleContainer.H:442
bool isOrganizedByCell() const
Whether the valid leaves are currently cell-sorted.
Definition CD_ParticleContainer.H:416
std::string getRealm() const
Realm label.
Definition CD_ParticleContainer.H:300
void remap()
Redistribute every valid particle to the patch/level/rank that owns its cell.
Definition CD_ParticleContainerImplem.H:494
static IntVect getParticleCellIndex(const RealVect &a_particlePosition, const RealVect &a_probLo, const Real &a_dx) noexcept
Get the cell index corresponding to the particle position.
Definition CD_ParticleOpsImplem.H:32
Arena-backed Struct-of-Arrays particle container for a single grid patch.
Definition CD_ParticleSoA.H:655
void append(const RealVect &a_position, const double a_weight)
Append one particle with a default-constructed payload.
Definition CD_ParticleSoA.H:962
RealVect position(const std::size_t a_index) const noexcept
Position of particle i as a RealVect (by value, assembled from the scalar columns).
Definition CD_ParticleSoA.H:1195
double & weight(const std::size_t a_index) noexcept
Weight of particle i.
Definition CD_ParticleSoA.H:1229
std::size_t size() const noexcept
Number of particles currently stored.
Definition CD_ParticleSoA.H:882
static Real getNormal01()
Get a number from a normal distribution centered on zero and variance 1.
Definition CD_RandomImplem.H:174
constexpr int compStatus
Component holding the band cell's status. See MirrorDeposition::Status.
Definition CD_MirrorDeposition.H:53
Refusal
Why reflect() refused to trust its Jacobian.
Definition CD_MirrorDeposition.H:108
@ NonPositiveJacobian
The Jacobian came out non-positive, i.e. the reflection is past the surface's centre of curvature.
@ SmallDenominator
The Jacobian's denominator came too close to zero.
@ LargeJacobian
The Jacobian exceeded the permitted magnitude.
@ None
No refusal; the Jacobian was accepted.
bool reflect(const RealVect &a_pos, const Real *a_surfaceData, const Real a_maxJacobian, const Real a_minDenominator, RealVect &a_image, Real &a_jacobian, Real &a_signedDistance, Refusal &a_refusal) noexcept
Reflect a position across the quadratic surface patch stored for its cell, and weight the image.
Definition CD_MirrorDepositionImplem.H:124
constexpr int numComp
Total number of components in the surface-data holder. Eight in 2-D, thirteen in 3-D.
Definition CD_MirrorDeposition.H:73
Real sum(const Real &a_value) noexcept
Compute the sum across all MPI ranks.
Definition CD_ParallelOpsImplem.H:354