Spline model for strain energy

Hallo, I want to calculate the traction and strain curve for a beam with a nonlinear spline to model the strain energy. Basically I want to have a specific spline response in my final plot. I could not find how to do that, I attached the code I am working on. Could you please help me? Thanks.
I am doing a huge mistake probably but here is my code:

import gmsh
import sys
from dolfin import *
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline

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

un = "trial"
b = 1
lc = 1e-1 * 10
def createGeometryAndMesh(b):
    # Clear all models and create a new one
    gmsh.initialize()
    gmsh.clear()
    gmsh.model.add("t3")


    P1 = gmsh.model.occ.addPoint(-b , 0, 0, lc)
    P2 = gmsh.model.occ.addPoint(b, scale *0, 0, lc)
    P3 = gmsh.model.occ.addPoint(-b,  b , 0, lc)
    P4 = gmsh.model.occ.addPoint(b ,  b , 0, lc)

    L1 = gmsh.model.occ.addLine(P1, P2)
    L2 = gmsh.model.occ.addLine(P2, P4)
    L3 = gmsh.model.occ.addLine(P4, P3)
    L4 = gmsh.model.occ.addLine(P3, P1)
    
    Curve1 = gmsh.model.occ.addCurveLoop([L1, L2, L3, L4])
    Surface1 = gmsh.model.occ.addPlaneSurface([Curve1],  1)
       
    # Synchronize the CAD model
    gmsh.model.occ.synchronize()
    # Create physical groups
    pg1 = gmsh.model.addPhysicalGroup(2, [Surface1])
    gmsh.model.setPhysicalName(2, pg1, "beam")

    # Set mesh size at all points
    gmsh.model.mesh.setSize(gmsh.model.getEntities(0), lc)

    gmsh.model.occ.synchronize()
    gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
    gmsh.model.mesh.generate()

    
    def checkForEvent():
        action = gmsh.onelab.getString("ONELAB/Action")
        if len(action) and action[0] == "check":
            gmsh.onelab.setString("ONELAB/Action", [""])
            createGeometryAndMesh()
            gmsh.graphics.draw()
        return True

    
    # Refine the mesh
    gmsh.model.mesh.refine()
    
    gmsh.write("t3.opt");

    gmsh.finalize()

createGeometryAndMesh(b)


# Importing mesh from gmsh and defining surface and boundary markers
msh = meshio.read("./Result--" + str(un)+ "geo.msh")

for key in msh.cell_data_dict["gmsh:physical"].keys():
    if key == "triangle":
        triangle_data = msh.cell_data_dict["gmsh:physical"][key]

for cell in msh.cells:
    if cell.type == "tetra":
        tetra_cells = cell.data
    elif cell.type == "triangle":
        triangle_cells = cell.data

triangle_mesh = meshio.Mesh(points=msh.points,
                            cells=[("triangle", triangle_cells)],
                            cell_data={"name_to_read": [triangle_data]})

meshio.write("./Result--" + str(un)+ "mesh.xdmf",
             triangle_mesh)
meshio.write("mesh.xdmf", triangle_mesh)


mesh = Mesh()
mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim())
with XDMFFile("mesh.xdmf") as infile:
    infile.read(mesh)
mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim())
with XDMFFile("mesh.xdmf") as infile:
    infile.read(mvc, "name_to_read")
mf = cpp.mesh.MeshFunctionSizet(mesh, mvc)
print(mesh.topology().dim() - 1)

boundary_markers = MeshFunction('size_t', mesh, mesh.topology().dim() - 1, 0)

class Boundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[1], 0.0)

b_c = Boundary()
b_c.mark(boundary_markers, 3)
File("./Result--" + str(un)+  "MSH3.pvd") << boundary_markers

# Compiling subdomains
ds = Measure('ds', domain=mesh, subdomain_data=boundary_markers)
dx = Measure("dx", domain=mesh, subdomain_data=mf, subdomain_id=1)
V = VectorFunctionSpace(mesh, "CG", 2)  
W_tensor = TensorFunctionSpace(mesh, "CG", 2)

def boundary_bot(x, on_boundary):
    tol = 1E-7
    return on_boundary and near(x[1], 0, tol)

bc_bot = DirichletBC(V, [0, 0, 0], boundary_bot)

