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>, detail::IndexTypeHasher<D>>
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 detail::vecAddTo(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 detail::vecAddTo(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) {
1150 } else if (boundary == Boundary::MASK) {
1151 detail::vecAddTo(sum,
1152 maskVelocityBoundary(node.index, toT(v[nodeId])));
1153 } else {
1154 detail::vecAddTo(sum, toT(v[nodeId])); // AMBIENT/NONE: zero-flux
1155 }
1156 }
1157 }
1158 }
1159
1160 // (Av)[i] = (2*D) * v[i] - sum_at_v[i] + b[i]
1161 template <class SolverT>
1162 void harmonicMatvec(const std::vector<Vec3D<SolverT>> &v,
1163 const std::vector<Vec3D<T>> &b,
1164 std::vector<Vec3D<SolverT>> &Av) const {
1165 const T diagVal = static_cast<T>(2 * D);
1166#pragma omp parallel for schedule(static)
1167 for (std::size_t i = 0; i < nodes.size(); ++i) {
1168 Vec3D<T> sum;
1169 computeHarmonicStencilAt(i, v, sum);
1170 for (unsigned c = 0; c < D; ++c)
1171 Av[i][c] = static_cast<SolverT>(diagVal * v[i][c] - sum[c] + b[i][c]);
1172 }
1173 }
1174
1176 iterations = 0;
1177 residual = 0.;
1178 if (nodes.empty())
1179 return;
1180
1181 using SolverT = T;
1182
1183 const std::size_t n = nodes.size();
1184 const T diagVal = static_cast<T>(2 * D); // constant for all nodes
1185
1186 // b[i] = BC constants (reaction + mask velocities), computed at v = zeros.
1187 // OOB/NONE/AMBIENT faces contribute v[i] = 0 at zeros, so only Dirichlet
1188 // BCs survive — correctly isolating the RHS constant vector.
1189 std::vector<Vec3D<T>> b(n);
1190 {
1191 const std::vector<Vec3D<SolverT>> zeros(
1192 n, Vec3D<SolverT>{SolverT(0), SolverT(0), SolverT(0)});
1193#pragma omp parallel for schedule(static)
1194 for (std::size_t i = 0; i < n; ++i)
1195 computeHarmonicStencilAt(i, zeros, b[i]);
1196 }
1197
1198 // Warm-start from previous substep's velocity field.
1199 std::vector<Vec3D<SolverT>> x(n);
1200 for (std::size_t i = 0; i < n; ++i)
1201 for (unsigned c = 0; c < D; ++c) {
1202 const T value = nodes[i].velocity[c];
1203 x[i][c] = static_cast<SolverT>(std::isfinite(value) ? value : T(0));
1204 }
1205
1206#ifdef VIENNALS_GPU_BICGSTAB
1207 if (gpu::gpuIsValid(gpuHarmonicBufs_)) {
1208 const std::size_t nf = 2u * D * n;
1209 if (harmonicDiagGpu_.size() != n || harmonicCoeffGpu_.size() != nf) {
1210 VIENNACORE_LOG_ERROR("OxidationDeformation: harmonic GPU geometry has "
1211 "the wrong size for the current node set.");
1212 }
1213
1214 Timer<> tUpload, tSolve;
1215 std::vector<Vec3D<SolverT>> xSolved(n);
1216 unsigned maxGpuIterations = 0;
1217 double maxGpuResidual = 0.0;
1218
1219 for (unsigned c = 0; c < D; ++c) {
1220 std::vector<double> bGpu(n), xGpu(n);
1221 for (std::size_t i = 0; i < n; ++i) {
1222 bGpu[i] = static_cast<double>(b[i][c]);
1223 xGpu[i] = static_cast<double>(x[i][c]);
1224 }
1225
1226 tUpload.start();
1227 const bool gpuUploadOk =
1228 (c == 0) ? gpu::gpuUploadSolverArrays(
1229 gpuHarmonicBufs_, harmonicDiagGpu_.data(),
1230 bGpu.data(), harmonicCoeffGpu_.data(),
1231 static_cast<uint32_t>(n), harmonicCoeffGpu_.size())
1232 : gpu::gpuUploadRhs(gpuHarmonicBufs_, bGpu.data(),
1233 static_cast<uint32_t>(n));
1234 tUpload.finish();
1235 if (!gpuUploadOk) {
1236 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, "
1237 "but uploading harmonic solver arrays failed." +
1238 gpuErrorDetail());
1239 }
1240
1241 unsigned gpuIterations = 0;
1242 double gpuResidual = 0.0;
1243 tSolve.start();
1244 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
1245 gpuHarmonicBufs_, xGpu.data(),
1246 static_cast<double>(std::numeric_limits<SolverT>::epsilon()),
1247 deformationParameters.harmonicIterations,
1248 static_cast<double>(deformationParameters.tolerance), gpuIterations,
1249 gpuResidual);
1250 tSolve.finish();
1251
1252 if (!gpuConverged || !std::isfinite(gpuResidual)) {
1253 VIENNACORE_LOG_ERROR(
1254 "OxidationDeformation: harmonic GPU BiCGSTAB failed or produced "
1255 "a non-finite residual for component " +
1256 std::to_string(c) + " (iters=" + std::to_string(gpuIterations) +
1257 ", residual=" + std::to_string(gpuResidual) + ").");
1258 }
1259
1260 maxGpuIterations = std::max(maxGpuIterations, gpuIterations);
1261 maxGpuResidual = std::max(maxGpuResidual, gpuResidual);
1262 for (std::size_t i = 0; i < n; ++i)
1263 xSolved[i][c] = static_cast<SolverT>(xGpu[i]);
1264 }
1265
1266 for (std::size_t i = 0; i < n; ++i)
1267 for (unsigned c = 0; c < D; ++c)
1268 nodes[i].velocity[c] = static_cast<T>(xSolved[i][c]);
1269 iterations = maxGpuIterations;
1270 residual = maxGpuResidual;
1271
1272 if (Logger::hasTiming()) {
1273 Logger::getInstance()
1274 .addTiming("harmonic n=" + std::to_string(n) +
1275 " iters=" + std::to_string(iterations) + " res=" +
1276 std::to_string(residual) + " [GPU] GPU BiCGSTAB",
1277 tSolve)
1278 .print();
1279 }
1280 if (Logger::hasDebug()) {
1281 Logger::getInstance()
1282 .addTiming("harmonic n=" + std::to_string(n) + " [GPU] GPU upload",
1283 tUpload)
1284 .print();
1285 }
1286 return;
1287 }
1288#endif
1289
1290 // r = b - A*x
1291 const Vec3D<SolverT> zero3{SolverT(0), SolverT(0), SolverT(0)};
1292 std::vector<Vec3D<SolverT>> Ax(n);
1293 harmonicMatvec(x, b, Ax);
1294 std::vector<Vec3D<SolverT>> r(n), r_hat(n);
1295 for (std::size_t i = 0; i < n; ++i)
1296 for (unsigned c = 0; c < D; ++c) {
1297 r[i][c] = static_cast<SolverT>(b[i][c] - Ax[i][c]);
1298 r_hat[i][c] = r[i][c];
1299 }
1300
1301 // BiCGSTAB with diagonal preconditioner (diag = 2*D, constant).
1302 std::vector<Vec3D<SolverT>> pv(n, zero3), sv(n, zero3), y(n), z(n), s(n),
1303 t(n);
1304 T rho = T(1), alpha = T(1), omega = T(1);
1305
1306 auto vecDot = [&](const std::vector<Vec3D<SolverT>> &a,
1307 const std::vector<Vec3D<SolverT>> &bv) {
1308 T sum = T(0);
1309 for (std::size_t i = 0; i < n; ++i)
1310 for (unsigned c = 0; c < D; ++c)
1311 sum += static_cast<T>(a[i][c]) * static_cast<T>(bv[i][c]);
1312 return sum;
1313 };
1314
1315 auto vecMaxAbs = [&](const std::vector<Vec3D<SolverT>> &vin) {
1316 T m = T(0);
1317 for (std::size_t i = 0; i < n; ++i)
1318 for (unsigned c = 0; c < D; ++c)
1319 m = std::max(m, std::abs(static_cast<T>(vin[i][c])));
1320 return m;
1321 };
1322
1323 const T b_norm = [&] {
1324 T m = T(0);
1325 for (std::size_t i = 0; i < n; ++i)
1326 for (unsigned c = 0; c < D; ++c)
1327 m = std::max(m, std::abs(b[i][c]));
1328 return (m < T(1e-100)) ? T(1) : m;
1329 }();
1330
1331 for (; iterations < deformationParameters.harmonicIterations;
1332 ++iterations) {
1333 const T rho_new = vecDot(r_hat, r);
1334 if (!std::isfinite(rho_new) || std::abs(rho_new) < T(1e-100))
1335 break;
1336 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
1337 !std::isfinite(omega) || std::abs(omega) < T(1e-100))
1338 break;
1339
1340 const T beta = (rho_new / rho) * (alpha / omega);
1341 if (!std::isfinite(beta))
1342 break;
1343 rho = rho_new;
1344
1345 for (std::size_t i = 0; i < n; ++i)
1346 for (unsigned c = 0; c < D; ++c)
1347 pv[i][c] = static_cast<SolverT>(r[i][c] +
1348 beta * (pv[i][c] - omega * sv[i][c]));
1349
1350 // y = M^{-1} p = p / (2*D)
1351 for (std::size_t i = 0; i < n; ++i)
1352 for (unsigned c = 0; c < D; ++c)
1353 y[i][c] = static_cast<SolverT>(static_cast<T>(pv[i][c]) / diagVal);
1354
1355 harmonicMatvec(y, b, sv);
1356
1357 const T r_hat_v = vecDot(r_hat, sv);
1358 if (!std::isfinite(r_hat_v) || std::abs(r_hat_v) < T(1e-100))
1359 break;
1360
1361 alpha = rho_new / r_hat_v;
1362 if (!std::isfinite(alpha))
1363 break;
1364
1365 for (std::size_t i = 0; i < n; ++i)
1366 for (unsigned c = 0; c < D; ++c)
1367 s[i][c] = static_cast<SolverT>(r[i][c] - alpha * sv[i][c]);
1368
1369 residual = vecMaxAbs(s);
1370 if (!std::isfinite(residual))
1371 break;
1372 if (residual < deformationParameters.tolerance * b_norm) {
1373 for (std::size_t i = 0; i < n; ++i)
1374 for (unsigned c = 0; c < D; ++c)
1375 x[i][c] = static_cast<SolverT>(x[i][c] + alpha * y[i][c]);
1376 ++iterations;
1377 break;
1378 }
1379
1380 // z = M^{-1} s
1381 for (std::size_t i = 0; i < n; ++i)
1382 for (unsigned c = 0; c < D; ++c)
1383 z[i][c] = static_cast<SolverT>(static_cast<T>(s[i][c]) / diagVal);
1384
1385 harmonicMatvec(z, b, t);
1386
1387 const T t_s = vecDot(t, s);
1388 const T t_t = vecDot(t, t);
1389 if (!std::isfinite(t_s) || !std::isfinite(t_t))
1390 break;
1391 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
1392 if (!std::isfinite(omega))
1393 break;
1394
1395 for (std::size_t i = 0; i < n; ++i)
1396 for (unsigned c = 0; c < D; ++c) {
1397 x[i][c] =
1398 static_cast<SolverT>(x[i][c] + alpha * y[i][c] + omega * z[i][c]);
1399 r[i][c] = static_cast<SolverT>(s[i][c] - omega * t[i][c]);
1400 }
1401
1402 residual = vecMaxAbs(r);
1403 if (!std::isfinite(residual))
1404 break;
1405 if (residual < deformationParameters.tolerance * b_norm) {
1406 ++iterations;
1407 break;
1408 }
1409 }
1410
1411 bool finiteSolution = true;
1412 for (std::size_t i = 0; i < n; ++i)
1413 for (unsigned c = 0; c < D; ++c)
1414 if (!std::isfinite(static_cast<T>(x[i][c])))
1415 finiteSolution = false;
1416
1417 if (finiteSolution) {
1418 for (std::size_t i = 0; i < n; ++i)
1419 for (unsigned c = 0; c < D; ++c)
1420 nodes[i].velocity[c] = static_cast<T>(x[i][c]);
1421 } else {
1422 residual = std::numeric_limits<T>::infinity();
1423 }
1424 if (residual > deformationParameters.tolerance * b_norm)
1425 VIENNACORE_LOG_WARNING(
1426 "solveVelocity (harmonic): BiCGSTAB did not converge after " +
1427 std::to_string(iterations) + "/" +
1428 std::to_string(deformationParameters.harmonicIterations) +
1429 " iterations (residual=" + std::to_string(residual / b_norm) +
1430 ", tolerance=" + std::to_string(deformationParameters.tolerance) +
1431 ")");
1432 }
1433
1434 // Returns component-wise diagonal entries of the Stokes operator A_v.
1435 // Geometry-fixed within a mechanics solve; computed once and reused by the
1436 // SIMPLE velocity-correction step: v_c^{k+1}=v_c* - grad_c(dp)/(eta*a_ic).
1437 //
1438 // With traction-coupled MASK contact, ghost=v_node+const for every component,
1439 // so the MASK face self-coupling cancels and the face coefficient is removed.
1440 std::vector<Vec3D<T>> computeVelocityDiagonals() const {
1441 const std::size_t n = nodes.size();
1442 std::vector<Vec3D<T>> diagV(n, Vec3D<T>{T(0), T(0), T(0)});
1443 if (n == 0)
1444 return diagV;
1445 const std::vector<Vec3D<T>> zeros(n, Vec3D<T>{T(0), T(0), T(0)});
1446 std::vector<Vec3D<T>> tmp(n);
1447#pragma omp parallel for schedule(static)
1448 for (std::size_t i = 0; i < n; ++i) {
1449 T diag{};
1450 computeVelocityStencilAt(i, zeros, diag, tmp[i]);
1451 for (unsigned comp = 0; comp < D; ++comp)
1452 diagV[i][comp] = diag;
1453 }
1454
1455 return diagV;
1456 }
1457
1459 T mechanicsResidual = 0.;
1460
1461 // SIMPLE (Semi-Implicit Method for Pressure-Linked Equations) coupling.
1462 // The Gauss-Seidel p→v→p loop has spectral radius > 1 on thin geometries,
1463 // causing divergence that worsens with more iterations. SIMPLE adds a
1464 // velocity-correction step after the pressure update that provably
1465 // eliminates the oscillation mode:
1466 //
1467 // 1. Momentum predictor: A_v * v* = vBC - (∇p^k - ∇·σ'(v^k)) / η
1468 // 2. Pressure update: A_p * p^{k+1} = pBC + K · div(v*)
1469 // 3. Velocity correction: v^{k+1} = v* - ∇δp / (η · a_i)
1470 // where δp = p^{k+1} - p^k, a_i = diag(A_v)[i]
1471 //
1472 // Step 3 ensures the corrected velocity is consistent with the new
1473 // pressure without re-solving the full momentum equation. Unlike the
1474 // Aitken clamp (which can only damp, not stabilise, ρ > 1 iterations),
1475 // this correction is unconditionally convergent for steady Stokes.
1476
1477 const std::vector<Vec3D<T>> diagV =
1478 computeVelocityDiagonals(); // geometry-fixed within this call
1479
1480 for (unsigned iteration = 0;
1481 iteration < deformationParameters.mechanicsIterations; ++iteration) {
1482 const auto previousVelocity = collectVelocities(); // v^k
1483 const auto previousPressure = collectPressures(); // p^k
1484
1487
1488 // Step 1: momentum predictor uses current p^k (in nodes[i].pressure).
1489 Timer<> tStokes, tPressure;
1490 tStokes.start();
1491 solveStokesVelocity(); // produces v* in nodes[i].velocity
1492 tStokes.finish();
1493 if (!std::isfinite(lastStokesResidual_)) {
1494 mechanicsResidual = std::numeric_limits<T>::infinity();
1495 break;
1496 }
1497
1498 // Step 2: pressure solve uses divergence of v*.
1499 tPressure.start();
1500 solvePressure(); // produces p^{k+1} in nodes[i].pressure
1501 tPressure.finish();
1502 if (!std::isfinite(lastPressureResidual_)) {
1503 mechanicsResidual = std::numeric_limits<T>::infinity();
1504 break;
1505 }
1506
1507 // Step 3: SIMPLE velocity correction: v^{k+1} = v* - ∇δp / (η · a_i).
1508 applySimpleVelocityCorrection(previousPressure, diagV);
1509
1510 mechanicsResidual = std::max(maxVelocityChange(previousVelocity),
1511 maxPressureChange(previousPressure));
1512 if (!std::isfinite(mechanicsResidual)) {
1513 mechanicsResidual = std::numeric_limits<T>::infinity();
1514 break;
1515 }
1516
1517 if (Logger::hasDebug())
1518 Logger::getInstance()
1519 .addTiming(
1520 " mechanics[" + std::to_string(iteration) +
1521 "] stokes iters=" + std::to_string(lastStokesIters_) +
1522 "/" +
1523 std::to_string(deformationParameters.stokesIterations) +
1524 " res=" + std::to_string(lastStokesResidual_),
1525 tStokes)
1526 .addTiming(
1527 " mechanics[" + std::to_string(iteration) +
1528 "] pressure iters=" + std::to_string(lastPressureIters_) +
1529 "/" +
1530 std::to_string(deformationParameters.pressureIterations) +
1531 " res=" + std::to_string(lastPressureResidual_) +
1532 " coupling=" + std::to_string(mechanicsResidual),
1533 tPressure)
1534 .print();
1535
1536 if (mechanicsResidual < deformationParameters.mechanicsTolerance)
1537 break;
1538 }
1539
1542 residual = mechanicsResidual;
1543 if (residual > deformationParameters.mechanicsTolerance)
1544 VIENNACORE_LOG_WARNING(
1545 "solveMechanics: did not converge after " +
1546 std::to_string(deformationParameters.mechanicsIterations) +
1547 " iterations (residual=" + std::to_string(residual) + ", tolerance=" +
1548 std::to_string(deformationParameters.mechanicsTolerance) + ")");
1549 }
1550
1551 // SIMPLE velocity correction: v^{k+1} = v* - ∇(p^{k+1} - p^k) / (η · a_i)
1552 //
1553 // δp gradient uses homogeneous Neumann at all boundary faces (δp ghost = 0).
1554 // The boundary pressure correction is re-enforced by the next pressure solve,
1555 // so this approximation only affects the current-iteration correction, not
1556 // the converged solution.
1557 void applySimpleVelocityCorrection(const std::vector<T> &pressureOld,
1558 const std::vector<Vec3D<T>> &diagV) {
1559 if (nodes.empty())
1560 return;
1561 if (deformationParameters.viscosity <= std::numeric_limits<T>::epsilon())
1562 return;
1563
1564 const std::size_t n = nodes.size();
1565 const T invEta = T(1) / deformationParameters.viscosity;
1566
1567#pragma omp parallel for schedule(static)
1568 for (std::size_t i = 0; i < n; ++i) {
1569 for (unsigned dir = 0; dir < D; ++dir) {
1570 const T ai = diagV[i][dir];
1571 if (ai <= std::numeric_limits<T>::epsilon())
1572 continue;
1573
1574 // Negative-offset face (fi = dir*2).
1575 T dpMinus, dMinus;
1576 {
1577 const unsigned fi = dir * 2u;
1578 IndexType nb = nodes[i].index;
1579 nb[dir] -= 1;
1580 if (inBounds(nb)) {
1581 const std::size_t j = nodeLookupFlat[linearIndex(nb)];
1582 if (j != noNode) {
1583 dpMinus = nodes[j].pressure - pressureOld[j];
1584 dMinus = gridDelta;
1585 } else {
1586 dpMinus = T(0);
1587 dMinus = faceBCDists_[fi * n + i];
1588 }
1589 } else {
1590 dpMinus = T(0);
1591 dMinus = gridDelta;
1592 }
1593 }
1594
1595 // Positive-offset face (fi = dir*2+1).
1596 T dpPlus, dPlus;
1597 {
1598 const unsigned fi = dir * 2u + 1u;
1599 IndexType nb = nodes[i].index;
1600 nb[dir] += 1;
1601 if (inBounds(nb)) {
1602 const std::size_t j = nodeLookupFlat[linearIndex(nb)];
1603 if (j != noNode) {
1604 dpPlus = nodes[j].pressure - pressureOld[j];
1605 dPlus = gridDelta;
1606 } else {
1607 dpPlus = T(0);
1608 dPlus = faceBCDists_[fi * n + i];
1609 }
1610 } else {
1611 dpPlus = T(0);
1612 dPlus = gridDelta;
1613 }
1614 }
1615
1616 const T dpCenter = nodes[i].pressure - pressureOld[i];
1617 const T gradDP =
1618 firstDerivative(dpMinus, dpCenter, dpPlus, dMinus, dPlus);
1619 const T correction =
1620 gradDP * invEta / ai * deformationParameters.relaxation;
1621 if (std::isfinite(correction) && std::isfinite(nodes[i].velocity[dir]))
1622 nodes[i].velocity[dir] -= correction;
1623 }
1624 }
1625 }
1626
1627 // Fills diag = centerCoefficient and rhs = pressureSum for one node.
1628 // Dirichlet (ambient) nodes are encoded as identity rows: diag=1,
1629 // rhs=ambientBP.
1630 template <class SolverT>
1631 void computePressureStencilAt(std::size_t nodeId,
1632 const std::vector<SolverT> &p,
1633 const std::vector<T> &ambientBP, T &diag,
1634 T &rhs) const {
1635 if (touchesAmbient_[nodeId]) {
1636 diag = T(1);
1637 rhs = ambientBP[nodeId];
1638 return;
1639 }
1640 diag = T(0);
1641 rhs = T(0);
1642 for (unsigned direction = 0; direction < D; ++direction) {
1643 const auto plus =
1644 pressureStencilPoint(p, ambientBP, nodeId, direction, 1);
1645 const auto minus =
1646 pressureStencilPoint(p, ambientBP, nodeId, direction, -1);
1647 const T dSum = plus.distance + minus.distance;
1648 const T plusCoeff = T(2) / (plus.distance * dSum);
1649 const T minusCoeff = T(2) / (minus.distance * dSum);
1650 rhs += plusCoeff * plus.value + minusCoeff * minus.value;
1651 diag += plusCoeff + minusCoeff;
1652 }
1653 }
1654
1655 // (Av)[i] = precomputedDiag[i]*v[i] - rhs_at_v[i] + pBC[i]
1656 template <class SolverT>
1657 void
1658 pressureMatvec(const std::vector<SolverT> &v, const std::vector<T> &ambientBP,
1659 const std::vector<T> &precomputedDiag,
1660 const std::vector<T> &pBC, std::vector<SolverT> &Av) const {
1661#pragma omp parallel for schedule(static)
1662 for (std::size_t i = 0; i < nodes.size(); ++i) {
1663 T diag, rhs;
1664 computePressureStencilAt(i, v, ambientBP, diag, rhs);
1665 Av[i] = static_cast<SolverT>(precomputedDiag[i] * v[i] - rhs + pBC[i]);
1666 }
1667 }
1668
1670 if (nodes.empty())
1671 return;
1672
1673 using SolverT = T;
1674
1675 const std::size_t n = nodes.size();
1676 const T eps = std::numeric_limits<T>::epsilon();
1677
1678 std::vector<T> divergence(n), ambientBP(n);
1679#pragma omp parallel for schedule(static)
1680 for (std::size_t i = 0; i < n; ++i) {
1681 divergence[i] = divergenceAt(nodes[i].index);
1682 ambientBP[i] = freeSurfacePressureBoundary(nodes[i].index);
1683 }
1684
1685 auto warnBadPressureAssembly = [](const std::string &stage,
1686 std::size_t nodeId, const IndexType &idx,
1687 T value) {
1688 VIENNACORE_LOG_WARNING(
1689 "solvePressure: non-finite/overflow " + stage +
1690 " at node=" + std::to_string(nodeId) + " index=(" +
1691 std::to_string(idx[0]) + "," + std::to_string(idx[1]) +
1692 (D == 3 ? "," + std::to_string(idx[2]) : std::string()) +
1693 ") value=" + std::to_string(value));
1694 };
1695
1696 const T solverMax = static_cast<T>(std::numeric_limits<SolverT>::max());
1697 for (std::size_t i = 0; i < n; ++i) {
1698 if (!std::isfinite(divergence[i]) ||
1699 std::abs(divergence[i]) > solverMax) {
1700 warnBadPressureAssembly("divergence", i, nodes[i].index, divergence[i]);
1701 break;
1702 }
1703 if (!std::isfinite(ambientBP[i]) || std::abs(ambientBP[i]) > solverMax) {
1704 warnBadPressureAssembly("ambient pressure boundary", i, nodes[i].index,
1705 ambientBP[i]);
1706 break;
1707 }
1708 }
1709
1710 // Geometry-fixed diagonal and BC constants (kept in T for full precision).
1711 std::vector<T> diag(n), pBC(n);
1712 {
1713 const std::vector<SolverT> zeros(n, SolverT(0));
1714#pragma omp parallel for schedule(static)
1715 for (std::size_t i = 0; i < n; ++i)
1716 computePressureStencilAt(i, zeros, ambientBP, diag[i], pBC[i]);
1717 }
1718
1719 for (std::size_t i = 0; i < n; ++i) {
1720 if (!std::isfinite(diag[i]) || std::abs(diag[i]) > solverMax) {
1721 warnBadPressureAssembly("pressure diagonal", i, nodes[i].index,
1722 diag[i]);
1723 break;
1724 }
1725 if (!std::isfinite(pBC[i]) || std::abs(pBC[i]) > solverMax) {
1726 warnBadPressureAssembly("pressure boundary rhs", i, nodes[i].index,
1727 pBC[i]);
1728 break;
1729 }
1730 }
1731
1732 std::vector<T> b(n);
1733 T b_norm = T(0);
1734 for (std::size_t i = 0; i < n; ++i) {
1735 b[i] = pBC[i] + deformationParameters.bulkModulus * divergence[i];
1736 b_norm = std::max(b_norm, std::abs(b[i]));
1737 }
1738 for (std::size_t i = 0; i < n; ++i) {
1739 if (!std::isfinite(b[i]) || std::abs(b[i]) > solverMax) {
1740 warnBadPressureAssembly("pressure rhs", i, nodes[i].index, b[i]);
1741 break;
1742 }
1743 }
1744 if (b_norm < T(1e-100))
1745 b_norm = T(1);
1746
1747 std::vector<SolverT> x(n);
1748 for (std::size_t i = 0; i < n; ++i) {
1749 T guess = touchesAmbient_[i] ? ambientBP[i] : nodes[i].pressure;
1750 if (!std::isfinite(guess))
1751 guess = deformationParameters.ambientPressure;
1752 x[i] = static_cast<SolverT>(guess);
1753 }
1754
1755#ifdef VIENNALS_GPU_BICGSTAB
1756 if (gpu::gpuIsValid(gpuPressBufs_)) {
1757 const std::size_t nf = 2u * D * n;
1758 if (actualDiagGpu_.size() != n || pressCoeffGpu_.size() != nf) {
1759 VIENNACORE_LOG_ERROR("OxidationDeformation: pressure GPU geometry has "
1760 "the wrong size for the current node set.");
1761 }
1762
1763 Timer<> tUpload, tSolve;
1764 std::vector<double> bGpu(n), xGpu(n);
1765 for (std::size_t i = 0; i < n; ++i) {
1766 bGpu[i] = static_cast<double>(b[i]);
1767 xGpu[i] = static_cast<double>(x[i]);
1768 }
1769
1770 tUpload.start();
1771 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
1772 gpuPressBufs_, actualDiagGpu_.data(), bGpu.data(),
1773 pressCoeffGpu_.data(), static_cast<uint32_t>(n),
1774 pressCoeffGpu_.size());
1775 tUpload.finish();
1776 if (!gpuUploadOk) {
1777 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, but "
1778 "uploading pressure solver arrays or factorizing "
1779 "ILU failed." +
1780 gpuErrorDetail());
1781 }
1782
1783 unsigned gpuIterations = 0;
1784 double gpuResidual = 0.0;
1785 tSolve.start();
1786 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
1787 gpuPressBufs_, xGpu.data(), static_cast<double>(eps),
1788 deformationParameters.pressureIterations,
1789 static_cast<double>(deformationParameters.pressureTolerance),
1790 gpuIterations, gpuResidual);
1791 tSolve.finish();
1792
1793 // gpuResidual is the GPU true residual ||b - A*x||_inf recomputed at
1794 // convergence (not the recursive BiCGSTAB residual), so no separate CPU
1795 // stencil evaluation is needed.
1796 if (!gpuConverged || !std::isfinite(gpuResidual)) {
1797 VIENNACORE_LOG_ERROR(
1798 "OxidationDeformation: pressure GPU BiCGSTAB failed or produced "
1799 "a non-finite residual (iters=" +
1800 std::to_string(gpuIterations) +
1801 ", residual=" + std::to_string(gpuResidual) + ").");
1802 }
1803
1804 {
1805 const T beta = deformationParameters.pressureRelaxation;
1806 const T oneMinB = T(1) - beta;
1807 for (std::size_t i = 0; i < n; ++i)
1808 nodes[i].pressure =
1809 oneMinB * nodes[i].pressure + beta * static_cast<T>(xGpu[i]);
1810 }
1811 lastPressureIters_ = gpuIterations;
1812 lastPressureResidual_ = gpuResidual / b_norm;
1813
1814 if (Logger::hasDebug()) {
1815 const std::string tag =
1816 "pressure n=" + std::to_string(n) +
1817 " iters=" + std::to_string(lastPressureIters_) +
1818 " res=" + std::to_string(lastPressureResidual_) + " [GPU]";
1819 Logger::getInstance()
1820 .addTiming(tag + " GPU upload", tUpload)
1821 .addTiming(tag + " GPU BiCGSTAB", tSolve)
1822 .print();
1823 }
1824 return;
1825 }
1826#endif
1827
1828 // Precompute off-diagonal structure and the CORRECT matrix diagonal for
1829 // SSOR.
1830 //
1831 // Key insight: diag[i] from computePressureStencilAt includes self-coupling
1832 // contributions from REACTION/MASK/OOB faces (those return v[nodeId]
1833 // itself). The ACTUAL matrix diagonal A[i,i] = sum of off-diagonal
1834 // (interior-neighbor) coefficients only. Using the wrong diagonal in the
1835 // SSOR sweeps makes the preconditioner invalid near boundaries.
1836 //
1837 // Also: NONE-type non-interior faces (OOB or no crossing) use gridDelta in
1838 // pressureStencilPoint, NOT faceBCDists_ (which defaults to T(1)).
1839 //
1840 // Face-major layout: fi = dir*2 + (offset==+1 ? 1 : 0)
1841 // Even fi (offset=-1): lower-index neighbor → forward sweep
1842 // Odd fi (offset=+1): higher-index neighbor → backward sweep
1843 std::vector<T> pressCoeff(2 * D * n, T(0));
1844 std::vector<std::size_t> pressNeighId(2 * D * n, noNode);
1845 std::vector<T> actualDiag(n, T(0)); // A[i,i] = sum of interior coefficients
1846
1847 for (std::size_t id = 0; id < n; ++id) {
1848 if (touchesAmbient_[id]) {
1849 actualDiag[id] = T(1);
1850 continue;
1851 } // identity row
1852 for (unsigned dir = 0; dir < D; ++dir) {
1853 const unsigned fiNeg = dir * 2u;
1854 const unsigned fiPos = dir * 2u + 1u;
1855 IndexType nbNeg = nodes[id].index;
1856 nbNeg[dir] -= 1;
1857 IndexType nbPos = nodes[id].index;
1858 nbPos[dir] += 1;
1859 const std::size_t jNeg =
1860 inBounds(nbNeg) ? nodeLookupFlat[linearIndex(nbNeg)] : noNode;
1861 const std::size_t jPos =
1862 inBounds(nbPos) ? nodeLookupFlat[linearIndex(nbPos)] : noNode;
1863
1864 // Effective distance matching pressureStencilPoint:
1865 // interior neighbour → gridDelta
1866 // AMBIENT/REACTION/MASK crossing → faceBCDists_ (actual sub-grid
1867 // distance) NONE (OOB or no crossing) → gridDelta
1868 // (pressureStencilPoint fallthrough)
1869 auto effectiveDist = [&](unsigned fi, std::size_t j) -> T {
1870 if (j != noNode)
1871 return gridDelta;
1872 const Boundary bt = faceBCTypes_[fi * n + id];
1873 if (bt != Boundary::NONE)
1874 return faceBCDists_[fi * n + id];
1875 return gridDelta;
1876 };
1877
1878 const T dNeg = effectiveDist(fiNeg, jNeg);
1879 const T dPos = effectiveDist(fiPos, jPos);
1880 const T dSum = dNeg + dPos;
1881 if (dSum <= eps)
1882 continue;
1883
1884 if (jNeg != noNode && !touchesAmbient_[jNeg]) {
1885 const T c = T(2) / (dNeg * dSum);
1886 pressCoeff[fiNeg * n + id] = c;
1887 pressNeighId[fiNeg * n + id] = jNeg;
1888 actualDiag[id] += c; // A[i,i] += interior off-diagonal coefficient
1889 } else if (jNeg != noNode ||
1890 faceBCTypes_[fiNeg * n + id] == Boundary::AMBIENT) {
1891 // j is an ambient-only neighbour (identity-row Dirichlet p=0), OR
1892 // this face directly crosses the free surface (AMBIENT Dirichlet p=0
1893 // at the sub-grid crossing distance). RHS contribution is c·0=0.
1894 // REACTION faces are solid-wall Neumann ∂p/∂n=0: no contribution.
1895 actualDiag[id] += T(2) / (dNeg * dSum);
1896 }
1897 if (jPos != noNode && !touchesAmbient_[jPos]) {
1898 const T c = T(2) / (dPos * dSum);
1899 pressCoeff[fiPos * n + id] = c;
1900 pressNeighId[fiPos * n + id] = jPos;
1901 actualDiag[id] += c;
1902 } else if (jPos != noNode ||
1903 faceBCTypes_[fiPos * n + id] == Boundary::AMBIENT) {
1904 actualDiag[id] += T(2) / (dPos * dSum);
1905 }
1906 }
1907 // Guard against fully-isolated nodes (surrounded by boundaries on every
1908 // face)
1909 if (actualDiag[id] <= eps)
1910 actualDiag[id] = T(1);
1911 }
1912
1913 // ILU(0) preconditioner for the (non-symmetric) pressure matrix.
1914 //
1915 // The sub-grid interface distances make A[i,j] ≠ A[j,i] in general, so
1916 // SSOR is not guaranteed to converge. ILU(0) handles non-symmetric
1917 // matrices robustly.
1918 //
1919 // Factorisation A ≈ L * U (zero fill-in, natural node ordering):
1920 // L – unit lower triangular: L[i,j] = A[i,j] / U[j,j] for j < i
1921 // U – upper triangular: U[i,j] = A[i,j] for j > i
1922 // U[i,i] = A[i,i] - Σ_{k<i} L[i,k] * A[k,i]
1923 //
1924 // With A[i,j] = -pressCoeff[fi*n+i] and A[j,i] = -pressCoeff[(fi^1)*n+j]:
1925 // U[i,i] = actualDiag[i] - Σ_{lower j} pressCoeff[fi_L*n+i]
1926 // * pressCoeff[fi_U*n+j]
1927 // / ilu_diag[j]
1928 //
1929 // Preconditioner application M_ILU^{-1} r = z:
1930 // Forward (L y = r, unit lower triangular, no diagonal divide):
1931 // y[i] = r[i] + Σ_{j<i} (pressCoeff[fi_L*n+i] / ilu_diag[j]) * y[j]
1932 // Backward (U z = y):
1933 // z[i] = (y[i] + Σ_{j>i} pressCoeff[fi_U*n+i] * z[j]) / ilu_diag[i]
1934 std::vector<T> ilu_diag(n);
1935 for (std::size_t id = 0; id < n; ++id) {
1936 if (touchesAmbient_[id]) {
1937 ilu_diag[id] = T(1);
1938 continue;
1939 }
1940 ilu_diag[id] = actualDiag[id];
1941 for (unsigned dir = 0; dir < D; ++dir) {
1942 const unsigned fi_L = dir * 2u; // lower face (offset=-1)
1943 const unsigned fi_U =
1944 fi_L + 1u; // upper face (offset=+1, j's face toward i)
1945 const std::size_t j = pressNeighId[fi_L * n + id];
1946 if (j == noNode || ilu_diag[j] <= eps)
1947 continue;
1948 // L[id,j] = A[id,j] / U[j,j] = (-pressCoeff_L) / ilu_diag[j]
1949 // A[j,id] = -pressCoeff[fi_U * n + j] (j's upper-face coefficient
1950 // toward id) ilu_diag[id] -= L[id,j] * A[j,id]
1951 // = (-pressCoeff_L / ilu_diag[j]) * (-pressCoeff_fi_U[j])
1952 // = pressCoeff_L * pressCoeff_fi_U[j] / ilu_diag[j]
1953 // (positive drop)
1954 ilu_diag[id] -=
1955 pressCoeff[fi_L * n + id] * pressCoeff[fi_U * n + j] / ilu_diag[j];
1956 }
1957 if (ilu_diag[id] <= eps)
1958 ilu_diag[id] = actualDiag[id]; // guard non-positive pivot
1959 }
1960
1961 auto applyIlu = [&](const std::vector<SolverT> &in,
1962 std::vector<SolverT> &out) {
1963 std::vector<T> y(n);
1964 // Forward solve: L * y = in (L is unit lower triangular)
1965 for (std::size_t i = 0; i < n; ++i) {
1966 T val = static_cast<T>(in[i]);
1967 for (unsigned dir = 0; dir < D; ++dir) {
1968 const unsigned fi_L = dir * 2u;
1969 const std::size_t j = pressNeighId[fi_L * n + i];
1970 if (j != noNode)
1971 // L[i,j] = -pressCoeff[fi_L*n+i] / ilu_diag[j], subtract
1972 // A[i,j]*y[j]: y[i] -= L[i,j] * y[j] = -(-pressCoeff/ilu_diag[j]) *
1973 // y[j] = +(coeff/ilu) * y[j]
1974 val += (pressCoeff[fi_L * n + i] / ilu_diag[j]) * y[j];
1975 }
1976 y[i] = val; // no diagonal divide (unit lower triangular)
1977 }
1978 // Backward solve: U * z = y
1979 for (std::size_t i = n; i-- > 0;) {
1980 T val = y[i];
1981 for (unsigned dir = 0; dir < D; ++dir) {
1982 const unsigned fi_U = dir * 2u + 1u;
1983 const std::size_t j = pressNeighId[fi_U * n + i];
1984 if (j != noNode)
1985 // U[i,j] = -pressCoeff[fi_U*n+i], subtract U[i,j]*z[j]:
1986 // val -= U[i,j] * z[j] = -(-pressCoeff) * z[j] = +(pressCoeff) *
1987 // z[j]
1988 val += pressCoeff[fi_U * n + i] * static_cast<T>(out[j]);
1989 }
1990 out[i] = static_cast<SolverT>(val / ilu_diag[i]);
1991 }
1992 };
1993
1994 std::vector<SolverT> Ax(n);
1995 pressureMatvec(x, ambientBP, diag, pBC, Ax);
1996 for (std::size_t i = 0; i < n; ++i) {
1997 if (!std::isfinite(static_cast<T>(Ax[i])) ||
1998 std::abs(static_cast<T>(Ax[i])) > solverMax) {
1999 warnBadPressureAssembly("initial pressure matvec", i, nodes[i].index,
2000 static_cast<T>(Ax[i]));
2001 break;
2002 }
2003 }
2004 std::vector<SolverT> r(n), r_hat(n), p(n, SolverT(0)), v(n, SolverT(0)),
2005 y(n), z(n), s(n), t(n);
2006 for (std::size_t i = 0; i < n; ++i) {
2007 r[i] = static_cast<SolverT>(b[i] - Ax[i]);
2008 r_hat[i] = r[i];
2009 }
2010
2011 T rho = T(1), alpha = T(1), omega = T(1);
2012 T pressureResidual = T(0);
2013 unsigned pressureIter = 0;
2014 bool pressureBreakdown = false;
2015 for (std::size_t i = 0; i < n; ++i) {
2016 const T ri = static_cast<T>(r[i]);
2017 if (!std::isfinite(ri)) {
2018 pressureBreakdown = true;
2019 break;
2020 }
2021 pressureResidual = std::max(pressureResidual, std::abs(ri));
2022 }
2023
2024 for (; !pressureBreakdown &&
2025 pressureIter < deformationParameters.pressureIterations;
2026 ++pressureIter) {
2027 T rho_new = T(0);
2028 for (std::size_t i = 0; i < n; ++i)
2029 rho_new += static_cast<T>(r_hat[i]) * static_cast<T>(r[i]);
2030
2031 if (!std::isfinite(rho_new)) {
2032 pressureBreakdown = true;
2033 break;
2034 }
2035 if (std::abs(rho_new) < T(1e-100))
2036 break;
2037 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
2038 !std::isfinite(omega) || std::abs(omega) < T(1e-100)) {
2039 pressureBreakdown = true;
2040 break;
2041 }
2042
2043 const T beta = (rho_new / rho) * (alpha / omega);
2044 if (!std::isfinite(beta)) {
2045 pressureBreakdown = true;
2046 break;
2047 }
2048 rho = rho_new;
2049
2050 for (std::size_t i = 0; i < n; ++i)
2051 p[i] = static_cast<SolverT>(r[i] + beta * (p[i] - omega * v[i]));
2052
2053 applyIlu(p, y);
2054
2055 pressureMatvec(y, ambientBP, diag, pBC, v);
2056
2057 T r_hat_v = T(0);
2058 for (std::size_t i = 0; i < n; ++i)
2059 r_hat_v += static_cast<T>(r_hat[i]) * static_cast<T>(v[i]);
2060 if (!std::isfinite(r_hat_v)) {
2061 pressureBreakdown = true;
2062 break;
2063 }
2064 if (std::abs(r_hat_v) < T(1e-100))
2065 break;
2066
2067 alpha = rho_new / r_hat_v;
2068 if (!std::isfinite(alpha)) {
2069 pressureBreakdown = true;
2070 break;
2071 }
2072
2073 for (std::size_t i = 0; i < n; ++i)
2074 s[i] = static_cast<SolverT>(r[i] - alpha * v[i]);
2075
2076 pressureResidual = T(0);
2077 for (std::size_t i = 0; i < n; ++i)
2078 pressureResidual =
2079 std::max(pressureResidual, std::abs(static_cast<T>(s[i])));
2080 if (!std::isfinite(pressureResidual)) {
2081 pressureBreakdown = true;
2082 break;
2083 }
2084 if (pressureResidual < deformationParameters.pressureTolerance * b_norm) {
2085 for (std::size_t i = 0; i < n; ++i)
2086 x[i] = static_cast<SolverT>(x[i] + alpha * y[i]);
2087 break;
2088 }
2089
2090 applyIlu(s, z);
2091
2092 pressureMatvec(z, ambientBP, diag, pBC, t);
2093
2094 T t_s = T(0), t_t = T(0);
2095 for (std::size_t i = 0; i < n; ++i) {
2096 t_s += static_cast<T>(t[i]) * static_cast<T>(s[i]);
2097 t_t += static_cast<T>(t[i]) * static_cast<T>(t[i]);
2098 }
2099 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
2100 pressureBreakdown = true;
2101 break;
2102 }
2103 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
2104 if (!std::isfinite(omega)) {
2105 pressureBreakdown = true;
2106 break;
2107 }
2108
2109 for (std::size_t i = 0; i < n; ++i) {
2110 x[i] = static_cast<SolverT>(x[i] + alpha * y[i] + omega * z[i]);
2111 r[i] = static_cast<SolverT>(s[i] - omega * t[i]);
2112 }
2113
2114 pressureResidual = T(0);
2115 for (std::size_t i = 0; i < n; ++i)
2116 pressureResidual =
2117 std::max(pressureResidual, std::abs(static_cast<T>(r[i])));
2118 if (!std::isfinite(pressureResidual)) {
2119 pressureBreakdown = true;
2120 break;
2121 }
2122 if (pressureResidual < deformationParameters.pressureTolerance * b_norm)
2123 break;
2124 }
2125
2126 if (pressureBreakdown)
2127 pressureResidual = std::numeric_limits<T>::infinity();
2128
2129 bool finiteSolution = !pressureBreakdown;
2130 for (std::size_t i = 0; i < n; ++i)
2131 if (!std::isfinite(static_cast<T>(x[i])))
2132 finiteSolution = false;
2133
2134 lastPressureIters_ = pressureIter;
2135 lastPressureResidual_ = pressureResidual / b_norm;
2136 if (finiteSolution) {
2137 const T beta = deformationParameters.pressureRelaxation;
2138 const T oneMinB = T(1) - beta;
2139 for (std::size_t i = 0; i < n; ++i)
2140 nodes[i].pressure =
2141 oneMinB * nodes[i].pressure + beta * static_cast<T>(x[i]);
2142 } else {
2143 lastPressureResidual_ = std::numeric_limits<T>::infinity();
2144 }
2145 if (lastPressureResidual_ > deformationParameters.pressureTolerance)
2146 VIENNACORE_LOG_WARNING(
2147 "solvePressure: BiCGSTAB did not converge after " +
2148 std::to_string(lastPressureIters_) + "/" +
2149 std::to_string(deformationParameters.pressureIterations) +
2150 " iterations (residual=" + std::to_string(lastPressureResidual_) +
2151 ", tolerance=" +
2152 std::to_string(deformationParameters.pressureTolerance) + ")");
2153 }
2154
2155 // Fills scalar diag = centerCoefficient and Vec3D rhs = velocitySum for one
2156 // node.
2157 template <class SolverT>
2158 void computeVelocityStencilAt(std::size_t nodeId,
2159 const std::vector<Vec3D<SolverT>> &v, T &diag,
2160 Vec3D<T> &rhs) const {
2161 diag = T(0);
2162 rhs = {T(0), T(0), T(0)};
2163 for (unsigned direction = 0; direction < D; ++direction) {
2164 const auto plus = velocityStencilPoint(v, nodeId, direction, 1);
2165 const auto minus = velocityStencilPoint(v, nodeId, direction, -1);
2166 const T dSum = plus.distance + minus.distance;
2167 const T plusCoeff = T(2) / (plus.distance * dSum);
2168 const T minusCoeff = T(2) / (minus.distance * dSum);
2169 detail::vecAddTo(rhs, detail::vecScaled(plus.value, plusCoeff));
2170 detail::vecAddTo(rhs, detail::vecScaled(minus.value, minusCoeff));
2171 diag += plusCoeff + minusCoeff;
2172 }
2173 }
2174
2176 if (deformationParameters.viscosity <= std::numeric_limits<T>::epsilon())
2177 return;
2178 if (nodes.empty())
2179 return;
2180
2181 using SolverT = T;
2182
2183 const std::size_t n = nodes.size();
2184 const T eps = std::numeric_limits<T>::epsilon();
2185
2186 // Geometry-fixed diagonal, BC constants, and forcing (all in T).
2187 std::vector<T> diag(n);
2188 std::vector<Vec3D<T>> vBC(n), forcing(n);
2189 {
2190 const std::vector<Vec3D<SolverT>> zeros(
2191 n, Vec3D<SolverT>{SolverT(0), SolverT(0), SolverT(0)});
2192#pragma omp parallel for schedule(static)
2193 for (std::size_t i = 0; i < n; ++i) {
2194 computeVelocityStencilAt(i, zeros, diag[i], vBC[i]);
2195 forcing[i] = momentumForcing(nodes[i].index);
2196 }
2197 }
2198 const auto precondDiag = computeVelocityDiagonals();
2199
2200 std::vector<Vec3D<T>> b(n);
2201 T b_norm = T(0);
2202 for (std::size_t i = 0; i < n; ++i) {
2203 for (unsigned c = 0; c < D; ++c) {
2204 b[i][c] = vBC[i][c] - forcing[i][c] / deformationParameters.viscosity;
2205 b_norm = std::max(b_norm, std::abs(b[i][c]));
2206 }
2207 }
2208 if (b_norm < T(1e-100))
2209 b_norm = T(1);
2210
2211 // Initial guess from current node velocities (warm-start), converted to
2212 // SolverT.
2213 std::vector<Vec3D<SolverT>> x(n);
2214 {
2215 const auto vel = collectVelocities();
2216 for (std::size_t i = 0; i < n; ++i)
2217 for (unsigned c = 0; c < D; ++c) {
2218 const T value = vel[i][c];
2219 x[i][c] = static_cast<SolverT>(std::isfinite(value) ? value : T(0));
2220 }
2221 }
2222
2223#ifdef VIENNALS_GPU_BICGSTAB
2224 if (gpu::gpuIsValid(gpuStokesBufs_)) {
2225 const std::size_t nf = 2u * D * n;
2226 if (stokesDiagGpu_.size() != D * n || stokesCoeffGpu_.size() != nf) {
2227 VIENNACORE_LOG_ERROR("OxidationDeformation: Stokes GPU geometry has "
2228 "the wrong size for the current node set.");
2229 }
2230
2231 Timer<> tUpload, tSolve;
2232 std::vector<Vec3D<SolverT>> xSolved(n);
2233 unsigned maxGpuIterations = 0;
2234 double maxGpuResidual = 0.0;
2235
2236 for (unsigned c = 0; c < D; ++c) {
2237 std::vector<double> bGpu(n), xGpu(n);
2238 for (std::size_t i = 0; i < n; ++i) {
2239 bGpu[i] = static_cast<double>(b[i][c]);
2240 xGpu[i] = static_cast<double>(x[i][c]);
2241 }
2242
2243 tUpload.start();
2244 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
2245 gpuStokesBufs_, stokesDiagGpu_.data() + c * n, bGpu.data(),
2246 stokesCoeffGpu_.data(), static_cast<uint32_t>(n),
2247 stokesCoeffGpu_.size());
2248 tUpload.finish();
2249 if (!gpuUploadOk) {
2250 VIENNACORE_LOG_ERROR("OxidationDeformation: GPU mode was selected, "
2251 "but uploading Stokes solver arrays failed." +
2252 gpuErrorDetail());
2253 }
2254
2255 unsigned gpuIterations = 0;
2256 double gpuResidual = 0.0;
2257 tSolve.start();
2258 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
2259 gpuStokesBufs_, xGpu.data(), static_cast<double>(eps),
2260 deformationParameters.stokesIterations,
2261 static_cast<double>(deformationParameters.stokesTolerance),
2262 gpuIterations, gpuResidual);
2263 tSolve.finish();
2264
2265 if (!gpuConverged || !std::isfinite(gpuResidual)) {
2266 VIENNACORE_LOG_ERROR(
2267 "OxidationDeformation: Stokes GPU BiCGSTAB failed or produced "
2268 "a non-finite residual for component " +
2269 std::to_string(c) + " (iters=" + std::to_string(gpuIterations) +
2270 ", residual=" + std::to_string(gpuResidual) + ").");
2271 }
2272
2273 maxGpuIterations = std::max(maxGpuIterations, gpuIterations);
2274 maxGpuResidual = std::max(maxGpuResidual, gpuResidual);
2275 for (std::size_t i = 0; i < n; ++i)
2276 xSolved[i][c] = static_cast<SolverT>(xGpu[i]);
2277 }
2278
2279 for (std::size_t i = 0; i < n; ++i)
2280 for (unsigned c = 0; c < D; ++c)
2281 nodes[i].velocity[c] = static_cast<T>(xSolved[i][c]);
2282
2283 lastStokesIters_ = maxGpuIterations;
2284 lastStokesResidual_ = maxGpuResidual / b_norm;
2285
2286 if (Logger::hasDebug()) {
2287 const std::string tag = "stokes n=" + std::to_string(n) +
2288 " iters=" + std::to_string(lastStokesIters_) +
2289 " res=" + std::to_string(lastStokesResidual_) +
2290 " [GPU]";
2291 Logger::getInstance()
2292 .addTiming(tag + " GPU upload", tUpload)
2293 .addTiming(tag + " GPU BiCGSTAB", tSolve)
2294 .print();
2295 }
2296 return;
2297 }
2298#endif
2299
2300 // Stokes SpMV: (Av)[i] = diag[i]*vin[i] - rhs_at_vin[i] + vBC[i], stored as
2301 // SolverT.
2302 auto stokesMatvec = [&](const std::vector<Vec3D<SolverT>> &vin,
2303 std::vector<Vec3D<SolverT>> &Av) {
2304#pragma omp parallel for schedule(static)
2305 for (std::size_t i = 0; i < n; ++i) {
2306 T d;
2307 Vec3D<T> rhs;
2308 computeVelocityStencilAt(i, vin, d, rhs);
2309 for (unsigned c = 0; c < D; ++c)
2310 Av[i][c] =
2311 static_cast<SolverT>(diag[i] * vin[i][c] - rhs[c] + vBC[i][c]);
2312 }
2313 };
2314
2315 // Dot product accumulated in T for numerical stability.
2316 auto vecDot = [&](const std::vector<Vec3D<SolverT>> &a,
2317 const std::vector<Vec3D<SolverT>> &bv) {
2318 T sum = T(0);
2319 for (std::size_t i = 0; i < n; ++i)
2320 for (unsigned c = 0; c < D; ++c) {
2321 const T av = static_cast<T>(a[i][c]);
2322 const T bvVal = static_cast<T>(bv[i][c]);
2323 if (!std::isfinite(av) || !std::isfinite(bvVal))
2324 return std::numeric_limits<T>::quiet_NaN();
2325 sum += av * bvVal;
2326 }
2327 return sum;
2328 };
2329
2330 auto vecMaxAbs = [&](const std::vector<Vec3D<SolverT>> &vin) {
2331 T m = T(0);
2332 for (std::size_t i = 0; i < n; ++i)
2333 for (unsigned c = 0; c < D; ++c) {
2334 const T value = static_cast<T>(vin[i][c]);
2335 if (!std::isfinite(value))
2336 return std::numeric_limits<T>::infinity();
2337 m = std::max(m, std::abs(value));
2338 }
2339 return m;
2340 };
2341
2342 // r = b - A*x
2343 const Vec3D<SolverT> zero3{SolverT(0), SolverT(0), SolverT(0)};
2344 std::vector<Vec3D<SolverT>> Ax(n), r(n), r_hat(n);
2345 std::vector<Vec3D<SolverT>> pv(n, zero3), sv(n, zero3), y(n), z(n), s(n),
2346 t(n);
2347 stokesMatvec(x, Ax);
2348 for (std::size_t i = 0; i < n; ++i)
2349 for (unsigned c = 0; c < D; ++c) {
2350 r[i][c] = static_cast<SolverT>(b[i][c] - Ax[i][c]);
2351 r_hat[i][c] = r[i][c];
2352 }
2353
2354 T rho = T(1), alpha = T(1), omega = T(1);
2355 T velocityResidual = T(0);
2356 unsigned stokesIter = 0;
2357 bool stokesBreakdown = false;
2358 velocityResidual = vecMaxAbs(r);
2359
2360 for (; stokesIter < deformationParameters.stokesIterations; ++stokesIter) {
2361 const T rho_new = vecDot(r_hat, r);
2362 if (!std::isfinite(rho_new)) {
2363 stokesBreakdown = true;
2364 break;
2365 }
2366 if (std::abs(rho_new) < T(1e-100))
2367 break;
2368 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
2369 !std::isfinite(omega) || std::abs(omega) < T(1e-100)) {
2370 stokesBreakdown = true;
2371 break;
2372 }
2373
2374 const T beta = (rho_new / rho) * (alpha / omega);
2375 if (!std::isfinite(beta)) {
2376 stokesBreakdown = true;
2377 break;
2378 }
2379 rho = rho_new;
2380
2381 for (std::size_t i = 0; i < n; ++i)
2382 for (unsigned c = 0; c < D; ++c)
2383 pv[i][c] = static_cast<SolverT>(r[i][c] +
2384 beta * (pv[i][c] - omega * sv[i][c]));
2385
2386 for (std::size_t i = 0; i < n; ++i)
2387 for (unsigned c = 0; c < D; ++c) {
2388 const T pvc = pv[i][c];
2389 const T pcDiag = precondDiag[i][c];
2390 y[i][c] = static_cast<SolverT>((pcDiag > eps) ? pvc / pcDiag : pvc);
2391 }
2392
2393 stokesMatvec(y, sv);
2394
2395 const T r_hat_v = vecDot(r_hat, sv);
2396 if (!std::isfinite(r_hat_v)) {
2397 stokesBreakdown = true;
2398 break;
2399 }
2400 if (std::abs(r_hat_v) < T(1e-100))
2401 break;
2402
2403 alpha = rho_new / r_hat_v;
2404 if (!std::isfinite(alpha)) {
2405 stokesBreakdown = true;
2406 break;
2407 }
2408
2409 for (std::size_t i = 0; i < n; ++i)
2410 for (unsigned c = 0; c < D; ++c)
2411 s[i][c] = static_cast<SolverT>(r[i][c] - alpha * sv[i][c]);
2412
2413 velocityResidual = vecMaxAbs(s);
2414 if (!std::isfinite(velocityResidual)) {
2415 stokesBreakdown = true;
2416 break;
2417 }
2418 if (velocityResidual < deformationParameters.stokesTolerance * b_norm) {
2419 for (std::size_t i = 0; i < n; ++i)
2420 for (unsigned c = 0; c < D; ++c)
2421 x[i][c] = static_cast<SolverT>(x[i][c] + alpha * y[i][c]);
2422 break;
2423 }
2424
2425 for (std::size_t i = 0; i < n; ++i)
2426 for (unsigned c = 0; c < D; ++c) {
2427 const T sc = s[i][c];
2428 const T pcDiag = precondDiag[i][c];
2429 z[i][c] = static_cast<SolverT>((pcDiag > eps) ? sc / pcDiag : sc);
2430 }
2431
2432 stokesMatvec(z, t);
2433
2434 const T t_s = vecDot(t, s);
2435 const T t_t = vecDot(t, t);
2436 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
2437 stokesBreakdown = true;
2438 break;
2439 }
2440 omega = (t_t > T(1e-100)) ? t_s / t_t : T(0);
2441 if (!std::isfinite(omega)) {
2442 stokesBreakdown = true;
2443 break;
2444 }
2445
2446 for (std::size_t i = 0; i < n; ++i)
2447 for (unsigned c = 0; c < D; ++c) {
2448 x[i][c] =
2449 static_cast<SolverT>(x[i][c] + alpha * y[i][c] + omega * z[i][c]);
2450 r[i][c] = static_cast<SolverT>(s[i][c] - omega * t[i][c]);
2451 }
2452
2453 velocityResidual = vecMaxAbs(r);
2454 if (!std::isfinite(velocityResidual)) {
2455 stokesBreakdown = true;
2456 break;
2457 }
2458 if (velocityResidual < deformationParameters.stokesTolerance * b_norm)
2459 break;
2460 }
2461
2462 if (stokesBreakdown)
2463 velocityResidual = std::numeric_limits<T>::infinity();
2464
2465 bool finiteSolution = !stokesBreakdown;
2466 for (std::size_t i = 0; i < n; ++i)
2467 for (unsigned c = 0; c < D; ++c)
2468 if (!std::isfinite(static_cast<T>(x[i][c])))
2469 finiteSolution = false;
2470
2471 if (finiteSolution) {
2472 for (std::size_t i = 0; i < n; ++i)
2473 for (unsigned c = 0; c < D; ++c)
2474 nodes[i].velocity[c] = static_cast<T>(x[i][c]);
2475 }
2476
2477 lastStokesIters_ = stokesIter;
2478 lastStokesResidual_ = finiteSolution ? velocityResidual / b_norm
2479 : std::numeric_limits<T>::infinity();
2480 if (lastStokesResidual_ > deformationParameters.stokesTolerance)
2481 VIENNACORE_LOG_WARNING(
2482 "solveStokesVelocity: BiCGSTAB did not converge after " +
2483 std::to_string(lastStokesIters_) + "/" +
2484 std::to_string(deformationParameters.stokesIterations) +
2485 " iterations (residual=" + std::to_string(lastStokesResidual_) +
2486 ", tolerance=" +
2487 std::to_string(deformationParameters.stokesTolerance) + ")");
2488 }
2489
2490 std::vector<Vec3D<T>> collectVelocities() const {
2491 std::vector<Vec3D<T>> velocities;
2492 velocities.reserve(nodes.size());
2493 for (const auto &node : nodes)
2494 velocities.push_back(node.velocity);
2495 return velocities;
2496 }
2497
2498 std::vector<T> collectPressures() const {
2499 std::vector<T> pressures;
2500 pressures.reserve(nodes.size());
2501 for (const auto &node : nodes)
2502 pressures.push_back(node.pressure);
2503 return pressures;
2504 }
2505
2506 template <class SolverT>
2507 StencilPoint<T>
2508 pressureStencilPoint(const std::vector<SolverT> &pressure,
2509 const std::vector<T> &ambientBoundaryPressure,
2510 std::size_t nodeId, unsigned direction,
2511 int offset) const {
2512 const auto &node = nodes[nodeId];
2513 IndexType neighbor = node.index;
2514 neighbor[direction] += offset;
2515
2516 if (!inBounds(neighbor))
2517 return {static_cast<T>(pressure[nodeId]), gridDelta};
2518
2519 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2520 if (neighborId != noNode) {
2521 if (touchesAmbient_[neighborId])
2522 return {ambientBoundaryPressure[neighborId], gridDelta};
2523 return {static_cast<T>(pressure[neighborId]), gridDelta};
2524 }
2525
2526 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2527 const std::size_t nn = nodes.size();
2528 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2529 const T faceDist = faceBCDists_[fi * nn + nodeId];
2530 if (faceType == Boundary::AMBIENT)
2531 return {ambientBoundaryPressure[nodeId], faceDist};
2532 // Reaction interface: Neumann ∂p/∂n=0 (solid-wall BC for pressure).
2533 // The ghost node takes the same value as the interior node, giving zero
2534 // contribution to the Laplacian stencil. The pressure is anchored only by
2535 // the AMBIENT Dirichlet (p=0 at the free surface), which is always present
2536 // for any connected oxide region.
2537 if (faceType == Boundary::REACTION)
2538 return {static_cast<T>(pressure[nodeId]), faceDist};
2539 if (faceType == Boundary::MASK)
2540 return {maskPressureBoundary(node.index, direction, offset,
2541 static_cast<T>(pressure[nodeId])),
2542 faceDist};
2543
2544 return {static_cast<T>(pressure[nodeId]), gridDelta};
2545 }
2546
2547 template <class SolverT>
2548 StencilPoint<Vec3D<T>>
2549 velocityStencilPoint(const std::vector<Vec3D<SolverT>> &velocity,
2550 std::size_t nodeId, unsigned direction,
2551 int offset) const {
2552 const auto &node = nodes[nodeId];
2553 IndexType neighbor = node.index;
2554 neighbor[direction] += offset;
2555
2556 const auto toT = [](const Vec3D<SolverT> &v) -> Vec3D<T> {
2557 return {static_cast<T>(v[0]), static_cast<T>(v[1]), static_cast<T>(v[2])};
2558 };
2559
2560 if (!inBounds(neighbor))
2561 return {toT(velocity[nodeId]), gridDelta};
2562
2563 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2564 if (neighborId != noNode)
2565 return {toT(velocity[neighborId]), gridDelta};
2566
2567 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2568 const std::size_t nn = nodes.size();
2569 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2570 const T faceDist = faceBCDists_[fi * nn + nodeId];
2571 if (faceType == Boundary::REACTION)
2572 return {reactionBoundaryVelocity(node.index), faceDist};
2573 if (faceType == Boundary::AMBIENT)
2574 return {freeSurfaceVelocityBoundary(node.index, direction, offset,
2575 faceDist, toT(velocity[nodeId])),
2576 faceDist};
2577 if (faceType == Boundary::MASK)
2578 return {maskVelocityBoundary(node.index, toT(velocity[nodeId])),
2579 faceDist};
2580
2581 return {toT(velocity[nodeId]), gridDelta};
2582 }
2583
2584 StencilPoint<T> currentPressureStencilPoint(std::size_t nodeId,
2585 unsigned direction,
2586 int offset) const {
2587 const auto &node = nodes[nodeId];
2588 IndexType neighbor = node.index;
2589 neighbor[direction] += offset;
2590
2591 if (!inBounds(neighbor))
2592 return {node.pressure, gridDelta};
2593
2594 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2595 if (neighborId != noNode)
2596 return {nodes[neighborId].pressure, gridDelta};
2597
2598 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2599 const std::size_t nn = nodes.size();
2600 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2601 const T faceDist = faceBCDists_[fi * nn + nodeId];
2602 if (faceType == Boundary::AMBIENT)
2603 return {freeSurfacePressureBoundary(node.index), faceDist};
2604 if (faceType == Boundary::REACTION)
2605 return {node.pressure, faceDist}; // Neumann ∂p/∂n=0
2606 if (faceType == Boundary::MASK)
2607 return {
2608 maskPressureBoundary(node.index, direction, offset, node.pressure),
2609 faceDist};
2610
2611 return {node.pressure, gridDelta};
2612 }
2613
2614 StencilPoint<Vec3D<T>> currentVelocityStencilPoint(std::size_t nodeId,
2615 unsigned direction,
2616 int offset) const {
2617 const auto &node = nodes[nodeId];
2618 IndexType neighbor = node.index;
2619 neighbor[direction] += offset;
2620
2621 if (!inBounds(neighbor))
2622 return {node.velocity, gridDelta};
2623
2624 const std::size_t neighborId = nodeLookupFlat[linearIndex(neighbor)];
2625 if (neighborId != noNode)
2626 return {nodes[neighborId].velocity, gridDelta};
2627
2628 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
2629 const std::size_t nn = nodes.size();
2630 const Boundary faceType = faceBCTypes_[fi * nn + nodeId];
2631 const T faceDist = faceBCDists_[fi * nn + nodeId];
2632 if (faceType == Boundary::REACTION)
2633 return {reactionBoundaryVelocity(node.index), faceDist};
2634 if (faceType == Boundary::AMBIENT)
2635 return {freeSurfaceVelocityBoundary(node.index, direction, offset,
2636 faceDist, node.velocity),
2637 faceDist};
2638 if (faceType == Boundary::MASK)
2639 return {maskVelocityBoundary(node.index, node.velocity), faceDist};
2640
2641 return {node.velocity, gridDelta};
2642 }
2643
2644 T maxVelocityChange(const std::vector<Vec3D<T>> &previous) const {
2645 T maxChange = 0.;
2646 T maxVelocity = 0.;
2647 const auto count = std::min(previous.size(), nodes.size());
2648 for (std::size_t i = 0; i < count; ++i) {
2649 for (unsigned j = 0; j < D; ++j) {
2650 maxChange = std::max(maxChange,
2651 std::abs(nodes[i].velocity[j] - previous[i][j]));
2652 maxVelocity = std::max(maxVelocity, std::abs(nodes[i].velocity[j]));
2653 }
2654 }
2655
2656 if (maxVelocity <= std::numeric_limits<T>::epsilon())
2657 return maxChange;
2658 return maxChange / maxVelocity;
2659 }
2660
2661 T maxPressureChange(const std::vector<T> &previous) const {
2662 T maxChange = 0.;
2663 T maxPressure = 0.;
2664 const auto count = std::min(previous.size(), nodes.size());
2665 for (std::size_t i = 0; i < count; ++i) {
2666 maxChange =
2667 std::max(maxChange, std::abs(nodes[i].pressure - previous[i]));
2668 maxPressure = std::max(maxPressure, std::abs(nodes[i].pressure));
2669 }
2670
2671 if (maxPressure <= std::numeric_limits<T>::epsilon())
2672 return maxChange;
2673 return maxChange / maxPressure;
2674 }
2675
2676 std::array<T, 9>
2677 currentBoundaryDeviatoricStress(const IndexType &index) const {
2678 const auto strainRate = strainRateTensorAt(index);
2679 const auto deviatoricRate =
2680 deviatoricTensor(strainRate, divergenceAt(index));
2681 const auto previousStress = previousDeviatoricStress(index);
2682 const T relaxationTime = effectiveStressRelaxationTime();
2683 const T decay =
2684 (relaxationTime <= std::numeric_limits<T>::epsilon())
2685 ? T(0)
2686 : std::exp(-deformationParameters.stressTimeStep / relaxationTime);
2687
2688 std::array<T, 9> deviatoricStress{};
2689 for (unsigned i = 0; i < 9; ++i) {
2690 const T viscousStress =
2691 T(2) * deformationParameters.viscosity * deviatoricRate[i];
2692 deviatoricStress[i] =
2693 decay * previousStress[i] + (T(1) - decay) * viscousStress;
2694 }
2695
2696 return deviatoricStress;
2697 }
2698
2699 T freeSurfacePressureBoundary(const IndexType &index) const {
2700 const auto normal = interfaceNormal(index, Boundary::AMBIENT);
2701 const auto deviatoricStress = currentBoundaryDeviatoricStress(index);
2702
2703 return deformationParameters.ambientPressure +
2704 normalStress(deviatoricStress, normal);
2705 }
2706
2707 T maskPressureBoundary(const IndexType & /*index*/, unsigned /*direction*/,
2708 int /*offset*/, T fallbackPressure) const {
2709 return fallbackPressure;
2710 }
2711
2712 Vec3D<T> freeSurfaceVelocityBoundary(const IndexType &index,
2713 unsigned direction, int offset,
2714 T distance,
2715 const Vec3D<T> &interiorVelocity) const {
2716 Vec3D<T> boundaryVelocity = interiorVelocity;
2717 const auto normal = interfaceNormal(index, Boundary::AMBIENT);
2718 const auto deviatoricStress = deviatoricStressAt(index);
2719 const T pressure = pressureAt(index);
2720
2721 Vec3D<T> deviatoricTraction{0., 0., 0.};
2722 for (unsigned component = 0; component < D; ++component) {
2723 for (unsigned j = 0; j < D; ++j)
2724 deviatoricTraction[component] +=
2725 deviatoricStress[tensorIndex(component, j)] * normal[j];
2726 }
2727
2728 for (unsigned component = 0; component < D; ++component) {
2729 const T normalTraction =
2730 pressure * normal[component] - deviatoricTraction[component];
2731 const T faceDerivative = normalTraction * normal[direction] /
2732 std::max(deformationParameters.viscosity,
2733 std::numeric_limits<T>::epsilon());
2734 boundaryVelocity[component] +=
2735 static_cast<T>(offset) * distance * faceDerivative;
2736 }
2737
2738 return boundaryVelocity;
2739 }
2740
2741 Vec3D<T> maskVelocityBoundary(const IndexType &index,
2742 const Vec3D<T> &interiorVelocity) const {
2743 if (maskVelocityField != nullptr) {
2744 Vec3D<T> coordinate{0., 0., 0.};
2745 for (unsigned i = 0; i < D; ++i)
2746 coordinate[i] = index[i] * gridDelta;
2747 return maskVelocityField->getVectorVelocity(
2748 coordinate, deformationParameters.material, {0., 0., 0.}, 0);
2749 }
2750 return {0., 0., 0.};
2751 }
2752
2754 avgExpansionSpeed_ = 0.;
2755 if (nodes.empty())
2756 return;
2757
2758 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2759 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2760 auto maskIt = makeMaskIterator();
2761 std::size_t count = 0;
2762
2763 for (const auto &node : nodes) {
2764 bool touchesReactionBoundary = false;
2765 for (unsigned direction = 0; direction < D; ++direction) {
2766 for (int offset : {-1, 1}) {
2767 IndexType neighbor = node.index;
2768 neighbor[direction] += offset;
2769 if (!inBounds(neighbor))
2770 continue;
2771
2772 if (lookupNode(neighbor) != noNode)
2773 continue;
2774
2775 if (classifyBoundary(reactionIt, ambientIt, maskIt, node.index,
2776 neighbor) == Boundary::REACTION) {
2777 touchesReactionBoundary = true;
2778 break;
2779 }
2780 }
2781 if (touchesReactionBoundary)
2782 break;
2783 }
2784
2785 if (touchesReactionBoundary) {
2786 Vec3D<T> coordinate{0., 0., 0.};
2787 for (unsigned i = 0; i < D; ++i)
2788 coordinate[i] = node.index[i] * gridDelta;
2789 avgExpansionSpeed_ +=
2790 (oxidationParameters.expansionCoefficient - T(1)) *
2791 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
2792 {0., 0., 0.}, 0));
2793 ++count;
2794 }
2795 }
2796
2797 if (count > 0)
2798 avgExpansionSpeed_ /= static_cast<T>(count);
2799 }
2800
2801 Vec3D<T> reactionBoundaryVelocity(const IndexType &index) const {
2802 Vec3D<T> coordinate{0., 0., 0.};
2803 for (unsigned i = 0; i < D; ++i)
2804 coordinate[i] = index[i] * gridDelta;
2805 const T expansionVelocity = localExpansionSpeed(coordinate);
2806 return detail::vecScaled(reactionNormal(index),
2807 reactionSign * expansionVelocity);
2808 }
2809
2810 Vec3D<T> unresolvedAmbientVelocity(const Vec3D<T> &coordinate) const {
2811 if (diffusionField == nullptr || ambientInterface == nullptr)
2812 return {0., 0., 0.};
2813
2814 IndexType index;
2815 for (unsigned i = 0; i < D; ++i)
2816 index[i] = std::llround(coordinate[i] / gridDelta);
2817
2818 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2819 const auto normal = levelSetNormal(ambientIt, index);
2820 return detail::vecScaled(normal, localExpansionSpeed(coordinate));
2821 }
2822
2824 Vec3D<T> maxVelocity{0., 0., 0.};
2825 if (ambientInterface == nullptr || diffusionField == nullptr)
2826 return maxVelocity;
2827
2828 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2829 for (; !ambientIt.isFinished(); ++ambientIt) {
2830 if (!ambientIt.isDefined())
2831 continue;
2832
2833 Vec3D<T> coordinate{0., 0., 0.};
2834 const auto &index = ambientIt.getStartIndices();
2835 for (unsigned d = 0; d < D; ++d)
2836 coordinate[d] = index[d] * gridDelta;
2837
2838 const auto velocity = unresolvedAmbientVelocity(coordinate);
2839 for (unsigned d = 0; d < D; ++d)
2840 maxVelocity[d] = std::max(maxVelocity[d], std::abs(velocity[d]));
2841 }
2842 return maxVelocity;
2843 }
2844
2845 T divergenceAt(const IndexType &index) const {
2846 T divergence = 0.;
2847 for (unsigned i = 0; i < D; ++i) {
2848 divergence += velocityDerivative(index, i, i);
2849 }
2850 return divergence;
2851 }
2852
2853 Vec3D<T> pressureGradient(const IndexType &index) const {
2854 Vec3D<T> gradient{0., 0., 0.};
2855 for (unsigned i = 0; i < D; ++i)
2856 gradient[i] = pressureDerivative(index, i);
2857 return gradient;
2858 }
2859
2860 Vec3D<T> momentumForcing(const IndexType &index) const {
2861 Vec3D<T> forcing = pressureGradient(index);
2862 const auto stressDivergence = deviatoricStressDivergence(index);
2863 for (unsigned i = 0; i < D; ++i)
2864 forcing[i] -= stressDivergence[i];
2865 return forcing;
2866 }
2867
2868 Vec3D<T> deviatoricStressDivergence(const IndexType &index) const {
2869 Vec3D<T> divergence{0., 0., 0.};
2870 for (unsigned component = 0; component < D; ++component) {
2871 for (unsigned direction = 0; direction < D; ++direction) {
2872 IndexType pos = index;
2873 IndexType neg = index;
2874 pos[direction] += 1;
2875 neg[direction] -= 1;
2876 const auto posStress = deviatoricStressAt(pos);
2877 const auto negStress = deviatoricStressAt(neg);
2878 divergence[component] +=
2879 (posStress[tensorIndex(component, direction)] -
2880 negStress[tensorIndex(component, direction)]) /
2881 (T(2) * gridDelta);
2882 }
2883 }
2884 return divergence;
2885 }
2886
2887 std::array<T, 9> deviatoricStressAt(const IndexType &index) const {
2888 if (!inBounds(index))
2889 return {};
2890
2891 const std::size_t nodeId = lookupNode(index);
2892 if (nodeId == noNode)
2893 return {};
2894
2895 std::array<T, 9> deviatoric = nodes[nodeId].stressTensor;
2896 for (unsigned i = 0; i < 3; ++i)
2897 deviatoric[tensorIndex(i, i)] += nodes[nodeId].pressure;
2898 return deviatoric;
2899 }
2900
2901 T pressureAt(const IndexType &index) const {
2902 if (!inBounds(index))
2903 return deformationParameters.ambientPressure;
2904
2905 const std::size_t nodeId = lookupNode(index);
2906 if (nodeId == noNode)
2907 return deformationParameters.ambientPressure;
2908 return nodes[nodeId].pressure;
2909 }
2910
2911 T localExpansionSpeed(const Vec3D<T> &coordinate) const {
2912 return (oxidationParameters.expansionCoefficient - T(1)) *
2913 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
2914 {0., 0., 0.}, 0));
2915 }
2916
2917 Vec3D<T> reactionNormal(const IndexType &index) const {
2918 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2919 return levelSetNormal(reactionIt, index);
2920 }
2921
2922 Vec3D<T> interfaceNormal(const IndexType &index, Boundary boundary) const {
2923 if (boundary == Boundary::AMBIENT) {
2924 ConstSparseIterator ambientIt(ambientInterface->getDomain());
2925 return levelSetNormal(ambientIt, index);
2926 }
2927 if (boundary == Boundary::MASK && maskInterface != nullptr) {
2928 ConstSparseIterator maskIt(maskInterface->getDomain());
2929 return levelSetNormal(maskIt, index);
2930 }
2931
2932 ConstSparseIterator reactionIt(reactionInterface->getDomain());
2933 return levelSetNormal(reactionIt, index);
2934 }
2935
2936 Vec3D<T> levelSetNormal(ConstSparseIterator &levelSetIt,
2937 const IndexType &index) const {
2938 Vec3D<T> normal{0., 0., 0.};
2939 T norm = 0.;
2940
2941 for (unsigned i = 0; i < D; ++i) {
2942 IndexType pos = index;
2943 IndexType neg = index;
2944 pos[i] += 1;
2945 neg[i] -= 1;
2946 if (!inBounds(pos))
2947 pos = index;
2948 if (!inBounds(neg))
2949 neg = index;
2950 normal[i] = detail::clampLevelSetPhi(valueAt(levelSetIt, pos)) -
2951 detail::clampLevelSetPhi(valueAt(levelSetIt, neg));
2952 norm += normal[i] * normal[i];
2953 }
2954
2955 if (norm <= std::numeric_limits<T>::epsilon()) {
2956 normal = Vec3D<T>{0., 0., 0.};
2957 normal[D - 1] = 1.;
2958 return normal;
2959 }
2960
2961 norm = std::sqrt(norm);
2962 for (unsigned i = 0; i < D; ++i)
2963 normal[i] /= norm;
2964 return normal;
2965 }
2966
2968#pragma omp parallel for schedule(static)
2969 for (std::size_t i = 0; i < nodes.size(); ++i)
2970 nodes[i].strainTrace = divergenceAt(nodes[i].index);
2971 }
2972
2974 const T relaxationTime = effectiveStressRelaxationTime();
2975 const T decay =
2976 (relaxationTime <= std::numeric_limits<T>::epsilon())
2977 ? T(0)
2978 : std::exp(-deformationParameters.stressTimeStep / relaxationTime);
2979
2980 // Per-node computation is independent; collect history keys into a vector
2981 // to avoid concurrent map writes, then build the map sequentially below.
2982 std::vector<std::pair<IndexType, std::array<T, 9>>> historyEntries(
2983 nodes.size());
2984#pragma omp parallel for schedule(static)
2985 for (std::size_t i = 0; i < nodes.size(); ++i) {
2986 auto &node = nodes[i];
2987 node.strainRateTensor = strainRateTensorAt(node.index);
2988 const auto deviatoricRate =
2989 deviatoricTensor(node.strainRateTensor, node.strainTrace);
2990 const auto previousStress = previousDeviatoricStress(node.index);
2991
2992 std::array<T, 9> deviatoricStress{};
2993 for (unsigned j = 0; j < 9; ++j) {
2994 const T viscousStress =
2995 T(2) * deformationParameters.viscosity * deviatoricRate[j];
2996 deviatoricStress[j] =
2997 decay * previousStress[j] + (T(1) - decay) * viscousStress;
2998 }
2999
3000 node.stressTensor = deviatoricStress;
3001 for (unsigned j = 0; j < 3; ++j)
3002 node.stressTensor[tensorIndex(j, j)] -= node.pressure;
3003
3004 node.vonMisesStress = vonMisesFromDeviatoric(deviatoricStress);
3005 historyEntries[i] = {node.index, deviatoricStress};
3006 }
3007
3008 std::unordered_map<IndexType, std::array<T, 9>, detail::IndexTypeHasher<D>>
3009 nextHistory;
3010 nextHistory.reserve(nodes.size());
3011 for (const auto &entry : historyEntries)
3012 nextHistory[entry.first] = entry.second;
3013 deviatoricStressHistory.swap(nextHistory);
3014 }
3015
3016 std::array<T, 9> strainRateTensorAt(const IndexType &index) const {
3017 std::array<T, 9> tensor{};
3018 for (unsigned i = 0; i < D; ++i) {
3019 for (unsigned j = 0; j < D; ++j) {
3020 tensor[tensorIndex(i, j)] = T(0.5) * (velocityDerivative(index, i, j) +
3021 velocityDerivative(index, j, i));
3022 }
3023 }
3024 return tensor;
3025 }
3026
3027 T velocityDerivative(const IndexType &index, unsigned component,
3028 unsigned direction) const {
3029 const std::size_t nodeId = lookupNode(index);
3030 if (nodeId == noNode)
3031 return 0.;
3032
3033 const auto plus = currentVelocityStencilPoint(nodeId, direction, 1);
3034 const auto minus = currentVelocityStencilPoint(nodeId, direction, -1);
3035 return firstDerivative(
3036 minus.value[component], nodes[nodeId].velocity[component],
3037 plus.value[component], minus.distance, plus.distance);
3038 }
3039
3040 T pressureDerivative(const IndexType &index, unsigned direction) const {
3041 const std::size_t nodeId = lookupNode(index);
3042 if (nodeId == noNode)
3043 return 0.;
3044
3045 const auto plus = currentPressureStencilPoint(nodeId, direction, 1);
3046 const auto minus = currentPressureStencilPoint(nodeId, direction, -1);
3047 return firstDerivative(minus.value, nodes[nodeId].pressure, plus.value,
3048 minus.distance, plus.distance);
3049 }
3050
3051 std::array<T, 9> deviatoricTensor(const std::array<T, 9> &tensor,
3052 T trace) const {
3053 std::array<T, 9> result = tensor;
3054 const T mean = trace / T(3);
3055 for (unsigned i = 0; i < 3; ++i)
3056 result[tensorIndex(i, i)] -= mean;
3057 return result;
3058 }
3059
3060 std::array<T, 9> previousDeviatoricStress(const IndexType &index) const {
3061 const auto found = deviatoricStressHistory.find(index);
3062 if (found == deviatoricStressHistory.end())
3063 return {};
3064 return found->second;
3065 }
3066
3068 if (deformationParameters.stressRelaxationTime > T(0))
3069 return deformationParameters.stressRelaxationTime;
3070 if (deformationParameters.shearModulus > std::numeric_limits<T>::epsilon())
3071 return deformationParameters.viscosity /
3072 deformationParameters.shearModulus;
3073 return T(0);
3074 }
3075
3076 T vonMisesFromDeviatoric(const std::array<T, 9> &deviatoricStress) const {
3077 T doubleContraction = 0.;
3078 for (unsigned i = 0; i < 3; ++i) {
3079 for (unsigned j = 0; j < 3; ++j) {
3080 const T value = T(0.5) * (deviatoricStress[tensorIndex(i, j)] +
3081 deviatoricStress[tensorIndex(j, i)]);
3082 doubleContraction += value * value;
3083 }
3084 }
3085 return std::sqrt(T(1.5) * doubleContraction);
3086 }
3087
3088 T normalStress(const std::array<T, 9> &tensor, const Vec3D<T> &normal) const {
3089 T result = 0.;
3090 for (unsigned i = 0; i < 3; ++i) {
3091 for (unsigned j = 0; j < 3; ++j)
3092 result += normal[i] * tensor[tensorIndex(i, j)] * normal[j];
3093 }
3094 return result;
3095 }
3096
3097 Boundary classifyBoundary(ConstSparseIterator &reactionIt,
3098 ConstSparseIterator &ambientIt,
3099 ConstSparseIterator &maskIt,
3100 const IndexType &inside,
3101 const IndexType &outside) const {
3102 return boundaryIntersection(reactionIt, ambientIt, maskIt, inside, outside)
3103 .boundary;
3104 }
3105
3106 BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt,
3107 ConstSparseIterator &ambientIt,
3108 ConstSparseIterator &maskIt,
3109 const IndexType &inside,
3110 const IndexType &outside) const {
3111 const T reactionInside = valueAt(reactionIt, inside);
3112 const T reactionOutside = valueAt(reactionIt, outside);
3113 const T ambientInside = valueAt(ambientIt, inside);
3114 const T ambientOutside = valueAt(ambientIt, outside);
3115 const T maskInside = valueAtMask(maskIt, inside);
3116 const T maskOutside = valueAtMask(maskIt, outside);
3117
3118 const bool reactionCrosses = crosses(reactionInside, reactionOutside);
3119 const bool ambientCrosses = crosses(ambientInside, ambientOutside);
3120 const bool maskCrosses =
3121 maskInterface != nullptr && crosses(maskInside, maskOutside);
3122
3123 if (!reactionCrosses && !ambientCrosses && !maskCrosses)
3124 return {Boundary::NONE, gridDelta};
3125 if (reactionCrosses && !ambientCrosses && !maskCrosses)
3126 return {Boundary::REACTION,
3127 crossingDistance(reactionInside, reactionOutside)};
3128 if (!reactionCrosses && ambientCrosses && !maskCrosses)
3130 maskInside, maskOutside,
3131 crossingDistance(ambientInside, ambientOutside));
3132 if (!reactionCrosses && !ambientCrosses && maskCrosses)
3133 return {Boundary::MASK, crossingDistance(maskInside, maskOutside)};
3134
3135 const T reactionDistance =
3136 reactionCrosses ? crossingDistance(reactionInside, reactionOutside)
3137 : std::numeric_limits<T>::max();
3138 const T ambientDistance =
3139 ambientCrosses ? crossingDistance(ambientInside, ambientOutside)
3140 : std::numeric_limits<T>::max();
3141 const T maskDistance = maskCrosses
3142 ? crossingDistance(maskInside, maskOutside)
3143 : std::numeric_limits<T>::max();
3144 if (reactionDistance <= ambientDistance && reactionDistance <= maskDistance)
3145 return {Boundary::REACTION, reactionDistance};
3146 if (ambientDistance != std::numeric_limits<T>::max()) {
3147 const auto maskedAmbient =
3148 ambientCrossingInsideMask(maskInside, maskOutside, ambientDistance);
3149 if (maskedAmbient.boundary == Boundary::MASK)
3150 return maskedAmbient;
3151 }
3152 if (maskDistance <= ambientDistance)
3153 return {Boundary::MASK, maskDistance};
3154 return {Boundary::AMBIENT, ambientDistance};
3155 }
3156
3157 bool touchesBoundary(ConstSparseIterator &reactionIt,
3158 ConstSparseIterator &ambientIt,
3159 ConstSparseIterator &maskIt, const IndexType &index,
3160 Boundary requestedBoundary) const {
3161 for (unsigned direction = 0; direction < D; ++direction) {
3162 for (int offset : {-1, 1}) {
3163 IndexType neighbor = index;
3164 neighbor[direction] += offset;
3165 if (!inBounds(neighbor))
3166 continue;
3167
3168 if (lookupNode(neighbor) != noNode)
3169 continue;
3170
3171 if (classifyBoundary(reactionIt, ambientIt, maskIt, index, neighbor) ==
3172 requestedBoundary)
3173 return true;
3174 }
3175 }
3176 return false;
3177 }
3178
3179 bool isInsideOxide(T reactionPhi, T ambientPhi) const {
3180 // GeometricAdvect can leave a tiny positive residual (~4*epsilon) when the
3181 // interface lands exactly on a grid point at non-zero coordinates, because
3182 // k*gridDelta is not exactly representable in floating point. Allow a
3183 // tolerance of 1e-9 grid units so that grid points on the surface (phi≈0)
3184 // are correctly classified as inside the oxide.
3185 constexpr T eps = T(1e-9);
3186 return reactionSign * reactionPhi >= -eps &&
3187 ambientSign * ambientPhi >= -eps;
3188 }
3189
3190 ConstSparseIterator makeMaskIterator() const {
3191 if (maskInterface == nullptr)
3192 return ConstSparseIterator(reactionInterface->getDomain());
3193 return ConstSparseIterator(maskInterface->getDomain());
3194 }
3195
3196 bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3197 if (maskInterface == nullptr)
3198 return false;
3199 return maskSign * valueAt(maskIt, index) >= 0.;
3200 }
3201
3202 T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3203 if (maskInterface == nullptr)
3204 return std::numeric_limits<T>::max();
3205 return valueAt(maskIt, index);
3206 }
3207
3208 BoundaryIntersection ambientCrossingInsideMask(T maskInside, T maskOutside,
3209 T distance) const {
3210 if (isMaskAtCrossing(maskInside, maskOutside, distance))
3211 return {Boundary::MASK, distance};
3212 // Outer node is wholly inside the mask body: the oxide/gas surface has
3213 // drifted into the nitride. Apply mask Dirichlet BC (not traction-free)
3214 // so the deformation solver does not advance the surface further in.
3215 // Mirrors the equivalent check in lsOxidationDiffusion::classifyBoundary.
3216 if (maskInterface != nullptr &&
3217 static_cast<T>(maskSign) * maskOutside >= T(0))
3218 return {Boundary::MASK, distance};
3219 return {Boundary::AMBIENT, distance};
3220 }
3221
3222 bool isMaskAtCrossing(T maskInside, T maskOutside, T distance) const {
3223 if (maskInterface == nullptr)
3224 return false;
3225 const T fraction = std::clamp(distance / gridDelta, T(0), T(1));
3226 const T insidePhi = detail::clampLevelSetPhi(maskInside);
3227 const T outsidePhi = detail::clampLevelSetPhi(maskOutside);
3228 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
3229 return static_cast<T>(maskSign) * maskPhi >= T(0);
3230 }
3231
3232 T crossingDistance(T insidePhi, T outsidePhi) const {
3234 insidePhi, outsidePhi,
3235 deformationParameters.minMechanicsBoundaryDistance, gridDelta);
3236 }
3237
3238 static T firstDerivative(T minusValue, T centerValue, T plusValue,
3239 T minusDistance, T plusDistance) {
3240 const T denominator =
3241 minusDistance * plusDistance * (minusDistance + plusDistance);
3242 if (denominator <= std::numeric_limits<T>::epsilon())
3243 return 0.;
3244
3245 return (-plusDistance * plusDistance * minusValue +
3246 (plusDistance * plusDistance - minusDistance * minusDistance) *
3247 centerValue +
3248 minusDistance * minusDistance * plusValue) /
3249 denominator;
3250 }
3251
3252 static constexpr unsigned tensorIndex(unsigned row, unsigned column) {
3253 return 3 * row + column;
3254 }
3255};
3256
3257} // 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:3088
T pressureAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2901
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:2490
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:2584
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:3027
void computeDiagnostics()
Definition lsOxidationDeformation.hpp:2967
T getResidual() const
Definition lsOxidationDeformation.hpp:550
T localExpansionSpeed(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:2911
static T firstDerivative(T minusValue, T centerValue, T plusValue, T minusDistance, T plusDistance)
Definition lsOxidationDeformation.hpp:3238
T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3202
void setReactionInterface(SmartPointer< Domain< T, D > > passedInterface)
Definition lsOxidationDeformation.hpp:228
Vec3D< T > estimateMaxUnresolvedAmbientVelocity() const
Definition lsOxidationDeformation.hpp:2823
void computePressureStencilAt(std::size_t nodeId, const std::vector< SolverT > &p, const std::vector< T > &ambientBP, T &diag, T &rhs) const
Definition lsOxidationDeformation.hpp:1631
Vec3D< T > levelSetNormal(ConstSparseIterator &levelSetIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:2936
void computeAvgExpansionSpeed()
Definition lsOxidationDeformation.hpp:2753
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:2707
Vec3D< T > freeSurfaceVelocityBoundary(const IndexType &index, unsigned direction, int offset, T distance, const Vec3D< T > &interiorVelocity) const
Definition lsOxidationDeformation.hpp:2712
StencilPoint< Vec3D< T > > currentVelocityStencilPoint(std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2614
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:2158
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:2644
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:2508
void markGeometryChanged()
Definition lsOxidationDeformation.hpp:303
Vec3D< T > reactionNormal(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2917
std::array< T, 9 > currentBoundaryDeviatoricStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2677
void setDeformationParameters(OxidationDeformationParameters passedParameters)
Definition lsOxidationDeformation.hpp:277
T pressureDerivative(const IndexType &index, unsigned direction) const
Definition lsOxidationDeformation.hpp:3040
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:2868
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:3016
ConstSparseIterator makeMaskIterator() const
Definition lsOxidationDeformation.hpp:3190
Vec3D< T > momentumForcing(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2860
Vec3D< T > reactionBoundaryVelocity(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2801
std::array< T, 9 > previousDeviatoricStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3060
BoundaryIntersection ambientCrossingInsideMask(T maskInside, T maskOutside, T distance) const
Definition lsOxidationDeformation.hpp:3208
StencilPoint< Vec3D< T > > velocityStencilPoint(const std::vector< Vec3D< SolverT > > &velocity, std::size_t nodeId, unsigned direction, int offset) const
Definition lsOxidationDeformation.hpp:2549
Boundary classifyBoundary(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3097
std::vector< T > collectPressures() const
Definition lsOxidationDeformation.hpp:2498
void apply()
Definition lsOxidationDeformation.hpp:308
std::array< T, 9 > deviatoricStressAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2887
T getLastStokesResidual() const
Definition lsOxidationDeformation.hpp:552
Vec3D< T > maskVelocityBoundary(const IndexType &index, const Vec3D< T > &interiorVelocity) const
Definition lsOxidationDeformation.hpp:2741
void clearSolveBounds()
Definition lsOxidationDeformation.hpp:297
T maxPressureChange(const std::vector< T > &previous) const
Definition lsOxidationDeformation.hpp:2661
std::array< T, 9 > deviatoricTensor(const std::array< T, 9 > &tensor, T trace) const
Definition lsOxidationDeformation.hpp:3051
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:2853
BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3106
void setOxidationParameters(OxidationParameters passedParameters)
Definition lsOxidationDeformation.hpp:271
bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3196
void setGpuPreconditioner(GpuPreconditioner prec)
Definition lsOxidationDeformation.hpp:224
void solvePressure()
Definition lsOxidationDeformation.hpp:1669
std::vector< Node > nodes
Definition lsOxidationDeformation.hpp:195
T effectiveStressRelaxationTime() const
Definition lsOxidationDeformation.hpp:3067
T divergenceAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2845
bool hasFiniteSolution() const
Definition lsOxidationDeformation.hpp:562
T crossingDistance(T insidePhi, T outsidePhi) const
Definition lsOxidationDeformation.hpp:3232
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:3222
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:2922
std::array< T, 9 > getStrainRateTensor(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:519
void solveStokesVelocity()
Definition lsOxidationDeformation.hpp:2175
T vonMisesFromDeviatoric(const std::array< T, 9 > &deviatoricStress) const
Definition lsOxidationDeformation.hpp:3076
void setSolveBounds(const IndexType &passedMinIndex, const IndexType &passedMaxIndex)
Definition lsOxidationDeformation.hpp:288
void solveVelocity()
Definition lsOxidationDeformation.hpp:1175
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:1458
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:1658
void harmonicMatvec(const std::vector< Vec3D< SolverT > > &v, const std::vector< Vec3D< T > > &b, std::vector< Vec3D< SolverT > > &Av) const
Definition lsOxidationDeformation.hpp:1162
T freeSurfacePressureBoundary(const IndexType &index) const
Definition lsOxidationDeformation.hpp:2699
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:3252
void clearMaskInterface()
Definition lsOxidationDeformation.hpp:248
bool touchesBoundary(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &index, Boundary requestedBoundary) const
Definition lsOxidationDeformation.hpp:3157
void applySimpleVelocityCorrection(const std::vector< T > &pressureOld, const std::vector< Vec3D< T > > &diagV)
Definition lsOxidationDeformation.hpp:1557
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:3179
T getLastPressureResidual() const
Definition lsOxidationDeformation.hpp:551
Vec3D< T > unresolvedAmbientVelocity(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:2810
void computeStressTensors()
Definition lsOxidationDeformation.hpp:2973
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:1440
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:90
static constexpr std::size_t noNode
Definition lsOxidationSolverBase.hpp:96
bool crosses(T a, T b) const
Definition lsOxidationSolverBase.hpp:110
std::size_t lookupNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:137
std::size_t linearIndex(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:143
void initNodeLookup()
Definition lsOxidationSolverBase.hpp:130
bool inBounds(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:123
std::array< std::size_t, D > strides
Definition lsOxidationSolverBase.hpp:101
T gridDelta
Definition lsOxidationSolverBase.hpp:102
std::vector< std::size_t > nodeLookupFlat
Definition lsOxidationSolverBase.hpp:97
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:207
std::array< std::size_t, D > extents
Definition lsOxidationSolverBase.hpp:100
viennahrle::ConstSparseIterator< typename Domain< T, D >::DomainType > ConstSparseIterator
Definition lsOxidationSolverBase.hpp:93
T valueAt(ConstSparseIterator &it, const IndexType &index) const
Definition lsOxidationSolverBase.hpp:118
bool increment(IndexType &index) const
Definition lsOxidationSolverBase.hpp:152
IndexType minIndex
Definition lsOxidationSolverBase.hpp:98
viennahrle::Index< D > IndexType
Definition lsOxidationSolverBase.hpp:92
std::size_t findNearbyNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:166
IndexType maxIndex
Definition lsOxidationSolverBase.hpp:99
T levelSetCrossingDistance(T insidePhi, T outsidePhi, T minBoundaryFraction, T gridDelta)
Definition lsOxidationSolverBase.hpp:76
T clampLevelSetPhi(T v)
Clamp HRLE far-field sentinels (±DBL_MAX) to ±1 before differencing to prevent DBL_MAX² overflow that...
Definition lsOxidationSolverBase.hpp:71
Vec3D< T > vecScaled(const Vec3D< T > &source, T factor)
Definition lsOxidationSolverBase.hpp:40
void vecAddTo(Vec3D< T > &target, const Vec3D< T > &source)
Definition lsOxidationSolverBase.hpp:64
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
Definition lsOxidationSolverBase.hpp:34