epilink 0.2.1a0__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.
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ python-version: ["3.9", "3.10", "3.11"]
14
+ jit: ["on", "off"]
15
+
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Setup Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e .[dev]
29
+
30
+ - name: Lint + type-check
31
+ run: |
32
+ ruff check .
33
+ black --check .
34
+ mypy src/epilink
35
+
36
+ - name: Test
37
+ env:
38
+ NUMBA_DISABLE_JIT: ${{ matrix.jit == 'off' && '1' || '0' }}
39
+ run: |
40
+ if [ "${{ matrix.jit }}" = "off" ]; then
41
+ THRESH=80
42
+ else
43
+ THRESH=70
44
+ fi
45
+ pytest --cov=epilink --cov-report=xml --cov-report=term-missing \
46
+ --cov-fail-under=$THRESH \
47
+ tests/ \
48
+ -W error::DeprecationWarning:epilink
49
+
50
+ - name: Upload coverage to Codecov
51
+ if: always()
52
+ uses: codecov/codecov-action@v4
53
+ with:
54
+ files: ./coverage.xml
55
+ # Set a secret CODECOV_TOKEN for private repos; for public repos this can be omitted
56
+ token: ${{ secrets.CODECOV_TOKEN }}
57
+ fail_ci_if_error: false
58
+ flags: unittests
59
+ name: py${{ matrix.python-version }}-jit-${{ matrix.jit }}
@@ -0,0 +1,35 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment:
15
+ name: pypi
16
+ url: https://pypi.org/p/epilink
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Setup Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.11"
27
+
28
+ - name: Build distributions
29
+ run: |
30
+ python -m pip install --upgrade pip build twine
31
+ python -m build
32
+ python -m twine check dist/*
33
+
34
+ - name: Publish to PyPI
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,93 @@
1
+ name: Test Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version_suffix:
7
+ description: 'PEP 440 suffix (e.g., dev123, rc1, b1). Leave blank for dev<run_number>.'
8
+ required: false
9
+ default: ''
10
+
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+
15
+ jobs:
16
+ publish-testpypi:
17
+ runs-on: ubuntu-latest
18
+ environment:
19
+ name: testpypi
20
+ url: https://test.pypi.org/p/epilink
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@v4
24
+ with:
25
+ fetch-depth: 0
26
+
27
+ - name: Setup Python
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.11"
31
+
32
+ - name: Set test version
33
+ run: |
34
+ python -m pip install --upgrade pip tomlkit
35
+ SUFFIX="${{ inputs.version_suffix }}"
36
+ if [ -z "$SUFFIX" ]; then
37
+ SUFFIX="dev${GITHUB_RUN_NUMBER}"
38
+ fi
39
+
40
+ export EPILINK_SUFFIX="$SUFFIX"
41
+ python - <<'PY'
42
+ import os
43
+ from pathlib import Path
44
+ import subprocess
45
+ import tomlkit
46
+
47
+ path = Path("pyproject.toml")
48
+ text = path.read_text(encoding="utf-8")
49
+ data = tomlkit.parse(text)
50
+ project = data.get("project")
51
+ if not project:
52
+ raise SystemExit("version not found in pyproject.toml")
53
+
54
+ if "version" in project:
55
+ base_version = str(project["version"])
56
+ elif "version" in project.get("dynamic", []):
57
+ try:
58
+ tag = subprocess.check_output(
59
+ ["git", "describe", "--tags", "--abbrev=0"],
60
+ text=True,
61
+ ).strip()
62
+ except subprocess.CalledProcessError:
63
+ tag = "0.0.0"
64
+ base_version = tag[1:] if tag.startswith("v") else tag
65
+ else:
66
+ raise SystemExit("version not found in pyproject.toml")
67
+
68
+ suffix = os.environ["EPILINK_SUFFIX"]
69
+ new_version = f"{base_version}.{suffix}"
70
+
71
+ project["version"] = new_version
72
+ if "dynamic" in project:
73
+ dynamic = [item for item in project["dynamic"] if item != "version"]
74
+ if dynamic:
75
+ project["dynamic"] = dynamic
76
+ else:
77
+ del project["dynamic"]
78
+
79
+ path.write_text(tomlkit.dumps(data), encoding="utf-8")
80
+ print(f"Using test version: {new_version}")
81
+ PY
82
+
83
+ - name: Build distributions
84
+ run: |
85
+ python -m pip install --upgrade pip build twine
86
+ python -m build
87
+ python -m twine check dist/*
88
+
89
+ - name: Publish to TestPyPI
90
+ uses: pypa/gh-action-pypi-publish@release/v1
91
+ with:
92
+ repository-url: https://test.pypi.org/legacy/
93
+ verbose: true
@@ -0,0 +1,35 @@
1
+ # Python bytecode
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # Build artifacts
6
+ build/
7
+ dist/
8
+ *.egg-info/
9
+ *.egg
10
+ *.whl
11
+ src/epilink/_version.py
12
+
13
+ # Test and coverage artifacts
14
+ .coverage
15
+ .coverage.*
16
+ htmlcov/
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .mypy_cache/
20
+
21
+ # Virtual environments
22
+ .venv/
23
+ venv/
24
+ env/
25
+
26
+ # MkDocs
27
+ site/
28
+
29
+ # IDEs
30
+ .idea/
31
+ .vscode/
32
+ *.iml
33
+
34
+ # macOS
35
+ .DS_Store
@@ -0,0 +1,14 @@
1
+ repos:
2
+ - repo: https://github.com/psf/black
3
+ rev: 24.8.0
4
+ hooks:
5
+ - id: black
6
+ - repo: https://github.com/astral-sh/ruff-pre-commit
7
+ rev: v0.6.4
8
+ hooks:
9
+ - id: ruff
10
+ args: [--fix]
11
+ - repo: https://github.com/pre-commit/mirrors-mypy
12
+ rev: v1.11.2
13
+ hooks:
14
+ - id: mypy
@@ -0,0 +1,24 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "epilink: Epidemiological Linkage Inference from Temporal and Genetic Data with a Variable Infectiousness (E/P/I) Model for SARS-CoV-2"
4
+ version: "0.1.0"
5
+ license: "MIT"
6
+ url: "https://github.com/ydnkka/epilink"
7
+ authors:
8
+ - family-names: Arthur
9
+ given-names: Dominic
10
+ repository-code: "https://github.com/ydnkka/epilink"
11
+ references:
12
+ - type: article
13
+ authors:
14
+ - family-names: Hart
15
+ given-names: William S
16
+ - family-names: Maini
17
+ given-names: Philip K
18
+ - family-names: Thompson
19
+ given-names: Robin N
20
+ title: "High infectiousness immediately before COVID-19 symptom onset highlights the importance of continued contact tracing"
21
+ journal: "eLife"
22
+ year: 2021
23
+ volume: "10"
24
+ doi: "10.7554/eLife.65534"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dominic Arthur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: epilink
3
+ Version: 0.2.1a0
4
+ Summary: Epidemiological Linkage Inference from Temporal and Genetic Data with a Variable Infectiousness (E/P/I) Model for SARS-CoV-2.
5
+ Project-URL: Homepage, https://github.com/ydnkka/epilink
6
+ Project-URL: Issues, https://github.com/ydnkka/epilink/issues
7
+ Project-URL: Documentation, https://github.com/ydnkka/epilink#readme
8
+ Project-URL: Repository, https://github.com/ydnkka/epilink
9
+ Project-URL: Bug Tracker, https://github.com/ydnkka/epilink/issues
10
+ Project-URL: Release Notes, https://github.com/ydnkka/epilink/releases
11
+ Author-email: Dominic Arthur <arthurdominic04@gmail.com>
12
+ Maintainer-email: Dominic Arthur <arthurdominic04@gmail.com>
13
+ License: MIT License
14
+
15
+ Copyright (c) 2025 Dominic Arthur
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining a copy
18
+ of this software and associated documentation files (the “Software”), to deal
19
+ in the Software without restriction, including without limitation the rights
20
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
+ copies of the Software, and to permit persons to whom the Software is
22
+ furnished to do so, subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in
25
+ all copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33
+ THE SOFTWARE.
34
+ License-File: LICENSE
35
+ Keywords: SARS-CoV-2,epidemiology,genomics,infectious-disease,linkage-inference,transmission
36
+ Classifier: Development Status :: 4 - Beta
37
+ Classifier: Intended Audience :: Science/Research
38
+ Classifier: License :: OSI Approved :: MIT License
39
+ Classifier: Natural Language :: English
40
+ Classifier: Operating System :: OS Independent
41
+ Classifier: Programming Language :: Python :: 3
42
+ Classifier: Programming Language :: Python :: 3.9
43
+ Classifier: Programming Language :: Python :: 3.10
44
+ Classifier: Programming Language :: Python :: 3.11
45
+ Classifier: Programming Language :: Python :: 3.12
46
+ Classifier: Topic :: Scientific/Engineering
47
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
48
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
49
+ Requires-Python: >=3.9
50
+ Requires-Dist: llvmlite<0.44,>=0.41
51
+ Requires-Dist: networkx<4.0,>=3.0
52
+ Requires-Dist: numba<0.61,>=0.58
53
+ Requires-Dist: numpy<2.0,>=1.22
54
+ Requires-Dist: pandas<3.0,>=2.0
55
+ Requires-Dist: scipy<2.0,>=1.9
56
+ Provides-Extra: all
57
+ Requires-Dist: black>=24.1; extra == 'all'
58
+ Requires-Dist: mkdocs-material>=9.5; extra == 'all'
59
+ Requires-Dist: mkdocs>=1.5; extra == 'all'
60
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'all'
61
+ Requires-Dist: mypy>=1.4; extra == 'all'
62
+ Requires-Dist: pytest-cov>=4.0; extra == 'all'
63
+ Requires-Dist: pytest-mock>=3.10; extra == 'all'
64
+ Requires-Dist: pytest>=7.3; extra == 'all'
65
+ Requires-Dist: ruff>=0.5; extra == 'all'
66
+ Provides-Extra: dev
67
+ Requires-Dist: black>=24.1; extra == 'dev'
68
+ Requires-Dist: mypy>=1.4; extra == 'dev'
69
+ Requires-Dist: pre-commit>=3.3; extra == 'dev'
70
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
71
+ Requires-Dist: pytest-mock>=3.10; extra == 'dev'
72
+ Requires-Dist: pytest>=7.3; extra == 'dev'
73
+ Requires-Dist: ruff>=0.5; extra == 'dev'
74
+ Provides-Extra: docs
75
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
76
+ Requires-Dist: mkdocs>=1.5; extra == 'docs'
77
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
78
+ Provides-Extra: lint
79
+ Requires-Dist: black>=24.1; extra == 'lint'
80
+ Requires-Dist: ruff>=0.5; extra == 'lint'
81
+ Provides-Extra: test
82
+ Requires-Dist: pytest-cov>=4.0; extra == 'test'
83
+ Requires-Dist: pytest-mock>=3.10; extra == 'test'
84
+ Requires-Dist: pytest>=7.3; extra == 'test'
85
+ Provides-Extra: type-check
86
+ Requires-Dist: mypy>=1.4; extra == 'type-check'
87
+ Description-Content-Type: text/markdown
88
+
89
+ # epilink: Epidemiological Linkage from Temporal and Genetic Data
90
+
91
+ [![codecov](https://codecov.io/gh/ydnkka/epilink/branch/master/graph/badge.svg)](https://codecov.io/gh/ydnkka/epilink)
92
+
93
+ Estimate the probability that two cases are epidemiologically linked from their temporal and genetic distances. Implements a mechanistic SARS‑CoV‑2 infectiousness model (E/P/I) with optional Numba acceleration. Usable from Python or the command line.
94
+
95
+ ## Features
96
+
97
+ - Estimate P(link | genetic distance g, temporal gap t)
98
+ - Parameterised infectiousness profiles (TOIT/TOST; configurable)
99
+ - Fast simulation kernels with optional JIT (Numba)
100
+ - Python API plus a lightweight CLI for batch runs
101
+
102
+ ---
103
+
104
+ ## Installation
105
+
106
+ From PyPI (yet to be released):
107
+
108
+ ```bash
109
+ pip install epilink
110
+ ```
111
+
112
+ Recommended (conda/mamba):
113
+
114
+ ```bash
115
+ # Create a fresh env (uses compiled deps from conda-forge)
116
+ conda create -n epilink -c conda-forge python=3.11 numpy scipy numba networkx pandas pip
117
+ conda activate epilink
118
+ git clone https://github.com/ydnkka/epilink.git
119
+ cd epilink
120
+
121
+ # Install the package from source without touching conda-managed deps
122
+ pip install -e . --no-deps
123
+
124
+ # (Optional) Dev tools: tests, linting, docs
125
+ pip install "pytest>=7.3" "pytest-cov>=4.0" "mypy>=1.4" "ruff>=0.5" "black>=24.1" \
126
+ "pre-commit>=3.3" "mkdocs>=1.5" "mkdocs-material>=9.5" "mkdocstrings[python]>=0.24"
127
+ ```
128
+
129
+ Alternative (pip + venv):
130
+
131
+ ```bash
132
+ python -m venv .venv
133
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
134
+ python -m pip install --upgrade pip
135
+ pip install -e .[dev]
136
+ ```
137
+
138
+ Notes:
139
+ - Prefer conda-forge for NumPy/SciPy/Numba (especially on Apple Silicon).
140
+ - In a conda env, avoid pip-installing compiled deps; use `pip install -e . --no-deps`.
141
+
142
+ ---
143
+
144
+ ## Quickstart
145
+
146
+ ### Python API
147
+
148
+ ```python
149
+ import numpy as np
150
+ from epilink import TOIT, MolecularClock, linkage_probability, linkage_probability_matrix
151
+
152
+ toit = TOIT(rng_seed=123)
153
+ clock = MolecularClock(relax_rate=False, rng_seed=123)
154
+
155
+ # Probability that a pair with 2 SNPs and 4 days apart is directly linked (m=0)
156
+ p = linkage_probability(
157
+ toit=toit,
158
+ clock=clock,
159
+ genetic_distance=2,
160
+ temporal_distance=4,
161
+ intermediate_generations=0,
162
+ num_simulations=10_000,
163
+ )
164
+ print("P(link):", p)
165
+
166
+ # Grid over genetic distances (SNPs) and temporal gaps (days)
167
+ gd = np.arange(0, 6) # 0..5 SNPs
168
+ td = np.arange(0, 15, 3) # 0..12 days, step 3
169
+ mat = linkage_probability_matrix(
170
+ toit=toit,
171
+ clock=clock,
172
+ genetic_distances=gd,
173
+ temporal_distances=td,
174
+ num_simulations=10_000,
175
+ )
176
+ print(mat)
177
+ ```
178
+
179
+ ### CLI
180
+
181
+ ```bash
182
+ # Help
183
+ epilink --help
184
+ epilink point --help
185
+ epilink grid --help
186
+
187
+ # Single pair
188
+ epilink point -g 2 -t 4 --nsims 200
189
+
190
+ # Multiple pairs (CSV to stdout)
191
+ epilink point -g 0 1 2 -t 0 2 5 --nsims 500 > out.csv
192
+
193
+ # Custom infectiousness profile and TOIT support
194
+ epilink point -g 2 -t 4 --nsims 200 \
195
+ --a 0 --b 40 \
196
+ --incubation-shape 5.0 --incubation-scale 1.1 \
197
+ --latent-shape 2.0 --symptomatic-rate 0.4 \
198
+ --symptomatic-shape 1.2 --rel-presymptomatic-infectiousness 2.0
199
+
200
+ # Grid (CSV to file)
201
+ epilink grid --g-start 0 --g-stop 5 --g-step 1 \
202
+ --t-start 0 --t-stop 12 --t-step 3 \
203
+ --nsims 10000 --out grid.csv
204
+ ```
205
+
206
+ Commonly used options (see `--help` for full list):
207
+ - `-m, --intermediate-generations` e.g. `"0,1,2"`
208
+ - `--relax-rate`
209
+ - `--subs-rate 1e-3`
210
+ - `--subs-rate-sigma 0.33`
211
+ - `--seed 12345`
212
+ - `--a 0 --b 60` (TOIT support bounds)
213
+ - `--incubation-shape`, `--incubation-scale`, `--latent-shape`, `--symptomatic-rate`, `--symptomatic-shape`, `--rel-presymptomatic-infectiousness`
214
+
215
+ ---
216
+
217
+ ## Development
218
+
219
+ Run tests:
220
+
221
+ ```bash
222
+ pytest --cov=epilink --cov-report=term-missing
223
+ # To count Python-side coverage of JIT kernels:
224
+ # NUMBA_DISABLE_JIT=1 pytest
225
+ ```
226
+
227
+ Code quality:
228
+
229
+ ```bash
230
+ ruff check .
231
+ black .
232
+ mypy src/epilink
233
+ ```
234
+
235
+ Pre-commit:
236
+
237
+ ```bash
238
+ pre-commit install
239
+ pre-commit run -a
240
+ ```
241
+
242
+ Docs:
243
+
244
+ ```bash
245
+ mkdocs serve
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Examples
251
+
252
+ Examples in `examples/`:
253
+ - `python examples/quickstart.py`
254
+ - `python examples/grid_to_csv.py --out grid.csv`
255
+
256
+ Install plotting deps if needed:
257
+
258
+ ```bash
259
+ mamba install -c conda-forge matplotlib seaborn
260
+ ```
261
+
262
+ ---
263
+
264
+ ## License
265
+
266
+ MIT License (see [LICENSE](LICENSE))
267
+
268
+ ---
269
+
270
+ ## Development & Contributing
271
+
272
+ For developers:
273
+ - **Testing releases**: See [TESTPYPI.md](TESTPYPI.md) for instructions on testing package releases on TestPyPI before publishing to PyPI
274
+ - **Contributing**: Pull requests welcome! Please ensure tests pass and coverage remains high
275
+
276
+ ---
277
+
278
+ ## Contact
279
+
280
+ - Questions or issues: open an [issue](https://github.com/ydnkka/epilink/issues)
281
+ - Maintainer: [@ydnkka](https://github.com/ydnkka)
282
+
283
+ ---
284
+
285
+ ## Reference
286
+
287
+ Hart WS, Maini PK, Thompson RN (2021). High infectiousness immediately before COVID‑19 symptom onset highlights the importance of continued contact tracing. eLife, 10:e65534. https://doi.org/10.7554/eLife.65534