From 94e45168f89cafeee9f45d908a103c724a2e19ed Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:26:09 +0200 Subject: [PATCH 01/21] Add solar-hsat to config 1. Solar PV with single-axis tracking is added as 'solar-hast' to carriers. 2. Capacity factors are taken from atlite. Land-use is 15% higher acc. to NREL (Land-Use Requirements for Solar Power Plants in the United States). 3. The option for adding solar-hast to the network is set to 'True' as default. 4. A color is added for solar-hsat --- config/config.default.yaml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 0db6dc037..c00ca0b3e 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -111,7 +111,7 @@ electricity: H2: 168 extendable_carriers: - Generator: [solar, onwind, offwind-ac, offwind-dc, OCGT] + Generator: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, OCGT] StorageUnit: [] # battery, H2 Store: [battery, H2] Link: [] # H2 pipeline @@ -121,7 +121,7 @@ electricity: everywhere_powerplants: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] conventional_carriers: [nuclear, oil, OCGT, CCGT, coal, lignite, geothermal, biomass] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc, hydro] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, hydro] estimate_renewable_capacities: enable: true @@ -232,6 +232,21 @@ renewable: natura: true excluder_resolution: 100 clip_p_max_pu: 1.e-2 + solar-hsat: + cutout: europe-2013-sarah + resource: + method: pv + panel: CSi + orientation: + slope: 35. + azimuth: 180. + tracking: horizontal + capacity_per_sqkm: 4.43 # 15% higher land usage acc. to NREL + corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32] + luisa: false # [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242, 1310, 1320, 1330, 1410, 1421, 1422, 2110, 2120, 2130, 2210, 2220, 2230, 2310, 2410, 2420, 3210, 3320, 3330] + natura: true + excluder_resolution: 100 + clip_p_max_pu: 1.e-2 hydro: cutout: europe-2013-era5 carriers: [ror, PHS, hydro] @@ -311,6 +326,7 @@ pypsa_eur: - offwind-ac - offwind-dc - solar + - solar-hsat - ror - nuclear StorageUnit: @@ -573,6 +589,7 @@ sector: biogas_upgrading_cc: false conventional_generation: OCGT: gas + solar_utility_singla_axis_tracking : true biomass_to_liquid: false biosng: false limit_max_growth: @@ -890,6 +907,7 @@ plotting: # solar solar: "#f9d002" solar PV: "#f9d002" + solar-hsat: "#fdb915" solar thermal: '#ffbf2b' residential rural solar thermal: '#f1c069' services rural solar thermal: '#eabf61' From 91de7a891d649a8111c940f7afd94a0d6a27ffc7 Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:28:33 +0200 Subject: [PATCH 02/21] Update rule: determine_availability_matrix Add solar-hsat as an Onshore technology --- rules/build_electricity.smk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index ed341d2f8..fac383d9c 100644 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -233,7 +233,7 @@ rule determine_availability_matrix_MD_UA: offshore_shapes=resources("offshore_shapes.geojson"), regions=lambda w: ( resources("regions_onshore.geojson") - if w.technology in ("onwind", "solar") + if w.technology in ("onwind", "solar", "solar-hsat") else resources("regions_offshore.geojson") ), cutout=lambda w: "cutouts/" From b9cb82cc3bc8608939f611a32d1b242126f61e85 Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:30:42 +0200 Subject: [PATCH 03/21] Update build_renewable_profiles Add solar-hsat to Onshore technologies --- rules/build_electricity.smk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index fac383d9c..afe0db8e6 100644 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -301,7 +301,7 @@ rule build_renewable_profiles: offshore_shapes=resources("offshore_shapes.geojson"), regions=lambda w: ( resources("regions_onshore.geojson") - if w.technology in ("onwind", "solar") + if w.technology in ("onwind", "solar", "solar-hsat") else resources("regions_offshore.geojson") ), cutout=lambda w: "cutouts/" From f0187437c106db0e1b394b6b90e991f3cd9e991d Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:32:58 +0200 Subject: [PATCH 04/21] Update costs Rename 'solar-utility single-axis' (taken from technology data) to solar-hsat --- scripts/add_electricity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 7e60203fe..31ed10485 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -235,6 +235,8 @@ def load_costs(tech_costs, config, max_hours, Nyears=1.0): + (1 - config["rooftop_share"]) * costs.at["solar-utility", "capital_cost"] ) + costs= costs.rename({'solar-utility single-axis tracking':'solar-hsat'}) + def costs_for_storage(store, link1, link2=None, max_hours=1.0): capital_cost = link1["capital_cost"] + max_hours * store["capital_cost"] if link2 is not None: From 692d837b14592e56b92eaa8ec1a46091304068d4 Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:35:29 +0200 Subject: [PATCH 05/21] Update add_electricity_grid_connection Add solar-hsat as technology with a grid connection cost --- scripts/prepare_sector_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index b6ec98b2f..589c28d46 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1115,7 +1115,7 @@ def insert_gas_distribution_costs(n, costs): def add_electricity_grid_connection(n, costs): - carriers = ["onwind", "solar"] + carriers = ["onwind", "solar", "solar-hsat"] gens = n.generators.index[n.generators.carrier.isin(carriers)] From be876761f2f7e4727da6e2cf83e6b1b511738774 Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:37:53 +0200 Subject: [PATCH 06/21] Add remove option for solar-hsat Add function to remove solar-hsat if the config option is 'False' --- scripts/prepare_sector_network.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 589c28d46..20aa89596 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3319,7 +3319,13 @@ def remove_h2_network(n): if "EU H2 Store" in n.stores.index: n.stores.drop("EU H2 Store", inplace=True) +def remove_solar_tracking(n): + for tech in ['solar-hsat']: + print('removing '+tech) + n.mremove("Generator" , n.generators.index[n.generators.carrier==tech]) + + def limit_individual_line_extension(n, maxext): logger.info(f"Limiting new HVAC and HVDC extensions to {maxext} MW") n.lines["s_nom_max"] = n.lines["s_nom"] + maxext @@ -3696,6 +3702,9 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): if options["electricity_distribution_grid"]: insert_electricity_distribution_grid(n, costs) + if not options["solar_utility_singla_axis_tracking"]: + remove_solar_tracking(n) + maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"]) if options["gas_distribution_grid"]: From f69ec97a18eb29bd815145e87edea92f828fff21 Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:45:31 +0200 Subject: [PATCH 07/21] Add solar_potential constraint The constraint ensures the combined installed capacity of solar and solar-hsat does not exceed the total solar capacity of the node --- scripts/solve_network.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index dbde8813a..47a9c1e76 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -43,6 +43,7 @@ set_scenario_config, update_config_from_wildcards, ) +from functools import reduce from pypsa.descriptors import get_activity_mask from pypsa.descriptors import get_switchable_as_dense as get_as_dense @@ -198,6 +199,61 @@ def _add_land_use_constraint_m(n, planning_horizons, config): n.generators.p_nom_max.clip(lower=0, inplace=True) +def add_solar_potential_constraints(n, config): + """ + Add constraint to make sure the sum capacity of all solar technologies (fixed, tracking, ets. ) is below the region potential. + Example: + ES1 0: total solar potential is 10 GW, meaning: + solar potential : 10 GW + solar-hsat potential : 8 GW (solar with single axis tracking is assumed to have higher land use) + The constraint ensures that: + solar_p_nom + solar_hsat_p_nom * 1.13 <= 10 GW + """ + land_use_factors= { + 'solar-hsat' : config['renewable']['solar']['capacity_per_sqkm']/config['renewable']['solar-hsat']['capacity_per_sqkm'] , + } + gen_index = n.generators[n.generators.p_nom_extendable].index + + filters = [("solar", True), ("thermal", False), ("rooftop", False)] ## filter all utility solar generation except solar thermal + solar = reduce(lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], filters, gen_index) + solar_today = n.generators[(n.generators.carrier=='solar') & (n.generators.p_nom_extendable)].index + solar_hsat = n.generators[(n.generators.carrier=='solar-hsat') ].index + land_use = pd.DataFrame(1, index=solar, columns=['land_use_factor']) + for key in land_use_factors.keys(): + land_use = land_use.apply(lambda x: (x*land_use_factors[key]) if key in x.name else x, axis=1) + + rename = {"Generator-ext": "Generator"} + if "m" in snakemake.wildcards.clusters: + location = ( + pd.Series([' '.join(i.split(' ')[:2]) for i in n.generators.index], index=n.generators.index) + ) + ggrouper= pd.Series(n.generators.loc[solar].index.rename('bus').map(location), index=n.generators.loc[solar].index,).to_xarray() + rhs = (n.generators.loc[solar_today,"p_nom_max"] + .groupby(n.generators.loc[solar_today].index.rename('bus').map(location)).sum() - + n.generators.loc[solar_hsat,"p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].index.rename('bus').map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + + else : + location = ( + n.buses.location + if "location" in n.buses.columns + else pd.Series(n.buses.index, index=n.buses.index) + ) + ggrouper= (n.generators.loc[solar].bus) + rhs = (n.generators.loc[solar_today,"p_nom_max"] + .groupby(n.generators.loc[solar_today].bus.map(location)).sum() - + n.generators.loc[solar_hsat,"p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].bus.map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + + lhs = ( + (n.model["Generator-p_nom"].rename(rename).loc[solar] + *land_use.squeeze().values) + .groupby(ggrouper) + .sum() + ) + + print('adding solar rooftop constraints...') + n.model.add_constraints(lhs <= rhs, name="solar_potential") def add_co2_sequestration_limit(n, limit=200): """ From 9a55c8fa7bd62344aa86a5fb1a57e056a600466f Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:47:30 +0200 Subject: [PATCH 08/21] Update potential constraint Only implement constraint if solar-hsat option is selected in config --- scripts/solve_network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 47a9c1e76..0d5ce1e6d 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -918,6 +918,10 @@ def extra_functionality(n, snapshots): if EQ_o := constraints["EQ"]: add_EQ_constraints(n, EQ_o.replace("EQ", "")) + + if config["sector"]['solar_utility_singla_axis_tracking']: + add_solar_potential_constraints(n, config) + add_battery_constraints(n) add_lossy_bidirectional_link_constraints(n) add_pipe_retrofit_constraint(n) From fde0cd9aae16deccb30745330045dae9f500ac9d Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:49:14 +0200 Subject: [PATCH 09/21] Update land_use constraints Add solar-hsat and solar-rooftop to land use constraints --- 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 0d5ce1e6d..8048af61e 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -124,7 +124,7 @@ def check_p_min_p_max(p_nom_max): def _add_land_use_constraint(n): # warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind' - for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc"]: + for carrier in ["solar", "solar rooftop", "solar-hsat", "onwind", "offwind-ac", "offwind-dc"]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -159,7 +159,7 @@ def _add_land_use_constraint_m(n, planning_horizons, config): grouping_years = config["existing_capacities"]["grouping_years_power"] current_horizon = snakemake.wildcards.planning_horizons - for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc"]: + for carrier in ["solar", "solar rooftop", "solar-hsat", "onwind", "offwind-ac", "offwind-dc"]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 From 3e93c14dd4cac4cdba266315b29ed8ef531885b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:32:22 +0000 Subject: [PATCH 10/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/config.default.yaml | 6 +- scripts/add_electricity.py | 2 +- scripts/prepare_sector_network.py | 11 +-- scripts/solve_network.py | 116 ++++++++++++++++++++---------- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index c00ca0b3e..11890db06 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -240,13 +240,13 @@ renewable: orientation: slope: 35. azimuth: 180. - tracking: horizontal + tracking: horizontal capacity_per_sqkm: 4.43 # 15% higher land usage acc. to NREL corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32] luisa: false # [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242, 1310, 1320, 1330, 1410, 1421, 1422, 2110, 2120, 2130, 2210, 2220, 2230, 2310, 2410, 2420, 3210, 3320, 3330] natura: true excluder_resolution: 100 - clip_p_max_pu: 1.e-2 + clip_p_max_pu: 1.e-2 hydro: cutout: europe-2013-era5 carriers: [ror, PHS, hydro] @@ -589,7 +589,7 @@ sector: biogas_upgrading_cc: false conventional_generation: OCGT: gas - solar_utility_singla_axis_tracking : true + solar_utility_singla_axis_tracking: true biomass_to_liquid: false biosng: false limit_max_growth: diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 31ed10485..374e40aae 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -235,7 +235,7 @@ def load_costs(tech_costs, config, max_hours, Nyears=1.0): + (1 - config["rooftop_share"]) * costs.at["solar-utility", "capital_cost"] ) - costs= costs.rename({'solar-utility single-axis tracking':'solar-hsat'}) + costs = costs.rename({"solar-utility single-axis tracking": "solar-hsat"}) def costs_for_storage(store, link1, link2=None, max_hours=1.0): capital_cost = link1["capital_cost"] + max_hours * store["capital_cost"] diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 20aa89596..f5eecf3db 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3319,13 +3319,14 @@ def remove_h2_network(n): if "EU H2 Store" in n.stores.index: n.stores.drop("EU H2 Store", inplace=True) + def remove_solar_tracking(n): - for tech in ['solar-hsat']: - print('removing '+tech) - n.mremove("Generator" , n.generators.index[n.generators.carrier==tech]) + for tech in ["solar-hsat"]: + print("removing " + tech) + n.mremove("Generator", n.generators.index[n.generators.carrier == tech]) + - def limit_individual_line_extension(n, maxext): logger.info(f"Limiting new HVAC and HVDC extensions to {maxext} MW") n.lines["s_nom_max"] = n.lines["s_nom"] + maxext @@ -3703,7 +3704,7 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): insert_electricity_distribution_grid(n, costs) if not options["solar_utility_singla_axis_tracking"]: - remove_solar_tracking(n) + remove_solar_tracking(n) maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"]) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 8048af61e..08521273f 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -31,6 +31,7 @@ import os import re import sys +from functools import reduce import numpy as np import pandas as pd @@ -43,7 +44,6 @@ set_scenario_config, update_config_from_wildcards, ) -from functools import reduce from pypsa.descriptors import get_activity_mask from pypsa.descriptors import get_switchable_as_dense as get_as_dense @@ -124,7 +124,14 @@ def check_p_min_p_max(p_nom_max): def _add_land_use_constraint(n): # warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind' - for carrier in ["solar", "solar rooftop", "solar-hsat", "onwind", "offwind-ac", "offwind-dc"]: + for carrier in [ + "solar", + "solar rooftop", + "solar-hsat", + "onwind", + "offwind-ac", + "offwind-dc", + ]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -159,7 +166,14 @@ def _add_land_use_constraint_m(n, planning_horizons, config): grouping_years = config["existing_capacities"]["grouping_years_power"] current_horizon = snakemake.wildcards.planning_horizons - for carrier in ["solar", "solar rooftop", "solar-hsat", "onwind", "offwind-ac", "offwind-dc"]: + for carrier in [ + "solar", + "solar rooftop", + "solar-hsat", + "onwind", + "offwind-ac", + "offwind-dc", + ]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -199,7 +213,8 @@ def _add_land_use_constraint_m(n, planning_horizons, config): n.generators.p_nom_max.clip(lower=0, inplace=True) -def add_solar_potential_constraints(n, config): + +def add_solar_potential_constraints(n, config): """ Add constraint to make sure the sum capacity of all solar technologies (fixed, tracking, ets. ) is below the region potential. Example: @@ -209,51 +224,81 @@ def add_solar_potential_constraints(n, config): The constraint ensures that: solar_p_nom + solar_hsat_p_nom * 1.13 <= 10 GW """ - land_use_factors= { - 'solar-hsat' : config['renewable']['solar']['capacity_per_sqkm']/config['renewable']['solar-hsat']['capacity_per_sqkm'] , - } + land_use_factors = { + "solar-hsat": config["renewable"]["solar"]["capacity_per_sqkm"] + / config["renewable"]["solar-hsat"]["capacity_per_sqkm"], + } gen_index = n.generators[n.generators.p_nom_extendable].index - filters = [("solar", True), ("thermal", False), ("rooftop", False)] ## filter all utility solar generation except solar thermal - solar = reduce(lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], filters, gen_index) - solar_today = n.generators[(n.generators.carrier=='solar') & (n.generators.p_nom_extendable)].index - solar_hsat = n.generators[(n.generators.carrier=='solar-hsat') ].index - land_use = pd.DataFrame(1, index=solar, columns=['land_use_factor']) + filters = [ + ("solar", True), + ("thermal", False), + ("rooftop", False), + ] ## filter all utility solar generation except solar thermal + solar = reduce( + lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], + filters, + gen_index, + ) + solar_today = n.generators[ + (n.generators.carrier == "solar") & (n.generators.p_nom_extendable) + ].index + solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index + land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) for key in land_use_factors.keys(): - land_use = land_use.apply(lambda x: (x*land_use_factors[key]) if key in x.name else x, axis=1) + land_use = land_use.apply( + lambda x: (x * land_use_factors[key]) if key in x.name else x, axis=1 + ) rename = {"Generator-ext": "Generator"} if "m" in snakemake.wildcards.clusters: - location = ( - pd.Series([' '.join(i.split(' ')[:2]) for i in n.generators.index], index=n.generators.index) + location = pd.Series( + [" ".join(i.split(" ")[:2]) for i in n.generators.index], + index=n.generators.index, ) - ggrouper= pd.Series(n.generators.loc[solar].index.rename('bus').map(location), index=n.generators.loc[solar].index,).to_xarray() - rhs = (n.generators.loc[solar_today,"p_nom_max"] - .groupby(n.generators.loc[solar_today].index.rename('bus').map(location)).sum() - - n.generators.loc[solar_hsat,"p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].index.rename('bus').map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + ggrouper = pd.Series( + n.generators.loc[solar].index.rename("bus").map(location), + index=n.generators.loc[solar].index, + ).to_xarray() + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby(n.generators.loc[solar_today].index.rename("bus").map(location)) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) + .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) - else : + else: location = ( n.buses.location if "location" in n.buses.columns else pd.Series(n.buses.index, index=n.buses.index) ) - ggrouper= (n.generators.loc[solar].bus) - rhs = (n.generators.loc[solar_today,"p_nom_max"] - .groupby(n.generators.loc[solar_today].bus.map(location)).sum() - - n.generators.loc[solar_hsat,"p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].bus.map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + ggrouper = n.generators.loc[solar].bus + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby(n.generators.loc[solar_today].bus.map(location)) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].bus.map(location)) + .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) lhs = ( - (n.model["Generator-p_nom"].rename(rename).loc[solar] - *land_use.squeeze().values) - .groupby(ggrouper) - .sum() - ) + ( + n.model["Generator-p_nom"].rename(rename).loc[solar] + * land_use.squeeze().values + ) + .groupby(ggrouper) + .sum() + ) + + print("adding solar rooftop constraints...") + n.model.add_constraints(lhs <= rhs, name="solar_potential") - print('adding solar rooftop constraints...') - n.model.add_constraints(lhs <= rhs, name="solar_potential") def add_co2_sequestration_limit(n, limit=200): """ @@ -918,9 +963,8 @@ def extra_functionality(n, snapshots): if EQ_o := constraints["EQ"]: add_EQ_constraints(n, EQ_o.replace("EQ", "")) - - if config["sector"]['solar_utility_singla_axis_tracking']: - add_solar_potential_constraints(n, config) + if config["sector"]["solar_utility_singla_axis_tracking"]: + add_solar_potential_constraints(n, config) add_battery_constraints(n) add_lossy_bidirectional_link_constraints(n) From 7a2f07e135e8d9a699516d6f3b58ce5cb877d53a Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:33:51 +0200 Subject: [PATCH 11/21] Fix component index error The list of carriers for adjust_renewable_profiles is read from config['electricity']['renewable_carriers'] which includes 'solar-hsat' and would result in 'Components Index(['AL0 0 solar-hsat',...) for attribute p_max_pu of Generator are not in main components dataframe generators' warning for the myopic run if the solar tracking option is false. --- scripts/add_brownfield.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 16b4e0870..17f19c293 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -201,6 +201,8 @@ def adjust_renewable_profiles(n, input_profiles, params, year): for carrier in params["carriers"]: if carrier == "hydro": continue + if carrier =='solar-hsat' and not snakemake.config['sector']['solar_utility_singla_axis_tracking']: + continue with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: if ds.indexes["bus"].empty or "year" not in ds.indexes: continue From c66549c577469e60d1d406f643c154aba0d6060f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:34:08 +0000 Subject: [PATCH 12/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/add_brownfield.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 17f19c293..4559f80fc 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -201,8 +201,11 @@ def adjust_renewable_profiles(n, input_profiles, params, year): for carrier in params["carriers"]: if carrier == "hydro": continue - if carrier =='solar-hsat' and not snakemake.config['sector']['solar_utility_singla_axis_tracking']: - continue + if ( + carrier == "solar-hsat" + and not snakemake.config["sector"]["solar_utility_singla_axis_tracking"] + ): + continue with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: if ds.indexes["bus"].empty or "year" not in ds.indexes: continue From ea0e2f66ce41107addcc018bca05700c9ead542c Mon Sep 17 00:00:00 2001 From: Parisra <102019906+Parisra@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:58:42 +0200 Subject: [PATCH 13/21] Fix empty array error In case there are no extendable solar generators in the network, the constraint is not implemented. Without this condition, an 'empty array' error will arise. --- scripts/solve_network.py | 101 +++++++++++++++------------------------ 1 file changed, 38 insertions(+), 63 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 08521273f..de8c49353 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -228,76 +228,51 @@ def add_solar_potential_constraints(n, config): "solar-hsat": config["renewable"]["solar"]["capacity_per_sqkm"] / config["renewable"]["solar-hsat"]["capacity_per_sqkm"], } - gen_index = n.generators[n.generators.p_nom_extendable].index - - filters = [ - ("solar", True), - ("thermal", False), - ("rooftop", False), - ] ## filter all utility solar generation except solar thermal - solar = reduce( - lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], - filters, - gen_index, - ) - solar_today = n.generators[ - (n.generators.carrier == "solar") & (n.generators.p_nom_extendable) - ].index - solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index - land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) - for key in land_use_factors.keys(): - land_use = land_use.apply( - lambda x: (x * land_use_factors[key]) if key in x.name else x, axis=1 - ) - rename = {"Generator-ext": "Generator"} - if "m" in snakemake.wildcards.clusters: - location = pd.Series( - [" ".join(i.split(" ")[:2]) for i in n.generators.index], - index=n.generators.index, - ) - ggrouper = pd.Series( - n.generators.loc[solar].index.rename("bus").map(location), - index=n.generators.loc[solar].index, - ).to_xarray() - rhs = ( - n.generators.loc[solar_today, "p_nom_max"] - .groupby(n.generators.loc[solar_today].index.rename("bus").map(location)) - .sum() - - n.generators.loc[solar_hsat, "p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) - .sum() - * land_use_factors["solar-hsat"] - ).clip(lower=0) - else: - location = ( + gen_index = n.generators[n.generators.p_nom_extendable].index + filters = [("solar", True), ("thermal", False), ("rooftop", False)] ## filter all utility solar generation except solar thermal + solar = reduce(lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], filters, gen_index) + solar_today = n.generators[(n.generators.carrier=='solar') & (n.generators.p_nom_extendable)].index + solar_hsat = n.generators[(n.generators.carrier=='solar-hsat') ].index + + if not solar.empty : + + land_use = pd.DataFrame(1, index=solar, columns=['land_use_factor']) + for key in land_use_factors.keys(): + land_use = land_use.apply(lambda x: (x*land_use_factors[key]) if key in x.name else x, axis=1) + + if "m" in snakemake.wildcards.clusters: + location = ( + pd.Series([' '.join(i.split(' ')[:2]) for i in n.generators.index], index=n.generators.index) + ) + ggrouper= pd.Series(n.generators.loc[solar].index.rename('bus').map(location), index=n.generators.loc[solar].index,).to_xarray() + rhs = (n.generators.loc[solar_today,"p_nom_max"] + .groupby(n.generators.loc[solar_today].index.rename('bus').map(location)).sum() - + n.generators.loc[solar_hsat,"p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].index.rename('bus').map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + + else : + location = ( n.buses.location if "location" in n.buses.columns else pd.Series(n.buses.index, index=n.buses.index) - ) - ggrouper = n.generators.loc[solar].bus - rhs = ( - n.generators.loc[solar_today, "p_nom_max"] - .groupby(n.generators.loc[solar_today].bus.map(location)) - .sum() - - n.generators.loc[solar_hsat, "p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].bus.map(location)) - .sum() - * land_use_factors["solar-hsat"] - ).clip(lower=0) + ) + ggrouper= (n.generators.loc[solar].bus) + rhs = (n.generators.loc[solar_today,"p_nom_max"] + .groupby(n.generators.loc[solar_today].bus.map(location)).sum() - + n.generators.loc[solar_hsat,"p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].bus.map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) - lhs = ( - ( - n.model["Generator-p_nom"].rename(rename).loc[solar] - * land_use.squeeze().values - ) - .groupby(ggrouper) - .sum() - ) + lhs = ( + (n.model["Generator-p_nom"].rename(rename).loc[solar] + *land_use.squeeze().values) + .groupby(ggrouper) + .sum() + ) - print("adding solar rooftop constraints...") - n.model.add_constraints(lhs <= rhs, name="solar_potential") + print('adding solar rooftop constraints...') + n.model.add_constraints(lhs <= rhs, name="solar_potential") def add_co2_sequestration_limit(n, limit=200): From 2d2b0338cc23c534e1d4ffca58c0e8cd01cf79a0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:59:08 +0000 Subject: [PATCH 14/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/solve_network.py | 94 ++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index de8c49353..302f1be24 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -231,48 +231,78 @@ def add_solar_potential_constraints(n, config): rename = {"Generator-ext": "Generator"} gen_index = n.generators[n.generators.p_nom_extendable].index - filters = [("solar", True), ("thermal", False), ("rooftop", False)] ## filter all utility solar generation except solar thermal - solar = reduce(lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], filters, gen_index) - solar_today = n.generators[(n.generators.carrier=='solar') & (n.generators.p_nom_extendable)].index - solar_hsat = n.generators[(n.generators.carrier=='solar-hsat') ].index - - if not solar.empty : - - land_use = pd.DataFrame(1, index=solar, columns=['land_use_factor']) + filters = [ + ("solar", True), + ("thermal", False), + ("rooftop", False), + ] ## filter all utility solar generation except solar thermal + solar = reduce( + lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], + filters, + gen_index, + ) + solar_today = n.generators[ + (n.generators.carrier == "solar") & (n.generators.p_nom_extendable) + ].index + solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index + + if not solar.empty: + + land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) for key in land_use_factors.keys(): - land_use = land_use.apply(lambda x: (x*land_use_factors[key]) if key in x.name else x, axis=1) + land_use = land_use.apply( + lambda x: (x * land_use_factors[key]) if key in x.name else x, axis=1 + ) if "m" in snakemake.wildcards.clusters: - location = ( - pd.Series([' '.join(i.split(' ')[:2]) for i in n.generators.index], index=n.generators.index) + location = pd.Series( + [" ".join(i.split(" ")[:2]) for i in n.generators.index], + index=n.generators.index, ) - ggrouper= pd.Series(n.generators.loc[solar].index.rename('bus').map(location), index=n.generators.loc[solar].index,).to_xarray() - rhs = (n.generators.loc[solar_today,"p_nom_max"] - .groupby(n.generators.loc[solar_today].index.rename('bus').map(location)).sum() - - n.generators.loc[solar_hsat,"p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].index.rename('bus').map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) - - else : + ggrouper = pd.Series( + n.generators.loc[solar].index.rename("bus").map(location), + index=n.generators.loc[solar].index, + ).to_xarray() + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby( + n.generators.loc[solar_today].index.rename("bus").map(location) + ) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) + .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) + + else: location = ( - n.buses.location - if "location" in n.buses.columns - else pd.Series(n.buses.index, index=n.buses.index) + n.buses.location + if "location" in n.buses.columns + else pd.Series(n.buses.index, index=n.buses.index) ) - ggrouper= (n.generators.loc[solar].bus) - rhs = (n.generators.loc[solar_today,"p_nom_max"] - .groupby(n.generators.loc[solar_today].bus.map(location)).sum() - - n.generators.loc[solar_hsat,"p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].bus.map(location)).sum() * land_use_factors['solar-hsat'] ).clip(lower=0) + ggrouper = n.generators.loc[solar].bus + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby(n.generators.loc[solar_today].bus.map(location)) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].bus.map(location)) + .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) lhs = ( - (n.model["Generator-p_nom"].rename(rename).loc[solar] - *land_use.squeeze().values) - .groupby(ggrouper) + ( + n.model["Generator-p_nom"].rename(rename).loc[solar] + * land_use.squeeze().values + ) + .groupby(ggrouper) .sum() - ) + ) - print('adding solar rooftop constraints...') - n.model.add_constraints(lhs <= rhs, name="solar_potential") + print("adding solar rooftop constraints...") + n.model.add_constraints(lhs <= rhs, name="solar_potential") def add_co2_sequestration_limit(n, limit=200): From a9be2825ecfa043166883826bbf5d258a7be51da Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 14:59:34 +0200 Subject: [PATCH 15/21] reforumlate constraint --- scripts/solve_network.py | 123 ++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 66 deletions(-) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 302f1be24..665274b74 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -230,79 +230,70 @@ def add_solar_potential_constraints(n, config): } rename = {"Generator-ext": "Generator"} - gen_index = n.generators[n.generators.p_nom_extendable].index - filters = [ - ("solar", True), - ("thermal", False), - ("rooftop", False), - ] ## filter all utility solar generation except solar thermal - solar = reduce( - lambda gen_index, f: gen_index[gen_index.str.contains(f[0]) == f[1]], - filters, - gen_index, - ) + solar_carriers = ["solar", "solar-hsat"] + solar = n.generators[n.generators.carrier.isin(solar_carriers) + & n.generators.p_nom_extendable].index + solar_today = n.generators[ (n.generators.carrier == "solar") & (n.generators.p_nom_extendable) ].index solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index - if not solar.empty: + if solar.empty: return - land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) - for key in land_use_factors.keys(): - land_use = land_use.apply( - lambda x: (x * land_use_factors[key]) if key in x.name else x, axis=1 - ) + land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) + for carrier, factor in land_use_factors.items(): + land_use = land_use.apply( + lambda x: (x * factor) if carrier in x.name else x, axis=1 + ) - if "m" in snakemake.wildcards.clusters: - location = pd.Series( - [" ".join(i.split(" ")[:2]) for i in n.generators.index], - index=n.generators.index, - ) - ggrouper = pd.Series( - n.generators.loc[solar].index.rename("bus").map(location), - index=n.generators.loc[solar].index, - ).to_xarray() - rhs = ( - n.generators.loc[solar_today, "p_nom_max"] - .groupby( - n.generators.loc[solar_today].index.rename("bus").map(location) - ) - .sum() - - n.generators.loc[solar_hsat, "p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) - .sum() - * land_use_factors["solar-hsat"] - ).clip(lower=0) - - else: - location = ( - n.buses.location - if "location" in n.buses.columns - else pd.Series(n.buses.index, index=n.buses.index) + if "m" in snakemake.wildcards.clusters: + location = pd.Series( + [" ".join(i.split(" ")[:2]) for i in n.generators.index], + index=n.generators.index, + ) + ggrouper = pd.Series( + n.generators.loc[solar].index.rename("bus").map(location), + index=n.generators.loc[solar].index, + ).to_xarray() + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby( + n.generators.loc[solar_today].index.rename("bus").map(location) ) - ggrouper = n.generators.loc[solar].bus - rhs = ( - n.generators.loc[solar_today, "p_nom_max"] - .groupby(n.generators.loc[solar_today].bus.map(location)) - .sum() - - n.generators.loc[solar_hsat, "p_nom_opt"] - .groupby(n.generators.loc[solar_hsat].bus.map(location)) - .sum() - * land_use_factors["solar-hsat"] - ).clip(lower=0) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) + .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) - lhs = ( - ( - n.model["Generator-p_nom"].rename(rename).loc[solar] - * land_use.squeeze().values - ) - .groupby(ggrouper) + else: + location = ( + pd.Series(n.buses.index, index=n.buses.index) + ) + ggrouper = n.generators.loc[solar].bus + rhs = ( + n.generators.loc[solar_today, "p_nom_max"] + .groupby(n.generators.loc[solar_today].bus.map(location)) + .sum() + - n.generators.loc[solar_hsat, "p_nom_opt"] + .groupby(n.generators.loc[solar_hsat].bus.map(location)) .sum() + * land_use_factors["solar-hsat"] + ).clip(lower=0) + + lhs = ( + ( + n.model["Generator-p_nom"].rename(rename).loc[solar] + * land_use.squeeze() ) + .groupby(ggrouper) + .sum() + ) - print("adding solar rooftop constraints...") - n.model.add_constraints(lhs <= rhs, name="solar_potential") + print("adding solar rooftop constraints...") + n.model.add_constraints(lhs <= rhs, name="solar_potential") def add_co2_sequestration_limit(n, limit=200): @@ -1053,13 +1044,13 @@ def solve_network(n, config, solving, **kwargs): from _helpers import mock_snakemake snakemake = mock_snakemake( - "solve_sector_network", - configfiles="../config/test/config.perfect.yaml", + "solve_network", + configfiles="../config/test/config.electricity.yaml", simpl="", opts="", - clusters="37", - ll="v1.0", - sector_opts="CO2L0-1H-T-H-B-I-A-dist1", + clusters="5", + ll="v1.5", + sector_opts="Co2L-24h", planning_horizons="2030", ) configure_logging(snakemake) From 541e267a838ff74fa29516597b6301e3200effab Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 15:00:42 +0200 Subject: [PATCH 16/21] replace print with logger.info --- scripts/prepare_sector_network.py | 2 +- scripts/solve_network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f5eecf3db..f6f41876a 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3323,7 +3323,7 @@ def remove_h2_network(n): def remove_solar_tracking(n): for tech in ["solar-hsat"]: - print("removing " + tech) + logger.info("removing " + tech) n.mremove("Generator", n.generators.index[n.generators.carrier == tech]) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 665274b74..b0cef3ca2 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -292,7 +292,7 @@ def add_solar_potential_constraints(n, config): .sum() ) - print("adding solar rooftop constraints...") + logger.info("Adding solar potential constraint.") n.model.add_constraints(lhs <= rhs, name="solar_potential") From 6723191031b9e4b873d8431fa0d98d5eb7fc11f4 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 15:04:04 +0200 Subject: [PATCH 17/21] simplify if statement --- scripts/add_brownfield.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 4559f80fc..7b0c4d39b 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -199,13 +199,10 @@ def adjust_renewable_profiles(n, input_profiles, params, year): ) for carrier in params["carriers"]: - if carrier == "hydro": - continue - if ( - carrier == "solar-hsat" - and not snakemake.config["sector"]["solar_utility_singla_axis_tracking"] - ): - continue + if carrier == "hydro" or (carrier == "solar-hsat" and + not snakemake.config["sector"]["solar_utility_singla_axis_tracking"] + ): continue + with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: if ds.indexes["bus"].empty or "year" not in ds.indexes: continue From e4bc92592c4bb813d940ca47e18673756e312703 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 15:06:32 +0200 Subject: [PATCH 18/21] add solar-hsat to tests --- config/test/config.electricity.yaml | 3 ++- config/test/config.myopic.yaml | 2 +- config/test/config.overnight.yaml | 2 +- config/test/config.perfect.yaml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config/test/config.electricity.yaml b/config/test/config.electricity.yaml index 579644157..c58965c80 100644 --- a/config/test/config.electricity.yaml +++ b/config/test/config.electricity.yaml @@ -32,7 +32,8 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc] + atlite: diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index 5abae36d0..2f7cede7c 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -42,7 +42,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc] atlite: default_cutout: be-03-2013-era5 diff --git a/config/test/config.overnight.yaml b/config/test/config.overnight.yaml index 7fb53e427..864e89d06 100644 --- a/config/test/config.overnight.yaml +++ b/config/test/config.overnight.yaml @@ -36,7 +36,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc] atlite: default_cutout: be-03-2013-era5 diff --git a/config/test/config.perfect.yaml b/config/test/config.perfect.yaml index 5d77c9c51..aacf552b8 100644 --- a/config/test/config.perfect.yaml +++ b/config/test/config.perfect.yaml @@ -39,7 +39,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar, onwind, offwind-ac, offwind-dc] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc] sector: min_part_load_fischer_tropsch: 0 From e8de4f308f25e656fc6957b8c6a5d8b9ca5e31b7 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 15:08:52 +0200 Subject: [PATCH 19/21] rename solar-hsat in config --- config/config.default.yaml | 2 +- scripts/add_brownfield.py | 2 +- scripts/prepare_sector_network.py | 2 +- scripts/solve_network.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 11890db06..1fd69b33d 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -589,7 +589,7 @@ sector: biogas_upgrading_cc: false conventional_generation: OCGT: gas - solar_utility_singla_axis_tracking: true + solar_utility_horizontal_axis_tracking: true biomass_to_liquid: false biosng: false limit_max_growth: diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 7b0c4d39b..f1874b242 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -200,7 +200,7 @@ def adjust_renewable_profiles(n, input_profiles, params, year): for carrier in params["carriers"]: if carrier == "hydro" or (carrier == "solar-hsat" and - not snakemake.config["sector"]["solar_utility_singla_axis_tracking"] + not snakemake.config["sector"]["solar_utility_horizontal_axis_tracking"] ): continue with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f6f41876a..53a8c9613 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -3703,7 +3703,7 @@ def lossy_bidirectional_links(n, carrier, efficiencies={}): if options["electricity_distribution_grid"]: insert_electricity_distribution_grid(n, costs) - if not options["solar_utility_singla_axis_tracking"]: + if not options["solar_utility_horizontal_axis_tracking"]: remove_solar_tracking(n) maybe_adjust_costs_and_potentials(n, snakemake.params["adjustments"]) diff --git a/scripts/solve_network.py b/scripts/solve_network.py index b0cef3ca2..822d226d8 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -959,7 +959,7 @@ def extra_functionality(n, snapshots): if EQ_o := constraints["EQ"]: add_EQ_constraints(n, EQ_o.replace("EQ", "")) - if config["sector"]["solar_utility_singla_axis_tracking"]: + if config["sector"]["solar_utility_horizontal_axis_tracking"]: add_solar_potential_constraints(n, config) add_battery_constraints(n) From 6a4e49d5cbe1d26c3e0f1899bebe70dc172bbcf1 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 15 May 2024 16:14:40 +0200 Subject: [PATCH 20/21] add release notes --- doc/release_notes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 6605f9a45..b653d0e87 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -9,6 +9,9 @@ Release Notes Upcoming Release ================ +* New technology, solar PV with single-axis horizontal tracking (on a N-S axis), + with a carrier called ``solar-hsat`` to the networks. The default option for adding + this technology is set to ``true`` in the ``config.yaml``. * The technology-data version was updated to v0.9.0. From a352cc346b57912cac9734148c4b6e70648fc23c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 14:18:16 +0000 Subject: [PATCH 21/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- config/test/config.myopic.yaml | 4 +-- config/test/config.overnight.yaml | 2 +- config/test/config.perfect.yaml | 2 +- scripts/add_brownfield.py | 8 +++--- scripts/solve_network.py | 44 ++++++++++++++++++------------- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/config/test/config.myopic.yaml b/config/test/config.myopic.yaml index 9cc5a293b..3a3a78562 100644 --- a/config/test/config.myopic.yaml +++ b/config/test/config.myopic.yaml @@ -42,7 +42,7 @@ electricity: Store: [H2] Link: [H2 pipeline] - renewable_carriers: [solar,solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] + renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, offwind-float] atlite: default_cutout: be-03-2013-era5 @@ -69,7 +69,7 @@ renewable: solar: cutout: be-03-2013-era5 solar-hsat: - cutout: be-03-2013-era5 + cutout: be-03-2013-era5 clustering: temporal: diff --git a/config/test/config.overnight.yaml b/config/test/config.overnight.yaml index e1da7a770..92379ae27 100644 --- a/config/test/config.overnight.yaml +++ b/config/test/config.overnight.yaml @@ -63,7 +63,7 @@ renewable: solar: cutout: be-03-2013-era5 solar-hsat: - cutout: be-03-2013-era5 + cutout: be-03-2013-era5 clustering: temporal: diff --git a/config/test/config.perfect.yaml b/config/test/config.perfect.yaml index 3170611d7..781b3fd49 100644 --- a/config/test/config.perfect.yaml +++ b/config/test/config.perfect.yaml @@ -70,7 +70,7 @@ renewable: solar: cutout: be-03-2013-era5 solar-hsat: - cutout: be-03-2013-era5 + cutout: be-03-2013-era5 clustering: temporal: diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index 1c7a62c80..f54b24ddb 100644 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -195,9 +195,11 @@ def adjust_renewable_profiles(n, input_profiles, params, year): ) for carrier in params["carriers"]: - if carrier == "hydro" or (carrier == "solar-hsat" and - not snakemake.config["sector"]["solar_utility_horizontal_axis_tracking"] - ): continue + if carrier == "hydro" or ( + carrier == "solar-hsat" + and not snakemake.config["sector"]["solar_utility_horizontal_axis_tracking"] + ): + continue with xr.open_dataset(getattr(input_profiles, "profile_" + carrier)) as ds: if ds.indexes["bus"].empty or "year" not in ds.indexes: diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 30275926f..92028f1ec 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -124,8 +124,15 @@ def check_p_min_p_max(p_nom_max): def _add_land_use_constraint(n): # warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind' - for carrier in ["solar","solar rooftop", - "solar-hsat", "onwind", "offwind-ac", "offwind-dc", "offwind-float"]: + for carrier in [ + "solar", + "solar rooftop", + "solar-hsat", + "onwind", + "offwind-ac", + "offwind-dc", + "offwind-float", + ]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -160,8 +167,14 @@ def _add_land_use_constraint_m(n, planning_horizons, config): grouping_years = config["existing_capacities"]["grouping_years_power"] current_horizon = snakemake.wildcards.planning_horizons - for carrier in ["solar", "solar rooftop", - "solar-hsat", "onwind", "offwind-ac", "offwind-dc"]: + for carrier in [ + "solar", + "solar rooftop", + "solar-hsat", + "onwind", + "offwind-ac", + "offwind-dc", + ]: extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable n.generators.loc[extendable_i, "p_nom_min"] = 0 @@ -219,15 +232,17 @@ def add_solar_potential_constraints(n, config): rename = {"Generator-ext": "Generator"} solar_carriers = ["solar", "solar-hsat"] - solar = n.generators[n.generators.carrier.isin(solar_carriers) - & n.generators.p_nom_extendable].index - + solar = n.generators[ + n.generators.carrier.isin(solar_carriers) & n.generators.p_nom_extendable + ].index + solar_today = n.generators[ (n.generators.carrier == "solar") & (n.generators.p_nom_extendable) ].index solar_hsat = n.generators[(n.generators.carrier == "solar-hsat")].index - if solar.empty: return + if solar.empty: + return land_use = pd.DataFrame(1, index=solar, columns=["land_use_factor"]) for carrier, factor in land_use_factors.items(): @@ -246,9 +261,7 @@ def add_solar_potential_constraints(n, config): ).to_xarray() rhs = ( n.generators.loc[solar_today, "p_nom_max"] - .groupby( - n.generators.loc[solar_today].index.rename("bus").map(location) - ) + .groupby(n.generators.loc[solar_today].index.rename("bus").map(location)) .sum() - n.generators.loc[solar_hsat, "p_nom_opt"] .groupby(n.generators.loc[solar_hsat].index.rename("bus").map(location)) @@ -257,9 +270,7 @@ def add_solar_potential_constraints(n, config): ).clip(lower=0) else: - location = ( - pd.Series(n.buses.index, index=n.buses.index) - ) + location = pd.Series(n.buses.index, index=n.buses.index) ggrouper = n.generators.loc[solar].bus rhs = ( n.generators.loc[solar_today, "p_nom_max"] @@ -272,10 +283,7 @@ def add_solar_potential_constraints(n, config): ).clip(lower=0) lhs = ( - ( - n.model["Generator-p_nom"].rename(rename).loc[solar] - * land_use.squeeze() - ) + (n.model["Generator-p_nom"].rename(rename).loc[solar] * land_use.squeeze()) .groupby(ggrouper) .sum() )