Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions .github/workflows/inspect-r-api-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ concurrency:

jobs:
regenerate-cache:
runs-on: ubuntu-latest
# The parity oracle's operating system (docs/parity.md): Ubuntu 24.04.
runs-on: ubuntu-24.04
env:
RGL_USE_NULL: 'true'
R_KEEP_PKG_SOURCE: 'yes'
Expand Down Expand Up @@ -119,11 +120,46 @@ jobs:
with:
python-version: '3.11'

- name: Set up R
uses: r-lib/actions/setup-r@v2
with:
r-version: release
use-public-rspm: true
# The committed cache is R's output under the environment docs/parity.md
# pins: CRAN's R 4.5.0 build for Ubuntu 24.04, the reference BLAS and
# LAPACK, and Ubuntu's Rcpp, RcppParallel and jsonlite. The current R
# release or OpenBLAS (what setup-r installs) moves some results, so a
# cache generated under them cannot pass the port's exact parity tests.
- name: Set up R 4.5.0 as the parity oracle runs it
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends ca-certificates wget
wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \
| sudo tee /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc > /dev/null
echo "deb https://cloud.r-project.org/bin/linux/ubuntu noble-cran40/" \
| sudo tee /etc/apt/sources.list.d/cran-r.list > /dev/null
sudo apt-get update
sudo apt-get install -y --no-install-recommends --allow-downgrades \
r-base-core=4.5.0-3.2404.0 r-base-dev=4.5.0-3.2404.0 \
libblas3 liblapack3 r-cran-rcpp r-cran-rcppparallel r-cran-jsonlite
sudo apt-mark hold r-base-core r-base-dev
sudo update-alternatives --set libblas.so.3-x86_64-linux-gnu \
/usr/lib/x86_64-linux-gnu/blas/libblas.so.3
sudo update-alternatives --set liblapack.so.3-x86_64-linux-gnu \
/usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3
mkdir -p "${RUNNER_TEMP}/R-library"
echo "R_LIBS_USER=${RUNNER_TEMP}/R-library" >> "${GITHUB_ENV}"

- name: Confirm the oracle environment
shell: bash
run: |
set -euo pipefail
Rscript -e '
si <- sessionInfo()
cat(R.version.string, "\nBLAS: ", si$BLAS, "\nLAPACK:", si$LAPACK, "\n")
for (p in c("Rcpp", "RcppParallel", "jsonlite")) cat(p, format(packageVersion(p)), "\n")
stopifnot(
identical(paste(R.version$major, R.version$minor, sep = "."), "4.5.0"),
grepl("/blas/libblas[.]so", si$BLAS),
grepl("/lapack/liblapack[.]so", si$LAPACK)
)'

- name: Install Linux system dependencies
shell: bash
Expand Down Expand Up @@ -158,7 +194,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
Rscript -e "options(repos=c(CRAN='https://cloud.r-project.org')); install.packages(c('remotes','jsonlite','mlbench')); remotes::install_deps('upstream/package', dependencies=NA, upgrade='never')"
# jsonlite, Rcpp and RcppParallel stay at the Ubuntu builds installed above.
Rscript -e "options(repos=c(CRAN='https://cloud.r-project.org')); install.packages(c('remotes','mlbench')); remotes::install_deps('upstream/package', dependencies=NA, upgrade='never')"

- name: Install exact NNS source package
shell: bash
Expand All @@ -183,7 +220,8 @@ jobs:
shell: bash
run: |
set -o pipefail
python scripts/regenerate_r_cache.py --fresh --allow-ci 2>&1 | tee parity-regeneration.log
# tests/invariants also reads the cache, so its entries are regenerated too.
python scripts/regenerate_r_cache.py --fresh --allow-ci -- tests/parity tests/invariants 2>&1 | tee parity-regeneration.log

- name: Confirm generated cache version
if: always()
Expand Down Expand Up @@ -231,8 +269,10 @@ jobs:
env:
R_COMMIT: ${{ steps.package.outputs.r_commit }}
R_VERSION: ${{ steps.package.outputs.r_version }}
SOURCE_TARBALL: ${{ steps.package.outputs.source_tarball }}
run: |
python - <<'PY'
import hashlib
import json
import os
from pathlib import Path
Expand All @@ -241,6 +281,13 @@ jobs:
manifest = json.loads(path.read_text(encoding='utf-8'))
manifest['r_commit'] = os.environ['R_COMMIT']
manifest['r_version'] = os.environ['R_VERSION']
tarball = Path('upstream') / os.environ['SOURCE_TARBALL']
if tarball.is_file():
manifest['r_source_archive'] = {
'filename': tarball.name,
'sha256': hashlib.sha256(tarball.read_bytes()).hexdigest(),
'repository_commit': os.environ['R_COMMIT'],
}
path.write_text(json.dumps(manifest, indent=2) + '\n', encoding='utf-8')
print(f"Manifest now records {manifest['r_repo']}@{manifest['r_commit']} "
f"(NNS {manifest['r_version']}).")
Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/r14-reproduction.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: R14 parity reproduction bundle

on:
workflow_dispatch:
push:
branches: [automation/r14-stack-cache-20261106]
paths: [.github/workflows/r14-reproduction.yml]

permissions:
contents: read

