Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
83da896
fix: Stabilize beta tails and inverse CDF
day01 Aug 12, 2026
26aba0b
test: Add beta MP500 regressions
day01 Aug 12, 2026
400838d
chore: Split beta implementation into modules
day01 Aug 12, 2026
7d5aac3
fix: Improve tiny beta tails
day01 Aug 12, 2026
975d999
chore: Restore inverse beta attribution
day01 Aug 12, 2026
cf122e5
chore: Benchmark beta regressions
day01 Aug 12, 2026
d314496
fix: Extend accurate tiny beta path
day01 Aug 12, 2026
1ad4464
test: Tighten beta reference checks
day01 Aug 12, 2026
43d2cfc
fix: Speed up small-shape beta inverses
day01 Aug 12, 2026
5e08223
chore: Attribute beta algorithm sources
day01 Aug 12, 2026
89a6777
fix: Improve symmetric beta center
day01 Aug 12, 2026
971178f
chore: Correct beta source attribution
day01 Aug 12, 2026
a3e9f35
fix: Speed up shape-two beta inverses
day01 Aug 12, 2026
778b47b
fix: Improve beta forward prefactor
day01 Aug 12, 2026
5dd84d9
chore: Document scaled gamma sources
day01 Aug 12, 2026
d678c1f
fix: Accelerate shape-two beta inverses
day01 Aug 12, 2026
52fb021
test: Reproduce shape-two adjacent rounding
day01 Aug 12, 2026
a7f78dd
fix: Stabilize shape-two inverse rounding
day01 Aug 12, 2026
df3f3ef
test: Add asymmetric beta MP500 regressions
day01 Aug 12, 2026
d31b6b3
fix: Improve asymmetric beta accuracy
day01 Aug 12, 2026
98a3d63
test: Cover concentrated beta ratio bounds
day01 Aug 12, 2026
4de5078
fix: Certify concentrated beta quantiles
day01 Aug 12, 2026
2bc58b0
test: Reproduce normal-tail square rounding
day01 Aug 12, 2026
eec8f90
fix: Preserve normal-tail square residual
day01 Aug 12, 2026
3bf9bc8
fix: Accelerate concentrated beta endpoint
day01 Aug 12, 2026
d58a419
perf: Cache shape-two adjacent errors
day01 Aug 12, 2026
32e34b9
test: Cover real shape-two inverses
day01 Aug 12, 2026
d9d3547
perf: Optimize real shape-two inverses
day01 Aug 12, 2026
f5c9e44
test: Reproduce subnormal shape-two quantiles
day01 Aug 12, 2026
7d81dab
fix: Normalize subnormal shape-two values
day01 Aug 12, 2026
6fbdf86
test: Cover beta asymptotic gates
day01 Aug 12, 2026
c98d89c
fix: Extend beta asymptotic accuracy range
day01 Aug 12, 2026
a377680
test: Reproduce inverse beta endpoint failures
day01 Aug 12, 2026
10873fb
fix: Remove uncertified beta endpoint shortcut
day01 Aug 12, 2026
72c9647
test: Reproduce zero logarithm handling
day01 Aug 12, 2026
4676e8a
fix: Handle zero in accurate logarithm
day01 Aug 12, 2026
3ce0a0d
fix: Round shape-two endpoint quantiles
day01 Aug 12, 2026
5f3a261
perf: Gate beta endpoint certification
day01 Aug 12, 2026
e2075f4
test: Cover subnormal beta inverse cells
day01 Aug 12, 2026
0d71f5b
test: Reproduce beta endpoint gate error
day01 Aug 12, 2026
3b2391e
fix: Certify subnormal beta inverse cells
day01 Aug 12, 2026
d9b8953
test: Require exact beta inverse regressions
day01 Aug 12, 2026
5904ad1
docs: Describe beta convergence failures
day01 Aug 12, 2026
3f6393c
test: Reproduce unit-shape subnormal rounding
day01 Aug 12, 2026
c73ecac
fix: Round unit-shape beta subnormals
day01 Aug 12, 2026
50226d6
perf: Bracket subnormal beta quantiles locally
day01 Aug 12, 2026
a2ddd9f
test: Cover integer-shape subnormal rounding
day01 Aug 12, 2026
175d3a5
fix: Round integer-shape beta subnormals
day01 Aug 12, 2026
2adb605
chore: Benchmark beta endpoint quantiles
day01 Aug 12, 2026
4c117d8
perf: Stop converged logarithm series
day01 Aug 12, 2026
705b4d1
perf: Defer beta inverse logarithm
day01 Aug 12, 2026
c0e2b5e
perf: Avoid redundant beta tail logarithms
day01 Aug 12, 2026
1632393
test: Cover shape-two zero endpoint rounding
day01 Aug 12, 2026
2bee0ca
fix: Round beta-two zero endpoint quantiles
day01 Aug 12, 2026
dfd76ae
perf: Reduce small-shape inverse series
day01 Aug 13, 2026
535daee
test: Cover beta solver zero denominators
day01 Aug 13, 2026
ec7a791
fix: Guard beta solver denominators
day01 Aug 13, 2026
b73c255
test: Allow one ULP across coverage builds
day01 Aug 13, 2026
491f52e
test: Accept one ULP across platforms
day01 Aug 13, 2026
9197681
test: Cover beta log boundary contracts
day01 Aug 13, 2026
224c17e
test: Verify tiny beta log tail
day01 Aug 13, 2026
240c15a
test: Cover beta log algorithm branches
day01 Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ name = "statrs"
version = "0.19.1"
authors = ["Michael Ma"]
description = "Statistical computing library for Rust"
license = "MIT"
license = "MIT AND BSL-1.0"
keywords = ["probability", "statistics", "stats", "distribution", "math"]
categories = ["science"]
homepage = "https://github.com/statrs-dev/statrs"
repository = "https://github.com/statrs-dev/statrs"
edition = "2024"

