Coder Social home page Coder Social logo

adtzlr / matadi Goto Github PK

View Code? Open in Web Editor NEW
19.0 4.0 2.0 653 KB

Material Definition with Automatic Differentiation

License: GNU General Public License v3.0

Python 100.00%
constitutive-model hyperelasticity constitution finite-element-analysis automatic-differentiation algorithmic-differentiation python scientific-computing fem viscoelasticity

matadi's Introduction

Material Definition with Automatic Differentiation.

PyPI version shields.io License: GPL v3 codecov DOI Codestyle black PyPI - Downloads

matADi is a 3.8+ Python package for the definition of a constitutive hyperelastic material formulation by a strain energy density function. Both gradient (stress) and hessian (elasticity tensor) are carried out by Automatic Differentiation (AD) using casADi [1] as a backend. A high-level interface for hyperelastic materials based on the deformation gradient exists. Several isotropic hyperelastic material formulations like the Neo-Hookean or the Ogden model are included in the model library. Gradient and hessian methods allow input data with trailing axes which is especially useful for Python-based finite element modules, e.g. scikit-fem or FElupe. Mixed-field formulations suitable for nearly-incompressible material behavior are supported as well as single-field formulations based on the deformation gradient. A numerical lab is included to run, plot and analyze homogeneous uniaxial, equi-biaxial, planar shear and simple shear load cases.

Installation

Install matADi from PyPI via pip.

pip install matadi

Usage

First, a symbolic variable on which our strain energy function will be based on has to be created.

Note: A variable of matADi is an instance of a symbolic variable of casADi (casadi.SX.sym). Most (but not all) of matadi.math functions are simple links to (symbolic) casADi-functions.

from matadi import Variable, Material
from matadi.math import det, transpose, trace

F = Variable("F", 3, 3)

Next, take your favorite paper on hyperelasticity or be creative and define your own strain energy density function as a function of some variables x (where x is always a list of variables).

def neohooke(x, C10=0.5, bulk=200.0):
    """Strain energy density function of a nearly-incompressible 
    Neo-Hookean isotropic hyperelastic material formulation."""

    F = x[0]
    
    J = det(F)
    C = transpose(F) @ F

    return C10 * (J ** (-2 / 3) * trace(C) - 3) + bulk * (J - 1) ** 2 / 2

With this simple Python function at hand, we create an instance of a Material, which allows extra args and kwargs to be passed to our strain energy function. This instance now enables the evaluation of both gradient (stress) and hessian (elasticity) via methods based on automatic differentiation - optionally also on input data containing trailing axes. If necessary, the strain energy density function itself will be evaluated on input data with optional trailing axes by the function method.

Mat = Material(
    x=[F],
    fun=neohooke,
    kwargs={"C10": 0.5, "bulk": 10.0},
)

# init some random deformation gradients
import numpy as np

defgrad = np.random.rand(3, 3, 5, 100) - 0.5

for a in range(3):
    defgrad[a, a] += 1.0

W = Mat.function([defgrad])[0]
P = Mat.gradient([defgrad])[0]
A = Mat.hessian([defgrad])[0]

In a similar way, gradient-vector-products and hessian-vector-products are accessible via gradient_vector_product and hessian_vector_product methods, respectively.

v = np.random.rand(3, 3, 5, 100) - 0.5
u = np.random.rand(3, 3, 5, 100) - 0.5

W   = Mat.function([defgrad])[0]
dW  = Mat.gradient_vector_product([defgrad], [v])[0]
DdW = Mat.hessian_vector_product([defgrad], [v], [u])[0]

Template classes for hyperelasticity

matADi provides several template classes suitable for hyperelastic materials. Some isotropic hyperelastic material formulations are located in matadi.models (see list below). These strain energy functions have to be passed as the fun argument into an instance of MaterialHyperelastic. Usage is exactly the same as described above. To convert a hyperelastic material based on the deformation gradient into a mixed three-field formulation suitable for nearly-incompressible behavior (displacements, pressure and volume ratio) an instance of a MaterialHyperelastic class has to be passed to ThreeFieldVariation. For plane strain or plane stress use MaterialHyperelasticPlaneStrain, MaterialHyperelasticPlaneStressIncompressible or MaterialHyperelasticPlaneStressLinearElastic instead of MaterialHyperelastic. For plane strain displacements, pressure and volume ratio mixed-field formulations use ThreeFieldVariationPlaneStrain. For a two-field formulation (displacements, pressure) use TwoFieldVariation on top of MaterialHyperelastic.

