Skip to content

Commit 7c690d8

Browse files
pfebrerzerothi
andauthored
Fix neighborlist for skewed cells (#976)
* Fix neighborlist for skewed cells * minor cleanups and fixed regression on test --------- Co-authored-by: Nick Papior <nickpapior@gmail.com>
1 parent 5d1d2be commit 7c690d8

File tree

3 files changed

+82
-24
lines changed

3 files changed

+82
-24
lines changed

changes/orphan.7.fix.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Fixed skewed cell neighbor search
2+
3+
For the linear neighbor search mechanism
4+
it needed to account for skewed cells
5+
when the binsize is very close to the inter-atomic
6+
distances.
7+
8+
(reported by @ialcon on Discord, thanks!)

src/sisl/geom/_neighbors/_finder.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def __init__(
144144
geometry: Geometry,
145145
R: Optional[Union[float, np.ndarray]] = None,
146146
overlap: bool = False,
147-
bin_size: Union[float, tuple[float, float, float]] = 2,
147+
bin_size: Union[float, tuple[float, float, float]] = 1,
148148
):
149149
self.setup(geometry, R=R, overlap=overlap, bin_size=bin_size)
150150

@@ -153,7 +153,7 @@ def setup(
153153
geometry: Optional[Geometry] = None,
154154
R: Optional[Union[float, np.ndarray]] = None,
155155
overlap: bool = None,
156-
bin_size: Union[float, tuple[float, float, float]] = 2,
156+
bin_size: Union[float, tuple[float, float, float]] = 1,
157157
):
158158
r"""Prepares everything for neighbor finding.
159159
@@ -184,14 +184,15 @@ def setup(
184184
is within the sphere of the first atom. Note that this implies that
185185
atom :math:`I` might be atom :math:`J`'s neighbor while the opposite is not true.
186186
bin_size :
187-
the factor for the radius to determine how large the bins are,
188-
optionally along each lattice vector.
189-
It can minimally be 2, meaning that the maximum radius to consider
190-
is twice the radius considered. For larger values, more atoms will be in
191-
each bin (and thus fewer bins).
192-
Hence, this value can be used to fine-tune the memory requirement by
193-
decreasing number of bins, at the cost of a bit more run-time searching
194-
bins.
187+
the neighbor finding algorithm divides space with a uniform grid.
188+
The minimal size of each grid bin depends on the radius for neighbor
189+
search and the lattice vectors (the more skewed the cell, the bigger
190+
the bins need to be).
191+
This argument accepts a factor by which to multiply the bin size,
192+
with 1 resulting in the minimum bin size.
193+
Larger values reduce the amount of memory needed by the algorithm, but
194+
will slow down the neighbor finding since there will be more potential
195+
neighbors for each atom.
195196
"""
196197
# Set the geometry. Copy it because we may need to modify the supercell size.
197198
if geometry is not None:
@@ -235,28 +236,47 @@ def setup(
235236
"All R values are 0 or less. Please provide some positive values"
236237
)
237238

239+
# Find the minimum length needed in each lattice vector direction
240+
# (the more skewed the cell is, the bigger the bins have to be).
241+
# The minimum bin size dictated by two lattice vectors (v1, v2) is:
242+
# 2 * (max_R / sin[angle(v1, v2)] )
243+
# The factor 2 max_R is taken out and applied after.
244+
min_bin_sizes = np.ones(3)
245+
lattice_norms = self.geometry.length
246+
for i in range(2):
247+
for j in range(i + 1, 3):
248+
249+
# Compute angle of vectors i and j
250+
scalar_prod = np.dot(self.geometry.cell[i], self.geometry.cell[j])
251+
cos_alpha = scalar_prod / (lattice_norms[i] * lattice_norms[j])
252+
253+
# Required bin size along vector i due to its relation with j
254+
# This ensures a positive number
255+
required_bin_size = 1 / (1 - cos_alpha**2) ** 0.5
256+
min_bin_sizes[i] = max(min_bin_sizes[i], required_bin_size)
257+
min_bin_sizes[j] = max(min_bin_sizes[j], required_bin_size)
258+
238259
bin_size = np.asarray(bin_size)
239-
if np.any(bin_size < 2):
260+
if np.any(bin_size < 1):
240261
raise ValueError(
241-
"The bin_size must be larger than 2 to only search in the "
242-
"neighboring bins. Please increase to a value >=2"
262+
"The bin_size argument must be larger than 1 so that the search"
263+
f"is performed on neighboring bins. Received {bin_size}"
243264
)
244265

245-
bin_size = max_R * bin_size
266+
# Correct for maximum distance in the bin-size (R on both sides)
267+
bin_size = min_bin_sizes * bin_size * 2 * max_R
246268

247269
# We add a small amount to bin_size to avoid ambiguities when
248270
# a position is exactly at the center of a bin.
249271
bin_size += 0.001
250272

251-
lattice_sizes = self.geometry.length
252-
253-
self._R_too_big = np.any(bin_size > lattice_sizes)
273+
self._R_too_big = np.any(bin_size > lattice_norms)
254274
if self._R_too_big:
255275
# This means that nsc must be at least 5.
256276

257277
# We round the amount of cells needed in each direction
258278
# to the closest next odd number.
259-
nsc = np.ceil(bin_size / lattice_sizes) // 2 * 2 + 1
279+
nsc = np.ceil(bin_size / lattice_norms) // 2 * 2 + 1
260280
# And then set it as the number of supercells.
261281
self.geometry.set_nsc(nsc.astype(int))
262282
if self._aux_R.ndim == 1:
@@ -272,13 +292,13 @@ def setup(
272292
)
273293

274294
# Recompute lattice sizes
275-
lattice_sizes = self._bins_geometry.length
295+
lattice_norms = self._bins_geometry.length
276296

277297
else:
278298
self._bins_geometry = self.geometry
279299

280300
# Get the number of bins along each cell direction.
281-
nbins_float = lattice_sizes / bin_size
301+
nbins_float = lattice_norms / bin_size
282302
self.nbins = tuple(np.floor(nbins_float).astype(int))
283303
self.total_nbins = np.prod(self.nbins)
284304

src/sisl/geom/_neighbors/tests/test_finder.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ def test_bin_sizes():
325325
geom = Geometry([[0, 0, 0], [1, 0, 0]], lattice=[2, 10, 10])
326326

327327
# We should have fewer bins along the first lattice vector
328-
n1 = NeighborFinder(geom, R=1.5, bin_size=2)
329-
n2 = NeighborFinder(geom, R=1.5, bin_size=4)
328+
n1 = NeighborFinder(geom, R=1.5, bin_size=1)
329+
n2 = NeighborFinder(geom, R=1.5, bin_size=2)
330330

331331
assert n1.total_nbins > n2.total_nbins
332332
# When the bin is bigger than the unit cell, this situation
@@ -336,8 +336,8 @@ def test_bin_sizes():
336336
assert n1.nbins[2] > n2.nbins[2]
337337

338338
# We should have the same number of bins the 2nd and 3rd lattice vectors
339-
n3 = NeighborFinder(geom, R=1.5, bin_size=2)
340-
n4 = NeighborFinder(geom, R=1.5, bin_size=(2, 4, 4))
339+
n3 = NeighborFinder(geom, R=1.5, bin_size=1)
340+
n4 = NeighborFinder(geom, R=1.5, bin_size=(1, 2, 2))
341341

342342
assert n3.nbins[0] == n4.nbins[0]
343343
assert n3.nbins[1] > n4.nbins[1]
@@ -352,3 +352,33 @@ def test_outside_box():
352352
# IT SHOULD BE SUPPORTED IN THE FUTURE
353353
with pytest.raises(ValueError):
354354
n = NeighborFinder(geom, R=1.1)
355+
356+
357+
def test_skewed_cell(pbc):
358+
"""Skewed cells are more complex for determining bin size,
359+
here we check that the finder works for a case where treating
360+
the skewed cell as an orthogonal cell will not work.
361+
"""
362+
R = 1.12
363+
atom_0_xyz = np.array([R - 0.004, 0, 0])
364+
atom_1_xyz = atom_0_xyz + [1, 0.5, 0] # Atom 1.118 Ang away
365+
366+
geom = Geometry(
367+
[atom_0_xyz, atom_1_xyz],
368+
lattice=[
369+
[(R + 0.01) * 6, 0, 0],
370+
[-3.5, (R + 0.01) * 6, 0],
371+
[0, 0, 20],
372+
],
373+
)
374+
set_pbc(geom, pbc)
375+
376+
neighfinder = NeighborFinder(geom, R=R, overlap=False)
377+
378+
neighfinder.assert_consistency()
379+
380+
# Check that the finder took into account the skewed cell
381+
# by dividing space in (2, 3, 8) instead of (3, 3, 8).
382+
assert neighfinder.nbins == (2, 3, 8)
383+
# Atoms should have one neighbor
384+
assert neighfinder.find_neighbors()[0].n_neighbors == 1

0 commit comments

Comments
 (0)