BayesianEstimationTool 1.0.1__tar.gz → 1.0.2__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.
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.egg-info/PKG-INFO +25 -13
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.egg-info/SOURCES.txt +18 -0
- bayesianestimationtool-1.0.2/ExampleInstaller.py +41 -0
- bayesianestimationtool-1.0.2/Examples/1_SimpleExampleNuts.py +149 -0
- bayesianestimationtool-1.0.2/Examples/2_SimpleExampleSvi.py +149 -0
- bayesianestimationtool-1.0.2/Examples/3_SaveExampleNuts.py +151 -0
- bayesianestimationtool-1.0.2/Examples/4_LoadExampleNuts.py +174 -0
- bayesianestimationtool-1.0.2/Examples/5_SaveExampleSvi.py +151 -0
- bayesianestimationtool-1.0.2/Examples/6_LoadExampleSvi.py +174 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/PKG-INFO +25 -13
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/README.md +24 -12
- bayesianestimationtool-1.0.2/docs/Cheatsheet.pdf +86 -0
- bayesianestimationtool-1.0.2/docs/Manual.pdf +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/pyproject.toml +4 -1
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.egg-info/dependency_links.txt +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.egg-info/requires.txt +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.egg-info/top_level.txt +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/BayesianEstimationTool_CheatSheet.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/LICENSE +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/Method.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/MethodValidation.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/NutsMethod.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/SviMethod.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/__init__.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/setup.cfg +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_custom_method.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_estimator_integration.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_estimator_validation.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_longitudinal_transfer.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_method_contract.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_method_validation.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_nuts_numerics.py +0 -0
- {bayesianestimationtool-1.0.1 → bayesianestimationtool-1.0.2}/tests/test_scientific_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BayesianEstimationTool
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Hierarchical Bayesian estimation and prediction for mechanistic models
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/rafaelrbravo/BayesianEstimationTool
|
|
@@ -15,6 +15,8 @@ Dynamic: license-file
|
|
|
15
15
|
|
|
16
16
|
# BayesianEstimationTool (BET)
|
|
17
17
|
|
|
18
|
+
[](https://doi.org/10.5281/zenodo.23431103)
|
|
19
|
+
|
|
18
20
|
BayesianEstimationTool fits population-level parameters and individual-specific latent variables with NumPyro inference methods.
|
|
19
21
|
|
|
20
22
|
## Documentation
|
|
@@ -37,25 +39,19 @@ The GitHub Actions test workflow regenerates the PDFs (and the Python-authored c
|
|
|
37
39
|
|
|
38
40
|
## Installation
|
|
39
41
|
|
|
40
|
-
BET requires Python 3.10 or newer. Install
|
|
42
|
+
BET requires Python 3.10 or newer. Install from [PyPI](https://pypi.org/project/BayesianEstimationTool/):
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
45
|
python -m pip install BayesianEstimationTool
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
python -m pip install BayesianEstimationTool==1.0.1
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Alternatively, install directly from the tagged GitHub source:
|
|
48
|
+
Alternatively, install the latest source directly from GitHub:
|
|
53
49
|
|
|
54
50
|
```bash
|
|
55
|
-
python -m pip install "git+https://github.com/rafaelrbravo/BayesianEstimationTool.git
|
|
51
|
+
python -m pip install "git+https://github.com/rafaelrbravo/BayesianEstimationTool.git"
|
|
56
52
|
```
|
|
57
53
|
|
|
58
|
-
You can also download a wheel (`.whl`) or source archive (`.tar.gz`) from [GitHub Releases](https://github.com/rafaelrbravo/BayesianEstimationTool/releases) and install it with `python -m pip install PATH_TO_DOWNLOADED_FILE`.
|
|
54
|
+
You can also download a wheel (`.whl`) or source archive (`.tar.gz`) from [GitHub Releases](https://github.com/rafaelrbravo/BayesianEstimationTool/releases) and install it with `python -m pip install PATH_TO_DOWNLOADED_FILE`.
|
|
59
55
|
|
|
60
56
|
Verify installation:
|
|
61
57
|
|
|
@@ -67,10 +63,26 @@ For editable development from a source checkout, run `python -m pip install -e .
|
|
|
67
63
|
|
|
68
64
|
See the manual for the minimal model and training examples.
|
|
69
65
|
|
|
66
|
+
## Bundled examples and PDF documentation
|
|
67
|
+
|
|
68
|
+
After installing BET, copy its bundled runnable examples and PDF manual/cheatsheet to an editable folder in the current directory (no GitHub download needed):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python -m BayesianEstimationTool.ExampleInstaller
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
To select a different parent directory:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -m BayesianEstimationTool.ExampleInstaller ~/Documents
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This creates `BET-Examples/` with the six Python examples and a `Documentation/` subfolder containing `Manual.pdf` and `Cheatsheet.pdf`. Run a script with `python BET-Examples/1_SimpleExampleNuts.py`. The installer refuses to overwrite an existing `BET-Examples/` directory. The installer is available in package builds that include bundled examples and documentation.
|
|
81
|
+
|
|
70
82
|
## How to cite
|
|
71
83
|
|
|
72
84
|
If you use BET in your research, please cite:
|
|
73
85
|
|
|
74
|
-
Bravo, R., Tercan, B., Lima, E., Aguilar, B., & Yankeelov, T. (2026). *BayesianEstimationTool* (Version 1.0.
|
|
86
|
+
Bravo, R., Tercan, B., Lima, E., Aguilar, B., & Yankeelov, T. (2026). *BayesianEstimationTool* (Version 1.0.2) [Computer software]. https://doi.org/10.5281/zenodo.23431103
|
|
75
87
|
|
|
76
|
-
GitHub also provides a **Cite this repository** option using [CITATION.cff](CITATION.cff).
|
|
88
|
+
This DOI identifies all versions of BET. GitHub also provides a **Cite this repository** option using [CITATION.cff](CITATION.cff).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
BayesianEstimationTool.py
|
|
2
2
|
BayesianEstimationTool_CheatSheet.py
|
|
3
|
+
ExampleInstaller.py
|
|
3
4
|
LICENSE
|
|
4
5
|
Method.py
|
|
5
6
|
MethodValidation.py
|
|
@@ -10,16 +11,33 @@ __init__.py
|
|
|
10
11
|
pyproject.toml
|
|
11
12
|
./BayesianEstimationTool.py
|
|
12
13
|
./BayesianEstimationTool_CheatSheet.py
|
|
14
|
+
./ExampleInstaller.py
|
|
13
15
|
./Method.py
|
|
14
16
|
./MethodValidation.py
|
|
15
17
|
./NutsMethod.py
|
|
16
18
|
./SviMethod.py
|
|
17
19
|
./__init__.py
|
|
20
|
+
./Examples/1_SimpleExampleNuts.py
|
|
21
|
+
./Examples/2_SimpleExampleSvi.py
|
|
22
|
+
./Examples/3_SaveExampleNuts.py
|
|
23
|
+
./Examples/4_LoadExampleNuts.py
|
|
24
|
+
./Examples/5_SaveExampleSvi.py
|
|
25
|
+
./Examples/6_LoadExampleSvi.py
|
|
26
|
+
./docs/Cheatsheet.pdf
|
|
27
|
+
./docs/Manual.pdf
|
|
18
28
|
BayesianEstimationTool.egg-info/PKG-INFO
|
|
19
29
|
BayesianEstimationTool.egg-info/SOURCES.txt
|
|
20
30
|
BayesianEstimationTool.egg-info/dependency_links.txt
|
|
21
31
|
BayesianEstimationTool.egg-info/requires.txt
|
|
22
32
|
BayesianEstimationTool.egg-info/top_level.txt
|
|
33
|
+
Examples/1_SimpleExampleNuts.py
|
|
34
|
+
Examples/2_SimpleExampleSvi.py
|
|
35
|
+
Examples/3_SaveExampleNuts.py
|
|
36
|
+
Examples/4_LoadExampleNuts.py
|
|
37
|
+
Examples/5_SaveExampleSvi.py
|
|
38
|
+
Examples/6_LoadExampleSvi.py
|
|
39
|
+
docs/Cheatsheet.pdf
|
|
40
|
+
docs/Manual.pdf
|
|
23
41
|
tests/test_custom_method.py
|
|
24
42
|
tests/test_estimator_integration.py
|
|
25
43
|
tests/test_estimator_validation.py
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Copy bundled BET examples and PDF documentation into an editable folder."""
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="Copy bundled BET examples and documentation.")
|
|
10
|
+
parser.add_argument("path", nargs="?", default=".", help="Parent directory for BET-Examples (default: current directory)")
|
|
11
|
+
args = parser.parse_args()
|
|
12
|
+
package = Path(__file__).resolve().parent
|
|
13
|
+
source = package / "Examples"
|
|
14
|
+
docs = package / "docs"
|
|
15
|
+
parent = Path(args.path).expanduser().resolve()
|
|
16
|
+
target = parent / "BET-Examples"
|
|
17
|
+
if not parent.is_dir():
|
|
18
|
+
print(f"Parent directory does not exist: {parent}", file=sys.stderr)
|
|
19
|
+
return 1
|
|
20
|
+
if not source.is_dir() or not list(source.glob("*.py")):
|
|
21
|
+
print("BET examples are missing from this installation.", file=sys.stderr)
|
|
22
|
+
return 1
|
|
23
|
+
pdfs = sorted(docs.glob("*.pdf")) if docs.is_dir() else []
|
|
24
|
+
if not pdfs:
|
|
25
|
+
print("BET documentation PDFs are missing from this installation.", file=sys.stderr)
|
|
26
|
+
return 1
|
|
27
|
+
if target.exists():
|
|
28
|
+
print(f"Destination already exists: {target}\\nNothing was overwritten.", file=sys.stderr)
|
|
29
|
+
return 1
|
|
30
|
+
shutil.copytree(source, target)
|
|
31
|
+
docs_target = target / "Documentation"
|
|
32
|
+
docs_target.mkdir()
|
|
33
|
+
for pdf in pdfs:
|
|
34
|
+
shutil.copy2(pdf, docs_target / pdf.name)
|
|
35
|
+
print(f"Copied BET examples and documentation PDFs to {target}")
|
|
36
|
+
print("Run an example with: python BET-Examples/1_SimpleExampleNuts.py")
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
sys.exit(main())
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import numpyro as npo
|
|
2
|
+
npo.set_host_device_count(4)
|
|
3
|
+
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
import numpyro.distributions as dist
|
|
6
|
+
from jax import random
|
|
7
|
+
|
|
8
|
+
import BayesianEstimationTool as bet
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------
|
|
12
|
+
# Generate 10 synthetic patient trajectories
|
|
13
|
+
# ---------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
keyG,keyNoise=random.split(random.PRNGKey(2),2)
|
|
16
|
+
|
|
17
|
+
nPatients=10
|
|
18
|
+
nTimes=8
|
|
19
|
+
times=jnp.linspace(0,10,nTimes)
|
|
20
|
+
|
|
21
|
+
# True population:
|
|
22
|
+
# mean growth rate = 0.15
|
|
23
|
+
# std growth rate = 0.03
|
|
24
|
+
|
|
25
|
+
trueG=0.15+0.03*random.normal(keyG,(nPatients,))
|
|
26
|
+
|
|
27
|
+
# Give patients slightly different initial tumor sizes.
|
|
28
|
+
|
|
29
|
+
v0=jnp.linspace(0.8,1.2,nPatients)
|
|
30
|
+
trueTumorSize=v0[:,None]*jnp.exp(trueG[:,None]*times)
|
|
31
|
+
tumorSize=trueTumorSize+0.05*random.normal(keyNoise,trueTumorSize.shape)
|
|
32
|
+
|
|
33
|
+
data={
|
|
34
|
+
"times":jnp.tile(times,(nPatients,1)),
|
|
35
|
+
"v0":v0,
|
|
36
|
+
"tumorSize":tumorSize,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------
|
|
41
|
+
# Model components
|
|
42
|
+
# ---------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
# Analytical solution of dV/dt = gV.
|
|
45
|
+
|
|
46
|
+
# localZs["g"] is the standardized patient-specific deviation.
|
|
47
|
+
# Convert it to the physical growth rate using the population mean/std.
|
|
48
|
+
|
|
49
|
+
def ModelFn(globalParams,localZs,data):
|
|
50
|
+
g=globalParams["gMean"]+globalParams["gStd"]*localZs["g"]
|
|
51
|
+
return {"tumorSize":data["v0"]*jnp.exp(g*data["times"])}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Population parameters and observation noise.
|
|
55
|
+
|
|
56
|
+
def GlobalPriorFn(dataShapes):
|
|
57
|
+
return {
|
|
58
|
+
"gMean":dist.Normal(0.15,0.1),
|
|
59
|
+
"gStd":dist.HalfNormal(0.1),
|
|
60
|
+
"sigma":dist.HalfNormal(0.1),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Training likelihood for one patient; BET vectorizes it across patients.
|
|
65
|
+
|
|
66
|
+
def TrainLikelihoodFn(globalParams,localZs,data,modelOut):
|
|
67
|
+
return {"tumorSize":(data["tumorSize"],dist.Normal(modelOut["tumorSize"],globalParams["sigma"]))}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Test likelihood for one patient; only the first nFit observations are used.
|
|
71
|
+
|
|
72
|
+
def TestLikelihoodFn(globalParams,localZs,data,modelOut):
|
|
73
|
+
nFit=3
|
|
74
|
+
tumorSize=modelOut["tumorSize"]
|
|
75
|
+
mask=jnp.arange(tumorSize.shape[-1])<nFit
|
|
76
|
+
return {"tumorSize":(data["tumorSize"],dist.Normal(tumorSize,globalParams["sigma"]).mask(mask))}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Score only the last five observations; the first three may be used for fitting.
|
|
80
|
+
|
|
81
|
+
def ScoreFn(modelOut,data):
|
|
82
|
+
return jnp.mean(jnp.abs(modelOut["tumorSize"][3:]-data["tumorSize"][3:]))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------
|
|
86
|
+
# Construct Estimator
|
|
87
|
+
# ---------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
est=bet.Estimator(data=data,ModelFn=ModelFn,localZNames=["g"],GlobalPriorFn=GlobalPriorFn,rngSeed=0)
|
|
90
|
+
|
|
91
|
+
# Patients 0-4 train the population model.
|
|
92
|
+
# Patients 5-9 are completely held out.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------
|
|
96
|
+
# 1. TRAINING
|
|
97
|
+
#
|
|
98
|
+
# Infer:
|
|
99
|
+
# - population mean of g
|
|
100
|
+
# - population std of g
|
|
101
|
+
# - g for each training patient
|
|
102
|
+
# - observation noise sigma
|
|
103
|
+
#
|
|
104
|
+
# using all observations from the five training patients.
|
|
105
|
+
# ---------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
est.Train( trainDataIdxs=jnp.arange(5), TrainLikelihoodFn=TrainLikelihoodFn, Method=bet.Nuts(numWarmup=500,numSamples=500,numChains=4), printSummary=True)
|
|
108
|
+
trainScores=est.ScoreTrainSamples(ScoreFn=ScoreFn)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------
|
|
112
|
+
# 2. TESTING WITHOUT INFERENCE
|
|
113
|
+
#
|
|
114
|
+
# Do not use any test-patient observations for inference.
|
|
115
|
+
#
|
|
116
|
+
# Use the median global posterior state to keep this introductory run fast.
|
|
117
|
+
# For each test patient, sample local Zs from the learned
|
|
118
|
+
# population distribution and generate trajectories.
|
|
119
|
+
#
|
|
120
|
+
# numSamples controls the number of local Z samples generated.
|
|
121
|
+
# ---------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
est.Test( testDataIdxs=jnp.arange(5,10), TestLikelihoodFn=None, nGlobalSamples=None, nLocalZSamples=2000)
|
|
124
|
+
noInferenceScores=est.ScoreTestSamples(ScoreFn=ScoreFn)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------
|
|
128
|
+
# 3. TESTING WITH NUTS
|
|
129
|
+
#
|
|
130
|
+
# Use the median global parameter values from the training posterior
|
|
131
|
+
# and infer each test patient's local Z using early observations.
|
|
132
|
+
#
|
|
133
|
+
# TestLikelihoodFn exposes only the first nFit=3 tumor measurements,
|
|
134
|
+
# so each test patient's g is updated using its early trajectory.
|
|
135
|
+
#
|
|
136
|
+
# ScoreFn evaluates only the final five measurements, held out from inference.
|
|
137
|
+
# ---------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
est.Test( testDataIdxs=jnp.arange(5,10), TestLikelihoodFn=TestLikelihoodFn, Method=bet.Nuts, nGlobalSamples=None, printSummary=True)
|
|
140
|
+
testNutsScores=est.ScoreTestSamples(ScoreFn=ScoreFn)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------
|
|
144
|
+
# Results
|
|
145
|
+
# ---------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
print("Training score: ",jnp.mean(trainScores))
|
|
148
|
+
print("Test score, no inference: ",jnp.mean(noInferenceScores))
|
|
149
|
+
print("Test score, NUTS: ",jnp.mean(testNutsScores))
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import numpyro as npo
|
|
2
|
+
npo.set_host_device_count(4)
|
|
3
|
+
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
import numpyro.distributions as dist
|
|
6
|
+
from jax import random
|
|
7
|
+
|
|
8
|
+
import BayesianEstimationTool as bet
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------
|
|
12
|
+
# Generate 10 synthetic patient trajectories
|
|
13
|
+
# ---------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
keyG,keyNoise=random.split(random.PRNGKey(2),2)
|
|
16
|
+
|
|
17
|
+
nPatients=10
|
|
18
|
+
nTimes=8
|
|
19
|
+
times=jnp.linspace(0,10,nTimes)
|
|
20
|
+
|
|
21
|
+
# True population:
|
|
22
|
+
# mean growth rate = 0.15
|
|
23
|
+
# std growth rate = 0.03
|
|
24
|
+
|
|
25
|
+
trueG=0.15+0.03*random.normal(keyG,(nPatients,))
|
|
26
|
+
|
|
27
|
+
# Give patients slightly different initial tumor sizes.
|
|
28
|
+
|
|
29
|
+
v0=jnp.linspace(0.8,1.2,nPatients)
|
|
30
|
+
trueTumorSize=v0[:,None]*jnp.exp(trueG[:,None]*times)
|
|
31
|
+
tumorSize=trueTumorSize+0.05*random.normal(keyNoise,trueTumorSize.shape)
|
|
32
|
+
|
|
33
|
+
data={
|
|
34
|
+
"times":jnp.tile(times,(nPatients,1)),
|
|
35
|
+
"v0":v0,
|
|
36
|
+
"tumorSize":tumorSize,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------
|
|
41
|
+
# Model components
|
|
42
|
+
# ---------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
# Analytical solution of dV/dt = gV.
|
|
45
|
+
|
|
46
|
+
# localZs["g"] is the standardized patient-specific deviation.
|
|
47
|
+
# Convert it to the physical growth rate using the population mean/std.
|
|
48
|
+
|
|
49
|
+
def ModelFn(globalParams,localZs,data):
|
|
50
|
+
g=globalParams["gMean"]+globalParams["gStd"]*localZs["g"]
|
|
51
|
+
return {"tumorSize":data["v0"]*jnp.exp(g*data["times"])}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Population parameters and observation noise.
|
|
55
|
+
|
|
56
|
+
def GlobalPriorFn(dataShapes):
|
|
57
|
+
return {
|
|
58
|
+
"gMean":dist.Normal(0.15,0.1),
|
|
59
|
+
"gStd":dist.HalfNormal(0.1),
|
|
60
|
+
"sigma":dist.HalfNormal(0.1),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Training likelihood for one patient; BET vectorizes it across patients.
|
|
65
|
+
|
|
66
|
+
def TrainLikelihoodFn(globalParams,localZs,data,modelOut):
|
|
67
|
+
return {"tumorSize":(data["tumorSize"],dist.Normal(modelOut["tumorSize"],globalParams["sigma"]))}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Test likelihood for one patient; only the first nFit observations are used.
|
|
71
|
+
|
|
72
|
+
def TestLikelihoodFn(globalParams,localZs,data,modelOut):
|
|
73
|
+
nFit=3
|
|
74
|
+
tumorSize=modelOut["tumorSize"]
|
|
75
|
+
mask=jnp.arange(tumorSize.shape[-1])<nFit
|
|
76
|
+
return {"tumorSize":(data["tumorSize"],dist.Normal(tumorSize,globalParams["sigma"]).mask(mask))}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Score only the last five observations; the first three may be used for fitting.
|
|
80
|
+
|
|
81
|
+
def ScoreFn(modelOut,data):
|
|
82
|
+
return jnp.mean(jnp.abs(modelOut["tumorSize"][3:]-data["tumorSize"][3:]))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------
|
|
86
|
+
# Construct Estimator
|
|
87
|
+
# ---------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
est=bet.Estimator(data=data,ModelFn=ModelFn,localZNames=["g"],GlobalPriorFn=GlobalPriorFn,rngSeed=0)
|
|
90
|
+
|
|
91
|
+
# Patients 0-4 train the population model.
|
|
92
|
+
# Patients 5-9 are completely held out.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------
|
|
96
|
+
# 1. TRAINING
|
|
97
|
+
#
|
|
98
|
+
# Infer:
|
|
99
|
+
# - population mean of g
|
|
100
|
+
# - population std of g
|
|
101
|
+
# - g for each training patient
|
|
102
|
+
# - observation noise sigma
|
|
103
|
+
#
|
|
104
|
+
# using all observations from the five training patients.
|
|
105
|
+
# ---------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
est.Train( trainDataIdxs=jnp.arange(5), TrainLikelihoodFn=TrainLikelihoodFn, Method=bet.Svi(numSteps=10000,numSamples=2000,stepSize=0.01), printSummary=True)
|
|
108
|
+
trainScores=est.ScoreTrainSamples(ScoreFn=ScoreFn)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------
|
|
112
|
+
# 2. TESTING WITHOUT INFERENCE
|
|
113
|
+
#
|
|
114
|
+
# Do not use any test-patient observations for inference.
|
|
115
|
+
#
|
|
116
|
+
# Use the median global posterior state to keep this introductory run fast.
|
|
117
|
+
# For each test patient, sample local Zs from the learned
|
|
118
|
+
# population distribution and generate trajectories.
|
|
119
|
+
#
|
|
120
|
+
# numSamples controls the number of local Z samples generated.
|
|
121
|
+
# ---------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
est.Test( testDataIdxs=jnp.arange(5,10), TestLikelihoodFn=None, nGlobalSamples=None, nLocalZSamples=2000)
|
|
124
|
+
noInferenceScores=est.ScoreTestSamples(ScoreFn=ScoreFn)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------
|
|
128
|
+
# 3. TESTING WITH SVI
|
|
129
|
+
#
|
|
130
|
+
# Use the median global parameter values from the training posterior
|
|
131
|
+
# and infer each test patient's local Z using early observations.
|
|
132
|
+
#
|
|
133
|
+
# TestLikelihoodFn exposes only the first nFit=3 tumor measurements,
|
|
134
|
+
# so each test patient's g is updated using its early trajectory.
|
|
135
|
+
#
|
|
136
|
+
# ScoreFn evaluates only the final five measurements, held out from inference.
|
|
137
|
+
# ---------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
est.Test( testDataIdxs=jnp.arange(5,10), TestLikelihoodFn=TestLikelihoodFn, Method=bet.Svi, nGlobalSamples=None, printSummary=True)
|
|
140
|
+
testSviScores=est.ScoreTestSamples(ScoreFn=ScoreFn)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------
|
|
144
|
+
# Results
|
|
145
|
+
# ---------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
print("Training score: ",jnp.mean(trainScores))
|
|
148
|
+
print("Test score, no inference: ",jnp.mean(noInferenceScores))
|
|
149
|
+
print("Test score, SVI: ",jnp.mean(testSviScores))
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import numpyro as npo
|
|
2
|
+
npo.set_host_device_count(4)
|
|
3
|
+
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
import numpyro.distributions as dist
|
|
6
|
+
from jax import random
|
|
7
|
+
|
|
8
|
+
import BayesianEstimationTool as bet
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------
|
|
12
|
+
# Generate 10 synthetic patient trajectories
|
|
13
|
+
# ---------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
keyG,keyD,keySize,keyNoise=random.split(random.PRNGKey(2),4)
|
|
16
|
+
|
|
17
|
+
nPatients=10
|
|
18
|
+
nTimes=11
|
|
19
|
+
times=jnp.linspace(0,10,nTimes)
|
|
20
|
+
drugStart=4.0
|
|
21
|
+
|
|
22
|
+
# Generate correlated patient-specific growth and drug-response effects.
|
|
23
|
+
|
|
24
|
+
rho=0.8
|
|
25
|
+
zG=random.normal(keyG,(nPatients,))
|
|
26
|
+
zDIndependent=random.normal(keyD,(nPatients,))
|
|
27
|
+
zD=rho*zG+jnp.sqrt(1.0-rho**2)*zDIndependent
|
|
28
|
+
|
|
29
|
+
# True population:
|
|
30
|
+
# mean growth rate = 0.15
|
|
31
|
+
# std growth rate = 0.03
|
|
32
|
+
# mean drug effect = 0.25
|
|
33
|
+
# std drug effect = 0.05
|
|
34
|
+
|
|
35
|
+
trueG=0.15+0.03*zG
|
|
36
|
+
trueD=0.25+0.05*zD
|
|
37
|
+
|
|
38
|
+
# Give patients different initial tumor sizes.
|
|
39
|
+
|
|
40
|
+
v0=random.uniform(keySize,(nPatients,),minval=0.8,maxval=1.2)
|
|
41
|
+
|
|
42
|
+
timeOnDrug=jnp.maximum(times-drugStart,0.0)
|
|
43
|
+
trueTumorSize=v0[:,None]*jnp.exp(trueG[:,None]*times-trueD[:,None]*timeOnDrug)
|
|
44
|
+
tumorSize=trueTumorSize+0.05*random.normal(keyNoise,trueTumorSize.shape)
|
|
45
|
+
|
|
46
|
+
data={
|
|
47
|
+
"times":jnp.tile(times,(nPatients,1)),
|
|
48
|
+
"drugStart":jnp.full(nPatients,drugStart),
|
|
49
|
+
"v0":v0,
|
|
50
|
+
"tumorSize":tumorSize,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------
|
|
55
|
+
# Model components
|
|
56
|
+
# ---------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
# Analytical solution: dV/dt = gV before treatment and (g-d)V after.
|
|
59
|
+
|
|
60
|
+
# localZs["g"] and localZs["d"] are standardized
|
|
61
|
+
# patient-specific deviations.
|
|
62
|
+
#
|
|
63
|
+
# Convert them to physical parameter values using the inferred
|
|
64
|
+
# population means and standard deviations.
|
|
65
|
+
|
|
66
|
+
def ModelFn(globalParams,localZs,data):
|
|
67
|
+
g=globalParams["gMean"]+globalParams["gStd"]*localZs["g"]
|
|
68
|
+
d=globalParams["dMean"]+globalParams["dStd"]*localZs["d"]
|
|
69
|
+
tumorSize=data["v0"]*jnp.exp(g*data["times"]-d*jnp.maximum(data["times"]-data["drugStart"],0.0))
|
|
70
|
+
return {"tumorSize":tumorSize}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Population parameters and observation noise.
|
|
74
|
+
|
|
75
|
+
def GlobalPriorFn(dataShapes):
|
|
76
|
+
return {
|
|
77
|
+
"gMean":dist.Normal(0.15,0.1),
|
|
78
|
+
"gStd":dist.HalfNormal(0.1),
|
|
79
|
+
"dMean":dist.Normal(0.25,0.1),
|
|
80
|
+
"dStd":dist.HalfNormal(0.1),
|
|
81
|
+
"sigma":dist.HalfNormal(0.1),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Training likelihood for one patient; BET vectorizes it across patients.
|
|
86
|
+
|
|
87
|
+
def TrainLikelihoodFn(globalParams,localZs,data,modelOut):
|
|
88
|
+
return {"tumorSize":(data["tumorSize"],dist.Normal(modelOut["tumorSize"],globalParams["sigma"]))}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Score one trajectory against all available observations.
|
|
92
|
+
|
|
93
|
+
def ScoreFn(modelOut,data):
|
|
94
|
+
return jnp.mean(jnp.abs(modelOut["tumorSize"]-data["tumorSize"]))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------
|
|
98
|
+
# Construct Estimator
|
|
99
|
+
# ---------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
est=bet.Estimator(data=data,ModelFn=ModelFn,localZNames=["g","d"],GlobalPriorFn=GlobalPriorFn,rngSeed=0)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------
|
|
105
|
+
# 1. TRAINING
|
|
106
|
+
#
|
|
107
|
+
# Patients 0-4 train the population model.
|
|
108
|
+
#
|
|
109
|
+
# Infer:
|
|
110
|
+
# - population mean growth rate
|
|
111
|
+
# - population std growth rate
|
|
112
|
+
# - population mean drug effect
|
|
113
|
+
# - population std drug effect
|
|
114
|
+
# - correlation between patient-specific g and d
|
|
115
|
+
# - patient-specific g and d values
|
|
116
|
+
# - observation noise sigma
|
|
117
|
+
#
|
|
118
|
+
# using all observations from the five training patients.
|
|
119
|
+
# ---------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
est.Train(trainDataIdxs=jnp.arange(5),TrainLikelihoodFn=TrainLikelihoodFn,Method=bet.Nuts(numWarmup=500,numSamples=500,numChains=4),printSummary=True)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------
|
|
125
|
+
# 2. SCORE TRAINING POSTERIOR
|
|
126
|
+
# ---------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
trainScores=est.ScoreTrainSamples(ScoreFn=ScoreFn)
|
|
129
|
+
|
|
130
|
+
print("Training score before save:",jnp.mean(trainScores))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------
|
|
134
|
+
# 3. SAVE
|
|
135
|
+
#
|
|
136
|
+
# The Estimator is saved as a NumPy .npz archive.
|
|
137
|
+
#
|
|
138
|
+
# By default, Save stores:
|
|
139
|
+
# - Estimator metadata
|
|
140
|
+
# - training/test run settings, including method name and constructor arguments
|
|
141
|
+
# - training/test indices
|
|
142
|
+
# - RNG state
|
|
143
|
+
# - training global posterior samples
|
|
144
|
+
# - training Cholesky samples
|
|
145
|
+
# - training local posterior samples
|
|
146
|
+
# - test local Zs/globals/cholesky, if testing has been run
|
|
147
|
+
#
|
|
148
|
+
# Model outputs and the full input dataset are not saved by default.
|
|
149
|
+
# ---------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
est.Save("SimpleTumorGrowthNuts.npz")
|