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 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
- return self.__class__.model_validate(protein_data)
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, exclude_residue_names: list[str] | None = None) -> None:
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 (``LIG``) are always excluded — they are parameterized
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 exclude_residue_names: Additional residue names to skip during validation.
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
- excluded = list({"LIG"} | {name.upper() for name in (exclude_residue_names or [])})
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(self, path: Path | str | None = None, name: str | None = None) -> None:
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
- with api_client() as client:
332
- response = client.get(f"/protein/{self.uuid}/get_pdb_file")
333
- response.raise_for_status()
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, "wb") as f:
337
- f.write(response.content)
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
- return Protein(**protein_data)
394
+ protein = Protein(**protein_data)
395
+ protein._workflow_uuid = workflow_uuid
396
+ return protein
355
397
 
356
398
 
357
399
  def list_proteins(
@@ -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[WorkflowResult]], type[WorkflowResult]]:
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[WorkflowResult]) -> type[WorkflowResult]:
150
+ def decorator(cls: type[R]) -> type[R]:
151
151
  RESULT_REGISTRY[workflow_type] = cls
152
152
  return cls
153
153
 
@@ -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]:
@@ -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.ligand_rmsd,
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(uuid)
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(uuid)
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(uuid)
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
@@ -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(uuid)
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
- Protein(uuid=protein).validate_protein_forcefield()
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
- return Protein(uuid=str(self._workflow.protein))
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.8
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>=0.0.228
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=J1u---gqVAI_LMJatslW1LiYKAhQVGjyUrR7FH1B5as,1748
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=uFMF2BXgIvn6KOw3cdE-LDvZPPbjAxqqwgxakLd85XA,17763
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=pkmAl8aPDznu15bSuRW0j8id-B7Xmzy_tq3LgZfQ7NQ,4786
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=ngv5IFbOVFe42uSFkeEtM2qt-zAJFfJyF8ducexGK5U,11020
18
- rowan/workflows/base.py,sha256=k858otXLuorw4MZ1hvqZI_m0n_38htC7Hy3OMCX1TOA,32961
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=SfFzZER-fYGPn9OZyEE9BSAestjCbbUaP4z9OapA1ko,9391
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=68be_Xgiy1AykGdIL4fR1pqKllD1nLm3IidsStzKtz4,5521
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=5TRsCBFD0QObE5tpmCOxi137xsKMfQrRO6ef8TLrnkk,11767
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=haT0liJorxyHgO02WX8WslqAu7Dc7QOSs6VbE9ZJwuw,16433
45
- rowan/workflows/protein_md.py,sha256=zqKEtL514uchgbxsyO4vhokzTU-X4lTFA1BM5O8HvTU,10117
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=D8WNFWAm4VwO_Gn50XHQS4kUAqu-KiJFHAOLx5VGvdk,15347
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.8.dist-info/METADATA,sha256=I21zA1ConlWw06jgs294uPgw5-L97vflydhXYQk1D3Q,2436
56
- rowan_python-3.1.8.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
57
- rowan_python-3.1.8.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
58
- rowan_python-3.1.8.dist-info/RECORD,,
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,,