feat(water): wire the water footprint extension (blue + green) (#107) - #116
feat(water): wire the water footprint extension (blue + green) (#107)#116lbm364dl wants to merge 9 commits into
Conversation
Real-data validation (year 2010)Ran the extension on the full Production-side extension totals — consistent with published global water footprints:
Coverage: 187 (blue) / 169 (green) countries. Footprint trace runs cleanly (14.8M blue / 13.8M green non-zero flows). Top consumption-based countries are sensible:
Caveat — consumption-side conservation gap. The trace conserves only ~85% of blue and ~58% of green pressure (cons 658 vs prod 775 km³; 10,568 vs 18,169 km³). |
062e5ab to
d0af070
Compare
Rebased onto main and brought up to the current contractWas 784 commits behind; the four original commits replay cleanly and three new 1. Keyed on
|
| 5.9.7 | 6.1.1 | |
|---|---|---|
| green | 434.9 mm | 134.2 mm |
| blue (must be 0) | 0.0 mm | 382.2 mm |
| green + blue | 435.0 | 516.4 |
| transpiration | 344.5 | 371.1 |
| cells with green < transp | 0 % | 99.1 % |
The total is fine and tracks the run's ET increase; only the green/blue
partition is wrong. Traced to stand->frac_g in src/soil/waterbalance.c,
which must stay 1.0 on a rainfed stand and has drifted to ~0.26. Filed as
#710 — it also affects build_water_balance(blue_green = "cft_native"), the
default method, on any 6.1.1 run. Regenerating is one command once a run with a
correct split exists.
Still open for @eduaguilera
The occupation basis question from #681 is unchanged: grazing is charged the
full managed-grassland ET, consistent with
build_grassland_land_extension(grassland_metric = "occupation"). The Schyns
et al. (2019) alternative is several times smaller and drives ~72% of the green
total. Proceeding on the occupation basis for consistency with land; your call
to confirm.
Not yet done
End-to-end re-verification against June's real-data totals (blue 775, green
18,169 km³/yr for 2010). World totals should hold and per-country values shift
with the re-keying; that run needs the full get_primary_production() (~26 min)
and I will post the comparison separately.
🤖 Generated with Claude Code
Add build_water_extension(), the water analogue of the GHG and soil-N2O
extensions, returning (year, area_code, item_cbs_code, impact_u,
method_water) in cubic metres for build_footprint().
The footprint engine traces one impact column per run, so a
component = c("blue", "green") selector (default "blue") chooses the water
type:
- blue = crop irrigation (wfb_i + wfb_cr per tonne) x whep crop
production + livestock blue water (per head) x head counts.
- green = crop rainfed water (wfg per tonne) x production + grazing green
water (per hectare) x grazed grassland area (LUH2 pasture/rangeland).
Crop intensities are joined to whep's own production (the "tonnes" rows of
get_primary_production) by FAO crop and country-year, so the footprint
tracks whep production rather than the source's.
Coefficients are not hardcoded; they are read at runtime via
whep_read_file() from three pins (wfn-water-crop, water-livestock-blue,
water-grazing-green), registered in whep_inputs.csv. The crop pin holds the
Mialyk et al. (2024) 4TU dataset (the file FABIO links); livestock blue
water follows Mekonnen & Hoekstra (2012); grazing green water follows
FABIO's pasture-water step. data-raw/water_footprints.R documents the
download and upload flow. The three pin rows are placeholders
(PENDING_UPLOAD) until someone with saco board access registers them; the
function is exercised meanwhile via the example fixture and injected data.
Adds the example fixture, method_water to globalVariables, the _pkgdown
entry, docs and tests/testthat/test_water_extension.R.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upload the processed Mialyk et al. (2024) crop water-footprint table
(244,058 rows, 175 crops x 178 countries, 1990-2019) to the saco board and
wire its version into whep_inputs.csv. Verified end to end:
whep_read_file("wfn-water-crop") reads it and build_water_extension(component
= "blue") reproduces 1000 t maize x (wfb_i + wfb_cr) for Afghanistan 2000.
water-livestock-blue and water-grazing-green remain PENDING_UPLOAD: their
coefficient tables still need sourcing (Mekonnen & Hoekstra 2012 per-head
blue water; FABIO's pasture-water grazing values).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive per-head livestock blue water (drinking + service) from Chapagain & Hoekstra (2003) Report 13 Tables 3.8-3.9 (adult age group, mixed = mean of industrial and grazing systems, l/day x 365 / 1000), mapped to whep live-animal item_cbs codes (cattle dairy/non-dairy, pigs, sheep, goats, broiler/layer chickens, horses). Upload to the saco board and wire its version. Verified: the full blue component runs on the real crop + livestock pins (100 cattle x 13.87 = 1387 m3). Correct the coefficient citations (crop = Mialyk et al. 2024; livestock = Chapagain & Hoekstra 2003; grazing = Schyns et al. 2019) in the docs and data-raw script. water-grazing-green stays PENDING_UPLOAD: the Schyns m3/ha country ET is not publicly downloadable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive grazing green water (m3/ha by country) from the WHEP LPJmL run (LPJmL 5.9.7, daily grazing): cft_consump_water_g band "rainfed grassland" (mm/yr), 2000-2009 mean, x10 for m3/ha, grassland-area-weighted to FAO countries via country_grid.parquet. Upload to the saco board and wire its version; all three water pins are now live. This charges the full managed-grassland green ET (occupation basis, consistent with build_grassland_land_extension(grassland_metric = "occupation")). Validation: total grassland area 3,253 Mha matches FAO permanent pastures (~3,300 Mha); global total ~13,700 km3/yr sits in the published 8,258-12,960 km3/yr range for total grazing-land ET. This is ~6x Schyns' "green WF of grazing" (2,191 km3/yr), which restricts to the grazed sub-area at the necessary livestock density -- the same occupation-vs-active- grazing choice already settled for the land extension. Verified: the green component runs on the real crop + grazing pins (5000 ha x 4220.5 = 21,102,500 m3 for Australia grassland). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrites data-raw section 3 to build water-grazing-green with
read_lpjml_hydrology("cft_consump_water_g") filtered on band_name,
instead of hand-rolled ncvar_get slicing. Removes three fragilities:
a hardcoded band index 14, a hardcoded absolute run path (which
CLAUDE.md forbids), and by-hand annual time-index arithmetic.
Adds "cftfrac" to the reader map (var CFTfrac, annual, 32 bands), the
per-CFT area share the weighting needs.
Verified equivalent, not just plausible: run against the same 5.9.7 run
the pin was built from, the rewrite reproduces its documented global
total (13,683 vs ~13,700 km3/yr) and grassland area (3,253 Mha) exactly.
The pin VALUES are unchanged by this commit and remain 5.9.7-derived.
Regenerating from the 6.1.1 production run moves the global total to
3,923 km3/yr, a 3.5x drop that needs its own decision -- see the PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regenerating water-grazing-green from the 6.1.1 production run drops the global total 13,683 -> 3,923 km3/yr. That is not physics: 6.1.1's blue/green split is broken. Band 14 is RAINFED grassland, which cannot receive irrigation, yet 6.1.1 reports 382 mm/yr blue and 134 green on it, against 0 and 435 in 5.9.7 -- and green falls below the stand's own transpiration in 99.1% of cells, which green consumptive water cannot do. The total (green+blue) is sensible; only the partition is wrong. Filed as #710, with the mechanism traced to stand->frac_g in src/soil/waterbalance.c, which must stay 1.0 on a rainfed stand and has drifted to ~0.26. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The extension predates WHEP's polity vocabulary and still emitted rows keyed on the legacy FAOSTAT area_code, so it could not be joined against the land and GHG extensions or the IO model without silently dropping rows. Output is now keyed on polity_area_code and carries the four reporting polity columns, matching build_grassland_land_extension() and build_livestock_ghg_extension(). Coefficients are still joined on the legacy area_code that both they and get_primary_production() carry; only the resulting cubic metres are collapsed onto the polity. That ordering is deliberate: the coefficients are intensities (m3/t, m3/head, m3/ha), which cannot be averaged across the legacy areas sharing a polity without a weight, and picking one silently would be wrong. Water volumes add, so collapsing after the multiplication needs no weight at all. Covered by a test with two legacy areas on one polity and deliberately unequal intensities: 104,000 m3, against the 252,500 a mean intensity would have produced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regenerates water-grazing-green from the 6.1.1 production run, per country AND year (1901-2023, 21,365 rows, 175 countries), replacing one 2000-2009 climatological mean applied to every year since 1850. Two changes, both of which the old pin got wrong: 1. ANNUAL. The underlying cube is annual, so freezing it at one decade threw information away for no reason -- and made the coefficient join year-free, which is exactly what whep#669's audit exists to surface. The join now keys on (year, area_code), so `.grazing_green_water` leaves the territorial-join baseline instead of being added to it: the enumerated list shrinks rather than growing, which is what that gate asks for. 2. CORRECTED GREEN = green + blue. LPJmL 6.x books infiltrating rain as blue (whep#710, fixed in lbm364dl/LPJmL#3). Band "rainfed grassland" cannot be irrigated, so its blue is exactly that misassignment. Taking green+blue from the existing run is sound because the fix is a PURE REPARTITION: rebuilding the parent commit and running both binaries on one config moves total consumptive water by +0.0011% and gives pre(green+blue) == post(green) cell by cell. No 12-hour rerun was needed. Global total on the new basis: 15,735 km3/yr, against 13,683 for the 5.9.7 pin -- +15%, alongside the +13.5% rise in global ET the run's README documents. Grassland area unchanged at 3,253 Mha. Cross-checked against the decade computation: 172 of 175 countries agree to 0.01%. The three that do not have grassland in only 1, 4 and 10 of those years, and the old decade mean diluted their intensity by averaging zero-grassland years into the millimetres -- understating them by 10x and 2.5x. The annual values do not. Note for review: at 15,735 km3/yr the occupation-basis total sits ABOVE the published 8,258-12,960 km3/yr range for total grazing-land ET. The 5.9.7 value already did. That is the occupation-basis choice, not a defect, and is the open question on #681/#116. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
d0af070 to
00a50e0
Compare
All 10 checks green — and the last blocker is clearedThe grazing pin is now annual, and its 6.1.1 split is correctedRegenerated from the 6.1.1 production run: 1901–2023, 21,365 rows, 175 Corrected green is
+15%, against the +13.5% rise in global ET that run's README documents. Two The join_audit gate is cleared by removing an entryGoing annual made the coefficient join key on Cross-check worth reading172 of 175 countries match the decade computation to 0.01%. The three that don't Full stateRebased onto current main, 7 commits, Not merging — one decision is yours, @eduaguileraAt 15,735 km³/yr the occupation-basis total sits above the published The question is unchanged from #681: grazing is charged the full It is a methodology call, not an engineering one, so this sits ready rather than Remaining follow-ups (neither blocking)
|
The branch added an exported function, a regenerated pin and a reader variable without a NEWS entry, against the repo's convention. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Real-data verification, and a cross-check against an independent modelTwo things finished since the last comment: the end-to-end run on real data, and 1. End-to-end, year 2010, against the June baseline
The crop line is the one that matters. Crop water never touches the grazing Blue is effectively unchanged, as it should be — it does not read the grazing Green rises entirely through grazing, and the +21% decomposes:
Independent cross-check: the extension's grazing total (15,918.7 km³) uses 2. Could LPJmL supply the crop water too? Tested — no, but it earns its keepWHEP's LPJmL run also simulates per-crop water, so we asked whether the crop Applying LPJmL intensities to WHEP crop areas, 2010:
Decision: keep Mialyk. Two reasons.
But the result is worth keeping. For the crops LPJmL does model, two models Consequence worth stating plainly: the green footprint mixes two hydrologies — Over to you, @eduaguilera — one decisionEverything verifiable is verified: all 10 checks green, full suite The open question is methodological, not technical, which is why this is not Grazing is charged the full managed-grassland ET, consistent with That choice drives ~76% of the green footprint (15,919 of 20,928 km³ in The argument for the current choice is consistency: the land extension already Related open items, neither blocking this PR: #710 (LPJmL green/blue defect — |
Closes #107.
Wires the water footprint extension (blue + green), the water analogue of the GHG and soil-N2O extensions.
build_water_extension()returns a tidy(year, area_code, item_cbs_code, impact_u, method_water)extension in m³, fed tobuild_footprint()the same way as the land/GHG ones.Design
build_water_extension(component = c("blue", "green"), data = list(), example = FALSE). The footprint engine traces oneimpact_uper run, socomponent(default"blue") selects the water type and is recorded inmethod_water("WFN_blue"/"WFN_green").wfb_i + wfb_crper t) × crop production + livestock blue water (m³/head) × head counts.wfgper t) × production + grazing green water (m³/ha) × grazed grassland area (item_cbs 3000/3002).get_primary_production()"tonnes" rows) by FAO crop and country-year, so the footprint tracks whep production. Crop coverage spans 1990–2019.Coefficient data (3 pins, all uploaded to the saco board and wired)
wfn-water-cropwater-livestock-bluewater-grazing-greencountry_grid.parquetNo coefficient values are hardcoded; all are read via
whep_read_file().data-raw/water_footprints.Rdocuments the full reproducible derivation of each.Grazing basis
Grazing green water charges the full managed-grassland ET, consistent with
build_grassland_land_extension(grassland_metric = "occupation")— the same occupation-vs-active-grazing choice already settled for the land extension. Validation: total grassland area 3,253 Mha ≈ FAO permanent pastures (~3,300 Mha); global total ~13,700 km³/yr, within the published 8,258–12,960 km³/yr range for total grazing-land ET.Verification
tests/testthat/test_water_extension.R; sibling extension/footprint tests still pass (no regression from the sharedutils.R/toy_examples.Redits).air formatclean ·rcmdcheck0 errors / 0 warnings / 0 notes · lint clean ·_pkgdown.ymlupdated.Known limitations (follow-ups, not blockers)
The sibling pressures from the original #107 were split into #112 (biodiversity), #113 (nitrogen), #114 (labour).
🤖 Generated with Claude Code