# Methods to calculate neighbors of a particle
pyscal3 includes different methods to explore the local environment of a particle that rely on the calculation of nearest neighbors. Various approaches to compute the neighbors of particles are discussed here.
## Fixed cutoff method
The most common method to calculate the nearest neighbors of an atom is using a cutoff radius. Commonly, a cutoff is selected as the first minimum of the radial distribution functions. Once a cutoff is selected, the neighbors of an atom are those that fall within this selected radius. The following code snippet will use the cutoff method to calculate neighbors. In this example, `conf.dump` is assumed to
be the input configuration of the system. A cutoff radius of 3 is assumed for calculation of neighbors.
``` python
from pyscal3 import System
sys = pc.System()
sys.read.file('conf.dump')
sys.find.neighbors(method='cutoff', cutoff=3)
```
## Adaptive cutoff methods
A fixed cutoff radius can introduce limitations to explore the local environment of the particle in some cases:
- At finite temperatures, when thermal fluctuations take place, the selection of a fixed cutoff may result in an inaccurate description of the local environment.
- If there is more than one structure present in the system, for example, bcc and fcc, the selection of cutoff such that it includes the first shell of both structures can be difficult.
In order to achieve a more accurate description of the local environment, various adaptive approaches have been proposed. Two of the methods implemented in the module are discussed below.
### Solid angle based nearest neighbor algorithm (SANN)
SANN algorithm [1] determines the cutoff radius by counting the solid angles around an atom and equating it to $4\pi$. The algorithm solves the following equation iteratively.
$$
R_i^{(m)} = \frac{\sum_{j=1}^m r_{i,j}}{m-2} < r_{i, m+1}
$$
where $i$ is the host atom, $j$ are its neighbors with $r_{ij}$ is the distance between atoms $i$ and $j$. $R_i$ is the cutoff radius for each particle $i$ which is found by increasing the neighbor of neighbors $m$ iteratively. For a description of the algorithm and more details, please check the reference [1]. SANN algorithm can be used to find the neighbors by,
``` python
from pyscal3 import System
sys = pc.System()
sys.read.file('conf.dump')
sys.find.neighbors(method='cutoff', cutoff='sann')
```
Since SANN algorithm involves sorting, a sufficiently large cutoff is used in the beginning to reduce the number entries to be sorted. This parameter is calculated by,
$$
r_{initial} = \mathrm{threshold} \times \bigg(\frac{\mathrm{Simulation~box~volume}}{\mathrm{Number~of~particles}}\bigg)^{\frac{1}{3}}
$$
a tunable `threshold` parameter can be set through function arguments.
### Adaptive cutoff method
An adaptive cutoff specific for each atom can also be found using an algorithm similar to adaptive common neighbor analysis [2]. This adaptive cutoff is calculated by first making a list of all neighbor
distances for each atom similar to SANN method. Once this list is available, then the cutoff is calculated from,
$$
r_{cut}(i) = \mathrm{padding}\times \bigg(\frac{1}{\mathrm{nlimit}} \sum_{j=1}^{\mathrm{nlimit}} r_{ij} \bigg)
$$
This method can be chosen by,
``` python
from pyscal3 import System
sys = pc.System()
sys.read.file('conf.dump')
sys.find.neighbors(method='cutoff', cutoff='adaptive')
```
The `padding` and `nlimit` parameters in the above equation can be tuned using the respective keywords.
Either of the adaptive method can be used to find neighbors, which can then be used to calculate Steinhardt\'s parameters or their averaged version.
## Voronoi tessellation
[Voronoi tessellation](https://en.wikipedia.org/wiki/Voronoi_diagram) provides a completely parameter free geometric approach for calculation of neighbors. [Voro++](http://math.lbl.gov/voro++/) code is used for Voronoi tessellation. Neighbors can be calculated using this method by,
``` python
from pyscal3 import System
sys = pc.System()
sys.read.file('conf.dump')
sys.find.neighbors(method='voronoi')
```
Finding neighbors using Voronoi tessellation also calculates a weight for each neighbor. The weight of a neighbor $j$ towards a host atom $i$ is given by,
$$
W_{ij} = \frac{A_{ij}}{\sum_{j=1}^N A_{ij}}
$$
where $A_{ij}$ is the area of Voronoi facet between atom $i$ and $j$, $N$ are all the neighbors identified through Voronoi tessellation. This weight can be used later for calculation of weighted Steinhardt's
parameters. Optionally, it is possible to choose the exponent for this weight. Option `voroexp` is used to set this option. For example if `voroexp=2`, the weight would be calculated as,
$$
W_{ij} = \frac{A_{ij}^2}{\sum_{j=1}^N A_{ij}^2}
$$
## References
1. van Meel, J. A., Filion, L., Valeriani, C. & Frenkel, D. A parameter-free, solid-angle based, nearest- neighbor algorithm. J Chem Phys 234107, (2012).
2. Stukowski, A. Structure identification methods for atomistic simulations of crystalline materials. Modelling and Simulation in Materials Science and Engineering 20, (2012).