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.

Files changed (31) hide show
  1. rowan_mcp/functions/admet.py +89 -0
  2. rowan_mcp/functions/bde.py +106 -0
  3. rowan_mcp/functions/calculation_retrieve.py +89 -0
  4. rowan_mcp/functions/conformers.py +77 -0
  5. rowan_mcp/functions/descriptors.py +89 -0
  6. rowan_mcp/functions/docking.py +290 -0
  7. rowan_mcp/functions/docking_enhanced.py +174 -0
  8. rowan_mcp/functions/electronic_properties.py +202 -0
  9. rowan_mcp/functions/folder_management.py +130 -0
  10. rowan_mcp/functions/fukui.py +216 -0
  11. rowan_mcp/functions/hydrogen_bond_basicity.py +87 -0
  12. rowan_mcp/functions/irc.py +125 -0
  13. rowan_mcp/functions/macropka.py +120 -0
  14. rowan_mcp/functions/molecular_converter.py +423 -0
  15. rowan_mcp/functions/molecular_dynamics.py +191 -0
  16. rowan_mcp/functions/molecule_lookup.py +57 -0
  17. rowan_mcp/functions/multistage_opt.py +168 -0
  18. rowan_mcp/functions/pdb_handler.py +200 -0
  19. rowan_mcp/functions/pka.py +81 -0
  20. rowan_mcp/functions/redox_potential.py +349 -0
  21. rowan_mcp/functions/scan.py +536 -0
  22. rowan_mcp/functions/scan_analyzer.py +347 -0
  23. rowan_mcp/functions/solubility.py +277 -0
  24. rowan_mcp/functions/spin_states.py +747 -0
  25. rowan_mcp/functions/system_management.py +361 -0
  26. rowan_mcp/functions/tautomers.py +88 -0
  27. rowan_mcp/functions/workflow_management.py +422 -0
  28. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/METADATA +3 -18
  29. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/RECORD +31 -4
  30. {rowan_mcp-2.0.0.dist-info → rowan_mcp-2.0.1.dist-info}/WHEEL +0 -0
  31. {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()