3#include <Eigen/Geometry>
10 using V = Eigen::Vector3d;
11 void require(
bool ok,
const char *message)
15 throw std::runtime_error(message);
21 for (
size_t k = 0;
k < section.size(); ++
k)
23 const auto &arc = *section[
k];
24 require(arc.P1 == section[(
k + 1) % section.size()]->P0,
"Section is not exactly connected");
25 require(std::abs((arc.P0 -
plane.P).dot(
plane.unitNormal)) < 1.e-6,
"Section vertex left plane");
28 require(
area >= -1.e-10,
"Section orientation is reversed");
36 for (
const auto *tet : region.
simplices())
38 const auto vertices = tet->vertexPositionMatrix();
39 std::vector<Eigen::Vector2d>
points;
40 auto add = [&](
const V &
p)
42 const Eigen::Vector2d
x =
plane.localPosition(
p);
43 for (
const auto &existing : points)
45 if ((
x - existing).norm() < 1.e-9)
52 for (
int i = 0;
i < 4; ++
i)
55 if (std::abs(
a) < 1.e-10)
59 for (
int j =
i + 1;
j < 4; ++
j)
62 if (
a *
b < 0 && std::abs(
a) > 1.e-10 && std::abs(
b) > 1.e-10)
72 Eigen::Vector2d center = Eigen::Vector2d::Zero();
73 for (
const auto &
p : points)
75 center +=
p / double(
points.size());
78 [&](
const auto &
a,
const auto &
b)
80 return std::atan2(a.y() - center.y(), a.x() - center.x()) <
81 std::atan2(b.y() - center.y(), b.x() - center.x());
83 for (
size_t k = 0;
k <
points.size(); ++
k)
87 area += 0.5 * (
a.x() *
b.y() -
a.y() *
b.x());
92 void exercise(
const std::string &path,
const Eigen::Matrix3d &rotation,
const V &shift)
94 std::weak_ptr<model::Polyhedron<3, 0>> weakVertex;
95 std::weak_ptr<model::Polyhedron<3, 2>> weakFace;
98 const auto &topology =
mesh.polyhedralMesh();
100 weakVertex = topology.factory<0>().begin()->second;
101 weakFace = topology.factory<2>().begin()->second;
105 require(region.second.get() == &topology.
region(region.first),
"Parallel region objects remain");
106 require(®ion.second->
faces() == ®ion.second->
children,
"Faces are not the region children");
107 for (
const auto* simplex : region.second->
simplices())
109 require(
simplex->region == region.second,
"Simplex references a different region");
111 for (
const auto& face : region.second->
faces())
113 require(face.second.get() == &topology.face(face.first),
"Parallel face objects remain");
114 require(face.second->parents.at(region.first).lock() == region.second,
"Face parent is not its region");
115 require(!face.second->simplices().empty(),
"Face lost its boundary simplices");
118 for (
const auto& edge : topology.factory<1>())
120 require(edge.second->simplices().size() == 1,
"Edge does not store its mesh simplex");
122 for (
const auto&
vertex : topology.factory<0>())
124 require(
vertex.second->simplices().size() == 1,
"Vertex does not store its mesh simplex");
126 bool sharedJunction =
false;
127 for (
const auto &edge : topology.factory<1>())
129 sharedJunction = sharedJunction || edge.second->parents.size() > 2;
131 require(sharedJunction,
"Fixture did not exercise an edge with more than two parent faces");
132 for (
const auto &
f : topology.factory<2>())
134 const auto &face = *
f.second;
135 require((face.parents.size() == 1) == face.isExternal(),
"External classification differs");
136 for (
const auto &incidence : face.perimeter)
138 require(topology.factory<1>().at(incidence.edge->key) == incidence.edge,
"Duplicate edge object");
139 for (
const auto &
vertex : incidence.edge->children)
141 require(topology.factory<0>().at(
vertex.first) ==
vertex.second,
"Duplicate vertex object");
142 require(
vertex.second->parents.at(incidence.edge->key).lock() == incidence.edge,
143 "Missing edge parent");
145 for (
const auto &
parent : face.parents)
147 const auto other = incidence.otherFace(
parent.first);
148 require(other.otherFace(
parent.first).face.lock() ==
f.second,
"Face switch not reciprocal");
149 require(incidence.next().prev().source() == incidence.source(),
"Broken perimeter links");
155 V center = V::Zero();
156 for (
const auto *tet :
r.second->simplices())
158 center += tet->center() / double(
r.second->simplices().size());
160 for (
const V &localNormal : {
V(1, 0.317, -0.219),
V(0.731, 1, 0.277)})
164 const double expected = referenceArea(*
r.second, plane);
165 require(std::abs(sectionArea(section, plane) - expected) < 1.e-7 * std::max(1.0, expected),
166 "Grain contour area differs from tetrahedral cuts");
167 for (
const auto &segment : section)
169 for (
const auto *face : segment->faces)
171 require(
r.second->faces().count(face->sID),
"Section includes another grain's face");
176 expected) < 1.e-7 * std::max(1.0, expected),
177 "Reversed normal changed contour area");
179 const auto &face = *topology.
region(
r.first).children.begin()->second;
180 const auto &incidence = face.perimeter.front();
181 const V vertex = incidence.source()->position;
183 const double expected = referenceArea(*
r.second, vertexPlane);
185 expected) < 1.e-7 * std::max(1.0, expected),
186 "Vertex-hit contour changed area");
187 const V normal = incidence.tangent().cross(rotation *
V(0.419, 0.833, 0.231));
190 sectionArea(edgeSection, edgePlane);
191 if (!edgeSection.empty())
194 for (
const auto &segment : edgeSection)
196 shared = shared || segment->faces.size() == 2;
198 require(shared,
"Coincident edge lost its two supporting faces");
202 require(weakVertex.expired() && weakFace.expired(),
"Polyhedral topology contains an ownership cycle");
210 const std::string meshes = std::string(
argv[1]) +
"/Library/Meshes/";
211 const Eigen::Matrix3d rotation = Eigen::AngleAxisd(0.71,
V(1, 2, 3).normalized()).toRotationMatrix();
212 for (
const std::string name : {
"bicrystal_48.msh",
"polycrystalCube_10_periodicX.msh",
213 "polycrystalCube_10_periodicXY.msh",
"poly30_periodicXYZ.msh"})
215 exercise(meshes + name, Eigen::Matrix3d::Identity(), V::Zero());
216 exercise(meshes + name, rotation,
V(10, -5, 3));
222 V lo = V::Constant(std::numeric_limits<double>::max());
224 for (
const auto *tet : r.second->simplices())
226 for (
const auto *
vertex : tet->vertices())
228 lo = lo.cwiseMin(
vertex->P0);
229 hi = hi.cwiseMax(
vertex->P0);
234 require(std::abs(sectionArea(section, facePlane) - (hi.x() - lo.x()) * (hi.y() - lo.y())) < 1.e-8,
235 "Coincident face area incorrect");
237 "Vertex tangency is not empty");
239 "Edge tangency is not empty");
241 std::weak_ptr<model::Polyhedron<3,3>> oldRegion =
mesh.
regions().begin()->second;
242 std::weak_ptr<model::Polyhedron<3, 0>> oldVertex =
mesh.polyhedralMesh().factory<0>().begin()->second;
243 mesh.readMesh(meshes +
"bicrystal_48.msh", rotation,
V(1, 2, 3), {});
244 require(oldRegion.expired(),
"Reload retained the previous region objects");
245 require(oldVertex.expired(),
"Reload retained the previous topology");
246 mesh.polyhedralMesh().validate();
247 std::cout <<
"PASS factory sharing, reciprocal incidences, grain contours, degeneracies and ownership\n";
249 catch (
const std::exception &error)
251 std::cerr << error.what() <<
'\n';
Polyhedron< _dim, _dim > * region(const size_t &id)
function int_R syms a b c u real f
auto eval(const std::shared_ptr< T > &t)
const MeshFacesContainerType & faces() const
const SimplexContainerType & simplices() const
void exercise(const std::filesystem::path &root, int rank, bool clamp)
void require(bool value, const char *message)