rdworks 0.47.1__tar.gz → 0.48.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 (87) hide show
  1. {rdworks-0.47.1 → rdworks-0.48.1}/PKG-INFO +1 -1
  2. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/__init__.py +1 -1
  3. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/ionized.py +83 -59
  4. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks.egg-info/PKG-INFO +1 -1
  5. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks.egg-info/SOURCES.txt +3 -5
  6. {rdworks-0.47.1 → rdworks-0.48.1}/tests/test_basics.py +1 -99
  7. rdworks-0.48.1/tests/test_ionized.py +34 -0
  8. rdworks-0.47.1/tests/test_decimals.py → rdworks-0.48.1/tests/test_round.py +79 -71
  9. rdworks-0.48.1/tests/test_torsion.py +98 -0
  10. {rdworks-0.47.1 → rdworks-0.48.1}/tests/test_xtb.py +11 -7
  11. rdworks-0.47.1/tests/test_gypsumdl.py +0 -15
  12. rdworks-0.47.1/tests/test_iupac_name.py +0 -39
  13. rdworks-0.47.1/tests/test_nn_xtb.py +0 -91
  14. rdworks-0.47.1/tests/test_web.py +0 -378
  15. {rdworks-0.47.1 → rdworks-0.48.1}/LICENSE +0 -0
  16. {rdworks-0.47.1 → rdworks-0.48.1}/README.md +0 -0
  17. {rdworks-0.47.1 → rdworks-0.48.1}/pyproject.toml +0 -0
  18. {rdworks-0.47.1 → rdworks-0.48.1}/setup.cfg +0 -0
  19. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/__init__.py +0 -0
  20. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/autograph.py +0 -0
  21. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/centroid.py +0 -0
  22. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/dynamictreecut.py +0 -0
  23. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/nmrclust.py +0 -0
  24. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/autograph/rckmeans.py +0 -0
  25. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/bitqt/__init__.py +0 -0
  26. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/bitqt/bitqt.py +0 -0
  27. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/conf.py +0 -0
  28. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/descriptor.py +0 -0
  29. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/display.py +0 -0
  30. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/matchedseries.py +0 -0
  31. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/mol.py +0 -0
  32. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/mollibr.py +0 -0
  33. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/pka.py +0 -0
  34. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Asinex_fragment.xml +0 -0
  35. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Astex_RO3.xml +0 -0
  36. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010A.xml +0 -0
  37. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010B.xml +0 -0
  38. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/Baell2010C.xml +0 -0
  39. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-015-hits.xml +0 -0
  40. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-less-than-150-hits.xml +0 -0
  41. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/PAINS-more-than-150-hits.xml +0 -0
  42. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Baell2010_PAINS/makexml.py +0 -0
  43. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Brenk2008_Dundee/makexml.py +0 -0
  44. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/CNS.xml +0 -0
  45. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/BMS.xml +0 -0
  46. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/Dundee.xml +0 -0
  47. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/Glaxo.xml +0 -0
  48. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/Inpharmatica.xml +0 -0
  49. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/LINT.xml +0 -0
  50. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/MLSMR.xml +0 -0
  51. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/PAINS.xml +0 -0
  52. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/SureChEMBL.xml +0 -0
  53. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ChEMBL_Walters/makexml.py +0 -0
  54. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999.xml +0 -0
  55. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Acid.xml +0 -0
  56. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999Base.xml +0 -0
  57. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999ElPh.xml +0 -0
  58. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/Hann1999NuPh.xml +0 -0
  59. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Hann1999_Glaxo/makexml.py +0 -0
  60. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Kazius2005/Kazius2005.xml +0 -0
  61. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/Kazius2005/makexml.py +0 -0
  62. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ZINC_druglike.xml +0 -0
  63. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ZINC_fragment.xml +0 -0
  64. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ZINC_leadlike.xml +0 -0
  65. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/fragment.xml +0 -0
  66. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ionized/simple_smarts_pattern.csv +0 -0
  67. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/ionized/smarts_pattern.csv +0 -0
  68. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/misc/makexml.py +0 -0
  69. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/misc/reactive-part-2.xml +0 -0
  70. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/misc/reactive-part-3.xml +0 -0
  71. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/predefined/misc/reactive.xml +0 -0
  72. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/readin.py +0 -0
  73. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/rgroup.py +0 -0
  74. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/scaffold.py +0 -0
  75. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/std.py +0 -0
  76. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/stereoisomers.py +0 -0
  77. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/tautomers.py +0 -0
  78. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/testdata.py +0 -0
  79. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/torsion.py +0 -0
  80. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/units.py +0 -0
  81. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/utils.py +0 -0
  82. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/xml.py +0 -0
  83. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/xtb/__init__.py +0 -0
  84. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks/xtb/wrapper.py +0 -0
  85. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks.egg-info/dependency_links.txt +0 -0
  86. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks.egg-info/requires.txt +0 -0
  87. {rdworks-0.47.1 → rdworks-0.48.1}/src/rdworks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdworks
