Failure in importing second-order mesh in legacy FEniCS

Hi everyone in the community,
I’m quite a newbie in Fenics(2019.1.0)/Fenicsx(0.9), actually also a newbie in FEM simulation, so please forgive me if my questions are not so smart.

Recently I’m working on 2D contact problems using the third medium method. Here is a simple box example to model:
Initial state of the box:


Final state of the box:

A second-order mesh is supposed to be used, so I created the mesh using GMSH, here is the box.geo:

p0x = 0.0;
p0y = 0.0;
p1x = 2.0;
p1y = 0.5;
Nx = 40;
Ny = 10;
lc = 1.0;

Point(1) = {p0x, p0y, 0, lc};
Point(2) = {p0x, p1y, 0, lc};
Point(3) = {p1x, p1y, 0, lc};
Point(4) = {p1x, p0y, 0, lc};

Line(1) = {1, 2};
Line(2) = {2, 3};
Line(3) = {3, 4};
Line(4) = {4, 1};

Curve Loop(1) = {1, 2, 3, 4};
Plane Surface(1) = {1};
Physical Surface("My surface") = {1};

Transfinite Curve {1, -3} = Ny+1;
Transfinite Curve {2, -4} = Nx+1;
Transfinite Surface{1};
//Recombine Surface{1};
Mesh.ElementOrder = 2;        // Second-order elements
Mesh.Algorithm = 8;           // Delaunay mesh algorithm

Mesh 2;

Then I import the mesh into Fenics:

mesh_name = "box"
msh = meshio.read(f"{mesh_name}.msh")

def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
    points = mesh.points[:, :2] if prune_z else mesh.points
    out_mesh = meshio.Mesh(points=points, cells={cell_type: cells}, cell_data={
                            "name_to_read":[cell_data]})
    return out_mesh

triangle_mesh = create_mesh(msh, "triangle6", prune_z=True)
meshio.write(f"{mesh_name}_mesh.xdmf", triangle_mesh)

mesh = Mesh()
with XDMFFile(f"{mesh_name}_mesh.xdmf") as infile:
    infile.read(mesh)

But when running the script, the problem is not solved at all and the box looks very strange in the xdmf file:
n=: 1
Step: 1 ut.n = 1.0 Nsteps = 10.0
Prescribed top displacement = -0.1
Solving nonlinear variational problem.
Newton iteration 0: r (abs) = 7.776e-16 (tol = 1.000e-10) r (rel) = 1.000e+00 (tol = 1.000e-09)
Newton solver finished in 0 iterations and 0 linear solver iterations.
n=: 2
Step: 2 ut.n = 2.0 Nsteps = 10.0
Prescribed top displacement = -0.2
Solving nonlinear variational problem.
Newton iteration 0: r (abs) = 7.776e-16 (tol = 1.000e-10) r (rel) = 1.000e+00 (tol = 1.000e-09)
Newton solver finished in 0 iterations and 0 linear solver iterations.

If I modify the mesh order into 1, i.e. in box.geo, set Mesh.ElementOrder = 1, the problem can be solved successfully, and the results are shown in the first two images.
So I think I am not importing the second-order mesh correctly — there must be something missing. I would appreciate any help!

FYI, I have also succeeded in loading the box.msh(with second-order mesh) in a Fenicsx script using gmshio.read_from_msh, and the problem can be solved without any issues. I want to stick with Fenics because there are some arc_length solvers available here, while Fenicsx doesn’t have them yet.

If someone is interested, here is the complete Fenics script for the problem:

from dolfin import*
import meshio

# Parameters
file_name = "box_gmsh2_tri" # name of the simulation
mu, K = 10.0, 20.0
gamma = 2.0e-4
alpha_r = 0.1
Nsteps  = 10 # loading steps

# read msh file
mesh_name = "box"
msh = meshio.read(f"{mesh_name}.msh")

def create_mesh(mesh, cell_type, prune_z=False):
    cells = mesh.get_cells_type(cell_type)
    cell_data = mesh.get_cell_data("gmsh:physical", cell_type)
    points = mesh.points[:, :2] if prune_z else mesh.points
    out_mesh = meshio.Mesh(points=points, cells={cell_type: cells}, cell_data={
                            "name_to_read":[cell_data]})
    return out_mesh

triangle_mesh = create_mesh(msh, "triangle6", prune_z=True)
meshio.write(f"{mesh_name}_mesh.xdmf", triangle_mesh)

mesh = Mesh()
with XDMFFile(f"{mesh_name}_mesh.xdmf") as infile:
    infile.read(mesh)

# Define subdomains for different materials
tol = 1e-8
medium = CompiledSubDomain("x[0] >= 0.1 - tol && x[0] <= 1.9 + tol && x[1] >= 0.1 - tol && x[1] <= 0.4 + tol", tol=tol)
body = CompiledSubDomain("x[0] <= 0.1 + tol || x[0] >= 1.9 - tol || x[1] <= 0.1 + tol || x[1] >= 0.4 - tol", tol=tol)

# Create mesh function to mark materials
materials = MeshFunction("size_t", mesh, mesh.topology().dim())
materials.set_all(0)  
body.mark(materials, 1)      # Mark body as 1
medium.mark(materials, 2)    # Mark medium as 2
dx = Measure("dx", subdomain_data=materials)

