svMultiPhysics
Loading...
Searching...
No Matches
BasisFunction.h
1// SPDX-FileCopyrightText: Copyright (c) Stanford University, The Regents of the University of California, and others.
2// SPDX-License-Identifier: BSD-3-Clause
3
4#ifndef SVMP_FE_BASIS_BASISFUNCTION_H
5#define SVMP_FE_BASIS_BASISFUNCTION_H
6
7#include "BasisExceptions.h"
8#include "BasisTraits.h"
9#include "Math/Matrix.h"
10#include "Math/Vector.h"
11#include "Types.h"
12
13#include <cstddef>
14#include <span>
15#include <vector>
16
17/**
18 * @defgroup FE_Basis Basis
19 * @ingroup FE
20 * @brief Basis-function interfaces, concrete basis families, and reference-node conventions.
21 *
22 * @details
23 * ## Scope
24 *
25 * The Basis module owns reference-element shape functions. It provides the
26 * number of basis functions and the values and derivatives,
27 * @f$N_i@f$, @f$\partial N_i / \partial \xi_j@f$, and
28 * @f$\partial^2 N_i / \partial \xi_j \partial \xi_k@f$ at reference
29 * points. It does not own mesh storage, quadrature selection, field
30 * formulation policy, or transformation of derivatives to physical
31 * coordinates. Those decisions stay with the solver layer that has the mesh,
32 * material model, and equation context.
33 *
34 * The main pieces are:
35 * - @ref svmp::FE::basis::BasisFunction "BasisFunction" (BasisFunction.h): the
36 * abstract query and evaluation contract for code that does not need to know
37 * the concrete family.
38 * - @ref FE_LagrangeBasis "LagrangeBasis" and
39 * @ref FE_SerendipityBasis "SerendipityBasis": the implemented nodal
40 * families, including analytical first and second derivatives in reference
41 * coordinates.
42 * - basis_factory (BasisFactory.h): runtime construction from a
43 * @ref svmp::FE::basis::BasisRequest "BasisRequest".
44 * basis_factory::default_basis_request() centralizes the family/order that
45 * matches each supported element's public node layout.
46 * - @ref svmp::FE::basis::ReferenceNodeLayout "ReferenceNodeLayout"
47 * (NodeOrderingConventions.h): canonical reference-node coordinates and the
48 * output ordering used by every basis evaluator.
49 * - @ref svmp::FE::basis::BasisTopology "BasisTopology" (BasisTraits.h) and the
50 * @ref FE_BasisExceptions "basis exceptions" (BasisExceptions.h): topology
51 * classification, compile-time helpers, and module-specific exception types.
52 *
53 * ## Object and evaluation contract
54 *
55 * A basis object is immutable after construction. It represents one reference
56 * topology (e.g. tetrahedron, hexahedron), basis family (Lagrange or
57 * serendipity), and effective polynomial order, and can be shared
58 * safely across evaluations. Construction may be computationally expensive -- it
59 * can build node lattices or invert interpolation matrices -- so a basis should
60 * be constructed only once for each distinct basis request, through
61 * basis_factory, and reused rather than rebuilt inside element loops.
62 *
63 * Every evaluator takes a three-component reference coordinate. For
64 * lower-dimensional elements, only the first dimension() components are
65 * active. Returned gradients always have three components and Hessians are
66 * always 3-by-3 matrices; inactive reference directions are expected to be
67 * zero for conforming lower-dimensional bases. The *_to overloads write to
68 * caller-owned spans and are the override points a concrete family implements:
69 * the nodal families (LagrangeBasis, SerendipityBasis) compute directly into the
70 * span, so this is the allocation-free path for assembly. The std::vector
71 * overloads are convenient for setup, tests, and adapter code; they are defined
72 * once on the base class, which sizes the output and forwards to the matching
73 * span overload.
74 *
75 * Outputs are in ReferenceNodeLayout basis order, not necessarily the mesh or
76 * solver's native node order. A caller that stores elements in another local
77 * ordering must apply the appropriate permutation at the boundary between the
78 * basis module and that storage format.
79 *
80 * ## Inputs and ownership
81 *
82 * Constructing and evaluating a basis combines several independent choices:
83 *
84 * - **Element topology comes from the mesh.** The mesh cell type is translated
85 * to ElementType, which defines the reference topology and public node
86 * layout. This is structural information, not a complete discretization
87 * policy.
88 * - **Geometry interpolation follows the mesh nodes.** The basis used for the
89 * reference-to-physical map must be compatible with the element's node
90 * count and ordering. For that case, callers normally use
91 * basis_factory::create_default_for(element_type), which selects the
92 * Lagrange or serendipity space associated with that element layout. A
93 * Tetra10 mesh therefore implies a quadratic geometry map; a Hex20 mesh
94 * implies the supported Hex20 serendipity geometry basis.
95 * - **Field approximation is chosen by the formulation.** Field bases do not
96 * have to match the geometry map. Mixed formulations, stabilized methods,
97 * enrichment, and convergence studies may use different families or orders
98 * for different fields on the same mesh topology. Those bases should be
99 * requested explicitly with basis_factory::create() and a BasisRequest
100 * naming the desired family, topology, and order.
101 * - **Evaluation points come from the caller.** Quadrature rules, probe
102 * points, interpolation targets, and error-sampling locations are outside
103 * this module. The basis only evaluates at the reference coordinates it is
104 * given.
105 *
106 * @dot "Basis inputs and responsibilities"
107 * digraph fe_basis_information_flow {
108 * rankdir=LR;
109 * node [shape=box, fontname=Helvetica, fontsize=10];
110 * mesh [label="Mesh element type"];
111 * request [label="BasisRequest\nfamily + order"];
112 * topology [label="Reference topology\nand node layout"];
113 * basis [label="Basis object", style=filled, fillcolor=lightgray];
114 * points [label="Reference points"];
115 * outputs [label="Reference values\nand derivatives"];
116 * mesh -> topology;
117 * request -> basis;
118 * topology -> basis;
119 * basis -> outputs;
120 * points -> outputs;
121 * }
122 * @enddot
123 *
124 * ## Reference scope and the solver adapter
125 *
126 * The solver-facing adapter in nn.cpp is the boundary between this reference
127 * basis contract and legacy solver storage. It translates solver element
128 * enums to ElementType, obtains cached default bases for mesh/face shape
129 * tables, permutes from ReferenceNodeLayout order into solver node order, and
130 * stores N, Nx, and, where needed, packed Nxx at Gauss points. At that stage
131 * Nx and Nxx are still derivatives with respect to reference coordinates.
132 * Physical-coordinate derivatives are formed later, for a particular
133 * configuration and element geometry, by composing the cached reference data
134 * with the mapping Jacobian (nn::gnn for first derivatives and nn::gn_nxx for
135 * second derivatives).
136 */
137
138namespace svmp::FE::basis {
139
140/** @brief Gradient vector type used by basis evaluators. */
141using Gradient = math::Vector<double, 3>;
142
143/** @brief Hessian matrix type used by basis evaluators. */
144using Hessian = math::Matrix<double, 3, 3>;
145
146/**
147 * @brief Throw BasisEvaluationException when an output span is smaller than the
148 * basis size. \p label is the full "Class::method" context used in the message,
149 * so each basis family passes its own qualified name.
150 */
151void require_span_size(std::size_t actual, std::size_t expected, const char* label);
152
153/**
154 * @brief Abstract interface for finite-element basis-function families.
155 * @ingroup FE_Basis
156 *
157 * BasisFunction defines the common query and evaluation API used by solver
158 * code that does not need to know the concrete basis implementation. Concrete
159 * families implement the span output primitives -- shape function values at
160 * minimum, and optionally analytical gradients and Hessians; the vector
161 * overloads and the combined evaluator are provided once by the base class. The
162 * interface is deliberately limited to reference-space quantities; callers own
163 * node ordering translation, physical mapping, and any field-level discretization
164 * policy.
165 */
167public:
168 /** @brief Destroy a basis function through the abstract interface. */
169 virtual ~BasisFunction() = default;
170
171 /**
172 * @brief Return the concrete basis family.
173 * @return Basis family identifier.
174 */
175 virtual BasisType basis_type() const noexcept = 0;
176
177 /**
178 * @brief Return the reference topology of this basis.
179 *
180 * @details Together with order() and basis_type(), this is the authoritative
181 * identity of a basis: a topology, a polynomial order, and a basis family,
182 * with no node-count assumption. The family is part of the identity because
183 * the same topology and order can denote different bases -- a hexahedron at
184 * order 2 is the Hex20 serendipity space or the Hex27 Lagrange space
185 * depending on basis_type(). Arbitrary-order bases are constructed from a
186 * BasisTopology and an order; named ElementType layouts (Hex8, Hex27, ...)
187 * are a fixed-order shorthand that maps to the same (topology, order, family)
188 * triple.
189 *
190 * @return Reference topology.
191 */
192 virtual BasisTopology topology() const noexcept = 0;
193
194 /**
195 * @brief Return the reference-space dimension of the basis.
196 * @return Reference dimension, from zero for points through three for volume elements.
197 */
198 virtual int dimension() const noexcept = 0;
199
200 /**
201 * @brief Return the polynomial order represented by this basis.
202 * @return Polynomial order of the basis. A named element layout reports the
203 * order implied by that layout (Quad8 and Hex20 report 2, Hex8
204 * reports 1), not its node count.
205 */
206 virtual int order() const noexcept = 0;
207
208 /**
209 * @brief Return the number of basis functions and reference nodes.
210 * @return Basis function count.
211 */
212 virtual std::size_t size() const noexcept = 0;
213
214 /**
215 * @brief Return the reference interpolation nodes in basis ordering.
216 *
217 * @details Nodal families return one reference-element coordinate per basis
218 * function, in the same order as the evaluator outputs. Bases that do not
219 * define interpolation nodes (non-nodal families, or abstract base usage)
220 * return an empty vector. The returned reference is valid for the lifetime
221 * of the basis object.
222 *
223 * @return Reference node coordinates: size() entries for nodal families,
224 * empty otherwise.
225 */
226 virtual const std::vector<math::Vector<double, 3>>& nodes() const noexcept;
227
228 /**
229 * @brief Evaluate basis function values at a reference coordinate.
230 *
231 * @details Convenience overload: it sizes \p values to size() and forwards to
232 * evaluate_values_to(). It is implemented once on the base class, so concrete
233 * families override the span primitive rather than this overload. The result
234 * is delivered through the output argument rather than by return value so a
235 * caller can reuse one container across repeated evaluations (for example,
236 * across quadrature points) instead of allocating on every call.
237 *
238 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
239 * @param values Receives one value per basis function.
240 */
241 void evaluate_values(const math::Vector<double, 3>& xi,
242 std::vector<double>& values) const;
243
244 /**
245 * @brief Evaluate basis gradients at a reference coordinate.
246 *
247 * @details Convenience overload over evaluate_gradients_to(); see
248 * evaluate_values() for the sizing and forwarding contract.
249 *
250 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
251 * @param gradients Receives one three-component gradient per basis function.
252 * @throws BasisEvaluationException If gradients are not available for the basis.
253 */
254 void evaluate_gradients(const math::Vector<double, 3>& xi,
255 std::vector<Gradient>& gradients) const;
256
257 /**
258 * @brief Evaluate basis Hessians at a reference coordinate.
259 *
260 * @details Convenience overload over evaluate_hessians_to(); see
261 * evaluate_values() for the sizing and forwarding contract.
262 *
263 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
264 * @param hessians Receives one 3-by-3 Hessian per basis function.
265 * @throws BasisEvaluationException If Hessians are not available for the basis.
266 */
267 void evaluate_hessians(const math::Vector<double, 3>& xi,
268 std::vector<Hessian>& hessians) const;
269
270 /**
271 * @brief Evaluate values, gradients, and Hessians together.
272 *
273 * @details Convenience overload over evaluate_all_to(): it sizes all three
274 * containers to size() and forwards them in a single pass.
275 *
276 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
277 * @param values Receives one value per basis function.
278 * @param gradients Receives one three-component gradient per basis function.
279 * @param hessians Receives one 3-by-3 Hessian per basis function.
280 */
281 void evaluate_all(const math::Vector<double, 3>& xi,
282 std::vector<double>& values,
283 std::vector<Gradient>& gradients,
284 std::vector<Hessian>& hessians) const;
285
286 /**
287 * @brief Evaluate basis values into caller-provided storage.
288 *
289 * @details This span primitive is the single required override for a concrete
290 * basis: the vector overloads above and the combined evaluate_all_to() are all
291 * defined in terms of it, so a minimal basis implements only this method.
292 *
293 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
294 * @param values_out Output span with at least size() entries.
295 */
296 virtual void evaluate_values_to(const math::Vector<double, 3>& xi,
297 std::span<double> values_out) const = 0;
298
299 /**
300 * @brief Evaluate basis gradients into caller-provided storage.
301 *
302 * @details Override to supply analytical gradients. The base implementation
303 * throws, so a family that provides no gradients reports it uniformly through
304 * every gradient entry point.
305 *
306 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
307 * @param gradients_out Output span with at least size() entries.
308 * @throws BasisEvaluationException If gradients are not available for the basis.
309 */
310 virtual void evaluate_gradients_to(const math::Vector<double, 3>& xi,
311 std::span<Gradient> gradients_out) const;
312
313 /**
314 * @brief Evaluate basis Hessians into caller-provided storage.
315 *
316 * @details Override to supply analytical Hessians. The base implementation
317 * throws, so a family that provides no Hessians reports it uniformly through
318 * every Hessian entry point.
319 *
320 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
321 * @param hessians_out Output span with at least size() entries.
322 * @throws BasisEvaluationException If Hessians are not available for the basis.
323 */
324 virtual void evaluate_hessians_to(const math::Vector<double, 3>& xi,
325 std::span<Hessian> hessians_out) const;
326
327protected:
328 /**
329 * @brief Evaluate any non-empty subset of values, gradients, and Hessians
330 * into caller-provided storage in a single pass.
331 *
332 * @details An empty span selects "skip that quantity". The base
333 * implementation forwards each requested quantity to its single-quantity span
334 * primitive; families that can share per-point setup override this to compute
335 * the requested quantities together. It backs the public evaluate_all()
336 * overload.
337 *
338 * @param xi Reference coordinate. Lower-dimensional elements use the active prefix components.
339 * @param values_out Values output span, or empty to skip.
340 * @param gradients_out Gradients output span, or empty to skip.
341 * @param hessians_out Hessians output span, or empty to skip.
342 */
343 virtual void evaluate_all_to(const math::Vector<double, 3>& xi,
344 std::span<double> values_out,
345 std::span<Gradient> gradients_out,
346 std::span<Hessian> hessians_out) const;
347
348 /**
349 * @brief Approximate gradients by centered finite differences of values.
350 *
351 * @details This helper is primarily a verification utility for tests: it
352 * provides a basis-independent reference that checks a concrete basis's
353 * analytical evaluate_gradients() against centered finite differences of
354 * evaluate_values(). It lives on the base class so any BasisFunction can be
355 * checked uniformly, and having no production caller is by design — every
356 * shipped basis supplies analytical gradients. Centered differences add
357 * truncation/roundoff sensitivity and require multiple value evaluations
358 * per reference coordinate, so analytical gradients are always preferred
359 * outside this testing context.
360 */
361 void numerical_gradient(const math::Vector<double, 3>& xi,
362 std::vector<Gradient>& gradients,
363 double eps = double(1e-6)) const;
364
365 /**
366 * @brief Approximate Hessians by centered finite differences of gradients.
367 *
368 * @details Companion verification utility to numerical_gradient: it checks
369 * a basis's analytical evaluate_hessians() against centered finite
370 * differences of evaluate_gradients(). Because it differentiates gradients,
371 * it is only meaningful for bases that already provide them. Like
372 * numerical_gradient it is test-support rather than a production fallback —
373 * finite-difference Hessians amplify numerical error and require repeated
374 * gradient evaluations, so analytical Hessians are used everywhere outside
375 * tests.
376 */
377 void numerical_hessian(const math::Vector<double, 3>& xi,
378 std::vector<Hessian>& hessians,
379 double eps = double(1e-5)) const;
380};
381
382} // namespace svmp::FE::basis
383
384#endif // SVMP_FE_BASIS_BASISFUNCTION_H
Reference-topology vocabulary (BasisTopology) and the internal ElementType/topology/order maps.
Fixed-size vector types for FE computations, backed by Eigen.
Fixed-size matrix types for FE computations, backed by Eigen.
Fundamental type definitions for the finite element library.
The Vector template class is used for storing int and double data.
Definition Vector.h:26
Abstract interface for finite-element basis-function families.
Definition BasisFunction.h:166
void evaluate_gradients(const math::Vector< double, 3 > &xi, std::vector< Gradient > &gradients) const
Evaluate basis gradients at a reference coordinate.
Definition BasisFunction.cpp:33
virtual void evaluate_gradients_to(const math::Vector< double, 3 > &xi, std::span< Gradient > gradients_out) const
Evaluate basis gradients into caller-provided storage.
Definition BasisFunction.cpp:61
void evaluate_values(const math::Vector< double, 3 > &xi, std::vector< double > &values) const
Evaluate basis function values at a reference coordinate.
Definition BasisFunction.cpp:27
virtual ~BasisFunction()=default
Destroy a basis function through the abstract interface.
void numerical_hessian(const math::Vector< double, 3 > &xi, std::vector< Hessian > &hessians, double eps=double(1e-5)) const
Approximate Hessians by centered finite differences of gradients.
Definition BasisFunction.cpp:118
virtual const std::vector< math::Vector< double, 3 > > & nodes() const noexcept
Return the reference interpolation nodes in basis ordering.
Definition BasisFunction.cpp:17
virtual BasisTopology topology() const noexcept=0
Return the reference topology of this basis.
void evaluate_all(const math::Vector< double, 3 > &xi, std::vector< double > &values, std::vector< Gradient > &gradients, std::vector< Hessian > &hessians) const
Evaluate values, gradients, and Hessians together.
Definition BasisFunction.cpp:45
virtual std::size_t size() const noexcept=0
Return the number of basis functions and reference nodes.
virtual BasisType basis_type() const noexcept=0
Return the concrete basis family.
virtual int order() const noexcept=0
Return the polynomial order represented by this basis.
virtual void evaluate_hessians_to(const math::Vector< double, 3 > &xi, std::span< Hessian > hessians_out) const
Evaluate basis Hessians into caller-provided storage.
Definition BasisFunction.cpp:68
virtual void evaluate_values_to(const math::Vector< double, 3 > &xi, std::span< double > values_out) const =0
Evaluate basis values into caller-provided storage.
void evaluate_hessians(const math::Vector< double, 3 > &xi, std::vector< Hessian > &hessians) const
Evaluate basis Hessians at a reference coordinate.
Definition BasisFunction.cpp:39
void numerical_gradient(const math::Vector< double, 3 > &xi, std::vector< Gradient > &gradients, double eps=double(1e-6)) const
Approximate gradients by centered finite differences of values.
Definition BasisFunction.cpp:93
virtual int dimension() const noexcept=0
Return the reference-space dimension of the basis.
virtual void evaluate_all_to(const math::Vector< double, 3 > &xi, std::span< double > values_out, std::span< Gradient > gradients_out, std::span< Hessian > hessians_out) const
Evaluate any non-empty subset of values, gradients, and Hessians into caller-provided storage in a si...
Definition BasisFunction.cpp:78
BasisTopology
Reference-cell topology of a basis (the shape, independent of order).
Definition BasisTraits.h:28
BasisType
Basis function families.
Definition Types.h:247