PhysicsBasedAnimationToolkit 0.0.10
Cross-platform C++20 library of algorithms and data structures commonly used in computer graphics research on physically-based simulation.
Loading...
Searching...
No Matches
Mesh.h
Go to the documentation of this file.
1
10
11#ifndef PBAT_FEM_MESH_H
12#define PBAT_FEM_MESH_H
13
14#include "Concepts.h"
15#include "Jacobian.h"
16#include "pbat/Aliases.h"
18#include "pbat/common/Eigen.h"
19#include "pbat/io/Archive.h"
20#include "pbat/math/Rational.h"
22
23#include <algorithm>
24#include <exception>
25#include <map>
26#include <numeric>
27#include <ranges>
28
29namespace pbat::fem {
30
39template <
40 CElement TElement,
41 int Dims,
42 common::CFloatingPoint TScalar = Scalar,
43 common::CIndex TIndex = Index>
44struct Mesh
45{
46 using ElementType = TElement;
47 using ScalarType = TScalar;
48 using IndexType = TIndex;
49 static int constexpr kDims = Dims;
50 static int constexpr kOrder = ElementType::kOrder;
51 static int constexpr kNodesPerElement = ElementType::kNodes;
52 using NodeMatrix =
53 Eigen::Matrix<ScalarType, kDims, Eigen::Dynamic>;
55 Eigen::Matrix<IndexType, kNodesPerElement, Eigen::Dynamic>;
56
57 Mesh() = default;
67 template <class TDerivedV, class TDerivedC>
68 Mesh(Eigen::MatrixBase<TDerivedV> const& V, Eigen::DenseBase<TDerivedC> const& C);
78 template <class TDerivedV, class TDerivedC>
79 void Construct(Eigen::MatrixBase<TDerivedV> const& V, Eigen::DenseBase<TDerivedC> const& C);
85 template <int QuadratureOrder>
86 auto QuadraturePoints() const -> Eigen::Matrix<ScalarType, Eigen::Dynamic, Eigen::Dynamic>;
95 template <
96 int QuadratureOrder,
97 int kPoints = TElement::template QuadratureType<QuadratureOrder, ScalarType>::kPoints>
98 auto QuadratureWeights() const -> Eigen::Vector<ScalarType, kPoints>;
99
104 void Serialize(io::Archive& archive) const;
109 void Deserialize(io::Archive const& archive);
110
113};
114
115namespace detail {
116
117template <CElement TElement, common::CIndex TIndex>
118class NodalKey
119{
120 public:
121 using SelfType = NodalKey<TElement, TIndex>;
122 static int constexpr kVertices = TElement::AffineBaseType::kNodes;
123
124 NodalKey(
125 Eigen::Vector<TIndex, kVertices> const& cellVertices,
126 Eigen::Vector<TIndex, kVertices> const& sortOrder,
127 Eigen::Vector<math::Rational, kVertices> const& N)
128 : mCellVertices(cellVertices), mSortOrder(sortOrder), mN(N), mSize()
129 {
130 // Remove vertices whose corresponding shape function is zero
131 auto it = std::remove_if(mSortOrder.begin(), mSortOrder.end(), [this](TIndex o) {
132 return mN[o] == 0;
133 });
134 // Count number of non-zero shape functions into Size
135 mSize = static_cast<decltype(mSize)>(std::distance(mSortOrder.begin(), it));
136 }
137
138 bool operator==(SelfType const& rhs) const
139 {
140 // Sizes must match
141 if (mSize != rhs.mSize)
142 return false;
143 for (auto i = 0u; i < mSize; ++i)
144 {
145 // Vertices involved in affine map must match
146 TIndex const lhsVertex = mCellVertices[mSortOrder[i]];
147 TIndex const rhsVertex = rhs.mCellVertices[rhs.mSortOrder[i]];
148 if (lhsVertex != rhsVertex)
149 return false;
150 // Affine weights at matching vertices must match
151 math::Rational const lhsN = mN[mSortOrder[i]];
152 math::Rational const rhsN = rhs.mN[rhs.mSortOrder[i]];
153 if (lhsN != rhsN)
154 return false;
155 }
156 // Everything matches, (*this) and rhs must represent same node
157 return true;
158 }
159
160 bool operator<(SelfType const& rhs) const
161 {
162 // Sort by size first
163 if (mSize != rhs.mSize)
164 return mSize < rhs.mSize;
165 // Then sort by vertex indices
166 for (auto i = 0; i < mSize; ++i)
167 {
168 TIndex const lhsVertex = mCellVertices[mSortOrder[i]];
169 TIndex const rhsVertex = rhs.mCellVertices[rhs.mSortOrder[i]];
170 if (lhsVertex != rhsVertex)
171 return lhsVertex < rhsVertex;
172 }
173 // Then sort by coordinates
174 for (auto i = 0; i < mSize; ++i)
175 {
176 math::Rational const lhsN = mN[mSortOrder[i]];
177 math::Rational const rhsN = rhs.mN[rhs.mSortOrder[i]];
178 if (lhsN != rhsN)
179 return lhsN < rhsN;
180 }
181 // (*this) == rhs is true, so (*this) is not less than rhs
182 return false;
183 }
184
185 private:
186 Eigen::Vector<TIndex, kVertices> mCellVertices;
187 Eigen::Vector<TIndex, kVertices> mSortOrder;
188 Eigen::Vector<math::Rational, kVertices> mN;
189 int mSize;
190};
191
192} // namespace detail
193
194template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
195template <class TDerivedV, class TDerivedC>
196inline Mesh<TElement, Dims, TScalar, TIndex>::Mesh(
197 Eigen::MatrixBase<TDerivedV> const& V,
198 Eigen::DenseBase<TDerivedC> const& C)
199{
200 Construct(V, C);
201}
202
203template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
204template <class TDerivedV, class TDerivedC>
206 Eigen::MatrixBase<TDerivedV> const& V,
207 Eigen::DenseBase<TDerivedC> const& C)
208{
209 PBAT_PROFILE_NAMED_SCOPE("pbat.fem.Mesh.Construct");
210 static_assert(
211 kDims >= ElementType::kDims,
212 "Element TElement does not exist in Dims dimensions");
213 static_assert(
214 std::is_same_v<TScalar, typename TDerivedV::Scalar>,
215 "Vertex positions matrix V must have the same scalar type as TScalar");
216 static_assert(
217 std::is_same_v<TIndex, typename TDerivedC::Scalar>,
218 "Cell vertex indices matrix C must have the same index type as TIndex");
219
220 // Smart nodal indexing is only relevant for higher-order meshes
221 if constexpr (kOrder == 1)
222 {
223 X = V;
224 E = C;
225 return;
226 }
227 else
228 {
229 using AffineElementType = typename ElementType::AffineBaseType;
230 auto constexpr kVerticesPerCell = AffineElementType::kNodes;
231 assert(C.rows() == kVerticesPerCell);
232 assert(V.rows() == kDims);
233
234 using NodalKey = detail::NodalKey<TElement, TIndex>;
235 using NodeMap = std::map<NodalKey, TIndex>;
236 using DVector = Eigen::Vector<TScalar, kDims>;
237
238 auto const numberOfCells = C.cols();
239 auto const numberOfVertices = V.cols();
240
241 NodeMap nodeMap{};
242 std::vector<DVector> nodes{};
243 nodes.reserve(static_cast<std::size_t>(numberOfVertices));
244
245 // Construct mesh topology, i.e. assign mesh nodes to elements,
246 // ensuring that adjacent elements share their common nodes.
247 E.resize(ElementType::kNodes, numberOfCells);
248 for (auto c = 0; c < numberOfCells; ++c)
249 {
250 Eigen::Vector<TIndex, kVerticesPerCell> cellVertices = C.col(c);
251 Eigen::Matrix<TScalar, kDims, kVerticesPerCell> const Xc =
252 V(Eigen::placeholders::all, cellVertices);
253
254 // Sort based on cell vertex index
255 decltype(cellVertices) sortOrder{};
256 std::iota(sortOrder.begin(), sortOrder.end(), TIndex(0));
257 std::ranges::sort(sortOrder, [&](TIndex i, TIndex j) {
258 return cellVertices[i] < cellVertices[j];
259 });
260 // Loop over nodes of element and create the node on first visit
261 auto const nodalCoordinates = common::ToEigen(ElementType::Coordinates)
262 .reshaped(ElementType::kDims, ElementType::kNodes)
263 .template cast<math::Rational>() /
264 ElementType::kOrder;
265 for (auto i = 0; i < nodalCoordinates.cols(); ++i)
266 {
267 // Use exact rational arithmetic to evaluate affine element shape functions at the
268 // node to get its exact affine coordinates
269 auto const Xi = nodalCoordinates.col(i);
270 auto const N = AffineElementType::N(Xi);
271 NodalKey const key{cellVertices, sortOrder, N};
272 auto it = nodeMap.find(key);
273 bool const bNodeAlreadyCreated = it != nodeMap.end();
274 if (!bNodeAlreadyCreated)
275 {
276 auto const nodeIdx = static_cast<TIndex>(nodes.size());
277 Eigen::Vector<TScalar, kDims> const xi = Xc * N.template cast<TScalar>();
278 nodes.push_back(xi);
279 bool bInserted{};
280 std::tie(it, bInserted) = nodeMap.insert({key, nodeIdx});
281 assert(bInserted);
282 }
283 TIndex const node = it->second;
284 E(i, c) = node;
285 }
286 }
287 // Collect node positions
288 X = common::ToEigen(nodes);
289 }
290}
291
292template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
293template <int QuadratureOrder>
294inline Eigen::Matrix<TScalar, Eigen::Dynamic, Eigen::Dynamic>
296{
297 using QuadraturePointPositions = Eigen::Matrix<TScalar, Eigen::Dynamic, Eigen::Dynamic>;
298 using AffineElementType = typename ElementType::AffineBaseType;
299 using QuadratureRuleType =
300 typename ElementType::template QuadratureType<QuadratureOrder, TScalar>;
301 auto constexpr kQuadPts = QuadratureRuleType::kPoints;
302 auto const numberOfElements = E.cols();
303 auto const XgRef = common::ToEigen(QuadratureRuleType::points)
304 .reshaped(QuadratureRuleType::kDims + 1, kQuadPts);
305 QuadraturePointPositions Xg(Dims, numberOfElements * kQuadPts);
306 for (auto e = 0; e < numberOfElements; ++e)
307 {
308 auto const nodes = E.col(e);
309 auto const vertices = nodes(ElementType::Vertices);
310 auto constexpr kRowsJ = Dims;
311 auto constexpr kColsJ = AffineElementType::kNodes;
312 Eigen::Matrix<TScalar, kRowsJ, kColsJ> const Ve = X(Eigen::placeholders::all, vertices);
313 for (auto g = 0; g < kQuadPts; ++g)
314 {
315 Xg.col(e * kQuadPts + g) = Ve * XgRef.col(g);
316 }
317 }
318 return Xg;
319}
320
321template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
322template <int QuadratureOrder, int kPoints>
323inline Eigen::Vector<TScalar, kPoints>
325{
326 using QuadratureRuleType =
327 typename ElementType::template QuadratureType<QuadratureOrder, TScalar>;
328 auto constexpr kQuadPts = QuadratureRuleType::kPoints;
329 static_assert(
330 kQuadPts == kPoints,
331 "kPoints must match the number of quadrature points for the given QuadratureOrder");
332 Eigen::Vector<TScalar, kQuadPts> const wg = common::ToEigen(QuadratureRuleType::weights);
333 return wg;
334}
335
336template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
338{
339 io::Archive meshArchive = archive["pbat.fem.Mesh"];
340 meshArchive.WriteData("X", X);
341 meshArchive.WriteData("E", E);
342 meshArchive.WriteMetaData("kDims", kDims);
343 meshArchive.WriteMetaData("kOrder", kOrder);
344}
345
346template <CElement TElement, int Dims, common::CFloatingPoint TScalar, common::CIndex TIndex>
348{
349 io::Archive const meshArchive = archive["pbat.fem.Mesh"];
350 int const kDimsRead = meshArchive.ReadMetaData<int>("kDims");
351 int const kOrderRead = meshArchive.ReadMetaData<int>("kOrder");
352 if (kDimsRead != kDims)
353 {
354 throw std::runtime_error(
355 "pbat::fem::Mesh::Deserialize(): kDims in archive does not match template parameter "
356 "kDims");
357 }
358 if (kOrderRead != kOrder)
359 {
360 throw std::runtime_error(
361 "pbat::fem::Mesh::Deserialize(): kOrder in archive does not match template parameter "
362 "kOrder");
363 }
364 X = meshArchive.ReadData<NodeMatrix>("X");
365 E = meshArchive.ReadData<ElementMatrix>("E");
366 if (E.rows() != ElementType::kNodes)
367 {
368 throw std::runtime_error(
369 "pbat::fem::Mesh::Deserialize(): Number of rows in E does not match Element::kNodes");
370 }
371}
372
373} // namespace pbat::fem
374
375#endif // PBAT_FEM_MESH_H
(De)serializer
Functions to compute jacobians, their determinants, domain quadrature weights and mapping domain to r...
Fixed size rational number representation using std::int64_t as numerator and denominator.
Archive class for reading and writing data to HDF5 files.
Definition Archive.h:29
T ReadData(std::string const &path) const
Read data from the archive.
Definition Archive.h:179
void WriteMetaData(std::string const &key, T const &value)
Write metadata to the archive.
Definition Archive.h:158
void WriteData(std::string const &path, T const &data)
Write data to the archive.
Definition Archive.h:137
T ReadMetaData(std::string const &key) const
Read metadata from the archive.
Definition Archive.h:195
Concepts for common types.
Eigen adaptors for ranges.
Reference finite element.
Definition Concepts.h:63
auto ToEigen(R &&r) -> Eigen::Map< Eigen::Vector< std::ranges::range_value_t< R >, Eigen::Dynamic > const >
Map a range of scalars to an eigen vector of such scalars.
Definition Eigen.h:28
Finite Element Method (FEM)
Definition Concepts.h:19
Namespace for I/O.
Definition Archive.cpp:7
Eigen::Vector< Scalar, N > Vector
Fixed-size vector type.
Definition Aliases.h:24
std::ptrdiff_t Index
Index type.
Definition Aliases.h:17
double Scalar
Scalar type.
Definition Aliases.h:18
Eigen::Matrix< Scalar, Rows, Cols > Matrix
Fixed-size matrix type.
Definition Aliases.h:31
Profiling utilities for the Physics-Based Animation Toolkit (PBAT)
void Construct(Eigen::MatrixBase< TDerivedV > const &V, Eigen::DenseBase< TDerivedC > const &C)
Constructs a finite element mesh given some input geometric mesh.
Definition Mesh.h:205
static int constexpr kOrder
Definition Mesh.h:50
Eigen::Matrix< ScalarType, kDims, Eigen::Dynamic > NodeMatrix
Node positions matrix type.
Definition Mesh.h:52
auto QuadratureWeights() const -> Eigen::Vector< ScalarType, kPoints >
void Deserialize(io::Archive const &archive)
TScalar ScalarType
Floating point type.
Definition Mesh.h:47
void Serialize(io::Archive &archive) const
static int constexpr kNodesPerElement
Definition Mesh.h:51
TIndex IndexType
Index type.
Definition Mesh.h:48
auto QuadraturePoints() const -> Eigen::Matrix< ScalarType, Eigen::Dynamic, Eigen::Dynamic >
Compute quadrature points in domain space on this mesh.
Eigen::Matrix< IndexType, kNodesPerElement, Eigen::Dynamic > ElementMatrix
Element indices matrix type.
Definition Mesh.h:54
ElementMatrix E
Definition Mesh.h:112
Mesh(Eigen::MatrixBase< TDerivedV > const &V, Eigen::DenseBase< TDerivedC > const &C)
Constructs a finite element mesh given some input geometric mesh.
Definition Mesh.h:196
TElement ElementType
Underlying finite element type.
Definition Mesh.h:46
static int constexpr kDims
Definition Mesh.h:49
Fixed size rational number using std::int64_t for numerator and denominator.
Definition Rational.h:29