rowan-mcp 1.0.2__py3-none-any.whl → 2.0.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.
Potentially problematic release.
This version of rowan-mcp might be problematic. Click here for more details.
- rowan_mcp/__init__.py +1 -1
- rowan_mcp/__main__.py +3 -5
- rowan_mcp/functions_v2/BENCHMARK.md +86 -0
- rowan_mcp/functions_v2/molecule_lookup.py +232 -0
- rowan_mcp/functions_v2/protein_management.py +141 -0
- rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
- rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
- rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
- rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
- rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
- rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
- rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
- rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
- rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
- rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
- rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
- rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
- rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
- rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
- rowan_mcp/server.py +109 -144
- rowan_mcp/tests/basic_calculation_from_json.py +0 -0
- rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
- rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
- rowan_mcp/tests/bde.py +37 -0
- rowan_mcp/tests/benchmark_queries.md +120 -0
- rowan_mcp/tests/cofolding_screen.py +131 -0
- rowan_mcp/tests/conformer_dependent_redox.py +37 -0
- rowan_mcp/tests/conformers.py +31 -0
- rowan_mcp/tests/data.json +189 -0
- rowan_mcp/tests/docking_screen.py +157 -0
- rowan_mcp/tests/irc.py +24 -0
- rowan_mcp/tests/macropka.py +13 -0
- rowan_mcp/tests/multistage_opt.py +13 -0
- rowan_mcp/tests/optimization.py +21 -0
- rowan_mcp/tests/phenol_pka.py +36 -0
- rowan_mcp/tests/pka.py +36 -0
- rowan_mcp/tests/protein_cofolding.py +17 -0
- rowan_mcp/tests/scan.py +28 -0
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +41 -33
- rowan_mcp-2.0.0.dist-info/RECORD +42 -0
- rowan_mcp/functions/admet.py +0 -94
- rowan_mcp/functions/bde.py +0 -113
- rowan_mcp/functions/calculation_retrieve.py +0 -89
- rowan_mcp/functions/conformers.py +0 -80
- rowan_mcp/functions/descriptors.py +0 -92
- rowan_mcp/functions/docking.py +0 -340
- rowan_mcp/functions/docking_enhanced.py +0 -174
- rowan_mcp/functions/electronic_properties.py +0 -205
- rowan_mcp/functions/folder_management.py +0 -137
- rowan_mcp/functions/fukui.py +0 -219
- rowan_mcp/functions/hydrogen_bond_basicity.py +0 -94
- rowan_mcp/functions/irc.py +0 -125
- rowan_mcp/functions/macropka.py +0 -120
- rowan_mcp/functions/molecular_converter.py +0 -423
- rowan_mcp/functions/molecular_dynamics.py +0 -191
- rowan_mcp/functions/molecule_lookup.py +0 -57
- rowan_mcp/functions/multistage_opt.py +0 -171
- rowan_mcp/functions/pdb_handler.py +0 -200
- rowan_mcp/functions/pka.py +0 -88
- rowan_mcp/functions/redox_potential.py +0 -352
- rowan_mcp/functions/scan.py +0 -536
- rowan_mcp/functions/scan_analyzer.py +0 -347
- rowan_mcp/functions/solubility.py +0 -277
- rowan_mcp/functions/spin_states.py +0 -747
- rowan_mcp/functions/system_management.py +0 -368
- rowan_mcp/functions/tautomers.py +0 -91
- rowan_mcp/functions/workflow_management.py +0 -422
- rowan_mcp-1.0.2.dist-info/RECORD +0 -34
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
rowan_mcp/functions/pka.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Calculate pKa values for molecules using Rowan API.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import rowan
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
# Set up logging
|
|
10
|
-
import logging
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
# Configure rowan API key
|
|
14
|
-
if not hasattr(rowan, 'api_key') or not rowan.api_key:
|
|
15
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
16
|
-
if api_key:
|
|
17
|
-
rowan.api_key = api_key
|
|
18
|
-
logger.info("Rowan API key configured")
|
|
19
|
-
else:
|
|
20
|
-
logger.error("No ROWAN_API_KEY found in environment")
|
|
21
|
-
|
|
22
|
-
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
23
|
-
"""Log Rowan API calls and let Rowan handle its own errors."""
|
|
24
|
-
|
|
25
|
-
# Simple logging for calculations
|
|
26
|
-
logger.info(f" Starting {workflow_type.replace('_', ' ')}...")
|
|
27
|
-
|
|
28
|
-
# Let Rowan handle everything - no custom error handling
|
|
29
|
-
return rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
30
|
-
|
|
31
|
-
def rowan_pka(
|
|
32
|
-
name: str,
|
|
33
|
-
molecule: str,
|
|
34
|
-
folder_uuid: Optional[str] = None,
|
|
35
|
-
blocking: bool = True,
|
|
36
|
-
ping_interval: int = 5
|
|
37
|
-
) -> str:
|
|
38
|
-
"""Calculate pKa values for molecules.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
name: Name for the calculation
|
|
42
|
-
molecule: Molecule SMILES string
|
|
43
|
-
folder_uuid: UUID of folder to organize calculation in
|
|
44
|
-
blocking: Whether to wait for completion (default: True)
|
|
45
|
-
ping_interval: How often to check status in seconds (default: 5)
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
pKa calculation results
|
|
49
|
-
"""
|
|
50
|
-
try:
|
|
51
|
-
result = log_rowan_api_call(
|
|
52
|
-
workflow_type="pka",
|
|
53
|
-
name=name,
|
|
54
|
-
molecule=molecule,
|
|
55
|
-
folder_uuid=folder_uuid,
|
|
56
|
-
blocking=blocking,
|
|
57
|
-
ping_interval=ping_interval
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return str(result)
|
|
61
|
-
|
|
62
|
-
except Exception as e:
|
|
63
|
-
error_response = {
|
|
64
|
-
"error": f"pKa calculation failed: {str(e)}",
|
|
65
|
-
"name": name,
|
|
66
|
-
"molecule": molecule
|
|
67
|
-
}
|
|
68
|
-
return str(error_response)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_rowan_pka():
|
|
72
|
-
"""Test the rowan_pka function."""
|
|
73
|
-
try:
|
|
74
|
-
# Test with minimal parameters
|
|
75
|
-
result = rowan_pka(
|
|
76
|
-
name="test_pka_water",
|
|
77
|
-
molecule="O"
|
|
78
|
-
)
|
|
79
|
-
print("pKa test successful")
|
|
80
|
-
print(f"Result: {result}")
|
|
81
|
-
return True
|
|
82
|
-
except Exception as e:
|
|
83
|
-
print(f"pKa test failed: {e}")
|
|
84
|
-
return False
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if __name__ == "__main__":
|
|
88
|
-
test_rowan_pka()
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Rowan redox potential function for electrochemistry.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import logging
|
|
7
|
-
import time
|
|
8
|
-
from typing import Optional
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
import rowan
|
|
12
|
-
except ImportError:
|
|
13
|
-
rowan = None
|
|
14
|
-
|
|
15
|
-
# Setup logging
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
# Setup API key
|
|
19
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
20
|
-
if rowan and api_key:
|
|
21
|
-
rowan.api_key = api_key
|
|
22
|
-
|
|
23
|
-
def lookup_molecule_smiles(molecule_name: str) -> str:
|
|
24
|
-
"""Look up canonical SMILES for common molecule names."""
|
|
25
|
-
# Common molecule SMILES database
|
|
26
|
-
MOLECULE_SMILES = {
|
|
27
|
-
# Aromatics
|
|
28
|
-
"phenol": "Oc1ccccc1",
|
|
29
|
-
"benzene": "c1ccccc1",
|
|
30
|
-
"toluene": "Cc1ccccc1",
|
|
31
|
-
"aniline": "Nc1ccccc1",
|
|
32
|
-
"benzoic acid": "O=C(O)c1ccccc1",
|
|
33
|
-
"pyridine": "c1ccncc1",
|
|
34
|
-
"furan": "c1ccoc1",
|
|
35
|
-
"thiophene": "c1ccsc1",
|
|
36
|
-
"pyrrole": "c1cc[nH]c1",
|
|
37
|
-
"imidazole": "c1c[nH]cn1",
|
|
38
|
-
"indole": "c1ccc2c(c1)cc[nH]2",
|
|
39
|
-
"quinoline": "c1ccc2ncccc2c1",
|
|
40
|
-
|
|
41
|
-
# Aliphatics
|
|
42
|
-
"methane": "C",
|
|
43
|
-
"ethane": "CC",
|
|
44
|
-
"propane": "CCC",
|
|
45
|
-
"butane": "CCCC",
|
|
46
|
-
"pentane": "CCCCC",
|
|
47
|
-
"hexane": "CCCCCC",
|
|
48
|
-
"heptane": "CCCCCCC",
|
|
49
|
-
"octane": "CCCCCCCC",
|
|
50
|
-
"nonane": "CCCCCCCCC",
|
|
51
|
-
"decane": "CCCCCCCCCC",
|
|
52
|
-
|
|
53
|
-
# Alcohols
|
|
54
|
-
"methanol": "CO",
|
|
55
|
-
"ethanol": "CCO",
|
|
56
|
-
"propanol": "CCCO",
|
|
57
|
-
"isopropanol": "CC(C)O",
|
|
58
|
-
"butanol": "CCCCO",
|
|
59
|
-
"isobutanol": "CC(C)CO",
|
|
60
|
-
"tert-butanol": "CC(C)(C)O",
|
|
61
|
-
|
|
62
|
-
# Simple molecules
|
|
63
|
-
"water": "O",
|
|
64
|
-
"hydrogen peroxide": "OO",
|
|
65
|
-
"ammonia": "N",
|
|
66
|
-
"methyl amine": "CN",
|
|
67
|
-
"ethyl amine": "CCN",
|
|
68
|
-
"formaldehyde": "C=O",
|
|
69
|
-
"acetaldehyde": "CC=O",
|
|
70
|
-
"acetone": "CC(=O)C",
|
|
71
|
-
"formic acid": "C(=O)O",
|
|
72
|
-
"acetic acid": "CC(=O)O",
|
|
73
|
-
"acetamide": "CC(=O)N",
|
|
74
|
-
"dimethyl sulfoxide": "CS(=O)C",
|
|
75
|
-
"hydrogen sulfide": "S",
|
|
76
|
-
"carbon dioxide": "O=C=O",
|
|
77
|
-
"carbon monoxide": "C#O",
|
|
78
|
-
|
|
79
|
-
# Cyclic compounds
|
|
80
|
-
"cyclopropane": "C1CC1",
|
|
81
|
-
"cyclobutane": "C1CCC1",
|
|
82
|
-
"cyclopentane": "C1CCCC1",
|
|
83
|
-
"cyclohexane": "C1CCCCC1",
|
|
84
|
-
"cycloheptane": "C1CCCCCC1",
|
|
85
|
-
"cyclooctane": "C1CCCCCCC1",
|
|
86
|
-
|
|
87
|
-
# Ethers
|
|
88
|
-
"diethyl ether": "CCOCC",
|
|
89
|
-
"tetrahydrofuran": "C1CCOC1",
|
|
90
|
-
"dioxane": "C1COCCO1",
|
|
91
|
-
|
|
92
|
-
# Halogens
|
|
93
|
-
"chloroform": "C(Cl)(Cl)Cl",
|
|
94
|
-
"carbon tetrachloride": "C(Cl)(Cl)(Cl)Cl",
|
|
95
|
-
"methyl chloride": "CCl",
|
|
96
|
-
"dichloromethane": "C(Cl)Cl",
|
|
97
|
-
"fluoromethane": "CF",
|
|
98
|
-
"bromomethane": "CBr",
|
|
99
|
-
"iodomethane": "CI",
|
|
100
|
-
|
|
101
|
-
# Sugars
|
|
102
|
-
"glucose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O",
|
|
103
|
-
"fructose": "C([C@H]([C@H]([C@@H]([C@H](CO)O)O)O)O)O",
|
|
104
|
-
"sucrose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O[C@]2([C@H]([C@@H]([C@@H](O2)CO)O)O)CO)O)O)O)O",
|
|
105
|
-
|
|
106
|
-
# Nucleotides
|
|
107
|
-
"adenine": "c1nc(c2c(n1)ncn2)N",
|
|
108
|
-
"guanine": "c1nc2c(n1)c(=O)[nH]c(=n2)N",
|
|
109
|
-
"cytosine": "c1c(nc(=O)[nH]c1=O)N",
|
|
110
|
-
"thymine": "Cc1c[nH]c(=O)[nH]c1=O",
|
|
111
|
-
"uracil": "c1c[nH]c(=O)[nH]c1=O",
|
|
112
|
-
|
|
113
|
-
# Amino acids
|
|
114
|
-
"glycine": "C(C(=O)O)N",
|
|
115
|
-
"alanine": "C[C@@H](C(=O)O)N",
|
|
116
|
-
"valine": "CC(C)[C@@H](C(=O)O)N",
|
|
117
|
-
"leucine": "CC(C)C[C@@H](C(=O)O)N",
|
|
118
|
-
"isoleucine": "CC[C@H](C)[C@@H](C(=O)O)N",
|
|
119
|
-
"phenylalanine": "c1ccc(cc1)C[C@@H](C(=O)O)N",
|
|
120
|
-
"tyrosine": "c1cc(ccc1C[C@@H](C(=O)O)N)O",
|
|
121
|
-
"tryptophan": "c1ccc2c(c1)c(c[nH]2)C[C@@H](C(=O)O)N",
|
|
122
|
-
"serine": "C([C@@H](C(=O)O)N)O",
|
|
123
|
-
"threonine": "C[C@H]([C@@H](C(=O)O)N)O",
|
|
124
|
-
"asparagine": "C([C@@H](C(=O)O)N)C(=O)N",
|
|
125
|
-
"glutamine": "C(CC(=O)N)[C@@H](C(=O)O)N",
|
|
126
|
-
"aspartic acid": "C([C@@H](C(=O)O)N)C(=O)O",
|
|
127
|
-
"glutamic acid": "C(CC(=O)O)[C@@H](C(=O)O)N",
|
|
128
|
-
"lysine": "C(CCN)C[C@@H](C(=O)O)N",
|
|
129
|
-
"arginine": "C(C[C@@H](C(=O)O)N)CN=C(N)N",
|
|
130
|
-
"histidine": "c1c([nH]cn1)C[C@@H](C(=O)O)N",
|
|
131
|
-
"cysteine": "C([C@@H](C(=O)O)N)S",
|
|
132
|
-
"methionine": "CCSC[C@@H](C(=O)O)N",
|
|
133
|
-
"proline": "C1C[C@H](NC1)C(=O)O",
|
|
134
|
-
|
|
135
|
-
# Drugs and bioactive molecules
|
|
136
|
-
"aspirin": "CC(=O)Oc1ccccc1C(=O)O",
|
|
137
|
-
"caffeine": "Cn1c(=O)c2c(ncn2C)n(C)c1=O",
|
|
138
|
-
"nicotine": "CN1CCC[C@H]1c2cccnc2",
|
|
139
|
-
"morphine": "CN1CC[C@]23c4c5ccc(O)c4O[C@H]2[C@@H](O)C=C[C@H]3[C@H]1C5",
|
|
140
|
-
"codeine": "COc1ccc2c3c1O[C@H]1[C@@H](O)C=C[C@H]4[C@@H]1C[C@@](CC3)(C2)N4C",
|
|
141
|
-
"glucose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O",
|
|
142
|
-
"cholesterol": "C[C@H](CCCC(C)C)[C@H]1CC[C@@H]2[C@@H]1CC[C@H]3[C@@H]2CC=C4[C@@]3(CC[C@@H](C4)O)C",
|
|
143
|
-
"testosterone": "C[C@]12CC[C@H]3[C@H]([C@@H]1CC[C@@H]2O)CCC4=CC(=O)CC[C@]34C",
|
|
144
|
-
"estradiol": "C[C@]12CC[C@H]3[C@H]([C@@H]1CC[C@@H]2O)CCC4=C3C=CC(=C4)O",
|
|
145
|
-
|
|
146
|
-
# Solvents
|
|
147
|
-
"dichloromethane": "C(Cl)Cl",
|
|
148
|
-
"chloroform": "C(Cl)(Cl)Cl",
|
|
149
|
-
"carbon tetrachloride": "C(Cl)(Cl)(Cl)Cl",
|
|
150
|
-
"acetonitrile": "CC#N",
|
|
151
|
-
"dimethylformamide": "CN(C)C=O",
|
|
152
|
-
"dimethyl sulfoxide": "CS(=O)C",
|
|
153
|
-
"ethyl acetate": "CCOC(=O)C",
|
|
154
|
-
"diethyl ether": "CCOCC",
|
|
155
|
-
"tetrahydrofuran": "C1CCOC1",
|
|
156
|
-
"1,4-dioxane": "C1COCCO1",
|
|
157
|
-
"toluene": "Cc1ccccc1",
|
|
158
|
-
"xylene": "Cc1ccccc1C",
|
|
159
|
-
"mesitylene": "Cc1cc(C)cc(C)c1",
|
|
160
|
-
"hexane": "CCCCCC",
|
|
161
|
-
"heptane": "CCCCCCC",
|
|
162
|
-
"octane": "CCCCCCCC",
|
|
163
|
-
"cyclohexane": "C1CCCCC1",
|
|
164
|
-
"benzene": "c1ccccc1",
|
|
165
|
-
"nitromethane": "C[N+](=O)[O-]",
|
|
166
|
-
"acetone": "CC(=O)C",
|
|
167
|
-
"2-butanone": "CCC(=O)C",
|
|
168
|
-
"2-propanol": "CC(C)O",
|
|
169
|
-
"1-butanol": "CCCCO",
|
|
170
|
-
"2-butanol": "CC(C)CO",
|
|
171
|
-
"tert-butanol": "CC(C)(C)O",
|
|
172
|
-
"ethylene glycol": "OCCO",
|
|
173
|
-
"propylene glycol": "CC(CO)O",
|
|
174
|
-
"glycerol": "C(C(CO)O)O",
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
# Try exact match first
|
|
178
|
-
name_lower = molecule_name.lower().strip()
|
|
179
|
-
if name_lower in MOLECULE_SMILES:
|
|
180
|
-
return MOLECULE_SMILES[name_lower]
|
|
181
|
-
|
|
182
|
-
# If not found, return the original input (assume it's already SMILES)
|
|
183
|
-
return molecule_name
|
|
184
|
-
|
|
185
|
-
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
186
|
-
"""Log Rowan API calls with detailed parameters."""
|
|
187
|
-
|
|
188
|
-
try:
|
|
189
|
-
start_time = time.time()
|
|
190
|
-
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
191
|
-
api_time = time.time() - start_time
|
|
192
|
-
|
|
193
|
-
if isinstance(result, dict) and 'uuid' in result:
|
|
194
|
-
job_status = result.get('status', result.get('object_status', 'Unknown'))
|
|
195
|
-
status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
|
|
196
|
-
status_text = status_names.get(job_status, f"Unknown ({job_status})")
|
|
197
|
-
|
|
198
|
-
return result
|
|
199
|
-
|
|
200
|
-
except Exception as e:
|
|
201
|
-
api_time = time.time() - start_time
|
|
202
|
-
raise e
|
|
203
|
-
|
|
204
|
-
def rowan_redox_potential(
|
|
205
|
-
name: str,
|
|
206
|
-
molecule: str,
|
|
207
|
-
reduction: bool = True,
|
|
208
|
-
oxidation: bool = True,
|
|
209
|
-
mode: str = "rapid",
|
|
210
|
-
folder_uuid: Optional[str] = None,
|
|
211
|
-
blocking: bool = True,
|
|
212
|
-
ping_interval: int = 5
|
|
213
|
-
) -> str:
|
|
214
|
-
"""Predict redox potentials vs. SCE in acetonitrile.
|
|
215
|
-
|
|
216
|
-
Calculates oxidation and reduction potentials for:
|
|
217
|
-
- Electrochemical reaction design
|
|
218
|
-
- Battery and energy storage applications
|
|
219
|
-
- Understanding electron transfer processes
|
|
220
|
-
|
|
221
|
-
**Important**: Only acetonitrile solvent is supported by Rowan's redox workflow.
|
|
222
|
-
|
|
223
|
-
Use this for: Electrochemistry, battery materials, electron transfer studies
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
name: Name for the calculation
|
|
227
|
-
molecule: Molecule SMILES string or common name (e.g., "phenol", "benzene")
|
|
228
|
-
reduction: Whether to calculate reduction potential (default: True)
|
|
229
|
-
oxidation: Whether to calculate oxidation potential (default: True)
|
|
230
|
-
mode: Calculation accuracy mode - "reckless", "rapid", "careful", "meticulous" (default: "rapid")
|
|
231
|
-
folder_uuid: Optional folder UUID for organization
|
|
232
|
-
blocking: Whether to wait for completion (default: True)
|
|
233
|
-
ping_interval: Check status interval in seconds (default: 5)
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
Redox potential results vs. SCE in acetonitrile
|
|
237
|
-
"""
|
|
238
|
-
# Look up SMILES if a common name was provided
|
|
239
|
-
canonical_smiles = lookup_molecule_smiles(molecule)
|
|
240
|
-
|
|
241
|
-
# Validate mode
|
|
242
|
-
valid_modes = ["reckless", "rapid", "careful", "meticulous"]
|
|
243
|
-
mode_lower = mode.lower()
|
|
244
|
-
if mode_lower not in valid_modes:
|
|
245
|
-
return f" Invalid mode '{mode}'. Valid modes: {', '.join(valid_modes)}"
|
|
246
|
-
|
|
247
|
-
# At least one type must be selected
|
|
248
|
-
if not reduction and not oxidation:
|
|
249
|
-
return f" At least one of 'reduction' or 'oxidation' must be True"
|
|
250
|
-
|
|
251
|
-
logger.info(f" Name: {name}")
|
|
252
|
-
logger.info(f" Input: {molecule}")
|
|
253
|
-
logger.info(f" Mode: {mode_lower}")
|
|
254
|
-
logger.info(f" Reduction: {reduction}")
|
|
255
|
-
logger.info(f" Oxidation: {oxidation}")
|
|
256
|
-
|
|
257
|
-
# Build parameters for Rowan API
|
|
258
|
-
redox_params = {
|
|
259
|
-
"name": name,
|
|
260
|
-
"molecule": canonical_smiles,
|
|
261
|
-
"reduction": reduction,
|
|
262
|
-
"oxidation": oxidation,
|
|
263
|
-
"mode": mode_lower,
|
|
264
|
-
"solvent": "acetonitrile", # Required by Rowan
|
|
265
|
-
"folder_uuid": folder_uuid,
|
|
266
|
-
"blocking": blocking,
|
|
267
|
-
"ping_interval": ping_interval
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
result = log_rowan_api_call(
|
|
271
|
-
workflow_type="redox_potential",
|
|
272
|
-
**redox_params
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
if blocking:
|
|
276
|
-
status = result.get('status', result.get('object_status', 'Unknown'))
|
|
277
|
-
|
|
278
|
-
if status == 2: # Completed successfully
|
|
279
|
-
formatted = f" Redox potential analysis for '{name}' completed successfully!\n\n"
|
|
280
|
-
elif status == 3: # Failed
|
|
281
|
-
formatted = f" Redox potential analysis for '{name}' failed!\n\n"
|
|
282
|
-
else:
|
|
283
|
-
formatted = f" Redox potential analysis for '{name}' finished with status {status}\n\n"
|
|
284
|
-
|
|
285
|
-
formatted += f" Molecule: {molecule}\n"
|
|
286
|
-
formatted += f" SMILES: {canonical_smiles}\n"
|
|
287
|
-
formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
288
|
-
formatted += f" Status: {status}\n"
|
|
289
|
-
formatted += f"⚙ Mode: {mode_lower.title()}\n"
|
|
290
|
-
formatted += f"💧 Solvent: Acetonitrile\n"
|
|
291
|
-
|
|
292
|
-
# Show which potentials were calculated
|
|
293
|
-
calc_types = []
|
|
294
|
-
if reduction:
|
|
295
|
-
calc_types.append("Reduction")
|
|
296
|
-
if oxidation:
|
|
297
|
-
calc_types.append("Oxidation")
|
|
298
|
-
formatted += f" Calculated: {' + '.join(calc_types)} potential(s)\n"
|
|
299
|
-
|
|
300
|
-
# Try to extract redox potential results
|
|
301
|
-
if isinstance(result, dict) and 'object_data' in result and result['object_data']:
|
|
302
|
-
data = result['object_data']
|
|
303
|
-
|
|
304
|
-
if reduction and 'reduction_potential' in data and data['reduction_potential'] is not None:
|
|
305
|
-
formatted += f" Reduction Potential: {data['reduction_potential']:.3f} V vs. SCE\n"
|
|
306
|
-
|
|
307
|
-
if oxidation and 'oxidation_potential' in data and data['oxidation_potential'] is not None:
|
|
308
|
-
formatted += f" Oxidation Potential: {data['oxidation_potential']:.3f} V vs. SCE\n"
|
|
309
|
-
|
|
310
|
-
# Legacy support for older format
|
|
311
|
-
if 'redox_potential' in data and data['redox_potential'] is not None:
|
|
312
|
-
redox_type = data.get('redox_type', 'unknown')
|
|
313
|
-
formatted += f" {redox_type.title()} Potential: {data['redox_potential']:.3f} V vs. SCE\n"
|
|
314
|
-
|
|
315
|
-
if status == 2:
|
|
316
|
-
formatted += f"\n **Results Available:**\n"
|
|
317
|
-
formatted += f"• Potentials reported vs. SCE (Saturated Calomel Electrode)\n"
|
|
318
|
-
formatted += f"• Calculated in acetonitrile solvent\n"
|
|
319
|
-
formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for detailed data\n"
|
|
320
|
-
|
|
321
|
-
return formatted
|
|
322
|
-
else:
|
|
323
|
-
formatted = f" Redox potential analysis for '{name}' submitted!\n\n"
|
|
324
|
-
formatted += f" Molecule: {molecule}\n"
|
|
325
|
-
formatted += f" SMILES: {canonical_smiles}\n"
|
|
326
|
-
formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
327
|
-
formatted += f" Status: {result.get('status', 'Submitted')}\n"
|
|
328
|
-
formatted += f"⚙ Mode: {mode_lower.title()}\n"
|
|
329
|
-
|
|
330
|
-
calc_types = []
|
|
331
|
-
if reduction:
|
|
332
|
-
calc_types.append("Reduction")
|
|
333
|
-
if oxidation:
|
|
334
|
-
calc_types.append("Oxidation")
|
|
335
|
-
formatted += f" Will calculate: {' + '.join(calc_types)} potential(s)\n"
|
|
336
|
-
|
|
337
|
-
return formatted
|
|
338
|
-
|
|
339
|
-
def test_rowan_redox_potential():
|
|
340
|
-
"""Test the rowan_redox_potential function."""
|
|
341
|
-
try:
|
|
342
|
-
# Test with a simple molecule (non-blocking to avoid long wait)
|
|
343
|
-
result = rowan_redox_potential("test_redox", "phenol", blocking=False)
|
|
344
|
-
print(" Redox potential test successful!")
|
|
345
|
-
print(f"Result length: {len(result)} characters")
|
|
346
|
-
return True
|
|
347
|
-
except Exception as e:
|
|
348
|
-
print(f" Redox potential test failed: {e}")
|
|
349
|
-
return False
|
|
350
|
-
|
|
351
|
-
if __name__ == "__main__":
|
|
352
|
-
test_rowan_redox_potential()
|