diff --git a/docs/decisions/0005-cspi-asymptote-covariate.md b/docs/decisions/0005-cspi-asymptote-covariate.md new file mode 100644 index 0000000..b2b9766 --- /dev/null +++ b/docs/decisions/0005-cspi-asymptote-covariate.md @@ -0,0 +1,89 @@ +# 0005. CSPI site-productivity asymptote covariate + +Status: PROPOSED (draft; awaiting team sign-off on the beta and clamp knobs) +Date: 2026-06-27 +Relates to: #75 (Block 1+3 to production), #76 (blocked CSPI plot join), ADR 0003 (hybrid recal engine) + +## Context + +The CONUS yield-curve cells (forest-type x ecoregion) are fit independently, with no +site-productivity term. The agreed upgraded design (#75) adds CSPI (climate site +productivity index) so cell asymptotes reflect carrying capacity and sparse cells borrow +strength. The intended plot-level join (CSPI ID -> PLT_CN, #76) is blocked: CSPI build +coordinates do not match the FIA true-coordinate convention (0% match at every precision). + +## Decision + +Use CSPI as a SPATIAL RASTER covariate, not a plot join. Sample the wall-to-wall 1 km +CSPI surface (CSPI_v2_5component_1km.tif, EPSG:4326, 0 to 100) at each FIA plot's +coordinates on Cardinal, aggregate to the fit cell key (ft_group|prov_code), and scale the +cell asymptote multiplicatively. FIA true coordinates never leave the cluster; only the +coordinate-free cell table (ycx_cell_cspi.csv) is emitted. This sidesteps #76 entirely. + +Scaling form: A_scaled = A * (1 + shrink * (clamp((CSPI/REF)^beta) - 1)), with +REF = 56.36 (CONUS median CSPI), beta = 1.0, clamp = [0.80, 1.25], +shrink = 30 / (30 + n_plots). The free-fit exponent (2.77 within forest type, 3.13 pooled) +is REJECTED as confound-amplified and biologically too strong: productive ecoregions carry +both high CSPI and high-biomass forest types, and CSPI explains only ~9% of asymptote +spread. beta and the clamp half-width are documented ASSUMPTIONS (team knobs), mirroring +the senescence S_SEN convention, not estimated quantities. + +t0 neutrality: behind the YCX_CSPI_ASYM flag, a second block re-anchors area for non-FIA +states so the scaled run reproduces baseline t0 (FIA-anchored states are already pinned via +the tg anchor). The flag is OFF by default; the live ycx_canonical_ci_fiadb.R is unchanged. + +## Consequences + +Verified on the full 48-state CONUS run (rcp45): CONUS aggregate is t0-neutral (+0.02% at +2025) and trajectory-neutral (-0.00% at 2125). The effect is redistributive at cell and +state scale (per-state 2125 delta -0.39% to +0.72%, median 0.00%), not a national shift. +360 of 873 cells (41%) are sparse (n_plots < 30) and receive full CSPI weight (mean scalar +0.976). The production value is sub-national accuracy and principled asymptotes where data +is thin, not a change to published CONUS totals, which is a virtue for a production change. + +Open for sign-off before merge and data regeneration: the beta exponent (default 1.0) and +the clamp half-width (default +/- 25%). Raising beta increases the effect. This PR adds the +mechanism and provenance only; it does NOT regenerate live data or alter published numbers. +On sign-off, regenerate the canonical CI with the flag on, validate t0 vs FIA, and bump +the Zenodo record to v1.2. + +## Stress-test update (2026-06-27) + +A stress battery (knob sweep, spatial cross-validation, Bakuzis site-ordering; see +20260627_cspi_stress_bakuzis_assessment.md) revised the scope. Spatial leave-one-ecoregion-out +cross-validation shows CSPI generalizes on the REMEASUREMENT asymptotes (held-out log-RMSE +0.783 to 0.716, +8.5%, within-ft partial r +0.37) but NOT on the current HYBRID production +asymptotes (+0.0%, within-ft partial r +0.08; hybrid asymptotes are far noisier and are a +different, peak-decline parameter). Knob sweep is robust (A-weighted change ~3% at beta=1.0 +clamp 25%, bounded under ~6% even at beta=2.0). Bakuzis site-ordering PASSES (monotone, +non-crossing site curves). + +Revised decision: apply CSPI scaling to the remeasurement-track refit (the Block 1+3 curves +this ADR's #75 promotes), NOT the hybrid engine. The mechanism, t0-pin, and wiring are +reused unchanged; only the input fit table changes at promotion time. Do not promote CSPI on +the hybrid engine: harmless (CONUS-neutral, well-ordered) but unsupported out-of-sample. +Keep beta=1.0 and clamp +/-25%. This PR stays DRAFT until the remeas promotion lands. + +## Deep-assessment update (2026-06-28): revise beta upward + +A deeper out-of-sample assessment (leave-one-ecoregion-out CV; 20260628_cspi_deep_assessment.md) +revises the beta choice and strengthens the case for CSPI: + +- **CSPI is non-redundant.** Held-out CV skill for the cell asymptote: CSPI +8.4%, ClimateNA + site index +0.9% (no skill), latitude +3.7%. Partial corr(logA, logCSPI | SI) = +0.41, so + CSPI's signal is independent of the existing SI product. No simpler covariate substitutes. +- **Optimal beta is higher than 1.0.** Held-out relRMSE improvement rises monotonically with + beta (1.0 +6.5%, 1.5 +8.8%, 2.0 +10.5%, free 2.77 +11.5%). The earlier "steep slope = + confounded" worry is refuted; the steepness generalizes. REVISED production setting: + **beta = 1.5** on the spatial density layer. +- **Clamp interaction:** at beta=1.5 the [0.70,1.45] clamp binds heavily in low-productivity + regions (e.g., 37.5% of Maine cells hit the 0.70 floor). Since the unclamped CV supports the + stronger signal, the clamp should be loosened to ~[0.60,1.60] when using beta=1.5, so it caps + only true extremes rather than truncating real signal. The clamp was a safety bound from the + now-refuted steepness concern. +- **Unchanged:** the asymptote scalar still cancels on the t0-anchored reserve trajectory, so + beta affects only the spatial/absolute product (density maps), not the anchored carbon line. + +Revised recommendation: promote CSPI on the spatial density layer at **beta=1.5, clamp +[0.60,1.60]**. Production overlays (CONUS 1 km, Maine 30 m) have been rebuilt at beta=1.5; a +final clamp-widening to [0.60,1.60] is the last tweak before promotion. diff --git a/scripts/yield_curve_engine/canonical_inputs/ycx_cell_cspi.csv b/scripts/yield_curve_engine/canonical_inputs/ycx_cell_cspi.csv new file mode 100644 index 0000000..112b018 --- /dev/null +++ b/scripts/yield_curve_engine/canonical_inputs/ycx_cell_cspi.csv @@ -0,0 +1,1229 @@ +level,key,n_plots,cspi_mean,cspi_sd,cspi_scalar +cell,Alder/maple|11.1.1,7,60.60147,5.89105,1.07531 +cell,Alder/maple|11.1.3,2,58.14740,0.00000,1.03176 +cell,Alder/maple|6.2.11,74,63.97586,6.98068,1.13518 +cell,Alder/maple|6.2.12,4,64.21818,2.83955,1.13948 +cell,Alder/maple|6.2.3,11,55.77972,3.85707,0.98975 +cell,Alder/maple|6.2.5,100,67.42657,4.27646,1.19641 +cell,Alder/maple|6.2.7,233,70.10532,2.35363,1.24394 +cell,Alder/maple|6.2.8,4,59.86569,3.83640,1.06225 +cell,Alder/maple|6.2.9,1,61.84761,0.00000,1.09742 +cell,Alder/maple|7.1.7,208,69.22760,2.93770,1.22837 +cell,Alder/maple|7.1.8,732,72.29416,2.19914,1.28278 +cell,Alder/maple|7.1.9,94,70.95146,2.79930,1.25896 +cell,Aspen/birch|0.0.0,430,49.26848,4.20847,0.87422 +cell,Aspen/birch|10.1.2,10,52.33231,5.56243,0.92858 +cell,Aspen/birch|10.1.3,155,48.01442,2.59095,0.85196 +cell,Aspen/birch|10.1.4,40,44.53377,2.12306,0.79020 +cell,Aspen/birch|10.1.5,83,45.84248,3.51636,0.81342 +cell,Aspen/birch|10.1.6,94,47.02557,2.07107,0.83442 +cell,Aspen/birch|10.1.7,2,50.26497,0.51148,0.89190 +cell,Aspen/birch|10.1.8,19,49.10822,2.89677,0.87137 +cell,Aspen/birch|12.1.1,6,50.51828,2.35002,0.89639 +cell,Aspen/birch|13.1.1,105,53.44551,3.93604,0.94833 +cell,Aspen/birch|5.2.1,27446,47.09015,3.30303,0.83556 +cell,Aspen/birch|5.2.2,3890,43.09566,1.10379,0.76469 +cell,Aspen/birch|5.3.1,1477,50.58613,4.45556,0.89760 +cell,Aspen/birch|5.3.3,108,61.58127,1.90804,1.09269 +cell,Aspen/birch|6.2.10,486,47.53313,2.77260,0.84342 +cell,Aspen/birch|6.2.11,3,55.19649,0.00000,0.97940 +cell,Aspen/birch|6.2.12,18,53.52797,3.77265,0.94980 +cell,Aspen/birch|6.2.13,880,48.57729,2.86431,0.86195 +cell,Aspen/birch|6.2.14,1701,49.29710,3.27048,0.87472 +cell,Aspen/birch|6.2.15,84,48.58347,4.65729,0.86206 +cell,Aspen/birch|6.2.3,75,55.28862,3.37098,0.98104 +cell,Aspen/birch|6.2.4,74,50.14281,2.76775,0.88973 +cell,Aspen/birch|6.2.5,27,51.65847,5.95815,0.91662 +cell,Aspen/birch|6.2.7,2,58.87071,3.34940,1.04460 +cell,Aspen/birch|6.2.8,23,52.92502,4.22787,0.93910 +cell,Aspen/birch|6.2.9,4,56.19043,3.29157,0.99704 +cell,Aspen/birch|7.1.7,14,68.08962,1.11047,1.20818 +cell,Aspen/birch|8.1.1,154,58.65929,3.64849,1.04085 +cell,Aspen/birch|8.1.10,64,63.93965,2.20356,1.13454 +cell,Aspen/birch|8.1.3,130,60.26868,2.66081,1.06940 +cell,Aspen/birch|8.1.4,2591,53.29804,4.99946,0.94572 +cell,Aspen/birch|8.1.5,359,62.19989,2.01273,1.10367 +cell,Aspen/birch|8.1.6,182,60.47471,3.08477,1.07306 +cell,Aspen/birch|8.1.7,88,62.88185,3.30357,1.11577 +cell,Aspen/birch|8.1.8,1278,51.58494,3.80462,0.91532 +cell,Aspen/birch|8.2.1,85,60.23482,3.45472,1.06880 +cell,Aspen/birch|8.2.2,231,57.05979,1.92844,1.01246 +cell,Aspen/birch|8.2.3,6,64.91015,0.84189,1.15176 +cell,Aspen/birch|8.2.4,4,61.53793,2.07645,1.09192 +cell,Aspen/birch|8.3.3,2,64.03889,2.03466,1.13630 +cell,Aspen/birch|8.3.4,1,59.19954,0.00000,1.05043 +cell,Aspen/birch|8.4.1,64,62.37593,2.19559,1.10679 +cell,Aspen/birch|8.4.2,51,62.80296,1.85186,1.11437 +cell,Aspen/birch|8.4.3,106,62.29540,1.51810,1.10536 +cell,Aspen/birch|8.4.4,5,63.68054,1.34581,1.12994 +cell,Aspen/birch|9.2.1,177,43.23430,1.17835,0.76715 +cell,Aspen/birch|9.2.2,959,43.67048,1.72218,0.77489 +cell,Aspen/birch|9.2.3,28,62.40576,2.54660,1.10732 +cell,Aspen/birch|9.3.1,5,47.15308,4.25758,0.83668 +cell,Aspen/birch|9.3.3,22,43.25024,2.71916,0.76743 +cell,Aspen/birch|9.4.5,1,49.03898,0.00000,0.87014 +cell,California mixed conifer|10.1.3,3,47.96191,2.90244,0.85103 +cell,California mixed conifer|10.1.5,1,45.60407,0.00000,0.80919 +cell,California mixed conifer|11.1.1,83,60.23309,6.11637,1.06877 +cell,California mixed conifer|11.1.3,37,51.51202,3.66218,0.91402 +cell,California mixed conifer|6.2.11,1076,58.69925,5.82150,1.04155 +cell,California mixed conifer|6.2.12,1652,62.90541,5.40178,1.11619 +cell,California mixed conifer|6.2.7,41,61.34473,3.32472,1.08850 +cell,California mixed conifer|6.2.8,287,55.43385,5.18752,0.98361 +cell,California mixed conifer|7.1.8,1,67.02834,0.00000,1.18934 +cell,Douglas-fir|10.1.2,116,52.77847,4.13747,0.93650 +cell,Douglas-fir|10.1.3,108,50.25012,2.10907,0.89163 +cell,Douglas-fir|10.1.4,47,44.44008,2.18551,0.78854 +cell,Douglas-fir|10.1.5,22,47.11674,2.70064,0.83604 +cell,Douglas-fir|10.1.6,165,44.94513,2.17690,0.79750 +cell,Douglas-fir|10.1.7,3,41.75050,2.47324,0.74082 +cell,Douglas-fir|10.1.8,19,52.26509,2.81770,0.92739 +cell,Douglas-fir|11.1.1,22,69.52604,11.48960,1.23366 +cell,Douglas-fir|11.1.3,1,56.97365,0.00000,1.01094 +cell,Douglas-fir|12.1.1,23,50.72186,2.88296,0.90000 +cell,Douglas-fir|13.1.1,409,53.13117,4.58348,0.94275 +cell,Douglas-fir|5.2.1,3,50.09261,2.61323,0.88884 +cell,Douglas-fir|6.2.10,2527,47.45210,2.85121,0.84199 +cell,Douglas-fir|6.2.11,1232,65.48874,5.78760,1.16203 +cell,Douglas-fir|6.2.13,323,48.21315,3.05544,0.85549 +cell,Douglas-fir|6.2.14,943,48.05023,3.07611,0.85260 +cell,Douglas-fir|6.2.15,1842,49.96804,4.90280,0.88663 +cell,Douglas-fir|6.2.3,3211,55.08600,3.79040,0.97744 +cell,Douglas-fir|6.2.4,259,50.80384,3.38024,0.90146 +cell,Douglas-fir|6.2.5,1143,58.73149,6.80581,1.04213 +cell,Douglas-fir|6.2.7,4519,69.82760,3.06960,1.23901 +cell,Douglas-fir|6.2.8,429,59.52553,4.83586,1.05622 +cell,Douglas-fir|6.2.9,1123,54.30111,3.19449,0.96351 +cell,Douglas-fir|7.1.7,439,69.78202,2.32666,1.23821 +cell,Douglas-fir|7.1.8,3437,71.98113,2.85956,1.27723 +cell,Douglas-fir|7.1.9,362,72.31163,2.23700,1.28309 +cell,Douglas-fir|8.1.3,4,56.84920,3.41361,1.00873 +cell,Douglas-fir|8.1.6,2,61.48707,0.00000,1.09102 +cell,Douglas-fir|9.3.1,12,48.87535,2.48670,0.86724 +cell,Douglas-fir|9.3.3,124,46.95004,2.56216,0.83308 +cell,Douglas-fir|9.4.3,3,47.96129,2.54347,0.85102 +cell,Elm/ash/cottonwood|0.0.0,195,53.23333,4.64991,0.94457 +cell,Elm/ash/cottonwood|10.1.2,4,49.11501,2.63626,0.87149 +cell,Elm/ash/cottonwood|10.1.4,19,42.72126,3.27330,0.75804 +cell,Elm/ash/cottonwood|10.1.5,2,40.95834,0.00000,0.72676 +cell,Elm/ash/cottonwood|10.1.6,24,40.72587,3.52930,0.72264 +cell,Elm/ash/cottonwood|10.1.7,18,41.68365,1.62728,0.73963 +cell,Elm/ash/cottonwood|10.1.8,7,41.89390,3.42574,0.74336 +cell,Elm/ash/cottonwood|10.2.1,1,35.86768,0.00000,0.63643 +cell,Elm/ash/cottonwood|10.2.2,5,38.84342,6.27826,0.68923 +cell,Elm/ash/cottonwood|10.2.4,4,35.71483,1.73775,0.63372 +cell,Elm/ash/cottonwood|11.1.1,2,60.83282,0.00000,1.07941 +cell,Elm/ash/cottonwood|11.1.2,6,50.92849,0.35749,0.90367 +cell,Elm/ash/cottonwood|12.1.1,4,40.13746,0.69939,0.71220 +cell,Elm/ash/cottonwood|13.1.1,17,44.74400,2.98318,0.79393 +cell,Elm/ash/cottonwood|15.4.1,20,51.89001,4.75177,0.92073 +cell,Elm/ash/cottonwood|5.2.1,5540,48.50733,4.31328,0.86071 +cell,Elm/ash/cottonwood|5.2.2,809,43.05819,1.19438,0.76402 +cell,Elm/ash/cottonwood|5.3.1,339,58.54182,5.14292,1.03876 +cell,Elm/ash/cottonwood|5.3.3,50,62.17520,1.69542,1.10323 +cell,Elm/ash/cottonwood|6.2.10,35,44.05645,3.53871,0.78173 +cell,Elm/ash/cottonwood|6.2.11,1,59.94367,0.00000,1.06364 +cell,Elm/ash/cottonwood|6.2.12,6,56.56168,4.58858,1.00363 +cell,Elm/ash/cottonwood|6.2.13,9,45.16961,7.01677,0.80149 +cell,Elm/ash/cottonwood|6.2.14,23,47.41412,3.58526,0.84131 +cell,Elm/ash/cottonwood|6.2.15,6,48.48973,3.88382,0.86040 +cell,Elm/ash/cottonwood|6.2.3,34,55.89802,3.93901,0.99185 +cell,Elm/ash/cottonwood|6.2.4,24,52.18081,2.30981,0.92589 +cell,Elm/ash/cottonwood|6.2.5,20,58.59872,7.95779,1.03977 +cell,Elm/ash/cottonwood|6.2.7,22,69.08756,3.81900,1.22588 +cell,Elm/ash/cottonwood|6.2.8,2,52.78160,1.46140,0.93655 +cell,Elm/ash/cottonwood|7.1.7,24,68.78833,1.95538,1.22057 +cell,Elm/ash/cottonwood|7.1.8,10,70.52258,4.68214,1.25135 +cell,Elm/ash/cottonwood|7.1.9,15,68.38955,3.34384,1.21350 +cell,Elm/ash/cottonwood|8.1.1,600,59.91674,3.48317,1.06316 +cell,Elm/ash/cottonwood|8.1.10,241,63.93648,1.82879,1.13448 +cell,Elm/ash/cottonwood|8.1.3,128,60.91870,2.29981,1.08094 +cell,Elm/ash/cottonwood|8.1.4,1969,55.17054,4.73285,0.97894 +cell,Elm/ash/cottonwood|8.1.5,772,64.23233,3.53821,1.13973 +cell,Elm/ash/cottonwood|8.1.6,1068,61.35015,2.99426,1.08859 +cell,Elm/ash/cottonwood|8.1.7,428,63.26750,2.83362,1.12261 +cell,Elm/ash/cottonwood|8.1.8,272,52.51089,3.52687,0.93175 +cell,Elm/ash/cottonwood|8.2.1,505,60.70827,3.84591,1.07720 +cell,Elm/ash/cottonwood|8.2.2,520,57.93943,2.45303,1.02807 +cell,Elm/ash/cottonwood|8.2.3,245,64.53139,2.24047,1.14504 +cell,Elm/ash/cottonwood|8.2.4,699,62.67864,2.14603,1.11216 +cell,Elm/ash/cottonwood|8.3.1,168,62.11535,2.32077,1.10217 +cell,Elm/ash/cottonwood|8.3.2,1576,61.53212,3.14270,1.09182 +cell,Elm/ash/cottonwood|8.3.3,866,59.10413,3.06009,1.04874 +cell,Elm/ash/cottonwood|8.3.4,1412,57.52044,3.17209,1.02064 +cell,Elm/ash/cottonwood|8.3.5,1907,57.44263,2.59593,1.01926 +cell,Elm/ash/cottonwood|8.3.6,510,55.73494,2.45746,0.98896 +cell,Elm/ash/cottonwood|8.3.7,896,56.55372,3.37884,1.00348 +cell,Elm/ash/cottonwood|8.3.8,293,51.18309,3.49627,0.90819 +cell,Elm/ash/cottonwood|8.4.1,327,60.27483,2.56156,1.06951 +cell,Elm/ash/cottonwood|8.4.2,121,62.11564,2.04902,1.10217 +cell,Elm/ash/cottonwood|8.4.3,447,61.79521,1.91405,1.09649 +cell,Elm/ash/cottonwood|8.4.4,67,60.98484,2.88680,1.08211 +cell,Elm/ash/cottonwood|8.4.5,339,57.55666,2.84351,1.02128 +cell,Elm/ash/cottonwood|8.4.6,21,51.76294,2.65479,0.91848 +cell,Elm/ash/cottonwood|8.4.7,227,52.48149,2.83781,0.93123 +cell,Elm/ash/cottonwood|8.4.8,65,53.48580,2.44660,0.94905 +cell,Elm/ash/cottonwood|8.4.9,70,58.62238,1.81688,1.04019 +cell,Elm/ash/cottonwood|8.5.1,437,59.58420,2.83154,1.05726 +cell,Elm/ash/cottonwood|8.5.2,2559,55.62366,2.65375,0.98698 +cell,Elm/ash/cottonwood|8.5.3,300,60.04877,4.41445,1.06550 +cell,Elm/ash/cottonwood|8.5.4,40,63.04082,2.74488,1.11859 +cell,Elm/ash/cottonwood|9.2.1,132,48.36110,4.78494,0.85812 +cell,Elm/ash/cottonwood|9.2.2,220,46.42607,3.00329,0.82378 +cell,Elm/ash/cottonwood|9.2.3,1149,62.62839,3.91676,1.11127 +cell,Elm/ash/cottonwood|9.2.4,1149,59.39107,3.72305,1.05383 +cell,Elm/ash/cottonwood|9.3.1,106,48.67802,5.81630,0.86374 +cell,Elm/ash/cottonwood|9.3.3,317,43.42694,2.93491,0.77056 +cell,Elm/ash/cottonwood|9.3.4,18,48.15067,3.15978,0.85438 +cell,Elm/ash/cottonwood|9.4.1,120,42.03107,1.66512,0.74580 +cell,Elm/ash/cottonwood|9.4.2,646,51.54143,5.78238,0.91455 +cell,Elm/ash/cottonwood|9.4.3,64,41.80279,4.33633,0.74174 +cell,Elm/ash/cottonwood|9.4.4,227,56.11620,1.78438,0.99572 +cell,Elm/ash/cottonwood|9.4.5,227,48.64040,2.58383,0.86307 +cell,Elm/ash/cottonwood|9.4.6,48,43.59757,2.70812,0.77359 +cell,Elm/ash/cottonwood|9.4.7,132,48.74854,2.09680,0.86499 +cell,Elm/ash/cottonwood|9.5.1,118,58.45143,2.92583,1.03716 +cell,Elm/ash/cottonwood|9.6.1,7,46.80032,2.38833,0.83042 +cell,Exotic hardwoods|10.1.3,129,46.74143,4.54876,0.82938 +cell,Exotic hardwoods|10.1.4,21,44.55944,2.43557,0.79066 +cell,Exotic hardwoods|10.1.5,487,44.48825,3.52222,0.78940 +cell,Exotic hardwoods|10.1.6,496,46.55939,2.34958,0.82615 +cell,Exotic hardwoods|10.1.7,44,45.41132,2.72660,0.80577 +cell,Exotic hardwoods|10.2.1,32,39.70737,3.64239,0.70456 +cell,Exotic hardwoods|10.2.2,271,37.88102,2.12236,0.67216 +cell,Exotic hardwoods|10.2.4,1244,38.06640,1.52025,0.67545 +cell,Exotic hardwoods|11.1.1,8,51.24667,2.60608,0.90932 +cell,Exotic hardwoods|11.1.3,6,52.21045,4.52766,0.92642 +cell,Exotic hardwoods|12.1.1,1107,40.42810,3.40313,0.71735 +cell,Exotic hardwoods|13.1.1,1082,47.71069,4.30896,0.84657 +cell,Exotic hardwoods|6.2.10,70,44.38953,3.77298,0.78764 +cell,Exotic hardwoods|6.2.11,4,53.43376,4.71959,0.94812 +cell,Exotic hardwoods|6.2.12,27,49.57154,4.61859,0.87959 +cell,Exotic hardwoods|6.2.13,694,49.26154,3.32629,0.87409 +cell,Exotic hardwoods|6.2.14,840,49.44554,2.36990,0.87736 +cell,Exotic hardwoods|6.2.15,22,46.67567,8.14561,0.82821 +cell,Exotic hardwoods|6.2.3,20,58.26361,3.19196,1.03382 +cell,Exotic hardwoods|6.2.5,2,63.36977,8.76295,1.12443 +cell,Exotic hardwoods|6.2.7,1,54.88709,0.00000,0.97391 +cell,Exotic hardwoods|6.2.8,43,50.11534,4.20869,0.88924 +cell,Exotic hardwoods|6.2.9,35,50.20941,6.31139,0.89091 +cell,Exotic hardwoods|7.1.8,1,73.53642,0.00000,1.30482 +cell,Exotic hardwoods|8.3.8,199,47.20557,2.96059,0.83761 +cell,Exotic hardwoods|9.4.1,631,37.44181,1.53777,0.66436 +cell,Exotic hardwoods|9.4.2,854,41.72302,1.54743,0.74033 +cell,Exotic hardwoods|9.4.3,923,39.04866,1.89122,0.69288 +cell,Exotic hardwoods|9.4.5,335,44.77736,1.61152,0.79453 +cell,Exotic hardwoods|9.4.6,732,40.26126,2.51214,0.71439 +cell,Exotic hardwoods|9.4.7,128,47.41560,1.76541,0.84134 +cell,Exotic hardwoods|9.5.1,310,47.11316,4.52808,0.83597 +cell,Exotic hardwoods|9.6.1,989,41.79517,1.71369,0.74161 +cell,Exotic softwoods|0.0.0,6,54.49013,3.62717,0.96687 +cell,Exotic softwoods|5.2.1,190,52.36873,4.48973,0.92923 +cell,Exotic softwoods|5.3.1,45,54.40832,3.93819,0.96542 +cell,Exotic softwoods|5.3.3,20,62.47733,1.05029,1.10859 +cell,Exotic softwoods|6.2.3,1,57.03432,0.00000,1.01201 +cell,Exotic softwoods|8.1.1,26,60.12935,1.94712,1.06693 +cell,Exotic softwoods|8.1.10,21,63.59778,1.23726,1.12847 +cell,Exotic softwoods|8.1.3,106,61.12409,1.98659,1.08458 +cell,Exotic softwoods|8.1.4,63,54.27027,3.99584,0.96297 +cell,Exotic softwoods|8.1.5,7,62.61091,1.12494,1.11096 +cell,Exotic softwoods|8.1.6,95,62.11712,2.59905,1.10220 +cell,Exotic softwoods|8.1.7,6,61.59042,2.25565,1.09285 +cell,Exotic softwoods|8.1.8,24,49.77725,2.07083,0.88324 +cell,Exotic softwoods|8.2.1,4,61.98589,1.86152,1.09987 +cell,Exotic softwoods|8.2.2,13,57.14783,1.80974,1.01403 +cell,Exotic softwoods|8.2.4,5,64.05087,0.78171,1.13651 +cell,Exotic softwoods|8.3.1,5,62.75052,1.13586,1.11344 +cell,Exotic softwoods|8.4.1,13,61.20657,2.61354,1.08604 +cell,Exotic softwoods|8.4.2,38,62.09072,1.78760,1.10173 +cell,Exotic softwoods|8.4.3,12,61.76291,2.08335,1.09592 +cell,Exotic softwoods|9.2.1,2,47.84195,3.16265,0.84890 +cell,Exotic softwoods|9.2.3,3,61.49196,2.75882,1.09111 +cell,Exotic softwoods|9.3.4,2,46.59957,0.00000,0.82686 +cell,Fir/spruce/mtn hemlock|10.1.2,19,57.18499,3.78960,1.01469 +cell,Fir/spruce/mtn hemlock|10.1.3,42,49.13740,2.51947,0.87189 +cell,Fir/spruce/mtn hemlock|10.1.4,24,44.45338,1.71642,0.78878 +cell,Fir/spruce/mtn hemlock|10.1.5,86,45.43172,3.65593,0.80614 +cell,Fir/spruce/mtn hemlock|10.1.6,60,46.28816,2.67354,0.82133 +cell,Fir/spruce/mtn hemlock|10.1.7,3,49.55898,1.46394,0.87937 +cell,Fir/spruce/mtn hemlock|10.1.8,8,54.35769,1.34625,0.96452 +cell,Fir/spruce/mtn hemlock|10.2.1,10,43.84896,0.71731,0.77805 +cell,Fir/spruce/mtn hemlock|11.1.3,5,50.13395,0.43493,0.88957 +cell,Fir/spruce/mtn hemlock|12.1.1,3,50.84250,2.20513,0.90214 +cell,Fir/spruce/mtn hemlock|13.1.1,208,54.11387,3.11822,0.96019 +cell,Fir/spruce/mtn hemlock|5.2.1,7,51.65963,4.37996,0.91664 +cell,Fir/spruce/mtn hemlock|6.2.10,1911,45.97218,2.86909,0.81573 +cell,Fir/spruce/mtn hemlock|6.2.11,198,59.21592,4.56508,1.05072 +cell,Fir/spruce/mtn hemlock|6.2.12,472,61.86154,4.24732,1.09767 +cell,Fir/spruce/mtn hemlock|6.2.13,933,46.43456,3.47584,0.82393 +cell,Fir/spruce/mtn hemlock|6.2.14,2471,47.93888,2.65132,0.85062 +cell,Fir/spruce/mtn hemlock|6.2.15,1605,50.27883,5.28778,0.89214 +cell,Fir/spruce/mtn hemlock|6.2.3,1936,57.00114,4.36356,1.01142 +cell,Fir/spruce/mtn hemlock|6.2.4,864,50.22467,3.60478,0.89118 +cell,Fir/spruce/mtn hemlock|6.2.5,831,57.31026,6.23376,1.01691 +cell,Fir/spruce/mtn hemlock|6.2.7,1332,64.24352,4.43960,1.13993 +cell,Fir/spruce/mtn hemlock|6.2.8,693,56.31046,3.78069,0.99917 +cell,Fir/spruce/mtn hemlock|6.2.9,1403,55.26266,3.26143,0.98058 +cell,Fir/spruce/mtn hemlock|7.1.7,7,67.26456,2.13738,1.19354 +cell,Fir/spruce/mtn hemlock|7.1.8,68,68.76147,3.86144,1.22010 +cell,Fir/spruce/mtn hemlock|7.1.9,12,71.41239,3.63130,1.26714 +cell,Fir/spruce/mtn hemlock|8.1.10,3,65.50817,2.04375,1.16237 +cell,Fir/spruce/mtn hemlock|8.1.4,5,57.71068,0.89073,1.02401 +cell,Fir/spruce/mtn hemlock|8.1.5,1,62.49836,0.00000,1.10897 +cell,Fir/spruce/mtn hemlock|8.1.6,2,59.73003,3.50121,1.05984 +cell,Fir/spruce/mtn hemlock|8.2.1,1,61.85470,0.00000,1.09754 +cell,Fir/spruce/mtn hemlock|8.4.2,4,61.81172,0.00000,1.09678 +cell,Fir/spruce/mtn hemlock|8.4.3,1,60.69392,0.00000,1.07695 +cell,Fir/spruce/mtn hemlock|9.3.3,1,48.04824,0.00000,0.85256 +cell,Hemlock/Sitka spruce|10.1.2,4,60.20676,1.07341,1.06830 +cell,Hemlock/Sitka spruce|6.2.11,2,70.07422,0.00000,1.24339 +cell,Hemlock/Sitka spruce|6.2.15,40,60.75265,2.75132,1.07799 +cell,Hemlock/Sitka spruce|6.2.3,691,58.35976,3.16960,1.03553 +cell,Hemlock/Sitka spruce|6.2.4,6,55.40485,1.79518,0.98310 +cell,Hemlock/Sitka spruce|6.2.5,493,65.76227,2.78568,1.16688 +cell,Hemlock/Sitka spruce|6.2.7,692,68.79139,2.82616,1.22063 +cell,Hemlock/Sitka spruce|6.2.8,5,62.25089,4.38044,1.10457 +cell,Hemlock/Sitka spruce|7.1.7,106,69.81789,2.43613,1.23884 +cell,Hemlock/Sitka spruce|7.1.8,862,71.67547,2.18721,1.27180 +cell,Hemlock/Sitka spruce|7.1.9,9,71.76225,2.99657,1.27334 +cell,Loblolly/shortleaf pine|15.4.1,2,44.23441,0.00000,0.78489 +cell,Loblolly/shortleaf pine|5.3.1,15,59.42428,1.97922,1.05442 +cell,Loblolly/shortleaf pine|5.3.3,6,61.28022,0.05178,1.08735 +cell,Loblolly/shortleaf pine|8.1.1,2,62.05378,0.00000,1.10108 +cell,Loblolly/shortleaf pine|8.1.3,3,59.97951,2.38738,1.06427 +cell,Loblolly/shortleaf pine|8.1.6,1,62.84568,0.00000,1.11513 +cell,Loblolly/shortleaf pine|8.1.7,20,64.68054,2.42997,1.14769 +cell,Loblolly/shortleaf pine|8.1.8,1,51.02411,0.00000,0.90537 +cell,Loblolly/shortleaf pine|8.2.3,1,63.77501,0.00000,1.13162 +cell,Loblolly/shortleaf pine|8.2.4,11,62.38594,2.01888,1.10697 +cell,Loblolly/shortleaf pine|8.3.1,127,60.82990,1.81687,1.07936 +cell,Loblolly/shortleaf pine|8.3.2,70,58.92726,3.02091,1.04560 +cell,Loblolly/shortleaf pine|8.3.3,540,58.71042,2.81633,1.04175 +cell,Loblolly/shortleaf pine|8.3.4,17820,56.52317,3.08868,1.00294 +cell,Loblolly/shortleaf pine|8.3.5,26052,57.40494,2.61489,1.01859 +cell,Loblolly/shortleaf pine|8.3.6,1787,57.98856,2.79037,1.02894 +cell,Loblolly/shortleaf pine|8.3.7,15387,57.28617,3.37741,1.01648 +cell,Loblolly/shortleaf pine|8.3.8,153,53.78248,3.79106,0.95431 +cell,Loblolly/shortleaf pine|8.4.1,2203,58.14984,2.29458,1.03181 +cell,Loblolly/shortleaf pine|8.4.2,116,61.52556,1.90265,1.09170 +cell,Loblolly/shortleaf pine|8.4.3,184,60.35084,1.28792,1.07086 +cell,Loblolly/shortleaf pine|8.4.4,1015,59.30965,2.18802,1.05239 +cell,Loblolly/shortleaf pine|8.4.5,494,58.14310,3.12882,1.03169 +cell,Loblolly/shortleaf pine|8.4.6,296,53.64938,2.02475,0.95195 +cell,Loblolly/shortleaf pine|8.4.7,646,52.73341,2.13327,0.93570 +cell,Loblolly/shortleaf pine|8.4.8,2891,53.77122,2.20998,0.95411 +cell,Loblolly/shortleaf pine|8.4.9,1605,57.18680,2.19121,1.01472 +cell,Loblolly/shortleaf pine|8.5.1,9045,60.30841,2.24301,1.07011 +cell,Loblolly/shortleaf pine|8.5.2,219,55.65342,2.48151,0.98751 +cell,Loblolly/shortleaf pine|8.5.3,4233,60.74912,5.39709,1.07793 +cell,Loblolly/shortleaf pine|8.5.4,596,61.67016,1.99097,1.09427 +cell,Loblolly/shortleaf pine|9.2.4,1,51.10176,0.00000,0.90675 +cell,Loblolly/shortleaf pine|9.4.5,1,47.93270,0.00000,0.85051 +cell,Loblolly/shortleaf pine|9.4.7,32,57.07453,1.61105,1.01273 +cell,Loblolly/shortleaf pine|9.5.1,173,60.21518,3.09443,1.06845 +cell,Lodgepole pine|10.1.2,2,54.62676,5.77435,0.96929 +cell,Lodgepole pine|10.1.3,8,50.05725,0.82547,0.88821 +cell,Lodgepole pine|10.1.4,24,44.34754,1.46819,0.78690 +cell,Lodgepole pine|10.1.5,8,54.72319,2.09019,0.97100 +cell,Lodgepole pine|10.1.6,5,43.99158,0.33855,0.78058 +cell,Lodgepole pine|10.1.8,10,49.40416,2.86425,0.87662 +cell,Lodgepole pine|11.1.3,7,55.32760,0.92362,0.98173 +cell,Lodgepole pine|6.2.10,2206,47.13927,2.30633,0.83643 +cell,Lodgepole pine|6.2.11,11,59.13286,5.70212,1.04925 +cell,Lodgepole pine|6.2.12,337,57.80493,4.57603,1.02569 +cell,Lodgepole pine|6.2.13,320,45.27566,2.31967,0.80337 +cell,Lodgepole pine|6.2.14,809,46.75637,1.94348,0.82964 +cell,Lodgepole pine|6.2.15,866,48.54475,3.69957,0.86137 +cell,Lodgepole pine|6.2.3,800,54.73303,3.14331,0.97118 +cell,Lodgepole pine|6.2.4,305,51.33903,2.72997,0.91096 +cell,Lodgepole pine|6.2.5,229,51.13959,3.34505,0.90742 +cell,Lodgepole pine|6.2.7,352,59.08365,3.90318,1.04838 +cell,Lodgepole pine|6.2.8,1025,53.29970,3.83768,0.94575 +cell,Lodgepole pine|6.2.9,496,54.29274,2.24274,0.96337 +cell,Lodgepole pine|7.1.7,3,70.56972,1.72138,1.25218 +cell,Lodgepole pine|7.1.8,14,71.81589,4.66921,1.27429 +cell,Lodgepole pine|9.3.1,6,46.96081,2.85917,0.83327 +cell,Lodgepole pine|9.3.3,12,50.24348,1.37184,0.89152 +cell,Longleaf/slash pine|15.4.1,143,50.40851,3.26059,0.89444 +cell,Longleaf/slash pine|8.3.4,145,55.01633,1.53176,0.97620 +cell,Longleaf/slash pine|8.3.5,10887,57.84385,2.70687,1.02638 +cell,Longleaf/slash pine|8.3.6,5,56.59821,2.86557,1.00427 +cell,Longleaf/slash pine|8.3.7,1064,59.81004,2.81952,1.06126 +cell,Longleaf/slash pine|8.3.8,3,51.61821,0.00000,0.91591 +cell,Longleaf/slash pine|8.4.1,62,56.49224,1.49071,1.00239 +cell,Longleaf/slash pine|8.4.9,12,55.28091,1.95684,0.98090 +cell,Longleaf/slash pine|8.5.1,905,60.50839,2.13317,1.07366 +cell,Longleaf/slash pine|8.5.2,2,60.86269,0.00000,1.07994 +cell,Longleaf/slash pine|8.5.3,13932,60.35897,4.25947,1.07100 +cell,Longleaf/slash pine|9.5.1,21,60.30785,2.01136,1.07010 +cell,Maple/beech/birch|0.0.0,448,51.56426,4.01279,0.91495 +cell,Maple/beech/birch|5.2.1,21183,48.83005,3.43400,0.86644 +cell,Maple/beech/birch|5.2.2,94,43.73938,1.09198,0.77611 +cell,Maple/beech/birch|5.2.3,4,47.06204,0.27941,0.83506 +cell,Maple/beech/birch|5.3.1,12986,54.21804,4.90279,0.96204 +cell,Maple/beech/birch|5.3.3,2776,61.97270,1.51776,1.09964 +cell,Maple/beech/birch|6.2.10,2,41.66104,1.88742,0.73923 +cell,Maple/beech/birch|8.1.1,1077,59.61390,3.37334,1.05778 +cell,Maple/beech/birch|8.1.10,843,63.72470,1.73831,1.13073 +cell,Maple/beech/birch|8.1.2,1,61.65461,0.00000,1.09399 +cell,Maple/beech/birch|8.1.3,2966,60.85056,2.30294,1.07973 +cell,Maple/beech/birch|8.1.4,3220,54.24839,4.73575,0.96258 +cell,Maple/beech/birch|8.1.5,1179,62.76625,2.33635,1.11372 +cell,Maple/beech/birch|8.1.6,986,61.35018,2.88845,1.08859 +cell,Maple/beech/birch|8.1.7,799,62.21557,2.80897,1.10395 +cell,Maple/beech/birch|8.1.8,3599,51.95730,3.83261,0.92193 +cell,Maple/beech/birch|8.2.1,346,60.68958,3.40131,1.07687 +cell,Maple/beech/birch|8.2.2,281,58.29436,2.03378,1.03437 +cell,Maple/beech/birch|8.2.3,143,65.21042,2.45771,1.15709 +cell,Maple/beech/birch|8.2.4,798,62.40981,1.82224,1.10739 +cell,Maple/beech/birch|8.3.1,136,63.12782,2.11970,1.12013 +cell,Maple/beech/birch|8.3.2,728,61.28798,2.73746,1.08749 +cell,Maple/beech/birch|8.3.3,1213,60.73603,3.05972,1.07769 +cell,Maple/beech/birch|8.3.5,4,62.62626,3.03194,1.11123 +cell,Maple/beech/birch|8.3.6,17,55.17306,2.35818,0.97899 +cell,Maple/beech/birch|8.3.7,6,53.21598,3.29133,0.94426 +cell,Maple/beech/birch|8.3.8,1,53.48127,0.00000,0.94897 +cell,Maple/beech/birch|8.4.1,1485,62.64822,2.19227,1.11162 +cell,Maple/beech/birch|8.4.2,2206,63.56793,2.13454,1.12794 +cell,Maple/beech/birch|8.4.3,1795,62.19729,1.80438,1.10362 +cell,Maple/beech/birch|8.4.4,379,63.62325,2.38501,1.12893 +cell,Maple/beech/birch|8.4.5,165,58.84919,2.18947,1.04421 +cell,Maple/beech/birch|8.4.6,1,55.93204,0.00000,0.99245 +cell,Maple/beech/birch|8.4.9,79,59.29430,1.15225,1.05211 +cell,Maple/beech/birch|8.5.1,16,62.96361,4.36061,1.11722 +cell,Maple/beech/birch|8.5.2,3,60.63120,0.48948,1.07583 +cell,Maple/beech/birch|8.5.4,26,63.51458,3.28889,1.12700 +cell,Maple/beech/birch|9.2.1,90,49.37111,5.12644,0.87604 +cell,Maple/beech/birch|9.2.2,93,46.91331,2.36230,0.83243 +cell,Maple/beech/birch|9.2.3,513,61.24677,3.46953,1.08676 +cell,Maple/beech/birch|9.2.4,621,59.76945,2.50573,1.06054 +cell,Maple/beech/birch|9.3.1,31,51.99735,5.58450,0.92264 +cell,Maple/beech/birch|9.3.3,39,43.27969,2.62086,0.76795 +cell,Maple/beech/birch|9.3.4,5,47.41842,5.36117,0.84139 +cell,Maple/beech/birch|9.4.1,7,41.64697,1.93536,0.73898 +cell,Maple/beech/birch|9.4.2,164,53.86148,5.40291,0.95571 +cell,Maple/beech/birch|9.4.3,4,48.83119,1.43322,0.86646 +cell,Maple/beech/birch|9.4.4,167,55.90543,1.74747,0.99198 +cell,Maple/beech/birch|9.4.5,10,54.61503,2.67345,0.96908 +cell,Oak/gum/cypress|15.4.1,359,50.79347,3.98131,0.90127 +cell,Oak/gum/cypress|5.2.1,2,58.14958,0.00000,1.03180 +cell,Oak/gum/cypress|5.3.1,1,65.04037,0.00000,1.15407 +cell,Oak/gum/cypress|5.3.3,11,61.52294,1.00495,1.09166 +cell,Oak/gum/cypress|8.1.1,11,62.15933,3.85445,1.10295 +cell,Oak/gum/cypress|8.1.10,19,64.85697,1.98534,1.15082 +cell,Oak/gum/cypress|8.1.6,21,63.47970,2.15224,1.12638 +cell,Oak/gum/cypress|8.1.7,57,64.16687,2.41747,1.13857 +cell,Oak/gum/cypress|8.2.2,8,57.82709,0.49350,1.02608 +cell,Oak/gum/cypress|8.2.3,4,63.49182,0.58241,1.12659 +cell,Oak/gum/cypress|8.2.4,41,61.86775,2.02365,1.09778 +cell,Oak/gum/cypress|8.3.1,4,63.33105,1.77506,1.12374 +cell,Oak/gum/cypress|8.3.2,185,59.62755,2.73855,1.05803 +cell,Oak/gum/cypress|8.3.3,224,58.64370,2.02872,1.04057 +cell,Oak/gum/cypress|8.3.4,877,56.91081,2.72849,1.00982 +cell,Oak/gum/cypress|8.3.5,12622,57.93509,2.54686,1.02799 +cell,Oak/gum/cypress|8.3.6,932,57.16967,2.82838,1.01441 +cell,Oak/gum/cypress|8.3.7,4259,57.18872,3.45070,1.01475 +cell,Oak/gum/cypress|8.3.8,243,52.11863,3.38915,0.92479 +cell,Oak/gum/cypress|8.4.1,103,57.73333,2.42387,1.02442 +cell,Oak/gum/cypress|8.4.2,9,60.83419,2.22119,1.07944 +cell,Oak/gum/cypress|8.4.3,1,61.74389,0.00000,1.09558 +cell,Oak/gum/cypress|8.4.4,15,60.20590,3.57362,1.06829 +cell,Oak/gum/cypress|8.4.5,61,56.42512,3.06825,1.00120 +cell,Oak/gum/cypress|8.4.6,12,51.32470,0.87418,0.91070 +cell,Oak/gum/cypress|8.4.7,224,53.51234,2.55090,0.94952 +cell,Oak/gum/cypress|8.4.8,146,54.43311,2.33812,0.96586 +cell,Oak/gum/cypress|8.4.9,113,57.87968,2.10239,1.02701 +cell,Oak/gum/cypress|8.5.1,5647,59.90991,2.34853,1.06304 +cell,Oak/gum/cypress|8.5.2,3977,55.50588,2.89240,0.98489 +cell,Oak/gum/cypress|8.5.3,8477,60.75157,4.54572,1.07797 +cell,Oak/gum/cypress|8.5.4,151,62.41314,2.57106,1.10745 +cell,Oak/gum/cypress|9.2.3,3,60.93614,0.89420,1.08125 +cell,Oak/gum/cypress|9.2.4,71,58.41496,3.65451,1.03651 +cell,Oak/gum/cypress|9.4.2,59,44.23812,3.63103,0.78496 +cell,Oak/gum/cypress|9.4.3,4,38.66191,0.91543,0.68601 +cell,Oak/gum/cypress|9.4.5,63,47.54923,2.31377,0.84371 +cell,Oak/gum/cypress|9.4.6,16,44.41326,2.27637,0.78806 +cell,Oak/gum/cypress|9.4.7,16,48.61744,2.23461,0.86266 +cell,Oak/gum/cypress|9.5.1,228,60.15487,2.73829,1.06738 +cell,Oak/gum/cypress|9.6.1,9,45.35283,3.33561,0.80474 +cell,Oak/hickory|0.0.0,47,55.00561,4.77701,0.97601 +cell,Oak/hickory|10.2.4,21,38.20611,1.29653,0.67793 +cell,Oak/hickory|13.1.1,1,42.47259,0.00000,0.75363 +cell,Oak/hickory|15.4.1,2,56.79060,0.00000,1.00769 +cell,Oak/hickory|5.2.1,5380,51.53404,5.00067,0.91442 +cell,Oak/hickory|5.2.2,39,44.01392,1.40143,0.78098 +cell,Oak/hickory|5.3.1,962,61.25985,3.25746,1.08699 +cell,Oak/hickory|5.3.3,1086,62.60782,1.49509,1.11091 +cell,Oak/hickory|6.2.10,60,47.05613,2.27672,0.83496 +cell,Oak/hickory|8.1.1,190,59.13092,3.28579,1.04921 +cell,Oak/hickory|8.1.10,374,64.09858,2.23281,1.13736 +cell,Oak/hickory|8.1.2,2,61.59996,2.93542,1.09302 +cell,Oak/hickory|8.1.3,542,61.11558,2.53327,1.08443 +cell,Oak/hickory|8.1.4,3484,56.22869,4.45259,0.99772 +cell,Oak/hickory|8.1.5,3795,62.92584,1.88595,1.11655 +cell,Oak/hickory|8.1.6,1526,61.71383,3.17086,1.09504 +cell,Oak/hickory|8.1.7,1421,63.33456,2.68653,1.12380 +cell,Oak/hickory|8.1.8,119,57.23417,3.13618,1.01556 +cell,Oak/hickory|8.2.1,575,62.64657,3.88203,1.11160 +cell,Oak/hickory|8.2.2,321,59.35879,2.19805,1.05326 +cell,Oak/hickory|8.2.3,452,65.23706,2.45343,1.15756 +cell,Oak/hickory|8.2.4,1069,62.46814,1.83964,1.10843 +cell,Oak/hickory|8.3.1,1367,61.68048,2.14141,1.09445 +cell,Oak/hickory|8.3.2,4140,61.05673,2.63943,1.08339 +cell,Oak/hickory|8.3.3,8066,58.80400,3.35014,1.04341 +cell,Oak/hickory|8.3.4,14619,57.98586,2.88175,1.02890 +cell,Oak/hickory|8.3.5,13270,57.68921,2.74824,1.02363 +cell,Oak/hickory|8.3.6,2060,56.39450,2.53547,1.00066 +cell,Oak/hickory|8.3.7,4624,56.27926,3.46130,0.99861 +cell,Oak/hickory|8.3.8,1018,49.98594,3.20104,0.88695 +cell,Oak/hickory|8.4.1,9243,60.79355,2.62668,1.07872 +cell,Oak/hickory|8.4.2,4481,62.68867,2.01801,1.11234 +cell,Oak/hickory|8.4.3,4104,61.30141,1.89654,1.08773 +cell,Oak/hickory|8.4.4,6381,61.59333,2.71220,1.09291 +cell,Oak/hickory|8.4.5,13416,57.93416,2.95819,1.02798 +cell,Oak/hickory|8.4.6,2752,53.86613,2.08391,0.95580 +cell,Oak/hickory|8.4.7,1776,52.59199,2.73384,0.93319 +cell,Oak/hickory|8.4.8,2065,54.20329,2.38257,0.96178 +cell,Oak/hickory|8.4.9,3759,58.84278,1.94564,1.04410 +cell,Oak/hickory|8.5.1,2042,60.31152,2.52998,1.07016 +cell,Oak/hickory|8.5.2,466,55.64963,2.28178,0.98744 +cell,Oak/hickory|8.5.3,1087,61.29305,4.63954,1.08758 +cell,Oak/hickory|8.5.4,242,62.01653,2.67394,1.10042 +cell,Oak/hickory|9.2.1,96,46.61159,4.38444,0.82707 +cell,Oak/hickory|9.2.2,226,45.45276,2.46846,0.80651 +cell,Oak/hickory|9.2.3,975,63.20272,3.66142,1.12146 +cell,Oak/hickory|9.2.4,2102,60.53377,3.52232,1.07411 +cell,Oak/hickory|9.3.1,58,55.23157,2.63531,0.98002 +cell,Oak/hickory|9.3.3,101,43.65145,2.60192,0.77455 +cell,Oak/hickory|9.3.4,2,53.85487,0.00000,0.95560 +cell,Oak/hickory|9.4.1,5,41.86736,0.35256,0.74289 +cell,Oak/hickory|9.4.2,216,48.09871,5.51763,0.85346 +cell,Oak/hickory|9.4.4,179,55.19349,2.21178,0.97935 +cell,Oak/hickory|9.4.5,1268,49.26085,3.43366,0.87408 +cell,Oak/hickory|9.4.6,291,43.58495,2.47994,0.77337 +cell,Oak/hickory|9.4.7,53,51.55463,4.87055,0.91478 +cell,Oak/hickory|9.5.1,93,58.76533,4.86503,1.04273 +cell,Oak/hickory|9.6.1,114,43.36374,1.63888,0.76944 +cell,Oak/pine|0.0.0,15,52.85898,4.03511,0.93792 +cell,Oak/pine|15.4.1,40,50.01985,3.74134,0.88755 +cell,Oak/pine|5.2.1,1426,48.99580,4.42115,0.86938 +cell,Oak/pine|5.2.2,21,43.47128,0.67048,0.77135 +cell,Oak/pine|5.3.1,519,58.42846,3.55279,1.03675 +cell,Oak/pine|5.3.3,93,62.92312,1.74168,1.11650 +cell,Oak/pine|8.1.1,103,58.79998,3.07068,1.04334 +cell,Oak/pine|8.1.10,3,62.75136,0.93688,1.11345 +cell,Oak/pine|8.1.3,157,61.56752,2.52568,1.09245 +cell,Oak/pine|8.1.4,423,56.58106,3.80931,1.00397 +cell,Oak/pine|8.1.5,93,62.34993,1.85644,1.10633 +cell,Oak/pine|8.1.6,78,60.92472,2.51211,1.08104 +cell,Oak/pine|8.1.7,456,61.76452,2.39733,1.09594 +cell,Oak/pine|8.1.8,192,56.33818,4.01821,0.99966 +cell,Oak/pine|8.2.1,27,61.41278,3.86836,1.08970 +cell,Oak/pine|8.2.2,21,58.00856,1.64241,1.02930 +cell,Oak/pine|8.2.3,3,65.47065,1.79703,1.16171 +cell,Oak/pine|8.2.4,65,62.30057,1.63623,1.10546 +cell,Oak/pine|8.3.1,132,60.90810,1.94472,1.08075 +cell,Oak/pine|8.3.2,203,59.51062,2.28119,1.05595 +cell,Oak/pine|8.3.3,973,58.27764,2.65100,1.03407 +cell,Oak/pine|8.3.4,6712,57.20509,2.98763,1.01504 +cell,Oak/pine|8.3.5,12083,57.64510,2.58931,1.02285 +cell,Oak/pine|8.3.6,738,57.59158,2.60954,1.02190 +cell,Oak/pine|8.3.7,5171,57.16631,3.45197,1.01435 +cell,Oak/pine|8.3.8,146,50.77103,3.38884,0.90088 +cell,Oak/pine|8.4.1,1963,59.34200,2.70704,1.05296 +cell,Oak/pine|8.4.2,179,61.86445,1.77697,1.09772 +cell,Oak/pine|8.4.3,310,60.71419,1.61757,1.07731 +cell,Oak/pine|8.4.4,1559,59.89626,2.39215,1.06279 +cell,Oak/pine|8.4.5,1534,57.68341,2.93462,1.02353 +cell,Oak/pine|8.4.6,260,53.29659,2.05030,0.94569 +cell,Oak/pine|8.4.7,609,52.90714,2.34975,0.93878 +cell,Oak/pine|8.4.8,1403,53.92565,2.28277,0.95685 +cell,Oak/pine|8.4.9,1056,57.89135,2.14940,1.02722 +cell,Oak/pine|8.5.1,2694,60.37057,2.42440,1.07121 +cell,Oak/pine|8.5.2,77,55.90750,3.01986,0.99202 +cell,Oak/pine|8.5.3,3198,60.69391,4.79794,1.07695 +cell,Oak/pine|8.5.4,208,61.52873,2.90249,1.09176 +cell,Oak/pine|9.2.1,3,56.79615,2.09524,1.00779 +cell,Oak/pine|9.2.3,96,60.62605,3.88262,1.07574 +cell,Oak/pine|9.2.4,120,60.43493,2.46947,1.07235 +cell,Oak/pine|9.3.1,15,55.70965,0.68978,0.98851 +cell,Oak/pine|9.3.3,22,47.24712,1.32976,0.83835 +cell,Oak/pine|9.3.4,11,46.72333,3.57246,0.82905 +cell,Oak/pine|9.4.1,1,41.63380,0.00000,0.73875 +cell,Oak/pine|9.4.2,106,49.06207,5.59672,0.87055 +cell,Oak/pine|9.4.3,4,47.66089,1.37025,0.84569 +cell,Oak/pine|9.4.4,42,56.33201,3.11571,0.99955 +cell,Oak/pine|9.4.5,56,49.22468,1.71679,0.87344 +cell,Oak/pine|9.4.7,35,51.44989,4.11521,0.91292 +cell,Oak/pine|9.5.1,90,61.07073,2.61034,1.08363 +cell,Other eastern softwoods|0.0.0,3,55.14133,0.24881,0.97842 +cell,Other eastern softwoods|5.3.1,7,63.91659,2.07010,1.13413 +cell,Other eastern softwoods|8.1.1,15,57.52665,1.03184,1.02075 +cell,Other eastern softwoods|8.1.3,3,58.51705,4.01954,1.03832 +cell,Other eastern softwoods|8.1.4,15,56.08471,4.62834,0.99516 +cell,Other eastern softwoods|8.1.5,27,63.09283,2.24117,1.11951 +cell,Other eastern softwoods|8.1.6,3,61.95132,0.66665,1.09926 +cell,Other eastern softwoods|8.1.7,11,64.32343,2.15777,1.14135 +cell,Other eastern softwoods|8.2.1,17,59.64697,3.16044,1.05837 +cell,Other eastern softwoods|8.2.2,2,60.46751,0.00000,1.07293 +cell,Other eastern softwoods|8.2.4,13,61.65572,0.79658,1.09401 +cell,Other eastern softwoods|8.3.1,61,61.65718,1.96358,1.09404 +cell,Other eastern softwoods|8.3.2,48,59.35100,1.72769,1.05312 +cell,Other eastern softwoods|8.3.3,460,57.54687,2.20073,1.02111 +cell,Other eastern softwoods|8.3.4,146,56.96011,3.18830,1.01070 +cell,Other eastern softwoods|8.3.5,138,57.89025,2.06922,1.02720 +cell,Other eastern softwoods|8.3.6,7,56.69431,1.25644,1.00598 +cell,Other eastern softwoods|8.3.7,34,52.62254,3.58335,0.93373 +cell,Other eastern softwoods|8.3.8,55,51.08752,2.82632,0.90649 +cell,Other eastern softwoods|8.4.1,127,59.39658,1.70486,1.05393 +cell,Other eastern softwoods|8.4.2,5,61.05540,0.89349,1.08336 +cell,Other eastern softwoods|8.4.3,4,60.64240,1.38358,1.07603 +cell,Other eastern softwoods|8.4.4,4,60.29754,0.00000,1.06991 +cell,Other eastern softwoods|8.4.5,435,57.33093,2.73896,1.01727 +cell,Other eastern softwoods|8.4.6,35,53.29918,2.01083,0.94574 +cell,Other eastern softwoods|8.4.7,119,53.42861,2.09018,0.94803 +cell,Other eastern softwoods|8.4.8,25,53.85750,1.83014,0.95564 +cell,Other eastern softwoods|8.4.9,15,58.57858,0.99535,1.03941 +cell,Other eastern softwoods|8.5.1,4,57.16227,2.35099,1.01428 +cell,Other eastern softwoods|8.5.3,10,60.16224,3.92287,1.06751 +cell,Other eastern softwoods|8.5.4,5,61.99187,3.14656,1.09998 +cell,Other eastern softwoods|9.2.1,1,51.57730,0.00000,0.91518 +cell,Other eastern softwoods|9.2.3,56,61.30611,3.25158,1.08781 +cell,Other eastern softwoods|9.2.4,82,59.62363,1.91306,1.05796 +cell,Other eastern softwoods|9.3.1,30,54.89399,2.00995,0.97403 +cell,Other eastern softwoods|9.3.3,27,45.80371,4.28488,0.81274 +cell,Other eastern softwoods|9.3.4,32,46.86436,3.50608,0.83156 +cell,Other eastern softwoods|9.4.1,2,42.88493,0.00000,0.76095 +cell,Other eastern softwoods|9.4.2,178,46.85918,5.37639,0.83147 +cell,Other eastern softwoods|9.4.3,6,46.19363,0.55864,0.81966 +cell,Other eastern softwoods|9.4.4,20,55.58737,1.87695,0.98634 +cell,Other eastern softwoods|9.4.5,44,48.86836,2.13128,0.86712 +cell,Other eastern softwoods|9.4.7,18,49.04736,1.64578,0.87029 +cell,Other eastern softwoods|9.5.1,1,57.14914,0.00000,1.01405 +cell,Other western softwoods|10.1.2,17,41.26904,3.69219,0.73227 +cell,Other western softwoods|10.1.3,474,38.89971,6.09550,0.69023 +cell,Other western softwoods|10.1.4,74,42.60099,2.16623,0.75591 +cell,Other western softwoods|10.1.5,75,44.97276,3.88483,0.79799 +cell,Other western softwoods|10.1.6,4,44.95348,1.08108,0.79765 +cell,Other western softwoods|10.1.8,2,40.83834,0.00000,0.72463 +cell,Other western softwoods|11.1.1,20,57.27509,10.99505,1.01628 +cell,Other western softwoods|11.1.3,8,51.28028,2.51671,0.90991 +cell,Other western softwoods|12.1.1,5,47.51625,2.78818,0.84312 +cell,Other western softwoods|13.1.1,44,49.13173,5.38468,0.87179 +cell,Other western softwoods|6.2.10,686,44.12574,2.49370,0.78296 +cell,Other western softwoods|6.2.11,38,54.63760,6.68459,0.96948 +cell,Other western softwoods|6.2.12,198,54.54115,4.89860,0.96777 +cell,Other western softwoods|6.2.13,36,45.59572,2.91731,0.80905 +cell,Other western softwoods|6.2.14,167,46.33226,2.60629,0.82212 +cell,Other western softwoods|6.2.15,126,44.19468,2.16160,0.78419 +cell,Other western softwoods|6.2.3,8,53.76820,5.39615,0.95406 +cell,Other western softwoods|6.2.4,49,47.69401,3.04381,0.84628 +cell,Other western softwoods|6.2.5,61,47.50828,3.79772,0.84298 +cell,Other western softwoods|6.2.7,14,55.97520,7.54887,0.99322 +cell,Other western softwoods|6.2.8,682,43.86280,5.33209,0.77830 +cell,Other western softwoods|6.2.9,1057,41.98155,7.26456,0.74492 +cell,Other western softwoods|7.1.8,16,74.01124,4.45500,1.31325 +cell,Other western softwoods|7.1.9,1,74.83640,0.00000,1.32789 +cell,Other western softwoods|9.3.1,2,46.72697,0.00000,0.82912 +cell,Other western softwoods|9.3.3,13,45.64838,2.53360,0.80998 +cell,Pinyon/juniper|10.1.3,402,40.29221,3.76274,0.71494 +cell,Pinyon/juniper|10.1.4,383,40.52314,2.32298,0.71904 +cell,Pinyon/juniper|10.1.5,5599,39.33254,3.92121,0.69791 +cell,Pinyon/juniper|10.1.6,4855,41.82444,2.62358,0.74213 +cell,Pinyon/juniper|10.1.7,4741,40.85349,2.82928,0.72490 +cell,Pinyon/juniper|10.1.8,10,40.61983,6.53014,0.72075 +cell,Pinyon/juniper|10.2.1,470,39.03171,3.13901,0.69258 +cell,Pinyon/juniper|10.2.2,69,39.59256,1.51998,0.70253 +cell,Pinyon/juniper|10.2.4,330,38.25827,2.34980,0.67885 +cell,Pinyon/juniper|11.1.1,30,44.32846,3.63084,0.78656 +cell,Pinyon/juniper|11.1.3,163,43.95189,2.92709,0.77988 +cell,Pinyon/juniper|12.1.1,340,41.54035,2.67906,0.73709 +cell,Pinyon/juniper|13.1.1,5305,45.07827,3.52186,0.79986 +cell,Pinyon/juniper|6.2.10,227,43.22724,3.30018,0.76702 +cell,Pinyon/juniper|6.2.11,10,48.43252,4.53545,0.85938 +cell,Pinyon/juniper|6.2.12,80,43.85395,4.02755,0.77814 +cell,Pinyon/juniper|6.2.13,742,43.63484,3.44044,0.77425 +cell,Pinyon/juniper|6.2.14,1576,45.73089,2.72095,0.81144 +cell,Pinyon/juniper|6.2.15,1,43.60531,0.00000,0.77373 +cell,Pinyon/juniper|6.2.3,5,46.72425,4.35394,0.82907 +cell,Pinyon/juniper|6.2.4,1,45.94528,0.00000,0.81525 +cell,Pinyon/juniper|6.2.8,77,42.51120,4.43590,0.75431 +cell,Pinyon/juniper|6.2.9,2,44.15064,6.14892,0.78340 +cell,Pinyon/juniper|8.4.5,18,54.63650,1.09194,0.96947 +cell,Pinyon/juniper|9.3.1,2,58.75334,0.00000,1.04251 +cell,Pinyon/juniper|9.3.3,454,42.33649,1.54215,0.75121 +cell,Pinyon/juniper|9.3.4,4,45.31533,0.00000,0.80407 +cell,Pinyon/juniper|9.4.1,54,39.75569,2.17190,0.70542 +cell,Pinyon/juniper|9.4.2,58,41.79973,2.21538,0.74169 +cell,Pinyon/juniper|9.4.3,1482,41.50634,2.49630,0.73648 +cell,Pinyon/juniper|9.4.5,276,47.00301,1.69516,0.83402 +cell,Pinyon/juniper|9.4.6,1250,42.88747,3.51968,0.76099 +cell,Pinyon/juniper|9.4.7,8,48.34219,1.63767,0.85778 +cell,Pinyon/juniper|9.6.1,15,42.97960,1.55154,0.76263 +cell,Ponderosa pine|10.1.2,196,50.87975,3.67567,0.90281 +cell,Ponderosa pine|10.1.3,17,43.33172,3.54125,0.76887 +cell,Ponderosa pine|10.1.4,21,43.85540,1.68468,0.77817 +cell,Ponderosa pine|10.1.5,26,46.06271,4.01861,0.81733 +cell,Ponderosa pine|10.1.6,112,46.72227,3.32543,0.82904 +cell,Ponderosa pine|10.1.7,156,46.00115,3.09084,0.81624 +cell,Ponderosa pine|10.1.8,20,53.81687,2.31642,0.95492 +cell,Ponderosa pine|10.2.1,9,41.93094,2.36953,0.74402 +cell,Ponderosa pine|11.1.1,54,60.75302,5.47199,1.07800 +cell,Ponderosa pine|11.1.3,26,51.00465,5.50346,0.90502 +cell,Ponderosa pine|12.1.1,9,50.28579,2.24140,0.89227 +cell,Ponderosa pine|13.1.1,2573,51.19975,4.21275,0.90848 +cell,Ponderosa pine|6.2.10,1679,45.53177,2.64792,0.80791 +cell,Ponderosa pine|6.2.11,227,59.80410,5.75688,1.06116 +cell,Ponderosa pine|6.2.12,468,56.77949,6.93013,1.00749 +cell,Ponderosa pine|6.2.13,152,45.62882,2.58334,0.80963 +cell,Ponderosa pine|6.2.14,1310,48.09393,3.04376,0.85337 +cell,Ponderosa pine|6.2.15,507,52.97714,3.27813,0.94002 +cell,Ponderosa pine|6.2.3,675,53.33289,3.42442,0.94633 +cell,Ponderosa pine|6.2.5,234,53.88236,3.73190,0.95608 +cell,Ponderosa pine|6.2.7,290,61.74264,6.36851,1.09556 +cell,Ponderosa pine|6.2.8,2573,52.46178,4.94710,0.93088 +cell,Ponderosa pine|6.2.9,2368,52.54354,3.77955,0.93233 +cell,Ponderosa pine|7.1.8,2,68.56133,0.89797,1.21655 +cell,Ponderosa pine|7.1.9,1,74.19289,0.00000,1.31647 +cell,Ponderosa pine|9.2.1,1,42.85944,0.00000,0.76049 +cell,Ponderosa pine|9.2.4,1,59.61845,0.00000,1.05786 +cell,Ponderosa pine|9.3.1,22,44.86443,1.54056,0.79607 +cell,Ponderosa pine|9.3.3,1010,44.00863,2.03047,0.78089 +cell,Ponderosa pine|9.3.4,24,43.56668,1.39792,0.77304 +cell,Ponderosa pine|9.4.1,305,41.63908,1.33011,0.73884 +cell,Ponderosa pine|9.4.3,62,46.81357,2.24681,0.83066 +cell,Redwood|11.1.1,14,81.14958,4.23492,1.43991 +cell,Redwood|6.2.11,1,69.67654,0.00000,1.23633 +cell,Redwood|6.2.12,1,59.41861,0.00000,1.05432 +cell,Redwood|7.1.8,260,77.21636,4.18451,1.37012 +cell,Spruce/fir|0.0.0,560,48.66994,2.57247,0.86360 +cell,Spruce/fir|5.2.1,16385,46.08928,2.66805,0.81780 +cell,Spruce/fir|5.2.2,5181,42.59626,1.25365,0.75582 +cell,Spruce/fir|5.3.1,4143,48.61542,3.48558,0.86263 +cell,Spruce/fir|5.3.3,18,62.64452,1.02235,1.11156 +cell,Spruce/fir|6.2.10,63,45.62015,1.33828,0.80948 +cell,Spruce/fir|8.1.1,85,56.56660,3.90387,1.00371 +cell,Spruce/fir|8.1.10,1,63.30663,0.00000,1.12331 +cell,Spruce/fir|8.1.3,21,61.21567,1.15351,1.08621 +cell,Spruce/fir|8.1.4,727,53.38988,4.02093,0.94735 +cell,Spruce/fir|8.1.5,17,61.94961,1.25824,1.09923 +cell,Spruce/fir|8.1.6,31,59.34571,2.17987,1.05302 +cell,Spruce/fir|8.1.7,12,63.41087,2.94436,1.12516 +cell,Spruce/fir|8.1.8,3252,50.52581,3.19492,0.89653 +cell,Spruce/fir|8.2.1,74,57.54075,1.98570,1.02100 +cell,Spruce/fir|8.2.2,1,56.97423,0.00000,1.01095 +cell,Spruce/fir|8.2.4,2,60.84766,0.00000,1.07968 +cell,Spruce/fir|8.4.1,34,62.28084,1.90479,1.10511 +cell,Spruce/fir|8.4.2,47,62.34684,1.73516,1.10628 +cell,Spruce/fir|8.4.3,1,61.02212,0.00000,1.08277 +cell,Spruce/fir|8.4.4,29,63.80515,2.28711,1.13215 +cell,Spruce/fir|9.2.2,12,43.50367,0.65922,0.77193 +cell,Spruce/fir|9.2.3,3,65.55109,0.00000,1.16313 +cell,Tanoak/laurel|11.1.1,59,66.70785,10.27961,1.18366 +cell,Tanoak/laurel|6.2.11,504,67.28389,6.46555,1.19388 +cell,Tanoak/laurel|6.2.12,26,67.82783,4.19074,1.20353 +cell,Tanoak/laurel|6.2.7,39,68.18965,5.15800,1.20995 +cell,Tanoak/laurel|6.2.8,4,57.72932,1.18012,1.02434 +cell,Tanoak/laurel|7.1.8,560,75.98842,4.64600,1.34833 +cell,Tanoak/laurel|7.1.9,2,72.77439,0.48866,1.29130 +cell,Tropical hardwoods|15.4.1,128,52.14246,4.08933,0.92521 +cell,Tropical hardwoods|8.3.5,1,59.56656,0.00000,1.05694 +cell,Tropical hardwoods|8.5.3,391,60.66538,5.98818,1.07644 +cell,Western larch|10.1.2,5,53.29014,3.61994,0.94558 +cell,Western larch|6.2.10,27,50.89385,3.07048,0.90306 +cell,Western larch|6.2.15,16,52.13694,4.45880,0.92511 +cell,Western larch|6.2.3,654,54.91713,3.33639,0.97444 +cell,Western larch|6.2.4,90,53.28051,3.09332,0.94540 +cell,Western larch|6.2.5,27,51.60024,2.13124,0.91559 +cell,Western larch|6.2.7,14,60.58608,1.68038,1.07503 +cell,Western larch|6.2.8,25,58.53356,3.61588,1.03861 +cell,Western larch|6.2.9,192,55.63234,2.26385,0.98714 +cell,Western oak|10.1.2,11,46.11970,1.71085,0.81834 +cell,Western oak|10.2.1,3,42.88394,1.13771,0.76093 +cell,Western oak|11.1.1,1456,53.09746,7.25318,0.94216 +cell,Western oak|11.1.2,51,50.27283,2.93846,0.89204 +cell,Western oak|11.1.3,81,46.49228,3.44442,0.82495 +cell,Western oak|6.2.11,552,58.67441,7.55243,1.04111 +cell,Western oak|6.2.12,222,62.89870,6.50349,1.11607 +cell,Western oak|6.2.7,13,65.94495,4.50719,1.17012 +cell,Western oak|6.2.8,187,52.70325,5.13514,0.93516 +cell,Western oak|6.2.9,2,50.03386,0.00000,0.88780 +cell,Western oak|7.1.7,1,67.43864,0.00000,1.19663 +cell,Western oak|7.1.8,78,72.92175,5.46919,1.29392 +cell,Western oak|7.1.9,35,69.53399,3.85881,1.23380 +cell,Western white pine|6.2.11,20,57.66768,3.63568,1.02325 +cell,Western white pine|6.2.12,44,58.65118,3.47030,1.04070 +cell,Western white pine|6.2.3,31,58.26946,1.82010,1.03393 +cell,Western white pine|6.2.5,2,61.59813,0.00000,1.09299 +cell,Western white pine|6.2.7,3,66.20949,1.74176,1.17482 +cell,Western white pine|6.2.8,2,54.48923,0.00000,0.96685 +cell,Western white pine|6.2.9,2,54.86349,0.00000,0.97349 +cell,Western white pine|7.1.8,10,66.99062,2.47792,1.18868 +cell,White/red/jack pine|0.0.0,99,52.35994,4.73132,0.92907 +cell,White/red/jack pine|5.2.1,8235,48.05831,4.00147,0.85274 +cell,White/red/jack pine|5.2.2,327,43.21721,0.96544,0.76684 +cell,White/red/jack pine|5.2.3,1,47.73013,0.00000,0.84692 +cell,White/red/jack pine|5.3.1,1715,57.28150,3.81061,1.01640 +cell,White/red/jack pine|5.3.3,177,62.12608,1.96353,1.10236 +cell,White/red/jack pine|8.1.1,167,58.65223,3.62747,1.04072 +cell,White/red/jack pine|8.1.10,31,63.58034,1.63973,1.12816 +cell,White/red/jack pine|8.1.3,365,61.10317,2.39098,1.08421 +cell,White/red/jack pine|8.1.4,1720,55.37844,4.61646,0.98263 +cell,White/red/jack pine|8.1.5,238,62.06978,1.99831,1.10136 +cell,White/red/jack pine|8.1.6,207,61.09763,2.79508,1.08411 +cell,White/red/jack pine|8.1.7,908,61.96769,2.56408,1.09955 +cell,White/red/jack pine|8.1.8,801,54.13847,3.84206,0.96063 +cell,White/red/jack pine|8.2.1,84,61.65871,3.98735,1.09407 +cell,White/red/jack pine|8.2.2,11,57.43424,2.24810,1.01911 +cell,White/red/jack pine|8.2.3,20,64.88549,1.50181,1.15132 +cell,White/red/jack pine|8.2.4,24,61.99988,1.41463,1.10012 +cell,White/red/jack pine|8.3.1,19,61.50361,2.36750,1.09131 +cell,White/red/jack pine|8.3.2,37,62.48419,1.27395,1.10871 +cell,White/red/jack pine|8.3.3,34,61.53691,2.45257,1.09191 +cell,White/red/jack pine|8.3.4,91,60.06880,1.51754,1.06586 +cell,White/red/jack pine|8.4.1,272,61.88842,2.02708,1.09814 +cell,White/red/jack pine|8.4.2,116,63.12056,2.12780,1.12001 +cell,White/red/jack pine|8.4.3,121,62.59593,1.91401,1.11070 +cell,White/red/jack pine|8.4.4,535,60.59760,2.66455,1.07524 +cell,White/red/jack pine|8.4.9,48,59.12284,1.05662,1.04907 +cell,White/red/jack pine|8.5.1,16,62.06356,1.63671,1.10125 +cell,White/red/jack pine|8.5.4,28,62.75483,2.21510,1.11352 +cell,White/red/jack pine|9.2.1,8,52.25385,2.30585,0.92719 +cell,White/red/jack pine|9.2.3,11,62.23697,3.03986,1.10433 +cell,White/red/jack pine|9.3.4,3,50.52232,0.00000,0.89646 +cell,White/red/jack pine|9.4.4,3,55.50642,0.00000,0.98490 +cell,Woodland hardwoods|0.0.0,11,52.10069,5.88922,0.92447 +cell,Woodland hardwoods|10.1.2,10,50.50024,4.46645,0.89607 +cell,Woodland hardwoods|10.1.3,2,41.89111,6.00089,0.74331 +cell,Woodland hardwoods|10.1.5,4,46.96523,3.65386,0.83335 +cell,Woodland hardwoods|10.1.6,1,42.30879,0.00000,0.75072 +cell,Woodland hardwoods|10.1.8,2,45.54548,3.85558,0.80815 +cell,Woodland hardwoods|10.2.1,1,36.23093,0.00000,0.64288 +cell,Woodland hardwoods|10.2.2,6,36.43318,1.03730,0.64647 +cell,Woodland hardwoods|10.2.4,5,37.38875,0.97636,0.66342 +cell,Woodland hardwoods|11.1.1,63,55.25925,7.30806,0.98052 +cell,Woodland hardwoods|11.1.2,4,45.11223,5.16453,0.80047 +cell,Woodland hardwoods|11.1.3,5,55.32999,12.84836,0.98177 +cell,Woodland hardwoods|12.1.1,6,39.27801,1.37411,0.69695 +cell,Woodland hardwoods|13.1.1,8,46.01405,2.53610,0.81647 +cell,Woodland hardwoods|15.4.1,2,52.07487,0.00000,0.92401 +cell,Woodland hardwoods|5.2.1,378,46.57679,2.97369,0.82645 +cell,Woodland hardwoods|5.2.2,47,43.16183,1.01407,0.76586 +cell,Woodland hardwoods|5.3.1,135,55.61265,6.56295,0.98679 +cell,Woodland hardwoods|5.3.3,211,62.00828,1.48210,1.10027 +cell,Woodland hardwoods|6.2.10,44,44.75605,2.69348,0.79415 +cell,Woodland hardwoods|6.2.11,240,61.83340,5.88499,1.09717 +cell,Woodland hardwoods|6.2.12,34,60.84437,5.93360,1.07962 +cell,Woodland hardwoods|6.2.14,7,48.30026,3.94300,0.85704 +cell,Woodland hardwoods|6.2.15,9,53.63422,5.81701,0.95168 +cell,Woodland hardwoods|6.2.3,29,52.57016,4.64542,0.93280 +cell,Woodland hardwoods|6.2.4,2,48.96305,6.28354,0.86880 +cell,Woodland hardwoods|6.2.5,16,53.69058,6.45947,0.95268 +cell,Woodland hardwoods|6.2.7,43,68.97466,3.04788,1.22388 +cell,Woodland hardwoods|6.2.8,46,53.52037,7.04231,0.94966 +cell,Woodland hardwoods|6.2.9,38,55.10534,4.76429,0.97778 +cell,Woodland hardwoods|7.1.7,5,70.01321,1.82238,1.24231 +cell,Woodland hardwoods|7.1.8,57,74.85075,4.62022,1.32815 +cell,Woodland hardwoods|7.1.9,5,71.06360,1.61090,1.26095 +cell,Woodland hardwoods|8.1.1,49,59.91656,3.68892,1.06315 +cell,Woodland hardwoods|8.1.10,14,64.77624,2.13507,1.14938 +cell,Woodland hardwoods|8.1.3,104,61.02907,2.22761,1.08289 +cell,Woodland hardwoods|8.1.4,46,51.34514,5.19059,0.91106 +cell,Woodland hardwoods|8.1.5,38,62.24629,1.93991,1.10449 +cell,Woodland hardwoods|8.1.6,13,61.10268,2.65728,1.08420 +cell,Woodland hardwoods|8.1.7,77,63.97128,2.37754,1.13510 +cell,Woodland hardwoods|8.1.8,66,50.17323,2.95165,0.89027 +cell,Woodland hardwoods|8.2.1,8,61.93934,4.00755,1.09905 +cell,Woodland hardwoods|8.2.2,4,59.43390,1.02212,1.05459 +cell,Woodland hardwoods|8.2.3,4,67.53081,3.33811,1.19826 +cell,Woodland hardwoods|8.2.4,9,62.08558,1.32080,1.10164 +cell,Woodland hardwoods|8.3.1,26,62.22193,1.77241,1.10406 +cell,Woodland hardwoods|8.3.2,10,62.58069,2.21218,1.11043 +cell,Woodland hardwoods|8.3.3,20,57.85652,2.90376,1.02660 +cell,Woodland hardwoods|8.3.4,62,56.67983,2.63038,1.00572 +cell,Woodland hardwoods|8.3.5,116,57.17882,2.61613,1.01458 +cell,Woodland hardwoods|8.3.6,6,54.29312,2.37443,0.96337 +cell,Woodland hardwoods|8.3.7,16,56.09118,3.35925,0.99528 +cell,Woodland hardwoods|8.4.1,227,62.28860,1.77753,1.10524 +cell,Woodland hardwoods|8.4.2,74,63.07001,2.45995,1.11911 +cell,Woodland hardwoods|8.4.3,29,62.21668,1.53912,1.10397 +cell,Woodland hardwoods|8.4.4,178,63.08361,2.90279,1.11935 +cell,Woodland hardwoods|8.4.5,16,57.09872,2.13495,1.01315 +cell,Woodland hardwoods|8.4.6,2,54.00881,0.00000,0.95833 +cell,Woodland hardwoods|8.4.7,1,52.96638,0.00000,0.93983 +cell,Woodland hardwoods|8.4.8,11,54.60179,2.05591,0.96885 +cell,Woodland hardwoods|8.4.9,11,57.55596,2.48307,1.02127 +cell,Woodland hardwoods|8.5.1,18,59.82662,1.81986,1.06156 +cell,Woodland hardwoods|8.5.2,3,56.20943,0.62294,0.99738 +cell,Woodland hardwoods|8.5.3,53,58.18876,3.54537,1.03250 +cell,Woodland hardwoods|9.2.1,9,44.92380,1.27085,0.79712 +cell,Woodland hardwoods|9.2.2,7,43.53981,1.67330,0.77257 +cell,Woodland hardwoods|9.2.3,17,59.44955,3.77158,1.05487 +cell,Woodland hardwoods|9.2.4,10,60.69254,5.29962,1.07692 +cell,Woodland hardwoods|9.3.1,3,44.25460,0.81873,0.78525 +cell,Woodland hardwoods|9.3.3,61,42.40555,1.96329,0.75244 +cell,Woodland hardwoods|9.3.4,1,51.46993,0.00000,0.91328 +cell,Woodland hardwoods|9.4.1,23,41.25515,1.68974,0.73203 +cell,Woodland hardwoods|9.4.2,47,47.59760,5.25415,0.84457 +cell,Woodland hardwoods|9.4.3,5,42.86033,6.24326,0.76051 +cell,Woodland hardwoods|9.4.4,2,56.91802,0.00000,1.00995 +cell,Woodland hardwoods|9.4.5,50,47.23141,2.17233,0.83807 +cell,Woodland hardwoods|9.4.6,56,45.40926,2.67340,0.80574 +cell,Woodland hardwoods|9.4.7,3,49.40770,0.02459,0.87669 +cell,Woodland hardwoods|9.5.1,8,57.29296,5.02140,1.01660 +cell,Woodland hardwoods|9.6.1,1,41.07429,0.00000,0.72882 +state_prov,1|8.3.3,924,58.81221,1.29705,1.04356 +state_prov,1|8.3.4,3000,56.59112,1.70123,1.00415 +state_prov,1|8.3.5,16833,57.70243,2.27077,1.02387 +state_prov,1|8.4.1,2192,56.66923,1.62488,1.00553 +state_prov,1|8.4.9,3708,57.19826,2.07141,1.01492 +state_prov,1|8.5.3,300,59.15855,3.65718,1.04970 +state_prov,10|8.3.1,8,61.99652,0.47028,1.10006 +state_prov,10|8.5.1,419,62.07100,1.81837,1.10138 +state_prov,12|15.4.1,696,50.95158,3.92903,0.90408 +state_prov,12|8.3.5,6646,58.88281,2.98413,1.04481 +state_prov,12|8.5.3,19089,59.84025,4.82050,1.06180 +state_prov,13|8.3.4,12499,56.50952,2.67092,1.00270 +state_prov,13|8.3.5,17974,56.85703,2.48998,1.00887 +state_prov,13|8.4.1,1699,58.06947,1.67690,1.03038 +state_prov,13|8.4.4,2312,59.81227,1.84488,1.06130 +state_prov,13|8.4.9,121,60.25018,1.07960,1.06907 +state_prov,13|8.5.1,10,59.00509,0.73313,1.04698 +state_prov,13|8.5.3,10056,61.70830,3.99677,1.09495 +state_prov,16|10.1.2,74,56.30980,2.38520,0.99916 +state_prov,16|10.1.3,587,47.07763,4.25827,0.83534 +state_prov,16|10.1.4,7,47.15753,0.33697,0.83676 +state_prov,16|10.1.5,9,45.90681,2.41049,0.81457 +state_prov,16|10.1.8,92,49.34544,5.72224,0.87558 +state_prov,16|6.2.10,1049,46.64083,3.96960,0.82759 +state_prov,16|6.2.13,141,50.36706,1.78846,0.89371 +state_prov,16|6.2.15,4550,50.17641,5.20973,0.89033 +state_prov,16|6.2.3,2905,59.02643,3.32774,1.04736 +state_prov,16|6.2.9,289,54.04630,2.69076,0.95899 +state_prov,17|0.0.0,5,66.07772,2.07608,1.17248 +state_prov,17|8.1.5,67,65.69553,1.69800,1.16570 +state_prov,17|8.2.1,60,67.71573,2.69778,1.20154 +state_prov,17|8.2.3,715,65.08497,2.47094,1.15486 +state_prov,17|8.3.2,3485,61.73164,2.54780,1.09536 +state_prov,17|8.3.3,773,62.26605,1.80477,1.10484 +state_prov,17|8.5.2,9,57.73737,1.93758,1.02449 +state_prov,18|8.1.6,449,64.13175,1.98234,1.13795 +state_prov,18|8.2.2,18,61.95278,0.75751,1.09928 +state_prov,18|8.2.3,146,64.75582,2.05130,1.14902 +state_prov,18|8.2.4,1751,62.56428,1.79393,1.11013 +state_prov,18|8.3.2,931,62.18774,2.05385,1.10345 +state_prov,18|8.3.3,2961,62.71345,1.86148,1.11278 +state_prov,19|8.1.5,372,64.72210,2.17331,1.14842 +state_prov,19|8.3.2,48,65.52726,2.29414,1.16271 +state_prov,19|9.2.3,1197,65.30898,2.61712,1.15884 +state_prov,19|9.2.4,488,64.32501,1.75696,1.14138 +state_prov,20|8.4.5,30,55.66315,0.60415,0.98768 +state_prov,20|9.2.3,401,58.50738,1.67002,1.03815 +state_prov,20|9.2.4,1525,58.96960,2.29468,1.04635 +state_prov,20|9.4.1,7,41.90052,2.70656,0.74348 +state_prov,20|9.4.2,493,54.36303,3.89345,0.96461 +state_prov,20|9.4.3,32,47.81692,2.08083,0.84846 +state_prov,20|9.4.4,605,56.04533,1.74984,0.99446 +state_prov,20|9.4.5,231,55.27240,1.34854,0.98075 +state_prov,21|8.3.2,913,57.12904,2.32362,1.01369 +state_prov,21|8.3.3,2956,58.15825,2.08535,1.03195 +state_prov,21|8.3.6,169,54.94822,2.03777,0.97500 +state_prov,21|8.4.2,1881,61.07244,1.52529,1.08366 +state_prov,21|8.4.3,1069,60.04494,1.36966,1.06543 +state_prov,21|8.4.9,640,59.24395,1.34844,1.05122 +state_prov,21|8.5.2,18,58.41827,2.49497,1.03657 +state_prov,22|8.3.5,645,59.74087,1.77247,1.06004 +state_prov,22|8.3.6,632,58.68877,2.25452,1.04137 +state_prov,22|8.3.7,9199,57.65040,2.49805,1.02294 +state_prov,22|8.5.2,3370,55.85897,3.10726,0.99116 +state_prov,22|8.5.3,451,58.35668,2.91838,1.03548 +state_prov,22|9.5.1,290,59.53341,2.65829,1.05636 +state_prov,23|5.3.1,7539,48.56098,3.82124,0.86166 +state_prov,23|8.1.7,494,60.87745,1.70964,1.08020 +state_prov,23|8.1.8,9604,51.75579,3.84916,0.91835 +state_prov,24|8.3.1,254,63.32840,2.05574,1.12369 +state_prov,24|8.3.5,327,62.94615,2.74706,1.11691 +state_prov,24|8.4.1,235,60.72858,1.98850,1.07756 +state_prov,24|8.4.2,130,63.44849,1.44908,1.12582 +state_prov,24|8.4.4,26,63.84791,1.46243,1.13291 +state_prov,24|8.5.1,637,62.94254,3.23420,1.11685 +state_prov,25|5.3.1,955,60.96752,2.07087,1.08180 +state_prov,25|8.1.7,1133,62.09895,2.30527,1.10188 +state_prov,25|8.5.4,94,62.00093,2.21667,1.10014 +state_prov,26|0.0.0,1384,50.55616,3.86878,0.89706 +state_prov,26|5.2.1,35760,49.10716,4.27331,0.87135 +state_prov,26|5.2.3,5,47.19566,0.38448,0.83744 +state_prov,26|8.1.1,7,47.55779,0.87776,0.84386 +state_prov,26|8.1.2,3,61.61818,2.07589,1.09335 +state_prov,26|8.1.4,853,58.48144,2.77971,1.03769 +state_prov,26|8.1.6,3766,61.11485,2.96565,1.08442 +state_prov,26|8.2.2,1254,57.74049,1.98050,1.02454 +state_prov,26|8.2.4,211,61.11537,1.56770,1.08443 +state_prov,27|0.0.0,179,45.40436,1.10981,0.80565 +state_prov,27|5.2.1,31206,45.34377,2.06573,0.80458 +state_prov,27|5.2.2,10408,42.85828,1.21505,0.76047 +state_prov,27|8.1.4,4415,52.75360,6.03007,0.93606 +state_prov,27|8.1.5,1604,63.01723,1.98649,1.11817 +state_prov,27|9.2.1,55,52.75129,3.00441,0.93601 +state_prov,27|9.2.2,1369,44.26799,2.27512,0.78549 +state_prov,27|9.2.3,525,61.93690,3.81700,1.09900 +state_prov,28|8.3.5,14537,58.45450,2.66573,1.03721 +state_prov,28|8.3.6,4349,57.47554,2.56178,1.01984 +state_prov,28|8.5.2,1225,55.36877,2.64510,0.98246 +state_prov,28|8.5.3,385,60.51061,2.61541,1.07369 +state_prov,29|8.3.2,1620,61.14488,1.82815,1.08495 +state_prov,29|8.3.6,40,59.30190,1.64940,1.05225 +state_prov,29|8.4.5,12397,59.19596,1.84522,1.05037 +state_prov,29|8.5.2,161,58.70061,2.19821,1.04158 +state_prov,29|9.2.3,241,60.66352,1.77487,1.07641 +state_prov,29|9.2.4,1875,61.02875,1.78635,1.08289 +state_prov,30|10.1.4,9,40.38504,3.52009,0.71659 +state_prov,30|6.2.10,4813,47.40740,2.47853,0.84119 +state_prov,30|6.2.15,574,48.94265,2.22251,0.86843 +state_prov,30|6.2.3,2813,53.64345,3.26080,0.95184 +state_prov,30|6.2.4,1674,50.64646,3.46259,0.89867 +state_prov,30|9.3.1,73,45.21402,3.05808,0.80227 +state_prov,30|9.3.3,1308,44.28669,2.35745,0.78582 +state_prov,31|9.2.3,349,60.19158,3.17985,1.06803 +state_prov,31|9.3.1,88,55.34971,1.51540,0.98212 +state_prov,31|9.3.3,91,46.21976,2.47290,0.82012 +state_prov,31|9.3.4,102,46.55125,3.57341,0.82600 +state_prov,31|9.4.1,293,41.96417,1.42298,0.74461 +state_prov,31|9.4.2,363,51.95614,6.18348,0.92191 +state_prov,32|10.1.3,284,40.77373,4.40984,0.72349 +state_prov,32|10.1.5,4443,38.94223,3.73950,0.69099 +state_prov,32|10.1.7,4,39.14205,2.16616,0.69453 +state_prov,32|10.2.1,201,39.47129,3.10544,0.70037 +state_prov,32|6.2.12,73,53.62469,4.41710,0.95151 +state_prov,33|5.3.1,4007,55.10117,4.15743,0.97771 +state_prov,33|8.1.7,660,59.86185,1.61901,1.06218 +state_prov,34|5.3.1,261,62.77693,1.84896,1.11391 +state_prov,34|8.1.7,2,71.55140,0.00000,1.26960 +state_prov,34|8.3.1,232,63.47542,2.48128,1.12630 +state_prov,34|8.4.1,143,63.36862,1.64691,1.12441 +state_prov,34|8.5.1,69,60.50778,3.44990,1.07364 +state_prov,34|8.5.4,1114,61.58226,2.17226,1.09271 +state_prov,35|10.1.6,231,42.84426,1.58393,0.76022 +state_prov,35|10.1.7,1710,42.05278,2.06081,0.74618 +state_prov,35|10.2.4,678,38.72618,1.88048,0.68715 +state_prov,35|12.1.1,43,40.31813,1.59394,0.71540 +state_prov,35|13.1.1,3615,46.37342,3.88681,0.82285 +state_prov,35|6.2.14,2405,48.57061,2.75552,0.86183 +state_prov,35|9.4.1,45,36.60707,1.54851,0.64955 +state_prov,35|9.4.3,975,42.24353,2.28188,0.74957 +state_prov,36|0.0.0,12,56.80344,2.44876,1.00792 +state_prov,36|5.3.1,5108,56.57287,4.30419,1.00382 +state_prov,36|5.3.3,363,62.50631,1.07733,1.10911 +state_prov,36|8.1.1,2303,59.38391,3.35305,1.05370 +state_prov,36|8.1.10,231,63.09848,1.59536,1.11961 +state_prov,36|8.1.3,3477,60.90442,2.36183,1.08068 +state_prov,36|8.1.7,552,64.21401,2.37322,1.13941 +state_prov,36|8.3.1,9,62.14379,3.16432,1.10267 +state_prov,36|8.4.1,189,63.07652,1.37777,1.11922 +state_prov,36|8.5.4,78,65.83012,2.76166,1.16808 +state_prov,37|8.3.4,9008,58.40808,3.09551,1.03639 +state_prov,37|8.3.5,5571,56.97852,2.34671,1.01102 +state_prov,37|8.4.4,3817,62.15290,2.83437,1.10284 +state_prov,37|8.5.1,9003,59.68283,2.19392,1.05901 +state_prov,38|9.2.1,377,44.04825,1.88655,0.78159 +state_prov,38|9.2.2,148,46.97929,2.47354,0.83360 +state_prov,38|9.3.1,27,42.74642,1.28508,0.75849 +state_prov,38|9.3.3,323,41.69433,1.18562,0.73982 +state_prov,39|0.0.0,5,58.82301,6.45682,1.04375 +state_prov,39|8.1.1,37,65.18151,3.61899,1.15657 +state_prov,39|8.1.10,901,64.19142,2.03179,1.13901 +state_prov,39|8.2.2,141,61.65690,1.91464,1.09403 +state_prov,39|8.2.4,778,62.66994,2.10387,1.11201 +state_prov,39|8.3.3,103,60.23435,1.55192,1.06879 +state_prov,39|8.4.3,2699,61.58756,1.70048,1.09280 +state_prov,4|10.1.6,243,40.73696,2.49100,0.72283 +state_prov,4|10.1.7,3220,40.53262,3.29470,0.71921 +state_prov,4|10.2.1,142,40.62810,2.54052,0.72090 +state_prov,4|10.2.2,317,38.38610,2.09755,0.68112 +state_prov,4|10.2.4,5,38.22704,0.45296,0.67830 +state_prov,4|12.1.1,1460,40.99490,3.69281,0.72741 +state_prov,4|13.1.1,6133,48.36681,5.31931,0.85822 +state_prov,40|8.3.7,687,53.17725,2.78942,0.94357 +state_prov,40|8.3.8,22,51.47372,2.26657,0.91335 +state_prov,40|8.4.5,760,52.54390,1.68461,0.93233 +state_prov,40|8.4.6,329,50.87845,0.84959,0.90278 +state_prov,40|8.4.7,1187,50.71110,1.79010,0.89981 +state_prov,40|8.4.8,2485,53.51974,2.05116,0.94965 +state_prov,40|9.2.4,269,51.51460,2.61169,0.91407 +state_prov,40|9.4.2,496,45.23870,3.65171,0.80271 +state_prov,40|9.4.3,39,39.50645,0.64619,0.70100 +state_prov,40|9.4.4,35,51.56693,2.13637,0.91500 +state_prov,40|9.4.5,1019,48.94431,1.80813,0.86846 +state_prov,41|10.1.2,62,50.48063,7.30898,0.89572 +state_prov,41|10.1.3,308,36.19393,4.90548,0.64222 +state_prov,41|6.2.11,1950,63.83598,6.26520,1.13270 +state_prov,41|6.2.7,5196,68.34200,5.01128,1.21265 +state_prov,41|6.2.8,4075,52.41703,5.15748,0.93008 +state_prov,41|6.2.9,6232,51.68590,6.25125,0.91711 +state_prov,41|7.1.8,3301,72.05841,2.66192,1.27860 +state_prov,41|7.1.9,501,71.83473,2.74597,1.27463 +state_prov,42|5.3.1,27,63.56015,1.54532,1.12781 +state_prov,42|5.3.3,4193,62.11580,1.60287,1.10218 +state_prov,42|8.1.1,8,62.52085,4.61537,1.10936 +state_prov,42|8.1.10,482,63.64145,1.63927,1.12925 +state_prov,42|8.1.3,1052,60.96826,2.34055,1.08182 +state_prov,42|8.3.1,322,63.01490,1.88449,1.11813 +state_prov,42|8.4.1,2565,62.15036,1.68873,1.10279 +state_prov,42|8.4.2,1061,63.11049,1.95217,1.11983 +state_prov,42|8.4.3,1157,62.85200,1.83142,1.11524 +state_prov,42|8.4.4,81,63.72132,0.82603,1.13067 +state_prov,42|8.5.1,7,65.00069,2.12813,1.15337 +state_prov,42|8.5.4,10,66.04120,1.11317,1.17183 +state_prov,44|8.1.7,407,65.04802,2.52317,1.15421 +state_prov,45|8.3.4,8690,55.15142,3.22165,0.97860 +state_prov,45|8.3.5,8431,56.61628,2.39739,1.00459 +state_prov,45|8.4.4,336,59.98556,2.29113,1.06438 +state_prov,45|8.5.1,9098,60.43196,2.17926,1.07230 +state_prov,45|8.5.3,1400,63.58293,3.53085,1.12821 +state_prov,46|6.2.10,1331,44.48683,2.04309,0.78937 +state_prov,46|9.2.1,87,53.21372,3.49595,0.94422 +state_prov,46|9.2.3,25,56.95018,1.22747,1.01052 +state_prov,46|9.3.1,104,53.52796,3.79978,0.94980 +state_prov,46|9.3.3,298,43.48665,3.09310,0.77162 +state_prov,46|9.4.1,136,41.08010,0.90382,0.72892 +state_prov,47|8.3.3,4681,56.45683,2.13540,1.00176 +state_prov,47|8.3.5,1801,55.14719,1.45797,0.97853 +state_prov,47|8.3.6,662,53.86398,1.84031,0.95576 +state_prov,47|8.4.1,2117,58.80398,1.69948,1.04341 +state_prov,47|8.4.2,417,61.80656,1.20276,1.09669 +state_prov,47|8.4.4,1651,59.75611,3.27281,1.06031 +state_prov,47|8.4.9,2299,59.65717,1.37612,1.05855 +state_prov,47|8.5.2,120,55.04455,1.92181,0.97671 +state_prov,48|10.2.4,921,37.63786,1.44222,0.66784 +state_prov,48|13.1.1,4,39.43328,2.83274,0.69970 +state_prov,48|8.3.7,13680,58.57754,3.61995,1.03939 +state_prov,48|8.3.8,2089,50.48735,3.63245,0.89584 +state_prov,48|9.4.1,613,37.50045,1.45292,0.66540 +state_prov,48|9.4.2,976,41.86825,1.57608,0.74291 +state_prov,48|9.4.3,1175,38.63848,0.97756,0.68560 +state_prov,48|9.4.5,1081,46.01506,2.02266,0.81649 +state_prov,48|9.4.6,2393,42.25241,3.38003,0.74972 +state_prov,48|9.4.7,425,49.55111,3.71462,0.87923 +state_prov,48|9.5.1,752,54.67247,7.51125,0.97010 +state_prov,48|9.6.1,1135,42.02682,1.85268,0.74572 +state_prov,49|10.1.3,90,41.38061,3.39820,0.73425 +state_prov,49|10.1.4,26,41.37957,1.19000,0.73424 +state_prov,49|10.1.5,1624,43.27332,3.39802,0.76784 +state_prov,49|10.1.6,3574,41.65307,2.91859,0.73909 +state_prov,49|10.2.1,17,41.04873,1.00954,0.72836 +state_prov,49|6.2.13,3918,46.76279,3.75408,0.82975 +state_prov,49|6.2.14,103,49.64719,2.46891,0.88094 +state_prov,5|8.3.6,210,55.15531,1.99120,0.97867 +state_prov,5|8.3.7,7891,54.49090,2.14091,0.96688 +state_prov,5|8.4.5,3291,54.23818,1.93810,0.96240 +state_prov,5|8.4.6,3050,54.08861,1.94138,0.95974 +state_prov,5|8.4.7,2415,53.75013,2.27149,0.95374 +state_prov,5|8.4.8,4121,54.21363,2.38915,0.96196 +state_prov,5|8.5.2,2403,55.05078,2.10941,0.97682 +state_prov,50|5.3.1,4255,55.12149,3.15663,0.97807 +state_prov,50|8.1.1,124,58.43714,3.25873,1.03690 +state_prov,50|8.1.7,32,60.44942,1.53767,1.07261 +state_prov,51|8.3.1,1213,60.70195,1.50003,1.07709 +state_prov,51|8.3.4,8688,59.14709,1.83890,1.04950 +state_prov,51|8.3.5,4315,59.21563,1.80825,1.05072 +state_prov,51|8.4.1,4897,61.49815,1.98570,1.09122 +state_prov,51|8.4.2,873,63.67165,1.46834,1.12978 +state_prov,51|8.4.4,1939,62.04019,1.97845,1.10084 +state_prov,51|8.5.1,1581,60.24214,2.50925,1.06893 +state_prov,53|10.1.2,258,50.13462,3.47376,0.88958 +state_prov,53|6.2.3,2463,53.90612,2.62701,0.95651 +state_prov,53|6.2.5,3185,58.46304,7.16245,1.03736 +state_prov,53|6.2.7,2296,67.13258,3.76451,1.19119 +state_prov,53|6.2.8,838,58.10876,4.75895,1.03108 +state_prov,53|6.2.9,202,57.58401,2.76659,1.02177 +state_prov,53|7.1.7,807,69.56454,2.50877,1.23435 +state_prov,53|7.1.8,1729,71.33250,2.29293,1.26572 +state_prov,53|7.1.9,35,70.55095,2.01595,1.25185 +state_prov,54|8.3.1,7,61.21621,0.64266,1.08621 +state_prov,54|8.4.1,2086,62.76466,2.20967,1.11369 +state_prov,54|8.4.2,3085,63.85831,1.93920,1.13310 +state_prov,54|8.4.3,2190,61.54784,1.88764,1.09210 +state_prov,54|8.4.4,5,62.38609,0.98666,1.10697 +state_prov,55|0.0.0,229,53.03891,3.80457,0.94112 +state_prov,55|5.2.1,19209,49.49256,2.78802,0.87819 +state_prov,55|8.1.4,8995,55.52247,3.70471,0.98519 +state_prov,55|8.1.5,4483,62.76114,2.32622,1.11363 +state_prov,55|8.2.1,1666,61.01433,3.70832,1.08263 +state_prov,55|8.2.3,17,64.92786,1.22060,1.15207 +state_prov,55|9.2.3,116,60.84790,1.51128,1.07968 +state_prov,56|10.1.4,526,41.78274,2.84287,0.74139 +state_prov,56|10.1.8,5,52.75523,0.55475,0.93608 +state_prov,56|6.2.10,2830,45.52958,2.80903,0.80787 +state_prov,56|6.2.13,30,45.40855,1.17330,0.80573 +state_prov,56|6.2.14,558,45.91642,1.94017,0.81474 +state_prov,56|9.3.3,183,42.91709,1.67061,0.76152 +state_prov,56|9.4.1,39,41.96882,1.12135,0.74469 +state_prov,6|10.1.3,71,40.52206,4.82902,0.71902 +state_prov,6|10.1.5,317,38.64886,6.97911,0.68578 +state_prov,6|10.2.1,166,37.53861,3.27922,0.66608 +state_prov,6|10.2.2,34,36.53134,2.59700,0.64821 +state_prov,6|11.1.1,1818,54.51258,8.51385,0.96727 +state_prov,6|11.1.2,61,49.99892,3.20654,0.88718 +state_prov,6|11.1.3,341,46.84306,5.03700,0.83118 +state_prov,6|6.2.11,2243,60.43232,7.38441,1.07231 +state_prov,6|6.2.12,3516,60.55128,6.64709,1.07442 +state_prov,6|6.2.7,118,59.69147,4.31274,1.05916 +state_prov,6|6.2.8,1194,49.52090,7.42878,0.87869 +state_prov,6|7.1.8,1078,76.07270,4.75229,1.34983 +state_prov,8|10.1.4,85,42.29825,2.55867,0.75054 +state_prov,8|10.1.6,1768,44.54356,2.63671,0.79038 +state_prov,8|10.1.7,33,42.53343,2.24849,0.75471 +state_prov,8|6.2.14,6781,47.73287,3.14483,0.84697 +state_prov,8|9.4.1,15,43.05302,2.51467,0.76393 +state_prov,8|9.4.3,336,43.59985,2.86780,0.77363 +state_prov,9|5.3.1,192,63.39530,1.56119,1.12488 +state_prov,9|8.1.7,1003,64.33541,1.96515,1.14156 +state,1,26957,57.47963,2.19722,1.01991 +state,10,427,62.06960,1.80225,1.10136 +state,12,26431,59.36544,4.63817,1.05338 +state,13,44671,58.06062,3.57712,1.03022 +state,16,9703,52.40713,6.37376,0.92991 +state,17,5114,62.40061,2.78185,1.10723 +state,18,6256,62.74073,1.95906,1.11327 +state,19,2105,64.98213,2.39542,1.15304 +state,20,3324,57.26828,3.22960,1.01616 +state,21,7646,59.03659,2.38433,1.04754 +state,22,14587,57.43323,2.81900,1.01909 +state,23,17637,50.64564,4.45552,0.89865 +state,24,1609,62.73634,2.80007,1.11319 +state,25,2182,61.59953,2.27047,1.09302 +state,26,43243,50.69354,5.59836,0.89950 +state,27,49761,46.20490,5.01353,0.81986 +state,28,20496,58.10097,2.77582,1.03094 +state,29,16334,59.61667,2.00031,1.05783 +state,30,11264,49.14215,4.19630,0.87197 +state,31,1286,51.31216,7.85909,0.91048 +state,32,5005,39.28171,4.17293,0.69701 +state,33,4667,55.77442,4.23817,0.98966 +state,34,1821,62.10520,2.36716,1.10199 +state,35,9702,45.05095,4.30697,0.79938 +state,36,12322,59.12261,4.20842,1.04907 +state,37,27399,59.05798,3.07424,1.04792 +state,38,875,43.63491,2.55167,0.77425 +state,39,4664,62.26889,2.16784,1.10489 +state,4,11520,44.70738,5.96303,0.79328 +state,40,7328,51.45249,3.29344,0.91297 +state,41,21625,60.27388,10.49265,1.06949 +state,42,10965,62.30232,1.88694,1.10549 +state,44,407,65.04802,2.52317,1.15421 +state,45,27955,57.79213,3.71239,1.02546 +state,46,1981,45.11769,3.96807,0.80056 +state,47,13748,57.60317,2.84683,1.02211 +state,48,25244,51.95722,8.69938,0.92192 +state,49,9352,44.15870,4.14323,0.78355 +state,5,23381,54.34098,2.17073,0.96422 +state,50,4411,55.25335,3.22836,0.98041 +state,51,23506,60.21005,2.30042,1.06836 +state,53,11813,61.65373,8.03865,1.09398 +state,54,7373,62.85911,2.22415,1.11537 +state,55,34715,53.39027,5.76930,0.94735 +state,56,4171,44.96870,2.99560,0.79792 +state,6,10957,58.16879,10.88465,1.03214 +state,8,9018,46.87557,3.39157,0.83176 +state,9,1195,64.18436,1.93649,1.13888 +conus,CONUS,638153,54.98251,7.35235,1.00000 diff --git a/scripts/yield_curve_engine/cspi/20260627_block2_climate_hook_spec.md b/scripts/yield_curve_engine/cspi/20260627_block2_climate_hook_spec.md new file mode 100644 index 0000000..988e86f --- /dev/null +++ b/scripts/yield_curve_engine/cspi/20260627_block2_climate_hook_spec.md @@ -0,0 +1,49 @@ +# Block 2 rcp-native climate: hook built + validated, data-acquisition spec + +*2026-06-27. Advances the Block 2 blocker as far as possible without the external climate run.* + +## What is done (no external dependency) + +1. **Per-rcp CSI table built** (`config/csi_states_ext_rcp.csv`, 48 states x {rcp45, rcp85}). + Columns: state, rcp, csi_2030, csi_2060, csi_2090, source. First cut reproduces the current + behavior exactly (rcp85 = full observed CSI stress; rcp45 = half the departure = the current + SCALE_rcp=0.5). `ycx_make_rcp_csi.py` generates it. +2. **Engine hook validated behavior-preserving.** The current engine computes + `pm(t) = 1 + SCALE_rcp * CSI_BETA * (CSI(t)/CSI_2030 - 1)` from a single CSI. The rcp-native + refactor reads the rcp-specific row and drops SCALE_rcp: + `pm(t) = 1 + CSI_BETA * (CSI_rcp(t)/CSI_rcp_2030 - 1)`. Validated identical (ME 2090: + rcp85 1.0795=1.0795, rcp45 1.0398=1.0398). So real per-rcp CSI is drop-in: replace the + firstcut columns, no other code change. +3. **48-state ClimateNA inputs pre-generated** (`climatena_inputs_48/climatena_input_.csv`, + 638,751 plots, format ID1,ID2,lat,long,el with el=0 so ClimateNA fills elevation from its + DEM). Built from membership LAT/LON server-side; only the input-location step is removed -- + these are plot locations, the same protected coordinates that stay on Cardinal, so the + ClimateNA run must also be done on a machine cleared for FIA true coords. + +## The one remaining external action (the actual blocker) + +Run ClimateNA (Windows/desktop software, or its batch API) on the 48-state inputs to produce +future climate normals, then derive per-rcp CSI: + +1. **ClimateNA run:** future projections, 13-GCM ensemble mean, scenarios rcp45 and rcp85, + normal periods centered ~2030 (2011-2040), ~2060 (2041-2070), ~2090 (2071-2100). Output + variables: MAT, MAP, CMD, DD5 (the CSI predictors; add tmin_wt, tmax_sm if the CSI formula + uses them). +2. **Derive CSI per state per rcp:** run the ClimateNA outputs through the existing CSI / site- + index relationship (`SiteIndex/clim_ranger_spatial_model.rds` maps climate -> SI), aggregate + plot -> state mean, normalize to the 2030 value (CSI_2030 = 1). +3. **Replace** the firstcut columns in `config/csi_states_ext_rcp.csv` with the derived values + (keep the schema). Point the engine at this table. Done -- the engine is now rcp-native. + +## Why this is genuinely blocked here + +ClimateNA is external desktop/batch software not installed on Cardinal, and no future GCM +climate is staged (only current-climate ClimateNA SI rasters and the 4-state pilot input files). +Acquiring the future climate is a data/software step outside the cluster. Everything downstream +of it is built and validated. + +``` +[BLOCK2_STATE]: per-rcp CSI table + behavior-preserving engine hook built and validated; 48-state ClimateNA inputs pre-generated (638,751 plots). +[REMAINING]: external ClimateNA future run (rcp45/85, 13-GCM, 3 normals) -> clim_ranger -> per-state per-rcp CSI -> replace firstcut columns. Drop-in. +[PROTOCOL]: ClimateNA run must stay on an FIA-true-coords-cleared machine (inputs are protected plot coordinates). +``` diff --git a/scripts/yield_curve_engine/cspi/20260627_cspi_signoff_decision_package.md b/scripts/yield_curve_engine/cspi/20260627_cspi_signoff_decision_package.md new file mode 100644 index 0000000..91cc978 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/20260627_cspi_signoff_decision_package.md @@ -0,0 +1,73 @@ +# CSPI beta/clamp sign-off: decision package + +*2026-06-27. Packages the remaining CSPI decision so it collapses to one low-stakes call.* + +## The reframe: this sign-off is lower-stakes than it looked + +The stress test proved that a uniform asymptote scalar **cancels** in the t0-anchored reserve +multiplier (ME/CA realized reserve identical with the CSPI cap on vs off). Consequence: + +- **beta and clamp do NOT change any published trajectory number.** On the anchored carbon/ + biomass/product trajectories they are a mathematical no-op. +- They affect **only** the magnitude of the spatial t0 redistribution (mean ~2%, up to ~6%, + totals preserved), i.e., how much standing carbon is reallocated toward productive cells on + the density maps. That is bounded by the clamp and well-ordered (Bakuzis PASS). + +So the decision is not "does CSPI change our numbers" (it does not, on the trajectory). It is +"how strong should the spatial productivity adjustment be on the density layer," with a safe, +bounded default already in hand. + +## Recommendation + +1. **Do not promote CSPI on the anchored trajectory** (asymptote cap). It is provably inert; + adding it would be complexity with no effect. Close that path. +2. **Adopt CSPI on the spatial density layer** with **beta = 1.0, clamp +/- 25%** as the + default. It changes no published trajectory number, redistributes ~2% of standing carbon + toward productive sites, is biologically well-ordered, and is reversible (flag/clamp). +3. **Defer per-cell SDImax and the 30 m density render** as refinements; not needed to adopt. + +Under this framing the sign-off can be a quick yes to the default, because the downside is +bounded and nothing published moves. + +## Push-button execution runbook (on "go") + +1. **Merge PR #91** (holoros/perseus-forest-intelligence, `feature/cspi-asymptote-covariate`). + It is mechanism + provenance + ADR 0005 + stress memo; no live-data regen in the PR itself. +2. **Deploy the CONUS CSPI overlay** additively to gh-pages `public/raster/`: + `out/conus_cspi_scalar.png` + `_bounds.json` (drop-in, matches the existing overlay + convention). Follow the additive-to-gh-pages path in CONTRIBUTING (do NOT deploy main). +3. **Publish Zenodo v1.2.0:** from `zenodo_staging/perseus-yield-curves/v1.2/` on Cardinal: + `python new_version.py --token-file ~/.zenodo_token --parent-doi 10.5281/zenodo.20959003 \ + --metadata zenodo_metadata.json --files-list files_to_upload.txt --publish` + (copy new_version.py from the zenodo-deposit skill scripts/ first). +4. **Backfill the minted v1.2 DOI** into ADR 0005, the README, and CITATION.cff; commit. + +Each step is prepared; total hands-on time is minutes. + +## Draft note to the team (requesting the sign-off) + +> Subject: CSPI site-productivity layer -- quick sign-off (low stakes) +> +> Team -- the CSPI site-productivity work is done and stress-tested. One finding simplifies the +> decision: on our anchored carbon/biomass trajectories, scaling the curve asymptote by CSPI +> cancels out mathematically (we verified state trajectories are identical with it on or off). +> So CSPI does not change any published trajectory number. +> +> Where it does add value is the spatial layer: it reallocates about 2% of each state's standing +> carbon toward more productive sites on the density maps, with state totals preserved and +> biologically sensible ordering (productive East gains, arid West loses). The adjustment is +> bounded (max +/- 25% per cell) and reversible. +> +> I recommend we adopt it on the density layer with the default settings (proportional, +/- 25% +> cap). It is drop-in (EPSG:5070 overlay ready), changes no published number, and improves +> sub-state spatial accuracy. If you are good with the default, I will merge the (draft) PR, +> deploy the CONUS overlay, and publish the dataset update (Zenodo v1.2). Details and the +> stress test are in the assessment memo. -- Aaron + +(Drafted for Aaron to send/edit; the email-drafter skill can tailor tone and recipients.) + +``` +[SIGNOFF_REFRAME]: beta/clamp are a no-op on published trajectories (asymptote cancels); they only tune the bounded ~2% spatial redistribution. Low stakes. +[RECOMMENDATION]: adopt CSPI on the spatial density layer at beta=1.0/clamp 25%; do not promote on the anchored trajectory. +[READY]: merge #91 + deploy CONUS overlay + publish Zenodo v1.2 -- runbook + draft team note prepared; minutes of hands-on once approved. +``` diff --git a/scripts/yield_curve_engine/cspi/20260627_cspi_stress_bakuzis_assessment.md b/scripts/yield_curve_engine/cspi/20260627_cspi_stress_bakuzis_assessment.md new file mode 100644 index 0000000..67d3b09 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/20260627_cspi_stress_bakuzis_assessment.md @@ -0,0 +1,272 @@ +# CSPI stress test + Bakuzis assessment (#75) + +*2026-06-27. Autonomous OODA run. Stress-tests the CSPI asymptote covariate on three axes: +knob sensitivity, spatial cross-validation (generalization), and Bakuzis site-ordering +(biological realism). Companion to OODA_cspi_asymptote_scaling.md.* + +## Headline + +The CSPI covariate is **biologically sound and bounded, and its signal is real and +generalizable on the remeasurement asymptotes (the #75 refit), but NOT on the current +hybrid production asymptotes that the flagged engine scales.** This is a target mismatch: +CSPI should be applied to the remeasurement-track curves it was validated against, which is +exactly what the Block 1+3 dev-to-prod promotion produces. Applying it to the current hybrid +fits is harmless (CONUS-neutral, well-ordered) but adds no out-of-sample predictive value. + +## A. Knob sensitivity (beta x clamp) + +Per-cell scalar over 3,662 hybrid carbon cells. A-weighted mean |asymptote change| and the +fraction of cells whose raw scalar hits the clamp: + +| beta | clamp | median | p05 | p95 | A-wt mean |change| | % clamped | +|---|---|---|---|---|---|---| +| 0.5 | 0.25 | 1.000 | 0.976 | 1.019 | 1.6% | 0.0 | +| 1.0 | 0.25 | 1.001 | 0.955 | 1.038 | 3.2% | 4.8 | +| 1.5 | 0.25 | 1.001 | 0.941 | 1.058 | 4.3% | 15.3 | +| 2.0 | 0.25 | 1.001 | 0.933 | 1.077 | 5.1% | 27.6 | +| 2.0 | 0.40 | 1.001 | 0.918 | 1.080 | 6.0% | 9.4 | + +Reading: the production setting (beta=1.0, clamp +/-25%) moves asymptotes ~3% on an +A-weighted basis with 4.8% of cells clamped. Even at beta=2.0 the effect stays under ~6%, +and the clamp engages more (protecting against runaway), confirming the bounding works as +designed. Conclusion is robust to the knobs: no setting produces a destabilizing effect. + +## B. Spatial cross-validation (leave-one-ecoregion-out, within forest type) + +Does cell CSPI predict the held-out cell asymptote out-of-sample? Fit log(A) ~ log(CSPI) on +all but one ecoregion within a forest type, predict the held-out ecoregion, compare RMSE to +a no-CSPI forest-type-mean baseline. + +| Asymptote set (what is scaled / validated) | within-ft partial r | CV RMSE base | CV RMSE +CSPI | improvement | ft improved | +|---|---|---|---|---|---| +| Hybrid (engine currently scales these) | +0.077 | 1.489 | 1.488 | +0.0% | 7/26 | +| Remeas (CSPI signal source; the #75 refit) | +0.373 | 0.783 | 0.716 | **+8.5%** | 13/25 | + +Reading: on the **remeas** asymptotes CSPI generalizes (held-out RMSE drops 8.5%, most +forest types improve, log-RMSE 0.78). On the **hybrid** asymptotes CSPI has no out-of-sample +skill (+0.0%) and the asymptotes are far noisier (log-RMSE 1.49 ~ 4.4x spread), reflecting a +different parameter (peak-decline form A) than the remeas saturating asymptote. The earlier +in-sample correlation (0.45 pooled, 0.37 within-ft) was on the remeas fits and held up under +CV there; it does not transfer to the hybrid fits. + +## C. Bakuzis site-ordering (biological realism) + +The full Bakuzis matrix (Eichhorn, Reineke) needs HT/TPH/QMD, which the YC engine does not +carry; the applicable law-like relation is site ordering. CSPI quartile classes (midpoints +47.1, 55.3, 59.3, 62.5) were turned into representative carbon trajectories via the median +hybrid curve with class-scaled asymptote. + +- Asymptote ordering Q1 < Q2 < Q3 < Q4 at every age: **PASS** +- No site-curve crossings across age 5 to 100: **PASS** +- Carbon at age 100 by class: 63,965 < 75,102 < 80,608 < 84,867 (monotone) + +Per the Bakuzis principle, this falsifies-but-does-not-confirm: the CSPI mechanism does not +violate site ordering and produces properly ranked, non-crossing site curves. It is +biologically well-behaved. + +## Recommendation + +1. **Re-scope the target.** Apply CSPI scaling to the remeasurement-track asymptotes (the + Block 1+3 refit that #75 promotes to production), not the current hybrid fits. The + mechanism, t0-pin, and engine wiring are all reusable as-is; only the input fit table + changes from `ycx__hybrid_fits.csv` to the remeas cell fits at promotion time. +2. **Keep the conservative knobs.** beta=1.0 and clamp +/-25% are safe and bounded; the CV + gain on remeas (+8.5%) does not justify a higher beta, and higher beta increases clamping. +3. **Hold the PR as draft and the Zenodo deposit as staged** until the remeas-track promotion + lands, then wire CSPI to it and re-run this stress battery on the remeas-scaled engine. +4. **Do not promote CSPI on the hybrid engine.** It is harmless but unsupported there; doing + so would add complexity with no validated benefit. + +This is the OODA adversarial value: the stress test caught that the validated signal and the +scaled target were different curve forms, which the in-sample correlation alone had masked. + +``` +[STRESS_STATE]: knob sweep robust; spatial CV shows CSPI generalizes on remeas (+8.5%) not hybrid (+0.0%); Bakuzis site-ordering PASS. +[DECISION]: re-scope CSPI to the remeas-track production refit; keep beta=1.0/clamp 25%; keep PR draft + Zenodo staged. +[NEXT]: when Block 1+3 remeas fits are promoted, swap the engine input to remeas, re-run stress, then merge + publish. +``` + +## ADDENDUM: remeas-track engine run (48 states, rcp45) + +Built `ycx_canonical_ci_fiadb_remeas_cspi.R` (reads remeas cell fits, 2-part cell key) and +ran the full 48 states, CSPI off vs on. This is the validated configuration: CSPI applied to +the asymptotes where it generalizes. + +**CSPI effect on the remeas form (the clean test):** + +| Scenario | 2025 | 2075 | 2125 | +|---|---|---|---| +| Reserve | +0.00% | +0.01% | +0.01% | +| BAU | +0.00% | +0.01% | +0.01% | + +Per-state 2125 delta -0.39% (NE) to +0.29% (CA), median 0.00%. Same conclusion as on the +hybrid form: CSPI is t0-neutral and CONUS-neutral, redistributive at cell/state scale. The +difference is that here it is applied to the form where its out-of-sample skill is real. + +**Curve-form caution (separate, important).** The remeas potential-growth form over-projects +relative to the hybrid production form: CONUS reserve carbon (raw engine sums, flat2400-heavy +area, comparison-only) is 77,724 (2025) and 179,804 (2125) for remeas vs 55,177 and 77,490 +for hybrid. The remeas (d=0, saturating, no decline) curves keep climbing while the hybrid +peak-decline curves turn over, so remeas is ~2.3x higher by 2125. This confirms the standing +caution (CONUS_YC_MASTER_HANDOFF gotcha): the monotone remeas potential-growth curves must +NOT be promoted as the reserve trajectory; the reserve arm needs the realized curve +(potential x CSPI carrying-capacity x disturbance x senescence, the Block 5 form). + +**Synthesis (the production home for CSPI).** CSPI generalizes on the remeas asymptotes +(+8.5% CV) and is biologically well-ordered (Bakuzis PASS), but the raw remeas potential +over-projects. CSPI's correct production role is as the carrying-capacity term in the +realized reserve curve: remeas potential growth, capped by CSPI site productivity, drawn down +by age-structured disturbance and explicit senescence (Block 5). It is necessary but not +sufficient on its own. CSPI should NOT be bolted onto the hybrid engine (no skill) nor used +to scale the raw remeas potential alone (over-projects); it belongs in the Block 5 realized +curve as the asymptote cap. beta=1.0, clamp +/-25% remain the right knobs. + +``` +[REMEAS_RUN]: 48/48 both arms; CSPI t0-neutral (+0.00%) and CONUS-neutral (+0.01% at 2125), redistributive; applied to the validated form. +[CURVE_FORM]: remeas potential over-projects ~2.3x vs hybrid by 2125 (d=0 monotone, no decline); not promotable as the reserve trajectory alone. +[PRODUCTION_HOME]: CSPI = the carrying-capacity cap in the Block 5 realized reserve curve (potential x CSPI x disturbance x senescence), not the hybrid engine and not the raw remeas potential. +[NEXT]: integrate CSPI as the asymptote cap into the Block 5 realized-reserve builder, run that 48-state, re-stress, then it is the dev->prod candidate. +``` + +## ADDENDUM 2: the asymptote cap CANCELS in the anchored reserve (decisive) + +Integrated the CSPI cap into the realized-reserve builders behind the flag and tested both +the buggy mean-age version and the CORRECT per-plot calendar (ycx_reserve_realized_calendar.R, +#90). The cap mechanism is well-ordered (state scalars: ME 0.899, AZ clamped 0.800 [low +productivity, capped down], OR 1.069, FL 1.053, CA 1.032 [high, nudged up]). + +But the realized reserve is **identical with the cap on or off**: + +| State | CSPI scalar | realized reserve 2125, cap OFF | cap ON | +|---|---|---|---| +| ME | 0.899 | 489.5 Tg (2.21x t0) | 489.5 Tg (2.21x t0) | +| CA | 1.032 | 653.0 Tg (1.01x t0) | 653.0 Tg (1.01x t0) | +| AZ | 0.800 | 141.7 Tg (1.03x t0) | (identical) | + +**Why (structural):** the reserve calendar is a t0-anchored multiplier, +reserve(year) = t0 * mean_plots Gsen(age) / mean_plots Gsen(age0). A uniform asymptote +scalar c multiplies Gsen everywhere, so it cancels exactly in the ratio: (c*X)/(c*Y) = X/Y. +A per-state (or per-cell, uniformly applied) CSPI cap on the asymptote LEVEL therefore has +zero effect on the anchored reserve trajectory. This is the root cause of every near-zero +reconciliation in this engine family: the small +/-0.4% per-state effect seen in the +canonical engine came only from DIFFERENTIAL per-cell scaling under area weighting, not from +the asymptote level itself. + +**Decisive conclusion and redirect.** CSPI is real (generalizes +8.5% CV on remeas +asymptotes) and biologically well-ordered (Bakuzis PASS), but as an asymptote-LEVEL covariate +it is structurally neutralized by the t0-anchoring used throughout the production engines. +The asymptote-cap framing is the wrong lever. To deliver CSPI's validated value, apply it as +one of: +1. **Absolute / spatial level** (NOT anchored): the t0 carbon-density maps, TreeMap pixel + expansion, and area model, where CSPI changes levels and does not cancel. This is the + highest-value home (spatial accuracy of standing carbon). +2. **Curve SHAPE** (rate k or age-to-maturity), not the asymptote, since shape terms do not + cancel in the t0-anchored ratio. Requires re-deriving CSPI against the rate, not the + asymptote (the CV here validated the asymptote relationship, not a rate relationship). +3. **Cell-level differential** feeding an area-weighted sum (the canonical engine), accepting + the small redistributive +/-0.4% effect. + +Do NOT pursue the asymptote cap on the anchored reserve: it provably does nothing. + +``` +[CANCELLATION]: proven -- uniform asymptote scalar cancels in the t0-anchored reserve multiplier (ME/CA identical to 4 sig figs cap on vs off). +[REDIRECT]: CSPI belongs in the ABSOLUTE/spatial products (t0 density maps, area model) or as a curve-SHAPE term, not an asymptote cap on the anchored trajectory. +[STATUS]: PR #91 draft + Zenodo v1.2 staged remain correct holds; the asymptote-cap production path is closed by this result. Team decision: pursue CSPI on the spatial/t0 layer (recommended) or re-derive against curve rate. +``` + +## ADDENDUM 3: spatial t0 layer prototype (where CSPI is NOT cancelled) + +Tested the recommended home directly. For each state, each plot's t0 carbon density is the +fitted curve at its stand age (baseline, uniform-by-area within cell); the CSPI version +multiplies each cell's density by its CSPI scalar and renormalizes within the state so the +FIA-anchored STATE TOTAL is preserved. The redistribution among cells (total-variation +distance, = fraction of the state's standing carbon moved): + +| Metric | Value | +|---|---| +| states / cells | 48 / 2,203 | +| mean carbon redistributed | 2.09% | +| median | 1.44% | +| range | 0.32% to 6.32% | +| most redistributed | NE 6.3%, CA 5.0%, WA 4.9%, OR 4.9%, WI 4.7%, AZ 4.4% | + +Reading: unlike the asymptote cap on the anchored reserve (exactly 0), CSPI moves a real, +non-zero amount of standing carbon on the absolute density layer, reallocating toward +productive cells while preserving FIA-anchored state totals. The magnitude is modest (~2% +mean, up to ~6% in large productivity-gradient states), which is the honest size of CSPI's +value: it improves sub-state spatial carbon accuracy by a few percent. This is CSPI's +production home. + +## Final disposition of the CSPI investigation + +- CSPI signal: REAL and generalizable on remeas asymptotes (+8.5% spatial CV), well-ordered + (Bakuzis PASS), robust to knobs. +- Asymptote cap on the anchored reserve trajectory: provably ZERO effect (cancels in the + t0-anchored multiplier). Closed. +- Canonical engine (area-weighted cell sum): tiny redistributive effect (+/-0.4%), t0-neutral. +- Spatial t0 density layer: NON-ZERO, ~2% mean (up to 6%) carbon reallocation toward + productive cells, state totals preserved. RECOMMENDED production integration. + +Production implementation (queued): wire `cspi_scalar` into the per-cell / per-TreeMap-pixel +t0 density in the raster/area builder (`ycx_build_rasters.R` and the area model), renormalized +to FIA state totals. Keep beta=1.0, clamp +/-25%. Re-run the Bakuzis site-ordering check on +the spatial product. PR #91 and Zenodo v1.2 stay staged; on team sign-off, this spatial +integration (not the asymptote cap) is what ships. + +``` +[SPATIAL_PROTOTYPE]: CSPI moves mean 2.09% (max 6.32%) of state standing carbon toward productive cells, totals preserved -- NON-ZERO, unlike the cancelled asymptote cap. +[FINAL]: CSPI production home = spatial t0 density allocation. Asymptote-cap path closed. Signal real (+8.5% CV), well-ordered (Bakuzis PASS), spatial value ~2%. +[NEXT]: wire cspi_scalar into ycx_build_rasters.R / area model per-pixel density (renorm to FIA totals); re-run site-ordering check; then it is the dev->prod candidate. +``` + +## ADDENDUM 4: CSPI-adjusted CONUS carbon-density raster (built) + +`ycx_cspi_raster.py` bins per-plot modeled t0 carbon density (remeas curve at stand age) to a +0.25 deg CONUS grid server-side (coordinates stay on the cluster; only the gridded PNG/array +leaves), then applies the per-cell CSPI scalar renormalized within each state to preserve the +FIA-anchored state total. Outputs baseline, CSPI-adjusted, and difference maps. + +- Grid 104x236, 10,764 filled cells; baseline density mean 58.4 Mg C/ha (max 588). +- CSPI reallocation: mean |diff| 3.6 Mg/ha; 6,996 cells move >1 Mg/ha; range -74 to +177. +- The difference map is biologically coherent: the productive Pacific Northwest coast and + northern hardwood/Lake States gain carbon (up to +177 Mg/ha), the arid interior West and + Southwest drylands lose it, with state totals preserved. This is correct site-productivity + behavior and a visual confirmation of the spatial site-ordering relation. + +Files: `cspi_raster_base.png`, `cspi_raster_cspi.png`, `cspi_raster_diff.png` (+ thumbs). +This is the working prototype of CSPI's production home. The production version replaces the +0.25 deg lon/lat binning with the explorer's Albers (EPSG:5070) replication and applies the +scalar per TreeMap pixel; the mechanism and renormalization shown here carry over unchanged. + +``` +[RASTER]: CSPI-adjusted CONUS carbon-density raster built; mean |reallocation| 3.6 Mg/ha, biologically coherent (PNW/northern hardwoods gain, interior West/SW lose), state totals preserved. +[CSPI INVESTIGATION COMPLETE]: signal real (+8.5% CV), well-ordered (Bakuzis PASS), asymptote-cap cancels (closed), spatial home demonstrated (~2% / 3.6 Mg/ha reallocation). +[PROD NEXT]: port the binning to Albers/TreeMap-pixel in ycx_build_rasters.R; that raster + the ycx_cell_cspi.csv covariate are the dev->prod CSPI deliverable. +``` + +## ADDENDUM 5: Albers (EPSG:5070) production port -- drop-in overlays + +`ycx_cspi_raster_albers.py` ports the prototype to the explorer's exact overlay system: +EPSG:5070 Albers, extent x0 -2561585 / y1 1714610 / x1 2463176 / y0 -1604872.736, 20 km grid +(166x251), via pyproj. Emits bare overlay PNGs with alpha for nodata plus companion +`*_bounds.json` files in the explorer's convention (verified byte-identical to e.g. +conus_asym_agb_bounds.json), so they are drop-in for public/raster/. + +- Grid 166x251 at 20 km, 6,786 filled cells; baseline mean 50.4 Mg C/ha (max 211.8); + mean |CSPI reallocation| 2.40 Mg/ha. +- Geography verified: high carbon in the humid East/Appalachians, low in the arid West; + CSPI reallocates toward productive sites. Albers projection renders correctly. +- Artifacts: out/conus_yc_agc_{base,cspi,cspidiff}.png (+ _bounds.json), and a labeled + review panel conus_yc_cspi_albers_review.png. + +Remaining for full production fidelity (blocked, separate): the 30 m TreeMap-pixel version +needs GDAL/terra, currently broken on Cardinal. The 20 km FIA-binned Albers product here is +the same resolution as the existing live hybrid overlays (conus_hybrid_agc2022) and is +deploy-ready by the same path. The CSPI thread is now complete end-to-end: covariate -> +validated signal -> structural finding -> spatial home -> production-projection overlays. + +``` +[ALBERS_PORT]: EPSG:5070 drop-in overlays + bounds.json built at 20km (matches live hybrid overlays); geography verified; mean reallocation 2.4 Mg/ha. +[DEPLOY PATH]: additive to gh-pages public/raster/ (same as existing overlays); gated on team beta/clamp sign-off + the source->deploy reconciliation noted in CONTRIBUTING. +[30m FIDELITY]: deferred -- needs GDAL/terra fix on Cardinal (known issue). +``` diff --git a/scripts/yield_curve_engine/cspi/20260628_cspi_deep_assessment.md b/scripts/yield_curve_engine/cspi/20260628_cspi_deep_assessment.md new file mode 100644 index 0000000..d3640e0 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/20260628_cspi_deep_assessment.md @@ -0,0 +1,72 @@ +# Deep CSPI assessment: is it worth promoting, and how (2026-06-28) + +*Three decision-critical out-of-sample tests on the remeasurement cell asymptotes, leave-one- +ecoregion-out CV within forest type. 656 cells, covariates sampled at public plot coords +(212,364 samples). Decides the CSPI next steps. Companion to the earlier stress assessment.* + +## T2: CSPI vs simpler covariates — CSPI is NOT redundant + +Held-out CV skill for predicting the cell log-asymptote (improvement over a forest-type-mean +baseline): + +| Covariate | held-out RMSE | vs baseline | within-ft r | +|---|---|---|---| +| **CSPI** | 0.796 | **+8.4%** | +0.39 | +| ClimateNA site index | 0.860 | +0.9% | -0.06 | +| Latitude | 0.836 | +3.7% | +0.24 | + +The existing ClimateNA SI product has **essentially no skill** (+0.9%, r near 0) for the +yield-curve asymptote; latitude has a little; CSPI clearly leads. CSPI is not a repackaging of +information already on hand. + +## T3: CSPI adds over the existing SI — fully independent signal + +Within-forest-type partial correlation of log-asymptote with log-CSPI, **controlling for +ClimateNA SI**: +0.41 (n=643) — essentially unchanged from the raw +0.39. Conditioning on SI +removes none of CSPI's signal. CSPI's predictive content is independent of, and far larger +than, the existing site-index layer. + +## T1: optimal beta — the data supports a HIGHER beta than 1.0 + +Held-out relRMSE of the asymptote when scaled by (CSPI/median)^beta vs a flat ft-mean: + +| beta | held-out improvement | +|---|---| +| 0.5 | +3.5% | +| 1.0 | +6.5% | +| 1.5 | +8.8% | +| 2.0 | +10.5% | +| 2.77 (free slope) | +11.5% | + +Out-of-sample skill rises monotonically with beta. **This refutes my earlier call.** I had set +beta=1.0 and dismissed the free slope (~2.77) as confound-amplified; the leave-one-ecoregion-out +CV shows the steep slope generalizes best, so the steepness is real predictive signal, not a +confound. The marginal gain flattens beyond ~2.0 (2.0 gets +10.5%, free +11.5%), so beta ~1.5 to +2.0 captures nearly all the skill with less extrapolation risk. + +## Decision and next steps + +1. **Promote CSPI — it is genuinely valuable and non-redundant.** It carries real, out-of-sample + productivity signal for the asymptote (+8 to +11%) that no available covariate provides + (ClimateNA SI +0.9%, latitude +3.7%), and that signal is independent of SI. +2. **Use beta ~1.5 to 2.0 on the SPATIAL layer, not 1.0.** The CV-optimal beta is higher than the + conservative default; raise it to ~1.5-2.0 and widen or drop the +/-25% clamp accordingly + (the clamp was a safety bound the data now says is too tight). Recommend beta=1.5 as the + skill/robustness balance, or 2.0 to maximize spatial accuracy. +3. **The structural finding still holds:** a uniform asymptote scalar cancels in the t0-anchored + reserve trajectory, so beta only affects the ABSOLUTE/spatial product (the density maps), not + the anchored carbon trajectory. So CSPI's home remains the spatial density layer, now with a + stronger, evidence-based beta. +4. **Do not block on alternatives.** Since SI has no skill here, there is no simpler substitute; + CSPI is the covariate to use. + +Concrete next action when promoting: rebuild the CONUS CSPI spatial overlay (ycx_cspi_conus_overlay.py) +at beta=1.5 (and a wider clamp, e.g. [0.7,1.45]), regenerate the spatial redistribution and the +30m product on that beta, and use that as the production CSPI layer. Optionally re-derive the +asymptote-cap calibration directly from the free CV slope per forest type. + +``` +[DEEP_ASSESS]: CSPI out-of-sample skill +8.4% (asymptote), beats ClimateNA SI (+0.9%) and latitude (+3.7%); independent of SI (partial r +0.41). Optimal beta 1.5-2.0 (CV rises monotonically), NOT the conservative 1.0. +[DECISION]: promote CSPI on the spatial density layer at beta~1.5-2.0, widen/drop the clamp; no simpler covariate substitutes. Anchored-trajectory cancellation unchanged. +[NEXT]: rebuild the CONUS + 30m CSPI overlays at beta=1.5; optionally per-ft CV-calibrated slope. +``` diff --git a/scripts/yield_curve_engine/cspi/README.md b/scripts/yield_curve_engine/cspi/README.md new file mode 100644 index 0000000..2a953c9 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/README.md @@ -0,0 +1,6 @@ +# CSPI site-productivity asymptote covariate (#75 step 3) + +CSPI scales the per-cell yield-curve asymptote in the canonical FIADB engine, behind the +YCX_CSPI_ASYM flag. Default OFF; live engine unchanged. See docs/decisions/0005. +Knobs (sign-off pending): beta=1.0; clamp [0.80,1.25]; sparse shrink 30/(30+n_plots). +Verified rcp45 48 states: CONUS t0 +0.02%, 2125 -0.00%; per-state -0.39% to +0.72%. diff --git a/scripts/yield_curve_engine/cspi/SESSION_SUMMARY_2026-06-27.md b/scripts/yield_curve_engine/cspi/SESSION_SUMMARY_2026-06-27.md new file mode 100644 index 0000000..275a3b8 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/SESSION_SUMMARY_2026-06-27.md @@ -0,0 +1,147 @@ +# CONUS YC session summary + handoff — 2026-06-27 + +*Consolidated record of the full autopilot session (run under autonomous-ooda-manager). +Covers the CSPI covariate investigation end to end, Block 4 carbon pools, SDI/RD projection, +the CONUS spatial products, and the terra/rasterio infrastructure resolution. Pairs with +CONUS_YC_MASTER_HANDOFF.md (prior engagement) and the dated track notes referenced below. +Nothing in this session touched the live explorer, the production perseus_db, or Zenodo.* + +--- + +## 1. TL;DR + +Investigated CSPI (climate site productivity index) as the #75 site-productivity covariate, +end to end. Result: CSPI is real and well-ordered, but its production value is on the spatial +density layer, not the anchored curve asymptote. Along the way: filled the canonical-CI carbon +pools (Block 4) from FIA, made SDI/RD projectable, built drop-in CONUS spatial overlays, and +retired the long-standing GDAL/terra blocker. Two external gates remain: the team's beta/clamp +sign-off (CSPI merge/publish) and future-climate data acquisition (Block 2). + +--- + +## 2. The CSPI investigation (the spine of the session) + +| Step | Finding | +|---|---| +| Extraction | CSPI sampled from the 1 km raster at FIA plot coords server-side, aggregated to fit cells (ft_group|prov_code); 99.9% coverage, 873 cells. Sidesteps the blocked plot-level join (#76). | +| Wiring + 48-state runs (rcp45 + rcp85) | Flagged into the canonical engine with a t0-preserving re-anchor; CONUS t0 +0.02%, 2125 -0.00%; per-state -0.39% to +0.72%, median 0. Redistributive, t0-neutral. | +| Stress test | Knob sweep robust; spatial CV shows CSPI generalizes on the REMEAS asymptotes (+8.5%, r 0.37) but NOT on the HYBRID production fits (+0.0%); Bakuzis site-ordering PASS. | +| Decisive structural finding | A uniform asymptote scalar CANCELS in the t0-anchored reserve multiplier (ME/CA identical cap on vs off). The asymptote-cap production path is closed by proof, not opinion. | +| Spatial redirect | On the absolute density layer CSPI is NOT cancelled: it reallocates ~2% (mean 3.6 Mg/ha, up to 6%) of each state's standing carbon toward productive cells, totals preserved. This is CSPI's production home. | +| Production overlays | Built drop-in EPSG:5070 overlays at 20 km and CONUS 1 km, plus a Maine 30 m proof; geography coherent (E-W productivity divide, PNW/northern-hardwood gains). | + +Bottom line: CSPI improves sub-state spatial carbon accuracy by a few percent; it does not (and +structurally cannot) change the t0-anchored trajectory via the asymptote. Knobs: beta=1.0, +clamp +/-25% (documented assumptions, team sign-off pending). + +Detail: `OODA_cspi_asymptote_scaling.md` (5 addenda), `20260627_cspi_stress_bakuzis_assessment.md`. + +--- + +## 3. Block 4 carbon pools (NA columns -> FIA-anchored full ecosystem) + +FIA-anchored pool ratios (pool / live AG carbon) from TREE + COND, 48 states: + +| Pool | CONUS ratio | Pool | CONUS ratio | +|---|---|---|---| +| Belowground (live) | 0.196 | Litter | 0.207 | +| Standing + down dead | 0.231 | Soil organic | 1.925 (dominant) | +| Understory | 0.048 | **Total non-AG** | **2.61x AG** | + +Filled the six NA pool columns (`canonical_pools/`): CONUS reserve live AGC 10,901 Tg -> total +ecosystem 38,869 Tg (live AG = 28%, soil ~53%), matching published US forest budgets. Rule: +live-linked pools (BG, understory) scale with AGC(t); slow pools held at t0 (soil always). The +dead/litter coupling sensitivity bounds 100-yr total ecosystem change to +13.5% (slow pools +constant) .. +18.5% (fully track AGC); truth likely +14-16%. + +Detail: `20260627_trackAB_terra_pools.md`. + +--- + +## 4. SDI / RD projection (the structure gap, closed) + +The carbon engine projects AGC but not TPA/QMD. Fix: fit SDI~AGC from FIA per-plot data +(46 states, mean r 0.88) and project SDI from the engine's AGC; RD = SDI/SDImax. Fills +sdi_mean_wtd / rd_mean_wtd over the trajectory. CONUS reserve RD 0.44 (2025) -> 0.54 (2125); +the t0 value cross-validates the direct FIA measurement (RD 0.46). t0 also computed directly +(`ycx_sdi_rd.csv`: CONUS SDI 207, RD 0.46). SDImax = 99th-pct plot SDI per state (per-cell +SDImax is the refinement). + +Detail: `20260627_tracks123_render_sdi_climate.md`. + +--- + +## 5. Infrastructure: the GDAL/terra blocker is retired + +terra is unfixable on Cardinal (needs libproj.so.25 / libgdal.so.33; no proj/gdal modules +exist to relink/rebuild) but moot: rasterio (GDAL 3.9.3, bundled) works and is the supported +path. All raster work this session used rasterio. 30 m TreeMap rasters read fine in EPSG:5070. +ACTION for the handoffs: replace "gdal/terra broken, avoid terra" with "use rasterio." + +--- + +## 6. Deliverables (all on Cardinal ~/yield_curves_conus and local + ~/Documents/Claude/yield_curves_conus_summary) + +Scripts: +- `ycx_cspi_cell_covariate.py` — CSPI extraction -> `ycx_cell_cspi.csv` (873 cells) +- `ycx_cspi_scale.R` — bounded/damped/shrunk asymptote scalar module +- `ycx_canonical_ci_fiadb_cspi.R` — flagged canonical engine (scaling + t0-pin) +- `ycx_canonical_ci_fiadb_remeas_cspi.R` — remeas-track variant +- `ycx_cspi_reconcile.py` — CONUS reconciliation +- `ycx_cspi_stress.py` — knob sweep + spatial CV + site-ordering +- `ycx_cspi_spatial.py` — spatial redistribution prototype +- `ycx_cspi_raster.py`, `ycx_cspi_raster_albers.py`, `ycx_cspi_conus_overlay.py`, `ycx_cspi_30m_me.py` — spatial rasters/overlays +- `ycx_pool_ratios.py` -> `ycx_pool_ratios.csv`; `ycx_pool_expand.py` -> `canonical_pools/` +- `ycx_sdi_rd.py` -> `ycx_sdi_rd.csv`; `ycx_sdi_agc_fit.py` -> `ycx_sdi_agc_fit.csv`; `ycx_sdi_project.py` + +Spatial products (drop-in, EPSG:5070 + bounds.json): `conus_cspi_scalar.{png,tif}`, +`conus_yc_agc_{base,cspi,cspidiff}.png`, `me_cspi_scalar_300m.tif`. + +Figures: `cspi_vs_asymptote.png`, `cspi_raster_diff.png`, `conus_cspi_scalar_review.png`, +`conus_yc_cspi_albers_review.png`, `me_cspi_scalar_300m.png`. + +Docs: `OODA_cspi_asymptote_scaling.md`, `20260627_cspi_stress_bakuzis_assessment.md`, +`20260627_trackAB_terra_pools.md`, `20260627_tracks123_render_sdi_climate.md`, +`HANDOFF_CSPI_75_2026-06-27.md`, this file. + +GitHub: draft PR #91 (holoros/perseus-forest-intelligence, branch +`feature/cspi-asymptote-covariate`) — CSPI scripts, covariate table, ADR 0005, stress memo, +spatial scripts. Flag off by default; no live-data regen. DRAFT pending sign-off. + +Zenodo: v1.2.0 staged (NOT published) at Cardinal +`zenodo_staging/perseus-yield-curves/v1.2/` against concept DOI 10.5281/zenodo.20959003. + +--- + +## 7. Open gates (neither resolvable by autopilot) + +1. **CSPI beta / clamp sign-off (team).** beta=1.0, clamp +/-25%. On sign-off: decide the + production home (recommended: spatial density layer, since the asymptote cap is a no-op), + then merge #91 + deploy the CONUS CSPI overlay additively to gh-pages + publish Zenodo v1.2. +2. **Block 2 rcp-native climate: future-climate data.** Needs downscaled rcp45/85 future normals + (ClimateNA future or CMIP) -> clim_ranger -> per-rcp CSI. Engine hook already present. + +--- + +## 8. Queued next steps (when unblocked / resourced) + +- Full CONUS 30 m density render: tiled rasterio over TreeMap2016_FLDTYPCD.tif (forest type -> + cell -> density x CSPI); pipeline proven on Maine. +- Block 4 refinements: slow dead/litter input-decay dynamics; per-cell SDImax for RD. +- Deploy the CONUS CSPI overlay + pooled/SDI-filled CI to the explorer after sign-off. +- Block 2 once future climate is obtained. + +--- + +## 9. Resume / reproduction notes + +- Cardinal access: hpc-cardinal skill (key auto-loaded from ~/Documents/Claude/.ssh-cardinal); + re-run the session-setup block each bash call (sandbox is per-call). +- Raster work: rasterio (GDAL 3.9.3), NOT terra. FIA true coords stay server-side; only gridded + / coordinate-free outputs leave (true-coords protocol honored throughout). +- Engine: `module load gcc/12.3.0 R/4.4.0`. Flag `YCX_CSPI_ASYM=1` turns on CSPI in the + `*_cspi.R` engines; default off reproduces the live engine. +- Splicing R via python on Cardinal: use a QUOTED heredoc (`<<'PY'`) or the shell mangles quotes. +- The canonical/ CI is the FIA-anchored production data (CONUS reserve t0 = 10,901 Tg). The + ci_full*/ and ci_rem*/ dirs are session experiments (flat2400-heavy; comparison only). diff --git a/scripts/yield_curve_engine/cspi/conus_yc_cspi_albers_review.png b/scripts/yield_curve_engine/cspi/conus_yc_cspi_albers_review.png new file mode 100644 index 0000000..05ec47f Binary files /dev/null and b/scripts/yield_curve_engine/cspi/conus_yc_cspi_albers_review.png differ diff --git a/scripts/yield_curve_engine/cspi/cspi_raster_diff.png b/scripts/yield_curve_engine/cspi/cspi_raster_diff.png new file mode 100644 index 0000000..c91ed5c Binary files /dev/null and b/scripts/yield_curve_engine/cspi/cspi_raster_diff.png differ diff --git a/scripts/yield_curve_engine/cspi/cspi_validation_figure.png b/scripts/yield_curve_engine/cspi/cspi_validation_figure.png new file mode 100644 index 0000000..a5d58c9 Binary files /dev/null and b/scripts/yield_curve_engine/cspi/cspi_validation_figure.png differ diff --git a/scripts/yield_curve_engine/cspi/cspi_vs_asymptote.png b/scripts/yield_curve_engine/cspi/cspi_vs_asymptote.png new file mode 100644 index 0000000..9160c86 Binary files /dev/null and b/scripts/yield_curve_engine/cspi/cspi_vs_asymptote.png differ diff --git a/scripts/yield_curve_engine/cspi/ycx_canonical_ci_fiadb_cspi.R b/scripts/yield_curve_engine/cspi/ycx_canonical_ci_fiadb_cspi.R new file mode 100644 index 0000000..82c65ab --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_canonical_ci_fiadb_cspi.R @@ -0,0 +1,145 @@ +## ycx_canonical_ci_fiadb.R +## CONUS yield curves applied to FIADB, emitted in the SAME structure as the +## other PERSEUS models (CEM-style per-state CI CSV: scenario, cycle, year, +## mmt_*_mean/lo/hi, ..., n_sims, n_conditions), so it ingests via the canonical +## adapter alongside CEM/FVS/CBM. +## +## Scenarios (CEM harvest_Q multipliers on the working harvested fraction): +## No_harvest 0.00, Harvest_m25_mill 0.75, BAU 1.00, Harvest_p25_pulp 1.25, +## Harvest_p50_biomass 1.50. managed = phi*harvest_traj + (1-phi)*reserve, +## phi = min(harvested_share * harvest_Q, 1 - reserved_share) per state (FIADB +## management shares); No_harvest = reserve. +## Climate arm (rcp45/rcp85): productivity multiplier on the curve asymptote from +## the CSI stress trajectory, pm(t)=1+SCALE_rcp*CSI_BETA*(CSI(t)/CSI_2030-1), +## SCALE rcp45=0.5, rcp85=1.0 (rcp85 = full CSI stress). [YC has no rcp-native +## climate; this is the documented YC climate parameterization.] +## Expansion (FIADB): state total = density * area, area = n_plots*A0 with A0 +## anchored so AG-carbon 2025 reproduces fia.json tg_agc (median A0 elsewhere), +## matching the existing yc_fia_empirical area model. +## Metrics emitted (YC AG subset of the CEM schema; BGC/dead/litter/soil/ +## understory/total_ecosystem/rd/sdi left NA -- not YC outputs): +## mmt_agc (Tg C), mmt_biomass (Tg dry), total_vol_mcf, merch_vol_mcf, +## total_area_mha. CI band = +/-8% (fit/parameter uncertainty placeholder). +## +## Usage: Rscript ycx_canonical_ci_fiadb.R + +args<-commandArgs(trailingOnly=TRUE) +ST <-toupper(args[1]); OUT<-args[2]; FIAJSON<-args[3]; SHARES<-args[4]; RCP<-args[5] +dir.create(OUT,showWarnings=FALSE,recursive=TRUE) +cfg<-file.path(OUT,"..","config"); if(!dir.exists(cfg)) cfg<-"config" +LBAC_TO_MGHA<-0.00045359237*2.4710538; TONAC_TO_MGHA<-2.2417; CUFTAC_TO_M3HA<-0.069972; AC_PER_HA<-2.4710538 +START<-2025L; STEP<-5L; HORIZON<-100L; years<-seq(START,START+HORIZON,STEP) +REGEN_AGE<-5L; CI_BAND<-0.08 +SCEN<-list(No_harvest=0.00, Harvest_m25_mill=0.75, BAU=1.00, Harvest_p25_pulp=1.25, Harvest_p50_biomass=1.50) +SCALE_RCP<-if(RCP=="rcp85") 1.0 else 0.5 +## owner-typical regimes (from ycx_02_perseus.R) +REG<-list(Industrial=list(type="clearcut",R=45),NIPF=list(type="partial",E=20,f=0.30), + State=list(type="partial",E=25,f=0.25),`Public-Other`=list(type="partial",E=30,f=0.15)); DEF_REG<-"NIPF" +hyb<-function(age,p) p[1]*(1-exp(-p[2]*age))^p[3]*exp(-p[4]*pmax(0,age-p[5])) +RESP<-c(carbon_lbac="mmt_agc", agb_tonac="mmt_biomass", voltot_cuftac="total_vol_mcf", merchvol_cuftac="merch_vol_mcf") +CONV<-c(carbon_lbac=LBAC_TO_MGHA, agb_tonac=TONAC_TO_MGHA, voltot_cuftac=1, merchvol_cuftac=1) # vols stay cuft/ac -> scaled at expansion + +cat(sprintf("[ci-fiadb] %s %s\n",ST,RCP)) +## ---- inputs ---- +fit<-read.csv(file.path(OUT,"..",sprintf("ycx_%s_hybrid_fits.csv",ST)),stringsAsFactors=FALSE) +if (identical(Sys.getenv("YCX_CSPI_ASYM"),"1")) { + .cs<-tryCatch(read.csv(file.path(OUT,"..","ycx_cell_cspi.csv"),stringsAsFactors=FALSE),error=function(e)NULL) + if(!is.null(.cs)){ + .REF<-56.36;.BETA<-1.0;.CLO<-0.80;.CHI<-1.25;.N0<-30 + .cm<-setNames(.cs$cspi_mean[.cs$level=="cell"],.cs$key[.cs$level=="cell"]) + .k2<-paste(fit$ft_group,fit$prov_code,sep="|") + .cv<-.cm[.k2];.raw<-(.cv/.REF)^.BETA;.raw[!is.finite(.raw)]<-1 + .cl<-pmin(pmax(.raw,.CLO),.CHI) + .nn<-suppressWarnings(as.numeric(fit$n_plots));.nn[!is.finite(.nn)]<-0 + .scal<-1+(.N0/(.N0+.nn))*(.cl-1) + .fit0<-fit; fit$A<-fit$A*.scal + .H0<-list(); for(i in seq_len(nrow(.fit0))){r<-.fit0[i,]; if(r$response=="carbon_lbac"){id<-if(r$scope=="state")"state" else r$cell_key; if(is.null(.H0[[id]])) .H0[[id]]<-c(r$A,r$k,r$p,r$d,r$Astar)}} + geth0<-function(cell){v<-.H0[[cell]]; if(!is.null(v))return(v); .H0[["state"]]} + .CSPI_ON<-TRUE + cat(sprintf("[cspi] %s scaled %d rows scalar med=%.3f range %.2f-%.2f\n",ST,nrow(fit),median(.scal,na.rm=TRUE),min(.scal,na.rm=TRUE),max(.scal,na.rm=TRUE))) + } +} +H<-list(); for(i in seq_len(nrow(fit))){r<-fit[i,]; id<-paste(r$response, if(r$scope=="state")"state" else r$cell_key, sep="@@"); if(is.null(H[[id]])) H[[id]]<-c(r$A,r$k,r$p,r$d,r$Astar)} +geth<-function(rv,cell){v<-H[[paste(rv,cell,sep="@@")]]; if(!is.null(v))return(v); H[[paste(rv,"state",sep="@@")]]} +mem<-read.csv(file.path(cfg,sprintf("ycx_membership_%s.csv",ST)),stringsAsFactors=FALSE) +mem<-mem[order(mem$PLT_CN,-mem$INVYR),]; mem<-mem[!duplicated(mem$PLT_CN),] +mem<-mem[!is.na(mem$STDAGE)&mem$STDAGE>0,] +mem$cell<-paste(mem$ft_group,mem$prov_code,mem$owner4,sep="|") +mem$oreg<-ifelse(mem$owner4 %in% names(REG),mem$owner4,DEF_REG) +sh<-read.csv(SHARES,stringsAsFactors=FALSE); shr<-sh[sh$state==ST,] +harv<-if(nrow(shr)) shr$harvested_share[1] else 0.12; resv<-if(nrow(shr)) shr$reserved_share[1] else 0.02 +csi<-read.csv(file.path(cfg,"csi_states_ext.csv"),stringsAsFactors=FALSE); cr<-csi[csi$state==ST,] +CSI_BETA<-as.numeric(readLines(file.path(cfg,"ycx_beta.txt"))[1]); if(!is.finite(CSI_BETA))CSI_BETA<-0.45 +pm<-rep(1,length(years)) +if(nrow(cr)==1 && is.finite(cr$csi_2090)){ ci<-approx(c(2030,2060,2090),c(cr$csi_2030,cr$csi_2060,cr$csi_2090),xout=pmin(pmax(years,2030),2090),rule=2)$y + pm<-1+SCALE_RCP*CSI_BETA*(ci/cr$csi_2030-1) } +fa<-tryCatch(read.csv(FIAJSON,stringsAsFactors=FALSE),error=function(e) NULL) # FIAJSON = csv: state,tg_agc +## data-driven CI band: per-response relative population lack-of-fit (ycx_fit_band.R), +## replacing the flat CI_BAND placeholder. Fallback to CI_BAND if a value is missing. +bnd<-tryCatch(read.csv(file.path(OUT,"..","ycx_fit_bands.csv"),stringsAsFactors=FALSE),error=function(e)NULL) +band_of<-function(rvname){ if(!is.null(bnd)){ x<-suppressWarnings(as.numeric(bnd$rel_band[bnd$state==ST & bnd$response==rvname])); if(length(x)&&is.finite(x[1])) return(x[1]) }; CI_BAND } + +## ---- per-plot reserve + harvest trajectories, per response, climate-adjusted ---- +ny<-length(years); n<-nrow(mem) +proj_resv<-function(age0,p){ hyb(age0+(years-START),p) } +proj_harv<-function(age0,p,reg){ s<-numeric(ny) + if(reg$type=="clearcut"){ age<-age0; for(j in 1:ny){ if(j>1){age<-age+STEP; if(age>=reg$R) age<-REGEN_AGE+(age-reg$R)}; s[j]<-hyb(age,p)} } + else { S<-hyb(age0,p); for(j in 1:ny){ ra<-age0+(years[j]-START); if(j>1){S<-max(S+(hyb(ra,p)-hyb(ra-STEP,p)),0); if(floor(ra/reg$E)>floor((ra-STEP)/reg$E)) S<-(1-reg$f)*S}; s[j]<-S} } + s } +## accumulate per response: density-sum over plots (mean density * n) for reserve & harvest +sumR<-setNames(vector("list",length(RESP)),names(RESP)); sumH<-sumR; npl<-setNames(rep(0L,length(RESP)),names(RESP)) +for(rv in names(RESP)){ R<-numeric(ny); Hm<-numeric(ny); cnt<-0L + for(i in 1:n){ p<-geth(rv,mem$cell[i]); if(is.null(p)) next + r<-proj_resv(mem$STDAGE[i],p)*pm; h<-proj_harv(mem$STDAGE[i],p,REG[[mem$oreg[i]]])*pm + r[!is.finite(r)|r<0]<-0; h[!is.finite(h)|h<0]<-0 + R<-R+r; Hm<-Hm+h; cnt<-cnt+1L } + sumR[[rv]]<-as.numeric(R); sumH[[rv]]<-as.numeric(Hm); npl[rv]<-cnt } +# mean density per plot +for(rv in names(RESP)){ sumR[[rv]]<-sumR[[rv]]/max(npl[rv],1); sumH[[rv]]<-sumH[[rv]]/max(npl[rv],1) } + +## ---- FIADB area model: anchor to REAL per-state forest area ---- +## Priority: (1) fia.json official AG-carbon anchor for published states -> solve +## area so reserve carbon 2025 reproduces fia.json exactly; (2) else real +## per-state forest area from TreeMap 2022 (forested-pixel area, EPSG:5070). +## On the 7 fia.json states the two areas agree to <=~5% (e.g. OR 10.11 vs +## 10.55 Mha), so the seam is smooth. The old flat A0=2400 ha/plot assumed every +## FIA plot represented 2400 ha of FOREST, which over-expands sparsely forested +## states (e.g. WV 7184 plots -> 17.2 Mha, exceeding the state's land area). +nplots<-npl["carbon_lbac"] +dens_c_2025<-sumR[["carbon_lbac"]][1]*LBAC_TO_MGHA # Mg C/ha reserve 2025 +tg<-if(!is.null(fa)&&ST %in% fa$state) fa$tg_agc[fa$state==ST][1] else NA +tma<-tryCatch(read.csv(file.path(OUT,"treemap_area.csv"),stringsAsFactors=FALSE),error=function(e)NULL) +tm_mha<-if(!is.null(tma)&&ST %in% tma$state) tma$area_mha[tma$state==ST][1] else NA +if(is.finite(tg)&&dens_c_2025>0&&nplots>0){ area_ha<-tg*1e6/dens_c_2025; anchor_src<-"fia.json" } else +if(is.finite(tm_mha)){ area_ha<-tm_mha*1e6; anchor_src<-"treemap_area" } else { area_ha<-nplots*2400; anchor_src<-"flat2400" } +if (exists(".CSPI_ON") && isTRUE(.CSPI_ON) && anchor_src!="fia.json") { + .db<-0;.cnt<-0L + for(i in 1:n){ p<-geth0(mem$cell[i]); if(is.null(p)) next; v<-hyb(mem$STDAGE[i],p)*pm[1]; if(is.finite(v)&&v>0){.db<-.db+v;.cnt<-.cnt+1L} } + .dens_base<-(.db/max(.cnt,1))*LBAC_TO_MGHA + if(.dens_base>0 && dens_c_2025>0){ area_ha<-area_ha*.dens_base/dens_c_2025; area_mha<-area_ha/1e6; anchor_src<-paste0(anchor_src,"+t0pin") } +} +A0<-area_ha/max(nplots,1); area_mha<-area_ha/1e6 +phi_bau<-min(harv, 1-resv) + +## ---- build CI rows: scenario x year ---- +rows<-list() +to_total<-function(rv, dens_meanperplot){ # dens_meanperplot in native units/ac; return state total in metric unit + if(rv=="carbon_lbac") return(dens_meanperplot*LBAC_TO_MGHA*area_ha/1e6) # Tg C + if(rv=="agb_tonac") return(dens_meanperplot*TONAC_TO_MGHA*area_ha/1e6) # Tg dry + if(rv=="voltot_cuftac") return(dens_meanperplot*(area_ha*AC_PER_HA)/1e6) # Mcf (cuft total /1e6) + if(rv=="merchvol_cuftac")return(dens_meanperplot*(area_ha*AC_PER_HA)/1e6) } +for(sc in names(SCEN)){ Q<-SCEN[[sc]]; phi<-min(phi_bau*Q, 1-resv) + for(j in 1:ny){ row<-list(scenario=sc, cycle=j, year=years[j]) + for(rv in names(RESP)){ blended<-phi*sumH[[rv]][j]+(1-phi)*sumR[[rv]][j]; tot<-to_total(rv,blended) + mc<-RESP[[rv]]; bb<-band_of(rv); row[[paste0(mc,"_mean")]]<-round(tot,5); row[[paste0(mc,"_lo")]]<-round(tot*(1-bb),5); row[[paste0(mc,"_hi")]]<-round(tot*(1+bb),5) } + ## CEM pool columns the YC engine does not model -> NA (schema parity for ingest_cem_state.R) + for(mc in c("mmt_bgc","mmt_dead_c","mmt_litter_c","mmt_soil_c","mmt_under_c","mmt_total_c","rd_mean_wtd","sdi_mean_wtd")) + for(s in c("_mean","_lo","_hi")) row[[paste0(mc,s)]]<-NA_real_ + row[["total_area_mha_mean"]]<-round(area_mha,5); row[["total_area_mha_lo"]]<-round(area_mha,5); row[["total_area_mha_hi"]]<-round(area_mha,5) + row[["n_sims"]]<-1; row[["n_conditions"]]<-nplots + rows[[length(rows)+1]]<-as.data.frame(row,stringsAsFactors=FALSE) } } +df<-do.call(rbind,rows) +f<-file.path(OUT,sprintf("ci_yc_fiadb_%s_%s.csv",tolower(ST),RCP)); write.csv(df,f,row.names=FALSE) +cat(sprintf("[ci-fiadb] %s %s: anchor=%s A0=%.0f ha/plot area=%.2f Mha phi_bau=%.3f -> %s (%d rows)\n",ST,RCP,anchor_src,A0,area_mha,phi_bau,basename(f),nrow(df))) +cat(sprintf(" reserve agc 2025=%.1f 2125=%.1f Tg; BAU agc 2125=%.1f\n", + df$mmt_agc_mean[df$scenario=="No_harvest"&df$year==2025], df$mmt_agc_mean[df$scenario=="No_harvest"&df$year==2125], df$mmt_agc_mean[df$scenario=="BAU"&df$year==2125])) diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_30m_me.py b/scripts/yield_curve_engine/cspi/ycx_cspi_30m_me.py new file mode 100644 index 0000000..db218dc --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_30m_me.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Track A 30 m proof: CSPI scalar surface for Maine on the TreeMap grid, via rasterio +(terra not needed). Reprojects the 1 km CSPI raster onto the ME_TM_22 grid (EPSG:5070), +masks to forested TreeMap pixels, applies the production scalar clamp((CSPI/REF)^beta,0.8,1.25), +and renders. Demonstrates the unblocked 30 m raster pipeline end to end. Output at ~300 m +(decimation factor 10) to keep the proof light; finer res is the same code with a smaller factor.""" +import numpy as np, rasterio +from rasterio.warp import reproject, Resampling +import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt +REF=56.36; BETA=1.5; FACT=10 # beta=1.5 (CV-optimal, deep assessment 2026-06-28); 30m*10=~300m proof grid +CLO,CHI=0.70,1.45 # clamp widened to match beta=1.5 +TM="/users/PUOM0008/crsfaaron/TREEMAP/ME_TM_22.tif" +CSPI="/fs/scratch/PUOM0008/crsfaaron/cspi_v7/v2both/CSPI_v2_5component_1km.tif" +with rasterio.open(TM) as t: + H,W=t.height//FACT, t.width//FACT + # decimated read for forest mask + transform + tm=t.read(1,out_shape=(1,H,W),resampling=Resampling.nearest) + dst_tf=t.transform*t.transform.scale(t.width/W, t.height/H) + dst_crs=t.crs; nod=t.nodata +forest=(tm!=nod)&(tm>0) +cspi=np.full((H,W),np.nan,dtype="float32") +with rasterio.open(CSPI) as c: + reproject(source=rasterio.band(c,1),destination=cspi, + src_transform=c.transform,src_crs=c.crs, + dst_transform=dst_tf,dst_crs=dst_crs,resampling=Resampling.bilinear) +scal=np.clip((cspi/REF)**BETA,CLO,CHI) +scal=np.where(forest & np.isfinite(cspi),scal,np.nan) +v=scal[np.isfinite(scal)] +print(f"ME 300m grid {H}x{W}; forest pixels {int(forest.sum())}; CSPI-scalar valid {v.size}") +print(f"scalar: min {v.min():.3f} median {np.median(v):.3f} max {v.max():.3f}; %clamped {100*np.mean((v<=0.8001)|(v>=0.2499+1)):.1f}") +import os; os.makedirs("out",exist_ok=True) +# GeoTIFF +prof=dict(driver="GTiff",height=H,width=W,count=1,dtype="float32",crs=dst_crs,transform=dst_tf,nodata=np.nan,compress="deflate") +with rasterio.open("out/me_cspi_scalar_300m.tif","w",**prof) as o: o.write(scal.astype("float32"),1) +fig,ax=plt.subplots(figsize=(5,6),dpi=130) +im=ax.imshow(scal,cmap="RdBu_r",vmin=0.85,vmax=1.15); ax.set_title("Maine 30m(->300m) CSPI asymptote scalar\n(TreeMap-masked, EPSG:5070, via rasterio)",fontsize=9) +ax.set_xticks([]);ax.set_yticks([]); fig.colorbar(im,ax=ax,shrink=0.6,label="CSPI scalar") +fig.tight_layout(); fig.savefig("out/me_cspi_scalar_300m.png"); fig.set_size_inches(2.6,3.1); fig.savefig("out/me_cspi_scalar_300m_thumb.png",dpi=72) +print("wrote out/me_cspi_scalar_300m.{tif,png}") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_cell_covariate.py b/scripts/yield_curve_engine/cspi/ycx_cspi_cell_covariate.py new file mode 100644 index 0000000..f91a8c3 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_cell_covariate.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +ycx_cspi_cell_covariate.py (#75, CSPI raster-covariate path; sidesteps the #76 plot join) + +Samples the wall-to-wall CSPI raster at each FIA plot's coordinates and aggregates +to the SAME cells the remeasurement curves were fit on (state x ecoregion x forest-type), +producing a coordinate-free per-cell CSPI table for asymptote scaling. + +Compliance: FIA true coordinates are read only to sample the raster, here, on Cardinal. +The output table carries NO coordinates, only cell_key -> cspi_mean. Run on Cardinal. + +Inputs (Cardinal): + ~/yield_curves_conus/config/ycx_membership_.csv (48 states; has LAT,LON,cell_key,...) + CSPI raster (default cspi_4c_raw.tif, EPSG:4326, ~30 m) +Output: + ~/yield_curves_conus/ycx_cell_cspi.csv + +Run: python3 ycx_cspi_cell_covariate.py [raster.tif] +""" +import os, glob, sys, csv, math +import numpy as np +import rasterio + +CFG = os.path.expanduser("~/yield_curves_conus/config") +RAST = sys.argv[1] if len(sys.argv) > 1 else \ + "/fs/scratch/PUOM0008/crsfaaron/cspi_v7/v2both/CSPI_v2_5component_1km.tif" +OUT = os.path.expanduser("~/yield_curves_conus/ycx_cell_cspi.csv") +CHUNK = 50000 # points per sampling batch + +def load_membership(): + rows = [] + for f in sorted(glob.glob(os.path.join(CFG, "ycx_membership_*.csv"))): + with open(f, newline="") as fh: + for r in csv.DictReader(fh): + try: + lat = float(r["LAT"]); lon = float(r["LON"]) + except (ValueError, KeyError): + continue + if not (math.isfinite(lat) and math.isfinite(lon)): + continue + ft = r.get("ft_group",""); prov = r.get("prov_code","") + # Fit cells are keyed ft_group|prov_code (2-part); membership cell_key + # adds owner (3-part). Build the 2-part key to match ycx__remeas_fits. + fitcell = f"{ft}|{prov}" + rows.append((r.get("STATECD",""), fitcell, + ft, prov, r.get("owner4",""), lon, lat)) + return rows + +def sample_raster(rows): + vals = np.full(len(rows), np.nan, dtype="float64") + with rasterio.open(RAST) as r: + b = r.bounds + print(f"[raster] {os.path.basename(RAST)} CRS={r.crs} bounds=" + f"[{b.left:.3f},{b.bottom:.3f},{b.right:.3f},{b.top:.3f}] " + f"res={[round(v,5) for v in r.res]} nodata={r.nodata}", flush=True) + coords = [(x[5], x[6]) for x in rows] + for i in range(0, len(coords), CHUNK): + block = coords[i:i+CHUNK] + for j, v in enumerate(r.sample(block, indexes=1)): + vv = float(v[0]) + vals[i+j] = vv if math.isfinite(vv) else np.nan + print(f" sampled {min(i+CHUNK,len(coords))}/{len(coords)}", flush=True) + return vals + +def agg(keys, vals): + """mean/sd/n over finite vals grouped by key tuple.""" + d = {} + for k, v in zip(keys, vals): + if not math.isfinite(v): + continue + d.setdefault(k, []).append(v) + out = {} + for k, a in d.items(): + a = np.asarray(a) + out[k] = (a.size, float(a.mean()), float(a.std(ddof=1)) if a.size > 1 else 0.0) + return out + +def main(): + rows = load_membership() + print(f"[membership] {len(rows)} plots across {len(set(r[0] for r in rows))} states", flush=True) + vals = sample_raster(rows) + nfin = int(np.isfinite(vals).sum()) + print(f"[sample] valid CSPI for {nfin}/{len(vals)} plots " + f"({100*nfin/max(1,len(vals)):.1f}%)", flush=True) + finite = vals[np.isfinite(vals)] + print(f"[scale] CSPI min={finite.min():.3f} median={np.median(finite):.3f} " + f"max={finite.max():.3f}", flush=True) + ref = float(np.median(finite)) # scale-invariant reference + + cell = agg([r[1] for r in rows], vals) # full cell_key + sp = agg([(r[0], r[3]) for r in rows], vals) # state x prov_code + st = agg([r[0] for r in rows], vals) # state + conus = (finite.size, float(finite.mean()), float(finite.std(ddof=1))) + + with open(OUT, "w", newline="") as fh: + w = csv.writer(fh) + w.writerow(["level","key","n_plots","cspi_mean","cspi_sd","cspi_scalar"]) + for k,(n,m,s) in sorted(cell.items()): + w.writerow(["cell", k, n, f"{m:.5f}", f"{s:.5f}", f"{m/ref:.5f}"]) + for (state,prov),(n,m,s) in sorted(sp.items()): + w.writerow(["state_prov", f"{state}|{prov}", n, f"{m:.5f}", f"{s:.5f}", f"{m/ref:.5f}"]) + for k,(n,m,s) in sorted(st.items()): + w.writerow(["state", k, n, f"{m:.5f}", f"{s:.5f}", f"{m/ref:.5f}"]) + n,m,s = conus + w.writerow(["conus", "CONUS", n, f"{m:.5f}", f"{s:.5f}", "1.00000"]) + print(f"[write] {OUT}: {len(cell)} cells, {len(sp)} state_prov, {len(st)} states " + f"(reference median CSPI={ref:.4f}; cspi_scalar = cell_mean / reference)", flush=True) + +if __name__ == "__main__": + main() diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_conus_overlay.py b/scripts/yield_curve_engine/cspi/ycx_cspi_conus_overlay.py new file mode 100644 index 0000000..475c83a --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_conus_overlay.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Track 1: CONUS CSPI scalar overlay at native ~1 km on the explorer's EPSG:5070 Albers grid. +Drop-in for public/raster/. Reprojects the 1 km CSPI raster onto the explorer extent, applies +the production scalar clamp((CSPI/REF)^beta,0.8,1.25). This is the reusable CONUS CSPI multiplier +(the source CSPI is 1 km, so 1 km is its native resolution; 30 m applies only to forest-type +density products). Run on Cardinal from ~/yield_curves_conus.""" +import numpy as np, rasterio, json +from rasterio.warp import reproject, Resampling +import matplotlib; matplotlib.use("Agg") +import matplotlib.pyplot as plt, matplotlib.cm as cm, matplotlib.colors as mcolors +X0,Y1,X1,Y0=-2561585.0,1714610.0,2463176.0,-1604872.736 +REF=56.36; BETA=1.5; RES=1000.0 # beta raised to CV-optimal 1.5 (deep assessment 2026-06-28) +CLO,CHI=0.70,1.45 # clamp widened from [0.80,1.25] to admit the stronger beta +nx=int(round((X1-X0)/RES)); ny=int(round((Y1-Y0)/RES)) +from affine import Affine +dst_tf=Affine(RES,0,X0,0,-RES,Y1) +cspi=np.full((ny,nx),np.nan,dtype="float32") +with rasterio.open("/fs/scratch/PUOM0008/crsfaaron/cspi_v7/v2both/CSPI_v2_5component_1km.tif") as c: + reproject(source=rasterio.band(c,1),destination=cspi,src_transform=c.transform,src_crs=c.crs, + dst_transform=dst_tf,dst_crs="EPSG:5070",resampling=Resampling.bilinear) +scal=np.where(np.isfinite(cspi),np.clip((cspi/REF)**BETA,CLO,CHI),np.nan) +v=scal[np.isfinite(scal)] +print(f"CONUS 1km Albers grid {ny}x{nx}; valid {v.size}; scalar min {v.min():.3f} median {np.median(v):.3f} max {v.max():.3f}") +import os; os.makedirs("out",exist_ok=True) +# drop-in overlay (bare RGBA, alpha nodata) + bounds.json +norm=mcolors.Normalize(0.8,1.25); rgba=cm.get_cmap("RdBu_r")(norm(np.clip(scal,0.8,1.25))) +rgba[...,3]=np.where(np.isfinite(scal),1.0,0.0) +plt.imsave("out/conus_cspi_scalar.png",(rgba*255).astype(np.uint8)) +json.dump({"x0":X0,"y1":Y1,"x1":X1,"y0":Y0},open("out/conus_cspi_scalar_bounds.json","w")) +# GeoTIFF for reuse as a multiplier layer +prof=dict(driver="GTiff",height=ny,width=nx,count=1,dtype="float32",crs="EPSG:5070",transform=dst_tf,nodata=np.nan,compress="deflate") +with rasterio.open("out/conus_cspi_scalar.tif","w",**prof) as o: o.write(scal.astype("float32"),1) +# labeled review +fig,ax=plt.subplots(figsize=(8,4.4),dpi=130) +im=ax.imshow(scal,cmap="RdBu_r",vmin=0.85,vmax=1.15,aspect=1.0); ax.set_title("CONUS CSPI asymptote scalar (1km, EPSG:5070)",fontsize=10) +ax.set_xticks([]);ax.set_yticks([]); fig.colorbar(im,ax=ax,shrink=0.7,label="CSPI scalar") +fig.tight_layout(); fig.savefig("out/conus_cspi_scalar_review.png"); fig.set_size_inches(4,2.2); fig.savefig("out/conus_cspi_scalar_review_thumb.png",dpi=72) +print("wrote out/conus_cspi_scalar.{png,tif} + bounds.json + review") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_deepassess.py b/scripts/yield_curve_engine/cspi/ycx_cspi_deepassess.py new file mode 100644 index 0000000..c14042a --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_deepassess.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Deep CSPI assessment to decide next steps. Run on Cardinal from ~/yield_curves_conus. +Three decision-critical tests on the REMEAS cell asymptotes (where CSPI's signal lives): + T1 optimal beta: leave-one-ecoregion-out CV held-out RMSE of log(A) for baseline (ft-mean) + vs CSPI at free slope and capped beta in {0.5,1,1.5,2}. Finds the production beta. + T2 CSPI vs simpler covariates: same CV skill for CSPI vs ClimateNA site index vs latitude. + Decision: is CSPI worth the complexity over what is already available? + T3 incremental value: within-ft partial correlation of log(A) with CSPI controlling for SI. + If ~0, CSPI is redundant with the existing site-index product. +Covariates are cell means (ft_group|prov_code), sampled at PUBLIC plot coords (fuzz washes out +at cell scale). No outbound fetch; all rasters staged.""" +import csv, glob, math, os, numpy as np, rasterio +from pyproj import Transformer + +CSPI_TIF="/fs/scratch/PUOM0008/crsfaaron/cspi_v7/v2both/CSPI_v2_5component_1km.tif" +SI_TIF ="/users/PUOM0008/crsfaaron/SiteIndex/ClimateNA_SI_m.tif" +cfg="config"; SUB=3 # subsample every SUBth plot for raster sampling (cell means stable) + +# remeas carbon asymptote per cell (ft|prov), n>=10 +A={} +for f in glob.glob("ycx_*_remeas_fits.csv"): + for r in csv.DictReader(open(f)): + if r.get("scope")=="cell" and r.get("response")=="carbon_lbac": + try: + a=float(r["A"]); n=int(r.get("n_plots") or 0) + except: continue + if a>0 and n>=10: A[r["ft_group"]+"|"+r["prov_code"]]=(a, r["ft_group"]) + +# gather plot coords + cell keys (subsampled) +keys=[]; lons=[]; lats=[] +for ff in sorted(glob.glob(cfg+"/ycx_membership_*.csv")): + rows=list(csv.DictReader(open(ff))) + for i,r in enumerate(rows): + if i%SUB: continue + k=r.get("ft_group","")+"|"+r.get("prov_code","") + if k not in A: continue + try: lo=float(r["LON"]); la=float(r["LAT"]) + except: continue + if math.isfinite(lo) and math.isfinite(la): + keys.append(k); lons.append(lo); lats.append(la) +lons=np.array(lons); lats=np.array(lats); keys=np.array(keys) +print(f"[join] {len(keys)} plot samples across {len(set(keys))} cells") + +def sample(tif, lon, lat): + with rasterio.open(tif) as r: + tf=Transformer.from_crs("EPSG:4326", r.crs, always_xy=True) + xs,ys=tf.transform(lon,lat) + out=np.full(len(lon),np.nan) + for i,v in enumerate(r.sample(list(zip(xs,ys)),indexes=1)): + vv=float(v[0]); out[i]=vv if math.isfinite(vv) else np.nan + return out +cspi=sample(CSPI_TIF,lons,lats); si=sample(SI_TIF,lons,lats) +print(f"[sample] CSPI valid {np.isfinite(cspi).mean()*100:.0f}% SI valid {np.isfinite(si).mean()*100:.0f}%") + +# cell means +def cellmean(vals): + d={} + for k,v in zip(keys,vals): + if math.isfinite(v): d.setdefault(k,[]).append(v) + return {k:np.mean(v) for k,v in d.items() if len(v)>=3} +cm_cspi=cellmean(cspi); cm_si=cellmean(si); cm_lat=cellmean(lats) +cells=[k for k in A if k in cm_cspi and k in cm_si and k in cm_lat] +ft=np.array([A[k][1] for k in cells]); y=np.log(np.array([A[k][0] for k in cells])) +X={"CSPI":np.log(np.array([cm_cspi[k] for k in cells])), + "SI": np.log(np.array([np.clip(cm_si[k],1e-3,None) for k in cells])), + "LAT": np.array([cm_lat[k] for k in cells])} +prov=np.array([k.split("|")[1] for k in cells]) +print(f"[cells] {len(cells)} with all covariates + asymptote") + +def cv(xv): + se_m=se_b=cnt=0 + for g in set(ft): + m=ft==g + if m.sum()<8 or len(set(prov[m]))<3: continue + lA=y[m]; xx=xv[m]; pv=prov[m] + for h in set(pv): + tr=pv!=h; te=pv==h + if tr.sum()<4 or te.sum()<1: continue + b,a=np.polyfit(xx[tr],lA[tr],1) + se_m+=np.sum((lA[te]-(a+b*xx[te]))**2); se_b+=np.sum((lA[te]-lA[tr].mean())**2); cnt+=te.sum() + return math.sqrt(se_m/cnt), math.sqrt(se_b/cnt), cnt +def wcorr(xv): # within-ft partial corr + xs=[];ys=[] + for g in set(ft): + m=ft==g + if m.sum()>=5: xs+=list(xv[m]-xv[m].mean()); ys+=list(y[m]-y[m].mean()) + return np.corrcoef(xs,ys)[0,1] + +print("\n== T2: CSPI vs simpler covariates (leave-one-ecoregion-out CV of log asymptote) ==") +rb=None +for name in ("CSPI","SI","LAT"): + rm,rbase,n=cv(X[name]); rb=rbase + print(f" {name:5s}: held-out RMSE {rm:.3f} vs baseline {rbase:.3f} ({100*(1-rm/rbase):+.1f}%) within-ft r={wcorr(X[name]):+.3f}") + +print("\n== T3: does CSPI add over ClimateNA SI? (within-ft partial corr of logA~CSPI | SI) ==") +# residualize logA and logCSPI on SI within ft, correlate residuals +rx=[];ry=[] +for g in set(ft): + m=ft==g + if m.sum()<6: continue + s=X["SI"][m]; c=X["CSPI"][m]; a=y[m] + if np.std(s)<1e-6: continue + bc=np.polyfit(s,c,1); rc=c-(bc[0]*s+bc[1]) + ba=np.polyfit(s,a,1); ra=a-(ba[0]*s+ba[1]) + rx+=list(rc); ry+=list(ra) +print(f" partial corr(logA, logCSPI | SI), within-ft = {np.corrcoef(rx,ry)[0,1]:+.3f} (n={len(rx)})") + +print("\n== T1: optimal beta (apply scalar A*(CSPI/med)^beta, held-out relRMSE on A) ==") +medC=np.median(np.exp(X["CSPI"])); Aabs=np.exp(y) +print(f" baseline ft-mean relRMSE included above; scalar-form held-out by beta:") +for beta in (0.5,1.0,1.5,2.0,2.77): + se=base=cnt=0 + for g in set(ft): + m=ft==g + if m.sum()<8 or len(set(prov[m]))<3: continue + Cm=np.exp(X["CSPI"][m]); Am=Aabs[m]; pv=prov[m] + for h in set(pv): + tr=pv!=h; te=pv==h + if tr.sum()<4 or te.sum()<1: continue + mu=np.mean(Am[tr]) # ft-mean anchor from train + pred=mu*(Cm[te]/np.mean(Cm[tr]))**beta + se+=np.sum((Am[te]-pred)**2); base+=np.sum((Am[te]-mu)**2); cnt+=te.sum() + print(f" beta={beta:>4}: held-out relRMSE {math.sqrt(se/cnt)/np.mean(Aabs):.3f} vs flat {math.sqrt(base/cnt)/np.mean(Aabs):.3f} ({100*(1-math.sqrt(se/base)):+.1f}%)") +print("DEEPASSESS_DONE") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_figure.py b/scripts/yield_curve_engine/cspi/ycx_cspi_figure.py new file mode 100644 index 0000000..176da3c --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_figure.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Publication-quality CSPI validation summary figure (3 panels). Headless (Agg). Values from +the deep assessment (20260628_cspi_deep_assessment.md) and per-ft addendum. Run on Cardinal.""" +import numpy as np, matplotlib +matplotlib.use("Agg"); import matplotlib.pyplot as plt +plt.rcParams.update({"font.size":9,"axes.spines.top":False,"axes.spines.right":False}) +fig,ax=plt.subplots(1,3,figsize=(12,3.6),dpi=200) + +# Panel A: out-of-sample skill by covariate +cov=["CSPI","Latitude","ClimateNA\nsite index"]; sk=[8.4,3.7,0.9] +c=["#1b7837","#7fbf7b","#b8b8b8"] +ax[0].bar(cov,sk,color=c,edgecolor="black",linewidth=0.5) +for i,v in enumerate(sk): ax[0].text(i,v+0.2,f"+{v}%",ha="center",fontsize=9,fontweight="bold") +ax[0].set_ylabel("held-out skill gain (%)\nleave-one-ecoregion-out CV") +ax[0].set_title("A. CSPI vs simpler covariates",fontsize=10,loc="left") +ax[0].set_ylim(0,10) + +# Panel B: held-out skill vs beta +beta=[0.5,1.0,1.5,2.0,2.77]; imp=[3.5,6.5,8.8,10.5,11.5] +ax[1].plot(beta,imp,"-o",color="#1b7837",lw=2,ms=6) +ax[1].axvline(1.5,ls="--",color="#d95f0e",lw=1.2); ax[1].text(1.55,4,"production\nbeta=1.5",color="#d95f0e",fontsize=8) +ax[1].set_xlabel("scaling exponent beta"); ax[1].set_ylabel("held-out skill gain (%)") +ax[1].set_title("B. Optimal beta (rises with steepness)",fontsize=10,loc="left") +ax[1].set_ylim(0,13) + +# Panel C: per-forest-type CSPI slope distribution +sl={"Douglas-fir":5.75,"Fir/spruce/mtn hemlock":5.09,"Oak/hickory":4.03,"Other eastern softwoods":3.97, +"Aspen/birch":3.46,"Woodland hardwoods":3.46,"Hemlock/Sitka spruce":3.77,"Elm/ash/cottonwood":2.87, +"Ponderosa pine":2.48,"Exotic softwoods":1.97,"Oak/gum/cypress":1.83,"Spruce/fir":1.68,"Alder/maple":1.67, +"Oak/pine":1.48,"Pinyon/juniper":1.10,"Other western softwoods":0.93,"Lodgepole pine":0.88, +"Exotic hardwoods":0.85,"White/red/jack pine":0.60,"Western larch":0.39,"Longleaf/slash pine":0.08, +"Maple/beech/birch":-0.40,"Western oak":-0.63,"Loblolly/shortleaf pine":-0.85} +items=sorted(sl.items(),key=lambda t:t[1]) +names=[k for k,_ in items]; vals=[v for _,v in items] +cols=["#b2182b" if v<0.2 else "#2166ac" for v in vals] +ax[2].barh(range(len(vals)),vals,color=cols,edgecolor="black",linewidth=0.3) +ax[2].set_yticks(range(len(vals))); ax[2].set_yticklabels(names,fontsize=5.5) +ax[2].axvline(1.5,ls="--",color="#d95f0e",lw=1); ax[2].axvline(0,color="black",lw=0.6) +ax[2].set_xlabel("CSPI->asymptote slope (log-log)") +ax[2].set_title("C. Slope varies by forest type",fontsize=10,loc="left") +ax[2].text(1.6,1,"global\n1.5",color="#d95f0e",fontsize=7) + +fig.suptitle("CSPI as a yield-curve site-productivity covariate: out-of-sample validation",fontsize=11,y=1.02) +fig.tight_layout() +fig.savefig("out/cspi_validation_figure.png",bbox_inches="tight") +fig.savefig("out/cspi_validation_figure.pdf",bbox_inches="tight") +fig.set_size_inches(7,2.1); fig.savefig("out/cspi_validation_figure_thumb.png",dpi=72,bbox_inches="tight") +print("wrote out/cspi_validation_figure.{png,pdf} + thumb") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_perft.py b/scripts/yield_curve_engine/cspi/ycx_cspi_perft.py new file mode 100644 index 0000000..9e7b11e --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_perft.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Per-forest-type CSPI slope calibration vs a single global beta. Run on Cardinal from +~/yield_curves_conus. Decision: does calibrating the CSPI->asymptote slope per forest type +improve held-out skill enough to justify the added complexity over a single global beta=1.5? +Leave-one-ecoregion-out CV on the remeas cell asymptotes; cell CSPI from ycx_cell_cspi.csv.""" +import csv, glob, math, numpy as np +cm={r["key"]:float(r["cspi_mean"]) for r in csv.DictReader(open("ycx_cell_cspi.csv")) if r["level"]=="cell"} +rows=[] +for f in glob.glob("ycx_*_remeas_fits.csv"): + for r in csv.DictReader(open(f)): + if r.get("scope")=="cell" and r.get("response")=="carbon_lbac": + k=r["ft_group"]+"|"+r["prov_code"] + try: a=float(r["A"]); n=int(r.get("n_plots") or 0) + except: continue + if a>0 and n>=10 and k in cm: rows.append((r["ft_group"], r["prov_code"], math.log(a), math.log(cm[k]))) +ft=np.array([x[0] for x in rows]); prov=np.array([x[1] for x in rows]) +y=np.array([x[2] for x in rows]); x=np.array([x[3] for x in rows]) +REF=math.log(56.36) +print(f"cells: {len(rows)}; forest types: {len(set(ft))}") + +# global slope via within-ft demeaning (the single-beta production model) +def cv(per_ft): + se=base=cnt=0 + for g in set(ft): + m=ft==g + if m.sum()<8 or len(set(prov[m]))<3: continue + lA=y[m]; lC=x[m]; pv=prov[m] + for h in set(pv): + tr=pv!=h; te=pv==h + if tr.sum()<4 or te.sum()<1: continue + if per_ft: + b,a=np.polyfit(lC[tr],lA[tr],1) # slope fit on THIS ft's train cells + else: + # global slope: pool within-ft-demeaned over all OTHER fts+train + xs=[];ys=[] + for g2 in set(ft): + mm=(ft==g2) + if g2==g: mm=mm&(prov!=h) # exclude held-out ecoregion of this ft + if mm.sum()>=3: xs+=list(x[mm]-x[mm].mean()); ys+=list(y[mm]-y[mm].mean()) + b=np.sum(np.array(xs)*np.array(ys))/np.sum(np.array(xs)**2); a=lA[tr].mean()-b*lC[tr].mean() + pred=a+b*lC[te]; mu=lA[tr].mean() + se+=np.sum((lA[te]-pred)**2); base+=np.sum((lA[te]-mu)**2); cnt+=te.sum() + return math.sqrt(se/cnt), math.sqrt(base/cnt), cnt + +gm,gb,gn=cv(False); pm,pb,pn=cv(True) +print(f"\nGlobal-slope model: held-out RMSE {gm:.3f} vs baseline {gb:.3f} ({100*(1-gm/gb):+.1f}%)") +print(f"Per-forest-type model: held-out RMSE {pm:.3f} vs baseline {pb:.3f} ({100*(1-pm/pb):+.1f}%)") +print(f"Per-ft improvement over global: {100*(1-pm/gm):+.1f}%") + +# per-ft slopes (full-data fit, n>=10 cells, >=3 ecoregions) to show heterogeneity +print("\nPer-forest-type CSPI slopes (log-log, full data):") +sl=[] +for g in sorted(set(ft)): + m=ft==g + if m.sum()>=10 and len(set(prov[m]))>=3: + b=np.polyfit(x[m],y[m],1)[0]; sl.append(b) + print(f" {g:28s} n={m.sum():4d} slope={b:+.2f}") +sl=np.array(sl) +print(f"\nslope spread: median {np.median(sl):.2f} IQR [{np.percentile(sl,25):.2f},{np.percentile(sl,75):.2f}] range [{sl.min():.2f},{sl.max():.2f}]") +print("DECISION: per-ft worth it if it beats global by >~2-3% held-out AND slopes vary widely.") +print("PERFT_DONE") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_raster.py b/scripts/yield_curve_engine/cspi/ycx_cspi_raster.py new file mode 100644 index 0000000..9d95cbd --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_raster.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""CSPI-adjusted CONUS t0 carbon-density raster (#75 spatial integration prototype). +Run on Cardinal from ~/yield_curves_conus. FIA true coordinates are binned to a ~0.25 deg +grid server-side; only the gridded PNG/array leaves (no point coords). Produces: + baseline density (modeled t0 carbon, Mg C/ha), CSPI-adjusted density (per-cell CSPI scalar, + renormalized per state to preserve the FIA-anchored state total), and their difference. +Density per plot = remeas carbon curve at STDAGE; CSPI scalar from ycx_cell_cspi.csv.""" +import csv, glob, math, numpy as np +import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt + +L2M=0.00045359237*2.4710538 +scal={r["key"]:float(r["cspi_scalar"]) for r in csv.DictReader(open("ycx_cell_cspi.csv")) if r["level"]=="cell"} +def hyb(a,A,k,p): return A*(1-math.exp(-k*a))**p +# grid +LON0,LON1,LAT0,LAT1,RES=-125.0,-66.0,24.0,50.0,0.25 +nx=int((LON1-LON0)/RES); ny=int((LAT1-LAT0)/RES) +base_sum=np.zeros((ny,nx)); cspi_sum=np.zeros((ny,nx)); cnt=np.zeros((ny,nx)) +def ix(lon,lat): + c=int((lon-LON0)/RES); r=int((LAT1-lat)/RES) + return (r,c) if (0<=r0): continue + key=r.get("ft_group","")+"|"+r.get("prov_code","") + d=hyb(age,*P.get(key,Pst))*L2M + if not math.isfinite(d) or d<0: continue + rows.append((lon,lat,d,scal.get(key,1.0))) + if not rows: continue + d=np.array([x[2] for x in rows]); s=np.array([x[3] for x in rows]) + dc=d*s; dc=dc*(d.sum()/dc.sum()) # renormalize within state -> preserve state total + for (lon,lat,_,_),db,dcv in zip(rows,d,dc): + r,c=ix(lon,lat) + if r is None: continue + base_sum[r,c]+=db; cspi_sum[r,c]+=dcv; cnt[r,c]+=1 + +with np.errstate(invalid="ignore"): + base=np.where(cnt>0, base_sum/cnt, np.nan) + cspi=np.where(cnt>0, cspi_sum/cnt, np.nan) + diff=cspi-base +print(f"grid {ny}x{nx}; filled cells {int(np.sum(cnt>0))}") +fin=np.isfinite(base) +print(f"baseline density Mg C/ha: min {np.nanmin(base):.1f} mean {np.nanmean(base):.1f} max {np.nanmax(base):.1f}") +print(f"CSPI-baseline diff: min {np.nanmin(diff):.2f} mean {np.nanmean(diff):.3f} max {np.nanmax(diff):.2f} Mg/ha") +print(f"abs reallocation: mean |diff| {np.nanmean(np.abs(diff)):.3f} Mg/ha; cells with |diff|>1: {int(np.nansum(np.abs(diff)>1))}") + +ext=[LON0,LON1,LAT0,LAT1] +def render(arr,title,fn,cmap,vmin=None,vmax=None,dpi=130): + fig,ax=plt.subplots(figsize=(8,4.2),dpi=dpi) + im=ax.imshow(arr,extent=ext,origin="upper",cmap=cmap,vmin=vmin,vmax=vmax,aspect=1.3) + ax.set_title(title,fontsize=10); ax.set_xticks([]); ax.set_yticks([]) + fig.colorbar(im,ax=ax,shrink=0.7,label="Mg C/ha") + fig.tight_layout(); fig.savefig(fn); fig.set_size_inches(4,2.1); fig.savefig(fn.replace(".png","_thumb.png"),dpi=72); plt.close(fig) +import os; os.makedirs("out",exist_ok=True) +vmax=np.nanpercentile(base,98) +render(base,"CONUS t0 AG carbon density (baseline, remeas)","out/cspi_raster_base.png","viridis",0,vmax) +render(cspi,"CONUS t0 AG carbon density (CSPI-adjusted)","out/cspi_raster_cspi.png","viridis",0,vmax) +dl=np.nanpercentile(np.abs(diff),98) +render(diff,"CSPI reallocation (CSPI - baseline)","out/cspi_raster_diff.png","RdBu_r",-dl,dl) +print("wrote out/cspi_raster_{base,cspi,diff}.png (+thumbs)") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_raster_albers.py b/scripts/yield_curve_engine/cspi/ycx_cspi_raster_albers.py new file mode 100644 index 0000000..5db73db --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_raster_albers.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""CSPI-adjusted CONUS carbon-density raster in EPSG:5070 Albers, matching the explorer's +overlay extent (production port of ycx_cspi_raster.py). Server-side binning; only gridded +PNGs + bounds.json leave. Emits drop-in overlay PNGs (bare, alpha nodata) + *_bounds.json, +and a labeled 3-panel review figure. Run on Cardinal from ~/yield_curves_conus.""" +import csv, glob, math, json, numpy as np +import matplotlib; matplotlib.use("Agg") +import matplotlib.pyplot as plt, matplotlib.cm as cm, matplotlib.colors as mcolors +from pyproj import Transformer + +# explorer Albers extent (EPSG:5070) +X0,Y1,X1,Y0=-2561585.0,1714610.0,2463176.0,-1604872.736 +RES=20000.0 +nx=int(round((X1-X0)/RES)); ny=int(round((Y1-Y0)/RES)) +tf=Transformer.from_crs("EPSG:4326","EPSG:5070",always_xy=True) +L2M=0.00045359237*2.4710538 +scal={r["key"]:float(r["cspi_scalar"]) for r in csv.DictReader(open("ycx_cell_cspi.csv")) if r["level"]=="cell"} +def hyb(a,A,k,p): return A*(1-math.exp(-k*a))**p +def rc(x,y): + c=int((x-X0)/RES); r=int((Y1-y)/RES) + return (r,c) if (0<=r0):continue + d=hyb(age,*P.get(r.get("ft_group","")+"|"+r.get("prov_code",""),Pst))*L2M + if not math.isfinite(d) or d<0: continue + lons.append(lon);lats.append(lat);dd.append(d);ss.append(scal.get(r.get("ft_group","")+"|"+r.get("prov_code",""),1.0)) + if not dd: continue + d=np.array(dd); s=np.array(ss); dc=d*s; dc=dc*(d.sum()/dc.sum()) + xs,ys=tf.transform(lons,lats) + for x,y,db,dcv in zip(xs,ys,d,dc): + r,c=rc(x,y) + if r is None: continue + bs[r,c]+=db; cs[r,c]+=dcv; ct[r,c]+=1 +with np.errstate(invalid="ignore"): + base=np.where(ct>0,bs/ct,np.nan); cspi=np.where(ct>0,cs/ct,np.nan); diff=cspi-base +print(f"Albers grid {ny}x{nx} res {RES/1000:.0f}km; filled {int(np.sum(ct>0))}") +print(f"baseline Mg C/ha mean {np.nanmean(base):.1f} max {np.nanmax(base):.1f}; mean|diff| {np.nanmean(np.abs(diff)):.2f}") + +import os; os.makedirs("out",exist_ok=True) +def overlay(arr,fn,cmap,vmin,vmax): + norm=mcolors.Normalize(vmin,vmax); rgba=cm.get_cmap(cmap)(norm(np.clip(arr,vmin,vmax))) + rgba[...,3]=np.where(np.isfinite(arr),1.0,0.0) # alpha 0 where nodata + plt.imsave(fn,(rgba*255).astype(np.uint8)) # bare overlay, row0=top=Y1 + json.dump({"x0":X0,"y1":Y1,"x1":X1,"y0":Y0},open(fn.replace(".png","_bounds.json"),"w")) +vmax=float(np.nanpercentile(base,98)); dl=float(np.nanpercentile(np.abs(diff),98)) +overlay(base,"out/conus_yc_agc_base.png","viridis",0,vmax) +overlay(cspi,"out/conus_yc_agc_cspi.png","viridis",0,vmax) +overlay(diff,"out/conus_yc_agc_cspidiff.png","RdBu_r",-dl,dl) +# labeled review panel +fig,ax=plt.subplots(1,3,figsize=(15,3.6),dpi=120) +for a,arr,t,cmp,vlo,vhi in [(ax[0],base,"baseline","viridis",0,vmax),(ax[1],cspi,"CSPI-adjusted","viridis",0,vmax),(ax[2],diff,"CSPI - baseline","RdBu_r",-dl,dl)]: + im=a.imshow(arr,cmap=cmp,vmin=vlo,vmax=vhi,aspect=1.0); a.set_title(t,fontsize=10);a.set_xticks([]);a.set_yticks([]); fig.colorbar(im,ax=a,shrink=0.7) +fig.suptitle("CONUS t0 AG carbon density (Mg C/ha), EPSG:5070 Albers",fontsize=11) +fig.tight_layout(); fig.savefig("out/conus_yc_cspi_albers_review.png"); fig.set_size_inches(9,2.2); fig.savefig("out/conus_yc_cspi_albers_review_thumb.png",dpi=72) +print("wrote overlays conus_yc_agc_{base,cspi,cspidiff}.png + bounds + review panel") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_reconcile.py b/scripts/yield_curve_engine/cspi/ycx_cspi_reconcile.py new file mode 100644 index 0000000..06dc3ff --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_reconcile.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""CONUS reconciliation: flagged CSPI engine vs baseline, 48 states, rcp45. +Reads ci_full_base/ and ci_full_cspi/ ci_yc_fiadb__rcp45.csv, sums mmt_agc +across states by year for the reserve (No_harvest) and BAU scenarios, reports +CONUS t0 (2025) and 2125 base vs CSPI, per-state delta distribution, and the +sparse-cell footprint from ycx_cell_cspi.csv. Run on Cardinal after CSPI48_DONE. +""" +import csv, glob, os, math +BASE="ci_full_base"; CSPI="ci_full_cspi" + +def conus(dirp, scen): + tot={} + for f in glob.glob(os.path.join(dirp,"ci_yc_fiadb_*_rcp45.csv")): + st=os.path.basename(f).split("_")[3] + for r in csv.DictReader(open(f)): + if r["scenario"]!=scen: continue + y=int(r["year"]); v=r.get("mmt_agc_mean","") + if v=="" or v=="NA": continue + tot.setdefault(y,{}); tot[y][st]=float(v) + return tot + +def line(scen): + b=conus(BASE,scen); c=conus(CSPI,scen) + yrs=sorted(b) + print(f"\n== scenario {scen} (CONUS sum mmt_agc Tg C) ==") + print(f"{'year':>6} {'base':>10} {'cspi':>10} {'delta%':>8}") + for y in (yrs[0], yrs[len(yrs)//2], yrs[-1]): + sb=sum(b[y].values()); sc=sum(c[y].get(st,0) for st in b[y]) + print(f"{y:>6} {sb:>10.1f} {sc:>10.1f} {100*(sc/sb-1):>+8.2f}") + # per-state delta at 2125 + y=yrs[-1]; ds=[] + for st in b[y]: + if st in c[y] and b[y][st]>0: ds.append((100*(c[y][st]/b[y][st]-1), st)) + ds.sort() + print(f" per-state delta% at {y}: n={len(ds)} min {ds[0][0]:+.2f}({ds[0][1]}) " + f"median {ds[len(ds)//2][0]:+.2f} max {ds[-1][0]:+.2f}({ds[-1][1]})") + +def sparse(): + f="ycx_cell_cspi.csv" + n=ns=0; sca=[] + for r in csv.DictReader(open(f)): + if r["level"]!="cell": continue + n+=1; npl=float(r["n_plots"]) + if npl<30: ns+=1; sca.append(float(r["cspi_scalar"])) + if sca: + m=sum(sca)/len(sca) + print(f"\n== sparse-cell footprint == {ns} of {n} cells have n_plots<30 " + f"(full CSPI weight); their cspi_scalar mean={m:.3f}") + +if __name__=="__main__": + print("base files:", len(glob.glob(BASE+"/ci_*.csv")), + "cspi files:", len(glob.glob(CSPI+"/ci_*.csv"))) + line("No_harvest"); line("BAU"); sparse() diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_scale.R b/scripts/yield_curve_engine/cspi/ycx_cspi_scale.R new file mode 100644 index 0000000..3a6dfa1 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_scale.R @@ -0,0 +1,73 @@ +# ycx_cspi_scale.R (#75 step 3: CSPI asymptote scaling module) +# Slots into ycx_canonical_ci_* behind a flag (use_cspi_asym = TRUE). +# Damped, bounded, sparse-cell-weighted multiplicative scalar on the fitted +# cell asymptote. CSPI refines and borrows strength; it does not overturn the fit. +# +# Rationale (see OODA_cspi_asymptote_scaling.md): the free-fit CSPI exponent +# (2.77 within forest type, 3.13 pooled) is confound-amplified and biologically +# too strong. Production uses beta = 1.0 (proportional), clamp +/- 25%, and +# shrinks the scalar toward 1.0 for well-populated cells. +# +# Inputs : ycx_cell_cspi.csv (level,key,n_plots,cspi_mean,cspi_sd,cspi_scalar) +# fits data.table with columns cell_key, ft_group, prov_code, A, n_plots +# Output : same fits table with columns A_cspi (scaled) and cspi_used. + +suppressWarnings(suppressMessages(library(data.table))) + +# ---- team knobs (documented assumptions, not estimates) ---- +CSPI_REF <- 56.36 # CONUS median CSPI from ycx_cell_cspi.csv +BETA <- 1.5 # CV-optimal (deep assessment 2026-06-28: held-out skill rises to ~free slope) +CLAMP_LO <- 0.70 # widened to admit beta=1.5 +CLAMP_HI <- 1.45 # widened to admit beta=1.5 +N0 <- 30 # sparse-cell shrinkage half-weight (full CSPI weight when n << N0) + +`%||%` <- function(a, b) if (is.null(a) || length(a) == 0) b else a + +scale_asymptote_cspi <- function(fits, cspi_path, log_path = "error_log.txt", + ref = CSPI_REF, beta = BETA, + clamp = c(CLAMP_LO, CLAMP_HI), n0 = N0) { + out <- tryCatch({ + stopifnot(all(c("cell_key", "A", "n_plots") %in% names(fits))) + cspi <- fread(cspi_path) + cell <- cspi[level == "cell", .(cell_key = key, cspi_mean)] + + x <- merge(as.data.table(fits), cell, by = "cell_key", all.x = TRUE) + + # raw multiplicative scalar, clamped + raw <- (x$cspi_mean / ref) ^ beta + raw[!is.finite(raw)] <- 1.0 # no CSPI -> no change + clamped <- pmin(pmax(raw, clamp[1]), clamp[2]) + + # sparse-cell shrinkage toward 1.0: full CSPI weight when n small + n <- as.numeric(x$n_plots %||% 0); n[!is.finite(n)] <- 0 + shrink <- n0 / (n0 + n) + scal <- 1 + shrink * (clamped - 1) + + x[, cspi_used := scal] + x[, A_cspi := A * scal] + x[] + }, error = function(e) { + cat(sprintf("[ycx_cspi_scale] %s\n", conditionMessage(e)), file = log_path, append = TRUE) + z <- as.data.table(fits); z[, `:=`(cspi_used = 1.0, A_cspi = A)]; z[] + }) + out +} + +# ---- standalone self-test / pilot summary (ME, CA) when run directly ---- +if (sys.nframe() == 0) { + set.seed(2026) + cspi_path <- path.expand("~/yield_curves_conus/ycx_cell_cspi.csv") + fit_files <- c(ME = "~/yield_curves_conus/ycx_ME_remeas_fits.csv", + CA = "~/yield_curves_conus/ycx_CA_remeas_fits.csv") + for (st in names(fit_files)) { + f <- tryCatch(fread(path.expand(fit_files[st])), error = function(e) NULL) + if (is.null(f)) next + f <- f[scope == "cell" & response == "carbon_lbac" & A > 0] + if (!nrow(f)) next + s <- scale_asymptote_cspi(f, cspi_path) + cat(sprintf("%s: %d cells | cspi_used %.2f..%.2f (median %.2f) | mean A change %+.1f%%\n", + st, nrow(s), min(s$cspi_used), max(s$cspi_used), median(s$cspi_used), + 100 * (mean(s$A_cspi) / mean(s$A) - 1))) + } + gc() +} diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_spatial.py b/scripts/yield_curve_engine/cspi/ycx_cspi_spatial.py new file mode 100644 index 0000000..c70d9e8 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_spatial.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""CSPI spatial t0 redistribution prototype (#75). Run on Cardinal from ~/yield_curves_conus. +Where CSPI is NOT cancelled: the absolute t0 carbon-density allocation within a state. +Baseline: each plot's t0 density = curve(STDAGE) (uniform-by-area within cell), cell carbon +proportional to summed plot density. CSPI version: multiply each cell's density by its CSPI +scalar, then renormalize within state so the FIA-anchored STATE TOTAL is preserved. Measures +how much standing carbon CSPI moves among cells (total-variation distance) and whether the +move is well-ordered (productive cells gain). Uses remeas fits (the validated form).""" +import csv, glob, math, numpy as np + +REF=56.36 +# cell CSPI scalar (2-part key) and state CSPI +scal={}; +for r in csv.DictReader(open("ycx_cell_cspi.csv")): + if r["level"]=="cell": scal[r["key"]]=float(r["cspi_scalar"]) + +def hyb(a,A,k,p,d,As): return A*(1-math.exp(-k*a))**p*math.exp(-d*max(0,a-As)) + +ABBR2FIPS={"AL":1,"AZ":4,"AR":5,"CA":6,"CO":8,"CT":9,"DE":10,"FL":12,"GA":13,"ID":16,"IL":17,"IN":18,"IA":19,"KS":20,"KY":21,"LA":22,"ME":23,"MD":24,"MA":25,"MI":26,"MN":27,"MS":28,"MO":29,"MT":30,"NE":31,"NV":32,"NH":33,"NJ":34,"NM":35,"NY":36,"NC":37,"ND":38,"OH":39,"OK":40,"OR":41,"PA":42,"RI":44,"SC":45,"SD":46,"TN":47,"TX":48,"UT":49,"VA":51,"VT":50,"WA":53,"WV":54,"WI":55,"WY":56} + +tot_moved=[]; conus_base=0.0; conus_gain_hi=0.0; allcells=0; movecells=0 +for ff in sorted(glob.glob("config/ycx_membership_*.csv")): + ST=ff.split("_")[-1].split(".")[0] + fitf=f"ycx_{ST}_remeas_fits.csv" + try: fits=list(csv.DictReader(open(fitf))) + except: continue + # carbon params per cell (2-part) + state fallback + P={}; Pst=None + for r in fits: + if r.get("response")!="carbon_lbac": continue + try: v=(float(r["A"]),float(r["k"]),float(r["p"]),0.0,200.0) + except: continue + if r["scope"]=="state": Pst=v + elif r["scope"]=="cell": P[r["ft_group"]+"|"+r["prov_code"]]=v + if Pst is None: continue + # per-cell baseline carbon (sum plot density), and cell CSPI scalar + cellC={}; cellS={} + for r in csv.DictReader(open(ff)): + try: age=float(r["STDAGE"]) + except: continue + if not math.isfinite(age) or age<=0: continue + key=r.get("ft_group","")+"|"+r.get("prov_code","") + p=P.get(key,Pst); dens=hyb(age,*p) + if not math.isfinite(dens) or dens<0: continue + cellC[key]=cellC.get(key,0.0)+dens + cellS[key]=scal.get(key,1.0) + if not cellC: continue + keys=list(cellC); base=np.array([cellC[k] for k in keys]); sc=np.array([cellS[k] for k in keys]) + if base.sum()<=0: continue + new=base*sc; new=new*(base.sum()/new.sum()) # renormalize: preserve state total + sb=base/base.sum(); sn=new/new.sum() + tv=0.5*np.sum(np.abs(sn-sb)) # fraction of state carbon moved + tot_moved.append((ST,100*tv,len(keys))) + conus_base+=base.sum(); allcells+=len(keys); movecells+=int(np.sum(np.abs(sn-sb)>1e-4)) + +tv=np.array([x[1] for x in tot_moved]) +print(f"states={len(tot_moved)} cells={allcells}") +print(f"CSPI spatial redistribution (% of each state's standing carbon moved among cells):") +print(f" mean {tv.mean():.2f}% median {np.median(tv):.2f}% min {tv.min():.2f}% max {tv.max():.2f}%") +top=sorted(tot_moved,key=lambda x:-x[1])[:6] +print(" most redistributed states:", ", ".join(f"{s}={v:.1f}%" for s,v,_ in top)) +print(f"\nInterpretation: this is the spatial value CSPI delivers that the t0-anchored") +print(f"trajectory cancelled. State TOTALS are preserved (FIA anchor); CSPI reallocates") +print(f"standing carbon toward productive cells. Non-zero here = CSPI is NOT cancelled on") +print(f"the absolute density layer, unlike the asymptote cap on the anchored reserve.") diff --git a/scripts/yield_curve_engine/cspi/ycx_cspi_stress.py b/scripts/yield_curve_engine/cspi/ycx_cspi_stress.py new file mode 100644 index 0000000..a8fc874 --- /dev/null +++ b/scripts/yield_curve_engine/cspi/ycx_cspi_stress.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""CSPI stress-test battery (#75). Run on Cardinal from ~/yield_curves_conus. +A. Knob sensitivity: beta x clamp sweep on the cell scalar distribution. +B. Spatial cross-validation: leave-one-ecoregion-out, does CSPI predict held-out + asymptote within forest type vs a no-CSPI baseline. +C. Bakuzis site-ordering: CSPI productivity classes must yield monotone, non-crossing + carbon trajectories across age (the law-like site-ordering relation). +Uses hybrid_fits (the asymptote the engine actually scales) joined to ycx_cell_cspi.csv. +""" +import csv, glob, math, statistics as st +import numpy as np + +REF=56.36; N0=30 +cspi={r["key"]:(float(r["cspi_mean"]),float(r["n_plots"])) + for r in csv.DictReader(open("ycx_cell_cspi.csv")) if r["level"]=="cell"} + +# join hybrid_fits carbon A per cell (ft|prov), across 48 states +rows=[] # (ft, prov, A, n, cspi) +for f in glob.glob("ycx_*_hybrid_fits.csv"): + for r in csv.DictReader(open(f)): + if r.get("scope")=="cell" and r.get("response")=="carbon_lbac": + k=r.get("ft_group","")+"|"+r.get("prov_code","") # 2-part key matches cspi table + try: A=float(r["A"]); n=int(r.get("n_plots") or 0) + except: continue + if A>0 and k in cspi: + rows.append((r.get("ft_group",""), r.get("prov_code",""), A, n, cspi[k][0])) +ft=np.array([x[0] for x in rows]); prov=np.array([x[1] for x in rows]) +A=np.array([x[2] for x in rows]); n=np.array([x[3] for x in rows],float) +C=np.array([x[4] for x in rows]) +print(f"[join] {len(rows)} state-cells (hybrid carbon, A>0, CSPI present)") + +def scalar(beta,clamp,cv,nn): + raw=(cv/REF)**beta + cl=np.clip(raw,1-clamp,1+clamp) + return 1+(N0/(N0+nn))*(cl-1) + +print("\n== A. KNOB SENSITIVITY (cell scalar; A-weighted mean |change|; % raw clamped) ==") +print(f"{'beta':>5}{'clamp':>7}{'med':>8}{'p05':>8}{'p95':>8}{'wmean|d|':>10}{'%clamp':>8}") +for beta in (0.5,1.0,1.5,2.0): + for clamp in (0.15,0.25,0.40): + s=scalar(beta,clamp,C,n) + raw=(C/REF)**beta + clamped=100*np.mean((raw<1-clamp)|(raw>1+clamp)) + wmean=np.sum(A*np.abs(s-1))/np.sum(A) + print(f"{beta:>5}{clamp:>7}{np.median(s):>8.3f}{np.percentile(s,5):>8.3f}" + f"{np.percentile(s,95):>8.3f}{wmean:>10.4f}{clamped:>8.1f}") + +print("\n== B. SPATIAL CV (leave-one-ecoregion-out, within forest type) ==") +# model: logA = a + b*logCSPI fit on train ecoregions; baseline: ft train mean logA +se_m=se_b=cnt=0 +fts_improved=0; fts_tested=0 +for g in set(ft): + m=ft==g + if m.sum()<8: continue + provs=set(prov[m]) + if len(provs)<3: continue + fts_tested+=1; gse_m=gse_b=0; gc=0 + lA=np.log(A[m]); lC=np.log(C[m]); pv=prov[m] + for hold in provs: + tr=pv!=hold; te=pv==hold + if tr.sum()<4 or te.sum()<1: continue + b,a=np.polyfit(lC[tr],lA[tr],1) + pred=a+b*lC[te]; base=lA[tr].mean() + gse_m+=np.sum((lA[te]-pred)**2); gse_b+=np.sum((lA[te]-base)**2); gc+=te.sum() + if gc>0: + se_m+=gse_m; se_b+=gse_b; cnt+=gc + if gse_m=30) ==") +keep=n>=30 +ft2,prov2,A2,C2=ft[keep],prov[keep],A[keep],C[keep] +se_m=se_b=cnt=0; fi=0; ftt=0 +for g in set(ft2): + m=ft2==g + if m.sum()<8: continue + provs=set(prov2[m]) + if len(provs)<3: continue + ftt+=1; gm=gb=0; gc=0 + lA=np.log(A2[m]); lC=np.log(C2[m]); pv=prov2[m] + for hold in provs: + tr=pv!=hold; te=pv==hold + if tr.sum()<4 or te.sum()<1: continue + b,a=np.polyfit(lC[tr],lA[tr],1); pred=a+b*lC[te]; base=lA[tr].mean() + gm+=np.sum((lA[te]-pred)**2); gb+=np.sum((lA[te]-base)**2); gc+=te.sum() + if gc>0: + se_m+=gm; se_b+=gb; cnt+=gc + if gm0: + print(f" well-sampled held-out cells: {cnt}; forest types: {ftt}") + print(f" RMSE(logA) baseline = {math.sqrt(se_b/cnt):.4f}; with CSPI = {math.sqrt(se_m/cnt):.4f} " + f"({100*(1-math.sqrt(se_m/cnt)/math.sqrt(se_b/cnt)):+.1f}%)") + print(f" forest types improved: {fi}/{ftt}") + # in-sample within-ft partial corr on well-sampled + xs=[];ys=[] + for g in set(ft2): + m=ft2==g + if m.sum()>=5: + xs+=list(np.log(C2[m])-np.log(C2[m]).mean()); ys+=list(np.log(A2[m])-np.log(A2[m]).mean()) + print(f" within-ft partial corr(logCSPI,logA), well-sampled: {np.corrcoef(xs,ys)[0,1]:+.3f}") + +print("\n== C. BAKUZIS SITE-ORDERING (CSPI classes -> non-crossing monotone curves) ==") +# representative hybrid carbon curve (median params), asymptote scaled by class CSPI (beta=1) +params=[] +for f in glob.glob("ycx_*_hybrid_fits.csv"): + for r in csv.DictReader(open(f)): + if r.get("scope")=="cell" and r.get("response")=="carbon_lbac": + try: params.append((float(r["k"]),float(r["p"]),float(r["d"]),float(r["Astar"]))) + except: pass +k=np.median([p[0] for p in params]); p=np.median([p[1] for p in params]) +d=np.median([p[2] for p in params]); As=np.median([p[3] for p in params]) +qs=np.percentile(C,[12.5,37.5,62.5,87.5]) # class-representative CSPI (quartile midpoints) +Amed=np.median(A) +ages=np.arange(0,101,5) +def hyb(age,Acap): return Acap*(1-np.exp(-k*age))**p*np.exp(-d*np.maximum(0,age-As)) +curves={f"Q{i+1}(CSPI={q:.0f})": hyb(ages, Amed*(q/REF)**1.0) for i,q in enumerate(qs)} +mat=np.array([curves[c] for c in curves]) # 4 x nages +# ordering: at every age >0, Q10): ok=False; bad.append(int(ag)) +print(f" class CSPI quartile midpoints: {[round(q,1) for q in qs]}") +print(f" asymptote ordering Q1 no SCALE factor +pm_new_45=1+BETA*(half(c90)/c30-1) +print("ME 2090 rcp85: old(scale=1.0)=%.4f new(rcp-native)=%.4f"%(pm_old(c90,1.0),pm_new_85)) +print("ME 2090 rcp45: old(scale=0.5)=%.4f new(rcp-native)=%.4f"%(pm_old(c90,0.5),pm_new_45)) +print("match => refactor is behavior-preserving; real per-rcp CSI replaces the columns later")