Skip to content

feat: condition grassland biomass on rangeland/pastureland - #11

Merged
jeffseif merged 2 commits into
mainfrom
jeffseif/pasture-ipcc-density
Oct 13, 2026
Merged

jeffseif merged 2 commits into
mainfrom
jeffseif/pasture-ipcc-density

Conversation

@jeffseif

@jeffseif jeffseif commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Background

Previously, grassland biomass was estimated using BLUE's vegetation carbon density. While this is appropriate for rangeland, it estimated pastureland's biomass too high (e.g., 18 tC/ha vs 7.6 for tropical moist and wet zones). Now, biomass (and emissions) are conditioned on the type of grassland. Offline measurements estimate that emissions should fall by 3-15% for several tiles within latin america.

This closes a methodological gap with Orbae 2.3 for pasture, but differences remain for natural grasslands and would require ingestion of CTREES for topping up -- which requires some more investigation. The gap is mentioned in this spreadsheet.

Notes

  1. Although this changes emit's output, I didn't bump the cache version because more changes are coming
  2. Table 6.4 has no tropical montane row, so a montane pasture source keeps the BLUE value
  3. This defers full matching with Orbae 2.3 for natural grassland because those changes are more involved and require some investigation first
  4. Here are the new numbers which can be shown to match Orbae v2.3 (p. 41, Table 5)
uv run python -c "import jdluc.emit, pprint ; pprint.pprint(jdluc.emit.CLIMATE_ZONE_TO_PASTURE_TCARBON_PER_HA)"
{<Zone.TROPICAL_MONTANE: 1>: 7.0,
 <Zone.TROPICAL_WET: 2>: 7.567,
 <Zone.TROPICAL_MOIST: 3>: 7.567,
 <Zone.TROPICAL_DRY: 4>: 4.0889999999999995,
 <Zone.WARM_TEMPERATE_MOIST: 5>: 6.345,
 <Zone.WARM_TEMPERATE_DRY: 6>: 2.8669999999999995,
 <Zone.COOL_TEMPERATE_MOIST: 7>: 5.64,
 <Zone.COOL_TEMPERATE_DRY: 8>: 3.0549999999999997,
 <Zone.BOREAL_MOIST: 9>: 3.9949999999999997,
 <Zone.BOREAL_DRY: 10>: 3.9949999999999997}

@jeffseif
jeffseif requested a review from blallen October 7, 2026 23:15
@jeffseif
jeffseif added this pull request to stack #12 October 7, 2026 23:15
@jeffseif
jeffseif force-pushed the jeffseif/pasture-ipcc-density branch from a93ac33 to f4273a2 Compare October 8, 2026 18:54
Comment thread jdluc/emit.py
Comment on lines +711 to +713
grassland_carbon = get_grassland_carbon(
climate_zones=climate_zones, from_pasture=conversion_record.from_pasture
)

@blallen blallen Oct 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex is worried about cache invalidation:

This changes emitted and dropped pasture carbon, but emit.workflow remains at cache version 1. Existing materializations bypass this calculation entirely; base and head produce the same key for identical tile arguments (verified with 30S_060W). That can mix the old and new methodology across cached and uncached tiles. Please invalidate the affected dependency chain before using or releasing this revision: emit, statistical.get_downscaled_luc_emissions, statistical.workflow, and jurisdictional_direct.workflow. Their caches do not incorporate upstream versions, so changing emit alone still leaves previously calculated factors stale. More planned changes do not make the current result reproducible with an existing scratch root.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and I just saw your note about not doing because more changes are coming... I wonder if that approach makes sense? maybe this is a reason to get some code versioning going?

@blallen blallen Oct 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex also flagged this for #13, here's the consensus result

Following up after reviewing #13: I rechecked the actual cache locations at both PR bases and heads; they are unchanged for identical arguments. The affected chain also includes trace.workflow: a cached final table bypasses attribution entirely.

A coordinated invalidation once the stack is ready seems reasonable, provided it happens before backfill or release. Across #11 and #13 that means emit, statistical downscale, both attribution workflows, and trace. For #13 alone, only the three parquet stages need invalidation.

Code versioning could help, but it needs to account for helper and upstream changes; hashing only a cached wrapper's source would miss these. The existing manual version bumps or a verified purge are sufficient for this release.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for surfacing this!

