rowan-mcp 1.0.2__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 +1 -1
- 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.2.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +41 -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 -88
- 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.2.dist-info/RECORD +0 -34
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
- {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,205 +0,0 @@
|
|
|
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
|
-
# Setup API key
|
|
26
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
27
|
-
if api_key and rowan:
|
|
28
|
-
rowan.api_key = api_key
|
|
29
|
-
|
|
30
|
-
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
31
|
-
"""Log Rowan API calls with detailed parameters."""
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
start_time = time.time()
|
|
35
|
-
|
|
36
|
-
if not rowan:
|
|
37
|
-
raise ImportError("Rowan package not available - please install with 'pip install rowan'")
|
|
38
|
-
|
|
39
|
-
logger.info(f"Calling Rowan {workflow_type} workflow")
|
|
40
|
-
for key, value in kwargs.items():
|
|
41
|
-
if key != 'ping_interval':
|
|
42
|
-
logger.info(f" {key}: {value}")
|
|
43
|
-
|
|
44
|
-
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
45
|
-
|
|
46
|
-
end_time = time.time()
|
|
47
|
-
duration = end_time - start_time
|
|
48
|
-
logger.info(f"Rowan {workflow_type} completed in {duration:.2f} seconds")
|
|
49
|
-
|
|
50
|
-
return result
|
|
51
|
-
|
|
52
|
-
except Exception as e:
|
|
53
|
-
logger.error(f"Rowan {workflow_type} failed: {str(e)}")
|
|
54
|
-
raise e
|
|
55
|
-
|
|
56
|
-
def lookup_molecule_smiles(molecule_name: str) -> str:
|
|
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
|
|
64
|
-
|
|
65
|
-
lookup = get_lookup_instance()
|
|
66
|
-
smiles, source, metadata = lookup.get_smiles(molecule_name)
|
|
67
|
-
|
|
68
|
-
if smiles:
|
|
69
|
-
logger.info(f"Molecule lookup successful: '{molecule_name}' → '{smiles}' (source: {source})")
|
|
70
|
-
return smiles
|
|
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
|
|
84
|
-
|
|
85
|
-
def rowan_electronic_properties(
|
|
86
|
-
name: str,
|
|
87
|
-
molecule: str,
|
|
88
|
-
# Settings parameters (quantum chemistry calculation settings)
|
|
89
|
-
method: Optional[str] = None,
|
|
90
|
-
basis_set: Optional[str] = None,
|
|
91
|
-
engine: Optional[str] = None,
|
|
92
|
-
charge: int = 0,
|
|
93
|
-
multiplicity: int = 1,
|
|
94
|
-
# Cube computation control parameters
|
|
95
|
-
compute_density_cube: bool = True,
|
|
96
|
-
compute_electrostatic_potential_cube: bool = True,
|
|
97
|
-
compute_num_occupied_orbitals: int = 1,
|
|
98
|
-
compute_num_virtual_orbitals: int = 1,
|
|
99
|
-
# Workflow control parameters
|
|
100
|
-
mode: Optional[str] = None,
|
|
101
|
-
folder_uuid: Optional[str] = None,
|
|
102
|
-
blocking: bool = True,
|
|
103
|
-
ping_interval: int = 5
|
|
104
|
-
) -> str:
|
|
105
|
-
"""Calculate comprehensive electronic structure properties using Rowan's ElectronicPropertiesWorkflow.
|
|
106
|
-
|
|
107
|
-
Implements the ElectronicPropertiesWorkflow class for computing detailed electronic properties including:
|
|
108
|
-
- **Molecular Orbitals**: HOMO/LUMO energies, orbital cubes, occupation numbers
|
|
109
|
-
- **Electron Density**: Total, α/β spin densities, spin density differences
|
|
110
|
-
- **Electrostatic Properties**: Dipole moments, quadrupole moments, electrostatic potential
|
|
111
|
-
- **Population Analysis**: Mulliken charges, Löwdin charges
|
|
112
|
-
- **Bond Analysis**: Wiberg bond orders, Mayer bond orders
|
|
113
|
-
- **Visualization Data**: Cube files for density, ESP, and molecular orbitals
|
|
114
|
-
|
|
115
|
-
**Molecule Lookup**: Uses advanced PubChemPy + SQLite caching + RDKit validation system
|
|
116
|
-
for robust molecule identification and SMILES canonicalization.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
name: Name for the calculation
|
|
120
|
-
molecule: Molecule name (e.g., "aspirin", "taxol") or SMILES string
|
|
121
|
-
method: QM method (default: b3lyp for electronic properties)
|
|
122
|
-
basis_set: Basis set (default: def2-svp for balanced accuracy)
|
|
123
|
-
engine: Computational engine (default: psi4)
|
|
124
|
-
charge: Molecular charge (default: 0)
|
|
125
|
-
multiplicity: Spin multiplicity (default: 1 for singlet)
|
|
126
|
-
compute_density_cube: Generate electron density cube (default: True)
|
|
127
|
-
compute_electrostatic_potential_cube: Generate ESP cube (default: True)
|
|
128
|
-
compute_num_occupied_orbitals: Number of occupied MOs to save (default: 1)
|
|
129
|
-
compute_num_virtual_orbitals: Number of virtual MOs to save (default: 1)
|
|
130
|
-
mode: Calculation mode/precision (optional)
|
|
131
|
-
folder_uuid: Optional folder UUID for organization
|
|
132
|
-
blocking: Whether to wait for completion (default: True)
|
|
133
|
-
ping_interval: Check status interval in seconds (default: 5)
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
Comprehensive electronic properties results following ElectronicPropertiesWorkflow format
|
|
137
|
-
"""
|
|
138
|
-
# Look up canonical SMILES using advanced molecule lookup (PubChemPy + caching + RDKit)
|
|
139
|
-
canonical_smiles = lookup_molecule_smiles(molecule)
|
|
140
|
-
|
|
141
|
-
# Apply smart defaults for electronic properties calculations
|
|
142
|
-
if method is None:
|
|
143
|
-
method = "b3lyp" # Good for electronic properties
|
|
144
|
-
if basis_set is None:
|
|
145
|
-
basis_set = "def2-svp" # Balanced accuracy/cost for properties
|
|
146
|
-
if engine is None:
|
|
147
|
-
engine = "psi4" # Robust for electronic properties
|
|
148
|
-
|
|
149
|
-
# Validate orbital count parameters
|
|
150
|
-
if compute_num_occupied_orbitals < 0:
|
|
151
|
-
return f"compute_num_occupied_orbitals must be non-negative (got {compute_num_occupied_orbitals})"
|
|
152
|
-
if compute_num_virtual_orbitals < 0:
|
|
153
|
-
return f"compute_num_virtual_orbitals must be non-negative (got {compute_num_virtual_orbitals})"
|
|
154
|
-
|
|
155
|
-
# Build parameters following ElectronicPropertiesWorkflow specification
|
|
156
|
-
electronic_params = {
|
|
157
|
-
"name": name,
|
|
158
|
-
"molecule": canonical_smiles, # API interface requirement
|
|
159
|
-
"initial_molecule": canonical_smiles, # ElectronicPropertiesWorkflow requirement
|
|
160
|
-
# Settings (quantum chemistry parameters) - exactly as in ElectronicPropertiesWorkflow
|
|
161
|
-
"settings": {
|
|
162
|
-
"method": method.lower(),
|
|
163
|
-
"basis_set": basis_set.lower(),
|
|
164
|
-
"engine": engine.lower(),
|
|
165
|
-
"charge": charge,
|
|
166
|
-
"multiplicity": multiplicity
|
|
167
|
-
},
|
|
168
|
-
# Cube computation control - exactly as in ElectronicPropertiesWorkflow
|
|
169
|
-
"compute_density_cube": compute_density_cube,
|
|
170
|
-
"compute_electrostatic_potential_cube": compute_electrostatic_potential_cube,
|
|
171
|
-
"compute_num_occupied_orbitals": compute_num_occupied_orbitals,
|
|
172
|
-
"compute_num_virtual_orbitals": compute_num_virtual_orbitals,
|
|
173
|
-
# Workflow parameters
|
|
174
|
-
"folder_uuid": folder_uuid,
|
|
175
|
-
"blocking": blocking,
|
|
176
|
-
"ping_interval": ping_interval
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
# Add mode if specified (optional in ElectronicPropertiesWorkflow)
|
|
180
|
-
if mode:
|
|
181
|
-
electronic_params["mode"] = mode
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
result = log_rowan_api_call(
|
|
185
|
-
workflow_type="electronic_properties",
|
|
186
|
-
**electronic_params
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
return result
|
|
190
|
-
|
|
191
|
-
except Exception as e:
|
|
192
|
-
return f"Electronic properties calculation failed: {str(e)}"
|
|
193
|
-
|
|
194
|
-
def test_electronic_properties():
|
|
195
|
-
"""Test the electronic properties function with advanced molecule lookup."""
|
|
196
|
-
return rowan_electronic_properties(
|
|
197
|
-
name="test_electronic_properties",
|
|
198
|
-
molecule="aspirin", # Test advanced lookup with a pharmaceutical
|
|
199
|
-
method="hf",
|
|
200
|
-
basis_set="sto-3g",
|
|
201
|
-
blocking=True
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
if __name__ == "__main__":
|
|
205
|
-
print(test_electronic_properties())
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Folder management operations for Rowan API.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import rowan
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
# Set up logging
|
|
10
|
-
import logging
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
# Configure rowan API key
|
|
14
|
-
if not hasattr(rowan, 'api_key') or not rowan.api_key:
|
|
15
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
16
|
-
if api_key:
|
|
17
|
-
rowan.api_key = api_key
|
|
18
|
-
logger.info("Rowan API key configured")
|
|
19
|
-
else:
|
|
20
|
-
logger.error("No ROWAN_API_KEY found in environment")
|
|
21
|
-
|
|
22
|
-
def rowan_folder_management(
|
|
23
|
-
action: str,
|
|
24
|
-
folder_uuid: Optional[str] = None,
|
|
25
|
-
name: Optional[str] = None,
|
|
26
|
-
parent_uuid: Optional[str] = None,
|
|
27
|
-
notes: Optional[str] = None,
|
|
28
|
-
starred: Optional[bool] = None,
|
|
29
|
-
public: Optional[bool] = None,
|
|
30
|
-
created_at: Optional[str] = None,
|
|
31
|
-
updated_at: Optional[str] = None,
|
|
32
|
-
is_root: Optional[bool] = None,
|
|
33
|
-
uuid: Optional[str] = None,
|
|
34
|
-
name_contains: Optional[str] = None,
|
|
35
|
-
page: int = 0,
|
|
36
|
-
size: int = 50
|
|
37
|
-
) -> str:
|
|
38
|
-
"""Unified folder management tool for all folder operations. Available actions: create, retrieve, update, delete, list.
|
|
39
|
-
|
|
40
|
-
**Available Actions:**
|
|
41
|
-
- **create**: Create a new folder (requires: name, optional: parent_uuid, notes, starred, public)
|
|
42
|
-
- **retrieve**: Get folder details (requires: folder_uuid)
|
|
43
|
-
- **update**: Update folder properties (requires: folder_uuid, optional: name, parent_uuid, notes, starred, public)
|
|
44
|
-
- **delete**: Delete a folder (requires: folder_uuid)
|
|
45
|
-
- **list**: List folders with filters (optional: name_contains, parent_uuid, starred, public, page, size)
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
action: Action to perform ('create', 'retrieve', 'update', 'delete', 'list')
|
|
49
|
-
folder_uuid: UUID of the folder (required for retrieve, update, delete)
|
|
50
|
-
name: Folder name (required for create, optional for update)
|
|
51
|
-
parent_uuid: Parent folder UUID (optional for create/update, if not provided creates in root)
|
|
52
|
-
notes: Folder notes (optional for create/update)
|
|
53
|
-
starred: Star the folder (optional for create/update)
|
|
54
|
-
public: Make folder public (optional for create/update)
|
|
55
|
-
created_at: The date and time at which this folder was created
|
|
56
|
-
updated_at: The date and time at which this folder was most recently updated
|
|
57
|
-
is_root: Whether or not this folder is the user's root folder
|
|
58
|
-
uuid: The UUID of this folder
|
|
59
|
-
name_contains: Filter by name containing text (optional for list)
|
|
60
|
-
page: Page number for pagination (default: 1, for list)
|
|
61
|
-
size: Results per page (default: 50, for list)
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
Results of the folder operation
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
action = action.lower()
|
|
68
|
-
|
|
69
|
-
try:
|
|
70
|
-
if action == "create":
|
|
71
|
-
if not name:
|
|
72
|
-
return "Error: 'name' is required for creating a folder"
|
|
73
|
-
|
|
74
|
-
return rowan.Folder.create(
|
|
75
|
-
name=name,
|
|
76
|
-
parent_uuid=parent_uuid,
|
|
77
|
-
notes=notes or "",
|
|
78
|
-
starred=starred or False,
|
|
79
|
-
public=public or False
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
elif action == "retrieve":
|
|
83
|
-
if not folder_uuid:
|
|
84
|
-
return "Error: 'folder_uuid' is required for retrieving a folder"
|
|
85
|
-
|
|
86
|
-
return rowan.Folder.retrieve(uuid=folder_uuid)
|
|
87
|
-
|
|
88
|
-
elif action == "update":
|
|
89
|
-
if not folder_uuid:
|
|
90
|
-
return "Error: 'folder_uuid' is required for updating a folder"
|
|
91
|
-
|
|
92
|
-
return rowan.Folder.update(
|
|
93
|
-
uuid=folder_uuid,
|
|
94
|
-
name=name,
|
|
95
|
-
notes=notes,
|
|
96
|
-
starred=starred,
|
|
97
|
-
public=public,
|
|
98
|
-
parent_uuid=parent_uuid
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
elif action == "delete":
|
|
103
|
-
if not folder_uuid:
|
|
104
|
-
return "Error: 'folder_uuid' is required for deleting a folder"
|
|
105
|
-
|
|
106
|
-
rowan.Folder.delete(uuid=folder_uuid)
|
|
107
|
-
return "Folder deleted successfully"
|
|
108
|
-
|
|
109
|
-
elif action == "list":
|
|
110
|
-
return rowan.Folder.list(
|
|
111
|
-
name_contains=name_contains,
|
|
112
|
-
parent_uuid=parent_uuid,
|
|
113
|
-
starred=starred,
|
|
114
|
-
public=public,
|
|
115
|
-
page=page,
|
|
116
|
-
size=size
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
except Exception as e:
|
|
120
|
-
return f"Error in folder {action}: {str(e)}"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def test_rowan_folder_management():
|
|
124
|
-
"""Test the rowan_folder_management function."""
|
|
125
|
-
try:
|
|
126
|
-
# Test listing folders
|
|
127
|
-
result = rowan_folder_management(action="list", size=5)
|
|
128
|
-
print("Folder management test successful!")
|
|
129
|
-
print(f"Result: {result[:200]}...") # Show first 200 chars
|
|
130
|
-
return True
|
|
131
|
-
except Exception as e:
|
|
132
|
-
print(f"Folder management test failed: {e}")
|
|
133
|
-
return False
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if __name__ == "__main__":
|
|
137
|
-
test_rowan_folder_management()
|
rowan_mcp/functions/fukui.py
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Fukui Analysis for Rowan MCP Server
|
|
3
|
-
|
|
4
|
-
This module provides Fukui indices calculations for reactivity prediction including:
|
|
5
|
-
- f(+) indices for electrophilic attack sites
|
|
6
|
-
- f(-) indices for nucleophilic attack sites
|
|
7
|
-
- f(0) indices for radical attack sites
|
|
8
|
-
- Global electrophilicity index
|
|
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
|
-
# Setup API key
|
|
26
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
27
|
-
if api_key and rowan:
|
|
28
|
-
rowan.api_key = api_key
|
|
29
|
-
|
|
30
|
-
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
31
|
-
"""Log Rowan API calls with detailed parameters."""
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
start_time = time.time()
|
|
35
|
-
|
|
36
|
-
if not rowan:
|
|
37
|
-
raise ImportError("Rowan package not available - please install with 'pip install rowan'")
|
|
38
|
-
|
|
39
|
-
logger.info(f"Calling Rowan {workflow_type} workflow")
|
|
40
|
-
for key, value in kwargs.items():
|
|
41
|
-
if key != 'ping_interval':
|
|
42
|
-
logger.info(f" {key}: {value}")
|
|
43
|
-
|
|
44
|
-
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
45
|
-
|
|
46
|
-
end_time = time.time()
|
|
47
|
-
duration = end_time - start_time
|
|
48
|
-
logger.info(f"Rowan {workflow_type} completed in {duration:.2f} seconds")
|
|
49
|
-
|
|
50
|
-
return result
|
|
51
|
-
|
|
52
|
-
except Exception as e:
|
|
53
|
-
logger.error(f"Rowan {workflow_type} failed: {str(e)}")
|
|
54
|
-
raise e
|
|
55
|
-
|
|
56
|
-
def lookup_molecule_smiles(molecule_name: str) -> str:
|
|
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
|
|
64
|
-
|
|
65
|
-
lookup = get_lookup_instance()
|
|
66
|
-
smiles, source, metadata = lookup.get_smiles(molecule_name)
|
|
67
|
-
|
|
68
|
-
if smiles:
|
|
69
|
-
logger.info(f"Molecule lookup successful: '{molecule_name}' → '{smiles}' (source: {source})")
|
|
70
|
-
return smiles
|
|
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
|
|
84
|
-
|
|
85
|
-
def rowan_fukui(
|
|
86
|
-
name: str,
|
|
87
|
-
molecule: str,
|
|
88
|
-
optimize: bool = True,
|
|
89
|
-
opt_method: Optional[str] = None,
|
|
90
|
-
opt_basis_set: Optional[str] = None,
|
|
91
|
-
opt_engine: Optional[str] = None,
|
|
92
|
-
fukui_method: str = "gfn1_xtb",
|
|
93
|
-
fukui_basis_set: Optional[str] = None,
|
|
94
|
-
fukui_engine: Optional[str] = None,
|
|
95
|
-
charge: int = 0,
|
|
96
|
-
multiplicity: int = 1,
|
|
97
|
-
folder_uuid: Optional[str] = None,
|
|
98
|
-
blocking: bool = True,
|
|
99
|
-
ping_interval: int = 5
|
|
100
|
-
) -> str:
|
|
101
|
-
"""Calculate Fukui indices for reactivity prediction with comprehensive control.
|
|
102
|
-
|
|
103
|
-
Predicts sites of chemical reactivity by analyzing electron density changes upon
|
|
104
|
-
gaining/losing electrons. Uses a two-step process: optimization + Fukui calculation.
|
|
105
|
-
|
|
106
|
-
** Fukui Index Types:**
|
|
107
|
-
- **f(+)**: Electrophilic attack sites (nucleophile reactivity)
|
|
108
|
-
- **f(-)**: Nucleophilic attack sites (electrophile reactivity)
|
|
109
|
-
- **f(0)**: Radical attack sites (average of f(+) and f(-))
|
|
110
|
-
- **Global Electrophilicity Index**: Overall electrophilic character
|
|
111
|
-
|
|
112
|
-
** Key Features:**
|
|
113
|
-
- Optional geometry optimization before Fukui calculation
|
|
114
|
-
- Separate control over optimization and Fukui calculation methods
|
|
115
|
-
- Per-atom reactivity indices for site-specific analysis
|
|
116
|
-
- Global reactivity descriptors
|
|
117
|
-
|
|
118
|
-
**Molecule Lookup**: Uses advanced PubChemPy + SQLite caching + RDKit validation system
|
|
119
|
-
for robust molecule identification and SMILES canonicalization.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
name: Name for the calculation
|
|
123
|
-
molecule: Molecule name (e.g., "aspirin", "taxol") or SMILES string
|
|
124
|
-
optimize: Whether to optimize geometry before Fukui calculation (default: True)
|
|
125
|
-
opt_method: Method for optimization (default: None, uses engine default)
|
|
126
|
-
opt_basis_set: Basis set for optimization (default: None, uses engine default)
|
|
127
|
-
opt_engine: Engine for optimization (default: None, auto-selected)
|
|
128
|
-
fukui_method: Method for Fukui calculation (default: "gfn1_xtb")
|
|
129
|
-
fukui_basis_set: Basis set for Fukui calculation (default: None, uses method default)
|
|
130
|
-
fukui_engine: Engine for Fukui calculation (default: None, auto-selected)
|
|
131
|
-
charge: Molecular charge (default: 0)
|
|
132
|
-
multiplicity: Spin multiplicity (default: 1)
|
|
133
|
-
folder_uuid: Optional folder UUID for organization
|
|
134
|
-
blocking: Whether to wait for completion (default: True)
|
|
135
|
-
ping_interval: Check status interval in seconds (default: 5)
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Fukui indices and reactivity analysis with per-atom and global descriptors
|
|
139
|
-
"""
|
|
140
|
-
# Look up SMILES if a common name was provided
|
|
141
|
-
canonical_smiles = lookup_molecule_smiles(molecule)
|
|
142
|
-
|
|
143
|
-
# Build optimization settings if requested
|
|
144
|
-
opt_settings = None
|
|
145
|
-
if optimize:
|
|
146
|
-
opt_settings = {
|
|
147
|
-
"charge": charge,
|
|
148
|
-
"multiplicity": multiplicity
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
# Add optimization method/basis/engine if specified
|
|
152
|
-
if opt_method:
|
|
153
|
-
opt_settings["method"] = opt_method.lower()
|
|
154
|
-
if opt_basis_set:
|
|
155
|
-
opt_settings["basis_set"] = opt_basis_set.lower()
|
|
156
|
-
|
|
157
|
-
# Default to fast optimization if no engine specified
|
|
158
|
-
if not opt_engine and not opt_method:
|
|
159
|
-
opt_settings["method"] = "gfn2_xtb" # Fast optimization
|
|
160
|
-
logger.info(f"No optimization method specified, defaulting to GFN2-xTB")
|
|
161
|
-
|
|
162
|
-
# Build Fukui calculation settings
|
|
163
|
-
fukui_settings = {
|
|
164
|
-
"method": fukui_method.lower(),
|
|
165
|
-
"charge": charge,
|
|
166
|
-
"multiplicity": multiplicity
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
# Add Fukui basis set if specified
|
|
170
|
-
if fukui_basis_set:
|
|
171
|
-
fukui_settings["basis_set"] = fukui_basis_set.lower()
|
|
172
|
-
|
|
173
|
-
# Validate Fukui method
|
|
174
|
-
valid_fukui_methods = ["gfn1_xtb", "gfn2_xtb", "hf", "b3lyp", "pbe", "m06-2x"]
|
|
175
|
-
if fukui_method.lower() not in valid_fukui_methods:
|
|
176
|
-
pass # Warning already logged by cleanup script
|
|
177
|
-
|
|
178
|
-
# Build parameters for Rowan API
|
|
179
|
-
fukui_params = {
|
|
180
|
-
"name": name,
|
|
181
|
-
"molecule": canonical_smiles,
|
|
182
|
-
"fukui_settings": fukui_settings,
|
|
183
|
-
"folder_uuid": folder_uuid,
|
|
184
|
-
"blocking": blocking,
|
|
185
|
-
"ping_interval": ping_interval
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
# Add optimization settings if enabled
|
|
189
|
-
if optimize and opt_settings:
|
|
190
|
-
fukui_params["opt_settings"] = opt_settings
|
|
191
|
-
|
|
192
|
-
# Add engines if specified
|
|
193
|
-
if opt_engine:
|
|
194
|
-
fukui_params["opt_engine"] = opt_engine.lower()
|
|
195
|
-
if fukui_engine:
|
|
196
|
-
fukui_params["fukui_engine"] = fukui_engine.lower()
|
|
197
|
-
|
|
198
|
-
try:
|
|
199
|
-
result = log_rowan_api_call(
|
|
200
|
-
workflow_type="fukui",
|
|
201
|
-
**fukui_params
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
return result
|
|
205
|
-
|
|
206
|
-
except Exception as e:
|
|
207
|
-
return f"Fukui analysis failed: {str(e)}"
|
|
208
|
-
|
|
209
|
-
def test_fukui():
|
|
210
|
-
"""Test the fukui function."""
|
|
211
|
-
return rowan_fukui(
|
|
212
|
-
name="test_fukui",
|
|
213
|
-
molecule="benzene",
|
|
214
|
-
fukui_method="gfn1_xtb",
|
|
215
|
-
blocking=True
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
if __name__ == "__main__":
|
|
219
|
-
print(test_fukui())
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Calculate hydrogen bond basicity (pKBHX) values for molecules to predict H-bond acceptor strength - useful for queries about pyridine/imine nitrogen basicity, comparing acceptor sites, or understanding binding selectivity. Input: molecule (SMILES string).
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import rowan
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
# Set up logging
|
|
10
|
-
import logging
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
# Configure rowan API key
|
|
14
|
-
if not hasattr(rowan, 'api_key') or not rowan.api_key:
|
|
15
|
-
api_key = os.getenv("ROWAN_API_KEY")
|
|
16
|
-
if api_key:
|
|
17
|
-
rowan.api_key = api_key
|
|
18
|
-
logger.info("🔑 Rowan API key configured")
|
|
19
|
-
else:
|
|
20
|
-
logger.error("No ROWAN_API_KEY found in environment")
|
|
21
|
-
|
|
22
|
-
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
23
|
-
"""Log Rowan API calls and let Rowan handle its own errors."""
|
|
24
|
-
|
|
25
|
-
# Simple logging for calculations
|
|
26
|
-
logger.info(f" Starting {workflow_type.replace('_', ' ')}...")
|
|
27
|
-
|
|
28
|
-
# Let Rowan handle everything - no custom error handling
|
|
29
|
-
return rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
30
|
-
|
|
31
|
-
def rowan_hydrogen_bond_basicity(
|
|
32
|
-
name: str,
|
|
33
|
-
molecule: str,
|
|
34
|
-
do_csearch: bool = True,
|
|
35
|
-
do_optimization: bool = True,
|
|
36
|
-
folder_uuid: Optional[str] = None,
|
|
37
|
-
blocking: bool = True,
|
|
38
|
-
ping_interval: int = 5
|
|
39
|
-
) -> str:
|
|
40
|
-
"""Calculate hydrogen bond basicity (pKBHX) values for molecules.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
name: Name for the calculation
|
|
44
|
-
molecule: Molecule SMILES string
|
|
45
|
-
do_csearch: Whether to perform conformational search (default: True)
|
|
46
|
-
do_optimization: Whether to perform optimization (default: True)
|
|
47
|
-
folder_uuid: UUID of folder to organize calculation in
|
|
48
|
-
blocking: Whether to wait for completion (default: True)
|
|
49
|
-
ping_interval: How often to check status in seconds (default: 5)
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Hydrogen bond basicity calculation results with pKBHX values
|
|
53
|
-
"""
|
|
54
|
-
try:
|
|
55
|
-
result = log_rowan_api_call(
|
|
56
|
-
workflow_type="hydrogen_bond_basicity",
|
|
57
|
-
name=name,
|
|
58
|
-
molecule=molecule,
|
|
59
|
-
do_csearch=do_csearch,
|
|
60
|
-
do_optimization=do_optimization,
|
|
61
|
-
folder_uuid=folder_uuid,
|
|
62
|
-
blocking=blocking,
|
|
63
|
-
ping_interval=ping_interval
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
return str(result)
|
|
67
|
-
|
|
68
|
-
except Exception as e:
|
|
69
|
-
error_response = {
|
|
70
|
-
"error": f"Hydrogen bond basicity calculation failed: {str(e)}",
|
|
71
|
-
"name": name,
|
|
72
|
-
"molecule": molecule
|
|
73
|
-
}
|
|
74
|
-
return str(error_response)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def test_rowan_hydrogen_bond_basicity():
|
|
78
|
-
"""Test the rowan_hydrogen_bond_basicity function."""
|
|
79
|
-
try:
|
|
80
|
-
# Test with pyridine
|
|
81
|
-
result = rowan_hydrogen_bond_basicity(
|
|
82
|
-
name="test_pyridine_basicity",
|
|
83
|
-
molecule="c1ccncc1"
|
|
84
|
-
)
|
|
85
|
-
print("✅ Hydrogen bond basicity test successful!")
|
|
86
|
-
print(f"Result: {result}")
|
|
87
|
-
return True
|
|
88
|
-
except Exception as e:
|
|
89
|
-
print(f"Hydrogen bond basicity test failed: {e}")
|
|
90
|
-
return False
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if __name__ == "__main__":
|
|
94
|
-
test_rowan_hydrogen_bond_basicity()
|