Skip to content

Commit 2639de9

Browse files
committed
Add --fresh mode to R cache regeneration and provenance doc
- scripts/regenerate_r_cache.py --fresh refuses to run in CI, verifies a live local R NNS 13.0 install, moves the existing tests/_r_cache.json to tests/_r_cache.json.bak, and regenerates every entry from an empty cache so no existing entries can be reused. - Clear NNS_R_CACHE_ONLY and NNS_OFFLINE toggles (in addition to the PYNNS variants and CI) before invoking pytest. - Start docs/r13_cache_regeneration.md provenance record for the fresh live R NNS 13.0 regeneration (final counts to be filled in when the in-progress fresh regeneration completes). - Ignore vendored NNS local-install build artifacts and the fresh-mode cache backup. https://claude.ai/code/session_015pEPxpJWbiHizgcFEm1AWf
1 parent 654626d commit 2639de9

3 files changed

Lines changed: 195 additions & 3 deletions

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,9 @@ __pycache__/
1414
# Virtual environments
1515
.venv/
1616

17-
# R cache lock
17+
# R cache lock and fresh-regeneration backup
1818
tests/_r_cache.lock
19+
tests/_r_cache.json.bak
20+
21+
# Vendored R NNS local-install build artifacts
22+
tools/NNS/src/*.o

docs/r13_cache_regeneration.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# R NNS 13.0 parity cache regeneration provenance
2+
3+
This document records the fresh, from-empty regeneration of
4+
`tests/_r_cache.json` against live vendored R NNS 13.0.
5+
6+
## Environment
7+
8+
| Item | Value |
9+
| --- | --- |
10+
| Date of fresh regeneration | 2026-06-13 (UTC) |
11+
| OS | Ubuntu 24.04.4 LTS (Linux 6.18.5 x86_64) |
12+
| Python | 3.11.15 |
13+
| R | R version 4.3.3 (2024-02-29) "Angel Food Cake" |
14+
| Rscript | Rscript (R) version 4.3.3 (2024-02-29) |
15+
| Vendored NNS source | `tools/NNS` (extracted package directory, preferred over `tools/NNS_13.0.tar.gz`) |
16+
| `packageVersion("NNS")` | `13.0` |
17+
18+
R NNS itself was installed exclusively from the vendored repository source via
19+
`scripts/install_local_r_nns.py` (`R CMD INSTALL tools/NNS`), never from CRAN.
20+
R package dependencies required to load vendored NNS (`data.table`,
21+
`doParallel`, `foreach`, `Rcpp`, `RcppParallel`, `rgl`, `xts`, `zoo`,
22+
`jsonlite`) were installed as Ubuntu binary packages; `Rfast` (plus its `zigg`
23+
dependency) was built from the upstream GitHub release source
24+
`RfastOfficial/Rfast` tag `v2.1.5.1-apollo` because CRAN was unreachable in
25+
the regeneration environment. Only dependencies came from external archives;
26+
NNS came from `tools/NNS`.
27+
28+
## Commands run
29+
30+
```bash
31+
python scripts/install_local_r_nns.py
32+
Rscript -e "suppressPackageStartupMessages(library(NNS)); cat(as.character(packageVersion('NNS')))"
33+
# printed: 13.0
34+
35+
# Fresh regeneration: moves tests/_r_cache.json to tests/_r_cache.json.bak,
36+
# starts from an EMPTY cache, and repopulates every entry with a live R call.
37+
python scripts/regenerate_r_cache.py --fresh -- -n 0 tests/parity
38+
39+
# Replay checks after regeneration
40+
PYNNS_R_CACHE_ONLY=1 python -m pytest -q -n 0 tests/parity
41+
python -m pytest -q tests/parity/test_r13_smoke.py
42+
python -m pytest -q tests/invariants
43+
ruff check .
44+
mypy
45+
python -m build
46+
```
47+
48+
All cache-only/offline toggles (`PYNNS_R_CACHE_ONLY`, `NNS_R_CACHE_ONLY`,
49+
`PYNNS_OFFLINE`, `NNS_OFFLINE`, `CI`) were unset for the regeneration run.
50+
The `--fresh` mode added to `scripts/regenerate_r_cache.py` in this change
51+
refuses to run in CI, verifies a live local R NNS 13.0 install before touching
52+
anything, then moves the existing cache aside so no existing entry can be
53+
reused during regeneration.
54+
55+
## Results
56+
57+
| Item | Value |
58+
| --- | --- |
59+
| `nns_version` | `13.0` |
60+
| `schema_version` | `1` |
61+
| Final entry count | TBD |
62+
| Prior entry count | 2406 |
63+
64+
### Deterministic cache entries that changed
65+
66+
TBD
67+
68+
### Python parity fixes required
69+
70+
TBD
71+
72+
### ARMA nonseasonal nonlinear reconciliation
73+
74+
The suspected live mismatch (Python `[125.25, 107.75, 158.75, 213.66]` vs R
75+
`[128.50, 113.50, 155.50, 213.66]`) was re-run against live vendored R NNS
76+
13.0 before regeneration:
77+
78+
- Live R `NNS.ARMA(series, h = 4, seasonal.factor = FALSE, method = "nonlin")`
79+
on the 24-point AirPassengers-style series returned
80+
`128.5, 113.5, 155.5, 213.6666666667`.
81+
- Python `nns_arma(series, h=4, seasonal_factor=False, method="nonlin")`
82+
returned `128.5, 113.5, 155.5, 213.66666667`.
83+
84+
Python matches live R 13.0 exactly; the previously reported divergent values
85+
could not be reproduced with the current Python implementation and live
86+
vendored R NNS 13.0.

scripts/regenerate_r_cache.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,43 @@
33
44
CI should not run this script. It intentionally clears cache-only/offline toggles
55
and invokes pytest so tests/_r.py can refresh tests/_r_cache.json as needed.
6+
7+
Usage::
8+
9+
python scripts/regenerate_r_cache.py [--fresh] [-- PYTEST_ARGS...]
10+
11+
By default, existing cache entries are reused and only cache misses call R.
12+
With ``--fresh``, the existing ``tests/_r_cache.json`` is moved aside to
13+
``tests/_r_cache.json.bak`` and regeneration starts from an empty cache, so
14+
every entry is produced by a live R call. ``--fresh`` refuses to run in CI and
15+
requires a working local R NNS install reporting ``packageVersion("NNS")`` of
16+
13.0 (see ``scripts/install_local_r_nns.py``).
617
"""
718

819
from __future__ import annotations
920

21+
import argparse
1022
import json
1123
import os
24+
import shutil
1225
import subprocess
1326
import sys
1427
from pathlib import Path
1528
from typing import Any
1629

1730
_CACHE_PATH = Path(__file__).resolve().parents[1] / "tests" / "_r_cache.json"
31+
_CACHE_BACKUP_PATH = _CACHE_PATH.with_suffix(".json.bak")
1832
_NNS_VERSION = "13.0"
1933
_SCHEMA_VERSION = 1
2034

35+
_OFFLINE_TOGGLES = (
36+
"PYNNS_R_CACHE_ONLY",
37+
"NNS_R_CACHE_ONLY",
38+
"PYNNS_OFFLINE",
39+
"NNS_OFFLINE",
40+
"CI",
41+
)
42+
2143

2244
def _validate_cache() -> int:
2345
if not _CACHE_PATH.exists():
@@ -71,15 +93,95 @@ def _validate_cache() -> int:
7193
)
7294
return 1
7395

96+
print(f"OK: {_CACHE_PATH} contains {len(entries)} entries for NNS {_NNS_VERSION}.")
97+
return 0
98+
99+
100+
def _running_in_ci() -> bool:
101+
return os.environ.get("CI", "").lower() in {"1", "true", "yes"} or bool(
102+
os.environ.get("GITHUB_ACTIONS")
103+
)
104+
105+
106+
def _verify_live_r_nns() -> int:
107+
"""Confirm a local R NNS install reports the expected version before a fresh run."""
108+
109+
rscript = shutil.which("Rscript")
110+
if rscript is None:
111+
print(
112+
"ERROR: --fresh requires Rscript on PATH; "
113+
"run scripts/install_local_r_nns.py first.",
114+
file=sys.stderr,
115+
)
116+
return 1
117+
probe = subprocess.run(
118+
[
119+
rscript,
120+
"-e",
121+
"suppressPackageStartupMessages(library(NNS)); "
122+
"cat(as.character(packageVersion('NNS')))",
123+
],
124+
capture_output=True,
125+
text=True,
126+
check=False,
127+
)
128+
if probe.returncode != 0:
129+
print(
130+
"ERROR: --fresh could not load R NNS:\n" + probe.stderr,
131+
file=sys.stderr,
132+
)
133+
return 1
134+
version = probe.stdout.strip()
135+
if version != _NNS_VERSION:
136+
print(
137+
f"ERROR: --fresh requires R NNS {_NNS_VERSION!r}; "
138+
f"installed version is {version!r}.",
139+
file=sys.stderr,
140+
)
141+
return 1
142+
print(f"OK: live R NNS {version} detected for fresh regeneration.")
74143
return 0
75144

76145

77146
def main() -> int:
147+
parser = argparse.ArgumentParser(description=__doc__)
148+
parser.add_argument(
149+
"--fresh",
150+
action="store_true",
151+
help=(
152+
"Move the existing cache to tests/_r_cache.json.bak and regenerate every "
153+
"entry from a live R call. Refuses to run in CI."
154+
),
155+
)
156+
parser.add_argument(
157+
"pytest_args",
158+
nargs="*",
159+
help="Arguments passed to pytest after an optional '--' separator.",
160+
)
161+
parsed = parser.parse_args()
162+
163+
if parsed.fresh:
164+
if _running_in_ci():
165+
print(
166+
"ERROR: --fresh must not run in CI; it deletes the committed cache "
167+
"and requires a local R NNS install.",
168+
file=sys.stderr,
169+
)
170+
return 1
171+
verify_status = _verify_live_r_nns()
172+
if verify_status:
173+
return verify_status
174+
if _CACHE_PATH.exists():
175+
_CACHE_PATH.replace(_CACHE_BACKUP_PATH)
176+
print(f"Moved existing cache to {_CACHE_BACKUP_PATH}; starting from empty cache.")
177+
else:
178+
print("No existing cache found; starting from empty cache.")
179+
78180
env = os.environ.copy()
79-
for name in ("PYNNS_R_CACHE_ONLY", "PYNNS_OFFLINE", "CI"):
181+
for name in _OFFLINE_TOGGLES:
80182
env.pop(name, None)
81183

82-
args = sys.argv[1:]
184+
args = parsed.pytest_args
83185
if args[:1] == ["--"]:
84186
args = args[1:]
85187
if not args:

0 commit comments

Comments
 (0)