Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import numpy as np 

2import itertools 

3from scipy import sparse as sp 

4from scipy.spatial import cKDTree 

5import scipy.sparse.csgraph as csgraph 

6 

7from ase.data import atomic_numbers, covalent_radii 

8from ase.geometry import complete_cell, find_mic, wrap_positions 

9from ase.geometry import minkowski_reduce 

10from ase.cell import Cell 

11 

12 

13def natural_cutoffs(atoms, mult=1, **kwargs): 

14 """Generate a radial cutoff for every atom based on covalent radii. 

15 

16 The covalent radii are a reasonable cutoff estimation for bonds in 

17 many applications such as neighborlists, so function generates an 

18 atoms length list of radii based on this idea. 

19 

20 * atoms: An atoms object 

21 * mult: A multiplier for all cutoffs, useful for coarse grained adjustment 

22 * kwargs: Symbol of the atom and its corresponding cutoff, 

23 used to override the covalent radii 

24 """ 

25 return [kwargs.get(atom.symbol, covalent_radii[atom.number] * mult) 

26 for atom in atoms] 

27 

28 

29def build_neighbor_list(atoms, cutoffs=None, **kwargs): 

30 """Automatically build and update a NeighborList. 

31 

32 Parameters: 

33 

34 atoms : :class:`~ase.Atoms` object 

35 Atoms to build Neighborlist for. 

36 cutoffs: list of floats 

37 Radii for each atom. If not given it will be produced by calling 

38 :func:`ase.neighborlist.natural_cutoffs` 

39 kwargs: arbitrary number of options 

40 Will be passed to the constructor of 

41 :class:`~ase.neighborlist.NeighborList` 

42 

43 Returns: 

44 

45 return: :class:`~ase.neighborlist.NeighborList` 

46 A :class:`~ase.neighborlist.NeighborList` instance (updated). 

47 """ 

48 if cutoffs is None: 

49 cutoffs = natural_cutoffs(atoms) 

50 

51 nl = NeighborList(cutoffs, **kwargs) 

52 nl.update(atoms) 

53 

54 return nl 

55 

56 

57def get_distance_matrix(graph, limit=3): 

58 """Get Distance Matrix from a Graph. 

59 

60 Parameters: 

61 

62 graph: array, matrix or sparse matrix, 2 dimensions (N, N) 

63 Graph representation of the connectivity. 

64 See `scipy doc <https://docs.scipy.org/doc/scipy/reference/generated\ 

65/scipy.sparse.csgraph.dijkstra.html#scipy.sparse.csgraph.dijkstra>`_ 

66 for reference. 

67 limit: integer 

68 Maximum number of steps to analyze. For most molecular information, 

69 three should be enough. 

70 

71 Returns: 

72 

73 return: scipy.sparse.csr_matrix, shape (N, N) 

74 A scipy.sparse.csr_matrix. All elements that are not connected within 

75 *limit* steps are set to zero. 

76 

77 This is a potential memory bottleneck, as csgraph.dijkstra produces a 

78 dense output matrix. Here we replace all np.inf values with 0 and 

79 transform back to csr_matrix. 

80 Why not dok_matrix like the connectivity-matrix? Because row-picking 

81 is most likely and this is super fast with csr. 

82 """ 

83 mat = csgraph.dijkstra(graph, directed=False, limit=limit) 

84 mat[mat == np.inf] = 0 

85 return sp.csr_matrix(mat, dtype=np.int8) 

86 

87 

88def get_distance_indices(distanceMatrix, distance): 

89 """Get indices for each node that are distance or less away. 

90 

91 Parameters: 

92 

93 distanceMatrix: any one of scipy.sparse matrices (NxN) 

94 Matrix containing distance information of atoms. Get it e.g. with 

95 :func:`~ase.neighborlist.get_distance_matrix` 

96 distance: integer 

97 Number of steps to allow. 

98 

99 Returns: 

100 

101 return: list of length N 

102 List of length N. return[i] has all indices connected to item i. 

103 

104 The distance matrix only contains shortest paths, so when looking for 

105 distances longer than one, we need to add the lower values for cases 

106 where atoms are connected via a shorter path too. 

107 """ 

108 shape = distanceMatrix.get_shape() 

109 indices = [] 

110 # iterate over rows 

111 for i in range(shape[0]): 

112 row = distanceMatrix.getrow(i)[0] 

113 # find all non-zero 

114 found = sp.find(row) 

115 # screen for smaller or equal distance 

116 equal = np.where(found[-1] <= distance)[0] 

117 # found[1] contains the indexes 

118 indices.append([found[1][x] for x in equal]) 

119 return indices 

120 

121 

122def mic(dr, cell, pbc=True): 

123 """ 

124 Apply minimum image convention to an array of distance vectors. 

125 

126 Parameters: 

127 

128 dr : array_like 

129 Array of distance vectors. 

130 cell : array_like 

131 Simulation cell. 

132 pbc : array_like, optional 

133 Periodic boundary conditions in x-, y- and z-direction. Default is to 

134 assume periodic boundaries in all directions. 

135 

136 Returns: 

137 

138 dr : array 

139 Array of distance vectors, wrapped according to the minimum image 

140 convention. 

141 """ 

142 dr, _ = find_mic(dr, cell, pbc) 

143 return dr 

144 

145 

146def primitive_neighbor_list(quantities, pbc, cell, positions, cutoff, 

147 numbers=None, self_interaction=False, 

148 use_scaled_positions=False, max_nbins=1e6): 

149 """Compute a neighbor list for an atomic configuration. 

150 

151 Atoms outside periodic boundaries are mapped into the box. Atoms 

152 outside nonperiodic boundaries are included in the neighbor list 

153 but complexity of neighbor list search for those can become n^2. 

154 

155 The neighbor list is sorted by first atom index 'i', but not by second 

156 atom index 'j'. 

157 

158 Parameters: 

159 

160 quantities: str 

161 Quantities to compute by the neighbor list algorithm. Each character 

162 in this string defines a quantity. They are returned in a tuple of 

163 the same order. Possible quantities are 

164 

165 * 'i' : first atom index 

166 * 'j' : second atom index 

167 * 'd' : absolute distance 

168 * 'D' : distance vector 

169 * 'S' : shift vector (number of cell boundaries crossed by the bond 

170 between atom i and j). With the shift vector S, the 

171 distances D between atoms can be computed from: 

172 D = positions[j]-positions[i]+S.dot(cell) 

173 pbc: array_like 

174 3-tuple indicating giving periodic boundaries in the three Cartesian 

175 directions. 

176 cell: 3x3 matrix 

177 Unit cell vectors. 

178 positions: list of xyz-positions 

179 Atomic positions. Anything that can be converted to an ndarray of 

180 shape (n, 3) will do: [(x1,y1,z1), (x2,y2,z2), ...]. If 

181 use_scaled_positions is set to true, this must be scaled positions. 

182 cutoff: float or dict 

183 Cutoff for neighbor search. It can be: 

184 

185 * A single float: This is a global cutoff for all elements. 

186 * A dictionary: This specifies cutoff values for element 

187 pairs. Specification accepts element numbers of symbols. 

188 Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} 

189 * A list/array with a per atom value: This specifies the radius of 

190 an atomic sphere for each atoms. If spheres overlap, atoms are 

191 within each others neighborhood. See 

192 :func:`~ase.neighborlist.natural_cutoffs` 

193 for an example on how to get such a list. 

194 self_interaction: bool 

195 Return the atom itself as its own neighbor if set to true. 

196 Default: False 

197 use_scaled_positions: bool 

198 If set to true, positions are expected to be scaled positions. 

199 max_nbins: int 

200 Maximum number of bins used in neighbor search. This is used to limit 

201 the maximum amount of memory required by the neighbor list. 

202 

203 Returns: 

204 

205 i, j, ... : array 

206 Tuple with arrays for each quantity specified above. Indices in `i` 

207 are returned in ascending order 0..len(a)-1, but the order of (i,j) 

208 pairs is not guaranteed. 

209 

210 """ 

211 

212 # Naming conventions: Suffixes indicate the dimension of an array. The 

213 # following convention is used here: 

214 # c: Cartesian index, can have values 0, 1, 2 

215 # i: Global atom index, can have values 0..len(a)-1 

216 # xyz: Bin index, three values identifying x-, y- and z-component of a 

217 # spatial bin that is used to make neighbor search O(n) 

218 # b: Linearized version of the 'xyz' bin index 

219 # a: Bin-local atom index, i.e. index identifying an atom *within* a 

220 # bin 

221 # p: Pair index, can have value 0 or 1 

222 # n: (Linear) neighbor index 

223 

224 # Return empty neighbor list if no atoms are passed here 

225 if len(positions) == 0: 

226 empty_types = dict(i=(int, (0, )), 

227 j=(int, (0, )), 

228 D=(float, (0, 3)), 

229 d=(float, (0, )), 

230 S=(int, (0, 3))) 

231 retvals = [] 

232 for i in quantities: 

233 dtype, shape = empty_types[i] 

234 retvals += [np.array([], dtype=dtype).reshape(shape)] 

235 if len(retvals) == 1: 

236 return retvals[0] 

237 else: 

238 return tuple(retvals) 

239 

240 # Compute reciprocal lattice vectors. 

241 b1_c, b2_c, b3_c = np.linalg.pinv(cell).T 

242 

243 # Compute distances of cell faces. 

244 l1 = np.linalg.norm(b1_c) 

245 l2 = np.linalg.norm(b2_c) 

246 l3 = np.linalg.norm(b3_c) 

247 face_dist_c = np.array([1 / l1 if l1 > 0 else 1, 

248 1 / l2 if l2 > 0 else 1, 

249 1 / l3 if l3 > 0 else 1]) 

250 

251 if isinstance(cutoff, dict): 

252 max_cutoff = max(cutoff.values()) 

253 else: 

254 if np.isscalar(cutoff): 

255 max_cutoff = cutoff 

256 else: 

257 cutoff = np.asarray(cutoff) 

258 max_cutoff = 2 * np.max(cutoff) 

259 

260 # We use a minimum bin size of 3 A 

261 bin_size = max(max_cutoff, 3) 

262 # Compute number of bins such that a sphere of radius cutoff fits into 

263 # eight neighboring bins. 

264 nbins_c = np.maximum((face_dist_c / bin_size).astype(int), [1, 1, 1]) 

265 nbins = np.prod(nbins_c) 

266 # Make sure we limit the amount of memory used by the explicit bins. 

267 while nbins > max_nbins: 

