rdworks 0.37.1__tar.gz → 0.39.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.37.1 → rdworks-0.39.1}/PKG-INFO +1 -1
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/__init__.py +1 -1
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/conf.py +126 -80
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/mol.py +8 -8
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/std.py +23 -1
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks.egg-info/PKG-INFO +1 -1
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_basics.py +30 -2
- {rdworks-0.37.1 → rdworks-0.39.1}/LICENSE +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/README.md +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/pyproject.toml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/setup.cfg +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/__init__.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/autograph.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/centroid.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/dynamictreecut.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/nmrclust.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/autograph/rckmeans.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/bitqt/__init__.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/bitqt/bitqt.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/descriptor.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/display.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/ionized.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/matchedseries.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/mollibr.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/pka.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Asinex_fragment.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Astex_RO3.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010A.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010B.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010C.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-015-hits.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-150-hits.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-more-than-150-hits.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Baell2010_PAINS/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Brenk2008_Dundee/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/CNS.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/BMS.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/Dundee.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/Glaxo.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/Inpharmatica.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/LINT.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/MLSMR.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/PAINS.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/SureChEMBL.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ChEMBL_Walters/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Acid.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Base.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999ElPh.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999NuPh.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Hann1999_Glaxo/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Kazius2005/Kazius2005.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/Kazius2005/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ZINC_druglike.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ZINC_fragment.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ZINC_leadlike.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/fragment.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ionized/simple_smarts_pattern.csv +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/ionized/smarts_pattern.csv +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/misc/makexml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/misc/reactive-part-2.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/misc/reactive-part-3.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/predefined/misc/reactive.xml +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/readin.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/rgroup.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/scaffold.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/stereoisomers.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/tautomers.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/torsion.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/units.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/utils.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/xml.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/xtb/__init__.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks/xtb/wrapper.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks.egg-info/SOURCES.txt +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks.egg-info/dependency_links.txt +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks.egg-info/requires.txt +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/src/rdworks.egg-info/top_level.txt +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_decimals.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_gypsumdl.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_iupac_name.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_nn_xtb.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_web.py +0 -0
- {rdworks-0.37.1 → rdworks-0.39.1}/tests/test_xtb_wrapper.py +0 -0
@@ -39,11 +39,20 @@ class Conf:
|
|
39
39
|
"""
|
40
40
|
assert isinstance(molecule, str | Chem.Mol) or molecule is None
|
41
41
|
|
42
|
-
self.rdmol = None # must contain one and only one rdkit conformer
|
43
42
|
self.name = name
|
43
|
+
self.rdmol = None # must contain one and only one rdkit conformer
|
44
44
|
self.natoms = 0
|
45
|
-
self.charge = 0
|
46
|
-
self.spin = 1
|
45
|
+
self.charge = 0 # molecular formal charge
|
46
|
+
self.spin = 1 # molecular spin multiplicity for ASE
|
47
|
+
# Molecular spin multiplicity describes the number of possible orientations of
|
48
|
+
# spin angular momentum for a given molecule, essentially indicating the total
|
49
|
+
# number of unpaired electrons.
|
50
|
+
# Spin Angular Momentum (S): up (+1/2) or down (-1/2)
|
51
|
+
# Spin Multiplicity (2S + 1)
|
52
|
+
# 0 unpaired electron has S = 0, 2S + 1 = 0, called a singlet.
|
53
|
+
# 1 unpaired electron has S = 1/2, 2S + 1 = 2, called a doublet (radical).
|
54
|
+
# 2 unpaired electrons has S = 1, 2S + 1 = 3, called a triplet.
|
55
|
+
|
47
56
|
self.props = {}
|
48
57
|
|
49
58
|
if molecule is None:
|
@@ -63,10 +72,12 @@ class Conf:
|
|
63
72
|
|
64
73
|
num_atoms = self.rdmol.GetNumAtoms()
|
65
74
|
tot_atoms = self.rdmol.GetNumAtoms(onlyExplicit=False)
|
75
|
+
|
66
76
|
assert num_atoms == tot_atoms, "Conf() Error: missing hydrogens"
|
77
|
+
|
67
78
|
self.natoms = num_atoms
|
68
79
|
self.charge = rdmolops.GetFormalCharge(self.rdmol)
|
69
|
-
self.props.update({'atoms':
|
80
|
+
self.props.update({'atoms': self.natoms, 'charge': self.charge})
|
70
81
|
|
71
82
|
assert self.rdmol.GetConformer().Is3D(), "Conf() Error: not 3D"
|
72
83
|
|
@@ -148,7 +159,6 @@ class Conf:
|
|
148
159
|
|
149
160
|
return self
|
150
161
|
|
151
|
-
|
152
162
|
|
153
163
|
def optimize(self,
|
154
164
|
calculator: str | Callable = 'MMFF94',
|
@@ -402,6 +412,95 @@ class Conf:
|
|
402
412
|
return {i: d[:4] for i, d in enumerate(get_torsion_atoms(self.rdmol, strict))}
|
403
413
|
|
404
414
|
|
415
|
+
def torsion_energies_one(self,
|
416
|
+
calculator: str | Callable,
|
417
|
+
indices: tuple,
|
418
|
+
simplify: bool = True,
|
419
|
+
fmax: float = 0.05,
|
420
|
+
interval: float = 20.0,
|
421
|
+
use_converged_only: bool = True,
|
422
|
+
**kwargs) -> Self:
|
423
|
+
"""Calculate potential energy profile for a torsion angle.
|
424
|
+
|
425
|
+
Args:
|
426
|
+
calculator (str | Callable): 'MMFF', 'UFF', or ASE calculator.
|
427
|
+
indices (tuple): atom indices (i,j,k,l) for a torsion angle
|
428
|
+
simplify (bool, optional): whether to use fragementation. Defaults to True.
|
429
|
+
fmax (float, optional): convergence limit for optimize. Defaults to 0.05.
|
430
|
+
interval (float, optional): angle intervals. Defaults to 20.0.
|
431
|
+
use_converged_only (bool, optional): whether to use only converged data. Defaults to True.
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
Self: modified self.
|
435
|
+
"""
|
436
|
+
ref_conf = self.copy()
|
437
|
+
|
438
|
+
data = {'indices': indices,
|
439
|
+
'angle': [],
|
440
|
+
'init': [],
|
441
|
+
'last': [],
|
442
|
+
'Converged': [],
|
443
|
+
}
|
444
|
+
|
445
|
+
if simplify:
|
446
|
+
frag, frag_ijkl = create_torsion_fragment(ref_conf.rdmol, indices)
|
447
|
+
frag_conf = Conf(frag)
|
448
|
+
for angle in np.arange(-180.0, 180.0, interval):
|
449
|
+
# Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
|
450
|
+
conf = frag_conf.copy()
|
451
|
+
conf.set_torsion(*frag_ijkl, angle) # atoms bonded to `l` move.
|
452
|
+
conf = conf.optimize(calculator, fmax, **kwargs)
|
453
|
+
# conf.optimize() updates coordinates and conf.props:
|
454
|
+
# `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
|
455
|
+
data['angle'].append(angle)
|
456
|
+
data['init'].append(conf.props['E_tot_init(kcal/mol)'])
|
457
|
+
data['last'].append(conf.props['E_tot(kcal/mol)'])
|
458
|
+
data['Converged'].append(conf.props['Converged'])
|
459
|
+
frag_cleaned, _ = clean_2d(frag, reset_isotope=True, remove_H=True)
|
460
|
+
# to serialize the molecule
|
461
|
+
data['frag'] = Chem.MolToMolBlock(frag_cleaned)
|
462
|
+
data['frag_indices'] = frag_ijkl
|
463
|
+
else:
|
464
|
+
for angle in np.arange(-180.0, 180.0, interval):
|
465
|
+
# Iterated numpy.ndarray does not contain the last 180: -180., ..., (180).
|
466
|
+
conf = ref_conf.copy()
|
467
|
+
conf.set_torsion(*indices, angle) # atoms bonded to `l` move.
|
468
|
+
conf = conf.optimize(calculator, fmax, **kwargs)
|
469
|
+
# conf.optimize() updates coordinates and conf.props:
|
470
|
+
# `E_tot_init(kcal/mol)`, `E_tot(kcal/mol)`, `Converged`.
|
471
|
+
data['angle'].append(conf.props['angle'])
|
472
|
+
data['init'].append(conf.props['E_tot_init(kcal/mol)'])
|
473
|
+
data['last'].append(conf.props['E_tot(kcal/mol)'])
|
474
|
+
data['Converged'].append(conf.props['Converged'])
|
475
|
+
|
476
|
+
# Post-processing
|
477
|
+
if use_converged_only:
|
478
|
+
data['angle'] = list(itertools.compress(data['angle'], data['Converged']))
|
479
|
+
data['init' ] = list(itertools.compress(data['init' ], data['Converged']))
|
480
|
+
data['last' ] = list(itertools.compress(data['last' ], data['Converged']))
|
481
|
+
|
482
|
+
relax = np.array(data['init']) - np.median(data['last'])
|
483
|
+
E_rel = relax - np.min(relax)
|
484
|
+
|
485
|
+
torsion_energy_profile = {
|
486
|
+
'indices' : data['indices'],
|
487
|
+
'angle' : np.round(np.array(data['angle']), 1).tolist(), # np.ndarray -> list for serialization
|
488
|
+
'E_rel(kcal/mol)': np.round(E_rel, 2).tolist(), # np.ndarray -> list for serialization
|
489
|
+
}
|
490
|
+
|
491
|
+
if simplify:
|
492
|
+
torsion_energy_profile.update({
|
493
|
+
'frag' : data.get('frag', None),
|
494
|
+
'frag_indices' : data.get('frag_indices', None),
|
495
|
+
})
|
496
|
+
|
497
|
+
self.props['torsion'] = torsion_energy_profile
|
498
|
+
self.props['torsion_calculator'] = str(calculator)
|
499
|
+
|
500
|
+
return self
|
501
|
+
|
502
|
+
|
503
|
+
|
405
504
|
def torsion_energies(self,
|
406
505
|
calculator: str | Callable,
|
407
506
|
torsion_key: int | None = None,
|
@@ -417,7 +516,7 @@ class Conf:
|
|
417
516
|
|
418
517
|
Args:
|
419
518
|
calculator (str | Callable): 'MMFF', 'UFF', or ASE calculator.
|
420
|
-
torsion_key (int | None): torsion
|
519
|
+
torsion_key (int | None): key to the torsion indices to calculate. Defaults to None (all).
|
421
520
|
simplify (bool, optional): whether to use fragment surrogate. Defaults to True.
|
422
521
|
fmax (float, optional): fmax of ASE optimizer. Defaults to 0.05.
|
423
522
|
interval (float, optional): interval of torsion angles in degree. Defaults to 15.0.
|
@@ -427,80 +526,23 @@ class Conf:
|
|
427
526
|
Self: modified self.
|
428
527
|
"""
|
429
528
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
torsion_atoms_indices = {torsion_key: self.torsion_atoms()[torsion_key]}
|
529
|
+
torsion_atoms_dict = self.torsion_atoms() # {0: (5, 4, 3, 1)}
|
530
|
+
if (torsion_key is not None) and torsion_atoms_dict.get(torsion_key):
|
531
|
+
torsion_atoms_dict = {torsion_key: torsion_atoms_dict.get(torsion_key)}
|
532
|
+
# single torsion angle atom indices
|
435
533
|
|
436
|
-
|
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
|
534
|
+
conf = self.copy()
|
462
535
|
|
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
536
|
torsion_energy_profiles = {}
|
489
|
-
for tk,
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
torsion_energy_profiles[tk] =
|
497
|
-
|
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
|
-
|
537
|
+
for tk, indices in torsion_atoms_dict.items():
|
538
|
+
conf = conf.torsion_energies_one(calculator,
|
539
|
+
indices,
|
540
|
+
simplify,
|
541
|
+
fmax,
|
542
|
+
interval,
|
543
|
+
use_converged_only)
|
544
|
+
torsion_energy_profiles[tk] = conf.props['torsion']
|
545
|
+
|
504
546
|
self.props['torsion'] = torsion_energy_profiles
|
505
547
|
self.props['torsion_calculator'] = str(calculator)
|
506
548
|
|
@@ -548,8 +590,10 @@ class Conf:
|
|
548
590
|
str: serialized string for json.loads()
|
549
591
|
"""
|
550
592
|
serialized = json.dumps({
|
551
|
-
'name' : self.name,
|
552
|
-
'natoms': self.natoms,
|
593
|
+
'name' : self.name,
|
594
|
+
'natoms': self.natoms,
|
595
|
+
'charge': self.charge,
|
596
|
+
'spin': self.spin,
|
553
597
|
'props' : recursive_round(self.props, decimals),
|
554
598
|
'molblock' : self.to_molblock(),
|
555
599
|
})
|
@@ -573,7 +617,9 @@ class Conf:
|
|
573
617
|
data = json.loads(serialized)
|
574
618
|
|
575
619
|
self.name = data['name']
|
576
|
-
self.natoms = data['natoms']
|
620
|
+
self.natoms = int(data['natoms'])
|
621
|
+
self.charge = int(data['charge'])
|
622
|
+
self.spin = int(data['spin'])
|
577
623
|
self.props = data['props']
|
578
624
|
self.rdmol = Chem.MolFromMolBlock(data['molblock'], sanitize=False, removeHs=False)
|
579
625
|
|
@@ -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
|
@@ -83,17 +83,18 @@ class Mol:
|
|
83
83
|
"""
|
84
84
|
assert isinstance(molecule, str | Chem.Mol | Conf) or molecule is None
|
85
85
|
|
86
|
+
self.name = ''
|
86
87
|
self.rdmol = None # 2D, one and only one Conformer
|
87
88
|
self.smiles = '' # isomeric SMILES
|
88
89
|
self.confs = [] # container for 3D conformers
|
89
|
-
self.name = ''
|
90
90
|
self.InChIKey = '' # 27 characters (SHA-256 hash of InChI)
|
91
|
-
self.InChI = ''
|
92
91
|
self.props = {}
|
93
|
-
|
92
|
+
|
94
93
|
self.max_workers = max_workers
|
95
94
|
self.chunksize = chunksize
|
96
95
|
self.progress = progress
|
96
|
+
|
97
|
+
self.fp = None
|
97
98
|
|
98
99
|
if molecule is None:
|
99
100
|
return
|
@@ -141,8 +142,7 @@ class Mol:
|
|
141
142
|
self.name = 'untitled'
|
142
143
|
|
143
144
|
self.rdmol.SetProp('_Name', self.name) # _Name can't be None
|
144
|
-
self.
|
145
|
-
self.InChIKey = inchi.InchiToInchiKey(self.InChI)
|
145
|
+
self.InChIKey = generate_inchi_key(self.rdmol)
|
146
146
|
self.props.update({
|
147
147
|
'aka' : [], # <-- to be set by MolLibr.unique()
|
148
148
|
'atoms' : self.rdmol.GetNumAtoms(), # hydrogens not excluded?
|
@@ -1570,6 +1570,7 @@ class Mol:
|
|
1570
1570
|
serialized = json.dumps({
|
1571
1571
|
'name' : self.name,
|
1572
1572
|
'smiles': self.smiles,
|
1573
|
+
'InChIKey': self.InChIKey,
|
1573
1574
|
'props' : recursive_round(self.props, decimals),
|
1574
1575
|
'confs' : [conf.serialize() for conf in self.confs],
|
1575
1576
|
})
|
@@ -1596,8 +1597,7 @@ class Mol:
|
|
1596
1597
|
self.smiles = data['smiles'] # isomeric SMILES, no H
|
1597
1598
|
self.rdmol = Chem.MolFromSmiles(data['smiles']) # for 2D depiction
|
1598
1599
|
self.rdmol.SetProp('_Name', self.name)
|
1599
|
-
self.
|
1600
|
-
self.InChIKey = inchi.InchiToInchiKey(self.InChI)
|
1600
|
+
self.InChIKey = data['InChIKey']
|
1601
1601
|
self.props = data['props']
|
1602
1602
|
self.confs = [Conf().deserialize(_) for _ in data['confs']] # for 3D conformers (iterable)
|
1603
1603
|
|
@@ -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.
|
@@ -475,7 +475,6 @@ 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)}
|
@@ -486,12 +485,41 @@ def test_torsion_fragment():
|
|
486
485
|
assert frag_ijkl == ta2[0]
|
487
486
|
|
488
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)
|
514
|
+
|
515
|
+
|
489
516
|
def test_torsion_energies():
|
490
517
|
libr = MolLibr(torsion_dataset_smiles, torsion_dataset_names)
|
491
518
|
with open(workdir / 'test_torsion_energies.html', 'w') as f:
|
492
519
|
for mol in libr[:1]:
|
493
520
|
mol = mol.make_confs().drop_confs(similar=True, similar_rmsd=0.3).sort_confs().rename()
|
494
|
-
mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94',
|
521
|
+
mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94',
|
522
|
+
interval=15)
|
495
523
|
f.write(mol.to_html())
|
496
524
|
print(mol.dumps('torsion', decimals=2))
|
497
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
|