Reproducible perturbation-kernel estimators with SIMD and GPU backends
Project description
perturbation-kernel
Perturbative stability estimation with a reproducibility guarantee at the bit level.
Given a base state s, a parametrised Markov kernel P((s, θ), ·), an
intensity law ρ over θ, a measurable forward map F: S → O, and a
functional Φ: M₁(O) → R, the engine evaluates the plug-in estimator
Φ̂_N(s) = Φ( (1/N) Σᵢ δ_{F(Sᵢ)} ), Sᵢ ~ P((s, θᵢ), ·), θᵢ ~ ρ
and returns it with a non-asymptotic error bound. The estimate is a pure
function of (family, N, seed): no ambient entropy, no thread-count
dependence, no accumulation order that varies with hardware.
Reference implementation of SCHEMA.md v1.0.0.
Install
pip install perturbation-kernel
abi3 wheels for CPython 3.8+ on Linux, macOS and Windows, both architectures. No Rust toolchain required.
Use
import perturbation_kernel as pk
cfg = pk.Config(n=262_144, seed=20260610, invariance_lambda=1.0)
r = pk.Markov(k=5, theta_max=0.3).run(cfg)
r.value # 0.8802871704101562
r.functional # 'tail_survival'
r.execution # {'backend': 'auto', 'simd_path': 'neon', 'precision': 'f64', ...}
Built-in families
| Family | Kernel P((s, θ), ·) |
Functional Φ |
Codomain |
|---|---|---|---|
Gaussian(base, sigma_max) |
s + θ·N(0, I), θ ~ U[0, σ_max] |
negative summed coordinate variance | (-∞, 0] |
Bistable(x0, dt, theta_max) |
one Euler-Maruyama step in V(x) = (x²-1)² |
polarisation E[sgn X] |
[-1, 1] |
Markov(k, theta_max, start, base_label) |
mix to U{0..k-1} w.p. θ |
survival P(X = base_label) |
[0, 1] |
All three satisfy the C2 identity contract: θ₀ = 0 and
P((s, θ₀), ·) = δ_s, so the null perturbation is exactly recoverable
rather than approximately so.
pk.Markov(k=5, theta_max=0.0, start=2, base_label=2).run(cfg).value # exactly 1.0
pk.Gaussian(base=[1.5, -2.0], sigma_max=0.0).run(cfg).value # exactly 0.0
Determinism
Draw i consumes the substream ChaCha20(mix64(seed, i)), so the
ensemble is index-addressable and order-free. Reduction is a fixed-shape
pairwise tree keyed to the index order, not to the thread count, which
is what keeps a non-associative float sum reproducible.
a = pk.Markov(k=5, theta_max=0.3).run(pk.Config(n=50_000, seed=7))
b = pk.Markov(k=5, theta_max=0.3).run(pk.Config(n=50_000, seed=7, backend="scalar"))
a.value.hex() == b.value.hex() # True — identical mantissa, not "close"
CI verifies this against golden values captured from v1.0.0, across four native OS/architecture combinations and every feature configuration.
Backends
pk.available_backends() # ['auto', 'scalar', 'simd', 'gpu', 'gpu_f32']
pk.simd_path() # 'neon' | 'avx2' | 'scalar'
pk.gpu_device() # 'Apple M4 Max (Metal, IntegratedGpu)' or None
| Backend | Execution | Agreement |
|---|---|---|
scalar |
single-threaded, portable | reference |
simd |
NEON vpaddq_f64 / AVX2 vhaddpd+vpermpd |
bit-identical |
auto |
vectorised, rayon above 4096 draws |
bit-identical |
gpu |
wgpu compute, emulated binary64 |
bit-identical |
gpu_f32 |
wgpu compute, single precision |
statistically equivalent |
Vectorisation is exact because each tree-level output is one IEEE-754 addition of one fixed operand pair; four lanes perform the same four additions. No lane-crossing accumulator, no reassociation, and the centring step is an explicit subtract-then-multiply so it cannot contract into an FMA (which rounds once where the reference rounds twice).
gpu is exact for Markov because that family's arithmetic reduces to
a short, fully-specified list: f64.wgsl emulates binary64
multiplication in u32 pairs with round-to-nearest-even, rand's Lemire
rejection sampler is transcribed rather than approximated, and the
indicator observation makes the reduction integer addition — associative,
hence scheduling-invariant. Families drawing normal deviates require
ln/exp, whose accuracy WGSL leaves to the driver, so gpu rejects
them rather than silently returning a different number:
pk.Gaussian(base=[0.5], sigma_max=0.3).run(pk.Config(n=1024, backend="gpu"))
# RuntimeError: ... use backend "gpu_f32" to accept a single-precision result
Error bounds
Declare Λ (Wasserstein-1 Lipschitz constant of Φ), the observation
diameter D, and d_obs, and the report carries the Theorem 7.3 bound:
a McDiarmid concentration term √(Λ²D²/2N · ln(2/η)) plus a
Fournier-Guillin bias term for the empirical-measure convergence rate.
An accuracy claim below the Theorem 7.3(c) sample floor is rejected at run time rather than reported optimistically:
pk.sample_floor(invariance_lambda=1.0, observation_diameter=1.0,
epsilon=0.05, eta=0.05, obs_dim=1) # 262144
pk.Markov(k=5, theta_max=0.3).run(pk.Config(
n=1000, seed=1, invariance_lambda=1.0,
epsilon=0.05, eta=0.05, observation_diameter=1.0, obs_dim=1))
# ValueError: sample-complexity floor: requested (0.05,0.05) needs N >= 262144
The floor is dominated by the bias term, not the variance term: the
Wasserstein rate converges more slowly than the concentration does, so
tightening ε by 10× costs 256× the compute.
Other languages
The same engine, the same bits, through the C ABI (and wasm for
TypeScript): Rust, C/C++, Zig, Julia, TypeScript.
CI runs a cross-language agreement job that compares raw f64 bit
patterns across all of them rather than each against a constant.
Documentation
https://godofecht.github.io/perturbation-kernel/
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file perturbation_kernel-2.1.0.tar.gz.
File metadata
- Download URL: perturbation_kernel-2.1.0.tar.gz
- Upload date:
- Size: 135.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b31d935a75ba936102011784bbbdcabcc3d3bec00a5fd7173abcd62634b6c3d
|
|
| MD5 |
6c561aba493ec6f6e90a0466aa8416a3
|
|
| BLAKE2b-256 |
b68fe7098a1dd4afba418307a2a6f8aed33044ec33fad4529017510abf06a7bb
|
File details
Details for the file perturbation_kernel-2.1.0-cp38-abi3-win_amd64.whl.
File metadata
- Download URL: perturbation_kernel-2.1.0-cp38-abi3-win_amd64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.8+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fd8522d3d0f54df3f642904909018d4b02d7f76d299f0b61b0da78a5b919052
|
|
| MD5 |
49ca5b4b4181801ef69d68082f5d30ef
|
|
| BLAKE2b-256 |
fc6085714aa74a5c1c6610880a03579a844eff8918b632cce8fc3709099c6a20
|
File details
Details for the file perturbation_kernel-2.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: perturbation_kernel-2.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.5 MB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
270ea6da27cb38bb463a9293b6b72b6f7a430a700239fec16b384d913a544476
|
|
| MD5 |
c5cfb967e3be9ee97993d481fadfc28e
|
|
| BLAKE2b-256 |
1b218c013c8f391589f9ff636e2c96855a844213e6cf1f9decd98b92f7fe7537
|
File details
Details for the file perturbation_kernel-2.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: perturbation_kernel-2.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c0d58922e682d7864be7bd245fa7c2e9a161991fbe1461dc3b49aa5f082d2c3
|
|
| MD5 |
86e7e62e37e3d8adf2c5c241b023e034
|
|
| BLAKE2b-256 |
fb5843427b42339b39e94ce527fec1bebc062f2b8677041a2153d8cb6ae4b0de
|
File details
Details for the file perturbation_kernel-2.1.0-cp38-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: perturbation_kernel-2.1.0-cp38-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.9 MB
- Tags: CPython 3.8+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31b9fe3e1f6e2c8604dbff4575bb892199aeff40395f8158ca0175d783b48a4d
|
|
| MD5 |
4fcbb5ea514a6afe4ce5f4e1c5d0a04b
|
|
| BLAKE2b-256 |
393b3699d2e7da1ccb0c3c1718bb38482e119a1f4c5fdeeee8f6eb6e4f17a9b6
|
File details
Details for the file perturbation_kernel-2.1.0-cp38-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: perturbation_kernel-2.1.0-cp38-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.0 MB
- Tags: CPython 3.8+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53b7fbee4391d6e90a41d0a355a40fb17678cd4d43074e929ccd6693867aae29
|
|
| MD5 |
42e7aecf127e57b84d505adb0a0655ea
|
|
| BLAKE2b-256 |
ac090bdf9e8dcb31ededb420aecec645ad138c19ef7afde5dc45afe664bd7f86
|