268 nbins_c = np.maximum(nbins_c // 2, [1, 1, 1]) 

269 nbins = np.prod(nbins_c) 

270 

271 # Compute over how many bins we need to loop in the neighbor list search. 

272 neigh_search_x, neigh_search_y, neigh_search_z = \ 

273 np.ceil(bin_size * nbins_c / face_dist_c).astype(int) 

274 

275 # If we only have a single bin and the system is not periodic, then we 

276 # do not need to search neighboring bins 

277 neigh_search_x = 0 if nbins_c[0] == 1 and not pbc[0] else neigh_search_x 

278 neigh_search_y = 0 if nbins_c[1] == 1 and not pbc[1] else neigh_search_y 

279 neigh_search_z = 0 if nbins_c[2] == 1 and not pbc[2] else neigh_search_z 

280 

281 # Sort atoms into bins. 

282 if use_scaled_positions: 

283 scaled_positions_ic = positions 

284 positions = np.dot(scaled_positions_ic, cell) 

285 else: 

286 scaled_positions_ic = np.linalg.solve(complete_cell(cell).T, 

287 positions.T).T 

288 bin_index_ic = np.floor(scaled_positions_ic * nbins_c).astype(int) 

289 cell_shift_ic = np.zeros_like(bin_index_ic) 

290 

291 for c in range(3): 

292 if pbc[c]: 

293 # (Note: np.divmod does not exist in older numpies) 

294 cell_shift_ic[:, c], bin_index_ic[:, c] = \ 

295 divmod(bin_index_ic[:, c], nbins_c[c]) 

296 else: 

297 bin_index_ic[:, c] = np.clip(bin_index_ic[:, c], 0, nbins_c[c] - 1) 

298 

299 # Convert Cartesian bin index to unique scalar bin index. 

300 bin_index_i = (bin_index_ic[:, 0] + 

301 nbins_c[0] * (bin_index_ic[:, 1] + 

302 nbins_c[1] * bin_index_ic[:, 2])) 

303 

304 # atom_i contains atom index in new sort order. 

305 atom_i = np.argsort(bin_index_i) 

306 bin_index_i = bin_index_i[atom_i] 

307 

308 # Find max number of atoms per bin 

309 max_natoms_per_bin = np.bincount(bin_index_i).max() 

310 

311 # Sort atoms into bins: atoms_in_bin_ba contains for each bin (identified 

312 # by its scalar bin index) a list of atoms inside that bin. This list is 

313 # homogeneous, i.e. has the same size *max_natoms_per_bin* for all bins. 

314 # The list is padded with -1 values. 

315 atoms_in_bin_ba = -np.ones([nbins, max_natoms_per_bin], dtype=int) 

316 for i in range(max_natoms_per_bin): 

317 # Create a mask array that identifies the first atom of each bin. 

318 mask = np.append([True], bin_index_i[:-1] != bin_index_i[1:]) 

319 # Assign all first atoms. 

320 atoms_in_bin_ba[bin_index_i[mask], i] = atom_i[mask] 

321 

322 # Remove atoms that we just sorted into atoms_in_bin_ba. The next 

323 # "first" atom will be the second and so on. 

324 mask = np.logical_not(mask) 

325 atom_i = atom_i[mask] 

326 bin_index_i = bin_index_i[mask] 

327 

328 # Make sure that all atoms have been sorted into bins. 

329 assert len(atom_i) == 0 

330 assert len(bin_index_i) == 0 

331 

332 # Now we construct neighbor pairs by pairing up all atoms within a bin or 

333 # between bin and neighboring bin. atom_pairs_pn is a helper buffer that 

334 # contains all potential pairs of atoms between two bins, i.e. it is a list 

335 # of length max_natoms_per_bin**2. 

336 atom_pairs_pn = np.indices((max_natoms_per_bin, max_natoms_per_bin), 

337 dtype=int) 

338 atom_pairs_pn = atom_pairs_pn.reshape(2, -1) 

339 

340 # Initialized empty neighbor list buffers. 

341 first_at_neightuple_nn = [] 

342 secnd_at_neightuple_nn = [] 

343 cell_shift_vector_x_n = [] 

344 cell_shift_vector_y_n = [] 

345 cell_shift_vector_z_n = [] 

346 

347 # This is the main neighbor list search. We loop over neighboring bins and 

348 # then construct all possible pairs of atoms between two bins, assuming 

349 # that each bin contains exactly max_natoms_per_bin atoms. We then throw 

350 # out pairs involving pad atoms with atom index -1 below. 

351 binz_xyz, biny_xyz, binx_xyz = np.meshgrid(np.arange(nbins_c[2]), 

352 np.arange(nbins_c[1]), 

353 np.arange(nbins_c[0]), 

354 indexing='ij') 

355 # The memory layout of binx_xyz, biny_xyz, binz_xyz is such that computing 

356 # the respective bin index leads to a linearly increasing consecutive list. 

357 # The following assert statement succeeds: 

358 # b_b = (binx_xyz + nbins_c[0] * (biny_xyz + nbins_c[1] * 

359 # binz_xyz)).ravel() 

360 # assert (b_b == np.arange(np.prod(nbins_c))).all() 

361 

362 # First atoms in pair. 

363 _first_at_neightuple_n = atoms_in_bin_ba[:, atom_pairs_pn[0]] 

364 for dz in range(-neigh_search_z, neigh_search_z + 1): 

365 for dy in range(-neigh_search_y, neigh_search_y + 1): 

366 for dx in range(-neigh_search_x, neigh_search_x + 1): 

367 # Bin index of neighboring bin and shift vector. 

368 shiftx_xyz, neighbinx_xyz = divmod(binx_xyz + dx, nbins_c[0]) 

369 shifty_xyz, neighbiny_xyz = divmod(biny_xyz + dy, nbins_c[1]) 

370 shiftz_xyz, neighbinz_xyz = divmod(binz_xyz + dz, nbins_c[2]) 

371 neighbin_b = (neighbinx_xyz + nbins_c[0] * 

372 (neighbiny_xyz + nbins_c[1] * neighbinz_xyz) 

373 ).ravel() 

374 

375 # Second atom in pair. 

376 _secnd_at_neightuple_n = \ 

377 atoms_in_bin_ba[neighbin_b][:, atom_pairs_pn[1]] 

378 

379 # Shift vectors. 

380 _cell_shift_vector_x_n = \ 

381 np.resize(shiftx_xyz.reshape(-1, 1), 

382 (max_natoms_per_bin**2, shiftx_xyz.size)).T 

383 _cell_shift_vector_y_n = \ 

384 np.resize(shifty_xyz.reshape(-1, 1), 

385 (max_natoms_per_bin**2, shifty_xyz.size)).T 

386 _cell_shift_vector_z_n = \ 

387 np.resize(shiftz_xyz.reshape(-1, 1), 

388 (max_natoms_per_bin**2, shiftz_xyz.size)).T 

389 

390 # We have created too many pairs because we assumed each bin 

391 # has exactly max_natoms_per_bin atoms. Remove all surperfluous 

392 # pairs. Those are pairs that involve an atom with index -1. 

393 mask = np.logical_and(_first_at_neightuple_n != -1, 

394 _secnd_at_neightuple_n != -1) 

395 if mask.sum() > 0: 

396 first_at_neightuple_nn += [_first_at_neightuple_n[mask]] 

397 secnd_at_neightuple_nn += [_secnd_at_neightuple_n[mask]] 

398 cell_shift_vector_x_n += [_cell_shift_vector_x_n[mask]] 

399 cell_shift_vector_y_n += [_cell_shift_vector_y_n[mask]] 

400 cell_shift_vector_z_n += [_cell_shift_vector_z_n[mask]] 

401 

402 # Flatten overall neighbor list. 

403 first_at_neightuple_n = np.concatenate(first_at_neightuple_nn) 

404 secnd_at_neightuple_n = np.concatenate(secnd_at_neightuple_nn) 

405 cell_shift_vector_n = np.transpose([np.concatenate(cell_shift_vector_x_n), 

406 np.concatenate(cell_shift_vector_y_n), 

407 np.concatenate(cell_shift_vector_z_n)]) 

408 

409 # Add global cell shift to shift vectors 

410 cell_shift_vector_n += cell_shift_ic[first_at_neightuple_n] - \ 

411 cell_shift_ic[secnd_at_neightuple_n] 

412 

413 # Remove all self-pairs that do not cross the cell boundary. 

414 if not self_interaction: 

415 m = np.logical_not(np.logical_and( 

416 first_at_neightuple_n == secnd_at_neightuple_n, 

417 (cell_shift_vector_n == 0).all(axis=1))) 

418 first_at_neightuple_n = first_at_neightuple_n[m] 

419 secnd_at_neightuple_n = secnd_at_neightuple_n[m] 

420 cell_shift_vector_n = cell_shift_vector_n[m] 

421 

422 # For nonperiodic directions, remove any bonds that cross the domain 

423 # boundary. 

424 for c in range(3): 

425 if not pbc[c]: 

426 m = cell_shift_vector_n[:, c] == 0 

427 first_at_neightuple_n = first_at_neightuple_n[m] 

428 secnd_at_neightuple_n = secnd_at_neightuple_n[m] 

429 cell_shift_vector_n = cell_shift_vector_n[m] 

430 

431 # Sort neighbor list. 

432 i = np.argsort(first_at_neightuple_n) 

433 first_at_neightuple_n = first_at_neightuple_n[i] 

434 secnd_at_neightuple_n = secnd_at_neightuple_n[i] 

435 cell_shift_vector_n = cell_shift_vector_n[i] 

436 

437 # Compute distance vectors. 

438 distance_vector_nc = positions[secnd_at_neightuple_n] - \ 

439 positions[first_at_neightuple_n] + \ 

440 cell_shift_vector_n.dot(cell) 

441 abs_distance_vector_n = \ 

442 np.sqrt(np.sum(distance_vector_nc * distance_vector_nc, axis=1)) 

443 

444 # We have still created too many pairs. Only keep those with distance 

445 # smaller than max_cutoff. 

446 mask = abs_distance_vector_n < max_cutoff 

447 first_at_neightuple_n = first_at_neightuple_n[mask] 

448 secnd_at_neightuple_n = secnd_at_neightuple_n[mask] 

449 cell_shift_vector_n = cell_shift_vector_n[mask] 

450 distance_vector_nc = distance_vector_nc[mask] 

451 abs_distance_vector_n = abs_distance_vector_n[mask] 

452 

453 if isinstance(cutoff, dict) and numbers is not None: 

454 # If cutoff is a dictionary, then the cutoff radii are specified per 

455 # element pair. We now have a list up to maximum cutoff. 

456 per_pair_cutoff_n = np.zeros_like(abs_distance_vector_n) 

457 for (atomic_number1, atomic_number2), c in cutoff.items(): 

458 try: 

459 atomic_number1 = atomic_numbers[atomic_number1] 

460 except KeyError: 

461 pass 

462 try: 

463 atomic_number2 = atomic_numbers[atomic_number2] 

464 except KeyError: 

465 pass 

466 if atomic_number1 == atomic_number2: 

467 mask = np.logical_and( 

468 numbers[first_at_neightuple_n] == atomic_number1, 

469 numbers[secnd_at_neightuple_n] == atomic_number2) 

470 else: 

471 mask = np.logical_or( 

472 np.logical_and( 

473 numbers[first_at_neightuple_n] == atomic_number1, 

474 numbers[secnd_at_neightuple_n] == atomic_number2), 

475 np.logical_and( 

476 numbers[first_at_neightuple_n] == atomic_number2, 

477 numbers[secnd_at_neightuple_n] == atomic_number1)) 

478 per_pair_cutoff_n[mask] = c 

479 mask = abs_distance_vector_n < per_pair_cutoff_n 

480 first_at_neightuple_n = first_at_neightuple_n[mask] 

481 secnd_at_neightuple_n = secnd_at_neightuple_n[mask] 

482 cell_shift_vector_n = cell_shift_vector_n[mask] 

483 distance_vector_nc = distance_vector_nc[mask] 

484 abs_distance_vector_n = abs_distance_vector_n[mask] 

485 elif not np.isscalar(cutoff): 

486 # If cutoff is neither a dictionary nor a scalar, then we assume it is 

487 # a list or numpy array that contains atomic radii. Atoms are neighbors 

488 # if their radii overlap. 

489 mask = abs_distance_vector_n < \ 

490 cutoff[first_at_neightuple_n] + cutoff[secnd_at_neightuple_n] 

491 first_at_neightuple_n = first_at_neightuple_n[mask] 

492 secnd_at_neightuple_n = secnd_at_neightuple_n[mask] 

493 cell_shift_vector_n = cell_shift_vector_n[mask] 

494 distance_vector_nc = distance_vector_nc[mask] 

495 abs_distance_vector_n = abs_distance_vector_n[mask] 

496 

497 # Assemble return tuple. 

498 retvals = [] 

499 for q in quantities: 

500 if q == 'i': 

501 retvals += [first_at_neightuple_n] 

502 elif q == 'j': 

503 retvals += [secnd_at_neightuple_n] 

504 elif q == 'D': 

505 retvals += [distance_vector_nc] 

506 elif q == 'd': 

507 retvals += [abs_distance_vector_n] 

508 elif q == 'S': 

509 retvals += [cell_shift_vector_n] 

510 else: 

511 raise ValueError('Unsupported quantity specified.') 

512 if len(retvals) == 1: 

513 return retvals[0] 

514 else: 

515 return tuple(retvals) 

516 

517 

518def neighbor_list(quantities, a, cutoff, self_interaction=False, 

519 max_nbins=1e6): 

520 """Compute a neighbor list for an atomic configuration. 

521 

522 Atoms outside periodic boundaries are mapped into the box. Atoms 

523 outside nonperiodic boundaries are included in the neighbor list 

524 but complexity of neighbor list search for those can become n^2. 

525 

526 The neighbor list is sorted by first atom index 'i', but not by second 

527 atom index 'j'. 

528 

529 Parameters: 

530 

531 quantities: str 

532 Quantities to compute by the neighbor list algorithm. Each character 

533 in this string defines a quantity. They are returned in a tuple of 

534 the same order. Possible quantities are: 

535 

536 * 'i' : first atom index 

537 * 'j' : second atom index 

538 * 'd' : absolute distance 

539 * 'D' : distance vector 

540 * 'S' : shift vector (number of cell boundaries crossed by the bond 

541 between atom i and j). With the shift vector S, the 

542 distances D between atoms can be computed from: 

543 D = a.positions[j]-a.positions[i]+S.dot(a.cell) 

544 a: :class:`ase.Atoms` 

545 Atomic configuration. 

546 cutoff: float or dict 

547 Cutoff for neighbor search. It can be: 

548 

549 * A single float: This is a global cutoff for all elements. 

550 * A dictionary: This specifies cutoff values for element 

551 pairs. Specification accepts element numbers of symbols. 

552 Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85} 

553 * A list/array with a per atom value: This specifies the radius of 

554 an atomic sphere for each atoms. If spheres overlap, atoms are 

555 within each others neighborhood. See 

556 :func:`~ase.neighborlist.natural_cutoffs` 

557 for an example on how to get such a list. 

558 

559 self_interaction: bool 

560 Return the atom itself as its own neighbor if set to true. 

561 Default: False 

562 max_nbins: int 

563 Maximum number of bins used in neighbor search. This is used to limit 

564 the maximum amount of memory required by the neighbor list. 

565 

566 Returns: 

567 

568 i, j, ...: array 

569 Tuple with arrays for each quantity specified above. Indices in `i` 

570 are returned in ascending order 0..len(a), but the order of (i,j) 

571 pairs is not guaranteed. 

572 

573 Examples: 

574 

575 Examples assume Atoms object *a* and numpy imported as *np*. 

576 

577 1. Coordination counting:: 

578 

579 i = neighbor_list('i', a, 1.85) 

580 coord = np.bincount(i) 

581 

582 2. Coordination counting with different cutoffs for each pair of species:: 

583 

584 i = neighbor_list('i', a, 

585 {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85}) 

586 coord = np.bincount(i) 

587 

588 3. Pair distribution function:: 

589 

590 d = neighbor_list('d', a, 10.00) 

591 h, bin_edges = np.histogram(d, bins=100) 

592 pdf = h/(4*np.pi/3*( 

593 bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a) 

594 

595 4. Pair potential:: 

596 

597 i, j, d, D = neighbor_list('ijdD', a, 5.0) 

598 energy = (-C/d**6).sum() 

599 forces = (6*C/d**5 * (D/d).T).T 

600 forces_x = np.bincount(j, weights=forces[:, 0], minlength=len(a)) - \ 

601 np.bincount(i, weights=forces[:, 0], minlength=len(a)) 

602 forces_y = np.bincount(j, weights=forces[:, 1], minlength=len(a)) - \ 

603 np.bincount(i, weights=forces[:, 1], minlength=len(a)) 

604 forces_z = np.bincount(j, weights=forces[:, 2], minlength=len(a)) - \ 

605 np.bincount(i, weights=pair_forces[:, 2], minlength=len(a)) 

606 

607 5. Dynamical matrix for a pair potential stored in a block sparse format:: 

608 

609 from scipy.sparse import bsr_matrix 

610 i, j, dr, abs_dr = neighbor_list('ijDd', atoms) 

611 energy = (dr.T / abs_dr).T 

612 dynmat = -(dde * (energy.reshape(-1, 3, 1) 

613 * energy.reshape(-1, 1, 3)).T).T \ 

614 -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \ 

615 (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T 

616 dynmat_bsr = bsr_matrix((dynmat, j, first_i), 

617 shape=(3*len(a), 3*len(a))) 

618 

619 dynmat_diag = np.empty((len(a), 3, 3)) 

620 for x in range(3): 

621 for y in range(3): 

622 dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y]) 

623 

624 dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)), 

625 np.arange(len(a) + 1)), 

626 shape=(3 * len(a), 3 * len(a))) 

627 

628 """ 

629 return primitive_neighbor_list(quantities, a.pbc, 

630 a.get_cell(complete=True), 

631 a.positions, cutoff, numbers=a.numbers, 

632 self_interaction=self_interaction, 

633 max_nbins=max_nbins) 

634 

635 

636def first_neighbors(natoms, first_atom): 

637 """ 

638 Compute an index array pointing to the ranges within the neighbor list that 

639 contain the neighbors for a certain atom. 

640 

641 Parameters: 

642 

643 natoms : int 

644 Total number of atom. 

645 first_atom : array_like 

646 Array containing the first atom 'i' of the neighbor tuple returned 

647 by the neighbor list. 

648 

649 Returns: 

650 

651 seed : array 

652 Array containing pointers to the start and end location of the 

653 neighbors of a certain atom. Neighbors of atom k have indices from s[k] 

654 to s[k+1]-1. 

655 """ 

656 if len(first_atom) == 0: 

657 return np.zeros(natoms + 1, dtype=int) 

658 # Create a seed array (which is returned by this function) populated with 

659 # -1. 

660 seed = -np.ones(natoms + 1, dtype=int) 

661 

662 first_atom = np.asarray(first_atom) 

663 

664 # Mask array contains all position where the number in the (sorted) array 

665 # with first atoms (in the neighbor pair) changes. 

666 mask = first_atom[:-1] != first_atom[1:] 

667 

668 # Seed array needs to start at 0 

669 seed[first_atom[0]] = 0 

670 # Seed array needs to stop at the length of the neighbor list 

671 seed[-1] = len(first_atom) 

672 # Populate all intermediate seed with the index of where the mask array is 

673 # true, i.e. the index where the first_atom array changes. 

674 seed[first_atom[1:][mask]] = (np.arange(len(mask)) + 1)[mask] 

675 

676 # Now fill all remaining -1 value with the value in the seed array right 

677 # behind them. (There are no neighbor so seed[i] and seed[i+1] must point) 

678 # to the same index. 

679 mask = seed == -1 

680 while mask.any(): 

681 seed[mask] = seed[np.arange(natoms + 1)[mask] + 1] 

682 mask = seed == -1 

683 return seed 

684 

685 

686def get_connectivity_matrix(nl, sparse=True): 

687 """Return connectivity matrix for a given NeighborList (dtype=numpy.int8). 

688 

689 A matrix of shape (nAtoms, nAtoms) will be returned. 

690 Connected atoms i and j will have matrix[i,j] == 1, unconnected 

691 matrix[i,j] == 0. If bothways=True the matrix will be symmetric, 

692 otherwise not! 

693 

694 If *sparse* is True, a scipy csr matrix is returned. 

695 If *sparse* is False, a numpy matrix is returned. 

696 

697 Note that the old and new neighborlists might give different results 

698 for periodic systems if bothways=False. 

699 

700 Example: 

701 

702 Determine which molecule in a system atom 1 belongs to. 

703 

704 >>> from ase import neighborlist 

705 >>> from ase.build import molecule 

706 >>> from scipy import sparse 

707 >>> mol = molecule('CH3CH2OH') 

708 >>> cutOff = neighborlist.natural_cutoffs(mol) 

709 >>> neighborList = neighborlist.NeighborList( 

710 ... cutOff, self_interaction=False, bothways=True) 

711 >>> neighborList.update(mol) 

712 >>> matrix = neighborList.get_connectivity_matrix() 

713 >>> #or: matrix = neighborlist.get_connectivity_matrix(neighborList.nl) 

714 >>> n_components, component_list = sparse.csgraph.connected_components( 

715 ... matrix) 

716 >>> idx = 1 

717 >>> molIdx = component_list[idx] 

718 >>> print("There are {} molecules in the system".format(n_components)) 

719 >>> print("Atom {} is part of molecule {}".format(idx, molIdx)) 

720 >>> molIdxs = [i for i in range(len(component_list)) 

721 ... if component_list[i] == molIdx] 

722 >>> print("Atoms are part of molecule {}: {}".format(molIdx, molIdxs)) 

723 """ 

724 

725 nAtoms = len(nl.cutoffs) 

726 

727 if nl.nupdates <= 0: 

728 raise RuntimeError( 

729 'Must call update(atoms) on your neighborlist first!') 

730 

731 if sparse: 

732 matrix = sp.dok_matrix((nAtoms, nAtoms), dtype=np.int8) 

733 else: 

734 matrix = np.zeros((nAtoms, nAtoms), dtype=np.int8) 

735 

736 for i in range(nAtoms): 

737 for idx in nl.get_neighbors(i)[0]: 

738 matrix[i, idx] = 1 

739 

740 return matrix 

741 

742 

743class NewPrimitiveNeighborList: 

744 """Neighbor list object. Wrapper around neighbor_list and first_neighbors. 

745 

746 cutoffs: list of float 

747 List of cutoff radii - one for each atom. If the spheres (defined by 

748 their cutoff radii) of two atoms overlap, they will be counted as 

749 neighbors. 

750 skin: float 

751 If no atom has moved more than the skin-distance since the 

752 last call to the 

753 :meth:`~ase.neighborlist.NewPrimitiveNeighborList.update()` 

754 method, then the neighbor list can be reused. This will save 

755 some expensive rebuilds of the list, but extra neighbors outside 

756 the cutoff will be returned. 

757 sorted: bool 

758 Sort neighbor list. 

759 self_interaction: bool 

760 Should an atom return itself as a neighbor? 

761 bothways: bool 

762 Return all neighbors. Default is to return only "half" of 

763 the neighbors. 

764 

765 Example:: 

766 

767 nl = NeighborList([2.3, 1.7]) 

768 nl.update(atoms) 

769 indices, offsets = nl.get_neighbors(0) 

770 """ 

771 

772 def __init__(self, cutoffs, skin=0.3, sorted=False, self_interaction=True, 

773 bothways=False, use_scaled_positions=False): 

774 self.cutoffs = np.asarray(cutoffs) + skin 

775 self.skin = skin 

776 self.sorted = sorted 

777 self.self_interaction = self_interaction 

778 self.bothways = bothways 

779 self.nupdates = 0 

780 self.use_scaled_positions = use_scaled_positions 

781 self.nneighbors = 0 

782 self.npbcneighbors = 0 

783 

784 def update(self, pbc, cell, positions, numbers=None): 

785 """Make sure the list is up to date.""" 

786 

787 if self.nupdates == 0: 

788 self.build(pbc, cell, positions, numbers=numbers) 

789 return True 

790 

791 if ((self.pbc != pbc).any() or (self.cell != cell).any() or 

792 ((self.positions - positions)**2).sum(1).max() > self.skin**2): 

793 self.build(pbc, cell, positions, numbers=numbers) 

794 return True 

795 

796 return False 

797 

798 def build(self, pbc, cell, positions, numbers=None): 

799 """Build the list. 

800 """ 

801 self.pbc = np.array(pbc, copy=True) 

802 self.cell = np.array(cell, copy=True) 

803 self.positions = np.array(positions, copy=True) 

804 

805 pair_first, pair_second, offset_vec = \ 

806 primitive_neighbor_list( 

807 'ijS', pbc, cell, positions, self.cutoffs, numbers=numbers, 

808 self_interaction=self.self_interaction, 

809 use_scaled_positions=self.use_scaled_positions) 

810 

811 if len(positions) > 0 and not self.bothways: 

812 offset_x, offset_y, offset_z = offset_vec.T 

813 

814 mask = offset_z > 0 

815 mask &= offset_y == 0 

816 mask |= offset_y > 0 

817 mask &= offset_x == 0 

818 mask |= offset_x > 0 

819 mask |= (pair_first <= pair_second) & (offset_vec == 0).all(axis=1) 

820 

821 pair_first = pair_first[mask] 

822 pair_second = pair_second[mask] 

823 offset_vec = offset_vec[mask] 

824 

825 if len(positions) > 0 and self.sorted: 

826 mask = np.argsort(pair_first * len(pair_first) + 

827 pair_second) 

828 pair_first = pair_first[mask] 

829 pair_second = pair_second[mask] 

830 offset_vec = offset_vec[mask] 

831 

832 self.pair_first = pair_first 

833 self.pair_second = pair_second 

834 self.offset_vec = offset_vec 

835 

836 # Compute the index array point to the first neighbor 

837 self.first_neigh = first_neighbors(len(positions), pair_first) 

838 

839 self.nupdates += 1 

840 

841 def get_neighbors(self, a): 

842 """Return neighbors of atom number a. 

843 

844 A list of indices and offsets to neighboring atoms is 

845 returned. The positions of the neighbor atoms can be 

846 calculated like this: 

847 

848 >>> indices, offsets = nl.get_neighbors(42) 

849 >>> for i, offset in zip(indices, offsets): 

850 >>> print(atoms.positions[i] + dot(offset, atoms.get_cell())) 

851 

852 Notice that if get_neighbors(a) gives atom b as a neighbor, 

853 then get_neighbors(b) will not return a as a neighbor - unless 

854 bothways=True was used.""" 

855 

856 return (self.pair_second[self.first_neigh[a]:self.first_neigh[a + 1]], 

857 self.offset_vec[self.first_neigh[a]:self.first_neigh[a + 1]]) 

858 

859 

860class PrimitiveNeighborList: 

861 """Neighbor list that works without Atoms objects. 

862 

863 This is less fancy, but can be used to avoid conversions between 

864 scaled and non-scaled coordinates which may affect cell offsets 

865 through rounding errors. 

866 """ 

867 

868 def __init__(self, cutoffs, skin=0.3, sorted=False, self_interaction=True, 

869 bothways=False, use_scaled_positions=False): 

870 self.cutoffs = np.asarray(cutoffs) + skin 

871 self.skin = skin 

872 self.sorted = sorted 

873 self.self_interaction = self_interaction 

874 self.bothways = bothways 

875 self.nupdates = 0 

876 self.use_scaled_positions = use_scaled_positions 

877 self.nneighbors = 0 

878 self.npbcneighbors = 0 

879 

880 def update(self, pbc, cell, coordinates): 

881 """Make sure the list is up to date.""" 

882 

883 if self.nupdates == 0: 

884 self.build(pbc, cell, coordinates) 

885 return True 

886 

887 if ((self.pbc != pbc).any() or (self.cell != cell).any() or ( 

888 (self.coordinates 

889 - coordinates)**2).sum(1).max() > self.skin**2): 

890 self.build(pbc, cell, coordinates) 

891 return True 

892 

893 return False 

894 

895 def build(self, pbc, cell, coordinates): 

896 """Build the list. 

897 

898 Coordinates are taken to be scaled or not according 

899 to self.use_scaled_positions. 

900 """ 

901 self.pbc = pbc = np.array(pbc, copy=True) 

902 self.cell = cell = Cell(cell) 

903 self.coordinates = coordinates = np.array(coordinates, copy=True) 

904 

905 if len(self.cutoffs) != len(coordinates): 

906 raise ValueError('Wrong number of cutoff radii: {0} != {1}' 

907 .format(len(self.cutoffs), len(coordinates))) 

908 

909 if len(self.cutoffs) > 0: 

910 rcmax = self.cutoffs.max() 

911 else: 

912 rcmax = 0.0 

913 

914 if self.use_scaled_positions: 

915 positions0 = cell.cartesian_positions(coordinates) 

916 else: 

917 positions0 = coordinates 

918 

919 rcell, op = minkowski_reduce(cell, pbc) 

920 positions = wrap_positions(positions0, rcell, pbc=pbc, eps=0) 

921 

922 natoms = len(positions) 

923 self.nneighbors = 0 

924 self.npbcneighbors = 0 

925 self.neighbors = [np.empty(0, int) for a in range(natoms)] 

926 self.displacements = [np.empty((0, 3), int) for a in range(natoms)] 

927 self.nupdates += 1 

928 if natoms == 0: 

929 return 

930 

931 N = [] 

932 ircell = np.linalg.pinv(rcell) 

933 for i in range(3): 

934 if self.pbc[i]: 

935 v = ircell[:, i] 

936 h = 1 / np.linalg.norm(v) 

937 n = int(2 * rcmax / h) + 1 

938 else: 

939 n = 0 

940 N.append(n) 

941 

942 tree = cKDTree(positions, copy_data=True) 

943 offsets = cell.scaled_positions(positions - positions0) 

944 offsets = offsets.round().astype(int) 

945 

946 for n1, n2, n3 in itertools.product(range(0, N[0] + 1), 

947 range(-N[1], N[1] + 1), 

948 range(-N[2], N[2] + 1)): 

949 if n1 == 0 and (n2 < 0 or n2 == 0 and n3 < 0): 

950 continue 

951 

952 displacement = (n1, n2, n3) @ rcell 

953 for a in range(natoms): 

954 

955 indices = tree.query_ball_point(positions[a] - displacement, 

956 r=self.cutoffs[a] + rcmax) 

957 if not len(indices): 

958 continue 

959 

960 indices = np.array(indices) 

961 delta = positions[indices] + displacement - positions[a] 

962 cutoffs = self.cutoffs[indices] + self.cutoffs[a] 

963 i = indices[np.linalg.norm(delta, axis=1) < cutoffs] 

964 if n1 == 0 and n2 == 0 and n3 == 0: 

965 if self.self_interaction: 

966 i = i[i >= a] 

967 else: 

968 i = i[i > a] 

969 

970 self.nneighbors += len(i) 

971 self.neighbors[a] = np.concatenate((self.neighbors[a], i)) 

972 

973 disp = (n1, n2, n3) @ op + offsets[i] - offsets[a] 

974 self.npbcneighbors += disp.any(1).sum() 

975 self.displacements[a] = np.concatenate((self.displacements[a], 

976 disp)) 

977 

978 if self.bothways: 

979 neighbors2 = [[] for a in range(natoms)] 

980 displacements2 = [[] for a in range(natoms)] 

981 for a in range(natoms): 

982 for b, disp in zip(self.neighbors[a], self.displacements[a]): 

983 neighbors2[b].append(a) 

984 displacements2[b].append(-disp) 

985 for a in range(natoms): 

986 nbs = np.concatenate((self.neighbors[a], neighbors2[a])) 

987 disp = np.array(list(self.displacements[a]) + displacements2[a]) 

988 # Force correct type and shape for case of no neighbors: 

989 self.neighbors[a] = nbs.astype(int) 

990 self.displacements[a] = disp.astype(int).reshape((-1, 3)) 

991 

992 if self.sorted: 

993 for a, i in enumerate(self.neighbors): 

994 mask = (i < a) 

995 if mask.any(): 

996 j = i[mask] 

997 offsets = self.displacements[a][mask] 

998 for b, offset in zip(j, offsets): 

999 self.neighbors[b] = np.concatenate( 

1000 (self.neighbors[b], [a])) 

1001 self.displacements[b] = np.concatenate( 

1002 (self.displacements[b], [-offset])) 

1003 mask = np.logical_not(mask) 

1004 self.neighbors[a] = self.neighbors[a][mask] 

1005 self.displacements[a] = self.displacements[a][mask] 

1006 

1007 def get_neighbors(self, a): 

1008 """Return neighbors of atom number a. 

1009 

1010 A list of indices and offsets to neighboring atoms is 

1011 returned. The positions of the neighbor atoms can be 

1012 calculated like this:: 

1013 

1014 indices, offsets = nl.get_neighbors(42) 

1015 for i, offset in zip(indices, offsets): 

1016 print(atoms.positions[i] + offset @ atoms.get_cell()) 

1017 

1018 Notice that if get_neighbors(a) gives atom b as a neighbor, 

1019 then get_neighbors(b) will not return a as a neighbor - unless 

1020 bothways=True was used.""" 

1021 

1022 return self.neighbors[a], self.displacements[a] 

1023 

1024 

1025class NeighborList: 

1026 """Neighbor list object. 

1027 

1028 cutoffs: list of float 

1029 List of cutoff radii - one for each atom. If the spheres 

1030 (defined by their cutoff radii) of two atoms overlap, they 

1031 will be counted as neighbors. See 

1032 :func:`~ase.neighborlist.natural_cutoffs` for an example on 

1033 how to get such a list. 

1034 

1035 skin: float 

1036 If no atom has moved more than the skin-distance since the 

1037 last call to the 

1038 :meth:`~ase.neighborlist.NeighborList.update()` method, then 

1039 the neighbor list can be reused. This will save some 

1040 expensive rebuilds of the list, but extra neighbors outside 

1041 the cutoff will be returned. 

1042 self_interaction: bool 

1043 Should an atom return itself as a neighbor? 

1044 bothways: bool 

1045 Return all neighbors. Default is to return only "half" of 

1046 the neighbors. 

1047 primitive: class 

1048 Define which implementation to use. Older and quadratically-scaling 

1049 :class:`~ase.neighborlist.PrimitiveNeighborList` or newer and 

1050 linearly-scaling :class:`~ase.neighborlist.NewPrimitiveNeighborList`. 

1051 

1052 Example:: 

1053 

1054 nl = NeighborList([2.3, 1.7]) 

1055 nl.update(atoms) 

1056 indices, offsets = nl.get_neighbors(0) 

1057 

1058 """ 

1059 

1060 def __init__(self, cutoffs, skin=0.3, sorted=False, self_interaction=True, 

1061 bothways=False, primitive=PrimitiveNeighborList): 

1062 self.nl = primitive(cutoffs, skin, sorted, 

1063 self_interaction=self_interaction, 

1064 bothways=bothways) 

1065 

1066 def update(self, atoms): 

1067 """ 

1068 See :meth:`ase.neighborlist.PrimitiveNeighborList.update` or 

1069 :meth:`ase.neighborlist.PrimitiveNeighborList.update`. 

1070 """ 

1071 return self.nl.update(atoms.pbc, atoms.get_cell(complete=True), 

1072 atoms.positions) 

1073 

1074 def get_neighbors(self, a): 

1075 """ 

1076 See :meth:`ase.neighborlist.PrimitiveNeighborList.get_neighbors` or 

1077 :meth:`ase.neighborlist.PrimitiveNeighborList.get_neighbors`. 

1078 """ 

1079 if self.nl.nupdates <= 0: 

1080 raise RuntimeError('Must call update(atoms) on your neighborlist ' 

1081 'first!') 

1082 

1083 return self.nl.get_neighbors(a) 

1084 

1085 def get_connectivity_matrix(self, sparse=True): 

1086 """ 

1087 See :func:`~ase.neighborlist.get_connectivity_matrix`. 

1088 """ 

1089 return get_connectivity_matrix(self.nl, sparse) 

1090 

1091 @property 

1092 def nupdates(self): 

1093 """Get number of updates.""" 

1094 return self.nl.nupdates 

1095 

1096 @property 

1097 def nneighbors(self): 

1098 """Get number of neighbors.""" 

1099 return self.nl.nneighbors 

1100 

1101 @property 

1102 def npbcneighbors(self): 

1103 """Get number of pbc neighbors.""" 

1104 return self.nl.npbcneighbors