Copyright (C) 2021 Jørgen S. Dokken
This file is part of DOLFINx (https://www.fenicsproject.org)
SPDX-License-Identifier: LGPL-3.0-or-later
Using pyvista for visualization¶
import dolfinx
import dolfinx.io
import dolfinx.plot
import numpy as np
import ufl
from mpi4py import MPI
try:
import pyvista
except ModuleNotFoundError:
print("pyvista is required for this demo")
exit(0)
# If environment variable PYVISTA_OFF_SCREEN is set to true save a png
# otherwise create interactive plot
if pyvista.OFF_SCREEN:
pyvista.start_xvfb(wait=0.1)
# Set some global options for all plots
transparent = False
figsize = 800
pyvista.rcParams["background"] = [0.5, 0.5, 0.5]
Plotting a 3D dolfinx.Function with pyvista¶
# Interpolate a simple scalar function in 3D
def int_u(x):
return x[0] + 3 * x[1] + 5 * x[2]
mesh = dolfinx.UnitCubeMesh(MPI.COMM_WORLD, 4, 3, 5, cell_type=dolfinx.cpp.mesh.CellType.tetrahedron)
V = dolfinx.FunctionSpace(mesh, ("CG", 1))
u = dolfinx.Function(V)
u.interpolate(int_u)
# Extract mesh data from DOLFINx (only plot cells owned by the
# processor) and create a pyvista UnstructuredGrid
num_cells = mesh.topology.index_map(mesh.topology.dim).size_local
cell_entities = np.arange(num_cells, dtype=np.int32)
pyvista_cells, cell_types = dolfinx.plot.create_vtk_topology(mesh, mesh.topology.dim, cell_entities)
grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, mesh.geometry.x)
# Compute the function values at the vertices, this is equivalent to a
# P1 Lagrange interpolation, and can be directly attached to the Pyvista
# mesh. Discard complex value if running DOLFINx with complex PETSc as
# backend
vertex_values = u.compute_point_values()
if np.iscomplexobj(vertex_values):
vertex_values = vertex_values.real
# Create point cloud of vertices, and add the vertex values to the cloud
grid.point_arrays["u"] = vertex_values
grid.set_active_scalars("u")
# Create a pyvista plotter which is used to visualize the output
plotter = pyvista.Plotter()
plotter.add_text("Mesh and corresponding dof values",
position="upper_edge", font_size=14, color="black")
# Some styling arguments for the colorbar
sargs = dict(height=0.6, width=0.1, vertical=True, position_x=0.825, position_y=0.2, fmt="%1.2e",
title_font_size=40, color="black", label_font_size=25)
# Plot the mesh (as a wireframe) with the finite element function
# visualized as the point cloud
plotter.add_mesh(grid, style="wireframe", line_width=2, color="black")
# To be able to visualize the mesh and nodes at the same time, we have
# to copy the grid
plotter.add_mesh(grid.copy(), style="points", render_points_as_spheres=True,
scalars=vertex_values, point_size=10)
plotter.set_position([1.5, 0.5, 4])
# Save as png if we are using a container with no rendering
if pyvista.OFF_SCREEN:
plotter.screenshot("3D_wireframe_with_nodes.png", transparent_background=transparent,
window_size=[figsize, figsize])
else:
plotter.show()
# Create a new plotter, and plot the values as a surface over the mesh
plotter = pyvista.Plotter()
plotter.add_text("Function values over the surface of a mesh",
position="upper_edge", font_size=14, color="black")
# Define some styling arguments for a colorbar
sargs = dict(height=0.1, width=0.8, vertical=False, position_x=0.1,
position_y=0.05, fmt="%1.2e",
title_font_size=40, color="black", label_font_size=25)
# Adjust camera to show the entire mesh
plotter.set_position([-2, -2, 2.1])
plotter.set_focus([1, 1, -0.01])
plotter.set_viewup([0, 0, 1])
# Add mesh with edges
plotter.add_mesh(grid, show_edges=True, scalars="u", scalar_bar_args=sargs)
if pyvista.OFF_SCREEN:
plotter.screenshot("3D_function.png", transparent_background=transparent, window_size=[figsize, figsize])
else:
plotter.show()
Plotting a 2D dolfinx.Function with pyvista using warp by scalar¶
As in the previous section, we interpolate a function into a CG function space
def int_2D(x):
return np.sin(np.pi * x[0]) * np.sin(2 * x[1] * np.pi)
mesh = dolfinx.UnitSquareMesh(MPI.COMM_WORLD, 12, 12, cell_type=dolfinx.cpp.mesh.CellType.quadrilateral)
V = dolfinx.FunctionSpace(mesh, ("Lagrange", 1))
u = dolfinx.Function(V)
u.interpolate(int_2D)
# As in the previous section, we extract the geometry and topology of
# the mesh, and attach values to the vertices
num_cells = mesh.topology.index_map(mesh.topology.dim).size_local
cells = np.arange(num_cells, dtype=np.int32)
pyvista_cells, cell_types = dolfinx.plot.create_vtk_topology(mesh, mesh.topology.dim, cells)
grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, mesh.geometry.x)
point_values = u.compute_point_values()
if np.iscomplexobj(point_values):
point_values = point_values.real
grid.point_arrays["u"] = point_values
# We set the function "u" as the active scalar for the mesh, and warp
# the mesh in z-direction by its values
grid.set_active_scalars("u")
warped = grid.warp_by_scalar()
# Plot mesh with scalar bar
plotter = pyvista.Plotter()
plotter.add_text("Warped function", position="upper_edge", font_size=14, color="black")
sargs = dict(height=0.8, width=0.1, vertical=True, position_x=0.05,
position_y=0.05, fmt="%1.2e",
title_font_size=40, color="black", label_font_size=25)
plotter.set_position([-3, 2.6, 0.3])
plotter.set_focus([3, -1, -0.15])
plotter.set_viewup([0, 0, 1])
plotter.add_mesh(warped, show_edges=True, scalar_bar_args=sargs)
if pyvista.OFF_SCREEN:
plotter.screenshot("2D_function_warp.png", transparent_background=transparent, window_size=[figsize, figsize])
else:
plotter.show()
Plotting a dolfinx.Function¶
In the previous sections we have considered CG-1 spaces, which have a 1-1 correspondence with the vertices of the geometry. To be able to plot higher order function spaces, both CG and DG spaces, we have to adjust our plotting technique.
# We start by projecting a discontinuous function into a second order DG
# space Note that we use the `cell_tags` from the previous section to
# restrict the integration domain on the RHS.
dx = ufl.Measure("dx", subdomain_data=cell_tags)
V = dolfinx.FunctionSpace(mesh, ("DG", 2))
uh = dolfinx.Function(V)
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
x = ufl.SpatialCoordinate(mesh)
a = ufl.inner(u, v) * dx
L = ufl.inner(x[0], v) * dx(1) + ufl.inner(0.01, v) * dx(0)
problem = dolfinx.fem.LinearProblem(a, L, u=uh, petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
problem.solve()
# To get a topology that has a 1-1 correspondence with the degrees of
# freedom in the function space, we call
# `dolfinx.plot.create_vtk_topology`. We obtain the geometry for
# the dofs owned on this process by tabulation of the dof coordinates.
topology, cell_types = dolfinx.plot.create_vtk_topology(V)
num_dofs_local = uh.function_space.dofmap.index_map.size_local
geometry = uh.function_space.tabulate_dof_coordinates()[:num_dofs_local]
# We discard the complex values if using PETSc in complex mode
values = uh.vector.array.real if np.iscomplexobj(uh.vector.array) else uh.vector.array
# We create a pyvista mesh from the topology and geometry, and attach
# the coefficients of the degrees of freedom
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
grid.point_arrays["DG"] = values
grid.set_active_scalars("DG")
# We would also like to visualize the underlying mesh and obtain that as
# we have done previously
num_cells = mesh.topology.index_map(mesh.topology.dim).size_local
cell_entities = np.arange(num_cells, dtype=np.int32)
pyvista_cells, cell_types = dolfinx.plot.create_vtk_topology(mesh, mesh.topology.dim, cell_entities)
org_grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, mesh.geometry.x)
# We visualize the data
plotter = pyvista.Plotter()
plotter.add_text("Second order discontinuous elements",
position="upper_edge", font_size=14, color="black")
sargs = dict(height=0.1, width=0.8, vertical=False, position_x=0.1, position_y=0, color="black")
plotter.add_mesh(grid, show_edges=False, scalar_bar_args=sargs, line_width=0)
plotter.add_mesh(org_grid, color="white", style="wireframe", line_width=5)
plotter.add_mesh(grid.copy(), style="points", point_size=15, render_points_as_spheres=True, line_width=0)
plotter.view_xy()
if pyvista.OFF_SCREEN:
plotter.screenshot(f"DG_{MPI.COMM_WORLD.rank}.png",
transparent_background=transparent, window_size=[figsize, figsize])
else:
plotter.show()
Plotting a dolfinx.Function with vector values¶
# In the previous sections, we have considered how to plot scalar valued
# functions. This section will show you how to plot vector valued
# functions. We start by interpolating an expression into a second order
# CG space.
def vel(x):
vals = np.zeros((2, x.shape[1]))
vals[0] = np.sin(x[1])
vals[1] = 0.1 * x[0]
return vals
mesh = dolfinx.UnitSquareMesh(MPI.COMM_WORLD, 6, 6, dolfinx.cpp.mesh.CellType.triangle)
V = dolfinx.VectorFunctionSpace(mesh, ("CG", 2))
uh = dolfinx.Function(V)
uh.interpolate(vel)
# We use the `dolfinx.plot.create_vtk_topology`
# function, as in the previous section. However, we input a set of cell
# entities, which can restrict the plotting to subsets of our mesh
num_cells = mesh.topology.index_map(mesh.topology.dim).size_local
cell_entities = np.arange(num_cells, dtype=np.int32)
topology, cell_types = dolfinx.plot.create_vtk_topology(V, cell_entities)
# As we deal with a vector function space, we need to adjust the values
# in the underlying one dimensional array in dolfinx.Function, by
# reshaping the data, and add an extra column to make it a 3D vector
num_dofs_local = uh.function_space.dofmap.index_map.size_local
geometry = uh.function_space.tabulate_dof_coordinates()[:num_dofs_local]
values = np.zeros((V.dofmap.index_map.size_local, 3), dtype=np.float64)
values[:, :mesh.geometry.dim] = uh.vector.array.real.reshape(V.dofmap.index_map.size_local, V.dofmap.index_map_bs)
# Create a point cloud of glyphs
function_grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
function_grid["vectors"] = values
function_grid.set_active_vectors("vectors")
glyphs = function_grid.glyph(orient="vectors", factor=0.1)
# To add streamlines in 2D, you need to supply the two points creating a
# sampling line
# NOTE: This crashes if ran in parallel, seems to be a pyvista issue
# with pointa, pointb
if MPI.COMM_WORLD.size == 0:
streamlines = function_grid.streamlines(vectors="vectors", return_source=False,
pointa=(0.5, 0.0, 0), pointb=(0.5, 1, 0))
# Create pyvista mesh from the mesh
pyvista_cells, cell_types = dolfinx.plot.create_vtk_topology(mesh, mesh.topology.dim, cell_entities)
grid = pyvista.UnstructuredGrid(pyvista_cells, cell_types, mesh.geometry.x)
# Add mesh, glyphs and streamlines to plotter
plotter = pyvista.Plotter()
plotter.add_text("Second order vector function.",
position="upper_edge", font_size=14, color="black")
sargs = dict(height=0.1, width=0.8, vertical=False, position_x=0.1, position_y=0, color="black")
plotter.add_mesh(grid, show_edges=True, color="black", style="wireframe")
plotter.add_mesh(glyphs)
if MPI.COMM_WORLD.size == 0:
plotter.add_mesh(streamlines.tube(radius=0.001), show_scalar_bar=False)
plotter.view_xy()
if pyvista.OFF_SCREEN:
plotter.screenshot(f"vectors_{MPI.COMM_WORLD.rank}.png",
transparent_background=transparent, window_size=[figsize, figsize])
else:
plotter.show()
Plotting a dolfinx.Function with vector values¶
In this section we illustrate how to visualize streamlines in 3D
def vel(x):
vals = np.zeros((3, x.shape[1]))
vals[0] = -(x[1] - 0.5)
vals[1] = x[0] - 0.5
vals[2] = 0
return vals
mesh = dolfinx.UnitCubeMesh(MPI.COMM_WORLD, 4, 4, 4, dolfinx.cpp.mesh.CellType.hexahedron)
V = dolfinx.VectorFunctionSpace(mesh, ("DG", 2))
uh = dolfinx.Function(V)
uh.interpolate(vel)
num_cells = mesh.topology.index_map(mesh.topology.dim).size_local
cell_entities = np.arange(num_cells, dtype=np.int32)
topology, cell_types = dolfinx.plot.create_vtk_topology(V, cell_entities)
num_dofs_local = uh.function_space.dofmap.index_map.size_local
geometry = uh.function_space.tabulate_dof_coordinates()[:num_dofs_local]
values = np.zeros((V.dofmap.index_map.size_local, 3), dtype=np.float64)
values[:, :mesh.geometry.dim] = uh.vector.array.real.reshape(V.dofmap.index_map.size_local, V.dofmap.index_map_bs)
# Create a point cloud of glyphs
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
grid["vectors"] = values
grid.set_active_vectors("vectors")
glyphs = grid.glyph(orient="vectors", factor=0.1)
streamlines = grid.streamlines(vectors="vectors", return_source=False, source_radius=1, n_points=150)
# Create Create plotter
plotter = pyvista.Plotter()
plotter.add_text("Vector function as streamlines.",
position="upper_edge", font_size=20, color="black")
sargs = dict(height=0.1, width=0.8, vertical=False, position_x=0.1, position_y=0, color="black")
plotter.add_mesh(grid, style="wireframe")
plotter.add_mesh(glyphs)
plotter.add_mesh(streamlines.tube(radius=0.001))
plotter.view_xy()
if pyvista.OFF_SCREEN:
plotter.screenshot(f"streamlines_{MPI.COMM_WORLD.rank}.png",
transparent_background=transparent, window_size=[figsize, figsize])
else:
plotter.show()