xenosite-predict 0.2.0__tar.gz
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.
- xenosite_predict-0.2.0/.gitattributes +1 -0
- xenosite_predict-0.2.0/.github/workflows/publish.yml +57 -0
- xenosite_predict-0.2.0/.gitignore +55 -0
- xenosite_predict-0.2.0/.python-version +1 -0
- xenosite_predict-0.2.0/Makefile +126 -0
- xenosite_predict-0.2.0/PKG-INFO +197 -0
- xenosite_predict-0.2.0/README.md +182 -0
- xenosite_predict-0.2.0/pyproject.toml +79 -0
- xenosite_predict-0.2.0/src/xenosite/predict/__init__.py +65 -0
- xenosite_predict-0.2.0/src/xenosite/predict/__main__.py +51 -0
- xenosite_predict-0.2.0/src/xenosite/predict/_private.py +36 -0
- xenosite_predict-0.2.0/src/xenosite/predict/api.py +147 -0
- xenosite_predict-0.2.0/src/xenosite/predict/backends/__init__.py +133 -0
- xenosite_predict-0.2.0/src/xenosite/predict/backends/adapters.py +180 -0
- xenosite_predict-0.2.0/src/xenosite/predict/backends/http.py +60 -0
- xenosite_predict-0.2.0/src/xenosite/predict/backends/legacy.py +71 -0
- xenosite_predict-0.2.0/src/xenosite/predict/backends/onnx.py +111 -0
- xenosite_predict-0.2.0/src/xenosite/predict/compare.py +85 -0
- xenosite_predict-0.2.0/src/xenosite/predict/errors.py +33 -0
- xenosite_predict-0.2.0/src/xenosite/predict/features/__init__.py +13 -0
- xenosite_predict-0.2.0/src/xenosite/predict/forest.py +433 -0
- xenosite_predict-0.2.0/src/xenosite/predict/models/__init__.py +14 -0
- xenosite_predict-0.2.0/src/xenosite/predict/molecule.py +91 -0
- xenosite_predict-0.2.0/src/xenosite/predict/numbering.py +606 -0
- xenosite_predict-0.2.0/src/xenosite/predict/parallel.py +430 -0
- xenosite_predict-0.2.0/src/xenosite/predict/py.typed +1 -0
- xenosite_predict-0.2.0/src/xenosite/predict/registry.py +126 -0
- xenosite_predict-0.2.0/src/xenosite/predict/symmetry.py +24 -0
- xenosite_predict-0.2.0/src/xenosite/predict/types.py +119 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/__init__.py +1 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/__init__.py +38 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/_ob.py +217 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/atom.py +550 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/bond.py +659 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/bond_lonepair.py +774 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/epoxidation_bond_names.json +383 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/epoxidation_mol_names.json +39 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/heuristic.py +79 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/molgraph.py +208 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/names.py +58 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/ndealk_bond_names.json +390 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/phase1_mol.py +70 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/phase1_mol_names.json +43 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/phase1_site_names.json +406 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/quinone.py +116 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/quinone_atom_names.json +394 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/quinone_mol_names.json +25 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/quinone_pair_names.json +8 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/reactivity_atom_names.json +213 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/reactivity_mol.py +69 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/reactivity_mol_names.json +39 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/two_stage.py +65 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/ugt.py +342 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/features/ugt_atom_names.json +86 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/__init__.py +20 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/_base.py +86 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/bioactivation.py +74 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/epoxidation.py +99 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/ndealk.py +143 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/phase1.py +193 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/quinone.py +169 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/reactivity.py +115 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/models/ugt.py +50 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v0_legacy/symmetry.py +218 -0
- xenosite_predict-0.2.0/src/xenosite/predict/v1/__init__.py +1 -0
- xenosite_predict-0.2.0/src/xenosite/predict/weights.py +339 -0
- xenosite_predict-0.2.0/tests/fixtures/stub_onnx/ugt/atom.onnx +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tests/fixtures/ob_dumps.json.gz filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Publish to PyPI via Trusted Publishing (OIDC). No API tokens in GitHub secrets.
|
|
2
|
+
#
|
|
3
|
+
# One-time setup (package not on PyPI yet → pending publisher):
|
|
4
|
+
# 1. https://pypi.org/manage/account/publishing/
|
|
5
|
+
# 2. Add pending publisher:
|
|
6
|
+
# PyPI project name: xenosite-predict
|
|
7
|
+
# Owner: swamidasslab
|
|
8
|
+
# Repository: xenosite-predict
|
|
9
|
+
# Workflow name: publish.yml
|
|
10
|
+
# Environment: pypi
|
|
11
|
+
# 3. GitHub → Settings → Environments → create `pypi`
|
|
12
|
+
# (optional but recommended: required reviewers)
|
|
13
|
+
# 4. Merge this workflow, then push a tag vX.Y.Z (or Run workflow → publish)
|
|
14
|
+
#
|
|
15
|
+
# Docs: https://docs.pypi.org/trusted-publishers/
|
|
16
|
+
|
|
17
|
+
name: Publish
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
push:
|
|
21
|
+
tags:
|
|
22
|
+
- "v*"
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
build:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: astral-sh/setup-uv@v5
|
|
34
|
+
- name: Build sdist
|
|
35
|
+
run: uv build --sdist
|
|
36
|
+
- uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
publish:
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment:
|
|
45
|
+
name: pypi
|
|
46
|
+
url: https://pypi.org/p/xenosite-predict
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write # required for trusted publishing
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/download-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
- name: Publish to PyPI
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
56
|
+
with:
|
|
57
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.hypothesis/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
|
|
23
|
+
# Local model artifacts — never commit weights or extracted legacy trees
|
|
24
|
+
weights/legacy/
|
|
25
|
+
weights/onnx/
|
|
26
|
+
weights/*.tgz
|
|
27
|
+
weights/**/*.onnx
|
|
28
|
+
weights/**/*.safetensors
|
|
29
|
+
weights/**/*.model
|
|
30
|
+
weights/**/*.pyp
|
|
31
|
+
weights/**/*.pkl
|
|
32
|
+
weights/**/*.pickle
|
|
33
|
+
*.onnx
|
|
34
|
+
*.safetensors
|
|
35
|
+
*.model
|
|
36
|
+
*.pyp
|
|
37
|
+
# Tiny stub graphs for parallel/async unit tests (not real weights).
|
|
38
|
+
!tests/fixtures/stub_onnx/**/*.onnx
|
|
39
|
+
|
|
40
|
+
# uv
|
|
41
|
+
uv.lock
|
|
42
|
+
|
|
43
|
+
# Regenerable uncompressed OpenBabel dumps from `make dump-ob`.
|
|
44
|
+
# The gzipped suite (ob_dumps.json.gz) is committed via Git LFS.
|
|
45
|
+
tests/fixtures/ob_dump_aspirin.json
|
|
46
|
+
tests/fixtures/ob_dumps.json
|
|
47
|
+
# Regenerable ONNX score cache from `make capture-suite-onnx`
|
|
48
|
+
tests/fixtures/suite_onnx_cache.json
|
|
49
|
+
|
|
50
|
+
# Local scratch / ad-hoc investigation scripts (see tools/archive/)
|
|
51
|
+
tools/archive/
|
|
52
|
+
|
|
53
|
+
# Editor / session artifacts
|
|
54
|
+
.cursor/
|
|
55
|
+
:memory:.ses
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# uv has no first-class task runner; this Makefile is the equivalent.
|
|
2
|
+
# Tools live under tools/ and are NOT installed with the wheel.
|
|
3
|
+
|
|
4
|
+
PYTHON ?= uv run python
|
|
5
|
+
PYTEST ?= uv run pytest
|
|
6
|
+
CONVERT ?= uv run --group convert python
|
|
7
|
+
IMAGE ?= dockerreg01.accounts.ad.wustl.edu/swamidass/xenosite-legacy:api
|
|
8
|
+
LEGACY_COMPOSE ?= tools/legacy-test-api/compose.yml
|
|
9
|
+
LEGACY_REPLICAS ?= 24
|
|
10
|
+
TARBALL ?= ../xenosite-legacy/data/xenosite_legacy_data_trimmed.tgz
|
|
11
|
+
ONNX_DIR ?= weights/onnx/v0
|
|
12
|
+
ONNX_TARBALL ?= weights/xenosite_onnx_v0.tgz
|
|
13
|
+
|
|
14
|
+
.PHONY: extract-weights convert-onnx convert-onnx-$(MODEL) pack-onnx extract-onnx download-onnx test test-golden test-live \
|
|
15
|
+
legacy-test-api legacy-test-api-down py2-dump-image dump-ob dump-ob dump-ob-features \
|
|
16
|
+
capture-suite-onnx gather-golden drift-report drift-descriptors help \
|
|
17
|
+
regather-ob-dumps regather-golden-onnx
|
|
18
|
+
|
|
19
|
+
help:
|
|
20
|
+
@echo "extract-weights copy pickles/TSV/source from $(IMAGE) into weights/legacy/"
|
|
21
|
+
@echo "convert-onnx pickle → safetensors → ONNX (all models)"
|
|
22
|
+
@echo "convert-onnx MODEL=epoxidation"
|
|
23
|
+
@echo "pack-onnx tarball of *.onnx + *.meta.json (no _dump) → $(ONNX_TARBALL)"
|
|
24
|
+
@echo "extract-onnx unpack $(ONNX_TARBALL) into $(ONNX_DIR)/"
|
|
25
|
+
@echo "download-onnx fetch $$XENOSITE_ONNX_URL into $(ONNX_DIR)/"
|
|
26
|
+
@echo "test unit tests, Docker-free (-n auto via pyproject.toml)"
|
|
27
|
+
@echo "test-golden golden_descriptor_suite ONNX parity (-n auto)"
|
|
28
|
+
@echo "test-live pytest -m live (skips if Docker/image/weights missing)"
|
|
29
|
+
@echo "py2-dump-image build python:2.7-slim dump image (numpy + OpenBabel 2.4 + RDKit)"
|
|
30
|
+
@echo "dump-ob fill descriptor suite incrementally (skip dumps already present)"
|
|
31
|
+
@echo "capture-suite-onnx cache ONNX scores (CAPTURE_WORKERS=24 default; CAPTURE_MODEL/SMILES to filter)"
|
|
32
|
+
@echo "gather-golden regather failing suite rows from legacy-test-api (GATHER_WORKERS=24)"
|
|
33
|
+
@echo "regather-ob-dumps refresh quinone rows in ob_dumps from py3 legacy OMP port"
|
|
34
|
+
@echo "regather-golden-onnx refresh golden scores from ONNX + GOLDEN_PARAMETER"
|
|
35
|
+
@echo "drift-report classify ONNX vs golden from cache (DRIFT_WORKERS=24 default)"
|
|
36
|
+
@echo "drift-descriptors cross-tab descriptor vs score drift for one model"
|
|
37
|
+
@echo "legacy-test-api nginx LB + cache, scale API with LEGACY_REPLICAS=24"
|
|
38
|
+
@echo "legacy-test-api-down"
|
|
39
|
+
|
|
40
|
+
extract-weights:
|
|
41
|
+
$(PYTHON) tools/extract_weights.py --image $(IMAGE) --tarball $(TARBALL) --out weights/legacy
|
|
42
|
+
|
|
43
|
+
convert-onnx:
|
|
44
|
+
$(CONVERT) tools/convert_onnx.py --src weights/legacy --out $(ONNX_DIR) $(if $(MODEL),--model $(MODEL),)
|
|
45
|
+
|
|
46
|
+
pack-onnx:
|
|
47
|
+
$(PYTHON) tools/pack_onnx.py --src $(ONNX_DIR) --out $(ONNX_TARBALL)
|
|
48
|
+
|
|
49
|
+
extract-onnx:
|
|
50
|
+
$(PYTHON) tools/pack_onnx.py --extract --src $(ONNX_DIR) --out $(ONNX_TARBALL)
|
|
51
|
+
|
|
52
|
+
download-onnx:
|
|
53
|
+
$(PYTHON) -m xenosite.predict download --dest $(ONNX_DIR)
|
|
54
|
+
|
|
55
|
+
test:
|
|
56
|
+
$(PYTEST) -m "not live"
|
|
57
|
+
|
|
58
|
+
test-golden:
|
|
59
|
+
$(PYTEST) tests/test_golden_suite.py
|
|
60
|
+
|
|
61
|
+
test-live:
|
|
62
|
+
$(PYTEST) -m live
|
|
63
|
+
|
|
64
|
+
legacy-test-api:
|
|
65
|
+
docker compose -f $(LEGACY_COMPOSE) up --build -d --scale legacy-test-api=$(LEGACY_REPLICAS)
|
|
66
|
+
|
|
67
|
+
legacy-test-api-down:
|
|
68
|
+
docker compose -f $(LEGACY_COMPOSE) down
|
|
69
|
+
|
|
70
|
+
py2-dump-image:
|
|
71
|
+
docker build --platform linux/amd64 -t xenosite-predict-py2:dump tools/py2-dump
|
|
72
|
+
|
|
73
|
+
SMILES ?= O=C(C)Oc1ccccc1C(=O)O
|
|
74
|
+
MODEL ?= epoxidation
|
|
75
|
+
|
|
76
|
+
# Optional filters for capture-suite-onnx (empty = full golden suite)
|
|
77
|
+
CAPTURE_MODEL ?=
|
|
78
|
+
CAPTURE_SMILES ?=
|
|
79
|
+
CAPTURE_WORKERS ?= 24
|
|
80
|
+
DRIFT_WORKERS ?= 24
|
|
81
|
+
GATHER_WORKERS ?= 24
|
|
82
|
+
GATHER_MODEL ?=
|
|
83
|
+
|
|
84
|
+
dump-ob-features:
|
|
85
|
+
$(PYTHON) tools/dump_ob.py --smiles '$(SMILES)' --model $(MODEL)
|
|
86
|
+
|
|
87
|
+
dump-ob:
|
|
88
|
+
$(PYTHON) tools/dump_ob.py --suite
|
|
89
|
+
|
|
90
|
+
capture-suite-onnx:
|
|
91
|
+
$(PYTHON) tools/capture_suite_onnx.py \
|
|
92
|
+
--workers $(CAPTURE_WORKERS) \
|
|
93
|
+
$(if $(CAPTURE_MODEL),--model $(CAPTURE_MODEL),) \
|
|
94
|
+
$(if $(CAPTURE_SMILES),--smiles '$(CAPTURE_SMILES)',) \
|
|
95
|
+
$(if $(FORCE),--force,)
|
|
96
|
+
|
|
97
|
+
gather-golden:
|
|
98
|
+
$(PYTHON) tools/gather_golden_suite.py \
|
|
99
|
+
--workers $(GATHER_WORKERS) \
|
|
100
|
+
--failing-only \
|
|
101
|
+
--force \
|
|
102
|
+
$(if $(GATHER_MODEL),--models $(GATHER_MODEL),)
|
|
103
|
+
|
|
104
|
+
drift-report:
|
|
105
|
+
-$(PYTHON) tools/report_suite_drift.py \
|
|
106
|
+
--workers $(DRIFT_WORKERS) \
|
|
107
|
+
$(if $(CAPTURE_MODEL),--model $(CAPTURE_MODEL),) \
|
|
108
|
+
$(if $(REFRESH),--refresh,)
|
|
109
|
+
|
|
110
|
+
DRIFT_MODEL ?= quinone
|
|
111
|
+
|
|
112
|
+
drift-descriptors:
|
|
113
|
+
$(PYTHON) tools/analyze_descriptor_score_drift.py \
|
|
114
|
+
--model $(DRIFT_MODEL) \
|
|
115
|
+
--workers $(DRIFT_WORKERS)
|
|
116
|
+
|
|
117
|
+
regather-ob-dumps:
|
|
118
|
+
$(PYTHON) tools/regather_internal_ob_dumps.py --models quinone
|
|
119
|
+
|
|
120
|
+
regather-golden-onnx:
|
|
121
|
+
$(PYTHON) tools/regather_golden_from_onnx.py \
|
|
122
|
+
--workers $(GATHER_WORKERS) \
|
|
123
|
+
--force \
|
|
124
|
+
--include-smoke \
|
|
125
|
+
--models epoxidation,quinone,reactivity,ugt,ndealk,isozyme \
|
|
126
|
+
$(if $(GATHER_MODEL),--models $(GATHER_MODEL),)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xenosite-predict
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: RDKit public API + internal OpenBabel 2.4 + ONNX predictors for XenoSite (xenosite.predict)
|
|
5
|
+
Author-email: "S. Joshua Swamidass" <swamidass@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: httpx>=0.24
|
|
8
|
+
Requires-Dist: numpy>=1.26
|
|
9
|
+
Requires-Dist: onnxruntime>=1.16
|
|
10
|
+
Requires-Dist: openbabel>=3.2.1
|
|
11
|
+
Requires-Dist: pydantic>=2.5
|
|
12
|
+
Requires-Dist: rdkit>=2023.9.2
|
|
13
|
+
Requires-Dist: xenosite-forest>=0.1.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# xenosite.predict
|
|
17
|
+
|
|
18
|
+
Python 3.11+ RDKit + ONNX predictors for XenoSite. Dist name **xenosite-predict**; import **`xenosite.predict`** (PEP 420 namespace). Checkout as a sibling of `xenosite-api` and `xenosite-legacy`; origin is [github.com/swamidasslab/xenosite-predict](https://github.com/swamidasslab/xenosite-predict).
|
|
19
|
+
|
|
20
|
+
Publish **sdist only** (no wheel): ONNX weights stay local (`weights/`, gitignored). OpenBabel comes from PyPI (`uv add openbabel`, currently 3.2.x wheels). `make test` is Docker-free. Feature tests compare the installed OpenBabel to the committed `tests/fixtures/ob_dumps.json.gz` (Git LFS). `make test-live` skips if Docker, the legacy image, or ONNX files are missing. Do not commit model weights, pickles, or extracted `libridass/` trees.
|
|
21
|
+
|
|
22
|
+
This package is **not** wired into `xenosite-api` yet.
|
|
23
|
+
|
|
24
|
+
## User API
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from xenosite.predict import predict, predict_many, apredict, apredict_many, list_models
|
|
28
|
+
|
|
29
|
+
mol = predict("O=C(C)Oc1ccccc1C(=O)O", model="epoxidation")
|
|
30
|
+
mol = predict("O=C(C)Oc1ccccc1C(=O)O", models=["epoxidation", ("ugt", "0")])
|
|
31
|
+
mol = predict(mol, models=["quinone"]) # append
|
|
32
|
+
list_models() # what this process can actually run (backend-aware)
|
|
33
|
+
|
|
34
|
+
# Many molecules (process pool for ONNX; sync API)
|
|
35
|
+
mols = predict_many(smiles_list, model="ugt", workers=4)
|
|
36
|
+
|
|
37
|
+
# Async (event-loop friendly; same workers under the hood)
|
|
38
|
+
mol = await apredict(smi, model="ugt")
|
|
39
|
+
mols = await apredict_many(smiles_list, model="ugt", workers=4)
|
|
40
|
+
mols = await asyncio.gather(*[apredict(s, model="ugt") for s in smiles_list])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **One molecule at a time** for ``predict`` / ``apredict`` (no multi-mol batch inside a single call).
|
|
44
|
+
- **Many molecules:** ``predict_many`` / ``apredict_many`` run each input independently in parallel.
|
|
45
|
+
- **Parse once** when several models run on one molecule. Canonical SMILES is **non-isomeric** (`isomericSmiles=False`).
|
|
46
|
+
- **`models=`** is a name (default version `"0"`) or `(name, version)` pairs. Do not pass one version string for a whole list.
|
|
47
|
+
- **Indices** are 0-based RDKit atom/bond indices. Scores are floats (`atol=1e-4` in tests).
|
|
48
|
+
- **Name lookup is omitted.** Pass SMILES, not drug names.
|
|
49
|
+
- Import does **not** open ONNX, HTTP, or OpenBabel. Load on first use of that `(model, version)`. Callers never import `openbabel` / `pybel`.
|
|
50
|
+
- First `predict()` downloads ONNX weights when `XENOSITE_ONNX_URL` is set and none are cached (an **INFO** line reports when they are found or downloaded). No separate `download_weights()` call is required.
|
|
51
|
+
- **Workers:** ONNX batch/async paths use a process pool (descriptor generation is CPU-bound; threads do not help). Set ``workers=`` or ``XENOSITE_WORKERS``. ``XENOSITE_ORT_INTRA_OP`` caps ORT threads per process under concurrency.
|
|
52
|
+
- **Legacy vs principled:** production defaults differ from golden-test-api parity in four internal `_parameter` flags (ndealk site keys, quinone OMP paths, bond symmetry, bond NRings). **Score impact summary:** [`docs/legacy-vs-principled.md`](docs/legacy-vs-principled.md#expected-score-impact-production-vs-legacy). Walkthrough: `tests/v0_legacy/test_legacy_vs_principled_guide.py`.
|
|
53
|
+
|
|
54
|
+
### `predict_many` / `apredict` / `apredict_many`
|
|
55
|
+
|
|
56
|
+
| Helper | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `predict_many(inputs, …, workers=…)` | Sync batch: one molecule per input, process pool for ONNX |
|
|
59
|
+
| `apredict(inp, …)` | Async single molecule (offloads to the shared pool) |
|
|
60
|
+
| `apredict_many(inputs, …)` | Async batch (same workers as `predict_many`) |
|
|
61
|
+
|
|
62
|
+
`workers` defaults to CPU count (`XENOSITE_WORKERS` overrides). New models reuse the existing `predict` / runner path — no per-model async code.
|
|
63
|
+
|
|
64
|
+
### `predict(inp, model=..., models=..., backend=..., backends=..., env=...)`
|
|
65
|
+
|
|
66
|
+
| Arg | Meaning |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `inp` | SMILES or an existing `Molecule` (results append) |
|
|
69
|
+
| `model` | Single name; ignored if `models` is set |
|
|
70
|
+
| `models` | `str` or `(name, version)` iterable |
|
|
71
|
+
| `backend` | Pin the whole call: `"onnx"`, `"http"`, `"legacy"`, a URL, or a backend object |
|
|
72
|
+
| `backends` | Per-`(name, version)` override (ONNX epoxidation + HTTP bioactivation) |
|
|
73
|
+
| `env` | Picker mapping; `None` uses `os.environ`. Tests clear `XENOSITE_*` |
|
|
74
|
+
|
|
75
|
+
### Return type (`Molecule`)
|
|
76
|
+
|
|
77
|
+
Ported from `xenosite-api` `types.py`: `smiles`, `atoms`, `bonds`, `results`. Result variants: `MolBondResult`, `MolAtomResult`, `MolAtomPairResult`, `AtomResult`, `BondResult`, `AtomBondResult`. Each result has `model` and `version`.
|
|
78
|
+
|
|
79
|
+
### `list_models()`
|
|
80
|
+
|
|
81
|
+
Returns dicts `{name, version, available, backend, reason, heads, two_stage, pipeline}` for **this process**, not a fictional union of every backend.
|
|
82
|
+
|
|
83
|
+
### ONNX weights
|
|
84
|
+
|
|
85
|
+
ONNX graphs are not in the sdist. Set `XENOSITE_ONNX_URL` to an https tarball
|
|
86
|
+
or a local `.tgz` path (the URL is not stored in this repo). The first
|
|
87
|
+
`predict()` (or `list_models()`) downloads into `$XDG_CACHE_HOME/xenosite/onnx/v0`
|
|
88
|
+
(or `~/.cache/xenosite/onnx/v0`, or `XENOSITE_MODELS_WEIGHTS` if set) and prints
|
|
89
|
+
an INFO line when weights are found or downloaded. Download logs and errors
|
|
90
|
+
never echo the URL (so a private weight location does not leak via stderr or
|
|
91
|
+
tracebacks). Tests that pass `env={}` never fetch. `python -m xenosite.predict download` and `make download-onnx`
|
|
92
|
+
are optional pre-fetch helpers.
|
|
93
|
+
|
|
94
|
+
### Errors
|
|
95
|
+
|
|
96
|
+
`InvalidMolecule`, `UnknownModel`, `BackendNotConfigured`, `WeightsNotFound`, `WeightsDownloadError`, `ModelNotAvailable`, `OpenBabelNotAvailable`.
|
|
97
|
+
|
|
98
|
+
## Backends
|
|
99
|
+
|
|
100
|
+
Picker (explicit env wins; first match):
|
|
101
|
+
|
|
102
|
+
1. `XENOSITE_BACKEND` is an `http://` / `https://` URL → **HTTP** against that deployed **xenosite-api**. Optional `XENOSITE_API_KEY` as Bearer.
|
|
103
|
+
2. Else `XENOSITE_MODELS_WEIGHTS` → local **ONNX** directory.
|
|
104
|
+
3. Else auto-detect `./weights/onnx/v0` (or a flat `./weights/onnx` tree) → local ONNX.
|
|
105
|
+
4. Else user cache (`$XDG_CACHE_HOME/xenosite/onnx/v0`) if `*.onnx` exist.
|
|
106
|
+
5. Else, when `XENOSITE_ONNX_URL` is set in the process env, download that archive into the cache (INFO on found/download).
|
|
107
|
+
6. Else raise `BackendNotConfigured`.
|
|
108
|
+
|
|
109
|
+
Live parity compares **ONNX vs the legacy test-API**, not vs production HTTP. Tests must pass `backend=` and must not inherit a developer shell (`XENOSITE_*` are cleared in `conftest.py`).
|
|
110
|
+
|
|
111
|
+
| Backend | Role |
|
|
112
|
+
|---|---|
|
|
113
|
+
| ONNX | Converted numpy-NN heads under `weights/onnx/v0/<model>/<head>.onnx` |
|
|
114
|
+
| HTTP | `GET {origin}/v0/<model>?smiles=` (xenosite-api) |
|
|
115
|
+
| Legacy | Derived Docker test API (`POST /predict/<model>`, `POST /nn/<model>/<head>`) |
|
|
116
|
+
|
|
117
|
+
Per-model override: `predict(..., backends={("bioactivation", "0"): "http"})`.
|
|
118
|
+
|
|
119
|
+
## Built-in models (version `"0"`)
|
|
120
|
+
|
|
121
|
+
| Name | User results | Notes |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `epoxidation` | `MolBondResult` | Two-stage: bond ONNX then mol ONNX (Top-N site scores). Averages two atom orderings. |
|
|
124
|
+
| `quinone` | `MolAtomPairResult` | Atom → pair → mol. Includes null-pair molecule `O=C(Br)C(F)(F)F`. |
|
|
125
|
+
| `reactivity` | four `MolAtomResult` (`reactivity.gsh` / `.protein` / `.cyanide` / `.dna`) | Two-stage atom then mol. |
|
|
126
|
+
| `ugt` | `AtomResult` | Internal OpenBabel topological + mol descriptors. No MOPAC/SmartCYP on the inference path. |
|
|
127
|
+
| `ndealk` | `BondResult` (HLM slice) | Same ONNX as isozyme. Check `CCCC1CCCNC1C=O` for off-by-1. |
|
|
128
|
+
| `isozyme` | ten `BondResult` (`isozyme.3a4`, … `isozyme.hlm`) | Production Flask uses **ndealk1** for `metabolism1`, not the MOPAC metabolism predictor. |
|
|
129
|
+
| `phase1` | five `AtomBondResult` | TF `molecularNN` → ONNX (`site` + `mol`). Bond_and_LonePair descriptors + topology-group pooling. |
|
|
130
|
+
| `bioactivation` | `MolAtomResult` + metabolites | **Pipeline last** (enumeration + other models), not a single ONNX. |
|
|
131
|
+
|
|
132
|
+
## Makefile (tools are not in the sdist)
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
make extract-weights # Docker image or fallback tarball → weights/legacy/
|
|
136
|
+
make convert-onnx # pickle → ONNX; MODEL=epoxidation for one model
|
|
137
|
+
make pack-onnx # weights/xenosite_onnx_v0.tgz (runtime graphs, no _dump)
|
|
138
|
+
make extract-onnx # unpack that tarball into weights/onnx/v0/
|
|
139
|
+
make download-onnx # fetch $XENOSITE_ONNX_URL into weights/onnx/v0/
|
|
140
|
+
make test # pytest -m "not live" (no Docker)
|
|
141
|
+
make test-live # pytest -m live; fixture skips if Docker/image missing
|
|
142
|
+
make py2-dump-image # python:2.7-slim + numpy + Debian OpenBabel 2.4
|
|
143
|
+
make dump-ob # OpenBabel feature dump via that image (no WashU)
|
|
144
|
+
make legacy-test-api # build/run derived test image
|
|
145
|
+
make legacy-test-api-down
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Convert deps: `uv run --group convert`. Installed runtime: rdkit, openbabel (PyPI 3.2.x), numpy, onnxruntime, httpx, pydantic. OpenBabel is **internal** (not part of the public API). No TensorFlow, pandas, or pickle at inference.
|
|
149
|
+
|
|
150
|
+
The dump image remains the OpenBabel **2.4.1** feature oracle. Host inference uses the PyPI **3.2.x** wheel; `tests/test_ob_features.py` reports 3.x vs 2.4 drift at atol `1e-4` / rtol `0`. Do not vendor OpenBabel sources (GPL).
|
|
151
|
+
|
|
152
|
+
Populate pickles from `dockerreg01.accounts.ad.wustl.edu/swamidass/xenosite-legacy:api` (needs registry login) or the sibling tarball `xenosite-legacy/data/xenosite_legacy_data_trimmed.tgz`. `make convert-onnx` unpickles in a public **python:2.7-slim** dump image (`tools/py2-dump/`), not the WashU API image.
|
|
153
|
+
|
|
154
|
+
The same dump image is the OpenBabel **feature oracle**: Debian Buster `python-openbabel` 2.4.1 and `python-rdkit` from archive.debian.org, running as `/usr/bin/python` (the image's `/usr/local` CPython cannot load the multiarch SWIG module). `make dump-ob` feeds an RDKit molblock so 1-based OB indices align with 0-based RDKit, and dumps BondTD/AtomTD/UGT/Heuristic/Bond_and_LonePair rows from sibling `xenosite-legacy/src`. It is **idempotent**: molecule/model pairs already in the suite are skipped, and the JSON is checkpointed after each chunk. The gzipped suite `tests/fixtures/ob_dumps.json.gz` is committed via Git LFS so dump tests run without Docker; uncompressed JSON stays gitignored. Clone with Git LFS (`git lfs pull`).
|
|
155
|
+
|
|
156
|
+
Public parse/canonicalize stays RDKit. Feature graphs call OpenBabel internally (PyPI 3.2.x). `tests/test_ob_features.py` compares host OpenBabel 3.2 rows to 2.4 dumps at atol `1e-4` / rtol `0`. Missing dumps fail. Hypothesis draws random finite matrices for ONNX heads (`test_onnx_random_matrix_finite`) and live `/nn` vs ONNX (`test_random_vector_nn`). The convert dump `tests/fixtures/random_vectors.json` is the Python-2 regression (ONNX == pickled numpy NN).
|
|
157
|
+
|
|
158
|
+
## Layout
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
src/xenosite/predict/ # user API (installed)
|
|
162
|
+
tools/ # extract, convert, legacy-test-api (not in the wheel)
|
|
163
|
+
weights/ # local only — README + .gitignore committed
|
|
164
|
+
tests/ # unit + @pytest.mark.live
|
|
165
|
+
docs/vendored-diffs.md # NN/feature hashes, MOPAC/SmartCYP gate
|
|
166
|
+
docs/legacy-vs-principled.md # production defaults vs golden legacy modes
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
uv sync --group dev
|
|
173
|
+
make test
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Publishing to PyPI (trusted publishing)
|
|
177
|
+
|
|
178
|
+
No long-lived PyPI tokens. Releases use GitHub OIDC via `.github/workflows/publish.yml`.
|
|
179
|
+
|
|
180
|
+
1. On PyPI, add a **pending** trusted publisher (project not published yet) at
|
|
181
|
+
[pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/):
|
|
182
|
+
- Project: `xenosite-predict`
|
|
183
|
+
- Owner: `swamidasslab`
|
|
184
|
+
- Repo: `xenosite-predict`
|
|
185
|
+
- Workflow: `publish.yml`
|
|
186
|
+
- Environment: `pypi`
|
|
187
|
+
2. In GitHub → Settings → Environments, create `pypi` (add required reviewers if you want a human gate).
|
|
188
|
+
3. Merge the workflow, then either push a tag `v0.2.0` or run **Publish** manually.
|
|
189
|
+
4. The first successful publish creates the PyPI project; later releases reuse the same publisher.
|
|
190
|
+
|
|
191
|
+
Do **not** commit `XENOSITE_ONNX_URL`, API keys, or weight hostnames. Keep those in local env / deployment secrets only.
|
|
192
|
+
|
|
193
|
+
Vendored-tree comparison (sibling checkout, not committed):
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
uv run python tools/compare_vendored.py --root ../xenosite-legacy/src/libridass
|
|
197
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# xenosite.predict
|
|
2
|
+
|
|
3
|
+
Python 3.11+ RDKit + ONNX predictors for XenoSite. Dist name **xenosite-predict**; import **`xenosite.predict`** (PEP 420 namespace). Checkout as a sibling of `xenosite-api` and `xenosite-legacy`; origin is [github.com/swamidasslab/xenosite-predict](https://github.com/swamidasslab/xenosite-predict).
|
|
4
|
+
|
|
5
|
+
Publish **sdist only** (no wheel): ONNX weights stay local (`weights/`, gitignored). OpenBabel comes from PyPI (`uv add openbabel`, currently 3.2.x wheels). `make test` is Docker-free. Feature tests compare the installed OpenBabel to the committed `tests/fixtures/ob_dumps.json.gz` (Git LFS). `make test-live` skips if Docker, the legacy image, or ONNX files are missing. Do not commit model weights, pickles, or extracted `libridass/` trees.
|
|
6
|
+
|
|
7
|
+
This package is **not** wired into `xenosite-api` yet.
|
|
8
|
+
|
|
9
|
+
## User API
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from xenosite.predict import predict, predict_many, apredict, apredict_many, list_models
|
|
13
|
+
|
|
14
|
+
mol = predict("O=C(C)Oc1ccccc1C(=O)O", model="epoxidation")
|
|
15
|
+
mol = predict("O=C(C)Oc1ccccc1C(=O)O", models=["epoxidation", ("ugt", "0")])
|
|
16
|
+
mol = predict(mol, models=["quinone"]) # append
|
|
17
|
+
list_models() # what this process can actually run (backend-aware)
|
|
18
|
+
|
|
19
|
+
# Many molecules (process pool for ONNX; sync API)
|
|
20
|
+
mols = predict_many(smiles_list, model="ugt", workers=4)
|
|
21
|
+
|
|
22
|
+
# Async (event-loop friendly; same workers under the hood)
|
|
23
|
+
mol = await apredict(smi, model="ugt")
|
|
24
|
+
mols = await apredict_many(smiles_list, model="ugt", workers=4)
|
|
25
|
+
mols = await asyncio.gather(*[apredict(s, model="ugt") for s in smiles_list])
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **One molecule at a time** for ``predict`` / ``apredict`` (no multi-mol batch inside a single call).
|
|
29
|
+
- **Many molecules:** ``predict_many`` / ``apredict_many`` run each input independently in parallel.
|
|
30
|
+
- **Parse once** when several models run on one molecule. Canonical SMILES is **non-isomeric** (`isomericSmiles=False`).
|
|
31
|
+
- **`models=`** is a name (default version `"0"`) or `(name, version)` pairs. Do not pass one version string for a whole list.
|
|
32
|
+
- **Indices** are 0-based RDKit atom/bond indices. Scores are floats (`atol=1e-4` in tests).
|
|
33
|
+
- **Name lookup is omitted.** Pass SMILES, not drug names.
|
|
34
|
+
- Import does **not** open ONNX, HTTP, or OpenBabel. Load on first use of that `(model, version)`. Callers never import `openbabel` / `pybel`.
|
|
35
|
+
- First `predict()` downloads ONNX weights when `XENOSITE_ONNX_URL` is set and none are cached (an **INFO** line reports when they are found or downloaded). No separate `download_weights()` call is required.
|
|
36
|
+
- **Workers:** ONNX batch/async paths use a process pool (descriptor generation is CPU-bound; threads do not help). Set ``workers=`` or ``XENOSITE_WORKERS``. ``XENOSITE_ORT_INTRA_OP`` caps ORT threads per process under concurrency.
|
|
37
|
+
- **Legacy vs principled:** production defaults differ from golden-test-api parity in four internal `_parameter` flags (ndealk site keys, quinone OMP paths, bond symmetry, bond NRings). **Score impact summary:** [`docs/legacy-vs-principled.md`](docs/legacy-vs-principled.md#expected-score-impact-production-vs-legacy). Walkthrough: `tests/v0_legacy/test_legacy_vs_principled_guide.py`.
|
|
38
|
+
|
|
39
|
+
### `predict_many` / `apredict` / `apredict_many`
|
|
40
|
+
|
|
41
|
+
| Helper | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `predict_many(inputs, …, workers=…)` | Sync batch: one molecule per input, process pool for ONNX |
|
|
44
|
+
| `apredict(inp, …)` | Async single molecule (offloads to the shared pool) |
|
|
45
|
+
| `apredict_many(inputs, …)` | Async batch (same workers as `predict_many`) |
|
|
46
|
+
|
|
47
|
+
`workers` defaults to CPU count (`XENOSITE_WORKERS` overrides). New models reuse the existing `predict` / runner path — no per-model async code.
|
|
48
|
+
|
|
49
|
+
### `predict(inp, model=..., models=..., backend=..., backends=..., env=...)`
|
|
50
|
+
|
|
51
|
+
| Arg | Meaning |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `inp` | SMILES or an existing `Molecule` (results append) |
|
|
54
|
+
| `model` | Single name; ignored if `models` is set |
|
|
55
|
+
| `models` | `str` or `(name, version)` iterable |
|
|
56
|
+
| `backend` | Pin the whole call: `"onnx"`, `"http"`, `"legacy"`, a URL, or a backend object |
|
|
57
|
+
| `backends` | Per-`(name, version)` override (ONNX epoxidation + HTTP bioactivation) |
|
|
58
|
+
| `env` | Picker mapping; `None` uses `os.environ`. Tests clear `XENOSITE_*` |
|
|
59
|
+
|
|
60
|
+
### Return type (`Molecule`)
|
|
61
|
+
|
|
62
|
+
Ported from `xenosite-api` `types.py`: `smiles`, `atoms`, `bonds`, `results`. Result variants: `MolBondResult`, `MolAtomResult`, `MolAtomPairResult`, `AtomResult`, `BondResult`, `AtomBondResult`. Each result has `model` and `version`.
|
|
63
|
+
|
|
64
|
+
### `list_models()`
|
|
65
|
+
|
|
66
|
+
Returns dicts `{name, version, available, backend, reason, heads, two_stage, pipeline}` for **this process**, not a fictional union of every backend.
|
|
67
|
+
|
|
68
|
+
### ONNX weights
|
|
69
|
+
|
|
70
|
+
ONNX graphs are not in the sdist. Set `XENOSITE_ONNX_URL` to an https tarball
|
|
71
|
+
or a local `.tgz` path (the URL is not stored in this repo). The first
|
|
72
|
+
`predict()` (or `list_models()`) downloads into `$XDG_CACHE_HOME/xenosite/onnx/v0`
|
|
73
|
+
(or `~/.cache/xenosite/onnx/v0`, or `XENOSITE_MODELS_WEIGHTS` if set) and prints
|
|
74
|
+
an INFO line when weights are found or downloaded. Download logs and errors
|
|
75
|
+
never echo the URL (so a private weight location does not leak via stderr or
|
|
76
|
+
tracebacks). Tests that pass `env={}` never fetch. `python -m xenosite.predict download` and `make download-onnx`
|
|
77
|
+
are optional pre-fetch helpers.
|
|
78
|
+
|
|
79
|
+
### Errors
|
|
80
|
+
|
|
81
|
+
`InvalidMolecule`, `UnknownModel`, `BackendNotConfigured`, `WeightsNotFound`, `WeightsDownloadError`, `ModelNotAvailable`, `OpenBabelNotAvailable`.
|
|
82
|
+
|
|
83
|
+
## Backends
|
|
84
|
+
|
|
85
|
+
Picker (explicit env wins; first match):
|
|
86
|
+
|
|
87
|
+
1. `XENOSITE_BACKEND` is an `http://` / `https://` URL → **HTTP** against that deployed **xenosite-api**. Optional `XENOSITE_API_KEY` as Bearer.
|
|
88
|
+
2. Else `XENOSITE_MODELS_WEIGHTS` → local **ONNX** directory.
|
|
89
|
+
3. Else auto-detect `./weights/onnx/v0` (or a flat `./weights/onnx` tree) → local ONNX.
|
|
90
|
+
4. Else user cache (`$XDG_CACHE_HOME/xenosite/onnx/v0`) if `*.onnx` exist.
|
|
91
|
+
5. Else, when `XENOSITE_ONNX_URL` is set in the process env, download that archive into the cache (INFO on found/download).
|
|
92
|
+
6. Else raise `BackendNotConfigured`.
|
|
93
|
+
|
|
94
|
+
Live parity compares **ONNX vs the legacy test-API**, not vs production HTTP. Tests must pass `backend=` and must not inherit a developer shell (`XENOSITE_*` are cleared in `conftest.py`).
|
|
95
|
+
|
|
96
|
+
| Backend | Role |
|
|
97
|
+
|---|---|
|
|
98
|
+
| ONNX | Converted numpy-NN heads under `weights/onnx/v0/<model>/<head>.onnx` |
|
|
99
|
+
| HTTP | `GET {origin}/v0/<model>?smiles=` (xenosite-api) |
|
|
100
|
+
| Legacy | Derived Docker test API (`POST /predict/<model>`, `POST /nn/<model>/<head>`) |
|
|
101
|
+
|
|
102
|
+
Per-model override: `predict(..., backends={("bioactivation", "0"): "http"})`.
|
|
103
|
+
|
|
104
|
+
## Built-in models (version `"0"`)
|
|
105
|
+
|
|
106
|
+
| Name | User results | Notes |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `epoxidation` | `MolBondResult` | Two-stage: bond ONNX then mol ONNX (Top-N site scores). Averages two atom orderings. |
|
|
109
|
+
| `quinone` | `MolAtomPairResult` | Atom → pair → mol. Includes null-pair molecule `O=C(Br)C(F)(F)F`. |
|
|
110
|
+
| `reactivity` | four `MolAtomResult` (`reactivity.gsh` / `.protein` / `.cyanide` / `.dna`) | Two-stage atom then mol. |
|
|
111
|
+
| `ugt` | `AtomResult` | Internal OpenBabel topological + mol descriptors. No MOPAC/SmartCYP on the inference path. |
|
|
112
|
+
| `ndealk` | `BondResult` (HLM slice) | Same ONNX as isozyme. Check `CCCC1CCCNC1C=O` for off-by-1. |
|
|
113
|
+
| `isozyme` | ten `BondResult` (`isozyme.3a4`, … `isozyme.hlm`) | Production Flask uses **ndealk1** for `metabolism1`, not the MOPAC metabolism predictor. |
|
|
114
|
+
| `phase1` | five `AtomBondResult` | TF `molecularNN` → ONNX (`site` + `mol`). Bond_and_LonePair descriptors + topology-group pooling. |
|
|
115
|
+
| `bioactivation` | `MolAtomResult` + metabolites | **Pipeline last** (enumeration + other models), not a single ONNX. |
|
|
116
|
+
|
|
117
|
+
## Makefile (tools are not in the sdist)
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
make extract-weights # Docker image or fallback tarball → weights/legacy/
|
|
121
|
+
make convert-onnx # pickle → ONNX; MODEL=epoxidation for one model
|
|
122
|
+
make pack-onnx # weights/xenosite_onnx_v0.tgz (runtime graphs, no _dump)
|
|
123
|
+
make extract-onnx # unpack that tarball into weights/onnx/v0/
|
|
124
|
+
make download-onnx # fetch $XENOSITE_ONNX_URL into weights/onnx/v0/
|
|
125
|
+
make test # pytest -m "not live" (no Docker)
|
|
126
|
+
make test-live # pytest -m live; fixture skips if Docker/image missing
|
|
127
|
+
make py2-dump-image # python:2.7-slim + numpy + Debian OpenBabel 2.4
|
|
128
|
+
make dump-ob # OpenBabel feature dump via that image (no WashU)
|
|
129
|
+
make legacy-test-api # build/run derived test image
|
|
130
|
+
make legacy-test-api-down
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Convert deps: `uv run --group convert`. Installed runtime: rdkit, openbabel (PyPI 3.2.x), numpy, onnxruntime, httpx, pydantic. OpenBabel is **internal** (not part of the public API). No TensorFlow, pandas, or pickle at inference.
|
|
134
|
+
|
|
135
|
+
The dump image remains the OpenBabel **2.4.1** feature oracle. Host inference uses the PyPI **3.2.x** wheel; `tests/test_ob_features.py` reports 3.x vs 2.4 drift at atol `1e-4` / rtol `0`. Do not vendor OpenBabel sources (GPL).
|
|
136
|
+
|
|
137
|
+
Populate pickles from `dockerreg01.accounts.ad.wustl.edu/swamidass/xenosite-legacy:api` (needs registry login) or the sibling tarball `xenosite-legacy/data/xenosite_legacy_data_trimmed.tgz`. `make convert-onnx` unpickles in a public **python:2.7-slim** dump image (`tools/py2-dump/`), not the WashU API image.
|
|
138
|
+
|
|
139
|
+
The same dump image is the OpenBabel **feature oracle**: Debian Buster `python-openbabel` 2.4.1 and `python-rdkit` from archive.debian.org, running as `/usr/bin/python` (the image's `/usr/local` CPython cannot load the multiarch SWIG module). `make dump-ob` feeds an RDKit molblock so 1-based OB indices align with 0-based RDKit, and dumps BondTD/AtomTD/UGT/Heuristic/Bond_and_LonePair rows from sibling `xenosite-legacy/src`. It is **idempotent**: molecule/model pairs already in the suite are skipped, and the JSON is checkpointed after each chunk. The gzipped suite `tests/fixtures/ob_dumps.json.gz` is committed via Git LFS so dump tests run without Docker; uncompressed JSON stays gitignored. Clone with Git LFS (`git lfs pull`).
|
|
140
|
+
|
|
141
|
+
Public parse/canonicalize stays RDKit. Feature graphs call OpenBabel internally (PyPI 3.2.x). `tests/test_ob_features.py` compares host OpenBabel 3.2 rows to 2.4 dumps at atol `1e-4` / rtol `0`. Missing dumps fail. Hypothesis draws random finite matrices for ONNX heads (`test_onnx_random_matrix_finite`) and live `/nn` vs ONNX (`test_random_vector_nn`). The convert dump `tests/fixtures/random_vectors.json` is the Python-2 regression (ONNX == pickled numpy NN).
|
|
142
|
+
|
|
143
|
+
## Layout
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
src/xenosite/predict/ # user API (installed)
|
|
147
|
+
tools/ # extract, convert, legacy-test-api (not in the wheel)
|
|
148
|
+
weights/ # local only — README + .gitignore committed
|
|
149
|
+
tests/ # unit + @pytest.mark.live
|
|
150
|
+
docs/vendored-diffs.md # NN/feature hashes, MOPAC/SmartCYP gate
|
|
151
|
+
docs/legacy-vs-principled.md # production defaults vs golden legacy modes
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
uv sync --group dev
|
|
158
|
+
make test
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Publishing to PyPI (trusted publishing)
|
|
162
|
+
|
|
163
|
+
No long-lived PyPI tokens. Releases use GitHub OIDC via `.github/workflows/publish.yml`.
|
|
164
|
+
|
|
165
|
+
1. On PyPI, add a **pending** trusted publisher (project not published yet) at
|
|
166
|
+
[pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/):
|
|
167
|
+
- Project: `xenosite-predict`
|
|
168
|
+
- Owner: `swamidasslab`
|
|
169
|
+
- Repo: `xenosite-predict`
|
|
170
|
+
- Workflow: `publish.yml`
|
|
171
|
+
- Environment: `pypi`
|
|
172
|
+
2. In GitHub → Settings → Environments, create `pypi` (add required reviewers if you want a human gate).
|
|
173
|
+
3. Merge the workflow, then either push a tag `v0.2.0` or run **Publish** manually.
|
|
174
|
+
4. The first successful publish creates the PyPI project; later releases reuse the same publisher.
|
|
175
|
+
|
|
176
|
+
Do **not** commit `XENOSITE_ONNX_URL`, API keys, or weight hostnames. Keep those in local env / deployment secrets only.
|
|
177
|
+
|
|
178
|
+
Vendored-tree comparison (sibling checkout, not committed):
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
uv run python tools/compare_vendored.py --root ../xenosite-legacy/src/libridass
|
|
182
|
+
```
|