Skip to content

patchwork.mesh

Block-structured adaptive mesh hierarchy and mpiarray-backed patch fields.

Attributes

NameDescription
IndexBoxNo description.
PhysicalBoundsNo description.

Classes

NameDescription
AMRHierarchyA 2D nested patch hierarchy with mpiarray-backed fields.
PatchOne rectangular patch; its box uses level-local, half-open cell indices.

IndexBoxattributemodule attribute#

IndexBox = tuple[tuple[int, int], tuple[int, int]]

PhysicalBoundsattributemodule attribute#

PhysicalBounds = tuple[tuple[float, float], tuple[float, float]]

AMRHierarchyclass#

class AMRHierarchy(base_shape: tuple[int, int], bounds: PhysicalBounds = ((0.0, 1.0), (0.0, 1.0)), *, refinement_ratio: int = 2, periodic: tuple[bool, bool] = (False, False), base_boxes: Sequence[Sequence[Sequence[int]]] | None = None, placement: str = 'all', base_ranks: Sequence[int | Sequence[int] | None] | None = None, comm: Any = None)

A 2D nested patch hierarchy with mpiarray-backed fields.

Patch coordinates are integer cell bounds on their own refinement level. A child patch’s bounds must align with the parent level and lie within the patches of that level. The refinement ratio is two by default. Fields are cell-centered, or node-centered (one value per cell corner, see :meth:allocate_field). periodic flags the axes on which the last cell neighbours the first; the node-centered solver (:class:~patchwork.nodal.NodalPoisson) uses it. Without an MPI launcher, mpiarray supplies a serial communicator and the same interface works with a single process.

Under MPI every rank builds the same hierarchy, with the same calls in the same order. Each patch then lives on some of the ranks of comm and has a communicator of its own for them: a small patch on one rank, a large one on several, over which mpiarray splits its fields. placement says where a patch goes that is added without ranks: "all" puts it on every rank, "balanced" on the one rank that holds the fewest cells so far.

The domain is the rectangle of base_shape cells, or with base_boxes the union of those boxes of base cells: an L, a T, a rectangle with a notch. The boxes are the patches of level zero. Where the domain does not fill the rectangle, its edge is a boundary like the rectangle’s own faces.

base_shapeattributeinstance attribute#

base_shape = tuple((int(n) for n in base_shape))

boundsattributeinstance attribute#

bounds = tuple((tuple((float(x) for x in axis)) for axis in bounds))

commattributeinstance attribute#

comm = mpa.default_comm() if comm is None else comm

periodicattributeinstance attribute#

periodic = (bool(periodic[0]), bool(periodic[1]))

placementattributeinstance attribute#

placement = placement

refinement_ratioattributeinstance attribute#

refinement_ratio = int(refinement_ratio)

cells_on_rankproperty#

cells_on_rank: tuple[float, ...]

Cells of all patches held by every rank (a patch on several ranks is shared).

field_namesproperty#

field_names: tuple[str, ...]

Names allocated throughout the hierarchy.

local_patchesproperty#

local_patches: tuple[Patch, ...]

The patches this rank holds a part of, ordered as :attr:patches.

lower_boundproperty#

lower_bound: tuple[float, float]

Physical lower-left corner of the domain.

max_levelproperty#

max_level: int

Finest level currently present.

patchesproperty#

patches: tuple[Patch, ...]

All patches, ordered by level and then lower corner.

upper_boundproperty#

upper_bound: tuple[float, float]

Physical upper-right corner of the domain.

add_patchmethod#

def add_patch(level: int, box: Sequence[Sequence[int]], ranks: int | Sequence[int] | None = None) -> Patch

Add a non-overlapping, properly nested fine patch.

box is half-open and measured in cells at level. Bounds must align to the cells of the level below, and the patch must lie within the patches of that level (it may span several that touch).

ranks are the ranks of the hierarchy’s communicator that hold the patch: one rank or several, over which its fields are split. Without it, placement decides. Under MPI every rank adds the same patches in the same order.

allocate_fieldmethod#

def allocate_field(name: str, *, dtype: Any = float, halo: int = 1, split: Any = 0, centering: str = 'cell', conservative: bool = False) -> None

Allocate a named zero-filled field on every existing patch.

A patch’s arrays exist on the ranks that hold it. The default splits them over those ranks along the first axis (which then needs at least one cell per rank). Use split=None to replicate them instead. With centering="node" the values sit on the cell corners: a patch of (nx, ny) cells holds (nx + 1, ny + 1) of them, and patches that touch or nest share the nodes they have in common.

A conservative field is an amount per area, such as a density, whose integral (:meth:integral) :meth:regrid keeps: it is restricted before a regrid, and the cells of new patches are filled from the level below so that the fine cells of a coarse cell keep its mean. It must be cell-centered.

centeringmethod#

def centering(name: str) -> str

"cell" or "node": where the values of field name sit.

exchange_halosmethod#

def exchange_halos(name: str) -> None

Fill the halo of every patch of field name from the rest of the hierarchy.