include = ["CHANGELOG.md", "LICENSE.md", "src/", "tests/"]
include = [
"CHANGELOG.md",
"LICENSE.md",
"LICENSE-BOOST.md",
"THIRD_PARTY_NOTICES.md",
"src/",
"tests/",
"benches/",
]

# When changing MSRV: Also update the README
rust-version = "1.89.0"
Expand All @@ -29,6 +37,11 @@ name = "density"
harness = false
required-features = ["rand", "std", "kde"]

[[bench]]
name = "beta"
harness = false
required-features = ["std"]

[features]
default = ["std", "nalgebra", "rand"]
std = ["approx/std", "num-traits/std", "nalgebra?/std", "rand?/std"]
Expand Down
23 changes: 23 additions & 0 deletions LICENSE-BOOST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# statrs

![tests][actions-test-badge]
[![MIT licensed][license-badge]](./LICENSE.md)
[![MIT and BSL-1.0 licensed][license-badge]](./THIRD_PARTY_NOTICES.md)
[![Crate][crates-badge]][crates-url]
[![docs.rs][docsrs-badge]][docs-url]
[![codecov-statrs][codecov-badge]][codecov-url]
Expand All @@ -10,7 +10,7 @@
[actions-test-badge]: https://github.com/statrs-dev/statrs/actions/workflows/test.yml/badge.svg
[crates-badge]: https://img.shields.io/crates/v/statrs.svg
[crates-url]: https://crates.io/crates/statrs
[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license-badge]: https://img.shields.io/badge/license-MIT%20AND%20BSL--1.0-blue.svg
[docsrs-badge]: https://img.shields.io/docsrs/statrs
[docs-url]: https://docs.rs/statrs/*/statrs
[codecov-badge]: https://codecov.io/gh/statrs-dev/statrs/graph/badge.svg?token=XtMSMYXvIf
Expand Down
24 changes: 24 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Third-party notices

## Boost.Math

