From 6cd46bf26125f7e08d99d8f52c15709e06c6fcee Mon Sep 17 00:00:00 2001 From: LukasFrankenQ Date: Sat, 11 Nov 2023 23:15:41 +0000 Subject: [PATCH] added option for hourly egs capacity factors --- config/config.default.yaml | 2 + rules/build_sector.smk | 13 ++++++- scripts/build_egs_potentials.py | 67 +++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index 1a35c5b1..c00c6e25 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -511,6 +511,8 @@ sector: enhanced_geothermal: true enhanced_geothermal_optimism: false # if true, egs costs are reducing towards 2050 according to Aghahosseini et al., (2020) enhanced_geothermal_performant: true # if true, adds only the cheapest patch of EGS potential to each region + enhanced_geothermal_flexible: true # if true, adds a storage unit simulating flexible operation of EGS, see Ricks et al. 2023 + enhanced_geothermal_var_cf: true # if true, adds time-dependent capacity factor to EGS, see Ricks et al. 2023 # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#industry industry: diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 8ac3f6ed..94725dca 100644 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -705,12 +705,20 @@ rule build_transport_demand: rule build_egs_potentials: + params: + snapshots=config["snapshots"], input: egs_cost="data/egs_costs.json", - shapes=RESOURCES + "regions_onshore_elec_s{simpl}_{clusters}.geojson", + regions=RESOURCES + "regions_onshore_elec_s{simpl}_{clusters}.geojson", + air_temperature=RESOURCES + "temp_air_total_elec_s{simpl}_{clusters}.nc" + if config["sector"]["enhanced_geothermal_var_cf"] + else [], output: egs_potentials=RESOURCES + "egs_potentials_s{simpl}_{clusters}.csv", egs_overlap=RESOURCES + "egs_overlap_s{simpl}_{clusters}.csv", + egs_capacity_factors=RESOURCES + "egs_capacity_factors_s{simpl}_{clusters}.csv", + if config["sector"]["enhanced_geothermal_var_cf"] + else [], threads: 1 resources: mem_mb=2000, @@ -790,6 +798,9 @@ rule prepare_sector_network: cop_air_urban=RESOURCES + "cop_air_urban_elec_s{simpl}_{clusters}.nc", egs_potentials=RESOURCES + "egs_potentials_s{simpl}_{clusters}.csv", egs_overlap=RESOURCES + "egs_overlap_s{simpl}_{clusters}.csv", + egs_capacity_factors=RESOURCES + "egs_capacity_factors_s{simpl}_{clusters}.csv", + if config["sector"]["enhanced_geothermal_var_cf"] + else [], solar_thermal_total=RESOURCES + "solar_thermal_total_elec_s{simpl}_{clusters}.nc" if config["sector"]["solar_thermal"] diff --git a/scripts/build_egs_potentials.py b/scripts/build_egs_potentials.py index d0cc2acd..b7de14ff 100644 --- a/scripts/build_egs_potentials.py +++ b/scripts/build_egs_potentials.py @@ -22,8 +22,10 @@ logger = logging.getLogger(__name__) import json -import geopandas as gpd +import numpy as np import pandas as pd +import xarray as xr +import geopandas as gpd from shapely.geometry import Polygon @@ -84,6 +86,48 @@ def prepare_egs_data(egs_file): return egs_data +def get_capacity_factors( + network_regions_file, + air_temperatures_file + ): + """ + Performance of EGS is higher for lower temperatures, due to more efficient air cooling + Data from Ricks et al.: The Role of Flexible Geothermal Power in Decarbonized Elec Systems + """ + + delta_T = [-15, -10, -5, 0, 5, 10, 15, 20] + cf = [1.17, 1.13, 1.07, 1, 0.925, 0.84, 0.75, 0.65] + + x = np.linspace(-15, 20, 200) + y = np.interp(x, delta_T, cf) + + upper_x = np.linspace(20, 25, 50) + m_upper = (y[-1] - y[-2]) / (x[-1] - x[-2]) + upper_y = upper_x * m_upper - x[-1] * m_upper + y[-1] + + lower_x = np.linspace(-20, -15, 50) + m_lower = (y[1] - y[0]) / (x[1] - x[0]) + lower_y = lower_x * m_lower - x[0] * m_lower + y[0] + + x = np.hstack((lower_x, x, upper_x)) + y = np.hstack((lower_y, y, upper_y)) + + network_regions = gpd.read_file(network_regions_file).set_crs(epsg=4326) + index = network_regions["name"] + + air_temp = xr.open_dataset(air_temperatures_file) + + snapshots = pd.date_range(freq="h", **snakemake.params.snapshots) + capacity_factors = pd.DataFrame(index=snapshots) + + for bus in index: + temp = air_temp.sel(name=bus).to_dataframe()["temperature"] + capacity_factors[bus] = np.interp((temp - temp.mean()).values, x, y) + + return capacity_factors + + + if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake @@ -117,23 +161,23 @@ if __name__ == "__main__": ) egs_data = egs_data.loc[egs_data["PowerSust"] > 0].reset_index(drop=True) - egs_shapes = egs_data.geometry + egs_regions = egs_data.geometry - network_shapes = ( - gpd.read_file(snakemake.input.shapes) + network_regions = ( + gpd.read_file(snakemake.input.regions) .set_index("name", drop=True) .set_crs(epsg=4326) ) overlap_matrix = pd.DataFrame( - index=network_shapes.index, + index=network_regions.index, columns=egs_data.index, ) - for name, polygon in network_shapes.geometry.items(): + for name, polygon in network_regions.geometry.items(): overlap_matrix.loc[name] = ( - egs_shapes.intersection(polygon).area - ) / egs_shapes.area + egs_regions.intersection(polygon).area + ) / egs_regions.area overlap_matrix.to_csv(snakemake.output["egs_overlap"]) @@ -141,3 +185,10 @@ if __name__ == "__main__": egs_data["p_nom_max"] = egs_data["PowerSust"] / sustainability_factor egs_data[["p_nom_max", "CAPEX"]].to_csv(snakemake.output["egs_potentials"]) + + capacity_factors = get_capacity_factors( + snakemake.input["regions"], + snakemake.input["air_temperature"], + ) + + capacity_factors.to_csv(snakemake.output["egs_capacity_factors"]) \ No newline at end of file