Hyperelastic model problems on plotting stresses

Hello,

Thanks a lot in advance for the kind and helpful reply. I am currently dealing with a ogden model, but I have 3 problems with it. I will show my code below after 2 questions.

  1. I do not know how to build up the fiber’s direction and the sheet’s direction in FEniCS. I used 2 Constant pairs but I am not sure if this is fine. If someone has built up this kind of model, please let me know how you built in those two coefficients.
  2. I am trying to print (at point (1, 0.5)) and plot the distribution of the stress tensor in order to draw a stress/strain curve later. But when I ran my code (I took reference from some other questions), the result was very weird. The result is 0 which it should not be. So I wish if someone could help me out.

I can get a good result from directly calculate the stress from the strain (if use elastic equations) but I have to calculate the stress from differentiating the potential energy function because it is hyperelastic model.

Below is my codes, thanks a lot for your warmly help in advance:

from __future__ import print_function
from dolfin import *
from fenics import *
import matplotlib.pyplot as plt
import math
import ufl

# Optimization options for the form compiler
parameters["form_compiler"]["cpp_optimize"] = True
ffc_options = {"optimize": True, \
                "eliminate_zeros": True, \
                "precompute_basis_const": True, \
                "precompute_ip_const": True}

# Create mesh and define function space
mesh = RectangleMesh(Point(0, 0), Point(1, 1), 32, 32)
V = VectorFunctionSpace(mesh, "CG", 1)

# Mark boundary subdomians
left =  CompiledSubDomain("near(x[0], side) && on_boundary", side = 0.0)
right = CompiledSubDomain("near(x[0], side) && on_boundary", side = 1.0)

# Define Dirichlet boundary (x = 0 or x = 1)
c = Expression(("0.0", "0.0"), degree = 2)
r = Expression(("0.15", "0.0"), degree = 2)

bcl = DirichletBC(V, c, left)
bcr = DirichletBC(V, r, right)
bcs = [bcl, bcr]

# Define functions
du = TrialFunction(V)            # Incremental displacement
v  = TestFunction(V)             # Test function
u  = Function(V)                 # Displacement from previous iteration
B  = Constant((0.0, 0.0))  # Body force per unit volume
T  = Constant((0.0, 0.0))  # Traction force on the boundary

# Kinematics
d = u.geometric_dimension()
I = Identity(d)             # Identity tensor
F = I + grad(u)  
           # Deformation gradient
C = F.T*F                  # Right Cauchy-Green tensor

# Invariants of deformation tensors
Ic = tr(C)
J = det(F)

# define fibre and sheet directions
f0 = Constant((0.01, 0))
s0 = Constant((0, 0.01))

# Elasticity parameters
# E, nu = 1, 0.47
# mu, lmbd = Constant(E/(2*(1 + nu))), E*nu/((1 + nu)*(1 - 2*nu))
# lmbda = Constant(2*mu*lmbd/(lmbd+2*mu))
# kappa = Constant(E/3/(1 - 2*nu))
 
# define constant
a = Constant(0.342)
b = Constant(0.001)
a_f = Constant(18.535)
b_f = Constant(15.972)
a_s = Constant(2.564)
b_s = Constant(10.446)
a_fs = Constant(0.417)
b_fs = Constant(11.602)
p1 = ln(J)/J
p2 = J - 1
p = p2


# a, b, a_f, b_f, a_s, b_s, a_fs, b_fs = 1, 1, 1, 1, 1, 1, 1, 1
kappa = Constant(1500)

# direction function
I_4f = dot(f0, C*f0)
I_4s = dot(s0, C*s0)
I_8fs = dot(f0, C*s0)

# Stored strain energy density ((in-)compressible ogden model)
psi = a/2/b*(exp((b)*(Ic - 2)) - 1) + a_f/2/b_f*exp(b_f*(I_4f - 1)**2 - 1) + a_s/2/b_s*exp(b_s*(I_4s - 1)**2 - 1) + a_fs/2/b_fs*(exp(b_fs*(I_8fs**2)) - 1) - kappa*p*(J - 1)

# Total potential energy
Pi = psi*dx - dot(B, u)*dx - dot(T, u)*ds

# Compute first variation of Pi (directional derivative about u in the direction of v)
F = derivative(Pi, u, v)

# Compute Jacobian of F
J = derivative(F, u, du)

# Solve variational problem
solve(F == 0, u, bcs, J=J, form_compiler_parameters=ffc_options)

# compute the stress
F = I + grad(u)
F = variable(F)
P = diff(psi, F)
S = inv(F)*P
sig = F*S*F.T*(1/det(F)*I)
print(sig(1.0, 0.5))
sig = project(sig,TensorFunctionSpace(mesh, 'DG', 0))
print(sig(1.0, 0.5))
c = plot(sig)
plt.colorbar(c)
plt.show()

