Conversation
Ported from the a_CDRs development branch. Adds perennialisation (conversion of 1st-generation biofuel cropland to perennial grasses) as an optional CDR technology: land area displaced from 1G biofuel production is backed out from existing biomass potentials and NUTS2 crop yields, converted to a CO2 sequestration potential, and dispatched via an April-October harvest profile. Retrieves NUTS2 Eurostat crop-yield data from the same Zenodo-archived CO2-removal data package afforestation will also depend on (rules/retrieve.smk: retrieve_co2_removal_data, kept byte-identical to avoid conflicts whichever PR merges first). Off by default (sector.perennials: false). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # scripts/_helpers.py
Mirrors the pattern set by rock_weathering_PR (scripts/lib/validation/config/rock_weathering.py): - New scripts/lib/validation/config/perennials.py with PerennialsConfig (sequestration_co2), registered on the top-level ConfigSchema in _schema.py. - sector.py: added sector.perennials: bool (the CDR technology toggle). - data.py: added data.co2_removal_data as a _DataSourceConfig (feeds the retrieve_co2_removal_data rule's Eurostat crop yield inputs). - Regenerated config/config.default.yaml and config/schema.default.json from the new models; both config-schema sync tests now pass (they were failing before, same root cause as biochar_PR's still-open gap: perennials config wasn't covered by any pydantic model). Also added a release_notes.md entry matching rock_weathering_PR's "Upcoming Release" bullet style, referencing tracking issue PyPSA#2143. This commit was preceded by a merge of upstream/master (57 commits behind at the time) to pull in the validation framework this depends on. That merge had exactly one conflict, in scripts/_helpers.py: both branches had independently appended unrelated functions to the end of the file (resolve_biomass_classes() here vs. _simplify_polys()/ load_data_versions() upstream) - resolved by keeping both. Full test suite (29 tests) and ruff both pass. Verified via Snakemake dry-run with sector.perennials temporarily set to true that the full 77-job DAG resolves end-to-end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mh2EaBSg63Gsi4epA1Rmky
for more information, see https://pre-commit.ci
…gbr" The technology-data source (BertoGBG/technology-data pypsa-eur_AA branch) names this row "perennials gbr" (Grass BioRefinery), not "perennials refining". add_perennials() was looking up the wrong name, which would crash with KeyError: 'perennials refining' as soon as a run actually exercised this path. Already fixed on the `perennialisation` branch; ported here and to a_CDRs/heat_industry.
for more information, see https://pre-commit.ci
martavp
left a comment
There was a problem hiding this comment.
Thanks @BertoGBG , I left small changes and also run it locally, and it works fine.
In addition:
(a) build_perennials_yields and build_perennials_yields_eurostat_average.py include too many comments (from Claude, I guess) that need to be removed, though they can be useful to keep within the review process.
(b) Could you add in the PR description a short description of why convert_nuts2_to_regions_yields from build_biomass_potentials.py can’t be directly used? (i.e. what we discussed via email)
|
|
||
| <!-- Upcoming Release --> | ||
| <!-- ================= --> | ||
| * feat: Add perennialisation as a carbon dioxide removal (CDR) technology, converting 1st-generation biofuel cropland to perennial grasses, with node-level potential derived from NUTS2-resolved Eurostat crop yields ([#2143](https://github.com/PyPSA/pypsa-eur/issues/2143)). |
There was a problem hiding this comment.
Good and concise. Can we add the following sentence for completeness?
Perennialisation also provideds biogas, which is added to the biogas potential.
| n.add( | ||
| "Link", | ||
| nodes, | ||
| suffix=" perennials refining", |
There was a problem hiding this comment.
why not
suffix=" co2 perennials"
as the rest of the elements?
| snakemake.input.perennials_yields_1G_biofuels | ||
| ).set_index("name") | ||
|
|
||
| # calculate perennials potential based on the conversion on first generation biofuels for equal area |
There was a problem hiding this comment.
| # calculate perennials potential based on the conversion on first generation biofuels for equal area | |
| # calculate potential of CO2 removed via perennials based on the conversion on first generation biofuels for equal area |
| n.add( | ||
| "Link", | ||
| nodes, | ||
| suffix=" perennials refining", |
There was a problem hiding this comment.
| suffix=" perennials refining", | |
| suffix=" co2 perennials", |
| n.add( | ||
| "Store", | ||
| nodes, | ||
| suffix=" CO2s perennials", |
There was a problem hiding this comment.
| suffix=" CO2s perennials", | |
| suffix=" co2 perennials", |
| "Downloading carbon dioxide removal data (afforestation, perennialisation inputs)" | ||
| run: | ||
| with ZipFile(input.zip) as z: | ||
| # GitHub's release archive nests everything under a single |
There was a problem hiding this comment.
you can remove these commented lines
| "sector: perennials=true but no 1G-biofuel groups are in " | ||
| "biomass: classes: 'not included'. Restore upstream defaults " | ||
| "so the groups can be reallocated automatically." | ||
| ) |
There was a problem hiding this comment.
this AssertionError is difficult to understand, I suggest something like:
"sector: perennials=true but no 1G-biofuel groups are in "
"biomass: classes: 'not included'. If all 1G-biofuels are used "
"there is no land available for perennials. "
| return pd.read_csv(cost_file, index_col=0) | ||
|
|
||
|
|
||
| # 1G biofuel crop groups and their target biomass class names. |
There was a problem hiding this comment.
I suggest moving the additions in _helpers.py to build_biomass_potentials. py
| df_nuts2 = gpd.GeoDataFrame(nuts2.geometry).join(yields) | ||
|
|
||
| # Address the missign countries (df_nuts2 contains all NUTS2 + missing shapes (with NaNs)) | ||
| missing_countries = ["AL", "RS", "BA", "XK"] |
There was a problem hiding this comment.
when running only selected countries, this gives an error
| df_nuts2 = gpd.GeoDataFrame(nuts2.geometry).join(yields) | ||
|
|
||
| # Address the missign countries (df_nuts2 contains all NUTS2 + missing shapes (with NaNs)) | ||
| missing_countries = ["AL", "RS", "BA", "XK"] |
There was a problem hiding this comment.
| missing_countries = ["AL", "RS", "BA", "XK"] | |
| missing_countries = df_nuts2.index.intersection(["AL", "RS", "BA", "XK"]) |
Closes # (if applicable).
This PR introduces Perennialisation as Carbon Dioxide Removal (CDR) technology, based on #2143
Perennialisation is defined as the process of replacing biofuels crops (1st generation unsustainable crops in pypsa-eur) with perennial crops (as clover or grass) which differ from lignocellulosic short rotation coppice (as willow or poplar). The overall substitution process is assumed to reduce the CO2 emission by direct removal and sequestration in the carbon soil (as perennials are not harvested every season) and significant reduction in fertilizers and nitrogen emissions compared to seasonal crops. The perennials crops are refined to two products: biogas (added to the model) and proteins for animals (which are assumed to be sold reducing the operational cost of the plant).
The substitution is based on equal land use converting the biofuels crops from Enspreso to perennials green crops using yields derived form Eurostat (using NUTS2 resolution for both). The input data for the yields of both 1st generation biofuels crops and perennials crops are pre-processed and compiled. They available at the zenodo record https://zenodo.org/records/20799337 (includes also afforestation CDR for a future PR) which is downloaded by the rule retrieve_co2_removal_data within retrieve.smk.
The calculation of the CO2 removal potential based on substitution is done by the rules, located in build_sector.smk :
build_perennials_yields_eurostat_average (and matching script)
build_perennials_yields (and matching script)
The whole process of CDR and biogas production is introduced within prepare_sector_network.py .
The default.config.yaml contains the main parameters for calculating the CDR potential (including the key assumption on the sequestration_co2 per hectare per year), and sets perennialisation default to false.
NOTE on Technology-data version.
Data dependency note (technology-data)
add_perennials() requires the "perennials refining" cost entries, which are not part of technology-data v0.14.0 — the version currently pinned in data/versions.csv (costs/"latest supported"). There is an ongoing PR for merging those cost data at PyPSA/technology-data#255
Checklist
Required:
doc/release_notes.md.If applicable:
scripts/lib/validation.doc/*.mdfiles.