rowan

Par mkurman · zorai

npx skills add https://github.com/mkurman/zorai --skill rowan

name: rowan description: Rowan est une plateforme de workflow en chimie computationnelle et modélisation moléculaire native du cloud avec une API Python. À utiliser pour les prédictions de pKa et macropKa, les ensembles de conformères et tautomères, l'amarrage et l'amarrage d'analogues, le corepliement protéine-ligand, la génération d'ASM, la dynamique moléculaire, la perméabilité, les workflows de descripteurs, et les tâches associées de modélisation de petites molécules ou de protéines. Idéale pour le criblage batch programmatique, les pipelines de chimie multi-étapes, et les workflows qui nécessiteraient autrement de maintenir une infrastructure HPC/GPU locale. license: Propriétaire (clé API requise) compatibility: Python 3.12+, clé API requise tags: [scientific-skills, rowan, api, cheminformatics, workflow, devops, python, simulation] metadata: skill-author: Rowan Science trigger-keywords: ["prédiction pKa", "amarrage moléculaire", "recherche de conformères", "workflow chimie", "découverte de médicaments", "SMILES", "structure protéique", "modélisation moléculaire batch", "chimie cloud"] -------|----------------|-------------------|-------| | Descripteurs | <1 min | 0,5–2 | Léger, bon pour le tri | | pKa (transition simple) | 2–5 min | 2–5 | Dépend de la taille de la molécule | | MacropKa (pH 0–14) | 5–15 min | 5–15 | Échantillonnage plus large, coût plus élevé | | Recherche de conformères | 3–10 min | 3–10 | La qualité de l'ensemble importe | | Recherche de tautomères | 2–5 min | 2–5 | Systèmes hétérocycliques | | Amarrage (ligand simple) | 5–20 min | 5–20 | Dépend de la taille de la poche, affinage | | Amarrage d'analogues (10–50 ligands) | 30–120 min | 30–100+ | Cadre de référence partagé | | Génération d'ASM | 5–30 min | 5–30 | Dépend de la longueur de la séquence | | Corepliement protéine-ligand | 15–60 min | 20–50+ | Prédiction de structure IA, très gourmande en GPU |

Démarrage rapide

uv pip install rowan-python
import rowan
rowan.api_key = "your_api_key_here"  # ou définir la variable d'env ROWAN_API_KEY

# Soumettre un workflow de descripteurs — complète en moins d'une minute
wf = rowan.submit_descriptors_workflow("CC(=O)Oc1ccccc1C(=O)O", name="aspirin")
result = wf.result()

print(result.descriptors['MW'])    # 180.16
print(result.descriptors['SLogP']) # 1.19
print(result.descriptors['TPSA'])  # 59.44

Si cela s'affiche sans erreur, vous êtes correctement configuré.

Installation

uv pip install rowan-python
# ou : pip install rowan-python

Gestion des utilisateurs et webhooks

Authentification

Définissez une clé API via une variable d'environnement (recommandé) :

export ROWAN_API_KEY="your_api_key_here"

Ou définissez-la directement en Python :

import rowan
rowan.api_key = "your_api_key_here"

Vérifiez l'authentification :

import rowan
user = rowan.whoami()  # Renvoie les infos utilisateur si authentifié
print(f"User: {user.email}")
print(f"Credits available: {user.credits_available_string}")

Gestion des secrets de webhook

Pour la vérification de la signature des webhooks, gérez les secrets via votre compte utilisateur :

import rowan

# Récupérez votre secret de webhook actuel (renvoie None s'il n'existe pas)
secret = rowan.get_webhook_secret()
if secret is None:
    secret = rowan.create_webhook_secret()
print(f"Secret key: {secret.secret}")

# Renouvelez votre secret (invalide l'ancien, crée un nouveau)
# À utiliser régulièrement pour la sécurité
new_secret = rowan.rotate_webhook_secret()
print(f"New secret created (old secret disabled): {new_secret.secret}")

# Vérifiez les signatures de webhooks entrants
is_valid = rowan.verify_webhook_secret(
    request_body=b"...",           # Corps brut de la requête (bytes)
    signature="X-Rowan-Signature", # Depuis l'en-tête de requête
    secret=secret.secret
)

Formats d'entrée de molécules

Rowan accepte les molécules aux formats suivants :

  • SMILES (préféré) : "CCO", "c1ccccc1O"
  • Motifs SMARTS (pour certains workflows) : sous-ensemble SMARTS pour la correspondance de sous-structure
  • InChI (si pris en charge dans votre version API) : "InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3"

L'API validera l'entrée et lèvera une rowan.ValidationError si une molécule ne peut pas être parsée. Utilisez toujours des SMILES canonicalisés pour la reproductibilité.

Conseil : Utilisez RDKit pour valider les SMILES avant la soumission :

from rdkit import Chem
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)
if mol is None:
    raise ValueError(f"Invalid SMILES: {smiles}")

Schéma d'utilisation principal

