rdworks 0.36.4__tar.gz → 0.38.1__tar.gz

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.
Files changed (84) hide show
  1. {rdworks-0.36.4 → rdworks-0.38.1}/PKG-INFO +1 -1
  2. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/__init__.py +1 -1
  3. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/conf.py +133 -7
  4. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/mol.py +39 -100
  5. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/std.py +25 -1
  6. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/PKG-INFO +1 -1
  7. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_basics.py +33 -2
  8. {rdworks-0.36.4 → rdworks-0.38.1}/LICENSE +0 -0
  9. {rdworks-0.36.4 → rdworks-0.38.1}/README.md +0 -0
  10. {rdworks-0.36.4 → rdworks-0.38.1}/pyproject.toml +0 -0
  11. {rdworks-0.36.4 → rdworks-0.38.1}/setup.cfg +0 -0
  12. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/__init__.py +0 -0
  13. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/autograph.py +0 -0
  14. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/centroid.py +0 -0
  15. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/dynamictreecut.py +0 -0
  16. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/nmrclust.py +0 -0
  17. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/rckmeans.py +0 -0
  18. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/bitqt/__init__.py +0 -0
  19. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/bitqt/bitqt.py +0 -0
  20. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/descriptor.py +0 -0
  21. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/display.py +0 -0
  22. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/ionized.py +0 -0
  23. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/matchedseries.py +0 -0
  24. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/mollibr.py +0 -0
  25. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/pka.py +0 -0
  26. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Asinex_fragment.xml +0 -0
  27. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Astex_RO3.xml +0 -0
  28. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010A.xml +0 -0
  29. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010B.xml +0 -0
  30. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010C.xml +0 -0
  31. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-015-hits.xml +0 -0
  32. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-150-hits.xml +0 -0
  33. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-more-than-150-hits.xml +0 -0
  34. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/makexml.py +0 -0
  35. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Brenk2008_Dundee/makexml.py +0 -0
  36. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/CNS.xml +0 -0
  37. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/BMS.xml +0 -0
  38. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Dundee.xml +0 -0
  39. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Glaxo.xml +0 -0
  40. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Inpharmatica.xml +0 -0
  41. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/LINT.xml +0 -0
  42. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/MLSMR.xml +0 -0
  43. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/PAINS.xml +0 -0
  44. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/SureChEMBL.xml +0 -0
  45. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/makexml.py +0 -0
  46. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999.xml +0 -0
  47. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Acid.xml +0 -0
  48. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Base.xml +0 -0
  49. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999ElPh.xml +0 -0
  50. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999NuPh.xml +0 -0
  51. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/makexml.py +0 -0
  52. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Kazius2005/Kazius2005.xml +0 -0
  53. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Kazius2005/makexml.py +0 -0
  54. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_druglike.xml +0 -0
  55. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_fragment.xml +0 -0
  56. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_leadlike.xml +0 -0
  57. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/fragment.xml +0 -0
  58. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ionized/simple_smarts_pattern.csv +0 -0
  59. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ionized/smarts_pattern.csv +0 -0
  60. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/makexml.py +0 -0
  61. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive-part-2.xml +0 -0
  62. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive-part-3.xml +0 -0
  63. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive.xml +0 -0
  64. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/readin.py +0 -0
  65. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/rgroup.py +0 -0
  66. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/scaffold.py +0 -0
  67. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/stereoisomers.py +0 -0
  68. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/tautomers.py +0 -0
  69. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/torsion.py +0 -0
  70. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/units.py +0 -0
  71. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/utils.py +0 -0
  72. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xml.py +0 -0
  73. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xtb/__init__.py +0 -0
  74. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xtb/wrapper.py +0 -0
  75. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/SOURCES.txt +0 -0
  76. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/dependency_links.txt +0 -0
  77. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/requires.txt +0 -0
  78. {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/top_level.txt +0 -0
  79. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_decimals.py +0 -0
  80. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_gypsumdl.py +0 -0
  81. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_iupac_name.py +0 -0
  82. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_nn_xtb.py +0 -0
  83. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_web.py +0 -0
  84. {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_xtb_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdworks
3
- Version: 0.36.4
3
+ Version: 0.38.1
4
4
  Summary: Frequently used tasks built on RDKit and other tools
5
5
  Author-email: Sung-Hun Bae <sunghun.bae@gmail.com>
6
6
  Maintainer-email: Sung-Hun Bae <sunghun.bae@gmail.com>
@@ -1,4 +1,4 @@
1
- __version__ = '0.36.4'
1
+ __version__ = '0.38.1'
2
2
 
3
3
  from rdworks.conf import Conf
4
4
  from rdworks.mol import Mol
@@ -3,6 +3,7 @@ import copy
3
3
  import json
4
4
  import numpy as np
5
5
  import ase
6
+ import itertools
6
7
 
7
8
  from collections.abc import Callable
8
9
  from typing import Self
@@ -15,6 +16,8 @@ from rdkit.Chem import rdMolTransforms, AllChem, rdMolAlign, rdmolops
15
16
  from rdkit.Chem.Draw import rdMolDraw2D
16
17
  from PIL import Image
17
18
 
19
+ from rdworks.std import clean_2d
20
+ from rdworks.torsion import get_torsion_atoms, create_torsion_fragment
18
21
  from rdworks.units import ev2kcalpermol, pm2angstrom
19
22
  from rdworks.utils import recursive_round
20
23
  from rdworks.xtb.wrapper import GFN2xTB
@@ -387,7 +390,124 @@ class Conf:
387
390
  raise RuntimeError("ASE calculator error")
388
391
 
389
392
 
390
- def torsion_angle(self, i:int, j:int, k:int, l:int) -> float:
393
+ def torsion_atoms(self, strict: bool = True) -> dict[int, tuple]:
394
+ """Determine torsion/dihedral angle atoms (i-j-k-l) and rotating group for each rotatable bond (j-k).
395
+
396
+ Args:
397
+ strict (bool): whether to exclude amide/imide/ester/acid bonds.
398
+
399
+ Returns:
400
+ {torsion_key: (i, j, k, l), ...,}
401
+ """
402
+ return {i: d[:4] for i, d in enumerate(get_torsion_atoms(self.rdmol, strict))}
403
+
404
+
405
+ def torsion_energies(self,
406
+ calculator: str | Callable,
407
+ torsion_key: int | None = None,
408
+ simplify: bool = True,
409
+ fmax: float = 0.05,
410
+ interval: float = 20.0,
411
+ use_converged_only: bool = True,
412
+ **kwargs,
413
+ ) -> Self:
414
+ """Calculates potential energy profiles for each torsion angle using ASE optimizer.
415
+
416
+ It uses the first conformer as a reference.
417
+
418
+ Args:
419
+ calculator (str | Callable): 'MMFF', 'UFF', or ASE calculator.
420
+ torsion_key (int | None): key to the torsion indices to calculate. Defaults to None (all).
421
+ simplify (bool, optional): whether to use fragment surrogate. Defaults to True.
422
+ fmax (float, optional): fmax of ASE optimizer. Defaults to 0.05.
423
+ interval (float, optional): interval of torsion angles in degree. Defaults to 15.0.
424
+ use_converged_only (bool, optional): whether to use only converged data. Defaults to True.
425
+
426
+ Returns:
427
+ Self: modified self.
428
+ """
429
+
430
+ if torsion_key is None:
431
+ torsion_atoms_indices = self.torsion_atoms()
432
+ # {0: (5, 4, 3, 1)}
433
+ else:
434
+ torsion_atoms_indices = {torsion_key: self.torsion_atoms()[torsion_key]}
435
+
436
+ ref_conf = self.copy()
437
+
438
+ data = {}
439
+
440
+ if simplify:
441
+ for tk, indices in torsion_atoms_indices.items():
442
+ frag, frag_ijkl = create_torsion_fragment(ref_conf.rdmol, indices)
443
+ frag_conf = Conf(frag)
444
+ data[tk] = {'indices': indices, 'angle':[], 'init':[], 'last':[], 'Converged':[]}
445
+ for angle in np.arange(-180.0, 180.0, interval):
446
+ # Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
447
+ conf = frag_conf.copy()
448
+ conf.props.update({'torsion_key': tk, 'angle': float(angle)})
449
+ conf.set_torsion(*frag_ijkl, angle) # atoms bonded to `l` move.
450
+ conf = conf.optimize(calculator, fmax, **kwargs)
451
+ # conf.optimize() updates coordinates and conf.props:
452
+ # `angle`, `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
453
+ tk = conf.props['torsion_key']
454
+ data[tk]['angle'].append(conf.props['angle'])
455
+ data[tk]['init'].append(conf.props['E_tot_init(kcal/mol)'])
456
+ data[tk]['last'].append(conf.props['E_tot(kcal/mol)'])
457
+ data[tk]['Converged'].append(conf.props['Converged'])
458
+ frag_cleaned, _ = clean_2d(frag, reset_isotope=True, remove_H=True)
459
+ # to serialize the molecule
460
+ data[tk]['frag'] = Chem.MolToMolBlock(frag_cleaned)
461
+ data[tk]['frag_indices'] = frag_ijkl
462
+
463
+ else:
464
+ # mol.confs will be populated with torsion conformers.
465
+ # It is designed for a batch optimization in the future.
466
+ torsion_angle_confs = []
467
+ for tk, indices in torsion_atoms_indices.items():
468
+ data[tk] = {'indices': indices, 'angle':[], 'init':[], 'last':[], 'Converged':[]}
469
+ for angle in np.arange(-180.0, 180.0, interval):
470
+ # Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
471
+ x = ref_conf.copy()
472
+ x.props.update({'torsion_key': tk, 'angle': float(angle)})
473
+ x.set_torsion(*indices, angle) # atoms bonded to `l` move.
474
+ torsion_angle_confs.append(x)
475
+
476
+ # Calculate relaxation energies
477
+ for conf in torsion_angle_confs:
478
+ conf = conf.optimize(calculator, fmax, **kwargs)
479
+ # conf.optimize() updates coordinates and conf.props:
480
+ # `angle`, `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
481
+ tk = conf.props['torsion_key']
482
+ data[tk]['angle'].append(conf.props['angle'])
483
+ data[tk]['init'].append(conf.props['E_tot_init(kcal/mol)'])
484
+ data[tk]['last'].append(conf.props['E_tot(kcal/mol)'])
485
+ data[tk]['Converged'].append(conf.props['Converged'])
486
+
487
+ # Post-processing
488
+ torsion_energy_profiles = {}
489
+ for tk, dictdata in data.items():
490
+ if use_converged_only:
491
+ dictdata['angle'] = list(itertools.compress(dictdata['angle'], dictdata['Converged']))
492
+ dictdata['init'] = list(itertools.compress(dictdata['init'], dictdata['Converged']))
493
+ dictdata['last'] = list(itertools.compress(dictdata['last'], dictdata['Converged']))
494
+ relax = np.array(dictdata['init']) - np.median(dictdata['last'])
495
+ E_rel = relax - np.min(relax)
496
+ torsion_energy_profiles[tk] = {
497
+ 'indices' : dictdata['indices'],
498
+ 'angle' : np.round(np.array(dictdata['angle']), 1).tolist(), # np.ndarray -> list for serialization
499
+ 'E_rel(kcal/mol)': np.round(E_rel, 2).tolist(), # np.ndarray -> list for serialization
500
+ 'frag' : dictdata.get('frag', None),
501
+ 'frag_indices' : dictdata.get('frag_indices', None),
502
+ }
503
+
504
+ self.props['torsion'] = torsion_energy_profiles
505
+ self.props['torsion_calculator'] = str(calculator)
506
+
507
+ return self
508
+
509
+
510
+ def torsion_angle(self, i: int, j: int, k: int, l: int) -> float:
391
511
  """Get dihedral angle (i-j-k-l) in degrees.
392
512
 
393
513
  Args:
@@ -419,8 +539,10 @@ class Conf:
419
539
  return json.dumps(self.props)
420
540
 
421
541
 
422
- def serialize(self, decimals:int=3) -> str:
423
- """Serialize information necessary to rebuild.
542
+ def serialize(self, decimals: int = 3) -> str:
543
+ """Serialize information necessary to rebuild a Conf object.
544
+ Args:
545
+ decimals (int, optional): number of decimal places for float data type. Defaults to 3.
424
546
 
425
547
  Returns:
426
548
  str: serialized string for json.loads()
@@ -435,14 +557,18 @@ class Conf:
435
557
  return serialized
436
558
 
437
559
 
438
- def deserialize(self, serialized:str) -> Self:
439
- """De-serialize information and rebuild.
560
+ def deserialize(self, serialized: str) -> Self:
561
+ """De-serialize information and rebuild a Conf object.
562
+
563
+ Example:
564
+ serialized = conf1.serialize()
565
+ conf2 = Conf().deserialize(serialized)
440
566
 
441
567
  Args:
442
- serialized (str): _description_
568
+ serialized (str): serialized string.
443
569
 
444
570
  Returns:
445
- Self: _description_
571
+ Self: modified self.
446
572
  """
447
573
  data = json.loads(serialized)
448
574
 
@@ -33,7 +33,7 @@ from rdkit.ML.Cluster import Butina
33
33
  from PIL import Image
34
34
 
35
35
  from rdworks.conf import Conf
36
- from rdworks.std import desalt_smiles, standardize, clean_2d
36
+ from rdworks.std import generate_inchi_key, desalt_smiles, standardize, clean_2d
37
37
  from rdworks.xml import list_predefined_xml, get_predefined_xml, parse_xml
38
38
  from rdworks.scaffold import rigid_fragment_indices
39
39
  from rdworks.descriptor import rd_descriptor, rd_descriptor_f
@@ -141,8 +141,7 @@ class Mol:
141
141
  self.name = 'untitled'
142
142
 
143
143
  self.rdmol.SetProp('_Name', self.name) # _Name can't be None
144
- self.InChI = Chem.MolToInchi(self.rdmol)
145
- self.InChIKey = inchi.InchiToInchiKey(self.InChI)
144
+ self.InChIKey = generate_inchi_key(self.rdmol)
146
145
  self.props.update({
147
146
  'aka' : [], # <-- to be set by MolLibr.unique()
148
147
  'atoms' : self.rdmol.GetNumAtoms(), # hydrogens not excluded?
@@ -942,18 +941,6 @@ class Mol:
942
941
  return [atom.GetAtomicNum() for atom in self.rdmol.GetAtoms()]
943
942
 
944
943
 
945
- def torsion_atoms(self, strict: bool = True) -> dict[int, tuple]:
946
- """Determine torsion/dihedral angle atoms (i-j-k-l) and rotating group for each rotatable bond (j-k).
947
-
948
- Args:
949
- strict (bool): whether to exclude amide/imide/ester/acid bonds.
950
-
951
- Returns:
952
- {torsion_key: (i, j, k, l), ...,}
953
- """
954
- return {i: d[:4] for i, d in enumerate(get_torsion_atoms(self.rdmol, strict))}
955
-
956
-
957
944
  def compute(self, **kwargs) -> Self:
958
945
  """Change settings for parallel computing.
959
946
 
@@ -972,6 +959,18 @@ class Mol:
972
959
  return self
973
960
 
974
961
 
962
+ def torsion_atoms(self, strict: bool = True) -> dict[int, tuple]:
963
+ """Determine torsion/dihedral angle atoms (i-j-k-l) and rotating group for each rotatable bond (j-k).
964
+
965
+ Args:
966
+ strict (bool): whether to exclude amide/imide/ester/acid bonds.
967
+
968
+ Returns:
969
+ {torsion_key: (i, j, k, l), ...,}
970
+ """
971
+ return {i: d[:4] for i, d in enumerate(get_torsion_atoms(self.rdmol, strict))}
972
+
973
+
975
974
  def torsion_energies(self,
976
975
  calculator: str | Callable,
977
976
  torsion_key: int | None = None,
@@ -997,87 +996,16 @@ class Mol:
997
996
  Self: modified self.
998
997
  """
999
998
  assert self.count() > 0, "torsion_energies() requires at least one conformer"
1000
-
1001
- self = self.compute(**kwargs)
1002
-
1003
- if torsion_key is None:
1004
- torsion_atoms_indices = self.torsion_atoms()
1005
- # {0: (5, 4, 3, 1)}
1006
- else:
1007
- torsion_atoms_indices = {torsion_key: self.torsion_atoms()[torsion_key]}
1008
-
1009
999
  ref_conf = self.confs[0].copy()
1010
-
1011
- data = {}
1012
-
1013
- if simplify:
1014
- for tk, indices in torsion_atoms_indices.items():
1015
- frag, frag_ijkl = create_torsion_fragment(ref_conf.rdmol, indices)
1016
- frag_conf = Conf(frag)
1017
- data[tk] = {'indices': indices, 'angle':[], 'init':[], 'last':[], 'Converged':[]}
1018
- for angle in np.arange(-180.0, 180.0, interval):
1019
- # Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
1020
- conf = frag_conf.copy()
1021
- conf.props.update({'torsion_key': tk, 'angle': float(angle)})
1022
- conf.set_torsion(*frag_ijkl, angle) # atoms bonded to `l` move.
1023
- conf = conf.optimize(calculator, fmax, **kwargs)
1024
- # conf.optimize() updates coordinates and conf.props:
1025
- # `angle`, `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
1026
- tk = conf.props['torsion_key']
1027
- data[tk]['angle'].append(conf.props['angle'])
1028
- data[tk]['init'].append(conf.props['E_tot_init(kcal/mol)'])
1029
- data[tk]['last'].append(conf.props['E_tot(kcal/mol)'])
1030
- data[tk]['Converged'].append(conf.props['Converged'])
1031
- frag_cleaned, _ = clean_2d(frag, reset_isotope=True, remove_H=True)
1032
- rdDepictor.Compute2DCoords(frag_cleaned)
1033
- # to serialize the molecule
1034
- data[tk]['frag'] = Chem.MolToMolBlock(frag_cleaned)
1035
- data[tk]['frag_indices'] = frag_ijkl
1036
-
1037
- else:
1038
- # mol.confs will be populated with torsion conformers.
1039
- # It is designed for a batch optimization in the future.
1040
- mol = self.copy()
1041
- mol.confs = []
1042
- for tk, indices in torsion_atoms_indices.items():
1043
- data[tk] = {'indices': indices, 'angle':[], 'init':[], 'last':[], 'Converged':[]}
1044
- for angle in np.arange(-180.0, 180.0, interval):
1045
- # Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
1046
- x = ref_conf.copy()
1047
- x.props.update({'torsion_key': tk, 'angle': float(angle)})
1048
- x.set_torsion(*indices, angle) # atoms bonded to `l` move.
1049
- mol.confs.append(x)
1050
-
1051
- # Calculate relaxation energies
1052
- for conf in mol.confs:
1053
- conf = conf.optimize(calculator, fmax, **kwargs)
1054
- # conf.optimize() updates coordinates and conf.props:
1055
- # `angle`, `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
1056
- tk = conf.props['torsion_key']
1057
- data[tk]['angle'].append(conf.props['angle'])
1058
- data[tk]['init'].append(conf.props['E_tot_init(kcal/mol)'])
1059
- data[tk]['last'].append(conf.props['E_tot(kcal/mol)'])
1060
- data[tk]['Converged'].append(conf.props['Converged'])
1061
-
1062
- # Post-processing
1063
- torsion_energy_profiles = {}
1064
- for tk, dictdata in data.items():
1065
- if use_converged_only:
1066
- dictdata['angle'] = list(itertools.compress(dictdata['angle'], dictdata['Converged']))
1067
- dictdata['init'] = list(itertools.compress(dictdata['init'], dictdata['Converged']))
1068
- dictdata['last'] = list(itertools.compress(dictdata['last'], dictdata['Converged']))
1069
- relax = np.array(dictdata['init']) - np.median(dictdata['last'])
1070
- E_rel = relax - np.min(relax)
1071
- torsion_energy_profiles[tk] = {
1072
- 'indices' : dictdata['indices'],
1073
- 'angle' : np.round(np.array(dictdata['angle']), 1).tolist(), # np.ndarray -> list for serialization
1074
- 'E_rel(kcal/mol)': np.round(E_rel, 2).tolist(), # np.ndarray -> list for serialization
1075
- 'frag' : dictdata.get('frag', None),
1076
- 'frag_indices' : dictdata.get('frag_indices', None),
1077
- }
1078
-
1079
- self.props['torsion'] = torsion_energy_profiles
1080
- self.props['torsion_calculator'] = str(calculator)
1000
+ ref_conf = ref_conf.torsion_energies(calculator,
1001
+ torsion_key,
1002
+ simplify,
1003
+ fmax,
1004
+ interval,
1005
+ use_converged_only,
1006
+ **kwargs)
1007
+ for k in ['torsion', 'torsion_calculator']:
1008
+ self.props[k] = ref_conf.props[k]
1081
1009
 
1082
1010
  return self
1083
1011
 
@@ -1629,7 +1557,15 @@ class Mol:
1629
1557
  return json.dumps(props)
1630
1558
 
1631
1559
 
1632
- def serialize(self, decimals: int = 2) -> str:
1560
+ def serialize(self, decimals: int = 2) -> str:
1561
+ """Serialize information necessary to rebuild a Mol object.
1562
+
1563
+ Args:
1564
+ decimals (int, optional): number of decimal places for float data type. Defaults to 2.
1565
+
1566
+ Returns:
1567
+ str: serialized string for json.loads()
1568
+ """
1633
1569
  serialized = json.dumps({
1634
1570
  'name' : self.name,
1635
1571
  'smiles': self.smiles,
@@ -1641,10 +1577,14 @@ class Mol:
1641
1577
 
1642
1578
 
1643
1579
  def deserialize(self, serialized: str) -> Self:
1644
- """Updates self with the serialized string input.
1580
+ """De-serialize the information and build a new Mol object.
1581
+
1582
+ Example:
1583
+ serialized = mol1.serialize()
1584
+ mol2 = Mol().deserialize(serialized)
1645
1585
 
1646
1586
  Args:
1647
- serialized (str): input
1587
+ serialized (str): serialized string.
1648
1588
 
1649
1589
  Returns:
1650
1590
  Self: modified self.
@@ -1655,8 +1595,7 @@ class Mol:
1655
1595
  self.smiles = data['smiles'] # isomeric SMILES, no H
1656
1596
  self.rdmol = Chem.MolFromSmiles(data['smiles']) # for 2D depiction
1657
1597
  self.rdmol.SetProp('_Name', self.name)
1658
- self.InChI = Chem.MolToInchi(self.rdmol)
1659
- self.InChIKey = inchi.InchiToInchiKey(self.InChI)
1598
+ self.InChIKey = generate_inchi_key(self.rdmol)
1660
1599
  self.props = data['props']
1661
1600
  self.confs = [Conf().deserialize(_) for _ in data['confs']] # for 3D conformers (iterable)
1662
1601
 
@@ -1,10 +1,32 @@
1
1
  import operator
2
2
 
3
3
  from rdkit import Chem
4
- from rdkit.Chem import rdDepictor
4
+ from rdkit.Chem import rdDepictor, inchi
5
5
  from rdkit.Chem.MolStandardize import rdMolStandardize
6
6
 
7
7
 
8
+ def generate_inchi_key(rdmol: Chem.Mol) -> str:
9
+ """Generate InChIKey.
10
+
11
+ Note:
12
+ - An InChIKey is a 27-character string consisting of three parts:
13
+ - 14 characters: Derived from the connectivity layer of the InChI.
14
+ - Hyphen (-): Separates the first two blocks.
15
+ - 9 characters: Derived from the remaining InChI layers.
16
+ - A hyphen (-): Separates the second and third blocks.
17
+ - A final checksum character: Ensures the integrity of the key.
18
+
19
+ Args:
20
+ rdmol (Chem.Mol): input molecule
21
+
22
+ Returns:
23
+ str: 27-character InChIKey
24
+ """
25
+ InChI = Chem.MolToInchi(rdmol)
26
+ InChIKey = inchi.InchiToInchiKey(InChI)
27
+
28
+ return InChIKey
29
+
8
30
 
9
31
  def desalt_smiles(smiles: str) -> tuple[str, Chem.Mol]:
10
32
  """Remove salt(s) from SMILES.
@@ -180,4 +202,6 @@ def clean_2d(rdmol: Chem.Mol,
180
202
  if remove_H:
181
203
  mol = Chem.RemoveHs(mol)
182
204
 
205
+ rdDepictor.Compute2DCoords(mol)
206
+
183
207
  return (mol, conformers)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdworks
3
- Version: 0.36.4
3
+ Version: 0.38.1
4
4
  Summary: Frequently used tasks built on RDKit and other tools
5
5
  Author-email: Sung-Hun Bae <sunghun.bae@gmail.com>
6
6
  Maintainer-email: Sung-Hun Bae <sunghun.bae@gmail.com>
@@ -475,12 +475,42 @@ def test_torsion_fragment():
475
475
  frag, frag_ijkl = create_torsion_fragment(mol.confs[0].rdmol, ta[6])
476
476
  assert frag_ijkl == (5, 6, 7, 12)
477
477
 
478
-
479
478
  mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
480
479
  ta2 = mol2.torsion_atoms()
481
480
  # {0: (5, 4, 3, 1)}
482
481
  assert len(ta2) == 1
483
482
  frag, frag_ijkl = create_torsion_fragment(mol2.confs[0].rdmol, ta2[0])
483
+ # expects no fragmentation
484
+ assert frag == mol2.confs[0].rdmol
485
+ assert frag_ijkl == ta2[0]
486
+
487
+
488
+ def test_torsion_fragment_from_conf():
489
+ from rdworks.torsion import create_torsion_fragment
490
+ mol = Mol(molecule="CC(C)C1=C(C(=C(N1CC[C@H](C[C@H](CC(=O)O)O)O)C2=CC=C(C=C2)F)C3=CC=CC=C3)C(=O)NC4=CC=CC=C4",
491
+ name="atorvastatin").make_confs(n=1)
492
+ ref_conf = mol.confs[0]
493
+ ta = ref_conf.torsion_atoms()
494
+ assert len(ta) == 12
495
+ # {0: (0, 1, 3, 7), 1: (3, 4, 32, 33), 2: (4, 5, 26, 27), 3: (7, 6, 19, 20),
496
+ # 4: (3, 7, 8, 9), 5: (7, 8, 9, 10), 6: (8, 9, 10, 18), 7: (18, 10, 11, 12),
497
+ # 8: (10, 11, 12, 17), 9: (17, 12, 13, 14), 10: (12, 13, 14, 15), 11: (36, 35, 34, 32)}
498
+ frag, frag_ijkl = create_torsion_fragment(ref_conf.rdmol, ta[6])
499
+ assert frag_ijkl == (5, 6, 7, 12)
500
+
501
+ ref_conf = ref_conf.torsion_energies(calculator='MMFF94', torsion_key=6, interval=15)
502
+
503
+ mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
504
+ ref_conf2 = mol2.confs[0]
505
+ ta2 = ref_conf2.torsion_atoms()
506
+ # {0: (5, 4, 3, 1)}
507
+ assert len(ta2) == 1
508
+ frag, frag_ijkl = create_torsion_fragment(ref_conf2.rdmol, ta2[0])
509
+ # expects no fragmentation
510
+ assert frag == ref_conf2.rdmol
511
+ assert frag_ijkl == ta2[0]
512
+
513
+ ref_conf2 = ref_conf2.torsion_energies(calculator='MMFF94', interval=15)
484
514
 
485
515
 
486
516
  def test_torsion_energies():
@@ -488,7 +518,8 @@ def test_torsion_energies():
488
518
  with open(workdir / 'test_torsion_energies.html', 'w') as f:
489
519
  for mol in libr[:1]:
490
520
  mol = mol.make_confs().drop_confs(similar=True, similar_rmsd=0.3).sort_confs().rename()
491
- mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94', interval=15)
521
+ mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94',
522
+ interval=15)
492
523
  f.write(mol.to_html())
493
524
  print(mol.dumps('torsion', decimals=2))
494
525
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes