Source code for nvalchemiops.neighbors.naive.launchers

# 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.

"""Warp-facing launchers for naive neighbor lists."""

from __future__ import annotations

from typing import NamedTuple

import warp as wp

from nvalchemiops.neighbors.naive.dispatch import (
    _has_naive_pair_outputs,
    _is_cpu_device,
    _pbc_mode_from_wrap,
)
from nvalchemiops.neighbors.naive.kernels import (
    BLOCK_DIM,
    get_naive_neighbor_matrix_dual_cutoff_kernel,
    get_naive_neighbor_matrix_kernel,
)
from nvalchemiops.neighbors.neighbor_utils import (
    DTYPE_INFO_ALL,
    compute_inv_cells,
    resolve_buffer_alias,
    selective_zero_num_neighbors,
    wrap_positions_batch,
    wrap_positions_single,
)
from nvalchemiops.neighbors.neighbor_utils import (
    empty_sentinel as _empty_sentinel,
)
from nvalchemiops.neighbors.output_args import (
    _is_empty_pair_params,
    _prepare_pair_output_args,
)

__all__ = [
    "get_naive_neighbor_matrix_kernel",
    "get_naive_neighbor_matrix_dual_cutoff_kernel",
    "naive_neighbor_matrix",
    "naive_neighbor_matrix_pbc",
    "batch_naive_neighbor_matrix",
    "batch_naive_neighbor_matrix_pbc",
    "naive_neighbor_matrix_dual_cutoff",
    "naive_neighbor_matrix_pbc_dual_cutoff",
    "batch_naive_neighbor_matrix_dual_cutoff",
    "batch_naive_neighbor_matrix_pbc_dual_cutoff",
]

_SUPPORTED_DTYPES = (wp.float16, wp.float32, wp.float64)
_DTYPE_INFO: dict[type, tuple[type, type]] = {
    dtype: DTYPE_INFO_ALL[dtype] for dtype in _SUPPORTED_DTYPES
}


class _ScalarSentinels(NamedTuple):
    """Zero-size placeholders for inactive scalar-kernel inputs."""

    offsets: wp.array
    cell: wp.array
    shift_range: wp.array
    num_shifts: wp.array
    batch_idx: wp.array
    batch_ptr: wp.array
    target_indices: wp.array
    neighbor_matrix: wp.array
    neighbor_matrix_shifts: wp.array
    num_neighbors: wp.array
    neighbor_vectors: wp.array
    neighbor_distances: wp.array
    pair_params: wp.array
    pair_energies: wp.array
    pair_forces: wp.array
    rebuild_flags: wp.array


def _reject_pair_fn_for_dual_cutoff(
    pair_fn: wp.Function | None, pair_params: wp.array | None
) -> None:
    """Reject active pair_fn/pair_params kwargs in dual-cutoff launchers."""
    if pair_fn is not None:
        raise ValueError("pair_fn is not supported with dual_cutoff=True")
    if pair_params is not None and not _is_empty_pair_params(pair_params):
        raise ValueError("pair_params is only valid when pair_fn is provided")


def _wrap_pbc_positions(
    positions: wp.array,
    cell: wp.array,
    inv_cell: wp.array,
    pbc: wp.array | None,
    positions_wrapped: wp.array,
    per_atom_cell_offsets: wp.array,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    batch_idx: wp.array | None,
) -> None:
    """Launch the shared position-wrapping helper for one batching mode."""
    if batched:
        if batch_idx is None:
            raise ValueError("batch_idx is required for batched PBC wrapping")
        wrap_positions_batch(
            positions,
            cell,
            inv_cell,
            batch_idx,
            positions_wrapped,
            per_atom_cell_offsets,
            wp_dtype,
            device,
            pbc=pbc,
        )
        return
    wrap_positions_single(
        positions,
        cell,
        inv_cell,
        positions_wrapped,
        per_atom_cell_offsets,
        wp_dtype,
        device,
        pbc=pbc,
    )


def _prepare_pbc_positions(
    positions: wp.array,
    cell: wp.array,
    pbc: wp.array | None,
    batch_idx: wp.array | None,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    wrap_positions: bool,
    positions_wrapped_buffer: wp.array | None = None,
    per_atom_cell_offsets_buffer: wp.array | None = None,
    inv_cell_buffer: wp.array | None = None,
):
    """Prepare wrapped or prewrapped positions and per-atom cell offsets.

    Caller-supplied scratch buffers are used when provided.  If any are
    absent the launcher allocates a fresh buffer for the call.
    """
    if not wrap_positions:
        return positions, _empty_sentinel(1, wp.vec3i, device)

    total_atoms = positions.shape[0]
    vec_dtype, mat_dtype = _DTYPE_INFO[wp_dtype]
    if inv_cell_buffer is None:
        inv_cell_buffer = wp.empty((cell.shape[0],), dtype=mat_dtype, device=device)
    if positions_wrapped_buffer is None:
        positions_wrapped_buffer = wp.empty(
            (total_atoms,), dtype=vec_dtype, device=device
        )
    if per_atom_cell_offsets_buffer is None:
        per_atom_cell_offsets_buffer = wp.empty(
            (total_atoms,), dtype=wp.vec3i, device=device
        )
    compute_inv_cells(cell, inv_cell_buffer, wp_dtype, device)
    _wrap_pbc_positions(
        positions,
        cell,
        inv_cell_buffer,
        pbc,
        positions_wrapped_buffer,
        per_atom_cell_offsets_buffer,
        wp_dtype,
        device,
        batched=batched,
        batch_idx=batch_idx,
    )
    return positions_wrapped_buffer, per_atom_cell_offsets_buffer


def _scalar_sentinels(wp_dtype: type, device: str) -> _ScalarSentinels:
    """Return zero-size placeholders for inactive scalar-kernel inputs."""
    vec_dtype, mat_dtype = _DTYPE_INFO[wp_dtype]
    return _ScalarSentinels(
        offsets=_empty_sentinel(1, wp.vec3i, device),
        cell=_empty_sentinel(1, mat_dtype, device),
        shift_range=_empty_sentinel(1, wp.vec3i, device),
        num_shifts=_empty_sentinel(1, wp.int32, device),
        batch_idx=_empty_sentinel(1, wp.int32, device),
        batch_ptr=_empty_sentinel(1, wp.int32, device),
        target_indices=_empty_sentinel(1, wp.int32, device),
        neighbor_matrix=_empty_sentinel(2, wp.int32, device),
        neighbor_matrix_shifts=_empty_sentinel(2, wp.vec3i, device),
        num_neighbors=_empty_sentinel(1, wp.int32, device),
        neighbor_vectors=_empty_sentinel(2, vec_dtype, device),
        neighbor_distances=_empty_sentinel(2, wp_dtype, device),
        pair_params=_empty_sentinel(2, wp_dtype, device),
        pair_energies=_empty_sentinel(2, wp_dtype, device),
        pair_forces=_empty_sentinel(2, vec_dtype, device),
        rebuild_flags=_empty_sentinel(1, wp.bool, device),
    )


