rowan-python 3.1.4__py3-none-any.whl → 3.1.5__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
@@ -2,7 +2,10 @@
2
2
  from . import constants
3
3
  from stjames import (
4
4
  Atom,
5
+ BandStructure,
5
6
  BindingPoseContact,
7
+ HydrationBridgeResidue,
8
+ HydrationSite,
6
9
  ConformerGenSettings,
7
10
  Constraint,
8
11
  ConstraintType,
@@ -27,6 +30,7 @@ from stjames import (
27
30
  Settings,
28
31
  Solvent,
29
32
  SolventSettings,
33
+ SinglePointEnergySettings,
30
34
  Task,
31
35
  VibrationalMode,
32
36
  )
rowan/molecule.py CHANGED
@@ -237,6 +237,50 @@ class Molecule(BaseModel):
237
237
  """Nuclear gradient (Hartree/Bohr)."""
238
238
  return self._stjames.gradient
239
239
 
240
+ @property
241
+ def symmetry(self) -> str | int | None:
242
+ """Space group number (1–230) or point group symbol."""
243
+ return self._stjames.symmetry
244
+
245
+ @property
246
+ def xrd_peaks(self) -> list[tuple[int, int, int, float]] | None:
247
+ """XRD reflections as (h, k, l, intensity) tuples."""
248
+ return self._stjames.x_ray_diffraction_peaks
249
+
250
+ @property
251
+ def band_structure(self) -> "stjames.BandStructure | None":
252
+ """Electronic band structure and DOS (periodic systems only)."""
253
+ return self._stjames.band_structure
254
+
255
+ @property
256
+ def band_gap(self) -> float | None:
257
+ """Band gap (Hartree)."""
258
+ bs = self._stjames.band_structure
259
+ return bs.band_gap if bs is not None else None
260
+
261
+ @property
262
+ def density_of_states(self) -> list[tuple[float, float]] | None:
263
+ """Total DOS as (energy in Hartree, k-weighted count) pairs (Fermi level = 0)."""
264
+ bs = self._stjames.band_structure
265
+ return bs.total_density_of_states if bs is not None else None
266
+
267
+ @property
268
+ def elastic_tensor(
269
+ self,
270
+ ) -> (
271
+ tuple[
272
+ tuple[float, float, float, float, float, float],
273
+ tuple[float, float, float, float, float, float],
274
+ tuple[float, float, float, float, float, float],
275
+ tuple[float, float, float, float, float, float],
276
+ tuple[float, float, float, float, float, float],
277
+ tuple[float, float, float, float, float, float],
278
+ ]
279
+ | None
280
+ ):
281
+ """Elastic stiffness matrix in GPa (Voigt order: xx yy zz yz xz xy)."""
282
+ return self._stjames.elastic_tensor
283
+
240
284
  # -- Geometric utilities --
241
285
 
242
286
  def distance(self, i: int, j: int) -> float:
@@ -47,6 +47,11 @@ from .bde import (
47
47
  find_cx_bonds,
48
48
  submit_bde_workflow,
49
49
  )
50
+ from .binding_affinity import (
51
+ BindingAffinityResult,
52
+ BindingAffinityScore,
53
+ submit_binding_affinity_workflow,
54
+ )
50
55
  from .conformer_search import ConformerSearchResult, submit_conformer_search_workflow
51
56
  from .descriptors import DescriptorsResult, submit_descriptors_workflow
52
57
  from .docking import DockingResult, DockingScore, submit_docking_workflow
@@ -136,6 +136,54 @@ class BasicCalculationResult(WorkflowResult):
136
136
  mol = self.molecule
137
137
  return mol.frequencies if mol else None
138
138
 
139
+ @property
140
+ def symmetry(self) -> str | int | None:
141
+ """Space group number (1–230) or point group symbol."""
142
+ mol = self.molecule
143
+ return mol.symmetry if mol else None
144
+
145
+ @property
146
+ def xrd_peaks(self) -> list[tuple[int, int, int, float]] | None:
147
+ """XRD reflections as (h, k, l, intensity) tuples."""
148
+ mol = self.molecule
149
+ return mol.xrd_peaks if mol else None
150
+
151
+ @property
152
+ def band_structure(self) -> stjames.BandStructure | None:
153
+ """Electronic band structure and DOS (periodic systems only)."""
154
+ mol = self.molecule
155
+ return mol.band_structure if mol else None
156
+
157
+ @property
158
+ def band_gap(self) -> float | None:
159
+ """Band gap (Hartree)."""
160
+ mol = self.molecule
161
+ return mol.band_gap if mol else None
162
+
163
+ @property
164
+ def density_of_states(self) -> list[tuple[float, float]] | None:
165
+ """Total DOS as (energy in Hartree, k-weighted count) pairs (Fermi level = 0)."""
166
+ mol = self.molecule
167
+ return mol.density_of_states if mol else None
168
+
169
+ @property
170
+ def elastic_tensor(
171
+ self,
172
+ ) -> (
173
+ tuple[
174
+ tuple[float, float, float, float, float, float],
175
+ tuple[float, float, float, float, float, float],
176
+ tuple[float, float, float, float, float, float],
177
+ tuple[float, float, float, float, float, float],
178
+ tuple[float, float, float, float, float, float],
179
+ tuple[float, float, float, float, float, float],
180
+ ]
181
+ | None
182
+ ):
183
+ """Elastic stiffness matrix in GPa (Voigt order: xx yy zz yz xz xy)."""
184
+ mol = self.molecule
185
+ return mol.elastic_tensor if mol else None
186
+
139
187
 
140
188
  def settings_from_preset(preset: PresetName, **overrides: Any) -> stjames.Settings:
141
189
  """
@@ -0,0 +1,140 @@
1
+ """Binding affinity workflow — SQM-based scoring of protein–ligand complexes."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import stjames
6
+ from stjames import SinglePointEnergySettings
7
+
8
+ from ..folder import Folder
9
+ from ..protein import Protein
10
+ from ..types import StructureInput
11
+ from ..utils import api_client
12
+ from .base import (
13
+ Message,
14
+ Workflow,
15
+ WorkflowResult,
16
+ molecule_to_dict,
17
+ parse_messages,
18
+ register_result,
19
+ require_coordinates,
20
+ )
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class BindingAffinityScore:
25
+ """
26
+ Binding affinity score for a single pose.
27
+
28
+ :param binding_affinity: binding affinity in kcal/mol
29
+ :param strain: strain energy in kcal/mol, or None if not computed
30
+ """
31
+
32
+ binding_affinity: float
33
+ strain: float | None
34
+
35
+
36
+ @register_result("binding_affinity")
37
+ class BindingAffinityResult(WorkflowResult):
38
+ """Result from a binding affinity workflow."""
39
+
40
+ _stjames_class = stjames.BindingAffinityWorkflow
41
+
42
+ def __repr__(self) -> str:
43
+ n = len(self.scores)
44
+ return f"<BindingAffinityResult scores={n}>"
45
+
46
+ @property
47
+ def scores(self) -> list[BindingAffinityScore]:
48
+ """Binding affinity scores for each scored pose."""
49
+ return [
50
+ BindingAffinityScore(
51
+ binding_affinity=r.binding_affinity,
52
+ strain=r.strain,
53
+ )
54
+ for r in (self._workflow.binding_affinity_results or [])
55
+ ]
56
+
57
+ @property
58
+ def messages(self) -> list[Message]:
59
+ """Any messages or warnings from the workflow."""
60
+ return parse_messages(self._workflow.messages)
61
+
62
+
63
+ def submit_binding_affinity_workflow(
64
+ protein: str | Protein,
65
+ ligand_residue_name: str | None = None,
66
+ ligand_structures: list[StructureInput] | None = None,
67
+ binding_affinity_settings: SinglePointEnergySettings | None = None,
68
+ name: str = "Binding Affinity Workflow",
69
+ folder_uuid: str | None = None,
70
+ folder: Folder | None = None,
71
+ max_credits: int | None = None,
72
+ webhook_url: str | None = None,
73
+ is_draft: bool = False,
74
+ ) -> Workflow:
75
+ """
76
+ Submits a binding affinity workflow to the API.
77
+
78
+ Scores ligand poses using SQM-based energies. Two submission modes:
79
+
80
+ **Mode 1 — holo protein:** protein already contains the bound ligand. Pass
81
+ ``ligand_residue_name`` to identify which residue is the ligand vs. the receptor.
82
+ Do not pass ``ligand_structures``.
83
+
84
+ **Mode 2 — apo protein + external poses:** protein has no bound ligand. Pass
85
+ ``ligand_structures`` with poses that are already in the protein's coordinate frame.
86
+ Do not pass ``ligand_residue_name``. When scoring multiple ligands, prefer this mode
87
+ over separate per-ligand workflows — all poses share the same pocket geometry.
88
+
89
+ :param protein: protein structure. Can be input as a UUID or a Protein object.
90
+ :param ligand_residue_name: residue name identifying the ligand in a holo protein PDB
91
+ (mode 1 only).
92
+ :param ligand_structures: external ligand poses to score, already in the protein's
93
+ coordinate frame. Must have 3D coordinates (mode 2 only).
94
+ :param binding_affinity_settings: SQM settings controlling geometry optimization and
95
+ energy evaluation. Defaults to PM6-D3H4X/COSMO optimization followed by
96
+ PM6-D3H4X/COSMO2 single-point in water.
97
+ :param name: name of the workflow.
98
+ :param folder_uuid: UUID of the folder to place the workflow in.
99
+ :param folder: Folder object to store the workflow in.
100
+ :param max_credits: maximum number of credits to use for the workflow.
101
+ :param webhook_url: URL that Rowan will POST to when the workflow completes.
102
+ :param is_draft: if True, submit the workflow as a draft without starting execution.
103
+ :returns: Workflow object representing the submitted workflow.
104
+ :raises ValueError: if folder arguments conflict.
105
+ :raises requests.HTTPError: if the request to the API fails.
106
+ """
107
+ if folder and folder_uuid:
108
+ raise ValueError("Provide either `folder` or `folder_uuid`, not both.")
109
+ if folder:
110
+ folder_uuid = folder.uuid
111
+ if isinstance(protein, Protein):
112
+ protein = protein.uuid
113
+
114
+ mol_dicts: list[dict] = []
115
+ if ligand_structures:
116
+ for mol in ligand_structures:
117
+ require_coordinates(mol)
118
+ mol_dicts.append(molecule_to_dict(mol))
119
+
120
+ workflow = stjames.BindingAffinityWorkflow(
121
+ protein=protein,
122
+ ligand_residue_name=ligand_residue_name,
123
+ ligand_structures=mol_dicts or [],
124
+ binding_affinity_settings=binding_affinity_settings or SinglePointEnergySettings(),
125
+ )
126
+
127
+ data = {
128
+ "workflow_type": "binding_affinity",
129
+ "workflow_data": workflow.model_dump(serialize_as_any=True, mode="json"),
130
+ "name": name,
131
+ "folder_uuid": folder_uuid,
132
+ "max_credits": max_credits,
133
+ "webhook_url": webhook_url,
134
+ "is_draft": is_draft,
135
+ }
136
+
137
+ with api_client() as client:
138
+ response = client.post("/workflow", json=data)
139
+ response.raise_for_status()
140
+ return Workflow(**response.json())
@@ -73,6 +73,11 @@ class PoseAnalysisMDResult(WorkflowResult):
73
73
  for t in raw
74
74
  ]
