Wolfram Research

Function Repository Resource:

RadialDistributionFunctionList (1.0.0) current version: 2.0.0 »

Source Notebook

Compute the relative probability of finding a particle at a given distance from another particle

Contributed by: Nicholas E. Brunk, Wolfram|Alpha Math Team

ResourceFunction["RadialDistributionFunctionList"][{p1,p2,},L]

returns the radial distribution function resulting from each pair of points pi,pj within a periodic box of uniform dimensions L.

ResourceFunction["RadialDistributionFunctionList"][{p1,p2,},L,w]

returns the radial distribution function using separation bin width w.

Details and Options

The radial distribution function is also known as the pair correlation function.
The default value for the bin width is (w = 0.005), a common choice in reduced units quantified relative to the particle diameter.
ResourceFunction["RadialDistributionFunctionList"] will work in up to three dimensions, applying periodicity uniformly in each.
ResourceFunction["RadialDistributionFunctionList"] normalizes the y axis to generate a density relative to that of an ideal gas at the same density (determined by box size L and the number of coordinates specified).
ResourceFunction["RadialDistributionFunctionList"] utilizes Compile for performance enhancement, and takes the same options.

Examples

Basic Examples (2) 

Compute the radial distribution function from two points:

In[1]:=
ResourceFunction["RadialDistributionFunctionList", ResourceVersion->"1.0.0"][{{0, 0, 0}, {0.5, 0, 0}}, 1.] // Short
Out[1]=

Compute the radial distribution function of a set of coordinates enclosed in a periodic 1D "box" of size (L = 4):

In[2]:=
coords = {{-1}, {0}, {1}};
rdfData = ResourceFunction["RadialDistributionFunctionList"][coords, 4];

Display the coordinates in number line format along the axis for which the positions differ:

In[3]:=
NumberLinePlot[First /@ coords]
Out[3]=

The radial distribution function has nonzero peaks at positions near those of the exact distances apart:

In[4]:=
Cases[rdfData, elem_ /; elem[[2]] != 0]
Out[4]=

Plot the radial distribution function characteristic of this system:

In[5]:=
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[5]=

Options (1) 

RadialDistributionFunctionList takes the same options as Compile, and benefits from automated compilation to C:

In[6]:=
ResourceFunction["RadialDistributionFunctionList"][
   Table[RandomReal[{0, 1.}, 3], {10000}], 1., 0.005] // AbsoluteTiming // First
Out[6]=
In[7]:=
ResourceFunction["RadialDistributionFunctionList"][
   Table[RandomReal[{0, 1.}, 3], {10000}], 1., 0.005, CompilationTarget -> "C"] // AbsoluteTiming // First
Out[7]=

Applications (2) 

Compute the radial distribution function characteristic of a cube with unit length, enclosed in a sufficiently larger periodic box:

