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