From f68f4c420531ba3d65b54e06e85248fd1210fee2 Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Mon, 17 Jun 2019 16:08:09 +0200 Subject: [PATCH 01/10] solve_network.py: add minmax constraint per country --- scripts/solve_network.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index f9894261..3fd4aee6 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -77,6 +77,22 @@ 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_country_carrier_generation_constraints(n, opts=None): + agg_p_nom_minmax = pd.read_csv("data/agg_p_nom_minmax.csv", index_col=list(range(2))) + + gen_country = n.generators.bus.map(n.buses.country) + + def agg_p_nom_min_rule(model, country, carrier): + return sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) >= agg_p_nom_minmax.at[(country, carrier),'min'] + + def agg_p_nom_max_rule(model, country, carrier): + return sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) <= agg_p_nom_minmax.at[(country, carrier),'max'] + + n.model.agg_p_nom_min = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_min_rule) + n.model.agg_p_nom_max = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_max_rule) + 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): From c1f79c42756e332288b20571e0bdb7a1d483163a Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Mon, 17 Jun 2019 16:09:58 +0200 Subject: [PATCH 02/10] agg_p_nom_minmax.csv: add csv with minmax values --- data/agg_p_nom_minmax.csv | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 data/agg_p_nom_minmax.csv diff --git a/data/agg_p_nom_minmax.csv b/data/agg_p_nom_minmax.csv new file mode 100644 index 00000000..1d04e20c --- /dev/null +++ b/data/agg_p_nom_minmax.csv @@ -0,0 +1,31 @@ +country,carrier,min,max +DE,onwind,0.1,1.00E+09 +DE,offwind-ac,0.1,1.00E+09 +DE,offwind-dc,0.1,1.00E+09 +DE,solar,0.2,1.00E+09 +LU,onwind,0,1.00E+09 +LU,solar,0,1.00E+09 +NL,onwind,0,1.00E+09 +NL,offwind-ac,0,1.00E+09 +NL,offwind-dc,0,1.00E+09 +NL,solar,0,1.00E+09 +GB,onwind,0,1.00E+09 +GB,offwind-ac,0,1.00E+09 +GB,offwind-dc,0,1.00E+09 +GB,solar,0,1.00E+09 +IE,onwind,0,1.00E+09 +IE,offwind-ac,0,1.00E+09 +IE,offwind-dc,0,1.00E+09 +IE,solar,0,1.00E+09 +FR,onwind,0,1.00E+09 +FR,offwind-ac,0,1.00E+09 +FR,offwind-dc,0,1.00E+09 +FR,solar,0,1.00E+09 +DK,onwind,0,1.00E+09 +DK,offwind-ac,0,1.00E+09 +DK,offwind-dc,0,1.00E+09 +DK,solar,0,1.00E+09 +BE,onwind,0,1.00E+09 +BE,offwind-ac,0,1.00E+09 +BE,offwind-dc,0,1.00E+09 +BE,solar,0,1.00E+09 From c065cad8118584184503cd5578beccc2270980ce Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Mon, 17 Jun 2019 16:14:01 +0200 Subject: [PATCH 03/10] Fix missing code --- scripts/solve_network.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 3fd4aee6..b7352281 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -161,7 +161,10 @@ def solve_network(n, config=None, solver_log=None, opts=None, callback=None): free_output_series_dataframes(n) pypsa.opf.network_lopf_build_model(n, formulation=solve_opts['formulation']) + + add_country_carrier_generation_constraints(n, opts) add_opts_constraints(n, opts) + if not fix_ext_lines: add_lv_constraint(n) add_lc_constraint(n) From 504a9fd595defec60a50a4b0e46d0f825cab6ee5 Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Mon, 17 Jun 2019 16:56:13 +0200 Subject: [PATCH 04/10] Add handle for limits --- config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config.yaml b/config.yaml index 9d54e0f3..3900c50b 100644 --- a/config.yaml +++ b/config.yaml @@ -27,6 +27,7 @@ enable: electricity: voltages: [220., 300., 380.] co2limit: 7.75e+7 # 0.05 * 3.1e9*0.5 + agg_p_nom_limits: data/agg_p_nom_minmax.csv extendable_carriers: Generator: [OCGT] From a2c736e2bfca391e482d4d81d917564d348f5446 Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Mon, 17 Jun 2019 16:58:09 +0200 Subject: [PATCH 05/10] Include config handle --- scripts/solve_network.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index b7352281..797cff0b 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -78,7 +78,11 @@ def add_opts_constraints(n, opts=None): 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_country_carrier_generation_constraints(n, opts=None): - agg_p_nom_minmax = pd.read_csv("data/agg_p_nom_minmax.csv", index_col=list(range(2))) + agg_p_nom_limits = snakemake.config['electricity']['agg_p_nom_limits'] + if os.path.isfile(agg_p_nom_limits): + agg_p_nom_minmax = pd.read_csv(agg_p_nom_limits, index_col=list(range(2))) + else: + raise FileNotFoundError("Need to specify the path to a .csv file containing aggregate capacity limits per country in config['electricity']['agg_p_nom_limit'].") gen_country = n.generators.bus.map(n.buses.country) From d7fcbbba7ba5872a6fa9f01fc1fb9c6775be65f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6rsch?= Date: Mon, 17 Jun 2019 23:21:54 +0200 Subject: [PATCH 06/10] solve_network: Backwards compatibility Make sure not providing a csv-file with capacity limits does not break. --- scripts/solve_network.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 797cff0b..29d2714f 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -77,22 +77,32 @@ 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_country_carrier_generation_constraints(n, opts=None): - agg_p_nom_limits = snakemake.config['electricity']['agg_p_nom_limits'] - if os.path.isfile(agg_p_nom_limits): +def add_country_carrier_generation_constraints(n): + agg_p_nom_limits = snakemake.config['electricity'].get('agg_p_nom_limits') + if agg_p_nom_limits is None: return + + try: agg_p_nom_minmax = pd.read_csv(agg_p_nom_limits, index_col=list(range(2))) - else: - raise FileNotFoundError("Need to specify the path to a .csv file containing aggregate capacity limits per country in config['electricity']['agg_p_nom_limit'].") + except IOError: + logger.exception("Need to specify the path to a .csv file containing aggregate capacity limits per country in config['electricity']['agg_p_nom_limit'].") + logger.info("Adding per carrier generation capacity constraints for individual countries") + gen_country = n.generators.bus.map(n.buses.country) def agg_p_nom_min_rule(model, country, carrier): - return sum(model.generator_p_nom[gen] - for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) >= agg_p_nom_minmax.at[(country, carrier),'min'] - + min = agg_p_nom_minmax.at[(country, carrier), 'min'] + return ((sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) + <= min) + if np.isfinite(min) else po.Constraint.Skip) + def agg_p_nom_max_rule(model, country, carrier): - return sum(model.generator_p_nom[gen] - for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) <= agg_p_nom_minmax.at[(country, carrier),'max'] + max = agg_p_nom_minmax.at[(country, carrier), 'max'] + return ((sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) + <= max) + if np.isfinite(max) else po.Constraint.Skip) n.model.agg_p_nom_min = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_min_rule) n.model.agg_p_nom_max = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_max_rule) From 2f728b33c826f98596ac0d043ab7e72475a2b428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6rsch?= Date: Mon, 17 Jun 2019 23:33:06 +0200 Subject: [PATCH 07/10] solve_network: Move country/carrier constraints to opts as CCL --- scripts/solve_network.py | 51 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 29d2714f..52844e63 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -77,35 +77,35 @@ 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_country_carrier_generation_constraints(n): - agg_p_nom_limits = snakemake.config['electricity'].get('agg_p_nom_limits') - if agg_p_nom_limits is None: return - - try: - agg_p_nom_minmax = pd.read_csv(agg_p_nom_limits, index_col=list(range(2))) - except IOError: - logger.exception("Need to specify the path to a .csv file containing aggregate capacity limits per country in config['electricity']['agg_p_nom_limit'].") + # Add constraints on the per-carrier capacity in each country + if 'CCL' in opts: + agg_p_nom_limits = snakemake.config['electricity'].get('agg_p_nom_limits') - logger.info("Adding per carrier generation capacity constraints for individual countries") - - gen_country = n.generators.bus.map(n.buses.country) + try: + agg_p_nom_minmax = pd.read_csv(agg_p_nom_limits, index_col=list(range(2))) + except IOError: + logger.exception("Need to specify the path to a .csv file containing aggregate capacity limits per country in config['electricity']['agg_p_nom_limit'].") - def agg_p_nom_min_rule(model, country, carrier): - min = agg_p_nom_minmax.at[(country, carrier), 'min'] - return ((sum(model.generator_p_nom[gen] - for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) - <= min) - if np.isfinite(min) else po.Constraint.Skip) + logger.info("Adding per carrier generation capacity constraints for individual countries") - def agg_p_nom_max_rule(model, country, carrier): - max = agg_p_nom_minmax.at[(country, carrier), 'max'] - return ((sum(model.generator_p_nom[gen] - for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) - <= max) - if np.isfinite(max) else po.Constraint.Skip) + gen_country = n.generators.bus.map(n.buses.country) - n.model.agg_p_nom_min = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_min_rule) - n.model.agg_p_nom_max = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_max_rule) + def agg_p_nom_min_rule(model, country, carrier): + min = agg_p_nom_minmax.at[(country, carrier), 'min'] + return ((sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) + <= min) + if np.isfinite(min) else po.Constraint.Skip) + + def agg_p_nom_max_rule(model, country, carrier): + max = agg_p_nom_minmax.at[(country, carrier), 'max'] + return ((sum(model.generator_p_nom[gen] + for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) + <= max) + if np.isfinite(max) else po.Constraint.Skip) + + n.model.agg_p_nom_min = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_min_rule) + n.model.agg_p_nom_max = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_max_rule) def add_lv_constraint(n): line_volume = getattr(n, 'line_volume_limit', None) @@ -176,7 +176,6 @@ def solve_network(n, config=None, solver_log=None, opts=None, callback=None): pypsa.opf.network_lopf_build_model(n, formulation=solve_opts['formulation']) - add_country_carrier_generation_constraints(n, opts) add_opts_constraints(n, opts) if not fix_ext_lines: From ffe7cf7db216c6741c55bf1b20d2d1b39154efa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6rsch?= Date: Mon, 17 Jun 2019 23:39:55 +0200 Subject: [PATCH 08/10] agg_p_nom_min_max.csv: Skip unconstrained values --- data/agg_p_nom_minmax.csv | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/data/agg_p_nom_minmax.csv b/data/agg_p_nom_minmax.csv index 1d04e20c..111215bc 100644 --- a/data/agg_p_nom_minmax.csv +++ b/data/agg_p_nom_minmax.csv @@ -1,31 +1,31 @@ country,carrier,min,max -DE,onwind,0.1,1.00E+09 -DE,offwind-ac,0.1,1.00E+09 -DE,offwind-dc,0.1,1.00E+09 -DE,solar,0.2,1.00E+09 -LU,onwind,0,1.00E+09 -LU,solar,0,1.00E+09 -NL,onwind,0,1.00E+09 -NL,offwind-ac,0,1.00E+09 -NL,offwind-dc,0,1.00E+09 -NL,solar,0,1.00E+09 -GB,onwind,0,1.00E+09 -GB,offwind-ac,0,1.00E+09 -GB,offwind-dc,0,1.00E+09 -GB,solar,0,1.00E+09 -IE,onwind,0,1.00E+09 -IE,offwind-ac,0,1.00E+09 -IE,offwind-dc,0,1.00E+09 -IE,solar,0,1.00E+09 -FR,onwind,0,1.00E+09 -FR,offwind-ac,0,1.00E+09 -FR,offwind-dc,0,1.00E+09 -FR,solar,0,1.00E+09 -DK,onwind,0,1.00E+09 -DK,offwind-ac,0,1.00E+09 -DK,offwind-dc,0,1.00E+09 -DK,solar,0,1.00E+09 -BE,onwind,0,1.00E+09 -BE,offwind-ac,0,1.00E+09 -BE,offwind-dc,0,1.00E+09 -BE,solar,0,1.00E+09 +DE,onwind,0.1, +DE,offwind-ac,0.1, +DE,offwind-dc,0.1, +DE,solar,0.2, +LU,onwind,, +LU,solar,, +NL,onwind,, +NL,offwind-ac,, +NL,offwind-dc,, +NL,solar,, +GB,onwind,, +GB,offwind-ac,, +GB,offwind-dc,, +GB,solar,, +IE,onwind,, +IE,offwind-ac,, +IE,offwind-dc,, +IE,solar,, +FR,onwind,, +FR,offwind-ac,, +FR,offwind-dc,, +FR,solar,, +DK,onwind,, +DK,offwind-ac,, +DK,offwind-dc,, +DK,solar,, +BE,onwind,, +BE,offwind-ac,, +BE,offwind-dc,, +BE,solar,, From ac32f2cad69cc6eda8cf294f4205e06e2332af2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6rsch?= Date: Tue, 18 Jun 2019 08:27:04 +0200 Subject: [PATCH 09/10] Fix typo introduced in previous commit --- scripts/solve_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 52844e63..c7b8978a 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -94,7 +94,7 @@ def add_opts_constraints(n, opts=None): min = agg_p_nom_minmax.at[(country, carrier), 'min'] return ((sum(model.generator_p_nom[gen] for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) - <= min) + >= min) if np.isfinite(min) else po.Constraint.Skip) def agg_p_nom_max_rule(model, country, carrier): From 97985e3c5219a75392291125914225d116ba81e9 Mon Sep 17 00:00:00 2001 From: Jeroen Peters Date: Tue, 18 Jun 2019 11:25:59 +0200 Subject: [PATCH 10/10] Fix by changing po.Constraint.Skip into pypsa.opt.Constraint.Skip --- scripts/solve_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index c7b8978a..e20a8398 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -95,14 +95,14 @@ def add_opts_constraints(n, opts=None): return ((sum(model.generator_p_nom[gen] for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) >= min) - if np.isfinite(min) else po.Constraint.Skip) + if np.isfinite(min) else pypsa.opt.Constraint.Skip) def agg_p_nom_max_rule(model, country, carrier): max = agg_p_nom_minmax.at[(country, carrier), 'max'] return ((sum(model.generator_p_nom[gen] for gen in n.generators.index[(gen_country == country) & (n.generators.carrier == carrier)]) <= max) - if np.isfinite(max) else po.Constraint.Skip) + if np.isfinite(max) else pypsa.opt.Constraint.Skip) n.model.agg_p_nom_min = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_min_rule) n.model.agg_p_nom_max = pypsa.opt.Constraint(list(agg_p_nom_minmax.index), rule=agg_p_nom_max_rule)