Portions of `src/function/beta/{bgrat,forward,fraction,log_forward,recurrence,series}.rs` are adapted from Boost.Math 1.90.0, `include/boost/math/special_functions/beta.hpp`.

Portions of `src/function/beta/normal_tail.rs` are adapted from Boost.Math 1.90.0, `include/boost/math/special_functions/erf.hpp`.

Copyright John Maddock 2006.
Copyright Matt Borland 2024.

The Boost-derived portions are licensed under the Boost Software License 1.0; see `LICENSE-BOOST.md`. Statrs modifications are licensed under MIT, so these files are subject to both licenses.

Source: https://github.com/boostorg/math/blob/e0fcd19f7227d81391770ea46015acc3c80af810/include/boost/math/special_functions/beta.hpp

Source: https://github.com/boostorg/math/blob/e0fcd19f7227d81391770ea46015acc3c80af810/include/boost/math/special_functions/erf.hpp

## special 0.8.1

The initial inverse-beta estimate in `src/function/beta/inverse/initial.rs` is adapted from `special` 0.8.1 under its MIT license option.

Copyright 2014–2019 The special Developers.

Source: https://github.com/stainless-steel/special/blob/c64902430bd50e8c8225c7c8b410334ffedf2f15/src/beta.rs
70 changes: 70 additions & 0 deletions benches/beta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use statrs::function::beta::{beta_reg, inv_beta_reg};
use std::hint::black_box;

fn bench_beta_reg(c: &mut Criterion) {
let mut group = c.benchmark_group("beta_reg");
for (name, a, b, x) in [
("typical", 2.0, 5.0, 0.3),
(
"large_symmetric_adjacent",
1e8,
1e8,
f64::from_bits(0.5_f64.to_bits() + 1),
),
(
"moderate_fraction",
25.32628846940565,
3.1028101710805442,
0.9276950604606229,
),
] {
group.bench_with_input(
BenchmarkId::new("cdf", name),
&(a, b, x),
|bencher, input| {
bencher
.iter(|| beta_reg(black_box(input.0), black_box(input.1), black_box(input.2)));
},
);
}
group.finish();
}

fn bench_inv_beta_reg(c: &mut Criterion) {
let mut group = c.benchmark_group("inv_beta_reg");
for (name, a, b, probability) in [
("typical", 2.0, 5.0, 0.3),
("nontermination_regression", 200.0, 2.0, 1e-60),
("panic_regression", 200.0, 2.0, 1e-165),
("tiny_quantile", 0.1, 500.0, 1e-30),
(
"subnormal_shape_two",
0.5,
2.0,
f64::from_bits(0x1e72_f942_2c23_c47c),
),
("subnormal_unit_shape", 1.0, 10.0, f64::from_bits(5)),
("shape_two_large_median", 2.0, 1e308, 0.5),
(
"shape_two_zero_cell",
2.0,
1e200,
f64::from_bits(0x0c8b_4ec7_f919_73ff),
),
] {
group.bench_with_input(
BenchmarkId::new("quantile", name),
&(a, b, probability),
|bencher, input| {
bencher.iter(|| {
inv_beta_reg(black_box(input.0), black_box(input.1), black_box(input.2))
});
},
);
}
group.finish();
}

