Structural analysis of cantilever beam in multi-layer complexes

Hi, everyone!
I’m doing a displacement analysis of a five-layer composite cuboid cantilever beam, I have meshed it and divided it into five physic groups by gmsh, and the .msh file has been read by dofinx, but now I’m having some problems with the displacement results. Can someone help me find the mistakes? Thanks.

Each layer is divided into two units(z-dircetion)

# Obtain the stiffness matrix for all material layers
layer_materials = [get_elasticity_matrix(layer) for layer in all_layers]

# Create multiple DG0 functions to store the stiffness matrix components separately
C_components = [fem.functionspace(mesh, ("DG", 0)) for _ in range(36)]
C_funcs = [fem.Function(C) for C in C_components]

# Assign a stiffness matrix to each layer
for i, group in enumerate(physical_groups):
    layer_cells = cell_tag.find(group)
    C_i = layer_materials[i].flatten() 

    for j in range(36):
        C_funcs[j].x.array[layer_cells] = C_i[j]

V = functionspace(mesh, ("Lagrange", 1, (mesh.geometry.dim,)))

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)


def epsilon(u):
    return ufl.sym(ufl.grad(u))


def sigma(u):
    C_mat = ufl.as_matrix([
        [C_funcs[0], C_funcs[5], C_funcs[4], C_funcs[15], C_funcs[14], C_funcs[13]],
        [C_funcs[5], C_funcs[1], C_funcs[3], C_funcs[16], C_funcs[12], C_funcs[17]],
        [C_funcs[4], C_funcs[3], C_funcs[2], C_funcs[18], C_funcs[19], C_funcs[20]],
        [C_funcs[15], C_funcs[16], C_funcs[18], C_funcs[21], C_funcs[22], C_funcs[23]],
        [C_funcs[14], C_funcs[12], C_funcs[19], C_funcs[22], C_funcs[24], C_funcs[25]],
        [C_funcs[13], C_funcs[17], C_funcs[20], C_funcs[23], C_funcs[25], C_funcs[26]]
    ])

    e = epsilon(u)
    e_voigt = ufl.as_vector([e[0, 0], e[1, 1], e[2, 2], 2 * e[1, 2], 2 * e[0, 2], 2 * e[0, 1]])
    s_voigt = ufl.dot(C_mat, e_voigt)
    return ufl.as_tensor([
        [s_voigt[0], s_voigt[5], s_voigt[4]],
        [s_voigt[5], s_voigt[1], s_voigt[3]],
        [s_voigt[4], s_voigt[3], s_voigt[2]]
    ])


# Define boundary conditions
def left_boundary(x):
    return np.isclose(x[0], 0, atol=1e-6)

left_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, left_boundary)
mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
left_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, left_facets)
u_bc = np.zeros(mesh.geometry.dim, dtype=np.float64)  
bc_left = dolfinx.fem.dirichletbc(u_bc, left_dofs, V)

T = fem.Constant(mesh, default_scalar_type((0, 0, 0)))
ds = ufl.Measure("ds", domain=mesh)
f = fem.Constant(mesh, default_scalar_type((0, 0, -0.0001*9.81)))

metadata = {"quadrature_degree": 2}  
dx = ufl.Measure("dx", domain=mesh, subdomain_data=cell_tag, metadata=metadata)
a = ufl.inner(sigma(u), epsilon(v)) * dx
L = ufl.dot(f, v) * dx + ufl.dot(T, v) * ds

problem = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc_left],
                                          petsc_options={"ksp_type": "preonly", "pc_type": "lu"})

uh = problem.solve()
displacement = uh.x.array.reshape(-1, 3)

I reworked the code based on this article

> Blockquote

# Obtain the stiffness matrix for all material layers
layer_materials = [get_elasticity_matrix(layer) for layer in all_layers]

#=======================
# assemble siffness tensor
#=======================
material_tags = np.unique(cell_tag.values)

