46 if(!std::isfinite(spec.targetDensity) || spec.targetDensity < 0.0)
48 throw std::invalid_argument(
"Ellipsoidal density must be finite and nonnegative");
50 if(spec.targetDensity == 0.0)
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)
60 throw std::invalid_argument(
"Invalid ellipsoidal size distribution, pattern, or attempt limit");
62 if(!spec.crystalAxes.allFinite() ||
63 (spec.crystalAxes.transpose() * spec.crystalAxes - Eigen::Matrix3d::Identity()).cwiseAbs().maxCoeff() >
66 throw std::invalid_argument(
"Ellipsoidal crystalAxes must contain orthogonal unit vectors");
68 Eigen::Vector3d logMean;
69 Eigen::Vector3d logStd;
70 for(
int k = 0;
k < 3; ++
k)
72 const double mean = spec.axesLognormalDistributionMean_SI(
k);
73 const double stdSI = spec.axesLognormalDistributionStd_SI(
k);
75 double logVariance = 0.0;
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));
82 logStd(
k) = std::sqrt(logVariance);
83 logMean(
k) = std::log(mean) - 0.5 * logVariance;
87 candidate.
crystalAxes_SI = spec.crystalAxes * spec.axesLognormalDistributionMean_SI.asDiagonal();
92 candidate.
phaseID = spec.phaseID;
93 validateEllipsoid(candidate,
b);
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()))
99 throw std::invalid_argument(
"Ellipsoidal density exceeds numerical range");
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))
106 throw std::invalid_argument(
"Ellipsoidal pattern exceeds numerical range");
109 std::normal_distribution<double> normal(0.0, 1.0);
110 size_t generated = 0;
112 while(generated < targetCount)
114 if(attempts == spec.maxAttempts)
116 throw std::runtime_error(
"Ellipsoidal density placement exhausted maxAttempts after generating " +
117 std::to_string(generated) +
" of " + std::to_string(targetCount));
120 const auto point = mg.
ddBase.
poly.randomLatticePointInMesh();
121 candidate.
center = point.first.cartesian();
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;
129 if(!projected.first || projected.second->region->regionID !=
static_cast<size_t>(point.second))
134 Eigen::Vector3d sampledLengths;
135 for(
int k = 0;
k < 3; ++
k)
137 if(spec.axesLognormalDistributionStd_SI(
k) == 0.0)
139 sampledLengths(
k) = spec.axesLognormalDistributionMean_SI(
k);
143 sampledLengths(
k) = std::exp(logMean(
k) + logStd(
k) * normal(random));
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)
161 std::cout <<
"Generated ellipsoidal number density=" << generated / volumeSI <<
" m^-3\n";
167 using V = Eigen::Vector3d;
168 using M = Eigen::Matrix3d;
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();
176 if(!searchPair.first)
178 std::cout <<
"Ellipsoidal inclusion center outside mesh; cannot determine crystal frame; not generated\n";
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)
189 throw std::invalid_argument(
"EllipsoidalInclusionsGenerator: geometry exceeds numerical range");
192 if(!spec.allowOutside)
196 for(
const auto &face : mg.
ddBase.
mesh.template simplexFactory<2>().liveSimplices())
198 if(face.second->isBoundarySimplex())
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)
205 std::cout <<
"Ellipsoidal inclusion reaches mesh boundary; not generated\n";
211 if(!spec.allowOverlap)
216 M::Identity() * inclusion.a * inclusion.a))
218 std::cout <<
"Ellipsoidal inclusion overlaps a sphere; not generated\n";
225 inclusion.axes * inclusion.axes.transpose()))
227 std::cout <<
"Ellipsoidal inclusion overlaps an ellipsoid; not generated\n";
233 const V extent = shape.diagonal().cwiseSqrt();
236 V lower = V::Constant(std::numeric_limits<double>::infinity()), upper = -lower;
240 if(edge.inclusionID != inclusion.inclusionID)
244 for(
const auto nodeID : {edge.sourceID, edge.sinkID})
248 std::find_if(nodes.begin(), nodes.end(), [&](
const auto &n) { return n.nodeID == nodeID; });
249 if(node == nodes.end() || !node->P.allFinite())
251 throw std::runtime_error(
"EllipsoidalInclusionsGenerator: invalid polyhedron node");
253 lower = lower.cwiseMin(node->P);
254 upper = upper.cwiseMax(node->P);
260 throw std::runtime_error(
"EllipsoidalInclusionsGenerator: polyhedron has no boundary edges");
262 if(((spec.center + extent).array() >= lower.array()).all() &&
263 ((spec.center - extent).array() <= upper.array()).all())
265 std::cout <<
"Ellipsoidal inclusion intersects polyhedron bounding box; not generated\n";
270 mg.
insertInclusion(spec.center, axes, strain, spec.velocityReductionFactor, spec.phaseID);
271 std::cout <<
"Generated ellipsoidal inclusion\n";