rowan-python 3.0.12__py3-none-any.whl → 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rowan/__init__.py +13 -1
- rowan/config.py +1 -7
- rowan/folder.py +5 -5
- rowan/molecule.py +13 -6
- rowan/project.py +4 -4
- rowan/protein.py +8 -7
- rowan/types.py +4 -2
- rowan/user.py +26 -20
- rowan/workflows/__init__.py +9 -2
- rowan/workflows/admet.py +2 -3
- rowan/workflows/analogue_docking.py +31 -14
- rowan/workflows/base.py +49 -13
- rowan/workflows/basic_calculation.py +18 -10
- rowan/workflows/batch_docking.py +2 -2
- rowan/workflows/bde.py +37 -22
- rowan/workflows/conformer_search.py +116 -13
- rowan/workflows/descriptors.py +14 -5
- rowan/workflows/docking.py +23 -7
- rowan/workflows/double_ended_ts_search.py +20 -9
- rowan/workflows/electronic_properties.py +13 -6
- rowan/workflows/fukui.py +12 -5
- rowan/workflows/hydrogen_bond_donor_acceptor_strength.py +19 -7
- rowan/workflows/interaction_energy_decomposition.py +17 -9
- rowan/workflows/ion_mobility.py +19 -6
- rowan/workflows/irc.py +124 -67
- rowan/workflows/macropka.py +2 -3
- rowan/workflows/membrane_permeability.py +8 -5
- rowan/workflows/multistage_optimization.py +18 -4
- rowan/workflows/nmr.py +12 -4
- rowan/workflows/pka.py +5 -3
- rowan/workflows/pose_analysis_md.py +42 -5
- rowan/workflows/protein_binder_design.py +14 -1
- rowan/workflows/protein_cofolding.py +11 -2
- rowan/workflows/protein_md.py +53 -2
- rowan/workflows/rbfe_graph.py +11 -3
- rowan/workflows/redox_potential.py +7 -5
- rowan/workflows/scan.py +47 -12
- rowan/workflows/solubility.py +2 -3
- rowan/workflows/solvent_dependent_conformers.py +19 -23
- rowan/workflows/spin_states.py +52 -4
- rowan/workflows/strain.py +7 -5
- rowan/workflows/tautomer_search.py +9 -7
- {rowan_python-3.0.12.dist-info → rowan_python-3.1.0.dist-info}/METADATA +6 -2
- rowan_python-3.1.0.dist-info/RECORD +55 -0
- {rowan_python-3.0.12.dist-info → rowan_python-3.1.0.dist-info}/WHEEL +1 -1
- rowan_python-3.0.12.dist-info/RECORD +0 -55
- {rowan_python-3.0.12.dist-info → rowan_python-3.1.0.dist-info}/licenses/LICENSE +0 -0
rowan/__init__.py
CHANGED
|
@@ -4,11 +4,14 @@ from stjames import (
|
|
|
4
4
|
Atom,
|
|
5
5
|
Constraint,
|
|
6
6
|
ConstraintType,
|
|
7
|
+
ConformerClusteringSettings,
|
|
7
8
|
ConformerGenSettingsUnion,
|
|
8
9
|
Correction,
|
|
9
10
|
Engine,
|
|
10
11
|
ETKDGSettings,
|
|
12
|
+
GreedyClusteringSettings,
|
|
11
13
|
iMTDSettings,
|
|
14
|
+
KMeansClusteringSettings,
|
|
12
15
|
Method,
|
|
13
16
|
Mode,
|
|
14
17
|
MultiStageOptSettings,
|
|
@@ -17,10 +20,19 @@ from stjames import (
|
|
|
17
20
|
PBCDFTSettings,
|
|
18
21
|
PeriodicCell,
|
|
19
22
|
Settings,
|
|
23
|
+
Solvent,
|
|
20
24
|
SolventSettings,
|
|
21
25
|
Task,
|
|
22
26
|
)
|
|
27
|
+
from stjames.excited_state_settings import TDDFTSettings
|
|
23
28
|
from stjames.pbc_dft_settings import PBCDFTSmearing
|
|
29
|
+
from stjames.engine_compatibility import (
|
|
30
|
+
ENGINE_METHODS,
|
|
31
|
+
ENGINE_SOLVENT_MODELS,
|
|
32
|
+
ENGINE_SUPPORTS_BASIS_SET,
|
|
33
|
+
METHOD_ENGINES,
|
|
34
|
+
get_supported_corrections,
|
|
35
|
+
)
|
|
24
36
|
from stjames.optimization.freezing_string_method import (
|
|
25
37
|
FSMInterpolation,
|
|
26
38
|
FSMOptimizationCoordinates,
|
|
@@ -34,7 +46,7 @@ from .api_keys import *
|
|
|
34
46
|
from .calculation import *
|
|
35
47
|
from .folder import *
|
|
36
48
|
from .molecule import *
|
|
37
|
-
from .types import RdkitMol, StJamesMolecule
|
|
49
|
+
from .types import RdkitMol, StJamesMolecule, StructureInput
|
|
38
50
|
from .workflows import *
|
|
39
51
|
from .project import *
|
|
40
52
|
from .protein import *
|
rowan/config.py
CHANGED
|
@@ -373,7 +373,7 @@ class ConformerGeneratorSettings:
|
|
|
373
373
|
solvent_warning: bool = False
|
|
374
374
|
|
|
375
375
|
|
|
376
|
-
# Main group elements for ETKDG
|
|
376
|
+
# Main group elements for ETKDG
|
|
377
377
|
_MAIN_GROUP = {1, 5, 6, 7, 8, 9, 14, 15, 16, 17, 35, 53}
|
|
378
378
|
|
|
379
379
|
CONFORMER_GENERATOR_SETTINGS: dict[str, ConformerGeneratorSettings] = {
|
|
@@ -395,12 +395,6 @@ CONFORMER_GENERATOR_SETTINGS: dict[str, ConformerGeneratorSettings] = {
|
|
|
395
395
|
disable_ts=True,
|
|
396
396
|
allowed_engines=["aimnet2", "xtb"], # MCMM only supports these for energy
|
|
397
397
|
),
|
|
398
|
-
"lyrebird": ConformerGeneratorSettings(
|
|
399
|
-
disable_constraints=True,
|
|
400
|
-
disable_open_shell=True,
|
|
401
|
-
disable_ts=True,
|
|
402
|
-
atoms_supported=_MAIN_GROUP,
|
|
403
|
-
),
|
|
404
398
|
}
|
|
405
399
|
|
|
406
400
|
|
rowan/folder.py
CHANGED
|
@@ -11,13 +11,13 @@ class Folder(BaseModel):
|
|
|
11
11
|
"""
|
|
12
12
|
A class representing a folder in the Rowan API.
|
|
13
13
|
|
|
14
|
-
:ivar uuid:
|
|
15
|
-
:ivar name:
|
|
16
|
-
:ivar parent_uuid:
|
|
14
|
+
:ivar uuid: UUID of the folder.
|
|
15
|
+
:ivar name: Name of the folder.
|
|
16
|
+
:ivar parent_uuid: UUID of the parent folder.
|
|
17
17
|
:ivar notes: Folder notes.
|
|
18
18
|
:ivar starred: Whether the folder is starred.
|
|
19
19
|
:ivar public: Whether the folder is public.
|
|
20
|
-
:ivar created_at:
|
|
20
|
+
:ivar created_at: Date and time the folder was created.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
uuid: str
|
|
@@ -225,7 +225,7 @@ def get_folder(path: str, create: bool = True) -> Folder:
|
|
|
225
225
|
:param path: Folder name or ``/``-separated path, e.g. ``"project/subdir/run1"``.
|
|
226
226
|
:param create: If True (default), create missing folders. If False, raise ValueError if any
|
|
227
227
|
segment is not found.
|
|
228
|
-
:returns:
|
|
228
|
+
:returns: Deepest :class:`Folder` in the path.
|
|
229
229
|
:raises ValueError: If the path is empty, or ``create=False`` and a folder is not found.
|
|
230
230
|
"""
|
|
231
231
|
segments = [s for s in path.split("/") if s]
|
rowan/molecule.py
CHANGED
|
@@ -14,12 +14,6 @@ class Molecule(BaseModel):
|
|
|
14
14
|
|
|
15
15
|
Can be created from SMILES, XYZ, or directly from atoms. Wraps stjames.Molecule
|
|
16
16
|
internally but provides a cleaner interface.
|
|
17
|
-
|
|
18
|
-
:param charge: Molecular charge.
|
|
19
|
-
:param multiplicity: Spin multiplicity.
|
|
20
|
-
:param atoms: List of atoms with positions.
|
|
21
|
-
:param energy: Electronic energy (Hartree).
|
|
22
|
-
:param smiles: SMILES string representation.
|
|
23
17
|
"""
|
|
24
18
|
|
|
25
19
|
_stjames: stjames.Molecule = PrivateAttr()
|
|
@@ -85,6 +79,19 @@ class Molecule(BaseModel):
|
|
|
85
79
|
"""
|
|
86
80
|
return cls.from_xyz(Path(path).read_text(), charge=charge, multiplicity=multiplicity)
|
|
87
81
|
|
|
82
|
+
@classmethod
|
|
83
|
+
def molecules_from_sdf(cls, path: str | Path) -> list[Self]:
|
|
84
|
+
"""
|
|
85
|
+
Read all records from an SDF file as a list of molecules.
|
|
86
|
+
|
|
87
|
+
Each record becomes one Molecule, in file order, with atom ordering preserved
|
|
88
|
+
across records -- so a multi-conformer SDF reads as a consistent ensemble.
|
|
89
|
+
|
|
90
|
+
:param path: path to the SDF file
|
|
91
|
+
:returns: one Molecule per record
|
|
92
|
+
"""
|
|
93
|
+
return [cls.from_stjames(mol) for mol in stjames.Molecule.molecules_from_sdf(path)]
|
|
94
|
+
|
|
88
95
|
@classmethod
|
|
89
96
|
def from_stjames(cls, stj: stjames.Molecule) -> Self:
|
|
90
97
|
"""
|
rowan/project.py
CHANGED
|
@@ -12,9 +12,9 @@ class Project(BaseModel):
|
|
|
12
12
|
"""
|
|
13
13
|
A class representing a project in the Rowan API.
|
|
14
14
|
|
|
15
|
-
:ivar uuid:
|
|
16
|
-
:ivar name:
|
|
17
|
-
:ivar created_at:
|
|
15
|
+
:ivar uuid: UUID of the project.
|
|
16
|
+
:ivar name: Name of the project.
|
|
17
|
+
:ivar created_at: Date and time the project was created.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
uuid: str
|
|
@@ -139,7 +139,7 @@ def set_project(name: str) -> Project:
|
|
|
139
139
|
folder = rowan.get_folder("docking/batch_1")
|
|
140
140
|
|
|
141
141
|
:param name: Exact name of the project to activate.
|
|
142
|
-
:returns:
|
|
142
|
+
:returns: Matched project.
|
|
143
143
|
:raises ValueError: If no project with that name is found.
|
|
144
144
|
"""
|
|
145
145
|
matches = list_projects(name_contains=name, size=100)
|
rowan/protein.py
CHANGED
|
@@ -16,13 +16,13 @@ class Protein(BaseModel):
|
|
|
16
16
|
Data is not loaded by default to avoid unnecessary downloads that could impact performance.
|
|
17
17
|
Call `load_data()` to fetch and attach the protein data to this `Protein` object.
|
|
18
18
|
|
|
19
|
-
:ivar uuid:
|
|
20
|
-
:ivar created_at:
|
|
19
|
+
:ivar uuid: UUID of the protein
|
|
20
|
+
:ivar created_at: Creation date of the protein
|
|
21
21
|
:ivar used_in_workflow: Whether the protein is used in a workflow
|
|
22
|
-
:ivar ancestor_uuid:
|
|
22
|
+
:ivar ancestor_uuid: UUID of the ancestor protein
|
|
23
23
|
:ivar sanitized: Whether the protein is sanitized
|
|
24
|
-
:ivar name:
|
|
25
|
-
:ivar data:
|
|
24
|
+
:ivar name: Name of the protein
|
|
25
|
+
:ivar data: Data of the protein
|
|
26
26
|
:ivar public: Whether the protein is public
|
|
27
27
|
"""
|
|
28
28
|
|
|
@@ -36,7 +36,7 @@ class Protein(BaseModel):
|
|
|
36
36
|
public: bool | None = None
|
|
37
37
|
pocket: list[list[float]] | None = None
|
|
38
38
|
|
|
39
|
-
def __repr__(self):
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
40
|
return f"<Protein name='{self.name}' created_at='{self.created_at}' uuid='{self.uuid}'>"
|
|
41
41
|
|
|
42
42
|
def refresh(self, in_place: bool = True) -> Self:
|
|
@@ -67,7 +67,6 @@ class Protein(BaseModel):
|
|
|
67
67
|
public: bool | None = None,
|
|
68
68
|
pocket: list[list[float]] | None = None,
|
|
69
69
|
) -> Self:
|
|
70
|
-
# Use current values unless new ones are passed in
|
|
71
70
|
"""
|
|
72
71
|
Updates protein data
|
|
73
72
|
|
|
@@ -77,6 +76,7 @@ class Protein(BaseModel):
|
|
|
77
76
|
:param pocket: New pocket of the protein
|
|
78
77
|
:returns: Updated protein object
|
|
79
78
|
"""
|
|
79
|
+
# Use current values unless new ones are passed in
|
|
80
80
|
updated_payload = {
|
|
81
81
|
"name": name if name is not None else self.name,
|
|
82
82
|
"data": data if data is not None else self.data,
|
|
@@ -312,6 +312,7 @@ def upload_protein(
|
|
|
312
312
|
|
|
313
313
|
:param name: Name of the protein to create
|
|
314
314
|
:param file_path: Path to the PDB file to upload
|
|
315
|
+
:param project_uuid: UUID of the project to create the protein in
|
|
315
316
|
:returns: Protein object representing the uploaded protein
|
|
316
317
|
:raises requests.HTTPError: if the request to the API fails
|
|
317
318
|
"""
|
rowan/types.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Shared type aliases for the Rowan package."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import TypeAlias
|
|
4
4
|
|
|
5
5
|
import stjames
|
|
6
6
|
from rdkit import Chem
|
|
@@ -9,7 +9,9 @@ from .molecule import Molecule as RowanMolecule
|
|
|
9
9
|
|
|
10
10
|
RdkitMol: TypeAlias = Chem.rdchem.Mol | Chem.rdchem.RWMol
|
|
11
11
|
StJamesMolecule: TypeAlias = stjames.Molecule
|
|
12
|
-
|
|
12
|
+
# 3D-structure input: molecule objects carrying coordinates. Excludes bare SMILES strings
|
|
13
|
+
# (no geometry) and dicts (internal serialization only).
|
|
14
|
+
StructureInput: TypeAlias = RowanMolecule | StJamesMolecule | RdkitMol
|
|
13
15
|
SolventInput: TypeAlias = stjames.Solvent | str | None
|
|
14
16
|
SMILES: TypeAlias = str
|
|
15
17
|
UUID: TypeAlias = str
|
rowan/user.py
CHANGED
|
@@ -11,9 +11,9 @@ class Organization(BaseModel):
|
|
|
11
11
|
"""
|
|
12
12
|
A Rowan organization
|
|
13
13
|
|
|
14
|
-
:ivar name:
|
|
15
|
-
:ivar weekly_credits:
|
|
16
|
-
:ivar credits:
|
|
14
|
+
:ivar name: Name of the organization.
|
|
15
|
+
:ivar weekly_credits: Weekly credits of the organization.
|
|
16
|
+
:ivar credits: Credits of the organization.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
name: str
|
|
@@ -25,7 +25,7 @@ class OrganizationRole(BaseModel):
|
|
|
25
25
|
"""
|
|
26
26
|
A Rowan organization role
|
|
27
27
|
|
|
28
|
-
:ivar name:
|
|
28
|
+
:ivar name: Name of the organization role.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
name: str
|
|
@@ -35,7 +35,7 @@ class SubscriptionPlan(BaseModel):
|
|
|
35
35
|
"""
|
|
36
36
|
A Rowan subscription plan
|
|
37
37
|
|
|
38
|
-
:ivar name:
|
|
38
|
+
:ivar name: Name of the subscription plan.
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
name: str
|
|
@@ -45,7 +45,7 @@ class IndividualSubscription(BaseModel):
|
|
|
45
45
|
"""
|
|
46
46
|
A Rowan individual subscription
|
|
47
47
|
|
|
48
|
-
:ivar subscription_plan:
|
|
48
|
+
:ivar subscription_plan: Subscription plan of the individual subscription.
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
51
|
subscription_plan: SubscriptionPlan
|
|
@@ -55,19 +55,23 @@ class User(BaseModel):
|
|
|
55
55
|
"""
|
|
56
56
|
A Rowan user
|
|
57
57
|
|
|
58
|
-
:ivar uuid:
|
|
59
|
-
:ivar username:
|
|
60
|
-
:ivar email:
|
|
61
|
-
:ivar firstname:
|
|
62
|
-
:ivar lastname:
|
|
63
|
-
:ivar weekly_credits:
|
|
64
|
-
:ivar credits:
|
|
65
|
-
:ivar billing_name:
|
|
66
|
-
:ivar billing_address:
|
|
67
|
-
:ivar credit_balance_warning:
|
|
68
|
-
:ivar organization:
|
|
69
|
-
:ivar organization_role:
|
|
70
|
-
:ivar individual_subscription:
|
|
58
|
+
:ivar uuid: UUID of the user.
|
|
59
|
+
:ivar username: Username of the user.
|
|
60
|
+
:ivar email: Email of the user.
|
|
61
|
+
:ivar firstname: First name of the user.
|
|
62
|
+
:ivar lastname: Last name of the user.
|
|
63
|
+
:ivar weekly_credits: Weekly credits of the user.
|
|
64
|
+
:ivar credits: Credits of the user.
|
|
65
|
+
:ivar billing_name: Billing name of the user.
|
|
66
|
+
:ivar billing_address: Billing address of the user.
|
|
67
|
+
:ivar credit_balance_warning: Credit balance warning of the user.
|
|
68
|
+
:ivar organization: Organization of the user.
|
|
69
|
+
:ivar organization_role: Organization role of the user.
|
|
70
|
+
:ivar individual_subscription: Individual subscription of the user.
|
|
71
|
+
:ivar enabled_workflows: Workflow types this account may submit, as backend slugs that
|
|
72
|
+
mostly match the ``submit_<slug>_workflow`` names (note ``molecular_dynamics`` is
|
|
73
|
+
protein MD).
|
|
74
|
+
:ivar feature_list: Feature flags enabled for this account.
|
|
71
75
|
"""
|
|
72
76
|
|
|
73
77
|
uuid: str
|
|
@@ -83,6 +87,8 @@ class User(BaseModel):
|
|
|
83
87
|
organization: Organization | None = None
|
|
84
88
|
organization_role: OrganizationRole | None = None
|
|
85
89
|
individual_subscription: IndividualSubscription | None = None
|
|
90
|
+
enabled_workflows: list[str] = []
|
|
91
|
+
feature_list: list[str] = []
|
|
86
92
|
|
|
87
93
|
@property
|
|
88
94
|
def name(self) -> str:
|
|
@@ -173,7 +179,7 @@ def verify_webhook_secret(
|
|
|
173
179
|
) -> bool:
|
|
174
180
|
"""Verify an incoming webhook request from Rowan.
|
|
175
181
|
|
|
176
|
-
:param raw_body:
|
|
182
|
+
:param raw_body: Raw (unparsed) request body bytes.
|
|
177
183
|
:param signature_header: Value of the X-Rowan-Signature header.
|
|
178
184
|
:param secret: Your webhook secret (from :func:`create_webhook_secret` or
|
|
179
185
|
:func:`rotate_webhook_secret`).
|
rowan/workflows/__init__.py
CHANGED
|
@@ -9,7 +9,6 @@ Basic pattern:
|
|
|
9
9
|
|
|
10
10
|
from stjames import (
|
|
11
11
|
ETKDGSettings,
|
|
12
|
-
LyrebirdSettings,
|
|
13
12
|
MonteCarloMultipleMinimumSettings,
|
|
14
13
|
SolventModel,
|
|
15
14
|
iMTDGCSettings,
|
|
@@ -40,7 +39,14 @@ from .base import (
|
|
|
40
39
|
)
|
|
41
40
|
from .basic_calculation import BasicCalculationResult, submit_basic_calculation_workflow
|
|
42
41
|
from .batch_docking import BatchDockingResult, submit_batch_docking_workflow
|
|
43
|
-
from .bde import
|
|
42
|
+
from .bde import (
|
|
43
|
+
BDEEntry,
|
|
44
|
+
BDEResult,
|
|
45
|
+
find_bonds,
|
|
46
|
+
find_ch_bonds,
|
|
47
|
+
find_cx_bonds,
|
|
48
|
+
submit_bde_workflow,
|
|
49
|
+
)
|
|
44
50
|
from .conformer_search import ConformerSearchResult, submit_conformer_search_workflow
|
|
45
51
|
from .descriptors import DescriptorsResult, submit_descriptors_workflow
|
|
46
52
|
from .docking import DockingResult, DockingScore, submit_docking_workflow
|
|
@@ -94,6 +100,7 @@ from .protein_binder_design import (
|
|
|
94
100
|
from .protein_cofolding import (
|
|
95
101
|
CofoldingModel,
|
|
96
102
|
CofoldingResult,
|
|
103
|
+
CofoldingTemplate,
|
|
97
104
|
ConstraintTarget,
|
|
98
105
|
ContactConstraint,
|
|
99
106
|
PocketConstraint,
|
rowan/workflows/admet.py
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
import stjames
|
|
4
4
|
|
|
5
5
|
from ..folder import Folder
|
|
6
|
-
from ..types import MoleculeInput
|
|
7
6
|
from ..utils import api_client
|
|
8
|
-
from .base import Workflow, WorkflowResult, extract_smiles, register_result
|
|
7
|
+
from .base import SMILES, Workflow, WorkflowResult, extract_smiles, register_result
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@register_result("admet")
|
|
@@ -26,7 +25,7 @@ class ADMETResult(WorkflowResult):
|
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def submit_admet_workflow(
|
|
29
|
-
initial_smiles:
|
|
28
|
+
initial_smiles: SMILES,
|
|
30
29
|
name: str = "ADMET Workflow",
|
|
31
30
|
folder_uuid: str | None = None,
|
|
32
31
|
folder: Folder | None = None,
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
"""Analogue docking workflow - dock analogues using a template ligand."""
|
|
2
2
|
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
3
5
|
import stjames
|
|
4
6
|
|
|
5
7
|
from ..calculation import Calculation, retrieve_calculation
|
|
6
8
|
from ..folder import Folder
|
|
7
9
|
from ..molecule import Molecule
|
|
8
10
|
from ..protein import Protein, retrieve_protein
|
|
9
|
-
from ..types import MoleculeInput
|
|
10
11
|
from ..utils import api_client
|
|
11
|
-
from .base import
|
|
12
|
+
from .base import (
|
|
13
|
+
StructureInput,
|
|
14
|
+
Workflow,
|
|
15
|
+
WorkflowResult,
|
|
16
|
+
molecule_to_dict,
|
|
17
|
+
register_result,
|
|
18
|
+
require_coordinates,
|
|
19
|
+
)
|
|
12
20
|
from .docking import DockingScore
|
|
13
21
|
|
|
14
22
|
|
|
@@ -32,6 +40,13 @@ class AnalogueDockingResult(WorkflowResult):
|
|
|
32
40
|
best_smiles = smiles
|
|
33
41
|
return f"<AnalogueDockingResult analogues={n} best=({best_score:.2f}, {best_smiles!r})>"
|
|
34
42
|
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
"""Default `complex_pdb` to None on each analogue's scores, then parse."""
|
|
45
|
+
for scores in (self.workflow_data.get("analogue_scores") or {}).values():
|
|
46
|
+
for score in scores:
|
|
47
|
+
score.setdefault("complex_pdb", None)
|
|
48
|
+
super().__post_init__()
|
|
49
|
+
|
|
35
50
|
@property
|
|
36
51
|
def analogue_scores(self) -> dict[str, list[DockingScore]]:
|
|
37
52
|
"""Docking scores for each analogue SMILES."""
|
|
@@ -157,11 +172,11 @@ class AnalogueDockingResult(WorkflowResult):
|
|
|
157
172
|
|
|
158
173
|
def submit_analogue_docking_workflow(
|
|
159
174
|
analogues: list[str],
|
|
160
|
-
initial_molecule:
|
|
175
|
+
initial_molecule: StructureInput,
|
|
161
176
|
protein: str | Protein,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
scoring_function: Literal["vina", "vinardo"] = "vinardo",
|
|
178
|
+
exhaustiveness: int = 8,
|
|
179
|
+
max_poses: int = 4,
|
|
165
180
|
num_conformers_per_analogue: int = 100,
|
|
166
181
|
require_posebusters: bool = False,
|
|
167
182
|
run_local_optimization: bool = False,
|
|
@@ -178,9 +193,9 @@ def submit_analogue_docking_workflow(
|
|
|
178
193
|
:param analogues: SMILES strings to dock.
|
|
179
194
|
:param initial_molecule: Template to which to align molecules to.
|
|
180
195
|
:param protein: Protein to dock. Can be input as a uuid or a Protein object.
|
|
181
|
-
:param
|
|
182
|
-
:param
|
|
183
|
-
:param
|
|
196
|
+
:param scoring_function: Docking scoring function: "vina" or "vinardo".
|
|
197
|
+
:param exhaustiveness: How many times Vina attempts to find a pose for each conformer.
|
|
198
|
+
:param max_poses: Maximum number of poses generated per input conformer.
|
|
184
199
|
:param num_conformers_per_analogue: Maximum number of conformers to generate per analogue.
|
|
185
200
|
:param require_posebusters: Filter conformers based on PoseBusters validity before docking.
|
|
186
201
|
:param run_local_optimization: Whether to run a local opt in docking pocket or just score.
|
|
@@ -193,24 +208,26 @@ def submit_analogue_docking_workflow(
|
|
|
193
208
|
:returns: Workflow object representing the submitted analogue-docking workflow.
|
|
194
209
|
:raises requests.HTTPError: if the request to the API fails.
|
|
195
210
|
"""
|
|
211
|
+
require_coordinates(initial_molecule)
|
|
196
212
|
if folder and folder_uuid:
|
|
197
213
|
raise ValueError("Provide either `folder` or `folder_uuid`, not both.")
|
|
198
214
|
if folder:
|
|
199
215
|
folder_uuid = folder.uuid
|
|
200
216
|
docking_settings = {
|
|
201
|
-
"executable":
|
|
202
|
-
"exhaustiveness": exhaustiveness,
|
|
217
|
+
"executable": "vina",
|
|
203
218
|
"scoring_function": scoring_function,
|
|
219
|
+
"exhaustiveness": exhaustiveness,
|
|
220
|
+
"max_poses": max_poses,
|
|
204
221
|
}
|
|
205
222
|
|
|
206
|
-
|
|
223
|
+
mol_dict = molecule_to_dict(initial_molecule)
|
|
207
224
|
|
|
208
225
|
if isinstance(protein, Protein):
|
|
209
226
|
protein = protein.uuid
|
|
210
227
|
|
|
211
228
|
workflow = stjames.AnalogueDockingWorkflow(
|
|
212
229
|
analogues=analogues,
|
|
213
|
-
initial_molecule=
|
|
230
|
+
initial_molecule=mol_dict,
|
|
214
231
|
protein=protein,
|
|
215
232
|
docking_settings=docking_settings,
|
|
216
233
|
num_conformers_per_analogue=num_conformers_per_analogue,
|
|
@@ -219,7 +236,7 @@ def submit_analogue_docking_workflow(
|
|
|
219
236
|
)
|
|
220
237
|
|
|
221
238
|
data = {
|
|
222
|
-
"initial_molecule":
|
|
239
|
+
"initial_molecule": mol_dict,
|
|
223
240
|
"workflow_type": "analogue_docking",
|
|
224
241
|
"workflow_data": workflow.model_dump(serialize_as_any=True, mode="json"),
|
|
225
242
|
"name": name,
|
rowan/workflows/base.py
CHANGED
|
@@ -15,7 +15,7 @@ from rdkit import Chem
|
|
|
15
15
|
|
|
16
16
|
from ..folder import Folder
|
|
17
17
|
from ..molecule import Molecule as RowanMolecule
|
|
18
|
-
from ..types import SMILES,
|
|
18
|
+
from ..types import SMILES, StructureInput
|
|
19
19
|
from ..utils import api_client
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
@@ -30,7 +30,9 @@ Task = stjames.Task
|
|
|
30
30
|
Settings = stjames.Settings
|
|
31
31
|
MultiStageOptSettings = stjames.MultiStageOptSettings
|
|
32
32
|
ETKDGSettings = stjames.ETKDGSettings
|
|
33
|
+
OpenConfSettings = stjames.OpenConfSettings
|
|
33
34
|
ConformerGenSettings = stjames.conformers.ConformerGenSettings
|
|
35
|
+
ConformerClusteringSettings = stjames.ConformerClusteringSettings
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
@dataclass(frozen=True, slots=True)
|
|
@@ -106,8 +108,21 @@ class WorkflowResult:
|
|
|
106
108
|
try:
|
|
107
109
|
obj = self._stjames_class.model_validate(self.workflow_data) # type: ignore[attr-defined]
|
|
108
110
|
object.__setattr__(self, "_workflow", obj)
|
|
109
|
-
except ValidationError:
|
|
110
|
-
|
|
111
|
+
except ValidationError as e:
|
|
112
|
+
if not self.complete:
|
|
113
|
+
# Still-running workflow: partial data is expected; leave `_workflow`
|
|
114
|
+
# None and fall back to `.data` until the workflow finishes.
|
|
115
|
+
return
|
|
116
|
+
# A completed workflow whose data won't validate is a real mismatch between
|
|
117
|
+
# the installed stjames model and the server response. Fail clearly here
|
|
118
|
+
# rather than as a downstream AttributeError on a None `_workflow`.
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"""\
|
|
121
|
+
Could not parse the completed '{self.workflow_type}' workflow into \
|
|
122
|
+
{self._stjames_class.__name__}. The installed stjames version is likely out of sync with \
|
|
123
|
+
the server (a field is missing, extra, or the wrong type). Underlying validation error:
|
|
124
|
+
{e}"""
|
|
125
|
+
) from e
|
|
111
126
|
|
|
112
127
|
@property
|
|
113
128
|
def data(self) -> dict[str, Any]:
|
|
@@ -546,7 +561,7 @@ Workflow: {self.name}
|
|
|
546
561
|
f.write(response.content)
|
|
547
562
|
|
|
548
563
|
|
|
549
|
-
def extract_smiles(mol: SMILES |
|
|
564
|
+
def extract_smiles(mol: SMILES | StructureInput | dict[str, Any]) -> SMILES:
|
|
550
565
|
"""
|
|
551
566
|
Extract a SMILES string from a molecule input or return the string directly.
|
|
552
567
|
|
|
@@ -575,7 +590,7 @@ def extract_smiles(mol: SMILES | MoleculeInput) -> SMILES:
|
|
|
575
590
|
return smiles
|
|
576
591
|
|
|
577
592
|
|
|
578
|
-
def molecule_to_stjames(mol:
|
|
593
|
+
def molecule_to_stjames(mol: StructureInput | dict[str, Any]) -> stjames.Molecule:
|
|
579
594
|
"""Convert any molecule input type to a stjames.Molecule."""
|
|
580
595
|
match mol:
|
|
581
596
|
case RowanMolecule():
|
|
@@ -590,16 +605,37 @@ def molecule_to_stjames(mol: MoleculeInput) -> stjames.Molecule:
|
|
|
590
605
|
raise TypeError(f"Cannot convert {type(mol)} to stjames.Molecule")
|
|
591
606
|
|
|
592
607
|
|
|
593
|
-
def
|
|
608
|
+
def require_coordinates(mol: StructureInput) -> None:
|
|
594
609
|
"""
|
|
595
|
-
|
|
610
|
+
Validate that a molecule input carries real 3D coordinates.
|
|
611
|
+
|
|
612
|
+
Geometry-based workflows operate on 3D structure, so reject inputs that have no
|
|
613
|
+
geometry rather than silently embedding an arbitrary conformer: a SMILES string
|
|
614
|
+
(no coordinates) or an RDKit molecule with no conformer.
|
|
615
|
+
|
|
616
|
+
:param mol: molecule input to check
|
|
617
|
+
:raises ValueError: if the input has no 3D coordinates
|
|
618
|
+
"""
|
|
619
|
+
if isinstance(mol, str):
|
|
620
|
+
raise ValueError(
|
|
621
|
+
"This workflow requires a 3D structure, not a SMILES string. Build one "
|
|
622
|
+
'explicitly, e.g. rowan.Molecule.from_smiles("CCO") or rowan.Molecule.from_xyz(...).'
|
|
623
|
+
)
|
|
624
|
+
if isinstance(mol, Chem.Mol) and mol.GetNumConformers() == 0:
|
|
625
|
+
raise ValueError(
|
|
626
|
+
"RDKit molecule has no 3D conformer. Embed one first (e.g. "
|
|
627
|
+
"rdkit.Chem.AllChem.EmbedMolecule) or use rowan.Molecule.from_smiles(...)."
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def molecule_to_dict(mol: StructureInput | dict[str, Any]) -> dict[str, Any]:
|
|
632
|
+
"""
|
|
633
|
+
Convert a 3D molecule input to a dict for API submission.
|
|
596
634
|
|
|
597
635
|
:param mol: Molecule as Molecule, stjames.Molecule, RDKit Mol, or dict.
|
|
598
636
|
:returns: Dict representation suitable for API submission.
|
|
599
637
|
"""
|
|
600
638
|
match mol:
|
|
601
|
-
case str():
|
|
602
|
-
return RowanMolecule.from_smiles(mol).to_stjames().model_dump(mode="json")
|
|
603
639
|
case RowanMolecule():
|
|
604
640
|
return mol.to_stjames().model_dump(mode="json")
|
|
605
641
|
case stjames.Molecule():
|
|
@@ -615,8 +651,8 @@ def molecule_to_dict(mol: MoleculeInput) -> dict[str, Any]:
|
|
|
615
651
|
def submit_workflow(
|
|
616
652
|
workflow_type: stjames.WORKFLOW_NAME,
|
|
617
653
|
workflow_data: dict[str, Any] | None = None,
|
|
618
|
-
initial_molecule:
|
|
619
|
-
initial_smiles:
|
|
654
|
+
initial_molecule: StructureInput | dict[str, Any] | None = None,
|
|
655
|
+
initial_smiles: SMILES | None = None,
|
|
620
656
|
name: str | None = None,
|
|
621
657
|
folder_uuid: str | Folder | None = None,
|
|
622
658
|
max_credits: int | None = None,
|
|
@@ -785,8 +821,8 @@ def list_workflows(
|
|
|
785
821
|
def batch_submit_workflow(
|
|
786
822
|
workflow_type: stjames.WORKFLOW_NAME,
|
|
787
823
|
workflow_data: dict[str, Any] | None = None,
|
|
788
|
-
initial_molecules: list[
|
|
789
|
-
initial_smileses: list[
|
|
824
|
+
initial_molecules: list[StructureInput | dict[str, Any]] | None = None,
|
|
825
|
+
initial_smileses: list[SMILES] | None = None,
|
|
790
826
|
names: list[str] | None = None,
|
|
791
827
|
folder_uuid: str | Folder | None = None,
|
|
792
828
|
max_credits: int | None = None,
|