La plupart des tâches Rowan suivent le même schéma en trois étapes :

  1. Soumettre un workflow
  2. Attendre la fin (avec streaming optionnel)
  3. Récupérer les résultats typés avec des propriétés pratiques
import rowan

# 1. Soumettre — utiliser la fonction de workflow spécifique (pas la générique submit_workflow)
workflow = rowan.submit_descriptors_workflow(
    "CC(=O)Oc1ccccc1C(=O)O",
    name="aspirin descriptors",
)

# 2. & 3. Attendre et récupérer
result = workflow.result()  # Bloque jusqu'à la fin (par défaut : wait=True, poll_interval=5)
print(result.data)              # Dict brut
print(result.descriptors['MW']) # 180.16 — utiliser result.descriptors dict, pas result.molecular_weight

Pour les workflows à longue durée d'exécution, utilisez le streaming :

for partial in workflow.stream_result(poll_interval=5):
    print(f"Progress: {partial.complete}%")
    print(partial.data)

result() vs. stream_result()

Motif À utiliser quand Durée
result() Vous pouvez attendre le résultat complet <5 min généralement
stream_result() Vous voulez un retour de progression ou avez besoin de résultats partiels précoces >5 min, ou utilisation interactive

Directive : Utilisez result() pour les descripteurs, pKa. Utilisez stream_result() pour la recherche de conformères, l'amarrage, le corepliement.

Travail avec les résultats

L'API de Rowan inclut des objets de résultats de workflow typés avec des propriétés pratiques.

Utiliser les propriétés typées et .data

Les résultats ont deux schémas d'accès :

  1. Propriétés pratiques (recommandé en premier) : result.descriptors, result.best_pose, result.conformer_energies
  2. Fallback brut : result.data — dictionnaire brut depuis l'API

Exemple :

result = rowan.submit_descriptors_workflow(
    "CCO",
    name="ethanol",
).result()

# Propriété pratique (renvoie dict de tous les descripteurs) :
print(result.descriptors['MW'])   # 46.042
print(result.descriptors['SLogP'])  # -0.001
print(result.descriptors['TPSA'])   # 57.96

# Fallback de données brutes (descripteurs imbriqués sous la clé 'descriptors') :
print(result.data['descriptors'])
# {'MW': 46.042, 'SLogP': -0.001, 'TPSA': 57.96, 'nHBDon': 1.0, 'nHBAcc': 1.0, ...}

Remarque : DescriptorsResult n'a pas de propriété molecular_weight. Les clés de descripteur utilisent des noms courts (MW, SLogP, nHBDon) et non des noms verbeux.

Invalidation de cache

Certaines propriétés de résultat sont chargées tardivement (par ex., géométries de conformères, structures protéiques). Pour rafraîchir :

result.clear_cache()
new_structures = result.conformer_molecules  # Récupérées à nouveau

Projets, dossiers et organisation

Pour les campagnes non triviales, utilisez les projets et dossiers pour garder le travail organisé.

Projets

import rowan

# Créer un projet
project = rowan.create_project(name="CDK2 lead optimization")
rowan.set_project("CDK2 lead optimization")

# Tous les workflows suivants vont dans ce projet
wf = rowan.submit_descriptors_workflow("CCO", name="test compound")

# Récupérer plus tard
project = rowan.retrieve_project("CDK2 lead optimization")
workflows = rowan.list_workflows(project=project, size=50)

Dossiers

# Créer une structure de dossiers hiérarchique
folder = rowan.create_folder(name="docking/batch_1/screening")

wf = rowan.submit_docking_workflow(
    # ... paramètres d'amarrage ...
    folder=folder,
    name="compound_001",
)

# Lister les workflows dans un dossier
results = rowan.list_workflows(folder=folder)

Arbres de décision pour les workflows

pKa vs. MacropKa

Utilisez le pKa microscopique quand :

  • Vous avez besoin du pKa d'un seul groupe ionisable
  • Vous vous intéressez aux transitions acide-base et à la thermodynamique de protonation
  • La molécule a un ou deux sites ionisables
  • La rapidité est critique (plus rapide, moins de crédits)

Utilisez macropKa quand :

  • Vous avez besoin du comportement dépendant du pH sur une plage physiologiquement pertinente (par ex., 0–14)
  • Vous voulez une charge agrégée et des populations d'état de protonation sur le pH
  • La molécule a plusieurs groupes ionisables avec protonation couplée
  • Vous avez besoin de propriétés en aval comme la solubilité aqueuse à différents pH

Exemple de décision :

Phénol (pKa ~10) : Utiliser pKa microscopique
Amine (pKa ~9–10) : Utiliser pKa microscopique
Médicament multi-ionisable (N, O, groupe acide) : Utiliser macropKa
Évaluation ADME sur pH du tractus GI : Utiliser macropKa

Recherche de conformères vs. recherche de tautomères

Utilisez la recherche de conformères quand :

  • Une seule forme tautomère est connue
  • Vous avez besoin d'un ensemble 3D divers pour l'amarrage, la MD ou l'analyse SAR
  • Les liaisons rotatives dominent l'espace chimique

