v0.16.3
Loading...
Searching...
No Matches
VEC-2: Nonlinear Elastic
Note
This tutorial presents the nonlinear elastic formulation of a cantilever beam under prescribed loads.
Prerequisites of this tutorial include VEC-0: Linear elasticity and SCL-4: Nonlinear Poisson's equation


Note
Intended learning outcome:
  • solving nonlinear vector-valued problem in MoFEM
  • extension of linear elasticity to large strains with Hencky material
  • developing code that can be compiled for 2D or 3D cases
  • using matrix functions
  • implementation of tangent stiffness matrix and verification with PETSc

The solution of the non linear elasticity problem is presented in this tutorial. Let's consider an isotropic elastic cantilever beam with prescribed gravity load as presented in Figure 1.

Figure 1: Cantilever beam considered in this example.

Strong Form

We consider the deformation of an elastic body undergoing finite strains. The balance of linear momentum in the reference configuration reads:

\[ \nabla \cdot \mathbf{P} + \mathbf{b} = 0 \quad \text{in } \mathbf{\Omega} \]

\[ \begin{cases} \mathbf{u} = \overline{\mathbf{u}} & \text{ on } \mathbf{\Gamma_u} \\ \mathbf{P} \cdot \mathbf{n} = \overline{\mathbf{t}} & \text{ on } \mathbf{\Gamma_t} \end{cases} \]

We consider the equilibrium problem in the domain \(\mathbf{\Omega}\), governed by the divergence operator acting on the First Piola–Kirchhoff stress tensor \(\mathbf{P}\) and the body forces \(\mathbf{b}\) per unit reference volume. The boundary \(\partial \mathbf{\Omega}\) is decomposed into \(\mathbf{\Gamma_u}\) and \(\mathbf{\Gamma_t}\), which are disjoint. On \(\mathbf{\Gamma_u}\), the displacement field \(\mathbf{u}\) is prescribed by \(\overline{\mathbf{u}}\), defining the Dirichlet (essential) boundary condition. On \(\mathbf{\Gamma_t}\), the traction vector \(\overline{\mathbf{t}}\) is applied through the action of the stress tensor \(\mathbf{P}\) on the outward unit normal vector \(\mathbf{n}\), defining the Neumann (natural) boundary condition. To account for large deformations, Hencky (logarithmic) strain measures are employed, allowing the constitutive response to follow the structure of Hooke’s law while incorporating geometric nonlinearities through the kinematics.

Constitutive model

To model large-strain elastic behavior, we begin with the deformation gradient

\[ \mathbf{F} = \frac{\partial \mathbf{x}}{\partial \mathbf{X}}, \]

which maps an infinitesimal material line element in the reference configuration to the current configuration. The deformation gradient contains both stretch and rigid-body rotation.

Using the polar decomposition theorem, the deformation gradient can be written as

\[ \mathbf{F} = \mathbf{R}\mathbf{U} = \mathbf{V}\mathbf{R}, \]

where

  • \(\mathbf{R}\) is a proper orthogonal rotation tensor,
  • \(\mathbf{U}\) is the right stretch tensor,
  • \(\mathbf{V}\) is the left stretch tensor.

Since rigid-body rotations do not contribute to strain, they are removed by forming the right Cauchy–Green deformation tensor

\[ \mathbf{C} = \mathbf{F}^{\mathrm T}\mathbf{F}. \]

Substituting the polar decomposition gives

\[ \mathbf{C} = (\mathbf{R}\mathbf{U})^{\mathrm T} (\mathbf{R}\mathbf{U}) = \mathbf{U}^{\mathrm T} \mathbf{R}^{\mathrm T} \mathbf{R} \mathbf{U} = \mathbf{U}^{2}, \]

because \(\mathbf{R}^{\mathrm T}\mathbf{R}=\mathbf{I}\) and \(\mathbf{U}\) is symmetric. Consequently, the tensor \(\mathbf{C}\) contains only stretch information, while all rigid-body rotations are eliminated through the multiplication \(\mathbf{F}^{\mathrm T}\mathbf{F}\).

The Hencky (logarithmic) strain tensor is then defined as

\[ \mathbf{H} = \frac{1}{2}\ln(\mathbf{C}) = \ln(\mathbf{U}). \]

The logarithmic strain is particularly attractive for finite-deformation elasticity because it measures strain directly from the principal stretches and reduces to the infinitesimal strain tensor in the small-deformation limit.

The strain energy density \(\Psi\) is defined as a function of \(\mathbf{H}\). The Hencky stress \(\mathbf{T}\) is work-conjugate to \(\mathbf{H}\):

\[ \dot{\Psi} = \mathbf{P} : \dot{\mathbf{F}} = \mathbf{T} : \dot{\mathbf{H}} \]

The Hencky strain energy density function for isotropic materials is defined as

\begin{equation} \Psi(\mathbf{H}) = \mu\, \mathbf{H}:\mathbf{H} + \frac{\lambda}{2}\, \left(\mathrm{tr}(\mathbf{H})\right)^2, \label{eq:hencky_energy} \end{equation}

where \(\mu\) and \(\lambda\) are the Lamé parameters. The work-conjugate stress tensor is obtained by differentiating the strain energy density with respect to the Hencky strain:

\begin{equation} \mathbf{T} = \frac{\partial \Psi}{\partial \mathbf{H}} = 2\mu\,\mathbf{H} + \lambda\,\mathrm{tr}(\mathbf{H})\,\mathbf{I} = \mathbb{D}:\mathbf{H}, \end{equation}

The second Piola–Kirchhoff stress tensor \(\mathbf{S}\) follows from the chain rule:

\[ \mathbf{S} = 2 \frac{\partial \Psi}{\partial \mathbf{C}} = 2 \, \mathbf{T} : \frac{\partial \mathbf{H}}{\partial \mathbf{C}} \]

Finally, the first Piola–Kirchhoff stress tensor is:

\begin{equation} \mathbf{P} = \mathbf{F} \mathbf{S} \label{eq:piola} \end{equation}

Weak Form

The weak form of the nonlinear elastic problem is obtained by multiplying the balance equation by an admissible test function \(\delta \mathbf{u}\) and integrating over the reference configuration:

\[ \begin{array}{c} \int_{\mathbf{\Omega}} \nabla \delta \mathbf{u} : \mathbf{P}(\mathbf{u}) \, d\Omega - \int_{\mathbf{\Omega}} \delta \mathbf{u} \cdot \mathbf{b} \, d\Omega - \int_{\mathbf{\Gamma_t}} \delta \mathbf{u} \cdot \overline{\mathbf{t}} \, d\Gamma - \int_{\mathbf{\Gamma_u}} \delta \mathbf{u} \cdot \left( \mathbf{P} \cdot \mathbf{n} \right) \, d\Gamma = 0 , \, \, \, \forall \, \delta \mathbf{u} \in \mathbf{H}^1(\mathbf{\Omega}) \end{array} \]

