Skip to content

v2.2.0 — Bessel accuracy fixes & installed-package correctness

Latest

Choose a tag to compare

@OldCrow OldCrow released this 16 Aug 17:57
· 1 commit to main since this release

If you consume libstats as an installed package, this release fixes a silent accuracy defect that affected you. See #97 below.

Why 2.2.0 and not 2.1.1

The fixes here are patch-shaped, but three things landed alongside them that break drop-in:

  • CMake minimum rises 3.20 → 3.25
  • Install paths move to GNUInstallDirs (so lib64 on distros that use it)
  • A new installed header, libstats/libstats_config.h, joins the public set

The library API itself is unchanged. Upgrading from v2.1.0 is a recompile, not a code change.

Fixed

#97 — installed packages silently compiled the Tier 2 Bessel fallback. LIBSTATS_HAS_CXX17_BESSEL selects between two bodies of the same inline functions in libstats/core/bessel.h, but was applied as a target_compile_definitions on an interface target the library pulls in as $<LINK_ONLY:...>. LINK_ONLY propagates the link and strips usage requirements, so the macro reached libstats' own TUs and the in-tree tests, and never a find_package consumer. Every consumer on every platform got the low-accuracy tier: measured against a clean install tree, bessel_i0(10) went from 1.3×10⁻⁸ relative to 1.5×10⁻¹⁶. Because the two bodies differed inside inline functions linked into one program, it was also an ODR violation. The probe result now travels in a generated, installed libstats_config.h, which also reaches pkg-config and plain-include-path consumers.

#97std::terminate on negative Bessel arguments under libstdc++. Tier 1 called std::cyl_bessel_i raw, which libstdc++ answers with a thrown std::domain_error for x < 0 while MSVC does not — so the noexcept wrappers aborted on GCC only. Latent until the fix above, since no consumer was reaching Tier 1.

#96 — top three coefficients of the I1/I0 complement series were wrong, c10 by 0.199. Re-derived by exact rational series division of the two Hankel expansions. The cause is recorded in the source because the original validation looked convincing: a Vandermonde solve is ill-conditioned and degrades at high order while staying exact at low order, so checking that the low coefficients came out dyadic confirmed precisely the half a bad solve gets right.

#92log I0 was discontinuous at x = 700. The asymptotic above the cut carried only two terms, truncating at O(x⁻³) — exactly the size of the 2.14×10⁻¹⁰ step. Extended to five terms; the seam goes from 0.4→1881 ULP down to 0.4→0.8.

#93 — circular variance lost ~9 bits at large κ. 1 - I1/I0 cancels however accurately the ratio is computed. Adds bessel_i1_i0_complement and bessel_i1_over_i0, each computed directly in its own regime.

#90 — reconfiguring a build directory dropped Threads::Threads from the installed export. Cache variables persist across configure passes; imported targets do not. The same commit and prefix could produce a different package depending on how many times its build directory had been configured.

#94 — the Ninja generator could not configure the repo, and run_tests had been running the entire suite rather than the 41 tests its own comment described.

Added

  • libstats/libstats_config.h — generated and installed, carrying the build's Bessel tier
  • tests/test_bessel_tier.cpp — a two-sided tier canary plus a negative-argument symmetry test

Validation

Native run on AMD Zen 4 with AVX-512 confirmed active, MSVC 14.51 Release, clean build tree: 49/49 correctness. One timing test (UniformEnhancedTest.SIMDAndParallelBatchImplementations) is flaky on that machine — 2 failures in 3 runs against an adaptive speedup threshold — and is pre-existing; nothing in this release touches uniform or the dispatch thresholds. The NEON and AVX2 legs are CI-validated but not natively re-run for this release, which carries no SIMD kernel change.

Full detail in CHANGELOG.md.