99 using IndexType = viennahrle::Index<D>;
100 using ConstSparseIterator =
101 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
104 static constexpr T boltzmannConstant =
T(1.380649e-23);
105 static constexpr T minStressFactor =
T(1.e-6);
106 static constexpr T maxStressFactor =
T(1.e6);
126 enum class Boundary { NONE, REACTION, AMBIENT, MASK };
130 T concentration = 0.;
131 Vec3D<T> siNormal = {0., 0.,
137 T nodeCoefficient = 1.;
141 SmartPointer<Domain<T, D>> reactionInterface =
nullptr;
142 SmartPointer<Domain<T, D>> ambientInterface =
nullptr;
143 SmartPointer<Domain<T, D>> maskInterface =
nullptr;
145 int reactionSign = 1;
146 int ambientSign = -1;
148 IndexType requestedMinIndex{};
149 IndexType requestedMaxIndex{};
150 unsigned iterations = 0;
151 T residual = std::numeric_limits<T>::max();
152 T normalizedResidual_ = std::numeric_limits<T>::max();
153 bool lastSolveConverged_ =
false;
154 T maxScalarVelocity_ = 0.;
156 bool nodesDirty_ =
true;
159 bool useRequestedBounds =
false;
160 bool warmStartable_ =
162 std::unordered_map<std::size_t, T> pressureLookup;
163 std::unordered_map<std::size_t, T> concentrationCache_;
164 std::vector<Node> nodes;
168 std::vector<Boundary> faceBCTypes_;
169 std::vector<T> faceBCDists_;
172 std::vector<std::size_t> neighborIds_;
177#ifdef VIENNALS_GPU_BICGSTAB
181 gpu::GpuBiCGSTABBuffers *gpuBufs_ =
nullptr;
203 : reactionInterface(passedReactionInterface),
204 ambientInterface(passedAmbientInterface), parameters(passedParameters) {
208#ifdef VIENNALS_GPU_BICGSTAB
209 gpu::freeGpuBuffers(gpuBufs_);
214 template <
class... Args>
static auto New(Args &&...args) {
215 return SmartPointer<OxidationDiffusion>::New(std::forward<Args>(args)...);
226 reactionInterface = passedInterface;
232 ambientInterface = passedInterface;
238 int passedMaskSign = 1) {
239 maskInterface = passedInterface;
240 maskSign = (passedMaskSign < 0) ? -1 : 1;
246 maskInterface =
nullptr;
252 parameters = passedParameters;
260 for (
unsigned i = 0; i <
D; ++i)
261 index[i] = std::llround(coordinate[i] /
gridDelta);
266 pressureLookup.clear();
272 if (std::isfinite(pressure))
273 pressureLookup[key] = pressure;
275 pressureLookup.erase(key);
281 for (
unsigned i = 0; i <
D; ++i)
282 index[i] = std::llround(coordinate[i] /
gridDelta);
289 reactionSign = (passedReactionSign < 0) ? -1 : 1;
290 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
297 const IndexType &passedMaxIndex) {
298 requestedMinIndex = passedMinIndex;
299 requestedMaxIndex = passedMaxIndex;
300 useRequestedBounds =
true;
306 useRequestedBounds =
false;
312 if (reactionInterface ==
nullptr || ambientInterface ==
nullptr) {
313 Logger::getInstance()
314 .addError(
"OxidationDiffusion: Missing level-set "
321 if (!initialiseGrid())
326 Logger::getInstance()
327 .addWarning(
"OxidationDiffusion: no oxide nodes found after "
328 "buildNodes(). Verify that the reaction and ambient "
329 "level sets enclose a non-empty oxide band.")
334 concentrationCache_.clear();
335 for (
const auto &node : nodes)
339 maxScalarVelocity_ = 0.;
340 ConstSparseIterator reactionIt(reactionInterface->getDomain());
341 for (
const auto &node : nodes) {
342 const auto sample = reactionBoundarySampleFromNode(reactionIt, node);
347 std::abs(parameters.velocitySign) * rate * sample.concentration /
348 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient);
349 maxScalarVelocity_ = std::max(maxScalarVelocity_, vel);
356 const Vec3D<T> &normalVector,
357 unsigned long )
final {
361 if (parameters.material >= 0 && material != parameters.material)
365 for (
unsigned i = 0; i <
D; ++i)
366 index[i] = std::llround(coordinate[i] /
gridDelta);
368 const auto boundarySample = reactionBoundarySample(index);
369 const IndexType rateIndex =
370 boundarySample.found ? boundarySample.nodeIndex : index;
371 const T concentration = boundarySample.found
372 ? boundarySample.concentration
376 (parameters.oxidantMoleculeDensity *
377 parameters.expansionCoefficient);
381 const Vec3D<T> & )
final {
382 if (parameters.material >= 0 && material != parameters.material)
386 std::abs(parameters.velocitySign) * parameters.reactionRate *
387 parameters.equilibriumConcentration /
388 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient);
390 return std::max(maxScalarVelocity_, bulkVel);
395 for (
unsigned i = 0; i <
D; ++i)
396 index[i] = std::llround(coordinate[i] /
gridDelta);
406 return nodes[nearby].concentration;
408 return nodes[nodeId].concentration;
413 for (
unsigned i = 0; i <
D; ++i)
414 index[i] = std::llround(coordinate[i] /
gridDelta);
419 const auto sample = reactionBoundarySample(index);
429 for (
const auto &node : nodes)
430 if (!std::isfinite(node.concentration))
436 return concentrationCache_;
440 concentrationCache_ = std::move(cache);
448 gpuPreconditioner_ = preconditioner;
460 if (nodes.empty() || ambientInterface ==
nullptr)
463 std::vector<T> concentrations;
464 ConstSparseIterator it(ambientInterface->getDomain());
465 for (; !it.isFinished(); ++it) {
468 const IndexType idx = it.getStartIndices();
477 const T value = nodeId !=
noNode ? nodes[nodeId].concentration :
T(0);
478 concentrations.push_back(std::isfinite(value) ? value :
T(0));
480 ambientInterface->getPointData().insertReplaceScalarData(
481 std::move(concentrations),
"OxConcentration");
487 if (pressureLookup.empty() || ambientInterface ==
nullptr)
490 std::vector<T> pressures;
491 ConstSparseIterator it(ambientInterface->getDomain());
492 for (; !it.isFinished(); ++it) {
495 const IndexType idx = it.getStartIndices();
497 const T value = pIt != pressureLookup.end() ? pIt->second :
T(0);
498 pressures.push_back(std::isfinite(value) ? value :
T(0));
500 ambientInterface->getPointData().insertReplaceScalarData(
501 std::move(pressures),
"OxPressure");
513 ReactionBoundarySample
516 for (
unsigned i = 0; i <
D; ++i)
517 index[i] = std::llround(coordinate[i] /
gridDelta);
518 return reactionBoundarySample(index);
528 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient));
532 bool initialiseGrid() {
534 reactionInterface, ambientInterface, maskInterface, useRequestedBounds,
535 requestedMinIndex, requestedMaxIndex, parameters.
maxGridPoints,
536 "OxidationDiffusion");
547 warmStartable_ = !concentrationCache_.empty();
548 if (!warmStartable_) {
551 const int cIdx = ambientInterface->getPointData().getScalarDataIndex(
554 ambientInterface->getPointData().getScalarDataIndex(
"OxPressure");
556 (cIdx != -1) ? ambientInterface->getPointData().getScalarData(cIdx)
559 (pIdx != -1) ? ambientInterface->getPointData().getScalarData(pIdx)
561 if (cd !=
nullptr || pd !=
nullptr) {
563 for (; !it.isFinished(); ++it) {
566 const auto ptId = it.getPointId();
567 const std::size_t key =
569 if (cd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(cd->size()) &&
570 std::isfinite((*cd)[ptId]))
571 concentrationCache_[key] = (*cd)[ptId];
572 if (pd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(pd->size()) &&
573 std::isfinite((*pd)[ptId]))
574 pressureLookup[key] = (*pd)[ptId];
576 warmStartable_ = !concentrationCache_.empty();
580 ConstSparseIterator reactionIt(reactionInterface->getDomain());
581 ConstSparseIterator ambientIt(ambientInterface->getDomain());
582 auto maskIt = makeMaskIterator();
586 const T reactionPhi =
valueAt(reactionIt, index);
587 const T ambientPhi =
valueAt(ambientIt, index);
588 if (isInsideOxide(reactionPhi, ambientPhi) &&
589 !isInsideMask(maskIt, index)) {
590 const std::size_t
id = nodes.size();
592 T seedConc = parameters.equilibriumConcentration;
595 if (cacheIt != concentrationCache_.end())
596 seedConc = cacheIt->second;
597 if (!std::isfinite(seedConc))
598 seedConc = parameters.equilibriumConcentration;
599 Node newNode{index, seedConc};
600 if (parameters.reactionRateRatio111 !=
T(1))
601 newNode.siNormal = computeSiNormal(index, reactionIt);
602 nodes.push_back(newNode);
612 const std::size_t n = nodes.size();
613 faceBCTypes_.assign(2 *
D * n, Boundary::NONE);
614 faceBCDists_.assign(2 *
D * n,
T(1));
615 ConstSparseIterator faceReactionIt(reactionInterface->getDomain());
616 ConstSparseIterator faceAmbientIt(ambientInterface->getDomain());
617 auto faceMaskIt = makeMaskIterator();
618 for (std::size_t
id = 0;
id < n; ++id) {
619 const auto &node = nodes[id];
620 for (
unsigned dir = 0; dir <
D; ++dir) {
621 for (
int off : {-1, 1}) {
622 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
623 IndexType nb = node.index;
627 const auto bc = classifyBoundary(faceReactionIt, faceAmbientIt,
628 faceMaskIt, node.index, nb);
629 faceBCTypes_[fi * n + id] = bc.first;
630 faceBCDists_[fi * n + id] = bc.second;
637 neighborIds_.assign(2 *
D * n,
noNode);
638 for (std::size_t
id = 0;
id < n; ++id) {
639 for (
unsigned dir = 0; dir <
D; ++dir) {
640 for (
int off : {-1, 1}) {
641 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
642 IndexType nb = nodes[id].index;
650 bool loggedBackend =
false;
651#ifdef VIENNALS_GPU_BICGSTAB
653 gpu::freeGpuBuffers(gpuBufs_);
659 gpuBufs_ = gpu::allocGpuBuffers(
static_cast<uint32_t
>(n), 2 *
D, useIlu0);
666 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
667 "CUDA buffers could not be allocated or the CUDA "
668 "context could not be initialized.");
671 const std::size_t nf = 2u *
D * n;
672 std::vector<uint32_t> nb32(nf);
673 for (std::size_t k = 0; k < nf; ++k)
674 nb32[k] = (neighborIds_[k] ==
noNode)
676 :
static_cast<uint32_t
>(neighborIds_[k]);
677 if (!gpu::gpuUploadNeighborIds(gpuBufs_, nb32.data(), nf)) {
678 gpu::freeGpuBuffers(gpuBufs_);
680 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
681 "uploading GPU neighbor IDs failed.");
682 }
else if (useIlu0 &&
683 !gpu::gpuSetupCSR(gpuBufs_, nb32.data(),
684 static_cast<uint32_t
>(n), 2 *
D)) {
685 gpu::freeGpuBuffers(gpuBufs_);
687 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
688 "CUSPARSE setup for the GPU BiCGSTAB solver "
692 logDiffusionBackend(
"GPU BiCGSTAB",
694 std::string(useIlu0 ?
"ILU0" :
"Jacobi"));
695 loggedBackend =
true;
700 if (!loggedBackend) {
701#ifdef VIENNALS_GPU_BICGSTAB
702 logDiffusionBackend(
"CPU BiCGSTAB",
704 ?
"GPU mode not selected"
705 :
"GPU requested but unavailable");
707 logDiffusionBackend(
"CPU BiCGSTAB",
708 "ViennaLS was built without GPU BiCGSTAB support");
722 void computeFaceCoeffs(std::vector<T> &faceCoeffs)
const {
723 const std::size_t n = nodes.size();
724 faceCoeffs.assign(2 *
D * n,
T(0));
725 const T eps = std::numeric_limits<T>::epsilon();
726 const std::vector<T> zeros(n,
T(0));
728 for (std::size_t
id = 0;
id < n; ++id) {
729 const T D_eff = getEffectiveDiffusionCoefficient(nodes[
id].index);
730 for (
unsigned dir = 0; dir <
D; ++dir) {
731 const unsigned fiNeg = dir * 2u;
732 const unsigned fiPos = dir * 2u + 1u;
736 const auto neg = makeStencilSide(
id, zeros, dir, -1, D_eff);
737 const auto pos = makeStencilSide(
id, zeros, dir, 1, D_eff);
738 const T distNeg = neg.distance;
739 const T distPos = pos.distance;
740 const T distSum = distNeg + distPos;
744 if (neighborIds_[fiNeg * n +
id] !=
noNode && distNeg > eps)
745 faceCoeffs[fiNeg * n + id] =
T(2) * D_eff / (distNeg * distSum);
746 if (neighborIds_[fiPos * n +
id] !=
noNode && distPos > eps)
747 faceCoeffs[fiPos * n + id] =
T(2) * D_eff / (distPos * distSum);
755 template <
class SolverT>
756 void computeStencilAt(std::size_t nodeId,
const std::vector<SolverT> &x,
757 T &diag,
T &rhs)
const {
760 const T D_eff = getEffectiveDiffusionCoefficient(nodes[nodeId].index);
761 for (
unsigned direction = 0; direction <
D; ++direction) {
762 const auto neg = makeStencilSide(nodeId, x, direction, -1, D_eff);
763 const auto pos = makeStencilSide(nodeId, x, direction, 1, D_eff);
764 addAxisContribution(rhs, diag, neg, pos, D_eff);
771 template <
class SolverT>
772 void matvec(
const std::vector<SolverT> &v,
773 const std::vector<T> &precomputedDiag,
const std::vector<T> &b,
774 std::vector<SolverT> &Av)
const {
775#pragma omp parallel for schedule(static)
776 for (std::size_t i = 0; i < nodes.size(); ++i) {
778 computeStencilAt(i, v, diag, rhs);
779 Av[i] =
static_cast<SolverT
>(precomputedDiag[i] *
v[i] - rhs + b[i]);
783 void solveDiffusion() {
786 normalizedResidual_ = 0.;
787 lastSolveConverged_ =
false;
789 lastSolveConverged_ =
true;
795 using SolverT = float;
797 const std::size_t n = nodes.size();
798 const T eps = std::numeric_limits<T>::epsilon();
804 std::vector<T> diag(n), b(n);
806 const std::vector<SolverT> zeros(n, SolverT(0));
807#pragma omp parallel for schedule(static)
808 for (std::size_t i = 0; i < n; ++i)
809 computeStencilAt(i, zeros, diag[i], b[i]);
814 bool finiteSystem =
true;
815 for (std::size_t i = 0; i < n; ++i) {
817 finiteSystem && std::isfinite(diag[i]) && std::isfinite(b[i]);
818 b_norm = std::max(b_norm, std::abs(b[i]));
820 if (b_norm <
T(1e-100))
823 residual = std::numeric_limits<T>::infinity();
824 normalizedResidual_ = residual;
825 Logger::getInstance()
826 .addWarning(
"solveDiffusion: assembled non-finite matrix/RHS; "
827 "rejecting this coupled trial.")
834 auto fallbackInitialGuess = [&](std::size_t i) ->
T {
835 if (std::isfinite(diag[i]) && std::isfinite(b[i]) && diag[i] > eps) {
836 const T guess = b[i] / diag[i];
837 if (std::isfinite(guess))
843 std::vector<SolverT> x(n);
844 for (std::size_t i = 0; i < n; ++i) {
846 warmStartable_ ? nodes[i].concentration : fallbackInitialGuess(i);
847 if (!std::isfinite(guess))
848 guess = fallbackInitialGuess(i);
849 x[i] =
static_cast<SolverT
>(guess);
850 if (!std::isfinite(x[i]))
853 const auto initialGuess = x;
855#ifdef VIENNALS_GPU_BICGSTAB
862 if (gpu::gpuIsValid(gpuBufs_)) {
863 Timer<> tPrep, tUpload, tSolve;
865 std::vector<double> diagGpu(n), bGpu(n);
866 for (std::size_t i = 0; i < n; ++i) {
867 diagGpu[i] =
static_cast<double>(diag[i]);
868 bGpu[i] =
static_cast<double>(b[i]);
872 std::vector<T> faceCoeffs;
873 computeFaceCoeffs(faceCoeffs);
874 std::vector<double> coeffGpu(faceCoeffs.size());
875 for (std::size_t k = 0; k < faceCoeffs.size(); ++k)
876 coeffGpu[k] =
static_cast<double>(faceCoeffs[k]);
878 std::vector<double> xGpu(n);
879 for (std::size_t i = 0; i < n; ++i)
880 xGpu[i] =
static_cast<double>(x[i]);
884 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
885 gpuBufs_, diagGpu.data(), bGpu.data(), coeffGpu.data(),
886 static_cast<uint32_t
>(n), faceCoeffs.size());
889 if (Logger::hasDebug()) {
890 const std::string tag =
891 "diffusion n=" + std::to_string(n) +
" [GPU upload failed]";
892 Logger::getInstance()
893 .addTiming(tag +
" diag/b precompute", tDiag)
894 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
895 .addTiming(tag +
" GPU upload", tUpload)
898 VIENNACORE_LOG_ERROR(
"OxidationDiffusion: GPU mode was selected, but "
899 "uploading GPU solver arrays or factorizing ILU "
906 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
907 gpuBufs_, xGpu.data(),
static_cast<double>(eps),
908 parameters.maxIterations,
static_cast<double>(parameters.tolerance),
909 iterations, residual);
912 const bool finiteGpuSolution =
914 std::all_of(xGpu.begin(), xGpu.end(),
915 [](
double value) { return std::isfinite(value); });
916 const unsigned gpuIterations = iterations;
917 const T gpuResidual = residual;
919 if (finiteGpuSolution) {
922 for (std::size_t i = 0; i < n; ++i)
923 nodes[i].concentration =
static_cast<T>(xGpu[i]);
924 normalizedResidual_ = gpuResidual / b_norm;
925 lastSolveConverged_ = std::isfinite(normalizedResidual_) &&
926 normalizedResidual_ <= parameters.tolerance;
928 if (Logger::hasTiming()) {
929 const std::string tag =
"diffusion n=" + std::to_string(n) +
930 " iters=" + std::to_string(iterations) +
931 " res=" + std::to_string(residual) +
" [GPU]";
932 Logger::getInstance()
933 .addTiming(tag +
" GPU BiCGSTAB", tSolve)
936 if (Logger::hasDebug()) {
937 const std::string tag =
"diffusion n=" + std::to_string(n) +
" [GPU]";
938 Logger::getInstance()
939 .addTiming(tag +
" diag/b precompute", tDiag)
940 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
941 .addTiming(tag +
" GPU upload", tUpload)
947 if (Logger::hasDebug()) {
948 const std::string tag =
"diffusion n=" + std::to_string(n) +
949 " iters=" + std::to_string(gpuIterations) +
950 " res=" + std::to_string(gpuResidual) +
952 Logger::getInstance()
953 .addTiming(tag +
" diag/b precompute", tDiag)
954 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
955 .addTiming(tag +
" GPU upload", tUpload)
956 .addTiming(tag +
" GPU BiCGSTAB", tSolve)
961 reportGpuUnavailable(
962 "OxidationDiffusion: GPU mode was selected, but GPU BiCGSTAB "
963 "failed, did not converge, or produced non-finite concentrations "
965 std::to_string(gpuIterations) +
966 ", residual=" + std::to_string(gpuResidual) +
").");
970 VIENNACORE_LOG_ERROR(
"OxidationDiffusion: explicit GPU mode was "
971 "requested, but ViennaLS was built without "
972 "VIENNALS_GPU_BICGSTAB.");
974 VIENNACORE_LOG_WARNING(
"OxidationDiffusion: GPU mode Auto was requested, "
975 "but ViennaLS was built without "
976 "VIENNALS_GPU_BICGSTAB. Using the CPU solver.");
981 std::vector<SolverT> Ax(n);
982 matvec(x, diag, b, Ax);
983 std::vector<SolverT> r(n), r_hat(n);
984 for (std::size_t i = 0; i < n; ++i) {
985 r[i] =
static_cast<SolverT
>(b[i] - Ax[i]);
994 std::vector<SolverT> p(n, SolverT(0)),
v(n, SolverT(0)), y(n), z(n), s(n),
996 T rho =
T(1), alpha =
T(1), omega =
T(1);
997 bool bicgstabBreakdown =
false;
998 for (std::size_t i = 0; i < n; ++i) {
999 const T ri =
static_cast<T>(r[i]);
1000 if (!std::isfinite(ri)) {
1001 bicgstabBreakdown =
true;
1004 residual = std::max(residual, std::abs(ri));
1007 for (; !bicgstabBreakdown && iterations < parameters.maxIterations;
1010 for (std::size_t i = 0; i < n; ++i)
1011 rho_new +=
static_cast<T>(r_hat[i]) *
static_cast<T>(r[i]);
1013 if (!std::isfinite(rho_new)) {
1014 bicgstabBreakdown =
true;
1017 if (std::abs(rho_new) <
T(1e-100))
1019 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
1020 !std::isfinite(omega) || std::abs(omega) <
T(1e-100)) {
1021 bicgstabBreakdown =
true;
1025 const T beta = (rho_new / rho) * (alpha / omega);
1026 if (!std::isfinite(beta)) {
1027 bicgstabBreakdown =
true;
1032 for (std::size_t i = 0; i < n; ++i)
1033 p[i] =
static_cast<SolverT
>(r[i] + beta * (p[i] - omega * v[i]));
1035 for (std::size_t i = 0; i < n; ++i) {
1037 y[i] =
static_cast<SolverT
>((diag[i] > eps) ? pi / diag[i] : pi);
1040 matvec(y, diag, b, v);
1043 for (std::size_t i = 0; i < n; ++i)
1044 r_hat_v +=
static_cast<T>(r_hat[i]) *
static_cast<T>(
v[i]);
1045 if (!std::isfinite(r_hat_v)) {
1046 bicgstabBreakdown =
true;
1049 if (std::abs(r_hat_v) <
T(1e-100))
1052 alpha = rho_new / r_hat_v;
1053 if (!std::isfinite(alpha)) {
1054 bicgstabBreakdown =
true;
1058 for (std::size_t i = 0; i < n; ++i)
1059 s[i] =
static_cast<SolverT
>(r[i] - alpha * v[i]);
1062 for (std::size_t i = 0; i < n; ++i)
1063 residual = std::max(residual, std::abs(
static_cast<T>(s[i])));
1064 if (!std::isfinite(residual)) {
1065 bicgstabBreakdown =
true;
1068 if (residual < parameters.tolerance * b_norm) {
1069 for (std::size_t i = 0; i < n; ++i)
1070 x[i] =
static_cast<SolverT
>(x[i] + alpha * y[i]);
1075 for (std::size_t i = 0; i < n; ++i) {
1077 z[i] =
static_cast<SolverT
>((diag[i] > eps) ? si / diag[i] : si);
1080 matvec(z, diag, b, t);
1082 T t_s =
T(0), t_t =
T(0);
1083 for (std::size_t i = 0; i < n; ++i) {
1084 t_s +=
static_cast<T>(t[i]) *
static_cast<T>(s[i]);
1085 t_t +=
static_cast<T>(t[i]) *
static_cast<T>(t[i]);
1087 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
1088 bicgstabBreakdown =
true;
1091 omega = (t_t >
T(1e-100)) ? t_s / t_t :
T(0);
1092 if (!std::isfinite(omega)) {
1093 bicgstabBreakdown =
true;
1097 for (std::size_t i = 0; i < n; ++i) {
1098 x[i] =
static_cast<SolverT
>(x[i] + alpha * y[i] + omega * z[i]);
1099 r[i] =
static_cast<SolverT
>(s[i] - omega * t[i]);
1103 for (std::size_t i = 0; i < n; ++i)
1104 residual = std::max(residual, std::abs(
static_cast<T>(r[i])));
1105 if (!std::isfinite(residual)) {
1106 bicgstabBreakdown =
true;
1109 if (residual < parameters.tolerance * b_norm) {
1115 if (bicgstabBreakdown)
1116 residual = std::numeric_limits<T>::infinity();
1118 const bool finiteCpuSolution =
1119 !bicgstabBreakdown &&
1120 std::all_of(x.begin(), x.end(),
1121 [](SolverT value) { return std::isfinite(value); });
1122 if (finiteCpuSolution) {
1123 for (std::size_t i = 0; i < n; ++i)
1124 nodes[i].concentration = x[i];
1126 for (std::size_t i = 0; i < n; ++i)
1127 nodes[i].concentration = initialGuess[i];
1128 residual = std::numeric_limits<T>::infinity();
1129 Logger::getInstance()
1130 .addWarning(
"solveDiffusion: CPU BiCGSTAB produced non-finite "
1131 "concentrations; keeping the sanitized initial guess.")
1134 normalizedResidual_ = residual / b_norm;
1135 lastSolveConverged_ = finiteCpuSolution &&
1136 std::isfinite(normalizedResidual_) &&
1137 normalizedResidual_ <= parameters.tolerance;
1140 if (Logger::hasTiming()) {
1141 const std::string path =
" [CPU]";
1142 const std::string tag =
"diffusion n=" + std::to_string(n) +
1143 " iters=" + std::to_string(iterations) +
1144 " res=" + std::to_string(residual) + path;
1145 Logger::getInstance().addTiming(tag +
" CPU BiCGSTAB", tCpuSolve).print();
1147 if (Logger::hasDebug()) {
1148 const std::string path =
" [CPU]";
1149 Logger::getInstance()
1150 .addTiming(
"diffusion n=" + std::to_string(n) + path +
1151 " diag/b precompute",
1155 if (residual > parameters.tolerance * b_norm)
1156 VIENNACORE_LOG_WARNING(
1157 "solveDiffusion: BiCGSTAB did not converge after " +
1158 std::to_string(iterations) +
"/" +
1159 std::to_string(parameters.maxIterations) +
1160 " iterations (residual=" + std::to_string(residual / b_norm) +
1161 ", tolerance=" + std::to_string(parameters.tolerance) +
")");
1164#ifdef VIENNALS_GPU_BICGSTAB
1165 static std::string gpuErrorDetail() {
1166 const char *detail = gpu::gpuGetLastErrorMessage();
1167 if (detail && detail[0] !=
'\0')
1168 return std::string(
" Detail: ") + detail;
1176 void reportGpuUnavailable(
const std::string &message)
const {
1178 VIENNACORE_LOG_WARNING(message + gpuErrorDetail() +
1179 " Falling back to the CPU solver.");
1181 VIENNACORE_LOG_ERROR(message + gpuErrorDetail());
1186 void logDiffusionBackend(
const std::string &backend,
1187 const std::string &detail)
const {
1188 if (!Logger::hasInfo())
1190 const std::string msg =
1191 "OxidationDiffusion: using " + backend +
1192 " for diffusion solve (nodes=" + std::to_string(nodes.size()) +
1193 (detail.empty() ? std::string() :
", " + detail) +
").";
1194 if (msg == lastLoggedBackend_)
1196 lastLoggedBackend_ = msg;
1197 Logger::getInstance().addInfo(msg).print();
1202 template <
class SolverT>
1204 makeStencilSide(std::size_t nodeId,
const std::vector<SolverT> &previous,
1205 unsigned direction,
int offset,
T diffusion)
const {
1206 const IndexType &nodeIndex = nodes[nodeId].index;
1207 IndexType neighbor = nodeIndex;
1208 neighbor[direction] += offset;
1211 return zeroFluxSide();
1213 const std::size_t neighborId =
lookupNode(neighbor);
1214 if (neighborId !=
noNode)
1215 return {
gridDelta, 0., previous[neighborId]};
1217 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
1218 const std::size_t n = nodes.size();
1219 const Boundary faceType = faceBCTypes_[fi * n + nodeId];
1220 const T faceDist = faceBCDists_[fi * n + nodeId];
1221 if (faceType == Boundary::REACTION)
1222 return reactionBoundarySide(nodeIndex, faceDist, diffusion);
1223 if (faceType == Boundary::AMBIENT)
1224 return ambientBoundarySide(faceDist, diffusion);
1225 if (faceType == Boundary::MASK)
1226 return maskBoundarySide(faceDist, diffusion);
1227 return zeroFluxSide();
1230 void addAxisContribution(
T &rightHandSide,
T &diagonal,
1231 const StencilSide &negativeSide,
1232 const StencilSide &positiveSide,
T diffusion)
const {
1233 const T distanceSum = negativeSide.distance + positiveSide.distance;
1234 if (distanceSum <= std::numeric_limits<T>::epsilon())
1237 addSideContribution(rightHandSide, diagonal, negativeSide, distanceSum,
1239 addSideContribution(rightHandSide, diagonal, positiveSide, distanceSum,
1243 void addSideContribution(
T &rightHandSide,
T &diagonal,
1244 const StencilSide &side,
T distanceSum,
1245 T diffusion)
const {
1246 if (side.distance <= std::numeric_limits<T>::epsilon())
1249 const T coefficient =
T(2) *
diffusion / (side.distance * distanceSum);
1250 rightHandSide += coefficient * side.constant;
1251 diagonal += coefficient * (
T(1) - side.nodeCoefficient);
1254 StencilSide zeroFluxSide()
const {
return {
gridDelta, 1., 0.}; }
1256 StencilSide reactionBoundarySide(
const IndexType &nodeIndex,
T distance,
1257 T diffusion)
const {
1260 const T denominator = conductance + reactionRate;
1261 if (denominator <= std::numeric_limits<T>::epsilon())
1262 return zeroFluxSide();
1264 return {distance, conductance / denominator, 0.};
1268 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1270 const std::size_t directId =
lookupNode(index);
1271 if (directId !=
noNode) {
1273 reactionBoundarySampleFromNode(reactionIt, nodes[directId]);
1283 std::size_t bestNode = std::numeric_limits<std::size_t>::max();
1284 T bestDistance2 = std::numeric_limits<T>::max();
1286 for (
int radius = 1; radius <= 4; ++radius) {
1288 offset.fill(-radius);
1290 IndexType candidate = index;
1292 for (
unsigned d = 0; d <
D; ++d) {
1293 candidate[d] += offset[d];
1294 distance2 +=
static_cast<T>(offset[d] * offset[d]);
1297 if (distance2 >
T(0) &&
inBounds(candidate)) {
1299 if (foundId !=
noNode && distance2 < bestDistance2) {
1301 reactionBoundarySampleFromNode(reactionIt, nodes[foundId]);
1303 bestDistance2 = distance2;
1310 for (; dim <
D; ++dim) {
1311 if (offset[dim] < radius) {
1315 offset[dim] = -radius;
1321 if (bestNode != std::numeric_limits<std::size_t>::max())
1325 if (bestNode == std::numeric_limits<std::size_t>::max())
1327 return reactionBoundarySampleFromNode(reactionIt, nodes[bestNode]);
1331 reactionBoundarySampleFromNode(ConstSparseIterator &reactionIt,
1332 const Node &node)
const {
1335 T bestDistance = std::numeric_limits<T>::max();
1336 const T insidePhi =
valueAt(reactionIt, node.index);
1338 for (
unsigned direction = 0; direction <
D; ++direction) {
1339 for (
const int offset : {-1, 1}) {
1340 IndexType neighbor = node.index;
1341 neighbor[direction] += offset;
1345 const T outsidePhi =
valueAt(reactionIt, neighbor);
1346 if (!
crosses(insidePhi, outsidePhi))
1349 const T distance = crossingDistance(insidePhi, outsidePhi);
1350 if (distance >= bestDistance)
1353 const T diffusion = getEffectiveDiffusionCoefficient(node.index);
1354 const auto side = reactionBoundarySide(node.index, distance, diffusion);
1356 best.distance = distance;
1357 best.concentration =
1358 side.nodeCoefficient * node.concentration + side.constant;
1359 best.crossingAxis = direction;
1360 best.crossingOffset = offset;
1361 bestDistance = distance;
1368 StencilSide ambientBoundarySide(
T distance,
T diffusion)
const {
1370 const T denominator = conductance + parameters.transferCoefficient;
1371 if (denominator <= std::numeric_limits<T>::epsilon())
1372 return zeroFluxSide();
1374 return {distance, conductance / denominator,
1375 parameters.transferCoefficient *
1376 parameters.equilibriumConcentration / denominator};
1379 StencilSide maskBoundarySide(
T distance,
T diffusion)
const {
1380 if (parameters.maskTransferCoefficient <= std::numeric_limits<T>::epsilon())
1381 return zeroFluxSide();
1384 const T denominator = conductance + parameters.maskTransferCoefficient;
1385 if (denominator <= std::numeric_limits<T>::epsilon())
1386 return zeroFluxSide();
1388 return {distance, conductance / denominator,
1389 parameters.maskTransferCoefficient * parameters.maskConcentration /
1394 T rate = parameters.reactionRate;
1396 if (parameters.reactionActivationVolume !=
T(0)) {
1397 T pressure = parameters.referencePressure;
1398 const auto foundPressure =
1400 if (foundPressure != pressureLookup.end())
1401 pressure = foundPressure->second;
1402 if (!std::isfinite(pressure))
1403 pressure = parameters.referencePressure;
1405 stressExponent(pressure, parameters.reactionActivationVolume);
1406 rate *= stressFactor(exponent);
1409 if (parameters.reactionRateRatio111 !=
T(1)) {
1410 const std::size_t nodeId =
lookupNode(index);
1412 const auto &normal = nodes[nodeId].siNormal;
1414 for (
unsigned d = 0; d <
D; ++d)
1415 dot += normal[d] * parameters.crystalAxis[d];
1417 (parameters.reactionRateRatio111 -
T(1)) * (
T(1) - dot * dot);
1424 T getEffectiveDiffusionCoefficient(
const IndexType &index)
const {
1425 if (parameters.diffusionActivationVolume ==
T(0))
1426 return parameters.diffusionCoefficient;
1428 T pressure = parameters.referencePressure;
1430 if (found != pressureLookup.end())
1431 pressure = found->second;
1432 if (!std::isfinite(pressure))
1433 pressure = parameters.referencePressure;
1436 stressExponent(pressure, parameters.diffusionActivationVolume);
1437 return parameters.diffusionCoefficient * stressFactor(exponent);
1440 T stressExponent(
T pressure,
T activationVolume)
const {
1441 const T thermalEnergy =
1442 boltzmannConstant * std::max(parameters.temperature,
T(1.));
1443 return -(pressure - parameters.referencePressure) * activationVolume /
1447 static T stressFactor(
T exponent) {
1448 if (!std::isfinite(exponent))
1450 if (exponent <= std::log(minStressFactor))
1451 return minStressFactor;
1452 if (exponent >= std::log(maxStressFactor))
1453 return maxStressFactor;
1454 return std::exp(exponent);
1457 Vec3D<T> computeSiNormal(
const IndexType &index,
1458 ConstSparseIterator &reactionIt)
const {
1462 auto reflectToGrid = [&](IndexType idx) {
1463 auto &g = reactionInterface->getGrid();
1464 for (
unsigned d2 = 0;
d2 <
D; ++
d2) {
1465 const auto lo = g.getMinGridPoint(d2);
1466 const auto hi = g.getMaxGridPoint(d2);
1468 idx[
d2] = 2 * lo - idx[
d2];
1470 idx[
d2] = 2 * hi - idx[
d2];
1474 Vec3D<T> gradient{0., 0., 0.};
1475 for (
unsigned d = 0; d <
D; ++d) {
1476 IndexType plus = index, minus = index;
1482 valueAt(reactionIt, reflectToGrid(minus)))) /
1486 for (
unsigned d = 0; d <
D; ++d)
1487 len += gradient[d] * gradient[d];
1488 len = std::sqrt(len);
1489 if (len > std::numeric_limits<T>::epsilon()) {
1490 for (
unsigned d = 0; d <
D; ++d)
1493 gradient = Vec3D<T>{0., 0., 0.};
1494 gradient[
D - 1] =
T(1);
1499 std::pair<Boundary, T> classifyBoundary(ConstSparseIterator &reactionIt,
1500 ConstSparseIterator &ambientIt,
1501 ConstSparseIterator &maskIt,
1502 const IndexType &inside,
1503 const IndexType &outside)
const {
1504 const T reactionInside =
valueAt(reactionIt, inside);
1505 const T reactionOutside =
valueAt(reactionIt, outside);
1506 const T ambientInside =
valueAt(ambientIt, inside);
1507 const T ambientOutside =
valueAt(ambientIt, outside);
1508 const T maskInside = valueAtMask(maskIt, inside);
1509 const T maskOutside = valueAtMask(maskIt, outside);
1511 const T reactionDistance =
1512 crosses(reactionInside, reactionOutside)
1513 ? crossingDistance(reactionInside, reactionOutside)
1514 : std::numeric_limits<
T>::max();
1515 const T ambientDistance =
1516 crosses(ambientInside, ambientOutside)
1517 ? crossingDistance(ambientInside, ambientOutside)
1518 : std::numeric_limits<
T>::max();
1519 const T maskDistance =
1520 maskInterface !=
nullptr &&
crosses(maskInside, maskOutside)
1521 ? crossingDistance(maskInside, maskOutside)
1522 : std::numeric_limits<
T>::max();
1524 if (reactionDistance == std::numeric_limits<T>::max() &&
1525 ambientDistance == std::numeric_limits<T>::max() &&
1526 maskDistance == std::numeric_limits<T>::max())
1529 if (reactionDistance <= ambientDistance && reactionDistance <= maskDistance)
1530 return {Boundary::REACTION, reactionDistance};
1538 if (ambientDistance != std::numeric_limits<T>::max() &&
1539 (isMaskAtCrossing(maskInside, maskOutside, ambientDistance) ||
1540 (maskInterface !=
nullptr &&
1541 static_cast<T>(maskSign) * valueAtMask(maskIt, outside) >=
T(0))))
1542 return {Boundary::MASK, ambientDistance};
1544 if (maskDistance <= ambientDistance)
1545 return {Boundary::MASK, maskDistance};
1546 return {Boundary::AMBIENT, ambientDistance};
1549 bool isInsideOxide(
T reactionPhi,
T ambientPhi)
const {
1555 constexpr T eps =
T(1e-9);
1556 return reactionSign * reactionPhi >= -eps &&
1557 ambientSign * ambientPhi >= -eps;
1560 ConstSparseIterator makeMaskIterator()
const {
1561 if (maskInterface ==
nullptr)
1566 bool isInsideMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
1567 if (maskInterface ==
nullptr)
1569 return maskSign *
valueAt(maskIt, index) >= 0.;
1572 T valueAtMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
1573 if (maskInterface ==
nullptr)
1574 return std::numeric_limits<T>::max();
1575 return valueAt(maskIt, index);
1578 bool isMaskAtCrossing(
T maskInside,
T maskOutside,
T distance)
const {
1579 if (maskInterface ==
nullptr)
1581 const T fraction = std::clamp(distance /
gridDelta,
T(0),
T(1));
1584 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
1585 return static_cast<T>(maskSign) * maskPhi >=
T(0);
1588 T crossingDistance(
T insidePhi,
T outsidePhi)
const {
1590 insidePhi, outsidePhi, parameters.minBoundaryDistance,
gridDelta);