We seek \(\mathbf{u} \in \mathbf{H}^1(\mathbf{\Omega})\) such that \(\mathbf{u} = 0\) on \(\mathbf{\Gamma_u}\), and satisfying the weak form.

\[ \begin{array}{c} \int_{\mathbf{\Omega}} \nabla \delta \mathbf{u} : \mathbf{P}(\mathbf{u}) \, d\Omega = \int_{\mathbf{\Omega}} \delta \mathbf{u} \cdot \mathbf{b} \, d\Omega + \int_{\mathbf{\Gamma_t}} \delta \mathbf{u} \cdot \overline{\mathbf{t}} \, d\Gamma , \, \, \, \forall \, \delta \mathbf{u} \in \mathbf{H}^1_0(\mathbf{\Omega}) \text{ with } \delta \mathbf{u} = \mathbf{0} \text{ on } \mathbf{\Gamma_u} \end{array} \]

Linearization

The nonlinear weak form is solved using a Newton–Raphson scheme. Linearization about the current iterate \(\mathbf{u}\) yields:

\[ \mathcal{R}(\mathbf{u}_0 + \Delta \mathbf{u}; \delta \mathbf{u}) \approx \mathcal{R}(\mathbf{u}_0; \delta \mathbf{u}) + D\mathcal{R}(\mathbf{u}_0)[\Delta \mathbf{u}; \delta \mathbf{u}] + \mathcal{O}(\|\Delta \mathbf{u}\|^2) \]

\[ \mathcal{R}(\mathbf{u}_0; \delta \mathbf{u}) = \int_{\Omega_0} \mathbf{P}(\mathbf{u}) : \nabla \delta \mathbf{u} \, dV - \int_{\Omega_0} \delta \mathbf{u} \cdot \mathbf{b} \, dV - \int_{\Gamma_t} \delta \mathbf{u} \cdot \bar{\mathbf{t}} \, dA. \]

\[ D\mathcal{R}(\mathbf{u}_0)[\Delta \mathbf{u}; \delta \mathbf{u}] =\int_{\Omega_0} \delta \mathbf{P}: \nabla \delta \mathbf{u} d V = \int_{\Omega_0}(\nabla \delta \mathbf{u}): \frac{\partial \mathbf{P}}{\partial \mathbf{F}}:(\nabla \Delta \mathbf{u}) d V \]

The final equation after linearization reads and assembled in the weak form is as follows, in the code is well know as lef hand side and right hand side respectively.

\[ \int_{\Omega_0}(\nabla \delta \mathbf{u}): \frac{\partial \mathbf{P}}{\partial \mathbf{F}}\left(\mathbf{u}^k\right):(\nabla \Delta \mathbf{u}) d V=-\left[\int_{\Omega_0} \mathbf{P}\left(\mathbf{u}^k\right): \nabla \delta \mathbf{u} d V-\int_{\Omega_0} \delta \mathbf{u} \cdot \mathbf{b} d V-\int_{\Gamma_t} \delta \mathbf{u} \cdot \mathbf{t}_0 d A\right] \quad, \, \, \, \forall \delta \mathbf{u} \in \mathbf{H}^1_0(\Omega) \]

The total derivative of \(\mathbf{P}\) or what is called the tangent stifness matrix can be computed using the chain rule:

\begin{equation} \delta \mathbf{P} = \delta \mathbf{F} \cdot \mathbf{T} : \left(2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}\right) + \mathbf{F} \cdot \delta \mathbf{T} : \left(2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}\right) + \mathbf{F} \cdot \mathbf{T} : \left(2 \frac{\partial^2 \mathbf{H}}{\partial \mathbf{C}^2} : \delta \mathbf{C}\right) \label{eq:dP} \end{equation}

So we can see in above equation that we need to find \(\delta \mathbf{C}\) and \(\delta \mathbf{T}\) by using the chain rule once more being careful that we are dealing with tensorial quantities.

The variation of \(\mathbf{C}\) becomes

\[ \delta \mathbf{C} = \mathbf{F}^\mathrm{T} (\delta \mathbf{F}) + (\delta \mathbf{F})^\mathrm{T} \mathbf{F} \]

which defines the partial derivative of \(\mathbf{C}\) with respect to \(\mathbf{F}\) as

\[ \frac{\partial \mathbf{C}}{\partial \mathbf{F}}=\mathbf{I} \otimes \mathbf{F}+\mathbf{F} \otimes \mathbf{I} \]

The variation of the Hencky stress, \(\mathbf{T}\) is found by exploiting the relationship between the Hencky strain and the right Cauchy-Green tensor, is then

\[ \delta \mathbf{T}=\frac{\partial \mathbf{T}}{\partial \mathbf{H}}: \delta \mathbf{H}=\frac{\partial \mathbf{T}}{\partial \mathbf{H}}:\left(2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}: \delta \mathbf{C}\right) . \]

\[ \delta \mathbf{T}=\frac{\partial \mathbf{T}}{\partial \mathbf{H}}: \delta \mathbf{H}=\frac{\partial \mathbf{T}}{\partial \mathbf{H}}:\left(2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}: \delta \mathbf{C}\right) \]

Replacing in equation \eqref{eq:dP}

\[ \delta \mathbf{P}=\delta \mathbf{F} \cdot \mathbf{T}: 2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}+\mathbf{F} \cdot \mathbf{T}:\left(2 \frac{\partial^2 \mathbf{H}}{\partial \mathbf{C}^2}: \delta \mathbf{C}\right) + \mathbf{F} \cdot\left[\mathbb{D}: \frac{\partial \mathbf{H}}{\partial \mathbf{C}}: \delta \mathbf{C}\right]: 2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}} \]

\[ \delta \mathbf{P}=\delta \mathbf{F} \cdot \mathbf{T}: 2 \frac{\partial \mathbf{H}}{\partial \mathbf{C}}+\mathbf{F} \cdot\left[2 \frac{\partial^2 \mathbf{H}}{\partial \mathbf{C}^2}: \mathbf{T}+ \frac{1}{2}\frac{\partial \mathbf{H}}{\partial \mathbf{C}}: \mathbb{D}: \frac{\partial \mathbf{H}}{\partial \mathbf{C}}\right]: \delta \mathbf{C} \]

The resulting variation of \(\mathbf{P}\) can be expressed as comparing to the above equation:

\[ \delta \mathbf{P} = \frac{\partial \mathbf{P}}{\partial \mathbf{F}} : \delta \mathbf{F} \]

where the partial derivative is defined as :

