|
a |
|
b/tests/test_spatial.py |
|
|
1 |
import os |
|
|
2 |
|
|
|
3 |
import pytest |
|
|
4 |
from numpy.testing import (assert_almost_equal, |
|
|
5 |
assert_array_equal, |
|
|
6 |
assert_array_almost_equal) |
|
|
7 |
import numpy as np |
|
|
8 |
|
|
|
9 |
import oddt |
|
|
10 |
from oddt.spatial import (angle, |
|
|
11 |
dihedral, |
|
|
12 |
rmsd, |
|
|
13 |
distance, |
|
|
14 |
rotate) |
|
|
15 |
from .utils import shuffle_mol |
|
|
16 |
|
|
|
17 |
|
|
|
18 |
test_data_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
19 |
|
|
|
20 |
ASPIRIN_SDF = """ |
|
|
21 |
RDKit 3D |
|
|
22 |
|
|
|
23 |
13 13 0 0 0 0 0 0 0 0999 V2000 |
|
|
24 |
3.3558 -0.4356 -1.0951 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
25 |
2.0868 -0.6330 -0.3319 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
26 |
2.0284 -0.9314 0.8534 O 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
27 |
1.0157 -0.4307 -1.1906 O 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
28 |
-0.2079 -0.5332 -0.5260 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
29 |
-0.9020 -1.7350 -0.6775 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
30 |
-2.1373 -1.8996 -0.0586 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
31 |
-2.6805 -0.8641 0.6975 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
32 |
-1.9933 0.3419 0.8273 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
33 |
-0.7523 0.5244 0.2125 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
34 |
-0.0600 1.8264 0.3368 C 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
35 |
0.9397 2.1527 -0.2811 O 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
36 |
-0.6931 2.6171 1.2333 O 0 0 0 0 0 0 0 0 0 0 0 0 |
|
|
37 |
1 2 1 0 |
|
|
38 |
2 3 2 0 |
|
|
39 |
2 4 1 0 |
|
|
40 |
4 5 1 0 |
|
|
41 |
5 6 2 0 |
|
|
42 |
6 7 1 0 |
|
|
43 |
7 8 2 0 |
|
|
44 |
8 9 1 0 |
|
|
45 |
9 10 2 0 |
|
|
46 |
10 11 1 0 |
|
|
47 |
11 12 2 0 |
|
|
48 |
11 13 1 0 |
|
|
49 |
10 5 1 0 |
|
|
50 |
M END |
|
|
51 |
|
|
|
52 |
""" |
|
|
53 |
|
|
|
54 |
|
|
|
55 |
def test_angles(): |
|
|
56 |
"""Test spatial computations - angles""" |
|
|
57 |
|
|
|
58 |
# Angles |
|
|
59 |
assert_array_almost_equal(angle(np.array((1, 0, 0)), |
|
|
60 |
np.array((0, 0, 0)), |
|
|
61 |
np.array((0, 1, 0))), 90) |
|
|
62 |
|
|
|
63 |
assert_array_almost_equal(angle(np.array((1, 0, 0)), |
|
|
64 |
np.array((0, 0, 0)), |
|
|
65 |
np.array((1, 1, 0))), 45) |
|
|
66 |
|
|
|
67 |
# Check benzene ring angle |
|
|
68 |
mol = oddt.toolkit.readstring('smi', 'c1ccccc1') |
|
|
69 |
mol.make3D() |
|
|
70 |
assert_array_almost_equal(angle(mol.coords[0], |
|
|
71 |
mol.coords[1], |
|
|
72 |
mol.coords[2]), 120, decimal=1) |
|
|
73 |
|
|
|
74 |
|
|
|
75 |
def test_dihedral(): |
|
|
76 |
"""Test dihedrals""" |
|
|
77 |
# Dihedrals |
|
|
78 |
assert_array_almost_equal(dihedral(np.array((1, 0, 0)), |
|
|
79 |
np.array((0, 0, 0)), |
|
|
80 |
np.array((0, 1, 0)), |
|
|
81 |
np.array((1, 1, 0))), 0) |
|
|
82 |
|
|
|
83 |
assert_array_almost_equal(dihedral(np.array((1, 0, 0)), |
|
|
84 |
np.array((0, 0, 0)), |
|
|
85 |
np.array((0, 1, 0)), |
|
|
86 |
np.array((1, 1, 1))), -45) |
|
|
87 |
|
|
|
88 |
# Check benzene ring dihedral |
|
|
89 |
mol = oddt.toolkit.readstring('smi', 'c1ccccc1') |
|
|
90 |
mol.make3D() |
|
|
91 |
assert abs(dihedral(*mol.coords[:4])) < 2. |
|
|
92 |
|
|
|
93 |
|
|
|
94 |
def test_distance(): |
|
|
95 |
mol1 = oddt.toolkit.readstring('sdf', ASPIRIN_SDF) |
|
|
96 |
d = distance(mol1.coords, mol1.coords) |
|
|
97 |
n_atoms = len(mol1.coords) |
|
|
98 |
assert d.shape, (n_atoms == n_atoms) |
|
|
99 |
assert_array_equal(d[np.eye(len(mol1.coords), dtype=bool)], np.zeros(n_atoms)) |
|
|
100 |
|
|
|
101 |
d = distance(mol1.coords, mol1.coords.mean(axis=0).reshape(1, 3)) |
|
|
102 |
assert d.shape, (n_atoms == 1) |
|
|
103 |
ref_dist = [[3.556736951371501], [2.2058040428631056], [2.3896002745745415], |
|
|
104 |
[1.6231668718498249], [0.7772981740050453], [2.0694947503940004], |
|
|
105 |
[2.8600587871157184], [2.9014207091233857], [2.1850791695403564], |
|
|
106 |
[0.9413368403116871], [1.8581710293650173], [2.365629642108773], |
|
|
107 |
[2.975007440512798]] |
|
|
108 |
assert_array_almost_equal(d, ref_dist) |
|
|
109 |
|
|
|
110 |
|
|
|
111 |
def test_spatial(): |
|
|
112 |
"""Test spatial misc computations""" |
|
|
113 |
mol = oddt.toolkit.readstring('smi', 'c1ccccc1') |
|
|
114 |
mol.make3D() |
|
|
115 |
mol2 = mol.clone |
|
|
116 |
# Test rotation |
|
|
117 |
assert_almost_equal(mol2.coords, rotate(mol2.coords, np.pi, np.pi, np.pi)) |
|
|
118 |
|
|
|
119 |
# Rotate perpendicular to ring |
|
|
120 |
mol2.coords = rotate(mol2.coords, 0, 0, np.pi) |
|
|
121 |
|
|
|
122 |
# RMSD |
|
|
123 |
assert_almost_equal(rmsd(mol, mol2, method=None), 2.77, decimal=1) |
|
|
124 |
# Hungarian must be close to zero (RDKit is 0.3) |
|
|
125 |
assert_almost_equal(rmsd(mol, mol2, method='hungarian'), 0, decimal=0) |
|
|
126 |
# Minimized by symetry must close to zero |
|
|
127 |
assert_almost_equal(rmsd(mol, mol2, method='min_symmetry'), 0, decimal=0) |
|
|
128 |
|
|
|
129 |
|
|
|
130 |
def test_rmsd(): |
|
|
131 |
# pick one molecule from docked poses |
|
|
132 |
mols = list(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) |
|
|
133 |
mols = list(filter(lambda x: x.title == '312335', mols)) |
|
|
134 |
|
|
|
135 |
res = { |
|
|
136 |
'method=None': |
|
|
137 |
[4.7536, 2.5015, 2.7942, 1.1282, 0.7444, 1.6257, 4.7625, |
|
|
138 |
2.7168, 2.5504, 1.9304, 2.6201, 3.1742, 3.2254, 4.7785, |
|
|
139 |
4.8035, 7.8963, 2.2385, 4.8625, 3.2037], |
|
|
140 |
'method=hungarian': |
|
|
141 |
[0.9013, 1.0730, 1.0531, 1.0286, 0.7353, 1.4094, 0.5391, |
|
|
142 |
1.3297, 1.0881, 1.7796, 2.6064, 3.1577, 3.2135, 0.8126, |
|
|
143 |
1.2909, 2.5217, 2.0836, 1.8325, 3.1874], |
|
|
144 |
'method=min_symmetry': |
|
|
145 |
[0.9013, 1.0732, 1.0797, 1.0492, 0.7444, 1.6257, 0.5391, |
|
|
146 |
1.5884, 1.0935, 1.9304, 2.6201, 3.1742, 3.2254, 1.1513, |
|
|
147 |
1.5206, 2.5361, 2.2385, 1.971, 3.2037], |
|
|
148 |
} |
|
|
149 |
|
|
|
150 |
kwargs_grid = [{'method': None}, |
|
|
151 |
{'method': 'hungarian'}, |
|
|
152 |
{'method': 'min_symmetry'}] |
|
|
153 |
for kwargs in kwargs_grid: |
|
|
154 |
res_key = '_'.join('%s=%s' % (k, v) |
|
|
155 |
for k, v in sorted(kwargs.items())) |
|
|
156 |
assert_array_almost_equal([rmsd(mols[0], mol, **kwargs) |
|
|
157 |
for mol in mols[1:]], |
|
|
158 |
res[res_key], decimal=4) |
|
|
159 |
|
|
|
160 |
# test shuffled rmsd |
|
|
161 |
for _ in range(5): |
|
|
162 |
for kwargs in kwargs_grid: |
|
|
163 |
# dont use method=None in shuffled tests |
|
|
164 |
if kwargs['method'] is None: |
|
|
165 |
continue |
|
|
166 |
res_key = '_'.join('%s=%s' % (k, v) |
|
|
167 |
for k, v in sorted(kwargs.items())) |
|
|
168 |
assert_array_almost_equal([rmsd(mols[0], |
|
|
169 |
shuffle_mol(mol), |
|
|
170 |
**kwargs) |
|
|
171 |
for mol in mols[1:]], |
|
|
172 |
res[res_key], decimal=4) |
|
|
173 |
|
|
|
174 |
|
|
|
175 |
def test_rmsd_errors(): |
|
|
176 |
mol = oddt.toolkit.readstring('smi', 'c1ccccc1') |
|
|
177 |
mol.make3D() |
|
|
178 |
mol.addh() |
|
|
179 |
mol2 = next(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf'))) |
|
|
180 |
|
|
|
181 |
for method in [None, 'hungarian', 'min_symmetry']: |
|
|
182 |
with pytest.raises(ValueError, match='Unequal number of atoms'): |
|
|
183 |
rmsd(mol, mol2, method=method) |
|
|
184 |
|
|
|
185 |
for _ in range(5): |
|
|
186 |
with pytest.raises(ValueError, match='Unequal number of atoms'): |
|
|
187 |
rmsd(shuffle_mol(mol), shuffle_mol(mol2), method=method) |