// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: MIT package stats import ( "math" "strings" "testing" "sourcedock.dev/petrbalvin/tensor/internal/core" ) // TestQuantileDeepTails pins the deep-left-tail quantiles that // the old 200-pass bisection silently mis-answered and the 1e300 // bracket floors refused: the answers must be within a rounding of the // exact values, never an unconverged midpoint with a nil error. func TestQuantileDeepTails(t *testing.T) { v, err := ExponentialQuantile(1e-100, 1) if err != nil { t.Fatalf("ExponentialQuantile: %v", err) } if relErr(v, 1e-100) > 1e-6 { t.Fatalf("ExponentialQuantile(1e-100, 1) = %g, want 1e-100", v) } v, err = ChiSquareQuantile(1e-30, 1) if err != nil { t.Fatalf("ChiSquareQuantile: %v", err) } // The exact χ²(1) deep lower tail: x = z² with Φ(z) = 0.5 + q/2, // so z ≈ (q/2)/φ(0) and x ≈ (π/2)·q². want := math.Pi / 2 * 1e-60 if relErr(v, want) > 1e-4 { t.Fatalf("ChiSquareQuantile(1e-30, 1) = %g, want %g", v, want) } v, err = GammaQuantile(1e-100, 0.5, 0.5) if err != nil { t.Fatalf("GammaQuantile: %v", err) } if v < 0.5*1e-200 || v > 2*1e-200 { t.Fatalf("GammaQuantile(1e-100, 0.5, 0.5) = %g, want ≈ 0.785e-200 (χ²(1)/2 at q²)", v) } // The denormal floor: the smallest representable q still answers // the smallest representable scale, or refuses loudly; never a // silent wrong number. if _, err = ExponentialQuantile(5e-324, 1); err != nil { t.Fatalf("ExponentialQuantile(5e-324): %v", err) } } func relErr(got, want float64) float64 { d := math.Abs(got - want) if want == 0 { return d } return d / math.Abs(want) } // TestNormalQuantileExtremeTail pins that a q below 2⁻⁵³, where // 1−q rounds to exactly 1, still answers through the accurate upper // tail instead of the bogus "q = 1" refusal. func TestNormalQuantileExtremeTail(t *testing.T) { v, err := NormalQuantile(1e-17) if err != nil { t.Fatalf("NormalQuantile(1e-17): %v", err) } // Φ(−8.2907496456... ) = 1e-17 to the digits that matter here. if relErr(0.5*math.Erfc(-v/math.Sqrt2), 1e-17) > 1e-6 { t.Fatalf("NormalQuantile(1e-17) = %g, tail = %g", v, 0.5*math.Erfc(-v/math.Sqrt2)) } v, err = StudentTQuantile(1e-17, 5) if err != nil { t.Fatalf("StudentTQuantile(1e-17, 5): %v", err) } tail, err := studentTUpperTail(-v, 5) if err != nil { t.Fatalf("studentTUpperTail: %v", err) } // The quantile mirrors the one-sided tail: P(T > −t) = q. if relErr(tail, 1e-17) > 1e-6 { t.Fatalf("StudentTQuantile(1e-17, 5) = %g, one-sided tail = %g, want %g", v, tail, 1e-17) } // The heavy tails stay representable far past where t² overflows: // Cauchy answers 1/(πq) exactly, df = 2 answers 1/sqrt(2q), where // the incomplete-beta argument used to saturate to a phantom zero // and clamp the quantile at the overflow wall. vc, err := StudentTQuantile(1e-200, 1) if err != nil { t.Fatalf("StudentTQuantile(1e-200, 1): %v", err) } if want := -1 / (math.Pi * 1e-200); relErr(vc, want) > 1e-6 { t.Fatalf("StudentTQuantile(1e-200, 1) = %g, want %g", vc, want) } v2, err := StudentTQuantile(1e-160, 2) if err != nil { t.Fatalf("StudentTQuantile(1e-160, 2): %v", err) } if want := -1 / math.Sqrt(2*1e-160); relErr(v2, want) > 1e-6 { t.Fatalf("StudentTQuantile(1e-160, 2) = %g, want %g", v2, want) } } // TestRegressionConstantResponse pins R² = 1 for a constant // response reproduced exactly, where the 1 − 0/0 form reported NaN // with a nil error. func TestRegressionConstantResponse(t *testing.T) { x := mustFloats(t, []float64{1, 0, 1, 1, 1, 2, 1, 3}, 4, 2) y := mustFloats(t, []float64{5, 5, 5, 5}, 4) res, err := LinearRegression(x, y) if err != nil { t.Fatalf("LinearRegression: %v", err) } if res.RSquared != 1 || res.AdjustedRSquared != 1 { t.Fatalf("constant response: R² = %v, adj = %v, want 1 and 1", res.RSquared, res.AdjustedRSquared) } w := mustFloats(t, []float64{1, 2, 1, 1}, 4) wres, err := WeightedLinearRegression(x, y, w) if err != nil { t.Fatalf("WeightedLinearRegression: %v", err) } if wres.RSquared != 1 || wres.AdjustedRSquared != 1 { t.Fatalf("weighted constant response: R² = %v, adj = %v, want 1 and 1", wres.RSquared, wres.AdjustedRSquared) } } // TestHistogramFullRange pins the refusal of a sample holding // both float extremes, whose edges would be ±Inf and whose counts // silently collapsed into bin 0. func TestHistogramFullRange(t *testing.T) { a := mustFloats(t, []float64{-math.MaxFloat64, 0, math.MaxFloat64}) if _, _, err := Histogram(a, 2); err == nil { t.Fatal("Histogram: expected a range error") } b := mustFloats(t, []float64{-math.MaxFloat64, 1, math.MaxFloat64, 4}, 2, 2) bx, err := core.Slice(b, 1, 0, 1) if err != nil { t.Fatalf("Slice: %v", err) } if _, _, _, err := Histogram2D(bx, b, 2, 2); err == nil { t.Fatal("Histogram2D: expected a range error") } } // TestChiSquareGOFRejectsInfiniteExpected pins that an // infinite expectation is refused at the entry point, under its own // name, rather than surfacing as a NaN inside the tail function. func TestChiSquareGOFRejectsInfiniteExpected(t *testing.T) { obs := mustFloats(t, []float64{10, 12, 9}) exp := mustFloats(t, []float64{math.Inf(1), 10, 10}) _, _, _, err := ChiSquareGoodnessOfFit(obs, exp) if err == nil || !strings.Contains(err.Error(), "expected frequencies") { t.Fatalf("ChiSquareGoodnessOfFit: err = %v, want the expected-frequencies refusal", err) } }