def _launch_naive_neighbor_matrix_no_pbc(
    positions: wp.array,
    cutoff: float,
    neighbor_matrix: wp.array,
    num_neighbors: wp.array,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    batch_idx: wp.array | None = None,
    batch_ptr: wp.array | None = None,
    half_fill: bool = False,
    rebuild_flags: wp.array | None = None,
    target_indices: wp.array | None = None,
    return_vectors: bool = False,
    return_distances: bool = False,
    pair_fn: wp.Function | None = None,
    pair_params: wp.array | None = None,
    neighbor_vectors: wp.array | None = None,
    neighbor_distances: wp.array | None = None,
    pair_energies: wp.array | None = None,
    pair_forces: wp.array | None = None,
    strategy: str = "auto",
) -> None:
    """Launch the single-cutoff no-PBC naive neighbor-matrix path."""
    if batched and (batch_idx is None or batch_ptr is None):
        raise ValueError("batch_idx and batch_ptr are required for batched launch")

    has_pair_outputs = _has_naive_pair_outputs(
        target_indices,
        return_vectors,
        return_distances,
        pair_fn,
        pair_params,
        neighbor_vectors,
        neighbor_distances,
        pair_energies,
        pair_forces,
    )
    partial = target_indices is not None
    # Dispatch scalar vs tile-cooperative.  Pair-output and partial
    # (target_indices) paths have only a scalar specialization; CPU always
    # uses scalar (Warp forces block_dim=1).  The single-system path tiles on
    # CUDA unconditionally, while the batched path applies the adaptive
    # ``use_tiled`` heuristic: the tile-cooperative kernel wins for
    # few-large-systems but the scalar thread-local-counter kernel wins for
    # many-small-systems, so dispatch on the atoms-per-system density.
    if strategy not in {"auto", "scalar", "tile"}:
        raise ValueError(
            f"strategy must be 'auto' | 'scalar' | 'tile', got {strategy!r}",
        )
    if strategy == "scalar":
        strategy = "scalar"
    elif strategy == "tile":
        if has_pair_outputs or _is_cpu_device(device):
            raise ValueError(
                "strategy='tile' requires CUDA and no pair-output or "
                "target_indices path",
            )
        strategy = "tile"
    elif has_pair_outputs or _is_cpu_device(device):
        strategy = "scalar"
    elif batched:
        total_atoms = positions.shape[0]
        if total_atoms < 2048:
            use_tiled = False
        else:
            num_systems = batch_ptr.shape[0] - 1
            use_tiled = total_atoms >= 256 * num_systems
            if use_tiled and total_atoms > 12288:
                use_tiled = total_atoms >= 512 * num_systems
        strategy = "tile" if use_tiled else "scalar"
    else:
        strategy = "tile"

    (
        empty_offsets,
        empty_cell,
        empty_shift_range,
        empty_num_shifts,
        empty_batch_idx,
        empty_batch_ptr,
        empty_target_indices,
        empty_matrix,
        empty_shifts,
        empty_num_neighbors,
        empty_vectors,
        empty_distances,
        empty_pair_params,
        empty_energies,
        empty_forces,
        empty_rebuild_flags,
    ) = _scalar_sentinels(wp_dtype, device)
    batch_idx_arg = batch_idx if batch_idx is not None else empty_batch_idx
    batch_ptr_arg = batch_ptr if batch_ptr is not None else empty_batch_ptr
    target_indices_arg = (
        target_indices if target_indices is not None else empty_target_indices
    )
    rebuild_flags_arg = (
        rebuild_flags if rebuild_flags is not None else empty_rebuild_flags
    )
    cutoff_sq = wp_dtype(cutoff * cutoff)

    if strategy == "tile":
        wp.launch_tiled(
            kernel=get_naive_neighbor_matrix_kernel(
                wp_dtype,
                pbc_mode="none",
                batched=batched,
                half_fill=half_fill,
                selective=rebuild_flags is not None,
                strategy="tile",
            ),
            dim=[1, positions.shape[0]],
            inputs=[
                positions,
                empty_offsets,
                cutoff_sq,
                empty_cell,
                empty_shift_range,
                empty_num_shifts,
                batch_idx_arg,
                batch_ptr_arg,
                neighbor_matrix,
                empty_shifts,
                num_neighbors,
                rebuild_flags_arg,
            ],
            block_dim=BLOCK_DIM,
            device=device,
        )
        return

    if has_pair_outputs:
        (
            neighbor_vectors_arg,
            neighbor_distances_arg,
            pair_params_arg,
            pair_energies_arg,
            pair_forces_arg,
        ) = _prepare_pair_output_args(
            wp_dtype,
            device,
            return_vectors=return_vectors,
            return_distances=return_distances,
            pair_fn=pair_fn,
            pair_params=pair_params,
            neighbor_vectors=neighbor_vectors,
            neighbor_distances=neighbor_distances,
            pair_energies=pair_energies,
            pair_forces=pair_forces,
            neighbor_vectors_name="neighbor_vectors",
            neighbor_distances_name="neighbor_distances",
            pair_energies_name="pair_energies",
            pair_forces_name="pair_forces",
        )
        dim = int(target_indices.shape[0]) if partial else int(positions.shape[0])
    else:
        neighbor_vectors_arg = empty_vectors
        neighbor_distances_arg = empty_distances
        pair_params_arg = empty_pair_params
        pair_energies_arg = empty_energies
        pair_forces_arg = empty_forces
        dim = int(positions.shape[0])

    wp.launch(
        kernel=get_naive_neighbor_matrix_kernel(
            wp_dtype,
            pbc_mode="none",
            batched=batched,
            half_fill=half_fill,
            selective=rebuild_flags is not None,
            partial=partial,
            return_vectors=return_vectors,
            return_distances=return_distances,
            pair_fn=pair_fn,
        ),
        dim=(1, 1, dim),
        inputs=[
            positions,
            empty_offsets,
            cutoff_sq,
            wp_dtype(0.0),
            empty_cell,
            empty_shift_range,
            empty_num_shifts,
            batch_idx_arg,
            batch_ptr_arg,
            target_indices_arg,
            neighbor_matrix,
            empty_shifts,
            num_neighbors,
            empty_matrix,
            empty_shifts,
            empty_num_neighbors,
            neighbor_vectors_arg,
            neighbor_distances_arg,
            pair_params_arg,
            pair_energies_arg,
            pair_forces_arg,
            rebuild_flags_arg,
        ],
        device=device,
    )


def _launch_naive_neighbor_matrix_pbc(
    positions: wp.array,
    cutoff: float,
    cell: wp.array,
    pbc: wp.array | None,
    shift_range: wp.array,
    neighbor_matrix: wp.array,
    neighbor_matrix_shifts: wp.array,
    num_neighbors: wp.array,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    num_shifts: int | None = None,
    batch_ptr: wp.array | None = None,
    batch_idx: wp.array | None = None,
    num_shifts_arr: wp.array | None = None,
    max_shifts_per_system: int | None = None,
    max_atoms_per_system: int | None = None,
    half_fill: bool = False,
    rebuild_flags: wp.array | None = None,
    wrap_positions: bool = True,
    target_indices: wp.array | None = None,
    return_vectors: bool = False,
    return_distances: bool = False,
    pair_fn: wp.Function | None = None,
    pair_params: wp.array | None = None,
    neighbor_vectors: wp.array | None = None,
    neighbor_distances: wp.array | None = None,
    pair_energies: wp.array | None = None,
    pair_forces: wp.array | None = None,
    positions_wrapped_buffer: wp.array | None = None,
    per_atom_cell_offsets_buffer: wp.array | None = None,
    inv_cell_buffer: wp.array | None = None,
    strategy: str = "auto",
) -> None:
    """Launch the single-cutoff PBC naive neighbor-matrix path."""
    if batched:
        if batch_ptr is None or batch_idx is None or num_shifts_arr is None:
            raise ValueError(
                "batch_ptr, batch_idx, and num_shifts_arr are required for batched PBC"
            )
        if max_shifts_per_system is None or max_atoms_per_system is None:
            raise ValueError(
                "max_shifts_per_system and max_atoms_per_system are required for batched PBC"
            )
    elif num_shifts is None:
        raise ValueError("num_shifts is required for single-system PBC")

    has_pair_outputs = _has_naive_pair_outputs(
        target_indices,
        return_vectors,
        return_distances,
        pair_fn,
        pair_params,
        neighbor_vectors,
        neighbor_distances,
        pair_energies,
        pair_forces,
    )
    partial = target_indices is not None
    pbc_mode = _pbc_mode_from_wrap(wrap_positions)
    if strategy not in {"auto", "scalar", "tile"}:
        raise ValueError(
            f"strategy must be 'auto' | 'scalar' | 'tile', got {strategy!r}",
        )
    can_tile = (
        not has_pair_outputs
        and not _is_cpu_device(device)
        and (not batched or wrap_positions)
    )
    if strategy == "tile":
        if not can_tile:
            raise ValueError(
                "strategy='tile' requires CUDA, no pair-output or "
                "target_indices path, and wrap_positions=True for batched PBC",
            )
        strategy = "tile"
    elif strategy == "scalar":
        strategy = "scalar"
    elif can_tile:
        strategy = "tile"
    else:
        strategy = "scalar"

    positions_work, per_atom_cell_offsets = _prepare_pbc_positions(
        positions,
        cell,
        pbc,
        batch_idx,
        wp_dtype,
        device,
        batched=batched,
        wrap_positions=wrap_positions,
        positions_wrapped_buffer=positions_wrapped_buffer,
        per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer,
        inv_cell_buffer=inv_cell_buffer,
    )
    (
        _empty_offsets,
        _empty_cell,
        _empty_shift_range,
        empty_num_shifts,
        empty_batch_idx,
        empty_batch_ptr,
        empty_target_indices,
        empty_matrix,
        empty_shifts,
        empty_num_neighbors,
        empty_vectors,
        empty_distances,
        empty_pair_params,
        empty_energies,
        empty_forces,
        empty_rebuild_flags,
    ) = _scalar_sentinels(wp_dtype, device)
    batch_idx_arg = batch_idx if batch_idx is not None else empty_batch_idx
    batch_ptr_arg = batch_ptr if batch_ptr is not None else empty_batch_ptr
    target_indices_arg = (
        target_indices if target_indices is not None else empty_target_indices
    )
    rebuild_flags_arg = (
        rebuild_flags if rebuild_flags is not None else empty_rebuild_flags
    )
    cutoff_sq = wp_dtype(cutoff * cutoff)

    if batched:
        launch_dim = (
            cell.shape[0],
            int(max_shifts_per_system),
            int(max_atoms_per_system),
        )
        num_shifts_arg = num_shifts_arr
        tile_dim = [int(max_shifts_per_system), positions.shape[0]]
    else:
        launch_dim = (1, int(num_shifts), positions.shape[0])
        num_shifts_arg = empty_num_shifts
        tile_dim = [int(num_shifts), positions.shape[0]]

    if strategy == "tile":
        wp.launch_tiled(
            kernel=get_naive_neighbor_matrix_kernel(
                wp_dtype,
                pbc_mode=pbc_mode.value,
                batched=batched,
                half_fill=half_fill,
                selective=rebuild_flags is not None,
                strategy="tile",
            ),
            dim=tile_dim,
            inputs=[
                positions_work,
                per_atom_cell_offsets,
                cutoff_sq,
                cell,
                shift_range,
                num_shifts_arg,
                batch_idx_arg,
                batch_ptr_arg,
                neighbor_matrix,
                neighbor_matrix_shifts,
                num_neighbors,
                rebuild_flags_arg,
            ],
            block_dim=BLOCK_DIM,
            device=device,
        )
        return

    if has_pair_outputs:
        active_shift_dim = int(max_shifts_per_system) if batched else int(num_shifts)
        if partial and not half_fill:
            active_shift_dim = 2 * active_shift_dim - 1
        if partial:
            launch_dim = (1, active_shift_dim, int(target_indices.shape[0]))
        elif batched:
            launch_dim = (cell.shape[0], active_shift_dim, int(max_atoms_per_system))
        else:
            launch_dim = (1, active_shift_dim, positions.shape[0])
        (
            neighbor_vectors_arg,
            neighbor_distances_arg,
            pair_params_arg,
            pair_energies_arg,
            pair_forces_arg,
        ) = _prepare_pair_output_args(
            wp_dtype,
            device,
            return_vectors=return_vectors,
            return_distances=return_distances,
            pair_fn=pair_fn,
            pair_params=pair_params,
            neighbor_vectors=neighbor_vectors,
            neighbor_distances=neighbor_distances,
            pair_energies=pair_energies,
            pair_forces=pair_forces,
            neighbor_vectors_name="neighbor_vectors",
            neighbor_distances_name="neighbor_distances",
            pair_energies_name="pair_energies",
            pair_forces_name="pair_forces",
        )
    else:
        neighbor_vectors_arg = empty_vectors
        neighbor_distances_arg = empty_distances
        pair_params_arg = empty_pair_params
        pair_energies_arg = empty_energies
        pair_forces_arg = empty_forces

    wp.launch(
        kernel=get_naive_neighbor_matrix_kernel(
            wp_dtype,
            pbc_mode=pbc_mode.value,
            batched=batched,
            half_fill=half_fill,
            selective=rebuild_flags is not None,
            partial=partial,
            return_vectors=return_vectors,
            return_distances=return_distances,
            pair_fn=pair_fn,
        ),
        dim=launch_dim,
        inputs=[
            positions_work,
            per_atom_cell_offsets,
            cutoff_sq,
            wp_dtype(0.0),
            cell,
            shift_range,
            num_shifts_arg,
            batch_idx_arg,
            batch_ptr_arg,
            target_indices_arg,
            neighbor_matrix,
            neighbor_matrix_shifts,
            num_neighbors,
            empty_matrix,
            empty_shifts,
            empty_num_neighbors,
            neighbor_vectors_arg,
            neighbor_distances_arg,
            pair_params_arg,
            pair_energies_arg,
            pair_forces_arg,
            rebuild_flags_arg,
        ],
        device=device,
    )


def _launch_naive_neighbor_matrix_dual_cutoff(
    positions: wp.array,
    cutoff1: float,
    cutoff2: float,
    neighbor_matrix1: wp.array,
    num_neighbors1: wp.array,
    neighbor_matrix2: wp.array,
    num_neighbors2: wp.array,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    batch_idx: wp.array | None = None,
    batch_ptr: wp.array | None = None,
    half_fill: bool = False,
    rebuild_flags: wp.array | None = None,
) -> None:
    """Launch dual-cutoff no-PBC kernels."""
    kernel = get_naive_neighbor_matrix_dual_cutoff_kernel(
        wp_dtype,
        pbc_mode="none",
        batched=batched,
        selective=rebuild_flags is not None,
    )
    (
        empty_offsets,
        empty_cell,
        empty_shift_range,
        empty_num_shifts,
        empty_batch_idx,
        empty_batch_ptr,
        empty_target_indices,
        _empty_matrix,
        empty_shifts,
        _empty_num_neighbors,
        empty_vectors,
        empty_distances,
        empty_pair_params,
        empty_energies,
        empty_forces,
        empty_rebuild_flags,
    ) = _scalar_sentinels(wp_dtype, device)
    if batched and (batch_idx is None or batch_ptr is None):
        raise ValueError("batch_idx and batch_ptr are required for batched dual cutoff")
    batch_idx_arg = batch_idx if batch_idx is not None else empty_batch_idx
    batch_ptr_arg = batch_ptr if batch_ptr is not None else empty_batch_ptr
    rebuild_flags_arg = (
        rebuild_flags if rebuild_flags is not None else empty_rebuild_flags
    )
    wp.launch(
        kernel=kernel,
        dim=(1, 1, positions.shape[0]),
        inputs=[
            positions,
            empty_offsets,
            wp_dtype(cutoff1 * cutoff1),
            wp_dtype(cutoff2 * cutoff2),
            empty_cell,
            empty_shift_range,
            empty_num_shifts,
            batch_idx_arg,
            batch_ptr_arg,
            empty_target_indices,
            neighbor_matrix1,
            empty_shifts,
            num_neighbors1,
            neighbor_matrix2,
            empty_shifts,
            num_neighbors2,
            empty_vectors,
            empty_distances,
            empty_pair_params,
            empty_energies,
            empty_forces,
            rebuild_flags_arg,
        ],
        device=device,
    )


def _launch_naive_neighbor_matrix_pbc_dual_cutoff(
    positions: wp.array,
    cutoff1: float,
    cutoff2: float,
    cell: wp.array,
    pbc: wp.array | None,
    shift_range: wp.array,
    neighbor_matrix1: wp.array,
    neighbor_matrix2: wp.array,
    neighbor_matrix_shifts1: wp.array,
    neighbor_matrix_shifts2: wp.array,
    num_neighbors1: wp.array,
    num_neighbors2: wp.array,
    wp_dtype: type,
    device: str,
    *,
    batched: bool,
    num_shifts: int | None = None,
    batch_ptr: wp.array | None = None,
    batch_idx: wp.array | None = None,
    num_shifts_arr: wp.array | None = None,
    max_shifts_per_system: int | None = None,
    max_atoms_per_system: int | None = None,
    half_fill: bool = False,
    rebuild_flags: wp.array | None = None,
    wrap_positions: bool = True,
    positions_wrapped_buffer: wp.array | None = None,
    per_atom_cell_offsets_buffer: wp.array | None = None,
    inv_cell_buffer: wp.array | None = None,
) -> None:
    """Launch dual-cutoff PBC kernels."""
    pbc_mode = _pbc_mode_from_wrap(wrap_positions)
    positions_work, per_atom_cell_offsets = _prepare_pbc_positions(
        positions,
        cell,
        pbc,
        batch_idx,
        wp_dtype,
        device,
        batched=batched,
        wrap_positions=wrap_positions,
        positions_wrapped_buffer=positions_wrapped_buffer,
        per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer,
        inv_cell_buffer=inv_cell_buffer,
    )
    kernel = get_naive_neighbor_matrix_dual_cutoff_kernel(
        wp_dtype,
        pbc_mode=pbc_mode.value,
        batched=batched,
        selective=rebuild_flags is not None,
    )
    (
        _empty_offsets,
        _empty_cell,
        _empty_shift_range,
        empty_num_shifts,
        empty_batch_idx,
        empty_batch_ptr,
        empty_target_indices,
        _empty_matrix,
        _empty_shifts,
        _empty_num_neighbors,
        empty_vectors,
        empty_distances,
        empty_pair_params,
        empty_energies,
        empty_forces,
        empty_rebuild_flags,
    ) = _scalar_sentinels(wp_dtype, device)
    batch_idx_arg = batch_idx if batch_idx is not None else empty_batch_idx
    batch_ptr_arg = batch_ptr if batch_ptr is not None else empty_batch_ptr
    rebuild_flags_arg = (
        rebuild_flags if rebuild_flags is not None else empty_rebuild_flags
    )

    if batched:
        if batch_ptr is None or batch_idx is None or num_shifts_arr is None:
            raise ValueError(
                "batch_ptr, batch_idx, and num_shifts_arr are required for batched PBC"
            )
        if max_shifts_per_system is None or max_atoms_per_system is None:
            raise ValueError(
                "max_shifts_per_system and max_atoms_per_system are required for batched PBC"
            )
        launch_dim = (cell.shape[0], max_shifts_per_system, max_atoms_per_system)
        num_shifts_arg = num_shifts_arr
    else:
        if num_shifts is None:
            raise ValueError("num_shifts is required for single-system PBC")
        launch_dim = (1, num_shifts, positions.shape[0])
        num_shifts_arg = empty_num_shifts

    wp.launch(
        kernel=kernel,
        dim=launch_dim,
        inputs=[
            positions_work,
            per_atom_cell_offsets,
            wp_dtype(cutoff1 * cutoff1),
            wp_dtype(cutoff2 * cutoff2),
            cell,
            shift_range,
            num_shifts_arg,
            batch_idx_arg,
            batch_ptr_arg,
            empty_target_indices,
            neighbor_matrix1,
            neighbor_matrix_shifts1,
            num_neighbors1,
            neighbor_matrix2,
            neighbor_matrix_shifts2,
            num_neighbors2,
            empty_vectors,
            empty_distances,
            empty_pair_params,
            empty_energies,
            empty_forces,
            rebuild_flags_arg,
        ],
        device=device,
    )


