Source code for felupe.constitution.linear_elasticity._linear_elastic

# -*- 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

from ...math import cdya, dya, identity, trace, transpose
from .._base import ConstitutiveMaterial
from ._lame_converter import lame_converter


[docs] class LinearElastic(ConstitutiveMaterial): r"""Isotropic linear-elastic material formulation. Parameters ---------- E : float Young's modulus. nu : float Poisson ratio. Notes ----- The stress-strain relation of the linear-elastic material formulation is given in Eq. :eq:`linear-elastic` .. math:: :label: linear-elastic \begin{bmatrix} \sigma_{11} \\ \sigma_{22} \\ \sigma_{33} \\ \sigma_{12} \\ \sigma_{23} \\ \sigma_{31} \end{bmatrix} = \frac{E}{(1+\nu)(1-2\nu)}\begin{bmatrix} 1-\nu & \nu & \nu & 0 & 0 & 0\\ \nu & 1-\nu & \nu & 0 & 0 & 0\\ \nu & \nu & 1-\nu & 0 & 0 & 0\\ 0 & 0 & 0 & \frac{1-2\nu}{2} & 0 & 0 \\ 0 & 0 & 0 & 0 & \frac{1-2\nu}{2} & 0 \\ 0 & 0 & 0 & 0 & 0 & \frac{1-2\nu}{2} \end{bmatrix} \cdot \begin{bmatrix} \varepsilon_{11} \\ \varepsilon_{22} \\ \varepsilon_{33} \\ 2 \varepsilon_{12} \\ 2 \varepsilon_{23} \\ 2 \varepsilon_{31} \end{bmatrix} with the small-strain tensor from Eq. :eq:`linear-elastic-strain`. .. math:: :label: linear-elastic-strain \boldsymbol{\varepsilon} = \frac{1}{2} \left( \frac{\partial \boldsymbol{u}} {\partial \boldsymbol{X}} + \left( \frac{\partial \boldsymbol{u}} {\partial \boldsymbol{X}} \right)^T \right) .. warning:: This material formulation must not be used in analyses where large rotations, large displacements or large strains occur. In this case, consider using a :class:`~felupe.Hyperelastic` material formulation instead. :class:`~felupe.LinearElasticLargeStrain` is based on a compressible version of the Neo-Hookean material formulation and is safe to use for large rotations, large displacements and large strains. Examples -------- .. pyvista-plot:: :context: >>> import felupe as fem >>> >>> umat = fem.LinearElastic(E=1, nu=0.3) >>> ax = umat.plot() .. 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.LinearElasticLargeStrain : Linear-elastic material formulation suitable for large-strain analyses based on the compressible Neo-Hookean material formulation. felupe.Hyperelastic : A hyperelastic material definition with a given function for the strain energy density function per unit undeformed volume with automatic differentiation. """ def __init__(self, E, nu): self.E = E self.nu = nu self.kwargs = {"E": self.E, "nu": self.nu} # aliases for gradient and hessian self.stress = self.gradient self.elasticity = self.hessian # initial variables for calling # ``self.gradient(self.x)`` and ``self.hessian(self.x)`` self.x = [np.eye(3), np.zeros(0)]
[docs] def gradient(self, x): r"""Evaluate the stress tensor (as a function of the deformation gradient). Parameters ---------- x : list of ndarray List with Deformation gradient :math:`\boldsymbol{F}` (3x3) as first item. Returns ------- ndarray Stress tensor (3x3) """ E = self.E nu = self.nu F, statevars = x[0], x[-1] # convert the deformation gradient to strain H = F - identity(F) strain = (H + transpose(H)) / 2 # init stress stress = np.zeros_like(strain) # normal stress components for a, b, c in zip([0, 1, 2], [1, 2, 0], [2, 0, 1]): stress[a, a] = (1 - nu) * strain[a, a] + nu * (strain[b, b] + strain[c, c]) # shear stress components for a, b in zip([0, 0, 1], [1, 2, 2]): stress[a, b] = stress[b, a] = (1 - 2 * nu) / 2 * 2 * strain[a, b] return [E / (1 + nu) / (1 - 2 * nu) * stress, statevars]
[docs] def hessian(self, x=None, shape=(1, 1), dtype=None): r"""Evaluate the elasticity tensor. The Deformation gradient is only used for the shape of the trailing axes. Parameters ---------- x : list of ndarray, optional List with Deformation gradient :math:`\boldsymbol{F}` (3x3) as first item (default is None). shape : tuple of int, optional Tuple with shape of the trailing axes (default is (1, 1)). Returns ------- ndarray elasticity tensor (3x3x3x3) """ E = self.E nu = self.nu if x is not None: dtype = x[0].dtype elast = np.zeros((3, 3, 3, 3, *shape), dtype=dtype) # diagonal normal components for i in range(3): elast[i, i, i, i] = 1 - nu # off-diagonal normal components for j in range(3): if j != i: elast[i, i, j, j] = nu # diagonal shear components (full-symmetric) elast[ [0, 1, 0, 1, 0, 2, 0, 2, 1, 2, 1, 2], [1, 0, 1, 0, 2, 0, 2, 0, 2, 1, 2, 1], [0, 0, 1, 1, 0, 0, 2, 2, 1, 1, 2, 2], [1, 1, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1], ] = (1 - 2 * nu) / 2 return [E / (1 + nu) / (1 - 2 * nu) * elast]
[docs] class LinearElasticTensorNotation(ConstitutiveMaterial): r"""Isotropic linear-elastic material formulation. Parameters ---------- E : float Young's modulus. nu : float Poisson ratio. Notes ----- .. math:: \boldsymbol{\sigma} &= 2 \mu \ \boldsymbol{\varepsilon} + \gamma \ \text{tr}(\boldsymbol{\varepsilon}) \ \boldsymbol{I} \frac{\boldsymbol{\partial \sigma}}{\partial \boldsymbol{\varepsilon}} &= 2 \mu \ \boldsymbol{I} \odot \boldsymbol{I} + \gamma \ \boldsymbol{I} \otimes \boldsymbol{I} with the strain tensor .. math:: \boldsymbol{\varepsilon} = \frac{1}{2} \left( \frac{\partial \boldsymbol{u}} {\partial \boldsymbol{X}} + \left( \frac{\partial \boldsymbol{u}} {\partial \boldsymbol{X}} \right)^T \right) Examples -------- .. pyvista-plot:: :context: >>> import felupe as fem >>> >>> umat = fem.constitution.LinearElasticTensorNotation(E=1, nu=0.3) >>> ax = umat.plot() .. pyvista-plot:: :include-source: False :context: :force_static: >>> import pyvista as pv >>> >>> fig = ax.get_figure() >>> chart = pv.ChartMPL(fig) >>> chart.show() """ def __init__(self, E, nu, parallel=False): self.parallel = parallel self.E = E self.nu = nu self.kwargs = {"E": self.E, "nu": self.nu} # aliases for gradient and hessian self.stress = self.gradient self.elasticity = self.hessian # initial variables for calling # ``self.gradient(self.x)`` and ``self.hessian(self.x)`` self.x = [np.eye(3), np.zeros(0)]
[docs] def gradient(self, x): r"""Evaluate the stress tensor (as a function of the deformation gradient). Parameters ---------- x : list of ndarray List with Deformation gradient :math:`\boldsymbol{F}` (3x3) as first item. Returns ------- ndarray Stress tensor (3x3) """ E = self.E nu = self.nu F, statevars = x[0], x[-1] # convert to lame constants gamma, mu = lame_converter(E, nu) # convert the deformation gradient to strain H = F - identity(F) strain = (H + transpose(H)) / 2 return [2 * mu * strain + gamma * trace(strain) * identity(strain), statevars]
[docs] def hessian(self, x=None, shape=(1, 1), dtype=None): r"""Evaluate the elasticity tensor. The Deformation gradient is only used for the shape of the trailing axes. Parameters ---------- x : list of ndarray List with Deformation gradient :math:`\boldsymbol{F}` (3x3) as first item. (default is None) shape : (int, ...), optional Tuple with shape of the trailing axes (default is (1, 1)) dtype : data-type or None, optional Data-type of the returned array (default is None). Returns ------- ndarray elasticity tensor (3x3x3x3) """ E = self.E nu = self.nu if x is not None: dtype = x[0].dtype eye = identity(dim=3, shape=shape, dtype=dtype) # convert to lame constants gamma, mu = lame_converter(E, nu) elast = 2 * mu * cdya(eye, eye, parallel=self.parallel) + gamma * dya( eye, eye, parallel=self.parallel ) return [elast]
[docs] class LinearElasticPlaneStrain: """Plane-strain isotropic linear-elastic material formulation. Parameters ---------- E : float Young's modulus. nu : float Poisson ratio. Notes ----- .. warning:: This class must not be used with :class:`~felupe.FieldPlaneStrain` but with :class:`~felupe.Field` instead! """ def __init__(self, E, nu): self.E = E self.nu = nu self.kwargs = {"E": self.E, "nu": self.nu} self._umat = LinearElasticPlaneStress(*self._convert(self.E, self.nu)) # initial variables for calling # ``self.gradient(self.x)`` and ``self.hessian(self.x)`` self.x = [np.eye(2), np.zeros(0)] self.elasticity = self.hessian def _convert(self, E, nu): """Convert Lamé - constants to effective plane strain constants. Parameters ---------- E : float Young's modulus nu : float Poisson ratio Returns ------- float Effective Young's modulus for plane strain formulation float Effective Poisson ratio for plane strain formulation """ E_eff = E / (1 - nu**2) nu_eff = nu / (1 - nu) return E_eff, nu_eff
[docs] def gradient(self, x): r"""Evaluate the 2d-stress tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- ndarray In-plane components of stress tensor (2x2) """ return self._umat.gradient(x)
[docs] def hessian(self, x): r"""Evaluate the 2d-elasticity tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- ndarray In-plane components of elasticity tensor (2x2x2x2) """ return self._umat.hessian(x)
[docs] def strain(self, x): r"""Evaluate the strain tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- e : ndarray Strain tensor (3x3) """ F = x[0] e = np.zeros((3, 3, *F.shape[-2:])) for a in range(2): e[a, a] = F[a, a] - 1 e[0, 1] = e[1, 0] = F[0, 1] + F[1, 0] return [e]
[docs] def stress(self, x): r""" "Evaluate the 3d-stress tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- ndarray Stress tensor (3x3) """ F = x[0] s = np.pad(self.gradient(F)[0], ((0, 1), (0, 1), (0, 0), (0, 0))) s[2, 2] = self.nu * (s[0, 0] + s[1, 1]) return [s]
[docs] class LinearElasticPlaneStress: """Plane-stress isotropic linear-elastic material formulation. Parameters ---------- E : float Young's modulus. nu : float Poisson ratio. """ def __init__(self, E, nu): self.E = E self.nu = nu self.kwargs = {"E": self.E, "nu": self.nu} # initial variables for calling # ``self.gradient(self.x)`` and ``self.hessian(self.x)`` self.x = [np.eye(2), np.zeros(0)] self.elasticity = self.hessian
[docs] def gradient(self, x): r"""Evaluate the 2d-stress tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- ndarray In-plane components of stress tensor (2x2) """ F, statevars = x[0], x[-1] E = self.E nu = self.nu stress = np.zeros((2, 2, *F.shape[-2:])) stress[0, 0] = E / (1 - nu**2) * ((F[0, 0] - 1) + nu * (F[1, 1] - 1)) stress[1, 1] = E / (1 - nu**2) * ((F[1, 1] - 1) + nu * (F[0, 0] - 1)) stress[0, 1] = E / (1 - nu**2) * (1 - nu) / 2 * (F[0, 1] + F[1, 0]) stress[1, 0] = stress[0, 1] return [stress, statevars]
[docs] def hessian(self, x=None, shape=(1, 1)): r"""Evaluate the elasticity tensor from the deformation gradient. Parameters ---------- x : list of ndarray, optional List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item (default is None)- shape : tuple of int, optional Tuple with shape of the trailing axes (default is (1, 1)). Returns ------- ndarray In-plane components of elasticity tensor (2x2x2x2). """ E = self.E nu = self.nu elast = np.zeros((2, 2, 2, 2, *shape)) for a in range(2): elast[a, a, a, a] = E / (1 - nu**2) for b in range(2): if b != a: elast[a, a, b, b] = E / (1 - nu**2) * nu elast[0, 1, 0, 1] = E / (1 - nu**2) * (1 - nu) / 2 elast[1, 0, 1, 0] = elast[1, 0, 0, 1] = elast[0, 1, 1, 0] = elast[0, 1, 0, 1] return [elast]
[docs] def strain(self, x): r"""Evaluate the strain tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- e : ndarray Strain tensor (3x3) """ F = x[0] nu = self.nu e = np.zeros((3, 3, *F.shape[-2:])) for a in range(2): e[a, a] = F[a, a] - 1 e[0, 1] = e[1, 0] = F[0, 1] + F[1, 0] e[2, 2] = -nu / (1 - nu) * (F[0, 0] + F[1, 1]) return [e]
[docs] def stress(self, x): r""" "Evaluate the 3d-stress tensor from the deformation gradient. Parameters ---------- x : list of ndarray List with In-plane components (2x2) of the Deformation gradient :math:`\boldsymbol{F}` as first item. Returns ------- ndarray Stress tensor (3x3) """ F = x[0] return [np.pad(self.gradient(F)[0], ((0, 1), (0, 1), (0, 0), (0, 0)))]