Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
EllipsoidalInclusionsGenerator.cpp
Go to the documentation of this file.
1/* This file is part of MoDELib, distributed under GNU GPL v2. */
5#include <limits>
6#include <random>
7
8namespace model
9{
10 namespace
11 {
12 void validateEllipsoid(const EllipsoidalInclusionIndividualSpecification &spec, const double b)
13 {
14 using V = Eigen::Vector3d;
15 using M = Eigen::Matrix3d;
16
17 if(!std::isfinite(b) || b <= 0)
18 {
19 throw std::invalid_argument("EllipsoidalInclusionsGenerator: invalid Burgers-vector scale");
20 }
21 const M crystalAxes = spec.crystalAxes_SI / b;
22 const V lengths = crystalAxes.colwise().stableNorm();
23 const M crystalStrain = Eigen::Map<const M>(spec.crystalEigenDistortion.data()).transpose();
24 if(!spec.center.allFinite() || !crystalAxes.allFinite() || !lengths.allFinite() ||
25 lengths.minCoeff() <= 0 || lengths.minCoeff() / lengths.maxCoeff() < 1.e-6 ||
26 !crystalStrain.allFinite() || !std::isfinite(spec.velocityReductionFactor))
27 {
28 throw std::invalid_argument("EllipsoidalInclusionsGenerator: invalid geometry or material input");
29 }
30 const M crystalRotation = crystalAxes * lengths.cwiseInverse().asDiagonal();
31 if((crystalRotation.transpose() * crystalRotation - M::Identity()).cwiseAbs().maxCoeff() > 1.e-12)
32 {
33 throw std::invalid_argument("EllipsoidalInclusionsGenerator: semiaxes must be orthogonal");
34 }
35 const double strainScale = crystalStrain.cwiseAbs().maxCoeff();
36 if((crystalStrain - crystalStrain.transpose()).cwiseAbs().maxCoeff() > 1.e-12 * strainScale)
37 {
38 throw std::invalid_argument("EllipsoidalInclusionsGenerator: eigen-distortion must be symmetric");
39 }
40 }
41 } // namespace
42
45 {
46 if(!std::isfinite(spec.targetDensity) || spec.targetDensity < 0.0)
47 {
48 throw std::invalid_argument("Ellipsoidal density must be finite and nonnegative");
49 }
50 if(spec.targetDensity == 0.0)
51 {
52 return;
53 }
54 if(!spec.axesLognormalDistributionMean_SI.allFinite() ||
55 (spec.axesLognormalDistributionMean_SI.array() <= 0.0).any() ||
56 !spec.axesLognormalDistributionStd_SI.allFinite() ||
57 (spec.axesLognormalDistributionStd_SI.array() < 0.0).any() || !spec.patternVector_SI.allFinite() ||
58 spec.maxAttempts == 0)
59 {
60 throw std::invalid_argument("Invalid ellipsoidal size distribution, pattern, or attempt limit");
61 }
62 if(!spec.crystalAxes.allFinite() ||
63 (spec.crystalAxes.transpose() * spec.crystalAxes - Eigen::Matrix3d::Identity()).cwiseAbs().maxCoeff() >
64 1.e-12)
65 {
66 throw std::invalid_argument("Ellipsoidal crystalAxes must contain orthogonal unit vectors");
67 }
68 Eigen::Vector3d logMean;
69 Eigen::Vector3d logStd;
70 for(int k = 0; k < 3; ++k)
71 {
72 const double mean = spec.axesLognormalDistributionMean_SI(k);
73 const double stdSI = spec.axesLognormalDistributionStd_SI(k);
74 // log(1 + (std/mean)^2), evaluated without squaring an extreme ratio.
75 double logVariance = 0.0;
76 if(stdSI > 0.0)
77 {
78 const double logRatio = std::log(stdSI) - std::log(mean);
79 logVariance = logRatio > 0.0 ? 2.0 * logRatio + std::log1p(std::exp(-2.0 * logRatio))
80 : std::log1p(std::exp(2.0 * logRatio));
81 }
82 logStd(k) = std::sqrt(logVariance);
83 logMean(k) = std::log(mean) - 0.5 * logVariance;
84 }
85 const double b = mg.ddBase.poly.b_SI;
87 candidate.crystalAxes_SI = spec.crystalAxes * spec.axesLognormalDistributionMean_SI.asDiagonal();
88 candidate.crystalEigenDistortion = spec.crystalEigenDistortion;
89 candidate.allowOverlap = spec.allowOverlap;
90 candidate.allowOutside = spec.allowOutside;
91 candidate.velocityReductionFactor = spec.velocityReductionFactor;
92 candidate.phaseID = spec.phaseID;
93 validateEllipsoid(candidate, b);
94 const double volumeSI = mg.ddBase.mesh.volume() * std::pow(b, 3);
95 const double requested = std::ceil(spec.targetDensity * volumeSI);
96 if(!std::isfinite(volumeSI) || volumeSI <= 0 || !std::isfinite(requested) ||
97 requested >= static_cast<double>(std::numeric_limits<size_t>::max()))
98 {
99 throw std::invalid_argument("Ellipsoidal density exceeds numerical range");
100 }
101 const size_t targetCount = static_cast<size_t>(requested);
102 const Eigen::Vector3d pattern = spec.patternVector_SI.transpose() / b;
103 const double spacing = pattern.stableNorm();
104 if(!pattern.allFinite() || !std::isfinite(spacing))
105 {
106 throw std::invalid_argument("Ellipsoidal pattern exceeds numerical range");
107 }
108 std::mt19937 random;
109 std::normal_distribution<double> normal(0.0, 1.0);
110 size_t generated = 0;
111 size_t attempts = 0;
112 while(generated < targetCount)
113 {
114 if(attempts == spec.maxAttempts)
115 {
116 throw std::runtime_error("Ellipsoidal density placement exhausted maxAttempts after generating " +
117 std::to_string(generated) + " of " + std::to_string(targetCount));
118 }
119 ++attempts;
120 const auto point = mg.ddBase.poly.randomLatticePointInMesh();
121 candidate.center = point.first.cartesian();
122 if(spacing > 0.0)
123 {
124 const Eigen::Vector3d normalGlobal = mg.ddBase.poly.grain(point.second)->C2G * (pattern / spacing);
125 const double distance = candidate.center.dot(normalGlobal);
126 candidate.center += (std::round(distance / spacing) * spacing - distance) * normalGlobal;
127 // Projection must not change the grain defining the plane orientation.
128 const auto projected = mg.ddBase.mesh.search(candidate.center);
129 if(!projected.first || projected.second->region->regionID != static_cast<size_t>(point.second))
130 {
131 continue;
132 }
133 }
134 Eigen::Vector3d sampledLengths;
135 for(int k = 0; k < 3; ++k)
136 {
137 if(spec.axesLognormalDistributionStd_SI(k) == 0.0)
138 {
139 sampledLengths(k) = spec.axesLognormalDistributionMean_SI(k);
140 }
141 else
142 {
143 sampledLengths(k) = std::exp(logMean(k) + logStd(k) * normal(random));
144 }
145 }
146 candidate.crystalAxes_SI = spec.crystalAxes * sampledLengths.asDiagonal();
147 const Eigen::Vector3d lengths = candidate.crystalAxes_SI.colwise().stableNorm();
148 if(!candidate.crystalAxes_SI.allFinite() || !lengths.allFinite() || lengths.minCoeff() <= 0 ||
149 lengths.minCoeff() / lengths.maxCoeff() < 1.e-6)
150 {
151 continue;
152 }
153 const auto before = mg.config().ellipsoidalInclusions().size();
154 EllipsoidalInclusionsGenerator single(candidate, mg);
155 if(mg.config().ellipsoidalInclusions().size() > before)
156 {
157 ++generated;
158 attempts = 0;
159 }
160 }
161 std::cout << "Generated ellipsoidal number density=" << generated / volumeSI << " m^-3\n";
162 }
163
166 {
167 using V = Eigen::Vector3d;
168 using M = Eigen::Matrix3d;
169 const double b = mg.ddBase.poly.b_SI;
170 validateEllipsoid(spec, b);
171 const M crystalAxes = spec.crystalAxes_SI / b;
172 const V lengths = crystalAxes.colwise().stableNorm();
173 const M crystalRotation = crystalAxes * lengths.cwiseInverse().asDiagonal();
174 const M crystalStrain = Eigen::Map<const M>(spec.crystalEigenDistortion.data()).transpose();
175 const auto searchPair = mg.ddBase.mesh.search(spec.center);
176 if(!searchPair.first)
177 {
178 std::cout << "Ellipsoidal inclusion center outside mesh; cannot determine crystal frame; not generated\n";
179 return;
180 }
181 const auto &grain = mg.ddBase.poly.grain(searchPair.second->region->regionID);
182 const M strain = grain->C2G * crystalStrain * grain->C2G.transpose();
183 const M axes = grain->C2G * crystalAxes;
184 const M rotation = grain->C2G * crystalRotation;
185 const M inverse = lengths.cwiseInverse().asDiagonal() * rotation.transpose();
186 const M shape = axes * axes.transpose();
187 if(!shape.allFinite() || !inverse.allFinite() || shape.diagonal().minCoeff() <= 0)
188 {
189 throw std::invalid_argument("EllipsoidalInclusionsGenerator: geometry exceeds numerical range");
190 }
191
192 if(!spec.allowOutside)
193 {
194 // A connected ellipsoid whose center is inside remains inside unless
195 // it reaches a boundary triangle. This also handles nonconvex meshes.
196 for(const auto &face : mg.ddBase.mesh.template simplexFactory<2>().liveSimplices())
197 {
198 if(face.second->isBoundarySimplex())
199 {
200 const auto vertices = face.second->vertices();
202 inverse * (vertices[0]->P0 - spec.center), inverse * (vertices[1]->P0 - spec.center),
203 inverse * (vertices[2]->P0 - spec.center)) <= 1.0 + 1.e-12)
204 {
205 std::cout << "Ellipsoidal inclusion reaches mesh boundary; not generated\n";
206 return;
207 }
208 }
209 }
210 }
211 if(!spec.allowOverlap)
212 {
213 for(const auto &inclusion : mg.config().sphericalInclusions())
214 {
215 if(ellipsoidalPlacement::overlap(inclusion.C - spec.center, shape,
216 M::Identity() * inclusion.a * inclusion.a))
217 {
218 std::cout << "Ellipsoidal inclusion overlaps a sphere; not generated\n";
219 return;
220 }
221 }
222 for(const auto &inclusion : mg.config().ellipsoidalInclusions())
223 {
224 if(ellipsoidalPlacement::overlap(inclusion.C - spec.center, shape,
225 inclusion.axes * inclusion.axes.transpose()))
226 {
227 std::cout << "Ellipsoidal inclusion overlaps an ellipsoid; not generated\n";
228 return;
229 }
230 }
231 // Conservative test for arbitrary polyhedra: disjoint bounding boxes
232 // certify separation. Intersecting boxes may reject a valid placement.
233 const V extent = shape.diagonal().cwiseSqrt();
234 for(const auto &inclusion : mg.config().polyhedronInclusions())
235 {
236 V lower = V::Constant(std::numeric_limits<double>::infinity()), upper = -lower;
237 bool found = false;
238 for(const auto &edge : mg.config().polyhedronInclusionEdges())
239 {
240 if(edge.inclusionID != inclusion.inclusionID)
241 {
242 continue;
243 }
244 for(const auto nodeID : {edge.sourceID, edge.sinkID})
245 {
246 const auto &nodes = mg.config().polyhedronInclusionNodes();
247 const auto node =
248 std::find_if(nodes.begin(), nodes.end(), [&](const auto &n) { return n.nodeID == nodeID; });
249 if(node == nodes.end() || !node->P.allFinite())
250 {
251 throw std::runtime_error("EllipsoidalInclusionsGenerator: invalid polyhedron node");
252 }
253 lower = lower.cwiseMin(node->P);
254 upper = upper.cwiseMax(node->P);
255 found = true;
256 }
257 }
258 if(!found)
259 {
260 throw std::runtime_error("EllipsoidalInclusionsGenerator: polyhedron has no boundary edges");
261 }
262 if(((spec.center + extent).array() >= lower.array()).all() &&
263 ((spec.center - extent).array() <= upper.array()).all())
264 {
265 std::cout << "Ellipsoidal inclusion intersects polyhedron bounding box; not generated\n";
266 return;
267 }
268 }
269 }
270 mg.insertInclusion(spec.center, axes, strain, spec.velocityReductionFactor, spec.phaseID);
271 std::cout << "Generated ellipsoidal inclusion\n";
272 }
273} // namespace model
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
const std::vector< PolyhedronInclusionNodeIO< dim > > & polyhedronInclusionNodes() const
const std::vector< PolyhedronInclusionEdgeIO > & polyhedronInclusionEdges() const
EllipsoidalInclusionsGenerator(const EllipsoidalInclusionDensitySpecification &spec, MicrostructureGenerator &mg)
DefectiveCrystalBase< 3 > & ddBase
size_t insertInclusion(const VectorDimD &pos, const double &R, const Eigen::Matrix< double, dim, dim > &eT, const double &vrc, const int &type)
std::pair< bool, const Simplex< _dim, _dim > * > search(const Eigen::Matrix< double, dim, 1 > &P) const
const double & volume() const
b
Definition int_R.m:16
double triangleDistanceSquared(const Eigen::Vector3d &a, const Eigen::Vector3d &b, const Eigen::Vector3d &c)
bool overlap(const Eigen::Vector3d &displacement, const Eigen::Matrix3d &first, const Eigen::Matrix3d &second)
const Polycrystal< dim > & poly
const SimplicialMesh< dim > & mesh
int k
void vertices