ViennaLS
Loading...
Searching...
No Matches
lsOxidationSolverBase.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <lsDomain.hpp>
4
5#include <hrleSparseIterator.hpp>
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12#include <functional>
13#include <limits>
14#include <string>
15#include <vector>
16
17namespace viennals {
18
19using namespace viennacore;
20
21namespace detail {
22
23// Key of a grid index. The per-node maps (pressure, stress and
24// concentration of the oxidation solvers, the mask carry velocity) are keyed
25// by this value alone, so it must be injective: the coordinates are packed
26// into 64 bits, 32 per axis in 2-D and 21 in 3-D (|index| < 2^20). The
27// hash_combine it replaces collided for 9 in 10 indices of a 5 nm trench
28// domain ((x, y) with (x - 1, y + 67)), and nodes read each other's stress.
29template <int D>
30inline std::size_t gridIndexHash(const viennahrle::Index<D> &index) {
31 static_assert(D == 2 || D == 3);
32 constexpr unsigned bits = D == 3 ? 21 : 32;
33 constexpr std::uint64_t mask = (std::uint64_t(1) << bits) - 1;
34 constexpr std::int64_t offset = std::int64_t(1) << (bits - 1);
35 std::uint64_t key = 0;
36 for (unsigned i = 0; i < static_cast<unsigned>(D); ++i)
37 key = (key << bits) | (static_cast<std::uint64_t>(
38 static_cast<std::int64_t>(index[i]) + offset) &
39 mask);
40 return static_cast<std::size_t>(key);
41}
42
43// Hash functor for viennahrle::Index<D> (the key above).
44template <int D> struct IndexTypeHasher {
45 std::size_t operator()(const viennahrle::Index<D> &idx) const {
46 return gridIndexHash<D>(idx);
47 }
48};
49
50template <class T> inline Vec3D<T> vecScaled(const Vec3D<T> &source, T factor) {
51 Vec3D<T> result{0., 0., 0.};
52 for (unsigned i = 0; i < 3; ++i)
53 result[i] = source[i] * factor;
54 return result;
55}
56
57template <class T>
58inline Vec3D<T> vecAdd(const Vec3D<T> &a, const Vec3D<T> &b) {
59 Vec3D<T> result{0., 0., 0.};
60 for (unsigned i = 0; i < 3; ++i)
61 result[i] = a[i] + b[i];
62 return result;
63}
64
65template <class T>
66inline Vec3D<T> vecSubtract(const Vec3D<T> &a, const Vec3D<T> &b) {
67 Vec3D<T> result{0., 0., 0.};
68 for (unsigned i = 0; i < 3; ++i)
69 result[i] = a[i] - b[i];
70 return result;
71}
72
73template <class T>
74inline void vecAddTo(Vec3D<T> &target, const Vec3D<T> &source) {
75 for (unsigned i = 0; i < 3; ++i)
76 target[i] += source[i];
77}
78
81template <class T> inline T clampLevelSetPhi(T v) {
82 return v > T(1) ? T(1) : (v < T(-1) ? T(-1) : v);
83}
84
85template <class T>
86inline T levelSetCrossingDistance(T insidePhi, T outsidePhi,
87 T minBoundaryFraction, T gridDelta) {
88 const T denom = std::abs(insidePhi) + std::abs(outsidePhi);
89 if (denom <= std::numeric_limits<T>::epsilon())
90 return gridDelta;
91 return std::max(minBoundaryFraction * gridDelta,
92 gridDelta * std::abs(insidePhi) / denom);
93}
94
95} // namespace detail
96
100template <class T, int D> class OxidationSolverBase {
101protected:
102 using IndexType = viennahrle::Index<D>;
104 viennahrle::ConstSparseIterator<typename Domain<T, D>::DomainType>;
105
106 static constexpr std::size_t noNode = std::numeric_limits<std::size_t>::max();
107 std::vector<std::size_t> nodeLookupFlat;
110 std::array<std::size_t, D> extents{};
111 std::array<std::size_t, D> strides{};
113 // Box sides on a reflective domain boundary: the mirror plane through the
114 // outermost grid points.
115 std::array<bool, D> mirrorLow_{}, mirrorHigh_{};
116
117 template <class GridType> void setMirrorSides(const GridType &grid) {
118 for (unsigned i = 0; i < D; ++i) {
119 const bool reflective = grid.getBoundaryConditions(i) ==
120 viennahrle::BoundaryType::REFLECTIVE_BOUNDARY;
121 mirrorLow_[i] = reflective && minIndex[i] == grid.getMinGridPoint(i);
122 mirrorHigh_[i] = reflective && maxIndex[i] == grid.getMaxGridPoint(i);
123 }
124 }
125
126 // The stencil side (direction, offset) of the grid point at index: beyond
127 // a mirror side the neighbour is the mirror image of the opposite one, so
128 // the side is -offset; else offset.
129 int stencilOffset(const IndexType &index, unsigned direction,
130 int offset) const {
131 IndexType neighbor = index;
132 neighbor[direction] += offset;
133 if (inBounds(neighbor) ||
134 !(offset < 0 ? mirrorLow_[direction] : mirrorHigh_[direction]))
135 return offset;
136 IndexType opposite = index;
137 opposite[direction] -= offset;
138 return inBounds(opposite) ? -offset : offset;
139 }
140
146
147 bool crosses(T a, T b) const {
148 // Use a small tolerance consistent with isInsideOxide so that grid points
149 // that land exactly on an interface (phi ≈ 0 from floating-point rounding
150 // in GeometricAdvect) are treated as surface points in boundary detection.
151 constexpr T eps = T(1e-9);
152 return (a <= eps && b >= -eps) || (a >= -eps && b <= eps);
153 }
154
155 T valueAt(ConstSparseIterator &it, const IndexType &index) const {
156 it.goToIndices(index);
157 return it.getValue();
158 }
159
160 // Gradient direction of a level set at a grid point. Per axis the central
161 // difference where both neighbours are defined, one-sided (with the point
162 // itself) where one is. Clamping phi to one grid unit before differencing
163 // -- the earlier form, kept only where the point itself is outside the
164 // narrow band -- clipped the steeper component next to the interface and
165 // turned the normals of tilted fronts towards the diagonal: a 30-degree
166 // front by 2.6 degrees at phi = 0.3 and 5.4 at 0.6, at any grid spacing.
167 // onGrid(idx, ok) maps a neighbour onto the grid (e.g. a reflection) and
168 // clears ok if it is off the grid. Zero if degenerate.
169 template <class OnGrid>
171 const IndexType &index,
172 OnGrid onGrid) const {
173 // value at idx (the point itself if idx is off the grid), and whether
174 // it is a defined value
175 auto sample = [&](const IndexType &idx, T &value) {
176 bool ok = true;
177 const IndexType q = onGrid(idx, ok);
178 it.goToIndices(ok ? q : index);
179 value = it.getValue();
180 return ok && it.isDefined() && std::isfinite(value);
181 };
182 T centre = T(0);
183 const bool centreDefined = sample(index, centre);
184 Vec3D<T> gradient{T(0), T(0), T(0)};
185 for (unsigned d = 0; d < D; ++d) {
186 IndexType plus = index, minus = index;
187 plus[d] += 1;
188 minus[d] -= 1;
189 T vp = T(0), vm = T(0);
190 const bool okP = sample(plus, vp), okM = sample(minus, vm);
191 if (okP && okM)
192 gradient[d] = T(0.5) * (vp - vm);
193 else if (centreDefined && okP)
194 gradient[d] = vp - centre;
195 else if (centreDefined && okM)
196 gradient[d] = centre - vm;
197 else
198 gradient[d] = T(0.5) * (detail::clampLevelSetPhi(vp) -
200 }
201 return gradient;
202 }
203
204 bool inBounds(const IndexType &index) const {
205 for (unsigned i = 0; i < D; ++i)
206 if (index[i] < minIndex[i] || index[i] > maxIndex[i])
207 return false;
208 return true;
209 }
210
212 std::size_t total = 1;
213 for (unsigned i = 0; i < D; ++i)
214 total *= extents[i];
215 nodeLookupFlat.assign(total, noNode);
216 }
217
218 std::size_t lookupNode(const IndexType &index) const {
219 if (!inBounds(index))
220 return noNode;
221 return nodeLookupFlat[linearIndex(index)];
222 }
223
224 std::size_t linearIndex(const IndexType &index) const {
225 if (!inBounds(index))
226 return std::numeric_limits<std::size_t>::max();
227 std::size_t result = 0;
228 for (unsigned i = 0; i < D; ++i)
229 result += static_cast<std::size_t>(index[i] - minIndex[i]) * strides[i];
230 return result;
231 }
232
233 bool increment(IndexType &index) const {
234 for (unsigned i = 0; i < D; ++i) {
235 if (index[i] < maxIndex[i]) {
236 ++index[i];
237 return true;
238 }
239 index[i] = minIndex[i];
240 }
241 return false;
242 }
243
244 // Searches with expanding radius shells so that RK2 Stage 2 can find oxide
245 // nodes even after the surface has advanced several grid cells beyond the
246 // band.
247 std::size_t findNearbyNode(const IndexType &index) const {
248 for (int radius = 1; radius <= 4; ++radius) {
249 T bestDistance2 = std::numeric_limits<T>::max();
250 std::size_t bestNode = std::numeric_limits<std::size_t>::max();
251
252 IndexType offset{};
253 offset.fill(-radius);
254 while (true) {
255 IndexType candidate = index;
256 T distance2 = 0.;
257 for (unsigned i = 0; i < D; ++i) {
258 candidate[i] += offset[i];
259 distance2 += static_cast<T>(offset[i] * offset[i]);
260 }
261
262 if (distance2 > 0 && inBounds(candidate)) {
263 const std::size_t foundId = nodeLookupFlat[linearIndex(candidate)];
264 if (foundId != noNode && distance2 < bestDistance2) {
265 bestDistance2 = distance2;
266 bestNode = foundId;
267 }
268 }
269
270 unsigned dim = 0;
271 for (; dim < D; ++dim) {
272 if (offset[dim] < radius) {
273 ++offset[dim];
274 break;
275 }
276 offset[dim] = -radius;
277 }
278 if (dim == D)
279 break;
280 }
281
282 if (bestNode != std::numeric_limits<std::size_t>::max())
283 return bestNode;
284 }
285 return std::numeric_limits<std::size_t>::max();
286 }
287
288 // All nodes at the smallest distance from index (the first shell, up to 4
289 // cells, holding a node that passes accept(j)). Averaging them instead of
290 // taking the first one found keeps a result free of the scan order (-y
291 // first, then -x), which moved the free surface of a stress-free oxide
292 // cylinder by up to 4 nm in y in 0.3 hr.
293 template <class Accept>
294 std::vector<std::size_t> findNearbyNodes(const IndexType &index,
295 Accept accept) const {
296 std::vector<std::size_t> best;
297 for (int radius = 1; radius <= 4 && best.empty(); ++radius) {
298 long bestDistance2 = std::numeric_limits<long>::max();
299 IndexType offset{};
300 offset.fill(-radius);
301 while (true) {
302 IndexType candidate = index;
303 long distance2 = 0;
304 for (unsigned i = 0; i < D; ++i) {
305 candidate[i] += offset[i];
306 distance2 += static_cast<long>(offset[i]) * offset[i];
307 }
308 if (distance2 > 0 && distance2 <= bestDistance2 &&
309 inBounds(candidate)) {
310 const std::size_t foundId = nodeLookupFlat[linearIndex(candidate)];
311 if (foundId != noNode && accept(foundId)) {
312 if (distance2 < bestDistance2) {
313 best.clear();
314 bestDistance2 = distance2;
315 }
316 best.push_back(foundId);
317 }
318 }
319 unsigned dim = 0;
320 for (; dim < D; ++dim) {
321 if (offset[dim] < radius) {
322 ++offset[dim];
323 break;
324 }
325 offset[dim] = -radius;
326 }
327 if (dim == D)
328 break;
329 }
330 }
331 return best;
332 }
333
335 SmartPointer<Domain<T, D>> reactionInterface,
336 SmartPointer<Domain<T, D>> ambientInterface,
337 SmartPointer<Domain<T, D>> maskInterface, bool useRequestedBounds,
338 const IndexType &requestedMinIndex, const IndexType &requestedMaxIndex,
339 std::size_t maxGridPoints, const std::string &solverName) {
340 auto &reactionGrid = reactionInterface->getGrid();
341 auto &ambientGrid = ambientInterface->getGrid();
342 gridDelta = reactionGrid.getGridDelta();
343
344 if (std::abs(gridDelta - ambientGrid.getGridDelta()) >
345 std::numeric_limits<T>::epsilon() ||
346 (maskInterface != nullptr &&
347 std::abs(gridDelta - maskInterface->getGrid().getGridDelta()) >
348 std::numeric_limits<T>::epsilon())) {
349 Logger::getInstance()
350 .addError(solverName + ": Interface grid deltas must match.")
351 .print();
352 return false;
353 }
354
355 IndexType gridMin = reactionGrid.getMinGridPoint();
356 IndexType gridMax = reactionGrid.getMaxGridPoint();
357 for (unsigned i = 0; i < D; ++i) {
358 gridMin[i] = std::max(gridMin[i], ambientGrid.getMinGridPoint(i));
359 gridMax[i] = std::min(gridMax[i], ambientGrid.getMaxGridPoint(i));
360 if (maskInterface != nullptr) {
361 gridMin[i] =
362 std::max(gridMin[i], maskInterface->getGrid().getMinGridPoint(i));
363 gridMax[i] =
364 std::min(gridMax[i], maskInterface->getGrid().getMaxGridPoint(i));
365 }
366 }
367
368 auto bounds = definedPointBounds(reactionInterface);
369 mergeDefinedPointBounds(bounds, ambientInterface);
370 if (maskInterface != nullptr)
371 mergeDefinedPointBounds(bounds, maskInterface);
372
373 minIndex = bounds.foundDefinedPoints ? bounds.min : gridMin;
374 maxIndex = bounds.foundDefinedPoints ? bounds.max : gridMax;
375 applyBoundsPaddingAndClamp(minIndex, maxIndex, gridMin, gridMax);
376
377 std::size_t numGridPoints = 1;
378 for (unsigned i = 0; i < D; ++i) {
379 if (useRequestedBounds) {
380 minIndex[i] = std::max(minIndex[i], requestedMinIndex[i]);
381 maxIndex[i] = std::min(maxIndex[i], requestedMaxIndex[i]);
382 }
383 if (maxIndex[i] < minIndex[i]) {
384 Logger::getInstance()
385 .addError(solverName + ": Cartesian solve region is empty.")
386 .print();
387 return false;
388 }
389 extents[i] = static_cast<std::size_t>(maxIndex[i] - minIndex[i] + 1);
390 numGridPoints *= extents[i];
391 strides[i] = (i == 0) ? 1 : strides[i - 1] * extents[i - 1];
392 }
393
394 if (numGridPoints > maxGridPoints) {
395 Logger::getInstance()
396 .addError(solverName +
397 ": Cartesian solve region exceeds maxGridPoints.")
398 .print();
399 return false;
400 }
401 setMirrorSides(reactionGrid);
402 return true;
403 }
404
405 bool initializeGridFromMask(SmartPointer<Domain<T, D>> maskInterface,
406 bool useRequestedBounds,
407 const IndexType &requestedMinIndex,
408 const IndexType &requestedMaxIndex,
409 std::size_t maxGridPoints,
410 const std::string &solverName) {
411 auto &grid = maskInterface->getGrid();
412 gridDelta = grid.getGridDelta();
413
414 auto bounds = definedPointBounds(maskInterface);
415 minIndex = bounds.foundDefinedPoints ? bounds.min : grid.getMinGridPoint();
416 maxIndex = bounds.foundDefinedPoints ? bounds.max : grid.getMaxGridPoint();
417 applyBoundsPaddingAndClamp(minIndex, maxIndex, grid.getMinGridPoint(),
418 grid.getMaxGridPoint());
419
420 std::size_t numGridPoints = 1;
421 for (unsigned i = 0; i < D; ++i) {
422 if (useRequestedBounds) {
423 minIndex[i] = std::max(minIndex[i], requestedMinIndex[i]);
424 maxIndex[i] = std::min(maxIndex[i], requestedMaxIndex[i]);
425 }
426 if (maxIndex[i] < minIndex[i]) {
427 Logger::getInstance()
428 .addError(solverName + ": Cartesian solve region is empty.")
429 .print();
430 return false;
431 }
432 extents[i] = static_cast<std::size_t>(maxIndex[i] - minIndex[i] + 1);
433 numGridPoints *= extents[i];
434 strides[i] = (i == 0) ? 1 : strides[i - 1] * extents[i - 1];
435 }
436
437 if (numGridPoints > maxGridPoints) {
438 Logger::getInstance()
439 .addError(solverName +
440 ": Cartesian solve region exceeds maxGridPoints.")
441 .print();
442 return false;
443 }
444 setMirrorSides(grid);
445 return true;
446 }
447
448private:
449 GridBounds definedPointBounds(SmartPointer<Domain<T, D>> levelSet) const {
450 GridBounds bounds;
451 ConstSparseIterator it(levelSet->getDomain());
452 for (; !it.isFinished(); ++it) {
453 if (!it.isDefined())
454 continue;
455
456 const auto &index = it.getStartIndices();
457 if (!bounds.foundDefinedPoints) {
458 bounds.min = index;
459 bounds.max = index;
460 bounds.foundDefinedPoints = true;
461 } else {
462 for (unsigned i = 0; i < D; ++i) {
463 bounds.min[i] = std::min(bounds.min[i], index[i]);
464 bounds.max[i] = std::max(bounds.max[i], index[i]);
465 }
466 }
467 }
468 return bounds;
469 }
470
471 void mergeDefinedPointBounds(GridBounds &target,
472 SmartPointer<Domain<T, D>> levelSet) const {
473 const auto source = definedPointBounds(levelSet);
474 if (!source.foundDefinedPoints)
475 return;
476 if (!target.foundDefinedPoints) {
477 target = source;
478 return;
479 }
480 for (unsigned i = 0; i < D; ++i) {
481 target.min[i] = std::min(target.min[i], source.min[i]);
482 target.max[i] = std::max(target.max[i], source.max[i]);
483 }
484 }
485
486 static void applyBoundsPaddingAndClamp(IndexType &minIndex,
488 const IndexType &gridMin,
489 const IndexType &gridMax) {
490 constexpr int padding = 4;
491 for (unsigned i = 0; i < D; ++i) {
492 // Clamp to the physical grid before adding/subtracting padding.
493 // lsInterior on an INFINITE_BOUNDARY level-set can leave far-field
494 // sentinel entries (near std::numeric_limits<IndexType>::min/max) in
495 // the HRLE structure. definedPointBounds() visits them and records
496 // the extreme index. Subtracting padding from that sentinel directly
497 // overflows, turning the tiny sentinel into a large positive value and
498 // making extents[i] enormous. Clamping first makes the arithmetic safe.
499 minIndex[i] =
500 std::max(gridMin[i], std::max(gridMin[i], minIndex[i]) - padding);
501 maxIndex[i] =
502 std::min(gridMax[i], std::min(gridMax[i], maxIndex[i]) + padding);
503 }
504 }
505};
506
507} // namespace viennals
constexpr int D
Definition Epitaxy.cpp:12
double T
Definition Epitaxy.cpp:13
double bounds[2 *D]
Definition OxidationNitride.cpp:40
constexpr T gridDelta
Definition OxidationNitride.cpp:35
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::array< bool, D > mirrorHigh_
Definition lsOxidationSolverBase.hpp:115
void setMirrorSides(const GridType &grid)
Definition lsOxidationSolverBase.hpp:117
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
bool initializeGridFromMask(SmartPointer< Domain< T, D > > maskInterface, bool useRequestedBounds, const IndexType &requestedMinIndex, const IndexType &requestedMaxIndex, std::size_t maxGridPoints, const std::string &solverName)
Definition lsOxidationSolverBase.hpp:405
std::vector< std::size_t > nodeLookupFlat
Definition lsOxidationSolverBase.hpp:107
std::array< bool, D > mirrorLow_
Definition lsOxidationSolverBase.hpp:115
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
int stencilOffset(const IndexType &index, unsigned direction, int offset) const
Definition lsOxidationSolverBase.hpp:129
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
Definition lsOxidationSolverBase.hpp:21
Vec3D< T > vecAdd(const Vec3D< T > &a, const Vec3D< T > &b)
Definition lsOxidationSolverBase.hpp:58
T levelSetCrossingDistance(T insidePhi, T outsidePhi, T minBoundaryFraction, T gridDelta)
Definition lsOxidationSolverBase.hpp:86
Vec3D< T > vecSubtract(const Vec3D< T > &a, const Vec3D< T > &b)
Definition lsOxidationSolverBase.hpp:66
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
std::size_t gridIndexHash(const viennahrle::Index< D > &index)
Definition lsOxidationSolverBase.hpp:30
void vecAddTo(Vec3D< T > &target, const Vec3D< T > &source)
Definition lsOxidationSolverBase.hpp:74
Definition lsAdvect.hpp:41
Definition lsOxidationSolverBase.hpp:141
IndexType max
Definition lsOxidationSolverBase.hpp:143
bool foundDefinedPoints
Definition lsOxidationSolverBase.hpp:144
IndexType min
Definition lsOxidationSolverBase.hpp:142
Definition lsOxidationSolverBase.hpp:44
std::size_t operator()(const viennahrle::Index< D > &idx) const
Definition lsOxidationSolverBase.hpp:45