criterion_group!(benches, bench_beta_reg, bench_inv_beta_reg);
criterion_main!(benches);
58 changes: 51 additions & 7 deletions src/distribution/beta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ impl ContinuousCDF<f64, f64> for Beta {
/// Calculates the cumulative distribution function for the beta
/// distribution at `x`.
///
/// # Panics
///
/// If the numerical method does not converge.
///
/// # Formula
///
/// ```text
Expand All @@ -151,6 +155,10 @@ impl ContinuousCDF<f64, f64> for Beta {

/// Calculates the survival function for the beta distribution at `x`.
///
/// # Panics
///
/// If the numerical method does not converge.
///
/// # Formula
///
/// ```text
Expand All @@ -167,12 +175,9 @@ impl ContinuousCDF<f64, f64> for Beta {
} else if self.shape_a == 1.0 && self.shape_b == 1.0 {
1. - x
} else if x < (self.shape_a + 1.0) / (self.shape_a + self.shape_b + 2.0) {
// Below the continued fraction split point of `beta_reg`,
// `beta_reg(b, a, 1 - x)` reduces to `1 - beta_reg(a, b, x)`;
// computing the complement here instead avoids `1.0 - x`
// rounding to 1.0 for tiny x (< ~1.1e-16), which would lose
// the lower tail entirely. See #432
1.0 - beta::beta_reg(self.shape_a, self.shape_b, x)
beta::checked_ln_beta_reg_complement(self.shape_a, self.shape_b, x)
.unwrap()
.exp()
} else {
beta::beta_reg(self.shape_b, self.shape_a, 1.0 - x)
}
Expand All @@ -183,7 +188,7 @@ impl ContinuousCDF<f64, f64> for Beta {
///
/// # Panics
///
/// If x is not in `[0, 1]`.
/// If x is not in `[0, 1]` or the numerical method does not converge.
///
/// # Formula
///
Expand All @@ -208,6 +213,10 @@ impl ContinuousCDF<f64, f64> for Beta {
///
/// If x is not in `[0, 1]`.
///
/// # Panics
///
/// If the numerical method does not converge.
///
/// # Formula
///
/// ```text
Expand Down Expand Up @@ -651,6 +660,18 @@ mod tests {
}
}

#[test]
fn test_cdf_large_symmetric_shapes() {
for shape in [1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8] {
let dist = Beta::new(shape, shape).unwrap();
let cdf = dist.cdf(0.5);
let sf = dist.sf(0.5);
assert_eq!(cdf, 0.5);
assert_eq!(sf, 0.5);
assert_eq!(cdf + sf, 1.0);
}
}

#[test]
fn test_sf() {
let sf = |arg: f64| move |x: Beta| x.sf(arg);
Expand Down Expand Up @@ -684,6 +705,17 @@ mod tests {
}
}

#[test]
fn test_sf_tiny_shape_preserves_representable_tail() {
let distribution = Beta::new(
f64::from_bits(0x00000000000007e8),
f64::from_bits(0x4040000000000000),
)
.unwrap();
let x = f64::from_bits(0x01556e1fc2f8f359);
assert_eq!(distribution.sf(x).to_bits(), 0x0000000000155101);
}

#[test]
fn test_inverse_cdf() {
// let inverse_cdf = |arg: f64| move |x: Beta| x.inverse_cdf(arg);
Expand All @@ -705,6 +737,18 @@ mod tests {
}
}

#[test]
fn test_inverse_cdf_extreme_lower_tail() {
let dist = Beta::new(200.0, 2.0).unwrap();
let actual = dist.inverse_cdf(1e-170);
let expected = 0.13765877485659653;
let relative_error = ((actual - expected) / expected).abs();
assert!(
relative_error <= 5e-13,
"actual {actual}, expected {expected}"
);
}

#[test]
fn test_cdf_input_lt_0() {
let cdf = |arg: f64| move |x: Beta| x.cdf(arg);
Expand Down
2 changes: 1 addition & 1 deletion src/distribution/binomial/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ mod tests {
test_absolute(0.3, 3, 0.657, 1e-14, sf(0));
test_absolute(0.3, 3, 0.216, 1e-15, sf(1));
test_exact(0.3, 3, 0.0, sf(3));
test_absolute(0.3, 10, 0.9717524751000001, 1e-16, sf(0));
test_absolute(0.3, 10, 0.9717524751, 1e-16, sf(0));
test_absolute(0.3, 10, 0.850691654100002, 1e-14, sf(1));
test_exact(0.3, 10, 0.0, sf(10));
test_exact(1.0, 1, 1.0, sf(0));
Expand Down
Loading