From the patches of the same level the halo overlaps (across periodic faces too), else by bilinear interpolation of the level below, and between the ranks of each patch. Halo points outside the domain are left for the boundary conditions. Collective over comm; the schedule is built at the first call and kept until a patch is added. See :class:~patchwork.halos.HaloExchange.

gathermethod#

def gather(patch: Patch, name: str, root: int | None = None) -> Any

The whole of field name of patch as a NumPy array.

Collective over the hierarchy’s communicator: every rank calls it, whether it holds the patch or not. The array is returned on every rank, or with root on that rank only (None on the others).

integralmethod#

def integral(name: str) -> float

The integral of field name over the domain, from the finest values.

Cell-centered: the sum of value times area over the cells that no finer patch covers. Node-centered: the trapezoidal rule over those cells. Collective over comm; the same value on every rank.

level_shapemethod#

def level_shape(level: int) -> tuple[int, int]

Number of cells in the full domain at a refinement level.

patches_at_levelmethod#

def patches_at_level(level: int) -> tuple[Patch, ...]

Patches on one level.

regridmethod#

def regrid(tags: Mapping[int, Any], *, efficiency: float = 0.7, buffer: int = 1, nesting: int = 1, max_patch_size: int | None = None, placement: str | None = None) -> AMRHierarchy

A new hierarchy with patches over the tagged cells, and the fields moved to it.

tags maps a level to booleans of that level’s shape (:meth:level_shape): the cells that need the next finer level. Make them with :func:~patchwork.regrid.gradient_tags, :func:~patchwork.regrid.function_tags or by hand; each rank may tag from the data it holds, and the tags are combined over the ranks.

Level by level from the coarsest, the tagged cells, grown by buffer cells, are covered with boxes by :func:~patchwork.regrid.cluster (at least efficiency of their cells tagged, at most max_patch_size cells of the new level long). A level also covers, with a margin, what the finer levels need, and each patch keeps nesting cells of the level below between itself and that level’s edge (the edge of the domain excepted). The base level stays as it is. The levels above the last tagged one are dropped.

Every field is moved: a point takes the value of an old patch of the same level that has it, else the bilinear interpolation of the new level below. A conservative field (see :meth:allocate_field) is first restricted on this hierarchy, and its new cells are filled so that the fine cells of a coarse cell keep its mean: its :meth:integral is the same before and after. Halos are not filled; call :meth:exchange_halos.

New patches are placed by placement (default: this hierarchy’s), the largest first. Collective over comm; this hierarchy is left as it is, except for the restriction of conservative fields.

restrictmethod#

def restrict(name: str) -> None

Give the points of field name under finer patches the fine values.

The mean of the fine cells (cell-centered) or the coinciding fine node (node-centered), finest level first. Collective over comm; call :meth:exchange_halos after it for the halos. See :class:~patchwork.halos.Restriction.

spacingmethod#

def spacing(level: int = 0) -> tuple[float, float]

Cell spacing at a refinement level.

Patchclassdataclass#

class Patch(level: int, box: IndexBox, ranks: tuple[int, ...] = (0), comm: Any = field(default=None, repr=False, compare=False), _fields: dict[str, DistributedArray] = field(default_factory=dict, repr=False))

One rectangular patch; its box uses level-local, half-open cell indices.

A patch lives on some of the ranks of its hierarchy: ranks, the same on every rank. On those ranks comm is the patch’s own communicator, over which mpiarray splits its fields; on the others it is None and the patch holds no data (is_local is false).

boxattributeinstance attribute#

box: IndexBox

commattributeclass attributeinstance attribute#

comm: Any = field(default=None, repr=False, compare=False)

levelattributeinstance attribute#

level: int

ranksattributeclass attributeinstance attribute#

ranks: tuple[int, ...] = (0)

field_namesproperty#

field_names: tuple[str, ...]

Names of allocated fields on this patch.

is_localproperty#

is_local: bool

Whether this rank holds a part of the patch.

node_shapeproperty#

node_shape: tuple[int, int]

Number of nodes (cell corners) along each axis: one more than cells.

shapeproperty#

shape: tuple[int, int]

Number of cells along each axis.

fieldmethod#

def field(name: str) -> DistributedArray

Get an allocated field by name, on a rank that holds the patch.

local_cell_centersmethod#

def local_cell_centers(hierarchy: AMRHierarchy, name: str | None = None) -> tuple[Any, Any]

Coordinates of this rank’s cells of the cell-centered field name.

Two arrays of the shape of field(name).local, for filling it. Without name, of the first cell-centered field allocated.

local_node_coordinatesmethod#

def local_node_coordinates(hierarchy: AMRHierarchy, name: str) -> tuple[Any, Any]

Coordinates of this rank’s nodes of the node-centered field name.

Two arrays of the shape of field(name).local, for filling it.

node_coordinatesmethod#

def node_coordinates(hierarchy: AMRHierarchy) -> tuple[Any, Any]

Coordinates of the nodes of the whole patch along each axis (1D arrays).

physical_boundsmethod#

def physical_bounds(hierarchy: AMRHierarchy) -> PhysicalBounds

Physical rectangle occupied by this patch.