rowan-python 3.1.8__py3-none-any.whl → 3.1.10__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 +2 -0
- rowan/protein.py +62 -20
- rowan/workflows/__init__.py +1 -0
- rowan/workflows/analogue_docking.py +1 -1
- rowan/workflows/base.py +2 -2
- rowan/workflows/docking.py +1 -1
- rowan/workflows/logp.py +80 -0
- rowan/workflows/nmr.py +40 -2
- rowan/workflows/pose_analysis_md.py +4 -2
- rowan/workflows/protein_cofolding.py +6 -2
- rowan/workflows/protein_md.py +30 -4
- rowan/workflows/relative_binding_free_energy_perturbation.py +3 -1
- {rowan_python-3.1.8.dist-info → rowan_python-3.1.10.dist-info}/METADATA +2 -2
- {rowan_python-3.1.8.dist-info → rowan_python-3.1.10.dist-info}/RECORD +16 -15
- {rowan_python-3.1.8.dist-info → rowan_python-3.1.10.dist-info}/WHEEL +0 -0
- {rowan_python-3.1.8.dist-info → rowan_python-3.1.10.dist-info}/licenses/LICENSE +0 -0
rowan/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ from . import constants
|
|
|
3
3
|
from stjames import (
|
|
4
4
|
Atom,
|
|
5
5
|
BandStructure,
|
|
6
|
+
Binder,
|
|
6
7
|
BindingPoseContact,
|
|
7
8
|
HydrationBridgeResidue,
|
|
8
9
|
HydrationSite,
|
|
@@ -24,6 +25,7 @@ from stjames import (
|
|
|
24
25
|
Mode,
|
|
25
26
|
MSAFormat,
|
|
26
27
|
MultiStageOptSettings,
|
|
28
|
+
NMR_SUPPORTED_SOLVENTS,
|
|
27
29
|
OpenConfSettings,
|
|
28
30
|
OptimizationSettings,
|
|
29
31
|
PBCDFTSettings,
|
rowan/protein.py
CHANGED
|
@@ -4,7 +4,8 @@ from datetime import datetime
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, Self
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel
|
|
7
|
+
from pydantic import BaseModel, PrivateAttr
|
|
8
|
+
from stjames.pdb import PDB, pdb_object_to_pdb_filestring
|
|
8
9
|
|
|
9
10
|
from .project import Project
|
|
10
11
|
from .utils import api_client
|
|
@@ -37,6 +38,8 @@ class Protein(BaseModel):
|
|
|
37
38
|
public: bool | None = None
|
|
38
39
|
pocket: list[list[float]] | None = None
|
|
39
40
|
|
|
41
|
+
_workflow_uuid: str | None = PrivateAttr(default=None)
|
|
42
|
+
|
|
40
43
|
def __repr__(self) -> str:
|
|
41
44
|
return f"<Protein name='{self.name}' created_at='{self.created_at}' uuid='{self.uuid}'>"
|
|
42
45
|
|
|
@@ -119,18 +122,24 @@ class Protein(BaseModel):
|
|
|
119
122
|
response.raise_for_status()
|
|
120
123
|
return Protein(**response.json())
|
|
121
124
|
|
|
122
|
-
def refresh(self, in_place: bool = True) -> Self:
|
|
125
|
+
def refresh(self, in_place: bool = True, workflow_uuid: str | None = None) -> Self:
|
|
123
126
|
"""
|
|
124
127
|
Loads protein data
|
|
125
128
|
|
|
129
|
+
:param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
|
|
130
|
+
only through a workflow you can read. Not needed for proteins from a workflow result.
|
|
126
131
|
:returns: protein with loaded data
|
|
127
132
|
"""
|
|
133
|
+
workflow_uuid = workflow_uuid or self._workflow_uuid
|
|
134
|
+
params = {"workflow_uuid": workflow_uuid} if workflow_uuid else None
|
|
128
135
|
with api_client() as client:
|
|
129
|
-
response = client.get(f"/protein/{self.uuid}")
|
|
136
|
+
response = client.get(f"/protein/{self.uuid}", params=params)
|
|
130
137
|
response.raise_for_status()
|
|
131
138
|
protein_data = response.json()
|
|
132
139
|
if not in_place:
|
|
133
|
-
|
|
140
|
+
refreshed = self.__class__.model_validate(protein_data)
|
|
141
|
+
refreshed._workflow_uuid = workflow_uuid
|
|
142
|
+
return refreshed
|
|
134
143
|
|
|
135
144
|
self.name = protein_data.get("name")
|
|
136
145
|
self.data = protein_data.get("data")
|
|
@@ -138,6 +147,7 @@ class Protein(BaseModel):
|
|
|
138
147
|
self.pocket = protein_data.get("pocket")
|
|
139
148
|
self.sanitized = protein_data.get("sanitized")
|
|
140
149
|
self.used_in_workflow = protein_data.get("used_in_workflow")
|
|
150
|
+
self._workflow_uuid = workflow_uuid
|
|
141
151
|
return self
|
|
142
152
|
|
|
143
153
|
def update(
|
|
@@ -291,7 +301,7 @@ class Protein(BaseModel):
|
|
|
291
301
|
|
|
292
302
|
raise RuntimeError(f"Protein preparation timed out after {timeout:.0f}s for {self.uuid}.")
|
|
293
303
|
|
|
294
|
-
def validate_protein_forcefield(self,
|
|
304
|
+
def validate_protein_forcefield(self, exclude_residues: list[str | int] | None = None) -> None:
|
|
295
305
|
"""
|
|
296
306
|
Validate that this protein can be parameterized with the MD forcefield.
|
|
297
307
|
|
|
@@ -299,16 +309,24 @@ class Protein(BaseModel):
|
|
|
299
309
|
recognized by OpenMM and that there are no clashing atoms. Call this
|
|
300
310
|
before submitting any MD workflow to catch preparation issues early.
|
|
301
311
|
|
|
302
|
-
Ligand residues (
|
|
303
|
-
separately by the MD workflow from the provided SMILES.
|
|
312
|
+
Ligand residues (`LIG`) are always excluded — they are parameterized
|
|
313
|
+
separately by the MD workflow from the provided SMILES. Pass the keys of a
|
|
314
|
+
binder's `small_molecules` to exclude those as well.
|
|
315
|
+
|
|
316
|
+
A name is matched case-insensitively and excludes only the first residue
|
|
317
|
+
with that name, so further copies are still validated. Integer entries are
|
|
318
|
+
0-based indices into the protein's sorted non-polymer records, and reference
|
|
319
|
+
a record without naming it.
|
|
304
320
|
|
|
305
321
|
If validation fails, try re-preparing with ``remove_invalid_hydrogens=True``:
|
|
306
322
|
``protein.prepare(remove_invalid_hydrogens=True)``
|
|
307
323
|
|
|
308
|
-
:param
|
|
324
|
+
:param exclude_residues: additional residue names and/or 0-based non-polymer indices to skip
|
|
309
325
|
:raises requests.HTTPError: if validation fails or the API request fails.
|
|
310
326
|
"""
|
|
311
|
-
|
|
327
|
+
entries: list[str | int] = ["LIG"]
|
|
328
|
+
entries += [r.upper() if isinstance(r, str) else r for r in exclude_residues or []]
|
|
329
|
+
excluded = list(dict.fromkeys(entries))
|
|
312
330
|
with api_client() as client:
|
|
313
331
|
response = client.post(
|
|
314
332
|
f"/protein/{self.uuid}/validate_forcefield",
|
|
@@ -316,42 +334,66 @@ class Protein(BaseModel):
|
|
|
316
334
|
)
|
|
317
335
|
response.raise_for_status()
|
|
318
336
|
|
|
319
|
-
def download_pdb_file(
|
|
337
|
+
def download_pdb_file(
|
|
338
|
+
self,
|
|
339
|
+
path: Path | str | None = None,
|
|
340
|
+
name: str | None = None,
|
|
341
|
+
workflow_uuid: str | None = None,
|
|
342
|
+
) -> None:
|
|
320
343
|
"""
|
|
321
|
-
Downloads the PDB file for a protein
|
|
344
|
+
Downloads the PDB file for a protein.
|
|
345
|
+
|
|
346
|
+
Builds the file locally from this protein's data, calling `refresh()` if needed, so it
|
|
347
|
+
works anywhere `refresh()` does — including proteins reachable only through a workflow.
|
|
322
348
|
|
|
323
349
|
:param path: Directory to save the file to (defaults to current directory)
|
|
324
350
|
:param name: Optional custom name for the file (defaults to protein name)
|
|
351
|
+
:param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
|
|
352
|
+
only through a workflow you can read. Not needed for proteins from a workflow result.
|
|
325
353
|
:raises requests.HTTPError: if the request to the API fails
|
|
326
354
|
"""
|
|
327
355
|
path = Path(path) if path is not None else Path.cwd()
|
|
328
356
|
|
|
329
357
|
path.mkdir(parents=True, exist_ok=True)
|
|
330
358
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
if self.data is None:
|
|
360
|
+
self.refresh(workflow_uuid=workflow_uuid)
|
|
361
|
+
|
|
362
|
+
pdb_object = PDB.model_validate(self.data)
|
|
363
|
+
pdb_string = pdb_object_to_pdb_filestring(
|
|
364
|
+
pdb=pdb_object,
|
|
365
|
+
header=True,
|
|
366
|
+
source=True,
|
|
367
|
+
keyword=True,
|
|
368
|
+
crystallography=True,
|
|
369
|
+
remark=False,
|
|
370
|
+
)
|
|
334
371
|
|
|
335
|
-
file_path = path / f"{name or self.name}.pdb"
|
|
336
|
-
with open(file_path, "
|
|
337
|
-
f.write(
|
|
372
|
+
file_path = path / f"{name or self.name or self.uuid}.pdb"
|
|
373
|
+
with open(file_path, "w") as f:
|
|
374
|
+
f.write(pdb_string)
|
|
338
375
|
|
|
339
376
|
|
|
340
|
-
def retrieve_protein(uuid: str) -> Protein:
|
|
377
|
+
def retrieve_protein(uuid: str, workflow_uuid: str | None = None) -> Protein:
|
|
341
378
|
"""
|
|
342
379
|
Retrieves a protein from the API using its UUID.
|
|
343
380
|
|
|
344
381
|
:param uuid: UUID of the protein to retrieve.
|
|
382
|
+
:param workflow_uuid: UUID of a workflow referencing this protein, for proteins reachable
|
|
383
|
+
only through a workflow you can read. Not needed for proteins from a workflow result.
|
|
345
384
|
:returns: Protein object representing the retrieved protein.
|
|
346
385
|
:raises requests.HTTPError: if the request to the API fails.
|
|
347
386
|
"""
|
|
387
|
+
params = {"workflow_uuid": workflow_uuid} if workflow_uuid else None
|
|
348
388
|
|
|
349
389
|
with api_client() as client:
|
|
350
|
-
response = client.get(f"/protein/{uuid}")
|
|
390
|
+
response = client.get(f"/protein/{uuid}", params=params)
|
|
351
391
|
response.raise_for_status()
|
|
352
392
|
protein_data = response.json()
|
|
353
393
|
|
|
354
|
-
|
|
394
|
+
protein = Protein(**protein_data)
|
|
395
|
+
protein._workflow_uuid = workflow_uuid
|
|
396
|
+
return protein
|
|
355
397
|
|
|
356
398
|
|
|
357
399
|
def list_proteins(
|
rowan/workflows/__init__.py
CHANGED
|
@@ -83,6 +83,7 @@ from .interaction_energy_decomposition import (
|
|
|
83
83
|
)
|
|
84
84
|
from .ion_mobility import IonMobilityResult, submit_ion_mobility_workflow
|
|
85
85
|
from .irc import IRCResult, submit_irc_workflow
|
|
86
|
+
from .logp import LogPResult, submit_logp_workflow
|
|
86
87
|
from .macropka import (
|
|
87
88
|
MacropKaMicrostate,
|
|
88
89
|
MacropKaResult,
|
|
@@ -160,7 +160,7 @@ class AnalogueDockingResult(WorkflowResult):
|
|
|
160
160
|
|
|
161
161
|
cache_key = f"complex_{smiles}_{index}"
|
|
162
162
|
if cache_key not in self._cache:
|
|
163
|
-
self._cache[cache_key] = retrieve_protein(uuid)
|
|
163
|
+
self._cache[cache_key] = retrieve_protein(uuid, workflow_uuid=self.workflow_uuid)
|
|
164
164
|
return self._cache[cache_key]
|
|
165
165
|
|
|
166
166
|
def get_complexes(self, smiles: str) -> list[Protein]:
|
rowan/workflows/base.py
CHANGED
|
@@ -144,10 +144,10 @@ the server (a field is missing, extra, or the wrong type). Underlying validation
|
|
|
144
144
|
RESULT_REGISTRY: dict[str, type[WorkflowResult]] = {}
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
def register_result(workflow_type: str) -> Callable[[type[
|
|
147
|
+
def register_result[R: WorkflowResult](workflow_type: str) -> Callable[[type[R]], type[R]]:
|
|
148
148
|
"""Decorator to register a result class for a workflow type."""
|
|
149
149
|
|
|
150
|
-
def decorator(cls: type[
|
|
150
|
+
def decorator(cls: type[R]) -> type[R]:
|
|
151
151
|
RESULT_REGISTRY[workflow_type] = cls
|
|
152
152
|
return cls
|
|
153
153
|
|
rowan/workflows/docking.py
CHANGED
|
@@ -133,7 +133,7 @@ class DockingResult(WorkflowResult):
|
|
|
133
133
|
|
|
134
134
|
cache_key = f"complex_{index}"
|
|
135
135
|
if cache_key not in self._cache:
|
|
136
|
-
self._cache[cache_key] = retrieve_protein(uuid)
|
|
136
|
+
self._cache[cache_key] = retrieve_protein(uuid, workflow_uuid=self.workflow_uuid)
|
|
137
137
|
return self._cache[cache_key]
|
|
138
138
|
|
|
139
139
|
def get_complexes(self) -> list[Protein]:
|
rowan/workflows/logp.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""logP workflow - predict the octanol/water partition coefficient."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import stjames
|
|
6
|
+
|
|
7
|
+
from ..folder import Folder
|
|
8
|
+
from ..utils import api_client
|
|
9
|
+
from .base import SMILES, Workflow, WorkflowResult, extract_smiles, register_result
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register_result("logp")
|
|
13
|
+
class LogPResult(WorkflowResult):
|
|
14
|
+
"""Result from a logP workflow."""
|
|
15
|
+
|
|
16
|
+
_stjames_class = stjames.LogPWorkflow
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
return f"<LogPResult logp={self.logp}>"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def logp(self) -> float | None:
|
|
23
|
+
"""Predicted octanol/water partition coefficient."""
|
|
24
|
+
return self._workflow.logp
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def submit_logp_workflow(
|
|
28
|
+
initial_smiles: SMILES,
|
|
29
|
+
method: Literal["chemprop_sangster2026", "crippen", "cosmors"] = "chemprop_sangster2026",
|
|
30
|
+
name: str = "LogP Workflow",
|
|
31
|
+
folder_uuid: str | None = None,
|
|
32
|
+
folder: Folder | None = None,
|
|
33
|
+
max_credits: int | None = None,
|
|
34
|
+
webhook_url: str | None = None,
|
|
35
|
+
is_draft: bool = False,
|
|
36
|
+
) -> Workflow:
|
|
37
|
+
"""
|
|
38
|
+
Submits a logP workflow to the API.
|
|
39
|
+
|
|
40
|
+
:param initial_smiles: Molecule to predict logP for. Accepts a SMILES string or any
|
|
41
|
+
molecule type (RowanMolecule, stjames.Molecule, RDKit Mol, or dict). The molecule
|
|
42
|
+
must have a SMILES string associated with it, as this workflow is SMILES-based and
|
|
43
|
+
does not use 3D coordinates.
|
|
44
|
+
:param method: logP prediction method:
|
|
45
|
+
- "chemprop_sangster2026": chemprop v2 D-MPNN trained on experimental octanol/water
|
|
46
|
+
logP from the Sangster dataset.
|
|
47
|
+
- "crippen": RDKit implementation of the Wildman-Crippen atom-contribution model.
|
|
48
|
+
- "cosmors": Boltzmann-weighted COSMO-RS logP over a conformer ensemble.
|
|
49
|
+
:param name: Name of the workflow.
|
|
50
|
+
:param folder_uuid: UUID of the folder to store the workflow in.
|
|
51
|
+
:param folder: Folder object to store the workflow in.
|
|
52
|
+
:param max_credits: Maximum number of credits to use for the workflow.
|
|
53
|
+
:param webhook_url: URL that Rowan will POST to when the workflow completes.
|
|
54
|
+
:param is_draft: If True, submit the workflow as a draft without starting execution.
|
|
55
|
+
:returns: Workflow object representing the submitted workflow.
|
|
56
|
+
:raises ValueError: If the molecule has no SMILES associated with it.
|
|
57
|
+
:raises requests.HTTPError: if the request to the API fails.
|
|
58
|
+
"""
|
|
59
|
+
if folder and folder_uuid:
|
|
60
|
+
raise ValueError("Provide either `folder` or `folder_uuid`, not both.")
|
|
61
|
+
if folder:
|
|
62
|
+
folder_uuid = folder.uuid
|
|
63
|
+
initial_smiles = extract_smiles(initial_smiles)
|
|
64
|
+
workflow = stjames.LogPWorkflow(initial_smiles=initial_smiles, logp_method=method)
|
|
65
|
+
|
|
66
|
+
data = {
|
|
67
|
+
"workflow_type": "logp",
|
|
68
|
+
"workflow_data": workflow.model_dump(mode="json"),
|
|
69
|
+
"initial_smiles": initial_smiles,
|
|
70
|
+
"name": name,
|
|
71
|
+
"folder_uuid": folder_uuid,
|
|
72
|
+
"max_credits": max_credits,
|
|
73
|
+
"webhook_url": webhook_url,
|
|
74
|
+
"is_draft": is_draft,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
with api_client() as client:
|
|
78
|
+
response = client.post("/workflow", json=data)
|
|
79
|
+
response.raise_for_status()
|
|
80
|
+
return Workflow(**response.json())
|
rowan/workflows/nmr.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""NMR workflow - predict Nuclear Magnetic Resonance spectra."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
import stjames
|
|
6
7
|
|
|
@@ -98,6 +99,32 @@ class NMRResult(WorkflowResult):
|
|
|
98
99
|
return self._workflow.symmetry_equivalent_nuclei
|
|
99
100
|
|
|
100
101
|
|
|
102
|
+
def _nmr_multistage_opt_settings(solvent: SolventInput) -> stjames.MultiStageOptSettings:
|
|
103
|
+
"""
|
|
104
|
+
Build NMR optimization settings, adding a solvated AIMNet2 singlepoint.
|
|
105
|
+
|
|
106
|
+
Optimization runs gas-phase (matching MagNet); the solvated singlepoint, whose solvent model
|
|
107
|
+
is looked up from stjames.NMR_SOLVENT_MODELS, reweights the conformer ensemble.
|
|
108
|
+
|
|
109
|
+
:param solvent: solvent for the prediction
|
|
110
|
+
:returns: multi-stage optimization settings
|
|
111
|
+
"""
|
|
112
|
+
singlepoint_settings = stjames.Settings(
|
|
113
|
+
method=stjames.Method.AIMNET2_WB97MD3,
|
|
114
|
+
tasks=[stjames.Task.ENERGY],
|
|
115
|
+
solvent_settings=stjames.SolventSettings(
|
|
116
|
+
solvent=solvent, model=stjames.NMR_SOLVENT_MODELS[stjames.Solvent(solvent)]
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return stjames.MultiStageOptSettings(
|
|
121
|
+
optimization_settings=[
|
|
122
|
+
stjames.Settings(method=stjames.Method.AIMNET2_WB97MD3, tasks=[stjames.Task.OPTIMIZE])
|
|
123
|
+
],
|
|
124
|
+
singlepoint_settings=singlepoint_settings,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
101
128
|
def submit_nmr_workflow(
|
|
102
129
|
initial_molecule: StructureInput,
|
|
103
130
|
solvent: SolventInput = "chloroform",
|
|
@@ -114,7 +141,10 @@ def submit_nmr_workflow(
|
|
|
114
141
|
Submits a Nuclear Magnetic Resonance (NMR) prediction workflow to the API.
|
|
115
142
|
|
|
116
143
|
:param initial_molecule: Molecule to predict NMR spectra for.
|
|
117
|
-
:param solvent: Solvent for NMR calculation (default: chloroform).
|
|
144
|
+
:param solvent: Solvent for NMR calculation (default: chloroform). Must be an NMR-supported
|
|
145
|
+
solvent (see rowan.NMR_SUPPORTED_SOLVENTS); others raise ValueError. A solvated AIMNet2
|
|
146
|
+
singlepoint reweights the conformer ensemble, using CPCM-X where supported and
|
|
147
|
+
otherwise ALPB.
|
|
118
148
|
:param do_csearch: Whether to perform a conformational search. Requires do_optimization.
|
|
119
149
|
:param do_optimization: Whether to optimize conformer geometries.
|
|
120
150
|
:param name: Name of the workflow.
|
|
@@ -127,6 +157,12 @@ def submit_nmr_workflow(
|
|
|
127
157
|
:raises requests.HTTPError: if the request to the API fails.
|
|
128
158
|
"""
|
|
129
159
|
require_coordinates(initial_molecule)
|
|
160
|
+
if stjames.Solvent(solvent) not in stjames.NMR_SUPPORTED_SOLVENTS:
|
|
161
|
+
supported = ", ".join(sorted(s.value for s in stjames.NMR_SUPPORTED_SOLVENTS))
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"{stjames.Solvent(solvent).value!r} is not an NMR-supported solvent. "
|
|
164
|
+
f"NMR-supported solvents: {supported}."
|
|
165
|
+
)
|
|
130
166
|
if do_csearch and not do_optimization:
|
|
131
167
|
raise ValueError(
|
|
132
168
|
"`do_optimization` must be True when `do_csearch` is True; the conformers from "
|
|
@@ -138,13 +174,15 @@ def submit_nmr_workflow(
|
|
|
138
174
|
folder_uuid = folder.uuid
|
|
139
175
|
mol_dict = molecule_to_dict(initial_molecule)
|
|
140
176
|
|
|
141
|
-
workflow_data = {"initial_molecule": mol_dict, "solvent": solvent}
|
|
177
|
+
workflow_data: dict[str, Any] = {"initial_molecule": mol_dict, "solvent": solvent}
|
|
142
178
|
|
|
143
179
|
if not do_csearch:
|
|
144
180
|
workflow_data["conf_gen_settings"] = None
|
|
145
181
|
|
|
146
182
|
if not do_optimization:
|
|
147
183
|
workflow_data["multistage_opt_settings"] = None
|
|
184
|
+
else:
|
|
185
|
+
workflow_data["multistage_opt_settings"] = _nmr_multistage_opt_settings(solvent)
|
|
148
186
|
|
|
149
187
|
workflow = stjames.NMRSpectroscopyWorkflow.model_validate(workflow_data)
|
|
150
188
|
|
|
@@ -62,7 +62,7 @@ class PoseAnalysisMDResult(WorkflowResult):
|
|
|
62
62
|
return [
|
|
63
63
|
TrajectoryResult(
|
|
64
64
|
uuid=t.uuid,
|
|
65
|
-
ligand_rmsd=t.
|
|
65
|
+
ligand_rmsd=t.binder_rmsd,
|
|
66
66
|
contacts=t.contacts,
|
|
67
67
|
sasa=t.sasa,
|
|
68
68
|
polar_sasa=t.polar_sasa,
|
|
@@ -104,7 +104,9 @@ class PoseAnalysisMDResult(WorkflowResult):
|
|
|
104
104
|
if not (uuid := self.minimized_protein_uuid):
|
|
105
105
|
return None
|
|
106
106
|
if "minimized_protein" not in self._cache:
|
|
107
|
-
self._cache["minimized_protein"] = retrieve_protein(
|
|
107
|
+
self._cache["minimized_protein"] = retrieve_protein(
|
|
108
|
+
uuid, workflow_uuid=self.workflow_uuid
|
|
109
|
+
)
|
|
108
110
|
return self._cache["minimized_protein"]
|
|
109
111
|
|
|
110
112
|
@property
|
|
@@ -165,7 +165,9 @@ class ProteinCofoldingResult(WorkflowResult):
|
|
|
165
165
|
if not (uuid := self.predicted_structure_uuid):
|
|
166
166
|
return None
|
|
167
167
|
if "predicted_structure" not in self._cache:
|
|
168
|
-
self._cache["predicted_structure"] = retrieve_protein(
|
|
168
|
+
self._cache["predicted_structure"] = retrieve_protein(
|
|
169
|
+
uuid, workflow_uuid=self.workflow_uuid
|
|
170
|
+
)
|
|
169
171
|
return self._cache["predicted_structure"]
|
|
170
172
|
|
|
171
173
|
def get_refined_structure(self) -> Protein | None:
|
|
@@ -178,7 +180,9 @@ class ProteinCofoldingResult(WorkflowResult):
|
|
|
178
180
|
if not (uuid := self.predicted_refined_structure_uuid):
|
|
179
181
|
return None
|
|
180
182
|
if "refined_structure" not in self._cache:
|
|
181
|
-
self._cache["refined_structure"] = retrieve_protein(
|
|
183
|
+
self._cache["refined_structure"] = retrieve_protein(
|
|
184
|
+
uuid, workflow_uuid=self.workflow_uuid
|
|
185
|
+
)
|
|
182
186
|
return self._cache["refined_structure"]
|
|
183
187
|
|
|
184
188
|
@property
|
rowan/workflows/protein_md.py
CHANGED
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
import stjames
|
|
7
|
-
from stjames import GreedyClusteringSettings, KMeansClusteringSettings
|
|
7
|
+
from stjames import Binder, GreedyClusteringSettings, KMeansClusteringSettings
|
|
8
8
|
|
|
9
9
|
from ..folder import Folder
|
|
10
10
|
from ..protein import Protein, retrieve_protein
|
|
@@ -87,7 +87,9 @@ class ProteinMDResult(WorkflowResult):
|
|
|
87
87
|
if not (uuid := self.minimized_protein_uuid):
|
|
88
88
|
return None
|
|
89
89
|
if "minimized_protein" not in self._cache:
|
|
90
|
-
self._cache["minimized_protein"] = retrieve_protein(
|
|
90
|
+
self._cache["minimized_protein"] = retrieve_protein(
|
|
91
|
+
uuid, workflow_uuid=self.workflow_uuid
|
|
92
|
+
)
|
|
91
93
|
return self._cache["minimized_protein"]
|
|
92
94
|
|
|
93
95
|
@property
|
|
@@ -171,6 +173,10 @@ def submit_protein_md_workflow(
|
|
|
171
173
|
ionic_strength_M: float = 0.0,
|
|
172
174
|
water_buffer: float = 10.0,
|
|
173
175
|
save_solvent: bool = False,
|
|
176
|
+
num_solvent_to_save: int | None = None,
|
|
177
|
+
binder: Binder | None = None,
|
|
178
|
+
protein_restraint_cutoff: float | None = None,
|
|
179
|
+
protein_restraint_constant: float = 100,
|
|
174
180
|
analysis_interval_ps: float | None = None,
|
|
175
181
|
clustering: KMeansClusteringSettings | GreedyClusteringSettings | None = None,
|
|
176
182
|
validate_forcefield: bool = True,
|
|
@@ -198,13 +204,26 @@ def submit_protein_md_workflow(
|
|
|
198
204
|
:param ionic_strength_M: ionic strength of the solution, in M (molar)
|
|
199
205
|
:param water_buffer: amount of water to add around the protein, in A
|
|
200
206
|
:param save_solvent: whether solvent should be saved
|
|
207
|
+
:param num_solvent_to_save: keep this many solvent molecules nearest the binder, or all if None;
|
|
208
|
+
only meaningful when save_solvent is True and a binder is present
|
|
209
|
+
:param binder: optional binder specification (protein chains and/or small molecules).
|
|
210
|
+
When set, per-frame MM/GBSA scores are computed against the whole binder.
|
|
211
|
+
Per-frame binder RMSD is populated only when the binder is a single component
|
|
212
|
+
(one small molecule → heavy-atom RMSD; one binder chain → backbone N/CA/C/O RMSD);
|
|
213
|
+
it is empty for multi-molecule, multi-chain, or combined chain+molecule binders.
|
|
214
|
+
:param protein_restraint_cutoff: cutoff distance from the binder past which Cα atoms are
|
|
215
|
+
harmonically restrained, in Å; None disables restraints
|
|
216
|
+
:param protein_restraint_constant: force constant for Cα backbone restraints, in kcal/mol/Ų
|
|
201
217
|
:param analysis_interval_ps: Interval at which to compute per-frame SASA and polar SASA, in ps.
|
|
202
218
|
None disables those analyses.
|
|
203
219
|
:param clustering: How to cluster trajectory frames. None disables clustering; pass a
|
|
204
220
|
KMeansClusteringSettings (num_clusters) or GreedyClusteringSettings (cutoff_angstrom).
|
|
205
221
|
:param validate_forcefield: if True (default), validate the protein forcefield
|
|
206
222
|
compatibility before submitting. Raises an error early if the protein cannot
|
|
207
|
-
be parameterized or has clashing residues.
|
|
223
|
+
be parameterized or has clashing residues. Binder small molecules are skipped,
|
|
224
|
+
whether given by residue name or by index, since they are parameterized from their
|
|
225
|
+
SMILES rather than the protein forcefield; cofactors, metals, and glycans outside
|
|
226
|
+
the binder are still validated.
|
|
208
227
|
:param name: Name of the workflow.
|
|
209
228
|
:param folder_uuid: UUID of the folder to place the workflow in.
|
|
210
229
|
:param folder: Folder object to store the workflow in.
|
|
@@ -222,7 +241,10 @@ def submit_protein_md_workflow(
|
|
|
222
241
|
protein = protein.uuid
|
|
223
242
|
|
|
224
243
|
if validate_forcefield:
|
|
225
|
-
|
|
244
|
+
exclude_residues = (
|
|
245
|
+
list(binder.small_molecules) if binder is not None and binder.small_molecules else None
|
|
246
|
+
)
|
|
247
|
+
Protein(uuid=protein).validate_protein_forcefield(exclude_residues=exclude_residues)
|
|
226
248
|
|
|
227
249
|
workflow = stjames.ProteinMolecularDynamicsWorkflow(
|
|
228
250
|
protein=protein,
|
|
@@ -238,6 +260,10 @@ def submit_protein_md_workflow(
|
|
|
238
260
|
ionic_strength_M=ionic_strength_M,
|
|
239
261
|
water_buffer=water_buffer,
|
|
240
262
|
save_solvent=save_solvent,
|
|
263
|
+
num_solvent_to_save=num_solvent_to_save,
|
|
264
|
+
binder=binder,
|
|
265
|
+
protein_restraint_cutoff=protein_restraint_cutoff,
|
|
266
|
+
protein_restraint_constant=protein_restraint_constant,
|
|
241
267
|
analysis_interval_ps=analysis_interval_ps,
|
|
242
268
|
clustering=clustering,
|
|
243
269
|
)
|
|
@@ -90,7 +90,9 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
|
|
|
90
90
|
@property
|
|
91
91
|
def protein(self) -> Protein:
|
|
92
92
|
"""Prepared protein structure used as the simulation target."""
|
|
93
|
-
|
|
93
|
+
protein = Protein(uuid=str(self._workflow.protein))
|
|
94
|
+
protein._workflow_uuid = self.workflow_uuid
|
|
95
|
+
return protein
|
|
94
96
|
|
|
95
97
|
@property
|
|
96
98
|
def settings(self) -> TMDRBFESettings:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rowan-python
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.10
|
|
4
4
|
Summary: Rowan Python Library
|
|
5
5
|
Project-URL: Homepage, https://github.com/rowansci/rowan-client
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/rowansci/rowan-client/issues
|
|
@@ -11,7 +11,7 @@ Requires-Dist: httpx
|
|
|
11
11
|
Requires-Dist: nest-asyncio
|
|
12
12
|
Requires-Dist: rdkit
|
|
13
13
|
Requires-Dist: setuptools
|
|
14
|
-
Requires-Dist: stjames
|
|
14
|
+
Requires-Dist: stjames<0.0.238,>=0.0.237
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
17
|
# Rowan Python Library
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
rowan/__init__.py,sha256=
|
|
1
|
+
rowan/__init__.py,sha256=MhgIDJjrk8M_O9NtBL9I2_e5iTHCcc7Ju3XbBi2wtUA,1788
|
|
2
2
|
rowan/api_keys.py,sha256=TvG5l5MmQ3Qt8Z3Y7jCA_lcrwEHuI7xHy-KLbIdQ8_A,4793
|
|
3
3
|
rowan/calculation.py,sha256=nuUtLTtn9D-FCM9TJl3h5euBgIYqa5fr5r_PSNBS7Xo,3809
|
|
4
4
|
rowan/config.py,sha256=TejQKSxnzNKKTNL9-2bCLq6RvAh54oVA5Ivl1p_ZT8Q,20899
|
|
@@ -6,16 +6,16 @@ rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
|
|
|
6
6
|
rowan/folder.py,sha256=RRg68dObluJ2RCTKX3rb7RxziOXOQUYxkfQSU2R5VMk,13708
|
|
7
7
|
rowan/molecule.py,sha256=MFx0D3_LJm1G3UiDogkfCHvae31gR6xo_N0LzrNC-ks,15733
|
|
8
8
|
rowan/project.py,sha256=4f1XGRe_EADjbd5uFGPvsVXz9s3t4ZiL8loTBlahn7A,5404
|
|
9
|
-
rowan/protein.py,sha256=
|
|
9
|
+
rowan/protein.py,sha256=QsEJ0542ElJlk9jIQPwRNKU2J3CW03t6JDXuaf0OdUw,19940
|
|
10
10
|
rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
rowan/test_utils.py,sha256=DTuZqftx6ycIatajAxSGWgDaGglLvTw2TYnVJHiiNdo,1936
|
|
12
12
|
rowan/types.py,sha256=MvcYvEYr6N9RXLal44MqsnMxoWfcBa-VLD_rGsW4HOc,591
|
|
13
13
|
rowan/user.py,sha256=rOCYVhSoRY71fCG_OCSD7cLAWWnAMANWGitF3ogfq30,6212
|
|
14
14
|
rowan/utils.py,sha256=J-3o9_ZlOrl4_ZuuC1yjEOPdD1IkKO0BOXBncV3Ij7c,5670
|
|
15
|
-
rowan/workflows/__init__.py,sha256=
|
|
15
|
+
rowan/workflows/__init__.py,sha256=O6JLuWa_Hgo6FuaLQ1oB_t8uSS0OQz5NN0g9Jv79MFU,4837
|
|
16
16
|
rowan/workflows/admet.py,sha256=jDx1Kkgmp0XaeojPzaCFY_yUwvqtSM7Cezps-93cqPw,2865
|
|
17
|
-
rowan/workflows/analogue_docking.py,sha256=
|
|
18
|
-
rowan/workflows/base.py,sha256=
|
|
17
|
+
rowan/workflows/analogue_docking.py,sha256=Zs42YjX1DFiSCXelwPpsCCHWKWJFCHPNcihIyISkVe8,11054
|
|
18
|
+
rowan/workflows/base.py,sha256=njMY1Jf-oZbhVvDsMb389I1J1T6HYN4WMOuZi0ijeKk,32928
|
|
19
19
|
rowan/workflows/basic_calculation.py,sha256=EovymT6fV6mxmt4G4kRtiskSPGB7w7KAHqBaGtgQI2Q,12941
|
|
20
20
|
rowan/workflows/batch_docking.py,sha256=ePnb1hcsmnUfffl1-k9HNkOC09RjY9bZS9XcqAGNeFs,3621
|
|
21
21
|
rowan/workflows/bde.py,sha256=1ieYUeu-5Lg637eSjhbRr_6qSIiIcjhsLOzaA4zbg3o,6681
|
|
@@ -24,7 +24,7 @@ rowan/workflows/conformer_search.py,sha256=9_DpRfmeNWIVm68Y95zcHCK5Q0yt9le6W70rM
|
|
|
24
24
|
rowan/workflows/constants.py,sha256=el8jWE9gnGTLNWn5_n_V0H362vIRneOqgy7BOQ8CScg,575
|
|
25
25
|
rowan/workflows/covalent_inhibitor_scan.py,sha256=BNqGLE2CjSlkOlXj_7KcsX9GEstMD3Ts-v2qIlRWK48,4717
|
|
26
26
|
rowan/workflows/descriptors.py,sha256=QjjpzPDpAO_EhPOK--gle6sGpVHMpdAs_yrvC0oiseY,3092
|
|
27
|
-
rowan/workflows/docking.py,sha256=
|
|
27
|
+
rowan/workflows/docking.py,sha256=8WUNIacm41xRP9OL1RWalS4lOniAGZivhCT79cBrUY8,9425
|
|
28
28
|
rowan/workflows/double_ended_ts_search.py,sha256=nse7oK-8R5h_FtpZZAzes0TQCs2XOdEbdyZFOECK8B0,8193
|
|
29
29
|
rowan/workflows/electronic_properties.py,sha256=MXbsI4OHyXWt3f4Qc5g7-SNjyyEoYzocWVdcP1H06sY,8436
|
|
30
30
|
rowan/workflows/fukui.py,sha256=Lyx3FgSdidzPMj-PpSzOb9uXCZj2S_52p8zBYxBbprg,4412
|
|
@@ -32,27 +32,28 @@ rowan/workflows/hydrogen_bond_donor_acceptor_strength.py,sha256=xgFFemIZpIOkgkiY
|
|
|
32
32
|
rowan/workflows/interaction_energy_decomposition.py,sha256=wK9Mc9uGmFBG8H03LRRRz3nZzM6p7Z2ol1yiZbUB5ec,6176
|
|
33
33
|
rowan/workflows/ion_mobility.py,sha256=GqVVPRffaBJvygB3mwbMJq2aW7y19J3AnkDx_ZMIRss,4021
|
|
34
34
|
rowan/workflows/irc.py,sha256=qA8rLvhSkyOBBDtUw8Yq1iaRHiCUil0pI_HsMhVcuEQ,12069
|
|
35
|
+
rowan/workflows/logp.py,sha256=7a5scVVGFwtbHsQZm8pAzubHEtokf5gt0VIxgOC8698,3107
|
|
35
36
|
rowan/workflows/macropka.py,sha256=IxGwr9LziIwj_x6pXEhJN6SywS_KULziuYoETNqM370,5684
|
|
36
37
|
rowan/workflows/membrane_permeability.py,sha256=wHZ1IPlXwBB343uVAm7EuhedcMPd-Uy4jMqHfXRHwqY,4680
|
|
37
38
|
rowan/workflows/msa.py,sha256=k_FlxxtdgvmxDN3LyLDEQeFfI8_lyBJz1KFtz3S4oNo,4816
|
|
38
39
|
rowan/workflows/multistage_optimization.py,sha256=04kZPC3SmVOxqEQOwyBrS3DlelM-b8e531T_LuVt3tU,7385
|
|
39
|
-
rowan/workflows/nmr.py,sha256=
|
|
40
|
+
rowan/workflows/nmr.py,sha256=V0-vHEadlbHk5dUmOZg-DSqkDXm4sF-xNsUZRTk1arM,7209
|
|
40
41
|
rowan/workflows/pka.py,sha256=NZ-3i-cH_b7YOHj2iq0roXEcZmC8SYapmzMOX6ObRqw,8546
|
|
41
42
|
rowan/workflows/pocket_detection.py,sha256=aGHY0puxekp4c4nsNYHcvKCe1fsetygL04BcSvNFvE8,3864
|
|
42
|
-
rowan/workflows/pose_analysis_md.py,sha256=
|
|
43
|
+
rowan/workflows/pose_analysis_md.py,sha256=U_OzWB8eaffX5SoNnR8eAXD8D22abIzqb-qPoyUuRi8,11831
|
|
43
44
|
rowan/workflows/protein_binder_design.py,sha256=pgywTQDuXHplZYka-61_S6CC4WTPDCwJrbupp825eC4,9281
|
|
44
|
-
rowan/workflows/protein_cofolding.py,sha256=
|
|
45
|
-
rowan/workflows/protein_md.py,sha256=
|
|
45
|
+
rowan/workflows/protein_cofolding.py,sha256=MTetn8zwd14faH1W1tbmdozIDvkleo5yt66sSlVQYaY,16561
|
|
46
|
+
rowan/workflows/protein_md.py,sha256=I0boTTtItBtUa8qhYsWClC5_0YT_MjcuAffsyzIp9tg,11862
|
|
46
47
|
rowan/workflows/rbfe_graph.py,sha256=0eMfvwtQhyajUC8T-bhdcYNKi6Pkja6x6dHjCnEwxPw,8081
|
|
47
48
|
rowan/workflows/redox_potential.py,sha256=bBeT1K9XGVpjCFZeeuv7Vtr3EYHN2okJ4LqYc3gMV04,5427
|
|
48
|
-
rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=
|
|
49
|
+
rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=xyVtmVYT008AfVgWbP-oaLvgYf7siH_HjX7_PJxdG1w,15425
|
|
49
50
|
rowan/workflows/scan.py,sha256=R5Y6Md3ohPoFVJ4NYvx3_k3JNwMnVw1CDsNPD9S8sr4,7518
|
|
50
51
|
rowan/workflows/solubility.py,sha256=ulZwdG8JRl0RCwWPatYUZGMhlAZeb6wqjVbvzQYFKmE,8555
|
|
51
52
|
rowan/workflows/solvent_dependent_conformers.py,sha256=Z2xESmcM8WcTsR2TSTUa--RrhrQ-5ri_WagD9wJeVwc,6812
|
|
52
53
|
rowan/workflows/spin_states.py,sha256=rjkgie2-XVNIN7O6P93yn6EPv9Ogjy8iTj3ufZIsUgY,9331
|
|
53
54
|
rowan/workflows/strain.py,sha256=kCW_BlX__sdQG1JVbFZuB-57rkpnxa_JCw3h4uKdGDk,6425
|
|
54
55
|
rowan/workflows/tautomer_search.py,sha256=mbRl0ZJ7wibueRF8c8_idhXJ1rtwg7LBt--5QwQ_Cck,5767
|
|
55
|
-
rowan_python-3.1.
|
|
56
|
-
rowan_python-3.1.
|
|
57
|
-
rowan_python-3.1.
|
|
58
|
-
rowan_python-3.1.
|
|
56
|
+
rowan_python-3.1.10.dist-info/METADATA,sha256=SUJMfB1vlLJiInjerGxUBYjzS7eFXHlaKP5_ma5ctBs,2446
|
|
57
|
+
rowan_python-3.1.10.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
58
|
+
rowan_python-3.1.10.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
|
|
59
|
+
rowan_python-3.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|