Merge pull request #217 from PyPSA/myopic-fix

Myopic fix
This commit is contained in:
Fabian Neumann 2022-04-12 14:47:22 +02:00 committed by GitHub
commit d4a82a2a41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 210 additions and 126 deletions

View File

@ -1,3 +1,4 @@
attribute,type,unit,default,description,status attribute,type,unit,default,description,status
build_year,integer,year,n/a,build year,Input (optional) carrier,string,n/a,n/a,carrier,Input (optional)
lifetime,float,years,n/a,lifetime,Input (optional) lifetime,float,years,inf,lifetime,Input (optional)
build_year,int,year ,0,build year,Input (optional)

1 attribute type unit default description status
2 build_year carrier integer string year n/a n/a build year carrier Input (optional)
3 lifetime float years n/a inf lifetime Input (optional) Input (optional)
4 build_year int year 0 build year Input (optional)

View File

@ -2,12 +2,12 @@ attribute,type,unit,default,description,status
bus2,string,n/a,n/a,2nd bus,Input (optional) bus2,string,n/a,n/a,2nd bus,Input (optional)
bus3,string,n/a,n/a,3rd bus,Input (optional) bus3,string,n/a,n/a,3rd bus,Input (optional)
bus4,string,n/a,n/a,4th bus,Input (optional) bus4,string,n/a,n/a,4th bus,Input (optional)
efficiency2,static or series,per unit,1.,2nd bus efficiency,Input (optional) efficiency2,static or series,per unit,1,2nd bus efficiency,Input (optional)
efficiency3,static or series,per unit,1.,3rd bus efficiency,Input (optional) efficiency3,static or series,per unit,1,3rd bus efficiency,Input (optional)
efficiency4,static or series,per unit,1.,4th bus efficiency,Input (optional) efficiency4,static or series,per unit,1,4th bus efficiency,Input (optional)
p2,series,MW,0.,2nd bus output,Output p2,series,MW,0,2nd bus output,Output
p3,series,MW,0.,3rd bus output,Output p3,series,MW,0,3rd bus output,Output
p4,series,MW,0.,4th bus output,Output p4,series,MW,0,4th bus output,Output
build_year,integer,year,n/a,build year,Input (optional)
lifetime,float,years,n/a,lifetime,Input (optional)
carrier,string,n/a,n/a,carrier,Input (optional) carrier,string,n/a,n/a,carrier,Input (optional)
lifetime,float,years,inf,lifetime,Input (optional)
build_year,int,year ,0,build year,Input (optional)

1 attribute type unit default description status
2 bus2 string n/a n/a 2nd bus Input (optional)
3 bus3 string n/a n/a 3rd bus Input (optional)
4 bus4 string n/a n/a 4th bus Input (optional)
5 efficiency2 static or series per unit 1. 1 2nd bus efficiency Input (optional)
6 efficiency3 static or series per unit 1. 1 3rd bus efficiency Input (optional)
7 efficiency4 static or series per unit 1. 1 4th bus efficiency Input (optional)
8 p2 series MW 0. 0 2nd bus output Output
9 p3 series MW 0. 0 3rd bus output Output
10 p4 series MW 0. 0 4th bus output Output
build_year integer year n/a build year Input (optional)
lifetime float years n/a lifetime Input (optional)
11 carrier string n/a n/a carrier Input (optional)
12 lifetime float years inf lifetime Input (optional)
13 build_year int year 0 build year Input (optional)

View File

@ -1,4 +1,4 @@
attribute,type,unit,default,description,status attribute,type,unit,default,description,status
build_year,integer,year,n/a,build year,Input (optional)
lifetime,float,years,n/a,lifetime,Input (optional)
carrier,string,n/a,n/a,carrier,Input (optional) carrier,string,n/a,n/a,carrier,Input (optional)
lifetime,float,years,inf,lifetime,Input (optional)
build_year,int,year ,0,build year,Input (optional)