\begin{equation} \frac{\partial \mathbf{P}}{\partial \mathbf{F}} = \mathbf{I} \otimes \mathbf{S} + \mathbf{F} : \left[\frac{1}{2}\left(4\frac{\partial^2 \mathbf{H}}{\partial \mathbf{C}^2} : \mathbf{T} + \frac{\partial \mathbf{H}}{\partial \mathbf{C}} : \mathbb{D} : \frac{\partial \mathbf{H}}{\partial \mathbf{C}}\right)\right] : \frac{\partial \mathbf{C}}{\partial \mathbf{F}} \label{eq:tangent} \end{equation}

Discretization

After discretization and linearization using the Newton–Raphson method, the nonlinear equilibrium equations are reduced at each iteration to a linear system of algebraic equations replacing with the respective shape function \(\mathbf{N}\). The right-hand side residual vector is expressed as \(\mathbf{R}\).

\[ \Delta \mathbf{u}^h = \sum_\beta \mathbf{N}_\beta \Delta \mathbf{d}_\beta, \quad \delta \mathbf{u}^h = \sum_\alpha \mathbf{N}_\alpha \delta \mathbf{d}_\alpha. \]

\[ \mathbf{K}_{\alpha \beta} = \int_{\Omega_0} (\nabla \mathbf{N}_\alpha) : \frac{\partial \mathbf{P}}{\partial \mathbf{F}} : (\nabla \mathbf{N}_\beta) \, dV \]

Implementation

This section outlines the main implementation aspects of the nonlinear elastic solver. The overall structure follows closely the workflow introduced in VEC-0: Linear elasticity, with extensions required to handle finite strains and nonlinear material response. The following steps are performed:

  • definition of the spatial dimension and mesh entities,
  • initialization of MoFEM, PETSc, and MOAB data structures,
  • registration of the MoFEM discrete manager with PETSc,
  • creation of the MOAB and MoFEM core objects,
  • setup of logging channels for monitoring solver behavior.

The function Example::setupProblem() is identical to that used in VEC-0: Linear elasticity. The displacement field \(\mathbf{u}\) is approximated in an \(H^1\)-conforming space, using the AINSWORTH_LEGENDRE_BASE with polynomial order 2 by default. Boundary conditions are imposed using MoFEM operators, as illustrated below:

auto *pipeline_mng = mField.getInterface<PipelineManager>();
auto simple = mField.getInterface<Simple>();
auto bc_mng = mField.getInterface<BcManager>();
auto time_scale = boost::make_shared<ExampleTimeScale>();
CHKERR BoundaryNaturalBC::AddFluxToPipeline<OpForce>::add(
pipeline_mng->getOpBoundaryRhsPipeline(), mField, "U", {time_scale},
"FORCE", "PRESSURE", Sev::inform);
//! [Define gravity vector]
CHKERR DomainNaturalBC::AddFluxToPipeline<OpBodyForce>::add(
pipeline_mng->getOpDomainRhsPipeline(), mField, "U", {time_scale},
"BODY_FORCE", Sev::inform);
// Essential BC
CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_X",
"U", 0, 0);
CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_Y",
"U", 1, 1);
CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_Z",
"U", 2, 2);
CHKERR bc_mng->pushMarkDOFsOnEntities<DisplacementCubitBcData>(
simple->getProblemName(), "U");
}
void simple(double P1[], double P2[], double P3[], double c[], const int N)
Definition acoustic.cpp:69
#define MoFEMFunctionBegin
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
MoFEMErrorCode getInterface(IFACE *&iface) const
Get interface reference to pointer of interface.
MoFEMErrorCode boundaryCondition()
[Set up problem]

Operators are pushed to the pipeline using the same integration rules as in VEC-0: Linear elasticity. This ensures consistency between linear and nonlinear formulations.

Hencky stress

The Hencky stress template is used to calculate the equation \( \eqref{eq:hencky_stress} \) . This loop applies Hooke’s law to the logarithmic strain at each Gauss point, producing the Hencky stress that drives the nonlinear solve. Material block operations follow the same structure as in VEC-0: Linear elasticity, since the material response remains linear isotropic in terms of the chosen strain measure.

template <int DIM, typename DomainEleOp, int S>
struct OpCalculateHenckyStressImpl<DIM, GAUSS, DomainEleOp, S>
: public DomainEleOp {
OpCalculateHenckyStressImpl(const std::string field_name,
boost::shared_ptr<CommonData> common_data)
commonDataPtr(common_data) {
std::fill(&DomainEleOp::doEntities[MBEDGE],
&DomainEleOp::doEntities[MBMAXTYPE], false);
}
MoFEMErrorCode doWork(int side, EntityType type, EntData &data) {
FTensor::Index<'i', DIM> i;
FTensor::Index<'j', DIM> j;
FTensor::Index<'k', DIM> k;
FTensor::Index<'l', DIM> l;
// const size_t nb_gauss_pts = matGradPtr->size2();
const size_t nb_gauss_pts = DomainEleOp::getGaussPts().size2();
auto t_D = getFTensor4DdgFromMat<DIM, DIM, S>(*commonDataPtr->matDPtr);
auto t_logC = getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matLogC);
constexpr auto size_symm = (DIM * (DIM + 1)) / 2;
commonDataPtr->matHenckyStress.resize(nb_gauss_pts, size_symm, false);
auto t_T = getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matHenckyStress);
for (size_t gg = 0; gg != nb_gauss_pts; ++gg) {
t_T(i, j) = t_D(i, j, k, l) * t_logC(k, l);
++t_logC;
++t_T;
++t_D;
}
}
private:
boost::shared_ptr<CommonData> commonDataPtr;
};
std::string type
FTensor::Index< 'i', SPACE_DIM > i
FTensor::Index< 'l', 3 > l
FTensor::Index< 'j', 3 > j
FTensor::Index< 'k', 3 > k
constexpr auto size_symm
Definition plastic.cpp:42
constexpr auto field_name
Data on single entity (This is passed as argument to DataOperator::doWork)

First Piola–Kirchhoff stress

The first Piola–Kirchhoff stress tensor from the equation \( \eqref{eq:piola} \) is obtained from the second Piola–Kirchhoff stress using the chain rule associated with the Hencky strain definition. This stress measure enters directly the weak form and residual evaluation

