Steinhardt Bond-Order Parameters#
Steinhardt parameters \(q_l\) quantify the local symmetry around each atom using spherical harmonics. They are widely used to distinguish crystal structures:
Structure |
\(q_4\) |
\(q_6\) |
|---|---|---|
FCC |
0.19 |
0.57 |
BCC |
0.04 |
0.51 |
HCP |
0.10 |
0.48 |
import pyscal
from ase.build import bulk
import numpy as np
Basic Usage#
# Create FCC Cu
atoms = bulk("Cu", "fcc", cubic=True).repeat(4)
# Find neighbors first
pyscal.find_neighbors(atoms, method="cutoff", cutoff=0)
# Compute q4 and q6
q = pyscal.steinhardt_parameter(atoms, l=[4, 6])
print(f"q4: {q[0].mean():.4f} +/- {q[0].std():.6f}")
print(f"q6: {q[1].mean():.4f} +/- {q[1].std():.6f}")
q4: 0.1909 +/- 0.000000
q6: 0.5745 +/- 0.000000
Comparing Crystal Structures#
from pyscal.structures import make_crystal
structures = {
"FCC": make_crystal("fcc", lattice_constant=3.6, repetitions=(4, 4, 4)),
"BCC": make_crystal("bcc", lattice_constant=2.87, repetitions=(4, 4, 4)),
"HCP": make_crystal("hcp", lattice_constant=3.2, repetitions=(4, 4, 4), ca_ratio=1.633),
}
print(f"{'Structure':<10} {'q4':>8} {'q6':>8}")
print("-" * 28)
for name, s in structures.items():
pyscal.find_neighbors(s, method="cutoff", cutoff=0)
q = pyscal.steinhardt_parameter(s, l=[4, 6])
print(f"{name:<10} {q[0].mean():>8.4f} {q[1].mean():>8.4f}")
Structure q4 q6
----------------------------
FCC 0.1909 0.5745
BCC 0.0364 0.5107
HCP 0.0972 0.4848
Averaged Steinhardt Parameters#
Averaged parameters \(\bar{q}_l\) smooth out thermal noise by averaging the complex spherical harmonic coefficients over the neighbor shell. Set averaged=True.
atoms = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms, method="cutoff", cutoff=0)
q_avg = pyscal.steinhardt_parameter(atoms, l=[4, 6], averaged=True)
print(f"Averaged q4: {q_avg[0].mean():.4f}")
print(f"Averaged q6: {q_avg[1].mean():.4f}")
Averaged q4: 0.1909
Averaged q6: 0.5745
Higher-Order Parameters#
You can compute any \(l\) value. Higher values capture finer angular details.
atoms = bulk("Cu", "fcc", cubic=True).repeat(4)
pyscal.find_neighbors(atoms, method="cutoff", cutoff=0)
q = pyscal.steinhardt_parameter(atoms, l=[2, 4, 6, 8, 10, 12])
for i, l_val in enumerate([2, 4, 6, 8, 10, 12]):
print(f"q{l_val:<3}: {q[i].mean():.4f}")
q2 : 0.0000
q4 : 0.1909
q6 : 0.5745
q8 : 0.4039
q10 : 0.0129
q12 : 0.6001
Accessing Stored Results#
Results are stored in atoms.arrays with keys pyscal_q<l> (or pyscal_q<l>_avg for averaged).
# Direct access to per-atom arrays
q6_values = atoms.arrays["pyscal_q6"]
print(f"q6 per atom (first 5): {q6_values[:5]}")
print(f"Shape: {q6_values.shape}")
q6 per atom (first 5): [0.57452426 0.57452426 0.57452426 0.57452426 0.57452426]
Shape: (256,)