augax 0.3.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.
Files changed (41) hide show
  1. augax-0.3.0/.github/release.yml +19 -0
  2. augax-0.3.0/.github/workflows/build-wheels.yml +170 -0
  3. augax-0.3.0/.github/workflows/build_docs.yml +65 -0
  4. augax-0.3.0/.github/workflows/unittests.yml +75 -0
  5. augax-0.3.0/.gitignore +107 -0
  6. augax-0.3.0/CMakeLists.txt +28 -0
  7. augax-0.3.0/LICENSE.txt +13 -0
  8. augax-0.3.0/PKG-INFO +105 -0
  9. augax-0.3.0/README.md +92 -0
  10. augax-0.3.0/ci/311.yml +30 -0
  11. augax-0.3.0/ci/312.yml +29 -0
  12. augax-0.3.0/ci/313.yml +29 -0
  13. augax-0.3.0/ci/314.yml +30 -0
  14. augax-0.3.0/ci/check_wheel.py +44 -0
  15. augax-0.3.0/docs/.gitignore +1 -0
  16. augax-0.3.0/docs/Makefile +22 -0
  17. augax-0.3.0/docs/docs_env.yml +24 -0
  18. augax-0.3.0/docs/source/_static/references.bib +23 -0
  19. augax-0.3.0/docs/source/api.rst +46 -0
  20. augax-0.3.0/docs/source/conf.py +110 -0
  21. augax-0.3.0/docs/source/generated/augax._dispatch.rst +6 -0
  22. augax-0.3.0/docs/source/generated/augax._ks_dispatch.rst +6 -0
  23. augax-0.3.0/docs/source/generated/augax._ks_sample_batched.rst +6 -0
  24. augax-0.3.0/docs/source/generated/augax._require_x64.rst +6 -0
  25. augax-0.3.0/docs/source/generated/augax._sample_batched.rst +6 -0
  26. augax-0.3.0/docs/source/generated/augax._seed_from_key.rst +6 -0
  27. augax-0.3.0/docs/source/generated/augax.ks_sample.rst +6 -0
  28. augax-0.3.0/docs/source/generated/augax.ks_sample_numpy.rst +6 -0
  29. augax-0.3.0/docs/source/generated/augax.pg_sample.rst +6 -0
  30. augax-0.3.0/docs/source/index.md +25 -0
  31. augax-0.3.0/docs/source/installation.md +45 -0
  32. augax-0.3.0/docs/source/references.md +5 -0
  33. augax-0.3.0/docs/update_version_json.py +42 -0
  34. augax-0.3.0/docs/versions.json +3 -0
  35. augax-0.3.0/environment.yml +16 -0
  36. augax-0.3.0/pyproject.toml +39 -0
  37. augax-0.3.0/src/augax/__init__.py +259 -0
  38. augax-0.3.0/src/cpp/augax.cpp +1185 -0
  39. augax-0.3.0/tests/test_batching.py +42 -0
  40. augax-0.3.0/tests/test_ks.py +113 -0
  41. augax-0.3.0/tests/test_pg.py +151 -0