template <int DIM, typename DomainEleOp, int S>
struct OpCalculatePiolaStressImpl<DIM, GAUSS, DomainEleOp, S>
: public DomainEleOp {
OpCalculatePiolaStressImpl(const std::string field_name,
boost::shared_ptr<CommonData> common_data)
commonDataPtr(common_data) {
std::fill(&DomainEleOp::doEntities[MBEDGE],
&DomainEleOp::doEntities[MBMAXTYPE], false);
}
MoFEMErrorCode doWork(int side, EntityType type, EntData &data) {
FTensor::Index<'i', DIM> i;
FTensor::Index<'j', DIM> j;
FTensor::Index<'k', DIM> k;
FTensor::Index<'l', DIM> l;
[[maybe_unused]] constexpr auto t_kd = FTensor::Kronecker_Delta<int>();
// const size_t nb_gauss_pts = matGradPtr->size2();
const size_t nb_gauss_pts = DomainEleOp::getGaussPts().size2();
#ifdef HENCKY_SMALL_STRAIN
auto t_D = getFTensor4DdgFromMat<DIM, DIM, S>(*commonDataPtr->matDPtr);
#endif
auto t_logC = getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matLogC);
auto t_logC_dC = getFTensor4DdgFromMat<DIM, DIM>(commonDataPtr->matLogCdC);
constexpr auto size_symm = (DIM * (DIM + 1)) / 2;
commonDataPtr->matFirstPiolaStress.resize(nb_gauss_pts, DIM * DIM, false);
commonDataPtr->matSecondPiolaStress.resize(nb_gauss_pts, size_symm, false);
auto t_P = getFTensor2FromMat<DIM, DIM>(commonDataPtr->matFirstPiolaStress);
auto t_T = getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matHenckyStress);
auto t_S =
getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matSecondPiolaStress);
auto t_grad = getFTensor2FromMat<DIM, DIM>(*(commonDataPtr->matGradPtr));
for (size_t gg = 0; gg != nb_gauss_pts; ++gg) {
#ifdef HENCKY_SMALL_STRAIN
t_P(i, j) = t_D(i, j, k, l) * t_grad(k, l);
#else
t_F(i, j) = t_grad(i, j) + t_kd(i, j);
t_S(k, l) = t_T(i, j) * t_logC_dC(i, j, k, l);
t_P(i, l) = t_F(i, k) * t_S(k, l);
#endif
++t_grad;
++t_logC;
++t_logC_dC;
++t_P;
++t_T;
++t_S;
#ifdef HENCKY_SMALL_STRAIN
++t_D;
#endif
}
}
private:
boost::shared_ptr<CommonData> commonDataPtr;
};
Kronecker Delta class.
constexpr auto t_kd

Pushing left hand side

The domain tangent stiffness matrix is assembled by pushing the corresponding left-hand side operators to the pipeline. The operator factory opFactoryDomainLhs is used for this purpose:

template <int DIM, AssemblyType A, IntegrationType I, typename DomainEleOp>
MoFEMErrorCode opFactoryDomainLhs(
MoFEM::Interface &m_field,
boost::ptr_deque<ForcesAndSourcesCore::UserDataOperator> &pip,
std::string field_name, boost::shared_ptr<HenckyOps::CommonData> common_ptr,
Sev sev) {
using B = typename FormsIntegrators<DomainEleOp>::template Assembly<
A>::template BiLinearForm<I>;
using OpKPiola = typename B::template OpGradTensorGrad<1, DIM, DIM, -1>;
using H = HenckyIntegrators<DomainEleOp>;
// Assumes constant D matrix per entity
pip.push_back(new typename H::template OpHenckyTangent<DIM, I, 0>(
field_name, common_ptr));
pip.push_back(
new OpKPiola(field_name, field_name, common_ptr->getMatTangent()));
}
template <int DIM, AssemblyType A, IntegrationType I, typename DomainEleOp>
MoFEMErrorCode opFactoryDomainLhs(
MoFEM::Interface &m_field,
boost::ptr_deque<ForcesAndSourcesCore::UserDataOperator> &pip,
std::string field_name, std::string block_name, Sev sev, double scale = 1) {
auto common_ptr = commonDataFactory<DIM, I, DomainEleOp>(
m_field, pip, field_name, block_name, sev, scale);
CHKERR opFactoryDomainLhs<DIM, A, I, DomainEleOp>(m_field, pip, field_name,
common_ptr, sev);
}
constexpr AssemblyType A
double scale
Definition plastic.cpp:124
double H
Hardening.
Definition plastic.cpp:129
FormsIntegrators< DomainEleOp >::Assembly< PETSC >::BiLinearForm< GAUSS >::OpGradTensorGrad< 1, SPACE_DIM, SPACE_DIM, -1 > OpKPiola
[Only used for dynamics]
Definition seepage.cpp:63
Deprecated interface functions.

Material tangent implementation

Here we assemble the material tangent matrix at each Gauss point. The nonlinearity is present from the outset through the full finite-strain kinematics, since the formulation is expressed in terms of the deformation gradient. The Hencky tangent operator defined in equation \eqref{eq:tangent}, which consistently accounts for geometric nonlinear effects, is therefore implemented within this complete finite-strain framework.

template <int DIM, typename DomainEleOp, int S>
struct OpHenckyTangentImpl<DIM, GAUSS, DomainEleOp, S> : public DomainEleOp {
OpHenckyTangentImpl(const std::string field_name,
boost::shared_ptr<CommonData> common_data,
boost::shared_ptr<MatrixDouble> mat_D_ptr = nullptr)
commonDataPtr(common_data) {
std::fill(&DomainEleOp::doEntities[MBEDGE],
&DomainEleOp::doEntities[MBMAXTYPE], false);
if (mat_D_ptr)
matDPtr = mat_D_ptr;
else
matDPtr = commonDataPtr->matDPtr;
}
MoFEMErrorCode doWork(int side, EntityType type, EntData &data) {
FTensor::Index<'i', DIM> i;
FTensor::Index<'j', DIM> j;
FTensor::Index<'k', DIM> k;
FTensor::Index<'l', DIM> l;
FTensor::Index<'m', DIM> m;
FTensor::Index<'n', DIM> n;
FTensor::Index<'o', DIM> o;
FTensor::Index<'p', DIM> p;
[[maybe_unused]] constexpr auto t_kd = FTensor::Kronecker_Delta<int>();
// const size_t nb_gauss_pts = matGradPtr->size2();
const size_t nb_gauss_pts = DomainEleOp::getGaussPts().size2();
commonDataPtr->matTangent.resize(nb_gauss_pts, DIM * DIM * DIM * DIM);
auto dP_dF =
getFTensor4FromMat<DIM, DIM, DIM, DIM>(commonDataPtr->matTangent);
auto t_D = getFTensor4DdgFromMat<DIM, DIM, S>(*matDPtr);
auto t_eig_val = getFTensor1FromMat<DIM>(commonDataPtr->matEigVal);
auto t_eig_vec = getFTensor2FromMat<DIM, DIM>(commonDataPtr->matEigVec);
auto t_T = getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matHenckyStress);
auto t_S =
getFTensor2SymmetricFromMat<DIM>(commonDataPtr->matSecondPiolaStress);
auto t_grad = getFTensor2FromMat<DIM, DIM>(*(commonDataPtr->matGradPtr));
auto t_logC_dC = getFTensor4DdgFromMat<DIM, DIM>(commonDataPtr->matLogCdC);
for (size_t gg = 0; gg != nb_gauss_pts; ++gg) {
#ifdef HENCKY_SMALL_STRAIN
dP_dF(i, j, k, l) = t_D(i, j, k, l);
#else
t_F(i, j) = t_grad(i, j) + t_kd(i, j);
// rare case when two eigen values are equal
auto nb_uniq = getUniqNb<DIM>(getVectorAdaptor(&t_eig_val(0), DIM),
dC_dF(i, j, k, l) = (t_kd(i, l) * t_F(k, j)) + (t_kd(j, l) * t_F(k, i));
auto TL = EigenMatrix::getDiffDiffMat(t_eig_val, t_eig_vec, f, d_f, dd_f,
t_T, nb_uniq);
TL(i, j, k, l) *= 4;
P_D_P_plus_TL(i, j, k, l) =
TL(i, j, k, l) +
(t_logC_dC(i, j, o, p) * t_D(o, p, m, n)) * t_logC_dC(m, n, k, l);
P_D_P_plus_TL(i, j, k, l) *= 0.5;
dP_dF(i, j, m, n) = t_kd(i, m) * (t_kd(k, n) * t_S(k, j));
dP_dF(i, j, m, n) +=
t_F(i, k) * (P_D_P_plus_TL(k, j, o, p) * dC_dF(o, p, m, n));
#endif
++dP_dF;
++t_grad;
++t_eig_val;
++t_eig_vec;
++t_logC_dC;
++t_S;
++t_T;
++t_D;
}
}
private:
boost::shared_ptr<CommonData> commonDataPtr;
boost::shared_ptr<MatrixDouble> matDPtr;
};
const double n
refractive index of diffusive medium
auto getDiffDiffMat(A &&t_val, B &&t_vec, Fun< double > f, Fun< double > d_f, Fun< double > dd_f, C &&t_S, const int nb)
Get the Diff Diff Mat object.
FTensor::Index< 'm', 3 > m

