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,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan docking function for MCP tool integration.
|
|
3
|
+
Implements protein-ligand docking following the stjames-public workflow pattern.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Union, List, Tuple, Dict, Any
|
|
7
|
+
import rowan
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
# Set up logging
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Get API key from environment
|
|
16
|
+
api_key = os.environ.get("ROWAN_API_KEY")
|
|
17
|
+
if api_key:
|
|
18
|
+
rowan.api_key = api_key
|
|
19
|
+
else:
|
|
20
|
+
logger.warning("ROWAN_API_KEY not found in environment")
|
|
21
|
+
|
|
22
|
+
def fetch_pdb_content(pdb_id: str) -> Optional[str]:
|
|
23
|
+
"""Fetch PDB content from RCSB PDB database.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
pdb_id: 4-character PDB ID (e.g., "1COX")
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
PDB file content as string, or None if fetch fails
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
url = f"https://files.rcsb.org/download/{pdb_id.upper()}.pdb"
|
|
33
|
+
response = requests.get(url, timeout=30)
|
|
34
|
+
response.raise_for_status()
|
|
35
|
+
return response.text
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Failed to fetch PDB {pdb_id}: {str(e)}")
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def rowan_docking(
|
|
41
|
+
name: str,
|
|
42
|
+
# Ligand specification (following stjames priority)
|
|
43
|
+
molecules: Optional[List[str]] = None,
|
|
44
|
+
smiles: Optional[Union[str, List[str]]] = None,
|
|
45
|
+
# Protein specification
|
|
46
|
+
target: Optional[str] = None,
|
|
47
|
+
target_uuid: Optional[str] = None,
|
|
48
|
+
# Pocket specification
|
|
49
|
+
pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
|
|
50
|
+
# Workflow parameters
|
|
51
|
+
do_csearch: bool = True,
|
|
52
|
+
do_optimization: bool = True,
|
|
53
|
+
do_pose_refinement: bool = True,
|
|
54
|
+
conformers: Optional[List[str]] = None,
|
|
55
|
+
# MCP workflow parameters
|
|
56
|
+
folder_uuid: Optional[str] = None,
|
|
57
|
+
blocking: bool = True,
|
|
58
|
+
ping_interval: int = 5
|
|
59
|
+
) -> str:
|
|
60
|
+
"""Perform protein-ligand docking using Rowan's ML workflow.
|
|
61
|
+
|
|
62
|
+
Performs comprehensive protein-ligand docking with conformer generation,
|
|
63
|
+
strain filtering, AutoDock Vina docking, and pose refinement.
|
|
64
|
+
|
|
65
|
+
IMPORTANT: For docking, you need to provide the protein target in one of two ways:
|
|
66
|
+
1. target_uuid: UUID of a pre-uploaded protein in your Rowan account
|
|
67
|
+
2. target: Reserved for future use (currently not supported via API)
|
|
68
|
+
|
|
69
|
+
To dock against a known protein like 1COX, you should:
|
|
70
|
+
1. Upload the PDB file to your Rowan account via the web interface
|
|
71
|
+
2. Get the protein UUID from your account
|
|
72
|
+
3. Use that UUID with the target_uuid parameter
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: Name for the docking calculation
|
|
76
|
+
molecules: List of molecules to dock (SMILES strings)
|
|
77
|
+
smiles: SMILES string(s) of ligands (can be string or list)
|
|
78
|
+
target: Not currently supported - use target_uuid instead
|
|
79
|
+
target_uuid: UUID of pre-uploaded protein target from your Rowan account
|
|
80
|
+
pocket: Tuple of (center, size) where each is (x, y, z) coordinates
|
|
81
|
+
Example: ((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
|
|
82
|
+
do_csearch: Whether to perform conformer search (default: True)
|
|
83
|
+
do_optimization: Whether to optimize starting structures (default: True)
|
|
84
|
+
do_pose_refinement: Whether to optimize non-rotatable bonds in output poses (default: True)
|
|
85
|
+
conformers: List of pre-optimized conformer UUIDs (optional)
|
|
86
|
+
folder_uuid: Optional folder UUID to organize the calculation
|
|
87
|
+
blocking: Whether to wait for completion (default: True)
|
|
88
|
+
ping_interval: Interval in seconds to check calculation status
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Formatted string with workflow results or submission confirmation
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
# Using pre-uploaded protein
|
|
95
|
+
rowan_docking(
|
|
96
|
+
name="aspirin_docking",
|
|
97
|
+
smiles="CC(=O)Oc1ccccc1C(=O)O",
|
|
98
|
+
target_uuid="your-protein-uuid-here",
|
|
99
|
+
pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Multiple molecules
|
|
103
|
+
rowan_docking(
|
|
104
|
+
name="multi_ligand_docking",
|
|
105
|
+
molecules=["CCO", "CC(C)O", "CCCO"],
|
|
106
|
+
target_uuid="your-protein-uuid-here",
|
|
107
|
+
pocket=((0.0, 0.0, 0.0), (25.0, 25.0, 25.0))
|
|
108
|
+
)
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Validate that we have ligand input
|
|
112
|
+
if molecules is None and smiles is None:
|
|
113
|
+
return "Error: Either 'molecules' or 'smiles' parameter must be provided"
|
|
114
|
+
|
|
115
|
+
# Validate that we have protein input
|
|
116
|
+
if target is None and target_uuid is None:
|
|
117
|
+
return "Error: Either 'target' or 'target_uuid' parameter must be provided"
|
|
118
|
+
|
|
119
|
+
# If target is provided, inform user it's not currently supported
|
|
120
|
+
if target is not None:
|
|
121
|
+
return ("Error: Direct PDB content upload is not currently supported through this API. "
|
|
122
|
+
"Please use one of these alternatives:\n"
|
|
123
|
+
"1. Upload your PDB file through the Rowan web interface at https://labs.rowansci.com\n"
|
|
124
|
+
"2. Get the protein UUID from your account\n"
|
|
125
|
+
"3. Use the 'target_uuid' parameter with that UUID\n\n"
|
|
126
|
+
"For known proteins like 1COX, 2COX, etc., they may already be available "
|
|
127
|
+
"in the Rowan protein library.")
|
|
128
|
+
|
|
129
|
+
# Validate pocket if provided
|
|
130
|
+
if pocket is not None:
|
|
131
|
+
try:
|
|
132
|
+
center, size = pocket
|
|
133
|
+
if len(center) != 3 or len(size) != 3:
|
|
134
|
+
return "Error: Pocket must be ((x,y,z), (x,y,z)) format"
|
|
135
|
+
|
|
136
|
+
# Validate that size dimensions are positive
|
|
137
|
+
if any(s <= 0 for s in size):
|
|
138
|
+
return "Error: Pocket size dimensions must be positive"
|
|
139
|
+
|
|
140
|
+
except (TypeError, ValueError) as e:
|
|
141
|
+
return f"Error: Invalid pocket format. Expected ((x,y,z), (x,y,z)). Got: {pocket}"
|
|
142
|
+
else:
|
|
143
|
+
# Default pocket if not provided
|
|
144
|
+
pocket = ((0.0, 0.0, 0.0), (20.0, 20.0, 20.0))
|
|
145
|
+
logger.info("Using default pocket: center=(0,0,0), size=(20,20,20)")
|
|
146
|
+
|
|
147
|
+
# Determine initial_molecule for rowan.compute()
|
|
148
|
+
# For docking, the 'molecule' parameter is the ligand to dock
|
|
149
|
+
initial_molecule = None
|
|
150
|
+
ligands_list = None
|
|
151
|
+
|
|
152
|
+
if molecules and len(molecules) > 0:
|
|
153
|
+
initial_molecule = molecules[0]
|
|
154
|
+
ligands_list = molecules
|
|
155
|
+
elif isinstance(smiles, str):
|
|
156
|
+
initial_molecule = smiles
|
|
157
|
+
ligands_list = [smiles]
|
|
158
|
+
elif isinstance(smiles, list) and len(smiles) > 0:
|
|
159
|
+
initial_molecule = smiles[0]
|
|
160
|
+
ligands_list = smiles
|
|
161
|
+
else:
|
|
162
|
+
return "Error: Could not determine initial molecule from inputs"
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
# Build parameters following stjames DockingWorkflow structure
|
|
166
|
+
compute_params = {
|
|
167
|
+
"name": name,
|
|
168
|
+
"molecule": initial_molecule, # Required by rowan.compute() - this is the ligand
|
|
169
|
+
"workflow_type": "docking",
|
|
170
|
+
"folder_uuid": folder_uuid,
|
|
171
|
+
"blocking": blocking,
|
|
172
|
+
"ping_interval": ping_interval,
|
|
173
|
+
# DockingWorkflow specific parameters
|
|
174
|
+
"do_csearch": do_csearch,
|
|
175
|
+
"do_optimization": do_optimization,
|
|
176
|
+
"do_pose_refinement": do_pose_refinement,
|
|
177
|
+
"pocket": pocket,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# For multiple ligands, pass them as 'molecules' parameter
|
|
181
|
+
# This allows docking multiple ligands in one workflow
|
|
182
|
+
if ligands_list and len(ligands_list) > 1:
|
|
183
|
+
compute_params["molecules"] = ligands_list
|
|
184
|
+
|
|
185
|
+
# Add protein specification (only one of these should be present)
|
|
186
|
+
if target_uuid is not None:
|
|
187
|
+
compute_params["target_uuid"] = target_uuid
|
|
188
|
+
# Note: Direct PDB upload is not supported in current implementation
|
|
189
|
+
# Users must pre-upload proteins through the Rowan web interface
|
|
190
|
+
|
|
191
|
+
# Add conformers if provided
|
|
192
|
+
if conformers is not None:
|
|
193
|
+
compute_params["conformers"] = conformers
|
|
194
|
+
|
|
195
|
+
# Submit docking calculation and return raw result
|
|
196
|
+
result = rowan.compute(**compute_params)
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"Error in rowan_docking: {str(e)}")
|
|
201
|
+
return f"Docking calculation failed: {str(e)}"
|
|
202
|
+
|
|
203
|
+
def rowan_docking_pdb_id(
|
|
204
|
+
name: str,
|
|
205
|
+
pdb_id: str,
|
|
206
|
+
# Ligand specification
|
|
207
|
+
molecules: Optional[List[str]] = None,
|
|
208
|
+
smiles: Optional[Union[str, List[str]]] = None,
|
|
209
|
+
# Other parameters same as rowan_docking
|
|
210
|
+
pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
|
|
211
|
+
do_csearch: bool = True,
|
|
212
|
+
do_optimization: bool = True,
|
|
213
|
+
do_pose_refinement: bool = True,
|
|
214
|
+
conformers: Optional[List[str]] = None,
|
|
215
|
+
folder_uuid: Optional[str] = None,
|
|
216
|
+
blocking: bool = True,
|
|
217
|
+
ping_interval: int = 5
|
|
218
|
+
) -> str:
|
|
219
|
+
"""Helper function that explains how to dock with a PDB ID.
|
|
220
|
+
|
|
221
|
+
NOTE: Direct PDB upload is not currently supported through the API.
|
|
222
|
+
This function provides guidance on how to dock against known proteins.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
name: Name for the docking calculation
|
|
226
|
+
pdb_id: 4-character PDB ID (e.g., "1COX", "2COX", "6COX")
|
|
227
|
+
(other parameters same as rowan_docking)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Instructions on how to perform docking with the given PDB
|
|
231
|
+
"""
|
|
232
|
+
# Check if we can fetch the PDB to verify it exists
|
|
233
|
+
logger.info(f"Checking if PDB {pdb_id} exists...")
|
|
234
|
+
pdb_content = fetch_pdb_content(pdb_id)
|
|
235
|
+
|
|
236
|
+
if pdb_content is None:
|
|
237
|
+
return (f"Error: PDB ID '{pdb_id}' not found in RCSB database. "
|
|
238
|
+
f"Please check that the PDB ID is valid.")
|
|
239
|
+
|
|
240
|
+
# Provide instructions for the user
|
|
241
|
+
ligand_param = ""
|
|
242
|
+
if molecules:
|
|
243
|
+
ligand_param = f" molecules={molecules},\n"
|
|
244
|
+
elif smiles:
|
|
245
|
+
if isinstance(smiles, str):
|
|
246
|
+
ligand_param = f' smiles="{smiles}",\n'
|
|
247
|
+
else:
|
|
248
|
+
ligand_param = f" smiles={smiles},\n"
|
|
249
|
+
|
|
250
|
+
return (f"PDB {pdb_id} found in RCSB database!\n\n"
|
|
251
|
+
f"To perform docking with this protein:\n\n"
|
|
252
|
+
f"1. Go to https://labs.rowansci.com\n"
|
|
253
|
+
f"2. Upload the PDB file for {pdb_id}\n"
|
|
254
|
+
f" - You can download it from: https://files.rcsb.org/download/{pdb_id.upper()}.pdb\n"
|
|
255
|
+
f"3. Once uploaded, find the protein UUID in your account\n"
|
|
256
|
+
f"4. Use the docking function with target_uuid:\n\n"
|
|
257
|
+
f"```python\n"
|
|
258
|
+
f"rowan_docking(\n"
|
|
259
|
+
f" name=\"{name}\",\n"
|
|
260
|
+
f"{ligand_param}"
|
|
261
|
+
f" target_uuid=\"your-protein-uuid-here\",\n"
|
|
262
|
+
f" pocket={pocket},\n"
|
|
263
|
+
f" do_csearch={do_csearch},\n"
|
|
264
|
+
f" do_optimization={do_optimization},\n"
|
|
265
|
+
f" do_pose_refinement={do_pose_refinement}\n"
|
|
266
|
+
f")\n"
|
|
267
|
+
f"```\n\n"
|
|
268
|
+
f"Note: Some common proteins may already be available in the Rowan protein library.\n"
|
|
269
|
+
f"Check your account for pre-uploaded structures.")
|
|
270
|
+
|
|
271
|
+
def test_rowan_docking():
|
|
272
|
+
"""Test the rowan_docking function."""
|
|
273
|
+
try:
|
|
274
|
+
# Test with minimal parameters
|
|
275
|
+
result = rowan_docking(
|
|
276
|
+
name="test_docking",
|
|
277
|
+
smiles="CCO", # Ethanol
|
|
278
|
+
target_uuid="test-protein-uuid", # Would need real UUID
|
|
279
|
+
pocket=((0.0, 0.0, 0.0), (20.0, 20.0, 20.0)),
|
|
280
|
+
blocking=False
|
|
281
|
+
)
|
|
282
|
+
print("Docking test result:")
|
|
283
|
+
print(result)
|
|
284
|
+
return True
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print(f"Docking test failed: {e}")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
if __name__ == "__main__":
|
|
290
|
+
test_rowan_docking()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced docking function that accepts PDB IDs directly
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union, List, Tuple
|
|
6
|
+
import logging
|
|
7
|
+
from .docking import rowan_docking
|
|
8
|
+
from .pdb_handler import handle_pdb_input
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
def rowan_docking_enhanced(
|
|
13
|
+
name: str,
|
|
14
|
+
# Ligand specification
|
|
15
|
+
smiles: Optional[Union[str, List[str]]] = None,
|
|
16
|
+
molecules: Optional[List[str]] = None,
|
|
17
|
+
# Protein specification - NEW: accepts PDB ID or UUID
|
|
18
|
+
target: Optional[str] = None, # Can be PDB ID (e.g., "4Z18") or UUID
|
|
19
|
+
# Pocket specification
|
|
20
|
+
pocket: Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]] = None,
|
|
21
|
+
# Workflow parameters
|
|
22
|
+
do_csearch: bool = True,
|
|
23
|
+
do_optimization: bool = True,
|
|
24
|
+
do_pose_refinement: bool = True,
|
|
25
|
+
conformers: Optional[List[str]] = None,
|
|
26
|
+
# MCP workflow parameters
|
|
27
|
+
folder_uuid: Optional[str] = None,
|
|
28
|
+
blocking: bool = True,
|
|
29
|
+
ping_interval: int = 5
|
|
30
|
+
) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Enhanced docking function that accepts PDB IDs directly.
|
|
33
|
+
|
|
34
|
+
This function intelligently handles protein input:
|
|
35
|
+
- If target looks like a UUID (36 chars with dashes), uses it directly
|
|
36
|
+
- If target looks like a PDB ID (e.g., "4Z18"), attempts to fetch and use it
|
|
37
|
+
- Provides clear instructions when manual upload is needed
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
name: Name for the docking calculation
|
|
41
|
+
smiles: SMILES string(s) of ligands
|
|
42
|
+
molecules: Alternative to smiles - list of molecules
|
|
43
|
+
target: PDB ID (e.g., "4Z18") or protein UUID
|
|
44
|
+
pocket: Pocket specification ((center), (size))
|
|
45
|
+
(other parameters same as rowan_docking)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Docking results or clear instructions on what to do
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
# Using PDB ID directly
|
|
52
|
+
rowan_docking_enhanced(
|
|
53
|
+
name="my_docking",
|
|
54
|
+
smiles="CC(=O)Oc1ccccc1C(=O)O", # Aspirin
|
|
55
|
+
target="4Z18", # PDB ID
|
|
56
|
+
pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Using UUID (if you already have one)
|
|
60
|
+
rowan_docking_enhanced(
|
|
61
|
+
name="my_docking",
|
|
62
|
+
smiles="CC(=O)Oc1ccccc1C(=O)O",
|
|
63
|
+
target="abc123de-f456-7890-ghij-klmnopqrstuv", # UUID
|
|
64
|
+
pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0))
|
|
65
|
+
)
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# Validate ligand input
|
|
69
|
+
if smiles is None and molecules is None:
|
|
70
|
+
return "Error: Either 'smiles' or 'molecules' must be provided"
|
|
71
|
+
|
|
72
|
+
# Validate protein input
|
|
73
|
+
if target is None:
|
|
74
|
+
return "Error: 'target' parameter is required (PDB ID or protein UUID)"
|
|
75
|
+
|
|
76
|
+
# Handle the target input
|
|
77
|
+
logger.info(f"Processing target input: {target}")
|
|
78
|
+
pdb_result = handle_pdb_input(target, folder_uuid)
|
|
79
|
+
|
|
80
|
+
# If we successfully got a UUID, proceed with docking
|
|
81
|
+
if pdb_result['success'] and pdb_result['uuid']:
|
|
82
|
+
logger.info(f"Successfully resolved target to UUID: {pdb_result['uuid']}")
|
|
83
|
+
|
|
84
|
+
# Call the main docking function with the UUID
|
|
85
|
+
result = rowan_docking(
|
|
86
|
+
name=name,
|
|
87
|
+
smiles=smiles,
|
|
88
|
+
molecules=molecules,
|
|
89
|
+
target_uuid=pdb_result['uuid'],
|
|
90
|
+
pocket=pocket,
|
|
91
|
+
do_csearch=do_csearch,
|
|
92
|
+
do_optimization=do_optimization,
|
|
93
|
+
do_pose_refinement=do_pose_refinement,
|
|
94
|
+
conformers=conformers,
|
|
95
|
+
folder_uuid=folder_uuid,
|
|
96
|
+
blocking=blocking,
|
|
97
|
+
ping_interval=ping_interval
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Add context about PDB if it was used
|
|
101
|
+
if 'pdb_id' in pdb_result:
|
|
102
|
+
result = f"🧬 Using PDB {pdb_result['pdb_id']}:\n\n{result}"
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
# If we couldn't get a UUID, provide helpful information
|
|
107
|
+
else:
|
|
108
|
+
pdb_id = pdb_result.get('pdb_id', target)
|
|
109
|
+
|
|
110
|
+
# Format a helpful response
|
|
111
|
+
response = f"🔍 PDB Input Detected: {pdb_id}\n\n"
|
|
112
|
+
response += f"❌ {pdb_result['message']}\n\n"
|
|
113
|
+
|
|
114
|
+
if pdb_result.get('instructions'):
|
|
115
|
+
response += pdb_result['instructions']
|
|
116
|
+
else:
|
|
117
|
+
response += f"""
|
|
118
|
+
📋 To dock with PDB {pdb_id}:
|
|
119
|
+
|
|
120
|
+
1. **Download the PDB file**:
|
|
121
|
+
https://files.rcsb.org/download/{pdb_id}.pdb
|
|
122
|
+
|
|
123
|
+
2. **Upload to Rowan**:
|
|
124
|
+
https://labs.rowansci.com
|
|
125
|
+
|
|
126
|
+
3. **Get the protein UUID** from your account
|
|
127
|
+
|
|
128
|
+
4. **Run docking with the UUID**:
|
|
129
|
+
```python
|
|
130
|
+
rowan_docking_enhanced(
|
|
131
|
+
name="{name}",
|
|
132
|
+
smiles={f'"{smiles}"' if isinstance(smiles, str) else smiles},
|
|
133
|
+
target="your-protein-uuid-here",
|
|
134
|
+
pocket={pocket}
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Note: Direct PDB upload through the API is not currently supported.
|
|
139
|
+
Proteins must be uploaded through the web interface.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
def test_enhanced_docking():
|
|
145
|
+
"""Test the enhanced docking function"""
|
|
146
|
+
|
|
147
|
+
print("Testing enhanced docking with PDB ID...")
|
|
148
|
+
|
|
149
|
+
# Test with PDB ID
|
|
150
|
+
result = rowan_docking_enhanced(
|
|
151
|
+
name="test_4z18_docking",
|
|
152
|
+
smiles="CC(=O)Oc1ccccc1C(=O)O", # Aspirin
|
|
153
|
+
target="4Z18", # PDB ID
|
|
154
|
+
pocket=((10.0, 15.0, 20.0), (20.0, 20.0, 20.0)),
|
|
155
|
+
blocking=False
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
print(result)
|
|
159
|
+
|
|
160
|
+
# Test with UUID
|
|
161
|
+
print("\n" + "="*60 + "\n")
|
|
162
|
+
print("Testing with UUID...")
|
|
163
|
+
|
|
164
|
+
result = rowan_docking_enhanced(
|
|
165
|
+
name="test_uuid_docking",
|
|
166
|
+
smiles="CC(=O)Oc1ccccc1C(=O)O",
|
|
167
|
+
target="12345678-1234-5678-1234-567812345678", # Fake UUID format
|
|
168
|
+
blocking=False
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
print(result)
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
test_enhanced_docking()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Electronic Properties Analysis for Rowan MCP Server
|
|
3
|
+
|
|
4
|
+
This module provides electronic structure property calculations including:
|
|
5
|
+
- HOMO/LUMO energies and molecular orbitals
|
|
6
|
+
- Electron density and electrostatic potential
|
|
7
|
+
- Population analysis and bond orders
|
|
8
|
+
- Orbital visualization data
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import logging
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import rowan
|
|
18
|
+
except ImportError:
|
|
19
|
+
rowan = None
|
|
20
|
+
|
|
21
|
+
# Configure logging
|
|
22
|
+
logging.basicConfig(level=logging.INFO)
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
28
|
+
"""Log Rowan API calls with detailed parameters."""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
start_time = time.time()
|
|
32
|
+
|
|
33
|
+
if not rowan:
|
|
34
|
+
raise ImportError("Rowan package not available - please install with 'pip install rowan'")
|
|
35
|
+
|
|
36
|
+
logger.info(f"Calling Rowan {workflow_type} workflow")
|
|
37
|
+
for key, value in kwargs.items():
|
|
38
|
+
if key != 'ping_interval':
|
|
39
|
+
logger.info(f" {key}: {value}")
|
|
40
|
+
|
|
41
|
+
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
42
|
+
|
|
43
|
+
end_time = time.time()
|
|
44
|
+
duration = end_time - start_time
|
|
45
|
+
logger.info(f"Rowan {workflow_type} completed in {duration:.2f} seconds")
|
|
46
|
+
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Rowan {workflow_type} failed: {str(e)}")
|
|
51
|
+
raise e
|
|
52
|
+
|
|
53
|
+
def lookup_molecule_smiles(molecule_name: str) -> str:
|
|
54
|
+
"""Look up canonical SMILES using the advanced molecule_lookup system.
|
|
55
|
+
|
|
56
|
+
Uses PubChemPy + SQLite caching + RDKit validation for scalable molecule lookup.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
# Import the advanced molecule lookup system
|
|
60
|
+
from .molecule_lookup import get_lookup_instance
|
|
61
|
+
|
|
62
|
+
lookup = get_lookup_instance()
|
|
63
|
+
smiles, source, metadata = lookup.get_smiles(molecule_name)
|
|
64
|
+
|
|
65
|
+
if smiles:
|
|
66
|
+
logger.info(f"Molecule lookup successful: '{molecule_name}' → '{smiles}' (source: {source})")
|
|
67
|
+
return smiles
|
|
68
|
+
else:
|
|
69
|
+
logger.warning(f"Molecule lookup failed for '{molecule_name}': {metadata.get('error', 'Unknown error')}")
|
|
70
|
+
# Return original input as fallback (might be valid SMILES)
|
|
71
|
+
return molecule_name
|
|
72
|
+
|
|
73
|
+
except ImportError as e:
|
|
74
|
+
logger.error(f"Could not import molecule_lookup: {e}")
|
|
75
|
+
# Fallback: return original input
|
|
76
|
+
return molecule_name
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Molecule lookup error for '{molecule_name}': {e}")
|
|
79
|
+
# Fallback: return original input
|
|
80
|
+
return molecule_name
|
|
81
|
+
|
|
82
|
+
def rowan_electronic_properties(
|
|
83
|
+
name: str,
|
|
84
|
+
molecule: str,
|
|
85
|
+
# Settings parameters (quantum chemistry calculation settings)
|
|
86
|
+
method: Optional[str] = None,
|
|
87
|
+
basis_set: Optional[str] = None,
|
|
88
|
+
engine: Optional[str] = None,
|
|
89
|
+
charge: int = 0,
|
|
90
|
+
multiplicity: int = 1,
|
|
91
|
+
# Cube computation control parameters
|
|
92
|
+
compute_density_cube: bool = True,
|
|
93
|
+
compute_electrostatic_potential_cube: bool = True,
|
|
94
|
+
compute_num_occupied_orbitals: int = 1,
|
|
95
|
+
compute_num_virtual_orbitals: int = 1,
|
|
96
|
+
# Workflow control parameters
|
|
97
|
+
mode: Optional[str] = None,
|
|
98
|
+
folder_uuid: Optional[str] = None,
|
|
99
|
+
blocking: bool = True,
|
|
100
|
+
ping_interval: int = 5
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Calculate comprehensive electronic structure properties using Rowan's ElectronicPropertiesWorkflow.
|
|
103
|
+
|
|
104
|
+
Implements the ElectronicPropertiesWorkflow class for computing detailed electronic properties including:
|
|
105
|
+
- **Molecular Orbitals**: HOMO/LUMO energies, orbital cubes, occupation numbers
|
|
106
|
+
- **Electron Density**: Total, α/β spin densities, spin density differences
|
|
107
|
+
- **Electrostatic Properties**: Dipole moments, quadrupole moments, electrostatic potential
|
|
108
|
+
- **Population Analysis**: Mulliken charges, Löwdin charges
|
|
109
|
+
- **Bond Analysis**: Wiberg bond orders, Mayer bond orders
|
|
110
|
+
- **Visualization Data**: Cube files for density, ESP, and molecular orbitals
|
|
111
|
+
|
|
112
|
+
**Molecule Lookup**: Uses advanced PubChemPy + SQLite caching + RDKit validation system
|
|
113
|
+
for robust molecule identification and SMILES canonicalization.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Name for the calculation
|
|
117
|
+
molecule: Molecule name (e.g., "aspirin", "taxol") or SMILES string
|
|
118
|
+
method: QM method (default: b3lyp for electronic properties)
|
|
119
|
+
basis_set: Basis set (default: def2-svp for balanced accuracy)
|
|
120
|
+
engine: Computational engine (default: psi4)
|
|
121
|
+
charge: Molecular charge (default: 0)
|
|
122
|
+
multiplicity: Spin multiplicity (default: 1 for singlet)
|
|
123
|
+
compute_density_cube: Generate electron density cube (default: True)
|
|
124
|
+
compute_electrostatic_potential_cube: Generate ESP cube (default: True)
|
|
125
|
+
compute_num_occupied_orbitals: Number of occupied MOs to save (default: 1)
|
|
126
|
+
compute_num_virtual_orbitals: Number of virtual MOs to save (default: 1)
|
|
127
|
+
mode: Calculation mode/precision (optional)
|
|
128
|
+
folder_uuid: Optional folder UUID for organization
|
|
129
|
+
blocking: Whether to wait for completion (default: True)
|
|
130
|
+
ping_interval: Check status interval in seconds (default: 5)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Comprehensive electronic properties results following ElectronicPropertiesWorkflow format
|
|
134
|
+
"""
|
|
135
|
+
# Look up canonical SMILES using advanced molecule lookup (PubChemPy + caching + RDKit)
|
|
136
|
+
canonical_smiles = lookup_molecule_smiles(molecule)
|
|
137
|
+
|
|
138
|
+
# Apply smart defaults for electronic properties calculations
|
|
139
|
+
if method is None:
|
|
140
|
+
method = "b3lyp" # Good for electronic properties
|
|
141
|
+
if basis_set is None:
|
|
142
|
+
basis_set = "def2-svp" # Balanced accuracy/cost for properties
|
|
143
|
+
if engine is None:
|
|
144
|
+
engine = "psi4" # Robust for electronic properties
|
|
145
|
+
|
|
146
|
+
# Validate orbital count parameters
|
|
147
|
+
if compute_num_occupied_orbitals < 0:
|
|
148
|
+
return f"compute_num_occupied_orbitals must be non-negative (got {compute_num_occupied_orbitals})"
|
|
149
|
+
if compute_num_virtual_orbitals < 0:
|
|
150
|
+
return f"compute_num_virtual_orbitals must be non-negative (got {compute_num_virtual_orbitals})"
|
|
151
|
+
|
|
152
|
+
# Build parameters following ElectronicPropertiesWorkflow specification
|
|
153
|
+
electronic_params = {
|
|
154
|
+
"name": name,
|
|
155
|
+
"molecule": canonical_smiles, # API interface requirement
|
|
156
|
+
"initial_molecule": canonical_smiles, # ElectronicPropertiesWorkflow requirement
|
|
157
|
+
# Settings (quantum chemistry parameters) - exactly as in ElectronicPropertiesWorkflow
|
|
158
|
+
"settings": {
|
|
159
|
+
"method": method.lower(),
|
|
160
|
+
"basis_set": basis_set.lower(),
|
|
161
|
+
"engine": engine.lower(),
|
|
162
|
+
"charge": charge,
|
|
163
|
+
"multiplicity": multiplicity
|
|
164
|
+
},
|
|
165
|
+
# Cube computation control - exactly as in ElectronicPropertiesWorkflow
|
|
166
|
+
"compute_density_cube": compute_density_cube,
|
|
167
|
+
"compute_electrostatic_potential_cube": compute_electrostatic_potential_cube,
|
|
168
|
+
"compute_num_occupied_orbitals": compute_num_occupied_orbitals,
|
|
169
|
+
"compute_num_virtual_orbitals": compute_num_virtual_orbitals,
|
|
170
|
+
# Workflow parameters
|
|
171
|
+
"folder_uuid": folder_uuid,
|
|
172
|
+
"blocking": blocking,
|
|
173
|
+
"ping_interval": ping_interval
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Add mode if specified (optional in ElectronicPropertiesWorkflow)
|
|
177
|
+
if mode:
|
|
178
|
+
electronic_params["mode"] = mode
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
result = log_rowan_api_call(
|
|
182
|
+
workflow_type="electronic_properties",
|
|
183
|
+
**electronic_params
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
return f"Electronic properties calculation failed: {str(e)}"
|
|
190
|
+
|
|
191
|
+
def test_electronic_properties():
|
|
192
|
+
"""Test the electronic properties function with advanced molecule lookup."""
|
|
193
|
+
return rowan_electronic_properties(
|
|
194
|
+
name="test_electronic_properties",
|
|
195
|
+
molecule="aspirin", # Test advanced lookup with a pharmaceutical
|
|
196
|
+
method="hf",
|
|
197
|
+
basis_set="sto-3g",
|
|
198
|
+
blocking=True
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
print(test_electronic_properties())
|