pyjess 0.5.1__cp312-cp312-macosx_11_0_arm64.whl → 0.6.0__cp312-cp312-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyjess might be problematic. Click here for more details.
- pyjess/_jess.cpython-312-darwin.so +0 -0
- pyjess/_jess.pyi +3 -0
- pyjess/_jess.pyx +95 -37
- pyjess/tests/__init__.py +2 -0
- pyjess/tests/test_hit.py +33 -0
- pyjess/tests/test_jess.py +73 -2
- {pyjess-0.5.1.dist-info → pyjess-0.6.0.dist-info}/METADATA +29 -11
- {pyjess-0.5.1.dist-info → pyjess-0.6.0.dist-info}/RECORD +10 -9
- {pyjess-0.5.1.dist-info → pyjess-0.6.0.dist-info}/WHEEL +1 -1
- {pyjess-0.5.1.dist-info → pyjess-0.6.0.dist-info}/licenses/COPYING +0 -0
|
Binary file
|
pyjess/_jess.pyi
CHANGED
|
@@ -183,6 +183,8 @@ class Query(Generic[_T], Iterator[Hit[_T]]):
|
|
|
183
183
|
def __next__(self) -> Hit[_T]: ...
|
|
184
184
|
|
|
185
185
|
class Hit(Generic[_T]):
|
|
186
|
+
def __getstate__(self) -> Dict[str, object]: ...
|
|
187
|
+
def __setstate__(self, state: Dict[str, object]) -> None: ...
|
|
186
188
|
@property
|
|
187
189
|
def rmsd(self) -> float: ...
|
|
188
190
|
@property
|
|
@@ -219,4 +221,5 @@ class Jess(Generic[_T], Sequence[_T]):
|
|
|
219
221
|
max_candidates: int = 1000,
|
|
220
222
|
ignore_chain: bool = False,
|
|
221
223
|
best_match: bool = False,
|
|
224
|
+
reorder: bool = True,
|
|
222
225
|
) -> Query[_T]: ...
|
pyjess/_jess.pyx
CHANGED
|
@@ -329,8 +329,9 @@ cdef class Atom:
|
|
|
329
329
|
atom metadata from.
|
|
330
330
|
|
|
331
331
|
"""
|
|
332
|
-
cdef
|
|
333
|
-
cdef
|
|
332
|
+
cdef const unsigned char* s
|
|
333
|
+
cdef bytearray b
|
|
334
|
+
cdef Atom atom
|
|
334
335
|
|
|
335
336
|
if isinstance(text, str):
|
|
336
337
|
b = bytearray(text, 'utf-8')
|
|
@@ -339,14 +340,15 @@ cdef class Atom:
|
|
|
339
340
|
if not b.endswith(b'\n'):
|
|
340
341
|
b.append(b'\n')
|
|
341
342
|
b.append(b'\0')
|
|
343
|
+
s = b
|
|
342
344
|
|
|
343
345
|
atom = cls.__new__(cls)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
346
|
+
with nogil:
|
|
347
|
+
atom._atom = <_Atom*> malloc(sizeof(_Atom))
|
|
348
|
+
if atom._atom == NULL:
|
|
349
|
+
raise MemoryError("Failed to allocate atom")
|
|
350
|
+
if not jess.atom.Atom_parse(atom._atom, <const char*> s):
|
|
351
|
+
raise ValueError(f"Failed to parse atom: {text!r}")
|
|
350
352
|
|
|
351
353
|
return atom
|
|
352
354
|
|
|
@@ -1205,7 +1207,7 @@ cdef class Query:
|
|
|
1205
1207
|
cdef bint _rewind(self) noexcept nogil:
|
|
1206
1208
|
self._partial = True
|
|
1207
1209
|
|
|
1208
|
-
cdef int _copy_atoms(self, _Template* tpl, Hit hit) except -1 nogil:
|
|
1210
|
+
cdef int _copy_atoms(self, const _Template* tpl, Hit hit) except -1 nogil:
|
|
1209
1211
|
cdef _Atom** atoms = jess.jess.JessQuery_atoms(self._jq)
|
|
1210
1212
|
cdef int count = tpl.count(tpl)
|
|
1211
1213
|
|
|
@@ -1216,6 +1218,15 @@ cdef class Query:
|
|
|
1216
1218
|
memcpy(&hit._atoms[i], atoms[i], sizeof(_Atom))
|
|
1217
1219
|
return count
|
|
1218
1220
|
|
|
1221
|
+
cdef int _copy_superposition(self, _Superposition* sup, Hit hit) noexcept nogil:
|
|
1222
|
+
cdef const double* M = jess.super.Superposition_rotation(sup)
|
|
1223
|
+
cdef const double* c = jess.super.Superposition_centroid(sup, 0)
|
|
1224
|
+
cdef const double* v = jess.super.Superposition_centroid(sup, 1)
|
|
1225
|
+
memcpy(hit._rotation, M, 9*sizeof(double))
|
|
1226
|
+
memcpy(hit._centre[0], c, 3*sizeof(double))
|
|
1227
|
+
memcpy(hit._centre[1], v, 3*sizeof(double))
|
|
1228
|
+
return 0
|
|
1229
|
+
|
|
1219
1230
|
def __next__(self):
|
|
1220
1231
|
assert self._jq is not NULL
|
|
1221
1232
|
|
|
@@ -1227,11 +1238,11 @@ cdef class Query:
|
|
|
1227
1238
|
cdef Hit hit = Hit.__new__(Hit)
|
|
1228
1239
|
|
|
1229
1240
|
# prepare the hit to be returned
|
|
1230
|
-
hit._sup = NULL
|
|
1231
|
-
hit._atoms = NULL
|
|
1232
1241
|
hit.rmsd = INFINITY
|
|
1242
|
+
hit._atoms = NULL
|
|
1233
1243
|
hit._molecule = self.molecule
|
|
1234
1244
|
hit_tpl = NULL
|
|
1245
|
+
hit_found = False
|
|
1235
1246
|
|
|
1236
1247
|
# search the next hit without the GIL to allow parallel queries.
|
|
1237
1248
|
with nogil:
|
|
@@ -1240,14 +1251,13 @@ cdef class Query:
|
|
|
1240
1251
|
# was obtained with the current template and not with the
|
|
1241
1252
|
# previous one
|
|
1242
1253
|
tpl = jess.jess.JessQuery_template(self._jq)
|
|
1243
|
-
if
|
|
1254
|
+
if hit_found and hit_tpl != tpl:
|
|
1244
1255
|
self._rewind()
|
|
1245
1256
|
break
|
|
1246
1257
|
|
|
1247
1258
|
# load superposition and compute RMSD for the current iteration
|
|
1248
1259
|
sup = jess.jess.JessQuery_superposition(self._jq)
|
|
1249
1260
|
rmsd = jess.super.Superposition_rmsd(sup)
|
|
1250
|
-
keep_sup = False
|
|
1251
1261
|
|
|
1252
1262
|
# NB(@althonos): we don't need to compute the E-value to get the
|
|
1253
1263
|
# best match by molecule/template pair since the
|
|
@@ -1272,22 +1282,20 @@ cdef class Query:
|
|
|
1272
1282
|
stacklevel=2,
|
|
1273
1283
|
)
|
|
1274
1284
|
else:
|
|
1275
|
-
if hit._sup != NULL:
|
|
1276
|
-
jess.super.Superposition_free(hit._sup)
|
|
1277
1285
|
self._copy_atoms(tpl, hit)
|
|
1278
|
-
|
|
1286
|
+
self._copy_superposition(sup, hit)
|
|
1279
1287
|
hit.rmsd = rmsd
|
|
1280
1288
|
hit_tpl = tpl
|
|
1289
|
+
hit_found = True
|
|
1281
1290
|
|
|
1282
1291
|
# free superposition items that are not used in a hit, and
|
|
1283
1292
|
# return hits immediately if we are not in best match mode
|
|
1284
1293
|
self._candidates += 1
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
if hit._sup != NULL and not self.best_match:
|
|
1294
|
+
jess.super.Superposition_free(sup)
|
|
1295
|
+
if hit_found and not self.best_match:
|
|
1288
1296
|
break
|
|
1289
1297
|
|
|
1290
|
-
if
|
|
1298
|
+
if not hit_found:
|
|
1291
1299
|
raise StopIteration
|
|
1292
1300
|
|
|
1293
1301
|
# get the template object for the hit
|
|
@@ -1305,7 +1313,8 @@ cdef class Hit:
|
|
|
1305
1313
|
molecule (`~pyjess.Molecule`): The query molecule.
|
|
1306
1314
|
|
|
1307
1315
|
"""
|
|
1308
|
-
cdef
|
|
1316
|
+
cdef double[9] _rotation
|
|
1317
|
+
cdef double[2][3] _centre
|
|
1309
1318
|
cdef _Atom* _atoms
|
|
1310
1319
|
|
|
1311
1320
|
cdef readonly double rmsd
|
|
@@ -1313,19 +1322,49 @@ cdef class Hit:
|
|
|
1313
1322
|
cdef Molecule _molecule
|
|
1314
1323
|
|
|
1315
1324
|
def __dealloc__(self):
|
|
1316
|
-
jess.super.Superposition_free(self._sup)
|
|
1317
1325
|
free(self._atoms)
|
|
1318
1326
|
|
|
1327
|
+
def __getstate__(self):
|
|
1328
|
+
return {
|
|
1329
|
+
"rotation": list(self._rotation),
|
|
1330
|
+
"centre": list(self._centre),
|
|
1331
|
+
"atoms": self.atoms(transform=False),
|
|
1332
|
+
"rmsd": self.rmsd,
|
|
1333
|
+
"template": self.template,
|
|
1334
|
+
"molecule": self.molecule(transform=False),
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
def __setstate__(self, state):
|
|
1338
|
+
cdef size_t i
|
|
1339
|
+
cdef size_t count
|
|
1340
|
+
cdef Atom atom
|
|
1341
|
+
|
|
1342
|
+
self.rmsd = state["rmsd"]
|
|
1343
|
+
self.template = state["template"]
|
|
1344
|
+
self._molecule = state["molecule"]
|
|
1345
|
+
self._rotation = state["rotation"]
|
|
1346
|
+
self._centre = state["centre"]
|
|
1347
|
+
|
|
1348
|
+
# check number of atoms is consistent
|
|
1349
|
+
count = len(self.template)
|
|
1350
|
+
if len(state["atoms"]) != count:
|
|
1351
|
+
raise ValueError(f"unexpected number of atoms: {len(state['atoms'])!r} (expected {count!r})")
|
|
1352
|
+
# allocate or reallocate memory for atoms
|
|
1353
|
+
self._atoms = <_Atom*> realloc(self._atoms, count * sizeof(_Atom))
|
|
1354
|
+
if self._atoms is NULL:
|
|
1355
|
+
raise MemoryError("Failed to allocate hit atoms")
|
|
1356
|
+
# copy atom data
|
|
1357
|
+
for i, atom in enumerate(state["atoms"]):
|
|
1358
|
+
memcpy(&self._atoms[i], atom._atom, sizeof(_Atom))
|
|
1359
|
+
|
|
1319
1360
|
@property
|
|
1320
1361
|
def determinant(self):
|
|
1321
1362
|
"""`float`: The determinant of the rotation matrix.
|
|
1322
1363
|
"""
|
|
1323
|
-
|
|
1324
|
-
cdef const double* p
|
|
1364
|
+
cdef const double* p = self._rotation
|
|
1325
1365
|
cdef double det = 0.0
|
|
1326
1366
|
|
|
1327
1367
|
with nogil:
|
|
1328
|
-
p = jess.super.Superposition_rotation(self._sup)
|
|
1329
1368
|
det += p[0] * (p[4] * p[8] - p[5] * p[7])
|
|
1330
1369
|
det -= p[1] * (p[3] * p[8] - p[5] * p[6])
|
|
1331
1370
|
det += p[2] * (p[3] * p[7] - p[4] * p[6])
|
|
@@ -1369,7 +1408,6 @@ cdef class Hit:
|
|
|
1369
1408
|
|
|
1370
1409
|
"""
|
|
1371
1410
|
assert self.template._tpl is not NULL
|
|
1372
|
-
assert self._sup is not NULL
|
|
1373
1411
|
|
|
1374
1412
|
cdef Atom atom
|
|
1375
1413
|
cdef int i
|
|
@@ -1378,21 +1416,23 @@ cdef class Hit:
|
|
|
1378
1416
|
cdef int count = self.template._tpl.count(self.template._tpl)
|
|
1379
1417
|
cdef list atoms = []
|
|
1380
1418
|
|
|
1381
|
-
cdef const double* M =
|
|
1382
|
-
cdef const double* c =
|
|
1383
|
-
cdef const double* v =
|
|
1419
|
+
cdef const double* M = self._rotation
|
|
1420
|
+
cdef const double* c = self._centre[0]
|
|
1421
|
+
cdef const double* v = self._centre[1]
|
|
1384
1422
|
|
|
1385
1423
|
for k in range(count):
|
|
1386
|
-
|
|
1387
1424
|
atom = Atom.__new__(Atom)
|
|
1388
|
-
atom._atom = <_Atom*> malloc(sizeof(_Atom))
|
|
1389
|
-
memcpy(atom._atom, &self._atoms[k], sizeof(_Atom))
|
|
1390
|
-
|
|
1391
1425
|
if transform:
|
|
1426
|
+
atom._atom = <_Atom*> malloc(sizeof(_Atom))
|
|
1427
|
+
memcpy(atom._atom, &self._atoms[k], sizeof(_Atom))
|
|
1392
1428
|
for i in range(3):
|
|
1393
1429
|
atom._atom.x[i] = v[i]
|
|
1394
1430
|
for j in range(3):
|
|
1395
1431
|
atom._atom.x[i] += M[3*i + j] * (self._atoms[k].x[j] - c[j])
|
|
1432
|
+
else:
|
|
1433
|
+
atom.owned = True
|
|
1434
|
+
atom.owner = self
|
|
1435
|
+
atom._atom = &self._atoms[k]
|
|
1396
1436
|
|
|
1397
1437
|
atoms.append(atom)
|
|
1398
1438
|
|
|
@@ -1413,16 +1453,15 @@ cdef class Hit:
|
|
|
1413
1453
|
|
|
1414
1454
|
"""
|
|
1415
1455
|
assert self.template._tpl is not NULL
|
|
1416
|
-
assert self._sup is not NULL
|
|
1417
1456
|
|
|
1418
1457
|
cdef _Atom* atom
|
|
1419
1458
|
cdef Molecule mol
|
|
1420
1459
|
cdef size_t i
|
|
1421
1460
|
cdef size_t j
|
|
1422
1461
|
cdef size_t k
|
|
1423
|
-
cdef const double* M
|
|
1424
|
-
cdef const double* c
|
|
1425
|
-
cdef const double* v
|
|
1462
|
+
cdef const double* M = self._rotation
|
|
1463
|
+
cdef const double* c = self._centre[0]
|
|
1464
|
+
cdef const double* v = self._centre[1]
|
|
1426
1465
|
|
|
1427
1466
|
if not transform:
|
|
1428
1467
|
return self._molecule
|
|
@@ -1545,6 +1584,7 @@ cdef class Jess:
|
|
|
1545
1584
|
int max_candidates = 1000,
|
|
1546
1585
|
bint ignore_chain = False,
|
|
1547
1586
|
bint best_match = False,
|
|
1587
|
+
bint reorder = True,
|
|
1548
1588
|
):
|
|
1549
1589
|
"""Scan for templates matching the given molecule.
|
|
1550
1590
|
|
|
@@ -1563,10 +1603,27 @@ cdef class Jess:
|
|
|
1563
1603
|
the atoms to match.
|
|
1564
1604
|
best_match (`bool`): Pass `True` to return only the best match
|
|
1565
1605
|
to each template.
|
|
1606
|
+
reorder (`bool`): Whether to enable template atom reordering
|
|
1607
|
+
to accelerate matching in the scanner algorithm. Pass
|
|
1608
|
+
`False` to reverse to the original, slower algorithm
|
|
1609
|
+
which matches atoms in the same order as they appear in
|
|
1610
|
+
the template, at the cost
|
|
1566
1611
|
|
|
1567
1612
|
Returns:
|
|
1568
1613
|
`~pyjess.Query`: An iterator over the query hits.
|
|
1569
1614
|
|
|
1615
|
+
Caution:
|
|
1616
|
+
Since ``v0.6.0``, this function uses an optimized variant of
|
|
1617
|
+
the Jess scanning algorithm which minimized the number of steps
|
|
1618
|
+
needed to generate matches, by re-ordering the order the
|
|
1619
|
+
template atoms are iterated upon. Because of this change,
|
|
1620
|
+
the query may return *exactly* the same matches but in an order
|
|
1621
|
+
that *differs* from the original Jess version. If you really
|
|
1622
|
+
need results in the original order, set ``reorder`` to `False`.
|
|
1623
|
+
|
|
1624
|
+
.. versionadded:: 0.6.0
|
|
1625
|
+
The ``reorder`` argument, defaulting to `True`.
|
|
1626
|
+
|
|
1570
1627
|
"""
|
|
1571
1628
|
cdef Query query = Query.__new__(Query)
|
|
1572
1629
|
query.ignore_chain = ignore_chain
|
|
@@ -1580,5 +1637,6 @@ cdef class Jess:
|
|
|
1580
1637
|
molecule._mol,
|
|
1581
1638
|
distance_cutoff,
|
|
1582
1639
|
max_dynamic_distance,
|
|
1640
|
+
reorder,
|
|
1583
1641
|
)
|
|
1584
1642
|
return query
|
pyjess/tests/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from . import (
|
|
2
2
|
test_atom,
|
|
3
|
+
test_hit,
|
|
3
4
|
test_jess,
|
|
4
5
|
test_molecule,
|
|
5
6
|
test_template_atom,
|
|
@@ -8,6 +9,7 @@ from . import (
|
|
|
8
9
|
|
|
9
10
|
def load_tests(loader, suite, pattern):
|
|
10
11
|
suite.addTests(loader.loadTestsFromModule(test_atom))
|
|
12
|
+
suite.addTests(loader.loadTestsFromModule(test_hit))
|
|
11
13
|
suite.addTests(loader.loadTestsFromModule(test_jess))
|
|
12
14
|
suite.addTests(loader.loadTestsFromModule(test_molecule))
|
|
13
15
|
suite.addTests(loader.loadTestsFromModule(test_template_atom))
|
pyjess/tests/test_hit.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import unittest
|
|
3
|
+
import sys
|
|
4
|
+
import pickle
|
|
5
|
+
|
|
6
|
+
from .._jess import Template, Molecule, Jess
|
|
7
|
+
from .utils import files
|
|
8
|
+
from . import data
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestHit(unittest.TestCase):
|
|
12
|
+
|
|
13
|
+
@unittest.skipUnless(files, "importlib.resources not available")
|
|
14
|
+
@classmethod
|
|
15
|
+
def setUpClass(cls):
|
|
16
|
+
with files(data).joinpath("template_01.qry").open() as f:
|
|
17
|
+
template = Template.load(f)
|
|
18
|
+
jess = Jess([template])
|
|
19
|
+
with files(data).joinpath("pdb1lnb.pdb").open() as f:
|
|
20
|
+
molecule = Molecule.load(f)
|
|
21
|
+
|
|
22
|
+
cls.hit = next(jess.query(molecule, 1, 2, 2))
|
|
23
|
+
|
|
24
|
+
def test_pickle(self):
|
|
25
|
+
hit = pickle.loads(pickle.dumps(self.hit))
|
|
26
|
+
self.assertIsNot(self.hit, hit)
|
|
27
|
+
self.assertEqual(self.hit.rmsd, hit.rmsd)
|
|
28
|
+
self.assertEqual(self.hit.determinant, hit.determinant)
|
|
29
|
+
self.assertEqual(self.hit.evalue, hit.evalue)
|
|
30
|
+
self.assertEqual(self.hit.template, hit.template)
|
|
31
|
+
self.assertListEqual(self.hit.atoms(transform=True), hit.atoms(transform=True))
|
|
32
|
+
self.assertListEqual(self.hit.atoms(transform=False), hit.atoms(transform=False))
|
|
33
|
+
self.assertEqual(self.hit.molecule(transform=False), hit.molecule(transform=False))
|
pyjess/tests/test_jess.py
CHANGED
|
@@ -204,7 +204,7 @@ class TestJess(unittest.TestCase):
|
|
|
204
204
|
self.assertAlmostEqual(hit.log_evalue, -2.04, places=1)
|
|
205
205
|
|
|
206
206
|
@unittest.skipUnless(files, "importlib.resources not available")
|
|
207
|
-
def
|
|
207
|
+
def test_mcsa_query_no_reorder(self):
|
|
208
208
|
with files(data).joinpath("1.3.3.tpl").open() as f:
|
|
209
209
|
template = Template.load(f)
|
|
210
210
|
jess = Jess([template])
|
|
@@ -213,7 +213,7 @@ class TestJess(unittest.TestCase):
|
|
|
213
213
|
with files(data).joinpath("1AMY+1.3.3.txt").open() as f:
|
|
214
214
|
results = list(filter(None, f.read().split("REMARK")))
|
|
215
215
|
|
|
216
|
-
hits = list(jess.query(molecule, 2, 4, 4))
|
|
216
|
+
hits = list(jess.query(molecule, 2, 4, 4, reorder=False))
|
|
217
217
|
self.assertEqual(len(hits), len(results))
|
|
218
218
|
for hit, block in zip(hits, results):
|
|
219
219
|
self.assertIs(hit.template, template)
|
|
@@ -240,3 +240,74 @@ class TestJess(unittest.TestCase):
|
|
|
240
240
|
self.assertAlmostEqual(atom.occupancy, float(atom_line[55:61]), places=3)
|
|
241
241
|
self.assertAlmostEqual(atom.temperature_factor, float(atom_line[61:67]), places=3)
|
|
242
242
|
|
|
243
|
+
atoms = hit.atoms(transform=False)
|
|
244
|
+
self.assertEqual(len(atoms), len(atom_lines))
|
|
245
|
+
for atom, atom_line in zip(atoms, atom_lines):
|
|
246
|
+
self.assertEqual(atom.serial, int(atom_line[7:12]))
|
|
247
|
+
self.assertEqual(atom.name, atom_line[13:17].strip())
|
|
248
|
+
self.assertEqual(atom.residue_name, atom_line[17:21].strip())
|
|
249
|
+
self.assertEqual(atom.chain_id, atom_line[21:23].strip())
|
|
250
|
+
self.assertEqual(atom.residue_number, int(atom_line[23:27]))
|
|
251
|
+
self.assertAlmostEqual(atom.occupancy, float(atom_line[55:61]), places=3)
|
|
252
|
+
self.assertAlmostEqual(atom.temperature_factor, float(atom_line[61:67]), places=3)
|
|
253
|
+
|
|
254
|
+
@unittest.skipUnless(files, "importlib.resources not available")
|
|
255
|
+
def test_mcsa_query_reorder(self):
|
|
256
|
+
with files(data).joinpath("1.3.3.tpl").open() as f:
|
|
257
|
+
template = Template.load(f)
|
|
258
|
+
jess = Jess([template])
|
|
259
|
+
with files(data).joinpath("1AMY.pdb").open() as f:
|
|
260
|
+
molecule = Molecule.load(f)
|
|
261
|
+
with files(data).joinpath("1AMY+1.3.3.txt").open() as f:
|
|
262
|
+
results = list(filter(None, f.read().split("REMARK")))
|
|
263
|
+
|
|
264
|
+
hits = list(jess.query(molecule, 2, 4, 4, reorder=True))
|
|
265
|
+
self.assertEqual(len(hits), len(results))
|
|
266
|
+
|
|
267
|
+
# `reorder=True` means that we may get results in a different order
|
|
268
|
+
# to Jess, so we need to match the hits in the file by residue number
|
|
269
|
+
# to make sure we compare them consistently.
|
|
270
|
+
|
|
271
|
+
results_by_serials = {}
|
|
272
|
+
for block in results:
|
|
273
|
+
lines = block.strip().splitlines()
|
|
274
|
+
serials = tuple([ int(line.split()[1]) for line in lines[1:-1] ])
|
|
275
|
+
results_by_serials[serials] = block
|
|
276
|
+
|
|
277
|
+
for hit in hits:
|
|
278
|
+
self.assertIs(hit.template, template)
|
|
279
|
+
block = results_by_serials[tuple(atom.serial for atom in hit.atoms(False))]
|
|
280
|
+
|
|
281
|
+
lines = block.strip().splitlines()
|
|
282
|
+
query_id, rmsd, template_id, _, determinant, _, logE = lines[0].split()
|
|
283
|
+
self.assertEqual(query_id, "1AMY")
|
|
284
|
+
self.assertAlmostEqual(float(rmsd), hit.rmsd, places=3)
|
|
285
|
+
self.assertAlmostEqual(float(determinant), hit.determinant, places=1)
|
|
286
|
+
self.assertAlmostEqual(float(logE), hit.log_evalue, places=1)
|
|
287
|
+
|
|
288
|
+
atom_lines = lines[1:-1]
|
|
289
|
+
atoms = hit.atoms()
|
|
290
|
+
self.assertEqual(len(atoms), len(atom_lines))
|
|
291
|
+
for atom, atom_line in zip(atoms, atom_lines):
|
|
292
|
+
self.assertEqual(atom.serial, int(atom_line[7:12]))
|
|
293
|
+
self.assertEqual(atom.name, atom_line[13:17].strip())
|
|
294
|
+
self.assertEqual(atom.residue_name, atom_line[17:21].strip())
|
|
295
|
+
self.assertEqual(atom.chain_id, atom_line[21:23].strip())
|
|
296
|
+
self.assertEqual(atom.residue_number, int(atom_line[23:27]))
|
|
297
|
+
self.assertAlmostEqual(atom.x, float(atom_line[31:39]), places=3)
|
|
298
|
+
self.assertAlmostEqual(atom.y, float(atom_line[39:47]), places=3)
|
|
299
|
+
self.assertAlmostEqual(atom.z, float(atom_line[47:55]), places=3)
|
|
300
|
+
self.assertAlmostEqual(atom.occupancy, float(atom_line[55:61]), places=3)
|
|
301
|
+
self.assertAlmostEqual(atom.temperature_factor, float(atom_line[61:67]), places=3)
|
|
302
|
+
|
|
303
|
+
atoms = hit.atoms(transform=False)
|
|
304
|
+
self.assertEqual(len(atoms), len(atom_lines))
|
|
305
|
+
for atom, atom_line in zip(atoms, atom_lines):
|
|
306
|
+
self.assertEqual(atom.serial, int(atom_line[7:12]))
|
|
307
|
+
self.assertEqual(atom.name, atom_line[13:17].strip())
|
|
308
|
+
self.assertEqual(atom.residue_name, atom_line[17:21].strip())
|
|
309
|
+
self.assertEqual(atom.chain_id, atom_line[21:23].strip())
|
|
310
|
+
self.assertEqual(atom.residue_number, int(atom_line[23:27]))
|
|
311
|
+
self.assertAlmostEqual(atom.occupancy, float(atom_line[55:61]), places=3)
|
|
312
|
+
self.assertAlmostEqual(atom.temperature_factor, float(atom_line[61:67]), places=3)
|
|
313
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pyjess
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Cython bindings and Python interface to JESS, a 3D template matching software.
|
|
5
5
|
Keywords: bioinformatics,structure,template,matching
|
|
6
6
|
Author-Email: Martin Larralde <martin.larralde@embl.de>
|
|
@@ -93,7 +93,9 @@ during his PhD in the [Thornton group](https://www.ebi.ac.uk/research/thornton/)
|
|
|
93
93
|
PyJess is a Python module that provides bindings to Jess using
|
|
94
94
|
[Cython](https://cython.org/). It allows creating templates, querying them
|
|
95
95
|
with protein structures, and retrieving the hits using a Python API without
|
|
96
|
-
performing any external I/O.
|
|
96
|
+
performing any external I/O. It's also more than 10x faster than Jess thanks to
|
|
97
|
+
[algorithmic optimizations](https://pyjess.readthedocs.io/en/latest/guide/optimizations.html)
|
|
98
|
+
added to improve the original Jess code while producing consistent results.
|
|
97
99
|
|
|
98
100
|
|
|
99
101
|
## 🔧 Installing
|
|
@@ -127,7 +129,8 @@ Jess if you are using it in an academic work, for instance as:
|
|
|
127
129
|
|
|
128
130
|
## 💡 Example
|
|
129
131
|
|
|
130
|
-
Load
|
|
132
|
+
Load [`Template`](https://pyjess.readthedocs.io/en/latest/api/template.html#pyjess.Template)
|
|
133
|
+
objects to be used as references from different template files:
|
|
131
134
|
|
|
132
135
|
```python
|
|
133
136
|
import pathlib
|
|
@@ -135,11 +138,10 @@ import pyjess
|
|
|
135
138
|
|
|
136
139
|
templates = []
|
|
137
140
|
for path in sorted(pathlib.Path("vendor/jess/examples").glob("template_*.qry")):
|
|
138
|
-
|
|
139
|
-
templates.append(pyjess.Template.load(file, id=path.stem))
|
|
141
|
+
templates.append(pyjess.Template.load(path, id=path.stem))
|
|
140
142
|
```
|
|
141
143
|
|
|
142
|
-
Create a `Jess` instance and use it to query a molecule (a PDB structure)
|
|
144
|
+
Create a [`Jess`](https://pyjess.readthedocs.io/en/latest/api/jess.html#pyjess.Jess) instance and use it to query a [`Molecule`](https://pyjess.readthedocs.io/en/latest/api/molecule.html#pyjess.Molecule) (a PDB structure)
|
|
143
145
|
against the stored templates:
|
|
144
146
|
|
|
145
147
|
```python
|
|
@@ -161,9 +163,11 @@ for hit in query:
|
|
|
161
163
|
|
|
162
164
|
## 🧶 Thread-safety
|
|
163
165
|
|
|
164
|
-
Once a `Jess`
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
Once a [`Jess`](https://pyjess.readthedocs.io/en/latest/api/jess.html#pyjess.Jess)
|
|
167
|
+
instance has been created, the templates cannot be edited anymore,
|
|
168
|
+
making the [`Jess.query`](https://pyjess.readthedocs.io/en/latest/api/jess.html#pyjess.Jess.query) method re-entrant and thread-safe. This allows querying
|
|
169
|
+
several molecules against the same templates in parallel using e.g a
|
|
170
|
+
[`ThreadPool`](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.ThreadPool):
|
|
167
171
|
|
|
168
172
|
```python
|
|
169
173
|
molecules = []
|
|
@@ -177,8 +181,22 @@ with multiprocessing.ThreadPool() as pool:
|
|
|
177
181
|
*⚠️ Prior to PyJess `v0.2.1`, the Jess code was running some thread-unsafe operations which have now been patched.
|
|
178
182
|
If running Jess in parallel, make sure to use `v0.2.1` or later to use the code patched with re-entrant functions*.
|
|
179
183
|
|
|
180
|
-
|
|
184
|
+
## ⏱️ Benchmarks
|
|
181
185
|
|
|
186
|
+
The following table reports the runtime of PyJess to match $n=132$ protein
|
|
187
|
+
structures to the $m=7607$ templates of
|
|
188
|
+
[EnzyMM](https://github.com/RayHackett/enzymm), using $J=12$ threads to parallelize.
|
|
189
|
+
|
|
190
|
+
| Version | Runtime (s) | Match Speed (N * M / s * J) | Speedup |
|
|
191
|
+
| ----------- | ----------- | --------------------------- | ----------- |
|
|
192
|
+
| ``v0.4.2`` | 618.1 | 135.4 | N/A |
|
|
193
|
+
| ``v0.5.0`` | 586.3 | 142.7 | x1.05 |
|
|
194
|
+
| ``v0.5.1`` | 365.6 | 228.9 | x1.69 |
|
|
195
|
+
| ``v0.5.2`` | 327.2 | 255.7 | x1.88 |
|
|
196
|
+
| ``v0.6.0`` | 54.5 | 1535.4 | **x11.34** |
|
|
197
|
+
|
|
198
|
+
*Benchmarks were run on a quiet [i7-1255U](https://www.intel.com/content/www/us/en/products/sku/226259/intel-core-i71255u-processor-12m-cache-up-to-4-70-ghz/specifications.html) CPU running @4.70GHz with 10 physical cores / 12 logical
|
|
199
|
+
cores.*
|
|
182
200
|
|
|
183
201
|
## 💭 Feedback
|
|
184
202
|
|
|
@@ -211,7 +229,7 @@ This library is provided under the [MIT License](https://choosealicense.com/lice
|
|
|
211
229
|
*This project is in no way not affiliated, sponsored, or otherwise endorsed
|
|
212
230
|
by the JESS authors. It was developed
|
|
213
231
|
by [Martin Larralde](https://github.com/althonos/) during his PhD project
|
|
214
|
-
at the [
|
|
232
|
+
at the [Leiden University Medical Center](https://www.lumc.nl/en/) in
|
|
215
233
|
the [Zeller team](https://github.com/zellerlab).*
|
|
216
234
|
|
|
217
235
|
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
pyjess-0.
|
|
2
|
-
pyjess-0.
|
|
3
|
-
pyjess-0.
|
|
4
|
-
pyjess-0.
|
|
5
|
-
pyjess/_jess.cpython-312-darwin.so,sha256=
|
|
6
|
-
pyjess/_jess.pyi,sha256=
|
|
1
|
+
pyjess-0.6.0.dist-info/RECORD,,
|
|
2
|
+
pyjess-0.6.0.dist-info/WHEEL,sha256=a5hFNLnG478el62xhySpcm13yln7Jlz1BenyGRdoSlE,114
|
|
3
|
+
pyjess-0.6.0.dist-info/METADATA,sha256=OG5zbwpmxVLSmlQQ6xvyicRYKB6JrLBkDa-zhmWa6js,12882
|
|
4
|
+
pyjess-0.6.0.dist-info/licenses/COPYING,sha256=gLCfHtBLTrghVX7GkpmZqoozWMNN46502m_OUiYy01Y,1103
|
|
5
|
+
pyjess/_jess.cpython-312-darwin.so,sha256=Jxe6NYIP8tP1Q2PUjRMrBn3U64zybQZSJsg_cDbg1UY,350888
|
|
6
|
+
pyjess/_jess.pyi,sha256=3FLL_E4q_fWlThfTwnas56zpdDFoN5z_xYD0aY-0-dk,7045
|
|
7
7
|
pyjess/CMakeLists.txt,sha256=Oa0pniEQx9jXyFCJGyrswn9ahWSSVuW1madyeP6StoI,35
|
|
8
8
|
pyjess/__init__.py,sha256=h4XXLdS4FnyVa-MBs_k3eZMG1jWxeiOJnwfBaJA9gyQ,745
|
|
9
9
|
pyjess/.gitignore,sha256=uQBOufp4v50qn0aZKv6zbSo00cjfB-v9KySog7rlmIU,19
|
|
10
|
-
pyjess/_jess.pyx,sha256=
|
|
10
|
+
pyjess/_jess.pyx,sha256=5qCasXyFa0e2BfhiFfx5Qlt7xiZeb7CFfwzfFgfVfTs,53007
|
|
11
11
|
pyjess/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
pyjess/tests/__init__.py,sha256=
|
|
12
|
+
pyjess/tests/__init__.py,sha256=ka1wkfXesesZ3f6p5Dg55P4YYyS3R4gKRmhpQRtWVec,599
|
|
13
13
|
pyjess/tests/test_molecule.py,sha256=RHBE0yL_j71nQOwXJ6t_PzkZi4BaFPY5Q0VwKWM6elk,5311
|
|
14
14
|
pyjess/tests/utils.py,sha256=dsaphex7qomJCvSHWnVy79iYDPGiL59xqGAtRoVAeWc,196
|
|
15
|
+
pyjess/tests/test_hit.py,sha256=qN0qcGWHdvM9PZzBLWwuORhAXaZLp9c-CuZgO3GAbr8,1212
|
|
15
16
|
pyjess/tests/test_atom.py,sha256=omNznNbRXMDt2j1plAUlfWPGCfmtkYpj2jysEX1zQuY,4631
|
|
16
|
-
pyjess/tests/test_jess.py,sha256=
|
|
17
|
+
pyjess/tests/test_jess.py,sha256=qNXtgQ9IWJ0NH4MRhFPMQ54ocpLaxwJRP-0lZv_N0Ns,14710
|
|
17
18
|
pyjess/tests/test_template.py,sha256=XMLELYRB4j7xavziZ-ntq15PjhNHNfJJkctUq9BkvEI,4541
|
|
18
19
|
pyjess/tests/test_template_atom.py,sha256=s9tJ_SAgvKeGwbVjaTWY-EtsUeQp3eu4NF5ja3oO_84,3405
|
|
19
20
|
pyjess/tests/data/pdb1lnb.pdb,sha256=E9Jjy4qQ75O1UKIXcVyVJHE1XDNx1Rb7ENPVrehW6N8,270054
|
|
File without changes
|