alterlab-rowan
Drives the Rowan cloud quantum-chemistry platform via its Python API for computational chemistry — pKa prediction, geometry optimization, conformer searching, molecular property calculations, protein-ligand docking (AutoDock Vina), and AI protein cofolding (Chai-1, Boltz-1/2), wi
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/cheminformatics/alterlab-rowan
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Rowan: Cloud-Based Quantum Chemistry Platform
Overview
Rowan is a cloud-based computational chemistry platform that provides programmatic access to quantum chemistry workflows through a Python API. It enables automation of complex molecular simulations without requiring local computational resources or expertise in multiple quantum chemistry packages.
Key Capabilities:
- Molecular property prediction (pKa, redox potential, solubility, ADMET-Tox)
- Geometry optimization and conformer searching
- Protein-ligand docking with AutoDock Vina
- AI-powered protein cofolding with Chai-1 and Boltz models
- Access to DFT, semiempirical, and neural network potential methods
- Cloud compute with automatic resource allocation
Why Rowan:
- No local compute cluster required
- Unified API for dozens of computational methods
- Results viewable in web interface at labs.rowansci.com
- Automatic resource scaling
When to Use This Skill
Use this skill when the user wants to:
- Predict pKa / macro-pKa, redox potentials, solubility, or other properties without local QM software
- Run geometry optimizations, conformer searches, or single points with NNPs (AIMNet2, Egret), xTB, or DFT in the cloud
- Dock ligands (Vina/GNINA) or co-fold protein–ligand complexes (Boltz, Chai-1, OpenFold3) as managed cloud jobs
- Script and batch these jobs from Python (
rowan-python), organized in folders with credit caps
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Running and analyzing a local OpenMM MD trajectory (RMSD/RMSF, contacts) | alterlab-molecular-dynamics |
| Open-source, local diffusion docking with DiffDock (no cloud account) | alterlab-diffdock |
| Running Boltz-2 or Chai-1 locally on your own GPU | alterlab-boltz or alterlab-chai |
| Local conformers, descriptors, or RDKit force-field minimization | alterlab-rdkit |
Installation and Authentication
Installation
Requires Python >= 3.12. This skill targets rowan-python 3.x (current 3.2.0 as of 2026-09; v2 had a different result API).
uv pip install "rowan-python>=3.2"
Installing rowan-python also pulls in stjames (molecule/result models) and rdkit.
Authentication
Generate an API key at labs.rowansci.com/account/api-keys.
Option 1: Direct assignment
import rowan
rowan.api_key = "your_api_key_here"
Option 2: Environment variable (recommended)
export ROWAN_API_KEY="your_api_key_here"
The API key is automatically read from ROWAN_API_KEY on module import.
Verify Setup
import rowan
# Check authentication
user = rowan.whoami()
print(f"Logged in as: {user.username}")
print(f"Credits available: {user.credits}")
The Result Pattern (read this first)
Every submit_*_workflow returns a Workflow. Do NOT read workflow.data[...] by hand and do NOT call the deprecated wait_for_result(). The v3 idiom is a single call:
mol = rowan.Molecule.from_smiles("c1ccccc1O") # 3D structure for the default 3D method
workflow = rowan.submit_pka_workflow(mol, name="phenol pKa")
result = workflow.result() # blocks until done, returns a typed WorkflowResult
print(result.strongest_acid) # typed attribute access, not a dict key
Key facts:
workflow.result(wait=True, poll_interval=5)blocks, fetches, and raisesrowan.WorkflowErrorif the workflow failed or was stopped. Usewait=Falseto grab whatever is ready without blocking.workflow.statusis the integer enumstjames.Status(QUEUED=0, RUNNING=1, COMPLETED_OK=2, FAILED=3, STOPPED=4, AWAITING_QUEUE=5, DRAFT=6, PREEMPTED=7), not a string. Useworkflow.done()/workflow.is_finished()rather than comparing to"completed".- Geometry-based workflows now reject a bare SMILES string. As of rowan-python 3.x,
submit_basic_calculation_workflow,submit_docking_workflow, and any 3D pKa/conformer method callrequire_coordinates, which raisesValueErroron a SMILES with no coordinates. Build a 3D molecule first:mol = rowan.Molecule.from_smiles("CCO")(orstjames.Molecule.from_smiles(...), which auto-generates coordinates), then passmol. A SMILES string is still accepted by SMILES-based methods (submit_macropka_workflow, and pKa withmethod="starling"/"chemprop_nevolianis2025").Molecule.from_smiles(smiles)takes only the SMILES (nocharge=/multiplicity=kwargs).
Core Workflows
1. pKa Prediction
Predict micro-pKa / acid dissociation constants:
import rowan
# The default pKa method is now a 3D method, so build a molecule (bare SMILES is rejected).
workflow = rowan.submit_pka_workflow(
rowan.Molecule.from_smiles("c1ccccc1O"), # Phenol
name="phenol pKa calculation",
pka_range=(2, 12), # default
method="gxtb_wagen2026", # default (g-xTB); "aimnet2_wagen2024" also 3D.
# "starling" / "chemprop_nevolianis2025" take a SMILES string.
)
result = workflow.result()
print(f"Strongest acid pKa: {result.strongest_acid}")
print(f"Strongest base pKa: {result.strongest_base}")
For macroscopic pKa, microstate populations vs. pH, isoelectric point, and logD/solubility-vs-pH, use rowan.submit_macropka_workflow(...) and read result.pka_values, result.microstates, result.isoelectric_point.
2. Conformer Search
Generate and rank a conformer ensemble:
import rowan
workflow = rowan.submit_conformer_search_workflow(
"CCCC", # Butane
name="butane conformer search",
final_method="aimnet2_wb97md3", # NNP; default
)
result = workflow.result()
print(f"Found {result.num_conformers} conformers")
for energy in result.get_energies(): # relative energies, kcal/mol
print(f" ΔE = {energy:.2f} kcal/mol")
lowest = result.get_conformer(0) # stjames.Molecule of the lowest-energy conformer
3. Geometry Optimization
submit_basic_calculation_workflow is task-driven: pass tasks (e.g. ["optimize"], ["energy"], ["optimize", "frequencies"]), not a workflow_type string.
import rowan
workflow = rowan.submit_basic_calculation_workflow(
rowan.Molecule.from_smiles("CC(=O)O"), # Acetic acid (needs 3D coords; SMILES is rejected)
tasks=["optimize"],
preset="organic_nnp", # quick NNP preset; or set method=/basis_set= explicitly
name="acetic acid optimization",
)
result = workflow.result()
print(f"Final energy: {result.energy} Hartree")
optimized_mol = result.molecule # stjames.Molecule with optimized coordinates
4. Protein-Ligand Docking
Dock small molecules to protein targets. The pocket is [[center_x, center_y, center_z], [size_x, size_y, size_z]] in Angstroms — a list of two 3-vectors, NOT a dict.
import rowan
# Create protein from a PDB ID (fetched from RCSB)
protein = rowan.create_protein_from_pdb_id(name="EGFR kinase", code="1M17")
protein.sanitize() # strip waters/ions, fix residues
pocket = [[10.0, 20.0, 30.0], # center (Å)
[20.0, 20.0, 20.0]] # box size (Å)
workflow = rowan.submit_docking_workflow(
protein=protein, # Protein object or its .uuid
pocket=pocket,
# 3D input required — a bare SMILES string raises ValueError
initial_molecule=rowan.Molecule.from_smiles("Cc1ccc(NC(=O)c2ccc(CN3CCN(C)CC3)cc2)cc1"),
# engine options go in docking_settings; the loose scoring_function=/exhaustiveness=
# kwargs are deprecated (rowan.GninaSettings selects GNINA instead of Vina)
docking_settings=rowan.VinaSettings(scoring_function="vinardo"), # or "vina"
name="EGFR docking",
)
result = workflow.result()
best = result.scores[0] # DockingScore, sorted best-first
print(f"Best docking score: {best.score} kcal/mol")
best_pose = result.best_pose # stjames.Molecule of the top pose
5. Protein Cofolding (AI Structure Prediction)
Predict protein-ligand complex structures using AI models:
import rowan
protein_seq = "MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVPSTAIREISLLKELNHPNIVKLLDVIHTENKLYLVFEFLHQDLKKFMDASALTGIPLPLIKSYLFQLLQGLAFCHSHRVLHRDLKPQNLLINTEGAIKLADFGLARAFGVPVRTYTHEVVTLWYRAPEILLGCKYYSTAVDIWSLGCIFAEMVTRRALFPGDSEIDQLFRIFRTLGTPDEVVWPGVTSMPDYKPSFPKWARQDFSKVVPPLDEDGRSLLSQMLHYDPNKRISAKAALAHPFFQDVTKPVPHLRL"
ligand = "CCC(C)CN=C1NCC2(CCCOC2)CN1"
workflow = rowan.submit_protein_cofolding_workflow(
initial_protein_sequences=[protein_seq],
initial_smiles_list=[ligand],
name="kinase-ligand cofolding",
model="chai_1r", # default is "boltz_2"; see note below for the full list
)
result = workflow.result()
top = result.predictions[0] # first CofoldingResult sample
print(f"pTM: {top.scores.ptm}") # predicted TM score (0-1)
print(f"interface pTM: {top.scores.iptm}")
Note: in rowan-python 3.2 the cofolding model strings are
chai_1r,boltz_1,boltz_2(default),boltz_2_1,openfold_3, anddecaf_boltz(there is noboltz_1x). Confidence lives onresult.scores/ each prediction's.scoresas.ptmand.iptm.
Workflow Management
List and Query Workflows
# List recent workflows (page is 0-indexed; default size=10)
workflows = rowan.list_workflows(size=10)
for wf in workflows:
print(f"{wf.name}: {wf.status.name}") # status is an int enum
# Filter by type / name substring / folder
pka_runs = rowan.list_workflows(workflow_type="pka", name_contains="phenol")
folder_runs = rowan.list_workflows(parent_uuid=folder.uuid)
# Retrieve specific workflow
workflow = rowan.retrieve_workflow("workflow-uuid")
Batch Operations
# Submit many workflows of one type at once. This is a thin loop over the generic
# submit_workflow: it skips the per-type input checks the submit_*_workflow helpers do,
# so pass workflow_data= for non-default settings.
workflows = rowan.batch_submit_workflow(
workflow_type="pka",
initial_smileses=["CCO", "CC(=O)O", "c1ccccc1O"],
)
# Non-blocking status poll: returns {uuid: status_int} (stjames.Status values)
statuses = rowan.batch_poll_status([wf.uuid for wf in workflows])
Folder Organization
# Create folder for project
folder = rowan.create_folder(name="Drug Discovery Project")
# Submit workflow to folder
workflow = rowan.submit_pka_workflow(
rowan.Molecule.from_smiles("CCO"),
name="compound pKa",
folder=folder, # or folder_uuid=folder.uuid (not both)
)
# List workflows in folder
folder_workflows = rowan.list_workflows(parent_uuid=folder.uuid)
Computational Methods
Rowan supports multiple levels of theory:
Neural Network Potentials:
- AIMNet2 (ωB97M-D3) - Fast and accurate
- Egret - Rowan's proprietary model
Semiempirical:
- GFN1-xTB, GFN2-xTB - Fast for large molecules
DFT:
- B3LYP, PBE, ωB97X variants
- Multiple basis sets available
Methods are automatically selected based on workflow type, or can be specified explicitly in workflow parameters.
Reference Documentation
For detailed API documentation, consult these reference files:
references/api_reference.md: Workflow class, submission functions, retrieval methods, the result patternreferences/workflow_types.md: The full set of workflow types with parameters - pKa, docking, cofolding, etc.references/molecule_handling.md: stjames.Molecule class - creating molecules from SMILES, XYZ, RDKitreferences/proteins_and_organization.md: Protein upload, folder management, project organizationreferences/results_interpretation.md: Understanding workflow outputs, confidence scores, validation
Common Patterns
Pattern 1: Property Prediction Pipeline
Submit everything first, then collect results — submission is non-blocking, result() blocks.
import rowan
smiles_list = ["CCO", "c1ccccc1O", "CC(=O)O"]
# Submit all pKa calculations (default 3D method -> build molecules from the SMILES)
workflows = [
rowan.submit_pka_workflow(rowan.Molecule.from_smiles(smi), name=f"pKa: {smi}")
for smi in smiles_list
]
# Collect results
for wf in workflows:
result = wf.result()
print(f"{wf.name}: pKa = {result.strongest_acid}")
Pattern 2: Virtual Screening
For screening a library against one target, prefer the dedicated batch-docking workflow over a Python loop.
import rowan
protein = rowan.upload_protein(name="Drug Target", file_path="target.pdb")
protein.sanitize()
pocket = [[x, y, z], [20.0, 20.0, 20.0]] # center, size (Å)
workflow = rowan.submit_batch_docking_workflow(
smiles_list=compound_library,
protein=protein,
pocket=pocket,
name="library screen",
)
result = workflow.result()
Pattern 3: Conformer-Based Analysis
import rowan
conf_wf = rowan.submit_conformer_search_workflow(
"C1CCCCC1", # any SMILES
name="conformer search",
)
result = conf_wf.result()
energies = result.get_energies() # relative energies, kcal/mol, ascending
print(f"Found {result.num_conformers} conformers")
print(f"Energy range: {energies[0]:.2f} to {energies[-1]:.2f} kcal/mol")
Best Practices
- Set API key via environment variable for security and convenience
- Use folders to organize related workflows
- Use
workflow.result()— it waits, fetches, and raises on failure in one call - Use batch functions (
batch_submit_workflow,submit_batch_docking_workflow) for many similar jobs - Cap spend with
max_credits=on any submission, and checkrowan.whoami().credits
Error Handling
workflow.result() raises rowan.WorkflowError if the workflow failed or was stopped, so wrap it:
import rowan
workflow = rowan.submit_pka_workflow(
rowan.Molecule.from_smiles("c1ccccc1O"), name="calculation", max_credits=10
) # input problems (e.g. a bare SMILES for a 3D method) raise ValueError at submit time
try:
result = workflow.result() # blocks until done; raises on failure
print(result.strongest_acid)
except rowan.WorkflowError as e:
# workflow failed/stopped — inspect workflow.logfile for details
print(f"Workflow failed: {e}")
print(workflow.logfile)
workflow.status is the int enum stjames.Status; check workflow.done() for a non-blocking finished test.
Additional Resources
- Web Interface: https://labs.rowansci.com
- Documentation: https://docs.rowansci.com
- Tutorials: https://docs.rowansci.com/tutorials
Part of the AlterLab Academic Skills suite.
Files (alterlab-academic-skills)
-
evals
-
evals.json 4.7 KB
{ "skill": "alterlab-rowan", "evals": [ { "id": "pka-prediction", "prompt": "Can you predict the pKa of phenol for me on Rowan? I don't have a local compute cluster, just give me the strongest acid pKa.", "expected_output": "Invokes alterlab-rowan. Submits a pKa workflow via rowan.submit_pka_workflow with a 3D structure built by rowan.Molecule.from_smiles('c1ccccc1O') (the default gxtb_wagen2026 method rejects a bare SMILES; a SMILES string only works with method='starling' or 'chemprop_nevolianis2025'), gets the typed result with workflow.result(), and reads result.strongest_acid. Emphasizes no local setup is needed (cloud compute).", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "Rowan" }, { "type": "behavior", "value": "Submits a Rowan pKa workflow with rowan.submit_pka_workflow and returns the strongest-acid pKa (result.strongest_acid) from cloud compute." } ] }, { "id": "conformer-search-geometry", "prompt": "I need a conformer search and geometry optimization for a small drug molecule, ideally with a fast neural network potential like AIMNet2 rather than full DFT. Can Rowan do that in the cloud?", "expected_output": "Invokes alterlab-rowan. Submits a conformer search (rowan.submit_conformer_search_workflow) and a geometry optimization (submit_basic_calculation_workflow with tasks=['optimize']), reading back the conformer energies (result.get_energies()) and the optimized result.molecule. Notes Rowan exposes AIMNet2 (neural network potential) as a fast level of theory alongside semiempirical xTB and DFT.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "AIMNet2" }, { "type": "behavior", "value": "Submits conformer search and geometry optimization workflows and mentions selectable methods (NNP/xTB/DFT)." } ] }, { "id": "docking-vina", "prompt": "Dock this candidate inhibitor into the EGFR kinase (PDB 1M17) binding pocket on Rowan with AutoDock Vina and return the docking score. I want it cloud-side, no local install.", "expected_output": "Invokes alterlab-rowan. Creates the protein (rowan.create_protein_from_pdb_id code='1M17'), defines the pocket as [[center],[size]] coordinate lists, submits rowan.submit_docking_workflow with the ligand as a 3D structure (rowan.Molecule.from_smiles(...); a bare SMILES raises ValueError) and docking_settings=rowan.VinaSettings(...), and reads the best score from result.scores[0].score (or result.best_pose). Frames it as cloud AutoDock Vina docking with automatic resource allocation.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "docking" }, { "type": "behavior", "value": "Uses submit_docking_workflow against a Rowan-hosted protein with a defined pocket and returns a docking score." } ] }, { "id": "protein-cofolding", "prompt": "I have a kinase sequence and a ligand SMILES and want an AI-predicted protein-ligand complex structure using Chai-1, with the pTM and interface pTM confidence scores.", "expected_output": "Invokes alterlab-rowan. Submits rowan.submit_protein_cofolding_workflow with initial_protein_sequences and initial_smiles_list, model='chai_1r' (default boltz_2; also boltz_1, boltz_2_1, openfold_3), and reads the confidence scores from result.predictions[0].scores.ptm and .iptm. Frames it as AI cofolding structure prediction on the Rowan cloud.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "cofolding" }, { "type": "behavior", "value": "Calls submit_protein_cofolding_workflow with model='chai_1r' and reports the pTM / interface-pTM (iptm) confidence scores from the typed result." } ] }, { "id": "near-miss-molecular-dynamics", "prompt": "I want to run a 50 ns explicit-solvent molecular dynamics trajectory of this protein-ligand complex with OpenMM and then analyze the RMSD and binding-pose stability over time.", "expected_output": "Does NOT invoke this skill; defers to alterlab-molecular-dynamics. The user wants a time-resolved explicit-solvent MD trajectory (OpenMM) with RMSD/stability analysis, which is the molecular-dynamics skill's job. Rowan's cloud workflows (quantum chemistry, docking, cofolding, managed protein MD) are not a local OpenMM run with MDAnalysis trajectory analysis, which is what the user asked for.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-molecular-dynamics" } ] } ] }
-
-
references
-
api_reference.md 12.9 KB
# Rowan API Reference ## Table of Contents 1. [Workflow Class](#workflow-class) 2. [Workflow Submission Functions](#workflow-submission-functions) 3. [Workflow Retrieval Functions](#workflow-retrieval-functions) 4. [Batch Operations](#batch-operations) 5. [Utility Functions](#utility-functions) --- ## Workflow Class The `Workflow` class represents a submitted computational job. ### Attributes | Attribute | Type | Description | |-----------|------|-------------| | `uuid` | str | Unique identifier | | `name` | str | User-assigned name | | `status` | `stjames.Status` | Int enum: `QUEUED=0, RUNNING=1, COMPLETED_OK=2, FAILED=3, STOPPED=4, AWAITING_QUEUE=5, DRAFT=6, PREEMPTED=7` | | `created_at` | datetime | Submission timestamp | | `started_at` | datetime | Execution start (None if not started) | | `completed_at` | datetime | Completion timestamp (None if not finished) | | `elapsed` | float | Wall-clock seconds | | `credits_charged` | float | Credits consumed | | `data` | dict | Raw workflow results (lazy-loaded; prefer `.result()`) | | `workflow_type` | str | Type of calculation | | `parent_uuid` | str | Parent folder UUID | | `logfile` | str | Log text (useful for diagnosing failures) | **Note:** `data` is not loaded by default. Use `workflow.result()` for typed access; it fetches for you. ### The result pattern (preferred) ```python # Block until done, fetch, and return a typed WorkflowResult. # Raises rowan.WorkflowError if the workflow FAILED or was STOPPED. result = workflow.result(wait=True, poll_interval=5) # Non-blocking: return whatever is currently available partial = workflow.result(wait=False) ``` The returned object is a typed `WorkflowResult` subclass (e.g. `pKaResult`, `DockingResult`) with attribute/property access — see `results_interpretation.md`. ### Methods #### Status ```python status = workflow.get_status() # -> stjames.Status (int enum), re-fetches from API if workflow.done(): # non-blocking finished check (also: is_finished()) print("Done!") # Deprecated: wait_for_result(poll_interval=5) just blocks and returns self. # Prefer workflow.result(). (There is no `timeout` argument.) workflow.fetch_latest(in_place=True) # refresh fields from API in place ``` #### Data Operations ```python # Update metadata (only these fields) workflow.update(name="New name", notes="Additional notes", starred=True) workflow.delete() # delete workflow workflow.delete_data() # delete only results data, keep metadata # Downloads (availability depends on workflow type) workflow.download_dcd_files(output_dir="trajectories/") # MD trajectories workflow.download_msa_files(output_dir="msa/") # MSA / cofolding ``` > There is no `workflow.download_sdf_file` and no `workflow.error_message`. To get poses as structures, use the typed `DockingResult` (`result.best_pose`, `result.get_poses()`); for failure details read `workflow.logfile`. #### Execution Control ```python workflow.stop() # stop a running workflow workflow.submit_draft() # start a workflow submitted with is_draft=True ``` --- ## Workflow Submission Functions ### Molecule input `initial_molecule` accepts a `rowan.Molecule`, an `stjames.Molecule`, or an RDKit `Chem.Mol`/`RWMol`. **Geometry-based workflows (basic calculation, docking, 3D pKa methods, most others) require 3D coordinates**: a bare SMILES `str` — or an RDKit mol without a conformer — raises `ValueError` at submit time, so build one with `rowan.Molecule.from_smiles(smi)` (embeds 3D). SMILES strings are accepted only where the method builds geometry itself: pKa with `method="starling"` / `"chemprop_nevolianis2025"`, conformer search with the default OpenConf/ETKDG generator, and functions whose parameter is `initial_smiles` (e.g. `submit_macropka_workflow`, `submit_solubility_workflow`). ### Generic Submission ```python rowan.submit_workflow( workflow_type: str, # one of the supported types, e.g. "pka", "docking", "conformer_search" workflow_data: dict | None = None, # workflow-specific parameters initial_molecule: MoleculeInput | None = None, # SMILES / stjames.Molecule / RDKit Mol initial_smiles: str | None = None, name: str | None = None, folder_uuid: str | Folder | None = None, max_credits: int | None = None, webhook_url: str | None = None, is_draft: bool = False, ) -> Workflow ``` ### Specialized Submission Functions All functions return a `Workflow` object. All accept `initial_molecule` as SMILES/stjames/RDKit, plus the common `name`, `folder_uuid`/`folder`, `max_credits` (int), `webhook_url`, and `is_draft` parameters (omitted below for brevity). #### Property Prediction ```python # pKa calculation (micro-pKa) rowan.submit_pka_workflow( initial_molecule, # 3D Molecule for 3D methods; SMILES str for SMILES methods pka_range: tuple = (2, 12), method: str = "gxtb_wagen2026", # default (3D); "aimnet2_wagen2024" (3D); # "chemprop_nevolianis2025", "starling" (SMILES) solvent: str | None = "water", ) # Macroscopic pKa (microstates, pI, logD/solubility vs pH) rowan.submit_macropka_workflow( initial_smiles, # NOTE: takes initial_smiles min_pH: int = 0, max_pH: int = 14, min_charge: int = -2, max_charge: int = 2, compute_aqueous_solubility: bool = True, ) rowan.submit_redox_potential_workflow(initial_molecule, ...) rowan.submit_solubility_workflow(initial_molecule, ...) rowan.submit_fukui_workflow(initial_molecule, ...) # Bond dissociation energy rowan.submit_bde_workflow(initial_molecule, ...) # see workflow_types.md for bond-selection params ``` #### Molecular Modeling ```python # Basic calculation: task-driven (NOT a workflow_type string) rowan.submit_basic_calculation_workflow( initial_molecule, tasks: list[str], # e.g. ["optimize"], ["energy"], ["optimize", "frequencies"] method: str | None = None, # e.g. "aimnet2_wb97md3", "gfn2_xtb" basis_set: str | None = None, # for DFT preset: str | None = None, # "general_nnp" | "organic_nnp" | "rapid_semiempirical" | "routine_dft" | "careful_dft" ) rowan.submit_conformer_search_workflow( initial_molecule, final_method: str = "aimnet2_wb97md3", transition_state: bool = False, ) rowan.submit_tautomer_search_workflow(initial_molecule, ...) # Coordinate / dihedral scan rowan.submit_scan_workflow(initial_molecule, ...) # scan settings in workflow_data; see workflow_types.md # Transition-state search (double-ended / FSM) rowan.submit_double_ended_ts_search_workflow(initial_molecule, ...) # Intrinsic reaction coordinate rowan.submit_irc_workflow(initial_molecule, ...) ``` > There is no `submit_ts_search_workflow` or `submit_dihedral_scan_workflow` in v3 — use `submit_double_ended_ts_search_workflow` and `submit_scan_workflow`. #### Protein-Ligand Workflows ```python # Docking — pocket is [[center], [size]], a list of two 3-vectors (Å) rowan.submit_docking_workflow( protein: str | Protein, # UUID or Protein object pocket: list[list[float]], # [[cx, cy, cz], [sx, sy, sz]] initial_molecule, # 3D structure required (SMILES str raises ValueError) docking_settings=None, # rowan.VinaSettings(executable=, scoring_function=, # exhaustiveness=, max_poses=) or rowan.GninaSettings(...) # executable= / scoring_function= / exhaustiveness= / max_poses= still accepted but deprecated do_csearch: bool = False, do_optimization: bool = False, do_pose_refinement: bool = True, ) # Batch docking — note argument order: smiles_list, protein, pocket rowan.submit_batch_docking_workflow( smiles_list: list[str], protein: str | Protein, pocket: list[list[float]], executable: str = "vina", # rowan-python 3.2 defaults scoring_function: str = "vinardo", exhaustiveness: float = 8, ) # Protein cofolding rowan.submit_protein_cofolding_workflow( initial_protein_sequences: list[str] | None = None, initial_dna_sequences: list[str] | None = None, initial_rna_sequences: list[str] | None = None, initial_smiles_list: list[str] | None = None, ligand_binding_affinity_index: int | None = None, use_msa_server: bool = True, use_potentials: bool = False, num_samples: int | None = None, compute_strain: bool = False, do_pose_refinement: bool = False, model: str = "boltz_2", # "chai_1r" | "boltz_1" | "boltz_2" | "boltz_2_1" | "openfold_3" | "decaf_boltz" ) ``` #### Spectroscopy & Analysis ```python rowan.submit_nmr_workflow(initial_molecule, ...) # NMR shifts rowan.submit_ion_mobility_workflow(initial_molecule, ...) # collision cross-section rowan.submit_descriptors_workflow(initial_molecule, ...) # molecular descriptors ``` --- ## Workflow Retrieval Functions ```python # Retrieve single workflow by UUID workflow = rowan.retrieve_workflow(uuid: str) -> Workflow # Retrieve multiple workflows workflows = rowan.retrieve_workflows(uuids: list) -> list[Workflow] # List workflows with filtering workflows = rowan.list_workflows( parent_uuid: str = None, # Filter by folder name_contains: str = None, # Filter by name (substring) status: int = None, # stjames.Status int value (e.g. 2 == COMPLETED_OK) workflow_type: str = None, # e.g., "pka", "docking" starred: bool = None, public: bool = None, page: int = 0, # 0-indexed pagination size: int = 10 # Results per page ) -> list[Workflow] ``` --- ## Batch Operations ```python # Submit multiple workflows of one type at once workflows = rowan.batch_submit_workflow( workflow_type: str, # workflow type for all workflow_data: dict | None = None, initial_molecules: list | None = None, # Molecule / stjames.Molecule / RDKit (3D) or dicts initial_smileses: list[str] | None = None, names: list[str] | None = None, folder_uuid: str | Folder | None = None, max_credits: int | None = None, ) -> list[Workflow] # Poll status of multiple workflows (non-blocking) statuses = rowan.batch_poll_status( uuids: list # List of workflow UUIDs ) -> dict[str, int] # {uuid: stjames.Status int} ``` --- ## Utility Functions ```python # Get current user info user = rowan.whoami() -> User # user.username, user.email, user.credits, user.weekly_credits # Convert SMILES to stjames.Molecule mol = rowan.smiles_to_stjames(smiles: str) -> Molecule # Get API key from environment api_key = rowan.get_api_key() -> str # Low-level API client (context manager wrapping httpx) with rowan.api_client() as client: ... ``` > There is no `rowan.molecule_lookup` (name -> SMILES) in v3. Resolve names to SMILES with an external tool (e.g. RDKit, PubChem) before submitting. --- ## User Class Returned by `rowan.whoami()`. ### Attributes | Attribute | Type | Description | |-----------|------|-------------| | `username` | str | Username | | `email` | str | Email address | | `firstname` | str | First name | | `lastname` | str | Last name | | `credits` | float | Available credits | | `weekly_credits` | float | Weekly credit allocation | | `organization` | dict | Organization details | | `individual_subscription` | dict | Subscription information | --- ## Error Handling The library raises `rowan.WorkflowError` when you request the result of a failed/stopped workflow, and `requests.HTTPError` on transport/auth/validation failures from the API. (There are no `RowanAPIError` / `AuthenticationError` / `RateLimitError` classes.) ```python import rowan import requests try: workflow = rowan.submit_pka_workflow(rowan.Molecule.from_smiles("c1ccccc1O"), name="test") result = workflow.result() # raises WorkflowError if it failed/stopped print(result.strongest_acid) except rowan.WorkflowError as e: print(f"Workflow failed: {e}") # inspect workflow.logfile for details except requests.HTTPError as e: print(f"API error: {e}") # bad key, invalid input, etc. ``` --- ## Common Patterns ### Waiting for Multiple Workflows ```python import rowan import time workflows = [rowan.submit_pka_workflow(rowan.Molecule.from_smiles(smi)) for smi in smiles_list] # Poll until all finished (non-blocking) while not all(wf.done() for wf in workflows): time.sleep(10) # Collect results for wf in workflows: try: print(wf.result(wait=False).strongest_acid) except rowan.WorkflowError as e: print(f"{wf.name}: {e}") ``` ### Organizing Workflows in Folders ```python import rowan # Top-level folder + subfolder project = rowan.create_folder("Drug Discovery") lead_folder = rowan.create_folder("Lead Compounds", parent_uuid=project.uuid) # Submit to a specific folder workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), name="Lead 1 pKa", folder=lead_folder, # or folder_uuid=lead_folder.uuid ) ``` > `rowan.create_project(name)` exists for top-level projects, but `create_folder` does not accept a `project_uuid` argument — nest folders with `parent_uuid`. -
molecule_handling.md 9.6 KB
# Rowan Molecule Handling Reference ## Overview Rowan uses the `stjames` library for molecular representations. The `stjames.Molecule` class provides a unified interface for creating molecules from various sources and accessing molecular properties. ## Table of Contents 1. [Creating Molecules](#creating-molecules) 2. [Molecule Attributes](#molecule-attributes) 3. [Geometry Methods](#geometry-methods) 4. [File I/O](#file-io) 5. [Conversion Functions](#conversion-functions) 6. [Working with Atoms](#working-with-atoms) --- ## Creating Molecules ### From SMILES ```python import stjames # Simple SMILES mol = stjames.Molecule.from_smiles("CCO") # Ethanol mol = stjames.Molecule.from_smiles("c1ccccc1") # Benzene # With stereochemistry mol = stjames.Molecule.from_smiles("C[C@H](O)[C@@H](O)C") # meso-2,3-butanediol # Charged molecules mol = stjames.Molecule.from_smiles("[NH4+]") # Ammonium mol = stjames.Molecule.from_smiles("CC(=O)[O-]") # Acetate # Complex drug-like molecules mol = stjames.Molecule.from_smiles("CC(=O)Oc1ccccc1C(=O)O") # Aspirin ``` **Note:** `from_smiles()` automatically generates 3D coordinates. Its only argument is the SMILES string — charge and multiplicity are inferred from the SMILES (see below for how to set them explicitly). --- ### From XYZ String ```python import stjames xyz_string = """3 Water molecule O 0.000 0.000 0.117 H 0.000 0.757 -0.469 H 0.000 -0.757 -0.469""" mol = stjames.Molecule.from_xyz(xyz_string) ``` **XYZ format with optional metadata in comment line:** ``` N_atoms charge=0 multiplicity=1 energy=-76.4 comment Element X Y Z ... ``` --- ### From XYZ File ```python import stjames mol = stjames.Molecule.from_file("structure.xyz") ``` --- ### From Extended XYZ (EXTXYZ) Extended XYZ supports additional properties like forces and cell parameters. ```python import stjames extxyz_string = """3 Lattice="10.0 0.0 0.0 0.0 10.0 0.0 0.0 0.0 10.0" Properties=species:S:1:pos:R:3:forces:R:3 energy=-76.4 O 0.000 0.000 0.117 0.01 0.02 0.03 H 0.000 0.757 -0.469 0.00 0.00 0.00 H 0.000 -0.757 -0.469 0.00 0.00 0.00""" mol = stjames.Molecule.from_extxyz(extxyz_string) # Access cell information if mol.cell: print(f"Cell: {mol.cell.lattice_vectors}") ``` --- ### From RDKit Molecule ```python import stjames from rdkit import Chem from rdkit.Chem import AllChem # Create RDKit molecule with 3D coordinates rdkit_mol = Chem.MolFromSmiles("CCO") rdkit_mol = Chem.AddHs(rdkit_mol) AllChem.EmbedMolecule(rdkit_mol) AllChem.MMFFOptimizeMolecule(rdkit_mol) # Convert to stjames mol = stjames.Molecule.from_rdkit(rdkit_mol) ``` --- ### Specifying Charge and Multiplicity `from_smiles()` takes **only** the SMILES — it does not accept `charge`/`multiplicity` keywords. Encode charge in the SMILES itself, or set the fields explicitly via `from_xyz(...)` (which does accept them) or by assigning on the model. ```python import stjames # Charge encoded in SMILES mol = stjames.Molecule.from_smiles("CC(=O)[O-]") # acetate, charge -1 inferred mol = stjames.Molecule.from_smiles("[NH4+]") # ammonium, charge +1 inferred # Explicit charge/multiplicity when building from coordinates mol = stjames.Molecule.from_xyz(o2_xyz, charge=0, multiplicity=3) # triplet O2 # Or set on the model (it is a pydantic Molecule) mol = stjames.Molecule.from_smiles("CCO") mol.charge = 1 mol.multiplicity = 2 ``` --- ## Molecule Attributes ### Basic Properties ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Charge and spin print(f"Charge: {mol.charge}") # 0 print(f"Multiplicity: {mol.multiplicity}") # 1 # Number of atoms print(f"Number of atoms: {len(mol.atoms)}") ``` ### Computed Properties (after calculation) ```python # After running a calculation print(f"Energy: {mol.energy} Hartree") print(f"Dipole: {mol.dipole}") # (x, y, z) in Debye # Atomic properties print(f"Mulliken charges: {mol.mulliken_charges}") print(f"Mulliken spin densities: {mol.mulliken_spin_densities}") ``` ### Thermochemistry (after frequency calculation) ```python # After frequency calculation print(f"ZPE: {mol.zero_point_energy} Hartree") print(f"Thermal enthalpy correction: {mol.thermal_enthalpy_corr}") print(f"Thermal free-energy correction: {mol.thermal_free_energy_corr}") print(f"Gibbs free energy: {mol.gibbs_free_energy} Hartree") # property (alias for sum_energy_free_energy) ``` ### Vibrational Modes (after frequency calculation) ```python for mode in mol.vibrational_modes: print(f"Frequency: {mode.frequency} cm⁻¹") ``` ### Periodic Cell ```python if mol.cell: print(f"Lattice vectors: {mol.cell.lattice_vectors}") print(f"Is periodic: True") ``` --- ## Geometry Methods **Atom indices here are 1-based** (the methods reject 0). This differs from `mol.atoms[...]` list access, which is 0-based. ### Distance Between Atoms ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Distance between atoms 1 and 2 (1-indexed), in Angstroms d = mol.distance(1, 2) print(f"C-C bond length: {d:.3f} Å") ``` ### Angle Between Three Atoms ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Angle formed by atoms 1-2-3 (C-C-O); degrees=True by default angle = mol.angle(1, 2, 3, degrees=True) print(f"C-C-O angle: {angle:.1f}°") angle_rad = mol.angle(1, 2, 3, degrees=False) # radians ``` ### Dihedral Angle ```python import stjames mol = stjames.Molecule.from_smiles("CCCC") # Dihedral angle for atoms 1-2-3-4 (1-indexed) dihedral = mol.dihedral(1, 2, 3, 4, degrees=True) print(f"Dihedral: {dihedral:.1f}°") # positive_domain defaults to True (0 to 360); pass False for -180..180 dihedral_signed = mol.dihedral(1, 2, 3, 4, degrees=True, positive_domain=False) ``` ### Translation ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Translate by vector translated = mol.translated([1.0, 0.0, 0.0]) # Move 1 Å in x direction ``` --- ## File I/O ### Export to XYZ ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Get XYZ string xyz_str = mol.to_xyz(comment="Ethanol optimized structure") print(xyz_str) # Write to file mol.to_xyz(comment="Ethanol", out_file="ethanol.xyz") ``` ### Export to Extended XYZ ```python import stjames mol = stjames.Molecule.from_smiles("CCO") # Include energy in comment xyz_str = mol.to_xyz(comment=f"energy={mol.energy}") ``` --- ## Conversion Functions ### SMILES to Molecule (Rowan Utility) ```python import rowan # Quick conversion using Rowan's utility mol = rowan.smiles_to_stjames("CCO") ``` > There is no built-in name-to-SMILES lookup (no `rowan.molecule_lookup`). Resolve common names to SMILES with an external source (PubChem, a local dictionary, or RDKit), then submit: > > ```python > import rowan > aspirin = "CC(=O)Oc1ccccc1C(=O)O" > workflow = rowan.submit_pka_workflow(aspirin, name="Aspirin pKa") > ``` --- ## Working with Atoms ### Atom Class Each atom in `mol.atoms` is an `stjames.Atom`. Coordinates live in `position` (a 3-vector); there are no `.x/.y/.z` attributes. Use the `atomic_symbol` property for the element symbol. ```python import stjames mol = stjames.Molecule.from_smiles("CCO") for i, atom in enumerate(mol.atoms): x, y, z = atom.position print(f"Atom {i}: {atom.atomic_symbol} at ({x:.3f}, {y:.3f}, {z:.3f})") ``` ### Atom Attributes | Attribute | Type | Description | |-----------|------|-------------| | `atomic_number` | int | Atomic number | | `atomic_symbol` | str | Element symbol (e.g. "C", "O", "H"), derived property | | `position` | list[float] | `[x, y, z]` coordinates (Å) | | `mass` | float | Atomic mass (amu) | ### Getting Coordinates as Array ```python import numpy as np mol = stjames.Molecule.from_smiles("CCO") # mol.coordinates is already a list of [x, y, z]; convert to ndarray positions = np.array(mol.coordinates) print(f"Positions shape: {positions.shape}") # (N_atoms, 3) ``` --- ## Common Patterns ### Batch Molecule Creation ```python import stjames smiles_list = ["CCO", "CC(=O)O", "c1ccccc1", "c1ccccc1O"] molecules = [] for smi in smiles_list: try: mol = stjames.Molecule.from_smiles(smi) molecules.append(mol) except Exception as e: print(f"Failed to create molecule from {smi}: {e}") print(f"Created {len(molecules)} molecules") ``` ### Modifying Charge/Multiplicity ```python import stjames # Create neutral molecule mol = stjames.Molecule.from_smiles("c1ccccc1") # Make a cation-doublet copy by assigning on the model (from_smiles takes no charge kwarg) mol_cation = mol.model_copy() mol_cation.charge = 1 mol_cation.multiplicity = 2 ``` ### Combining Geometry Analysis ```python import stjames mol = stjames.Molecule.from_smiles("CCCC") # Analyze butane conformer (geometry methods are 1-indexed) print("Butane geometry analysis:") print(f" C1-C2 bond: {mol.distance(1, 2):.3f} Å") print(f" C2-C3 bond: {mol.distance(2, 3):.3f} Å") print(f" C3-C4 bond: {mol.distance(3, 4):.3f} Å") print(f" C-C-C angle: {mol.angle(1, 2, 3, degrees=True):.1f}°") print(f" C-C-C-C dihedral: {mol.dihedral(1, 2, 3, 4, degrees=True):.1f}°") ``` --- ## Electron Sanity Check The `stjames.Molecule` class can validate that charge and multiplicity are consistent with the number of electrons via `mol.check_electron_sanity()`. Set the spin state when building from coordinates (`from_xyz(..., multiplicity=3)`) or by assigning `mol.multiplicity`: ```python import stjames # Triplet oxygen, built from coordinates with explicit multiplicity mol = stjames.Molecule.from_xyz(o2_xyz, charge=0, multiplicity=3) mol.check_electron_sanity() # raises if charge/multiplicity are inconsistent ``` The validation ensures: - Number of electrons = sum(atomic_numbers) - charge - Multiplicity is compatible with electron count (odd/even) -
proteins_and_organization.md 9.5 KB
# Rowan Proteins and Organization Reference ## Table of Contents 1. [Protein Management](#protein-management) 2. [Folder Organization](#folder-organization) 3. [Project Management](#project-management) 4. [Best Practices](#best-practices) --- ## Protein Management ### Protein Class The `Protein` class represents a protein structure stored on Rowan. **Attributes:** | Attribute | Type | Description | |-----------|------|-------------| | `uuid` | str | Unique identifier | | `name` | str | User-assigned name | | `data` | str | PDB structure data (lazy-loaded) | | `sanitized` | bool | Whether structure has been cleaned | | `pocket` | list[list[float]] | Stored binding pocket `[[center], [size]]`, if set | | `public` | bool | Public visibility flag | | `ancestor_uuid` | str | Containing folder UUID | | `created_at` | datetime | Upload timestamp | --- ### Upload Protein from File ```python import rowan # Upload PDB file protein = rowan.upload_protein( name="EGFR Kinase", file_path="protein.pdb" ) print(f"Protein UUID: {protein.uuid}") print(f"Name: {protein.name}") ``` --- ### Create from PDB ID Fetch structure directly from RCSB PDB database. ```python import rowan # Download from PDB protein = rowan.create_protein_from_pdb_id( name="EGFR Kinase (1M17)", code="1M17" ) print(f"Created protein: {protein.uuid}") ``` --- ### Retrieve Protein ```python import rowan # Get by UUID protein = rowan.retrieve_protein("protein-uuid") # List all proteins proteins = rowan.list_proteins() for p in proteins: print(f"{p.name}: {p.uuid}") # Filter by name substring proteins = rowan.list_proteins(name_contains="EGFR") ``` --- ### Sanitize Protein Clean up protein structure (remove waters, artifacts, fix residues). ```python import rowan protein = rowan.create_protein_from_pdb_id("Target", "1M17") # Sanitize the structure protein.sanitize() # Check status print(f"Sanitized: {protein.sanitized}") ``` **Sanitization performs:** - Removes non-protein molecules (waters, ligands, ions) - Fixes missing atoms in residues - Resolves alternate conformations - Standardizes residue names --- ### Update Protein Metadata ```python import rowan protein = rowan.retrieve_protein("protein-uuid") # Update name protein.update(name="EGFR Kinase Domain") # Define binding pocket — [[center], [size]] (Å), same format as docking protein.update( pocket=[[10.0, 20.0, 30.0], [20.0, 20.0, 20.0]] ) ``` --- ### Download Protein Structure ```python import rowan protein = rowan.retrieve_protein("protein-uuid") # Load structure data protein.refresh() # Fetches PDB data if not loaded # Download to file protein.download_pdb_file("output.pdb") # Or access data directly pdb_content = protein.data ``` --- ### Delete Protein ```python import rowan protein = rowan.retrieve_protein("protein-uuid") protein.delete() ``` --- ## Folder Organization ### Folder Class Folders provide hierarchical organization for workflows. **Attributes:** | Attribute | Type | Description | |-----------|------|-------------| | `uuid` | str | Unique identifier | | `name` | str | Folder name | | `parent_uuid` | str | Parent folder UUID (None for root) | | `starred` | bool | Starred status | | `public` | bool | Public visibility | | `created_at` | datetime | Creation timestamp | --- ### Create Folder ```python import rowan # Create root folder folder = rowan.create_folder(name="Drug Discovery Project") # Create subfolder subfolder = rowan.create_folder( name="Lead Compounds", parent_uuid=folder.uuid ) ``` --- ### Retrieve Folder ```python import rowan # Get by UUID folder = rowan.retrieve_folder("folder-uuid") # List all folders folders = rowan.list_folders() for f in folders: print(f"{f.name}: {f.uuid}") # Filter (name substring) folders = rowan.list_folders(name_contains="Project", starred=True) ``` --- ### Update Folder ```python import rowan folder = rowan.retrieve_folder("folder-uuid") # Rename folder.update(name="New Name") # Move to different parent folder.update(parent_uuid="new-parent-uuid") # Star folder folder.update(starred=True) ``` --- ### Print Folder Tree Visualize folder hierarchy. ```python import rowan # Print the tree rooted at a given folder UUID (uuid is required) rowan.print_folder_tree("folder-uuid") # Or call it on a Folder object folder = rowan.retrieve_folder("folder-uuid") folder.print_folder_tree() ``` Output: ``` 📁 Drug Discovery Project ├── 📁 Lead Compounds │ ├── 📄 Lead 1 pKa (completed) │ └── 📄 Lead 2 pKa (completed) └── 📁 Backup Series └── 📄 Backup 1 conformers (running) ``` --- ### Delete Folder **Warning:** Deleting a folder removes all workflows inside! ```python import rowan folder = rowan.retrieve_folder("folder-uuid") folder.delete() # Deletes folder and all contents ``` --- ### Submit Workflow to Folder ```python import rowan import stjames folder = rowan.create_folder(name="pKa Calculations") mol = stjames.Molecule.from_smiles("CCO") workflow = rowan.submit_pka_workflow( initial_molecule=mol, name="Ethanol pKa", folder_uuid=folder.uuid # Organize in folder ) ``` --- ### List Workflows in Folder ```python import rowan folder = rowan.retrieve_folder("folder-uuid") workflows = rowan.list_workflows(parent_uuid=folder.uuid) # list_workflows uses parent_uuid for wf in workflows: print(f"{wf.name}: {wf.status.name}") ``` --- ## Project Management ### Project Class Projects are top-level containers for organizing folders and workflows. **Attributes:** | Attribute | Type | Description | |-----------|------|-------------| | `uuid` | str | Unique identifier | | `name` | str | Project name | | `root_folder_uuid` | str | UUID of the project's root folder (nest folders under this) | | `created_at` | datetime | Creation timestamp | --- ### Create Project ```python import rowan project = rowan.create_project(name="Cancer Drug Discovery") print(f"Project UUID: {project.uuid}") ``` --- ### Retrieve Project ```python import rowan # Get by UUID project = rowan.retrieve_project("project-uuid") # List all projects projects = rowan.list_projects() for p in projects: print(f"{p.name}: {p.uuid}") # Get default project default = rowan.default_project() ``` --- ### Update Project ```python import rowan project = rowan.retrieve_project("project-uuid") project.update(name="Renamed Project") ``` --- ### Delete Project **Warning:** Deletes all folders and workflows in project! ```python import rowan project = rowan.retrieve_project("project-uuid") project.delete() ``` --- ### Create Folder Under a Project `create_folder` does not take a `project_uuid` — nest folders under a project's root folder via `parent_uuid`. ```python import rowan project = rowan.create_project("Drug Discovery") folder = rowan.create_folder( name="Phase 1 Compounds", parent_uuid=project.root_folder_uuid, ) ``` --- ## Best Practices ### Organizing a Drug Discovery Campaign ```python import rowan # Create project structure project = rowan.create_project("EGFR Inhibitor Campaign") root = project.root_folder_uuid # Create organized folders under the project's root folder target_folder = rowan.create_folder("Target Preparation", parent_uuid=root) hit_folder = rowan.create_folder("Hit Finding", parent_uuid=root) lead_folder = rowan.create_folder("Lead Optimization", parent_uuid=root) # Upload and prepare protein protein = rowan.create_protein_from_pdb_id("EGFR", "1M17") protein.sanitize() # Define binding site — [[center], [size]] (Å) pocket = [[10.0, 20.0, 30.0], # from crystal ligand [20.0, 20.0, 20.0]] # Submit docking workflows to hit folder for smiles in hit_compounds: workflow = rowan.submit_docking_workflow( protein=protein.uuid, pocket=pocket, initial_molecule=rowan.Molecule.from_smiles(smiles), # 3D required; bare SMILES raises name=f"Dock: {smiles[:20]}", folder_uuid=hit_folder.uuid ) ``` ### Reusing Proteins Across Workflows ```python import rowan # Upload once protein = rowan.upload_protein("My Target", "target.pdb") protein.sanitize() # Save UUID for later use protein_uuid = protein.uuid # Use in multiple workflows for compound in compounds: workflow = rowan.submit_docking_workflow( protein=protein_uuid, # Reuse same protein pocket=pocket, initial_molecule=compound, name=f"Dock: {compound.name}" ) ``` ### Folder Naming Conventions ```python import rowan from datetime import datetime # Include date in folder name date_str = datetime.now().strftime("%Y%m%d") folder = rowan.create_folder(f"{date_str}_Lead_Optimization") # Include project phase folder = rowan.create_folder("Phase2_pKa_Calculations") # Include target name folder = rowan.create_folder("EGFR_Conformer_Search") ``` ### Cleaning Up Old Workflows ```python import rowan import stjames from datetime import datetime, timedelta # Find old completed workflows (status filter takes the Status int value) old_cutoff = datetime.now() - timedelta(days=30) workflows = rowan.list_workflows(status=stjames.Status.COMPLETED_OK.value) for wf in workflows: if wf.completed_at and wf.completed_at < old_cutoff: # Delete data but keep metadata wf.delete_data() # Or delete entirely # wf.delete() ``` ### Monitoring Credit Usage ```python import rowan # Check before submitting user = rowan.whoami() print(f"Available credits: {user.credits}") # Cap spend per workflow (max_credits is an int) workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), name="pKa calculation", max_credits=10, ) ``` -
results_interpretation.md 12.8 KB
# Rowan Results Interpretation Reference ## Table of Contents 1. [Accessing Workflow Results](#accessing-workflow-results) 2. [Property Prediction Results](#property-prediction-results) 3. [Molecular Modeling Results](#molecular-modeling-results) 4. [Docking Results](#docking-results) 5. [Cofolding Results](#cofolding-results) 6. [Validation and Quality Assessment](#validation-and-quality-assessment) --- ## Accessing Workflow Results ### Basic Pattern Use `workflow.result()` — it blocks, fetches, and returns a typed `WorkflowResult` with attribute access. It raises `rowan.WorkflowError` on failure, so you usually do not check status by hand. ```python import rowan workflow = rowan.submit_pka_workflow(rowan.Molecule.from_smiles("c1ccccc1O"), name="test") try: result = workflow.result() # blocks until done; raises on failure/stop print(result.strongest_acid) # typed attribute, NOT workflow.data['...'] except rowan.WorkflowError as e: print(f"Failed: {e}") # see workflow.logfile for details ``` > The raw `workflow.data` dict still exists, but field names there are not stable across workflow types — prefer the typed `result` object below. Each result object also exposes `.data` (the raw dict) if you need it. ### Workflow Status Values `workflow.status` is the integer enum `stjames.Status` (use `.name` for a label, `workflow.done()` for a finished check): | Status | Value | Description | |--------|-------|-------------| | `QUEUED` | 0 | Queued, waiting for resources | | `RUNNING` | 1 | Currently executing | | `COMPLETED_OK` | 2 | Successfully finished | | `FAILED` | 3 | Execution failed | | `STOPPED` | 4 | Manually stopped | | `AWAITING_QUEUE` | 5 | Waiting to enter the queue | | `DRAFT` | 6 | Submitted as a draft (call `submit_draft()`) | | `PREEMPTED` | 7 | Preempted by the scheduler | ### Credits Charged ```python # After completion print(f"Credits used: {workflow.credits_charged}") ``` --- ## Property Prediction Results ### pKa Results ```python workflow = rowan.submit_pka_workflow(rowan.Molecule.from_smiles("c1ccccc1O"), name="pKa") result = workflow.result() # pKaResult strongest_acid = result.strongest_acid # most acidic pKa strongest_base = result.strongest_base # most basic pKa (if applicable) # Protonation states found for microstate in result.conjugate_acids: print("conjugate acid:", microstate) for microstate in result.conjugate_bases: print("conjugate base:", microstate) ``` For macroscopic pKa / pH-dependent speciation, use `submit_macropka_workflow` and read `result.pka_values`, `result.microstates`, `result.microstate_weights_by_ph`, and `result.isoelectric_point`. **Interpretation:** - pKa < 0: Strong acid - pKa 0-7: Acidic - pKa 7-14: Basic - pKa > 14: Very weak acid --- ### Redox Potential Results ```python result = workflow.result() # RedoxPotentialResult print(f"Oxidation: {result.oxidation_potential} V") print(f"Reduction: {result.reduction_potential} V") ``` **Interpretation:** - Higher oxidation potential = harder to oxidize - Lower reduction potential = harder to reduce - Compare to reference compounds for context --- ### Solubility Results ```python result = workflow.result() # SolubilityResult # result.solubilities holds the predicted solubility entries # (per solvent / temperature, as log S in mol/L) for entry in result.solubilities: print(entry) ``` **Interpretation:** - Log S > -1: High solubility (>0.1 M) - Log S -1 to -3: Medium solubility - Log S < -3: Low solubility (<0.001 M) --- ### Fukui Index Results ```python result = workflow.result() # FukuiResult f_plus = result.fukui_positive # f+ : susceptibility to nucleophilic attack (per atom) f_minus = result.fukui_negative # f- : susceptibility to electrophilic attack (per atom) f_zero = result.fukui_zero # f0 : radical attack (per atom) print(f"Global electrophilicity index: {result.global_electrophilicity_index}") for i, (fp, fm, fz) in enumerate(zip(f_plus, f_minus, f_zero)): print(f"Atom {i}: f+ = {fp:.3f}, f- = {fm:.3f}, f0 = {fz:.3f}") ``` **Interpretation:** - High f+ = susceptible to nucleophilic attack - High f- = susceptible to electrophilic attack - High f0 = susceptible to radical attack --- ## Molecular Modeling Results ### Geometry Optimization Results ```python result = workflow.result() # BasicCalculationResult final_mol = result.molecule # stjames.Molecule with optimized coords final_energy = result.energy # Hartree print(f"Final energy: {final_energy:.6f} Hartree") # Also available: result.molecules (all steps), result.dipole, result.charges, # result.frequencies (if a frequencies task was requested), # result.optimization_energies() (energy at each optimization step) ``` --- ### Conformer Search Results ```python result = workflow.result() # ConformerSearchResult energies = result.get_energies() # relative energies (kcal/mol), ascending print(f"Found {result.num_conformers} conformers") for i, rel_energy in enumerate(energies): print(f"Conformer {i}: ΔE = {rel_energy:.2f} kcal/mol") conformers = result.get_conformers() # list of stjames.Molecule lowest = result.get_conformer(0) # lowest-energy conformer # Also: result.sasa, result.polar_sasa, result.radii_of_gyration ``` **Interpretation:** - Conformers within 3 kcal/mol are typically accessible at room temperature - Lowest energy conformer may not be most populated in solution - Consider ensemble averaging for properties --- ### Frequency Calculation Results Run with `tasks=["optimize", "frequencies"]` (or `["frequencies"]`). The typed `BasicCalculationResult` exposes the frequencies and a molecule carrying thermochemistry. ```python result = workflow.result() # BasicCalculationResult frequencies = result.frequencies # cm⁻¹ (negative values = imaginary modes) mol = result.molecule # Check for imaginary frequencies imaginary = [f for f in frequencies if f < 0] if imaginary: print(f"Warning: {len(imaginary)} imaginary frequency/frequencies") else: print("Structure is a true minimum") # Thermochemistry lives on the molecule (Hartree) print(f"ZPE: {mol.zero_point_energy}") print(f"Gibbs free energy: {mol.gibbs_free_energy}") ``` **Interpretation:** - 0 imaginary frequencies = minimum - 1 imaginary frequency = transition state - >1 imaginary frequencies = higher-order saddle point --- ### Scan Results A coordinate/dihedral scan is submitted with `rowan.submit_scan_workflow(...)`. Read the per-point energies from the result; the raw points live in `result.data`. Convert relative energies to kcal/mol with the factor below to find the rotation barrier. ```python result = workflow.result() # ScanResult data = result.data # raw scan points (angles + energies, Hartree) ``` --- ## Docking Results ### Single Docking Results ```python result = workflow.result() # DockingResult # result.scores is a list of DockingScore (sorted best-first) best = result.scores[0] print(f"Best docking score: {best.score:.2f} kcal/mol") # more negative = better print(f"Ligand strain: {best.strain} kcal/mol") print(f"RMSD: {best.rmsd}, PoseBusters valid: {best.posebusters_valid}") for i, s in enumerate(result.scores): print(f"Pose {i}: score = {s.score:.2f} kcal/mol") # Top pose / all poses as structures best_pose = result.best_pose # stjames.Molecule all_poses = result.get_poses() # list of stjames.Molecule ``` **Interpretation:** - Vina scores typically -12 to -6 kcal/mol for drug-like molecules - More negative = stronger predicted binding - Ligand strain > 3 kcal/mol suggests unlikely binding mode --- ### Batch Docking Results `submit_batch_docking_workflow` returns a single workflow whose result (`BatchDockingResult`) exposes `result.scores` for the screened library. Each entry carries the same `DockingScore` fields shown above (`score`, `strain`, `rmsd`, `posebusters_valid`); rank ligands by their best score. Use `result.data` for the full raw aggregation. **Scoring Function Differences:** - **Vina**: Original scoring function - **Vinardo**: Updated parameters, often more accurate --- ## Cofolding Results ### Protein-Ligand Complex Prediction ```python result = workflow.result() # ProteinCofoldingResult # One CofoldingResult per sample; take the first top = result.predictions[0] print(f"pTM: {top.scores.ptm:.3f}") # predicted TM score (0-1) print(f"interface pTM: {top.scores.iptm:.3f}") print(f"avg LDDT: {top.scores.avg_lddt}") print(f"PoseBusters valid: {top.posebusters_valid}") # Binding-affinity prediction (if computed via ligand_binding_affinity_index) if result.affinity_score: print(f"Affinity pred_value: {result.affinity_score.pred_value}") # Predicted structures are stored by UUID; download via the workflow predicted_uuid = top.predicted_structure_uuid ``` > The score attributes are `scores.ptm` and `scores.iptm` (not `ptm_score` / `interface_ptm`). `result.scores` gives the aggregate scores; `result.predictions` gives per-sample results. **Confidence Score Interpretation:** | Score Range | Confidence | Recommendation | |-------------|------------|----------------| | > 0.8 | High | Likely accurate | | 0.5 - 0.8 | Moderate | Use with caution | | < 0.5 | Low | Validate experimentally | --- ### Interpreting Low Confidence Low confidence may indicate: - Novel protein fold not well-represented in training data - Flexible or disordered regions - Unusual ligand (large, charged, or complex) - Multiple possible binding modes **Recommendations for low confidence:** 1. Try multiple models (Chai-1, Boltz-1, Boltz-2) 2. Compare predictions across models 3. Use docking for binding pose refinement 4. Validate with experimental data if available --- ## Validation and Quality Assessment ### Cross-Validation with Multiple Methods ```python import rowan energies = {} for method in ["gfn2_xtb", "aimnet2_wb97md3"]: wf = rowan.submit_basic_calculation_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), # 3D input required tasks=["optimize"], method=method, name=f"opt_{method}", ) energies[method] = wf.result().energy for method, energy in energies.items(): print(f"{method}: {energy:.6f} Hartree") ``` ### Consistency Checks ```python # For pKa (pass a pKaResult) def validate_pka(result): pka = result.strongest_acid if pka is not None and (pka < -5 or pka > 20): print("Warning: pKa outside typical range") # For docking (pass a DockingResult) def validate_docking(result): best = result.scores[0] if best.score > 0: print("Warning: Positive docking score suggests poor binding") if best.strain and best.strain > 5: print("Warning: High ligand strain - binding mode may be unrealistic") ``` ### Experimental Validation Guidelines | Property | Validation Method | |----------|-------------------| | pKa | Potentiometric titration, UV spectroscopy | | Solubility | Shake-flask, nephelometry | | Docking pose | X-ray crystallography, cryo-EM | | Binding affinity | SPR, ITC, fluorescence polarization | | Cofolding | X-ray, NMR, HDX-MS | --- ## Common Issues and Solutions ### Issue: Workflow Failed ```python import stjames if workflow.status == stjames.Status.FAILED: print(workflow.logfile) # diagnostic log; there is no workflow.error_message # Common causes: # - Invalid SMILES # - Molecule too large # - Convergence failure # - Credit limit exceeded ``` (`workflow.result()` raises `rowan.WorkflowError` for failed/stopped workflows, so wrapping it in try/except is usually simpler than checking status.) ### Issue: Unexpected Results 1. **pKa off by >2 units**: Check tautomers, ensure correct protonation state 2. **Docking gives positive scores**: Ligand may not fit binding site 3. **Optimization not converged**: Try different starting geometry 4. **High strain energy**: Conformer may be wrong ### Issue: Missing Attributes ```python # Typed result attributes may be None when not computed energy = getattr(result, "energy", None) if energy is None: print("Energy not available") ``` --- ## Data Export Patterns ### Export to CSV ```python import pandas as pd import rowan rows = [] for wf in workflows: try: result = wf.result(wait=False) # don't block on still-running jobs except rowan.WorkflowError: continue rows.append({ "name": wf.name, "pka": result.strongest_acid, "credits": wf.credits_charged, }) pd.DataFrame(rows).to_csv("results.csv", index=False) ``` ### Export Structures ```python # Poses come back as stjames.Molecule objects you can write to XYZ result = docking_workflow.result() result.best_pose.to_xyz(out_file="best_pose.xyz") # Download MD trajectory files (for MD workflows) workflow.download_dcd_files(output_dir="trajectories/") ``` -
workflow_types.md 14.3 KB
# Rowan Workflow Types Reference In the snippets below, `mol` is a 3D structure such as `mol = rowan.Molecule.from_smiles("CCO")`. Since rowan-python 3.x, geometry-based workflows reject a bare SMILES string (`ValueError`); only functions whose parameter is `initial_smiles` (e.g. macro-pKa, solubility), SMILES-based pKa methods, and default-settings conformer search take SMILES directly. ## Table of Contents 1. [Property Prediction Workflows](#property-prediction-workflows) 2. [Molecular Modeling Workflows](#molecular-modeling-workflows) 3. [Protein-Ligand Workflows](#protein-ligand-workflows) 4. [Spectroscopy Workflows](#spectroscopy-workflows) 5. [Advanced Workflows](#advanced-workflows) --- ## Property Prediction Workflows ### pKa Calculation Predict acid dissociation constants. ```python workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), # default gxtb_wagen2026 needs 3D; name="pKa calculation" # pass a SMILES str only with ) # method="starling"/"chemprop_nevolianis2025" ``` **Result (`pKaResult`):** - `result.strongest_acid`: pKa of most acidic proton - `result.strongest_base`: pKa of most basic site - `result.conjugate_acids`, `result.conjugate_bases`: protonation microstates For macroscopic pKa, pH-dependent speciation, and isoelectric point use `submit_macropka_workflow` (`MacropKaResult`: `pka_values`, `microstates`, `isoelectric_point`). --- ### Redox Potential Calculate oxidation/reduction potentials. ```python workflow = rowan.submit_redox_potential_workflow( mol, name="redox potential" ) ``` **Result (`RedoxPotentialResult`):** - `result.oxidation_potential`: oxidation potential (V) - `result.reduction_potential`: reduction potential (V) --- ### Solubility Prediction Predict aqueous and nonaqueous solubility. ```python workflow = rowan.submit_solubility_workflow( "CC(=O)Nc1ccc(O)cc1", # takes initial_smiles (a SMILES string), not a Molecule method="fastsolv", # default; also "kingfisher", "esol" name="solubility" ) ``` **Result (`SolubilityResult`):** - `result.solubilities`: predicted solubility entries (log S, per solvent/temperature) --- ### Hydrogen-Bond Basicity Calculate H-bond acceptor strength. ```python workflow = rowan.submit_hydrogen_bond_basicity_workflow( rowan.Molecule.from_smiles("CC(=O)C"), # structure input (no SMILES str) name="H-bond basicity" ) ``` **Output:** - `hb_basicity`: pKBHX value --- ### Bond Dissociation Energy (BDE) Calculate homolytic bond dissociation energies. Select bonds to break by the atom(s) whose bonds to fragment (`atoms=`), or with the convenience flags `all_CH` / `all_CX`. There is no `bond_indices` argument. ```python workflow = rowan.submit_bde_workflow( mol, all_CH=True, # break all C-H bonds; or atoms=[3, 7] name="BDE calculation" ) ``` **Result (`BDEResult`):** bond dissociation energies (kcal/mol) per fragmented bond, in `result.data`. --- ### Fukui Indices Calculate reactivity indices for nucleophilic/electrophilic attack. ```python workflow = rowan.submit_fukui_workflow( mol, name="Fukui indices" ) ``` **Result (`FukuiResult`):** - `result.fukui_positive` (f+): susceptibility to nucleophilic attack, per atom - `result.fukui_negative` (f-): susceptibility to electrophilic attack, per atom - `result.fukui_zero` (f0): radical attack, per atom - `result.global_electrophilicity_index` --- ### Spin States Calculate relative energies of different spin multiplicities. ```python workflow = rowan.submit_spin_states_workflow( mol, name="spin states" ) ``` **Output:** - `spin_state_energies`: Energy of each multiplicity - `ground_state`: Lowest energy multiplicity --- ### ADME-Tox Predictions Predict absorption, distribution, metabolism, excretion, and toxicity. ```python workflow = rowan.submit_admet_workflow( mol, name="ADMET" ) ``` **Output:** - Various ADMET descriptors including: - `logP`, `logD` - `herg_inhibition` - `cyp_inhibition` - `bioavailability` - `bbb_permeability` --- ## Molecular Modeling Workflows ### Single-Point Energy Calculate energy at fixed geometry. ```python workflow = rowan.submit_basic_calculation_workflow( mol, tasks=["energy"], name="single point" ) ``` **Result (`BasicCalculationResult`):** - `result.energy`: Total energy (Hartree) - `result.dipole`: Dipole moment vector - `result.charges`: Atomic partial charges --- ### Geometry Optimization Optimize molecular geometry to minimum energy. ```python workflow = rowan.submit_basic_calculation_workflow( mol, tasks=["optimize"], name="optimization" ) ``` **Result (`BasicCalculationResult`):** - `result.molecule`: Optimized structure (stjames.Molecule) - `result.energy`: Final energy (Hartree) - `result.optimization_energies()`: Energy at each step --- ### Vibrational Frequencies Calculate IR/Raman frequencies and thermochemistry. ```python workflow = rowan.submit_basic_calculation_workflow( mol, tasks=["optimize", "frequencies"], name="frequency" ) ``` **Result (`BasicCalculationResult`):** - `result.frequencies`: Vibrational frequencies (cm⁻¹; negative = imaginary) - `result.molecule.zero_point_energy`, `.thermal_enthalpy_corr`, `.thermal_free_energy_corr`, `.gibbs_free_energy`: thermochemistry (Hartree) --- ### Conformer Search Generate and optimize conformer ensemble. ```python workflow = rowan.submit_conformer_search_workflow( mol, name="conformer search" ) ``` **Result (`ConformerSearchResult`):** - `result.num_conformers`: number of unique conformers - `result.get_energies()`: relative energies (kcal/mol), ascending - `result.get_conformers()` / `result.get_conformer(0)`: conformers as `stjames.Molecule` - `result.sasa`, `result.polar_sasa`, `result.radii_of_gyration` --- ### Tautomer Search Enumerate and rank tautomers. ```python workflow = rowan.submit_tautomer_search_workflow( mol, name="tautomer search" ) ``` **Result (`TautomerResult`):** ranked tautomers with relative energies and Boltzmann populations (see `result.data`). --- ### Coordinate / Dihedral Scan Scan a bond, angle, or torsion energy surface with `submit_scan_workflow`. The scan coordinate and range are passed via the scan settings (see the API docs); there is no `submit_dihedral_scan_workflow`. ```python workflow = rowan.submit_scan_workflow( mol, name="dihedral scan", # scan_settings define the coordinate (atoms), start/stop, and number of steps ) ``` **Result (`ScanResult`):** per-point angles and energies (Hartree) in `result.data`; convert relative energies to kcal/mol (× 627.509) for the rotation barrier. --- ### Multistage Optimization Progressive refinement with multiple methods. ```python workflow = rowan.submit_multistage_optimization_workflow( mol, name="multistage opt" ) ``` Progressively refines the geometry (e.g. xTB → NNP → DFT) using built-in stage settings. **Result:** optimized structure plus per-stage energies in `result.data`. --- ### Transition State Search Find a transition-state geometry. v3 exposes a double-ended (reactant/product) TS search and an IRC workflow; there is no single-`submit_ts_search_workflow`. ```python # Double-ended TS search (provide reactant/product endpoints in the inputs) workflow = rowan.submit_double_ended_ts_search_workflow( mol, name="TS search" ) # Intrinsic reaction coordinate from a TS irc = rowan.submit_irc_workflow(ts_mol, name="IRC") ``` **Output:** transition-state structure, the imaginary frequency, and barrier information in `result.data`. --- ### Strain Calculation Calculate ligand strain energy. ```python workflow = rowan.submit_strain_workflow( mol, name="strain" ) ``` **Output:** - `strain_energy`: Conformational strain (kcal/mol) - `reference_energy`: Lowest energy conformer energy --- ### Electronic Properties (orbitals) Calculate frontier-orbital energies and related electronic properties. (The workflow type is `electronic_properties`; there is no `orbitals` type.) ```python workflow = rowan.submit_electronic_properties_workflow( mol, name="electronic properties" ) ``` **Output:** - HOMO / LUMO energies and HOMO-LUMO gap - additional electronic descriptors in `result.data` --- ## Protein-Ligand Workflows ### Docking Dock ligand to protein binding site. ```python workflow = rowan.submit_docking_workflow( protein=protein_uuid, # UUID or Protein object pocket=[[10.0, 20.0, 30.0], # center (Å) [20.0, 20.0, 20.0]], # box size (Å) initial_molecule=mol, docking_settings=rowan.VinaSettings( # loose executable=/scoring_function=/ executable="vina", # exhaustiveness= kwargs are deprecated scoring_function="vinardo", # "vina" or "vinardo" exhaustiveness=8, ), do_csearch=False, # conformer search before docking do_optimization=False, # optimize conformers do_pose_refinement=True, # refine poses (default True) name="docking" ) ``` **Result (`DockingResult`):** - `result.scores`: list of `DockingScore` (sorted best-first), each with `.score` (kcal/mol), `.strain`, `.rmsd`, `.posebusters_valid`, `.pose` - `result.best_pose`: top pose as `stjames.Molecule` - `result.get_poses()`: all poses as molecules --- ### Batch Docking Screen multiple ligands against one target. ```python workflow = rowan.submit_batch_docking_workflow( smiles_list=["CCO", "c1ccccc1", "CC(=O)O"], # note: smiles_list is first protein=protein_uuid, pocket=[[cx, cy, cz], [sx, sy, sz]], executable="qvina2", scoring_function="vina", name="batch docking" ) ``` **Result (`BatchDockingResult`):** `result.scores` holds the per-ligand docking scores (same `DockingScore` fields as single docking); rank ligands by their best score. --- ### Protein Cofolding Predict protein-ligand complex structure using AI. ```python workflow = rowan.submit_protein_cofolding_workflow( initial_protein_sequences=["MSKGEELFT..."], initial_smiles_list=["CCO"], model="boltz_2", # "chai_1r" | "boltz_1" | "boltz_2" | "boltz_2_1" | "openfold_3" | "decaf_boltz" use_msa_server=True, # build an MSA (default True; improves accuracy) use_potentials=False, # apply physical-potential refinement num_samples=None, # number of predictions to generate compute_strain=False, # calculate ligand strain do_pose_refinement=False, name="cofolding" ) ``` **Models (rowan-python 3.2):** `chai_1r`, `boltz_1`, `boltz_2` (default), `boltz_2_1`, `openfold_3`, `decaf_boltz`. There is no `boltz_1x`. **Result (`ProteinCofoldingResult`):** - `result.predictions`: list of per-sample `CofoldingResult` - each prediction's `.scores.ptm` and `.scores.iptm` (predicted TM / interface confidence, 0-1), `.scores.avg_lddt` - `result.affinity_score` (if `ligand_binding_affinity_index` was set) - `result.predicted_structure_uuid` for the predicted structure --- ### Pose-Analysis MD Molecular dynamics simulation of docked pose. ```python workflow = rowan.submit_pose_analysis_md_workflow( # protein + docked pose inputs; see the function signature for required args name="pose MD" ) ``` **Output:** a short MD trajectory plus ligand RMSD / interaction analysis in `result.data`. > For full, long-timescale explicit-solvent MD trajectories and trajectory analysis, that is a separate molecular-dynamics tool, not this Rowan skill — Rowan's `pose_analysis_md` is a short pose-stability check. --- ## Spectroscopy Workflows ### NMR Prediction Predict NMR chemical shifts. ```python workflow = rowan.submit_nmr_workflow( initial_molecule=mol, name="NMR" ) ``` **Output:** - `h_shifts`: ¹H chemical shifts (ppm) - `c_shifts`: ¹³C chemical shifts (ppm) - `coupling_constants`: J-coupling values --- ### Ion Mobility Predict collision cross-section for mass spectrometry. ```python workflow = rowan.submit_ion_mobility_workflow( initial_molecule=mol, name="ion mobility" ) ``` **Output:** - `ccs`: Collision cross-section (Ų) - `conformer_ccs`: CCS per conformer --- ## Advanced Workflows ### Molecular Descriptors Calculate comprehensive descriptor set. ```python workflow = rowan.submit_descriptors_workflow( initial_molecule=mol, name="descriptors" ) ``` **Output:** - 2D descriptors (RDKit-based) - 3D descriptors (xTB-based) - Electronic descriptors --- ### MSA (Multiple Sequence Alignment) Generate MSA for protein sequences. ```python workflow = rowan.submit_msa_workflow( initial_protein_sequences=["MSKGEELFT..."], # not `sequences=` output_formats={"colabfold"}, # optional: colabfold | chai | boltz name="MSA" ) ``` **Output:** the alignment(s) in the requested format(s), accessible via `result.data` / the typed `MSAResult`. --- ### Protein Binder Design (BoltzGen) Design protein binders. ```python workflow = rowan.submit_protein_binder_design_workflow( # target sequence + hotspot residues; see the function signature name="binder design" ) ``` **Output:** - designed binder sequences and per-design confidence in `result.data` --- ## Workflow Parameters Reference ### Common Parameters All workflow submission functions accept: | Parameter | Type | Description | |-----------|------|-------------| | `name` | str | Workflow name (optional) | | `folder_uuid` / `folder` | str / Folder | Organize in folder | | `max_credits` | int | Credit limit | | `webhook_url` | str | URL Rowan POSTs to on completion | | `is_draft` | bool | Submit without starting (call `submit_draft()` later) | ### Method Selection For basic calculations, pass `tasks` plus `method`/`basis_set`/`preset` as direct keyword arguments (not a `workflow_data` dict): ```python workflow = rowan.submit_basic_calculation_workflow( mol, tasks=["optimize"], method="gfn2_xtb", # or e.g. "aimnet2_wb97md3" basis_set="def2-SVP", # for DFT # or instead use a preset: # preset="organic_nnp", # general_nnp | organic_nnp | rapid_semiempirical | routine_dft | careful_dft ) ``` **Method families:** - Neural network potentials (e.g. AIMNet2, Egret) - Semiempirical: `gfn1_xtb`, `gfn2_xtb` - DFT: hybrid/GGA functionals with selectable basis sets The exact accepted method strings come from `stjames.Method`; the `preset` argument is the simplest way to pick a sensible level of theory.
-
-
SKILL.md 15.2 KB
--- name: alterlab-rowan description: Drives the Rowan cloud quantum-chemistry platform via its Python API for computational chemistry — pKa prediction, geometry optimization, conformer searching, molecular property calculations, protein-ligand docking (AutoDock Vina), and AI protein cofolding (Chai-1, Boltz-1/2), with cloud compute and no local setup. Use when running DFT or semiempirical methods, neural network potentials (AIMNet2), molecular property or protein-ligand binding predictions, or automated computational chemistry pipelines. Part of the AlterLab Academic Skills suite. license: MIT allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*) compatibility: "Requires a Rowan account and API key (ROWAN_API_KEY); jobs run on Rowan's cloud and consume credits, and submitted structures are sent to Rowan's servers. rowan-python >= 3.2, Python >= 3.12." metadata: skill-author: AlterLab version: "1.2.0" last_updated: "2026-09-23" --- # Rowan: Cloud-Based Quantum Chemistry Platform ## Overview Rowan is a cloud-based computational chemistry platform that provides programmatic access to quantum chemistry workflows through a Python API. It enables automation of complex molecular simulations without requiring local computational resources or expertise in multiple quantum chemistry packages. **Key Capabilities:** - Molecular property prediction (pKa, redox potential, solubility, ADMET-Tox) - Geometry optimization and conformer searching - Protein-ligand docking with AutoDock Vina - AI-powered protein cofolding with Chai-1 and Boltz models - Access to DFT, semiempirical, and neural network potential methods - Cloud compute with automatic resource allocation **Why Rowan:** - No local compute cluster required - Unified API for dozens of computational methods - Results viewable in web interface at labs.rowansci.com - Automatic resource scaling ## When to Use This Skill Use this skill when the user wants to: - Predict pKa / macro-pKa, redox potentials, solubility, or other properties without local QM software - Run geometry optimizations, conformer searches, or single points with NNPs (AIMNet2, Egret), xTB, or DFT in the cloud - Dock ligands (Vina/GNINA) or co-fold protein–ligand complexes (Boltz, Chai-1, OpenFold3) as managed cloud jobs - Script and batch these jobs from Python (`rowan-python`), organized in folders with credit caps ### Does NOT Trigger | Scenario | Use Instead | |----------|-------------| | Running and analyzing a local OpenMM MD trajectory (RMSD/RMSF, contacts) | `alterlab-molecular-dynamics` | | Open-source, local diffusion docking with DiffDock (no cloud account) | `alterlab-diffdock` | | Running Boltz-2 or Chai-1 locally on your own GPU | `alterlab-boltz` or `alterlab-chai` | | Local conformers, descriptors, or RDKit force-field minimization | `alterlab-rdkit` | ## Installation and Authentication ### Installation Requires Python >= 3.12. This skill targets `rowan-python` 3.x (current 3.2.0 as of 2026-09; v2 had a different result API). ```bash uv pip install "rowan-python>=3.2" ``` Installing `rowan-python` also pulls in `stjames` (molecule/result models) and `rdkit`. ### Authentication Generate an API key at [labs.rowansci.com/account/api-keys](https://labs.rowansci.com/account/api-keys). **Option 1: Direct assignment** ```python import rowan rowan.api_key = "your_api_key_here" ``` **Option 2: Environment variable (recommended)** ```bash export ROWAN_API_KEY="your_api_key_here" ``` The API key is automatically read from `ROWAN_API_KEY` on module import. ### Verify Setup ```python import rowan # Check authentication user = rowan.whoami() print(f"Logged in as: {user.username}") print(f"Credits available: {user.credits}") ``` ## The Result Pattern (read this first) Every `submit_*_workflow` returns a `Workflow`. Do NOT read `workflow.data[...]` by hand and do NOT call the deprecated `wait_for_result()`. The v3 idiom is a single call: ```python mol = rowan.Molecule.from_smiles("c1ccccc1O") # 3D structure for the default 3D method workflow = rowan.submit_pka_workflow(mol, name="phenol pKa") result = workflow.result() # blocks until done, returns a typed WorkflowResult print(result.strongest_acid) # typed attribute access, not a dict key ``` Key facts: - `workflow.result(wait=True, poll_interval=5)` blocks, fetches, and raises `rowan.WorkflowError` if the workflow failed or was stopped. Use `wait=False` to grab whatever is ready without blocking. - `workflow.status` is the **integer** enum `stjames.Status` (`QUEUED=0, RUNNING=1, COMPLETED_OK=2, FAILED=3, STOPPED=4, AWAITING_QUEUE=5, DRAFT=6, PREEMPTED=7`), not a string. Use `workflow.done()` / `workflow.is_finished()` rather than comparing to `"completed"`. - **Geometry-based workflows now reject a bare SMILES string.** As of rowan-python 3.x, `submit_basic_calculation_workflow`, `submit_docking_workflow`, and any 3D pKa/conformer method call `require_coordinates`, which raises `ValueError` on a SMILES with no coordinates. Build a 3D molecule first: `mol = rowan.Molecule.from_smiles("CCO")` (or `stjames.Molecule.from_smiles(...)`, which auto-generates coordinates), then pass `mol`. A SMILES string is still accepted by SMILES-based methods (`submit_macropka_workflow`, and pKa with `method="starling"`/`"chemprop_nevolianis2025"`). `Molecule.from_smiles(smiles)` takes only the SMILES (no `charge=`/`multiplicity=` kwargs). ## Core Workflows ### 1. pKa Prediction Predict micro-pKa / acid dissociation constants: ```python import rowan # The default pKa method is now a 3D method, so build a molecule (bare SMILES is rejected). workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), # Phenol name="phenol pKa calculation", pka_range=(2, 12), # default method="gxtb_wagen2026", # default (g-xTB); "aimnet2_wagen2024" also 3D. # "starling" / "chemprop_nevolianis2025" take a SMILES string. ) result = workflow.result() print(f"Strongest acid pKa: {result.strongest_acid}") print(f"Strongest base pKa: {result.strongest_base}") ``` For macroscopic pKa, microstate populations vs. pH, isoelectric point, and logD/solubility-vs-pH, use `rowan.submit_macropka_workflow(...)` and read `result.pka_values`, `result.microstates`, `result.isoelectric_point`. ### 2. Conformer Search Generate and rank a conformer ensemble: ```python import rowan workflow = rowan.submit_conformer_search_workflow( "CCCC", # Butane name="butane conformer search", final_method="aimnet2_wb97md3", # NNP; default ) result = workflow.result() print(f"Found {result.num_conformers} conformers") for energy in result.get_energies(): # relative energies, kcal/mol print(f" ΔE = {energy:.2f} kcal/mol") lowest = result.get_conformer(0) # stjames.Molecule of the lowest-energy conformer ``` ### 3. Geometry Optimization `submit_basic_calculation_workflow` is task-driven: pass `tasks` (e.g. `["optimize"]`, `["energy"]`, `["optimize", "frequencies"]`), not a `workflow_type` string. ```python import rowan workflow = rowan.submit_basic_calculation_workflow( rowan.Molecule.from_smiles("CC(=O)O"), # Acetic acid (needs 3D coords; SMILES is rejected) tasks=["optimize"], preset="organic_nnp", # quick NNP preset; or set method=/basis_set= explicitly name="acetic acid optimization", ) result = workflow.result() print(f"Final energy: {result.energy} Hartree") optimized_mol = result.molecule # stjames.Molecule with optimized coordinates ``` ### 4. Protein-Ligand Docking Dock small molecules to protein targets. The pocket is `[[center_x, center_y, center_z], [size_x, size_y, size_z]]` in Angstroms — a list of two 3-vectors, NOT a dict. ```python import rowan # Create protein from a PDB ID (fetched from RCSB) protein = rowan.create_protein_from_pdb_id(name="EGFR kinase", code="1M17") protein.sanitize() # strip waters/ions, fix residues pocket = [[10.0, 20.0, 30.0], # center (Å) [20.0, 20.0, 20.0]] # box size (Å) workflow = rowan.submit_docking_workflow( protein=protein, # Protein object or its .uuid pocket=pocket, # 3D input required — a bare SMILES string raises ValueError initial_molecule=rowan.Molecule.from_smiles("Cc1ccc(NC(=O)c2ccc(CN3CCN(C)CC3)cc2)cc1"), # engine options go in docking_settings; the loose scoring_function=/exhaustiveness= # kwargs are deprecated (rowan.GninaSettings selects GNINA instead of Vina) docking_settings=rowan.VinaSettings(scoring_function="vinardo"), # or "vina" name="EGFR docking", ) result = workflow.result() best = result.scores[0] # DockingScore, sorted best-first print(f"Best docking score: {best.score} kcal/mol") best_pose = result.best_pose # stjames.Molecule of the top pose ``` ### 5. Protein Cofolding (AI Structure Prediction) Predict protein-ligand complex structures using AI models: ```python import rowan protein_seq = "MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVPSTAIREISLLKELNHPNIVKLLDVIHTENKLYLVFEFLHQDLKKFMDASALTGIPLPLIKSYLFQLLQGLAFCHSHRVLHRDLKPQNLLINTEGAIKLADFGLARAFGVPVRTYTHEVVTLWYRAPEILLGCKYYSTAVDIWSLGCIFAEMVTRRALFPGDSEIDQLFRIFRTLGTPDEVVWPGVTSMPDYKPSFPKWARQDFSKVVPPLDEDGRSLLSQMLHYDPNKRISAKAALAHPFFQDVTKPVPHLRL" ligand = "CCC(C)CN=C1NCC2(CCCOC2)CN1" workflow = rowan.submit_protein_cofolding_workflow( initial_protein_sequences=[protein_seq], initial_smiles_list=[ligand], name="kinase-ligand cofolding", model="chai_1r", # default is "boltz_2"; see note below for the full list ) result = workflow.result() top = result.predictions[0] # first CofoldingResult sample print(f"pTM: {top.scores.ptm}") # predicted TM score (0-1) print(f"interface pTM: {top.scores.iptm}") ``` > Note: in rowan-python 3.2 the cofolding model strings are `chai_1r`, `boltz_1`, `boltz_2` (default), `boltz_2_1`, `openfold_3`, and `decaf_boltz` (there is no `boltz_1x`). Confidence lives on `result.scores` / each prediction's `.scores` as `.ptm` and `.iptm`. ## Workflow Management ### List and Query Workflows ```python # List recent workflows (page is 0-indexed; default size=10) workflows = rowan.list_workflows(size=10) for wf in workflows: print(f"{wf.name}: {wf.status.name}") # status is an int enum # Filter by type / name substring / folder pka_runs = rowan.list_workflows(workflow_type="pka", name_contains="phenol") folder_runs = rowan.list_workflows(parent_uuid=folder.uuid) # Retrieve specific workflow workflow = rowan.retrieve_workflow("workflow-uuid") ``` ### Batch Operations ```python # Submit many workflows of one type at once. This is a thin loop over the generic # submit_workflow: it skips the per-type input checks the submit_*_workflow helpers do, # so pass workflow_data= for non-default settings. workflows = rowan.batch_submit_workflow( workflow_type="pka", initial_smileses=["CCO", "CC(=O)O", "c1ccccc1O"], ) # Non-blocking status poll: returns {uuid: status_int} (stjames.Status values) statuses = rowan.batch_poll_status([wf.uuid for wf in workflows]) ``` ### Folder Organization ```python # Create folder for project folder = rowan.create_folder(name="Drug Discovery Project") # Submit workflow to folder workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("CCO"), name="compound pKa", folder=folder, # or folder_uuid=folder.uuid (not both) ) # List workflows in folder folder_workflows = rowan.list_workflows(parent_uuid=folder.uuid) ``` ## Computational Methods Rowan supports multiple levels of theory: **Neural Network Potentials:** - AIMNet2 (ωB97M-D3) - Fast and accurate - Egret - Rowan's proprietary model **Semiempirical:** - GFN1-xTB, GFN2-xTB - Fast for large molecules **DFT:** - B3LYP, PBE, ωB97X variants - Multiple basis sets available Methods are automatically selected based on workflow type, or can be specified explicitly in workflow parameters. ## Reference Documentation For detailed API documentation, consult these reference files: - **`references/api_reference.md`**: Workflow class, submission functions, retrieval methods, the result pattern - **`references/workflow_types.md`**: The full set of workflow types with parameters - pKa, docking, cofolding, etc. - **`references/molecule_handling.md`**: stjames.Molecule class - creating molecules from SMILES, XYZ, RDKit - **`references/proteins_and_organization.md`**: Protein upload, folder management, project organization - **`references/results_interpretation.md`**: Understanding workflow outputs, confidence scores, validation ## Common Patterns ### Pattern 1: Property Prediction Pipeline Submit everything first, then collect results — submission is non-blocking, `result()` blocks. ```python import rowan smiles_list = ["CCO", "c1ccccc1O", "CC(=O)O"] # Submit all pKa calculations (default 3D method -> build molecules from the SMILES) workflows = [ rowan.submit_pka_workflow(rowan.Molecule.from_smiles(smi), name=f"pKa: {smi}") for smi in smiles_list ] # Collect results for wf in workflows: result = wf.result() print(f"{wf.name}: pKa = {result.strongest_acid}") ``` ### Pattern 2: Virtual Screening For screening a library against one target, prefer the dedicated batch-docking workflow over a Python loop. ```python import rowan protein = rowan.upload_protein(name="Drug Target", file_path="target.pdb") protein.sanitize() pocket = [[x, y, z], [20.0, 20.0, 20.0]] # center, size (Å) workflow = rowan.submit_batch_docking_workflow( smiles_list=compound_library, protein=protein, pocket=pocket, name="library screen", ) result = workflow.result() ``` ### Pattern 3: Conformer-Based Analysis ```python import rowan conf_wf = rowan.submit_conformer_search_workflow( "C1CCCCC1", # any SMILES name="conformer search", ) result = conf_wf.result() energies = result.get_energies() # relative energies, kcal/mol, ascending print(f"Found {result.num_conformers} conformers") print(f"Energy range: {energies[0]:.2f} to {energies[-1]:.2f} kcal/mol") ``` ## Best Practices 1. **Set API key via environment variable** for security and convenience 2. **Use folders** to organize related workflows 3. **Use `workflow.result()`** — it waits, fetches, and raises on failure in one call 4. **Use batch functions** (`batch_submit_workflow`, `submit_batch_docking_workflow`) for many similar jobs 5. **Cap spend with `max_credits=`** on any submission, and check `rowan.whoami().credits` ## Error Handling `workflow.result()` raises `rowan.WorkflowError` if the workflow failed or was stopped, so wrap it: ```python import rowan workflow = rowan.submit_pka_workflow( rowan.Molecule.from_smiles("c1ccccc1O"), name="calculation", max_credits=10 ) # input problems (e.g. a bare SMILES for a 3D method) raise ValueError at submit time try: result = workflow.result() # blocks until done; raises on failure print(result.strongest_acid) except rowan.WorkflowError as e: # workflow failed/stopped — inspect workflow.logfile for details print(f"Workflow failed: {e}") print(workflow.logfile) ``` `workflow.status` is the int enum `stjames.Status`; check `workflow.done()` for a non-blocking finished test. ## Additional Resources - **Web Interface**: https://labs.rowansci.com - **Documentation**: https://docs.rowansci.com - **Tutorials**: https://docs.rowansci.com/tutorials Part of the AlterLab Academic Skills suite.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.