Source code for felupe.constitution.tensortrax._hyperelastic

# -*- coding: utf-8 -*-
"""
This file is part of FElupe.

FElupe is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

FElupe is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with FElupe.  If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as np
import tensortrax as tr

from ...math import cdya_ik, dot, transpose
from .._material import Material


[docs] class Hyperelastic(Material): r"""A hyperelastic material definition with a given function for the strain energy density function per unit undeformed volume with Automatic Differentiation provided by :mod:`tensortrax`. Parameters ---------- fun : callable A strain energy density function in terms of the right Cauchy-Green deformation tensor :math:`\boldsymbol{C}`. Function signature must be ``fun = lambda C, **kwargs: psi`` for functions without state variables and ``fun = lambda C, statevars, **kwargs: [psi, statevars_new]`` for functions with state variables. The right Cauchy-Green deformation tensor will be a :class:`tensortrax.Tensor` when the function is evaluated. It is important to use only differentiable math-functions from :mod:`tensortrax.math`. nstatevars : int, optional Number of state variables (default is 0). parallel : bool, optional A flag to invoke threaded strain energy density function evaluations (default is False). May introduce additional overhead for small-sized problems. **kwargs : dict, optional Optional keyword-arguments for the strain energy density function. Notes ----- The strain energy density function :math:`\psi` must be given in terms of the right Cauchy-Green deformation tensor :math:`\boldsymbol{C} = \boldsymbol{F}^T \boldsymbol{F}`. .. warning:: It is important to use only differentiable math-functions from :mod:`tensortrax.math`! Take this minimal code-block as template .. math:: \psi = \psi(\boldsymbol{C}) .. code-block:: import felupe as fem import felupe.constitution.tensortrax as mat import tensortrax.math as tm def neo_hooke(C, mu): "Strain energy function of the Neo-Hookean material formulation." return mu / 2 * (tm.linalg.det(C) ** (-1/3) * tm.trace(C) - 3) umat = mat.Hyperelastic(neo_hooke, mu=1) and this code-block for material formulations with state variables. .. math:: \psi = \psi(\boldsymbol{C}, \boldsymbol{\zeta}) .. code-block:: import felupe as fem import felupe.constitution.tensortrax as mat import tensortrax.math as tm def viscoelastic(C, Cin, mu, eta, dtime): "Finite strain viscoelastic material formulation." # unimodular part of the right Cauchy-Green deformation tensor Cu = tm.linalg.det(C) ** (-1 / 3) * C # update of state variables by evolution equation Ci = tm.special.from_triu_1d(Cin, like=C) + mu / eta * dtime * Cu Ci = tm.linalg.det(Ci) ** (-1 / 3) * Ci # first invariant of elastic part of right Cauchy-Green deformation tensor I1 = tm.trace(Cu @ tm.linalg.inv(Ci)) # strain energy function and state variable return mu / 2 * (I1 - 3), tm.special.triu_1d(Ci) umat = mat.Hyperelastic(viscoelastic, mu=1, eta=1, dtime=1, nstatevars=6) .. note:: See the `documentation of tensortrax <https://tensortrax.readthedocs.io>`_ for further details. Examples -------- View force-stretch curves on elementary incompressible deformations. .. pyvista-plot:: :context: >>> import felupe as fem >>> import felupe.constitution.tensortrax as mat >>> import tensortrax.math as tm >>> >>> def neo_hooke(C, mu): ... "Strain energy function of the Neo-Hookean material formulation." ... return mu / 2 * (tm.linalg.det(C) ** (-1/3) * tm.trace(C) - 3) >>> >>> umat = mat.Hyperelastic(neo_hooke, mu=1) >>> ax = umat.plot(incompressible=True) .. pyvista-plot:: :include-source: False :context: :force_static: >>> import pyvista as pv >>> >>> fig = ax.get_figure() >>> chart = pv.ChartMPL(fig) >>> chart.show() See Also -------- felupe.constitution.tensortrax.models.hyperelastic.saint_venant_kirchhoff : Strain energy function of the Saint Venant-Kirchhoff material formulation. felupe.constitution.tensortrax.models.hyperelastic.neo_hooke : Strain energy function of the Neo-Hookean material formulation. felupe.constitution.tensortrax.models.hyperelastic.mooney_rivlin : Strain energy function of the Mooney-Rivlin material formulation. felupe.constitution.tensortrax.models.hyperelastic.yeoh : "Strain energy function of the Yeoh material formulation. felupe.constitution.tensortrax.models.hyperelastic.third_order_deformation : Strain energy function of the Third-Order-Deformation material formulation. felupe.constitution.tensortrax.models.hyperelastic.ogden : Strain energy function of the Ogden material formulation. felupe.constitution.tensortrax.models.hyperelastic.arruda_boyce : Strain energy function of the Arruda-Boyce material formulation. felupe.constitution.tensortrax.models.hyperelastic.extended_tube : Strain energy function of the Extended-Tube material formulation. felupe.constitution.tensortrax.models.hyperelastic.van_der_waals : Strain energy function of the Van der Waals material formulation. felupe.constitution.tensortrax.models.hyperelastic.finite_strain_viscoelastic : Finite strain viscoelastic material formulation. """ def __init__(self, fun, nstatevars=0, parallel=False, **kwargs): if nstatevars > 0: # split the original function into two sub-functions self.fun = tr.take(fun, item=0) self.fun_statevars = tr.take(fun, item=1) else: self.fun = fun self.parallel = parallel keyword_args = kwargs if hasattr(fun, "kwargs"): keyword_args = {**fun.kwargs, **keyword_args} super().__init__( stress=self._stress, elasticity=self._elasticity, nstatevars=nstatevars, **keyword_args, ) def _stress(self, x, **kwargs): F = np.ascontiguousarray(x[0]) if self.nstatevars > 0: statevars = (x[1],) else: statevars = () C = dot(transpose(F), F) dWdC = tr.gradient(self.fun, wrt=0, ntrax=2, parallel=self.parallel, sym=True)( C, *statevars, **kwargs ) if self.nstatevars > 0: statevars_new = tr.function( self.fun_statevars, wrt=0, ntrax=2, parallel=self.parallel )(C, *statevars, **kwargs) else: statevars_new = None return [dot(F, 2 * dWdC), statevars_new] def _elasticity(self, x, **kwargs): F = np.ascontiguousarray(x[0]) if self.nstatevars > 0: statevars = (x[1],) else: statevars = () C = dot(transpose(F), F) d2WdCdC, dWdC, W = tr.hessian( self.fun, wrt=0, ntrax=2, full_output=True, parallel=self.parallel, sym=True )(C, *statevars, **kwargs) A = 4 * np.einsum( "iI...,kK...,IJKL...->iJkL...", F, F, np.ascontiguousarray(d2WdCdC) ) B = cdya_ik(np.eye(3), 2 * dWdC) return [np.sum(np.broadcast_arrays(A, B), axis=0)]