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

Thank you for the explanation.
That make sense, I thought of changing linear shape function of rotation degrees of freedom to the quadratic. Namely:

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

That results in symmetric distribution of the stresses; however, there are artifacts close to the boundary conditions.


Is there a way to get rid of them, or at least improve the interpolation close to the edges?

What spaces are you using for the output stresses? And how does it look upon refinement in Paraview (Rather than matplotlib, that does some weird smoothing).

Wow, that is immediate answer.

To output stresses I am using a space with linear shape functions:

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

The same figure of x stresses in Paraview looks like that:

I am still concerned about results close to the edges and corners

Below I am pasting the entire code:

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("Lagrange", domain.basix_cell(), 2)  # quadratic shape functions
Te = basix.ufl.element(
    "Lagrange", domain.basix_cell(), 2, shape=(2,)
)  # quadratic 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)

import dolfinx.io

with dolfinx.io.VTXWriter(domain.comm, "src/.img_dump/tmp/str_quad.bp", [stresses]) as vtx:
    vtx.write(0.0)

pyplot.show()

Again, use discontinuous elements to represent the stresses, as I did above:

Ok,

I am sorry, I misunderstood the first answer.

I appreciate your time in this matter.