Cluster¶
The cluster module uses a set of coordinates and a cutoff distance to determine clustered points. The example below generates random points, and shows that they form clusters. This case is two-dimensional (with \(z=0\) for all particles) for simplicity, but the cluster module works for both 2D and 3D simulations.
In [1]:
import numpy as np
import freud
import matplotlib.pyplot as plt
First, we generate random points to cluster.
In [2]:
points = np.empty(shape=(0, 2))
for center_point in [(0, 0), (2, 2), (-1, 2), (2, 3)]:
points = np.concatenate(
(points, np.random.multivariate_normal(mean=center_point, cov=0.05*np.eye(2), size=(100,))))
fig, ax = plt.subplots(1, 1, figsize=(9, 6))
ax.scatter(points[:,0], points[:,1])
ax.set_title('Raw points before clustering', fontsize=20)
ax.tick_params(axis='both', which='both', labelsize=14, size=8)
plt.show()
# We must add a z=0 component to this array for freud
points = np.hstack((points, np.zeros((points.shape[0], 1))))
Now we create a box and a cluster compute object.
In [3]:
box = freud.box.Box.square(L=10)
cl = freud.cluster.Cluster(box=box, rcut=1.0)
Next, we use the computeClusters
method to determine clusters and
the clusterIdx
property to return their identities. Note that we use
freud
’s method chaining here, where a compute method returns the
compute object.
In [4]:
cluster_idx = cl.computeClusters(points).cluster_idx
print(cluster_idx)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
/Users/vramasub/miniconda3/envs/test36/lib/python3.6/site-packages/ipykernel_launcher.py:1: FreudDeprecationWarning: The computeCellList function is deprecated in favor of the compute method and will be removed in a future version of freud.
"""Entry point for launching an IPython kernel.
In [5]:
fig, ax = plt.subplots(1, 1, figsize=(9, 6))
for cluster_id in range(cl.num_clusters):
cluster_point_indices = np.where(cluster_idx == cluster_id)[0]
ax.scatter(points[cluster_point_indices, 0], points[cluster_point_indices, 1], label="Cluster {}".format(cluster_id))
print("There are {} points in cluster {}.".format(len(cluster_point_indices), cluster_id))
ax.set_title('Clusters identified', fontsize=20)
ax.legend(loc='best', fontsize=14)
ax.tick_params(axis='both', which='both', labelsize=14, size=8)
plt.show()
There are 200 points in cluster 0.
There are 200 points in cluster 1.
We may also compute the clusters’ centers of mass and gyration tensor
using the ClusterProperties
class.
In [6]:
clp = freud.cluster.ClusterProperties(box=box)
clp.computeProperties(points, cl.cluster_idx)
Out[6]:
<freud.cluster.ClusterProperties at 0x11bc71c18>
Plotting these clusters with their centers of mass, with size proportional to the number of clustered points:
In [7]:
plt.figure(figsize=(9, 6))
for cluster_id in range(cl.num_clusters):
cluster_point_indices = np.where(cluster_idx == cluster_id)[0]
plt.scatter(points[cluster_point_indices, 0], points[cluster_point_indices, 1],
label="Cluster {}".format(cluster_id))
for cluster_id in range(cl.num_clusters):
cluster_point_indices = np.where(cluster_idx == cluster_id)[0]
plt.scatter(clp.cluster_COM[cluster_id, 0], clp.cluster_COM[cluster_id, 1],
s=len(cluster_point_indices),
label="Cluster {} COM".format(cluster_id))
plt.title('Center of mass for each cluster', fontsize=20)
plt.legend(loc='best', fontsize=14)
plt.gca().tick_params(axis='both', which='both', labelsize=14, size=8)
plt.show()
The 3x3 gyration tensors \(G\) can also be computed for each cluster. For this two-dimensional case, the \(z\) components of the gyration tensor are zero.
In [8]:
for cluster_id in range(cl.num_clusters):
G = clp.cluster_G[cluster_id]
print("The gyration tensor of cluster {} is:\n{}".format(cluster_id, G))
The gyration tensor of cluster 0 is:
[[ 0.278339 -0.4618815 0. ]
[-0.4618815 1.0055021 0. ]
[ 0. 0. 0. ]]
The gyration tensor of cluster 1 is:
[[0.04810311 0.00591482 0. ]
[0.00591482 0.28178927 0. ]
[0. 0. 0. ]]
This gyration tensor can be used to determine the principal axes of the cluster and radius of gyration along each principal axis. Here, we plot the gyration tensor’s eigenvectors with length corresponding to the square root of the eigenvalues (the singular values).
In [9]:
plt.figure(figsize=(9, 6))
for cluster_id in range(cl.num_clusters):
cluster_point_indices = np.where(cluster_idx == cluster_id)[0]
plt.scatter(points[cluster_point_indices, 0], points[cluster_point_indices, 1], label="Cluster {}".format(cluster_id))
for cluster_id in range(cl.num_clusters):
com = clp.cluster_COM[cluster_id]
G = clp.cluster_G[cluster_id]
evals, evecs = np.linalg.eig(G)
radii = np.sqrt(evals)
for evalue, evec in zip(evals[:2], evecs[:2, :2]):
print("Cluster {} has radius of gyration {:.4f} along the axis of ({:.4f}, {:.4f}).".format(cluster_id, evalue, *evec))
arrows = (radii * evecs)[:2, :2]
for arrow in arrows:
plt.arrow(com[0], com[1], arrow[0], arrow[1], width=0.08)
plt.title('Eigenvectors of the gyration tensor for each cluster', fontsize=20)
plt.legend(loc='best', fontsize=14)
plt.gca().tick_params(axis='both', which='both', labelsize=14, size=8)
Cluster 0 has radius of gyration 0.0541 along the axis of (-0.8996, 0.4367).
Cluster 0 has radius of gyration 1.2297 along the axis of (-0.4367, -0.8996).
Cluster 1 has radius of gyration 0.0480 along the axis of (-0.9997, -0.0253).
Cluster 1 has radius of gyration 0.2819 along the axis of (0.0253, -0.9997).