Skip to content

CONUS Leg B: species-free serializer (63) + loader SF-path tests#82

Closed
holoros wants to merge 45 commits into
mainfrom
feature/conus-sf-serializer
Closed

CONUS Leg B: species-free serializer (63) + loader SF-path tests#82
holoros wants to merge 45 commits into
mainfrom
feature/conus-sf-serializer

Conversation

@holoros

@holoros holoros commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Lands the species-independent (trait-driven) component fits into categories_conus_sf per variant, matching the shape config_loader.get_conus_sf_runtime_block decodes. Adds 63_conus_sf_to_variant_json.R (extract+serialize in one pass, recomputes trait standardization from species_traits.rds, builds the Leg A/Leg B hybrid source map) and config/tests/test_conus_sf_loader.py (4/4 passing). Completes the Option B schema: Leg A was already populated across 25 variants; this fills the Leg B gap the loader already supported. DG lands now (pilot fit exists); HG/HtDBH/HCB/mortality/crown-recession land as those species-free fits complete.

holoros added 30 commits June 2, 2026 14:54
Adds Greg Johnson CONUS gompit survival to the perseus projection path with a
four-arm runner (default/calibrated/posthoc/iterative), closed-cohort toggle,
and FVS TreeId-tracked TPA. Fixes project_mortality.spp_group to return an int
group (was a str -> KeyError against cch_organon; the cycle_survival path had
never run end to end).

Validation (NE, 6 stands, 100yr) rejects naive substitution: disabling FVS
mortality (MORTMULT 0) to let gompit own mortality makes FVS growth unrealistic
(no competition mortality -> runaway density/biomass; NOAUTOES is a no-op for
NE). See GOMPIT_INTEGRATION_FINDINGS.md for the diagnosis and the four design
options. Do not launch a CONUS gompit campaign on this wiring as-is.
Model-level prediction check of Greg Johnson gompit survival vs per-species base
rate on 7.6M FIA remeasurement records (133 species). gompit AUC 0.740 vs 0.673,
log loss 0.420 vs 0.443, 131/133 species improved. Headline: gompit tracks
observed survival down into the most crowded crown-closure quartile (0.779 pred
vs 0.777 obs) where the base rate stays flat at 0.835 and misses crowding
mortality. No FVS engine -> no density-feedback confound; this is the defensible
mortality-model result, distinct from the not-yet-valid projection wiring.

Adds R figure script, PNG, summary md, and the source comparison CSVs.
Substitutes Greg Johnson gompit survival for FVS native mortality INSIDE the
growth loop (TREGRO->MORTS), so growth, density, and the substituted mortality
interact each cycle. Resolves the density-feedback failure of the post-hoc TPA
overlay.

New: base/gompmort.f90 (GOMPLOAD coefficient reader + species resolve via
FIAJSP; GOMPCCH ORGANON crown-closure-at-tip port, affine-mapped; GOMPSURV
period survival) and common/GOMPMC.f90 shared state. Hooked into vls/morts.f90
(shared by NE/CS/LS): per-tree gompit override for fitted species, native
background for unfit, VARMRT density redistribution bypassed. Env-gated
(FVS_GOMPIT, FVS_GOMPIT_COEF) so one binary does native or gompit.

NE validation (8 stands, 100yr): gompit 147 t/ac vs native 173 at yr100 --
bounded, realistic, no runaway (cf. post-hoc 923, iterative crash). Gompit kills
less in open stands, more in crowded stands: the crown-closure signal, dynamic.
CS and LS share vls/morts.f90, so adding base/gompmort.f90 to their source
lists was sufficient -- both build and validate with no further code changes
(gompit module is variant-agnostic). Mean AGB at yr100 (native -> gompit):
NE 176->161 (-8%), CS 166->124 (-26%), LS 156->94 (-40%). All bounded and
realistic, no runaway; gompit trims late-rotation stocking as crown closure
rises, variant-specific in magnitude. Adds 3-panel figure + per-variant CSVs.
Proves the gompit hook generalises beyond the shared vls family: SN uses its own
sn/morts.f90 and got the identical pattern (include, GOMPCCH, per-tree override,
VARMRT bypass, GOMPLOAD in MORCON), builds and runs bounded.

Scaled A/B (NE/CS/LS ~200 stands each, SN 10), mean AGB yr100 native->gompit:
NE 63->49 (-21%), CS 107->84 (-21%), LS 95->56 (-41%), SN 56->12 (-78%). All
bounded, no runaway/crash. 4-panel figure updated.

