57 lines
1.7 KiB
Go
57 lines
1.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package stats
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/tensor/internal/core"
|
|
)
|
|
|
|
// The pooled pair-slope buffer is sliced to the pair count before the
|
|
// median reads it: a reused buffer carries a longer tail from an earlier
|
|
// fit, and the median must never see it. The sequence below makes the
|
|
// long fit prime the pool and the short fits reuse it; a garbage
|
|
// collection between the calls would drop the entry and weaken the pin,
|
|
// so the fits run back to back.
|
|
|
|
func TestTheilSenPooledReuseKeepsExactSlope(t *testing.T) {
|
|
// Both fixtures sit exactly on y = 3x - 2, so every pair slope is
|
|
// exactly 3 and the answers are exact whatever the pair order.
|
|
makeLine := func(n int) (*core.Array, *core.Array) {
|
|
xs := make([]float64, n)
|
|
ys := make([]float64, n)
|
|
for i := range n {
|
|
xs[i] = float64(i) + 0.5
|
|
ys[i] = 3*xs[i] - 2
|
|
}
|
|
x, err := core.FromFloats(xs, n)
|
|
if err != nil {
|
|
t.Fatalf("FromFloats: %v", err)
|
|
}
|
|
y, err := core.FromFloats(ys, n)
|
|
if err != nil {
|
|
t.Fatalf("FromFloats: %v", err)
|
|
}
|
|
return x, y
|
|
}
|
|
bigX, bigY := makeLine(300)
|
|
smallX, smallY := makeLine(40)
|
|
if _, _, err := TheilSenRegression(bigX, bigY); err != nil {
|
|
t.Fatalf("long fit: %v", err)
|
|
}
|
|
for round := range 3 {
|
|
intercept, slope, err := TheilSenRegression(smallX, smallY)
|
|
if err != nil {
|
|
t.Fatalf("short fit %d: %v", round, err)
|
|
}
|
|
if slope != 3 {
|
|
t.Fatalf("short fit %d: slope = %v, want exactly 3", round, slope)
|
|
}
|
|
if intercept != -2 {
|
|
t.Fatalf("short fit %d: intercept = %v, want exactly -2", round, intercept)
|
|
}
|
|
}
|
|
}
|