Merge branch 'update-district-heating-cops' into country-specific-dh-forward-temperatures

This commit is contained in:
AmosSchledorn 2024-08-06 15:51:22 +02:00
commit d903f4ace9
30 changed files with 1164 additions and 483 deletions

View File

@ -65,10 +65,10 @@ The dataset consists of:
(alternating current lines at and above 220kV voltage level and all high (alternating current lines at and above 220kV voltage level and all high
voltage direct current lines) and 3803 substations. voltage direct current lines) and 3803 substations.
- The open power plant database - The open power plant database
[powerplantmatching](https://github.com/FRESNA/powerplantmatching). [powerplantmatching](https://github.com/PyPSA/powerplantmatching).
- Electrical demand time series from the - Electrical demand time series from the
[OPSD project](https://open-power-system-data.org/). [OPSD project](https://open-power-system-data.org/).
- Renewable time series based on ERA5 and SARAH, assembled using the [atlite tool](https://github.com/FRESNA/atlite). - Renewable time series based on ERA5 and SARAH, assembled using the [atlite tool](https://github.com/PyPSA/atlite).
- Geographical potentials for wind and solar generators based on land use (CORINE) and excluding nature reserves (Natura2000) are computed with the [atlite library](https://github.com/PyPSA/atlite). - Geographical potentials for wind and solar generators based on land use (CORINE) and excluding nature reserves (Natura2000) are computed with the [atlite library](https://github.com/PyPSA/atlite).
A sector-coupled extension adds demand A sector-coupled extension adds demand

0
borg-it Executable file → Normal file
View File

View File

@ -355,7 +355,6 @@ biomass:
- Secondary Forestry residues - woodchips - Secondary Forestry residues - woodchips
- Sawdust - Sawdust
- Residues from landscape care - Residues from landscape care
- Municipal waste
not included: not included:
- Sugar from sugar beet - Sugar from sugar beet
- Rape seed - Rape seed
@ -369,6 +368,8 @@ biomass:
biogas: biogas:
- Manure solid, liquid - Manure solid, liquid
- Sludge - Sludge
municipal solid waste:
- Municipal waste
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#solar-thermal # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#solar-thermal
solar_thermal: solar_thermal:
@ -397,6 +398,7 @@ sector:
biomass: true biomass: true
industry: true industry: true
agriculture: true agriculture: true
fossil_fuels: true
district_heating: district_heating:
potential: 0.6 potential: 0.6
progress: progress:
@ -426,6 +428,14 @@ sector:
heat_exchanger_pinch_point_temperature_difference: 5 #K heat_exchanger_pinch_point_temperature_difference: 5 #K
isentropic_compressor_efficiency: 0.8 isentropic_compressor_efficiency: 0.8
heat_loss: 0.0 heat_loss: 0.0
heat_pump_sources:
urban central:
- air
urban decentral:
- air
rural:
- air
- ground
cluster_heat_buses: true cluster_heat_buses: true
heat_demand_cutout: default heat_demand_cutout: default
bev_dsm_restriction_value: 0.75 bev_dsm_restriction_value: 0.75
@ -614,7 +624,9 @@ sector:
conventional_generation: conventional_generation:
OCGT: gas OCGT: gas
biomass_to_liquid: false biomass_to_liquid: false
electrobiofuels: false
biosng: false biosng: false
municipal_solid_waste: false
limit_max_growth: limit_max_growth:
enable: false enable: false
# allowing 30% larger than max historic growth # allowing 30% larger than max historic growth
@ -636,6 +648,12 @@ sector:
max_boost: 0.25 max_boost: 0.25
var_cf: true var_cf: true
sustainability_factor: 0.0025 sustainability_factor: 0.0025
solid_biomass_import:
enable: false
price: 54 #EUR/MWh
max_amount: 1390 # TWh
upstream_emissions_factor: .1 #share of solid biomass CO2 emissions at full combustion
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#industry # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#industry
industry: industry:
@ -1033,6 +1051,8 @@ plotting:
biogas: '#e3d37d' biogas: '#e3d37d'
biomass: '#baa741' biomass: '#baa741'
solid biomass: '#baa741' solid biomass: '#baa741'
municipal solid waste: '#91ba41'
solid biomass import: '#d5ca8d'
solid biomass transport: '#baa741' solid biomass transport: '#baa741'
solid biomass for industry: '#7a6d26' solid biomass for industry: '#7a6d26'
solid biomass for industry CC: '#47411c' solid biomass for industry CC: '#47411c'
@ -1046,6 +1066,7 @@ plotting:
services rural biomass boiler: '#c6cf98' services rural biomass boiler: '#c6cf98'
services urban decentral biomass boiler: '#dde5b5' services urban decentral biomass boiler: '#dde5b5'
biomass to liquid: '#32CD32' biomass to liquid: '#32CD32'
electrobiofuels: 'red'
BioSNG: '#123456' BioSNG: '#123456'
# power transmission # power transmission
lines: '#6c9459' lines: '#6c9459'
@ -1072,7 +1093,7 @@ plotting:
V2G: '#e5ffa8' V2G: '#e5ffa8'
land transport EV: '#baf238' land transport EV: '#baf238'
land transport demand: '#38baf2' land transport demand: '#38baf2'
Li ion: '#baf238' EV battery: '#baf238'
# hot water storage # hot water storage
water tanks: '#e69487' water tanks: '#e69487'
residential rural water tanks: '#f7b7a3' residential rural water tanks: '#f7b7a3'

View File

@ -23,7 +23,7 @@ Visby-Nas,Sweden - Nas 57°0558″N 18°1427″E / 57.09944°N 18.24
SwePol,Poland - Wierzbięcin 54°308″N 16°5328″E / 54.50222°N 16.89111°E,Sweden - Stärnö 56°911″N 14°5029″E / 56.15306°N 14.84139°E,245(245/0),450,600.0,2000,Thyr,Supplier: ABB,[38],16.891111111111112,54.50222222222222,14.841388888888888,56.153055555555554 SwePol,Poland - Wierzbięcin 54°308″N 16°5328″E / 54.50222°N 16.89111°E,Sweden - Stärnö 56°911″N 14°5029″E / 56.15306°N 14.84139°E,245(245/0),450,600.0,2000,Thyr,Supplier: ABB,[38],16.891111111111112,54.50222222222222,14.841388888888888,56.153055555555554
Tjæreborg,Denmark - Tjæreborg/Enge 55°2652″N 8°3534″E / 55.44778°N 8.59278°E,Denmark - Tjæreborg/Substation 55°2807″N 8°3336″E / 55.46861°N 8.56000°E,4.3(4.3/0),9,7.0,2000,IGBT,Interconnection to wind power generating stations,,8.592777777777778,55.44777777777778,8.56,55.46861111111111 Tjæreborg,Denmark - Tjæreborg/Enge 55°2652″N 8°3534″E / 55.44778°N 8.59278°E,Denmark - Tjæreborg/Substation 55°2807″N 8°3336″E / 55.46861°N 8.56000°E,4.3(4.3/0),9,7.0,2000,IGBT,Interconnection to wind power generating stations,,8.592777777777778,55.44777777777778,8.56,55.46861111111111
Italy-Greece,Greece - Arachthos 39°1100″N 20°5748″E / 39.18333°N 20.96333°E,Italy - Galatina 40°953″N 18°749″E / 40.16472°N 18.13028°E,310(200/110),400,500.0,2001,Thyr,,,20.963333333333335,39.18333333333333,18.130277777777778,40.164722222222224 Italy-Greece,Greece - Arachthos 39°1100″N 20°5748″E / 39.18333°N 20.96333°E,Italy - Galatina 40°953″N 18°749″E / 40.16472°N 18.13028°E,310(200/110),400,500.0,2001,Thyr,,,20.963333333333335,39.18333333333333,18.130277777777778,40.164722222222224
Moyle,UK - Auchencrosh 55°0410″N 4°5850″W / 55.06944°N 4.98056°W,UK - N. Ireland- Ballycronan More 54°5034″N 5°4611″W / 54.84278°N 5.76972°W,63.5(63.5/0),250,2501.0,2001,Thyr,"Supplier: Siemens- Nexans",[39],-4.980555555555556,55.06944444444444,-5.769722222222223,54.842777777777776 Moyle,UK - Auchencrosh 55°0410″N 4°5850″W / 55.06944°N 4.98056°W,UK - N. Ireland- Ballycronan More 54°5034″N 5°4611″W / 54.84278°N 5.76972°W,63.5(63.5/0),250,500.0,2001,Thyr,"Supplier: Siemens- Nexans",[39],-4.980555555555556,55.06944444444444,-5.769722222222223,54.842777777777776
HVDC Troll,Norway - Kollsnes 60°3301″N 4°5026″E / 60.55028°N 4.84056°E,Norway - Offshore platform Troll A 60°4000″N 3°4000″E / 60.66667°N 3.66667°E,70(70/0),60,80.0,2004,IGBT,Power supply for offshore gas compressor Supplier: ABB,[40],4.8405555555555555,60.55027777777778,3.6666666666666665,60.666666666666664 HVDC Troll,Norway - Kollsnes 60°3301″N 4°5026″E / 60.55028°N 4.84056°E,Norway - Offshore platform Troll A 60°4000″N 3°4000″E / 60.66667°N 3.66667°E,70(70/0),60,80.0,2004,IGBT,Power supply for offshore gas compressor Supplier: ABB,[40],4.8405555555555555,60.55027777777778,3.6666666666666665,60.666666666666664
Estlink,Finland - Espoo 60°1214″N 24°3306″E / 60.20389°N 24.55167°E,Estonia - Harku 59°235″N 24°3337″E / 59.38472°N 24.56028°E,105(105/0),150,350.0,2006,IGBT,Supplier: ABB,[40],24.551666666666666,60.20388888888889,24.560277777777777,59.38472222222222 Estlink,Finland - Espoo 60°1214″N 24°3306″E / 60.20389°N 24.55167°E,Estonia - Harku 59°235″N 24°3337″E / 59.38472°N 24.56028°E,105(105/0),150,350.0,2006,IGBT,Supplier: ABB,[40],24.551666666666666,60.20388888888889,24.560277777777777,59.38472222222222
NorNed,Netherlands - Eemshaven 53°264″N 6°5157″E / 53.43444°N 6.86583°E,Norway - Feda 58°1658″N 6°5155″E / 58.28278°N 6.86528°E,580(580/0),450,700.0,2008,Thyr,"Supplier: ABB- Nexans",[40],6.865833333333334,53.434444444444445,6.865277777777778,58.28277777777778 NorNed,Netherlands - Eemshaven 53°264″N 6°5157″E / 53.43444°N 6.86583°E,Norway - Feda 58°1658″N 6°5155″E / 58.28278°N 6.86528°E,580(580/0),450,700.0,2008,Thyr,"Supplier: ABB- Nexans",[40],6.865833333333334,53.434444444444445,6.865277777777778,58.28277777777778

1 Name Converterstation 1 Converterstation 2 Total Length (Cable/Pole) (km) Volt (kV) Power (MW) Year Type Remarks Ref x1 y1 x2 y2
23 SwePol Poland - Wierzbięcin 54°30′8″N 16°53′28″E / 54.50222°N 16.89111°E Sweden - Stärnö 56°9′11″N 14°50′29″E / 56.15306°N 14.84139°E 245(245/0) 450 600.0 2000 Thyr Supplier: ABB [38] 16.891111111111112 54.50222222222222 14.841388888888888 56.153055555555554
24 Tjæreborg Denmark - Tjæreborg/Enge 55°26′52″N 8°35′34″E / 55.44778°N 8.59278°E Denmark - Tjæreborg/Substation 55°28′07″N 8°33′36″E / 55.46861°N 8.56000°E 4.3(4.3/0) 9 7.0 2000 IGBT Interconnection to wind power generating stations 8.592777777777778 55.44777777777778 8.56 55.46861111111111
25 Italy-Greece Greece - Arachthos 39°11′00″N 20°57′48″E / 39.18333°N 20.96333°E Italy - Galatina 40°9′53″N 18°7′49″E / 40.16472°N 18.13028°E 310(200/110) 400 500.0 2001 Thyr 20.963333333333335 39.18333333333333 18.130277777777778 40.164722222222224
26 Moyle UK - Auchencrosh 55°04′10″N 4°58′50″W / 55.06944°N 4.98056°W UK - N. Ireland- Ballycronan More 54°50′34″N 5°46′11″W / 54.84278°N 5.76972°W 63.5(63.5/0) 250 2501.0 500.0 2001 Thyr Supplier: Siemens- Nexans [39] -4.980555555555556 55.06944444444444 -5.769722222222223 54.842777777777776
27 HVDC Troll Norway - Kollsnes 60°33′01″N 4°50′26″E / 60.55028°N 4.84056°E Norway - Offshore platform Troll A 60°40′00″N 3°40′00″E / 60.66667°N 3.66667°E 70(70/0) 60 80.0 2004 IGBT Power supply for offshore gas compressor Supplier: ABB [40] 4.8405555555555555 60.55027777777778 3.6666666666666665 60.666666666666664
28 Estlink Finland - Espoo 60°12′14″N 24°33′06″E / 60.20389°N 24.55167°E Estonia - Harku 59°23′5″N 24°33′37″E / 59.38472°N 24.56028°E 105(105/0) 150 350.0 2006 IGBT Supplier: ABB [40] 24.551666666666666 60.20388888888889 24.560277777777777 59.38472222222222
29 NorNed Netherlands - Eemshaven 53°26′4″N 6°51′57″E / 53.43444°N 6.86583°E Norway - Feda 58°16′58″N 6°51′55″E / 58.28278°N 6.86528°E 580(580/0) 450 700.0 2008 Thyr Supplier: ABB- Nexans [40] 6.865833333333334 53.434444444444445 6.865277777777778 58.28277777777778

View File

@ -4,18 +4,23 @@ heating,--,"{true, false}",Flag to include heating sector.
biomass,--,"{true, false}",Flag to include biomass sector. biomass,--,"{true, false}",Flag to include biomass sector.
industry,--,"{true, false}",Flag to include industry sector. industry,--,"{true, false}",Flag to include industry sector.
agriculture,--,"{true, false}",Flag to include agriculture sector. agriculture,--,"{true, false}",Flag to include agriculture sector.
fossil_fuels,--,"{true, false}","Flag to include imports of fossil fuels ( [""coal"", ""gas"", ""oil"", ""lignite""])"
district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_ district_heating,--,,`prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_
#NAME?,--,float,maximum fraction of urban demand which can be supplied by district heating. Ignored where below current fraction. -- potential,--,float,maximum fraction of urban demand which can be supplied by district heating
#NAME?,--,Dictionary with planning horizons as keys., Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating -- progress,--,Dictionary with planning horizons as keys., Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating
#NAME?,--,float,Share increase in district heat demand in urban central due to heat losses -- district_heating_loss,--,float,Share increase in district heat demand in urban central due to heat losses
#NAME?,°C,float,Forward temperature in district heating -- forward_temperature,°C,float,Forward temperature in district heating
#NAME?,°C,float,Return temperature in district heating. Must be lower than forward temperature -- return_temperature,°C,float,Return temperature in district heating. Must be lower than forward temperature
#NAME?,K,float,Cooling of heat source for heat pumps -- heat_source_cooling,K,float,Cooling of heat source for heat pumps
#NAME?,,, -- heat_pump_cop_approximation,,,
#NAME?,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation -- -- refrigerant,--,"{ammonia, isobutane}",Heat pump refrigerant assumed for COP approximation
#NAME?,K,float,Heat pump pinch point temperature difference in heat exchangers assumed for approximation. -- -- heat_exchanger_pinch_point_temperature_difference,K,float,Heat pump pinch point temperature difference in heat exchangers assumed for approximation.
#NAME?,--,float,Isentropic efficiency of heat pump compressor assumed for approximation. Must be between 0 and 1. -- -- isentropic_compressor_efficiency,--,float,Isentropic efficiency of heat pump compressor assumed for approximation. Must be between 0 and 1.
#NAME?,--,float,Heat pump heat loss assumed for approximation. Must be between 0 and 1. -- -- heat_loss,--,float,Heat pump heat loss assumed for approximation. Must be between 0 and 1.
-- heat_pump_sources,--,,
-- -- urban central,--,List of heat sources for heat pumps in urban central heating,
-- -- urban decentral,--,List of heat sources for heat pumps in urban decentral heating,
-- -- rural,--,List of heat sources for heat pumps in rural heating,
cluster_heat_buses,--,"{true, false}",Cluster residential and service heat buses in `prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_ to one to save memory. cluster_heat_buses,--,"{true, false}",Cluster residential and service heat buses in `prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_ to one to save memory.
,,, ,,,
bev_dsm_restriction _value,--,float,Adds a lower state of charge (SOC) limit for battery electric vehicles (BEV) to manage its own energy demand (DSM). Located in `build_transport_demand.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_transport_demand.py>`_. Set to 0 for no restriction on BEV DSM bev_dsm_restriction _value,--,float,Adds a lower state of charge (SOC) limit for battery electric vehicles (BEV) to manage its own energy demand (DSM). Located in `build_transport_demand.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_transport_demand.py>`_. Set to 0 for no restriction on BEV DSM
@ -65,16 +70,16 @@ heat_pump_sink_T,°C,float,The temperature heat sink used in heat pumps based on
reduce_space_heat _exogenously,--,"{true, false}",Influence on space heating demand by a certain factor (applied before losses in district heating). reduce_space_heat _exogenously,--,"{true, false}",Influence on space heating demand by a certain factor (applied before losses in district heating).
reduce_space_heat _exogenously_factor,--,Dictionary with planning horizons as keys.,"A positive factor can mean renovation or demolition of a building. If the factor is negative, it can mean an increase in floor area, increased thermal comfort, population growth. The default factors are determined by the `Eurocalc Homes and buildings decarbonization scenario <http://tool.european-calculator.eu/app/buildings/building-types-area/?levers=1ddd4444421213bdbbbddd44444ffffff11f411111221111211l212221>`_" reduce_space_heat _exogenously_factor,--,Dictionary with planning horizons as keys.,"A positive factor can mean renovation or demolition of a building. If the factor is negative, it can mean an increase in floor area, increased thermal comfort, population growth. The default factors are determined by the `Eurocalc Homes and buildings decarbonization scenario <http://tool.european-calculator.eu/app/buildings/building-types-area/?levers=1ddd4444421213bdbbbddd44444ffffff11f411111221111211l212221>`_"
retrofitting,,, retrofitting,,,
#NAME?,--,"{true, false}",Add retrofitting as an endogenous system which co-optimise space heat savings. -- retro_endogen,--,"{true, false}",Add retrofitting as an endogenous system which co-optimise space heat savings.
#NAME?,--,float,Weight costs for building renovation -- cost_factor,--,float,Weight costs for building renovation
#NAME?,--,float,The interest rate for investment in building components -- interest_rate,--,float,The interest rate for investment in building components
#NAME?,--,"{true, false}",Annualise the investment costs of retrofitting -- annualise_cost,--,"{true, false}",Annualise the investment costs of retrofitting
#NAME?,--,"{true, false}",Weight the costs of retrofitting depending on taxes in countries -- tax_weighting,--,"{true, false}",Weight the costs of retrofitting depending on taxes in countries
#NAME?,--,"{true, false}",Weight the costs of retrofitting depending on labour/material costs per country -- construction_index,--,"{true, false}",Weight the costs of retrofitting depending on labour/material costs per country
tes,--,"{true, false}",Add option for storing thermal energy in large water pits associated with district heating systems and individual thermal energy storage (TES) tes,--,"{true, false}",Add option for storing thermal energy in large water pits associated with district heating systems and individual thermal energy storage (TES)
tes_tau,,,The time constant used to calculate the decay of thermal energy in thermal energy storage (TES): 1- :math:`e^{-1/24τ}`. tes_tau,,,The time constant used to calculate the decay of thermal energy in thermal energy storage (TES): 1- :math:`e^{-1/24τ}`.
#NAME?,days,float,The time constant in decentralized thermal energy storage (TES) -- decentral,days,float,The time constant in decentralized thermal energy storage (TES)
#NAME?,days,float,The time constant in centralized thermal energy storage (TES) -- central,days,float,The time constant in centralized thermal energy storage (TES)
boilers,--,"{true, false}",Add option for transforming gas into heat using gas boilers boilers,--,"{true, false}",Add option for transforming gas into heat using gas boilers
resistive_heaters,--,"{true, false}",Add option for transforming electricity into heat using resistive heaters (independently from gas boilers) resistive_heaters,--,"{true, false}",Add option for transforming electricity into heat using resistive heaters (independently from gas boilers)
oil_boilers,--,"{true, false}",Add option for transforming oil into heat using boilers oil_boilers,--,"{true, false}",Add option for transforming oil into heat using boilers
@ -97,12 +102,12 @@ SMR CC,--,"{true, false}",Add option for transforming natural gas into hydrogen
regional_methanol_demand,--,"{true, false}",Spatially resolve methanol demand. Set to true if regional CO2 constraints needed. regional_methanol_demand,--,"{true, false}",Spatially resolve methanol demand. Set to true if regional CO2 constraints needed.
regional_oil_demand,--,"{true, false}",Spatially resolve oil demand. Set to true if regional CO2 constraints needed. regional_oil_demand,--,"{true, false}",Spatially resolve oil demand. Set to true if regional CO2 constraints needed.
regional_co2 _sequestration_potential,,, regional_co2 _sequestration_potential,,,
#NAME?,--,"{true, false}",Add option for regionally-resolved geological carbon dioxide sequestration potentials based on `CO2StoP <https://setis.ec.europa.eu/european-co2-storage-database_en>`_. -- enable,--,"{true, false}",Add option for regionally-resolved geological carbon dioxide sequestration potentials based on `CO2StoP <https://setis.ec.europa.eu/european-co2-storage-database_en>`_.
#NAME?,--,string or list,Name (or list of names) of the attribute(s) for the sequestration potential -- attribute,--,string or list,Name (or list of names) of the attribute(s) for the sequestration potential
#NAME?,--,"{true, false}",Add options for including onshore sequestration potentials -- include_onshore,--,"{true, false}",Add options for including onshore sequestration potentials
#NAME?,Gt ,float,Any sites with lower potential than this value will be excluded -- min_size,Gt ,float,Any sites with lower potential than this value will be excluded
#NAME?,Gt ,float,The maximum sequestration potential for any one site. -- max_size,Gt ,float,The maximum sequestration potential for any one site.
#NAME?,years,float,The years until potential exhausted at optimised annual rate -- years_of_storage,years,float,The years until potential exhausted at optimised annual rate
co2_sequestration_potential,MtCO2/a,float,The potential of sequestering CO2 in Europe per year co2_sequestration_potential,MtCO2/a,float,The potential of sequestering CO2 in Europe per year
co2_sequestration_cost,currency/tCO2,float,The cost of sequestering a ton of CO2 co2_sequestration_cost,currency/tCO2,float,The cost of sequestering a ton of CO2
co2_sequestration_lifetime,years,int,The lifetime of a CO2 sequestration site co2_sequestration_lifetime,years,int,The lifetime of a CO2 sequestration site
@ -129,9 +134,9 @@ electricity_distribution _grid_cost_factor,,,Multiplies the investment cost of t
electricity_grid _connection,--,"{true, false}",Add the cost of electricity grid connection for onshore wind and solar electricity_grid _connection,--,"{true, false}",Add the cost of electricity grid connection for onshore wind and solar
transmission_efficiency,,,Section to specify transmission losses or compression energy demands of bidirectional links. Splits them into two capacity-linked unidirectional links. transmission_efficiency,,,Section to specify transmission losses or compression energy demands of bidirectional links. Splits them into two capacity-linked unidirectional links.
-- {carrier},--,str,The carrier of the link. -- {carrier},--,str,The carrier of the link.
#NAME?,p.u.,float,Length-independent transmission efficiency. -- -- efficiency_static,p.u.,float,Length-independent transmission efficiency.
#NAME?,p.u. per 1000 km,float,Length-dependent transmission efficiency ($\eta^{\text{length}}$) -- -- efficiency_per_1000km,p.u. per 1000 km,float,Length-dependent transmission efficiency ($\eta^{\text{length}}$)
#NAME?,p.u. per 1000 km,float,Length-dependent electricity demand for compression ($\eta \cdot \text{length}$) implemented as multi-link to local electricity bus. -- -- compression_per_1000km,p.u. per 1000 km,float,Length-dependent electricity demand for compression ($\eta \cdot \text{length}$) implemented as multi-link to local electricity bus.
H2_network,--,"{true, false}",Add option for new hydrogen pipelines H2_network,--,"{true, false}",Add option for new hydrogen pipelines
gas_network,--,"{true, false}","Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm <https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation>`_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well." gas_network,--,"{true, false}","Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm <https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation>`_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well."
H2_retrofit,--,"{true, false}",Add option for retrofiting existing pipelines to transport hydrogen. H2_retrofit,--,"{true, false}",Add option for retrofiting existing pipelines to transport hydrogen.
@ -146,18 +151,24 @@ biogas_upgrading_cc,--,"{true, false}",Add option to capture CO2 from biomass up
conventional_generation,,,Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel. conventional_generation,,,Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel.
biomass_to_liquid,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil biomass_to_liquid,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil
biosng,--,"{true, false}",Add option for transforming solid biomass into synthesis gas with the same properties as natural gas biosng,--,"{true, false}",Add option for transforming solid biomass into synthesis gas with the same properties as natural gas
municipal_solid_waste,--,"{true, false}",Add option for municipal solid waste
limit_max_growth,,, limit_max_growth,,,
#NAME?,--,"{true, false}",Add option to limit the maximum growth of a carrier -- enable,--,"{true, false}",Add option to limit the maximum growth of a carrier
#NAME?,p.u.,float,The maximum growth factor of a carrier (e.g. 1.3 allows 30% larger than max historic growth) -- factor,p.u.,float,The maximum growth factor of a carrier (e.g. 1.3 allows 30% larger than max historic growth)
#NAME?,,, -- max_growth,,,
-- -- {carrier},GW,float,The historic maximum growth of a carrier -- -- {carrier},GW,float,The historic maximum growth of a carrier
#NAME?,,, -- max_relative_growth,,,
-- -- {carrier},p.u.,float,The historic maximum relative growth of a carrier -- -- {carrier},p.u.,float,The historic maximum relative growth of a carrier
,,, ,,,
enhanced_geothermal,,, enhanced_geothermal,,,
#NAME?,--,"{true, false}",Add option to include Enhanced Geothermal Systems -- enable,--,"{true, false}",Add option to include Enhanced Geothermal Systems
#NAME?,--,"{true, false}",Add option for flexible operation (see Ricks et al. 2024) -- flexible,--,"{true, false}",Add option for flexible operation (see Ricks et al. 2024)
#NAME?,--,int,The maximum hours the reservoir can be charged under flexible operation -- max_hours,--,int,The maximum hours the reservoir can be charged under flexible operation
#NAME?,--,float,The maximum boost in power output under flexible operation -- max_boost,--,float,The maximum boost in power output under flexible operation
#NAME?,--,"{true, false}",Add option for variable capacity factor (see Ricks et al. 2024) -- var_cf,--,"{true, false}",Add option for variable capacity factor (see Ricks et al. 2024)
#NAME?,--,float,Share of sourced heat that is replenished by the earth's core (see details in `build_egs_potentials.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_egs_potentials.py>`_) -- sustainability_factor,--,float,Share of sourced heat that is replenished by the earth's core (see details in `build_egs_potentials.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_egs_potentials.py>`_)
solid_biomass_import,,,
-- enable,--,"{true, false}",Add option to include solid biomass imports
-- price,currency/MWh,float,Price for importing solid biomass
-- max_amount,Twh,float,Maximum solid biomass import potential
-- upstream_emissions_factor,p.u.,float,Upstream emissions of solid biomass imports

1 Unit Values Description
4 biomass -- {true, false} Flag to include biomass sector.
5 industry -- {true, false} Flag to include industry sector.
6 agriculture -- {true, false} Flag to include agriculture sector.
7 fossil_fuels -- {true, false} Flag to include imports of fossil fuels ( ["coal", "gas", "oil", "lignite"])
8 district_heating -- `prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_
9 #NAME? -- potential -- float maximum fraction of urban demand which can be supplied by district heating. Ignored where below current fraction. maximum fraction of urban demand which can be supplied by district heating
10 #NAME? -- progress -- Dictionary with planning horizons as keys. Increase of today's district heating demand to potential maximum district heating share. Progress = 0 means today's district heating share. Progress = 1 means maximum fraction of urban demand is supplied by district heating
11 #NAME? -- district_heating_loss -- float Share increase in district heat demand in urban central due to heat losses
12 #NAME? -- forward_temperature °C float Forward temperature in district heating
13 #NAME? -- return_temperature °C float Return temperature in district heating. Must be lower than forward temperature
14 #NAME? -- heat_source_cooling K float Cooling of heat source for heat pumps
15 #NAME? -- heat_pump_cop_approximation
16 #NAME? -- -- refrigerant -- {ammonia, isobutane} Heat pump refrigerant assumed for COP approximation
17 #NAME? -- -- heat_exchanger_pinch_point_temperature_difference K float Heat pump pinch point temperature difference in heat exchangers assumed for approximation.
18 #NAME? -- -- isentropic_compressor_efficiency -- float Isentropic efficiency of heat pump compressor assumed for approximation. Must be between 0 and 1.
19 #NAME? -- -- heat_loss -- float Heat pump heat loss assumed for approximation. Must be between 0 and 1.
20 -- heat_pump_sources --
21 -- -- urban central -- List of heat sources for heat pumps in urban central heating
22 -- -- urban decentral -- List of heat sources for heat pumps in urban decentral heating
23 -- -- rural -- List of heat sources for heat pumps in rural heating
24 cluster_heat_buses -- {true, false} Cluster residential and service heat buses in `prepare_sector_network.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/prepare_sector_network.py>`_ to one to save memory.
25
26 bev_dsm_restriction _value -- float Adds a lower state of charge (SOC) limit for battery electric vehicles (BEV) to manage its own energy demand (DSM). Located in `build_transport_demand.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_transport_demand.py>`_. Set to 0 for no restriction on BEV DSM
70 reduce_space_heat _exogenously -- {true, false} Influence on space heating demand by a certain factor (applied before losses in district heating).
71 reduce_space_heat _exogenously_factor -- Dictionary with planning horizons as keys. A positive factor can mean renovation or demolition of a building. If the factor is negative, it can mean an increase in floor area, increased thermal comfort, population growth. The default factors are determined by the `Eurocalc Homes and buildings decarbonization scenario <http://tool.european-calculator.eu/app/buildings/building-types-area/?levers=1ddd4444421213bdbbbddd44444ffffff11f411111221111211l212221>`_
72 retrofitting
73 #NAME? -- retro_endogen -- {true, false} Add retrofitting as an endogenous system which co-optimise space heat savings.
74 #NAME? -- cost_factor -- float Weight costs for building renovation
75 #NAME? -- interest_rate -- float The interest rate for investment in building components
76 #NAME? -- annualise_cost -- {true, false} Annualise the investment costs of retrofitting
77 #NAME? -- tax_weighting -- {true, false} Weight the costs of retrofitting depending on taxes in countries
78 #NAME? -- construction_index -- {true, false} Weight the costs of retrofitting depending on labour/material costs per country
79 tes -- {true, false} Add option for storing thermal energy in large water pits associated with district heating systems and individual thermal energy storage (TES)
80 tes_tau The time constant used to calculate the decay of thermal energy in thermal energy storage (TES): 1- :math:`e^{-1/24τ}`.
81 #NAME? -- decentral days float The time constant in decentralized thermal energy storage (TES)
82 #NAME? -- central days float The time constant in centralized thermal energy storage (TES)
83 boilers -- {true, false} Add option for transforming gas into heat using gas boilers
84 resistive_heaters -- {true, false} Add option for transforming electricity into heat using resistive heaters (independently from gas boilers)
85 oil_boilers -- {true, false} Add option for transforming oil into heat using boilers
102 regional_methanol_demand -- {true, false} Spatially resolve methanol demand. Set to true if regional CO2 constraints needed.
103 regional_oil_demand -- {true, false} Spatially resolve oil demand. Set to true if regional CO2 constraints needed.
104 regional_co2 _sequestration_potential
105 #NAME? -- enable -- {true, false} Add option for regionally-resolved geological carbon dioxide sequestration potentials based on `CO2StoP <https://setis.ec.europa.eu/european-co2-storage-database_en>`_.
106 #NAME? -- attribute -- string or list Name (or list of names) of the attribute(s) for the sequestration potential
107 #NAME? -- include_onshore -- {true, false} Add options for including onshore sequestration potentials
108 #NAME? -- min_size Gt float Any sites with lower potential than this value will be excluded
109 #NAME? -- max_size Gt float The maximum sequestration potential for any one site.
110 #NAME? -- years_of_storage years float The years until potential exhausted at optimised annual rate
111 co2_sequestration_potential MtCO2/a float The potential of sequestering CO2 in Europe per year
112 co2_sequestration_cost currency/tCO2 float The cost of sequestering a ton of CO2
113 co2_sequestration_lifetime years int The lifetime of a CO2 sequestration site
134 electricity_grid _connection -- {true, false} Add the cost of electricity grid connection for onshore wind and solar
135 transmission_efficiency Section to specify transmission losses or compression energy demands of bidirectional links. Splits them into two capacity-linked unidirectional links.
136 -- {carrier} -- str The carrier of the link.
137 #NAME? -- -- efficiency_static p.u. float Length-independent transmission efficiency.
138 #NAME? -- -- efficiency_per_1000km p.u. per 1000 km float Length-dependent transmission efficiency ($\eta^{\text{length}}$)
139 #NAME? -- -- compression_per_1000km p.u. per 1000 km float Length-dependent electricity demand for compression ($\eta \cdot \text{length}$) implemented as multi-link to local electricity bus.
140 H2_network -- {true, false} Add option for new hydrogen pipelines
141 gas_network -- {true, false} Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm <https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation>`_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well.
142 H2_retrofit -- {true, false} Add option for retrofiting existing pipelines to transport hydrogen.
151 conventional_generation Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel.
152 biomass_to_liquid -- {true, false} Add option for transforming solid biomass into liquid fuel with the same properties as oil
153 biosng -- {true, false} Add option for transforming solid biomass into synthesis gas with the same properties as natural gas
154 municipal_solid_waste -- {true, false} Add option for municipal solid waste
155 limit_max_growth
156 #NAME? -- enable -- {true, false} Add option to limit the maximum growth of a carrier
157 #NAME? -- factor p.u. float The maximum growth factor of a carrier (e.g. 1.3 allows 30% larger than max historic growth)
158 #NAME? -- max_growth
159 -- -- {carrier} GW float The historic maximum growth of a carrier
160 #NAME? -- max_relative_growth
161 -- -- {carrier} p.u. float The historic maximum relative growth of a carrier
162
163 enhanced_geothermal
164 #NAME? -- enable -- {true, false} Add option to include Enhanced Geothermal Systems
165 #NAME? -- flexible -- {true, false} Add option for flexible operation (see Ricks et al. 2024)
166 #NAME? -- max_hours -- int The maximum hours the reservoir can be charged under flexible operation
167 #NAME? -- max_boost -- float The maximum boost in power output under flexible operation
168 #NAME? -- var_cf -- {true, false} Add option for variable capacity factor (see Ricks et al. 2024)
169 #NAME? -- sustainability_factor -- float Share of sourced heat that is replenished by the earth's core (see details in `build_egs_potentials.py <https://github.com/PyPSA/pypsa-eur-sec/blob/master/scripts/build_egs_potentials.py>`_)
170 solid_biomass_import
171 -- enable -- {true, false} Add option to include solid biomass imports
172 -- price currency/MWh float Price for importing solid biomass
173 -- max_amount Twh float Maximum solid biomass import potential
174 -- upstream_emissions_factor p.u. float Upstream emissions of solid biomass imports

View File

@ -242,7 +242,7 @@ Rule overview
file file
<https://pypsa-eur.readthedocs.io/en/latest/preparation/build_powerplants.html?highlight=powerplants>`__ <https://pypsa-eur.readthedocs.io/en/latest/preparation/build_powerplants.html?highlight=powerplants>`__
generated by pypsa-eur which, in turn, is based on the `powerplantmatching generated by pypsa-eur which, in turn, is based on the `powerplantmatching
<https://github.com/FRESNA/powerplantmatching>`__ database. <https://github.com/PyPSA/powerplantmatching>`__ database.
Existing wind and solar capacities are retrieved from `IRENA annual statistics Existing wind and solar capacities are retrieved from `IRENA annual statistics
<https://www.irena.org/Statistics/Download-Data>`__ and distributed among the <https://www.irena.org/Statistics/Download-Data>`__ and distributed among the

View File

@ -25,7 +25,7 @@ With these and the externally extracted ENTSO-E online map topology
Then the process continues by calculating conventional power plant capacities, potentials, and per-unit availability time series for variable renewable energy carriers and hydro power plants with the following rules: Then the process continues by calculating conventional power plant capacities, potentials, and per-unit availability time series for variable renewable energy carriers and hydro power plants with the following rules:
- :mod:`build_powerplants` for today's thermal power plant capacities using `powerplantmatching <https://github.com/FRESNA/powerplantmatching>`__ allocating these to the closest substation for each powerplant, - :mod:`build_powerplants` for today's thermal power plant capacities using `powerplantmatching <https://github.com/PyPSA/powerplantmatching>`__ allocating these to the closest substation for each powerplant,
- :mod:`build_ship_raster` for building shipping traffic density, - :mod:`build_ship_raster` for building shipping traffic density,
- :mod:`build_renewable_profiles` for the hourly capacity factors and installation potentials constrained by land-use in each substation's Voronoi cell for PV, onshore and offshore wind, and - :mod:`build_renewable_profiles` for the hourly capacity factors and installation potentials constrained by land-use in each substation's Voronoi cell for PV, onshore and offshore wind, and
- :mod:`build_hydro_profile` for the hourly per-unit hydro power availability time series. - :mod:`build_hydro_profile` for the hourly per-unit hydro power availability time series.

View File

@ -10,7 +10,17 @@ Release Notes
Upcoming Release Upcoming Release
================ ================
* Changed heat pump COP approximation for central heating to be based on Jensen et al. 2018 and a default forward temperature of 90C. This is more realistic for district heating than the previously used approximation method. * Changed heat pump COP approximation for central heating to be based on `Jensen et al. (2018) <https://backend.orbit.dtu.dk/ws/portalfiles/portal/151965635/MAIN_Final.pdf>`__ and a default forward temperature of 90C. This is more realistic for district heating than the previously used approximation method.
* split solid biomass potentials into solid biomass and municipal solid waste. Add option to use municipal solid waste. This option is only activated in combination with the flag ``waste_to_energy``
* Add option to import solid biomass
* Add option to produce electrobiofuels (flag ``electrobiofuels``) from solid biomass and hydrogen, as a combination of BtL and Fischer-Tropsch to make more use of the biogenic carbon
* Add flag ``sector: fossil_fuels`` in config to remove the option of importing fossil fuels
* Renamed the carrier of batteries in BEVs from `battery storage` to `EV battery` and the corresponding bus carrier from `Li ion` to `EV battery`. This is to avoid confusion with stationary battery storage.
* Changed default assumptions about waste heat usage from PtX and fuel cells in district heating. * Changed default assumptions about waste heat usage from PtX and fuel cells in district heating.
The default value for the link efficiency scaling factor was changed from 100% to 25%. The default value for the link efficiency scaling factor was changed from 100% to 25%.

View File

@ -215,6 +215,15 @@ rule build_temperature_profiles:
"../scripts/build_temperature_profiles.py" "../scripts/build_temperature_profiles.py"
# def output_cop(wildcards):
# return {
# f"cop_{source}_{sink}": resources(
# "cop_" + source + "_" + sink + "_" + "elec_s{simpl}_{clusters}.nc"
# )
# for sink, source in config["sector"]["heat_pump_sources"].items()
# }
rule build_cop_profiles: rule build_cop_profiles:
params: params:
heat_pump_sink_T_decentral_heating=config_provider( heat_pump_sink_T_decentral_heating=config_provider(
@ -232,22 +241,12 @@ rule build_cop_profiles:
heat_pump_cop_approximation_central_heating=config_provider( heat_pump_cop_approximation_central_heating=config_provider(
"sector", "district_heating", "heat_pump_cop_approximation" "sector", "district_heating", "heat_pump_cop_approximation"
), ),
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
input: input:
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"), temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"), temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"),
output: output:
cop_air_decentral_heating=resources( cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
"cop_air_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_decentral_heating=resources(
"cop_soil_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_central_heating=resources(
"cop_air_central_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_central_heating=resources(
"cop_soil_central_heating_elec_s{simpl}_{clusters}.nc"
),
resources: resources:
mem_mb=20000, mem_mb=20000,
log: log:
@ -965,6 +964,8 @@ rule prepare_sector_network:
adjustments=config_provider("adjustments", "sector"), adjustments=config_provider("adjustments", "sector"),
emissions_scope=config_provider("energy", "emissions"), emissions_scope=config_provider("energy", "emissions"),
RDIR=RDIR, RDIR=RDIR,
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
heat_systems=config_provider("sector", "heat_systems"),
input: input:
unpack(input_profile_offwind), unpack(input_profile_offwind),
**rules.cluster_gas_network.output, **rules.cluster_gas_network.output,
@ -1041,18 +1042,7 @@ rule prepare_sector_network:
), ),
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"), temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"), temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"),
cop_soil_decentral_heating=resources( cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
"cop_soil_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_decentral_heating=resources(
"cop_air_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_central_heating=resources(
"cop_air_central_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_central_heating=resources(
"cop_soil_central_heating_elec_s{simpl}_{clusters}.nc"
),
solar_thermal_total=lambda w: ( solar_thermal_total=lambda w: (
resources("solar_thermal_total_elec_s{simpl}_{clusters}.nc") resources("solar_thermal_total_elec_s{simpl}_{clusters}.nc")
if config_provider("sector", "solar_thermal")(w) if config_provider("sector", "solar_thermal")(w)

View File

@ -9,6 +9,7 @@ rule add_existing_baseyear:
sector=config_provider("sector"), sector=config_provider("sector"),
existing_capacities=config_provider("existing_capacities"), existing_capacities=config_provider("existing_capacities"),
costs=config_provider("costs"), costs=config_provider("costs"),
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
input: input:
network=RESULTS network=RESULTS
+ "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", + "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
@ -21,18 +22,7 @@ rule add_existing_baseyear:
config_provider("scenario", "planning_horizons", 0)(w) config_provider("scenario", "planning_horizons", 0)(w)
) )
), ),
cop_soil_decentral_heating=resources( cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
"cop_soil_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_decentral_heating=resources(
"cop_air_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_central_heating=resources(
"cop_air_central_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_central_heating=resources(
"cop_soil_central_heating_elec_s{simpl}_{clusters}.nc"
),
existing_heating_distribution=resources( existing_heating_distribution=resources(
"existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv" "existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
), ),
@ -79,6 +69,7 @@ rule add_brownfield:
snapshots=config_provider("snapshots"), snapshots=config_provider("snapshots"),
drop_leap_day=config_provider("enable", "drop_leap_day"), drop_leap_day=config_provider("enable", "drop_leap_day"),
carriers=config_provider("electricity", "renewable_carriers"), carriers=config_provider("electricity", "renewable_carriers"),
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
input: input:
unpack(input_profile_tech_brownfield), unpack(input_profile_tech_brownfield),
simplify_busmap=resources("busmap_elec_s{simpl}.csv"), simplify_busmap=resources("busmap_elec_s{simpl}.csv"),
@ -87,18 +78,7 @@ rule add_brownfield:
+ "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", + "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
network_p=solved_previous_horizon, #solved network at previous time step network_p=solved_previous_horizon, #solved network at previous time step
costs=resources("costs_{planning_horizons}.csv"), costs=resources("costs_{planning_horizons}.csv"),
cop_soil_decentral_heating=resources( cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
"cop_soil_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_decentral_heating=resources(
"cop_air_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_central_heating=resources(
"cop_air_central_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_central_heating=resources(
"cop_soil_central_heating_elec_s{simpl}_{clusters}.nc"
),
output: output:
RESULTS RESULTS
+ "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", + "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",