3
- Version: 0.47.1
3
+ Version: 0.48.1
4
4
  Summary: Routine 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.47.1'
1
+ __version__ = '0.48.1'
2
2
 
3
3
  from rdworks.conf import Conf
4
4
  from rdworks.mol import Mol
@@ -1,4 +1,5 @@
1
1
  import importlib.resources
2
+ from types import SimpleNamespace
2
3
  import pandas as pd
3
4
 
4
5
  from rdkit import Chem
@@ -6,6 +7,11 @@ from rdkit import Chem
6
7
  # adapted from https://github.com/dptech-corp/Uni-pKa/enumerator
7
8
 
8
9
  class IonizedStates:
10
+ """Knowledge-based enumeration of (de)protonated states"""
11
+
12
+ smarts_path = importlib.resources.files('rdworks.predefined.ionized')
13
+ ionization_patterns = pd.read_csv(smarts_path / 'simple_smarts_pattern.csv')
14
+
9
15
  # Unreasonable chemical structures
10
16
  unreasonable_patterns = [
11
17
  Chem.MolFromSmarts(s) for s in [
@@ -31,67 +37,38 @@ class IonizedStates:
31
37
  "[N+1](=O)-[O]-[H]",
32
38
  ]]
33
39
 
34
- smarts_path = importlib.resources.files('rdworks.predefined.ionized')
35
- protonation_patterns = pd.read_csv(smarts_path / 'simple_smarts_pattern.csv')
36
40
 
37
- def __init__(self, smiles:str):
41
+ def __init__(self, smiles: str, charge_min: int = -2, charge_max: int = 2):
38
42
  self.smiles = Chem.CanonSmiles(smiles)
43
+ self.charge_max = charge_max
44
+ self.charge_min = charge_min
45
+
39
46
  self.rdmol = Chem.MolFromSmiles(self.smiles)
40
47
  self.rdmol_H = Chem.AddHs(self.rdmol)
41
48
  self.charge = Chem.GetFormalCharge(self.rdmol_H)
42
- self.charge_max = 2
43
- self.charge_min = -2
49
+
44
50
  # initial states
45
51
  self.states = {self.smiles : (self.rdmol_H, self.charge)}
46
- # initial protonation sites
47
- self.protonation_sites = {self.smiles : self.set_protonation_sites(self.smiles)}
48
- # generate initial states
49
- self.protonate(self.smiles)
52
+
53
+ # initial ionization sites
54
+ self.sites = {self.smiles: self.set_ionization_sites(self.smiles)}
50
55
 
51
-
52
- def get_protonation_sites(self) -> dict:
53
- return self.protonation_sites
56
+ # pKa pairs:
57
+ # HA(acid) + H2O == A-(base) + H3O+ or HA+(acid) + H2O == A(base) + H3O+
58
+ self.pairs = []
54
59
 
55
-
56
- def get_states_by_charge(self) -> dict:
60
+ # iteratively build an ensemble of ionized states
57
61
  self.ensemble()
58
- data = {}
59
- for smiles, (romol, charge) in self.states.items():
60
- if charge in data:
61
- data[charge].append(smiles)
62
- else:
63
- data[charge] = [smiles]
64
62
 
65
- return data
66
-
67
- def get_states(self) -> list:
68
- return [smiles for smiles in self.states]
69
-
70
-
71
- def get_states_mol(self) -> list[Chem.Mol]:
72
- return [romol for smiles, (romol, charge) in self.states.items()]
73
-
74
-
75
- def get_num_states(self) -> int:
76
- return len(self.states)
77
63
 
78
64
 
79
65
  @staticmethod
80
- def clean_smiles(rdmol:Chem.Mol) -> str:
81
- Chem.SanitizeMol(rdmol)
82
- rdmol = Chem.MolFromSmiles(Chem.MolToSmiles(rdmol))
83
- rdmol_H = Chem.AddHs(rdmol)
84
- rdmol = Chem.RemoveHs(rdmol_H)
85
- return Chem.CanonSmiles(Chem.MolToSmiles(rdmol))
86
-
87
-
88
- @staticmethod
89
- def set_protonation_sites(smiles:str) -> tuple:
66
+ def set_ionization_sites(smiles: str) -> tuple:
90
67
  subject = Chem.MolFromSmiles(smiles)
