Spaces:
Running
Running
File size: 8,124 Bytes
4320faf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | """
Offline tests for AutoDock Vina 1.2.7 log parsing (Phase 3e).
The fixture log below is a literal AutoDock Vina v1.2.7 run captured from
production (docking job 5bb69899-0f1c-451c-8c01-df310ea5abf2). The parser
must decode the header, metadata, and the `mode | affinity | rmsd l.b.| rmsd u.b.`
table exactly as Vina 1.2.7 emits it.
"""
from app.tools.docking import parse_vina_log, _parse_vina_poses
import pytest
REAL_VINA_127_LOG = """AutoDock Vina v1.2.7
#################################################################
# If you used AutoDock Vina in your work, please cite: #
# #
# J. Eberhardt, D. Santos-Martins, A. F. Tillack, and S. Forli #
# AutoDock Vina 1.2.0: New Docking Methods, Expanded Force #
# Field, and Python Bindings, J. Chem. Inf. Model. (2021) #
# DOI 10.1021/acs.jcim.1c00203 #
# #
# O. Trott, A. J. Olson, #
# AutoDock Vina: improving the speed and accuracy of docking #
# with a new scoring function, efficient optimization and #
# multithreading, J. Comp. Chem. (2010) #
# DOI 10.1002/jcc.21334 #
# #
# Please see https://github.com/ccsb-scripps/AutoDock-Vina for #
# more information. #
#################################################################
Scoring function : vina
Rigid receptor: /tmp/tmp31jjxdut/protein.pdbqt
Ligand: /tmp/tmp31jjxdut/ligand.pdbqt
Grid center: X 2 Y 2 Z 2
Grid size : X 20 Y 20 Z 20
Grid space : 0.375
Exhaustiveness: 8
CPU: 0
Verbosity: 1
Computing Vina grid ... done.
Performing docking (random seed: 1431381492) ...
0% 10 20 30 40 50 60 70 80 90 100%
|----|----|----|----|----|----|----|----|----|----|
***************************************************
mode | affinity | dist from best mode
| (kcal/mol) | rmsd l.b.| rmsd u.b.
-----+------------+----------+----------
1 0 0 0
2 0 6.008 8.028
3 0 5.276 6.043
4 0 5.028 6.856
5 0 9.301 9.946
6 0 13.25 15.24
7 0.0008125 13.16 14.62
8 0.0008125 8.006 8.726
9 0.0008125 15.62 17.15
"""
# A synthetic negative-affinity log (typical real docking run) to confirm
# scientific notation + negative affinities parse correctly.
NEGATIVE_AFFINITY_LOG = """AutoDock Vina v1.2.5
Grid center: X -3.5 Y 12.75 Z 41.25
Grid size : X 24 Y 24 Z 24
Exhaustiveness: 16
Performing docking (random seed: 123456789) ...
mode | affinity | dist from best mode
| (kcal/mol) | rmsd l.b.| rmsd u.b.
-----+------------+----------+----------
1 -8.754 0.000 0.000
2 -8.5e+00 1.234 2.345
3 -7.912 3.456 4.567
"""
class TestParseVinaLogReal:
def test_version_detected(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert parsed["vina_version"] == "1.2.7"
def test_grid_center_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert parsed["grid_center"] == [2.0, 2.0, 2.0]
def test_grid_size_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert parsed["grid_size"] == [20.0, 20.0, 20.0]
def test_exhaustiveness_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert parsed["exhaustiveness"] == 8
def test_random_seed_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert parsed["random_seed"] == 1431381492
def test_negative_random_seed(self):
log = REAL_VINA_127_LOG.replace(
"random seed: 1431381492", "random seed: -1704811452"
)
parsed = parse_vina_log(log)
assert parsed["random_seed"] == -1704811452
def test_scientific_notation_modes(self):
log = REAL_VINA_127_LOG.replace(
" 7 0.0008125 13.16 14.62",
" 7 7.81E-06 13.16 14.62",
)
parsed = parse_vina_log(log)
mode7 = next(m for m in parsed["modes"] if m["model"] == 7)
assert mode7["affinity"] == pytest.approx(7.81e-06)
def test_nine_modes_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
assert len(parsed["modes"]) == 9
def test_mode_one_rmsd_zero(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
mode1 = parsed["modes"][0]
assert mode1["model"] == 1
assert mode1["rmsd_lb"] == 0.0
assert mode1["rmsd_ub"] == 0.0
def test_decimal_affinity_parsed(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
mode7 = next(m for m in parsed["modes"] if m["model"] == 7)
assert mode7["affinity"] == pytest.approx(0.0008125)
def test_mode_two_rmsd_values(self):
parsed = parse_vina_log(REAL_VINA_127_LOG)
mode2 = next(m for m in parsed["modes"] if m["model"] == 2)
assert mode2["rmsd_lb"] == pytest.approx(6.008)
assert mode2["rmsd_ub"] == pytest.approx(8.028)
class TestParseVinaLogNegative:
def test_negative_affinity(self):
parsed = parse_vina_log(NEGATIVE_AFFINITY_LOG)
assert parsed["vina_version"] == "1.2.5"
assert len(parsed["modes"]) == 3
assert parsed["modes"][0]["affinity"] == pytest.approx(-8.754)
assert parsed["modes"][0]["rmsd_lb"] == 0.0
assert parsed["modes"][0]["rmsd_ub"] == 0.0
def test_scientific_notation(self):
parsed = parse_vina_log(NEGATIVE_AFFINITY_LOG)
assert parsed["modes"][1]["affinity"] == pytest.approx(-8.5)
def test_negative_grid_center(self):
parsed = parse_vina_log(NEGATIVE_AFFINITY_LOG)
assert parsed["grid_center"] == [-3.5, 12.75, 41.25]
def test_random_seed(self):
parsed = parse_vina_log(NEGATIVE_AFFINITY_LOG)
assert parsed["random_seed"] == 123456789
def test_exhaustiveness(self):
parsed = parse_vina_log(NEGATIVE_AFFINITY_LOG)
assert parsed["exhaustiveness"] == 16
class TestParseVinaEmpty:
def test_empty_log(self):
parsed = parse_vina_log("")
assert parsed["vina_version"] == ""
assert parsed["modes"] == []
assert parsed["random_seed"] is None
def test_no_table_log(self):
parsed = parse_vina_log("AutoDock Vina v1.2.7\nScoring function : vina\n")
assert parsed["vina_version"] == "1.2.7"
assert parsed["modes"] == []
class TestParseVinaPoses:
def test_poses_carry_rmsd(self):
output_pdbqt = (
"MODEL 1\n"
"HETATM 1 C LIG A 1 1.000 2.000 3.000 1.00 0.00\n"
"ENDMDL\n"
"MODEL 2\n"
"HETATM 1 C LIG A 1 2.000 3.000 4.000 1.00 0.00\n"
"HETATM 2 N LIG A 1 2.500 3.500 4.500 1.00 0.00\n"
"ENDMDL\n"
"END\n"
)
poses = _parse_vina_poses(output_pdbqt, REAL_VINA_127_LOG)
by_model = {p["model"]: p for p in poses}
assert by_model[1]["rmsd_lb"] == 0.0
assert by_model[1]["rmsd_ub"] == 0.0
assert by_model[2]["rmsd_lb"] == pytest.approx(6.008)
assert by_model[2]["rmsd_ub"] == pytest.approx(8.028)
assert by_model[1]["atoms"] == 1
assert by_model[2]["atoms"] == 2
assert by_model[2]["affinity"] == 0.0
def test_poses_missing_mode_has_null_rmsd(self):
output_pdbqt = (
"MODEL 42\n"
"HETATM 1 C LIG A 1 1.000 2.000 3.000 1.00 0.00\n"
"ENDMDL\n"
"END\n"
)
poses = _parse_vina_poses(output_pdbqt, REAL_VINA_127_LOG)
assert len(poses) == 1
assert poses[0]["rmsd_lb"] is None
assert poses[0]["rmsd_ub"] is None
assert poses[0]["affinity"] is None
|