Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testEllipsoidalGenerator.cpp
Go to the documentation of this file.
4#include <filesystem>
5#include <fstream>
6#include <chrono>
7#include <random>
8#include <stdexcept>
9using V = Eigen::Vector3d;
10using M = Eigen::Matrix3d;
11void check(bool ok, const char *msg)
12{
13 if(!ok)
14 {
15 throw std::runtime_error(msg);
16 }
17}
18int main(int argc, char **argv)
19{
20 try
21 {
22 check(argc == 2, "Expected repository root");
23 const std::filesystem::path root(argv[1]);
24 const auto dir = std::filesystem::temp_directory_path() /
25 ("model-ellipsoid-generator-" +
26 std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
27 for(const auto sub : {"inputFiles", "config", "aux", "F"})
28 {
29 std::filesystem::create_directories(dir / sub);
30 }
31 const auto input = dir / "inputFiles";
32 std::filesystem::copy_file(root / "Library/Materials/W.txt", input / "W.txt");
33 std::filesystem::copy_file(root / "Library/Meshes/unitCube24.msh", input / "mesh.msh");
34 {
35 std::ofstream f(input / "DefectiveCrystal.txt");
36 f << "physics=InclusionMicrostructure;\nuseFEM=0;\nNsteps=1;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary="
37 "0;\nstartAtTimeStep=0;\n";
38 }
39 {
40 std::ofstream f(input / "polycrystal.txt");
41 f << "materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\n"
42 << "C2G1=0.6 -0.8 0 0.8 0.6 0 0 0 1;\nF=1000 0 0 0 1000 0 0 0 1000;\nX0=0 0 0;\nperiodicFaceIDs=;\n";
43 }
44 model::DefectiveCrystalBase<3> base(dir.string());
47 spec.center = base.mesh.xCenter();
48 const M axes = V(30, 20, 10).asDiagonal();
49 M C2G;
50 C2G << .6, -.8, 0, .8, .6, 0, 0, 0, 1;
51 const M globalAxes = C2G * axes;
52 spec.crystalAxes_SI = axes * base.poly.b_SI;
53 spec.crystalEigenDistortion << .01, .002, 0, .002, .01, 0, 0, 0, .01;
54 const M crystalStrain = Eigen::Map<const M>(spec.crystalEigenDistortion.data()).transpose();
55 const M globalStrain = C2G * crystalStrain * C2G.transpose();
57 check(mg.config().ellipsoidalInclusions().size() == 1, "Valid inclusion rejected");
58 check(mg.config().ellipsoidalInclusions()[0].eT.isApprox(globalStrain), "Ellipsoid tensor rotation failed");
59 check(mg.config().ellipsoidalInclusions()[0].axes.isApprox(globalAxes),
60 "Wrong crystal-to-global rotation or unit conversion");
62 check(mg.config().ellipsoidalInclusions().size() == 1, "Overlap accepted");
63 spec.allowOverlap = true;
65 check(mg.config().ellipsoidalInclusions().size() == 2, "allowOverlap ignored");
66 spec.center = base.mesh.xMax() + V::Ones() * 1000;
68 check(mg.config().ellipsoidalInclusions().size() == 2, "Outside inclusion accepted");
69 spec.allowOutside = true;
71 check(mg.config().ellipsoidalInclusions().size() == 2, "Outside center has no crystal frame");
72 spec.center = V(490, 0, 0);
73 spec.allowOutside = false;
75 check(mg.config().ellipsoidalInclusions().size() == 2, "Boundary crossing accepted");
76 spec.allowOutside = true;
78 check(mg.config().ellipsoidalInclusions().size() == 3, "allowOutside ignored for inside center");
79 spec.crystalAxes_SI.setZero();
80 bool rejected = false;
81 try
82 {
84 }
85 catch(const std::invalid_argument &)
86 {
87 rejected = true;
88 }
89 check(rejected && mg.config().ellipsoidalInclusions().size() == 3, "Invalid input mutated configuration");
90 // Density generation: independent scales, crystal-frame planes, and target count.
92 model::MicrostructureGenerator cloudGenerator(base);
93 cloudGenerator.addEllipsoidalInclusionDensity(cloud);
94 check(cloudGenerator.config().ellipsoidalInclusions().empty(), "Zero density generated inclusions");
95 const double volumeSI = base.mesh.volume() * std::pow(base.poly.b_SI, 3);
96 cloud.targetDensity = 11.5 / volumeSI;
97 cloud.crystalAxes = M::Identity();
98 cloud.crystalEigenDistortion = spec.crystalEigenDistortion;
99 cloud.axesLognormalDistributionMean_SI = V(30, 24, 8).transpose() * base.poly.b_SI;
100 cloud.axesLognormalDistributionStd_SI = V(3, 4.8, 2.4).transpose() * base.poly.b_SI;
101 cloud.patternVector_SI << 100 * base.poly.b_SI, 0, 0;
102 cloud.allowOutside = true;
103 cloud.allowOverlap = true;
104 cloudGenerator.addEllipsoidalInclusionDensity(cloud);
105 check(cloudGenerator.config().ellipsoidalInclusions().size() == 12, "Density count incorrect");
106 const V planeNormal = C2G.col(0);
107 std::mt19937 expectedRandom;
108 std::normal_distribution<double> expectedNormal(0.0, 1.0);
109 bool independentScales = false;
110 V firstLengths = cloudGenerator.config().ellipsoidalInclusions().front().axes.colwise().norm();
111 bool variedSizes = false;
112 for(const auto &inclusion : cloudGenerator.config().ellipsoidalInclusions())
113 {
114 const double planeIndex = inclusion.C.dot(planeNormal) / 100;
115 check(std::abs(planeIndex - std::round(planeIndex)) < 1.e-10, "Center missed crystal-frame plane");
116 const M localAxes = C2G.transpose() * inclusion.axes;
117 const V lengths = localAxes.colwise().norm();
118 check((localAxes * lengths.cwiseInverse().asDiagonal()).isApprox(M::Identity(), 1.e-12),
119 "Density generation rotated crystal semiaxes incorrectly");
120 check(inclusion.eT.isApprox(globalStrain), "Density tensor rotation failed");
121 for(int k = 0; k < 3; ++k)
122 {
123 const double mean = cloud.axesLognormalDistributionMean_SI(k);
124 const double cv = cloud.axesLognormalDistributionStd_SI(k) / mean;
125 const double variance = std::log1p(cv * cv);
126 const double expectedLength =
127 std::exp(std::log(mean) - variance / 2 + std::sqrt(variance) * expectedNormal(expectedRandom));
128 check(std::abs(lengths(k) * base.poly.b_SI / expectedLength - 1) < 1.e-12,
129 "Arithmetic SI mean/std were not converted to lognormal parameters");
130 }
131 const V factors = lengths.cwiseQuotient(V(30, 20, 10));
132 independentScales = independentScales || std::abs(factors(0) - factors(1)) > 1.e-6;
133 variedSizes = variedSizes || !lengths.isApprox(firstLengths);
134 }
135 check(independentScales && variedSizes, "Semiaxes were not perturbed independently");
136 cloud.targetDensity = .5 / volumeSI;
137 cloud.axesLognormalDistributionStd_SI.setZero();
138 cloud.patternVector_SI.setZero();
139 cloudGenerator.addEllipsoidalInclusionDensity(cloud);
140 check(cloudGenerator.config().ellipsoidalInclusions().size() == 13, "Density must add new inclusions");
141 check(cloudGenerator.config().ellipsoidalInclusions().back().axes.isApprox(
142 C2G * (cloud.axesLognormalDistributionMean_SI / base.poly.b_SI).asDiagonal()),
143 "Zero-spread sizes incorrect");
145 bool invalidDensity = false;
146 try
147 {
148 cloudGenerator.addEllipsoidalInclusionDensity(cloud);
149 }
150 catch(const std::invalid_argument &)
151 {
152 invalidDensity = true;
153 }
154 check(invalidDensity && cloudGenerator.config().ellipsoidalInclusions().size() == 13,
155 "Invalid density distribution changed configuration");
156 cloud.axesLognormalDistributionStd_SI.setZero();
157 cloud.axesLognormalDistributionMean_SI.setConstant(2000 * base.poly.b_SI);
158 cloud.allowOutside = false;
159 cloud.maxAttempts = 2;
160 bool exhausted = false;
161 try
162 {
163 cloudGenerator.addEllipsoidalInclusionDensity(cloud);
164 }
165 catch(const std::runtime_error &)
166 {
167 exhausted = true;
168 }
169 check(exhausted && cloudGenerator.config().ellipsoidalInclusions().size() == 13,
170 "Impossible density placement did not stop");
171 // File parsing and density dispatch, including nonzero default spread/pattern.
172 {
173 std::ofstream f(input / "ellipsoidDensity.txt");
174 f << std::setprecision(17) << "type=EllipsoidalInclusions;\nstyle=density;\ntargetDensity=" << .5 / volumeSI
175 << ";\n"
176 << "crystalAxes=" << M::Identity() << ";\n"
177 << "axesLognormalDistributionMean_SI=" << V(30, 20, 10).transpose() * base.poly.b_SI
178 << ";\naxesLognormalDistributionStd_SI=0 0 0;\n"
179 << "crystalEigenDistortion=.01 .002 0 .002 .01 0 0 0 .01;\n"
180 << "patternVector_SI=0 0 0;\nallowOverlap=1;\nallowOutside=1;\n"
181 << "velocityReductionFactor=.75;\nphaseID=0;\nmaxAttempts=100;\n";
182 std::ofstream list(input / "initialMicrostructure.txt");
183 list << "microstructureFile=ellipsoidDensity.txt;\n";
184 }
185 cloudGenerator.readMicrostructureFile();
186 check(cloudGenerator.config().ellipsoidalInclusions().size() == 1 &&
187 cloudGenerator.config().ellipsoidalInclusions().front().axes.isApprox(globalAxes) &&
188 cloudGenerator.config().ellipsoidalInclusions().front().eT.isApprox(globalStrain),
189 "Density file dispatch failed");
191 (root / "Library/Microstructures/ellipsoidalInclusionsDensity.txt").string());
192 check(templateDensity.axesLognormalDistributionStd_SI(0) == 6e-9 && templateDensity.maxAttempts == 10000,
193 "Density library template parsing failed");
194 // Exercise both spherical entry points and polyhedral generation in a rotated grain.
197 spherical.centers = V::Zero().transpose();
198 spherical.radii_SI = {5 * base.poly.b_SI};
199 spherical.crystalEigenDistortions = spec.crystalEigenDistortion;
200 spherical.velocityReductionFactors = {1.0};
201 spherical.phaseIDs = {0};
202 spherical.allowOverlap = true;
203 spherical.allowOutside = false;
204 other.addSphericalInclusionIndividual(spherical);
205 check(other.config().sphericalInclusions().size() == 1 &&
206 other.config().sphericalInclusions().back().eT.isApprox(globalStrain),
207 "Individual sphere tensor rotation failed");
209 density.targetDensity = 0.5 / (base.mesh.volume() * std::pow(base.poly.b_SI, 3));
210 density.diameterLognormalDistribution_M = 2 * base.poly.b_SI;
211 density.diameterLognormalDistribution_A = base.poly.b_SI;
213 density.crystalEigenDistortion = spec.crystalEigenDistortion;
214 density.allowOverlap = true;
215 density.allowOutside = true;
216 density.phaseID = 0;
217 other.addSphericalInclusionDensity(density);
218 check(other.config().sphericalInclusions().size() == 2 &&
219 other.config().sphericalInclusions().back().eT.isApprox(globalStrain),
220 "Density sphere tensor rotation failed");
222 polyhedral.mshFile = (input / "mesh.msh").string();
223 polyhedral.X0 = V::Zero().transpose();
224 polyhedral.F = M::Identity() * 10;
225 polyhedral.crystalEigenDistortion = spec.crystalEigenDistortion;
226 polyhedral.velocityReductionFactor = 1;
227 polyhedral.phaseID = 0;
228 other.addPolyhedronInclusionIndividual(polyhedral);
229 check(other.config().polyhedronInclusions().size() == 1 &&
230 other.config().polyhedronInclusions().back().eT.isApprox(globalStrain),
231 "Polyhedron tensor rotation failed");
232 const size_t sphere = mg.insertInclusion(V::Zero(), 1., M::Zero(), 1., 0);
233 check(sphere == 3, "Sphere ID collided with ellipsoid");
234 mg.config().ellipsoidalInclusions()[0].inclusionID = 100;
235 check(mg.insertInclusion(V::Zero(), axes, M::Zero(), 1., 0) == 101, "Sparse IDs collided");
236 std::map<size_t, V> nodes{{0, V::Zero()}, {1, V::UnitX()}, {2, V::UnitY()}};
237 std::map<size_t, std::vector<size_t>> faces{{0, {0, 1, 2}}};
238 check(mg.insertInclusion(nodes, faces, M::Zero(), 1., 0) == 102, "Polyhedron ID collided");
239 // File dispatcher, followed by a round trip through generated records.
240 {
241 std::ofstream f(input / "ellipsoid.txt");
242 f << std::setprecision(17)
243 << "type=EllipsoidalInclusions;\nstyle=individual;\ncenter=" << base.mesh.xCenter().transpose() << ";\n"
244 << "crystalAxes_SI=" << axes * base.poly.b_SI << ";\n"
245 << "crystalEigenDistortion=.01 .002 0 .002 .01 0 0 0 "
246 ".01;\nvelocityReductionFactor=.75;\nphaseID=0;\nallowOverlap=0;\nallowOutside=0;\n";
247 std::ofstream list(input / "initialMicrostructure.txt");
248 list << "microstructureFile=ellipsoid.txt;\n";
249 }
251 check(mg.config().ellipsoidalInclusions().size() == 1, "File dispatch failed");
252 check(mg.config().ellipsoidalInclusions()[0].axes.isApprox(globalAxes), "File input lost grain rotation");
253 check(mg.config().ellipsoidalInclusions()[0].mobilityReduction == .75, "Metadata lost");
254 mg.config().writeTxt(0);
255 model::DefectiveCrystalConfigIO<3> restored((dir / "config").string());
256 restored.readTxt(0);
257 check(restored.ellipsoidalInclusions().size() == 1, "Generated inclusion not serialized");
258 check(restored.ellipsoidalInclusions()[0].eT.isApprox(globalStrain), "Serialized tensor rotation failed");
259 // Load generated records into the simulation and compare its stress sum.
260 model::MicrostructureContainer<3> simulation(base);
261 auto inclusions = std::make_shared<model::InclusionMicrostructure<3>>(simulation);
262 simulation.microstructures().push_back(inclusions);
263 std::ofstream unused;
264 // A sphere with a larger ID is read first, testing interleaved shape IDs.
265 restored.sphericalInclusions().emplace_back(7, base.mesh.xCenter() + V(100, 0, 0), 5.0, M::Identity() * .002,
266 1.0, 0);
267 simulation.initializeConfiguration(restored, unused, unused);
268 check(inclusions->eshelbyInclusions().size() == 2, "Simulation failed to load mixed inclusions");
269 const auto &record = restored.ellipsoidalInclusions().front();
270 const auto loaded = std::dynamic_pointer_cast<model::EllipsoidalInclusion<3>>(
271 inclusions->eshelbyInclusions().at(record.inclusionID));
272 check(loaded && loaded->sID == record.inclusionID && loaded->phaseID == record.phaseID &&
273 loaded->mobilityReduction == record.mobilityReduction && loaded->secondPhase,
274 "Loaded ellipsoid metadata incorrect");
275 const model::EllipsoidalInclusion<3> reference(record.C, record.axes, record.eT, base.poly.nu, base.poly.mu,
276 record.mobilityReduction, record.phaseID, loaded->secondPhase);
277 const auto &sphereRecord = restored.sphericalInclusions().front();
278 const model::SphericalInclusion<3> sphereReference(sphereRecord.C, sphereRecord.a, sphereRecord.eT,
279 base.poly.nu, base.poly.mu, 1.0, 0, loaded->secondPhase);
280 for(const V &point : {record.C, V(record.C + V(70, 45, 20))})
281 {
282 M expected = M::Zero();
283 for(const auto &shift : base.periodicShifts)
284 {
285 expected += reference.stress(point + shift) + sphereReference.stress(point + shift);
286 }
287 check(expected.norm() > 0 && simulation.stress(point, nullptr, nullptr, nullptr).isApprox(expected, 1.e-12),
288 "Ellipsoid stress missing from simulation sum");
289 }
290 model::DefectiveCrystalConfigIO<3> saved((dir / "config").string());
291 model::DefectiveCrystalAuxIO<3> aux((dir / "aux").string());
292 simulation.output(saved, aux, unused, unused);
293 check(saved.ellipsoidalInclusions().size() == 1 && saved.sphericalInclusions().size() == 1,
294 "Simulation output lost an inclusion");
295 check(saved.ellipsoidalInclusions()[0].axes.isApprox(record.axes) &&
296 saved.ellipsoidalInclusions()[0].eT.isApprox(record.eT) &&
297 saved.ellipsoidalInclusions()[0].inclusionID == record.inclusionID,
298 "Simulation output changed ellipsoid data");
299 saved.writeBin(1);
300 model::DefectiveCrystalConfigIO<3> restart((dir / "config").string());
301 restart.readBin(1);
302 const V probe = record.C + V(70, 45, 20);
303 const M before = simulation.stress(probe, nullptr, nullptr, nullptr);
304 simulation.initializeConfiguration(restart, unused, unused);
305 check(simulation.stress(probe, nullptr, nullptr, nullptr).isApprox(before, 1.e-12),
306 "Restart changed the stress field");
307 for(const int phaseID : {-1, 999})
308 {
309 restart.ellipsoidalInclusions()[0].phaseID = phaseID;
310 bool invalidPhase = false;
311 try
312 {
313 simulation.initializeConfiguration(restart, unused, unused);
314 }
315 catch(const std::runtime_error &)
316 {
317 invalidPhase = true;
318 }
319 check(invalidPhase, "Invalid ellipsoidal phase accepted");
320 }
321 restart.ellipsoidalInclusions()[0].phaseID = 0;
322 restart.ellipsoidalInclusions()[0].inclusionID = 7;
323 bool duplicate = false;
324 try
325 {
326 simulation.initializeConfiguration(restart, unused, unused);
327 }
328 catch(const std::runtime_error &)
329 {
330 duplicate = true;
331 }
332 check(duplicate, "Duplicate inclusion IDs accepted");
333 restart.clear();
334 simulation.initializeConfiguration(restart, unused, unused);
335 check(inclusions->eshelbyInclusions().empty() && simulation.stress(probe, nullptr, nullptr, nullptr).isZero(),
336 "Empty reload retained stress");
337 std::filesystem::remove_all(dir);
338 std::cout
339 << "PASS generator insertion, IDs, flags, validation, file dispatch, simulation stress and restart IO\n";
340 return 0;
341 }
342 catch(const std::exception &error)
343 {
344 std::cerr << error.what() << '\n';
345 return 1;
346 }
347}
Eigen::Matrix3d M
Eigen::Vector3d V
const std::vector< SphericalInclusionIO< dim > > & sphericalInclusions() const
const std::vector< PolyhedronInclusionIO< dim > > & polyhedronInclusions() const
const std::vector< EllipsoidalInclusionIO< dim > > & ellipsoidalInclusions() const
MatrixDim stress(const VectorDim &x) const override
void addSphericalInclusionIndividual(const SphericalInclusionIndividualSpecification &spec)
void addSphericalInclusionDensity(const SphericalInclusionDensitySpecification &spec)
void addEllipsoidalInclusionIndividual(const EllipsoidalInclusionIndividualSpecification &spec)
void addEllipsoidalInclusionDensity(const EllipsoidalInclusionDensitySpecification &spec)
void addPolyhedronInclusionIndividual(const PolyhedronInclusionIndividualSpecification &spec)
size_t insertInclusion(const VectorDimD &pos, const double &R, const Eigen::Matrix< double, dim, dim > &eT, const double &vrc, const int &type)
MatrixDim stress(const VectorDim &x) const override
function int_R syms a b c u real f
Definition int_R.m:4
void initializeConfiguration(const DefectiveCrystalConfigIO< dim > &configIO, const std::ofstream &f_file, const std::ofstream &F_labels) override
MatrixDim stress(const VectorDim &, const NodeType *const, const ElementType *const, const SimplexDim *const) const override
void output(DefectiveCrystalConfigIO< dim > &configIO, DefectiveCrystalAuxIO< dim > &auxIO, std::ofstream &f_file, std::ofstream &F_labels) const override
Eigen::Matrix< double, Eigen::Dynamic, 3 *3 > crystalEigenDistortions
void check(bool ok, const char *msg)
M sphere(const V &r, double a, const M &e, double n=nu, double m=mu)
void argv
void b int argc
int k
int e