Utilisez la recherche de tautomères quand :

  • L'équilibre tautomère est incertain (par ex., hétérocycles, systèmes céto-énol)
  • Vous avez besoin de modéliser tous les isomères de protonation pertinents
  • Les calculs en aval (amarrage, pKa) dépendent de la forme tautomère

Workflow combiné :

# Étape 1 : Trouver le meilleur tautomère
taut_wf = rowan.submit_tautomer_search_workflow(
    initial_molecule="O=c1[nH]ccnc1",
    name="imidazole tautomers",
)
best_taut = taut_wf.result().best_tautomer

# Étape 2 : Générer des conformères à partir du meilleur tautomère
conf_wf = rowan.submit_conformer_search_workflow(
    initial_molecule=best_taut,
    name="imidazole conformers",
)

Amarrage vs. amarrage d'analogues vs. corepliement

Workflow À utiliser quand Entrée Sortie
Amarrage Ligand simple, poche connue Protéine + SMILES + coords poche Pose, score, dG
Amarrage d'analogues 5–100+ composés apparentés Protéine + liste SMILES + ligand référence Toutes les poses, alignées sur référence
Corepliement protéine-ligand Séquence + ligand, pas de structure cristalline Séquence protéine + SMILES Complexe lié prédit par ML

Catégories de workflows courantes

1. Descripteurs

Un point d'entrée léger pour le tri batch, l'analyse SAR ou les scripts exploratoires.

wf = rowan.submit_descriptors_workflow(
    "CC(=O)Oc1ccccc1C(=O)O",  # arg positionnel, accepte chaîne SMILES
    name="aspirin descriptors",
)

result = wf.result()
print(result.descriptors['MW'])    # 180.16
print(result.descriptors['SLogP']) # 1.19
print(result.descriptors['TPSA'])  # 59.44
print(result.data['descriptors'])
# {'MW': 180.16, 'SLogP': 1.19, 'TPSA': 59.44, 'nHBDon': 1.0, 'nHBAcc': 4.0, ...}

Clés de descripteur courantes :

Clé Description Plage médicament typique
MW Poids moléculaire (Da) <500 (Lipinski)
SLogP LogP calculé (lipophilie) -2 à +5
TPSA Surface polaire topologique (Ų) <140 pour biodisponibilité orale
nHBDon Nombre de donneurs de liaison H ≤5 (Lipinski)
nHBAcc Nombre d'accepteurs de liaison H ≤10 (Lipinski)
nRot Nombre de liaisons rotatives <10 pour médicaments oraux
nRing Nombre de cycles
nHeavyAtom Nombre d'atomes lourds
FilterItLogS Solubilité aqueuse estimée (LogS) >-4 préféré
Lipinski Passage Ro5 Lipinski (1.0) ou échec (0.0)

Le résultat contient des centaines de descripteurs moléculaires supplémentaires (BCUT, GETAWAY, WHIM, etc.) ; accédez à n'importe lequel via result.descriptors['key'].

2. pKa microscopique

Pour la thermodynamique d'état de protonation et le comportement acide/base d'une structure spécifique.

Deux méthodes sont disponibles :

