Issue with dolfinx.fem.Function.interpolate

Dear all,

I am using Fenicsx to model a static problem of the Reissner-Mindlin unit square plate under unifrom load, with displacements fixed on each border.

I based my code on two very good tutorials:

When I get the stress results (sigma_x), the results are correct; however, they are not smooth and symmetric (please see the figure below).

As you can see, the stress seems to be moved sligthly to left.

I do believe that I am misusing interpolate function, though, I do not know what to do to improve this figure/ these results.

I work with:

  • python=3.11.10
  • fenics-libdolfinx=0.9.0
  • matplotlib=3.8.3

This is the code that I used to generate this figure:

import numpy as np
import ufl
import basix

from mpi4py import MPI
from dolfinx import fem

from dolfinx.fem.petsc import LinearProblem
from dolfinx.mesh import (
    CellType,
    DiagonalType,
    create_unit_square,
    locate_entities_boundary,
)

from matplotlib import pyplot, tri as tri_plot


"""
Based on:
https://bleyerj.github.io/comet-fenicsx/intro/plates/plates.html
https://jsdokken.com/dolfinx-tutorial/chapter2/linearelasticity_code.html
"""
# number of elements per edge
N = 20

domain = create_unit_square(
    MPI.COMM_WORLD, N, N, cell_type=CellType.triangle, diagonal=DiagonalType.crossed
)

# material parameters
E = 31.45e9
nu = 0.2
kappa = 5.0 / 6.0
thick = 0.05

# bending stiffness
D = fem.Constant(domain, E * thick**3 / (1 - nu**2) / 12.0)
# shear stiffness
F = fem.Constant(domain, E / 2 / (1 + nu) * thick * 5.0 / 6.0)

# uniform transversal load
f = fem.Constant(domain, -1e5)


# Useful function for defining strains and stresses
def curvature(u):
    (w, theta) = ufl.split(u)
    return ufl.as_vector(
        [theta[0].dx(0), theta[1].dx(1), theta[0].dx(1) + theta[1].dx(0)]
    )


def shear_strain(u):
    (w, theta) = ufl.split(u)
    return ufl.grad(w) - theta


def bending_moment(u):
    DD = ufl.as_matrix([[D, nu * D, 0], [nu * D, D, 0], [0, 0, D * (1 - nu) / 2.0]])
    return ufl.dot(DD, curvature(u))


def shear_force(u):
    return F * shear_strain(u)


# Definition of function space for U:displacement, T:rotation
Ue = basix.ufl.element("P", domain.basix_cell(), 2)  # quadratic shape functions
Te = basix.ufl.element("P", domain.basix_cell(), 1, shape=(2,))  # linear shape functions
V = fem.functionspace(domain, basix.ufl.mixed_element([Ue, Te]))

# Functions
u = fem.Function(V, name="Unknown")
u_ = ufl.TestFunction(V)
(w_, theta_) = ufl.split(u_)
du = ufl.TrialFunction(V)

# Linear and bilinear forms
dx = ufl.Measure("dx", domain=domain)
L = f * w_ * dx
a = (
    ufl.dot(bending_moment(u_), curvature(du))
    + ufl.dot(shear_force(u_), shear_strain(du))
) * dx


# Boundary of the plate
# Find all edges of unit square plate
def border(x):
    return np.logical_or(
        np.logical_or(np.isclose(x[0], 0), np.isclose(x[0], 1)),
        np.logical_or(np.isclose(x[1], 0), np.isclose(x[1], 1)),
    )


# fix displacement on the border
facet_dim = 1
sliding_facets = locate_entities_boundary(domain, facet_dim, border)
sliding_dofs = fem.locate_dofs_topological(V.sub(0), facet_dim, sliding_facets)

u0 = fem.Function(V)
bcs = [fem.dirichletbc(u0, sliding_dofs)]

# Solve the problem
problem = fem.petsc.LinearProblem(
    a, L, u=u, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"}
)
problem.solve()

# Get deflections
w_fun_space, w_idx = V.sub(0).collapse()
w_values = u.x.array[w_idx]

# Get stresses
bending_stress = bending_moment(u) * 6 / thick**2

# Create a function space for stress using a vector element with linear shape functions
V_sigma = fem.functionspace(
    domain,
    basix.ufl.element("Lagrange", basix.CellType.triangle, 1, shape=(3,)),
)
# Define the stress expression
stress_expr = fem.Expression(bending_stress, V_sigma.element.interpolation_points())

# Create the function to store interpolated stresses
stresses = fem.Function(V_sigma)
stresses.interpolate(stress_expr)

# get functionspace and indices of stress corresponding to sigma_x
fs_x, idx_x = V_sigma.sub(0).collapse()

### generate figure with matplotlib
nodes = domain.geometry.x
triangles = domain.geometry.dofmap
stress_x = stresses.x.array[idx_x] / 1e6  # show stress in MPa

# set the colormap 
pyplot.set_cmap("jet")
fig, ax = pyplot.subplots()
triangulation = tri_plot.Triangulation(nodes[:, 0], nodes[:, 1], triangles)

levels = np.linspace(min(stress_x), max(stress_x), 16)