Hi BilZah,

  1. This is correct. In any case, I believe you have cardiac mechanics in mind? You could take a look at the cbcbeat library delevoped at Simula, which in particular handles fiber generation. If not, for this 2D case what you defined is ok. Not thought for mechanics, but only for electrophysiology as far as I know.
  2. What you wrote is ok, It’s just that you have an unloaded problem (B=T=0), so try modifying that, otherwise your problem will remain still, as that is in fact the solution to your problem.

Best regards!
Nico

1 Like

Hello Nico,

Thank you so much for your reply! Yes I am trying to make cardiac mechanics. Thank you for your confirmation. But I will also need an Ogden to solve rubbers too. So far as I knew, if I want to build a rubber-like Ogden model, I should try to derive principal stretches from Cauchy strains. It is a bit tricky and I have not had any clues on it so far. Do you have any experience about it? Or any relative or possible resource that I can achieve it?
For the second question, thank you very much for your help. I know where is my problem now!

Best!
Bill

Glad it helped. Regarding the principal stretches, I believe you can compute them analytically in 2D, being the zeros of a quadratic equation. More in general, I do not hace experience on this regard, I’m sorry.

Best

1 Like

Hi,
Indeed computing an Ogden model is not straightforward due to the dependence on principal stretches. You can indeed compute them analytically in 2D but in 3D it is not possible.
We recently developed an interface between FEniCS and the MFront constitutive behaviour code generator thourgh the MFrontGenericInterfaceSupport project . Documentation and commented demos are available here: Overview of the mgis.fenics module
This enables to solve problems involving complex non-linear constitutive equations inside FEniCS including plasticity for example.
If you want to compute an Ogden model, you just need to adapt for instance the small strain plasticity example with the Ogden MFront behaviour described here.

1 Like

Hello bleyerj,

Yeah, I tried to compute ogden model directly. But it is very difficult indeed. Thank you so much for your resources and generous help!

Best,
Bill

It’s okay if you don’t have. I will also try to find any open source on my own too. I appreciate your help a lot!

Hi,

I’m not sure if this is still a problem but I think that - using Cardanos formula - you can make an Ogden material model working in Fenics. Below, I just copied some code that I implemented for exactly this purpose: it calculates eigenvalues and projection tensors for 2D- and 3D tensors and allows you to get the Ogden material model working.

###########################
# load required libraries #
###########################
import dolfin as df                                                             # dolfin module
import ufl                                                                      # ufl module
import numpy as np                                                              # numpy
import copy as cp                                                               # copy for deepcopies

####################################################
# Method for analytical calculation of eigenvalues #
####################################################
def eigenValues(T, tol=df.DOLFIN_EPS):
    '''
    Method to calculate the (real) eigenvalues of a second order tensor T using
    its principal invariants.
    '''
    dim=T.geometric_dimension()                                                 # get geometric dimension of tensor T to decide which subroutine to use for eigenvalue computations

    # choose projection tensor routine based on dimensionality of problem
    if dim == 2:                                                                # 2D-problem
        return _eigVal2D(T,tol)
    else:                                                                       # 3D-problem
        return _eigVal3D(T,tol)


########################################################################
# Method for the analytical calculation of eigenvalues for 2D-Problems #
########################################################################
def _eigVal2D(T, tol):
    '''
    Analytically calculate eigenvalues for a two-dimensional tensor with a
    characteristic polynomial equation of the form

                    lambda^2 - I1*lambda + I3 = 0   .

    Since the characteristic polynomial is a reduced quadratic equation, the
    eigenvalues can be determined using the p-q-formula.

    NOTE:
    The method implemented here, implicitly assumes that the polynomial has
    only real roots, since imaginary ones should not occur in this use case.

    In order to ensure eigenvalues with algebraic multiplicity of 1, the idea
    of numerical perturbations is adopted from "Computation of isotropic tensor
    functions" by C. Miehe (1993). Since direct comparisons with conditionals
    have proven to be very slow, not the eigenvalues but the coefficients
    occuring during the calculation of them are perturbated to get distinct
    values.
    '''

    # determine perturbation from tolerance
    pert = 2*tol

    # get required invariants
    I1 = df.tr(T)                                                               # trace of tensor
    I3 = df.det(T)                                                              # determinant of tensor

    # determine discriminant
    # -> If the discriminant is 0, eigenvalues with a algebraic multiplicity
    # -> of 2 could occur. Here, this is prevented by "artificially creating"
    # -> eigenvalues with an algebraic multiplicity of 1 by adding a numerical
    # -> perturbation to discriminants that are close to 0. This is especially
    # -> necessary since a zero discriminant will cause problems during the
    # -> automatic differentiation procedure
    D = I1**2-4*I3                                                              # preliminary value for discriminant
    D=ufl.conditional(ufl.lt(D,tol),abs(D)+pert,D)                              # add numerical perturbation to discriminant, if close to zero; ensure positiveness of D

    # calculate polynomial roots
    # -> Here, the polynomial solution formula is only used to calculate the
    # -> "bigger" root, since - if I1 and sqrt(D) are of similar magnitude - a
    # -> loss of significance could lead to erroneous results. For the second
    # -> root, Vietas formula is used.
    lambda1 = 0.5*(I1+ufl.sqrt(D))
    lambda2 = I3/lambda1

    # return polynomial roots (eigenvalues)
    return lambda1, lambda2