75
75
 
76
+ @property
77
+ def hydration_sites(self) -> list[stjames.HydrationSite]:
78
+ """Hydration sites identified across all trajectories."""
79
+ return self._workflow.hydration_sites or []
80
+
76
81
  @property
77
82
  def average_rmsds(self) -> list[float | None]:
78
83
  """Average ligand RMSD per trajectory (Angstrom)."""
@@ -97,6 +97,9 @@ def submit_relative_binding_free_energy_graph_workflow(
97
97
  greedy_k_min_cut: int = 3,
98
98
  refine_cutoff: float | None = None,
99
99
  seed_graph: RBFEGraph | None = None,
100
+ generate_intermediate_ligands: bool = False,
101
+ intermediate_max_dummy_atoms: int = 25,
102
+ intermediate_min_dummy_atom_improvement: int = 5,
100
103
  name: str = "RBFE Graph",
101
104
  folder_uuid: str | None = None,
102
105
  folder: Folder | None = None,
@@ -120,6 +123,12 @@ def submit_relative_binding_free_energy_graph_workflow(
120
123
  :param seed_graph: RBFE graph from a prior run to extend, as returned by a
121
124
  completed result's ``graph``. Its existing edges (and any computed results)
122
125
  are preserved, and only edges for newly added ligands are built.
126
+ :param generate_intermediate_ligands: Generate virtual intermediate ligands to make
127
+ difficult edges easier.
128
+ :param intermediate_max_dummy_atoms: An edge with more dummy atoms than this is split
129
+ when a generated intermediate brings both resulting legs back to at most this value.
130
+ :param intermediate_min_dummy_atom_improvement: Minimum reduction in the worst leg's
131
+ dummy-atom count required for a generated intermediate to be accepted.
123
132
  :param name: Name of the workflow.
124
133
  :param folder_uuid: UUID of the folder to place the workflow in.
125
134
  :param folder: Folder object to store the workflow in.
@@ -169,6 +178,9 @@ def submit_relative_binding_free_energy_graph_workflow(
169
178
  greedy_k_min_cut=greedy_k_min_cut,
170
179
  refine_cutoff=refine_cutoff,
171
180
  seed_graph=seed_graph,
181
+ generate_intermediate_ligands=generate_intermediate_ligands,
182
+ intermediate_max_dummy_atoms=intermediate_max_dummy_atoms,
183
+ intermediate_min_dummy_atom_improvement=intermediate_min_dummy_atom_improvement,
172
184
  )
173
185
 
174
186
  data = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rowan-python
3
- Version: 3.1.4
3
+ Version: 3.1.5
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.208
14
+ Requires-Dist: stjames>=0.0.217
15
15
  Description-Content-Type: text/markdown
16
16
 
17
17
  # Rowan Python Library
@@ -1,23 +1,24 @@
1
- rowan/__init__.py,sha256=AfjV9IsjgykiU39Eh9vjUP_3at-OSqqhSgNhPxutma0,1456
1
+ rowan/__init__.py,sha256=ui5-72WoXjhvvKU3XqOZAvaz-BXkDT13z1m62fISsvg,1553
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
5
5
  rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
6
6
  rowan/folder.py,sha256=RRg68dObluJ2RCTKX3rb7RxziOXOQUYxkfQSU2R5VMk,13708
7
- rowan/molecule.py,sha256=W6eBuIQAjZs5JiQFmyRnbcbj2Qy2XbZnm_J0WuMslG4,13881
7
+ rowan/molecule.py,sha256=1l-3YCN_A75Uz_Q-dMWuhkeF8rhD1z2JQM7AENyjKYM,15512
8
8
  rowan/project.py,sha256=RtxYE9jv3Yz6fH5I56iDGRG5EbwlOHiSK-HF1uxc0d4,4582
9
9
  rowan/protein.py,sha256=T2JAqfncP80yV5gEWYA-TlpNY3WeWaNHPE0Xg5voU24,17968
10
10
  rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  rowan/types.py,sha256=MvcYvEYr6N9RXLal44MqsnMxoWfcBa-VLD_rGsW4HOc,591
12
12
  rowan/user.py,sha256=rOCYVhSoRY71fCG_OCSD7cLAWWnAMANWGitF3ogfq30,6212
13
13
  rowan/utils.py,sha256=DBphY1he8Vl3RNf6CbLp5BSX9RG5EfHzhMs-9TmcE_g,4299
14
- rowan/workflows/__init__.py,sha256=ohl2q-Nqgs4E9cl5KIGigQgE2ifiTmY5dib5R_qNxO4,4510
14
+ rowan/workflows/__init__.py,sha256=qQZrfkB0f8B82WyNP6a9YeP6I3Cmfans9HvqD2amw28,4635
15
15
  rowan/workflows/admet.py,sha256=jDx1Kkgmp0XaeojPzaCFY_yUwvqtSM7Cezps-93cqPw,2865
16
16
  rowan/workflows/analogue_docking.py,sha256=IxVaYD8ITsLFfvZ6N7v4KmvIS25DcUQkv-_tMhEfG_4,9707
17
17
  rowan/workflows/base.py,sha256=k858otXLuorw4MZ1hvqZI_m0n_38htC7Hy3OMCX1TOA,32961
18
- rowan/workflows/basic_calculation.py,sha256=CnAnbRYrnjbxgcPAZxPUARUjaqPCnAAjf-RFku8R1r0,11240
18
+ rowan/workflows/basic_calculation.py,sha256=EovymT6fV6mxmt4G4kRtiskSPGB7w7KAHqBaGtgQI2Q,12941
19
19
  rowan/workflows/batch_docking.py,sha256=ePnb1hcsmnUfffl1-k9HNkOC09RjY9bZS9XcqAGNeFs,3621
20
20
  rowan/workflows/bde.py,sha256=1ieYUeu-5Lg637eSjhbRr_6qSIiIcjhsLOzaA4zbg3o,6681
21
+ rowan/workflows/binding_affinity.py,sha256=Pengz5gmhYvRd3P6yTe1p7nLZnDENp_gwDJo2q5kiEg,5089
21
22
  rowan/workflows/conformer_search.py,sha256=9_DpRfmeNWIVm68Y95zcHCK5Q0yt9le6W70rMeIQndY,15467
22
23
  rowan/workflows/constants.py,sha256=el8jWE9gnGTLNWn5_n_V0H362vIRneOqgy7BOQ8CScg,575
23
24
  rowan/workflows/descriptors.py,sha256=QjjpzPDpAO_EhPOK--gle6sGpVHMpdAs_yrvC0oiseY,3092
@@ -36,11 +37,11 @@ rowan/workflows/multistage_optimization.py,sha256=04kZPC3SmVOxqEQOwyBrS3DlelM-b8
36
37
  rowan/workflows/nmr.py,sha256=68be_Xgiy1AykGdIL4fR1pqKllD1nLm3IidsStzKtz4,5521
37
38
  rowan/workflows/pka.py,sha256=NZ-3i-cH_b7YOHj2iq0roXEcZmC8SYapmzMOX6ObRqw,8546
38
39
  rowan/workflows/pocket_detection.py,sha256=aGHY0puxekp4c4nsNYHcvKCe1fsetygL04BcSvNFvE8,3864
39
- rowan/workflows/pose_analysis_md.py,sha256=XJIfvn-H7GA6lVtw9uKjlgznVSSsr9bJiDt_PjKlPbA,11572
40
+ rowan/workflows/pose_analysis_md.py,sha256=5TRsCBFD0QObE5tpmCOxi137xsKMfQrRO6ef8TLrnkk,11767
40
41
  rowan/workflows/protein_binder_design.py,sha256=pgywTQDuXHplZYka-61_S6CC4WTPDCwJrbupp825eC4,9281
41
42
  rowan/workflows/protein_cofolding.py,sha256=CV13VUHC0NLDDaS1-GGTQ3RySkzEE30xzu1_4oPu6lo,16239
42
43
  rowan/workflows/protein_md.py,sha256=zqKEtL514uchgbxsyO4vhokzTU-X4lTFA1BM5O8HvTU,10117
43
- rowan/workflows/rbfe_graph.py,sha256=7UJA3ZBbvtaB9l1KQi9279Pc89moVnBHO-XJpxEr5uQ,7238
44
+ rowan/workflows/rbfe_graph.py,sha256=0eMfvwtQhyajUC8T-bhdcYNKi6Pkja6x6dHjCnEwxPw,8081
44
45
  rowan/workflows/redox_potential.py,sha256=bBeT1K9XGVpjCFZeeuv7Vtr3EYHN2okJ4LqYc3gMV04,5427
45
46
  rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=vi5Qj5aDAERtjcHDsgzI6vRV_BZJnJ8boOj5aG6GXzo,13817
46
47
  rowan/workflows/scan.py,sha256=R5Y6Md3ohPoFVJ4NYvx3_k3JNwMnVw1CDsNPD9S8sr4,7518
@@ -49,7 +50,7 @@ rowan/workflows/solvent_dependent_conformers.py,sha256=Z2xESmcM8WcTsR2TSTUa--Rrh
49
50
  rowan/workflows/spin_states.py,sha256=rjkgie2-XVNIN7O6P93yn6EPv9Ogjy8iTj3ufZIsUgY,9331
50
51
  rowan/workflows/strain.py,sha256=kCW_BlX__sdQG1JVbFZuB-57rkpnxa_JCw3h4uKdGDk,6425
51
52
  rowan/workflows/tautomer_search.py,sha256=mbRl0ZJ7wibueRF8c8_idhXJ1rtwg7LBt--5QwQ_Cck,5767
52
- rowan_python-3.1.4.dist-info/METADATA,sha256=qogpVoI_aoX_24Z2diW8JdOR2sphEYp-HuLtTFvvN-w,2052
53
- rowan_python-3.1.4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
54
- rowan_python-3.1.4.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
55
- rowan_python-3.1.4.dist-info/RECORD,,
53
+ rowan_python-3.1.5.dist-info/METADATA,sha256=g-Ttz5kgCuMcbHCBKoHlsBOJJLEvEdcTtornkx9eIQA,2052
54
+ rowan_python-3.1.5.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
55
+ rowan_python-3.1.5.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
56
+ rowan_python-3.1.5.dist-info/RECORD,,