contour = ax.tricontourf(
    triangulation,
    stress_x,
    levels=levels,
)

fig.colorbar(contour, ticks=levels)

# show mesh
ax.triplot(
     triangulation, color="black", marker=" ", linestyle="-", linewidth=0.1
 )

ax.axis("equal")
ax.grid(linewidth=1)

pyplot.show()

Thank you for your help

You are interpolating a discontinuous function, i.e.

into a continuous space.

This will cause some interpolation error which can be manifested in non-symmetry.

Talking about smoothness, as the bending moment only depends on theta, which is a piecewise linear, the best you will get is a piecewise constant function.

Here using paraview to illustrate this:

Upon mesh refinement, the stresses gets better, in the sense that the jumps between each element becomes smaller: (40x40)

80x80

Script to reproduce the bp file can be found below:

import numpy as np
import ufl
import basix

from mpi4py import MPI
from dolfinx import fem

from dolfinx.fem.petsc import LinearProblem
from dolfinx.mesh import (
    CellType,
    DiagonalType,
    create_unit_square,
    locate_entities_boundary,
)

# from matplotlib import pyplot, tri as tri_plot


"""
Based on:
https://bleyerj.github.io/comet-fenicsx/intro/plates/plates.html
https://jsdokken.com/dolfinx-tutorial/chapter2/linearelasticity_code.html
"""
# number of elements per edge
N = 80

domain = create_unit_square(
    MPI.COMM_WORLD, N, N, cell_type=CellType.triangle, diagonal=DiagonalType.crossed
)

# material parameters
E = 31.45e9
nu = 0.2
kappa = 5.0 / 6.0
thick = 0.05

# bending stiffness
D = fem.Constant(domain, E * thick**3 / (1 - nu**2) / 12.0)
# shear stiffness
F = fem.Constant(domain, E / 2 / (1 + nu) * thick * 5.0 / 6.0)

# uniform transversal load
f = fem.Constant(domain, -1e5)


# Useful function for defining strains and stresses
def curvature(u):
    (w, theta) = ufl.split(u)
    return ufl.as_vector(
        [theta[0].dx(0), theta[1].dx(1), theta[0].dx(1) + theta[1].dx(0)]
    )


def shear_strain(u):
    (w, theta) = ufl.split(u)
    return ufl.grad(w) - theta


def bending_moment(u):
    DD = ufl.as_matrix([[D, nu * D, 0], [nu * D, D, 0], [0, 0, D * (1 - nu) / 2.0]])
    return ufl.dot(DD, curvature(u))


def shear_force(u):
    return F * shear_strain(u)


# Definition of function space for U:displacement, T:rotation
Ue = basix.ufl.element("P", domain.basix_cell(), 2)  # quadratic shape functions
Te = basix.ufl.element(
    "P", domain.basix_cell(), 1, shape=(2,)
)  # linear shape functions
V = fem.functionspace(domain, basix.ufl.mixed_element([Ue, Te]))

# Functions
u = fem.Function(V, name="Unknown")
u_ = ufl.TestFunction(V)
(w_, theta_) = ufl.split(u_)
du = ufl.TrialFunction(V)

# Linear and bilinear forms
dx = ufl.Measure("dx", domain=domain)
L = f * w_ * dx
a = (
    ufl.dot(bending_moment(u_), curvature(du))
    + ufl.dot(shear_force(u_), shear_strain(du))
) * dx


# Boundary of the plate
# Find all edges of unit square plate
def border(x):
    return np.logical_or(
        np.logical_or(np.isclose(x[0], 0), np.isclose(x[0], 1)),
        np.logical_or(np.isclose(x[1], 0), np.isclose(x[1], 1)),
    )


# fix displacement on the border
facet_dim = 1
sliding_facets = locate_entities_boundary(domain, facet_dim, border)
sliding_dofs = fem.locate_dofs_topological(V.sub(0), facet_dim, sliding_facets)

u0 = fem.Function(V)
bcs = [fem.dirichletbc(u0, sliding_dofs)]

# Solve the problem
problem = fem.petsc.LinearProblem(
    a, L, u=u, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"}
)
problem.solve()

# Get deflections
w_fun_space, w_idx = V.sub(0).collapse()
w_values = u.x.array[w_idx]

# Get stresses
bending_stress = bending_moment(u) * 6 / thick**2

# Create a function space for stress using a vector element with linear shape functions
V_sigma = fem.functionspace(
    domain,
    basix.ufl.element(
        "Lagrange", basix.CellType.triangle, 1, shape=(3,), discontinuous=True
    ),
)
# Define the stress expression
stress_expr = fem.Expression(bending_stress, V_sigma.element.interpolation_points())

# Create the function to store interpolated stresses
stresses = fem.Function(V_sigma)
stresses.interpolate(stress_expr)

# # get functionspace and indices of stress corresponding to sigma_x
# fs_x, idx_x = V_sigma.sub(0).collapse()
fs_x = stresses.sub(0).collapse()
import dolfinx.io

with dolfinx.io.VTXWriter(domain.comm, "stress.bp", [stresses]) as vtx:
    vtx.write(0.0)

1 Like