91
68
  subject = Chem.AddHs(subject)
92
69
  charge = Chem.GetFormalCharge(subject)
93
70
  indices = [] # atom indices of protonation/deprotonation site(s)
94
- for i, name, smarts, smarts_index, acid_or_base in IonizedStates.protonation_patterns.itertuples():
71
+ for i, name, smarts, smarts_index, acid_or_base in IonizedStates.ionization_patterns.itertuples():
95
72
  pattern = Chem.MolFromSmarts(smarts)
96
73
  matches = subject.GetSubstructMatches(pattern)
97
74
  # returns a list of tuples, where each tuple contains the indices
@@ -100,21 +77,34 @@ class IonizedStates:
100
77
  if len(matches) > 0:
101
78
  smarts_index = int(smarts_index)
102
79
  indices += [(match[smarts_index], acid_or_base) for match in matches]
80
+
103
81
  return (list(set(indices)), subject, charge)
82
+
83
+
84
+ @staticmethod
85
+ def clean_smiles(rdmol: Chem.Mol) -> str:
86
+ Chem.SanitizeMol(rdmol)
87
+ rdmol = Chem.MolFromSmiles(Chem.MolToSmiles(rdmol))
88
+ rdmol_H = Chem.AddHs(rdmol)
89
+ rdmol = Chem.RemoveHs(rdmol_H)
90
+ return Chem.CanonSmiles(Chem.MolToSmiles(rdmol))
104
91
 
105
92
 
106
93
  @staticmethod
107
- def reasonable(romol:Chem.Mol) -> bool:
94
+ def reasonable(romol: Chem.Mol) -> bool:
108
95
  return all([len(romol.GetSubstructMatches(p)) == 0 for p in IonizedStates.unreasonable_patterns])
96
+
109
97
 
110
-
111
- def protonate(self, smiles:str) -> int:
98
+ def ionize(self, smiles: str | None = None) -> int:
112
99
  num_added_states = 0
113
100
 
114
- if smiles not in self.protonation_sites:
115
- self.protonation_sites[smiles] = self.set_protonation_sites(smiles)
101
+ if smiles is None:
102
+ smiles = self.smiles
103
+
104
+ if smiles not in self.sites:
105
+ self.sites[smiles] = self.set_ionization_sites(smiles)
116
106
 
117
- (indices, subject, charge) = self.protonation_sites[smiles]
107
+ (indices, subject, charge) = self.sites[smiles]
118
108
 
119
109
  if (charge >= self.charge_max) or (charge <= self.charge_min):
120
110
  # formal charge will be increased or decreased by protonation/deprotonation
@@ -149,22 +139,56 @@ class IonizedStates:
149
139
  edmol = Chem.AddHs(edmol)
150
140
 
151
141
  # Clean up and save SMILES
152
- state_smiles = IonizedStates.clean_smiles(edmol)
153
- state_mol = Chem.MolFromSmiles(state_smiles)
154
- state_mol = Chem.AddHs(state_mol)
155
- state_charge = Chem.GetFormalCharge(state_mol)
156
- if self.reasonable(state_mol):
157
- if state_smiles in self.states:
142
+ ionized_smiles = IonizedStates.clean_smiles(edmol)
143
+ ionized_mol = Chem.MolFromSmiles(ionized_smiles)
144
+ ionized_mol = Chem.AddHs(ionized_mol)
145
+ ionized_charge = Chem.GetFormalCharge(ionized_mol)
146
+ if self.reasonable(ionized_mol):
147
+ if ionized_smiles in self.states:
158
148
  continue
159
- self.states[state_smiles] = (state_mol, state_charge)
149
+ self.states[ionized_smiles] = (ionized_mol, ionized_charge)
160
150
  num_added_states += 1
151
+
152
+ # store acid-base pair information for pKa
153
+ if acid_or_base == 'A':
154
+ self.pairs.append((i, smiles, ionized_smiles))
155
+ elif acid_or_base == 'B':
156
+ self.pairs.append((i, ionized_smiles, smiles))
161
157
 
162
158
  return num_added_states
163
-
159
+
164
160
 
165
161
  def ensemble(self) -> None:
162
+ # populate initial states
163
+ self.ionize()
164
+
165
+ # propagate
166
166
  num_added_states = None
167
167
  while num_added_states is None or num_added_states > 0:
168
- states = self.states.copy()
168
+ states = self.states.copy() # dictionary
169
+ # self.ionize(smiles) below will change self.states
170
+ # so we cannot iterate self.states. Instead we will
171
+ # iterate over a copy of the self.states
169
172
  for smiles in states:
