rowan-python 3.0.11__py3-none-any.whl → 3.0.12__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rowan-python
3
- Version: 3.0.11
3
+ Version: 3.0.12
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
@@ -11,8 +11,6 @@ rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  rowan/types.py,sha256=rCAnUlCsemyBK9Bbus0pL-THoqNqJIQHaMoDferIfFU,471
12
12
  rowan/user.py,sha256=Tnwz1-u_92ACt1xATQegtMj3FcosFsAG4m-4YuAkiyg,5955
13
13
  rowan/utils.py,sha256=c1s6Ze-OqLtfvrD23OV60otskejmj-CD88nNf8_nFcw,3636
14
- rowan/rowan_rdkit/__init__.py,sha256=EATX2VRzywzKxqkpCUMTf7RNQLkWsfi5VcCNDW6EIiw,503
15
- rowan/rowan_rdkit/chem_utils.py,sha256=ZWdLziT59Qr5JzjvV789CAyRq0m5JIawsOP4RxUbQQA,35529
16
14
  rowan/workflows/__init__.py,sha256=CM3GKDTafp6vl79dWqSOvkVfwH1Ty8zuc8yzi65ABxM,4438
17
15
  rowan/workflows/admet.py,sha256=0_wIwXXLfHF-3kgGx_1EM1ljjaYHLeEijJ-GbMYxpL8,2904
18
16
  rowan/workflows/analogue_docking.py,sha256=LJpbbaug0tZ9Cg-m0b7EgGH5hJ5894fHOC16Wefx7mE,9206
@@ -51,7 +49,7 @@ rowan/workflows/solvent_dependent_conformers.py,sha256=ovvnhCE4xlkpdhccLHEq7oBJR
51
49
  rowan/workflows/spin_states.py,sha256=c0y2cwO9NE8DmxmQ9ZouA5QoBhxgZiTYFIqhC-geVAo,7382
52
50
  rowan/workflows/strain.py,sha256=N-CoOT-hjYzjYTMYR9Uzoy4EhnrLQEOeDNSNphsUIXQ,6380
53
51
  rowan/workflows/tautomer_search.py,sha256=GB96BjvVsJDgLCTH4S4nkAlBoCvKud_18Y3PTY9Q528,5779
