-
Notifications
You must be signed in to change notification settings - Fork 365
Add lfp, vanadium, lair, pair battery technologies (Form-Energy Storage) #1961
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add lfp, vanadium, lair, pair battery technologies (Form-Energy Storage) #1961
Conversation
Co-authored-by: Gianvito Colucci <gianvi.colucci@gmail.com>
Co-authored-by: Gianvito Colucci <gianvi.colucci@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds support for multiple new battery storage technologies (LFP, vanadium, liquid-air, compressed-air, and iron-air) to PyPSA-EUR, extending beyond the original battery and H2 storage options. The implementation allows these technologies to be configured as either StorageUnits or as Store-Link combinations.
Key changes:
- Introduces a
STORE_LOOKUPdictionary mapping storage technology names to their component specifications (store, bicharger, charger, discharger) - Refactors storage cost calculations to support multiple technologies with varying configurations
- Updates both electricity-only and sector-coupled network preparation to support new storage types
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/add_electricity.py | Adds STORE_LOOKUP dictionary and refactors attach_storageunits/attach_stores functions to support multiple storage technologies with generic implementation |
| scripts/process_cost_data.py | Updates cost calculation logic to iterate over STORE_LOOKUP entries and handle different charger/discharger configurations |
| scripts/prepare_sector_network.py | Renames add_storage_and_grids to add_h2_gas_infrastructure, removes battery-specific code, and integrates generic storage attachment functions |
| rules/build_sector.smk | Adds electricity parameter provider to make configuration available in sector network preparation |
| config/config.default.yaml | Adds max_hours configuration for new storage technologies and updates extendable_carriers comments |
| doc/configtables/electricity.csv | Updates StorageUnit and Store documentation to list new supported technologies |
| config/plotting.default.yaml | Adds nice names and color mappings for new storage technologies |
| doc/release_notes.rst | Documents the new feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| n.add( | ||
| "Link", | ||
| b_buses_i + " charger", | ||
| bus_names, | ||
| suffix=f" {charge_name}", | ||
| bus0=buses_i, | ||
| bus1=b_buses_i, | ||
| carrier="battery charger", | ||
| # the efficiencies are "round trip efficiencies" | ||
| efficiency=costs.at["battery inverter", "efficiency"] ** 0.5, | ||
| capital_cost=costs.at["battery inverter", "capital_cost"], | ||
| bus1=bus_names, | ||
| carrier=f"{carrier} {charge_name}", | ||
| efficiency=costs.at[lookup_charge, "efficiency"] ** roundtrip_correction, | ||
| capital_cost=costs.at[lookup_charge, "capital_cost"], | ||
| p_nom_extendable=True, | ||
| marginal_cost=costs.at["battery inverter", "marginal_cost"], | ||
| lifetime=costs.at[lookup_charge, "lifetime"], | ||
| ) | ||
|
|
||
| n.add( | ||
| "Link", | ||
| b_buses_i + " discharger", | ||
| bus0=b_buses_i, | ||
| bus_names, | ||
| suffix=f" {discharge_name}", | ||
| bus0=bus_names, | ||
| bus1=buses_i, | ||
| carrier="battery discharger", | ||
| efficiency=costs.at["battery inverter", "efficiency"] ** 0.5, | ||
| carrier=f"{carrier} {discharge_name}", | ||
| efficiency=costs.at[lookup_discharge, "efficiency"] ** roundtrip_correction, | ||
| p_nom_extendable=True, | ||
| marginal_cost=costs.at["battery inverter", "marginal_cost"], | ||
| lifetime=costs.at[lookup_discharge, "lifetime"], | ||
| ) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The carrier names for the charger and discharger links (e.g., "battery charger", "battery discharger") are not being registered with n.add("Carrier", ...). While line 1042 registers the main carrier (e.g., "battery"), the derived carrier names used in lines 1087 and 1100 should also be registered. This could cause issues with carrier-based filtering or plotting. Consider adding these carriers to the network before creating the links.
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | ||
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description still refers only to "battery and/or hydrogen" but should be updated to reflect all the new storage technologies that are now supported (li-ion, vanadium, lfp, lair, pair, iron-air). Consider updating to: "Adds extendable storage units at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air."
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | |
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | |
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}","Adds extendable storage units at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air." | |
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}","Adds extendable storage units at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air." |
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | ||
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description still refers only to "battery and/or hydrogen" but should be updated to reflect all the new storage technologies that are now supported (li-ion, vanadium, lfp, lair, pair, iron-air). Consider updating to: "Adds extendable stores at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air."
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | |
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}",Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity. | |
| -- StorageUnit,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}","Adds extendable stores at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air." | |
| -- Store,--,"Any subset of {'battery','H2','li-ion','vanadium','lfp','lair','pair','iron-air'}","Adds extendable stores at every node/bus after clustering without capacity limits and with zero initial capacity. Supported technologies include battery, H2, li-ion, vanadium, lfp, lair, pair, and iron-air." |
| if bicharger: | ||
| costs.loc[k] = costs_for_storage( | ||
| costs.loc[store], | ||
| costs.loc[bicharger], | ||
| max_hours=v, | ||
| ) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here has a potential issue. When bicharger is defined but store is None (i.e., the store component is not found in costs), the code will execute line 210-214 and attempt to access costs.loc[store] where store is None. This will cause a KeyError. The condition should check that both bicharger and store are not None before proceeding.
| n.add( | ||
| "Link", | ||
| b_buses_i + " charger", | ||
| bus_names, | ||
| suffix=f" {charge_name}", | ||
| bus0=buses_i, | ||
| bus1=b_buses_i, | ||
| carrier="battery charger", | ||
| # the efficiencies are "round trip efficiencies" | ||
| efficiency=costs.at["battery inverter", "efficiency"] ** 0.5, | ||
| capital_cost=costs.at["battery inverter", "capital_cost"], | ||
| bus1=bus_names, | ||
| carrier=f"{carrier} {charge_name}", | ||
| efficiency=costs.at[lookup_charge, "efficiency"] ** roundtrip_correction, | ||
| capital_cost=costs.at[lookup_charge, "capital_cost"], | ||
| p_nom_extendable=True, | ||
| marginal_cost=costs.at["battery inverter", "marginal_cost"], | ||
| lifetime=costs.at[lookup_charge, "lifetime"], | ||
| ) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Link components are missing the marginal_cost parameter. While this may default to 0 in PyPSA, for consistency with the StorageUnit implementation (line 1005) and to allow for non-zero marginal costs when needed, this parameter should be explicitly set. Consider adding marginal_cost=costs.at[lookup_charge, "marginal_cost"] for the charger link.
| efficiency=costs.at[lookup_discharge, "efficiency"] ** roundtrip_correction, | ||
| p_nom_extendable=True, | ||
| marginal_cost=costs.at["battery inverter", "marginal_cost"], | ||
| lifetime=costs.at[lookup_discharge, "lifetime"], |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Link component is missing the marginal_cost parameter. While this may default to 0 in PyPSA, for consistency with the StorageUnit implementation (line 1005) and to allow for non-zero marginal costs when needed, this parameter should be explicitly set. Consider adding marginal_cost=costs.at[lookup_discharge, "marginal_cost"] for the discharger link.
| lifetime=costs.at[lookup_discharge, "lifetime"], | |
| lifetime=costs.at[lookup_discharge, "lifetime"], | |
| marginal_cost=costs.at[lookup_discharge, "marginal_cost"], |
| ================ | ||
|
|
||
| * Include new storage technologies such as li-ion, vanadium, lfp, lair, pair and iron-air. These technologies can now be configured as either store-link combinations or standalone storage units. | ||
| Implemented in both `add_electricity.py` and `prepare_sector_network.py`(https://github.com/PyPSA/pypsa-eur/pull/1961). |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The release note has a broken markdown link. The closing parenthesis should come before the opening parenthesis for the link URL. It should be: Implemented in both add_electricity.py and prepare_sector_network.py (https://github.com/PyPSA/pypsa-eur/pull/1961).
| Implemented in both `add_electricity.py` and `prepare_sector_network.py`(https://github.com/PyPSA/pypsa-eur/pull/1961). | |
| Implemented in both `add_electricity.py` and `prepare_sector_network.py` (https://github.com/PyPSA/pypsa-eur/pull/1961). |
| buses_i : list | ||
| List of high voltage electricity buses. | ||
| extendable_carriers : dict | ||
| Dictionary of extendable energy carriers. |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring parameter description for extendable_carriers is misleading. It's described as "Dictionary of extendable energy carriers" but the actual input is now a list (as can be seen from line 6333 where it's called with extendable_stores which is a list). Update to: "List of extendable storage carrier names."
| Dictionary of extendable energy carriers. | |
| List of extendable storage carrier names. |
| buses_i : list | ||
| List of high voltage electricity buses. | ||
| extendable_carriers : dict | ||
| Dictionary of extendable energy carriers. |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring parameter description for extendable_carriers is misleading. It's described as "Dictionary of extendable energy carriers" but the actual input is now a list (as can be seen from line 6325 where it's called with extendable_storageunits which is a list). Update to: "List of extendable storage carrier names."
| Dictionary of extendable energy carriers. | |
| List of extendable storage carrier names. |
Closes #7
Previously attempted in OET #58 (Better to upstream to OS)
Changes proposed in this Pull Request
This is a feature that were first implemented in the form-energy-storage storage that has many applications in future projects (procurement-metastudy, google-go)
It aims to make any storage technologies implementable both in the forms of stores and links, or as storage units.
With it, novel storage technologies such as
li-ion,vanadium,lfp,lair,pair,iron-aircan be included.The original design made many changes such as the options for multiple
max_hoursfor one technology. I think that is too detailed and have been omitted.The original design rename the term batteries to li-ion for more clarity, but this would lead to backward incompatibility, so we just let both terms be valid.
It works for both in
add_electricityandprepare_sector_network.Technology mapping, nice names, color is to be added.
I tested my contribution locally and it works as intended.
Code and workflow changes are sufficiently documented.
Changed dependencies are added to
pixi.toml(usingpixi add <dependency-name>).Changes in configuration options are added in
config/config.default.yaml.Changes in configuration options are documented in
doc/configtables/*.csv.For new data sources or versions, these instructions have been followed.
A release note
doc/release_notes.rstis added.