Skip to contents

Combines LPJmL monthly hydrology outputs into an annual per-cell water balance that closes as water_input_mm = aet_mm + runoff_mm + drainage_mm + soil_water_change_mm. Actual evapotranspiration (aet_mm) is the sum of the transpiration, evaporation and interception components (LPJmL has no direct AET, PET or temperature output). Water input is precipitation plus irrigation. drainage_mm is LPJmL deep seepage, the leaching driver downstream; runoff_mm is the runoff that does not pass through that seepage, which leaves the cell laterally and is not a leaching term but must appear in the budget. LPJmL's own total runoff (mrunoff.nc) is not used as runoff_mm directly, because it already contains the seepage: the seepage recharges groundwater and LPJmL adds the groundwater outflow to runoff, so adding seepage on top of it books that water twice. Drainage defaults to native seepage; the "residual" method instead reconstructs it as water_input - aet - runoff - soil_water_change, for use only when the seepage file is absent. Evapotranspiration is split into a blue (irrigation-sourced) and green (rain-sourced) part. The result is returned per grid cell, or aggregated to polity totals when resolution = "polity".

The output also exposes the footprint-relevant terms folded into the budget: prec_mm (precipitation) and irrig_mm (applied irrigation, the gross blue-water volume), which satisfy water_input_mm = prec_mm + irrig_mm; blue_consump_mm and green_consump_mm, the LPJmL-native consumptive blue and green water (the per-CFT cft_consump_water_b / cft_consump_water_g cubes when supplied, summed over the bands bands selects, otherwise the blue and green AET); and cft_nir_mm, the net irrigation requirement (LPJmL cft_nir), the net blue-water demand, summed to cell level when data$cft_nir is supplied and NA otherwise. Potential evapotranspiration (pet_mm) comes from the CRU climate forcing that drives the LPJmL run and is NA until that forcing is wired (see data$pet); no PET formula is fabricated here.

Usage

build_water_balance(
  method = list(),
  resolution = c("grid", "polity"),
  polity_validity = c("keep", "flag", "drop"),
  data = list(),
  bands = NULL,
  example = FALSE
)

Arguments

method

Named list selecting the estimation method for each term: aet ("components", the only method), drainage ("seepage" default, LPJmL native seepage, or "residual", a seepage reconstruction from the budget residual usable only when the seepage file is absent), runoff ("total_minus_seepage" default, LPJmL total runoff less the seepage it already contains, or "surface_lateral", the sum of LPJmL's surface and lateral runoff outputs; see Runoff below) and blue_green ("cft_native" default, per-crop blue/green consumptive water, or "irrig_share", the irrigation share of water input). Members left out take their default. "cft_native" warns when the per-CFT cubes report consumptive blue water on a rainfed band, which no irrigation can supply: that is the signature of an LPJmL 6.x run without the green/blue fix, whose split is unusable (the numbers are still returned, so the warning changes nothing; "irrig_share" does not read those cubes).

partial_year is the one member that is a policy rather than a method: what to do when a cell-year does not carry all twelve months, so that summing it to an annual flux would return a total over eleven. "abort" (default) refuses, naming the absent cell-months; "warn" returns the short sums; "drop" excludes the incomplete cell-years, so the year is absent rather than wrong. It is stamped into method_water as partial:<choice> only when it is not the default, because "abort" and "warn" return byte-identical numbers and only "drop" removes rows a reader could not otherwise account for.

Runoff. The two runoff methods are two ways of removing the seepage from LPJmL's total runoff, and they differ in what else they keep. "total_minus_seepage" keeps every other runoff component LPJmL routes (surface, lateral, snow, TOPMODEL baseflow and the lateral-water overflow), and treats this year's seepage as this year's groundwater outflow. Where groundwater storage changes within the year that difference lands in runoff_mm, which can then be negative (2.1% of cells in 2009 and 2.2% in 2010 on the LPJmL 6.1.1 run). "surface_lateral" reads mrunoff_surf.nc and mrunoff_lat.nc, never negative, but LPJmL does not write its baseflow or overflow, so they fall out of the budget. On that run, summed over 58,795 cells, the budget residual is +3.6% (2009) and +1.3% (2010) of water input with "total_minus_seepage", and +6.8% and +4.2% with "surface_lateral", against -19.2% and -21.4% when the seepage was counted twice. drainage = "residual" requires "surface_lateral": "total_minus_seepage" needs the seepage that the residual method exists to do without. Because it then also absorbs the baseflow and overflow, the residual exceeds seepage (by 30% in 2009 and 19% in 2010 on that run).

resolution

"grid" (per cell, default) or "polity" (aggregated to year and area_code).

polity_validity

What to do with a row whose (area_code, year) resolves to a polity that did not exist in that year (the cell-polity crosswalk has no year dimension, so an early-20th-century cell is labelled with its present-day territory). "keep" (default) keeps every row, which is the historical behaviour, and warns naming the rows, years and area codes involved. "flag" keeps them and adds the per-row logical reporting_polity_out_of_span, marking exactly which rows are stand-ins. "drop" removes them. All three warn; only "drop" changes the numbers. See polity_coverage_gaps(), which reports the same rows for an already-built table.

data

Optional named list of pre-loaded inputs to avoid NetCDF reads: hydrology tibbles transp, evap, interc, prec, irrig, runoff (LPJmL total runoff), runoff_surf and runoff_lat (read only for runoff = "surface_lateral") and seepage (each lon, lat, year, value; annual-summed automatically when a month column is present), swc (lon, lat, year, month, layer, value fractional saturation), optional per-crop consumptive water cft_consump_water_b and cft_consump_water_g (each lon, lat, year, value mm/yr), an optional cft_nir net-irrigation-requirement input (lon, lat, year, value mm/yr, summed to cell level when supplied; exposed as cft_nir_mm, else NA) and a cell_polity crosswalk (lon, lat, area_code, polity_frac, cell_area_ha). Each falls back to read_lpjml_hydrology() when absent, except cft_nir (see Details), pet and the consumptive-water inputs. Read the latter with read_lpjml_hydrology("cft_consump_water_g", monthly = FALSE), which names their CFT bands so bands can select among them.

Supplying any per-CFT cube also requires stand_frac (lon, lat, year, band_name or band, value), the per-CFT stand area fractions, read by default with read_lpjml_hydrology("stand_frac", monthly = FALSE). Every per-CFT cube is a density per square metre of ITS OWN STAND, so its bands must be weighted by these fractions before they are summed to a cell. Without them the aggregation aborts rather than returning an unweighted sum, which is not a whole-cell total at all: it overstates by 1 / (managed fraction of the cell).

bands

Optional character vector of LPJmL crop-functional-type band names restricting which bands the per-CFT consumptive-water and net irrigation terms are summed over, e.g. "rainfed grassland" to charge a grazing footprint the grassland water alone. NULL (default) sums every band, the whole-cell total. Bands are matched on the band_name the file itself carries, so an unknown name aborts rather than silently returning the whole-cell total; the band index is never used, because which crop a given index denotes is a property of how the run was configured. Only blue_consump_mm, green_consump_mm and cft_nir_mm are per-CFT, so this leaves the water budget itself untouched: aet_mm, runoff_mm, drainage_mm, soil_water_change_mm and the blue/green AET split (aet_blue_mm, aet_green_mm) are whole-cell quantities and take the same values whatever bands asks for. The split in particular is computed from the all-band consumptive totals, never the selected ones, because the fraction of a cell's evapotranspiration that came from irrigation cannot depend on which crop the caller asked about.

bands is also the only route to per-crop water today: read the per-CFT cubes once into data, then call once per band. Nothing in WHEP consumes the per-crop numbers yet, and on an LPJmL 6.x run without the green/blue fix their blue/green split is unusable (see method), so treat a per-band blue-water series as provisional.

example

If TRUE, return a small fixture instead of reading data. Defaults to FALSE.

Value

A tibble. For resolution = "grid": lon, lat, area_code, year, water_input_mm, prec_mm, irrig_mm, pet_mm, aet_mm, aet_blue_mm, aet_green_mm, blue_consump_mm, green_consump_mm, cft_nir_mm, drainage_mm, runoff_mm, soil_water_change_mm and method_water. For resolution = "polity": the same terms aggregated to year and area_code. Both resolutions carry the polity columns below, plus reporting_polity_out_of_span when polity_validity = "flag".

Polity columns

Every area-keyed output carries the polity its area_code resolves to in that row's year:

  • polity_area_code: The numeric key rows are AGGREGATED on, for the matrix workflows. It is a bucket, not an identity: use reporting_polity_code to say which territory a row belongs to.

  • reporting_polity_code: The polity itself, e.g. ESP-1846-1914. It is year-aware, so the same area_code resolves to different polities in different years, which is the point of the crosswalk.

  • reporting_polity_name: Its name. It can differ from the area's own name where the area folds into an aggregate.

  • reporting_polity_has_geometry: Whether the polity has a polygon in the WHEP polity database, for callers that need to map or intersect it. FALSE is a documented gap upstream, not an error.

Rows whose area_code resolves to no polity keep the columns with NA rather than being dropped, so a gap is visible instead of silent.

Rows before the back-cast anchor year resolve to the polity live in that anchor year rather than to the polity live in the row's own year, because WHEP's pre-anchor series are back-cast onto the anchor-year territory. See add_polity_code() for the reasoning. Where that polity is not live in the row's own year – 41.5% of the pre-1961 (area, year) cells – add_polity_code() says so as mapping_status == "backcast_anchor", and polity_coverage_gaps() reports it as gap_kind == "backcast_anchor". These columns do not say so either way.

A row whose year no mapped period covers is resolved to the NEAREST period of the same area instead, so reporting_polity_code can name a polity that did not exist in that row's year – FAOSTAT bucket 206 "Sudan (former)" keeps reporting after SUD-1956-2011 ends, and its post-2011 rows carry that code. These columns do not say so: add_polity_code() reports such a row as mapping_status == "out_of_span", and that column is dropped here so that adding it does not change the schema of every area-keyed output at once. polity_coverage_gaps() reports the stand-in rows of a built table, and options(whep.polity_mapping_status = "flag") (or "status") carries the signal on the outputs themselves. Both are opt-in; the default is no extra column.

Examples

build_water_balance(example = TRUE)
#> # A tibble: 8 × 22
#>    year area_code polity_area_code reporting_polity_code reporting_polity_name
#>   <int>     <int>            <int> <chr>                 <chr>                
#> 1  2000        79               79 DEU-1990-2025         Germany              
#> 2  2000        79               79 DEU-1990-2025         Germany              
#> 3  2000        21               21 BRA-1909-2025         Brazil               
#> 4  2000        21               21 BRA-1909-2025         Brazil               
#> 5  2000       114              114 KEN-1963-2025         Kenya                
#> 6  2000       114              114 KEN-1963-2025         Kenya                
#> 7  2000       203              203 ESP-1800-2025         Spain                
#> 8  2000       203              203 ESP-1800-2025         Spain                
#> # ℹ 17 more variables: reporting_polity_has_geometry <lgl>, lon <dbl>,
#> #   lat <dbl>, water_input_mm <dbl>, prec_mm <dbl>, irrig_mm <dbl>,
#> #   pet_mm <lgl>, aet_mm <dbl>, aet_blue_mm <dbl>, aet_green_mm <dbl>,
#> #   blue_consump_mm <dbl>, green_consump_mm <dbl>, cft_nir_mm <lgl>,
#> #   drainage_mm <dbl>, runoff_mm <dbl>, soil_water_change_mm <dbl>,
#> #   method_water <chr>