########################################################################
# Method for the analytical calculation of eigenvalues for 3D-Problems #
########################################################################
def _eigVal3D(T, tol):
    '''
    Analytically calculate eigenvalues for a three-dimensional tensor T with a
    characteristic polynomial equation of the form

                lambda^3 - I1*lambda^2 + I2*lambda - I3 = 0   .

    Since the characteristic polynomial is in its normal form , the eigenvalues
    can be determined using Cardanos formula. This algorithm is based on:
    "Efficient numerical diagonalization of hermitian 3 × 3 matrices" by
    J. Kopp (equations: 21-34, with coefficients: c2=-I1, c1=I2, c0=-I3).

    NOTE:
    The method implemented here, implicitly assumes that the polynomial has
    only real roots, since imaginary ones should not occur in this use case.

    In order to ensure eigenvalues with algebraic multiplicity of 1, the idea
    of numerical perturbations is adopted from "Computation of isotropic tensor
    functions" by C. Miehe (1993). Since direct comparisons with conditionals
    have proven to be very slow, not the eigenvalues but the coefficients
    occuring during the calculation of them are perturbated to get distinct
    values.
    '''

    # determine perturbation from tolerance
    pert = 2*tol

    # get required invariants
    I1 = df.tr(T)                                                               # trace of tensor
    I2 = 0.5*(df.tr(T)**2-df.inner(T,T))                                        # 2nd invariant of tensor
    I3 = df.det(T)                                                              # determinant of tensor

    # determine terms p and q according to the paper
    # -> Follow the argumentation within the paper, to see why p must be
    # -> positive. Additionally ensure non-zero denominators to avoid problems
    # -> during the automatic differentiation
    p = I1**2 - 3*I2                                                            # preliminary value for p
    p = ufl.conditional(ufl.lt(p,tol),abs(p)+pert,p)                            # add numerical perturbation to p, if close to zero; ensure positiveness of p
    q = 27/2*I3 + I1**3 - 9/2*I1*I2                                             # preliminary value for q
    q = ufl.conditional(ufl.lt(abs(q),tol),q+ufl.sign(q)*pert,q)                # add numerical perturbation (with sign) to value of q, if close to zero

    # determine angle phi for calculation of roots
    phiNom2 =  27*( 1/4*I2**2*(p-I2) + I3*(27/4*I3-q) )                         # preliminary value for squared nominator of expression for angle phi
    phiNom2 = ufl.conditional(ufl.lt(phiNom2,tol),abs(phiNom2)+pert,phiNom2)    # add numerical perturbation to ensure non-zero nominator expression for angle phi
    phi = 1/3*ufl.atan_2(ufl.sqrt(phiNom2),q)                                   # calculate angle phi

    # calculate polynomial roots
    lambda1 = 1/3*(ufl.sqrt(p)*2*ufl.cos(phi)+I1)
    lambda2 = 1/3*(-ufl.sqrt(p)*(ufl.cos(phi)+ufl.sqrt(3)*ufl.sin(phi))+I1)
    lambda3 = 1/3*(-ufl.sqrt(p)*(ufl.cos(phi)-ufl.sqrt(3)*ufl.sin(phi))+I1)

    # return polynomial roots (eigenvalues)
    return lambda1, lambda2, lambda3


##############################################################
# Method for an analytical calculation of projection tensors #
##############################################################
def projectionTensors(T, *eigenValues):
    '''
    Method to calculate the projection tensors of a second order tensor T using
    its already known eigenvalues.
    '''
    dim=T.geometric_dimension()                                                 # get geometric dimension of tensor T to decide which subroutine to use for projection tensor computations

    # choose projection tensor routine based on dimensionality of problem
    if dim == 2:                                                                # 2D-problem
        return _projTen2D(T,*eigenValues)
    else:                                                                       # 3D-problem
        return _projTen3D(T,*eigenValues)