[docs] def naive_neighbor_matrix( positions: wp.array, cutoff: float, neighbor_matrix: wp.array, num_neighbors: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, target_indices: wp.array | None = None, return_vectors: bool = False, return_distances: bool = False, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, neighbor_vectors: wp.array | None = None, neighbor_distances: wp.array | None = None, pair_energies: wp.array | None = None, pair_forces: wp.array | None = None, strategy: str = "auto", ) -> None: """Core warp launcher for naive neighbor matrix construction (no PBC). Computes pairwise distances and fills the neighbor matrix with atom indices within the cutoff distance. Internally dispatches between two kernels based on ``device``: a scalar SIMT kernel on CPU and a tile-cooperative kernel on CUDA (``wp.launch_tiled`` with ``block_dim = BLOCK_DIM`` cooperatively sweeping the j-loop). Both produce identical pair sets; per-row ordering within ``neighbor_matrix`` may differ. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Atomic coordinates in Cartesian space. cutoff : float Cutoff distance for neighbor detection in Cartesian units. Must be positive. Atoms within this distance are considered neighbors. neighbor_matrix : wp.array, shape (total_atoms, max_neighbors), dtype=wp.int32 OUTPUT: Neighbor matrix to be filled with neighbor atom indices. Must be pre-allocated. Entries are filled with atom indices. num_neighbors : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom. Must be pre-allocated. Updated in-place with actual neighbor counts. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j to avoid double counting. If False, store all neighbor relationships symmetrically. rebuild_flags : wp.array, shape (1,), dtype=wp.bool, optional When provided, the kernel checks this flag on the GPU and skips work when False (no CPU-GPU sync). target_indices : wp.array, shape (M,), dtype=wp.int32, optional Unique, in-bounds global atom indices restricting which atoms act as sources (rows) in the output. Output rows correspond to ``target_indices`` in order. When omitted, all atoms are sources. return_vectors : bool, default=False If True, write per-pair displacement vectors into ``neighbor_vectors``. Requires ``neighbor_vectors`` to be supplied. return_distances : bool, default=False If True, write per-pair Euclidean distances into ``neighbor_distances``. Requires ``neighbor_distances`` to be supplied. pair_fn : wp.Function, optional Module-scope ``@wp.func`` with signature ``pair_fn(r_ij, distance, pair_params, i, j) -> (energy, force)``. When provided, the kernel evaluates this callback per accepted pair and writes per-pair energies/forces into ``pair_energies`` / ``pair_forces``. ``pair_fn`` is keyed into the kernel cache by its function-object identity, so callers must use module-scope singleton ``@wp.func`` objects (not lambdas or nested defs). pair_params : wp.array, shape (num_atoms, num_parameters), dtype=positions.dtype, optional Per-atom parameter table passed to ``pair_fn``. ``pair_params[i]`` is the parameter row of length ``num_parameters`` belonging to atom ``i`` and ``pair_fn`` may read any ``pair_params[j]`` row it needs (e.g. for Lorentz-Berthelot mixing in the Lennard-Jones pair potential). Required when ``pair_fn`` is provided. neighbor_vectors : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``return_vectors=True``. Stores the displacement vector ``positions[j] - positions[i]`` for each recorded pair. neighbor_distances : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``return_distances=True``. Stores the Euclidean distance for each recorded pair. pair_energies : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``pair_fn`` is provided. Stores the per-pair energy returned by ``pair_fn``. pair_forces : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``pair_fn`` is provided. Stores the per-pair force returned by ``pair_fn``. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - The CUDA path uses ``wp.launch_tiled(block_dim=BLOCK_DIM)``; Warp forces ``block_dim = 1`` on CPU which would silently break the lane-cooperative partitioning, so CPU callers take the scalar path. - The tile-cooperative path is taken only for the default call. When any of ``target_indices`` / ``return_vectors`` / ``return_distances`` / ``pair_fn`` is supplied, the scalar factory kernel is used regardless of device (no tile variant for these axes). See Also -------- naive_neighbor_matrix_pbc : Version with periodic boundary conditions batch_naive_neighbor_matrix : Batched (multi-system) variant get_naive_neighbor_matrix_kernel : Low-level single-cutoff kernel accessor """ if not _has_naive_pair_outputs( target_indices, return_vectors, return_distances, pair_fn, pair_params, neighbor_vectors, neighbor_distances, pair_energies, pair_forces, ): _launch_naive_neighbor_matrix_no_pbc( positions, cutoff, neighbor_matrix, num_neighbors, wp_dtype, device, batched=False, half_fill=half_fill, rebuild_flags=rebuild_flags, ) return _launch_naive_neighbor_matrix_no_pbc( positions, cutoff, neighbor_matrix, num_neighbors, wp_dtype, device, batched=False, half_fill=half_fill, rebuild_flags=rebuild_flags, target_indices=target_indices, return_vectors=return_vectors, return_distances=return_distances, pair_fn=pair_fn, pair_params=pair_params, neighbor_vectors=neighbor_vectors, neighbor_distances=neighbor_distances, pair_energies=pair_energies, pair_forces=pair_forces, strategy=strategy, )
[docs] def batch_naive_neighbor_matrix( positions: wp.array, cutoff: float, batch_idx: wp.array, batch_ptr: wp.array, neighbor_matrix: wp.array, num_neighbors: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, target_indices: wp.array | None = None, return_vectors: bool = False, return_distances: bool = False, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, neighbor_vectors: wp.array | None = None, neighbor_distances: wp.array | None = None, pair_energies: wp.array | None = None, pair_forces: wp.array | None = None, strategy: str = "auto", ) -> None: """Core warp launcher for batched naive neighbor matrix construction (no PBC). Computes pairwise distances and fills the neighbor matrix for multiple systems in a batch using pure warp operations. No periodic boundary conditions are applied. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Concatenated Cartesian coordinates for all systems. cutoff : float Cutoff distance for neighbor detection in Cartesian units. batch_idx : wp.array, shape (total_atoms,), dtype=wp.int32 System index for each atom. batch_ptr : wp.array, shape (num_systems + 1,), dtype=wp.int32 Cumulative atom counts defining system boundaries. neighbor_matrix : wp.array, shape (total_atoms, max_neighbors), dtype=wp.int32 OUTPUT: Neighbor matrix to be filled with neighbor atom indices. num_neighbors : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j to avoid double counting. rebuild_flags : wp.array, shape (num_systems,), dtype=wp.bool, optional Per-system rebuild flags. If provided, only systems where ``rebuild_flags[i]`` is True are processed; others are skipped on the GPU without CPU sync. Per-system counters are reset via :func:`selective_zero_num_neighbors` internally. target_indices : wp.array, shape (M,), dtype=wp.int32, optional Unique, in-bounds global atom indices restricting which atoms act as sources. In batched mode each target searches only atoms in its own system (the system is resolved via ``batch_idx``). Output rows follow ``target_indices``. return_vectors : bool, default=False If True, write per-pair displacement vectors into ``neighbor_vectors``. return_distances : bool, default=False If True, write per-pair Euclidean distances into ``neighbor_distances``. pair_fn : wp.Function, optional Module-scope ``@wp.func`` with signature ``pair_fn(r_ij, distance, pair_params, i, j) -> (energy, force)``. Required to be a module-scope singleton; keyed into the kernel cache by object identity. pair_params : wp.array, shape (num_atoms, num_parameters), dtype=positions.dtype, optional Per-atom parameter table passed to ``pair_fn``. ``pair_params[i]`` is the parameter row of length ``num_parameters`` belonging to atom ``i`` and ``pair_fn`` may read any ``pair_params[j]`` row it needs (e.g. for Lorentz-Berthelot mixing in the Lennard-Jones pair potential). neighbor_vectors : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``return_vectors=True``. neighbor_distances : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``return_distances=True``. pair_energies : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``pair_fn`` is provided. pair_forces : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``pair_fn`` is provided. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - Default calls dispatch internally: * On CPU, always use the scalar kernel (Warp forces ``block_dim=1`` on CPU). * On CUDA, use the tile-cooperative kernel when the adaptive ``use_tiled`` heuristic favours it (``total_atoms >= 2048`` and ``total_atoms >= 256 * num_systems``, with a tighter ``>= 512 * num_systems`` threshold above 12 288 atoms); otherwise fall back to the scalar kernel. - When any of the pair-output kwargs is supplied, the scalar factory kernel is used (no tile variant for the pair-output kwargs). See Also -------- batch_naive_neighbor_matrix_pbc : Version with periodic boundary conditions naive_neighbor_matrix : Single-system variant get_naive_neighbor_matrix_kernel : Low-level single-cutoff kernel accessor """ if not _has_naive_pair_outputs( target_indices, return_vectors, return_distances, pair_fn, pair_params, neighbor_vectors, neighbor_distances, pair_energies, pair_forces, ): if rebuild_flags is not None: selective_zero_num_neighbors( num_neighbors, batch_idx, rebuild_flags, device ) _launch_naive_neighbor_matrix_no_pbc( positions, cutoff, neighbor_matrix, num_neighbors, wp_dtype, device, batched=True, batch_idx=batch_idx, batch_ptr=batch_ptr, half_fill=half_fill, rebuild_flags=rebuild_flags, ) return if rebuild_flags is not None: selective_zero_num_neighbors(num_neighbors, batch_idx, rebuild_flags, device) _launch_naive_neighbor_matrix_no_pbc( positions, cutoff, neighbor_matrix, num_neighbors, wp_dtype, device, batched=True, batch_idx=batch_idx, batch_ptr=batch_ptr, half_fill=half_fill, rebuild_flags=rebuild_flags, target_indices=target_indices, return_vectors=return_vectors, return_distances=return_distances, pair_fn=pair_fn, pair_params=pair_params, neighbor_vectors=neighbor_vectors, neighbor_distances=neighbor_distances, pair_energies=pair_energies, pair_forces=pair_forces, strategy=strategy, )
[docs] def naive_neighbor_matrix_pbc( positions: wp.array, cutoff: float, cell: wp.array, shift_range: wp.array, num_shifts: int, neighbor_matrix: wp.array, neighbor_matrix_shifts: wp.array, num_neighbors: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, wrap_positions: bool = True, target_indices: wp.array | None = None, return_vectors: bool = False, return_distances: bool = False, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, neighbor_vectors: wp.array | None = None, neighbor_distances: wp.array | None = None, pair_energies: wp.array | None = None, pair_forces: wp.array | None = None, positions_wrapped_buffer: wp.array | None = None, per_atom_cell_offsets_buffer: wp.array | None = None, inv_cell_buffer: wp.array | None = None, strategy: str = "auto", # Deprecated kwarg aliases: positions_wrapped: wp.array | None = None, per_atom_cell_offsets: wp.array | None = None, inv_cell: wp.array | None = None, pbc: wp.array | None = None, ) -> None: """Core warp launcher for naive neighbor matrix construction with PBC. Computes neighbor relationships between atoms across periodic boundaries. Internally dispatches between two kernel families based on ``device``: a scalar SIMT kernel on CPU and a tile-cooperative kernel on CUDA (``wp.launch_tiled`` with ``block_dim = BLOCK_DIM``). Both produce identical pair sets and shift vectors; per-row ordering inside ``neighbor_matrix`` may differ. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Atomic coordinates in Cartesian space. cutoff : float Cutoff distance for neighbor detection in Cartesian units. cell : wp.array, shape (1, 3, 3), dtype=wp.mat33* Cell matrix defining lattice vectors in Cartesian coordinates. shift_range : wp.array, shape (1, 3), dtype=wp.vec3i Shift range per dimension for the single system. num_shifts : int Number of periodic shifts for the single system. neighbor_matrix : wp.array, shape (total_atoms, max_neighbors), dtype=wp.int32 OUTPUT: Neighbor matrix to be filled with neighbor atom indices. neighbor_matrix_shifts : wp.array, shape (total_atoms, max_neighbors, 3), dtype=wp.vec3i OUTPUT: Matrix storing shift vectors for each neighbor relationship. num_neighbors : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j. rebuild_flags : wp.array, shape (1,), dtype=wp.bool, optional When provided, the kernel checks this flag on the GPU and skips work when False (no CPU-GPU sync). wrap_positions : bool, default=True If True, wrap input positions into the primary cell before neighbor search. When False the positions are assumed to be already wrapped (e.g. by a preceding integration step). target_indices : wp.array, shape (M,), dtype=wp.int32, optional Unique, in-bounds global atom indices restricting which atoms act as sources. Output rows follow ``target_indices``. return_vectors : bool, default=False If True, write per-pair displacement vectors (including the periodic shift contribution) into ``neighbor_vectors``. return_distances : bool, default=False If True, write per-pair Euclidean distances into ``neighbor_distances``. pair_fn : wp.Function, optional Module-scope ``@wp.func`` with signature ``pair_fn(r_ij, distance, pair_params, i, j) -> (energy, force)``. Keyed by object identity. pair_params : wp.array, shape (num_atoms, num_parameters), dtype=positions.dtype, optional Per-atom parameter table passed to ``pair_fn``. ``pair_params[i]`` is the parameter row of length ``num_parameters`` belonging to atom ``i`` and ``pair_fn`` may read any ``pair_params[j]`` row it needs (e.g. for Lorentz-Berthelot mixing in the Lennard-Jones pair potential). neighbor_vectors : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``return_vectors=True``. neighbor_distances : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``return_distances=True``. pair_energies : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``pair_fn`` is provided. pair_forces : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``pair_fn`` is provided. positions_wrapped_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3*, optional Caller-supplied scratch buffer for wrapped positions (only used when ``wrap_positions=True``). Treated as scratch — overwritten on every call. When omitted the launcher allocates a fresh buffer for the call. per_atom_cell_offsets_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3i, optional Caller-supplied scratch buffer for per-atom cell offsets (only used when ``wrap_positions=True``). inv_cell_buffer : wp.array, shape (num_systems,), dtype=wp.mat33*, optional Caller-supplied scratch buffer for inverse cell matrices (only used when ``wrap_positions=True``). pbc : wp.array, shape (1, 3), dtype=wp.bool, optional Per-axis periodic boundary flags. When supplied, axes marked False are left unwrapped during position wrapping. When omitted, wrapping uses the existing all-axis behavior. positions_wrapped, per_atom_cell_offsets, inv_cell : deprecated Deprecated aliases of the ``*_buffer`` kwargs above. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - When ``wrap_positions`` is True, positions are wrapped into the primary cell in a preprocessing step before the neighbor search kernel. - The scratch buffers used for the wrap step (``positions_wrapped_buffer``, ``per_atom_cell_offsets_buffer``, ``inv_cell_buffer``) may be supplied by the caller to eliminate per-call allocation; their contents are overwritten on every call. When omitted the launcher allocates a fresh buffer for the call. - The CUDA path uses ``wp.launch_tiled(block_dim=BLOCK_DIM)``; CPU is forced to ``block_dim = 1`` by Warp, so CPU callers take the scalar path. - When any of the pair-output kwargs is supplied, the scalar factory kernel is used (no tile variant for the pair-output kwargs). See Also -------- naive_neighbor_matrix : Version without periodic boundary conditions batch_naive_neighbor_matrix_pbc : Batched (multi-system) variant get_naive_neighbor_matrix_kernel : Low-level single-cutoff kernel accessor """ positions_wrapped_buffer = resolve_buffer_alias( "positions_wrapped_buffer", positions_wrapped_buffer, "positions_wrapped", positions_wrapped, ) per_atom_cell_offsets_buffer = resolve_buffer_alias( "per_atom_cell_offsets_buffer", per_atom_cell_offsets_buffer, "per_atom_cell_offsets", per_atom_cell_offsets, ) inv_cell_buffer = resolve_buffer_alias( "inv_cell_buffer", inv_cell_buffer, "inv_cell", inv_cell, ) if not _has_naive_pair_outputs( target_indices, return_vectors, return_distances, pair_fn, pair_params, neighbor_vectors, neighbor_distances, pair_energies, pair_forces, ): _launch_naive_neighbor_matrix_pbc( positions, cutoff, cell, pbc, shift_range, neighbor_matrix, neighbor_matrix_shifts, num_neighbors, wp_dtype, device, batched=False, num_shifts=num_shifts, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, positions_wrapped_buffer=positions_wrapped_buffer, per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer, inv_cell_buffer=inv_cell_buffer, ) return _launch_naive_neighbor_matrix_pbc( positions, cutoff, cell, pbc, shift_range, neighbor_matrix, neighbor_matrix_shifts, num_neighbors, wp_dtype, device, num_shifts=num_shifts, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, target_indices=target_indices, return_vectors=return_vectors, return_distances=return_distances, pair_fn=pair_fn, pair_params=pair_params, neighbor_vectors=neighbor_vectors, neighbor_distances=neighbor_distances, pair_energies=pair_energies, pair_forces=pair_forces, batched=False, positions_wrapped_buffer=positions_wrapped_buffer, per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer, inv_cell_buffer=inv_cell_buffer, strategy=strategy, )
[docs] def batch_naive_neighbor_matrix_pbc( positions: wp.array, cell: wp.array, cutoff: float, batch_ptr: wp.array, batch_idx: wp.array, shift_range: wp.array, num_shifts_arr: wp.array, max_shifts_per_system: int, neighbor_matrix: wp.array, neighbor_matrix_shifts: wp.array, num_neighbors: wp.array, wp_dtype: type, device: str, max_atoms_per_system: int, half_fill: bool = False, rebuild_flags: wp.array | None = None, wrap_positions: bool = True, target_indices: wp.array | None = None, return_vectors: bool = False, return_distances: bool = False, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, neighbor_vectors: wp.array | None = None, neighbor_distances: wp.array | None = None, pair_energies: wp.array | None = None, pair_forces: wp.array | None = None, positions_wrapped_buffer: wp.array | None = None, per_atom_cell_offsets_buffer: wp.array | None = None, inv_cell_buffer: wp.array | None = None, strategy: str = "auto", # Deprecated kwarg aliases: positions_wrapped: wp.array | None = None, per_atom_cell_offsets: wp.array | None = None, inv_cell: wp.array | None = None, pbc: wp.array | None = None, ) -> None: """Core warp launcher for batched naive neighbor matrix construction with PBC. Computes neighbor relationships between atoms across periodic boundaries for multiple systems in a batch using pure warp operations. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Concatenated Cartesian coordinates for all systems. cell : wp.array, shape (num_systems, 3, 3), dtype=wp.mat33* Cell matrices for each system. cutoff : float Cutoff distance for neighbor detection. batch_ptr : wp.array, shape (num_systems + 1,), dtype=wp.int32 Cumulative atom counts defining system boundaries. batch_idx : wp.array, shape (total_atoms,), dtype=wp.int32 System index for each atom. shift_range : wp.array, shape (num_systems, 3), dtype=wp.vec3i Shift range per dimension per system. num_shifts_arr : wp.array, shape (num_systems,), dtype=wp.int32 Number of shifts per system. max_shifts_per_system : int Maximum per-system shift count (launch dimension). neighbor_matrix : wp.array, shape (total_atoms, max_neighbors), dtype=wp.int32 OUTPUT: Neighbor matrix. neighbor_matrix_shifts : wp.array, shape (total_atoms, max_neighbors, 3), dtype=wp.vec3i OUTPUT: Shift vectors for each neighbor. num_neighbors : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors per atom. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). max_atoms_per_system : int Maximum number of atoms in any single system. half_fill : bool, default=False If True, only store half of the neighbor relationships. rebuild_flags : wp.array, shape (num_systems,), dtype=wp.bool, optional Per-system rebuild flags. wrap_positions : bool, default=True If True, wrap input positions into the primary cell. target_indices : wp.array, shape (M,), dtype=wp.int32, optional Unique, in-bounds global atom indices restricting which atoms act as sources. In batched mode each target searches only atoms in its own system (resolved via ``batch_idx``). Output rows follow ``target_indices``. return_vectors : bool, default=False If True, write per-pair displacement vectors (including the periodic shift contribution) into ``neighbor_vectors``. return_distances : bool, default=False If True, write per-pair Euclidean distances into ``neighbor_distances``. pair_fn : wp.Function, optional Module-scope ``@wp.func`` with signature ``pair_fn(r_ij, distance, pair_params, i, j) -> (energy, force)``. Keyed by object identity. pair_params : wp.array, shape (num_atoms, num_parameters), dtype=positions.dtype, optional Per-atom parameter table passed to ``pair_fn``. ``pair_params[i]`` is the parameter row of length ``num_parameters`` belonging to atom ``i`` and ``pair_fn`` may read any ``pair_params[j]`` row it needs (e.g. for Lorentz-Berthelot mixing in the Lennard-Jones pair potential). neighbor_vectors : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``return_vectors=True``. neighbor_distances : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``return_distances=True``. pair_energies : wp.array, shape (rows, max_neighbors), dtype=wp.float*, optional OUTPUT: Required if ``pair_fn`` is provided. pair_forces : wp.array, shape (rows, max_neighbors), dtype=wp.vec3*, optional OUTPUT: Required if ``pair_fn`` is provided. positions_wrapped_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3*, optional Caller-supplied scratch for wrapped positions (used when ``wrap_positions=True``). Optional — the launcher allocates when omitted. per_atom_cell_offsets_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3i, optional Caller-supplied scratch for per-atom cell offsets. inv_cell_buffer : wp.array, shape (num_systems,), dtype=wp.mat33*, optional Caller-supplied scratch for inverse cell matrices. pbc : wp.array, shape (num_systems, 3), dtype=wp.bool, optional Per-system, per-axis periodic boundary flags. When supplied, axes marked False are left unwrapped during position wrapping. When omitted, wrapping uses the existing all-axis behavior. positions_wrapped, per_atom_cell_offsets, inv_cell : deprecated Deprecated aliases of the ``*_buffer`` kwargs above. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - When ``wrap_positions`` is True, positions are wrapped into the primary cell in a preprocessing step before the neighbor search kernel. - The scratch buffers used for the wrap step may be supplied by the caller (``positions_wrapped_buffer``, ``per_atom_cell_offsets_buffer``, ``inv_cell_buffer``) to eliminate per-call allocation; when omitted the launcher allocates fresh per call (batched callers do not share the single-system cache). - Default calls dispatch internally: * On CPU, use the scalar 3D-launch kernels. * On CUDA with ``wrap_positions=True``, use the tile-cooperative kernel when the adaptive ``use_tiled`` heuristic favours it (``256 <= avg_atoms_per_system < 6144`` and either ``avg_atoms_per_system >= 2048`` or ``total_atoms <= 8192``); otherwise fall back to the scalar 3D-launch kernel. * When ``wrap_positions=False`` the prewrapped scalar kernels are used on both devices (no tiled prewrapped variant). - When any of the pair-output kwargs is supplied, the scalar factory kernel is used (no tile variant for the pair-output kwargs). See Also -------- batch_naive_neighbor_matrix : Version without periodic boundary conditions naive_neighbor_matrix_pbc : Single-system variant get_naive_neighbor_matrix_kernel : Low-level single-cutoff kernel accessor """ positions_wrapped_buffer = resolve_buffer_alias( "positions_wrapped_buffer", positions_wrapped_buffer, "positions_wrapped", positions_wrapped, ) per_atom_cell_offsets_buffer = resolve_buffer_alias( "per_atom_cell_offsets_buffer", per_atom_cell_offsets_buffer, "per_atom_cell_offsets", per_atom_cell_offsets, ) inv_cell_buffer = resolve_buffer_alias( "inv_cell_buffer", inv_cell_buffer, "inv_cell", inv_cell, ) if not _has_naive_pair_outputs( target_indices, return_vectors, return_distances, pair_fn, pair_params, neighbor_vectors, neighbor_distances, pair_energies, pair_forces, ): if rebuild_flags is not None: selective_zero_num_neighbors( num_neighbors, batch_idx, rebuild_flags, device ) _launch_naive_neighbor_matrix_pbc( positions, cutoff, cell, pbc, shift_range, neighbor_matrix, neighbor_matrix_shifts, num_neighbors, wp_dtype, device, batched=True, batch_ptr=batch_ptr, batch_idx=batch_idx, num_shifts_arr=num_shifts_arr, max_shifts_per_system=max_shifts_per_system, max_atoms_per_system=max_atoms_per_system, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, positions_wrapped_buffer=positions_wrapped_buffer, per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer, inv_cell_buffer=inv_cell_buffer, ) return if rebuild_flags is not None: selective_zero_num_neighbors(num_neighbors, batch_idx, rebuild_flags, device) _launch_naive_neighbor_matrix_pbc( positions, cutoff, cell, pbc, shift_range, neighbor_matrix, neighbor_matrix_shifts, num_neighbors, wp_dtype, device, batch_ptr=batch_ptr, batch_idx=batch_idx, num_shifts_arr=num_shifts_arr, max_shifts_per_system=max_shifts_per_system, max_atoms_per_system=max_atoms_per_system, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, target_indices=target_indices, return_vectors=return_vectors, return_distances=return_distances, pair_fn=pair_fn, pair_params=pair_params, neighbor_vectors=neighbor_vectors, neighbor_distances=neighbor_distances, pair_energies=pair_energies, pair_forces=pair_forces, batched=True, )
[docs] def naive_neighbor_matrix_dual_cutoff( positions: wp.array, cutoff1: float, cutoff2: float, neighbor_matrix1: wp.array, num_neighbors1: wp.array, neighbor_matrix2: wp.array, num_neighbors2: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, ) -> None: """Core warp launcher for naive dual cutoff neighbor matrix construction (no PBC). Computes pairwise distances and fills two neighbor matrices with atom indices within different cutoff distances using pure warp operations. No periodic boundary conditions are applied. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Atomic coordinates in Cartesian space. cutoff1 : float First cutoff distance (typically smaller). cutoff2 : float Second cutoff distance (typically larger). neighbor_matrix1 : wp.array, shape (total_atoms, max_neighbors1), dtype=wp.int32 OUTPUT: First neighbor matrix to be filled. num_neighbors1 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom within cutoff1. neighbor_matrix2 : wp.array, shape (total_atoms, max_neighbors2), dtype=wp.int32 OUTPUT: Second neighbor matrix to be filled. num_neighbors2 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom within cutoff2. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j to avoid double counting. rebuild_flags : wp.array, shape (1,), dtype=wp.bool, optional Per-system rebuild flags. If provided, only rebuilds when ``rebuild_flags[0]`` is True; otherwise skips on the GPU without CPU sync. pair_fn : Any, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. Kept in the signature so callers can pass through generic kwargs. pair_params : wp.array, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - Dual-cutoff mode does not support pair outputs or target-row restriction; ``pair_fn`` / ``pair_params`` raise ``ValueError`` if provided, and ``target_indices`` / ``return_vectors`` / ``return_distances`` are absent from the signature. See Also -------- naive_neighbor_matrix_pbc_dual_cutoff : Version with periodic boundary conditions batch_naive_neighbor_matrix_dual_cutoff : Batched (multi-system) variant get_naive_neighbor_matrix_dual_cutoff_kernel : Low-level dual-cutoff kernel accessor """ _reject_pair_fn_for_dual_cutoff(pair_fn, pair_params) _launch_naive_neighbor_matrix_dual_cutoff( positions, cutoff1, cutoff2, neighbor_matrix1, num_neighbors1, neighbor_matrix2, num_neighbors2, wp_dtype, device, batched=False, half_fill=half_fill, rebuild_flags=rebuild_flags, )
[docs] def naive_neighbor_matrix_pbc_dual_cutoff( positions: wp.array, cutoff1: float, cutoff2: float, cell: wp.array, shift_range: wp.array, num_shifts: int, neighbor_matrix1: wp.array, neighbor_matrix2: wp.array, neighbor_matrix_shifts1: wp.array, neighbor_matrix_shifts2: wp.array, num_neighbors1: wp.array, num_neighbors2: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, wrap_positions: bool = True, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, positions_wrapped_buffer: wp.array | None = None, per_atom_cell_offsets_buffer: wp.array | None = None, inv_cell_buffer: wp.array | None = None, # Deprecated kwarg aliases: positions_wrapped: wp.array | None = None, per_atom_cell_offsets: wp.array | None = None, inv_cell: wp.array | None = None, pbc: wp.array | None = None, ) -> None: """Core warp launcher for naive dual cutoff neighbor matrix construction with PBC. Computes neighbor relationships between atoms across periodic boundaries for two different cutoff distances using pure warp operations. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Atomic coordinates in Cartesian space. cutoff1 : float First cutoff distance (typically smaller). cutoff2 : float Second cutoff distance (typically larger). cell : wp.array, shape (1, 3, 3), dtype=wp.mat33* Cell matrix defining lattice vectors in Cartesian coordinates. shift_range : wp.array, shape (1, 3), dtype=wp.vec3i Shift range per dimension for the single system. num_shifts : int Number of periodic shifts for the single system. neighbor_matrix1 : wp.array, shape (total_atoms, max_neighbors1), dtype=wp.int32 OUTPUT: First neighbor matrix to be filled. neighbor_matrix2 : wp.array, shape (total_atoms, max_neighbors2), dtype=wp.int32 OUTPUT: Second neighbor matrix to be filled. neighbor_matrix_shifts1 : wp.array, shape (total_atoms, max_neighbors1, 3), dtype=wp.vec3i OUTPUT: Shift vectors for first neighbor matrix. neighbor_matrix_shifts2 : wp.array, shape (total_atoms, max_neighbors2, 3), dtype=wp.vec3i OUTPUT: Shift vectors for second neighbor matrix. num_neighbors1 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom within cutoff1. num_neighbors2 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Number of neighbors found for each atom within cutoff2. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j to avoid double counting. rebuild_flags : wp.array, shape (1,), dtype=wp.bool, optional Per-system rebuild flags. If provided, only rebuilds when ``rebuild_flags[0]`` is True; otherwise skips on the GPU without CPU sync. wrap_positions : bool, default=True If True, wrap input positions into the primary cell before neighbor search. Set to False when positions are already wrapped (e.g. by a preceding integration step) to save two GPU kernel launches per call. pair_fn : Any, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. pair_params : wp.array, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. positions_wrapped_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3*, optional Caller-supplied scratch for wrapped positions (used when ``wrap_positions=True``). Optional — the launcher allocates when omitted. per_atom_cell_offsets_buffer : wp.array, shape (total_atoms,), dtype=wp.vec3i, optional Caller-supplied scratch for per-atom cell offsets. inv_cell_buffer : wp.array, shape (num_systems,), dtype=wp.mat33*, optional Caller-supplied scratch for inverse cell matrices. pbc : wp.array, shape (1, 3), dtype=wp.bool, optional Per-axis periodic boundary flags. When supplied, axes marked False are left unwrapped during position wrapping. When omitted, wrapping uses the existing all-axis behavior. positions_wrapped, per_atom_cell_offsets, inv_cell : deprecated Deprecated aliases of the ``*_buffer`` kwargs above. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - When ``wrap_positions`` is True, positions are wrapped into the primary cell in a preprocessing step before the neighbor search kernel. - The scratch buffers used for the wrap step (``positions_wrapped_buffer``, ``per_atom_cell_offsets_buffer``, ``inv_cell_buffer``) may be supplied by the caller to eliminate per-call allocation; their contents are overwritten on every call. When omitted the launcher allocates a fresh buffer for the call. - Dual-cutoff mode does not support pair outputs or target-row restriction; ``pair_fn`` / ``pair_params`` raise ``ValueError`` if provided. See Also -------- naive_neighbor_matrix_dual_cutoff : Version without periodic boundary conditions batch_naive_neighbor_matrix_pbc_dual_cutoff : Batched (multi-system) variant get_naive_neighbor_matrix_dual_cutoff_kernel : Low-level dual-cutoff kernel accessor """ _reject_pair_fn_for_dual_cutoff(pair_fn, pair_params) positions_wrapped_buffer = resolve_buffer_alias( "positions_wrapped_buffer", positions_wrapped_buffer, "positions_wrapped", positions_wrapped, ) per_atom_cell_offsets_buffer = resolve_buffer_alias( "per_atom_cell_offsets_buffer", per_atom_cell_offsets_buffer, "per_atom_cell_offsets", per_atom_cell_offsets, ) inv_cell_buffer = resolve_buffer_alias( "inv_cell_buffer", inv_cell_buffer, "inv_cell", inv_cell, ) _launch_naive_neighbor_matrix_pbc_dual_cutoff( positions, cutoff1, cutoff2, cell, pbc, shift_range, neighbor_matrix1, neighbor_matrix2, neighbor_matrix_shifts1, neighbor_matrix_shifts2, num_neighbors1, num_neighbors2, wp_dtype, device, batched=False, num_shifts=num_shifts, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, positions_wrapped_buffer=positions_wrapped_buffer, per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer, inv_cell_buffer=inv_cell_buffer, )
[docs] def batch_naive_neighbor_matrix_dual_cutoff( positions: wp.array, cutoff1: float, cutoff2: float, batch_idx: wp.array, batch_ptr: wp.array, neighbor_matrix1: wp.array, num_neighbors1: wp.array, neighbor_matrix2: wp.array, num_neighbors2: wp.array, wp_dtype: type, device: str, half_fill: bool = False, rebuild_flags: wp.array | None = None, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, ) -> None: """Core warp launcher for batched naive dual cutoff neighbor matrix construction (no PBC). Computes pairwise distances and fills two neighbor matrices with atom indices within different cutoff distances for multiple systems in a batch. No periodic boundary conditions are applied. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Concatenated Cartesian coordinates for all systems. cutoff1 : float First cutoff distance (typically smaller). cutoff2 : float Second cutoff distance (typically larger). batch_idx : wp.array, shape (total_atoms,), dtype=wp.int32 System index for each atom. batch_ptr : wp.array, shape (num_systems + 1,), dtype=wp.int32 Cumulative atom counts defining system boundaries. neighbor_matrix1 : wp.array, shape (total_atoms, max_neighbors1), dtype=wp.int32 OUTPUT: First neighbor matrix. num_neighbors1 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Neighbor counts for cutoff1. neighbor_matrix2 : wp.array, shape (total_atoms, max_neighbors2), dtype=wp.int32 OUTPUT: Second neighbor matrix. num_neighbors2 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Neighbor counts for cutoff2. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). half_fill : bool, default=False If True, only store relationships where i < j. rebuild_flags : wp.array, shape (num_systems,), dtype=wp.bool, optional Per-system rebuild flags. If provided, only systems where ``rebuild_flags[i]`` is True are processed; others are skipped on the GPU without CPU sync. Per-system counters are reset via :func:`selective_zero_num_neighbors` for both ``num_neighbors1`` and ``num_neighbors2`` internally. pair_fn : Any, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. pair_params : wp.array, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. pbc : wp.array, shape (num_systems, 3), dtype=wp.bool, optional Per-system, per-axis periodic boundary flags. When supplied, axes marked False are left unwrapped during position wrapping. When omitted, wrapping uses the existing all-axis behavior. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - Dual-cutoff mode does not support pair outputs or target-row restriction; ``pair_fn`` / ``pair_params`` raise ``ValueError`` if provided. See Also -------- batch_naive_neighbor_matrix_pbc_dual_cutoff : Version with PBC naive_neighbor_matrix_dual_cutoff : Single-system variant get_naive_neighbor_matrix_dual_cutoff_kernel : Low-level dual-cutoff kernel accessor """ _reject_pair_fn_for_dual_cutoff(pair_fn, pair_params) if rebuild_flags is not None: selective_zero_num_neighbors(num_neighbors1, batch_idx, rebuild_flags, device) selective_zero_num_neighbors(num_neighbors2, batch_idx, rebuild_flags, device) _launch_naive_neighbor_matrix_dual_cutoff( positions, cutoff1, cutoff2, neighbor_matrix1, num_neighbors1, neighbor_matrix2, num_neighbors2, wp_dtype, device, batched=True, batch_idx=batch_idx, batch_ptr=batch_ptr, half_fill=half_fill, rebuild_flags=rebuild_flags, )
[docs] def batch_naive_neighbor_matrix_pbc_dual_cutoff( positions: wp.array, cell: wp.array, cutoff1: float, cutoff2: float, batch_ptr: wp.array, batch_idx: wp.array, shift_range: wp.array, num_shifts_arr: wp.array, max_shifts_per_system: int, neighbor_matrix1: wp.array, neighbor_matrix2: wp.array, neighbor_matrix_shifts1: wp.array, neighbor_matrix_shifts2: wp.array, num_neighbors1: wp.array, num_neighbors2: wp.array, wp_dtype: type, device: str, max_atoms_per_system: int, half_fill: bool = False, rebuild_flags: wp.array | None = None, wrap_positions: bool = True, pair_fn: wp.Function | None = None, pair_params: wp.array | None = None, positions_wrapped_buffer: wp.array | None = None, per_atom_cell_offsets_buffer: wp.array | None = None, inv_cell_buffer: wp.array | None = None, # Deprecated kwarg aliases: positions_wrapped: wp.array | None = None, per_atom_cell_offsets: wp.array | None = None, inv_cell: wp.array | None = None, pbc: wp.array | None = None, ) -> None: """Core warp launcher for batched naive dual cutoff neighbor matrix construction with PBC. Computes neighbor relationships between atoms across periodic boundaries for two different cutoff distances and multiple systems in a batch. Parameters ---------- positions : wp.array, shape (total_atoms, 3), dtype=wp.vec3* Concatenated Cartesian coordinates for all systems. cell : wp.array, shape (num_systems, 3, 3), dtype=wp.mat33* Cell matrices for each system in the batch. cutoff1 : float First cutoff distance (typically smaller). cutoff2 : float Second cutoff distance (typically larger). batch_ptr : wp.array, shape (num_systems + 1,), dtype=wp.int32 Cumulative atom counts defining system boundaries. batch_idx : wp.array, shape (total_atoms,), dtype=wp.int32 System index for each atom. Required for the position-wrapping preprocessing step that maps atoms to their system's cell. shift_range : wp.array, shape (num_systems, 3), dtype=wp.vec3i Shift range per dimension per system. num_shifts_arr : wp.array, shape (num_systems,), dtype=wp.int32 Number of shifts per system. max_shifts_per_system : int Maximum per-system shift count (launch dimension). neighbor_matrix1 : wp.array, shape (total_atoms, max_neighbors1), dtype=wp.int32 OUTPUT: First neighbor matrix. neighbor_matrix2 : wp.array, shape (total_atoms, max_neighbors2), dtype=wp.int32 OUTPUT: Second neighbor matrix. neighbor_matrix_shifts1 : wp.array, shape (total_atoms, max_neighbors1, 3), dtype=wp.vec3i OUTPUT: Shift vectors for first neighbor matrix. neighbor_matrix_shifts2 : wp.array, shape (total_atoms, max_neighbors2, 3), dtype=wp.vec3i OUTPUT: Shift vectors for second neighbor matrix. num_neighbors1 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Neighbor counts for cutoff1. num_neighbors2 : wp.array, shape (total_atoms,), dtype=wp.int32 OUTPUT: Neighbor counts for cutoff2. wp_dtype : type Warp dtype (wp.float32, wp.float64, or wp.float16). device : str Warp device string (e.g., 'cuda:0', 'cpu'). max_atoms_per_system : int Maximum number of atoms in any single system. half_fill : bool, default=False If True, only store half of the neighbor relationships. rebuild_flags : wp.array, shape (num_systems,), dtype=wp.bool, optional Per-system rebuild flags. If provided, only systems where ``rebuild_flags[i]`` is True are processed; others are skipped on the GPU without CPU sync. Per-system counters are reset via :func:`selective_zero_num_neighbors` for both ``num_neighbors1`` and ``num_neighbors2`` internally. wrap_positions : bool, default=True If True, wrap input positions into the primary cell before neighbor search. Set to False when positions are already wrapped (e.g. by a preceding integration step) to save two GPU kernel launches per call. pair_fn : Any, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. pair_params : wp.array, optional Not supported in dual-cutoff mode; raises ``ValueError`` if provided. Notes ----- - This is a low-level warp interface. For framework bindings, use torch/jax wrappers. - Output arrays must be pre-allocated by caller. - When ``wrap_positions`` is True, positions are wrapped into the primary cell in a preprocessing step before the neighbor search kernel. - Dual-cutoff mode does not support pair outputs or target-row restriction; ``pair_fn`` / ``pair_params`` raise ``ValueError`` if provided. See Also -------- batch_naive_neighbor_matrix_dual_cutoff : Version without PBC naive_neighbor_matrix_pbc_dual_cutoff : Single-system variant get_naive_neighbor_matrix_dual_cutoff_kernel : Low-level dual-cutoff kernel accessor """ _reject_pair_fn_for_dual_cutoff(pair_fn, pair_params) positions_wrapped_buffer = resolve_buffer_alias( "positions_wrapped_buffer", positions_wrapped_buffer, "positions_wrapped", positions_wrapped, ) per_atom_cell_offsets_buffer = resolve_buffer_alias( "per_atom_cell_offsets_buffer", per_atom_cell_offsets_buffer, "per_atom_cell_offsets", per_atom_cell_offsets, ) inv_cell_buffer = resolve_buffer_alias( "inv_cell_buffer", inv_cell_buffer, "inv_cell", inv_cell, ) if rebuild_flags is not None: selective_zero_num_neighbors(num_neighbors1, batch_idx, rebuild_flags, device) selective_zero_num_neighbors(num_neighbors2, batch_idx, rebuild_flags, device) _launch_naive_neighbor_matrix_pbc_dual_cutoff( positions, cutoff1, cutoff2, cell, pbc, shift_range, neighbor_matrix1, neighbor_matrix2, neighbor_matrix_shifts1, neighbor_matrix_shifts2, num_neighbors1, num_neighbors2, wp_dtype, device, batched=True, batch_ptr=batch_ptr, batch_idx=batch_idx, num_shifts_arr=num_shifts_arr, max_shifts_per_system=max_shifts_per_system, max_atoms_per_system=max_atoms_per_system, half_fill=half_fill, rebuild_flags=rebuild_flags, wrap_positions=wrap_positions, positions_wrapped_buffer=positions_wrapped_buffer, per_atom_cell_offsets_buffer=per_atom_cell_offsets_buffer, inv_cell_buffer=inv_cell_buffer, )