54
- rowan_python-3.0.11.dist-info/METADATA,sha256=YvbKSL7wGaXuiFFHhnXUEvUvLj0G6Dx-HXOY5kw8X3I,1601
55
- rowan_python-3.0.11.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
56
- rowan_python-3.0.11.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
57
- rowan_python-3.0.11.dist-info/RECORD,,
52
+ rowan_python-3.0.12.dist-info/METADATA,sha256=0pcHHITAsvBIWYCiohdeX3Tc11wdjBMa81rTkyuxI8M,1601
53
+ rowan_python-3.0.12.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
54
+ rowan_python-3.0.12.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
55
+ rowan_python-3.0.12.dist-info/RECORD,,
@@ -1,29 +0,0 @@
1
- from .chem_utils import (
2
- batch_charges,
3
- batch_conformers,
4
- batch_energy,
5
- batch_optimize,
6
- batch_pka,
7
- batch_tautomers,
8
- run_charges,
9
- run_conformers,
10
- run_energy,
11
- run_optimize,
12
- run_pka,
13
- run_tautomers,
14
- )
15
-
16
- __all__ = [
17
- "batch_charges",
18
- "batch_conformers",
19
- "batch_energy",
20
- "batch_optimize",
21
- "batch_pka",
22
- "batch_tautomers",
23
- "run_charges",
24
- "run_conformers",
25
- "run_energy",
26
- "run_optimize",
27
- "run_pka",
28
- "run_tautomers",
29
- ]
@@ -1,1019 +0,0 @@
1
- import asyncio
2
- import copy
3
- import logging
4
- import time
5
- from typing import Iterable, Literal, TypedDict
6
-
7
- import numpy as np
8
- import stjames
9
- from rdkit import Chem
10
- from rdkit.Chem import AllChem
11
-
12
- import rowan
13
- from rowan.types import RdkitMol
14
- from rowan.utils import ATOMIC_NUMBER_TO_ATOMIC_SYMBOL, get_api_key
15
-
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
-
25
- class ConversionError(ValueError):
26
- pass
27
-
28
-
29
- class NoConformersError(Exception):
30
- pass
31
-
32
-
33
- class MethodTooSlowError(Exception):
34
- pass
35
-
36
-
37
- class ChargesResult(TypedDict):
38
- conformer_index: int
39
- charges: list[float]
40
-
41
-
42
- ChargesResults = list[ChargesResult]
43
-
44
-
45
- class ConformerResult(TypedDict):
46
- molecule: RdkitMol
47
- energies: list[float]
48
-
49
-
50
- class PKaResult(TypedDict):
51
- element: str
52
- index: int
53
- pKa: float
54
-
55
-
56
- class PKaResults(TypedDict):
57
- acidic_pkas: list[PKaResult]
58
- basic_pkas: list[PKaResult]
59
-
60
-
61
- class TautomerResult(TypedDict):
62
- molecule: RdkitMol
63
- predicted_relative_energy: float
64
- weight: float
65
-
66
-
67
- TautomerResults = list[TautomerResult]
68
-
69
-
70
- class ConformerEnergyResult(TypedDict):
71
- conformer_index: int
72
- energy: float
73
-
74
-
75
- class OptimizeResult(TypedDict):
76
- molecule: RdkitMol
77
- energies: list[float]
78
-
79
-
80
- def apply_nest_asyncio() -> None:
81
- try:
82
- asyncio.get_running_loop()
83
- except RuntimeError:
84
- return
85
- try:
86
- import nest_asyncio # type: ignore [import-untyped]
87
-
88
- nest_asyncio.apply()
89
- except ImportError:
90
- pass
91
-
92
-
93
- # actually apply it
94
- apply_nest_asyncio()
95
-
96
-
97
- def _get_rdkit_mol_from_uuid(calculation_uuid: str) -> RdkitMol:
98
- calc = rowan.retrieve_calculation(calculation_uuid)
99
- if calc.molecule is None:
100
- raise ValueError(f"Calculation {calculation_uuid} has no molecules")
101
- return Chem.MolFromXYZBlock(calc.molecule.to_xyz()) # type: ignore[attr-defined]
102
-
103
-
104
- def _embed_rdkit_mol(rdkm: RdkitMol) -> RdkitMol:
105
- try:
106
- AllChem.SanitizeMol(rdkm) # type: ignore[attr-defined]
107
- except Exception as e:
108
- raise ValueError("Molecule could not be generated -- invalid chemistry!") from e
109
-
110
- rdkm = AllChem.AddHs(rdkm) # type: ignore[attr-defined]
111
- try:
112
- assert AllChem.EmbedMolecule(rdkm, maxAttempts=200) >= 0 # type: ignore[attr-defined]
113
- except AssertionError as e:
114
- status1 = AllChem.EmbedMolecule(rdkm, maxAttempts=200, useRandomCoords=True) # type: ignore[attr-defined]
115
- if status1 < 0:
116
- raise ValueError("Cannot embed molecule!") from e
117
- try:
118
- assert AllChem.MMFFOptimizeMolecule(rdkm, maxIters=200) >= 0 # type: ignore[attr-defined, call-arg]
119
- except AssertionError:
120
- pass
121
-
122
- return rdkm
123
-
124
-
125
- def _rdkit_to_stjames(rdkm: RdkitMol, cid: int = 0) -> stjames.Molecule:
126
- return stjames.Molecule.from_rdkit(rdkm, cid=cid)
127
-
128
-
129
- def run_pka(
130
- mol: RdkitMol,
131
- mode: pKaMode = "rapid",
132
- timeout: int = 600,
133
- name: str = "pKa API Workflow",
134
- pka_range: tuple[int, int] = (2, 12),
135
- deprotonate_elements: list[int] | None = None,
136
- protonate_elements: list[int] | None = None,
137
- folder_uuid: str | None = None,
138
- ) -> PKaResults:
139
- """
140
- Calculate the pKa of a Molecule.
141
-
142
- :param mol: RDKit Molecule
143
- :param mode: pKa calculation Mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
144
- for options.
145
- :param timeout: time in seconds before the Workflow times out
146
- :param name: name for the job
147
- :param pka_range: range of pKa values to calculate
148
- :param deprotonate_elements: elements to deprotonate
149
- :param protonate_elements: elements to protonate
150
- :param folder_uuid: folder UUID
151
- :returns: dictionary of pKa values indexed by atom
152
- """
153
- protonate_elements = protonate_elements or [7]
154
- deprotonate_elements = deprotonate_elements or [7, 8, 16]
155
-
156
- return asyncio.run(
157
- _single_pka(
158
- mol,
159
- mode,
160
- timeout,
161
- name,
162
- pka_range,
163
- deprotonate_elements,
164
- protonate_elements,
165
- folder_uuid,
166
- )
167
- )
168
-
169
-
170
- def batch_pka(
171
- mols: Iterable[RdkitMol],
172
- mode: pKaMode = "rapid",
173
- timeout: int = 600,
174
- name: str = "pKa API Workflow",
175
- pka_range: tuple[int, int] = (2, 12),
176
- deprotonate_elements: list[int] | None = None,
177
- protonate_elements: list[int] | None = None,
178
- folder_uuid: str | None = None,
179
- ) -> list[PKaResults]:
180
- """
181
- Calculate the pKa of a batch of Molecules.
182
-
183
- :param mols: list of RDKit Molecules
184
- :param mode: pKa calculation mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
185
- for options.
186
- :param timeout: time in seconds before the Workflow times out
187
- :param name: name for the job
188
- :param pka_range: range of pKa values to calculate
189
- :param deprotonate_elements: elements to deprotonate
190
- :param protonate_elements: elements to protonate
191
- :returns: list of dictionary of pKa values indexed by atom
192
- """
193
- protonate_elements = protonate_elements or [7]
194
- deprotonate_elements = deprotonate_elements or [7, 8, 16]
195
-
196
- async def _run():
197
- tasks = [
198
- _single_pka(
199
- mol,
200
- mode,
201
- timeout,
202
- name,
203
- pka_range,
204
- deprotonate_elements,
205
- protonate_elements,
206
- folder_uuid,
207
- )
208
- for mol in mols
209
- ]
210
- return await asyncio.gather(*tasks)
211
-
212
- return asyncio.run(_run())
213
-
214
-
215
- async def _single_pka(
216
- mol: RdkitMol,
217
- mode: pKaMode = "rapid",
218
- timeout: int = 600,
219
- name: str = "pKa API Workflow",
220
- pka_range: tuple[int, int] = (2, 12),
221
- deprotonate_elements: list[int] | None = None,
222
- protonate_elements: list[int] | None = None,
223
- folder_uuid: str | None = None,
224
- ) -> PKaResults:
225
- """
226
- Calculate the pKa of a Molecule.
227
-
228
- :param mol: RDKit Molecule
229
- :param mode: pKa calculation mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
230
- for options.
231
- :param timeout: time in seconds before the Workflow times out
232
- :param name: name for the job
233
- :param pka_range: range of pKa values to calculate
234
- :param deprotonate_elements: elements to deprotonate
235
- :param protonate_elements: elements to protonate
236
- :param folder_uuid: folder UUID
237
- :returns: dictionary of pKa values
238
- """
239
- get_api_key()
240
- protonate_elements = protonate_elements or [7]
241
- deprotonate_elements = deprotonate_elements or [7, 8, 16]
242
-
243
- post = rowan.submit_workflow(
244
- name=name,
245
- workflow_type="pka",
246
- initial_molecule=_rdkit_to_stjames(mol),
247
- workflow_data={
248
- "pka_range": pka_range,
249
- "deprotonate_elements": deprotonate_elements,
250
- "deprotonate_atoms": [],
251
- "protonate_elements": protonate_elements,
252
- "protonate_atoms": [],
253
- "mode": mode,
254
- },
255
- folder_uuid=folder_uuid,
256
- )
257
-
258
- start = time.time()
259
- while not post.is_finished():
260
- await asyncio.sleep(5)
261
- if time.time() - start > timeout:
262
- raise TimeoutError("Workflow timed out")
263
-
264
- data = rowan.retrieve_workflow(post.uuid).data
265
-
266
- if not data:
267
- raise Exception("Could not retrieve workflow data")
268
-
269
- acidic_pkas: list[PKaResult] = []
270
- for microstate in data.get("conjugate_bases", []):
271
- atomic_number = data.get("initial_molecule", {})["atoms"][microstate["atom_index"] - 1][
272
- "atomic_number"
273
- ]
274
- acidic_pkas.append(
275
- {
276
- "element": ATOMIC_NUMBER_TO_ATOMIC_SYMBOL[str(atomic_number)],
277
- "index": microstate["atom_index"],
278
- "pKa": round(microstate["pka"], 2),
279
- }
280
- )
281
-
282
- basic_pkas: list[PKaResult] = []
283
- for microstate in data.get("conjugate_bases", []):
284
- atomic_number = data.get("initial_molecule", {})["atoms"][microstate["atom_index"] - 1][
285
- "atomic_number"
286
- ]
287
-
288
- basic_pkas.append(
289
- {
290
- "element": ATOMIC_NUMBER_TO_ATOMIC_SYMBOL[str(atomic_number)],
291
- "index": microstate["atom_index"],
292
- "pKa": round(microstate["pka"], 2),
293
- }
294
- )
295
-
296
- return {"acidic_pkas": acidic_pkas, "basic_pkas": basic_pkas}
297
-
298
-
299
- def run_tautomers(
300
- mol: RdkitMol,
301
- mode: TautomerMode = "reckless",
302
- timeout: int = 600,
303
- name: str = "Tautomers API Workflow",
304
- folder_uuid: str | None = None,
305
- ) -> TautomerResults:
306
- """
307
- Generate possible tautomers of a Molecule.
308
-
309
- :param mol: RDKit Molecule
310
- :param mode: Tautomer mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
311
- for options.
312
- :param timeout: time in seconds before the Workflow times out
313
- :param name: name for the job
314
- :param folder_uuid: folder UUID
315
- :returns: list of dictionaries containing RDKit Molecule, relative energies, and weights
316
- """
317
- return asyncio.run(_single_tautomers(mol, mode, timeout, name, folder_uuid))
318
-
319
-
320
- def batch_tautomers(
321
- mols: Iterable[RdkitMol],
322
- mode: TautomerMode = "reckless",
323
- timeout: int = 600,
324
- name: str = "Tautomers API Workflow",
325
- folder_uuid: str | None = None,
326
- ) -> list[TautomerResults]:
327
- """
328
- Generate possible tautomers of a Molecule.
329
-
330
- :param mols: RDKit Molecule
331
- :param mode: Tautomer mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
332
- for options.
333
- :param timeout: time in seconds before the Workflow times out
334
- :param name: name for the job
335
- :param folder_uuid: folder UUID
336
- :returns: List of lists of dicts containing RDKit Molecule, relative energies, and weights.
337
- """
338
-
339
- async def _run():
340
- tasks = [_single_tautomers(mol, mode, timeout, name, folder_uuid) for mol in mols]
341
- return await asyncio.gather(*tasks)
342
-
343
- return asyncio.run(_run())
344
-
345
-
346
- async def _single_tautomers(
347
- mol: RdkitMol,
348
- mode: TautomerMode = "reckless",
349
- timeout: int = 600,
350
- name: str = "Tautomers API Workflow",
351
- folder_uuid: str | None = None,
352
- ) -> TautomerResults:
353
- """
354
- Generate possible tautomers of a Molecule.
355
-
356
- :param mol: RDKit Molecule
357
- :param mode: Tautomer mode
358
- :param timeout: time in seconds before the Workflow times out
359
- :param name: name for the job
360
- :param folder_uuid: folder UUID
361
- :returns: dictionaries containing RDKit Molecule, relative energy, and weight
362
- """
363
- get_api_key()
364
-
365
- post = rowan.submit_workflow(
366
- name=name,
367
- workflow_type="tautomers",
368
- initial_molecule=_rdkit_to_stjames(mol),
369
- workflow_data={"mode": mode},
370
- folder_uuid=folder_uuid,
371
- )
372
-
373
- start = time.time()
374
- while not post.is_finished():
375
- await asyncio.sleep(5)
376
- if time.time() - start > timeout:
377
- raise TimeoutError("Workflow timed out")
378
-
379
- data = rowan.retrieve_workflow(post.uuid).data
380
-
381
- if not data:
382
- raise Exception("Could not retrieve workflow data")
383
-
384
- return [
385
- {
386
- "molecule": _get_rdkit_mol_from_uuid(tautomer["structures"][0]["uuid"]),
387
- "predicted_relative_energy": round(tautomer["predicted_relative_energy"], 2),
388
- "weight": round(tautomer["weight"], 5),
389
- }
390
- for tautomer in data.get("tautomers", [])
391
- ]
392
-
393
-
394
- def run_energy(
395
- mol: RdkitMol,
396
- method: str = "aimnet2_wb97md3",
397
- engine: str = "aimnet2",
398
- mode: str = "auto",
399
- timeout: int = 600,
400
- name: str = "Energy API Workflow",
401
- folder_uuid: str | None = None,
402
- ) -> list[ConformerEnergyResult]:
403
- """
404
- Computes the energy for the given molecule.
405
-
406
- :param mol: RDKit Molecule
407
- :param method: Method to use for the calculation.
408
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
409
- :param engine: Engine to run the energy. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
410
- :param mode: Mode to run the energy. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
411
- for options.
412
- :param timeout: time in seconds before the Workflow times out
413
- :param name: name for the job
414
- :param folder_uuid: folder UUID
415
- :raises MethodTooSlowError: if the method is invalid
416
- :returns: dictionary with the energy in Hartree and the conformer index
417
- """
418
- return asyncio.run(_single_energy(mol, method, engine, mode, timeout, name, folder_uuid))
419
-
420
-
421
- def batch_energy(
422
- mols: Iterable[RdkitMol],
423
- method: str = "aimnet2_wb97md3",
424
- engine: str = "aimnet2",
425
- mode: str = "auto",
426
- timeout: int = 600,
427
- name: str = "Energy API Workflow",
428
- folder_uuid: str | None = None,
429
- ) -> list[list[ConformerEnergyResult]]:
430
- """
431
- Computes the energy for the given molecule.
432
-
433
- :param mols: RDKit Molecule
434
- :param method: Method to use for the calculation.
435
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
436
- :param engine: Engine to run the energy. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
437
- :param mode: Mode to run the energy. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
438
- for options.
439
- :param timeout: time in seconds before the Workflow times out
440
- :param name: name for the job
441
- :param folder_uuid: folder UUID
442
- :raises MethodTooSlowError: if the method is invalid
443
- :returns: list of dictionaries with the energy in Hartree and the conformer index
444
- """
445
-
446
- async def _run():
447
- tasks = [
448
- _single_energy(mol, method, engine, mode, timeout, name, folder_uuid) for mol in mols
449
- ]
450
- return await asyncio.gather(*tasks)
451
-
452
- return asyncio.run(_run())
453
-
454
-
455
- async def _single_energy(
456
- mol: RdkitMol,
457
- method: str = "aimnet2_wb97md3",
458
- engine: str = "aimnet2",
459
- mode: str = "auto",
460
- timeout: int = 600,
461
- name: str = "Energy API Workflow",
462
- folder_uuid: str | None = None,
463
- ) -> list[ConformerEnergyResult]:
464
- """
465
- Computes the energy for the given molecule.
466
-
467
- :param mol: RDKit Molecule
468
- :param method: Method to use for the calculation.
469
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
470
- :param engine: Engine to run the energy. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
471
- :param mode: Mode to run the energy
472
- :param timeout: time in seconds before the Workflow times out
473
- :param name: name for the job
474
- :param folder_uuid: folder UUID
475
- :raises MethodTooSlowError: if the method is invalid
476
- :returns: dictionary with the energy in Hartree and the conformer index
477
- """
478
- get_api_key()
479
- method = stjames.Method(method)
480
-
481
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
482
- mol = _embed_rdkit_mol(mol)
483
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
484
- raise NoConformersError("This molecule has no conformers")
485
-
486
- if method not in FAST_METHODS:
487
- raise MethodTooSlowError(
488
- "This method is too slow; try running this through our web interface."
489
- )
490
-
491
- workflow_uuids = []
492
- for conformer in mol.GetConformers():
493
- cid = conformer.GetId() # type: ignore[call-arg]
494
- stjames_mol = _rdkit_to_stjames(mol, cid)
495
- post = rowan.submit_workflow(
496
- name=name,
497
- workflow_type="basic_calculation",
498
- initial_molecule=stjames_mol,
499
- workflow_data={
500
- "settings": {
501
- "method": method.value,
502
- "corrections": [],
503
- "tasks": ["energy"],
504
- "mode": mode,
505
- "opt_settings": {"constraints": []},
506
- },
507
- "engine": engine,
508
- },
509
- folder_uuid=folder_uuid,
510
- )
511
-
512
- workflow_uuids.append(post.uuid)
513
-
514
- start = time.time()
515
- while not all(rowan.retrieve_workflow(uuid).is_finished() for uuid in workflow_uuids):
516
- await asyncio.sleep(5)
517
- if time.time() - start > timeout:
518
- raise TimeoutError("Workflow timed out")
519
-
520
- results = [rowan.retrieve_workflow(uuid).data for uuid in workflow_uuids]
521
-
522
- energies: list[float] = []
523
- for data in results:
524
- if data is not None:
525
- calc = rowan.retrieve_calculation(data["calculation_uuid"])
526
- if calc.energy is not None:
527
- energies.append(calc.energy)
528
-
529
- return [{"conformer_index": index, "energy": energy} for index, energy in enumerate(energies)]
530
-
531
-
532
- def run_optimize(
533
- mol: RdkitMol,
534
- method: str = "aimnet2_wb97md3",
535
- engine: str = "aimnet2",
536
- mode: str = "auto",
537
- return_energies: bool = False,
538
- timeout: int = 600,
539
- name: str = "Optimize API Workflow",
540
- folder_uuid: str | None = None,
541
- ) -> OptimizeResult:
542
- """
543
- Optimize each of a molecule's conformers and then return the molecule.
544
-
545
- :param mol: RDKit Molecule
546
- :param method: Method to use for the calculation.
547
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
548
- :param engine: Engine to run the optimization. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
549
- :param return_energies: whether to return energies in Hartree too
550
- :param mode: Mode to run the optimization. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
551
- for options.
552
- :param timeout: time in seconds before the Workflow times out
553
- :param name: name for the job
554
- :param folder_uuid: folder UUID
555
- :raises MethodTooSlowError: if the method is invalid
556
- :returns: dictionary with the optimized conformer(s) and optional list of energies per conformer
557
- """
558
- return asyncio.run(
559
- _single_optimize(mol, method, engine, mode, return_energies, timeout, name, folder_uuid)
560
- )
561
-
562
-
563
- def batch_optimize(
564
- mols: Iterable[RdkitMol],
565
- method: str = "aimnet2_wb97md3",
566
- engine: str = "aimnet2",
567
- mode: str = "auto",
568
- return_energies: bool = False,
569
- timeout: int = 600,
570
- name: str = "Optimize API Workflow",
571
- folder_uuid: str | None = None,
572
- ) -> list[OptimizeResult]:
573
- """
574
- Optimize each of a Molecule's conformers and then return the Molecule.
575
-
576
- :param mols: input Molecule
577
- :param method: Method to use for the calculation.
578
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
579
- :param engine: Engine to run the optimization. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
580
- :param mode: Mode to run the optimization. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
581
- for options.
582
- :param return_energies: whether to return energies in Hartree too
583
- :param timeout: time in seconds before the Workflow times out
584
- :param name: name for the job
585
- :param folder_uuid: folder UUID
586
- :raises MethodTooSlowError: if the method is invalid
587
- :returns: dictionaries with optimized conformer(s) and optional list of energies per conformer
588
- """
589
-
590
- async def _run():
591
- tasks = [
592
- _single_optimize(mol, method, engine, mode, return_energies, timeout, name, folder_uuid)
593
- for mol in mols
594
- ]
595
- return await asyncio.gather(*tasks)
596
-
597
- return asyncio.run(_run())
598
-
599
-
600
- async def _single_optimize(
601
- mol: RdkitMol,
602
- method: str = "aimnet2_wb97md3",
603
- engine: str = "aimnet2",
604
- mode: str = "auto",
605
- return_energies: bool = False,
606
- timeout: int = 600,
607
- name: str = "Optimize API Workflow",
608
- folder_uuid: str | None = None,
609
- ) -> OptimizeResult:
610
- """
611
- Optimize each of a molecule's conformers and then return the molecule.
612
-
613
- :param mol: RDKit Molecule
614
- :param method: Method to use for the calculation.
615
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
616
- :param engine: Engine to run the optimization. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
617
- :param mode: Mode to run the optimization. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
618
- :param return_energies: whether to return energies in Hartree too
619
- :param timeout: time in seconds before the Workflow times out
620
- :param name: name for the job
621
- :param folder_uuid: folder UUID
622
- :raises MethodTooSlowError: if the method is invalid
623
- :returns: dictionary with the optimized conformer(s) and optional list of energies per conformer
624
- """
625
- get_api_key()
626
- method = stjames.Method(method)
627
-
628
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
629
- mol = _embed_rdkit_mol(mol)
630
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
631
- raise NoConformersError("This molecule has no conformers")
632
-
633
- if method not in FAST_METHODS:
634
- raise MethodTooSlowError(
635
- "This method is too slow; try running this through our web interface."
636
- )
637
-
638
- optimized_mol = copy.deepcopy(mol)
639
-
640
- workflow_uuids = []
641
- for conformer in mol.GetConformers():
642
- cid = conformer.GetId() # type: ignore[call-arg]
643
- stjames_mol = _rdkit_to_stjames(mol, cid)
644
-
645
- post = rowan.submit_workflow(
646
- name=name,
647
- workflow_type="basic_calculation",
648
- initial_molecule=stjames_mol,
649
- workflow_data={
650
- "settings": {
651
- "method": method.value,
652
- "corrections": [],
653
- "tasks": ["optimize"],
654
- "mode": mode,
655
- "opt_settings": {"constraints": []},
656
- },
657
- "engine": engine,
658
- },
659
- folder_uuid=folder_uuid,
660
- )
661
-
662
- workflow_uuids.append(post.uuid)
663
-
664
- start = time.time()
665
- while not all(rowan.retrieve_workflow(uuid).is_finished() for uuid in workflow_uuids):
666
- await asyncio.sleep(5)
667
- if time.time() - start > timeout:
668
- raise TimeoutError("Workflow timed out")
669
-
670
- results = [rowan.retrieve_workflow(uuid).data for uuid in workflow_uuids]
671
- calculations = [
672
- rowan.retrieve_calculation(data["calculation_uuid"]) for data in results if data is not None
673
- ]
674
- optimized_positions: list[list[tuple[float, float, float]]] = []
675
- energies: list[float] = []
676
- for calc in calculations:
677
- if calc.molecule is not None:
678
- optimized_positions.append([atom.position for atom in calc.molecule.atoms])
679
- if calc.energy is not None:
680
- energies.append(calc.energy)
681
-
682
- for i, conformer in enumerate(optimized_mol.GetConformers()):
683
- conformer.SetPositions(np.array(optimized_positions[i])) # type: ignore[attr-defined]
684
-
685
- return {
686
- "molecule": mol,
687
- "energies": energies if return_energies else [],
688
- }
689
-
690
-
691
- def run_conformers(
692
- mol: RdkitMol,
693
- num_conformers=10,
694
- method: str = "aimnet2_wb97md3",
695
- mode: str = "rapid",
696
- return_energies: bool = False,
697
- timeout: int = 600,
698
- name: str = "Conformer API Workflow",
699
- folder_uuid: str | None = None,
700
- ) -> ConformerResult:
701
- """
702
- Generate conformers for a Molecule.
703
-
704
- :param mol: RDKit Molecule
705
- :param num_conformers: number of conformers to generate
706
- :param method: Method to use for the calculation.
707
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
708
- :param mode: Mode for conformer generation. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
709
- for options.
710
- :param return_energies: whether to return energies in Hartree too
711
- :param timeout: time in seconds before the Workflow times out
712
- :param name: name for the job
713
- :param folder_uuid: folder UUID
714
- :returns: dictionary with the RDKit Molecule and energies
715
- """
716
- return asyncio.run(
717
- _single_conformers(
718
- mol,
719
- num_conformers,
720
- method,
721
- mode,
722
- return_energies,
723
- timeout,
724
- name,
725
- folder_uuid,
726
- )
727
- )
728
-
729
-
730
- def batch_conformers(
731
- mols: Iterable[RdkitMol],
732
- num_conformers=10,
733
- method: str = "aimnet2_wb97md3",
734
- mode: str = "rapid",
735
- return_energies: bool = False,
736
- timeout: int = 600,
737
- name: str = "Conformer API Workflow",
738
- folder_uuid: str | None = None,
739
- ) -> list[ConformerResult]:
740
- """
741
- Generate conformers for a Molecule.
742
-
743
- :param mols: RDKit molecule object
744
- :param num_conformers: number of conformers to generate
745
- :param method: Method to use for the calculation.
746
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
747
- :param mode: conformer generation mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
748
- for options.
749
- :param return_energies: whether to return energies in Hartree too
750
- :param timeout: time in seconds before the Workflow times out
751
- :param name: name for the job
752
- :returns: list of dictonaries with RDKit Molecule and energies
753
- """
754
-
755
- async def _run():
756
- tasks = [
757
- _single_conformers(
758
- mol,
759
- num_conformers,
760
- method,
761
- mode,
762
- return_energies,
763
- timeout,
764
- name,
765
- folder_uuid,
766
- )
767
- for mol in mols
768
- ]
769
- return await asyncio.gather(*tasks)
770
-
771
- return asyncio.run(_run())
772
-
773
-
774
- async def _single_conformers(
775
- mol: RdkitMol,
776
- num_conformers=10,
777
- method: str = "aimnet2_wb97md3",
778
- mode: str = "rapid",
779
- return_energies: bool = False,
780
- timeout: int = 600,
781
- name: str = "Conformer API Workflow",
782
- folder_uuid: str | None = None,
783
- ) -> ConformerResult:
784
- """
785
- Generate conformers for a molecule.
786
-
787
- :param mol: RDKit Molecule
788
- :param num_conformers: number of conformers to generate
789
- :param method: Method to use for the calculation.
790
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
791
- :param mode: conformer generation mode. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
792
- for options.
793
- :param return_energies: whether to return energies in Hartree too
794
- :param timeout: time in seconds before the Workflow times out
795
- :param name: name for the job
796
- :param folder_uuid: folder UUID
797
- :returns: dictionary with RDKit molecule and energies
798
- """
799
- get_api_key()
800
- method = stjames.Method(method)
801
-
802
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
803
- mol = _embed_rdkit_mol(mol)
804
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
805
- raise NoConformersError("This molecule has no conformers")
806
-
807
- if method not in FAST_METHODS:
808
- raise MethodTooSlowError(
809
- "This method is too slow; try running this through our web interface."
810
- )
811
-
812
- post = rowan.submit_workflow(
813
- name=name,
814
- workflow_type="conformer_search",
815
- initial_molecule=_rdkit_to_stjames(mol),
816
- workflow_data={
817
- "conf_gen_mode": "rapid",
818
- "mode": mode,
819
- "mso_mode": "manual",
820
- "multistage_opt_settings": {
821
- "mode": "manual",
822
- "optimization_settings": [
823
- {
824
- "method": method.value,
825
- "tasks": ["optimize"],
826
- "corrections": [],
827
- "mode": "auto",
828
- }
829
- ],
830
- "solvent": None,
831
- "transition_state": False,
832
- "constraints": [],
833
- },
834
- },
835
- folder_uuid=folder_uuid,
836
- )
837
-
838
- start = time.time()
839
- while not post.is_finished():
840
- await asyncio.sleep(5)
841
- if time.time() - start > timeout:
842
- raise TimeoutError("Workflow timed out")
843
-
844
- data = rowan.retrieve_workflow(post.uuid).data
845
-
846
- if data is None:
847
- raise NoConformersError("This molecule has no conformers")
848
-
849
- sorted_data = sorted(
850
- zip(data["energies"], data["conformer_uuids"], strict=True),
851
- key=lambda x: x[0],
852
- )
853
-
854
- if len(sorted_data) < num_conformers:
855
- logging.warning(
856
- "Number of conformers requested is greater than number of conformers available"
857
- )
858
- num_conformers = min(num_conformers, len(sorted_data))
859
-
860
- # Extract the UUIDs of the lowest n energies
861
- lowest_n_uuids = [item[1][0] for item in sorted_data[:num_conformers]]
862
- lowest_energies = [item[0] for item in sorted_data[:num_conformers]]
863
-
864
- AllChem.EmbedMultipleConfs(mol, numConfs=num_conformers) # type: ignore[attr-defined]
865
-
866
- for i, conformer in enumerate(mol.GetConformers()):
867
- calc = rowan.retrieve_calculation(lowest_n_uuids[i])
868
- if calc.molecule is None:
869
- raise ValueError(f"Calculation {lowest_n_uuids[i]} has no molecules")
870
- pos = [atom.position for atom in calc.molecule.atoms]
871
- conformer.SetPositions(np.array(pos)) # type: ignore[attr-defined]
872
-
873
- return {
874
- "molecule": mol,
875
- "energies": lowest_energies if return_energies else [],
876
- }
877
-
878
-
879
- def run_charges(
880
- mol: RdkitMol,
881
- method: str = "aimnet2_wb97md3",
882
- engine: str = "aimnet2",
883
- mode: str = "auto",
884
- timeout: int = 600,
885
- name: str = "Charges API Workflow",
886
- folder_uuid: str | None = None,
887
- ) -> ChargesResults:
888
- """
889
- Computes atom-centered charges for the given Molecule.
890
-
891
- :param mol: RDKit Molecule
892
- :param method: Method to use for the calculation.
893
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
894
- :param engine: Engine to run the charges. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
895
- :param mode: Mode to run the calculation in. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
896
- for options.
897
- :param timeout: timeout in seconds
898
- :param name: name of the job
899
- :param folder_uuid: folder UUID
900
- :raises MethodTooSlowError: if the method is invalid
901
- :returns: dictionary with the charges and the conformer index
902
- """
903
- return asyncio.run(_single_charges(mol, method, engine, mode, timeout, name, folder_uuid))
904
-
905
-
906
- def batch_charges(
907
- mols: Iterable[RdkitMol],
908
- method: str = "aimnet2_wb97md3",
909
- engine: str = "aimnet2",
910
- mode: str = "auto",
911
- timeout: int = 600,
912
- name: str = "Charges API Workflow",
913
- folder_uuid: str | None = None,
914
- ) -> list[ChargesResults]:
915
- """
916
- Computes atom-centered charges for the given Molecules.
917
-
918
- :param mols: RDKit Molecule
919
- :param method: Method to use for the calculation.
920
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
921
- :param engine: Engine to run the charges. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
922
- :param mode: Mode to run the calculation in. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
923
- for options.
924
- :param timeout: timeout in seconds
925
- :param name: name of the job
926
- :param folder_uuid: folder UUID
927
- :raises MethodTooSlowError: if the method is invalid
928
- :returns: list of dictionaries with the charges and the conformer index
929
- """
930
-
931
- async def _run():
932
- tasks = [
933
- _single_charges(mol, method, engine, mode, timeout, name, folder_uuid) for mol in mols
934
- ]
935
- return await asyncio.gather(*tasks)
936
-
937
- return asyncio.run(_run())
938
-
939
-
940
- async def _single_charges(
941
- mol: RdkitMol,
942
- method: str = "aimnet2_wb97md3",
943
- engine: str = "aimnet2",
944
- mode: str = "auto",
945
- timeout: int = 600,
946
- name: str = "Energy API Workflow",
947
- folder_uuid: str | None = None,
948
- ) -> ChargesResults:
949
- """
950
- Computes atom-centered charges for the given Molecule.
951
-
952
- :param mol: RDKit Molecule
953
- :param method: Method to use for the calculation.
954
- See [list of available methods](https://github.com/rowansci/stjames-public/blob/master/stjames/method.py)
955
- :param engine: Engine to run the charges. See [list of available engines](https://github.com/rowansci/stjames-public/blob/master/stjames/engine.py)
956
- :param mode: Mode to run the calculation in. See [list of available modes](https://github.com/rowansci/stjames-public/blob/master/stjames/mode.py)
957
- for options.
958
- :param timeout: timeout in seconds
959
- :param name: name of the job
960
- :param folder_uuid: folder UUID
961
- :raises MethodTooSlowError: if the method is invalid
962
- :returns: dictionary with the charges and the conformer index
963
- """
964
- get_api_key()
965
- method = stjames.Method(method)
966
-
967
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
968
- mol = _embed_rdkit_mol(mol)
969
- if mol.GetNumConformers() == 0: # type: ignore[call-arg]
970
- raise NoConformersError("This molecule has no conformers")
971
-
972
- if method not in FAST_METHODS:
973
- raise MethodTooSlowError(
974
- "This method is too slow; try running this through our web interface."
975
- )
976
-
977
- workflow_uuids = []
978
- for conformer in mol.GetConformers():
979
- cid = conformer.GetId() # type: ignore[call-arg]
980
-
981
- post = rowan.submit_workflow(
982
- name=name,
983
- workflow_type="basic_calculation",
984
- initial_molecule=_rdkit_to_stjames(mol, cid),
985
- workflow_data={
986
- "settings": {
987
- "method": method.value,
988
- "corrections": [],
989
- "tasks": ["charge"],
990
- "mode": mode,
991
- "opt_settings": {"constraints": []},
992
- },
993
- "engine": engine,
994
- },
995
- folder_uuid=folder_uuid,
996
- )
997
-
998
- workflow_uuids.append(post.uuid)
999
-
1000
- start = time.time()
1001
- while not all(rowan.retrieve_workflow(uuid).is_finished() for uuid in workflow_uuids):
1002
- await asyncio.sleep(5)
1003
- if time.time() - start > timeout:
1004
- raise TimeoutError("Workflow timed out")
1005
-
1006
- def grab_charges(uuid: str) -> list[float]:
1007
- """Grab mulliken charges by UUID of workflow."""
1008
- data = rowan.retrieve_workflow(uuid).data
1009
- if data is None:
1010
- raise KeyError("Workflow data not found")
1011
- calc = rowan.retrieve_calculation(data["calculation_uuid"])
1012
- if calc.molecule is None or calc.molecule.charges is None:
1013
- raise KeyError("Calculation has no charges")
1014
- return calc.molecule.charges
1015
-
1016
- return [
1017
- {"conformer_index": i, "charges": grab_charges(uuid)}
1018
- for i, uuid in enumerate(workflow_uuids)
1019
- ]