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,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan ADMET properties function for drug discovery.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import rowan
|
|
13
|
+
except ImportError:
|
|
14
|
+
rowan = None
|
|
15
|
+
|
|
16
|
+
# Setup logging
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
21
|
+
"""Log Rowan API calls with detailed parameters."""
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
start_time = time.time()
|
|
25
|
+
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
26
|
+
api_time = time.time() - start_time
|
|
27
|
+
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
except Exception as e:
|
|
31
|
+
api_time = time.time() - start_time
|
|
32
|
+
|
|
33
|
+
raise e
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def rowan_admet(
|
|
37
|
+
name: str,
|
|
38
|
+
molecule: str,
|
|
39
|
+
folder_uuid: Optional[str] = None,
|
|
40
|
+
blocking: bool = True,
|
|
41
|
+
ping_interval: int = 5
|
|
42
|
+
) -> str:
|
|
43
|
+
"""Predict ADME-Tox properties for drug discovery.
|
|
44
|
+
|
|
45
|
+
ADMET (Absorption, Distribution, Metabolism, Excretion, Toxicity) properties are crucial
|
|
46
|
+
for drug development. This workflow predicts drug-like properties including:
|
|
47
|
+
- Bioavailability and permeability
|
|
48
|
+
- Metabolic stability and clearance
|
|
49
|
+
- Toxicity indicators and safety profiles
|
|
50
|
+
- Drug-likeness metrics
|
|
51
|
+
|
|
52
|
+
Use this for: Drug discovery, pharmaceutical development, toxicity screening
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: Name for the calculation
|
|
56
|
+
molecule: Molecule SMILES string
|
|
57
|
+
folder_uuid: Optional folder UUID for organization
|
|
58
|
+
blocking: Whether to wait for completion (default: True)
|
|
59
|
+
ping_interval: Check status interval in seconds (default: 5)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
ADMET prediction results
|
|
63
|
+
"""
|
|
64
|
+
result = log_rowan_api_call(
|
|
65
|
+
workflow_type="admet",
|
|
66
|
+
name=name,
|
|
67
|
+
molecule=molecule,
|
|
68
|
+
folder_uuid=folder_uuid,
|
|
69
|
+
blocking=blocking,
|
|
70
|
+
ping_interval=ping_interval
|
|
71
|
+
)
|
|
72
|
+
return str(result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_rowan_admet():
|
|
76
|
+
"""Test the rowan_admet function."""
|
|
77
|
+
try:
|
|
78
|
+
# Test with a simple molecule (non-blocking to avoid long wait)
|
|
79
|
+
result = rowan_admet("test_admet", "CCO", blocking=False)
|
|
80
|
+
print("ADMET test successful!")
|
|
81
|
+
print(f"Result length: {len(result)} characters")
|
|
82
|
+
return True
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"ADMET test failed: {e}")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
test_rowan_admet()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Calculate bond dissociation energy (BDE) for molecules to determine bond strength - useful for reaction prediction, stability analysis, or understanding molecular fragmentation. Modes: reckless/rapid/careful/meticulous. Input: molecule (SMILES), optional atoms to break.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import rowan
|
|
7
|
+
from typing import Optional, List, Union
|
|
8
|
+
|
|
9
|
+
# Set up logging
|
|
10
|
+
import logging
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
16
|
+
"""Log Rowan API calls and let Rowan handle its own errors."""
|
|
17
|
+
|
|
18
|
+
# Simple logging for calculations
|
|
19
|
+
logger.info(f" Starting {workflow_type.replace('_', ' ')}...")
|
|
20
|
+
|
|
21
|
+
# Let Rowan handle everything - no custom error handling
|
|
22
|
+
return rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
23
|
+
|
|
24
|
+
def rowan_bde(
|
|
25
|
+
name: str,
|
|
26
|
+
molecule: str,
|
|
27
|
+
mode: str = "rapid",
|
|
28
|
+
atoms: Optional[List[int]] = None,
|
|
29
|
+
optimize_fragments: Optional[bool] = None,
|
|
30
|
+
all_CH: bool = False,
|
|
31
|
+
all_CX: bool = False,
|
|
32
|
+
folder_uuid: Optional[str] = None,
|
|
33
|
+
blocking: bool = True,
|
|
34
|
+
ping_interval: int = 5
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Calculate bond dissociation energy (BDE) for molecules.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
name: Name for the calculation
|
|
40
|
+
molecule: Molecule SMILES string
|
|
41
|
+
mode: Calculation mode - reckless/rapid/careful/meticulous (default: rapid)
|
|
42
|
+
atoms: Specific atoms to dissociate (1-indexed)
|
|
43
|
+
optimize_fragments: Whether to optimize fragments (default depends on mode)
|
|
44
|
+
all_CH: Dissociate all C-H bonds (default: False)
|
|
45
|
+
all_CX: Dissociate all C-X bonds where X is halogen (default: False)
|
|
46
|
+
folder_uuid: UUID of folder to organize calculation in
|
|
47
|
+
blocking: Whether to wait for completion (default: True)
|
|
48
|
+
ping_interval: How often to check status in seconds (default: 5)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Bond dissociation energy calculation results
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
# Build kwargs for API call
|
|
55
|
+
kwargs = {
|
|
56
|
+
"workflow_type": "bde",
|
|
57
|
+
"name": name,
|
|
58
|
+
"molecule": molecule,
|
|
59
|
+
"mode": mode.lower(),
|
|
60
|
+
"folder_uuid": folder_uuid,
|
|
61
|
+
"blocking": blocking,
|
|
62
|
+
"ping_interval": ping_interval
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Add optional parameters only if specified
|
|
66
|
+
if atoms is not None:
|
|
67
|
+
kwargs["atoms"] = atoms
|
|
68
|
+
if optimize_fragments is not None:
|
|
69
|
+
kwargs["optimize_fragments"] = optimize_fragments
|
|
70
|
+
if all_CH:
|
|
71
|
+
kwargs["all_CH"] = all_CH
|
|
72
|
+
if all_CX:
|
|
73
|
+
kwargs["all_CX"] = all_CX
|
|
74
|
+
|
|
75
|
+
result = log_rowan_api_call(**kwargs)
|
|
76
|
+
|
|
77
|
+
return str(result)
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
error_response = {
|
|
81
|
+
"error": f"BDE calculation failed: {str(e)}",
|
|
82
|
+
"name": name,
|
|
83
|
+
"molecule": molecule
|
|
84
|
+
}
|
|
85
|
+
return str(error_response)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_rowan_bde():
|
|
89
|
+
"""Test the rowan_bde function."""
|
|
90
|
+
try:
|
|
91
|
+
# Test with ethane C-C bond
|
|
92
|
+
result = rowan_bde(
|
|
93
|
+
name="test_ethane_CC",
|
|
94
|
+
molecule="CC",
|
|
95
|
+
mode="rapid"
|
|
96
|
+
)
|
|
97
|
+
print("✅ BDE test successful!")
|
|
98
|
+
print(f"Result: {result}")
|
|
99
|
+
return True
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"BDE test failed: {e}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
test_rowan_bde()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan calculation retrieval functions for MCP tool integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
import rowan
|
|
7
|
+
|
|
8
|
+
def rowan_calculation_retrieve(calculation_uuid: str) -> str:
|
|
9
|
+
"""Retrieve details of a specific calculation.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
calculation_uuid: UUID of the calculation to retrieve
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Calculation details
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
calculation = rowan.Calculation.retrieve(uuid=calculation_uuid)
|
|
20
|
+
return format_calculation_details(calculation, calculation_uuid)
|
|
21
|
+
|
|
22
|
+
except Exception as e:
|
|
23
|
+
return f" Error retrieving calculation: {str(e)}"
|
|
24
|
+
|
|
25
|
+
# Fallback error handling removed - keeping function simple and focused
|
|
26
|
+
|
|
27
|
+
def format_calculation_details(calculation: Dict[str, Any], calculation_uuid: str) -> str:
|
|
28
|
+
"""Format calculation details for display."""
|
|
29
|
+
|
|
30
|
+
formatted = f"⚙ **Calculation Details:**\n\n"
|
|
31
|
+
formatted += f" Name: {calculation.get('name', 'N/A')}\n"
|
|
32
|
+
formatted += f"UUID: {calculation_uuid}\n"
|
|
33
|
+
formatted += f" Status: {calculation.get('status', 'Unknown')}\n"
|
|
34
|
+
formatted += f" Elapsed: {calculation.get('elapsed', 0):.3f}s\n"
|
|
35
|
+
|
|
36
|
+
# Settings information
|
|
37
|
+
settings = calculation.get('settings', {})
|
|
38
|
+
if settings:
|
|
39
|
+
formatted += f"\n⚙ **Settings:**\n"
|
|
40
|
+
formatted += f" Method: {settings.get('method', 'N/A')}\n"
|
|
41
|
+
if settings.get('basis_set'):
|
|
42
|
+
formatted += f" Basis Set: {settings.get('basis_set')}\n"
|
|
43
|
+
if settings.get('tasks'):
|
|
44
|
+
formatted += f" Tasks: {', '.join(settings.get('tasks', []))}\n"
|
|
45
|
+
|
|
46
|
+
# Molecule information
|
|
47
|
+
molecules = calculation.get('molecules', [])
|
|
48
|
+
if molecules:
|
|
49
|
+
formatted += f"\n **Molecules:** {len(molecules)} structure(s)\n"
|
|
50
|
+
if len(molecules) > 0 and isinstance(molecules[0], dict):
|
|
51
|
+
first_mol = molecules[0]
|
|
52
|
+
if 'smiles' in first_mol:
|
|
53
|
+
formatted += f" Primary SMILES: {first_mol['smiles']}\n"
|
|
54
|
+
|
|
55
|
+
# Results/output data
|
|
56
|
+
if 'output' in calculation:
|
|
57
|
+
output = calculation['output']
|
|
58
|
+
formatted += f"\n **Results Available:**\n"
|
|
59
|
+
if isinstance(output, dict):
|
|
60
|
+
for key, value in list(output.items())[:5]: # Show first 5 items
|
|
61
|
+
if isinstance(value, (int, float)):
|
|
62
|
+
formatted += f" {key}: {value:.4f}\n"
|
|
63
|
+
elif isinstance(value, str) and len(value) < 50:
|
|
64
|
+
formatted += f" {key}: {value}\n"
|
|
65
|
+
elif isinstance(value, list) and len(value) < 10:
|
|
66
|
+
formatted += f" {key}: {value}\n"
|
|
67
|
+
else:
|
|
68
|
+
formatted += f" {key}: <complex data>\n"
|
|
69
|
+
else:
|
|
70
|
+
formatted += f" Raw output: {str(output)[:200]}...\n"
|
|
71
|
+
|
|
72
|
+
return formatted
|
|
73
|
+
|
|
74
|
+
# Specialized object data formatting removed - keeping function simple
|
|
75
|
+
|
|
76
|
+
def test_rowan_calculation_retrieve():
|
|
77
|
+
"""Test the calculation retrieve function."""
|
|
78
|
+
try:
|
|
79
|
+
# Test with a dummy UUID to see error handling
|
|
80
|
+
result = rowan_calculation_retrieve("test-uuid-123")
|
|
81
|
+
print(" Calculation retrieve test successful!")
|
|
82
|
+
print(f"Sample result: {result[:200]}...")
|
|
83
|
+
return True
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f" Calculation retrieve test failed: {e}")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
test_rowan_calculation_retrieve()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan conformers function for conformational analysis.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import rowan
|
|
12
|
+
except ImportError:
|
|
13
|
+
rowan = None
|
|
14
|
+
|
|
15
|
+
# Setup logging
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
21
|
+
"""Log Rowan API calls and let Rowan handle its own errors."""
|
|
22
|
+
|
|
23
|
+
# Simple logging for long-running calculations
|
|
24
|
+
if workflow_type in ["multistage_opt", "conformer_search"]:
|
|
25
|
+
blocking = kwargs.get('blocking', True)
|
|
26
|
+
if blocking:
|
|
27
|
+
logger.info(f" Starting {workflow_type.replace('_', ' ')}...")
|
|
28
|
+
else:
|
|
29
|
+
logger.info(f" Submitting {workflow_type.replace('_', ' ')} without waiting")
|
|
30
|
+
|
|
31
|
+
# Let Rowan handle everything - no custom error handling
|
|
32
|
+
return rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
33
|
+
|
|
34
|
+
def rowan_conformers(
|
|
35
|
+
name: str,
|
|
36
|
+
molecule: str,
|
|
37
|
+
max_conformers: int = 50,
|
|
38
|
+
mode: str = "rapid",
|
|
39
|
+
folder_uuid: Optional[str] = None,
|
|
40
|
+
blocking: bool = True,
|
|
41
|
+
ping_interval: int = 5
|
|
42
|
+
):
|
|
43
|
+
"""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.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
name: Name for the calculation
|
|
47
|
+
molecule: Molecule SMILES string or common name
|
|
48
|
+
max_conformers: Maximum number of conformers to generate (default: 50)
|
|
49
|
+
mode: Conformer search mode - "reckless", "rapid", "careful", "meticulous" (default: "rapid")
|
|
50
|
+
folder_uuid: UUID of folder to organize calculation in
|
|
51
|
+
blocking: Whether to wait for completion (default: True)
|
|
52
|
+
ping_interval: How often to check status in seconds (default: 5)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Conformer search results (actual results if blocking=True)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Validate mode parameter
|
|
59
|
+
valid_modes = ["reckless", "rapid", "careful", "meticulous"]
|
|
60
|
+
if mode not in valid_modes:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Invalid mode '{mode}'. Valid modes are: {', '.join(valid_modes)}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return log_rowan_api_call(
|
|
66
|
+
workflow_type="conformer_search",
|
|
67
|
+
name=name,
|
|
68
|
+
molecule=molecule,
|
|
69
|
+
mode=mode,
|
|
70
|
+
max_conformers=max_conformers,
|
|
71
|
+
folder_uuid=folder_uuid,
|
|
72
|
+
blocking=blocking,
|
|
73
|
+
ping_interval=ping_interval
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
pass
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rowan molecular descriptors function for QSAR modeling.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import rowan
|
|
12
|
+
except ImportError:
|
|
13
|
+
rowan = None
|
|
14
|
+
|
|
15
|
+
# Setup logging
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def log_rowan_api_call(workflow_type: str, **kwargs):
|
|
21
|
+
"""Log Rowan API calls with detailed parameters."""
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
start_time = time.time()
|
|
25
|
+
result = rowan.compute(workflow_type=workflow_type, **kwargs)
|
|
26
|
+
api_time = time.time() - start_time
|
|
27
|
+
|
|
28
|
+
if isinstance(result, dict) and 'uuid' in result:
|
|
29
|
+
job_status = result.get('status', result.get('object_status', 'Unknown'))
|
|
30
|
+
status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
|
|
31
|
+
status_text = status_names.get(job_status, f"Unknown ({job_status})")
|
|
32
|
+
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
except Exception as e:
|
|
36
|
+
api_time = time.time() - start_time
|
|
37
|
+
raise e
|
|
38
|
+
|
|
39
|
+
def rowan_descriptors(
|
|
40
|
+
name: str,
|
|
41
|
+
molecule: str,
|
|
42
|
+
folder_uuid: Optional[str] = None,
|
|
43
|
+
blocking: bool = True,
|
|
44
|
+
ping_interval: int = 5
|
|
45
|
+
) -> str:
|
|
46
|
+
"""Calculate molecular descriptors for data science.
|
|
47
|
+
|
|
48
|
+
Generates comprehensive molecular descriptors including:
|
|
49
|
+
- Topological and geometric descriptors
|
|
50
|
+
- Electronic and physicochemical properties
|
|
51
|
+
- Graph-based molecular features
|
|
52
|
+
- Machine learning ready feature vectors
|
|
53
|
+
|
|
54
|
+
Use this for: QSAR modeling, machine learning, chemical space analysis
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Name for the calculation
|
|
58
|
+
molecule: Molecule SMILES string
|
|
59
|
+
folder_uuid: Optional folder UUID for organization
|
|
60
|
+
blocking: Whether to wait for completion (default: True)
|
|
61
|
+
ping_interval: Check status interval in seconds (default: 5)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Molecular descriptors results
|
|
65
|
+
"""
|
|
66
|
+
result = log_rowan_api_call(
|
|
67
|
+
workflow_type="descriptors",
|
|
68
|
+
name=name,
|
|
69
|
+
molecule=molecule,
|
|
70
|
+
folder_uuid=folder_uuid,
|
|
71
|
+
blocking=blocking,
|
|
72
|
+
ping_interval=ping_interval
|
|
73
|
+
)
|
|
74
|
+
return str(result)
|
|
75
|
+
|
|
76
|
+
def test_rowan_descriptors():
|
|
77
|
+
"""Test the rowan_descriptors function."""
|
|
78
|
+
try:
|
|
79
|
+
# Test with a simple molecule (non-blocking to avoid long wait)
|
|
80
|
+
result = rowan_descriptors("test_descriptors", "CCO", blocking=False)
|
|
81
|
+
print(" Descriptors test successful!")
|
|
82
|
+
print(f"Result length: {len(result)} characters")
|
|
83
|
+
return True
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f" Descriptors test failed: {e}")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
test_rowan_descriptors()
|