Pushing right hand side

The internal force vector is assembled using the right-hand side operator factory opFactoryDomainRhs:

template <int DIM, AssemblyType A, IntegrationType I, typename DomainEleOp>
MoFEMErrorCode opFactoryDomainRhs(
MoFEM::Interface &m_field,
boost::ptr_deque<ForcesAndSourcesCore::UserDataOperator> &pip,
std::string field_name, boost::shared_ptr<HenckyOps::CommonData> common_ptr,
Sev sev) {
using B = typename FormsIntegrators<DomainEleOp>::template Assembly<
A>::template LinearForm<I>;
typename B::template OpGradTimesTensor<1, DIM, DIM>;
pip.push_back(
new OpInternalForcePiola("U", common_ptr->getMatFirstPiolaStress()));
}
template <int DIM, AssemblyType A, IntegrationType I, typename DomainEleOp>
MoFEMErrorCode opFactoryDomainRhs(
MoFEM::Interface &m_field,
boost::ptr_deque<ForcesAndSourcesCore::UserDataOperator> &pip,
std::string field_name, std::string block_name, Sev sev, double scale = 1) {
auto common_ptr = commonDataFactory<DIM, I, DomainEleOp>(
m_field, pip, field_name, block_name, sev, scale);
CHKERR opFactoryDomainRhs<DIM, A, I, DomainEleOp>(m_field, pip, field_name,
common_ptr, sev);
}
FormsIntegrators< DomainEleOp >::Assembly< A >::LinearForm< I >::OpGradTimesTensor< 1, FIELD_DIM, SPACE_DIM > OpGradTimesTensor
FormsIntegrators< DomainEleOp >::Assembly< PETSC >::LinearForm< GAUSS >::OpGradTimesTensor< 1, SPACE_DIM, SPACE_DIM > OpInternalForcePiola
Definition seepage.cpp:65

The operator computing the contraction of stresses with displacement gradients is implemented in finite_elements.hpp.

Solver

The nonlinear system is solved using PETSc’s time-stepping interface (TS), which internally employs a Newton–Raphson (SNES) solver at each time step. The nonlinear solver and all associated tolerances are configured through the call to TSSetFromOptions(ts), which parses the parameters provided in param_file.petsc. In addition, during the postprocessing stage, we define and register the set of variables to be visualized in ParaView, ensuring that the relevant fields are available for analysis and interpretation of the computed solution.

