Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
src/InclusionMicrostructure/EllipsoidalInclusion.cpp
Go to the documentation of this file.
1/* This file is part of MoDELib, the Mechanics Of Defects Evolution Library.
2 * MoDELib is distributed without any warranty under the
3 * GNU General Public License (GPL) v2 <http://www.gnu.org/licenses/>.
4 */
5#ifndef model_EllipsoidalInclusion_cpp_
6#define model_EllipsoidalInclusion_cpp_
7
9#include <algorithm>
10#include <atomic>
11#include <cmath>
12#include <functional>
13#include <limits>
14#include <stdexcept>
15
16namespace
17{
18 Eigen::Matrix3d checkedStrain(const Eigen::Matrix3d &e, const double nu, const double mu)
19 {
20 if(!e.allFinite() || !std::isfinite(nu) || !std::isfinite(mu) || mu <= 0.0 || nu <= -1.0 || nu >= 0.5)
21 {
22 throw std::invalid_argument("EllipsoidalInclusion: invalid strain or elastic constants");
23 }
24 const double scale = e.cwiseAbs().maxCoeff();
25 if(scale > 0.0 && (e / scale - e.transpose() / scale).norm() > 1.0e-12)
26 {
27 throw std::invalid_argument("EllipsoidalInclusion: eigenstrain must be symmetric");
28 }
29 return (0.5 * (e + e.transpose())).eval();
30 }
31} // namespace
32
33namespace model
34{
35 template <int dim>
36 EllipsoidalInclusion<dim>::EllipsoidalInclusion(const VectorDim &_C, const MatrixDim &_axes, const MatrixDim &_eT,
37 const double &_nu, const double &_mu,
38 const double &_mobilityReduction, const int &_phaseID,
39 const std::shared_ptr<SecondPhase<dim>> &sph,
40 const double quadratureTolerance,
41 const unsigned int quadratureMaxDepth)
42 : EshelbyInclusionBase<dim>(checkedStrain(_eT, _nu, _mu), _nu, _mu, _mobilityReduction, _phaseID, sph), C(_C),
43 axes(_axes), lengthScale(0.0), rotation(_axes), axisProduct(0.0), tolerance(quadratureTolerance),
44 maxDepth(quadratureMaxDepth)
45 {
46 if(!C.allFinite() || !axes.allFinite() || !std::isfinite(tolerance) || tolerance < 1.0e-14 ||
47 tolerance > 1.0e-3 || maxDepth == 0 || maxDepth > 50)
48 {
49 throw std::invalid_argument("EllipsoidalInclusion: invalid geometry or quadrature settings");
50 }
51 VectorDim lengths;
52 for(int i = 0; i < 3; ++i)
53 {
54 lengths(i) = rotation.col(i).stableNorm();
55 if(!(lengths(i) > 0.0) || !std::isfinite(lengths(i)))
56 {
57 throw std::invalid_argument("EllipsoidalInclusion: semiaxes must have positive finite lengths");
58 }
59 rotation.col(i) /= lengths(i);
60 }
61 if((rotation.transpose() * rotation - MatrixDim::Identity()).norm() > 1.0e-12)
62 {
63 throw std::invalid_argument("EllipsoidalInclusion: axes columns must be mutually orthogonal");
64 }
65 // Remove roundoff-size nonorthogonality, preserving handedness.
66 rotation.col(1) =
67 (rotation.col(1) - rotation.col(0) * rotation.col(0).dot(rotation.col(1))).normalized().eval();
68 VectorDim third = rotation.col(0).cross(rotation.col(1));
69 if(third.dot(rotation.col(2)) < 0.0)
70 {
71 third = -third;
72 }
73 rotation.col(2) = third;
74 lengthScale = lengths.maxCoeff();
75 lengths /= lengthScale;
76 // A deliberate conditioning bound; do not silently return unreliable stresses.
77 if(lengths.minCoeff() < 1.0e-6)
78 {
79 throw std::invalid_argument("EllipsoidalInclusion: semiaxis ratio exceeds 1e6");
80 }
81 axisSquared = lengths.array().square().matrix();
82 axisProduct = lengths.prod();
83 interiorIntegrals = integrate(0.0);
84 interiorStressCache = std::make_shared<const InteriorStressCache>(
85 InteriorStressCache{this->eT, evaluateStress(VectorDim::Zero(), true, this->eT)});
86 }
87
88 template <int dim>
89 typename EllipsoidalInclusion<dim>::VectorDim EllipsoidalInclusion<dim>::localPosition(const VectorDim &x) const
90 {
91 if(!x.allFinite())
92 {
93 throw std::invalid_argument("EllipsoidalInclusion: nonfinite observation point");
94 }
95 const VectorDim r = rotation.transpose() * ((x - C) / lengthScale);
96 if(!r.allFinite() || !std::isfinite(r.squaredNorm()))
97 {
98 throw std::overflow_error("EllipsoidalInclusion: observation point exceeds numerical range");
99 }
100 return r;
101 }
102
103 template <int dim> bool EllipsoidalInclusion<dim>::contains(const VectorDim &x) const
104 {
105 const VectorDim r = localPosition(x);
106 return (r.array().square() / axisSquared.array()).sum() < 1.0;
107 }
108
109 template <int dim> double EllipsoidalInclusion<dim>::ellipsoidalCoordinate(const VectorDim &r) const
110 {
111 // f(ell)=sum r_i^2/(a_i^2+ell)-1 is decreasing and convex.
112 // max(0, |r|^2-max(a_i^2)) <= root <= |r|^2.
113 const VectorDim rSquared = r.array().square().matrix();
114 const double radiusSquared = rSquared.sum();
115 const double smallestAxisSquared = axisSquared.minCoeff();
116 const double relativeTolerance = 8.0 * std::numeric_limits<double>::epsilon();
117 double lo = std::max(0.0, radiusSquared - axisSquared.maxCoeff());
118 double hi = radiusSquared;
119 double ell = lo;
120 double previousStep = hi - lo;
121 for(unsigned int k = 0; k < 256; ++k)
122 {
123 const double positionTolerance = relativeTolerance * (smallestAxisSquared + ell);
124 if(hi - lo <= positionTolerance)
125 {
126 return lo + 0.5 * (hi - lo);
127 }
128 const VectorDim denominator = (axisSquared.array() + ell).matrix();
129 const VectorDim terms = (rSquared.array() / denominator.array()).matrix();
130 const double f = terms.sum() - 1.0;
131 const double minusDerivative = (terms.array() / denominator.array()).sum();
132 const double correction = f / minusDerivative;
133 // Do not require the bracket to collapse after Newton has converged.
134 if(f == 0.0 || (std::abs(f) <= relativeTolerance && std::abs(correction) <= positionTolerance))
135 {
136 return ell;
137 }
138 if(f > 0.0)
139 {
140 lo = ell;
141 }
142 else
143 {
144 hi = ell;
145 }
146 const double newton = ell + correction;
147 double next = newton;
148 if(!std::isfinite(newton) || newton <= lo || newton >= hi || std::abs(correction) > 0.5 * previousStep)
149 {
150 // Preserve a valid bracket when Newton is unsafe or too slow.
151 next = lo + 0.5 * (hi - lo);
152 }
153 previousStep = std::abs(next - ell);
154 ell = next;
155 }
156 throw std::runtime_error("EllipsoidalInclusion: ellipsoidal coordinate did not converge");
157 }
158
159 template <int dim>
160 typename EllipsoidalInclusion<dim>::Integrals EllipsoidalInclusion<dim>::integrate(const double ell) const
161 {
162 // J_i=int ds/((a_i^2+s) Delta), K_ij=int s ds/((a_i^2+s)(a_j^2+s) Delta).
163 // s=(1+ell)/t^2-1 maps infinity to t=0, eliminating the endpoint singularity.
164 // Integrate values scaled by (1+ell)^(3/2) so far-field accuracy is relative.
165 const double h = 1.0 + ell;
166 const auto integrand = [&](double t) -> Integrals
167 {
168 const double t2 = t * t, oneMinusT2 = (1.0 - t) * (1.0 + t);
169 // This form avoids cancellation for very small semiaxes at t near 1.
170 const VectorDim d = (oneMinusT2 + t2 * (axisSquared.array() + ell) / h).matrix();
171 const double w = 2.0 * t2 / std::sqrt(d.prod());
172 Integrals f;
173 for(int i = 0; i < 3; ++i)
174 {
175 f(i) = w / d(i);
176 for(int j = 0; j < 3; ++j)
177 {
178 f(3 + 3 * i + j) = w * (oneMinusT2 + t2 * ell / h) / (d(i) * d(j));
179 }
180 }
181 return f;
182 };
183 // Embedded Gauss 7 / Kronrod 15 rule, all components positive.
184 const double z[8] = {0.9914553711208126, 0.9491079123427585, 0.8648644233597691, 0.7415311855993945,
185 0.5860872354676911, 0.4058451513773972, 0.2077849550078985, 0.0};
186 const double wk[8] = {0.02293532201052922, 0.06309209262997855, 0.1047900103222502, 0.1406532597155259,
187 0.1690047266392679, 0.1903505780647854, 0.2044329400752989, 0.2094821410847278};
188 const double wg[4] = {0.1294849661688697, 0.2797053914892767, 0.3818300505051189, 0.4179591836734694};
189 unsigned int evaluations = 0;
190 std::function<Integrals(double, double, unsigned int)> adaptive;
191 adaptive = [&](double left, double right, unsigned int depth) -> Integrals
192 {
193 if(++evaluations > 200000)
194 {
195 throw std::runtime_error("EllipsoidalInclusion: quadrature work limit exceeded");
196 }
197 const double mid = 0.5 * (left + right), half = 0.5 * (right - left);
198 const Integrals center = integrand(mid);
199 Integrals fine = wk[7] * center, coarse = wg[3] * center;
200 for(int k = 0; k < 7; ++k)
201 {
202 const Integrals pair = integrand(mid - half * z[k]) + integrand(mid + half * z[k]);
203 fine += wk[k] * pair;
204 if(k % 2 == 1)
205 {
206 coarse += wg[(k - 1) / 2] * pair;
207 }
208 }
209 fine *= half;
210 coarse *= half;
211 if(!fine.allFinite())
212 {
213 throw std::runtime_error("EllipsoidalInclusion: nonfinite quadrature");
214 }
215 if(((fine - coarse).array().abs() <= tolerance * ((right - left) + fine.array().abs())).all())
216 {
217 return fine;
218 }
219 if(depth == maxDepth)
220 {
221 throw std::runtime_error("EllipsoidalInclusion: quadrature failed to converge");
222 }
223 const Integrals first = adaptive(left, mid, depth + 1);
224 return (first + adaptive(mid, right, depth + 1)).eval();
225 };
226 return (adaptive(0.0, 1.0, 0) / h / std::sqrt(h)).eval();
227 }
228
229 template <int dim>
230 typename EllipsoidalInclusion<dim>::MatrixDim EllipsoidalInclusion<dim>::stress(const VectorDim &x) const
231 {
232 const VectorDim r = localPosition(x);
233 const bool inside = (r.array().square() / axisSquared.array()).sum() <= 1.0;
234 if(inside)
235 {
236 const auto cache = std::atomic_load(&interiorStressCache);
237 if((cache->eigenstrain.array() == this->eT.array()).all())
238 {
239 return cache->stress;
240 }
241 // The base exposes mutable eT. Refresh only after successful validation.
242 const MatrixDim eigenstrain = this->eT;
243 const MatrixDim sigma = evaluateStress(r, true, eigenstrain);
244 const auto updated = std::make_shared<const InteriorStressCache>(InteriorStressCache{eigenstrain, sigma});
245 std::atomic_store(&interiorStressCache, updated);
246 return sigma;
247 }
248 return evaluateStress(r, false, this->eT);
249 }
250
251 template <int dim>
252 typename EllipsoidalInclusion<dim>::MatrixDim
253 EllipsoidalInclusion<dim>::evaluateStress(const VectorDim &r, const bool inside, const MatrixDim &eigenstrain) const
254 {
255 const MatrixDim e = checkedStrain(eigenstrain, this->nu, this->mu);
256 // Derive from eT rather than the potentially stale base-class pT.
257 const MatrixDim p =
258 rotation.transpose() *
259 (2.0 * this->mu * (e + this->nu / (1.0 - 2.0 * this->nu) * e.trace() * MatrixDim::Identity())) * rotation;
260 if(!p.allFinite())
261 {
262 throw std::overflow_error("EllipsoidalInclusion: eigenstress overflow");
263 }
264 if(p.cwiseAbs().maxCoeff() == 0.0)
265 {
266 return MatrixDim::Zero();
267 }
268 const double ell = inside ? 0.0 : ellipsoidalCoordinate(r);
269 const Integrals values = inside ? interiorIntegrals : integrate(ell);
270 const double pi = std::acos(-1.0), factor = pi * axisProduct;
271 MatrixDim H = MatrixDim::Zero(), T = MatrixDim::Zero();
272 for(int i = 0; i < 3; ++i)
273 {
274 H(i, i) = -2.0 * factor * values(i);
275 for(int j = 0; j < 3; ++j)
276 {
277 T(i, j) = -4.0 * factor * p(i, j) * values(3 + 3 * i + j);
278 }
279 for(int j = 0; j < 3; ++j)
280 {
281 T(i, i) -= 2.0 * factor * p(j, j) * values(3 + 3 * i + j);
282 }
283 }
284 if(!inside)
285 {
286 const VectorDim b = (axisSquared.array() + ell).inverse().matrix();
287 const MatrixDim B = b.asDiagonal();
288 const VectorDim q = B * r;
289 const double d = q.squaredNorm();
290 const double invDelta = std::sqrt(b.prod());
291 const VectorDim l = 2.0 * q / d;
292 const VectorDim Bq = B * q;
293 const MatrixDim ll = l * l.transpose();
294 const MatrixDim lH = (2.0 * B - 2.0 * (Bq * l.transpose() + l * Bq.transpose()) + 2.0 * q.dot(Bq) * ll) / d;
295 H += 4.0 * factor * invDelta / d * (q * q.transpose());
296 // Exact moving-lower-limit terms for Hessian(p:Hessian(Psi)).
297 // At s=ell, f=1-r.B.r=0, F=s/Delta*(8 q.p.q-4 f tr(pB)).
298 const double trPB = (p.diagonal().array() * b.array()).sum();
299 const double v = 8.0 * q.dot(p * q);
300 const double w = ell * invDelta;
301 const VectorDim Fx = w * (16.0 * B * p * q + 8.0 * trPB * q);
302 const double Fs = invDelta * (1.0 - 0.5 * ell * b.sum()) * v + w * (-16.0 * Bq.dot(p * q) - 4.0 * d * trPB);
303 T += 0.25 * factor * (Fx * l.transpose() + l * Fx.transpose() + Fs * ll + w * v * lH);
304 }
305 // Kelvin Green tensor: u=-[4(1-nu)p.grad(Phi)-grad(p:Hess(Psi))]/[16 pi mu(1-nu)].
306 MatrixDim sigma = -(2.0 * (1.0 - this->nu) * (p * H + H * p) - T +
307 2.0 * this->nu * (p.cwiseProduct(H)).sum() * MatrixDim::Identity()) /
308 (8.0 * pi * (1.0 - this->nu));
309 if(inside)
310 {
311 sigma -= p;
312 }
313 sigma = (rotation * sigma * rotation.transpose()).eval();
314 if(!sigma.allFinite())
315 {
316 throw std::overflow_error("EllipsoidalInclusion: stress overflow");
317 }
318 return (0.5 * sigma + 0.5 * sigma.transpose()).eval();
319 }
320
321 template class EllipsoidalInclusion<3>;
322} // namespace model
323#endif
MatrixDim stress(const VectorDim &x) const override
EllipsoidalInclusion(const VectorDim &_C, const MatrixDim &_axes, const MatrixDim &_eT, const double &_nu, const double &_mu, const double &_mobilityReduction, const int &_phaseID, const std::shared_ptr< SecondPhase< dim > > &sph, const double quadratureTolerance=1.0e-3, const unsigned int quadratureMaxDepth=30)
EshelbyInclusionBase(const MatrixDim &_eT, const double &_nu, const double &_mu, const double &_mobilityReduction, const int &_phaseID, const std::shared_ptr< SecondPhase< dim > > &sph)
initial point of the source segment B
the magnitude of the R vector q
id z()
final point of the source segment t
function int_R syms a b c u real f
Definition int_R.m:4
b
Definition int_R.m:16
x
Definition int_R.m:12
float r
Definition plotLoops.py:41
Eigen::Matrix< double, 3, 1 > VectorDim
Definition pyMoDELib.cxx:66
Eigen::Matrix< double, 3, 3 > MatrixDim
Definition pyMoDELib.cxx:67
const double mu
const double nu
REAL denominator
right
int j
REAL w[16]
left
vertex p[3]
int k
REAL * h
int i
int e
vertex first
REAL v[4]