Operations
pyvoro2 exposes three high-level operations. They correspond to three common
questions you may ask about a set of sites. The same three verbs also exist in
pyvoro2.planar for 2D workflows.
- What does the full tessellation look like?
(Compute every Voronoi/power cell.) - Which site owns this location in space?
(Assign arbitrary query points to sites.) - What would the cell of a hypothetical point be?
(Compute a “probe” cell without inserting the point.)
All operations are stateless: pyvoro2 creates a Voro++ container in C++, inserts the sites, performs the computation, and returns Python data structures. There is no persistent container object that you need to manage.
The same three operation names also exist in the dedicated 2D namespace
pyvoro2.planar. See Planar 2D for the planar-specific domains,
result schema, and wrapper conveniences.
Coordinate scale and numerical safety
Voro++ uses fixed absolute tolerances internally. pyvoro2 reserves the
distance through 1e-5 (squared distance at most 1e-10) as a mandatory
backend-safety regime and rejects any generator pair there before insertion.
This prevents public duplicate options from admitting a known fatal/invalid
native pair, but very small unit systems can still be unsuitable for meaningful
geometry.
pyvoro2 intentionally does not rescale inputs automatically.
If you work in very small or very large units, rescale explicitly before
calling compute, locate, or ghost_cells (for example, multiply all
coordinates and domain vectors by a constant).
You can also request an optional Python-side policy above the mandatory floor:
result = pyvoro2.compute(
points,
domain=cell,
duplicate_check='raise',
duplicate_threshold=1e-3,
)
duplicate_check='off' disables only this additional policy. warn warns for
a safe pair strictly below the user threshold and then proceeds; raise
raises DuplicateError. A threshold at or below 1e-5 adds no optional range.
duplicate_wrap=False selects unwrapped Cartesian distance only for the
optional policy. Mandatory periodic safety always uses certified minimum-image
geometry, including seams, corners, partial periodicity, and triclinic cells.
All inserted generators also obey native containment. On every non-periodic
axis the valid interval is half-open: lo <= x < hi. Periodic axes are remapped
to their primary interval. This applies to the persistent sites used by all
three operations and to temporary ghost_cells generators. Locate queries are
not inserted and retain their existing outside-query behavior.
Native construction controls and rejection
Every compute, locate, and ghost_cells call validates its inputs before
constructing the stateless native container. Site coordinates must have shape
(n, 3), query coordinates must have shape (m, 3), and all coordinates must
be finite. Power radii must have shape (n,), while ghost radii are scalar or
query-aligned as documented; every radius must be finite and non-negative.
Box bounds must be finite and strictly ordered, and periodic parameters must
produce finite, safe native constructor arithmetic. Invalid values raise
ValueError before native construction.
The three native grid controls have strict meanings:
init_memis the positive initial per-block particle capacity. It must be a positive exact non-Boolean index-protocol scalar within the C++intrange. Python integers and in-range NumPy signed or unsigned integer scalars are common examples; other genuine index-protocol scalars are accepted too. Python/NumPy Booleans, floats, strings, complex values, and scalar arrays are rejected.blocksis an explicit length-3 sequence of positive exact non-Boolean index-protocol scalars, one per axis, with the same examples, rejections, and C++intrange. When supplied, these are the selected counts instead of counts derived fromblock_size.block_sizeis an optional positive finite real scalar used to derive block counts. A supplied value is validated even when explicitblocksselect the counts.
Before allocation, pyvoro2 also checks native integer products and a
source-derived estimate of allocations known to occur eagerly during container
construction. The aggregate estimate may be at most exactly 1 GiB
(1,073,741,824 bytes); a larger estimate raises ValueError. This is a safety
limit, not a promise about total peak memory, and there is no unsafe override.
Use fewer blocks, a larger derived block_size, or a smaller init_mem when a
valid configuration exceeds it. These guards change rejection behavior only;
valid in-cap tessellations follow the existing numerical path.
1) compute(...): tessellate all sites
compute computes the Voronoi (standard) or power/Laguerre (weighted) cell for
each site. It returns a TessellationResult by default in both dimensions.
Standard Voronoi
result = pyvoro2.compute(points, domain=box, mode='standard')
This is the classic “midplane” Voronoi construction. Raw cell dictionaries are
available as result.cells; input-aligned measures and empty state are
available as result.cell_measures and result.empty_mask.
Power/Laguerre (weighted)
result = pyvoro2.compute(
points,
domain=box,
mode='power',
weights=weights,
include_empty=True,
)
Here weights[i] is the \(w_i\) in
\(\lVert x-p_i\rVert^2-w_i\). Weights have squared-length units and may be
negative. pyvoro2 uses one common global shift to convert them to non-negative
backend radii before entering Voro++; this representation shift does not change
the diagram. Adding a common constant to all weights is therefore geometrically
invariant. The input and converted representation must remain finite;
non-finite input or overflow during conversion raises ValueError before the
native call.
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.
Power-mode compute(...) requires exactly one of weights= or the existing
radii= representation. Radii have length units and should not be interpreted
as unique physical radii; valid radius-based power computations remain
unchanged. Standard-mode compute(...) rejects both representations. Direct
weights are available on compute(...), not on locate(...) or
ghost_cells(...).
Power diagrams can produce empty cells (volume 0). Voro++ omits those in its iteration;
pyvoro2 can reinsert explicit empty-cell records when include_empty=True.
Periodic neighbor image shifts
In periodic domains, a face between \(i\) and \(j\) corresponds to a specific periodic image of \(j\). If your goal is a periodic neighbor graph, this image information is essential.
Request it with:
result = pyvoro2.compute(
points,
domain=cell,
return_faces=True,
return_vertices=True,
return_face_shifts=True,
)
result.require_boundaries() returns the faces aligned with original input
order. Each face can include:
adjacent_cell: neighbor idadjacent_shift: integer shift(na, nb, nc)describing which neighbor image produced the face
Structured and raw cell output
The structured result is the normal path. Code that deliberately needs the raw cell dictionaries can select the explicit low-level output mode:
cells = pyvoro2.compute(points, domain=box, output='cells')
output='cells' is a supported current output mode and is retained in v0.8; it
is not part of the compatibility-removal list. With this mode,
return_diagnostics=True returns (cells, diagnostics). With the preferred
structured output,
diagnostics are stored in result.tessellation_diagnostics and the return is
always one TessellationResult:
result = pyvoro2.compute(
points,
domain=box,
return_diagnostics=True,
)
diagnostics = result.require_tessellation_diagnostics()
2) locate(...): assign query points to sites
locate answers a simpler question than full tessellation:
Given a query point \(q\), which site owns it?
This wraps the Voro++ routine find_voronoi_cell.
out = pyvoro2.locate(points, queries, domain=cell, return_owner_position=True)
owner_ids = out['owner_id']
If return_owner_position=True, the output also contains owner_pos.
In periodic domains this position may be a periodic image of the generator, chosen
consistently with the query point.
3) ghost_cells(...): compute probe (ghost) cells
ghost_cells asks a slightly different question:
What would the cell of \(q\) look like if \(q\) were inserted as an additional site?
This wraps the Voro++ routine compute_ghost_cell.
ghost = pyvoro2.ghost_cells(points, queries, domain=cell)
Each returned record describes the polyhedron of the ghost cell.
A ghost query is temporarily inserted. It must therefore lie in every
non-periodic half-open interval and be safely distinct from the persistent
generators. An outside non-periodic ghost now raises ValueError; a valid
inserted ghost may still produce an empty cell geometrically.
query vs site in periodic domains
For periodic domains, pyvoro2 wraps each query into the primary cell before calling Voro++. Therefore each record contains:
query: the original coordinate you suppliedsite: the coordinate actually used for the computation (a wrapped periodic representative)
They are in the same coordinate system and differ only by an integer lattice translation. This wrapping makes results easier to compare and visualize.