pyjess 0.5.1__cp39-cp39-macosx_11_0_arm64.whl → 0.6.0__cp39-cp39-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.

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 bytearray b
333
- cdef Atom atom
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
- atom._atom = <_Atom*> malloc(sizeof(_Atom))
345
- if atom._atom == NULL:
346
- raise MemoryError("Failed to allocate atom")
347
-
348
- if not jess.atom.Atom_parse(atom._atom, b):
349
- raise ValueError(f"Failed to parse atom: {text!r}")
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 hit._sup != NULL and hit_tpl != tpl:
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
- hit._sup = sup
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
- if hit._sup != sup:
1286
- jess.super.Superposition_free(sup)
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 hit._sup == NULL:
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 _Superposition* _sup
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
- assert self._sup is not NULL
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 = jess.super.Superposition_rotation(self._sup)
1382
- cdef const double* c = jess.super.Superposition_centroid(self._sup, 0)
1383
- cdef const double* v = jess.super.Superposition_centroid(self._sup, 1)
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 = jess.super.Superposition_rotation(self._sup)
1424
- cdef const double* c = jess.super.Superposition_centroid(self._sup, 0)
1425
- cdef const double* v = jess.super.Superposition_centroid(self._sup, 1)
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))
@@ -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 test_mcsa_query(self):
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.5.1
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 templates to be used as references from different template files:
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
- with path.open() as file:
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` instance has been created, the templates cannot be edited anymore,
165
- making the `Jess.query` method re-entrant. This allows querying several
166
- molecules against the same templates in parallel using a thread pool:
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
- <!-- ## ⏱️ Benchmarks -->
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 [European Molecular Biology Laboratory](https://www.embl.de/) in
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.5.1.dist-info/RECORD,,
2
- pyjess-0.5.1.dist-info/WHEEL,sha256=Bp5NfjQuMM_4-YV1MlnhqEfxPV5ySj3kUtyINz66rJc,112
3
- pyjess-0.5.1.dist-info/METADATA,sha256=OMnKlTvBTrR8tfxUFdUJXnJPezgo10GrHWTxap3eG7A,11248
4
- pyjess-0.5.1.dist-info/licenses/COPYING,sha256=gLCfHtBLTrghVX7GkpmZqoozWMNN46502m_OUiYy01Y,1103
5
- pyjess/_jess.pyi,sha256=OjbEcgr16eFEnAFbT_zp-jqjd4XME2V_gxuKZJBXVoc,6896
1
+ pyjess-0.6.0.dist-info/RECORD,,
2
+ pyjess-0.6.0.dist-info/WHEEL,sha256=ZF0EG4evfIAXWc5yWRjXEB82QuVPxkr7Vob35_6t9b0,112
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.pyi,sha256=3FLL_E4q_fWlThfTwnas56zpdDFoN5z_xYD0aY-0-dk,7045
6
6
  pyjess/CMakeLists.txt,sha256=Oa0pniEQx9jXyFCJGyrswn9ahWSSVuW1madyeP6StoI,35
7
- pyjess/_jess.cpython-39-darwin.so,sha256=PMz1ldrwX9p4DBYE8GP3EY6UqX96arocVLVIs8gaxxM,317240
7
+ pyjess/_jess.cpython-39-darwin.so,sha256=Y7hPhVXLv-U5ugk_prQMf5IMEx8gM0XWwlpLsX1RqWY,334312
8
8
  pyjess/__init__.py,sha256=h4XXLdS4FnyVa-MBs_k3eZMG1jWxeiOJnwfBaJA9gyQ,745
9
9
  pyjess/.gitignore,sha256=uQBOufp4v50qn0aZKv6zbSo00cjfB-v9KySog7rlmIU,19
10
- pyjess/_jess.pyx,sha256=kbAp0zWfxliPvzJ9c5G3oeRUKFcxtedo4oav-1uQotQ,50546
10
+ pyjess/_jess.pyx,sha256=5qCasXyFa0e2BfhiFfx5Qlt7xiZeb7CFfwzfFgfVfTs,53007
11
11
  pyjess/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- pyjess/tests/__init__.py,sha256=MdHWtr6A8S4TBWlkoj4olFK2FXAGc5uJdbWtgFrDLpk,528
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=lTp_KzkbPF5J0nWewO4LJrKeKuXtSlzj114kPG-54zM,10642
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
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: scikit-build-core 0.11.5
2
+ Generator: scikit-build-core 0.11.6
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp39-cp39-macosx_11_0_arm64
5
5