rdworks 0.48.1__tar.gz → 0.49.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.48.1 → rdworks-0.49.1}/PKG-INFO +1 -1
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/__init__.py +9 -8
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/ionized.py +4 -1
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/mol.py +31 -20
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/xtb/wrapper.py +21 -17
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks.egg-info/PKG-INFO +1 -1
- {rdworks-0.48.1 → rdworks-0.49.1}/tests/test_ionized.py +33 -1
- rdworks-0.49.1/tests/test_xtb.py +160 -0
- rdworks-0.48.1/tests/test_xtb.py +0 -76
- {rdworks-0.48.1 → rdworks-0.49.1}/LICENSE +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/README.md +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/pyproject.toml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/setup.cfg +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/__init__.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/autograph.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/centroid.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/dynamictreecut.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/nmrclust.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/autograph/rckmeans.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/bitqt/__init__.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/bitqt/bitqt.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/conf.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/descriptor.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/display.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/matchedseries.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/mollibr.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/pka.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Asinex_fragment.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Astex_RO3.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010A.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010B.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010C.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-015-hits.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-150-hits.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-more-than-150-hits.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Baell2010_PAINS/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Brenk2008_Dundee/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/CNS.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/BMS.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/Dundee.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/Glaxo.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/Inpharmatica.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/LINT.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/MLSMR.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/PAINS.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/SureChEMBL.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ChEMBL_Walters/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Acid.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Base.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999ElPh.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999NuPh.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Hann1999_Glaxo/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Kazius2005/Kazius2005.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/Kazius2005/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ZINC_druglike.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ZINC_fragment.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ZINC_leadlike.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/fragment.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ionized/simple_smarts_pattern.csv +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/ionized/smarts_pattern.csv +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/misc/makexml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/misc/reactive-part-2.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/misc/reactive-part-3.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/predefined/misc/reactive.xml +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/readin.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/rgroup.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/scaffold.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/std.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/stereoisomers.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/tautomers.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/testdata.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/torsion.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/units.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/utils.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/xml.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks/xtb/__init__.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks.egg-info/SOURCES.txt +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks.egg-info/dependency_links.txt +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks.egg-info/requires.txt +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/src/rdworks.egg-info/top_level.txt +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/tests/test_basics.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/tests/test_round.py +0 -0
- {rdworks-0.48.1 → rdworks-0.49.1}/tests/test_torsion.py +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
__version__ = '0.
|
1
|
+
__version__ = '0.49.1'
|
2
2
|
|
3
3
|
from rdworks.conf import Conf
|
4
4
|
from rdworks.mol import Mol
|
@@ -25,10 +25,11 @@ __rdkit_version__ = rdkit.rdBase.rdkitVersion
|
|
25
25
|
|
26
26
|
rdkit_logger = rdkit.RDLogger.logger().setLevel(rdkit.RDLogger.CRITICAL)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
logger = logging.getLogger(__name__)
|
29
|
+
logger.setLevel(logging.INFO) # level: DEBUG < INFO < WARNING < ERROR < CRITICAL
|
30
|
+
|
31
|
+
logger_stream = logging.StreamHandler() # sys.stdout or sys.stderr
|
32
|
+
logger_format = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
|
33
|
+
datefmt='%Y-%m-%d %H:%M:%S')
|
34
|
+
logger_stream.setFormatter(logger_format)
|
35
|
+
logger.addHandler(logger_stream)
|
@@ -130,6 +130,9 @@ class IonizedStates:
|
|
130
130
|
edmol.RemoveAtom(bonded_H_indices[0])
|
131
131
|
|
132
132
|
elif acid_or_base == 'B': # protonate
|
133
|
+
# note that protonation at tertiary nitrogen may results in stereoisomers
|
134
|
+
# current implementation ignores the stereochemistry
|
135
|
+
# use rdworks.complete_stereoisomers() function to complete the stereoisomers
|
133
136
|
B = edmol.GetAtomWithIdx(i)
|
134
137
|
assert B.GetAtomicNum() > 1, f"Cannot protonate an atom (idx={i}; {B.GetAtomicNum()})"
|
135
138
|
charge = B.GetFormalCharge() + 1
|
@@ -138,7 +141,7 @@ class IonizedStates:
|
|
138
141
|
B.SetNumExplicitHs(nH+1)
|
139
142
|
edmol = Chem.AddHs(edmol)
|
140
143
|
|
141
|
-
#
|
144
|
+
# clean up and save SMILES
|
142
145
|
ionized_smiles = IonizedStates.clean_smiles(edmol)
|
143
146
|
ionized_mol = Chem.MolFromSmiles(ionized_smiles)
|
144
147
|
ionized_mol = Chem.AddHs(ionized_mol)
|
@@ -46,7 +46,8 @@ from rdworks.display import render_svg, render_png
|
|
46
46
|
|
47
47
|
from scour.scour import scourString
|
48
48
|
|
49
|
-
|
49
|
+
|
50
|
+
logger = logging.getLogger(__name__)
|
50
51
|
|
51
52
|
|
52
53
|
class Mol:
|
@@ -328,11 +329,7 @@ class Mol:
|
|
328
329
|
return self
|
329
330
|
|
330
331
|
|
331
|
-
def make_confs(self,
|
332
|
-
n:int = 50,
|
333
|
-
method:str = 'ETKDG',
|
334
|
-
calculator:str | Callable = 'MMFF94',
|
335
|
-
) -> Self:
|
332
|
+
def make_confs(self, n: int = 50, method: str = 'ETKDG', **kwargs) -> Self:
|
336
333
|
"""Generates 3D conformers.
|
337
334
|
|
338
335
|
Args:
|
@@ -348,12 +345,8 @@ class Mol:
|
|
348
345
|
High-Quality Conformer Generation with CONFORGE: Algorithm and Performance Assessment.
|
349
346
|
J. Chem. Inf. Model. 63, 5549-5570 (2023).
|
350
347
|
"""
|
351
|
-
|
352
|
-
|
353
|
-
# rot_bonds = rd_descriptor_f['RotBonds'](self.rdmol)
|
354
|
-
# n = min(max(1, int(8.481 * (rot_bonds **1.642))), 1000)
|
355
|
-
# n = max(1, math.ceil(n * n_rel)) # ensures that n is at least 1
|
356
|
-
|
348
|
+
verbose = kwargs.get('verbose', False)
|
349
|
+
|
357
350
|
self.confs = []
|
358
351
|
|
359
352
|
if method.upper() == 'ETKDG':
|
@@ -426,6 +419,7 @@ class Mol:
|
|
426
419
|
os.remove(tmp_filename)
|
427
420
|
|
428
421
|
# energy evaluations for ranking
|
422
|
+
calculator = kwargs.get('calculator', 'MMFF94')
|
429
423
|
for conf in self.confs:
|
430
424
|
conf.potential_energy(calculator) # default: MMFF94
|
431
425
|
|
@@ -436,8 +430,15 @@ class Mol:
|
|
436
430
|
for conf in self.confs:
|
437
431
|
conf.props.update({"E_rel(kcal/mol)": conf.props[sort_by] - lowest_energy})
|
438
432
|
|
433
|
+
# rename conformers
|
439
434
|
self = self.rename()
|
440
435
|
|
436
|
+
if verbose:
|
437
|
+
rot_bonds = rd_descriptor_f['RotBonds'](self.rdmol)
|
438
|
+
nrb_suggested = int(8.481 * (rot_bonds **1.642))
|
439
|
+
logger.info(f"make_confs() rotatable bonds {rot_bonds} (suggested conformers {nrb_suggested}) generated {self.count()}")
|
440
|
+
logger.info(f"make_confs() updated potential energies E_tot(kcal/mol) and E_rel(kcal/mol) by {calculator}")
|
441
|
+
|
441
442
|
return self
|
442
443
|
|
443
444
|
|
@@ -499,10 +500,15 @@ class Mol:
|
|
499
500
|
Returns:
|
500
501
|
Self: modified self.
|
501
502
|
"""
|
503
|
+
verbose = kwargs.get('verbose', False)
|
504
|
+
|
502
505
|
if calculator is not None:
|
503
506
|
# re-calculate potential energies
|
507
|
+
if verbose :
|
508
|
+
logger.info(f"sort_cons() calculate potential energy by {calculator}")
|
509
|
+
|
504
510
|
for conf in self.confs:
|
505
|
-
PE = conf.potential_energy(calculator
|
511
|
+
PE = conf.potential_energy(calculator, **kwargs) # sets `E_tot(kcal/mol)`
|
506
512
|
|
507
513
|
if all(['E_tot(kcal/mol)' in conf.props for conf in self.confs]):
|
508
514
|
sort_by = 'E_tot(kcal/mol)'
|
@@ -767,7 +773,7 @@ class Mol:
|
|
767
773
|
cluster: bool | None =None,
|
768
774
|
k: int | None = None,
|
769
775
|
window: float | None = None,
|
770
|
-
|
776
|
+
**kwargs) -> Self:
|
771
777
|
"""Drop conformers that meet some condition(s).
|
772
778
|
|
773
779
|
Args:
|
@@ -792,6 +798,8 @@ class Mol:
|
|
792
798
|
Self: modified self.
|
793
799
|
"""
|
794
800
|
|
801
|
+
verbose = kwargs.get('verbose', False)
|
802
|
+
|
795
803
|
reasons = [f'stereo flipped',
|
796
804
|
f'unconverged',
|
797
805
|
f'similar({similar_rmsd})',
|
@@ -805,13 +813,13 @@ class Mol:
|
|
805
813
|
mask = [Chem.MolToSmiles(Chem.RemoveHs(_.rdmol)) == self.smiles for _ in self.confs]
|
806
814
|
self.confs = list(itertools.compress(self.confs, mask))
|
807
815
|
if verbose:
|
808
|
-
|
816
|
+
logger.info(f'drop_confs() {mask.count(False):3d} {reasons[0]:<{w}} -> {self.count()}')
|
809
817
|
|
810
818
|
if unconverged and self.count() > 0:
|
811
819
|
mask = [_.props['Converged'] if 'Converged' in _.props else True for _ in self.confs]
|
812
820
|
self.confs = list(itertools.compress(self.confs, mask))
|
813
821
|
if verbose:
|
814
|
-
|
822
|
+
logger.info(f'drop_confs() {mask.count(False):3d} {reasons[1]:<{w}} -> {self.count()}')
|
815
823
|
|
816
824
|
if similar and self.count() > 1:
|
817
825
|
# it is observed that there are essentially identical conformers
|
@@ -831,7 +839,7 @@ class Mol:
|
|
831
839
|
mask = [conf_idx in centroid_indices for conf_idx, conf in enumerate(self.confs)]
|
832
840
|
self.confs = list(itertools.compress(self.confs, mask))
|
833
841
|
if verbose:
|
834
|
-
|
842
|
+
logger.info(f'drop_confs() {mask.count(False):3d} {reasons[2]:<{w}} -> {self.count()}')
|
835
843
|
|
836
844
|
# note: it will retain the conformers with lower index
|
837
845
|
# so, it should be sorted before dropping
|
@@ -856,9 +864,12 @@ class Mol:
|
|
856
864
|
mask = [_.props['centroid'] if 'centroid' in _.props else True for _ in self.confs]
|
857
865
|
self.confs = list(itertools.compress(self.confs, mask))
|
858
866
|
if verbose:
|
859
|
-
|
867
|
+
logger.info(f'drop_confs() {mask.count(False):3d} {reasons[3]:<{w}} -> {self.count()}')
|
860
868
|
|
861
869
|
if (k or window) and self.count() > 0:
|
870
|
+
# confs must be sorted by energies
|
871
|
+
if not all(['E_rel(kcal/mol)' in _.props for _ in self.confs]):
|
872
|
+
self = self.sort_confs(**kwargs)
|
862
873
|
if k:
|
863
874
|
mask_k = [i < k for i,_ in enumerate(self.confs)]
|
864
875
|
else:
|
@@ -871,7 +882,7 @@ class Mol:
|
|
871
882
|
mask = [(x and y) for (x,y) in zip(mask_k, mask_window)]
|
872
883
|
self.confs = list(itertools.compress(self.confs, mask))
|
873
884
|
if verbose:
|
874
|
-
|
885
|
+
logger.info(f'drop_confs() {mask.count(False):3d} {reasons[4]:<{w}} -> {self.count()}')
|
875
886
|
|
876
887
|
return self
|
877
888
|
|
@@ -974,7 +985,7 @@ class Mol:
|
|
974
985
|
|
975
986
|
|
976
987
|
def torsion_energies(self,
|
977
|
-
calculator: str | Callable,
|
988
|
+
calculator: str | Callable = 'MMFF94',
|
978
989
|
torsion_key: int | None = None,
|
979
990
|
simplify: bool = True,
|
980
991
|
fmax: float = 0.05,
|
@@ -14,7 +14,8 @@ from rdkit import Chem
|
|
14
14
|
from rdkit.Geometry import Point3D
|
15
15
|
|
16
16
|
|
17
|
-
logger = logging.getLogger()
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
18
19
|
|
19
20
|
# In ASE, the default energy unit is eV (electron volt).
|
20
21
|
# It will be converted to kcal/mol
|
@@ -25,7 +26,7 @@ ev2kcalpermol = 23.060547830619026
|
|
25
26
|
|
26
27
|
|
27
28
|
class GFN2xTB:
|
28
|
-
def __init__(self, molecule: Chem.Mol, ncores: int =
|
29
|
+
def __init__(self, molecule: Chem.Mol, ncores: int | None = None):
|
29
30
|
assert isinstance(molecule, Chem.Mol), "molecule is not rdkit.Chem.Mol type"
|
30
31
|
assert molecule.GetConformer().Is3D(), "molecule is not a 3D conformer"
|
31
32
|
assert self.is_xtb_ready(), "xtb is not accessible"
|
@@ -35,6 +36,9 @@ class GFN2xTB:
|
|
35
36
|
self.symbols = [ atom.GetSymbol() for atom in molecule.GetAtoms() ]
|
36
37
|
self.positions = molecule.GetConformer().GetPositions().tolist()
|
37
38
|
|
39
|
+
if ncores is None:
|
40
|
+
ncores = os.cpu_count()
|
41
|
+
|
38
42
|
# Parallelisation
|
39
43
|
os.environ['OMP_STACKSIZE'] = '4G'
|
40
44
|
os.environ['OMP_NUM_THREADS'] = f'{ncores},1'
|
@@ -107,7 +111,7 @@ class GFN2xTB:
|
|
107
111
|
Returns:
|
108
112
|
str | None: version statement.
|
109
113
|
"""
|
110
|
-
if GFN2xTB.
|
114
|
+
if GFN2xTB.is_xtb_ready():
|
111
115
|
cmd = ['xtb', '--version']
|
112
116
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
113
117
|
assert proc.returncode == 0, "GFN2xTB() Error: xtb not available"
|
@@ -338,12 +342,9 @@ class GFN2xTB:
|
|
338
342
|
|
339
343
|
with tempfile.TemporaryDirectory() as temp_dir: # tmpdir is a string
|
340
344
|
workdir = Path(temp_dir)
|
341
|
-
if verbose:
|
342
|
-
logger.info(f'xtb.singlepoint workdir= {temp_dir}')
|
343
345
|
|
344
346
|
geometry_input_path = workdir / 'geometry.xyz'
|
345
347
|
xtbout_path = workdir / 'xtbout.json'
|
346
|
-
stdout_path = workdir / 'fort.6'
|
347
348
|
wbo_path = workdir / 'wbo'
|
348
349
|
geometry_output_path = workdir / 'xtbtopo.mol'
|
349
350
|
|
@@ -354,8 +355,6 @@ class GFN2xTB:
|
|
354
355
|
|
355
356
|
options = ['--gfn', '2', '--json']
|
356
357
|
|
357
|
-
Gsolv = None
|
358
|
-
|
359
358
|
if water is not None and isinstance(water, str):
|
360
359
|
if water == 'gbsa':
|
361
360
|
options += ['--gbsa', 'H2O']
|
@@ -366,6 +365,9 @@ class GFN2xTB:
|
|
366
365
|
elif water == 'cpcmx' and self.is_cpcmx_option_ready():
|
367
366
|
options += ['--cpcmx', 'water']
|
368
367
|
|
368
|
+
if verbose:
|
369
|
+
logger.info(f"singlepoint() {' '.join(cmd+options)}")
|
370
|
+
|
369
371
|
# 'xtbout.json', 'xtbrestart', 'xtbtopo.mol', 'charges', and 'wbo' files will be
|
370
372
|
# created in the current working directory.
|
371
373
|
proc = subprocess.run(cmd + options, cwd=temp_dir, capture_output=True, text=True)
|
@@ -381,19 +383,20 @@ class GFN2xTB:
|
|
381
383
|
if xtbout_path.is_file():
|
382
384
|
with open(xtbout_path, 'r') as f:
|
383
385
|
datadict = json.load(f) # takes the file object as input
|
386
|
+
|
387
|
+
Gsolv = None
|
384
388
|
|
385
|
-
if
|
389
|
+
if water is not None:
|
386
390
|
# Free Energy contributions: [Eh] [kcal/mol]
|
387
391
|
# -------------------------------------------------------------------------
|
388
392
|
# solvation free energy (dG_solv): -0.92587E-03 -0.58099
|
389
393
|
# gas phase energy (E) -0.52068E+01
|
390
394
|
# -------------------------------------------------------------------------
|
391
395
|
# total free energy (dG) -0.52077E+01
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
Gsolv = float(m.group('kcalpermol'))
|
396
|
+
for line in proc.stdout.splitlines():
|
397
|
+
if 'solvation free energy' in line:
|
398
|
+
m = re.search(r"solvation free energy \(dG_solv\)\:\s+[-+]?\d*\.?\d+E[-+]?\d*\s+(?P<kcalpermol>[-+]?\d*\.?\d+)", line)
|
399
|
+
Gsolv = float(m.group('kcalpermol'))
|
397
400
|
|
398
401
|
Wiberg_bond_orders = self.load_wbo(wbo_path)
|
399
402
|
|
@@ -429,8 +432,6 @@ class GFN2xTB:
|
|
429
432
|
"""
|
430
433
|
with tempfile.TemporaryDirectory() as temp_dir: # tmpdir is a string
|
431
434
|
workdir = Path(temp_dir)
|
432
|
-
if verbose:
|
433
|
-
logger.info(f'xtb.optimize workdir= {temp_dir}')
|
434
435
|
|
435
436
|
geometry_input_path = workdir / 'geometry.xyz'
|
436
437
|
xtbout_path = workdir / 'xtbout.json'
|
@@ -452,6 +453,9 @@ class GFN2xTB:
|
|
452
453
|
elif water == 'cpcmx':
|
453
454
|
logger.warning('optimize with --cpcmx option is not implemented in xtb yet')
|
454
455
|
|
456
|
+
if verbose:
|
457
|
+
logger.info(f"optimize() {' '.join(cmd+options)}")
|
458
|
+
|
455
459
|
proc = subprocess.run(cmd + options, cwd=temp_dir, capture_output=True, text=True)
|
456
460
|
|
457
461
|
if proc.returncode == 0 and xtbout_path.is_file():
|
@@ -523,4 +527,4 @@ class GFN2xTB:
|
|
523
527
|
with open(xtb_esp_dat, 'r') as f:
|
524
528
|
pass
|
525
529
|
|
526
|
-
return None
|
530
|
+
return None
|
@@ -13,6 +13,12 @@ def test_ionizedstate():
|
|
13
13
|
print(k, v)
|
14
14
|
print()
|
15
15
|
|
16
|
+
p = x.get_pairs()
|
17
|
+
print('pairs:')
|
18
|
+
for k in p:
|
19
|
+
print(k)
|
20
|
+
print()
|
21
|
+
|
16
22
|
indices = d['CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1nnc(Cc2ccc(C)cc2)o1'][0]
|
17
23
|
|
18
24
|
assert (11, 'B') in indices
|
@@ -30,5 +36,31 @@ def test_ionizedstate():
|
|
30
36
|
assert set(expected).intersection(set(results)) == set(expected)
|
31
37
|
|
32
38
|
|
39
|
+
def test_gypsum_dl():
|
40
|
+
import gypsum_dl
|
41
|
+
smiles = 'O=C(NCCCC)[C@H](CCC1)N1[C@@H](CC)C2=NN=C(CC3=CC=C(C)C=C3)O2'
|
42
|
+
state_smiles = list(
|
43
|
+
gypsum_dl.GypsumDL(smiles,
|
44
|
+
min_ph=6.4,
|
45
|
+
max_ph=8.4,
|
46
|
+
pka_precision=1.0,
|
47
|
+
thoroughness=3,
|
48
|
+
max_variants_per_compound=5,
|
49
|
+
second_embed=False,
|
50
|
+
skip_optimize_geometry=False,
|
51
|
+
skip_alternate_ring_conformations=False,
|
52
|
+
skip_adding_hydrogen=False,
|
53
|
+
skip_making_tautomers=False,
|
54
|
+
skip_enumerate_chiral_mol=False,
|
55
|
+
skip_enumerate_double_bonds=False,
|
56
|
+
let_tautomers_change_chirality=False,
|
57
|
+
use_durrant_lab_filters=True,
|
58
|
+
job_manager='serial',
|
59
|
+
num_processors=1,
|
60
|
+
))
|
61
|
+
for smi in state_smiles:
|
62
|
+
print(smi)
|
63
|
+
|
33
64
|
if __name__ == '__main__':
|
34
|
-
test_ionizedstate()
|
65
|
+
test_ionizedstate()
|
66
|
+
test_gypsum_dl()
|
@@ -0,0 +1,160 @@
|
|
1
|
+
from rdworks import Mol
|
2
|
+
from rdworks.xtb.wrapper import GFN2xTB
|
3
|
+
from rdworks.testdata import drugs
|
4
|
+
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
|
8
|
+
# In ASE, the default energy unit is eV (electron volt).
|
9
|
+
# It will be converted to kcal/mol
|
10
|
+
# CODATA 2018 energy conversion factor
|
11
|
+
hartree2ev = 27.211386245988
|
12
|
+
hartree2kcalpermol = 627.50947337481
|
13
|
+
ev2kcalpermol = 23.060547830619026
|
14
|
+
|
15
|
+
|
16
|
+
datadir = Path(__file__).parent.resolve() / "data"
|
17
|
+
workdir = Path(__file__).parent.resolve() / "outfiles"
|
18
|
+
|
19
|
+
workdir.mkdir(exist_ok=True)
|
20
|
+
|
21
|
+
name = 'Atorvastatin'
|
22
|
+
testmol = Mol(drugs[name], name).make_confs(n=50).optimize_confs()
|
23
|
+
testmol = testmol.drop_confs(similar=True, verbose=True).sort_confs()
|
24
|
+
|
25
|
+
|
26
|
+
def test_xtb_wrapper():
|
27
|
+
from rdworks.xtb.wrapper import GFN2xTB
|
28
|
+
assert GFN2xTB.is_xtb_ready() == True
|
29
|
+
assert GFN2xTB.is_cpx_ready() == True
|
30
|
+
assert GFN2xTB.is_cpcmx_option_ready() == True
|
31
|
+
assert GFN2xTB.is_ready() == True
|
32
|
+
assert GFN2xTB.version() is not None
|
33
|
+
|
34
|
+
|
35
|
+
def test_singlepoint():
|
36
|
+
mol = testmol.copy()
|
37
|
+
|
38
|
+
print("number of conformers=", mol.count())
|
39
|
+
print("number of atoms=", mol.confs[0].natoms)
|
40
|
+
|
41
|
+
gfn2xtb = GFN2xTB(mol.confs[0].rdmol, ncores=8)
|
42
|
+
|
43
|
+
print("GFN2xTB.singlepoint()")
|
44
|
+
outdict = gfn2xtb.singlepoint()
|
45
|
+
print(outdict)
|
46
|
+
print()
|
47
|
+
|
48
|
+
print("GFN2xTB.singlepoint(water='gbsa')")
|
49
|
+
outdict = gfn2xtb.singlepoint(water='gbsa')
|
50
|
+
print(outdict)
|
51
|
+
print()
|
52
|
+
|
53
|
+
print("GFN2xTB.singlepoint(water='alpb')")
|
54
|
+
outdict = gfn2xtb.singlepoint(water='alpb')
|
55
|
+
print(outdict)
|
56
|
+
print()
|
57
|
+
|
58
|
+
print("GFN2xTB.singlepoint(water='cpcmx')")
|
59
|
+
outdict = gfn2xtb.singlepoint(water='cpcmx')
|
60
|
+
print(outdict)
|
61
|
+
print()
|
62
|
+
|
63
|
+
|
64
|
+
def test_optimize():
|
65
|
+
mol = testmol.copy()
|
66
|
+
print("number of conformers=", mol.count())
|
67
|
+
print("GFN2xTB.optimize()")
|
68
|
+
outdict = GFN2xTB(mol.confs[0].rdmol, ncores=8).optimize(verbose=True)
|
69
|
+
print(outdict)
|
70
|
+
print()
|
71
|
+
|
72
|
+
|
73
|
+
def test_state_generate():
|
74
|
+
import rdworks
|
75
|
+
import numpy as np
|
76
|
+
import os
|
77
|
+
|
78
|
+
task_queue = 'xtb'
|
79
|
+
|
80
|
+
kT = 0.001987 * 300.0 # (kcal/mol K)
|
81
|
+
|
82
|
+
smiles = 'CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1nnc(Cc2ccc(C)cc2)o1'
|
83
|
+
n = 50
|
84
|
+
method = 'ETKDG'
|
85
|
+
|
86
|
+
standardized = rdworks.Mol(smiles)
|
87
|
+
libr = rdworks.complete_tautomers(standardized)
|
88
|
+
|
89
|
+
PE = []
|
90
|
+
for mol in libr:
|
91
|
+
mol = mol.make_confs(n=n, method=method, verbose=True)
|
92
|
+
mol = mol.optimize_confs(calculator='MMFF94', verbose=True)
|
93
|
+
mol = mol.drop_confs(similar=True, similar_rmsd=0.3, verbose=True)
|
94
|
+
mol = mol.sort_confs(calculator='xTB', verbose=True)
|
95
|
+
mol = mol.drop_confs(k=10, window=10.0, verbose=True) # enforcing both conditions
|
96
|
+
_PE = []
|
97
|
+
for conf in mol.confs:
|
98
|
+
conf = conf.optimize(calculator='xTB', verbose=True)
|
99
|
+
# GFN2xTB requires 3D coordinates
|
100
|
+
xtb = GFN2xTB(conf.rdmol).singlepoint(water='cpcmx', verbose=True)
|
101
|
+
_PE.append(xtb.PE)
|
102
|
+
# SimpleNamespace(
|
103
|
+
# PE = datadict['total energy'] * hartree2kcalpermol,
|
104
|
+
# Gsolv = Gsolv,
|
105
|
+
# charges = datadict['partial charges'],
|
106
|
+
# wbo = Wiberg_bond_orders,
|
107
|
+
# )
|
108
|
+
PE.append(_PE)
|
109
|
+
print(_PE)
|
110
|
+
|
111
|
+
# calculate population
|
112
|
+
PE = np.array(PE)
|
113
|
+
PE = PE - np.min(PE)
|
114
|
+
Boltzmann_factors = np.exp(-PE/kT)
|
115
|
+
# partition function
|
116
|
+
Z = np.sum(Boltzmann_factors)
|
117
|
+
# population
|
118
|
+
p = np.sum(Boltzmann_factors/Z, axis=1)
|
119
|
+
|
120
|
+
sorted_indices = sorted(list(enumerate(p)), key=lambda x: x[1], reverse=True) # [(0,p0), (1,p1), ...]
|
121
|
+
|
122
|
+
molecular_states = []
|
123
|
+
for idx, population in sorted_indices:
|
124
|
+
if population < 0.05:
|
125
|
+
continue
|
126
|
+
|
127
|
+
# state.keys() = ['rdmol','smiles','charge','population','pKa', 'qikprop']
|
128
|
+
|
129
|
+
state_mol = libr[idx].rename(f'state.{idx+1}').qed(
|
130
|
+
properties=['QED', 'MolWt', 'LogP', 'TPSA', 'HBD', 'HBA'])
|
131
|
+
|
132
|
+
basic_properties = {
|
133
|
+
'QED' : round(state_mol.props['QED'], 2),
|
134
|
+
'MolWt' : round(state_mol.props['MolWt'], 2),
|
135
|
+
'LogP' : round(state_mol.props['LogP'], 2),
|
136
|
+
'TPSA' : round(state_mol.props['TPSA'], 2),
|
137
|
+
'HBD' : state_mol.props['HBD'],
|
138
|
+
'HBA' : state_mol.props['HBA'],
|
139
|
+
}
|
140
|
+
|
141
|
+
state_props = {
|
142
|
+
'method': task_queue,
|
143
|
+
'PE(kcal/mol)': state_mol.confs[0].props['E_tot(kcal/mol)'],
|
144
|
+
'population' : round(float(population), 3),
|
145
|
+
'basic_properties': basic_properties,
|
146
|
+
'rdkit_version': rdworks.__rdkit_version__,
|
147
|
+
'rdworks_version': rdworks.__version__,
|
148
|
+
}
|
149
|
+
|
150
|
+
molecular_states.append((state_mol.serialize(compressed=True), state_props))
|
151
|
+
|
152
|
+
print(molecular_states)
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
if __name__ == '__main__':
|
157
|
+
# test_xtb_wrapper()
|
158
|
+
# test_singlepoint()
|
159
|
+
# test_optimize()
|
160
|
+
test_state_generate()
|
rdworks-0.48.1/tests/test_xtb.py
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
from rdworks import Mol
|
2
|
-
from rdworks.xtb.wrapper import GFN2xTB
|
3
|
-
from rdworks.testdata import drugs
|
4
|
-
|
5
|
-
from pathlib import Path
|
6
|
-
|
7
|
-
|
8
|
-
# In ASE, the default energy unit is eV (electron volt).
|
9
|
-
# It will be converted to kcal/mol
|
10
|
-
# CODATA 2018 energy conversion factor
|
11
|
-
hartree2ev = 27.211386245988
|
12
|
-
hartree2kcalpermol = 627.50947337481
|
13
|
-
ev2kcalpermol = 23.060547830619026
|
14
|
-
|
15
|
-
|
16
|
-
datadir = Path(__file__).parent.resolve() / "data"
|
17
|
-
workdir = Path(__file__).parent.resolve() / "outfiles"
|
18
|
-
|
19
|
-
workdir.mkdir(exist_ok=True)
|
20
|
-
|
21
|
-
name = 'Atorvastatin'
|
22
|
-
testmol = Mol(drugs[name], name).make_confs(n=50).optimize_confs()
|
23
|
-
testmol = testmol.drop_confs(similar=True, verbose=True).sort_confs()
|
24
|
-
|
25
|
-
|
26
|
-
def test_xtb_wrapper():
|
27
|
-
from rdworks.xtb.wrapper import GFN2xTB
|
28
|
-
assert GFN2xTB.is_xtb_ready() == True
|
29
|
-
assert GFN2xTB.is_cpx_ready() == True
|
30
|
-
assert GFN2xTB.is_cpcmx_option_ready() == True
|
31
|
-
assert GFN2xTB.is_ready() == True
|
32
|
-
assert GFN2xTB.version() is not None
|
33
|
-
|
34
|
-
|
35
|
-
def test_singlepoint():
|
36
|
-
mol = testmol.copy()
|
37
|
-
|
38
|
-
print("number of conformers=", mol.count())
|
39
|
-
print("number of atoms=", mol.confs[0].natoms)
|
40
|
-
|
41
|
-
gfn2xtb = GFN2xTB(mol.confs[0].rdmol, ncores=8)
|
42
|
-
|
43
|
-
print("GFN2xTB.singlepoint()")
|
44
|
-
outdict = gfn2xtb.singlepoint()
|
45
|
-
print(outdict)
|
46
|
-
print()
|
47
|
-
|
48
|
-
print("GFN2xTB.singlepoint(water='gbsa')")
|
49
|
-
outdict = gfn2xtb.singlepoint(water='gbsa')
|
50
|
-
print(outdict)
|
51
|
-
print()
|
52
|
-
|
53
|
-
print("GFN2xTB.singlepoint(water='alpb')")
|
54
|
-
outdict = gfn2xtb.singlepoint(water='alpb')
|
55
|
-
print(outdict)
|
56
|
-
print()
|
57
|
-
|
58
|
-
print("GFN2xTB.singlepoint(water='cpcmx')")
|
59
|
-
outdict = gfn2xtb.singlepoint(water='cpcmx')
|
60
|
-
print(outdict)
|
61
|
-
print()
|
62
|
-
|
63
|
-
|
64
|
-
def test_optimize():
|
65
|
-
mol = testmol.copy()
|
66
|
-
print("number of conformers=", mol.count())
|
67
|
-
print("GFN2xTB.optimize()")
|
68
|
-
outdict = GFN2xTB(mol.confs[0].rdmol, ncores=8).optimize(verbose=True)
|
69
|
-
print(outdict)
|
70
|
-
print()
|
71
|
-
|
72
|
-
|
73
|
-
if __name__ == '__main__':
|
74
|
-
test_xtb_wrapper()
|
75
|
-
test_singlepoint()
|
76
|
-
test_optimize()
|
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
|