ViennaLS
Loading...
Searching...
No Matches
lsOxidation.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <lsAdvect.hpp>
6#include <lsExpand.hpp>
7#include <lsInterior.hpp>
9#include <lsReduce.hpp>
10
11#include <algorithm>
12#include <cmath>
13#include <exception>
14#include <iostream>
15
16#include <limits>
17#include <optional>
18#include <stdexcept>
19#include <string>
20#include <unordered_map>
21#include <vcTimer.hpp>
22
23namespace viennals {
24
25using namespace viennacore;
26
35
36template <class T>
37std::optional<T> findLOCOSInterfaceY(SmartPointer<Domain<T, 2>> levelSet,
38 viennahrle::IndexType i,
39 viennahrle::IndexType jMin,
40 viennahrle::IndexType jMax) {
41 using ConstIterator =
42 viennahrle::ConstSparseIterator<typename Domain<T, 2>::DomainType>;
43 ConstIterator it(levelSet->getDomain());
44
45 const T gridDelta = levelSet->getGrid().getGridDelta();
46 viennahrle::Index<2> previousIndex{i, jMin};
47 it.goToIndices(previousIndex);
48 T previous = it.getValue();
49
50 for (auto j = jMin + 1; j <= jMax; ++j) {
51 viennahrle::Index<2> currentIndex{i, j};
52 it.goToIndices(currentIndex);
53 const T current = it.getValue();
54 if ((previous <= 0. && current >= 0.) ||
55 (previous >= 0. && current <= 0.)) {
56 const T denominator = std::abs(previous) + std::abs(current);
57 const T fraction = denominator > std::numeric_limits<T>::epsilon()
58 ? std::abs(previous) / denominator
59 : 0.;
60 return (static_cast<T>(j - 1) + fraction) * gridDelta;
61 }
62 previous = current;
63 }
64
65 return std::nullopt;
66}
67
74template <class T>
76 SmartPointer<Domain<T, 2>> siInitial, SmartPointer<Domain<T, 2>> siAfter,
77 SmartPointer<Domain<T, 2>> ambientInitial,
78 SmartPointer<Domain<T, 2>> ambientAfter, T xMin, T xMax,
79 viennahrle::IndexType jMin, viennahrle::IndexType jMax,
80 T expansionCoefficient) {
82 const T gridDelta = siInitial->getGrid().getGridDelta();
83 const auto iMin =
84 static_cast<viennahrle::IndexType>(std::ceil(xMin / gridDelta));
85 const auto iMax =
86 static_cast<viennahrle::IndexType>(std::floor(xMax / gridDelta));
87
88 for (auto i = iMin; i <= iMax; ++i) {
89 const auto si0 = findLOCOSInterfaceY<T>(siInitial, i, jMin, jMax);
90 const auto si1 = findLOCOSInterfaceY<T>(siAfter, i, jMin, jMax);
91 const auto amb0 = findLOCOSInterfaceY<T>(ambientInitial, i, jMin, jMax);
92 const auto amb1 = findLOCOSInterfaceY<T>(ambientAfter, i, jMin, jMax);
93 if (!si0 || !si1 || !amb0 || !amb1)
94 continue;
95
96 result.siliconRecession += std::max(*si0 - *si1, T(0.)) * gridDelta;
97 result.ambientLift += std::max(*amb1 - *amb0, T(0.)) * gridDelta;
98 ++result.samples;
99 }
100
101 result.expectedAmbientLift =
102 result.siliconRecession * (expansionCoefficient - T(1.));
103 if (result.siliconRecession > 0.)
104 result.ambientLiftRatio = result.ambientLift / result.siliconRecession;
105 if (result.expectedAmbientLift > 0.)
106 result.relativeError =
107 std::abs(result.ambientLift - result.expectedAmbientLift) /
108 result.expectedAmbientLift;
109 return result;
110}
111
174template <class T, int D> class OxidationMaskTracker {
175 using IndexType = viennahrle::Index<D>;
176 using ConstSparseIterator =
177 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
178 using DisplacementMap =
179 std::unordered_map<IndexType, Vec3D<T>, detail::IndexTypeHasher<D>>;
180
181 // phi_0 is expanded to |phi| <= 4.5: a departure point lies as far from
182 // the reference surface as its band point from the current one.
183 static constexpr int referenceWidth = 9;
184 static constexpr T referenceLimit = T(5);
185
186 const Domain<T, D> *mask_;
187 SmartPointer<Domain<T, D>> reference_;
188 DisplacementMap displacement_;
189 T gridDelta_;
190
191public:
192 explicit OxidationMaskTracker(SmartPointer<Domain<T, D>> mask)
193 : mask_(mask.get()), reference_(SmartPointer<Domain<T, D>>::New(mask)),
194 gridDelta_(mask->getGrid().getGridDelta()) {
195 Expand<T, D>(reference_, referenceWidth).apply();
196 }
197
199 bool tracks(const SmartPointer<Domain<T, D>> &mask) const {
200 return mask.get() == mask_ && mask->getGrid().getGridDelta() == gridDelta_;
201 }
202
205 template <class VelocityFunction>
206 void correct(SmartPointer<Domain<T, D>> mask, VelocityFunction &&velocity,
207 T dt) {
208 const auto &grid = mask->getGrid();
209 ConstSparseIterator referenceIt(reference_->getDomain());
210 auto referenceValue = [&](const IndexType &index) {
211 referenceIt.goToIndices(grid.globalIndices2LocalIndices(index));
212 return std::clamp(referenceIt.getValue(), -referenceLimit,
213 referenceLimit);
214 };
215
216 struct Update {
217 unsigned segment;
218 std::size_t localId;
219 T value;
220 };
221 std::vector<Update> updates;
222 DisplacementMap next;
223 auto &domain = mask->getDomain();
224 for (ConstSparseIterator it(domain); !it.isFinished(); ++it) {
225 if (!it.isDefined())
226 continue;
227 const IndexType index = it.getStartIndices();
228 Vec3D<T> coordinate{T(0), T(0), T(0)};
229 for (unsigned d = 0; d < D; ++d)
230 coordinate[d] = static_cast<T>(index[d]) * gridDelta_;
231 const Vec3D<T> v = velocity(coordinate);
232 T departure[D];
233 for (unsigned d = 0; d < D; ++d)
234 departure[d] = static_cast<T>(index[d]) - v[d] * dt / gridDelta_;
235 Vec3D<T> u = departureDisplacement(departure);
236 for (unsigned d = 0; d < D; ++d)
237 u[d] += v[d] * dt;
238
239 // phi_0 at the departure point x - u, D-linear.
240 IndexType lower;
241 T fraction[D];
242 for (unsigned d = 0; d < D; ++d) {
243 const T c = static_cast<T>(index[d]) - u[d] / gridDelta_;
244 const T cFloor = std::floor(c);
245 lower[d] = static_cast<viennahrle::IndexType>(cFloor);
246 fraction[d] = c - cFloor;
247 }
248 T value = T(0);
249 for (int corner = 0; corner < (1 << D); ++corner) {
250 IndexType vertex = lower;
251 T weight = T(1);
252 for (unsigned d = 0; d < D; ++d) {
253 const bool up = (corner >> d) & 1;
254 vertex[d] += up ? 1 : 0;
255 weight *= up ? fraction[d] : T(1) - fraction[d];
256 }
257 if (weight > T(0))
258 value += weight * referenceValue(vertex);
259 }
260 const unsigned segment = it.getSegmentId();
261 updates.push_back(
262 {segment, it.getPointId() - domain.getPointIdOffset(segment), value});
263 next.emplace(index, u);
264 }
265 // The re-sampled values are no ViennaLS narrow band: a stretched or bent
266 // nitride gives neighbours like -0.504 / +0.527 across its surface,
267 // which the level-set algorithms reject (they walked off the HRLE and
268 // crashed). Normalise as Advect does: such a pair (both beyond 0.5) is
269 // clamped to +-0.5, the outer layers are rebuilt from the active points.
270 // Only such pairs: clamping e.g. -0.88 next to +0.12 moves the surface
271 // by 0.07 cells every step, which lifted the nitride off the oxide until
272 // the mechanics lost the contact.
273 std::unordered_map<IndexType, T, detail::IndexTypeHasher<D>> values;
274 values.reserve(updates.size());
275 std::size_t k = 0;
276 for (ConstSparseIterator it(domain); !it.isFinished(); ++it)
277 if (it.isDefined())
278 values.emplace(it.getStartIndices(), updates[k++].value);
279 k = 0;
280 for (ConstSparseIterator it(domain); !it.isFinished(); ++it) {
281 if (!it.isDefined())
282 continue;
283 auto &update = updates[k++];
284 const IndexType index = it.getStartIndices();
285 for (unsigned d = 0; d < D && std::abs(update.value) > T(0.5); ++d)
286 for (const int side : {-1, 1}) {
287 IndexType neighbor = index;
288 neighbor[d] += side;
289 const auto found = values.find(neighbor);
290 if (found != values.end() && std::abs(found->second) > T(0.5) &&
291 std::signbit(found->second) != std::signbit(update.value))
292 update.value = std::clamp(update.value, T(-0.5), T(0.5));
293 }
294 }
295 for (const auto &update : updates)
296 domain.getDomainSegment(update.segment).definedValues[update.localId] =
297 update.value;
298 const int width = std::max(mask->getLevelSetWidth(), 2);
299 Reduce<T, D>(mask, 1).apply();
300 Expand<T, D>(mask, width).apply();
301 displacement_ = std::move(next);
302 }
303
304private:
305 // u_old at a departure point (grid units): D-linear over the stored band
306 // points, skipping corners outside the old band; else the nearest stored
307 // point within 2 cells (the band moves less than a cell per step and u
308 // varies on the scale of the body); zero before the first step.
309 Vec3D<T> departureDisplacement(const T (&position)[D]) const {
310 IndexType lower;
311 T fraction[D];
312 for (unsigned d = 0; d < D; ++d) {
313 const T cFloor = std::floor(position[d]);
314 lower[d] = static_cast<viennahrle::IndexType>(cFloor);
315 fraction[d] = position[d] - cFloor;
316 }
317 Vec3D<T> sum{T(0), T(0), T(0)};
318 T weightSum = T(0);
319 for (int corner = 0; corner < (1 << D); ++corner) {
320 IndexType vertex = lower;
321 T weight = T(1);
322 for (unsigned d = 0; d < D; ++d) {
323 const bool up = (corner >> d) & 1;
324 vertex[d] += up ? 1 : 0;
325 weight *= up ? fraction[d] : T(1) - fraction[d];
326 }
327 if (weight <= T(0))
328 continue;
329 const auto found = displacement_.find(vertex);
330 if (found == displacement_.end())
331 continue;
332 for (unsigned d = 0; d < D; ++d)
333 sum[d] += weight * found->second[d];
334 weightSum += weight;
335 }
336 if (weightSum > T(1e-6)) {
337 for (unsigned d = 0; d < D; ++d)
338 sum[d] /= weightSum;
339 return sum;
340 }
341 IndexType nearest;
342 for (unsigned d = 0; d < D; ++d)
343 nearest[d] =
344 static_cast<viennahrle::IndexType>(std::llround(position[d]));
345 return nearestDisplacement(nearest);
346 }
347
348 Vec3D<T> nearestDisplacement(const IndexType &index) const {
349 const Vec3D<T> *best = nullptr;
350 int bestDistance = std::numeric_limits<int>::max();
351 IndexType offset;
352 for (unsigned d = 0; d < D; ++d)
353 offset[d] = -2;
354 while (true) {
355 int distance = 0;
356 IndexType neighbor = index;
357 for (unsigned d = 0; d < D; ++d) {
358 neighbor[d] += offset[d];
359 distance += offset[d] * offset[d];
360 }
361 if (distance < bestDistance) {
362 const auto it = displacement_.find(neighbor);
363 if (it != displacement_.end()) {
364 best = &it->second;
365 bestDistance = distance;
366 }
367 }
368 unsigned d = 0;
369 for (; d < D; ++d) {
370 if (offset[d] < 2) {
371 ++offset[d];
372 break;
373 }
374 offset[d] = -2;
375 }
376 if (d == D)
377 break;
378 }
379 return best != nullptr ? *best : Vec3D<T>{T(0), T(0), T(0)};
380 }
381};
382
383template <class T, int D> class Oxidation {
384 using IndexType = viennahrle::Index<D>;
385
386 SmartPointer<Domain<T, D>> siInterface = nullptr;
387 SmartPointer<Domain<T, D>> ambientInterface = nullptr;
388 SmartPointer<Domain<T, D>> maskInterface = nullptr;
389
390 OxidationParameters oxidationParams;
391 OxidationDeformationParameters deformationParams;
392 OxidationCouplingParameters couplingParams;
393 OxidationMaskParameters maskParams;
394
397 static constexpr int maskInteriorSign = -1;
403 bool maskBarrierOnly_ = false;
404
405 IndexType diffusionMinIndex{};
406 IndexType diffusionMaxIndex{};
407 bool diffusionBoundsSet = false;
408
409 // Populated by apply(); available for diagnostics afterwards.
410 SmartPointer<OxidationDiffusion<T, D>> diffusionField;
411 SmartPointer<OxidationDeformation<T, D>> deformationField;
412 T lastMaxVelocity_ = T(0);
413
414 GpuMode gpuMode_ = GpuMode::Cpu;
415 std::unordered_map<std::size_t, T> concentrationCache_;
416 // Stress feedback carried between solves (see
417 // OxidationDiffusion::setFeedbackCache).
418 typename OxidationDiffusion<T, D>::PressureLookup pressureCache_;
419 typename OxidationDiffusion<T, D>::StressLookup stressCache_;
420 std::optional<OxidationMaskTracker<T, D>> maskTracker_;
421
422public:
423 const std::unordered_map<std::size_t, T> &getConcentrationCache() const {
424 return concentrationCache_;
425 }
426 void setConcentrationCache(std::unordered_map<std::size_t, T> cache) {
427 concentrationCache_ = std::move(cache);
428 }
429
430 Oxidation() = default;
431
432 Oxidation(SmartPointer<Domain<T, D>> passedSiInterface,
433 SmartPointer<Domain<T, D>> passedAmbientInterface,
434 SmartPointer<Domain<T, D>> passedMaskInterface = nullptr)
435 : siInterface(passedSiInterface),
436 ambientInterface(passedAmbientInterface),
437 maskInterface(passedMaskInterface) {}
438
439 template <class... Args> static auto New(Args &&...args) {
440 return SmartPointer<Oxidation>::New(std::forward<Args>(args)...);
441 }
442
443 void setGpuMode(GpuMode mode) { gpuMode_ = mode; }
444
445 void setSiInterface(SmartPointer<Domain<T, D>> si) { siInterface = si; }
446 void setAmbientInterface(SmartPointer<Domain<T, D>> ambient) {
447 ambientInterface = ambient;
448 }
449 void setMaskInterface(SmartPointer<Domain<T, D>> mask) {
450 maskInterface = mask;
451 }
452
454 oxidationParams = params;
455 }
457 deformationParams = params;
458 }
460 couplingParams = params;
461 }
463 maskParams = params;
464 }
465
467 void setSpatialScheme(SpatialSchemeEnum scheme) { spatialScheme = scheme; }
468
469 void setMaskBarrierOnly(bool enable) { maskBarrierOnly_ = enable; }
470
472 void setTemporalScheme(TemporalSchemeEnum scheme) { temporalScheme = scheme; }
473
477 void setSolveBounds(const IndexType &minIndex, const IndexType &maxIndex) {
478 diffusionMinIndex = minIndex;
479 diffusionMaxIndex = maxIndex;
480 diffusionBoundsSet = true;
481 }
482
484 SmartPointer<OxidationDiffusion<T, D>> getDiffusionField() const {
485 return diffusionField;
486 }
487
489 SmartPointer<OxidationDeformation<T, D>> getDeformationField() const {
490 return deformationField;
491 }
492
494 T getLastMaxVelocity() const { return lastMaxVelocity_; }
495
497 void apply(T advectionTime) { applyImpl(advectionTime, std::nullopt); }
498
500 T applyCFLLimited(T requestedTime, T cflFactor) {
501 return applyImpl(requestedTime, std::clamp(cflFactor, T(1e-3), T(0.499)));
502 }
503
504private:
505 T applyImpl(T requestedTime, std::optional<T> cflFactor) {
506 if (siInterface == nullptr || ambientInterface == nullptr) {
507 Logger::getInstance()
508 .addError("Oxidation: Si or ambient interface is null.")
509 .print();
510 return T(0);
511 }
512
513 if (requestedTime <= T(0))
514 return T(0);
515
516 Timer<> tStep;
517 tStep.start();
518
519 const bool hasMask = (maskInterface != nullptr);
520 // The nitride is part of the oxide mechanics solve (OxidationDeformation):
521 // there is no separate mask solve and no coupling loop, and each surface
522 // moves with its own material's solved velocity.
523 const bool monolithicMask = hasMask && !maskBarrierOnly_;
524 const std::string prefix = hasMask ? "LOCOS" : "Oxidation";
525
526 VIENNACORE_LOG_INFO(prefix + ": starting time step, requested_dt=" +
527 std::to_string(requestedTime) + " hr");
528
529 const auto baseConcentrationCache = concentrationCache_;
530 const auto basePressureCache = pressureCache_;
531 const auto baseStressCache = stressCache_;
532 std::string lastFieldFailureReason;
533
534 auto solveFields = [&](T stressTimeStep) -> bool {
535 auto rejectSolve = [&](const std::string &reason) {
536 lastFieldFailureReason = reason;
537 return false;
538 };
539
540 auto validateCoupledModel = [&](const SmartPointer<OxidationModel<T, D>>
541 &model) {
542 if (!model->hasConverged()) {
543 const auto reason = model->getFailureReason();
544 return rejectSolve(
545 reason.empty()
546 ? "pressure-concentration coupling failed (residual=" +
547 std::to_string(model->getResidual()) + ", tolerance=" +
548 std::to_string(couplingParams.tolerance) + ")"
549 : reason);
550 }
551 if (!diffusionField->lastSolveConverged() ||
552 !diffusionField->hasFiniteConcentrationField()) {
553 return rejectSolve(
554 "diffusion solve failed (residual=" +
555 std::to_string(diffusionField->getNormalizedResidual()) +
556 ", tolerance=" + std::to_string(oxidationParams.tolerance) + ")");
557 }
558 if (!deformationField->lastSolveConverged() ||
559 !deformationField->hasFiniteSolution()) {
560 return rejectSolve(
561 "deformation solve failed (mechanics=" +
562 std::to_string(deformationField->getResidual()) +
563 ", continuity=" +
564 std::to_string(deformationField->getLastContinuityResidual()) +
565 ", stokes=" +
566 std::to_string(deformationField->getLastStokesResidual()) + ")");
567 }
568 return true;
569 };
570
571 lastFieldFailureReason.clear();
572 auto stepDeformationParams = deformationParams;
573 stepDeformationParams.stressTimeStep = stressTimeStep;
574 if (monolithicMask) {
575 stepDeformationParams.nitrideYoungModulus = maskParams.youngModulus;
576 stepDeformationParams.nitridePoissonRatio = maskParams.poissonRatio;
577 stepDeformationParams.nitrideViscosity = maskParams.viscosity;
578 stepDeformationParams.nitrideShearActivationVolume =
579 maskParams.shearActivationVolume;
580 }
581
582 // --- Coupled diffusion + deformation solve ---
583
584 diffusionField = OxidationDiffusion<T, D>::New(
585 siInterface, ambientInterface, oxidationParams);
586 diffusionField->setConcentrationCache(baseConcentrationCache);
587 diffusionField->setFeedbackCache(basePressureCache, baseStressCache);
588 diffusionField->setGpuMode(gpuMode_);
589 if (hasMask)
590 diffusionField->setMaskInterface(maskInterface, maskInteriorSign);
591
592 deformationField = OxidationDeformation<T, D>::New(
593 siInterface, ambientInterface, diffusionField, oxidationParams,
594 stepDeformationParams);
595 deformationField->setGpuMode(gpuMode_);
596 // Barrier-only: the mask is withheld from the mechanics so the oxide
597 // sees a free surface and expands outward.
598 if (hasMask && !maskBarrierOnly_)
599 deformationField->setMaskInterface(maskInterface, maskInteriorSign);
600
601 auto coupledModel = OxidationModel<T, D>::New(
602 diffusionField, deformationField, couplingParams);
603 if (diffusionBoundsSet)
604 coupledModel->setSolveBounds(diffusionMinIndex, diffusionMaxIndex);
605 VIENNACORE_LOG_DEBUG(
606 prefix + ": solving coupled diffusion/deformation field for dt=" +
607 std::to_string(stressTimeStep) + " hr");
608 Timer<> tCoupled;
609 tCoupled.start();
610 coupledModel->apply();
611 tCoupled.finish();
612 if (Logger::hasTiming())
613 Logger::getInstance().addTiming(" coupled(iter=1)", tCoupled).print();
614 VIENNACORE_LOG_DEBUG(prefix +
615 ": coupled diffusion/deformation solve complete");
616 return validateCoupledModel(coupledModel);
617 };
618
619 auto makeAmbientVelocity = [&]() -> SmartPointer<VelocityField<T>> {
620 // Barrier-only: the oxide's free surface must NOT be constrained by
621 // the mask -- it expands freely and carries the mask along.
622 // Monolithic: oxide and nitride share one solve; each surface moves
623 // with its own material's velocity.
624 if (monolithicMask)
625 return OxidationMaterialVelocity<T, D>::New(deformationField, false);
626 return deformationField;
627 };
628
629 T advectionTime = requestedTime;
630 SmartPointer<VelocityField<T>> ambientVelocity;
631
632 auto computeMaxVelocity =
633 [&](const SmartPointer<VelocityField<T>> &passedAmbientVelocity) {
634 T maxVelocity = diffusionField->getDissipationAlpha(0, -1, {});
635 // The mask moves with the oxide velocity field (its own nitride
636 // nodes, or carried in barrier-only mode): covered above.
637 for (unsigned d = 0; d < D; ++d)
638 maxVelocity =
639 std::max(maxVelocity,
640 passedAmbientVelocity->getDissipationAlpha(d, -1, {}));
641 return maxVelocity;
642 };
643
644 auto cflLimitedTime = [&](T trialTime, T maxVelocity) {
645 if (maxVelocity <= std::numeric_limits<T>::epsilon())
646 return trialTime;
647 const T gridDelta = siInterface->getGrid().getGridDelta();
648 return std::min(trialTime, (*cflFactor) * gridDelta / maxVelocity);
649 };
650
651 if (cflFactor) {
652 T trialTime = requestedTime;
653 const T minTrialTime = std::max(
654 requestedTime * T(1e-10), std::numeric_limits<T>::epsilon() * T(100));
655 bool accepted = false;
656 for (unsigned attempt = 0; attempt < 16; ++attempt) {
657 const bool predictorConverged = solveFields(trialTime);
658 if (!predictorConverged) {
659 const T nextTrial = trialTime * T(0.5);
660 VIENNACORE_LOG_INFO(
661 prefix + ": rejecting non-converged coupled predictor (" +
662 lastFieldFailureReason + "), retrying with requested_dt=" +
663 std::to_string(nextTrial) + " hr");
664 trialTime = nextTrial;
665 if (trialTime < minTrialTime)
666 break;
667 continue;
668 }
669
670 ambientVelocity = makeAmbientVelocity();
671 T maxVelocity = computeMaxVelocity(ambientVelocity);
672 if (!std::isfinite(maxVelocity))
673 VIENNACORE_LOG_ERROR(prefix + ": non-finite CFL velocity estimate.");
674
675 advectionTime = cflLimitedTime(trialTime, maxVelocity);
676 VIENNACORE_LOG_INFO(
677 prefix +
678 ": CFL decision requested_dt=" + std::to_string(trialTime) +
679 " hr, actual_dt=" + std::to_string(advectionTime) +
680 " hr, max_velocity=" + std::to_string(maxVelocity) + " um/hr");
681
682 if (advectionTime < trialTime * (T(1) - T(1e-8))) {
683 const bool finalConverged = solveFields(advectionTime);
684 if (!finalConverged) {
685 const T nextTrial = advectionTime * T(0.5);
686 VIENNACORE_LOG_INFO(
687 prefix + ": rejecting non-converged CFL re-solve (" +
688 lastFieldFailureReason + "), retrying with requested_dt=" +
689 std::to_string(nextTrial) + " hr");
690 trialTime = nextTrial;
691 if (trialTime < minTrialTime)
692 break;
693 continue;
694 }
695 ambientVelocity = makeAmbientVelocity();
696 maxVelocity = computeMaxVelocity(ambientVelocity);
697 if (!std::isfinite(maxVelocity))
698 VIENNACORE_LOG_ERROR(prefix +
699 ": non-finite accepted CFL velocity.");
700
701 const T verifiedTime = cflLimitedTime(advectionTime, maxVelocity);
702 if (verifiedTime < advectionTime * (T(1) - T(1e-8))) {
703 VIENNACORE_LOG_INFO(prefix +
704 ": rejecting CFL re-solve because accepted "
705 "velocity requires requested_dt=" +
706 std::to_string(verifiedTime) + " hr");
707 trialTime = verifiedTime;
708 if (trialTime < minTrialTime)
709 break;
710 continue;
711 }
712 }
713
714 lastMaxVelocity_ = computeMaxVelocity(ambientVelocity);
715 accepted = true;
716 break;
717 }
718 if (!accepted)
719 VIENNACORE_LOG_ERROR(prefix +
720 ": unable to find a converged CFL-limited step" +
721 (lastFieldFailureReason.empty()
722 ? std::string(".")
723 : std::string(" (last failure: ") +
724 lastFieldFailureReason + ")."));
725 } else {
726 const bool fieldsConverged = solveFields(requestedTime);
727 if (!fieldsConverged)
728 VIENNACORE_LOG_ERROR(
729 prefix + ": coupled solve failed" +
730 (lastFieldFailureReason.empty()
731 ? std::string(".")
732 : std::string(" (") + lastFieldFailureReason + ")."));
733 ambientVelocity = makeAmbientVelocity();
734 }
735
736 concentrationCache_ = diffusionField->getConcentrationCache();
737 pressureCache_ = diffusionField->getPressureCache();
738 stressCache_ = diffusionField->getStressCache();
739
740 diffusionField->markSolved();
741
742 // VOLTRACE: per-stage ambient-volume audit for the under-mask volume
743 // leak. Smoothed-Heaviside sum over the HRLE: band cells contribute
744 // clamp(0.5 - phi, 0, 1), negative background runs contribute fully.
745 // Only DIFFERENCES between stages are meaningful.
746 auto ambientVolume = [&]() -> T {
747 const T delta = ambientInterface->getGrid().getGridDelta();
748 T cellVol = T(1);
749 for (unsigned d = 0; d < D; ++d)
750 cellVol *= delta;
751 T vol = T(0);
752 for (viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>
753 it(ambientInterface->getDomain());
754 !it.isFinished(); it.next()) {
755 T cells = T(1);
756 for (unsigned d = 0; d < D; ++d)
757 cells *= T(it.getEndIndices(static_cast<int>(d)) -
758 it.getStartIndices(static_cast<int>(d)) + 1);
759 if (it.isDefined())
760 vol += std::min(std::max(T(0.5) - it.getValue(), T(0)), T(1)) *
761 cellVol * cells;
762 else if (it.getValue() < T(0))
763 vol += cellVol * cells;
764 }
765 return vol;
766 };
767 const T volBefore = ambientVolume();
768
769 // Pre-advection clip: keep oxide outside the mask body (LOCOS only).
770 if (hasMask)
771 BooleanOperation<T, D>(ambientInterface, maskInterface,
773 .apply();
774 const T volAfterPreclip = ambientVolume();
775
776 diffusionField->writePersistentFields();
777 deformationField->writeFieldsToLevelSet();
778
779 auto advect = [&](SmartPointer<Domain<T, D>> levelSet,
780 SmartPointer<VelocityField<T>> velocityField) {
781 Advect<T, D> adv;
782 adv.insertNextLevelSet(levelSet);
783 adv.setVelocityField(velocityField);
784 adv.setSpatialScheme(spatialScheme);
785 adv.setTemporalScheme(temporalScheme);
786 adv.setAdvectionTime(advectionTime);
787 adv.apply();
788 };
789
790 Timer<> tAdvect;
791 tAdvect.start();
792 advect(ambientInterface, ambientVelocity);
793 advect(siInterface, diffusionField);
794 if (hasMask && maskBarrierOnly_)
795 // Carried by the oxide: the contact face samples the oxide velocity in
796 // place; OxidationMaskCarry extends that same velocity through the mask
797 // body so the whole slab translates together.
798 advect(maskInterface,
799 OxidationMaskCarry<T, D>::New(deformationField, maskInterface));
800 else if (monolithicMask) {
801 if (!maskTracker_ || !maskTracker_->tracks(maskInterface))
802 maskTracker_.emplace(maskInterface);
803 advect(maskInterface,
804 OxidationMaterialVelocity<T, D>::New(deformationField, true));
805 maskTracker_->correct(
806 maskInterface,
807 [&](const Vec3D<T> &x) {
808 return deformationField->getMaterialVelocity(x, true);
809 },
810 advectionTime);
811 }
812 tAdvect.finish();
813 VIENNACORE_LOG_TIMING(std::string(" advection(") + (hasMask ? "3" : "2") +
814 " surfaces)",
815 tAdvect);
816 const T volAfterAdvect = ambientVolume();
817
818 // Post-advection clip: remove oxide that grew into the mask (LOCOS only).
819 // Mask gets Interior fill first so the BooleanOp has accurate φ_mask values
820 // at points inside the mask region.
821 if (hasMask) {
822 Interior<T, D>(maskInterface).apply();
823 BooleanOperation<T, D>(ambientInterface, maskInterface,
825 .apply();
826 }
827 const T volAfterPostclip = ambientVolume();
828 {
829 Interior<T, D> fill(ambientInterface);
830 fill.setGuide(siInterface); // stop fill at Si surface
831 fill.apply();
832 }
833 {
834 const T volAfterFill = ambientVolume();
835 const T toNm = std::pow(T(1000), static_cast<int>(D));
836 // vol_start/vol_end are ABSOLUTE (nm^D): vol_start(N+1) != vol_end(N)
837 // exposes volume changes between substeps (outside this pipeline).
838 VIENNACORE_LOG_INFO(
839 prefix + ": VOLTRACE dV_preclip=" +
840 std::to_string((volAfterPreclip - volBefore) * toNm) + " dV_advect=" +
841 std::to_string((volAfterAdvect - volAfterPreclip) * toNm) +
842 " dV_postclip=" +
843 std::to_string((volAfterPostclip - volAfterAdvect) * toNm) +
844 " dV_fill=" +
845 std::to_string((volAfterFill - volAfterPostclip) * toNm) +
846 " vol_start=" + std::to_string(volBefore * toNm) +
847 " vol_end=" + std::to_string(volAfterFill * toNm) + " nm^D");
848 }
849
850 // Re-write persistent fields (concentration, pressure) now that the HRLE
851 // has interior points. The first writePersistentFields() above only
852 // covered the narrow band; after lsAdvect the new HRLE is again a narrow
853 // band stripped of interior data. By re-writing here we ensure that the
854 // next outer step's buildNodes() can warm-start ALL oxide nodes — not just
855 // the surface band — when it reads back from pointData.
856 diffusionField->writePersistentFields();
857 deformationField->writeFieldsToLevelSet();
858
859 VIENNACORE_LOG_INFO(prefix + ": time step complete, actual_dt=" +
860 std::to_string(advectionTime) + " hr");
861
862 tStep.finish();
863 VIENNACORE_LOG_TIMING("── step total", tStep);
864
865 return advectionTime;
866 }
867};
868
869} // 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
viennahrle::Domain< T, D > DomainType
Definition lsDomain.hpp:32
Expand()=default
static auto New(Args &&...args)
Definition lsOxidationDeformation.hpp:246
const std::unordered_map< std::size_t, T > & getConcentrationCache() const
Definition lsOxidationDiffusion.hpp:705
std::unordered_map< std::size_t, T > PressureLookup
Definition lsOxidationDiffusion.hpp:360
static auto New(Args &&...args)
Definition lsOxidationDiffusion.hpp:274
std::unordered_map< std::size_t, std::array< T, 9 > > StressLookup
Definition lsOxidationDiffusion.hpp:358
static auto New(Args &&...args)
Definition lsOxidationMask.hpp:144
OxidationMaskTracker(SmartPointer< Domain< T, D > > mask)
Definition lsOxidation.hpp:192
void correct(SmartPointer< Domain< T, D > > mask, VelocityFunction &&velocity, T dt)
Re-sample the just advected mask: dt is the advection time, velocity (Vec3D<T>(const Vec3D<T> &coordi...
Definition lsOxidation.hpp:206
bool tracks(const SmartPointer< Domain< T, D > > &mask) const
False for another mask, or after the mask was resampled to another grid.
Definition lsOxidation.hpp:199
static auto New(Args &&...args)
Definition lsOxidationDeformation.hpp:4008
static auto New(Args &&...args)
Definition lsOxidationModel.hpp:48
void setCouplingParameters(OxidationCouplingParameters params)
Definition lsOxidation.hpp:459
static auto New(Args &&...args)
Definition lsOxidation.hpp:439
SmartPointer< OxidationDeformation< T, D > > getDeformationField() const
Return the deformation field populated by the most recent apply() call.
Definition lsOxidation.hpp:489
void setMaskBarrierOnly(bool enable)
Definition lsOxidation.hpp:469
void setDeformationParameters(OxidationDeformationParameters params)
Definition lsOxidation.hpp:456
T applyCFLLimited(T requestedTime, T cflFactor)
Execute one CFL-limited oxidation step; returns the actual time advanced.
Definition lsOxidation.hpp:500
const std::unordered_map< std::size_t, T > & getConcentrationCache() const
Definition lsOxidation.hpp:423
void setSolveBounds(const IndexType &minIndex, const IndexType &maxIndex)
Set the Cartesian index bounding box for the diffusion and deformation solves. If not set,...
Definition lsOxidation.hpp:477
void setSpatialScheme(SpatialSchemeEnum scheme)
Set the spatial integration scheme for all advections.
Definition lsOxidation.hpp:467
void setMaskInterface(SmartPointer< Domain< T, D > > mask)
Definition lsOxidation.hpp:449
void setMaskParameters(OxidationMaskParameters params)
Definition lsOxidation.hpp:462
void setGpuMode(GpuMode mode)
Definition lsOxidation.hpp:443
void setSiInterface(SmartPointer< Domain< T, D > > si)
Definition lsOxidation.hpp:445
SmartPointer< OxidationDiffusion< T, D > > getDiffusionField() const
Return the diffusion field populated by the most recent apply() call.
Definition lsOxidation.hpp:484
void setAmbientInterface(SmartPointer< Domain< T, D > > ambient)
Definition lsOxidation.hpp:446
Oxidation(SmartPointer< Domain< T, D > > passedSiInterface, SmartPointer< Domain< T, D > > passedAmbientInterface, SmartPointer< Domain< T, D > > passedMaskInterface=nullptr)
Definition lsOxidation.hpp:432
void setTemporalScheme(TemporalSchemeEnum scheme)
Set the temporal integration scheme for all advections.
Definition lsOxidation.hpp:472
void setOxidationParameters(OxidationParameters params)
Definition lsOxidation.hpp:453
void apply(T advectionTime)
Execute one oxidation time step of duration advectionTime.
Definition lsOxidation.hpp:497
T getLastMaxVelocity() const
Maximum interface velocity (µm/hr) from the most recent CFL-limited step.
Definition lsOxidation.hpp:494
void setConcentrationCache(std::unordered_map< std::size_t, T > cache)
Definition lsOxidation.hpp:426
Reduce()=default
Definition lsAdvect.hpp:41
SpatialSchemeEnum
Enumeration for the different spatial discretization schemes used by the advection kernel.
Definition lsAdvectIntegrationSchemes.hpp:10
@ ENGQUIST_OSHER_1ST_ORDER
Definition lsAdvectIntegrationSchemes.hpp:11
GpuMode
Where the large linear solves of the oxidation solvers run: the diffusion solve and the 3-D mechanics...
Definition lsOxidationDiffusion.hpp:43
@ Cpu
Always use CPU (default).
Definition lsOxidationDiffusion.hpp:44
std::optional< T > findLOCOSInterfaceY(SmartPointer< Domain< T, 2 > > levelSet, viennahrle::IndexType i, viennahrle::IndexType jMin, viennahrle::IndexType jMax)
Definition lsOxidation.hpp:37
TemporalSchemeEnum
Enumeration for the different time integration schemes used to select the advection kernel.
Definition lsAdvectIntegrationSchemes.hpp:31
@ FORWARD_EULER
Definition lsAdvectIntegrationSchemes.hpp:32
@ RELATIVE_COMPLEMENT
Definition lsBooleanOperation.hpp:30
LOCOSConservationDiagnostics< T > computeLOCOSOpenWindowConservation(SmartPointer< Domain< T, 2 > > siInitial, SmartPointer< Domain< T, 2 > > siAfter, SmartPointer< Domain< T, 2 > > ambientInitial, SmartPointer< Domain< T, 2 > > ambientAfter, T xMin, T xMax, viennahrle::IndexType jMin, viennahrle::IndexType jMax, T expansionCoefficient)
Measure the open-window volume balance after one 2D LOCOS step.
Definition lsOxidation.hpp:75
Definition lsOxidation.hpp:27
T ambientLiftRatio
Definition lsOxidation.hpp:31
T siliconRecession
Definition lsOxidation.hpp:28
T relativeError
Definition lsOxidation.hpp:32
unsigned samples
Definition lsOxidation.hpp:33
T ambientLift
Definition lsOxidation.hpp:29
T expectedAmbientLift
Definition lsOxidation.hpp:30
Definition lsOxidationModel.hpp:14
Parameters for the Cartesian-grid oxide deformation model.
Definition lsOxidationDeformation.hpp:38
Nitride (Si₃N₄) mask of the LOCOS process. The grid points inside the mask join the oxide mechanics s...
Definition lsOxidationMask.hpp:18
Parameters for the steady oxidant diffusion model used by OxidationDiffusion.
Definition lsOxidationDiffusion.hpp:56
Definition lsOxidationSolverBase.hpp:44