Skip to content

Widen supported Python versions to 3.9-3.12 - #2

Merged
adowling2 merged 4 commits into
mainfrom
python-version-support
Aug 7, 2026
Merged

Widen supported Python versions to 3.9-3.12#2
adowling2 merged 4 commits into
mainfrom
python-version-support

Conversation

@adowling2

Copy link
Copy Markdown
Contributor

Summary

Widens supported Python versions from >=3.9,<3.10 to >=3.9,<3.13, verified with
real runs on every claimed version plus a full from-scratch ~28-minute stochastic
reproduction rerun on 3.12. No dependency version changed and no tolerance or
reference/baseline file was touched
— see the guardrails section below.

Stage 2: verification table (version × check)

Each version installed into a fresh conda env (conda create -n ... python=X.Y pip,
then pip install -e ".[dev]"), never reusing another version's env.

Check 3.9 (existing dev env) 3.10 3.11 3.12
pytest -q 204 passed, 2 deselected 204 passed, 2 deselected 204 passed, 2 deselected 204 passed, 2 deselected
test_matches_synthetic_baseline (atol=1e-10) passed passed passed passed
pytest -q tests/regression 22 passed, 2 deselected 22 passed, 2 deselected 22 passed, 2 deselected 22 passed, 2 deselected
Import contract (bits_for_gaps Julia-free/TF-lazy) OK, version 0.1.2 OK, version 0.1.2 OK, version 0.1.2 OK, version 0.1.2
Lazy GPflow import (AnisotropicSE()) OK, ndim=2 OK, ndim=2 OK, ndim=2 OK, ndim=2
setuptools.__version__ (the <81 bound) 80.9.0 80.10.2 80.10.2 80.10.2

Every version resolved the exact same pinned stack (numpy 1.26.4, scipy 1.13.1,
tensorflow/tensorflow-macos 2.16.2, gpflow 2.9.2, tensorflow-probability 0.24.0,
tf-keras 2.16.0) from pip install -e ".[dev]" alone — no version-specific dependency
resolution surprises. All four temporary conda envs (bfg-py310, bfg-py311,
bfg-py312) were removed after use; only the pre-existing bits_for_gaps (3.9) dev
env remains.

Stage 3: reproduction on Python 3.12

In the bfg-py312 env with pip install -e ".[dev,vle]" and
PYTHON_JULIACALL_HANDLE_SIGNALS=yes:

  • pytest -m vle: 2 passed — Julia/Clapeyron work fine on 3.12.

  • python paper/reproduce.py: all 11 figures regenerated from paper/data/ in
    ~66 seconds wall-clock. Spot-checked two rendered PNGs (the Wilson McCabe-Thiele
    diagram and the HMC trace plot) — both render correctly, R-hat ≈ 1.005–1.009 on the
    trace plot.

  • python paper/full_reproduction.py (the 15-iteration adaptive HMC + acquisition
    loop, from scratch, no archived data as input): completed in 0.47 h (28 min),
    inside the documented ~25–30 min range. Compared against the existing Python 3.9
    fresh-run numbers already recorded in paper/REPRODUCTION.md (full write-up added
    in this PR, "Cross-Python-version check" section):

    Python 3.9 fresh run Python 3.12 fresh run
    Max entropy, iter 1 1.4577 1.4576908
    Max entropy, iter 2 1.2470 1.2470073
    Max entropy, iter 15 -0.0084 -0.2244
    R-hat, iter 15 1.00523, 1.00730, 1.00879 1.00527, 1.00736, 1.00879
    ESS, iter 15 1468.29, 2428.09, 653.15 1468.06, 2424.94, 653.15
    Hyperparameter posterior mean 1.35645, 0.86239, 3.19502 1.35606, 0.86216, 3.19502
    Hyperparameter posterior median 1.28619, 0.81949, 3.04407 1.28501, 0.81881, 3.04407
    Test RMSE, iter 1 → 15 4.337 → 0.887 (~4.9×) 4.385 → 0.856 (~5.1×)
    Column tracking (liquid / vapor mole fraction) within 0.03 / 0.014 within 0.027 / 0.014
    Both columns converged? yes yes

    Iterations 1–2's max entropy matched to 6+ significant figures — striking,
    given no bitwise-reproducibility guarantee exists across interpreters. From
    iteration 3 on, the two runs' design trajectories diverge, as expected: each
    iteration's posterior-predictive diagnostic draws from TensorFlow's ambient,
    unseeded
    RNG (already documented as non-reproducible in mixture.py's module
    docstring — this is the same non-reproducibility that already separates two
    independent runs within one Python version, not something new to 3.12). Despite
    that divergence, every statistical conclusion holds: R-hat/ESS stay comfortably
    converged, the hyperparameter-posterior ordering (lengthscale_2 >
    lengthscale_1) is preserved, entropy still decays and crosses zero in the same
    iteration range, test RMSE still drops by a comparable factor, and both McCabe-
    Thiele columns still converge and track each other within the same tolerance band
    already used elsewhere in this repo (0.03/0.014).

    Conclusion: Python 3.12 gives statistically equivalent science to Python 3.9.
    Run artifacts stayed in the gitignored results_remaked/; nothing was committed.

