diff --git a/freud/cluster.py b/freud/cluster.py index 8a5962b76..5555ec738 100644 --- a/freud/cluster.py +++ b/freud/cluster.py @@ -5,6 +5,7 @@ The :class:`freud.cluster` module aids in finding and computing the properties of clusters of points in a system. """ +import numpy as np from freud.locality import _PairCompute from freud.util import _Compute @@ -57,7 +58,11 @@ def compute(self, system, keys=None, neighbors=None): nq, nlist, qargs, query_points, num_query_points = self._preprocess_arguments( system, neighbors=neighbors ) - print(type(nq),type(nlist),type(qargs._cpp_obj),type(keys)) + if keys is not None: + keys = freud.util._convert_array( + keys, shape = (num_query_points, ), dtype = np.uint32 + ) + self._cpp_obj.compute( nq._cpp_obj, nlist._cpp_obj, @@ -68,19 +73,19 @@ def compute(self, system, keys=None, neighbors=None): @_Compute._computed_property def num_clusters(self): """in0t: The number of clusters.""" - return self.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 self.getClusterIdx().toNumpyArray() + 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.getClusterKeys() + cluster_keys = self._cpp_obj.getClusterKeys() return cluster_keys def __repr__(self): @@ -113,187 +118,167 @@ def _repr_png_(self): return None -# 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) - -# Note: -# The center of mass and geometric center for each cluster are computed -# using the minimum image convention -# """ - -# def freud._cluster.ClusterProperties * _cpp_obj - -# def __init__(self): -# self._cpp_obj = freud._cluster.ClusterProperties() - -# def __init__(self): -# pass - - -# def compute(self, system, cluster_idx, masses=None): -# r"""Compute properties of the point clusters. -# Loops over all points in the given array and determines the geometric -# center, center of mass, moment of inertia, gyration tensors, and -# radius of gyration :cite:`Vymetal2011` of each cluster. After calling -# this method, properties can be accessed via their attributes. - -# Example:: - -# >>> import freud -# >>> # Compute clusters using box, positions, and nlist data -# >>> box, points = freud.data.make_random_system(10, 100) -# >>> cl = freud.cluster.Cluster() -# >>> cl.compute((box, points), neighbors={'r_max': 1.0}) -# freud.cluster.Cluster() -# >>> # Compute cluster properties based on identified clusters -# >>> cl_props = freud.cluster.ClusterProperties() -# >>> cl_props.compute((box, points), cl.cluster_idx) -# freud.cluster.ClusterProperties() - -# Args: -# system: -# Any object that is a valid argument to -# :class:`freud.locality.NeighborQuery.from_system`. -# cluster_idx ((:math:`N_{points}`,) :class:`np.ndarray`): -# Cluster indexes for each point. -# masses ((:math:`N_{points}`, ) :class:`numpy.ndarray`): -# Masses corresponding to each point, defaulting to 1 if not -# provided or :code:`None` (Default value = :code:`None`). -# """ -# def 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) -# def const unsigned int[::1] l_cluster_idx = cluster_idx - -# def float* l_masses_ptr = NULL -# def float[::1] l_masses -# if masses is not None: -# l_masses = freud.util._convert_array(masses, shape=(len(masses), )) -# l_masses_ptr = &l_masses[0] - -# self._cpp_obj.compute(nq.get_ptr(), -# &l_cluster_idx[0], -# l_masses_ptr) -# return self - -# @_Compute._computed_property -# def centers(self): -# r"""(:math:`N_{clusters}`, 3) :class:`numpy.ndarray`: The geometric centers -# of the clusters, independent of mass and defined as - -# .. math:: - -# \mathbf{C}_g^k = \frac{1}{N_k} \sum_{i=0}^{N_k} \mathbf{r_i} - -# where :math:`\mathbf{C}_g^k` is the center of the :math:`k` th cluster, -# :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._cpp_obj.getClusterCenters(), -# freud.util.arr_type_t.FLOAT, 3) - -# @_Compute._computed_property -# def centers_of_mass(self): -# r"""(:math:`N_{clusters}`, 3) :class:`numpy.ndarray`: The centers of mass -# of the clusters: - -# .. math:: - -# \mathbf{C}_m^k = \frac{1}{M_k} \sum_{i=0}^{N_k} m_i \mathbf{r_i} - -# where :math:`\mathbf{C}_m^k` is the center of mass of the :math:`k` th -# cluster, :math:`M_k` is the total mass of particles in the :math:`k` th -# cluster, :math:`\mathbf{r_i}` are their positions and :math:`m_i` are -# their masses. -# """ -# return freud.util.make_managed_numpy_array( -# &self._cpp_obj.getClusterCentersOfMass(), -# freud.util.arr_type_t.FLOAT, 3) - -# @_Compute._computed_property -# def gyrations(self): -# r"""(:math:`N_{clusters}`, 3, 3) :class:`numpy.ndarray`: The gyration -# tensors of the clusters. Normalized by particle number: - -# .. math:: - -# \mathbf{S}_k = \frac{1}{N_k} \begin{bmatrix} -# \sum_i x_i^2 & \sum_i x_i y_i & \sum_i x_i z_i \\ -# \sum_i y_i x_i & \sum_i y_i^2 & \sum_i y_i z_i \\ -# \sum_i z_i x_i & \sum_i z_i y_i & \sum_i z_i^2 \\ -# \end{bmatrix} - -# where :math:`\mathbf{S}_k` is the gyration tensor of the :math:`k` th -# cluster. -# """ -# return freud.util.make_managed_numpy_array( -# &self._cpp_obj.getClusterGyrations(), -# freud.util.arr_type_t.FLOAT) - -# @_Compute._computed_property -# def inertia_tensors(self): -# r"""(:math:`N_{clusters}`, 3, 3) :class:`numpy.ndarray`: The inertia -# tensors of the clusters. Neither normalized by mass nor number: - -# .. math:: - -# \mathbf{I}_k = \begin{bmatrix} -# \sum_i m_i(y_i^2+z_i^2)& \sum_i -m_i(x_iy_i)& \sum_i -m_i(x_iz_i)\\ -# \sum_i -m_i(y_ix_i)& \sum_i m_i(x_i^2+z_i^2)& \sum_i -m_i(y_iz_i)\\ -# \sum_i -m_i(z_ix_i)& \sum_i -m_i(z_iy_i)& \sum_i m_i(y_i^2+x_i^2)\\ -# \end{bmatrix} - -# where :math:`\mathbf{I}_k` is the inertia tensor of the :math:`k` th -# cluster. -# """ -# return freud.util.make_managed_numpy_array( -# &self._cpp_obj.getClusterMomentsOfInertia(), -# freud.util.arr_type_t.FLOAT) - -# @_Compute._computed_property -# def sizes(self): -# """(:math:`N_{clusters}`) :class:`numpy.ndarray`: The cluster sizes.""" -# return freud.util.make_managed_numpy_array( -# &self._cpp_obj.getClusterSizes(), -# freud.util.arr_type_t.UNSIGNED_INT) - -# @_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._cpp_obj.getClusterMasses(), -# freud.util.arr_type_t.FLOAT) - -# @_Compute._computed_property -# def radii_of_gyration(self): -# r"""(:math:`N_{clusters}`,) :class:`numpy.ndarray`: The radius of -# gyration of each cluster. Defined by IUPAP as - -# .. math:: - -# R_g^k = \left(\frac{1}{M} \sum_{i=0}^{N_k} m_i s_i^2 \right)^{1/2} - -# where :math:`s_i` is the distance of particle :math:`i` from -# the center of mass. - -# """ -# 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__) +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) + + Note: + The center of mass and geometric center for each cluster are computed + using the minimum image convention + """ + + def __init__(self): + self._cpp_obj = freud._cluster.ClusterProperties() + + def compute(self, system, cluster_idx, masses=None): + r"""Compute properties of the point clusters. + Loops over all points in the given array and determines the geometric + center, center of mass, moment of inertia, gyration tensors, and + radius of gyration :cite:`Vymetal2011` of each cluster. After calling + this method, properties can be accessed via their attributes. + + Example:: + + >>> import freud + >>> # Compute clusters using box, positions, and nlist data + >>> box, points = freud.data.make_random_system(10, 100) + >>> cl = freud.cluster.Cluster() + >>> cl.compute((box, points), neighbors={'r_max': 1.0}) + freud.cluster.Cluster() + >>> # Compute cluster properties based on identified clusters + >>> cl_props = freud.cluster.ClusterProperties() + >>> cl_props.compute((box, points), cl.cluster_idx) + freud.cluster.ClusterProperties() + + Args: + system: + Any object that is a valid argument to + :class:`freud.locality.NeighborQuery.from_system`. + cluster_idx ((:math:`N_{points}`,) :class:`np.ndarray`): + Cluster indexes for each point. + masses ((:math:`N_{points}`, ) :class:`numpy.ndarray`): + Masses corresponding to each point, defaulting to 1 if not + provided or :code:`None` (Default value = :code:`None`). + """ + nq = freud.locality.NeighborQuery.from_system(system) + + cluster_idx = freud.util._convert_array( + cluster_idx, shape = (nq.points.shape[0], ), dtype = np.uint32 + ) + if masses is not None: + masses = freud.util._convert_array(masses, shape = (len(masses), ) + ) + print(type(self._cpp_obj), type(nq._cpp_obj), type(cluster_idx), type(masses)) + print(cluster_idx,cluster_idx.shape, cluster_idx.dtype) + self._cpp_obj.compute(nq._cpp_obj, + cluster_idx, + masses) + return self + + @_Compute._computed_property + def centers(self): + r"""(:math:`N_{clusters}`, 3) :class:`numpy.ndarray`: The geometric centers + of the clusters, independent of mass and defined as + + .. math:: + + \mathbf{C}_g^k = \frac{1}{N_k} \sum_{i=0}^{N_k} \mathbf{r_i} + + where :math:`\mathbf{C}_g^k` is the center of the :math:`k` th cluster, + :math:`N_k` is the number of particles in the :math:`k` th cluster and + :math:`\mathbf{r_i}` are their positions. + """ + return self._cpp_obj.getClusterCenters().toNumpyArray() + + @_Compute._computed_property + def centers_of_mass(self): + r"""(:math:`N_{clusters}`, 3) :class:`numpy.ndarray`: The centers of mass + of the clusters: + + .. math:: + + \mathbf{C}_m^k = \frac{1}{M_k} \sum_{i=0}^{N_k} m_i \mathbf{r_i} + + where :math:`\mathbf{C}_m^k` is the center of mass of the :math:`k` th + cluster, :math:`M_k` is the total mass of particles in the :math:`k` th + cluster, :math:`\mathbf{r_i}` are their positions and :math:`m_i` are + their masses. + """ + return self._cpp_obj.getClusterCentersOfMass().toNumpyArray() + + @_Compute._computed_property + def gyrations(self): + r"""(:math:`N_{clusters}`, 3, 3) :class:`numpy.ndarray`: The gyration + tensors of the clusters. Normalized by particle number: + + .. math:: + + \mathbf{S}_k = \frac{1}{N_k} \begin{bmatrix} + \sum_i x_i^2 & \sum_i x_i y_i & \sum_i x_i z_i \\ + \sum_i y_i x_i & \sum_i y_i^2 & \sum_i y_i z_i \\ + \sum_i z_i x_i & \sum_i z_i y_i & \sum_i z_i^2 \\ + \end{bmatrix} + + where :math:`\mathbf{S}_k` is the gyration tensor of the :math:`k` th + cluster. + """ + return self._cpp_obj.getClusterGyrations().toNumpyArray() + + @_Compute._computed_property + def inertia_tensors(self): + r"""(:math:`N_{clusters}`, 3, 3) :class:`numpy.ndarray`: The inertia + tensors of the clusters. Neither normalized by mass nor number: + + .. math:: + + \mathbf{I}_k = \begin{bmatrix} + \sum_i m_i(y_i^2+z_i^2)& \sum_i -m_i(x_iy_i)& \sum_i -m_i(x_iz_i)\\ + \sum_i -m_i(y_ix_i)& \sum_i m_i(x_i^2+z_i^2)& \sum_i -m_i(y_iz_i)\\ + \sum_i -m_i(z_ix_i)& \sum_i -m_i(z_iy_i)& \sum_i m_i(y_i^2+x_i^2)\\ + \end{bmatrix} + + where :math:`\mathbf{I}_k` is the inertia tensor of the :math:`k` th + cluster. + """ + return self._cpp_obj.getClusterMomentsOfInertia().toNumpyArray() + + @_Compute._computed_property + def sizes(self): + """(:math:`N_{clusters}`) :class:`numpy.ndarray`: The cluster sizes.""" + 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 self._cpp_obj.getClusterMasses().toNumpyArray() + + @_Compute._computed_property + def radii_of_gyration(self): + r"""(:math:`N_{clusters}`,) :class:`numpy.ndarray`: The radius of + gyration of each cluster. Defined by IUPAP as + + .. math:: + + R_g^k = \left(\frac{1}{M} \sum_{i=0}^{N_k} m_i s_i^2 \right)^{1/2} + + where :math:`s_i` is the distance of particle :math:`i` from + the center of mass. + + """ + 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__) diff --git a/freud/cluster/export-Cluster.cc b/freud/cluster/export-Cluster.cc index d8baace0e..2e4d49652 100644 --- a/freud/cluster/export-Cluster.cc +++ b/freud/cluster/export-Cluster.cc @@ -19,9 +19,9 @@ using nb_array = nanobind::ndarray self, std::shared_ptr nq, std::shared_ptr nlist, - const locality::QueryArgs& qargs, nb_array> keys) + const locality::QueryArgs& qargs, nb_array> keys) { - unsigned int* keys_data = reinterpret_cast(keys.data()); + const auto* keys_data = reinterpret_cast(keys.data()); self->compute(nq, nlist, qargs, keys_data); } }; //end namespace wrap @@ -31,7 +31,7 @@ namespace detail { { nanobind::class_(module, "Cluster") .def(nb::init<>()) - .def("compute", &wrap::compute, nanobind::arg("nq"), nanobind::arg("nlist"), nanobind::arg("qargs"), nanobind::arg("keys").none()) + .def("compute", &wrap::compute, nanobind::arg("nq"), nanobind::arg("nlist").none(), nanobind::arg("qargs"), nanobind::arg("keys").none()) .def("getNumClusters", &Cluster::getNumClusters) .def("getClusterIdx", &Cluster::getClusterIdx) .def("getClusterKeys", &Cluster::getClusterKeys); @@ -39,5 +39,4 @@ namespace detail { }; //end namespace detail -}; }; -//m.def("func", &func, "arg"_a.none()); \ No newline at end of file +}; }; \ No newline at end of file diff --git a/freud/cluster/export-ClusterProperties.cc b/freud/cluster/export-ClusterProperties.cc index 61203c4b0..65b42c656 100644 --- a/freud/cluster/export-ClusterProperties.cc +++ b/freud/cluster/export-ClusterProperties.cc @@ -18,11 +18,11 @@ template using nb_array = nanobind::ndarray; namespace wrap { - void compute(std::shared_ptr & self, std::shared_ptr nq, nb_array> cluster_idx, - nb_array> masses) + void compute(std::shared_ptr self, std::shared_ptr nq, nb_array> cluster_idx, + nb_array> masses) { - float* masses_data = reinterpret_cast(masses.data()); - unsigned int* cluster_idx_data = reinterpret_cast(cluster_idx.data()); + const auto* masses_data = reinterpret_cast(masses.data()); + const auto* cluster_idx_data = reinterpret_cast(cluster_idx.data()); self->compute(nq, cluster_idx_data, masses_data); } }; //end namespace wrap @@ -43,5 +43,4 @@ namespace detail { }; //end namespace detail -}; }; -//m.def("func", &func, "arg"_a.none()); \ No newline at end of file +}; }; \ No newline at end of file