Source code for atomvoxelizer.cupy_backend

from __future__ import annotations

import numpy as np

from .voxelgrid import VoxelGrid, _cached_sphere_offsets, _cached_sphere_offsets_and_distances

try:
    from .numba_backend import VoxelGridNumba as _BaseVoxelGrid
except ImportError:  # pragma: no cover - depends on optional dependency
    _BaseVoxelGrid = VoxelGrid

try:
    import cupy as cp
except ImportError as exc:  # pragma: no cover - depends on optional dependency
    raise ImportError(
        "VoxelGridCuPy requires the optional dependency CuPy. Install the CuPy package "
        "that matches your CUDA environment, for example `pip install cupy-cuda12x`."
    ) from exc


[docs] class VoxelGridCuPy(_BaseVoxelGrid): """CuPy-backed voxel grid. The class keeps the same public API as :class:`atomvoxelizer.VoxelGrid`, uses the Numba backend as its base when available, stores ``grid`` as a CuPy array, and overrides the mutating sphere operations. """ @property def backend_name(self): return "cupy" def __init__(self, cell, resolution=None, gpts=None, dtype=np.float32): super().__init__(cell=cell, resolution=resolution, gpts=gpts, dtype=dtype) self.grid = cp.asarray(self.grid)
[docs] def to_numpy(self): """Return the voxel values as a NumPy array.""" return cp.asnumpy(self.grid)
def _sphere_indices(self, center, radius): center_frac = np.asarray(center, dtype=np.float64) @ self.cell_inv % 1.0 center_idx = np.floor(center_frac * self.gpts).astype(np.int32) offsets = _cached_sphere_offsets(float(radius), tuple(self.gpts), tuple(map(tuple, self.cell))) indices = (offsets + center_idx) % self.gpts return tuple(cp.asarray(indices[:, axis]) for axis in range(3)) def _sphere_indices_and_values(self, center, radius, value, mask): self._validate_mask(mask) center_idx = self._center_index(center) if mask == "constant": offsets = _cached_sphere_offsets(float(radius), tuple(self.gpts), tuple(map(tuple, self.cell))) values = value else: offsets, distances = _cached_sphere_offsets_and_distances( float(radius), tuple(self.gpts), tuple(map(tuple, self.cell)) ) values = cp.asarray(distances) * value indices = (offsets + center_idx) % self.gpts return tuple(cp.asarray(indices[:, axis]) for axis in range(3)), values
[docs] def set_sphere(self, center, radius, value=1, mask="constant"): indices, values = self._sphere_indices_and_values(center, radius, value, mask) self.grid[indices] = values
[docs] def add_sphere(self, center, radius, value=1, mask="constant"): indices, values = self._sphere_indices_and_values(center, radius, value, mask) cp.add.at(self.grid, indices, values)
[docs] def mul_sphere(self, center, radius, factor=2, mask="constant"): indices, values = self._sphere_indices_and_values(center, radius, factor, mask) self.grid[indices] *= values
[docs] def div_sphere(self, center, radius, factor=2, mask="constant"): indices, values = self._sphere_indices_and_values(center, radius, factor, mask) self.grid[indices] /= values
[docs] def min_sphere(self, center, radius, value=1, mask="distance"): self._check_ordered_grid("min_sphere") indices, values = self._sphere_indices_and_values(center, radius, value, mask) cp.minimum.at(self.grid, indices, values)
[docs] def add_spheres(self, centers, radii, value=1, mask="constant"): centers, radii = self._validate_spheres(centers, radii) for center, radius in zip(centers, radii): self.add_sphere(center, float(radius), value=value, mask=mask)
[docs] def set_spheres(self, centers, radii, value=1, mask="constant"): centers, radii = self._validate_spheres(centers, radii) for center, radius in zip(centers, radii): self.set_sphere(center, float(radius), value=value, mask=mask)
[docs] def mul_spheres(self, centers, radii, factor=2, mask="constant"): centers, radii = self._validate_spheres(centers, radii) for center, radius in zip(centers, radii): self.mul_sphere(center, float(radius), factor=factor, mask=mask)
[docs] def div_spheres(self, centers, radii, factor=2, mask="constant"): centers, radii = self._validate_spheres(centers, radii) for center, radius in zip(centers, radii): self.div_sphere(center, float(radius), factor=factor, mask=mask)
[docs] def min_spheres(self, centers, radii, value=1, mask="distance"): self._check_ordered_grid("min_spheres") centers, radii = self._validate_spheres(centers, radii) for center, radius in zip(centers, radii): self.min_sphere(center, float(radius), value=value, mask=mask)
[docs] def clamp_grid(self, min_val=0.0, max_val=1.0): self._check_ordered_grid("clamp_grid") self.grid = cp.clip(self.grid, min_val, max_val)
[docs] def synchronize(self): """Synchronize the current CuPy device.""" cp.cuda.Stream.null.synchronize()