Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# CI — lint + the full default test suite (unit + integration + regression) on the
# pure-Python core (no Julia/[vle] extra installed). `pytest -q`'s own addopts
# (`-m 'not vle'`) deselects the Julia-backed tests automatically -- confirmed none of
# the non-`vle`-marked tests import anything Julia-touching at module import time, so
# this runs clean without the [vle] extra. The frozen stack (TF 2.16.2 / GPflow 2.9.2 /
# Py 3.9) is what's pinned in pyproject.toml; Julia-backed VLE + regression tests that
# need it stay a separate, gated job for whenever CI grows a Julia-capable runner.
# pure-Python core (no Julia/[vle] extra installed), across every Python version this
# package claims to support (3.9-3.12; see pyproject.toml's requires-python and
# docs/installation.md for why 3.13+ isn't -- upstream, GPflow requires numpy<2 and no
# numpy 1.x supports 3.13). `pytest -q`'s own addopts (`-m 'not vle'`) deselects the
# Julia-backed tests automatically -- confirmed none of the non-`vle`-marked tests
# import anything Julia-touching at module import time, so this runs clean without the
# [vle] extra. Julia-backed VLE + regression tests that need it stay a separate, gated
# job for whenever CI grows a Julia-capable runner.
name: CI

on:
Expand All @@ -14,7 +16,7 @@ on:
branches: [main]

jobs:
test:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -27,5 +29,21 @@ jobs:
pip install -e ".[dev]"
- name: Lint (ruff)
run: ruff check .

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install (core + dev)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run the default test suite (unit + integration + regression, not vle)
run: pytest -q
6 changes: 5 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
# the autodoc_mock_imports fallback if this ever becomes too slow/flaky on RTD's
# builders). Julia/Clapeyron (the `[vle]` extra) are never installed here -- the docs
# build never needs them (see docs/vle_example.md).
#
# Built on Python 3.12 (the top of the supported 3.9-3.12 range; see
# pyproject.toml's requires-python) so autodoc's real import of the TF-backed stack
# genuinely exercises the newest supported interpreter, not just the oldest.
version: 2

build:
os: ubuntu-24.04
tools:
python: "3.9"
python: "3.12"

sphinx:
configuration: docs/conf.py
Expand Down
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ entry below summarizes the library relative to the paper's original research cod

## [Unreleased]

Nothing yet.
### Added

- Support for Python 3.10, 3.11, and 3.12, alongside the existing 3.9 (`pyproject.toml`'s
`requires-python` widened from `>=3.9,<3.10` to `>=3.9,<3.13`; Python 3.9 alone is EOL
as of October 2025). Verified with a full test-suite run -- including the exact-value
regression pins (`tests/integration/data/synthetic_baseline.json`, atol 1e-10) and the
paper reference-value regressions -- on all four versions; no dependency version
changed. `.github/workflows/ci.yml` now runs the default suite across a 3.9-3.12
matrix. `.readthedocs.yaml` builds on Python 3.12.
- A documented explanation of why Python 3.13+ isn't supported: GPflow requires
`numpy<2` in every release, and no NumPy 1.x publishes a Python 3.13 wheel -- an
upstream constraint, not a limitation of this package's own pins. See
`docs/installation.md`.

## [0.1.2] - 2026-08-06

Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ The **core library is pure Python** (GPflow / TensorFlow / NumPy / SciPy) with n
dependency. Julia + Clapeyron are only needed for the `vle_distillation` example, which
isn't part of the PyPI package -- see "From source" below.

Supports **Python 3.9-3.12**. Python 3.13+ isn't available: this package depends on
GPflow, and GPflow requires `numpy<2` in every release -- no NumPy 1.x publishes a
Python 3.13 wheel. That's an upstream constraint, not something this package can work
around; see [`docs/installation.md`](docs/installation.md) for the full explanation.

**macOS note:** set `export PYTHON_JULIACALL_HANDLE_SIGNALS=yes` before importing
`juliacall`, or Julia crashes with a bus error (SIGBUS).

Expand Down
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ design with Bayesian hierarchical Gaussian-process (GP) surrogates. Prior physic
knowledge is encoded through priors on the GP hyperparameters; new data is chosen by
maximizing the predictive differential entropy of the resulting hierarchical
posterior, so *hyperparameter uncertainty* -- not just predictive variance -- drives
data acquisition. That distinction is the paper's contribution and the reason the
library exists: a plain GP's predictive variance ignores uncertainty in the
hyperparameters themselves, which matters most exactly when data is scarce.
data acquisition. Many other adaptive sampling methods in literature use the GP's predictive variance
and ignore uncertainty in the hyperparameters themselves, which (we argue) matters most exactly when data is scarce.
BITS for GAPS addresses this limitation.