170
- num_added_states = self.protonate(smiles)
173
+ num_added_states = self.ionize(smiles)
174
+
175
+
176
+ def count(self) -> int:
177
+ return len(self.states)
178
+
179
+
180
+ def get_sites(self) -> dict:
181
+ return self.sites
182
+
183
+
184
+ def get_smiles(self) -> list[str]:
185
+ return [smiles for smiles in self.states]
186
+
187
+
188
+ def get_rdmol(self) -> list[Chem.Mol]:
189
+ return [romol for smiles, (romol, charge) in self.states.items()]
190
+
191
+
192
+ def get_pairs(self) -> list:
193
+ return self.pairs
194
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdworks
3
- Version: 0.47.1
3
+ Version: 0.48.1
4
4
  Summary: Routine 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>
@@ -75,9 +75,7 @@ src/rdworks/predefined/misc/reactive.xml
75
75
  src/rdworks/xtb/__init__.py
76
76
  src/rdworks/xtb/wrapper.py
77
77
  tests/test_basics.py
78
- tests/test_decimals.py
79
- tests/test_gypsumdl.py
80
- tests/test_iupac_name.py
81
- tests/test_nn_xtb.py
82
- tests/test_web.py
78
+ tests/test_ionized.py
79
+ tests/test_round.py
80
+ tests/test_torsion.py
83
81
  tests/test_xtb.py
@@ -51,24 +51,6 @@ drug_names = [
51
51
  "Methixene", "Ethopropazine", "Aspirin", "Fluconazole", "Linezolid",
52
52
  ]
53
53
 
54
- # Lahey, S.-L. J., Thien Phuc, T. N. & Rowley, C. N.
55
- # Benchmarking Force Field and the ANI Neural Network Potentials for the
56
- # Torsional Potential Energy Surface of Biaryl Drug Fragments.
57
- # J. Chem. Inf. Model. 60, 6258–6268 (2020)
58
-
59
- torsion_dataset_smiles = [
60
- "C1(C2=CC=CN2)=CC=CC=C1",
61
- "C1(C2=NC=CN2)=CC=CC=C1",
62
- "C1(N2C=CC=C2)=NC=CC=N1",
63
- "C1(C2=NC=NC=N2)=CC=CC=C1",
64
- "C1(N2C=CC=C2)=CC=CC=C1",
65
- "O=C(N1)C=CC=C1C2=COC=C2",
66
- "C1(C2=NC=CC=N2)=NC=CC=N1",
67
- "O=C(N1)C=CC=C1C2=NC=CN2",
68
- ]
69
-
70
- torsion_dataset_names=["07", "09","20", "39", "10", "23", "12", "29"]
71
-
72
54
 
73
55
  def test_init_mol():
74
56
  mol = Mol(drug_smiles[0], drug_names[0])
@@ -458,86 +440,6 @@ def test_optimize_confs():
458
440
  mol = mol.make_confs().optimize_confs(calculator='MMFF94')
459
441
 
460
442
 
461
- def test_xtb_wrapper():
462
- from rdworks.xtb.wrapper import GFN2xTB
463
- assert GFN2xTB.is_xtb_ready() == True
464
- assert GFN2xTB.is_cpx_ready() == True
465
- assert GFN2xTB.is_cpcmx_option_ready() == True
466
- assert GFN2xTB.is_ready() == True
467
- assert GFN2xTB.version() is not None
468
-
469
-
470
- def test_torsion_fragment():
471
- from rdworks.torsion import create_torsion_fragment
472
- 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",
473
- name="atorvastatin").make_confs(n=1)
474
- ta = mol.torsion_atoms()
475
- assert len(ta) == 12
476
- # {0: (0, 1, 3, 7), 1: (3, 4, 32, 33), 2: (4, 5, 26, 27), 3: (7, 6, 19, 20),
477
- # 4: (3, 7, 8, 9), 5: (7, 8, 9, 10), 6: (8, 9, 10, 18), 7: (18, 10, 11, 12),
478
- # 8: (10, 11, 12, 17), 9: (17, 12, 13, 14), 10: (12, 13, 14, 15), 11: (36, 35, 34, 32)}
479
- (frag, frag_ijkl, frag_created, wbo_filtered) = create_torsion_fragment(mol.confs[0].rdmol, ta[6])
480
- assert frag_ijkl == (5, 6, 7, 12)
481
- assert frag_created == True
482
- assert wbo_filtered == True
483
-
484
- mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
485
- ta2 = mol2.torsion_atoms()
486
- # {0: (5, 4, 3, 1)}
487
- assert len(ta2) == 1
488
-
489
- (frag, frag_ijkl, frag_created, wbo_filtered) = create_torsion_fragment(mol2.confs[0].rdmol, ta2[0])
490
- # expects no fragmentation
491
- assert frag == mol2.confs[0].rdmol
492
- assert frag_ijkl == ta2[0]
493
- assert frag_created == False
494
- assert wbo_filtered == False
495
-
496
-
497
- def test_torsion_fragment_from_conf():
498
- from rdworks.torsion import create_torsion_fragment
499
- 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",
500
- name="atorvastatin").make_confs(n=1)
501
- ref_conf = mol.confs[0]
502
- ta = ref_conf.torsion_atoms()
503
- assert len(ta) == 12
504
- # {0: (0, 1, 3, 7), 1: (3, 4, 32, 33), 2: (4, 5, 26, 27), 3: (7, 6, 19, 20),
505
- # 4: (3, 7, 8, 9), 5: (7, 8, 9, 10), 6: (8, 9, 10, 18), 7: (18, 10, 11, 12),
506
- # 8: (10, 11, 12, 17), 9: (17, 12, 13, 14), 10: (12, 13, 14, 15), 11: (36, 35, 34, 32)}
507
- frag, frag_ijkl, frag_created, wbo_filtered = create_torsion_fragment(ref_conf.rdmol, ta[6])
508
- assert frag_ijkl == (5, 6, 7, 12)
509
- assert frag_created == True
510
- assert wbo_filtered == True
511
-
512
- ref_conf = ref_conf.torsion_energies(calculator='MMFF94', torsion_key=6, interval=15)
513
-
514
- mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
515
- ref_conf2 = mol2.confs[0]
516
- ta2 = ref_conf2.torsion_atoms()
517
- # {0: (5, 4, 3, 1)}
518
- assert len(ta2) == 1
519
- frag, frag_ijkl, frag_created, wbo_filtered = create_torsion_fragment(ref_conf2.rdmol, ta2[0])
520
- # expects no fragmentation
521
- assert frag == ref_conf2.rdmol
522
- assert frag_ijkl == ta2[0]
523
- assert frag_created == False
524
- assert wbo_filtered == False
525
-
526
- ref_conf2 = ref_conf2.torsion_energies(calculator='MMFF94', interval=15)
527
- ref_conf3 = ref_conf2.torsion_energies_one(calculator='MMFF94', indices=frag_ijkl)
528
-
529
-
530
- def test_torsion_energies():
531
- libr = MolLibr(torsion_dataset_smiles, torsion_dataset_names)
532
- with open(workdir / 'test_torsion_energies.html', 'w') as f:
533
- for mol in libr[:1]:
534
- mol = mol.make_confs().drop_confs(similar=True, similar_rmsd=0.3).sort_confs().rename()
535
- mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94',
536
- interval=15)
537
- f.write(mol.to_html())
538
- print(mol.dumps('torsion', decimals=2))
539
-
540
-
541
443
  def test_workflow():
542
444
  state_mol = Mol('Cc1nc2cc(Cl)nc(Cl)c2nc1C', 'A-1250')
543
445
  state_mol = state_mol.make_confs(n=50, method='ETKDG')
@@ -563,4 +465,4 @@ def test_serialization():
563
465
  rebuilt = Mol().deserialize(serialized)
564
466
  assert rebuilt.count() == 10
565
467
  assert rebuilt.name == name
566
- assert rebuilt == mol
468
+ assert rebuilt == mol
@@ -0,0 +1,34 @@
1
+ from rdworks import IonizedStates
2
+
3
+
4
+ def test_ionizedstate():
5
+ smiles = 'O=C(NCCCC)[C@H](CCC1)N1[C@@H](CC)C2=NN=C(CC3=CC=C(C)C=C3)O2'
6
+ x = IonizedStates(smiles)
7
+
8
+ assert x.count() == 7
9
+
10
+ d = x.get_sites()
11
+ print('sites:')
12
+ for k, v in d.items():
13
+ print(k, v)
14
+ print()
15
+
16
+ indices = d['CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1nnc(Cc2ccc(C)cc2)o1'][0]
17
+
18
+ assert (11, 'B') in indices
19
+ assert (16, 'B') in indices
20
+ assert (17, 'B') in indices
21
+
22
+ expected = ['CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1nnc(Cc2ccc(C)cc2)o1',
23
+ 'CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1[nH+]nc(Cc2ccc(C)cc2)o1',
24
+ 'CCCCNC(=O)[C@@H]1CCC[NH+]1[C@@H](CC)c1nnc(Cc2ccc(C)cc2)o1',
25
+ 'CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1n[nH+]c(Cc2ccc(C)cc2)o1',
26
+ 'CCCCNC(=O)[C@@H]1CCC[NH+]1[C@@H](CC)c1[nH+]nc(Cc2ccc(C)cc2)o1',
27
+ 'CCCCNC(=O)[C@@H]1CCCN1[C@@H](CC)c1[nH+][nH+]c(Cc2ccc(C)cc2)o1',
28
+ 'CCCCNC(=O)[C@@H]1CCC[NH+]1[C@@H](CC)c1n[nH+]c(Cc2ccc(C)cc2)o1']
29
+ results = x.get_smiles()
30
+ assert set(expected).intersection(set(results)) == set(expected)
31
+
32
+
33
+ if __name__ == '__main__':
34
+ test_ionizedstate()
@@ -1,71 +1,79 @@
1
- from rdworks.utils import recursive_round
2
-
3
- def test_recursive_round():
4
- data1 = {
5
- "name": "Test Data",
6
- "version": 1.0,
7
- "values": [1.23456, 2.34567, {"nested_value": 3.456789}],
8
- "details": {
9
- "temperature": 25.1234567,
10
- "pressure": 1013.256789,
11
- "measurements": [0.000123, 123.45000, 987.654321]
12
- },
13
- "another_list": [
14
- {"a": 1.111, "b": 2.2222},
15
- [3.33333, 4.444444]
16
- ],
17
- "integer_val": 10,
18
- "string_val": "hello"
19
- }
20
-
21
- data2 = [
22
- 10.123,
23
- "string",
24
- [1.0, 2.3456, {"key": 9.87654321}],
25
- {"val1": 7.777777, "val2": [0.1, 0.02, 0.003]}
26
- ]
27
-
28
- data3 = 123.456789
29
-
30
- data4 = "Just a string"
31
-
32
- data5 = [1, 2, 3] # No floats
33
-
34
- print("Original data1:", data1)
35
- modified_data1_dp2 = recursive_round(data1, 2)
36
- print("Modified data1 (2 decimal places):", modified_data1_dp2)
37
- modified_data1_dp0 = recursive_round(data1, 0)
38
-
39
- print("Modified data1 (0 decimal places):", modified_data1_dp0)
40
- modified_data1_dp4 = recursive_round(data1, 4)
41
- print("Modified data1 (4 decimal places):", modified_data1_dp4)
42
-
43
- print("\nOriginal data2:", data2)
44
- modified_data2_dp3 = recursive_round(data2, 3)
45
- print("Modified data2 (3 decimal places):", modified_data2_dp3)
46
-
47
- print("\nOriginal data3:", data3)
48
- modified_data3_dp1 = recursive_round(data3, 1)
49
- print("Modified data3 (1 decimal place):", modified_data3_dp1)
50
-
51
- print("\nOriginal data4:", data4)
52
- modified_data4_dp2 = recursive_round(data4, 2)
53
- print("Modified data4 (2 decimal places):", modified_data4_dp2) # Should be unchanged
54
-
55
- print("\nOriginal data5:", data5)
56
- modified_data5_dp2 = recursive_round(data5, 2)
57
- print("Modified data5 (2 decimal places):", modified_data5_dp2) # Should be unchanged
58
-
59
- # Example of invalid decimal_places
60
- try:
61
- recursive_round(data1, -1)
62
- except ValueError as e:
63
- print(f"\nError caught: {e}")
64
-
65
- try:
66
- recursive_round(data1, 1.5)
67
- except ValueError as e:
68
- print(f"Error caught: {e}")
69
-
70
- if __name__ == '__main__':
71
- test_recursive_round()
1
+ def test_recursive_round():
2
+
3
+ from rdworks.utils import recursive_round
4
+
5
+ data1 = {
6
+ "name": "Test Data",
7
+ "version": 1.0,
8
+ "values": [1.23456, 2.34567, {"nested_value": 3.456789}],
9
+ "details": {
10
+ "temperature": 25.1234567,
11
+ "pressure": 1013.256789,
12
+ "measurements": [0.000123, 123.45000, 987.654321]
13
+ },
14
+ "another_list": [
15
+ {"a": 1.111, "b": 2.2222},
16
+ [3.33333, 4.444444]
17
+ ],
18
+ "integer_val": 10,
19
+ "string_val": "hello"
20
+ }
21
+
22
+ data2 = [
23
+ 10.123,
24
+ "string",
25
+ [1.0, 2.3456, {"key": 9.87654321}],
26
+ {"val1": 7.777777, "val2": [0.1, 0.02, 0.003]}
27
+ ]
28
+
29
+ data3 = 123.456789
30
+
31
+ data4 = "Just a string"
32
+
33
+ data5 = [1, 2, 3] # No floats
34
+
35
+ print("Original data1:", data1)
36
+
37
+ modified_data1_dp2 = recursive_round(data1, 2)
38
+
39
+ assert modified_data1_dp2 == {'name': 'Test Data', 'version': 1.0, 'values': [1.23, 2.35, {'nested_value': 3.46}], 'details': {'temperature': 25.12, 'pressure': 1013.26, 'measurements': [0.0, 123.45, 987.65]}, 'another_list': [{'a': 1.11, 'b': 2.22}, [3.33, 4.44]], 'integer_val': 10, 'string_val': 'hello'}
40
+ print("\nModified data1 (2 decimal places):", modified_data1_dp2)
41
+
42
+ modified_data1_dp0 = recursive_round(data1, 0)
43
+ assert modified_data1_dp0 == {'name': 'Test Data', 'version': 1.0, 'values': [1.0, 2.0, {'nested_value': 3.0}], 'details': {'temperature': 25.0, 'pressure': 1013.0, 'measurements': [0.0, 123.0, 988.0]}, 'another_list': [{'a': 1.0, 'b': 2.0}, [3.0, 4.0]], 'integer_val': 10, 'string_val': 'hello'}
44
+ print("\nModified data1 (0 decimal places):", modified_data1_dp0)
45
+
46
+ print("\nOriginal data2:", data2)
47
+ modified_data2_dp3 = recursive_round(data2, 3)
48
+ assert modified_data2_dp3 == [10.123, 'string', [1.0, 2.346, {'key': 9.877}], {'val1': 7.778, 'val2': [0.1, 0.02, 0.003]}]
49
+ print("\nModified data2 (3 decimal places):", modified_data2_dp3)
50
+
51
+ print("\nOriginal data3:", data3)
52
+ modified_data3_dp1 = recursive_round(data3, 1)
53
+ assert modified_data3_dp1 == 123.5
54
+ print("\nModified data3 (1 decimal place):", modified_data3_dp1)
55
+
56
+ print("\nOriginal data4:", data4)
57
+ modified_data4_dp2 = recursive_round(data4, 2)
58
+ assert modified_data4_dp2 == 'Just a string'
59
+ print("\nModified data4 (2 decimal places):", modified_data4_dp2) # Should be unchanged
60
+
61
+ print("\nOriginal data5:", data5)
62
+ modified_data5_dp2 = recursive_round(data5, 2)
63
+ assert modified_data5_dp2 == [1, 2, 3]
64
+ print("\nModified data5 (2 decimal places):", modified_data5_dp2) # Should be unchanged
65
+
66
+ # Example of invalid decimal_places
67
+ try:
68
+ recursive_round(data1, -1)
69
+ except ValueError as e:
70
+ print(f"\nError caught: {e}")
71
+
72
+ try:
73
+ recursive_round(data1, 1.5)
74
+ except ValueError as e:
75
+ print(f"Error caught: {e}")
76
+
77
+
78
+ if __name__ == '__main__':
79
+ test_recursive_round()
@@ -0,0 +1,98 @@
1
+ from rdworks.torsion import create_torsion_fragment
2
+ from rdworks import Mol, MolLibr
3
+ from pathlib import Path
4
+
5
+
6
+ datadir = Path(__file__).parent.resolve() / "data"
7
+ workdir = Path(__file__).parent.resolve() / "outfiles"
8
+
9
+ workdir.mkdir(exist_ok=True)
10
+
11
+
12
+ # Lahey, S.-L. J., Thien Phuc, T. N. & Rowley, C. N.
13
+ # Benchmarking Force Field and the ANI Neural Network Potentials for the
14
+ # Torsional Potential Energy Surface of Biaryl Drug Fragments.
15
+ # J. Chem. Inf. Model. 60, 6258–6268 (2020)
16
+
17
+ torsion_dataset_smiles = [
18
+ "C1(C2=CC=CN2)=CC=CC=C1",
19
+ "C1(C2=NC=CN2)=CC=CC=C1",
20
+ "C1(N2C=CC=C2)=NC=CC=N1",
21
+ "C1(C2=NC=NC=N2)=CC=CC=C1",
22
+ "C1(N2C=CC=C2)=CC=CC=C1",
23
+ "O=C(N1)C=CC=C1C2=COC=C2",
24
+ "C1(C2=NC=CC=N2)=NC=CC=N1",
25
+ "O=C(N1)C=CC=C1C2=NC=CN2",
26
+ ]
27
+
28
+ torsion_dataset_names=["07", "09","20", "39", "10", "23", "12", "29"]
29
+
30
+
31
+ def test_torsion_fragment():
32
+
33
+ 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",
34
+ name="atorvastatin").make_confs(n=1)
35
+ ta = mol.torsion_atoms()
36
+ assert len(ta) == 12
37
+ # {0: (0, 1, 3, 7), 1: (3, 4, 32, 33), 2: (4, 5, 26, 27), 3: (7, 6, 19, 20),
38
+ # 4: (3, 7, 8, 9), 5: (7, 8, 9, 10), 6: (8, 9, 10, 18), 7: (18, 10, 11, 12),
39
+ # 8: (10, 11, 12, 17), 9: (17, 12, 13, 14), 10: (12, 13, 14, 15), 11: (36, 35, 34, 32)}
40
+ (frag, frag_ijkl, frag_created, wbo_filtered) = create_torsion_fragment(mol.confs[0].rdmol, ta[6])
41
+ assert frag_ijkl == (5, 6, 7, 12)
42
+ assert frag_created == True
43
+ assert wbo_filtered == True
44
+
45
+ mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
46
+ ta2 = mol2.torsion_atoms()
47
+ # {0: (5, 4, 3, 1)}
48
+ assert len(ta2) == 1
49
+
50
+ (frag, frag_ijkl, frag_created, wbo_filtered) = create_torsion_fragment(mol2.confs[0].rdmol, ta2[0])
51
+ # expects no fragmentation
52
+ assert frag == mol2.confs[0].rdmol
53
+ assert frag_ijkl == ta2[0]
54
+ assert frag_created == False
55
+ assert wbo_filtered == False
56
+
57
+
58
+ def test_torsion_fragment_from_conf():
59
+ 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",
60
+ name="atorvastatin").make_confs(n=1)
61
+ ref_conf = mol.confs[0]
62
+ ta = ref_conf.torsion_atoms()
63
+ assert len(ta) == 12
64
+ # {0: (0, 1, 3, 7), 1: (3, 4, 32, 33), 2: (4, 5, 26, 27), 3: (7, 6, 19, 20),
65
+ # 4: (3, 7, 8, 9), 5: (7, 8, 9, 10), 6: (8, 9, 10, 18), 7: (18, 10, 11, 12),
66
+ # 8: (10, 11, 12, 17), 9: (17, 12, 13, 14), 10: (12, 13, 14, 15), 11: (36, 35, 34, 32)}
67
+ frag, frag_ijkl, frag_created, wbo_filtered = create_torsion_fragment(ref_conf.rdmol, ta[6])
68
+ assert frag_ijkl == (5, 6, 7, 12)
69
+ assert frag_created == True
70
+ assert wbo_filtered == True
71
+
72
+ ref_conf = ref_conf.torsion_energies(calculator='MMFF94', torsion_key=6, interval=15)
73
+
74
+ mol2 = Mol(molecule='CC(=O)Nc1ccc(O)cc1', name='acetaminophen.3').make_confs(n=1)
75
+ ref_conf2 = mol2.confs[0]
76
+ ta2 = ref_conf2.torsion_atoms()
77
+ # {0: (5, 4, 3, 1)}
78
+ assert len(ta2) == 1
79
+ frag, frag_ijkl, frag_created, wbo_filtered = create_torsion_fragment(ref_conf2.rdmol, ta2[0])
80
+ # expects no fragmentation
81
+ assert frag == ref_conf2.rdmol
82
+ assert frag_ijkl == ta2[0]
83
+ assert frag_created == False
84
+ assert wbo_filtered == False
85
+
86
+ ref_conf2 = ref_conf2.torsion_energies(calculator='MMFF94', interval=15)
87
+ ref_conf3 = ref_conf2.torsion_energies_one(calculator='MMFF94', indices=frag_ijkl)
88
+
89
+
90
+ def test_torsion_energies():
91
+ libr = MolLibr(torsion_dataset_smiles, torsion_dataset_names)
92
+ with open(workdir / 'test_torsion_energies.html', 'w') as f:
93
+ for mol in libr[:1]:
94
+ mol = mol.make_confs().drop_confs(similar=True, similar_rmsd=0.3).sort_confs().rename()
95
+ mol = mol.optimize_confs(calculator='MMFF94').torsion_energies(calculator='MMFF94',
96
+ interval=15)
97
+ f.write(mol.to_html())
98
+ print(mol.dumps('torsion', decimals=2))