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.
- {rdworks-0.36.4 → rdworks-0.38.1}/PKG-INFO +1 -1
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/__init__.py +1 -1
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/conf.py +133 -7
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/mol.py +39 -100
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/std.py +25 -1
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/PKG-INFO +1 -1
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_basics.py +33 -2
- {rdworks-0.36.4 → rdworks-0.38.1}/LICENSE +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/README.md +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/pyproject.toml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/setup.cfg +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/__init__.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/autograph.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/centroid.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/dynamictreecut.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/nmrclust.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/autograph/rckmeans.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/bitqt/__init__.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/bitqt/bitqt.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/descriptor.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/display.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/ionized.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/matchedseries.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/mollibr.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/pka.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Asinex_fragment.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Astex_RO3.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010A.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010B.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010C.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-015-hits.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-150-hits.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-more-than-150-hits.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Baell2010_PAINS/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Brenk2008_Dundee/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/CNS.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/BMS.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Dundee.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Glaxo.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/Inpharmatica.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/LINT.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/MLSMR.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/PAINS.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/SureChEMBL.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ChEMBL_Walters/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Acid.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Base.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999ElPh.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999NuPh.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Hann1999_Glaxo/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Kazius2005/Kazius2005.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/Kazius2005/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_druglike.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_fragment.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ZINC_leadlike.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/fragment.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ionized/simple_smarts_pattern.csv +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/ionized/smarts_pattern.csv +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/makexml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive-part-2.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive-part-3.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/predefined/misc/reactive.xml +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/readin.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/rgroup.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/scaffold.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/stereoisomers.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/tautomers.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/torsion.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/units.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/utils.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xml.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xtb/__init__.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks/xtb/wrapper.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/SOURCES.txt +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/dependency_links.txt +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/requires.txt +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/src/rdworks.egg-info/top_level.txt +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_decimals.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_gypsumdl.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_iupac_name.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_nn_xtb.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_web.py +0 -0
- {rdworks-0.36.4 → rdworks-0.38.1}/tests/test_xtb_wrapper.py +0 -0
@@ -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
|
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):
|
568
|
+
serialized (str): serialized string.
|
443
569
|
|
444
570
|
Returns:
|
445
|
-
Self:
|
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.
|
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
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
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
|
-
"""
|
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):
|
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.
|
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)
|
@@ -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',
|
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
|
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
|
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
|
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
|
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
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|