from matadi import MaterialHyperelastic, ThreeFieldVariation
from matadi.models import neo_hooke

# init some random data
pressure = np.random.rand(5, 100)
volratio = np.random.rand(5, 100) / 10 + 1

kwargs = {"C10": 0.5, "bulk": 20.0}

NH = MaterialHyperelastic(fun=neo_hooke, **kwargs)

W = NH.function([defgrad])[0]
P = NH.gradient([defgrad])[0]
A = NH.hessian([defgrad])[0]

NH_upJ = ThreeFieldVariation(NH)

W_upJ = NH_upJ.function([defgrad, pressure, volratio])
P_upJ = NH_upJ.gradient([defgrad, pressure, volratio])
A_upJ = NH_upJ.hessian([defgrad, pressure, volratio])

The output of NH_upJ.gradient([defgrad, pressure, volratio]) is a list with gradients of the functional as [dWdF, dWdp, dWdJ]. Hessian entries are provided as a list of upper triangle entries, e.g. NH_upJ.hessian([defgrad, pressure, volratio]) returns [d2WdFdF, d2WdFdp, d2WdFdJ, d2Wdpdp, d2WdpdJ, d2WdJdJ].

Available isotropic hyperelastic material models:

Available anisotropic hyperelastic material models:

Available micro-sphere hyperelastic frameworks (Miehe, Göktepe, Lulei) [2]:

Available micro-sphere hyperelastic material models (Miehe, Göktepe, Lulei) [2]:

Any user-defined isotropic hyperelastic strain energy density function may be passed as the fun argument of MaterialHyperelastic by using the following template:

def fun(F, **kwargs):
    # user code
    return W

In order to apply the above material model only on the isochoric part of the deformation gradient [3], use the decorator @isochoric_volumetric_split. If the keyword bulk is passed, an additional volumetric strain energy function is added to the base material formulation.

from matadi.models import isochoric_volumetric_split
from matadi.math import trace, transpose

@isochoric_volumetric_split
def nh(F, C10=0.5):
    # user code of strain energy function, e.g. neo-hooke
    return C10 * (trace(transpose(F) @ F) - 3)

NH = MaterialHyperelastic(nh, C10=0.5, bulk=200.0)

Lab

In matADi's Lab 🥼 numeric experiments on homogeneous loadcases on compressible or nearly-incompressible material formulations are performed. For incompressible materials use LabIncompressible instead. Let's take the non-affine micro-sphere material model suitable for rubber elasticity with parameters from [2, Fig. 19] and run uniaxial, biaxial and planar shear tests.

from matadi import Lab, MaterialHyperelastic
from matadi.models import miehe_goektepe_lulei

mat = MaterialHyperelastic(
    miehe_goektepe_lulei, 
    mu=0.1475, 
    N=3.273, 
    p=9.31, 
    U=9.94, 
    q=0.567, 
    bulk=5000.0,
)

lab = Lab(mat)
data = lab.run(
    ux=True, 
    bx=True, 
    ps=True, 
    shear=True, 
    stretch_min=1.0, 
    stretch_max=2.0, 
    shear_max=1.0,
)
fig, ax = lab.plot(data, stability=True)
fig2, ax2 = lab.plot_shear(data)

Lab experiments(Microsphere)

Lab experiments shear(Microsphere)

Unstable states of deformation can be indicated as dashed lines with the stability argument lab.plot(data, stability=True). This checks whether if a) the volume ratio is greater zero, b) the monotonic increasing slope of force per undeformed area vs. stretch and c) the sign of the resulting stretch from a small superposed force in one direction.

Hints and usage in FEM modules

For tensor-valued material definitions use MaterialTensor (e.g. any stress-strain relation). Also, please have a look at casADi's documentation. It is very powerful but unfortunately does not support all the Python stuff you would expect. For example Python's default if-else-statements can't be used in combination with symbolic conditions (use math.if_else(cond, if_true, if_false) instead). Contrary to casADi, the gradient of the eigenvalue function is stabilized by a perturbation of the diagonal components.

A Material with state variables

A generalized material model with optional state variables, optionally for the (u/p)-formulation, is created by an instance of MaterialTensor. If the argument triu is set to True the gradient method returns only the upper triangle entries of the gradient components. If some of the input variables are internal state variables the number of these variables have to be passed to the optional argument statevars. While the hyperelastic material classes are defined by a strain energy function, this one is defined by the first Piola-Kirchhoff stress tensor. Internally, state variables are equal to default variables but they are excluded from gradient calculations. State variables may also be used as placeholders for additional quantities, e.g. the initial deformation gradient at the beginning of an increment or the time increment. Hence, it is a very flexible class not restricted to hyperelasticity. For consistency, the methods gradient and hessian of a tensor-based material refer to the gradient and hessian of the strain energy function.

Included pseudo-elastic material models:

Included viscoelastic material models:

Included other material models:

import numpy as np

from matadi.models import (
    displacement_pressure_split, neo_hooke, volumetric, ogden_roxburgh
)
from matadi import MaterialTensor, Variable
from matadi.math import det, gradient

F = Variable("F", 3, 3)
z = Variable("z", 1, 1)

@displacement_pressure_split
def fun(x, C10=0.5, bulk=5000, r=3, m=1, beta=0):
    "Strain energy function: Neo-Hooke & Ogden-Roxburgh."
    
    # split `x` into the deformation gradient and the state variable
    F, Wmaxn = x[0], x[-1]
    
    # isochoric and volumetric parts of the hyperelastic 
    # strain energy function
    W = neo_hooke(F, C10)
    U = volumetric(det(F), bulk)
    
    # pseudo-elastic softening function
    eta, Wmax = ogden_roxburgh(W, Wmaxn, r, m, beta)
    
    # first Piola-Kirchhoff stress and updated state variable
    # for a pseudo-elastic material formulation
    return eta * gradient(W, F) + gradient(U, F), Wmax

# get pressure variable from augmented function
p = fun.p

# Material as a function of `F` and `p`
# with **one** additional state variable `z`
NH = MaterialTensor(x=[F, p, z], fun=fun, triu=True, statevars=1)

defgrad = np.random.rand(3, 3, 5, 100) - 0.5
pressure = np.random.rand(1, 5, 100)
statevars = np.random.rand(1, 1, 5, 100)

for a in range(3):
    defgrad[a, a] += 1.0

dWdF, dWdp, statevars_new = NH.gradient([defgrad, pressure, statevars])
d2WdFdF, d2WdFdp, d2Wdpdp = NH.hessian([defgrad, pressure, statevars])

The Neo-Hooke, the MORPH and the Finite-Strain-Viscoelastic [4] material model formulations are available as ready-to-go materials in matadi.models as:

Hint: The state variable concept is also implemented for the Material class.

Simple examples for using matadi with scikit-fem as well as with felupe are shown in the Discussion section.

References

[1] J. A. E. Andersson, J. Gillis, G. Horn, J. B. Rawlings, and M. Diehl, CasADi - A software framework for nonlinear optimization and optimal control, Math. Prog. Comp., vol. 11, no. 1, pp. 1–36, 2019, DOI:10.1007/s12532-018-0139-4

[2] C. Miehe, S. Göktepe and F. Lulei, A micro-macro approach to rubber-like materials. Part I: the non-affine micro-sphere model of rubber elasticity, Journal of the Mechanics and Physics of Solids, vol. 52, no. 11. Elsevier BV, pp. 2617–2660, Nov. 2004. DOI:10.1016/j.jmps.2004.03.011

[3] J. C. Simo, R. L. Taylor, and K. S. Pister, Variational and projection methods for the volume constraint in finite deformation elasto-plasticity, Computer Methods in Applied Mechanics and Engineering, vol. 51, no. 1–3, pp. 177–208, Sep. 1985, DOI:10.1016/0045-7825(85)90033-7

[4] A. V. Shutov, R. Landgraf, and J. Ihlemann, An explicit solution for implicit time stepping in multiplicative finite strain viscoelasticity, Computer Methods in Applied Mechanics and Engineering, vol. 265. Elsevier BV, pp. 213–225, Oct. 2013, DOI:10.1016/j.cma.2013.07.004

matadi's People

Contributors

adtzlr avatar sad-abd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

matadi's Issues

Change Ogden material

There are two versions of the Ogden material formulation, one with mu / alpha and one with 2 mu / alpha^2. Change to the second version due automatic consistency with linear elasticity (mu is always equal to the initial shear modulus)

Enhance MaterialTensor

  • allow a list as output of a user defined function
  • add triu argument for gradient evaluation (like hessian-method for mixed-field hyperelastic materials)
    allow state variables (same as default variables but for which no gradient is evaluated) --> as seperate issue/PR

