ViennaLS
Loading...
Searching...
No Matches
lsOxidationModel.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <lsOxidationMask.hpp>
4
5#include <algorithm>
6#include <array>
7#include <string>
8#include <vector>
9
10#include <vcTimer.hpp>
11
12namespace viennals {
13
15 unsigned maxIterations = 10;
16 double tolerance = 1e-6;
17 double relaxation = 1.;
18};
19
23template <class T, int D> class OxidationModel {
24 using IndexType = viennahrle::Index<D>;
25
26 SmartPointer<OxidationDiffusion<T, D>> diffusionField = nullptr;
27 SmartPointer<OxidationDeformation<T, D>> deformationField = nullptr;
29 IndexType minIndex{};
30 IndexType maxIndex{};
31 bool useRequestedBounds = false;
32 unsigned iterations = 0;
33 T residual = std::numeric_limits<T>::max();
34 bool converged_ = false;
35 std::string failureReason_;
36
37public:
38 OxidationModel() = default;
39
41 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField,
42 SmartPointer<OxidationDeformation<T, D>> passedDeformationField,
43 OxidationCouplingParameters passedParameters = {})
44 : diffusionField(passedDiffusionField),
45 deformationField(passedDeformationField), parameters(passedParameters) {
46 }
47
48 template <class... Args> static auto New(Args &&...args) {
49 return SmartPointer<OxidationModel>::New(std::forward<Args>(args)...);
50 }
51
53 SmartPointer<OxidationDiffusion<T, D>> passedDiffusionField) {
54 diffusionField = passedDiffusionField;
55 }
56
58 SmartPointer<OxidationDeformation<T, D>> passedDeformationField) {
59 deformationField = passedDeformationField;
60 }
61
63 parameters = passedParameters;
64 }
65
66 void setSolveBounds(const IndexType &passedMinIndex,
67 const IndexType &passedMaxIndex) {
68 minIndex = passedMinIndex;
69 maxIndex = passedMaxIndex;
70 useRequestedBounds = true;
71 }
72
73 void clearSolveBounds() { useRequestedBounds = false; }
74
75 void apply() {
76 iterations = 0;
77 residual = 0.;
78 converged_ = false;
79 failureReason_.clear();
80 if (diffusionField == nullptr || deformationField == nullptr) {
81 Logger::getInstance()
82 .addError("OxidationModel: Missing diffusion or deformation "
83 "field.")
84 .print();
85 return;
86 }
87
88 if (useRequestedBounds) {
89 diffusionField->setSolveBounds(minIndex, maxIndex);
90 deformationField->setSolveBounds(minIndex, maxIndex);
91 } else {
92 diffusionField->clearSolveBounds();
93 deformationField->clearSolveBounds();
94 }
95
96 // forEachSolutionNode order is deterministic on a fixed grid, so
97 // nodeIndices is collected once and the feedback vector is refilled
98 // in-place on subsequent iterations. Per node it holds the pressure
99 // and, with a reaction activation volume, the 9 Cauchy stress
100 // components (stride 10).
101 const auto oxParams = diffusionField->getParameters();
102 const bool stressFeedback = oxParams.reactionActivationVolume != 0.;
103 const bool pressureFeedback = oxParams.diffusionActivationVolume != 0.;
104 const T thermal = T(1.380649e-23) * std::max(T(oxParams.temperature), T(1));
105 const std::size_t stride = stressFeedback ? 10 : 1;
106 std::vector<IndexType> nodeIndices;
107 std::vector<T> p_raw;
108 std::vector<T> p_km1, p_k; // Aitken Δ² history
109 // One viscosity pass per coupling iteration (setCouplingIterate), off
110 // again on every exit of this solve.
111 struct CouplingIterateReset {
112 SmartPointer<OxidationDeformation<T, D>> field;
113 ~CouplingIterateReset() { field->setCouplingIterate(false); }
114 } couplingIterateReset{deformationField};
115
116 for (; iterations < parameters.maxIterations; ++iterations) {
117 VIENNACORE_LOG_DEBUG("OxidationModel: coupling iteration " +
118 std::to_string(iterations + 1) + "/" +
119 std::to_string(parameters.maxIterations) +
120 " starting diffusion solve");
121 diffusionField->apply();
122 VIENNACORE_LOG_DEBUG(
123 "OxidationModel: diffusion solve complete, nodes=" +
124 std::to_string(diffusionField->getNumberOfSolutionNodes()) +
125 ", iterations=" + std::to_string(diffusionField->getIterations()) +
126 ", residual=" + std::to_string(diffusionField->getResidual()));
127 if (!diffusionField->lastSolveConverged() ||
128 !diffusionField->hasFiniteConcentrationField()) {
129 residual = std::numeric_limits<T>::infinity();
130 failureReason_ =
131 "diffusion solve failed (residual=" +
132 std::to_string(diffusionField->getNormalizedResidual()) +
133 ", tolerance=" +
134 std::to_string(diffusionField->getParameters().tolerance) + ")";
135 Logger::getInstance()
136 .addWarning("OxidationModel: " + failureReason_ + ".")
137 .print();
138 return;
139 }
140
141 VIENNACORE_LOG_DEBUG("OxidationModel: coupling iteration " +
142 std::to_string(iterations + 1) + "/" +
143 std::to_string(parameters.maxIterations) +
144 " starting deformation solve");
145 if (stressFeedback || pressureFeedback)
146 deformationField->setCouplingIterate(true);
147 Timer<> tDeform;
148 tDeform.start();
149 deformationField->apply();
150 tDeform.finish();
151 VIENNACORE_LOG_DEBUG(
152 "OxidationModel: deformation solve complete, nodes=" +
153 std::to_string(deformationField->getNumberOfSolutionNodes()) +
154 ", iterations=" + std::to_string(deformationField->getIterations()) +
155 ", residual=" + std::to_string(deformationField->getResidual()));
156 if (!deformationField->lastSolveConverged() ||
157 !deformationField->hasFiniteSolution()) {
158 residual = std::numeric_limits<T>::infinity();
159 failureReason_ =
160 "deformation solve failed (mechanics=" +
161 std::to_string(deformationField->getResidual()) + ", continuity=" +
162 std::to_string(deformationField->getLastContinuityResidual()) +
163 ", stokes=" +
164 std::to_string(deformationField->getLastStokesResidual()) + ")";
165 VIENNACORE_LOG_WARNING("OxidationModel: " + failureReason_ + ".");
166 return;
167 }
168 if (Logger::hasTiming())
169 Logger::getInstance()
170 .addTiming(" deformation(couplingIter=" +
171 std::to_string(iterations + 1) + ")",
172 tDeform)
173 .print();
174
175 // Collect the raw feedback G(x_k) from deformation.
176 if (iterations == 0) {
177 nodeIndices.clear();
178 p_raw.clear();
179 deformationField->forEachSolutionNode(
180 [&](const IndexType &idx, T p, const std::array<T, 9> &stress) {
181 nodeIndices.push_back(idx);
182 p_raw.push_back(p);
183 if (stressFeedback)
184 p_raw.insert(p_raw.end(), stress.begin(), stress.end());
185 });
186 } else {
187 std::size_t ii = 0;
188 deformationField->forEachSolutionNode(
189 [&](const IndexType &, T p, const std::array<T, 9> &stress) {
190 p_raw[ii++] = p;
191 if (stressFeedback)
192 for (const T component : stress)
193 p_raw[ii++] = component;
194 });
195 }
196 const std::size_t n = p_raw.size();
197 // The first iterate is measured against the feedback the diffusion
198 // solve actually used (carried over from the previous step), not
199 // against zero: measured against zero, any stress below tolerance*kT/V
200 // passed after one iteration, so the stress->rate update ran once per
201 // time step — an explicit lag that let a stiff ripple double every step.
202 if (iterations == 0 && p_k.empty()) {
203 p_k.reserve(n);
204 for (const auto &idx : nodeIndices) {
205 T pressure;
206 std::array<T, 9> stress;
207 diffusionField->getFeedback(idx, pressure, stress);
208 p_k.push_back(pressure);
209 if (stressFeedback)
210 p_k.insert(p_k.end(), stress.begin(), stress.end());
211 }
212 }
213 if (!std::all_of(p_raw.begin(), p_raw.end(),
214 [](T value) { return std::isfinite(value); })) {
215 residual = std::numeric_limits<T>::infinity();
216 failureReason_ =
217 "deformation pressure feedback produced non-finite values";
218 VIENNACORE_LOG_WARNING("OxidationModel: " + failureReason_ + ".");
219 return;
220 }
221
222 // Aitken Δ²: find θ minimising ||(1-θ)F_{k-1} + θF_k||².
223 // Clamped to [0.1, 1.0]: no extrapolation (θ>1) because the
224 // pressure→concentration feedback is nonlinear (exponential kinetics)
225 // and overshooting the pressure causes oscillatory divergence.
226 T aitkenTheta = T(1);
227 if (p_k.size() == n && p_km1.size() == n) {
228 T dot_Fkm1_dF = T(0), dot_dF_dF = T(0);
229 for (std::size_t i = 0; i < n; ++i) {
230 const T Fkm1 = p_k[i] - p_km1[i];
231 const T Fk = p_raw[i] - p_k[i];
232 const T dF = Fk - Fkm1;
233 dot_Fkm1_dF += Fkm1 * dF;
234 dot_dF_dF += dF * dF;
235 }
236 if (dot_dF_dF > T(1e-100))
237 aitkenTheta =
238 std::max(T(0.1), std::min(T(1.0), -dot_Fkm1_dF / dot_dF_dF));
239 }
240
241 // Blend, and measure convergence on what the laws actually use: the
242 // pressure at every node (diffusivity law, V_D) and the interface
243 // normal stress after the median + smoothing (reaction law, V_k). Raw
244 // tensor components are the wrong measure: nodes the moving Si front
245 // has just uncovered carry no previous stress, so a max over them read
246 // 1.0 every step although the law fills them from their neighbours.
247 // Each change is relative to its own magnitude, floored at kT/V (a
248 // change small against kT/V does not move the rate).
249 std::vector<T> p_blended(n);
250 T maxChange = T(0), maxPressure = T(0);
251 for (std::size_t i = 0; i < n; ++i) {
252 const T oldP = p_k.empty() ? T(0) : p_k[i];
253 p_blended[i] = (T(1) - aitkenTheta) * oldP + aitkenTheta * p_raw[i];
254 if (i % stride == 0) {
255 maxChange = std::max(maxChange, std::abs(p_blended[i] - oldP));
256 maxPressure = std::max(maxPressure, std::abs(p_blended[i]));
257 }
258 }
259 residual = T(0);
260 if (pressureFeedback)
261 residual =
262 maxChange /
263 std::max(maxPressure,
264 thermal / std::abs(T(oxParams.diffusionActivationVolume)));
265 if (stressFeedback) {
267 candidate.reserve(nodeIndices.size());
268 for (std::size_t i = 0; i < nodeIndices.size(); ++i) {
269 std::array<T, 9> stress;
270 std::copy_n(p_blended.begin() + i * stride + 1, 9, stress.begin());
271 candidate[detail::gridIndexHash<D>(nodeIndices[i])] = stress;
272 }
273 const auto next = diffusionField->interfaceNormalStressFor(candidate);
274 const auto &used = diffusionField->getInterfaceNormalStress();
275 const T unstressed = -T(oxParams.referencePressure);
276 T maxStressChange = T(0), maxStress = T(0);
277 for (std::size_t i = 0; i < next.size() && i < used.size(); ++i) {
278 if (!std::isfinite(next[i]) && !std::isfinite(used[i]))
279 continue;
280 const T a = std::isfinite(next[i]) ? next[i] : unstressed;
281 const T b = std::isfinite(used[i]) ? used[i] : unstressed;
282 maxStressChange = std::max(maxStressChange, std::abs(a - b));
283 maxStress = std::max(maxStress, std::abs(a));
284 }
285 residual = std::max(
286 residual,
287 maxStressChange /
288 std::max(maxStress,
289 thermal /
290 std::abs(T(oxParams.reactionActivationVolume))));
291 }
292
293 // Converged: keep the feedback the last solves used. lsOxidation marks
294 // the diffusion solved before advecting (no re-solve), so feeding the
295 // new values here made the Si front move with a rate that neither the
296 // concentration nor the oxide flow had been solved with — an explicit
297 // lag that let a stiff stress ripple double every step.
298 if (residual < parameters.tolerance) {
299 // The advection uses the velocities of this last solve: finish its
300 // viscosity iteration (warm-started: a few iterations).
301 deformationField->setCouplingIterate(false);
302 if (stressFeedback || pressureFeedback)
303 deformationField->apply();
304 break;
305 }
306
307 // Feed the blended pressures and stresses to diffusion.
308 for (std::size_t i = 0; i < nodeIndices.size(); ++i) {
309 diffusionField->setPressure(nodeIndices[i], p_blended[i * stride]);
310 if (stressFeedback) {
311 std::array<T, 9> stress;
312 std::copy_n(p_blended.begin() + i * stride + 1, 9, stress.begin());
313 diffusionField->setStress(nodeIndices[i], stress);
314 }
315 }
316
317 // Shift Aitken history; seed p_km1 with zeros on the first iteration.
318 p_km1 = p_k.empty() ? std::vector<T>(n, T(0)) : std::move(p_k);
319 p_k = p_blended;
320
321 VIENNACORE_LOG_DEBUG(
322 "OxidationModel: coupling iteration " +
323 std::to_string(iterations + 1) +
324 " pressure-feedback residual=" + std::to_string(residual));
325 if (residual < parameters.tolerance)
326 break;
327 }
328 const unsigned completedIterations =
329 std::min(iterations + 1, parameters.maxIterations);
330 VIENNACORE_LOG_DEBUG("OxidationModel: coupled solve complete, iterations=" +
331 std::to_string(completedIterations) +
332 ", residual=" + std::to_string(residual));
333 converged_ = std::isfinite(residual) && residual <= parameters.tolerance;
334 if (!converged_)
335 VIENNACORE_LOG_WARNING(
336 "OxidationModel: pressure-concentration coupling did not "
337 "converge after " +
338 std::to_string(completedIterations) +
339 " iterations (residual=" + std::to_string(residual) +
340 ", tolerance=" + std::to_string(parameters.tolerance) +
341 "). Consider increasing maxIterations or relaxation.");
342 }
343
344 unsigned getIterations() const { return iterations; }
345 T getResidual() const { return residual; }
346 bool hasConverged() const { return converged_; }
347 std::string getFailureReason() const { return failureReason_; }
348};
349
350} // namespace viennals
double T
Definition Epitaxy.cpp:13
Propagates the volume expansion generated at the Si/SiO2 interface through the oxide as a Cartesian-g...
Definition lsOxidationDeformation.hpp:112
Solves the oxidant diffusion step of the Suvorov et al. (10.1007/s10825-006-0003-z) oxidation model o...
Definition lsOxidationDiffusion.hpp:126
std::unordered_map< std::size_t, std::array< T, 9 > > StressLookup
Definition lsOxidationDiffusion.hpp:358
std::vector< T > interfaceNormalStressFor(const StressLookup &lookup) const
The interface normal stress the reaction law would use with lookup: the median sample at every node w...
Definition lsOxidationDiffusion.hpp:385
void setSolveBounds(const IndexType &passedMinIndex, const IndexType &passedMaxIndex)
Definition lsOxidationModel.hpp:66
void setDeformationField(SmartPointer< OxidationDeformation< T, D > > passedDeformationField)
Definition lsOxidationModel.hpp:57
static auto New(Args &&...args)
Definition lsOxidationModel.hpp:48
T getResidual() const
Definition lsOxidationModel.hpp:345
OxidationModel(SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField, SmartPointer< OxidationDeformation< T, D > > passedDeformationField, OxidationCouplingParameters passedParameters={})
Definition lsOxidationModel.hpp:40
bool hasConverged() const
Definition lsOxidationModel.hpp:346
void setDiffusionField(SmartPointer< OxidationDiffusion< T, D > > passedDiffusionField)
Definition lsOxidationModel.hpp:52
unsigned getIterations() const
Definition lsOxidationModel.hpp:344
void clearSolveBounds()
Definition lsOxidationModel.hpp:73
void apply()
Definition lsOxidationModel.hpp:75
std::string getFailureReason() const
Definition lsOxidationModel.hpp:347
void setParameters(OxidationCouplingParameters passedParameters)
Definition lsOxidationModel.hpp:62
std::size_t gridIndexHash(const viennahrle::Index< D > &index)
Definition lsOxidationSolverBase.hpp:30
Definition lsAdvect.hpp:41
Definition lsOxidationModel.hpp:14
double tolerance
Definition lsOxidationModel.hpp:16
double relaxation
Definition lsOxidationModel.hpp:17
unsigned maxIterations
Definition lsOxidationModel.hpp:15