rowan-mcp 1.0.1__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 +2 -2
- 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.1.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +49 -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 -137
- 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.1.dist-info/RECORD +0 -34
- {rowan_mcp-1.0.1.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
- {rowan_mcp-1.0.1.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Rowan molecular dynamics function for MCP tool integration.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
-
import rowan
|
|
7
|
-
|
|
8
|
-
def rowan_molecular_dynamics(
|
|
9
|
-
name: str,
|
|
10
|
-
molecule: str,
|
|
11
|
-
ensemble: str = "nvt",
|
|
12
|
-
initialization: str = "random",
|
|
13
|
-
timestep: float = 1.0,
|
|
14
|
-
num_steps: int = 500,
|
|
15
|
-
save_interval: int = 10,
|
|
16
|
-
temperature: float = 300.0,
|
|
17
|
-
pressure: Optional[float] = None,
|
|
18
|
-
langevin_thermostat_timescale: float = 100.0,
|
|
19
|
-
berendsen_barostat_timescale: float = 1000.0,
|
|
20
|
-
constraints: Optional[List[Dict[str, Any]]] = None,
|
|
21
|
-
confining_constraint: Optional[Dict[str, Any]] = None,
|
|
22
|
-
# Calculation settings parameters
|
|
23
|
-
method: Optional[str] = None,
|
|
24
|
-
basis_set: Optional[str] = None,
|
|
25
|
-
engine: Optional[str] = None,
|
|
26
|
-
charge: int = 0,
|
|
27
|
-
multiplicity: int = 1,
|
|
28
|
-
# Workflow control parameters
|
|
29
|
-
folder_uuid: Optional[str] = None,
|
|
30
|
-
blocking: bool = True,
|
|
31
|
-
ping_interval: int = 5
|
|
32
|
-
) -> str:
|
|
33
|
-
"""Run molecular dynamics simulations following Rowan's MolecularDynamicsWorkflow.
|
|
34
|
-
|
|
35
|
-
Performs MD simulations to study molecular dynamics, conformational sampling,
|
|
36
|
-
and thermal properties using various thermodynamic ensembles.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
name: Name for the calculation
|
|
40
|
-
molecule: Molecule SMILES string or common name
|
|
41
|
-
ensemble: Thermodynamic ensemble ("nvt", "npt", "nve")
|
|
42
|
-
initialization: Initial velocities ("random", "quasiclassical", "read")
|
|
43
|
-
timestep: Integration timestep in femtoseconds
|
|
44
|
-
num_steps: Number of MD steps to run
|
|
45
|
-
save_interval: Save trajectory every N steps
|
|
46
|
-
temperature: Temperature in Kelvin
|
|
47
|
-
pressure: Pressure in atm (required for NPT)
|
|
48
|
-
langevin_thermostat_timescale: Thermostat coupling timescale in fs
|
|
49
|
-
berendsen_barostat_timescale: Barostat coupling timescale in fs
|
|
50
|
-
constraints: List of pairwise harmonic constraints
|
|
51
|
-
confining_constraint: Spherical harmonic constraint
|
|
52
|
-
method: QM method for force calculation
|
|
53
|
-
basis_set: Basis set for force calculation
|
|
54
|
-
engine: Computational engine for force calculation
|
|
55
|
-
charge: Molecular charge
|
|
56
|
-
multiplicity: Spin multiplicity
|
|
57
|
-
folder_uuid: Optional folder UUID for organization
|
|
58
|
-
blocking: Whether to wait for completion
|
|
59
|
-
ping_interval: Check status interval in seconds
|
|
60
|
-
|
|
61
|
-
Example:
|
|
62
|
-
result = rowan_molecular_dynamics(
|
|
63
|
-
name="ethanol_md_simulation",
|
|
64
|
-
molecule="ethanol",
|
|
65
|
-
ensemble="NVT",
|
|
66
|
-
temperature=298,
|
|
67
|
-
num_steps=1000,
|
|
68
|
-
blocking=False
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
Molecular dynamics workflow result
|
|
73
|
-
"""
|
|
74
|
-
# Parameter validation
|
|
75
|
-
valid_ensembles = ["nvt", "npt", "nve"]
|
|
76
|
-
valid_initializations = ["random", "quasiclassical", "read"]
|
|
77
|
-
|
|
78
|
-
# Validate ensemble
|
|
79
|
-
ensemble_lower = ensemble.lower()
|
|
80
|
-
if ensemble_lower not in valid_ensembles:
|
|
81
|
-
return f" Error: Invalid ensemble '{ensemble}'. Valid options: {', '.join(valid_ensembles)}"
|
|
82
|
-
|
|
83
|
-
# Validate initialization
|
|
84
|
-
initialization_lower = initialization.lower()
|
|
85
|
-
if initialization_lower not in valid_initializations:
|
|
86
|
-
return f" Error: Invalid initialization '{initialization}'. Valid options: {', '.join(valid_initializations)}"
|
|
87
|
-
|
|
88
|
-
# Validate numeric parameters
|
|
89
|
-
if timestep <= 0:
|
|
90
|
-
return f" Error: timestep must be positive (got {timestep})"
|
|
91
|
-
if num_steps <= 0:
|
|
92
|
-
return f" Error: num_steps must be positive (got {num_steps})"
|
|
93
|
-
if save_interval <= 0:
|
|
94
|
-
return f" Error: save_interval must be positive (got {save_interval})"
|
|
95
|
-
if temperature <= 0:
|
|
96
|
-
return f" Error: temperature must be positive (got {temperature})"
|
|
97
|
-
|
|
98
|
-
# Validate NPT ensemble requirements
|
|
99
|
-
if ensemble_lower == "npt" and pressure is None:
|
|
100
|
-
return f" Error: NPT ensemble requires pressure to be specified"
|
|
101
|
-
if pressure is not None and pressure <= 0:
|
|
102
|
-
return f" Error: pressure must be positive (got {pressure})"
|
|
103
|
-
|
|
104
|
-
# Convert molecule name to SMILES using lookup system
|
|
105
|
-
try:
|
|
106
|
-
from .molecule_lookup import get_lookup_instance
|
|
107
|
-
lookup = get_lookup_instance()
|
|
108
|
-
smiles, source, metadata = lookup.get_smiles(molecule)
|
|
109
|
-
if smiles:
|
|
110
|
-
resolved_smiles = smiles
|
|
111
|
-
else:
|
|
112
|
-
resolved_smiles = molecule # Fallback to original
|
|
113
|
-
except Exception:
|
|
114
|
-
resolved_smiles = molecule # Fallback if lookup fails
|
|
115
|
-
|
|
116
|
-
# Apply smart defaults for MD calculations
|
|
117
|
-
if engine is None:
|
|
118
|
-
engine = "xtb" # Default to xTB for fast MD forces
|
|
119
|
-
if method is None and engine.lower() == "xtb":
|
|
120
|
-
method = "gfn2-xtb" # Default xTB method
|
|
121
|
-
elif method is None and engine.lower() != "xtb":
|
|
122
|
-
method = "b3lyp" # Default DFT method for other engines
|
|
123
|
-
if basis_set is None and engine.lower() != "xtb":
|
|
124
|
-
basis_set = "def2-svp" # Default basis set for non-xTB engines
|
|
125
|
-
|
|
126
|
-
# Build MD settings
|
|
127
|
-
md_settings = {
|
|
128
|
-
"ensemble": ensemble_lower,
|
|
129
|
-
"initialization": initialization_lower,
|
|
130
|
-
"timestep": timestep,
|
|
131
|
-
"num_steps": num_steps,
|
|
132
|
-
"save_interval": save_interval,
|
|
133
|
-
"temperature": temperature,
|
|
134
|
-
"langevin_thermostat_timescale": langevin_thermostat_timescale,
|
|
135
|
-
"berendsen_barostat_timescale": berendsen_barostat_timescale,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# Add optional fields if provided
|
|
139
|
-
if pressure is not None:
|
|
140
|
-
md_settings["pressure"] = pressure
|
|
141
|
-
|
|
142
|
-
if constraints:
|
|
143
|
-
md_settings["constraints"] = constraints
|
|
144
|
-
|
|
145
|
-
if confining_constraint:
|
|
146
|
-
md_settings["confining_constraint"] = confining_constraint
|
|
147
|
-
|
|
148
|
-
# Build calc_settings
|
|
149
|
-
calc_settings = {
|
|
150
|
-
"charge": charge,
|
|
151
|
-
"multiplicity": multiplicity,
|
|
152
|
-
"engine": engine.lower()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
# Add method if specified
|
|
156
|
-
if method:
|
|
157
|
-
calc_settings["method"] = method.lower()
|
|
158
|
-
|
|
159
|
-
# Add basis_set if specified (not needed for xTB)
|
|
160
|
-
if basis_set and engine.lower() != "xtb":
|
|
161
|
-
calc_settings["basis_set"] = basis_set.lower()
|
|
162
|
-
|
|
163
|
-
# Build parameters for Rowan API
|
|
164
|
-
workflow_params = {
|
|
165
|
-
"name": name,
|
|
166
|
-
"molecule": resolved_smiles,
|
|
167
|
-
"workflow_type": "molecular_dynamics",
|
|
168
|
-
"settings": md_settings,
|
|
169
|
-
"calc_settings": calc_settings,
|
|
170
|
-
"folder_uuid": folder_uuid,
|
|
171
|
-
"blocking": blocking,
|
|
172
|
-
"ping_interval": ping_interval
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
# Add calc_engine at top level
|
|
176
|
-
if engine:
|
|
177
|
-
workflow_params["calc_engine"] = engine.lower()
|
|
178
|
-
|
|
179
|
-
try:
|
|
180
|
-
# Submit molecular dynamics calculation to Rowan
|
|
181
|
-
result = rowan.compute(**workflow_params)
|
|
182
|
-
return str(result)
|
|
183
|
-
except Exception as e:
|
|
184
|
-
error_response = {
|
|
185
|
-
"success": False,
|
|
186
|
-
"error": f"Molecular dynamics calculation failed: {str(e)}",
|
|
187
|
-
"name": name,
|
|
188
|
-
"molecule": molecule,
|
|
189
|
-
"resolved_smiles": resolved_smiles
|
|
190
|
-
}
|
|
191
|
-
return str(error_response)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
from urllib.request import urlopen
|
|
2
|
-
from urllib.parse import quote
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def CIRconvert(ids):
|
|
6
|
-
"""
|
|
7
|
-
Convert molecule name/identifier to SMILES using Chemical Identifier Resolver.
|
|
8
|
-
|
|
9
|
-
Args:
|
|
10
|
-
ids (str): Molecule name or identifier (e.g., 'Aspirin', '3-Methylheptane', CAS numbers)
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
str: SMILES string if found, 'Did not work' if failed
|
|
14
|
-
"""
|
|
15
|
-
try:
|
|
16
|
-
url = 'http://cactus.nci.nih.gov/chemical/structure/' + quote(ids) + '/smiles'
|
|
17
|
-
ans = urlopen(url).read().decode('utf8')
|
|
18
|
-
return ans
|
|
19
|
-
except:
|
|
20
|
-
return 'Did not work'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def rowan_molecule_lookup(molecule_name: str) -> str:
|
|
24
|
-
"""
|
|
25
|
-
Convert a molecule name to SMILES using Chemical Identifier Resolver.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
molecule_name (str): Name of the molecule (e.g., 'aspirin', 'benzene')
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
str: SMILES notation, or error message if not found
|
|
32
|
-
"""
|
|
33
|
-
smiles = CIRconvert(molecule_name)
|
|
34
|
-
|
|
35
|
-
if smiles == 'Did not work':
|
|
36
|
-
return f"{molecule_name}: Not found"
|
|
37
|
-
else:
|
|
38
|
-
return smiles.strip() # Remove any trailing newlines
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def batch_convert(identifiers):
|
|
42
|
-
"""
|
|
43
|
-
Convert multiple molecule identifiers to SMILES.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
identifiers (list): List of molecule names/identifiers
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
dict: Dictionary mapping identifiers to SMILES
|
|
50
|
-
"""
|
|
51
|
-
results = {}
|
|
52
|
-
|
|
53
|
-
for ids in identifiers:
|
|
54
|
-
results[ids] = CIRconvert(ids)
|
|
55
|
-
|
|
56
|
-
return results
|
|
57
|
-
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Rowan multistage optimization function for geometry optimization.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import logging
|
|
7
|
-
import time
|
|
8
|
-
from typing import Optional, List, Dict, Any
|
|
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 log_rowan_api_call(workflow_type: str, **kwargs):
|
|
24
|
-
"""Log Rowan API calls with detailed parameters."""
|
|
25
|
-
|
|
26
|
-
# Special handling for long-running calculations
|
|
27
|
-
if workflow_type in ["multistage_opt", "conformer_search"]:
|
|
28
|
-
ping_interval = kwargs.get('ping_interval', 5)
|
|
29
|
-
blocking = kwargs.get('blocking', True)
|
|
30
|
-
if blocking:
|
|
31
|
-
if workflow_type == "multistage_opt":
|
|
32
|
-
logger.info(f" Multi-stage optimization may take several minutes...")
|
|
33
|
-
else:
|
|
34
|
-
logger.info(f" Conformer search may take several minutes...")
|
|
35
|
-
logger.info(f" Progress will be checked every {ping_interval} seconds")
|
|
36
|
-
else:
|
|
37
|
-
logger.info(f" {workflow_type.replace('_', ' ').title()} submitted without waiting")
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
start_time = time.time()
|
|
41
|
-
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
42
|
-
api_time = time.time() - start_time
|
|
43
|
-
|
|
44
|
-
if isinstance(result, dict) and 'uuid' in result:
|
|
45
|
-
job_status = result.get('status', result.get('object_status', 'Unknown'))
|
|
46
|
-
status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
|
|
47
|
-
status_text = status_names.get(job_status, f"Unknown ({job_status})")
|
|
48
|
-
|
|
49
|
-
return result
|
|
50
|
-
|
|
51
|
-
except Exception as e:
|
|
52
|
-
api_time = time.time() - start_time
|
|
53
|
-
raise e
|
|
54
|
-
|
|
55
|
-
def rowan_multistage_opt(
|
|
56
|
-
name: str,
|
|
57
|
-
molecule: str,
|
|
58
|
-
mode: str = "rapid",
|
|
59
|
-
solvent: Optional[str] = None,
|
|
60
|
-
xtb_preopt: bool = False,
|
|
61
|
-
constraints: Optional[List[Dict[str, Any]]] = None,
|
|
62
|
-
transition_state: bool = False,
|
|
63
|
-
frequencies: bool = False,
|
|
64
|
-
folder_uuid: Optional[str] = None,
|
|
65
|
-
blocking: bool = True,
|
|
66
|
-
ping_interval: int = 30
|
|
67
|
-
) -> str:
|
|
68
|
-
"""Run multi-level geometry optimization using stjames MultiStageOptWorkflow.
|
|
69
|
-
|
|
70
|
-
Performs hierarchical optimization using multiple levels of theory based on mode. There are only four valid modes:
|
|
71
|
-
|
|
72
|
-
**RAPID** (default): GFN2-xTB optimization → r²SCAN-3c single point
|
|
73
|
-
**RECKLESS**: GFN-FF optimization → GFN2-xTB single point
|
|
74
|
-
**CAREFUL**: GFN2-xTB preopt → r²SCAN-3c optimization → ωB97X-3c single point
|
|
75
|
-
**METICULOUS**: GFN2-xTB preopt → r²SCAN-3c opt → ωB97X-3c opt → ωB97M-D3BJ/def2-TZVPPD single point
|
|
76
|
-
|
|
77
|
-
This follows the exact stjames MultiStageOptWorkflow implementation for:
|
|
78
|
-
- High accuracy final structures
|
|
79
|
-
- Efficient computational cost
|
|
80
|
-
- Reliable convergence across chemical space
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
name: Name for the calculation
|
|
84
|
-
molecule: Molecule SMILES string
|
|
85
|
-
mode: Optimization mode - "rapid", "reckless", "careful", or "meticulous" (default: "rapid")
|
|
86
|
-
solvent: Solvent for single point calculation (e.g., "water", "dmso", "acetone")
|
|
87
|
-
xtb_preopt: Whether to include xTB pre-optimization step (default: False)
|
|
88
|
-
constraints: List of optimization constraints (default: None)
|
|
89
|
-
transition_state: Whether this is a transition state optimization (default: False)
|
|
90
|
-
frequencies: Whether to calculate vibrational frequencies (default: False)
|
|
91
|
-
folder_uuid: Optional folder UUID for organization
|
|
92
|
-
blocking: Whether to wait for completion (default: True)
|
|
93
|
-
ping_interval: Check status interval in seconds (default: 30)
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
Comprehensive optimization results following MultiStageOptWorkflow format
|
|
97
|
-
|
|
98
|
-
Example:
|
|
99
|
-
# Basic optimization
|
|
100
|
-
result = rowan_multistage_opt("aspirin_opt", "CC(=O)Oc1ccccc1C(=O)O")
|
|
101
|
-
|
|
102
|
-
# With solvent and frequency analysis
|
|
103
|
-
result = rowan_multistage_opt(
|
|
104
|
-
"aspirin_water",
|
|
105
|
-
"CC(=O)Oc1ccccc1C(=O)O",
|
|
106
|
-
mode="careful",
|
|
107
|
-
solvent="water",
|
|
108
|
-
frequencies=True
|
|
109
|
-
)
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
# Validate mode parameter - only allow Rowan's supported modes
|
|
113
|
-
valid_modes = ["rapid", "reckless", "careful", "meticulous"]
|
|
114
|
-
if mode.lower() not in valid_modes:
|
|
115
|
-
error_msg = f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}"
|
|
116
|
-
logger.error(f"Mode validation failed: {error_msg}")
|
|
117
|
-
return f"Error: {error_msg}"
|
|
118
|
-
|
|
119
|
-
# Prepare parameters following stjames MultiStageOptWorkflow structure
|
|
120
|
-
params = {
|
|
121
|
-
"name": name,
|
|
122
|
-
"molecule": molecule,
|
|
123
|
-
"initial_molecule": molecule, # Required by MultiStageOptWorkflow
|
|
124
|
-
"mode": mode.lower(),
|
|
125
|
-
"folder_uuid": folder_uuid,
|
|
126
|
-
"blocking": blocking,
|
|
127
|
-
"ping_interval": ping_interval
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
# Add optional parameters if specified
|
|
131
|
-
if solvent:
|
|
132
|
-
params["solvent"] = solvent
|
|
133
|
-
|
|
134
|
-
if xtb_preopt:
|
|
135
|
-
params["xtb_preopt"] = xtb_preopt
|
|
136
|
-
|
|
137
|
-
if constraints:
|
|
138
|
-
params["constraints"] = constraints
|
|
139
|
-
|
|
140
|
-
if transition_state:
|
|
141
|
-
params["transition_state"] = transition_state
|
|
142
|
-
|
|
143
|
-
if frequencies:
|
|
144
|
-
params["frequencies"] = frequencies
|
|
145
|
-
|
|
146
|
-
# Submit to Rowan using multistage_opt workflow
|
|
147
|
-
result = log_rowan_api_call(
|
|
148
|
-
workflow_type="multistage_opt",
|
|
149
|
-
**params
|
|
150
|
-
)
|
|
151
|
-
return str(result)
|
|
152
|
-
|
|
153
|
-
def test_rowan_multistage_opt():
|
|
154
|
-
"""Test the rowan_multistage_opt function with stjames parameters."""
|
|
155
|
-
try:
|
|
156
|
-
# Test with stjames-compatible parameters
|
|
157
|
-
result = rowan_multistage_opt(
|
|
158
|
-
name="test_stjames_opt",
|
|
159
|
-
molecule="CCO",
|
|
160
|
-
mode="rapid",
|
|
161
|
-
blocking=False
|
|
162
|
-
)
|
|
163
|
-
print("✅ Multistage optimization test successful!")
|
|
164
|
-
print(f"Result length: {len(result)} characters")
|
|
165
|
-
return True
|
|
166
|
-
except Exception as e:
|
|
167
|
-
print(f"Multistage optimization test failed: {e}")
|
|
168
|
-
return False
|
|
169
|
-
|
|
170
|
-
if __name__ == "__main__":
|
|
171
|
-
test_rowan_multistage_opt()
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
PDB handler for Rowan docking - attempts to handle PDB input intelligently
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import logging
|
|
7
|
-
import requests
|
|
8
|
-
from typing import Optional, Dict, Any, Tuple
|
|
9
|
-
import rowan
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
def parse_pdb_content(pdb_content: str) -> Dict[str, Any]:
|
|
14
|
-
"""
|
|
15
|
-
Parse PDB content into a structured format that might work with Rowan.
|
|
16
|
-
|
|
17
|
-
This attempts to extract key information from PDB files that the
|
|
18
|
-
Rowan API might accept.
|
|
19
|
-
"""
|
|
20
|
-
lines = pdb_content.split('\n')
|
|
21
|
-
|
|
22
|
-
# Extract basic information
|
|
23
|
-
header = ""
|
|
24
|
-
title = ""
|
|
25
|
-
atoms = []
|
|
26
|
-
|
|
27
|
-
for line in lines:
|
|
28
|
-
if line.startswith("HEADER"):
|
|
29
|
-
header = line[10:50].strip()
|
|
30
|
-
elif line.startswith("TITLE"):
|
|
31
|
-
title += line[10:].strip() + " "
|
|
32
|
-
elif line.startswith("ATOM") or line.startswith("HETATM"):
|
|
33
|
-
atoms.append(line)
|
|
34
|
-
|
|
35
|
-
# Try to create a structure that might work with Rowan's PDB format
|
|
36
|
-
pdb_data = {
|
|
37
|
-
"description": {
|
|
38
|
-
"title": title.strip() or header,
|
|
39
|
-
"classification": header
|
|
40
|
-
},
|
|
41
|
-
"experiment": {
|
|
42
|
-
"technique": "X-RAY DIFFRACTION" # Default assumption
|
|
43
|
-
},
|
|
44
|
-
"geometry": {},
|
|
45
|
-
"contents": pdb_content, # Keep full content
|
|
46
|
-
"name": f"pdb_upload_{len(pdb_content)}"
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return pdb_data
|
|
50
|
-
|
|
51
|
-
def attempt_pdb_upload(pdb_id: str, pdb_content: str, folder_uuid: Optional[str] = None) -> Tuple[Optional[str], str]:
|
|
52
|
-
"""
|
|
53
|
-
Attempt to upload PDB content to Rowan.
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
Tuple of (uuid or None, status message)
|
|
57
|
-
"""
|
|
58
|
-
try:
|
|
59
|
-
# First, try the Protein.create method if it exists
|
|
60
|
-
if hasattr(rowan, 'Protein') and hasattr(rowan.Protein, 'create'):
|
|
61
|
-
logger.info(f"Attempting to create protein from PDB {pdb_id}")
|
|
62
|
-
result = rowan.Protein.create(
|
|
63
|
-
name=f"{pdb_id}_upload",
|
|
64
|
-
pdb_content=pdb_content,
|
|
65
|
-
folder_uuid=folder_uuid
|
|
66
|
-
)
|
|
67
|
-
if result and 'uuid' in result:
|
|
68
|
-
return result['uuid'], f"Successfully uploaded PDB {pdb_id}"
|
|
69
|
-
|
|
70
|
-
# Try alternative upload methods
|
|
71
|
-
if hasattr(rowan, 'upload_pdb'):
|
|
72
|
-
result = rowan.upload_pdb(pdb_content, name=pdb_id)
|
|
73
|
-
if result and 'uuid' in result:
|
|
74
|
-
return result['uuid'], f"Successfully uploaded PDB {pdb_id}"
|
|
75
|
-
|
|
76
|
-
# Try to create a file upload
|
|
77
|
-
if hasattr(rowan, 'File') and hasattr(rowan.File, 'create'):
|
|
78
|
-
result = rowan.File.create(
|
|
79
|
-
name=f"{pdb_id}.pdb",
|
|
80
|
-
content=pdb_content,
|
|
81
|
-
file_type="pdb",
|
|
82
|
-
folder_uuid=folder_uuid
|
|
83
|
-
)
|
|
84
|
-
if result and 'uuid' in result:
|
|
85
|
-
return result['uuid'], f"Uploaded PDB {pdb_id} as file"
|
|
86
|
-
|
|
87
|
-
except Exception as e:
|
|
88
|
-
logger.error(f"Failed to upload PDB {pdb_id}: {str(e)}")
|
|
89
|
-
|
|
90
|
-
return None, f"Cannot upload PDB through API - manual upload required"
|
|
91
|
-
|
|
92
|
-
def handle_pdb_input(pdb_input: str, folder_uuid: Optional[str] = None) -> Dict[str, Any]:
|
|
93
|
-
"""
|
|
94
|
-
Handle PDB input (either PDB ID or UUID) and return information for docking.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
pdb_input: Either a PDB ID (like "4Z18") or a UUID
|
|
98
|
-
folder_uuid: Optional folder for uploads
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Dictionary with:
|
|
102
|
-
- success: bool
|
|
103
|
-
- uuid: str (if successful)
|
|
104
|
-
- message: str (status/error message)
|
|
105
|
-
- pdb_id: str (if it was a PDB ID)
|
|
106
|
-
- instructions: str (what to do next)
|
|
107
|
-
"""
|
|
108
|
-
# Check if it's a UUID (36 chars with dashes)
|
|
109
|
-
if len(pdb_input) == 36 and pdb_input.count('-') == 4:
|
|
110
|
-
return {
|
|
111
|
-
"success": True,
|
|
112
|
-
"uuid": pdb_input,
|
|
113
|
-
"message": "Using provided protein UUID",
|
|
114
|
-
"instructions": None
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# Assume it's a PDB ID
|
|
118
|
-
pdb_id = pdb_input.upper()
|
|
119
|
-
|
|
120
|
-
# Fetch PDB content
|
|
121
|
-
try:
|
|
122
|
-
url = f"https://files.rcsb.org/download/{pdb_id}.pdb"
|
|
123
|
-
response = requests.get(url, timeout=30)
|
|
124
|
-
response.raise_for_status()
|
|
125
|
-
pdb_content = response.text
|
|
126
|
-
logger.info(f"Successfully fetched PDB {pdb_id} ({len(pdb_content)} characters)")
|
|
127
|
-
except Exception as e:
|
|
128
|
-
return {
|
|
129
|
-
"success": False,
|
|
130
|
-
"uuid": None,
|
|
131
|
-
"message": f"Failed to fetch PDB {pdb_id}: {str(e)}",
|
|
132
|
-
"pdb_id": pdb_id,
|
|
133
|
-
"instructions": "Please verify the PDB ID is correct"
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
# Attempt to upload
|
|
137
|
-
uuid, upload_status = attempt_pdb_upload(pdb_id, pdb_content, folder_uuid)
|
|
138
|
-
|
|
139
|
-
if uuid:
|
|
140
|
-
return {
|
|
141
|
-
"success": True,
|
|
142
|
-
"uuid": uuid,
|
|
143
|
-
"message": upload_status,
|
|
144
|
-
"pdb_id": pdb_id,
|
|
145
|
-
"instructions": None
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
# Upload failed - provide instructions
|
|
149
|
-
instructions = f"""
|
|
150
|
-
To use PDB {pdb_id} for docking:
|
|
151
|
-
|
|
152
|
-
1. Download the PDB file:
|
|
153
|
-
https://files.rcsb.org/download/{pdb_id}.pdb
|
|
154
|
-
|
|
155
|
-
2. Upload to your Rowan account:
|
|
156
|
-
https://labs.rowansci.com
|
|
157
|
-
|
|
158
|
-
3. Get the protein UUID from your account
|
|
159
|
-
|
|
160
|
-
4. Use the UUID in your docking call:
|
|
161
|
-
rowan_docking(
|
|
162
|
-
target_uuid="your-protein-uuid-here",
|
|
163
|
-
...
|
|
164
|
-
)
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
"success": False,
|
|
169
|
-
"uuid": None,
|
|
170
|
-
"message": upload_status,
|
|
171
|
-
"pdb_id": pdb_id,
|
|
172
|
-
"instructions": instructions
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
def enhance_docking_with_pdb_handler(
|
|
176
|
-
target_input: Optional[str] = None,
|
|
177
|
-
target_uuid: Optional[str] = None,
|
|
178
|
-
**kwargs
|
|
179
|
-
) -> Tuple[Optional[str], str]:
|
|
180
|
-
"""
|
|
181
|
-
Enhanced target handling for docking that accepts PDB IDs.
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
Tuple of (target_uuid or None, message)
|
|
185
|
-
"""
|
|
186
|
-
# If UUID already provided, use it
|
|
187
|
-
if target_uuid:
|
|
188
|
-
return target_uuid, "Using provided target UUID"
|
|
189
|
-
|
|
190
|
-
# If target_input provided, try to handle it
|
|
191
|
-
if target_input:
|
|
192
|
-
result = handle_pdb_input(target_input, kwargs.get('folder_uuid'))
|
|
193
|
-
|
|
194
|
-
if result['success']:
|
|
195
|
-
return result['uuid'], result['message']
|
|
196
|
-
else:
|
|
197
|
-
# Return None but with helpful message
|
|
198
|
-
return None, result['instructions'] or result['message']
|
|
199
|
-
|
|
200
|
-
return None, "No protein target specified"
|