Skip to content

Commit

Permalink
Merge pull request #1282 from glotzerlab/nanobind-cluster
Browse files Browse the repository at this point in the history
Nanobind port: Cluster module
  • Loading branch information
joaander authored Sep 17, 2024
2 parents 70ee09b + 83eae33 commit b370e8c
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 190 deletions.
26 changes: 15 additions & 11 deletions freud/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ add_library(
# box
box/Box.h
# cluster
# cluster/Cluster.h
# cluster/Cluster.cc
# cluster/ClusterProperties.h
# cluster/ClusterProperties.cc
cluster/Cluster.h
cluster/Cluster.cc
cluster/ClusterProperties.h
cluster/ClusterProperties.cc
# density
# density/CorrelationFunction.h
# density/CorrelationFunction.cc
Expand Down Expand Up @@ -133,9 +133,12 @@ set_target_properties(freud PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(freud PUBLIC TBB::tbb)
target_include_directories(freud SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/extern/)

# cluster nanobind_add_module(_cluster cluster/...)
# target_link_libraries(_cluster PUBLIC freud TBB::tbb)
# target_set_install_rpath(_cluster)
# cluster
nanobind_add_module(
_cluster cluster/module-Cluster.cc cluster/export-Cluster.cc
cluster/export-ClusterProperties.cc)
target_link_libraries(_cluster PUBLIC freud TBB::tbb)
target_set_install_rpath(_cluster)

# density nanobind_add_module(_density density/...)
# target_link_libraries(_density PUBLIC freud TBB::tbb)
Expand Down Expand Up @@ -193,6 +196,7 @@ target_set_install_rpath(_util)
set(python_files
__init__.py
box.py
cluster.py
data.py
errors.py
locality.py
Expand All @@ -202,17 +206,17 @@ set(python_files
interface.py
plot.py
util.py)
# cluster.py density.py diffraction.py environment.py order.py interface.py)
# density.py diffraction.py environment.py order.py)

copy_files_to_build("${python_files}" "freud" "*.py")

# install
if(SKBUILD)
install(FILES ${python_files} DESTINATION freud)
install(TARGETS _box DESTINATION freud)
# install(TARGETS _cluster DESTINATION freud) install(TARGETS _density
# DESTINATION freud) install(TARGETS _diffraction DESTINATION freud)
# install(TARGETS _environment DESTINATION freud)
install(TARGETS _cluster DESTINATION freud)
# install(TARGETS _density DESTINATION freud) install(TARGETS _diffraction
# DESTINATION freud) install(TARGETS _environment DESTINATION freud)
install(TARGETS _locality DESTINATION freud)
# install(TARGETS _order DESTINATION freud)
install(TARGETS _parallel DESTINATION freud)
Expand Down
7 changes: 3 additions & 4 deletions freud/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Copyright (c) 2010-2024 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.


# cluster,; density,; diffraction,; environment,; interface,; msd,; order,
from . import box, data, interface, locality, msd, parallel, pmft
# density,; diffraction,; environment,; order,
from . import box, cluster, data, interface, locality, msd, parallel, pmft
from .box import Box
from .locality import AABBQuery, LinkCell, NeighborList
from .parallel import NumThreads, get_num_threads, set_num_threads
Expand All @@ -17,7 +16,7 @@
__all__ = [
"__version__",
"box",
# "cluster",
"cluster",
"data",
# "density",
# "diffraction",
Expand Down
149 changes: 45 additions & 104 deletions freud/cluster.pyx → freud/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,16 @@
of clusters of points in a system.
"""

from cython.operator cimport dereference

from freud.locality cimport _PairCompute
from freud.util cimport _Compute

import numpy as np

import freud._cluster
import freud.locality
import freud.util
from freud.locality import _PairCompute
from freud.util import _Compute

cimport numpy as np

cimport freud._cluster
cimport freud.locality
cimport freud.util

# numpy must be initialized. When using numpy from C or Cython you must
# _always_ do that, or you will have segfaults
np.import_array()

cdef class Cluster(_PairCompute):
class Cluster(_PairCompute):
"""Finds clusters using a network of neighbors.
Given a set of points and their neighbors, :class:`freud.cluster.Cluster`
Expand All @@ -50,16 +39,8 @@
:code:`cluster_keys` contains the point ids present in each cluster.
"""

cdef freud._cluster.Cluster * thisptr

def __cinit__(self):
self.thisptr = new freud._cluster.Cluster()

def __init__(self):
pass

def __dealloc__(self):
del self.thisptr
self._cpp_obj = freud._cluster.Cluster()

def compute(self, system, keys=None, neighbors=None):
r"""Compute the clusters for the given set of points.
Expand All @@ -77,52 +58,36 @@ def compute(self, system, keys=None, neighbors=None):
<https://freud.readthedocs.io/en/stable/topics/querying.html>`_
(Default value: None).
"""
cdef:
freud.locality.NeighborQuery nq
freud.locality.NeighborList nlist
freud.locality._QueryArgs qargs
const float[:, ::1] l_query_points
unsigned int num_query_points

nq, nlist, qargs, l_query_points, num_query_points = \
self._preprocess_arguments(system, neighbors=neighbors)

cdef unsigned int* l_keys_ptr = NULL
cdef unsigned int[::1] l_keys
nq, nlist, qargs, query_points, num_query_points = self._preprocess_arguments(
system, neighbors=neighbors
)
if keys is not None:
l_keys = freud.util._convert_array(
keys, shape=(num_query_points, ), dtype=np.uint32)
l_keys_ptr = &l_keys[0]

self.thisptr.compute(
nq.get_ptr(),
nlist.get_ptr(),
dereference(qargs.thisptr),
l_keys_ptr)
keys = freud.util._convert_array(
keys, shape=(num_query_points,), dtype=np.uint32
)

self._cpp_obj.compute(nq._cpp_obj, nlist._cpp_obj, qargs._cpp_obj, keys)
return self

@_Compute._computed_property
def num_clusters(self):
"""int: The number of clusters."""
return self.thisptr.getNumClusters()
return self._cpp_obj.getNumClusters()

@_Compute._computed_property
def cluster_idx(self):
"""(:math:`N_{points}`) :class:`numpy.ndarray`: The cluster index for
each point."""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterIdx(),
freud.util.arr_type_t.UNSIGNED_INT)
return self._cpp_obj.getClusterIdx().toNumpyArray()

@_Compute._computed_property
def cluster_keys(self):
"""list(list): A list of lists of the keys contained in each
cluster."""
cluster_keys = self.thisptr.getClusterKeys()
return cluster_keys
return self._cpp_obj.getClusterKeys()

def __repr__(self):
return "freud.cluster.{cls}()".format(cls=type(self).__name__)
return f"freud.cluster.{type(self).__name__}()"

def plot(self, ax=None):
"""Plot cluster distribution.
Expand All @@ -136,49 +101,42 @@ def plot(self, ax=None):
(:class:`matplotlib.axes.Axes`): Axis with the plot.
"""
import freud.plot

try:
values, counts = np.unique(self.cluster_idx, return_counts=True)
except ValueError:
return None
return freud.plot.clusters_plot(
values, counts, num_clusters_to_plot=10, ax=ax)
return freud.plot.clusters_plot(values, counts, num_clusters_to_plot=10, ax=ax)

def _repr_png_(self):
try:
import freud.plot

return freud.plot._ax_to_bytes(self.plot())
except (AttributeError, ImportError):
return None


cdef class ClusterProperties(_Compute):
class ClusterProperties(_Compute):
r"""Routines for computing properties of point clusters.
Given a set of points and cluster ids (from :class:`~.Cluster` or another
source), this class determines the following properties for each cluster:
- Geometric center
- Center of mass
- Gyration tensor
- Moment of inertia tensor
- Size (number of points)
- Mass (total mass of each cluster)
- Geometric center
- Center of mass
- Gyration tensor
- Moment of inertia tensor
- Size (number of points)
- Mass (total mass of each cluster)
Note:
The center of mass and geometric center for each cluster are computed
using the minimum image convention
"""

cdef freud._cluster.ClusterProperties * thisptr

def __cinit__(self):
self.thisptr = new freud._cluster.ClusterProperties()

def __init__(self):
pass

def __dealloc__(self):
del self.thisptr
self._cpp_obj = freud._cluster.ClusterProperties()

def compute(self, system, cluster_idx, masses=None):
r"""Compute properties of the point clusters.
Expand Down Expand Up @@ -210,21 +168,14 @@ def compute(self, system, cluster_idx, masses=None):
Masses corresponding to each point, defaulting to 1 if not
provided or :code:`None` (Default value = :code:`None`).
"""
cdef freud.locality.NeighborQuery nq = \
freud.locality.NeighborQuery.from_system(system)
cluster_idx = freud.util._convert_array(
cluster_idx, shape=(nq.points.shape[0], ), dtype=np.uint32)
cdef const unsigned int[::1] l_cluster_idx = cluster_idx
nq = freud.locality.NeighborQuery.from_system(system)

cdef float* l_masses_ptr = NULL
cdef float[::1] l_masses
cluster_idx = freud.util._convert_array(
cluster_idx, shape=(nq.points.shape[0],), dtype=np.uint32
)
if masses is not None:
l_masses = freud.util._convert_array(masses, shape=(len(masses), ))
l_masses_ptr = &l_masses[0]

self.thisptr.compute(nq.get_ptr(),
<unsigned int*> &l_cluster_idx[0],
l_masses_ptr)
masses = freud.util._convert_array(masses, shape=(len(masses),))
self._cpp_obj.compute(nq._cpp_obj, cluster_idx, masses)
return self

@_Compute._computed_property
Expand All @@ -240,9 +191,7 @@ def centers(self):
:math:`N_k` is the number of particles in the :math:`k` th cluster and
:math:`\mathbf{r_i}` are their positions.
"""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterCenters(),
freud.util.arr_type_t.FLOAT, 3)
return self._cpp_obj.getClusterCenters().toNumpyArray()

@_Compute._computed_property
def centers_of_mass(self):
Expand All @@ -258,9 +207,7 @@ def centers_of_mass(self):
cluster, :math:`\mathbf{r_i}` are their positions and :math:`m_i` are
their masses.
"""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterCentersOfMass(),
freud.util.arr_type_t.FLOAT, 3)
return self._cpp_obj.getClusterCentersOfMass().toNumpyArray()

@_Compute._computed_property
def gyrations(self):
Expand All @@ -278,9 +225,7 @@ def gyrations(self):
where :math:`\mathbf{S}_k` is the gyration tensor of the :math:`k` th
cluster.
"""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterGyrations(),
freud.util.arr_type_t.FLOAT)
return self._cpp_obj.getClusterGyrations().toNumpyArray()

@_Compute._computed_property
def inertia_tensors(self):
Expand All @@ -298,25 +243,19 @@ def inertia_tensors(self):
where :math:`\mathbf{I}_k` is the inertia tensor of the :math:`k` th
cluster.
"""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterMomentsOfInertia(),
freud.util.arr_type_t.FLOAT)
return self._cpp_obj.getClusterMomentsOfInertia().toNumpyArray()

@_Compute._computed_property
def sizes(self):
"""(:math:`N_{clusters}`) :class:`numpy.ndarray`: The cluster sizes."""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterSizes(),
freud.util.arr_type_t.UNSIGNED_INT)
return self._cpp_obj.getClusterSizes().toNumpyArray()

@_Compute._computed_property
def cluster_masses(self):
"""(:math:`N_{clusters}`) :class:`numpy.ndarray`: The total mass of
particles in each cluster.
"""
return freud.util.make_managed_numpy_array(
&self.thisptr.getClusterMasses(),
freud.util.arr_type_t.FLOAT)
return self._cpp_obj.getClusterMasses().toNumpyArray()

@_Compute._computed_property
def radii_of_gyration(self):
Expand All @@ -331,8 +270,10 @@ def radii_of_gyration(self):
the center of mass.
"""
return np.sqrt(np.trace(self.inertia_tensors, axis1=-2, axis2=-1)
/(2*self.cluster_masses))
return np.sqrt(
np.trace(self.inertia_tensors, axis1=-2, axis2=-1)
/ (2 * self.cluster_masses)
)

def __repr__(self):
return "freud.cluster.{cls}()".format(cls=type(self).__name__)
return f"freud.cluster.{type(self).__name__}()"
9 changes: 5 additions & 4 deletions freud/cluster/Cluster.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
//! Finds clusters using a network of neighbors.
namespace freud { namespace cluster {

void Cluster::compute(const freud::locality::NeighborQuery* nq, const freud::locality::NeighborList* nlist,
freud::locality::QueryArgs qargs, const unsigned int* keys)
void Cluster::compute(const std::shared_ptr<locality::NeighborQuery> nq,
const std::shared_ptr<locality::NeighborList> nlist, const locality::QueryArgs& qargs,
const unsigned int* keys)
{
const unsigned int num_points = nq->getNPoints();
m_cluster_idx.prepare(num_points);
m_cluster_idx = std::make_shared<util::ManagedArray<unsigned int>>(num_points);
DisjointSets dj(num_points);

freud::locality::loopOverNeighbors(
Expand Down Expand Up @@ -80,7 +81,7 @@ void Cluster::compute(const freud::locality::NeighborQuery* nq, const freud::loc
{
size_t s = dj.find(i);
size_t cluster_idx = cluster_reindex[cluster_label[s]];
m_cluster_idx[i] = cluster_idx;
(*m_cluster_idx)[i] = cluster_idx;
unsigned int key = i;
if (keys != nullptr)
{
Expand Down
Loading

0 comments on commit b370e8c

Please sign in to comment.