1 attribute type unit default description status
build_year integer year n/a build year Input (optional)
lifetime float years n/a lifetime Input (optional)
2 carrier string n/a n/a carrier Input (optional)
3 lifetime float years inf lifetime Input (optional)
4 build_year int year 0 build year Input (optional)

View File

@ -8,15 +8,22 @@ idx = pd.IndexSlice
import pypsa import pypsa
import yaml import yaml
import numpy as np
from add_existing_baseyear import add_build_year_to_new_assets from add_existing_baseyear import add_build_year_to_new_assets
from helper import override_component_attrs from helper import override_component_attrs
from solve_network import basename
def add_brownfield(n, n_p, year): def add_brownfield(n, n_p, year):
print("adding brownfield") print("adding brownfield")
# electric transmission grid set optimised capacities of previous as minimum
n.lines.s_nom_min = n_p.lines.s_nom_opt
dc_i = n.links[n.links.carrier=="DC"].index
n.links.loc[dc_i, "p_nom_min"] = n_p.links.loc[dc_i, "p_nom_opt"]
for c in n_p.iterate_components(["Link", "Generator", "Store"]): for c in n_p.iterate_components(["Link", "Generator", "Store"]):
attr = "e" if c.name == "Store" else "p" attr = "e" if c.name == "Store" else "p"
@ -25,7 +32,7 @@ def add_brownfield(n, n_p, year):
# CO2 or global EU values since these are already in n # CO2 or global EU values since these are already in n
n_p.mremove( n_p.mremove(
c.name, c.name,
c.df.index[c.df.lifetime.isna()] c.df.index[c.df.lifetime==np.inf]
) )
# remove assets whose build_year + lifetime < year # remove assets whose build_year + lifetime < year
@ -75,16 +82,44 @@ def add_brownfield(n, n_p, year):
for tattr in n.component_attrs[c.name].index[selection]: for tattr in n.component_attrs[c.name].index[selection]:
n.import_series_from_dataframe(c.pnl[tattr], c.name, tattr) n.import_series_from_dataframe(c.pnl[tattr], c.name, tattr)
# deal with gas network
pipe_carrier = ['gas pipeline']
if snakemake.config["sector"]['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
h2_retrofitted_fixed_i = n.links[(n.links.carrier=='H2 pipeline retrofitted') & (n.links.build_year!=year)].index
gas_pipes_i = n.links[n.links.carrier.isin(pipe_carrier)].index
CH4_per_H2 = 1 / snakemake.config["sector"]["H2_retrofit_capacity_per_CH4"]
fr = "H2 pipeline retrofitted"
to = "gas pipeline"
# today's pipe capacity
pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom']
# already retrofitted capacity from gas -> H2
already_retrofitted = (n.links.loc[h2_retrofitted_fixed_i, 'p_nom']
.rename(lambda x: basename(x).replace(fr, to)).groupby(level=0).sum())
remaining_capacity = pipe_capacity - CH4_per_H2 * already_retrofitted.reindex(index=pipe_capacity.index).fillna(0)
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.
n.links.loc[new_pipes, "p_nom_min"] = 0.
#%%
if __name__ == "__main__": if __name__ == "__main__":
if 'snakemake' not in globals(): if 'snakemake' not in globals():
from helper import mock_snakemake from helper import mock_snakemake
snakemake = mock_snakemake( snakemake = mock_snakemake(
'add_brownfield', 'add_brownfield',
simpl='', simpl='',
clusters=48, clusters="37",
opts="",
lv=1.0, lv=1.0,
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1', sector_opts='168H-T-H-B-I-solar+p3-dist1',
planning_horizons=2030, planning_horizons=2030,
) )

View File

@ -12,9 +12,11 @@ import xarray as xr
import pypsa import pypsa
import yaml import yaml
from prepare_sector_network import prepare_costs from prepare_sector_network import prepare_costs, define_spatial
from helper import override_component_attrs from helper import override_component_attrs
from types import SimpleNamespace
spatial = SimpleNamespace()
def add_build_year_to_new_assets(n, baseyear): def add_build_year_to_new_assets(n, baseyear):
""" """
@ -28,7 +30,7 @@ def add_build_year_to_new_assets(n, baseyear):
# Give assets with lifetimes and no build year the build year baseyear # Give assets with lifetimes and no build year the build year baseyear
for c in n.iterate_components(["Link", "Generator", "Store"]): for c in n.iterate_components(["Link", "Generator", "Store"]):
assets = c.df.index[~c.df.lifetime.isna() & c.df.build_year==0] assets = c.df.index[(c.df.lifetime!=np.inf) & (c.df.build_year==0)]
c.df.loc[assets, "build_year"] = baseyear c.df.loc[assets, "build_year"] = baseyear
# add -baseyear to name # add -baseyear to name
@ -201,6 +203,11 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
suffix = '-ac' if generator == 'offwind' else '' suffix = '-ac' if generator == 'offwind' else ''
name_suffix = f' {generator}{suffix}-{baseyear}' name_suffix = f' {generator}{suffix}-{baseyear}'
# to consider electricity grid connection costs or a split between
# solar utility and rooftop as well, rather take cost assumptions
# from existing network than from the cost database
capital_cost = n.generators.loc[n.generators.carrier==generator+suffix, "capital_cost"].mean()
if 'm' in snakemake.wildcards.clusters: if 'm' in snakemake.wildcards.clusters:
for ind in capacity.index: for ind in capacity.index:
@ -220,7 +227,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
carrier=generator, carrier=generator,
p_nom=capacity[ind] / len(inv_ind), # split among regions in a country p_nom=capacity[ind] / len(inv_ind), # split among regions in a country
marginal_cost=costs.at[generator,'VOM'], marginal_cost=costs.at[generator,'VOM'],
capital_cost=costs.at[generator,'fixed'], capital_cost=capital_cost,
efficiency=costs.at[generator, 'efficiency'], efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu, p_max_pu=p_max_pu,
build_year=grouping_year, build_year=grouping_year,
@ -238,7 +245,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
carrier=generator, carrier=generator,
p_nom=capacity, p_nom=capacity,
marginal_cost=costs.at[generator, 'VOM'], marginal_cost=costs.at[generator, 'VOM'],
capital_cost=costs.at[generator, 'fixed'], capital_cost=capital_cost,
efficiency=costs.at[generator, 'efficiency'], efficiency=costs.at[generator, 'efficiency'],
p_max_pu=p_max_pu.rename(columns=n.generators.bus), p_max_pu=p_max_pu.rename(columns=n.generators.bus),
build_year=grouping_year, build_year=grouping_year,
@ -246,11 +253,14 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
) )
else: else:
bus0 = vars(spatial)[carrier[generator]].nodes
if "EU" not in vars(spatial)[carrier[generator]].locations:
bus0 = bus0.intersection(capacity.index + " gas")
n.madd("Link", n.madd("Link",
capacity.index, capacity.index,
suffix= " " + generator +"-" + str(grouping_year), suffix= " " + generator +"-" + str(grouping_year),
bus0="EU " + carrier[generator], bus0=bus0,
bus1=capacity.index, bus1=capacity.index,
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=generator, carrier=generator,
@ -399,10 +409,11 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
lifetime=costs.at[costs_name, 'lifetime'] lifetime=costs.at[costs_name, 'lifetime']
) )
n.madd("Link", n.madd("Link",
nodes[name], nodes[name],
suffix= f" {name} gas boiler-{grouping_year}", suffix= f" {name} gas boiler-{grouping_year}",
bus0="EU gas", bus0=spatial.gas.nodes,
bus1=nodes[name] + " " + name + " heat", bus1=nodes[name] + " " + name + " heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=name + " gas boiler", carrier=name + " gas boiler",
@ -417,7 +428,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
n.madd("Link", n.madd("Link",
nodes[name], nodes[name],
suffix=f" {name} oil boiler-{grouping_year}", suffix=f" {name} oil boiler-{grouping_year}",
bus0="EU oil", bus0=spatial.oil.nodes,
bus1=nodes[name] + " " + name + " heat", bus1=nodes[name] + " " + name + " heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=name + " oil boiler", carrier=name + " oil boiler",
@ -436,17 +447,17 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
threshold = snakemake.config['existing_capacities']['threshold_capacity'] threshold = snakemake.config['existing_capacities']['threshold_capacity']
n.mremove("Link", [index for index in n.links.index.to_list() if str(grouping_year) in index and n.links.p_nom[index] < threshold]) n.mremove("Link", [index for index in n.links.index.to_list() if str(grouping_year) in index and n.links.p_nom[index] < threshold])
#%%
if __name__ == "__main__": if __name__ == "__main__":
if 'snakemake' not in globals(): if 'snakemake' not in globals():
from helper import mock_snakemake from helper import mock_snakemake
snakemake = mock_snakemake( snakemake = mock_snakemake(
'add_existing_baseyear', 'add_existing_baseyear',
simpl='', simpl='',
clusters=45, clusters="37",
lv=1.0, lv=1.0,
opts='', opts='',
sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1', sector_opts='168H-T-H-B-I-solar+p3-dist1',
planning_horizons=2020, planning_horizons=2020,
) )
@ -459,7 +470,8 @@ if __name__ == "__main__":
overrides = override_component_attrs(snakemake.input.overrides) overrides = override_component_attrs(snakemake.input.overrides)
n = pypsa.Network(snakemake.input.network, override_component_attrs=overrides) n = pypsa.Network(snakemake.input.network, override_component_attrs=overrides)
# define spatial resolution of carriers
spatial = define_spatial(n.buses[n.buses.carrier=="AC"].index, options)
add_build_year_to_new_assets(n, baseyear) add_build_year_to_new_assets(n, baseyear)
Nyears = n.snapshot_weightings.generators.sum() / 8760. Nyears = n.snapshot_weightings.generators.sum() / 8760.
@ -471,7 +483,7 @@ if __name__ == "__main__":
snakemake.config['costs']['lifetime'] snakemake.config['costs']['lifetime']
) )
grouping_years=snakemake.config['existing_capacities']['grouping_years'] grouping_years = snakemake.config['existing_capacities']['grouping_years']
add_power_capacities_installed_before_baseyear(n, grouping_years, costs, baseyear) add_power_capacities_installed_before_baseyear(n, grouping_years, costs, baseyear)
if "H" in opts: if "H" in opts:

View File

@ -223,6 +223,26 @@ def plot_map(network, components=["links", "stores", "storage_units", "generator
bbox_inches="tight" bbox_inches="tight"
) )
def group_pipes(df, drop_direction=False):
"""Group pipes which connect same buses and return overall capacity.
"""
if drop_direction:
positive_order = df.bus0 < df.bus1
df_p = df[positive_order]
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
df_n = df[~positive_order].rename(columns=swap_buses)
df = pd.concat([df_p, df_n])
# there are pipes for each investment period rename to AC buses name for plotting
df.index = df.apply(
lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}",
axis=1
)
# group pipe lines connecting the same buses and rename them for plotting
pipe_capacity = df["p_nom_opt"].groupby(level=0).sum()
return pipe_capacity
def plot_h2_map(network): def plot_h2_map(network):
@ -235,7 +255,7 @@ def plot_h2_map(network):
bus_size_factor = 1e5 bus_size_factor = 1e5
linewidth_factor = 1e4 linewidth_factor = 1e4
# MW below which not drawn # MW below which not drawn
line_lower_threshold = 1e3 line_lower_threshold = 1e2
# Drop non-electric buses so they don't clutter the plot # Drop non-electric buses so they don't clutter the plot
n.buses.drop(n.buses.index[n.buses.carrier != "AC"], inplace=True) n.buses.drop(n.buses.index[n.buses.carrier != "AC"], inplace=True)
@ -246,28 +266,20 @@ def plot_h2_map(network):
# make a fake MultiIndex so that area is correct for legend # make a fake MultiIndex so that area is correct for legend
bus_sizes.rename(index=lambda x: x.replace(" H2", ""), level=0, inplace=True) bus_sizes.rename(index=lambda x: x.replace(" H2", ""), level=0, inplace=True)
# drop all links which are not H2 pipelines
n.links.drop(n.links.index[~n.links.carrier.str.contains("H2 pipeline")], inplace=True) n.links.drop(n.links.index[~n.links.carrier.str.contains("H2 pipeline")], inplace=True)
h2_new = n.links.loc[n.links.carrier=="H2 pipeline", "p_nom_opt"] h2_new = n.links.loc[n.links.carrier=="H2 pipeline"]
h2_retro = n.links.loc[n.links.carrier=='H2 pipeline retrofitted'] h2_retro = n.links.loc[n.links.carrier=='H2 pipeline retrofitted']
# sum capacitiy for pipelines from different investment periods
h2_new = group_pipes(h2_new)
h2_retro = group_pipes(h2_retro, drop_direction=True).reindex(h2_new.index).fillna(0)
positive_order = h2_retro.bus0 < h2_retro.bus1
h2_retro_p = h2_retro[positive_order]
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
h2_retro_n = h2_retro[~positive_order].rename(columns=swap_buses)
h2_retro = pd.concat([h2_retro_p, h2_retro_n])
h2_retro.index = h2_retro.apply(
lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}",
axis=1
)
h2_retro = h2_retro["p_nom_opt"]
n.links.rename(index=lambda x: x.split("-2")[0], inplace=True)
n.links = n.links.groupby(level=0).first()
link_widths_total = (h2_new + h2_retro) / linewidth_factor link_widths_total = (h2_new + h2_retro) / linewidth_factor
link_widths_total = link_widths_total.groupby(level=0).sum().reindex(n.links.index).fillna(0.) link_widths_total = link_widths_total.reindex(n.links.index).fillna(0.)
link_widths_total[n.links.p_nom_opt < line_lower_threshold] = 0. link_widths_total[n.links.p_nom_opt < line_lower_threshold] = 0.
retro = n.links.p_nom_opt.where(n.links.carrier=='H2 pipeline retrofitted', other=0.) retro = n.links.p_nom_opt.where(n.links.carrier=='H2 pipeline retrofitted', other=0.)
@ -695,11 +707,11 @@ if __name__ == "__main__":
snakemake = mock_snakemake( snakemake = mock_snakemake(
'plot_network', 'plot_network',
simpl='', simpl='',
clusters=45, clusters="45",
lv=1.5, lv=1.0,
opts='', opts='',
sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1', sector_opts='168H-T-H-B-I-A-solar+p3-dist1',
planning_horizons=2030, planning_horizons="2050",
) )
overrides = override_component_attrs(snakemake.input.overrides) overrides = override_component_attrs(snakemake.input.overrides)