With the above enhancements it will be possible to code a u/p - Formulation:

# W(u, p) = w(u) + p (J - 1) - p^2 / (2 K)
# dWdE = dwdE + p J inv(C) (= S)
# dWdp = (J - 1) - p / K (= constraint)

Lab: Indicate stability

Hyperelastic models may be unstable even if the force-stretch relation seems ok (see .

Stability

For each state of homogenous deformation (uniaxial, biaxial and planar loading)
a) the elasticity matrix has to be evaluated
b) converted to shape of (6,6) (Voigt-notation) in order to invert the elasticity matrix
c) and the sign of the resulting stretch in direction 1 as linear solution of a unit load in direction 1 is checked (1 = stable and 0 = unstable)

Additional checks:

  • volume ratio J = det(F)
  • slope of force per undeformed area vs. stretch

This is really important!

From a user side, the unstable regions should simply be plotted with dashed lines (vs. solid lines in stable regions).

Add isochoric-volumetric split decorator

Implemented material models should only contain the essential code. A multiplicative split of the deformation gradient should be applied via a decorator.

  • add decorator
  • simplify material models
  • check tests

Add isotropic linear-elastic function for `MaterialHyperelastic`

Although a bit of an overkill; implement a linear-elastic strain energy function which can be used in MaterialHyperelastic. It takes the symmetric part of the displacement gradient as strain variable, on which a quadratic potential is described.

Code:

from matadi import MaterialHyperelastic
from matadi.math import eye, sym, trace

def linear_elastic(F, mu, lmbda):
    strain = sym(F - eye(3))
    return mu * trace(strain @ strain) + lmbda / 2 * trace(strain) ** 2

mat = MaterialHyperelastic(fun=linear_elastic, mu=1.0, lmbda=2.0)

TODO:

  • add eye and sym to math

Implement gradient-vector- and hessian-vector-product

Enable evaluations of hyperelastic forms without explicit gradients and hessians evaluated by AD; only jacobian - vector products (jtimes) are generated.

Material definition with Automatic Differentiation:

import casadi as ca

class NeoHooke:
    
    def __init__(self, mu, bulk):
    
        F  = ca.SX.sym("F", 3, 3)
        dF = ca.SX.sym("dF", 3, 3)
        DF = ca.SX.sym("DF", 3, 3)
    
        C = ca.transpose(F) @ F
        J = ca.det(F)
        
        W = mu * (J**(-2/3) * ca.trace(C) - 3) + bulk * (J - 1)**2/2
        
        dW  = ca.jtimes( W, F, dF)
        DdW = ca.jtimes(dW, F, DF)
        
        self._gvp = ca.Function("g", [F, dF], [dW])
        self._hvp = ca.Function("h", [F, dF, DF], [DdW])
    
    def ravel(self, T):
        return T.reshape(T.shape[0], -1, order="F")
    
    def gradient_vector_product(self, F, dF):
        n = F.shape[-2] * F.shape[-1]
        gvp = self._gvp.map(n, "thread", 4)
        return np.array(
                gvp(self.ravel(F), self.ravel(dF))
            ).reshape(F.shape[-2:], order="F")
    
    def hessian_vector_product(self, F, dF, DF):
        n = F.shape[-2] * F.shape[-1]
        hvp = self._hvp.map(n, "thread", 4)
        return np.array(
                hvp(self.ravel(F), self.ravel(dF), self.ravel(DF))
            ).reshape(F.shape[-2:], order="F")
And here is the defintion of (bi-) linear forms with the above material class in scikit-fem:
umat = NeoHooke(mu=1.0, bulk=2.0)

def deformation_gradient(w):
    dudX = grad(w["displacement"])
    F = dudX + identity(dudX)
    return F

@LinearForm(nthreads=4)
def L(v, w):
    F = deformation_gradient(w)
    return umat.gradient_vector_product(F, grad(v))

@BilinearForm(nthreads=4)
def a(u, v, w):
    F = deformation_gradient(w)
    return umat.hessian_vector_product(F, grad(v), grad(u))

Originally posted by @adtzlr in kinnala/scikit-fem#839 (comment)

Add Micro-sphere model for rubber elasticity

Implement the affine, non-affine, stretch and area-stretch micro-sphere models for rubber elasticity [1].

