from stats.linmod import lstsq_qr, lstsq_chol, lstsq_svd
#: Longley (NIST StRD): y, then x1 ... x6, one row per year 1947-1962
LONGLEY = [
[60323.0, 83.0, 234289.0, 2356.0, 1590.0, 107608.0, 1947.0],
[61122.0, 88.5, 259426.0, 2325.0, 1456.0, 108632.0, 1948.0],
[60171.0, 88.2, 258054.0, 3682.0, 1616.0, 109773.0, 1949.0],
[61187.0, 89.5, 284599.0, 3351.0, 1650.0, 110929.0, 1950.0],
[63221.0, 96.2, 328975.0, 2099.0, 3099.0, 112075.0, 1951.0],
[63639.0, 98.1, 346999.0, 1932.0, 3594.0, 113270.0, 1952.0],
[64989.0, 99.0, 365385.0, 1870.0, 3547.0, 115094.0, 1953.0],
[63761.0, 100.0, 363112.0, 3578.0, 3350.0, 116219.0, 1954.0],
[66019.0, 101.2, 397469.0, 2904.0, 3048.0, 117388.0, 1955.0],
[67857.0, 104.6, 419180.0, 2822.0, 2857.0, 118734.0, 1956.0],
[68169.0, 108.4, 442769.0, 2936.0, 2798.0, 120445.0, 1957.0],
[66513.0, 110.8, 444546.0, 4681.0, 2637.0, 121950.0, 1958.0],
[68655.0, 112.6, 482704.0, 3813.0, 2552.0, 123366.0, 1959.0],
[69564.0, 114.2, 502601.0, 3931.0, 2514.0, 125368.0, 1960.0],
[69331.0, 115.7, 518173.0, 4806.0, 2572.0, 127852.0, 1961.0],
[70551.0, 116.9, 554894.0, 4007.0, 2827.0, 130081.0, 1962.0],
]
#: NIST's certified coefficients B0 ... B6
CERTIFIED = [-3482258.63459582, 15.0618722713733, -0.358191792925910e-01, -2.02022980381683,
-1.03322686717359, -0.511041056535807e-01, 1829.15146461355]
#: digits of agreement are capped here, as lib/stats/nist_lls.py caps them
LRE_CAP: Num = 15.0
#: a column whose pivot is below this, relative, is dropped
RANK_TOL: Num = 1e-10
REFINE: Num = 1.0
NO_REFINE: Num = 0.0
#| The log relative error of the worst coefficient: how many digits agree.
def worst_lre(b: Arr, c: Arr) -> Num {
worst = LRE_CAP
for i to len(c) {
err = abs(b[i] - c[i]) / abs(c[i])
if err > 0.0 {
worst = min(worst, max(0.0, -log(err) / log(10.0)))
}
}
return worst
}
data = LONGLEY
n = len(data)
y = data[:, 0]
X = ones(n, len(CERTIFIED))
X[:, 1:] = data[:, 1:]
b_qr, r1, p1, c1 = lstsq_qr(X, y, RANK_TOL, REFINE)
b_qr0, r2, p2, c2 = lstsq_qr(X, y, RANK_TOL, NO_REFINE)
b_chol, r3, p3, c3 = lstsq_chol(X, y)
b_svd, r4, p4, c4 = lstsq_svd(X, y, RANK_TOL)
lre = [worst_lre(b_qr, CERTIFIED), worst_lre(b_qr0, CERTIFIED), worst_lre(b_chol, CERTIFIED),
worst_lre(b_svd, CERTIFIED)]
show lre
figure("Longley: certified digits of the worst coefficient", "solver", "digits")
bars([0.0, 1.0, 2.0, 3.0], lre, "lib/stats/linmod.viv")
xtick("QR, refined", 0.0)
xtick("QR", 1.0)
xtick("Cholesky", 2.0)
xtick("SVD", 3.0)
#: lib/stats/nist_lls.json records 13.2 digits for QR with refinement and 7.1 for Cholesky
QR_DIGITS: Num = 13.0
CHOL_DIGITS: Num = 7.0
test "QR keeps the digits the normal equations lose" {
assert_gt(lre[0], QR_DIGITS)
assert_gt(lre[2], CHOL_DIGITS)
assert_lt(lre[2], lre[0])
}
# SPDX-License-Identifier: MIT OR Apache-2.0
# SPDX-FileCopyrightText: 2026 The Viv authors