##############################################################################
# Method for an analytical calculation of projection tensors for 2D-problems #
##############################################################################
def _projTen2D(T, lambda1, lambda2):
    '''
    Analytically calculate projection tensors for a two-dimensional tensor T
    with two eigenvalues of algebraic multiplicity 1 (distinct eigenvalues).

    Mathematical manipulations show that the projection tensors of a simplified
    two-dimensional problem can be computed independent of the third eigenvalue
    by just using the simplified version of the three-dimensional problem:

        M_a = T-lambda_b*I/(lambda_a-lambda_b)

    with a,b={1,2} and b!=a
    '''

    # get required quantities:
    I = ufl.Identity(2)

    # calculate individual projection tensors
    M1 = (T-lambda2*I)/(lambda1-lambda2)
    M2 = (T-lambda1*I)/(lambda2-lambda1)

    # return projection tensors
    return M1, M2


##############################################################################
# Method for an analytical calculation of projection tensors for 3D-problems #
##############################################################################
def _projTen3D(T, lambda1, lambda2, lambda3):
    '''
    Analytically calculate projection tensors for a three-dimensional tensor T
    with 3 eigenvalues of algebraic multiplicity 1 (distinct eigenvalues).

    Using some mathematical manipulations on the initial eigenvalue problem and
    the properties of projection tensors, they can be expressed in terms of the
    tensor T and its eigenvalues as "Frobenius-covariants"

        M_a = prod_b (T-lambda_b*I)/(lambda_a-lambda_b)

    for a,b={1,2,3} and b!=a.
    '''
    # get required quantities
    I = ufl.Identity(3)

    # calculate the individual projection tensors
    M1 = (T-lambda2*I)*(T-lambda3*I)/((lambda1-lambda2)*(lambda1-lambda3))
    M2 = (T-lambda3*I)*(T-lambda1*I)/((lambda2-lambda3)*(lambda2-lambda1))
    M3 = (T-lambda1*I)*(T-lambda2*I)/((lambda3-lambda1)*(lambda3-lambda2))

    # return projection tensors
    return M1, M2, M3

Using the projection tensors for the evaluation of stresses does not converge as good as using the derivative of the energy - I think this is normal since there might be problems, when you have eigenvalues with an algebraic muliplicity of more than 1. In my case, a simple version (2D with projection tensors) of using the model would look like this:

# material parameters
nOgden = 2
nu = 0.49
mu = [-6.77e3, 1.88e3]                                                       # mu parameters for matrix
alpha = [-6.88, 2.0]                                                         # alpha parameters (exponents) for matrix
kappa = sum([mu[i]*alpha[i] for i in range(nOgden)])*(1+nu)/(3*(1-2*nu)) # bulk modulus for matrix

# Kinematics
dim = u.geometric_dimension()
I = Identity(dim)                                                               # identity tensor
F = I+grad(u)                                                                     # deformation gradient
detF = det(F)                                                                   # Jacobi determinant
C = F.T*F                                                                       # right Cauchy-Green tensor

# spectral decomposition for Ogden material
lam21, lam22 = eigenValues(C,tol=1e-10)                                         # eigenvalues of C
M1, M2 = projectionTensors(C, lam21, lam22)                                     # projection tensors for C
lam1_iso = detF**(-1./3.)*sqrt(lam21)                                           # first eigenvalue of F_iso
lam2_iso = detF**(-1./3.)*sqrt(lam22)                                           # second eigenvalue of F_iso
lam3_iso = detF**(-1./3.)                                                       # third eigenvalue of F_iso (2D case)


# stress
g = kappa/2*(detF*detF - 1)                                                  # reoccuring function for volumetric part of stress
T1_dev = 1/lam21*sum([mu[i]*(2/3*lam1_iso**alpha[i]-1/3*(lam2_iso**alpha[i]+lam3_iso**alpha[i])) for i in range(nOgden)])  # contribution of first eigenvalue to deviatoric part of 2nd Piola-Kirchhoff stress
T2_dev = 1/lam22*sum([mu[i]*(2/3*lam2_iso**alpha[i]-1/3*(lam1_iso**alpha[i]+lam3_iso**alpha[i])) for i in range(nOgden)])  # contribution of second eigenvalue to deviatoric part of 2nd Piola-Kirchhoff stress
T_vol = 1/lam21**2*g*M1 + 1/lam22*g*M2                                          # volumetric part of 2nd Piola-Kirchhoff stress
T_dev = T1_dev*M1 + T2_dev*M2                                                   # deviatoric part of 2nd-Piola-Kirchhoff stress
T = T_dev + T_vol                                                            # 2nd Piola-Kirchhoff stress in matrix
P = T*F.T                                                                 # 1st Piola-Kirchhoff stress in matrix

I also tried it in 3D and at least for simple cases, it worked quite good.
Hope this helps…

4 Likes