Tasks

  • Add numeric integration scheme on the surface of a sphere [2]
  • Add affine stretch model
  • Add affine tube model
  • Add non-affine stretch model
  • Add non-affine tube model
  • Add one-dimensional micro-sphere model (langevin and gauss model)
  • Add links to README
  • Add tests

References

[1] C. Miehe, “A micro-macro approach to rubber-like materials. Part I: the non-affine micro-sphere model of rubber elasticity,” Journal of the Mechanics and Physics of Solids, vol. 52, no. 11. Elsevier BV, pp. 2617–2660, Nov. 2004. DOI:10.1016/j.jmps.2004.03.011

[2] P. Bažant and B. H. Oh, “Efficient Numerical Integration on the Surface of a Sphere,” ZAMM - Journal of Applied Mathematics and Mechanics / Zeitschrift für Angewandte Mathematik und Mechanik, vol. 66, no. 1. Wiley, pp. 37–49, 1986. DOI:10.1002/zamm.19860660108

Add `MaterialComposite`

Create a composite material out of a list of Material with same input arguments and results will be summed up.

Bug in Calculation of free energy of single chain

The driving force of the 1d micro-sphere model has to be integrated w.r.t. the stretch, in order to obtain the 1d strain energy function. The Padé approximation of the inverse Langevin function will be integrated w.r.t. the stretch. Here is the result of WolframAlpha

integrate (3*N - λ^2)/(N-λ^2) dλ

which gives:

λ + 2 sqrt(N) tanh^(-1)(λ/sqrt(N))

This is transformed into a Python function:

def langevin(stretch, mu, N):
    """Langevin model (Padé approximation) given by the free energy 
    of a single chain as a function of the stretch."""

    return mu * (stretch + 2 * sqrt(N) * atanh(stretch / sqrt(N)))

Originally posted by @adtzlr in #28 (comment)

Performance considerations

If the hyperelastic materials would be based on C = F.T @ F and C would be stored as a vector in Voigt-notation, this gives a speed-up of 2 in stress evaluation and of 6 in elasticity evaluation! However, the code gets much more complicated.

Add `MaterialTensor` for non scalar valued material definitions

Introduce a tensor valued material definition by a stress-like function in terms of a tensor valued kinematic variable. This is more or less a copy and paste thing; replace gradient -> jacobian and remove hessian because these only operate on scalar valued functions.

Fix hyperelastic materials to work with FElupe>5.3.1

FElupe>5.3.1 calls materials with

# allow (unused) state variables to be passed as the c-
W = umat.function([deformation_gradient, statevars])
P, statevars_new = umat.gradient([deformation_gradient, statevars]) # also returns the updated state variables
A = umat.hessian([deformation_gradient, statevars])

This is just a small change for matADi with a wrapper for the function, gradient and hessian methods for hyperelastic materials.

class MaterialHyperelastic:
    def __init__(self, fun, **kwargs):
        F = Variable("F", 3, 3)
        self.x = [F]
        self.fun = fun
        self.kwargs = kwargs
        self.W = Material(self.x, self._fun_wrapper, kwargs=self.kwargs)
        self.gradient_vector_product = self.W.gradient_vector_product
        self.hessian_vector_product = self.W.hessian_vector_product

    def _fun_wrapper(self, x, **kwargs):
        return self.fun(x[0], **kwargs)

    def function(self, x, *args, **kwargs):
        return self.W.function(x[:1], *args, **kwargs)

    def gradient(self, x, *args, **kwargs):
        return [*self.W.gradient(x[:1], *args, **kwargs), None]

    def hessian(self, x, *args, **kwargs):
        return self.W.hessian(x[:1], *args, **kwargs)

Lab: add incompressible loadcases

Lab

Current behavior

Currently the material is analyzed as is. That means the volume change is introduced through the material. This is also called the nearly-incompressible framework.

New Feature

Add a new Lab feature to run incompressible loadcases. Here the incompressibility is enforced by a constraint eqation (stretch_1 * stretch_2 * stretch_3 = 1). It doesn't matter if the material contains a volumetric part or not because is is deactivated by the constraint.

fix (u/p) formulation

The constraint equation needs to be divided by d2W/dJdJ in order to be consistent with the derivation of a potential.

Fix matadi.math.dot

Matadi's dot-product is taken over from casadi where the first argument is transposed. However in matadi the dot-product should be

A = dot(B, C) should be A(i,j) = B(i,k) C(k,j)

and not

A = dot(B, C) should be A(i,j) = B(k, i) C(k,j).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.