1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5
6 #ifndef MINT_FINITEELEMENT_HPP_
7 #define MINT_FINITEELEMENT_HPP_
8
9 #include "axom/core/Macros.hpp" // for disable copy/assignment macros
10 #include "axom/core/Types.hpp" // for nullptr definition
11
12 #include "axom/core/numerics/Matrix.hpp" // for Matrix definition
13
14 #include "axom/mint/config.hpp" // for axom::IndexType
15 #include "axom/mint/fem/FEBasis.hpp" // for FEBasis traits class
16
17 namespace axom
18 {
19 namespace mint
20 {
21 /*!
22 * \enum
23 *
24 * \brief Enum of return codes from FiniteElement::computeReferenceCoords()
25 *
26 * \see FiniteElement::computeReferenceCoords()
27 */
28 enum
29 {
30 INVERSE_MAP_FAILED = -1, /*!< INVERSE_MAP_FAILED */
31 OUTSIDE_ELEMENT, /*!< OUTSIDE_ELEMENT */
32 INSIDE_ELEMENT /*!< INSIDE_ELEMENT */
33 };
34
35 /*!
36 * \brief The FiniteElement object is used to represent a mesh element
37 * \f$ \Omega^e \f$ corresponding to a mesh \f$ \mathcal{M} \f$ .
38 *
39 * The FiniteElement is associated with a Finite Element Basis (FEBasis),
40 * which defines a set of shape functions \f$ \mathcal{N}_i( \hat{\xi} ) \f$,
41 * on a corresponding element, \f$ \bar{\Omega}^e \f$ in reference
42 * space. This class provides the following key operations:
43 * <ul>
44 * <li> Evaluate the shape function and its derivatives </li>
45 * <li> Forward/Inverse mapping between physical/reference coordinates</li>
46 * <li> Compute the jacobian \f$ \mathcal{J}(\hat{\xi} ) \f$ </li>
47 * </ul>
48 *
49 * The FiniteElement class is the primary object that application codes will
50 * use to perform these operations. Once a FiniteElement object is
51 * instantiated it can then be bound to a Finite Element Basis by invoking
52 * the bind_basis( ) method on the target FiniteElement instance, which is
53 * templated on two enum values: (a) the BasisType, defined in
54 * (\ref FEBasisTypes.hpp) and (b) the CellType defined in(\ref CellType.hpp).
55 * The rationale behind this is to insulate the user from the low-level classes
56 * and provide a simple and unified interface to Finite Element operations.
57 *
58 * A simple example illustrating how to use the FiniteElement class is given
59 * below:
60 * \code
61 * using axom;
62 *
63 * double xp[2]; // test query point
64 * double xr[2]; // query point reference coordinates
65 * double wgts[4]; // buffer to store interpolation weights
66 *
67 * ...
68 *
69 * mint::Mesh* m = get_mesh();
70 * const int N = m->getMeshNumberOfCells();
71 *
72 * const bool zero_copy = true;
73 *
74 * for ( int idx=0; idx < N; ++idx ) {
75 *
76 * ...
77 * // gather cell coordinates in a buffer
78 * double coords[ ] = {
79 * x1[ idx ], y1[ idx ],
80 * x2[ idx ], y2[ idx ],
81 * x3[ idx ], y3[ idx ],
82 * x4[ idx ], y4[ idx ],
83 * };
84 *
85 * // put cell coordinates in matrix form
86 * numeric::Matrix< double > m( 2, 4, coords, zero_copy );
87 *
88 * // construct finite element and bind to basis
89 * mint::FiniteElement fe( m, mint::QUAD, zero_copy );
90 * mint::bind_basis< MINT_LAGRANGE_BASIS, mint::QUAD >( fe );
91 *
92 * // compute reference coordinates
93 * int status = fe.computeReferenceCoords( xp, xr );
94 *
95 * if ( status == mint::INSIDE_ELEMENT ) {
96 * fe.evaluateShapeFunction( xr, wgts );
97 * ....
98 * } // END if point is inside fe
99 *
100 * } // END for all cells
101 *
102 * ...
103 * \endcode
104 *
105 * \see FEBasis
106 * \see CellType.hpp
107 * \see FEBasisTypes.hpp
108 * \see ShapeFunction
109 */
110 class FiniteElement
111 {
112 public:
113 /*!
114 * \brief Default constructor. Disabled.
115 */
116 FiniteElement() = delete;
117
118 /*!
119 * \brief Constructs a FiniteElement instance from a matrix consisting of the
120 * element coordinates and a cell type.
121 *
122 * \param [in] M (ndims x nnodes) matrix consisting of the element coordinates
123 * \param [in] cellType the celltype, e.g., MINT_QUAD, etc.
124 * \param [in] useExternal optional argument that indicates whether the
125 * coordinate data will be shallow copied to the internal data-structures.
126 * Defaults to false if argument is not specified.
127 *
128 * \note The element coordinates are arranged in the supplied matrix, M, such
129 * that the number of rows corresponds to the dimension of the element and
130 * number of columns corresponds to the number of nodes.
131 */
132 FiniteElement(numerics::Matrix<double>& M,
133 CellType cellType,
134 bool useExternal = false);
135
136 /*!
137 * \brief Destructor.
138 */
139 ~FiniteElement();
140
141 /*!
142 * \brief Checks if this instance is pointing to an external, user-supplied,
143 * buffer for the element coordinate information.
144 *
145 * \return status true if an external buffer is used, otherwise, false.
146 */
usesExternalBuffer() const147 bool usesExternalBuffer() const { return m_usingExternal; };
148
149 /*!
150 * \brief Overrides the max number of iterations for the Newton-Raphson, used
151 * for the isoparametric inverse mapping from physical coordinates to
152 * reference coordinates.
153 *
154 * \note Each Basis/Element pair prescribes a default value for the maximum
155 * number of Newton-Raphson iterations. This method provides the flexibility
156 * of overriding this value.
157 *
158 * \warning If bind_basis() is called after invoking this method, the max
159 * newton iterations would be set to the nominal value. Typically, this
160 * method should be called after bind_basis() is invoked.
161 *
162 * \param [in] N user-supplied number for
163 */
setMaxSolverIterations(int numIters)164 void setMaxSolverIterations(int numIters)
165 {
166 m_maxNewtonIterations = numIters;
167 };
168
169 /*!
170 * \brief Returns the max number of iterations used for the Newton-Raphson.
171 * \return N max number of iterations for the Newton-Raphson
172 */
getMaxSolverIterations() const173 int getMaxSolverIterations() const { return m_maxNewtonIterations; };
174
175 /*!
176 * \brief Returns the cell type associated with this FiniteElement instance.
177 * \return ctype the corresponding cell type.
178 * \post (ctype != MINT_UNDEFINED_CELL) && (ctype < MINT_NUM_CELL_TYPES)
179 * \see CellType.hpp for a list of possible return values.
180 */
getCellType() const181 CellType getCellType() const { return m_ctype; };
182
183 /*!
184 * \brief Returns the Basis associated with this FiniteElement instance.
185 * \return basis the basis type, may be MINT_UNDEFINED_BASIS if no basis
186 * is bound to this FiniteElement.
187 *
188 * \see FEBasis
189 */
getBasisType() const190 int getBasisType() const { return m_shape_func_type; };
191
192 /*!
193 * \brief Returns the physical dimension for this FiniteElement instance.
194 * \return dim the dimension of this FiniteElement.
195 */
getPhysicalDimension() const196 int getPhysicalDimension() const { return m_dim; };
197
198 /*!
199 * \brief Returns the dimension of the element in reference space.
200 * \return ref_dim the reference dimension of the element.
201 * \post ref_dim \f$ \in [1,3] \f$
202 */
getReferenceDimension() const203 int getReferenceDimension() const { return m_reference_dim; };
204
205 /*!
206 * \brief Returns the number of nodes, i.e., degrees-of-freedom (dofs) of the
207 * FiniteElement.
208 * \return N the number of nodes of the element
209 */
getNumNodes() const210 int getNumNodes() const { return m_numnodes; };
211
212 /*!
213 * \brief Returns the number of degrees of freedom associated with the
214 * Finite Element basis bound to this FiniteElement instance.
215 * \return N the number of degrees of freedom, or -1 if no basis is set.
216 */
getNumDofs() const217 int getNumDofs() const { return m_numdofs; };
218
219 /*!
220 * \brief Returns a pointer to the nodes of the element in physical space,
221 * \f$ \forall\hat{x_i} \in \Omega^e \f$
222 *
223 * \return ptr pointer to the nodes of the elements in physical space.
224 *
225 * \note The nodes of the element are arranged in a flat array using a
226 * column-major layout. It is convenient to access the nodes of the element
227 * by wrapping the return pointer in a matrix object with getNumNodes()
228 * columns and getPhysicalDimension() rows. This can be achieved as follows:
229 * \code
230 * ...
231 * double* nodesptr = fe->getPhysicalNodes( );
232 * numerics::Matrix< double > pnodes( ndims, nnodes, nodesptr, true );
233 *
234 * for ( int inode=0; inode < nnodes; ++inode ) {
235 * double* node = pnodes.getColumn( inode );
236 *
237 * std::cout << "Node [" << inode << "]: ";
238 * for ( int idim=0; idim < ndims << ++idim ) {
239 * std::cout << nodes[ idim ] << " ";
240 * } // END
241 *
242 * std::cout << std::endl;
243 *
244 * } // END for all nodes
245 * ...
246 * \endcode
247 *
248 * \see numerics::Matrix
249 *
250 * \post ptr != nullptr
251 */
252 /// @{
253
getPhysicalNodes()254 double* getPhysicalNodes() { return m_xyz; };
getPhysicalNodes() const255 const double* getPhysicalNodes() const { return m_xyz; };
256
257 /// @}
258
259 /*!
260 * \brief Returns a pointer to the nodes of the element in reference space,
261 * \f$ \forall\hat{\xi_i} \in \bar{\Omega}^e \f$
262 *
263 * \return ptr pointer to the nodes of the element in reference space.
264 *
265 * \note The nodes of the element are arranged in a flat array using a
266 * column-major layout. It is convenient to access the nodes of the element
267 * by wrapping the return pointer in a matrix object as follows:
268 * \code
269 * ...
270 * double* nodesptr = fe->getReferenceNodes( );
271 * numerics::Matrix< double > pnodes( ndims, nnodes, nodesptr, true );
272 *
273 * for ( int inode=0; inode < nnodes; ++inode ) {
274 * double* node = pnodes.getColumn( inode );
275 *
276 * std::cout << "Node [" << inode << "]: ";
277 * for ( int idim=0; idim < ndims << ++idim ) {
278 * std::cout << nodes[ idim ] << " ";
279 * } // END
280 *
281 * std::cout << std::endl;
282 *
283 * } // END for all nodes
284 * ...
285 * \endcode
286 *
287 * \post ptr == nullptr iff getBasisType()==MINT_UNDEFINED_BASIS
288 */
289 /// @{
290
getReferenceNodes()291 double* getReferenceNodes() { return m_reference_coords; };
getReferenceNodes() const292 const double* getReferenceNodes() const { return m_reference_coords; };
293
294 /// @}
295
296 /*!
297 * \brief Returns a pointer to the centroid of the reference element
298 * \f$ \xi_c \in \bar{\Omega}^e \f$
299 *
300 * \return ptr pointer to the reference center.
301 *
302 * \post ptr == nullptr iff getBasisType()==MINT_UNDEFINED_BASIS
303 *
304 * \note ptr points to a buffer that is ndims long, where ndims is the
305 * dimension of the reference element.
306 */
307 /// @{
308
getReferenceCenter()309 double* getReferenceCenter() { return m_reference_center; };
getReferenceCenter() const310 const double* getReferenceCenter() const { return m_reference_center; };
311
312 /// @}
313
314 /*!
315 * \brief Given a point in physical space, \f$ \bar{x_p} \f$, this
316 * method computes the corresponding reference coordinates,
317 * \f$ \bar{xi} \f$ in the reference space of the element.
318 *
319 * \param [in] xp physical coordinates of the point in query.
320 * \param [out] xr computed reference coordinates \f$ \bar{\xi} \f$
321 * \param [in] TOL optional tolerance for Newton-Raphson. Default is 1.e-12.
322 *
323 * \return rc return code
324 * <ul>
325 * <li> mint::INVERSE_MAP_FAILED if the Newton-Raphson fails </li>
326 * <li> mint::OUTSIDE_ELEMENT if xp is outside the element </li>
327 * <li> mint::INSIDE_ELEMENT if xp is inside the element </li>
328 * </ul>
329 *
330 * \pre xp != nullptr
331 * \pre xr != nullptr
332 * \pre this->getBasisType() != MINT_UNDEFINED_BASIS
333 */
334 int computeReferenceCoords(const double* xp, double* xr, double TOL = 1.e-12);
335
336 /*!
337 * \brief Given reference coordinates, \f$ \xi \in \bar{\Omega}^e \f$,
338 * this method computes the corresponding physical coordinates, given by
339 * \f$ \mathbf{x}(\xi)=\sum\limits_{i=0}^n{N}_i^e({\xi}){x_i} \f$
340 *
341 * \param [in] xr buffer consisting of the reference coordinates
342 * \param [out] pt buffer to store the computed physical coordinates
343 *
344 * \pre xr != nullptr
345 * \pre xp != nullptr
346 * \pre this->getBasisType() != MINT_UNDEFINED_BASIS
347 */
348 void computePhysicalCoords(const double* xr, double* xp);
349
350 /*!
351 * \brief Given reference coordinates, \f$ \xi \in \bar{\Omega}^e \f$,
352 * this method computes the jacobian, \f$ \mathcal{J}(\xi) \f$
353 *
354 * \param [in] xr buffer consisting of the reference coordinates
355 * \param [out] J the jacobian matrix evaluated at the given location
356 *
357 * \pre xr != nullptr
358 * \pre this->getBasisType() != MINT_UNDEFINED_BASIS
359 */
360 void jacobian(const double* xr, numerics::Matrix<double>& J);
361
362 /*!
363 * \brief Given reference coordinates, \f$ \xi \in \bar{\Omega}^e \f$,
364 * this method evaluates the shape functions at each degree of freedom,
365 * \f$ \phi_i(\xi)=N_i^e(\xi) \f$
366 *
367 * \param [in] xr reference coordinates where the shape functions is evaluated
368 * \param [out] phi buffer to store the shape functions \f$ \phi_i \f$
369 *
370 * \note The user-supplied output buffer has to be at least ndofs long
371 *
372 * \pre xr != nullptr
373 * \pre phi != nullptr
374 */
375 void evaluateShapeFunctions(const double* xr, double* phi);
376
377 /*!
378 * \brief Given reference coordinates, \f$ \xi \in \bar{\Omega}^e \f$,
379 * this method evaluates the first derivatives of the shape function at
380 * each degree of freedom, \f$ \partial\phi_i(\xi)=N_i^e(\xi) \f$
381 *
382 * \param [in] xr reference coordinates where derivatives are computed
383 * \param [out] phidot buffer to store the shape function derivatives
384 *
385 * \note The output buffer
386 *
387 * \note The derivatives in the output buffer, phidot, are arranged using a
388 * column-major flat array layout. It is therefore convenient to think of
389 * the data as a Matrix, where each row represents a degree of freedom and
390 * each column a dimension.
391 *
392 * \note The matrix class may be used as a way to access the derivative
393 * information more conveniently as illustrated below:
394 * \code
395 * ...
396 * fe->evaluateDerivatives( xr, phidot);
397 * numerics::Matrix< double > shape_derivs( ndofs, ndims, phidot, true );
398 *
399 * for ( int idim=0; idim < ndims; ++idim ) {
400 *
401 * double* derivs = pnodes.getColumn( idim );
402 *
403 * std::cout << "dimension => " << idim << ":\n";
404 *
405 * for ( int idof=0; idof < ndofs << ++idof ) {
406 *
407 * std::cout << "\t dof[" << idof << "]=" << derivs[ idof ] << "\n";
408 *
409 * } // END for each dof
410 *
411 * } // END for each dimension
412 * ...
413 * \endcode
414 *
415 * \pre xr != nullptr
416 * \pre phidot != nullptr
417 */
418 void evaluateDerivatives(const double* xr, double* phidot);
419
420 /// \name Friend Functions
421 /// @{
422
423 /*!
424 * \brief Binds the given FiniteElement instance to a FiniteElement basis.
425 *
426 * \param [in] fe reference to a finite element object.
427 *
428 * \tparam BasisType the basis type, e.g., MINT_LAGRANGE_BASIS
429 * \tparam CellType the cell type, e.g., MINT_QUAD, etc.
430 *
431 * \post fe.getBasisType() != MINT_UNDEFINED_BASIS
432 */
433 template <int BasisType, CellType CellType>
434 friend void bind_basis(FiniteElement& fe);
435
436 /// @}
437
438 private:
439 /// \name Internal Helper methods
440 /// @{
441
442 /*!
443 * \brief Helper method used to allocate internal data-structures.
444 */
445 void setUp();
446
447 /*!
448 * \brief Helper method used to deallocate all dynamically allocated memory
449 * and destroy internal data-structures.
450 */
451 void tearDown();
452
453 /*!
454 * \brief Given reference coordinates, \f$ \xi \in \bar{\Omega}^e \f$,
455 * this method checks if the point is inside the reference element.
456 *
457 * \param [in] xr reference coordinates of the point in query.
458 * \param [in] TOL optional user-supplied tolerance. Default is 1.e-12.
459 *
460 * \note Necessary and sufficient condition to determine if
461 * \f$ \xi \in \bar{\Omega}^e \f$ are:
462 * <ul>
463 * <li>
464 * The shape functions at each degree of freedom should be within the
465 * frame of the reference element \f$[ \xi_0, \xi_1 ]\f$:
466 * \f$ \xi_0 \le N_i^e(\xi) \le \xi_1 \f$, \f$ \forall i \f$ </li>
467 * <li>
468 * The shape functions should sum to unity:
469 * \f$ \sum\limits_{i=0}^n{N}_i^e({\xi}){x_i} = 1 \f$
470 * </li>
471 * </ul>
472 *
473 * \return status true if inside the reference element, otherwise, false.
474 *
475 * \pre xr != nullptr
476 */
477 bool inReferenceElement(const double* xr, double TOL = 1.e-12);
478
479 /// @}
480
481 /// \name Private Data Members
482 /// @{
483
484 int m_dim; /*!< physical dimension */
485 CellType m_ctype; /*!< cell type, e.g., MINT_QUAD, etc. */
486 int m_shape_func_type; /*!< basis type, e.g., MINT_LAGRANGE */
487 int m_maxNewtonIterations; /*!< max newton iterations for inverse map */
488 int m_numnodes; /*!< number of nodes of the element */
489
490 double* m_jac; /*!< stores jacobian at some point */
491 double* m_xyz; /*!< stores element coordinates */
492 double* m_phi; /*!< stores shape functions at some point */
493 double* m_phidot; /*!< stores shape function derivatives */
494
495 bool m_usingExternal; /*!< indicates whether the element coordinates
496 are pointing to an external buffer. */
497 /// @}
498
499 /// \name Reference Element Attributes
500 /// @{
501
502 typedef void (*ShapeFunctionPtr)(const double* lc, double* phi);
503 ShapeFunctionPtr m_shapeFunction; /*! shape function functor */
504 ShapeFunctionPtr m_shapeFunctionDerivatives; /*! derivatives functor */
505
506 double m_reference_min; /*!< min of reference frame \f$ \xi_0 \f$ */
507 double m_reference_max; /*!< max of reference frame \f$ \xi_1 \f$ */
508 int m_reference_dim; /*!< dimension of the reference space */
509 int m_numdofs; /*!< number of degrees of freedom */
510
511 double* m_reference_coords; /*!< stores reference coords \f$ \xi_i \f$ */
512 double* m_reference_center; /*!< stores reference center \f$ \xi_c \f$ */
513
514 /// @}
515
516 DISABLE_COPY_AND_ASSIGNMENT(FiniteElement);
517 DISABLE_MOVE_AND_ASSIGNMENT(FiniteElement);
518 };
519
520 } /* namespace mint */
521 } // namespace axom
522
523 //------------------------------------------------------------------------------
524 // IMPLEMENTATION OF TEMPLATED FRIEND METHODS
525 //------------------------------------------------------------------------------
526 namespace axom
527 {
528 namespace mint
529 {
530 template <int BasisType, CellType CELLTYPE>
bind_basis(FiniteElement & fe)531 void bind_basis(FiniteElement& fe)
532 {
533 SLIC_ASSERT(CELLTYPE == fe.getCellType());
534 SLIC_ASSERT(fe.m_reference_center != nullptr);
535 SLIC_ASSERT(fe.m_reference_coords != nullptr);
536
537 typedef mint::FEBasis<BasisType, CELLTYPE> FEBasisType;
538 typedef typename FEBasisType::ShapeFunctionType ShapeType;
539
540 if(CELLTYPE != fe.getCellType())
541 {
542 SLIC_WARNING("Inconsistent celltype, cannot bind FEBasis!");
543 return;
544 }
545
546 fe.m_shape_func_type = FEBasisType::BasisType;
547 fe.m_maxNewtonIterations = ShapeType::maxNewtonIters();
548 fe.m_reference_dim = ShapeType::dimension();
549 fe.m_numdofs = ShapeType::numDofs();
550 fe.m_reference_min = ShapeType::min();
551 fe.m_reference_max = ShapeType::max();
552
553 ShapeType::coords(fe.m_reference_coords);
554 ShapeType::center(fe.m_reference_center);
555
556 fe.m_shapeFunction = &ShapeType::evaluate;
557 fe.m_shapeFunctionDerivatives = &ShapeType::derivatives;
558
559 SLIC_ASSERT(fe.m_shape_func_type != MINT_UNDEFINED_BASIS);
560 SLIC_ASSERT(fe.m_shapeFunction != nullptr);
561 SLIC_ASSERT(fe.m_shapeFunctionDerivatives != nullptr);
562 }
563
564 } /* namespace mint */
565 } /* namespace axom */
566
567 #endif /* MINT_FINITEELEMENT_HPP_ */
568