rowan-python 3.1.6__py3-none-any.whl → 3.1.8__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 +5 -1
- rowan/molecule.py +9 -0
- rowan/project.py +24 -4
- rowan/protein.py +2 -4
- rowan/test_utils.py +54 -0
- rowan/utils.py +40 -0
- rowan/workflows/analogue_docking.py +27 -6
- rowan/workflows/relative_binding_free_energy_perturbation.py +34 -0
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.8.dist-info}/METADATA +21 -3
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.8.dist-info}/RECORD +12 -11
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.8.dist-info}/WHEEL +0 -0
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.8.dist-info}/licenses/LICENSE +0 -0
rowan/__init__.py
CHANGED
|
@@ -38,7 +38,11 @@ from stjames import (
|
|
|
38
38
|
VibrationalMode,
|
|
39
39
|
VinaSettings,
|
|
40
40
|
)
|
|
41
|
-
from stjames.workflows.relative_binding_free_energy_perturbation import
|
|
41
|
+
from stjames.workflows.relative_binding_free_energy_perturbation import (
|
|
42
|
+
RBFEGraph,
|
|
43
|
+
RBFEGraphEdge,
|
|
44
|
+
TMDRBFESettings,
|
|
45
|
+
)
|
|
42
46
|
from stjames.excited_state_settings import TDDFTSettings
|
|
43
47
|
from stjames.pbc_dft_settings import PBCDFTSmearing
|
|
44
48
|
from stjames.engine_compatibility import (
|
rowan/molecule.py
CHANGED
|
@@ -144,6 +144,15 @@ class Molecule(BaseModel):
|
|
|
144
144
|
|
|
145
145
|
# -- Properties --
|
|
146
146
|
|
|
147
|
+
@property
|
|
148
|
+
def name(self) -> str | None:
|
|
149
|
+
"""Optional molecule name."""
|
|
150
|
+
return self._stjames.name
|
|
151
|
+
|
|
152
|
+
@name.setter
|
|
153
|
+
def name(self, value: str | None) -> None:
|
|
154
|
+
self._stjames.name = value
|
|
155
|
+
|
|
147
156
|
@property
|
|
148
157
|
def charge(self) -> int:
|
|
149
158
|
"""Molecular charge."""
|
rowan/project.py
CHANGED
|
@@ -126,6 +126,29 @@ def create_project(
|
|
|
126
126
|
return Project(**project_data)
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
def get_project(name: str, create: bool = False) -> Project:
|
|
130
|
+
"""
|
|
131
|
+
Get a project by exact name, optionally creating it if it does not exist.
|
|
132
|
+
|
|
133
|
+
The project analogue of :func:`get_folder`. Unlike ``get_folder``, ``create``
|
|
134
|
+
defaults to ``False``: a project is a top-level container, so a typo should
|
|
135
|
+
raise rather than silently spawn a new one. Does not change the active project -
|
|
136
|
+
assign ``rowan.project_uuid`` or use :func:`set_project` for that.
|
|
137
|
+
|
|
138
|
+
:param name: Exact name of the project.
|
|
139
|
+
:param create: If True, create the project when no exact match exists.
|
|
140
|
+
:returns: Matched (or newly created) project.
|
|
141
|
+
:raises ValueError: If no match is found and ``create`` is False.
|
|
142
|
+
"""
|
|
143
|
+
matches = list_projects(name_contains=name, size=100)
|
|
144
|
+
project = next((p for p in matches if p.name == name), None)
|
|
145
|
+
if project is not None:
|
|
146
|
+
return project
|
|
147
|
+
if create:
|
|
148
|
+
return create_project(name)
|
|
149
|
+
raise ValueError(f"Project {name!r} not found")
|
|
150
|
+
|
|
151
|
+
|
|
129
152
|
def set_project(name: str) -> Project:
|
|
130
153
|
"""
|
|
131
154
|
Set the active project by name for all subsequent API calls.
|
|
@@ -142,10 +165,7 @@ def set_project(name: str) -> Project:
|
|
|
142
165
|
:returns: Matched project.
|
|
143
166
|
:raises ValueError: If no project with that name is found.
|
|
144
167
|
"""
|
|
145
|
-
|
|
146
|
-
project = next((p for p in matches if p.name == name), None)
|
|
147
|
-
if project is None:
|
|
148
|
-
raise ValueError(f"Project {name!r} not found")
|
|
168
|
+
project = get_project(name)
|
|
149
169
|
rowan.project_uuid = project.uuid
|
|
150
170
|
return project
|
|
151
171
|
|
rowan/protein.py
CHANGED
|
@@ -400,22 +400,20 @@ def upload_protein(
|
|
|
400
400
|
if isinstance(project_uuid, Project):
|
|
401
401
|
project_uuid = project_uuid.uuid
|
|
402
402
|
with api_client() as client:
|
|
403
|
-
# Step 1: Read the file and post it to the conversion endpoint.
|
|
404
403
|
conversion_payload = {"name": name, "text": file_path.read_text()}
|
|
405
404
|
conversion_response = client.post("/convert/pdb_file_to_protein", json=conversion_payload)
|
|
405
|
+
conversion_response.raise_for_status()
|
|
406
406
|
|
|
407
|
-
# Extract the JSON data from the conversion response.
|
|
408
407
|
protein_data = conversion_response.json()
|
|
409
408
|
|
|
410
|
-
# Step 2: Use the converted data to create the final protein object.
|
|
411
409
|
creation_payload = {
|
|
412
410
|
"name": name,
|
|
413
411
|
"protein_data": protein_data,
|
|
414
412
|
"project_uuid": project_uuid,
|
|
415
413
|
}
|
|
416
414
|
final_response = client.post("/protein", json=creation_payload)
|
|
415
|
+
final_response.raise_for_status()
|
|
417
416
|
|
|
418
|
-
# Deserialize the final JSON response into a Protein object and return it.
|
|
419
417
|
return Protein(**final_response.json())
|
|
420
418
|
|
|
421
419
|
|
rowan/test_utils.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Tests for Rowan request utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from pytest import MonkeyPatch, raises
|
|
6
|
+
|
|
7
|
+
import rowan
|
|
8
|
+
from rowan.utils import api_credentials, get_api_key, get_project_uuid
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_api_credentials_override_global_configuration(monkeypatch: MonkeyPatch) -> None:
|
|
12
|
+
"""Prefer context-local credentials without changing global configuration."""
|
|
13
|
+
monkeypatch.setattr(rowan, "api_key", "global-key")
|
|
14
|
+
monkeypatch.setattr(rowan, "project_uuid", "global-project")
|
|
15
|
+
monkeypatch.setenv("ROWAN_API_KEY", "environment-key")
|
|
16
|
+
|
|
17
|
+
with api_credentials("context-key", project_uuid="context-project"):
|
|
18
|
+
assert get_api_key() == "context-key"
|
|
19
|
+
assert get_project_uuid() == "context-project"
|
|
20
|
+
|
|
21
|
+
assert get_api_key() == "global-key"
|
|
22
|
+
assert get_project_uuid() == "global-project"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_api_credentials_restore_nested_context() -> None:
|
|
26
|
+
"""Restore outer credentials after a nested context exits."""
|
|
27
|
+
with api_credentials("outer-key", project_uuid="outer-project"):
|
|
28
|
+
with api_credentials("inner-key"):
|
|
29
|
+
assert get_api_key() == "inner-key"
|
|
30
|
+
assert get_project_uuid() is None
|
|
31
|
+
|
|
32
|
+
assert get_api_key() == "outer-key"
|
|
33
|
+
assert get_project_uuid() == "outer-project"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_api_credentials_isolate_async_tasks() -> None:
|
|
37
|
+
"""Keep credentials isolated across concurrent asynchronous tasks."""
|
|
38
|
+
|
|
39
|
+
async def read_after_yield(api_key: str) -> str:
|
|
40
|
+
with api_credentials(api_key):
|
|
41
|
+
await asyncio.sleep(0)
|
|
42
|
+
return get_api_key()
|
|
43
|
+
|
|
44
|
+
async def run_workers() -> list[str]:
|
|
45
|
+
return list(await asyncio.gather(read_after_yield("first"), read_after_yield("second")))
|
|
46
|
+
|
|
47
|
+
assert asyncio.run(run_workers()) == ["first", "second"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_api_credentials_reject_empty_key() -> None:
|
|
51
|
+
"""Reject empty context-local API keys."""
|
|
52
|
+
with raises(ValueError, match="cannot be empty"):
|
|
53
|
+
with api_credentials(""):
|
|
54
|
+
pass
|
rowan/utils.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from dataclasses import dataclass
|
|
3
5
|
from typing import Generator
|
|
4
6
|
|
|
5
7
|
import httpx
|
|
@@ -10,6 +12,40 @@ import rowan
|
|
|
10
12
|
from .constants import API_URL
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class _APIContext:
|
|
17
|
+
"""Store credentials isolated to the current execution context."""
|
|
18
|
+
|
|
19
|
+
api_key: str
|
|
20
|
+
project_uuid: str | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_api_context: ContextVar[_APIContext | None] = ContextVar("rowan_api_context", default=None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def api_credentials(api_key: str, project_uuid: str | None = None) -> Generator[None, None, None]:
|
|
28
|
+
"""Temporarily use Rowan credentials in the current execution context.
|
|
29
|
+
|
|
30
|
+
Context-local credentials take precedence over module-level and environment configuration.
|
|
31
|
+
Nested contexts restore the previous credentials when they exit, and concurrent threads or
|
|
32
|
+
asynchronous tasks remain isolated from one another.
|
|
33
|
+
|
|
34
|
+
:param api_key: Rowan API key
|
|
35
|
+
:param project_uuid: active project UUID, if any
|
|
36
|
+
:yields: control while the credentials are active
|
|
37
|
+
:raises ValueError: API key is empty
|
|
38
|
+
"""
|
|
39
|
+
if not api_key:
|
|
40
|
+
raise ValueError("API key cannot be empty.")
|
|
41
|
+
|
|
42
|
+
token = _api_context.set(_APIContext(api_key=api_key, project_uuid=project_uuid))
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
_api_context.reset(token)
|
|
47
|
+
|
|
48
|
+
|
|
13
49
|
def get_api_key() -> str:
|
|
14
50
|
"""
|
|
15
51
|
Get the API key from the environment variable ROWAN_API_KEY or the module-level attribute
|
|
@@ -19,6 +55,8 @@ def get_api_key() -> str:
|
|
|
19
55
|
|
|
20
56
|
:returns: API key.
|
|
21
57
|
"""
|
|
58
|
+
if (context := _api_context.get()) is not None:
|
|
59
|
+
return context.api_key
|
|
22
60
|
if hasattr(rowan, "api_key") and rowan.api_key:
|
|
23
61
|
return rowan.api_key
|
|
24
62
|
elif (api_key := os.environ.get("ROWAN_API_KEY")) is not None:
|
|
@@ -36,6 +74,8 @@ def get_project_uuid() -> str | None:
|
|
|
36
74
|
|
|
37
75
|
:returns: Project UUID string, or None if not set.
|
|
38
76
|
"""
|
|
77
|
+
if (context := _api_context.get()) is not None:
|
|
78
|
+
return context.project_uuid
|
|
39
79
|
if hasattr(rowan, "project_uuid") and rowan.project_uuid:
|
|
40
80
|
return rowan.project_uuid
|
|
41
81
|
return None
|
|
@@ -98,15 +98,26 @@ class AnalogueDockingResult(WorkflowResult):
|
|
|
98
98
|
Best docked pose per analogue, keyed by SMILES.
|
|
99
99
|
|
|
100
100
|
Fetches the final geometry from each analogue's top-scoring pose.
|
|
101
|
-
Analogues with no successful poses are excluded.
|
|
101
|
+
Analogues with no successful poses are excluded. When ``analogue_names``
|
|
102
|
+
were supplied at submission, each pose's ``name`` is set to the analogue
|
|
103
|
+
name, so poses can be re-keyed by name (e.g. ``{p.name: p for p in ...}``).
|
|
102
104
|
|
|
103
105
|
:returns: Dictionary mapping SMILES to docked ``Molecule`` with 3D coordinates.
|
|
104
106
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
107
|
+
# analogue_names is empty or parallel to analogues; zip drops the tail when empty.
|
|
108
|
+
name_by_smiles = dict(
|
|
109
|
+
zip(self._workflow.analogues, self._workflow.analogue_names, strict=False)
|
|
110
|
+
)
|
|
111
|
+
poses = {}
|
|
112
|
+
for smiles, scores in self.analogue_scores.items():
|
|
113
|
+
if not scores:
|
|
114
|
+
continue
|
|
115
|
+
pose = self.get_pose(smiles).molecules[-1]
|
|
116
|
+
# Keep any name the pose already carries; otherwise fall back to analogue_names.
|
|
117
|
+
if not pose.name:
|
|
118
|
+
pose.name = name_by_smiles.get(smiles)
|
|
119
|
+
poses[smiles] = pose
|
|
120
|
+
return poses
|
|
110
121
|
|
|
111
122
|
def get_poses(self, smiles: str) -> list[Calculation]:
|
|
112
123
|
"""
|
|
@@ -175,6 +186,7 @@ def submit_analogue_docking_workflow(
|
|
|
175
186
|
analogues: list[str],
|
|
176
187
|
initial_molecule: StructureInput,
|
|
177
188
|
protein: str | Protein,
|
|
189
|
+
analogue_names: list[str] | None = None,
|
|
178
190
|
scoring_function: Literal["vina", "vinardo"] = "vinardo",
|
|
179
191
|
exhaustiveness: int = 8,
|
|
180
192
|
max_poses: int = 4,
|
|
@@ -194,6 +206,9 @@ def submit_analogue_docking_workflow(
|
|
|
194
206
|
:param analogues: SMILES strings to dock.
|
|
195
207
|
:param initial_molecule: Template to which to align molecules to.
|
|
196
208
|
:param protein: Protein to dock. Can be input as a uuid or a Protein object.
|
|
209
|
+
:param analogue_names: Names parallel to `analogues`. When provided, each docked
|
|
210
|
+
pose's `Molecule.name` is set to its analogue name (see `best_poses`), and must
|
|
211
|
+
be the same length as `analogues`. If omitted, poses are identified by SMILES.
|
|
197
212
|
:param scoring_function: Docking scoring function: "vina" or "vinardo".
|
|
198
213
|
:param exhaustiveness: How many times Vina attempts to find a pose for each conformer.
|
|
199
214
|
:param max_poses: Maximum number of poses generated per input conformer.
|
|
@@ -210,6 +225,11 @@ def submit_analogue_docking_workflow(
|
|
|
210
225
|
:raises requests.HTTPError: if the request to the API fails.
|
|
211
226
|
"""
|
|
212
227
|
require_coordinates(initial_molecule)
|
|
228
|
+
if analogue_names is not None and len(analogue_names) != len(analogues):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"analogue_names must match analogues in length "
|
|
231
|
+
f"(got {len(analogue_names)} names for {len(analogues)} analogues)."
|
|
232
|
+
)
|
|
213
233
|
if folder and folder_uuid:
|
|
214
234
|
raise ValueError("Provide either `folder` or `folder_uuid`, not both.")
|
|
215
235
|
if folder:
|
|
@@ -228,6 +248,7 @@ def submit_analogue_docking_workflow(
|
|
|
228
248
|
|
|
229
249
|
workflow = stjames.AnalogueDockingWorkflow(
|
|
230
250
|
analogues=analogues,
|
|
251
|
+
analogue_names=analogue_names or [],
|
|
231
252
|
initial_molecule=mol_dict,
|
|
232
253
|
protein=protein,
|
|
233
254
|
docking_settings=docking_settings,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""RBFE perturbation workflow - run relative binding free energy FEP simulations."""
|
|
2
2
|
|
|
3
|
+
import csv
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Literal
|
|
@@ -91,6 +92,11 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
|
|
|
91
92
|
"""Prepared protein structure used as the simulation target."""
|
|
92
93
|
return Protein(uuid=str(self._workflow.protein))
|
|
93
94
|
|
|
95
|
+
@property
|
|
96
|
+
def settings(self) -> TMDRBFESettings:
|
|
97
|
+
"""FEP settings used for the simulation."""
|
|
98
|
+
return self._workflow.settings
|
|
99
|
+
|
|
94
100
|
@property
|
|
95
101
|
def graph(self) -> RBFEGraph | None:
|
|
96
102
|
"""The RBFE graph with per-edge results, or None if not constructed."""
|
|
@@ -187,6 +193,34 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
|
|
|
187
193
|
k: RelativeBindingFreeEnergyResult(dg=v.dg, dg_err=v.dg_err) for k, v in raw.items()
|
|
188
194
|
}
|
|
189
195
|
|
|
196
|
+
def write_ligand_results_csv(self, path: Path | str = "ligand_results.csv") -> Path:
|
|
197
|
+
"""
|
|
198
|
+
Write per-ligand results to a CSV, matching the platform's ligand-results export.
|
|
199
|
+
|
|
200
|
+
Columns are ``Ligand name``, ``ΔG``, ``ΔG error``, ``SMILES``, quoted and
|
|
201
|
+
BOM-prefixed for spreadsheet compatibility, identical to the download in the web UI.
|
|
202
|
+
|
|
203
|
+
:param path: destination CSV path.
|
|
204
|
+
:returns: path written to.
|
|
205
|
+
:raises ValueError: if per-ligand results are not yet available.
|
|
206
|
+
"""
|
|
207
|
+
if (results := self.ligand_dg_results) is None:
|
|
208
|
+
raise ValueError("No per-ligand results yet - has the workflow completed?")
|
|
209
|
+
|
|
210
|
+
# Match JS Number->string: whole numbers render without a trailing ".0" (0.0 -> "0").
|
|
211
|
+
def num(x: float) -> str:
|
|
212
|
+
return str(int(x)) if x == int(x) else str(x)
|
|
213
|
+
|
|
214
|
+
ligands = self.ligands
|
|
215
|
+
path = Path(path)
|
|
216
|
+
with path.open("w", newline="", encoding="utf-8-sig") as f:
|
|
217
|
+
writer = csv.writer(f, quoting=csv.QUOTE_ALL, lineterminator="\n")
|
|
218
|
+
writer.writerow(["Ligand name", "ΔG", "ΔG error", "SMILES"])
|
|
219
|
+
for name, res in results.items():
|
|
220
|
+
smiles = ligands[name].smiles if name in ligands else None
|
|
221
|
+
writer.writerow([name, num(res.dg), num(res.dg_err), smiles or ""])
|
|
222
|
+
return path
|
|
223
|
+
|
|
190
224
|
@property
|
|
191
225
|
def diagnostics(self) -> RelativeBindingFreeEnergyDiagnostics | None:
|
|
192
226
|
"""Aggregate QC metrics from the FEP simulation."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rowan-python
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.8
|
|
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
|
|
@@ -30,9 +30,27 @@ The documentation is available [here](https://docs.rowansci.com/python-api).
|
|
|
30
30
|
|
|
31
31
|
## Agent skill
|
|
32
32
|
|
|
33
|
-
Ships with a [skill](skills/computational-chemistry-and-biology/)
|
|
33
|
+
Ships with a [computational chemistry and biology skill](skills/computational-chemistry-and-biology/)
|
|
34
|
+
that helps coding agents choose and run Rowan workflows through either Rowan MCP tools or the
|
|
35
|
+
Rowan Python SDK.
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
### Claude Code
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude plugin marketplace add https://github.com/rowansci/rowan-python.git
|
|
41
|
+
claude plugin install computational-chemistry-and-biology@rowan
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Codex
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codex plugin marketplace add rowansci/rowan-python --ref master
|
|
48
|
+
codex plugin add computational-chemistry-and-biology@rowan
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Start a new Claude Code or Codex session after installation. For manual installation, download the
|
|
52
|
+
[latest skill ZIP](https://github.com/rowansci/rowan-python/releases/download/skill-latest/computational-chemistry-and-biology-skill.zip)
|
|
53
|
+
and extract it into your agent's skills directory.
|
|
36
54
|
|
|
37
55
|
## Running examples
|
|
38
56
|
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
rowan/__init__.py,sha256=
|
|
1
|
+
rowan/__init__.py,sha256=J1u---gqVAI_LMJatslW1LiYKAhQVGjyUrR7FH1B5as,1748
|
|
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=
|
|
8
|
-
rowan/project.py,sha256=
|
|
9
|
-
rowan/protein.py,sha256=
|
|
7
|
+
rowan/molecule.py,sha256=MFx0D3_LJm1G3UiDogkfCHvae31gR6xo_N0LzrNC-ks,15733
|
|
8
|
+
rowan/project.py,sha256=4f1XGRe_EADjbd5uFGPvsVXz9s3t4ZiL8loTBlahn7A,5404
|
|
9
|
+
rowan/protein.py,sha256=uFMF2BXgIvn6KOw3cdE-LDvZPPbjAxqqwgxakLd85XA,17763
|
|
10
10
|
rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
rowan/test_utils.py,sha256=DTuZqftx6ycIatajAxSGWgDaGglLvTw2TYnVJHiiNdo,1936
|
|
11
12
|
rowan/types.py,sha256=MvcYvEYr6N9RXLal44MqsnMxoWfcBa-VLD_rGsW4HOc,591
|
|
12
13
|
rowan/user.py,sha256=rOCYVhSoRY71fCG_OCSD7cLAWWnAMANWGitF3ogfq30,6212
|
|
13
|
-
rowan/utils.py,sha256=
|
|
14
|
+
rowan/utils.py,sha256=J-3o9_ZlOrl4_ZuuC1yjEOPdD1IkKO0BOXBncV3Ij7c,5670
|
|
14
15
|
rowan/workflows/__init__.py,sha256=pkmAl8aPDznu15bSuRW0j8id-B7Xmzy_tq3LgZfQ7NQ,4786
|
|
15
16
|
rowan/workflows/admet.py,sha256=jDx1Kkgmp0XaeojPzaCFY_yUwvqtSM7Cezps-93cqPw,2865
|
|
16
|
-
rowan/workflows/analogue_docking.py,sha256=
|
|
17
|
+
rowan/workflows/analogue_docking.py,sha256=ngv5IFbOVFe42uSFkeEtM2qt-zAJFfJyF8ducexGK5U,11020
|
|
17
18
|
rowan/workflows/base.py,sha256=k858otXLuorw4MZ1hvqZI_m0n_38htC7Hy3OMCX1TOA,32961
|
|
18
19
|
rowan/workflows/basic_calculation.py,sha256=EovymT6fV6mxmt4G4kRtiskSPGB7w7KAHqBaGtgQI2Q,12941
|
|
19
20
|
rowan/workflows/batch_docking.py,sha256=ePnb1hcsmnUfffl1-k9HNkOC09RjY9bZS9XcqAGNeFs,3621
|
|
@@ -44,14 +45,14 @@ rowan/workflows/protein_cofolding.py,sha256=haT0liJorxyHgO02WX8WslqAu7Dc7QOSs6Vb
|
|
|
44
45
|
rowan/workflows/protein_md.py,sha256=zqKEtL514uchgbxsyO4vhokzTU-X4lTFA1BM5O8HvTU,10117
|
|
45
46
|
rowan/workflows/rbfe_graph.py,sha256=0eMfvwtQhyajUC8T-bhdcYNKi6Pkja6x6dHjCnEwxPw,8081
|
|
46
47
|
rowan/workflows/redox_potential.py,sha256=bBeT1K9XGVpjCFZeeuv7Vtr3EYHN2okJ4LqYc3gMV04,5427
|
|
47
|
-
rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=
|
|
48
|
+
rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=D8WNFWAm4VwO_Gn50XHQS4kUAqu-KiJFHAOLx5VGvdk,15347
|
|
48
49
|
rowan/workflows/scan.py,sha256=R5Y6Md3ohPoFVJ4NYvx3_k3JNwMnVw1CDsNPD9S8sr4,7518
|
|
49
50
|
rowan/workflows/solubility.py,sha256=ulZwdG8JRl0RCwWPatYUZGMhlAZeb6wqjVbvzQYFKmE,8555
|
|
50
51
|
rowan/workflows/solvent_dependent_conformers.py,sha256=Z2xESmcM8WcTsR2TSTUa--RrhrQ-5ri_WagD9wJeVwc,6812
|
|
51
52
|
rowan/workflows/spin_states.py,sha256=rjkgie2-XVNIN7O6P93yn6EPv9Ogjy8iTj3ufZIsUgY,9331
|
|
52
53
|
rowan/workflows/strain.py,sha256=kCW_BlX__sdQG1JVbFZuB-57rkpnxa_JCw3h4uKdGDk,6425
|
|
53
54
|
rowan/workflows/tautomer_search.py,sha256=mbRl0ZJ7wibueRF8c8_idhXJ1rtwg7LBt--5QwQ_Cck,5767
|
|
54
|
-
rowan_python-3.1.
|
|
55
|
-
rowan_python-3.1.
|
|
56
|
-
rowan_python-3.1.
|
|
57
|
-
rowan_python-3.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|