T = functionspace(mesh, ("DG", 0, (6, 6)))
stiffness = dolfinx.fem.Function(T)

def tensor(x, tens):
    tens_reshaped = tens.reshape((6 * 6, 1))
    return np.broadcast_to(tens_reshaped, (6 * 6, x.shape[1]))

for tag in material_tags:
    domain = cell_tag.find(tag)
    if tag <= len(layer_materials):
        C = layer_materials[tag-1]
        stiffness.interpolate(lambda x: tensor(x, C.astype(np.float32)), domain)



#=======================
# helpers
#=======================
def epsilon(u):
    return ufl.sym(ufl.grad(u)) # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)


def strain2voigt(e, dim: int = 3):
    """e is a 2nd-order tensor, returns its Voigt vectorial representation"""
    if dim == 3:
        return ufl.as_vector([e[0,0],e[1,1],e[2,2],2*e[1,2], 2*e[0,2],2*e[0,1]])
    else:
        return ufl.as_vector([e[0,0],e[1,1],2*e[0,1]])


def voigt2stress(s, dim: int = 3):
    """
    s is a stress-like vector (no 2 factor on last component)
    returns its tensorial representation
    """
    if dim == 3:
        return ufl.as_tensor([
                              [s[0], s[5], s[4]],
                              [s[5], s[1], s[3]],
                              [s[4], s[3], s[2]]
                            ])
    else:
        return ufl.as_tensor([
                                [s[0], s[2]],
                                [s[2], s[1]]
                            ])


def sigma(u, stiff_tens):
    return voigt2stress(ufl.dot(stiff_tens, strain2voigt(epsilon(u))))


#=======================
# problem
#=======================

V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1, (mesh.geometry.dim,)))

# Define variational problem
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)

a = ufl.inner(
    ufl.dot(ufl.as_matrix(stiffness), strain2voigt(epsilon(u))),
    strain2voigt(epsilon(v))
) * ufl.dx


def left_boundary(x):
    return np.isclose(x[0], 0, atol=1e-6)



left_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, left_boundary)


mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)

left_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, left_facets)
u_bc = np.zeros(mesh.geometry.dim, dtype=np.float64)  
bc_left = dolfinx.fem.dirichletbc(u_bc, left_dofs, V)

T = fem.Constant(mesh, default_scalar_type((0, 0, 0)))
ds = ufl.Measure("ds", domain=mesh)
f = fem.Constant(mesh, default_scalar_type((0, 0, -0.0001*9.81)))

problem = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc_left],
                                          petsc_options={"ksp_type": "preonly", "pc_type": "lu"})

uh = problem.solve()
displacement = uh.x.array.reshape(-1, 3)

Now the displacement result is still not right,please.

I think it will be difficult for someone in the community to provide help:

  • Your code is not runnable. It is missing imports, meshes, function declarations, etc.
  • You provide almost no context to the problem.
  • You don’t specify what actually is your problem. “Im having some problems with the displacement results” and “the displacement result is still not right” is by no means sufficient.

But most importantly:

  • This community is there to help solve targeted “unit” problems. I.e., to look at code snippets that give wrong results or errors. It is unlikely that someone can (and is willing to) invest the time to learn your application and debug a full code.

Thank you for your reply, I’m sorry I didn’t articulate my question correctly. I created a model and divided the physics groups by using gmsh. Now the code works fine, but I’m not sure I’m correctly dispensing five different layers of material. Allow me to put the full code.
Here’s my code to create a model and divide the physical groups by using gmsh:

import gmsh

gmsh.initialize()
gmsh.model.add("CompositePlate")

# Define the overall box
dx, dy = 10., 2.
layer_heights = [0.2, 0.02, 0.2, 0.02, 0.2]  #The thickness of each layer
dz = sum(layer_heights)  # total thickness


layer_boxes = []
z_offset = 0
for i, thickness in enumerate(layer_heights):
    box = gmsh.model.occ.addBox(0, 0, z_offset, dx, dy, thickness)
    layer_boxes.append((3, box))
    z_offset += thickness

