Finite elements¶
Several disciplines associate finite elements with different meanings. In Ikarus, finite elements have two different objectives. The first one is to provide an evaluation of the scalars, vectors, and matrices. These are associated with an algebraic representation of discrete energies, weak forms, or bilinear forms. These algebraic objects are usually constructed using some combination of local functions and parameters stemming from the underlying physical problem, e.g., load factor, Young's modulus, or viscosity.
The second task of finite elements is to evaluate derived results in the element parameter space, e.g., stresses or geometric quantities. This leads to the following interface for the finite elements:
Interface¶
Finite elements should have non-template and non-virtual (considering non-virtual interface idiom (NVI) for the latter) interface methods.
This is because these methods are usually used to assemble quantities.
These functions are calculateScalar
, calculateVector
and calculateMatrix
. These are public methods.
Such an interface is provided by the local functions are shown below:
ScalarType calculateScalar(const FErequirements& req);
void calculateVector(const FErequirements& req, VectorType& b);
void calculateMatrix(const FErequirements& req, MatrixType& A);
void calculateLocalSystem(const FErequirements& req, MatrixType& A, VectorType& b);
void calculateAt(const Resultrequirements& req, const Eigen::Vector<double, Traits::mydim>& local,
ResultTypeMap<ScalarType>& result);
void globalFlatIndices(std::vector<GlobalIndex>& indices);
Since we would also like to use autodiff
for our element, we have their protected implementations.
These methods are templates to allow double or autodiff
scalar types.
These methods are calculateScalarImpl
, calculateVectorImpl
and calculateMatrixImpl
.
calculateScalar
, calculateVector
and calculateMatrix
simply forward to these implementation functions.
Please refer to the FE requirements to learn more about the finite element requirements and result requirements.
The first four methods receive an object of type FErequirements
. This object is responsible for passing different types of information needed for the local evaluation of the local linear algebra objects.
The first method, evaluateScalar
, simply returns by value because it is cheaper to return a double
, for example when evaluating energy.
The other methods, evaluateVector
, evaluateMatrix
, and calculateLocalSystem
, receive one or two additional output arguments where the results are to be written.
This interface is needed to circumvent the dynamic memory allocation, that is required if these methods return by value.
The method calculateAt
is responsible for evaluating several results, and it receives the ResultRequirements
object.
These results are stored inside the output argument result
, which is of the type ResultTypeMap
.
Additionally, there is the argument local
, which contains the element coordinates where the results are to be evaluated.
A typical calculateAt
method is implemented as shown below:
typename ResultTypeMap<double>::ResultArray res;
if(req.isResultRequested( ResultType::gradientNormOfMagnetization)) {
res.resize(1,1);
res(0,0)=...;
result.insertOrAssignResult(ResultType::gradientNormOfMagnetization,res);
}
if(req.isResultRequested( ResultType::BField)) {
res.setZero(3,1);
res=...;
result.insertOrAssignResult(ResultType::BField,res);
}
if(req.isResultRequested( ResultType::cauchyStress)) {
res.setZero(3,3);
res = ...;
result.insertOrAssignResult(ResultType::cauchyStress,res);
}
ResultTypeMap<double>::ResultArray
ResultTypeMap<double>::ResultArray
is an object of type Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic,0,3,3>
.
Thus, the maximum size of result
is limited to a 3x3 matrix. This is used to circumvent dynamic memory allocations again.
The last method is the globalFlatIndices
. It is used to write a finite element's global indices to the output parameter indices
.
This information originates from a basis
object. See existing implementations for details.
Linear and Non-linear Elasticity¶
LinearElastic
and NonLinearElastic
classes are designed in a generic way.
This means that they could be directly used for any \(n\)-dimensional finite element in the geometrically linear and non-linear cases.
They inherit from the class PowerBasisFE
, which helps to arrange the nodal degrees of freedom in a FlatInterleaved
format.
Refer DUNE1 for more details. The constructor for both classes of elements has the following signature:
template <typename VolumeLoad = LoadDefault, typename NeumannBoundaryLoad = LoadDefault>
LinearElastic(const Basis& globalBasis, const typename LocalView::Element& element, double emod, double nu,
VolumeLoad p_volumeLoad = {}, const BoundaryPatch<GridView>* p_neumannBoundary = nullptr,
NeumannBoundaryLoad p_neumannBoundaryLoad = {}) {}
template <typename VolumeLoad = LoadDefault, typename NeumannBoundaryLoad = LoadDefault>
NonLinearElastic(const Basis& globalBasis, const typename LocalView::Element& element, const Material& p_mat,
VolumeLoad p_volumeLoad = {}, const BoundaryPatch<GridView>* p_neumannBoundary = nullptr,
NeumannBoundaryLoad p_neumannBoundaryLoad = {})
The first argument defines the basis function used to interpolate the solution field, while the second argument points to the gridElement
itself.
The next set of arguments are related to the material law to be used.
For the geometrically linear case, the Young's modulus and the Poisson's ratio are passed, and a planeStress
material model is assumed.
For the geometrically non-linear case, the material model is to be passed as an argument.
This could be for instance, the St. Venant-Kirchhoff material law or the Neo-Hookean material law.
volumeLoad
and neumannBoundaryLoad
are optional parameters that could be passed as per the use case.
It is necessary to note that a neumannBoundary
must be defined if a neumannBoundaryLoad
is to be applied.
Member functions are defined as per the interface mentioned above.
Two member functions are written such that the stiffness matrices and load vectors can also be obtained by automatic differentiation.
These functions are protected
. They are:
calculateScalarImpl(const FERequirementType& par, const Eigen::VectorX<ScalarType>& dx)
calculateVectorImpl(const FERequirementType& par, const Eigen::VectorX<ScalarType>& dx, Eigen::VectorX<ScalarType>& force)
getStrainFunction(const FERequirementType& par, const Eigen::VectorX<ScalarType>& dx)
is used to get the desired strain measure.
It can be used to toggle between the geometrically linear and non-linear cases.
LinearStrains
are used for the geometrically linear case, while GreenLagrangianStrains
are used for the non-linear case.
These strain measures are defined as expressions in dune-localfefunctions
. Refer to Expressions for more details.
The strain-displacement operators are obtained by evaluating the derivative of the strain measure with respect to the nodal degrees of freedom.
This is then used to evaluate the stiffness matrix.
For more details on derivatives w.r.t. coefficients, refer here.
Finally, the calculateAt()
function evaluates the linearStress
and PK2Stress
(the second Piola-Kirchhoff stress tensor) as per the ResultRequirementsType
.
An implementation for a push forward operation to evaluate the cauchyStress
is an open task.
Refer to open tasks for more details.
Enhanced Assumed Strain Elements¶
The Enhanced Assumed Strain (EAS) elements are a class of finite elements that helps to avoid the locking phenomenon. They are obtained by re-parametrizing the Hu-Washizu principle and enforcing an orthogonality condition. This results in an extension of the standard pure displacement formulation with an enhanced strain field (\(\tilde\epsilon\)). (\(\tilde\epsilon\)) is as an additional independent variable. The locking characteristics of the pure displacement formulations can be eliminated with an appropriate choice of ansatz space for \(\tilde\epsilon\). For further theoretical aspects, the readers are referred to 2 and 3. The EAS formulation is currently implemented for the linear-elastic case, but it could be extended to the non-linear regime. The currently implemented EAS elements are the following:
- Q1E4
- Q1E5
- Q1E7
- H1E9
- H1E21
The notation used here is described as follows: the first alphabet stands for a Quadrilateral (Q) or a Hexahedral (H) element. The second index denotes the order of the element. E stands for the EAS element, and the number following that denotes the number of EAS parameters used to enhance the strain field. The only difference amongst various EAS formulations arises from the matrix, \(\mathbf{M}\) which is used to approximate the enhanced strain field. An example for the calculation of the matrix \(\mathbf{M}\) for a Q1E4 element is shown below:
template <typename Geometry>
struct EASQ1E4 {
static constexpr int strainSize = 3;
static constexpr int enhancedStrainSize = 4;
EASQ1E4() = default;
explicit EASQ1E4(const Geometry& geometry)
: geometry{std::make_unique<Geometry>(geometry)}, T0InverseTransformed{calcTransformationMatrix2D(geometry)} {}
auto calcM(const Dune::FieldVector<double, 2>& quadPos) const {
Eigen::Matrix<double, strainSize, enhancedStrainSize> M;
M.setZero(strainSize, enhancedStrainSize);
const double xi = quadPos[0];
const double eta = quadPos[1];
M(0, 0) = 2 * xi - 1.0;
M(1, 1) = 2 * eta - 1.0;
M(2, 2) = 2 * xi - 1.0;
M(2, 3) = 2 * eta - 1.0;
const double detJ = geometry->integrationElement(quadPos);
M = T0InverseTransformed / detJ * M;
return M;
}
std::unique_ptr<Geometry> geometry;
Eigen::Matrix3d T0InverseTransformed;
};
In order to add a new EAS element, the following additions are to be made:
- Create a
struct
to calculate the matrix \(\mathbf{M}\) as shown above exemplarily for the Q1E4 element. - Add the new variant to the corresponding list of 2D and 3D variants as shown below:
- Finally, add the new EAS variant with an appropriate switch statement (as shown below) to automatically call the
desired functions
void setEASType(int numberOfEASParameters) { if constexpr (Traits::mydim == 2) { switch (numberOfEASParameters) { case 0: onlyDisplacementBase = true; break; case 4: easVariant = EASQ1E4(DisplacementBasedElement::getLocalView().element().geometry()); break; case 5: easVariant = EASQ1E5(DisplacementBasedElement::getLocalView().element().geometry()); break; case 7: easVariant = EASQ1E7(DisplacementBasedElement::getLocalView().element().geometry()); break; default: DUNE_THROW(Dune::NotImplemented, "The given EAS parameters are not available for the 2D case."); break; } } else if constexpr (Traits::mydim == 3) { switch (numberOfEASParameters) { case 0: onlyDisplacementBase = true; break; case 9: easVariant = EASH1E9(DisplacementBasedElement::getLocalView().element().geometry()); break; case 21: easVariant = EASH1E21(DisplacementBasedElement::getLocalView().element().geometry()); break; default: DUNE_THROW(Dune::NotImplemented, "The given EAS parameters are not available for the 3D case."); break; } } }
If the number of EAS parameters is set to zero, the pure displacement formulation is then utilised for analysis.
-
Oliver Sander. DUNE—The Distributed and Unified Numerics Environment. Volume 140. Springer Nature, 2020. doi:10.1007/978-3-030-59702-3. ↩
-
J. C. Simo and M. S. Rifai. A class of mixed assumed strain methods and the method of incompatible modes. Int. J. Numer. Meth. Engng., 29(8):1595–1638, June 1990. doi:10.1002/nme.1620290802. ↩
-
U. Andelfinger and E. Ramm. EAS-elements for two-dimensional, three-dimensional, plate and shell structures and their equivalence to HR-elements. International Journal for Numerical Methods in Engineering, 36(8):1311–1337, 1993. doi:10.1002/nme.1620360805. ↩