environment.yml decision

Moved to Python 3.12 (from 3.9), since Stage 3's rerun came out clean on every
axis above. The dev/reproduction environment now uses the top of the newly-supported
range, so day-to-day development and the paper/reproduce.py /
paper/full_reproduction.py scripts exercise the newest supported interpreter by
default. The file's header comment explains the decision and gives the one-line
change (python=3.12python=3.9) for anyone who specifically wants the
interpreter the archived published results (paper/data/, paper/reference/) were
originally produced on — every other pin in the file is unaffected either way.

No dependency version changed; no tolerance or reference file touched

  • numpy, scipy, tensorflow/tensorflow-macos, gpflow, tensorflow-probability,
    tf-keras, and the setuptools<81 bound are byte-for-byte identical to what was
    on main. Confirmed in the built wheel's Requires-Dist metadata (see Stage 5
    below) and by diffing pyproject.toml's dependencies list.
  • tests/integration/data/synthetic_baseline.json and paper/reference/*.json were
    not modified (git diff main -- tests/integration/data paper/reference is empty) —
    every version's test_matches_synthetic_baseline and tests/regression run passed
    against the existing files, at the existing tolerances.
  • paper/data/ and paper/reference/ contents untouched.

Why Python 3.13+ is not supported (upstream constraint)

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 NumPy 1.x release
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, no matter what this
package's own pins say. TensorFlow's newest release (2.21.0) also publishes no Python
3.14 wheels. Python 3.12 is the hard ceiling until GPflow adds NumPy 2 support
upstream
— no timeline is promised or implied. Documented in
docs/installation.md (a dedicated "Why not Python 3.13+?" admonition), README.md,
and CHANGELOG.md.

Stage 5: final gates

  • pytest -q: 204 passed, 2 deselected on both 3.9 and 3.12 (re-verified after
    every commit on this branch).
  • pytest -m vle: 2 passed on 3.9.
  • ruff check .: clean.
  • sphinx-build -W docs docs/_build/html (clean rebuild): clean, no warnings/errors.
  • python -m build + twine check dist/*: both sdist and wheel PASSED. Wheel
    metadata confirms Requires-Python: <3.13,>=3.9 and every Requires-Dist pin
    unchanged (numpy<2,>=1.26, scipy<1.14,>=1.13, gpflow==2.9.2,
    tensorflow-probability==0.24.0, tf-keras==2.16.0, tensorflow(-macos)==2.16.2,
    setuptools<81). dist//build/ removed after inspection.

Files changed

  • pyproject.toml: requires-python widened; classifiers for 3.10/3.11/3.12 added.
  • .github/workflows/ci.yml: split into a lint job (ruff, once) and a test job
    matrixed across Python 3.9–3.12.
  • .readthedocs.yaml: build Python 3.9 → 3.12.
  • environment.yml: python=3.9python=3.12 (see decision above).
  • docs/installation.md, README.md, CHANGELOG.md: document the widened support
    and the 3.13+ upstream-constraint explanation.
  • paper/REPRODUCTION.md: new "Cross-Python-version check" section with the Stage 3
    numbers above.

🤖 Generated with Claude Code

adowling2 and others added 4 commits August 6, 2026 10:57
pyproject.toml: requires-python ">=3.9,<3.10" -> ">=3.9,<3.13" (Python
3.9 alone is EOL as of October 2025). Added trove classifiers for 3.10,
3.11, 3.12. No dependency versions changed -- numpy/scipy/tensorflow/
gpflow/tfp/tf-keras/setuptools<81 are exactly as pinned before.

3.13+ is not attempted: GPflow requires numpy<2 in every release
(including the latest 2.11.1), and no numpy 1.x publishes a cp313
wheel -- an upstream constraint, not a consequence of our pinning.
TensorFlow's newest release also has no cp314 wheels. Python 3.12 is
therefore the hard ceiling until GPflow supports numpy 2.

.github/workflows/ci.yml: split into a `lint` job (ruff, once, on 3.9)
and a `test` job running the full default suite across a 3.9/3.10/3.11/
3.12 matrix (fail-fast: false, so one version's failure doesn't hide
the others' results). Still no Julia in CI; `pytest -q`'s own
`-m 'not vle'` addopts keeps deselecting the Julia-backed tests.

.readthedocs.yaml: build tool Python 3.9 -> 3.12 (the top of the newly
supported range), so autodoc's real import of the TF-backed stack
exercises the newest interpreter rather than the oldest.

environment.yml intentionally NOT touched yet -- that decision is
deferred until Stage 3's Python 3.12 reproduction rerun confirms (or
doesn't) that the dev/reproduction environment can safely move there.

Verified on the existing Python 3.9 dev env: pytest -q (204 passed, 2
deselected, unchanged), ruff clean, and pyproject.toml round-trips
through tomli with the intended requires-python/classifiers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/installation.md: the "Frozen dependency stack" admonition now
states Python 3.9-3.12 explicitly (was silently implying 3.9-only via
"Python 3.9, NumPy 1.26, ..."). Added a dedicated "Why not Python
3.13+?" admonition: GPflow requires numpy<2 in every release including
the latest, no NumPy 1.x publishes a Python 3.13 wheel, and TensorFlow
has no Python 3.14 wheels either -- stated as an upstream constraint
this package cannot work around, with no timeline promised.

README.md: added the same "Supports Python 3.9-3.12" statement (with a
pointer to docs/installation.md for the full explanation) next to the
pip install instructions, where previously there was no Python-version
statement at all.

CHANGELOG.md: [Unreleased] now describes the widened requires-python,
the verification performed (full suite + regression pins on all four
versions, no dependency version changed), the CI matrix, and the RTD
Python bump -- no version number or date invented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n rerun

environment.yml: python=3.9 -> python=3.12, now that Stage 3's from-
scratch paper/full_reproduction.py rerun on 3.12 has confirmed the
science holds (see paper/REPRODUCTION.md's new section). Every other
pin is unchanged. Header comment explains the decision and how to get
a python=3.9 env instead, for anyone who specifically wants the
interpreter the archived published results were produced on.

docs/installation.md: updated the "conda env create" comment to match.

paper/REPRODUCTION.md: adds "Cross-Python-version check: the same
rerun on Python 3.12" -- a from-scratch ~28-minute full_reproduction.py
run compared against the existing Python 3.9 fresh-run numbers:
- Iterations 1-2's max entropy match the 3.9 run to 6+ significant
  figures (1.4576908 vs 1.4577; 1.2470073 vs 1.2470) -- striking, given
  no bitwise-reproducibility guarantee exists across interpreters.
- From iteration 3 on the two runs' design trajectories diverge, as
  expected: each iteration's posterior-predictive diagnostic draws
  from TensorFlow's ambient, unseeded RNG (mixture.py's documented
  non-reproducibility applies across interpreters, same as within one).
- R-hat/ESS at iteration 15: 1.00527/1.00736/1.00879 and
  1468.06/2424.94/653.15 (3.12) vs 1.00523/1.00730/1.00879 and
  1468.29/2428.09/653.15 (3.9) -- both comfortably converged.
- Hyperparameter posterior: same lengthscale_2 > lengthscale_1
  ordering; mean/median within ~0.1% of the 3.9 run.
- Test RMSE: 4.385 -> 0.856 (iter 1 -> 15, ~5.1x), matching the 3.9
  run's ~4.9x and the paper's own ~6.5x reduction.
- Both Wilson and surrogate McCabe-Thiele columns converged, tracking
  each other within 0.027 (liquid) / 0.014 (vapor) mole fraction at
  every stage -- inside the existing 0.03/0.014 tolerance band.

Conclusion: Python 3.12 gives statistically equivalent science to
Python 3.9. Run artifacts stayed in the gitignored results_remaked/,
per usual; not committed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@adowling2
adowling2 merged commit 3286690 into main Aug 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant