Finding Neighbors#
Neighbor finding is the foundation step for most pyscal descriptors. The find_neighbors function supports several methods:
Method |
Description |
|---|---|
|
Fixed cutoff radius |
|
Adaptive (per-atom) cutoff |
|
SANN method (parameter-free) |
|
Voronoi tessellation |
|
Nearest N neighbors |
After calling find_neighbors, neighbor data is stored on the ASE Atoms object — typically in atoms.info (for ragged/variable-length lists).
import pyscal
from ase.build import bulk
import numpy as np
Fixed Cutoff#
The simplest approach: all atom pairs within a given distance are neighbors.
atoms = bulk("Cu", "fcc", cubic=True).repeat(4)
# Fixed cutoff of 3.0 Angstrom
pyscal.find_neighbors(atoms, method="cutoff", cutoff=3.0)
# Check that neighbor info was stored
print(f"Neighbor keys in atoms.info: {[k for k in atoms.info if 'neighbor' in k.lower()]}")
print(f"Neighbor method: {atoms.info.get('pyscal_neighbor_method', 'N/A')}")
Neighbor keys in atoms.info: ['pyscal_neighbors_found', 'pyscal_neighbor_method']
Neighbor method: cutoff
Adaptive Cutoff#
With cutoff=0, pyscal computes an adaptive cutoff for each atom based on the local structure.
atoms2 = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms2, method="cutoff", cutoff=0)
print(f"Neighbors found: {atoms2.info.get('pyscal_neighbors_found', False)}")
print(f"Method: {atoms2.info.get('pyscal_neighbor_method', 'N/A')}")
Neighbors found: True
Method: cutoff
SANN Method#
The Solid-Angle based Nearest Neighbor (SANN) method determines neighbors based on solid angles, requiring no input parameters.
atoms3 = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms3, method="cutoff", cutoff="sann")
print(f"SANN neighbors found: {atoms3.info.get('pyscal_neighbors_found', False)}")
SANN neighbors found: True
Voronoi Tessellation#
Voronoi tessellation determines neighbors by shared Voronoi faces.
atoms4 = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms4, method="voronoi")
# Voronoi also computes volumes
if "pyscal_voronoi_volume" in atoms4.arrays:
print(f"Voronoi volume (atom 0): {atoms4.arrays['pyscal_voronoi_volume'][0]:.4f}")
Voronoi volume (atom 0): 11.7615
Nearest N Neighbors#
Find exactly N nearest neighbors for each atom.
atoms5 = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms5, method="number", nmax=12)
print(f"Nearest-12 neighbors found: {atoms5.info.get('pyscal_neighbors_found', False)}")
Nearest-12 neighbors found: True
PBC-Aware Distance#
get_distance computes the minimum-image distance between two positions, respecting periodic boundary conditions.
atoms6 = bulk("Cu", "fcc", cubic=True)
pos = atoms6.get_positions()
# Distance between first two atoms
d = pyscal.get_distance(atoms6, pos[0], pos[1])
print(f"Distance between atom 0 and atom 1: {d:.4f} Angstrom")
Distance between atom 0 and atom 1: 2.5527 Angstrom
Using Neighbors for Descriptors#
Once neighbors are found, you can compute any descriptor. Here’s a quick example:
atoms = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms, method="cutoff", cutoff=0)
# Now compute Steinhardt parameters
q = pyscal.steinhardt_parameter(atoms, l=[4, 6])
print(f"q4 mean: {q[0].mean():.4f}")
print(f"q6 mean: {q[1].mean():.4f}")
q4 mean: 0.1909
q6 mean: 0.5745
Which Method Should I Use?#
Method |
Best for |
|---|---|
Adaptive ( |
General-purpose, good default |
Fixed cutoff |
When you know the exact cutoff radius |
SANN |
Parameter-free, works well for disordered systems |
Voronoi |
When you also need Voronoi volumes/vectors |
Nearest N |
When you need exactly N neighbors (e.g., 12 for FCC) |