Pair Computations

Some computations in freud do not depend on bonds at all. For example, freud.density.GaussianDensity creates a “continuous equivalent” of a system of points by placing normal distributions at each point’s location to smear out its position, then summing the value of these distributions at a set of fixed grid points. This calculation can be quite useful because it allows the application of various analysis tools like fast Fourier transforms, which require regular grids. For the purposes of this tutorial, however, the importance of this class is that it is an example of a calculation where neighbors are unimportant: the calculation is performed on a per-point basis only.

The much more common pattern in freud, though, is that calculations involve the local neighborhoods of points. To support efficient, flexible computations of such quantities, various Compute classes essentially expose the same API as the query interface demonstrated in the previous section. These PairCompute classes are designed to mirror the querying functionality of freud as closely as possible.

As an example, let’s consider freud.density.LocalDensity, which calculates the density of points in the local neighborhood of each point. Adapting our code from the previous section, the simplest usage of this class would be as follows:

import numpy as np
import freud

L = 10
num_points = 100

points = np.random.rand(num_points)*L - L/2
box = freud.box.Box.cube(L)

# r_max specifies how far to search around each point for neighbors
r_max = 2

# For systems where the points represent, for instance, particles with a
# specific size, the diameter is used to add fractional volumes for
# neighbors that would be overlapping the sphere of radius r_max around
# each point.
diameter = 0.001

ld = freud.density.LocalDensity(r_max, diameter)
ld.compute(system=(box, points))

# Access the density.
ld.density

Using the same example system we’ve been working with so far, we’ve now calculated an estimate for the number of points in the neighborhood of each point. Since we already told the computation how far to search for neighbors based on r_max, all we had to do was pass a tuple (box, points) to compute indicate where the points were located.

Binary Systems

Imagine that instead of a single set of points, we actually had two different types of points and we were interested in finding the density of one set of points in the vicinity of the other. In that case, we could modify the above calculation as follows:

import numpy as np
import freud
L = 10
num_points = 100
points = np.random.rand(num_points)*L - L/2
query_points = np.random.rand(num_points/10)*L - L/2

r_max = 2
diameter = 0.001

ld = freud.density.LocalDensity(r_max, diameter)
ld.compute(system=(box, points), query_points=query_points)

# Access the density.
ld.density

The choice of names names here is suggestive of exactly what this calculation is now doing. Internally, freud.density.LocalDensity will search for all points that are within the cutoff distance r_max of every query_point (essentially using the query interface we introduced previously) and use that to calculate ld.density. Note that this means that ld.density now contains densities for every query_point, i.e. it is of length 10, not 100. Moreover, recall that one of the features of the querying API is the specification of whether or not to count particles as their own neighbors. PairCompute classes will attempt to make an intelligent determination of this for you; if you do not pass in a second set of query_points, they will assume that you are computing with a single set of points and automatically exclude self-neighbors, but otherwise all neighbors will be included.

So far, we have included all points within a fixed radius; however, one might instead wish to consider the density in some shell, such as the density between 1 and 2 distance units away. To address this need, you could simply adapt the call to compute above as follows:

ld.compute(system=(box, points), query_points=query_points,
           neighbors=dict(r_max=2, r_min=1))

The neighbors argument to PairCompute classes allows users to specify arbitary query arguments, making it possible to easily modify freud calculations on-the-fly. The neighbors argument is actually more general than query arguments you’ve seen so far: if query arguments are not precise enough to specify the exact set of neighbors you want to compute with, you can instead provide a NeighborList directly

ld.compute(system=(box, points), query_points=query_points,
           neighbors=nlist)

This feature allows users essentially arbitrary flexibility to specify the bonds that should be included in any bond-based computation. A common use-case for this is constructing a NeighborList using freud.locality.Voronoi; Voronoi constructions provide a powerful alternative method of defining neighbor relationships that can improve the accuracy and robustness of certain calculations in freud.

You may have noticed in the last example that all the arguments are specified using keyword arguments. As the previous examples have attempted to show, the query_points argument defines a second set of points to be used when performing calculations on binary systems, while the neighbors argument is how users can specify which neighbors to consider in the calculation.

The system argument is what, to this point, we have been specifying as a tuple (box, points). However, we don’t have to use this tuple. Instead, we can pass in any freud.locality.NeighborQuery, the central class in freud’s querying infrastructure. In fact, you’ve already seen examples of freud.locality.NeighborQuery: the freud.locality.AABBQuery object that we originally used to find neighbors. There are also a number of other input types that can be converted via freud.locality.NeighborQuery.from_system(), see also Reading Simulation Data for freud. Since these objects all contain a freud.box.Box and a set of points, they can be directly passed to computations:

aq = freud.locality.AABBQuery(box, points)
ld.compute(system=aq, query_points=query_points, neighbors=nlist)

For more information on why you might want to use freud.locality.NeighborQuery objects instead of the tuples, see Using freud Efficiently. For now, just consider this to be a way in which you can simplify your calls to many freud computes in one script by storing (box, points) into another objects.

You’ve now covered the most important information needed to use freud! To recap, we’ve discussed how freud handles periodic boundary conditions, the structure and usage of Compute classes, and methods for finding and performing calculations with pairs of neighbors. For more detailed information on specific methods in freud, see the Examples page or look at the API documentation for specific modules.