# Verify material classification
num_body_cells = sum(1 for cell in cells(mesh) if materials[cell] == 1)
num_medium_cells = sum(1 for cell in cells(mesh) if materials[cell] == 2)

print(f"Total number of cells in the mesh: {mesh.num_cells()}")
print(f"Number of medium cells: {num_medium_cells}")
print(f"Number of body cells: {num_body_cells}")

# Store material markers for ParaView visualization
V0 = FunctionSpace(mesh, "DG", 0)
material_function = Function(V0, name="material")

cell_values = material_function.vector()
for cell in cells(mesh):
    cell_values[cell.index()] = materials[cell]
material_function.vector().set_local(cell_values.get_local())

# Subdomain where the point displacement is applied
top_point = CompiledSubDomain("near(x[0], 1.0) && near(x[1], 0.5)")

# Boundary subdomians
left =  CompiledSubDomain("near(x[0], 0.0) && near(x[1], 0.0)")
right = CompiledSubDomain("near(x[0], 2.0) && near(x[1], 0.0)")

# Displacement
Vu = VectorFunctionSpace(mesh, 'CG', 2) # 2. order
du = TrialFunction(Vu)
v = TestFunction(Vu)
u = Function(Vu, name="displacement")

# Apply BCs
ul = Constant((0.0, 0.0))
ur = Constant((0.0))
n = 0
ut = Expression(("-(n/Nsteps)"), n=n, Nsteps=Nsteps, degree=1)
bcl = DirichletBC(Vu, ul, left, method="pointwise")
bcr = DirichletBC(Vu.sub(1), ur, right, method="pointwise")
bct = DirichletBC(Vu.sub(1), ut, top_point, method="pointwise") # Point displacement
bcu =[bcl, bcr, bct]

# Kinematics
d = len(u)
I = Identity(d)            
F_0 = I + grad(u)             # Deformation gradient in 2D
F = as_tensor([[F_0[0, 0], F_0[0, 1], 0],
               [F_0[1, 0], F_0[1, 1], 0],
               [0, 0, 1]])                  # Deformation gradient in 3D
C = F.T*F
print (d)

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

# Stored strain energy density (compressible neo-Hookean model)
psi = (K/2)*(ln(J_safe))**2 + (mu/2)*(J_safe**(-2/3)*Ic - 3) # 3D
psi_= (mu/2)*(J_safe**(-2/3)*Ic - 3)

# Potential energy
epsilon = 0
Pi_body = psi*dx(1)  # - dot(pload, u)*ds(3)

Pi_tan_RJ = (gamma / 2) * alpha_r * (
    inner(grad((F[0,1] - F[1,0]) / (F[0,0] + F[1,1]+ epsilon)), grad((F[0,1] - F[1,0]) / (F[0,0] + F[1,1]+ epsilon)))
    + inner(grad(J_safe), grad(J_safe))
    ) * dx(2)

Pi_medium = gamma*psi_*dx(2) + Pi_tan_RJ

Pi_total = Pi_body + Pi_medium

# First variation of Pi_total
L = derivative(Pi_total, u, v)
J_L = derivative(L, u, du)

# output for paraview
outY= XDMFFile('./{}/results_{}.xdmf'.format(file_name, file_name))
outY.parameters["flush_output"] = True
outY.parameters["functions_share_mesh"] = True
outY.write(u,0)
outY.write(material_function, 0) # Write material markers

# loop
while n < Nsteps: 
    n += 1
    ut.n = n 
    bct = DirichletBC(Vu.sub(1), ut, top_point, method="pointwise")
    bcu = [bcl, bcr, bct]
    ut.n = n 
    
    print("n=: ", n)
    print("Step:", n, "ut.n =", ut.n, "Nsteps =", ut.Nsteps)
    print("Prescribed top displacement =", ut(Point(1.0, 0.5)))

    problem = NonlinearVariationalProblem(L, u, bcu, J=J_L)
    solver = NonlinearVariationalSolver(problem)

    prm = solver.parameters
    #prm["newton_solver"]["relaxation_parameter"] = 0.1  # Try 0.1 to 0.5
    prm["nonlinear_solver"] = "newton"
    prm["newton_solver"]["absolute_tolerance"] = 1e-10
    prm["newton_solver"]["relative_tolerance"] = 1e-9
    prm["newton_solver"]["maximum_iterations"] = 50
    prm["newton_solver"]["report"] = True
    prm["newton_solver"]["error_on_nonconvergence"] = True

    solver.solve()

    outY.write(u, n)
    outY.write(material_function, n)

outY.close()

Legacy FEniCS does not have out of the box support for second order meshes. One would manually have to permute the topological entities when adding them through the use of dolfin.MeshEditor.
Some of this has been explained in: Generation of second-order triangular meshes in legacy FEniCS - #4 by dokken

Maybe what you need is implemented in DOLFINy? (dolfiny/src/dolfiny/continuation.py at 39ddc50e99e38235c2aac8c489605726fbee64df · fenics-dolfiny/dolfiny · GitHub, ref: file:///home/dokken/Downloads/fenics2022-tdc-zilian-2.pdf)