Coverage for /builds/ase/ase/ase/geometry/distance.py: 100.00%

40 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3import numpy as np 

4 

5 

6def distance(s1, s2, permute=True): 

7 """Get the distance between two structures s1 and s2. 

8 

9 The distance is defined by the Frobenius norm of 

10 the spatial distance between all coordinates (see 

11 numpy.linalg.norm for the definition). 

12 

13 permute: minimise the distance by 'permuting' same elements 

14 """ 

15 

16 s1 = s1.copy() 

17 s2 = s2.copy() 

18 for s in [s1, s2]: 

19 s.translate(-s.get_center_of_mass()) 

20 s2pos = 1. * s2.get_positions() 

21 

22 def align(struct, xaxis='x', yaxis='y'): 

23 """Align moments of inertia with the coordinate system.""" 

24 Is, Vs = struct.get_moments_of_inertia(True) 

25 IV = list(zip(Is, Vs)) 

26 IV.sort(key=lambda x: x[0]) 

27 struct.rotate(IV[0][1], xaxis) 

28 

29 Is, Vs = struct.get_moments_of_inertia(True) 

30 IV = list(zip(Is, Vs)) 

31 IV.sort(key=lambda x: x[0]) 

32 struct.rotate(IV[1][1], yaxis) 

33 

34 align(s1) 

35 

36 def dd(s1, s2, permute): 

37 if permute: 

38 s2 = s2.copy() 

39 dist = 0 

40 for a in s1: 

41 imin = None 

42 dmin = np.inf 

43 for i, b in enumerate(s2): 

44 if a.symbol == b.symbol: 

45 d = np.sum((a.position - b.position)**2) 

46 if d < dmin: 

47 dmin = d 

48 imin = i 

49 dist += dmin 

50 s2.pop(imin) 

51 return np.sqrt(dist) 

52 else: 

53 return np.linalg.norm(s1.get_positions() - s2.get_positions()) 

54 

55 dists = [] 

56 # principles 

57 for x, y in zip(['x', '-x', 'x', '-x'], ['y', 'y', '-y', '-y']): 

58 s2.set_positions(s2pos) 

59 align(s2, x, y) 

60 dists.append(dd(s1, s2, permute)) 

61 

62 return min(dists)