Skip to content

feat: add Sobol quasi-Monte Carlo sampler - #244

Merged
gomezzz merged 4 commits into
developfrom
feat/qmc-sobol
Jul 24, 2026
Merged

feat: add Sobol quasi-Monte Carlo sampler#244
gomezzz merged 4 commits into
developfrom
feat/qmc-sobol

Conversation

@gomezzz

@gomezzz gomezzz commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

What

First-class quasi-Monte Carlo via a scrambled Sobol sampler that plugs into
MonteCarlo.integrate through the existing rng= slot — no changes to
MonteCarlo itself:

from torchquad import MonteCarlo, Sobol
mc = MonteCarlo()
mc.integrate(fn, dim=3, N=2**13, integration_domain=[[0,1]]*3,
             rng=Sobol(backend="torch", seed=0))

For smooth integrands the error shrinks close to O(1/N) instead of plain Monte
Carlo's O(1/sqrt(N)). In the tests, at N=2¹³ the Sobol error is ~3e-10 vs
~1e-4…1e-3 for pseudo-random MC across all four backends.

  • torch: torch.quasirandom.SobolEngine (native; points moved onto the
    current default device so GPU domains line up, matching RNG's torch.rand).
  • numpy / jax / tensorflow: scipy.stats.qmc.Sobol (SciPy is already a hard
    dependency) converted to the backend.
  • Sample points are constants, so autodiff through the integrand and the domain
    is preserved exactly as for plain MC.
  • The class mirrors RNG's per-backend shape (the established exception to the
    backend-agnostic rule for RNG-like samplers) and is exposed as torchquad.Sobol.

Notes / scope

  • Targets the eager integrate path; the JIT-compiled path builds its own RNG
    and is out of scope here.
  • Per-backend Sobol implementations scramble differently, so results are
    reproducible for a fixed seed within a backend but not bit-for-bit across
    backends (documented on the class).

Test plan

  • sobol_test.py, all backends: analytic accuracy, Sobol < plain-MC error at
    equal N (deterministic, fixed seed), seed reproducibility, torch gradient flow — 13 passed
  • monte_carlo_test.py still green (19 passed together)
  • ruff / pydoclint / vulture clean; sphinx-build -W builds (Sobol auto-documented via __all__)

Addresses #140 (MonteCarlo path; VEGAS + JIT support to follow). Motivated by #217.

Roadmap F1.

Adds a scrambled Sobol low-discrepancy sampler, shaped like RNG, that plugs into
MonteCarlo.integrate via the existing rng= slot. For smooth integrands the error
shrinks close to O(1/N) instead of Monte Carlo's O(1/sqrt(N)).

- torch backend uses torch.quasirandom.SobolEngine (native, moved onto the
  default device so GPU domains line up); numpy/jax/tensorflow use
  scipy.stats.qmc.Sobol converted to the backend.
- Sample points are constants, so autodiff through the integrand and domain is
  preserved exactly as with plain Monte Carlo.
- Exposed as torchquad.Sobol.

Tests (all backends): analytic accuracy, Sobol beating plain MC at equal N,
seed reproducibility, and a torch gradient-flow check.

Addresses #140 (MonteCarlo path; VEGAS and JIT support to follow). Motivated by
the QMC request in #217.
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

Overall Coverage

Coverage Report
FileStmtsMissCoverMissing
torchquad
   __init__.py26292%65–66
torchquad/integration
   base_integrator.py49786%28, 71–74, 78, 113, 117, 122
   boole.py30390%54, 73–76
   gaussian.py51394%84, 108, 128
   grid_integrator.py97397%50, 164, 268
   integration_grid.py49492%64–65, 132, 135
   monte_carlo.py93397%79, 101, 230
   qmc.py20195%67
   rng.py60788%71–74, 93–94, 102
   simpson.py29390%52, 71–74
   utils.py1311192%35–36, 102, 173, 177, 184, 196, 198, 204, 299, 306
   vegas.py162498%102–103, 193, 340
   vegas_map.py103892%247–257
torchquad/utils
   deployment_test.py1926367%53–55, 65–66, 69–70, 78–79, 91–93, 164, 175–176, 180–181, 183–184, 196–199, 211–213, 221, 232–234, 237, 247, 250, 253, 256–259, 267–275, 280–288, 297–306, 316–323
   enable_cuda.py15660%17–22
   set_log_level.py14286%45–46
   set_precision.py34974%35–38, 50–51, 70–72
TOTAL125113989% 

Tests Skipped Failures Errors Time
117 0 💤 0 ❌ 0 🔥 2m 33s ⏱️

- Tighten the accuracy-test tolerance to 1e-5 so it fails if Sobol degrades to
  plain-MC quality (measured error ~2e-8 SciPy / ~5e-7 torch).
