Visualizing quadrature functions as point clouds

Visualizing quadrature functions as point clouds#

Author: Henrik N.T. Finsberg

SPDX-License-Identifier: MIT

Quadrature functions are not possible to visualize directly in ParaView, as they are not defined on a mesh. However, we can visualize them as point clouds. In this example we will show how you can use scifem to save your quadrature fuctions as XDMF files, which can be loaded into ParaView for visualization.

Note

This demo requires a backend for writing HDF5 files. Please install scifem with either scifem[adios2] or scifem[h5py].

First, we import the necessary modules.

import logging
from mpi4py import MPI
import numpy as np
import basix
import dolfinx
import scifem
# First initialize logging
logging.basicConfig(level=logging.INFO)

Now let’s create a quadrature function on a unit square mesh. We will use the basix.ufl module to create the quadrature element.

mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10, dolfinx.mesh.CellType.triangle, dtype=np.float64)
el = basix.ufl.quadrature_element(
    scheme="default", degree=3, cell=mesh.basix_cell(), value_shape=()
)
V = dolfinx.fem.functionspace(mesh, el)
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: np.sin(np.pi * x[0]) * np.sin(np.pi * x[1]))

One option that already exists with the current DOLFINx/Pyvista API is to plot them as points

import pyvista
plotter = pyvista.Plotter()
plotter.add_points(
    V.tabulate_dof_coordinates(),
    scalars=u.x.array,
    render_points_as_spheres=True,
    point_size=20,
    show_scalar_bar=False,
)
if not pyvista.OFF_SCREEN:
    plotter.show()
2026-08-24 20:16:53.500 (   0.719s) [    7F0B6BCD03C0]vtkXOpenGLRenderWindow.:1460  WARN| bad X server connection. DISPLAY=:99.0
INFO:trame_server.utils.namespace:Translator(prefix=None)
INFO:wslink.backends.aiohttp:awaiting runner setup
INFO:wslink.backends.aiohttp:awaiting site startup
INFO:wslink.backends.aiohttp:Print WSLINK_READY_MSG
INFO:wslink.backends.aiohttp:Schedule auto shutdown with timeout 0
INFO:wslink.backends.aiohttp:awaiting running future

Using scifem, we can write the point cloud data to an XDMFFile that can be opened with Paraview.

with scifem.xdmf.XDMFFile("point_cloud.xdmf", [u]) as xdmf:
    xdmf.write(0.0)
/dolfinx-env/lib/python3.14/site-packages/scifem/xdmf.py:420: UserWarning: ADIOS2 not available, using h5py
  warnings.warn(msg)
WARNING:root:ADIOS2 not available, using h5py
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[6], line 1
----> 1 with scifem.xdmf.XDMFFile("point_cloud.xdmf", [u]) as xdmf:
      2     xdmf.write(0.0)

File /dolfinx-env/lib/python3.14/site-packages/scifem/xdmf.py:654, in XDMFFile.__init__(self, filename, functions, filemode, backend)
    651     raise ValueError("All functions must be in the same function space")
    652 self._data = data
--> 654 self._init_backend()

File /dolfinx-env/lib/python3.14/site-packages/scifem/xdmf.py:424, in BaseXDMFFile._init_backend(self)
    422         self.backend = "h5py"
    423 if self.backend == "h5py":
--> 424     self._init_h5py()

File /dolfinx-env/lib/python3.14/site-packages/scifem/xdmf.py:430, in BaseXDMFFile._init_h5py(self)
    426 def _init_h5py(self) -> None:
    427     logger.debug("Initializing h5py")
    428     self._outfile = h5pyfile(
    429         h5name=self.h5name, filemode=self.filemode, comm=self._data.comm
--> 430     ).__enter__()
    431     self._step = self._outfile.create_group(np.bytes_("Step0"))
    432     points = self._step.create_dataset(
    433         "Points",
    434         (self._data.num_dofs_global, self._data.points.shape[1]),
    435         dtype=self._data.points.dtype,
    436     )

File /usr/lib/python3.14/contextlib.py:141, in _GeneratorContextManager.__enter__(self)
    139 del self.args, self.kwds, self.func
    140 try:
--> 141     return next(self.gen)
    142 except StopIteration:
    143     raise RuntimeError("generator didn't yield") from None

File /dolfinx-env/lib/python3.14/site-packages/scifem/xdmf.py:148, in h5pyfile(h5name, filemode, force_serial, comm)
    137 @contextlib.contextmanager
    138 def h5pyfile(h5name, filemode="r", force_serial: bool = False, comm=None):
    139     """Context manager for opening an HDF5 file with h5py.
    140 
    141     Args:
   (...)    146 
    147     """
--> 148     import h5py
    150     if comm is None:
    151         comm = MPI.COMM_WORLD

ModuleNotFoundError: No module named 'h5py'

The point cloud can now be loaded into ParaView for visualization, by selecting “Point Gaussian” as the representation. Point cloud in ParaView

We can write any dolfinx.fem.FunctionSpace that has support for tabulate_dof_coordinates to a point cloud. For example, we can create a higher order Lagrange space, and write two functions to file.

Q = dolfinx.fem.functionspace(mesh, ("Lagrange", 3, (2, )))
q_1 = dolfinx.fem.Function(Q, name="q_sin")
q_1.interpolate(lambda x: (np.sin(np.pi * x[0]),  np.sin(np.pi * x[1])))
q_2 = dolfinx.fem.Function(Q, name="q_cos")
q_2.interpolate(lambda x: (np.cos(np.pi * x[0]),  np.cos(np.pi * x[1])))

We write these two functions to file as illustrated above

with scifem.xdmf.XDMFFile("point_cloud_lagrange.xdmf", [q_1, q_2]) as xdmf:
    xdmf.write(0.0)

yielding the following point clouds in ParaView after applying glyphs. Point cloud in ParaView

If you have time dependent data, you can write multiple time steps to the same file using e.g

with scifem.xdmf.XDMFFile("point_cloud_lagrange.xdmf", [q_1, q_2]) as xdmf:
    xdmf.write(0.0)
    q_1.interpolate(lambda x: (-x[0], x[1]))
    q_2.interpolate(lambda x: (x[0], -x[1]))
    xdmf.write(0.3)

If you need to keep the file open for longer, you can use the following syntax

xdmf = scifem.xdmf.XDMFFile("point_cloud_lagrange.xdmf", [q_1, q_2])
xdmf.write(0.0)
q_1.interpolate(lambda x: (-x[0], x[1]))
q_2.interpolate(lambda x: (x[0], -x[1]))
xdmf.write(0.3)
xdmf.close()