66 e << 0.01, 0.003, -0.002, 0.003, -0.006, 0.001, -0.002, 0.001, 0.004;
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})
73 for(
int kind = 0; kind < 8; ++kind)
78 strain(kind, kind) = 0.01;
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;
88 strain = kind == 6 ? (0.01 * M::Identity()).eval() :
e;
90 E ell(center, 2 *
Q, strain, n,
mu, 1.0, 0,
nullptr);
91#ifdef COMPARE_SUPPLIED_SPHERE
94 for(
double radius : {0.0, 0.7, 1.999, 2.0000000002, 2.2, 4.0, 20.0, 2000.0, 2.e6})
96 V dir(normal(gen), normal(gen), normal(gen));
98 const V x = center + radius * dir;
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");
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)})
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)
117 V dir(normal(gen), normal(gen), normal(gen));
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");
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());
130 std::cerr <<
"lengths " << lengths.transpose() <<
" jump " << jump <<
"\n";
132 check(jump < 2.e-6,
"traction continuity");
134 const double step = lengths.minCoeff() * 1.e-4;
135 V divergence = V::Zero();
136 for(
int j = 0;
j < 3; ++
j)
138 divergence += (local.
stress(outside + step * V::Unit(
j)).col(
j) -
139 local.
stress(outside - step * V::Unit(
j)).col(
j)) /
142 check(divergence.norm() * lengths.minCoeff() / local.
stress(V::Zero()).norm() < 2.e-6,
143 "exterior equilibrium");
146 for(
double scale : {1.e-9, 1.0, 1.e9})
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,
150 E ref(V::Zero(),
makeAxes(
V(3, 0, 0),
V(0, 2, 0),
V(0, 0, 1)),
e,
nu,
mu, 1, 0,
nullptr);
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();
159 (2 *
mu * (1 +
nu) * 0.01 / (3 * (1 -
nu) * std::pow(observation.norm(), 3)) *
160 (M::Identity() - 3 * unit * unit.transpose()))
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);
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)})
171 close(normalTolerance.
stress(point), tight.
stress(point), 1.e-8,
"quadrature refinement");
174 for(
const V &lengths : {
V(3, 2, 1),
V(10, 1, 0.1),
V(100, 1, 1),
V(1, 1, 0.01)})
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})
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");
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");
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;
200 regular.
stress(V::Zero());
202 catch(
const std::invalid_argument &)
204 rejectedCachedStrain =
true;
206 check(rejectedCachedStrain,
"invalid updated strain cannot return cached stress");
208 close(regular.
stress(V::Zero()), originalInterior, 1.e-10,
"cache survives rejected strain update");
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");
215 std::atomic<bool> concurrentCacheOK(
true);
216 std::vector<std::thread> readers;
217 for(
int reader = 0; reader < 4; ++reader)
219 readers.emplace_back(
224 for(
int sample = 0; sample < 1000; ++sample)
226 if((regular.
stress(V::Zero()) - 2 * originalInterior).norm() >
227 1.e-10 * originalInterior.norm())
229 concurrentCacheOK.store(
false);
233 catch(
const std::exception &)
235 concurrentCacheOK.store(
false);
239 for(
auto &reader : readers)
243 check(concurrentCacheOK.load(),
"concurrent interior cache refresh and reads");
244 bool failedQuadrature =
false;
247 E limited(V::Zero(),
makeAxes(
V(100, 0, 0), V::UnitY(), V::UnitZ()),
e,
nu,
mu, 1, 0,
nullptr, 1.e-14, 1);
249 catch(
const std::runtime_error &)
251 failedQuadrature =
true;
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");
261 E bad(V::Zero(),
makeAxes(V::Zero(), V::UnitY(), V::UnitZ()),
e,
nu,
mu, 1, 0,
nullptr);
263 catch(
const std::invalid_argument &)
269 E bad(V::Zero(),
makeAxes(V::UnitX(),
V(1, 1, 0), V::UnitZ()),
e,
nu,
mu, 1, 0,
nullptr);
271 catch(
const std::invalid_argument &)
277 E bad(V::Zero(), M::Identity(),
e, 0.5,
mu, 1, 0,
nullptr);
279 catch(
const std::invalid_argument &)
287 E bad(V::Zero(), M::Identity(), nonsym,
nu,
mu, 1, 0,
nullptr);
289 catch(
const std::invalid_argument &)
293 check(rejected == 4,
"invalid inputs rejected");
295 for(
int i = 0;
i < 3; ++
i)
297 for(
int j =
i + 1;
j < 3; ++
j)
299 M invalidAxes =
V(3.0, 2.0, 1.0).asDiagonal();
300 invalidAxes.col(
j) += 0.01 * invalidAxes.col(
i);
301 bool rejectedAxes =
false;
304 E bad(V::Zero(), invalidAxes,
e,
nu,
mu, 1, 0,
nullptr);
306 catch(
const std::invalid_argument &)
310 check(rejectedAxes,
"nonorthogonal matrix columns rejected");
313 M nonfiniteAxes = M::Identity();
314 nonfiniteAxes(1, 2) = std::numeric_limits<double>::quiet_NaN();
315 bool rejectedNonfinite =
false;
318 E bad(V::Zero(), nonfiniteAxes,
e,
nu,
mu, 1, 0,
nullptr);
320 catch(
const std::invalid_argument &)
322 rejectedNonfinite =
true;
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";
334 catch(
const std::exception &
ex)
336 std::cerr <<
"FAIL: " <<
ex.what() <<
"\n";