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.
@@ -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.3
1
+ Metadata-Version: 2.4
2
2
  Name: rowan-python
3
- Version: 1.0.0
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
- rowan_python-1.0.0.dist-info/METADATA,sha256=IgayeoNx2nNIrsHExXgGMUW7hJDArdRdK5rkZ4ro_Xs,1030
9
- rowan_python-1.0.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
10
- rowan_python-1.0.0.dist-info/licenses/LICENSE,sha256=i7ehYBS-6gGmbTcgU4mgk28pyOx2kScJ0kcx8n7bWLM,1084
11
- rowan_python-1.0.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any