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