From 89821477061239d8aca1351b001a43e815e5d67b Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Wed, 27 Nov 2019 18:34:53 +0100 Subject: [PATCH 1/6] Convert code to use PyPSA nomopyomo branch; only works for LV=1.0 Will extend to LV > 1.0 soon. --- .gitignore | 3 +- Snakefile | 2 +- config.yaml | 4 +- scripts/solve_network.py | 102 ++++++++++++++++++++++++--------------- 4 files changed, 67 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index 2987cc72..00230bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ gurobi.log *.pyc /cutouts -/tmp \ No newline at end of file +/tmp +/pypsa \ No newline at end of file diff --git a/Snakefile b/Snakefile index 9330dc9c..995a71ee 100644 --- a/Snakefile +++ b/Snakefile @@ -219,7 +219,7 @@ rule solve_network: memory="logs/" + config['run'] + "/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_memory.log" benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}" threads: 4 - resources: mem=120000 #memory in MB; 40 GB enough for 45+B+I; 100 GB based on RESI usage for 128 + resources: mem=50000 #memory in MB; 40 GB enough for 45+B+I; 100 GB based on RESI usage for 128 # group: "solve" # with group, threads is ignored https://bitbucket.org/snakemake/snakemake/issues/971/group-job-description-does-not-contain script: "scripts/solve_network.py" diff --git a/config.yaml b/config.yaml index 86916b81..e0e70d8c 100644 --- a/config.yaml +++ b/config.yaml @@ -2,7 +2,7 @@ logging_level: INFO results_dir: 'results/' summary_dir: results -run: '191108-h2_pipeline_network' +run: '191127-nomopyomo' scenario: sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ] @@ -10,7 +10,7 @@ scenario: lv: [1.0]#, 1.125, 1.25, 1.5, 2.0]# or opt clusters: [50] #[90, 128, 181] #[45, 64, 90, 128, 181, 256] #, 362] # (2**np.r_[5.5:9:.5]).astype(int) minimum is 37 opts: [''] #for pypsa-eur - sector_opts: [Co2L0-3H-T-H-B-I,Co2L0-5H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] + sector_opts: [Co2L0-3H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] # Co2L will give default (5%); Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 535bdda4..e927c8d4 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -1,7 +1,15 @@ +import os + +os.system("conda config --add channels http://conda.anaconda.org/gurobi") + +os.system("conda install -y gurobi=8.1.0") + import sys -sys.path = ["/home/vres/data/tom/lib/pypsa"] + sys.path +sys.path = ["pypsa"] + sys.path + + import numpy as np import pandas as pd @@ -12,6 +20,8 @@ import os import pypsa +from pypsa.linopt import get_var, linexpr, define_constraints + from pypsa.descriptors import free_output_series_dataframes # Suppress logging of the slack bus choices @@ -121,47 +131,69 @@ def add_eps_storage_constraint(n): fix_sus_i = n.storage_units.index[~ n.storage_units.p_nom_extendable] n.model.objective.expr += sum(n.epsilon * n.model.state_of_charge[su, n.snapshots[0]] for su in fix_sus_i) -def add_battery_constraints(network): +def add_battery_constraints(n): - nodes = list(network.buses.index[network.buses.carrier == "battery"]) + nodes = n.buses.index[n.buses.carrier == "battery"] - def battery(model, node): - return model.link_p_nom[node + " charger"] == model.link_p_nom[node + " discharger"]*network.links.at[node + " charger","efficiency"] + link_p_nom = get_var(n, "Link", "p_nom") - network.model.battery = pypsa.opt.Constraint(nodes, rule=battery) + lhs = linexpr((1,link_p_nom[nodes + " charger"]), + (-n.links.loc[nodes + " charger", "efficiency"], + link_p_nom[nodes + " discharger"].values)) + define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio') +def add_chp_constraints(n): -def add_chp_constraints(network): - options = snakemake.config["sector"] + options = { "chp_parameters" : { 'eta_elec' : 0.468, #electrical efficiency with no heat output + 'c_v' : 0.15, #loss of fuel for each addition of heat + 'c_m' : 0.75, #backpressure ratio + 'p_nom_ratio' : 1., #ratio of max heat output to max electrical output + }} - if hasattr(network.links.index,"str") and network.links.index.str.contains("CHP").any(): + if hasattr(n.links.index,"str") and n.links.index.str.contains("CHP").any(): #AC buses with district heating urban_central = n.buses.index[n.buses.carrier == "urban central heat"] if not urban_central.empty: urban_central = urban_central.str[:-len(" urban central heat")] + link_p_nom = get_var(n, "Link", "p_nom") - def chp_nom(model,node): - return network.links.at[node + " urban central CHP electric","efficiency"]*options['chp_parameters']['p_nom_ratio']*model.link_p_nom[node + " urban central CHP electric"] == network.links.at[node + " urban central CHP heat","efficiency"]*options['chp_parameters']['p_nom_ratio']*model.link_p_nom[node + " urban central CHP heat"] + #chp_nom + lhs = linexpr((n.links.loc[urban_central + " urban central CHP electric","efficiency"] + *options['chp_parameters']['p_nom_ratio'], + link_p_nom[urban_central + " urban central CHP electric"]), + (-n.links.loc[urban_central + " urban central CHP heat","efficiency"].values + *options['chp_parameters']['p_nom_ratio'], + link_p_nom[urban_central + " urban central CHP heat"].values)) + define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio') + link_p = get_var(n, "Link", "p") - network.model.chp_nom = pypsa.opt.Constraint(urban_central,rule=chp_nom) + #backpressure + lhs = linexpr((options['chp_parameters']['c_m'] + *n.links.loc[urban_central + " urban central CHP heat","efficiency"], + link_p[urban_central + " urban central CHP heat"]), + (-n.links.loc[urban_central + " urban central CHP electric","efficiency"].values, + link_p[urban_central + " urban central CHP electric"].values)) + define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure') - def backpressure(model,node,snapshot): - return options['chp_parameters']['c_m']*network.links.at[node + " urban central CHP heat","efficiency"]*model.link_p[node + " urban central CHP heat",snapshot] <= network.links.at[node + " urban central CHP electric","efficiency"]*model.link_p[node + " urban central CHP electric",snapshot] + #top_iso_fuel_line + lhs = linexpr((1,link_p[urban_central + " urban central CHP heat"]), + (1,link_p[urban_central + " urban central CHP electric"].values), + (-1,link_p_nom[urban_central + " urban central CHP electric"].values)) - network.model.backpressure = pypsa.opt.Constraint(urban_central,list(network.snapshots),rule=backpressure) - - - def top_iso_fuel_line(model,node,snapshot): - return model.link_p[node + " urban central CHP heat",snapshot] + model.link_p[node + " urban central CHP electric",snapshot] <= model.link_p_nom[node + " urban central CHP electric"] - - network.model.top_iso_fuel_line = pypsa.opt.Constraint(urban_central,list(network.snapshots),rule=top_iso_fuel_line) + define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line') +def extra_functionality(n, snapshots): + #add_opts_constraints(n, opts) + #add_lv_constraint(n) + #add_eps_storage_constraint(n) + add_chp_constraints(n) + add_battery_constraints(n) @@ -191,16 +223,6 @@ def solve_network(n, config=None, solver_log=None, opts=None): def run_lopf(n, allow_warning_status=False, fix_zero_lines=False, fix_ext_lines=False): free_output_series_dataframes(n) - if not hasattr(n, 'opt') or not isinstance(n.opt, pypsa.opf.PersistentSolver): - pypsa.opf.network_lopf_build_model(n, formulation=solve_opts['formulation']) - add_opts_constraints(n, opts) - add_lv_constraint(n) - #add_eps_storage_constraint(n) - add_battery_constraints(n) - add_chp_constraints(n) - - pypsa.opf.network_lopf_prepare_solver(n, solver_name=solver_name) - if fix_zero_lines: fix_lines_b = (n.lines.s_nom_opt == 0.) & n.lines.s_nom_extendable fix_links_b = (n.links.carrier=='DC') & (n.links.p_nom_opt == 0.) & n.links.p_nom_extendable @@ -237,15 +259,15 @@ def solve_network(n, config=None, solver_log=None, opts=None): #sys.exit() - status, termination_condition = \ - pypsa.opf.network_lopf_solve(n, - solver_logfile=solver_log, - solver_options=solver_options, - formulation=solve_opts['formulation'], - extra_postprocessing=extra_postprocessing - #keep_files=True - #free_memory={'pypsa'} - ) + status, termination_condition = n.lopf(pyomo=False, + solver_name=solver_name, + solver_logfile=solver_log, + solver_options=solver_options, + extra_functionality=extra_functionality, + formulation=solve_opts['formulation']) + #extra_postprocessing=extra_postprocessing + #keep_files=True + #free_memory={'pypsa'} assert status == "ok" or allow_warning_status and status == 'warning', \ ("network_lopf did abort with status={} " From 066f1b0bb5e8615552d7dd85513e41cb704c52d1 Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Fri, 6 Dec 2019 16:55:28 +0100 Subject: [PATCH 2/6] Fix solve_network.py to use nomopyomo for LV > 1.0 --- config.yaml | 4 +-- scripts/solve_network.py | 69 ++++++++++++---------------------------- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/config.yaml b/config.yaml index e0e70d8c..9e413493 100644 --- a/config.yaml +++ b/config.yaml @@ -2,12 +2,12 @@ logging_level: INFO results_dir: 'results/' summary_dir: results -run: '191127-nomopyomo' +run: '191202-nomopyomo' scenario: sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ] simpl: [''] - lv: [1.0]#, 1.125, 1.25, 1.5, 2.0]# or opt + lv: [1.0,1.25]#, 1.125, 1.25, 1.5, 2.0]# or opt clusters: [50] #[90, 128, 181] #[45, 64, 90, 128, 181, 256] #, 362] # (2**np.r_[5.5:9:.5]).astype(int) minimum is 37 opts: [''] #for pypsa-eur sector_opts: [Co2L0-3H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] diff --git a/scripts/solve_network.py b/scripts/solve_network.py index e927c8d4..88f5cabc 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -113,18 +113,6 @@ def add_opts_constraints(n, opts=None): ext_gens_i = n.generators.index[n.generators.carrier.isin(conv_techs) & n.generators.p_nom_extendable] n.model.safe_peakdemand = pypsa.opt.Constraint(expr=sum(n.model.generator_p_nom[gen] for gen in ext_gens_i) >= peakdemand - exist_conv_caps) -def add_lv_constraint(n): - line_volume = getattr(n, 'line_volume_limit', None) - if line_volume is not None and not np.isinf(line_volume): - n.model.line_volume_constraint = pypsa.opt.Constraint( - expr=((sum(n.model.passive_branch_s_nom["Line",line]*n.lines.at[line,"length"] - for line in n.lines.index[n.lines.s_nom_extendable]) + - sum(n.model.link_p_nom[link]*n.links.at[link,"length"] - for link in n.links.index[(n.links.carrier=='DC') & - n.links.p_nom_extendable])) - <= line_volume) - ) - def add_eps_storage_constraint(n): if not hasattr(n, 'epsilon'): n.epsilon = 1e-5 @@ -190,7 +178,6 @@ def add_chp_constraints(n): def extra_functionality(n, snapshots): #add_opts_constraints(n, opts) - #add_lv_constraint(n) #add_eps_storage_constraint(n) add_chp_constraints(n) add_battery_constraints(n) @@ -199,16 +186,11 @@ def extra_functionality(n, snapshots): def fix_branches(n, lines_s_nom=None, links_p_nom=None): if lines_s_nom is not None and len(lines_s_nom) > 0: - for l, s_nom in lines_s_nom.iteritems(): - n.model.passive_branch_s_nom["Line", l].fix(s_nom) - if isinstance(n.opt, pypsa.opf.PersistentSolver): - n.opt.update_var(n.model.passive_branch_s_nom) - + n.lines.loc[lines_s_nom.index,"s_nom"] = lines_s_nom.values + n.lines.loc[lines_s_nom.index,"s_nom_extendable"] = False if links_p_nom is not None and len(links_p_nom) > 0: - for l, p_nom in links_p_nom.iteritems(): - n.model.link_p_nom[l].fix(p_nom) - if isinstance(n.opt, pypsa.opf.PersistentSolver): - n.opt.update_var(n.model.link_p_nom) + n.links.loc[links_p_nom.index,"p_nom"] = links_p_nom.values + n.links.loc[links_p_nom.index,"p_nom_extendable"] = False def solve_network(n, config=None, solver_log=None, opts=None): if config is None: @@ -234,19 +216,19 @@ def solve_network(n, config=None, solver_log=None, opts=None): fix_branches(n, lines_s_nom=n.lines.loc[n.lines.s_nom_extendable, 's_nom_opt'], links_p_nom=n.links.loc[(n.links.carrier=='DC') & n.links.p_nom_extendable, 'p_nom_opt']) - - - if not fix_ext_lines and hasattr(n.model, 'line_volume_constraint'): - - def extra_postprocessing(n, snapshots, duals): - index = list(n.model.line_volume_constraint.keys()) - cdata = pd.Series(list(n.model.line_volume_constraint.values()), - index=index) - n.line_volume_limit_dual = -cdata.map(duals).sum() - print("line volume limit dual:",n.line_volume_limit_dual) - + if "line_volume_constraint" in n.global_constraints.index: + n.global_constraints.drop("line_volume_constraint",inplace=True) else: - extra_postprocessing = None + if "line_volume_constraint" not in n.global_constraints.index: + line_volume = getattr(n, 'line_volume_limit', None) + if line_volume is not None and not np.isinf(line_volume): + n.add("GlobalConstraint", + "line_volume_constraint", + type="transmission_volume_expansion_limit", + carrier_attribute="AC,DC", + sense="<=", + constant=line_volume) + # Firing up solve will increase memory consumption tremendously, so # make sure we freed everything we can @@ -274,6 +256,10 @@ def solve_network(n, config=None, solver_log=None, opts=None): "and termination_condition={}" .format(status, termination_condition)) + if not fix_ext_lines and "line_volume_constraint" in n.global_constraints.index: + n.line_volume_limit_dual = n.global_constraints.at["line_volume_constraint","mu"] + print("line volume limit dual:",n.line_volume_limit_dual) + return status, termination_condition lines_ext_b = n.lines.s_nom_extendable @@ -308,21 +294,6 @@ def solve_network(n, config=None, solver_log=None, opts=None): ) logger.debug("lines.num_parallel={}".format(n.lines.loc[lines_ext_typed_b, 'num_parallel'])) - if isinstance(n.opt, pypsa.opf.PersistentSolver): - n.calculate_dependent_values() - - assert solve_opts['formulation'] == 'kirchhoff', \ - "Updating persistent solvers has only been implemented for the kirchhoff formulation for now" - - n.opt.remove_constraint(n.model.cycle_constraints) - del n.model.cycle_constraints_index - del n.model.cycle_constraints_index_0 - del n.model.cycle_constraints_index_1 - del n.model.cycle_constraints - - pypsa.opf.define_passive_branch_flows_with_kirchhoff(n, n.snapshots, skip_vars=True) - n.opt.add_constraint(n.model.cycle_constraints) - iteration = 1 lines['s_nom_opt'] = lines['s_nom'] * n.lines['num_parallel'].where(n.lines.type != '', 1.) From 80642ae9af56a998d7baa35eec21e2fbc5b4082d Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Fri, 6 Dec 2019 16:56:33 +0100 Subject: [PATCH 3/6] Fix post-processing to read nomopyomo nc; fix MultiIndex issue With new pandas: pd.Index([])|pd.MultiIndex(...) returns a pd.Index, not a pd.MultiIndex, so just reversed: pd.MultiIndex(...)|pd.Index([]) This returns a pd.MultiIndex. --- scripts/make_summary.py | 32 +++++++++++++++++--------------- scripts/plot_network.py | 3 +++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/scripts/make_summary.py b/scripts/make_summary.py index cf4f40ce..acd749f4 100644 --- a/scripts/make_summary.py +++ b/scripts/make_summary.py @@ -36,6 +36,8 @@ override_component_attrs["Link"].loc["efficiency2"] = ["static or series","per u override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per unit",1.,"3rd bus efficiency","Input (optional)"] override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"] override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"] +override_component_attrs["StorageUnit"].loc["p_dispatch"] = ["series","MW",0.,"Storage discharging.","Output"] +override_component_attrs["StorageUnit"].loc["p_store"] = ["series","MW",0.,"Storage charging.","Output"] @@ -77,7 +79,7 @@ def calculate_nodal_cfs(n,label,nodal_cfs): cf_c = p_c/capacities_c index = pd.MultiIndex.from_tuples([(c.list_name,) + t for t in cf_c.index.to_list()]) - nodal_cfs = nodal_cfs.reindex(nodal_cfs.index|index) + nodal_cfs = nodal_cfs.reindex(index|nodal_cfs.index) nodal_cfs.loc[index,label] = cf_c.values return nodal_cfs @@ -102,7 +104,7 @@ def calculate_cfs(n,label,cfs): cf_c = p_c/capacities_c - cfs = cfs.reindex(cfs.index|pd.MultiIndex.from_product([[c.list_name],cf_c.index])) + cfs = cfs.reindex(pd.MultiIndex.from_product([[c.list_name],cf_c.index])|cfs.index) cfs.loc[idx[c.list_name,list(cf_c.index)],label] = cf_c.values @@ -116,7 +118,7 @@ def calculate_nodal_costs(n,label,nodal_costs): for c in n.iterate_components(n.branch_components|n.controllable_one_port_components^{"Load"}): capital_costs = (c.df.capital_cost*c.df[opt_name.get(c.name,"p") + "_nom_opt"]).groupby((c.df.location,c.df.carrier)).sum() index = pd.MultiIndex.from_tuples([(c.list_name,"capital") + t for t in capital_costs.index.to_list()]) - nodal_costs = nodal_costs.reindex(nodal_costs.index|index) + nodal_costs = nodal_costs.reindex(index|nodal_costs.index) nodal_costs.loc[index,label] = capital_costs.values if c.name == "Link": @@ -137,7 +139,7 @@ def calculate_nodal_costs(n,label,nodal_costs): marginal_costs = (p*c.df.marginal_cost).groupby((c.df.location,c.df.carrier)).sum() index = pd.MultiIndex.from_tuples([(c.list_name,"marginal") + t for t in marginal_costs.index.to_list()]) - nodal_costs = nodal_costs.reindex(nodal_costs.index|index) + nodal_costs = nodal_costs.reindex(index|nodal_costs.index) nodal_costs.loc[index,label] = marginal_costs.values return nodal_costs @@ -149,7 +151,7 @@ def calculate_costs(n,label,costs): capital_costs = c.df.capital_cost*c.df[opt_name.get(c.name,"p") + "_nom_opt"] capital_costs_grouped = capital_costs.groupby(c.df.carrier).sum() - costs = costs.reindex(costs.index|pd.MultiIndex.from_product([[c.list_name],["capital"],capital_costs_grouped.index])) + costs = costs.reindex(pd.MultiIndex.from_product([[c.list_name],["capital"],capital_costs_grouped.index])|costs.index) costs.loc[idx[c.list_name,"capital",list(capital_costs_grouped.index)],label] = capital_costs_grouped.values @@ -173,7 +175,7 @@ def calculate_costs(n,label,costs): marginal_costs_grouped = marginal_costs.groupby(c.df.carrier).sum() - costs = costs.reindex(costs.index|pd.MultiIndex.from_product([[c.list_name],["marginal"],marginal_costs_grouped.index])) + costs = costs.reindex(pd.MultiIndex.from_product([[c.list_name],["marginal"],marginal_costs_grouped.index])|costs.index) costs.loc[idx[c.list_name,"marginal",list(marginal_costs_grouped.index)],label] = marginal_costs_grouped.values @@ -198,7 +200,7 @@ def calculate_nodal_capacities(n,label,nodal_capacities): for c in n.iterate_components(n.branch_components|n.controllable_one_port_components^{"Load"}): nodal_capacities_c = c.df[opt_name.get(c.name,"p") + "_nom_opt"].groupby((c.df.location,c.df.carrier)).sum() index = pd.MultiIndex.from_tuples([(c.list_name,) + t for t in nodal_capacities_c.index.to_list()]) - nodal_capacities = nodal_capacities.reindex(nodal_capacities.index|index) + nodal_capacities = nodal_capacities.reindex(index|nodal_capacities.index) nodal_capacities.loc[index,label] = nodal_capacities_c.values return nodal_capacities @@ -211,7 +213,7 @@ def calculate_capacities(n,label,capacities): for c in n.iterate_components(n.branch_components|n.controllable_one_port_components^{"Load"}): capacities_grouped = c.df[opt_name.get(c.name,"p") + "_nom_opt"].groupby(c.df.carrier).sum() - capacities = capacities.reindex(capacities.index|pd.MultiIndex.from_product([[c.list_name],capacities_grouped.index])) + capacities = capacities.reindex(pd.MultiIndex.from_product([[c.list_name],capacities_grouped.index])|capacities.index) capacities.loc[idx[c.list_name,list(capacities_grouped.index)],label] = capacities_grouped.values @@ -238,7 +240,7 @@ def calculate_energy(n,label,energy): for port in [col[3:] for col in c.df.columns if col[:3] == "bus"]: c_energies -= c.pnl["p"+port].multiply(n.snapshot_weightings,axis=0).sum().groupby(c.df.carrier).sum() - energy = energy.reindex(energy.index|pd.MultiIndex.from_product([[c.list_name],c_energies.index])) + energy = energy.reindex(pd.MultiIndex.from_product([[c.list_name],c_energies.index])|energy.index) energy.loc[idx[c.list_name,list(c_energies.index)],label] = c_energies.values @@ -267,7 +269,7 @@ def calculate_supply(n,label,supply): s = c.pnl.p[items].max().multiply(c.df.loc[items,'sign']).groupby(c.df.loc[items,'carrier']).sum() - supply = supply.reindex(supply.index|pd.MultiIndex.from_product([[i],[c.list_name],s.index])) + supply = supply.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply.index) supply.loc[idx[i,c.list_name,list(s.index)],label] = s.values @@ -283,7 +285,7 @@ def calculate_supply(n,label,supply): #lots of sign compensation for direction and to do maximums s = (-1)**(1-int(end))*((-1)**int(end)*c.pnl["p"+end][items]).max().groupby(c.df.loc[items,'carrier']).sum() - supply = supply.reindex(supply.index|pd.MultiIndex.from_product([[i],[c.list_name],s.index])) + supply = supply.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply.index) supply.loc[idx[i,c.list_name,list(s.index)],label] = s.values return supply @@ -310,7 +312,7 @@ def calculate_supply_energy(n,label,supply_energy): s = c.pnl.p[items].sum().multiply(c.df.loc[items,'sign']).groupby(c.df.loc[items,'carrier']).sum() - supply_energy = supply_energy.reindex(supply_energy.index|pd.MultiIndex.from_product([[i],[c.list_name],s.index])) + supply_energy = supply_energy.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply_energy.index) supply_energy.loc[idx[i,c.list_name,list(s.index)],label] = s.values @@ -325,14 +327,14 @@ def calculate_supply_energy(n,label,supply_energy): s = (-1)*c.pnl["p"+end][items].sum().groupby(c.df.loc[items,'carrier']).sum() - supply_energy = supply_energy.reindex(supply_energy.index|pd.MultiIndex.from_product([[i],[c.list_name],s.index])) + supply_energy = supply_energy.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply_energy.index) supply_energy.loc[idx[i,c.list_name,list(s.index)],label] = s.values return supply_energy def calculate_metrics(n,label,metrics): - metrics = metrics.reindex(metrics.index|pd.Index(["line_volume","line_volume_limit","line_volume_AC","line_volume_DC","line_volume_shadow","co2_shadow"])) + metrics = metrics.reindex(pd.Index(["line_volume","line_volume_limit","line_volume_AC","line_volume_DC","line_volume_shadow","co2_shadow"])|metrics.index) metrics.at["line_volume_DC",label] = (n.links.length*n.links.p_nom_opt)[n.links.carrier == "DC"].sum() metrics.at["line_volume_AC",label] = (n.lines.length*n.lines.s_nom_opt).sum() @@ -402,7 +404,7 @@ def calculate_weighted_prices(n,label,weighted_prices): if names.empty: continue - load += n.links_t.p0[names].groupby(n.links.loc[names,"bus0"],axis=1).sum(axis=1) + load += n.links_t.p0[names].groupby(n.links.loc[names,"bus0"],axis=1).sum() #Add H2 Store when charging #if carrier == "H2": diff --git a/scripts/plot_network.py b/scripts/plot_network.py index c185b597..3ace38d4 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -32,6 +32,9 @@ override_component_attrs["Link"].loc["efficiency2"] = ["static or series","per u override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per unit",1.,"3rd bus efficiency","Input (optional)"] override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"] override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"] +override_component_attrs["StorageUnit"].loc["p_dispatch"] = ["series","MW",0.,"Storage discharging.","Output"] +override_component_attrs["StorageUnit"].loc["p_store"] = ["series","MW",0.,"Storage charging.","Output"] + def rename_techs_tyndp(tech): From ec9b447949b4fedc9dc61e27cdab77eda7fe7477 Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Fri, 6 Dec 2019 17:40:20 +0100 Subject: [PATCH 4/6] make_summary: Account for multi-links in supply* summaries --- scripts/make_summary.py | 47 ++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/scripts/make_summary.py b/scripts/make_summary.py index acd749f4..258daf1d 100644 --- a/scripts/make_summary.py +++ b/scripts/make_summary.py @@ -248,17 +248,13 @@ def calculate_energy(n,label,energy): def calculate_supply(n,label,supply): - """calculate the max dispatch of each component at the buses where the loads are attached""" + """calculate the max dispatch of each component at the buses aggregated by carrier""" - load_types = n.loads.carrier.value_counts().index + bus_carriers = n.buses.carrier.unique() - for i in load_types: - - buses = n.loads.bus[n.loads.carrier == i].values - - bus_map = pd.Series(False,index=n.buses.index) - - bus_map.loc[buses] = True + for i in bus_carriers: + bus_map = (n.buses.carrier == i) + bus_map.at[""] = False for c in n.iterate_components(n.one_port_components): @@ -275,9 +271,9 @@ def calculate_supply(n,label,supply): for c in n.iterate_components(n.branch_components): - for end in ["0","1"]: + for end in [col[3:] for col in c.df.columns if col[:3] == "bus"]: - items = c.df.index[c.df["bus" + end].map(bus_map)] + items = c.df.index[c.df["bus" + end].map(bus_map,na_action=False)] if len(items) == 0: continue @@ -285,23 +281,20 @@ def calculate_supply(n,label,supply): #lots of sign compensation for direction and to do maximums s = (-1)**(1-int(end))*((-1)**int(end)*c.pnl["p"+end][items]).max().groupby(c.df.loc[items,'carrier']).sum() - supply = supply.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply.index) - supply.loc[idx[i,c.list_name,list(s.index)],label] = s.values + supply = supply.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index+end])|supply.index) + supply.loc[idx[i,c.list_name,list(s.index+end)],label] = s.values return supply def calculate_supply_energy(n,label,supply_energy): - """calculate the total dispatch of each component at the buses where the loads are attached""" + """calculate the total energy supply/consuption of each component at the buses aggregated by carrier""" - load_types = n.loads.carrier.value_counts().index - for i in load_types: + bus_carriers = n.buses.carrier.unique() - buses = n.loads.bus[n.loads.carrier == i].values - - bus_map = pd.Series(False,index=n.buses.index) - - bus_map.loc[buses] = True + for i in bus_carriers: + bus_map = (n.buses.carrier == i) + bus_map.at[""] = False for c in n.iterate_components(n.one_port_components): @@ -310,7 +303,7 @@ def calculate_supply_energy(n,label,supply_energy): if len(items) == 0: continue - s = c.pnl.p[items].sum().multiply(c.df.loc[items,'sign']).groupby(c.df.loc[items,'carrier']).sum() + s = c.pnl.p[items].multiply(n.snapshot_weightings,axis=0).sum().multiply(c.df.loc[items,'sign']).groupby(c.df.loc[items,'carrier']).sum() supply_energy = supply_energy.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply_energy.index) supply_energy.loc[idx[i,c.list_name,list(s.index)],label] = s.values @@ -318,17 +311,17 @@ def calculate_supply_energy(n,label,supply_energy): for c in n.iterate_components(n.branch_components): - for end in ["0","1"]: + for end in [col[3:] for col in c.df.columns if col[:3] == "bus"]: - items = c.df.index[c.df["bus" + end].map(bus_map)] + items = c.df.index[c.df["bus" + str(end)].map(bus_map,na_action=False)] if len(items) == 0: continue - s = (-1)*c.pnl["p"+end][items].sum().groupby(c.df.loc[items,'carrier']).sum() + s = (-1)*c.pnl["p"+end][items].multiply(n.snapshot_weightings,axis=0).sum().groupby(c.df.loc[items,'carrier']).sum() - supply_energy = supply_energy.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index])|supply_energy.index) - supply_energy.loc[idx[i,c.list_name,list(s.index)],label] = s.values + supply_energy = supply_energy.reindex(pd.MultiIndex.from_product([[i],[c.list_name],s.index+end])|supply_energy.index) + supply_energy.loc[idx[i,c.list_name,list(s.index+end)],label] = s.values return supply_energy From 49a68bdd35cc93417edf690f40977f8d36dd1a13 Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Thu, 12 Dec 2019 15:03:51 +0100 Subject: [PATCH 5/6] Separate CHP gas/solid biomass, with/without CCS Also fix CHP p_nom_ratio constraint, and battery charger/discharger constraint. --- config.yaml | 9 +-- data/costs.csv | 35 +++++++- scripts/prepare_sector_network.py | 128 ++++++++++++++++++++++-------- scripts/solve_network.py | 46 +++++------ 4 files changed, 149 insertions(+), 69 deletions(-) diff --git a/config.yaml b/config.yaml index 9e413493..dc5f1bda 100644 --- a/config.yaml +++ b/config.yaml @@ -2,12 +2,12 @@ logging_level: INFO results_dir: 'results/' summary_dir: results -run: '191202-nomopyomo' +run: '191212-chp' scenario: sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ] simpl: [''] - lv: [1.0,1.25]#, 1.125, 1.25, 1.5, 2.0]# or opt + lv: [1.0]#, 1.125, 1.25, 1.5, 2.0]# or opt clusters: [50] #[90, 128, 181] #[45, 64, 90, 128, 181, 256] #, 362] # (2**np.r_[5.5:9:.5]).astype(int) minimum is 37 opts: [''] #for pypsa-eur sector_opts: [Co2L0-3H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] @@ -73,11 +73,6 @@ sector: 'tes_tau' : 3. 'boilers' : True 'chp' : True - 'chp_parameters': - 'eta_elec' : 0.468 #electrical efficiency with no heat output - 'c_v' : 0.15 #loss of fuel for each addition of heat - 'c_m' : 0.75 #backpressure ratio - 'p_nom_ratio' : 1. #ratio of max heat output to max electrical output 'solar_thermal' : True 'solar_cf_correction': 0.788457 # = >>> 1/1.2683 'marginal_cost_storage' : 0. #1e-4 diff --git a/data/costs.csv b/data/costs.csv index 9daca579..dd6571e0 100644 --- a/data/costs.csv +++ b/data/costs.csv @@ -175,9 +175,38 @@ decentral CHP,2030,lifetime,25,years,HP decentral CHP,2030,investment,1400,EUR/kWel,HP decentral CHP,2030,FOM,3,%/year,HP decentral CHP,2030,discount rate,0.04,per unit,Palzer thesis -central CHP,2030,lifetime,25,years,HP -central CHP,2030,investment,650,EUR/kWel,HP -central CHP,2030,FOM,3,%/year,HP +central gas CHP,2030,lifetime,30,years,DEA +central gas CHP,2030,investment,1300,EUR/kWel,DEA +central gas CHP,2030,FOM,3,%/year,DEA +central gas CHP,2030,efficiency,0.45,per unit,DEA (condensation mode) +central gas CHP,2030,c_b,0.7,per unit,DEA (backpressure ratio) +central gas CHP,2030,c_v,0.17,per unit,DEA (loss of fuel for additional heat) +central gas CHP,2030,p_nom_ratio,1.,per unit, +central gas CHP,2030,VOM,0.82,EUR/MWh,DEA +central gas CHP CCS,2030,lifetime,30,years,DEA +central gas CHP CCS,2030,investment,1900,EUR/kWel,DEA + DIW extra for CCS on gas plant +central gas CHP CCS,2030,FOM,3,%/year,DEA +central gas CHP CCS,2030,efficiency,0.405,per unit,DEA (condensation mode + efficiency loss due to capture) +central gas CHP CCS,2030,c_b,0.7,per unit,DEA (backpressure ratio) +central gas CHP CCS,2030,c_v,0.17,per unit,DEA (loss of fuel for additional heat) +central gas CHP CCS,2030,p_nom_ratio,1.,per unit, +central gas CHP CCS,2030,VOM,0.82,EUR/MWh,DEA +central solid biomass CHP,2030,lifetime,40,years,DEA +central solid biomass CHP,2030,investment,1990,EUR/kWel,DEA +central solid biomass CHP,2030,FOM,3,%/year,DEA +central solid biomass CHP,2030,efficiency,0.52,per unit,DEA (condensation mode) +central solid biomass CHP,2030,c_b,1.01,per unit,DEA (backpressure ratio) +central solid biomass CHP,2030,c_v,0.15,per unit,DEA (loss of fuel for additional heat) +central solid biomass CHP,2030,p_nom_ratio,1.,per unit, +central solid biomass CHP,2030,VOM,2.2,EUR/MWh,DEA +central solid biomass CHP CCS,2030,lifetime,40,years,DEA +central solid biomass CHP CCS,2030,investment,2590,EUR/kWel,DEA + DIW extra for CCS on gas plant +central solid biomass CHP CCS,2030,FOM,3,%/year,DEA +central solid biomass CHP CCS,2030,efficiency,0.468,per unit,DEA (condensation mode + efficiency loss due to capture) +central solid biomass CHP CCS,2030,c_b,1.01,per unit,DEA (backpressure ratio) +central solid biomass CHP CCS,2030,c_v,0.15,per unit,DEA (loss of fuel for additional heat) +central solid biomass CHP CCS,2030,p_nom_ratio,1.,per unit, +central solid biomass CHP CCS,2030,VOM,2.2,EUR/MWh,DEA micro CHP,2030,lifetime,20,years,DEA for PEMFC with methane (for unit consuming 2kW CH4) micro CHP,2030,investment,4500,EUR/kWCH4,DEA for PEMFC with methane (for unit consuming 2kW CH4) micro CHP,2030,FOM,6,%/year,DEA for PEMFC with methane (for unit consuming 2kW CH4) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 11a1d965..b7233be8 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -765,7 +765,7 @@ def add_heat(network): network.madd("Load", nodes[name], - suffix=" " + name + "heat", + suffix=" " + name + " heat", bus=nodes[name] + " " + name + " heat", carrier=name + " heat", p_set=heat_load) @@ -868,47 +868,72 @@ def add_heat(network): if options["chp"]: if name == "urban central": - #additional bus, to which we can also connect biomass - network.madd("Bus", - nodes[name] + " urban central CHP", - carrier="urban central CHP") + #add gas CHP; biomass CHP is added in biomass section + network.madd("Link", + nodes[name] + " urban central gas CHP electric", + bus0="EU gas", + bus1=nodes[name], + bus2="co2 atmosphere", + carrier="urban central gas CHP electric", + p_nom_extendable=True, + capital_cost=costs.at['central gas CHP','fixed']*costs.at['central gas CHP','efficiency'], + marginal_cost=costs.at['central gas CHP','VOM'], + efficiency=costs.at['central gas CHP','efficiency'], + efficiency2=costs.at['gas','CO2 intensity'], + c_b=costs.at['central gas CHP','c_b'], + c_v=costs.at['central gas CHP','c_v'], + p_nom_ratio=costs.at['central gas CHP','p_nom_ratio']) network.madd("Link", - nodes[name] + " gas to urban central CHP", + nodes[name] + " urban central gas CHP heat", bus0="EU gas", - bus1=nodes[name] + " urban central CHP", + bus1=nodes[name] + " urban central heat", + bus2="co2 atmosphere", + carrier="urban central gas CHP heat", + p_nom_extendable=True, + marginal_cost=costs.at['central gas CHP','VOM'], + efficiency=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_v'], + efficiency2=costs.at['gas','CO2 intensity']) + + network.madd("Link", + nodes[name] + " urban central gas CHP CCS electric", + bus0="EU gas", + bus1=nodes[name], bus2="co2 atmosphere", bus3="co2 stored", + carrier="urban central gas CHP CCS electric", + p_nom_extendable=True, + capital_cost=costs.at['central gas CHP CCS','fixed']*costs.at['central gas CHP CCS','efficiency'], + marginal_cost=costs.at['central gas CHP CCS','VOM'], + efficiency=costs.at['central gas CHP CCS','efficiency'], efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]), efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"], - carrier="gas to central CHP", - p_nom_extendable=True) + c_b=costs.at['central gas CHP CCS','c_b'], + c_v=costs.at['central gas CHP CCS','c_v'], + p_nom_ratio=costs.at['central gas CHP CCS','p_nom_ratio']) network.madd("Link", - nodes[name] + " urban central CHP electric", - bus0=nodes[name] + " urban central CHP", - bus1=nodes[name], - carrier="urban central CHP electric", - p_nom_extendable=True, - capital_cost=costs.at['central CHP','fixed']*options['chp_parameters']['eta_elec'], - efficiency=options['chp_parameters']['eta_elec']) - - network.madd("Link", - nodes[name] + " urban central CHP heat", - bus0=nodes[name] + " urban central CHP", + nodes[name] + " urban central gas CHP CCS heat", + bus0="EU gas", bus1=nodes[name] + " urban central heat", - carrier="urban central CHP heat", + bus2="co2 atmosphere", + bus3="co2 stored", + carrier="urban central gas CHP CCS heat", p_nom_extendable=True, - efficiency=options['chp_parameters']['eta_elec']/options['chp_parameters']['c_v']) + marginal_cost=costs.at['central gas CHP CCS','VOM'], + efficiency=costs.at['central gas CHP CCS','efficiency']/costs.at['central gas CHP CCS','c_v'], + efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]), + efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"]) + else: network.madd("Link", - nodes[name] + " " + name + " micro CHP", + nodes[name] + " " + name + " micro gas CHP", p_nom_extendable=True, - bus0=["EU gas"]*len(nodes[name]), + bus0="EU gas", bus1=nodes[name], bus2=nodes[name] + " " + name + " heat", bus3="co2 atmosphere", - carrier=name + " micro CHP", + carrier=name + " micro gas CHP", efficiency=costs.at['micro CHP','efficiency'], efficiency2=costs.at['micro CHP','efficiency-heat'], efficiency3=costs.at['gas','CO2 intensity'], @@ -1028,20 +1053,61 @@ def add_biomass(network): #AC buses with district heating urban_central = n.buses.index[n.buses.carrier == "urban central heat"] - if not urban_central.empty: + if not urban_central.empty and options["chp"]: urban_central = urban_central.str[:-len(" urban central heat")] - #with BECCS network.madd("Link", - urban_central + " solid biomass to urban central CHP", + urban_central + " urban central solid biomass CHP electric", bus0="EU solid biomass", - bus1=urban_central + " urban central CHP", + bus1=urban_central, + carrier="urban central solid biomass CHP electric", + p_nom_extendable=True, + capital_cost=costs.at['central solid biomass CHP','fixed']*costs.at['central solid biomass CHP','efficiency'], + marginal_cost=costs.at['central solid biomass CHP','VOM'], + efficiency=costs.at['central solid biomass CHP','efficiency'], + c_b=costs.at['central solid biomass CHP','c_b'], + c_v=costs.at['central solid biomass CHP','c_v'], + p_nom_ratio=costs.at['central solid biomass CHP','p_nom_ratio']) + + + network.madd("Link", + urban_central + " urban central solid biomass CHP heat", + bus0="EU solid biomass", + bus1=urban_central + " urban central heat", + carrier="urban central solid biomass CHP heat", + p_nom_extendable=True, + marginal_cost=costs.at['central solid biomass CHP','VOM'], + efficiency=costs.at['central solid biomass CHP','efficiency']/costs.at['central solid biomass CHP','c_v']) + + network.madd("Link", + urban_central + " urban central solid biomass CHP CCS electric", + bus0="EU solid biomass", + bus1=urban_central, bus2="co2 atmosphere", bus3="co2 stored", + carrier="urban central solid biomass CHP CCS electric", + p_nom_extendable=True, + capital_cost=costs.at['central solid biomass CHP CCS','fixed']*costs.at['central solid biomass CHP CCS','efficiency'], + marginal_cost=costs.at['central solid biomass CHP CCS','VOM'], + efficiency=costs.at['central solid biomass CHP CCS','efficiency'], efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], - carrier="solid biomass to urban central CHP", - p_nom_extendable=True) + c_b=costs.at['central solid biomass CHP','c_b'], + c_v=costs.at['central solid biomass CHP','c_v'], + p_nom_ratio=costs.at['central solid biomass CHP','p_nom_ratio']) + + network.madd("Link", + urban_central + " urban central solid biomass CHP CCS heat", + bus0="EU solid biomass", + bus1=urban_central + " urban central heat", + bus2="co2 atmosphere", + bus3="co2 stored", + carrier="urban central solid biomass CHP CCS heat", + p_nom_extendable=True, + marginal_cost=costs.at['central solid biomass CHP CCS','VOM'], + efficiency=costs.at['central solid biomass CHP CCS','efficiency']/costs.at['central solid biomass CHP CCS','c_v'], + efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], + efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"]) def add_industry(network): diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 88f5cabc..512e4fc7 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -126,53 +126,43 @@ def add_battery_constraints(n): link_p_nom = get_var(n, "Link", "p_nom") lhs = linexpr((1,link_p_nom[nodes + " charger"]), - (-n.links.loc[nodes + " charger", "efficiency"], + (-n.links.loc[nodes + " discharger", "efficiency"], link_p_nom[nodes + " discharger"].values)) define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio') def add_chp_constraints(n): + electric = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("electric")] + heat = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("heat")] - options = { "chp_parameters" : { 'eta_elec' : 0.468, #electrical efficiency with no heat output - 'c_v' : 0.15, #loss of fuel for each addition of heat - 'c_m' : 0.75, #backpressure ratio - 'p_nom_ratio' : 1., #ratio of max heat output to max electrical output - }} - - if hasattr(n.links.index,"str") and n.links.index.str.contains("CHP").any(): - - #AC buses with district heating - urban_central = n.buses.index[n.buses.carrier == "urban central heat"] - if not urban_central.empty: - urban_central = urban_central.str[:-len(" urban central heat")] + if not electric.empty: link_p_nom = get_var(n, "Link", "p_nom") - #chp_nom - lhs = linexpr((n.links.loc[urban_central + " urban central CHP electric","efficiency"] - *options['chp_parameters']['p_nom_ratio'], - link_p_nom[urban_central + " urban central CHP electric"]), - (-n.links.loc[urban_central + " urban central CHP heat","efficiency"].values - *options['chp_parameters']['p_nom_ratio'], - link_p_nom[urban_central + " urban central CHP heat"].values)) + #ratio of output heat to electricity set by p_nom_ratio + lhs = linexpr((n.links.loc[electric,"efficiency"] + *n.links.loc[electric,'p_nom_ratio'], + link_p_nom[electric]), + (-n.links.loc[heat,"efficiency"].values, + link_p_nom[heat].values)) define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio') link_p = get_var(n, "Link", "p") #backpressure - lhs = linexpr((options['chp_parameters']['c_m'] - *n.links.loc[urban_central + " urban central CHP heat","efficiency"], - link_p[urban_central + " urban central CHP heat"]), - (-n.links.loc[urban_central + " urban central CHP electric","efficiency"].values, - link_p[urban_central + " urban central CHP electric"].values)) + lhs = linexpr((n.links.loc[electric,'c_b'].values + *n.links.loc[heat,"efficiency"], + link_p[heat]), + (-n.links.loc[electric,"efficiency"].values, + link_p[electric].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure') #top_iso_fuel_line - lhs = linexpr((1,link_p[urban_central + " urban central CHP heat"]), - (1,link_p[urban_central + " urban central CHP electric"].values), - (-1,link_p_nom[urban_central + " urban central CHP electric"].values)) + lhs = linexpr((1,link_p[heat]), + (1,link_p[electric].values), + (-1,link_p_nom[electric].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line') From 41f7f44dcf2877d34c6856acc82a77e277793411 Mon Sep 17 00:00:00 2001 From: Tom Brown Date: Fri, 13 Dec 2019 18:06:38 +0100 Subject: [PATCH 6/6] Make CCS optional for SMR and industry process emissions --- config.yaml | 4 +- data/costs.csv | 50 +++++++----- scripts/prepare_sector_network.py | 128 ++++++++++++++++++++++-------- scripts/solve_network.py | 2 +- 4 files changed, 125 insertions(+), 59 deletions(-) diff --git a/config.yaml b/config.yaml index dc5f1bda..20365258 100644 --- a/config.yaml +++ b/config.yaml @@ -2,7 +2,7 @@ logging_level: INFO results_dir: 'results/' summary_dir: results -run: '191212-chp' +run: '191212-ccs' scenario: sectors: [E] # ,E+EV,E+BEV,E+BEV+V2G] # [ E+EV, E+BEV, E+BEV+V2G ] @@ -10,7 +10,7 @@ scenario: lv: [1.0]#, 1.125, 1.25, 1.5, 2.0]# or opt clusters: [50] #[90, 128, 181] #[45, 64, 90, 128, 181, 256] #, 362] # (2**np.r_[5.5:9:.5]).astype(int) minimum is 37 opts: [''] #for pypsa-eur - sector_opts: [Co2L0-3H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] + sector_opts: [Co2L0-3H-T-H-B-I,Co2L0p2-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#,Co2L0p1-3H-T-H-B-I,Co2L0p25-3H-T-H-B-I,Co2L0p5-3H-T-H-B-I]#[Co2L0-3H-T-H-B-I-onwind0-solar3,Co2L0-3H-T-H-B-I-onwind0p125-solar3,Co2L0-3H-T-H-B-I-onwind0p25-solar3,Co2L0-3H-T-H-B-I-onwind0p50-solar3,Co2L0-3H-T-H-B-I-solar3]#,Co2L0-3H-T-H-B-I-onwind0p25-solar3]#,Co2L0p05-3H-T-H-B-I,Co2L0p10-3H-T-H-B-I,Co2L0p20-3H-T-H-B-I,Co2L0p30-3H-T-H-B-I,Co2L0p50-3H-T-H-B-I]#[Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0-3H-T-H,Co2L0p20-3H-T-H] #Co2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p20-3H-T-HCo2L-3H-T-H,Co2L0p10-3H-T-H,Co2L0p30-3H-T-H,Co2L0p50-3H-T-H] #Co2L-3H,Co2L-3H-T,, LC-FL, LC-T, Ep-T, Co2L-T] # Co2L will give default (5%); Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions diff --git a/data/costs.csv b/data/costs.csv index dd6571e0..7779533b 100644 --- a/data/costs.csv +++ b/data/costs.csv @@ -73,15 +73,15 @@ CCGT,2030,efficiency,0.5,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 biomass,2030,efficiency,0.468,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 geothermal,2030,efficiency,0.239,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 nuclear,2030,efficiency,0.337,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 -gas,2030,CO2 intensity,0.187,tCO2/MWth,https://www.eia.gov/environment/emissions/co2_vol_mass.php +gas,2030,CO2 intensity,0.187,tCO2/MWhth,https://www.eia.gov/environment/emissions/co2_vol_mass.php coal,2030,efficiency,0.464,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 PC (Advanced/SuperC) lignite,2030,efficiency,0.447,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 oil,2030,efficiency,0.393,per unit,DIW DataDoc http://hdl.handle.net/10419/80348 CT -coal,2030,CO2 intensity,0.354,tCO2/MWth,https://www.eia.gov/environment/emissions/co2_vol_mass.php -lignite,2030,CO2 intensity,0.4,tCO2/MWth,German sources -oil,2030,CO2 intensity,0.248,tCO2/MWth,https://www.eia.gov/environment/emissions/co2_vol_mass.php -geothermal,2030,CO2 intensity,0.026,tCO2/MWth,https://www.eia.gov/environment/emissions/co2_vol_mass.php -solid biomass,2030,CO2 intensity,0.3,tCO2/MWth,TODO +coal,2030,CO2 intensity,0.354,tCO2/MWhth,https://www.eia.gov/environment/emissions/co2_vol_mass.php +lignite,2030,CO2 intensity,0.4,tCO2/MWhth,German sources +oil,2030,CO2 intensity,0.248,tCO2/MWhth,https://www.eia.gov/environment/emissions/co2_vol_mass.php +geothermal,2030,CO2 intensity,0.026,tCO2/MWhth,https://www.eia.gov/environment/emissions/co2_vol_mass.php +solid biomass,2030,CO2 intensity,0.3,tCO2/MWhth,TODO electrolysis,2030,investment,350,EUR/kWel,Palzer Thesis electrolysis,2030,FOM,4,%/year,NREL http://www.nrel.gov/docs/fy09osti/45873.pdf; budischak2013 electrolysis,2030,lifetime,18,years,NREL http://www.nrel.gov/docs/fy09osti/45873.pdf; budischak2013 @@ -109,6 +109,14 @@ SMR,2030,investment,540.56,EUR/kWCH4,https://www.gov.uk/government/publications/ SMR,2030,lifetime,25,years,TODO SMR,2030,FOM,5.4,%/year,https://www.gov.uk/government/publications/hydrogen-supply-chain-evidence-base; slide 42 assumption for 2030 SMR,2030,efficiency,0.74,per unit,https://www.gov.uk/government/publications/hydrogen-supply-chain-evidence-base; slide 42 assumption for 2030 +SMR CCS,2030,investment,1032,EUR/kWCH4,https://www.gov.uk/government/publications/hydrogen-supply-chain-evidence-base; slide 42 assumption for 2030; GBP 466 exchange 1.16; CCS costed at 300 EUR/tCO2/a +SMR CCS,2030,lifetime,25,years,TODO +SMR CCS,2030,FOM,5.4,%/year,https://www.gov.uk/government/publications/hydrogen-supply-chain-evidence-base; slide 42 assumption for 2030 +SMR CCS,2030,efficiency,0.67,per unit,https://www.gov.uk/government/publications/hydrogen-supply-chain-evidence-base; slide 42 assumption for 2030; CCS uses 10% of gas +industry CCS,2030,investment,300,EUR/tCO2/a,Saygin et al 2013 https://doi.org/10.1016/j.ijggc.2013.05.032 +industry CCS,2030,FOM,2,%/year,Saygin et al 2013 https://doi.org/10.1016/j.ijggc.2013.05.032 +industry CCS,2030,lifetime,25,years,Saygin et al 2013 https://doi.org/10.1016/j.ijggc.2013.05.032 +industry CCS,2030,efficiency,0.9,per unit,Saygin et al 2013 https://doi.org/10.1016/j.ijggc.2013.05.032 Fischer-Tropsch,2030,investment,677.6,EUR/kWH2,Fasihi doi:10.3390/su9020306 (60 kEUR/bpd = 847 EUR/kWL (1b = 1.7 MWh) 847*0.8 = 677.6) Fischer-Tropsch,2030,lifetime,30,years,doi:10.3390/su9020306 Fischer-Tropsch,2030,FOM,3,%/year,doi:10.3390/su9020306 @@ -118,7 +126,7 @@ DAC,2030,lifetime,30,years,Fasihi DAC,2030,FOM,4,%/year,Fasihi battery inverter,2030,investment,411,USD/kWel,budischak2013 battery inverter,2030,lifetime,20,years,budischak2013 -battery inverter,2030,efficiency,0.81,per unit,budischak2013; Lund and Kempton (2008) http://dx.doi.org/10.1016/j.enpol.2008.06.007 +battery inverter,2030,efficiency,0.81,per unit,budischak2013; Lund and Kempton (2008) https://doi.org/10.1016/j.enpol.2008.06.007 battery inverter,2030,FOM,3,%/year,budischak2013 battery storage,2030,investment,192,USD/kWh,budischak2013 battery storage,2030,lifetime,15,years,budischak2013 @@ -191,22 +199,22 @@ central gas CHP CCS,2030,c_b,0.7,per unit,DEA (backpressure ratio) central gas CHP CCS,2030,c_v,0.17,per unit,DEA (loss of fuel for additional heat) central gas CHP CCS,2030,p_nom_ratio,1.,per unit, central gas CHP CCS,2030,VOM,0.82,EUR/MWh,DEA -central solid biomass CHP,2030,lifetime,40,years,DEA -central solid biomass CHP,2030,investment,1990,EUR/kWel,DEA -central solid biomass CHP,2030,FOM,3,%/year,DEA -central solid biomass CHP,2030,efficiency,0.52,per unit,DEA (condensation mode) -central solid biomass CHP,2030,c_b,1.01,per unit,DEA (backpressure ratio) -central solid biomass CHP,2030,c_v,0.15,per unit,DEA (loss of fuel for additional heat) +central solid biomass CHP,2030,lifetime,40,years,DEA for wood pellets CHP +central solid biomass CHP,2030,investment,1990,EUR/kWel,DEA for wood pellets CHP +central solid biomass CHP,2030,FOM,3,%/year,DEA for wood pellets CHP +central solid biomass CHP,2030,efficiency,0.52,per unit,DEA for wood pellets CHP (condensation mode) +central solid biomass CHP,2030,c_b,1.01,per unit,DEA for wood pellets CHP (backpressure ratio) +central solid biomass CHP,2030,c_v,0.15,per unit,DEA for wood pellets CHP (loss of fuel for additional heat) central solid biomass CHP,2030,p_nom_ratio,1.,per unit, -central solid biomass CHP,2030,VOM,2.2,EUR/MWh,DEA -central solid biomass CHP CCS,2030,lifetime,40,years,DEA -central solid biomass CHP CCS,2030,investment,2590,EUR/kWel,DEA + DIW extra for CCS on gas plant -central solid biomass CHP CCS,2030,FOM,3,%/year,DEA -central solid biomass CHP CCS,2030,efficiency,0.468,per unit,DEA (condensation mode + efficiency loss due to capture) -central solid biomass CHP CCS,2030,c_b,1.01,per unit,DEA (backpressure ratio) -central solid biomass CHP CCS,2030,c_v,0.15,per unit,DEA (loss of fuel for additional heat) +central solid biomass CHP,2030,VOM,2.2,EUR/MWh,DEA for wood pellets CHP +central solid biomass CHP CCS,2030,lifetime,40,years,DEA for wood pellets CHP +central solid biomass CHP CCS,2030,investment,2590,EUR/kWel,DEA for wood pellets CHP + DIW extra for CCS on gas plant +central solid biomass CHP CCS,2030,FOM,3,%/year,DEA for wood pellets CHP +central solid biomass CHP CCS,2030,efficiency,0.468,per unit,DEA for wood pellets CHP (condensation mode + efficiency loss due to capture) +central solid biomass CHP CCS,2030,c_b,1.01,per unit,DEA for wood pellets CHP (backpressure ratio) +central solid biomass CHP CCS,2030,c_v,0.15,per unit,DEA for wood pellets CHP (loss of fuel for additional heat) central solid biomass CHP CCS,2030,p_nom_ratio,1.,per unit, -central solid biomass CHP CCS,2030,VOM,2.2,EUR/MWh,DEA +central solid biomass CHP CCS,2030,VOM,2.2,EUR/MWh,DEA for wood pellets CHP micro CHP,2030,lifetime,20,years,DEA for PEMFC with methane (for unit consuming 2kW CH4) micro CHP,2030,investment,4500,EUR/kWCH4,DEA for PEMFC with methane (for unit consuming 2kW CH4) micro CHP,2030,FOM,6,%/year,DEA for PEMFC with methane (for unit consuming 2kW CH4) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index b7233be8..3e89146d 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -623,16 +623,27 @@ def add_storage(network): if options['SMR']: network.madd("Link", - nodes + " SMR", + nodes + " SMR CCS", bus0=["EU gas"]*len(nodes), bus1=nodes+" H2", bus2="co2 atmosphere", bus3="co2 stored", p_nom_extendable=True, - carrier="SMR", - efficiency=costs.at["SMR","efficiency"], + carrier="SMR CCS", + efficiency=costs.at["SMR CCS","efficiency"], efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]), efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"], + capital_cost=costs.at["SMR CCS","fixed"]) + + network.madd("Link", + nodes + " SMR", + bus0=["EU gas"]*len(nodes), + bus1=nodes+" H2", + bus2="co2 atmosphere", + p_nom_extendable=True, + carrier="SMR", + efficiency=costs.at["SMR","efficiency"], + efficiency2=costs.at['gas','CO2 intensity'], capital_cost=costs.at["SMR","fixed"]) @@ -1123,43 +1134,70 @@ def add_industry(network): solid_biomass_by_country = industrial_demand["solid biomass"].groupby(pop_layout.ct).sum() countries = solid_biomass_by_country.index + network.madd("Bus", + ["solid biomass for industry"], + carrier="solid biomass for industry") + network.madd("Load", ["solid biomass for industry"], - bus="EU solid biomass", + bus="solid biomass for industry", carrier="solid biomass for industry", p_set=solid_biomass_by_country.sum()/8760.) - #Net transfer of CO2 from atmosphere to stored - network.madd("Load", - ["solid biomass for industry co2 from atmosphere"], - bus="co2 atmosphere", - carrier="solid biomass for industry co2 from atmosphere", - p_set=solid_biomass_by_country.sum()*costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"]/8760.) + network.madd("Link", + ["solid biomass for industry"], + bus0="EU solid biomass", + bus1="solid biomass for industry", + carrier="solid biomass for industry", + p_nom_extendable=True, + efficiency=1.) - network.madd("Load", - ["solid biomass for industry co2 to stored"], - bus="co2 stored", - carrier="solid biomass for industry co2 to stored", - p_set=-solid_biomass_by_country.sum()*costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"]/8760.) + network.madd("Link", + ["solid biomass for industry CCS"], + bus0="EU solid biomass", + bus1="solid biomass for industry", + bus2="co2 atmosphere", + bus3="co2 stored", + carrier="solid biomass for industry CCS", + p_nom_extendable=True, + capital_cost=costs.at["industry CCS","fixed"]*costs.at['solid biomass','CO2 intensity']*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h) + efficiency=0.9, + efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], + efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"]) + network.madd("Bus", + ["gas for industry"], + carrier="gas for industry") + network.madd("Load", ["gas for industry"], - bus="EU gas", + bus="gas for industry", carrier="gas for industry", p_set=industrial_demand.loc[nodes,"methane"].sum()/8760.) - network.madd("Load", - ["gas for industry co2 to atmosphere"], - bus="co2 atmosphere", - carrier="gas for industry co2 to atmosphere", - p_set=-industrial_demand.loc[nodes,"methane"].sum()*costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"])/8760.) + network.madd("Link", + ["gas for industry"], + bus0="EU gas", + bus1="gas for industry", + bus2="co2 atmosphere", + carrier="gas for industry", + p_nom_extendable=True, + efficiency=1., + efficiency2=costs.at['gas','CO2 intensity']) - network.madd("Load", - ["gas for industry co2 to stored"], - bus="co2 stored", - carrier="gas for industry co2 to stored", - p_set=-industrial_demand.loc[nodes,"methane"].sum()*costs.at['gas','CO2 intensity']*options["ccs_fraction"]/8760.) + network.madd("Link", + ["gas for industry CCS"], + bus0="EU gas", + bus1="gas for industry", + bus2="co2 atmosphere", + bus3="co2 stored", + carrier="gas for industry CCS", + p_nom_extendable=True, + capital_cost=costs.at["industry CCS","fixed"]*costs.at['gas','CO2 intensity']*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h) + efficiency=0.9, + efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]), + efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"]) network.madd("Load", @@ -1250,17 +1288,37 @@ def add_industry(network): carrier="industry new electricity", p_set = (industrial_demand.loc[nodes,"electricity"]-industrial_demand.loc[nodes,"current electricity"])/8760.) - network.madd("Load", - ["process emissions to atmosphere"], - bus="co2 atmosphere", - carrier="process emissions to atmosphere", - p_set = -industrial_demand.loc[nodes,"process emission"].sum()*(1-options["ccs_fraction"])/8760.) + network.madd("Bus", + ["process emissions"], + carrier="process emissions") + #this should be process emissions fossil+feedstock + #then need load on atmosphere for feedstock emissions that are currently going to atmosphere via Link Fischer-Tropsch demand network.madd("Load", - ["process emissions to stored"], - bus="co2 stored", - carrier="process emissions to stored", - p_set = -industrial_demand.loc[nodes,"process emission"].sum()*options["ccs_fraction"]/8760.) + ["process emissions"], + bus="process emissions", + carrier="process emissions", + p_set = -industrial_demand.loc[nodes,"process emission"].sum()/8760.) + + network.madd("Link", + ["process emissions"], + bus0="process emissions", + bus1="co2 atmosphere", + carrier="process emissions", + p_nom_extendable=True, + efficiency=1.) + + #assume enough local waste heat for CCS + network.madd("Link", + ["process emissions CCS"], + bus0="process emissions", + bus1="co2 atmosphere", + bus2="co2 stored", + carrier="process emissions CCS", + p_nom_extendable=True, + capital_cost=costs.at["industry CCS","fixed"]*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h) + efficiency=(1-options["ccs_fraction"]), + efficiency2=options["ccs_fraction"]) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 512e4fc7..502c7142 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -126,7 +126,7 @@ def add_battery_constraints(n): link_p_nom = get_var(n, "Link", "p_nom") lhs = linexpr((1,link_p_nom[nodes + " charger"]), - (-n.links.loc[nodes + " discharger", "efficiency"], + (-n.links.loc[nodes + " discharger", "efficiency"].values, link_p_nom[nodes + " discharger"].values)) define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio')