feat(water-balance): select consumptive water by CFT band (#107) - #681
Merged
Conversation
`build_water_balance()` summed the per-CFT consumptive-water cubes over every crop band, so the only figure it could report was the whole-cell total. A grazing water footprint needs the grassland band alone; charging grazing the whole-cell total would add every wheat field and rice paddy in the cell to the pasture. Adds `bands =`, which restricts the consumptive-water and `cft_nir` terms to named bands and defaults to NULL (every band, unchanged behaviour). Selection is by the `band_name` the file carries in NamePFT, never by index: which crop a given index denotes is a property of how the run was configured, so a positional filter would keep charging band 14 in a run whose band 14 is a different crop. An unknown name, or an input with no band names to match on, aborts rather than silently returning the whole-cell total. Getting there needed three reader fixes, all of which would have produced wrong numbers rather than an error: - The cft_nir map entry named `mcft_nir.nc` holding monthly `cft_nir`. No WHEP run has ever written that file -- all nine runs under LPJmL_runs/, 5.9.7 and 6.1.1 alike, write `cft_nir.nc` holding annual `nir` with 32 bands. Never surfaced because nothing calls it yet. - The reader assumed 12 time steps per year for every variable, but the per-CFT consumptive-water cubes are annual (nstep 1, mm/yr), so their time axis was decoded as months and `years=` sliced the wrong range. - ncvar_get() drops length-1 dimensions, so a single-year slice of an annual per-CFT cube came back 3-D and its band axis was decoded as time, scrambling crops into years. Now collapse_degen = FALSE. Facts verified against the 6.1.1 production run (global_1901-2023_spinup_300_our_inputs_lpjml611): vars consump_water_b / consump_water_g, nstep 1, mm/yr, 32 bands, band 14 "rainfed grassland" and band 30 "irrigated grassland". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 10, 2026
lbm364dl
added a commit
that referenced
this pull request
Aug 10, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares the ground for #116 (the water footprint extension, #107) by making
main's
build_water_balance()able to answer the question that extension asks.The problem
build_water_balance()reads the per-CFT consumptive-water cubes andimmediately sums them over every crop band, so the only figure it can report is
the whole-cell total. A grazing water footprint needs the grassland band
alone — charging grazing the whole-cell total adds every wheat field and rice
paddy in the cell to the pasture.
The change
bands =restricts the consumptive-water andcft_nirterms to named bands.NULL(default) totals every band, so existing callers are unaffected.Selection is by the
band_namethe file carries inNamePFT, never byindex: which crop a given index denotes is a property of how the run was
configured, so a positional filter would keep charging band 14 in a run whose
band 14 is a different crop. An unknown name — or an input with no band names
to match on — aborts rather than silently returning the whole-cell total.
Three reader bugs found on the way
Each would have produced wrong numbers rather than an error:
cft_nirmapped tomcft_nir.ncholding monthlycft_nirLPJmL_runs/, 5.9.7 and 6.1.1 alike, writecft_nir.ncholding annualnir, 32 bands. Never surfaced because nothing calls it yet.nstep1, mm/yr). Their time axis was decoded as months, soyears=sliced the wrong range out of the file.ncvar_get()drops length-1 dimensionscollapse_degen = FALSE. Monthly cubes never hit this — a one-year slice is still 12 steps.Verification
Facts checked against the 6.1.1 production run
(
global_1901-2023_spinup_300_our_inputs_lpjml611), not guessed: varsconsump_water_b/consump_water_g,nstep1, mm/yr, 32 bands, band 14rainfed grassland, band 30irrigated grassland. Themcft_nir.ncclaim waschecked against every run in the archive.
years=slicing,band filtering, both abort paths).
[ FAIL 0 | PASS 6632 ]·air formatclean · lint clean ·rcmdcheck0 errors / 0 warnings / 0 notes.Only
bands = NULLpaths run today, so nothing in main changes numerically.Follow-up
#116 rebases onto this, drops its
water-grazing-greenpin (a country-aggregatedcopy of exactly this LPJmL layer) and reads the grassland band from here
instead, per [[reuse what main already has]]. Note for @eduaguilera: that
extension charges grazing the full managed-grassland ET, consistent with
build_grassland_land_extension(grassland_metric = "occupation"). Thealternative — only the area needed to feed the animals, as in Schyns et al.
(2019) — is ~6x smaller and drives ~72% of the green water total. Proceeding on
the occupation basis for consistency with land, but it is your call to confirm.
🤖 Generated with Claude Code