SN -78% flagged: small sample + the coarse FIA->ORGANON group proxy fitting
southern pines/oaks poorly. Documents two items needing Aaron (not autopiloted):
the GOMPMORT keyword (shared keyword-table build-break risk) and group-map
refinement (would require re-fitting the affine cch map, 35d_validate_cch.R).
…alidated

Western variants share the Dixon/VARMRT mortality structure; a patcher applied
the identical hook to cr/ws/ec/ca morts.f90. All built and validated, bounded,
no runaway. yr100 native->gompit: EC -5%, CA -15%, CR -35%, WS -44%.

EC (-5%, PNW conifer) is the best ORGANON-proxy fit and nearly matches native;
SN (-78%, southern species) the worst -- the effect size tracks how well the
coarse FIA->ORGANON group map suits each variant, direct evidence for the
group-map refinement (needs affine re-validation, Aaron's loop).

Coverage now 8 variants across 4 mortality-routine families (NE/CS/LS shared
vls; SN own; CR/WS/EC/CA own western). Adds 2-panel all-variant figure
(trajectories + yr100 percent-change summary).
WC and PN share vwc/morts.f90, an ORGANON-based logistic mortality structurally
different from Dixon/VARMRT (annual RIP from per-species logistic, own SDI-max
feedback, no VARMRT). Adapted hook: for fitted species replace the annual RIP
with the gompit annual rate 1-exp(-H) (GOMPSURV T=1); the routine's existing
period conversion then reproduces gompit period survival exactly.

Validated, bounded: PN 328->190 (-42%), WC 204->89 (-56%). Dense wet Westside
conifer (high crown closure) -> large gompit effect, vs dry open EC (-5%);
effect size tracks both proxy fit and stand density, exactly what cch captures.

Coverage now 10 variants across 5 mortality-routine families. All-variant figure
updated (10 panels + sorted yr100 summary).
Universal patchers applied the gompit hook to all remaining variants whose
morts.f90 has the standard rate line (acd,bm,nc,oc,op,so,ut,em,tt,on) or the
WK2(I)=WKI assignment (ak,ci,ie,kt). All morts compile; 23/25 executables build
(on fails an unrelated pre-existing fire-module link error, fmcrow_; its hook
compiles fine). bc deferred -- different control flow broke insertion, needs a
manual hook.

18 variants validated native-vs-gompit, all bounded, no runaway/crash. yr100
change sorted: SN -78, CI -75, WC -56, WS -44, EM -43, PN -42, LS -41, AK -38,
CR -35, IE -24, NE -21, CS -21, CA -15, BM -9, EC -5, SO -0, NC +0. NC/SO/EC
(western conifer) near zero = best ORGANON-proxy fit; SN/CI largest. 17-variant
figure updated (UT excluded, degenerate 0-biomass sample).
Single reference: the model, the code (GOMPMC.f90, gompmort.f90, the 5-edit
morts hook), env activation, coverage table (24/25 hooked across all 5
mortality-routine families, 23 built, 18 validated with results), reproduction
steps, the two owner decisions (group-map refinement needs affine re-fit;
GOMPMORT keyword deferred for build-break risk), next steps, and the artifact
index.
…-builders

Validated TT (native 3.6 -> gompit 0.9, sparse subalpine, bounded). 19 variants
now validated. Confirmed BC and ON fail to build even unmodified (BC:
dbsrd1_/dminit_; ON: fmcrow_), so gompit covers all 23 buildable variants; BC
kept pristine (nonblock-DO control flow hostile to the hook AND unbuildable).
OC/OP/KT/ACD remain built+hooked but have no stress stands (their FIA plots are
labelled under other variants). Handoff + all-variant figure updated.
Activation now also via a keyfile keyword (reproducible), not just FVS_GOMPIT
env. Implemented at the only safe insertion point in the legacy dispatcher:
keywds.f90 TABLE slot 148 (previously blank, no index shift), a new GOTO target
(14800) + handler in vbase/initre.f90 that calls GOMPON, which flags LGOMPKW.
GOMPLOAD (MORCON, after the keyword reader) then activates on (env OR keyword).
BLOCK DATA GOMPBD initialises LGOMP/LGOMPKW/GHAVE (.AND. is not short-circuit in
the morts hook). Coeff path still from FVS_GOMPIT_COEF (deployment detail).

Validated on NE (5 stands, yr100 AGB): native 211.1, env-gompit 175.5,
keyword-gompit 175.5 -- keyword is byte-identical to the env path and both
differ from native. NE builds and links cleanly with the shared base edits.
…cally best)

Built a genus/crown-form crosswalk over all 18 ORGANON SWO groups and re-fit the
affine cch map on the held validation sample (113k trees, 4000 plots). Both the
full genus crosswalk (Spearman 0.853) and a conifer-only refinement (0.875)
DEGRADE the cch reproduction vs the coarse softwood/hardwood proxy (0.925). The
PNW-specific ORGANON crown equations add species variance that does not match
how the stored CCH1 was generated; uniformity wins for a rank-order proxy.

Conclusion: keep the validated coarse proxy unchanged. The variant spread in
gompit effect is a real cch-sensitivity property, not a crosswalk artifact --
which justifies the simple proxy. Resolves the flagged group-map item on
evidence.
Broad robustness test of the validated in-engine gompit system: 19 variants
across all 5 mortality-routine families x ~60 stands x native/gompit (~45,000
projection-years). ZERO gompit rows NaN/inf/negative/>2000 t/ac; no crashes, no
runaway. Max gompit AGB 1122 t/ac (NC dense redwood, physical). Effects NC -4%
to UT -82%, productive variants -13% to -27%.

Updates handoff: both flagged items resolved (group-map refinement tested and
rejected on evidence -- coarse proxy empirically best; GOMPMORT keyword
implemented and validated). The gompit Fortran integration is complete, robust,
keyword-activatable, and documented.
Skip a (variant,batch) task if its output csv+ledger already exist, so the full
381-task FIADB array can be resubmitted to rerun only timed-out/failed tasks
without redoing completed batches. Launched the full default-vs-calibrated FIADB
campaign (job array 0-380, 4h wall) over ~1.8M stands.
Grounded scope of the next phase: three FVS engines (default/calibrated/gompit)
into the perseus_api_v1 state-series model; disturbance+harvest scenarios from
the p_disturbance rasters -> statewide estimates; and TreeMap2022
spatially-explicit. Key feasibility result: TreeMap stores a donor FIA-plot ID
per 30m pixel (tm_id_plain.tif), so spatially-explicit projection reuses the
running FIADB per-plot compute almost for free via a raster join -- not a
billion-run campaign. Cost-ordered sequence + the harvest/disturbance/climate/
TreeMap-scope decisions that need Aaron.
…l v4)

Deep Cardinal search found the harvest model fully built: conus_hcs Harvest
Choice System, CV-validated CONUS v4, conditioned on price/ownership/region/rd,
delivered as 240m annual-probability + intensity rasters (p_partial,
p_clearcut, p_stand_replacement, intensity, expected_ba_removed, value_at_risk,
svi) + a stumpage panel. Harvest becomes a coupling task, not a modelling task.
Decisions resolved: harvest = couple conus_hcs; climate static; TreeMap ME pilot
first.
…eline

Recon found the PERSEUS engine is the empirical yield-curve engine (ycx) with
owner-specific harvest scenarios, TreeMap spatial expansion, and the
FIADB-vs-TreeMap comparison ALREADY built. FVS (default/calibrated/gompit) plugs
in as a drop-in growth engine: per-plot AGB trajectories replace the
Chapman-Richards curves, then reuse the same expansion/scenario/TreeMap/merge.
Confirmed join TreeMap TM_ID -> PLT_CN = campaign STAND_CN. Both tracks are
integration, not greenfield; gating input is the running FVS campaign.
Produces ycx_<ST>_state_series.csv (metric,mgmt,year,value,value_lo,value_hi,
native per-ha density) from the CONUS campaign csvs, the exact schema
ycx_merge_perseus.py reads. Engines fvs_default/calibrated/gompit; agb_dry +
agc_live_total (x0.47); calendar = 2025+PROJ_YEAR; reserve(no-harvest) bucket;
10/90 percentile bands. Validated on partial campaign output (18 states, 83k
plots, 2025-2125; OR agb_dry 30->382 Mg/ha). Full coverage + merge + ME pilot
follow as the campaign fills in.
Clips TreeMap2022_CONUS to Maine, strips the RAT to raw TM_ID, joins the VAT
(TM_ID->PLT_CN) -> me_treemap_donors.csv (TM_ID, n_pix, PLT_CN, area_ha). Result:
3578 unique donor FIA plots cover 6.78M forested ha of ME (75.3M 30m pixels).
Decouples the ME pilot from the CONUS campaign: only these 3578 plots need FVS.
…mpare

