Skip to main content

patchwork

Tests Static analysis Docs codecov PyPI Python

Patchwork is a small 2D block-structured adaptive mesh library. It stores cell-centered or node-centered fields on nested rectangular patches using mpiarray. The same API works without MPI installed or launched; under mpiexec, after installing the optional mpi extra, each patch lives on the ranks you give it (one for a small patch, several for a large one) with a communicator of its own, and exchange_halos fills every patch’s halo from its neighbours and the level below, wherever they are. Matplotlib plots make the level layout and each patch’s cell grid visible.

It provides hierarchy construction, patch validation, mpiarray-backed field storage, plotting, and composite linear solves:

  • solve_poisson: cell-centered finite volumes with one constant Dirichlet value on the domain boundary;
  • NodalPoisson: node-centered, the five-point Laplacian wherever the mesh is uniform, with Dirichlet and Neumann faces, periodic axes and embedded bodies at fixed potentials. It is factorized once and solved many times, which is what a particle-in-cell code needs.
  • CellOperator: cell-centered, -div(a grad u) + c u = f with a variable diffusion coefficient and a real or complex reaction (a skin layer, an inductive field), the same faces, periodic axes and bodies, quadratic interpolation along coarse-fine faces (symmetric, or conservative face by face), factorized once; fixed and floating bodies (EmbeddedFloating, one unknown with a given net flux), bodies made of cells (cell_body), and the gradient and curl of a solution from the operator’s own faces.

A hierarchy can also be the axisymmetric r–z plane (AMRHierarchy(geometry="cylindrical")): cells are rings, integral, restrict and conservative regridding weight them by their volumes, and CellOperator takes the curl-curl of an azimuthal component (component="azimuthal"), the operator of a magnetoquasistatic field.

regrid places the patches itself: tag the cells that need finer cells (by a field’s change across a cell, a function of the position, or by hand), and it covers them with properly nested patches (Berger-Rigoutsos clustering) and moves the fields over.

Markers live on the patches too: locate finds the finest patch of each point, owner and migrate put each marker on the rank that holds its cell, and deposit spreads the charge of the markers over the nodes (cloud-in-cell) patch by patch. accumulate_halos then brings it to the composite nodes: the halos of the blocks and the copies of a node in several patches are added to one copy, the one CompositeNodes.from_patches reads, so the charge is that of CompositeNodes.deposit, to round-off. regrid can fill the halos afterwards (fill_halos), keep patches off given cells (forbid, for example a periodic seam) and off one-cell-wide shapes (min_patch_size); a patch can be placed by a function (placement) and a large one split over ranks (max_cells_per_rank).

A distributed composite solve is planned next.

Quick example

from patchwork import AMRHierarchy, plot_patches

mesh = AMRHierarchy(base_shape=(32, 24))
fine = mesh.add_patch(level=1, box=((16, 48), (12, 36)))
mesh.allocate_field("density")
fine.field("density").local[:] = 1.0
fig, ax = plot_patches(mesh, field="density")
fig.savefig("mesh.png", dpi=180)

Two-level mesh showing the coarse grid, fine patch, and a sample density field

Node-centered Poisson solve

import numpy as np
from patchwork import AMRHierarchy, Dirichlet, EmbeddedDirichlet, Neumann, NodalPoisson

mesh = AMRHierarchy(base_shape=(32, 32), periodic=(False, True))
mesh.add_patch(level=1, box=((24, 40), (24, 40)))
solver = NodalPoisson(
    mesh,
    faces={"x_lower": Neumann(0.0), "x_upper": Dirichlet(100.0)},
    embedded=[
        EmbeddedDirichlet(
            inside=lambda p: np.hypot(p[0] - 0.5, p[1] - 0.5) < 0.05, value=-20.0
        )
    ],
)
x, y = solver.nodes.coordinates.T
potential = solver.solve(np.exp(-((x - 0.3) ** 2 + (y - 0.5) ** 2) / 0.01))

solver.solve can be called again with another source at the cost of two triangular solves. Nodes of a fine patch in the middle of a coarser cell’s edge take the interpolated value of the edge’s ends, so the potential is continuous across patch boundaries.

Potential around a small electrode, resolved by two nested patches

Domains that are not rectangles

The base level can be several boxes of cells. Their union is the domain, and the edges inside the bounding rectangle are a boundary of their own, "inner":

mesh = AMRHierarchy(
    base_shape=(32, 32),
    bounds=((-1.0, 1.0), (-1.0, 1.0)),
    base_boxes=[((0, 16), (0, 32)), ((16, 32), (16, 32))],  # an L
)
solver = NodalPoisson(mesh, faces={"x_upper": Dirichlet(1.0), "inner": Neumann(0.0)})

Potential on an L-shaped domain with nested patches at its inner corner

Patch boxes use half-open integer cell bounds on their own refinement level. The default refinement ratio is two. For runnable layouts and a manufactured composite solve, see examples/two_level_mesh.py, examples/nested_patches.py, examples/composite_poisson.py, examples/nodal_electrode.py, examples/l_shaped_domain.py, examples/t_shaped_domain.py, and examples/regrid_moving_charge.py.

