Skip to content

fix: restore default CO2 characterization for emission flows - #25

Merged
TimoDiepers merged 1 commit into
mainfrom
fix/co2-emission-flows-unmapped
Aug 3, 2026
Merged

TimoDiepers merged 1 commit into
mainfrom
fix/co2-emission-flows-unmapped

Conversation

@TimoDiepers

Copy link
Copy Markdown
Member

Problem

Since 1.4.0, create_characterization_functions_from_method() assigns no characterization function to ordinary CO2 emission flows (Carbon dioxide, fossil with categories ('air', 'non-urban air or from high stacks') etc.). characterize() then silently skips them, so dynamic LCIA (metric="GWP" or "radiative_forcing") misses ~90% of the climate impact. Downstream, every current bw_timex install is affected, since TimexLCA.dynamic_lcia() relies on this default mapping.

Root cause: the emission branch required "in air" in node["categories"], which only holds for the natural-resource flow Carbon dioxide, in air. Real emissions have ('air',), ('air', 'urban air close to ground'), ... and there was no else branch. Carbon dioxide, from soil or biomass stock was unmapped too, because the uptake check matched "soil" against categories while that flow carries "soil" in its name and ('air', ...) categories.

1.3.1 and earlier had a catch-all else and were fine.

Fix

Uptake is detected explicitly, everything else falls through to the emission function:

categories = node.get("categories") or ()
is_soil_uptake = "soil" in categories
is_uptake_resource = "in air" in categories and node.get("type") == "natural resource"

if is_soil_uptake or is_uptake_resource:
    if characterize_uptake:
        characterization_functions[node.id] = co2_uptake_func
else:
    characterization_functions[node.id] = co2_func

characterize_uptake=False now suppresses only the uptake functions, not all CO2 characterization.

Tests

New tests/test_default_characterization_functions.py builds a synthetic biosphere + method with bw2data.tests.bw2test, so no ecoinvent is needed:

  • 6 parametrized CO2 emission flows (all air subcategories, non-fossil, from soil or biomass stock) map to characterize_co2
  • emission flows stay characterized with characterize_uptake=False
  • Carbon dioxide, in air (natural resource) and CO2 to soil map to characterize_co2_uptake, and are absent when characterize_uptake=False
  • every method flow gets a function
  • end-to-end: dynamic GWP100 with fixed_time_horizon=False is within 5% of the static score of the same inventory

Verified failing before the fix (KeyError on the CO2 emission flows; end-to-end 551 vs. 22245 expected) and passing after. Full suite: 94 passed.

Version bumped to 1.4.1 with a changelog entry.

create_characterization_functions_from_method() only mapped CO2 flows whose
categories contained "in air", which is true for the natural resource flow
"Carbon dioxide, in air" but not for actual emissions to ('air', ...). Those
flows got no characterization function and were silently skipped, understating
dynamic climate scores by roughly an order of magnitude.

Uptake is now detected explicitly (soil categories, or "in air" + natural
resource type), all other CO2 flows fall through to the emission function, and
characterize_uptake=False only suppresses the uptake functions instead of all
CO2 characterization. The soil check looks at categories only, so
"Carbon dioxide, from soil or biomass stock" emitted to air is characterized
as an emission.
@TimoDiepers
TimoDiepers merged commit a951108 into main Aug 3, 2026
9 checks passed
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.

1 participant