Méthode Entrée Rapidité Couvre À utiliser quand
chemprop_nevolianis2025 Chaîne SMILES Rapide Déprotonation uniquement (bases conjuguées anioniques) Groupes acides uniquement ; criblage rapide
starling Chaîne SMILES Rapide Acide + base (protonation/déprotonation complète) La plupart des molécules ressemblant à des médicaments ; méthode SMILES préférée
aimnet2_wagen2024 (par défaut) Objet molécule 3D Plus lent, plus haute précision Acide + base Vous avez déjà une structure 3D (par ex. d'une recherche de conformères)
# Chemin rapide : entrée SMILES avec couverture acide+base complète (utiliser starling quand disponible)
wf = rowan.submit_pka_workflow(
    initial_molecule="c1ccccc1O",       # SMILES phénol ; param est initial_molecule, pas initial_smiles
    method="starling",   # méthode SMILES rapide, couvre acide+base ; chemprop_nevolianis2025 est déprotonation uniquement
    name="phenol pKa",
)

result = wf.result()
print(result.strongest_acid)    # 9.81 (pKa du site le plus acide)
print(result.conjugate_bases)   # liste de {pka, smiles, atom_index, ...} par site déprotonatable

3. MacropKa

Pour le comportement de protonation dépendant du pH sur une plage.

wf = rowan.submit_macropka_workflow(
    initial_smiles="CN1CCN(CC1)C2=NC=NC3=CC=CC=C32",  # imidazole
    min_pH=0,
    max_pH=14,
    min_charge=-2,  # par défaut
    max_charge=2,   # par défaut
    compute_aqueous_solubility=True,  # par défaut
    name="imidazole macropKa",
)

result = wf.result()
print(result.pka_values)               # liste des valeurs pKa
print(result.logd_by_ph)               # dict de {pH: logD}
print(result.aqueous_solubility_by_ph) # dict de {pH: solubilité}
print(result.isoelectric_point)        # point isoélectrique
print(result.data)
# {'pKa_values': [...], 'logD_by_pH': {...}, 'aqueous_solubility_by_pH': {...}, ...}

4. Recherche de conformères

Pour la génération d'ensemble 3D quand la qualité de l'ensemble importe.

wf = rowan.submit_conformer_search_workflow(
    initial_molecule="CCOC(=O)N1CCC(CC1)Oc1ncnc2ccccc12",
    num_conformers=50,  # Optionnel : surcharger la valeur par défaut
    name="conformer search",
)

result = wf.result()
print(result.conformer_energies)  # [0.0, 1.2, 2.5, ...]
print(result.conformer_molecules)  # Liste de molécules 3D
print(result.best_conformer)  # Conformère d'énergie minimale

5. Recherche de tautomères

Pour les hétérocycles et les systèmes où l'état tautomère affecte la modélisation en aval.

wf = rowan.submit_tautomer_search_workflow(
    initial_molecule="O=c1[nH]ccnc1",  # ou tautomère céto
    name="imidazolone tautomers",
)

result = wf.result()
print(result.best_tautomer)  # Chaîne SMILES la plus stable
print(result.tautomers)      # Liste des SMILES tautomères
print(result.molecules)      # Liste des objets molécule

6. Amarrage

Pour l'amarrage protéine-ligand avec affinage de pose optionnel et génération de conformères.

# Télécharger protéine une fois, réutiliser dans plusieurs workflows
protein = rowan.upload_protein(
    name="CDK2",
    file_path="cdk2.pdb",
)

# Définir la poche de liaison
pocket = {
    "center": [10.5, 24.2, 31.8],
    "size": [18.0, 18.0, 18.0],
}

# Soumettre l'amarrage
wf = rowan.submit_docking_workflow(
    protein=protein,
    pocket=pocket,
    initial_molecule="CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1",
    do_pose_refinement=True,
    do_conformer_search=True,
    name="lead docking",
)

result = wf.result()
print(result.scores)  # Scores d'amarrage (kcal/mol)
print(result.best_pose)  # Objet Mol avec coordonnées 3D
print(result.data)  # Dict résultat brut

Conseils de préparation protéique :

  • Les fichiers PDB doivent être relativement propres (supprimer l'eau/hétéroatomes sauf si intentionné)
  • Utiliser le même objet protéine sur une série d'amarrage pour la cohérence
  • Si vous avez un ID PDB, utiliser rowan.create_protein_from_pdb_id() à la place

7. Amarrage d'analogues

Pour placer une série de composés dans un contexte de liaison partagé.

# Série d'analogues (par ex., campagne SAR)
analogues = [
    "CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1",    # référence
    "CCNc1ncc(c(Nc2ccc(Cl)cc2)n1)-c1cccnc1",   # chloro
    "CCNc1ncc(c(Nc2ccc(OC)cc2)n1)-c1cccnc1",   # méthoxy
    "CCNc1ncc(c(Nc2cc(C)c(F)cc2)n1)-c1cccnc1", # méthyl, fluoro
]

wf = rowan.submit_analogue_docking_workflow(
    analogues=analogues,
    initial_molecule=analogues[0],  # Ligand référence
    protein=protein,
    pocket=pocket,
    name="SAR series docking",
)

result = wf.result()
print(result.analogue_scores)  # Liste de scores pour chaque analogue
print(result.best_poses)  # Liste de poses

8. Génération d'ASM

Pour l'alignement de séquences multiples (utile pour le corepliement en aval).

wf = rowan.submit_msa_workflow(
    initial_protein_sequences=[
        "MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVP"
    ],
    output_formats=["colabfold", "chai", "boltz"],
    name="target MSA",
)

result = wf.result()
result.download_files()  # Télécharge les alignements sur disque

9. Corepliement protéine-ligand

Pour la prédiction de complexe lié basée sur l'IA quand aucune structure cristalline n'est disponible.

wf = rowan.submit_protein_cofolding_workflow(
    initial_protein_sequences=[
        "MENFQKVEKIGEGTYGVVYKARNKLTGEVVALKKIRLDTETEGVP"
    ],
    initial_smiles_list=[
        "CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1"
    ],
    name="protein-ligand cofolding",
)

result = wf.result()
print(result.predictions)  # Liste des structures prédites
print(result.messages)  # Métadonnées du modèle/avertissements

predicted_structure = result.get_predicted_structure()
predicted_structure.write("predicted_complex.pdb")

Tous les types de workflows pris en charge

Tous les workflows suivent le même schéma soumettre → attendre → récupérer et supportent les webhooks et l'organisation projet/dossier.

Workflows de modélisation moléculaire principaux

Workflow Fonction À utiliser quand
Descripteurs submit_descriptors_workflow Tri en première passe : MW, LogP, TPSA, HBA/HBD, filtre Lipinski
pKa submit_pka_workflow Groupe ionisable simple ; besoin de thermodynamique de protonation
MacropKa submit_macropka_workflow Médicaments multi-ionisables ; charge dépendante du pH/LogD/solubilité
Recherche de conformères submit_conformer_search_workflow Ensemble 3D pour amarrage, MD ou SAR ; tautomère connu
Recherche de tautomères submit_tautomer_search_workflow Hétérocycles, céto-énol ; forme tautomère incertaine
Solubilité submit_solubility_workflow Prédiction de solubilité aqueuse ou spécifique au solvant
Perméabilité membranaire submit_membrane_permeability_workflow Caco-2, PAMPA, BBB, perméabilité plasmatique
ADMET submit_admet_workflow Balayage large de ressemblance aux médicaments et propriétés ADMET

Workflows de conception basée sur la structure

Workflow Fonction À utiliser quand
Amarrage submit_docking_workflow Ligand simple, poche de liaison connue
Amarrage d'analogues submit_analogue_docking_workflow Série SAR (5–100+ composés) dans une poche partagée
Amarrage batch submit_batch_docking_workflow Criblage rapide de la bibliothèque ; grands ensembles de composés
MD protéique submit_protein_md_workflow Dynamique à long terme ; échantillonnage conformationnel
MD analyse de pose submit_pose_analysis_md_workflow Affinage MD d'une pose d'amarrage
Corepliement protéique submit_protein_cofolding_workflow Pas de structure cristalline ; complexe lié prédit par IA
Conception de liant protéique submit_protein_binder_design_workflow Génération de liant de novo contre une cible protéique

Chimie computationnelle avancée

Workflow Fonction À utiliser quand
Calcul basique submit_basic_calculation_workflow Optimisation géométrique QM/ML ou énergie ponctuelle
Propriétés électroniques submit_electronic_properties_workflow Dipôle, charges partielles, HOMO-LUMO, ESP
BDE submit_bde_workflow Énergies de dissociation de liaison ; prédiction de point mou métabolique
Potentiel de redox submit_redox_potential_workflow Potentiels d'oxydation/réduction
États de spin submit_spin_states_workflow Ordre d'énergie des états de spin pour organométalliques/radicaux
Tension submit_strain_workflow Tension conformationnelle relative au minimum global
Scan submit_scan_workflow Scans PES ; profils de torsion
Optimisation multi-étapes submit_multistage_opt_workflow Optimisation progressive sur les niveaux de théorie

Chimie de réaction

Workflow Fonction À utiliser quand
Recherche TS à double extrémité submit_double_ended_ts_search_workflow État de transition entre deux structures connues
IRC submit_irc_workflow Confirmer la connectivité TS ; coordonnée de réaction intrinsèque

Propriétés avancées

Workflow Fonction À utiliser quand
RMN submit_nmr_workflow Décalages chimiques 1H/13C prédits pour vérification de structure
Mobilité ionique submit_ion_mobility_workflow Section transversale de collision (CCS) pour développement de méthode MS
Force de liaison hydrogène submit_hydrogen_bond_basicity_workflow Force de donneur/accepteur de liaison H pour formulation/solubilité
Fukui submit_fukui_workflow Indices de réactivité de site pour attaque électrophile/nucléophile
Décomposition d'énergie d'interaction submit_interaction_energy_decomposition_workflow Analyse d'interaction au niveau des fragments

Énergie libre de liaison

Workflow Fonction À utiliser quand
RBFE/FEP submit_relative_binding_free_energy_perturbation_workflow ΔΔG relatif pour série congénère
Graphe RBFE submit_rbfe_graph_workflow Construire et optimiser un réseau de perturbation RBFE

Biologie séquentielle et structurale

Workflow Fonction À utiliser quand
ASM submit_msa_workflow Alignement de séquences multiples pour corepliement (ColabFold, Chai, Boltz)
Conformères dépendants du solvant submit_solvent_dependent_conformers_workflow Ensembles de conformères conscients de la solvatation

Soumission et récupération batch

Pour les bibliothèques ou séries d'analogues, soumettez dans une boucle en utilisant la fonction de workflow spécifique. Les fonctions génériques rowan.batch_submit_workflow() et rowan.submit_workflow() retournent actuellement des erreurs 422 de l'API — utilisez les fonctions nommées (submit_descriptors_workflow, submit_pka_workflow, etc.) à la place.

Soumettre un batch

smileses = ["CCO", "CC(=O)O", "c1ccccc1O"]
names = ["ethanol", "acetic acid", "phenol"]

workflows = [
    rowan.submit_descriptors_workflow(smi, name=name)
    for smi, name in zip(smileses, names)
]

print(f"Submitted {len(workflows)} workflows")

Vérifier le statut batch

statuses = rowan.batch_poll_status([wf.uuid for wf in workflows])
# Retourne les comptages agrégés — pas par UUID :
# {'queued': 0, 'running': 1, 'complete': 2, 'failed': 0, 'total': 3, ...}

if statuses["complete"] == statuses["total"]:
    print("All workflows done")
elif statuses["failed"] > 0:
    print(f"{statuses['failed']} workflows failed")

Récupérer et collecter les résultats

results = []
for wf in workflows:
    try:
        result = wf.result()
        results.append(result.data)
    except rowan.WorkflowError as e:
        print(f"Workflow {wf.uuid} failed: {e}")

# Optionnellement agréger dans DataFrame
import pandas as pd
df = pd.DataFrame(results)

Motif non-bloquant / vérifier en arrière-plan

Pour les workflows à longue durée d'exécution où vous ne voulez pas garder un processus ouvert, soumettez les workflows, enregistrez leurs UUID, et vérifiez plus tard dans un processus séparé.

Session 1 — soumettre et enregistrer les UUID :

import rowan, json

rowan.api_key = "..."
smileses = ["CCO", "CC(=O)O", "c1ccccc1O"]

workflows = [
    rowan.submit_descriptors_workflow(smi, name=f"compound_{i}")
    for i, smi in enumerate(smileses)
]

# Enregistrer les UUID sur disque (ou dans une base de données)
uuids = [wf.uuid for wf in workflows]
with open("workflow_uuids.json", "w") as f:
    json.dump(uuids, f)

print("Submitted. Check back later.")

Session 2 — vérifier le statut et collecter les résultats quand prêt :

import rowan, json

rowan.api_key = "..."

with open("workflow_uuids.json") as f:
    uuids = json.load(f)

results = []
for uuid in uuids:
    wf = rowan.retrieve_workflow(uuid)
    if wf.done():
        result = wf.result(wait=False)
        results.append({"uuid": uuid, "data": result.data})
    else:
        print(f"{uuid}: still running ({wf.status})")

print(f"Collected {len(results)} completed results")

Webhooks et workflows asynchrones

Pour les campagnes à longue durée d'exécution ou quand vous ne voulez pas garder un processus actif, utilisez les webhooks pour notifier votre serveur quand les workflows se terminent.

Configurer les webhooks

Chaque fonction de soumission de workflow accepte un paramètre webhook_url :

wf = rowan.submit_docking_workflow(
    protein=protein,
    pocket=pocket,
    initial_molecule="CCO",
    webhook_url="https://myserver.com/rowan_callback",
    name="docking with webhook",
)

print(f"Workflow submitted. Result will be POSTed to webhook when complete.")

Les URLs de webhook peuvent être passées à n'importe quelle fonction de workflow spécifique (submit_docking_workflow(), submit_pka_workflow(), submit_descriptors_workflow(), etc.).

Authentification webhook avec secrets

Rowan supporte la vérification de signature de webhook pour s'assurer que les requêtes sont authentiques. Vous devrez :

  1. Créer ou récupérer un secret webhook :
import rowan

# Créer un nouveau secret webhook
secret = rowan.create_webhook_secret()
print(f"Your webhook secret: {secret.secret}")

# Ou récupérer un secret existant
secret = rowan.get_webhook_secret()

# Renouveler votre secret (invalide l'ancien, crée un nouveau)
new_secret = rowan.rotate_webhook_secret()
  1. Vérifier les requêtes webhook entrantes :
import rowan
import hmac
import json

def verify_webhook(request_body: bytes, signature: str, secret: str) -> bool:
    """Vérifier la signature HMAC-SHA256 d'une requête webhook."""
    return rowan.verify_webhook_secret(request_body, signature, secret)

Charge utile webhook et signature

Quand un workflow se termine, Rowan fait un POST d'une charge utile JSON à votre URL de webhook avec l'en-tête :

X-Rowan-Signature: <signature HMAC-SHA256>

Le corps de la requête contient le résultat complet du workflow :

{
  "workflow_uuid": "wf_12345abc",
  "workflow_type": "docking",
  "workflow_name": "lead docking",
  "status": "COMPLETED_OK",
  "created_at": "2025-04-01T12:00:00Z",
  "completed_at": "2025-04-01T12:15:30Z",
  "data": {
    "scores": [-8.2, -8.0, -7.9],
    "best_pose": {...},
    "metadata": {...}
  }
}

Exemple de gestionnaire webhook avec vérification de signature (FastAPI)

from fastapi import FastAPI, Request, HTTPException
import rowan
import json

app = FastAPI()
_ws = rowan.get_webhook_secret() or rowan.create_webhook_secret()
webhook_secret = _ws.secret

@app.post("/rowan_callback")
async def handle_rowan_webhook(request: Request):
    # Récupérer le corps de la requête et la signature
    body = await request.body()
    signature = request.headers.get("X-Rowan-Signature")

    if not signature:
        raise HTTPException(status_code=400, detail="Missing X-Rowan-Signature header")

    # Vérifier la signature
    if not rowan.verify_webhook_secret(body, signature, webhook_secret):
        raise HTTPException(status_code=401, detail="Invalid webhook signature")

    # Parser et traiter
    payload = json.loads(body)
    wf_uuid = payload["workflow_uuid"]
    status = payload["status"]

    if status == "COMPLETED_OK":
        print(f"Workflow {wf_uuid} succeeded!")
        result_data = payload["data"]
        # Traiter le résultat, mettre à jour la base de données, déclencher le workflow suivant, etc.
    elif status == "FAILED":
        print(f"Workflow {wf_uuid} failed!")
        # Gérer l'échec

    # Répondre rapidement pour éviter les retentatives
    return {"status": "received"}

Meilleures pratiques des webhooks

  • Toujours vérifier les signatures en utilisant rowan.verify_webhook_secret() pour s'assurer que les requêtes viennent de Rowan
  • Répondre rapidement (< 5 secondes) ; déplacer le traitement lourd vers des tâches async ou des travaux de fond
  • Implémenter l'idempotence : les workflows peuvent réessayer ; gérer les charges utiles dupliquées avec élégance en utilisant workflow_uuid
  • Enregistrer tous les événements pour le débogage et l'audit
  • Utiliser pour les campagnes longues : les webhooks brillent avec 50+ workflows ; pour les petits travaux, le polling avec result() est plus simple
  • Renouveler les secrets régulièrement en utilisant rowan.rotate_webhook_secret() pour la sécurité
  • Retourner le statut 2xx pour confirmer la réception ; Rowan peut réessayer sur les erreurs 5xx

Utilitaires protéiques

Télécharger des protéines

# Depuis un fichier PDB local
protein = rowan.upload_protein(
    name="egfr_kinase_domain",
    file_path="egfr_kinase.pdb",
)

# Depuis la base de données PDB
protein_from_pdb = rowan.create_protein_from_pdb_id(
    name="CDK2 (1M17)",
    code="1M17",
)

# Récupérer une protéine téléchargée précédemment
protein = rowan.retrieve_protein("protein-uuid")

# Lister toutes les protéines
my_proteins = rowan.list_proteins()

Conseils de préparation protéique

  • Format de fichier : PDB, mmCIF (Rowan détecte automatiquement)
  • Molécules d'eau : Rowan garde généralement l'eau pertinente ; supprimer l'eau en vrac avant si désiré
  • Hétéroatomes : Les cofacteurs, ions et ligands liés sont généralement conservés ; supprimer les hétéroatomes indésirables avant téléchargement
  • Protéines multi-chaînes : Entièrement supportées
  • Résolution : Fonctionne avec les structures RMN, les modèles d'homologie et la cryo-EM ; la qualité importe pour les prédictions en aval
  • Validation : Rowan valide la syntaxe PDB ; les fichiers gravement mal formés peuvent être rejetés

Exemple de bout en bout : campagne d'optimisation du composé-tête

Cet exemple démontre un workflow réaliste pour optimiser un composé-tête :

import rowan
import pandas as pd

# 1. Créer un projet et un dossier pour l'organisation
project = rowan.create_project(name="CDK2 Hit Optimization")
rowan.set_project("CDK2 Hit Optimization")
folder = rowan.create_folder(name="round_1_tautomers_and_pka")

# 2. Charger le composé-tête et les analogues
hit = "CCNc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1"  # Composé-tête connu
analogues = [
    "CCNc1ncc(c(Nc2ccccc2)n1)-c1cccnc1",      # Retirer F
    "CCNc1ncc(c(Nc2ccc(Cl)cc2)n1)-c1cccnc1",  # Cl au lieu de F
    "CCC(C)Nc1ncc(c(Nc2ccc(F)cc2)n1)-c1cccnc1",  # Propyle au lieu d'éthyle
]

# 3. Déterminer les meilleurs tautomères (par sécurité)
print("Searching tautomeric forms...")
taut_workflows = [
    rowan.submit_tautomer_search_workflow(
        smi, name=f"analog_{i}", folder=folder,
    )
    for i, smi in enumerate(analogues)
]

best_tautomers = []
for wf in taut_workflows:
    result = wf.result()
    best_tautomers.append(result.best_tautomer)

# 4. Prédire pKa et propriétés basiques pour tous les analogues
print("Predicting pKa and properties...")
pka_workflows = [
    rowan.submit_pka_workflow(
        smi, method="chemprop_nevolianis2025", name=f"pka_{i}", folder=folder,
    )
    for i, smi in enumerate(best_tautomers)
]

descriptor_workflows = [
    rowan.submit_descriptors_workflow(smi, name=f"desc_{i}", folder=folder)
    for i, smi in enumerate(best_tautomers)
]

# 5. Collecter les résultats
pka_results = []
for wf in pka_workflows:
    try:
        result = wf.result()
        pka_results.append({
            "compound": wf.name,
            "pka": result.strongest_acid,  # pKa du site acide le plus fort
            "uuid": wf.uuid,
        })
    except rowan.WorkflowError as e:
        print(f"pKa prediction failed for {wf.name}: {e}")

descriptor_results = []
for wf in descriptor_workflows:
    try:
        result = wf.result()
        desc = result.descriptors
        descriptor_results.append({
            "compound": wf.name,
            "mw": desc.get("MW"),
            "logp": desc.get("SLogP"),
            "hba": desc.get("nHBAcc"),
            "hbd": desc.get("nHBDon"),
            "uuid": wf.uuid,
        })
    except rowan.WorkflowError as e:
        print(f"Descriptor calculation failed for {wf.name}: {e}")

# 6. Fusionner et résumer
df_pka = pd.DataFrame(pka_results)
df_desc = pd.DataFrame(descriptor_results)
df = df_pka.merge(df_desc, on="compound", how="outer")

print("\n=== Preliminary SAR ===")
print(df.to_string())

# 7. Sélectionner le composé prometteur pour l'amarrage
# Les noms de composé sont "pka_0", "pka_1", etc. — extraire l'index pour retrouver SMILES
top_idx = int(df.loc[df["pka"].idxmin(), "compound"].split("_")[1])
top_smiles = best_tautomers[top_idx]

print(f"\nProceeding with docking: {top_smiles}")

# 8. Campagne d'amarrage
protein = rowan.create_protein_from_pdb_id(name="CDK2_1CKP", code="1CKP")
pocket = {"center": [10.5, 24.2, 31.8], "size": [18.0, 18.0, 18.0]}

docking_wf = rowan.submit_docking_workflow(
    protein=protein,
    pocket=pocket,
    initial_molecule=top_smiles,
    do_pose_refinement=True,
    name=f"docking_{top_compound}",
)

dock_result = docking_wf.result()
print(f"\nDocking score: {dock_result.scores[0]:.2f} kcal/mol")
print(f"Best pose saved to: best_pose.pdb")
dock_result.best_pose.write("best_pose.pdb")

Gestion des erreurs et dépannage

Erreurs courantes et solutions

import rowan

# Erreur 1 : SMILES invalide
try:
    wf = rowan.submit_descriptors_workflow("CCCC(CC", name="bad smiles")  # Invalide
except rowan.ValidationError as e:
    print(f"Invalid SMILES: {e}")
    # Solution : utiliser RDKit pour valider avant la soumission
    from rdkit import Chem
    smi = Chem.MolToSmiles(Chem.MolFromSmiles(smi))

# Erreur 2 : Clé API non définie
try:
    wf = rowan.submit_descriptors_workflow("CCO")
except rowan.AuthenticationError:
    print("API key not found. Set ROWAN_API_KEY env var or call rowan.api_key = '...'")

# Erreur 3 : Crédits insuffisants
try:
    wf = rowan.submit_protein_cofolding_workflow(...)
except rowan.InsufficientCreditsError as e:
    print(f"Not enough credits: {e}. Purchase more or reduce job size.")

# Erreur 4 : Workflow échoué (molécule mauvaise, etc.)
try:
    wf = rowan.submit_docking_workflow(...)
    result = wf.result()
except rowan.WorkflowError as e:
    print(f"Workflow failed: {e}")
    # Vérifier wf.status pour les détails
    print(f"Status: {wf.status}")

# Erreur 5 : Workflow pas encore complété — vérifier manuellement
result = wf.result(wait=True, poll_interval=5)  # attend et vérifie toutes les 5s
# Ou vérifier le statut sans bloquer :
if not wf.done():
    print("Workflow still running. Call wf.result() again later.")

Conseils de débogage

  • Vérifier le statut du workflow : wf.status, vérifier wf.done(), ou appeler wf.get_status()
  • Inspecter le résultat brut : result.data au lieu des propriétés pratiques
  • Relancer le workflow échoué : Enregistrer les UUID et réessayer avec rowan.retrieve_workflow(uuid)
  • Valider les molécules au préalable : Utiliser RDKit ou Chemaxon avant la soumission batch

Motifs d'utilisation recommandés

  • Préférer les workflows natifs de Rowan aux assemblages bas niveau quand ils existent
  • Utiliser les projets et dossiers pour toute campagne non triviale (>5 workflows)
  • Utiliser result() pour bloquer jusqu'à l'achèvement (par défaut : wait=True, poll_interval=5)
  • Utiliser les propriétés de résultat typés en premier, revenir à .data pour les champs non mappés
  • Utiliser la soumission batch pour les bibliothèques de composés ou les séries d'analogues
  • Enchaîner les workflows pour les campagnes de chimie multi-étapes :
    • pKa → macropKa → perméabilité (évaluation ADME)
    • recherche tautomère → amarrage → MD analyse de pose (affinage de pose)
    • génération ASM → corepliement protéine-ligand (prédiction de structure IA)
  • Utiliser les webhooks pour les campagnes à longue durée d'exécution (>50 workflows) ou les pipelines asynchrones
  • Utiliser le streaming pour un retour d'information interactif sur les recherches de conformères/amarrage grandes

Résumé

Utilisez Rowan quand votre workflow nécessite une exécution en cloud pour les tâches de conception moléculaire, particulièrement quand vous voulez une API unifiée et une gestion de résultats cohérente dans la modélisation de petites molécules, les protéines, l'amarrage, la prédiction ADME, et la génération de structure ML.

Rowan est une plateforme de workflow de conception moléculaire, pas simplement un moteur de chimie à distance. Elle gère la mise à l'échelle d'infrastructure, la persistance des résultats, et l'orchestration de pipelines multi-étapes pour que vous vous concentriez sur la science.

Skills similaires