```{admonition} Reference
K. D. Jones and A. W. Dowling, "BITS for GAPS: Bayesian Information-Theoretic
Expand Down
28 changes: 21 additions & 7 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,26 @@ optional VLE/distillation example, and even then only when you actually call int
pip install bits_for_gaps
```

```{admonition} Frozen dependency stack
```{admonition} Frozen dependency stack, Python 3.9-3.12
:class: note
The core pins an exact, verified-working stack rather than floating version ranges:
Python 3.9, NumPy 1.26, SciPy 1.13, GPflow 2.9.2, TensorFlow 2.16.2, TensorFlow
Probability 0.24.0. This is a deliberate reproducibility choice -- GPflow's TensorFlow
dependency makes casual version bumps risky, so modernizing the stack is left as a
separate, later effort.
The core pins an exact, verified-working dependency stack rather than floating version
ranges: NumPy 1.26, SciPy 1.13, GPflow 2.9.2, TensorFlow 2.16.2, TensorFlow Probability
0.24.0. This is a deliberate reproducibility choice -- GPflow's TensorFlow dependency
makes casual version bumps risky, so modernizing the stack is left as a separate, later
effort. The package itself supports **Python 3.9 through 3.12**, verified with a real
test-suite run (including the exact-value regression pins) on every one of those four
versions.
```

```{admonition} Why not Python 3.13+?
:class: important
This is an **upstream constraint**, not a project choice: `bits_for_gaps` depends on
GPflow, and GPflow requires `numpy<2` in every release it has ever published, including
the latest (2.11.1). No 1.x release of NumPy publishes a Python 3.13 wheel -- the first
NumPy version that does is 2.1.0. So GPflow (and therefore this package) cannot run on
Python 3.13 at all, regardless of what this package's own pins say. TensorFlow's newest
release similarly has no Python 3.14 wheels yet. Python 3.12 is the ceiling until GPflow
adds NumPy 2 support upstream -- no timeline is promised or implied.
```

Verify it imports (no Julia touched):
Expand All @@ -30,7 +43,8 @@ python -c "import bits_for_gaps; print(bits_for_gaps.__version__)"
```bash
git clone https://github.com/dowlinglab/bits_for_gaps
cd bits_for_gaps
conda env create -f environment.yml # Python 3.9 + the pinned stack
conda env create -f environment.yml # Python 3.12 + the pinned stack (see the file's
# header comment for using 3.9 instead)
conda activate bits_for_gaps
pip install -e .
```
Expand Down
13 changes: 12 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@
# conda activate bits_for_gaps
# pip install -e ".[dev,vle]"
#
# Python 3.12 (moved here from 3.9 -- see CHANGELOG.md's Python-3.9-3.12 entry): the
# package supports 3.9-3.12, and this dev/reproduction env now uses the top of that
# range so day-to-day development and `paper/reproduce.py`/`paper/full_reproduction.py`
# runs exercise the newest supported interpreter. Verified with a from-scratch,
# ~28-minute `paper/full_reproduction.py` rerun on 3.12: HMC R-hat/ESS, the
# hyperparameter posterior, and the McCabe-Thiele column all reproduce the paper's
# statistical/qualitative behavior (see paper/REPRODUCTION.md). If you specifically
# need the interpreter the archived published results (`paper/data/`, `paper/reference/`)
# were originally produced on, change `python=3.12` below to `python=3.9` -- every
# other pin in this file is unaffected either way.
#
# IMPORTANT (macOS): export PYTHON_JULIACALL_HANDLE_SIGNALS=yes before running
# anything that imports juliacall, or Julia crashes with a bus error (SIGBUS).
name: bits_for_gaps
channels:
- conda-forge
- defaults
dependencies:
- python=3.9
- python=3.12
- pip
- numpy=1.26.*
- scipy=1.13.*
Expand Down
50 changes: 50 additions & 0 deletions paper/REPRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,53 @@ considerably tighter than "qualitative" on the deterministic parts of the pipeli
(HMC diagnostics, hyperparameter posterior, entropy field). Reproduce via
`paper/full_reproduction.py` (module docstring has the exact constants used); not
gated in CI (see above).

### Cross-Python-version check: the same rerun on Python 3.12

The numbers above are from a Python 3.9 run (this package's original dev environment).
When widening supported Python versions to 3.9-3.12, `paper/full_reproduction.py` was
rerun from scratch on **3.12** (same TF 2.16.2/GPflow 2.9.2 pins -- only the
interpreter changed) to confirm the science, not just the test suite, holds across
versions. Completed in 0.47 h (28 min, inside the documented ~25-30 min range).

**Iterations 1-2 reproduced the 3.9 run's max entropy to 6+ significant figures**
(1.4576908 vs. 1.4577 at iteration 1; 1.2470073 vs. 1.2470 at iteration 2) -- striking,
given no bitwise-reproducibility guarantee was made across interpreters. From
iteration 3 on, the two runs' design trajectories diverge (expected: each iteration's
`predict_grid`-style posterior-predictive diagnostic draws from TensorFlow's ambient,
unseeded RNG -- see `bits_for_gaps/mixture.py`'s module docstring -- so the *same*
non-reproducibility this package already documents *within* one Python version also
separates two versions' runs once it fires). What matters for this check is that the
statistical conclusions survive that divergence:

| | R-hat (iter 15) | ESS (iter 15) |
|---|---|---|
| Python 3.9 fresh run (above) | 1.00523, 1.00730, 1.00879 | 1468.29, 2428.09, 653.15 |
| Python 3.12 fresh run | 1.00527, 1.00736, 1.00879 | 1468.06, 2424.94, 653.15 |

Both comfortably under the R-hat < 1.1 threshold, both healthy ESS -- HMC convergence
is not Python-version-sensitive.

| | mean | median |
|---|---|---|
| Python 3.9 fresh run (above) | 1.35645, 0.86239, 3.19502 | 1.28619, 0.81949, 3.04407 |
| Python 3.12 fresh run | 1.35606, 0.86216, 3.19502 | 1.28501, 0.81881, 3.04407 |

Same ordering (`lengthscale_2` > `lengthscale_1`) as every other run of this model.

Max entropy still decays from **1.458** (iteration 1) to **-0.224** (iteration 15),
crossing zero in the same iteration range as the 3.9 run. Test RMSE still drops from
**4.385** (iteration 1) to **0.856** (iteration 15) -- a ~5.1x reduction, matching the
3.9 fresh run's ~4.9x (4.337 -> 0.887) and the paper's own ~6.5x (4.34 -> 0.67); same
non-monotonic-between-iterations trend, expected for an entropy- (not error-) driven
acquisition. Both the Wilson and the 15-iteration-adaptive surrogate McCabe-Thiele
columns converged, tracking each other within 0.027 mole fraction (liquid) / 0.014
(vapor) at every stage -- inside the 0.03/0.014 band the 3.9 run and the regression
suite's own tolerance already use.

**Conclusion: Python 3.12 gives statistically equivalent science to Python 3.9**, with
the deterministic parts of the pipeline (HMC diagnostics, hyperparameter posterior
ordering) landing close enough to look almost version-independent, and the
RNG-sensitive parts (entropy trajectory past iteration 2, test RMSE, column stage
values) landing in the same regime as every other independent rerun of this model,
3.9 included.
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "bits_for_gaps"
dynamic = ["version"]
description = "Bayesian Information-Theoretic Sampling for hierarchical GAussian Process Surrogates (BITS for GAPS)"
readme = "README.md"
requires-python = ">=3.9,<3.10"
requires-python = ">=3.9,<3.13"
license = { file = "LICENSE" }
authors = [
{ name = "Alexander W. Dowling", email = "adowling@nd.edu" },
Expand All @@ -23,14 +23,20 @@ classifiers = [
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Operating System :: OS Independent",
]

# Frozen reproduction baseline: an exact, verified-working stack rather than floating
# version ranges, since GPflow's TensorFlow dependency makes casual version bumps
# risky. tensorflow-macos on Apple Silicon; plain tensorflow elsewhere.
# risky. tensorflow-macos on Apple Silicon; plain tensorflow elsewhere. Verified on
# Python 3.9-3.12 (see docs/installation.md); the numpy<2 pin is also why 3.13+ isn't
# available -- GPflow requires numpy<2 in every release, and no numpy 1.x supports
# Python 3.13 (an upstream constraint -- see docs/installation.md).
dependencies = [
"numpy>=1.26,<2",
"scipy>=1.13,<1.14",
Expand Down
Loading