ViennaLS
Loading...
Searching...
No Matches
lsOxidationDeformation.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <functional>
8#include <stdexcept>
9#include <string>
10#include <unordered_map>
11
12#include <omp.h>
13#include <vcTimer.hpp>
14
15namespace viennals {
16
19 double viscosity = 1.;
20 double bulkModulus = 1.;
21 double ambientPressure = 0.;
22 double pressureTolerance = 1e-8;
24 double shearModulus = 0.;
26 double stressTimeStep = 1.;
27 unsigned harmonicIterations = 500;
28 unsigned mechanicsIterations = 5;
29 unsigned pressureIterations = 10000;
30 unsigned stokesIterations = 200;
31 double mechanicsTolerance = 1e-8;
32 double stokesTolerance = 1e-8;
33 double tolerance = 1e-8;
34 double relaxation = 0.7; // SIMPLE velocity under-relaxation (0 < α ≤ 1)
36 0.5; // SIMPLE pressure under-relaxation (0 < β ≤ 1)
37 std::size_t maxGridPoints = 5000000;
38 int material = -1;
39};
40
58template <class T, int D>
59class OxidationDeformation final : public VelocityField<T>,
60 public OxidationSolverBase<T, D> {
61 using IndexType = viennahrle::Index<D>;
62 using ConstSparseIterator =
63 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
64
65private:
66 // bring base members into scope
83
84 enum class Boundary { NONE, REACTION, AMBIENT, MASK };
85
86 struct BoundaryIntersection {
87 Boundary boundary = Boundary::NONE;
88 T distance = 0.;
89 };
90
91 template <class ValueType> struct StencilPoint {
92 ValueType value{};
93 T distance = 1.;
94 };
95
96 struct Node {
97 IndexType index;
98 Vec3D<T> velocity{0., 0., 0.};
99 T pressure = 0.;
100 T strainTrace = 0.;
101 std::array<T, 9> strainRateTensor{};
102 std::array<T, 9> stressTensor{};
103 T vonMisesStress = 0.;
104 };
105
106 SmartPointer<Domain<T, D>> reactionInterface = nullptr;
107 SmartPointer<Domain<T, D>> ambientInterface = nullptr;
108 SmartPointer<Domain<T, D>> maskInterface = nullptr;
109 SmartPointer<OxidationDiffusion<T, D>> diffusionField = nullptr;
110 SmartPointer<VelocityField<T>> maskVelocityField = nullptr;
111 OxidationDeformationParameters deformationParameters;
112 OxidationParameters oxidationParameters;
113 int reactionSign = 1;
114 int ambientSign = -1;
115 int maskSign = 1;
116
117 IndexType requestedMinIndex{};
118 IndexType requestedMaxIndex{};
119 unsigned iterations = 0;
120 T residual = std::numeric_limits<T>::max();
121 // Last achieved iteration counts and residuals for pressure and Stokes
122 // solves.
123 unsigned lastPressureIters_ = 0;
124 T lastPressureResidual_ = 0.;
125 unsigned lastStokesIters_ = 0;
126 T lastStokesResidual_ = 0.;
127 T avgExpansionSpeed_ = 0.;
128 bool avgExpansionSpeedComputed = false;
129 bool solved = false;
130 bool nodesDirty_ = true;
131 std::array<T, D> maxVelocity_{};
132 bool useRequestedBounds = false;
133 std::unordered_map<IndexType, std::array<T, 9>, typename IndexType::hash>
134 deviatoricStressHistory;
135
136 // Warm-start storage: solutions from previous time step used as initial guess
137 std::vector<Vec3D<T>> previousVelocity_;
138 std::vector<T> previousPressure_;
139 bool hasPreviousSolution_ = false;
140
141 static bool isFiniteVec(const Vec3D<T> &value) {
142 for (unsigned i = 0; i < 3; ++i)
143 if (!std::isfinite(value[i]))
144 return false;
145 return true;
146 }
147
148 static bool isFiniteTensor(const std::array<T, 9> &value) {
149 for (const auto component : value)
150 if (!std::isfinite(component))
151 return false;
152 return true;
153 }
154
155 // Face-major flat BC arrays: index = fi * n + nodeId, fi in [0, 2*D).
156 std::vector<Boundary> faceBCTypes_;
157 std::vector<T> faceBCDists_;
158 std::vector<uint8_t>
159 touchesAmbient_; // 1 if node touches the ambient (free) surface
160
161 // GPU solver selection. Semantics match OxidationDiffusion.
162 GpuMode gpuMode_ = GpuMode::Cpu;
163 GpuPreconditioner gpuPreconditioner_ = GpuPreconditioner::Jacobi;
164 mutable std::string
165 lastLoggedBackend_; // suppresses repeated "using X" messages
166
167#ifdef VIENNALS_GPU_BICGSTAB
168 // Geometry-fixed (per buildNodes()) arrays for GPU pressure solve.
169 // Layout matches pressCoeff/pressNeighId in solvePressure() so the
170 // same spmvKernel can be reused without any CPU-side reformatting.
171 std::vector<double> pressCoeffGpu_; // face-major [2D * n]
172 std::vector<uint32_t> pressNeighId32_; // face-major [2D * n]
173 std::vector<double> actualDiagGpu_; // [n], effective GPU matrix diagonal
174 gpu::GpuBiCGSTABBuffers *gpuPressBufs_ = nullptr;
175
176 // Geometry-fixed arrays for GPU Stokes velocity solve. The diagonal is
177 // component-major because mixed MASK contact is Dirichlet in the normal
178 // component and Neumann/self-canceling in tangential components.
179 std::vector<double> stokesCoeffGpu_; // face-major [2D * n]
180 std::vector<uint32_t> stokesNeighId32_; // face-major [2D * n]
181 std::vector<double> stokesDiagGpu_; // component-major [D * n]
182 gpu::GpuBiCGSTABBuffers *gpuStokesBufs_ = nullptr;
183
184 // Harmonic velocity solver GPU arrays. The neighbor IDs are identical to
185 // Stokes (stokesNeighId32_ is reused); only the coefficients and diagonal
186 // differ (all interior coefficients = 1.0, diagonal = interior-face count).
187 std::vector<double>
188 harmonicCoeffGpu_; // face-major [2D * n], 1.0 for interior
189 std::vector<double>
190 harmonicDiagGpu_; // [n], = count of interior faces per node
191 gpu::GpuBiCGSTABBuffers *gpuHarmonicBufs_ = nullptr;
192#endif
193
194public:
195 std::vector<Node> nodes;
196
198
200 SmartPointer<Domain<T, D>> passedReactionInterface,
201 SmartPointer<Domain<T, D>> passedAmbientInterface,
202 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField,
203 OxidationParameters passedOxidationParameters,
204 OxidationDeformationParameters passedDeformationParameters = {})
205 : reactionInterface(passedReactionInterface),
206 ambientInterface(passedAmbientInterface),
207 diffusionField(passedDiffusionField),
208 deformationParameters(passedDeformationParameters),
209 oxidationParameters(passedOxidationParameters) {}
210
211 template <class... Args> static auto New(Args &&...args) {
212 return SmartPointer<OxidationDeformation>::New(std::forward<Args>(args)...);
213 }
214
216#ifdef VIENNALS_GPU_BICGSTAB
217 gpu::freeGpuBuffers(gpuPressBufs_);
218 gpu::freeGpuBuffers(gpuStokesBufs_);
219 gpu::freeGpuBuffers(gpuHarmonicBufs_);
220#endif
221 }
222
223 void setGpuMode(GpuMode mode) { gpuMode_ = mode; }
225 gpuPreconditioner_ = prec;
226 }
227
228 void setReactionInterface(SmartPointer<Domain<T, D>> passedInterface) {
229 reactionInterface = passedInterface;
230 nodesDirty_ = true;
231 solved = false;
232 }
233
234 void setAmbientInterface(SmartPointer<Domain<T, D>> passedInterface) {
235 ambientInterface = passedInterface;
236 nodesDirty_ = true;
237 solved = false;
238 }
239
240 void setMaskInterface(SmartPointer<Domain<T, D>> passedInterface,
241 int passedMaskSign = 1) {
242 maskInterface = passedInterface;
243 maskSign = (passedMaskSign < 0) ? -1 : 1;
244 nodesDirty_ = true;
245 solved = false;
246 }
247
249 maskInterface = nullptr;
250 nodesDirty_ = true;
251 solved = false;
252 }
253
254 void
255 setMaskVelocityField(SmartPointer<VelocityField<T>> passedVelocityField) {
256 maskVelocityField = passedVelocityField;
257 solved = false;
258 }
259
261 maskVelocityField = nullptr;
262 solved = false;
263 }
264
266 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField) {
267 diffusionField = passedDiffusionField;
268 solved = false;
269 }
270
272 oxidationParameters = passedParameters;
273 solved = false;
274 }
275
276 void
278 deformationParameters = passedParameters;
279 solved = false;
280 }
281
282 void setOxideSigns(int passedReactionSign, int passedAmbientSign) {
283 reactionSign = (passedReactionSign < 0) ? -1 : 1;
284 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
285 solved = false;
286 }
287
288 void setSolveBounds(const IndexType &passedMinIndex,
289 const IndexType &passedMaxIndex) {
290 requestedMinIndex = passedMinIndex;
291 requestedMaxIndex = passedMaxIndex;
292 useRequestedBounds = true;
293 nodesDirty_ = true;
294 solved = false;
295 }
296
298 useRequestedBounds = false;
299 nodesDirty_ = true;
300 solved = false;
301 }
302
304 nodesDirty_ = true;
305 solved = false;
306 }
307
308 void apply() {
309 if (reactionInterface == nullptr || ambientInterface == nullptr ||
310 diffusionField == nullptr) {
311 Logger::getInstance()
312 .addError("OxidationDeformation: Missing interface or "
313 "diffusion field.")
314 .print();
315 return;
316 }
317
318 if (nodesDirty_) {
319 if (!initialiseGrid())
320 return; // base class already logged the error
321 buildNodes();
322 nodesDirty_ = false;
323 if (nodes.empty())
324 Logger::getInstance()
325 .addWarning("OxidationDeformation: no oxide nodes found after "
326 "buildNodes(). Verify that the reaction and ambient "
327 "level sets enclose a non-empty oxide band.")
328 .print();
329 hasPreviousSolution_ =
330 false; // Geometry changed, invalidate in-memory warm-start
331 // Try restoring velocity, pressure, and stress history from level set
332 // pointData (written by writeFieldsToLevelSet() before the previous
333 // advection and remapped+filled by lsAdvect + lsInterior).
334 seedFromLevelSet();
335 } else if (hasPreviousSolution_ &&
336 previousVelocity_.size() == nodes.size() &&
337 previousPressure_.size() == nodes.size()) {
338 // Warm-start: restore previous solution as initial guess for solver.
339 // Geometry stability check: only warm-start if node count matches
340 // (prevents using stale solutions after grid refinement/coarsening). This
341 // typically reduces solver iterations by 30-50% since the previous step's
342 // solution is close to the new one.
343 for (std::size_t i = 0; i < nodes.size(); ++i) {
344 nodes[i].velocity = previousVelocity_[i];
345 nodes[i].pressure = previousPressure_[i];
346 }
347 }
348
349 const std::size_t nn = nodes.size();
350 Timer<> tHarmonic, tMechanics;
351 tHarmonic.start();
353 tHarmonic.finish();
354 tMechanics.start();
356 tMechanics.finish();
357 Logger::getInstance()
358 .addTiming(" deformation n=" + std::to_string(nn) + " harmonic",
359 tHarmonic)
360 .addTiming(" deformation n=" + std::to_string(nn) +
361 " mechanics-total",
362 tMechanics)
363 .print();
364 avgExpansionSpeedComputed = false;
365
366 maxVelocity_.fill(T(0));
367 for (const auto &node : nodes) {
368 for (unsigned d = 0; d < D; ++d)
369 maxVelocity_[d] = std::max(maxVelocity_[d], std::abs(node.velocity[d]));
370 }
371 const auto unresolvedMax = estimateMaxUnresolvedAmbientVelocity();
372 for (unsigned d = 0; d < D; ++d)
373 maxVelocity_[d] = std::max(maxVelocity_[d], unresolvedMax[d]);
374
375 // Save current solution for warm-start on next apply()
376 previousVelocity_.resize(nodes.size());
377 previousPressure_.resize(nodes.size());
378 for (std::size_t i = 0; i < nodes.size(); ++i) {
379 previousVelocity_[i] = nodes[i].velocity;
380 previousPressure_[i] = nodes[i].pressure;
381 }
382 hasPreviousSolution_ = true;
383
384 solved = true;
385 }
386
387 Vec3D<T> getVectorVelocity(const Vec3D<T> &coordinate, int material,
388 const Vec3D<T> & /*normalVector*/,
389 unsigned long /*pointId*/) final {
390 if (!solved)
391 apply();
392
393 if (deformationParameters.material >= 0 &&
394 material != deformationParameters.material)
395 return {0., 0., 0.};
396
397 const auto velocity = getVelocity(coordinate);
398 T norm2 = 0.;
399 for (unsigned d = 0; d < D; ++d)
400 norm2 += velocity[d] * velocity[d];
401 if (norm2 > std::numeric_limits<T>::epsilon())
402 return velocity;
403
404 return unresolvedAmbientVelocity(coordinate);
405 }
406
407 T getScalarVelocity(const Vec3D<T> &coordinate, int material,
408 const Vec3D<T> &normalVector,
409 unsigned long /*pointId*/) final {
410 return 0.;
411 }
412
413 T getDissipationAlpha(int direction, int material,
414 const Vec3D<T> & /*centralDifferences*/) final {
415 if (deformationParameters.material >= 0 &&
416 material != deformationParameters.material)
417 return 0.;
418 return maxVelocity_[direction];
419 }
420
421private:
422 template <class ValueType, class NodeAccessor>
423 ValueType getField(const IndexType &index, ValueType fallback,
424 NodeAccessor accessor) const {
425 const std::size_t nodeId = lookupNode(index);
426 if (nodeId != noNode)
427 return accessor(nodes[nodeId]);
428
429 const auto nearby = findNearbyNode(index);
430 if (nearby == noNode)
431 return fallback;
432 return accessor(nodes[nearby]);
433 }
434
435 template <class ValueType, class NodeAccessor>
436 ValueType getField(const Vec3D<T> &coordinate, ValueType fallback,
437 NodeAccessor accessor) const {
438 // D-linear interpolation for Vec3D<T> (velocity) and scalar T (pressure).
439 // These are the types queried during level-set advection; interpolating at
440 // the exact interface coordinate eliminates the O(gridDelta) nearest-node
441 // error that caused the SiO2/mask interface to shift with grid delta.
442 // Other types (e.g. stress tensor std::array<T,9>) use nearest-node because
443 // they lack scalar multiply and are not used for advection.
444 if constexpr (!std::is_same_v<ValueType, Vec3D<T>> &&
445 !std::is_same_v<ValueType, T>) {
446 IndexType index;
447 for (unsigned i = 0; i < D; ++i)
448 index[i] = std::llround(coordinate[i] / gridDelta);
449 return getField(index, fallback, accessor);
450 } else {
451 using IdxScalar = std::decay_t<decltype(std::declval<IndexType>()[0])>;
452 IndexType lo;
453 T frac[D];
454 for (unsigned d = 0; d < D; ++d) {
455 const T c = coordinate[d] / gridDelta;
456 const T c_flo = std::floor(c);
457 lo[d] = static_cast<IdxScalar>(c_flo);
458 frac[d] = c - c_flo;
459 }
460
461 ValueType result{};
462 T totalWeight = T(0);
463 for (int corner = 0; corner < (1 << D); ++corner) {
464 IndexType idx = lo;
465 T w = T(1);
466 for (unsigned d = 0; d < D; ++d) {
467 if ((corner >> d) & 1) {
468 idx[d]++;
469 w *= frac[d];
470 } else {
471 w *= T(1) - frac[d];
472 }
473 }
474 if (w < T(1e-14))
475 continue;
476 const std::size_t nodeId = lookupNode(idx);
477 if (nodeId == noNode)
478 continue;
479 result = result + accessor(nodes[nodeId]) * w;
480 totalWeight += w;
481 }
482
483 if (totalWeight < T(1e-14))
484 return fallback;
485 if (totalWeight < T(1) - T(1e-6))
486 result = result * (T(1) / totalWeight);
487 return result;
488 }
489 }
490
491public:
492 Vec3D<T> getVelocity(const Vec3D<T> &coordinate) const {
493 return getField(coordinate, Vec3D<T>{0., 0., 0.},
494 [](const Node &n) { return n.velocity; });
495 }
496
497 Vec3D<T> getVelocity(const IndexType &index) const {
498 return getField(index, Vec3D<T>{0., 0., 0.},
499 [](const Node &n) { return n.velocity; });
500 }
501
502 T getPressure(const Vec3D<T> &coordinate) const {
503 return getField(coordinate, T(0), [](const Node &n) { return n.pressure; });
504 }
505
506 T getPressure(const IndexType &index) const {
507 return getField(index, T(0), [](const Node &n) { return n.pressure; });
508 }
509
510 T getStrainTrace(const Vec3D<T> &coordinate) const {
511 return getField(coordinate, T(0),
512 [](const Node &n) { return n.strainTrace; });
513 }
514
515 T getStrainTrace(const IndexType &index) const {
516 return getField(index, T(0), [](const Node &n) { return n.strainTrace; });
517 }
518
519 std::array<T, 9> getStrainRateTensor(const Vec3D<T> &coordinate) const {
520 return getField(coordinate, std::array<T, 9>{},
521 [](const Node &n) { return n.strainRateTensor; });
522 }
523
524 std::array<T, 9> getStrainRateTensor(const IndexType &index) const {
525 return getField(index, std::array<T, 9>{},
526 [](const Node &n) { return n.strainRateTensor; });
527 }
528
529 std::array<T, 9> getStressTensor(const Vec3D<T> &coordinate) const {
530 return getField(coordinate, std::array<T, 9>{},
531 [](const Node &n) { return n.stressTensor; });
532 }
533
534 std::array<T, 9> getStressTensor(const IndexType &index) const {
535 return getField(index, std::array<T, 9>{},
536 [](const Node &n) { return n.stressTensor; });
537 }
538
539 T getVonMisesStress(const Vec3D<T> &coordinate) const {
540 return getField(coordinate, T(0),
541 [](const Node &n) { return n.vonMisesStress; });
542 }
543
544 T getVonMisesStress(const IndexType &index) const {
545 return getField(index, T(0),
546 [](const Node &n) { return n.vonMisesStress; });
547 }
548
549 unsigned getIterations() const { return iterations; }
550 T getResidual() const { return residual; }
551 T getLastPressureResidual() const { return lastPressureResidual_; }
552 T getLastStokesResidual() const { return lastStokesResidual_; }
553 bool lastSolveConverged() const {
554 return std::isfinite(residual) &&
555 residual <= deformationParameters.mechanicsTolerance &&
556 std::isfinite(lastPressureResidual_) &&
557 lastPressureResidual_ <= deformationParameters.pressureTolerance &&
558 std::isfinite(lastStokesResidual_) &&
559 lastStokesResidual_ <= deformationParameters.stokesTolerance &&
561 }
562 bool hasFiniteSolution() const {
563 for (const auto &node : nodes) {
564 if (!isFiniteVec(node.velocity) || !std::isfinite(node.pressure) ||
565 !std::isfinite(node.strainTrace) ||
566 !isFiniteTensor(node.strainRateTensor) ||
567 !isFiniteTensor(node.stressTensor) ||
568 !std::isfinite(node.vonMisesStress))
569 return false;
570 }
571 return true;
572 }
573 std::size_t getNumberOfSolutionNodes() const { return nodes.size(); }
575 if (!avgExpansionSpeedComputed) {
577 avgExpansionSpeedComputed = true;
578 }
579 return avgExpansionSpeed_;
580 }
581 template <class Callback> void forEachSolutionNode(Callback callback) const {
582 for (const auto &node : nodes)
583 callback(node.index, node.pressure);
584 }
585
591 if (nodes.empty() || ambientInterface == nullptr)
592 return;
593
594 using VD = typename PointData<T>::VectorDataType;
595 VD velocity, stressR0, stressR1, stressR2;
596
597 ConstSparseIterator it(ambientInterface->getDomain());
598 for (; !it.isFinished(); ++it) {
599 if (!it.isDefined())
600 continue;
601 const IndexType idx = it.getStartIndices();
602 const std::size_t nId = lookupNode(idx);
603
604 if (nId != noNode) {
605 const auto &n = nodes[nId];
606 velocity.push_back(
607 isFiniteVec(n.velocity) ? n.velocity : Vec3D<T>{T(0), T(0), T(0)});
608 const auto sIt = deviatoricStressHistory.find(idx);
609 if (sIt != deviatoricStressHistory.end() &&
610 isFiniteTensor(sIt->second)) {
611 const auto &s = sIt->second;
612 stressR0.push_back({s[0], s[1], s[2]});
613 stressR1.push_back({s[3], s[4], s[5]});
614 stressR2.push_back({s[6], s[7], s[8]});
615 } else {
616 stressR0.push_back({T(0), T(0), T(0)});
617 stressR1.push_back({T(0), T(0), T(0)});
618 stressR2.push_back({T(0), T(0), T(0)});
619 }
620 } else {
621 velocity.push_back({T(0), T(0), T(0)});
622 stressR0.push_back({T(0), T(0), T(0)});
623 stressR1.push_back({T(0), T(0), T(0)});
624 stressR2.push_back({T(0), T(0), T(0)});
625 }
626 }
627
628 auto &pd = ambientInterface->getPointData();
629 pd.insertReplaceVectorData(std::move(velocity), "OxVelocity");
630 pd.insertReplaceVectorData(std::move(stressR0), "OxStressR0");
631 pd.insertReplaceVectorData(std::move(stressR1), "OxStressR1");
632 pd.insertReplaceVectorData(std::move(stressR2), "OxStressR2");
633 }
634
635private:
639 void seedFromLevelSet() {
640 if (ambientInterface == nullptr || nodes.empty())
641 return;
642
643 auto &pd = ambientInterface->getPointData();
644 const int vIdx = pd.getVectorDataIndex("OxVelocity");
645 const int r0Idx = pd.getVectorDataIndex("OxStressR0");
646 const int r1Idx = pd.getVectorDataIndex("OxStressR1");
647 const int r2Idx = pd.getVectorDataIndex("OxStressR2");
648 const int pIdx = pd.getScalarDataIndex("OxPressure");
649
650 const bool hasVelocity = (vIdx != -1);
651 const bool hasStress = (r0Idx != -1 && r1Idx != -1 && r2Idx != -1);
652 const bool hasPressure = (pIdx != -1);
653
654 if (!hasVelocity && !hasStress && !hasPressure)
655 return;
656
657 const auto *vd = hasVelocity ? pd.getVectorData(vIdx) : nullptr;
658 const auto *r0d = hasStress ? pd.getVectorData(r0Idx) : nullptr;
659 const auto *r1d = hasStress ? pd.getVectorData(r1Idx) : nullptr;
660 const auto *r2d = hasStress ? pd.getVectorData(r2Idx) : nullptr;
661 const auto *ppd = hasPressure ? pd.getScalarData(pIdx) : nullptr;
662
663 previousVelocity_.assign(nodes.size(), Vec3D<T>{});
664 previousPressure_.assign(nodes.size(), T(0));
665
666 ConstSparseIterator it(ambientInterface->getDomain());
667 for (; !it.isFinished(); ++it) {
668 if (!it.isDefined())
669 continue;
670 const auto ptId = it.getPointId();
671 const IndexType idx = it.getStartIndices();
672 const std::size_t ni = lookupNode(idx);
673
674 if (ni != noNode) {
675 if (vd && ptId < static_cast<decltype(ptId)>(vd->size()) &&
676 isFiniteVec((*vd)[ptId]))
677 previousVelocity_[ni] = (*vd)[ptId];
678 if (ppd && ptId < static_cast<decltype(ptId)>(ppd->size()) &&
679 std::isfinite((*ppd)[ptId]))
680 previousPressure_[ni] = (*ppd)[ptId];
681 }
682
683 if (hasStress && ptId < static_cast<decltype(ptId)>(r0d->size()) &&
684 ptId < static_cast<decltype(ptId)>(r1d->size()) &&
685 ptId < static_cast<decltype(ptId)>(r2d->size())) {
686 const auto &row0 = (*r0d)[ptId];
687 const auto &row1 = (*r1d)[ptId];
688 const auto &row2 = (*r2d)[ptId];
689 std::array<T, 9> s{row0[0], row0[1], row0[2], row1[0], row1[1],
690 row1[2], row2[0], row2[1], row2[2]};
691 if (isFiniteTensor(s))
692 deviatoricStressHistory[idx] = s;
693 }
694 }
695
696 if (hasVelocity || hasPressure) {
697 hasPreviousSolution_ = true;
698 // Apply the restored state immediately to the current nodes so
699 // the Stokes solve warm-starts on this (nodesDirty_=true) apply() call.
700 for (std::size_t i = 0; i < nodes.size(); ++i) {
701 nodes[i].velocity = previousVelocity_[i];
702 nodes[i].pressure = previousPressure_[i];
703 }
704 }
705 }
706
707#ifdef VIENNALS_GPU_BICGSTAB
708 // Builds the face-major pressure matrix geometry for GPU reuse.
709 // The logic mirrors the explicit precomputation in solvePressure() so the
710 // same actualDiagGpu_ / pressCoeffGpu_ arrays can feed both the CPU ILU(0)
711 // factorization and the GPU SpMV without recomputation.
712 void buildPressureGpuGeometry() {
713 const std::size_t n = nodes.size();
714 const T eps = std::numeric_limits<T>::epsilon();
715 pressCoeffGpu_.assign(2 * D * n, 0.0);
716 pressNeighId32_.assign(2 * D * n, gpu::kNoNode);
717 actualDiagGpu_.assign(n, 0.0);
718
719 for (std::size_t id = 0; id < n; ++id) {
720 if (touchesAmbient_[id]) {
721 actualDiagGpu_[id] = 1.0;
722 continue;
723 }
724 for (unsigned dir = 0; dir < D; ++dir) {
725 const unsigned fiNeg = dir * 2u;
726 const unsigned fiPos = dir * 2u + 1u;
727 IndexType nbNeg = nodes[id].index;
728 nbNeg[dir] -= 1;
729 IndexType nbPos = nodes[id].index;
730 nbPos[dir] += 1;
731 const std::size_t jNeg =
732 inBounds(nbNeg) ? nodeLookupFlat[linearIndex(nbNeg)] : noNode;
733 const std::size_t jPos =
734 inBounds(nbPos) ? nodeLookupFlat[linearIndex(nbPos)] : noNode;
735
736 auto effDist = [&](unsigned fi, std::size_t j) -> T {
737 if (j != noNode)
738 return gridDelta;
739 const Boundary bt = faceBCTypes_[fi * n + id];
740 return (bt != Boundary::NONE) ? faceBCDists_[fi * n + id] : gridDelta;
741 };
742
743 const T dNeg = effDist(fiNeg, jNeg);
744 const T dPos = effDist(fiPos, jPos);
745 const T dSum = dNeg + dPos;
746 if (dSum <= eps)
747 continue;
748
749 auto processFace = [&](unsigned fi, std::size_t j, T d) {
750 const T c = T(2) / (d * dSum);
751 if (j != noNode && !touchesAmbient_[j]) {
752 pressCoeffGpu_[fi * n + id] = static_cast<double>(c);
753 pressNeighId32_[fi * n + id] = static_cast<uint32_t>(j);
754 actualDiagGpu_[id] += c;
755 } else if (j != noNode ||
756 faceBCTypes_[fi * n + id] == Boundary::AMBIENT) {
757 // j is an ambient-only neighbour (identity-row Dirichlet p=0), OR
758 // this face crosses the free surface directly (AMBIENT Dirichlet
759 // p=0 at the sub-grid crossing distance). REACTION faces are
760 // solid-wall Neumann ∂p/∂n=0: no contribution.
761 actualDiagGpu_[id] += c;
762 }
763 };
764 processFace(fiNeg, jNeg, dNeg);
765 processFace(fiPos, jPos, dPos);
766 }
767 if (actualDiagGpu_[id] <= eps)
768 actualDiagGpu_[id] = 1.0;
769 }
770 }
771
772 // Builds the face-major Stokes velocity matrix geometry for GPU reuse.
773 // The effective diagonal excludes OOB, AMBIENT, and traction-coupled MASK
774 // face self-coupling because those cancel exactly with the vBC correction in
775 // the Stokes matvec. Kinematic MASK faces remain Dirichlet-like and
776 // contribute to the diagonal.
777 void buildStokesGpuGeometry() {
778 const std::size_t n = nodes.size();
779 const T eps = std::numeric_limits<T>::epsilon();
780 stokesCoeffGpu_.assign(2 * D * n, 0.0);
781 stokesNeighId32_.assign(2 * D * n, gpu::kNoNode);
782 stokesDiagGpu_.assign(D * n, 0.0);
783
784 auto addDiag = [&](std::size_t id, T c) {
785 for (unsigned comp = 0; comp < D; ++comp)
786 stokesDiagGpu_[comp * n + id] += static_cast<double>(c);
787 };
788
789 for (std::size_t id = 0; id < n; ++id) {
790 for (unsigned dir = 0; dir < D; ++dir) {
791 const unsigned fiNeg = dir * 2u;
792 const unsigned fiPos = dir * 2u + 1u;
793 IndexType nbNeg = nodes[id].index;
794 nbNeg[dir] -= 1;
795 IndexType nbPos = nodes[id].index;
796 nbPos[dir] += 1;
797 const bool negInBounds = inBounds(nbNeg);
798 const bool posInBounds = inBounds(nbPos);
799 const std::size_t jNeg =
800 negInBounds ? nodeLookupFlat[linearIndex(nbNeg)] : noNode;
801 const std::size_t jPos =
802 posInBounds ? nodeLookupFlat[linearIndex(nbPos)] : noNode;
803
804 // Distance matching velocityStencilPoint: gridDelta for OOB/interior,
805 // faceBCDists_ for boundary-crossing faces.
806 auto stokesFaceDist = [&](unsigned fi, bool inb, std::size_t j) -> T {
807 if (!inb || j != noNode)
808 return gridDelta;
809 const Boundary bt = faceBCTypes_[fi * n + id];
810 return (bt != Boundary::NONE) ? faceBCDists_[fi * n + id] : gridDelta;
811 };
812
813 const T dNeg = stokesFaceDist(fiNeg, negInBounds, jNeg);
814 const T dPos = stokesFaceDist(fiPos, posInBounds, jPos);
815 const T dSum = dNeg + dPos;
816 if (dSum <= eps)
817 continue;
818
819 auto processFace = [&](unsigned fi, bool inb, std::size_t j, T d) {
820 const T c = T(2) / (d * dSum);
821 if (inb && j != noNode) {
822 // Interior neighbor: off-diagonal and diagonal contribution.
823 stokesCoeffGpu_[fi * n + id] = static_cast<double>(c);
824 stokesNeighId32_[fi * n + id] = static_cast<uint32_t>(j);
825 addDiag(id, c);
826 } else if (inb) {
827 // Boundary-crossing face.
828 // REACTION / MASK: Dirichlet → adds to diagonal.
829 // AMBIENT / OOB: excluded (self-coupling cancels with vBC
830 // correction).
831 const Boundary bt = faceBCTypes_[fi * n + id];
832 if (bt == Boundary::REACTION || bt == Boundary::MASK)
833 addDiag(id, c);
834 }
835 // !inb (OOB): self-coupling, excluded.
836 };
837 processFace(fiNeg, negInBounds, jNeg, dNeg);
838 processFace(fiPos, posInBounds, jPos, dPos);
839 }
840 for (unsigned comp = 0; comp < D; ++comp)
841 if (stokesDiagGpu_[comp * n + id] <= static_cast<double>(eps))
842 stokesDiagGpu_[comp * n + id] = 1.0;
843 }
844 }
845
846 // Builds face-major harmonic velocity geometry for GPU reuse.
847 // The neighbor IDs are identical to Stokes (stokesNeighId32_ is shared).
848 //
849 // The CPU harmonicMatvec is Av[i][c] = 2*D*v[i] - stencil_sum(v)[i] + b[i]
850 // where stencil_sum includes self-coupling (v[nodeId]) for OOB/AMBIENT/NONE
851 // faces and constants for REACTION/MASK faces. After the b/constant terms
852 // cancel, the effective matrix diagonal is:
853 // 2*D - n_OOB - n_AMBIENT - n_NONE = n_interior + n_REACTION + n_MASK
854 //
855 // REACTION and MASK faces have no off-diagonal entry (their velocity is a
856 // constant in b) but they DO count toward the diagonal. Excluding them
857 // gives a matrix that is not diagonally dominant near the Si/SiO2 boundary,
858 // which causes Jacobi-preconditioned BiCGSTAB to diverge.
859 void buildHarmonicGpuGeometry() {
860 const std::size_t n = nodes.size();
861 harmonicCoeffGpu_.assign(2 * D * n, 0.0);
862 harmonicDiagGpu_.assign(n, 0.0);
863 for (std::size_t id = 0; id < n; ++id) {
864 for (unsigned fi = 0; fi < 2 * D; ++fi) {
865 if (stokesNeighId32_[fi * n + id] != gpu::kNoNode) {
866 // Interior neighbor: off-diagonal coefficient 1.0 + diagonal 1.0.
867 harmonicCoeffGpu_[fi * n + id] = 1.0;
868 harmonicDiagGpu_[id] += 1.0;
869 } else {
870 // Non-interior face. REACTION/MASK contribute a Dirichlet constant
871 // to b but NOT self-coupling, so they add 1.0 to the diagonal just
872 // like an interior neighbor would (matching the CPU matrix row).
873 // OOB/AMBIENT/NONE contribute v[nodeId] (self-coupling), which
874 // cancels with the 2*D diagonal term and must be excluded here.
875 const Boundary bt = faceBCTypes_[fi * n + id];
876 if (bt == Boundary::REACTION || bt == Boundary::MASK)
877 harmonicDiagGpu_[id] += 1.0;
878 }
879 }
880 if (harmonicDiagGpu_[id] < 1e-10)
881 harmonicDiagGpu_[id] = 1.0;
882 }
883 }
884
885 // Allocates GPU buffers for pressure and Stokes solves and uploads the
886 // geometry-fixed CSR pattern. Called from buildNodes() after the face BC
887 // arrays and the two geometry helper arrays are populated.
888 void setupDeformationGpuBuffers() {
889 const std::size_t n = nodes.size();
890 if (n == 0) {
891 gpu::freeGpuBuffers(gpuPressBufs_);
892 gpuPressBufs_ = nullptr;
893 gpu::freeGpuBuffers(gpuStokesBufs_);
894 gpuStokesBufs_ = nullptr;
895 return;
896 }
897 const bool tryGpu = (gpuMode_ == GpuMode::Gpu || gpuMode_ == GpuMode::Auto);
898 const bool useIlu0 = (gpuPreconditioner_ == GpuPreconditioner::ILU0);
899
900 // Each setup step is chained with else-if: once one fails the handle is
901 // released and set to null, and every later step must be skipped rather
902 // than called with a null handle. (In GpuMode::Gpu reportGpuUnavailable
903 // throws, but in GpuMode::Auto it returns and execution continues here.)
904 gpu::freeGpuBuffers(gpuPressBufs_);
905 gpuPressBufs_ = nullptr;
906 if (tryGpu) {
907 gpuPressBufs_ =
908 gpu::allocGpuBuffers(static_cast<uint32_t>(n), 2 * D, useIlu0);
909 if (!gpuPressBufs_) {
910 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
911 "pressure solver CUDA buffers could not be "
912 "allocated or the CUDA context could not be "
913 "initialized.");
914 } else if (!gpu::gpuUploadNeighborIds(
915 gpuPressBufs_, pressNeighId32_.data(), 2u * D * n)) {
916 gpu::freeGpuBuffers(gpuPressBufs_);
917 gpuPressBufs_ = nullptr;
918 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
919 "uploading pressure GPU neighbor IDs failed.");
920 } else if (useIlu0 &&
921 !gpu::gpuSetupCSR(gpuPressBufs_, pressNeighId32_.data(),
922 static_cast<uint32_t>(n), 2 * D)) {
923 gpu::freeGpuBuffers(gpuPressBufs_);
924 gpuPressBufs_ = nullptr;
925 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
926 "CUSPARSE setup for the pressure GPU BiCGSTAB "
927 "solver failed.");
928 }
929 }
930
931 gpu::freeGpuBuffers(gpuStokesBufs_);
932 gpuStokesBufs_ = nullptr;
933 if (tryGpu) {
934 gpuStokesBufs_ =
935 gpu::allocGpuBuffers(static_cast<uint32_t>(n), 2 * D, useIlu0);
936 if (!gpuStokesBufs_) {
937 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
938 "Stokes solver CUDA buffers could not be "
939 "allocated or the CUDA context could not be "
940 "initialized.");
941 } else if (!gpu::gpuUploadNeighborIds(
942 gpuStokesBufs_, stokesNeighId32_.data(), 2u * D * n)) {
943 gpu::freeGpuBuffers(gpuStokesBufs_);
944 gpuStokesBufs_ = nullptr;
945 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
946 "uploading Stokes GPU neighbor IDs failed.");
947 } else if (useIlu0 &&
948 !gpu::gpuSetupCSR(gpuStokesBufs_, stokesNeighId32_.data(),
949 static_cast<uint32_t>(n), 2 * D)) {
950 gpu::freeGpuBuffers(gpuStokesBufs_);
951 gpuStokesBufs_ = nullptr;
952 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
953 "CUSPARSE setup for the Stokes GPU BiCGSTAB "
954 "solver failed.");
955 }
956 }
957
958 // Harmonic velocity: same neighbor IDs as Stokes, different coefficients.
959 gpu::freeGpuBuffers(gpuHarmonicBufs_);
960 gpuHarmonicBufs_ = nullptr;
961 if (tryGpu) {
962 gpuHarmonicBufs_ =
963 gpu::allocGpuBuffers(static_cast<uint32_t>(n), 2 * D, useIlu0);
964 if (!gpuHarmonicBufs_) {
965 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
966 "harmonic solver CUDA buffers could not be "
967 "allocated.");
968 } else if (!gpu::gpuUploadNeighborIds(
969 gpuHarmonicBufs_, stokesNeighId32_.data(), 2u * D * n)) {
970 gpu::freeGpuBuffers(gpuHarmonicBufs_);
971 gpuHarmonicBufs_ = nullptr;
972 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
973 "uploading harmonic GPU neighbor IDs failed.");
974 } else if (useIlu0 &&
975 !gpu::gpuSetupCSR(gpuHarmonicBufs_, stokesNeighId32_.data(),
976 static_cast<uint32_t>(n), 2 * D)) {
977 gpu::freeGpuBuffers(gpuHarmonicBufs_);
978 gpuHarmonicBufs_ = nullptr;
979 reportGpuUnavailable("OxidationDeformation: GPU mode was selected, but "
980 "CUSPARSE setup for the harmonic GPU BiCGSTAB "
981 "solver failed.");
982 }
983 }
984
985 // Only claim the GPU backend when every solver actually has usable
986 // buffers; under GpuMode::Auto any of them may have degraded to the CPU.
987 if (tryGpu && gpuPressBufs_ && gpuStokesBufs_ && gpuHarmonicBufs_)
988 logDeformationBackend("GPU BiCGSTAB",
989 "pressure/Stokes/harmonic, preconditioner=" +
990 std::string(useIlu0 ? "ILU0" : "Jacobi"));
991 }
992#endif // VIENNALS_GPU_BICGSTAB
993
994#ifdef VIENNALS_GPU_BICGSTAB
995 static std::string gpuErrorDetail() {
996 const char *detail = gpu::gpuGetLastErrorMessage();
997 if (detail && detail[0] != '\0')
998 return std::string(" Detail: ") + detail;
999 return {};
1000 }
1001
1006 void reportGpuUnavailable(const std::string &message) const {
1007 if (gpuMode_ == GpuMode::Auto) {
1008 VIENNACORE_LOG_WARNING(message + gpuErrorDetail() +
1009 " Falling back to the CPU solver.");
1010 } else {
1011 VIENNACORE_LOG_ERROR(message + gpuErrorDetail());
1012 }
1013 }
1014#endif
1015
1016 void logDeformationBackend(const std::string &backend,
1017 const std::string &detail) const {
1018 if (!Logger::hasInfo())
1019 return;
1020 const std::string msg = "OxidationDeformation: using " + backend +
1021 " for mechanics pressure/Stokes solves (nodes=" +
1022 std::to_string(nodes.size()) +
1023 (detail.empty() ? std::string() : ", " + detail) +
1024 ").";
1025 if (msg == lastLoggedBackend_)
1026 return;
1027 lastLoggedBackend_ = msg;
1028 Logger::getInstance().addInfo(msg).print();
1029 }
1030
1031public:
1034 reactionInterface, ambientInterface, maskInterface, useRequestedBounds,
1035 requestedMinIndex, requestedMaxIndex,
1036 deformationParameters.maxGridPoints, "OxidationDeformation");
1037 }
1038
1039 void buildNodes() {
1040 nodes.clear();
1042
1043 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1044 ConstSparseIterator ambientIt(ambientInterface->getDomain());
1045 auto maskIt = makeMaskIterator();
1046
1047 IndexType index = minIndex;
1048 while (true) {
1049 const T reactionPhi = valueAt(reactionIt, index);
1050 const T ambientPhi = valueAt(ambientIt, index);
1051 if (isInsideOxide(reactionPhi, ambientPhi) &&
1052 !isInsideMask(maskIt, index)) {
1053 const std::size_t id = nodes.size();
1054 nodeLookupFlat[linearIndex(index)] = id;
1055 nodes.push_back({index});
1056 }
1057
1058 if (!increment(index))
1059 break;
1060 }
1061
1062 // Precompute per-face boundary intersections into flat face-major arrays.
1063 const std::size_t n = nodes.size();
1064 faceBCTypes_.assign(2 * D * n, Boundary::NONE);
1065 faceBCDists_.assign(2 * D * n, T(1));
1066 touchesAmbient_.assign(n, uint8_t(0));
1067 for (std::size_t id = 0; id < n; ++id) {
1068 const auto &node = nodes[id];
1069 bool touchesAmbient = false;
1070 bool touchesSolidBoundary = false;
1071 for (unsigned dir = 0; dir < D; ++dir) {
1072 for (int off : {-1, 1}) {
1073 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
1074 IndexType nb = node.index;
1075 nb[dir] += off;
1076 if (!inBounds(nb) || lookupNode(nb) != noNode)
1077 continue; // NONE/1.0 already set
1078 const auto bi = boundaryIntersection(reactionIt, ambientIt, maskIt,
1079 node.index, nb);
1080 faceBCTypes_[fi * n + id] = bi.boundary;
1081 faceBCDists_[fi * n + id] = bi.distance;
1082 if (bi.boundary == Boundary::AMBIENT)
1083 touchesAmbient = true;
1084 else if (bi.boundary == Boundary::MASK ||
1085 bi.boundary == Boundary::REACTION)
1086 touchesSolidBoundary = true;
1087 }
1088 }
1089 // Do not collapse mixed mask/reaction/ambient corner nodes to a single
1090 // ambient pressure identity row. Those nodes need their per-face
1091 // boundary conditions; otherwise the mask-edge triple point injects an
1092 // artificial free-surface pressure singularity.
1093 touchesAmbient_[id] =
1094 (touchesAmbient && !touchesSolidBoundary) ? uint8_t(1) : uint8_t(0);
1095 }
1096
1097#ifdef VIENNALS_GPU_BICGSTAB
1098 buildPressureGpuGeometry();
1099 buildStokesGpuGeometry();
1100 buildHarmonicGpuGeometry();
1101 setupDeformationGpuBuffers();
1102#else
1103 if (gpuMode_ == GpuMode::Gpu) {
1104 VIENNACORE_LOG_ERROR("OxidationDeformation: explicit GPU mode was "
1105 "requested, but ViennaLS was built without "
1106 "VIENNALS_GPU_BICGSTAB.");
1107 } else if (gpuMode_ == GpuMode::Auto) {
1108 VIENNACORE_LOG_WARNING("OxidationDeformation: GPU mode Auto was "
1109 "requested, but ViennaLS was built without "
1110 "VIENNALS_GPU_BICGSTAB. Using the CPU solver.");
1111 }
1112#endif
1113 }
1114
1115 // Evaluates the harmonic stencil at one node.
1116 // sum = sum of neighbor/BC contributions (interior neighbors, reaction/mask
1117 // BCs, and self-coupling for OOB/NONE/AMBIENT faces). count is always 2*D
1118 // (every face is counted regardless of type).
1119 template <class SolverT>
1120 void computeHarmonicStencilAt(std::size_t nodeId,
1121 const std::vector<Vec3D<SolverT>> &v,
1122 Vec3D<T> &sum) const {
1123 const auto &node = nodes[nodeId];
1124 sum = {T(0), T(0), T(0)};
1125
1126 const auto toT = [](const Vec3D<SolverT> &w) -> Vec3D<T> {
1127 return {static_cast<T>(w[0]), static_cast<T>(w[1]), static_cast<T>(w[2])};
1128 };
1129
1130 for (unsigned direction = 0; direction < D; ++direction) {
1131 for (int offset : {-1, 1}) {
1132 IndexType neighbor = node.index;
1133 neighbor[direction] += offset;
1134
1135 if (!inBounds(neighbor)) {
1136 sum = sum + toT(v[nodeId]); // zero-flux: ghost = self
1137 continue;
1138 }
1139
1140 const std::size_t neighborId = lookupNode(neighbor);
1141 if (neighborId != noNode) {
1142 sum = sum + toT(v[neighborId]);
1143 continue;
1144 }
1145
1146 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
1147 const Boundary boundary = faceBCTypes_[fi * nodes.size() + nodeId];
1148 if (boundary == Boundary::REACTION) {
1149 sum = sum + reactionBoundaryVelocity(node.index);
1150 } else if (boundary == Boundary::MASK) {
1151 sum = sum + maskVelocityBoundary(node.index, toT(v[nodeId]));
1152 } else {
1153 sum = sum + toT(v[nodeId]); // AMBIENT/NONE: zero-flux
1154 }
1155 }
1156 }
1157 }
1158
1159 // (Av)[i] = (2*D) * v[i] - sum_at_v[i] + b[i]
1160 template <class SolverT>
1161 void harmonicMatvec(const std::vector<Vec3D<SolverT>> &v,
1162 const std::vector<Vec3D<T>> &b,
1163 std::vector<Vec3D<SolverT>> &Av) const {
1164 const T diagVal = static_cast<T>(2 * D);
1165#pragma omp parallel for schedule(static)
1166 for (std::size_t i = 0; i < nodes.size(); ++i) {
1167 Vec3D<T> sum;
1168 computeHarmonicStencilAt(i, v, sum);
1169 for (unsigned c = 0; c < D; ++c)
1170 Av[i][c] = static_cast<SolverT>(diagVal * v[i][c] - sum[c] + b[i][c]);
1171 }
1172 }
1173
1175 iterations = 0;
1176 residual = 0.;
1177 if (nodes.empty())
1178 return;
1179
1180 using SolverT = T;
1181
1182 const std::size_t n = nodes.size();
1183 const T diagVal = static_cast<T>(2 * D); // constant for all nodes
1184
1185 // b[i] = BC constants (reaction + mask velocities), computed at v = zeros.
1186 // OOB/NONE/AMBIENT faces contribute v[i] = 0 at zeros, so only Dirichlet
1187 // BCs survive — correctly isolating the RHS constant vector.
1188 std::vector<Vec3D<T>> b(n);
1189 {
1190 const std::vector<Vec3D<SolverT>> zeros(
1191 n, Vec3D<SolverT>{SolverT(0), SolverT(0), SolverT(0)});
1192#pragma omp parallel for schedule(static)
1193 for (std::size_t i = 0; i < n; ++i)
1194 computeHarmonicStencilAt(i, zeros, b[i]);
1195 }
1196
1197 // Warm-start from previous substep's velocity field.
1198 std::vector<Vec3D<SolverT>> x(n);
1199 for (std::size_t i = 0; i < n; ++i)
1200 for (unsigned c = 0; c < D; ++c) {
1201 const T value = nodes[i].velocity[c];
1202 x[i][c] = static_cast<SolverT>(std::isfinite(value) ? value : T(0));
1203 }
1204
1205#ifdef VIENNALS_GPU_BICGSTAB
1206 if (gpu::gpuIsValid(gpuHarmonicBufs_)) {
1207 const std::size_t nf = 2u * D * n;
1208 if (harmonicDiagGpu_.size() != n || harmonicCoeffGpu_.size() != nf) {
1209 VIENNACORE_LOG_ERROR("OxidationDeformation: harmonic GPU geometry has "
1210 "the wrong size for the current node set.");
1211 }
1212
1213 Timer<> tUpload, tSolve;
1214 std::vector<Vec3D<SolverT>> xSolved(n);
1215 unsigned maxGpuIterations = 0;
1216 double maxGpuResidual = 0.0;
1217
1218 for (unsigned c = 0; c < D; ++c) {
1219 std::vector<double> bGpu(n), xGpu(n);
1220 for (std::size_t i = 0; i < n; ++i) {
1221 bGpu[i] = static_cast<double>(b[i][c]);
1222 xGpu[i] = static_cast<double>(x[i][c]);
1223 }
1224
1225 tUpload.start();
1226 const bool gpuUploadOk =
1227 (c == 0) ? gpu::gpuUploadSolverArrays(
1228 gpuHarmonicBufs_, harmonicDiagGpu_.data(),
1229 bGpu.data(), harmonicCoeffGpu_.data(),
1230 static_cast<uint32_t>(n), harmonicCoeffGpu_.size())
1231 : gpu::gpuUploadRhs(gpuHarmonicBufs_, bGpu.data(),
1232 static_cast<uint32_t>(n));
1233 tUpload.finish();
1234 if (!gpuUploadOk) {
1235 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, "
1236 "but uploading harmonic solver arrays failed." +
1237 gpuErrorDetail());
1238 }
1239
1240 unsigned gpuIterations = 0;
1241 double gpuResidual = 0.0;
1242 tSolve.start();
1243 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
1244 gpuHarmonicBufs_, xGpu.data(),
1245 static_cast<double>(std::numeric_limits<SolverT>::epsilon()),
1246 deformationParameters.harmonicIterations,
1247 static_cast<double>(deformationParameters.tolerance), gpuIterations,
1248 gpuResidual);
1249 tSolve.finish();
1250
1251 if (!gpuConverged || !std::isfinite(gpuResidual)) {
1252 VIENNACORE_LOG_ERROR(
1253 "OxidationDeformation: harmonic GPU BiCGSTAB failed or produced "
1254 "a non-finite residual for component " +
1255 std::to_string(c) + " (iters=" + std::to_string(gpuIterations) +
1256 ", residual=" + std::to_string(gpuResidual) + ").");
1257 }
1258
1259 maxGpuIterations = std::max(maxGpuIterations, gpuIterations);
1260 maxGpuResidual = std::max(maxGpuResidual, gpuResidual);
1261 for (std::size_t i = 0; i < n; ++i)
1262 xSolved[i][c] = static_cast<SolverT>(xGpu[i]);
1263 }
1264
1265 for (std::size_t i = 0; i < n; ++i)
1266 for (unsigned c = 0; c < D; ++c)
1267 nodes[i].velocity[c] = static_cast<T>(xSolved[i][c]);
1268 iterations = maxGpuIterations;
1269 residual = maxGpuResidual;
1270
1271 if (Logger::hasTiming()) {
1272 Logger::getInstance()
1273 .addTiming("harmonic n=" + std::to_string(n) +
1274 " iters=" + std::to_string(iterations) + " res=" +
1275 std::to_string(residual) + " [GPU] GPU BiCGSTAB",
1276 tSolve)
1277 .print();
1278 }
1279 if (Logger::hasDebug()) {
1280 Logger::getInstance()
1281 .addTiming("harmonic n=" + std::to_string(n) + " [GPU] GPU upload",
1282 tUpload)
1283 .print();
1284 }
1285 return;
1286 }
1287#endif
1288
1289 // r = b - A*x
1290 const Vec3D<SolverT> zero3{SolverT(0), SolverT(0), SolverT(0)};
1291 std::vector<Vec3D<SolverT>> Ax(n);
1292 harmonicMatvec(x, b, Ax);
1293 std::vector<Vec3D<SolverT>> r(n), r_hat(n);
1294 for (std::size_t i = 0; i < n; ++i)
1295 for (unsigned c = 0; c < D; ++c) {
1296 r[i][c] = static_cast<SolverT>(b[i][c] - Ax[i][c]);
1297 r_hat[i][c] = r[i][c];
1298 }
1299
1300 // BiCGSTAB with diagonal preconditioner (diag = 2*D, constant).
1301 std::vector<Vec3D<SolverT>> pv(n, zero3), sv(n, zero3), y(n), z(n), s(n),
1302 t(n);
1303 T rho = T(1), alpha = T(1), omega = T(1);
1304
1305 auto vecDot = [&](const std::vector<Vec3D<SolverT>> &a,
1306 const std::vector<Vec3D<SolverT>> &bv) {
1307 T sum = T(0);
1308 for (std::size_t i = 0; i < n; ++i)
1309 for (unsigned c = 0; c < D; ++c)
1310 sum += static_cast<T>(a[i][c]) * static_cast<T>(bv[i][c]);
1311 return sum;
1312 };
1313
1314 auto vecMaxAbs = [&](const std::vector<Vec3D<SolverT>> &vin) {
1315 T m = T(0);
1316 for (std::size_t i = 0; i < n; ++i)
1317 for (unsigned c = 0; c < D; ++c)
1318 m = std::max(m, std::abs(static_cast<T>(vin[i][c])));
1319 return m;
1320 };
1321
1322 const T b_norm = [&] {
1323 T m = T(0);
1324 for (std::size_t i = 0; i < n; ++i)
1325 for (unsigned c = 0; c < D; ++c)
1326 m = std::max(m, std::abs(b[i][c]));
1327 return (m < T(1e-100)) ? T(1) : m;
1328 }();
1329
1330 for (; iterations < deformationParameters.harmonicIterations;
1331 ++iterations) {
1332 const T rho_new = vecDot(r_hat, r);
1333 if (!std::isfinite(rho_new) || std::abs(rho_new) < T(1e-100))
1334 break;
1335 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
1336 !std::isfinite(omega) || std::abs(omega) < T(1e-100))
1337 break;
1338
1339 const T beta = (rho_new / rho) * (alpha / omega);
1340 if (!std::isfinite(beta))
1341 break;
1342 rho = rho_new;
1343
1344 for (std::size_t i = 0; i < n; ++i)
1345 for (unsigned c = 0; c < D; ++c)
1346 pv[i][c] = static_cast<SolverT>(r[i][c] +
1347 beta * (pv[i][c] - omega * sv[i][c]));
1348
1349 // y = M^{-1} p = p / (2*D)
1350 for (std::size_t i = 0; i < n; ++i)
1351 for (unsigned c = 0; c < D; ++c)
1352 y[i][c] = static_cast<SolverT>(static_cast<T>(pv[i][c]) / diagVal);
1353
1354 harmonicMatvec(y, b, sv);
1355
1356 const T r_hat_v = vecDot(r_hat, sv);
1357 if (!std::isfinite(r_hat_v) || std::abs(r_hat_v) < T(1e-100))
1358 break;
1359
1360 alpha = rho_new / r_hat_v;
1361 if (!std::isfinite(alpha))
1362 break;
1363
1364 for (std::size_t i = 0; i < n; ++i)
1365 for (unsigned c = 0; c < D; ++c)
1366 s[i][c] = static_cast<SolverT>(r[i][c] - alpha * sv[i][c]);
1367
1368 residual = vecMaxAbs(s);
1369 if (!std::isfinite(residual))
1370 break;
1371 if (residual < deformationParameters.tolerance * b_norm) {
1372 for (std::size_t i = 0; i < n; ++i)
1373 for (unsigned c = 0; c < D; ++c)
1374 x[i][c] = static_cast<SolverT>(x[i][c] + alpha * y[i][c]);
1375 ++iterations;
1376 break;
1377 }
1378
1379 // z = M^{-1} s
1380 for (std::size_t i = 0; i < n; ++i)
1381 for (unsigned c = 0; c < D; ++c)
1382 z[i][c] = static_cast<SolverT>(static_cast<T>(s[i][c]) / diagVal);
1383
1384 harmonicMatvec(z, b, t);
1385
1386 const T t_s = vecDot(t, s);
1387 const T t_t = vecDot(t, t);
1388 if (!std::isfinite(t_s) || !std::isfinite(t_t))
1389 break;
1390 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
1391 if (!std::isfinite(omega))
1392 break;
1393
1394 for (std::size_t i = 0; i < n; ++i)
1395 for (unsigned c = 0; c < D; ++c) {
1396 x[i][c] =
1397 static_cast<SolverT>(x[i][c] + alpha * y[i][c] + omega * z[i][c]);
1398 r[i][c] = static_cast<SolverT>(s[i][c] - omega * t[i][c]);
1399 }
1400
1401 residual = vecMaxAbs(r);
1402 if (!std::isfinite(residual))
1403 break;
1404 if (residual < deformationParameters.tolerance * b_norm) {
1405 ++iterations;
1406 break;
1407 }
1408 }
1409
1410 bool finiteSolution = true;
1411 for (std::size_t i = 0; i < n; ++i)
1412 for (unsigned c = 0; c < D; ++c)
1413 if (!std::isfinite(static_cast<T>(x[i][c])))
1414 finiteSolution = false;
1415
1416 if (finiteSolution) {
1417 for (std::size_t i = 0; i < n; ++i)
1418 for (unsigned c = 0; c < D; ++c)
1419 nodes[i].velocity[c] = static_cast<T>(x[i][c]);
1420 } else {
1421 residual = std::numeric_limits<T>::infinity();
1422 }
1423 if (residual > deformationParameters.tolerance * b_norm)
1424 VIENNACORE_LOG_WARNING(
1425 "solveVelocity (harmonic): BiCGSTAB did not converge after " +
1426 std::to_string(iterations) + "/" +
1427 std::to_string(deformationParameters.harmonicIterations) +
1428 " iterations (residual=" + std::to_string(residual / b_norm) +
1429 ", tolerance=" + std::to_string(deformationParameters.tolerance) +
1430 ")");
1431 }
1432
1433 // Returns component-wise diagonal entries of the Stokes operator A_v.
1434 // Geometry-fixed within a mechanics solve; computed once and reused by the
1435 // SIMPLE velocity-correction step: v_c^{k+1}=v_c* - grad_c(dp)/(eta*a_ic).
1436 //
1437 // With traction-coupled MASK contact, ghost=v_node+const for every component,
1438 // so the MASK face self-coupling cancels and the face coefficient is removed.
1439 std::vector<Vec3D<T>> computeVelocityDiagonals() const {
1440 const std::size_t n = nodes.size();
1441 std::vector<Vec3D<T>> diagV(n, Vec3D<T>{T(0), T(0), T(0)});
1442 if (n == 0)
1443 return diagV;
1444 const std::vector<Vec3D<T>> zeros(n, Vec3D<T>{T(0), T(0), T(0)});
1445 std::vector<Vec3D<T>> tmp(n);
1446#pragma omp parallel for schedule(static)
1447 for (std::size_t i = 0; i < n; ++i) {
1448 T diag{};
1449 computeVelocityStencilAt(i, zeros, diag, tmp[i]);
1450 for (unsigned comp = 0; comp < D; ++comp)
1451 diagV[i][comp] = diag;
1452 }
1453
1454 return diagV;
1455 }
1456
1458 T mechanicsResidual = 0.;
1459
1460 // SIMPLE (Semi-Implicit Method for Pressure-Linked Equations) coupling.
1461 // The Gauss-Seidel p→v→p loop has spectral radius > 1 on thin geometries,
1462 // causing divergence that worsens with more iterations. SIMPLE adds a
1463 // velocity-correction step after the pressure update that provably
1464 // eliminates the oscillation mode:
1465 //
1466 // 1. Momentum predictor: A_v * v* = vBC - (∇p^k - ∇·σ'(v^k)) / η
1467 // 2. Pressure update: A_p * p^{k+1} = pBC + K · div(v*)
1468 // 3. Velocity correction: v^{k+1} = v* - ∇δp / (η · a_i)
1469 // where δp = p^{k+1} - p^k, a_i = diag(A_v)[i]
1470 //
1471 // Step 3 ensures the corrected velocity is consistent with the new
1472 // pressure without re-solving the full momentum equation. Unlike the
1473 // Aitken clamp (which can only damp, not stabilise, ρ > 1 iterations),
1474 // this correction is unconditionally convergent for steady Stokes.
1475
1476 const std::vector<Vec3D<T>> diagV =
1477 computeVelocityDiagonals(); // geometry-fixed within this call
1478
1479 for (unsigned iteration = 0;
1480 iteration < deformationParameters.mechanicsIterations; ++iteration) {
1481 const auto previousVelocity = collectVelocities(); // v^k
1482 const auto previousPressure = collectPressures(); // p^k
1483
1486
1487 // Step 1: momentum predictor uses current p^k (in nodes[i].pressure).
1488 Timer<> tStokes, tPressure;
1489 tStokes.start();
1490 solveStokesVelocity(); // produces v* in nodes[i].velocity
1491 tStokes.finish();
1492 if (!std::isfinite(lastStokesResidual_)) {
1493 mechanicsResidual = std::numeric_limits<T>::infinity();
1494 break;
1495 }
1496
1497 // Step 2: pressure solve uses divergence of v*.
1498 tPressure.start();
1499 solvePressure(); // produces p^{k+1} in nodes[i].pressure
1500 tPressure.finish();
1501 if (!std::isfinite(lastPressureResidual_)) {
1502 mechanicsResidual = std::numeric_limits<T>::infinity();
1503 break;
1504 }
1505
1506 // Step 3: SIMPLE velocity correction: v^{k+1} = v* - ∇δp / (η · a_i).
1507 applySimpleVelocityCorrection(previousPressure, diagV);
1508
1509 mechanicsResidual = std::max(maxVelocityChange(previousVelocity),
1510 maxPressureChange(previousPressure));
1511 if (!std::isfinite(mechanicsResidual)) {
1512 mechanicsResidual = std::numeric_limits<T>::infinity();
1513 break;
1514 }
1515
1516 if (Logger::hasDebug())
1517 Logger::getInstance()
1518 .addTiming(
1519 " mechanics[" + std::to_string(iteration) +
1520 "] stokes iters=" + std::to_string(lastStokesIters_) +
1521 "/" +
1522 std::to_string(deformationParameters.stokesIterations) +
1523 " res=" + std::to_string(lastStokesResidual_),
1524 tStokes)
1525 .addTiming(
1526 " mechanics[" + std::to_string(iteration) +
1527 "] pressure iters=" + std::to_string(lastPressureIters_) +
1528 "/" +
1529 std::to_string(deformationParameters.pressureIterations) +
1530 " res=" + std::to_string(lastPressureResidual_) +
1531 " coupling=" + std::to_string(mechanicsResidual),
1532 tPressure)
1533 .print();
1534
1535 if (mechanicsResidual < deformationParameters.mechanicsTolerance)
1536 break;
1537 }
1538
1541 residual = mechanicsResidual;
1542 if (residual > deformationParameters.mechanicsTolerance)
1543 VIENNACORE_LOG_WARNING(
1544 "solveMechanics: did not converge after " +
1545 std::to_string(deformationParameters.mechanicsIterations) +
1546 " iterations (residual=" + std::to_string(residual) + ", tolerance=" +
1547 std::to_string(deformationParameters.mechanicsTolerance) + ")");
1548 }
1549
1550 // SIMPLE velocity correction: v^{k+1} = v* - ∇(p^{k+1} - p^k) / (η · a_i)
1551 //
1552 // δp gradient uses homogeneous Neumann at all boundary faces (δp ghost = 0).
1553 // The boundary pressure correction is re-enforced by the next pressure solve,
1554 // so this approximation only affects the current-iteration correction, not
1555 // the converged solution.
1556 void applySimpleVelocityCorrection(const std::vector<T> &pressureOld,
1557 const std::vector<Vec3D<T>> &diagV) {
1558 if (nodes.empty())
1559 return;
1560 if (deformationParameters.viscosity <= std::numeric_limits<T>::epsilon())
1561 return;
1562
1563 const std::size_t n = nodes.size();
1564 const T invEta = T(1) / deformationParameters.viscosity;
1565
1566#pragma omp parallel for schedule(static)
1567 for (std::size_t i = 0; i < n; ++i) {
1568 for (unsigned dir = 0; dir < D; ++dir) {
1569 const T ai = diagV[i][dir];
1570 if (ai <= std::numeric_limits<T>::epsilon())
1571 continue;
1572
1573 // Negative-offset face (fi = dir*2).
1574 T dpMinus, dMinus;
1575 {
1576 const unsigned fi = dir * 2u;
1577 IndexType nb = nodes[i].index;
1578 nb[dir] -= 1;
1579 if (inBounds(nb)) {
1580 const std::size_t j = nodeLookupFlat[linearIndex(nb)];
1581 if (j != noNode) {
1582 dpMinus = nodes[j].pressure - pressureOld[j];
1583 dMinus = gridDelta;
1584 } else {
1585 dpMinus = T(0);
1586 dMinus = faceBCDists_[fi * n + i];
1587 }
1588 } else {
1589 dpMinus = T(0);
1590 dMinus = gridDelta;
1591 }
1592 }
1593
1594 // Positive-offset face (fi = dir*2+1).
1595 T dpPlus, dPlus;
1596 {
1597 const unsigned fi = dir * 2u + 1u;
1598 IndexType nb = nodes[i].index;
1599 nb[dir] += 1;
1600 if (inBounds(nb)) {
1601 const std::size_t j = nodeLookupFlat[linearIndex(nb)];
1602 if (j != noNode) {
1603 dpPlus = nodes[j].pressure - pressureOld[j];
1604 dPlus = gridDelta;
1605 } else {
1606 dpPlus = T(0);
1607 dPlus = faceBCDists_[fi * n + i];
1608 }
1609 } else {
1610 dpPlus = T(0);
1611 dPlus = gridDelta;
1612 }
1613 }
1614
1615 const T dpCenter = nodes[i].pressure - pressureOld[i];
1616 const T gradDP =
1617 firstDerivative(dpMinus, dpCenter, dpPlus, dMinus, dPlus);
1618 const T correction =
1619 gradDP * invEta / ai * deformationParameters.relaxation;
1620 if (std::isfinite(correction) && std::isfinite(nodes[i].velocity[dir]))
1621 nodes[i].velocity[dir] -= correction;
1622 }
1623 }
1624 }
1625
1626 // Fills diag = centerCoefficient and rhs = pressureSum for one node.
1627 // Dirichlet (ambient) nodes are encoded as identity rows: diag=1,
1628 // rhs=ambientBP.
1629 template <class SolverT>
1630 void computePressureStencilAt(std::size_t nodeId,
1631 const std::vector<SolverT> &p,
1632 const std::vector<T> &ambientBP, T &diag,
1633 T &rhs) const {
1634 if (touchesAmbient_[nodeId]) {
1635 diag = T(1);
1636 rhs = ambientBP[nodeId];
1637 return;
1638 }
1639 diag = T(0);
1640 rhs = T(0);
1641 for (unsigned direction = 0; direction < D; ++direction) {
1642 const auto plus =
1643 pressureStencilPoint(p, ambientBP, nodeId, direction, 1);
1644 const auto minus =
1645 pressureStencilPoint(p, ambientBP, nodeId, direction, -1);
1646 const T dSum = plus.distance + minus.distance;
1647 const T plusCoeff = T(2) / (plus.distance * dSum);
1648 const T minusCoeff = T(2) / (minus.distance * dSum);
1649 rhs += plusCoeff * plus.value + minusCoeff * minus.value;
1650 diag += plusCoeff + minusCoeff;
1651 }
1652 }
1653
1654 // (Av)[i] = precomputedDiag[i]*v[i] - rhs_at_v[i] + pBC[i]
1655 template <class SolverT>
1656 void
1657 pressureMatvec(const std::vector<SolverT> &v, const std::vector<T> &ambientBP,
1658 const std::vector<T> &precomputedDiag,
1659 const std::vector<T> &pBC, std::vector<SolverT> &Av) const {
1660#pragma omp parallel for schedule(static)
1661 for (std::size_t i = 0; i < nodes.size(); ++i) {
1662 T diag, rhs;
1663 computePressureStencilAt(i, v, ambientBP, diag, rhs);
1664 Av[i] = static_cast<SolverT>(precomputedDiag[i] * v[i] - rhs + pBC[i]);
1665 }
1666 }
1667
1669 if (nodes.empty())
1670 return;
1671
1672 using SolverT = T;
1673
1674 const std::size_t n = nodes.size();
1675 const T eps = std::numeric_limits<T>::epsilon();
1676
1677 std::vector<T> divergence(n), ambientBP(n);
1678#pragma omp parallel for schedule(static)
1679 for (std::size_t i = 0; i < n; ++i) {
1680 divergence[i] = divergenceAt(nodes[i].index);
1681 ambientBP[i] = freeSurfacePressureBoundary(nodes[i].index);
1682 }
1683
1684 auto warnBadPressureAssembly = [](const std::string &stage,
1685 std::size_t nodeId, const IndexType &idx,
1686 T value) {
1687 VIENNACORE_LOG_WARNING(
1688 "solvePressure: non-finite/overflow " + stage +
1689 " at node=" + std::to_string(nodeId) + " index=(" +
1690 std::to_string(idx[0]) + "," + std::to_string(idx[1]) +
1691 (D == 3 ? "," + std::to_string(idx[2]) : std::string()) +
1692 ") value=" + std::to_string(value));
1693 };
1694
1695 const T solverMax = static_cast<T>(std::numeric_limits<SolverT>::max());
1696 for (std::size_t i = 0; i < n; ++i) {
1697 if (!std::isfinite(divergence[i]) ||
1698 std::abs(divergence[i]) > solverMax) {
1699 warnBadPressureAssembly("divergence", i, nodes[i].index, divergence[i]);
1700 break;
1701 }
1702 if (!std::isfinite(ambientBP[i]) || std::abs(ambientBP[i]) > solverMax) {
1703 warnBadPressureAssembly("ambient pressure boundary", i, nodes[i].index,
1704 ambientBP[i]);
1705 break;
1706 }
1707 }
1708
1709 // Geometry-fixed diagonal and BC constants (kept in T for full precision).
1710 std::vector<T> diag(n), pBC(n);
1711 {
1712 const std::vector<SolverT> zeros(n, SolverT(0));
1713#pragma omp parallel for schedule(static)
1714 for (std::size_t i = 0; i < n; ++i)
1715 computePressureStencilAt(i, zeros, ambientBP, diag[i], pBC[i]);
1716 }
1717
1718 for (std::size_t i = 0; i < n; ++i) {
1719 if (!std::isfinite(diag[i]) || std::abs(diag[i]) > solverMax) {
1720 warnBadPressureAssembly("pressure diagonal", i, nodes[i].index,
1721 diag[i]);
1722 break;
1723 }
1724 if (!std::isfinite(pBC[i]) || std::abs(pBC[i]) > solverMax) {
1725 warnBadPressureAssembly("pressure boundary rhs", i, nodes[i].index,
1726 pBC[i]);
1727 break;
1728 }
1729 }
1730
1731 std::vector<T> b(n);
1732 T b_norm = T(0);
1733 for (std::size_t i = 0; i < n; ++i) {
1734 b[i] = pBC[i] + deformationParameters.bulkModulus * divergence[i];
1735 b_norm = std::max(b_norm, std::abs(b[i]));
1736 }
1737 for (std::size_t i = 0; i < n; ++i) {
1738 if (!std::isfinite(b[i]) || std::abs(b[i]) > solverMax) {
1739 warnBadPressureAssembly("pressure rhs", i, nodes[i].index, b[i]);
1740 break;
1741 }
1742 }
1743 if (b_norm < T(1e-100))
1744 b_norm = T(1);
1745
1746 std::vector<SolverT> x(n);
1747 for (std::size_t i = 0; i < n; ++i) {
1748 T guess = touchesAmbient_[i] ? ambientBP[i] : nodes[i].pressure;
1749 if (!std::isfinite(guess))
1750 guess = deformationParameters.ambientPressure;
1751 x[i] = static_cast<SolverT>(guess);
1752 }
1753
1754#ifdef VIENNALS_GPU_BICGSTAB
1755 if (gpu::gpuIsValid(gpuPressBufs_)) {
1756 const std::size_t nf = 2u * D * n;
1757 if (actualDiagGpu_.size() != n || pressCoeffGpu_.size() != nf) {
1758 VIENNACORE_LOG_ERROR("OxidationDeformation: pressure GPU geometry has "
1759 "the wrong size for the current node set.");
1760 }
1761
1762 Timer<> tUpload, tSolve;
1763 std::vector<double> bGpu(n), xGpu(n);
1764 for (std::size_t i = 0; i < n; ++i) {
1765 bGpu[i] = static_cast<double>(b[i]);
1766 xGpu[i] = static_cast<double>(x[i]);
1767 }
1768
1769 tUpload.start();
1770 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
1771 gpuPressBufs_, actualDiagGpu_.data(), bGpu.data(),
1772 pressCoeffGpu_.data(), static_cast<uint32_t>(n),
1773 pressCoeffGpu_.size());
1774 tUpload.finish();
1775 if (!gpuUploadOk) {
1776 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, but "
1777 "uploading pressure solver arrays or factorizing "
1778 "ILU failed." +
1779 gpuErrorDetail());
1780 }
1781
1782 unsigned gpuIterations = 0;
1783 double gpuResidual = 0.0;
1784 tSolve.start();
1785 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
1786 gpuPressBufs_, xGpu.data(), static_cast<double>(eps),
1787 deformationParameters.pressureIterations,
1788 static_cast<double>(deformationParameters.pressureTolerance),
1789 gpuIterations, gpuResidual);
1790 tSolve.finish();
1791
1792 // gpuResidual is the GPU true residual ||b - A*x||_inf recomputed at
1793 // convergence (not the recursive BiCGSTAB residual), so no separate CPU
1794 // stencil evaluation is needed.
1795 if (!gpuConverged || !std::isfinite(gpuResidual)) {
1796 VIENNACORE_LOG_ERROR(
1797 "OxidationDeformation: pressure GPU BiCGSTAB failed or produced "
1798 "a non-finite residual (iters=" +
1799 std::to_string(gpuIterations) +
1800 ", residual=" + std::to_string(gpuResidual) + ").");
1801 }
1802
1803 {
1804 const T beta = deformationParameters.pressureRelaxation;
1805 const T oneMinB = T(1) - beta;
1806 for (std::size_t i = 0; i < n; ++i)
1807 nodes[i].pressure =
1808 oneMinB * nodes[i].pressure + beta * static_cast<T>(xGpu[i]);
1809 }
1810 lastPressureIters_ = gpuIterations;
1811 lastPressureResidual_ = gpuResidual / b_norm;
1812
1813 if (Logger::hasDebug()) {
1814 const std::string tag =
1815 "pressure n=" + std::to_string(n) +
1816 " iters=" + std::to_string(lastPressureIters_) +
1817 " res=" + std::to_string(lastPressureResidual_) + " [GPU]";
1818 Logger::getInstance()
1819 .addTiming(tag + " GPU upload", tUpload)
1820 .addTiming(tag + " GPU BiCGSTAB", tSolve)
1821 .print();
1822 }
1823 return;
1824 }
1825#endif
1826
1827 // Precompute off-diagonal structure and the CORRECT matrix diagonal for
1828 // SSOR.
1829 //
1830 // Key insight: diag[i] from computePressureStencilAt includes self-coupling
1831 // contributions from REACTION/MASK/OOB faces (those return v[nodeId]
1832 // itself). The ACTUAL matrix diagonal A[i,i] = sum of off-diagonal
1833 // (interior-neighbor) coefficients only. Using the wrong diagonal in the
1834 // SSOR sweeps makes the preconditioner invalid near boundaries.
1835 //
1836 // Also: NONE-type non-interior faces (OOB or no crossing) use gridDelta in
1837 // pressureStencilPoint, NOT faceBCDists_ (which defaults to T(1)).
1838 //
1839 // Face-major layout: fi = dir*2 + (offset==+1 ? 1 : 0)
1840 // Even fi (offset=-1): lower-index neighbor → forward sweep
1841 // Odd fi (offset=+1): higher-index neighbor → backward sweep
1842 std::vector<T> pressCoeff(2 * D * n, T(0));
1843 std::vector<std::size_t> pressNeighId(2 * D * n, noNode);
1844 std::vector<T> actualDiag(n, T(0)); // A[i,i] = sum of interior coefficients
1845
1846 for (std::size_t id = 0; id < n; ++id) {
1847 if (touchesAmbient_[id]) {
1848 actualDiag[id] = T(1);
1849 continue;
1850 } // identity row
1851 for (unsigned dir = 0; dir < D; ++dir) {
1852 const unsigned fiNeg = dir * 2u;
1853 const unsigned fiPos = dir * 2u + 1u;
1854 IndexType nbNeg = nodes[id].index;
1855 nbNeg[dir] -= 1;
1856 IndexType nbPos = nodes[id].index;
1857 nbPos[dir] += 1;
1858 const std::size_t jNeg =
1859 inBounds(nbNeg) ? nodeLookupFlat[linearIndex(nbNeg)] : noNode;
1860 const std::size_t jPos =
1861 inBounds(nbPos) ? nodeLookupFlat[linearIndex(nbPos)] : noNode;
1862
1863 // Effective distance matching pressureStencilPoint:
1864 // interior neighbour → gridDelta
1865 // AMBIENT/REACTION/MASK crossing → faceBCDists_ (actual sub-grid
1866 // distance) NONE (OOB or no crossing) → gridDelta
1867 // (pressureStencilPoint fallthrough)
1868 auto effectiveDist = [&](unsigned fi, std::size_t j) -> T {
1869 if (j != noNode)
1870 return gridDelta;
1871 const Boundary bt = faceBCTypes_[fi * n + id];
1872 if (bt != Boundary::NONE)
1873 return faceBCDists_[fi * n + id];
1874 return gridDelta;
1875 };
1876
1877 const T dNeg = effectiveDist(fiNeg, jNeg);
1878 const T dPos = effectiveDist(fiPos, jPos);
1879 const T dSum = dNeg + dPos;
1880 if (dSum <= eps)
1881 continue;
1882
1883 if (jNeg != noNode && !touchesAmbient_[jNeg]) {
1884 const T c = T(2) / (dNeg * dSum);
1885 pressCoeff[fiNeg * n + id] = c;
1886 pressNeighId[fiNeg * n + id] = jNeg;
1887 actualDiag[id] += c; // A[i,i] += interior off-diagonal coefficient
1888 } else if (jNeg != noNode ||
1889 faceBCTypes_[fiNeg * n + id] == Boundary::AMBIENT) {
1890 // j is an ambient-only neighbour (identity-row Dirichlet p=0), OR
1891 // this face directly crosses the free surface (AMBIENT Dirichlet p=0
1892 // at the sub-grid crossing distance). RHS contribution is c·0=0.
1893 // REACTION faces are solid-wall Neumann ∂p/∂n=0: no contribution.
1894 actualDiag[id] += T(2) / (dNeg * dSum);
1895 }
1896 if (jPos != noNode && !touchesAmbient_[jPos]) {
1897 const T c = T(2) / (dPos * dSum);
1898 pressCoeff[fiPos * n + id] = c;
1899 pressNeighId[fiPos * n + id] = jPos;
1900 actualDiag[id] += c;
1901 } else if (jPos != noNode ||
1902 faceBCTypes_[fiPos * n + id] == Boundary::AMBIENT) {
1903 actualDiag[id] += T(2) / (dPos * dSum);
1904 }
1905 }
1906 // Guard against fully-isolated nodes (surrounded by boundaries on every
1907 // face)
1908 if (actualDiag[id] <= eps)
1909 actualDiag[id] = T(1);
1910 }
1911
1912 // ILU(0) preconditioner for the (non-symmetric) pressure matrix.
1913 //
1914 // The sub-grid interface distances make A[i,j] ≠ A[j,i] in general, so
1915 // SSOR is not guaranteed to converge. ILU(0) handles non-symmetric
1916 // matrices robustly.
1917 //
1918 // Factorisation A ≈ L * U (zero fill-in, natural node ordering):
1919 // L – unit lower triangular: L[i,j] = A[i,j] / U[j,j] for j < i
1920 // U – upper triangular: U[i,j] = A[i,j] for j > i
1921 // U[i,i] = A[i,i] - Σ_{k<i} L[i,k] * A[k,i]
1922 //
1923 // With A[i,j] = -pressCoeff[fi*n+i] and A[j,i] = -pressCoeff[(fi^1)*n+j]:
1924 // U[i,i] = actualDiag[i] - Σ_{lower j} pressCoeff[fi_L*n+i]
1925 // * pressCoeff[fi_U*n+j]
1926 // / ilu_diag[j]
1927 //
1928 // Preconditioner application M_ILU^{-1} r = z:
1929 // Forward (L y = r, unit lower triangular, no diagonal divide):
1930 // y[i] = r[i] + Σ_{j<i} (pressCoeff[fi_L*n+i] / ilu_diag[j]) * y[j]
1931 // Backward (U z = y):
1932 // z[i] = (y[i] + Σ_{j>i} pressCoeff[fi_U*n+i] * z[j]) / ilu_diag[i]
1933 std::vector<T> ilu_diag(n);
1934 for (std::size_t id = 0; id < n; ++id) {
1935 if (touchesAmbient_[id]) {
1936 ilu_diag[id] = T(1);
1937 continue;
1938 }
1939 ilu_diag[id] = actualDiag[id];
1940 for (unsigned dir = 0; dir < D; ++dir) {
1941 const unsigned fi_L = dir * 2u; // lower face (offset=-1)
1942 const unsigned fi_U =
1943 fi_L + 1u; // upper face (offset=+1, j's face toward i)
1944 const std::size_t j = pressNeighId[fi_L * n + id];
1945 if (j == noNode || ilu_diag[j] <= eps)
1946 continue;
1947 // L[id,j] = A[id,j] / U[j,j] = (-pressCoeff_L) / ilu_diag[j]
1948 // A[j,id] = -pressCoeff[fi_U * n + j] (j's upper-face coefficient
1949 // toward id) ilu_diag[id] -= L[id,j] * A[j,id]
1950 // = (-pressCoeff_L / ilu_diag[j]) * (-pressCoeff_fi_U[j])
1951 // = pressCoeff_L * pressCoeff_fi_U[j] / ilu_diag[j]
1952 // (positive drop)
1953 ilu_diag[id] -=
1954 pressCoeff[fi_L * n + id] * pressCoeff[fi_U * n + j] / ilu_diag[j];
1955 }
1956 if (ilu_diag[id] <= eps)
1957 ilu_diag[id] = actualDiag[id]; // guard non-positive pivot
1958 }
1959
1960 auto applyIlu = [&](const std::vector<SolverT> &in,
1961 std::vector<SolverT> &out) {
1962 std::vector<T> y(n);
1963 // Forward solve: L * y = in (L is unit lower triangular)
1964 for (std::size_t i = 0; i < n; ++i) {
1965 T val = static_cast<T>(in[i]);
1966 for (unsigned dir = 0; dir < D; ++dir) {
1967 const unsigned fi_L = dir * 2u;
1968 const std::size_t j = pressNeighId[fi_L * n + i];
1969 if (j != noNode)
1970 // L[i,j] = -pressCoeff[fi_L*n+i] / ilu_diag[j], subtract
1971 // A[i,j]*y[j]: y[i] -= L[i,j] * y[j] = -(-pressCoeff/ilu_diag[j]) *
1972 // y[j] = +(coeff/ilu) * y[j]
1973 val += (pressCoeff[fi_L * n + i] / ilu_diag[j]) * y[j];
1974 }
1975 y[i] = val; // no diagonal divide (unit lower triangular)
1976 }
1977 // Backward solve: U * z = y
1978 for (std::size_t i = n; i-- > 0;) {
1979 T val = y[i];
1980 for (unsigned dir = 0; dir < D; ++dir) {
1981 const unsigned fi_U = dir * 2u + 1u;
1982 const std::size_t j = pressNeighId[fi_U * n + i];
1983 if (j != noNode)
1984 // U[i,j] = -pressCoeff[fi_U*n+i], subtract U[i,j]*z[j]:
1985 // val -= U[i,j] * z[j] = -(-pressCoeff) * z[j] = +(pressCoeff) *
1986 // z[j]
1987 val += pressCoeff[fi_U * n + i] * static_cast<T>(out[j]);
1988 }
1989 out[i] = static_cast<SolverT>(val / ilu_diag[i]);
1990 }
1991 };
1992
1993 std::vector<SolverT> Ax(n);
1994 pressureMatvec(x, ambientBP, diag, pBC, Ax);
1995 for (std::size_t i = 0; i < n; ++i) {
1996 if (!std::isfinite(static_cast<T>(Ax[i])) ||
1997 std::abs(static_cast<T>(Ax[i])) > solverMax) {
1998 warnBadPressureAssembly("initial pressure matvec", i, nodes[i].index,
1999 static_cast<T>(Ax[i]));
2000 break;
2001 }
2002 }
2003 std::vector<SolverT> r(n), r_hat(n), p(n, SolverT(0)), v(n, SolverT(0)),
2004 y(n), z(n), s(n), t(n);
2005 for (std::size_t i = 0; i < n; ++i) {
2006 r[i] = static_cast<SolverT>(b[i] - Ax[i]);
2007 r_hat[i] = r[i];
2008 }
2009
2010 T rho = T(1), alpha = T(1), omega = T(1);
2011 T pressureResidual = T(0);
2012 unsigned pressureIter = 0;
2013 bool pressureBreakdown = false;
2014 for (std::size_t i = 0; i < n; ++i) {
2015 const T ri = static_cast<T>(r[i]);
2016 if (!std::isfinite(ri)) {
2017 pressureBreakdown = true;
2018 break;
2019 }
2020 pressureResidual = std::max(pressureResidual, std::abs(ri));
2021 }
2022
2023 for (; !pressureBreakdown &&
2024 pressureIter < deformationParameters.pressureIterations;
2025 ++pressureIter) {
2026 T rho_new = T(0);
2027 for (std::size_t i = 0; i < n; ++i)
2028 rho_new += static_cast<T>(r_hat[i]) * static_cast<T>(r[i]);
2029
2030 if (!std::isfinite(rho_new)) {
2031 pressureBreakdown = true;
2032 break;
2033 }
2034 if (std::abs(rho_new) < T(1e-100))
2035 break;
2036 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
2037 !std::isfinite(omega) || std::abs(omega) < T(1e-100)) {
2038 pressureBreakdown = true;
2039 break;
2040 }
2041
2042 const T beta = (rho_new / rho) * (alpha / omega);
2043 if (!std::isfinite(beta)) {
2044 pressureBreakdown = true;
2045 break;
2046 }
2047 rho = rho_new;
2048
2049 for (std::size_t i = 0; i < n; ++i)
2050 p[i] = static_cast<SolverT>(r[i] + beta * (p[i] - omega * v[i]));
2051
2052 applyIlu(p, y);
2053
2054 pressureMatvec(y, ambientBP, diag, pBC, v);
2055
2056 T r_hat_v = T(0);
2057 for (std::size_t i = 0; i < n; ++i)
2058 r_hat_v += static_cast<T>(r_hat[i]) * static_cast<T>(v[i]);
2059 if (!std::isfinite(r_hat_v)) {
2060 pressureBreakdown = true;
2061 break;
2062 }
2063 if (std::abs(r_hat_v) < T(1e-100))
2064 break;
2065
2066 alpha = rho_new / r_hat_v;
2067 if (!std::isfinite(alpha)) {
2068 pressureBreakdown = true;
2069 break;
2070 }
2071
2072 for (std::size_t i = 0; i < n; ++i)
2073 s[i] = static_cast<SolverT>(r[i] - alpha * v[i]);
2074
2075 pressureResidual = T(0);
2076 for (std::size_t i = 0; i < n; ++i)
2077 pressureResidual =
2078 std::max(pressureResidual, std::abs(static_cast<T>(s[i])));
2079 if (!std::isfinite(pressureResidual)) {
2080 pressureBreakdown = true;
2081 break;
2082 }
2083 if (pressureResidual < deformationParameters.pressureTolerance * b_norm) {
2084 for (std::size_t i = 0; i < n; ++i)
2085 x[i] = static_cast<SolverT>(x[i] + alpha * y[i]);
2086 break;
2087 }
2088
2089 applyIlu(s, z);
2090
2091 pressureMatvec(z, ambientBP, diag, pBC, t);
2092
2093 T t_s = T(0), t_t = T(0);
2094 for (std::size_t i = 0; i < n; ++i) {
2095 t_s += static_cast<T>(t[i]) * static_cast<T>(s[i]);
2096 t_t += static_cast<T>(t[i]) * static_cast<T>(t[i]);
2097 }
2098 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
2099 pressureBreakdown = true;
2100 break;
2101 }
2102 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
2103 if (!std::isfinite(omega)) {
2104 pressureBreakdown = true;
2105 break;
2106 }
2107
2108 for (std::size_t i = 0; i < n; ++i) {
2109 x[i] = static_cast<SolverT>(x[i] + alpha * y[i] + omega * z[i]);
2110 r[i] = static_cast<SolverT>(s[i] - omega * t[i]);
2111 }
2112
2113 pressureResidual = T(0);
2114 for (std::size_t i = 0; i < n; ++i)
2115 pressureResidual =
2116 std::max(pressureResidual, std::abs(static_cast<T>(r[i])));
2117 if (!std::isfinite(pressureResidual)) {
2118 pressureBreakdown = true;
2119 break;
2120 }
2121 if (pressureResidual < deformationParameters.pressureTolerance * b_norm)
2122 break;
2123 }
2124
2125 if (pressureBreakdown)
2126 pressureResidual = std::numeric_limits<T>::infinity();
2127
2128 bool finiteSolution = !pressureBreakdown;
2129 for (std::size_t i = 0; i < n; ++i)
2130 if (!std::isfinite(static_cast<T>(x[i])))
2131 finiteSolution = false;
2132
2133 lastPressureIters_ = pressureIter;
2134 lastPressureResidual_ = pressureResidual / b_norm;
2135 if (finiteSolution) {
2136 const T beta = deformationParameters.pressureRelaxation;
2137 const T oneMinB = T(1) - beta;
2138 for (std::size_t i = 0; i < n; ++i)
2139 nodes[i].pressure =
2140 oneMinB * nodes[i].pressure + beta * static_cast<T>(x[i]);
2141 } else {
2142 lastPressureResidual_ = std::numeric_limits<T>::infinity();
2143 }
2144 if (lastPressureResidual_ > deformationParameters.pressureTolerance)
2145 VIENNACORE_LOG_WARNING(
2146 "solvePressure: BiCGSTAB did not converge after " +
2147 std::to_string(lastPressureIters_) + "/" +
2148 std::to_string(deformationParameters.pressureIterations) +
2149 " iterations (residual=" + std::to_string(lastPressureResidual_) +
2150 ", tolerance=" +
2151 std::to_string(deformationParameters.pressureTolerance) + ")");
2152 }
2153
2154 // Fills scalar diag = centerCoefficient and Vec3D rhs = velocitySum for one
2155 // node.
2156 template <class SolverT>
2157 void computeVelocityStencilAt(std::size_t nodeId,
2158 const std::vector<Vec3D<SolverT>> &v, T &diag,
2159 Vec3D<T> &rhs) const {
2160 diag = T(0);
2161 rhs = {T(0), T(0), T(0)};
2162 for (unsigned direction = 0; direction < D; ++direction) {
2163 const auto plus = velocityStencilPoint(v, nodeId, direction, 1);
2164 const auto minus = velocityStencilPoint(v, nodeId, direction, -1);
2165 const T dSum = plus.distance + minus.distance;
2166 const T plusCoeff = T(2) / (plus.distance * dSum);
2167 const T minusCoeff = T(2) / (minus.distance * dSum);
2168 rhs = rhs + plusCoeff * plus.value + minusCoeff * minus.value;
2169 diag += plusCoeff + minusCoeff;
2170 }
2171 }
2172
2174 if (deformationParameters.viscosity <= std::numeric_limits<T>::epsilon())
2175 return;
2176 if (nodes.empty())
2177 return;
2178
2179 using SolverT = T;
2180
2181 const std::size_t n = nodes.size();
2182 const T eps = std::numeric_limits<T>::epsilon();
2183
2184 // Geometry-fixed diagonal, BC constants, and forcing (all in T).
2185 std::vector<T> diag(n);
2186 std::vector<Vec3D<T>> vBC(n), forcing(n);
2187 {
2188 const std::vector<Vec3D<SolverT>> zeros(
2189 n, Vec3D<SolverT>{SolverT(0), SolverT(0), SolverT(0)});
2190#pragma omp parallel for schedule(static)
2191 for (std::size_t i = 0; i < n; ++i) {
2192 computeVelocityStencilAt(i, zeros, diag[i], vBC[i]);
2193 forcing[i] = momentumForcing(nodes[i].index);
2194 }
2195 }
2196 const auto precondDiag = computeVelocityDiagonals();
2197
2198 std::vector<Vec3D<T>> b(n);
2199 T b_norm = T(0);
2200 for (std::size_t i = 0; i < n; ++i) {
2201 for (unsigned c = 0; c < D; ++c) {
2202 b[i][c] = vBC[i][c] - forcing[i][c] / deformationParameters.viscosity;
2203 b_norm = std::max(b_norm, std::abs(b[i][c]));
2204 }
2205 }
2206 if (b_norm < T(1e-100))
2207 b_norm = T(1);
2208
2209 // Initial guess from current node velocities (warm-start), converted to
2210 // SolverT.
2211 std::vector<Vec3D<SolverT>> x(n);
2212 {
2213 const auto vel = collectVelocities();
2214 for (std::size_t i = 0; i < n; ++i)
2215 for (unsigned c = 0; c < D; ++c) {
2216 const T value = vel[i][c];
2217 x[i][c] = static_cast<SolverT>(std::isfinite(value) ? value : T(0));
2218 }
2219 }
2220
2221#ifdef VIENNALS_GPU_BICGSTAB
2222 if (gpu::gpuIsValid(gpuStokesBufs_)) {
2223 const std::size_t nf = 2u * D * n;
2224 if (stokesDiagGpu_.size() != D * n || stokesCoeffGpu_.size() != nf) {
2225 VIENNACORE_LOG_ERROR("OxidationDeformation: Stokes GPU geometry has "
2226 "the wrong size for the current node set.");
2227 }
2228
2229 Timer<> tUpload, tSolve;
2230 std::vector<Vec3D<SolverT>> xSolved(n);
2231 unsigned maxGpuIterations = 0;
2232 double maxGpuResidual = 0.0;
2233
2234 for (unsigned c = 0; c < D; ++c) {
2235 std::vector<double> bGpu(n), xGpu(n);
2236 for (std::size_t i = 0; i < n; ++i) {
2237 bGpu[i] = static_cast<double>(b[i][c]);
2238 xGpu[i] = static_cast<double>(x[i][c]);
2239 }
2240
2241 tUpload.start();
2242 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
2243 gpuStokesBufs_, stokesDiagGpu_.data() + c * n, bGpu.data(),
2244 stokesCoeffGpu_.data(), static_cast<uint32_t>(n),
2245 stokesCoeffGpu_.size());
2246 tUpload.finish();
2247 if (!gpuUploadOk) {
2248 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, "
2249 "but uploading Stokes solver arrays failed." +
2250 gpuErrorDetail());
2251 }
2252
2253 unsigned gpuIterations = 0;
2254 double gpuResidual = 0.0;
2255 tSolve.start();
2256 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
2257 gpuStokesBufs_, xGpu.data(), static_cast<double>(eps),
2258 deformationParameters.stokesIterations,
2259 static_cast<double>(deformationParameters.stokesTolerance),
2260 gpuIterations, gpuResidual);
2261 tSolve.finish();
2262
2263 if (!gpuConverged || !std::isfinite(gpuResidual)) {
2264 VIENNACORE_LOG_ERROR(
2265 "OxidationDeformation: Stokes GPU BiCGSTAB failed or produced "
2266 "a non-finite residual for component " +
2267 std::to_string(c) + " (iters=" + std::to_string(gpuIterations) +
2268 ", residual=" + std::to_string(gpuResidual) + ").");
2269 }
2270
2271 maxGpuIterations = std::max(maxGpuIterations, gpuIterations);
2272 maxGpuResidual = std::max(maxGpuResidual, gpuResidual);
2273 for (std::size_t i = 0; i < n; ++i)
2274 xSolved[i][c] = static_cast<SolverT>(xGpu[i]);
2275 }
2276
2277 for (std::size_t i = 0; i < n; ++i)
2278 for (unsigned c = 0; c < D; ++c)
2279 nodes[i].velocity[c] = static_cast<T>(xSolved[i][c]);
2280
2281 lastStokesIters_ = maxGpuIterations;
2282 lastStokesResidual_ = maxGpuResidual / b_norm;
2283
2284 if (Logger::hasDebug()) {
2285 const std::string tag = "stokes n=" + std::to_string(n) +
2286 " iters=" + std::to_string(lastStokesIters_) +
2287 " res=" + std::to_string(lastStokesResidual_) +
2288 " [GPU]";
2289 Logger::getInstance()
2290 .addTiming(tag + " GPU upload", tUpload)
2291 .addTiming(tag + " GPU BiCGSTAB", tSolve)
2292 .print();
2293 }
2294 return;
2295 }
2296#endif
2297
2298 // Stokes SpMV: (Av)[i] = diag[i]*vin[i] - rhs_at_vin[i] + vBC[i], stored as
2299 // SolverT.
2300 auto stokesMatvec = [&](const std::vector<Vec3D<SolverT>> &vin,
2301 std::vector<Vec3D<SolverT>> &Av) {
2302#pragma omp parallel for schedule(static)
2303 for (std::size_t i = 0; i < n; ++i) {
2304 T d;
2305 Vec3D<T> rhs;
2306 computeVelocityStencilAt(i, vin, d, rhs);
2307 for (unsigned c = 0; c < D; ++c)
2308 Av[i][c] =
2309 static_cast<SolverT>(diag[i] * vin[i][c] - rhs[c] + vBC[i][c]);
2310 }
2311 };
2312
2313 // Dot product accumulated in T for numerical stability.
2314 auto vecDot = [&](const std::vector<Vec3D<SolverT>> &a,
2315 const std::vector<Vec3D<SolverT>> &bv) {
2316 T sum = T(0);
2317 for (std::size_t i = 0; i < n; ++i)
2318 for (unsigned c = 0; c < D; ++c) {
2319 const T av = static_cast<T>(a[i][c]);
2320 const T bvVal = static_cast<T>(bv[i][c]);
2321 if (!std::isfinite(av) || !std::isfinite(bvVal))
2322 return std::numeric_limits<T>::quiet_NaN();
2323 sum += av * bvVal;
2324 }
2325 return sum;
2326 };
2327
2328 auto vecMaxAbs = [&](const std::vector<Vec3D<SolverT>> &vin) {
2329 T m = T(0);
2330 for (std::size_t i = 0; i < n; ++i)
2331 for (unsigned c = 0; c < D; ++c) {
2332 const T value = static_cast<T>(vin[i][c]);
2333 if (!std::isfinite(value))
2334 return std::numeric_limits<T>::infinity();
2335 m = std::max(m, std::abs(value));
2336 }
2337 return m;
2338 };
2339
2340 // r = b - A*x
2341 const Vec3D<SolverT> zero3{SolverT(0), SolverT(0), SolverT(0)};
2342 std::vector<Vec3D<SolverT>> Ax(n), r(n), r_hat(n);
2343 std::vector<Vec3D<SolverT>> pv(n, zero3), sv(n, zero3), y(n), z(n), s(n),
2344 t(n);
2345 stokesMatvec(x, Ax);
2346 for (std::size_t i = 0; i < n; ++i)
2347 for (unsigned c = 0; c < D; ++c) {
2348 r[i][c] = static_cast<SolverT>(b[i][c] - Ax[i][c]);
2349 r_hat[i][c] = r[i][c];
2350 }
2351
2352 T rho = T(1), alpha = T(1), omega = T(1);
2353 T velocityResidual = T(0);
2354 unsigned stokesIter = 0;
2355 bool stokesBreakdown = false;
2356 velocityResidual = vecMaxAbs(r);
2357
2358 for (; stokesIter < deformationParameters.stokesIterations; ++stokesIter) {
2359 const T rho_new = vecDot(r_hat, r);
2360 if (!std::isfinite(rho_new)) {
2361 stokesBreakdown = true;
2362 break;
2363 }
2364 if (std::abs(rho_new) < T(1e-100))
2365 break;
2366 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
2367 !std::isfinite(omega) || std::abs(omega) < T(1e-100)) {
2368 stokesBreakdown = true;
2369 break;
2370 }
2371
2372 const T beta = (rho_new / rho) * (alpha / omega);
2373 if (!std::isfinite(beta)) {
2374 stokesBreakdown = true;
2375 break;
2376 }
2377 rho = rho_new;
2378
2379 for (std::size_t i = 0; i < n; ++i)
2380 for (unsigned c = 0; c < D; ++c)
2381 pv[i][c] = static_cast<SolverT>(r[i][c] +
2382 beta * (pv[i][c] - omega * sv[i][c]));
2383
2384 for (std::size_t i = 0; i < n; ++i)
2385 for (unsigned c = 0; c < D; ++c) {
2386 const T pvc = pv[i][c];
2387 const T pcDiag = precondDiag[i][c];
2388 y[i][c] = static_cast<SolverT>((pcDiag > eps) ? pvc / pcDiag : pvc);
2389 }
2390
2391 stokesMatvec(y, sv);
2392
2393 const T r_hat_v = vecDot(r_hat, sv);
2394 if (!std::isfinite(r_hat_v)) {
2395 stokesBreakdown = true;
2396 break;
2397 }
2398 if (std::abs(r_hat_v) < T(1e-100))
2399 break;
2400
2401 alpha = rho_new / r_hat_v;
2402 if (!std::isfinite(alpha)) {
2403 stokesBreakdown = true;
2404 break;
2405 }
2406
2407 for (std::size_t i = 0; i < n; ++i)
2408 for (unsigned c = 0; c < D; ++c)
2409 s[i][c] = static_cast<SolverT>(r[i][c] - alpha * sv[i][c]);
2410
2411 velocityResidual = vecMaxAbs(s);
2412 if (!std::isfinite(velocityResidual)) {
2413 stokesBreakdown = true;
2414 break;
2415 }
2416 if (velocityResidual < deformationParameters.stokesTolerance * b_norm) {
2417 for (std::size_t i = 0; i < n; ++i)
2418 for (unsigned c = 0; c < D; ++c)
2419 x[i][c] = static_cast<SolverT>(x[i][c] + alpha * y[i][c]);
2420 break;
2421 }
2422
2423 for (std::size_t i = 0; i < n; ++i)
2424 for (unsigned c = 0; c < D; ++c) {
2425 const T sc = s[i][c];
2426 const T pcDiag = precondDiag[i][c];
2427 z[i][c] = static_cast<SolverT>((pcDiag > eps) ? sc / pcDiag : sc);
2428 }
2429
2430 stokesMatvec(z, t);
2431
2432 const T t_s = vecDot(t, s);
2433 const T t_t = vecDot(t, t);
2434 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
2435 stokesBreakdown = true;
2436 break;
2437 }
2438 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
2439 if (!std::isfinite(omega)) {
2440 stokesBreakdown = true;
2441 break;
2442 }
2443
2444 for (std::size_t i = 0; i < n; ++i)
2445 for (unsigned c = 0; c < D; ++c) {
2446 x[i][c] =
2447 static_cast<SolverT>(x[i][c] + alpha * y[i][c] + omega * z[i][c]);
2448 r[i][c] = static_cast<SolverT>(s[i][c] - omega * t[i][c]);
2449 }
2450
2451 velocityResidual = vecMaxAbs(r);
2452 if (!std::isfinite(velocityResidual)) {
2453 stokesBreakdown = true;
2454 break;
2455 }
2456 if (velocityResidual < deformationParameters.stokesTolerance * b_norm)
2457 break;
2458 }
2459
2460 if (stokesBreakdown)
2461 velocityResidual = std::numeric_limits<T>::infinity();
2462
2463 bool finiteSolution = !stokesBreakdown;
2464 for (std::size_t i = 0; i < n; ++i)
2465 for (unsigned c = 0; c < D; ++c)
2466 if (!std::isfinite(static_cast<T>(x[i][c])))
2467 finiteSolution = false;
2468
2469 if (finiteSolution) {
2470 for (std::size_t i = 0; i < n; ++i)
2471 for (unsigned c = 0; c < D; ++c)
2472 nodes[i].velocity[c] = static_cast<T>(x[i][c]);
2473 }
2474
2475 lastStokesIters_ = stokesIter;
2476 lastStokesResidual_ = finiteSolution ? velocityResidual / b_norm
2477 : std::numeric_limits<T>::infinity();
2478 if (lastStokesResidual_ > deformationParameters.stokesTolerance)
2479 VIENNACORE_LOG_WARNING(
2480 "solveStokesVelocity: BiCGSTAB did not converge after " +
2481 std::to_string(lastStokesIters_) + "/" +
2482 std::to_string(deformationParameters.stokesIterations) +
2483 " iterations (residual=" + std::to_string(lastStokesResidual_) +
2484 ", tolerance=" +
2485 std::to_string(deformationParameters.stokesTolerance) + ")");
2486 }
2487
2488 std::vector<Vec3D<T>> collectVelocities() const {
2489 std::vector<Vec3D<T>> velocities;
2490 velocities.reserve(nodes.size());
2491 for (const auto &node : nodes)
2492 velocities.push_back(node.velocity);
2493 return velocities;
2494 }
2495
2496 std::vector<T> collectPressures() const {
2497 std::vector<T> pressures;
2498 pressures.reserve(nodes.size());
2499 for (const auto &node : nodes)
2500 pressures.push_back(node.pressure);
2501 return pressures;
2502 }
2503
2504 template <class SolverT>
2505 StencilPoint<T>
2506 pressureStencilPoint(const std::vector<SolverT> &pressure,
2507 const std::vector<T> &ambientBoundaryPressure,
2508 std::size_t nodeId, unsigned direction,
2509 int offset) const {
2510 const auto &node = nodes[nodeId];
2511 IndexType neighbor = node.index;
2512 neighbor[direction] += offset;
2513
2514 if (!inBounds(neighbor))
2515 return {static_cast<T>(pressure[nodeId]), gridDelta};
2516
2517 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2518 if (neighborId != noNode) {
2519 if (touchesAmbient_[neighborId])
2520 return {ambientBoundaryPressure[neighborId], gridDelta};
2521 return {static_cast<T>(pressure[neighborId]), gridDelta};
2522 }
2523
2524 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2525 const std::size_t nn = nodes.size();
2526 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2527 const T faceDist = faceBCDists_[fi * nn + nodeId];
2528 if (faceType == Boundary::AMBIENT)
2529 return {ambientBoundaryPressure[nodeId], faceDist};
2530 // Reaction interface: Neumann ∂p/∂n=0 (solid-wall BC for pressure).
2531 // The ghost node takes the same value as the interior node, giving zero
2532 // contribution to the Laplacian stencil. The pressure is anchored only by
2533 // the AMBIENT Dirichlet (p=0 at the free surface), which is always present
2534 // for any connected oxide region.
2535 if (faceType == Boundary::REACTION)
2536 return {static_cast<T>(pressure[nodeId]), faceDist};
2537 if (faceType == Boundary::MASK)
2538 return {maskPressureBoundary(node.index, direction, offset,
2539 static_cast<T>(pressure[nodeId])),
2540 faceDist};
2541
2542 return {static_cast<T>(pressure[nodeId]), gridDelta};
2543 }
2544
2545 template <class SolverT>
2546 StencilPoint<Vec3D<T>>
2547 velocityStencilPoint(const std::vector<Vec3D<SolverT>> &velocity,
2548 std::size_t nodeId, unsigned direction,
2549 int offset) const {
2550 const auto &node = nodes[nodeId];
2551 IndexType neighbor = node.index;
2552 neighbor[direction] += offset;
2553
2554 const auto toT = [](const Vec3D<SolverT> &v) -> Vec3D<T> {
2555 return {static_cast<T>(v[0]), static_cast<T>(v[1]), static_cast<T>(v[2])};
2556 };
2557
2558 if (!inBounds(neighbor))
2559 return {toT(velocity[nodeId]), gridDelta};
2560
2561 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2562 if (neighborId != noNode)
2563 return {toT(velocity[neighborId]), gridDelta};
2564
2565 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2566 const std::size_t nn = nodes.size();
2567 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2568 const T faceDist = faceBCDists_[fi * nn + nodeId];
2569 if (faceType == Boundary::REACTION)
2570 return {reactionBoundaryVelocity(node.index), faceDist};
2571 if (faceType == Boundary::AMBIENT)
2572 return {freeSurfaceVelocityBoundary(node.index, direction, offset,
2573 faceDist, toT(velocity[nodeId])),
2574 faceDist};
2575 if (faceType == Boundary::MASK)
2576 return {maskVelocityBoundary(node.index, toT(velocity[nodeId])),
2577 faceDist};
2578
2579 return {toT(velocity[nodeId]), gridDelta};
2580 }
2581
2582 StencilPoint<T> currentPressureStencilPoint(std::size_t nodeId,
2583 unsigned direction,
2584 int offset) const {
2585 const auto &node = nodes[nodeId];
2586 IndexType neighbor = node.index;
2587 neighbor[direction] += offset;
2588
2589 if (!inBounds(neighbor))
2590 return {node.pressure, gridDelta};
2591
2592 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2593 if (neighborId != noNode)
2594 return {nodes[neighborId].pressure, gridDelta};
2595
2596 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2597 const std::size_t nn = nodes.size();
2598 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2599 const T faceDist = faceBCDists_[fi * nn + nodeId];
2600 if (faceType == Boundary::AMBIENT)
2601 return {freeSurfacePressureBoundary(node.index), faceDist};
2602 if (faceType == Boundary::REACTION)
2603 return {node.pressure, faceDist}; // Neumann ∂p/∂n=0
2604 if (faceType == Boundary::MASK)
2605 return {
2606 maskPressureBoundary(node.index, direction, offset, node.pressure),
2607 faceDist};
2608
2609 return {node.pressure, gridDelta};
2610 }
2611
2612 StencilPoint<Vec3D<T>> currentVelocityStencilPoint(std::size_t nodeId,
2613 unsigned direction,
2614 int offset) const {
2615 const auto &node = nodes[nodeId];
2616 IndexType neighbor = node.index;
2617 neighbor[direction] += offset;
2618
2619 if (!inBounds(neighbor))
2620 return {node.velocity, gridDelta};
2621
2622 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2623 if (neighborId != noNode)
2624 return {nodes[neighborId].velocity, gridDelta};
2625
2626 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2627 const std::size_t nn = nodes.size();
2628 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2629 const T faceDist = faceBCDists_[fi * nn + nodeId];
2630 if (faceType == Boundary::REACTION)
2631 return {reactionBoundaryVelocity(node.index), faceDist};
2632 if (faceType == Boundary::AMBIENT)
2633 return {freeSurfaceVelocityBoundary(node.index, direction, offset,
2634 faceDist, node.velocity),
2635 faceDist};
2636 if (faceType == Boundary::MASK)
2637 return {maskVelocityBoundary(node.index, node.velocity), faceDist};
2638
2639 return {node.velocity, gridDelta};
2640 }
2641
2642 T maxVelocityChange(const std::vector<Vec3D<T>> &previous) const {
2643 T maxChange = 0.;
2644 T maxVelocity = 0.;
2645 const auto count = std::min(previous.size(), nodes.size());
2646 for (std::size_t i = 0; i < count; ++i) {
2647 for (unsigned j = 0; j < D; ++j) {
2648 maxChange = std::max(maxChange,
2649 std::abs(nodes[i].velocity[j] - previous[i][j]));
2650 maxVelocity = std::max(maxVelocity, std::abs(nodes[i].velocity[j]));
2651 }
2652 }
2653
2654 if (maxVelocity <= std::numeric_limits<T>::epsilon())
2655 return maxChange;
2656 return maxChange / maxVelocity;
2657 }
2658
2659 T maxPressureChange(const std::vector<T> &previous) const {
2660 T maxChange = 0.;
2661 T maxPressure = 0.;
2662 const auto count = std::min(previous.size(), nodes.size());
2663 for (std::size_t i = 0; i < count; ++i) {
2664 maxChange =
2665 std::max(maxChange, std::abs(nodes[i].pressure - previous[i]));
2666 maxPressure = std::max(maxPressure, std::abs(nodes[i].pressure));
2667 }
2668
2669 if (maxPressure <= std::numeric_limits<T>::epsilon())
2670 return maxChange;
2671 return maxChange / maxPressure;
2672 }
2673
2674 std::array<T, 9>
2675 currentBoundaryDeviatoricStress(const IndexType &index) const {
2676 const auto strainRate = strainRateTensorAt(index);
2677 const auto deviatoricRate =
2678 deviatoricTensor(strainRate, divergenceAt(index));
2679 const auto previousStress = previousDeviatoricStress(index);
2680 const T relaxationTime = effectiveStressRelaxationTime();
2681 const T decay =
2682 (relaxationTime <= std::numeric_limits<T>::epsilon())
2683 ? T(0)
2684 : std::exp(-deformationParameters.stressTimeStep / relaxationTime);
2685
2686 std::array<T, 9> deviatoricStress{};
2687 for (unsigned i = 0; i < 9; ++i) {
2688 const T viscousStress =
2689 T(2) * deformationParameters.viscosity * deviatoricRate[i];
2690 deviatoricStress[i] =
2691 decay * previousStress[i] + (T(1) - decay) * viscousStress;
2692 }
2693
2694 return deviatoricStress;
2695 }
2696
2697 T freeSurfacePressureBoundary(const IndexType &index) const {
2698 const auto normal = interfaceNormal(index, Boundary::AMBIENT);
2699 const auto deviatoricStress = currentBoundaryDeviatoricStress(index);
2700
2701 return deformationParameters.ambientPressure +
2702 normalStress(deviatoricStress, normal);
2703 }
2704
2705 T maskPressureBoundary(const IndexType & /*index*/, unsigned /*direction*/,
2706 int /*offset*/, T fallbackPressure) const {
2707 return fallbackPressure;
2708 }
2709
2710 Vec3D<T> freeSurfaceVelocityBoundary(const IndexType &index,
2711 unsigned direction, int offset,
2712 T distance,
2713 const Vec3D<T> &interiorVelocity) const {
2714 Vec3D<T> boundaryVelocity = interiorVelocity;
2715 const auto normal = interfaceNormal(index, Boundary::AMBIENT);
2716 const auto deviatoricStress = deviatoricStressAt(index);
2717 const T pressure = pressureAt(index);
2718
2719 Vec3D<T> deviatoricTraction{0., 0., 0.};
2720 for (unsigned component = 0; component < D; ++component) {
2721 for (unsigned j = 0; j < D; ++j)
2722 deviatoricTraction[component] +=
2723 deviatoricStress[tensorIndex(component, j)] * normal[j];
2724 }
2725
2726 for (unsigned component = 0; component < D; ++component) {
2727 const T normalTraction =
2728 pressure * normal[component] - deviatoricTraction[component];
2729 const T faceDerivative = normalTraction * normal[direction] /
2730 std::max(deformationParameters.viscosity,
2731 std::numeric_limits<T>::epsilon());
2732 boundaryVelocity[component] +=
2733 static_cast<T>(offset) * distance * faceDerivative;
2734 }
2735
2736 return boundaryVelocity;
2737 }
2738
2739 Vec3D<T> maskVelocityBoundary(const IndexType &index,
2740 const Vec3D<T> &interiorVelocity) const {
2741 if (maskVelocityField != nullptr) {
2742 Vec3D<T> coordinate{0., 0., 0.};
2743 for (unsigned i = 0; i < D; ++i)
2744 coordinate[i] = index[i] * gridDelta;
2745 return maskVelocityField->getVectorVelocity(
2746 coordinate, deformationParameters.material, {0., 0., 0.}, 0);
2747 }
2748 return {0., 0., 0.};
2749 }
2750
2752 avgExpansionSpeed_ = 0.;
2753 if (nodes.empty())
2754 return;
2755
2756 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2757 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2758 auto maskIt = makeMaskIterator();
2759 std::size_t count = 0;
2760
2761 for (const auto &node : nodes) {
2762 bool touchesReactionBoundary = false;
2763 for (unsigned direction = 0; direction < D; ++direction) {
2764 for (int offset : {-1, 1}) {
2765 IndexType neighbor = node.index;
2766 neighbor[direction] += offset;
2767 if (!inBounds(neighbor))
2768 continue;
2769
2770 if (lookupNode(neighbor) != noNode)
2771 continue;
2772
2773 if (classifyBoundary(reactionIt, ambientIt, maskIt, node.index,
2774 neighbor) == Boundary::REACTION) {
2775 touchesReactionBoundary = true;
2776 break;
2777 }
2778 }
2779 if (touchesReactionBoundary)
2780 break;
2781 }
2782
2783 if (touchesReactionBoundary) {
2784 Vec3D<T> coordinate{0., 0., 0.};
2785 for (unsigned i = 0; i < D; ++i)
2786 coordinate[i] = node.index[i] * gridDelta;
2787 avgExpansionSpeed_ +=
2788 (oxidationParameters.expansionCoefficient - T(1)) *
2789 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
2790 {0., 0., 0.}, 0));
2791 ++count;
2792 }
2793 }
2794
2795 if (count > 0)
2796 avgExpansionSpeed_ /= static_cast<T>(count);
2797 }
2798
2799 Vec3D<T> reactionBoundaryVelocity(const IndexType &index) const {
2800 Vec3D<T> coordinate{0., 0., 0.};
2801 for (unsigned i = 0; i < D; ++i)
2802 coordinate[i] = index[i] * gridDelta;
2803 const T expansionVelocity = localExpansionSpeed(coordinate);
2804 return reactionNormal(index) * (reactionSign * expansionVelocity);
2805 }
2806
2807 Vec3D<T> unresolvedAmbientVelocity(const Vec3D<T> &coordinate) const {
2808 if (diffusionField == nullptr || ambientInterface == nullptr)
2809 return {0., 0., 0.};
2810
2811 IndexType index;
2812 for (unsigned i = 0; i < D; ++i)
2813 index[i] = std::llround(coordinate[i] / gridDelta);
2814
2815 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2816 const auto normal = levelSetNormal(ambientIt, index);
2817 return normal * localExpansionSpeed(coordinate);
2818 }
2819
2821 Vec3D<T> maxVelocity{0., 0., 0.};
2822 if (ambientInterface == nullptr || diffusionField == nullptr)
2823 return maxVelocity;
2824
2825 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2826 for (; !ambientIt.isFinished(); ++ambientIt) {
2827 if (!ambientIt.isDefined())
2828 continue;
2829
2830 Vec3D<T> coordinate{0., 0., 0.};
2831 const auto &index = ambientIt.getStartIndices();
2832 for (unsigned d = 0; d < D; ++d)
2833 coordinate[d] = index[d] * gridDelta;
2834
2835 const auto velocity = unresolvedAmbientVelocity(coordinate);
2836 for (unsigned d = 0; d < D; ++d)
2837 maxVelocity[d] = std::max(maxVelocity[d], std::abs(velocity[d]));
2838 }
2839 return maxVelocity;
2840 }
2841
2842 T divergenceAt(const IndexType &index) const {
2843 T divergence = 0.;
2844 for (unsigned i = 0; i < D; ++i) {
2845 divergence += velocityDerivative(index, i, i);
2846 }
2847 return divergence;
2848 }
2849
2850 Vec3D<T> pressureGradient(const IndexType &index) const {
2851 Vec3D<T> gradient{0., 0., 0.};
2852 for (unsigned i = 0; i < D; ++i)
2853 gradient[i] = pressureDerivative(index, i);
2854 return gradient;
2855 }
2856
2857 Vec3D<T> momentumForcing(const IndexType &index) const {
2858 Vec3D<T> forcing = pressureGradient(index);
2859 const auto stressDivergence = deviatoricStressDivergence(index);
2860 for (unsigned i = 0; i < D; ++i)
2861 forcing[i] -= stressDivergence[i];
2862 return forcing;
2863 }
2864
2865 Vec3D<T> deviatoricStressDivergence(const IndexType &index) const {
2866 Vec3D<T> divergence{0., 0., 0.};
2867 for (unsigned component = 0; component < D; ++component) {
2868 for (unsigned direction = 0; direction < D; ++direction) {
2869 IndexType pos = index;
2870 IndexType neg = index;
2871 pos[direction] += 1;
2872 neg[direction] -= 1;
2873 const auto posStress = deviatoricStressAt(pos);
2874 const auto negStress = deviatoricStressAt(neg);
2875 divergence[component] +=
2876 (posStress[tensorIndex(component, direction)] -
2877 negStress[tensorIndex(component, direction)]) /
2878 (T(2) * gridDelta);
2879 }
2880 }
2881 return divergence;
2882 }
2883
2884 std::array<T, 9> deviatoricStressAt(const IndexType &index) const {
2885 if (!inBounds(index))
2886 return {};
2887
2888 const std::size_t nodeId = lookupNode(index);
2889 if (nodeId == noNode)
2890 return {};
2891
2892 std::array<T, 9> deviatoric = nodes[nodeId].stressTensor;
2893 for (unsigned i = 0; i < 3; ++i)
2894 deviatoric[tensorIndex(i, i)] += nodes[nodeId].pressure;
2895 return deviatoric;
2896 }
2897
2898 T pressureAt(const IndexType &index) const {
2899 if (!inBounds(index))
2900 return deformationParameters.ambientPressure;
2901
2902 const std::size_t nodeId = lookupNode(index);
2903 if (nodeId == noNode)
2904 return deformationParameters.ambientPressure;
2905 return nodes[nodeId].pressure;
2906 }
2907
2908 T localExpansionSpeed(const Vec3D<T> &coordinate) const {
2909 return (oxidationParameters.expansionCoefficient - T(1)) *
2910 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
2911 {0., 0., 0.}, 0));
2912 }
2913
2914 Vec3D<T> reactionNormal(const IndexType &index) const {
2915 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2916 return levelSetNormal(reactionIt, index);
2917 }
2918
2919 Vec3D<T> interfaceNormal(const IndexType &index, Boundary boundary) const {
2920 if (boundary == Boundary::AMBIENT) {
2921 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2922 return levelSetNormal(ambientIt, index);
2923 }
2924 if (boundary == Boundary::MASK && maskInterface != nullptr) {
2925 ConstSparseIterator maskIt(maskInterface->getDomain());
2926 return levelSetNormal(maskIt, index);
2927 }
2928
2929 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2930 return levelSetNormal(reactionIt, index);
2931 }
2932
2933 Vec3D<T> levelSetNormal(ConstSparseIterator &levelSetIt,
2934 const IndexType &index) const {
2935 Vec3D<T> normal{0., 0., 0.};
2936 T norm = 0.;
2937
2938 for (unsigned i = 0; i < D; ++i) {
2939 IndexType pos = index;
2940 IndexType neg = index;
2941 pos[i] += 1;
2942 neg[i] -= 1;
2943 if (!inBounds(pos))
2944 pos = index;
2945 if (!inBounds(neg))
2946 neg = index;
2947 normal[i] = detail::clampLevelSetPhi(valueAt(levelSetIt, pos)) -
2948 detail::clampLevelSetPhi(valueAt(levelSetIt, neg));
2949 norm += normal[i] * normal[i];
2950 }
2951
2952 if (norm <= std::numeric_limits<T>::epsilon()) {
2953 normal = Vec3D<T>{0., 0., 0.};
2954 normal[D - 1] = 1.;
2955 return normal;
2956 }
2957
2958 norm = std::sqrt(norm);
2959 for (unsigned i = 0; i < D; ++i)
2960 normal[i] /= norm;
2961 return normal;
2962 }
2963
2965#pragma omp parallel for schedule(static)
2966 for (std::size_t i = 0; i < nodes.size(); ++i)
2967 nodes[i].strainTrace = divergenceAt(nodes[i].index);
2968 }
2969
2971 const T relaxationTime = effectiveStressRelaxationTime();
2972 const T decay =
2973 (relaxationTime <= std::numeric_limits<T>::epsilon())
2974 ? T(0)
2975 : std::exp(-deformationParameters.stressTimeStep / relaxationTime);
2976
2977 // Per-node computation is independent; collect history keys into a vector
2978 // to avoid concurrent map writes, then build the map sequentially below.
2979 std::vector<std::pair<IndexType, std::array<T, 9>>> historyEntries(
2980 nodes.size());
2981#pragma omp parallel for schedule(static)
2982 for (std::size_t i = 0; i < nodes.size(); ++i) {
2983 auto &node = nodes[i];
2984 node.strainRateTensor = strainRateTensorAt(node.index);
2985 const auto deviatoricRate =
2986 deviatoricTensor(node.strainRateTensor, node.strainTrace);
2987 const auto previousStress = previousDeviatoricStress(node.index);
2988
2989 std::array<T, 9> deviatoricStress{};
2990 for (unsigned j = 0; j < 9; ++j) {
2991 const T viscousStress =
2992 T(2) * deformationParameters.viscosity * deviatoricRate[j];
2993 deviatoricStress[j] =
2994 decay * previousStress[j] + (T(1) - decay) * viscousStress;
2995 }
2996
2997 node.stressTensor = deviatoricStress;
2998 for (unsigned j = 0; j < 3; ++j)
2999 node.stressTensor[tensorIndex(j, j)] -= node.pressure;
3000
3001 node.vonMisesStress = vonMisesFromDeviatoric(deviatoricStress);
3002 historyEntries[i] = {node.index, deviatoricStress};
3003 }
3004
3005 std::unordered_map<IndexType, std::array<T, 9>, typename IndexType::hash>
3006 nextHistory;
3007 nextHistory.reserve(nodes.size());
3008 for (const auto &entry : historyEntries)
3009 nextHistory[entry.first] = entry.second;
3010 deviatoricStressHistory.swap(nextHistory);
3011 }
3012
3013 std::array<T, 9> strainRateTensorAt(const IndexType &index) const {
3014 std::array<T, 9> tensor{};
3015 for (unsigned i = 0; i < D; ++i) {
3016 for (unsigned j = 0; j < D; ++j) {
3017 tensor[tensorIndex(i, j)] = T(0.5) * (velocityDerivative(index, i, j) +
3018 velocityDerivative(index, j, i));
3019 }
3020 }
3021 return tensor;
3022 }
3023
3024 T velocityDerivative(const IndexType &index, unsigned component,
3025 unsigned direction) const {
3026 const std::size_t nodeId = lookupNode(index);
3027 if (nodeId == noNode)
3028 return 0.;
3029
3030 const auto plus = currentVelocityStencilPoint(nodeId, direction, 1);
3031 const auto minus = currentVelocityStencilPoint(nodeId, direction, -1);
3032 return firstDerivative(
3033 minus.value[component], nodes[nodeId].velocity[component],
3034 plus.value[component], minus.distance, plus.distance);
3035 }
3036
3037 T pressureDerivative(const IndexType &index, unsigned direction) const {
3038 const std::size_t nodeId = lookupNode(index);
3039 if (nodeId == noNode)
3040 return 0.;
3041
3042 const auto plus = currentPressureStencilPoint(nodeId, direction, 1);
3043 const auto minus = currentPressureStencilPoint(nodeId, direction, -1);
3044 return firstDerivative(minus.value, nodes[nodeId].pressure, plus.value,
3045 minus.distance, plus.distance);
3046 }
3047
3048 std::array<T, 9> deviatoricTensor(const std::array<T, 9> &tensor,
3049 T trace) const {
3050 std::array<T, 9> result = tensor;
3051 const T mean = trace / T(3);
3052 for (unsigned i = 0; i < 3; ++i)
3053 result[tensorIndex(i, i)] -= mean;
3054 return result;
3055 }
3056
3057 std::array<T, 9> previousDeviatoricStress(const IndexType &index) const {
3058 const auto found = deviatoricStressHistory.find(index);
3059 if (found == deviatoricStressHistory.end())
3060 return {};
3061 return found->second;
3062 }
3063
3065 if (deformationParameters.stressRelaxationTime > T(0))
3066 return deformationParameters.stressRelaxationTime;
3067 if (deformationParameters.shearModulus > std::numeric_limits<T>::epsilon())
3068 return deformationParameters.viscosity /
3069 deformationParameters.shearModulus;
3070 return T(0);
3071 }
3072
3073 T vonMisesFromDeviatoric(const std::array<T, 9> &deviatoricStress) const {
3074 T doubleContraction = 0.;
3075 for (unsigned i = 0; i < 3; ++i) {
3076 for (unsigned j = 0; j < 3; ++j) {
3077 const T value = T(0.5) * (deviatoricStress[tensorIndex(i, j)] +
3078 deviatoricStress[tensorIndex(j, i)]);
3079 doubleContraction += value * value;
3080 }
3081 }
3082 return std::sqrt(T(1.5) * doubleContraction);
3083 }
3084
3085 T normalStress(const std::array<T, 9> &tensor, const Vec3D<T> &normal) const {
3086 T result = 0.;
3087 for (unsigned i = 0; i < 3; ++i) {
3088 for (unsigned j = 0; j < 3; ++j)
3089 result += normal[i] * tensor[tensorIndex(i, j)] * normal[j];
3090 }
3091 return result;
3092 }
3093
3094 Boundary classifyBoundary(ConstSparseIterator &reactionIt,
3095 ConstSparseIterator &ambientIt,
3096 ConstSparseIterator &maskIt,
3097 const IndexType &inside,
3098 const IndexType &outside) const {
3099 return boundaryIntersection(reactionIt, ambientIt, maskIt, inside, outside)
3100 .boundary;
3101 }
3102
3103 BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt,
3104 ConstSparseIterator &ambientIt,
3105 ConstSparseIterator &maskIt,
3106 const IndexType &inside,
3107 const IndexType &outside) const {
3108 const T reactionInside = valueAt(reactionIt, inside);
3109 const T reactionOutside = valueAt(reactionIt, outside);
3110 const T ambientInside = valueAt(ambientIt, inside);
3111 const T ambientOutside = valueAt(ambientIt, outside);
3112 const T maskInside = valueAtMask(maskIt, inside);
3113 const T maskOutside = valueAtMask(maskIt, outside);
3114
3115 const bool reactionCrosses = crosses(reactionInside, reactionOutside);
3116 const bool ambientCrosses = crosses(ambientInside, ambientOutside);
3117 const bool maskCrosses =
3118 maskInterface != nullptr && crosses(maskInside, maskOutside);
3119
3120 if (!reactionCrosses && !ambientCrosses && !maskCrosses)
3121 return {Boundary::NONE, gridDelta};
3122 if (reactionCrosses && !ambientCrosses && !maskCrosses)
3123 return {Boundary::REACTION,
3124 crossingDistance(reactionInside, reactionOutside)};
3125 if (!reactionCrosses && ambientCrosses && !maskCrosses)
3127 maskInside, maskOutside,
3128 crossingDistance(ambientInside, ambientOutside));
3129 if (!reactionCrosses && !ambientCrosses && maskCrosses)
3130 return {Boundary::MASK, crossingDistance(maskInside, maskOutside)};
3131
3132 const T reactionDistance =
3133 reactionCrosses ? crossingDistance(reactionInside, reactionOutside)
3134 : std::numeric_limits<T>::max();
3135 const T ambientDistance =
3136 ambientCrosses ? crossingDistance(ambientInside, ambientOutside)
3137 : std::numeric_limits<T>::max();
3138 const T maskDistance = maskCrosses
3139 ? crossingDistance(maskInside, maskOutside)
3140 : std::numeric_limits<T>::max();
3141 if (reactionDistance <= ambientDistance && reactionDistance <= maskDistance)
3142 return {Boundary::REACTION, reactionDistance};
3143 if (ambientDistance != std::numeric_limits<T>::max()) {
3144 const auto maskedAmbient =
3145 ambientCrossingInsideMask(maskInside, maskOutside, ambientDistance);
3146 if (maskedAmbient.boundary == Boundary::MASK)
3147 return maskedAmbient;
3148 }
3149 if (maskDistance <= ambientDistance)
3150 return {Boundary::MASK, maskDistance};
3151 return {Boundary::AMBIENT, ambientDistance};
3152 }
3153
3154 bool touchesBoundary(ConstSparseIterator &reactionIt,
3155 ConstSparseIterator &ambientIt,
3156 ConstSparseIterator &maskIt, const IndexType &index,
3157 Boundary requestedBoundary) const {
3158 for (unsigned direction = 0; direction < D; ++direction) {
3159 for (int offset : {-1, 1}) {
3160 IndexType neighbor = index;
3161 neighbor[direction] += offset;
3162 if (!inBounds(neighbor))
3163 continue;
3164
3165 if (lookupNode(neighbor) != noNode)
3166 continue;
3167
3168 if (classifyBoundary(reactionIt, ambientIt, maskIt, index, neighbor) ==
3169 requestedBoundary)
3170 return true;
3171 }
3172 }
3173 return false;
3174 }
3175
3176 bool isInsideOxide(T reactionPhi, T ambientPhi) const {
3177 // GeometricAdvect can leave a tiny positive residual (~4*epsilon) when the
3178 // interface lands exactly on a grid point at non-zero coordinates, because
3179 // k*gridDelta is not exactly representable in floating point. Allow a
3180 // tolerance of 1e-9 grid units so that grid points on the surface (phi≈0)
3181 // are correctly classified as inside the oxide.
3182 constexpr T eps = T(1e-9);
3183 return reactionSign * reactionPhi >= -eps &&
3184 ambientSign * ambientPhi >= -eps;
3185 }
3186
3187 ConstSparseIterator makeMaskIterator() const {
3188 if (maskInterface == nullptr)
3189 return ConstSparseIterator(reactionInterface->getDomain());
3190 return ConstSparseIterator(maskInterface->getDomain());
3191 }
3192
3193 bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3194 if (maskInterface == nullptr)
3195 return false;
3196 return maskSign * valueAt(maskIt, index) >= 0.;
3197 }
3198
3199 T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3200 if (maskInterface == nullptr)
3201 return std::numeric_limits<T>::max();
3202 return valueAt(maskIt, index);
3203 }
3204
3205 BoundaryIntersection ambientCrossingInsideMask(T maskInside, T maskOutside,
3206 T distance) const {
3207 if (isMaskAtCrossing(maskInside, maskOutside, distance))
3208 return {Boundary::MASK, distance};
3209 // Outer node is wholly inside the mask body: the oxide/gas surface has
3210 // drifted into the nitride. Apply mask Dirichlet BC (not traction-free)
3211 // so the deformation solver does not advance the surface further in.
3212 // Mirrors the equivalent check in lsOxidationDiffusion::classifyBoundary.
3213 if (maskInterface != nullptr &&
3214 static_cast<T>(maskSign) * maskOutside >= T(0))
3215 return {Boundary::MASK, distance};
3216 return {Boundary::AMBIENT, distance};
3217 }
3218
3219 bool isMaskAtCrossing(T maskInside, T maskOutside, T distance) const {
3220 if (maskInterface == nullptr)
3221 return false;
3222 const T fraction = std::clamp(distance / gridDelta, T(0), T(1));
3223 const T insidePhi = detail::clampLevelSetPhi(maskInside);
3224 const T outsidePhi = detail::clampLevelSetPhi(maskOutside);
3225 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
3226 return static_cast<T>(maskSign) * maskPhi >= T(0);
3227 }
3228
3229 T crossingDistance(T insidePhi, T outsidePhi) const {
3231 insidePhi, outsidePhi,
3232 deformationParameters.minMechanicsBoundaryDistance, gridDelta);
3233 }
3234
3235 static T firstDerivative(T minusValue, T centerValue, T plusValue,
3236 T minusDistance, T plusDistance) {
3237 const T denominator =
3238 minusDistance * plusDistance * (minusDistance + plusDistance);
3239 if (denominator <= std::numeric_limits<T>::epsilon())
3240 return 0.;
3241
3242 return (-plusDistance * plusDistance * minusValue +
3243 (plusDistance * plusDistance - minusDistance * minusDistance) *
3244 centerValue +
3245 minusDistance * minusDistance * plusValue) /
3246 denominator;
3247 }
3248
3249 static constexpr unsigned tensorIndex(unsigned row, unsigned column) {
3250 return 3 * row + column;
3251 }
3252};
3253
3254} // namespace viennals
constexpr int D
Definition Epitaxy.cpp:12
double T
Definition Epitaxy.cpp:13
Class containing all information about the level set, including the dimensions of the domain,...
Definition lsDomain.hpp:27
void setGpuMode(GpuMode mode)
Definition lsOxidationDeformation.hpp:223
T normalStress(const std::array< T, 9 > &tensor, const Vec3D< T > &normal) const
Definition lsOxidationDeformation.hpp:3085
T pressureAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2898
bool initialiseGrid()
Definition lsOxidationDeformation.hpp:1032
std::array< T, 9 > getStressTensor(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:529
std::vector< Vec3D< T > > collectVelocities() const
Definition lsOxidationDeformation.hpp:2488
std::size_t getNumberOfSolutionNodes() const
Definition lsOxidationDeformation.hpp:573
void setMaskInterface(SmartPointer< Domain< T, D > > passedInterface, int passedMaskSign=1)
Definition lsOxidationDeformation.hpp:240
StencilPoint< T > currentPressureStencilPoint(std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2582
std::array< T, 9 > getStrainRateTensor(const IndexType &index) const
Definition lsOxidationDeformation.hpp:524
T getVonMisesStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:544
T velocityDerivative(const IndexType &index, unsigned component, unsigned direction) const
Definition lsOxidationDeformation.hpp:3024
void computeDiagnostics()
Definition lsOxidationDeformation.hpp:2964
T getResidual() const
Definition lsOxidationDeformation.hpp:550
T localExpansionSpeed(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:2908
static T firstDerivative(T minusValue, T centerValue, T plusValue, T minusDistance, T plusDistance)
Definition lsOxidationDeformation.hpp:3235
T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3199
void setReactionInterface(SmartPointer< Domain< T, D > > passedInterface)
Definition lsOxidationDeformation.hpp:228
Vec3D< T > estimateMaxUnresolvedAmbientVelocity() const
Definition lsOxidationDeformation.hpp:2820
void computePressureStencilAt(std::size_t nodeId, const std::vector< SolverT > &p, const std::vector< T > &ambientBP, T &diag, T &rhs) const
Definition lsOxidationDeformation.hpp:1630
Vec3D< T > levelSetNormal(ConstSparseIterator &levelSetIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:2933
void computeAvgExpansionSpeed()
Definition lsOxidationDeformation.hpp:2751
void buildNodes()
Definition lsOxidationDeformation.hpp:1039
unsigned getIterations() const
Definition lsOxidationDeformation.hpp:549
T maskPressureBoundary(const IndexType &, unsigned, int, T fallbackPressure) const
Definition lsOxidationDeformation.hpp:2705
Vec3D< T > freeSurfaceVelocityBoundary(const IndexType &index, unsigned direction, int offset, T distance, const Vec3D< T > &interiorVelocity) const
Definition lsOxidationDeformation.hpp:2710
StencilPoint< Vec3D< T > > currentVelocityStencilPoint(std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2612
Vec3D< T > getVectorVelocity(const Vec3D< T > &coordinate, int material, const Vec3D< T > &, unsigned long) final
Like getScalarVelocity, but returns a velocity value for each cartesian direction.
Definition lsOxidationDeformation.hpp:387
void computeVelocityStencilAt(std::size_t nodeId, const std::vector< Vec3D< SolverT > > &v, T &diag, Vec3D< T > &rhs) const
Definition lsOxidationDeformation.hpp:2157
T getScalarVelocity(const Vec3D< T > &coordinate, int material, const Vec3D< T > &normalVector, unsigned long) final
Should return a scalar value for the velocity at coordinate for a point of material with the given no...
Definition lsOxidationDeformation.hpp:407
T maxVelocityChange(const std::vector< Vec3D< T > > &previous) const
Definition lsOxidationDeformation.hpp:2642
StencilPoint< T > pressureStencilPoint(const std::vector< SolverT > &pressure, const std::vector< T > &ambientBoundaryPressure, std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2506
void markGeometryChanged()
Definition lsOxidationDeformation.hpp:303
Vec3D< T > reactionNormal(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2914
std::array< T, 9 > currentBoundaryDeviatoricStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2675
void setDeformationParameters(OxidationDeformationParameters passedParameters)
Definition lsOxidationDeformation.hpp:277
T pressureDerivative(const IndexType &index, unsigned direction) const
Definition lsOxidationDeformation.hpp:3037
bool lastSolveConverged() const
Definition lsOxidationDeformation.hpp:553
static auto New(Args &&...args)
Definition lsOxidationDeformation.hpp:211
Vec3D< T > deviatoricStressDivergence(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2865
T getStrainTrace(const IndexType &index) const
Definition lsOxidationDeformation.hpp:515
OxidationDeformation(SmartPointer< Domain< T, D > > passedReactionInterface, SmartPointer< Domain< T, D > > passedAmbientInterface, SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField, OxidationParameters passedOxidationParameters, OxidationDeformationParameters passedDeformationParameters={})
Definition lsOxidationDeformation.hpp:199
T getVonMisesStress(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:539
std::array< T, 9 > strainRateTensorAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3013
ConstSparseIterator makeMaskIterator() const
Definition lsOxidationDeformation.hpp:3187
Vec3D< T > momentumForcing(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2857
Vec3D< T > reactionBoundaryVelocity(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2799
std::array< T, 9 > previousDeviatoricStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3057
BoundaryIntersection ambientCrossingInsideMask(T maskInside, T maskOutside, T distance) const
Definition lsOxidationDeformation.hpp:3205
StencilPoint< Vec3D< T > > velocityStencilPoint(const std::vector< Vec3D< SolverT > > &velocity, std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2547
Boundary classifyBoundary(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3094
std::vector< T > collectPressures() const
Definition lsOxidationDeformation.hpp:2496
void apply()
Definition lsOxidationDeformation.hpp:308
std::array< T, 9 > deviatoricStressAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2884
T getLastStokesResidual() const
Definition lsOxidationDeformation.hpp:552
Vec3D< T > maskVelocityBoundary(const IndexType &index, const Vec3D< T > &interiorVelocity) const
Definition lsOxidationDeformation.hpp:2739
void clearSolveBounds()
Definition lsOxidationDeformation.hpp:297
T maxPressureChange(const std::vector< T > &previous) const
Definition lsOxidationDeformation.hpp:2659
std::array< T, 9 > deviatoricTensor(const std::array< T, 9 > &tensor, T trace) const
Definition lsOxidationDeformation.hpp:3048
std::array< T, 9 > getStressTensor(const IndexType &index) const
Definition lsOxidationDeformation.hpp:534
void forEachSolutionNode(Callback callback) const
Definition lsOxidationDeformation.hpp:581
Vec3D< T > pressureGradient(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2850
BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3103
void setOxidationParameters(OxidationParameters passedParameters)
Definition lsOxidationDeformation.hpp:271
bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3193
void setGpuPreconditioner(GpuPreconditioner prec)
Definition lsOxidationDeformation.hpp:224
void solvePressure()
Definition lsOxidationDeformation.hpp:1668
std::vector< Node > nodes
Definition lsOxidationDeformation.hpp:195
T effectiveStressRelaxationTime() const
Definition lsOxidationDeformation.hpp:3064
T divergenceAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2842
bool hasFiniteSolution() const
Definition lsOxidationDeformation.hpp:562
T crossingDistance(T insidePhi, T outsidePhi) const
Definition lsOxidationDeformation.hpp:3229
T getDissipationAlpha(int direction, int material, const Vec3D< T > &) final
If lsLocalLaxFriedrichsAnalytical is used as the spatial discretization scheme, this is called to pro...
Definition lsOxidationDeformation.hpp:413
void writeFieldsToLevelSet()
Write velocity (Vec3D) and viscoelastic stress history (3 tensor-row vectors) into ambientInterface->...
Definition lsOxidationDeformation.hpp:590
bool isMaskAtCrossing(T maskInside, T maskOutside, T distance) const
Definition lsOxidationDeformation.hpp:3219
void setMaskVelocityField(SmartPointer< VelocityField< T > > passedVelocityField)
Definition lsOxidationDeformation.hpp:255
void clearMaskVelocityField()
Definition lsOxidationDeformation.hpp:260
Vec3D< T > interfaceNormal(const IndexType &index, Boundary boundary) const
Definition lsOxidationDeformation.hpp:2919
std::array< T, 9 > getStrainRateTensor(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:519
void solveStokesVelocity()
Definition lsOxidationDeformation.hpp:2173
T vonMisesFromDeviatoric(const std::array< T, 9 > &deviatoricStress) const
Definition lsOxidationDeformation.hpp:3073
void setSolveBounds(const IndexType &passedMinIndex, const IndexType &passedMaxIndex)
Definition lsOxidationDeformation.hpp:288
void solveVelocity()
Definition lsOxidationDeformation.hpp:1174
void computeHarmonicStencilAt(std::size_t nodeId, const std::vector< Vec3D< SolverT > > &v, Vec3D< T > &sum) const
Definition lsOxidationDeformation.hpp:1120
Vec3D< T > getVelocity(const IndexType &index) const
Definition lsOxidationDeformation.hpp:497
void solveMechanics()
Definition lsOxidationDeformation.hpp:1457
void pressureMatvec(const std::vector< SolverT > &v, const std::vector< T > &ambientBP, const std::vector< T > &precomputedDiag, const std::vector< T > &pBC, std::vector< SolverT > &Av) const
Definition lsOxidationDeformation.hpp:1657
void harmonicMatvec(const std::vector< Vec3D< SolverT > > &v, const std::vector< Vec3D< T > > &b, std::vector< Vec3D< SolverT > > &Av) const
Definition lsOxidationDeformation.hpp:1161
T freeSurfacePressureBoundary(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2697
void setOxideSigns(int passedReactionSign, int passedAmbientSign)
Definition lsOxidationDeformation.hpp:282
void setDiffusionField(SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField)
Definition lsOxidationDeformation.hpp:265
~OxidationDeformation()
Definition lsOxidationDeformation.hpp:215
T getPressure(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:502
void setAmbientInterface(SmartPointer< Domain< T, D > > passedInterface)
Definition lsOxidationDeformation.hpp:234
static constexpr unsigned tensorIndex(unsigned row, unsigned column)
Definition lsOxidationDeformation.hpp:3249
void clearMaskInterface()
Definition lsOxidationDeformation.hpp:248
bool touchesBoundary(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &index, Boundary requestedBoundary) const
Definition lsOxidationDeformation.hpp:3154
void applySimpleVelocityCorrection(const std::vector< T > &pressureOld, const std::vector< Vec3D< T > > &diagV)
Definition lsOxidationDeformation.hpp:1556
T avgExpansionSpeed()
Definition lsOxidationDeformation.hpp:574
T getPressure(const IndexType &index) const
Definition lsOxidationDeformation.hpp:506
bool isInsideOxide(T reactionPhi, T ambientPhi) const
Definition lsOxidationDeformation.hpp:3176
T getLastPressureResidual() const
Definition lsOxidationDeformation.hpp:551
Vec3D< T > unresolvedAmbientVelocity(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:2807
void computeStressTensors()
Definition lsOxidationDeformation.hpp:2970
T getStrainTrace(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:510
Vec3D< T > getVelocity(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:492
std::vector< Vec3D< T > > computeVelocityDiagonals() const
Definition lsOxidationDeformation.hpp:1439
Solves the oxidant diffusion step of the Suvorov et al. (10.1007/s10825-006-0003-z) oxidation model o...
Definition lsOxidationDiffusion.hpp:98
Common Cartesian-grid infrastructure shared by the three oxidation solver classes (diffusion,...
Definition lsOxidationSolverBase.hpp:43
static constexpr std::size_t noNode
Definition lsOxidationSolverBase.hpp:49
bool crosses(T a, T b) const
Definition lsOxidationSolverBase.hpp:63
std::size_t lookupNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:90
std::size_t linearIndex(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:96
void initNodeLookup()
Definition lsOxidationSolverBase.hpp:83
bool inBounds(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:76
std::array< std::size_t, D > strides
Definition lsOxidationSolverBase.hpp:54
T gridDelta
Definition lsOxidationSolverBase.hpp:55
std::vector< std::size_t > nodeLookupFlat
Definition lsOxidationSolverBase.hpp:50
bool initializeGridFromInterfaces(SmartPointer< Domain< T, D > > reactionInterface, SmartPointer< Domain< T, D > > ambientInterface, SmartPointer< Domain< T, D > > maskInterface, bool useRequestedBounds, const IndexType &requestedMinIndex, const IndexType &requestedMaxIndex, std::size_t maxGridPoints, const std::string &solverName)
Definition lsOxidationSolverBase.hpp:160
std::array< std::size_t, D > extents
Definition lsOxidationSolverBase.hpp:53
viennahrle::ConstSparseIterator< typename Domain< T, D >::DomainType > ConstSparseIterator
Definition lsOxidationSolverBase.hpp:46
T valueAt(ConstSparseIterator &it, const IndexType &index) const
Definition lsOxidationSolverBase.hpp:71
bool increment(IndexType &index) const
Definition lsOxidationSolverBase.hpp:105
IndexType minIndex
Definition lsOxidationSolverBase.hpp:51
viennahrle::Index< D > IndexType
Definition lsOxidationSolverBase.hpp:45
std::size_t findNearbyNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:119
IndexType maxIndex
Definition lsOxidationSolverBase.hpp:52
T levelSetCrossingDistance(T insidePhi, T outsidePhi, T minBoundaryFraction, T gridDelta)
Definition lsOxidationSolverBase.hpp:29
T clampLevelSetPhi(T v)
Clamp HRLE far-field sentinels (±DBL_MAX) to ±1 before differencing to prevent DBL_MAX² overflow that...
Definition lsOxidationSolverBase.hpp:24
Definition lsAdvect.hpp:41
GpuMode
Selects the BiCGSTAB back-end for the diffusion solve. GPU failures are reported and not silently fal...
Definition lsOxidationDiffusion.hpp:26
@ Auto
Definition lsOxidationDiffusion.hpp:34
@ Gpu
Always use GPU; fail if unavailable or unsuccessful Use the GPU when it is usable,...
Definition lsOxidationDiffusion.hpp:28
@ Cpu
Always use CPU (default).
Definition lsOxidationDiffusion.hpp:27
GpuPreconditioner
Selects the preconditioner used by the GPU BiCGSTAB solver. Jacobi matches the CPU solver's precondit...
Definition lsOxidationDiffusion.hpp:41
@ ILU0
Definition lsOxidationDiffusion.hpp:41
@ Jacobi
Definition lsOxidationDiffusion.hpp:41
Parameters for the Cartesian-grid oxide deformation model.
Definition lsOxidationDeformation.hpp:18
double viscosity
Definition lsOxidationDeformation.hpp:19
unsigned stokesIterations
Definition lsOxidationDeformation.hpp:30
double tolerance
Definition lsOxidationDeformation.hpp:33
unsigned harmonicIterations
Definition lsOxidationDeformation.hpp:27
double shearModulus
Definition lsOxidationDeformation.hpp:24
double stressRelaxationTime
Definition lsOxidationDeformation.hpp:25
double relaxation
Definition lsOxidationDeformation.hpp:34
double minMechanicsBoundaryDistance
Definition lsOxidationDeformation.hpp:23
double bulkModulus
Definition lsOxidationDeformation.hpp:20
unsigned mechanicsIterations
Definition lsOxidationDeformation.hpp:28
unsigned pressureIterations
Definition lsOxidationDeformation.hpp:29
double pressureTolerance
Definition lsOxidationDeformation.hpp:22
double mechanicsTolerance
Definition lsOxidationDeformation.hpp:31
double stressTimeStep
Definition lsOxidationDeformation.hpp:26
double stokesTolerance
Definition lsOxidationDeformation.hpp:32
double pressureRelaxation
Definition lsOxidationDeformation.hpp:35
int material
Definition lsOxidationDeformation.hpp:38
double ambientPressure
Definition lsOxidationDeformation.hpp:21
std::size_t maxGridPoints
Definition lsOxidationDeformation.hpp:37
Parameters for the steady oxidant diffusion model used by OxidationDiffusion.
Definition lsOxidationDiffusion.hpp:45