Skip to content

kups.core.neighborlist.compact

Compactors for the neighbor list pipeline.

Compactor variants:

  • ReduceCompactor — compresses surviving candidate rows via jnp.where(keep, size=k) with a capacity assertion, preserving the emitted edge degree.
  • MaskOnlyCompactor — preserves the candidate count, replacing failing entries with OOB indices and zero shifts. Used by RefineMaskNeighborList.

Graph-level output shaping, such as restoring undirected symmetry after queried_keys deduplication, lives in pipeline postprocessors.

MaskOnlyCompactor

Bases: Compactor[D]

In-place compaction: failing entries become OOB indices and zero shifts.

No size change; preserves the candidate count from the selector. Pair candidates are already in their output index space.

Source code in src/kups/core/neighborlist/compact.py
@dataclass
class MaskOnlyCompactor[D: int](Compactor[D]):
    """In-place compaction: failing entries become OOB indices and zero shifts.

    No size change; preserves the candidate count from the selector. Pair
    candidates are already in their output index space.
    """

    def __call__(
        self,
        keep: Array,
        batch: CandidateBatch[D],
        ctx: PipelineContext,
    ) -> Edges[D]:
        indices = batch.edges.indices.apply_mask(keep[:, None])
        shifts = where_broadcast_last(keep, batch.edges.shifts, 0)
        return Edges(indices, shifts)

ReduceCompactor

Bases: Compactor[D]

Compact surviving candidates to a size-bounded Edges[D].

Compacts whole candidate rows, so pair neighbor lists and fixed higher-degree topology share the same implementation.

Source code in src/kups/core/neighborlist/compact.py
@dataclass
class ReduceCompactor[D: int](Compactor[D]):
    """Compact surviving candidates to a size-bounded ``Edges[D]``.

    Compacts whole candidate rows, so pair neighbor lists and fixed higher-degree
    topology share the same implementation.
    """

    avg_edges: Capacity[int]

    def __call__(
        self,
        keep: Array,
        batch: CandidateBatch[D],
        ctx: PipelineContext,
    ) -> Edges[D]:
        return self.compact_batch(keep, batch, ctx).edges

    def compact_batch(
        self,
        keep: Array,
        batch: CandidateBatch[D],
        ctx: PipelineContext,
    ) -> CandidateBatch[D]:
        """Compact edges together with the image flags needed by pair kernels."""
        max_edges = self.avg_edges.generate_assertion(keep.sum())
        sort_idxs = jnp.where(keep, size=max_edges.size, fill_value=keep.size)[0]

        def gather(values: Array) -> Array:
            if keep.size == 0:
                # JAX cannot gather a padded output from an empty source array.
                return jnp.zeros((max_edges.size, *values.shape[1:]), values.dtype)
            return values.at[sort_idxs].get(
                mode="fill", fill_value=0, indices_are_sorted=True
            )

        compacted = jax.tree.map(gather, batch)
        return CandidateBatch(
            Edges(
                compacted.edges.indices.apply_mask(sort_idxs[:, None] < keep.size),
                compacted.edges.shifts,
            ),
            compacted.is_minimum_image,
        )

compact_batch(keep, batch, ctx)

Compact edges together with the image flags needed by pair kernels.

Source code in src/kups/core/neighborlist/compact.py
def compact_batch(
    self,
    keep: Array,
    batch: CandidateBatch[D],
    ctx: PipelineContext,
) -> CandidateBatch[D]:
    """Compact edges together with the image flags needed by pair kernels."""
    max_edges = self.avg_edges.generate_assertion(keep.sum())
    sort_idxs = jnp.where(keep, size=max_edges.size, fill_value=keep.size)[0]

    def gather(values: Array) -> Array:
        if keep.size == 0:
            # JAX cannot gather a padded output from an empty source array.
            return jnp.zeros((max_edges.size, *values.shape[1:]), values.dtype)
        return values.at[sort_idxs].get(
            mode="fill", fill_value=0, indices_are_sorted=True
        )

    compacted = jax.tree.map(gather, batch)
    return CandidateBatch(
        Edges(
            compacted.edges.indices.apply_mask(sort_idxs[:, None] < keep.size),
            compacted.edges.shifts,
        ),
        compacted.is_minimum_image,
    )