rowan-mcp 1.0.0__py3-none-any.whl → 1.0.2__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/functions/conformers.py +3 -58
- rowan_mcp/functions/electronic_properties.py +2 -60
- rowan_mcp/functions/fukui.py +36 -172
- rowan_mcp/functions/macropka.py +4 -79
- rowan_mcp/functions/molecule_lookup.py +40 -429
- rowan_mcp/functions/pka.py +4 -53
- {rowan_mcp-1.0.0.dist-info → rowan_mcp-1.0.2.dist-info}/METADATA +40 -50
- {rowan_mcp-1.0.0.dist-info → rowan_mcp-1.0.2.dist-info}/RECORD +11 -12
- rowan_mcp/functions/molecule_cache.db +0 -0
- {rowan_mcp-1.0.0.dist-info → rowan_mcp-1.0.2.dist-info}/WHEEL +0 -0
- {rowan_mcp-1.0.0.dist-info → rowan_mcp-1.0.2.dist-info}/entry_points.txt +0 -0
rowan_mcp/__init__.py
CHANGED
|
@@ -42,8 +42,8 @@ def rowan_conformers(
|
|
|
42
42
|
folder_uuid: Optional[str] = None,
|
|
43
43
|
blocking: bool = True,
|
|
44
44
|
ping_interval: int = 5
|
|
45
|
-
)
|
|
46
|
-
"""Generate and optimize molecular conformers using Rowan's conformer_search workflow.
|
|
45
|
+
):
|
|
46
|
+
"""Generate and optimize molecular conformers using Rowan's conformer_search workflow. Valid modes are "reckless", "rapid", "careful", and "meticulous", and default to using SMILES strings for the "molecule" parameter.
|
|
47
47
|
|
|
48
48
|
Args:
|
|
49
49
|
name: Name for the calculation
|
|
@@ -65,7 +65,7 @@ def rowan_conformers(
|
|
|
65
65
|
f"Invalid mode '{mode}'. Valid modes are: {', '.join(valid_modes)}"
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
return log_rowan_api_call(
|
|
69
69
|
workflow_type="conformer_search",
|
|
70
70
|
name=name,
|
|
71
71
|
molecule=molecule,
|
|
@@ -75,61 +75,6 @@ def rowan_conformers(
|
|
|
75
75
|
blocking=blocking,
|
|
76
76
|
ping_interval=ping_interval
|
|
77
77
|
)
|
|
78
|
-
|
|
79
|
-
# Format results based on whether we waited or not
|
|
80
|
-
if blocking:
|
|
81
|
-
# We waited for completion - format actual results
|
|
82
|
-
status = result.get('status', result.get('object_status', 'Unknown'))
|
|
83
|
-
|
|
84
|
-
if status == 2: # Completed successfully
|
|
85
|
-
formatted = f" Conformer search for '{name}' completed successfully!\n\n"
|
|
86
|
-
elif status == 3: # Failed
|
|
87
|
-
formatted = f" Conformer search for '{name}' failed!\n\n"
|
|
88
|
-
else:
|
|
89
|
-
formatted = f" Conformer search for '{name}' finished with status {status}\n\n"
|
|
90
|
-
|
|
91
|
-
formatted += f" Molecule: {molecule}\n"
|
|
92
|
-
formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
93
|
-
formatted += f" Status: {status}\n"
|
|
94
|
-
formatted += f" Mode: {mode.upper()}\n"
|
|
95
|
-
formatted += f" Max Conformers: {max_conformers}\n"
|
|
96
|
-
|
|
97
|
-
# Try to extract actual results
|
|
98
|
-
if isinstance(result, dict) and 'object_data' in result and result['object_data']:
|
|
99
|
-
data = result['object_data']
|
|
100
|
-
|
|
101
|
-
# Count conformers found
|
|
102
|
-
if 'conformers' in data:
|
|
103
|
-
conformer_count = len(data['conformers']) if isinstance(data['conformers'], list) else data.get('num_conformers', 'Unknown')
|
|
104
|
-
formatted += f" Generated Conformers: {conformer_count}\n"
|
|
105
|
-
|
|
106
|
-
# Energy information
|
|
107
|
-
if 'energies' in data and isinstance(data['energies'], list) and data['energies']:
|
|
108
|
-
energies = data['energies']
|
|
109
|
-
min_energy = min(energies)
|
|
110
|
-
max_energy = max(energies)
|
|
111
|
-
energy_range = max_energy - min_energy
|
|
112
|
-
formatted += f" Energy Range: {min_energy:.3f} to {max_energy:.3f} kcal/mol (Δ={energy_range:.3f})\n"
|
|
113
|
-
formatted += f" Lowest Energy Conformer: {min_energy:.3f} kcal/mol\n"
|
|
114
|
-
|
|
115
|
-
# Additional properties if available
|
|
116
|
-
if 'properties' in data:
|
|
117
|
-
props = data['properties']
|
|
118
|
-
formatted += f" Properties calculated: {', '.join(props.keys())}\n"
|
|
119
|
-
|
|
120
|
-
# Basic guidance
|
|
121
|
-
if status == 2:
|
|
122
|
-
formatted += f"\n Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for detailed data\n"
|
|
123
|
-
else:
|
|
124
|
-
# Non-blocking mode - just submission confirmation
|
|
125
|
-
formatted = f" Conformer search for '{name}' submitted!\n\n"
|
|
126
|
-
formatted += f" Molecule: {molecule}\n"
|
|
127
|
-
formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
128
|
-
formatted += f" Status: {result.get('status', 'Submitted')}\n"
|
|
129
|
-
formatted += f" Mode: {mode.upper()}\n"
|
|
130
|
-
formatted += f" Max Conformers: {max_conformers}\n"
|
|
131
|
-
|
|
132
|
-
return formatted
|
|
133
78
|
|
|
134
79
|
if __name__ == "__main__":
|
|
135
80
|
pass
|
|
@@ -186,68 +186,10 @@ def rowan_electronic_properties(
|
|
|
186
186
|
**electronic_params
|
|
187
187
|
)
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
if blocking:
|
|
191
|
-
status = result.get('status', result.get('object_status', 'Unknown'))
|
|
192
|
-
|
|
193
|
-
if status == 2: # Completed successfully
|
|
194
|
-
formatted = f"Electronic properties calculation for '{name}' completed successfully!\n\n"
|
|
195
|
-
elif status == 3: # Failed
|
|
196
|
-
formatted = f"Electronic properties calculation for '{name}' failed!\n\n"
|
|
197
|
-
else:
|
|
198
|
-
formatted = f"Electronic properties calculation for '{name}' submitted!\n\n"
|
|
199
|
-
|
|
200
|
-
formatted += f"Molecule: {molecule}\n"
|
|
201
|
-
formatted += f"Canonical SMILES: {canonical_smiles}\n"
|
|
202
|
-
formatted += f"Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
203
|
-
formatted += f"Status: {status}\n\n"
|
|
204
|
-
|
|
205
|
-
formatted += f"Molecule Lookup: Advanced PubChemPy + SQLite + RDKit system\n\n"
|
|
206
|
-
formatted += f"Calculation Settings:\n"
|
|
207
|
-
formatted += f"• Method: {method.upper()}\n"
|
|
208
|
-
formatted += f"• Basis Set: {basis_set}\n"
|
|
209
|
-
formatted += f"• Engine: {engine.upper()}\n"
|
|
210
|
-
formatted += f"• Charge: {charge}, Multiplicity: {multiplicity}\n\n"
|
|
211
|
-
|
|
212
|
-
formatted += f"Property Calculations:\n"
|
|
213
|
-
formatted += f"• Density Cube: {'Enabled' if compute_density_cube else 'Disabled'}\n"
|
|
214
|
-
formatted += f"• ESP Cube: {'Enabled' if compute_electrostatic_potential_cube else 'Disabled'}\n"
|
|
215
|
-
formatted += f"• Occupied MOs: {compute_num_occupied_orbitals}\n"
|
|
216
|
-
formatted += f"• Virtual MOs: {compute_num_virtual_orbitals}\n\n"
|
|
217
|
-
|
|
218
|
-
if status == 2:
|
|
219
|
-
formatted += f"Additional Analysis:\n"
|
|
220
|
-
formatted += f"• Use rowan_calculation_retrieve('{result.get('uuid')}') for full calculation details\n"
|
|
221
|
-
formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for workflow metadata\n"
|
|
222
|
-
|
|
223
|
-
elif status == 3:
|
|
224
|
-
formatted += f"Troubleshooting:\n"
|
|
225
|
-
formatted += f"• Try simpler method/basis: method='hf', basis_set='sto-3g'\n"
|
|
226
|
-
formatted += f"• Check molecular charge and multiplicity\n"
|
|
227
|
-
formatted += f"• Disable cube generation for faster calculations\n"
|
|
228
|
-
formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for error details\n"
|
|
229
|
-
else:
|
|
230
|
-
formatted += f"Next Steps:\n"
|
|
231
|
-
formatted += f"• Monitor status with rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}')\n"
|
|
232
|
-
formatted += f"• Electronic properties calculations may take several minutes\n"
|
|
233
|
-
|
|
234
|
-
return formatted
|
|
235
|
-
else:
|
|
236
|
-
return str(result)
|
|
189
|
+
return result
|
|
237
190
|
|
|
238
191
|
except Exception as e:
|
|
239
|
-
|
|
240
|
-
error_msg += f"Molecule: {molecule}\n"
|
|
241
|
-
error_msg += f"Canonical SMILES: {canonical_smiles}\n"
|
|
242
|
-
error_msg += f"Settings: {method}/{basis_set}/{engine}\n\n"
|
|
243
|
-
error_msg += f"Molecule Lookup: Advanced PubChemPy + SQLite + RDKit system\n\n"
|
|
244
|
-
error_msg += f"Common Issues:\n"
|
|
245
|
-
error_msg += f"• Invalid method/basis set combination\n"
|
|
246
|
-
error_msg += f"• Incorrect charge/multiplicity for molecule\n"
|
|
247
|
-
error_msg += f"• Engine compatibility issues\n"
|
|
248
|
-
error_msg += f"• Molecule not found in PubChem database\n"
|
|
249
|
-
error_msg += f"• Try with default parameters first\n"
|
|
250
|
-
return error_msg
|
|
192
|
+
return f"Electronic properties calculation failed: {str(e)}"
|
|
251
193
|
|
|
252
194
|
def test_electronic_properties():
|
|
253
195
|
"""Test the electronic properties function with advanced molecule lookup."""
|
rowan_mcp/functions/fukui.py
CHANGED
|
@@ -54,53 +54,33 @@ def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
|
54
54
|
raise e
|
|
55
55
|
|
|
56
56
|
def lookup_molecule_smiles(molecule_name: str) -> str:
|
|
57
|
-
"""Look up canonical SMILES
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"aniline": "Nc1ccccc1",
|
|
65
|
-
"benzoic acid": "O=C(O)c1ccccc1",
|
|
66
|
-
"salicylic acid": "O=C(O)c1ccccc1O",
|
|
67
|
-
"aspirin": "CC(=O)Oc1ccccc1C(=O)O",
|
|
57
|
+
"""Look up canonical SMILES using the advanced molecule_lookup system.
|
|
58
|
+
|
|
59
|
+
Uses PubChemPy + SQLite caching + RDKit validation for scalable molecule lookup.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
# Import the advanced molecule lookup system
|
|
63
|
+
from .molecule_lookup import get_lookup_instance
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"acetone": "CC(=O)C",
|
|
72
|
-
"dmso": "CS(=O)C",
|
|
73
|
-
"dmf": "CN(C)C=O",
|
|
74
|
-
"thf": "C1CCOC1",
|
|
75
|
-
"dioxane": "C1COCCO1",
|
|
76
|
-
"chloroform": "ClC(Cl)Cl",
|
|
77
|
-
"dichloromethane": "ClCCl",
|
|
65
|
+
lookup = get_lookup_instance()
|
|
66
|
+
smiles, source, metadata = lookup.get_smiles(molecule_name)
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"ethylene": "C=C",
|
|
82
|
-
"acetylene": "C#C",
|
|
83
|
-
"formaldehyde": "C=O",
|
|
84
|
-
"ammonia": "N",
|
|
85
|
-
"hydrogen peroxide": "OO",
|
|
86
|
-
"carbon dioxide": "O=C=O",
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Normalize the input (lowercase, strip whitespace)
|
|
90
|
-
normalized_name = molecule_name.lower().strip()
|
|
91
|
-
|
|
92
|
-
# Direct lookup
|
|
93
|
-
if normalized_name in MOLECULE_SMILES:
|
|
94
|
-
return MOLECULE_SMILES[normalized_name]
|
|
95
|
-
|
|
96
|
-
# Try partial matches for common variations
|
|
97
|
-
for name, smiles in MOLECULE_SMILES.items():
|
|
98
|
-
if normalized_name in name or name in normalized_name:
|
|
99
|
-
logger.info(f"SMILES Lookup (partial match): '{molecule_name}' → '{name}' → '{smiles}'")
|
|
68
|
+
if smiles:
|
|
69
|
+
logger.info(f"Molecule lookup successful: '{molecule_name}' → '{smiles}' (source: {source})")
|
|
100
70
|
return smiles
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
71
|
+
else:
|
|
72
|
+
logger.warning(f"Molecule lookup failed for '{molecule_name}': {metadata.get('error', 'Unknown error')}")
|
|
73
|
+
# Return original input as fallback (might be valid SMILES)
|
|
74
|
+
return molecule_name
|
|
75
|
+
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
logger.error(f"Could not import molecule_lookup: {e}")
|
|
78
|
+
# Fallback: return original input
|
|
79
|
+
return molecule_name
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Molecule lookup error for '{molecule_name}': {e}")
|
|
82
|
+
# Fallback: return original input
|
|
83
|
+
return molecule_name
|
|
104
84
|
|
|
105
85
|
def rowan_fukui(
|
|
106
86
|
name: str,
|
|
@@ -135,9 +115,12 @@ def rowan_fukui(
|
|
|
135
115
|
- Per-atom reactivity indices for site-specific analysis
|
|
136
116
|
- Global reactivity descriptors
|
|
137
117
|
|
|
118
|
+
**Molecule Lookup**: Uses advanced PubChemPy + SQLite caching + RDKit validation system
|
|
119
|
+
for robust molecule identification and SMILES canonicalization.
|
|
120
|
+
|
|
138
121
|
Args:
|
|
139
122
|
name: Name for the calculation
|
|
140
|
-
molecule: Molecule
|
|
123
|
+
molecule: Molecule name (e.g., "aspirin", "taxol") or SMILES string
|
|
141
124
|
optimize: Whether to optimize geometry before Fukui calculation (default: True)
|
|
142
125
|
opt_method: Method for optimization (default: None, uses engine default)
|
|
143
126
|
opt_basis_set: Basis set for optimization (default: None, uses engine default)
|
|
@@ -212,135 +195,16 @@ def rowan_fukui(
|
|
|
212
195
|
if fukui_engine:
|
|
213
196
|
fukui_params["fukui_engine"] = fukui_engine.lower()
|
|
214
197
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if blocking:
|
|
221
|
-
status = result.get('status', result.get('object_status', 'Unknown'))
|
|
222
|
-
|
|
223
|
-
if status == 2: # Completed successfully
|
|
224
|
-
formatted = f"Fukui analysis for '{name}' completed successfully!\n\n"
|
|
225
|
-
elif status == 3: # Failed
|
|
226
|
-
formatted = f"Fukui analysis for '{name}' failed!\n\n"
|
|
227
|
-
else:
|
|
228
|
-
formatted = f"Fukui analysis for '{name}' finished with status {status}\n\n"
|
|
229
|
-
|
|
230
|
-
formatted += f"Molecule: {molecule}\n"
|
|
231
|
-
formatted += f"SMILES: {canonical_smiles}\n"
|
|
232
|
-
formatted += f"Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
233
|
-
formatted += f"Status: {status}\n"
|
|
234
|
-
|
|
235
|
-
# Computational settings summary
|
|
236
|
-
formatted += f"\nComputational Settings:\n"
|
|
237
|
-
formatted += f" Optimization: {'Enabled' if optimize else 'Disabled'}\n"
|
|
238
|
-
if optimize:
|
|
239
|
-
opt_method_display = opt_settings.get('method', 'default') if opt_settings else 'default'
|
|
240
|
-
formatted += f" Opt Method: {opt_method_display.upper()}\n"
|
|
241
|
-
if opt_engine:
|
|
242
|
-
formatted += f" Opt Engine: {opt_engine.upper()}\n"
|
|
243
|
-
formatted += f" Fukui Method: {fukui_method.upper()}\n"
|
|
244
|
-
if fukui_engine:
|
|
245
|
-
formatted += f" Fukui Engine: {fukui_engine.upper()}\n"
|
|
246
|
-
formatted += f" Charge: {charge}, Multiplicity: {multiplicity}\n"
|
|
198
|
+
try:
|
|
199
|
+
result = log_rowan_api_call(
|
|
200
|
+
workflow_type="fukui",
|
|
201
|
+
**fukui_params
|
|
202
|
+
)
|
|
247
203
|
|
|
248
|
-
|
|
249
|
-
if isinstance(result, dict) and 'object_data' in result and result['object_data']:
|
|
250
|
-
data = result['object_data']
|
|
251
|
-
|
|
252
|
-
# Global electrophilicity index
|
|
253
|
-
if 'global_electrophilicity_index' in data and data['global_electrophilicity_index'] is not None:
|
|
254
|
-
gei = data['global_electrophilicity_index']
|
|
255
|
-
formatted += f"\nGlobal Electrophilicity Index: {gei:.4f}\n"
|
|
256
|
-
if gei > 1.5:
|
|
257
|
-
formatted += f" → Strong electrophile (highly reactive towards nucleophiles)\n"
|
|
258
|
-
elif gei > 0.8:
|
|
259
|
-
formatted += f" → Moderate electrophile\n"
|
|
260
|
-
else:
|
|
261
|
-
formatted += f" → Weak electrophile\n"
|
|
204
|
+
return result
|
|
262
205
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if 'fukui_positive' in data and data['fukui_positive']:
|
|
266
|
-
fukui_available.append("f(+)")
|
|
267
|
-
if 'fukui_negative' in data and data['fukui_negative']:
|
|
268
|
-
fukui_available.append("f(-)")
|
|
269
|
-
if 'fukui_zero' in data and data['fukui_zero']:
|
|
270
|
-
fukui_available.append("f(0)")
|
|
271
|
-
|
|
272
|
-
if fukui_available:
|
|
273
|
-
formatted += f"\nFukui Indices Available: {', '.join(fukui_available)}\n"
|
|
274
|
-
|
|
275
|
-
# Analyze most reactive sites
|
|
276
|
-
formatted += f"\nMost Reactive Sites:\n"
|
|
277
|
-
|
|
278
|
-
# f(+) - electrophilic attack sites
|
|
279
|
-
if 'fukui_positive' in data and data['fukui_positive']:
|
|
280
|
-
f_plus = data['fukui_positive']
|
|
281
|
-
if isinstance(f_plus, list) and len(f_plus) > 0:
|
|
282
|
-
# Find top 3 sites
|
|
283
|
-
indexed_values = [(i+1, val) for i, val in enumerate(f_plus) if val is not None]
|
|
284
|
-
top_f_plus = sorted(indexed_values, key=lambda x: x[1], reverse=True)[:3]
|
|
285
|
-
formatted += f" f(+) Top Sites (electrophilic attack): "
|
|
286
|
-
formatted += f"{', '.join([f'Atom {atom}({val:.3f})' for atom, val in top_f_plus])}\n"
|
|
287
|
-
|
|
288
|
-
# f(-) - nucleophilic attack sites
|
|
289
|
-
if 'fukui_negative' in data and data['fukui_negative']:
|
|
290
|
-
f_minus = data['fukui_negative']
|
|
291
|
-
if isinstance(f_minus, list) and len(f_minus) > 0:
|
|
292
|
-
indexed_values = [(i+1, val) for i, val in enumerate(f_minus) if val is not None]
|
|
293
|
-
top_f_minus = sorted(indexed_values, key=lambda x: x[1], reverse=True)[:3]
|
|
294
|
-
formatted += f" f(-) Top Sites (nucleophilic attack): "
|
|
295
|
-
formatted += f"{', '.join([f'Atom {atom}({val:.3f})' for atom, val in top_f_minus])}\n"
|
|
296
|
-
|
|
297
|
-
# f(0) - radical attack sites
|
|
298
|
-
if 'fukui_zero' in data and data['fukui_zero']:
|
|
299
|
-
f_zero = data['fukui_zero']
|
|
300
|
-
if isinstance(f_zero, list) and len(f_zero) > 0:
|
|
301
|
-
indexed_values = [(i+1, val) for i, val in enumerate(f_zero) if val is not None]
|
|
302
|
-
top_f_zero = sorted(indexed_values, key=lambda x: x[1], reverse=True)[:3]
|
|
303
|
-
formatted += f" f(0) Top Sites (radical attack): "
|
|
304
|
-
formatted += f"{', '.join([f'Atom {atom}({val:.3f})' for atom, val in top_f_zero])}\n"
|
|
305
|
-
|
|
306
|
-
# Status-specific guidance
|
|
307
|
-
formatted += f"\nNext Steps:\n"
|
|
308
|
-
if status == 2: # Completed
|
|
309
|
-
formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for full per-atom data\n"
|
|
310
|
-
formatted += f"• Higher Fukui values indicate more reactive sites\n"
|
|
311
|
-
formatted += f"• f(+) predicts where nucleophiles will attack\n"
|
|
312
|
-
formatted += f"• f(-) predicts where electrophiles will attack\n"
|
|
313
|
-
formatted += f"• f(0) predicts radical reaction sites\n"
|
|
314
|
-
elif status == 3: # Failed
|
|
315
|
-
formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for error details\n"
|
|
316
|
-
formatted += f"• Troubleshooting:\n"
|
|
317
|
-
formatted += f" - Try disabling optimization: optimize=False\n"
|
|
318
|
-
formatted += f" - Use faster Fukui method: fukui_method='gfn1_xtb'\n"
|
|
319
|
-
formatted += f" - Check if molecule SMILES is valid\n"
|
|
320
|
-
formatted += f" - Verify charge and multiplicity are correct\n"
|
|
321
|
-
elif status in [0, 1, 5]: # Running
|
|
322
|
-
formatted += f"• Check progress: rowan_workflow_management(action='status', workflow_uuid='{result.get('uuid')}')\n"
|
|
323
|
-
if optimize:
|
|
324
|
-
formatted += f"• Two-step process: optimization → Fukui calculation\n"
|
|
325
|
-
formatted += f"• Fukui analysis may take 5-20 minutes depending on method and molecule size\n"
|
|
326
|
-
elif status == 4: # Stopped
|
|
327
|
-
formatted += f"• Check why stopped: rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}')\n"
|
|
328
|
-
formatted += f"• You can restart with same or modified parameters\n"
|
|
329
|
-
else: # Unknown
|
|
330
|
-
formatted += f"• Check status: rowan_workflow_management(action='status', workflow_uuid='{result.get('uuid')}')\n"
|
|
331
|
-
|
|
332
|
-
return formatted
|
|
333
|
-
else:
|
|
334
|
-
formatted = f"Fukui analysis for '{name}' submitted!\n\n"
|
|
335
|
-
formatted += f"Molecule: {molecule}\n"
|
|
336
|
-
formatted += f"SMILES: {canonical_smiles}\n"
|
|
337
|
-
formatted += f"Job UUID: {result.get('uuid', 'N/A')}\n"
|
|
338
|
-
formatted += f"Status: {result.get('status', 'Submitted')}\n"
|
|
339
|
-
formatted += f"Optimization: {'Enabled' if optimize else 'Disabled'}\n"
|
|
340
|
-
formatted += f"Fukui Method: {fukui_method.upper()}\n"
|
|
341
|
-
formatted += f"Charge: {charge}, Multiplicity: {multiplicity}\n"
|
|
342
|
-
formatted += f"\nUse rowan_workflow_management tools to check progress and retrieve results\n"
|
|
343
|
-
return formatted
|
|
206
|
+
except Exception as e:
|
|
207
|
+
return f"Fukui analysis failed: {str(e)}"
|
|
344
208
|
|
|
345
209
|
def test_fukui():
|
|
346
210
|
"""Test the fukui function."""
|
rowan_mcp/functions/macropka.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""MacropKa workflow function for MCP server."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import json
|
|
5
4
|
import logging
|
|
6
5
|
from typing import Optional, Union, List
|
|
7
6
|
|
|
@@ -61,11 +60,11 @@ def rowan_macropka(
|
|
|
61
60
|
try:
|
|
62
61
|
# Validate pH range
|
|
63
62
|
if min_pH >= max_pH:
|
|
64
|
-
return
|
|
63
|
+
return "Error: min_pH must be less than max_pH"
|
|
65
64
|
|
|
66
65
|
# Validate charge range
|
|
67
66
|
if min_charge >= max_charge:
|
|
68
|
-
return
|
|
67
|
+
return "Error: min_charge must be less than max_charge"
|
|
69
68
|
|
|
70
69
|
# Log the API call
|
|
71
70
|
log_rowan_api_call(
|
|
@@ -102,85 +101,11 @@ def rowan_macropka(
|
|
|
102
101
|
compute_solvation_energy=compute_solvation_energy
|
|
103
102
|
)
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
# Format completed results
|
|
107
|
-
status = result.get("status", "unknown")
|
|
108
|
-
uuid = result.get("uuid", "unknown")
|
|
109
|
-
|
|
110
|
-
if status == "success":
|
|
111
|
-
object_data = result.get("object_data", {})
|
|
112
|
-
|
|
113
|
-
# Extract key results
|
|
114
|
-
microstates = object_data.get("microstates", [])
|
|
115
|
-
pka_values = object_data.get("pKa_values", [])
|
|
116
|
-
isoelectric_point = object_data.get("isoelectric_point")
|
|
117
|
-
solvation_energy = object_data.get("solvation_energy")
|
|
118
|
-
kpuu_probability = object_data.get("kpuu_probability")
|
|
119
|
-
microstate_weights_by_pH = object_data.get("microstate_weights_by_pH", [])
|
|
120
|
-
logD_by_pH = object_data.get("logD_by_pH", [])
|
|
121
|
-
aqueous_solubility_by_pH = object_data.get("aqueous_solubility_by_pH", [])
|
|
122
|
-
|
|
123
|
-
formatted = f"✅ MacropKa calculation completed successfully!\n"
|
|
124
|
-
formatted += f"🔖 Workflow UUID: {uuid}\n"
|
|
125
|
-
formatted += f"📋 Status: {status}\n\n"
|
|
126
|
-
|
|
127
|
-
# Format pKa values
|
|
128
|
-
if pka_values:
|
|
129
|
-
formatted += "📊 pKa Values:\n"
|
|
130
|
-
for pka in pka_values:
|
|
131
|
-
formatted += f" • {pka.get('initial_charge', 'N/A')} → {pka.get('final_charge', 'N/A')}: pKa = {pka.get('pKa', 'N/A')}\n"
|
|
132
|
-
formatted += "\n"
|
|
133
|
-
|
|
134
|
-
# Format microstates
|
|
135
|
-
if microstates:
|
|
136
|
-
formatted += f"🔬 Microstates ({len(microstates)} found):\n"
|
|
137
|
-
for i, microstate in enumerate(microstates[:5]): # Show first 5
|
|
138
|
-
formatted += f" {i+1}. Charge: {microstate.get('charge', 'N/A')}, Energy: {microstate.get('energy', 'N/A')} kcal/mol\n"
|
|
139
|
-
if len(microstates) > 5:
|
|
140
|
-
formatted += f" ... and {len(microstates) - 5} more\n"
|
|
141
|
-
formatted += "\n"
|
|
142
|
-
|
|
143
|
-
# Add other properties
|
|
144
|
-
if isoelectric_point is not None:
|
|
145
|
-
formatted += f"⚡ Isoelectric Point: pH {isoelectric_point}\n"
|
|
146
|
-
|
|
147
|
-
if solvation_energy is not None:
|
|
148
|
-
formatted += f"💧 Solvation Energy: {solvation_energy} kcal/mol\n"
|
|
149
|
-
|
|
150
|
-
if kpuu_probability is not None:
|
|
151
|
-
formatted += f"🧠 Kpuu Probability (≥0.3): {kpuu_probability:.2%}\n"
|
|
152
|
-
|
|
153
|
-
# Show pH-dependent properties if available
|
|
154
|
-
if logD_by_pH:
|
|
155
|
-
formatted += f"\n📈 logD values available for {len(logD_by_pH)} pH points\n"
|
|
156
|
-
|
|
157
|
-
if aqueous_solubility_by_pH:
|
|
158
|
-
formatted += f"💧 Aqueous solubility values available for {len(aqueous_solubility_by_pH)} pH points\n"
|
|
159
|
-
|
|
160
|
-
if microstate_weights_by_pH:
|
|
161
|
-
formatted += f"⚖️ Microstate weights available for {len(microstate_weights_by_pH)} pH points\n"
|
|
162
|
-
|
|
163
|
-
return formatted
|
|
164
|
-
else:
|
|
165
|
-
# Handle failed calculation
|
|
166
|
-
return f"❌ MacropKa calculation failed\n🔖 UUID: {uuid}\n📋 Status: {status}\n💬 Check workflow details for more information"
|
|
167
|
-
else:
|
|
168
|
-
# Non-blocking mode - return submission confirmation
|
|
169
|
-
uuid = result.get("uuid", "unknown")
|
|
170
|
-
formatted = f"📋 MacropKa calculation submitted!\n"
|
|
171
|
-
formatted += f"🔖 Workflow UUID: {uuid}\n"
|
|
172
|
-
formatted += f"⏳ Status: Running...\n"
|
|
173
|
-
formatted += f"💡 Use rowan_workflow_management to check status\n"
|
|
174
|
-
formatted += f"\nCalculation parameters:\n"
|
|
175
|
-
formatted += f" • pH range: {min_pH} - {max_pH}\n"
|
|
176
|
-
formatted += f" • Charge range: {min_charge} to {max_charge}\n"
|
|
177
|
-
formatted += f" • Compute solvation energy: {compute_solvation_energy}\n"
|
|
178
|
-
formatted += f" • Compute aqueous solubility: {compute_aqueous_solubility}\n"
|
|
179
|
-
return formatted
|
|
104
|
+
return result
|
|
180
105
|
|
|
181
106
|
except Exception as e:
|
|
182
107
|
logger.error(f"Error in rowan_macropka: {str(e)}")
|
|
183
|
-
return
|
|
108
|
+
return f"MacropKa calculation failed: {str(e)}"
|
|
184
109
|
|
|
185
110
|
|
|
186
111
|
# Test function
|