This page was generated from utils/firedrake/webgui.ipynb.
Firedrake visualisation with webgui_jupyter_widgets
ngsPETSc.utils.firedrake_webgui exposes a Draw helper and a FiredrakeScene class that render Firedrake meshes and functions in Jupyter notebooks via webgui_jupyter_widgets. The same object covers
2D triangular and 3D tetrahedral meshes,
scalar and vector function spaces (Lagrange, DG, Raviart-Thomas, Nedelec, …),
high polynomial order (up to degree 10) via per-cell sub-triangulation,
live updates for time-dependent simulations and ipywidgets sliders.
Install the optional dependencies once:
pip install webgui_jupyter_widgets ipywidgets
[1]:
from firedrake import *
from ngsPETSc import Draw, FiredrakeScene
firedrake:WARNING OMP_NUM_THREADS is not set or is set to a value greater than 1, we suggest setting OMP_NUM_THREADS=1 to improve performance
1. Meshes
Draw accepts a MeshGeometry directly and produces an interactive 3D widget. Pass subdivision=k to refine the rendering of curved elements.
[2]:
mesh2d = UnitSquareMesh(8, 8)
Draw(mesh2d);
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[2], line 2
1 mesh2d = UnitSquareMesh(8, 8)
----> 2 Draw(mesh2d);
File ~/Devel/fd/release-real/src/ngsPETSc/ngsPETSc/utils/firedrake_webgui.py:647, in Draw(obj, mesh, subdivision, colormap, **kwargs)
644 height = kwargs.pop("height", None)
645 scene = FiredrakeScene(obj, mesh=mesh, subdivision=subdivision,
646 colormap=colormap, **kwargs)
--> 647 scene.Draw(width=width, height=height)
648 return scene
File ~/Devel/fd/release-real/lib/python3.14/site-packages/webgui_jupyter_widgets/widget.py:76, in BaseWebGuiScene.Draw(self, width, height)
74 self.widget = WebGuiWidget(layout=layout)
75 self.encoding='binary'
---> 76 self.widget.value = self.GetData()
77 display(self.widget)
File ~/Devel/fd/release-real/src/ngsPETSc/ngsPETSc/utils/firedrake_webgui.py:576, in FiredrakeScene.GetData(self, set_minmax)
573 tdim = mesh.topological_dimension
575 if tdim == 2:
--> 576 verts3d, tris, vals, funcdim, segs_enc = _build_2d(
577 mesh, func, n, self.encoding)
578 elif tdim == 3:
579 verts3d, tris, vals, funcdim, segs_enc = _build_3d_boundary(
580 mesh, func, n, self.encoding)
File ~/Devel/fd/release-real/src/ngsPETSc/ngsPETSc/utils/firedrake_webgui.py:290, in _build_2d(mesh, func, n, encoding)
288 def _build_2d(mesh, func, n, encoding):
289 """Build vertex / triangle / value arrays for a 2D mesh."""
--> 290 ref_pts = np.arrary(get_intrules(2,n))
291 npts = ref_pts.shape[0]
293 cell_coords = _per_cell_coords(mesh, ref_pts) # (ncells, npts, gdim)
File ~/Devel/fd/release-real/lib/python3.14/site-packages/numpy/__init__.py:792, in __getattr__(attr)
789 import numpy.char as char
790 return char.chararray
--> 792 raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
AttributeError: module 'numpy' has no attribute 'arrary'
[3]:
mesh3d = UnitCubeMesh(6, 6, 6)
Draw(mesh3d);
2. Scalar functions, including high-order CG
When drawing a Function, the rendering is automatically sub-triangulated to the polynomial degree of the source space (capped at 10). The override subdivision= is also available.
[4]:
x, y = SpatialCoordinate(mesh2d)
for k in (1, 4, 10):
V = FunctionSpace(mesh2d, "CG", k)
f = Function(V).interpolate(sin(k*pi*x) * cos(k*pi*y))
print(f"CG degree {k}")
Draw(f, colormap="mana")
CG degree 1
CG degree 4
CG degree 10
DG works just as well; cell discontinuities are preserved because each cell is sub-triangulated independently.
[6]:
DG = FunctionSpace(mesh2d, "DG", 3)
fdg = Function(DG).interpolate(conditional(x > 0.5, 1.0, -1.0) * y)
Draw(fdg, colormap="cool_to_warm");
3. Vector spaces: CG, RT, Nedelec
For H(div) and H(curl) elements the scene interpolates into a vector CG space of matching degree. The colour map is the field magnitude.
[7]:
RT = FunctionSpace(mesh2d, "RT", 2)
g = Function(RT).project(as_vector([sin(pi*x), cos(pi*y)]))
Draw(g);
[8]:
N = FunctionSpace(mesh2d, "N1curl", 2)
h = Function(N).project(as_vector([-y, x]))
Draw(h);
4. 3D scalar and vector functions
Only the boundary surface is rendered for tet meshes; the interior is implied by the mesh wireframe.
[9]:
X, Y, Z = SpatialCoordinate(mesh3d)
V3 = FunctionSpace(mesh3d, "CG", 4)
f3 = Function(V3).interpolate(sin(2*pi*X)*cos(2*pi*Y)*Z)
Draw(f3);
[9]:
RT3 = FunctionSpace(mesh3d, "RT", 1)
g3 = Function(RT3).project(as_vector([X - 0.5, Y - 0.5, Z - 0.5]))
Draw(g3);
5. Time-dependent simulation: heat equation
Create the scene once, then mutate the Function and call scene.Redraw() each step to push fresh data into the existing widget.
[10]:
import time
mesh = UnitSquareMesh(32, 32)
V = FunctionSpace(mesh, "CG", 1)
u = Function(V, name="u")
u_old = Function(V)
x, y = SpatialCoordinate(mesh)
u_old.interpolate(exp(-50*((x - 0.5)**2 + (y - 0.5)**2)))
u.assign(u_old)
v = TestFunction(V)
trial = TrialFunction(V)
dt = 1e-3
a = trial*v*dx + dt*dot(grad(trial), grad(v))*dx
L = u_old*v*dx
bc = DirichletBC(V, 0.0, "on_boundary")
problem = LinearVariationalProblem(a, L, u, bcs=bc)
solver = LinearVariationalSolver(problem)
scene = Draw(u)
for step in range(80):
solver.solve()
u_old.assign(u)
scene.Redraw()
time.sleep(0.02)
6. Live parameter sliders
ipywidgets sliders pair naturally with Redraw: bind the slider’s observe callback to a function that recomputes u and calls scene.Redraw().
[11]:
import ipywidgets as widgets
mesh = UnitSquareMesh(40, 40)
V = FunctionSpace(mesh, "CG", 2)
u = Function(V, name="u")
x, y = SpatialCoordinate(mesh)
def update(k_x=2.0, k_y=2.0, decay=10.0):
u.interpolate(exp(-decay*((x - 0.5)**2 + (y - 0.5)**2))
* sin(k_x*pi*x) * cos(k_y*pi*y))
scene.Redraw()
update()
scene = Draw(u)
widgets.interact(update,
k_x=widgets.FloatSlider(min=0.5, max=6.0, step=0.1, value=2.0),
k_y=widgets.FloatSlider(min=0.5, max=6.0, step=0.1, value=2.0),
decay=widgets.FloatSlider(min=1.0, max=40.0, step=0.5, value=10.0));
Sliding any of the controls re-runs update, which re-interpolates u and pushes the new field to the existing widget without recreating it.