View File

@ -28,7 +28,7 @@ from types import SimpleNamespace
spatial = SimpleNamespace() spatial = SimpleNamespace()
def define_spatial(nodes): def define_spatial(nodes, options):
""" """
Namespace for spatial Namespace for spatial
@ -38,7 +38,6 @@ def define_spatial(nodes):
""" """
global spatial global spatial
global options
spatial.nodes = nodes spatial.nodes = nodes
@ -95,6 +94,28 @@ def define_spatial(nodes):
spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes) spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes)
# oil
spatial.oil = SimpleNamespace()
spatial.oil.nodes = ["EU oil"]
spatial.oil.locations = ["EU"]
# uranium
spatial.uranium = SimpleNamespace()
spatial.uranium.nodes = ["EU uranium"]
spatial.uranium.locations = ["EU"]
# coal
spatial.coal = SimpleNamespace()
spatial.coal.nodes = ["EU coal"]
spatial.coal.locations = ["EU"]
# lignite
spatial.lignite = SimpleNamespace()
spatial.lignite.nodes = ["EU lignite"]
spatial.lignite.locations = ["EU"]
return spatial
from types import SimpleNamespace from types import SimpleNamespace
spatial = SimpleNamespace() spatial = SimpleNamespace()
@ -353,7 +374,8 @@ def add_carrier_buses(n, carrier, nodes=None):
""" """
if nodes is None: if nodes is None:
nodes = ["EU " + carrier] nodes = vars(spatial)[carrier].nodes
location = vars(spatial)[carrier].locations
# skip if carrier already exists # skip if carrier already exists
if carrier in n.carriers.index: if carrier in n.carriers.index:
@ -366,7 +388,7 @@ def add_carrier_buses(n, carrier, nodes=None):
n.madd("Bus", n.madd("Bus",
nodes, nodes,
location=nodes.str.replace(" " + carrier, ""), location=location,
carrier=carrier carrier=carrier
) )
@ -807,10 +829,8 @@ def add_generation(n, costs):
for generator, carrier in conventionals.items(): for generator, carrier in conventionals.items():
if carrier == 'gas':
carrier_nodes = spatial.gas.nodes carrier_nodes = vars(spatial)[carrier].nodes
else:
carrier_nodes = ["EU " + carrier]
add_carrier_buses(n, carrier, carrier_nodes) add_carrier_buses(n, carrier, carrier_nodes)
@ -1072,7 +1092,8 @@ def add_storage_and_grids(n, costs):
e_nom_max=h2_caverns.values, e_nom_max=h2_caverns.values,
e_cyclic=True, e_cyclic=True,
carrier="H2 Store", carrier="H2 Store",
capital_cost=h2_capital_cost capital_cost=h2_capital_cost,
lifetime=costs.at["hydrogen storage underground", "lifetime"]
) )
# hydrogen stored overground (where not already underground) # hydrogen stored overground (where not already underground)
@ -1157,9 +1178,9 @@ def add_storage_and_grids(n, costs):
# apply k_edge_augmentation weighted by length of complement edges # apply k_edge_augmentation weighted by length of complement edges
k_edge = options.get("gas_network_connectivity_upgrade", 3) k_edge = options.get("gas_network_connectivity_upgrade", 3)
augmentation = k_edge_augmentation(G, k_edge, avail=complement_edges.values) augmentation = list(k_edge_augmentation(G, k_edge, avail=complement_edges.values))
if list(augmentation): if augmentation:
new_gas_pipes = pd.DataFrame(augmentation, columns=["bus0", "bus1"]) new_gas_pipes = pd.DataFrame(augmentation, columns=["bus0", "bus1"])
new_gas_pipes["length"] = new_gas_pipes.apply(haversine, axis=1) new_gas_pipes["length"] = new_gas_pipes.apply(haversine, axis=1)
@ -1421,10 +1442,10 @@ def add_land_transport(n, costs):
if ice_share > 0: if ice_share > 0:
if "EU oil" not in n.buses.index: if "oil" not in n.buses.carrier.unique():
n.add("Bus", n.madd("Bus",
"EU oil", spatial.oil.nodes,
location="EU", location=spatial.oil.locations,
carrier="oil" carrier="oil"
) )
@ -1433,7 +1454,7 @@ def add_land_transport(n, costs):
n.madd("Load", n.madd("Load",
nodes, nodes,
suffix=" land transport oil", suffix=" land transport oil",
bus="EU oil", bus=spatial.oil.nodes,
carrier="land transport oil", carrier="land transport oil",
p_set=ice_share / ice_efficiency * transport[nodes] p_set=ice_share / ice_efficiency * transport[nodes]
) )
@ -2099,7 +2120,7 @@ def add_industry(n, costs):
n.madd("Load", n.madd("Load",
nodes, nodes,
suffix=" shipping oil", suffix=" shipping oil",
bus="EU oil", bus=spatial.oil.nodes,
carrier="shipping oil", carrier="shipping oil",
p_set=p_set p_set=p_set
) )
@ -2113,30 +2134,29 @@ def add_industry(n, costs):
p_set=-co2 p_set=-co2
) )
if "EU oil" not in n.buses.index: if "oil" not in n.buses.carrier.unique():
n.madd("Bus",
n.add("Bus", spatial.oil.nodes,
"EU oil", location=spatial.oil.locations,
location="EU",
carrier="oil" carrier="oil"
) )
if "EU oil Store" not in n.stores.index: if "oil" not in n.stores.carrier.unique():
#could correct to e.g. 0.001 EUR/kWh * annuity and O&M #could correct to e.g. 0.001 EUR/kWh * annuity and O&M
n.add("Store", n.madd("Store",
"EU oil Store", [oil_bus + " Store" for oil_bus in spatial.oil.nodes],
bus="EU oil", bus=spatial.oil.nodes,
e_nom_extendable=True, e_nom_extendable=True,
e_cyclic=True, e_cyclic=True,
carrier="oil", carrier="oil",
) )
if "EU oil" not in n.generators.index: if "oil" not in n.generators.carrier.unique():
n.add("Generator", n.madd("Generator",
"EU oil", spatial.oil.nodes,
bus="EU oil", bus=spatial.oil.nodes,
p_nom_extendable=True, p_nom_extendable=True,
carrier="oil", carrier="oil",
marginal_cost=costs.at["oil", 'fuel'] marginal_cost=costs.at["oil", 'fuel']
@ -2151,7 +2171,7 @@ def add_industry(n, costs):
n.madd("Link", n.madd("Link",
nodes_heat[name] + f" {name} oil boiler", nodes_heat[name] + f" {name} oil boiler",
p_nom_extendable=True, p_nom_extendable=True,
bus0="EU oil", bus0=spatial.oil.nodes,
bus1=nodes_heat[name] + f" {name} heat", bus1=nodes_heat[name] + f" {name} heat",
bus2="co2 atmosphere", bus2="co2 atmosphere",
carrier=f"{name} oil boiler", carrier=f"{name} oil boiler",
@ -2164,7 +2184,7 @@ def add_industry(n, costs):
n.madd("Link", n.madd("Link",
nodes + " Fischer-Tropsch", nodes + " Fischer-Tropsch",
bus0=nodes + " H2", bus0=nodes + " H2",
bus1="EU oil", bus1=spatial.oil.nodes,
bus2=spatial.co2.nodes, bus2=spatial.co2.nodes,
carrier="Fischer-Tropsch", carrier="Fischer-Tropsch",
efficiency=costs.at["Fischer-Tropsch", 'efficiency'], efficiency=costs.at["Fischer-Tropsch", 'efficiency'],
@ -2174,9 +2194,9 @@ def add_industry(n, costs):
lifetime=costs.at['Fischer-Tropsch', 'lifetime'] lifetime=costs.at['Fischer-Tropsch', 'lifetime']
) )
n.add("Load", n.madd("Load",
"naphtha for industry", ["naphtha for industry"],
bus="EU oil", bus=spatial.oil.nodes,
carrier="naphtha for industry", carrier="naphtha for industry",
p_set=industrial_demand.loc[nodes, "naphtha"].sum() / 8760 p_set=industrial_demand.loc[nodes, "naphtha"].sum() / 8760
) )
@ -2184,9 +2204,9 @@ def add_industry(n, costs):
all_aviation = ["total international aviation", "total domestic aviation"] all_aviation = ["total international aviation", "total domestic aviation"]
p_set = nodal_energy_totals.loc[nodes, all_aviation].sum(axis=1).sum() * 1e6 / 8760 p_set = nodal_energy_totals.loc[nodes, all_aviation].sum(axis=1).sum() * 1e6 / 8760
n.add("Load", n.madd("Load",
"kerosene for aviation", ["kerosene for aviation"],
bus="EU oil", bus=spatial.oil.nodes,
carrier="kerosene for aviation", carrier="kerosene for aviation",
p_set=p_set p_set=p_set
) )
@ -2339,7 +2359,7 @@ def add_agriculture(n, costs):
n.add("Load", n.add("Load",
"agriculture machinery oil", "agriculture machinery oil",
bus="EU oil", bus=spatial.oil.nodes,
carrier="agriculture machinery oil", carrier="agriculture machinery oil",
p_set=ice_share * machinery_nodal_energy.sum() * 1e6 / 8760 p_set=ice_share * machinery_nodal_energy.sum() * 1e6 / 8760
) )
@ -2443,7 +2463,7 @@ if __name__ == "__main__":
patch_electricity_network(n) patch_electricity_network(n)
define_spatial(pop_layout.index) spatial = define_spatial(pop_layout.index, options)
if snakemake.config["foresight"] == 'myopic': if snakemake.config["foresight"] == 'myopic':

View File

@ -185,28 +185,31 @@ def add_chp_constraints(n):
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure') define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
def basename(x):
return x.split("-2")[0]
def add_pipe_retrofit_constraint(n): def add_pipe_retrofit_constraint(n):
"""Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.""" """Add constraint for retrofitting existing CH4 pipelines to H2 pipelines."""
gas_pipes_i = n.links[n.links.carrier=="gas pipeline"].index gas_pipes_i = n.links.query("carrier == 'gas pipeline' and p_nom_extendable").index
h2_retrofitted_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].index h2_retrofitted_i = n.links.query("carrier == 'H2 pipeline retrofitted' and p_nom_extendable").index
if h2_retrofitted_i.empty or gas_pipes_i.empty: return if h2_retrofitted_i.empty or gas_pipes_i.empty: return
link_p_nom = get_var(n, "Link", "p_nom") link_p_nom = get_var(n, "Link", "p_nom")
pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom']
CH4_per_H2 = 1 / n.config["sector"]["H2_retrofit_capacity_per_CH4"] CH4_per_H2 = 1 / n.config["sector"]["H2_retrofit_capacity_per_CH4"]
fr = "H2 pipeline retrofitted" fr = "H2 pipeline retrofitted"
to = "gas pipeline" to = "gas pipeline"
pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'].rename(basename)
lhs = linexpr( lhs = linexpr(
(CH4_per_H2, link_p_nom.loc[h2_retrofitted_i].rename(index=lambda x: x.replace(fr, to))), (CH4_per_H2, link_p_nom.loc[h2_retrofitted_i].rename(index=lambda x: x.replace(fr, to))),
(1, link_p_nom.loc[gas_pipes_i]) (1, link_p_nom.loc[gas_pipes_i])
) )
lhs.rename(basename, inplace=True)
define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit') define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit')
@ -277,10 +280,11 @@ if __name__ == "__main__":
snakemake = mock_snakemake( snakemake = mock_snakemake(
'solve_network', 'solve_network',
simpl='', simpl='',
clusters=48, opts="",
clusters="37",
lv=1.0, lv=1.0,
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1', sector_opts='168H-T-H-B-I-A-solar+p3-dist1',
planning_horizons=2050, planning_horizons="2030",
) )
logging.basicConfig(filename=snakemake.log.python, logging.basicConfig(filename=snakemake.log.python,