99 using IndexType = viennahrle::Index<D>;
100 using ConstSparseIterator =
101 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
102 using IndexCacheMap =
103 std::unordered_map<IndexType, T, typename IndexType::hash>;
106 static constexpr T boltzmannConstant =
T(1.380649e-23);
107 static constexpr T minStressFactor =
T(1.e-6);
108 static constexpr T maxStressFactor =
T(1.e6);
128 enum class Boundary { NONE, REACTION, AMBIENT, MASK };
132 T concentration = 0.;
133 Vec3D<T> siNormal = {0., 0.,
139 T nodeCoefficient = 1.;
143 SmartPointer<Domain<T, D>> reactionInterface =
nullptr;
144 SmartPointer<Domain<T, D>> ambientInterface =
nullptr;
145 SmartPointer<Domain<T, D>> maskInterface =
nullptr;
147 int reactionSign = 1;
148 int ambientSign = -1;
150 IndexType requestedMinIndex{};
151 IndexType requestedMaxIndex{};
152 unsigned iterations = 0;
153 T residual = std::numeric_limits<T>::max();
154 T normalizedResidual_ = std::numeric_limits<T>::max();
155 bool lastSolveConverged_ =
false;
156 T maxScalarVelocity_ = 0.;
158 bool nodesDirty_ =
true;
161 bool useRequestedBounds =
false;
162 bool warmStartable_ =
164 IndexCacheMap pressureLookup;
165 IndexCacheMap concentrationCache_;
166 std::vector<Node> nodes;
170 std::vector<Boundary> faceBCTypes_;
171 std::vector<T> faceBCDists_;
174 std::vector<std::size_t> neighborIds_;
179#ifdef VIENNALS_GPU_BICGSTAB
183 gpu::GpuBiCGSTABBuffers *gpuBufs_ =
nullptr;
205 : reactionInterface(passedReactionInterface),
206 ambientInterface(passedAmbientInterface), parameters(passedParameters) {
210#ifdef VIENNALS_GPU_BICGSTAB
211 gpu::freeGpuBuffers(gpuBufs_);
220 template <
class... Args>
static auto New(Args &&...args) {
221 return SmartPointer<OxidationDiffusion>::New(std::forward<Args>(args)...);
232 reactionInterface = passedInterface;
238 ambientInterface = passedInterface;
244 int passedMaskSign = 1) {
245 maskInterface = passedInterface;
246 maskSign = (passedMaskSign < 0) ? -1 : 1;
252 maskInterface =
nullptr;
258 parameters = passedParameters;
266 for (
unsigned i = 0; i <
D; ++i)
267 index[i] = std::llround(coordinate[i] /
gridDelta);
272 pressureLookup.clear();
277 if (std::isfinite(pressure))
278 pressureLookup[index] = pressure;
280 pressureLookup.erase(index);
286 for (
unsigned i = 0; i <
D; ++i)
287 index[i] = std::llround(coordinate[i] /
gridDelta);
294 reactionSign = (passedReactionSign < 0) ? -1 : 1;
295 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
302 const IndexType &passedMaxIndex) {
303 requestedMinIndex = passedMinIndex;
304 requestedMaxIndex = passedMaxIndex;
305 useRequestedBounds =
true;
311 useRequestedBounds =
false;
317 if (reactionInterface ==
nullptr || ambientInterface ==
nullptr) {
318 Logger::getInstance()
319 .addError(
"OxidationDiffusion: Missing level-set "
326 if (!initialiseGrid())
331 Logger::getInstance()
332 .addWarning(
"OxidationDiffusion: no oxide nodes found after "
333 "buildNodes(). Verify that the reaction and ambient "
334 "level sets enclose a non-empty oxide band.")
339 concentrationCache_.clear();
340 for (
const auto &node : nodes)
341 concentrationCache_[node.index] = node.concentration;
343 maxScalarVelocity_ = 0.;
344 ConstSparseIterator reactionIt(reactionInterface->getDomain());
345 for (
const auto &node : nodes) {
346 const auto sample = reactionBoundarySampleFromNode(reactionIt, node);
351 std::abs(parameters.velocitySign) * rate * sample.concentration /
352 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient);
353 maxScalarVelocity_ = std::max(maxScalarVelocity_, vel);
360 const Vec3D<T> &normalVector,
361 unsigned long )
final {
365 if (parameters.material >= 0 && material != parameters.material)
369 for (
unsigned i = 0; i <
D; ++i)
370 index[i] = std::llround(coordinate[i] /
gridDelta);
372 const auto boundarySample = reactionBoundarySample(index);
373 const IndexType rateIndex =
374 boundarySample.found ? boundarySample.nodeIndex : index;
375 const T concentration = boundarySample.found
376 ? boundarySample.concentration
380 (parameters.oxidantMoleculeDensity *
381 parameters.expansionCoefficient);
385 const Vec3D<T> & )
final {
386 if (parameters.material >= 0 && material != parameters.material)
390 std::abs(parameters.velocitySign) * parameters.reactionRate *
391 parameters.equilibriumConcentration /
392 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient);
394 return std::max(maxScalarVelocity_, bulkVel);
399 for (
unsigned i = 0; i <
D; ++i)
400 index[i] = std::llround(coordinate[i] /
gridDelta);
410 return nodes[nearby].concentration;
412 return nodes[nodeId].concentration;
417 for (
unsigned i = 0; i <
D; ++i)
418 index[i] = std::llround(coordinate[i] /
gridDelta);
423 const auto sample = reactionBoundarySample(index);
433 for (
const auto &node : nodes)
434 if (!std::isfinite(node.concentration))
440 return concentrationCache_;
444 concentrationCache_ = std::move(cache);
452 gpuPreconditioner_ = preconditioner;
464 if (nodes.empty() || ambientInterface ==
nullptr)
467 std::vector<T> concentrations;
468 ConstSparseIterator it(ambientInterface->getDomain());
469 for (; !it.isFinished(); ++it) {
472 const IndexType idx = it.getStartIndices();
481 const T value = nodeId !=
noNode ? nodes[nodeId].concentration :
T(0);
482 concentrations.push_back(std::isfinite(value) ? value :
T(0));
484 ambientInterface->getPointData().insertReplaceScalarData(
485 std::move(concentrations),
"OxConcentration");
491 if (pressureLookup.empty() || ambientInterface ==
nullptr)
494 std::vector<T> pressures;
495 ConstSparseIterator it(ambientInterface->getDomain());
496 for (; !it.isFinished(); ++it) {
499 const IndexType idx = it.getStartIndices();
500 const auto pIt = pressureLookup.find(idx);
501 const T value = pIt != pressureLookup.end() ? pIt->second :
T(0);
502 pressures.push_back(std::isfinite(value) ? value :
T(0));
504 ambientInterface->getPointData().insertReplaceScalarData(
505 std::move(pressures),
"OxPressure");
517 ReactionBoundarySample
520 for (
unsigned i = 0; i <
D; ++i)
521 index[i] = std::llround(coordinate[i] /
gridDelta);
522 return reactionBoundarySample(index);
532 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient));
536 bool initialiseGrid() {
538 reactionInterface, ambientInterface, maskInterface, useRequestedBounds,
539 requestedMinIndex, requestedMaxIndex, parameters.
maxGridPoints,
540 "OxidationDiffusion");
551 warmStartable_ = !concentrationCache_.empty();
552 if (!warmStartable_) {
555 const int cIdx = ambientInterface->getPointData().getScalarDataIndex(
558 ambientInterface->getPointData().getScalarDataIndex(
"OxPressure");
560 (cIdx != -1) ? ambientInterface->getPointData().getScalarData(cIdx)
563 (pIdx != -1) ? ambientInterface->getPointData().getScalarData(pIdx)
565 if (cd !=
nullptr || pd !=
nullptr) {
567 for (; !it.isFinished(); ++it) {
570 const auto ptId = it.getPointId();
571 const auto key = it.getStartIndices();
572 if (cd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(cd->size()) &&
573 std::isfinite((*cd)[ptId]))
574 concentrationCache_[key] = (*cd)[ptId];
575 if (pd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(pd->size()) &&
576 std::isfinite((*pd)[ptId]))
577 pressureLookup[key] = (*pd)[ptId];
579 warmStartable_ = !concentrationCache_.empty();
583 ConstSparseIterator reactionIt(reactionInterface->getDomain());
584 ConstSparseIterator ambientIt(ambientInterface->getDomain());
585 auto maskIt = makeMaskIterator();
589 const T reactionPhi =
valueAt(reactionIt, index);
590 const T ambientPhi =
valueAt(ambientIt, index);
591 if (isInsideOxide(reactionPhi, ambientPhi) &&
592 !isInsideMask(maskIt, index)) {
593 const std::size_t
id = nodes.size();
595 T seedConc = parameters.equilibriumConcentration;
596 auto cacheIt = concentrationCache_.find(index);
597 if (cacheIt != concentrationCache_.end())
598 seedConc = cacheIt->second;
599 if (!std::isfinite(seedConc))
600 seedConc = parameters.equilibriumConcentration;
601 Node newNode{index, seedConc};
602 if (parameters.reactionRateRatio111 !=
T(1))
603 newNode.siNormal = computeSiNormal(index, reactionIt);
604 nodes.push_back(newNode);
614 const std::size_t n = nodes.size();
615 faceBCTypes_.assign(2 *
D * n, Boundary::NONE);
616 faceBCDists_.assign(2 *
D * n,
T(1));
617 ConstSparseIterator faceReactionIt(reactionInterface->getDomain());
618 ConstSparseIterator faceAmbientIt(ambientInterface->getDomain());
619 auto faceMaskIt = makeMaskIterator();
620 for (std::size_t
id = 0;
id < n; ++id) {
621 const auto &node = nodes[id];
622 for (
unsigned dir = 0; dir <
D; ++dir) {
623 for (
int off : {-1, 1}) {
624 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
625 IndexType nb = node.index;
629 const auto bc = classifyBoundary(faceReactionIt, faceAmbientIt,
630 faceMaskIt, node.index, nb);
631 faceBCTypes_[fi * n + id] = bc.first;
632 faceBCDists_[fi * n + id] = bc.second;
639 neighborIds_.assign(2 *
D * n,
noNode);
640 for (std::size_t
id = 0;
id < n; ++id) {
641 for (
unsigned dir = 0; dir <
D; ++dir) {
642 for (
int off : {-1, 1}) {
643 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
644 IndexType nb = nodes[id].index;
652 bool loggedBackend =
false;
653#ifdef VIENNALS_GPU_BICGSTAB
655 gpu::freeGpuBuffers(gpuBufs_);
661 gpuBufs_ = gpu::allocGpuBuffers(
static_cast<uint32_t
>(n), 2 *
D, useIlu0);
668 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
669 "CUDA buffers could not be allocated or the CUDA "
670 "context could not be initialized.");
673 const std::size_t nf = 2u *
D * n;
674 std::vector<uint32_t> nb32(nf);
675 for (std::size_t k = 0; k < nf; ++k)
676 nb32[k] = (neighborIds_[k] ==
noNode)
678 :
static_cast<uint32_t
>(neighborIds_[k]);
679 if (!gpu::gpuUploadNeighborIds(gpuBufs_, nb32.data(), nf)) {
680 gpu::freeGpuBuffers(gpuBufs_);
682 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
683 "uploading GPU neighbor IDs failed.");
684 }
else if (useIlu0 &&
685 !gpu::gpuSetupCSR(gpuBufs_, nb32.data(),
686 static_cast<uint32_t
>(n), 2 *
D)) {
687 gpu::freeGpuBuffers(gpuBufs_);
689 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
690 "CUSPARSE setup for the GPU BiCGSTAB solver "
694 logDiffusionBackend(
"GPU BiCGSTAB",
696 std::string(useIlu0 ?
"ILU0" :
"Jacobi"));
697 loggedBackend =
true;
702 if (!loggedBackend) {
703#ifdef VIENNALS_GPU_BICGSTAB
704 logDiffusionBackend(
"CPU BiCGSTAB",
706 ?
"GPU mode not selected"
707 :
"GPU requested but unavailable");
709 logDiffusionBackend(
"CPU BiCGSTAB",
710 "ViennaLS was built without GPU BiCGSTAB support");
724 void computeFaceCoeffs(std::vector<T> &faceCoeffs)
const {
725 const std::size_t n = nodes.size();
726 faceCoeffs.assign(2 *
D * n,
T(0));
727 const T eps = std::numeric_limits<T>::epsilon();
728 const std::vector<T> zeros(n,
T(0));
730 for (std::size_t
id = 0;
id < n; ++id) {
731 const T D_eff = getEffectiveDiffusionCoefficient(nodes[
id].index);
732 for (
unsigned dir = 0; dir <
D; ++dir) {
733 const unsigned fiNeg = dir * 2u;
734 const unsigned fiPos = dir * 2u + 1u;
738 const auto neg = makeStencilSide(
id, zeros, dir, -1, D_eff);
739 const auto pos = makeStencilSide(
id, zeros, dir, 1, D_eff);
740 const T distNeg = neg.distance;
741 const T distPos = pos.distance;
742 const T distSum = distNeg + distPos;
746 if (neighborIds_[fiNeg * n +
id] !=
noNode && distNeg > eps)
747 faceCoeffs[fiNeg * n + id] =
T(2) * D_eff / (distNeg * distSum);
748 if (neighborIds_[fiPos * n +
id] !=
noNode && distPos > eps)
749 faceCoeffs[fiPos * n + id] =
T(2) * D_eff / (distPos * distSum);
757 template <
class SolverT>
758 void computeStencilAt(std::size_t nodeId,
const std::vector<SolverT> &x,
759 T &diag,
T &rhs)
const {
762 const T D_eff = getEffectiveDiffusionCoefficient(nodes[nodeId].index);
763 for (
unsigned direction = 0; direction <
D; ++direction) {
764 const auto neg = makeStencilSide(nodeId, x, direction, -1, D_eff);
765 const auto pos = makeStencilSide(nodeId, x, direction, 1, D_eff);
766 addAxisContribution(rhs, diag, neg, pos, D_eff);
773 template <
class SolverT>
774 void matvec(
const std::vector<SolverT> &v,
775 const std::vector<T> &precomputedDiag,
const std::vector<T> &b,
776 std::vector<SolverT> &Av)
const {
777#pragma omp parallel for schedule(static)
778 for (std::size_t i = 0; i < nodes.size(); ++i) {
780 computeStencilAt(i, v, diag, rhs);
781 Av[i] =
static_cast<SolverT
>(precomputedDiag[i] *
v[i] - rhs + b[i]);
785 void solveDiffusion() {
788 normalizedResidual_ = 0.;
789 lastSolveConverged_ =
false;
791 lastSolveConverged_ =
true;
797 using SolverT = float;
799 const std::size_t n = nodes.size();
800 const T eps = std::numeric_limits<T>::epsilon();
806 std::vector<T> diag(n), b(n);
808 const std::vector<SolverT> zeros(n, SolverT(0));
809#pragma omp parallel for schedule(static)
810 for (std::size_t i = 0; i < n; ++i)
811 computeStencilAt(i, zeros, diag[i], b[i]);
816 bool finiteSystem =
true;
817 for (std::size_t i = 0; i < n; ++i) {
819 finiteSystem && std::isfinite(diag[i]) && std::isfinite(b[i]);
820 b_norm = std::max(b_norm, std::abs(b[i]));
822 if (b_norm <
T(1e-100))
825 residual = std::numeric_limits<T>::infinity();
826 normalizedResidual_ = residual;
827 Logger::getInstance()
828 .addWarning(
"solveDiffusion: assembled non-finite matrix/RHS; "
829 "rejecting this coupled trial.")
836 auto fallbackInitialGuess = [&](std::size_t i) ->
T {
837 if (std::isfinite(diag[i]) && std::isfinite(b[i]) && diag[i] > eps) {
838 const T guess = b[i] / diag[i];
839 if (std::isfinite(guess))
845 std::vector<SolverT> x(n);
846 for (std::size_t i = 0; i < n; ++i) {
848 warmStartable_ ? nodes[i].concentration : fallbackInitialGuess(i);
849 if (!std::isfinite(guess))
850 guess = fallbackInitialGuess(i);
851 x[i] =
static_cast<SolverT
>(guess);
852 if (!std::isfinite(x[i]))
855 const auto initialGuess = x;
857#ifdef VIENNALS_GPU_BICGSTAB
864 if (gpu::gpuIsValid(gpuBufs_)) {
865 Timer<> tPrep, tUpload, tSolve;
867 std::vector<double> diagGpu(n), bGpu(n);
868 for (std::size_t i = 0; i < n; ++i) {
869 diagGpu[i] =
static_cast<double>(diag[i]);
870 bGpu[i] =
static_cast<double>(b[i]);
874 std::vector<T> faceCoeffs;
875 computeFaceCoeffs(faceCoeffs);
876 std::vector<double> coeffGpu(faceCoeffs.size());
877 for (std::size_t k = 0; k < faceCoeffs.size(); ++k)
878 coeffGpu[k] =
static_cast<double>(faceCoeffs[k]);
880 std::vector<double> xGpu(n);
881 for (std::size_t i = 0; i < n; ++i)
882 xGpu[i] =
static_cast<double>(x[i]);
886 const bool gpuUploadOk = gpu::gpuUploadSolverArrays(
887 gpuBufs_, diagGpu.data(), bGpu.data(), coeffGpu.data(),
888 static_cast<uint32_t
>(n), faceCoeffs.size());
891 if (Logger::hasDebug()) {
892 const std::string tag =
893 "diffusion n=" + std::to_string(n) +
" [GPU upload failed]";
894 Logger::getInstance()
895 .addTiming(tag +
" diag/b precompute", tDiag)
896 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
897 .addTiming(tag +
" GPU upload", tUpload)
900 VIENNACORE_LOG_ERROR(
"OxidationDiffusion: GPU mode was selected, but "
901 "uploading GPU solver arrays or factorizing ILU "
908 const bool gpuConverged = gpu::gpuSolveBiCGSTAB(
909 gpuBufs_, xGpu.data(),
static_cast<double>(eps),
910 parameters.maxIterations,
static_cast<double>(parameters.tolerance),
911 iterations, residual);
914 const bool finiteGpuSolution =
916 std::all_of(xGpu.begin(), xGpu.end(),
917 [](
double value) { return std::isfinite(value); });
918 const unsigned gpuIterations = iterations;
919 const T gpuResidual = residual;
921 if (finiteGpuSolution) {
924 for (std::size_t i = 0; i < n; ++i)
925 nodes[i].concentration =
static_cast<T>(xGpu[i]);
926 normalizedResidual_ = gpuResidual / b_norm;
927 lastSolveConverged_ = std::isfinite(normalizedResidual_) &&
928 normalizedResidual_ <= parameters.tolerance;
930 if (Logger::hasTiming()) {
931 const std::string tag =
"diffusion n=" + std::to_string(n) +
932 " iters=" + std::to_string(iterations) +
933 " res=" + std::to_string(residual) +
" [GPU]";
934 Logger::getInstance()
935 .addTiming(tag +
" GPU BiCGSTAB", tSolve)
938 if (Logger::hasDebug()) {
939 const std::string tag =
"diffusion n=" + std::to_string(n) +
" [GPU]";
940 Logger::getInstance()
941 .addTiming(tag +
" diag/b precompute", tDiag)
942 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
943 .addTiming(tag +
" GPU upload", tUpload)
949 if (Logger::hasDebug()) {
950 const std::string tag =
"diffusion n=" + std::to_string(n) +
951 " iters=" + std::to_string(gpuIterations) +
952 " res=" + std::to_string(gpuResidual) +
954 Logger::getInstance()
955 .addTiming(tag +
" diag/b precompute", tDiag)
956 .addTiming(tag +
" GPU prep+faceCoeffs", tPrep)
957 .addTiming(tag +
" GPU upload", tUpload)
958 .addTiming(tag +
" GPU BiCGSTAB", tSolve)
963 reportGpuUnavailable(
964 "OxidationDiffusion: GPU mode was selected, but GPU BiCGSTAB "
965 "failed, did not converge, or produced non-finite concentrations "
967 std::to_string(gpuIterations) +
968 ", residual=" + std::to_string(gpuResidual) +
").");
972 VIENNACORE_LOG_ERROR(
"OxidationDiffusion: explicit GPU mode was "
973 "requested, but ViennaLS was built without "
974 "VIENNALS_GPU_BICGSTAB.");
976 VIENNACORE_LOG_WARNING(
"OxidationDiffusion: GPU mode Auto was requested, "
977 "but ViennaLS was built without "
978 "VIENNALS_GPU_BICGSTAB. Using the CPU solver.");
983 std::vector<SolverT> Ax(n);
984 matvec(x, diag, b, Ax);
985 std::vector<SolverT> r(n), r_hat(n);
986 for (std::size_t i = 0; i < n; ++i) {
987 r[i] =
static_cast<SolverT
>(b[i] - Ax[i]);
996 std::vector<SolverT> p(n, SolverT(0)),
v(n, SolverT(0)), y(n), z(n), s(n),
998 T rho =
T(1), alpha =
T(1), omega =
T(1);
999 bool bicgstabBreakdown =
false;
1000 for (std::size_t i = 0; i < n; ++i) {
1001 const T ri =
static_cast<T>(r[i]);
1002 if (!std::isfinite(ri)) {
1003 bicgstabBreakdown =
true;
1006 residual = std::max(residual, std::abs(ri));
1009 for (; !bicgstabBreakdown && iterations < parameters.maxIterations;
1012 for (std::size_t i = 0; i < n; ++i)
1013 rho_new +=
static_cast<T>(r_hat[i]) *
static_cast<T>(r[i]);
1015 if (!std::isfinite(rho_new)) {
1016 bicgstabBreakdown =
true;
1019 if (std::abs(rho_new) <
T(1e-100))
1021 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
1022 !std::isfinite(omega) || std::abs(omega) <
T(1e-100)) {
1023 bicgstabBreakdown =
true;
1027 const T beta = (rho_new / rho) * (alpha / omega);
1028 if (!std::isfinite(beta)) {
1029 bicgstabBreakdown =
true;
1034 for (std::size_t i = 0; i < n; ++i)
1035 p[i] =
static_cast<SolverT
>(r[i] + beta * (p[i] - omega * v[i]));
1037 for (std::size_t i = 0; i < n; ++i) {
1039 y[i] =
static_cast<SolverT
>((diag[i] > eps) ? pi / diag[i] : pi);
1042 matvec(y, diag, b, v);
1045 for (std::size_t i = 0; i < n; ++i)
1046 r_hat_v +=
static_cast<T>(r_hat[i]) *
static_cast<T>(
v[i]);
1047 if (!std::isfinite(r_hat_v)) {
1048 bicgstabBreakdown =
true;
1051 if (std::abs(r_hat_v) <
T(1e-100))
1054 alpha = rho_new / r_hat_v;
1055 if (!std::isfinite(alpha)) {
1056 bicgstabBreakdown =
true;
1060 for (std::size_t i = 0; i < n; ++i)
1061 s[i] =
static_cast<SolverT
>(r[i] - alpha * v[i]);
1064 for (std::size_t i = 0; i < n; ++i)
1065 residual = std::max(residual, std::abs(
static_cast<T>(s[i])));
1066 if (!std::isfinite(residual)) {
1067 bicgstabBreakdown =
true;
1070 if (residual < parameters.tolerance * b_norm) {
1071 for (std::size_t i = 0; i < n; ++i)
1072 x[i] =
static_cast<SolverT
>(x[i] + alpha * y[i]);
1077 for (std::size_t i = 0; i < n; ++i) {
1079 z[i] =
static_cast<SolverT
>((diag[i] > eps) ? si / diag[i] : si);
1082 matvec(z, diag, b, t);
1084 T t_s =
T(0), t_t =
T(0);
1085 for (std::size_t i = 0; i < n; ++i) {
1086 t_s +=
static_cast<T>(t[i]) *
static_cast<T>(s[i]);
1087 t_t +=
static_cast<T>(t[i]) *
static_cast<T>(t[i]);
1089 if (!std::isfinite(t_s) || !std::isfinite(t_t)) {
1090 bicgstabBreakdown =
true;
1093 omega = (t_t >
T(1e-100)) ? t_s / t_t :
T(0);
1094 if (!std::isfinite(omega)) {
1095 bicgstabBreakdown =
true;
1099 for (std::size_t i = 0; i < n; ++i) {
1100 x[i] =
static_cast<SolverT
>(x[i] + alpha * y[i] + omega * z[i]);
1101 r[i] =
static_cast<SolverT
>(s[i] - omega * t[i]);
1105 for (std::size_t i = 0; i < n; ++i)
1106 residual = std::max(residual, std::abs(
static_cast<T>(r[i])));
1107 if (!std::isfinite(residual)) {
1108 bicgstabBreakdown =
true;
1111 if (residual < parameters.tolerance * b_norm) {
1117 if (bicgstabBreakdown)
1118 residual = std::numeric_limits<T>::infinity();
1120 const bool finiteCpuSolution =
1121 !bicgstabBreakdown &&
1122 std::all_of(x.begin(), x.end(),
1123 [](SolverT value) { return std::isfinite(value); });
1124 if (finiteCpuSolution) {
1125 for (std::size_t i = 0; i < n; ++i)
1126 nodes[i].concentration = x[i];
1128 for (std::size_t i = 0; i < n; ++i)
1129 nodes[i].concentration = initialGuess[i];
1130 residual = std::numeric_limits<T>::infinity();
1131 Logger::getInstance()
1132 .addWarning(
"solveDiffusion: CPU BiCGSTAB produced non-finite "
1133 "concentrations; keeping the sanitized initial guess.")
1136 normalizedResidual_ = residual / b_norm;
1137 lastSolveConverged_ = finiteCpuSolution &&
1138 std::isfinite(normalizedResidual_) &&
1139 normalizedResidual_ <= parameters.tolerance;
1142 if (Logger::hasTiming()) {
1143 const std::string path =
" [CPU]";
1144 const std::string tag =
"diffusion n=" + std::to_string(n) +
1145 " iters=" + std::to_string(iterations) +
1146 " res=" + std::to_string(residual) + path;
1147 Logger::getInstance().addTiming(tag +
" CPU BiCGSTAB", tCpuSolve).print();
1149 if (Logger::hasDebug()) {
1150 const std::string path =
" [CPU]";
1151 Logger::getInstance()
1152 .addTiming(
"diffusion n=" + std::to_string(n) + path +
1153 " diag/b precompute",
1157 if (residual > parameters.tolerance * b_norm)
1158 VIENNACORE_LOG_WARNING(
1159 "solveDiffusion: BiCGSTAB did not converge after " +
1160 std::to_string(iterations) +
"/" +
1161 std::to_string(parameters.maxIterations) +
1162 " iterations (residual=" + std::to_string(residual / b_norm) +
1163 ", tolerance=" + std::to_string(parameters.tolerance) +
")");
1166#ifdef VIENNALS_GPU_BICGSTAB
1167 static std::string gpuErrorDetail() {
1168 const char *detail = gpu::gpuGetLastErrorMessage();
1169 if (detail && detail[0] !=
'\0')
1170 return std::string(
" Detail: ") + detail;
1178 void reportGpuUnavailable(
const std::string &message)
const {
1180 VIENNACORE_LOG_WARNING(message + gpuErrorDetail() +
1181 " Falling back to the CPU solver.");
1183 VIENNACORE_LOG_ERROR(message + gpuErrorDetail());
1188 void logDiffusionBackend(
const std::string &backend,
1189 const std::string &detail)
const {
1190 if (!Logger::hasInfo())
1192 const std::string msg =
1193 "OxidationDiffusion: using " + backend +
1194 " for diffusion solve (nodes=" + std::to_string(nodes.size()) +
1195 (detail.empty() ? std::string() :
", " + detail) +
").";
1196 if (msg == lastLoggedBackend_)
1198 lastLoggedBackend_ = msg;
1199 Logger::getInstance().addInfo(msg).print();
1204 template <
class SolverT>
1206 makeStencilSide(std::size_t nodeId,
const std::vector<SolverT> &previous,
1207 unsigned direction,
int offset,
T diffusion)
const {
1208 const IndexType &nodeIndex = nodes[nodeId].index;
1209 IndexType neighbor = nodeIndex;
1210 neighbor[direction] += offset;
1213 return zeroFluxSide();
1215 const std::size_t neighborId =
lookupNode(neighbor);
1216 if (neighborId !=
noNode)
1217 return {
gridDelta, 0., previous[neighborId]};
1219 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
1220 const std::size_t n = nodes.size();
1221 const Boundary faceType = faceBCTypes_[fi * n + nodeId];
1222 const T faceDist = faceBCDists_[fi * n + nodeId];
1223 if (faceType == Boundary::REACTION)
1224 return reactionBoundarySide(nodeIndex, faceDist, diffusion);
1225 if (faceType == Boundary::AMBIENT)
1226 return ambientBoundarySide(faceDist, diffusion);
1227 if (faceType == Boundary::MASK)
1228 return maskBoundarySide(faceDist, diffusion);
1229 return zeroFluxSide();
1232 void addAxisContribution(
T &rightHandSide,
T &diagonal,
1233 const StencilSide &negativeSide,
1234 const StencilSide &positiveSide,
T diffusion)
const {
1235 const T distanceSum = negativeSide.distance + positiveSide.distance;
1236 if (distanceSum <= std::numeric_limits<T>::epsilon())
1239 addSideContribution(rightHandSide, diagonal, negativeSide, distanceSum,
1241 addSideContribution(rightHandSide, diagonal, positiveSide, distanceSum,
1245 void addSideContribution(
T &rightHandSide,
T &diagonal,
1246 const StencilSide &side,
T distanceSum,
1247 T diffusion)
const {
1248 if (side.distance <= std::numeric_limits<T>::epsilon())
1251 const T coefficient =
T(2) *
diffusion / (side.distance * distanceSum);
1252 rightHandSide += coefficient * side.constant;
1253 diagonal += coefficient * (
T(1) - side.nodeCoefficient);
1256 StencilSide zeroFluxSide()
const {
return {
gridDelta, 1., 0.}; }
1258 StencilSide reactionBoundarySide(
const IndexType &nodeIndex,
T distance,
1259 T diffusion)
const {
1262 const T denominator = conductance + reactionRate;
1263 if (denominator <= std::numeric_limits<T>::epsilon())
1264 return zeroFluxSide();
1266 return {distance, conductance / denominator, 0.};
1270 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1272 const std::size_t directId =
lookupNode(index);
1273 if (directId !=
noNode) {
1275 reactionBoundarySampleFromNode(reactionIt, nodes[directId]);
1285 std::size_t bestNode = std::numeric_limits<std::size_t>::max();
1286 T bestDistance2 = std::numeric_limits<T>::max();
1288 for (
int radius = 1; radius <= 4; ++radius) {
1290 offset.fill(-radius);
1292 IndexType candidate = index;
1294 for (
unsigned d = 0; d <
D; ++d) {
1295 candidate[d] += offset[d];
1296 distance2 +=
static_cast<T>(offset[d] * offset[d]);
1299 if (distance2 >
T(0) &&
inBounds(candidate)) {
1301 if (foundId !=
noNode && distance2 < bestDistance2) {
1303 reactionBoundarySampleFromNode(reactionIt, nodes[foundId]);
1305 bestDistance2 = distance2;
1312 for (; dim <
D; ++dim) {
1313 if (offset[dim] < radius) {
1317 offset[dim] = -radius;
1323 if (bestNode != std::numeric_limits<std::size_t>::max())
1327 if (bestNode == std::numeric_limits<std::size_t>::max())
1329 return reactionBoundarySampleFromNode(reactionIt, nodes[bestNode]);
1333 reactionBoundarySampleFromNode(ConstSparseIterator &reactionIt,
1334 const Node &node)
const {
1337 T bestDistance = std::numeric_limits<T>::max();
1338 const T insidePhi =
valueAt(reactionIt, node.index);
1340 for (
unsigned direction = 0; direction <
D; ++direction) {
1341 for (
const int offset : {-1, 1}) {
1342 IndexType neighbor = node.index;
1343 neighbor[direction] += offset;
1347 const T outsidePhi =
valueAt(reactionIt, neighbor);
1348 if (!
crosses(insidePhi, outsidePhi))
1351 const T distance = crossingDistance(insidePhi, outsidePhi);
1352 if (distance >= bestDistance)
1355 const T diffusion = getEffectiveDiffusionCoefficient(node.index);
1356 const auto side = reactionBoundarySide(node.index, distance, diffusion);
1358 best.distance = distance;
1359 best.concentration =
1360 side.nodeCoefficient * node.concentration + side.constant;
1361 best.crossingAxis = direction;
1362 best.crossingOffset = offset;
1363 bestDistance = distance;
1370 StencilSide ambientBoundarySide(
T distance,
T diffusion)
const {
1372 const T denominator = conductance + parameters.transferCoefficient;
1373 if (denominator <= std::numeric_limits<T>::epsilon())
1374 return zeroFluxSide();
1376 return {distance, conductance / denominator,
1377 parameters.transferCoefficient *
1378 parameters.equilibriumConcentration / denominator};
1381 StencilSide maskBoundarySide(
T distance,
T diffusion)
const {
1382 if (parameters.maskTransferCoefficient <= std::numeric_limits<T>::epsilon())
1383 return zeroFluxSide();
1386 const T denominator = conductance + parameters.maskTransferCoefficient;
1387 if (denominator <= std::numeric_limits<T>::epsilon())
1388 return zeroFluxSide();
1390 return {distance, conductance / denominator,
1391 parameters.maskTransferCoefficient * parameters.maskConcentration /
1396 T rate = parameters.reactionRate;
1398 if (parameters.reactionActivationVolume !=
T(0)) {
1399 T pressure = parameters.referencePressure;
1400 const auto foundPressure = pressureLookup.find(index);
1401 if (foundPressure != pressureLookup.end())
1402 pressure = foundPressure->second;
1403 if (!std::isfinite(pressure))
1404 pressure = parameters.referencePressure;
1406 stressExponent(pressure, parameters.reactionActivationVolume);
1407 rate *= stressFactor(exponent);
1410 if (parameters.reactionRateRatio111 !=
T(1)) {
1411 const std::size_t nodeId =
lookupNode(index);
1413 const auto &normal = nodes[nodeId].siNormal;
1415 for (
unsigned d = 0; d <
D; ++d)
1416 dot += normal[d] * parameters.crystalAxis[d];
1418 (parameters.reactionRateRatio111 -
T(1)) * (
T(1) - dot * dot);
1425 T getEffectiveDiffusionCoefficient(
const IndexType &index)
const {
1426 if (parameters.diffusionActivationVolume ==
T(0))
1427 return parameters.diffusionCoefficient;
1429 T pressure = parameters.referencePressure;
1430 const auto found = pressureLookup.find(index);
1431 if (found != pressureLookup.end())
1432 pressure = found->second;
1433 if (!std::isfinite(pressure))
1434 pressure = parameters.referencePressure;
1437 stressExponent(pressure, parameters.diffusionActivationVolume);
1438 return parameters.diffusionCoefficient * stressFactor(exponent);
1441 T stressExponent(
T pressure,
T activationVolume)
const {
1442 const T thermalEnergy =
1443 boltzmannConstant * std::max(parameters.temperature,
T(1.));
1444 return -(pressure - parameters.referencePressure) * activationVolume /
1448 static T stressFactor(
T exponent) {
1449 if (!std::isfinite(exponent))
1451 if (exponent <= std::log(minStressFactor))
1452 return minStressFactor;
1453 if (exponent >= std::log(maxStressFactor))
1454 return maxStressFactor;
1455 return std::exp(exponent);
1458 Vec3D<T> computeSiNormal(
const IndexType &index,
1459 ConstSparseIterator &reactionIt)
const {
1463 auto reflectToGrid = [&](IndexType idx) {
1464 auto &g = reactionInterface->getGrid();
1465 for (
unsigned d2 = 0;
d2 <
D; ++
d2) {
1466 const auto lo = g.getMinGridPoint(d2);
1467 const auto hi = g.getMaxGridPoint(d2);
1469 idx[
d2] = 2 * lo - idx[
d2];
1471 idx[
d2] = 2 * hi - idx[
d2];
1475 Vec3D<T> gradient{0., 0., 0.};
1476 for (
unsigned d = 0; d <
D; ++d) {
1477 IndexType plus = index, minus = index;
1483 valueAt(reactionIt, reflectToGrid(minus)))) /
1487 for (
unsigned d = 0; d <
D; ++d)
1488 len += gradient[d] * gradient[d];
1489 len = std::sqrt(len);
1490 if (len > std::numeric_limits<T>::epsilon()) {
1491 for (
unsigned d = 0; d <
D; ++d)
1494 gradient = Vec3D<T>{0., 0., 0.};
1495 gradient[
D - 1] =
T(1);
1500 std::pair<Boundary, T> classifyBoundary(ConstSparseIterator &reactionIt,
1501 ConstSparseIterator &ambientIt,
1502 ConstSparseIterator &maskIt,
1503 const IndexType &inside,
1504 const IndexType &outside)
const {
1505 const T reactionInside =
valueAt(reactionIt, inside);
1506 const T reactionOutside =
valueAt(reactionIt, outside);
1507 const T ambientInside =
valueAt(ambientIt, inside);
1508 const T ambientOutside =
valueAt(ambientIt, outside);
1509 const T maskInside = valueAtMask(maskIt, inside);
1510 const T maskOutside = valueAtMask(maskIt, outside);
1512 const T reactionDistance =
1513 crosses(reactionInside, reactionOutside)
1514 ? crossingDistance(reactionInside, reactionOutside)
1515 : std::numeric_limits<
T>::max();
1516 const T ambientDistance =
1517 crosses(ambientInside, ambientOutside)
1518 ? crossingDistance(ambientInside, ambientOutside)
1519 : std::numeric_limits<
T>::max();
1520 const T maskDistance =
1521 maskInterface !=
nullptr &&
crosses(maskInside, maskOutside)
1522 ? crossingDistance(maskInside, maskOutside)
1523 : std::numeric_limits<
T>::max();
1525 if (reactionDistance == std::numeric_limits<T>::max() &&
1526 ambientDistance == std::numeric_limits<T>::max() &&
1527 maskDistance == std::numeric_limits<T>::max())
1530 if (reactionDistance <= ambientDistance && reactionDistance <= maskDistance)
1531 return {Boundary::REACTION, reactionDistance};
1539 if (ambientDistance != std::numeric_limits<T>::max() &&
1540 (isMaskAtCrossing(maskInside, maskOutside, ambientDistance) ||
1541 (maskInterface !=
nullptr &&
1542 static_cast<T>(maskSign) * valueAtMask(maskIt, outside) >=
T(0))))
1543 return {Boundary::MASK, ambientDistance};
1545 if (maskDistance <= ambientDistance)
1546 return {Boundary::MASK, maskDistance};
1547 return {Boundary::AMBIENT, ambientDistance};
1550 bool isInsideOxide(
T reactionPhi,
T ambientPhi)
const {
1556 constexpr T eps =
T(1e-9);
1557 return reactionSign * reactionPhi >= -eps &&
1558 ambientSign * ambientPhi >= -eps;
1561 ConstSparseIterator makeMaskIterator()
const {
1562 if (maskInterface ==
nullptr)
1563 return ConstSparseIterator(reactionInterface->getDomain());
1564 return ConstSparseIterator(maskInterface->getDomain());
1567 bool isInsideMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
1568 if (maskInterface ==
nullptr)
1570 return maskSign *
valueAt(maskIt, index) >= 0.;
1573 T valueAtMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
1574 if (maskInterface ==
nullptr)
1575 return std::numeric_limits<T>::max();
1576 return valueAt(maskIt, index);
1579 bool isMaskAtCrossing(
T maskInside,
T maskOutside,
T distance)
const {
1580 if (maskInterface ==
nullptr)
1582 const T fraction = std::clamp(distance /
gridDelta,
T(0),
T(1));
1585 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
1586 return static_cast<T>(maskSign) * maskPhi >=
T(0);
1589 T crossingDistance(
T insidePhi,
T outsidePhi)
const {
1591 insidePhi, outsidePhi, parameters.minBoundaryDistance,
gridDelta);