View File

@ -7,6 +7,7 @@ rule add_existing_baseyear:
sector=config_provider("sector"), sector=config_provider("sector"),
existing_capacities=config_provider("existing_capacities"), existing_capacities=config_provider("existing_capacities"),
costs=config_provider("costs"), costs=config_provider("costs"),
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
input: input:
network=RESULTS network=RESULTS
+ "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc", + "prenetworks/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",
@ -19,18 +20,7 @@ rule add_existing_baseyear:
config_provider("scenario", "planning_horizons", 0)(w) config_provider("scenario", "planning_horizons", 0)(w)
) )
), ),
cop_soil_decentral_heating=resources( cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),
"cop_soil_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_decentral_heating=resources(
"cop_air_decentral_heating_elec_s{simpl}_{clusters}.nc"
),
cop_air_central_heating=resources(
"cop_air_central_heating_elec_s{simpl}_{clusters}.nc"
),
cop_soil_central_heating=resources(
"cop_soil_central_heating_elec_s{simpl}_{clusters}.nc"
),
existing_heating_distribution=resources( existing_heating_distribution=resources(
"existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv" "existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
), ),

View File

@ -89,10 +89,6 @@ def add_brownfield(n, n_p, year):
# deal with gas network # deal with gas network
pipe_carrier = ["gas pipeline"] pipe_carrier = ["gas pipeline"]
if snakemake.params.H2_retrofit: if snakemake.params.H2_retrofit:
# drop capacities of previous year to avoid duplicating
to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year != year)
n.mremove("Link", n.links.loc[to_drop].index)
# subtract the already retrofitted from today's gas grid capacity # subtract the already retrofitted from today's gas grid capacity
h2_retrofitted_fixed_i = n.links[ h2_retrofitted_fixed_i = n.links[
(n.links.carrier == "H2 pipeline retrofitted") (n.links.carrier == "H2 pipeline retrofitted")
@ -115,10 +111,6 @@ def add_brownfield(n, n_p, year):
index=pipe_capacity.index index=pipe_capacity.index
).fillna(0) ).fillna(0)
n.links.loc[gas_pipes_i, "p_nom"] = remaining_capacity n.links.loc[gas_pipes_i, "p_nom"] = remaining_capacity
else:
new_pipes = n.links.carrier.isin(pipe_carrier) & (n.links.build_year == year)
n.links.loc[new_pipes, "p_nom"] = 0.0
n.links.loc[new_pipes, "p_nom_min"] = 0.0
def disable_grid_expansion_if_limit_hit(n): def disable_grid_expansion_if_limit_hit(n):