- Warn uniformly on non-power-of-two point counts: torch's SobolEngine would
  otherwise silently drop the balance property that SciPy warns about.
- Document the rng.py/qmc.py per-backend exception in CLAUDE.md rule 3.
- Add the Sobol sampler to the CHANGELOG.
@gomezzz

gomezzz commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Claude: Thanks — addressed in 4bb604d:

  1. CLAUDE.md rule 3: added integration/rng.py and integration/qmc.py as the documented per-backend-sampler exception.

  2. Accuracy tolerance: tightened 1e-31e-5. Measured Sobol error is ~2e-8 (SciPy backends) / ~5e-7 (torch native), so 1e-5 clears both yet fails if Sobol degrades to plain-MC quality (~1e-4).

  3. CHANGELOG: added a Sobol bullet under [Unreleased] → Added.

  4. Non-power-of-two: the torch path now emits a warning (verified) matching SciPy's, so behavior is uniform across backends.

  5. Tutorial QMC snippet: deferring to the F4 docs bundle (roadmap), which adds the 'passing parameters' / usage narrative — I'll include a rng=Sobol(...) example there so the QMC docs land coherently in one place. The API reference already picks up Sobol via __all__.

@gomezzz

gomezzz commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Claude: ### Accuracy comparison: Monte Carlo vs Sobol QMC vs VEGAS

Relative error against the analytic value at an equal sample budget of N = 2¹⁴ = 16384, numpy backend, float64, fixed seed=0. Every integrand is separable so the exact integral is known in closed form.

Integrand dim Monte Carlo Sobol QMC VEGAS
smooth: ∏ cos(πx/2) 2 3.84e-03 4.53e-09 4.33e-04
smooth: ∏ e^(-x) 5 4.84e-04 8.55e-06 8.15e-05
sharply peaked: ∏ e^(-((x-0.5)/0.1)²) 3 2.48e-02 4.17e-03 2.46e-03
smooth: ∏ e^(-x) 8 1.34e-03 1.11e-04 3.46e-04

Takeaways

  • On smooth integrands Sobol QMC is the clear winner — up to ~6 orders of magnitude better than plain MC (and consistently ahead of VEGAS), matching the expected O(1/N) vs O(1/√N) convergence.
  • On the sharply peaked integrand, VEGAS's adaptive stratification pays off and it edges out Sobol; Sobol still beats plain MC ~6×, but its low-discrepancy advantage shrinks when the mass is concentrated in a small region.
  • Plain Monte Carlo is the weakest in every case.

Caveat: VEGAS's N is an approximate max function-evaluation budget spread across warmup + refinement iterations, so its effective sample count isn't identical to MC/Sobol — this is a same-budget comparison, not an identical-sample-count one.

@gomezzz

gomezzz commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Claude: Added a whole-collection accuracy test (aa5a5a6) — thanks for the nudge, the previous single cos-integrand check was too thin.

sobol_test.py now runs every function in integration_test_functions (real + complex, including the multi-dimensional integrands) in 1-D, 3-D and 10-D via compute_integration_test_errors, and asserts the error against the closed-form value on all four backends. Measured max errors (fp64): 1-D 1.5e-6, 3-D 4.3e-6, 10-D 3.3e-9 on the SciPy backends. Bounds are per-dimension (1e-4 / 1e-4 / 1e-2) — orders of magnitude tighter than the plain-MC bounds in monte_carlo_test.py. torch's native SobolEngine is weaker in 10-D (~9.8e-4), which the looser 10-D bound accommodates.

Also updated REVIEW.md to require validating new integrators/samplers/error-estimates against the whole analytic collection rather than ad-hoc integrands. (Note: the repo's CLAUDE.md is gitignored, so that guidance lives in the tracked REVIEW.md.)

- Replace the single-integrand accuracy check with a collection test that runs
  every function in integration_test_functions (real + complex, incl. multi-dim)
  in 1-D/3-D/10-D via compute_integration_test_errors, with per-dimension bounds
  far tighter than plain Monte Carlo's (torch's native 10-D Sobol is weaker, so
  that bound is looser).
- REVIEW.md: require validating integrators/samplers/error-estimates against the
  whole analytic collection, not one or two ad-hoc integrands.
@gomezzz

gomezzz commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Claude: Merged develop in to resolve conflicts after #241/#242/#243/#246 landed (CHANGELOG + __init__.py; both kept). CI is green again (Running tests + dead_code). The whole-collection Sobol accuracy test is included.

@gomezzz
gomezzz merged commit 6e5bc35 into develop Jul 24, 2026
9 checks passed
@gomezzz
gomezzz deleted the feat/qmc-sobol branch July 24, 2026 20:18
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