gmsh.model.occ.fragment(layer_boxes, layer_boxes)
gmsh.model.occ.synchronize()

volumes = gmsh.model.getEntities(3)
for i, (dim, tag) in enumerate(volumes):
    gmsh.model.addPhysicalGroup(dim, [tag], i + 1)
    gmsh.model.setPhysicalName(dim, i + 1, f"Layer_{i+1}")


# Get All Edges (1D Segments)
entities = gmsh.model.getEntities(1)
edges_x = []  # X-direction
edges_y = []  # Y-direction
edges_z = []  # Z-direction

for dim, tag in entities:
    xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.getBoundingBox(dim, tag)

    if abs(xmax - xmin) > 1e-6 and abs(ymax - ymin) < 1e-6 and abs(zmax - zmin) < 1e-6:
        edges_x.append(tag)

    elif abs(xmax - xmin) < 1e-6 and abs(ymax - ymin) > 1e-6 and abs(zmax - zmin) < 1e-6:
        edges_y.append(tag)

    elif abs(xmax - xmin) < 1e-6 and abs(ymax - ymin) < 1e-6 and abs(zmax - zmin) > 1e-6:
        edges_z.append(tag)


# Sets the number of cells in each direction
num_x = 50
num_y = 10
num_z_per_layer = 2  # 2 units in the Z direction of each floor


# Transfinite mesh
for edge in edges_x:
    gmsh.model.mesh.setTransfiniteCurve(edge, num_x + 1)

for edge in edges_y:
    gmsh.model.mesh.setTransfiniteCurve(edge, num_y + 1)

for edge in edges_z:
    gmsh.model.mesh.setTransfiniteCurve(edge, num_z_per_layer + 1)

# Sets the faces to a structured quadrilateral mesh
surfaces = gmsh.model.getEntities(2)  # Get All Faces
for dim, tag in surfaces:
    gmsh.model.mesh.setTransfiniteSurface(tag)
    gmsh.model.mesh.setRecombine(dim, tag)

# Set the volume to a structured hexahedral mesh
volumes = gmsh.model.getEntities(3)  # Get All Volumes
for dim, tag in volumes:
    gmsh.model.mesh.setTransfiniteVolume(tag)

# generate mesh
gmsh.model.mesh.generate(3)

gmsh.fltk.run()

# Export mesh file
mesh_file_name = "generated_mesh_1.msh"
gmsh.write(mesh_file_name)
print(f"Mesh successfully written to {mesh_file_name}")

gmsh.finalize()

Here’s the full code for solving with fenicsx:

import json
import numpy as np
from numpy.linalg import inv
import mpi4py
from dolfinx.mesh import locate_entities_boundary
from dolfinx import mesh, fem, plot, io, default_scalar_type
import ufl
import pyvista as pv
import dolfinx.fem.petsc

# Read mesh file
mesh, cell_tag, facet_tag = dolfinx.io.gmshio.read_from_msh(msh_path, mpi4py.MPI.COMM_WORLD, 0, gdim=3)

# Read the material properties JSON file
with open(json_path, 'r') as f:
    materials = json.load(f)
all_layers = materials


# Rotation matrix
def rotation_matrix(C, theta):
    # x_3 roate
    theta = (np.pi * theta)
    c = np.cos(theta)
    s = np.sin(theta)
    R = np.zeros((6, 6), dtype=np.float64)

    R[0, 0] = c * c
    R[0, 1] = s * s
    R[0, 5] = 2 * c * s
    R[1, 0] = s * s
    R[1, 1] = c * c
    R[1, 5] = -2 * c * s
    R[2, 2] = 1
    R[3, 3] = c
    R[3, 4] = s
    R[4, 3] = -s
    R[4, 4] = c
    R[5, 0] = -c * s
    R[5, 1] = c * s
    R[5, 5] = (c * c) - (s * s)

    # Perform the rotation of the stiffness matrix C
    Chat = np.matmul(R, np.matmul(C, np.transpose(R)))

    return Chat

