ViennaLS
Loading...
Searching...
No Matches
lsOxidationDeformation.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <atomic>
8#include <cmath>
9#include <cstdint>
10#include <cstdio>
11#include <functional>
12#include <memory>
13#include <stdexcept>
14#include <string>
15#include <tuple>
16#include <unordered_map>
17
18#include <omp.h>
19#include <vcTimer.hpp>
20
21#include <Eigen/SparseCore>
22#include <Eigen/SparseLU>
23#include <amgcl/adapter/crs_tuple.hpp>
24#include <amgcl/amg.hpp>
25#include <amgcl/backend/builtin.hpp>
26#include <amgcl/coarsening/smoothed_aggregation.hpp>
27#include <amgcl/make_solver.hpp>
28#include <amgcl/preconditioner/schur_pressure_correction.hpp>
29#include <amgcl/relaxation/as_preconditioner.hpp>
30#include <amgcl/relaxation/ilu0.hpp>
31#include <amgcl/relaxation/spai0.hpp>
32#include <amgcl/solver/fgmres.hpp>
33#include <amgcl/solver/preonly.hpp>
34
35namespace viennals {
36
39 double viscosity = 1.;
40 double ambientPressure = 0.;
41 // Floor on the distance from a grid point to an interface crossing, in
42 // grid spacings. Interface ghost values extrapolate with 1/theta (theta
43 // this distance), so the floor bounds their amplification where a crossing
44 // lands next to a grid point.
46 // Maxwell oxide: shear modulus G (Pa), or the relaxation time tau (hr,
47 // overrides eta/G). Per substep dt the deviatoric stress follows
48 // S_end = decay S_start + (1 - decay) 2 eta D', decay = exp(-dt/tau)
49 // (exact for constant D'); S_start is the stored stress, carried with the
50 // oxide. The momentum solve treats (1 - decay) eta implicitly. G = tau =
51 // 0: purely viscous oxide.
52 double shearModulus = 0.;
54 double stressTimeStep = 1.;
55 // Stress-dependent oxide viscosity (Sutardja & Oldham, IEEE TED 36, 2415
56 // (1989)): eta(tau) = eta0 x / sinh(x), x = tau Vc / (2 k T), with tau the
57 // shear stress sqrt(J2) of the oxide's deviatoric stress and T the
58 // oxidation temperature. Below the critical shear stress 2kT/Vc (0.117 GPa
59 // for Vc = 300 A^3 at 1000 C) the viscosity stays near eta0; above it, it
60 // falls exponentially. This bounds the stress at sharp concave corners,
61 // where a linear viscous incompressible oxide has a pressure singularity
62 // that grows as the grid is refined. The Maxwell relaxation time scales
63 // with it (tau = eta / G). The momentum equation carries the full
64 // div(2 eta D') with the local viscosity. Vc in m^3; 0 = linear viscosity.
66 // Viscosity iteration of the stress-dependent oxide (one exact velocity-
67 // pressure solve per iteration, Anderson-accelerated): at most
68 // mechanicsIterations, until the relative velocity change between two
69 // iterations is below mechanicsTolerance.
70 unsigned mechanicsIterations = 200;
71 double mechanicsTolerance = 1e-4;
72 // Monolithic oxide + nitride mechanics. nitrideYoungModulus
73 // > 0 together with a mask interface makes the grid points inside the mask
74 // join the mechanics solve as a nitride body with stress memory
75 // (linear-elastic, or Maxwell with nitrideViscosity). The two materials
76 // share nodes and interface faces, so velocity and traction are continuous
77 // across the oxide/nitride interface by construction and no coupling
78 // iteration is needed. At a reflective domain boundary (the symmetry plane)
79 // the nitride, like the oxide, has no normal velocity and no shear. A mask
80 // interface requires it.
82 double nitridePoissonRatio = 0.27;
83 // Maxwell nitride (Senez et al., IEEE TED 43, 720 (1996)): the deviatoric
84 // stress relaxes over tau = eta_N / mu, the mean stress stays elastic.
85 // nitrideViscosity in Pa hr; 0 = linear-elastic nitride. With
86 // nitrideShearActivationVolume (m^3) > 0 the viscosity falls with the
87 // nitride's shear stress like the oxide's (Eyring, eta_N x / sinh x).
88 double nitrideViscosity = 0.;
90 std::size_t maxGridPoints = 5000000;
91 int material = -1;
92};
93
110template <class T, int D>
111class OxidationDeformation final : public VelocityField<T>,
112 public OxidationSolverBase<T, D> {
113 using IndexType = viennahrle::Index<D>;
114 using ConstSparseIterator =
115 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
116
117private:
118 // bring base members into scope
135
136 enum class Boundary { NONE, REACTION, AMBIENT, ANCHOR };
137
138 struct BoundaryIntersection {
139 Boundary boundary = Boundary::NONE;
140 T distance = 0.;
141 };
142
143 struct Node {
144 IndexType index;
145 Vec3D<T> velocity{0., 0., 0.};
146 T pressure = 0.;
147 T strainTrace = 0.;
148 std::array<T, 9> strainRateTensor{};
149 std::array<T, 9> stressTensor{};
150 T vonMisesStress = 0.;
151 bool nitride = false; // monolithic mode: node belongs to the nitride
152 };
153
154 SmartPointer<Domain<T, D>> reactionInterface = nullptr;
155 SmartPointer<Domain<T, D>> ambientInterface = nullptr;
156 SmartPointer<Domain<T, D>> maskInterface = nullptr;
157 SmartPointer<OxidationDiffusion<T, D>> diffusionField = nullptr;
158 OxidationDeformationParameters deformationParameters;
159 OxidationParameters oxidationParameters;
160 int reactionSign = 1;
161 int ambientSign = -1;
162 int maskSign = 1;
163
164 IndexType requestedMinIndex{};
165 IndexType requestedMaxIndex{};
166 unsigned iterations = 0;
167 T residual = std::numeric_limits<T>::max();
168 // Volume error of the last solve (relative to the expansion inflow) and
169 // backward error of its last linear solve.
170 T lastContinuityResidual_ = 0.;
171 T lastStokesResidual_ = 0.;
172 T avgExpansionSpeed_ = 0.;
173 bool avgExpansionSpeedComputed = false;
174 bool solved = false;
175 bool nodesDirty_ = true;
176 std::array<T, D> maxVelocity_{};
177 bool useRequestedBounds = false;
178 std::unordered_map<IndexType, std::array<T, 9>, detail::IndexTypeHasher<D>>
179 deviatoricStressHistory;
180 // Stress-dependent oxide viscosity per node (shearActivationVolume > 0).
181 std::vector<T> oxideEta_;
182 // Stress-dependent nitride viscosity per node
183 // (nitrideShearActivationVolume > 0).
184 std::vector<T> nitrideEta_;
185 // Monolithic nitride state: the step-start stress (from the mask level set;
186 // per node in nitrideStartNode_), the end-of-step stress, and that stress
187 // carried with the material.
188 std::unordered_map<IndexType, std::array<T, 9>, detail::IndexTypeHasher<D>>
189 nitrideStressStart_;
190 std::vector<std::array<T, 9>> nitrideStartNode_;
191 std::vector<std::array<T, 9>> nitrideStressEnd_;
192 std::vector<std::array<T, 9>> nitrideStressAdvected_;
193 bool nitrideStressAdvectedValid_ = false;
194 // Maxwell oxide: the end-of-step deviatoric stress per node (shear
195 // components the mean of the surrounding corners), and that stress carried
196 // with the flow.
197 std::vector<std::array<T, 9>> oxideStressEnd_;
198 std::vector<std::array<T, 9>> oxideStressAdvected_;
199 bool oxideStressAdvectedValid_ = false;
200 // Per-node values that stay fixed during one apply(), cached because the
201 // stencils need them for every row: the expansion velocity at Si faces and
202 // the step-start oxide stress.
203 // Valid only inside apply(); outside it they are recomputed per call.
204 std::vector<Vec3D<T>> reactionVelocity_;
205 std::vector<std::array<T, 9>> startStress_;
206 bool boundaryCacheValid_ = false;
207
208 static bool isFiniteVec(const Vec3D<T> &value) {
209 for (unsigned i = 0; i < 3; ++i)
210 if (!std::isfinite(value[i]))
211 return false;
212 return true;
213 }
214
215 static bool isFiniteTensor(const std::array<T, 9> &value) {
216 for (const auto component : value)
217 if (!std::isfinite(component))
218 return false;
219 return true;
220 }
221
222 // Face-major flat BC arrays: index = fi * n + nodeId, fi in [0, 2*D).
223 std::vector<Boundary> faceBCTypes_;
224 std::vector<T> faceBCDists_;
225
226public:
227 std::vector<Node> nodes;
228
230#ifdef VIENNALS_GPU_BICGSTAB
231 ~OxidationDeformation() { gpu::gpuMacFree(macGpu_); }
232#endif
233
235 SmartPointer<Domain<T, D>> passedReactionInterface,
236 SmartPointer<Domain<T, D>> passedAmbientInterface,
237 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField,
238 OxidationParameters passedOxidationParameters,
239 OxidationDeformationParameters passedDeformationParameters = {})
240 : reactionInterface(passedReactionInterface),
241 ambientInterface(passedAmbientInterface),
242 diffusionField(passedDiffusionField),
243 deformationParameters(passedDeformationParameters),
244 oxidationParameters(passedOxidationParameters) {}
245
246 template <class... Args> static auto New(Args &&...args) {
247 return SmartPointer<OxidationDeformation>::New(std::forward<Args>(args)...);
248 }
249
250 void setReactionInterface(SmartPointer<Domain<T, D>> passedInterface) {
251 reactionInterface = passedInterface;
252 nodesDirty_ = true;
253 solved = false;
254 }
255
256 void setAmbientInterface(SmartPointer<Domain<T, D>> passedInterface) {
257 ambientInterface = passedInterface;
258 nodesDirty_ = true;
259 solved = false;
260 }
261
262 void setMaskInterface(SmartPointer<Domain<T, D>> passedInterface,
263 int passedMaskSign = 1) {
264 maskInterface = passedInterface;
265 maskSign = (passedMaskSign < 0) ? -1 : 1;
266 nodesDirty_ = true;
267 solved = false;
268 }
269
271 maskInterface = nullptr;
272 nodesDirty_ = true;
273 solved = false;
274 }
275
277 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField) {
278 diffusionField = passedDiffusionField;
279 solved = false;
280 }
281
283 oxidationParameters = passedParameters;
284 solved = false;
285 }
286
287 void
289 deformationParameters = passedParameters;
290 solved = false;
291 }
292
298 void setCouplingIterate(bool iterate) { couplingIterate_ = iterate; }
299
303 void setGpuMode(GpuMode mode) { gpuMode_ = mode; }
304
305 void setOxideSigns(int passedReactionSign, int passedAmbientSign) {
306 reactionSign = (passedReactionSign < 0) ? -1 : 1;
307 ambientSign = (passedAmbientSign < 0) ? -1 : 1;
308 solved = false;
309 }
310
311 void setSolveBounds(const IndexType &passedMinIndex,
312 const IndexType &passedMaxIndex) {
313 requestedMinIndex = passedMinIndex;
314 requestedMaxIndex = passedMaxIndex;
315 useRequestedBounds = true;
316 nodesDirty_ = true;
317 solved = false;
318 }
319
321 useRequestedBounds = false;
322 nodesDirty_ = true;
323 solved = false;
324 }
325
327 nodesDirty_ = true;
328 solved = false;
329 }
330
331 // ===================== monolithic oxide + nitride =====================
332 bool monolithicNitride() const {
333 return deformationParameters.nitrideYoungModulus > 0. &&
334 maskInterface != nullptr;
335 }
336 T stepTime() const {
337 return std::max(T(deformationParameters.stressTimeStep),
338 std::numeric_limits<T>::min());
339 }
341 return std::clamp(T(deformationParameters.nitridePoissonRatio), T(-0.95),
342 T(0.49));
343 }
344 T nitrideMu() const {
345 return T(deformationParameters.nitrideYoungModulus) /
346 (T(2) * (T(1) + nitridePoisson()));
347 }
348 T nitrideLambda() const {
349 const T nu = nitridePoisson();
350 return T(deformationParameters.nitrideYoungModulus) * nu /
351 ((T(1) + nu) * (T(1) - T(2) * nu));
352 }
353
354 // Maxwell nitride: over one substep the deviator decays by
355 // exp(-dt mu / eta) and responds with the implicit modulus
356 // (1 - decay) eta (about mu dt for dt << tau); without a viscosity the
357 // nitride is elastic (decay 1, modulus mu dt).
358 bool viscousNitride() const {
359 return deformationParameters.nitrideViscosity > 0.;
360 }
362 return viscousNitride() &&
363 deformationParameters.nitrideShearActivationVolume > 0.;
364 }
365 T nitrideDecayFor(T eta) const {
366 if (!viscousNitride())
367 return T(1);
368 return std::exp(-stepTime() * nitrideMu() / eta);
369 }
370 T nitrideImplicitFor(T eta) const {
371 if (!viscousNitride())
372 return nitrideMu() * stepTime();
373 return std::max(-std::expm1(-stepTime() * nitrideMu() / eta) * eta,
374 std::numeric_limits<T>::min() * T(1e10));
375 }
376 T nitrideNodeViscosity(std::size_t i) const {
377 return (stressDependentNitride() && i < nitrideEta_.size())
378 ? nitrideEta_[i]
379 : T(deformationParameters.nitrideViscosity);
380 }
381 // Normal-stress moduli of nitride node i: sigma_dd = twoEta e_dd +
382 // volumetric div v + memory (elastic: 2 mu dt and lambda dt).
383 void nitrideModuli(std::size_t i, T &twoEta, T &volumetric) const {
384 const T dt = stepTime();
385 if (!viscousNitride()) {
386 twoEta = T(2) * nitrideMu() * dt;
387 volumetric = nitrideLambda() * dt;
388 return;
389 }
390 const T etaImpl = nitrideImplicitFor(nitrideNodeViscosity(i));
391 twoEta = T(2) * etaImpl;
392 volumetric = (nitrideLambda() + T(2) * nitrideMu() / T(3)) * dt -
393 T(2) * etaImpl / T(3);
394 }
395
396 std::array<T, 9> nitrideStressStartAt(const IndexType &index) const {
397 const auto it = nitrideStressStart_.find(index);
398 return it == nitrideStressStart_.end() ? std::array<T, 9>{} : it->second;
399 }
400 const std::array<T, 9> &nitrideStartOf(std::size_t i) const {
401 static const std::array<T, 9> zero{};
402 return i < nitrideStartNode_.size() ? nitrideStartNode_[i] : zero;
403 }
404
405 BoundaryIntersection nitrideBoundaryIntersection(
406 ConstSparseIterator &reactionIt, ConstSparseIterator &maskIt,
407 const IndexType &inside, const IndexType &outside) const {
408 const T maskInside = valueAtMask(maskIt, inside);
409 const T maskOutside = valueAtMask(maskIt, outside);
410 if (crosses(maskInside, maskOutside))
411 return {Boundary::AMBIENT, crossingDistance(maskInside, maskOutside)};
412 const T reactionInside = valueAt(reactionIt, inside);
413 const T reactionOutside = valueAt(reactionIt, outside);
414 if (crosses(reactionInside, reactionOutside)) // nitride on bare Si: weld
415 return {Boundary::ANCHOR,
416 crossingDistance(reactionInside, reactionOutside)};
417 return {Boundary::AMBIENT, gridDelta};
418 }
419
420 // Nitride stress: the step-start stress (the momentum's memory term; the
421 // nitride has no pressure) plus this step's increment. Elastic, or
422 // Maxwell (Senez 1996): the mean stress elastic, the deviator decayed,
423 // sigma_end = (m_start + K dt tr D) I + decay s_start + 2 eta_impl D'.
424 void computeNitrideStress(std::size_t i) {
425 auto &node = nodes[i];
426 const auto &start = nitrideStartOf(i);
427 const T dt = stepTime();
428 const T mu = nitrideMu();
429 const T lambda = nitrideLambda();
430 T trace = T(0);
431 for (unsigned d = 0; d < D; ++d)
432 trace += node.strainRateTensor[tensorIndex(d, d)];
433 std::array<T, 9> end = start;
434 if (!viscousNitride()) {
435 for (unsigned r = 0; r < 3; ++r) {
436 for (unsigned c = 0; c < 3; ++c)
437 end[tensorIndex(r, c)] +=
438 dt * T(2) * mu * node.strainRateTensor[tensorIndex(r, c)];
439 end[tensorIndex(r, r)] += dt * lambda * trace;
440 }
441 } else {
442 const T decay = nitrideDecayFor(nitrideNodeViscosity(i));
443 const T mean = (start[0] + start[4] + start[8]) / T(3);
444 T twoEta, volumetric;
445 nitrideModuli(i, twoEta, volumetric);
446 for (unsigned r = 0; r < 3; ++r) {
447 for (unsigned c = 0; c < 3; ++c)
448 end[tensorIndex(r, c)] =
449 decay * start[tensorIndex(r, c)] +
450 twoEta * node.strainRateTensor[tensorIndex(r, c)];
451 end[tensorIndex(r, r)] += (T(1) - decay) * mean + volumetric * trace;
452 }
453 }
454 if (i < nitrideStressEnd_.size())
455 nitrideStressEnd_[i] = end;
456 nitrideStressAdvectedValid_ = false;
457 node.stressTensor = end; // physical (output)
458 const T meanStress = (end[0] + end[4] + end[8]) / T(3);
459 auto deviator = end;
460 for (unsigned d = 0; d < 3; ++d)
461 deviator[tensorIndex(d, d)] -= meanStress;
462 node.vonMisesStress = vonMisesFromDeviatoric(deviator);
463 }
464
465 // End-of-step stress carried with the material (semi-Lagrangian: the value
466 // at x - v dt, D-linear over the nodes of the same material). Stored at
467 // grid points unadvected, the memory would slide relative to the moving
468 // material. Points whose upstream position has no node of the material
469 // keep their own value. Oxide whose upstream position lies in the Si was
470 // formed during the step: no history (new oxide is born stress-free).
471 // Renormalising over the oxide nodes there instead handed the first oxide
472 // layer its own old stress every step, so it never took in new material
473 // and its history grew without bound -- tension over the troughs of a
474 // corrugated front that grew as 1/h.
475 void advectStress(const std::vector<std::array<T, 9>> &end,
476 std::vector<std::array<T, 9>> &out, bool nitride) const {
477 const std::size_t n = nodes.size();
478 out = end;
479 if (end.size() != n)
480 return;
481 const T dt = stepTime();
482#pragma omp parallel for schedule(static)
483 for (std::size_t i = 0; i < n; ++i) {
484 if (nodes[i].nitride != nitride)
485 continue;
486 std::array<T, D> base{};
487 std::array<T, D> frac{};
488 std::array<T, D> departure{};
489 for (unsigned d = 0; d < D; ++d) {
490 const T x = static_cast<T>(nodes[i].index[d]) -
491 nodes[i].velocity[d] * dt / gridDelta;
492 departure[d] = x;
493 base[d] = std::floor(x);
494 frac[d] = x - base[d];
495 }
496 if (!nitride && macPhiRAt(departure) < T(0)) {
497 out[i] = std::array<T, 9>{};
498 continue;
499 }
500 std::array<T, 9> sum{};
501 T weightSum = T(0);
502 for (unsigned corner = 0; corner < (1u << D); ++corner) {
503 IndexType idx;
504 T weight = T(1);
505 for (unsigned d = 0; d < D; ++d) {
506 const bool up = (corner >> d) & 1u;
507 idx[d] = static_cast<viennahrle::IndexType>(base[d]) + (up ? 1 : 0);
508 weight *= up ? frac[d] : (T(1) - frac[d]);
509 }
510 if (weight <= T(0) || !inBounds(idx))
511 continue;
512 const std::size_t j = nodeLookupFlat[linearIndex(idx)];
513 if (j == noNode || nodes[j].nitride != nitride)
514 continue;
515 for (unsigned k = 0; k < 9; ++k)
516 sum[k] += weight * end[j][k];
517 weightSum += weight;
518 }
519 if (weightSum > T(1e-6))
520 for (unsigned k = 0; k < 9; ++k)
521 out[i][k] = sum[k] / weightSum;
522 }
523 }
525 advectStress(nitrideStressEnd_, nitrideStressAdvected_, true);
526 nitrideStressAdvectedValid_ = true;
527 }
529 advectStress(oxideStressEnd_, oxideStressAdvected_, false);
530 oxideStressAdvectedValid_ = true;
531 }
532
533 // Mean of field over the nodes of one material next to idx (face
534 // neighbours, else the full 3^D box). False if there are none.
535 bool neighbourStress(const IndexType &idx,
536 const std::vector<std::array<T, 9>> &field, bool nitride,
537 std::array<T, 9> &out) const {
538 if (field.size() != nodes.size())
539 return false;
540 std::array<T, 9> sum{};
541 unsigned count = 0;
542 auto add = [&](const IndexType &nb) {
543 const std::size_t j = lookupNode(nb);
544 if (j == noNode || nodes[j].nitride != nitride)
545 return;
546 for (unsigned k = 0; k < 9; ++k)
547 sum[k] += field[j][k];
548 ++count;
549 };
550 for (unsigned d = 0; d < D; ++d)
551 for (int off : {-1, 1}) {
552 IndexType nb = idx;
553 nb[d] += off;
554 add(nb);
555 }
556 if (count == 0) {
557 unsigned corners = 1;
558 for (unsigned d = 0; d < D; ++d)
559 corners *= 3;
560 for (unsigned code = 0; code < corners; ++code) {
561 IndexType nb = idx;
562 unsigned rest = code;
563 bool centre = true;
564 for (unsigned d = 0; d < D; ++d) {
565 const int off = static_cast<int>(rest % 3) - 1;
566 rest /= 3;
567 nb[d] += off;
568 centre = centre && off == 0;
569 }
570 if (!centre)
571 add(nb);
572 }
573 }
574 if (count == 0)
575 return false;
576 for (unsigned k = 0; k < 9; ++k)
577 out[k] = sum[k] / static_cast<T>(count);
578 return true;
579 }
580
581 // Persist nitride state on the mask level set: the advected stress as the
582 // next step's history ("NitStressR*"), plus velocity and end-of-step stress
583 // for visualisation ("MaskVelocity", "MaskStressR*", the names the elastic
584 // mask solver writes).
586 if (maskInterface == nullptr || nodes.empty())
587 return;
588 if (!nitrideStressAdvectedValid_)
590 using VD = typename PointData<T>::VectorDataType;
591 VD hist0, hist1, hist2, vel, out0, out1, out2;
592 ConstSparseIterator it(maskInterface->getDomain());
593 for (; !it.isFinished(); ++it) {
594 if (!it.isDefined())
595 continue;
596 const IndexType idx = it.getStartIndices();
597 const std::size_t j = lookupNode(idx);
598 if (j != noNode && nodes[j].nitride &&
599 j < nitrideStressAdvected_.size()) {
600 const auto &h = nitrideStressAdvected_[j];
601 const auto &e = nitrideStressEnd_[j];
602 hist0.push_back({h[0], h[1], h[2]});
603 hist1.push_back({h[3], h[4], h[5]});
604 hist2.push_back({h[6], h[7], h[8]});
605 vel.push_back(isFiniteVec(nodes[j].velocity)
606 ? nodes[j].velocity
607 : Vec3D<T>{T(0), T(0), T(0)});
608 out0.push_back({e[0], e[1], e[2]});
609 out1.push_back({e[3], e[4], e[5]});
610 out2.push_back({e[6], e[7], e[8]});
611 } else {
612 // Not a nitride node of this solve: a gas point the rising nitride
613 // will cover, or a point outside the solve. Carry the stress of the
614 // adjacent nitride nodes so a newly covered surface layer keeps its
615 // (bending) stress; else keep the stored value.
616 std::array<T, 9> old{};
617 if (!neighbourStress(idx, nitrideStressAdvected_, true, old))
618 old = nitrideStressStartAt(idx);
619 hist0.push_back({old[0], old[1], old[2]});
620 hist1.push_back({old[3], old[4], old[5]});
621 hist2.push_back({old[6], old[7], old[8]});
622 vel.push_back({T(0), T(0), T(0)});
623 out0.push_back({T(0), T(0), T(0)});
624 out1.push_back({T(0), T(0), T(0)});
625 out2.push_back({T(0), T(0), T(0)});
626 }
627 }
628 auto &pd = maskInterface->getPointData();
629 pd.insertReplaceVectorData(std::move(hist0), "NitStressR0");
630 pd.insertReplaceVectorData(std::move(hist1), "NitStressR1");
631 pd.insertReplaceVectorData(std::move(hist2), "NitStressR2");
632 pd.insertReplaceVectorData(std::move(vel), "MaskVelocity");
633 pd.insertReplaceVectorData(std::move(out0), "MaskStressR0");
634 pd.insertReplaceVectorData(std::move(out1), "MaskStressR1");
635 pd.insertReplaceVectorData(std::move(out2), "MaskStressR2");
636 }
637
639 nitrideStressStart_.clear();
640 if (!monolithicNitride())
641 return;
642 auto &pd = maskInterface->getPointData();
643 const int r0 = pd.getVectorDataIndex("NitStressR0");
644 const int r1 = pd.getVectorDataIndex("NitStressR1");
645 const int r2 = pd.getVectorDataIndex("NitStressR2");
646 if (r0 == -1 || r1 == -1 || r2 == -1)
647 return; // first step: stress-free nitride
648 const auto *d0 = pd.getVectorData(r0);
649 const auto *d1 = pd.getVectorData(r1);
650 const auto *d2 = pd.getVectorData(r2);
651 ConstSparseIterator it(maskInterface->getDomain());
652 for (; !it.isFinished(); ++it) {
653 if (!it.isDefined())
654 continue;
655 const auto id = it.getPointId();
656 if (id >= static_cast<decltype(id)>(d0->size()) ||
657 id >= static_cast<decltype(id)>(d1->size()) ||
658 id >= static_cast<decltype(id)>(d2->size()))
659 continue;
660 const auto &a = (*d0)[id];
661 const auto &b = (*d1)[id];
662 const auto &c = (*d2)[id];
663 std::array<T, 9> st{a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]};
664 if (isFiniteTensor(st))
665 nitrideStressStart_[it.getStartIndices()] = st;
666 }
667 // Nitride nodes without a stored value: mean of stored face neighbours.
668 std::vector<std::pair<IndexType, std::array<T, 9>>> fills;
669 for (const auto &node : nodes) {
670 if (!node.nitride || nitrideStressStart_.count(node.index))
671 continue;
672 std::array<T, 9> sum{};
673 unsigned count = 0;
674 for (unsigned d = 0; d < D; ++d)
675 for (int off : {-1, 1}) {
676 IndexType nb = node.index;
677 nb[d] += off;
678 const auto found = nitrideStressStart_.find(nb);
679 if (found == nitrideStressStart_.end())
680 continue;
681 for (unsigned k = 0; k < 9; ++k)
682 sum[k] += found->second[k];
683 ++count;
684 }
685 if (count == 0)
686 continue;
687 for (unsigned k = 0; k < 9; ++k)
688 sum[k] /= static_cast<T>(count);
689 fills.push_back({node.index, sum});
690 }
691 for (const auto &fill : fills)
692 nitrideStressStart_[fill.first] = fill.second;
693 }
694 // =======================================================================
695
696 void apply() {
697 if (reactionInterface == nullptr || ambientInterface == nullptr ||
698 diffusionField == nullptr) {
699 Logger::getInstance()
700 .addError("OxidationDeformation: Missing interface or "
701 "diffusion field.")
702 .print();
703 return;
704 }
705 if (maskInterface != nullptr && !monolithicNitride())
706 VIENNACORE_LOG_ERROR(
707 "OxidationDeformation: a mask interface needs the monolithic "
708 "nitride mechanics (nitrideYoungModulus > 0).");
709
710 if (nodesDirty_) {
711 if (!initialiseGrid())
712 return; // base class already logged the error
713 buildNodes();
714 nodesDirty_ = false;
715 if (nodes.empty())
716 Logger::getInstance()
717 .addWarning("OxidationDeformation: no oxide nodes found after "
718 "buildNodes(). Verify that the reaction and ambient "
719 "level sets enclose a non-empty oxide band.")
720 .print();
721 // Restore the stress history from the level-set point data (written
722 // by writeFieldsToLevelSet() before the previous advection and
723 // remapped + filled by lsAdvect + lsInterior).
724 seedFromLevelSet();
726 }
727
728 const std::size_t nn = nodes.size();
730 Timer<> tMechanics;
731 tMechanics.start();
733 tMechanics.finish();
734 boundaryCacheValid_ = false;
735 Logger::getInstance()
736 .addTiming(" deformation n=" + std::to_string(nn) +
737 " mechanics-total",
738 tMechanics)
739 .print();
740 avgExpansionSpeedComputed = false;
741
742 maxVelocity_.fill(T(0));
743 for (const auto &node : nodes) {
744 for (unsigned d = 0; d < D; ++d)
745 maxVelocity_[d] = std::max(maxVelocity_[d], std::abs(node.velocity[d]));
746 }
747 // The gas-side (unresolved) velocities enter the CFL step and the
748 // advection's dissipation, i.e. only the solve the advection uses: a
749 // stress-coupling iteration skips this walk over the ambient interface
750 // (with stress kinetics it took ~30 % of a LOCOS run).
751 if (!couplingIterate_) {
752 const auto unresolvedMax = estimateMaxUnresolvedAmbientVelocity();
753 for (unsigned d = 0; d < D; ++d)
754 maxVelocity_[d] = std::max(maxVelocity_[d], unresolvedMax[d]);
755 }
756
757 solved = true;
758 }
759
760 Vec3D<T> getVectorVelocity(const Vec3D<T> &coordinate, int material,
761 const Vec3D<T> & /*normalVector*/,
762 unsigned long /*pointId*/) final {
763 if (!solved)
764 apply();
765
766 if (deformationParameters.material >= 0 &&
767 material != deformationParameters.material)
768 return {0., 0., 0.};
769
770 auto velocity = getVelocity(coordinate);
771 T norm2 = 0.;
772 for (unsigned d = 0; d < D; ++d)
773 norm2 += velocity[d] * velocity[d];
774 bool resolved = norm2 > std::numeric_limits<T>::epsilon();
775
776 // Gas-side advection queries land on grid points with no solver node at
777 // any interpolation corner, so the D-linear lookup above comes back
778 // empty. Extend the solved velocity from the nearest oxide node instead
779 // of dropping to the expansion-speed heuristic: that heuristic evaluates
780 // the reaction speed from the LOCAL concentration, which at the oxide TOP
781 // is ~C_eq — overestimating the true interface speed by C_eq/C_si. Fed
782 // into the upwind stencil, that inflated gas-side value advects the free
783 // surface faster than the flow field and fabricates oxide volume — the
784 // error grows with thickness as C_si drops (measured: expansion 2.35 at
785 // 10 nm, 2.68 at 100 nm, target 2.27).
786 if (!resolved) {
787 IndexType index;
788 for (unsigned d = 0; d < D; ++d)
789 index[d] = std::llround(coordinate[d] / gridDelta);
790 resolved = nearbyMeanVelocity(
791 index, [](std::size_t) { return true; }, velocity);
792 }
793
794 const auto result =
795 resolved ? velocity : unresolvedAmbientVelocity(coordinate);
796
797 return result;
798 }
799
800 T getScalarVelocity(const Vec3D<T> &coordinate, int material,
801 const Vec3D<T> &normalVector,
802 unsigned long /*pointId*/) final {
803 return 0.;
804 }
805
806 T getDissipationAlpha(int direction, int material,
807 const Vec3D<T> & /*centralDifferences*/) final {
808 if (deformationParameters.material >= 0 &&
809 material != deformationParameters.material)
810 return 0.;
811 return maxVelocity_[direction];
812 }
813
814private:
815 template <class ValueType, class NodeAccessor>
816 ValueType getField(const IndexType &index, ValueType fallback,
817 NodeAccessor accessor) const {
818 const std::size_t nodeId = lookupNode(index);
819 if (nodeId != noNode)
820 return accessor(nodes[nodeId]);
821
822 const auto nearby = findNearbyNode(index);
823 if (nearby == noNode)
824 return fallback;
825 return accessor(nodes[nearby]);
826 }
827
828 template <class ValueType, class NodeAccessor>
829 ValueType getField(const Vec3D<T> &coordinate, ValueType fallback,
830 NodeAccessor accessor) const {
831 // D-linear interpolation for Vec3D<T> (velocity) and scalar T (pressure).
832 // These are the types queried during level-set advection; interpolating at
833 // the exact interface coordinate eliminates the O(gridDelta) nearest-node
834 // error that caused the SiO2/mask interface to shift with grid delta.
835 // Other types (e.g. stress tensor std::array<T,9>) use nearest-node because
836 // they lack scalar multiply and are not used for advection.
837 if constexpr (!std::is_same_v<ValueType, Vec3D<T>> &&
838 !std::is_same_v<ValueType, T>) {
839 IndexType index;
840 for (unsigned i = 0; i < D; ++i)
841 index[i] = std::llround(coordinate[i] / gridDelta);
842 return getField(index, fallback, accessor);
843 } else {
844 using IdxScalar = std::decay_t<decltype(std::declval<IndexType>()[0])>;
845 IndexType lo;
846 T frac[D];
847 for (unsigned d = 0; d < D; ++d) {
848 const T c = coordinate[d] / gridDelta;
849 const T c_flo = std::floor(c);
850 lo[d] = static_cast<IdxScalar>(c_flo);
851 frac[d] = c - c_flo;
852 }
853
854 ValueType result{};
855 T totalWeight = T(0);
856 for (int corner = 0; corner < (1 << D); ++corner) {
857 IndexType idx = lo;
858 T w = T(1);
859 for (unsigned d = 0; d < D; ++d) {
860 if ((corner >> d) & 1) {
861 idx[d]++;
862 w *= frac[d];
863 } else {
864 w *= T(1) - frac[d];
865 }
866 }
867 if (w < T(1e-14))
868 continue;
869 const std::size_t nodeId = lookupNode(idx);
870 if (nodeId == noNode)
871 continue;
872 result = result + accessor(nodes[nodeId]) * w;
873 totalWeight += w;
874 }
875
876 if (totalWeight < T(1e-14))
877 return fallback;
878 if (totalWeight < T(1) - T(1e-6))
879 result = result * (T(1) / totalWeight);
880 return result;
881 }
882 }
883
884public:
885 Vec3D<T> getVelocity(const Vec3D<T> &coordinate) const {
886 return getField(coordinate, Vec3D<T>{0., 0., 0.},
887 [](const Node &n) { return n.velocity; });
888 }
889
896 Vec3D<T> getResolvedVectorVelocity(const Vec3D<T> &coordinate) const {
897 auto velocity = getVelocity(coordinate);
898 T norm2 = 0.;
899 for (unsigned d = 0; d < D; ++d)
900 norm2 += velocity[d] * velocity[d];
901 if (norm2 > std::numeric_limits<T>::epsilon())
902 return velocity;
903 IndexType index;
904 for (unsigned d = 0; d < D; ++d)
905 index[d] = std::llround(coordinate[d] / gridDelta);
907 index, [](std::size_t) { return true; }, velocity))
908 return velocity;
909 return {0., 0., 0.};
910 }
911
912 // Mean velocity of the nearest nodes passing accept(j) (findNearbyNodes);
913 // false if there is none within 4 cells.
914 template <class Accept>
915 bool nearbyMeanVelocity(const IndexType &index, Accept accept,
916 Vec3D<T> &velocity) const {
917 const auto nearby = this->findNearbyNodes(index, accept);
918 if (nearby.empty())
919 return false;
920 velocity = Vec3D<T>{T(0), T(0), T(0)};
921 for (const auto j : nearby)
922 for (unsigned d = 0; d < D; ++d)
923 velocity[d] += nodes[j].velocity[d] / static_cast<T>(nearby.size());
924 return true;
925 }
926
927 Vec3D<T> getVelocity(const IndexType &index) const {
928 return getField(index, Vec3D<T>{0., 0., 0.},
929 [](const Node &n) { return n.velocity; });
930 }
931
939 Vec3D<T> getMaterialVelocity(const Vec3D<T> &coordinate, bool nitride) {
940 if (!solved)
941 apply();
942 using IdxScalar = std::decay_t<decltype(std::declval<IndexType>()[0])>;
943 IndexType lo;
944 T frac[D];
945 for (unsigned d = 0; d < D; ++d) {
946 const T c = coordinate[d] / gridDelta;
947 const T cFloor = std::floor(c);
948 lo[d] = static_cast<IdxScalar>(cFloor);
949 frac[d] = c - cFloor;
950 }
951 Vec3D<T> result{T(0), T(0), T(0)};
952 T totalWeight = T(0);
953 for (int corner = 0; corner < (1 << D); ++corner) {
954 IndexType idx = lo;
955 T w = T(1);
956 for (unsigned d = 0; d < D; ++d) {
957 if ((corner >> d) & 1) {
958 idx[d]++;
959 w *= frac[d];
960 } else {
961 w *= T(1) - frac[d];
962 }
963 }
964 if (w < T(1e-14))
965 continue;
966 const std::size_t j = lookupNode(idx);
967 if (j == noNode || nodes[j].nitride != nitride)
968 continue;
969 for (unsigned d = 0; d < D; ++d)
970 result[d] += w * nodes[j].velocity[d];
971 totalWeight += w;
972 }
973 if (totalWeight >= T(1e-14)) {
974 for (unsigned d = 0; d < D; ++d)
975 result[d] /= totalWeight;
976 return result;
977 }
978 IndexType index;
979 for (unsigned d = 0; d < D; ++d)
980 index[d] = std::llround(coordinate[d] / gridDelta);
982 index, [&](std::size_t j) { return nodes[j].nitride == nitride; },
983 result))
984 return result;
985 return nitride ? Vec3D<T>{T(0), T(0), T(0)}
986 : unresolvedAmbientVelocity(coordinate);
987 }
988
989 T getPressure(const Vec3D<T> &coordinate) const {
990 return getField(coordinate, T(0), [](const Node &n) { return n.pressure; });
991 }
992
993 T getPressure(const IndexType &index) const {
994 return getField(index, T(0), [](const Node &n) { return n.pressure; });
995 }
996
997 T getStrainTrace(const Vec3D<T> &coordinate) const {
998 return getField(coordinate, T(0),
999 [](const Node &n) { return n.strainTrace; });
1000 }
1001
1002 T getStrainTrace(const IndexType &index) const {
1003 return getField(index, T(0), [](const Node &n) { return n.strainTrace; });
1004 }
1005
1006 std::array<T, 9> getStrainRateTensor(const Vec3D<T> &coordinate) const {
1007 return getField(coordinate, std::array<T, 9>{},
1008 [](const Node &n) { return n.strainRateTensor; });
1009 }
1010
1011 std::array<T, 9> getStrainRateTensor(const IndexType &index) const {
1012 return getField(index, std::array<T, 9>{},
1013 [](const Node &n) { return n.strainRateTensor; });
1014 }
1015
1016 std::array<T, 9> getStressTensor(const Vec3D<T> &coordinate) const {
1017 return getField(coordinate, std::array<T, 9>{},
1018 [](const Node &n) { return n.stressTensor; });
1019 }
1020
1021 std::array<T, 9> getStressTensor(const IndexType &index) const {
1022 return getField(index, std::array<T, 9>{},
1023 [](const Node &n) { return n.stressTensor; });
1024 }
1025
1026 T getVonMisesStress(const Vec3D<T> &coordinate) const {
1027 return getField(coordinate, T(0),
1028 [](const Node &n) { return n.vonMisesStress; });
1029 }
1030
1031 T getVonMisesStress(const IndexType &index) const {
1032 return getField(index, T(0),
1033 [](const Node &n) { return n.vonMisesStress; });
1034 }
1035
1036 unsigned getIterations() const { return iterations; }
1037 T getResidual() const { return residual; }
1038 // Volume the final flow field fails to conserve, relative to the expansion
1039 // inflow (see solveMechanics).
1040 T getLastContinuityResidual() const { return lastContinuityResidual_; }
1041 // Residual of the last velocity-pressure solve: componentwise backward
1042 // error (LU, 2-D), relative residual (AMGCL, 3-D).
1043 T getLastStokesResidual() const { return lastStokesResidual_; }
1044 // Largest velocity-pressure solve residual lastSolveConverged() accepts.
1045 static constexpr T stokesTolerance = T(1e-6);
1046 bool lastSolveConverged() const {
1047 return std::isfinite(residual) &&
1048 residual <= deformationParameters.mechanicsTolerance &&
1049 std::isfinite(lastStokesResidual_) &&
1050 lastStokesResidual_ <= stokesTolerance && hasFiniteSolution();
1051 }
1052 bool hasFiniteSolution() const {
1053 for (const auto &node : nodes) {
1054 if (!isFiniteVec(node.velocity) || !std::isfinite(node.pressure) ||
1055 !std::isfinite(node.strainTrace) ||
1056 !isFiniteTensor(node.strainRateTensor) ||
1057 !isFiniteTensor(node.stressTensor) ||
1058 !std::isfinite(node.vonMisesStress))
1059 return false;
1060 }
1061 return true;
1062 }
1063 std::size_t getNumberOfSolutionNodes() const { return nodes.size(); }
1065 if (!avgExpansionSpeedComputed) {
1067 avgExpansionSpeedComputed = true;
1068 }
1069 return avgExpansionSpeed_;
1070 }
1071
1072 template <class Callback> void forEachSolutionNode(Callback callback) const {
1073 for (const auto &node : nodes)
1074 callback(node.index, node.pressure, node.stressTensor);
1075 }
1076
1082 if (nodes.empty() || ambientInterface == nullptr)
1083 return;
1085
1086 using VD = typename PointData<T>::VectorDataType;
1087 VD velocity, stressR0, stressR1, stressR2;
1088 // The oxide's end-of-step stress, carried with the flow, is the next
1089 // substep's S_start. Points that are not oxide nodes (gas band, newly
1090 // covered points, nitride) take the adjacent oxide nodes' value, else
1091 // keep their stored one.
1092 if (!oxideStressAdvectedValid_)
1094
1095 ConstSparseIterator it(ambientInterface->getDomain());
1096 for (; !it.isFinished(); ++it) {
1097 if (!it.isDefined())
1098 continue;
1099 const IndexType idx = it.getStartIndices();
1100 const std::size_t nId = lookupNode(idx);
1101
1102 velocity.push_back((nId != noNode && isFiniteVec(nodes[nId].velocity))
1103 ? nodes[nId].velocity
1104 : Vec3D<T>{T(0), T(0), T(0)});
1105 std::array<T, 9> stress{};
1106 if (nId != noNode && !nodes[nId].nitride &&
1107 nId < oxideStressAdvected_.size())
1108 stress = oxideStressAdvected_[nId];
1109 else if (nId == noNode && macPhiR(idx) < T(0))
1110 stress = {}; // Si: becomes oxide only as new, stress-free material
1111 else if (!neighbourStress(idx, oxideStressAdvected_, false, stress))
1112 stress = previousDeviatoricStress(idx);
1113 if (!isFiniteTensor(stress))
1114 stress = {};
1115 stressR0.push_back({stress[0], stress[1], stress[2]});
1116 stressR1.push_back({stress[3], stress[4], stress[5]});
1117 stressR2.push_back({stress[6], stress[7], stress[8]});
1118 }
1119
1120 auto &pd = ambientInterface->getPointData();
1121 pd.insertReplaceVectorData(std::move(velocity), "OxVelocity");
1122 pd.insertReplaceVectorData(std::move(stressR0), "OxStressR0");
1123 pd.insertReplaceVectorData(std::move(stressR1), "OxStressR1");
1124 pd.insertReplaceVectorData(std::move(stressR2), "OxStressR2");
1125 if (stressDependentViscosity()) { // viscosity / eta0, for visualisation
1126 typename PointData<T>::ScalarDataType factor;
1127 for (ConstSparseIterator vit(ambientInterface->getDomain());
1128 !vit.isFinished(); ++vit) {
1129 if (!vit.isDefined())
1130 continue;
1131 const std::size_t nId = lookupNode(vit.getStartIndices());
1132 factor.push_back((nId != noNode && !nodes[nId].nitride)
1133 ? nodeViscosity(nId) /
1134 T(deformationParameters.viscosity)
1135 : T(1));
1136 }
1137 pd.insertReplaceScalarData(std::move(factor), "OxViscosity");
1138 }
1139 { // mechanics pressure (Pa, compression positive), for visualisation
1140 typename PointData<T>::ScalarDataType pressure;
1141 for (ConstSparseIterator pit(ambientInterface->getDomain());
1142 !pit.isFinished(); ++pit) {
1143 if (!pit.isDefined())
1144 continue;
1145 const std::size_t nId = lookupNode(pit.getStartIndices());
1146 pressure.push_back((nId != noNode && !nodes[nId].nitride &&
1147 std::isfinite(nodes[nId].pressure))
1148 ? nodes[nId].pressure
1149 : T(0));
1150 }
1151 pd.insertReplaceScalarData(std::move(pressure), "OxMechanicsPressure");
1152 }
1153
1154 if (monolithicNitride())
1157 }
1158
1159private:
1162 void seedFromLevelSet() {
1163 if (ambientInterface == nullptr || nodes.empty())
1164 return;
1166
1167 auto &pd = ambientInterface->getPointData();
1168 const int r0Idx = pd.getVectorDataIndex("OxStressR0");
1169 const int r1Idx = pd.getVectorDataIndex("OxStressR1");
1170 const int r2Idx = pd.getVectorDataIndex("OxStressR2");
1171 if (r0Idx == -1 || r1Idx == -1 || r2Idx == -1)
1172 return;
1173 const auto *r0d = pd.getVectorData(r0Idx);
1174 const auto *r1d = pd.getVectorData(r1Idx);
1175 const auto *r2d = pd.getVectorData(r2Idx);
1176
1177 ConstSparseIterator it(ambientInterface->getDomain());
1178 for (; !it.isFinished(); ++it) {
1179 if (!it.isDefined())
1180 continue;
1181 const auto ptId = it.getPointId();
1182 const IndexType idx = it.getStartIndices();
1183 if (ptId < static_cast<decltype(ptId)>(r0d->size()) &&
1184 ptId < static_cast<decltype(ptId)>(r1d->size()) &&
1185 ptId < static_cast<decltype(ptId)>(r2d->size())) {
1186 const auto &row0 = (*r0d)[ptId];
1187 const auto &row1 = (*r1d)[ptId];
1188 const auto &row2 = (*r2d)[ptId];
1189 std::array<T, 9> s{row0[0], row0[1], row0[2], row1[0], row1[1],
1190 row1[2], row2[0], row2[1], row2[2]};
1191 if (isFiniteTensor(s))
1192 deviatoricStressHistory[idx] = s;
1193 }
1194 }
1195 }
1196
1197public:
1200 reactionInterface, ambientInterface, maskInterface, useRequestedBounds,
1201 requestedMinIndex, requestedMaxIndex,
1202 deformationParameters.maxGridPoints, "OxidationDeformation");
1203 }
1204
1205 void buildNodes() {
1206 ++nodesVersion_;
1207 nodes.clear();
1209
1210 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1211 ConstSparseIterator ambientIt(ambientInterface->getDomain());
1212 auto maskIt = makeMaskIterator();
1213
1214 const bool mono = monolithicNitride();
1215 IndexType index = minIndex;
1216 while (true) {
1217 const T reactionPhi = valueAt(reactionIt, index);
1218 const T ambientPhi = valueAt(ambientIt, index);
1219 const bool inMask = isInsideMask(maskIt, index);
1220 if (isInsideOxide(reactionPhi, ambientPhi) && !inMask) {
1221 const std::size_t id = nodes.size();
1222 nodeLookupFlat[linearIndex(index)] = id;
1223 nodes.push_back({index});
1224 } else if (mono && inMask &&
1225 static_cast<T>(reactionSign) * reactionPhi >= T(-1e-9)) {
1226 // Monolithic: the nitride body joins the mechanics solve.
1227 const std::size_t id = nodes.size();
1228 nodeLookupFlat[linearIndex(index)] = id;
1229 nodes.push_back({index});
1230 nodes.back().nitride = true;
1231 }
1232
1233 if (!increment(index))
1234 break;
1235 }
1236
1237 // Precompute per-face boundary intersections into flat face-major arrays.
1238 const std::size_t n = nodes.size();
1239 faceBCTypes_.assign(2 * D * n, Boundary::NONE);
1240 faceBCDists_.assign(2 * D * n, T(1));
1241 for (std::size_t id = 0; id < n; ++id) {
1242 const auto &node = nodes[id];
1243 for (unsigned dir = 0; dir < D; ++dir) {
1244 for (int off : {-1, 1}) {
1245 const unsigned fi = dir * 2u + (off == 1 ? 1u : 0u);
1246 IndexType nb = node.index;
1247 nb[dir] += off;
1248 if (!inBounds(nb))
1249 continue; // reflective domain boundary: the mirror plane
1250 if (lookupNode(nb) != noNode)
1251 continue; // NONE/1.0 already set
1252 BoundaryIntersection bi;
1253 if (node.nitride) {
1254 bi =
1255 nitrideBoundaryIntersection(reactionIt, maskIt, node.index, nb);
1256 } else {
1257 bi = boundaryIntersection(reactionIt, ambientIt, maskIt, node.index,
1258 nb);
1259 }
1260 faceBCTypes_[fi * n + id] = bi.boundary;
1261 faceBCDists_[fi * n + id] = bi.distance;
1262 }
1263 }
1264 }
1265
1266 nitrideStressEnd_.assign(n, std::array<T, 9>{});
1267 nitrideStressAdvectedValid_ = false;
1268 oxideStressEnd_.assign(n, std::array<T, 9>{});
1269 oxideStressAdvectedValid_ = false;
1270 }
1271
1272 // ============================ MAC mechanics ============================
1273 // Staggered (marker-and-cell) discretisation of the oxide + nitride
1274 // mechanics on the level-set grid: pressure at the grid points (oxide
1275 // nodes), velocity component d on the midpoint of the grid edge between a
1276 // and a + e_d ("face (d, a)"), shear stress sigma_ce at the corner (2-D) /
1277 // edge (3-D) a + (e_c + e_e)/2 ("corner (ce, a)"). Pressure differences
1278 // drive the face velocities directly, so no node-to-node pressure mode can
1279 // hide from the momentum equation (a collocated grid has one, which a
1280 // thick, stiff nitride drives to GPa amplitudes). The full variable-
1281 // viscosity stress div(2 eta D') is discretised. Interfaces come from the
1282 // level sets:
1283 // - Si front: a face is an unknown if its midpoint lies on the oxide side
1284 // (phi_R mean of its two grid points); a face in Si is linear through
1285 // its partner face and the front velocity at the crossing, theta from
1286 // phi_R, floor minMechanicsBoundaryDistance.
1287 // - Free surface: every face next to a solid grid point is an unknown.
1288 // The normal stress at a gas point is the solid point's stress,
1289 // extrapolated to its traction-free value (P sigma P)_dd at the
1290 // crossing (faceBCDists_), P = I - n n^T with the surface normal n from
1291 // the defined level-set values: a free surface carries only tangential
1292 // stress (extrapolating to zero strips it from the top cell row of every
1293 // sloped surface). A corner with two or more gas points lies on the
1294 // surface and carries the traction-free shear (P sigma P)_ce of its
1295 // solid points, plus a 1e-3 residual modulus that keeps a one-cell
1296 // feature (a bump of the mask level set) attached; a gas face in its
1297 // stencil takes its partner's value. Full shear at corners with one gas
1298 // point (steps of the surface), whose stencils need no gas faces.
1299 // - Oxide / nitride: one velocity field; the corner modulus is the
1300 // harmonic mean over the corner's solid points.
1301 // - Domain boundary (reflective level-set boundary through the outermost
1302 // grid points): normal velocity odd, tangential velocity even.
1303 // Shear-stress memory lives on the corners (macCornerStart_), carried with
1304 // the flow once per accepted step (macAdvectCorners) and stored on the
1305 // level sets between solves (macWriteCornerHistory). Each viscosity
1306 // iteration solves the coupled velocity-pressure system exactly: sparse LU
1307 // in 2-D (ordering and symbolic analysis reused per node set), AMGCL in 3-D
1308 // (a direct solve does not scale there; in 2-D it is 2x faster than AMGCL
1309 // on the stiff-nitride LOCOS stack).
1310
1311 struct MacLin { // affine expression sum t_k x_k + c
1312 std::vector<std::pair<std::int64_t, T>> t;
1313 T c = T(0);
1314 MacLin &add(const MacLin &o, T s = T(1)) {
1315 for (const auto &kv : o.t)
1316 t.emplace_back(kv.first, s * kv.second);
1317 c += s * o.c;
1318 return *this;
1319 }
1320 MacLin scaled(T s) const {
1321 MacLin r;
1322 r.t.reserve(t.size());
1323 for (const auto &kv : t)
1324 r.t.emplace_back(kv.first, s * kv.second);
1325 r.c = s * c;
1326 return r;
1327 }
1328 };
1329
1330 static constexpr unsigned macPairs = static_cast<unsigned>(D * (D - 1) / 2);
1331 static unsigned macPairIndex(unsigned c, unsigned e) {
1332 if (c > e)
1333 std::swap(c, e);
1334 return (D == 2) ? 0u : (c == 0 ? (e == 1 ? 0u : 1u) : 2u);
1335 }
1336 static void macPairDirs(unsigned pair, unsigned &c, unsigned &e) {
1337 if (D == 2 || pair == 0) {
1338 c = 0;
1339 e = 1;
1340 } else if (pair == 1) {
1341 c = 0;
1342 e = 2;
1343 } else {
1344 c = 1;
1345 e = 2;
1346 }
1347 }
1348
1349 enum : std::uint8_t { macGas = 0, macSi = 1, macOxide = 2, macNitride = 3 };
1350
1351 // MAC state (rebuilt with the node set).
1352 std::size_t macNodesVersion_ = std::numeric_limits<std::size_t>::max();
1353 std::size_t nodesVersion_ = 0;
1354 std::array<std::vector<std::int64_t>, D> macPlusFace_; // face (d, node)
1355 std::array<std::vector<std::int64_t>, D>
1356 macMinusFace_; // face (d, node - e_d)
1357 std::vector<std::int64_t> macPid_; // per node
1358 std::int64_t macNVel_ = 0, macN_ = 0;
1359 std::unordered_map<std::size_t, T>
1360 macPhiR_; // phi_R (> 0 oxide side) near nodes
1361 // Inside-positive ambient / mask level sets near the nodes (NaN where the
1362 // level set is undefined): free-surface normals.
1363 std::unordered_map<std::size_t, T> macPsiA_, macPsiM_;
1364 // Corners: id per (pair, lower grid point); per corner data.
1365 std::vector<std::unordered_map<std::size_t, std::int64_t>> macCornerId_;
1366 std::vector<std::pair<unsigned, IndexType>>
1367 macCorners_; // (pair, lower point)
1368 // macCornerEta_ the oxide's corner viscosity, macCornerEtaN_ the nitride's
1371 std::vector<std::uint8_t> macCornerGas_, macCornerOxide_;
1372 // Corner shear history across steps (pair, lower point) -> start stress.
1373 std::unordered_map<IndexType, std::array<T, 3>, detail::IndexTypeHasher<D>>
1375 std::unordered_map<IndexType, std::array<T, 3>, detail::IndexTypeHasher<D>>
1378 std::vector<T> macX_; // last solution
1379 // Front velocity of the Si ghost faces during one solveMechanics, per
1380 // (partner face unknown, axis and side of the ghost): it depends only on
1381 // the geometry and the diffusion solution, which the solve does not
1382 // change, but each ghost enters several stencils in every assembly, rate
1383 // and write-back pass. The first evaluation is kept (state 0 empty,
1384 // 1 being written, 2 ready); the parallel passes read it without locks.
1385 mutable std::vector<T> macGhostV_;
1386 std::unique_ptr<std::atomic<std::uint8_t>[]> macGhostState_;
1387 std::size_t macGhostSlots_ = 0;
1388 bool macGhostOn_ = false;
1389
1390 // Reflect a grid point into the solve box (reflective boundary through the
1391 // outermost grid points).
1392 IndexType macReflect(IndexType a) const {
1393 for (unsigned d = 0; d < D; ++d)
1394 while (a[d] < minIndex[d] || a[d] > maxIndex[d])
1395 a[d] = (a[d] < minIndex[d] ? 2 * minIndex[d] : 2 * maxIndex[d]) - a[d];
1396 return a;
1397 }
1398
1399 T macPhiR(const IndexType &a) const {
1400 const IndexType r = macReflect(a);
1401 const auto it = macPhiR_.find(linearIndex(r));
1402 return it == macPhiR_.end() ? T(1) : it->second;
1403 }
1404
1405 // phi_R (positive in the oxide) at a point x in index units, D-linear
1406 // over the grid points around it.
1407 T macPhiRAt(const std::array<T, D> &x) const {
1408 std::array<T, D> base{}, frac{};
1409 for (unsigned d = 0; d < D; ++d) {
1410 base[d] = std::floor(x[d]);
1411 frac[d] = x[d] - base[d];
1412 }
1413 T value = T(0);
1414 for (unsigned corner = 0; corner < (1u << D); ++corner) {
1415 IndexType idx;
1416 T w = T(1);
1417 for (unsigned d = 0; d < D; ++d) {
1418 const bool up = (corner >> d) & 1u;
1419 idx[d] = static_cast<viennahrle::IndexType>(base[d]) + (up ? 1 : 0);
1420 w *= up ? frac[d] : (T(1) - frac[d]);
1421 }
1422 if (w > T(0))
1423 value += w * std::clamp(macPhiR(idx), T(-1), T(1));
1424 }
1425 return value;
1426 }
1427
1428 std::uint8_t macKind(const IndexType &a) const {
1429 const IndexType r = macReflect(a);
1430 const std::size_t j = nodeLookupFlat[linearIndex(r)];
1431 if (j != noNode)
1432 return nodes[j].nitride ? macNitride : macOxide;
1433 return macPhiR(r) < T(0) ? macSi : macGas;
1434 }
1435
1436 std::size_t macNode(const IndexType &a) const {
1437 if (!inBounds(a))
1438 return noNode;
1439 return nodeLookupFlat[linearIndex(a)];
1440 }
1441
1442 // Unknown id of the in-box face (d, a) or -1.
1443 std::int64_t macFaceId(unsigned d, const IndexType &a) const {
1444 const std::size_t ja = macNode(a);
1445 if (ja != noNode)
1446 return macPlusFace_[d][ja];
1447 IndexType b = a;
1448 b[d] += 1;
1449 const std::size_t jb = macNode(b);
1450 if (jb != noNode)
1451 return macMinusFace_[d][jb];
1452 return -1;
1453 }
1454
1455 // Map face (d, a) into the box: returns the in-box face and the sign of
1456 // the velocity (odd in the normal direction of each reflection).
1457 T macFaceReflect(unsigned d, IndexType &a) const {
1458 T sign = T(1);
1459 for (unsigned e = 0; e < D; ++e) {
1460 if (e == d) {
1461 while (a[e] < minIndex[e] || a[e] > maxIndex[e] - 1) {
1462 if (a[e] < minIndex[e])
1463 a[e] = 2 * minIndex[e] - a[e] - 1;
1464 else
1465 a[e] = 2 * maxIndex[e] - a[e] - 1;
1466 sign = -sign;
1467 }
1468 } else {
1469 while (a[e] < minIndex[e] || a[e] > maxIndex[e]) {
1470 if (a[e] < minIndex[e])
1471 a[e] = 2 * minIndex[e] - a[e];
1472 else
1473 a[e] = 2 * maxIndex[e] - a[e];
1474 }
1475 }
1476 }
1477 return sign;
1478 }
1479
1480 T macPhiFace(unsigned d, IndexType a) const {
1481 IndexType b = a;
1482 b[d] += 1;
1483 return T(0.5) * (macPhiR(a) + macPhiR(b));
1484 }
1485
1486 // Front velocity component d for a Si ghost: the expansion velocity at the
1487 // front crossing between the in-box face (d, a) and the ghost face (d,
1488 // ghost), a fraction theta of the way (the oxidation front's own interface
1489 // point, see OxidationDiffusion::frontVelocityAt); zero for a nitride face
1490 // (weld). Falls back to the mean reaction velocity of the face's oxide
1491 // nodes where no crossing is near.
1492 T macFrontVelocity(unsigned d, const IndexType &a, const IndexType &ghost,
1493 T theta) const {
1494 IndexType b = a;
1495 b[d] += 1;
1496 T sum = T(0);
1497 int count = 0;
1498 bool nitride = false;
1499 for (const auto &q : {a, b}) {
1500 const std::size_t j = macNode(q);
1501 if (j == noNode)
1502 continue;
1503 if (nodes[j].nitride) {
1504 nitride = true;
1505 continue;
1506 }
1507 const auto v = reactionVelocityAt(j);
1508 if (v[0] != T(0) || v[1] != T(0) || (D == 3 && v[2] != T(0))) {
1509 sum += v[d];
1510 ++count;
1511 }
1512 }
1513 if (boundaryCacheValid_ && !(nitride && count == 0)) {
1514 Vec3D<T> p{T(0), T(0), T(0)};
1515 for (unsigned e = 0; e < D; ++e)
1516 p[e] = (static_cast<T>(a[e]) + (e == d ? T(0.5) : T(0)) +
1517 theta * static_cast<T>(ghost[e] - a[e])) *
1518 gridDelta;
1519 const auto front = diffusionField->frontVelocityAt(p);
1520 if (front.found)
1521 return static_cast<T>(reactionSign) *
1522 (oxidationParameters.expansionCoefficient - T(1)) * front.speed *
1523 front.normal[d];
1524 }
1525 if (count > 0)
1526 return sum / static_cast<T>(count);
1527 if (nitride)
1528 return T(0);
1529 for (const auto &q : {a, b}) {
1530 const std::size_t j = macNode(q);
1531 if (j != noNode)
1532 return reactionBoundaryVelocity(nodes[j].index)[d];
1533 }
1534 return T(0);
1535 }
1536
1537 // Velocity component d at face (d, a) as an affine expression of the
1538 // unknowns. partner: the adjacent face (same d, one step along another
1539 // or the same direction) that a ghost is built from.
1540 MacLin macFace(unsigned d, IndexType a, const IndexType *partner) const {
1541 const IndexType unreflected = a;
1542 const T sign = macFaceReflect(d, a);
1543 MacLin r;
1544 const std::int64_t id = macFaceId(d, a);
1545 if (id >= 0) {
1546 r.t.emplace_back(id, sign);
1547 return r;
1548 }
1549 // not an unknown: Si ghost, gas ghost, or nothing
1550 const T phiF = macPhiFace(d, a);
1551 if (partner == nullptr)
1552 return r; // zero
1553 IndexType pa = *partner;
1554 const T psign = macFaceReflect(d, pa);
1555 bool inBox = true;
1556 for (unsigned e = 0; e < D; ++e)
1557 inBox = inBox && a[e] == unreflected[e] && pa[e] == (*partner)[e];
1558 const std::int64_t pid = macFaceId(d, pa);
1559 MacLin pe;
1560 if (pid >= 0)
1561 pe.t.emplace_back(pid, psign);
1562 if (phiF >= T(0)) // gas face: zero gradient from the partner
1563 return pe;
1564 const T phiP = macPhiFace(d, pa);
1565 T theta = T(1);
1566 if (phiP > T(0) && phiP - phiF > T(0))
1567 theta = phiP / (phiP - phiF);
1568 const T thMin = std::clamp(
1569 T(deformationParameters.minMechanicsBoundaryDistance), T(0), T(1));
1570 theta = std::clamp(theta, thMin, T(1));
1571 // ghost = (1 - 1/theta) partner + v_front / theta (true orientation)
1572 const T vf =
1573 macGhostFrontVelocity(d, pa, a, theta, inBox ? pid : -1) * psign;
1574 MacLin g = pe.scaled(T(1) - T(1) / theta);
1575 g.c += vf / theta;
1576 return g;
1577 }
1578
1579 // macFrontVelocity(d, pa, a, theta) of a ghost face a next to the
1580 // unknown pid = (d, pa), kept during solveMechanics (see macGhostV_).
1581 // pid < 0 (a reflected face or no unknown partner): not kept.
1582 T macGhostFrontVelocity(unsigned d, const IndexType &pa, const IndexType &a,
1583 T theta, std::int64_t pid) const {
1584 std::size_t slot = macGhostSlots_;
1585 if (macGhostOn_ && pid >= 0) {
1586 int axis = -1, step = 0;
1587 for (unsigned e = 0; e < D; ++e) {
1588 const auto delta = a[e] - pa[e];
1589 if (delta == 0)
1590 continue;
1591 if (axis != -1 || (delta != 1 && delta != -1)) {
1592 axis = -2;
1593 break;
1594 }
1595 axis = static_cast<int>(e);
1596 step = static_cast<int>(delta);
1597 }
1598 if (axis >= 0)
1599 slot = static_cast<std::size_t>(pid) * 2 * D +
1600 2 * static_cast<std::size_t>(axis) + (step > 0 ? 1 : 0);
1601 }
1602 if (slot >= macGhostSlots_)
1603 return macFrontVelocity(d, pa, a, theta);
1604 auto &state = macGhostState_[slot];
1605 if (state.load(std::memory_order_acquire) == 2)
1606 return macGhostV_[slot];
1607 const T v = macFrontVelocity(d, pa, a, theta);
1608 std::uint8_t empty = 0;
1609 if (state.compare_exchange_strong(empty, 1, std::memory_order_acq_rel)) {
1610 macGhostV_[slot] = v;
1611 state.store(2, std::memory_order_release);
1612 }
1613 return v;
1614 }
1615
1616 // Empty ghost front velocities for this solve's node set and boundary
1617 // values; on until the solve ends (macGhostOn_).
1619 const std::size_t slots = static_cast<std::size_t>(macNVel_) * 2 * D;
1620 if (slots != macGhostSlots_) {
1621 macGhostV_.assign(slots, T(0));
1622 macGhostState_ = std::make_unique<std::atomic<std::uint8_t>[]>(slots);
1623 macGhostSlots_ = slots;
1624 }
1625 for (std::size_t k = 0; k < slots; ++k)
1626 macGhostState_[k].store(0, std::memory_order_relaxed);
1627 macGhostOn_ = true;
1628 }
1629
1630 // dv_d/dx_d at grid point q (faces (d, q - e_d), (d, q)).
1631 MacLin macNormalRate(unsigned d, const IndexType &q) const {
1632 IndexType lo = q;
1633 lo[d] -= 1;
1634 const IndexType hi = q;
1635 MacLin r = macFace(d, hi, &lo);
1636 r.add(macFace(d, lo, &hi), T(-1));
1637 return r.scaled(T(1) / gridDelta);
1638 }
1639
1640 // dv_c/dx_e + dv_e/dx_c at corner (ce, a).
1641 MacLin macShearRate(unsigned c, unsigned e, const IndexType &a) const {
1642 IndexType ae = a, ac = a;
1643 ae[e] += 1;
1644 ac[c] += 1;
1645 MacLin r = macFace(c, ae, &a);
1646 r.add(macFace(c, a, &ae), T(-1));
1647 r.add(macFace(e, ac, &a));
1648 r.add(macFace(e, a, &ac), T(-1));
1649 return r.scaled(T(1) / gridDelta);
1650 }
1651
1652 std::int64_t macCornerLookup(unsigned pair, const IndexType &a) const {
1653 const auto &m = macCornerId_[pair];
1654 const auto it = m.find(linearIndex(a));
1655 return it == m.end() ? -1 : it->second;
1656 }
1657
1658 // Corner (pair, a) mapped into the box: its id (or -1) and the sign of its
1659 // shear stress, odd under each reflection in c or e.
1660 std::int64_t macCornerReflected(unsigned pair, IndexType a, T &sign) const {
1661 unsigned c, e;
1662 macPairDirs(pair, c, e);
1663 sign = T(1);
1664 for (unsigned k : {c, e}) {
1665 while (a[k] < minIndex[k] || a[k] > maxIndex[k] - 1) {
1666 if (a[k] < minIndex[k])
1667 a[k] = 2 * minIndex[k] - a[k] - 1;
1668 else
1669 a[k] = 2 * maxIndex[k] - a[k] - 1;
1670 sign = -sign;
1671 }
1672 }
1673 return macCornerLookup(pair, a);
1674 }
1675
1676 // Inside-positive level set of the oxide (ambient) or the nitride (mask)
1677 // at a grid point; NaN where it is undefined or not cached.
1678 T macPsi(bool nitride, const IndexType &a) const {
1679 const auto &m = nitride ? macPsiM_ : macPsiA_;
1680 const auto it = m.find(linearIndex(macReflect(a)));
1681 return it == m.end() ? std::numeric_limits<T>::quiet_NaN() : it->second;
1682 }
1683
1684 // d psi / d x_e (per cell) at x: the mean of the one-sided differences
1685 // whose two ends are defined; NaN if there is none.
1686 T macPsiSlope(bool nitride, const IndexType &x, unsigned e) const {
1687 const T c = macPsi(nitride, x);
1688 T sum = T(0);
1689 int cnt = 0;
1690 if (std::isfinite(c))
1691 for (int sg : {-1, 1}) {
1692 IndexType y = x;
1693 y[e] += sg;
1694 const T v = macPsi(nitride, y);
1695 if (std::isfinite(v)) {
1696 sum += (v - c) * static_cast<T>(sg);
1697 ++cnt;
1698 }
1699 }
1700 return cnt ? sum / static_cast<T>(cnt)
1701 : std::numeric_limits<T>::quiet_NaN();
1702 }
1703
1704 static std::array<T, 3> macUnitOutward(const std::array<T, 3> &grad) {
1705 T norm = T(0);
1706 for (unsigned e = 0; e < D; ++e)
1707 norm += grad[e] * grad[e];
1708 std::array<T, 3> n{T(0), T(0), T(0)};
1709 if (norm <= T(1e-12))
1710 return n; // degenerate
1711 norm = std::sqrt(norm);
1712 for (unsigned e = 0; e < D; ++e)
1713 n[e] = -grad[e] / norm; // psi grows into the material
1714 return n;
1715 }
1716
1717 // Outward free-surface normal at the crossing between solid point s and
1718 // gas point q (their midpoint), from the material's level set; the axis
1719 // normal if the gradient is degenerate.
1720 std::array<T, 3> macSurfaceNormal(bool nitride, const IndexType &s,
1721 const IndexType &q) const {
1722 unsigned dq = 0;
1723 for (unsigned e = 0; e < D; ++e)
1724 if (q[e] != s[e])
1725 dq = e;
1726 const T step = static_cast<T>(q[dq] - s[dq]);
1727 std::array<T, 3> g{T(0), T(0), T(0)};
1728 g[dq] = (macPsi(nitride, q) - macPsi(nitride, s)) * step;
1729 if (!std::isfinite(g[dq])) {
1730 // the level set is undefined at the gas point: the surface passes
1731 // through the node, crossed along d
1732 g[dq] = step;
1733 return g;
1734 }
1735 for (unsigned e = 0; e < D; ++e) {
1736 if (e == dq)
1737 continue;
1738 T sum = T(0);
1739 int cnt = 0;
1740 for (const auto &x : {s, q}) {
1741 const T slope = macPsiSlope(nitride, x, e);
1742 if (std::isfinite(slope)) {
1743 sum += slope;
1744 ++cnt;
1745 }
1746 }
1747 g[e] = cnt ? sum / static_cast<T>(cnt) : T(0);
1748 }
1749 auto n = macUnitOutward(g);
1750 if (n[0] == T(0) && n[1] == T(0) && n[2] == T(0))
1751 n[dq] = step;
1752 return n;
1753 }
1754
1755 // Outward free-surface normal at corner (ce, a): gradient over its points.
1756 std::array<T, 3> macCornerNormal(bool nitride, unsigned c, unsigned e,
1757 const IndexType &a) const {
1758 // edge differences of the corner square whose two ends are defined
1759 bool defined = true;
1760 auto edges = [&](unsigned along, unsigned across) {
1761 T sum = T(0);
1762 int cnt = 0;
1763 for (int b = 0; b <= 1; ++b) {
1764 IndexType p = a;
1765 p[across] += b;
1766 IndexType q = p;
1767 q[along] += 1;
1768 const T diff = macPsi(nitride, q) - macPsi(nitride, p);
1769 if (std::isfinite(diff)) {
1770 sum += diff;
1771 ++cnt;
1772 }
1773 }
1774 defined = defined && cnt > 0;
1775 return cnt ? sum / static_cast<T>(cnt) : T(0);
1776 };
1777 std::array<T, 3> g{T(0), T(0), T(0)};
1778 g[c] = edges(c, e);
1779 g[e] = edges(e, c);
1780 if (!defined) // too far from the band: no normal (zero shear)
1781 return {T(0), T(0), T(0)};
1782 if constexpr (D == 3) {
1783 const unsigned k = 3 - c - e;
1784 T sum = T(0);
1785 int cnt = 0;
1786 for (int bc = 0; bc <= 1; ++bc)
1787 for (int be = 0; be <= 1; ++be) {
1788 IndexType p = a;
1789 p[c] += bc;
1790 p[e] += be;
1791 const T slope = macPsiSlope(nitride, p, k);
1792 if (std::isfinite(slope)) {
1793 sum += slope;
1794 ++cnt;
1795 }
1796 }
1797 g[k] = cnt ? sum / static_cast<T>(cnt) : T(0);
1798 }
1799 return macUnitOutward(g);
1800 }
1801
1802 // (P S P)_ab with P = I - n n^T: the part of the stress a free surface
1803 // with normal n can carry.
1804 static MacLin macProjected(const std::array<MacLin, 9> &S,
1805 const std::array<T, 3> &n, unsigned a,
1806 unsigned b) {
1807 auto P = [&](unsigned i, unsigned j) {
1808 return (i == j ? T(1) : T(0)) - n[i] * n[j];
1809 };
1810 MacLin r;
1811 for (unsigned c = 0; c < D; ++c)
1812 for (unsigned e = 0; e < D; ++e) {
1813 const T w = P(a, c) * P(e, b);
1814 if (w != T(0))
1815 r.add(S[tensorIndex(c, e)], w);
1816 }
1817 return r;
1818 }
1819
1820 // Cauchy stress at solid grid point q, scaled by 1/eta_ref: normal
1821 // components from the material law, shear the mean of the surrounding
1822 // corners that are not on the surface.
1823 std::array<MacLin, 9> macPointStress(const IndexType &q, T etaRef) const {
1824 std::array<MacLin, 9> S{};
1825 for (unsigned c = 0; c < D; ++c)
1826 S[tensorIndex(c, c)] = macNormalStress(c, q, q, etaRef);
1827 for (unsigned pair = 0; pair < macPairs; ++pair) {
1828 unsigned c, e;
1829 macPairDirs(pair, c, e);
1830 MacLin sum;
1831 int cnt = 0;
1832 for (int dc = -1; dc <= 0; ++dc)
1833 for (int de = -1; de <= 0; ++de) {
1834 IndexType lo = q;
1835 lo[c] += dc;
1836 lo[e] += de;
1837 T sign;
1838 const std::int64_t k = macCornerReflected(pair, lo, sign);
1839 if (k < 0 || macCornerGas_[static_cast<std::size_t>(k)])
1840 continue;
1841 sum.add(macShearStress(c, e, lo, etaRef));
1842 ++cnt;
1843 }
1844 if (cnt > 0)
1845 S[tensorIndex(c, e)] = S[tensorIndex(e, c)] =
1846 sum.scaled(T(1) / static_cast<T>(cnt));
1847 }
1848 return S;
1849 }
1850
1851 // Shear stress sigma_ce at corner (ce, a), scaled by 1/eta_ref. A corner
1852 // on the surface (>= 2 gas points) carries the traction-free shear of its
1853 // solid points plus the residual modulus.
1854 MacLin macShearStress(unsigned c, unsigned e, const IndexType &a,
1855 T etaRef) const {
1856 T sign;
1857 const std::int64_t k = macCornerReflected(macPairIndex(c, e), a, sign);
1858 if (k < 0)
1859 return MacLin{};
1860 const std::size_t kk = static_cast<std::size_t>(k);
1861 MacLin r = macShearRate(c, e, a).scaled(macCornerMod_[kk]);
1862 if (!macCornerGas_[kk]) {
1863 r.c += sign * macCornerMem_[kk];
1864 return r;
1865 }
1866 const auto n = macCornerNormal(!macCornerOxide_[kk], c, e, a);
1867 if (n[0] == T(0) && n[1] == T(0) && n[2] == T(0))
1868 return r;
1869 MacLin proj;
1870 int cnt = 0;
1871 for (int bc = 0; bc <= 1; ++bc)
1872 for (int be = 0; be <= 1; ++be) {
1873 IndexType q = a;
1874 q[c] += bc;
1875 q[e] += be;
1876 const auto kind = macKind(q);
1877 if (kind != macOxide && kind != macNitride)
1878 continue;
1879 proj.add(macProjected(macPointStress(q, etaRef), n, c, e));
1880 ++cnt;
1881 }
1882 if (cnt > 0)
1883 r.add(proj, T(1) / static_cast<T>(cnt));
1884 return r;
1885 }
1886
1887 // Normal stress sigma_dd at grid point q seen from face f whose other end
1888 // is s, scaled by 1/eta_ref.
1889 MacLin macNormalStress(unsigned d, const IndexType &q, const IndexType &s,
1890 T etaRef) const {
1891 const std::uint8_t kq = macKind(q);
1892 const std::size_t js = macNode(s);
1893 auto oxideLike = [&](std::size_t jp, const IndexType &at) {
1894 // -p + 2 eta_impl dv_d/dx_d + memory, properties of node jp
1895 MacLin r = macNormalRate(d, at).scaled(T(2) * nodeImplicitViscosity(jp) /
1896 etaRef);
1897 r.c += macNodeMemory(jp, d) / etaRef;
1898 if (macPid_[jp] >= 0)
1899 r.t.emplace_back(macPid_[jp], T(-1));
1900 return r;
1901 };
1902 auto nitrideLike = [&](std::size_t jp, const IndexType &at) {
1903 // 2 eta_impl dv_d/dx_d + (K dt - 2 eta_impl / 3) div v + memory
1904 T twoEta, volumetric;
1905 nitrideModuli(jp, twoEta, volumetric);
1906 MacLin r = macNormalRate(d, at).scaled(twoEta / etaRef);
1907 for (unsigned c = 0; c < D; ++c)
1908 r.add(macNormalRate(c, at), volumetric / etaRef);
1909 r.c += macNodeMemory(jp, d) / etaRef;
1910 return r;
1911 };
1912 if (kq == macOxide || kq == macNitride) {
1913 const IndexType qr = macReflect(q);
1914 const std::size_t jq = nodeLookupFlat[linearIndex(qr)];
1915 return kq == macOxide ? oxideLike(jq, q) : nitrideLike(jq, q);
1916 }
1917 if (js == noNode)
1918 return MacLin{};
1919 if (kq == macGas) {
1920 // extrapolated from s to its traction-free value (P sigma_s P)_dd at
1921 // the crossing: a free surface carries only tangential stress
1922 unsigned fi = 0;
1923 for (unsigned e = 0; e < D; ++e)
1924 if (q[e] != s[e])
1925 fi = e * 2u + (q[e] > s[e] ? 1u : 0u);
1926 const std::size_t n = nodes.size();
1927 T theta = faceBCDists_[fi * n + js] / gridDelta;
1928 if (faceBCTypes_[fi * n + js] != Boundary::AMBIENT)
1929 theta = T(1);
1930 theta = std::clamp(theta, T(1e-3), T(1));
1931 const auto S = macPointStress(s, etaRef);
1932 const auto normal = macSurfaceNormal(nodes[js].nitride, s, q);
1933 MacLin g = S[tensorIndex(d, d)].scaled(T(1) - T(1) / theta);
1934 g.add(macProjected(S, normal, d, d), T(1) / theta);
1935 return g;
1936 }
1937 // Si point: the node's material law with the ghost velocities
1938 return nodes[js].nitride ? nitrideLike(js, q) : oxideLike(js, q);
1939 }
1940
1941 // Normal memory stress (component d) of node j, scaled: oxide decay S_start,
1942 // nitride sigma_start (Maxwell: decayed deviator, elastic mean stress).
1943 T macNodeMemory(std::size_t j, unsigned d) const {
1944 if (nodes[j].nitride) {
1945 const auto &start = nitrideStartOf(j);
1946 if (!viscousNitride())
1947 return start[tensorIndex(d, d)];
1948 const T decay = nitrideDecayFor(nitrideNodeViscosity(j));
1949 const T mean = (start[0] + start[4] + start[8]) / T(3);
1950 return decay * start[tensorIndex(d, d)] + (T(1) - decay) * mean;
1951 }
1952 return nodeDecay(j) * startStress_[j][tensorIndex(d, d)];
1953 }
1954
1955 // Number faces, pressures and corners for the current node set.
1956 void macBuild() {
1957 const std::size_t n = nodes.size();
1958 for (unsigned d = 0; d < D; ++d) {
1959 macPlusFace_[d].assign(n, -1);
1960 macMinusFace_[d].assign(n, -1);
1961 }
1962 // phi_R, psi_A, psi_M on the nodes and their neighbours (two steps)
1963 macPhiR_.clear();
1964 macPsiA_.clear();
1965 macPsiM_.clear();
1966 {
1967 ConstSparseIterator reactionIt(reactionInterface->getDomain());
1968 ConstSparseIterator ambientIt(ambientInterface->getDomain());
1969 ConstSparseIterator maskIt = makeMaskIterator();
1970 auto put = [&](const IndexType &q) {
1971 if (!inBounds(q))
1972 return;
1973 const std::size_t li = linearIndex(q);
1974 if (macPhiR_.count(li))
1975 return;
1976 macPhiR_[li] = static_cast<T>(reactionSign) * valueAt(reactionIt, q);
1977 auto defined = [&](ConstSparseIterator &it) {
1978 it.goToIndices(q);
1979 return it.isDefined() ? it.getValue()
1980 : std::numeric_limits<T>::quiet_NaN();
1981 };
1982 macPsiA_[li] = static_cast<T>(ambientSign) * defined(ambientIt);
1983 if (maskInterface != nullptr)
1984 macPsiM_[li] = static_cast<T>(maskSign) * defined(maskIt);
1985 };
1986 for (std::size_t i = 0; i < n; ++i) {
1987 unsigned span = 1;
1988 for (unsigned d = 0; d < D; ++d)
1989 span *= 5;
1990 for (unsigned code = 0; code < span; ++code) {
1991 IndexType q = nodes[i].index;
1992 unsigned rest = code;
1993 for (unsigned d = 0; d < D; ++d) {
1994 q[d] += static_cast<int>(rest % 5) - 2;
1995 rest /= 5;
1996 }
1997 put(q);
1998 }
1999 }
2000 }
2001 // faces: unknown if a solid end point and the midpoint on the oxide side
2002 std::int64_t count = 0;
2003 for (unsigned d = 0; d < D; ++d) {
2004 for (std::size_t i = 0; i < n; ++i) {
2005 for (int side = 0; side < 2; ++side) {
2006 IndexType a = nodes[i].index;
2007 if (side == 0)
2008 a[d] -= 1; // face (d, node - e_d)
2009 IndexType b = a;
2010 b[d] += 1;
2011 if (a[d] < minIndex[d] || b[d] > maxIndex[d])
2012 continue; // mirror face
2013 auto &slot = side == 0 ? macMinusFace_[d][i] : macPlusFace_[d][i];
2014 if (slot >= 0)
2015 continue;
2016 // shared with the other end point if that is a node
2017 const std::size_t other = macNode(side == 0 ? a : b);
2018 std::int64_t existing = -1;
2019 if (other != noNode)
2020 existing =
2021 side == 0 ? macPlusFace_[d][other] : macMinusFace_[d][other];
2022 if (existing >= 0) {
2023 slot = existing;
2024 continue;
2025 }
2026 if (macPhiFace(d, a) < T(0))
2027 continue; // midpoint in Si
2028 slot = count++;
2029 if (other != noNode) {
2030 if (side == 0)
2031 macPlusFace_[d][other] = slot;
2032 else
2033 macMinusFace_[d][other] = slot;
2034 }
2035 }
2036 }
2037 }
2038 macNVel_ = count;
2039 macPid_.assign(n, -1);
2040 bool gasContact = false;
2041 bool hasNitride = false;
2042 for (std::size_t i = 0; i < n; ++i) {
2043 if (nodes[i].nitride) {
2044 hasNitride = true;
2045 continue;
2046 }
2047 macPid_[i] = count++;
2048 for (unsigned fi = 0; fi < 2 * D; ++fi)
2049 gasContact =
2050 gasContact || faceBCTypes_[fi * n + i] == Boundary::AMBIENT;
2051 }
2052 macN_ = count;
2053 macPinned_ = !gasContact && !hasNitride; // closed oxide: pressure gauge
2054 // corners: every corner with a solid point
2055 macCornerId_.assign(macPairs, {});
2056 macCorners_.clear();
2057 for (std::size_t i = 0; i < n; ++i)
2058 for (unsigned pair = 0; pair < macPairs; ++pair) {
2059 unsigned c, e;
2060 macPairDirs(pair, c, e);
2061 for (int dc = -1; dc <= 0; ++dc)
2062 for (int de = -1; de <= 0; ++de) {
2063 IndexType lo = nodes[i].index;
2064 lo[c] += dc;
2065 lo[e] += de;
2066 if (lo[c] < minIndex[c] || lo[c] > maxIndex[c] - 1 ||
2067 lo[e] < minIndex[e] || lo[e] > maxIndex[e] - 1)
2068 continue;
2069 auto &m = macCornerId_[pair];
2070 const std::size_t li = linearIndex(lo);
2071 if (m.count(li))
2072 continue;
2073 m[li] = static_cast<std::int64_t>(macCorners_.size());
2074 macCorners_.emplace_back(pair, lo);
2075 }
2076 }
2077 const std::size_t nk = macCorners_.size();
2078 macCornerGas_.assign(nk, 0);
2079 macCornerOxide_.assign(nk, 0);
2080 macCornerStartV_.assign(nk, T(0));
2081 macCornerEndV_.assign(nk, T(0));
2082 macCornerMod_.assign(nk, T(0));
2083 macCornerMem_.assign(nk, T(0));
2084 macCornerEta_.assign(nk, T(deformationParameters.viscosity));
2085 macCornerEtaN_.assign(nk, T(deformationParameters.nitrideViscosity));
2086 for (std::size_t k = 0; k < nk; ++k) {
2087 unsigned c, e;
2088 macPairDirs(macCorners_[k].first, c, e);
2089 const IndexType &lo = macCorners_[k].second;
2090 int oxide = 0, nitride = 0, gas = 0;
2091 for (int bc = 0; bc <= 1; ++bc)
2092 for (int be = 0; be <= 1; ++be) {
2093 IndexType q = lo;
2094 q[c] += bc;
2095 q[e] += be;
2096 const auto kind = macKind(q);
2097 gas += kind == macGas;
2098 oxide += kind == macOxide;
2099 nitride += kind == macNitride;
2100 }
2101 macCornerGas_[k] = gas >= 2;
2102 macCornerOxide_[k] = oxide >= nitride && oxide > 0;
2103 }
2104 macX_.assign(static_cast<std::size_t>(macN_), T(0));
2106 }
2107 bool macPinned_ = false;
2108 bool couplingIterate_ = false;
2109 // Node set version of the last converged viscosities (warm start).
2110 std::size_t macWarmVersion_ = std::numeric_limits<std::size_t>::max();
2111
2112 // Corner start stresses: the carried history, else the mean of the
2113 // surrounding nodes' stored shear.
2115 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
2116 const unsigned pair = macCorners_[k].first;
2117 unsigned c, e;
2118 macPairDirs(pair, c, e);
2119 const IndexType &lo = macCorners_[k].second;
2120 const auto it = macCornerStart_.find(lo);
2121 if (it != macCornerStart_.end() && macHasHistory(it->second[pair])) {
2122 macCornerStartV_[k] = it->second[pair];
2123 continue;
2124 }
2125 T sum = T(0);
2126 int cnt = 0;
2127 for (int bc = 0; bc <= 1; ++bc)
2128 for (int be = 0; be <= 1; ++be) {
2129 IndexType q = lo;
2130 q[c] += bc;
2131 q[e] += be;
2132 const std::size_t j = macNode(q);
2133 if (j == noNode)
2134 continue;
2135 sum += nodes[j].nitride ? nitrideStartOf(j)[tensorIndex(c, e)]
2136 : startStress_[j][tensorIndex(c, e)];
2137 ++cnt;
2138 }
2139 macCornerStartV_[k] = cnt ? sum / static_cast<T>(cnt) : T(0);
2140 }
2141 }
2142
2143 T macCornerDecay(std::size_t k) const {
2144 const T tau0 = effectiveStressRelaxationTime();
2145 if (tau0 <= std::numeric_limits<T>::epsilon())
2146 return T(0);
2147 const T tau = tau0 * macCornerEta_[k] / T(deformationParameters.viscosity);
2148 return std::exp(-T(deformationParameters.stressTimeStep) / tau);
2149 }
2150
2151 // Corner modulus and memory (scaled by 1/eta_ref) for the current viscosity.
2153#pragma omp parallel for schedule(static)
2154 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
2155 unsigned c, e;
2156 macPairDirs(macCorners_[k].first, c, e);
2157 const IndexType &lo = macCorners_[k].second;
2158 const T decay = macCornerDecay(k);
2159 const T etaImpl = std::max((T(1) - decay) * macCornerEta_[k],
2160 std::numeric_limits<T>::min() * T(1e10));
2161 const T etaImplN = nitrideImplicitFor(macCornerEtaN_[k]);
2162 T inv = T(0);
2163 int cnt = 0;
2164 for (int bc = 0; bc <= 1; ++bc)
2165 for (int be = 0; be <= 1; ++be) {
2166 IndexType q = lo;
2167 q[c] += bc;
2168 q[e] += be;
2169 const auto kind = macKind(q);
2170 if (kind == macOxide) {
2171 inv += T(1) / etaImpl;
2172 ++cnt;
2173 } else if (kind == macNitride) {
2174 inv += T(1) / etaImplN;
2175 ++cnt;
2176 }
2177 }
2178 macCornerMod_[k] = cnt ? static_cast<T>(cnt) / inv / etaRef : T(0);
2179 if (macCornerGas_[k]) { // on the surface: residual modulus, no memory
2180 macCornerMod_[k] *= T(1e-3);
2181 macCornerMem_[k] = T(0);
2182 continue;
2183 }
2184 macCornerMem_[k] =
2186 macCornerStartV_[k] / etaRef;
2187 }
2188 }
2189
2190 // Assemble and solve the linear MAC system for the current viscosity.
2191 bool macSolveLinear(T etaRef, T &backward) {
2192 macCornerCoefficients(etaRef);
2193 const std::size_t n = nodes.size();
2194 const std::int64_t N = macN_;
2195 std::vector<MacLin> rows(static_cast<std::size_t>(N));
2196 // momentum rows: one per face unknown (visit from the node owning it)
2197#pragma omp parallel for schedule(dynamic, 64)
2198 for (std::size_t i = 0; i < n; ++i) {
2199 for (unsigned d = 0; d < D; ++d)
2200 for (int side = 0; side < 2; ++side) {
2201 const std::int64_t id =
2202 side == 0 ? macMinusFace_[d][i] : macPlusFace_[d][i];
2203 if (id < 0)
2204 continue;
2205 IndexType a = nodes[i].index;
2206 if (side == 0)
2207 a[d] -= 1;
2208 // the face's owner: the node at a if it is one, else this node
2209 const std::size_t ja = macNode(a);
2210 if (side == 0 && ja != noNode)
2211 continue; // visited from a
2212 IndexType b = a;
2213 b[d] += 1;
2214 MacLin r = macNormalStress(d, b, a, etaRef);
2215 r.add(macNormalStress(d, a, b, etaRef), T(-1));
2216 for (unsigned e = 0; e < D; ++e) {
2217 if (e == d)
2218 continue;
2219 IndexType lo = a;
2220 lo[e] -= 1;
2221 r.add(macShearStress(d, e, a, etaRef));
2222 r.add(macShearStress(d, e, lo, etaRef), T(-1));
2223 }
2224 rows[static_cast<std::size_t>(id)] = r.scaled(T(1) / gridDelta);
2225 }
2226 }
2227 // continuity rows
2228#pragma omp parallel for schedule(static)
2229 for (std::size_t i = 0; i < n; ++i) {
2230 const std::int64_t pid = macPid_[i];
2231 if (pid < 0)
2232 continue;
2233 MacLin r;
2234 for (unsigned d = 0; d < D; ++d)
2235 r.add(macNormalRate(d, nodes[i].index));
2236 rows[static_cast<std::size_t>(pid)] = std::move(r);
2237 }
2238 if (macPinned_) { // closed oxide: gauge at the first oxide node
2239 for (std::size_t i = 0; i < n; ++i)
2240 if (macPid_[i] >= 0) {
2241 MacLin g;
2242 g.t.emplace_back(macPid_[i], T(1));
2243 g.c = -T(deformationParameters.ambientPressure) / etaRef;
2244 rows[static_cast<std::size_t>(macPid_[i])] = g;
2245 break;
2246 }
2247 }
2248 if constexpr (D == 3)
2249 return macAmgSolve(rows, backward); // consumes the rows
2250 else
2251 return macDirectSolve(rows, backward);
2252 }
2253
2254 // AMGCL: FGMRES, block-triangular velocity-pressure preconditioner (one
2255 // smoothed-aggregation V-cycle with ILU0 for the velocities, SPAI0 on
2256 // B diag(A)^-1 G for the pressure), row/column-equilibrated system,
2257 // warm-started from the previous iterate. The preconditioner works in
2258 // single precision (half the memory traffic of its V-cycles); FGMRES and
2259 // its residual stay double, so the solve meets the same tolerance.
2260 typedef amgcl::backend::builtin<double> MacAmgBackend;
2261 typedef amgcl::backend::builtin<float> MacAmgPrecondBackend;
2262 typedef amgcl::make_solver<
2263 amgcl::amg<MacAmgPrecondBackend, amgcl::coarsening::smoothed_aggregation,
2264 amgcl::relaxation::ilu0>,
2265 amgcl::solver::preonly<MacAmgPrecondBackend>>
2267 typedef amgcl::make_solver<
2268 amgcl::relaxation::as_preconditioner<MacAmgPrecondBackend,
2269 amgcl::relaxation::spai0>,
2270 amgcl::solver::preonly<MacAmgPrecondBackend>>
2272 typedef amgcl::preconditioner::schur_pressure_correction<MacAmgUSolver,
2275 typedef amgcl::solver::fgmres<MacAmgBackend> MacAmgKrylov;
2276
2277 // The 3-D solve runs on the GPU from this many unknowns (with the GPU
2278 // mode). Per solve on a 3-D LOCOS corner, CPU (10 threads) / GPU: 15.6k
2279 // unknowns 0.11 / 0.11 s, 59k 0.47 / 0.17 s, 360k 3.9 / 0.40 s; the
2280 // setup stays on the host.
2281 static constexpr std::size_t macGpuMinUnknowns = 20000;
2283 bool macGpuOff_ = false; // Auto mode after the GPU was unavailable
2284 void *macGpu_ = nullptr; // GPU setup (ViennaLS_GPU), owned
2285
2286 // The preconditioner of the last setup and the equilibration it was built
2287 // for. The next solve on the same node set (a stress-coupling iteration,
2288 // the converged solve, a CFL re-solve) changes only the viscosities and
2289 // the right-hand side: it reuses them, which saves the setup (~15 % of a
2290 // 3-D LOCOS solve) at the same iterations. A reused solve may take twice
2291 // the iterations of the solve after the setup (+20); beyond that, or when
2292 // it fails, a fresh setup solves it.
2293 std::unique_ptr<MacAmgPrecond> macAmgP_;
2294 std::unique_ptr<MacAmgKrylov> macAmgS_;
2295 std::size_t macAmgVersion_ = std::numeric_limits<std::size_t>::max();
2296 std::vector<double> macAmgRs_, macAmgCs_;
2297 std::size_t macAmgSetupIters_ = 0;
2298 // The CSR system and its equilibrated copy, kept between solves: on one
2299 // node set their sizes do not change, so a solve neither reallocates nor
2300 // zero-fills ~10^7 entries.
2301 std::vector<ptrdiff_t> macCsrPtr_, macCsrCol_;
2303
2304 bool macAmgSolve(std::vector<MacLin> &rows, T &backward) {
2305 // CSR, per row the columns sorted and duplicates summed, in two parallel
2306 // passes: sort each row in place and count its entries, then sum them
2307 // again into place (and free the row) -- the matrix of a serial build.
2308 const std::size_t N = rows.size();
2309 auto &ptr = macCsrPtr_, &col = macCsrCol_;
2310 auto &val = macCsrVal_, &rhs = macCsrRhs_;
2311 ptr.resize(N + 1);
2312 rhs.resize(N);
2313 ptr[0] = 0;
2314 const auto byColumn = [](const auto &a, const auto &b) {
2315 return a.first < b.first;
2316 };
2317#pragma omp parallel for schedule(dynamic, 256)
2318 for (std::size_t i = 0; i < N; ++i) {
2319 auto &t = rows[i].t;
2320 std::sort(t.begin(), t.end(), byColumn);
2321 ptrdiff_t count = 0;
2322 for (std::size_t q = 0; q < t.size();) {
2323 const std::int64_t c = t[q].first;
2324 double v = 0.;
2325 for (; q < t.size() && t[q].first == c; ++q)
2326 v += static_cast<double>(t[q].second);
2327 if (v != 0.)
2328 ++count;
2329 }
2330 ptr[i + 1] = count;
2331 rhs[i] = -static_cast<double>(rows[i].c);
2332 }
2333 for (std::size_t i = 0; i < N; ++i)
2334 ptr[i + 1] += ptr[i];
2335 col.resize(static_cast<std::size_t>(ptr[N]));
2336 val.resize(static_cast<std::size_t>(ptr[N]));
2337#pragma omp parallel for schedule(dynamic, 256)
2338 for (std::size_t i = 0; i < N; ++i) {
2339 auto &t = rows[i].t;
2340 auto k = static_cast<std::size_t>(ptr[i]);
2341 for (std::size_t q = 0; q < t.size();) {
2342 const std::int64_t c = t[q].first;
2343 double v = 0.;
2344 for (; q < t.size() && t[q].first == c; ++q)
2345 v += static_cast<double>(t[q].second);
2346 if (v != 0.) {
2347 col[k] = static_cast<ptrdiff_t>(c);
2348 val[k++] = v;
2349 }
2350 }
2351 std::vector<std::pair<std::int64_t, T>>().swap(t);
2352 }
2354 macAmgRs_.size() == N &&
2355 macAmgSolveWith(ptr, col, val, rhs, false, backward))
2356 return true;
2357 return macAmgSolveWith(ptr, col, val, rhs, true, backward);
2358 }
2359
2360 // FGMRES target (relative residual of the equilibrated system): 1e-9 for
2361 // the velocities the interfaces move with; 1e-7 for the solves inside the
2362 // stress-coupling iteration, whose pressure and stress only feed the
2363 // coupling test (couplingTolerance, 2e-2..5e-2). 1e-7 is 10x below the
2364 // acceptance (stokesTolerance) and saves 40 % of their iterations; on a
2365 // 3-D LOCOS corner it changed the result ~400x less than tightening the
2366 // coupling test from 2e-2 to 2e-3 does.
2367 static constexpr double macTolerance = 1e-9;
2368 static constexpr double macCouplingTolerance = 1e-7;
2369
2370 // One solve of the assembled system, with a fresh setup or with the kept
2371 // preconditioner and equilibration.
2372 bool macAmgSolveWith(const std::vector<ptrdiff_t> &ptr,
2373 const std::vector<ptrdiff_t> &col,
2374 const std::vector<double> &val,
2375 const std::vector<double> &rhs, bool fresh,
2376 T &backward) {
2377 const std::size_t N = rhs.size();
2378 if (fresh) {
2379 macAmgReset(); // the old hierarchy goes before the new one is built
2380 macAmgRs_.assign(N, 0.);
2381 macAmgCs_.assign(N, 0.);
2382 auto &rs = macAmgRs_, &cs = macAmgCs_;
2383 for (std::size_t i = 0; i < N; ++i)
2384 for (ptrdiff_t q = ptr[i]; q < ptr[i + 1]; ++q)
2385 rs[i] = std::max(rs[i], std::abs(val[q]));
2386 for (auto &v : rs)
2387 v = v > 0. ? 1. / v : 1.;
2388 for (std::size_t i = 0; i < N; ++i)
2389 for (ptrdiff_t q = ptr[i]; q < ptr[i + 1]; ++q)
2390 cs[col[q]] = std::max(cs[col[q]], std::abs(rs[i] * val[q]));
2391 for (auto &v : cs)
2392 v = v > 0. ? 1. / v : 1.;
2393 }
2394 const auto &rs = macAmgRs_, &cs = macAmgCs_;
2395 auto &sval = macSval_, &srhs = macSrhs_, &y = macY_;
2396 sval.resize(val.size());
2397 srhs.resize(N);
2398 y.resize(N);
2399#pragma omp parallel for schedule(static)
2400 for (std::size_t i = 0; i < N; ++i) {
2401 for (ptrdiff_t q = ptr[i]; q < ptr[i + 1]; ++q)
2402 sval[q] = rs[i] * val[q] * cs[col[q]];
2403 srhs[i] = rs[i] * rhs[i];
2404 y[i] = i < macX_.size() && std::isfinite(macX_[i])
2405 ? static_cast<double>(macX_[i]) / cs[i]
2406 : 0.;
2407 }
2408 const ptrdiff_t n = static_cast<ptrdiff_t>(N);
2409 auto A = std::tie(n, ptr, col, sval);
2410 std::size_t iters = 0;
2411 double err = 0.;
2412 const unsigned maxIterations =
2413 fresh ? 3000u : static_cast<unsigned>(2 * macAmgSetupIters_ + 20);
2414 const double target =
2416 if (fresh) {
2417 if (!macAmgSetup(ptr, col, sval, cs))
2418 return false;
2420 }
2421#ifdef VIENNALS_GPU_BICGSTAB
2422 if (macGpu_) {
2423 static_assert(sizeof(ptrdiff_t) == sizeof(std::int64_t));
2424 unsigned gpuIters = 0;
2425 if (!gpu::gpuMacSolve(
2426 macGpu_, n, reinterpret_cast<const std::int64_t *>(ptr.data()),
2427 reinterpret_cast<const std::int64_t *>(col.data()), sval.data(),
2428 srhs.data(), y.data(), maxIterations, target, gpuIters, err)) {
2429 // a GPU that set up and then fails is a malfunction, not an absence
2430 VIENNACORE_LOG_ERROR(
2431 std::string(
2432 "OxidationDeformation: the GPU mechanics solve failed: ") +
2433 gpu::gpuGetLastErrorMessage());
2434 return false;
2435 }
2436 iters = gpuIters;
2437 } else
2438#endif
2439 {
2440 try {
2441 macAmgS_->prm.maxiter = maxIterations;
2442 macAmgS_->prm.tol = target;
2443 std::tie(iters, err) = (*macAmgS_)(A, *macAmgP_, srhs, y);
2444 } catch (const std::exception &e) {
2445 macAmgReset();
2446 VIENNACORE_LOG_WARNING(
2447 std::string("OxidationDeformation: AMGCL failed: ") + e.what());
2448 return false;
2449 }
2450 }
2451 if (fresh)
2452 macAmgSetupIters_ = iters;
2453 lastAmgIters_ = static_cast<unsigned>(iters);
2454 std::vector<double> x(N);
2455 for (std::size_t i = 0; i < N; ++i)
2456 x[i] = cs[i] * y[i];
2457 for (std::size_t i = 0; i < N; ++i)
2458 if (!std::isfinite(x[i]))
2459 return false;
2460 // the relative residual: a componentwise backward error is ~1 on rows
2461 // whose data is zero, however good the iterative solution
2462 backward = static_cast<T>(err);
2463 if (!(err < 1e-6)) {
2464 if (fresh)
2465 VIENNACORE_LOG_WARNING(
2466 "OxidationDeformation: AMGCL did not converge (iters=" +
2467 std::to_string(iters) + ", residual=" + std::to_string(err) + ")");
2468 return false;
2469 }
2470 macX_.assign(N, T(0));
2471 for (std::size_t i = 0; i < N; ++i)
2472 macX_[i] = static_cast<T>(x[i]);
2473 return true;
2474 }
2475 unsigned lastAmgIters_ = 0;
2476
2478 macAmgP_.reset();
2479 macAmgS_.reset();
2480#ifdef VIENNALS_GPU_BICGSTAB
2481 gpu::gpuMacFree(macGpu_);
2482#endif
2483 macGpu_ = nullptr;
2484 }
2485
2486 // A fresh preconditioner for the equilibrated system: on the GPU with the
2487 // GPU mode and at least macGpuMinUnknowns unknowns, else on the CPU.
2488 bool macAmgSetup(const std::vector<ptrdiff_t> &ptr,
2489 const std::vector<ptrdiff_t> &col,
2490 const std::vector<double> &sval,
2491 const std::vector<double> &cs) {
2492 const std::size_t N = cs.size();
2493 // Near nullspace of the velocity block: one translation per component
2494 // (in the equilibrated unknowns). AMGCL's default, the constant vector,
2495 // moves all components of an aggregate together, and the aggregates mix
2496 // components: 8x the iterations on 3-D LOCOS.
2497 std::vector<double> B(static_cast<std::size_t>(macNVel_) * D, 0.);
2498 for (std::size_t i = 0; i < nodes.size(); ++i)
2499 for (unsigned d = 0; d < D; ++d)
2500 for (const std::int64_t id : {macMinusFace_[d][i], macPlusFace_[d][i]})
2501 if (id >= 0)
2502 B[static_cast<std::size_t>(id) * D + d] = 1. / cs[id];
2503#ifdef VIENNALS_GPU_BICGSTAB
2505 macGpu_ = gpu::gpuMacSetup(
2506 static_cast<std::int64_t>(N),
2507 reinterpret_cast<const std::int64_t *>(ptr.data()),
2508 reinterpret_cast<const std::int64_t *>(col.data()), sval.data(),
2509 static_cast<std::int64_t>(macNVel_), static_cast<int>(D), B.data());
2510 if (macGpu_)
2511 return true;
2512 const std::string message =
2513 std::string(
2514 "OxidationDeformation: GPU mode was selected, but the GPU "
2515 "mechanics setup failed: ") +
2516 gpu::gpuGetLastErrorMessage();
2517 if (gpuMode_ == GpuMode::Gpu) {
2518 VIENNACORE_LOG_ERROR(message);
2519 return false;
2520 }
2521 VIENNACORE_LOG_WARNING(message + " Falling back to the CPU solver.");
2522 macGpuOff_ = true;
2523 }
2524#endif
2525 try {
2526 typename MacAmgPrecond::params prm;
2527 prm.pmask.assign(N, 0);
2528 for (std::size_t i = static_cast<std::size_t>(macNVel_); i < N; ++i)
2529 prm.pmask[i] = 1;
2530 prm.type = 2;
2531 prm.approx_schur = true;
2532 prm.adjust_p = 2;
2533 auto &nullspace = prm.usolver.precond.coarsening.nullspace;
2534 nullspace.cols = static_cast<int>(D);
2535 nullspace.B = std::move(B);
2536 const ptrdiff_t n = static_cast<ptrdiff_t>(N);
2537 macAmgP_ =
2538 std::make_unique<MacAmgPrecond>(std::tie(n, ptr, col, sval), prm);
2539 typename MacAmgKrylov::params sprm; // tol: set per solve
2540 sprm.M = 80;
2541 macAmgS_ = std::make_unique<MacAmgKrylov>(N, sprm);
2542 } catch (const std::exception &e) {
2543 macAmgReset();
2544 VIENNACORE_LOG_WARNING(
2545 std::string("OxidationDeformation: AMGCL failed: ") + e.what());
2546 return false;
2547 }
2548 return true;
2549 }
2550
2551 std::unique_ptr<
2552 Eigen::SparseLU<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>>>
2554 std::size_t macLUVersion_ = std::numeric_limits<std::size_t>::max();
2555 Eigen::Index macLUNnz_ = -1, macLUN_ = -1;
2556 // Equilibration of the system the LU holds (valid with macLUFactored_).
2557 Eigen::VectorXd macLURs_, macLUCs_;
2558 bool macLUFactored_ = false;
2559
2560 // GMRES (right-preconditioned by the last factorisation, warm-started from
2561 // the last solution, <= 30 iterations, no restart) for a system whose
2562 // pattern the LU was factorised for. False when it does not converge, or
2563 // when it fails the componentwise test of lastSolveConverged() (the
2564 // normwise residual can be tiny while rows with small entries are off by
2565 // 1e-3); the fresh factorisation then solves it.
2566 bool macReuseSolve(const Eigen::SparseMatrix<double> &A,
2567 const Eigen::VectorXd &b, T &backward) {
2568 const Eigen::Index N = A.rows();
2569 const auto precondition = [&](const Eigen::VectorXd &v) {
2570 return Eigen::VectorXd(
2571 macLUCs_.asDiagonal() *
2572 Eigen::VectorXd(macLU_->solve(macLURs_.asDiagonal() * v)));
2573 };
2574 Eigen::VectorXd x = Eigen::VectorXd::Zero(N);
2575 if (macX_.size() == static_cast<std::size_t>(N))
2576 for (Eigen::Index k = 0; k < N; ++k)
2577 x[k] = static_cast<double>(macX_[static_cast<std::size_t>(k)]);
2578 const double bnorm = b.norm();
2579 if (!(bnorm > 0.))
2580 return false;
2581 constexpr int m = 30;
2582 constexpr double tol = 1e-10;
2583 Eigen::VectorXd r = b - A * x;
2584 const double beta = r.norm();
2585 if (beta > tol * bnorm) {
2586 std::vector<Eigen::VectorXd> V, Z;
2587 Eigen::MatrixXd H = Eigen::MatrixXd::Zero(m + 1, m);
2588 Eigen::VectorXd g = Eigen::VectorXd::Zero(m + 1);
2589 std::array<double, m> c{}, s{};
2590 V.push_back(r / beta);
2591 g[0] = beta;
2592 int k = 0;
2593 bool converged = false;
2594 for (int j = 0; j < m; ++j) {
2595 Z.push_back(precondition(V[j]));
2596 Eigen::VectorXd w = A * Z[j];
2597 for (int i = 0; i <= j; ++i) {
2598 H(i, j) = w.dot(V[i]);
2599 w -= H(i, j) * V[i];
2600 }
2601 const double hNext = w.norm();
2602 H(j + 1, j) = hNext;
2603 for (int i = 0; i < j; ++i) {
2604 const double t = c[i] * H(i, j) + s[i] * H(i + 1, j);
2605 H(i + 1, j) = -s[i] * H(i, j) + c[i] * H(i + 1, j);
2606 H(i, j) = t;
2607 }
2608 const double den = std::hypot(H(j, j), H(j + 1, j));
2609 if (!(den > 0.))
2610 return false;
2611 c[j] = H(j, j) / den;
2612 s[j] = H(j + 1, j) / den;
2613 H(j, j) = den;
2614 H(j + 1, j) = 0.;
2615 g[j + 1] = -s[j] * g[j];
2616 g[j] = c[j] * g[j];
2617 k = j + 1;
2618 if (std::abs(g[j + 1]) <= tol * bnorm || hNext == 0.) {
2619 converged = true;
2620 break;
2621 }
2622 V.push_back(w / hNext);
2623 }
2624 if (!converged)
2625 return false;
2626 const Eigen::VectorXd y =
2627 H.topLeftCorner(k, k).triangularView<Eigen::Upper>().solve(g.head(k));
2628 for (int i = 0; i < k; ++i)
2629 x += y[i] * Z[i];
2630 }
2631 const Eigen::VectorXd res = b - A * x;
2632 if (!x.allFinite() || !(res.norm() <= 10. * tol * bnorm))
2633 return false;
2634 const Eigen::VectorXd terms = A.cwiseAbs() * x.cwiseAbs() + b.cwiseAbs();
2635 backward = T(0);
2636 for (Eigen::Index k = 0; k < N; ++k)
2637 if (terms[k] > 0.)
2638 backward =
2639 std::max(backward, static_cast<T>(std::abs(res[k]) / terms[k]));
2640 if (!(backward <= stokesTolerance))
2641 return false;
2642 macX_.assign(static_cast<std::size_t>(N), T(0));
2643 for (Eigen::Index k = 0; k < N; ++k)
2644 macX_[static_cast<std::size_t>(k)] = static_cast<T>(x[k]);
2645 return true;
2646 }
2647
2648 // Sparse LU of the row/column-equilibrated system, two refinement passes.
2649 bool macDirectSolve(const std::vector<MacLin> &rows, T &backward) {
2650 const Eigen::Index N = static_cast<Eigen::Index>(rows.size());
2651 std::vector<Eigen::Triplet<double>> trip;
2652 std::size_t nnz = 0;
2653 for (const auto &r : rows)
2654 nnz += r.t.size();
2655 trip.reserve(nnz);
2656 Eigen::VectorXd rhs(N);
2657 for (Eigen::Index k = 0; k < N; ++k) {
2658 for (const auto &kv : rows[static_cast<std::size_t>(k)].t)
2659 trip.emplace_back(static_cast<int>(k), static_cast<int>(kv.first),
2660 static_cast<double>(kv.second));
2661 rhs[k] = -static_cast<double>(rows[static_cast<std::size_t>(k)].c);
2662 }
2663 Eigen::SparseMatrix<double> A(N, N);
2664 A.setFromTriplets(trip.begin(), trip.end());
2665 A.makeCompressed();
2666 Eigen::VectorXd rs = Eigen::VectorXd::Zero(N),
2667 cs = Eigen::VectorXd::Zero(N);
2668 for (Eigen::Index k = 0; k < A.outerSize(); ++k)
2669 for (Eigen::SparseMatrix<double>::InnerIterator it(A, k); it; ++it)
2670 rs[it.row()] = std::max(rs[it.row()], std::abs(it.value()));
2671 for (Eigen::Index k = 0; k < N; ++k)
2672 rs[k] = rs[k] > 0. ? 1. / rs[k] : 1.;
2673 for (Eigen::Index k = 0; k < A.outerSize(); ++k)
2674 for (Eigen::SparseMatrix<double>::InnerIterator it(A, k); it; ++it)
2675 cs[it.col()] =
2676 std::max(cs[it.col()], std::abs(rs[it.row()] * it.value()));
2677 for (Eigen::Index k = 0; k < N; ++k)
2678 cs[k] = cs[k] > 0. ? 1. / cs[k] : 1.;
2679 Eigen::SparseMatrix<double> As = rs.asDiagonal() * A * cs.asDiagonal();
2680 As.makeCompressed();
2681 // the pattern is fixed per node set: order and analyse once
2683 macLUNnz_ != As.nonZeros() || macLUN_ != N) {
2684 macLU_ = std::make_unique<Eigen::SparseLU<Eigen::SparseMatrix<double>,
2685 Eigen::COLAMDOrdering<int>>>();
2686 macLU_->analyzePattern(As);
2688 macLUNnz_ = As.nonZeros();
2689 macLUN_ = N;
2690 macLUFactored_ = false;
2691 }
2692 // A coupling iteration on an unchanged node set changes the viscosity and
2693 // the front velocities only: GMRES on the last factorisation.
2694 if (couplingIterate_ && macLUFactored_ && macReuseSolve(A, rhs, backward))
2695 return true;
2696 auto &lu = *macLU_;
2697 lu.factorize(As);
2698 if (lu.info() != Eigen::Success) {
2699 VIENNACORE_LOG_WARNING(
2700 "OxidationDeformation: MAC factorisation failed: " +
2701 lu.lastErrorMessage());
2702 macLUVersion_ = std::numeric_limits<std::size_t>::max();
2703 macLUFactored_ = false;
2704 return false;
2705 }
2706 macLURs_ = rs;
2707 macLUCs_ = cs;
2708 macLUFactored_ = true;
2709 Eigen::VectorXd x =
2710 cs.asDiagonal() * Eigen::VectorXd(lu.solve(rs.asDiagonal() * rhs));
2711 for (int pass = 0; pass < 2; ++pass) {
2712 const Eigen::VectorXd r = rhs - A * x;
2713 x += cs.asDiagonal() * Eigen::VectorXd(lu.solve(rs.asDiagonal() * r));
2714 }
2715 const Eigen::VectorXd res = rhs - A * x;
2716 const Eigen::VectorXd terms = A.cwiseAbs() * x.cwiseAbs() + rhs.cwiseAbs();
2717 backward = T(0);
2718 for (Eigen::Index k = 0; k < N; ++k)
2719 if (terms[k] > 0.)
2720 backward =
2721 std::max(backward, static_cast<T>(std::abs(res[k]) / terms[k]));
2722 if (!x.allFinite())
2723 return false;
2724 macX_.assign(static_cast<std::size_t>(N), T(0));
2725 for (Eigen::Index k = 0; k < N; ++k)
2726 macX_[static_cast<std::size_t>(k)] = static_cast<T>(x[k]);
2727 return true;
2728 }
2729
2730 T macEval(const MacLin &e) const {
2731 T v = e.c;
2732 for (const auto &kv : e.t)
2733 v += kv.second * macX_[static_cast<std::size_t>(kv.first)];
2734 return v;
2735 }
2736
2737 // Node strain-rate tensor (normal rates at the node, shear rates the mean
2738 // of the surrounding corners) and corner shear rates.
2739 void macRates(std::vector<std::array<T, 9>> &nodeRate,
2740 std::vector<T> &cornerRate) const {
2741 const std::size_t n = nodes.size();
2742 nodeRate.assign(n, std::array<T, 9>{});
2743 cornerRate.assign(macCorners_.size(), T(0));
2744#pragma omp parallel for schedule(static)
2745 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
2746 unsigned c, e;
2747 macPairDirs(macCorners_[k].first, c, e);
2748 cornerRate[k] = macEval(macShearRate(c, e, macCorners_[k].second));
2749 }
2750#pragma omp parallel for schedule(static)
2751 for (std::size_t i = 0; i < n; ++i) {
2752 auto &R = nodeRate[i];
2753 for (unsigned d = 0; d < D; ++d)
2754 R[tensorIndex(d, d)] = macEval(macNormalRate(d, nodes[i].index));
2755 for (unsigned pair = 0; pair < macPairs; ++pair) {
2756 unsigned c, e;
2757 macPairDirs(pair, c, e);
2758 T sum = T(0);
2759 int cnt = 0;
2760 for (int dc = -1; dc <= 0; ++dc)
2761 for (int de = -1; de <= 0; ++de) {
2762 IndexType lo = nodes[i].index;
2763 lo[c] += dc;
2764 lo[e] += de;
2765 T sign;
2766 const std::int64_t k = macCornerReflected(pair, lo, sign);
2767 if (k < 0)
2768 continue;
2769 sum += sign * cornerRate[static_cast<std::size_t>(k)];
2770 ++cnt;
2771 }
2772 const T g = cnt ? T(0.5) * sum / static_cast<T>(cnt) : T(0);
2773 R[tensorIndex(c, e)] = g;
2774 R[tensorIndex(e, c)] = g;
2775 }
2776 }
2777 }
2778
2779 // Anderson acceleration (history 5) of the viscosity fixed point u = G(u),
2780 // u = log eta at the oxide nodes and corners, G the consistent viscosity
2781 // for the strain rate of the solution with u. Restarted when the
2782 // residual more than doubles; the first step is the relaxed mean. On the
2783 // stiff-nitride LOCOS stack it needs 2.7x fewer iterations than the
2784 // relaxed (1/2) fixed point, with the same result.
2785 std::vector<std::vector<T>> macAAu_, macAAg_;
2786 void macAndersonStep(std::vector<T> &u, const std::vector<T> &g) {
2787 constexpr int m = 5;
2788 const std::size_t L = u.size();
2789 macAAu_.push_back(u);
2790 macAAg_.push_back(g);
2791 if (static_cast<int>(macAAu_.size()) > m + 1) {
2792 macAAu_.erase(macAAu_.begin());
2793 macAAg_.erase(macAAg_.begin());
2794 }
2795 const std::size_t h = macAAu_.size();
2796 // residuals f_j = g_j - u_j; restart if the newest grew much
2797 auto fnorm = [&](std::size_t j) {
2798 T sq = T(0);
2799 for (std::size_t k = 0; k < L; ++k) {
2800 const T f = macAAg_[j][k] - macAAu_[j][k];
2801 sq += f * f;
2802 }
2803 return std::sqrt(sq);
2804 };
2805 if (h >= 2 && fnorm(h - 1) > T(2) * fnorm(h - 2)) {
2806 macAAu_.erase(macAAu_.begin(), macAAu_.end() - 1);
2807 macAAg_.erase(macAAg_.begin(), macAAg_.end() - 1);
2808 }
2809 const std::size_t hh = macAAu_.size();
2810 if (hh < 2) { // plain relaxed step
2811 for (std::size_t k = 0; k < L; ++k)
2812 u[k] = T(0.5) * (u[k] + g[k]);
2813 return;
2814 }
2815 // least squares: min || f_last - sum_j gamma_j (f_last - f_j) ||
2816 const std::size_t q = hh - 1;
2817 std::vector<std::vector<T>> dF(q, std::vector<T>(L));
2818 std::vector<T> fl(L);
2819 for (std::size_t k = 0; k < L; ++k)
2820 fl[k] = macAAg_[q][k] - macAAu_[q][k];
2821 for (std::size_t j = 0; j < q; ++j)
2822 for (std::size_t k = 0; k < L; ++k)
2823 dF[j][k] = fl[k] - (macAAg_[j][k] - macAAu_[j][k]);
2824 std::vector<T> M(q * q, T(0)), rhs(q, T(0)), gamma(q, T(0));
2825 for (std::size_t a = 0; a < q; ++a) {
2826 for (std::size_t b = 0; b < q; ++b) {
2827 T sum = T(0);
2828 for (std::size_t k = 0; k < L; ++k)
2829 sum += dF[a][k] * dF[b][k];
2830 M[a * q + b] = sum;
2831 }
2832 T sum = T(0);
2833 for (std::size_t k = 0; k < L; ++k)
2834 sum += dF[a][k] * fl[k];
2835 rhs[a] = sum;
2836 M[a * q + a] *= T(1) + T(1e-10); // regularise
2837 }
2838 // Gaussian elimination (q <= m small)
2839 for (std::size_t c = 0; c < q; ++c) {
2840 std::size_t piv = c;
2841 for (std::size_t r = c + 1; r < q; ++r)
2842 if (std::abs(M[r * q + c]) > std::abs(M[piv * q + c]))
2843 piv = r;
2844 if (std::abs(M[piv * q + c]) < T(1e-300))
2845 continue;
2846 if (piv != c) {
2847 for (std::size_t k = 0; k < q; ++k)
2848 std::swap(M[c * q + k], M[piv * q + k]);
2849 std::swap(rhs[c], rhs[piv]);
2850 }
2851 for (std::size_t r = c + 1; r < q; ++r) {
2852 const T f = M[r * q + c] / M[c * q + c];
2853 for (std::size_t k = c; k < q; ++k)
2854 M[r * q + k] -= f * M[c * q + k];
2855 rhs[r] -= f * rhs[c];
2856 }
2857 }
2858 for (std::size_t c = q; c-- > 0;) {
2859 T sum = rhs[c];
2860 for (std::size_t k = c + 1; k < q; ++k)
2861 sum -= M[c * q + k] * gamma[k];
2862 gamma[c] = std::abs(M[c * q + c]) > T(1e-300) ? sum / M[c * q + c] : T(0);
2863 }
2864 // u_new = g_last - sum_j gamma_j (g_last - g_j)
2865 for (std::size_t k = 0; k < L; ++k) {
2866 T val = macAAg_[q][k];
2867 for (std::size_t j = 0; j < q; ++j)
2868 val -= gamma[j] * (macAAg_[q][k] - macAAg_[j][k]);
2869 u[k] = val;
2870 }
2871 }
2872
2873 void macUpdateViscosity(const std::vector<std::array<T, 9>> &nodeRate,
2874 const std::vector<T> &cornerRate) {
2875 const bool oxide = stressDependentViscosity();
2876 const bool nitride = stressDependentNitride();
2877 if (!oxide && !nitride)
2878 return;
2879 const std::size_t n = nodes.size();
2880 std::vector<T> locNode(n, T(0)), locCorner(macCorners_.size(), T(0)),
2881 locCornerN(macCorners_.size(), T(0));
2882 // dynamic: the bisection is costly only where the stress is high
2883#pragma omp parallel for schedule(dynamic, 64)
2884 for (std::size_t i = 0; i < n; ++i) {
2885 if (nodes[i].nitride ? !nitride : !oxide)
2886 continue;
2887 std::array<T, 9> start =
2888 nodes[i].nitride ? deviatorOf(nitrideStartOf(i)) : startStress_[i];
2889 for (unsigned pair = 0; pair < macPairs; ++pair) { // corner mean
2890 unsigned c, e;
2891 macPairDirs(pair, c, e);
2892 T sum = T(0);
2893 int cnt = 0;
2894 for (int dc = -1; dc <= 0; ++dc)
2895 for (int de = -1; de <= 0; ++de) {
2896 IndexType lo = nodes[i].index;
2897 lo[c] += dc;
2898 lo[e] += de;
2899 T sign;
2900 const std::int64_t k = macCornerReflected(pair, lo, sign);
2901 if (k < 0)
2902 continue;
2903 sum += sign * macCornerStartV_[static_cast<std::size_t>(k)];
2904 ++cnt;
2905 }
2906 start[tensorIndex(c, e)] = start[tensorIndex(e, c)] =
2907 cnt ? sum / static_cast<T>(cnt) : T(0);
2908 }
2909 const auto &R = nodeRate[i];
2910 T tr = T(0);
2911 for (unsigned d = 0; d < D; ++d)
2912 tr += R[tensorIndex(d, d)];
2913 std::array<T, 9> dev = R;
2914 for (unsigned d = 0; d < 3; ++d)
2915 dev[tensorIndex(d, d)] -= tr / T(3);
2916 locNode[i] = nodes[i].nitride ? localNitrideViscosity(start, dev)
2917 : localViscosity(start, dev);
2918 }
2919 // corner viscosity of one material from its points' normal start stress
2920 // and rate and the corner's shear
2921 auto cornerViscosity = [&](std::size_t k, bool ofNitride) {
2922 unsigned c, e;
2923 macPairDirs(macCorners_[k].first, c, e);
2924 const IndexType &lo = macCorners_[k].second;
2925 std::array<T, 9> start{}, rate{};
2926 int cnt = 0;
2927 for (int bc = 0; bc <= 1; ++bc)
2928 for (int be = 0; be <= 1; ++be) {
2929 IndexType q = lo;
2930 q[c] += bc;
2931 q[e] += be;
2932 const std::size_t j = macNode(q);
2933 if (j == noNode || nodes[j].nitride != ofNitride)
2934 continue;
2935 const auto s =
2936 ofNitride ? deviatorOf(nitrideStartOf(j)) : startStress_[j];
2937 for (unsigned d = 0; d < 3; ++d) {
2938 start[tensorIndex(d, d)] += s[tensorIndex(d, d)];
2939 rate[tensorIndex(d, d)] += nodeRate[j][tensorIndex(d, d)];
2940 }
2941 ++cnt;
2942 }
2943 if (cnt == 0)
2944 return T(0);
2945 for (unsigned d = 0; d < 3; ++d) {
2946 start[tensorIndex(d, d)] /= static_cast<T>(cnt);
2947 rate[tensorIndex(d, d)] /= static_cast<T>(cnt);
2948 }
2949 start[tensorIndex(c, e)] = start[tensorIndex(e, c)] = macCornerStartV_[k];
2950 rate[tensorIndex(c, e)] = rate[tensorIndex(e, c)] =
2951 T(0.5) * cornerRate[k];
2952 T tr = T(0);
2953 for (unsigned d = 0; d < D; ++d)
2954 tr += rate[tensorIndex(d, d)];
2955 for (unsigned d = 0; d < 3; ++d)
2956 rate[tensorIndex(d, d)] -= tr / T(3);
2957 return ofNitride ? localNitrideViscosity(start, rate)
2958 : localViscosity(start, rate);
2959 };
2960#pragma omp parallel for schedule(dynamic, 64)
2961 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
2962 if (macCornerGas_[k])
2963 continue;
2964 if (oxide && macCornerOxide_[k])
2965 locCorner[k] = cornerViscosity(k, false);
2966 if (nitride)
2967 locCornerN[k] = cornerViscosity(k, true);
2968 }
2969 // Anderson-accelerated fixed-point update in log eta
2970 std::vector<T> u, g;
2971 for (std::size_t i = 0; i < n; ++i)
2972 if (locNode[i] > T(0)) {
2973 u.push_back(std::log(nodes[i].nitride ? nitrideEta_[i] : oxideEta_[i]));
2974 g.push_back(std::log(locNode[i]));
2975 }
2976 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
2977 if (locCorner[k] > T(0)) {
2978 u.push_back(std::log(macCornerEta_[k]));
2979 g.push_back(std::log(locCorner[k]));
2980 }
2981 if (locCornerN[k] > T(0)) {
2982 u.push_back(std::log(macCornerEtaN_[k]));
2983 g.push_back(std::log(locCornerN[k]));
2984 }
2985 }
2986 macAndersonStep(u, g);
2987 const T hi = std::log(T(deformationParameters.viscosity));
2988 const T lo = hi + std::log(T(1e-6));
2989 const T hiN = std::log(std::max(T(deformationParameters.nitrideViscosity),
2990 std::numeric_limits<T>::min()));
2991 const T loN = hiN + std::log(T(1e-6));
2992 std::size_t q = 0;
2993 for (std::size_t i = 0; i < n; ++i)
2994 if (locNode[i] > T(0)) {
2995 if (nodes[i].nitride)
2996 nitrideEta_[i] = std::exp(std::clamp(u[q++], loN, hiN));
2997 else
2998 oxideEta_[i] = std::exp(std::clamp(u[q++], lo, hi));
2999 }
3000 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
3001 if (locCorner[k] > T(0))
3002 macCornerEta_[k] = std::exp(std::clamp(u[q++], lo, hi));
3003 if (locCornerN[k] > T(0))
3004 macCornerEtaN_[k] = std::exp(std::clamp(u[q++], loN, hiN));
3005 }
3006 }
3007
3010 initOxideViscosity(); // nodes, from the start stress
3013 const T eta0 = T(deformationParameters.viscosity);
3014 const T etaN0 = T(deformationParameters.nitrideViscosity);
3015 const T oxideVolume = T(deformationParameters.shearActivationVolume);
3016 const T nitrideVolume =
3017 T(deformationParameters.nitrideShearActivationVolume);
3018 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
3019 unsigned c, e;
3020 macPairDirs(macCorners_[k].first, c, e);
3021 std::array<T, 9> s{};
3022 s[tensorIndex(c, e)] = s[tensorIndex(e, c)] = macCornerStartV_[k];
3024 ? eta0 * eyringFactor(s, oxideVolume)
3025 : eta0;
3027 ? etaN0 * eyringFactor(s, nitrideVolume)
3028 : etaN0;
3029 }
3030 macAAu_.clear();
3031 macAAg_.clear();
3032 }
3033
3034 // Write the converged MAC solution into the node arrays the rest of the
3035 // class (output, stress history, level-set velocity) uses.
3036 void macWriteBack(const std::vector<std::array<T, 9>> &nodeRate,
3037 const std::vector<T> &cornerRate, T etaRef) {
3038 const std::size_t n = nodes.size();
3039 if (oxideStressEnd_.size() != n)
3040 oxideStressEnd_.assign(n, std::array<T, 9>{});
3041 oxideStressAdvectedValid_ = false;
3042 // corner end stresses (the stress the momentum used); dynamic: the
3043 // costly surface corners are clustered
3044#pragma omp parallel for schedule(dynamic, 64)
3045 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
3046 unsigned c, e;
3047 macPairDirs(macCorners_[k].first, c, e);
3048 macCornerEndV_[k] =
3049 macCornerGas_[k]
3050 ? macEval(macShearStress(c, e, macCorners_[k].second, etaRef)) *
3051 etaRef
3052 : (macCornerMod_[k] * cornerRate[k] + macCornerMem_[k]) * etaRef;
3053 }
3054#pragma omp parallel for schedule(dynamic, 64)
3055 for (std::size_t i = 0; i < n; ++i) {
3056 auto &node = nodes[i];
3057 for (unsigned d = 0; d < D; ++d) {
3058 IndexType lo = node.index;
3059 lo[d] -= 1;
3060 const IndexType hi = node.index;
3061 node.velocity[d] = T(0.5) * (macEval(macFace(d, lo, &hi)) +
3062 macEval(macFace(d, hi, &lo)));
3063 }
3064 node.pressure = macPid_[i] >= 0
3065 ? macX_[static_cast<std::size_t>(macPid_[i])] * etaRef
3066 : T(0);
3067 node.strainRateTensor = nodeRate[i];
3068 T tr = T(0);
3069 for (unsigned d = 0; d < D; ++d)
3070 tr += nodeRate[i][tensorIndex(d, d)];
3071 node.strainTrace = tr;
3072 // node shear stress: mean of the surrounding corners' end stress
3073 std::array<T, 9> shear{};
3074 for (unsigned pair = 0; pair < macPairs; ++pair) {
3075 unsigned c, e;
3076 macPairDirs(pair, c, e);
3077 T sum = T(0);
3078 int cnt = 0;
3079 for (int dc = -1; dc <= 0; ++dc)
3080 for (int de = -1; de <= 0; ++de) {
3081 IndexType lo = node.index;
3082 lo[c] += dc;
3083 lo[e] += de;
3084 T sign;
3085 const std::int64_t k = macCornerReflected(pair, lo, sign);
3086 if (k < 0)
3087 continue;
3088 sum += sign * macCornerEndV_[static_cast<std::size_t>(k)];
3089 ++cnt;
3090 }
3091 shear[tensorIndex(c, e)] = shear[tensorIndex(e, c)] =
3092 cnt ? sum / static_cast<T>(cnt) : T(0);
3093 }
3094 if (node.nitride) {
3095 computeNitrideStress(i); // normal end stress from the strain rate
3096 auto &end = nitrideStressEnd_[i];
3097 for (unsigned c = 0; c < 3; ++c)
3098 for (unsigned e = 0; e < 3; ++e)
3099 if (c != e)
3100 end[tensorIndex(c, e)] = shear[tensorIndex(c, e)];
3101 node.stressTensor = end;
3102 continue;
3103 }
3104 const T decay = nodeDecay(i);
3105 const T eta = nodeViscosity(i);
3106 const auto &start = startStress_[i];
3107 std::array<T, 9> end = shear;
3108 for (unsigned d = 0; d < 3; ++d)
3109 end[tensorIndex(d, d)] =
3110 decay * start[tensorIndex(d, d)] +
3111 (T(1) - decay) * T(2) * eta *
3112 (nodeRate[i][tensorIndex(d, d)] - tr / T(3));
3113 oxideStressEnd_[i] = end;
3114 node.stressTensor = end;
3115 for (unsigned d = 0; d < 3; ++d)
3116 node.stressTensor[tensorIndex(d, d)] -= node.pressure;
3117 std::array<T, 9> dev = end;
3118 const T mean = (end[0] + end[4] + end[8]) / T(3);
3119 for (unsigned d = 0; d < 3; ++d)
3120 dev[tensorIndex(d, d)] -= mean;
3121 node.vonMisesStress = vonMisesFromDeviatoric(dev);
3122 }
3123 // carried shear history for the next step (advected on write)
3124 macCornerEndMap_.clear();
3125 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
3126 auto &slot = macCornerEndMap_[macCorners_[k].second];
3127 slot[macCorners_[k].first] = macCornerEndV_[k];
3128 }
3130 }
3131
3132 // The mechanics solve: the viscosity iteration of the stress-dependent
3133 // oxide, one exact velocity-pressure solve per iteration.
3135 // node set version: rebuild the numbering when the nodes changed
3136 if (macNodesVersion_ != nodesVersion_ || macPid_.size() != nodes.size())
3137 macBuild();
3138 macGhostReset();
3139 struct GhostOff { // the kept front velocities end with this solve
3140 bool &on;
3141 ~GhostOff() { on = false; }
3142 } ghostOff{macGhostOn_};
3143 nitrideStartNode_.clear();
3144 if (monolithicNitride()) {
3145 nitrideStartNode_.assign(nodes.size(), std::array<T, 9>{});
3146 for (std::size_t i = 0; i < nodes.size(); ++i)
3147 if (nodes[i].nitride)
3148 nitrideStartNode_[i] = nitrideStressStartAt(nodes[i].index);
3149 }
3150 if (nitrideStressEnd_.size() != nodes.size())
3151 nitrideStressEnd_.assign(nodes.size(), std::array<T, 9>{});
3153 // A re-solve on the same node set (the next coupling iteration, a CFL
3154 // re-solve) starts from the last converged viscosities: the forcing
3155 // changes little, so the viscosity iteration needs a few steps instead
3156 // of a restart from the step-start stress.
3157 const bool nonlinear =
3159 if (nonlinear && macWarmVersion_ == macNodesVersion_ &&
3160 oxideEta_.size() == nodes.size() &&
3161 nitrideEta_.size() == nodes.size() &&
3162 macCornerEta_.size() == macCorners_.size()) {
3163 macAAu_.clear();
3164 macAAg_.clear();
3165 } else {
3167 }
3168 if (oxideEta_.size() != nodes.size())
3169 oxideEta_.assign(nodes.size(), T(deformationParameters.viscosity));
3170 if (nitrideEta_.size() != nodes.size())
3171 nitrideEta_.assign(nodes.size(),
3172 T(deformationParameters.nitrideViscosity));
3173 const T etaRef = implicitViscosity();
3174 std::vector<std::array<T, 9>> nodeRate;
3175 std::vector<T> cornerRate;
3176 std::vector<T> previous;
3177 T change = std::numeric_limits<T>::infinity();
3178 T backward = T(0);
3179 unsigned it = 0;
3180 const unsigned maxIt =
3181 std::max(1u, deformationParameters.mechanicsIterations);
3182 const bool onePass = couplingIterate_ && nonlinear;
3183 bool ok = true;
3184 for (it = 1; it <= maxIt; ++it) {
3185 if (it > 1)
3186 macUpdateViscosity(nodeRate, cornerRate);
3187 previous = macX_;
3188 if (!macSolveLinear(etaRef, backward)) {
3189 ok = false;
3190 break;
3191 }
3192 macRates(nodeRate, cornerRate);
3193 T dmax = T(0), vmax = T(0);
3194 for (std::int64_t k = 0; k < macNVel_; ++k) {
3195 const std::size_t kk = static_cast<std::size_t>(k);
3196 vmax = std::max(vmax, std::abs(macX_[kk]));
3197 if (kk < previous.size())
3198 dmax = std::max(dmax, std::abs(macX_[kk] - previous[kk]));
3199 }
3200 change = vmax > T(0) ? dmax / vmax : dmax;
3201 if (onePass)
3202 break;
3203 if (!nonlinear ||
3204 (it > 1 && change < T(deformationParameters.mechanicsTolerance)))
3205 break;
3206 }
3207 iterations = it;
3208 if (!ok) {
3209 residual = std::numeric_limits<T>::infinity();
3210 lastStokesResidual_ = std::numeric_limits<T>::infinity();
3211 macWarmVersion_ = std::numeric_limits<std::size_t>::max();
3212 return;
3213 }
3214 macWriteBack(nodeRate, cornerRate, etaRef);
3215 residual = nonlinear ? change : T(0);
3216 if (onePass) { // the viscosity for the next coupling iteration
3217 macUpdateViscosity(nodeRate, cornerRate);
3218 residual = T(0);
3219 }
3220 lastStokesResidual_ = backward;
3221 { // volume the MAC flow fails to conserve, relative to the inflow
3222 T sumDiv = T(0), cellVolume = T(1);
3223 for (unsigned d = 0; d < D; ++d)
3224 cellVolume *= gridDelta;
3225 for (std::size_t i = 0; i < nodes.size(); ++i)
3226 if (!nodes[i].nitride)
3227 sumDiv += std::abs(nodes[i].strainTrace);
3228 const T inflow = reactionInflow();
3229 lastContinuityResidual_ = inflow > std::numeric_limits<T>::epsilon()
3230 ? sumDiv * cellVolume / inflow
3231 : sumDiv * cellVolume;
3232 }
3234 macWarmVersion_ = residual <= deformationParameters.mechanicsTolerance
3236 : std::numeric_limits<std::size_t>::max();
3237 if (residual > deformationParameters.mechanicsTolerance)
3238 VIENNACORE_LOG_WARNING(
3239 "solveMechanics: viscosity iteration did not converge after " +
3240 std::to_string(it - 1) +
3241 " iterations (change=" + std::to_string(residual) + ", tolerance=" +
3242 std::to_string(deformationParameters.mechanicsTolerance) + ")");
3243 }
3244
3245 // Carry the corner shear history with the flow (semi-Lagrangian over the
3246 // corner lattice of the same pair), once per accepted step.
3249 return;
3251 if (macCornerEndMap_.empty())
3252 return;
3253 const T dt = stepTime();
3254 std::unordered_map<IndexType, std::array<T, 3>, detail::IndexTypeHasher<D>>
3255 out;
3256 for (std::size_t k = 0; k < macCorners_.size(); ++k) {
3257 const unsigned pair = macCorners_[k].first;
3258 unsigned c, e;
3259 macPairDirs(pair, c, e);
3260 const IndexType &lo = macCorners_[k].second;
3261 // corner velocity: mean of the nodes around it
3262 Vec3D<T> v{T(0), T(0), T(0)};
3263 int cnt = 0;
3264 for (int bc = 0; bc <= 1; ++bc)
3265 for (int be = 0; be <= 1; ++be) {
3266 IndexType q = lo;
3267 q[c] += bc;
3268 q[e] += be;
3269 const std::size_t j = macNode(q);
3270 if (j == noNode)
3271 continue;
3272 for (unsigned d = 0; d < D; ++d)
3273 v[d] += nodes[j].velocity[d];
3274 ++cnt;
3275 }
3276 T value = macCornerEndV_[k];
3277 if (cnt > 0) {
3278 std::array<T, D> base{}, frac{}, departure{};
3279 for (unsigned d = 0; d < D; ++d) {
3280 const T x = static_cast<T>(lo[d]) - v[d] / cnt * dt / gridDelta;
3281 base[d] = std::floor(x);
3282 frac[d] = x - base[d];
3283 // the corner sits half a cell along both directions of its pair
3284 departure[d] = x + (d == c || d == e ? T(0.5) : T(0));
3285 }
3286 // upstream of the Si front: oxide born this step, no history
3287 if (macCornerOxide_[k] && macPhiRAt(departure) < T(0)) {
3288 auto &slot = out.try_emplace(lo, macNoHistory()).first->second;
3289 slot[pair] = T(0);
3290 continue;
3291 }
3292 T sum = T(0), wsum = T(0);
3293 for (unsigned corner = 0; corner < (1u << D); ++corner) {
3294 IndexType idx;
3295 T w = T(1);
3296 for (unsigned d = 0; d < D; ++d) {
3297 const bool up = (corner >> d) & 1u;
3298 idx[d] = static_cast<viennahrle::IndexType>(base[d]) + (up ? 1 : 0);
3299 w *= up ? frac[d] : (T(1) - frac[d]);
3300 }
3301 if (w <= T(0))
3302 continue;
3303 T sign;
3304 const std::int64_t kk = macCornerReflected(pair, idx, sign);
3305 if (kk < 0 || macCornerGas_[static_cast<std::size_t>(kk)])
3306 continue;
3307 sum += w * sign * macCornerEndV_[static_cast<std::size_t>(kk)];
3308 wsum += w;
3309 }
3310 if (wsum > T(1e-6))
3311 value = sum / wsum;
3312 }
3313 auto &slot = out.try_emplace(lo, macNoHistory()).first->second;
3314 slot[pair] = value;
3315 }
3316 macCornerStart_ = std::move(out);
3317 }
3318
3319 // "No history" marker for a corner pair: far outside any stress value and
3320 // finite in single precision -- the arrays go to the output files as
3321 // Float32, where NaN or an overflow to infinity makes the norm range VTK
3322 // stores with them unreadable.
3323 static constexpr T macNone = T(1e30);
3324 static std::array<T, 3> macNoHistory() { return {macNone, macNone, macNone}; }
3325 static bool macHasHistory(T value) { return std::abs(value) < T(1e29); }
3326
3327 // The corner shear history (advected to the end of the step) is stored on
3328 // the level sets so that the next solver -- a new object every step, and in
3329 // a later process call -- starts from it. Every defined point keeps the
3330 // corners it is a vertex of, one array per vertex position k (offset
3331 // (k & 1, k >> 1) from the corner's lower point in the pair's plane), one
3332 // component per pair: a corner survives if any of its points is defined,
3333 // also next to Si where neither level set is. On the ambient level set
3334 // ("OxCornerShear0..3") and, with a nitride, on the mask level set
3335 // ("NitCornerShear0..3").
3336 IndexType macCornerLower(IndexType point, unsigned pair, unsigned k) const {
3337 unsigned c, e;
3338 macPairDirs(pair, c, e);
3339 point[c] -= static_cast<int>(k & 1u);
3340 point[e] -= static_cast<int>(k >> 1);
3341 return point;
3342 }
3343
3346 auto store = [&](SmartPointer<Domain<T, D>> levelSet,
3347 const std::string &name) {
3348 using VD = typename PointData<T>::VectorDataType;
3349 std::array<VD, 4> data;
3350 ConstSparseIterator it(levelSet->getDomain());
3351 for (; !it.isFinished(); ++it) {
3352 if (!it.isDefined())
3353 continue;
3354 const IndexType point = it.getStartIndices();
3355 for (unsigned k = 0; k < 4; ++k) {
3356 Vec3D<T> value{macNone, macNone, macNone};
3357 for (unsigned pair = 0; pair < macPairs; ++pair) {
3358 const auto found =
3359 macCornerStart_.find(macCornerLower(point, pair, k));
3360 if (found != macCornerStart_.end())
3361 value[pair] = found->second[pair];
3362 }
3363 data[k].push_back(value);
3364 }
3365 }
3366 for (unsigned k = 0; k < 4; ++k)
3367 levelSet->getPointData().insertReplaceVectorData(
3368 std::move(data[k]), name + std::to_string(k));
3369 };
3370 store(ambientInterface, "OxCornerShear");
3371 if (monolithicNitride())
3372 store(maskInterface, "NitCornerShear");
3373 }
3374
3376 macCornerStart_.clear();
3377 auto load = [&](SmartPointer<Domain<T, D>> levelSet,
3378 const std::string &name) {
3379 auto &pd = levelSet->getPointData();
3380 for (unsigned k = 0; k < 4; ++k) {
3381 const int index = pd.getVectorDataIndex(name + std::to_string(k));
3382 if (index == -1)
3383 return;
3384 const auto *data = pd.getVectorData(index);
3385 ConstSparseIterator it(levelSet->getDomain());
3386 for (; !it.isFinished(); ++it) {
3387 if (!it.isDefined())
3388 continue;
3389 const auto id = it.getPointId();
3390 if (id >= static_cast<decltype(id)>(data->size()))
3391 continue;
3392 const auto &v = (*data)[id];
3393 for (unsigned pair = 0; pair < macPairs; ++pair) {
3394 if (!macHasHistory(v[pair]))
3395 continue;
3396 auto &slot =
3398 .try_emplace(macCornerLower(it.getStartIndices(), pair, k),
3399 macNoHistory())
3400 .first->second;
3401 if (!macHasHistory(slot[pair]))
3402 slot[pair] = v[pair];
3403 }
3404 }
3405 }
3406 };
3407 load(ambientInterface, "OxCornerShear");
3408 if (maskInterface != nullptr)
3409 load(maskInterface, "NitCornerShear");
3410 }
3411
3412 // Oxide volume created per unit time at the Si/SiO2 interface (per unit
3413 // length in 2D): sum over REACTION faces of |v_exp . e_dir| * h^(D-1).
3415 const std::size_t n = nodes.size();
3416 T faceArea = T(1);
3417 for (unsigned d = 0; d + 1 < D; ++d)
3418 faceArea *= gridDelta;
3419 T inflow = T(0);
3420#pragma omp parallel for reduction(+ : inflow) schedule(static)
3421 for (std::size_t i = 0; i < n; ++i)
3422 for (unsigned fi = 0; fi < 2 * D; ++fi)
3423 if (faceBCTypes_[fi * n + i] == Boundary::REACTION) {
3424 const auto v = reactionVelocityAt(i);
3425 inflow += std::abs(v[fi / 2]) * faceArea;
3426 }
3427 return inflow;
3428 }
3429
3430 // One INFO line per mechanics solve: how well the final velocity satisfies
3431 // continuity. netVolumeRate = Σ div(v)·gridDelta^D over the oxide nodes
3432 // is the volume the discrete flow creates (+) or destroys (−) inside the
3433 // oxide per hour, in µm^D/hr. For an incompressible oxide it must vanish.
3434 void logContinuityDiagnostic(unsigned iterationsUsed) const {
3435 const std::size_t n = nodes.size();
3436 if (n == 0)
3437 return;
3438 T maxDiv = T(0), sumSq = T(0), net = T(0);
3439 T pMin = std::numeric_limits<T>::max();
3440 T pMax = std::numeric_limits<T>::lowest();
3441 std::size_t count = 0;
3442 for (std::size_t i = 0; i < n; ++i) {
3443 if (nodes[i].nitride)
3444 continue; // compressible elastic body without a pressure
3445 const T p = nodes[i].pressure;
3446 if (std::isfinite(p)) {
3447 pMin = std::min(pMin, p);
3448 pMax = std::max(pMax, p);
3449 }
3450 const T div = nodes[i].strainTrace;
3451 if (!std::isfinite(div))
3452 continue;
3453 maxDiv = std::max(maxDiv, std::abs(div));
3454 sumSq += div * div;
3455 net += div;
3456 ++count;
3457 }
3458 T cellVolume = T(1);
3459 for (unsigned d = 0; d < D; ++d)
3460 cellVolume *= gridDelta;
3461 if (pMin > pMax)
3462 pMin = pMax = T(0);
3463 char buffer[384];
3464 std::snprintf(
3465 buffer, sizeof(buffer),
3466 "OxidationDeformation: mechanics iterations=%u "
3467 "residual=%.3e continuity=%.3e |div v|max=%.3e rms=%.3e /hr "
3468 "netVolumeRate=%.4e "
3469 "um^%d/hr p=[%.3f, %.3f] GPa",
3470 iterationsUsed, static_cast<double>(residual),
3471 static_cast<double>(lastContinuityResidual_),
3472 static_cast<double>(maxDiv),
3473 static_cast<double>(count ? std::sqrt(sumSq / T(count)) : T(0)),
3474 static_cast<double>(net * cellVolume), D,
3475 static_cast<double>(pMin * T(1e-9)),
3476 static_cast<double>(pMax * T(1e-9)));
3477 VIENNACORE_LOG_INFO(std::string(buffer));
3478 }
3479
3481 avgExpansionSpeed_ = 0.;
3482 if (nodes.empty())
3483 return;
3484
3485 ConstSparseIterator reactionIt(reactionInterface->getDomain());
3486 ConstSparseIterator ambientIt(ambientInterface->getDomain());
3487 auto maskIt = makeMaskIterator();
3488 std::size_t count = 0;
3489
3490 for (const auto &node : nodes) {
3491 bool touchesReactionBoundary = false;
3492 for (unsigned direction = 0; direction < D; ++direction) {
3493 for (int offset : {-1, 1}) {
3494 IndexType neighbor = node.index;
3495 neighbor[direction] += offset;
3496 if (!inBounds(neighbor))
3497 continue;
3498
3499 if (lookupNode(neighbor) != noNode)
3500 continue;
3501
3502 if (classifyBoundary(reactionIt, ambientIt, maskIt, node.index,
3503 neighbor) == Boundary::REACTION) {
3504 touchesReactionBoundary = true;
3505 break;
3506 }
3507 }
3508 if (touchesReactionBoundary)
3509 break;
3510 }
3511
3512 if (touchesReactionBoundary) {
3513 Vec3D<T> coordinate{0., 0., 0.};
3514 for (unsigned i = 0; i < D; ++i)
3515 coordinate[i] = node.index[i] * gridDelta;
3516 avgExpansionSpeed_ +=
3517 (oxidationParameters.expansionCoefficient - T(1)) *
3518 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
3519 {0., 0., 0.}, 0));
3520 ++count;
3521 }
3522 }
3523
3524 if (count > 0)
3525 avgExpansionSpeed_ /= static_cast<T>(count);
3526 }
3527
3529 boundaryCacheValid_ = false;
3530 const std::size_t n = nodes.size();
3531 reactionVelocity_.assign(n, Vec3D<T>{T(0), T(0), T(0)});
3532 startStress_.assign(n, std::array<T, 9>{});
3533 if (diffusionField != nullptr) // a pending diffusion solve runs here,
3534 diffusionField->getScalarVelocity({T(0), T(0), T(0)}, 0, // not in the
3535 {T(0), T(0), T(0)}, 0); // loop below
3536#pragma omp parallel for schedule(static)
3537 for (std::size_t i = 0; i < n; ++i) {
3538 bool reaction = false;
3539 for (unsigned fi = 0; fi < 2 * D; ++fi)
3540 reaction = reaction || faceBCTypes_[fi * n + i] == Boundary::REACTION;
3541 if (reaction)
3542 reactionVelocity_[i] = reactionBoundaryVelocity(nodes[i].index);
3543 startStress_[i] = previousDeviatoricStress(nodes[i].index);
3544 }
3545 boundaryCacheValid_ = true;
3546 }
3547
3548 Vec3D<T> reactionVelocityAt(std::size_t i) const {
3549 return boundaryCacheValid_ ? reactionVelocity_[i]
3550 : reactionBoundaryVelocity(nodes[i].index);
3551 }
3552
3553 std::array<T, 9> startStressAt(const IndexType &index) const {
3554 if (boundaryCacheValid_) {
3555 const std::size_t i = lookupNode(index);
3556 if (i != noNode)
3557 return startStress_[i];
3558 }
3559 return previousDeviatoricStress(index);
3560 }
3561
3562 Vec3D<T> reactionBoundaryVelocity(const IndexType &index) const {
3563 Vec3D<T> coordinate{0., 0., 0.};
3564 for (unsigned i = 0; i < D; ++i)
3565 coordinate[i] = index[i] * gridDelta;
3566 const T expansionVelocity = localExpansionSpeed(coordinate);
3567 return detail::vecScaled(reactionNormal(index),
3568 reactionSign * expansionVelocity);
3569 }
3570
3571 Vec3D<T> unresolvedAmbientVelocity(const Vec3D<T> &coordinate) const {
3572 if (diffusionField == nullptr || ambientInterface == nullptr)
3573 return {0., 0., 0.};
3574
3575 IndexType index;
3576 for (unsigned i = 0; i < D; ++i)
3577 index[i] = std::llround(coordinate[i] / gridDelta);
3578
3579 ConstSparseIterator ambientIt(ambientInterface->getDomain());
3580 const auto normal = levelSetNormal(ambientIt, index);
3581 return detail::vecScaled(normal, localExpansionSpeed(coordinate));
3582 }
3583
3585 Vec3D<T> maxVelocity{0., 0., 0.};
3586 if (ambientInterface == nullptr || diffusionField == nullptr)
3587 return maxVelocity;
3588
3589 // Every defined point of the ambient interface, in parallel as in
3590 // cacheBoundaryValues (each query uses its own iterators; the max does
3591 // not depend on the order). Far from the Si front a query searches
3592 // ~10^3 nodes: on one thread this walk took 1/4 of a 3-D LOCOS run.
3593 std::vector<IndexType> points;
3594 for (ConstSparseIterator ambientIt(ambientInterface->getDomain());
3595 !ambientIt.isFinished(); ++ambientIt)
3596 if (ambientIt.isDefined())
3597 points.push_back(ambientIt.getStartIndices());
3598 if (points.empty())
3599 return maxVelocity;
3600 diffusionField->getScalarVelocity({T(0), T(0), T(0)}, 0, // a pending
3601 {T(0), T(0), T(0)}, 0); // solve runs here
3602#pragma omp parallel
3603 {
3604 Vec3D<T> local{0., 0., 0.};
3605#pragma omp for schedule(dynamic, 64) nowait
3606 for (std::size_t k = 0; k < points.size(); ++k) {
3607 Vec3D<T> coordinate{0., 0., 0.};
3608 for (unsigned d = 0; d < D; ++d)
3609 coordinate[d] = points[k][d] * gridDelta;
3610
3611 const auto velocity = unresolvedAmbientVelocity(coordinate);
3612 for (unsigned d = 0; d < D; ++d)
3613 local[d] = std::max(local[d], std::abs(velocity[d]));
3614 }
3615#pragma omp critical
3616 for (unsigned d = 0; d < D; ++d)
3617 maxVelocity[d] = std::max(maxVelocity[d], local[d]);
3618 }
3619 return maxVelocity;
3620 }
3621
3622 T localExpansionSpeed(const Vec3D<T> &coordinate) const {
3623 return (oxidationParameters.expansionCoefficient - T(1)) *
3624 std::abs(diffusionField->getScalarVelocity(coordinate, 0,
3625 {0., 0., 0.}, 0));
3626 }
3627
3628 Vec3D<T> reactionNormal(const IndexType &index) const {
3629 ConstSparseIterator reactionIt(reactionInterface->getDomain());
3630 return levelSetNormal(reactionIt, index);
3631 }
3632
3633 Vec3D<T> levelSetNormal(ConstSparseIterator &levelSetIt,
3634 const IndexType &index) const {
3635 Vec3D<T> normal = this->definedLevelSetGradient(
3636 levelSetIt, index, [&](const IndexType &q, bool &ok) {
3637 ok = inBounds(q);
3638 return q;
3639 });
3640 T norm = 0.;
3641 for (unsigned i = 0; i < D; ++i)
3642 norm += normal[i] * normal[i];
3643
3644 if (norm <= std::numeric_limits<T>::epsilon()) {
3645 normal = Vec3D<T>{0., 0., 0.};
3646 normal[D - 1] = 1.;
3647 return normal;
3648 }
3649
3650 norm = std::sqrt(norm);
3651 for (unsigned i = 0; i < D; ++i)
3652 normal[i] /= norm;
3653 return normal;
3654 }
3655
3656 std::array<T, 9> previousDeviatoricStress(const IndexType &index) const {
3657 const auto found = deviatoricStressHistory.find(index);
3658 if (found == deviatoricStressHistory.end())
3659 return {};
3660 return found->second;
3661 }
3662
3663 // exp(-dt/tau) of the Maxwell oxide; 0 for a purely viscous oxide.
3664 T maxwellDecay() const {
3665 const T tau = effectiveStressRelaxationTime();
3666 if (tau <= std::numeric_limits<T>::epsilon())
3667 return T(0);
3668 return std::exp(-T(deformationParameters.stressTimeStep) / tau);
3669 }
3670
3671 // Instantaneous (implicit) modulus of the oxide over one substep:
3672 // (1 - decay) eta, about G dt for dt << tau, eta when purely viscous.
3674 return std::max(T(deformationParameters.viscosity) *
3675 (T(1) - maxwellDecay()),
3676 std::numeric_limits<T>::min() * T(1e10));
3677 }
3678
3680 return deformationParameters.shearActivationVolume > 0.;
3681 }
3682
3683 // Oxide viscosity, Maxwell decay and implicit modulus at node i; the
3684 // constant-viscosity values unless the viscosity is stress-dependent.
3685 T nodeViscosity(std::size_t i) const {
3686 return (stressDependentViscosity() && i < oxideEta_.size())
3687 ? oxideEta_[i]
3688 : T(deformationParameters.viscosity);
3689 }
3690
3691 T nodeDecay(std::size_t i) const {
3693 return maxwellDecay();
3694 const T tau0 = effectiveStressRelaxationTime();
3695 if (tau0 <= std::numeric_limits<T>::epsilon())
3696 return T(0);
3697 const T tau = tau0 * nodeViscosity(i) / T(deformationParameters.viscosity);
3698 return std::exp(-T(deformationParameters.stressTimeStep) / tau);
3699 }
3700
3701 T nodeImplicitViscosity(std::size_t i) const {
3703 return implicitViscosity();
3704 return std::max(nodeViscosity(i) * (T(1) - nodeDecay(i)),
3705 std::numeric_limits<T>::min() * T(1e10));
3706 }
3707
3708 // x / sinh(x) for the shear stress of a deviatoric stress tensor, floored
3709 // at 1e-6 (the viscosity never drops below 1e-6 eta0).
3710 T eyringFactor(const std::array<T, 9> &deviatoric, T volume) const {
3711 T j2 = T(0);
3712 for (unsigned k = 0; k < 9; ++k)
3713 j2 += deviatoric[k] * deviatoric[k];
3714 const T shear = std::sqrt(T(0.5) * j2);
3715 const T kT =
3716 T(1.380649e-23) * std::max(T(oxidationParameters.temperature), T(1));
3717 const T x = shear * volume / (T(2) * kT);
3718 constexpr T floor = T(1e-6);
3719 if (!(x > T(1e-6)))
3720 return T(1);
3721 if (x > T(30))
3722 return floor;
3723 return std::max(x / std::sinh(x), floor);
3724 }
3725
3726 // Start of a mechanics solve: viscosity from the step-start stress.
3728 const T eta0 = T(deformationParameters.viscosity);
3729 oxideEta_.assign(nodes.size(), eta0);
3730 for (std::size_t i = 0; i < nodes.size(); ++i)
3731 if (!nodes[i].nitride)
3732 oxideEta_[i] =
3733 eta0 * eyringFactor(startStressAt(nodes[i].index),
3734 T(deformationParameters.shearActivationVolume));
3735 }
3736
3737 // Start of a mechanics solve: nitride viscosity from the step-start stress.
3739 const T eta0 = T(deformationParameters.nitrideViscosity);
3740 nitrideEta_.assign(nodes.size(), eta0);
3741 for (std::size_t i = 0; i < nodes.size(); ++i)
3742 if (nodes[i].nitride)
3743 nitrideEta_[i] =
3744 eta0 *
3746 T(deformationParameters.nitrideShearActivationVolume));
3747 }
3748
3749 static std::array<T, 9> deviatorOf(const std::array<T, 9> &stress) {
3750 auto s = stress;
3751 const T mean = (stress[0] + stress[4] + stress[8]) / T(3);
3752 for (unsigned d = 0; d < 3; ++d)
3753 s[d * 3 + d] -= mean;
3754 return s;
3755 }
3756
3757 // Viscosity consistent with this node's strain rate: the root of
3758 // eta = eta0 f(|S_end(eta)|), S_end = d(eta) S_start + (1 - d(eta)) 2 eta
3759 // D'
3760 // (f = x / sinh x, d = exp(-dt G / eta)). |S_end| grows with eta and f
3761 // falls with |S_end|, so the root is unique; bisection in log(eta) between
3762 // the floor and eta0. Iterating the stress -> viscosity map instead
3763 // oscillates for this strongly shear-thinning law.
3764 T localViscosity(const std::array<T, 9> &start,
3765 const std::array<T, 9> &deviatoricRate) const {
3766 return localViscosity(start, deviatoricRate,
3767 T(deformationParameters.viscosity),
3769 T(deformationParameters.shearActivationVolume));
3770 }
3771 // The nitride's (start: the deviator of its stress).
3772 T localNitrideViscosity(const std::array<T, 9> &start,
3773 const std::array<T, 9> &deviatoricRate) const {
3774 const T eta0 = T(deformationParameters.nitrideViscosity);
3775 return localViscosity(
3776 start, deviatoricRate, eta0, eta0 / nitrideMu(),
3777 T(deformationParameters.nitrideShearActivationVolume));
3778 }
3779 T localViscosity(const std::array<T, 9> &start,
3780 const std::array<T, 9> &deviatoricRate, T eta0, T tau0,
3781 T volume) const {
3782 const T dt = T(deformationParameters.stressTimeStep);
3783 const auto residual = [&](T logEta) {
3784 const T eta = std::exp(logEta);
3785 const T decay = (tau0 <= std::numeric_limits<T>::epsilon())
3786 ? T(0)
3787 : std::exp(-dt / (tau0 * eta / eta0));
3788 std::array<T, 9> end{};
3789 for (unsigned k = 0; k < 9; ++k)
3790 end[k] =
3791 decay * start[k] + (T(1) - decay) * T(2) * eta * deviatoricRate[k];
3792 return logEta - std::log(eta0 * eyringFactor(end, volume));
3793 };
3794 const T hi = std::log(eta0);
3795 const T rHi = residual(hi);
3796 if (rHi <= T(0))
3797 return eta0; // below the critical stress at eta0
3798 const T lo = hi + std::log(T(1e-6));
3799 const T rLo = residual(lo);
3800 if (rLo >= T(0))
3801 return std::exp(lo);
3802 // within 0.5e-10 of a root, as the midpoint of the bisection this
3803 // replaced (~40 evaluations; ~8 here)
3804 return std::exp(bracketedRoot(residual, lo, rLo, hi, rHi, T(0.5e-10)));
3805 }
3806
3807 // A root of f in [a, b], f(a) and f(b) of opposite signs, within tolerance
3808 // (Brent: inverse quadratic and secant steps, a bisection step whenever
3809 // they would not shrink the bracket fast enough; the bracket always
3810 // holds a sign change).
3811 template <class Function>
3812 static T bracketedRoot(const Function &f, T a, T fa, T b, T fb, T tolerance) {
3813 T c = b, fc = fb, d = b - a, e = d;
3814 for (int iteration = 0; iteration < 100; ++iteration) {
3815 if ((fb > T(0) && fc > T(0)) || (fb < T(0) && fc < T(0))) {
3816 c = a;
3817 fc = fa;
3818 d = e = b - a;
3819 }
3820 if (std::abs(fc) < std::abs(fb)) {
3821 a = b;
3822 b = c;
3823 c = a;
3824 fa = fb;
3825 fb = fc;
3826 fc = fa;
3827 }
3828 const T tol = T(2) * std::numeric_limits<T>::epsilon() * std::abs(b) +
3829 T(0.5) * tolerance;
3830 const T m = T(0.5) * (c - b);
3831 if (std::abs(m) <= tol || fb == T(0))
3832 return b;
3833 if (std::abs(e) >= tol && std::abs(fa) > std::abs(fb)) {
3834 const T s = fb / fa;
3835 T p, q;
3836 if (a == c) {
3837 p = T(2) * m * s;
3838 q = T(1) - s;
3839 } else {
3840 const T qa = fa / fc, r = fb / fc;
3841 p = s * (T(2) * m * qa * (qa - r) - (b - a) * (r - T(1)));
3842 q = (qa - T(1)) * (r - T(1)) * (s - T(1));
3843 }
3844 if (p > T(0))
3845 q = -q;
3846 else
3847 p = -p;
3848 if (T(2) * p <
3849 std::min(T(3) * m * q - std::abs(tol * q), std::abs(e * q))) {
3850 e = d;
3851 d = p / q;
3852 } else {
3853 d = m;
3854 e = m;
3855 }
3856 } else {
3857 d = m;
3858 e = m;
3859 }
3860 a = b;
3861 fa = fb;
3862 b += std::abs(d) > tol ? d : (m > T(0) ? tol : -tol);
3863 fb = f(b);
3864 }
3865 return b;
3866 }
3867
3869 if (deformationParameters.stressRelaxationTime > T(0))
3870 return deformationParameters.stressRelaxationTime;
3871 if (deformationParameters.shearModulus > std::numeric_limits<T>::epsilon())
3872 return deformationParameters.viscosity /
3873 deformationParameters.shearModulus;
3874 return T(0);
3875 }
3876
3877 T vonMisesFromDeviatoric(const std::array<T, 9> &deviatoricStress) const {
3878 T doubleContraction = 0.;
3879 for (unsigned i = 0; i < 3; ++i) {
3880 for (unsigned j = 0; j < 3; ++j) {
3881 const T value = T(0.5) * (deviatoricStress[tensorIndex(i, j)] +
3882 deviatoricStress[tensorIndex(j, i)]);
3883 doubleContraction += value * value;
3884 }
3885 }
3886 return std::sqrt(T(1.5) * doubleContraction);
3887 }
3888
3889 Boundary classifyBoundary(ConstSparseIterator &reactionIt,
3890 ConstSparseIterator &ambientIt,
3891 ConstSparseIterator &maskIt,
3892 const IndexType &inside,
3893 const IndexType &outside) const {
3894 return boundaryIntersection(reactionIt, ambientIt, maskIt, inside, outside)
3895 .boundary;
3896 }
3897
3898 BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt,
3899 ConstSparseIterator &ambientIt,
3900 ConstSparseIterator &maskIt,
3901 const IndexType &inside,
3902 const IndexType &outside) const {
3903 const T reactionInside = valueAt(reactionIt, inside);
3904 const T reactionOutside = valueAt(reactionIt, outside);
3905 const T ambientInside = valueAt(ambientIt, inside);
3906 const T ambientOutside = valueAt(ambientIt, outside);
3907 const T maskInside = valueAtMask(maskIt, inside);
3908 const T maskOutside = valueAtMask(maskIt, outside);
3909
3910 const bool reactionCrosses = crosses(reactionInside, reactionOutside);
3911 const bool ambientCrosses = crosses(ambientInside, ambientOutside);
3912 const bool maskCrosses =
3913 maskInterface != nullptr && crosses(maskInside, maskOutside);
3914
3915 if (!reactionCrosses && !ambientCrosses && !maskCrosses)
3916 return {Boundary::NONE, gridDelta};
3917
3918 const T reactionDistance =
3919 reactionCrosses ? crossingDistance(reactionInside, reactionOutside)
3920 : std::numeric_limits<T>::max();
3921 const T ambientDistance =
3922 ambientCrosses ? crossingDistance(ambientInside, ambientOutside)
3923 : std::numeric_limits<T>::max();
3924 const T maskDistance = maskCrosses
3925 ? crossingDistance(maskInside, maskOutside)
3926 : std::numeric_limits<T>::max();
3927 if (reactionCrosses && reactionDistance <= ambientDistance &&
3928 reactionDistance <= maskDistance)
3929 return {Boundary::REACTION, reactionDistance};
3930 // Every grid point inside the mask is a nitride node, so a face that
3931 // leaves the oxide across the mask interface borders gas: free surface.
3932 // An oxide/gas crossing that lies inside the mask keeps its own distance.
3933 if (ambientCrosses &&
3934 ambientEndsInMask(maskInside, maskOutside, ambientDistance))
3935 return {Boundary::AMBIENT, ambientDistance};
3936 return {Boundary::AMBIENT, std::min(ambientDistance, maskDistance)};
3937 }
3938
3939 bool isInsideOxide(T reactionPhi, T ambientPhi) const {
3940 // GeometricAdvect can leave a tiny positive residual (~4*epsilon) when the
3941 // interface lands exactly on a grid point at non-zero coordinates, because
3942 // k*gridDelta is not exactly representable in floating point. Allow a
3943 // tolerance of 1e-9 grid units so that grid points on the surface (phi≈0)
3944 // are correctly classified as inside the oxide.
3945 constexpr T eps = T(1e-9);
3946 return reactionSign * reactionPhi >= -eps &&
3947 ambientSign * ambientPhi >= -eps;
3948 }
3949
3950 ConstSparseIterator makeMaskIterator() const {
3951 if (maskInterface == nullptr)
3952 return ConstSparseIterator(reactionInterface->getDomain());
3953 return ConstSparseIterator(maskInterface->getDomain());
3954 }
3955
3956 bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3957 if (maskInterface == nullptr)
3958 return false;
3959 return maskSign * valueAt(maskIt, index) >= 0.;
3960 }
3961
3962 T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const {
3963 if (maskInterface == nullptr)
3964 return std::numeric_limits<T>::max();
3965 return valueAt(maskIt, index);
3966 }
3967
3968 // The oxide/gas crossing lies inside the mask, or the outer point does.
3969 bool ambientEndsInMask(T maskInside, T maskOutside, T distance) const {
3970 return isMaskAtCrossing(maskInside, maskOutside, distance) ||
3971 (maskInterface != nullptr &&
3972 static_cast<T>(maskSign) * maskOutside >= T(0));
3973 }
3974
3975 bool isMaskAtCrossing(T maskInside, T maskOutside, T distance) const {
3976 if (maskInterface == nullptr)
3977 return false;
3978 const T fraction = std::clamp(distance / gridDelta, T(0), T(1));
3979 const T insidePhi = detail::clampLevelSetPhi(maskInside);
3980 const T outsidePhi = detail::clampLevelSetPhi(maskOutside);
3981 const T maskPhi = insidePhi + fraction * (outsidePhi - insidePhi);
3982 return static_cast<T>(maskSign) * maskPhi >= T(0);
3983 }
3984
3985 T crossingDistance(T insidePhi, T outsidePhi) const {
3987 insidePhi, outsidePhi,
3988 deformationParameters.minMechanicsBoundaryDistance, gridDelta);
3989 }
3990
3991 static constexpr unsigned tensorIndex(unsigned row, unsigned column) {
3992 return 3 * row + column;
3993 }
3994};
3995
3998template <class T, int D>
4000 SmartPointer<OxidationDeformation<T, D>> field_;
4001 bool nitride_ = false;
4002
4003public:
4005 bool nitride)
4006 : field_(field), nitride_(nitride) {}
4007
4008 template <class... Args> static auto New(Args &&...args) {
4009 return SmartPointer<OxidationMaterialVelocity>::New(
4010 std::forward<Args>(args)...);
4011 }
4012
4013 Vec3D<T> getVectorVelocity(const Vec3D<T> &coordinate, int /*material*/,
4014 const Vec3D<T> & /*normalVector*/,
4015 unsigned long /*pointId*/) final {
4016 return field_->getMaterialVelocity(coordinate, nitride_);
4017 }
4018
4019 T getDissipationAlpha(int direction, int material,
4020 const Vec3D<T> &centralDifferences) final {
4021 return field_->getDissipationAlpha(direction, material, centralDifferences);
4022 }
4023};
4024
4025} // namespace viennals
constexpr int D
Definition Epitaxy.cpp:12
double T
Definition Epitaxy.cpp:13
constexpr T gridDelta
Definition OxidationNitride.cpp:35
Class containing all information about the level set, including the dimensions of the domain,...
Definition lsDomain.hpp:27
Propagates the volume expansion generated at the Si/SiO2 interface through the oxide as a Cartesian-g...
Definition lsOxidationDeformation.hpp:112
void setGpuMode(GpuMode mode)
GPU for the 3-D velocity-pressure solve (systems of at least macGpuMinUnknowns unknowns): Cpu (defaul...
Definition lsOxidationDeformation.hpp:303
@ macOxide
Definition lsOxidationDeformation.hpp:1349
@ macNitride
Definition lsOxidationDeformation.hpp:1349
@ macGas
Definition lsOxidationDeformation.hpp:1349
@ macSi
Definition lsOxidationDeformation.hpp:1349
std::vector< std::uint8_t > macCornerOxide_
Definition lsOxidationDeformation.hpp:1371
std::int64_t macCornerReflected(unsigned pair, IndexType a, T &sign) const
Definition lsOxidationDeformation.hpp:1660
std::size_t macWarmVersion_
Definition lsOxidationDeformation.hpp:2110
MacLin macNormalRate(unsigned d, const IndexType &q) const
Definition lsOxidationDeformation.hpp:1631
bool macGpuOff_
Definition lsOxidationDeformation.hpp:2283
MacLin macNormalStress(unsigned d, const IndexType &q, const IndexType &s, T etaRef) const
Definition lsOxidationDeformation.hpp:1889
T localNitrideViscosity(const std::array< T, 9 > &start, const std::array< T, 9 > &deviatoricRate) const
Definition lsOxidationDeformation.hpp:3772
bool macDirectSolve(const std::vector< MacLin > &rows, T &backward)
Definition lsOxidationDeformation.hpp:2649
std::size_t macNode(const IndexType &a) const
Definition lsOxidationDeformation.hpp:1436
bool initialiseGrid()
Definition lsOxidationDeformation.hpp:1198
std::array< T, 9 > getStressTensor(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:1016
std::size_t getNumberOfSolutionNodes() const
Definition lsOxidationDeformation.hpp:1063
Vec3D< T > getMaterialVelocity(const Vec3D< T > &coordinate, bool nitride)
Monolithic contact: the velocity of one material only (oxide or nitride), for advecting that material...
Definition lsOxidationDeformation.hpp:939
void setMaskInterface(SmartPointer< Domain< T, D > > passedInterface, int passedMaskSign=1)
Definition lsOxidationDeformation.hpp:262
amgcl::solver::fgmres< MacAmgBackend > MacAmgKrylov
Definition lsOxidationDeformation.hpp:2275
std::array< T, 9 > getStrainRateTensor(const IndexType &index) const
Definition lsOxidationDeformation.hpp:1011
T getVonMisesStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:1031
Vec3D< T > getResolvedVectorVelocity(const Vec3D< T > &coordinate) const
Purely mechanical velocity: the interpolated Stokes solution, extended from the nearest solved node w...
Definition lsOxidationDeformation.hpp:896
Eigen::VectorXd macLURs_
Definition lsOxidationDeformation.hpp:2557
amgcl::preconditioner::schur_pressure_correction< MacAmgUSolver, MacAmgPSolver > MacAmgPrecond
Definition lsOxidationDeformation.hpp:2274
T getResidual() const
Definition lsOxidationDeformation.hpp:1037
T localExpansionSpeed(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:3622
T valueAtMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3962
std::vector< std::vector< T > > macAAg_
Definition lsOxidationDeformation.hpp:2785
T macPhiRAt(const std::array< T, D > &x) const
Definition lsOxidationDeformation.hpp:1407
std::uint8_t macKind(const IndexType &a) const
Definition lsOxidationDeformation.hpp:1428
void setReactionInterface(SmartPointer< Domain< T, D > > passedInterface)
Definition lsOxidationDeformation.hpp:250
bool ambientEndsInMask(T maskInside, T maskOutside, T distance) const
Definition lsOxidationDeformation.hpp:3969
std::vector< std::int64_t > macPid_
Definition lsOxidationDeformation.hpp:1357
Eigen::Index macLUN_
Definition lsOxidationDeformation.hpp:2555
T nitrideImplicitFor(T eta) const
Definition lsOxidationDeformation.hpp:370
T implicitViscosity() const
Definition lsOxidationDeformation.hpp:3673
std::size_t macNodesVersion_
Definition lsOxidationDeformation.hpp:1352
Vec3D< T > estimateMaxUnresolvedAmbientVelocity() const
Definition lsOxidationDeformation.hpp:3584
Vec3D< T > levelSetNormal(ConstSparseIterator &levelSetIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3633
T macPsiSlope(bool nitride, const IndexType &x, unsigned e) const
Definition lsOxidationDeformation.hpp:1686
bool macAmgSetup(const std::vector< ptrdiff_t > &ptr, const std::vector< ptrdiff_t > &col, const std::vector< double > &sval, const std::vector< double > &cs)
Definition lsOxidationDeformation.hpp:2488
BoundaryIntersection nitrideBoundaryIntersection(ConstSparseIterator &reactionIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:405
static constexpr unsigned macPairs
Definition lsOxidationDeformation.hpp:1330
void computeAvgExpansionSpeed()
Definition lsOxidationDeformation.hpp:3480
T macGhostFrontVelocity(unsigned d, const IndexType &pa, const IndexType &a, T theta, std::int64_t pid) const
Definition lsOxidationDeformation.hpp:1582
void buildNodes()
Definition lsOxidationDeformation.hpp:1205
unsigned getIterations() const
Definition lsOxidationDeformation.hpp:1036
std::vector< double > macY_
Definition lsOxidationDeformation.hpp:2302
std::unique_ptr< MacAmgPrecond > macAmgP_
Definition lsOxidationDeformation.hpp:2293
void macRates(std::vector< std::array< T, 9 > > &nodeRate, std::vector< T > &cornerRate) const
Definition lsOxidationDeformation.hpp:2739
T nitrideNodeViscosity(std::size_t i) const
Definition lsOxidationDeformation.hpp:376
T macPhiR(const IndexType &a) const
Definition lsOxidationDeformation.hpp:1399
void initOxideViscosity()
Definition lsOxidationDeformation.hpp:3727
std::unordered_map< IndexType, std::array< T, 3 >, detail::IndexTypeHasher< D > > macCornerEndMap_
Definition lsOxidationDeformation.hpp:1376
Vec3D< T > getVectorVelocity(const Vec3D< T > &coordinate, int material, const Vec3D< T > &, unsigned long) final
Like getScalarVelocity, but returns a velocity value for each cartesian direction.
Definition lsOxidationDeformation.hpp:760
unsigned lastAmgIters_
Definition lsOxidationDeformation.hpp:2475
std::vector< T > macCornerMod_
Definition lsOxidationDeformation.hpp:1370
void * macGpu_
Definition lsOxidationDeformation.hpp:2284
T getScalarVelocity(const Vec3D< T > &coordinate, int material, const Vec3D< T > &normalVector, unsigned long) final
Should return a scalar value for the velocity at coordinate for a point of material with the given no...
Definition lsOxidationDeformation.hpp:800
T eyringFactor(const std::array< T, 9 > &deviatoric, T volume) const
Definition lsOxidationDeformation.hpp:3710
void computeNitrideStress(std::size_t i)
Definition lsOxidationDeformation.hpp:424
void setCouplingIterate(bool iterate)
While the stress coupling iterates (OxidationModel), a solve takes one pass of the viscosity iteratio...
Definition lsOxidationDeformation.hpp:298
void writeNitrideFieldsToMask()
Definition lsOxidationDeformation.hpp:585
static constexpr T stokesTolerance
Definition lsOxidationDeformation.hpp:1045
T stepTime() const
Definition lsOxidationDeformation.hpp:336
bool macLUFactored_
Definition lsOxidationDeformation.hpp:2558
void markGeometryChanged()
Definition lsOxidationDeformation.hpp:326
void macReadCornerHistory()
Definition lsOxidationDeformation.hpp:3375
bool couplingIterate_
Definition lsOxidationDeformation.hpp:2108
std::vector< std::pair< unsigned, IndexType > > macCorners_
Definition lsOxidationDeformation.hpp:1367
Vec3D< T > reactionNormal(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3628
std::vector< T > macCornerMem_
Definition lsOxidationDeformation.hpp:1370
std::vector< double > macSrhs_
Definition lsOxidationDeformation.hpp:2302
MacLin macFace(unsigned d, IndexType a, const IndexType *partner) const
Definition lsOxidationDeformation.hpp:1540
void setDeformationParameters(OxidationDeformationParameters passedParameters)
Definition lsOxidationDeformation.hpp:288
void macAmgReset()
Definition lsOxidationDeformation.hpp:2477
T nitridePoisson() const
Definition lsOxidationDeformation.hpp:340
std::vector< double > macCsrVal_
Definition lsOxidationDeformation.hpp:2302
T nitrideMu() const
Definition lsOxidationDeformation.hpp:344
Vec3D< T > reactionVelocityAt(std::size_t i) const
Definition lsOxidationDeformation.hpp:3548
bool lastSolveConverged() const
Definition lsOxidationDeformation.hpp:1046
void macWriteBack(const std::vector< std::array< T, 9 > > &nodeRate, const std::vector< T > &cornerRate, T etaRef)
Definition lsOxidationDeformation.hpp:3036
static constexpr T macNone
Definition lsOxidationDeformation.hpp:3323
GpuMode gpuMode_
Definition lsOxidationDeformation.hpp:2282
T nodeDecay(std::size_t i) const
Definition lsOxidationDeformation.hpp:3691
void advectNitrideStress()
Definition lsOxidationDeformation.hpp:524
std::size_t macAmgVersion_
Definition lsOxidationDeformation.hpp:2295
static auto New(Args &&...args)
Definition lsOxidationDeformation.hpp:246
T getStrainTrace(const IndexType &index) const
Definition lsOxidationDeformation.hpp:1002
Eigen::Index macLUNnz_
Definition lsOxidationDeformation.hpp:2555
bool macCornerAdvectedValid_
Definition lsOxidationDeformation.hpp:1377
T macPhiFace(unsigned d, IndexType a) const
Definition lsOxidationDeformation.hpp:1480
bool macAmgSolveWith(const std::vector< ptrdiff_t > &ptr, const std::vector< ptrdiff_t > &col, const std::vector< double > &val, const std::vector< double > &rhs, bool fresh, T &backward)
Definition lsOxidationDeformation.hpp:2372
bool macSolveLinear(T etaRef, T &backward)
Definition lsOxidationDeformation.hpp:2191
T reactionInflow() const
Definition lsOxidationDeformation.hpp:3414
std::array< T, 9 > nitrideStressStartAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:396
std::vector< ptrdiff_t > macCsrCol_
Definition lsOxidationDeformation.hpp:2301
void advectStress(const std::vector< std::array< T, 9 > > &end, std::vector< std::array< T, 9 > > &out, bool nitride) const
Definition lsOxidationDeformation.hpp:475
std::array< T, 3 > macSurfaceNormal(bool nitride, const IndexType &s, const IndexType &q) const
Definition lsOxidationDeformation.hpp:1720
OxidationDeformation(SmartPointer< Domain< T, D > > passedReactionInterface, SmartPointer< Domain< T, D > > passedAmbientInterface, SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField, OxidationParameters passedOxidationParameters, OxidationDeformationParameters passedDeformationParameters={})
Definition lsOxidationDeformation.hpp:234
T localViscosity(const std::array< T, 9 > &start, const std::array< T, 9 > &deviatoricRate, T eta0, T tau0, T volume) const
Definition lsOxidationDeformation.hpp:3779
T getVonMisesStress(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:1026
std::unordered_map< std::size_t, T > macPsiA_
Definition lsOxidationDeformation.hpp:1363
amgcl::backend::builtin< double > MacAmgBackend
Definition lsOxidationDeformation.hpp:2260
ConstSparseIterator makeMaskIterator() const
Definition lsOxidationDeformation.hpp:3950
std::array< T, 3 > macCornerNormal(bool nitride, unsigned c, unsigned e, const IndexType &a) const
Definition lsOxidationDeformation.hpp:1756
static MacLin macProjected(const std::array< MacLin, 9 > &S, const std::array< T, 3 > &n, unsigned a, unsigned b)
Definition lsOxidationDeformation.hpp:1804
Vec3D< T > reactionBoundaryVelocity(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3562
std::vector< double > macCsrRhs_
Definition lsOxidationDeformation.hpp:2302
bool macPinned_
Definition lsOxidationDeformation.hpp:2107
std::array< T, 9 > previousDeviatoricStress(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3656
void advectOxideStress()
Definition lsOxidationDeformation.hpp:528
static std::array< T, 3 > macNoHistory()
Definition lsOxidationDeformation.hpp:3324
Boundary classifyBoundary(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3889
void apply()
Definition lsOxidationDeformation.hpp:696
T getLastStokesResidual() const
Definition lsOxidationDeformation.hpp:1043
std::size_t nodesVersion_
Definition lsOxidationDeformation.hpp:1353
void clearSolveBounds()
Definition lsOxidationDeformation.hpp:320
std::vector< double > macAmgCs_
Definition lsOxidationDeformation.hpp:2296
std::vector< T > macCornerEtaN_
Definition lsOxidationDeformation.hpp:1369
static unsigned macPairIndex(unsigned c, unsigned e)
Definition lsOxidationDeformation.hpp:1331
bool viscousNitride() const
Definition lsOxidationDeformation.hpp:358
void macAdvectCorners()
Definition lsOxidationDeformation.hpp:3247
std::array< T, 9 > getStressTensor(const IndexType &index) const
Definition lsOxidationDeformation.hpp:1021
std::vector< std::uint8_t > macCornerGas_
Definition lsOxidationDeformation.hpp:1371
void forEachSolutionNode(Callback callback) const
callback(index, pressure, Cauchy stress) for every solution node.
Definition lsOxidationDeformation.hpp:1072
BoundaryIntersection boundaryIntersection(ConstSparseIterator &reactionIt, ConstSparseIterator &ambientIt, ConstSparseIterator &maskIt, const IndexType &inside, const IndexType &outside) const
Definition lsOxidationDeformation.hpp:3898
std::unique_ptr< MacAmgKrylov > macAmgS_
Definition lsOxidationDeformation.hpp:2294
void setOxidationParameters(OxidationParameters passedParameters)
Definition lsOxidationDeformation.hpp:282
bool isInsideMask(ConstSparseIterator &maskIt, const IndexType &index) const
Definition lsOxidationDeformation.hpp:3956
std::vector< std::vector< T > > macAAu_
Definition lsOxidationDeformation.hpp:2785
std::unique_ptr< Eigen::SparseLU< Eigen::SparseMatrix< double >, Eigen::COLAMDOrdering< int > > > macLU_
Definition lsOxidationDeformation.hpp:2553
std::int64_t macN_
Definition lsOxidationDeformation.hpp:1358
std::int64_t macCornerLookup(unsigned pair, const IndexType &a) const
Definition lsOxidationDeformation.hpp:1652
std::vector< Node > nodes
Definition lsOxidationDeformation.hpp:227
IndexType macReflect(IndexType a) const
Definition lsOxidationDeformation.hpp:1392
T effectiveStressRelaxationTime() const
Definition lsOxidationDeformation.hpp:3868
T getLastContinuityResidual() const
Definition lsOxidationDeformation.hpp:1040
std::vector< ptrdiff_t > macCsrPtr_
Definition lsOxidationDeformation.hpp:2301
MacLin macShearStress(unsigned c, unsigned e, const IndexType &a, T etaRef) const
Definition lsOxidationDeformation.hpp:1854
T nodeImplicitViscosity(std::size_t i) const
Definition lsOxidationDeformation.hpp:3701
bool hasFiniteSolution() const
Definition lsOxidationDeformation.hpp:1052
void macGhostReset()
Definition lsOxidationDeformation.hpp:1618
bool monolithicNitride() const
Definition lsOxidationDeformation.hpp:332
T crossingDistance(T insidePhi, T outsidePhi) const
Definition lsOxidationDeformation.hpp:3985
T nodeViscosity(std::size_t i) const
Definition lsOxidationDeformation.hpp:3685
static void macPairDirs(unsigned pair, unsigned &c, unsigned &e)
Definition lsOxidationDeformation.hpp:1336
T getDissipationAlpha(int direction, int material, const Vec3D< T > &) final
If lsLocalLaxFriedrichsAnalytical is used as the spatial discretization scheme, this is called to pro...
Definition lsOxidationDeformation.hpp:806
static std::array< T, 9 > deviatorOf(const std::array< T, 9 > &stress)
Definition lsOxidationDeformation.hpp:3749
void writeFieldsToLevelSet()
Write velocity (Vec3D) and viscoelastic stress history (3 tensor-row vectors) into ambientInterface->...
Definition lsOxidationDeformation.hpp:1081
amgcl::make_solver< amgcl::amg< MacAmgPrecondBackend, amgcl::coarsening::smoothed_aggregation, amgcl::relaxation::ilu0 >, amgcl::solver::preonly< MacAmgPrecondBackend > > MacAmgUSolver
Definition lsOxidationDeformation.hpp:2266
void macLoadCornerStart()
Definition lsOxidationDeformation.hpp:2114
std::array< std::vector< std::int64_t >, D > macMinusFace_
Definition lsOxidationDeformation.hpp:1356
std::vector< double > macSval_
Definition lsOxidationDeformation.hpp:2302
void cacheBoundaryValues()
Definition lsOxidationDeformation.hpp:3528
std::vector< T > macCornerEta_
Definition lsOxidationDeformation.hpp:1369
bool isMaskAtCrossing(T maskInside, T maskOutside, T distance) const
Definition lsOxidationDeformation.hpp:3975
std::unique_ptr< std::atomic< std::uint8_t >[]> macGhostState_
Definition lsOxidationDeformation.hpp:1386
MacLin macShearRate(unsigned c, unsigned e, const IndexType &a) const
Definition lsOxidationDeformation.hpp:1641
std::array< T, 9 > startStressAt(const IndexType &index) const
Definition lsOxidationDeformation.hpp:3553
void macWriteCornerHistory()
Definition lsOxidationDeformation.hpp:3344
std::vector< double > macAmgRs_
Definition lsOxidationDeformation.hpp:2296
std::array< T, 9 > getStrainRateTensor(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:1006
std::array< std::vector< std::int64_t >, D > macPlusFace_
Definition lsOxidationDeformation.hpp:1354
std::vector< T > macCornerStartV_
Definition lsOxidationDeformation.hpp:1369
T vonMisesFromDeviatoric(const std::array< T, 9 > &deviatoricStress) const
Definition lsOxidationDeformation.hpp:3877
std::size_t macLUVersion_
Definition lsOxidationDeformation.hpp:2554
void setSolveBounds(const IndexType &passedMinIndex, const IndexType &passedMaxIndex)
Definition lsOxidationDeformation.hpp:311
amgcl::backend::builtin< float > MacAmgPrecondBackend
Definition lsOxidationDeformation.hpp:2261
std::int64_t macNVel_
Definition lsOxidationDeformation.hpp:1358
bool macReuseSolve(const Eigen::SparseMatrix< double > &A, const Eigen::VectorXd &b, T &backward)
Definition lsOxidationDeformation.hpp:2566
static std::array< T, 3 > macUnitOutward(const std::array< T, 3 > &grad)
Definition lsOxidationDeformation.hpp:1704
std::unordered_map< IndexType, std::array< T, 3 >, detail::IndexTypeHasher< D > > macCornerStart_
Definition lsOxidationDeformation.hpp:1374
void nitrideModuli(std::size_t i, T &twoEta, T &volumetric) const
Definition lsOxidationDeformation.hpp:383
void macAndersonStep(std::vector< T > &u, const std::vector< T > &g)
Definition lsOxidationDeformation.hpp:2786
T localViscosity(const std::array< T, 9 > &start, const std::array< T, 9 > &deviatoricRate) const
Definition lsOxidationDeformation.hpp:3764
std::vector< T > macX_
Definition lsOxidationDeformation.hpp:1378
Vec3D< T > getVelocity(const IndexType &index) const
Definition lsOxidationDeformation.hpp:927
void solveMechanics()
Definition lsOxidationDeformation.hpp:3134
std::size_t macGhostSlots_
Definition lsOxidationDeformation.hpp:1387
bool stressDependentViscosity() const
Definition lsOxidationDeformation.hpp:3679
bool macGhostOn_
Definition lsOxidationDeformation.hpp:1388
void logContinuityDiagnostic(unsigned iterationsUsed) const
Definition lsOxidationDeformation.hpp:3434
std::size_t macAmgSetupIters_
Definition lsOxidationDeformation.hpp:2297
void setOxideSigns(int passedReactionSign, int passedAmbientSign)
Definition lsOxidationDeformation.hpp:305
Eigen::VectorXd macLUCs_
Definition lsOxidationDeformation.hpp:2557
static constexpr std::size_t macGpuMinUnknowns
Definition lsOxidationDeformation.hpp:2281
static constexpr double macTolerance
Definition lsOxidationDeformation.hpp:2367
T macPsi(bool nitride, const IndexType &a) const
Definition lsOxidationDeformation.hpp:1678
void seedNitrideFromMask()
Definition lsOxidationDeformation.hpp:638
std::int64_t macFaceId(unsigned d, const IndexType &a) const
Definition lsOxidationDeformation.hpp:1443
void setDiffusionField(SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField)
Definition lsOxidationDeformation.hpp:276
T macFaceReflect(unsigned d, IndexType &a) const
Definition lsOxidationDeformation.hpp:1457
T macNodeMemory(std::size_t j, unsigned d) const
Definition lsOxidationDeformation.hpp:1943
std::vector< T > macCornerEndV_
Definition lsOxidationDeformation.hpp:1370
T nitrideLambda() const
Definition lsOxidationDeformation.hpp:348
void initNitrideViscosity()
Definition lsOxidationDeformation.hpp:3738
T getPressure(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:989
void setAmbientInterface(SmartPointer< Domain< T, D > > passedInterface)
Definition lsOxidationDeformation.hpp:256
static constexpr unsigned tensorIndex(unsigned row, unsigned column)
Definition lsOxidationDeformation.hpp:3991
bool stressDependentNitride() const
Definition lsOxidationDeformation.hpp:361
static bool macHasHistory(T value)
Definition lsOxidationDeformation.hpp:3325
std::unordered_map< std::size_t, T > macPhiR_
Definition lsOxidationDeformation.hpp:1360
void macBuild()
Definition lsOxidationDeformation.hpp:1956
T macFrontVelocity(unsigned d, const IndexType &a, const IndexType &ghost, T theta) const
Definition lsOxidationDeformation.hpp:1492
T nitrideDecayFor(T eta) const
Definition lsOxidationDeformation.hpp:365
T macCornerDecay(std::size_t k) const
Definition lsOxidationDeformation.hpp:2143
std::vector< T > macGhostV_
Definition lsOxidationDeformation.hpp:1385
static T bracketedRoot(const Function &f, T a, T fa, T b, T fb, T tolerance)
Definition lsOxidationDeformation.hpp:3812
void macUpdateViscosity(const std::vector< std::array< T, 9 > > &nodeRate, const std::vector< T > &cornerRate)
Definition lsOxidationDeformation.hpp:2873
bool neighbourStress(const IndexType &idx, const std::vector< std::array< T, 9 > > &field, bool nitride, std::array< T, 9 > &out) const
Definition lsOxidationDeformation.hpp:535
void clearMaskInterface()
Definition lsOxidationDeformation.hpp:270
const std::array< T, 9 > & nitrideStartOf(std::size_t i) const
Definition lsOxidationDeformation.hpp:400
T avgExpansionSpeed()
Definition lsOxidationDeformation.hpp:1064
T macEval(const MacLin &e) const
Definition lsOxidationDeformation.hpp:2730
amgcl::make_solver< amgcl::relaxation::as_preconditioner< MacAmgPrecondBackend, amgcl::relaxation::spai0 >, amgcl::solver::preonly< MacAmgPrecondBackend > > MacAmgPSolver
Definition lsOxidationDeformation.hpp:2271
T maxwellDecay() const
Definition lsOxidationDeformation.hpp:3664
T getPressure(const IndexType &index) const
Definition lsOxidationDeformation.hpp:993
bool nearbyMeanVelocity(const IndexType &index, Accept accept, Vec3D< T > &velocity) const
Definition lsOxidationDeformation.hpp:915
bool isInsideOxide(T reactionPhi, T ambientPhi) const
Definition lsOxidationDeformation.hpp:3939
Vec3D< T > unresolvedAmbientVelocity(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:3571
IndexType macCornerLower(IndexType point, unsigned pair, unsigned k) const
Definition lsOxidationDeformation.hpp:3336
std::vector< std::unordered_map< std::size_t, std::int64_t > > macCornerId_
Definition lsOxidationDeformation.hpp:1365
T getStrainTrace(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:997
void macCornerCoefficients(T etaRef)
Definition lsOxidationDeformation.hpp:2152
std::array< MacLin, 9 > macPointStress(const IndexType &q, T etaRef) const
Definition lsOxidationDeformation.hpp:1823
static constexpr double macCouplingTolerance
Definition lsOxidationDeformation.hpp:2368
bool macAmgSolve(std::vector< MacLin > &rows, T &backward)
Definition lsOxidationDeformation.hpp:2304
Vec3D< T > getVelocity(const Vec3D< T > &coordinate) const
Definition lsOxidationDeformation.hpp:885
std::unordered_map< std::size_t, T > macPsiM_
Definition lsOxidationDeformation.hpp:1363
void macInitViscosity()
Definition lsOxidationDeformation.hpp:3008
Solves the oxidant diffusion step of the Suvorov et al. (10.1007/s10825-006-0003-z) oxidation model o...
Definition lsOxidationDiffusion.hpp:126
T getDissipationAlpha(int direction, int material, const Vec3D< T > &centralDifferences) final
If lsLocalLaxFriedrichsAnalytical is used as the spatial discretization scheme, this is called to pro...
Definition lsOxidationDeformation.hpp:4019
Vec3D< T > getVectorVelocity(const Vec3D< T > &coordinate, int, const Vec3D< T > &, unsigned long) final
Like getScalarVelocity, but returns a velocity value for each cartesian direction.
Definition lsOxidationDeformation.hpp:4013
OxidationMaterialVelocity(SmartPointer< OxidationDeformation< T, D > > field, bool nitride)
Definition lsOxidationDeformation.hpp:4004
static auto New(Args &&...args)
Definition lsOxidationDeformation.hpp:4008
Common Cartesian-grid infrastructure shared by the oxidation solver classes (diffusion,...
Definition lsOxidationSolverBase.hpp:100
static constexpr std::size_t noNode
Definition lsOxidationSolverBase.hpp:106
bool crosses(T a, T b) const
Definition lsOxidationSolverBase.hpp:147
std::size_t lookupNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:218
std::size_t linearIndex(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:224
void initNodeLookup()
Definition lsOxidationSolverBase.hpp:211
bool inBounds(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:204
std::array< std::size_t, D > strides
Definition lsOxidationSolverBase.hpp:111
T gridDelta
Definition lsOxidationSolverBase.hpp:112
std::vector< std::size_t > nodeLookupFlat
Definition lsOxidationSolverBase.hpp:107
bool initializeGridFromInterfaces(SmartPointer< Domain< T, D > > reactionInterface, SmartPointer< Domain< T, D > > ambientInterface, SmartPointer< Domain< T, D > > maskInterface, bool useRequestedBounds, const IndexType &requestedMinIndex, const IndexType &requestedMaxIndex, std::size_t maxGridPoints, const std::string &solverName)
Definition lsOxidationSolverBase.hpp:334
std::array< std::size_t, D > extents
Definition lsOxidationSolverBase.hpp:110
viennahrle::ConstSparseIterator< typename Domain< T, D >::DomainType > ConstSparseIterator
Definition lsOxidationSolverBase.hpp:103
std::vector< std::size_t > findNearbyNodes(const IndexType &index, Accept accept) const
Definition lsOxidationSolverBase.hpp:294
T valueAt(ConstSparseIterator &it, const IndexType &index) const
Definition lsOxidationSolverBase.hpp:155
bool increment(IndexType &index) const
Definition lsOxidationSolverBase.hpp:233
IndexType minIndex
Definition lsOxidationSolverBase.hpp:108
viennahrle::Index< D > IndexType
Definition lsOxidationSolverBase.hpp:102
std::size_t findNearbyNode(const IndexType &index) const
Definition lsOxidationSolverBase.hpp:247
Vec3D< T > definedLevelSetGradient(ConstSparseIterator &it, const IndexType &index, OnGrid onGrid) const
Definition lsOxidationSolverBase.hpp:170
IndexType maxIndex
Definition lsOxidationSolverBase.hpp:109
T levelSetCrossingDistance(T insidePhi, T outsidePhi, T minBoundaryFraction, T gridDelta)
Definition lsOxidationSolverBase.hpp:86
T clampLevelSetPhi(T v)
Clamp HRLE far-field sentinels (±DBL_MAX) to ±1 before differencing to prevent DBL_MAX² overflow that...
Definition lsOxidationSolverBase.hpp:81
Vec3D< T > vecScaled(const Vec3D< T > &source, T factor)
Definition lsOxidationSolverBase.hpp:50
Definition lsAdvect.hpp:41
GpuMode
Where the large linear solves of the oxidation solvers run: the diffusion solve and the 3-D mechanics...
Definition lsOxidationDiffusion.hpp:43
@ Gpu
Always use GPU; fail if unavailable or unsuccessful Use the GPU when it is usable,...
Definition lsOxidationDiffusion.hpp:45
@ Cpu
Always use CPU (default).
Definition lsOxidationDiffusion.hpp:44
Parameters for the Cartesian-grid oxide deformation model.
Definition lsOxidationDeformation.hpp:38
double viscosity
Definition lsOxidationDeformation.hpp:39
double shearActivationVolume
Definition lsOxidationDeformation.hpp:65
double shearModulus
Definition lsOxidationDeformation.hpp:52
double stressRelaxationTime
Definition lsOxidationDeformation.hpp:53
double nitrideViscosity
Definition lsOxidationDeformation.hpp:88
double minMechanicsBoundaryDistance
Definition lsOxidationDeformation.hpp:45
double nitrideShearActivationVolume
Definition lsOxidationDeformation.hpp:89
unsigned mechanicsIterations
Definition lsOxidationDeformation.hpp:70
double nitridePoissonRatio
Definition lsOxidationDeformation.hpp:82
double mechanicsTolerance
Definition lsOxidationDeformation.hpp:71
double stressTimeStep
Definition lsOxidationDeformation.hpp:54
int material
Definition lsOxidationDeformation.hpp:91
double ambientPressure
Definition lsOxidationDeformation.hpp:40
double nitrideYoungModulus
Definition lsOxidationDeformation.hpp:81
std::size_t maxGridPoints
Definition lsOxidationDeformation.hpp:90
Definition lsOxidationDeformation.hpp:1311
MacLin & add(const MacLin &o, T s=T(1))
Definition lsOxidationDeformation.hpp:1314
MacLin scaled(T s) const
Definition lsOxidationDeformation.hpp:1320
std::vector< std::pair< std::int64_t, T > > t
Definition lsOxidationDeformation.hpp:1312
T c
Definition lsOxidationDeformation.hpp:1313
Parameters for the steady oxidant diffusion model used by OxidationDiffusion.
Definition lsOxidationDiffusion.hpp:56
Definition lsOxidationSolverBase.hpp:44