Parity Strategy¶
nirs4all-lite needs parity gates because it is intended to become the portable
core behind multiple host-language APIs.
Tiers¶
Upstream native vs upstream binding: each upstream project proves its own binding parity first, especially
nirs4all-methods.nirs4all-lite native vs binding: lite pipelines produce identical results across Rust, Python, R, MATLAB/Octave, and WASM within declared tolerance.
nirs4all-lite vs full Python nirs4all: equivalent pipelines match the current Python library before the lite binding can replace any core path.
Fixture policy¶
Fixtures must record:
dataset identity and provenance;
pipeline descriptor;
upstream versions or commit SHAs;
numeric tolerance and dtype;
platform/runtime notes when relevant.
Golden outputs should be generated from the owning upstream implementation, not from host-language rewrites.
Pipeline definition fixtures must use the full Python nirs4all JSON/YAML
syntax. The parser-level contract accepts JSON/YAML paths, JSON/YAML text,
direct step lists, pipeline, and steps in every target binding, but
canonical fixtures should keep the same pipeline envelope used in
nirs4all/examples/pipeline_samples. Execution parity is a separate gate: a
definition that parses is not considered validated until native lite and every
target binding match the full Python nirs4all result for the portable operator
subset.
External operators follow the same rule. A language binding can expose operator
metadata before execution support exists, but an operator cannot be marked
execute-local, execute-remote, or parity-validated until a fixture proves
that the host-language adapter delegates to the same upstream behavior.
Initial parity targets¶
PLS pipelines backed by
nirs4all-methods.Format load/record conversion backed by
nirs4all-formats.Dataset materialization backed by
nirs4all-io.Catalog resolution backed by
nirs4all-datasets.DAG execution descriptors backed by
dag-mlanddag-ml-data.External operator adapters once the relevant executor can plan or call them.
Current execution gates¶
The initial numeric gates compare the JavaScript/WASM, Python, Rust, R, and
MATLAB/Octave bindings against full Python nirs4all on four shared JSON/YAML
pipeline fixtures:
SNV + PLS;
Savitzky-Golay + PLS;
Kennard-Stone + SNV + PLS;
Kennard-Stone + SNV + Savitzky-Golay + PLS component sweep.
The Python oracle is generated by
scripts/parity/generate_python_oracle.py into
tests/parity/expected/portable_python_oracle.json. It uses the real Python
nirs4all operators for Kennard-Stone, SNV, and Savitzky-Golay, plus
sklearn.cross_decomposition.PLSRegression. The WASM, Python, Rust, R, and
MATLAB/Octave tests then execute the same fixture descriptors through their
runPortablePipeline(), run_portable_pipeline(),
run_portable_pipeline_with_library(),
nirs4all_run_portable_pipeline(), and nirs4all.runPortablePipeline() APIs and
compare split indices, targets, RMSE, predictions, and selected sweep component
within the declared tolerances. The WASM gate also reuses the serialized
selected model through predictPortablePipeline() and checks that predictions
on the held-out rows match the selected run output.
The shared parser contract also has regression coverage for Savitzky-Golay
defaults and explicit boundary modes. mode defaults to interp to match full
Python nirs4all, while explicit methods-backed SciPy modes (mirror,
constant, nearest, wrap, interp) and cval are preserved across Python,
Rust, JavaScript/WASM, R, and MATLAB/Octave.
Regenerate the oracle from a workspace that has full Python nirs4all
installed:
PYTHONPATH=/home/delete/nirs4all/nirs4all \
/home/delete/nirs4all/nirs4all/.venv/bin/python \
scripts/parity/generate_python_oracle.py
Run the WASM parity gate after building/staging nirs4all-methods JS/WASM.
Set NIRS4ALL_METHODS_JS_DIST when the methods checkout is not a sibling of
this repository. Set NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 to make a
missing methods artefact fail instead of skip:
NIRS4ALL_METHODS_JS_DIST=/path/to/nirs4all-methods/bindings/js/dist \
NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 \
npm test --prefix bindings/wasm
Run the strict Python parity gate with nirs4all-methods importable and a
local libn4m build:
PYTHONPATH=bindings/python/src:/path/to/nirs4all-methods/bindings/python/src \
PLS4ALL_LIB_PATH=/path/to/libn4m.so \
NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 \
python -m unittest bindings/python/tests/test_execution_parity.py -v
Run the strict Rust parity gate with a local libn4m build:
NIRS4ALL_METHODS_LIB=/path/to/libn4m.so \
LD_LIBRARY_PATH=/path/to/libn4m-directory \
NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 \
cargo test -p nirs4all rust_binding_execution_matches_full_python_nirs4all_oracle -- --nocapture
Run the strict R parity gate after installing an n4m R binding that exposes
the portable preprocessing and splitter surface:
NIRS4ALL_LITE_PARITY_ORACLE=$PWD/tests/parity/expected/portable_python_oracle.json \
NIRS4ALL_LITE_PARITY_FIXTURES=$PWD/bindings/r/inst/extdata \
NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 \
Rscript bindings/r/tests/parity.R
Run the strict MATLAB/Octave parity gate after building the nirs4all-methods
+pls4all MEX shims:
NIRS4ALL_LITE_PARITY_ORACLE=$PWD/tests/parity/expected/portable_python_oracle.json \
NIRS4ALL_LITE_PARITY_FIXTURES=$PWD/tests/parity/fixtures \
NIRS4ALL_LITE_REQUIRE_METHODS_PARITY=1 \
octave --quiet --eval "addpath('bindings/matlab/tests'); parity"