Regridding

Tag the cells that need finer cells, and regrid covers them with properly nested patches and moves the fields over:

# the cells to refine: also function_tags(...), or a boolean array per level
tags = gradient_tags(mesh, "rho", threshold=10.0)
mesh = mesh.regrid(tags, max_patch_size=32)  # a new hierarchy, with the fields
solver = NodalPoisson(mesh)  # numbered and factorized again
potential = solver.solve("rho")

A field allocated with conservative=True (a density) keeps its integral through every regrid. In examples/regrid_moving_charge.py a charge crosses a grounded box and the mesh is regridded at every step: four levels on a 32 x 32 base, about 5,000 unknowns instead of 261,121 for a uniform mesh as fine as the finest patches. Tagging, regridding, rebuilding the solver and solving take about 21 ms per step on one core of an Apple M1, as long as one solve on the uniform mesh, which also needs 0.9 s to factorize; the two potentials agree to 0.13%.

The potential of a moving charge, with the patches that follow it

Profiling

patchwork ships scope-profiler regions around its expensive operations (halo exchanges, restriction, deposit and migration, tagging and regridding, building and solving the Poisson problems), named patchwork.<operation>. They are off by default, so an application that profiles itself records only its own regions. Turn them on with the patchwork tag:

import scope_profiler as sp

sp.setup(enable_tags=["patchwork"])  # or sp.session(enable_tags=["patchwork"])

or, without editing the code, SCOPE_PROFILER_ENABLE_TAGS=patchwork, scope-profiler run --enable-tag patchwork, or enable_tags in the TOML configuration. Switched off, a region costs less than 0.2 µs per call.

Install

With uv:

make install    # uv sync --extra dev, plus the pre-commit hooks
uv run python examples/two_level_mesh.py

Or create and activate a Python environment (3.10 or newer):

python -m venv env
source env/bin/activate
pip install --upgrade pip

Install the release from PyPI, where the package is called patchwork-amr (patchwork there is an unrelated project); the import is import patchwork:

pip install patchwork-amr

Or install the code and requirements from a clone with pip:

pip install -e ".[plot]"  # add ,mpi for MPI-enabled runs

Run the code with:

python examples/two_level_mesh.py

The plot extra installs Matplotlib. The test, docs and dev extras install the test runner, documentation tooling and linters:

pip install -e ".[dev]"

Development

Formatting and linting use ruff, type checking pyright, run by pre-commit and in CI:

make lint     # ruff check, ruff format --check, pyright
make test     # pytest with coverage

Commit messages follow Conventional Commits; see CONTRIBUTING.md.

Build docs

The documentation in docs/ is an Astro + Starlight site: hand-written pages, the notebooks in tutorials/ executed and published as pages, and the API reference generated from the docstrings with starlight-pydocs. It needs Node 22 or newer.

make docs-install     # npm packages and the Python docs extra
make docs-notebooks   # execute tutorials/*.ipynb and convert them to pages
make docs-dev         # live preview at http://localhost:4321/patchwork/
make docs-build       # the static site in docs/dist

Build the README

README.md is rendered from README.qmd with Quarto:

make readme

Releases

Before merging a release to main, update the version in pyproject.toml, src/patchwork/__init__.py and CITATION.cff (including its release date), and add the release notes to CHANGELOG.md. The push to main creates a GitHub release with a vX.Y.Z tag and publishes the package to PyPI with trusted publishing (OIDC). The one-time PyPI and GitHub configuration is described in the publishing guide.

Metadata

Release files for patchwork-amr 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for patchwork-amr 0.2.0
File Size Uploaded Provenance Actions
patchwork_amr-0.2.0.tar.gz 108.2 kB With provenance Details for patchwork_amr-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for patchwork-amr 0.2.0
File Interpreter ABI Platform Actions
patchwork_amr-0.2.0-py3-none-any.whl Python 3 none any Details for patchwork_amr-0.2.0-py3-none-any.whl

Total release size: 224.9 kB

Release files / patchwork_amr-0.2.0.tar.gz

Download URL patchwork_amr-0.2.0.tar.gz
Size 108.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c6a424edd60fc5469404a056eebd5230722a9c938039b8d7a88a70390a155098
BLAKE2b-256 checksum
How to use checksums
3a293a108b465ea38f5d215db45dd15931befabb278b9a1bf0d49bfd16c74b6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / patchwork_amr-0.2.0-py3-none-any.whl

Download URL patchwork_amr-0.2.0-py3-none-any.whl
Size 116.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c18512abfcd36ba7d253e29e62c801fb98dbe826010ef8d1e5e45ae57a67084d
BLAKE2b-256 checksum
How to use checksums
cbcd2a25fd5230ce436b5600ec27e2318ebfcc4ae0c3d71f87c0a5c425713b73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for patchwork-amr 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for patchwork_amr-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page