diff --git a/config/config.default.yaml b/config/config.default.yaml index 84e881d96f..3bcf65adbb 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -873,7 +873,14 @@ sector: co2_spatial: true co2_network: true co2_network_cost_factor: 1 + co2_network_liquefaction: false cc_fraction: 0.9 + cc_capital_cost_factor: + gas: 2.0 + biomass: 1.1 + coal: 1.1 + waste: 1.2 + cement: 1.0 hydrogen_underground_storage: true hydrogen_underground_storage_locations: - onshore diff --git a/config/plotting.default.yaml b/config/plotting.default.yaml index a2d7405123..f92739335a 100644 --- a/config/plotting.default.yaml +++ b/config/plotting.default.yaml @@ -693,6 +693,9 @@ plotting: DAC: '#ff5270' co2 stored: '#f2385a' co2 sequestered: '#f2682f' + co2 dense: '#65334d' + co2 expansion: '#c6ebbe' + co2 compression: '#a9dbb8' co2: '#f29dae' co2 vent: '#ffd4dc' CO2 pipeline: '#f5627f' diff --git a/config/schema.default.json b/config/schema.default.json index 91152b3c96..f9874bf191 100644 --- a/config/schema.default.json +++ b/config/schema.default.json @@ -4713,11 +4713,23 @@ "description": "The cost factor for the capital cost of the carbon dioxide transmission network.", "type": "number" }, + "co2_network_liquefaction": { + "default": false, + "description": "Add option for including compressor stations with investment costs and electricity demand for liquefaction step for carbon dioxide before transport.", + "type": "boolean" + }, "cc_fraction": { "default": 0.9, "description": "The default fraction of CO2 captured with post-combustion capture.", "type": "number" }, + "cc_capital_cost_factor": { + "additionalProperties": { + "type": "number" + }, + "description": "Size of the carbon capture unit depending on the amount of carbon dioxide in the flue gas. The more CO2, the smaller the capture unit and thus the lower the capital cost factor. Factors are given relative to cement capture. The default values are based on the DEA technology-data report on carbon capture, transport and storage Table 8 / Figure 12 (https://ens.dk/en/analyses-and-statistics/technology-data-carbon-capture-transport-and-storage).", + "type": "object" + }, "hydrogen_underground_storage": { "default": true, "description": "Add options for storing hydrogen underground. Storage potential depends regionally.", @@ -11162,11 +11174,23 @@ "description": "The cost factor for the capital cost of the carbon dioxide transmission network.", "type": "number" }, + "co2_network_liquefaction": { + "default": false, + "description": "Add option for including compressor stations with investment costs and electricity demand for liquefaction step for carbon dioxide before transport.", + "type": "boolean" + }, "cc_fraction": { "default": 0.9, "description": "The default fraction of CO2 captured with post-combustion capture.", "type": "number" }, + "cc_capital_cost_factor": { + "additionalProperties": { + "type": "number" + }, + "description": "Size of the carbon capture unit depending on the amount of carbon dioxide in the flue gas. The more CO2, the smaller the capture unit and thus the lower the capital cost factor. Factors are given relative to cement capture. The default values are based on the DEA technology-data report on carbon capture, transport and storage Table 8 / Figure 12 (https://ens.dk/en/analyses-and-statistics/technology-data-carbon-capture-transport-and-storage).", + "type": "object" + }, "hydrogen_underground_storage": { "default": true, "description": "Add options for storing hydrogen underground. Storage potential depends regionally.", diff --git a/data/versions.csv b/data/versions.csv index 4cf11f88cf..f6a07f450e 100644 --- a/data/versions.csv +++ b/data/versions.csv @@ -18,7 +18,7 @@ copernicus_land_cover,v3.0.1,archive,latest supported,2026-01-20,"The primary is copernicus_land_cover,v2.0.2,archive,deprecated supported,2026-01-13,,https://data.pypsa.org/workflows/eur/copernicus_land_cover/v2.0.2/PROBAV_LC100_global_v3.0.1_2015-base_Discrete-Classification-map_EPSG-4326.tif corine,v18_5,archive,latest supported,2026-01-13,,https://data.pypsa.org/workflows/eur/corine/v18_5/corine.zip corine,unknown,primary,latest supported,2025-12-02,Need to register with CLMS API and create an access token. The download URL is dynamic, -costs,v0.14.0,primary,latest supported,2026-02-13,Part of the `technologydata` repository and versioned on GitHub.,https://raw.githubusercontent.com/PyPSA/technology-data/refs/tags/v0.14.0/outputs +costs,co2_management,primary,latest supported,2026-05-12,Part of the `technologydata` repository and versioned on GitHub.,https://github.com/PyPSA/technology-data/tree/co2_management/outputs costs,v0.14.0,archive,latest supported,2026-02-13,Part of the `technologydata` repository and versioned on GitHub.,https://data.pypsa.org/workflows/eur/costs/v0.14.0 costs,v0.13.4,primary,supported,2025-12-02,Part of the `technologydata` repository and versioned on GitHub.,https://raw.githubusercontent.com/PyPSA/technology-data/refs/tags/v0.13.4/outputs costs,v0.13.4,archive,supported,2026-01-21,Part of the `technologydata` repository and versioned on GitHub.,https://data.pypsa.org/workflows/eur/costs/v0.13.4 diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 4bc3f28e86..2e792998cb 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -8,11 +8,14 @@ Release Notes .. Upcoming Release .. ================= + +* Adding option to include the compression step in carbon dioxide transport before transporting in dense phase and including electricity demand for post combustion carbon capture. Adjusting the capital costs for post combustion capture that differs depending on the carbon dioxide percentage in the flue gas. + * Fix: Re-introduce capital costs for non-bicharging discharge links in ``add_electricity.py``, e.g. fuel cells. * The lockfile update workflow now excludes packages published within the last 7 days to reduce the risk of pulling in broken or yanked releases (https://github.com/PyPSA/pypsa-eur/pull/2130). -* The industry reference year and the ammonia production data have been updated to 2023 (https://github.com/PyPSA/pypsa-eur/pull/2103) +* The industry reference year and the ammonia production data have been updated to 2023 (https://github.com/PyPSA/pypsa-eur/pull/2103) * refactor: Use scripts path provider consistently (https://github.com/PyPSA/pypsa-eur/pull/2093). diff --git a/scripts/lib/validation/config/sector.py b/scripts/lib/validation/config/sector.py index 5801f7fcbe..37d4f030f7 100644 --- a/scripts/lib/validation/config/sector.py +++ b/scripts/lib/validation/config/sector.py @@ -748,11 +748,24 @@ class SectorConfig(BaseModel): 1, description="The cost factor for the capital cost of the carbon dioxide transmission network.", ) + co2_network_liquefaction: bool = Field( + False, + description="Add option for including compressor stations with investment costs and electricity demand for liquefaction step for carbon dioxide before transport.", + ) cc_fraction: float = Field( 0.9, description="The default fraction of CO2 captured with post-combustion capture.", ) - + cc_capital_cost_factor: dict[str, float] = Field( + default_factory=lambda: { + "gas": 2.0, + "biomass": 1.1, + "coal": 1.1, + "waste": 1.2, + "cement": 1.0, + }, + description="Size of the carbon capture unit depending on the amount of carbon dioxide in the flue gas. The more CO2, the smaller the capture unit and thus the lower the capital cost factor. Factors are given relative to cement capture. The default values are based on the DEA technology-data report on carbon capture, transport and storage Table 8 / Figure 12 (https://ens.dk/en/analyses-and-statistics/technology-data-carbon-capture-transport-and-storage).", + ) hydrogen_underground_storage: bool = Field( True, description="Add options for storing hydrogen underground. Storage potential depends regionally.", diff --git a/scripts/plot_balance_map.py b/scripts/plot_balance_map.py index a58e82b658..6307e513b3 100644 --- a/scripts/plot_balance_map.py +++ b/scripts/plot_balance_map.py @@ -80,13 +80,32 @@ n.buses["x"] = n.buses.location.map(n.buses.x) n.buses["y"] = n.buses.location.map(n.buses.y) - # bus_size according to energy balance of bus carrier - eb = n.statistics.energy_balance(bus_carrier=carrier, groupby=["bus", "carrier"]) + if carrier == "co2 stored" and "co2 dense" in n.buses.carrier.unique(): + co2_carriers = ["co2 stored", "co2 dense"] + # Aggregate energy balance of "co2 stored" and "co2 dense" to get the total CO2 balance for each bus + eb = n.statistics.energy_balance( + bus_carrier=co2_carriers, groupby=["bus", "carrier"] + ) + eb = eb.rename( + index=lambda value: value.replace("co2 dense", carrier), level="bus" + ) + eb = eb.groupby(level=["component", "bus", "carrier"]).sum() + + # remove energy balance of transmission carriers which relate to losses + transmission_carriers = get_transmission_carriers( + n, bus_carrier=co2_carriers + ).rename({"name": "carrier"}) + else: + # bus_size according to energy balance of bus carrier + eb = n.statistics.energy_balance( + bus_carrier=carrier, groupby=["bus", "carrier"] + ) + + # remove energy balance of transmission carriers which relate to losses + transmission_carriers = get_transmission_carriers( + n, bus_carrier=carrier + ).rename({"name": "carrier"}) - # remove energy balance of transmission carriers which relate to losses - transmission_carriers = get_transmission_carriers(n, bus_carrier=carrier).rename( - {"name": "carrier"} - ) components = transmission_carriers.unique("component") carriers = transmission_carriers.unique("carrier") diff --git a/scripts/plot_balance_map_interactive.py b/scripts/plot_balance_map_interactive.py index 14078bb182..fe1d972671 100644 --- a/scripts/plot_balance_map_interactive.py +++ b/scripts/plot_balance_map_interactive.py @@ -115,18 +115,34 @@ def scalar_to_rgba( b_missing = n.carriers.query("color == '' or color.isnull()").index n.carriers.loc[b_missing, "color"] = missing_color - transmission_carriers = get_transmission_carriers(n, bus_carrier=carrier).rename( - {"name": "carrier"} - ) + if carrier == "co2 stored" and "co2 dense" in n.buses.carrier.unique(): + co2_carriers = ["co2 stored", "co2 dense"] + transmission_carriers = get_transmission_carriers( + n, bus_carrier=co2_carriers + ).rename({"name": "carrier"}) + + eb = n.statistics.energy_balance( + bus_carrier=co2_carriers, + groupby=["bus", "carrier"], + ) + eb = eb.rename( + index=lambda value: value.replace("co2 dense", carrier), level="bus" + ) + eb = eb.groupby(level=["component", "bus", "carrier"]).sum() + else: + transmission_carriers = get_transmission_carriers( + n, bus_carrier=carrier + ).rename({"name": "carrier"}) + + ### Pie charts + eb = n.statistics.energy_balance( + bus_carrier=carrier, + groupby=["bus", "carrier"], + ) + components = transmission_carriers.unique("component") carriers = transmission_carriers.unique("carrier") - ### Pie charts - eb = n.statistics.energy_balance( - bus_carrier=carrier, - groupby=["bus", "carrier"], - ) - # Only carriers that are also in the energy balance carriers_in_eb = carriers[carriers.isin(eb.index.get_level_values("carrier"))] diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index ad83a9b877..ecd4fd9fdd 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -71,8 +71,6 @@ def define_spatial(nodes, options): spatial.biomass.nodes_unsustainable = nodes + " unsustainable solid biomass" spatial.biomass.bioliquids = nodes + " unsustainable bioliquids" spatial.biomass.locations = nodes - spatial.biomass.industry = nodes + " solid biomass for industry" - spatial.biomass.industry_cc = nodes + " solid biomass for industry CC" spatial.msw.nodes = nodes + " municipal solid waste" spatial.msw.locations = nodes else: @@ -80,10 +78,11 @@ def define_spatial(nodes, options): spatial.biomass.nodes_unsustainable = ["EU unsustainable solid biomass"] spatial.biomass.bioliquids = ["EU unsustainable bioliquids"] spatial.biomass.locations = ["EU"] - spatial.biomass.industry = ["solid biomass for industry"] - spatial.biomass.industry_cc = ["solid biomass for industry CC"] spatial.msw.nodes = ["EU municipal solid waste"] spatial.msw.locations = ["EU"] + spatial.biomass.industry = nodes + " solid biomass for industry" + spatial.biomass.industry_cc = nodes + " solid biomass for industry CC" + spatial.biomass.industry.locations = nodes spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes) spatial.msw.df = pd.DataFrame(vars(spatial.msw), index=nodes) @@ -97,11 +96,13 @@ def define_spatial(nodes, options): spatial.co2.locations = nodes spatial.co2.vents = nodes + " co2 vent" spatial.co2.process_emissions = nodes + " process emissions" + spatial.co2.dense = nodes + " co2 dense" else: spatial.co2.nodes = ["co2 stored"] spatial.co2.locations = ["EU"] spatial.co2.vents = ["co2 vent"] spatial.co2.process_emissions = ["process emissions"] + spatial.co2.dense = ["co2 dense"] spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes) @@ -113,24 +114,20 @@ def define_spatial(nodes, options): spatial.gas.nodes = nodes + " gas" spatial.gas.locations = nodes spatial.gas.biogas = nodes + " biogas" - spatial.gas.industry = nodes + " gas for industry" - spatial.gas.industry_cc = nodes + " gas for industry CC" spatial.gas.biogas_to_gas = nodes + " biogas to gas" spatial.gas.biogas_to_gas_cc = nodes + " biogas to gas CC" else: spatial.gas.nodes = ["EU gas"] spatial.gas.locations = ["EU"] spatial.gas.biogas = ["EU biogas"] - spatial.gas.industry = ["gas for industry"] spatial.gas.biogas_to_gas = ["EU biogas to gas"] if options.get("biomass_spatial", options["biomass_transport"]): spatial.gas.biogas_to_gas_cc = nodes + " biogas to gas CC" else: spatial.gas.biogas_to_gas_cc = ["EU biogas to gas CC"] - if options.get("co2_spatial", options["co2_network"]): - spatial.gas.industry_cc = nodes + " gas for industry CC" - else: - spatial.gas.industry_cc = ["gas for industry CC"] + spatial.gas.industry = nodes + " gas for industry" + spatial.gas.industry_cc = nodes + " gas for industry CC" + spatial.gas.industry.locations = nodes spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes) @@ -715,7 +712,12 @@ def add_eu_bus(n, x=-5.5, y=46): def add_co2_tracking( - n, costs, options, sequestration_potential_file=None, co2_price: float = 0.0 + n, + costs, + options, + sequestration_potential_file=None, + co2_price: float = 0.0, + co2_liquefaction=False, ): """ Add CO2 tracking components to the network including atmospheric CO2, @@ -743,6 +745,8 @@ def add_co2_tracking( co2_price : float, optional CO2 price that needs to be paid for emitting into the atmosphere and which is gained by removing from the atmosphere. + co2_liquefaction : bool, optional + Whether to consider the liquefaction step with investment costs and electricity demand for compressors. Returns ------- @@ -806,16 +810,67 @@ def add_co2_tracking( carrier="co2 sequestered", unit="t_co2", ) + if co2_liquefaction: + n.add("Carrier", "co2 dense") + n.add( + "Bus", + spatial.co2.dense, + x=n.buses.loc[spatial.co2.locations, "x"].values, + y=n.buses.loc[spatial.co2.locations, "y"].values, + location=spatial.co2.locations, + carrier="co2 dense", + unit="t_co2", + ) + n.add("Carrier", "co2 compression") + n.add( + "Link", + spatial.co2.dense, + bus0=spatial.co2.nodes, + bus1=spatial.co2.dense, + bus2=spatial.nodes, + capital_cost=costs.at["CO2 dense phase compression", "capital_cost"], + efficiency=1.0, + efficiency2=-costs.at["CO2 dense phase compression", "electricity-input"], + p_nom_extendable=True, + carrier="co2 compression", + unit="t_co2", + ) + n.add("Carrier", "co2 expansion") + n.add( + "Link", + spatial.co2.nodes, + suffix=" expansion", + bus0=spatial.co2.dense, + bus1=spatial.co2.nodes, + efficiency=1.0, + p_nom=1e7, + carrier="co2 expansion", + unit="t_co2", + ) + n.add( + "Link", + sequestration_buses, + bus0=spatial.co2.dense, + bus1=sequestration_buses, + carrier="co2 sequestered", + marginal_cost=options["co2_sequestration_cost"], + efficiency=1.0, + p_nom=np.inf, + p_nom_extendable=False, + ) - n.add( - "Link", - sequestration_buses, - bus0=spatial.co2.nodes, - bus1=sequestration_buses, - carrier="co2 sequestered", - efficiency=1.0, - p_nom_extendable=True, - ) + else: + n.add( + "Link", + sequestration_buses, + bus0=spatial.co2.nodes, + bus1=sequestration_buses, + carrier="co2 sequestered", + marginal_cost=options["co2_sequestration_cost"], + efficiency=1.0, + p_nom=np.inf, + p_nom_extendable=False, + ) if options["regional_co2_sequestration_potential"]["enable"]: if sequestration_potential_file is None: @@ -850,7 +905,6 @@ def add_co2_tracking( sequestration_buses, e_nom_extendable=True, e_nom_max=e_nom_max, - capital_cost=options["co2_sequestration_cost"], marginal_cost=-0.1, bus=sequestration_buses, lifetime=options["co2_sequestration_lifetime"], @@ -871,7 +925,7 @@ def add_co2_tracking( ) -def add_co2_network(n, costs, co2_network_cost_factor=1.0): +def add_co2_network(n, costs, co2_network_cost_factor=1.0, co2_liquefaction=False): """ Add CO2 transport network to the PyPSA network. @@ -889,6 +943,8 @@ def add_co2_network(n, costs, co2_network_cost_factor=1.0): columns co2_network_cost_factor : float, optional Factor to scale the capital costs of the CO2 network, default 1.0 + co2_liquefaction : bool, optional + Whether to consider the liquefaction step for CO2 transport in dense phase or not. If True, compressor investment costs and electricity demand for compression are considered. Returns ------- @@ -920,11 +976,16 @@ def add_co2_network(n, costs, co2_network_cost_factor=1.0): capital_cost = cost_onshore + cost_submarine capital_cost *= co2_network_cost_factor + if co2_liquefaction: + suffix = " co2 dense" + else: + suffix = " co2 stored" + n.add( "Link", co2_links.index, - bus0=co2_links.bus0.values + " co2 stored", - bus1=co2_links.bus1.values + " co2 stored", + bus0=co2_links.bus0.values + suffix, + bus1=co2_links.bus1.values + suffix, p_min_pu=-1, p_nom_extendable=True, length=co2_links.length.values, @@ -1048,7 +1109,7 @@ def add_biomass_to_methanol_cc(n, costs): ) -def add_methanol_to_power(n, costs, pop_layout, types=None): +def add_methanol_to_power(n, costs, pop_layout, options, types=None): if types is None: types = {} @@ -1104,14 +1165,21 @@ def add_methanol_to_power(n, costs, pop_layout, types=None): "Adding methanol CCGT power plants with post-combustion carbon capture." ) - # TODO consider efficiency changes / energy inputs for CC - # efficiency * EUR/MW * (annuity + FOM) capital_cost = costs.at["CCGT", "efficiency"] * costs.at["CCGT", "capital_cost"] capital_cost_cc = ( capital_cost + costs.at["cement capture", "capital_cost"] + * options["cc_capital_cost_factor"]["gas"] + * costs.at["methanolisation", "carbondioxide-input"] + ) + efficiency_cc = ( + costs.at["CCGT", "efficiency"] + - ( + costs.at["cement capture", "electricity-input"] + + costs.at["cement capture", "compression-electricity-input"] + ) * costs.at["methanolisation", "carbondioxide-input"] ) @@ -1128,7 +1196,7 @@ def add_methanol_to_power(n, costs, pop_layout, types=None): capital_cost=capital_cost_cc, marginal_cost=costs.at["CCGT", "VOM"] * costs.at["CCGT", "efficiency"], # NB: VOM is per MWel - efficiency=costs.at["CCGT", "efficiency"], + efficiency=efficiency_cc, efficiency2=costs.at["cement capture", "capture_rate"] * costs.at["methanolisation", "carbondioxide-input"], efficiency3=(1 - costs.at["cement capture", "capture_rate"]) @@ -1180,7 +1248,7 @@ def add_methanol_reforming(n, costs): ) -def add_methanol_reforming_cc(n, costs): +def add_methanol_reforming_cc(n, costs, options): logger.info("Adding methanol steam reforming with carbon capture.") tech = "Methanol steam reforming" @@ -1194,8 +1262,13 @@ def add_methanol_reforming_cc(n, costs): capital_cost_cc = ( capital_cost + costs.at["cement capture", "capital_cost"] + * options["cc_capital_cost_factor"]["gas"] * costs.at["methanolisation", "carbondioxide-input"] ) + electricity_cc = ( + costs.at["cement capture", "electricity-input"] + + costs.at["cement capture", "compression-electricity-input"] + ) * costs.at["methanolisation", "carbondioxide-input"] n.add( "Link", @@ -1205,6 +1278,7 @@ def add_methanol_reforming_cc(n, costs): bus1=spatial.h2.nodes, bus2="co2 atmosphere", bus3=spatial.co2.nodes, + bus4=spatial.h2.locations, p_nom_extendable=True, capital_cost=capital_cost_cc, efficiency=1 / costs.at[tech, "methanol-input"], @@ -1212,6 +1286,7 @@ def add_methanol_reforming_cc(n, costs): * costs.at["methanolisation", "carbondioxide-input"], efficiency3=costs.at["cement capture", "capture_rate"] * costs.at["methanolisation", "carbondioxide-input"], + efficiency4=-electricity_cc, carrier=f"{tech} CC", lifetime=costs.at[tech, "lifetime"], ) @@ -2116,6 +2191,14 @@ def add_h2_gas_infrastructure( ) if options["coal_cc"]: + efficiency_cc = ( + costs.at["coal", "efficiency"] + - ( + costs.at["biomass CHP capture", "electricity-input"] + + costs.at["biomass CHP capture", "compression-electricity-input"] + ) + * costs.at["coal", "CO2 intensity"] + ) n.add( "Link", spatial.nodes, @@ -2132,7 +2215,7 @@ def add_h2_gas_infrastructure( * costs.at["coal", "CO2 intensity"], # NB: fixed cost is per MWel p_nom_extendable=True, carrier="coal", - efficiency=costs.at["coal", "efficiency"], + efficiency=efficiency_cc, efficiency2=costs.at["coal", "CO2 intensity"] * (1 - costs.at["biomass CHP capture", "capture_rate"]), efficiency3=costs.at["coal", "CO2 intensity"] @@ -3737,6 +3820,7 @@ def add_methanol( n=n, costs=costs, pop_layout=pop_layout, + options=options, types=methanol_options["methanol_to_power"], ) @@ -3744,7 +3828,7 @@ def add_methanol( add_methanol_reforming(n=n, costs=costs) if methanol_options["methanol_reforming_cc"]: - add_methanol_reforming_cc(n=n, costs=costs) + add_methanol_reforming_cc(n=n, costs=costs, options=options) def add_biomass( @@ -4584,20 +4668,17 @@ def add_industry( n.add( "Bus", spatial.biomass.industry, - location=spatial.biomass.locations, + location=spatial.biomass.industry.locations, carrier="solid biomass for industry", unit="MWh_LHV", ) - if options.get("biomass_spatial", options["biomass_transport"]): - p_set = ( - industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename( - index=lambda x: x + " solid biomass for industry" - ) - / nhours + p_set = ( + industrial_demand.loc[spatial.biomass.industry.locations, "solid biomass"].rename( + index=lambda x: x + " solid biomass for industry" ) - else: - p_set = industrial_demand["solid biomass"].sum() / nhours + / nhours + ) n.add( "Load", @@ -4617,44 +4698,44 @@ def add_industry( efficiency=1.0, ) - if len(spatial.biomass.industry_cc) <= 1 and len(spatial.co2.nodes) > 1: - link_names = nodes + " " + spatial.biomass.industry_cc - else: - link_names = spatial.biomass.industry_cc + ele_for_cc = costs.at["solid biomass", "CO2 intensity"] * ( + costs.at["cement capture", "electricity-input"] + + costs.at["cement capture", "compression-electricity-input"] + ) n.add( "Link", - link_names, + spatial.biomass.industry_cc, bus0=spatial.biomass.nodes, bus1=spatial.biomass.industry, bus2="co2 atmosphere", bus3=spatial.co2.nodes, + bus4=spatial.biomass.industry.locations, carrier="solid biomass for industry CC", p_nom_extendable=True, capital_cost=costs.at["cement capture", "capital_cost"] - * costs.at["solid biomass", "CO2 intensity"], + * costs.at["solid biomass", "CO2 intensity"] + * options["cc_capital_cost_factor"]["biomass"], efficiency=0.9, # TODO: make config option efficiency2=-costs.at["solid biomass", "CO2 intensity"] * costs.at["cement capture", "capture_rate"], efficiency3=costs.at["solid biomass", "CO2 intensity"] * costs.at["cement capture", "capture_rate"], + efficiency4=-ele_for_cc, lifetime=costs.at["cement capture", "lifetime"], ) n.add( "Bus", spatial.gas.industry, - location=spatial.gas.locations, + location=spatial.gas.industry.locations, carrier="gas for industry", unit="MWh_LHV", ) gas_demand = industrial_demand.loc[nodes, "methane"] / nhours - if options["gas_network"]: - spatial_gas_demand = gas_demand.rename(index=lambda x: x + " gas for industry") - else: - spatial_gas_demand = gas_demand.sum() + spatial_gas_demand = gas_demand.rename(index=lambda x: x + " gas for industry") n.add( "Load", @@ -4676,6 +4757,10 @@ def add_industry( efficiency2=costs.at["gas", "CO2 intensity"], ) + ele_for_cc = costs.at["gas", "CO2 intensity"] * ( + costs.at["cement capture", "electricity-input"] + + costs.at["cement capture", "compression-electricity-input"] + ) n.add( "Link", spatial.gas.industry_cc, @@ -4683,15 +4768,18 @@ def add_industry( bus1=spatial.gas.industry, bus2="co2 atmosphere", bus3=spatial.co2.nodes, + bus4=spatial.gas.industry.locations, carrier="gas for industry CC", p_nom_extendable=True, capital_cost=costs.at["cement capture", "capital_cost"] + * options["cc_capital_cost_factor"]["gas"] * costs.at["gas", "CO2 intensity"], efficiency=0.9, efficiency2=costs.at["gas", "CO2 intensity"] * (1 - costs.at["cement capture", "capture_rate"]), efficiency3=costs.at["gas", "CO2 intensity"] * costs.at["cement capture", "capture_rate"], + efficiency4=-ele_for_cc, lifetime=costs.at["cement capture", "lifetime"], ) @@ -5056,6 +5144,16 @@ def add_industry( ) # assume enough local waste heat for CC + if options["co2_spatial"]: + bus3 = spatial.co2.locations + efficiency3 = ( + costs.at["cement capture", "electricity-input"] + + costs.at["cement capture", "compression-electricity-input"] + ) + else: + bus3 = "" + efficiency3 = 1.0 + n.add( "Link", spatial.co2.locations, @@ -5063,11 +5161,14 @@ def add_industry( bus0=spatial.co2.process_emissions, bus1="co2 atmosphere", bus2=spatial.co2.nodes, + bus3=bus3, carrier="process emissions CC", p_nom_extendable=True, - capital_cost=costs.at["cement capture", "capital_cost"], + capital_cost=costs.at["cement capture", "capital_cost"] + * options["cc_capital_cost_factor"]["cement"], efficiency=1 - costs.at["cement capture", "capture_rate"], efficiency2=costs.at["cement capture", "capture_rate"], + efficiency3=-efficiency3, lifetime=costs.at["cement capture", "lifetime"], ) @@ -6333,6 +6434,7 @@ def add_import_options( options, sequestration_potential_file=snakemake.input.sequestration_potential, co2_price=co2_price, + co2_liquefaction=options["co2_network_liquefaction"], ) add_generation( @@ -6514,6 +6616,7 @@ def add_import_options( co2_network_cost_factor=snakemake.config["sector"][ "co2_network_cost_factor" ], + co2_liquefaction=options["co2_network_liquefaction"], ) if options["allam_cycle_gas"]: