rowan-python 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- rowan/rowan_rdkit/__init__.py +3 -0
- rowan/rowan_rdkit/chem_utils.py +610 -0
- {rowan_python-1.0.0.dist-info → rowan_python-1.1.0.dist-info}/METADATA +2 -2
- {rowan_python-1.0.0.dist-info → rowan_python-1.1.0.dist-info}/RECORD +6 -4
- {rowan_python-1.0.0.dist-info → rowan_python-1.1.0.dist-info}/WHEEL +1 -1
- {rowan_python-1.0.0.dist-info → rowan_python-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
from .chem_utils import pka, tautomers, conformers, energy, optimize, batch_pka, batch_tautomers, batch_energy, batch_optimize, batch_conformers
|
|
2
|
+
|
|
3
|
+
__all__ = ["pka", "tautomers", "energy", "conformers", "optimize", "batch_pka", "batch_tautomers", "batch_energy", "batch_optimize", "batch_conformers"]
|
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
from rdkit import Chem
|
|
2
|
+
from rdkit.Chem import AllChem
|
|
3
|
+
from typing import Literal, TypeAlias, Optional, List
|
|
4
|
+
import rowan
|
|
5
|
+
import copy
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import math
|
|
9
|
+
from rowan.utils import get_api_key
|
|
10
|
+
import stjames
|
|
11
|
+
import time
|
|
12
|
+
import numpy as np
|
|
13
|
+
import cctk
|
|
14
|
+
|
|
15
|
+
RdkitMol: TypeAlias = Chem.rdchem.Mol | Chem.rdchem.RWMol
|
|
16
|
+
pKaMode = Literal["reckless", "rapid", "careful"]
|
|
17
|
+
TautomerMode = Literal["reckless", "rapid", "careful"]
|
|
18
|
+
ConformerMode = Literal["reckless", "rapid"]
|
|
19
|
+
FAST_METHODS: list[stjames.Method] = [
|
|
20
|
+
*stjames.method.XTB_METHODS,
|
|
21
|
+
*stjames.method.NNP_METHODS,
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
class ConversionError(ValueError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class NoConformersError(Exception):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class MethodTooSlowError(Exception):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def _get_rdkit_mol_from_uuid(calculation_uuid: str) -> RdkitMol:
|
|
34
|
+
stjames_mol_dict = rowan.Calculation.retrieve(calculation_uuid)["molecules"][-1]
|
|
35
|
+
stjames_mol = stjames.Molecule(**stjames_mol_dict)
|
|
36
|
+
rdkm = Chem.MolFromXYZBlock(stjames_mol.to_xyz())
|
|
37
|
+
return rdkm
|
|
38
|
+
|
|
39
|
+
def embed_rdkit_mol(rdkm: RdkitMol):
|
|
40
|
+
try:
|
|
41
|
+
AllChem.SanitizeMol(rdkm)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise ValueError(f"Molecule could not be generated -- invalid chemistry!\n{e}")
|
|
44
|
+
|
|
45
|
+
rdkm = AllChem.AddHs(rdkm)
|
|
46
|
+
try:
|
|
47
|
+
status1 = AllChem.EmbedMolecule(rdkm, maxAttempts=200)
|
|
48
|
+
assert status1 >= 0
|
|
49
|
+
except Exception as e:
|
|
50
|
+
status1 = AllChem.EmbedMolecule(rdkm, maxAttempts=200, useRandomCoords=True)
|
|
51
|
+
if status1 < 0:
|
|
52
|
+
raise ValueError(f"Cannot embed molecule! Error: {e}")
|
|
53
|
+
try:
|
|
54
|
+
status2 = AllChem.MMFFOptimizeMolecule(rdkm, maxIters=200)
|
|
55
|
+
assert status2 >= 0
|
|
56
|
+
except AssertionError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
return rdkm
|
|
60
|
+
|
|
61
|
+
def rdkit_to_cctk(rdkm: RdkitMol, cid: int = 0) -> cctk.Molecule:
|
|
62
|
+
if len(rdkm.GetConformers()) == 0:
|
|
63
|
+
rdkm = embed_rdkit_mol(rdkm)
|
|
64
|
+
try:
|
|
65
|
+
nums = [atom.GetAtomicNum() for atom in rdkm.GetAtoms()]
|
|
66
|
+
geom = rdkm.GetConformers()[cid].GetPositions()
|
|
67
|
+
return cctk.Molecule(nums, geom, charge=Chem.GetFormalCharge(rdkm))
|
|
68
|
+
except IndexError as e:
|
|
69
|
+
raise ConversionError("RDKit molecule does not have a conformer with the given ID") from e
|
|
70
|
+
|
|
71
|
+
def cctk_to_stjames(cmol: cctk.Molecule) -> stjames.Molecule:
|
|
72
|
+
atomic_numbers = cmol.atomic_numbers.view(np.ndarray)
|
|
73
|
+
geometry = cmol.geometry.view(np.ndarray)
|
|
74
|
+
atoms = []
|
|
75
|
+
for i in range(cmol.num_atoms()):
|
|
76
|
+
atoms.append(stjames.Atom(atomic_number=atomic_numbers[i], position=geometry[i]))
|
|
77
|
+
|
|
78
|
+
return stjames.Molecule(atoms=atoms, charge=cmol.charge, multiplicity=cmol.multiplicity)
|
|
79
|
+
|
|
80
|
+
def rdkit_to_stjames(rdkm: RdkitMol, cid: int = 0) -> stjames.Molecule:
|
|
81
|
+
cmol = rdkit_to_cctk(rdkm, cid)
|
|
82
|
+
return cctk_to_stjames(cmol)
|
|
83
|
+
|
|
84
|
+
def pka(mol: RdkitMol,
|
|
85
|
+
mode: pKaMode = "rapid",
|
|
86
|
+
timeout: int = 600,
|
|
87
|
+
name: str = "pKa API Workflow",
|
|
88
|
+
pka_range: tuple[int, int] = (2, 12),
|
|
89
|
+
deprotonate_elements: list[int] = [7, 8, 16],
|
|
90
|
+
protonate_elements: list[int] = [7],
|
|
91
|
+
folder_uuid: Optional[stjames.UUID] = None)-> tuple[dict[int, float], dict[int, float]]:
|
|
92
|
+
return asyncio.run(_single_pka(mol, mode, timeout, name, pka_range, deprotonate_elements, protonate_elements, folder_uuid))
|
|
93
|
+
|
|
94
|
+
def batch_pka(mols: List[RdkitMol],
|
|
95
|
+
mode: pKaMode = "rapid",
|
|
96
|
+
timeout: int = 600,
|
|
97
|
+
name: str = "pKa API Workflow",
|
|
98
|
+
pka_range: tuple[int, int] = (2, 12),
|
|
99
|
+
deprotonate_elements: list[int] = [7, 8, 16],
|
|
100
|
+
protonate_elements: list[int] = [7],
|
|
101
|
+
folder_uuid: Optional[stjames.UUID] = None)-> tuple[dict[int, float], dict[int, float]]:
|
|
102
|
+
loop = asyncio.new_event_loop()
|
|
103
|
+
asyncio.set_event_loop(loop)
|
|
104
|
+
tasks = [
|
|
105
|
+
_single_pka(mol, mode, timeout, name, pka_range, deprotonate_elements, protonate_elements, folder_uuid)
|
|
106
|
+
for mol in mols
|
|
107
|
+
]
|
|
108
|
+
results = loop.run_until_complete(asyncio.gather(*tasks))
|
|
109
|
+
return results
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def _single_pka(mol: RdkitMol,
|
|
113
|
+
mode: pKaMode = "rapid",
|
|
114
|
+
timeout: int = 600,
|
|
115
|
+
name: str = "pKa API Workflow",
|
|
116
|
+
pka_range: tuple[int, int] = (2, 12),
|
|
117
|
+
deprotonate_elements: list[int] = [7, 8, 16],
|
|
118
|
+
protonate_elements: list[int] = [7],
|
|
119
|
+
folder_uuid: Optional[stjames.UUID] = None) -> tuple[dict[int, float], dict[int, float]]:
|
|
120
|
+
"""
|
|
121
|
+
Calculate the pKa of a molecule.
|
|
122
|
+
:param mol: RDKit molecule object
|
|
123
|
+
:return: dictionary of pKa values
|
|
124
|
+
"""
|
|
125
|
+
get_api_key()
|
|
126
|
+
post = rowan.Workflow.submit(
|
|
127
|
+
name=name,
|
|
128
|
+
workflow_type="pka",
|
|
129
|
+
initial_molecule=rdkit_to_stjames(mol),
|
|
130
|
+
workflow_data={
|
|
131
|
+
"pka_range": pka_range,
|
|
132
|
+
"deprotonate_elements": deprotonate_elements,
|
|
133
|
+
"deprotonate_atoms": [],
|
|
134
|
+
"protonate_elements": protonate_elements,
|
|
135
|
+
"protonate_atoms": [],
|
|
136
|
+
"mode": mode,
|
|
137
|
+
},
|
|
138
|
+
folder_uuid=folder_uuid
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
start = time.time()
|
|
142
|
+
while not rowan.Workflow.is_finished(post["uuid"]):
|
|
143
|
+
await asyncio.sleep(5)
|
|
144
|
+
if time.time() - start > timeout:
|
|
145
|
+
raise TimeoutError("Workflow timed out")
|
|
146
|
+
|
|
147
|
+
result = rowan.Workflow.retrieve(post["uuid"])
|
|
148
|
+
|
|
149
|
+
acidic_pkas = []
|
|
150
|
+
for microstate in result["object_data"]["conjugate_bases"]:
|
|
151
|
+
symbol = cctk.helper_functions.get_symbol(result["object_data"]["initial_molecule"]["atoms"][microstate["atom_index"]-1]["atomic_number"])
|
|
152
|
+
acidic_pkas.append({
|
|
153
|
+
"element": symbol,
|
|
154
|
+
"index": microstate["atom_index"],
|
|
155
|
+
"pKa": round(microstate["pka"], 2)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
basic_pkas = []
|
|
160
|
+
for microstate in result["object_data"]["conjugate_acids"]:
|
|
161
|
+
symbol = cctk.helper_functions.get_symbol(result["object_data"]["initial_molecule"]["atoms"][microstate["atom_index"]-1]["atomic_number"])
|
|
162
|
+
basic_pkas.append({
|
|
163
|
+
"element": symbol,
|
|
164
|
+
"index": microstate["atom_index"],
|
|
165
|
+
"pKa": round(microstate["pka"], 2)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
return {"acidic_pkas": acidic_pkas, "basic_pkas": basic_pkas}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def tautomers(mol: RdkitMol,
|
|
172
|
+
mode: TautomerMode = "reckless",
|
|
173
|
+
timeout: int = 600,
|
|
174
|
+
name: str = "Tautomers API Workflow",
|
|
175
|
+
folder_uuid: Optional[stjames.UUID] = None) -> list[tuple[RdkitMol, float]]:
|
|
176
|
+
"""
|
|
177
|
+
Generate possible tautomers of a molecule.
|
|
178
|
+
:param mol: RDKit molecule object
|
|
179
|
+
:return: A list of tautomer dictionaries which include the RDKit molecule object, the relative energy, and the weight
|
|
180
|
+
"""
|
|
181
|
+
return asyncio.run(_single_tautomers(mol, mode, timeout, name, folder_uuid))
|
|
182
|
+
|
|
183
|
+
def batch_tautomers(mols: List[RdkitMol],
|
|
184
|
+
mode: TautomerMode = "reckless",
|
|
185
|
+
timeout: int = 600,
|
|
186
|
+
name: str = "Tautomers API Workflow",
|
|
187
|
+
folder_uuid: Optional[stjames.UUID] = None) -> list[tuple[RdkitMol, float]]:
|
|
188
|
+
"""
|
|
189
|
+
Generate possible tautomers of a molecule.
|
|
190
|
+
:param mol: RDKit molecule object
|
|
191
|
+
:return: A list of lists of tautomer dictionaries which include the RDKit molecule object, the relative energy, and the weight
|
|
192
|
+
"""
|
|
193
|
+
loop = asyncio.new_event_loop()
|
|
194
|
+
asyncio.set_event_loop(loop)
|
|
195
|
+
tasks = [_single_tautomers(mol, mode, timeout, name, folder_uuid) for mol in mols]
|
|
196
|
+
results = loop.run_until_complete(asyncio.gather(*tasks))
|
|
197
|
+
return results
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def _single_tautomers(mol: RdkitMol,
|
|
201
|
+
mode: TautomerMode = "reckless",
|
|
202
|
+
timeout: int = 600,
|
|
203
|
+
name: str = "Tautomers API Workflow",
|
|
204
|
+
folder_uuid: Optional[stjames.UUID] = None) -> list[tuple[RdkitMol, float]]:
|
|
205
|
+
"""
|
|
206
|
+
Generate possible tautomers of a molecule.
|
|
207
|
+
:param mol: RDKit molecule object
|
|
208
|
+
:return: A list of tautomer dictionaries which include the RDKit molecule object, the relative energy, and the weight
|
|
209
|
+
"""
|
|
210
|
+
get_api_key()
|
|
211
|
+
post = rowan.Workflow.submit(
|
|
212
|
+
name=name,
|
|
213
|
+
workflow_type="tautomers",
|
|
214
|
+
initial_molecule=rdkit_to_stjames(mol),
|
|
215
|
+
workflow_data={
|
|
216
|
+
"mode": mode,
|
|
217
|
+
},
|
|
218
|
+
folder_uuid=folder_uuid
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
start = time.time()
|
|
222
|
+
while not rowan.Workflow.is_finished(post["uuid"]):
|
|
223
|
+
await asyncio.sleep(5)
|
|
224
|
+
if time.time() - start > timeout:
|
|
225
|
+
raise TimeoutError("Workflow timed out")
|
|
226
|
+
|
|
227
|
+
result = rowan.Workflow.retrieve(post["uuid"])
|
|
228
|
+
|
|
229
|
+
tautomers = []
|
|
230
|
+
for tautomer in result["object_data"]["tautomers"]:
|
|
231
|
+
rdkit_mol = _get_rdkit_mol_from_uuid(tautomer["structures"][0]["uuid"])
|
|
232
|
+
tautomers.append({
|
|
233
|
+
"molecule": rdkit_mol,
|
|
234
|
+
"predicted_relative_energy": round(tautomer["predicted_relative_energy"], 2),
|
|
235
|
+
"weight": round(tautomer["weight"], 5)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
#return relative weights too
|
|
239
|
+
return tautomers
|
|
240
|
+
|
|
241
|
+
def energy(
|
|
242
|
+
mol: RdkitMol,
|
|
243
|
+
method: str = "aimnet2_wb97md3",
|
|
244
|
+
engine: str = "aimnet2",
|
|
245
|
+
mode: str = "auto",
|
|
246
|
+
timeout: int = 600,
|
|
247
|
+
name: str = "Energy API Workflow",
|
|
248
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
249
|
+
):
|
|
250
|
+
"""
|
|
251
|
+
Computes the energy for the given molecule.
|
|
252
|
+
|
|
253
|
+
:param mol: the input molecule
|
|
254
|
+
:param method: the method with which to compute the molecule's energy
|
|
255
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
256
|
+
:returns: a dictionary with the energy in Hartree and the conformer index
|
|
257
|
+
"""
|
|
258
|
+
return asyncio.run(_single_energy(mol, method, engine, mode, timeout, name, folder_uuid))
|
|
259
|
+
|
|
260
|
+
def batch_energy(
|
|
261
|
+
mols: List[RdkitMol],
|
|
262
|
+
method: str = "aimnet2_wb97md3",
|
|
263
|
+
engine: str = "aimnet2",
|
|
264
|
+
mode: str = "auto",
|
|
265
|
+
timeout: int = 600,
|
|
266
|
+
name: str = "Energy API Workflow",
|
|
267
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
268
|
+
):
|
|
269
|
+
"""
|
|
270
|
+
Computes the energy for the given molecule.
|
|
271
|
+
|
|
272
|
+
:param mol: the input molecule
|
|
273
|
+
:param method: the method with which to compute the molecule's energy
|
|
274
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
275
|
+
:returns: a list of dictionaries with the energy in Hartree and the conformer index
|
|
276
|
+
"""
|
|
277
|
+
loop = asyncio.new_event_loop()
|
|
278
|
+
asyncio.set_event_loop(loop)
|
|
279
|
+
tasks = [_single_energy(mol, method, engine, mode, timeout, name, folder_uuid) for mol in mols]
|
|
280
|
+
results = loop.run_until_complete(asyncio.gather(*tasks))
|
|
281
|
+
return results
|
|
282
|
+
|
|
283
|
+
async def _single_energy(
|
|
284
|
+
mol: RdkitMol,
|
|
285
|
+
method: str = "aimnet2_wb97md3",
|
|
286
|
+
engine: str = "aimnet2",
|
|
287
|
+
mode: str = "auto",
|
|
288
|
+
timeout: int = 600,
|
|
289
|
+
name: str = "Energy API Workflow",
|
|
290
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
291
|
+
):
|
|
292
|
+
"""
|
|
293
|
+
Computes the energy for the given molecule.
|
|
294
|
+
|
|
295
|
+
:param mol: the input molecule
|
|
296
|
+
:param method: the method with which to compute the molecule's energy
|
|
297
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
298
|
+
:returns: a dictionary with the energy in Hartree and the conformer index
|
|
299
|
+
"""
|
|
300
|
+
get_api_key()
|
|
301
|
+
|
|
302
|
+
method = stjames.Method(method)
|
|
303
|
+
|
|
304
|
+
if mol.GetNumConformers() == 0:
|
|
305
|
+
mol = embed_rdkit_mol(mol)
|
|
306
|
+
if mol.GetNumConformers() == 0:
|
|
307
|
+
raise NoConformersError("This molecule has no conformers")
|
|
308
|
+
|
|
309
|
+
if method not in FAST_METHODS:
|
|
310
|
+
raise MethodTooSlowError(
|
|
311
|
+
"This method is too slow; try running this through our web interface."
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
workflow_uuids = []
|
|
315
|
+
for conformer in mol.GetConformers():
|
|
316
|
+
cid = conformer.GetId()
|
|
317
|
+
stjames_mol = rdkit_to_stjames(mol, cid)
|
|
318
|
+
get_api_key()
|
|
319
|
+
post = rowan.Workflow.submit(
|
|
320
|
+
name=name,
|
|
321
|
+
workflow_type="basic_calculation",
|
|
322
|
+
initial_molecule=stjames_mol,
|
|
323
|
+
workflow_data={
|
|
324
|
+
"settings": {
|
|
325
|
+
"method": method.value,
|
|
326
|
+
"corrections": [],
|
|
327
|
+
"tasks": [
|
|
328
|
+
"energy"
|
|
329
|
+
],
|
|
330
|
+
"mode": mode,
|
|
331
|
+
"opt_settings": {
|
|
332
|
+
"constraints": []
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
"engine": engine
|
|
336
|
+
},
|
|
337
|
+
folder_uuid=folder_uuid
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
workflow_uuids.append(post["uuid"])
|
|
341
|
+
|
|
342
|
+
start = time.time()
|
|
343
|
+
while not all(rowan.Workflow.is_finished(uuid) for uuid in workflow_uuids):
|
|
344
|
+
await asyncio.sleep(5)
|
|
345
|
+
if time.time() - start > timeout:
|
|
346
|
+
raise TimeoutError("Workflow timed out")
|
|
347
|
+
|
|
348
|
+
workflow_results = [rowan.Workflow.retrieve(uuid) for uuid in workflow_uuids]
|
|
349
|
+
energies = [rowan.Calculation.retrieve(workflow["object_data"]["calculation_uuid"])["molecules"][-1]["energy"] for workflow in workflow_results]
|
|
350
|
+
|
|
351
|
+
return [{"conformer_index": index, "energy": energy} for index, energy in enumerate(energies)]
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def optimize(
|
|
355
|
+
mol: RdkitMol,
|
|
356
|
+
method: str = "aimnet2_wb97md3",
|
|
357
|
+
engine: str = "aimnet2",
|
|
358
|
+
mode: str = "auto",
|
|
359
|
+
return_energies: bool = False,
|
|
360
|
+
timeout: int = 600,
|
|
361
|
+
name: str = "Optimize API Workflow",
|
|
362
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
363
|
+
) -> RdkitMol | tuple[RdkitMol, dict[int, float]]:
|
|
364
|
+
"""
|
|
365
|
+
Optimize each of a molecule's conformers and then return the molecule.
|
|
366
|
+
|
|
367
|
+
:param mol: the input molecule
|
|
368
|
+
:param method: the method with which to compute the molecule's energy
|
|
369
|
+
:param return_energies: whether to return energies in Hartree too
|
|
370
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
371
|
+
:returns: a dictionary with the molecule, with optimized conformers, and optionally a list of energies per conformer too
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
return asyncio.run(_single_optimize(mol, method, engine, mode, return_energies, timeout, name, folder_uuid))
|
|
375
|
+
|
|
376
|
+
def batch_optimize(
|
|
377
|
+
mols: List[RdkitMol],
|
|
378
|
+
method: str = "aimnet2_wb97md3",
|
|
379
|
+
engine: str = "aimnet2",
|
|
380
|
+
mode: str = "auto",
|
|
381
|
+
return_energies: bool = False,
|
|
382
|
+
timeout: int = 600,
|
|
383
|
+
name: str = "Optimize API Workflow",
|
|
384
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
385
|
+
) -> RdkitMol | tuple[RdkitMol, dict[int, float]]:
|
|
386
|
+
"""
|
|
387
|
+
Optimize each of a molecule's conformers and then return the molecule.
|
|
388
|
+
|
|
389
|
+
:param mol: the input molecule
|
|
390
|
+
:param method: the method with which to compute the molecule's energy
|
|
391
|
+
:param return_energies: whether to return energies in Hartree too
|
|
392
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
393
|
+
:returns: a list of dictionaries with the molecule, with optimized conformers, and optionally a list of energies per conformer too
|
|
394
|
+
|
|
395
|
+
"""
|
|
396
|
+
loop = asyncio.new_event_loop()
|
|
397
|
+
asyncio.set_event_loop(loop)
|
|
398
|
+
tasks = [_single_optimize(mol, method, engine, mode, return_energies, timeout, name, folder_uuid) for mol in mols]
|
|
399
|
+
results = loop.run_until_complete(asyncio.gather(*tasks))
|
|
400
|
+
return results
|
|
401
|
+
|
|
402
|
+
async def _single_optimize(
|
|
403
|
+
mol: RdkitMol,
|
|
404
|
+
method: str = "aimnet2_wb97md3",
|
|
405
|
+
engine: str = "aimnet2",
|
|
406
|
+
mode: str = "auto",
|
|
407
|
+
return_energies: bool = False,
|
|
408
|
+
timeout: int = 600,
|
|
409
|
+
name: str = "Optimize API Workflow",
|
|
410
|
+
folder_uuid: Optional[stjames.UUID] = None
|
|
411
|
+
) -> RdkitMol | tuple[RdkitMol, dict[int, float]]:
|
|
412
|
+
"""
|
|
413
|
+
Optimize each of a molecule's conformers and then return the molecule.
|
|
414
|
+
|
|
415
|
+
:param mol: the input molecule
|
|
416
|
+
:param method: the method with which to compute the molecule's energy
|
|
417
|
+
:param return_energies: whether to return energies in Hartree too
|
|
418
|
+
:raises: MethodTooSlowError if the method is invalid
|
|
419
|
+
:returns: a dictionary with the molecule, with optimized conformers, and optionally a list of energies per conformer too
|
|
420
|
+
|
|
421
|
+
"""
|
|
422
|
+
get_api_key()
|
|
423
|
+
|
|
424
|
+
method = stjames.Method(method)
|
|
425
|
+
|
|
426
|
+
if mol.GetNumConformers() == 0:
|
|
427
|
+
mol = embed_rdkit_mol(mol)
|
|
428
|
+
if mol.GetNumConformers() == 0:
|
|
429
|
+
raise NoConformersError("This molecule has no conformers")
|
|
430
|
+
|
|
431
|
+
if method not in FAST_METHODS:
|
|
432
|
+
raise MethodTooSlowError(
|
|
433
|
+
"This method is too slow; try running this through our web interface."
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
optimized_mol = copy.deepcopy(mol)
|
|
437
|
+
|
|
438
|
+
workflow_uuids = []
|
|
439
|
+
for conformer in mol.GetConformers():
|
|
440
|
+
cid = conformer.GetId()
|
|
441
|
+
stjames_mol = rdkit_to_stjames(mol, cid)
|
|
442
|
+
get_api_key()
|
|
443
|
+
post = rowan.Workflow.submit(
|
|
444
|
+
name=name,
|
|
445
|
+
workflow_type="basic_calculation",
|
|
446
|
+
initial_molecule=stjames_mol,
|
|
447
|
+
workflow_data={
|
|
448
|
+
"settings": {
|
|
449
|
+
"method": method.value,
|
|
450
|
+
"corrections": [],
|
|
451
|
+
"tasks": [
|
|
452
|
+
"optimize"
|
|
453
|
+
],
|
|
454
|
+
"mode": mode,
|
|
455
|
+
"opt_settings": {
|
|
456
|
+
"constraints": []
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
"engine": engine
|
|
460
|
+
},
|
|
461
|
+
folder_uuid=folder_uuid
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
workflow_uuids.append(post["uuid"])
|
|
465
|
+
|
|
466
|
+
start = time.time()
|
|
467
|
+
while not all(rowan.Workflow.is_finished(uuid) for uuid in workflow_uuids):
|
|
468
|
+
await asyncio.sleep(5)
|
|
469
|
+
if time.time() - start > timeout:
|
|
470
|
+
raise TimeoutError("Workflow timed out")
|
|
471
|
+
|
|
472
|
+
workflow_results = [rowan.Workflow.retrieve(uuid) for uuid in workflow_uuids]
|
|
473
|
+
calculations = [rowan.Calculation.retrieve(workflow["object_data"]["calculation_uuid"]) for workflow in workflow_results]
|
|
474
|
+
optimization_atoms = [cacluation["molecules"][-1]["atoms"] for cacluation in calculations]
|
|
475
|
+
optimized_positions = [[atom["position"] for atom in atoms] for atoms in optimization_atoms]
|
|
476
|
+
|
|
477
|
+
energies = [cacluation["molecules"][-1]["energy"] for cacluation in calculations]
|
|
478
|
+
|
|
479
|
+
for i, conformer in enumerate(optimized_mol.GetConformers()):
|
|
480
|
+
conformer.SetPositions(np.array(optimized_positions[i]))
|
|
481
|
+
|
|
482
|
+
return_dict = {"rdkit_molecule": mol}
|
|
483
|
+
|
|
484
|
+
if return_energies:
|
|
485
|
+
return_dict["energies"] = energies
|
|
486
|
+
|
|
487
|
+
return return_dict
|
|
488
|
+
|
|
489
|
+
def conformers(mol: RdkitMol, num_conformers=10,
|
|
490
|
+
method: str = "aimnet2_wb97md3",
|
|
491
|
+
mode: str = "rapid",
|
|
492
|
+
return_energies: bool = False,
|
|
493
|
+
timeout: int = 600,
|
|
494
|
+
name: str = "Conformer API Workflow",
|
|
495
|
+
folder_uuid: Optional[stjames.UUID] = None):
|
|
496
|
+
"""
|
|
497
|
+
Generate conformers for a molecule.
|
|
498
|
+
:param mol: RDKit molecule object
|
|
499
|
+
:param num_conformers: Number of conformers to generate
|
|
500
|
+
:return: A dictonary with the RDKit molecule with num_conformers lowest energy conformers and optional energies
|
|
501
|
+
"""
|
|
502
|
+
return asyncio.run(_single_conformers(mol, num_conformers, method, mode, return_energies, timeout, name, folder_uuid))
|
|
503
|
+
|
|
504
|
+
def batch_conformers(mols: List[RdkitMol], num_conformers=10,
|
|
505
|
+
method: str = "aimnet2_wb97md3",
|
|
506
|
+
mode: str = "rapid",
|
|
507
|
+
return_energies: bool = False,
|
|
508
|
+
timeout: int = 600,
|
|
509
|
+
name: str = "Conformer API Workflow",
|
|
510
|
+
folder_uuid: Optional[stjames.UUID] = None):
|
|
511
|
+
"""
|
|
512
|
+
Generate conformers for a molecule.
|
|
513
|
+
:param mol: RDKit molecule object
|
|
514
|
+
:param num_conformers: Number of conformers to generate
|
|
515
|
+
:return: A list of dictonaries with the RDKit molecule with num_conformers lowest energy conformers and optional energies
|
|
516
|
+
"""
|
|
517
|
+
loop = asyncio.new_event_loop()
|
|
518
|
+
asyncio.set_event_loop(loop)
|
|
519
|
+
tasks = [_single_conformers(mol, num_conformers, method, mode, return_energies, timeout, name, folder_uuid) for mol in mols]
|
|
520
|
+
results = loop.run_until_complete(asyncio.gather(*tasks))
|
|
521
|
+
return results
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
async def _single_conformers(mol: RdkitMol, num_conformers=10,
|
|
525
|
+
method: str = "aimnet2_wb97md3",
|
|
526
|
+
mode: str = "rapid",
|
|
527
|
+
return_energies: bool = False,
|
|
528
|
+
timeout: int = 600,
|
|
529
|
+
name: str = "Conformer API Workflow",
|
|
530
|
+
folder_uuid: Optional[stjames.UUID] = None):
|
|
531
|
+
"""
|
|
532
|
+
Generate conformers for a molecule.
|
|
533
|
+
:param mol: RDKit molecule object
|
|
534
|
+
:param num_conformers: Number of conformers to generate
|
|
535
|
+
:return: A dictonary with the RDKit molecule with num_conformers lowest energy conformers and optional energies
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
get_api_key()
|
|
539
|
+
|
|
540
|
+
method = stjames.Method(method)
|
|
541
|
+
|
|
542
|
+
if mol.GetNumConformers() == 0:
|
|
543
|
+
mol = embed_rdkit_mol(mol)
|
|
544
|
+
if mol.GetNumConformers() == 0:
|
|
545
|
+
raise NoConformersError("This molecule has no conformers")
|
|
546
|
+
|
|
547
|
+
if method not in FAST_METHODS:
|
|
548
|
+
raise MethodTooSlowError(
|
|
549
|
+
"This method is too slow; try running this through our web interface."
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
post = rowan.Workflow.submit(
|
|
553
|
+
name=name,
|
|
554
|
+
workflow_type="conformer_search",
|
|
555
|
+
initial_molecule=rdkit_to_stjames(mol),
|
|
556
|
+
workflow_data={
|
|
557
|
+
"conf_gen_mode": "rapid",
|
|
558
|
+
"mode": mode,
|
|
559
|
+
"mso_mode": "manual",
|
|
560
|
+
"multistage_opt_settings": {
|
|
561
|
+
"mode": "manual",
|
|
562
|
+
"optimization_settings": [
|
|
563
|
+
{
|
|
564
|
+
"method": method.value,
|
|
565
|
+
"tasks": [
|
|
566
|
+
"optimize"
|
|
567
|
+
],
|
|
568
|
+
"corrections": [],
|
|
569
|
+
"mode": "auto"
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
"solvent": None,
|
|
573
|
+
"transition_state": False,
|
|
574
|
+
"constraints": []
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
folder_uuid=folder_uuid
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
start = time.time()
|
|
581
|
+
while not rowan.Workflow.is_finished(post["uuid"]):
|
|
582
|
+
await asyncio.sleep(5)
|
|
583
|
+
if time.time() - start > timeout:
|
|
584
|
+
raise TimeoutError("Workflow timed out")
|
|
585
|
+
|
|
586
|
+
result = rowan.Workflow.retrieve(post["uuid"])
|
|
587
|
+
|
|
588
|
+
sorted_data = sorted(zip(result["object_data"]["energies"], result["object_data"]["conformer_uuids"]), key=lambda x: x[0])
|
|
589
|
+
|
|
590
|
+
if len(sorted_data) < num_conformers:
|
|
591
|
+
logging.warning("Number of conformers requested is greater than number of conformers available")
|
|
592
|
+
num_conformers = min(num_conformers, len(sorted_data))
|
|
593
|
+
|
|
594
|
+
# Extract the UUIDs of the lowest n energies
|
|
595
|
+
lowest_n_uuids = [item[1][0] for item in sorted_data[:num_conformers]]
|
|
596
|
+
lowest_energies = [item[0] for item in sorted_data[:num_conformers]]
|
|
597
|
+
|
|
598
|
+
AllChem.EmbedMultipleConfs(mol, numConfs=num_conformers)
|
|
599
|
+
|
|
600
|
+
for i, conformer in enumerate(mol.GetConformers()):
|
|
601
|
+
atoms = rowan.Calculation.retrieve(lowest_n_uuids[i])["molecules"][-1]["atoms"]
|
|
602
|
+
pos = [atom["position"] for atom in atoms]
|
|
603
|
+
conformer.SetPositions(np.array(pos))
|
|
604
|
+
|
|
605
|
+
return_dict = {"rdkit_molecule": mol}
|
|
606
|
+
|
|
607
|
+
if return_energies:
|
|
608
|
+
return_dict["energies"] = lowest_energies
|
|
609
|
+
|
|
610
|
+
return return_dict
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: rowan-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Rowan Python Library
|
|
5
5
|
Project-URL: Homepage, https://github.com/rowansci/rowan-client
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/rowansci/rowan-client/issues
|
|
@@ -5,7 +5,9 @@ rowan/constants.py,sha256=ZZvv3L0b2y3dMGlWGeaRmx40J5tBrpxNxvJgjP1TNjg,37
|
|
|
5
5
|
rowan/folder.py,sha256=W7-YnPxugqzIdw-t1sr-WjeSQa-x4IjZ2mV2DwIq3II,2965
|
|
6
6
|
rowan/utils.py,sha256=IMACnRJpjFns_DF-FZQDu8p8fbgu4C2dbDaxdGcSZQs,1405
|
|
7
7
|
rowan/workflow.py,sha256=An3CW9LlHxYByE4mRl1iYThYcIGry8TwTi5rgbAsEBc,4467
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
rowan_python-1.
|
|
11
|
-
rowan_python-1.
|
|
8
|
+
rowan/rowan_rdkit/__init__.py,sha256=31bFO_UzagQKXQlybrPc2wdFJ6lUGMmDzDqT-D6b0Fk,300
|
|
9
|
+
rowan/rowan_rdkit/chem_utils.py,sha256=3MRMuwi6PIr_xkKmswfMlB7P7qdJgSQs-Q3YVhGh4LY,22438
|
|
10
|
+
rowan_python-1.1.0.dist-info/METADATA,sha256=CKyKYywsk9M48GAZ6Rjk_Z7qjb6YpDkSti9AuOzOXl8,1030
|
|
11
|
+
rowan_python-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
rowan_python-1.1.0.dist-info/licenses/LICENSE,sha256=i7ehYBS-6gGmbTcgU4mgk28pyOx2kScJ0kcx8n7bWLM,1084
|
|
13
|
+
rowan_python-1.1.0.dist-info/RECORD,,
|
|
File without changes
|