Stage B (me_donor_fvs.py): runs FVS default+calibrated on the 3578 Maine donor
plots (100% matched in the CONUS standinit) -> me_donor_trajectories.csv.
Stage C (me_treemap_stageC_compare.py): joins trajectories to donor pixel areas
and aggregates Maine AGB/AGC two ways -- TreeMap spatial (sum density x pixel
area) vs FIADB uniform (mean density x total area) -- isolating the area
expansion choice with FVS as the growth engine; outputs comparison CSV + figure.
Decoupled from the CONUS campaign; Stage B running now.
Stage C now reads comma-separated trajectory CSVs so the Maine comparison shows
all three engines (default, calibrated, gompit) x (TreeMap spatial vs FIADB
uniform). Gompit arm launched on the same 3578 ME donor plots via the gompit lib
+ FVS_GOMPIT (default growth, gompit mortality), relabeled CONFIG=gompit.
The donor plots drew empty tree lists from FIA_fresh/treeinit (which does not
cover the TreeMap-2022 donor CNs -> AGB ~0). Switch to TreeMap's own tree table
(TreeMap2022_CONUS_Tree_Table.csv) -- the imputed donor tree list, the correct
self-consistent source for a TreeMap projection. me_donor_extract.py filters
trees+standinit to the ME donors; me_donor_fvs.py builds treeinit from the
TreeMap trees (SPCD/DIA/HT/CR/TPA_UNADJ).
Fixed Tg unit (short ton=9.07e-7 Tg, was 1000x off; ratios were always correct).
First spatially-explicit FVS result over 3578 ME donor plots (TreeMap tree
lists), 100yr no-harvest:
- TreeMap-spatial ME AGB 17.5% above FIADB-uniform at 2025 (894 vs 761 Tg;
  132 vs 112 Mg/ha); ratio converges to ~1.0 by 2125 as stands mature.
- Engines diverge: default over-accumulates (4117 Tg), calibrated moderates
  (3665), gompit plateaus then declines (peak ~2771 ~2090 -> 2667) -- the
  national mortality gives the most realistic late-succession ceiling.
Pipeline is a raster join over per-plot FVS compute; scales to CONUS. Adds
results md + figure + csv.
…ines

Mirrors ycx_merge_perseus.py. Reads per-state aggregated densities
(perseus_series_<config>) + n_plot counts, expands to Tg via a fixed
FIA-anchored area model, injects three national models under cls FVS
(reserve no-harvest), with incremental, non-destructive states/meta
bookkeeping. Default+calibrated merged; gompit added once its array lands.
FVS_TREEINIT_PLOT TREE_COUNT under-expands overstory ~6.5x (median 1.0 vs
FIA TPA_UNADJ ~5.85), concentrated in eastern variants, making campaign
biomass ~6x light and unpublishable. treeinit_fix_tpa.py rejoins each row
via TREE_CN to the raw FIA TREE table and restores TPA_UNADJ; engine
unchanged. CONUS mean BA 48.8->91.3 ft2/ac. Campaign relaunched against
treeinit_fixed (out_fvs_v2 / out_gompit_v2).
FVS reports the inventory treelist before imputing missing tree heights;
NSBE biomass needs height, so the 2025 point is systematically understated
(50-90%, by missing-height fraction) and jumps by 2030. Anchor + report the
national FVS series from 2030 (first complete-height year). All FIA-anchored
states now reconcile exactly to fia.json carbon at 2030; trajectories bounded
and engine-ordered (gompit<=calibrated<=default in late succession).
Per-state annual removal rates sampled at FIA plot locations from the
conus_hcs expected-BA-removed rasters + annualized p_disturbance
(build_state_harvest_rates.R). fvs_managed_scenario.py applies them to the
reserve trajectories; merge injects a managed (harvest) bucket + harvest_c_yr
for all three engines. Live on dashboard.
Scales the ME pilot to CONUS via the TreeMap VAT (pixel Count per PLT_CN ->
per-plot area, 241.9 Mha, no raster scan). FVS 2030 carbon on TreeMap area
(9284 TgC) cross-validates to within 9% of TreeMap's own imputed carbon.
Area-expansion choice (TreeMap-spatial vs FIADB-uniform) is negligible at
CONUS (~2%) but diverges at finer scales (state 0.36-1.39, forest type
0.23-1.96): the scale-dependence Aaron asked about.
… steps

treeinit_fix_v2.py fills tree heights (FIA TREE.HT + per-species H-D impute)
so the 2025 inventory biomass is whole; campaign rerun launched (out_*_v3).
fvs_strata_trends.py aggregates carbon by owner/ecoregion/state across
reserve+managed with bootstrap CI. Adds refinements/next-steps doc.
…esign

Height rerun (treeinit_h, 100% heights) is a negative result: the 2025 PY0
understatement is FVS reporting cycle-0 before its H-D model runs, not input
heights (v3 ~3-4% above v2 but the jump persists), so the 2030 anchor stands.
Adds the concrete custom-config draw-propagation design for the posterior CI.
holoros added 15 commits June 4, 2026 15:16
…ed on ME

Propagates posterior draws through FVS via UncertaintyEngine.generate_keywords_
for_draw, monkeypatching the calibrated keyword path (no production change).
Single-draw SLURM-array design. ME (ne, 30 draws x 60 plots): parameter CI is
1-5% wide vs 30-60% structural engine spread -> the mortality model choice, not
the calibrated parameters, dominates projection uncertainty.
…live

Intensive harvest confined to FIADB plantation plots (STDORGCD=1, 10.2% of
CONUS forest); extensive on natural stands; calibrated so the plantation-
weighted blend reproduces the sampled conus_hcs rate. Merge injects four
scenario buckets per engine. On dashboard.
…pipeline

fvs_dashboard_stress.py validates all invariants across 49 states x 3 engines
x 4 scenarios. Found+fixed the NV degenerate band (clamp to contain value);
confirmed gompit late-succession decline is intended and the calibrated engine
differs from default everywhere (13-32% lower). run_fvs_perseus_pipeline.sh
folds the treeinit TPA+height fix in at stage 1 for reproducible campaigns.
Single reference: what is live, the raw-FIA->dashboard pipeline, key findings
(treeinit bug, gompit capping, plantation scenarios, uncertainty hierarchy,
FIADB-vs-TreeMap scales), tooling inventory, Cardinal locations, reproduce
steps, and remaining roadmap. Dashboard now on height-corrected v3 canonical.
Published the CONUS FVS PERSEUS projection dataset (v1.0.0,
10.5281/zenodo.20555667; concept 20555666), 21 files, CC-BY-4.0, cross-linked
to the fvs-modern software DOI.
…onservation)

The FVS-only managed (extensive) bucket was orphaned vs the dashboard
meta.buckets taxonomy. Emit it as managed (conservation) so the FVS curves are
selectable alongside other engines. Live; stress test + API integrity pass.
…neration

FVS engines are direct-injected to public/api/series (not from the master DB
pipeline); they survive regenerations (additive) but a full rebuild needs a
re-run of pipeline stage 7. Both gates (fvs_dashboard_stress + check_api_
integrity) must pass. Parameter CI now covers 19 calibrated states.
fvs_strata_trends.py now breaks carbon trends by owner/ecoregion/state/
forest_type x scenario with bootstrap CI. Forest-type totals (default reserve
2125): Oak/hickory ~7790, Maple/beech/birch ~3820, Oak/pine ~2040, Spruce/fir
~1880 Tg C. strata_trends_v3_default.csv committed.
Shareable capstone: exec summary, live dashboard, key findings, pipeline,
deliverables, DB-pipeline coexistence, roadmap. Generated via docx-js.
63_conus_sf_to_variant_json.R lands CONUS species-free (trait-driven)
component fits into categories_conus_sf per variant, in the exact shape
config_loader.get_conus_sf_runtime_block decodes. Recomputes trait
standardization from species_traits.rds to match the fitting script and
stores scale_mean/scale_sd so out-of-bundle species standardize at
runtime. Builds the Leg A/Leg B hybrid source map from existing
categories_conus coverage.

test_conus_sf_loader.py verifies the loader SF path end to end against a
fixture shaped like the serializer output: block decode, in/out-of-bundle
trait effect, species-free linear predictor, and conus_hybrid resolution.
4/4 pass.
@holoros
holoros marked this pull request as draft July 2, 2026 17:06
@holoros

holoros commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Converting to draft. Recon of Cardinal (sf_integration_dev/) shows the species-free Leg B pipeline is already built and fit for all six components, with an existing 62b serializer and a completed benchmark, plus an explicit hold in README_sf_integration_dev.md (do not land production configs or merge until reviewed). This branch's 63 serializer + 5 species-free Stan models overlap that work; holding for reconciliation with 62b rather than merging in parallel.

@holoros

holoros commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as superseded. The canonical species-free pipeline is 62b_speciesfree_to_variant_json.R (in sf_integration_dev), which produced the landed configs in PR #83. This branch's 63 serializer + 5 drop-the-RE Stan models duplicate that work. Recommendation captured in the session notes: promote 62b (and its test_sf_loader.py) from sf_integration_dev into calibration/R so the canonical serializer is version-controlled in the repo proper. No need to carry two parallel serializers.

@holoros holoros closed this Jul 2, 2026
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