def boundary_top(x, on_boundary):
    tol = 1E-7
    return on_boundary and near(x[1], b, tol)

disp = Constant([0.0, 0.0, 0.0])
bc_top = DirichletBC(V, disp, boundary_top)
bcs = [bc_bot, bc_top]

du = TrialFunction(V)  # Incremental displacement
v = TestFunction(V)  # Test function
u = Function(V)  # Displacement from previous iteration
u0 = Function(V)  # Initial displacement
boundary_subdomains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_subdomains.set_all(0)
AutoSubDomain(boundary_top).mark(boundary_subdomains, 1)
dss = ds(subdomain_data=boundary_subdomains)


def kinematics(u):
    # Kinematics
    d = u.geometric_dimension()  # Space dimension
    I = Identity(d)  # Identity tensor
    F = variable(I + grad(u))  # Deformation gradient
    C = F.T * F  # Right Cauchy-Green tensor 
    
    # Invariants of deformation tensors
    Ic = tr(C)
    J = det(F)
    
    return Ic, J, F, C

from scipy import interpolate

# Strain data points and energy values
strain_values = [0.0, 0.6, 0.19, 0.25]
energy_values = [0.0, 0.03, 0.005, 0.047]

if any(strain_values[i] >= strain_values[i+1] for i in range(len(strain_values)-1)):
    # If there are repeated or non-unique values, preprocess the data
    unique_strain_values, unique_indices = np.unique(strain_values, return_index=True)
    unique_energy_values = [energy_values[i] for i in unique_indices]
    strain_values = list(unique_strain_values)
    energy_values = list(unique_energy_values)

# Calculate the spline for strain-energy interpolation
spline = CubicSpline(strain_values, energy_values)


# Solve variational problem
store_u = np.linspace(0, -0.4, 50 , endpoint=True) 

normal = Constant((0, 1, 0))
# Define the stress tensor function space
V_tensor = TensorFunctionSpace(mesh, "CG", 2)

force = []

for i in store_u:
    disp.assign(Constant([0.0, i, 0.0]))
    Ic, J, F, C = kinematics(u)
    # Define the custom strain energy density function
    def psi(F, u, i):
        strain = i
        energy = Constant(spline(strain))
        stress = diff(energy, F)
        return energy, stress

    psi, stress = psi(F, u, i)
    # Total potential energy
    Pi= psi * dx
    # Compute first variation of Pi (directional derivative about u in the direction of v)
    dPi = derivative(Pi, u, v)
    # Compute second variation of Pi (directional derivative about du in the direction of v)
    ddPi= derivative(dPi, u, du)
  
    # Define the nonlinear variational problem with Jacobian
    problem = NonlinearVariationalProblem(dPi u, bcs, J=ddPi)
    solver = NonlinearVariationalSolver(problem)
    solver.solve()
    
    # Create a new Function with the same FunctionSpace as stress to store the stress tensor
    stress_calculation = Function(W_tensor, name='Stress')
    stress_calculation.assign(stress)  # Assign the stress tensor to stress_calculation directly

    
    # Compute traction (force) on the boundary
    traction = dot(stress, normal)

    force.append(assemble((traction[1]) * dss(1)) )

Hi, I am not able to follow what are you trying to do here, please explain.

Pi_main is not defined.

Hi, thank you for replying. I edited the code. I am trying to have spline response for my strain energy. @violetus

I have modified the code in bits so that it is clear to discuss where the problem is arising -
Making geometry

import gmsh
import sys
from dolfin import *
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline

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