auto *simple = mField.getInterface<Simple>();
auto *pipeline_mng = mField.getInterface<PipelineManager>();
auto dm = simple->getDM();
auto ts = pipeline_mng->createTSIM();
auto add_extra_finite_elements_to_solver_pipelines = [&]() {
auto pre_proc_ptr = boost::make_shared<FEMethod>();
auto post_proc_rhs_ptr = boost::make_shared<FEMethod>();
auto post_proc_lhs_ptr = boost::make_shared<FEMethod>();
auto time_scale = boost::make_shared<ExampleTimeScale>();
auto get_bc_hook_rhs = [this, pre_proc_ptr, time_scale]() {
CHKERR EssentialPreProc<DisplacementCubitBcData>(mField, pre_proc_ptr,
{time_scale}, false)();
};
pre_proc_ptr->preProcessHook = get_bc_hook_rhs;
auto get_post_proc_hook_rhs = [this, post_proc_rhs_ptr]() {
CHKERR EssentialPreProcReaction<DisplacementCubitBcData>(
mField, post_proc_rhs_ptr, nullptr, Sev::verbose)();
CHKERR EssentialPostProcRhs<DisplacementCubitBcData>(
mField, post_proc_rhs_ptr, 1.)();
};
auto get_post_proc_hook_lhs = [this, post_proc_lhs_ptr]() {
CHKERR EssentialPostProcLhs<DisplacementCubitBcData>(
mField, post_proc_lhs_ptr, 1.)();
};
post_proc_rhs_ptr->postProcessHook = get_post_proc_hook_rhs;
post_proc_lhs_ptr->postProcessHook = get_post_proc_hook_lhs;
// This is low level pushing finite elements (pipelines) to solver
auto ts_ctx_ptr = getDMTsCtx(simple->getDM());
ts_ctx_ptr->getPreProcessIFunction().push_front(pre_proc_ptr);
ts_ctx_ptr->getPreProcessIJacobian().push_front(pre_proc_ptr);
ts_ctx_ptr->getPostProcessIFunction().push_back(post_proc_rhs_ptr);
ts_ctx_ptr->getPostProcessIJacobian().push_back(post_proc_lhs_ptr);
};
// Add extra finite elements to SNES solver pipelines to resolve essential
// boundary conditions
CHKERR add_extra_finite_elements_to_solver_pipelines();
auto create_monitor_fe = [dm](auto &&post_proc_fe) {
return boost::make_shared<Monitor>(dm.get(), post_proc_fe);
};
// Set monitor which postprocessing results and saves them to the hard drive
boost::shared_ptr<FEMethod> null_fe;
CHKERR postProcDomainFe->setTagsToTransfer(
std::vector<Tag>(listTagsToTransfer));
CHKERR postProcBdyFe->setTagsToTransfer(
std::vector<Tag>(listTagsToTransfer));
auto monitor_ptr =
create_monitor_fe(std::make_pair(postProcDomainFe, postProcBdyFe));
CHKERR DMMoFEMTSSetMonitor(dm, ts, simple->getDomainFEName(), null_fe,
null_fe, monitor_ptr);
// Set time solver
double ftime = 1;
CHKERR TSSetMaxTime(ts, ftime);
CHKERR TSSetExactFinalTime(ts, TS_EXACTFINALTIME_MATCHSTEP);
auto B = createDMMatrix(dm);
CHKERR TSSetI2Jacobian(ts, B, B, PETSC_NULLPTR, PETSC_NULLPTR);
auto D = createDMVector(simple->getDM());
CHKERR TSSetSolution(ts, D);
CHKERR TSSetFromOptions(ts);
PetscBool test_steps = PETSC_FALSE;
PetscBool test_piola_stress = PETSC_FALSE;
CHKERR PetscOptionsGetBool(nullptr, "", "-test_piola_stress",
&test_piola_stress, nullptr);
CHKERR PetscOptionsGetBool(nullptr, "", "-test_step_monitor", &test_steps,
nullptr);
const bool check_steps = test_steps || test_piola_stress;
auto check_piola_stress = [this](PetscReal time) {
auto simple = mField.getInterface<Simple>();
auto displacement_ptr = boost::make_shared<MatrixDouble>();
// These homogeneous cube fixtures prescribe Fzz = 1 - 0.1 * load time.
const double applied_F_zz = 1. - 0.1 * time;
t_P_expected(i, J) = 0.;
auto set_linear_elastic_piola = [&](const double strain_zz,
const double d_strain_d_F_zz) {
constexpr double E = 1.;
constexpr double nu = 0.3;
const double lambda = E * nu / ((1. + nu) * (1. - 2. * nu));
const double mu = E / (2. * (1. + nu));
for (int d = 0; d != SPACE_DIM - 1; ++d)
t_P_expected(d, d) = lambda * strain_zz;
t_P_expected(SPACE_DIM - 1, SPACE_DIM - 1) =
(lambda + 2. * mu) * strain_zz * d_strain_d_F_zz;
};
#ifndef AXISYMMETRIC
auto set_neohookean_piola = [&](const double F_zz, const double J) {
constexpr double c10 = 1.;
constexpr double K = 1.;
double I1 = (SPACE_DIM - 1) + F_zz * F_zz;
if constexpr (SPACE_DIM == 2)
I1 += 1.; // Plane strain has F33 = 1.
const double J_to_minus_two_thirds = std::pow(J, -2. / 3.);
for (int d = 0; d != SPACE_DIM; ++d) {
const double F_dd = d == SPACE_DIM - 1 ? F_zz : 1.;
const double inv_F_dd = 1. / F_dd;
t_P_expected(d, d) =
2. * c10 * J_to_minus_two_thirds * (F_dd - (I1 / 3.) * inv_F_dd) +
K * J * (J - 1.) * inv_F_dd;
}
};
#endif
auto eval_piola_at_point = [&](boost::shared_ptr<MatrixDouble> &mat_p,
std::array<double, 3> field_eval_coords) {
CHKERR mField.getInterface<TestingInterface>()
->addFieldEvaluator<SPACE_DIM>(
simple->getProblemName(), simple->getDomainFEName(),
field_eval_coords, [&](auto &pipeline) {
CHKERR AddHOOps<SPACE_DIM, SPACE_DIM, SPACE_DIM>::add(
pipeline, {H1}, "GEOMETRY");
pipeline.push_back(new OpCalculateVectorFieldValues<SPACE_DIM>(
"U", displacement_ptr));
if (useAdolcMaterial == PETSC_FALSE) {
auto common_ptr =
commonDataFactory<SPACE_DIM, GAUSS, DomainEleOp>(
mField, pipeline, "U", "MAT_ELASTIC", Sev::inform);
mat_p = common_ptr->getMatFirstPiolaStress();
} else {
#ifdef WITH_ADOL_C
modelType>::opPostProcFactory(mField, pipeline, "U",
mat_p = physicalEquationsPtr->matOpsDataPtr->getCommonDataPtr(
"P");
#else
SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
"ADOL-C support is not enabled. Please reconfigure with "
"-DWITH_ADOL_C=ON and recompile to use MatOps material model.");
#endif
}
});
};
boost::shared_ptr<MatrixDouble> mat_p;
using DL = DataLayoutTraits<DataLayout::GaussByCoeffs>;
// Infer the homogeneous fixture's response from the configured material
// dispatcher. The default MetaPiolaResponse tag represents Neo-Hookean.
std::string material_name = "Hencky";
#ifdef WITH_ADOL_C
int material_tag = physicalEquationsPtr->tAg;
bool found_range = false;
if (physicalEquationsPtr->tagVsRangePtr) {
for (const auto &[tag, range] : *physicalEquationsPtr->tagVsRangePtr) {
if (range.empty())
continue;
if (found_range && tag != material_tag)
"The analytical fixture requires a homogeneous material");
material_tag = tag;
found_range = true;
}
}
material_name = MatOps::MatOpsTagsRegistry::getTagName(material_tag);
}
#endif
#ifdef AXISYMMETRIC
if (material_name != "MetaPiolaResponse")
SETERRQ(
"The axisymmetric analytical fixture requires Neo-Hookean material");
#endif
if (material_name == "Hencky") {
// Homogeneous cube: E = 1, nu = 0.3.
set_linear_elastic_piola(std::log(applied_F_zz), 1. / applied_F_zz);
} else if (material_name == "MetaPiolaResponse") {
#ifdef AXISYMMETRIC
// Uniform expansion, c10 = K = 1, including hoop stretch. The
// deviatoric stress vanishes; P = K J (J - 1) F^{-T}.
const double stretch = 1. + 0.01 * time;
const double jacobian = stretch * stretch * stretch;
const double piola = jacobian * (jacobian - 1.) / stretch;
t_P_expected(i, J) = piola * delta(i, J);
CHKERR eval_piola_at_point(mat_p, {1.37, 0.41, 0.});
auto t_u = MatrixSizeHelper<GetFTensor1FromMatType<2, -1, DL>, DL>::get(
*displacement_ptr, 1)();
const double u_err = std::hypot(t_u(0) - (stretch - 1.) * 1.37,
t_u(1) - (stretch - 1.) * 0.41);
CHKERR mField.getInterface<TestingInterface>()
->checkError<TestType::POINTWISE>("axisymmetric displacement", u_err,
0., {1e-8, 0.});
#else
set_neohookean_piola(applied_F_zz, applied_F_zz);
#endif
} else if (material_name == "Umat") {
// Reference for this tutorial's linear-elastic UMAT fixture only.
char strain_type[32] = "small";
CHKERR PetscOptionsGetString(nullptr, "", "-umat_strain_type",
strain_type, sizeof(strain_type), nullptr);
if (std::string(strain_type) != "small")
"The analytical UMAT fixture requires small strain");
set_linear_elastic_piola(applied_F_zz - 1., 1.);
} else {
"No analytical fixture for material %s", material_name.c_str());
}
if (!mat_p)
CHKERR eval_piola_at_point(mat_p, {0.123, -0.087, 0.193});
auto t_P =
MatrixSizeHelper<GetFTensor2FromMatType<MAT_DIM, MAT_DIM, -1, DL>,
DL>::get(*mat_p, 1)();
t_P_diff(i, J) = t_P(i, J) - t_P_expected(i, J);
const double p_err = std::sqrt(t_P_diff(i, J) * t_P_diff(i, J));
for (int r = 0; r != MAT_DIM; ++r)
for (int c = 0; c != MAT_DIM; ++c)
MOFEM_LOG("EXAMPLE", Sev::inform)
<< "Piola stress (" << material_name << ") P(" << r << ", " << c
<< ") actual " << std::scientific << t_P(r, c) << " expected "
<< t_P_expected(r, c) << " diff " << t_P_diff(r, c);
MOFEM_LOG("EXAMPLE", Sev::inform) << "Piola stress (" << material_name
<< ") error " << std::scientific << p_err;
CHKERR mField.getInterface<TestingInterface>()
->checkError<TestType::POINTWISE>("Piola stress", p_err, 0.,
{1e-8, 0.});
};
if (check_steps) {
CHKERR mField.getInterface<TestingInterface>()->addTSMonitor(
ts, [this, check_piola_stress](TS, PetscInt step, PetscReal time,
Vec solution) {
auto simple = mField.getInterface<Simple>();
CHKERR VecGhostUpdateBegin(solution, INSERT_VALUES, SCATTER_FORWARD);
CHKERR VecGhostUpdateEnd(solution, INSERT_VALUES, SCATTER_FORWARD);
INSERT_VALUES, SCATTER_REVERSE);
CHKERR check_piola_stress(time);
MOFEM_LOG("EXAMPLE", Sev::inform)
<< "Numerical checks passed at TS step " << step << ", time "
<< time;
});
}
const auto solve_error = TSSolve(ts, nullptr);
CHKERR solve_error;
// Solver convergence is cheap to check and carries no magic numbers, so it
// stands alone right after the solve rather than being deferred to
// checkResults() through stored state.
{
PetscReal expected_time;
CHKERR TSGetMaxTime(ts, &expected_time);
auto testing = mField.getInterface<TestingInterface>();
CHKERR testing->checkSolver(ts, expected_time, {1e-12, 1e-12},
"nonlinear elasticity");
}
CHKERR TSGetTime(ts, &ftime);
PetscInt steps, snesfails, rejects, nonlinits, linits;
CHKERR TSGetStepNumber(ts, &steps);
CHKERR TSGetSNESFailures(ts, &snesfails);
CHKERR TSGetStepRejections(ts, &rejects);
CHKERR TSGetSNESIterations(ts, &nonlinits);
CHKERR TSGetKSPIterations(ts, &linits);
MOFEM_LOG_C("EXAMPLE", Sev::inform,
"steps %d (%d rejected, %d SNES fails), ftime %g, nonlinits "
"%d, linits %d",
steps, rejects, snesfails, ftime, nonlinits, linits);
}
#define MOFEM_LOG_C(channel, severity, format,...)
SmartPetscObj< Mat > jacobian
#define FTENSOR_INDEX(DIM, I)
constexpr int SPACE_DIM
#define MoFEMFunctionReturnHot(a)
Last executable line of each PETSc function used for error handling. Replaces return()
@ H1
continuous field
Definition definitions.h:85
@ MOFEM_ATOM_TEST_INVALID
Definition definitions.h:40
@ MOFEM_DATA_INCONSISTENCY
Definition definitions.h:31
#define MoFEMFunctionBeginHot
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
PetscErrorCode DMoFEMMeshToLocalVector(DM dm, Vec l, InsertMode mode, ScatterMode scatter_mode, RowColData rc=RowColData::COL)
set local (or ghosted) vector values on mesh for partition only
Definition DMMoFEM.cpp:543
auto createDMVector(DM dm, RowColData rc=RowColData::COL)
Get smart vector from DM.
Definition DMMoFEM.hpp:1250
auto createDMMatrix(DM dm)
Get smart matrix from DM.
Definition DMMoFEM.hpp:1207
#define MOFEM_LOG(channel, severity)
Log.
static double lambda
PetscBool testing
const double c
speed of light (cm/ns)
double D
FTensor::Index< 'J', DIM1 > J
Definition level_set.cpp:30
DataLayoutTraits< DataLayout::GaussByCoeffs > DL
Definition MatHuHu.hpp:33
PetscErrorCode DMMoFEMTSSetMonitor(DM dm, TS ts, const std::string fe_name, boost::shared_ptr< MoFEM::FEMethod > method, boost::shared_ptr< MoFEM::BasicMethod > pre_only, boost::shared_ptr< MoFEM::BasicMethod > post_only)
Set Monitor To TS solver.
Definition DMMoFEM.cpp:1075
auto getDMTsCtx(DM dm)
Get TS context data structure used by DM.
Definition DMMoFEM.hpp:1292
PetscErrorCode PetscOptionsGetBool(PetscOptions *, const char pre[], const char name[], PetscBool *bval, PetscBool *set)
decltype(GetFTensor1FromMatImpl< Tensor_Dim, S, DL, M >::get(std::declval< M & >(), 0, 0)) GetFTensor1FromMatType
PetscErrorCode PetscOptionsGetString(PetscOptions *, const char pre[], const char name[], char str[], size_t size, PetscBool *set)
decltype(GetFTensor2FromMatImpl< Tensor_Dim0, Tensor_Dim1, S, DL, M >::get(std::declval< M & >(), 0, 0)) GetFTensor2FromMatType
int r
Definition sdf.py:205
static constexpr double delta
static std::string getTagName(int tag)
Definition MatOps.cpp:90
virtual MPI_Comm & get_comm() const =0
static MoFEMErrorCode postStepInitialise(NonlinearElasticExample *example_ptr)
boost::shared_ptr< PostProcEleBdy > postProcBdyFe
std::vector< Tag > listTagsToTransfer
list of tags to transfer to postprocessor
boost::shared_ptr< MatOps::PhysicalEquations > physicalEquationsPtr
boost::shared_ptr< PostProcEleDomain > postProcDomainFe
MoFEMErrorCode TsSolve()
[TS Solve]