yup, not bumping the cache versions allowed me to reuse and reference warmed caches intentionally so that I could save time during the before/after measurements.

I think the main feature a data orchestration system would provide is having caches break when their upstreams change. our current setup presents the risk of downstreams yielding from stale caches, but gen AI is pretty good at flagging this.

@blallen

blallen commented Oct 12, 2026 •

Copy link
Copy Markdown

I skimmed the methodology and it seemed fine, asked codex to take a look too and it agrees, but probably would be good to get some other eyes on that part as well

I support this change as a better differentiated Tier 1 pasture default, subject to fixing the cache invalidation noted inline. Distinguishing managed pasture from natural rangeland is a useful methodological improvement. The remaining qualifications concern what these defaults establish, how the source class is determined, and how broadly the reported reductions apply.

Why the pasture default is reasonable

The implementation correctly transcribes total above- and below-ground herbaceous biomass and applies the 0.47 carbon fraction. Roots are already included, so an additional root-to-shoot multiplier would double-count them. These are broad defaults: the table gives a nominal 75% uncertainty, so the decimal precision should not imply comparable accuracy for individual pixels. IPCC 2006, Chapter 6, Table 6.4.

Orbae v2.3, Table 5 uses the same non-woody pasture assumption. That is a useful consistency check, but agreement between methods using the same input is not independent validation of pasture carbon or final commodity factors. The clarification in docs/validation.md is appropriate.

Natural grassland and source-class uncertainty

Keeping BLUE for natural grassland until the woody component is resolved is a reasonable interim boundary. Replacing its combined vegetation stock with an herbaceous-only value would omit woody carbon; retaining BLUE does not, however, establish that its current values are correct.

The inference that CTrees values of 15–26 Mg/ha are implausible solely because tree canopy is below 10%. Tree canopy is not shrub cover or biomass. GPW's general grassland extent allows up to 50% tree canopy; the below-10% restriction belongs specifically to open shrubland. The pipeline combines natural/semi-natural grassland and open shrubland as rangeland sources, so a separately filtered sample would need to be documented before generalizing that argument. GPW v2 definitions.

Likewise, cultivated grassland is a management class, not proof of treelessness. My inference is that a non-woody default can understate losses on woody or silvopastoral pasture. Annual natural/cultivated class flicker also remains important: the existing last-departure rule now selects between materially different carbon stocks. Lower totals alone would not demonstrate better conversion detection.

Regional effects and limits of the claimed improvement

The change is not uniformly downward. Calculated from the old and new code tables:

Pasture source zone Previous tC/ha New tC/ha Stock change
Tropical moist/wet 18.000 7.567 −58.0%
Tropical montane 7.000 7.000 Unchanged
Boreal dry 3.000 3.995 +33.2%

The montane BLUE fallback is explicitly documented and avoids inventing a missing table row. Boreal dry is an exception to the statement in docs/further_research.md that BLUE exceeds the IPCC values.

These percentages describe pasture vegetation stock, not total LUC emissions. The reported 3–15% decreases in selected Latin American tiles are plausible, but these have not been independently reproduced them; retaining their inputs and calculation would make that impact claim reviewable.

This PR also retains the existing full-source-stock release convention, destination-stock treatment and amortization. It improves the source-stock defaults without independently validating those other assumptions.

@jeffseif
jeffseif force-pushed the jeffseif/pasture-ipcc-density branch from f4273a2 to a73e113 Compare October 13, 2026 18:02

@jeffseif jeffseif left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably would be good to get some other eyes on that part as well

good call! you're not the only input on this change -- this was a gap between CS and Orbae and this PR closes that. I think I could have been more clear about that motivation.

Comment thread jdluc/emit.py
Comment on lines +711 to +713
grassland_carbon = get_grassland_carbon(
climate_zones=climate_zones, from_pasture=conversion_record.from_pasture
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for surfacing this!

yup, not bumping the cache versions allowed me to reuse and reference warmed caches intentionally so that I could save time during the before/after measurements.

I think the main feature a data orchestration system would provide is having caches break when their upstreams change. our current setup presents the risk of downstreams yielding from stale caches, but gen AI is pretty good at flagging this.

@jeffseif
jeffseif merged commit a1a11d9 into main Oct 13, 2026
2 checks passed
@jeffseif
jeffseif deleted the jeffseif/pasture-ipcc-density branch October 13, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants