Coverage for /builds/debichem-team/python-ase/ase/build/rotate.py: 100.00%

32 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +0000

1import numpy as np 

2 

3from ase.geometry import find_mic 

4 

5 

6def rotation_matrix_from_points(m0, m1): 

7 """Returns a rigid transformation/rotation matrix that minimizes the 

8 RMSD between two set of points. 

9 

10 m0 and m1 should be (3, npoints) numpy arrays with 

11 coordinates as columns:: 

12 

13 (x1 x2 x3 ... xN 

14 y1 y2 y3 ... yN 

15 z1 z2 z3 ... zN) 

16 

17 The centeroids should be set to origin prior to 

18 computing the rotation matrix. 

19 

20 The rotation matrix is computed using quaternion 

21 algebra as detailed in:: 

22 

23 Melander et al. J. Chem. Theory Comput., 2015, 11,1055 

24 """ 

25 

26 v0 = np.copy(m0) 

27 v1 = np.copy(m1) 

28 

29 # compute the rotation quaternion 

30 

31 R11, R22, R33 = np.sum(v0 * v1, axis=1) 

32 R12, R23, R31 = np.sum(v0 * np.roll(v1, -1, axis=0), axis=1) 

33 R13, R21, R32 = np.sum(v0 * np.roll(v1, -2, axis=0), axis=1) 

34 

35 f = [[R11 + R22 + R33, R23 - R32, R31 - R13, R12 - R21], 

36 [R23 - R32, R11 - R22 - R33, R12 + R21, R13 + R31], 

37 [R31 - R13, R12 + R21, -R11 + R22 - R33, R23 + R32], 

38 [R12 - R21, R13 + R31, R23 + R32, -R11 - R22 + R33]] 

39 

40 F = np.array(f) 

41 

42 w, V = np.linalg.eigh(F) 

43 # eigenvector corresponding to the most 

44 # positive eigenvalue 

45 q = V[:, np.argmax(w)] 

46 

47 # Rotation matrix from the quaternion q 

48 

49 R = quaternion_to_matrix(q) 

50 

51 return R 

52 

53 

54def quaternion_to_matrix(q): 

55 """Returns a rotation matrix. 

56 

57 Computed from a unit quaternion Input as (4,) numpy array. 

58 """ 

59 

60 q0, q1, q2, q3 = q 

61 R_q = [[q0**2 + q1**2 - q2**2 - q3**2, 

62 2 * (q1 * q2 - q0 * q3), 

63 2 * (q1 * q3 + q0 * q2)], 

64 [2 * (q1 * q2 + q0 * q3), 

65 q0**2 - q1**2 + q2**2 - q3**2, 

66 2 * (q2 * q3 - q0 * q1)], 

67 [2 * (q1 * q3 - q0 * q2), 

68 2 * (q2 * q3 + q0 * q1), 

69 q0**2 - q1**2 - q2**2 + q3**2]] 

70 return np.array(R_q) 

71 

72 

73def minimize_rotation_and_translation(target, atoms): 

74 """Minimize RMSD between atoms and target. 

75 

76 Rotate and translate atoms to best match target. Disregards rotation if PBC 

77 are found. Does not accound for changes in the cell. For more details, see:: 

78 

79 Melander et al. J. Chem. Theory Comput., 2015, 11,1055 

80 """ 

81 

82 p = atoms.get_positions() 

83 p0 = target.get_positions() 

84 

85 if sum(atoms.pbc) != 0: 

86 # maybe we can raise a warning about cell changes here since we don't 

87 # account for them? 

88 

89 # is this the best form of *find_mic version to use? 

90 dp_min, _dp_len = find_mic(p - p0, cell=target.cell, pbc=target.pbc) 

91 

92 # add displacement without net translation 

93 p = p0 + dp_min - np.mean(dp_min, axis=0) 

94 R = np.eye(3) # null rotation 

95 

96 # centeroids to origin 

97 c = np.mean(p, axis=0) 

98 p -= c 

99 c0 = np.mean(p0, axis=0) 

100 p0 -= c0 

101 

102 if sum(atoms.pbc) == 0: 

103 # Compute rotation matrix 

104 R = rotation_matrix_from_points(p.T, p0.T) 

105 

106 atoms.set_positions(np.dot(p, R.T) + c0)