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
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
1import numpy as np
3from ase.geometry import find_mic
6def rotation_matrix_from_points(m0, m1):
7 """Returns a rigid transformation/rotation matrix that minimizes the
8 RMSD between two set of points.
10 m0 and m1 should be (3, npoints) numpy arrays with
11 coordinates as columns::
13 (x1 x2 x3 ... xN
14 y1 y2 y3 ... yN
15 z1 z2 z3 ... zN)
17 The centeroids should be set to origin prior to
18 computing the rotation matrix.
20 The rotation matrix is computed using quaternion
21 algebra as detailed in::
23 Melander et al. J. Chem. Theory Comput., 2015, 11,1055
24 """
26 v0 = np.copy(m0)
27 v1 = np.copy(m1)
29 # compute the rotation quaternion
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)
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]]
40 F = np.array(f)
42 w, V = np.linalg.eigh(F)
43 # eigenvector corresponding to the most
44 # positive eigenvalue
45 q = V[:, np.argmax(w)]
47 # Rotation matrix from the quaternion q
49 R = quaternion_to_matrix(q)
51 return R
54def quaternion_to_matrix(q):
55 """Returns a rotation matrix.
57 Computed from a unit quaternion Input as (4,) numpy array.
58 """
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)
73def minimize_rotation_and_translation(target, atoms):
74 """Minimize RMSD between atoms and target.
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::
79 Melander et al. J. Chem. Theory Comput., 2015, 11,1055
80 """
82 p = atoms.get_positions()
83 p0 = target.get_positions()
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?
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)
92 # add displacement without net translation
93 p = p0 + dp_min - np.mean(dp_min, axis=0)
94 R = np.eye(3) # null rotation
96 # centeroids to origin
97 c = np.mean(p, axis=0)
98 p -= c
99 c0 = np.mean(p0, axis=0)
100 p0 -= c0
102 if sum(atoms.pbc) == 0:
103 # Compute rotation matrix
104 R = rotation_matrix_from_points(p.T, p0.T)
106 atoms.set_positions(np.dot(p, R.T) + c0)