In[8]:=
boxLength = 5.0;
coords = N@PolyhedronData["Cube", "Vertices"];
rdfData = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength, 0.005];
Graphics3D[{{Opacity[0.25], Cube[boxLength]}, {Sphere[#, 0.5] & /@ coords}}, Sequence[
 Boxed -> False, Axes -> True]]
Out[9]=

Plot the resulting radial distribution function, where you can see nearest neighbor, polygonal face hypotenuse and internal hypotenuse distances:

In[10]:=
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[10]=

Properties and Relations (3) 

By definition, the radial distribution function of an ideal gas converges to 1 at all distances when provided sufficient data (here, 1000 time steps on 100 positions of 100 particles):

In[11]:=
bulkCoordsData = Table[RandomReal[{0, 1.}, 3], {1000}, {100}];

Compute both the radial distribution function of just the first time step, and then that of the ensemble average over all time steps:

In[12]:=
singleTimestepRDF = ResourceFunction["RadialDistributionFunctionList"][
   First@bulkCoordsData, 1.0, 0.005];
ensAvgRDF = Mean[Map[ResourceFunction["RadialDistributionFunctionList"][#, 1.0, 0.005] &, bulkCoordsData]];

As expected, the radial distribution function converges to nearly 1 as sufficient data is introduced:

In[13]:=
ListLinePlot[{singleTimestepRDF, ensAvgRDF}, Sequence[
 PlotRange -> All, PlotLegends -> {"One Time Step", "Ensemble Average"}, Frame -> True,
   FrameLabel -> Map[
   Style[#, 16]& , {"Distance", "Relative Density"}]]]
Out[13]=

Normalization by the expected number of particles in that vicinity works in 2D cases as well:

In[14]:=
bulkCoordsData = Table[RandomReal[{0, 1.}, 2], {1000}, {100}];
singleTimestepRDF = ResourceFunction["RadialDistributionFunctionList"][
   First@bulkCoordsData, 1.0, 0.005];
ensAvgRDF = Mean[Map[ResourceFunction["RadialDistributionFunctionList"][#, 1.0, 0.005] &, bulkCoordsData]];
In[15]:=
ListLinePlot[{singleTimestepRDF, ensAvgRDF}, Sequence[
 PlotRange -> All, PlotLegends -> {"One Time Step", "Ensemble Average"}, Frame -> True,
   FrameLabel -> Map[
   Style[#, 16]& , {"Distance", "Relative Density"}]]]
Out[15]=

The same is true for 1D cases:

In[16]:=
bulkCoordsData = Table[{RandomReal[{0, 1.}]}, {1000}, {100}];
singleTimestepRDF = ResourceFunction["RadialDistributionFunctionList"][
   First@bulkCoordsData, 1.0, 0.005];
ensAvgRDF = Mean[Map[ResourceFunction["RadialDistributionFunctionList"][#, 1.0, 0.005] &, bulkCoordsData]];
In[17]:=
ListLinePlot[{singleTimestepRDF, ensAvgRDF}, Sequence[
 PlotRange -> All, PlotLegends -> {"One Time Step", "Ensemble Average"}, Frame -> True,
   FrameLabel -> Map[
   Style[#, 16]& , {"Distance", "Relative Density"}]]]
Out[17]=

Possible Issues (4) 

The radial distribution function is accurate out to and is thus truncated at this point, beyond which it would artificially decay:

In[18]:=
boxLength = 5;
coords = {{0, 0, 0}, {1, 0, 0}};
rdfData = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength];
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[19]=

If the box is sufficiently larger than the domain of the particle coordinates, the calculation is as expected (using real particle positions):

In[20]:=
boxLength = 5;
coords = N@PolyhedronData["Cube", "Vertices"];
rdfData = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength, 0.005];
Graphics3D[{{Opacity[0.25], Cube[boxLength]}, {Sphere[#, 0.5] & /@ coords}}, Sequence[
 Boxed -> False, Axes -> True]]
Out[21]=

The peaks are at the expected unit distance for the touching particles, at the polygonal face hypotenuse distance and the interior hypotenuse:

In[22]:=
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, PlotLegends -> {"One Time Step", "Ensemble Average"}, Frame -> True,
   FrameLabel -> Map[
   Style[#, 16]& , {"Distance", "Relative Density"}]]]
Out[22]=

If the box size (L) is too small, the minimum image convention is employed, applying periodicity in each dimension:

In[23]:=
boxLength = 1;
coords = N@PolyhedronData["Cube", "Vertices"];
rdfData = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength, 0.005];
Graphics3D[{{Opacity[0.25], Cube[boxLength]}, {Sphere[#, 0.25] & /@ coords}}, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[24]=

In this case, particles are perfectly overlapping and the separation distance between all pairs is rij=0; however, the convention is to put them in the first nonzero bin:

In[25]:=
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[25]=

Modifying the concentric shell bin width can affect normalization and positional accuracy, similar to the behavior in Riemann sums with too few discretizing rectangles:

In[26]:=
boxLength = 10;
coords = {{-1}, {0}, {1}};
NumberLinePlot@Flatten@coords
Out[27]=

Compare the difference when using two different bin widths:

In[28]:=
rdfDataLowBinWidth = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength, 0.005];
rdfDataHighBinWidth = ResourceFunction["RadialDistributionFunctionList"][coords, boxLength, 0.5];
In[29]:=
ListLinePlot[{rdfDataLowBinWidth, rdfDataHighBinWidth}, PlotRange -> {All, {0, 20}}, PlotLegends -> {"Low Bin Width (w = 0.005)", "High Bin Width (w = 0.5)"}, Frame -> True, FrameLabel -> (Style[#1, 16] &) /@ {"Distance", "Relative Density"}]
Out[29]=

Neat Examples (3) 

Compute the radial distribution function characteristic of an icosahedral distribution of particles and compare it with that of a cube, each with a minimum separation distance of 1:

In[30]:=
boxLength = 5;
icosaCoords = N@PolyhedronData["Icosahedron", "Vertices"];
cubeCoords = N@PolyhedronData["Cube", "Vertices"];
rdfData = ResourceFunction["RadialDistributionFunctionList"][#, boxLength, 0.005] & /@ {icosaCoords, cubeCoords};
Row@(Graphics3D[{{Opacity[0.25], Cube[boxLength]}, {Sphere[#, 0.5] & /@ #}}, Boxed -> False, Axes -> True, ViewPoint -> {1.35, -3, 0.8}, ImageSize -> 350] & /@ {icosaCoords, cubeCoords})
Out[31]=

Plot the resulting radial distribution functions to compare:

In[32]:=
ListLinePlot[rdfData, Sequence[
 PlotRange -> All, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[32]=

Compare the radial distribution function of both a simple cubic and face-centered cubic (FCC) crystalline lattice (neglecting periodicity, each with nearest neighbors touching at unit lengths):

In[33]:=
cubicCoords = N@Flatten[
    Table[{i, j, k}, {i, -2, 2, 1}, {j, -2, 2, 1}, {k, -2, 2, 1}], 2];

\[ScriptCapitalB] = Normal@LatticeData["FaceCenteredCubic", "Basis"];
fccCoords = N@Flatten[
    Table[i \[ScriptCapitalB][[1]] + j \[ScriptCapitalB][[2]] + k \[ScriptCapitalB][[3]], {i, 1, 5}, {j, 1, 5}, {k, 1, 5}], 2];
fccCoords = fccCoords/
   Min@DeleteCases[Flatten[DistanceMatrix@fccCoords], x_Real /; x == 0.];
Row@(Graphics3D[{{Sphere[#, 0.5] & /@ #}}, Boxed -> False, Axes -> True, ImageSize -> 300] & /@ {cubicCoords, fccCoords})
Out[37]=

Compute and plot the radial distribution functions to compare:

In[38]:=
rdfData = ResourceFunction["RadialDistributionFunctionList"][#, 20, 0.005] & /@ {cubicCoords, fccCoords};
ListLinePlot[rdfData, Sequence[
 PlotRange -> {{0, 5}, Automatic}, Frame -> True, FrameLabel -> Map[Style[#, 16]& , {"Distance", "Relative Density"}]]
 ]
Out[39]=

Compute the potential of mean force that has given rise to the interactions, in this example showing that the 3D ideal gas has no potential of interaction:

In[40]:=
bulkCoordsData = Table[RandomReal[{0, 1.}, 3], {1000}, {100}];
ensAvgRDF = Mean[Map[ResourceFunction["RadialDistributionFunctionList"][#, 1.0, 0.005] &, bulkCoordsData]];
In[41]:=
ListLinePlot[ensAvgRDF, PlotRange -> All, Frame -> True, FrameLabel -> (Style[#1, 16] &) /@ {"Distance", "Relative Density"}]
Out[41]=

Compute the corresponding potential of mean force (in units where kBT=1):

In[42]:=
pmf = ensAvgRDF /. {r_, gr_} :> {r, -Log[gr]};
In[43]:=
ListLinePlot[ensAvgRDF /. {r_, gr_} :> {r, -Log[gr]}, PlotRange -> All, Frame -> True, FrameLabel -> (Style[#1, 16] &) /@ {"Distance", "Potential of Mean Force"}]
Out[43]=

It can be seen that despite the noise associated finite data, the potential of mean force is fairly close to zero when averaged over the entire domain:

In[44]:=
Mean[Last /@ pmf]
Out[44]=

Publisher

Wolfram|Alpha Math Team

Version History

  • 2.0.0 – 23 March 2023
  • 1.1.0 – 27 September 2022
  • 1.0.0 – 16 September 2020

Source Metadata

Related Resources

Author Notes

This function's source code could be generalized to periodic boxes of unequal dimensions (LxLyLz).

In[1]:=
FileNameJoin[
  ReplacePart[
   FileNameSplit[FindFile["ResourceFunctionHelpers`"]], -1 -> "RadialDistributionFunctionList.wl"]] // SystemOpen

License Information