Planar 2D (pyvoro2.planar)
pyvoro2 now ships a dedicated planar 2D namespace:
import pyvoro2.planar as pv2
This is intentionally separate from the 3D top-level API. The goal is to keep both surfaces explicit and mathematically honest:
pyvoro2is the 3D package,pyvoro2.planaris the 2D package.
The current 2D release scope is deliberately limited to the domains that the vendored legacy backend supports well:
pv2.Boxpv2.RectangularCell
There is no planar PeriodicCell yet. Rectangular periodic domains can be
periodic in either or both planar axes.
Basic compute
import numpy as np
import pyvoro2.planar as pv2
pts = np.array([
[0.2, 0.2],
[0.8, 0.2],
[0.5, 0.8],
], dtype=float)
result = pv2.compute(
pts,
domain=pv2.Box(((0.0, 1.0), (0.0, 1.0))),
return_vertices=True,
return_edges=True,
)
The default return is the same TessellationResult class used in 3D. Its raw
planar records are available as result.cells and remain dimension-specific by
design:
areainstead ofvolume,edgesinstead offaces,adjacent_shiftis a length-2 periodic image shift when requested.
Power weights
Planar power diagrams accept mathematical weights through the same explicit
contract as the 3D compute(...) function:
weights = np.array([-0.1, 0.0, 0.2])
result = pv2.compute(
pts,
domain=pv2.Box(((0.0, 1.0), (0.0, 1.0))),
mode='power',
weights=weights,
include_empty=True,
)
The convention is \(\pi_i(x)=\lVert x-p_i\rVert^2-w_i\). Weights have
squared-length units and may be negative. Internally, one common global shift
converts them to non-negative length-unit backend radii, so a common additive
change to all weights leaves areas, adjacency, realized edges, and periodic
image shifts unchanged within numerical tolerance. Existing radii= calls
remain valid and numerically unchanged in power mode, but supplying both
representations is an error. Standard mode rejects either representation.
weights= is currently a compute(...) argument only. The input and converted
representation must remain finite; non-finite input or overflow during
conversion raises ValueError before native computation.
Finite representability is necessary for conversion but does not guarantee a
numerically resolvable native tessellation. Voro++ evaluates radical geometry
with binary64 squared-radius arithmetic, so very large absolute radii**2
values or genuine weight ranges relative to squared coordinate/domain scales
can lose geometric resolution. There is no universal safe cutoff: the onset
depends on scale, geometry, platform, and compiler, and periodic power
tessellations are a particularly sensitive regime. pyvoro2 does not silently
weaken validation or alter the requested power geometry in this unsupported
regime.
Rectangular periodic cells and edge shifts
For periodic rectangular domains, request return_edge_shifts=True when you
need the explicit periodic image of the neighboring site:
cell = pv2.RectangularCell(
((0.0, 1.0), (0.0, 1.0)),
periodic=(True, True),
)
result = pv2.compute(
pts,
domain=cell,
return_vertices=True,
return_edges=True,
return_edge_shifts=True,
)
The planar wrapper reconstructs these edge shifts in Python and also repairs a legacy backend quirk where some fully periodic adjacencies can otherwise appear with negative neighbor ids.
locate(...) and ghost_cells(...)
The planar namespace mirrors the 3D operation names:
owners = pv2.locate(pts, [[0.1, 0.2], [0.9, 0.2]], domain=cell)
ghost = pv2.ghost_cells(
pts,
[[0.5, 0.5]],
domain=cell,
return_vertices=True,
return_edges=True,
)
So the same three high-level questions exist in both dimensions:
- compute every cell,
- locate the owner of a query point,
- compute the hypothetical cell of a query point without inserting it.
Diagnostics and wrapper-level convenience
Planar compute(...) supports the same kind of post-compute convenience that
3D users already expect, but specialized for 2D semantics:
result = pv2.compute(
pts,
domain=cell,
return_diagnostics=True,
)
diag = result.require_tessellation_diagnostics()
For periodic domains, the wrapper automatically computes the temporary geometry
needed for reciprocity checks and then strips it back out of result.cells
unless you explicitly requested it. The result's boundary and periodic-shift
capability flags describe only that final user-visible geometry.
The same holds for normalization convenience:
result = pv2.compute(
pts,
domain=cell,
return_diagnostics=True,
normalize='topology',
)
This returns the common pv2.TessellationResult bundling:
- raw
cells, - optional tessellation diagnostics,
- optional normalized vertices,
- optional normalized topology.
pv2.PlanarComputeResult remains a compatibility-only identity alias to
TessellationResult during v0.7. The deprecated
return_result: bool | None = None selector uses None to mean “not supplied”;
passing either boolean emits a deprecation warning. Both transition surfaces are
removed in v0.8. New code uses TessellationResult and output=.
Code that deliberately needs raw cell dictionaries can select the explicit low-level output mode:
cells = pv2.compute(pts, domain=cell, output='cells')
cells, diag = pv2.compute(
pts,
domain=cell,
output='cells',
return_diagnostics=True,
)
output='cells' cannot be combined with normalization, because normalization
is structured output rather than a raw-cell side effect.
Planar normalization
The dedicated planar normalization helpers are:
pv2.normalize_vertices(...)pv2.normalize_edges(...)pv2.normalize_topology(...)pv2.validate_normalized_topology(...)
In planar topology work, the globally deduplicated boundary objects are edges, not faces.
Planar plotting
For quick inspection, use the optional matplotlib helper:
from pyvoro2.planar import plot_tessellation
fig, ax = plot_tessellation(result.cells, annotate_ids=True)
Install it with:
pip install "pyvoro2[viz2d]"
or install both 2D and 3D visualization helpers with:
pip install "pyvoro2[viz]"
Planar separator fitting
The canonical separator inverse API supports planar domains as well as 3D
domains. Use pyvoro2.inverse for the normal fixed-observation workflow and
pyvoro2.inverse.separator for advanced models, realization diagnostics, and
the experimental active-set outer loop.
The solver vocabulary is shared between 2D and 3D; what changes is the meaning
of the realized boundary measure:
- face area in 3D,
- edge length in 2D.
The current planar domain restriction still applies here: rectangular periodic
cells are supported, but there is no planar oblique-periodic PeriodicCell
yet.
See Choosing an API and Separator fitting for the canonical imports and lifecycle status.