// 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" ) // quantileSubgradient evaluates the check loss's subgradient at a fit // and the slack the zero-band residuals buy it. At the optimum of // Σρ_τ(y − Xβ) there must be a choice of s ∈ [τ−1, τ] per observation // with Xᵀs = 0; residuals above the band fix their s (τ above the // fit, τ−1 below it), and residuals within 1e-6 of zero, where no // floating point fit can place them exactly, are free to slide in // [τ−1, τ]. The certificate passes when every column's forced // gradient is within the slack its zero-band residuals can absorb. func quantileSubgradient(t *testing.T, x, y *core.Array, beta []float64, tau float64) (grad, free []float64) { t.Helper() const band = 1e-6 n, p := x.Shape()[0], x.Shape()[1] grad = make([]float64, p) free = make([]float64, p) for i := range n { r := y.FloatAt(i) for j := range p { r -= beta[j] * x.FloatAt(i*p+j) } var s float64 switch { case r > band: s = tau case r < -band: s = tau - 1 default: for j := range p { free[j] += math.Abs(x.FloatAt(i*p + j)) } continue } for j := range p { grad[j] += x.FloatAt(i*p+j) * s } } for j := range p { free[j] *= math.Max(tau, 1-tau) } return grad, free } // quantileHeteroFixture builds the seeded heteroscedastic model the // coverage and monotonicity pins share: x uniform on [−2, 2], the // response moved by 1 + 2x and widened by a scale that grows with // |x|, so the tau-th conditional quantile is a genuinely different // line from the mean's and no single slope fits every tau. func quantileHeteroFixture(t *testing.T, n int, seed int64) (*core.Array, *core.Array) { t.Helper() g := core.NewGenerator(seed) vals := make([]float64, 0, 2*n) yv := make([]float64, 0, n) for range n { x := 4*g.Unit() - 2 y := 1 + 2*x + (0.3+0.3*math.Abs(x))*g.NormalUnit() vals = append(vals, 1, x) yv = append(yv, y) } return mustFromFloats(t, vals, n, 2), mustFromFloats(t, yv, n) } // TestQuantileMedianCertificate pins tau = 0.5 against the L1 median // regression answer, twice over. An intercept-only design has the // sample median for its answer, and the general design is verified // against the subgradient optimality certificate, which is the exact // first-order condition of the check loss rather than another // algorithm's output. func TestQuantileMedianCertificate(t *testing.T) { // Intercept only, odd sample: the fit is the median, the single // point every absolute deviation bends toward. const n = 25 g := core.NewGenerator(19) ys := make([]float64, n) for i := range n { ys[i] = math.Round(40*g.Unit() - 20) } y := mustFromFloats(t, ys, n) ones := make([]float64, n) for i := range n { ones[i] = 1 } design := mustFromFloats(t, ones, n, 1) res, err := QuantileRegression(design, y, 0.5) if err != nil { t.Fatalf("QuantileRegression: %v", err) } if !res.Converged { t.Fatalf("the intercept-only fit did not converge") } median, err := Median(y) if err != nil { t.Fatalf("Median: %v", err) } if math.Abs(res.Coefficients[0]-median) > 1e-7 { t.Fatalf("the tau = 0.5 intercept-only fit is %.12g, want the median %.12g", res.Coefficients[0], median) } // A general design against the subgradient certificate. xs := make([]float64, 0, 2*n) yv := make([]float64, 0, n) for range n { x := 4*g.Unit() - 2 yv = append(yv, 1+2*x+0.4*g.NormalUnit()) xs = append(xs, 1, x) } design2 := mustFromFloats(t, xs, n, 2) y2 := mustFromFloats(t, yv, n) fit, err := QuantileRegression(design2, y2, 0.5) if err != nil { t.Fatalf("QuantileRegression: %v", err) } grad, free := quantileSubgradient(t, design2, y2, fit.Coefficients, 0.5) for j := range 2 { t.Logf("tau = 0.5 column %d: forced gradient %.3g against slack %.3g", j, grad[j], free[j]) if math.Abs(grad[j]) > free[j]+1e-6 { t.Fatalf("column %d fails the subgradient certificate: %.3g against slack %.3g", j, grad[j], free[j]) } } } // TestQuantileObjectiveMonotone instruments the interior-point run on // the heteroscedastic fixture: the recorded objective is the running // best check loss, monotone non-increasing from the ordinary least // squares start, and the record has to show real descent: the // tau = 0.9 quantile line is not the mean line, and the check loss at // the optimum must sit measurably below the start. func TestQuantileObjectiveMonotone(t *testing.T) { design, y := quantileHeteroFixture(t, 400, 23) fit, err := QuantileRegression(design, y, 0.9) if err != nil { t.Fatalf("QuantileRegression: %v", err) } if !fit.Converged { t.Fatalf("the interior-point run did not converge") } if len(fit.Objective) < 3 { t.Fatalf("the run recorded only %d objectives, too few to show a descent", len(fit.Objective)) } for k := 1; k < len(fit.Objective); k++ { if fit.Objective[k] > fit.Objective[k-1] { t.Fatalf("the objective rose at record %d: %.12g after %.12g", k, fit.Objective[k], fit.Objective[k-1]) } } if math.Abs(fit.Objective[len(fit.Objective)-1]-fit.CheckLoss) > 1e-9 { t.Fatalf("the record ends at %.12g but the fit reports %.12g", fit.Objective[len(fit.Objective)-1], fit.CheckLoss) } t.Logf("check loss descended from %.6f to %.6f over %d iterations", fit.Objective[0], fit.CheckLoss, fit.Iterations) if fit.Objective[0]-fit.CheckLoss < 1 { t.Fatalf("the tau = 0.9 fit barely left the mean fit: descent %.6g", fit.Objective[0]-fit.CheckLoss) } } // TestQuantileTracksConditionalQuantile measures the fit where it // matters: on six hundred held-out draws of the heteroscedastic model, // the tau = 0.9 line must cover ninety percent of the responses and // the tau = 0.1 line ten percent, the defining property of a // conditional quantile estimate. func TestQuantileTracksConditionalQuantile(t *testing.T) { design, y := quantileHeteroFixture(t, 400, 23) fit90, err := QuantileRegression(design, y, 0.9) if err != nil { t.Fatalf("QuantileRegression tau 0.9: %v", err) } fit10, err := QuantileRegression(design, y, 0.1) if err != nil { t.Fatalf("QuantileRegression tau 0.1: %v", err) } if !fit90.Converged || !fit10.Converged { t.Fatalf("a coverage fit did not converge") } // The fitted slopes track the true conditional quantile slope 2. if math.Abs(fit90.Coefficients[1]-2) > 0.5 { t.Fatalf("the tau = 0.9 slope is %.4f, far from the truth 2", fit90.Coefficients[1]) } g := core.NewGenerator(97) const held = 600 below90, below10 := 0, 0 for range held { x := 4*g.Unit() - 2 yi := 1 + 2*x + (0.3+0.3*math.Abs(x))*g.NormalUnit() q90 := fit90.Coefficients[0] + fit90.Coefficients[1]*x q10 := fit10.Coefficients[0] + fit10.Coefficients[1]*x if yi <= q90 { below90++ } if yi <= q10 { below10++ } } coverage90 := float64(below90) / held coverage10 := float64(below10) / held t.Logf("held-out coverage: tau = 0.9 covers %.3f, tau = 0.1 covers %.3f", coverage90, coverage10) if coverage90 < 0.84 || coverage90 > 0.96 { t.Fatalf("the tau = 0.9 fit covers %.3f of held-out draws, want near 0.9", coverage90) } if coverage10 < 0.04 || coverage10 > 0.16 { t.Fatalf("the tau = 0.1 fit covers %.3f of held-out draws, want near 0.1", coverage10) } } // TestQuantileExactFit walks the zero-loss corner: a constant response // is reproduced exactly by the start, the check loss is zero, and the // run is settled before the first interior-point iteration. func TestQuantileExactFit(t *testing.T) { design := mustFromFloats(t, []float64{1, 0, 1, 1, 1, 2, 1, 3, 1, 4}, 5, 2) y := mustFromFloats(t, []float64{4.2, 4.2, 4.2, 4.2, 4.2}, 5) res, err := QuantileRegression(design, y, 0.3) if err != nil { t.Fatalf("QuantileRegression: %v", err) } if !res.Converged { t.Fatalf("the exact fit did not report convergence") } if res.CheckLoss != 0 { t.Fatalf("the exact fit reports check loss %g, want 0", res.CheckLoss) } if res.Iterations != 0 { t.Fatalf("the exact fit spent %d iterations, want 0", res.Iterations) } for i := range 5 { if math.Abs(res.Fitted[i]-4.2) > 1e-9 || math.Abs(res.Residuals[i]) > 1e-9 { t.Fatalf("the exact fit moved row %d: fitted %.12g", i, res.Fitted[i]) } } } // TestQuantileOnIntegerArrays exercises the widening accessor's // fallback paths in the loss and the recovery: an integer design and // response reach the fit through FloatAt rather than a raw float // payload. func TestQuantileOnIntegerArrays(t *testing.T) { design := mustFromInts(t, []int64{1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6}, 7, 2) y := mustFromInts(t, []int64{2, 3, 4, 5, 6, 7, 8}, 7) res, err := QuantileRegression(design, y, 0.75) if err != nil { t.Fatalf("QuantileRegression on integer input: %v", err) } if !res.Converged { t.Fatalf("the integer-input fit did not converge") } grad, free := quantileSubgradient(t, design, y, res.Coefficients, 0.75) for j := range 2 { if math.Abs(grad[j]) > free[j]+1e-6 { t.Fatalf("column %d fails the subgradient certificate: %.3g against slack %.3g", j, grad[j], free[j]) } } } // TestQuantileValidation refuses the malformed inputs: tau outside the // open interval, wrong shapes, non-finite samples and a rank-deficient // design. func TestQuantileValidation(t *testing.T) { design := mustFromFloats(t, []float64{1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5}, 6, 2) y := mustFromFloats(t, []float64{1, 3, 2, 5, 4, 7}, 6) for _, tau := range []float64{0, 1, -0.5, 1.5, math.NaN()} { if _, err := QuantileRegression(design, y, tau); err == nil || !strings.Contains(err.Error(), "strictly inside") { t.Fatalf("tau = %g: got %v, want the tau refusal", tau, err) } } if _, err := QuantileRegression(mustFromFloats(t, []float64{1, 2, 3, 4, 5, 6}, 6), y, 0.5); err == nil || !strings.Contains(err.Error(), "must be rank 2") { t.Fatalf("a rank 1 design: got %v, want the rank refusal", err) } if _, err := QuantileRegression(design, mustFromFloats(t, []float64{1, 2, 3, 4, 5, 6}, 3, 2), 0.5); err == nil || !strings.Contains(err.Error(), "must be rank 1") { t.Fatalf("a rank 2 response: got %v, want the rank refusal", err) } if _, err := QuantileRegression(design, mustFromFloats(t, []float64{1, 2, 3}, 3), 0.5); err == nil || !strings.Contains(err.Error(), "rows but the response") { t.Fatalf("a length mismatch: got %v, want the length refusal", err) } if _, err := QuantileRegression(mustFromFloats(t, []float64{1, 0, 1, 1}, 2, 2), mustFromFloats(t, []float64{1, 2}, 2), 0.5); err == nil || !strings.Contains(err.Error(), "n > p") { t.Fatalf("n = p: got %v, want the n > p refusal", err) } if _, err := QuantileRegression(design, mustFromFloats(t, []float64{1, 2, 3, math.NaN(), 5, 7}, 6), 0.5); err == nil || !strings.Contains(err.Error(), "non-finite") { t.Fatalf("a non-finite response: got %v, want the non-finite refusal", err) } if _, err := QuantileRegression(core.New(core.Complex, 6, 2), y, 0.5); err == nil || !strings.Contains(err.Error(), "complex") { t.Fatalf("complex input: got %v, want the complex refusal", err) } singular := mustFromFloats(t, []float64{1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}, 4, 3) if _, err := QuantileRegression(singular, mustFromFloats(t, []float64{1, 2, 3, 4}, 4), 0.5); err == nil || !strings.Contains(err.Error(), "singular") { t.Fatalf("a rank-deficient design: got %v, want the singular-system refusal", err) } }