un = "trial"
b = 1
lc = 1e-1 * 10
scale = 1
def createGeometryAndMesh(b):
    # Clear all models and create a new one
    gmsh.initialize()
    gmsh.clear()
    gmsh.model.add("t3")


    P1 = gmsh.model.occ.addPoint(-b , 0, 0, lc)
    P2 = gmsh.model.occ.addPoint(b, scale *0, 0, lc)
    P3 = gmsh.model.occ.addPoint(-b,  b , 0, lc)
    P4 = gmsh.model.occ.addPoint(b ,  b , 0, lc)

    L1 = gmsh.model.occ.addLine(P1, P2)
    L2 = gmsh.model.occ.addLine(P2, P4)
    L3 = gmsh.model.occ.addLine(P4, P3)
    L4 = gmsh.model.occ.addLine(P3, P1)
    
    Curve1 = gmsh.model.occ.addCurveLoop([L1, L2, L3, L4])
    Surface1 = gmsh.model.occ.addPlaneSurface([Curve1],  1)
       
    # Synchronize the CAD model
    gmsh.model.occ.synchronize()
    # Create physical groups
    pg1 = gmsh.model.addPhysicalGroup(2, [Surface1])
    gmsh.model.setPhysicalName(2, pg1, "beam")

    # Set mesh size at all points
    gmsh.model.mesh.setSize(gmsh.model.getEntities(0), lc)

    gmsh.model.occ.synchronize()
    gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
    gmsh.model.mesh.generate()
    
    def checkForEvent():
        action = gmsh.onelab.getString("ONELAB/Action")
        if len(action) and action[0] == "check":
            gmsh.onelab.setString("ONELAB/Action", [""])
            createGeometryAndMesh()
            gmsh.graphics.draw()
        return True

    
    # Refine the mesh
    gmsh.model.mesh.refine()
    
    gmsh.write("geo.msh");

    gmsh.finalize()

createGeometryAndMesh(b)

Importing mesh using meshio

import meshio
msh = meshio.read("geo.msh")

for key in msh.cell_data_dict["gmsh:physical"].keys():
    if key == "triangle":
        triangle_data = msh.cell_data_dict["gmsh:physical"][key]

for cell in msh.cells:
    if cell.type == "tetra":
        tetra_cells = cell.data
    elif cell.type == "triangle":
        triangle_cells = cell.data

triangle_mesh = meshio.Mesh(points=msh.points,
                            cells=[("triangle", triangle_cells)],
                            cell_data={"name_to_read": [triangle_data]})

meshio.write("mesh.xdmf",triangle_mesh)

mesh = Mesh()
mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim())
with XDMFFile("mesh.xdmf") as infile:
    infile.read(mesh)    
mvc = MeshValueCollection("size_t", mesh, mesh.topology().dim())
with XDMFFile("mesh.xdmf") as infile:
    infile.read(mvc, "name_to_read")
mf = cpp.mesh.MeshFunctionSizet(mesh, mvc)

Defining boundary conditions

boundary_markers = MeshFunction('size_t', mesh, mesh.topology().dim() - 1, 0)

class Boundary(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[1], 0.0)

b_c = Boundary()
b_c.mark(boundary_markers, 3)
File("msh3.pvd") << boundary_markers

# Compiling subdomains
ds = Measure('ds', domain=mesh, subdomain_data=boundary_markers)
dx = Measure("dx", domain=mesh, subdomain_data=mf, subdomain_id=1)
V = VectorFunctionSpace(mesh, "CG", 2)  
W_tensor = TensorFunctionSpace(mesh, "DG", 0)

def boundary_bot(x, on_boundary):
    tol = 1E-7
    return on_boundary and near(x[1], 0, tol)

bc_bot = DirichletBC(V, [0, 0, 0], boundary_bot)

def boundary_top(x, on_boundary):
    tol = 1E-7
    return on_boundary and near(x[1], b, tol)

disp = Constant([0.0, 0.0, 0.0])
bc_top = DirichletBC(V, disp, boundary_top)
bcs = [bc_bot, bc_top]

Defining function spaces and kinematics

du = TrialFunction(V)  # Incremental displacement
v = TestFunction(V)  # Test function
u = Function(V)  # Displacement from previous iteration
u0 = Function(V)  # Initial displacement
boundary_subdomains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
boundary_subdomains.set_all(0)
AutoSubDomain(boundary_top).mark(boundary_subdomains, 1)
dss = ds(subdomain_data=boundary_subdomains)

def kinematics(u):
    # Kinematics
    d = u.geometric_dimension()  # Space dimension
    I = Identity(d)  # Identity tensor
    F = variable(I + grad(u))  # Deformation gradient
    C = F.T * F  # Right Cauchy-Green tensor 
    
    # Invariants of deformation tensors
    Ic = tr(C)
    J = det(F)
    
    return Ic, J, F, C

Strain and energy values preprocessing

from scipy import interpolate

# Strain data points and energy values
strain_values = [0.0, 0.6, 0.19, 0.25]
energy_values = [0.0, 0.03, 0.005, 0.047]

