127 using IndexType = viennahrle::Index<D>;
128 using ConstSparseIterator =
129 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
132 static constexpr T boltzmannConstant =
T(1.380649e-23);
133 static constexpr T minStressFactor =
T(1.e-6);
134 static constexpr T maxStressFactor =
T(1.e6);
155 enum class Boundary { NONE, REACTION, AMBIENT, MASK };
159 T concentration = 0.;
160 Vec3D<T> siNormal = {0., 0.,
169 T nodeCoefficient = 1.;
173 SmartPointer<Domain<T, D>> reactionInterface =
nullptr;
174 SmartPointer<Domain<T, D>> ambientInterface =
nullptr;
175 SmartPointer<Domain<T, D>> maskInterface =
nullptr;
177 int reactionSign = 1;
178 int ambientSign = -1;
180 IndexType requestedMinIndex{};
181 IndexType requestedMaxIndex{};
182 unsigned iterations = 0;
183 T residual = std::numeric_limits<T>::max();
184 T normalizedResidual_ = std::numeric_limits<T>::max();
185 bool lastSolveConverged_ =
false;
186 T maxScalarVelocity_ = 0.;
188 bool nodesDirty_ =
true;
191 bool useRequestedBounds =
false;
192 bool warmStartable_ =
194 std::unordered_map<std::size_t, T> pressureLookup;
197 std::unordered_map<std::size_t, std::array<T, 9>> stressLookup;
200 std::vector<T> interfaceNormalStress_;
201 std::vector<T> lawPressure_;
202 std::unordered_map<std::size_t, T> concentrationCache_;
203 std::vector<Node> nodes;
205 std::vector<Boundary> faceBCTypes_;
206 std::vector<T> faceBCDists_;
209 std::vector<T> faceReactionNormal_;
221 std::vector<Vec3D<T>> faceCrossingNormal_;
222 std::vector<T> faceReactionRate_;
223 std::vector<T> faceFrontSpeed_;
228 struct FrontCrossing {
234 std::vector<std::size_t> frontStart_;
235 std::vector<FrontCrossing> frontCrossings_;
238 std::vector<Vec3D<T>> interfaceAnchor_;
241 std::vector<std::size_t> neighborIds_;
247 static constexpr std::size_t gpuMinNodes = 20000;
249 bool gpuOff_ =
false;
270 : reactionInterface(passedReactionInterface),
271 ambientInterface(passedAmbientInterface), parameters(passedParameters) {
274 template <
class... Args>
static auto New(Args &&...args) {
275 return SmartPointer<OxidationDiffusion>::New(std::forward<Args>(args)...);
286 reactionInterface = passedInterface;
292 ambientInterface = passedInterface;
298 int passedMaskSign = 1) {
299 maskInterface = passedInterface;
300 maskSign = (passedMaskSign < 0) ? -1 : 1;
306 maskInterface =
nullptr;
312 parameters = passedParameters;
320 for (
unsigned i = 0; i <
D; ++i)
321 index[i] = std::llround(coordinate[i] /
gridDelta);
326 pressureLookup.clear();
327 stressLookup.clear();
333 void setStress(
const IndexType &index,
const std::array<T, 9> &stress) {
335 if (std::all_of(stress.begin(), stress.end(),
336 [](
T value) { return std::isfinite(value); }))
337 stressLookup[key] = stress;
339 stressLookup.erase(key);
346 std::array<T, 9> &stress)
const {
348 const auto p = pressureLookup.find(key);
350 p != pressureLookup.end() ? p->second :
T(parameters.referencePressure);
351 const auto s = stressLookup.find(key);
352 if (s != stressLookup.end())
358 using StressLookup = std::unordered_map<std::size_t, std::array<T, 9>>;
370 pressureLookup = std::move(pressures);
371 stressLookup = std::move(stresses);
378 return interfaceNormalStress_;
386 const std::size_t n = nodes.size();
387 constexpr T nan = std::numeric_limits<T>::quiet_NaN();
388 std::vector<T> value(n, nan);
389 if (faceBCTypes_.size() != 2 *
D * n)
391 for (std::size_t i = 0; i < n; ++i) {
392 if (nodes[i].inMask || !reactionNode(i))
394 const auto &normal = nodes[i].siNormal;
395 Vec3D<T> into = normal;
396 for (
unsigned d = 0; d <
D; ++d)
397 into[d] *=
static_cast<T>(reactionSign);
398 const auto normalStress = [&](std::size_t j) {
401 if (found == lookup.end())
402 return std::numeric_limits<T>::quiet_NaN();
404 for (
unsigned r = 0; r <
D; ++r)
405 for (
unsigned c = 0; c <
D; ++c)
406 v += normal[r] * found->second[3 * r + c] * normal[c];
410 if (frontStressAt(i, into, frontDistance(nodes[i].index),
T(0),
411 normalStress, sample) ||
412 medianNormalStress(nodes[i].index, lookup, sample))
415 std::vector<T> next(n);
416 for (
unsigned pass = 0; pass < 6; ++pass) {
417 for (std::size_t i = 0; i < n; ++i) {
419 if (!std::isfinite(value[i]))
427 IndexType neighbor = nodes[i].index;
428 for (
unsigned d = 0; d <
D; ++d) {
429 neighbor[d] += offset[d];
430 centre = centre && offset[d] == 0;
432 if (!centre &&
inBounds(neighbor)) {
434 if (j !=
noNode && std::isfinite(value[j])) {
440 while (d <
D && offset[d] == 1)
446 next[i] = sum /
static_cast<T>(count);
453 void setStress(
const Vec3D<T> &coordinate,
const std::array<T, 9> &stress) {
455 for (
unsigned i = 0; i <
D; ++i)
456 index[i] = std::llround(coordinate[i] /
gridDelta);
462 if (std::isfinite(pressure))
463 pressureLookup[key] = pressure;
465 pressureLookup.erase(key);
471 for (
unsigned i = 0; i <
D; ++i)
472 index[i] = std::llround(coordinate[i] /
gridDelta);
479 reactionSign = (passedReactionSign < 0) ? -1 : 1;
480 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
487 const IndexType &passedMaxIndex) {
488 requestedMinIndex = passedMinIndex;
489 requestedMaxIndex = passedMaxIndex;
490 useRequestedBounds =
true;
496 useRequestedBounds =
false;
502 if (reactionInterface ==
nullptr || ambientInterface ==
nullptr) {
503 Logger::getInstance()
504 .addError(
"OxidationDiffusion: Missing level-set "
511 if (!initialiseGrid())
516 Logger::getInstance()
517 .addWarning(
"OxidationDiffusion: no oxide nodes found after "
518 "buildNodes(). Verify that the reaction and ambient "
519 "level sets enclose a non-empty oxide band.")
522 if (parameters.reactionActivationVolume !=
T(0))
523 updateInterfaceNormalStress();
524 if (parameters.diffusionActivationVolume !=
T(0))
529 concentrationCache_.clear();
530 for (
const auto &node : nodes)
535 maxScalarVelocity_ = 0.;
536 for (
const T speed : faceFrontSpeed_)
537 maxScalarVelocity_ = std::max(maxScalarVelocity_, speed);
543 const Vec3D<T> &normalVector,
544 unsigned long )
final {
548 if (parameters.material >= 0 && material != parameters.material)
552 for (
unsigned i = 0; i <
D; ++i)
553 index[i] = std::llround(coordinate[i] /
gridDelta);
564 if (nodeId !=
noNode && nodes[nodeId].inMask)
571 ConstSparseIterator reactionIt(reactionInterface->getDomain());
572 const T phi =
valueAt(reactionIt, index);
573 if (std::isfinite(phi) && std::abs(phi) <=
T(2)) {
574 const auto g = computeSiNormal(index, reactionIt);
575 Vec3D<T> p{
T(0),
T(0),
T(0)};
576 for (
unsigned d = 0; d <
D; ++d)
577 p[d] = (
static_cast<T>(index[d]) - phi * g[d]) *
gridDelta;
580 return parameters.velocitySign * front.speed;
584 const auto boundarySample = reactionBoundarySample(index);
585 const IndexType rateIndex =
586 boundarySample.found ? boundarySample.nodeIndex : index;
588 const T concentration = boundarySample.found ? boundarySample.concentration
592 (parameters.oxidantMoleculeDensity *
593 parameters.expansionCoefficient);
608 const std::size_t n = nodes.size();
609 if (frontStart_.size() != n + 1)
611 Vec3D<T> p{
T(0),
T(0),
T(0)};
612 for (
unsigned d = 0; d <
D; ++d)
614 constexpr T width =
T(0.75);
615 T weightSum =
T(0), speed =
T(0);
616 Vec3D<T> normal{
T(0),
T(0),
T(0)};
617 forEachNodeNear(p, 2, [&](std::size_t j) {
618 for (std::size_t k = frontStart_[j]; k < frontStart_[j + 1]; ++k) {
619 const auto &crossing = frontCrossings_[k];
621 for (
unsigned d = 0; d <
D; ++d)
622 r2 += (crossing.point[d] - p[d]) * (crossing.point[d] - p[d]);
628 std::exp(-r2 / (
T(2) * width * width)) * crossing.areaWeight;
630 speed += w * crossing.speed;
631 for (
unsigned d = 0; d <
D; ++d)
632 normal[d] += w * crossing.normal[d];
635 if (weightSum <=
T(1e-12))
638 for (
unsigned d = 0; d <
D; ++d)
639 len += normal[d] * normal[d];
640 len = std::sqrt(len);
641 if (len <= std::numeric_limits<T>::epsilon())
644 result.
speed = speed / weightSum;
645 for (
unsigned d = 0; d <
D; ++d)
646 result.
normal[d] = normal[d] / len;
651 const Vec3D<T> & )
final {
652 if (parameters.material >= 0 && material != parameters.material)
656 std::abs(parameters.velocitySign) * parameters.reactionRate *
657 parameters.equilibriumConcentration /
658 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient);
660 return std::max(maxScalarVelocity_, bulkVel);
665 for (
unsigned i = 0; i <
D; ++i)
666 index[i] = std::llround(coordinate[i] /
gridDelta);
676 return nodes[nearby].concentration;
678 return nodes[nodeId].concentration;
683 for (
unsigned i = 0; i <
D; ++i)
684 index[i] = std::llround(coordinate[i] /
gridDelta);
689 const auto sample = reactionBoundarySample(index);
699 for (
const auto &node : nodes)
700 if (!std::isfinite(node.concentration))
706 return concentrationCache_;
710 concentrationCache_ = std::move(cache);
726 if (nodes.empty() || ambientInterface ==
nullptr)
729 std::vector<T> concentrations;
730 ConstSparseIterator it(ambientInterface->getDomain());
731 for (; !it.isFinished(); ++it) {
734 const IndexType idx = it.getStartIndices();
743 const T value = nodeId !=
noNode ? nodes[nodeId].concentration :
T(0);
744 concentrations.push_back(std::isfinite(value) ? value :
T(0));
746 ambientInterface->getPointData().insertReplaceScalarData(
747 std::move(concentrations),
"OxConcentration");
753 if (pressureLookup.empty() || ambientInterface ==
nullptr)
756 std::vector<T> pressures;
757 ConstSparseIterator it(ambientInterface->getDomain());
758 for (; !it.isFinished(); ++it) {
761 const IndexType idx = it.getStartIndices();
763 const T value = pIt != pressureLookup.end() ? pIt->second :
T(0);
764 pressures.push_back(std::isfinite(value) ? value :
T(0));
766 ambientInterface->getPointData().insertReplaceScalarData(
767 std::move(pressures),
"OxPressure");
779 ReactionBoundarySample
782 for (
unsigned i = 0; i <
D; ++i)
783 index[i] = std::llround(coordinate[i] /
gridDelta);
784 return reactionBoundarySample(index);
794 (parameters.oxidantMoleculeDensity * parameters.expansionCoefficient));
798 bool initialiseGrid() {
800 reactionInterface, ambientInterface, maskInterface, useRequestedBounds,
801 requestedMinIndex, requestedMaxIndex, parameters.
maxGridPoints,
802 "OxidationDiffusion");
814 warmStartable_ = !concentrationCache_.empty();
815 if (!warmStartable_) {
818 const int cIdx = ambientInterface->getPointData().getScalarDataIndex(
821 ambientInterface->getPointData().getScalarDataIndex(
"OxPressure");
823 (cIdx != -1) ? ambientInterface->getPointData().getScalarData(cIdx)
826 (pIdx != -1) ? ambientInterface->getPointData().getScalarData(pIdx)
831 const auto &pointData = ambientInterface->getPointData();
832 const std::array<int, 3> sIdx = {
833 pointData.getVectorDataIndex(
"OxStressR0"),
834 pointData.getVectorDataIndex(
"OxStressR1"),
835 pointData.getVectorDataIndex(
"OxStressR2")};
836 const bool stressWarmStart = parameters.reactionActivationVolume != 0. &&
837 pd !=
nullptr && sIdx[0] != -1 &&
838 sIdx[1] != -1 && sIdx[2] != -1;
839 if (cd !=
nullptr || pd !=
nullptr) {
840 ConstSparseIterator it(ambientInterface->getDomain());
841 for (; !it.isFinished(); ++it) {
844 const auto ptId = it.getPointId();
845 const std::size_t key =
847 if (cd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(cd->size()) &&
848 std::isfinite((*cd)[ptId]))
849 concentrationCache_[key] = (*cd)[ptId];
850 if (pd !=
nullptr && ptId <
static_cast<decltype(ptId)
>(pd->size()) &&
851 std::isfinite((*pd)[ptId]))
852 pressureLookup[key] = (*pd)[ptId];
853 if (stressWarmStart &&
854 ptId <
static_cast<decltype(ptId)
>(pd->size())) {
855 std::array<T, 9> stress{};
856 bool valid = std::isfinite((*pd)[ptId]);
857 for (
unsigned r = 0; r < 3 && valid; ++r) {
858 const auto *rows = pointData.getVectorData(sIdx[r]);
859 valid = rows !=
nullptr &&
860 ptId < static_cast<decltype(ptId)>(rows->size());
861 for (
unsigned c = 0; c < 3 && valid; ++c)
862 stress[3 * r + c] =
T((*rows)[ptId][c]);
865 for (
unsigned d = 0; d < 3; ++d)
866 stress[4 * d] -=
T((*pd)[ptId]);
867 stressLookup[key] = stress;
871 warmStartable_ = !concentrationCache_.empty();
875 ConstSparseIterator reactionIt(reactionInterface->getDomain());
876 ConstSparseIterator ambientIt(ambientInterface->getDomain());
877 auto maskIt = makeMaskIterator();
883 const bool meshMask =
884 maskInterface !=
nullptr && parameters.maskPermeabilityRatio > 0.;
888 const T reactionPhi =
valueAt(reactionIt, index);
889 const T ambientPhi =
valueAt(ambientIt, index);
890 const bool insideMask = isInsideMask(maskIt, index);
891 constexpr T eps =
T(1e-9);
892 const bool oxideNode =
893 isInsideOxide(reactionPhi, ambientPhi) && !insideMask;
894 const bool maskNode =
895 meshMask && insideMask && reactionSign * reactionPhi >= -eps;
896 if (oxideNode || maskNode) {
897 const std::size_t
id = nodes.size();
899 T seedConc = parameters.equilibriumConcentration;
902 if (cacheIt != concentrationCache_.end())
903 seedConc = cacheIt->second;
904 if (!std::isfinite(seedConc))
905 seedConc = parameters.equilibriumConcentration;
906 Node newNode{index, seedConc};
907 newNode.inMask = maskNode;
910 if (!maskNode && (parameters.reactionRateRatio111 !=
T(1) ||
911 parameters.reactionActivationVolume !=
T(0)))
912 newNode.siNormal = computeSiNormal(index, reactionIt);
913 nodes.push_back(newNode);
923 const std::size_t n = nodes.size();
924 faceBCTypes_.assign(2 *
D * n, Boundary::NONE);
925 faceBCDists_.assign(2 *
D * n,
T(1));
926 faceReactionNormal_.assign(2 *
D * n,
T(1));
927 faceCrossingNormal_.assign(2 *
D * n, Vec3D<T>{
T(0),
T(0),
T(0)});
928 ConstSparseIterator faceReactionIt(reactionInterface->getDomain());
929 ConstSparseIterator faceAmbientIt(ambientInterface->getDomain());
930 auto faceMaskIt = makeMaskIterator();
931 for (std::size_t
id = 0;
id < n; ++id) {
932 const auto &node = nodes[id];
933 for (
unsigned dir = 0; dir <
D; ++dir) {
934 for (
int off : {-1, 1}) {
935 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
936 IndexType nb = node.index;
940 auto bc = classifyBoundary(faceReactionIt, faceAmbientIt, faceMaskIt,
947 if (bc.first == Boundary::MASK)
948 bc.first = Boundary::AMBIENT;
949 else if (bc.first == Boundary::REACTION)
950 bc.first = Boundary::NONE;
952 faceBCTypes_[fi * n + id] = bc.first;
953 faceBCDists_[fi * n + id] = bc.second;
954 if (bc.first == Boundary::REACTION) {
957 const auto a = computeSiNormal(node.index, faceReactionIt);
958 const auto b = computeSiNormal(nb, faceReactionIt);
959 const T t = std::clamp(bc.second /
gridDelta,
T(0),
T(1));
960 Vec3D<T> m{
T(0),
T(0),
T(0)};
962 for (
unsigned e = 0; e <
D; ++e) {
963 m[e] = (
T(1) - t) * a[e] + t * b[e];
966 len = std::sqrt(len);
967 if (len > std::numeric_limits<T>::epsilon())
968 for (
unsigned e = 0; e <
D; ++e)
972 faceCrossingNormal_[fi * n + id] = m;
973 faceReactionNormal_[fi * n + id] = std::abs(m[dir]);
979 interfaceAnchor_.assign(n, Vec3D<T>{
T(0),
T(0),
T(0)});
980 if (parameters.reactionActivationVolume !=
T(0))
981 for (std::size_t
id = 0;
id < n; ++id) {
982 if (nodes[
id].inMask || !reactionNode(
id))
984 const T phi = frontDistance(nodes[
id].index);
985 for (
unsigned d = 0; d <
D; ++d)
986 interfaceAnchor_[
id][d] =
987 static_cast<T>(nodes[
id].index[d]) -
988 static_cast<T>(reactionSign) * nodes[
id].siNormal[d] * phi;
993 neighborIds_.assign(2 *
D * n,
noNode);
994 for (std::size_t
id = 0;
id < n; ++id) {
995 for (
unsigned dir = 0; dir <
D; ++dir) {
996 for (
int off : {-1, 1}) {
997 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
998 IndexType nb = nodes[id].index;
1006#ifdef VIENNALS_GPU_BICGSTAB
1008 logDiffusionBackend(
"CPU AMG-BiCGSTAB",
"GPU mode not selected");
1009 else if (n < gpuMinNodes)
1010 logDiffusionBackend(
"CPU AMG-BiCGSTAB",
1011 "below " + std::to_string(gpuMinNodes) +
" nodes");
1012 else if (gpuOff_ || !gpu::gpuRuntimeAvailable())
1013 logDiffusionBackend(
"CPU AMG-BiCGSTAB",
"GPU unavailable");
1015 logDiffusionBackend(
"GPU AMG-BiCGSTAB",
"AMGCL CUDA backend");
1017 logDiffusionBackend(
"CPU AMG-BiCGSTAB",
1018 "ViennaLS was built without GPU support");
1032 void computeFaceCoeffs(std::vector<T> &faceCoeffs)
const {
1033 const std::size_t n = nodes.size();
1034 faceCoeffs.assign(2 *
D * n,
T(0));
1035 const T eps = std::numeric_limits<T>::epsilon();
1036 const std::vector<T> zeros(n,
T(0));
1038 for (std::size_t
id = 0;
id < n; ++id) {
1039 const T D_eff = nodeDiffusionCoefficient(
id);
1040 for (
unsigned dir = 0; dir <
D; ++dir) {
1041 const unsigned fiNeg = dir * 2u;
1042 const unsigned fiPos = dir * 2u + 1u;
1046 const auto neg = makeStencilSide(
id, zeros, dir, -1, D_eff);
1047 const auto pos = makeStencilSide(
id, zeros, dir, 1, D_eff);
1048 const T distNeg = neg.distance;
1049 const T distPos = pos.distance;
1050 const T distSum = distNeg + distPos;
1057 const unsigned fiNegEff =
1058 stencilOffset(nodes[
id].index, dir, -1) == 1 ? fiPos : fiNeg;
1059 const unsigned fiPosEff =
1060 stencilOffset(nodes[
id].index, dir, 1) == -1 ? fiNeg : fiPos;
1061 if (neighborIds_[fiNegEff * n +
id] !=
noNode && distNeg > eps)
1062 faceCoeffs[fiNegEff * n + id] +=
1063 T(2) * faceDiffusionCoefficient(
id, dir, -1, D_eff) /
1064 (distNeg * distSum);
1065 if (neighborIds_[fiPosEff * n +
id] !=
noNode && distPos > eps)
1066 faceCoeffs[fiPosEff * n + id] +=
1067 T(2) * faceDiffusionCoefficient(
id, dir, 1, D_eff) /
1068 (distPos * distSum);
1076 template <
class SolverT>
1077 void computeStencilAt(std::size_t nodeId,
const std::vector<SolverT> &x,
1078 T &diag,
T &rhs)
const {
1081 const T D_eff = nodeDiffusionCoefficient(nodeId);
1082 for (
unsigned direction = 0; direction <
D; ++direction) {
1083 const auto neg = makeStencilSide(nodeId, x, direction, -1, D_eff);
1084 const auto pos = makeStencilSide(nodeId, x, direction, 1, D_eff);
1087 const T distSum = neg.distance + pos.distance;
1088 if (distSum <= std::numeric_limits<T>::epsilon())
1090 addSideContribution(
1091 rhs, diag, neg, distSum,
1092 faceDiffusionCoefficient(nodeId, direction, -1, D_eff));
1093 addSideContribution(
1094 rhs, diag, pos, distSum,
1095 faceDiffusionCoefficient(nodeId, direction, 1, D_eff));
1099 void solveDiffusion() {
1102 normalizedResidual_ = 0.;
1103 lastSolveConverged_ =
false;
1104 if (nodes.empty()) {
1105 lastSolveConverged_ =
true;
1109 const std::size_t n = nodes.size();
1110 const T eps = std::numeric_limits<T>::epsilon();
1116 std::vector<T> diag(n), b(n);
1118 const std::vector<T> zeros(n,
T(0));
1119#pragma omp parallel for schedule(static)
1120 for (std::size_t i = 0; i < n; ++i)
1121 computeStencilAt(i, zeros, diag[i], b[i]);
1125 bool finiteSystem =
true;
1126 for (std::size_t i = 0; i < n; ++i)
1128 finiteSystem && std::isfinite(diag[i]) && std::isfinite(b[i]);
1129 if (!finiteSystem) {
1130 residual = std::numeric_limits<T>::infinity();
1131 normalizedResidual_ = residual;
1132 Logger::getInstance()
1133 .addWarning(
"solveDiffusion: assembled non-finite matrix/RHS; "
1134 "rejecting this coupled trial.")
1147 std::vector<T> sorted(diag);
1148 const auto median = sorted.begin() + n / 2;
1149 std::nth_element(sorted.begin(), median, sorted.end());
1150 T concentrationScale =
T(0);
1151 for (std::size_t i = 0; i < n; ++i) {
1153 concentrationScale =
1154 std::max(concentrationScale, std::abs(b[i] / diag[i]));
1156 b_norm = *median * concentrationScale;
1158 if (b_norm <
T(1e-100))
1163 auto fallbackInitialGuess = [&](std::size_t i) ->
T {
1164 if (std::isfinite(diag[i]) && std::isfinite(b[i]) && diag[i] > eps) {
1165 const T guess = b[i] / diag[i];
1166 if (std::isfinite(guess))
1172 std::vector<T> x(n);
1173 for (std::size_t i = 0; i < n; ++i) {
1174 x[i] = warmStartable_ ? nodes[i].concentration : fallbackInitialGuess(i);
1175 if (!std::isfinite(x[i]))
1176 x[i] = fallbackInitialGuess(i);
1178 const auto initialGuess = x;
1193 std::vector<T> faceCoeffs;
1194 computeFaceCoeffs(faceCoeffs);
1195 std::vector<std::ptrdiff_t> rowPtr(n + 1, 0), cols;
1196 std::vector<T> values;
1197 cols.reserve((2 *
D + 1) * n);
1198 values.reserve((2 *
D + 1) * n);
1199 std::vector<std::pair<std::ptrdiff_t, T>> row;
1200 for (std::size_t i = 0; i < n; ++i) {
1201 row.assign(1, {
static_cast<std::ptrdiff_t
>(i), diag[i]});
1202 for (
unsigned f = 0; f < 2 *
D; ++f) {
1203 const std::size_t j = neighborIds_[f * n + i];
1204 if (j !=
noNode && faceCoeffs[f * n + i] !=
T(0))
1205 row.emplace_back(
static_cast<std::ptrdiff_t
>(j),
1206 -faceCoeffs[f * n + i]);
1208 std::sort(row.begin(), row.end());
1209 for (
const auto &entry : row) {
1210 cols.push_back(entry.first);
1211 values.push_back(entry.second);
1213 rowPtr[i + 1] =
static_cast<std::ptrdiff_t
>(cols.size());
1217 const T absTolerance = parameters.tolerance * b_norm;
1218 bool solverFailed =
false;
1220 std::string path =
" [CPU]";
1221#ifdef VIENNALS_GPU_BICGSTAB
1222 if (gpuMode_ !=
GpuMode::Cpu && !gpuOff_ && n >= gpuMinNodes) {
1223 static_assert(
sizeof(std::ptrdiff_t) ==
sizeof(std::int64_t));
1224 std::vector<double> valuesGpu(values.begin(), values.end()),
1225 bGpu(b.begin(), b.end()), xGpu(x.begin(), x.end());
1226 unsigned gpuIterations = 0;
1227 const int status = gpu::gpuDiffusionSolve(
1228 static_cast<std::int64_t
>(n),
1229 reinterpret_cast<const std::int64_t *
>(rowPtr.data()),
1230 reinterpret_cast<const std::int64_t *
>(cols.data()), valuesGpu.data(),
1231 bGpu.data(), xGpu.data(), parameters.maxIterations,
1232 static_cast<double>(absTolerance), gpuIterations);
1234 for (std::size_t i = 0; i < n; ++i)
1235 x[i] =
static_cast<T>(xGpu[i]);
1236 iterations = gpuIterations;
1239 }
else if (status == 0) {
1241 reportGpuUnavailable(
"OxidationDiffusion: GPU mode was selected, but "
1242 "the GPU is unavailable.");
1246 VIENNACORE_LOG_ERROR(
1247 "OxidationDiffusion: the GPU diffusion solve failed." +
1249 solverFailed =
true;
1256 VIENNACORE_LOG_ERROR(
"OxidationDiffusion: explicit GPU mode was "
1257 "requested, but ViennaLS was built without "
1258 "VIENNALS_GPU_BICGSTAB.");
1260 VIENNACORE_LOG_WARNING(
"OxidationDiffusion: GPU mode Auto was requested, "
1261 "but ViennaLS was built without "
1262 "VIENNALS_GPU_BICGSTAB. Using the CPU solver.");
1267 typedef amgcl::backend::builtin<T> Backend;
1268 typedef amgcl::make_solver<
1269 amgcl::amg<Backend, amgcl::coarsening::smoothed_aggregation,
1270 amgcl::relaxation::ilu0>,
1271 amgcl::solver::bicgstab<Backend>>
1273 typename Solver::params prm;
1274 prm.solver.maxiter = parameters.maxIterations;
1275 prm.solver.tol =
T(0);
1276 prm.solver.abstol = absTolerance;
1277 const auto rows =
static_cast<std::ptrdiff_t
>(n);
1278 auto A = std::tie(rows, rowPtr, cols, values);
1280 Solver solve(A, prm);
1281 std::size_t count = 0;
1283 std::tie(count, estimate) = solve(A, b, x);
1284 iterations =
static_cast<unsigned>(count);
1285 }
catch (
const std::exception &e) {
1286 solverFailed =
true;
1287 VIENNACORE_LOG_WARNING(std::string(
"solveDiffusion: AMGCL failed: ") +
1294 bool finiteSolution = !solverFailed;
1295 for (std::size_t i = 0; i < n && finiteSolution; ++i) {
1297 for (
auto k = rowPtr[i]; k < rowPtr[i + 1]; ++k)
1298 ax += values[k] * x[cols[k]];
1299 finiteSolution = std::isfinite(x[i]) && std::isfinite(ax);
1300 residual = std::max(residual, std::abs(b[i] - ax));
1302 if (finiteSolution) {
1303 for (std::size_t i = 0; i < n; ++i)
1304 nodes[i].concentration = x[i];
1306 for (std::size_t i = 0; i < n; ++i)
1307 nodes[i].concentration = initialGuess[i];
1308 residual = std::numeric_limits<T>::infinity();
1309 Logger::getInstance()
1310 .addWarning(
"solveDiffusion: the solve failed or produced "
1311 "non-finite concentrations; keeping the sanitized "
1315 normalizedResidual_ = residual / b_norm;
1316 lastSolveConverged_ = finiteSolution &&
1317 std::isfinite(normalizedResidual_) &&
1318 normalizedResidual_ <= parameters.tolerance;
1321 if (Logger::hasTiming()) {
1322 const std::string tag =
"diffusion n=" + std::to_string(n) +
1323 " iters=" + std::to_string(iterations) +
1324 " res=" + std::to_string(residual) + path;
1325 Logger::getInstance().addTiming(tag +
" AMG-BiCGSTAB", tSolve).print();
1327 if (Logger::hasDebug()) {
1328 Logger::getInstance()
1329 .addTiming(
"diffusion n=" + std::to_string(n) + path +
1330 " diag/b precompute",
1334 if (!lastSolveConverged_) {
1336 std::snprintf(numbers,
sizeof(numbers),
"residual=%.3e, tolerance=%.1e",
1337 double(normalizedResidual_),
double(parameters.tolerance));
1338 VIENNACORE_LOG_WARNING(
1339 "solveDiffusion: AMG-BiCGSTAB did not converge after " +
1340 std::to_string(iterations) +
"/" +
1341 std::to_string(parameters.maxIterations) +
" iterations (" + numbers +
1346#ifdef VIENNALS_GPU_BICGSTAB
1347 static std::string gpuErrorDetail() {
1348 const char *detail = gpu::gpuGetLastErrorMessage();
1349 if (detail && detail[0] !=
'\0')
1350 return std::string(
" Detail: ") + detail;
1358 void reportGpuUnavailable(
const std::string &message)
const {
1360 VIENNACORE_LOG_WARNING(message + gpuErrorDetail() +
1361 " Falling back to the CPU solver.");
1363 VIENNACORE_LOG_ERROR(message + gpuErrorDetail());
1368 void logDiffusionBackend(
const std::string &backend,
1369 const std::string &detail)
const {
1370 if (!Logger::hasInfo())
1372 const std::string msg =
1373 "OxidationDiffusion: using " + backend +
1374 " for diffusion solve (nodes=" + std::to_string(nodes.size()) +
1375 (detail.empty() ? std::string() :
", " + detail) +
").";
1376 if (msg == lastLoggedBackend_)
1378 lastLoggedBackend_ = msg;
1379 Logger::getInstance().addInfo(msg).print();
1384 template <
class SolverT>
1386 makeStencilSide(std::size_t nodeId,
const std::vector<SolverT> &previous,
1387 unsigned direction,
int offset,
T diffusion)
const {
1388 const IndexType &nodeIndex = nodes[nodeId].index;
1390 IndexType neighbor = nodeIndex;
1391 neighbor[direction] += offset;
1394 return zeroFluxSide();
1396 const std::size_t neighborId =
lookupNode(neighbor);
1397 if (neighborId !=
noNode)
1398 return {
gridDelta, 0., previous[neighborId]};
1400 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
1401 const std::size_t n = nodes.size();
1402 const Boundary faceType = faceBCTypes_[fi * n + nodeId];
1403 const T faceDist = faceBCDists_[fi * n + nodeId];
1404 if (faceType == Boundary::REACTION)
1405 return reactionBoundarySide(faceRate(nodeId, fi), faceDist, diffusion,
1406 faceReactionNormal_[fi * n + nodeId]);
1407 if (faceType == Boundary::AMBIENT)
1408 return ambientBoundarySide(faceDist, diffusion);
1409 if (faceType == Boundary::MASK)
1410 return maskBoundarySide(faceDist, diffusion);
1411 return zeroFluxSide();
1414 void addAxisContribution(
T &rightHandSide,
T &diagonal,
1415 const StencilSide &negativeSide,
1416 const StencilSide &positiveSide,
T diffusion)
const {
1417 const T distanceSum = negativeSide.distance + positiveSide.distance;
1418 if (distanceSum <= std::numeric_limits<T>::epsilon())
1421 addSideContribution(rightHandSide, diagonal, negativeSide, distanceSum,
1423 addSideContribution(rightHandSide, diagonal, positiveSide, distanceSum,
1427 void addSideContribution(
T &rightHandSide,
T &diagonal,
1428 const StencilSide &side,
T distanceSum,
1429 T diffusion)
const {
1430 if (side.distance <= std::numeric_limits<T>::epsilon())
1433 const T coefficient =
T(2) *
diffusion / (side.distance * distanceSum);
1434 rightHandSide += coefficient * side.constant;
1435 diagonal += coefficient * (
T(1) - side.nodeCoefficient);
1438 StencilSide zeroFluxSide()
const {
return {
gridDelta, 1., 0.}; }
1450 StencilSide reactionBoundarySide(
T rate,
T distance,
T diffusion,
1451 T normalComponent)
const {
1453 const T reactionRate = rate * normalComponent;
1454 const T denominator = conductance + reactionRate;
1455 if (denominator <= std::numeric_limits<T>::epsilon())
1456 return zeroFluxSide();
1458 return {distance, conductance / denominator, 0.};
1462 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1464 const std::size_t directId =
lookupNode(index);
1465 if (directId !=
noNode) {
1467 reactionBoundarySampleFromNode(reactionIt, nodes[directId]);
1477 std::size_t bestNode = std::numeric_limits<std::size_t>::max();
1478 T bestDistance2 = std::numeric_limits<T>::max();
1480 for (
int radius = 1; radius <= 4; ++radius) {
1482 offset.fill(-radius);
1484 IndexType candidate = index;
1486 for (
unsigned d = 0; d <
D; ++d) {
1487 candidate[d] += offset[d];
1488 distance2 +=
static_cast<T>(offset[d] * offset[d]);
1491 if (distance2 >
T(0) &&
inBounds(candidate)) {
1493 if (foundId !=
noNode && distance2 < bestDistance2) {
1495 reactionBoundarySampleFromNode(reactionIt, nodes[foundId]);
1497 bestDistance2 = distance2;
1504 for (; dim <
D; ++dim) {
1505 if (offset[dim] < radius) {
1509 offset[dim] = -radius;
1515 if (bestNode != std::numeric_limits<std::size_t>::max())
1519 if (bestNode == std::numeric_limits<std::size_t>::max())
1521 return reactionBoundarySampleFromNode(reactionIt, nodes[bestNode]);
1525 reactionBoundarySampleFromNode(ConstSparseIterator &reactionIt,
1526 const Node &node)
const {
1533 T bestDistance = std::numeric_limits<T>::max();
1534 const T insidePhi =
valueAt(reactionIt, node.index);
1535 const std::size_t nodeId =
lookupNode(node.index);
1536 const std::size_t n = nodes.size();
1537 std::optional<Vec3D<T>> siNormal;
1539 for (
unsigned direction = 0; direction <
D; ++direction) {
1540 for (
const int offset : {-1, 1}) {
1541 IndexType neighbor = node.index;
1542 neighbor[direction] += offset;
1546 const T outsidePhi =
valueAt(reactionIt, neighbor);
1547 if (!
crosses(insidePhi, outsidePhi))
1550 const T distance = crossingDistance(insidePhi, outsidePhi);
1551 if (distance >= bestDistance)
1554 const T diffusion = getEffectiveDiffusionCoefficient(node.index);
1555 const unsigned fi = direction * 2u + (offset == 1 ? 1u : 0u);
1556 T normalComponent, rate;
1557 if (nodeId !=
noNode && faceReactionNormal_.size() == 2 *
D * n &&
1558 faceBCTypes_[fi * n + nodeId] == Boundary::REACTION) {
1559 normalComponent = faceReactionNormal_[fi * n + nodeId];
1560 rate = faceRate(nodeId, fi);
1563 siNormal = computeSiNormal(node.index, reactionIt);
1564 normalComponent = std::abs((*siNormal)[direction]);
1568 reactionBoundarySide(rate, distance, diffusion, normalComponent);
1570 best.distance = distance;
1571 best.concentration =
1572 side.nodeCoefficient * node.concentration + side.constant;
1573 best.crossingAxis = direction;
1574 best.crossingOffset = offset;
1575 bestDistance = distance;
1582 StencilSide ambientBoundarySide(
T distance,
T diffusion)
const {
1584 const T denominator = conductance + parameters.transferCoefficient;
1585 if (denominator <= std::numeric_limits<T>::epsilon())
1586 return zeroFluxSide();
1588 return {distance, conductance / denominator,
1589 parameters.transferCoefficient *
1590 parameters.equilibriumConcentration / denominator};
1593 StencilSide maskBoundarySide(
T distance,
T diffusion)
const {
1594 if (parameters.maskTransferCoefficient <= std::numeric_limits<T>::epsilon())
1595 return zeroFluxSide();
1598 const T denominator = conductance + parameters.maskTransferCoefficient;
1599 if (denominator <= std::numeric_limits<T>::epsilon())
1600 return zeroFluxSide();
1602 return {distance, conductance / denominator,
1603 parameters.maskTransferCoefficient * parameters.maskConcentration /
1608 Vec3D<T> crossingPoint(std::size_t i,
unsigned fi)
const {
1609 const std::size_t n = nodes.size();
1610 Vec3D<T> c{
T(0),
T(0),
T(0)};
1611 for (
unsigned d = 0; d <
D; ++d)
1612 c[d] =
static_cast<T>(nodes[i].index[d]);
1614 (fi % 2 == 1 ?
T(1) :
T(-1)) * faceBCDists_[fi * n + i] /
gridDelta;
1621 void forEachNodeNear(
const Vec3D<T> &p,
int radius, F f)
const {
1622 IndexType centre, offset;
1623 for (
unsigned d = 0; d <
D; ++d)
1624 centre[d] =
static_cast<viennahrle::IndexType
>(std::llround(p[d]));
1625 offset.fill(-radius);
1627 IndexType q = centre;
1628 for (
unsigned d = 0; d <
D; ++d)
1634 while (d <
D && offset[d] == radius)
1635 offset[d++] = -radius;
1646 T interfaceNormalStressAt(
const Vec3D<T> &p)
const {
1647 if (interfaceNormalStress_.size() != nodes.size() ||
1648 interfaceAnchor_.size() != nodes.size())
1649 return std::numeric_limits<T>::quiet_NaN();
1650 T weightSum =
T(0), sum =
T(0);
1651 forEachNodeNear(p, 2, [&](std::size_t j) {
1652 const T v = interfaceNormalStress_[j];
1653 if (!std::isfinite(v))
1656 for (
unsigned d = 0; d <
D; ++d)
1657 r2 += (interfaceAnchor_[j][d] - p[d]) * (interfaceAnchor_[j][d] - p[d]);
1658 const T w = std::exp(-r2 /
T(2));
1662 return weightSum >
T(1e-12) ? sum / weightSum
1663 : std::numeric_limits<T>::quiet_NaN();
1669 T interfaceReactionRate(std::size_t i,
unsigned fi)
const {
1670 const std::size_t n = nodes.size();
1671 T rate = parameters.reactionRate;
1672 if (parameters.reactionActivationVolume !=
T(0)) {
1673 T normalStress = interfaceNormalStressAt(crossingPoint(i, fi));
1674 if (!std::isfinite(normalStress)) {
1675 normalStress = -parameters.referencePressure;
1676 medianNormalStress(nodes[i].index, normalStress);
1679 stressExponent(-normalStress, parameters.reactionActivationVolume);
1681 rate *= std::clamp(stressFactor(exponent),
T(0.1),
T(1.));
1683 if (parameters.reactionRateRatio111 !=
T(1)) {
1684 const auto &normal = faceCrossingNormal_[fi * n + i];
1686 for (
unsigned d = 0; d <
D; ++d)
1687 dot += normal[d] * parameters.crystalAxis[d];
1689 T(1) + (parameters.reactionRateRatio111 -
T(1)) * (
T(1) - dot * dot);
1694 void updateFaceRates() {
1695 const std::size_t n = nodes.size();
1696 faceReactionRate_.assign(2 *
D * n,
T(0));
1697#pragma omp parallel for schedule(static)
1698 for (std::size_t i = 0; i < n; ++i)
1699 for (
unsigned fi = 0; fi < 2 *
D; ++fi)
1700 if (faceBCTypes_[fi * n + i] == Boundary::REACTION)
1701 faceReactionRate_[fi * n + i] = interfaceReactionRate(i, fi);
1704 T faceRate(std::size_t i,
unsigned fi)
const {
1705 const std::size_t n = nodes.size();
1706 return faceReactionRate_.size() == 2 *
D * n
1707 ? faceReactionRate_[fi * n + i]
1714 void updateFaceSpeeds() {
1715 const std::size_t n = nodes.size();
1716 faceFrontSpeed_.assign(2 *
D * n,
T(0));
1717#pragma omp parallel for schedule(static)
1718 for (std::size_t i = 0; i < n; ++i)
1719 for (
unsigned fi = 0; fi < 2 *
D; ++fi) {
1720 if (faceBCTypes_[fi * n + i] != Boundary::REACTION)
1722 const T rate = faceRate(i, fi);
1723 const auto side = reactionBoundarySide(rate, faceBCDists_[fi * n + i],
1724 nodeDiffusionCoefficient(i),
1725 faceReactionNormal_[fi * n + i]);
1726 const T concentration =
1727 side.nodeCoefficient * nodes[i].concentration + side.constant;
1728 faceFrontSpeed_[fi * n + i] = std::abs(parameters.velocitySign) * rate *
1730 (parameters.oxidantMoleculeDensity *
1731 parameters.expansionCoefficient);
1733 frontStart_.assign(n + 1, 0);
1734 for (std::size_t i = 0; i < n; ++i) {
1735 std::size_t count = 0;
1736 for (
unsigned fi = 0; fi < 2 *
D; ++fi)
1737 count += faceBCTypes_[fi * n + i] == Boundary::REACTION;
1738 frontStart_[i + 1] = frontStart_[i] + count;
1740 frontCrossings_.resize(frontStart_[n]);
1741 for (std::size_t i = 0; i < n; ++i) {
1742 std::size_t k = frontStart_[i];
1743 for (
unsigned fi = 0; fi < 2 *
D; ++fi)
1744 if (faceBCTypes_[fi * n + i] == Boundary::REACTION)
1745 frontCrossings_[k++] = {
1746 crossingPoint(i, fi), faceReactionNormal_[fi * n + i],
1747 faceFrontSpeed_[fi * n + i], faceCrossingNormal_[fi * n + i]};
1752 T rate = parameters.reactionRate;
1754 if (parameters.reactionActivationVolume !=
T(0)) {
1755 T normalStress = -parameters.referencePressure;
1756 const std::size_t nodeId =
lookupNode(index);
1757 if (nodeId !=
noNode && nodeId < interfaceNormalStress_.size() &&
1758 std::isfinite(interfaceNormalStress_[nodeId]))
1759 normalStress = interfaceNormalStress_[nodeId];
1761 medianNormalStress(index, normalStress);
1764 stressExponent(-normalStress, parameters.reactionActivationVolume);
1777 rate *= std::clamp(stressFactor(exponent),
T(0.1),
T(1.));
1780 if (parameters.reactionRateRatio111 !=
T(1)) {
1781 const std::size_t nodeId =
lookupNode(index);
1783 const auto &normal = nodes[nodeId].siNormal;
1785 for (
unsigned d = 0; d <
D; ++d)
1786 dot += normal[d] * parameters.crystalAxis[d];
1788 (parameters.reactionRateRatio111 -
T(1)) * (
T(1) - dot * dot);
1800 bool medianNormalStress(
const IndexType &index,
T &result)
const {
1801 return medianNormalStress(index, stressLookup, result);
1817 template <
class Sample>
1818 bool frontStressAt(std::size_t i,
const Vec3D<T> &n,
T phi,
T sAt,
1819 Sample sample,
T &out)
const {
1820 const std::size_t count = nodes.size();
1822 for (
unsigned k = 0; k < 2; ++k) {
1823 const T s =
T(2 + k);
1824 std::array<T, D> base{}, frac{};
1825 for (
unsigned d = 0; d <
D; ++d) {
1826 const T y =
static_cast<T>(nodes[i].index[d]) + n[d] * (s - phi);
1827 base[d] = std::floor(y);
1828 frac[d] = y - base[d];
1830 T sum =
T(0), weightSum =
T(0);
1831 for (
unsigned corner = 0; corner < (1u <<
D); ++corner) {
1834 for (
unsigned d = 0; d <
D; ++d) {
1835 const bool up = (corner >> d) & 1u;
1836 idx[d] =
static_cast<viennahrle::IndexType
>(base[d]) + (up ? 1 : 0);
1837 w *= up ? frac[d] :
T(1) - frac[d];
1842 if (j ==
noNode || nodes[j].inMask)
1844 bool firstLayer =
false;
1845 if (faceBCTypes_.size() == 2 *
D * count)
1846 for (
unsigned f = 0; f < 2 *
D && !firstLayer; ++f)
1847 firstLayer = faceBCTypes_[f * count + j] == Boundary::REACTION;
1850 const T v = sample(j);
1851 if (!std::isfinite(v))
1856 if (weightSum <
T(0.5))
1858 q[k] = sum / weightSum;
1860 out = q[0] + (q[1] - q[0]) * (sAt -
T(2));
1861 return std::isfinite(out);
1865 T frontDistance(
const IndexType &index)
const {
1866 ConstSparseIterator it(reactionInterface->getDomain());
1867 it.goToIndices(index);
1869 it.isDefined() ?
static_cast<T>(reactionSign) * it.getValue() :
T(0);
1870 return std::isfinite(v) ? std::clamp(v,
T(0),
T(1.5)) :
T(0);
1873 bool reactionNode(std::size_t i)
const {
1874 const std::size_t n = nodes.size();
1875 if (faceBCTypes_.size() != 2 *
D * n)
1877 for (
unsigned f = 0; f < 2 *
D; ++f)
1878 if (faceBCTypes_[f * n + i] == Boundary::REACTION)
1883 bool medianNormalStress(
const IndexType &index,
const StressLookup &lookup,
1885 Vec3D<T> normal{
T(0),
T(0),
T(0)};
1886 normal[
D - 1] =
T(1);
1887 if (
const std::size_t centerNode =
lookupNode(index); centerNode !=
noNode)
1888 normal = nodes[centerNode].siNormal;
1889 T samples[2 *
D + 1];
1891 const auto sampleAt = [&](
const IndexType &sampleIndex) {
1894 const std::size_t sampleNode =
lookupNode(sampleIndex);
1895 if (sampleNode ==
noNode || nodes[sampleNode].inMask)
1898 if (found == lookup.end())
1901 for (
unsigned r = 0; r <
D; ++r)
1902 for (
unsigned c = 0; c <
D; ++c)
1903 value += normal[r] * found->second[3 * r + c] * normal[c];
1904 if (std::isfinite(value))
1905 samples[count++] = value;
1908 for (
unsigned d = 0; d <
D; ++d) {
1909 IndexType neighbor = index;
1917 std::nth_element(samples, samples + count / 2, samples + count);
1918 result = samples[count / 2];
1939 void updateInterfaceNormalStress() {
1946 void updateLawPressure() {
1947 const std::size_t n = nodes.size();
1948 lawPressure_.assign(n, std::numeric_limits<T>::quiet_NaN());
1949 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1950 for (std::size_t i = 0; i < n; ++i) {
1951 if (nodes[i].inMask || !reactionNode(i))
1955 Vec3D<T> into = (parameters.reactionRateRatio111 !=
T(1) ||
1956 parameters.reactionActivationVolume !=
T(0))
1958 : computeSiNormal(nodes[i].index, reactionIt);
1959 for (
unsigned d = 0; d <
D; ++d)
1960 into[d] *=
static_cast<T>(reactionSign);
1961 const T phi = frontDistance(nodes[i].index);
1962 const auto pressureAt = [&](std::size_t j) {
1965 return found == pressureLookup.end()
1966 ? std::numeric_limits<T>::quiet_NaN()
1970 if (frontStressAt(i, into, phi, phi, pressureAt, value))
1971 lawPressure_[i] = value;
1977 T nodeDiffusionCoefficient(std::size_t nodeId)
const {
1978 T value = getEffectiveDiffusionCoefficient(nodes[nodeId].index);
1979 if (nodes[nodeId].inMask)
1980 value /=
static_cast<T>(parameters.maskPermeabilityRatio);
1991 T faceDiffusionCoefficient(std::size_t nodeId,
unsigned direction,
int offset,
1992 T ownDiffusion)
const {
1993 offset =
stencilOffset(nodes[nodeId].index, direction, offset);
1994 IndexType neighbor = nodes[nodeId].index;
1995 neighbor[direction] += offset;
1997 return ownDiffusion;
1998 const std::size_t neighborId =
lookupNode(neighbor);
1999 if (neighborId ==
noNode)
2000 return ownDiffusion;
2001 const T neighborDiffusion = nodeDiffusionCoefficient(neighborId);
2002 const T sum = ownDiffusion + neighborDiffusion;
2003 if (sum <= std::numeric_limits<T>::epsilon())
2005 return T(2) * ownDiffusion * neighborDiffusion / sum;
2008 T getEffectiveDiffusionCoefficient(
const IndexType &index)
const {
2009 if (parameters.diffusionActivationVolume ==
T(0))
2010 return parameters.diffusionCoefficient;
2012 T pressure = parameters.referencePressure;
2014 if (found != pressureLookup.end())
2015 pressure = found->second;
2016 if (
const std::size_t
id =
lookupNode(index);
2017 id !=
noNode &&
id < lawPressure_.size() &&
2018 std::isfinite(lawPressure_[
id]))
2019 pressure = lawPressure_[id];
2020 if (!std::isfinite(pressure))
2021 pressure = parameters.referencePressure;
2024 stressExponent(pressure, parameters.diffusionActivationVolume);
2025 return parameters.diffusionCoefficient * stressFactor(exponent);
2028 T stressExponent(
T pressure,
T activationVolume)
const {
2029 const T thermalEnergy =
2030 boltzmannConstant * std::max(parameters.temperature,
T(1.));
2032 return -(pressure - parameters.referencePressure) * activationVolume /
2036 static T stressFactor(
T exponent) {
2037 if (!std::isfinite(exponent))
2039 if (exponent <= std::log(minStressFactor))
2040 return minStressFactor;
2041 if (exponent >= std::log(maxStressFactor))
2042 return maxStressFactor;
2043 return std::exp(exponent);
2046 Vec3D<T> computeSiNormal(
const IndexType &index,
2047 ConstSparseIterator &reactionIt)
const {
2051 auto reflectToGrid = [&](IndexType idx) {
2052 auto &g = reactionInterface->getGrid();
2053 for (
unsigned d2 = 0;
d2 <
D; ++
d2) {
2054 const auto lo = g.getMinGridPoint(d2);
2055 const auto hi = g.getMaxGridPoint(d2);
2057 idx[
d2] = 2 * lo - idx[
d2];
2059 idx[
d2] = 2 * hi - idx[
d2];
2064 reactionIt, index, [&](
const IndexType &q,
bool &ok) {
2066 return reflectToGrid(q);
2069 for (
unsigned d = 0; d <
D; ++d)
2070 len += gradient[d] * gradient[d];
2071 len = std::sqrt(len);
2072 if (len > std::numeric_limits<T>::epsilon()) {
2073 for (
unsigned d = 0; d <
D; ++d)
2076 gradient = Vec3D<T>{0., 0., 0.};
2077 gradient[
D - 1] =
T(1);
2082 std::pair<Boundary, T> classifyBoundary(ConstSparseIterator &reactionIt,
2083 ConstSparseIterator &ambientIt,
2084 ConstSparseIterator &maskIt,
2085 const IndexType &inside,
2086 const IndexType &outside)
const {
2087 const T reactionInside =
valueAt(reactionIt, inside);
2088 const T reactionOutside =
valueAt(reactionIt, outside);
2089 const T ambientInside =
valueAt(ambientIt, inside);
2090 const T ambientOutside =
valueAt(ambientIt, outside);
2091 const T maskInside = valueAtMask(maskIt, inside);
2092 const T maskOutside = valueAtMask(maskIt, outside);
2094 const T reactionDistance =
2095 crosses(reactionInside, reactionOutside)
2096 ? crossingDistance(reactionInside, reactionOutside)
2097 : std::numeric_limits<
T>::max();
2098 const T ambientDistance =
2099 crosses(ambientInside, ambientOutside)
2100 ? crossingDistance(ambientInside, ambientOutside)
2101 : std::numeric_limits<
T>::max();
2102 const T maskDistance =
2103 maskInterface !=
nullptr &&
crosses(maskInside, maskOutside)
2104 ? crossingDistance(maskInside, maskOutside)
2105 : std::numeric_limits<
T>::max();
2107 if (reactionDistance == std::numeric_limits<T>::max() &&
2108 ambientDistance == std::numeric_limits<T>::max() &&
2109 maskDistance == std::numeric_limits<T>::max())
2112 if (reactionDistance <= ambientDistance && reactionDistance <= maskDistance)
2113 return {Boundary::REACTION, reactionDistance};
2121 if (ambientDistance != std::numeric_limits<T>::max() &&
2122 (isMaskAtCrossing(maskInside, maskOutside, ambientDistance) ||
2123 (maskInterface !=
nullptr &&
2124 static_cast<T>(maskSign) * valueAtMask(maskIt, outside) >=
T(0))))
2125 return {Boundary::MASK, ambientDistance};
2127 if (maskDistance <= ambientDistance)
2128 return {Boundary::MASK, maskDistance};
2129 return {Boundary::AMBIENT, ambientDistance};
2132 bool isInsideOxide(
T reactionPhi,
T ambientPhi)
const {
2138 constexpr T eps =
T(1e-9);
2139 return reactionSign * reactionPhi >= -eps &&
2140 ambientSign * ambientPhi >= -eps;
2143 ConstSparseIterator makeMaskIterator()
const {
2144 if (maskInterface ==
nullptr)
2145 return ConstSparseIterator(reactionInterface->getDomain());
2146 return ConstSparseIterator(maskInterface->getDomain());
2149 bool isInsideMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
2150 if (maskInterface ==
nullptr)
2152 return maskSign *
valueAt(maskIt, index) >= 0.;
2155 T valueAtMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
2156 if (maskInterface ==
nullptr)
2157 return std::numeric_limits<T>::max();
2158 return valueAt(maskIt, index);
2161 bool isMaskAtCrossing(
T maskInside,
T maskOutside,
T distance)
const {
2162 if (maskInterface ==
nullptr)
2164 const T fraction = std::clamp(distance /
gridDelta,
T(0),
T(1));
2167 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
2168 return static_cast<T>(maskSign) * maskPhi >=
T(0);
2171 T crossingDistance(
T insidePhi,
T outsidePhi)
const {
2173 insidePhi, outsidePhi, parameters.minBoundaryDistance,
gridDelta);