All flags introduced to param_file.petsc are introduced in the TSSetFromOptions(ts) function.

Getting norms

Solver statistics, including residual norms, number of nonlinear iterations, and linear solver iterations, are retrieved for post-analysis:

auto simple = mField.getInterface<Simple>();
auto dm = simple->getDM();
auto T = createDMVector(simple->getDM());
CHKERR DMoFEMMeshToLocalVector(simple->getDM(), T, INSERT_VALUES,
SCATTER_FORWARD);
double nrm2;
CHKERR VecNorm(T, NORM_2, &nrm2);
MOFEM_LOG("EXAMPLE", Sev::inform) << "Solution norm " << nrm2;
auto post_proc_norm_fe = boost::make_shared<DomainEle>(mField);
auto post_proc_norm_rule_hook = [](int, int, int p) -> int { return 2 * p; };
post_proc_norm_fe->getRuleHook = post_proc_norm_rule_hook;
CHKERR AddHOOps<SPACE_DIM, SPACE_DIM, SPACE_DIM>::add(
post_proc_norm_fe->getOpPtrVector(), {H1}, "GEOMETRY");
enum NORMS { U_NORM_L2 = 0, PIOLA_NORM, LAST_NORM };
auto norms_vec =
(mField.get_comm_rank() == 0) ? LAST_NORM : 0, LAST_NORM);
CHKERR VecZeroEntries(norms_vec);
auto u_ptr = boost::make_shared<MatrixDouble>();
post_proc_norm_fe->getOpPtrVector().push_back(
new OpCalculateVectorFieldValues<SPACE_DIM>("U", u_ptr));
post_proc_norm_fe->getOpPtrVector().push_back(
new OpCalcNormL2Tensor1<SPACE_DIM>(u_ptr, norms_vec, U_NORM_L2));
if (useAdolcMaterial == PETSC_TRUE) {
#ifdef WITH_ADOL_C
opPostProcFactory(mField, post_proc_norm_fe->getOpPtrVector(), "U",
auto m_P = physicalEquationsPtr->matOpsDataPtr->getCommonDataPtr("P");
post_proc_norm_fe->getOpPtrVector().push_back(
new OpCalcNormL2Tensor2<MAT_DIM, MAT_DIM>(m_P, norms_vec,
PIOLA_NORM));
}
#else
SETERRQ(PETSC_COMM_WORLD, MOFEM_DATA_INCONSISTENCY,
"ADOL-C support is not enabled. Please reconfigure with "
"-DWITH_ADOL_C=ON and recompile to use AdolC material model.");
#endif
} else {
auto common_ptr = commonDataFactory<SPACE_DIM, GAUSS, DomainEleOp>(
mField, post_proc_norm_fe->getOpPtrVector(), "U", "MAT_ELASTIC",
Sev::inform);
post_proc_norm_fe->getOpPtrVector().push_back(
new OpCalcNormL2Tensor2<SPACE_DIM, SPACE_DIM>(
common_ptr->getMatFirstPiolaStress(), norms_vec, PIOLA_NORM));
}
CHKERR DMoFEMLoopFiniteElements(dm, simple->getDomainFEName(),
post_proc_norm_fe);
CHKERR VecAssemblyBegin(norms_vec);
CHKERR VecAssemblyEnd(norms_vec);
MOFEM_LOG_CHANNEL("SELF"); // Clear channel from old tags
if (mField.get_comm_rank() == 0) {
const double *norms;
CHKERR VecGetArrayRead(norms_vec, &norms);
MOFEM_TAG_AND_LOG("SELF", Sev::inform, "example")
<< "norm_u: " << std::scientific << std::sqrt(norms[U_NORM_L2]);
MOFEM_TAG_AND_LOG("SELF", Sev::inform, "example")
<< "norm_piola: " << std::scientific << std::sqrt(norms[PIOLA_NORM]);
CHKERR VecRestoreArrayRead(norms_vec, &norms);
}
}
#define MOFEM_TAG_AND_LOG(channel, severity, tag)
Tag and log in channel.
PetscErrorCode DMoFEMLoopFiniteElements(DM dm, const char fe_name[], MoFEM::FEMethod *method, CacheTupleWeakPtr cache_ptr=CacheTupleSharedPtr())
Executes FEMethod for finite elements in DM.
Definition DMMoFEM.cpp:605
#define MOFEM_LOG_CHANNEL(channel)
Set and reset channel.
auto createVectorMPI(MPI_Comm comm, PetscInt n, PetscInt N)
Create MPI Vector.
virtual int get_comm_rank() const =0
MoFEMErrorCode gettingNorms()
[TS Solve]

