Skip to content

refactor(carbon): move NSVB scalar test-oracle out of src into tests (#127.1) - #129

Open
mihiarc wants to merge 1 commit into
mainfrom
refactor/nsvb-scalar-oracle-127
Open

refactor(carbon): move NSVB scalar test-oracle out of src into tests (#127.1)#129
mihiarc wants to merge 1 commit into
mainfrom
refactor/nsvb-scalar-oracle-127

Conversation

@mihiarc

@mihiarc mihiarc commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Resolves the deferred item from #127: the parallel scalar NSVB reimplementation that shipped in src/ purely as a test oracle.

What moved

src/pyfia/carbon/nsvb/{equations,coefficients}.py scalar reference code → tests/nsvb_oracle.py, wired via pythonpath = ["tests"]:

  • equations.py: model_1model_5_jenkins, _model_k, harmonize_components, _eval_component, predict_tree_biomass, Coefficients, TreeBiomassResult
  • coefficients.py: lookup_coefficients, _row_to_dict, ecosubcd_to_division (the scalar one; ecosubcd_to_division_expr is production and stays)

The shipped library is now ~640 lines lighter and no longer carries a second implementation of the biomass math.

Why keep the oracle (not delete it)

test_nsvb_vectorized.py cross-checks the production vectorized pipeline tree-for-tree against this deliberately-independent implementation, and both are independently anchored to the hand-computed GTR-WO-104 worked-example values in test_nsvb_equations.py. That independent cross-check has already caught a real bug (the #123 half-implemented Model 2). Verified up front that the vectorized path is independently ground-truthed (the *_worked_example_exact tests assert compute_nsvb_biomass directly against the GTR sentinels), so the oracle is a genuine second check, not the sole source of truth.

Scope note — a coupling caught before removing

The issue also listed the carbon_fractions.py dict loaders. They stayed in src: they turned out to be production dependencies — _compute_default_{live,dead}_carbon_fraction (used by live_tree/standing_dead) derive their means from load_carbon_fractions_{live,dead}(). Only the two thin test-only getters would have been movable, which isn't worth splitting the module for. So only the equations + coefficients scalar reference moved.

Safety

No production code path changed. Shared constants (_K_*, _CULL_DENS_PROP), load_nsvb_coefficients/CoefficientTables, the build_*/get_vectorized_lookup_tables loaders, ecosubcd_to_division_expr, and the _df fraction loaders all stay in src. Carbon point estimates are byte-identical on GA/NV (ag + standing-dead). 6 test modules repointed to import nsvb_oracle.

924 unit + 12 property/validation tests pass; ruff + mypy clean.

…127.1)

The production NSVB path is fully vectorized (polars). A parallel *scalar*
reimplementation of the same math shipped in src/ purely as a test oracle
(equations.py Models 1-5 / predict_tree_biomass / harmonize_components /
_eval_component / Coefficients / TreeBiomassResult; coefficients.py
lookup_coefficients / _row_to_dict / ecosubcd_to_division). Move it to
tests/nsvb_oracle.py so the shipped library no longer carries a second
implementation, wired via `pythonpath = ["tests"]`.

The oracle is kept (not deleted): test_nsvb_vectorized.py cross-checks the
vectorized pipeline tree-for-tree against this independent implementation, and
both are anchored to the hand-computed GTR-WO-104 worked-example values in
test_nsvb_equations.py — a cross-check that has caught real bugs (the #123
half-implemented Model 2). Removing it would trade away that independent oracle.

Scope note: the carbon_fractions.py dict loaders were left in src. They looked
test-only but are production dependencies — `_compute_default_{live,dead}_carbon_fraction`
(used by live_tree/standing_dead) derive their means from them — so only the
equations + coefficients scalar reference moved. Verified before removing, per
the "check the coupling first" guidance.

No production code path changed: shared constants (`_K_*`, `_CULL_DENS_PROP`),
`load_nsvb_coefficients`/`CoefficientTables`, the `build_*`/`get_vectorized_lookup_tables`
loaders, `ecosubcd_to_division_expr`, and the `_df` fraction loaders all stay.
Carbon point estimates are byte-identical on GA/NV (ag+dead). 924 unit + 12
property/validation tests pass; ruff + mypy clean.
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.

1 participant