From 372473d76cdbca598517be253b50ce9d138d72e5 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 11:32:31 +0100 Subject: [PATCH 01/18] update mocksnakemake --- scripts/add_existing_baseyear.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index bb35e378..53f986c9 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -159,7 +159,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas inv_busmap = {} for k, v in busmap.iteritems(): inv_busmap[v] = inv_busmap.get(v, []) + [k] - + clustermaps = busmap_s.map(busmap) clustermaps.index = clustermaps.index.astype(int) @@ -197,7 +197,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas capacity = capacity[capacity > snakemake.config['existing_capacities']['threshold_capacity']] if generator in ['solar', 'onwind', 'offwind']: - + suffix = '-ac' if generator == 'offwind' else '' name_suffix = f' {generator}{suffix}-{baseyear}' @@ -213,7 +213,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas p_max_pu = n.generators_t.p_max_pu[[i + name_suffix for i in inv_ind]] p_max_pu.columns=[i + name_suffix for i in inv_ind ] - + n.madd("Generator", [i + name_suffix for i in inv_ind], bus=ind, @@ -436,17 +436,17 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years 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]) - +#%% if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake snakemake = mock_snakemake( 'add_existing_baseyear', simpl='', - clusters=45, + clusters="45", lv=1.0, 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=2020, ) From 953e1c883e335bbf885e5a35c9e509d745249d83 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 11:38:25 +0100 Subject: [PATCH 02/18] adjust functions to new default lifetime np.inf instead of previous NaN --- scripts/add_brownfield.py | 7 ++++--- scripts/add_existing_baseyear.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 0952c752..0164e64a 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -8,6 +8,7 @@ idx = pd.IndexSlice import pypsa import yaml +import numpy as np from add_existing_baseyear import add_build_year_to_new_assets from helper import override_component_attrs @@ -25,7 +26,7 @@ def add_brownfield(n, n_p, year): # CO2 or global EU values since these are already in n n_p.mremove( c.name, - c.df.index[c.df.lifetime.isna()] + c.df.index[c.df.lifetime==np.inf] ) # remove assets whose build_year + lifetime < year @@ -44,7 +45,7 @@ def add_brownfield(n, n_p, year): )] threshold = snakemake.config['existing_capacities']['threshold_capacity'] - + if not chp_heat.empty: threshold_chp_heat = (threshold * c.df.efficiency[chp_heat.str.replace("heat", "electric")].values @@ -55,7 +56,7 @@ def add_brownfield(n, n_p, year): c.name, chp_heat[c.df.loc[chp_heat, attr + "_nom_opt"] < threshold_chp_heat] ) - + n_p.mremove( c.name, c.df.index[c.df[attr + "_nom_extendable"] & ~c.df.index.isin(chp_heat) & (c.df[attr + "_nom_opt"] < threshold)] diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 53f986c9..6955fb6c 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -28,7 +28,7 @@ def add_build_year_to_new_assets(n, baseyear): # Give assets with lifetimes and no build year the build year baseyear 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 # add -baseyear to name From b5aa4234ef79490b7e4a45f3c92704fa1859a55d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 12:09:02 +0100 Subject: [PATCH 03/18] adjust capital cost of existing renewables to consider e.g. electricity grid connection costs as well --- scripts/add_existing_baseyear.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 6955fb6c..07eb3cda 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -201,6 +201,11 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas suffix = '-ac' if generator == 'offwind' else '' 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[n.generators.carrier==generator+suffix].capital_cost.mean() + if 'm' in snakemake.wildcards.clusters: for ind in capacity.index: @@ -220,7 +225,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas carrier=generator, p_nom=capacity[ind] / len(inv_ind), # split among regions in a country marginal_cost=costs.at[generator,'VOM'], - capital_cost=costs.at[generator,'fixed'], + capital_cost=capital_cost, efficiency=costs.at[generator, 'efficiency'], p_max_pu=p_max_pu, build_year=grouping_year, @@ -238,7 +243,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas carrier=generator, p_nom=capacity, marginal_cost=costs.at[generator, 'VOM'], - capital_cost=costs.at[generator, 'fixed'], + capital_cost=capital_cost, efficiency=costs.at[generator, 'efficiency'], p_max_pu=p_max_pu.rename(columns=n.generators.bus), build_year=grouping_year, @@ -471,7 +476,7 @@ if __name__ == "__main__": 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) if "H" in opts: From c9ce9190a726086c5c33147e05b7002d4998691d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 12:40:17 +0100 Subject: [PATCH 04/18] adjust to spatital resolved gas nodes --- scripts/add_existing_baseyear.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 07eb3cda..74019b8c 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -251,11 +251,15 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ) else: + bus0 = n.buses[(n.buses.carrier==carrier[generator])].index + if any(n.buses.loc[bus0,"location"]!="EU"): + bus0 = n.buses[n.buses.location.isin(capacity.index) & + (n.buses.carrier==carrier[generator])].index n.madd("Link", capacity.index, suffix= " " + generator +"-" + str(grouping_year), - bus0="EU " + carrier[generator], + bus0=bus0, bus1=capacity.index, bus2="co2 atmosphere", carrier=generator, @@ -404,10 +408,15 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years lifetime=costs.at[costs_name, 'lifetime'] ) + bus0 = n.buses[(n.buses.carrier=="gas")].index + if any(n.buses.loc[bus0,"location"]!="EU"): + bus0 = n.buses[n.buses.location.isin(nodal_df[f'{heat_type} gas boiler'][nodes[name]].index) & + (n.buses.carrier=="gas")].index + n.madd("Link", nodes[name], suffix= f" {name} gas boiler-{grouping_year}", - bus0="EU gas", + bus0=bus0, bus1=nodes[name] + " " + name + " heat", bus2="co2 atmosphere", carrier=name + " gas boiler", From 030bf96d329a48a84e8153b0adfef21395d2cabb Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 12:45:33 +0100 Subject: [PATCH 05/18] update mocksnakemake --- scripts/add_brownfield.py | 5 +++-- scripts/add_existing_baseyear.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 0164e64a..ddb0a626 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -83,9 +83,10 @@ if __name__ == "__main__": snakemake = mock_snakemake( 'add_brownfield', simpl='', - clusters=48, + clusters="45", + opts="", lv=1.0, - sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1', + sector_opts='Co2L0-168H-T-H-B-I-A-solar3-dist1', planning_horizons=2030, ) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 74019b8c..fae64904 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -450,7 +450,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years 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]) -#%% + if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake From 5b30f0ece6edc5c11d089ee62e04a1dac479f213 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 16:53:37 +0100 Subject: [PATCH 06/18] avoid doubling of the existing natural gas grid --- scripts/add_brownfield.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index ddb0a626..937ba409 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -76,7 +76,12 @@ def add_brownfield(n, n_p, year): for tattr in n.component_attrs[c.name].index[selection]: n.import_series_from_dataframe(c.pnl[tattr], c.name, tattr) + # deal with gas network + pipe_carrier = ['gas pipeline'] + to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year!=year) + n.mremove("Link", n.links.loc[to_drop].index) +#%% if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake @@ -86,7 +91,7 @@ if __name__ == "__main__": clusters="45", opts="", lv=1.0, - sector_opts='Co2L0-168H-T-H-B-I-A-solar3-dist1', + sector_opts='168H-T-H-B-I-A-solar+p3-dist1', planning_horizons=2030, ) From 9f8b54a3cea63f67b08c81fd85cc15a9edc2844c Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 16:59:14 +0100 Subject: [PATCH 07/18] adjust pipe retrofitting constraint to work with myopic --- scripts/solve_network.py | 51 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index e23f3437..5108e2ee 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -33,14 +33,14 @@ def _add_land_use_constraint(n): existing = n.generators.loc[n.generators.carrier==carrier,"p_nom"].groupby(n.generators.bus.map(n.buses.location)).sum() existing.index += " " + carrier + "-" + snakemake.wildcards.planning_horizons n.generators.loc[existing.index,"p_nom_max"] -= existing - + n.generators.p_nom_max.clip(lower=0, inplace=True) def _add_land_use_constraint_m(n): # if generators clustering is lower than network clustering, land_use accounting is at generators clusters - planning_horizons = snakemake.config["scenario"]["planning_horizons"] + planning_horizons = snakemake.config["scenario"]["planning_horizons"] grouping_years = snakemake.config["existing_capacities"]["grouping_years"] current_horizon = snakemake.wildcards.planning_horizons @@ -48,9 +48,9 @@ def _add_land_use_constraint_m(n): existing = n.generators.loc[n.generators.carrier==carrier,"p_nom"] ind = list(set([i.split(sep=" ")[0] + ' ' + i.split(sep=" ")[1] for i in existing.index])) - + previous_years = [ - str(y) for y in + str(y) for y in planning_horizons + grouping_years if y < int(snakemake.wildcards.planning_horizons) ] @@ -59,13 +59,13 @@ def _add_land_use_constraint_m(n): ind2 = [i for i in ind if i + " " + carrier + "-" + p_year in existing.index] sel_current = [i + " " + carrier + "-" + current_horizon for i in ind2] sel_p_year = [i + " " + carrier + "-" + p_year for i in ind2] - n.generators.loc[sel_current, "p_nom_max"] -= existing.loc[sel_p_year].rename(lambda x: x[:-4] + current_horizon) - + n.generators.loc[sel_current, "p_nom_max"] -= existing.loc[sel_p_year].rename(lambda x: x[:-4] + current_horizon) + n.generators.p_nom_max.clip(lower=0, inplace=True) def prepare_network(n, solve_opts=None): - + if 'clip_p_max_pu' in solve_opts: for df in (n.generators_t.p_max_pu, n.generators_t.p_min_pu, n.storage_units_t.inflow): df.where(df>solve_opts['clip_p_max_pu'], other=0., inplace=True) @@ -189,36 +189,42 @@ def add_chp_constraints(n): def add_pipe_retrofit_constraint(n): """Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.""" - gas_pipes_i = n.links[n.links.carrier=="gas pipeline"].index - h2_retrofitted_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].index + gas_pipes_i = n.links[n.links.carrier=="gas pipeline"].query("p_nom_extendable").index + h2_retrofitted_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].query("p_nom_extendable").index + h2_retrofitted_fixed_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].index.difference(h2_retrofitted_i) if h2_retrofitted_i.empty or gas_pipes_i.empty: return 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"] - fr = "H2 pipeline retrofitted" to = "gas pipeline" + + pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'].rename(index=lambda x: x.split("-2")[0]) + already_retrofitted = (n.links.loc[h2_retrofitted_fixed_i, 'p_nom'] + .rename(index= lambda x: x.split("-2")[0] + .replace(fr, to)).groupby(level=0).sum()) + remaining_capacity = pipe_capacity - CH4_per_H2 * already_retrofitted.reindex(index=pipe_capacity.index).fillna(0) + lhs = linexpr( (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]) ) - define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit') + lhs.rename(index=lambda x: x.split("-2")[0], inplace=True) + define_constraints(n, lhs, "=", remaining_capacity, 'Link', 'pipe_retrofit') def add_co2_sequestration_limit(n, sns): - + co2_stores = n.stores.loc[n.stores.carrier=='co2 stored'].index if co2_stores.empty or ('Store', 'e') not in n.variables.index: return - + vars_final_co2_stored = get_var(n, 'Store', 'e').loc[sns[-1], co2_stores] - + lhs = linexpr((1, vars_final_co2_stored)).sum() limit = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6 @@ -226,7 +232,7 @@ def add_co2_sequestration_limit(n, sns): if not "seq" in o: continue limit = float(o[o.find("seq")+3:]) break - + name = 'co2_sequestration_limit' sense = "<=" @@ -258,7 +264,7 @@ def solve_network(n, config, opts='', **kwargs): if cf_solving.get('skip_iterations', False): network_lopf(n, solver_name=solver_name, solver_options=solver_options, - extra_functionality=extra_functionality, + extra_functionality=extra_functionality, keep_shadowprices=keep_shadowprices, **kwargs) else: ilopf(n, solver_name=solver_name, solver_options=solver_options, @@ -275,12 +281,13 @@ if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake snakemake = mock_snakemake( - 'solve_network', + 'solve_network_myopic', simpl='', - clusters=48, + opts="", + clusters="45", lv=1.0, - sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1', - planning_horizons=2050, + sector_opts='168H-T-H-B-I-A-solar+p3-dist1', + planning_horizons="2030", ) logging.basicConfig(filename=snakemake.log.python, From c40904a72708aadea19761eca64ec6c386d27cf1 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 7 Jan 2022 16:59:48 +0100 Subject: [PATCH 08/18] adjust h2_plot function to work with myopic, since build year is now in links.index --- scripts/plot_network.py | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/scripts/plot_network.py b/scripts/plot_network.py index 9b8cddc3..752459a3 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -235,7 +235,7 @@ def plot_h2_map(network): bus_size_factor = 1e5 linewidth_factor = 1e4 # MW below which not drawn - line_lower_threshold = 1e3 + line_lower_threshold = 1e2 # Drop non-electric buses so they don't clutter the plot n.buses.drop(n.buses.index[n.buses.carrier != "AC"], inplace=True) @@ -249,7 +249,7 @@ def plot_h2_map(network): 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'] @@ -264,10 +264,18 @@ def plot_h2_map(network): axis=1 ) - h2_retro = h2_retro["p_nom_opt"] + h2_new.index = h2_new.apply( + lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}" + ,axis=1) + h2_new = h2_new["p_nom_opt"].groupby(level=0).sum() + h2_retro = h2_retro["p_nom_opt"].groupby(level=0).sum() + h2_retro = h2_retro.groupby(level=0).sum().reindex(h2_new.index).fillna(0) + + 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 = 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. retro = n.links.p_nom_opt.where(n.links.carrier=='H2 pipeline retrofitted', other=0.) @@ -281,7 +289,7 @@ def plot_h2_map(network): figsize=(7, 6), subplot_kw={"projection": ccrs.PlateCarree()} ) - + n.plot( bus_sizes=bus_sizes, bus_colors=snakemake.config['plotting']['tech_colors'], @@ -365,7 +373,7 @@ def plot_ch4_map(network): # Drop non-electric buses so they don't clutter the plot n.buses.drop(n.buses.index[n.buses.carrier != "AC"], inplace=True) - fossil_gas_i = n.generators[n.generators.carrier=="gas"].index + fossil_gas_i = n.generators[n.generators.carrier=="gas"].index fossil_gas = n.generators_t.p.loc[:,fossil_gas_i].mul(n.snapshot_weightings.generators, axis=0).sum().groupby(n.generators.loc[fossil_gas_i,"bus"]).sum() / bus_size_factor fossil_gas.rename(index=lambda x: x.replace(" gas", ""), inplace=True) fossil_gas = fossil_gas.reindex(n.buses.index).fillna(0) @@ -390,10 +398,10 @@ def plot_ch4_map(network): to_remove = n.links.index[~n.links.carrier.str.contains("gas pipeline")] n.links.drop(to_remove, inplace=True) - link_widths_rem = n.links.p_nom_opt / linewidth_factor + link_widths_rem = n.links.p_nom_opt / linewidth_factor link_widths_rem[n.links.p_nom_opt < line_lower_threshold] = 0. - link_widths_orig = n.links.p_nom / linewidth_factor + link_widths_orig = n.links.p_nom / linewidth_factor link_widths_orig[n.links.p_nom < line_lower_threshold] = 0. max_usage = n.links_t.p0.abs().max(axis=0) @@ -422,7 +430,7 @@ def plot_ch4_map(network): link_colors='lightgrey', link_widths=link_widths_orig, branch_components=["Link"], - ax=ax, + ax=ax, **map_opts ) @@ -452,7 +460,7 @@ def plot_ch4_map(network): facecolor='grey' ) labels = ["{} TWh".format(s) for s in (10, 100)] - + l2 = ax.legend( handles, labels, loc="upper left", @@ -462,7 +470,7 @@ def plot_ch4_map(network): title='gas generation', handler_map=make_handler_map_to_scale_circles_as_in(ax) ) - + ax.add_artist(l2) handles = [] @@ -471,7 +479,7 @@ def plot_ch4_map(network): for s in (50, 10): handles.append(plt.Line2D([0], [0], color="grey", linewidth=s * 1e3 / linewidth_factor)) labels.append("{} GW".format(s)) - + l1_1 = ax.legend( handles, labels, loc="upper left", @@ -481,7 +489,7 @@ def plot_ch4_map(network): handletextpad=1.5, title='gas pipeline used capacity' ) - + ax.add_artist(l1_1) fig.savefig( @@ -695,11 +703,11 @@ if __name__ == "__main__": snakemake = mock_snakemake( 'plot_network', simpl='', - clusters=45, - lv=1.5, + clusters="45", + lv=1.0, opts='', - sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1', - planning_horizons=2030, + sector_opts='168H-T-H-B-I-A-solar+p3-dist1', + planning_horizons="2050", ) overrides = override_component_attrs(snakemake.input.overrides) From aed81940b30aa0bd452a2b420a85a9206f8ad07d Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:15:14 +0100 Subject: [PATCH 09/18] add_existing_baseyear.py: Style improvement Co-authored-by: Fabian Neumann --- scripts/add_existing_baseyear.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index fae64904..9fbac0ce 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -204,7 +204,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas # 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[n.generators.carrier==generator+suffix].capital_cost.mean() + capital_cost = n.generators.loc[n.generators.carrier==generator+suffix, "capital_cost"].mean() if 'm' in snakemake.wildcards.clusters: From 5502943301aaa3126a2575a22db21f0bbcaf18ec Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:15:59 +0100 Subject: [PATCH 10/18] scripts/solve_network.py: Style improvement Co-authored-by: Fabian Neumann --- scripts/solve_network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 5108e2ee..7ea0f064 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -189,9 +189,9 @@ def add_chp_constraints(n): def add_pipe_retrofit_constraint(n): """Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.""" - gas_pipes_i = n.links[n.links.carrier=="gas pipeline"].query("p_nom_extendable").index - h2_retrofitted_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].query("p_nom_extendable").index - h2_retrofitted_fixed_i = n.links[n.links.carrier=='H2 pipeline retrofitted'].index.difference(h2_retrofitted_i) + gas_pipes_i = n.links.query("carrier == 'gas pipeline' and p_nom_extendable").index + h2_retrofitted_i = n.links.query("carrier == 'H2 pipeline retrofitted' and p_nom_extendable").index + h2_retrofitted_fixed_i = n.links.query("carrier == 'H2 pipeline retrofitted' and not p_nom_extendable").index if h2_retrofitted_i.empty or gas_pipes_i.empty: return From 08cef37e7e0c2e3cce74734731ce3fd2a31e284f Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 18 Mar 2022 10:18:02 +0100 Subject: [PATCH 11/18] improve code style --- scripts/plot_network.py | 44 ++++++++++++++++++++++------------------ scripts/solve_network.py | 11 +++++----- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/scripts/plot_network.py b/scripts/plot_network.py index 752459a3..416e4c4e 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -223,6 +223,26 @@ def plot_map(network, components=["links", "stores", "storage_units", "generator 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): @@ -246,31 +266,15 @@ def plot_h2_map(network): # make a fake MultiIndex so that area is correct for legend bus_sizes.rename(index=lambda x: x.replace(" H2", ""), level=0, inplace=True) - + # frop all links which are not H2 pipelines 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"] - 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_new.index = h2_new.apply( - lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}" - ,axis=1) - - h2_new = h2_new["p_nom_opt"].groupby(level=0).sum() - h2_retro = h2_retro["p_nom_opt"].groupby(level=0).sum() - h2_retro = h2_retro.groupby(level=0).sum().reindex(h2_new.index).fillna(0) n.links.rename(index=lambda x: x.split("-2")[0], inplace=True) n.links = n.links.groupby(level=0).first() diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 7ea0f064..46362ad0 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -185,6 +185,8 @@ def add_chp_constraints(n): define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure') +def basename(x): + return x.split("-2")[0] def add_pipe_retrofit_constraint(n): """Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.""" @@ -201,10 +203,9 @@ def add_pipe_retrofit_constraint(n): fr = "H2 pipeline retrofitted" to = "gas pipeline" - pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'].rename(index=lambda x: x.split("-2")[0]) + pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'].rename(basename) already_retrofitted = (n.links.loc[h2_retrofitted_fixed_i, 'p_nom'] - .rename(index= lambda x: x.split("-2")[0] - .replace(fr, to)).groupby(level=0).sum()) + .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) lhs = linexpr( @@ -212,7 +213,7 @@ def add_pipe_retrofit_constraint(n): (1, link_p_nom.loc[gas_pipes_i]) ) - lhs.rename(index=lambda x: x.split("-2")[0], inplace=True) + lhs.rename(basename, inplace=True) define_constraints(n, lhs, "=", remaining_capacity, 'Link', 'pipe_retrofit') @@ -284,7 +285,7 @@ if __name__ == "__main__": 'solve_network_myopic', simpl='', opts="", - clusters="45", + clusters="37", lv=1.0, sector_opts='168H-T-H-B-I-A-solar+p3-dist1', planning_horizons="2030", From cfb5a797cbfbd3d14f59b23516a318b2e7d4da4d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 18 Mar 2022 10:18:24 +0100 Subject: [PATCH 12/18] start to extend define_spatial for conventional carriers --- scripts/add_existing_baseyear.py | 7 +++++-- scripts/prepare_sector_network.py | 31 +++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 9fbac0ce..2ccda05b 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -12,9 +12,11 @@ import xarray as xr import pypsa 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 types import SimpleNamespace +spatial = SimpleNamespace() def add_build_year_to_new_assets(n, baseyear): """ @@ -473,7 +475,8 @@ if __name__ == "__main__": overrides = override_component_attrs(snakemake.input.overrides) n = pypsa.Network(snakemake.input.network, override_component_attrs=overrides) - + # define spatial resolution of carriers + define_spatial(n.buses[n.buses.carrier=="AC"].index, options) add_build_year_to_new_assets(n, baseyear) Nyears = n.snapshot_weightings.generators.sum() / 8760. diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index bd1c7038..1e41a954 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -28,7 +28,7 @@ from types import SimpleNamespace spatial = SimpleNamespace() -def define_spatial(nodes): +def define_spatial(nodes, options): """ Namespace for spatial @@ -38,7 +38,6 @@ def define_spatial(nodes): """ global spatial - global options spatial.nodes = nodes @@ -73,7 +72,7 @@ def define_spatial(nodes): spatial.co2.vents = ["co2 vent"] spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes) - + # gas spatial.gas = SimpleNamespace() @@ -95,6 +94,26 @@ def define_spatial(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"] + from types import SimpleNamespace spatial = SimpleNamespace() @@ -1049,7 +1068,7 @@ def add_storage_and_grids(n, costs): # only use sites with at least 2 TWh potential h2_caverns = h2_caverns[h2_caverns > 2] - + # convert TWh to MWh h2_caverns = h2_caverns * 1e6 @@ -1119,7 +1138,7 @@ def add_storage_and_grids(n, costs): carrier="gas pipeline", lifetime=costs.at['CH4 (g) pipeline', 'lifetime'] ) - + # remove fossil generators where there is neither # production, LNG terminal, nor entry-point beyond system scope @@ -2438,7 +2457,7 @@ if __name__ == "__main__": patch_electricity_network(n) - define_spatial(pop_layout.index) + define_spatial(pop_layout.index, options) if snakemake.config["foresight"] == 'myopic': From 4f288834b25881bded5747285fda25f8dff79a9f Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 18 Mar 2022 13:46:09 +0100 Subject: [PATCH 13/18] update overrides align with PyPSA version>=0.18 --- data/override_component_attrs/generators.csv | 5 +++-- data/override_component_attrs/links.csv | 16 ++++++++-------- data/override_component_attrs/stores.csv | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/data/override_component_attrs/generators.csv b/data/override_component_attrs/generators.csv index bd3925fc..4f214396 100644 --- a/data/override_component_attrs/generators.csv +++ b/data/override_component_attrs/generators.csv @@ -1,3 +1,4 @@ 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) +lifetime,float,years,inf,lifetime,Input (optional) +build_year,int,year ,0,build year,Input (optional) diff --git a/data/override_component_attrs/links.csv b/data/override_component_attrs/links.csv index 709a9211..0fc2747a 100644 --- a/data/override_component_attrs/links.csv +++ b/data/override_component_attrs/links.csv @@ -2,12 +2,12 @@ attribute,type,unit,default,description,status bus2,string,n/a,n/a,2nd bus,Input (optional) bus3,string,n/a,n/a,3rd 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) -efficiency3,static or series,per unit,1.,3rd bus efficiency,Input (optional) -efficiency4,static or series,per unit,1.,4th bus efficiency,Input (optional) -p2,series,MW,0.,2nd bus output,Output -p3,series,MW,0.,3rd 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) +efficiency2,static or series,per unit,1,2nd 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) +p2,series,MW,0,2nd bus output,Output +p3,series,MW,0,3rd bus output,Output +p4,series,MW,0,4th bus output,Output 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) diff --git a/data/override_component_attrs/stores.csv b/data/override_component_attrs/stores.csv index 1228fea9..4f214396 100644 --- a/data/override_component_attrs/stores.csv +++ b/data/override_component_attrs/stores.csv @@ -1,4 +1,4 @@ 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) +lifetime,float,years,inf,lifetime,Input (optional) +build_year,int,year ,0,build year,Input (optional) From 71a8bc6c96d59e77e4b55cb46ec86f0b60bb3df1 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 18 Mar 2022 13:46:40 +0100 Subject: [PATCH 14/18] use define spatial for all conventionals, add lifetime for H2 storage --- scripts/add_brownfield.py | 4 +- scripts/add_existing_baseyear.py | 20 ++++----- scripts/prepare_sector_network.py | 73 ++++++++++++++++--------------- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 937ba409..294c1ccf 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -88,10 +88,10 @@ if __name__ == "__main__": snakemake = mock_snakemake( 'add_brownfield', simpl='', - clusters="45", + clusters="37", opts="", lv=1.0, - sector_opts='168H-T-H-B-I-A-solar+p3-dist1', + sector_opts='168H-T-H-B-I-solar+p3-dist1', planning_horizons=2030, ) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 2ccda05b..a2ad99ec 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -253,10 +253,9 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas ) else: - bus0 = n.buses[(n.buses.carrier==carrier[generator])].index - if any(n.buses.loc[bus0,"location"]!="EU"): - bus0 = n.buses[n.buses.location.isin(capacity.index) & - (n.buses.carrier==carrier[generator])].index + bus0 = vars(spatial)[carrier[generator]].nodes + if "EU" not in vars(spatial)[carrier[generator]].locations: + bus0 = bus0.intersection(capacity.index + " gas") n.madd("Link", capacity.index, @@ -410,10 +409,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years lifetime=costs.at[costs_name, 'lifetime'] ) - bus0 = n.buses[(n.buses.carrier=="gas")].index - if any(n.buses.loc[bus0,"location"]!="EU"): - bus0 = n.buses[n.buses.location.isin(nodal_df[f'{heat_type} gas boiler'][nodes[name]].index) & - (n.buses.carrier=="gas")].index + bus0 = vars(spatial)["gas"].nodes n.madd("Link", nodes[name], @@ -452,17 +448,17 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years 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]) - +#%% if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake snakemake = mock_snakemake( 'add_existing_baseyear', simpl='', - clusters="45", + clusters="37", lv=1.0, opts='', - sector_opts='168H-T-H-B-I-A-solar+p3-dist1', + sector_opts='Co2L0-168H-T-H-B-I-solar+p3-dist1', planning_horizons=2020, ) @@ -476,7 +472,7 @@ if __name__ == "__main__": overrides = override_component_attrs(snakemake.input.overrides) n = pypsa.Network(snakemake.input.network, override_component_attrs=overrides) # define spatial resolution of carriers - define_spatial(n.buses[n.buses.carrier=="AC"].index, options) + spatial = define_spatial(n.buses[n.buses.carrier=="AC"].index, options) add_build_year_to_new_assets(n, baseyear) Nyears = n.snapshot_weightings.generators.sum() / 8760. diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 1e41a954..15b9eb5f 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -114,6 +114,8 @@ def define_spatial(nodes, options): spatial.lignite.nodes = ["EU lignite"] spatial.lignite.locations = ["EU"] + return spatial + from types import SimpleNamespace spatial = SimpleNamespace() @@ -371,7 +373,8 @@ def add_carrier_buses(n, carrier, nodes=None): """ if nodes is None: - nodes = ["EU " + carrier] + nodes = vars(spatial)[carrier].nodes + location = vars(spatial)[carrier].locations # skip if carrier already exists if carrier in n.carriers.index: @@ -384,7 +387,7 @@ def add_carrier_buses(n, carrier, nodes=None): n.madd("Bus", nodes, - location=nodes.str.replace(" " + carrier, ""), + location=location, carrier=carrier ) @@ -825,10 +828,8 @@ def add_generation(n, costs): for generator, carrier in conventionals.items(): - if carrier == 'gas': - carrier_nodes = spatial.gas.nodes - else: - carrier_nodes = ["EU " + carrier] + + carrier_nodes = vars(spatial)[carrier].nodes add_carrier_buses(n, carrier, carrier_nodes) @@ -1088,7 +1089,8 @@ def add_storage_and_grids(n, costs): e_nom_max=h2_caverns.values, e_cyclic=True, 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) @@ -1434,10 +1436,10 @@ def add_land_transport(n, costs): if ice_share > 0: - if "EU oil" not in n.buses.index: - n.add("Bus", - "EU oil", - location="EU", + if "oil" not in n.buses.carrier.unique(): + n.madd("Bus", + vars(spatial)["oil"].nodes, + location=vars(spatial)["oil"].locations, carrier="oil" ) @@ -1446,7 +1448,7 @@ def add_land_transport(n, costs): n.madd("Load", nodes, suffix=" land transport oil", - bus="EU oil", + bus=vars(spatial)["oil"].nodes, carrier="land transport oil", p_set=ice_share / ice_efficiency * transport[nodes] ) @@ -2113,7 +2115,7 @@ def add_industry(n, costs): n.madd("Load", nodes, suffix=" shipping oil", - bus="EU oil", + bus=vars(spatial)["oil"].nodes, carrier="shipping oil", p_set=p_set ) @@ -2127,30 +2129,29 @@ def add_industry(n, costs): p_set=-co2 ) - if "EU oil" not in n.buses.index: - - n.add("Bus", - "EU oil", - location="EU", + if "oil" not in n.buses.carrier.unique(): + n.madd("Bus", + vars(spatial)["oil"].nodes, + location=vars(spatial)["oil"].locations, 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 - n.add("Store", - "EU oil Store", - bus="EU oil", + n.madd("Store", + [oil_bus + " Store" for oil_bus in vars(spatial)["oil"].nodes], + bus=vars(spatial)["oil"].nodes, e_nom_extendable=True, e_cyclic=True, carrier="oil", ) - if "EU oil" not in n.generators.index: + if "oil" not in n.generators.carrier.unique(): - n.add("Generator", - "EU oil", - bus="EU oil", + n.madd("Generator", + vars(spatial)["oil"].nodes, + bus=vars(spatial)["oil"].nodes, p_nom_extendable=True, carrier="oil", marginal_cost=costs.at["oil", 'fuel'] @@ -2165,7 +2166,7 @@ def add_industry(n, costs): n.madd("Link", nodes_heat[name] + f" {name} oil boiler", p_nom_extendable=True, - bus0="EU oil", + bus0=vars(spatial)["oil"].nodes, bus1=nodes_heat[name] + f" {name} heat", bus2="co2 atmosphere", carrier=f"{name} oil boiler", @@ -2178,7 +2179,7 @@ def add_industry(n, costs): n.madd("Link", nodes + " Fischer-Tropsch", bus0=nodes + " H2", - bus1="EU oil", + bus1=vars(spatial)["oil"].nodes, bus2=spatial.co2.nodes, carrier="Fischer-Tropsch", efficiency=costs.at["Fischer-Tropsch", 'efficiency'], @@ -2188,9 +2189,9 @@ def add_industry(n, costs): lifetime=costs.at['Fischer-Tropsch', 'lifetime'] ) - n.add("Load", - "naphtha for industry", - bus="EU oil", + n.madd("Load", + ["naphtha for industry"], + bus=vars(spatial)["oil"].nodes, carrier="naphtha for industry", p_set=industrial_demand.loc[nodes, "naphtha"].sum() / 8760 ) @@ -2198,9 +2199,9 @@ def add_industry(n, costs): all_aviation = ["total international aviation", "total domestic aviation"] p_set = nodal_energy_totals.loc[nodes, all_aviation].sum(axis=1).sum() * 1e6 / 8760 - n.add("Load", - "kerosene for aviation", - bus="EU oil", + n.madd("Load", + ["kerosene for aviation"], + bus=vars(spatial)["oil"].nodes, carrier="kerosene for aviation", p_set=p_set ) @@ -2353,7 +2354,7 @@ def add_agriculture(n, costs): n.add("Load", "agriculture machinery oil", - bus="EU oil", + bus=vars(spatial)["oil"].nodes, carrier="agriculture machinery oil", p_set=ice_share * machinery_nodal_energy.sum() * 1e6 / 8760 ) @@ -2457,7 +2458,7 @@ if __name__ == "__main__": patch_electricity_network(n) - define_spatial(pop_layout.index, options) + spatial = define_spatial(pop_layout.index, options) if snakemake.config["foresight"] == 'myopic': From 03fca360ff0debc7e623c1126499c82c264cafad Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 21 Mar 2022 09:14:15 +0100 Subject: [PATCH 15/18] style improvement --- scripts/add_brownfield.py | 10 ++++++++-- scripts/add_existing_baseyear.py | 7 +++---- scripts/plot_network.py | 2 +- scripts/prepare_sector_network.py | 30 +++++++++++++++--------------- scripts/solve_network.py | 2 +- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 294c1ccf..068ca255 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -78,8 +78,14 @@ def add_brownfield(n, n_p, year): # deal with gas network pipe_carrier = ['gas pipeline'] - to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year!=year) - n.mremove("Link", n.links.loc[to_drop].index) + if snakemake.config["sector"]['H2_retrofit']: + to_drop = n.links.carrier.isin(pipe_carrier) & (n.links.build_year!=year) + n.mremove("Link", n.links.loc[to_drop].index) + 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__": diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index a2ad99ec..a0acc86f 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -409,12 +409,11 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years lifetime=costs.at[costs_name, 'lifetime'] ) - bus0 = vars(spatial)["gas"].nodes n.madd("Link", nodes[name], suffix= f" {name} gas boiler-{grouping_year}", - bus0=bus0, + bus0=spatial.gas.nodes, bus1=nodes[name] + " " + name + " heat", bus2="co2 atmosphere", carrier=name + " gas boiler", @@ -429,7 +428,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years n.madd("Link", nodes[name], suffix=f" {name} oil boiler-{grouping_year}", - bus0="EU oil", + bus0=spatial.oil.nodes, bus1=nodes[name] + " " + name + " heat", bus2="co2 atmosphere", carrier=name + " oil boiler", @@ -458,7 +457,7 @@ if __name__ == "__main__": clusters="37", lv=1.0, 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, ) diff --git a/scripts/plot_network.py b/scripts/plot_network.py index 416e4c4e..1a56cc4b 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -266,7 +266,7 @@ def plot_h2_map(network): # make a fake MultiIndex so that area is correct for legend bus_sizes.rename(index=lambda x: x.replace(" H2", ""), level=0, inplace=True) - # frop all links which are not H2 pipelines + # drop all links which are not H2 pipelines 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"] diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 15b9eb5f..b1b010ce 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1438,8 +1438,8 @@ def add_land_transport(n, costs): if "oil" not in n.buses.carrier.unique(): n.madd("Bus", - vars(spatial)["oil"].nodes, - location=vars(spatial)["oil"].locations, + spatial.oil.nodes, + location=spatial.oil.locations, carrier="oil" ) @@ -1448,7 +1448,7 @@ def add_land_transport(n, costs): n.madd("Load", nodes, suffix=" land transport oil", - bus=vars(spatial)["oil"].nodes, + bus=spatial.oil.nodes, carrier="land transport oil", p_set=ice_share / ice_efficiency * transport[nodes] ) @@ -2115,7 +2115,7 @@ def add_industry(n, costs): n.madd("Load", nodes, suffix=" shipping oil", - bus=vars(spatial)["oil"].nodes, + bus=spatial.oil.nodes, carrier="shipping oil", p_set=p_set ) @@ -2131,8 +2131,8 @@ def add_industry(n, costs): if "oil" not in n.buses.carrier.unique(): n.madd("Bus", - vars(spatial)["oil"].nodes, - location=vars(spatial)["oil"].locations, + spatial.oil.nodes, + location=spatial.oil.locations, carrier="oil" ) @@ -2140,8 +2140,8 @@ def add_industry(n, costs): #could correct to e.g. 0.001 EUR/kWh * annuity and O&M n.madd("Store", - [oil_bus + " Store" for oil_bus in vars(spatial)["oil"].nodes], - bus=vars(spatial)["oil"].nodes, + [oil_bus + " Store" for oil_bus in spatial.oil.nodes], + bus=spatial.oil.nodes, e_nom_extendable=True, e_cyclic=True, carrier="oil", @@ -2150,8 +2150,8 @@ def add_industry(n, costs): if "oil" not in n.generators.carrier.unique(): n.madd("Generator", - vars(spatial)["oil"].nodes, - bus=vars(spatial)["oil"].nodes, + spatial.oil.nodes, + bus=spatial.oil.nodes, p_nom_extendable=True, carrier="oil", marginal_cost=costs.at["oil", 'fuel'] @@ -2166,7 +2166,7 @@ def add_industry(n, costs): n.madd("Link", nodes_heat[name] + f" {name} oil boiler", p_nom_extendable=True, - bus0=vars(spatial)["oil"].nodes, + bus0=spatial.oil.nodes, bus1=nodes_heat[name] + f" {name} heat", bus2="co2 atmosphere", carrier=f"{name} oil boiler", @@ -2179,7 +2179,7 @@ def add_industry(n, costs): n.madd("Link", nodes + " Fischer-Tropsch", bus0=nodes + " H2", - bus1=vars(spatial)["oil"].nodes, + bus1=spatial.oil.nodes, bus2=spatial.co2.nodes, carrier="Fischer-Tropsch", efficiency=costs.at["Fischer-Tropsch", 'efficiency'], @@ -2191,7 +2191,7 @@ def add_industry(n, costs): n.madd("Load", ["naphtha for industry"], - bus=vars(spatial)["oil"].nodes, + bus=spatial.oil.nodes, carrier="naphtha for industry", p_set=industrial_demand.loc[nodes, "naphtha"].sum() / 8760 ) @@ -2201,7 +2201,7 @@ def add_industry(n, costs): n.madd("Load", ["kerosene for aviation"], - bus=vars(spatial)["oil"].nodes, + bus=spatial.oil.nodes, carrier="kerosene for aviation", p_set=p_set ) @@ -2354,7 +2354,7 @@ def add_agriculture(n, costs): n.add("Load", "agriculture machinery oil", - bus=vars(spatial)["oil"].nodes, + bus=spatial.oil.nodes, carrier="agriculture machinery oil", p_set=ice_share * machinery_nodal_energy.sum() * 1e6 / 8760 ) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 46362ad0..5e15386f 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -282,7 +282,7 @@ if __name__ == "__main__": if 'snakemake' not in globals(): from helper import mock_snakemake snakemake = mock_snakemake( - 'solve_network_myopic', + 'solve_network', simpl='', opts="", clusters="37", From cfdec7e56d651a62e7ba01ae7dc3aa51d47873e1 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 12 Apr 2022 09:56:58 +0200 Subject: [PATCH 16/18] simplify pipe retrofitting constraint --- scripts/add_brownfield.py | 16 ++++++++++++++++ scripts/solve_network.py | 6 +----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 068ca255..23ef1352 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -12,6 +12,7 @@ import numpy as np from add_existing_baseyear import add_build_year_to_new_assets from helper import override_component_attrs +from solve_network import basename def add_brownfield(n, n_p, year): @@ -79,8 +80,23 @@ def add_brownfield(n, n_p, year): # 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. diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 7d2045ed..4f6cc2c4 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -193,7 +193,6 @@ def add_pipe_retrofit_constraint(n): gas_pipes_i = n.links.query("carrier == 'gas pipeline' and p_nom_extendable").index h2_retrofitted_i = n.links.query("carrier == 'H2 pipeline retrofitted' and p_nom_extendable").index - h2_retrofitted_fixed_i = n.links.query("carrier == 'H2 pipeline retrofitted' and not p_nom_extendable").index if h2_retrofitted_i.empty or gas_pipes_i.empty: return @@ -204,9 +203,6 @@ def add_pipe_retrofit_constraint(n): to = "gas pipeline" pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'].rename(basename) - 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) lhs = linexpr( (CH4_per_H2, link_p_nom.loc[h2_retrofitted_i].rename(index=lambda x: x.replace(fr, to))), @@ -214,7 +210,7 @@ def add_pipe_retrofit_constraint(n): ) lhs.rename(basename, inplace=True) - define_constraints(n, lhs, "=", remaining_capacity, 'Link', 'pipe_retrofit') + define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit') def add_co2_sequestration_limit(n, sns): From 9322f90318cac0584dad44144a423aa95abd3dcd Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 12 Apr 2022 10:03:04 +0200 Subject: [PATCH 17/18] add minimum capacity for AC and DC lines depending on previous year --- scripts/add_brownfield.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 23ef1352..d7418a79 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -19,6 +19,11 @@ def add_brownfield(n, n_p, year): 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"]): attr = "e" if c.name == "Store" else "p" @@ -103,6 +108,7 @@ def add_brownfield(n, n_p, year): n.links.loc[new_pipes, "p_nom_min"] = 0. + #%% if __name__ == "__main__": if 'snakemake' not in globals(): From 408b494612216fa9b16e06c1b418a12229960b61 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 12 Apr 2022 10:45:11 +0200 Subject: [PATCH 18/18] bug fix with augmentation object --- scripts/prepare_sector_network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 7017e0b9..018981c0 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1067,14 +1067,14 @@ def add_storage_and_grids(n, costs): cavern_types = snakemake.config["sector"]["hydrogen_underground_storage_locations"] h2_caverns = pd.read_csv(snakemake.input.h2_cavern, index_col=0) - + if not h2_caverns.empty and options['hydrogen_underground_storage']: h2_caverns = h2_caverns[cavern_types].sum(axis=1) # only use sites with at least 2 TWh potential h2_caverns = h2_caverns[h2_caverns > 2] - + # convert TWh to MWh h2_caverns = h2_caverns * 1e6 @@ -1178,9 +1178,9 @@ def add_storage_and_grids(n, costs): # apply k_edge_augmentation weighted by length of complement edges 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["length"] = new_gas_pipes.apply(haversine, axis=1)