blob: ec7f3aa89bafa7ce51b6cf54d7e4c849e1e22bda [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import math
# This implements the quantile function (the inverse of the CDF) for
# Student's t-distribution, which is used by perfcompare.py for calculating
# confidence intervals.
#
# This function is implemented by SciPy as scipy.stats.t.ppf(), but we
# don't want to depend on SciPy because we don't have a build of it which
# works with fuchsia-vendored-python, and in general it is difficult to
# build Python libraries that use C extension modules for use with
# fuchsia-vendored-python. It is simpler to provide our own implementation
# of this one function instead!
#
# The implementation here was written by Gemini.
def t_pdf(x, df):
"""Probability Density Function of Student's t-distribution."""
coeff = math.exp(math.lgamma((df + 1) / 2) - math.lgamma(df / 2))
coeff /= math.sqrt(df * math.pi)
return coeff * (1 + (x**2) / df) ** (-(df + 1) / 2)
def _beta_cf(a, b, x, max_iter=200, eps=1e-12):
"""Continued fraction helper for incomplete beta (modified for stability)."""
m = 1
qab, qap, qam = a + b, a + 1, a - 1
c, d = 1.0, 1.0 - qab * x / qap
if abs(d) < eps:
d = eps
d = 1.0 / d
h = d
for m in range(1, max_iter + 1):
m2 = 2 * m
# Even step
aa = m * (b - m) * x / ((qam + m2) * (a + m2))
d = 1.0 + aa * d
if abs(d) < eps:
d = eps
c = 1.0 + aa / c
if abs(c) < eps:
c = eps
d = 1.0 / d
h *= d * c
# Odd step
aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2))
d = 1.0 + aa * d
if abs(d) < eps:
d = eps
c = 1.0 + aa / c
if abs(c) < eps:
c = eps
d = 1.0 / d
h *= d * c
# Early exit on convergence
if abs(d * c - 1.0) < eps:
break
return h
def beta_inc(a, b, x):
"""Regularized incomplete beta function with domain guards."""
if x <= 0:
return 0.0
if x >= 1:
return 1.0
# Log-gamma for the coefficient
lbeta = math.lgamma(a) + math.lgamma(b) - math.lgamma(a + b)
bt = math.exp(a * math.log(x) + b * math.log(1 - x) - lbeta)
if x < (a + 1) / (a + b + 2):
return bt * _beta_cf(a, b, x) / a
else:
return 1.0 - bt * _beta_cf(b, a, 1 - x) / b
def t_cdf(x, df):
"""CDF of Student's t using the relationship with the Beta function."""
if x == 0:
return 0.5
# The symmetry of the t-distribution
xt = df / (df + x**2)
prob = beta_inc(df / 2, 0.5, xt)
return 1 - 0.5 * prob if x > 0 else 0.5 * prob
def t_ppf(p, df, tol=1e-10):
"""Inverse CDF (Quantile function) using Newton-Raphson."""
if p <= 0 or p >= 1:
raise ValueError("Probability p must be in (0, 1)")
# Initial guess using a rough normal approximation
# For df > 2, the variance is df/(df-2), but 0 is a safe start
x = 0.0
for _ in range(50):
f_x = t_cdf(x, df) - p
f_prime_x = t_pdf(x, df)
# Avoid division by zero
if f_prime_x == 0:
break
dx = f_x / f_prime_x
x = x - dx
if abs(dx) < tol:
return x
return x