Checking results

Basic verification checks are performed to ensure solver consistency and detect possible convergence issues:

auto testing = mField.getInterface<TestingInterface>();
// -test_solution_norm <value> both supplies the golden value and is the
// trigger for this check, so it is read right here, where it is used,
// rather than through addTestCommandLine's own "-test_nb"/"-test_name"
// selectors, and rather than a class member set earlier in runProblem().
PetscReal expected_solution_norm = 0;
PetscBool test_solution_norm = PETSC_FALSE;
CHKERR PetscOptionsGetReal(nullptr, "", "-test_solution_norm",
&expected_solution_norm, &test_solution_norm);
if (test_solution_norm) {
CHKERR testing->addTest(1, "solution coefficient norm", [&]() {
auto simple = mField.getInterface<Simple>();
auto T = createDMVector(simple->getDM());
CHKERR DMoFEMMeshToLocalVector(simple->getDM(), T, INSERT_VALUES,
SCATTER_FORWARD);
double nrm2;
CHKERR VecNorm(T, NORM_2, &nrm2);
MOFEM_LOG("EXAMPLE", Sev::verbose) << "Regression norm " << nrm2;
CHKERR testing->checkValue<TestType::SCALAR>("solution coefficient norm",
nrm2, expected_solution_norm,
{1e-2, 0.});
});
}
CHKERR testing->runTests();
}
MoFEMErrorCode checkResults()
[Postprocessing results]

Running code and visualisation

Before running the simulation, verify the parameters in param_file.petsc. Only solver options relevant to the chosen linear solver should be enabled. For example:

## Linear solver
-ksp_type fgmres
-pc_type lu
-pc_factor_mat_solver_type mumps
-ksp_monitor

To run the three-dimensional case:

./nonlinear_elastic -file_name beam_3D.cub -order 2
constexpr int order

For the two-dimensional case:

./nonlinear_elastic_2d -file_name beam_2D.cub -order 2

Figure 3: Deformation of cantilever beams in 2D. The color map represents the displacement magnitude.

With -snes_atol = 1e-8 and -snes_rtol = 1e-8, the convergence behavior shown in Figure 4 is obtained.

Source Files

The full source code associated with this tutorial is provided in: