92 using IndexType = viennahrle::Index<D>;
93 using ConstSparseIterator =
94 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
117 Vec3D<T> velocity{0., 0., 0.};
118 bool contact =
false;
122 struct SparseMatrix {
123 std::size_t nodeCount = 0;
124 std::vector<std::size_t> rowPtr;
125 std::vector<std::size_t> colIndex;
126 std::vector<T> values;
127 std::vector<T> invDiagonal;
130 struct MultigridLevel {
131 std::vector<IndexType> indices;
133 std::vector<std::vector<std::size_t>> children;
134 std::vector<std::size_t> fineToCoarse;
138 SmartPointer<OxidationDeformation<T, D>> deformationField =
nullptr;
139 SmartPointer<Domain<T, D>> maskInterface =
nullptr;
140 SmartPointer<Domain<T, D>> ambientInterface =
nullptr;
143 int ambientSign = -1;
146 bool useRequestedBounds =
false;
147 T residual = std::numeric_limits<T>::max();
148 unsigned iterations = 0;
149 std::array<T, D> maxVelocity_{};
150 std::size_t contactNodes = 0;
151 std::size_t fixedNodes = 0;
152 T lastApplyVelocityChange = std::numeric_limits<T>::max();
153 T lastApplyAbsoluteVelocityChange = std::numeric_limits<T>::max();
155 std::size_t candidateContactFaces_ = 0;
156 std::size_t activeContactFaces_ = 0;
157 std::size_t tensileContactFaces_ = 0;
158 T minContactNormalTraction_ = std::numeric_limits<T>::max();
159 T maxContactNormalTraction_ = std::numeric_limits<T>::lowest();
160 T contactReleaseThreshold_ = 0.;
161 std::unordered_map<std::size_t, Vec3D<T>> previousContactTraction_;
162 std::unordered_map<std::size_t, T> previousContactReleaseScale_;
163 std::vector<T> previousAitkenResidual;
168 std::vector<Vec3D<T>> elasticU_;
169 IndexType requestedMinIndex{};
170 IndexType requestedMaxIndex{};
171 std::vector<Node> nodes;
173 std::vector<uint8_t> contactFaceActive_;
174 std::vector<Vec3D<T>> contactFaceVelocity_;
175 std::vector<Vec3D<T>> contactFaceTraction_;
176 std::vector<T> contactFaceDistance_;
177 std::unordered_map<IndexType, T, typename IndexType::hash> ambientPhiCache_;
184 std::vector<MultigridLevel> cachedMultigridLevels_;
185 std::vector<uint8_t> cachedContactFaceActive_;
186 std::size_t cachedNodeCount_ = 0;
194 : deformationField(passedDeformation), parameters(passedParameters) {}
200 : deformationField(passedDeformation), maskInterface(passedMaskInterface),
201 parameters(passedParameters), maskSign((passedMaskSign < 0) ? -1 : 1) {}
205 static SmartPointer<OxidationMaskBending>
208 return SmartPointer<OxidationMaskBending>::New(passedDeformation,
212 static SmartPointer<OxidationMaskBending>
216 return SmartPointer<OxidationMaskBending>::New(
217 passedDeformation, passedMaskInterface, passedParameters,
222 int passedMaskSign = 1) {
223 maskInterface = passedMaskInterface;
224 maskSign = (passedMaskSign < 0) ? -1 : 1;
233 int passedAmbientSign = -1) {
234 ambientInterface = passedAmbientInterface;
235 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
240 parameters = passedParameters;
245 const IndexType &passedMaxIndex) {
246 requestedMinIndex = passedMinIndex;
247 requestedMaxIndex = passedMaxIndex;
248 useRequestedBounds =
true;
253 useRequestedBounds =
false;
265 return lastApplyAbsoluteVelocityChange;
269 if (maskInterface ==
nullptr) {
273 if (deformationField ==
nullptr) {
274 Logger::getInstance()
275 .addWarning(
"OxidationMaskBending: deformation field is null; "
276 "mask bending will produce zero velocities.")
282 const auto previousVelocities = collectVelocitiesByGridPoint();
283 if (!initialiseGrid())
289 seedFromPrevious(previousVelocities);
291 Logger::getInstance()
292 .addWarning(
"OxidationMaskBending: no mask nodes found after "
293 "buildNodes(). Verify that the mask level set defines "
294 "a non-empty interior region within the solve bounds.")
299 if (contactNodes == 0)
300 VIENNACORE_LOG_DEBUG(
301 "OxidationMaskBending: mask has no oxide-contact nodes; "
302 "no traction will be applied this step.");
303 solveElasticVelocity();
304 validateNodeVelocities(
"solveElasticVelocity");
305 smoothVelocityField();
307 const auto fixedPointResidual = contactResidualVector(previousVelocities);
308 const T omega = aitkenRelaxation(fixedPointResidual);
309 relaxVelocities(previousVelocities, omega);
310 validateNodeVelocities(
"Aitken relaxation");
311 lastApplyVelocityChange = maxVelocityChange(previousVelocities);
312 lastApplyAbsoluteVelocityChange =
313 absoluteVelocityChange(previousVelocities);
315 maxVelocity_.fill(
T(0));
316 for (
const auto &node : nodes) {
317 for (
unsigned d = 0; d <
D; ++d)
318 maxVelocity_[d] = std::max(maxVelocity_[d], std::abs(node.velocity[d]));
320 VIENNACORE_LOG_DEBUG(
321 "OxidationMaskBending: contact faces active=" +
322 std::to_string(activeContactFaces_) +
"/" +
323 std::to_string(candidateContactFaces_) +
", tensileSkipped=" +
324 std::to_string(tensileContactFaces_) +
", normalTraction=[" +
326 candidateContactFaces_ == 0 ?
T(0) : minContactNormalTraction_) +
329 candidateContactFaces_ == 0 ?
T(0) : maxContactNormalTraction_) +
330 "], aitkenOmega=" + std::to_string(omega) +
", contactLoadRelaxation=" +
332 std::clamp(parameters.contactLoadRelaxation,
T(0.02),
T(1))) +
333 ", contactReleaseThreshold=" +
334 std::to_string(contactReleaseThreshold_) +
", residual=" +
335 std::to_string(lastApplyVelocityChange) +
", absVelocityChange=" +
336 std::to_string(lastApplyAbsoluteVelocityChange));
349 if (!isElasticContactMode() || nodes.empty())
351 const T dt = parameters.stressTimeStep;
353 for (
auto &node : nodes)
354 node.velocity = {
T(0),
T(0),
T(0)};
358 const std::size_t n = nodes.size();
360 for (std::size_t i = 0; i < n; ++i)
361 elasticU_[i] = nodes[i].velocity;
364 for (std::size_t i = 0; i < n; ++i)
365 for (
unsigned d = 0; d <
D; ++d)
366 maxUNew = std::max(maxUNew, std::abs(elasticU_[i][d]));
368 const T invDt =
T(1) / dt;
369 for (std::size_t i = 0; i < n; ++i)
370 for (
unsigned d = 0; d <
D; ++d)
371 nodes[i].velocity[d] = elasticU_[i][d] * invDt;
373 VIENNACORE_LOG_INFO(
"ElasticFinalize: dt=" + std::to_string(dt) +
374 " max|u_new|=" + std::to_string(maxUNew) +
375 " max|u_new/dt|=" + std::to_string(maxUNew * invDt));
377 maxVelocity_.fill(
T(0));
378 for (
const auto &node : nodes)
379 for (
unsigned d = 0; d <
D; ++d)
380 maxVelocity_[d] = std::max(maxVelocity_[d], std::abs(node.velocity[d]));
385 unsigned long )
final {
386 if (deformationField ==
nullptr)
389 if (parameters.material >= 0 && material != parameters.material)
395 if (maskInterface ==
nullptr || nodes.empty())
399 for (
unsigned i = 0; i <
D; ++i)
400 index[i] = std::llround(coordinate[i] /
gridDelta);
407 if (isElasticContactMode() && elasticU_.empty()) {
408 const T dt = parameters.stressTimeStep;
410 return {
T(0),
T(0),
T(0)};
411 return getVelocity(index) / dt;
414 return getVelocity(index);
418 const Vec3D<T> & )
final {
419 return maxVelocity_[direction];
425 if (nodes.empty() || maskInterface ==
nullptr)
428 using VD =
typename PointData<T>::VectorDataType;
435 const bool useElasticU = isElasticContactMode() && !elasticU_.empty();
438 ConstSparseIterator it(maskInterface->getDomain());
439 for (; !it.isFinished(); ++it) {
442 const std::size_t nId =
lookupNode(it.getStartIndices());
443 Vec3D<T> v{
T(0),
T(0),
T(0)};
445 v = (useElasticU && nId < elasticU_.size()) ? elasticU_[nId]
446 : nodes[nId].velocity;
447 velocity.push_back(v);
449 maskInterface->getPointData().insertReplaceVectorData(std::move(velocity),
456 const T lambda = lameLambda();
457 const T mu = lameMu();
459 std::vector<Vec3D<T>> velForStress(nodes.size());
460 for (std::size_t i = 0; i < nodes.size(); ++i)
461 velForStress[i] = (useElasticU && i < elasticU_.size())
465 VD stressR0, stressR1, stressR2;
466 for (ConstSparseIterator sit(maskInterface->getDomain()); !sit.isFinished();
468 if (!sit.isDefined())
470 const std::size_t nId =
lookupNode(sit.getStartIndices());
471 Vec3D<T> row0{
T(0),
T(0),
T(0)};
472 Vec3D<T> row1{
T(0),
T(0),
T(0)};
473 Vec3D<T> row2{
T(0),
T(0),
T(0)};
476 std::array<Vec3D<T>, 3> grad{};
477 for (
unsigned j = 0; j < static_cast<unsigned>(
D); ++j) {
478 const auto vp = simpleNeighborVelocity(velForStress, nId, j, +1);
479 const auto vm = simpleNeighborVelocity(velForStress, nId, j, -1);
480 for (
unsigned i = 0; i < 3; ++i)
481 grad[j][i] = (vp[i] - vm[i]) / (
T(2) *
gridDelta);
484 for (
unsigned i = 0; i < static_cast<unsigned>(
D); ++i)
487 row0[0] = lambda * div +
T(2) * mu * grad[0][0];
488 row0[1] = mu * (grad[1][0] + grad[0][1]);
489 row0[2] = (
D > 2) ? mu * (grad[2][0] + grad[0][2]) :
T(0);
491 row1[1] = lambda * div +
T(2) * mu * grad[1][1];
492 row1[2] = (
D > 2) ? mu * (grad[2][1] + grad[1][2]) :
T(0);
495 row2[2] = (
D > 2) ? lambda * div +
T(2) * mu * grad[2][2] :
T(0);
497 stressR0.push_back(row0);
498 stressR1.push_back(row1);
499 stressR2.push_back(row2);
501 maskInterface->getPointData().insertReplaceVectorData(std::move(stressR0),
503 maskInterface->getPointData().insertReplaceVectorData(std::move(stressR1),
505 maskInterface->getPointData().insertReplaceVectorData(std::move(stressR2),
510 bool initialiseGrid() {
512 maskInterface, useRequestedBounds, requestedMinIndex, requestedMaxIndex,
518 void seedFromLevelSet() {
519 if (maskInterface ==
nullptr || nodes.empty())
522 maskInterface->getPointData().getVectorDataIndex(
"MaskVelocity");
525 const auto *vd = maskInterface->getPointData().getVectorData(vIdx);
530 for (; !it.isFinished(); ++it) {
533 const auto ptId = it.getPointId();
534 if (ptId >=
static_cast<decltype(ptId)
>(vd->size()))
536 const std::size_t nId =
lookupNode(it.getStartIndices());
539 if (!isFinite((*vd)[ptId]))
540 throwNonFinite(
"stored mask velocity point data");
542 nodes[nId].velocity = (*vd)[ptId];
547 seedFromPrevious(
const std::unordered_map<std::size_t, Vec3D<T>> &previous) {
548 if (previous.empty())
550 for (
auto &node : nodes) {
551 const auto found = previous.find(
linearIndex(node.index));
552 if (found != previous.end() && isFinite(found->second))
553 node.velocity = found->second;
555 node.velocity = {
T(0),
T(0),
T(0)};
559 std::unordered_map<std::size_t, Vec3D<T>>
560 collectVelocitiesByGridPoint()
const {
561 std::unordered_map<std::size_t, Vec3D<T>> result;
562 result.reserve(nodes.size());
563 for (
const auto &node : nodes)
564 if (
inBounds(node.index) && isFinite(node.velocity))
565 result.emplace(
linearIndex(node.index), node.velocity);
569 static bool isFinite(
const Vec3D<T> &v) {
570 for (
unsigned i = 0; i < 3; ++i)
571 if (!std::isfinite(v[i]))
576 static bool isFiniteTensor(
const std::array<T, 9> &tensor) {
577 for (
T value : tensor)
578 if (!std::isfinite(value))
583 [[noreturn]]
void throwNonFinite(
const std::string &stage)
const {
584 const std::string message =
585 "OxidationMaskBending: " + stage +
" produced non-finite values.";
586 Logger::getInstance().addError(message).print();
587 throw std::runtime_error(message);
590 void validateNodeVelocities(
const std::string &stage)
const {
591 for (
const auto &node : nodes)
592 for (
unsigned i = 0; i <
D; ++i)
593 if (!std::isfinite(node.velocity[i]))
594 throwNonFinite(stage);
598 const std::unordered_map<std::size_t, Vec3D<T>> &previous)
const {
599 if (previous.empty())
600 return std::numeric_limits<T>::max();
602 T changeSquaredSum = 0.;
603 T magnitudeSquaredSum = std::numeric_limits<T>::epsilon();
604 for (
const auto &node : nodes) {
607 const auto found = previous.find(
linearIndex(node.index));
608 if (found == previous.end())
611 for (
unsigned i = 0; i <
D; ++i) {
612 const T delta = node.velocity[i] - found->second[i];
613 changeSquaredSum += delta * delta;
614 magnitudeSquaredSum += node.velocity[i] * node.velocity[i];
615 magnitudeSquaredSum += found->second[i] * found->second[i];
619 const T change = std::sqrt(changeSquaredSum / magnitudeSquaredSum);
620 if (!std::isfinite(change))
621 throwNonFinite(
"mask velocity coupling residual");
625 T absoluteVelocityChange(
626 const std::unordered_map<std::size_t, Vec3D<T>> &previous)
const {
627 if (previous.empty())
628 return std::numeric_limits<T>::max();
630 T changeSquaredSum = 0.;
631 std::size_t components = 0;
632 for (
const auto &node : nodes) {
635 const auto found = previous.find(
linearIndex(node.index));
636 if (found == previous.end())
639 for (
unsigned i = 0; i <
D; ++i) {
640 const T delta = node.velocity[i] - found->second[i];
641 if (!std::isfinite(delta))
642 throwNonFinite(
"mask absolute velocity coupling residual");
643 changeSquaredSum += delta * delta;
649 return std::numeric_limits<T>::max();
650 const T change = std::sqrt(changeSquaredSum /
static_cast<T>(components));
651 if (!std::isfinite(change))
652 throwNonFinite(
"mask absolute velocity coupling residual");
656 std::vector<T> contactResidualVector(
657 const std::unordered_map<std::size_t, Vec3D<T>> &previous)
const {
658 std::vector<T> residualVector;
659 residualVector.reserve(contactNodes *
D);
660 if (previous.empty())
661 return residualVector;
663 for (
const auto &node : nodes) {
666 const auto found = previous.find(
linearIndex(node.index));
667 if (found == previous.end())
669 for (
unsigned i = 0; i <
D; ++i) {
670 const T residualComponent = node.velocity[i] - found->second[i];
671 if (!std::isfinite(residualComponent))
672 throwNonFinite(
"mask contact residual");
673 residualVector.push_back(residualComponent);
676 return residualVector;
679 T aitkenRelaxation(
const std::vector<T> &residualVector) {
680 const T baseOmega = std::clamp(parameters.relaxation,
T(0.01),
T(1));
681 if (residualVector.empty()) {
682 previousAitkenResidual.clear();
683 aitkenOmega = baseOmega;
687 T omega = std::clamp(aitkenOmega,
T(0.01), baseOmega);
688 if (previousAitkenResidual.size() != residualVector.size())
690 if (previousAitkenResidual.size() == residualVector.size()) {
693 T residualNorm2 = 0.;
694 T previousNorm2 = 0.;
695 for (std::size_t i = 0; i < residualVector.size(); ++i) {
696 if (!std::isfinite(residualVector[i]) ||
697 !std::isfinite(previousAitkenResidual[i]))
698 throwNonFinite(
"mask Aitken residual");
699 const T delta = residualVector[i] - previousAitkenResidual[i];
700 numerator += previousAitkenResidual[i] * delta;
701 denominator += delta * delta;
702 residualNorm2 += residualVector[i] * residualVector[i];
703 previousNorm2 += previousAitkenResidual[i] * previousAitkenResidual[i];
706 if (!std::isfinite(numerator) || !std::isfinite(denominator))
707 throwNonFinite(
"mask Aitken coefficient");
709 if (denominator > std::numeric_limits<T>::epsilon()) {
710 omega = -aitkenOmega * numerator / denominator;
711 if (std::isfinite(omega)) {
717 const T omegaMax = (parameters.contactMode > 0) ? baseOmega :
T(1.5);
718 omega = std::clamp(omega,
T(0.05), omegaMax);
720 throwNonFinite(
"mask Aitken coefficient");
724 if (std::isfinite(residualNorm2) && std::isfinite(previousNorm2) &&
725 residualNorm2 > previousNorm2 *
T(1.05)) {
726 omega = std::min(omega, std::max(
T(0.05), aitkenOmega *
T(0.5)));
730 previousAitkenResidual = residualVector;
739 void smoothVelocityField() {
740 const std::size_t n = nodes.size();
744 std::vector<Vec3D<T>> smoothed(n);
745 for (std::size_t
id = 0;
id < n; ++id) {
746 if (nodes[
id].fixed) {
747 smoothed[id] = {
T(0),
T(0),
T(0)};
750 Vec3D<T> sum = nodes[id].velocity;
752 for (
unsigned dir = 0; dir <
D; ++dir) {
753 for (
int off : {-1, 1}) {
754 IndexType nb = nodes[id].index;
761 for (
unsigned c = 0; c <
D; ++c)
762 sum[c] += nodes[nbId].velocity[c];
766 const T w =
T(1) /
static_cast<T>(count);
767 for (
unsigned c = 0; c <
D; ++c)
768 smoothed[
id][c] = sum[c] * w;
771 for (std::size_t
id = 0;
id < n; ++id)
772 nodes[
id].velocity = smoothed[
id];
776 relaxVelocities(
const std::unordered_map<std::size_t, Vec3D<T>> &previous,
778 if (!std::isfinite(omega))
779 throwNonFinite(
"mask Aitken coefficient");
780 if (previous.empty() || omega ==
T(1))
783 for (
auto &node : nodes) {
784 const auto found = previous.find(
linearIndex(node.index));
785 if (found == previous.end())
787 for (
unsigned i = 0; i <
D; ++i) {
789 found->second[i] + omega * (node.velocity[i] - found->second[i]);
790 if (!std::isfinite(relaxed))
791 throwNonFinite(
"mask velocity relaxation");
792 node.velocity[i] = relaxed;
797 void buildAmbientPhiCache() {
798 ambientPhiCache_.clear();
799 if (ambientInterface ==
nullptr)
801 for (ConstSparseIterator it(ambientInterface->getDomain());
802 !it.isFinished(); ++it) {
805 ambientPhiCache_[it.getStartIndices()] =
806 static_cast<T>(ambientSign) * it.getValue();
811 auto oldContactTraction = std::move(previousContactTraction_);
812 auto oldContactReleaseScale = std::move(previousContactReleaseScale_);
813 previousContactTraction_.clear();
814 previousContactReleaseScale_.clear();
815 contactReleaseThreshold_ =
T(0);
818 buildAmbientPhiCache();
820 ConstSparseIterator maskIt(maskInterface->getDomain());
823 if (isInsideMask(maskIt, index)) {
824 const std::size_t
id = nodes.size();
826 nodes.push_back({index});
833 const std::size_t n = nodes.size();
834 contactFaceActive_.assign(2 *
D * n, uint8_t(0));
835 contactFaceVelocity_.assign(2 *
D * n, Vec3D<T>{
T(0),
T(0),
T(0)});
836 contactFaceTraction_.assign(2 *
D * n, Vec3D<T>{
T(0),
T(0),
T(0)});
837 contactFaceDistance_.assign(2 *
D * n,
gridDelta);
840 candidateContactFaces_ = 0;
841 activeContactFaces_ = 0;
842 tensileContactFaces_ = 0;
843 minContactNormalTraction_ = std::numeric_limits<T>::max();
844 maxContactNormalTraction_ = std::numeric_limits<T>::lowest();
845 for (std::size_t
id = 0;
id < n; ++id) {
846 auto &node = nodes[id];
847 node.contact = touchesContactBoundary(maskIt, node.index);
851 for (
unsigned dir = 0; dir <
D; ++dir) {
852 for (
int offset : {-1, 1}) {
853 const unsigned faceIdx = dir * 2u + (offset == 1 ? 1u : 0u);
854 IndexType neighbor = node.index;
855 neighbor[dir] += offset;
856 const bool neighborIsNode =
858 if (neighborIsNode ||
859 !isContactBoundary(node.index, dir, offset, maskIt))
862 const T faceDistance = maskFaceDistance(maskIt, node.index, neighbor);
863 ++candidateContactFaces_;
864 Vec3D<T> faceNormal{0., 0., 0.};
865 faceNormal[dir] =
static_cast<T>(offset);
866 Vec3D<T> oxidePt{0., 0., 0.};
867 for (
unsigned i = 0; i <
D; ++i)
871 const auto sigma = deformationField->getStressTensor(oxidePt);
872 if (!isFiniteTensor(sigma))
873 throwNonFinite(
"oxide stress at mask contact");
874 Vec3D<T> t{0., 0., 0.};
875 for (
unsigned i = 0; i <
D; ++i)
876 for (
unsigned j = 0; j <
D; ++j)
877 t[i] += sigma[3 * i + j] * faceNormal[j];
880 for (
unsigned i = 0; i <
D; ++i)
881 tn += t[i] * faceNormal[i];
883 if (!isFinite(t) || !std::isfinite(tn))
884 throwNonFinite(
"oxide traction at mask contact");
885 minContactNormalTraction_ = std::min(minContactNormalTraction_, tn);
886 maxContactNormalTraction_ = std::max(maxContactNormalTraction_, tn);
888 Vec3D<T> contactLoad = t;
889 if (parameters.unilateralContact && tn >=
T(0)) {
890 ++tensileContactFaces_;
891 contactLoad = {
T(0),
T(0),
T(0)};
894 if (parameters.contactMode > 0) {
895 const auto key = contactFaceKey(node.index, dir, offset);
896 const auto prevIt = oldContactTraction.find(key);
897 if (prevIt != oldContactTraction.end()) {
899 std::clamp(parameters.contactLoadRelaxation,
T(0.02),
T(1));
900 for (
unsigned c = 0; c <
D; ++c)
901 contactLoad[c] = prevIt->second[c] +
902 alpha * (contactLoad[c] - prevIt->second[c]);
907 for (
unsigned i = 0; i <
D; ++i)
908 loadNormal += contactLoad[i] * faceNormal[i];
910 T releaseThreshold = std::numeric_limits<T>::epsilon();
911 T releaseScale = std::max(std::abs(tn),
T(1));
912 if (parameters.contactMode > 0 && parameters.unilateralContact) {
913 const auto scaleIt = oldContactReleaseScale.find(
914 contactFaceKey(node.index, dir, offset));
915 if (scaleIt != oldContactReleaseScale.end())
916 releaseScale = std::max(releaseScale, scaleIt->second);
918 std::clamp(parameters.contactReleaseFraction,
T(0),
T(0.25)) *
920 contactReleaseThreshold_ =
921 std::max(contactReleaseThreshold_, releaseThreshold);
924 if (parameters.unilateralContact && loadNormal >= -releaseThreshold)
927 if (!isFinite(contactLoad))
928 throwNonFinite(
"relaxed oxide contact load");
930 if (parameters.contactMode > 0) {
931 const auto key = contactFaceKey(node.index, dir, offset);
932 previousContactTraction_[key] = contactLoad;
933 if (parameters.unilateralContact)
934 previousContactReleaseScale_[key] =
935 std::max(releaseScale, std::abs(loadNormal));
938 contactFaceActive_[faceIdx * n + id] = uint8_t(1);
939 ++activeContactFaces_;
940 contactFaceTraction_[faceIdx * n + id] = contactLoad;
941 contactFaceDistance_[faceIdx * n + id] = faceDistance;
942 if (usesKinematicContactBoundary()) {
943 auto oxVel = deformationField->getVectorVelocity(
944 oxidePt, parameters.material, faceNormal, 0);
945 if (isElasticContactMode()) {
949 oxVel = oxVel * parameters.stressTimeStep;
951 contactFaceVelocity_[faceIdx * n + id] = oxVel;
959 std::size_t contactFaceKey(
const IndexType &index,
unsigned direction,
961 std::size_t seed =
typename IndexType::hash{}(index);
962 seed ^= std::hash<unsigned>{}(direction) +
963 std::size_t(0x9e3779b97f4a7c15ULL) + (seed << 6) + (seed >> 2);
964 seed ^= std::hash<int>{}(offset) + std::size_t(0x9e3779b97f4a7c15ULL) +
965 (seed << 6) + (seed >> 2);
971 template <
class SolverT>
972 Vec3D<T> simpleNeighborVelocity(
const std::vector<Vec3D<SolverT>> &velocity,
973 std::size_t nodeId,
unsigned direction,
975 IndexType neighbor = nodes[nodeId].index;
976 neighbor[direction] += offset;
977 const auto toT = [](
const Vec3D<SolverT> &
v) -> Vec3D<T> {
978 return {
static_cast<T>(
v[0]),
static_cast<T>(v[1]),
static_cast<T>(
v[2])};
981 return toT(velocity[nodeId]);
983 return (foundId !=
noNode) ? toT(velocity[foundId]) : toT(velocity[nodeId]);
987 template <
class SolverT>
988 Vec3D<T> stressBoundaryGhost(
const std::vector<Vec3D<SolverT>> &velocity,
989 std::size_t nodeId,
unsigned normalDir,
990 int offset,
T distance,
991 const Vec3D<T> &traction)
const {
992 const T lambda = lameLambda();
993 const T mu = lameMu();
995 std::max(lambda +
T(2) * mu, std::numeric_limits<T>::epsilon());
996 const T signedOffset =
static_cast<T>(offset);
997 Vec3D<T> ghost{
static_cast<T>(velocity[nodeId][0]),
998 static_cast<T>(velocity[nodeId][1]),
999 static_cast<T>(velocity[nodeId][2])};
1000 T tangentialDivergence =
T(0);
1001 std::array<T, D> normalTangentialDerivative{};
1002 for (
unsigned tanDir = 0; tanDir <
D; ++tanDir) {
1003 if (tanDir == normalDir)
1005 const Vec3D<T> vPlus =
1006 simpleNeighborVelocity(velocity, nodeId, tanDir, +1);
1007 const Vec3D<T> vMinus =
1008 simpleNeighborVelocity(velocity, nodeId, tanDir, -1);
1009 tangentialDivergence +=
1010 (vPlus[tanDir] - vMinus[tanDir]) / (
T(2) *
gridDelta);
1011 normalTangentialDerivative[tanDir] =
1012 (vPlus[normalDir] - vMinus[normalDir]) / (
T(2) *
gridDelta);
1015 const T normalTraction = traction[normalDir] * signedOffset;
1016 const T normalDerivative =
1017 (normalTraction - lambda * tangentialDivergence) / denom;
1018 ghost[normalDir] += signedOffset * distance * normalDerivative;
1020 for (
unsigned tanDir = 0; tanDir <
D; ++tanDir) {
1021 if (tanDir == normalDir)
1023 const T normalDerivativeTangential =
1024 signedOffset * traction[tanDir] /
1025 std::max(mu, std::numeric_limits<T>::epsilon()) -
1026 normalTangentialDerivative[tanDir];
1027 ghost[tanDir] += signedOffset * distance * normalDerivativeTangential;
1032 template <
class SolverT>
1033 Vec3D<T> tractionFreeGhost(
const std::vector<Vec3D<SolverT>> &velocity,
1034 std::size_t nodeId,
unsigned normalDir,
1036 return stressBoundaryGhost(velocity, nodeId, normalDir, offset,
gridDelta,
1037 Vec3D<T>{
T(0),
T(0),
T(0)});
1040 template <
class SolverT>
1041 Vec3D<T> neighborVelocity(
const std::vector<Vec3D<SolverT>> &velocity,
1042 std::size_t nodeId,
unsigned direction,
1044 IndexType neighbor = nodes[nodeId].index;
1045 neighbor[direction] += offset;
1050 return {
static_cast<T>(velocity[foundId][0]),
1051 static_cast<T>(velocity[foundId][1]),
1052 static_cast<T>(velocity[foundId][2])};
1055 const unsigned faceIdx = direction * 2u + (offset == 1 ? 1u : 0u);
1056 const std::size_t nn = nodes.size();
1057 if (contactFaceActive_[faceIdx * nn + nodeId]) {
1058 if (usesKinematicContactBoundary())
1059 return contactFaceVelocity_[faceIdx * nn + nodeId] *
T(2) -
1060 Vec3D<T>{
static_cast<T>(velocity[nodeId][0]),
1061 static_cast<T>(velocity[nodeId][1]),
1062 static_cast<T>(velocity[nodeId][2])};
1064 return stressBoundaryGhost(velocity, nodeId, direction, offset,
1065 contactFaceDistance_[faceIdx * nn + nodeId],
1066 contactFaceTraction_[faceIdx * nn + nodeId]);
1069 return tractionFreeGhost(velocity, nodeId, direction, offset);
1072 template <
class SolverT>
1073 Vec3D<T> divergenceGradient(
const std::vector<Vec3D<SolverT>> &velocity,
1074 const IndexType &index)
const {
1075 Vec3D<T> gradient{
T(0),
T(0),
T(0)};
1076 for (
unsigned component = 0; component <
D; ++component) {
1077 IndexType plus = index;
1078 IndexType minus = index;
1079 plus[component] += 1;
1080 minus[component] -= 1;
1081 const T plusDiv = divergence(velocity, plus);
1082 const T minusDiv = divergence(velocity, minus);
1083 gradient[component] = (plusDiv - minusDiv) / (
T(2) *
gridDelta);
1088 template <
class SolverT>
1089 T divergence(
const std::vector<Vec3D<SolverT>> &velocity,
1090 const IndexType &index)
const {
1098 for (
unsigned direction = 0; direction <
D; ++direction) {
1099 const Vec3D<T> plus = neighborVelocity(velocity, nodeId, direction, 1);
1100 const Vec3D<T> minus = neighborVelocity(velocity, nodeId, direction, -1);
1101 result += (plus[direction] - minus[direction]) / (
T(2) *
gridDelta);
1109 template <
class SolverT>
1110 Vec3D<T> computeElasticStencilAt(std::size_t nodeId,
1111 const std::vector<Vec3D<SolverT>> &v,
1112 T gradDivWeight)
const {
1113 Vec3D<T> laplaceAverage{
T(0),
T(0),
T(0)};
1114 for (
unsigned direction = 0; direction <
D; ++direction)
1115 for (
int offset : {-1, 1})
1117 laplaceAverage + neighborVelocity(v, nodeId, direction, offset);
1119 const T count =
static_cast<T>(2 *
D);
1120 const Vec3D<T> lapAvg = laplaceAverage / count;
1121 const Vec3D<T> gradDivCorr =
1122 divergenceGradient(v, nodes[nodeId].index) *
1125 return lapAvg + gradDivCorr;
1129 template <
class SolverT>
1130 void elasticMatvec(
const std::vector<Vec3D<SolverT>> &v,
1131 const std::vector<Vec3D<T>> &b,
T gradDivWeight,
1132 std::vector<Vec3D<SolverT>> &Av)
const {
1133#pragma omp parallel for schedule(static)
1134 for (std::size_t i = 0; i < nodes.size(); ++i) {
1135 if (nodes[i].fixed) {
1136 for (
unsigned c = 0; c <
D; ++c)
1140 const Vec3D<T> Fv = computeElasticStencilAt(i, v, gradDivWeight);
1141 for (
unsigned c = 0; c <
D; ++c)
1143 static_cast<SolverT
>(
static_cast<T>(v[i][c]) - Fv[c] + b[i][c]);
1147 static constexpr std::size_t mgNoNode =
1148 std::numeric_limits<std::size_t>::max();
1150 static Vec3D<T> zeroVec() {
return Vec3D<T>{
T(0),
T(0),
T(0)}; }
1152 static IndexType coarsenIndex(
const IndexType &index) {
1154 for (
unsigned d = 0; d <
D; ++d) {
1155 const auto value = index[d];
1156 coarse[d] = (value >= 0) ? value / 2 : -((-value + 1) / 2);
1161 std::vector<MultigridLevel> buildMultigridHierarchy()
const {
1162 std::vector<MultigridLevel> levels;
1163 levels.emplace_back();
1164 auto &fine = levels.back();
1165 fine.indices.reserve(nodes.size());
1166 for (
const auto &node : nodes)
1167 fine.indices.push_back(node.index);
1169 constexpr unsigned maxLevels = 12;
1170 constexpr std::size_t minCoarsenNodes = 48;
1171 while (levels.size() < maxLevels &&
1172 levels.back().indices.size() > minCoarsenNodes) {
1173 const auto &previous = levels.back();
1174 MultigridLevel coarse;
1175 coarse.indices.reserve(previous.indices.size() / 2 + 1);
1176 coarse.children.reserve(previous.indices.size() / 2 + 1);
1177 coarse.fineToCoarse.assign(previous.indices.size(), mgNoNode);
1179 std::unordered_map<IndexType, std::size_t, typename IndexType::hash>
1181 coarseLookup.reserve(previous.indices.size());
1182 for (std::size_t fineId = 0; fineId < previous.indices.size(); ++fineId) {
1183 const IndexType coarseIndex = coarsenIndex(previous.indices[fineId]);
1184 auto found = coarseLookup.find(coarseIndex);
1185 if (found == coarseLookup.end()) {
1186 const std::size_t coarseId = coarse.indices.size();
1187 coarseLookup.emplace(coarseIndex, coarseId);
1188 coarse.indices.push_back(coarseIndex);
1189 coarse.children.emplace_back();
1190 found = coarseLookup.find(coarseIndex);
1193 const std::size_t coarseId = found->second;
1194 coarse.children[coarseId].push_back(fineId);
1195 coarse.fineToCoarse[fineId] = coarseId;
1198 if (coarse.indices.empty() ||
1199 coarse.indices.size() >= previous.indices.size())
1207 IndexType lo = coarse.indices.front(), hi = coarse.indices.front();
1208 for (
const auto &idx : coarse.indices)
1209 for (
unsigned d = 0; d <
D; ++d) {
1210 lo[d] = std::min(lo[d], idx[d]);
1211 hi[d] = std::max(hi[d], idx[d]);
1213 bool degenerate =
false;
1214 for (
unsigned d = 0; d <
D; ++d)
1221 levels.push_back(std::move(coarse));
1226 static T vectorDot(
const std::vector<Vec3D<T>> &a,
1227 const std::vector<Vec3D<T>> &b) {
1229 for (std::size_t i = 0; i < a.size(); ++i)
1230 for (
unsigned c = 0; c <
D; ++c)
1231 sum += a[i][c] * b[i][c];
1235 static T vectorNorm(
const std::vector<Vec3D<T>> &v) {
1236 return std::sqrt(std::max(vectorDot(v, v),
T(0)));
1239 static void vectorScale(std::vector<Vec3D<T>> &v,
T scale) {
1240 for (
auto &entry : v)
1241 for (
unsigned c = 0; c <
D; ++c)
1245 static void vectorAxpy(std::vector<Vec3D<T>> &y,
T alpha,
1246 const std::vector<Vec3D<T>> &x) {
1247 for (std::size_t i = 0; i < y.size(); ++i)
1248 for (
unsigned c = 0; c <
D; ++c)
1249 y[i][c] += alpha * x[i][c];
1252 static void vectorSubtractInPlace(std::vector<Vec3D<T>> &y,
T alpha,
1253 const std::vector<Vec3D<T>> &x) {
1254 for (std::size_t i = 0; i < y.size(); ++i)
1255 for (
unsigned c = 0; c <
D; ++c)
1256 y[i][c] -= alpha * x[i][c];
1259 static T flatValue(
const std::vector<Vec3D<T>> &v, std::size_t row) {
1260 return v[row /
D][row %
D];
1263 static void flatSet(std::vector<Vec3D<T>> &v, std::size_t row,
T value) {
1264 v[row /
D][row %
D] = value;
1267 void collectLocalCandidatesRecursive(
unsigned dim,
const IndexType ¢er,
1268 IndexType &candidate,
1269 std::vector<std::size_t> &out)
const {
1273 const std::size_t nodeId =
lookupNode(candidate);
1275 out.push_back(nodeId);
1279 for (
int offset = -2; offset <= 2; ++offset) {
1280 candidate[dim] = center[dim] + offset;
1281 collectLocalCandidatesRecursive(dim + 1, center, candidate, out);
1285 void collectLocalCandidates(std::size_t rowNode,
1286 std::vector<std::size_t> &out)
const {
1288 IndexType candidate = nodes[rowNode].index;
1289 collectLocalCandidatesRecursive(0, nodes[rowNode].index, candidate, out);
1290 out.push_back(rowNode);
1291 std::sort(out.begin(), out.end());
1292 out.erase(std::unique(out.begin(), out.end()), out.end());
1296 compressSparseRows(std::vector<std::unordered_map<std::size_t, T>> &rows,
1297 std::size_t nodeCount)
const {
1298 SparseMatrix matrix;
1299 matrix.nodeCount = nodeCount;
1300 matrix.rowPtr.assign(rows.size() + 1, 0);
1301 matrix.invDiagonal.assign(rows.size(),
T(1));
1303 for (std::size_t row = 0; row < rows.size(); ++row) {
1304 std::vector<std::pair<std::size_t, T>> entries(rows[row].begin(),
1306 std::sort(entries.begin(), entries.end(),
1307 [](
const auto &a,
const auto &b) { return a.first < b.first; });
1309 matrix.rowPtr[row] = matrix.values.size();
1311 for (
const auto &[col, value] : entries) {
1312 if (std::abs(value) <= std::numeric_limits<T>::epsilon() *
T(100))
1314 if (!std::isfinite(value))
1315 throwNonFinite(
"traction sparse matrix assembly");
1316 matrix.colIndex.push_back(col);
1317 matrix.values.push_back(value);
1322 if (std::abs(diagonal) > std::numeric_limits<T>::epsilon())
1323 matrix.invDiagonal[row] =
T(1) / diagonal;
1325 matrix.invDiagonal[row] =
T(1);
1327 matrix.rowPtr[rows.size()] = matrix.values.size();
1331 SparseMatrix buildFineElasticMatrix(
const std::vector<Vec3D<T>> &b,
1332 T gradDivWeight)
const {
1333 const std::size_t n = nodes.size();
1334 std::vector<std::unordered_map<std::size_t, T>> rows(n *
D);
1335 std::vector<Vec3D<T>> basis(n, zeroVec());
1336 std::vector<std::size_t> candidates;
1338 for (std::size_t rowNode = 0; rowNode < n; ++rowNode) {
1339 if (nodes[rowNode].fixed) {
1340 for (
unsigned rowComponent = 0; rowComponent <
D; ++rowComponent) {
1341 const std::size_t row = rowNode *
D + rowComponent;
1342 rows[row][row] =
T(1);
1356 collectLocalCandidates(rowNode, candidates);
1357 for (std::size_t colNode : candidates) {
1358 for (
unsigned colComponent = 0; colComponent <
D; ++colComponent) {
1359 basis[colNode][colComponent] =
T(1);
1360 const Vec3D<T> Fbasis =
1361 computeElasticStencilAt(rowNode, basis, gradDivWeight);
1362 basis[colNode][colComponent] =
T(0);
1364 for (
unsigned rowComponent = 0; rowComponent <
D; ++rowComponent) {
1365 const std::size_t row = rowNode *
D + rowComponent;
1366 const std::size_t col = colNode *
D + colComponent;
1368 (rowNode == colNode && rowComponent == colComponent) ?
T(1)
1371 identity - (Fbasis[rowComponent] - b[rowNode][rowComponent]);
1372 rows[row][col] += value;
1378 return compressSparseRows(rows, n);
1381 SparseMatrix buildGalerkinMatrix(
const SparseMatrix &fine,
1382 const MultigridLevel &coarseLevel)
const {
1383 const std::size_t coarseNodes = coarseLevel.indices.size();
1384 std::vector<std::unordered_map<std::size_t, T>> rows(coarseNodes *
D);
1386 for (std::size_t fineRow = 0; fineRow < fine.nodeCount *
D; ++fineRow) {
1387 const std::size_t fineRowNode = fineRow /
D;
1388 if (fineRowNode >= coarseLevel.fineToCoarse.size())
1390 const std::size_t coarseRowNode = coarseLevel.fineToCoarse[fineRowNode];
1391 if (coarseRowNode == mgNoNode)
1394 const T restrictionWeight =
1395 T(1) /
static_cast<T>(std::max<std::size_t>(
1396 coarseLevel.children[coarseRowNode].size(), 1));
1397 const std::size_t coarseRow = coarseRowNode *
D + fineRow %
D;
1399 for (std::size_t nz = fine.rowPtr[fineRow]; nz < fine.rowPtr[fineRow + 1];
1401 const std::size_t fineCol = fine.colIndex[nz];
1402 const std::size_t fineColNode = fineCol /
D;
1403 if (fineColNode >= coarseLevel.fineToCoarse.size())
1405 const std::size_t coarseColNode = coarseLevel.fineToCoarse[fineColNode];
1406 if (coarseColNode == mgNoNode)
1408 const std::size_t coarseCol = coarseColNode *
D + fineCol %
D;
1409 rows[coarseRow][coarseCol] += restrictionWeight * fine.values[nz];
1413 return compressSparseRows(rows, coarseNodes);
1416 void sparseMatvec(
const SparseMatrix &matrix,
const std::vector<Vec3D<T>> &x,
1417 std::vector<Vec3D<T>> &Ax)
const {
1418 Ax.assign(matrix.nodeCount, zeroVec());
1419#pragma omp parallel for schedule(static)
1420 for (std::size_t row = 0; row < matrix.nodeCount *
D; ++row) {
1422 for (std::size_t nz = matrix.rowPtr[row]; nz < matrix.rowPtr[row + 1];
1424 sum += matrix.values[nz] * flatValue(x, matrix.colIndex[nz]);
1425 flatSet(Ax, row, sum);
1429 void multigridProlong(
const std::vector<MultigridLevel> &levels,
1430 std::size_t coarseLevelId,
1431 const std::vector<Vec3D<T>> &coarse,
1432 std::vector<Vec3D<T>> &fine)
const {
1433 const auto &fineLevel = levels[coarseLevelId - 1];
1434 const auto &coarseLevel = levels[coarseLevelId];
1435 fine.assign(fineLevel.indices.size(), zeroVec());
1436 for (std::size_t coarseId = 0; coarseId < coarseLevel.children.size();
1438 for (std::size_t fineId : coarseLevel.children[coarseId])
1439 for (
unsigned c = 0; c <
D; ++c)
1440 fine[fineId][c] = coarse[coarseId][c];
1443 void multigridSmooth(
const SparseMatrix &matrix,
1444 const std::vector<Vec3D<T>> &rhs,
1445 std::vector<Vec3D<T>> &x,
unsigned sweeps,
1447 const std::size_t rows = matrix.nodeCount *
D;
1448 auto relaxRow = [&](std::size_t row) {
1449 T offDiagonal =
T(0);
1450 for (std::size_t nz = matrix.rowPtr[row]; nz < matrix.rowPtr[row + 1];
1452 const std::size_t col = matrix.colIndex[nz];
1455 offDiagonal += matrix.values[nz] * flatValue(x, col);
1458 (flatValue(rhs, row) - offDiagonal) * matrix.invDiagonal[row];
1460 flatValue(x, row) + omega * (updated - flatValue(x, row)));
1463 for (
unsigned sweep = 0; sweep < sweeps; ++sweep) {
1464 for (std::size_t row = 0; row < rows; ++row)
1466 for (std::size_t row = rows; row-- > 0;)
1471 void multigridResidual(
const SparseMatrix &matrix,
1472 const std::vector<Vec3D<T>> &rhs,
1473 const std::vector<Vec3D<T>> &x,
1474 std::vector<Vec3D<T>> &residualOut)
const {
1475 sparseMatvec(matrix, x, residualOut);
1476 for (std::size_t i = 0; i < rhs.size(); ++i)
1477 for (
unsigned c = 0; c <
D; ++c)
1478 residualOut[i][c] = rhs[i][c] - residualOut[i][c];
1481 void multigridRestrict(
const std::vector<Vec3D<T>> &fineResidual,
1482 const MultigridLevel &coarseLevel,
1483 std::vector<Vec3D<T>> &coarseRhs)
const {
1484 coarseRhs.assign(coarseLevel.indices.size(), zeroVec());
1485 for (std::size_t coarseId = 0; coarseId < coarseLevel.children.size();
1487 const auto &children = coarseLevel.children[coarseId];
1488 if (children.empty())
1490 for (std::size_t fineId : children)
1491 for (
unsigned c = 0; c <
D; ++c)
1492 coarseRhs[coarseId][c] += fineResidual[fineId][c];
1493 const T scale =
T(1) /
static_cast<T>(children.size());
1494 for (
unsigned c = 0; c <
D; ++c)
1495 coarseRhs[coarseId][c] *= scale;
1499 void multigridProlongAdd(
const std::vector<Vec3D<T>> &coarseCorrection,
1500 const MultigridLevel &coarseLevel,
1501 std::vector<Vec3D<T>> &fineCorrection)
const {
1502 for (std::size_t coarseId = 0; coarseId < coarseLevel.children.size();
1504 for (std::size_t fineId : coarseLevel.children[coarseId])
1505 for (
unsigned c = 0; c <
D; ++c)
1506 fineCorrection[fineId][c] += coarseCorrection[coarseId][c];
1510 void multigridVCycle(
const std::vector<MultigridLevel> &levels,
1511 std::size_t levelId,
const std::vector<Vec3D<T>> &rhs,
1512 std::vector<Vec3D<T>> &x,
T smootherOmega)
const {
1513 const auto &level = levels[levelId];
1514 if (levelId + 1 >= levels.size() || level.indices.size() <= 32) {
1515 multigridSmooth(level.matrix, rhs, x, 40, smootherOmega);
1519 multigridSmooth(level.matrix, rhs, x, 2, smootherOmega);
1521 std::vector<Vec3D<T>> fineResidual;
1522 multigridResidual(level.matrix, rhs, x, fineResidual);
1524 std::vector<Vec3D<T>> coarseRhs;
1525 const auto &coarseLevel = levels[levelId + 1];
1526 multigridRestrict(fineResidual, coarseLevel, coarseRhs);
1528 std::vector<Vec3D<T>> coarseCorrection(coarseRhs.size(), zeroVec());
1529 multigridVCycle(levels, levelId + 1, coarseRhs, coarseCorrection,
1531 multigridProlongAdd(coarseCorrection, coarseLevel, x);
1533 multigridSmooth(level.matrix, rhs, x, 2, smootherOmega);
1536 std::vector<Vec3D<T>>
1537 multigridPrecondition(
const std::vector<MultigridLevel> &levels,
1538 const std::vector<Vec3D<T>> &rhs,
1539 T smootherOmega)
const {
1540 std::vector<Vec3D<T>> correction(rhs.size(), zeroVec());
1543 multigridVCycle(levels, 0, rhs, correction, smootherOmega);
1547 void exactElasticResidual(
const SparseMatrix &matrix,
1548 const std::vector<Vec3D<T>> &x,
1549 const std::vector<Vec3D<T>> &b,
1550 std::vector<Vec3D<T>> &r)
const {
1551 std::vector<Vec3D<T>> Ax(x.size(), zeroVec());
1552 sparseMatvec(matrix, x, Ax);
1553 r.assign(x.size(), zeroVec());
1554 for (std::size_t i = 0; i < x.size(); ++i)
1555 for (
unsigned c = 0; c <
D; ++c)
1556 r[i][c] = b[i][c] - Ax[i][c];
1559 static std::vector<T>
1560 solveUpperTriangular(
const std::vector<std::vector<T>> &h,
1561 const std::vector<T> &g,
unsigned usedColumns) {
1562 std::vector<T> y(usedColumns,
T(0));
1563 for (
int row =
static_cast<int>(usedColumns) - 1; row >= 0; --row) {
1564 T sum = g[
static_cast<std::size_t
>(row)];
1565 for (
unsigned col =
static_cast<unsigned>(row) + 1; col < usedColumns;
1567 sum -= h[
static_cast<std::size_t
>(row)][col] * y[col];
1569 h[
static_cast<std::size_t
>(row)][
static_cast<std::size_t
>(row)];
1570 if (std::abs(diag) > std::numeric_limits<T>::epsilon())
1571 y[
static_cast<std::size_t
>(row)] = sum / diag;
1576 void solveElasticVelocity() {
1577 if (parameters.contactMode > 0) {
1578 solveElasticVelocityMultigridGMRES();
1581 solveElasticVelocityBiCGSTAB();
1584 void solveElasticVelocityMultigridGMRES() {
1590 const T lambda = lameLambda();
1591 const T mu = lameMu();
1592 const T gradDivWeight =
1594 std::max(lambda +
T(2) * mu, std::numeric_limits<T>::epsilon());
1595 const T smootherOmega =
1596 std::clamp(parameters.multigridSmootherOmega,
T(0.2),
T(1.4));
1598 const std::size_t n = nodes.size();
1599 const std::vector<Vec3D<T>> zeros(n, zeroVec());
1600 std::vector<Vec3D<T>> b(n, zeroVec());
1601#pragma omp parallel for schedule(static)
1602 for (std::size_t i = 0; i < n; ++i) {
1606 b[i] = computeElasticStencilAt(i, zeros, gradDivWeight);
1609 std::vector<Vec3D<T>> x(n, zeroVec());
1610 for (std::size_t i = 0; i < n; ++i)
1611 x[i] = nodes[i].fixed ? zeroVec() : nodes[i].velocity;
1620 const bool hierarchyDirty =
1621 cachedNodeCount_ != n || cachedContactFaceActive_ != contactFaceActive_;
1623 if (hierarchyDirty) {
1624 cachedMultigridLevels_ = buildMultigridHierarchy();
1625 if (cachedMultigridLevels_.empty())
1627 cachedMultigridLevels_[0].matrix =
1628 buildFineElasticMatrix(b, gradDivWeight);
1629 for (std::size_t level = 1; level < cachedMultigridLevels_.size();
1631 cachedMultigridLevels_[level].matrix =
1632 buildGalerkinMatrix(cachedMultigridLevels_[level - 1].matrix,
1633 cachedMultigridLevels_[level]);
1634 cachedNodeCount_ = n;
1635 cachedContactFaceActive_ = contactFaceActive_;
1638 if (cachedMultigridLevels_.empty())
1640 const auto &multigridLevels = cachedMultigridLevels_;
1642 std::vector<Vec3D<T>> r;
1643 exactElasticResidual(multigridLevels[0].matrix, x, b, r);
1645 const T rhsNorm = vectorNorm(b);
1646 const T absTolerance =
1647 std::max(parameters.tolerance * rhsNorm,
1648 std::numeric_limits<T>::epsilon() *
T(100) *
1649 std::sqrt(
static_cast<T>(
1650 std::max<std::size_t>(std::size_t(1), n *
D))));
1651 const T residualNormDenom = std::max(rhsNorm, absTolerance);
1652 auto updateGmresResidual = [&](
T absResidual) {
1653 residual = (absResidual <= absTolerance)
1655 : absResidual / residualNormDenom;
1658 T absResidual = vectorNorm(r);
1659 updateGmresResidual(absResidual);
1660 if (absResidual <= absTolerance || residual < parameters.tolerance) {
1661 for (std::size_t i = 0; i < n; ++i)
1662 nodes[i].velocity = x[i];
1666 constexpr unsigned restart = 32;
1667 while (iterations < parameters.maxIterations &&
1668 residual > parameters.tolerance) {
1669 const T beta = vectorNorm(r);
1670 if (!std::isfinite(beta))
1671 throwNonFinite(
"traction multigrid GMRES residual");
1672 if (beta <= absTolerance) {
1677 const unsigned innerLimit =
1678 std::min<unsigned>(restart, parameters.maxIterations - iterations);
1679 std::vector<std::vector<Vec3D<T>>>
v(innerLimit + 1,
1680 std::vector<Vec3D<T>>(n, zeroVec()));
1681 std::vector<std::vector<Vec3D<T>>> z(innerLimit,
1682 std::vector<Vec3D<T>>(n, zeroVec()));
1684 vectorScale(v[0],
T(1) / beta);
1686 std::vector<std::vector<T>> h(innerLimit + 1,
1687 std::vector<T>(innerLimit,
T(0)));
1688 std::vector<T> cs(innerLimit,
T(0));
1689 std::vector<T> sn(innerLimit,
T(0));
1690 std::vector<T> g(innerLimit + 1,
T(0));
1693 unsigned usedColumns = 0;
1694 for (
unsigned j = 0; j < innerLimit; ++j) {
1695 z[j] = multigridPrecondition(multigridLevels, v[j], smootherOmega);
1697 std::vector<Vec3D<T>> w(n, zeroVec());
1698 sparseMatvec(multigridLevels[0].matrix, z[j], w);
1700 for (
unsigned i = 0; i <= j; ++i) {
1701 h[i][j] = vectorDot(w, v[i]);
1702 vectorSubtractInPlace(w, h[i][j], v[i]);
1705 h[j + 1][j] = vectorNorm(w);
1706 if (h[j + 1][j] > std::numeric_limits<T>::epsilon()) {
1708 vectorScale(v[j + 1],
T(1) / h[j + 1][j]);
1711 for (
unsigned i = 0; i < j; ++i) {
1712 const T h0 = h[i][j];
1713 const T h1 = h[i + 1][j];
1714 h[i][j] = cs[i] * h0 + sn[i] * h1;
1715 h[i + 1][j] = -sn[i] * h0 + cs[i] * h1;
1718 const T h0 = h[j][j];
1719 const T h1 = h[j + 1][j];
1720 const T denom = std::hypot(h0, h1);
1721 if (denom <= std::numeric_limits<T>::epsilon()) {
1728 h[j][j] = cs[j] * h0 + sn[j] * h1;
1733 g[j + 1] = -sn[j] * g0;
1736 usedColumns = j + 1;
1737 const T projectedAbsResidual = std::abs(g[j + 1]);
1738 updateGmresResidual(projectedAbsResidual);
1739 if (!std::isfinite(residual))
1740 throwNonFinite(
"traction multigrid GMRES residual");
1741 if (projectedAbsResidual <= absTolerance ||
1742 residual < parameters.tolerance)
1746 if (usedColumns == 0)
1749 const auto y = solveUpperTriangular(h, g, usedColumns);
1750 for (
unsigned col = 0; col < usedColumns; ++col)
1751 vectorAxpy(x, y[col], z[col]);
1753 exactElasticResidual(multigridLevels[0].matrix, x, b, r);
1754 absResidual = vectorNorm(r);
1755 updateGmresResidual(absResidual);
1756 if (!std::isfinite(residual))
1757 throwNonFinite(
"traction multigrid GMRES residual");
1760 for (std::size_t i = 0; i < n; ++i)
1761 nodes[i].velocity = x[i];
1763 if (residual > parameters.tolerance)
1764 Logger::getInstance()
1765 .addWarning(
"solveElasticVelocity: traction multigrid GMRES did not "
1767 std::to_string(iterations) +
"/" +
1768 std::to_string(parameters.maxIterations) +
1769 " iterations (residual=" + std::to_string(residual) +
1770 ", tolerance=" + std::to_string(parameters.tolerance) +
1775 void solveElasticVelocityRelaxation() {
1781 const T lambda = lameLambda();
1782 const T mu = lameMu();
1783 const T gradDivWeight =
1785 std::max(lambda +
T(2) * mu, std::numeric_limits<T>::epsilon());
1786 const T relaxation = std::clamp(parameters.relaxation,
T(0.01),
T(1));
1788 std::vector<Vec3D<T>> current(nodes.size());
1789 std::vector<Vec3D<T>> next(nodes.size());
1790 for (std::size_t i = 0; i < nodes.size(); ++i)
1792 nodes[i].fixed ? Vec3D<T>{
T(0),
T(0),
T(0)} : nodes[i].velocity;
1794 for (; iterations < parameters.maxIterations; ++iterations) {
1796 T maxMagnitude = std::numeric_limits<T>::epsilon();
1799#pragma omp parallel for schedule(static) \
1800 reduction(max : maxDelta, maxMagnitude) reduction(min : finiteFlag)
1801 for (std::size_t i = 0; i < nodes.size(); ++i) {
1802 Vec3D<T> candidate =
1803 nodes[i].fixed ? Vec3D<T>{
T(0),
T(0),
T(0)}
1804 : computeElasticStencilAt(i, current, gradDivWeight);
1806 for (
unsigned c = 0; c <
D; ++c) {
1807 if (!std::isfinite(candidate[c]) || !std::isfinite(current[i][c])) {
1809 next[i][c] = current[i][c];
1813 current[i][c] +
relaxation * (candidate[c] - current[i][c]);
1814 maxDelta = std::max(maxDelta, std::abs(next[i][c] - current[i][c]));
1815 maxMagnitude = std::max(maxMagnitude, std::abs(next[i][c]));
1816 maxMagnitude = std::max(maxMagnitude, std::abs(current[i][c]));
1821 residual = std::numeric_limits<T>::infinity();
1822 throwNonFinite(
"traction mask solve");
1825 residual = maxDelta / maxMagnitude;
1827 if (residual < parameters.tolerance) {
1833 for (std::size_t i = 0; i < nodes.size(); ++i)
1834 nodes[i].velocity = current[i];
1836 if (residual > parameters.tolerance)
1837 Logger::getInstance()
1838 .addWarning(
"solveElasticVelocity: traction relaxation did not "
1840 std::to_string(iterations) +
"/" +
1841 std::to_string(parameters.maxIterations) +
1842 " iterations (residual=" + std::to_string(residual) +
1843 ", tolerance=" + std::to_string(parameters.tolerance) +
1848 void solveElasticVelocityBiCGSTAB() {
1854 using SolverT = float;
1856 const T lambda = lameLambda();
1857 const T mu = lameMu();
1858 const T gradDivWeight =
1860 std::max(lambda +
T(2) * mu, std::numeric_limits<T>::epsilon());
1862 const std::size_t n = nodes.size();
1863 const Vec3D<SolverT> zero3{SolverT(0), SolverT(0), SolverT(0)};
1867 std::vector<Vec3D<T>> b(n);
1869 const std::vector<Vec3D<SolverT>> zeros(n, zero3);
1870#pragma omp parallel for schedule(static)
1871 for (std::size_t i = 0; i < n; ++i) {
1873 b[i] = Vec3D<T>{
T(0),
T(0),
T(0)};
1875 b[i] = computeElasticStencilAt(i, zeros, gradDivWeight);
1880 std::vector<Vec3D<SolverT>> x(n, zero3);
1883 std::vector<Vec3D<SolverT>> r(n), r_hat(n);
1884 for (std::size_t i = 0; i < n; ++i)
1885 for (
unsigned c = 0; c <
D; ++c) {
1886 r[i][c] =
static_cast<SolverT
>(b[i][c]);
1887 r_hat[i][c] = r[i][c];
1894 std::vector<Vec3D<SolverT>> pv(n, zero3), sv(n, zero3), y(n), z(n), s(n),
1896 T rho =
T(1), alpha =
T(1), omega =
T(1);
1898 auto vecDot = [&](
const std::vector<Vec3D<SolverT>> &a,
1899 const std::vector<Vec3D<SolverT>> &bv) {
1901 for (std::size_t i = 0; i < n; ++i)
1902 for (
unsigned c = 0; c <
D; ++c) {
1903 const T av =
static_cast<T>(a[i][c]);
1904 const T bvVal =
static_cast<T>(bv[i][c]);
1905 if (!std::isfinite(av) || !std::isfinite(bvVal))
1906 return std::numeric_limits<T>::quiet_NaN();
1912 auto vecMaxAbs = [&](
const std::vector<Vec3D<SolverT>> &vin) {
1914 for (std::size_t i = 0; i < n; ++i)
1915 for (
unsigned c = 0; c <
D; ++c) {
1916 const T value =
static_cast<T>(vin[i][c]);
1917 if (!std::isfinite(value))
1918 return std::numeric_limits<T>::infinity();
1919 m = std::max(m, std::abs(value));
1924 const T b_norm = [&] {
1926 for (std::size_t i = 0; i < n; ++i)
1927 for (
unsigned c = 0; c <
D; ++c)
1928 m = std::max(m, std::abs(b[i][c]));
1929 return (m <
T(1e-100)) ?
T(1) : m;
1932 for (; iterations < parameters.maxIterations; ++iterations) {
1933 const T rho_new = vecDot(r_hat, r);
1934 if (!std::isfinite(rho_new) || std::abs(rho_new) <
T(1e-100))
1936 if (!std::isfinite(rho) || !std::isfinite(alpha) ||
1937 !std::isfinite(omega) || std::abs(omega) <
T(1e-100))
1940 const T beta = (rho_new / rho) * (alpha / omega);
1941 if (!std::isfinite(beta))
1945 for (std::size_t i = 0; i < n; ++i)
1946 for (
unsigned c = 0; c <
D; ++c)
1947 pv[i][c] =
static_cast<SolverT
>(r[i][c] +
1948 beta * (pv[i][c] - omega * sv[i][c]));
1952 elasticMatvec(y, b, gradDivWeight, sv);
1954 const T r_hat_v = vecDot(r_hat, sv);
1955 if (!std::isfinite(r_hat_v) || std::abs(r_hat_v) <
T(1e-100))
1958 alpha = rho_new / r_hat_v;
1959 if (!std::isfinite(alpha))
1962 for (std::size_t i = 0; i < n; ++i)
1963 for (
unsigned c = 0; c <
D; ++c)
1964 s[i][c] =
static_cast<SolverT
>(r[i][c] - alpha * sv[i][c]);
1966 residual = vecMaxAbs(s);
1967 if (!std::isfinite(residual))
1969 if (residual < parameters.tolerance * b_norm) {
1970 for (std::size_t i = 0; i < n; ++i)
1971 for (
unsigned c = 0; c <
D; ++c)
1972 x[i][c] =
static_cast<SolverT
>(x[i][c] + alpha * y[i][c]);
1979 elasticMatvec(z, b, gradDivWeight, t);
1981 const T t_s = vecDot(t, s);
1982 const T t_t = vecDot(t, t);
1983 if (!std::isfinite(t_s) || !std::isfinite(t_t) || t_t <=
T(1e-100))
1986 if (!std::isfinite(omega))
1989 for (std::size_t i = 0; i < n; ++i)
1990 for (
unsigned c = 0; c <
D; ++c) {
1992 static_cast<SolverT
>(x[i][c] + alpha * y[i][c] + omega * z[i][c]);
1993 r[i][c] =
static_cast<SolverT
>(s[i][c] - omega * t[i][c]);
1996 residual = vecMaxAbs(r);
1997 if (!std::isfinite(residual))
1999 if (residual < parameters.tolerance * b_norm) {
2005 bool finiteSolution =
true;
2006 for (std::size_t i = 0; i < n; ++i)
2007 for (
unsigned c = 0; c <
D; ++c)
2008 if (!std::isfinite(
static_cast<T>(x[i][c])))
2009 finiteSolution =
false;
2011 if (finiteSolution) {
2012 for (std::size_t i = 0; i < n; ++i)
2013 for (
unsigned c = 0; c <
D; ++c)
2014 nodes[i].velocity[c] =
static_cast<T>(x[i][c]);
2016 residual = std::numeric_limits<T>::infinity();
2017 throwNonFinite(
"legacy kinematic mask solve");
2019 if (residual > parameters.tolerance * b_norm)
2020 Logger::getInstance()
2022 "solveElasticVelocity: BiCGSTAB did not converge after " +
2023 std::to_string(iterations) +
"/" +
2024 std::to_string(parameters.maxIterations) +
2025 " iterations (residual=" + std::to_string(residual / b_norm) +
2026 ", tolerance=" + std::to_string(parameters.tolerance) +
")")
2030 bool touchesContactBoundary(ConstSparseIterator &maskIt,
2031 const IndexType &index)
const {
2032 for (
unsigned direction = 0; direction <
D; ++direction) {
2033 for (
int offset : {-1, 1}) {
2034 IndexType neighbor = index;
2035 neighbor[direction] += offset;
2039 if (isContactBoundary(index, direction, offset, maskIt))
2047 if (isContactBoundary(index, direction, offset, maskIt))
2054 bool isContactBoundary(
const IndexType &index,
unsigned direction,
int offset,
2055 ConstSparseIterator &maskIt)
const {
2056 const T grad = maskGradientComponent(index, direction, maskIt);
2062 if (
static_cast<T>(maskSign) *
static_cast<T>(offset) * grad >=
T(0))
2065 if (ambientInterface ==
nullptr)
2066 return direction ==
D - 1;
2068 IndexType ghostIndex = index;
2069 ghostIndex[direction] += offset;
2070 return isInsideOxide(ghostIndex);
2073 bool isInsideOxide(
const IndexType &index)
const {
2074 if (ambientInterface ==
nullptr)
2076 const auto it = ambientPhiCache_.find(index);
2077 return it != ambientPhiCache_.end() && it->second >=
T(0);
2080 T maskFaceDistance(ConstSparseIterator &maskIt,
const IndexType &inside,
2081 const IndexType &outside)
const {
2084 return crossingDistance(
valueAt(maskIt, inside),
valueAt(maskIt, outside));
2087 void markFixedNodes() {
2089 if (nodes.empty() || parameters.anchorBoundarySide == 0)
2091 if (parameters.anchorBoundaryDirection < 0 ||
2092 parameters.anchorBoundaryDirection >=
D)
2095 const unsigned dir =
2096 static_cast<unsigned>(parameters.anchorBoundaryDirection);
2103 if (dir ==
static_cast<unsigned>(
D - 1))
2104 Logger::getInstance()
2105 .addWarning(
"OxidationMaskBending: anchorBoundaryDirection=" +
2106 std::to_string(parameters.anchorBoundaryDirection) +
2107 " points along the oxide-growth axis (direction D-1=" +
2108 std::to_string(
D - 1) +
2109 "). The anchor is normally "
2110 "placed at a lateral edge (direction 0) so bending "
2111 "degrees of freedom are preserved. Set "
2112 "anchorBoundaryDirection=0 for a LOCOS cross-section.")
2114 IndexType nodeMin = nodes.front().index;
2115 IndexType nodeMax = nodes.front().index;
2116 for (
const auto &node : nodes) {
2117 for (
unsigned d = 0; d <
D; ++d) {
2118 nodeMin[d] = std::min(nodeMin[d], node.index[d]);
2119 nodeMax[d] = std::max(nodeMax[d], node.index[d]);
2123 const auto layers =
static_cast<viennahrle::IndexType
>(
2124 std::max(1u, parameters.anchorBoundaryLayers));
2125 for (
auto &node : nodes) {
2126 const bool onLower = parameters.anchorBoundarySide < 0 &&
2127 node.index[dir] <= nodeMin[dir] + layers - 1;
2128 const bool onUpper = parameters.anchorBoundarySide > 0 &&
2129 node.index[dir] >= nodeMax[dir] - layers + 1;
2130 if (onLower || onUpper) {
2132 node.velocity = {
T(0),
T(0),
T(0)};
2138 T maskGradientComponent(
const IndexType &index,
unsigned direction,
2139 ConstSparseIterator &maskIt)
const {
2140 IndexType pos = index;
2141 IndexType neg = index;
2142 pos[direction] += 1;
2143 neg[direction] -= 1;
2152 T clampedPoissonRatio()
const {
2153 return std::clamp(parameters.poissonRatio,
T(-0.95),
T(0.49));
2156 static constexpr T gasConstant =
T(8.31446261815324);
2158 bool isElasticContactMode()
const {
return parameters.contactMode == 2; }
2160 bool usesKinematicContactBoundary()
const {
2161 return parameters.contactMode == 0 || isElasticContactMode();
2164 T effectiveMaskViscosity()
const {
2172 if (isElasticContactMode())
2173 return parameters.youngModulus;
2174 const T temperature = std::max(parameters.temperature,
T(1.));
2175 const T referenceTemperature =
2176 std::max(parameters.referenceTemperature,
T(1.));
2177 return parameters.referenceViscosity *
2178 std::exp(parameters.creepActivationEnergy / gasConstant *
2179 (
T(1) / temperature -
T(1) / referenceTemperature));
2186 const T nu = clampedPoissonRatio();
2187 return effectiveMaskViscosity() / (
T(2) * (
T(1) + nu));
2190 T lameLambda()
const {
2191 const T nu = clampedPoissonRatio();
2192 return effectiveMaskViscosity() * nu / ((
T(1) + nu) * (
T(1) -
T(2) * nu));
2195 Vec3D<T> getVelocity(
const IndexType &index)
const {
2196 const std::size_t nodeId =
lookupNode(index);
2198 return nodes[nodeId].velocity;
2202 return {0., 0., 0.};
2203 return nodes[nearby].velocity;
2206 bool isInsideMask(ConstSparseIterator &maskIt,
const IndexType &index)
const {
2207 return maskSign *
valueAt(maskIt, index) >= 0.;
2210 T crossingDistance(
T insidePhi,
T outsidePhi)
const {
2212 insidePhi, outsidePhi, parameters.minBoundaryDistance,
gridDelta);