Visual representation (interpolation) of a 2nd order tensor in a 3D space (recipe)

I have seen that others have tried to do this. So I thought that I could share it

# Copyright 2023 edgar (fenicsproject.discourse.group)
# License: GNU General Public License version 3
# https://www.gnu.org/licenses/gpl-3.0.en.html
#
# Partially based and adapted from https://jsdokken.com/dolfinx-tutorial/
# which holds a Creative Commons Attribution 4.0 International License
# http://creativecommons.org/licenses/by/4.0/
import numpy as np
import pyvista
from mpi4py import MPI
from dolfinx import mesh
from dolfinx import fem
import ufl
from dolfinx.fem import FunctionSpace
from petsc4py.PETSc import ScalarType
from dolfinx import plot
from dolfinx import io
import dolfinx.fem.petsc as petsc


def clamped_boundary(x):
    return np.isclose(x[0], 0)


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


def sigma(u, lambda_, mu):
    return (lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u))
            + 2 * mu * epsilon(u))


# Scaled variables
L = 1
W = 0.2
mu = 1
rho = 1
delta = W / L
gamma = 0.4 * delta**2
beta = 1.25
lambda_ = beta
g = gamma

box_vertex1 = np.array([0, 0, 0])
box_vertex2 = np.array([L, W, W])
ncells = [20, 6, 6]

domain = mesh.create_box(
    MPI.COMM_WORLD, [box_vertex1, box_vertex2],
    ncells, cell_type=mesh.CellType.hexahedron)

fe_type = "Lagrange"
fe_degree = 1
gdim = domain.geometry.dim
V = FunctionSpace(domain, (fe_type, fe_degree, (gdim,)))

fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(
    domain, fdim, clamped_boundary)

u_D = np.array([0, 0, 0], dtype=ScalarType)
boundary_dof = fem.locate_dofs_topological(
    V, fdim, boundary_facets)
bc = fem.dirichletbc(u_D, boundary_dof, V)

T = fem.Constant(domain, ScalarType((0, 0, 0)))

ds = ufl.Measure("ds", domain=domain)

u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = fem.Constant(domain, ScalarType((0, 0, -rho * g)))
a = ufl.inner(sigma(u, lambda_, mu), epsilon(v)) * ufl.dx
L = ufl.dot(f, v) * ufl.dx + ufl.dot(T, v) * ds

problem = petsc.LinearProblem(
    a, L, bcs=[bc],
    petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh = problem.solve()

topology, cell_types, geometry = plot.vtk_mesh(V)
vtu = pyvista.UnstructuredGrid(
  topology, cell_types, geometry)

vtu["u"] = uh.x.array.reshape((geometry.shape[0], 3))
warped = vtu.warp_by_vector("u", factor=1.5)

pvpl = pyvista.Plotter()
undef_actor = pvpl.add_mesh(vtu, style="wireframe", color="k")
warped_actor = pvpl.add_mesh(warped, show_edges=True, scalars="u")
# https://tutorial.pyvista.org/tutorial/04_filters/solutions/e_glyphs.html
arrows = vtu.glyph(
    scale=True, orient=True, tolerance=0.05, factor=0.25)
pvpl.add_mesh(arrows, show_scalar_bar=False)
pvpl.show_axes()
pvpl.save_graphic("Media/dolfinx_tuto_linear_elasticity.svg")
pvpl.show()

sigma_dev = sigma(uh, lambda_, mu) - (
    1 / 3 * ufl.tr(sigma(uh, lambda_, mu))
    * ufl.Identity(len(uh)))
sigma_vm = ufl.sqrt(3 / 2 * ufl.inner(sigma_dev, sigma_dev))

V_vm = fem.FunctionSpace(
    domain, ("Discontinuous Lagrange", 0))
sigma_vm_expr = fem.Expression(
    sigma_vm, V_vm.element.interpolation_points())
sigma_vm_fun = fem.Function(V_vm)
sigma_vm_fun.interpolate(sigma_vm_expr)

warped.cell_data["sigma_vm"] = sigma_vm_fun.vector.array
warped.set_active_scalars("sigma_vm")
pvpl = pyvista.Plotter()
pvpl.add_actor(undef_actor)
pvpl.add_mesh(warped, scalars="sigma_vm")
pvpl.show_axes()
pvpl.save_graphic("Media/dolfinx_tuto_linear_elasticity_sigma_fun_vm.svg")
pvpl.show()

tens2_dim = (domain.geometry.dim,) * 2
interp_ord = 1
fe_type = "Lagrange"
CG_lin_2ord = fem.FunctionSpace(domain, (fe_type, interp_ord, tens2_dim))

sigma_expr = fem.Expression(
    sigma(uh, lambda_, mu), CG_lin_2ord.element.interpolation_points())
sigma_fun = fem.Function(CG_lin_2ord)
sigma_fun.interpolate(sigma_expr)

vtu["sigma"] = sigma_fun.x.array.reshape((geometry.shape[0], 3, 3))
vtu.set_active_tensors("sigma")
# warped["sigma"] = vtu.warp_by_vector("u", factor=1.5)
warped["sigma"] = vtu["sigma"]

# https://docs.pyvista.org/version/stable/examples/02-plot/multi-window.html
pyvista.global_theme.multi_rendering_splitting_position = 0.60
pvpl = pyvista.Plotter(shape="1|2")
pvpl.subplot(0)
pvpl.add_actor(undef_actor)
pvpl.add_mesh(warped, scalars="sigma",
              scalar_bar_args={"title": "stress norm"})
pvpl.show_axes()
pvpl.subplot(1)
pvpl.add_mesh(warped, scalars="sigma", component=0,
              scalar_bar_args={"title": "stress xx"})
pvpl.subplot(2)
pvpl.add_mesh(warped, scalars="sigma", component=8,
              scalar_bar_args={"title": "stress zz"})
pvpl.save_graphic("Media/dolfinx_tuto_linear_elasticity_sigma_fun.svg")
pvpl.show()

Thanks for FEniCS! Thanks to @dokken, @nate, @jackhale, @dparsons parsons for paying attention, sharing your time and helping everyone here.

  • FEniCSx software
    dolfinx: 0.7.0.dev0_r27554.cfeffe0-1
    basix: 0.7.0.dev0_r945.1117a8d-1
    ufl: 2023.2.0.dev0_r3562.77ae57c-1
    ffcx: 0.7.0.dev0_r7077.1d27238-1

  • Dependencies
    python: 3.11.5-2
    python-numpy: 1.26.0-1
    petsc: 3.19.5.1171.g37df9106526-1
    hdf5-openmpi: 1.14.2-1
    boost: 1.83.0-2
    adios2: 2.8.3-5
    scotch: 7.0.4-1
    pybind11: 2.11.1-1
    python-build: 1.0.1-1
    python-cffi: 1.15.1-4
    python-cppimport: 22.08.02.r6.g0849d17-1
    python-installer: 0.7.0-3
    python-mpi4py: 3.1.4-3
    python-pytest: 7.4.2-1
    python-scikit-build: 0.17.6-1
    python-setuptools: 1:68.0.0-1
    python-wheel: 0.40.0-3
    xtensor: 0.24.0-2
    xtensor-blas: 0.20.0-1

  • Operating system
    Arch Linux: 6.5.4-arch2-1

1 Like

1 Like