# SPDX-FileCopyrightText: Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Prepared immutable state for supported JAX neighbor-list routes."""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Any
import jax
import jax.numpy as jnp
from nvalchemiops.jax.neighbors._registration import (
_lazy_cell_inverse_kernel,
_lazy_cluster_geometry_kernel,
)
__all__ = [
"NeighborListState",
"check_neighbor_list_state",
"prepare_neighbor_list",
]
_TOKEN = object()
_CALLER_OWNED = frozenset(
{
"atom_periodic_shifts",
"atom_to_cell_mapping",
"atoms_per_cell_count",
"batch_idx_sorted",
"batch_ptr_padded",
"cell_atom_list",
"cell_atom_start_indices",
"cell_offsets",
"cells_per_dimension",
"group_ctr_x",
"group_ctr_y",
"group_ctr_z",
"group_ext_x",
"group_ext_y",
"group_ext_z",
"group_ptr",
"group_system",
"inv_cell_buffer",
"inv_cell_batch",
"morton_codes",
"neighbor_distances",
"neighbor_list",
"neighbor_list_shifts",
"neighbor_matrix",
"neighbor_matrix1",
"neighbor_matrix2",
"neighbor_matrix_shifts",
"neighbor_matrix_shifts1",
"neighbor_matrix_shifts2",
"neighbor_vectors",
"neighbor_search_radius",
"num_tiles",
"num_neighbors",
"num_neighbors1",
"num_neighbors2",
"pair_energies",
"pair_counts",
"pair_counter",
"pair_forces",
"pair_offsets",
"per_atom_cell_offsets_buffer",
"positions_wrapped_buffer",
"sort_inv",
"sorted_atom_index",
"sorted_pos_x",
"sorted_pos_y",
"sorted_pos_z",
"sorted_positions",
"sorted_shifts",
"tile_col_group",
"tile_counts",
"tile_offsets",
"tile_row_group",
"tile_system",
}
)
_CONFIG_KEYS = frozenset(
{
"atom_centric_path",
"coo_capacity",
"format",
"max_atoms_per_system",
"max_neighbors",
"max_neighbors1",
"max_neighbors2",
"max_shifts_per_system",
"max_tiles_per_group",
"max_total_cells",
"num_shifts_per_system",
"pair_fn",
"pair_centric_n_outer",
"pair_centric_r_max",
"pair_centric_total_cells",
"pair_params",
"return_distances",
"return_vectors",
"shift_range_per_dimension",
"span_margin",
"strategy",
"target_indices",
}
)
_EXECUTION_KEYS = _CONFIG_KEYS | {"rebuild_flags"}
_CELL_LEAF = 0
_PBC_LEAF = 1
_BATCH_IDX_LEAF = 2
_BATCH_PTR_LEAF = 3
_FIXED_CELL_GEOMETRY_LEAF = 4
_RESULT_LEAF_OFFSET = 5
_RESULT_LEAF_COUNT = 12
_SHIFT_RANGE_LEAF = -18
_NUM_SHIFTS_LEAF = -17
_TARGET_INDICES_LEAF = -16
_SPAN_CAPACITY_LEAF = -15
_SPAN_AXIS_LEAF = -14
_SPAN_REQUIRED_LEAF = -13
_INITIALIZED_LEAF = -12
_VALID_LEAF = -11
_FAILURE_CODE_LEAF = -10
_REQUIRED_LEAF = -9
_CAPACITY_LEAF = -8
_FIRST_FAILURE_LEAF = -7
_PRIVATE_MATRIX1_LEAF = -6
_PRIVATE_NUM1_LEAF = -5
_PRIVATE_SHIFT1_LEAF = -4
_PRIVATE_MATRIX2_LEAF = -3
_PRIVATE_NUM2_LEAF = -2
_PRIVATE_SHIFT2_LEAF = -1
_FIXED_CELL_INVERSE_REGISTRATION = _lazy_cell_inverse_kernel()
_FIXED_CLUSTER_GEOMETRY_REGISTRATION = _lazy_cluster_geometry_kernel()
_CUTOFF_GROUP_RESULT_NAMES = frozenset(
{
"neighbor_matrix",
"num_neighbors",
"neighbor_matrix_shifts",
"neighbor_list",
"neighbor_ptr",
"neighbor_list_shifts",
"metadata_valid",
}
)
def _result_property(name: str) -> property:
"""Create a documented read-only view of an applicable result leaf."""
def get(state: NeighborListState) -> jax.Array | None:
possible_base = name[:-1] if name.endswith(("1", "2")) else name
is_grouped = possible_base in _CUTOFF_GROUP_RESULT_NAMES
is_first = is_grouped and name.endswith("1")
is_second = is_grouped and name.endswith("2")
if is_grouped and state.cutoff2 is not None:
if not (is_first or is_second):
return None
result_name = possible_base if is_first else name
elif is_grouped and (is_first or is_second):
return None
else:
result_name = name
if result_name not in state._spec.result_names:
return None
return _result_leaf(state, result_name)
get.__name__ = name
doc = {
"metadata_valid": "Scalar Boolean recovery-metadata status, or ``None`` when not applicable.",
"metadata_valid1": "Primary-cutoff recovery-metadata status, or ``None``.",
"metadata_valid2": "Secondary-cutoff recovery-metadata status, or ``None``.",
"neighbor_distances": "Latest per-pair distances, or ``None`` when not requested.",
"neighbor_vectors": "Latest per-pair displacement vectors, or ``None`` when not requested.",
"pair_energies": "Latest pair-callback energies, or ``None`` when not requested.",
"pair_forces": "Latest pair-callback forces, or ``None`` when not requested.",
}.get(name)
if doc is None and name.startswith("neighbor_matrix_shifts"):
doc = "Latest matrix-format periodic shifts, or ``None`` when not applicable."
elif doc is None and name.startswith("neighbor_matrix"):
doc = "Latest fixed-width neighbor matrix, or ``None`` when not applicable."
elif doc is None and name.startswith("num_neighbors"):
doc = "Latest required neighbor count per source row, or ``None`` when not applicable."
elif doc is None and name.startswith("neighbor_list_shifts"):
doc = "Latest COO-format periodic shifts, or ``None`` when not applicable."
elif doc is None and name.startswith("neighbor_list"):
doc = "Latest COO neighbor indices, or ``None`` when not applicable."
elif doc is None and name.startswith("neighbor_ptr"):
doc = "Latest clipped CSR pointer for fixed COO output, or ``None``."
elif doc is None and name == "pair_offsets":
doc = "Segment offsets for the latest segmented COO output, or ``None``."
elif doc is None and name == "pair_counts":
doc = "Active pair count per segmented COO system, or ``None``."
elif doc is None and name == "tile_offsets":
doc = "Segment offsets for batched cluster tiles, or ``None``."
elif doc is None and name == "tile_counts":
doc = "Active cluster-tile count per system, or ``None``."
elif doc is None and name == "num_tiles":
doc = "Latest active cluster-tile count, or ``None``."
elif doc is None and name.startswith("tile_"):
doc = "Latest cluster-tile metadata, or ``None`` when not applicable."
elif doc is None:
doc = "Latest applicable route result, or ``None`` when not applicable."
return property(get, doc=doc)
def _result_leaf(
state: NeighborListState,
name: str,
leaves: tuple[Any, ...] | None = None,
) -> Any:
"""Return a named result leaf from the fixed prepared leaf layout."""
values = state._leaves if leaves is None else leaves
return values[_RESULT_LEAF_OFFSET + state._spec.result_names.index(name)]
@dataclass(frozen=True)
class _StateLeaves:
"""Named view over the fixed internal state-leaf layout."""
values: tuple[Any, ...]
@classmethod
def build(
cls,
*,
cell: Any,
pbc: Any,
batch_idx: Any,
batch_ptr: Any,
fixed_cell_geometry: Any,
result: tuple[Any, ...] | list[Any],
shift_range_per_dimension: Any,
num_shifts_per_system: Any,
target_indices: Any,
span_capacity: Any,
span_axis: Any,
span_required: Any,
initialized: Any,
valid: Any,
failure_code: Any,
required: Any,
capacity: Any,
first_failure: Any,
private_matrix1: Any,
private_num1: Any,
private_shift1: Any,
private_matrix2: Any,
private_num2: Any,
private_shift2: Any,
) -> tuple[Any, ...]:
"""Build the fixed PyTree layout from named state components.
The public state is intentionally a flat PyTree for JAX transform
compatibility. Keeping construction here makes the positional
order auditable while preserving the established leaf sequence.
"""
result = tuple(result)
if len(result) > _RESULT_LEAF_COUNT:
raise ValueError("prepared result contains too many state leaves")
result = result + (None,) * (_RESULT_LEAF_COUNT - len(result))
return (
cell,
pbc,
batch_idx,
batch_ptr,
fixed_cell_geometry,
*result,
shift_range_per_dimension,
num_shifts_per_system,
target_indices,
span_capacity,
span_axis,
span_required,
initialized,
valid,
failure_code,
required,
capacity,
first_failure,
private_matrix1,
private_num1,
private_shift1,
private_matrix2,
private_num2,
private_shift2,
)
def successor(
self,
result: tuple[Any, ...],
*,
span_axis: Any,
span_required: Any,
initialized: Any,
valid: Any,
failure_code: Any,
required: Any,
capacity: Any,
first_failure: Any,
private_leaves: tuple[Any, ...] | None = None,
) -> tuple[Any, ...]:
"""Build a successor using named fields and the same leaf order."""
private = self.private if private_leaves is None else tuple(private_leaves)
if len(private) != 6:
raise ValueError("prepared private state must contain six leaves")
return self.build(
cell=self.cell,
pbc=self.pbc,
batch_idx=self.batch_idx,
batch_ptr=self.batch_ptr,
fixed_cell_geometry=self.fixed_cell_geometry,
result=result,
shift_range_per_dimension=self.values[_SHIFT_RANGE_LEAF],
num_shifts_per_system=self.values[_NUM_SHIFTS_LEAF],
target_indices=self.values[_TARGET_INDICES_LEAF],
span_capacity=self.values[_SPAN_CAPACITY_LEAF],
span_axis=span_axis,
span_required=span_required,
initialized=initialized,
valid=valid,
failure_code=failure_code,
required=required,
capacity=capacity,
first_failure=first_failure,
private_matrix1=private[0],
private_num1=private[1],
private_shift1=private[2],
private_matrix2=private[3],
private_num2=private[4],
private_shift2=private[5],
)
@property
def cell(self) -> Any:
return self.values[_CELL_LEAF]
@property
def pbc(self) -> Any:
return self.values[_PBC_LEAF]
@property
def batch_idx(self) -> Any:
return self.values[_BATCH_IDX_LEAF]
@property
def batch_ptr(self) -> Any:
return self.values[_BATCH_PTR_LEAF]
@property
def fixed_cell_geometry(self) -> Any:
return self.values[_FIXED_CELL_GEOMETRY_LEAF]
@property
def initialized(self) -> Any:
return self.values[_INITIALIZED_LEAF]
@property
def valid(self) -> Any:
return self.values[_VALID_LEAF]
@property
def failure_code(self) -> Any:
return self.values[_FAILURE_CODE_LEAF]
@property
def required(self) -> Any:
return self.values[_REQUIRED_LEAF]
@property
def capacity(self) -> Any:
return self.values[_CAPACITY_LEAF]
@property
def first_failure(self) -> Any:
return self.values[_FIRST_FAILURE_LEAF]
@property
def span_axis(self) -> Any:
return self.values[_SPAN_AXIS_LEAF]
@property
def span_required(self) -> Any:
return self.values[_SPAN_REQUIRED_LEAF]
@property
def private_matrix1(self) -> Any:
return self.values[_PRIVATE_MATRIX1_LEAF]
@property
def private_num1(self) -> Any:
return self.values[_PRIVATE_NUM1_LEAF]
@property
def private_shift1(self) -> Any:
return self.values[_PRIVATE_SHIFT1_LEAF]
@property
def private_matrix2(self) -> Any:
return self.values[_PRIVATE_MATRIX2_LEAF]
@property
def private_num2(self) -> Any:
return self.values[_PRIVATE_NUM2_LEAF]
@property
def private_shift2(self) -> Any:
return self.values[_PRIVATE_SHIFT2_LEAF]
@property
def private(self) -> tuple[Any, ...]:
return (
self.private_matrix1,
self.private_num1,
self.private_shift1,
self.private_matrix2,
self.private_num2,
self.private_shift2,
)
def _private_state_leaves(leaves: tuple[Any, ...]) -> tuple[Any, ...]:
"""Return the fixed private matrix-storage suffix by field name."""
return _StateLeaves(leaves).private
def _build_successor_leaves(
leaves: tuple[Any, ...],
result: tuple[Any, ...],
*,
span_axis: Any,
span_required: Any,
initialized: Any,
valid: Any,
failure_code: Any,
required: Any,
capacity: Any,
first_failure_marker: Any,
private_leaves: tuple[Any, ...] | None = None,
) -> tuple[Any, ...]:
"""Build a successor without changing the public PyTree leaf layout."""
return _StateLeaves(leaves).successor(
result,
span_axis=span_axis,
span_required=span_required,
initialized=initialized,
valid=valid,
failure_code=failure_code,
required=required,
capacity=capacity,
first_failure=first_failure_marker,
private_leaves=private_leaves,
)
@dataclass(frozen=True)
class _Spec:
method: str
strategy: str
format: str
coo_layout: str | None
is_batched: bool
num_atoms: int
num_systems: int
cutoff: float
cutoff2: float | None
half_fill: bool
fill_value: int
wrap_positions: bool
selective: bool
return_vectors: bool
return_distances: bool
span_margin: float
synthesized_cell: bool
fixed_cell: bool
supports_compilation: bool
compilation_blocker: str | None
pair_fn: Any = None
result_names: tuple[str, ...] = ()
coo_capacity: int | tuple[int, int] | None = None
max_neighbors: tuple[int, int | None] = (0, None)
max_shifts_per_system: int | None = None
max_atoms_per_system: int | None = None
has_target_indices: bool = False
shared_batch_cell: bool = False
route_options: tuple[tuple[str, Any], ...] = ()
[docs]
@jax.tree_util.register_pytree_node_class
class NeighborListState:
"""Prepared JAX neighbor-list configuration, results, and status.
Instances are created by :func:`prepare_neighbor_list`; direct
construction is unsupported. The state is an immutable JAX PyTree. Pass
each successor returned by :func:`neighbor_list` to the next execution.
Configuration properties describe the route resolved during eager
preparation. Result properties mirror the selected route's existing tuple
and return ``None`` when a component does not apply. ``initialized`` and
``valid`` are Boolean arrays of shape ``(num_systems,)``.
Notes
-----
A failed system remains invalid after later successful executions. Call
:func:`check_neighbor_list_state` when a host-side exception is needed;
checking is optional and does not clear status. Preparing a new state is
the only way to reset failure history.
The read-only properties below expose the resolved method, strategy,
output layout, fixed system metadata, compilation eligibility, and current
lifecycle status. Result properties use the existing tuple component names
and return ``None`` when a component is not applicable. Result arrays
follow JAX functional semantics, and some are also successor-state leaves.
Consume those results before donating the state to a later call, and do not
subsequently use aliases retained from the donated state.
"""
__slots__ = ("_spec", "_leaves")
def __init__(
self,
spec: _Spec | None = None,
leaves: tuple[Any, ...] | None = None,
*,
token: object | None = None,
) -> None:
if token is not _TOKEN:
raise TypeError(
"state must be a NeighborListState returned by prepare_neighbor_list"
)
self._spec = spec
self._leaves = tuple(leaves)
def __setattr__(self, name: str, value: Any) -> None:
if hasattr(self, name):
raise AttributeError(f"prepared state attribute {name!r} is read-only")
super().__setattr__(name, value)
def tree_flatten(self) -> tuple[tuple[Any, ...], _Spec]:
"""Return dynamic array leaves and the static prepared-route specification."""
return self._leaves, self._spec
@classmethod
def tree_unflatten(cls, spec: _Spec, leaves: tuple[Any, ...]) -> NeighborListState:
"""Reconstruct a prepared state from its PyTree specification and leaves."""
return cls(spec, tuple(leaves), token=_TOKEN)
def _replace(self, leaves: tuple[Any, ...]) -> NeighborListState:
return NeighborListState(self._spec, tuple(leaves), token=_TOKEN)
@property
def method(self) -> str:
"""Return the resolved algorithm family."""
return self._spec.method
@property
def strategy(self) -> str:
"""Return the resolved implementation strategy."""
return self._spec.strategy
@property
def format(self) -> str:
"""Return the prepared scientific output format."""
return self._spec.format
@property
def coo_layout(self) -> str | None:
"""Return ``"compact"``, ``"segmented"``, or ``None``."""
return self._spec.coo_layout
@property
def is_batched(self) -> bool:
"""Return whether the resolved route is batched."""
return self._spec.is_batched
@property
def num_atoms(self) -> int:
"""Return the fixed atom count."""
return self._spec.num_atoms
@property
def num_systems(self) -> int:
"""Return the fixed system count."""
return self._spec.num_systems
@property
def cutoff(self) -> float:
"""Return the prepared primary cutoff."""
return self._spec.cutoff
@property
def cutoff2(self) -> float | None:
"""Return the prepared secondary cutoff, if any."""
return self._spec.cutoff2
@property
def half_fill(self) -> bool:
"""Return whether the route stores one directed pair half."""
return self._spec.half_fill
@property
def fill_value(self) -> int:
"""Return the prepared matrix padding value."""
return self._spec.fill_value
@property
def wrap_positions(self) -> bool:
"""Return whether applicable routes wrap positions before enumeration."""
return self._spec.wrap_positions
@property
def fixed_cell(self) -> bool:
"""Return whether repeated execution reuses prepared cell geometry."""
return self._spec.fixed_cell
@property
def selective(self) -> bool:
"""Return whether runtime per-system rebuild selection is enabled."""
return self._spec.selective
@property
def return_vectors(self) -> bool:
"""Return whether displacement vectors are requested."""
return self._spec.return_vectors
@property
def return_distances(self) -> bool:
"""Return whether pair distances are requested."""
return self._spec.return_distances
@property
def span_margin(self) -> float:
"""Return the synthesized-cell per-axis span-growth allowance."""
return self._spec.span_margin
@property
def supports_compilation(self) -> bool:
"""Return whether this prepared configuration is compilation eligible."""
return self._spec.supports_compilation
@property
def compilation_blocker(self) -> str | None:
"""Return the known compilation blocker, if any."""
return self._spec.compilation_blocker
@property
def initialized(self) -> jax.Array:
"""Return per-system result initialization status."""
return self._leaves[_INITIALIZED_LEAF]
@property
def valid(self) -> jax.Array:
"""Return sticky per-system validity history."""
return self._leaves[_VALID_LEAF]
_failure_code = property(lambda state: state._leaves[_FAILURE_CODE_LEAF])
_required = property(lambda state: state._leaves[_REQUIRED_LEAF])
_capacity = property(lambda state: state._leaves[_CAPACITY_LEAF])
_first_failure = property(lambda state: state._leaves[_FIRST_FAILURE_LEAF])
# The optional fixed partial-row map is deliberately a private PyTree leaf:
# it participates in JIT signatures but cannot be changed at execution.
_target_indices = property(lambda state: state._leaves[_TARGET_INDICES_LEAF])
_shift_range_per_dimension = property(
lambda state: state._leaves[_SHIFT_RANGE_LEAF]
)
_num_shifts_per_system = property(lambda state: state._leaves[_NUM_SHIFTS_LEAF])
_span_capacity = property(lambda state: state._leaves[_SPAN_CAPACITY_LEAF])
_span_axis = property(lambda state: state._leaves[_SPAN_AXIS_LEAF])
_span_required = property(lambda state: state._leaves[_SPAN_REQUIRED_LEAF])
neighbor_matrix = _result_property("neighbor_matrix")
num_neighbors = _result_property("num_neighbors")
neighbor_matrix_shifts = _result_property("neighbor_matrix_shifts")
neighbor_list = _result_property("neighbor_list")
neighbor_ptr = _result_property("neighbor_ptr")
neighbor_list_shifts = _result_property("neighbor_list_shifts")
neighbor_matrix1 = _result_property("neighbor_matrix1")
num_neighbors1 = _result_property("num_neighbors1")
neighbor_matrix_shifts1 = _result_property("neighbor_matrix_shifts1")
neighbor_list1 = _result_property("neighbor_list1")
neighbor_ptr1 = _result_property("neighbor_ptr1")
neighbor_list_shifts1 = _result_property("neighbor_list_shifts1")
neighbor_matrix2 = _result_property("neighbor_matrix2")
num_neighbors2 = _result_property("num_neighbors2")
neighbor_matrix_shifts2 = _result_property("neighbor_matrix_shifts2")
neighbor_list2 = _result_property("neighbor_list2")
neighbor_ptr2 = _result_property("neighbor_ptr2")
neighbor_list_shifts2 = _result_property("neighbor_list_shifts2")
neighbor_vectors = _result_property("neighbor_vectors")
neighbor_distances = _result_property("neighbor_distances")
pair_energies = _result_property("pair_energies")
pair_forces = _result_property("pair_forces")
metadata_valid = _result_property("metadata_valid")
metadata_valid1 = _result_property("metadata_valid1")
metadata_valid2 = _result_property("metadata_valid2")
pair_offsets = _result_property("pair_offsets")
pair_counts = _result_property("pair_counts")
tile_offsets = _result_property("tile_offsets")
tile_counts = _result_property("tile_counts")
num_tiles = _result_property("num_tiles")
tile_row_group = _result_property("tile_row_group")
tile_col_group = _result_property("tile_col_group")
tile_system = _result_property("tile_system")
sorted_atom_index = _result_property("sorted_atom_index")
sorted_pos_x = _result_property("sorted_pos_x")
sorted_pos_y = _result_property("sorted_pos_y")
sorted_pos_z = _result_property("sorted_pos_z")
batch_idx_sorted = _result_property("batch_idx_sorted")
batch_ptr_padded = _result_property("batch_ptr_padded")
group_ptr = _result_property("group_ptr")
@jax.jit
def _update_cluster_lifecycle(
initialized,
valid,
failure_code,
required,
capacity,
first_failure_marker,
active,
uninitialized_preserved,
tile_overflow,
row_overflow,
coo_overflow,
tile_required,
tile_capacity,
row_required,
row_capacity,
coo_required,
coo_capacity,
) -> tuple[jax.Array, ...]:
"""Update the cluster-only sticky status in one compiled device call."""
failed = (
(tile_overflow | row_overflow | coo_overflow) & active
) | uninitialized_preserved
current_code = jnp.where(
uninitialized_preserved,
6,
jnp.where(
tile_overflow,
4,
jnp.where(row_overflow, 1, jnp.where(coo_overflow, 2, 0)),
),
).astype(jnp.int32)
status_updated = active | uninitialized_preserved
first_failure = valid & failed & status_updated
return (
initialized | active,
jnp.where(status_updated, valid & ~failed, valid),
jnp.where(first_failure, current_code, failure_code),
jnp.where(
first_failure,
jnp.where(
current_code == 4,
tile_required,
jnp.where(current_code == 1, row_required, coo_required),
),
required,
),
jnp.where(
first_failure,
jnp.where(
current_code == 4,
tile_capacity,
jnp.where(current_code == 1, row_capacity, coo_capacity),
),
capacity,
),
jnp.where(first_failure, 1, first_failure_marker).astype(jnp.int32),
)
def _validate(
positions: jax.Array,
cell: jax.Array | None,
pbc: jax.Array | None,
batch_ptr: jax.Array | None,
) -> None:
if positions.ndim != 2 or positions.shape[1] != 3:
raise ValueError("positions must have shape (N, 3)")
if pbc is not None and cell is None:
raise ValueError("cell is required when pbc is provided")
if batch_ptr is not None and batch_ptr.shape[0] < 2:
raise ValueError("batch_ptr must have length at least 2")
if cell is not None and cell.ndim not in (2, 3):
raise ValueError("cell must have shape (3, 3) or (num_systems, 3, 3)")
def _validate_span_margin(value: int | float) -> float:
if not isinstance(value, (int, float)) or isinstance(value, bool):
raise ValueError("span_margin must be a finite nonnegative scalar")
value = float(value)
if not math.isfinite(value) or value < 0:
raise ValueError("span_margin must be a finite nonnegative scalar")
return value
def _validate_atomic_density(value: Any) -> float | None:
"""Validate and normalize the optional Python scalar density hint."""
if value is None:
return None
if type(value) not in (int, float):
raise ValueError("atomic_density must be a finite positive Python int or float")
try:
density = float(value)
except OverflowError as exc:
raise ValueError(
"atomic_density must be a finite positive Python int or float"
) from exc
if not math.isfinite(density) or density <= 0:
raise ValueError("atomic_density must be a finite positive Python int or float")
return density
def _batch_indices(
batch_idx: jax.Array | None,
batch_ptr: jax.Array | None,
num_systems: int,
num_atoms: int,
) -> jax.Array:
if batch_idx is not None:
return batch_idx.astype(jnp.int32)
if batch_ptr is not None:
return jnp.repeat(
jnp.arange(num_systems, dtype=jnp.int32), batch_ptr[1:] - batch_ptr[:-1]
)
return jnp.zeros((num_atoms,), dtype=jnp.int32)
def _fixed_cell_inverse(
cell: jax.Array,
positions: jax.Array,
*,
method: str,
strategy: str,
num_systems: int,
) -> jax.Array:
"""Prepare the route-precision inverse carried by a fixed-cell state.
Cell-list count/binning and tile-cooperative naive launchers use Warp's
``wp.inverse`` implementation. Scalar JAX paths, including cluster
geometry and pair-output wrappers, use ``jnp.linalg.inv``.
"""
dtype = positions.dtype if positions.dtype == jnp.float64 else jnp.float32
cell_batch = cell if cell.ndim == 3 else cell[jnp.newaxis, :, :]
if method.startswith("batch_") and cell_batch.shape[0] == 1 and num_systems > 1:
cell_batch = jnp.broadcast_to(cell_batch, (num_systems, 3, 3))
cell_batch = cell_batch.astype(dtype)
uses_warp_inverse = "cell_list" in method or (
method in {"naive", "batch_naive"} and strategy == "tile"
)
if uses_warp_inverse:
inverse = jnp.zeros_like(cell_batch)
(inverse,) = _FIXED_CELL_INVERSE_REGISTRATION[dtype](
cell_batch,
inverse,
launch_dims=(num_systems,),
)
else:
inverse = jnp.linalg.inv(cell_batch)
return jax.lax.stop_gradient(inverse)
def _synthetic_geometry(
positions: jax.Array,
batch_idx: jax.Array,
batch_ptr: jax.Array | None,
num_systems: int,
span_capacity: jax.Array,
cutoff: float,
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array]:
"""Translate each nonperiodic system and return its fixed prepared cell."""
if positions.shape[0] == 0:
shifted = positions
minimum = jnp.zeros((num_systems, 3), dtype=positions.dtype)
maximum = jnp.zeros((num_systems, 3), dtype=positions.dtype)
spans = jnp.zeros((num_systems, 3), dtype=positions.dtype)
elif num_systems == 1:
minimum = jnp.min(positions, axis=0)
maximum = jnp.max(positions, axis=0)
shifted = positions - minimum
spans = (maximum - minimum).reshape(1, 3)
minimum = minimum.reshape(1, 3)
maximum = maximum.reshape(1, 3)
else:
minimum = jax.ops.segment_min(positions, batch_idx, num_segments=num_systems)
maximum = jax.ops.segment_max(positions, batch_idx, num_segments=num_systems)
counts = (
batch_ptr[1:] - batch_ptr[:-1]
if batch_ptr is not None
else jnp.bincount(batch_idx, length=num_systems)
)
nonempty = counts[:, None] > 0
minimum = jnp.where(nonempty, minimum, jnp.zeros_like(minimum))
maximum = jnp.where(nonempty, maximum, jnp.zeros_like(maximum))
spans = jnp.where(nonempty, maximum - minimum, jnp.zeros_like(minimum))
shifted = positions - minimum[batch_idx]
cell_lengths = span_capacity + jnp.asarray(0.1 * cutoff, dtype=positions.dtype)
headroom = jnp.maximum(cell_lengths - span_capacity, 0)
magnitude = jnp.maximum(
jnp.maximum(jnp.abs(minimum), jnp.abs(maximum)), span_capacity
)
tolerance = jnp.minimum(
jnp.asarray(4 * jnp.finfo(positions.dtype).eps, dtype=positions.dtype)
* magnitude,
jnp.asarray(0.5, dtype=positions.dtype) * headroom,
)
span_failure_axes = ~(
(spans <= span_capacity)
| ((spans - span_capacity <= tolerance) & (spans <= cell_lengths))
)
cell = cell_lengths[:, :, None] * jnp.eye(3, dtype=positions.dtype)
return (
shifted,
cell if num_systems > 1 else cell.reshape(1, 3, 3),
spans,
span_failure_axes,
)
def _prepare_cluster_state(
positions: jax.Array,
cell: jax.Array,
pbc: jax.Array,
batch_idx: jax.Array | None,
batch_ptr: jax.Array | None,
*,
method: str,
systems: int,
num_atoms: int,
cutoff: float,
cutoff2: float | None,
half_fill: bool,
wrap_positions: bool,
span_margin: float,
fixed_cell: bool,
format: str,
selective: bool,
fill_value: int,
kwargs: dict[str, Any],
capacity: int | tuple[int, int] | None,
) -> NeighborListState:
"""Allocate the fixed buffers and spec for a prepared cluster route."""
max_tiles_per_group = int(
kwargs.get("max_tiles_per_group", max(1, (num_atoms + 31) // 32))
)
if max_tiles_per_group <= 0:
raise ValueError("max_tiles_per_group must be positive")
max_neighbors = int(
kwargs.get(
"max_neighbors1" if cutoff2 is not None else "max_neighbors", num_atoms
)
)
tile_offsets = None
if format == "coo":
capacity = int(capacity if capacity is not None else num_atoms * max_neighbors)
else:
capacity = None
if method == "cluster_tile":
groups = max(1, (num_atoms + 31) // 32)
tile_capacity = groups * min(groups, max_tiles_per_group)
else:
counts = jnp.asarray(batch_ptr[1:] - batch_ptr[:-1], dtype=jnp.int32)
groups = (counts + 31) // 32
tile_capacities = groups * jnp.minimum(groups, max_tiles_per_group)
tile_offsets = jnp.concatenate(
(
jnp.zeros((1,), jnp.int32),
jnp.cumsum(tile_capacities, dtype=jnp.int32),
)
)
tile_capacity = int(tile_offsets[-1])
if (
selective
and format == "coo"
and int(capacity) != int(jnp.sum(counts) * max_neighbors)
):
raise ValueError(
"batch selective cluster-tile COO capacity must equal total_atoms * max_neighbors"
)
if format == "tile":
names = (
(
"num_tiles",
"tile_row_group",
"tile_col_group",
"sorted_atom_index",
"sorted_pos_x",
"sorted_pos_y",
"sorted_pos_z",
)
if method == "cluster_tile"
else (
"num_tiles",
"tile_row_group",
"tile_col_group",
"tile_system",
"sorted_atom_index",
"sorted_pos_x",
"sorted_pos_y",
"sorted_pos_z",
"batch_idx_sorted",
"batch_ptr_padded",
"group_ptr",
)
)
elif format == "coo" and selective:
names = (
(
"neighbor_list",
"pair_offsets",
"pair_counts",
"neighbor_list_shifts",
"num_tiles",
"tile_row_group",
"tile_col_group",
)
if method == "cluster_tile"
else (
"neighbor_list",
"pair_offsets",
"pair_counts",
"neighbor_list_shifts",
"tile_offsets",
"tile_counts",
"num_tiles",
"tile_row_group",
"tile_col_group",
"tile_system",
)
)
elif format == "coo":
names = ("neighbor_list", "neighbor_ptr", "neighbor_list_shifts")
if kwargs.get("return_distances"):
names += ("neighbor_distances",)
if kwargs.get("return_vectors"):
names += ("neighbor_vectors",)
if kwargs.get("pair_fn") is not None:
names += ("pair_energies", "pair_forces")
elif cutoff2 is not None:
names = (
"neighbor_matrix",
"num_neighbors",
"neighbor_matrix_shifts",
"neighbor_matrix2",
"num_neighbors2",
"neighbor_matrix_shifts2",
)
if selective:
names += (
("num_tiles", "tile_row_group", "tile_col_group")
if method == "cluster_tile"
else (
"tile_offsets",
"tile_counts",
"num_tiles",
"tile_row_group",
"tile_col_group",
"tile_system",
)
)
else:
names = ("neighbor_matrix", "num_neighbors", "neighbor_matrix_shifts")
if kwargs.get("return_distances"):
names += ("neighbor_distances",)
if kwargs.get("return_vectors"):
names += ("neighbor_vectors",)
if kwargs.get("pair_fn") is not None:
names += ("pair_energies", "pair_forces")
if selective:
names += (
("num_tiles", "tile_row_group", "tile_col_group")
if method == "cluster_tile"
else (
"tile_offsets",
"tile_counts",
"num_tiles",
"tile_row_group",
"tile_col_group",
"tile_system",
)
)
outputs = [jnp.zeros((0,), dtype=jnp.int32) for _ in range(12)]
output_index = {name: i for i, name in enumerate(names)}
if selective:
if "neighbor_matrix" in output_index:
outputs[output_index["neighbor_matrix"]] = jnp.full(
(num_atoms, max_neighbors), fill_value, jnp.int32
)
if "num_neighbors" in output_index:
outputs[output_index["num_neighbors"]] = jnp.zeros((num_atoms,), jnp.int32)
if "neighbor_matrix_shifts" in output_index:
outputs[output_index["neighbor_matrix_shifts"]] = jnp.zeros(
(num_atoms, max_neighbors, 3), jnp.int32
)
if cutoff2 is not None:
width2 = int(kwargs.get("max_neighbors2", num_atoms))
outputs[output_index["neighbor_matrix2"]] = jnp.full(
(num_atoms, width2), fill_value, jnp.int32
)
outputs[output_index["num_neighbors2"]] = jnp.zeros((num_atoms,), jnp.int32)
outputs[output_index["neighbor_matrix_shifts2"]] = jnp.zeros(
(num_atoms, width2, 3), jnp.int32
)
elif format == "coo":
outputs[output_index["neighbor_list"]] = jnp.zeros(
(2, int(capacity)), jnp.int32
)
outputs[output_index["pair_offsets"]] = (
jnp.asarray([0, int(capacity)], jnp.int32)
if method == "cluster_tile"
else jnp.concatenate(
(
jnp.zeros((1,), jnp.int32),
jnp.cumsum(
(batch_ptr[1:] - batch_ptr[:-1]) * max_neighbors,
dtype=jnp.int32,
),
)
)
)
outputs[output_index["pair_counts"]] = jnp.zeros((systems,), jnp.int32)
outputs[output_index["neighbor_list_shifts"]] = jnp.zeros(
(int(capacity), 3), jnp.int32
)
outputs[output_index["num_tiles"]] = jnp.zeros((1,), jnp.int32)
outputs[output_index["tile_row_group"]] = jnp.zeros((tile_capacity,), jnp.int32)
outputs[output_index["tile_col_group"]] = jnp.zeros((tile_capacity,), jnp.int32)
if method == "batch_cluster_tile":
outputs[output_index["tile_offsets"]] = tile_offsets
outputs[output_index["tile_counts"]] = jnp.zeros((systems,), jnp.int32)
outputs[output_index["tile_system"]] = jnp.zeros(
(tile_capacity,), jnp.int32
)
elif format == "matrix":
outputs[output_index["neighbor_matrix"]] = jnp.full(
(num_atoms, max_neighbors), fill_value, jnp.int32
)
outputs[output_index["num_neighbors"]] = jnp.zeros((num_atoms,), jnp.int32)
outputs[output_index["neighbor_matrix_shifts"]] = jnp.zeros(
(num_atoms, max_neighbors, 3), jnp.int32
)
if cutoff2 is not None:
width2 = int(kwargs.get("max_neighbors2", num_atoms))
outputs[output_index["neighbor_matrix2"]] = jnp.full(
(num_atoms, width2), fill_value, jnp.int32
)
outputs[output_index["num_neighbors2"]] = jnp.zeros((num_atoms,), jnp.int32)
outputs[output_index["neighbor_matrix_shifts2"]] = jnp.zeros(
(num_atoms, width2, 3), jnp.int32
)
route_options = (
("max_tiles_per_group", max_tiles_per_group),
("max_pairs", capacity),
("tile_capacity", tile_capacity),
)
shared_batch_cell = method == "batch_cluster_tile" and cell.ndim == 2
prepared_cell = (
jnp.broadcast_to(cell, (systems, 3, 3)) if shared_batch_cell else cell
)
fixed_cell_geometry = None
if fixed_cell:
from nvalchemiops.jax.neighbors._cluster_tile_preload import (
_preload_cluster_tile_build_kernel,
)
cell_batch = prepared_cell if prepared_cell.ndim == 3 else prepared_cell[None]
cell_batch = jax.lax.stop_gradient(cell_batch.astype(jnp.float32))
inverse = jax.lax.stop_gradient(jnp.linalg.inv(cell_batch))
_preload_cluster_tile_build_kernel(device_source=cell_batch, fixed_cell=True)
qr_values = jnp.zeros((systems, 15), dtype=jnp.float32)
axis_aligned = jnp.zeros((systems,), dtype=jnp.bool_)
fractional_certified = jnp.zeros((systems,), dtype=jnp.bool_)
height_certified = jnp.zeros((systems,), dtype=jnp.bool_)
bbox_cutoff_bounds = jnp.zeros((systems, 3), dtype=jnp.float32)
build_cutoff = cutoff if cutoff2 is None else max(cutoff, cutoff2)
outer_cutoff_sq = float(
jnp.asarray(build_cutoff * build_cutoff, dtype=jnp.float32)
)
(
qr_values,
axis_aligned,
fractional_certified,
height_certified,
bbox_cutoff_bounds,
) = _FIXED_CLUSTER_GEOMETRY_REGISTRATION[jnp.float32](
cell_batch,
inverse,
outer_cutoff_sq,
qr_values,
axis_aligned,
fractional_certified,
height_certified,
bbox_cutoff_bounds,
launch_dims=(systems,),
)
fixed_cell_geometry = (
inverse,
None,
None,
None,
None,
jax.lax.stop_gradient(qr_values),
jax.lax.stop_gradient(axis_aligned),
jax.lax.stop_gradient(fractional_certified),
jax.lax.stop_gradient(height_certified),
jax.lax.stop_gradient(bbox_cutoff_bounds),
)
spec = _Spec(
method=method,
strategy="auto",
format=format,
coo_layout=(
"segmented"
if format == "coo" and selective
else ("compact" if format == "coo" else None)
),
is_batched=method.startswith("batch_"),
num_atoms=num_atoms,
num_systems=systems,
cutoff=float(cutoff),
cutoff2=None if cutoff2 is None else float(cutoff2),
half_fill=bool(half_fill),
fill_value=fill_value,
wrap_positions=bool(wrap_positions),
selective=selective,
return_vectors=bool(kwargs.get("return_vectors", False)),
return_distances=bool(kwargs.get("return_distances", False)),
span_margin=float(span_margin),
synthesized_cell=False,
fixed_cell=fixed_cell,
supports_compilation=(
method != "batch_cluster_tile" and not (format == "coo" and not selective)
),
compilation_blocker=(
"batch cluster-tile sizing requires concrete batch_ptr"
if method == "batch_cluster_tile"
else (
"compact cluster-tile COO uses eager shape compaction"
if format == "coo" and not selective
else None
)
),
pair_fn=kwargs.get("pair_fn"),
result_names=tuple(names),
coo_capacity=capacity,
max_neighbors=(
max_neighbors,
int(kwargs.get("max_neighbors2", num_atoms))
if cutoff2 is not None
else None,
),
max_shifts_per_system=None,
max_atoms_per_system=None,
has_target_indices=False,
shared_batch_cell=shared_batch_cell,
route_options=route_options,
)
leaves = _StateLeaves.build(
cell=prepared_cell,
pbc=pbc,
batch_idx=(
_batch_indices(batch_idx, batch_ptr, systems, num_atoms)
if method.startswith("batch_")
else batch_idx
),
batch_ptr=batch_ptr,
fixed_cell_geometry=fixed_cell_geometry,
result=outputs,
shift_range_per_dimension=jnp.empty((0, 3), dtype=jnp.int32),
num_shifts_per_system=jnp.empty((0,), dtype=jnp.int32),
target_indices=jnp.empty((0,), dtype=jnp.int32),
span_capacity=jnp.zeros((systems, 3), positions.dtype),
span_axis=jnp.zeros((systems,), jnp.int32),
span_required=jnp.zeros((systems,), positions.dtype),
initialized=jnp.zeros((systems,), bool),
valid=jnp.ones((systems,), bool),
failure_code=jnp.zeros((systems,), jnp.int32),
required=jnp.zeros((systems,), jnp.int32),
capacity=jnp.zeros((systems,), jnp.int32),
first_failure=jnp.zeros((systems,), jnp.int32),
private_matrix1=jnp.empty((0,), dtype=jnp.int32),
private_num1=jnp.empty((0,), dtype=jnp.int32),
private_shift1=jnp.empty((0,), dtype=jnp.int32),
private_matrix2=jnp.empty((0,), dtype=jnp.int32),
private_num2=jnp.empty((0,), dtype=jnp.int32),
private_shift2=jnp.empty((0,), dtype=jnp.int32),
)
return NeighborListState(spec, leaves, token=_TOKEN)
@dataclass(frozen=True)
class _PreparedResolution:
"""Resolved fixed route choices shared by prepared-state construction."""
method: str
kwargs: dict[str, Any]
systems: int
num_atoms: int
dual: bool
cluster: bool
format: str
capacity: int | tuple[int, int] | None
fill_value: int
target_indices: jax.Array | None
num_rows: int
def _resolve_prepared_route(
positions: jax.Array,
cutoff: float,
cell: jax.Array | None,
pbc: jax.Array | None,
batch_idx: jax.Array | None,
batch_ptr: jax.Array | None,
cutoff2: float | None,
half_fill: bool,
fill_value: int | None,
return_neighbor_list: bool,
method: str | None,
wrap_positions: bool,
selective: bool,
span_margin: float,
kwargs: dict[str, Any],
) -> _PreparedResolution:
"""Validate and resolve a prepared route without allocating its state."""
automatic_method = method is None
_validate(positions, cell, pbc, batch_ptr)
if not isinstance(selective, bool):
raise ValueError("selective must be a Boolean")
if cutoff is None or cutoff <= 0:
raise ValueError("cutoff must be positive")
span_margin = _validate_span_margin(span_margin)
if method is None:
# Resolve the same eager strategy decomposition as ``neighbor_list``
# now, while preparation is intentionally eager. A prepared call must
# never repeat this host-side selection under ``jit``.
from nvalchemiops.jax.neighbors._dispatch import _auto_method_from_geometry
from nvalchemiops.neighbors.base_dispatch import neighbor_list_strategy_run_args
if batch_ptr is not None:
inferred_systems = int(batch_ptr.shape[0] - 1)
elif batch_idx is not None and batch_idx.size:
inferred_systems = int(jnp.max(batch_idx)) + 1
else:
inferred_systems = 1
strategy_name = _auto_method_from_geometry(
positions,
max(
float(cutoff), float(cutoff2) if cutoff2 is not None else float(cutoff)
),
cell,
pbc,
batch_idx,
batch_ptr,
inferred_systems,
cutoff2=cutoff2,
half_fill=half_fill,
return_neighbor_list=return_neighbor_list,
target_indices=kwargs.get("target_indices"),
return_vectors=bool(kwargs.get("return_vectors", False)),
return_distances=bool(kwargs.get("return_distances", False)),
use_pair_fn=kwargs.get("pair_fn") is not None,
rebuild_flags=(
jnp.ones((inferred_systems,), dtype=jnp.bool_) if selective else None
),
wrap_positions=wrap_positions,
)
base, native, cell_strategy, path = neighbor_list_strategy_run_args(
strategy_name
)
method = (
"batch_" if batch_idx is not None or batch_ptr is not None else ""
) + base
kwargs = {
**kwargs,
"strategy": native if base == "naive" else cell_strategy,
"atom_centric_path": path,
}
if batch_idx is not None or batch_ptr is not None:
# Match the eager dispatcher for explicit methods: a batch-owned
# geometry always selects the batched binding, including callers that
# spell the method with its single-system name. Do this before dual
# cutoff normalization so ``naive_dual_cutoff`` becomes exactly
# ``batch_naive_dual_cutoff`` rather than receiving a second prefix.
method = {
"naive": "batch_naive",
"naive_dual_cutoff": "batch_naive_dual_cutoff",
"cell_list": "batch_cell_list",
"cluster_tile": "batch_cluster_tile",
}.get(method, method)
# Cluster-tile owns its dual-matrix implementation. Only naive routes
# are renamed to their distinct dual-cutoff entry points.
if (
cutoff2 is not None
and "dual_cutoff" not in method
and method not in {"cluster_tile", "batch_cluster_tile"}
):
method = (
"batch_naive_dual_cutoff"
if method.startswith("batch_")
else "naive_dual_cutoff"
)
if method not in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
"cell_list",
"batch_cell_list",
"cluster_tile",
"batch_cluster_tile",
}:
raise NotImplementedError(
f"unsupported prepared JAX neighbor-list method {method!r}"
)
if (
not automatic_method
and method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
and kwargs.get("strategy", "auto") == "auto"
):
# Explicit-family preparation pins strategy=auto to scalar so reuse
# cannot re-run route selection. method=None keeps its calibrated
# strategy choice resolved above.
kwargs = {**kwargs, "strategy": "scalar"}
if (
automatic_method
and selective
and method
in {"naive", "batch_naive", "naive_dual_cutoff", "batch_naive_dual_cutoff"}
and kwargs.get("strategy") == "tile"
):
# The automatic selector may rank the direct tiled kernel first for a
# small geometry. Selective execution has no tiled kernel, so resolve
# that automatic choice to the supported scalar implementation while
# retaining the explicit strategy='tile' rejection below.
kwargs = {**kwargs, "strategy": "scalar"}
if cutoff2 is None and "dual_cutoff" in method:
raise ValueError("cutoff2 must be provided for a dual-cutoff prepared state")
if cutoff2 is not None and float(cutoff) > float(cutoff2):
raise ValueError(
"cutoff must not exceed cutoff2 for a dual-cutoff prepared state"
)
unknown = set(kwargs) - _CONFIG_KEYS
if unknown:
raise TypeError(f"unexpected prepared neighbor-list options: {sorted(unknown)}")
if kwargs.get("pair_params") is not None:
raise ValueError(
"pair_params is a runtime input; do not pass it to prepare_neighbor_list"
)
if method in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
} and kwargs.get("strategy", "auto") not in {"auto", "scalar", "tile"}:
raise ValueError("unsupported naive strategy")
if method in {"cell_list", "batch_cell_list"}:
from nvalchemiops.jax.neighbors._resolution import resolve_cell_strategy
selected_cell_strategy = kwargs.get("strategy", "auto")
automatic_cell_strategy = selected_cell_strategy == "auto"
selected_cell_strategy = resolve_cell_strategy(
selected_cell_strategy,
total_atoms=int(positions.shape[0]),
cutoff=float(cutoff),
device_is_cpu=all(
device.platform == "cpu" for device in positions.devices()
),
half_fill=bool(half_fill),
)
if (
automatic_cell_strategy
and selected_cell_strategy == "pair_centric"
and (
kwargs.get("return_vectors")
or kwargs.get("return_distances")
or kwargs.get("pair_fn") is not None
)
):
# The atom-centric equivalent is already compilation eligible;
# prefer it for automatic preparation with pair outputs.
selected_cell_strategy = "atom_centric"
kwargs = {**kwargs, "strategy": selected_cell_strategy}
if batch_ptr is not None:
systems = int(batch_ptr.shape[0] - 1)
elif batch_idx is not None and batch_idx.size:
systems = int(jnp.max(batch_idx)) + 1
else:
systems = 1
n = int(positions.shape[0])
dual = cutoff2 is not None
if (
method in {"cell_list", "batch_cell_list"}
and return_neighbor_list
and kwargs.get("coo_capacity") is None
):
raise ValueError("coo_capacity is required for a prepared fixed-COO state")
cluster = method in {"cluster_tile", "batch_cluster_tile"}
derived_format = "coo" if return_neighbor_list else "matrix"
explicit_format = kwargs.get("format")
if (
explicit_format is not None
and explicit_format != derived_format
and not (explicit_format == "tile" and not return_neighbor_list and cluster)
):
raise ValueError(
"format contradicts return_neighbor_list; omit format or use "
f"format={derived_format!r}"
)
fmt = derived_format if explicit_format is None else explicit_format
if fmt not in {"matrix", "coo", "tile"}:
raise ValueError("format must be one of 'matrix', 'coo', or 'tile'")
if fmt == "tile" and not cluster:
raise ValueError("format='tile' requires a cluster_tile method")
if (
automatic_method
and cluster
and cutoff2 is None
and fmt != "tile"
and (fmt == "matrix" or kwargs.get("coo_capacity") is not None)
):
# The selector's cluster result is performance-oriented. Prepared
# execution instead prefers the existing compiled atom-centric cell
# route when its fixed output representation is equivalent.
method = "batch_cell_list" if method.startswith("batch_") else "cell_list"
kwargs = {**kwargs, "strategy": "atom_centric"}
cluster = False
capacity = kwargs.get("coo_capacity")
if fmt == "coo" and capacity is None and not cluster:
raise ValueError("coo_capacity is required for a prepared fixed-COO state")
if not cluster and capacity is not None:
from nvalchemiops.jax.neighbors.neighbor_utils import _validate_coo_capacities
normalized_capacity = _validate_coo_capacities(
capacity, fmt == "coo", num_cutoffs=2 if dual else 1
)
capacity = normalized_capacity if dual else normalized_capacity[0]
naive_method = method in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
if (
selective
and fmt != "matrix"
and not cluster
and not (naive_method and fmt == "coo")
):
raise NotImplementedError(
"prepared selective execution supports matrix routes only"
)
target_indices = kwargs.get("target_indices")
if target_indices is not None:
target_indices = jnp.asarray(target_indices, dtype=jnp.int32)
if target_indices.ndim != 1:
raise ValueError("target_indices must be a one-dimensional array")
if method in {"naive", "batch_naive"} and kwargs.get("strategy") == "tile":
if all(device.platform == "cpu" for device in positions.devices()):
raise ValueError(
"strategy='tile' requires CUDA; the tile-cooperative naive kernel cannot run on a CPU device (Warp forces block_dim=1). Use strategy='scalar' or 'auto' on CPU."
)
if (
kwargs.get("return_distances")
or kwargs.get("return_vectors")
or kwargs.get("pair_fn") is not None
):
raise NotImplementedError(
"strategy='tile' has no pair-output (return_distances / return_vectors / pair_fn) variant; use strategy='scalar'."
)
if selective:
raise NotImplementedError(
"strategy='tile' has no selective (rebuild_flags) variant; use strategy='scalar'."
)
if (
method == "batch_naive"
and pbc is not None
and not wrap_positions
and target_indices is None
):
raise NotImplementedError(
"strategy='tile' has no batched prewrapped-PBC tiled kernel (wrap_positions=False with PBC). Use strategy='scalar', or wrap_positions=True for the tile path."
)
if selective and (
target_indices is not None
or kwargs.get("return_vectors")
or kwargs.get("return_distances")
or kwargs.get("pair_fn") is not None
):
raise NotImplementedError(
"partial and pair-output prepared states do not support selective execution"
)
if cutoff2 is not None and (
target_indices is not None
or kwargs.get("pair_fn") is not None
or kwargs.get("return_vectors")
or kwargs.get("return_distances")
or (cluster and fmt != "matrix")
):
raise NotImplementedError(
"dual-cutoff prepared states do not support target_indices or pair outputs"
)
if (
method in {"cell_list", "batch_cell_list"}
and kwargs.get("strategy") == "pair_centric"
and target_indices is not None
):
raise NotImplementedError(
"strategy='pair_centric' with target_indices (partial neighbor lists) is not supported"
)
if cluster:
if pbc is None or not bool(jnp.all(pbc)):
raise NotImplementedError(
"cluster_tile prepared states require fully periodic pbc"
)
if half_fill:
raise NotImplementedError(
"cluster_tile prepared states do not support half_fill"
)
if target_indices is not None:
raise NotImplementedError(
"cluster_tile prepared states do not support target_indices"
)
if selective and fmt == "tile":
raise NotImplementedError(
"cluster_tile tile output does not support selective execution"
)
if (fmt == "tile" or selective) and (
kwargs.get("return_vectors")
or kwargs.get("return_distances")
or kwargs.get("pair_fn") is not None
):
raise NotImplementedError(
"cluster_tile geometry and pair outputs do not support tile or selective execution"
)
fv = n if fill_value is None else int(fill_value)
num_rows = n if target_indices is None else int(target_indices.shape[0])
return _PreparedResolution(
method=method,
kwargs=kwargs,
systems=systems,
num_atoms=n,
dual=dual,
cluster=cluster,
format=fmt,
capacity=capacity,
fill_value=fv,
target_indices=target_indices,
num_rows=num_rows,
)
[docs]
def prepare_neighbor_list(
positions: jax.Array,
cutoff: float,
cell: jax.Array | None = None,
pbc: jax.Array | None = None,
batch_idx: jax.Array | None = None,
batch_ptr: jax.Array | None = None,
cutoff2: float | None = None,
half_fill: bool = False,
fill_value: int | None = None,
return_neighbor_list: bool = False,
method: str | None = None,
format: str | None = None,
wrap_positions: bool = True,
*,
max_neighbors: int | None = None,
max_neighbors1: int | None = None,
max_neighbors2: int | None = None,
atomic_density: float | None = None,
coo_capacity: int | tuple[int, int] | None = None,
max_tiles_per_group: int | None = None,
return_vectors: bool = False,
return_distances: bool = False,
selective: bool = False,
pair_fn: Any = None,
span_margin: float = 0.0,
target_indices: jax.Array | None = None,
strategy: str = "auto",
atom_centric_path: str = "auto",
fixed_cell: bool = False,
**kwargs: Any,
) -> NeighborListState:
"""Resolve and allocate a reusable JAX neighbor-list route.
Preparation runs eagerly. It validates fixed metadata, resolves automatic
method and strategy selection once, and allocates the arrays carried by
the immutable state. Automatic selection may prefer an existing
compilation-eligible equivalent; inspect ``state.method`` and
``state.strategy``, or pass explicit choices to pin the implementation.
Execute the state with
``results, state = neighbor_list(..., state=state)``.
Parameters
----------
positions : jax.Array, shape (total_atoms, 3)
Exemplar positions. Atom count and order, shape, and dtype become part
of the prepared contract; coordinate values may change at execution.
cutoff : float
Primary neighbor cutoff.
cell : jax.Array, shape (3, 3) or (num_systems, 3, 3), optional
Exemplar cell. If omitted for a resolved cell-list route, preparation
creates a fixed nonperiodic bounding cell. Applicable cell values may
change later without changing shape, dtype, device, or PBC pattern.
pbc : jax.Array, shape (3,) or (num_systems, 3), dtype=bool, optional
Fixed periodic-axis pattern associated with ``cell``.
batch_idx, batch_ptr : jax.Array, dtype=int32, optional
Fixed, system-contiguous batch ownership metadata.
cutoff2 : float, optional
Secondary cutoff for an existing dual-cutoff route. It must be greater
than or equal to ``cutoff``.
half_fill : bool, default=False
Store one directed half of each pair where the route supports it.
fill_value : int, optional
Neighbor-matrix padding value. Defaults to ``total_atoms``.
return_neighbor_list : bool, default=False
Request the route's COO representation instead of matrix output.
method : str, optional
Method accepted by :func:`neighbor_list`, including batched and
dual-cutoff names. ``None`` runs automatic selection once during
preparation and may choose a compilation-eligible equivalent route.
format : {"matrix", "coo", "tile"}, optional
Fixed result representation. If omitted, matrix versus COO is derived
from ``return_neighbor_list``. An explicit matrix or COO value must
agree with that flag; tile output requires a nonselective cluster-tile
route.
wrap_positions : bool, default=True
Whether applicable naive routes wrap positions before enumeration.
max_neighbors : int, optional
Single-cutoff row width, or the primary width where the direct route
uses that spelling.
max_neighbors1, max_neighbors2 : int, optional
Independent primary and secondary row widths for dual-cutoff routes.
atomic_density : float, optional
Finite positive Python ``int`` or ``float`` in atoms per unit volume,
using the same length units as positions and cutoffs. Used during
preparation to estimate only omitted neighbor widths; explicit widths
take precedence. ``None`` keeps the existing atom-count-based defaults.
Ordinary dual-cutoff routes estimate each missing width at its cutoff;
cluster routes use the outer cutoff with a 32-neighbor floor. These
estimates are capacity heuristics.
coo_capacity : int or tuple of int, optional
Fixed JAX COO capacity, required for prepared non-cluster COO. A scalar
applies to both dual-cutoff groups; a pair sets them independently.
max_tiles_per_group : int, optional
Static cluster-tile capacity bound.
return_vectors, return_distances : bool, default=False
Request the corresponding existing pair-geometry outputs.
selective : bool, default=False
Prepare a state for runtime per-system ``rebuild_flags``. The flags
are execution inputs and are never stored in the prepared state.
pair_fn : callable, optional
Warp/JAX pair function using the direct neighbor-route callback
contract. Runtime ``pair_params`` are supplied to
:func:`neighbor_list`.
span_margin : float, default=0.0
Finite nonnegative per-axis span-growth allowance for a synthesized
nonperiodic cell-list cell. Runtime span is bounded by exemplar span
plus this value, with a small dtype-dependent rounding allowance
capped by the synthesized cell's remaining headroom. Equally small
real expansion can also pass. A nonzero value requires ``cell=None``
and a resolved cell-list route. This is not a neighbor-list skin.
fixed_cell : bool, default=False
Reuse cell-dependent topology geometry prepared from ``cell``. When
true, the caller promises that runtime cell values remain unchanged;
this promise is not checked by comparing cell values.
target_indices : jax.Array, shape (num_rows,), dtype=int32, optional
Fixed compact source-row selection for routes that support partial
rows.
strategy, atom_centric_path : str, default="auto"
Route-specific implementation choices resolved once during
preparation. Explicit values are never silently substituted.
**kwargs : Any
Additional fixed options already accepted by the resolved direct
route, such as cell-list sizing metadata. Runtime ``rebuild_flags``
and ``pair_params`` are rejected during preparation; unknown names
raise ``TypeError``.
Returns
-------
NeighborListState
Immutable state for repeated JAX execution.
Notes
-----
``supports_compilation`` reports route eligibility rather than compiler
history or a guarantee for every environment. A non-``None``
``compilation_blocker`` explains known route ineligibility.
Prepared state is a convenience interface. Method-specific functions with
explicit buffers and launch metadata remain the lower-overhead JAX option
when call latency is critical.
Unsupported prepared combinations include selective pair outputs or
partial rows on every route, selective cell-list COO, pair-centric cell
lists with partial rows, and dual-cutoff pair outputs or partial rows.
Explicit tiled-naive execution does not support selective execution or
pair outputs. Full-row batched prewrapped PBC remains unsupported; partial
topology-only naive tile supports wrapped and prewrapped PBC. Cluster-tile
preparation follows the direct route's CUDA, float32, fully periodic, and
``half_fill=False`` prerequisites; it does not support partial rows,
selective tile or pair output, or pair output with tile format.
Prepared batched cluster-tile execution, nonselective compact cluster-tile
COO, and pair-centric cell-list geometry or callback output are eager-only.
See Also
--------
neighbor_list : Execute the prepared state and return its successor.
NeighborListState : Inspect resolved configuration, results, and status.
check_neighbor_list_state : Convert sticky device status to an exception.
"""
atomic_density = _validate_atomic_density(atomic_density)
if not isinstance(fixed_cell, bool):
raise ValueError("fixed_cell must be a Python bool")
if fixed_cell and cell is None:
raise ValueError("fixed_cell=True requires an explicit cell")
explicit_options = {
"format": format,
"max_neighbors": max_neighbors,
"max_neighbors1": max_neighbors1,
"max_neighbors2": max_neighbors2,
"coo_capacity": coo_capacity,
"max_tiles_per_group": max_tiles_per_group,
"pair_fn": pair_fn,
"target_indices": target_indices,
}
kwargs = {
**kwargs,
**{
name: value for name, value in explicit_options.items() if value is not None
},
"return_vectors": return_vectors,
"return_distances": return_distances,
"span_margin": span_margin,
"strategy": strategy,
"atom_centric_path": atom_centric_path,
}
resolution = _resolve_prepared_route(
positions,
cutoff,
cell,
pbc,
batch_idx,
batch_ptr,
cutoff2,
half_fill,
fill_value,
return_neighbor_list,
method,
wrap_positions,
selective,
span_margin,
kwargs,
)
method = resolution.method
kwargs = dict(resolution.kwargs)
systems = resolution.systems
n = resolution.num_atoms
cluster = resolution.cluster
fmt = resolution.format
capacity = resolution.capacity
fv = resolution.fill_value
if atomic_density is not None:
from nvalchemiops.neighbors.neighbor_utils import estimate_max_neighbors
if cluster:
if cutoff2 is None:
if kwargs.get("max_neighbors") is None:
estimate = estimate_max_neighbors(
float(cutoff),
atomic_density=atomic_density,
max_neighbors_lower_bound=32,
)
kwargs["max_neighbors"] = estimate
else:
missing_primary = kwargs.get("max_neighbors1") is None
missing_secondary = kwargs.get("max_neighbors2") is None
if missing_primary or missing_secondary:
estimate_cutoff = max(float(cutoff), float(cutoff2))
estimate = estimate_max_neighbors(
estimate_cutoff,
atomic_density=atomic_density,
max_neighbors_lower_bound=32,
)
if missing_primary:
kwargs["max_neighbors1"] = estimate
if missing_secondary:
kwargs["max_neighbors2"] = estimate
elif cutoff2 is None:
if kwargs.get("max_neighbors") is None:
kwargs["max_neighbors"] = estimate_max_neighbors(
float(cutoff), atomic_density=atomic_density
)
else:
if kwargs.get("max_neighbors1") is None:
kwargs["max_neighbors1"] = estimate_max_neighbors(
float(cutoff), atomic_density=atomic_density
)
if kwargs.get("max_neighbors2") is None:
kwargs["max_neighbors2"] = estimate_max_neighbors(
float(cutoff2), atomic_density=atomic_density
)
if span_margin and not (
cell is None and method in {"cell_list", "batch_cell_list"}
):
raise ValueError(
"nonzero span_margin requires cell=None and a resolved nonperiodic cell-list method"
)
if cluster:
return _prepare_cluster_state(
positions,
cell,
pbc,
batch_idx,
batch_ptr,
method=method,
systems=systems,
num_atoms=n,
cutoff=float(cutoff),
cutoff2=None if cutoff2 is None else float(cutoff2),
half_fill=bool(half_fill),
wrap_positions=bool(wrap_positions),
span_margin=span_margin,
fixed_cell=fixed_cell,
format=fmt,
selective=selective,
fill_value=fv,
kwargs=kwargs,
capacity=capacity,
)
return _build_noncluster_state(
positions,
cell,
pbc,
batch_idx,
batch_ptr,
cutoff,
cutoff2,
half_fill,
wrap_positions,
selective,
span_margin,
fixed_cell,
kwargs,
resolution,
)
def _build_noncluster_state(
positions: jax.Array,
cell: jax.Array | None,
pbc: jax.Array | None,
batch_idx: jax.Array | None,
batch_ptr: jax.Array | None,
cutoff: float,
cutoff2: float | None,
half_fill: bool,
wrap_positions: bool,
selective: bool,
span_margin: float,
fixed_cell: bool,
kwargs: dict[str, Any],
resolution: _PreparedResolution,
) -> NeighborListState:
"""Allocate and assemble the resolved non-cluster prepared state."""
method = resolution.method
systems = resolution.systems
n = resolution.num_atoms
dual = resolution.dual
fmt = resolution.format
capacity = resolution.capacity
fv = resolution.fill_value
target_indices = resolution.target_indices
num_rows = resolution.num_rows
naive_method = method in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
returns_shifts = pbc is not None or method in {"cell_list", "batch_cell_list"}
names = (
("neighbor_list", "neighbor_ptr", "num_neighbors", "metadata_valid")
if fmt == "coo"
else ("neighbor_matrix", "num_neighbors")
)
if returns_shifts:
if fmt == "coo":
names = (
"neighbor_list",
"neighbor_ptr",
"neighbor_list_shifts",
"num_neighbors",
"metadata_valid",
)
else:
names += ("neighbor_matrix_shifts",)
if kwargs.get("return_distances"):
names += ("neighbor_distances",)
if kwargs.get("return_vectors"):
names += ("neighbor_vectors",)
if kwargs.get("pair_fn") is not None:
names += ("pair_energies", "pair_forces")
if dual:
names += (
("neighbor_list2", "neighbor_ptr2", "num_neighbors2", "metadata_valid2")
if fmt == "coo"
else ("neighbor_matrix2", "num_neighbors2")
)
if returns_shifts:
if fmt == "coo":
# Fixed COO keeps shifts before the count/metadata pair.
names = names[:-4] + (
"neighbor_list2",
"neighbor_ptr2",
"neighbor_list_shifts2",
"num_neighbors2",
"metadata_valid2",
)
else:
names += ("neighbor_matrix_shifts2",)
width = int(kwargs.get("max_neighbors1" if dual else "max_neighbors", n))
width2 = int(kwargs.get("max_neighbors2", n))
if fmt == "matrix":
outputs = [
jnp.full((num_rows, width), fv, jnp.int32),
jnp.zeros((num_rows,), jnp.int32),
]
if returns_shifts:
outputs.append(jnp.zeros((num_rows, width, 3), jnp.int32))
if kwargs.get("return_distances"):
outputs.append(jnp.zeros((num_rows, width), positions.dtype))
if kwargs.get("return_vectors"):
outputs.append(jnp.zeros((num_rows, width, 3), positions.dtype))
if kwargs.get("pair_fn") is not None:
outputs += [
jnp.zeros((num_rows, width), positions.dtype),
jnp.zeros((num_rows, width, 3), positions.dtype),
]
if dual:
outputs += [
jnp.full((n, width2), fv, jnp.int32),
jnp.zeros((n,), jnp.int32),
]
if returns_shifts:
outputs.append(jnp.zeros((n, width2, 3), jnp.int32))
else:
c1, c2 = (
(capacity if isinstance(capacity, tuple) else (capacity, capacity))
if dual
else (capacity, None)
)
outputs = [
jnp.zeros((2, int(c1)), jnp.int32),
jnp.zeros((num_rows + 1,), jnp.int32),
]
if returns_shifts:
outputs.append(jnp.zeros((int(c1), 3), jnp.int32))
outputs.extend(
(
jnp.zeros((num_rows,), jnp.int32),
jnp.asarray(True, dtype=jnp.bool_),
)
)
if kwargs.get("return_distances"):
outputs.append(jnp.zeros((int(c1),), positions.dtype))
if kwargs.get("return_vectors"):
outputs.append(jnp.zeros((int(c1), 3), positions.dtype))
if kwargs.get("pair_fn") is not None:
outputs += [
jnp.zeros((int(c1),), positions.dtype),
jnp.zeros((int(c1), 3), positions.dtype),
]
if dual:
outputs += [
jnp.zeros((2, int(c2)), jnp.int32),
jnp.zeros((n + 1,), jnp.int32),
]
if returns_shifts:
outputs.append(jnp.zeros((int(c2), 3), jnp.int32))
outputs.extend(
(
jnp.zeros((n,), jnp.int32),
jnp.asarray(True, dtype=jnp.bool_),
)
)
synthesized = method in {"cell_list", "batch_cell_list"} and cell is None
resolved_batch_idx = _batch_indices(batch_idx, batch_ptr, systems, n)
span_capacity = jnp.zeros((systems, 3), dtype=positions.dtype)
route_options = ()
fixed_cell_geometry = None
needs_fixed_inverse = fixed_cell and (
method in {"cell_list", "batch_cell_list"}
or (pbc is not None and wrap_positions)
)
fixed_inverse = (
_fixed_cell_inverse(
cell,
positions,
method=method,
strategy=str(kwargs.get("strategy", "auto")),
num_systems=systems,
)
if needs_fixed_inverse
else None
)
if fixed_cell:
fixed_cell_geometry = (
fixed_inverse,
None,
None,
None,
None,
None,
None,
None,
None,
None,
)
if method in {"cell_list", "batch_cell_list"}:
if synthesized:
shifted_positions, cell, spans, _span_failure_axes = _synthetic_geometry(
positions,
resolved_batch_idx,
batch_ptr,
systems,
jnp.zeros((systems, 3), dtype=positions.dtype),
cutoff,
)
span_capacity = spans + jnp.asarray(span_margin, dtype=positions.dtype)
# Keep cells strictly positive for empty and zero-span systems.
cell = (span_capacity + jnp.asarray(0.1 * cutoff, dtype=positions.dtype))[
:, :, None
] * jnp.eye(3, dtype=positions.dtype)
pbc = jnp.zeros((systems, 3), dtype=jnp.bool_)
if systems == 1:
pbc = pbc.reshape(3)
elif span_margin:
raise ValueError(
"nonzero span_margin requires cell=None and a resolved nonperiodic cell-list method"
)
from nvalchemiops.neighbors.cell_list import (
compute_batch_pair_centric_n_outer,
)
prepared_positions = shifted_positions if synthesized else positions
configured_max_cells = kwargs.get("max_total_cells")
atom_centric_path = kwargs.get("atom_centric_path", "auto")
if method == "batch_cell_list":
from nvalchemiops.jax.neighbors.batch_cell_list import (
batch_build_cell_list,
estimate_batch_cell_list_sizes,
)
if configured_max_cells is None:
configured_max_cells, _, _ = estimate_batch_cell_list_sizes(
prepared_positions,
batch_ptr=batch_ptr,
batch_idx=resolved_batch_idx,
cell=cell,
cutoff=cutoff,
pbc=pbc,
)
max_cells = int(configured_max_cells)
build_result = batch_build_cell_list(
prepared_positions,
batch_idx=resolved_batch_idx,
batch_ptr=batch_ptr,
cell=cell,
pbc=pbc,
cutoff=cutoff,
max_total_cells=max_cells,
)
cells_dim = build_result[0]
search_radius = build_result[6]
if fixed_cell:
cells_per_system = jnp.prod(cells_dim, axis=1, dtype=jnp.int32)
cell_offsets = jnp.concatenate(
(
jnp.zeros((1,), dtype=jnp.int32),
jnp.cumsum(cells_per_system[:-1], dtype=jnp.int32),
)
)
fixed_cell_geometry = (
fixed_inverse,
jax.lax.stop_gradient(cells_dim),
jax.lax.stop_gradient(search_radius),
jax.lax.stop_gradient(cells_per_system),
jax.lax.stop_gradient(cell_offsets),
None,
None,
None,
None,
None,
)
total_cells = int(jnp.sum(jnp.prod(cells_dim, axis=1)))
radius_max = tuple(
int(value) for value in jnp.max(search_radius, axis=0).tolist()
)
n_outer = compute_batch_pair_centric_n_outer(radius_max, False)
route_options = (
("max_total_cells", max_cells),
("atom_centric_path", atom_centric_path),
("pair_centric_total_cells", total_cells),
("pair_centric_n_outer", n_outer),
("pair_centric_r_max", radius_max),
)
else:
from nvalchemiops.jax.neighbors.cell_list import (
build_cell_list,
estimate_cell_list_sizes,
)
if configured_max_cells is None:
configured_max_cells, _, _ = estimate_cell_list_sizes(
prepared_positions, cell, cutoff, pbc
)
max_cells = int(configured_max_cells)
build_result = build_cell_list(
prepared_positions,
cutoff,
cell,
pbc,
max_total_cells=max_cells,
)
cells_dim = build_result[0]
search_radius = build_result[6]
if fixed_cell:
fixed_cell_geometry = (
fixed_inverse,
jax.lax.stop_gradient(cells_dim.reshape((1, 3))),
jax.lax.stop_gradient(search_radius.reshape((1, 3))),
None,
None,
None,
None,
None,
None,
None,
)
radius = tuple(int(value) for value in search_radius.tolist())
n_outer = compute_batch_pair_centric_n_outer(radius, bool(half_fill))
route_options = (
("max_total_cells", max_cells),
("atom_centric_path", atom_centric_path),
("pair_centric_n_outer", n_outer),
)
if method not in {"cell_list", "batch_cell_list"} and span_margin:
raise ValueError(
"nonzero span_margin requires cell=None and a resolved nonperiodic cell-list method"
)
eager_only = (
method in {"cell_list", "batch_cell_list"}
and kwargs.get("strategy") == "pair_centric"
and (
kwargs.get("return_vectors")
or kwargs.get("return_distances")
or kwargs.get("pair_fn") is not None
)
)
shift_range_per_dimension = None
num_shifts_per_system = None
max_shifts_per_system = None
max_atoms_per_system = None
if (
method
in {"naive", "batch_naive", "naive_dual_cutoff", "batch_naive_dual_cutoff"}
and pbc is not None
):
from nvalchemiops.jax.neighbors.neighbor_utils import compute_naive_num_shifts
shift_cell = cell if cell.ndim == 3 else cell[None]
shift_pbc = pbc if pbc.ndim == 2 else pbc[None]
shift_range_per_dimension, num_shifts_per_system, max_shifts_per_system = (
compute_naive_num_shifts(
shift_cell, float(cutoff2 if dual else cutoff), shift_pbc
)
)
if method.startswith("batch_"):
max_atoms_per_system = int(kwargs.get("max_atoms_per_system", n))
spec = _Spec(
method=method,
strategy=kwargs.get("strategy", "auto"),
format=fmt,
coo_layout="compact" if fmt == "coo" else None,
is_batched=method.startswith("batch_"),
num_atoms=n,
num_systems=systems,
cutoff=float(cutoff),
cutoff2=None if cutoff2 is None else float(cutoff2),
half_fill=bool(half_fill),
fill_value=fv,
wrap_positions=bool(wrap_positions),
selective=selective,
return_vectors=bool(kwargs.get("return_vectors", False)),
return_distances=bool(kwargs.get("return_distances", False)),
span_margin=span_margin,
synthesized_cell=synthesized,
fixed_cell=fixed_cell,
supports_compilation=not eager_only,
compilation_blocker=(
"pair-centric cell geometry and pair outputs are eager-only"
if eager_only
else None
),
pair_fn=kwargs.get("pair_fn"),
result_names=tuple(names),
coo_capacity=capacity,
max_neighbors=(width, width2 if dual else None),
max_shifts_per_system=max_shifts_per_system,
max_atoms_per_system=max_atoms_per_system,
has_target_indices=target_indices is not None,
shared_batch_cell=False,
route_options=route_options,
)
outputs += [None for _ in range(12 - len(outputs))]
selective_fixed_naive = selective and naive_method and fmt == "coo"
if selective_fixed_naive:
private_matrix1 = jnp.full((n, width), fv, dtype=jnp.int32)
private_num1 = jnp.zeros((n,), dtype=jnp.int32)
private_shift1 = (
jnp.zeros((n, width, 3), dtype=jnp.int32) if pbc is not None else None
)
if dual:
private_matrix2 = jnp.full((n, width2), fv, dtype=jnp.int32)
private_num2 = jnp.zeros((n,), dtype=jnp.int32)
private_shift2 = (
jnp.zeros((n, width2, 3), dtype=jnp.int32) if pbc is not None else None
)
else:
private_matrix2 = None
private_num2 = None
private_shift2 = None
else:
private_matrix1 = None
private_num1 = None
private_shift1 = None
private_matrix2 = None
private_num2 = None
private_shift2 = None
fixed_targets = target_indices
leaves = _StateLeaves.build(
cell=cell,
pbc=pbc,
batch_idx=resolved_batch_idx if method.startswith("batch_") else batch_idx,
batch_ptr=batch_ptr,
fixed_cell_geometry=fixed_cell_geometry,
result=outputs,
shift_range_per_dimension=shift_range_per_dimension,
num_shifts_per_system=num_shifts_per_system,
target_indices=fixed_targets,
span_capacity=span_capacity,
span_axis=jnp.zeros((systems,), jnp.int32),
span_required=jnp.zeros((systems,), positions.dtype),
initialized=jnp.zeros((systems,), bool),
valid=jnp.ones((systems,), bool),
failure_code=jnp.zeros((systems,), jnp.int32),
required=jnp.zeros((systems,), jnp.int32),
capacity=jnp.zeros((systems,), jnp.int32),
first_failure=jnp.zeros((systems,), jnp.int32),
private_matrix1=private_matrix1,
private_num1=private_num1,
private_shift1=private_shift1,
private_matrix2=private_matrix2,
private_num2=private_num2,
private_shift2=private_shift2,
)
return NeighborListState(spec, leaves, token=_TOKEN)
def _execute_prepared_cluster_route(
positions: jax.Array,
cell: jax.Array | None,
state: NeighborListState,
kwargs: dict[str, Any],
a: tuple[Any, ...],
leaf_view: _StateLeaves,
active: jax.Array,
uninitialized_preserved: jax.Array,
) -> tuple[tuple[Any, ...], NeighborListState]:
"""Execute and publish a prepared cluster-tile route."""
from nvalchemiops.jax.neighbors.batch_cluster_tile import (
batch_cluster_tile_neighbor_list,
)
from nvalchemiops.jax.neighbors.cluster_tile import cluster_tile_neighbor_list
options = dict(state._spec.route_options)
call = {
"max_neighbors": state._spec.max_neighbors[0],
"fill_value": state.fill_value,
"format": state.format,
"max_tiles_per_group": options["max_tiles_per_group"],
"cutoff2": state.cutoff2,
"_return_status": True,
}
if state.fixed_cell:
call["_fixed_cell_geometry"] = leaf_view.fixed_cell_geometry
if state.selective:
if "rebuild_flags" not in kwargs:
raise ValueError("cannot preserve uninitialized system 0")
active = kwargs["rebuild_flags"].astype(jnp.bool_).reshape(state.num_systems)
uninitialized_preserved = ~leaf_view.initialized & ~active
def old(name: str) -> Any:
return (
_result_leaf(state, name, a) if name in state._spec.result_names else None
)
if state.cutoff2 is not None and state.format == "matrix":
call.update(
previous_neighbor_matrix=old("neighbor_matrix"),
previous_num_neighbors=old("num_neighbors"),
previous_neighbor_matrix_shifts=old("neighbor_matrix_shifts"),
previous_neighbor_matrix2=old("neighbor_matrix2"),
previous_num_neighbors2=old("num_neighbors2"),
previous_neighbor_matrix_shifts2=old("neighbor_matrix_shifts2"),
)
if state.cutoff2 is None:
call.update(
return_vectors=state.return_vectors,
return_distances=state.return_distances,
pair_fn=state._spec.pair_fn,
pair_params=kwargs.get("pair_params"),
)
if state.format == "coo":
call["max_pairs"] = options["max_pairs"]
if state.selective:
call["rebuild_flags"] = active if state.is_batched else active[:1]
call.update(
previous_num_tiles=old("num_tiles"),
previous_tile_row_group=old("tile_row_group"),
previous_tile_col_group=old("tile_col_group"),
)
if state.is_batched:
call.update(
tile_offsets=old("tile_offsets"),
previous_tile_counts=old("tile_counts"),
previous_tile_system=old("tile_system"),
)
if state.format == "coo":
call.update(
pair_offsets=old("pair_offsets"),
previous_pair_counts=old("pair_counts"),
previous_neighbor_list=old("neighbor_list"),
previous_neighbor_list_shifts=old("neighbor_list_shifts"),
)
else:
call.update(
previous_neighbor_matrix=old("neighbor_matrix"),
previous_num_neighbors=old("num_neighbors"),
previous_neighbor_matrix_shifts=old("neighbor_matrix_shifts"),
)
if state.is_batched:
call["batch_ptr"] = leaf_view.batch_ptr
runtime_cell = cell if cell is not None else leaf_view.cell
if state._spec.shared_batch_cell and runtime_cell.ndim == 2:
runtime_cell = jnp.broadcast_to(runtime_cell, leaf_view.cell.shape)
raw = batch_cluster_tile_neighbor_list(
positions, state.cutoff, runtime_cell, **call
)
else:
raw = cluster_tile_neighbor_list(
positions,
state.cutoff,
cell if cell is not None else leaf_view.cell,
**call,
)
result, status_tail = raw[:-6], raw[-6:]
(
tile_overflow,
coo_overflow,
tile_required,
tile_capacity,
coo_required,
coo_capacity,
) = status_tail
matrix_overflow = jnp.zeros_like(jnp.asarray(coo_overflow))
matrix_selected = jnp.zeros_like(matrix_overflow)
matrix_required = jnp.zeros_like(jnp.asarray(coo_required))
matrix_capacity = jnp.zeros_like(jnp.asarray(coo_capacity))
if state.format == "matrix":
for suffix in ("", "2"):
count_name = f"num_neighbors{suffix}"
if count_name not in state._spec.result_names:
continue
width = state._spec.max_neighbors[1 if suffix else 0]
counts = result[state._spec.result_names.index(count_name)]
if state.is_batched:
owners = leaf_view.batch_idx
group_overflow = (
jax.ops.segment_sum(
(counts > width).astype(jnp.int32),
owners,
num_segments=state.num_systems,
)
> 0
)
group_required = jax.ops.segment_max(
counts,
owners,
num_segments=state.num_systems,
)
else:
group_overflow = jnp.any(counts > width).reshape(1)
group_required = jnp.max(counts, initial=jnp.int32(0)).reshape(1)
select_group = group_overflow & ~matrix_selected
matrix_overflow = matrix_overflow | group_overflow
matrix_required = jnp.where(
select_group,
group_required,
matrix_required,
)
matrix_capacity = jnp.where(
select_group,
jnp.full_like(group_required, width),
matrix_capacity,
)
matrix_selected = matrix_selected | group_overflow
(
initialized,
valid,
failure_code,
required,
capacity,
first_failure_marker,
) = _update_cluster_lifecycle(
leaf_view.initialized,
leaf_view.valid,
leaf_view.failure_code,
leaf_view.required,
leaf_view.capacity,
leaf_view.first_failure,
active,
uninitialized_preserved,
jnp.asarray(tile_overflow),
jnp.asarray(matrix_overflow),
jnp.asarray(coo_overflow),
jnp.asarray(tile_required),
jnp.asarray(tile_capacity),
jnp.asarray(matrix_required),
jnp.asarray(matrix_capacity),
jnp.asarray(coo_required),
jnp.asarray(coo_capacity),
)
successor = _build_successor_leaves(
a,
result,
span_axis=leaf_view.span_axis,
span_required=leaf_view.span_required,
initialized=initialized,
valid=valid,
failure_code=failure_code,
required=required,
capacity=capacity,
first_failure_marker=first_failure_marker,
)
return result, state._replace(successor)
@dataclass(frozen=True)
class _PreparedRouteExecution:
"""Resolved non-cluster route call and its reusable execution context."""
positions: jax.Array
cell: jax.Array | None
spans: jax.Array
span_failure_axes: jax.Array
dual: bool
selective_fixed_naive: bool
private_matrix1: Any
private_num1: Any
private_shift1: Any
private_matrix2: Any
private_num2: Any
private_shift2: Any
active: jax.Array
uninitialized_preserved: jax.Array
periodic_coverage_failure: jax.Array
periodic_required: jax.Array
periodic_capacity: jax.Array
raw: Any
def _prepare_noncluster_route_execution(
positions: jax.Array,
cell: jax.Array | None,
state: NeighborListState,
kwargs: dict[str, Any],
a: tuple[Any, ...],
leaf_view: _StateLeaves,
active: jax.Array,
uninitialized_preserved: jax.Array,
) -> _PreparedRouteExecution:
"""Assemble and dispatch a prepared naive or cell-list route."""
from nvalchemiops.jax.neighbors.batch_cell_list import batch_cell_list
from nvalchemiops.jax.neighbors.batch_naive import batch_naive_neighbor_list
from nvalchemiops.jax.neighbors.batch_naive_dual_cutoff import (
batch_naive_neighbor_list_dual_cutoff,
)
from nvalchemiops.jax.neighbors.cell_list import cell_list
from nvalchemiops.jax.neighbors.naive import naive_neighbor_list
from nvalchemiops.jax.neighbors.naive_dual_cutoff import (
naive_neighbor_list_dual_cutoff,
)
if state._spec.synthesized_cell:
positions, resolved_cell, spans, span_failure_axes = _synthetic_geometry(
positions,
leaf_view.batch_idx,
leaf_view.batch_ptr,
state.num_systems,
state._span_capacity,
state.cutoff,
)
cell = resolved_cell
else:
spans = jnp.zeros((state.num_systems, 3), dtype=positions.dtype)
span_failure_axes = jnp.zeros((state.num_systems, 3), dtype=jnp.bool_)
dual = state.cutoff2 is not None
selective_fixed_naive = (
state.selective
and state.format == "coo"
and state.method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
)
private_matrix1, private_num1, private_shift1 = (
leaf_view.private_matrix1,
leaf_view.private_num1,
leaf_view.private_shift1,
)
private_matrix2, private_num2, private_shift2 = (
leaf_view.private_matrix2,
leaf_view.private_num2,
leaf_view.private_shift2,
)
call = {
"cell": cell if cell is not None else leaf_view.cell,
"pbc": leaf_view.pbc,
"half_fill": state.half_fill,
"fill_value": state.fill_value,
"return_neighbor_list": state.format == "coo" and not selective_fixed_naive,
}
if (
state.fixed_cell
and state.method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
and leaf_view.pbc is None
):
# The explicit fixed cell remains stored and validated, but nonperiodic
# naive kernels do not consume it and reject cell without PBC.
call["cell"] = None
if state.fixed_cell:
call["_fixed_cell_geometry"] = leaf_view.fixed_cell_geometry
if not dual:
call.update(
return_vectors=state.return_vectors,
return_distances=state.return_distances,
strategy=state.strategy,
)
if state._spec.has_target_indices:
call["target_indices"] = state._target_indices
if state.selective:
if "rebuild_flags" not in kwargs:
raise ValueError("cannot preserve uninitialized system 0")
active = kwargs["rebuild_flags"].astype(jnp.bool_).reshape(state.num_systems)
uninitialized_preserved = ~leaf_view.initialized & ~active
if (
state.method not in {"cell_list", "batch_cell_list"}
and not selective_fixed_naive
):
call["rebuild_flags"] = active
for name in state._spec.result_names:
if name in {
"neighbor_matrix",
"num_neighbors",
"neighbor_matrix_shifts",
"neighbor_matrix2",
"num_neighbors2",
"neighbor_matrix_shifts2",
}:
call[name] = _result_leaf(state, name, a)
if selective_fixed_naive:
call["rebuild_flags"] = active
if dual:
call["neighbor_matrix1"] = leaf_view.private_matrix1
call["num_neighbors1"] = leaf_view.private_num1
if leaf_view.pbc is not None:
call["neighbor_matrix_shifts1"] = leaf_view.private_shift1
call["neighbor_matrix2"] = leaf_view.private_matrix2
call["num_neighbors2"] = leaf_view.private_num2
if leaf_view.pbc is not None:
call["neighbor_matrix_shifts2"] = leaf_view.private_shift2
else:
call["neighbor_matrix"] = leaf_view.private_matrix1
call["num_neighbors"] = leaf_view.private_num1
if leaf_view.pbc is not None:
call["neighbor_matrix_shifts"] = leaf_view.private_shift1
if state.method in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}:
call["wrap_positions"] = state.wrap_positions
if leaf_view.pbc is not None:
call.update(
shift_range_per_dimension=state._shift_range_per_dimension,
num_shifts_per_system=state._num_shifts_per_system,
max_shifts_per_system=state._spec.max_shifts_per_system,
)
if state.is_batched:
call["max_atoms_per_system"] = state._spec.max_atoms_per_system
periodic_coverage_failure = jnp.zeros((state.num_systems,), dtype=jnp.bool_)
periodic_required = jnp.zeros((state.num_systems,), dtype=jnp.int32)
periodic_capacity = jnp.zeros((state.num_systems,), dtype=jnp.int32)
if (
state.method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
and leaf_view.pbc is not None
and not state.fixed_cell
):
runtime_cell = call["cell"]
if runtime_cell.ndim == 2:
runtime_cell = runtime_cell[None]
runtime_pbc = leaf_view.pbc
if runtime_pbc.ndim == 1:
runtime_pbc = runtime_pbc[None]
reciprocal = jnp.swapaxes(jnp.linalg.inv(runtime_cell), -1, -2)
runtime_ranges = jnp.where(
runtime_pbc,
jnp.ceil(
jnp.linalg.vector_norm(reciprocal, axis=-1)
* float(state.cutoff2 if dual else state.cutoff)
).astype(jnp.int32),
0,
)
runtime_num_shifts = (
runtime_ranges[:, 0]
* (2 * runtime_ranges[:, 1] + 1)
* (2 * runtime_ranges[:, 2] + 1)
+ runtime_ranges[:, 1] * (2 * runtime_ranges[:, 2] + 1)
+ runtime_ranges[:, 2]
+ 1
).astype(jnp.int32)
periodic_coverage_failure = jnp.any(
runtime_ranges > state._shift_range_per_dimension, axis=1
)
periodic_required = runtime_num_shifts
periodic_capacity = state._num_shifts_per_system
if state.format == "coo":
if not selective_fixed_naive:
call["coo_capacity"] = state._spec.coo_capacity
if state.cutoff2 is None:
call["max_neighbors"] = state._spec.max_neighbors[0]
else:
call["max_neighbors1"] = state._spec.max_neighbors[0]
call["max_neighbors2"] = state._spec.max_neighbors[1]
elif state.cutoff2 is None:
call["max_neighbors"] = _result_leaf(state, "neighbor_matrix", a).shape[1]
else:
call["max_neighbors1"] = _result_leaf(state, "neighbor_matrix", a).shape[1]
call["max_neighbors2"] = _result_leaf(state, "neighbor_matrix2", a).shape[1]
if "pair_params" in kwargs:
call["pair_params"] = kwargs["pair_params"]
if state._spec.pair_fn is not None:
call["pair_fn"] = state._spec.pair_fn
if state.is_batched:
call.update(batch_idx=leaf_view.batch_idx, batch_ptr=leaf_view.batch_ptr)
fn = {
("naive", False): naive_neighbor_list,
("batch_naive", False): batch_naive_neighbor_list,
("naive_dual_cutoff", True): naive_neighbor_list_dual_cutoff,
("batch_naive_dual_cutoff", True): batch_naive_neighbor_list_dual_cutoff,
("cell_list", False): cell_list,
("batch_cell_list", False): batch_cell_list,
}.get((state.method, dual))
if state.method in {"cell_list", "batch_cell_list"}:
call.update(dict(state._spec.route_options))
call["_return_status"] = True
raw = (
fn(positions, state.cutoff, state.cutoff2, **call)
if dual
else fn(positions, state.cutoff, **call)
)
return _PreparedRouteExecution(
positions=positions,
cell=cell,
spans=spans,
span_failure_axes=span_failure_axes,
dual=dual,
selective_fixed_naive=selective_fixed_naive,
private_matrix1=private_matrix1,
private_num1=private_num1,
private_shift1=private_shift1,
private_matrix2=private_matrix2,
private_num2=private_num2,
private_shift2=private_shift2,
active=active,
uninitialized_preserved=uninitialized_preserved,
periodic_coverage_failure=periodic_coverage_failure,
periodic_required=periodic_required,
periodic_capacity=periodic_capacity,
raw=raw,
)
@dataclass(frozen=True)
class _PreparedExecutionInputs:
"""Validated runtime inputs shared by prepared route execution."""
leaves: tuple[Any, ...]
leaf_view: _StateLeaves
active: jax.Array
uninitialized_preserved: jax.Array
def _validate_prepared_execution(
positions: jax.Array,
cell: jax.Array | None,
state: NeighborListState,
kwargs: dict[str, Any],
) -> _PreparedExecutionInputs:
"""Validate runtime arrays before dispatching the prepared route."""
a = state._leaves
leaf_view = _StateLeaves(a)
if positions.shape != (state.num_atoms, 3):
raise ValueError("runtime positions shape does not match prepared state")
if positions.dtype != state._span_capacity.dtype:
raise ValueError("runtime positions dtype does not match prepared state")
if (
isinstance(positions, jax.Array)
and not isinstance(positions, jax.core.Tracer)
and isinstance(state._span_capacity, jax.Array)
and not isinstance(state._span_capacity, jax.core.Tracer)
):
if positions.devices() != state._span_capacity.devices():
raise ValueError("runtime positions device does not match prepared state")
if state.selective:
rebuild_flags = kwargs.get("rebuild_flags")
if rebuild_flags is None:
raise ValueError("selective prepared state requires rebuild_flags")
if (
rebuild_flags.shape != (state.num_systems,)
or rebuild_flags.dtype != jnp.bool_
):
raise ValueError(
"rebuild_flags must be a bool array with shape (num_systems,)"
)
elif kwargs.get("rebuild_flags") is not None:
raise ValueError("rebuild_flags requires a selective prepared state")
if state._spec.synthesized_cell and cell is not None:
raise ValueError("prepared nonperiodic state requires cell=None")
if state._spec.pair_fn is not None:
if "pair_params" not in kwargs or kwargs["pair_params"] is None:
raise ValueError(
"pair_fn requires pair_params (a per-atom (n_atoms, K) parameter array)."
)
pair_params = kwargs["pair_params"]
if not hasattr(pair_params, "ndim") or pair_params.ndim != 2:
raise ValueError(
"pair_params must be a JAX per-atom (n_atoms, K) parameter array"
)
if (
pair_params.shape[0] != state.num_atoms
or pair_params.dtype != positions.dtype
):
raise ValueError(
"pair_params must match prepared positions dtype and leading atom dimension"
)
if (
isinstance(pair_params, jax.Array)
and not isinstance(pair_params, jax.core.Tracer)
and isinstance(positions, jax.Array)
and not isinstance(positions, jax.core.Tracer)
):
if pair_params.devices() != positions.devices():
raise ValueError("pair_params must be on the same device as positions")
elif "pair_params" in kwargs and kwargs["pair_params"] is not None:
raise ValueError("pair_params requires pair_fn.")
active = (
kwargs["rebuild_flags"].astype(jnp.bool_)
if state.selective
else jnp.ones((state.num_systems,), dtype=jnp.bool_)
)
uninitialized_preserved = (
~leaf_view.initialized & ~active
if state.selective
else jnp.zeros((state.num_systems,), dtype=jnp.bool_)
)
if cell is not None and leaf_view.cell is not None:
expected_shape = (
(3, 3) if state._spec.shared_batch_cell else leaf_view.cell.shape
)
if cell.shape != expected_shape or cell.dtype != leaf_view.cell.dtype:
raise ValueError(
"runtime cell shape or dtype does not match prepared state"
)
if (
isinstance(cell, jax.Array)
and not isinstance(cell, jax.core.Tracer)
and isinstance(leaf_view.cell, jax.Array)
and not isinstance(leaf_view.cell, jax.core.Tracer)
):
if cell.devices() != leaf_view.cell.devices():
raise ValueError("runtime cell device does not match prepared state")
return _PreparedExecutionInputs(
leaves=a,
leaf_view=leaf_view,
active=active,
uninitialized_preserved=uninitialized_preserved,
)
@dataclass(frozen=True)
class _ProcessedNonclusterResult:
"""Published results, diagnostics, and private matrices for one execution."""
result: tuple[Any, ...]
diagnostic_result: tuple[Any, ...]
status: tuple[tuple[Any, ...], ...]
private_leaves: tuple[Any, ...]
def _process_noncluster_result(
state: NeighborListState,
execution: _PreparedRouteExecution,
leaves: tuple[Any, ...],
leaf_view: _StateLeaves,
) -> _ProcessedNonclusterResult:
"""Pack route results and apply selective publication atomically."""
dual = execution.dual
private_matrix1 = execution.private_matrix1
private_num1 = execution.private_num1
private_shift1 = execution.private_shift1
private_matrix2 = execution.private_matrix2
private_num2 = execution.private_num2
private_shift2 = execution.private_shift2
raw = execution.raw
nstatus = (
12 if dual else (11 if state.method in {"cell_list", "batch_cell_list"} else 6)
)
if execution.selective_fixed_naive:
from nvalchemiops.jax.neighbors.neighbor_utils import (
_pack_fixed_capacity_neighbor_list_from_neighbor_matrix,
)
if dual:
if leaf_view.pbc is not None:
(
private_matrix1,
private_num1,
private_shift1,
private_matrix2,
private_num2,
private_shift2,
) = raw
else:
private_matrix1, private_num1, private_matrix2, private_num2 = raw
private_shift1 = leaf_view.private_shift1
private_shift2 = leaf_view.private_shift2
elif leaf_view.pbc is not None:
private_matrix1, private_num1, private_shift1 = raw
else:
private_matrix1, private_num1 = raw
private_shift1 = leaf_view.private_shift1
capacity1 = (
state._spec.coo_capacity[0]
if isinstance(state._spec.coo_capacity, tuple)
else state._spec.coo_capacity
)
packed1, _ = _pack_fixed_capacity_neighbor_list_from_neighbor_matrix(
private_matrix1,
private_num1,
capacity1,
neighbor_shift_matrix=(
private_shift1 if leaf_view.pbc is not None else None
),
fill_value=state.fill_value,
metadata_valid=jnp.ones((), dtype=jnp.bool_),
)
if dual:
capacity2 = (
state._spec.coo_capacity[1]
if isinstance(state._spec.coo_capacity, tuple)
else state._spec.coo_capacity
)
packed2, _ = _pack_fixed_capacity_neighbor_list_from_neighbor_matrix(
private_matrix2,
private_num2,
capacity2,
neighbor_shift_matrix=(
private_shift2 if leaf_view.pbc is not None else None
),
fill_value=state.fill_value,
metadata_valid=jnp.ones((), dtype=jnp.bool_),
)
result = (*packed1, *packed2)
else:
result = packed1
status = ()
elif len(raw) >= nstatus + len(state._spec.result_names):
result, tail = raw[:-nstatus], raw[-nstatus:]
status = (tuple(tail[:6]), tuple(tail[6:])) if dual else (tuple(tail),)
else:
result = raw
status = ()
# Global fixed-COO packing is transactional: a selected update must not
# evict logical records owned by an initialized, unselected system.
rollback = jnp.asarray(False, dtype=jnp.bool_)
diagnostic_result = result
if execution.selective_fixed_naive and state.is_batched:
clipped_systems = []
for group_index in range(2 if dual else 1):
count_name = "num_neighbors2" if group_index else "num_neighbors"
ptr_name = "neighbor_ptr2" if group_index else "neighbor_ptr"
counts = jnp.asarray(
diagnostic_result[state._spec.result_names.index(count_name)]
)
stored_ptr = jnp.asarray(
diagnostic_result[state._spec.result_names.index(ptr_name)]
)
clipped_systems.append(
jax.ops.segment_sum(
(counts > jnp.diff(stored_ptr)).astype(jnp.int32),
leaf_view.batch_idx,
num_segments=state.num_systems,
)
> 0
)
inactive_clipped = jnp.logical_or.reduce(jnp.stack(clipped_systems))
inactive_clipped &= ~execution.active & leaf_view.initialized
rollback = jnp.any(inactive_clipped)
result = tuple(
jnp.where(
rollback,
_result_leaf(state, state._spec.result_names[index], leaves),
value,
)
for index, value in enumerate(result)
)
if execution.selective_fixed_naive:
private_matrix1 = jnp.where(
rollback, leaf_view.private_matrix1, private_matrix1
)
private_num1 = jnp.where(rollback, leaf_view.private_num1, private_num1)
if private_shift1 is not None:
private_shift1 = jnp.where(
rollback, leaf_view.private_shift1, private_shift1
)
if dual:
private_matrix2 = jnp.where(
rollback, leaf_view.private_matrix2, private_matrix2
)
private_num2 = jnp.where(rollback, leaf_view.private_num2, private_num2)
if private_shift2 is not None:
private_shift2 = jnp.where(
rollback, leaf_view.private_shift2, private_shift2
)
if state.selective and state.method in {"cell_list", "batch_cell_list"}:
row_indices = (
state._target_indices
if state._spec.has_target_indices
else jnp.arange(state.num_atoms)
)
row_active = (
execution.active[leaf_view.batch_idx[row_indices]]
if state.is_batched
else jnp.broadcast_to(execution.active[0], (row_indices.shape[0],))
)
merged = list(result)
matrix_names = {
"neighbor_matrix",
"num_neighbors",
"neighbor_matrix_shifts",
"neighbor_matrix2",
"num_neighbors2",
"neighbor_matrix_shifts2",
}
for index, name in enumerate(state._spec.result_names):
if name in matrix_names:
mask = row_active.reshape(
(row_indices.shape[0],) + (1,) * (result[index].ndim - 1)
)
merged[index] = jnp.where(
mask, result[index], _result_leaf(state, name, leaves)
)
result = tuple(merged)
return _ProcessedNonclusterResult(
result=tuple(result),
diagnostic_result=tuple(diagnostic_result),
status=tuple(status),
private_leaves=(
private_matrix1,
private_num1,
private_shift1,
private_matrix2,
private_num2,
private_shift2,
),
)
def _derive_noncluster_diagnostics(
state: NeighborListState,
leaf_view: _StateLeaves,
processed: _ProcessedNonclusterResult,
active: jax.Array,
*,
dual: bool,
selective_fixed_naive: bool,
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array]:
"""Choose one atomic failure diagnostic per system and cutoff group."""
status = list(processed.status)
if (
state.method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
or state.selective
):
status = []
for group_index in range(2 if dual else 1):
count_name = "num_neighbors2" if group_index else "num_neighbors"
counts = jnp.asarray(
processed.diagnostic_result[state._spec.result_names.index(count_name)]
)
if state.is_batched:
row_indices = (
state._target_indices
if state._spec.has_target_indices
else jnp.arange(state.num_atoms)
)
row_owners = leaf_view.batch_idx[row_indices]
row_required = jax.ops.segment_max(
counts, row_owners, num_segments=state.num_systems
)
coo_required = jax.ops.segment_sum(
counts, row_owners, num_segments=state.num_systems
)
else:
row_required = jnp.max(counts, initial=jnp.int32(0)).reshape(1)
coo_required = jnp.sum(counts, dtype=jnp.int32).reshape(1)
row_cap = jnp.full_like(
row_required, state._spec.max_neighbors[group_index]
)
coo_cap_value = (
state._spec.coo_capacity[group_index]
if isinstance(state._spec.coo_capacity, tuple)
else state._spec.coo_capacity
)
coo_cap = jnp.full_like(
row_required, 0 if coo_cap_value is None else coo_cap_value
)
if state.is_batched and coo_cap_value is not None:
ptr_name = "neighbor_ptr2" if group_index else "neighbor_ptr"
stored_ptr = jnp.asarray(
processed.diagnostic_result[
state._spec.result_names.index(ptr_name)
]
)
incomplete = counts > jnp.diff(stored_ptr)
clipped_system = (
jax.ops.segment_sum(
incomplete.astype(jnp.int32),
row_owners,
num_segments=state.num_systems,
)
> 0
)
global_coo_required = jnp.sum(counts, dtype=jnp.int32)
if selective_fixed_naive:
inactive_clipped = clipped_system & ~active & leaf_view.initialized
coo_failure_owner = clipped_system | (
active & jnp.any(inactive_clipped)
)
else:
coo_failure_owner = clipped_system
coo_required = jnp.where(
coo_failure_owner, global_coo_required, coo_required
)
coo_overflow = coo_failure_owner
else:
coo_overflow = coo_required > coo_cap
metadata_name = "metadata_valid2" if group_index else "metadata_valid"
metadata = (
jnp.asarray(
processed.diagnostic_result[
state._spec.result_names.index(metadata_name)
]
)
if metadata_name in state._spec.result_names
else jnp.asarray(True)
)
metadata_bad = jnp.broadcast_to(~metadata, row_required.shape)
status.append(
(
row_required > row_cap,
coo_overflow
if coo_cap_value is not None
else jnp.zeros_like(metadata_bad),
row_required,
row_cap,
coo_required,
coo_cap,
metadata_bad,
)
)
category_failures = [
jnp.zeros((state.num_systems,), dtype=jnp.bool_) for _ in range(4)
]
category_required = [
jnp.zeros((state.num_systems,), dtype=jnp.int32) for _ in range(4)
]
category_capacity = [
jnp.zeros((state.num_systems,), dtype=jnp.int32) for _ in range(4)
]
for group in status:
if state.method in {"cell_list", "batch_cell_list"} and len(group) >= 11:
metadata = (jnp.asarray(group[0]) == 3) | jnp.asarray(group[3])
diagnostics = (
(metadata, jnp.zeros_like(group[5]), jnp.zeros_like(group[6])),
(jnp.asarray(group[4]), jnp.asarray(group[9]), jnp.asarray(group[10])),
(jnp.asarray(group[1]), jnp.asarray(group[5]), jnp.asarray(group[6])),
(jnp.asarray(group[2]), jnp.asarray(group[7]), jnp.asarray(group[8])),
)
else:
row = jnp.asarray(group[0])
coo = jnp.asarray(group[1])
metadata = jnp.asarray(group[6]) if len(group) > 6 else jnp.zeros_like(row)
diagnostics = (
(metadata, jnp.zeros_like(row), jnp.zeros_like(row)),
(jnp.zeros_like(row), jnp.zeros_like(row), jnp.zeros_like(row)),
(row, jnp.asarray(group[2]), jnp.asarray(group[3])),
(coo, jnp.asarray(group[4]), jnp.asarray(group[5])),
)
for index, (failed, required, capacity) in enumerate(diagnostics):
failed = jnp.asarray(failed)
choose = failed & ~category_failures[index]
category_failures[index] |= failed
category_required[index] = jnp.where(
choose, required, category_required[index]
)
category_capacity[index] = jnp.where(
choose, capacity, category_capacity[index]
)
winner = jnp.zeros((state.num_systems,), dtype=jnp.bool_)
winner_code = jnp.zeros((state.num_systems,), dtype=jnp.int32)
winner_required = jnp.zeros((state.num_systems,), dtype=jnp.int32)
winner_capacity = jnp.zeros((state.num_systems,), dtype=jnp.int32)
for index, category in enumerate((3, 7, 1, 2)):
choose = category_failures[index] & ~winner
winner_code = jnp.where(choose, jnp.int32(category), winner_code)
winner_required = jnp.where(choose, category_required[index], winner_required)
winner_capacity = jnp.where(choose, category_capacity[index], winner_capacity)
winner |= category_failures[index]
return winner, winner_code, winner_required, winner_capacity
def _execute_prepared_neighbor_list_full(
positions: jax.Array,
cell: jax.Array | None,
state: NeighborListState,
*,
kwargs: dict[str, Any],
) -> tuple[tuple[Any, ...], NeighborListState]:
"""Run the resolved route and return ``(results, successor_state)``."""
if not isinstance(state, NeighborListState):
raise TypeError(
"state must be a NeighborListState returned by prepare_neighbor_list"
)
if any(
name in _CALLER_OWNED and value is not None for name, value in kwargs.items()
):
raise ValueError(
"caller-owned output or scratch buffers cannot be used with state"
)
unknown = next((name for name in kwargs if name not in _EXECUTION_KEYS), None)
if unknown is not None:
raise TypeError(f"unexpected prepared neighbor-list option: {unknown!r}")
inputs = _validate_prepared_execution(positions, cell, state, kwargs)
a = inputs.leaves
leaf_view = inputs.leaf_view
active = inputs.active
uninitialized_preserved = inputs.uninitialized_preserved
if state.method in {"cluster_tile", "batch_cluster_tile"}:
return _execute_prepared_cluster_route(
positions,
cell,
state,
kwargs,
a,
leaf_view,
active,
uninitialized_preserved,
)
execution = _prepare_noncluster_route_execution(
positions,
cell,
state,
kwargs,
a,
leaf_view,
active,
uninitialized_preserved,
)
positions = execution.positions
cell = execution.cell
spans = execution.spans
span_failure_axes = execution.span_failure_axes
dual = execution.dual
selective_fixed_naive = execution.selective_fixed_naive
active = execution.active
uninitialized_preserved = execution.uninitialized_preserved
processed = _process_noncluster_result(state, execution, a, leaf_view)
result = processed.result
winner, winner_code, winner_required, winner_capacity = (
_derive_noncluster_diagnostics(
state,
leaf_view,
processed,
active,
dual=dual,
selective_fixed_naive=selective_fixed_naive,
)
)
periodic_coverage_failure = execution.periodic_coverage_failure & active
span_failure = jnp.any(span_failure_axes, axis=1) & active
winner = winner & active
failed = winner | periodic_coverage_failure | span_failure | uninitialized_preserved
code = jnp.where(
uninitialized_preserved,
6,
jnp.where(
span_failure,
5,
jnp.where(
periodic_coverage_failure,
8,
jnp.where(
winner,
winner_code,
0,
),
),
),
).astype(jnp.int32)
required = jnp.where(
periodic_coverage_failure,
execution.periodic_required,
winner_required,
)
capacity = jnp.where(
periodic_coverage_failure,
execution.periodic_capacity,
winner_capacity,
)
old_initialized, old_valid = leaf_view.initialized, leaf_view.valid
current_code = code
status_updated = active | uninitialized_preserved
first_failure = old_valid & failed & status_updated
latched_code = jnp.where(first_failure, current_code, leaf_view.failure_code)
latched_required = jnp.where(first_failure, required, leaf_view.required)
latched_capacity = jnp.where(first_failure, capacity, leaf_view.capacity)
span_axis = jnp.argmax(span_failure_axes, axis=1).astype(jnp.int32)
span_required = jnp.take_along_axis(spans, span_axis[:, None], axis=1).reshape(-1)
latched_span_axis = jnp.where(
first_failure & (current_code == 5), span_axis, leaf_view.span_axis
)
latched_span_required = jnp.where(
first_failure & (current_code == 5), span_required, leaf_view.span_required
)
successor = _build_successor_leaves(
a,
result,
span_axis=latched_span_axis,
span_required=latched_span_required,
initialized=old_initialized | active,
valid=jnp.where(status_updated, old_valid & ~failed, old_valid),
failure_code=latched_code,
required=latched_required,
capacity=latched_capacity,
first_failure_marker=jnp.where(
first_failure, 1, leaf_view.first_failure
).astype(jnp.int32),
private_leaves=processed.private_leaves,
)
return result, state._replace(successor)
def _execute_prepared_neighbor_list(
positions: jax.Array,
cell: jax.Array | None,
state: NeighborListState,
*,
kwargs: dict[str, Any],
) -> tuple[tuple[Any, ...], NeighborListState]:
"""Execute a prepared route, preserving initialized inactive fixed COO state."""
if not isinstance(state, NeighborListState):
raise TypeError(
"state must be a NeighborListState returned by prepare_neighbor_list"
)
if any(
name in _CALLER_OWNED and value is not None for name, value in kwargs.items()
):
raise ValueError(
"caller-owned output or scratch buffers cannot be used with state"
)
unknown = next((name for name in kwargs if name not in _EXECUTION_KEYS), None)
if unknown is not None:
raise TypeError(f"unexpected prepared neighbor-list option: {unknown!r}")
inputs = _validate_prepared_execution(positions, cell, state, kwargs)
selective_fixed_naive = (
state.selective
and state.format == "coo"
and state.method
in {
"naive",
"batch_naive",
"naive_dual_cutoff",
"batch_naive_dual_cutoff",
}
)
if not selective_fixed_naive:
return _execute_prepared_neighbor_list_full(
positions, cell, state, kwargs=kwargs
)
published = tuple(
_result_leaf(state, name, inputs.leaves) for name in state._spec.result_names
)
preserve = ~jnp.any(inputs.active) & jnp.all(inputs.leaf_view.initialized)
return jax.lax.cond(
preserve,
lambda _: (published, state),
lambda _: _execute_prepared_neighbor_list_full(
positions, cell, state, kwargs=kwargs
),
operand=None,
)
[docs]
def check_neighbor_list_state(state: NeighborListState) -> None:
"""Raise a host-side exception for latched prepared-state failures.
Parameters
----------
state : NeighborListState
Successor state returned by prepared JAX execution.
Raises
------
TypeError
If ``state`` was not created by :func:`prepare_neighbor_list`.
NeighborOverflowError
If the lowest-index failed system exhausted row, COO, or route
capacity and required/capacity details are available.
TileBufferOverflow
If the lowest-index failed system exhausted cluster-tile capacity.
RuntimeError
For span, stale-metadata, preservation, or other runtime failures.
Notes
-----
Call this function outside ``jax.jit``. It reads device status and may
synchronize with the device.
Every failed system index is included in sorted order in the exception
message. The lowest failed index determines the structured exception or
runtime detail. Calling this function does not change the state or clear
sticky failure history. Check before consuming results when immediate
failure detection matters; a final check detects historical failures but
cannot make results already consumed after a failed step safe. Inspect
``state.initialized`` separately when initialization matters.
"""
if not isinstance(state, NeighborListState):
raise TypeError(
"state must be a NeighborListState returned by prepare_neighbor_list"
)
failed_systems = [int(i) for i in jnp.nonzero(~state.valid)[0].tolist()]
if failed_systems:
first_failed_system = failed_systems[0]
first_failure_code = int(
jnp.asarray(state._failure_code[first_failed_system]).item()
)
if first_failure_code == 4:
from nvalchemiops.neighbors.neighbor_utils import TileBufferOverflow
error = TileBufferOverflow(
int(state._capacity[failed_systems[0]]),
int(state._required[failed_systems[0]]),
failed_systems[0] if state.is_batched else None,
)
error.args = (
"prepared neighbor-list state failed for systems "
f"{failed_systems}; first failure: {error}",
)
raise error
if first_failure_code in (1, 2, 7):
from nvalchemiops.neighbors.neighbor_utils import NeighborOverflowError
error = NeighborOverflowError(
int(state._capacity[failed_systems[0]]),
int(state._required[failed_systems[0]]),
failed_systems[0] if state.is_batched else None,
)
error.args = (
"prepared neighbor-list state failed for systems "
f"{failed_systems}; first failure: {error}",
)
raise error
if first_failure_code == 8:
detail = (
"prepared periodic-image coverage is insufficient for system "
f"{first_failed_system}: cached {int(state._capacity[first_failed_system])} "
f"shifts, runtime requires {int(state._required[first_failed_system])}"
)
raise RuntimeError(
"prepared neighbor-list state failed for systems "
f"{failed_systems}; first failure: {detail}"
)
if first_failure_code == 3:
detail = (
"pair-centric launch metadata is stale; rebuild the launch metadata"
)
raise RuntimeError(
"prepared neighbor-list state failed for systems "
f"{failed_systems}; first failure: {detail}"
)
if first_failure_code == 5:
axis = int(state._span_axis[failed_systems[0]])
capacity = float(state._span_capacity[failed_systems[0], axis])
required = float(state._span_required[failed_systems[0]])
detail = f"prepared nonperiodic span exceeded for system {failed_systems[0]} on axis {axis}: capacity {capacity}, required {required}"
raise RuntimeError(
f"prepared neighbor-list state failed for systems {failed_systems}; first failure: {detail}"
)
if first_failure_code == 6:
detail = f"cannot preserve uninitialized system {failed_systems[0]}"
raise RuntimeError(
f"prepared neighbor-list state failed for systems {failed_systems}; first failure: {detail}"
)
raise RuntimeError(
f"prepared neighbor-list state failed for systems {failed_systems}; first failure: runtime validation failed"
)