# Define the stiffness matrix of anisotropic materials
def get_elasticity_matrix(layer):
    if layer["material_type"] == "Composite Layer (Anisotropic)":
        E1, E2, E3 = layer["E1"], layer["E2"], layer["E3"]
        nu21, nu31, nu32 = layer["nu21"], layer["nu31"], layer["nu32"]
        G12, G13, G23 = layer["G12"], layer["G13"], layer["G23"]
        fiber_angle = layer["fiber_angle"]
        layer["fiber_angle"] = np.clip(perturbed_angle, 0, 1)

        S = np.zeros((6, 6))
        S[0, 0] = 1 / E1
        S[0, 1] = -nu21 / E2
        S[0, 2] = -nu31 / E3
        S[1, 0] = S[0, 1]
        S[1, 1] = 1 / E2
        S[1, 2] = -nu32 / E3
        S[2, 0] = S[0, 2]
        S[2, 1] = S[1, 2]
        S[2, 2] = 1 / E3
        S[3, 3] = 1 / G23
        S[4, 4] = 1 / G13
        S[5, 5] = 1 / G12

        # The Stiffness Matrix C is calculated as the inverse of the conformance matrix
        C = inv(S)
        elasticity_matrix_rotated = rotation_matrix(C, perturbed_angle)
        return elasticity_matrix_rotated
    else:
        E, nu = layer["E"], layer["nu"]
        lmbda = E * nu / ((1 + nu) * (1 - 2 * nu))
        mu = E / (2 * (1 + nu))

        elasticity_matrix = np.array([
            [lmbda + 2 * mu, lmbda, lmbda, 0, 0, 0],
            [lmbda, lmbda + 2 * mu, lmbda, 0, 0, 0],
            [lmbda, lmbda, lmbda + 2 * mu, 0, 0, 0],
            [0, 0, 0, mu, 0, 0],
            [0, 0, 0, 0, mu, 0],
            [0, 0, 0, 0, 0, mu]
        ])

        return elasticity_matrix


# Obtain the stiffness matrix for all material layers
layer_materials = [get_elasticity_matrix(layer) for layer in all_layers]


#=======================
# assemble siffness tensor
#=======================
material_tags = np.unique(cell_tag.values)
# Define function space for different material
V_mat = functionspace(mesh, ("DG", 0, (6, 6)))
stiffness = dolfinx.fem.Function(V_mat)


def tensor(x, tens):
    tens_reshaped = tens.reshape((6 * 6, 1))
    return np.broadcast_to(tens_reshaped, (6 * 6, x.shape[1]))


for tag in material_tags:
    domain = cell_tag.find(tag)
    if tag <= len(layer_materials):
        C = layer_materials[tag-1]
        stiffness.interpolate(lambda x: tensor(x, C.astype(np.float32)), domain)


#=======================
# helpers
#=======================
def epsilon(u):
    return ufl.sym(ufl.grad(u)) # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)


def strain2voigt(e, dim: int = 3):
    """e is a 2nd-order tensor, returns its Voigt vectorial representation"""
    if dim == 3:
        return ufl.as_vector([e[0,0],e[1,1],e[2,2],2*e[1,2], 2*e[0,2],2*e[0,1]])
    else:
        return ufl.as_vector([e[0,0],e[1,1],2*e[0,1]])


def voigt2stress(s, dim: int = 3):
    """
    s is a stress-like vector (no 2 factor on last component)
    returns its tensorial representation
    """
    if dim == 3:
        return ufl.as_tensor([
                              [s[0], s[5], s[4]],
                              [s[5], s[1], s[3]],
                              [s[4], s[3], s[2]]
                            ])
    else:
        return ufl.as_tensor([
                                [s[0], s[2]],
                                [s[2], s[1]]
                            ])


def sigma(u, stiff_tens):
    return voigt2stress(ufl.dot(stiff_tens, strain2voigt(epsilon(u))))


#=======================
# problem
#=======================

# Define Function Space
V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1, (mesh.geometry.dim,)))

# Define variational problem
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)

a = ufl.inner(
    ufl.dot(ufl.as_matrix(stiffness), strain2voigt(epsilon(u))),
    strain2voigt(epsilon(v))
) * ufl.dx


# Define the judgment function of the left end face
def left_boundary(x):
    return np.isclose(x[0], 0, atol=1e-6)

# Find Left Face
left_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, left_boundary)

mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)

# Apply Fixed Boundary Condition (Left End Face)
left_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, left_facets)
u_bc = np.zeros(mesh.geometry.dim, dtype=np.float64)  
bc_left = dolfinx.fem.dirichletbc(u_bc, left_dofs, V)

# Apply gravity
T = fem.Constant(mesh, default_scalar_type((0, 0, 0)))
ds = ufl.Measure("ds", domain=mesh)
f = fem.Constant(mesh, default_scalar_type((0, 0, -0.000981)))

L = ufl.dot(f, v) * ufl.dx + ufl.dot(T, v) * ds

# Solve
# Linear Problem
problem = dolfinx.fem.petsc.LinearProblem(a, L, bcs=[bc_left],
                                          petsc_options={"ksp_type": "preonly", "pc_type": "lu"})


uh = problem.solve()
displacement = uh.x.array.reshape(-1, 3)

# Outputs the displacement of each layer
print("Layer\tMax Displacement\t\tMin Displacement\t\tMean Displacement") 
for i in range(len(layer_materials)):
    layer_cells = cell_tag.indices[cell_tag.values == (i + 1)]
    layer_nodes = np.unique(np.concatenate(
        [mesh.topology.connectivity(mesh.topology.dim, 0).links(c) for c in layer_cells]
    ))
    layer_displacement = displacement[layer_nodes]

    print(
        f"{i + 1}\t"
        f"{np.max(np.linalg.norm(layer_displacement, axis=1)):.6f}\t"
        f"{np.min(np.linalg.norm(layer_displacement, axis=1)):.6f}\t"
        f"{np.mean(np.linalg.norm(layer_displacement, axis=1)):.6f}"
    )

I’m not sure if my question is posted here for consultation, but thanks again for your reply.

This is the .json file that defines the properties of the material.

[
    {
        "layer_id": 1,
        "material_type": "Composite Layer (Anisotropic)",
        "E1": 135.0,
        "E2": 8.5,
        "E3": 8.5,
        "nu21": 0.022,
        "nu31": 0.022,
        "nu32": 0.45,
        "G12": 5.0,
        "G13": 5.0,
        "G23": 5.0,
        "fiber_angle": 0.25
    },
    {
        "layer_id": 2,
        "material_type": "Interface Layer (Isotropic)",
        "E": 4.5,
        "nu": 0.35
    },
    {
        "layer_id": 3,
        "material_type": "Composite Layer (Anisotropic)",
        "E1": 135.0,
        "E2": 8.5,
        "E3": 8.5,
        "nu21": 0.022,
        "nu31": 0.022,
        "nu32": 0.45,
        "G12": 5.0,
        "G13": 5.0,
        "G23": 5.0,
        "fiber_angle": 0.0
    },
    {
        "layer_id": 4,
        "material_type": "Interface Layer (Isotropic)",
        "E": 4.5,
        "nu": 0.35
    },
    {
        "layer_id": 5,
        "material_type": "Composite Layer (Anisotropic)",
        "E1": 135.0,
        "E2": 8.5,
        "E3": 8.5,
        "nu21": 0.022,
        "nu31": 0.022,
        "nu32": 0.45,
        "G12": 5.0,
        "G13": 5.0,
        "G23": 5.0,
        "fiber_angle": 0.75
    }
]