@@ -0,0 +1,19 @@
1
+ changelog:
2
+ exclude:
3
+ labels:
4
+ - ignore-for-release
5
+ authors:
6
+ - dependabot
7
+ categories:
8
+ - title: Bug Fixes
9
+ labels:
10
+ - bug
11
+ - title: Enhancements
12
+ labels:
13
+ - enhancement
14
+ - title: Maintenance
15
+ labels:
16
+ - maintenance
17
+ - title: Other Changes
18
+ labels:
19
+ - "*"
@@ -0,0 +1,170 @@
1
+ name: Build and publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*" # v1.2.3 → PyPI, v1.2.3a1/b1/rc1 → TestPyPI
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build_wheels:
11
+ name: Build wheels on ${{ matrix.os }}
12
+ permissions:
13
+ id-token: write # MANDATORY for trusted publishing to PyPI
14
+ contents: write # MANDATORY for the GitHub release action
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ include:
19
+ # Linux x86_64 — manylinux via cibuildwheel's default container
20
+ - os: ubuntu-latest
21
+ arch: x86_64
22
+ # Linux aarch64 — emulated via QEMU
23
+ - os: ubuntu-latest
24
+ arch: aarch64
25
+ # macOS Apple Silicon (arm64) -- pinned rather than macos-latest,
26
+ # since macos-latest now resolves to macOS 26 (Tahoe) and
27
+ # Homebrew's bottles for that host embed a minimum target of 26.0,
28
+ # which would make the wheel unusable on any older macOS. Bump
29
+ # this pin (and the target below) deliberately, not automatically.
30
+ - os: macos-15
31
+ arch: arm64
32
+ macosx_deployment_target: "15.0"
33
+ # Windows x86_64 -- pinned rather than windows-latest, since
34
+ # windows-latest now resolves to a windows-2025-vs2026 preview
35
+ # image whose bundled flang compiler fails to compile reference
36
+ # LAPACK's Fortran source (a compiler bug, not fixable here).
37
+ # Bump this pin deliberately once VS2026/flang has matured.
38
+ - os: windows-2022
39
+ arch: AMD64
40
+ runs-on: ${{ matrix.os }}
41
+ env:
42
+ CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-*"
43
+ # jaxlib (needed at build time for XLA FFI headers) has no musllinux
44
+ # wheels published at all -- only manylinux, macOS, and Windows. That's
45
+ # an upstream gap, not something fixable from this workflow, so skip
46
+ # musllinux targets entirely rather than let every build fail on it.
47
+ CIBW_SKIP: "*-musllinux*"
48
+ CIBW_ARCHS: ${{ matrix.arch }}
49
+ CIBW_BUILD_VERBOSITY: 1
50
+ MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
51
+
52
+ # ── Linux ──────────────────────────────────────────────────────────────
53
+ # The default manylinux2014 image is too old: jaxlib and scipy no
54
+ # longer publish wheels for it, which forced pip to backtrack to
55
+ # ancient jax versions and eventually build scipy from source (which
56
+ # then failed on a missing OpenBLAS). Use manylinux_2_28 instead, which
57
+ # is current enough for modern prebuilt wheels of everything involved.
58
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
59
+ CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
60
+ # augax is self-contained C++ (it links no SuiteSparse and no OpenMP), so
61
+ # no platform needs system libraries. jax comes from pyproject's
62
+ # [build-system] pin -- the build is isolated, so nothing is
63
+ # pip-installed here. cibuildwheel v4 repairs Windows wheels with
64
+ # delvewheel by default.
65
+
66
+ # ── Every platform: test the repaired wheel before it can publish ─────
67
+ # The reference-sampler tests need polyagamma, which has no macOS arm64 or
68
+ # cp314 wheels; they run in unittests.yml. These checks run everywhere.
69
+ CIBW_TEST_REQUIRES: pytest
70
+ CIBW_TEST_COMMAND: >
71
+ python {project}/ci/check_wheel.py &&
72
+ pytest -q {project}/tests/test_batching.py
73
+
74
+ steps:
75
+ - uses: actions/checkout@v6
76
+ with:
77
+ fetch-depth: 0 # setuptools-scm needs tags
78
+
79
+ # Enable QEMU for Linux aarch64 cross-compilation.
80
+ - name: Set up QEMU (Linux aarch64 only)
81
+ if: matrix.os == 'ubuntu-latest' && matrix.arch == 'aarch64'
82
+ uses: docker/setup-qemu-action@v3
83
+ with:
84
+ platforms: arm64
85
+
86
+ - name: Build wheels
87
+ # v2.x predates CPython 3.14, so the cp314-* selector above matched
88
+ # nothing and 3.14 wheels were silently never built.
89
+ uses: pypa/cibuildwheel@v4.2.1
90
+ with:
91
+ output-dir: wheelhouse
92
+
93
+ - name: Upload wheel artifacts
94
+ uses: actions/upload-artifact@v4
95
+ with:
96
+ name: wheels-${{ matrix.os }}-${{ matrix.arch }}
97
+ path: wheelhouse/*.whl
98
+
99
+ build_sdist:
100
+ name: Build source distribution
101
+ runs-on: ubuntu-latest
102
+ steps:
103
+ - uses: actions/checkout@v4
104
+ with:
105
+ fetch-depth: 0 # setuptools-scm needs tags
106
+ - name: Set up Python
107
+ uses: actions/setup-python@v5
108
+ with:
109
+ python-version: "3.12"
110
+ - name: Build sdist
111
+ run: |
112
+ pip install build
113
+ python -m build --sdist --outdir wheelhouse
114
+ - name: Upload sdist artifact
115
+ uses: actions/upload-artifact@v4
116
+ with:
117
+ name: sdist
118
+ path: wheelhouse/*.tar.gz
119
+
120
+ publish:
121
+ name: Publish to PyPI and create release
122
+ needs: [build_wheels, build_sdist]
123
+ runs-on: ubuntu-latest
124
+ permissions:
125
+ id-token: write
126
+ contents: write
127
+ # MANDATORY for trusted publishing: the environment name here MUST match the
128
+ # one configured in the PyPI/TestPyPI trusted-publisher UI. Without it, the
129
+ # OIDC token exchange fails with `invalid-publisher` / `environment: MISSING`.
130
+ environment: pypi
131
+ # Runs on any tag. test-* tags build+artifact only (no publish step fires).
132
+ if: startsWith(github.ref, 'refs/tags/')
133
+ steps:
134
+ - name: Download all artifacts
135
+ uses: actions/download-artifact@v4
136
+ with:
137
+ path: dist
138
+ merge-multiple: true
139
+
140
+ # v1.2.3a1 / v1.2.3b2 / v1.2.3rc1 → TestPyPI
141
+ - name: Publish pre-release to TestPyPI
142
+ if: |
143
+ startsWith(github.ref_name, 'v') &&
144
+ (contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc'))
145
+ uses: pypa/gh-action-pypi-publish@release/v1
146
+ with:
147
+ repository-url: https://test.pypi.org/legacy/
148
+ packages-dir: dist/
149
+
150
+ # v1.2.3 (clean) → real PyPI
151
+ - name: Publish release to PyPI
152
+ if: |
153
+ startsWith(github.ref_name, 'v') &&
154
+ !contains(github.ref_name, 'a') &&
155
+ !contains(github.ref_name, 'b') &&
156
+ !contains(github.ref_name, 'rc')
157
+ uses: pypa/gh-action-pypi-publish@release/v1
158
+ with:
159
+ packages-dir: dist/
160
+
161
+ # GitHub Release for any v* tag; marked pre-release when applicable
162
+ - name: Create GitHub Release
163
+ if: startsWith(github.ref_name, 'v')
164
+ uses: softprops/action-gh-release@v2
165
+ with:
166
+ prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
167
+ files: dist/*
168
+ generate_release_notes: true
169
+ env:
170
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,65 @@
1
+ name: Build Docs
2
+
3
+ on:
4
+ push:
5
+ # Sequence of patterns matched against refs/tags
6
+ tags:
7
+ - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
8
+ workflow_dispatch:
9
+ inputs:
10
+ version:
11
+ description: Manual Doc Build
12
+ default: test
13
+ required: false
14
+ permissions:
15
+ contents: write # required to push to gh-pages
16
+ jobs:
17
+ docs:
18
+ name: build & push docs
19
+ runs-on: ${{ matrix.os }}
20
+ timeout-minutes: 90
21
+ strategy:
22
+ matrix:
23
+ os: ['ubuntu-latest']
24
+ environment-file: [docs/docs_env.yml]
25
+ experimental: [false]
26
+ defaults:
27
+ run:
28
+ shell: bash -l {0}
29
+
30
+ steps:
31
+ - name: checkout repo
32
+ uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 0 # setuptools-scm needs tags
35
+
36
+ - name: setup micromamba
37
+ uses: mamba-org/setup-micromamba@v2
38
+ with:
39
+ environment-file: ${{ matrix.environment-file }}
40
+ micromamba-version: 'latest'
41
+
42
+ - name: make docs
43
+ run: |
44
+ pip install -e . --no-deps --force-reinstall
45
+ cd docs; make html
46
+
47
+ - name: commit docs
48
+ run: |
49
+ git clone https://github.com/knaaptime/augax.git --branch gh-pages --single-branch gh-pages
50
+ cp -r docs/build/html/* gh-pages/
51
+ cd gh-pages
52
+ git config --local user.email "action@github.com"
53
+ git config --local user.name "GitHub Action"
54
+ git add .
55
+ git commit -m "Update documentation" -a || true
56
+ # The above command will fail if no changes were present,
57
+ # so we ignore the return code.
58
+
59
+ - name: push to gh-pages
60
+ uses: ad-m/github-push-action@master
61
+ with:
62
+ branch: gh-pages
63
+ directory: gh-pages
64
+ github_token: ${{ secrets.GITHUB_TOKEN }}
65
+ force: true
@@ -0,0 +1,75 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches:
8
+ - "*"
9
+ schedule:
10
+ - cron: "0 0 * * 1,4"
11
+
12
+ jobs:
13
+ Test:
14
+ name: ${{ matrix.os }}, ${{ matrix.environment-file }}
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest]
20
+ environment-file:
21
+ - ci/311.yml
22
+ - ci/312.yml
23
+ - ci/313.yml
24
+ - ci/314.yml
25
+ include:
26
+ - environment-file: ci/313.yml
27
+ os: macos-latest
28
+ - environment-file: ci/313.yml
29
+ os: windows-latest
30
+ defaults:
31
+ run:
32
+ shell: bash -l {0}
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ with:
37
+ fetch-depth: 0 # setuptools-scm needs tags
38
+
39
+ - name: setup micromamba
40
+ uses: mamba-org/setup-micromamba@v1
41
+ with:
42
+ environment-file: ${{ matrix.environment-file }}
43
+ micromamba-version: 'latest'
44
+
45
+ - name: Install package
46
+ run: pip install -e . --no-build-isolation --no-deps
47
+
48
+ - name: Test package
49
+ run: |
50
+ pytest --cov src/augax \
51
+ -v \
52
+ -r a \
53
+ -n auto \
54
+ --color yes \
55
+ --cov src \
56
+ --cov-append \
57
+ --cov-report term-missing \
58
+ --cov-report xml \
59
+ tests
60
+ - name: codecov
61
+ uses: codecov/codecov-action@v5
62
+ with:
63
+ token: ${{ secrets.CODECOV_TOKEN }}
64
+ file: ./coverage.xml
65
+ name: cholax-codecov
66
+
67
+ - name: Generate and publish the report
68
+ if: |
69
+ failure()
70
+ && steps.status.outcome == 'failure'
71
+ && github.event_name == 'schedule'
72
+ && github.repository_owner == 'knaaptime'
73
+ uses: xarray-contrib/issue-from-pytest-log@v1
74
+ with:
75
+ log-path: pytest-log.jsonl
augax-0.3.0/.gitignore ADDED
@@ -0,0 +1,107 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ # C extensions
6
+ *.so
7
+ notebooks/cache
8
+ **.DS_Store
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ env/
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *,cover
48
+ .hypothesis/
49
+
50
+ # Translations
51
+ *.mo
52
+ *.pot
53
+
54
+ # Django stuff:
55
+ *.log
56
+ local_settings.py
57
+
58
+ # Flask stuff:
59
+ instance/
60
+ .webassets-cache
61
+
62
+ # Scrapy stuff:
63
+ .scrapy
64
+
65
+ # Sphinx documentation
66
+ docs/_build/
67
+
68
+ # PyBuilder
69
+ target/
70
+
71
+ # Jupyter Notebook
72
+ .ipynb_checkpoints
73
+
74
+ # pyenv
75
+ .python-version
76
+
77
+ # celery beat schedule file
78
+ celerybeat-schedule
79
+
80
+ # dotenv
81
+ .env
82
+
83
+ # virtualenv
84
+ .venv
85
+ venv/
86
+ ENV/
87
+
88
+ # Spyder project settings
89
+ .spyderproject
90
+
91
+ # Rope project settings
92
+ .ropeproject
93
+
94
+ # Pycharm
95
+ .idea
96
+
97
+ # VSCode
98
+ .vscode
99
+ # pixi environments
100
+ .pixi
101
+ *.egg-info
102
+
103
+ .claude/
104
+
105
+ **.ruff_cache/
106
+
107
+ **.pytest_cache
@@ -0,0 +1,28 @@
1
+ cmake_minimum_required(VERSION 3.18)
2
+ project(augax LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
8
+
9
+ # XLA FFI headers ship with jaxlib (header-only, nothing to link).
10
+ execute_process(
11
+ COMMAND "${Python_EXECUTABLE}" -c "import jax.ffi; print(jax.ffi.include_dir())"
12
+ OUTPUT_VARIABLE XLA_FFI_INCLUDE_DIR
13
+ OUTPUT_STRIP_TRAILING_WHITESPACE
14
+ COMMAND_ERROR_IS_FATAL ANY)
15
+
16
+ # Locate nanobind via its Python package.
17
+ execute_process(
18
+ COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
19
+ OUTPUT_VARIABLE NANOBIND_CMAKE_DIR
20
+ OUTPUT_STRIP_TRAILING_WHITESPACE
21
+ COMMAND_ERROR_IS_FATAL ANY)
22
+ list(APPEND CMAKE_PREFIX_PATH "${NANOBIND_CMAKE_DIR}")
23
+ find_package(nanobind REQUIRED CONFIG)
24
+
25
+ nanobind_add_module(augax_cpp NOMINSIZE src/cpp/augax.cpp)
26
+ target_include_directories(augax_cpp PRIVATE ${XLA_FFI_INCLUDE_DIR})
27
+
28
+ install(TARGETS augax_cpp LIBRARY DESTINATION .)
@@ -0,0 +1,13 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright 2026 elijah knaap
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
augax-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.2
2
+ Name: augax
3
+ Version: 0.3.0
4
+ Summary: JAX-native on-device augmentation draws (Pólya-Gamma) as XLA FFI custom calls
5
+ License: BSD-3-Clause
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: jax>=0.9.0
8
+ Requires-Dist: numpy
9
+ Provides-Extra: test
10
+ Requires-Dist: pytest; extra == "test"
11
+ Requires-Dist: scipy; extra == "test"
12
+ Description-Content-Type: text/markdown
13
+
14
+ # augax
15
+
16
+ On-device draws for **conditionally Gaussian augmentation** in JAX, each an XLA
17
+ FFI custom call: **Pólya-Gamma** sampling (`pg_sample`) and the **Kolmogorov
18
+ scale** of the logistic's normal scale mixture (`ks_sample`).
19
+
20
+ `augax.pg_sample(h, z, key)` draws `omega ~ PG(h, z)` from *inside* a JIT'd /
21
+ `lax.scan`-ed / `pmap`-ed program with **no host round-trip** — the native
22
+ replacement for `jax.pure_callback(random_polyagamma, ...)`, which pays a
23
+ device↔host round-trip on every Pólya-Gamma Gibbs sweep and serializes under
24
+ `jax.pmap`.
25
+
26
+ ```python
27
+ import jax, augax
28
+ jax.config.update("jax_enable_x64", True)
29
+
30
+ @jax.jit
31
+ def omega_update(h, z, key):
32
+ return augax.pg_sample(h, z, key)
33
+ ```
34
+
35
+ ## Method
36
+
37
+ `pg_sample` follows the hybrid dispatch of
38
+ [`zoj613/polyagamma`](https://github.com/zoj613/polyagamma) (`method=None`),
39
+ changed where that scheme is measurably biased:
40
+
41
+ - **`h > 50`**: a normal with the exact Pólya-Gamma mean and variance.
42
+ - **`8 <= h <= 50`**: the **saddlepoint** rejection sampler (Windle, Polson &
43
+ Scott 2014, arXiv:1405.0506), O(1) per accepted draw.
44
+ - **`h = 1`** (Bernoulli/logit), **or integer `h <= 4` with `|z| <= 1`**: the
45
+ exact **Devroye** sampler for `PG(1, z)` (Polson, Scott & Windle 2013), summed
46
+ `h` times.
47
+ - **Otherwise, `1 < h < 8`** — e.g. Negative-Binomial `h = y + alpha`: the
48
+ **alternate** sampler (Windle, Polson & Scott 2014, Section 4).
49
+ - **`h < 1`**: the Gamma-series representation — 20 terms plus a remainder
50
+ matched in mean and variance — exact in both moments.
51
+
52
+ Each departure from `polyagamma` 2.0.2 removes a bias measured with 2M draws
53
+ against the exact mean and variance:
54
+
55
+ - **Alternate sampler, `1 < h < 4`, small `|z|`**: `polyagamma` runs +0.4–0.9% in
56
+ the mean and +1–5% in the variance. Its right-hand envelope uses the constant
57
+ `(π/2)^(h/2)`, where both its Gamma proposal and the density's tail carry
58
+ `(π/2)^h`; augax uses the latter.
59
+ - **`h < 1`**: the alternate method assumes `h >= 1`; below it `polyagamma` runs
60
+ −0.4–0.8% in the mean. augax uses the Gamma series.
61
+ - **`4 < h < 8` with `|z| <= 4`**: `polyagamma` uses the saddlepoint
62
+ approximation, 1–2% low in the variance. augax uses the exact alternate
63
+ sampler.
64
+
65
+ The saddlepoint and alternate samplers are ported from `polyagamma`
66
+ (BSD-3-Clause). Every regime is tested against the closed-form mean and
67
+ variance.
68
+
69
+ The RNG is a per-call `xoshiro256**` seeded (via `splitmix64`) from the JAX PRNG
70
+ key, so draws are reproducible and each call owns its stream — safe when several
71
+ threads call at once. Normals and exponentials come from ziggurat samplers.
72
+
73
+ An array longer than 65,536 elements is split into blocks of that size, each
74
+ with its own stream, and the blocks are drawn in parallel on every hardware
75
+ thread (set `AUGAX_NUM_THREADS` to cap it). The blocks depend only on the array
76
+ length, so a draw is the same on any machine and any thread count. On a large
77
+ Negative-Binomial model (8.1M cells) this takes the draw from 3.6 s to 0.33 s on
78
+ 16 threads.
79
+
80
+ ## The Kolmogorov scale
81
+
82
+ A standard logistic disturbance is a normal scale mixture,
83
+ `eps | lam ~ N(0, lam^2)` with `lam = 2K` and `K` Kolmogorov distributed
84
+ (Stefanski 1991). `ks_sample(u, key)` draws `lam` given `eps = u`, which makes a
85
+ logistic latent variable conditionally Gaussian: the propagated disturbance of a
86
+ latent SAR logit, for one. The draw is exact: `1/lam^2` is proposed from an
87
+ inverse Gaussian, `IG(1/|u|, 1)` (a chi-square at `|u| < 1e-4`), and accepted
88
+ with probability `S(v) = f(v) e^{v/2} <= 1`, `f` the density of `(2K)^2`,
89
+ evaluated lazily by the squeeze of an alternating series — the Kolmogorov
90
+ series above `v = 4/3` and its Jacobi transform below, where the first's
91
+ partial sums are not bounds (Holmes & Held 2006). It is checked against the
92
+ identity `E[exp(-t^2 lam^2 / 2)] = pi t / sinh(pi t)` and against the
93
+ posterior moments by quadrature. `ks_sample_numpy(u, rng)` is the same C++
94
+ sampler for host callers.
95
+
96
+ ## Status
97
+
98
+ - ✅ All `h > 0`: the logit (`h = 1`), Negative-Binomial (`h = y + alpha`) and
99
+ large-shape regimes.
100
+ - Dispatch is automatic: the sampler is selected from `(h, z)` with no API
101
+ change.
102
+ - ✅ `ks_sample`: exact for every `u`, on the device and on the host.
103
+
104
+ CPU-only (the sampler is cheap scalar work; the point is to keep it on-device
105
+ next to the rest of a JIT'd Gibbs sweep).
augax-0.3.0/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # augax
2
+
3
+ On-device draws for **conditionally Gaussian augmentation** in JAX, each an XLA
4
+ FFI custom call: **Pólya-Gamma** sampling (`pg_sample`) and the **Kolmogorov
5
+ scale** of the logistic's normal scale mixture (`ks_sample`).
6
+
7
+ `augax.pg_sample(h, z, key)` draws `omega ~ PG(h, z)` from *inside* a JIT'd /
8
+ `lax.scan`-ed / `pmap`-ed program with **no host round-trip** — the native
9
+ replacement for `jax.pure_callback(random_polyagamma, ...)`, which pays a
10
+ device↔host round-trip on every Pólya-Gamma Gibbs sweep and serializes under
11
+ `jax.pmap`.
12
+
13
+ ```python
14
+ import jax, augax
15
+ jax.config.update("jax_enable_x64", True)
16
+
17
+ @jax.jit
18
+ def omega_update(h, z, key):
19
+ return augax.pg_sample(h, z, key)
20
+ ```
21
+
22
+ ## Method
23
+
24
+ `pg_sample` follows the hybrid dispatch of
25
+ [`zoj613/polyagamma`](https://github.com/zoj613/polyagamma) (`method=None`),
26
+ changed where that scheme is measurably biased:
27
+
28
+ - **`h > 50`**: a normal with the exact Pólya-Gamma mean and variance.
29
+ - **`8 <= h <= 50`**: the **saddlepoint** rejection sampler (Windle, Polson &
30
+ Scott 2014, arXiv:1405.0506), O(1) per accepted draw.
31
+ - **`h = 1`** (Bernoulli/logit), **or integer `h <= 4` with `|z| <= 1`**: the
32
+ exact **Devroye** sampler for `PG(1, z)` (Polson, Scott & Windle 2013), summed
33
+ `h` times.
34
+ - **Otherwise, `1 < h < 8`** — e.g. Negative-Binomial `h = y + alpha`: the
35
+ **alternate** sampler (Windle, Polson & Scott 2014, Section 4).
36
+ - **`h < 1`**: the Gamma-series representation — 20 terms plus a remainder
37
+ matched in mean and variance — exact in both moments.
38
+
39
+ Each departure from `polyagamma` 2.0.2 removes a bias measured with 2M draws
40
+ against the exact mean and variance:
41
+
42
+ - **Alternate sampler, `1 < h < 4`, small `|z|`**: `polyagamma` runs +0.4–0.9% in
43
+ the mean and +1–5% in the variance. Its right-hand envelope uses the constant
44
+ `(π/2)^(h/2)`, where both its Gamma proposal and the density's tail carry
45
+ `(π/2)^h`; augax uses the latter.
46
+ - **`h < 1`**: the alternate method assumes `h >= 1`; below it `polyagamma` runs
47
+ −0.4–0.8% in the mean. augax uses the Gamma series.
48
+ - **`4 < h < 8` with `|z| <= 4`**: `polyagamma` uses the saddlepoint
49
+ approximation, 1–2% low in the variance. augax uses the exact alternate
50
+ sampler.
51
+
52
+ The saddlepoint and alternate samplers are ported from `polyagamma`
53
+ (BSD-3-Clause). Every regime is tested against the closed-form mean and
54
+ variance.
55
+
56
+ The RNG is a per-call `xoshiro256**` seeded (via `splitmix64`) from the JAX PRNG
57
+ key, so draws are reproducible and each call owns its stream — safe when several
58
+ threads call at once. Normals and exponentials come from ziggurat samplers.
59
+
60
+ An array longer than 65,536 elements is split into blocks of that size, each
61
+ with its own stream, and the blocks are drawn in parallel on every hardware
62
+ thread (set `AUGAX_NUM_THREADS` to cap it). The blocks depend only on the array
63
+ length, so a draw is the same on any machine and any thread count. On a large
64
+ Negative-Binomial model (8.1M cells) this takes the draw from 3.6 s to 0.33 s on
65
+ 16 threads.
66
+
67
+ ## The Kolmogorov scale
68
+
69
+ A standard logistic disturbance is a normal scale mixture,
70
+ `eps | lam ~ N(0, lam^2)` with `lam = 2K` and `K` Kolmogorov distributed
71
+ (Stefanski 1991). `ks_sample(u, key)` draws `lam` given `eps = u`, which makes a
72
+ logistic latent variable conditionally Gaussian: the propagated disturbance of a
73
+ latent SAR logit, for one. The draw is exact: `1/lam^2` is proposed from an
74
+ inverse Gaussian, `IG(1/|u|, 1)` (a chi-square at `|u| < 1e-4`), and accepted
75
+ with probability `S(v) = f(v) e^{v/2} <= 1`, `f` the density of `(2K)^2`,
76
+ evaluated lazily by the squeeze of an alternating series — the Kolmogorov
77
+ series above `v = 4/3` and its Jacobi transform below, where the first's
78
+ partial sums are not bounds (Holmes & Held 2006). It is checked against the
79
+ identity `E[exp(-t^2 lam^2 / 2)] = pi t / sinh(pi t)` and against the
80
+ posterior moments by quadrature. `ks_sample_numpy(u, rng)` is the same C++
81
+ sampler for host callers.
82
+
83
+ ## Status
84
+
85
+ - ✅ All `h > 0`: the logit (`h = 1`), Negative-Binomial (`h = y + alpha`) and
86
+ large-shape regimes.
87
+ - Dispatch is automatic: the sampler is selected from `(h, z)` with no API
88
+ change.
89
+ - ✅ `ks_sample`: exact for every `u`, on the device and on the host.
90
+
91
+ CPU-only (the sampler is cheap scalar work; the point is to keep it on-device
92
+ next to the rest of a JIT'd Gibbs sweep).