jobs:
bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Archive tracked source and pinned R package
shell: bash
run: |
set -euo pipefail
mkdir -p reproduction/wheels
git archive --format=tar.gz --output=reproduction/python-source.tar.gz HEAD
git rev-parse HEAD > reproduction/python-commit.txt
curl --fail --location --retry 3 \
https://raw.githubusercontent.com/OVVO-Financial/NNS/9c8bd21a77b377cb49d43fececc662d62b00a94e/NNS_14.0.tar.gz \
--output reproduction/NNS_14.0.tar.gz
sha256sum reproduction/*.tar.gz > reproduction/SHA256SUMS
python -m pip download --only-binary=:all: --dest reproduction/wheels \
hypothesis pytest-benchmark pytest-xdist nanobind scikit-build-core ruff mypy
- uses: actions/upload-artifact@v4
with:
name: r14-reproduction-source
path: reproduction/
retention-days: 7
if-no-files-found: error

oracle-runtime:
runs-on: ubuntu-latest
container: debian:trixie
steps:
- name: Build exact R source oracle
shell: bash
run: |
set -euo pipefail
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl r-base-core r-base-dev r-cran-rcpp \
r-cran-rcppparallel r-cran-jsonlite r-cran-mass r-cran-mlbench
curl --fail --location --retry 3 \
https://raw.githubusercontent.com/OVVO-Financial/NNS/9c8bd21a77b377cb49d43fececc662d62b00a94e/NNS_14.0.tar.gz \
--output /tmp/NNS_14.0.tar.gz
echo '754dad65629b95bf3ddca9bf9018ef197f5c346dd12dcfb49e505835920bc5cb /tmp/NNS_14.0.tar.gz' | sha256sum -c -
MAKEFLAGS=-j2 R CMD INSTALL /tmp/NNS_14.0.tar.gz
mkdir -p oracle-runtime/dependent-libraries
Rscript -e 'library(NNS); stopifnot(packageVersion("NNS") == "14.0"); sessionInfo()' > oracle-runtime/session-info.txt
find /usr/lib/R /usr/local/lib/R -name '*.so' -print0 | \
xargs -0 -r ldd 2>/dev/null | awk '/=> \/.* \(/{print $3}' | sort -u > oracle-runtime/library-paths.txt
while IFS= read -r lib; do
cp -L "${lib}" oracle-runtime/dependent-libraries/
done < oracle-runtime/library-paths.txt
tar -h -C / -czf oracle-runtime/r14-runtime.tar.gz \
usr/bin/R usr/bin/Rscript usr/lib/R usr/share/R etc/R usr/local/lib/R
sha256sum oracle-runtime/r14-runtime.tar.gz > oracle-runtime/SHA256SUMS
- uses: actions/upload-artifact@v4
with:
name: r14-executable-oracle-debian13
path: oracle-runtime/
retention-days: 7
if-no-files-found: error
4 changes: 3 additions & 1 deletion .github/workflows/workflow-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
".github/workflows/docs.yml"
".github/workflows/release.yml"
".github/workflows/sync-from-nns-core.yml"
".github/workflows/r14-reproduction.yml"
)

mkdir -p .github/workflows
Expand All @@ -75,7 +76,8 @@ jobs:
".github/workflows/inspect-r-api-update.yml"|\
".github/workflows/docs.yml"|\
".github/workflows/release.yml"|\
".github/workflows/sync-from-nns-core.yml")
".github/workflows/sync-from-nns-core.yml"|\
".github/workflows/r14-reproduction.yml")
;;
*)
echo "Removing non-baseline workflow: ${path}"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ native and does not call R at runtime.
|---|---|
| Distribution package | `ovvo-nns` |
| Import package | `nns` |
| Current version | `2.2.0` |
| Current version | `2.3.0` |
| Python | `>=3.11` |
| Required runtime dependencies | NumPy, SciPy, Matplotlib |
| R required at runtime | No |
Expand Down
22 changes: 16 additions & 6 deletions docs/parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
## Target and status

R NNS 14.0 is the parity target: the source at
`OVVO-Financial/NNS@7b07a837c130ba7220dbe16c584221508757d5ae`
`OVVO-Financial/NNS@9c8bd21a77b377cb49d43fececc662d62b00a94e`
(`NNS_14.0.tar.gz`, SHA256
`5060f6ec426c889501740bf8a585d41f37a746554cb6fdf81f301ca41232cd5b`). Every shard
`754dad65629b95bf3ddca9bf9018ef197f5c346dd12dcfb49e505835920bc5cb`; the same
archive is still committed at the R repository's later `de5d4fe`). Every shard
in `tests/_r_cache/` is stamped `nns_version` `14.0`. NNS-core remains the
native C++ foundation; R 14 kernels it does not carry enter as hashed overlays
recorded in `sync/nns_source.json`.
Expand Down Expand Up @@ -58,6 +59,8 @@ without the native extension:
| Stochastic dominance (`NNS.FSD`/`SSD`/`TSD`, `.uni`, `NNS.SD.cluster`, `NNS.SD.efficient.set`) | 2,583 |
| `NNS.meboot`, `NNS.MC`, `NNS.SS` and ARMA intervals under `set.seed` | 502 |
| Permutation p-values (`NNS.seas`, `NNS.dep`, `NNS.caus`) | 198 |
| Multivariate `NNS.reg` predictions on 900-1,500 rows (every distance, `n.best`, tied coordinates) | 84 |
| Paths compiled in the 14.0 update: integer-degree `LPM.VaR`/`UPM.VaR`, `NNS.copula`, `NNS.dep` matrices, classification predictions, mixed `dy.d_` | 110 |

The `*_r14.py` parity tests pin these results with exact cache entries.

Expand Down Expand Up @@ -88,16 +91,16 @@ python -m build # packaging check

## Installing the R NNS 14.0 oracle

Install the pinned R 14.0 source package, never the CRAN release, as the
`r14-reproduction` workflow does:
Install the pinned R 14.0 source package, never the CRAN release, into R 4.5.0
(see the scope note under Bit-level parity):

```bash
curl --fail --location -o NNS_14.0.tar.gz \
https://raw.githubusercontent.com/OVVO-Financial/NNS/7b07a837c130ba7220dbe16c584221508757d5ae/NNS_14.0.tar.gz
https://raw.githubusercontent.com/OVVO-Financial/NNS/9c8bd21a77b377cb49d43fececc662d62b00a94e/NNS_14.0.tar.gz
```

```bash
echo '5060f6ec426c889501740bf8a585d41f37a746554cb6fdf81f301ca41232cd5b NNS_14.0.tar.gz' | sha256sum -c -
echo '754dad65629b95bf3ddca9bf9018ef197f5c346dd12dcfb49e505835920bc5cb NNS_14.0.tar.gz' | sha256sum -c -
```

```bash
Expand Down Expand Up @@ -150,6 +153,13 @@ package and point `R_LIBS` at its library when regenerating
(`R_LIBS=<lib> python scripts/regenerate_r_cache.py ...`). The earlier shards,
generated from a patched R 13.3, have all been regenerated from R 14.0.

The R 14.0 update of 13 November 2026 (`9c8bd21`) moved interpreted loops into
compiled code. Regenerating every shard from it under R 4.5.0 changed no value
that the earlier 14.0 build (`7b07a83`) returns: the 37 entries that differ
from the previous cache reproduce identically on both builds, so they had been
written by another R installation (an R that evaluates `x^3` as `x * x * x`,
for one). Generate the cache with R 4.5.0, as the scope note above requires.

## Automated parity check (with deferred autofix agent)

`NNS-python` automates R-behavior fidelity detection. The automated **fixing
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ovvo-nns"
version = "2.2.0"
version = "2.3.0"
description = "Python port of nonlinear nonparametric statistics from R NNS"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/nns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from nns.pm_matrix import pm_matrix as pm_matrix

__version__ = "2.2.0"
__version__ = "2.3.0"

_EXPORTS = {
"BoostResult": ("nns.boost", "BoostResult"),
Expand Down
28 changes: 16 additions & 12 deletions sync/nns_source.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"r_repo": "OVVO-Financial/NNS",
"r_commit": "7b07a837c130ba7220dbe16c584221508757d5ae",
"r_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e",
"r_version": "14.0",
"r_src_tree_hash": "1c82fa36a4af3ce6359cc34b3d841afa6bdd1815",
"core_repo": "OVVO-Financial/NNS-core",
Expand All @@ -11,11 +11,11 @@
"vendored_r_path": "tools/NNS",
"vendored_r_tarball": "tools/NNS_13.0.tar.gz",
"r_cache_path": "tests/_r_cache",
"notes": "Behavioral oracle: the exact R14 archive and commit recorded above. The cumulative Python implementation through PR #189 is preserved under draft #192. Native R14 overlays are explicit and hashed separately; extern/NNS-core remains byte-for-byte at the recorded accepted vendor revision. The vendored tools/NNS snapshot, r_src_tree_hash, and tools/NNS_13.0.tar.gz retain their historical lineage and do not identify the live R14 behavioral oracle. Local verification results are recorded in docs/R14_LOCAL_PARITY_REPAIR.md; remote CI/release approval is separate.",
"notes": "Behavioral oracle: the exact R14 archive and commit recorded above. The cumulative Python implementation through PR #189 is preserved under draft #192. Native R14 overlays are explicit and hashed separately; extern/NNS-core remains byte-for-byte at the recorded accepted vendor revision. The vendored tools/NNS snapshot, r_src_tree_hash, and tools/NNS_13.0.tar.gz retain their historical lineage and do not identify the live R14 behavioral oracle. Local verification results are recorded in docs/R14_LOCAL_PARITY_REPAIR.md; remote CI/release approval is separate. The R 14.0 update at 9c8bd21 (compiled speed paths; the same archive through de5d4fe) returns the same values as 7b07a837 on every cached input, so overlays ported from 7b07a837 keep that source and record the oracle they were verified against in verified_r_commit.",
"r_source_archive": {
"filename": "NNS_14.0.tar.gz",
"sha256": "5060f6ec426c889501740bf8a585d41f37a746554cb6fdf81f301ca41232cd5b",
"repository_commit": "7b07a837c130ba7220dbe16c584221508757d5ae"
"sha256": "754dad65629b95bf3ddca9bf9018ef197f5c346dd12dcfb49e505835920bc5cb",
"repository_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e"
},
"r14_native_overlays": [
{
Expand All @@ -24,38 +24,42 @@
"r_source_path": "src/central_tendencies.cpp",
"r_source_sha256": "f50530afc3f94e8c7faef14f59bd44bacf64e7a21bd5c2e8b9421404a82bd61d",
"r_source_commit": "7b07a837c130ba7220dbe16c584221508757d5ae",
"adaptation": "R14 gravity/mode port with std::vector marshalling. CMake replaces only the old central-tendency translation unit; vendor rescale behavior is retained."
"adaptation": "R14 gravity/mode port with std::vector marshalling. CMake replaces only the old central-tendency translation unit; vendor rescale behavior is retained.",
"verified_r_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e"
},
{
"path": "src/nns/_r14_dependence.hpp",
"sha256": "4babec2c922cc5a50396cd2154fff48b0e88c43449de83da46aec4d4e1cf848d",
"r_source_path": "src/NNS_dep.cpp",
"r_source_sha256": "2e577f57165fd38d779dcde369eabf4800135eb50e07079069c11be2fedaa115",
"r_source_commit": "7b07a837c130ba7220dbe16c584221508757d5ae",
"adaptation": "R14 multilevel/null-corrected dependence with vector marshalling. R::dhyper and R::pnorm are the ports of R's own nmath routines in src/nns/_rnmath.hpp, so the null expectations match R's bit for bit. Bound explicitly instead of the untouched vendor dependence estimator."
"adaptation": "R14 multilevel/null-corrected dependence with vector marshalling. R::dhyper and R::pnorm are the ports of R's own nmath routines in src/nns/_rnmath.hpp, so the null expectations match R's bit for bit. Bound explicitly instead of the untouched vendor dependence estimator.",
"verified_r_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e"
},
{
"path": "src/nns/_r14_mreg_predict.cpp",
"sha256": "f93147dd754f415aa5d3786578d002b7931c2360d8683ce5770614c946ad038c",
"r_source_path": "src/NNS_mreg_predict.cpp",
"r_source_sha256": "e0fac52e99f33cd29fbb07b51711ded56c016d9e1a4b4a8eac8a9b4b9ed98571",
"r_source_commit": "7b07a837c130ba7220dbe16c584221508757d5ae",
"adaptation": "The vendored NNS-core multivariate prediction kernel (src/mreg_predict.cpp, sha256 c98d3fbb93c3165109870cb3d2cc1a55c8cb0fa35770b3ff0204441fd20e1e04) with its Student-t and normal ensemble weights evaluated by R's own nmath dt and dnorm4 (src/nns/_rnmath.hpp) instead of the vendor's lgamma-based closed form, so the weights and predictions match R's NNS_mreg_predict_v2_cpp bit for bit. CMake replaces only the vendor translation unit."
"adaptation": "The vendored NNS-core multivariate prediction kernel (src/mreg_predict.cpp, sha256 c98d3fbb93c3165109870cb3d2cc1a55c8cb0fa35770b3ff0204441fd20e1e04) with its Student-t and normal ensemble weights evaluated by R's own nmath dt and dnorm4 (src/nns/_rnmath.hpp) instead of the vendor's lgamma-based closed form, so the weights and predictions match R's NNS_mreg_predict_v2_cpp bit for bit. CMake replaces only the vendor translation unit.",
"verified_r_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e"
},
{
"path": "src/nns/_r14_seas.cpp",
"sha256": "2b956af7f064a575d1acd1283ec5a6f4ced8e91b227e294634ce3ab7fd83420e",
"r_source_path": "src/NNS_seas.cpp",
"r_source_sha256": "cf950b8e1b92bc942c132135dfb6a4909ca9f77c60da186e23eac0d30b2ed23e",
"r_source_commit": "7b07a837c130ba7220dbe16c584221508757d5ae",
"adaptation": "R14 NNS.seas lag scan: SeasComponent and seas_score copied verbatim, scored across lags with nns::parallel_for in place of RcppParallel (each lag writes its own slot, so results never depend on the thread count). Built into the NNS-core library target for its Release optimisation; bound as seas_scores_r14."
"r_source_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e",
"adaptation": "R14 NNS.seas lag scan: SeasComponent and seas_score copied verbatim, scored across lags with nns::parallel_for in place of RcppParallel (each lag writes its own slot, so results never depend on the thread count). Built into the NNS-core library target for its Release optimisation; bound as seas_scores_r14.",
"verified_r_commit": "9c8bd21a77b377cb49d43fececc662d62b00a94e"
}
],
"r_cache_summary": {
"nns_version": "14.0",
"shards": 80,
"entries": 2764,
"generation": "Pinned live R 14.0 only; no Python-authored expected values."
"shards": 149,
"entries": 2999,
"generation": "Pinned live R NNS 14.0 under R 4.5.0 (x86-64 Linux, reference BLAS/LAPACK) only; no Python-authored expected values."
},
"r_nmath_ports": [
{
Expand Down
Loading
Loading