Skip to main content
Ctrl+K

Volumential

  • Getting started
  • Examples
  • User guide
  • Design notes
  • Benchmarks
    • API Reference
    • Development
    • Changelog
  • GitHub
  • Getting started
  • Examples
  • User guide
  • Design notes
  • Benchmarks
  • API Reference
  • Development
  • Changelog
  • GitHub

Section Navigation

  • Contributing
  • Tests and markers
  • CI and the review bots
  • Release and versioning
  • Gallery assets
  • AI-assisted development
  • Development
  • Gallery assets

Gallery assets#

The documentation treats figures as reproducible evidence rather than as decorative screenshots. Numerical gallery images come from maintained examples through doc/tools/render_gallery.py; Sphinx never executes those examples and never calls the renderer. A documentation build only consumes the files committed under doc/source/gallery/.

That directory holds two kinds of file. The computed figures and their manifest.json are written by the renderer, one subdirectory per target. The schematics (near-far-anatomy.svg, volume-fmm-workflow.svg) are drawn by hand and edited in place; nothing generates them, and every page that shows one says it is a schematic.

Regenerating#

The renderer needs the normal Volumential/OpenCL environment plus matplotlib, and an explicit PyOpenCL context selector, given either as PYOPENCL_CTX or as --pyopencl-ctx:

export PYOPENCL_CTX=portable:0
uv run --with matplotlib python doc/tools/render_gallery.py laplace2d
uv run --with matplotlib python doc/tools/render_gallery.py laplace2d-adaptive
uv run --with matplotlib python doc/tools/render_gallery.py poisson3d
uv run --with matplotlib python doc/tools/render_gallery.py branched-flow

Without a selector the renderer stops instead of letting PyOpenCL pick a device. It also removes PYOPENCL_TEST from the examples’ environment, because pyopencl.create_some_context prefers that variable over PYOPENCL_CTX, and it runs the examples with standard input closed, so PyOpenCL cannot prompt for a choice either. Every inherited VOLUMENTIAL_* variable is removed as well: some of them change a computation (poisson3d.py reads its resolution from VOLUMENTIAL_POISSON3D_*, the library reads cache and build switches), and a render is defined by the renderer’s own settings alone.

Smoke settings are the default. They run in seconds and show that the figure path works, but they do not resolve the problems: the smoke runs of laplace2d, laplace2d-adaptive and poisson3d have errors of order one, and the branched-flow smoke domain is little more than a wavelength across, too short for branches to form. --full switches every target to its example’s full settings, and all runs every target. Committed figures are full-settings renders from one command:

uv run --with matplotlib python doc/tools/render_gallery.py all --full --pyopencl-ctx portable:0

At full settings branched-flow is the example’s publication-pilot configuration, about a million unknowns with the fmmlib far-field backend, so it needs pyfmmlib as branched_flow_helmholtz2d.py describes, and it dominates the run. poisson3d builds a 3-D near-field table the first time it runs.

Target

Producer

Figures copied into the gallery

laplace2d

examples/laplace2d.py via VOLUMENTIAL_GALLERY_OUTPUT_DIR

laplace2d_overview.svg, laplace2d_tree.svg

laplace2d-adaptive

examples/laplace2d_adaptive.py via VOLUMENTIAL_GALLERY_OUTPUT_DIR

laplace2d_adaptive.svg

poisson3d

examples/poisson3d.py via VOLUMENTIAL_POISSON3D_OUTPUT_DIR

poisson3d_slices.png

branched-flow

examples/branched_flow_helmholtz2d.py --output-dir ...

branched_flow.png

Before any example runs, the renderer imports the packages the examples import, in the examples’ interpreter, to record their versions, and it stops if volumential would come from anywhere but this checkout. Then, for each target, it

  1. deletes the files that the previous run recorded for the target in the manifest, together with the figures it is about to write;

  2. runs the example with PYOPENCL_CTX, PYTHONHASHSEED=0, MPLBACKEND=Agg, an empty Matplotlib configuration directory of its own (MPLCONFIGDIR under the work directory, MATPLOTLIBRC removed, so a personal matplotlibrc cannot restyle a figure; the renderer refuses to run if a matplotlibrc has appeared in its work directories) and the smoke setting in its environment, and with build/gallery-work/<mode>/<target>/ as its working and output directory. Git ignores that directory. Everything the example writes lands there: data files, interactive HTML, figures the gallery does not use, and the near-field tables it caches, so each mode and target keeps its own tables and none of them is read from or written to the repository root;

  3. fails if any expected figure is missing, and otherwise copies only the figures into doc/source/gallery/<target>/;

  4. records the run in doc/source/gallery/manifest.json.

A table cached under build/gallery-work/ is reused by later renders in the same mode. Delete the directory to rebuild the tables; a fresh clone, which is where committed figures come from, always builds them.

--output-dir and --work-dir move the gallery root and the scratch directory, for example to render a preview outside the documentation tree. *.png is ignored repository-wide; doc/source/gallery/ is exempt, so the PNG figures of poisson3d and branched-flow can be committed there.

The gallery sits outside html_static_path on purpose. Sphinx copies every image a page shows into _images/, so a gallery under _static/ would be published twice. manifest.json is therefore not part of the built site; read it in the repository.

The manifest#

manifest.json has one entry per target under targets, so regenerating one target leaves the records of the others in place. An entry holds

Field

Meaning

revision

the commit checked out when the renderer ran

dirty

whether tracked files differed from that commit; untracked files and the files the renderer writes (every target’s figures and the manifest) are not counted, but a changed schematic is

mode

smoke or full

pyopencl_ctx

the context selector string passed to the example

device_type

the type of device that selector resolved to in the examples’ environment (CPU or GPU), not its name

regenerate

the renderer invocation that reproduces the entry, including --output-dir and --work-dir when they differ from the defaults (a directory outside the repository appears as <output-dir> or <work-dir>)

command

the example invocation, with the script path relative to the repository root

working_directory

where the example ran

environment

the variables the renderer set for the example; null means it removed the variable

outputs

the copied figures, relative to the manifest

versions

as imported by the examples’ interpreter: volumential, pyopencl, numpy, matplotlib and Python, and the distributions that decide the computed digits, boxtree, sumpy, loopy, pymbolic, modepy, pytential and pyfmmlib (null when not installed); a distribution installed from a Git repository also records the commit it was built from, as in 2024.10 (git <commit>), since the version string of such a build does not identify it

The manifest, not uv.lock, describes the environment a figure was rendered in; the two need not agree.

Paths are repository-relative. A directory outside the repository appears as <output-dir>/... or <work-dir>/..., never as an absolute path. The manifest deliberately carries nothing that identifies a machine: no host or user name, no absolute path, and no device or CPU name. The examples’ own console output does name the device, so logs of a gallery run do not belong in the repository.

Determinism#

Rendering is kept free of incidental variation so that regenerating a figure in the same environment does not churn its bytes:

  • the SVG figures of laplace2d and laplace2d-adaptive have a fixed size, font size and DPI of 150 for their rasterized shaded layers, a fixed SVG hash salt, and no date or creator metadata;

  • the PNG figures of poisson3d and branched-flow are saved at a fixed 150 DPI without the Matplotlib version tag;

  • branched-flow draws its random medium from a fixed seed, and the renderer fixes PYTHONHASHSEED.

A different device, driver or library version can still change the computed numbers and therefore the pixels; the manifest’s selector, device type and versions say which environment produced a committed figure.

CI preview#

The Examples (Smoke) job of CI and the review bots runs

python doc/tools/render_gallery.py laplace2d --output-dir build/gallery --pyopencl-ctx "$PYOPENCL_CTX"
python doc/tools/render_gallery.py laplace2d-adaptive --output-dir build/gallery --pyopencl-ctx "$PYOPENCL_CTX"

and uploads build/gallery/ (the three Laplace figures and their manifest) as the gallery-laplace2d-* artifact, kept for 14 days. The upload is attempted on every outcome, so the artifact exists whenever the renderer wrote files. On a pull request the recorded revision is the merge commit GitHub builds for the branch, not the branch head. The smoke settings are chosen to run quickly, not to resolve the problem, so the preview shows that the figure path works, not how accurate the method is.

Policy#

  • Do not call render_gallery.py from conf.py, a Sphinx extension, or a documentation build hook. A docs build must remain possible without a compute device.

  • Prefer a maintained example over a second docs-only implementation of the same numerical problem.

  • Commit the curated static image that a page uses together with the updated manifest.json. Regenerate committed assets from a clean checkout of a pushed commit, so that dirty is false and the recorded revision can be checked out, and merge the pull request that carries them with a merge commit, not a squash or rebase, so that the revision stays reachable from main.

  • A figure caption names the example, whether smoke or full settings produced it, and the regenerate command. Anything not computed by an example is labeled a schematic.

  • A computed figure compared against a reference says what the reference is. A whole-space solution is a reference for a box integral only where the source outside the box is negligible at the error level shown.

  • Smoke output is appropriate for explaining a mechanism. Do not quote its accuracy or timing as a result; Benchmarks and reproducibility defines the evidence needed for performance claims.

  • Interactive HTML (for example the Poisson 3-D Plotly isosurface) stays in the work directory; it is useful for local inspection, but a page needs a static figure.

examples/laplace2d.py is the reference pattern: setting VOLUMENTIAL_GALLERY_OUTPUT_DIR does not change the computation. After the solve, the example writes two SVG files from the data and tree it has already produced. laplace2d_overview.svg has four panels: the source \(f = -\Delta u\), the computed volume potential \(u_h\), the reference \(u = e^{-\alpha |x|^2}\), and the pointwise error \(|u_h - u|\). Each shows the values at the quadrature nodes, shaded by linear interpolation over a Delaunay triangulation of the nodes. \(u_h\) and \(u\) share one color scale; the error uses a logarithmic scale whose floor is \(\epsilon \max|u|\), with \(\epsilon\) the double-precision machine epsilon, so differences below the rounding of \(u\) are drawn at the floor. The reference is the whole-space solution of \(-\Delta u = f\), while the example integrates \(f\) over the box \([-0.5, 0.5]^2\) only. Outside the box the Gaussian factor is at most \(e^{-40}\) for \(\alpha = 160\), so the difference between the two is at rounding level, but the reference is not the exact value of the box integral. laplace2d_tree.svg shows the tree the FMM traversed, with the quadrature nodes as dots and heavier outlines for coarser levels; the example builds it from the mesh, so its leaves are the mesh cells. The figure titles carry the settings (smoke or full, quadrature order, mesh levels, multipole order, node count) and the maximum error of the run.

examples/laplace2d_adaptive.py follows the same pattern with one figure, written after both of its solves. laplace2d_adaptive.svg has a column for the uniform tree and one for the adaptive tree. The top panels draw the tree’s leaf boxes over the source at that tree’s nodes, on one symmetric logarithmic color scale, which shows both Gaussian terms of \(f\) and the rings where it is negative; the bottom panels show \(|u_h - u|\) at the nodes on one logarithmic scale with the floor described above, so the two trees’ errors can be compared by color. The reference is again the whole-space solution, and outside the box both Gaussian factors are below \(e^{-40}\). The titles carry the settings, each tree’s leaf and node counts and leaf sizes, and both of its errors to two digits.

previous

Release and versioning

next

AI-assisted development

On this page
  • Regenerating
  • The manifest
  • Determinism
  • CI preview
  • Policy
Edit on GitHub
Show Source

© Copyright 2026, Xiaoyu Wei.

Created using Sphinx 9.1.0.

Built with the PyData Sphinx Theme 0.22.0.