if any(strain_values[i] >= strain_values[i+1] for i in range(len(strain_values)-1)):
    # If there are repeated or non-unique values, preprocess the data
    unique_strain_values, unique_indices = np.unique(strain_values, return_index=True)
    unique_energy_values = [energy_values[i] for i in unique_indices]
    strain_values = list(unique_strain_values)
    energy_values = list(unique_energy_values)

# Calculate the spline for strain-energy interpolation
spline = CubicSpline(strain_values, energy_values)

# Solve variational problem
store_u = np.linspace(0, -0.4, 50 , endpoint=True) 

normal = Constant((0, 1, 0))
# Define the stress tensor function space
V_tensor = TensorFunctionSpace(mesh, "DG", 0)

Iterative loop for setting up solver and solving

force = []

for i in store_u:
    disp.assign(Constant([0.0, i, 0.0]))
    Ic, J, F, C = kinematics(u)
    # Define the custom strain energy density function
    def psi(F, u, i):
        strain = i
        energy = Constant(spline(strain))
        stress = diff(energy, F)
        return energy, stress

    psi, stress = psi(F, u, i)
    # Total potential energy
    Pi= psi * dx
    # Compute first variation of Pi (directional derivative about u in the direction of v)
    dPi = derivative(Pi, u, v)
    # Compute second variation of Pi (directional derivative about du in the direction of v)
    ddPi= derivative(dPi, u, du)
  
    # Define the nonlinear variational problem with Jacobian
    problem = NonlinearVariationalProblem(dPi, u, bcs, J=ddPi)
    solver = NonlinearVariationalSolver(problem)
    solver.solve()

Executing the last block is giving me error -

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
/tmp/ipykernel_383/4227113402.py in <module>
     20     problem = NonlinearVariationalProblem(dPi, u, bcs, J=ddPi)
     21     solver = NonlinearVariationalSolver(problem)
---> 22     solver.solve()

RuntimeError: 

*** -------------------------------------------------------------------------
*** DOLFIN encountered an error. If you are not able to resolve this issue
*** using the information listed below, you can ask for help at
***
***     fenics-support@googlegroups.com
***
*** Remember to include the error message listed below and, if possible,
*** include a *minimal* running example to reproduce the error.
***
*** -------------------------------------------------------------------------
*** Error:   Unable to successfully call PETSc function 'MatSetValuesLocal'.
*** Reason:  PETSc error code is: 63 (Argument out of range).
*** Where:   This error was encountered inside ./dolfin/la/PETScMatrix.cpp.
*** Process: 0
*** 
*** DOLFIN version: 2019.2.0.dev0
*** Git changeset:  ubuntu
*** -------------------------------------------------------------------------

This issue is arising because energy is constant and differentiating it wrt F return 0 which is similar to the issue seen in this post Error: Unable to successfully call PETSc function ‘MatSetValuesLocal’. Reason: PETSc error code is: 63 (Argument out of range), As suggested by @dokken in the post, you might want to have a look at Hyperelasticity — DOLFIN documentation.
I hope this helps.

2 Likes

thank you! @violetus

Hello again, @violetus, sorry I am not too familiar with these much, I could not solve the problem. Do you have any more suggestion?

Consider reading Incompressible Humphrey hyperelastic material in Fencis, this might be of your interest.

Here I defined strain again by following the hyperelasticity document. However I could not find how to manipulate the strain energy to have a specific spline respond.

    def psi(u, i, Ic, J, F, C):
        # Kinematics
        d = u.geometric_dimension()  # Space dimension
        I = Identity(d)  # Identity tensor
        C = F.T * F  # Right Cauchy-Green tensor
        
        # Invariants of deformation tensors
        Ic = tr(C)
        J = det(F)
    
        # Define the strain energy density function
        def strain_energy_density(Ic):
            return (E2 / 2) * (Ic - 3)
    
        # Calculate the strain tensor
        strain = C - Ic * I
    
        # Calculate the energy and stress using the strain energy density function
        energy = strain_energy_density(Ic)
        stress = diff(energy, F)
    
        return energy, stress

What do you mean by E2 here ?

E2 = calculate_spline(x_values, y_values, i)

Ok, maybe I have to workaround the code a bit and fully understand what’s going on there. On the side note, I tried to plot original data and interpolated data using -

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline

# Strain data points and energy values
strain_values = [0.0, 0.6, 0.19, 0.25]
energy_values = [0.0, 0.03, 0.005, 0.047]

if any(strain_values[i] >= strain_values[i+1] for i in range(len(strain_values)-1)):
    # If there are repeated or non-unique values, preprocess the data
    unique_strain_values, unique_indices = np.unique(strain_values, return_index=True)
    unique_energy_values = [energy_values[i] for i in unique_indices]
    strain_values = list(unique_strain_values)
    energy_values = list(unique_energy_values)

# Calculate the spline for strain-energy interpolation
spline = CubicSpline(strain_values, energy_values)

# Solve variational problem
store_u = np.linspace(0, -0.4, 50, endpoint=True)

# Calculate energy values using the spline for the given strain values
interpolated_energy_values = spline(store_u)

# Plot the original data points
plt.plot(strain_values, energy_values, 'o', label='Original Data')

# Plot the interpolated data using the spline
plt.plot(store_u, interpolated_energy_values, label='Interpolated Data')

plt.xlabel('Strain')
plt.ylabel('Energy')
plt.title('Strain-Energy Interpolation')
plt.legend()
plt.grid(True)
plt.show()


Could you please interpret the plot and tell what you are trying to achieve exactly ?
Also I would strongly suggest you to go through this post Incompressible Humphrey hyperelastic material in Fencis which shows how you can make user-defined functions and use them.

@violetus Yes, you’re right, I’m checking it out. It’s a little hard to understand as I’m new to Fenics.

What I want to do is: I want to apply a displacement from top to a rectangular. I want the obtain a force-displacement graph from this. This graph should give me a spline with known 3 or 4 points that I want.
I also want to thank you very much for your great help.

I think the post Incompressible Humphrey hyperelastic material in Fencis is doing the same thing which you want to achieve, the author is giving a time-dependent displacement and then calculating the stress corresponding to that and hence pressure vs displacement graph comes out to be like -

yes, I also divided it into steps like they did with time. However, I still haven’t figured out how to define the strain energy to have this curve.

In the context of the post Incompressible Humphrey hyperelastic material in Fencis the author defines strain energy density as -

psi = inner(dot(dgF,pkstrs), grad(_u))

In Hyperelasticity — DOLFIN documentation the author defines the strain energy density as -

# Stored strain energy density (compressible neo-Hookean model)
psi = (mu/2)*(Ic - 3) - mu*ln(J) + (lmbda/2)*(ln(J))**2

So, I think it depends on the user which model they want to choose.

where I define spline points as:

from scipy import interpolate

def calculate_spline(x_values, y_values, x):
    # Calculate spline coefficients
    spline = CubicSpline(x_values, y_values)
    y = spline(x)    
    return y

# Define the spline values
x_values = [0, 0.06, 0.19, 0.25]
y_values = [0, 0.03, 0.005, 0.047]

then I calculate psi as below:



for i in store_u:
    disp.assign(Constant([0.0, i, 0.0]))
    Ic, J, F, C = kinematics(u)
    # Define the custom strain energy density function

    def psi(u, i, Ic, J, F, C):
        # Kinematics
        d = u.geometric_dimension()  # Space dimension
        I = Identity(d)  # Identity tensor
        F = variable(I + grad(u))  # Deformation gradient
        C = F.T * F  # Right Cauchy-Green tensor 
        
        # Invariants of deformation tensors
        Ic = tr(C)
        J = det(F)
        # Define the strain energy density function
        def strain_energy_density(u, strain):
            energy = inner(dot(F, strain), grad(u))
            return energy
        

        # Calculate the strain tensor
        strain = C - Ic * I - calculate_spline(x_values, y_values, i)*inv(C)
    
        # Calculate the energy and stress using the strain energy density function
        energy = strain_energy_density(u, strain)
        stress = diff(energy, F)
    
        return energy, stress

it is working but the result is not correct

Can you please share the results ? And tell that what you were expecting instead of that.

this is the result I got: @violetus
image

I want to have something like this spline with the defined points. I want to have a specific response like this spline from my simulation.

image

You need to apply different energy model to get the type of response you want, so the result you are getting is most probably from Neo-Hookean energy model. I am getting a vague idea that what you are attempting to simulate is viscoelasticity therefore I would suggest you to go through this post Issues with a viscoelastic problem.

1 Like