Skip to content

gpuGranger: every pair in double by the Frisch-Waugh route; NA where unresolvable - #52

Merged
nullsatz merged 1 commit into
masterfrom
granger-rewrite
Oct 19, 2026
Merged

nullsatz merged 1 commit into
masterfrom
granger-rewrite

Conversation

@nullsatz

Copy link
Copy Markdown
Owner

First of the backports from widestats (its reviewed and verified granger).

The bug. gpuGranger set F = 0, p = 1 for any pair whose arithmetic went non-finite (collinear lags, a constant series, a cause identical to the effect): an undefined test reported as "no causality", silently. It also computed in single precision by default on uncentred data (F off by more than 10% on long random walks) and fitted one regression per pair with device memory growing as cols² · 2(lag+1) · n.

The method now. Each effect's restricted design [1, own lags] is orthonormalised once (Cholesky-QR2 on the GPU; Householder on the host above condition 1e7). Every cause's test against it comes from the products C = Q'L, L'e, Q'e as G = L'L − C'C, g = L'e − C'(Q'e), judged pair by pair (cond(G)/left · RSS_r/RSS_u < 1e6) and recomputed from the explicit residual lags M = L − QQ'L where that cancellation can't be trusted. All in double, series centred and scaled (changes no F). A pair ruled out by the effect design's condition (> 1e10), the cause's G (> 1e12) or the fraction of the cause left after projection (< 1e-20) is NA with a warning; a series against itself or a constant series is NA without one. lag is validated (1–16). precision is accepted but ignored, as the help page says.

Verification (RTX 2080 Ti and A5500, CUDA 12.6):

  • lm() to 1e-7 on random walks, drifting walks and trends at lags 1 and 3 (n = 5,000), and on 50 white-noise series × 20,000 times, which spans two tiles of effects; near-copies (cause = effect + 1e-6 noise) to 1e-6 against lm(tol = 1e-12) (lm's default rank tolerance drops the cause as aliased there); I(3) at lag 5: every pair flagged or within 1e-3.
  • Against widestats on the same data, max relative difference in F: walks 8e-11, drifting walks 7e-11, white noise 2e-12, I(2) lag 1 3e-11 and lag 3 2e-6 (both on the explicit path; documented accuracy for such series is 1e-4). NaN sets identical in every case.
  • Full testthat suite green; R CMD INSTALL clean.

Review (CUDA and host code transcribed to NumPy and checked): index arithmetic, cuBLAS strides, in-thread Cholesky/inverse/solve, bounds at lag 16, Householder, the weak rule all clean; fixed from it: the "NaN-propagating" minimum for left wasn't, device buffers are now freed before the error check that can longjmp, lag = 1e10 gets the intended message, a multi-tile test and deterministic assertions for the flagged cases.

Also: man/gpuGranger.Rd rewritten (formula, df, the conditioning rules, differencing advice, no more "single precision" in \value), NEWS for 2.1.1, a precision caveat in the benchmarks vignette (its Granger timings predate this change and will be re-measured).

🤖 Generated with Claude Code

… = 0, where unresolvable

The old kernels fitted one regression per pair by single-pass Gram-Schmidt
in the requested precision (single by default) on uncentred data, with
memory growing as the square of the number of series, and set F = 0,
p = 1 for any pair whose arithmetic produced a non-finite value: a
collinear design, a constant series, a cause identical to the effect all
came back as 'no causality'. Single precision was off by more than 10
percent on long random walks.

Now, following widestats' granger: each effect's restricted design is
orthonormalised once (Cholesky-QR2 on the GPU, Householder on the host
above condition 1e7); every cause's test against it comes from the
products C = Q'L, L'e, Q'e as G = L'L - C'C, g = L'e - C'(Q'e), checked
pair by pair (cond(G) / left * RSS_r / RSS_u below 1e6) and recomputed
from the explicit residual lags where the cancellation cannot be
trusted; all in double, series centred and scaled. A pair the effect's
design (cond > 1e10), the cause's G (> 1e12) or the fraction left
(< 1e-20) rules out is NA with a warning; a series on itself or a
constant series NA without one. lag checked (1 to 16); the precision
argument is accepted but ignored, as documented.

Verified on an RTX 2080 Ti and an A5500: lm() to 1e-7 on walks, drifting
walks and trends at lag 1 and 3 (n = 5,000; 50 series x 20,000 across two
tiles), near-copies to 1e-6 (lm with tol 1e-12), I(3) at lag 5 flagged or
within 1e-3; against widestats on the same panels, max relative
difference 8e-11 (walks, drift, white noise) and 2e-6 (I(2), lag 3,
both on the explicit path), NaN sets identical. Review fixes: a
NaN-propagating minimum for left, device buffers freed before the error
check, lag validated before coercion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@nullsatz
nullsatz merged commit c9848d6 into master Oct 19, 2026
10 checks passed
@nullsatz
nullsatz deleted the granger-rewrite branch October 19, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants