Coverage for ase / optimize / precon / neighbors.py: 97.30%
37 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
1# fmt: off
3import numpy as np
5from ase.constraints import FixAtoms
6from ase.filters import Filter
7from ase.geometry.cell import cell_to_cellpar
8from ase.neighborlist import neighbor_list
11def get_neighbours(atoms, r_cut, self_interaction=False,
12 neighbor_list=neighbor_list):
13 """Return a list of pairs of atoms within a given distance of each other.
15 Uses ase.neighborlist.neighbour_list to compute neighbors.
17 Args:
18 atoms: ase.atoms object to calculate neighbours for
19 r_cut: cutoff radius (float). Pairs of atoms are considered neighbours
20 if they are within a distance r_cut of each other (note that this
21 is double the parameter used in the ASE's neighborlist module)
22 neighbor_list: function (optional). Optionally replace the built-in
23 ASE neighbour list with an alternative with the same call
24 signature, e.g. `matscipy.neighbours.neighbour_list`.
26 Returns: a tuple (i_list, j_list, d_list, fixed_atoms):
27 i_list, j_list: i and j indices of each neighbour pair
28 d_list: absolute distance between the corresponding pair
29 fixed_atoms: indices of any fixed atoms
30 """
32 if isinstance(atoms, Filter):
33 atoms = atoms.atoms
35 i_list, j_list, d_list = neighbor_list('ijd', atoms, r_cut)
37 # filter out self-interactions (across PBC)
38 if not self_interaction:
39 mask = i_list != j_list
40 i_list = i_list[mask]
41 j_list = j_list[mask]
42 d_list = d_list[mask]
44 # filter out bonds where 1st atom (i) in pair is fixed
45 fixed_atoms = []
46 for constraint in atoms.constraints:
47 if isinstance(constraint, FixAtoms):
48 fixed_atoms.extend(list(constraint.index))
50 return i_list, j_list, d_list, fixed_atoms
53def estimate_nearest_neighbour_distance(atoms,
54 neighbor_list=neighbor_list):
55 """
56 Estimate nearest neighbour distance r_NN
58 Args:
59 atoms: Atoms object
60 neighbor_list: function (optional). Optionally replace the built-in
61 ASE neighbour list with an alternative with the same call
62 signature, e.g. `matscipy.neighbours.neighbour_list`.
64 Returns
65 -------
66 rNN: float
67 Nearest neighbour distance
68 """
70 if isinstance(atoms, Filter):
71 atoms = atoms.atoms
73 # start_time = time.time()
74 # compute number of neighbours of each atom. If any atom doesn't
75 # have a neighbour we increase the cutoff and try again, until our
76 # cutoff exceeds the size of the system
77 r_cut = 1.0
78 phi = (1.0 + np.sqrt(5.0)) / 2.0 # Golden ratio
80 # cell lengths and angles
81 a, b, c, _alpha, _beta, _gamma = cell_to_cellpar(atoms.cell)
82 extent = [a, b, c]
83 # print('estimate_nearest_neighbour_distance(): extent=%r' % extent)
85 while r_cut < 2.0 * max(extent):
86 # print('estimate_nearest_neighbour_distance(): '
87 # 'calling neighbour_list with r_cut=%.2f A' % r_cut)
88 i, _j, rij, _fixed_atoms = get_neighbours(
89 atoms, r_cut, self_interaction=True,
90 neighbor_list=neighbor_list)
91 if len(i) != 0:
92 nn_i = np.bincount(i, minlength=len(atoms))
93 if (nn_i != 0).all():
94 break
95 r_cut *= phi
96 else:
97 raise RuntimeError('increased r_cut to twice system extent without '
98 'finding neighbours for all atoms. This can '
99 'happen if your system is too small; try '
100 'setting r_cut manually')
102 # maximum of nearest neighbour distances
103 nn_distances = [np.min(rij[i == I]) for I in range(len(atoms))]
104 r_NN = np.max(nn_distances)
106 # print('estimate_nearest_neighbour_distance(): got r_NN=%.3f in %s s' %
107 # (r_NN, time.time() - start_time))
108 return r_NN