View File

@ -852,7 +852,7 @@ if __name__ == "__main__":
fuel_price = pd.read_csv( fuel_price = pd.read_csv(
snakemake.input.fuel_price, index_col=0, header=0, parse_dates=True snakemake.input.fuel_price, index_col=0, header=0, parse_dates=True
) )
fuel_price = fuel_price.reindex(n.snapshots).fillna(method="ffill") fuel_price = fuel_price.reindex(n.snapshots).ffill()
else: else:
fuel_price = None fuel_price = None

View File

@ -24,6 +24,10 @@ from _helpers import (
from add_electricity import sanitize_carriers from add_electricity import sanitize_carriers
from prepare_sector_network import cluster_heat_buses, define_spatial, prepare_costs from prepare_sector_network import cluster_heat_buses, define_spatial, prepare_costs
from scripts.definitions.heat_sector import HeatSector
from scripts.definitions.heat_system import HeatSystem
from scripts.definitions.heat_system_type import HeatSystemType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
cc = coco.CountryConverter() cc = coco.CountryConverter()
idx = pd.IndexSlice idx = pd.IndexSlice
@ -416,13 +420,13 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
def add_heating_capacities_installed_before_baseyear( def add_heating_capacities_installed_before_baseyear(
n, n: pypsa.Network,
baseyear, baseyear: int,
grouping_years, grouping_years: list,
cop: dict, cop: dict,
time_dep_hp_cop: bool, time_dep_hp_cop: bool,
costs, costs: pd.DataFrame,
default_lifetime, default_lifetime: int,
existing_heating: pd.DataFrame, existing_heating: pd.DataFrame,
): ):
""" """
@ -435,35 +439,30 @@ def add_heating_capacities_installed_before_baseyear(
currently assumed heating capacities split between residential and currently assumed heating capacities split between residential and
services proportional to heating load in both 50% capacities services proportional to heating load in both 50% capacities
in rural buses 50% in urban buses in rural buses 50% in urban buses
cop: dict cop: xr.DataArray
Dictionary with time-dependent coefficients of performance (COPs) for air and ground heat pumps as values and keys "air decentral", "ground decentral", "air central", "ground central" DataArray with time-dependent coefficients of performance (COPs) heat pumps. Coordinates are heat sources (see config), heat system types (see :file:`scripts/enums/HeatSystemType.py`), nodes and snapshots.
time_dep_hp_cop: bool time_dep_hp_cop: bool
If True, time-dependent (dynamic) COPs are used for heat pumps If True, time-dependent (dynamic) COPs are used for heat pumps
""" """
logger.debug(f"Adding heating capacities installed before {baseyear}") logger.debug(f"Adding heating capacities installed before {baseyear}")
for name in existing_heating.columns.get_level_values(0).unique(): for heat_system in existing_heating.columns.get_level_values(0).unique():
name_type = "central" if name == "urban central" else "decentral" heat_system = HeatSystem(heat_system)
nodes = pd.Index(n.buses.location[n.buses.index.str.contains(f"{name} heat")]) nodes = pd.Index(
n.buses.location[n.buses.index.str.contains(f"{heat_system} heat")]
)
if (name_type != "central") and options["electricity_distribution_grid"]: if (not heat_system == HeatSystem.URBAN_CENTRAL) and options[
"electricity_distribution_grid"
]:
nodes_elec = nodes + " low voltage" nodes_elec = nodes + " low voltage"
else: else:
nodes_elec = nodes nodes_elec = nodes
heat_pump_type = "air" if "urban" in name else "ground" too_large_grouping_years = [
gy for gy in grouping_years if gy >= int(baseyear)
# Add heat pumps ]
costs_name = f"decentral {heat_pump_type}-sourced heat pump"
efficiency = (
cop[f"{heat_pump_type} {name_type}"][nodes]
if time_dep_hp_cop
else costs.at[costs_name, "efficiency"]
)
too_large_grouping_years = [gy for gy in grouping_years if gy >= int(baseyear)]
if too_large_grouping_years: if too_large_grouping_years:
logger.warning( logger.warning(
f"Grouping years >= baseyear are ignored. Dropping {too_large_grouping_years}." f"Grouping years >= baseyear are ignored. Dropping {too_large_grouping_years}."
@ -487,18 +486,37 @@ def add_heating_capacities_installed_before_baseyear(
ratios = _years / _years.sum() ratios = _years / _years.sum()
for ratio, grouping_year in zip(ratios, valid_grouping_years): for ratio, grouping_year in zip(ratios, valid_grouping_years):
# Add heat pumps
for heat_source in snakemake.params.heat_pump_sources[
heat_system.system_type.value
]:
costs_name = heat_system.heat_pump_costs_name(heat_source)
efficiency = (
cop.sel(
heat_system=heat_system.system_type.value,
heat_source=heat_source,
name=nodes,
)
.to_pandas()
.reindex(index=n.snapshots)
if time_dep_hp_cop
else costs.at[costs_name, "efficiency"]
)
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=f" {name} {heat_pump_type} heat pump-{grouping_year}", suffix=f" {heat_system} {heat_source} heat pump-{grouping_year}",
bus0=nodes_elec, bus0=nodes_elec,
bus1=nodes + " " + name + " heat", bus1=nodes + " " + heat_system.value + " heat",
carrier=f"{name} {heat_pump_type} heat pump", carrier=f"{heat_system} {heat_source} heat pump",
efficiency=efficiency, efficiency=efficiency,
capital_cost=costs.at[costs_name, "efficiency"] capital_cost=costs.at[costs_name, "efficiency"]
* costs.at[costs_name, "fixed"], * costs.at[costs_name, "fixed"],
p_nom=existing_heating.loc[nodes, (name, f"{heat_pump_type} heat pump")] p_nom=existing_heating.loc[
nodes, (heat_system.value, f"{heat_source} heat pump")
]
* ratio * ratio
/ costs.at[costs_name, "efficiency"], / costs.at[costs_name, "efficiency"],
build_year=int(grouping_year), build_year=int(grouping_year),
@ -509,66 +527,70 @@ def add_heating_capacities_installed_before_baseyear(
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=f" {name} resistive heater-{grouping_year}", suffix=f" {heat_system} resistive heater-{grouping_year}",
bus0=nodes_elec, bus0=nodes_elec,
bus1=nodes + " " + name + " heat", bus1=nodes + " " + heat_system.value + " heat",
carrier=name + " resistive heater", carrier=heat_system.value + " resistive heater",
efficiency=costs.at[f"{name_type} resistive heater", "efficiency"], efficiency=costs.at[
heat_system.resistive_heater_costs_name, "efficiency"
],
capital_cost=( capital_cost=(
costs.at[f"{name_type} resistive heater", "efficiency"] costs.at[heat_system.resistive_heater_costs_name, "efficiency"]
* costs.at[f"{name_type} resistive heater", "fixed"] * costs.at[heat_system.resistive_heater_costs_name, "fixed"]
), ),
p_nom=( p_nom=(
existing_heating.loc[nodes, (name, "resistive heater")] existing_heating.loc[nodes, (heat_system.value, "resistive heater")]
* ratio * ratio
/ costs.at[f"{name_type} resistive heater", "efficiency"] / costs.at[heat_system.resistive_heater_costs_name, "efficiency"]
), ),
build_year=int(grouping_year), build_year=int(grouping_year),
lifetime=costs.at[f"{name_type} resistive heater", "lifetime"], lifetime=costs.at[heat_system.resistive_heater_costs_name, "lifetime"],
) )
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=f" {name} gas boiler-{grouping_year}", suffix=f"{heat_system} gas boiler-{grouping_year}",
bus0="EU gas" if "EU gas" in spatial.gas.nodes else nodes + " gas", bus0="EU gas" if "EU gas" in spatial.gas.nodes else nodes + " gas",
bus1=nodes + " " + name + " heat", bus1=nodes + " " + heat_system.value + " heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=name + " gas boiler", carrier=heat_system.value + " gas boiler",
efficiency=costs.at[f"{name_type} gas boiler", "efficiency"], efficiency=costs.at[heat_system.gas_boiler_costs_name, "efficiency"],
efficiency2=costs.at["gas", "CO2 intensity"], efficiency2=costs.at["gas", "CO2 intensity"],
capital_cost=( capital_cost=(
costs.at[f"{name_type} gas boiler", "efficiency"] costs.at[heat_system.gas_boiler_costs_name, "efficiency"]
* costs.at[f"{name_type} gas boiler", "fixed"] * costs.at[heat_system.gas_boiler_costs_name, "fixed"]
), ),
p_nom=( p_nom=(
existing_heating.loc[nodes, (name, "gas boiler")] existing_heating.loc[nodes, (heat_system.value, "gas boiler")]
* ratio * ratio
/ costs.at[f"{name_type} gas boiler", "efficiency"] / costs.at[heat_system.gas_boiler_costs_name, "efficiency"]
), ),
build_year=int(grouping_year), build_year=int(grouping_year),
lifetime=costs.at[f"{name_type} gas boiler", "lifetime"], lifetime=costs.at[heat_system.gas_boiler_costs_name, "lifetime"],
) )
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=f" {name} oil boiler-{grouping_year}", suffix=f" {heat_system} oil boiler-{grouping_year}",
bus0=spatial.oil.nodes, bus0=spatial.oil.nodes,
bus1=nodes + " " + name + " heat", bus1=nodes + " " + heat_system.value + " heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=name + " oil boiler", carrier=heat_system.value + " oil boiler",
efficiency=costs.at["decentral oil boiler", "efficiency"], efficiency=costs.at[heat_system.oil_boiler_costs_name, "efficiency"],
efficiency2=costs.at["oil", "CO2 intensity"], efficiency2=costs.at["oil", "CO2 intensity"],
capital_cost=costs.at["decentral oil boiler", "efficiency"] capital_cost=costs.at[heat_system.oil_boiler_costs_name, "efficiency"]
* costs.at["decentral oil boiler", "fixed"], * costs.at[heat_system.oil_boiler_costs_name, "fixed"],
p_nom=( p_nom=(
existing_heating.loc[nodes, (name, "oil boiler")] existing_heating.loc[nodes, (heat_system.value, "oil boiler")]
* ratio * ratio
/ costs.at["decentral oil boiler", "efficiency"] / costs.at[heat_system.oil_boiler_costs_name, "efficiency"]
), ),
build_year=int(grouping_year), build_year=int(grouping_year),
lifetime=costs.at[f"{name_type} gas boiler", "lifetime"], lifetime=costs.at[
f"{heat_system.central_or_decentral} gas boiler", "lifetime"
],
) )
# delete links with p_nom=nan corresponding to extra nodes in country # delete links with p_nom=nan corresponding to extra nodes in country
@ -643,28 +665,7 @@ if __name__ == "__main__":
n=n, n=n,
baseyear=baseyear, baseyear=baseyear,
grouping_years=grouping_years_heat, grouping_years=grouping_years_heat,
cop={ cop=xr.open_dataarray(snakemake.input.cop_profiles),
"air decentral": xr.open_dataarray(
snakemake.input.cop_air_decentral_heating
)
.to_pandas()
.reindex(index=n.snapshots),
"ground decentral": xr.open_dataarray(
snakemake.input.cop_soil_decentral_heating
)
.to_pandas()
.reindex(index=n.snapshots),
"air central": xr.open_dataarray(
snakemake.input.cop_air_central_heating
)
.to_pandas()
.reindex(index=n.snapshots),
"ground central": xr.open_dataarray(
snakemake.input.cop_soil_central_heating
)
.to_pandas()
.reindex(index=n.snapshots),
},
time_dep_hp_cop=options["time_dep_hp_cop"], time_dep_hp_cop=options["time_dep_hp_cop"],
costs=costs, costs=costs,
default_lifetime=snakemake.params.existing_capacities[ default_lifetime=snakemake.params.existing_capacities[

View File

@ -808,7 +808,7 @@ def voronoi(points, outline, crs=4326):
voronoi = gpd.GeoDataFrame(geometry=voronoi) voronoi = gpd.GeoDataFrame(geometry=voronoi)
joined = gpd.sjoin_nearest(pts, voronoi, how="right") joined = gpd.sjoin_nearest(pts, voronoi, how="right")
return joined.dissolve(by="Bus").squeeze() return joined.dissolve(by="Bus").reindex(points.index).squeeze()
def build_bus_shapes(n, country_shapes, offshore_shapes, countries): def build_bus_shapes(n, country_shapes, offshore_shapes, countries):

View File

@ -14,6 +14,24 @@ class BaseCopApproximator(ABC):
""" """
Abstract class for approximating the coefficient of performance (COP) of a Abstract class for approximating the coefficient of performance (COP) of a
heat pump. heat pump.
Attributes:
----------
forward_temperature_celsius : Union[xr.DataArray, np.array]
The forward temperature in Celsius.
source_inlet_temperature_celsius : Union[xr.DataArray, np.array]
The source inlet temperature in Celsius.
Methods:
-------
__init__(self, forward_temperature_celsius, source_inlet_temperature_celsius)
Initialize CopApproximator.
approximate_cop(self)
Approximate heat pump coefficient of performance (COP).
celsius_to_kelvin(t_celsius)
Convert temperature from Celsius to Kelvin.
logarithmic_mean(t_hot, t_cold)
Calculate the logarithmic mean temperature difference.
""" """
def __init__( def __init__(
@ -28,8 +46,8 @@ class BaseCopApproximator(ABC):
---------- ----------
forward_temperature_celsius : Union[xr.DataArray, np.array] forward_temperature_celsius : Union[xr.DataArray, np.array]
The forward temperature in Celsius. The forward temperature in Celsius.
return_temperature_celsius : Union[xr.DataArray, np.array] source_inlet_temperature_celsius : Union[xr.DataArray, np.array]
The return temperature in Celsius. The source inlet temperature in Celsius.
""" """
pass pass
@ -45,27 +63,23 @@ class BaseCopApproximator(ABC):
""" """
pass pass
def celsius_to_kelvin(
t_celsius: Union[float, xr.DataArray, np.array]
) -> Union[float, xr.DataArray, np.array]:
if (np.asarray(t_celsius) > 200).any():
raise ValueError(
"t_celsius > 200. Are you sure you are using the right units?"
)
return t_celsius + 273.15
def logarithmic_mean(
t_hot: Union[float, xr.DataArray, np.ndarray],
t_cold: Union[float, xr.DataArray, np.ndarray],
) -> Union[float, xr.DataArray, np.ndarray]:
if (np.asarray(t_hot <= t_cold)).any():
raise ValueError("t_hot must be greater than t_cold")
return (t_hot - t_cold) / np.log(t_hot / t_cold)
@staticmethod @staticmethod
def celsius_to_kelvin( def celsius_to_kelvin(
t_celsius: Union[float, xr.DataArray, np.array] t_celsius: Union[float, xr.DataArray, np.array]
) -> Union[float, xr.DataArray, np.array]: ) -> Union[float, xr.DataArray, np.array]:
"""
Convert temperature from Celsius to Kelvin.
Parameters:
----------
t_celsius : Union[float, xr.DataArray, np.array]
Temperature in Celsius.
Returns:
-------
Union[float, xr.DataArray, np.array]
Temperature in Kelvin.
"""
if (np.asarray(t_celsius) > 200).any(): if (np.asarray(t_celsius) > 200).any():
raise ValueError( raise ValueError(
"t_celsius > 200. Are you sure you are using the right units?" "t_celsius > 200. Are you sure you are using the right units?"
@ -77,6 +91,21 @@ class BaseCopApproximator(ABC):
t_hot: Union[float, xr.DataArray, np.ndarray], t_hot: Union[float, xr.DataArray, np.ndarray],
t_cold: Union[float, xr.DataArray, np.ndarray], t_cold: Union[float, xr.DataArray, np.ndarray],
) -> Union[float, xr.DataArray, np.ndarray]: ) -> Union[float, xr.DataArray, np.ndarray]:
"""
Calculate the logarithmic mean temperature difference.
Parameters:
----------
t_hot : Union[float, xr.DataArray, np.ndarray]
Hot temperature.
t_cold : Union[float, xr.DataArray, np.ndarray]
Cold temperature.
Returns:
-------
Union[float, xr.DataArray, np.ndarray]
Logarithmic mean temperature difference.
"""
if (np.asarray(t_hot <= t_cold)).any(): if (np.asarray(t_hot <= t_cold)).any():
raise ValueError("t_hot must be greater than t_cold") raise ValueError("t_hot must be greater than t_cold")
return (t_hot - t_cold) / np.log(t_hot / t_cold) return (t_hot - t_cold) / np.log(t_hot / t_cold)

View File

@ -20,6 +20,76 @@ class CentralHeatingCopApproximator(BaseCopApproximator):
default parameters from Pieper et al. (2020). The method is based on default parameters from Pieper et al. (2020). The method is based on
a thermodynamic heat pump model with some hard-to-know parameters a thermodynamic heat pump model with some hard-to-know parameters
being approximated. being approximated.
Attributes:
----------
forward_temperature_celsius : Union[xr.DataArray, np.array]
The forward temperature in Celsius.
return_temperature_celsius : Union[xr.DataArray, np.array]
The return temperature in Celsius.
source_inlet_temperature_celsius : Union[xr.DataArray, np.array]
The source inlet temperature in Celsius.
source_outlet_temperature_celsius : Union[xr.DataArray, np.array]
The source outlet temperature in Celsius.
delta_t_pinch_point : float, optional
The pinch point temperature difference, by default 5.
isentropic_compressor_efficiency : float, optional
The isentropic compressor efficiency, by default 0.8.
heat_loss : float, optional
The heat loss, by default 0.0.
Methods:
-------
__init__(
forward_temperature_celsius: Union[xr.DataArray, np.array],
source_inlet_temperature_celsius: Union[xr.DataArray, np.array],
return_temperature_celsius: Union[xr.DataArray, np.array],
source_outlet_temperature_celsius: Union[xr.DataArray, np.array],
delta_t_pinch_point: float = 5,
isentropic_compressor_efficiency: float = 0.8,
heat_loss: float = 0.0,
) -> None:
Initializes the CentralHeatingCopApproximator object.
approximate_cop(self) -> Union[xr.DataArray, np.array]:
Calculate the coefficient of performance (COP) for the system.
_approximate_delta_t_refrigerant_source(
self, delta_t_source: Union[xr.DataArray, np.array]
) -> Union[xr.DataArray, np.array]:
Approximates the temperature difference between the refrigerant and the source.
_approximate_delta_t_refrigerant_sink(
self,
refrigerant: str = "ammonia",
a: float = {"ammonia": 0.2, "isobutane": -0.0011},
b: float = {"ammonia": 0.2, "isobutane": 0.3},
c: float = {"ammonia": 0.016, "isobutane": 2.4},
) -> Union[xr.DataArray, np.array]:
Approximates the temperature difference between the refrigerant and heat sink.
_ratio_evaporation_compression_work_approximation(
self,
refrigerant: str = "ammonia",
a: float = {"ammonia": 0.0014, "isobutane": 0.0035},
) -> Union[xr.DataArray, np.array]:
Calculate the ratio of evaporation to compression work based on approximation.
_approximate_delta_t_refrigerant_sink(
self,
refrigerant: str = "ammonia",
a: float = {"ammonia": 0.2, "isobutane": -0.0011},
b: float = {"ammonia": 0.2, "isobutane": 0.3},
c: float = {"ammonia": 0.016, "isobutane": 2.4},
) -> Union[xr.DataArray, np.array]:
Approximates the temperature difference between the refrigerant and heat sink.
_ratio_evaporation_compression_work_approximation(
self,
refrigerant: str = "ammonia",
a: float = {"ammonia": 0.0014, "isobutane": 0.0035},
) -> Union[xr.DataArray, np.array]:
Calculate the ratio of evaporation to compression work based on approximation.
""" """
def __init__( def __init__(
@ -33,6 +103,7 @@ class CentralHeatingCopApproximator(BaseCopApproximator):
heat_loss: float = 0.0, heat_loss: float = 0.0,
) -> None: ) -> None:
""" """
Initializes the CentralHeatingCopApproximator object.
Parameters: Parameters:
---------- ----------
@ -74,6 +145,7 @@ class CentralHeatingCopApproximator(BaseCopApproximator):
Calculate the coefficient of performance (COP) for the system. Calculate the coefficient of performance (COP) for the system.
Returns: Returns:
--------
Union[xr.DataArray, np.array]: The calculated COP values. Union[xr.DataArray, np.array]: The calculated COP values.
""" """
return ( return (

View File

@ -18,6 +18,26 @@ class DecentralHeatingCopApproximator(BaseCopApproximator):
Uses a quadratic regression on the temperature difference between the source and sink based on empirical data proposed by Staffell et al. 2012. Uses a quadratic regression on the temperature difference between the source and sink based on empirical data proposed by Staffell et al. 2012.
Attributes
----------
forward_temperature_celsius : Union[xr.DataArray, np.array]
The forward temperature in Celsius.
source_inlet_temperature_celsius : Union[xr.DataArray, np.array]
The source inlet temperature in Celsius.
source_type : str
The source of the heat pump. Must be either 'air' or 'ground'.
Methods
-------
__init__(forward_temperature_celsius, source_inlet_temperature_celsius, source_type)
Initialize the DecentralHeatingCopApproximator object.
approximate_cop()
Compute the COP values using quadratic regression for air-/ground-source heat pumps.
_approximate_cop_air_source()
Evaluate quadratic regression for an air-sourced heat pump.
_approximate_cop_ground_source()
Evaluate quadratic regression for a ground-sourced heat pump.
References References
---------- ----------
[1] Staffell et al., Energy & Environmental Science 11 (2012): A review of domestic heat pumps, https://doi.org/10.1039/C2EE22653G. [1] Staffell et al., Energy & Environmental Science 11 (2012): A review of domestic heat pumps, https://doi.org/10.1039/C2EE22653G.
@ -30,34 +50,37 @@ class DecentralHeatingCopApproximator(BaseCopApproximator):
source_type: str, source_type: str,
): ):
""" """
Initialize the COPProfileBuilder object. Initialize the DecentralHeatingCopApproximator object.
Parameters: Parameters
---------- ----------
forward_temperature_celsius : Union[xr.DataArray, np.array] forward_temperature_celsius : Union[xr.DataArray, np.array]
The forward temperature in Celsius. The forward temperature in Celsius.
return_temperature_celsius : Union[xr.DataArray, np.array] source_inlet_temperature_celsius : Union[xr.DataArray, np.array]
The return temperature in Celsius. The source inlet temperature in Celsius.
source: str source_type : str
The source of the heat pump. Must be either 'air' or 'soil' The source of the heat pump. Must be either 'air' or 'ground'.
""" """
self.delta_t = forward_temperature_celsius - source_inlet_temperature_celsius self.delta_t = forward_temperature_celsius - source_inlet_temperature_celsius
if source_type not in ["air", "soil"]: if source_type not in ["air", "ground"]:
raise ValueError("'source' must be one of ['air', 'soil']") raise ValueError("'source_type' must be one of ['air', 'ground']")
else: else:
self.source_type = source_type self.source_type = source_type
def approximate_cop(self) -> Union[xr.DataArray, np.array]: def approximate_cop(self) -> Union[xr.DataArray, np.array]:
""" """
Compute output of quadratic regression for air-/ground-source heat Compute the COP values using quadratic regression for air-/ground-
pumps. source heat pumps.
Calls the appropriate method depending on `source`. Returns
-------
Union[xr.DataArray, np.array]
The calculated COP values.
""" """
if self.source_type == "air": if self.source_type == "air":
return self._approximate_cop_air_source() return self._approximate_cop_air_source()
elif self.source_type == "soil": elif self.source_type == "ground":
return self._approximate_cop_ground_source() return self._approximate_cop_ground_source()
def _approximate_cop_air_source(self) -> Union[xr.DataArray, np.array]: def _approximate_cop_air_source(self) -> Union[xr.DataArray, np.array]:

View File

@ -3,22 +3,29 @@
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import sys
import numpy as np import numpy as np
import pandas as pd
import xarray as xr import xarray as xr
from _helpers import get_country_from_node_name, set_scenario_config from _helpers import get_country_from_node_name, set_scenario_config
from CentralHeatingCopApproximator import CentralHeatingCopApproximator from CentralHeatingCopApproximator import CentralHeatingCopApproximator
from DecentralHeatingCopApproximator import DecentralHeatingCopApproximator from DecentralHeatingCopApproximator import DecentralHeatingCopApproximator
from scripts.definitions.heat_system_type import HeatSystemType
def map_temperature_dict_to_onshore_regions( def map_temperature_dict_to_onshore_regions(
temperature_dict: dict, onshore_regions: xr.DataArray, snapshots: xr.DataArray supply_temperature_by_country: dict,
onshore_regions: xr.DataArray,
snapshots: xr.DataArray,
) -> xr.DataArray: ) -> xr.DataArray:
""" """
Map dictionary of temperatures to onshore regions. Map dictionary of temperatures to onshore regions.
Parameters: Parameters:
---------- ----------
temperature_dict : dictionary supply_temperature_by_country : dictionary
Dictionary with temperatures as values and country keys as keys. One key must be named "default" Dictionary with temperatures as values and country keys as keys. One key must be named "default"
onshore_regions : xr.DataArray onshore_regions : xr.DataArray
Names of onshore regions Names of onshore regions
@ -32,9 +39,10 @@ def map_temperature_dict_to_onshore_regions(
[ [
[ [
( (
temperature_dict[get_country_from_node_name(node_name)] supply_temperature_by_country[get_country_from_node_name(node_name)]
if get_country_from_node_name(node_name) in temperature_dict.keys() if get_country_from_node_name(node_name)
else temperature_dict["default"] in supply_temperature_by_country.keys()
else supply_temperature_by_country["default"]
) )
for node_name in onshore_regions["name"].values for node_name in onshore_regions["name"].values
] ]
@ -46,6 +54,47 @@ def map_temperature_dict_to_onshore_regions(
) )
def get_cop(
heat_system_type: str,
heat_source: str,
source_inlet_temperature_celsius: xr.DataArray,
forward_temperature_by_node_and_time: xr.DataArray = None,
return_temperature_by_node_and_time: xr.DataArray = None,
) -> xr.DataArray:
"""
Calculate the coefficient of performance (COP) for a heating system.
Parameters
----------
heat_system_type : str
The type of heating system.
heat_source : str
The heat source used in the heating system.
source_inlet_temperature_celsius : xr.DataArray
The inlet temperature of the heat source in Celsius.
Returns
-------
xr.DataArray
The calculated coefficient of performance (COP) for the heating system.
"""
if HeatSystemType(heat_system_type).is_central:
return CentralHeatingCopApproximator(
forward_temperature_celsius=forward_temperature_by_node_and_time,
return_temperature_celsius=return_temperature_by_node_and_time,
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
source_outlet_temperature_celsius=source_inlet_temperature_celsius
- snakemake.params.heat_source_cooling_central_heating,
).approximate_cop()
else:
return DecentralHeatingCopApproximator(
forward_temperature_celsius=snakemake.params.heat_pump_sink_T_decentral_heating,
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
source_type=heat_source,
).approximate_cop()
if __name__ == "__main__": if __name__ == "__main__":
if "snakemake" not in globals(): if "snakemake" not in globals():
from _helpers import mock_snakemake from _helpers import mock_snakemake
@ -58,47 +107,46 @@ if __name__ == "__main__":
set_scenario_config(snakemake) set_scenario_config(snakemake)
for source_type in ["air", "soil"]:
# source inlet temperature (air/soil) is based on weather data
source_inlet_temperature_celsius: xr.DataArray = xr.open_dataarray(
snakemake.input[f"temp_{source_type}_total"]
)
# Approximate COP for decentral (individual) heating
cop_individual_heating: xr.DataArray = DecentralHeatingCopApproximator(
forward_temperature_celsius=snakemake.params.heat_pump_sink_T_decentral_heating,
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
source_type=source_type,
).approximate_cop()
cop_individual_heating.to_netcdf(
snakemake.output[f"cop_{source_type}_decentral_heating"]
)
# map forward and return temperatures specified on country-level to onshore regions # map forward and return temperatures specified on country-level to onshore regions
onshore_regions: xr.DataArray = source_inlet_temperature_celsius["name"] onshore_regions: xr.DataArray = source_inlet_temperature_celsius["name"]
forward_temperature_central_heating: xr.DataArray = ( forward_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions( map_temperature_dict_to_onshore_regions(
temperature_dict=snakemake.params.forward_temperature_central_heating, temperature_dict=snakemake.params.forward_temperature_central_heating,
onshore_regions=onshore_regions, onshore_regions=onshore_regions,
snapshots=source_inlet_temperature_celsius["time"], snapshots=source_inlet_temperature_celsius["time"],
) )
) )
return_temperature_central_heating: xr.DataArray = ( return_temperature_central_heating_by_node_and_time: xr.DataArray = (
map_temperature_dict_to_onshore_regions( map_temperature_dict_to_onshore_regions(
temperature_dict=snakemake.params.return_temperature_central_heating, temperature_dict=snakemake.params.return_temperature_central_heating,
onshore_regions=onshore_regions, onshore_regions=onshore_regions,
snapshots=source_inlet_temperature_celsius["time"], snapshots=source_inlet_temperature_celsius["time"],
) )
) )
cop_all_system_types = []
# Approximate COP for central (district) heating for heat_system_type, heat_sources in snakemake.params.heat_pump_sources.items():
cop_central_heating: xr.DataArray = CentralHeatingCopApproximator( cop_this_system_type = []
forward_temperature_celsius=forward_temperature_central_heating, for heat_source in heat_sources:
return_temperature_celsius=return_temperature_central_heating, source_inlet_temperature_celsius = xr.open_dataarray(
source_inlet_temperature_celsius=source_inlet_temperature_celsius, snakemake.input[f"temp_{heat_source.replace('ground', 'soil')}_total"]
source_outlet_temperature_celsius=source_inlet_temperature_celsius
- snakemake.params.heat_source_cooling_central_heating,
).approximate_cop()
cop_central_heating.to_netcdf(
snakemake.output[f"cop_{source_type}_central_heating"]
) )
cop_da = get_cop(
heat_system_type=heat_system_type,
heat_source=heat_source,
source_inlet_temperature_celsius=source_inlet_temperature_celsius,
forward_temperature_by_node_and_time=forward_temperature_central_heating_by_node_and_time,
return_temperature_by_node_and_time=return_temperature_central_heating_by_node_and_time,
)
cop_this_system_type.append(cop_da)
cop_all_system_types.append(
xr.concat(
cop_this_system_type, dim=pd.Index(heat_sources, name="heat_source")
)
)
cop_dataarray = xr.concat(
cop_all_system_types,
dim=pd.Index(snakemake.params.heat_pump_sources.keys(), name="heat_system"),
)
cop_dataarray.to_netcdf(snakemake.output.cop_profiles)

View File

@ -6,7 +6,7 @@
# coding: utf-8 # coding: utf-8
""" """
Retrieves conventional powerplant capacities and locations from Retrieves conventional powerplant capacities and locations from
`powerplantmatching <https://github.com/FRESNA/powerplantmatching>`_, assigns `powerplantmatching <https://github.com/PyPSA/powerplantmatching>`_, assigns
these to buses and creates a ``.csv`` file. It is possible to amend the these to buses and creates a ``.csv`` file. It is possible to amend the
powerplant database with custom entries provided in powerplant database with custom entries provided in
``data/custom_powerplants.csv``. ``data/custom_powerplants.csv``.
@ -30,17 +30,17 @@ Inputs
------ ------
- ``networks/base.nc``: confer :ref:`base`. - ``networks/base.nc``: confer :ref:`base`.
- ``data/custom_powerplants.csv``: custom powerplants in the same format as `powerplantmatching <https://github.com/FRESNA/powerplantmatching>`_ provides - ``data/custom_powerplants.csv``: custom powerplants in the same format as `powerplantmatching <https://github.com/PyPSA/powerplantmatching>`_ provides
Outputs Outputs
------- -------
- ``resource/powerplants.csv``: A list of conventional power plants (i.e. neither wind nor solar) with fields for name, fuel type, technology, country, capacity in MW, duration, commissioning year, retrofit year, latitude, longitude, and dam information as documented in the `powerplantmatching README <https://github.com/FRESNA/powerplantmatching/blob/master/README.md>`_; additionally it includes information on the closest substation/bus in ``networks/base.nc``. - ``resource/powerplants.csv``: A list of conventional power plants (i.e. neither wind nor solar) with fields for name, fuel type, technology, country, capacity in MW, duration, commissioning year, retrofit year, latitude, longitude, and dam information as documented in the `powerplantmatching README <https://github.com/PyPSA/powerplantmatching/blob/master/README.md>`_; additionally it includes information on the closest substation/bus in ``networks/base.nc``.
.. image:: img/powerplantmatching.png .. image:: img/powerplantmatching.png
:scale: 30 % :scale: 30 %
**Source:** `powerplantmatching on GitHub <https://github.com/FRESNA/powerplantmatching>`_ **Source:** `powerplantmatching on GitHub <https://github.com/PyPSA/powerplantmatching>`_
Description Description
----------- -----------

View File

@ -890,7 +890,7 @@ def calculate_gain_utilisation_factor(heat_transfer_perm2, Q_ht, Q_gain):
Calculates gain utilisation factor nu. Calculates gain utilisation factor nu.
""" """
# time constant of the building tau [h] = c_m [Wh/(m^2K)] * 1 /(H_tr_e+H_tb*H_ve) [m^2 K /W] # time constant of the building tau [h] = c_m [Wh/(m^2K)] * 1 /(H_tr_e+H_tb*H_ve) [m^2 K /W]
tau = c_m / heat_transfer_perm2.T.groupby(axis=1).sum().T tau = c_m / heat_transfer_perm2.groupby().sum()
alpha = alpha_H_0 + (tau / tau_H_0) alpha = alpha_H_0 + (tau / tau_H_0)
# heat balance ratio # heat balance ratio
gamma = (1 / Q_ht).mul(Q_gain.sum(axis=1), axis=0) gamma = (1 / Q_ht).mul(Q_gain.sum(axis=1), axis=0)

View File

@ -58,6 +58,9 @@ def build_clustered_gas_network(df, bus_regions, length_factor=1.25):
# drop pipes within the same region # drop pipes within the same region
df = df.loc[df.bus1 != df.bus0] df = df.loc[df.bus1 != df.bus0]
if df.empty:
return df
# recalculate lengths as center to center * length factor # recalculate lengths as center to center * length factor
df["length"] = df.apply( df["length"] = df.apply(
lambda p: length_factor lambda p: length_factor

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
from enum import Enum
class HeatSector(Enum):
"""
Enumeration class representing different heat sectors.
Attributes:
RESIDENTIAL (str): Represents the residential heat sector.
SERVICES (str): Represents the services heat sector.
"""
RESIDENTIAL = "residential"
SERVICES = "services"
def __str__(self) -> str:
"""
Returns the string representation of the heat sector.
Returns:
str: The string representation of the heat sector.
"""
return self.value

View File

@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
from enum import Enum
from scripts.definitions.heat_sector import HeatSector
from scripts.definitions.heat_system_type import HeatSystemType
class HeatSystem(Enum):
"""
Enumeration representing different heat systems.
Attributes
----------
RESIDENTIAL_RURAL : str
Heat system for residential areas in rural locations.
SERVICES_RURAL : str
Heat system for service areas in rural locations.
RESIDENTIAL_URBAN_DECENTRAL : str
Heat system for residential areas in urban decentralized locations.
SERVICES_URBAN_DECENTRAL : str
Heat system for service areas in urban decentralized locations.
URBAN_CENTRAL : str
Heat system for urban central areas.
Methods
-------
__str__()
Returns the string representation of the heat system.
central_or_decentral()
Returns whether the heat system is central or decentralized.
system_type()
Returns the type of the heat system.
sector()
Returns the sector of the heat system.
rural()
Returns whether the heat system is for rural areas.
urban_decentral()
Returns whether the heat system is for urban decentralized areas.
urban()
Returns whether the heat system is for urban areas.
heat_demand_weighting(urban_fraction=None, dist_fraction=None)
Calculates the heat demand weighting based on urban fraction and distribution fraction.
heat_pump_costs_name(heat_source)
Generates the name for the heat pump costs based on the heat source.
"""
RESIDENTIAL_RURAL = "residential rural"
SERVICES_RURAL = "services rural"
RESIDENTIAL_URBAN_DECENTRAL = "residential urban decentral"
SERVICES_URBAN_DECENTRAL = "services urban decentral"
URBAN_CENTRAL = "urban central"
def __init__(self, *args):
super().__init__(*args)
def __str__(self) -> str:
"""
Returns the string representation of the heat system.
Returns
-------
str
The string representation of the heat system.
"""
return self.value
@property
def central_or_decentral(self) -> str:
"""
Returns whether the heat system is central or decentralized.
Returns
-------
str
"central" if the heat system is central, "decentral" otherwise.
"""
if self == HeatSystem.URBAN_CENTRAL:
return "central"
else:
return "decentral"
@property
def system_type(self) -> HeatSystemType:
"""
Returns the type of the heat system.
Returns
-------
str
The type of the heat system.
Raises
------
RuntimeError
If the heat system is invalid.
"""
if self == HeatSystem.URBAN_CENTRAL:
return HeatSystemType.URBAN_CENTRAL
elif (
self == HeatSystem.RESIDENTIAL_URBAN_DECENTRAL
or self == HeatSystem.SERVICES_URBAN_DECENTRAL
):
return HeatSystemType.URBAN_DECENTRAL
elif self == HeatSystem.RESIDENTIAL_RURAL or self == HeatSystem.SERVICES_RURAL:
return HeatSystemType.RURAL
else:
raise RuntimeError(f"Invalid heat system: {self}")
@property
def sector(self) -> HeatSector:
"""
Returns the sector of the heat system.
Returns
-------
HeatSector
The sector of the heat system.
"""
if (
self == HeatSystem.RESIDENTIAL_RURAL
or self == HeatSystem.RESIDENTIAL_URBAN_DECENTRAL
):
return HeatSector.RESIDENTIAL
elif (
self == HeatSystem.SERVICES_RURAL
or self == HeatSystem.SERVICES_URBAN_DECENTRAL
):
return HeatSector.SERVICES
else:
"tot"
@property
def is_rural(self) -> bool:
"""
Returns whether the heat system is for rural areas.
Returns
-------
bool
True if the heat system is for rural areas, False otherwise.
"""
if self == HeatSystem.RESIDENTIAL_RURAL or self == HeatSystem.SERVICES_RURAL:
return True
else:
return False
@property
def is_urban_decentral(self) -> bool:
"""
Returns whether the heat system is for urban decentralized areas.
Returns
-------
bool
True if the heat system is for urban decentralized areas, False otherwise.
"""
if (
self == HeatSystem.RESIDENTIAL_URBAN_DECENTRAL
or self == HeatSystem.SERVICES_URBAN_DECENTRAL
):
return True
else:
return False
@property
def is_urban(self) -> bool:
"""
Returns whether the heat system is for urban areas.
Returns
-------
bool True if the heat system is for urban areas, False otherwise.
"""
return not self.is_rural
def heat_demand_weighting(self, urban_fraction=None, dist_fraction=None) -> float:
"""
Calculates the heat demand weighting based on urban fraction and
distribution fraction.
Parameters
----------
urban_fraction : float, optional
The fraction of urban heat demand.
dist_fraction : float, optional
The fraction of distributed heat demand.
Returns
-------
float
The heat demand weighting.
Raises
------
RuntimeError
If the heat system is invalid.
"""
if "rural" in self.value:
return 1 - urban_fraction
elif "urban central" in self.value:
return dist_fraction
elif "urban decentral" in self.value:
return urban_fraction - dist_fraction
else:
raise RuntimeError(f"Invalid heat system: {self}")
def heat_pump_costs_name(self, heat_source: str) -> str:
"""
Generates the name for the heat pump costs based on the heat source and
system.
Used to retrieve data from `technology-data <https://github.com/PyPSA/technology-data>`.
Parameters
----------
heat_source : str
The heat source.
Returns
-------
str
The name for the heat pump costs.
"""
return f"{self.central_or_decentral} {heat_source}-sourced heat pump"
@property
def resistive_heater_costs_name(self) -> str:
"""
Generates the name for the resistive heater costs based on the heat
system.
Used to retrieve data from `technology-data <https://github.com/PyPSA/technology-data>`.
Returns
-------
str
The name for the heater costs.
"""
return f"{self.central_or_decentral} resistive heater"
@property
def gas_boiler_costs_name(self) -> str:
"""
Generates the name for the gas boiler costs based on the heat system.
Used to retrieve data from `technology-data <https://github.com/PyPSA/technology-data>`.
Returns
-------
str
The name for the gas boiler costs.
"""
return f"{self.central_or_decentral} gas boiler"
@property
def oil_boiler_costs_name(self) -> str:
"""
Generates the name for the oil boiler costs based on the heat system.
Used to retrieve data from `technology-data <https://github.com/PyPSA/technology-data>`.
Returns
-------
str
The name for the oil boiler costs.
"""
return "decentral oil boiler"

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
from enum import Enum
class HeatSystemType(Enum):
"""
Enumeration representing different types of heat systems.
"""
URBAN_CENTRAL = "urban central"
URBAN_DECENTRAL = "urban decentral"
RURAL = "rural"
def __str__(self) -> str:
"""
Returns the string representation of the heat system type.
Returns:
str: The string representation of the heat system type.
"""
return self.value
@property
def is_central(self) -> bool:
"""
Returns whether the heat system type is central.
Returns:
bool: True if the heat system type is central, False otherwise.
"""
return self == HeatSystemType.URBAN_CENTRAL

View File

@ -631,7 +631,7 @@ def calculate_co2_emissions(n, label, df):
weightings = n.snapshot_weightings.generators.mul( weightings = n.snapshot_weightings.generators.mul(
n.investment_period_weightings["years"] n.investment_period_weightings["years"]
.reindex(n.snapshots) .reindex(n.snapshots)
.fillna(method="bfill") .bfill()
.fillna(1.0), .fillna(1.0),
axis=0, axis=0,
) )

View File

@ -70,7 +70,7 @@ if __name__ == "__main__":
optimized = optimized[["Generator", "StorageUnit"]].droplevel(0, axis=1) optimized = optimized[["Generator", "StorageUnit"]].droplevel(0, axis=1)
optimized = optimized.rename(columns=n.buses.country, level=0) optimized = optimized.rename(columns=n.buses.country, level=0)
optimized = optimized.rename(columns=carrier_groups, level=1) optimized = optimized.rename(columns=carrier_groups, level=1)
optimized = optimized.groupby(axis=1, level=[0, 1]).sum() optimized = optimized.T.groupby(level=[0, 1]).sum().T
data = pd.concat([historic, optimized], keys=["Historic", "Optimized"], axis=1) data = pd.concat([historic, optimized], keys=["Historic", "Optimized"], axis=1)
data.columns.names = ["Kind", "Country", "Carrier"] data.columns.names = ["Kind", "Country", "Carrier"]

View File

@ -137,9 +137,7 @@ def add_emission_prices(n, emission_prices={"co2": 0.0}, exclude_co2=False):
def add_dynamic_emission_prices(n): def add_dynamic_emission_prices(n):
co2_price = pd.read_csv(snakemake.input.co2_price, index_col=0, parse_dates=True) co2_price = pd.read_csv(snakemake.input.co2_price, index_col=0, parse_dates=True)
co2_price = co2_price[~co2_price.index.duplicated()] co2_price = co2_price[~co2_price.index.duplicated()]
co2_price = ( co2_price = co2_price.reindex(n.snapshots).ffill().bfill()
co2_price.reindex(n.snapshots).fillna(method="ffill").fillna(method="bfill")
)
emissions = ( emissions = (
n.generators.carrier.map(n.carriers.co2_emissions) / n.generators.efficiency n.generators.carrier.map(n.carriers.co2_emissions) / n.generators.efficiency

View File

@ -250,7 +250,7 @@ def adjust_stores(n):
n.stores.loc[cyclic_i, "e_cyclic_per_period"] = True n.stores.loc[cyclic_i, "e_cyclic_per_period"] = True
n.stores.loc[cyclic_i, "e_cyclic"] = False n.stores.loc[cyclic_i, "e_cyclic"] = False
# non cyclic store assumptions # non cyclic store assumptions
non_cyclic_store = ["co2", "co2 stored", "solid biomass", "biogas", "Li ion"] non_cyclic_store = ["co2", "co2 stored", "solid biomass", "biogas", "EV battery"]
co2_i = n.stores[n.stores.carrier.isin(non_cyclic_store)].index co2_i = n.stores[n.stores.carrier.isin(non_cyclic_store)].index
n.stores.loc[co2_i, "e_cyclic_per_period"] = False n.stores.loc[co2_i, "e_cyclic_per_period"] = False
n.stores.loc[co2_i, "e_cyclic"] = False n.stores.loc[co2_i, "e_cyclic"] = False

View File

@ -37,6 +37,10 @@ from pypsa.geo import haversine_pts
from pypsa.io import import_components_from_dataframe from pypsa.io import import_components_from_dataframe
from scipy.stats import beta from scipy.stats import beta
from scripts.definitions.heat_sector import HeatSector
from scripts.definitions.heat_system import HeatSystem
from scripts.definitions.heat_system_type import HeatSystemType
spatial = SimpleNamespace() spatial = SimpleNamespace()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -56,19 +60,25 @@ def define_spatial(nodes, options):
# biomass # biomass
spatial.biomass = SimpleNamespace() spatial.biomass = SimpleNamespace()
spatial.msw = SimpleNamespace()
if options.get("biomass_spatial", options["biomass_transport"]): if options.get("biomass_spatial", options["biomass_transport"]):
spatial.biomass.nodes = nodes + " solid biomass" spatial.biomass.nodes = nodes + " solid biomass"
spatial.biomass.locations = nodes spatial.biomass.locations = nodes
spatial.biomass.industry = nodes + " solid biomass for industry" spatial.biomass.industry = nodes + " solid biomass for industry"
spatial.biomass.industry_cc = nodes + " solid biomass for industry CC" spatial.biomass.industry_cc = nodes + " solid biomass for industry CC"
spatial.msw.nodes = nodes + " municipal solid waste"
spatial.msw.locations = nodes
else: else:
spatial.biomass.nodes = ["EU solid biomass"] spatial.biomass.nodes = ["EU solid biomass"]
spatial.biomass.locations = ["EU"] spatial.biomass.locations = ["EU"]
spatial.biomass.industry = ["solid biomass for industry"] spatial.biomass.industry = ["solid biomass for industry"]
spatial.biomass.industry_cc = ["solid biomass for industry CC"] spatial.biomass.industry_cc = ["solid biomass for industry CC"]
spatial.msw.nodes = ["EU municipal solid waste"]
spatial.msw.locations = ["EU"]
spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes) spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes)
spatial.msw.df = pd.DataFrame(vars(spatial.msw), index=nodes)
# co2 # co2
@ -542,6 +552,9 @@ def add_carrier_buses(n, carrier, nodes=None):
capital_cost=capital_cost, capital_cost=capital_cost,
) )
fossils = ["coal", "gas", "oil", "lignite"]
if options.get("fossil_fuels", True) and carrier in fossils:
n.madd( n.madd(
"Generator", "Generator",
nodes, nodes,
@ -1238,12 +1251,14 @@ def add_storage_and_grids(n, costs):
gas_pipes["p_nom_min"] = 0.0 gas_pipes["p_nom_min"] = 0.0
# 0.1 EUR/MWkm/a to prefer decommissioning to address degeneracy # 0.1 EUR/MWkm/a to prefer decommissioning to address degeneracy
gas_pipes["capital_cost"] = 0.1 * gas_pipes.length gas_pipes["capital_cost"] = 0.1 * gas_pipes.length
gas_pipes["p_nom_extendable"] = True
else: else:
gas_pipes["p_nom_max"] = np.inf gas_pipes["p_nom_max"] = np.inf
gas_pipes["p_nom_min"] = gas_pipes.p_nom gas_pipes["p_nom_min"] = gas_pipes.p_nom
gas_pipes["capital_cost"] = ( gas_pipes["capital_cost"] = (
gas_pipes.length * costs.at["CH4 (g) pipeline", "fixed"] gas_pipes.length * costs.at["CH4 (g) pipeline", "fixed"]
) )
gas_pipes["p_nom_extendable"] = False
n.madd( n.madd(
"Link", "Link",
@ -1252,14 +1267,14 @@ def add_storage_and_grids(n, costs):
bus1=gas_pipes.bus1 + " gas", bus1=gas_pipes.bus1 + " gas",
p_min_pu=gas_pipes.p_min_pu, p_min_pu=gas_pipes.p_min_pu,
p_nom=gas_pipes.p_nom, p_nom=gas_pipes.p_nom,
p_nom_extendable=True, p_nom_extendable=gas_pipes.p_nom_extendable,
p_nom_max=gas_pipes.p_nom_max, p_nom_max=gas_pipes.p_nom_max,
p_nom_min=gas_pipes.p_nom_min, p_nom_min=gas_pipes.p_nom_min,
length=gas_pipes.length, length=gas_pipes.length,
capital_cost=gas_pipes.capital_cost, capital_cost=gas_pipes.capital_cost,
tags=gas_pipes.name, tags=gas_pipes.name,
carrier="gas pipeline", carrier="gas pipeline",
lifetime=costs.at["CH4 (g) pipeline", "lifetime"], lifetime=np.inf,
) )
# remove fossil generators where there is neither # remove fossil generators where there is neither
@ -1541,14 +1556,14 @@ def add_EVs(
temperature, temperature,
): ):
n.add("Carrier", "Li ion") n.add("Carrier", "EV battery")
n.madd( n.madd(
"Bus", "Bus",
spatial.nodes, spatial.nodes,
suffix=" EV battery", suffix=" EV battery",
location=spatial.nodes, location=spatial.nodes,
carrier="Li ion", carrier="EV battery",
unit="MWh_el", unit="MWh_el",
) )
@ -1621,9 +1636,9 @@ def add_EVs(
n.madd( n.madd(
"Store", "Store",
spatial.nodes, spatial.nodes,
suffix=" battery storage", suffix=" EV battery",
bus=spatial.nodes + " EV battery", bus=spatial.nodes + " EV battery",
carrier="battery storage", carrier="EV battery",
e_cyclic=True, e_cyclic=True,
e_nom=e_nom, e_nom=e_nom,
e_max_pu=1, e_max_pu=1,
@ -1765,7 +1780,7 @@ def build_heat_demand(n):
.unstack(level=1) .unstack(level=1)
) )
sectors = ["residential", "services"] sectors = [sector.value for sector in HeatSector]
uses = ["water", "space"] uses = ["water", "space"]
heat_demand = {} heat_demand = {}
@ -1793,10 +1808,21 @@ def build_heat_demand(n):
return heat_demand return heat_demand
def add_heat(n, costs): def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray):
"""
Add heat sector to the network.
Parameters:
n (pypsa.Network): The PyPSA network object.
costs (pd.DataFrame): DataFrame containing cost information.
cop (xr.DataArray): DataArray containing coefficient of performance (COP) values.
Returns:
None
"""
logger.info("Add heat sector") logger.info("Add heat sector")
sectors = ["residential", "services"] sectors = [sector.value for sector in HeatSector]
heat_demand = build_heat_demand(n) heat_demand = build_heat_demand(n)
@ -1815,31 +1841,6 @@ def add_heat(n, costs):
for sector in sectors: for sector in sectors:
heat_demand[sector + " space"] = (1 - dE) * heat_demand[sector + " space"] heat_demand[sector + " space"] = (1 - dE) * heat_demand[sector + " space"]
heat_systems = [
"residential rural",
"services rural",
"residential urban decentral",
"services urban decentral",
"urban central",
]
cop = {
"air decentral": xr.open_dataarray(snakemake.input.cop_air_decentral_heating)
.to_pandas()
.reindex(index=n.snapshots),
"ground decentral": xr.open_dataarray(
snakemake.input.cop_soil_decentral_heating
)
.to_pandas()
.reindex(index=n.snapshots),
"air central": xr.open_dataarray(snakemake.input.cop_air_central_heating)
.to_pandas()
.reindex(index=n.snapshots),
"ground central": xr.open_dataarray(snakemake.input.cop_soil_central_heating)
.to_pandas()
.reindex(index=n.snapshots),
}
if options["solar_thermal"]: if options["solar_thermal"]:
solar_thermal = ( solar_thermal = (
xr.open_dataarray(snakemake.input.solar_thermal_total) xr.open_dataarray(snakemake.input.solar_thermal_total)
@ -1849,31 +1850,34 @@ def add_heat(n, costs):
# 1e3 converts from W/m^2 to MW/(1000m^2) = kW/m^2 # 1e3 converts from W/m^2 to MW/(1000m^2) = kW/m^2
solar_thermal = options["solar_cf_correction"] * solar_thermal / 1e3 solar_thermal = options["solar_cf_correction"] * solar_thermal / 1e3
for name in heat_systems: for (
name_type = "central" if name == "urban central" else "decentral" heat_system
) in (
HeatSystem
): # this loops through all heat systems defined in _entities.HeatSystem
if name == "urban central": if heat_system == HeatSystem.URBAN_CENTRAL:
nodes = dist_fraction.index[dist_fraction > 0] nodes = dist_fraction.index[dist_fraction > 0]
else: else:
nodes = pop_layout.index nodes = pop_layout.index
n.add("Carrier", name + " heat") n.add("Carrier", f"{heat_system} heat")
n.madd( n.madd(
"Bus", "Bus",
nodes + f" {name} heat", nodes + f" {heat_system.value} heat",
location=nodes, location=nodes,
carrier=name + " heat", carrier=f"{heat_system.value} heat",
unit="MWh_th", unit="MWh_th",
) )
if name == "urban central" and options.get("central_heat_vent"): if heat_system == HeatSystem.URBAN_CENTRAL and options.get("central_heat_vent"):
n.madd( n.madd(
"Generator", "Generator",
nodes + f" {name} heat vent", nodes + f" {heat_system} heat vent",
bus=nodes + f" {name} heat", bus=nodes + f" {heat_system} heat",
location=nodes, location=nodes,
carrier=name + " heat vent", carrier=f"{heat_system} heat vent",
p_nom_extendable=True, p_nom_extendable=True,
p_max_pu=0, p_max_pu=0,
p_min_pu=-1, p_min_pu=-1,
@ -1881,30 +1885,24 @@ def add_heat(n, costs):
) )
## Add heat load ## Add heat load
factor = heat_system.heat_demand_weighting(
for sector in sectors: urban_fraction=urban_fraction[nodes], dist_fraction=dist_fraction[nodes]
# heat demand weighting
if "rural" in name:
factor = 1 - urban_fraction[nodes]
elif "urban central" in name:
factor = dist_fraction[nodes]
elif "urban decentral" in name:
factor = urban_fraction[nodes] - dist_fraction[nodes]
else:
raise NotImplementedError(
f" {name} not in " f"heat systems: {heat_systems}"
) )
if not heat_system == HeatSystem.URBAN_CENTRAL:
if sector in name:
heat_load = ( heat_load = (
heat_demand[[sector + " water", sector + " space"]] heat_demand[
[
heat_system.sector.value + " water",
heat_system.sector.value + " space",
]
]
.T.groupby(level=1) .T.groupby(level=1)
.sum() .sum()
.T[nodes] .T[nodes]
.multiply(factor) .multiply(factor)
) )
if name == "urban central": if heat_system == HeatSystem.URBAN_CENTRAL:
heat_load = ( heat_load = (
heat_demand.T.groupby(level=1) heat_demand.T.groupby(level=1)
.sum() .sum()
@ -1917,20 +1915,25 @@ def add_heat(n, costs):
n.madd( n.madd(
"Load", "Load",
nodes, nodes,
suffix=f" {name} heat", suffix=f" {heat_system} heat",
bus=nodes + f" {name} heat", bus=nodes + f" {heat_system} heat",
carrier=name + " heat", carrier=f"{heat_system} heat",
p_set=heat_load, p_set=heat_load,
) )
## Add heat pumps ## Add heat pumps
for heat_source in snakemake.params.heat_pump_sources[
heat_pump_types = ["air"] if "urban" in name else ["ground", "air"] heat_system.system_type.value
]:
for heat_pump_type in heat_pump_types: costs_name = heat_system.heat_pump_costs_name(heat_source)
costs_name = f"{name_type} {heat_pump_type}-sourced heat pump"
efficiency = ( efficiency = (
cop[f"{heat_pump_type} {name_type}"][nodes] cop.sel(
heat_system=heat_system.system_type.value,
heat_source=heat_source,
name=nodes,
)
.to_pandas()
.reindex(index=n.snapshots)
if options["time_dep_hp_cop"] if options["time_dep_hp_cop"]
else costs.at[costs_name, "efficiency"] else costs.at[costs_name, "efficiency"]
) )
@ -1938,10 +1941,10 @@ def add_heat(n, costs):
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=f" {name} {heat_pump_type} heat pump", suffix=f" {heat_system} {heat_source} heat pump",
bus0=nodes, bus0=nodes,
bus1=nodes + f" {name} heat", bus1=nodes + f" {heat_system} heat",
carrier=f"{name} {heat_pump_type} heat pump", carrier=f"{heat_system} {heat_source} heat pump",
efficiency=efficiency, efficiency=efficiency,
capital_cost=costs.at[costs_name, "efficiency"] capital_cost=costs.at[costs_name, "efficiency"]
* costs.at[costs_name, "fixed"] * costs.at[costs_name, "fixed"]
@ -1951,59 +1954,65 @@ def add_heat(n, costs):
) )
if options["tes"]: if options["tes"]:
n.add("Carrier", name + " water tanks") n.add("Carrier", f"{heat_system} water tanks")
n.madd( n.madd(
"Bus", "Bus",
nodes + f" {name} water tanks", nodes + f" {heat_system} water tanks",
location=nodes, location=nodes,
carrier=name + " water tanks", carrier=f"{heat_system} water tanks",
unit="MWh_th", unit="MWh_th",
) )
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} water tanks charger", nodes + f" {heat_system} water tanks charger",
bus0=nodes + f" {name} heat", bus0=nodes + f" {heat_system} heat",
bus1=nodes + f" {name} water tanks", bus1=nodes + f" {heat_system} water tanks",
efficiency=costs.at["water tank charger", "efficiency"], efficiency=costs.at["water tank charger", "efficiency"],
carrier=name + " water tanks charger", carrier=f"{heat_system} water tanks charger",
p_nom_extendable=True, p_nom_extendable=True,
) )
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} water tanks discharger", nodes + f" {heat_system} water tanks discharger",
bus0=nodes + f" {name} water tanks", bus0=nodes + f" {heat_system} water tanks",
bus1=nodes + f" {name} heat", bus1=nodes + f" {heat_system} heat",
carrier=name + " water tanks discharger", carrier=f"{heat_system} water tanks discharger",
efficiency=costs.at["water tank discharger", "efficiency"], efficiency=costs.at["water tank discharger", "efficiency"],
p_nom_extendable=True, p_nom_extendable=True,
) )
tes_time_constant_days = options["tes_tau"][name_type] tes_time_constant_days = options["tes_tau"][
heat_system.central_or_decentral
]
n.madd( n.madd(
"Store", "Store",
nodes + f" {name} water tanks", nodes + f" {heat_system} water tanks",
bus=nodes + f" {name} water tanks", bus=nodes + f" {heat_system} water tanks",
e_cyclic=True, e_cyclic=True,
e_nom_extendable=True, e_nom_extendable=True,
carrier=name + " water tanks", carrier=f"{heat_system} water tanks",
standing_loss=1 - np.exp(-1 / 24 / tes_time_constant_days), standing_loss=1 - np.exp(-1 / 24 / tes_time_constant_days),
capital_cost=costs.at[name_type + " water tank storage", "fixed"], capital_cost=costs.at[
lifetime=costs.at[name_type + " water tank storage", "lifetime"], heat_system.central_or_decentral + " water tank storage", "fixed"
],
lifetime=costs.at[
heat_system.central_or_decentral + " water tank storage", "lifetime"
],
) )
if options["resistive_heaters"]: if options["resistive_heaters"]:
key = f"{name_type} resistive heater" key = f"{heat_system.central_or_decentral} resistive heater"
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} resistive heater", nodes + f" {heat_system} resistive heater",
bus0=nodes, bus0=nodes,
bus1=nodes + f" {name} heat", bus1=nodes + f" {heat_system} heat",
carrier=name + " resistive heater", carrier=f"{heat_system} resistive heater",
efficiency=costs.at[key, "efficiency"], efficiency=costs.at[key, "efficiency"],
capital_cost=costs.at[key, "efficiency"] capital_cost=costs.at[key, "efficiency"]
* costs.at[key, "fixed"] * costs.at[key, "fixed"]
@ -2013,16 +2022,16 @@ def add_heat(n, costs):
) )
if options["boilers"]: if options["boilers"]:
key = f"{name_type} gas boiler" key = f"{heat_system.central_or_decentral} gas boiler"
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} gas boiler", nodes + f" {heat_system} gas boiler",
p_nom_extendable=True, p_nom_extendable=True,
bus0=spatial.gas.df.loc[nodes, "nodes"].values, bus0=spatial.gas.df.loc[nodes, "nodes"].values,
bus1=nodes + f" {name} heat", bus1=nodes + f" {heat_system} heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=name + " gas boiler", carrier=f"{heat_system} gas boiler",
efficiency=costs.at[key, "efficiency"], efficiency=costs.at[key, "efficiency"],
efficiency2=costs.at["gas", "CO2 intensity"], efficiency2=costs.at["gas", "CO2 intensity"],
capital_cost=costs.at[key, "efficiency"] capital_cost=costs.at[key, "efficiency"]
@ -2032,22 +2041,26 @@ def add_heat(n, costs):
) )
if options["solar_thermal"]: if options["solar_thermal"]:
n.add("Carrier", name + " solar thermal") n.add("Carrier", f"{heat_system} solar thermal")
n.madd( n.madd(
"Generator", "Generator",
nodes, nodes,
suffix=f" {name} solar thermal collector", suffix=f" {heat_system} solar thermal collector",
bus=nodes + f" {name} heat", bus=nodes + f" {heat_system} heat",
carrier=name + " solar thermal", carrier=f"{heat_system} solar thermal",
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at[name_type + " solar thermal", "fixed"] capital_cost=costs.at[
heat_system.central_or_decentral + " solar thermal", "fixed"
]
* overdim_factor, * overdim_factor,
p_max_pu=solar_thermal[nodes], p_max_pu=solar_thermal[nodes],
lifetime=costs.at[name_type + " solar thermal", "lifetime"], lifetime=costs.at[
heat_system.central_or_decentral + " solar thermal", "lifetime"
],
) )
if options["chp"] and name == "urban central": if options["chp"] and heat_system == HeatSystem.URBAN_CENTRAL:
# add gas CHP; biomass CHP is added in biomass section # add gas CHP; biomass CHP is added in biomass section
n.madd( n.madd(
"Link", "Link",
@ -2104,16 +2117,20 @@ def add_heat(n, costs):
lifetime=costs.at["central gas CHP", "lifetime"], lifetime=costs.at["central gas CHP", "lifetime"],
) )
if options["chp"] and options["micro_chp"] and name != "urban central": if (
options["chp"]
and options["micro_chp"]
and heat_system.value != "urban central"
):
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} micro gas CHP", nodes + f" {heat_system} micro gas CHP",
p_nom_extendable=True, p_nom_extendable=True,
bus0=spatial.gas.df.loc[nodes, "nodes"].values, bus0=spatial.gas.df.loc[nodes, "nodes"].values,
bus1=nodes, bus1=nodes,
bus2=nodes + f" {name} heat", bus2=nodes + f" {heat_system} heat",
bus3="co2 atmosphere", bus3="co2 atmosphere",
carrier=name + " micro gas CHP", carrier=heat_system.value + " micro gas CHP",
efficiency=costs.at["micro CHP", "efficiency"], efficiency=costs.at["micro CHP", "efficiency"],
efficiency2=costs.at["micro CHP", "efficiency-heat"], efficiency2=costs.at["micro CHP", "efficiency-heat"],
efficiency3=costs.at["gas", "CO2 intensity"], efficiency3=costs.at["gas", "CO2 intensity"],
@ -2149,7 +2166,7 @@ def add_heat(n, costs):
) / heat_demand.T.groupby(level=[1]).sum().T ) / heat_demand.T.groupby(level=[1]).sum().T
for name in n.loads[ for name in n.loads[
n.loads.carrier.isin([x + " heat" for x in heat_systems]) n.loads.carrier.isin([x + " heat" for x in HeatSystem])
].index: ].index:
node = n.buses.loc[name, "location"] node = n.buses.loc[name, "location"]
ct = pop_layout.loc[node, "ct"] ct = pop_layout.loc[node, "ct"]
@ -2252,12 +2269,54 @@ def add_biomass(n, costs):
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename( solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename(
index=lambda x: x + " solid biomass" index=lambda x: x + " solid biomass"
) )
msw_biomass_potentials_spatial = biomass_potentials[
"municipal solid waste"
].rename(index=lambda x: x + " municipal solid waste")
else: else:
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum() solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum()
msw_biomass_potentials_spatial = biomass_potentials[
"municipal solid waste"
].sum()
n.add("Carrier", "biogas") n.add("Carrier", "biogas")
n.add("Carrier", "solid biomass") n.add("Carrier", "solid biomass")
if (
options["municipal_solid_waste"]
and not options["industry"]
and cf_industry["waste_to_energy"]
or cf_industry["waste_to_energy_cc"]
):
logger.warning(
"Flag municipal_solid_waste can be only used with industry "
"sector waste to energy."
"Setting municipal_solid_waste=False."
)
options["municipal_solid_waste"] = False
if options["municipal_solid_waste"]:
n.add("Carrier", "municipal solid waste")
n.madd(
"Bus",
spatial.msw.nodes,
location=spatial.msw.locations,
carrier="municipal solid waste",
)
e_max_pu = pd.Series([1] * (len(n.snapshots) - 1) + [0], index=n.snapshots)
n.madd(
"Store",
spatial.msw.nodes,
bus=spatial.msw.nodes,
carrier="municipal solid waste",
e_nom=msw_biomass_potentials_spatial,
marginal_cost=0, # costs.at["municipal solid waste", "fuel"],
e_max_pu=e_max_pu,
e_initial=msw_biomass_potentials_spatial,
)
n.madd( n.madd(
"Bus", "Bus",
spatial.gas.biogas, spatial.gas.biogas,
@ -2294,6 +2353,54 @@ def add_biomass(n, costs):
e_initial=solid_biomass_potentials_spatial, e_initial=solid_biomass_potentials_spatial,
) )
if options["solid_biomass_import"].get("enable", False):
biomass_import_price = options["solid_biomass_import"]["price"]
# convert TWh in MWh
biomass_import_max_amount = options["solid_biomass_import"]["max_amount"] * 1e6
biomass_import_upstream_emissions = options["solid_biomass_import"][
"upstream_emissions_factor"
]
logger.info(
"Adding biomass import with cost %.2f EUR/MWh, a limit of %.2f TWh, and embedded emissions of %.2f%%",
biomass_import_price,
options["solid_biomass_import"]["max_amount"],
biomass_import_upstream_emissions * 100,
)
n.add("Carrier", "solid biomass import")
n.madd(
"Bus",
["EU solid biomass import"],
location="EU",
carrier="solid biomass import",
)
n.madd(
"Store",
["solid biomass import"],
bus=["EU solid biomass import"],
carrier="solid biomass import",
e_nom=biomass_import_max_amount,
marginal_cost=biomass_import_price,
e_initial=biomass_import_max_amount,
)
n.madd(
"Link",
spatial.biomass.nodes,
suffix=" solid biomass import",
bus0=["EU solid biomass import"],
bus1=spatial.biomass.nodes,
bus2="co2 atmosphere",
carrier="solid biomass import",
efficiency=1.0,
efficiency2=biomass_import_upstream_emissions
* costs.at["solid biomass", "CO2 intensity"],
p_nom_extendable=True,
)
n.madd( n.madd(
"Link", "Link",
spatial.gas.biogas_to_gas, spatial.gas.biogas_to_gas,
@ -2365,6 +2472,19 @@ def add_biomass(n, costs):
carrier="solid biomass transport", carrier="solid biomass transport",
) )
if options["municipal_solid_waste"]:
n.madd(
"Link",
biomass_transport.index,
bus0=biomass_transport.bus0 + " municipal solid waste",
bus1=biomass_transport.bus1 + " municipal solid waste",
p_nom_extendable=False,
p_nom=5e4,
length=biomass_transport.length.values,
marginal_cost=biomass_transport.costs * biomass_transport.length.values,
carrier="municipal solid waste transport",
)
elif options["biomass_spatial"]: elif options["biomass_spatial"]:
# add artificial biomass generators at nodes which include transport costs # add artificial biomass generators at nodes which include transport costs
transport_costs = pd.read_csv( transport_costs = pd.read_csv(
@ -2394,6 +2514,26 @@ def add_biomass(n, costs):
type="operational_limit", type="operational_limit",
) )
if options["municipal_solid_waste"]:
# Add municipal solid waste
n.madd(
"Generator",
spatial.msw.nodes,
bus=spatial.msw.nodes,
carrier="municipal solid waste",
p_nom=10000,
marginal_cost=0 # costs.at["municipal solid waste", "fuel"]
+ bus_transport_costs * average_distance,
)
n.add(
"GlobalConstraint",
"msw limit",
carrier_attribute="municipal solid waste",
sense="<=",
constant=biomass_potentials["municipal solid waste"].sum(),
type="operational_limit",
)
# AC buses with district heating # AC buses with district heating
urban_central = n.buses.index[n.buses.carrier == "urban central heat"] urban_central = n.buses.index[n.buses.carrier == "urban central heat"]
if not urban_central.empty and options["chp"]: if not urban_central.empty and options["chp"]:
@ -2426,28 +2566,23 @@ def add_biomass(n, costs):
bus4=spatial.co2.df.loc[urban_central, "nodes"].values, bus4=spatial.co2.df.loc[urban_central, "nodes"].values,
carrier="urban central solid biomass CHP CC", carrier="urban central solid biomass CHP CC",
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at[key, "fixed"] * costs.at[key, "efficiency"] capital_cost=costs.at[key + " CC", "fixed"]
* costs.at[key + " CC", "efficiency"]
+ costs.at["biomass CHP capture", "fixed"] + costs.at["biomass CHP capture", "fixed"]
* costs.at["solid biomass", "CO2 intensity"], * costs.at["solid biomass", "CO2 intensity"],
marginal_cost=costs.at[key, "VOM"], marginal_cost=costs.at[key + " CC", "VOM"],
efficiency=costs.at[key, "efficiency"] efficiency=costs.at[key + " CC", "efficiency"]
- costs.at["solid biomass", "CO2 intensity"] - costs.at["solid biomass", "CO2 intensity"]
* ( * (
costs.at["biomass CHP capture", "electricity-input"] costs.at["biomass CHP capture", "electricity-input"]
+ costs.at["biomass CHP capture", "compression-electricity-input"] + costs.at["biomass CHP capture", "compression-electricity-input"]
), ),
efficiency2=costs.at[key, "efficiency-heat"] efficiency2=costs.at[key + " CC", "efficiency-heat"],
+ costs.at["solid biomass", "CO2 intensity"]
* (
costs.at["biomass CHP capture", "heat-output"]
+ costs.at["biomass CHP capture", "compression-heat-output"]
- costs.at["biomass CHP capture", "heat-input"]
),
efficiency3=-costs.at["solid biomass", "CO2 intensity"] efficiency3=-costs.at["solid biomass", "CO2 intensity"]
* costs.at["biomass CHP capture", "capture_rate"], * costs.at["biomass CHP capture", "capture_rate"],
efficiency4=costs.at["solid biomass", "CO2 intensity"] efficiency4=costs.at["solid biomass", "CO2 intensity"]
* costs.at["biomass CHP capture", "capture_rate"], * costs.at["biomass CHP capture", "capture_rate"],
lifetime=costs.at[key, "lifetime"], lifetime=costs.at[key + " CC", "lifetime"],
) )
if options["biomass_boiler"]: if options["biomass_boiler"]:
@ -2489,11 +2624,12 @@ def add_biomass(n, costs):
efficiency2=-costs.at["solid biomass", "CO2 intensity"] efficiency2=-costs.at["solid biomass", "CO2 intensity"]
+ costs.at["BtL", "CO2 stored"], + costs.at["BtL", "CO2 stored"],
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at["BtL", "fixed"], capital_cost=costs.at["BtL", "fixed"] * costs.at["BtL", "efficiency"],
marginal_cost=costs.at["BtL", "efficiency"] * costs.at["BtL", "VOM"], marginal_cost=costs.at["BtL", "VOM"] * costs.at["BtL", "efficiency"],
) )
# TODO: Update with energy penalty # Assuming that acid gas removal (incl. CO2) from syngas i performed with Rectisol
# process (Methanol) and that electricity demand for this is included in the base process
n.madd( n.madd(
"Link", "Link",
spatial.biomass.nodes, spatial.biomass.nodes,
@ -2509,9 +2645,46 @@ def add_biomass(n, costs):
+ costs.at["BtL", "CO2 stored"] * (1 - costs.at["BtL", "capture rate"]), + costs.at["BtL", "CO2 stored"] * (1 - costs.at["BtL", "capture rate"]),
efficiency3=costs.at["BtL", "CO2 stored"] * costs.at["BtL", "capture rate"], efficiency3=costs.at["BtL", "CO2 stored"] * costs.at["BtL", "capture rate"],
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at["BtL", "fixed"] capital_cost=costs.at["BtL", "fixed"] * costs.at["BtL", "efficiency"]
+ costs.at["biomass CHP capture", "fixed"] * costs.at["BtL", "CO2 stored"], + costs.at["biomass CHP capture", "fixed"] * costs.at["BtL", "CO2 stored"],
marginal_cost=costs.at["BtL", "efficiency"] * costs.at["BtL", "VOM"], marginal_cost=costs.at["BtL", "VOM"] * costs.at["BtL", "efficiency"],
)
# Electrobiofuels (BtL with hydrogen addition to make more use of biogenic carbon).
# Combination of efuels and biomass to liquid, both based on Fischer-Tropsch.
# Experimental version - use with caution
if options["electrobiofuels"]:
efuel_scale_factor = costs.at["BtL", "C stored"]
name = (
pd.Index(spatial.biomass.nodes)
+ " "
+ pd.Index(spatial.h2.nodes.str.replace(" H2", ""))
)
n.madd(
"Link",
name,
suffix=" electrobiofuels",
bus0=spatial.biomass.nodes,
bus1=spatial.oil.nodes,
bus2=spatial.h2.nodes,
bus3="co2 atmosphere",
carrier="electrobiofuels",
lifetime=costs.at["electrobiofuels", "lifetime"],
efficiency=costs.at["electrobiofuels", "efficiency-biomass"],
efficiency2=-costs.at["electrobiofuels", "efficiency-hydrogen"],
efficiency3=-costs.at["solid biomass", "CO2 intensity"]
+ costs.at["BtL", "CO2 stored"]
* (1 - costs.at["Fischer-Tropsch", "capture rate"]),
p_nom_extendable=True,
capital_cost=costs.at["BtL", "fixed"] * costs.at["BtL", "efficiency"]
+ efuel_scale_factor
* costs.at["Fischer-Tropsch", "fixed"]
* costs.at["Fischer-Tropsch", "efficiency"],
marginal_cost=costs.at["BtL", "VOM"] * costs.at["BtL", "efficiency"]
+ efuel_scale_factor
* costs.at["Fischer-Tropsch", "VOM"]
* costs.at["Fischer-Tropsch", "efficiency"],
) )
# BioSNG from solid biomass # BioSNG from solid biomass
@ -2529,11 +2702,12 @@ def add_biomass(n, costs):
efficiency3=-costs.at["solid biomass", "CO2 intensity"] efficiency3=-costs.at["solid biomass", "CO2 intensity"]
+ costs.at["BioSNG", "CO2 stored"], + costs.at["BioSNG", "CO2 stored"],
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at["BioSNG", "fixed"], capital_cost=costs.at["BioSNG", "fixed"] * costs.at["BioSNG", "efficiency"],
marginal_cost=costs.at["BioSNG", "efficiency"] * costs.at["BioSNG", "VOM"], marginal_cost=costs.at["BioSNG", "VOM"] * costs.at["BioSNG", "efficiency"],
) )
# TODO: Update with energy penalty for CC # Assuming that acid gas removal (incl. CO2) from syngas i performed with Rectisol
# process (Methanol) and that electricity demand for this is included in the base process
n.madd( n.madd(
"Link", "Link",
spatial.biomass.nodes, spatial.biomass.nodes,
@ -2551,10 +2725,10 @@ def add_biomass(n, costs):
+ costs.at["BioSNG", "CO2 stored"] + costs.at["BioSNG", "CO2 stored"]
* (1 - costs.at["BioSNG", "capture rate"]), * (1 - costs.at["BioSNG", "capture rate"]),
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at["BioSNG", "fixed"] capital_cost=costs.at["BioSNG", "fixed"] * costs.at["BioSNG", "efficiency"]
+ costs.at["biomass CHP capture", "fixed"] + costs.at["biomass CHP capture", "fixed"]
* costs.at["BioSNG", "CO2 stored"], * costs.at["BioSNG", "CO2 stored"],
marginal_cost=costs.at["BioSNG", "efficiency"] * costs.at["BioSNG", "VOM"], marginal_cost=costs.at["BioSNG", "VOM"] * costs.at["BioSNG", "efficiency"],
) )
@ -2781,10 +2955,11 @@ def add_industry(n, costs):
) )
domestic_navigation = pop_weighted_energy_totals.loc[ domestic_navigation = pop_weighted_energy_totals.loc[
nodes, "total domestic navigation" nodes, ["total domestic navigation"]
].squeeze() ].squeeze()
international_navigation = ( international_navigation = (
pd.read_csv(snakemake.input.shipping_demand, index_col=0).squeeze() * nyears pd.read_csv(snakemake.input.shipping_demand, index_col=0).squeeze(axis=1)
* nyears
) )
all_navigation = domestic_navigation + international_navigation all_navigation = domestic_navigation + international_navigation
p_set = all_navigation * 1e6 / nhours p_set = all_navigation * 1e6 / nhours
@ -2903,7 +3078,7 @@ def add_industry(n, costs):
carrier="oil", carrier="oil",
) )
if "oil" not in n.generators.carrier.unique(): if options.get("fossil_fuels", True) and "oil" not in n.generators.carrier.unique():
n.madd( n.madd(
"Generator", "Generator",
spatial.oil.nodes, spatial.oil.nodes,
@ -2949,20 +3124,16 @@ def add_industry(n, costs):
if options["oil_boilers"]: if options["oil_boilers"]:
nodes = pop_layout.index nodes = pop_layout.index
for name in [ for heat_system in HeatSystem:
"residential rural", if not heat_system == HeatSystem.URBAN_CENTRAL:
"services rural",
"residential urban decentral",
"services urban decentral",
]:
n.madd( n.madd(
"Link", "Link",
nodes + f" {name} oil boiler", nodes + f" {heat_system} oil boiler",
p_nom_extendable=True, p_nom_extendable=True,
bus0=spatial.oil.nodes, bus0=spatial.oil.nodes,
bus1=nodes + f" {name} heat", bus1=nodes + f" {heat_system} heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=f"{name} oil boiler", carrier=f"{heat_system} oil boiler",
efficiency=costs.at["decentral oil boiler", "efficiency"], efficiency=costs.at["decentral oil boiler", "efficiency"],
efficiency2=costs.at["oil", "CO2 intensity"], efficiency2=costs.at["oil", "CO2 intensity"],
capital_cost=costs.at["decentral oil boiler", "efficiency"] capital_cost=costs.at["decentral oil boiler", "efficiency"]
@ -3064,6 +3235,17 @@ def add_industry(n, costs):
efficiency3=process_co2_per_naphtha, efficiency3=process_co2_per_naphtha,
) )
if options.get("biomass", True) and options["municipal_solid_waste"]:
n.madd(
"Link",
spatial.msw.locations,
bus0=spatial.msw.nodes,
bus1=non_sequestered_hvc_locations,
carrier="municipal solid waste",
p_nom_extendable=True,
efficiency=1.0,
)
n.madd( n.madd(
"Link", "Link",
spatial.oil.demand_locations, spatial.oil.demand_locations,
@ -3113,7 +3295,9 @@ def add_industry(n, costs):
carrier="waste CHP CC", carrier="waste CHP CC",
p_nom_extendable=True, p_nom_extendable=True,
capital_cost=costs.at["waste CHP CC", "fixed"] capital_cost=costs.at["waste CHP CC", "fixed"]
* costs.at["waste CHP CC", "efficiency"], * costs.at["waste CHP CC", "efficiency"]
+ costs.at["biomass CHP capture", "fixed"]
* costs.at["oil", "CO2 intensity"],
marginal_cost=costs.at["waste CHP CC", "VOM"], marginal_cost=costs.at["waste CHP CC", "VOM"],
efficiency=costs.at["waste CHP CC", "efficiency"], efficiency=costs.at["waste CHP CC", "efficiency"],
efficiency2=costs.at["waste CHP CC", "efficiency-heat"], efficiency2=costs.at["waste CHP CC", "efficiency-heat"],
@ -3952,12 +4136,11 @@ if __name__ == "__main__":
snakemake = mock_snakemake( snakemake = mock_snakemake(
"prepare_sector_network", "prepare_sector_network",
# configfiles="test/config.overnight.yaml",
simpl="", simpl="",
opts="", opts="",
clusters="37", clusters="37",
ll="v1.0", ll="vopt",
sector_opts="730H-T-H-B-I-A-dist1", sector_opts="",
planning_horizons="2050", planning_horizons="2050",
) )
@ -4013,7 +4196,7 @@ if __name__ == "__main__":
add_land_transport(n, costs) add_land_transport(n, costs)
if options["heating"]: if options["heating"]:
add_heat(n, costs) add_heat(n=n, costs=costs, cop=xr.open_dataarray(snakemake.input.cop_profiles))
if options["biomass"]: if options["biomass"]:
add_biomass(n, costs) add_biomass(n, costs)