rowan-mcp 1.0.2__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.

Files changed (57) hide show
  1. rowan_mcp/__init__.py +1 -1
  2. rowan_mcp/__main__.py +3 -5
  3. rowan_mcp/functions/admet.py +0 -5
  4. rowan_mcp/functions/bde.py +1 -8
  5. rowan_mcp/functions/conformers.py +1 -4
  6. rowan_mcp/functions/descriptors.py +1 -4
  7. rowan_mcp/functions/docking.py +6 -56
  8. rowan_mcp/functions/electronic_properties.py +1 -4
  9. rowan_mcp/functions/folder_management.py +1 -8
  10. rowan_mcp/functions/fukui.py +1 -4
  11. rowan_mcp/functions/hydrogen_bond_basicity.py +1 -8
  12. rowan_mcp/functions/multistage_opt.py +1 -4
  13. rowan_mcp/functions/pka.py +1 -8
  14. rowan_mcp/functions/redox_potential.py +2 -5
  15. rowan_mcp/functions/system_management.py +1 -8
  16. rowan_mcp/functions/tautomers.py +1 -4
  17. rowan_mcp/functions_v2/BENCHMARK.md +86 -0
  18. rowan_mcp/functions_v2/molecule_lookup.py +232 -0
  19. rowan_mcp/functions_v2/protein_management.py +141 -0
  20. rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
  21. rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
  22. rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
  23. rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
  24. rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
  25. rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
  26. rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
  27. rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
  28. rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
  29. rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
  30. rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
  31. rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
  32. rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
  33. rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
  34. rowan_mcp/server.py +109 -144
  35. rowan_mcp/tests/basic_calculation_from_json.py +0 -0
  36. rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
  37. rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
  38. rowan_mcp/tests/bde.py +37 -0
  39. rowan_mcp/tests/benchmark_queries.md +120 -0
  40. rowan_mcp/tests/cofolding_screen.py +131 -0
  41. rowan_mcp/tests/conformer_dependent_redox.py +37 -0
  42. rowan_mcp/tests/conformers.py +31 -0
  43. rowan_mcp/tests/data.json +189 -0
  44. rowan_mcp/tests/docking_screen.py +157 -0
  45. rowan_mcp/tests/irc.py +24 -0
  46. rowan_mcp/tests/macropka.py +13 -0
  47. rowan_mcp/tests/multistage_opt.py +13 -0
  48. rowan_mcp/tests/optimization.py +21 -0
  49. rowan_mcp/tests/phenol_pka.py +36 -0
  50. rowan_mcp/tests/pka.py +36 -0
  51. rowan_mcp/tests/protein_cofolding.py +17 -0
  52. rowan_mcp/tests/scan.py +28 -0
  53. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/METADATA +38 -45
  54. rowan_mcp-2.0.1.dist-info/RECORD +69 -0
  55. rowan_mcp-1.0.2.dist-info/RECORD +0 -34
  56. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/WHEEL +0 -0
  57. {rowan_mcp-1.0.2.dist-info → rowan_mcp-2.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,141 @@
1
+ """
2
+ Rowan v2 API: Protein Management
3
+ Tools for creating, retrieving, and managing protein structures.
4
+ """
5
+
6
+ from typing import List, Dict, Any, Annotated
7
+ import rowan
8
+
9
+
10
+ def create_protein_from_pdb_id(
11
+ name: Annotated[str, "Name for the protein"],
12
+ code: Annotated[str, "PDB ID code (e.g., '1HCK')"]
13
+ ) -> Dict[str, Any]:
14
+ """Create a protein from a PDB ID.
15
+
16
+ Args:
17
+ name: Name for the protein
18
+ code: PDB ID code (e.g., '1HCK')
19
+
20
+ Returns:
21
+ Dictionary containing protein information
22
+ """
23
+ protein = rowan.create_protein_from_pdb_id(name=name, code=code)
24
+
25
+ return {
26
+ "uuid": protein.uuid,
27
+ "name": protein.name,
28
+ "sanitized": protein.sanitized,
29
+ "created_at": str(protein.created_at) if protein.created_at else None
30
+ }
31
+
32
+
33
+ def retrieve_protein(
34
+ uuid: Annotated[str, "UUID of the protein to retrieve"]
35
+ ) -> Dict[str, Any]:
36
+ """Retrieve a protein by UUID.
37
+
38
+ Args:
39
+ uuid: UUID of the protein to retrieve
40
+
41
+ Returns:
42
+ Dictionary containing the protein data
43
+ """
44
+ protein = rowan.retrieve_protein(uuid)
45
+
46
+ return {
47
+ "uuid": protein.uuid,
48
+ "name": protein.name,
49
+ "sanitized": protein.sanitized,
50
+ "created_at": str(protein.created_at) if protein.created_at else None
51
+ }
52
+
53
+
54
+ def list_proteins(
55
+ page: Annotated[int, "Page number (0-indexed)"] = 0,
56
+ size: Annotated[int, "Number per page"] = 20
57
+ ) -> List[Dict[str, Any]]:
58
+ """List proteins.
59
+
60
+ Args:
61
+ page: Page number (0-indexed)
62
+ size: Number per page
63
+
64
+ Returns:
65
+ List of protein dictionaries
66
+ """
67
+ proteins = rowan.list_proteins(page=page, size=size)
68
+
69
+ return [
70
+ {
71
+ "uuid": p.uuid,
72
+ "name": p.name,
73
+ "sanitized": p.sanitized,
74
+ "created_at": str(p.created_at) if p.created_at else None
75
+ }
76
+ for p in proteins
77
+ ]
78
+
79
+
80
+ def upload_protein(
81
+ name: Annotated[str, "Name for the protein"],
82
+ file_path: Annotated[str, "Path to PDB file"]
83
+ ) -> Dict[str, Any]:
84
+ """Upload a protein from a PDB file.
85
+
86
+ Args:
87
+ name: Name for the protein
88
+ file_path: Path to PDB file
89
+
90
+ Returns:
91
+ Dictionary containing protein information
92
+ """
93
+ from pathlib import Path
94
+ protein = rowan.upload_protein(name=name, file_path=Path(file_path))
95
+
96
+ return {
97
+ "uuid": protein.uuid,
98
+ "name": protein.name,
99
+ "sanitized": protein.sanitized,
100
+ "created_at": str(protein.created_at) if protein.created_at else None
101
+ }
102
+
103
+
104
+ def delete_protein(
105
+ uuid: Annotated[str, "UUID of the protein to delete"]
106
+ ) -> Dict[str, str]:
107
+ """Delete a protein.
108
+
109
+ Args:
110
+ uuid: UUID of the protein to delete
111
+
112
+ Returns:
113
+ Dictionary with confirmation message
114
+ """
115
+ protein = rowan.retrieve_protein(uuid)
116
+ protein.delete()
117
+
118
+ return {
119
+ "message": f"Protein {uuid} deleted",
120
+ "uuid": uuid
121
+ }
122
+
123
+
124
+ def sanitize_protein(
125
+ uuid: Annotated[str, "UUID of the protein to sanitize"]
126
+ ) -> Dict[str, Any]:
127
+ """Sanitize a protein for docking.
128
+
129
+ Args:
130
+ uuid: UUID of the protein to sanitize
131
+
132
+ Returns:
133
+ Dictionary with sanitization status
134
+ """
135
+ protein = rowan.retrieve_protein(uuid)
136
+ protein.sanitize()
137
+
138
+ return {
139
+ "uuid": uuid,
140
+ "message": f"Protein {uuid} sanitized"
141
+ }
@@ -0,0 +1,195 @@
1
+ """
2
+ Rowan v2 API: Basic Calculation Workflow
3
+ Submit basic quantum chemistry calculations with various methods and tasks.
4
+ """
5
+
6
+ # Simplified imports - no complex typing needed
7
+ from typing import Annotated
8
+ import rowan
9
+ import stjames
10
+ import json
11
+
12
+
13
+ def submit_basic_calculation_workflow(
14
+ initial_molecule: Annotated[str, "SMILES string or molecule JSON for quantum chemistry calculation"],
15
+ method: Annotated[str, "Computational method (e.g., 'gfn2-xtb', 'uma_m_omol', 'b3lyp-d3bj')"] = "uma_m_omol",
16
+ tasks: Annotated[str, "JSON array or comma-separated list of tasks (e.g., '[\"optimize\"]', 'optimize, frequencies')"] = "",
17
+ mode: Annotated[str, "Calculation mode: 'rapid', 'careful', 'meticulous', or 'auto'"] = "auto",
18
+ engine: Annotated[str, "Computational engine: 'omol25', 'xtb', 'psi4'"] = "omol25",
19
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Basic Calculation Workflow",
20
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
21
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
22
+ ):
23
+ """Submit a basic calculation workflow using Rowan v2 API.
24
+
25
+ Performs fundamental quantum chemistry calculations with configurable methods
26
+ and computational tasks. Returns a workflow object for tracking progress.
27
+
28
+ Examples:
29
+ # Simple water optimization with GFN2-xTB
30
+ result = submit_basic_calculation_workflow(
31
+ initial_molecule="O",
32
+ method="gfn2-xtb",
33
+ tasks=["optimize"],
34
+ engine="xtb",
35
+ name="Water Optimization"
36
+ )
37
+
38
+ # Butane optimization from SMILES
39
+ result = submit_basic_calculation_workflow(
40
+ initial_molecule="CCCC",
41
+ method="gfn2-xtb",
42
+ tasks=["optimize"],
43
+ mode="rapid",
44
+ engine="xtb",
45
+ name="Butane Optimization"
46
+ )
47
+
48
+ # Using a molecule dict (from data.json)
49
+ molecule_dict = {
50
+ "smiles": "CCCC",
51
+ "charge": 0,
52
+ "multiplicity": 1,
53
+ "atoms": [...] # atomic positions
54
+ }
55
+ result = submit_basic_calculation_workflow(
56
+ initial_molecule=molecule_dict,
57
+ method="gfn2-xtb",
58
+ tasks=["optimize"],
59
+ engine="xtb"
60
+ )
61
+ """
62
+
63
+ # Parse tasks parameter - handle string input
64
+ parsed_tasks = None
65
+ if tasks: # If not empty string
66
+ tasks = tasks.strip()
67
+ if tasks.startswith('[') and tasks.endswith(']'):
68
+ # JSON array format like '["optimize"]'
69
+ try:
70
+ parsed_tasks = json.loads(tasks)
71
+ except (json.JSONDecodeError, ValueError):
72
+ # Failed to parse as JSON, try as comma-separated
73
+ tasks = tasks.strip('[]').replace('"', '').replace("'", "")
74
+ parsed_tasks = [t.strip() for t in tasks.split(',') if t.strip()]
75
+ elif ',' in tasks:
76
+ # Comma-separated format like 'optimize, frequencies'
77
+ parsed_tasks = [t.strip() for t in tasks.split(',') if t.strip()]
78
+ else:
79
+ # Single task as string like 'optimize'
80
+ parsed_tasks = [tasks]
81
+
82
+
83
+ try:
84
+ # Handle initial_molecule parameter - could be JSON string, SMILES, or dict
85
+ if isinstance(initial_molecule, str):
86
+ # Check if it's a JSON string (starts with { or [)
87
+ initial_molecule_str = initial_molecule.strip()
88
+ if (initial_molecule_str.startswith('{') and initial_molecule_str.endswith('}')) or \
89
+ (initial_molecule_str.startswith('[') and initial_molecule_str.endswith(']')):
90
+ try:
91
+ # Parse the JSON string to dict
92
+ initial_molecule = json.loads(initial_molecule_str)
93
+
94
+ # Now handle as dict (fall through to dict handling below)
95
+ if isinstance(initial_molecule, dict) and 'smiles' in initial_molecule:
96
+ smiles = initial_molecule.get('smiles')
97
+ if smiles:
98
+ try:
99
+ initial_molecule = stjames.Molecule.from_smiles(smiles)
100
+ except Exception as e:
101
+ initial_molecule = smiles
102
+ except (json.JSONDecodeError, ValueError) as e:
103
+ # Not valid JSON, treat as SMILES string
104
+ try:
105
+ initial_molecule = stjames.Molecule.from_smiles(initial_molecule)
106
+ except Exception as e:
107
+ pass
108
+ else:
109
+ # Regular SMILES string
110
+ try:
111
+ initial_molecule = stjames.Molecule.from_smiles(initial_molecule)
112
+ except Exception as e:
113
+ pass
114
+ elif isinstance(initial_molecule, dict) and 'smiles' in initial_molecule:
115
+ # If we have a dict with SMILES, extract and use just the SMILES
116
+ smiles = initial_molecule.get('smiles')
117
+ if smiles:
118
+ try:
119
+ initial_molecule = stjames.Molecule.from_smiles(smiles)
120
+ except Exception as e:
121
+ initial_molecule = smiles
122
+
123
+ # Convert to appropriate format
124
+ if hasattr(initial_molecule, 'model_dump'):
125
+ initial_molecule_dict = initial_molecule.model_dump()
126
+ elif isinstance(initial_molecule, dict):
127
+ initial_molecule_dict = initial_molecule
128
+ else:
129
+ # Try to convert to StJamesMolecule if it's a string
130
+ try:
131
+ mol = stjames.Molecule.from_smiles(str(initial_molecule))
132
+ initial_molecule_dict = mol.model_dump()
133
+ except:
134
+ # If that fails, pass as-is
135
+ initial_molecule_dict = initial_molecule
136
+
137
+ # Convert method string to Method object to get the correct name
138
+ if isinstance(method, str):
139
+ # Handle common method name variations
140
+ method_map = {
141
+ 'gfn2_xtb': 'gfn2-xtb',
142
+ 'gfn1_xtb': 'gfn1-xtb',
143
+ 'gfn0_xtb': 'gfn0-xtb',
144
+ 'r2scan_3c': 'r2scan-3c',
145
+ 'wb97x_d3': 'wb97x-d3',
146
+ 'wb97m_d3bj': 'wb97m-d3bj',
147
+ 'b3lyp_d3bj': 'b3lyp-d3bj',
148
+ 'uma_m_omol': 'uma_m_omol', # This one stays the same
149
+ }
150
+ method = method_map.get(method, method)
151
+
152
+ try:
153
+ method_obj = stjames.Method(method)
154
+ method_name = method_obj.name
155
+ except:
156
+ # If Method conversion fails, use the string as-is
157
+ method_name = method
158
+ else:
159
+ method_name = method
160
+
161
+ # Use parsed tasks or default
162
+ final_tasks = parsed_tasks if parsed_tasks else ["optimize"]
163
+
164
+ # Build workflow_data following the official API structure
165
+ workflow_data = {
166
+ "settings": {
167
+ "method": method_name,
168
+ "tasks": final_tasks,
169
+ "mode": mode,
170
+ },
171
+ "engine": engine,
172
+ }
173
+
174
+ # Build the API request
175
+ data = {
176
+ "name": name,
177
+ "folder_uuid": folder_uuid if folder_uuid else None,
178
+ "workflow_type": "basic_calculation",
179
+ "workflow_data": workflow_data,
180
+ "initial_molecule": initial_molecule_dict,
181
+ "max_credits": max_credits if max_credits > 0 else None,
182
+ }
183
+
184
+ # Submit directly to API
185
+ from rowan.utils import api_client
186
+ from rowan import Workflow
187
+
188
+ with api_client() as client:
189
+ response = client.post("/workflow", json=data)
190
+ response.raise_for_status()
191
+ return Workflow(**response.json())
192
+
193
+ except Exception as e:
194
+ # Re-raise the exception so MCP can handle it
195
+ raise
@@ -0,0 +1,158 @@
1
+ """
2
+ Rowan v2 API: Conformer Search Workflow
3
+ Search for low-energy molecular conformations using various methods.
4
+ """
5
+
6
+ from typing import Any, Dict, Annotated
7
+ import rowan
8
+ import stjames
9
+
10
+ def submit_conformer_search_workflow(
11
+ initial_molecule: Annotated[str, "SMILES string representing the initial structure"],
12
+ conf_gen_mode: Annotated[str, "Conformer generation mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)"] = "rapid",
13
+ final_method: Annotated[str, "Final optimization method (e.g., 'aimnet2_wb97md3', 'r2scan_3c', 'wb97x-d3_def2-tzvp')"] = "aimnet2_wb97md3",
14
+ solvent: Annotated[str, "Solvent for implicit solvation (SMILES or name). Empty string for vacuum"] = "",
15
+ transition_state: Annotated[bool, "Whether to search for transition state conformers"] = False,
16
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Conformer Search Workflow",
17
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
18
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
19
+ ):
20
+ """Submit a conformer search workflow using Rowan v2 API.
21
+
22
+ Args:
23
+ initial_molecule: SMILES string representing the initial structure
24
+ conf_gen_mode: Conformer generation mode: 'rapid' (fast), 'careful' (balanced), or 'meticulous' (thorough)
25
+ final_method: Final optimization method (e.g., 'aimnet2_wb97md3', 'r2scan_3c', 'wb97x-d3_def2-tzvp')
26
+ solvent: Solvent for implicit solvation (e.g., 'water', 'ethanol', 'dmso'). Empty string for gas phase.
27
+ transition_state: Whether to search for transition state conformers
28
+ name: Workflow name for identification and tracking
29
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
30
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
31
+
32
+ Conformer Generation Modes:
33
+ - 'rapid': RDKit/MMFF, 300 conformers, 0.10 Å RMSD cutoff (recommended for most work)
34
+ - 'careful': CREST/GFN-FF quick mode, 150 conformers max
35
+ - 'meticulous': CREST/GFN2-xTB normal mode, 500 conformers max
36
+
37
+ Returns:
38
+ Workflow object representing the submitted workflow
39
+
40
+ Examples:
41
+ # Simple diethyl ether conformer search
42
+ result = submit_conformer_search_workflow(
43
+ initial_molecule="CCOCC"
44
+ )
45
+
46
+ # Basic butane conformer search with rapid mode
47
+ result = submit_conformer_search_workflow(
48
+ initial_molecule="CCCC",
49
+ conf_gen_mode="rapid"
50
+ )
51
+
52
+ # Careful search with solvent
53
+ result = submit_conformer_search_workflow(
54
+ initial_molecule="CC(C)CC(=O)O",
55
+ conf_gen_mode="careful",
56
+ solvent="water",
57
+ final_method="r2scan_3c"
58
+ )
59
+ """
60
+
61
+ try:
62
+ # Convert initial_molecule to appropriate format
63
+ if isinstance(initial_molecule, str):
64
+ # Try to parse as SMILES
65
+ try:
66
+ mol = stjames.Molecule.from_smiles(initial_molecule)
67
+ initial_molecule = mol.model_dump()
68
+ except Exception:
69
+ # If fails, pass as dict with smiles
70
+ initial_molecule = {"smiles": initial_molecule}
71
+ elif isinstance(initial_molecule, dict):
72
+ # Already in dict format
73
+ pass
74
+ elif hasattr(initial_molecule, 'model_dump'):
75
+ # StJamesMolecule
76
+ initial_molecule = initial_molecule.model_dump()
77
+ else:
78
+ # Assume RdkitMol or similar
79
+ try:
80
+ from rdkit import Chem
81
+ mol = stjames.Molecule.from_rdkit(initial_molecule, cid=0)
82
+ initial_molecule = mol.model_dump()
83
+ except:
84
+ pass
85
+
86
+ # Convert final_method to Method if it's a string
87
+ if isinstance(final_method, str):
88
+ final_method = stjames.Method(final_method)
89
+
90
+ # Determine solvent model based on method
91
+ solvent_model = None
92
+ if solvent:
93
+ solvent_model = "alpb" if final_method in stjames.XTB_METHODS else "cpcm"
94
+
95
+ # Create optimization settings following official API
96
+ opt_settings = stjames.Settings(
97
+ method=final_method,
98
+ tasks=["optimize"],
99
+ mode=stjames.Mode.AUTO,
100
+ solvent_settings={"solvent": solvent, "model": solvent_model} if solvent else None,
101
+ opt_settings={"transition_state": transition_state, "constraints": []},
102
+ )
103
+
104
+ # Create MultiStageOptSettings
105
+ msos = stjames.MultiStageOptSettings(
106
+ mode=stjames.Mode.MANUAL,
107
+ xtb_preopt=True,
108
+ optimization_settings=[opt_settings],
109
+ )
110
+
111
+ # Convert to dict and fix the soscf field
112
+ msos_dict = msos.model_dump()
113
+
114
+ # Fix soscf: convert boolean to string enum
115
+ if 'optimization_settings' in msos_dict:
116
+ for opt_setting in msos_dict['optimization_settings']:
117
+ if 'scf_settings' in opt_setting and 'soscf' in opt_setting['scf_settings']:
118
+ soscf_val = opt_setting['scf_settings']['soscf']
119
+ if isinstance(soscf_val, bool):
120
+ if soscf_val is False:
121
+ opt_setting['scf_settings']['soscf'] = 'never'
122
+ elif soscf_val is True:
123
+ opt_setting['scf_settings']['soscf'] = 'always'
124
+ elif soscf_val is None:
125
+ # Default to smart behavior
126
+ opt_setting['scf_settings']['soscf'] = 'upon_failure'
127
+
128
+ # Build workflow_data
129
+ workflow_data = {
130
+ "multistage_opt_settings": msos_dict,
131
+ "conf_gen_mode": conf_gen_mode,
132
+ "mso_mode": "manual",
133
+ "solvent": solvent if solvent else None,
134
+ "transition_state": transition_state,
135
+ }
136
+
137
+ # Build the API request
138
+ data = {
139
+ "name": name,
140
+ "folder_uuid": folder_uuid if folder_uuid else None,
141
+ "workflow_type": "conformer_search",
142
+ "workflow_data": workflow_data,
143
+ "initial_molecule": initial_molecule,
144
+ "max_credits": max_credits if max_credits > 0 else None,
145
+ }
146
+
147
+ # Submit to API
148
+ from rowan.utils import api_client
149
+ from rowan import Workflow
150
+
151
+ with api_client() as client:
152
+ response = client.post("/workflow", json=data)
153
+ response.raise_for_status()
154
+ return Workflow(**response.json())
155
+
156
+ except Exception as e:
157
+ # Re-raise the exception so MCP can handle it
158
+ raise e
@@ -0,0 +1,52 @@
1
+ """
2
+ Rowan v2 API: Descriptors Workflow
3
+ Calculate molecular descriptors for QSAR and molecular analysis.
4
+ """
5
+
6
+ from typing import Annotated
7
+ import rowan
8
+ import stjames
9
+
10
+
11
+ def submit_descriptors_workflow(
12
+ initial_molecule: Annotated[str, "SMILES string of the molecule to calculate descriptors for"],
13
+ name: Annotated[str, "Workflow name for identification and tracking"] = "Descriptors Workflow",
14
+ folder_uuid: Annotated[str, "UUID of folder to organize this workflow. Empty string uses default folder"] = "",
15
+ max_credits: Annotated[int, "Maximum credits to spend on this calculation. 0 for no limit"] = 0
16
+ ):
17
+ """Submit a molecular descriptors calculation workflow using Rowan v2 API.
18
+
19
+ Args:
20
+ initial_molecule: SMILES string or molecule object for descriptor calculation
21
+ name: Workflow name for identification and tracking
22
+ folder_uuid: UUID of folder to organize this workflow. Empty string uses default folder.
23
+ max_credits: Maximum credits to spend on this calculation. 0 for no limit.
24
+
25
+ Calculates a comprehensive set of molecular descriptors including:
26
+ - Physical properties (MW, logP, TPSA, etc.)
27
+ - Electronic properties (HOMO/LUMO, dipole moment, etc.)
28
+ - Structural features (rotatable bonds, H-bond donors/acceptors, etc.)
29
+ - Topological indices
30
+
31
+ Returns:
32
+ Workflow object representing the submitted workflow
33
+
34
+ Example:
35
+ # Basic descriptor calculation
36
+ result = submit_descriptors_workflow(
37
+ initial_molecule="CC(=O)Nc1ccc(O)cc1"
38
+ )
39
+
40
+ # For complex molecule
41
+ result = submit_descriptors_workflow(
42
+ initial_molecule="CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
43
+ name="Caffeine Descriptors"
44
+ )
45
+ """
46
+
47
+ return rowan.submit_descriptors_workflow(
48
+ initial_molecule=stjames.Molecule.from_smiles(initial_molecule),
49
+ name=name,
50
+ folder_uuid=folder_uuid if folder_uuid else None,
51
+ max_credits=max_credits if max_credits > 0 else None
52
+ )