Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testEllipsoidalInclusion.cpp
Go to the documentation of this file.
2#ifdef COMPARE_SUPPLIED_SPHERE
4#endif
5#include <Eigen/Geometry>
6#include <cmath>
7#include <iostream>
8#include <stdexcept>
9#include <random>
10#include <limits>
11#include <atomic>
12#include <thread>
13#include <vector>
14using M = Eigen::Matrix3d;
15using V = Eigen::Vector3d;
17M makeAxes(const V &a1, const V &a2, const V &a3)
18{
19 M axes;
20 axes.col(0) = a1;
21 axes.col(1) = a2;
22 axes.col(2) = a3;
23 return axes;
24}
25const double nu = 0.31, mu = 43.0;
26int checks = 0;
27double worstSphere = 0.0, worstTraction = 0.0;
28void check(bool ok, const char *msg)
29{
30 ++checks;
31 if(!ok)
32 {
33 throw std::runtime_error(msg);
34 }
35}
36void close(const M &a, const M &b, double tol, const char *msg)
37{
38 const double error = (a - b).norm() / std::max(1.e-30, b.norm());
39 if(error > tol)
40 {
41 std::cerr << msg << ": relative error=" << error << "\n";
42 }
43 check(error <= tol, msg);
44}
45M sphere(const V &r, double a, const M &e, double n = nu, double m = mu)
46{
47 const M I = M::Identity(), p = 2 * m * (e + n / (1 - 2 * n) * e.trace() * I);
48 const double R = r.norm(), L = (5 * n - 1) / (15 * (1 - n)), B = (4 - 5 * n) / (15 * (1 - n));
49 if(R <= a)
50 {
51 return 2 * m * ((L + n / (1 - 2 * n) * (2 * B + 3 * L)) * e.trace() * I + 2 * B * e) - p;
52 }
53 const double R2 = R * R, c = a * a / R2;
54 const V pr = p * r;
55 const double rr = r.dot(pr), tr = p.trace();
56 return std::pow(a / R, 3) / (2 * (1 - n)) *
57 (((10 * (1 - 2 * n) + 6 * c) / 15) * p + (2 * n - 2 * c) / R2 * (pr * r.transpose() + r * pr.transpose()) +
58 ((3 * c - 5 * (1 - 2 * n)) / 15 * tr + (1 - 2 * n - c) / R2 * rr) * I +
59 (-(5 - 7 * c) / (R2 * R2) * rr + (1 - c) / R2 * tr) * r * r.transpose());
60}
61int main()
62{
63 try
64 {
65 M e;
66 e << 0.01, 0.003, -0.002, 0.003, -0.006, 0.001, -0.002, 0.001, 0.004;
67 std::mt19937 gen(21);
68 std::normal_distribution<double> normal;
69 const M Q = Eigen::AngleAxisd(0.71, V(1, 2, 3).normalized()).toRotationMatrix();
70 const V center(0.2, -0.3, 0.1);
71 for(const double n : {-0.8, 0.0, 0.31, 0.49})
72 {
73 for(int kind = 0; kind < 8; ++kind)
74 {
75 M strain = M::Zero();
76 if(kind < 3)
77 {
78 strain(kind, kind) = 0.01;
79 }
80 else if(kind < 6)
81 {
82 int i = kind == 3 ? 0 : kind == 4 ? 0 : 1;
83 int j = kind == 3 ? 1 : 2;
84 strain(i, j) = strain(j, i) = 0.01;
85 }
86 else
87 {
88 strain = kind == 6 ? (0.01 * M::Identity()).eval() : e;
89 }
90 E ell(center, 2 * Q, strain, n, mu, 1.0, 0, nullptr);
91#ifdef COMPARE_SUPPLIED_SPHERE
92 model::SphericalInclusion<3> original(center, 2, strain, n, mu, 1.0, 0, nullptr);
93#endif
94 for(double radius : {0.0, 0.7, 1.999, 2.0000000002, 2.2, 4.0, 20.0, 2000.0, 2.e6})
95 {
96 V dir(normal(gen), normal(gen), normal(gen));
97 dir.normalize();
98 const V x = center + radius * dir;
99 const M ref = sphere(x - center, 2, strain, n, mu), got = ell.stress(x);
100 worstSphere = std::max(worstSphere, (got - ref).norm() / ref.norm());
101 close(got, ref, 3.e-9, "sphere formula");
102#ifdef COMPARE_SUPPLIED_SPHERE
103 close(got, original.stress(x), 3.e-9, "supplied sphere class");
104#endif
105 }
106 }
107 }
108 E boundary(V::Zero(), M::Identity(), e, nu, mu, 1, 0, nullptr);
109 check(!boundary.contains(V::UnitX()), "contains excludes exact boundary");
110 close(boundary.stress(V::UnitX()), sphere(V::UnitX(), 1, e), 1.e-10, "interior boundary convention");
111 for(const V &lengths : {V(3, 2, 1), V(10, 1, 0.1), V(1, 1, 0.01), V(100, 1, 1)})
112 {
113 E local(V::Zero(), lengths.asDiagonal(), e, nu, mu, 1, 0, nullptr, 1.e-10);
114 E rotated(center, Q * lengths.asDiagonal(), Q * e * Q.transpose(), nu, mu, 1, 0, nullptr, 1.e-10);
115 for(int k = 0; k < 12; ++k)
116 {
117 V dir(normal(gen), normal(gen), normal(gen));
118 dir.normalize();
119 const V surf = lengths.cwiseProduct(dir), inside = 0.3 * surf, outside = 1.6 * surf;
120 close(local.stress(inside), local.stress(V::Zero()), 1.e-13, "uniform interior");
121 close(rotated.stress(center + Q * outside), Q * local.stress(outside) * Q.transpose(), 2.e-8,
122 "rotation covariance");
123 const V normalTo = (dir.array() / lengths.array()).matrix().normalized();
124 const V ti = local.stress((1 - 1.e-10) * surf) * normalTo;
125 const V to = local.stress((1 + 1.e-10) * surf) * normalTo;
126 const double jump = (ti - to).norm() / std::max(1.e-30, local.stress(V::Zero()).norm());
127 worstTraction = std::max(worstTraction, jump);
128 if(jump >= 2.e-6)
129 {
130 std::cerr << "lengths " << lengths.transpose() << " jump " << jump << "\n";
131 }
132 check(jump < 2.e-6, "traction continuity");
133 // Equilibrium in the exterior, using independent central spatial differences.
134 const double step = lengths.minCoeff() * 1.e-4;
135 V divergence = V::Zero();
136 for(int j = 0; j < 3; ++j)
137 {
138 divergence += (local.stress(outside + step * V::Unit(j)).col(j) -
139 local.stress(outside - step * V::Unit(j)).col(j)) /
140 (2 * step);
141 }
142 check(divergence.norm() * lengths.minCoeff() / local.stress(V::Zero()).norm() < 2.e-6,
143 "exterior equilibrium");
144 }
145 }
146 for(double scale : {1.e-9, 1.0, 1.e9})
147 {
148 E ell(V::Zero(), makeAxes(scale * V(3, 0, 0), scale * V(0, 2, 0), scale * V(0, 0, 1)), e, nu, mu, 1, 0,
149 nullptr);
150 E ref(V::Zero(), makeAxes(V(3, 0, 0), V(0, 2, 0), V(0, 0, 1)), e, nu, mu, 1, 0, nullptr);
151 close(ell.stress(scale * V(4, 3, 2)), ref.stress(V(4, 3, 2)), 1.e-10, "scale invariance");
152 }
153 // Independent dilatational sphere values, beyond the general sphere formula.
154 E dil(V::Zero(), M::Identity(), 0.01 * M::Identity(), nu, mu, 1, 0, nullptr);
155 close(dil.stress(V::Zero()), (-4 * mu * (1 + nu) * 0.01 / (3 * (1 - nu)) * M::Identity()).eval(), 1.e-10,
156 "hydrostatic sphere interior");
157 const V observation(2, 3, 4), unit = observation.normalized();
158 close(dil.stress(observation),
159 (2 * mu * (1 + nu) * 0.01 / (3 * (1 - nu) * std::pow(observation.norm(), 3)) *
160 (M::Identity() - 3 * unit * unit.transpose()))
161 .eval(),
162 1.e-10, "dilatational sphere exterior");
163 E regular(V::Zero(), makeAxes(V(3, 0, 0), V(0, 2, 0), V(0, 0, 1)), e, nu, mu, 1, 0, nullptr);
164 E permuted(V::Zero(), makeAxes(V(0, 0, -1), V(-3, 0, 0), V(0, 2, 0)), e, nu, mu, 1, 0, nullptr);
165 close(regular.stress(V(4, 3, 2)), permuted.stress(V(4, 3, 2)), 1.e-10,
166 "semiaxis sign and permutation invariance");
167 E tight(V::Zero(), makeAxes(V(10, 0, 0), V(0, 1, 0), V(0, 0, 0.1)), e, nu, mu, 1, 0, nullptr, 1.e-12, 40);
168 E normalTolerance(V::Zero(), makeAxes(V(10, 0, 0), V(0, 1, 0), V(0, 0, 0.1)), e, nu, mu, 1, 0, nullptr, 1.e-10);
169 for(const V &point : {V::Zero().eval(), V(10.001, 0, 0), V(0, 0, 0.100001), V(12, 2, 1)})
170 {
171 close(normalTolerance.stress(point), tight.stress(point), 1.e-8, "quadrature refinement");
172 }
173 // The default must match an explicit 1e-3, while tighter overrides remain usable.
174 for(const V &lengths : {V(3, 2, 1), V(10, 1, 0.1), V(100, 1, 1), V(1, 1, 0.01)})
175 {
176 E defaultTolerance(V::Zero(), Q * lengths.asDiagonal(), e, nu, mu, 1, 0, nullptr);
177 E explicitTolerance(V::Zero(), Q * lengths.asDiagonal(), e, nu, mu, 1, 0, nullptr, 1.e-3);
178 E referenceTolerance(V::Zero(), Q * lengths.asDiagonal(), e, nu, mu, 1, 0, nullptr, 1.e-12, 40);
179 for(double factor : {0.0, 1.000001, 2.0, 10.0})
180 {
181 const V point = Q * (factor * lengths.cwiseProduct(V(1, 2, 3).normalized()));
182 const M actual = defaultTolerance.stress(point);
183 check((actual - explicitTolerance.stress(point)).norm() == 0.0, "default quadrature tolerance is 1e-3");
184 close(actual, referenceTolerance.stress(point), 1.e-3, "default accuracy on sampled points");
185 }
186 }
187 regular.eT = 2 * e;
188 close(regular.stress(V(4, 3, 2)), 2 * permuted.stress(V(4, 3, 2)), 1.e-10, "mutable base eigenstrain honored");
189 const M originalInterior = permuted.stress(V::Zero());
190 close(regular.stress(V::Zero()), 2 * originalInterior, 1.e-10, "interior cache refreshed after eT update");
191 close(regular.stress(V(0.1, 0.1, 0.1)), 2 * originalInterior, 1.e-10, "refreshed interior cache reused");
192 regular.eT.setZero();
193 check(regular.stress(V::Zero()).norm() == 0.0, "interior cache refresh to zero strain");
194 regular.eT = e;
195 close(regular.stress(V::Zero()), originalInterior, 1.e-10, "interior cache refresh from zero strain");
196 regular.eT(0, 1) += 0.1;
197 bool rejectedCachedStrain = false;
198 try
199 {
200 regular.stress(V::Zero());
201 }
202 catch(const std::invalid_argument &)
203 {
204 rejectedCachedStrain = true;
205 }
206 check(rejectedCachedStrain, "invalid updated strain cannot return cached stress");
207 regular.eT = e;
208 close(regular.stress(V::Zero()), originalInterior, 1.e-10, "cache survives rejected strain update");
209 E copied = regular;
210 copied.eT = 3 * e;
211 close(copied.stress(V::Zero()), 3 * originalInterior, 1.e-10, "copied inclusion refreshes its own cache");
212 close(regular.stress(V::Zero()), originalInterior, 1.e-10, "copy refresh leaves original cache unchanged");
213 // Concurrent readers may refresh the same stale cache; eT stays fixed.
214 regular.eT = 2 * e;
215 std::atomic<bool> concurrentCacheOK(true);
216 std::vector<std::thread> readers;
217 for(int reader = 0; reader < 4; ++reader)
218 {
219 readers.emplace_back(
220 [&]()
221 {
222 try
223 {
224 for(int sample = 0; sample < 1000; ++sample)
225 {
226 if((regular.stress(V::Zero()) - 2 * originalInterior).norm() >
227 1.e-10 * originalInterior.norm())
228 {
229 concurrentCacheOK.store(false);
230 }
231 }
232 }
233 catch(const std::exception &)
234 {
235 concurrentCacheOK.store(false);
236 }
237 });
238 }
239 for(auto &reader : readers)
240 {
241 reader.join();
242 }
243 check(concurrentCacheOK.load(), "concurrent interior cache refresh and reads");
244 bool failedQuadrature = false;
245 try
246 {
247 E limited(V::Zero(), makeAxes(V(100, 0, 0), V::UnitY(), V::UnitZ()), e, nu, mu, 1, 0, nullptr, 1.e-14, 1);
248 }
249 catch(const std::runtime_error &)
250 {
251 failedQuadrature = true;
252 }
253 check(failedQuadrature, "quadrature nonconvergence reported");
254 E tiny(V::Zero(), M::Identity(), 1.e-12 * e, nu, mu, 1, 0, nullptr);
255 close(tiny.stress(V(2, 1, 1)), 1.e-12 * sphere(V(2, 1, 1), 1, e), 1.e-10, "small eigenstrain preserved");
256 E zero(V::Zero(), M::Identity(), M::Zero(), nu, mu, 1, 0, nullptr);
257 check(zero.stress(V::Zero()).norm() == 0, "zero strain");
258 int rejected = 0;
259 try
260 {
261 E bad(V::Zero(), makeAxes(V::Zero(), V::UnitY(), V::UnitZ()), e, nu, mu, 1, 0, nullptr);
262 }
263 catch(const std::invalid_argument &)
264 {
265 ++rejected;
266 }
267 try
268 {
269 E bad(V::Zero(), makeAxes(V::UnitX(), V(1, 1, 0), V::UnitZ()), e, nu, mu, 1, 0, nullptr);
270 }
271 catch(const std::invalid_argument &)
272 {
273 ++rejected;
274 }
275 try
276 {
277 E bad(V::Zero(), M::Identity(), e, 0.5, mu, 1, 0, nullptr);
278 }
279 catch(const std::invalid_argument &)
280 {
281 ++rejected;
282 }
283 M nonsym = e;
284 nonsym(0, 1) += 0.1;
285 try
286 {
287 E bad(V::Zero(), M::Identity(), nonsym, nu, mu, 1, 0, nullptr);
288 }
289 catch(const std::invalid_argument &)
290 {
291 ++rejected;
292 }
293 check(rejected == 4, "invalid inputs rejected");
294 // Check every column pair, independently of their lengths.
295 for(int i = 0; i < 3; ++i)
296 {
297 for(int j = i + 1; j < 3; ++j)
298 {
299 M invalidAxes = V(3.0, 2.0, 1.0).asDiagonal();
300 invalidAxes.col(j) += 0.01 * invalidAxes.col(i);
301 bool rejectedAxes = false;
302 try
303 {
304 E bad(V::Zero(), invalidAxes, e, nu, mu, 1, 0, nullptr);
305 }
306 catch(const std::invalid_argument &)
307 {
308 rejectedAxes = true;
309 }
310 check(rejectedAxes, "nonorthogonal matrix columns rejected");
311 }
312 }
313 M nonfiniteAxes = M::Identity();
314 nonfiniteAxes(1, 2) = std::numeric_limits<double>::quiet_NaN();
315 bool rejectedNonfinite = false;
316 try
317 {
318 E bad(V::Zero(), nonfiniteAxes, e, nu, mu, 1, 0, nullptr);
319 }
320 catch(const std::invalid_argument &)
321 {
322 rejectedNonfinite = true;
323 }
324 check(rejectedNonfinite, "nonfinite axes matrix rejected");
325 const M inputAxes = Q * V(3.0, 2.0, 1.0).asDiagonal();
326 E matrixInput(center, inputAxes, e, nu, mu, 1, 0, nullptr);
327 check((matrixInput.axes - inputAxes).norm() == 0.0, "public axes preserves semiaxis columns");
328 check(matrixInput.contains(center + 0.9 * inputAxes.col(0)), "matrix column length sets semiaxis");
329 check(!matrixInput.contains(center + 1.1 * inputAxes.col(0)), "point beyond matrix semiaxis excluded");
330 std::cout << "PASS " << checks << " checks; worst sphere relative error " << worstSphere
331 << "; worst normalized traction jump " << worstTraction << "\n";
332 return 0;
333 }
334 catch(const std::exception &ex)
335 {
336 std::cerr << "FAIL: " << ex.what() << "\n";
337 return 1;
338 }
339}
Eigen::Matrix3d M
Eigen::Vector3d V
MatrixDim stress(const VectorDim &x) const override
MatrixDim stress(const VectorDim &x) const override
initial point of the source segment B
The q vector I
c
Definition int_R.m:15
a
Definition int_R.m:14
b
Definition int_R.m:16
x
Definition int_R.m:12
void check(bool ok, const char *msg)
M makeAxes(const V &a1, const V &a2, const V &a3)
model::EllipsoidalInclusion< 3 > E
const double mu
const double nu
double worstSphere
double worstTraction
M sphere(const V &r, double a, const M &e, double n=nu, double m=mu)
void close(const M &a, const M &b, double tol, const char *msg)
Q
int j
REAL ex
vertex p[3]
int k
int i
int e
void t struct mesh * m