From 977b40057332742b557e07529242102e80ff9f08 Mon Sep 17 00:00:00 2001 From: Mathias Berg Rosendal <77012503+Mathias157@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:30:52 +0200 Subject: [PATCH 01/19] Improvements (#4) * Added offshore wind * Updated DAG plot * Merge remote-tracking branch 'origin/clustering' into improvements --- src/Analysis/dot_source.txt | 30 ++++--- src/Analysis/snakemake_dag.pdf | Bin 11449 -> 15182 bytes src/Modules/offshore_wind.py | 159 +++++++++++++++++++++++++++++++++ src/assumptions.yaml | 13 ++- src/snakefile | 17 +++- 5 files changed, 196 insertions(+), 23 deletions(-) create mode 100644 src/Modules/offshore_wind.py diff --git a/src/Analysis/dot_source.txt b/src/Analysis/dot_source.txt index 8caa644..772167b 100644 --- a/src/Analysis/dot_source.txt +++ b/src/Analysis/dot_source.txt @@ -2,20 +2,21 @@ digraph snakemake_dag { graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; - 0[label = "all", color = "0.58 0.6 0.85", style="rounded,dashed"]; - 1[label = "exo_electricity_demand", color = "0.62 0.6 0.85", style="rounded,dashed"]; - 2[label = "create_conversion_dictionaries", color = "0.31 0.6 0.85", style="rounded,dashed"]; - 3[label = "format_energinet_data", color = "0.40 0.6 0.85", style="rounded,dashed"]; - 4[label = "transport_road_demand", color = "0.53 0.6 0.85", style="rounded,dashed"]; - 5[label = "format_dkstat_transport_data", color = "0.36 0.6 0.85", style="rounded,dashed"]; - 6[label = "exo_heat_demand", color = "0.00 0.6 0.85", style="rounded,dashed"]; - 7[label = "format_vpdk21_data", color = "0.49 0.6 0.85", style="rounded,dashed"]; - 8[label = "format_dkstat_industry_data", color = "0.04 0.6 0.85", style="rounded,dashed"]; - 9[label = "format_balmorel_data", color = "0.09 0.6 0.85", style="rounded,dashed"]; - 10[label = "geographic_sets", color = "0.13 0.6 0.85", style="rounded,dashed"]; - 11[label = "grids", color = "0.18 0.6 0.85", style="rounded,dashed"]; - 12[label = "investment_options", color = "0.22 0.6 0.85", style="rounded,dashed"]; - 13[label = "transport_h2_demand", color = "0.27 0.6 0.85", style="rounded,dashed"]; + 0[label = "all", color = "0.25 0.6 0.85", style="rounded,dashed"]; + 1[label = "exo_electricity_demand", color = "0.29 0.6 0.85", style="rounded,dashed"]; + 2[label = "create_conversion_dictionaries", color = "0.17 0.6 0.85", style="rounded,dashed"]; + 3[label = "format_energinet_data", color = "0.50 0.6 0.85", style="rounded,dashed"]; + 4[label = "transport_road_demand", color = "0.00 0.6 0.85", style="rounded,dashed"]; + 5[label = "format_dkstat_transport_data", color = "0.08 0.6 0.85", style="rounded,dashed"]; + 6[label = "exo_heat_demand", color = "0.42 0.6 0.85", style="rounded,dashed"]; + 7[label = "format_vpdk21_data", color = "0.38 0.6 0.85", style="rounded,dashed"]; + 8[label = "format_dkstat_industry_data", color = "0.54 0.6 0.85", style="rounded,dashed"]; + 9[label = "format_balmorel_data", color = "0.58 0.6 0.85", style="rounded,dashed"]; + 10[label = "geographic_sets", color = "0.62 0.6 0.85", style="rounded,dashed"]; + 11[label = "grids", color = "0.04 0.6 0.85", style="rounded,dashed"]; + 12[label = "investment_options", color = "0.12 0.6 0.85", style="rounded,dashed"]; + 13[label = "transport_h2_demand", color = "0.21 0.6 0.85", style="rounded,dashed"]; + 14[label = "offshore_wind", color = "0.33 0.6 0.85", style="rounded,dashed"]; 1 -> 0 4 -> 0 6 -> 0 @@ -24,6 +25,7 @@ digraph snakemake_dag { 11 -> 0 12 -> 0 13 -> 0 + 14 -> 0 2 -> 1 3 -> 1 5 -> 4 diff --git a/src/Analysis/snakemake_dag.pdf b/src/Analysis/snakemake_dag.pdf index 809ad63b06d1f91a6f1bdc4db167ec7176aecd81..bbe6cff76052e180a0aa5d4c4769ba283b7ffd2f 100644 GIT binary patch delta 13543 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 f-~RvmdU%? Date: Mon, 30 Sep 2024 18:53:28 +0200 Subject: [PATCH 02/19] Cleaned up --- README.md | 11 +- src/ElectricVehicles.ipynb | 142 -------- src/Modules/createDH.py | 331 ------------------ src/Modules/createHYDROGEN.py | 83 ----- src/Modules/createINDUSTRY.py | 286 --------------- .../existing_powerplants.py} | 0 .../technology_data.py} | 11 - src/Modules/{createVRE.py => vre_profiles.py} | 0 src/README.md | 19 - src/create_Demands.py | 274 --------------- src/dot_source.txt | 36 -- src/main.py | 187 ---------- 12 files changed, 9 insertions(+), 1371 deletions(-) delete mode 100644 src/ElectricVehicles.ipynb delete mode 100644 src/Modules/createDH.py delete mode 100644 src/Modules/createHYDROGEN.py delete mode 100644 src/Modules/createINDUSTRY.py rename src/{create_GKFX_DK.py => Modules/existing_powerplants.py} (100%) rename src/{create_TechData.py => Modules/technology_data.py} (94%) rename src/Modules/{createVRE.py => vre_profiles.py} (100%) delete mode 100644 src/README.md delete mode 100644 src/create_Demands.py delete mode 100644 src/dot_source.txt delete mode 100644 src/main.py diff --git a/README.md b/README.md index 7908082..9163f29 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Balmorel Pre-Processing -These scripts process raw data into Balmorel input. An application presented at the EGU24 conference is illustrated in the poster below (check Zenodo link below for a high-res pdf). For more info, read README in the src folder. +These scripts process raw data into Balmorel input for a 2050 greenfield model of the Danish energy system at municipal spatial resolution (99 nodes). + +An application presented at the EGU24 conference is illustrated in the poster below (check Zenodo link below for a high-res pdf). For more info, read README in the src folder. ![Application example](https://github.com/Mathias157/balmorel-preprocessing/blob/master/Raw%20Data%20Processing/Conference%20Poster%20for%20Analysis%20of%20Spatial%20Resolutions%20for%20Modelling%20Sector-Coupled%20Energy%20Systems.png) Data can be downloaded in the Zenodo link below and should be placed in src/ @@ -13,4 +15,9 @@ The necessary python packages can be installed in a virtual environment by follo ```` conda env create -f environment.yaml -```` \ No newline at end of file +```` + +## Get Started + +The processing is initiated through a snakemake command in a command-line interface in the src directory. +A plot of the processes can be found [here](src/Analysis/snakemake_dag.pdf). Note that vre_profiles.py and existing_powerplants is currently not part of the snakemake process, but the output from vre_profiles.py is used in the offshore_wind process. \ No newline at end of file diff --git a/src/ElectricVehicles.ipynb b/src/ElectricVehicles.ipynb deleted file mode 100644 index 3e9659a..0000000 --- a/src/ElectricVehicles.ipynb +++ /dev/null @@ -1,142 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 74, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Bus__count',\n", - " 'European_countries_car_ownership.csv',\n", - " 'KFZ__count',\n", - " 'Lfw__count',\n", - " 'Lkw__count',\n", - " 'LoA__count',\n", - " 'Lzg__count',\n", - " 'Pkw__count',\n", - " 'PmA__count',\n", - " 'Sat__count',\n", - " 'traffic.tex']" - ] - }, - "execution_count": 74, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os \n", - "import pandas as pd\n", - "import numpy as np\n", - "from pybalmorel import IncFile\n", - "\n", - "path = r'C:\\Users\\mathi\\gitRepos\\balmorel-preprocessing\\Raw Data Processing\\Data\\PyPSA-Eur-Sec Data\\emobility'\n", - "\n", - "os.listdir(path)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 127, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 127, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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cMEFOOywwwAAmzdvxpNPPokf/9i4y9M0DaqqYtu2bTjqqKM8/74RIkQQwAOk0AGn/A3wpm8CagV48ifAfV8BLv55e49tnqGqaiiUVQDAdLGCB148gDe+YnGbj2r+wjV5mZmZwcsvv8z+v23bNmzatAkDAwNYuXIlrr76auzZs4dlMT74wQ/iu9/9Lj796U/jr//6r3Hffffhpz/9KW6//fbgfgsOiqJ4tm7aCWrVUCiKAlVV2f/f85734HWvex1GRkZwzz33IJPJ4NxzzwVA/iYAcPvtt2PZsmU1r1OvYHV2dtb8/4tf/CIuuugi3H777bjzzjtxzTXX4Cc/+QnOP/98zMzM4G//9m/x0Y9+tOF4V65c6f2XjRAhgjPKBWBEz7ec9jFAUYAzPgs8fRuw5V5g+x+AVae19xjnEfKl2gKH/31yX0Re2gjXu/yjjz6KM888k/2fZlPe97734eabb8a+ffuwc+dO9vXVq1fj9ttvx5VXXol//ud/xvLly/Fv//Zv87pM2gte/epXY8WKFbj11ltx55134oILLmCE5+ijj0Y6ncbOnTtrLCJRrF27FmvXrsWVV16Jd7/73fj3f/93nH/++TjxxBPx7LPP4vDDDw/614kQIYITRp4FtCqQXQD06DclA6uBEy8BHr0JePj7EXlpIfKlas3/f/vsMPKlSihvlucCXK/6GWecYVtVY9Y994wzzsATTzzh9kdFqMNFF12EG264AS+++CJ+97vfsc93d3fjk5/8JK688kqoqorXvOY1mJycxB/+8Af09PTgfe97n+nrzc7O4lOf+hTe+c53YvXq1di9ezf+/Oc/4y//8i8BAJ/5zGfwqle9CldccQUuv/xydHZ24tlnn8U999yD7373uy35nSNEmLfYt5k8Ll5HVBeKo99GyMvI8+05rnmKXJEoL90dCfRnU9g5nsd9z4/gLeuWtvnI5iei2UYhwnve8x48++yzWLZsGU47rfaO68tf/jI+//nPY8OGDTjqqKNw7rnn4vbbb8fq1astXy8ej2NsbAyXXHIJ1q5di3e9611405vehH/4h38AQHI4999/P1588UWcfvrpOOGEE/CFL3wBS5dGb9YIEZqO/U+SxyXH1X5+4FDyOLEDUGvVgAjNA1VeutIJvGXdEgDA/27e285DmtdQtBB025mamkJvby8mJyfR09NT87VCoYBt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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# KFZ lader til at være alle køretøjer, og den som PyPSA bruger \n", - "\n", - "all_profiles = pd.DataFrame()\n", - "for file in os.listdir(path):\n", - " if '__count' in file:\n", - " f = pd.read_csv(os.path.join(path, file), skiprows=2)\n", - " f = f.pivot_table(index=['day', 'hour'],\n", - " values='count')\n", - " f = f / np.max(f)\n", - " \n", - " if len(all_profiles) == 0:\n", - " all_profiles = pd.concat((all_profiles, f.reset_index()))\n", - " all_profiles.columns = ['day', 'hour', file]\n", - " else:\n", - " all_profiles[file] = f.loc[:, 'count'].values \n", - " # f.plot(title=file)\n", - " \n", - "# all_profiles.pivot_table(index=['day', 'hour']).plot()\n", - "inv_demand = 1/all_profiles['KFZ__count'] \n", - "inv_demand = inv_demand / np.max(inv_demand)\n", - "ax = inv_demand.plot(label='Inverse', legend=True)\n", - "(all_profiles['KFZ__count']).plot(ax=ax, label='Demand', legend=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 136, - "metadata": {}, - "outputs": [], - "source": [ - "FLEXDEM_MAXCONS = IncFile(name='FLEXDEM_MAXLIMIT',\n", - " path='base/data',\n", - " suffix=\"\\n;\\nFLEXMAXLIMIT(FLEXUSER, RRR, SSS, TTT) = FLEXMAXLIMIT1('S01',TTT,FLEXUSER);\\nFLEXMAXLIMIT1(SSS,TTT,FLEXUSER)=0;\")\n", - "\n", - "\n", - "FLEXDEM_MAXCONS.prefix = \"\"\"* Normalised, inverse of mobility demand as a maximum charging constraint on flexible EV demand\n", - "* Data from KFZ mobility demand in Brown, T., D. Schlachtberger, A. Kies, S. Schramm, and M. Greiner. ‘Synergies of Sector Coupling and Transmission Reinforcement in a Cost-Optimised, Highly Renewable European Energy System’. Energy 160 (October 2018): 720–39. https://doi.org/10.1016/j.energy.2018.06.222.\n", - "TABLE FLEXMAXLIMIT1(SSS, TTT, FLEXUSER) 'Maximum flexible consumption wrt. season and term (%)' \n", - "\"\"\"\n", - "\n", - "# Make correct index\n", - "ind = pd.MultiIndex.from_product((['S01'], ['T00%d'%i for i in range(1, 10)] + ['T0%d'%i for i in range(10, 100)] + ['T%d'%i for i in range(100, 169)]))\n", - "ind.names = ['S', 'T']\n", - "body = pd.DataFrame(data=inv_demand.values, index=ind, columns=['ELECTRIC_VEHICLES']).reset_index()\n", - "\n", - "# Insert to incfile and prepare\n", - "FLEXDEM_MAXCONS.body = body\n", - "FLEXDEM_MAXCONS.body_prepare(index=['S', 'T'],\n", - " columns=None,\n", - " values='ELECTRIC_VEHICLES')\n", - "FLEXDEM_MAXCONS.save()\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python (.BAF-Env)", - "language": "python", - "name": ".baf-env" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.11" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/src/Modules/createDH.py b/src/Modules/createDH.py deleted file mode 100644 index fc27113..0000000 --- a/src/Modules/createDH.py +++ /dev/null @@ -1,331 +0,0 @@ -""" -Creating Danish heat demand for Balmorel - -Futuregas dataset default. Can be used to get temporal profiles - -Aalborg dataset, can be used to get individual and district heat gross supply: -https://vbn.aau.dk/da/datasets/kommunepakker-varmeplan-danmark-2021 -- Note that heat demand should be combined wrt. the s1-s5 scenarios (sums to 53.95 TWh without heat efficiency investments) - -Python package requirements: -- xlrd - -Created on 11.03.2024 - -@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) -""" -#%% ------------------------------- ### -### 0. Script Settings ### -### ------------------------------- ### - -from typing import Union -import matplotlib -import matplotlib.pyplot as plt -import pandas as pd -import geopandas as gpd -from format_vpdk21 import VPDK21 -from format_dkstat import DKSTAT -from Submodules.municipal_template import DataContainer -import xarray as xr -import numpy as np -import os -try: - import cmcrameri - cmap = cmcrameri.cm.cmaps['roma_r'] - cmap = cmcrameri.cm.cmaps['vik'] - colors = [cmap(i) for i in range(256)] -except ModuleNotFoundError: - print('cmrameri package not installed, using default colourmaps') - cmap = matplotlib.colormaps['viridis'] - colors = [cmap(i) for i in range(256)] - -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' - -#%% ------------------------------- ### -### 1. FutureGas District Heat ### -### ------------------------------- ### - -class DistrictHeat: - """Class for district heating data - - Existing datasets: - - Denmark_Futuregas - - Args: - dataset (str, optional): _description_. Defaults to 'DK'. - """ - - def __init__(self, dataset: str = 'Denmark_Futuregas') -> None: - if dataset.lower() == 'denmark_futuregas': - DH_shapes = 'Data/Shapefiles/Balmorel Areas/Balmorel_areas.shp' - name_col = 'BalmorelAr' - # Load data - self.DH = pd.read_parquet('Data/Timeseries/DKMUNI36_DH.gzip') - self.DHT = pd.read_parquet('Data/Timeseries/DKMUNI36_DH_VAR_T.gzip') - self.geo = gpd.read_file(DH_shapes) - self.geo.index = self.geo[name_col] - self.geo = self.geo.to_crs('EPSG:4326') - - # Delete CA_Vestfrb with no data in DH - self.geo = self.geo.drop(index='DK_CA_Vestfrb') - - # Aggregate to the 36 DH areas: - i = 0 - for dh_area in ['DK_SA_E_BG', - 'DK_SA_E_NG_CHP', - 'DK_SA_E_NG_HO', - 'DK_SA_E_ST_CHP', - 'DK_SA_E_ST_HO', - 'DK_SA_E_WO_HO']: - if i == 0: - temp = self.geo.loc[dh_area].copy() - temp.crs = 'EPSG:4326' - else: - temp.geometry = temp.geometry.union(self.geo.loc[dh_area].geometry) - self.geo = self.geo.drop(index=dh_area) - i += 1 - self.geo.loc['DK_E_Rural'] = temp - i = 0 - for dh_area in ['DK_SA_W_BG', - 'DK_SA_W_EB', - 'DK_SA_W_NG_CHP', - 'DK_SA_W_NG_HO', - 'DK_SA_W_ST_HO', - 'DK_SA_W_WO_CHP', - 'DK_SA_W_WO_HO']: - if i == 0: - temp = self.geo.loc[dh_area].copy() - else: - temp.geometry = temp.geometry.union(self.geo.loc[dh_area].geometry) - self.geo = self.geo.drop(index=dh_area) - i += 1 - self.geo.loc['DK_W_Rural'] = temp - self.geo.crs = 'EPSG:4326' - - else: - print("Dataset doesn't exist - this is an empty object\n") - print("Available datasets:\n- Denmark_Futuregas (default)") - - def assign_DH(self, areas: gpd.GeoDataFrame, df_intercepts: pd.DataFrame, - value_col: str = 'Value') -> None: - """DH data must have A and Y sets, where A matches the areas index\n - Ends up with format:\n - Y1\tY2\tY3\t--\tYN\n - A1\n - A2\n - |\n - AM - """ - # DH table for IncFile - df_DH = pd.DataFrame(index=areas.index) - - # Use df_intercepts to assign DH - temp = self.DH.pivot_table(index='A', columns='Y') - - # Check the dataframe structure - if type(temp.columns) == pd.Index: - col_ind = "year" - elif type(temp.columns) == pd.MultiIndex: - col_ind = "(value_col, year)" - else: - print('Wrong column format!') - - for agg_area in areas.index: - for year in temp.columns.get_level_values(1): - df_DH.loc[agg_area, year] = (temp.loc[:,eval(col_ind)] * df_intercepts[agg_area]).sum() - - self.dfDH = df_DH - - # Finalise index - self.dfDH.index = 'RESH . ' + pd.Series(self.dfDH.index) + '_A' - self.dfDH.index.name = '' - - def assign_DHT(self, areas: gpd.GeoDataFrame, df_intercepts: pd.DataFrame, - value_col: str = 'Value', agg_func: str = 'sum') -> None: - """DHT data must have S.T and A sets, where A matches the areas index\n - Ends up with format:\n - \t\t\tA1\tA2\tA3\t--\tAN\n - S01\t.\tT001\n - S01\t.\tT002\n - |\n - S52\t.\tT168\n - """ - - # Pivot DHT - temp = self.DHT.pivot_table(index=['S', 'T'], columns='A') - - # DHT table for IncFile - df_DHT = pd.DataFrame(index=temp.index, columns=areas.index, - data=0) - - # Check the dataframe structure - if type(temp.columns) == pd.Index: - col_ind = "dh_area" - elif type(temp.columns) == pd.MultiIndex: - col_ind = "(value_col, dh_area)" - else: - print('Wrong column format!') - - if agg_func.lower() == 'sum': - for agg_area in areas.index: - for dh_area in [dh_area0 for dh_area0 in df_intercepts[agg_area].index if df_intercepts.loc[dh_area0, agg_area] > 1e-10]: - df_DHT[agg_area] += temp.loc[(slice(None),slice(None)),eval(col_ind)] * df_intercepts.loc[dh_area, agg_area] - print() - - self.dfDHT = df_DHT - - # Finalise index - self.dfDHT.index = pd.Series(self.dfDHT.index.get_level_values(0)) + ' . ' + pd.Series(self.dfDHT.index.get_level_values(1)) - self.dfDHT.index.name = '' - self.dfDHT.columns.name = '' - self.dfDHT.columns = pd.Series(self.dfDHT.columns) + '_A' - - - def assign_DH_profile(): - ... - - def join_geo_with(self, df: pd.DataFrame, kwargs: dict = {'how' : 'inner'}) -> None: - # Join df to pandas dataframe - self.geo = self.geo.join(df, **kwargs) - - def plot_original_data(self, year: str, areas: gpd.GeoDataFrame, - plot_density: bool = False, fc: str = 'white', - area_fc: Union[str, list] = [.85, .85, .85], - ax: Union[matplotlib.axes._axes.Axes, str] = '') -> tuple[matplotlib.figure.Figure, - matplotlib.axes._axes.Axes]: - if ax == '': - # Create plot if no ax was inserted - fig, ax = plt.subplots(facecolor=fc) - areas.plot(ax=ax, facecolor=area_fc, linewidth=.3) - df = self.geo.copy() - df = df.join(self.DH[self.DH.Y == year].pivot_table(index='A'), how='inner') - df = df.to_crs('EPSG:4328') - if plot_density: - df['Value'] = df['Value'] / df.area # Heat density (MWh/m^2) - leg = 'MWh$\cdot$m$^{-2}$' - else: - df['Value'] = df['Value'] / 1e6 # TWh - leg = 'TWh' - df = df.to_crs('EPSG:4326') - df.plot(ax=ax, column ='Value', legend=True, cmap=cmap) - ax.set_title('Original Data - %s'%leg) - ax.set_ylabel('Latitude') - ax.set_xlabel('Longitude') - - return fig, ax - - def plot_aggregated_data(self, year: str, areas: gpd.GeoDataFrame, - plot_density: bool = False) -> tuple[matplotlib.figure.Figure, - matplotlib.axes._axes.Axes]: - - if hasattr(self, 'dfDH'): - fig, ax = plt.subplots(facecolor=fc) - df = areas.copy() - df2 = self.dfDH.copy() - df2.index = df2.index.str.replace('RESH . ', '').str.replace('_A', '') - df = df.join(df2, how='inner') - df = df.to_crs('EPSG:4328') - if plot_density: - df[year] = df[year] / df.area # Heat density (MWh/m^2) - leg = 'MWh$\cdot$m$^{-2}$' - else: - df[year] = df[year] / 1e6 # TWh - leg = 'TWh' - df2 = areas.copy() - df2[year] = df[year] - df2.plot(ax=ax, column =year, legend=True, cmap=cmap) - ax.set_title('Aggregated Data - %s'%leg) - ax.set_ylabel('Latitude') - ax.set_xlabel('Longitude') - - return fig, ax - - else: - print('Aggregation have not been made.') - -def find_value(df: pd.DataFrame, element: any, - func: str = 'max', ind: int = 0) -> any: - val_idx = eval("df[element].%s()"%func) == df[element] - temp = df.index[val_idx] - l = len(temp) - if l > 1: - print('%d %s values! Picked index %d'%(l, func, ind)) - return temp[ind] - - -#%% ------------------------------- ### -### 2. VPDK21 District Heat ### -### ------------------------------- ### - -class DistrictHeatAAU: - def __init__(self) -> None: - f1 = VPDK21() - f2 = DKSTAT() - self.f1 = f1 - self.f2 = f2 - - def combine_data(self, plot: bool = False): - data = DataContainer() - data.muni = data.muni.merge(self.f1.DH) - data.muni = data.muni.merge(self.f1.IND) - data.muni = data.muni.merge(self.f2.IND) - - - ## Get industry demands per municipality, per heat type - data.muni.heat_demand_mwh.loc[{'user': 'industry_phl'}] = data.muni.heat_demand_normalised.sel(year=2019, user='industry_phl') * data.muni.energy_demand_type_mwh.sel(year=2018, user='other') / data.muni.energy_demand_type_mwh.sel(year=2018).sum() * data.muni.energy_demand_mun_mwh.sel(year=2018) - data.muni.heat_demand_mwh.loc[{'user': 'industry_phm'}] = data.muni.heat_demand_normalised.sel(year=2019, user='industry_phm') * data.muni.energy_demand_type_mwh.sel(year=2018, user='other') / data.muni.energy_demand_type_mwh.sel(year=2018).sum() * data.muni.energy_demand_mun_mwh.sel(year=2018) - data.muni.heat_demand_mwh.loc[{'user': 'industry_phh'}] = data.muni.heat_demand_normalised.sel(year=2019, user='industry_phh') * data.muni.energy_demand_type_mwh.sel(year=2018, user='other') / data.muni.energy_demand_type_mwh.sel(year=2018).sum() * data.muni.energy_demand_mun_mwh.sel(year=2018) - - ## Drop year 2018, user electricity, other and district heat from industry data - data.muni = data.muni.drop_vars(['energy_demand_mun_mwh', - 'energy_demand_type_mwh', - 'heat_demand_normalised']) - data.muni = data.muni.sel(year=[2019], user=['district_heating', 'individual', - 'industry_phl', 'industry_phm', 'industry_phh']) - - self.data = data.muni - - if plot: - # Plot it - temp = data.muni.heat_demand_mwh.sel(year=2019, user='district_heating').data - temp += data.muni.heat_demand_mwh.sel(year=2019, user='individual').data - temp += data.muni.heat_demand_mwh.sel(year=2019, user='industry_phl').data.astype(float) - temp += data.muni.heat_demand_mwh.sel(year=2019, user='industry_phm').data.astype(float) - temp += data.muni.heat_demand_mwh.sel(year=2019, user='industry_phh').data.astype(float) - - fig, ax = plt.subplots() - data.get_polygons().plot(ax=ax, - column=temp, - cmap=cmap, - vmin=0, - vmax=6e6, - legend=True).set_title('sum') - fig.savefig(f'Output/Figures/Heat/total_heatdemand.png', - transparent=True, - bbox_inches='tight') - - for user in ['district_heating', 'individual', 'industry_phl', - 'industry_phm', 'industry_phh']: - fig, ax = plt.subplots() - a = data.get_polygons().plot(ax=ax, - cmap=cmap, - vmin=0, - vmax=6e6, - column=data.muni.heat_demand_mwh.sel(year=2019, user=user).data, - legend=True).set_title(user) - fig.savefig(f'Output/Figures/Heat/{user}_heatdemand.png', - transparent=True, - bbox_inches='tight') - - -if __name__ == '__main__': - X = DistrictHeatAAU() - X.combine_data() - diff --git a/src/Modules/createHYDROGEN.py b/src/Modules/createHYDROGEN.py deleted file mode 100644 index 13ba1ba..0000000 --- a/src/Modules/createHYDROGEN.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Created on 17.03.2024 - -@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) -""" -#%% ------------------------------- ### -### 0. Script Settings ### -### ------------------------------- ### - -import matplotlib.pyplot as plt -import pandas as pd -import geopandas as gpd -from shapely import Point -import numpy as np -from pybalmorel.functions import IncFile, read_lines - -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' - -#%% ------------------------------- ### -### 1. -### ------------------------------- ### - -# From Kountouris et al 2024 -XH2LOSS = 2.5e-08 # MWh/Mwh -XH2COST = 1e-6 # €/Mwh -XH2INVCOST = 0.55 # €/MW - new onshore pipe -# XH2INVCOST = 0.9 # €/MW - new offshore pipe -# XH2INVCOST = 0.15 # €/MW - repurposed onshore pipe -# XH2INVCOST = 0.2 # €/MW - repurposed offshore pipe - - -class Hydrogen: - - def __init__(self, choice: str) -> None: - - # Read pre-calculated distances - self.grid = pd.read_parquet('Data/Shapefiles/Distances/%s_Distances.gzip'%choice.upper()) - - # Gas storage DK1 - self.gasstorage = gpd.GeoDataFrame({'geometry' : Point(9.416895889014997, 56.64039606410427)}, - index=['Lille Torup'], crs='EPSG:4326') - - - def create_hydrogen_data(self, areas: gpd.GeoDataFrame, the_index: str) -> dict: - - incfiles = {} - incfilenames = ['HYDROGEN_XH2INVCOST', - 'HYDROGEN_XH2LOSS', - 'HYDROGEN_AGKN'] - - for name in incfilenames: - incfiles[name] = IncFile(name=name, - path='Output', - body=pd.DataFrame(), - prefix=read_lines(name+'_prefix.inc', - file_path='Data/IncFilePreSuf'), - suffix=read_lines(name+'_suffix.inc', - file_path='Data/IncFilePreSuf')) - - # Make investment and loss - incfiles['HYDROGEN_XH2INVCOST'].body = self.grid[self.grid.Y == '2050'] - incfiles['HYDROGEN_XH2LOSS'].body = self.grid[self.grid.Y == '2050'] - incfiles['HYDROGEN_XH2INVCOST'].body.loc[:, 'Value'] = incfiles['HYDROGEN_XH2INVCOST'].body['Value']*XH2INVCOST - incfiles['HYDROGEN_XH2LOSS'].body.loc[:, 'Value'] = incfiles['HYDROGEN_XH2LOSS'].body['Value']*XH2LOSS - incfiles['HYDROGEN_XH2INVCOST'].body.loc[:, 'Y'] = incfiles['HYDROGEN_XH2INVCOST'].body['Y'].str.replace('2050', '2030') - incfiles['HYDROGEN_XH2LOSS'].body.loc[:, 'Y'] = incfiles['HYDROGEN_XH2LOSS'].body['Y'].str.replace('2050', '2030') - - # Find salt-cavern investment option - saltcavern = areas[areas.geometry.contains(self.gasstorage.geometry[0])][the_index].iloc[0] - incfiles['HYDROGEN_AGKN'].body = "AGKN('%s_A', 'GNR_H2S_H2-CAVERN_Y-2030') = YES;\n"%saltcavern - incfiles['HYDROGEN_AGKN'].body = "AGKN('%s_A', 'GNR_H2S_H2-CAVERN_Y-2040') = YES;\n"%saltcavern - incfiles['HYDROGEN_AGKN'].body = "AGKN('%s_A', 'GNR_H2S_H2-CAVERN_Y-2050') = YES;\n"%saltcavern - - return incfiles - - diff --git a/src/Modules/createINDUSTRY.py b/src/Modules/createINDUSTRY.py deleted file mode 100644 index 6493272..0000000 --- a/src/Modules/createINDUSTRY.py +++ /dev/null @@ -1,286 +0,0 @@ -""" -Created on 14.03.2024 - -@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) - -Script for creating data for industry sector -Sources: - - Balmorel Data 2024 - - Manz, Pia, Tobias Fleiter, and Wolfgang Eichhammer. “The Effect of Low-Carbon Processes on Industrial Excess Heat Potentials for District Heating in the EU: A GIS-Based Analysis.” Smart Energy 10 (May 1, 2023): 100103. https://doi.org/10.1016/j.segy.2023.100103. - -""" -#%% ------------------------------- ### -### 0. Script Settings ### -### ------------------------------- ### - -import os -import matplotlib -import matplotlib.pyplot as plt -import pandas as pd -import numpy as np -import geopandas as gpd -from pybalmorel import IncFile -from pybalmorel.functions import read_lines -from Modules.geofiles import prepared_geofiles -from shapely.geometry import Point, Polygon, LineString -from typing import Union -try: - import cmcrameri - cmap = cmcrameri.cm.cmaps['roma_r'] - colors = [cmap(i) for i in range(256)] -except ModuleNotFoundError: - print('cmrameri package not installed, using default colourmaps') - cmap = matplotlib.colormaps['viridis'] - colors = [cmap(i) for i in range(256)] - -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' - -#%% ------------------------------- ### -### 1. Industry Data ### -### ------------------------------- ### - -### 1.0 Assumptions -XHLOSS = 0.1 # MWh/Mwh -XHCOST = 0.001 # €/Mwh -XHINVCOST = 396000 # €/MW - - -class Industry: - """Class for industry addon data - - Existing datasets: - - Denmark - - Args: - dataset (str, optional): _description_. Defaults to 'DK'. - """ - - def __init__(self, dataset: str = 'EUBalmorel') -> None: - if dataset.lower() == 'eubalmorel': - choice = 'Balmorel2022' - the_index, self.geo, c = prepared_geofiles(choice) - - # Load data - datasets = pd.Series([ds0 for ds0 in os.listdir('Data/BalmorelData') if 'INDUSTRY' in ds0]) - - for ds in datasets: - setattr(self, ds.replace('INDUSTRY_', '').replace('.gzip', ''), pd.read_parquet('Data/BalmorelData/' + ds)) - - else: - print("Dataset doesn't exist - this is an empty object\n") - print("Available datasets:\n- EUBalmorel (default)") - - # Load high resolution dataset - self.PS = pd.read_csv('Data/Gas, Transport and Industry Data/Industrial_Database.csv', - sep=';', encoding='UTF-8') - - # Delete sources with no coordinates (you could look the cities up with google maps API) - self.PS = self.PS[self.PS.Latitude.isna() == False] - self.PS.index = np.arange(len(self.PS)) # Fix index - - # Fill NaN's with zeros - self.PS['Emissions_ETS_2014'] = self.PS['Emissions_ETS_2014'].fillna(0) - - self.PS['geometry'] = gpd.GeoSeries([Point(xy) for xy in zip(self.PS.Longtitude, self.PS.Latitude)]) - self.PS = gpd.GeoDataFrame(self.PS) - self.PS.geometry.crs = 'EPSG:4326' - self.geoindex = the_index - - - def assign_original_region(self): - self.PS['R'] = '' - for area in self.geo.index: - # Find containing region - idx = self.PS.within(self.geo.geometry[area]) - self.PS.loc[idx, 'R'] = area # Should only have one value - - # If it's not contained within a region, use smallest distance to find remaining - for PS in self.PS[self.PS.R == ''].index: - temp = self.geo.copy().to_crs('EPSG:4328') - dist = temp.distance(self.PS.geometry[PS]) - - try: - self.PS.loc[PS, 'R'] = dist[dist == dist.min()].index[0] # Should only have one value - except IndexError: - print('Datapoint %d probably had invalid geometry'%PS) - - def assign_emission_fractions(self): - # Use EPRTR emissions for CH - self.PS.loc[self.PS.R == 'CH', 'Emissions_ETS_2014'] = self.PS[self.PS.R == 'CH']['Emissions_EPRTR_2014'] - # RS only has one source with no data - self.PS.loc[self.PS.R == 'RS', 'Emissions_ETS_2014'] = 0 - - # Total emissions in a region - emisum = self.PS.pivot_table(index='R', values=['Emissions_ETS_2014'], fill_value=0, aggfunc='sum') - - # The fraction of emissions at point sources - for R in self.PS.R.unique(): - idx = self.PS.R == R - self.PS.loc[idx, 'EmiFrac'] = self.PS.loc[idx, 'Emissions_ETS_2014'] / emisum.loc[R, 'Emissions_ETS_2014'] - - - def apply_ef_to_original_data(self, incfiles: dict, original_area: str, - new_area: str, frac_sum: str, include_new_area: bool = False) -> None: - """Assign industry heat demand from original area to new area""" - - for attr in ['DH', 'DH_VAR_T', 'GKFX', 'DE']: - # Find original Balmorel data - if attr != 'DE': - temp = getattr(self, attr)[getattr(self, attr).A.str.find(original_area) != -1].copy() - else: - temp = getattr(self, attr)[getattr(self, attr).R == original_area].copy() - - # Apply sum to Balmorel data - temp.loc[:, 'Value'] = temp['Value'] * frac_sum - - # Replace original area name with new area name - if attr != 'DE': - temp.loc[:, 'A'] = temp['A'].str.replace(original_area, new_area) - else: - temp.loc[:, 'R'] = temp['R'].str.replace(original_area, new_area) - - if include_new_area: - temp.loc[:, 'new_area'] = new_area # If wanting to plot it - - # Concatenate to body in incfile - incfiles['INDUSTRY_' + attr].body_concat(temp) - - def create_industry_data(self, areas: gpd.GeoDataFrame, - save_new_area_to_df: bool = False, - plot_point_sources: bool = False, - ax: matplotlib.axes.Axes = '') -> dict: - """Prepares industry data .inc files - """ - - # Placeholder for incfiles - incfiles = {} - incfilenames = ['INDUSTRY_AGKN', 'INDUSTRY_DE', 'INDUSTRY_DH', - 'INDUSTRY_DH_VAR_T', 'INDUSTRY_GKFX', 'INDUSTRY_CCCRRRAAA', - 'INDUSTRY_RRRAAA', 'INDUSTRY_AAA', 'INDUSTRY_DISLOSS_E_AG'] - for name in incfilenames: - incfiles[name] = IncFile(name=name, - path='Output', - body=pd.DataFrame(), - prefix=read_lines(name+'_prefix.inc', - file_path='Data/IncFilePreSuf'), - suffix=read_lines(name+'_suffix.inc', - file_path='Data/IncFilePreSuf')) - incfiles['INDUSTRY_DISLOSS_E_AG'].body = '' - - for new_area in areas.index: - idx = self.PS.within(areas.geometry[new_area]) - - try: - if plot_point_sources & (ax != ''): - self.PS[idx].plot(ax=ax, zorder=5) - - # Get original areas (can be more, if the chosen spatial resolution is coarser than the original) - original_areas = self.PS.R[idx].unique() - - # Go through original areas - for original_area in original_areas: - - # Sum fraction of emissions in the new area - frac_sum = self.PS.loc[idx & (self.PS.R == original_area), 'EmiFrac'].sum() - - # Disaggregate DH, DH_VAR_T, DE, GKFX - self.apply_ef_to_original_data(incfiles, original_area, new_area, frac_sum, save_new_area_to_df) - - # Disaggregate AGKN - temp = self.AGKN[self.AGKN.A.str.find(original_area) != -1].copy() - temp['A'] = temp['A'].str.replace(original_area, new_area) - - incfiles['INDUSTRY_AGKN'].body_concat(temp) # perhaps make a IncFile.body.concat function.. - - # Create technology specific distribution loss - for a in temp['A'].unique(): - incfiles['INDUSTRY_DISLOSS_E_AG'].body += f"DISLOSS_E_AG_IND('{a}',G)$((GDATA(G,'GDTYPE') EQ GETOH OR GDATA(G,'GDTYPE') EQ GESTO OR GDATA(G,'GDTYPE') EQ GESTOS) AND (SUM(Y,GKFX(Y,'{a}',G)) OR AGKN('{a}',G)))=SUM(IR$RRRAAA(IR,'{a}'),DISLOSS_E(IR));\n" - - except ValueError: - print('No industry in %s'%new_area) - - return incfiles - - def plot_original_data(self, emission_scale: float = 5e5, - bounds: list[float, float, float, float] = [-12, 30, 33, 73], - fc: str = 'white', - area_fc: Union[str, list] = [.85, .85, .85], - ax: Union[matplotlib.axes._axes.Axes, str] = '') -> tuple[matplotlib.figure.Figure, - matplotlib.axes._axes.Axes]: - if ax == '': - # Create plot if no ax was inserted - fig, ax = plt.subplots(facecolor=fc) - self.geo.plot(ax=ax, facecolor=area_fc) - self.PS.plot(ax=ax, marker='o', color=[.3, .3, .3], - markersize=self.PS['Emissions_ETS_2014']/emission_scale) - ax.set_title('Original Data') - ax.set_xlabel('Longitude') - ax.set_ylabel('Latitude') - ax.set_xlim([bounds[0], bounds[1]]) - ax.set_ylim([bounds[2], bounds[3]]) - fig.savefig('Output/Figures/IND_original.png', bbox_inches='tight') - fig.savefig('Output/Figures/IND_original.pdf', bbox_inches='tight') - - return fig, ax - - def plot_aggregated_data(self, incfiles: dict, - areas: gpd.GeoDataFrame, - indicator: str = 'DH') -> tuple[matplotlib.figure.Figure, - matplotlib.axes._axes.Axes]: - - # Generate industry data for a specific spatial resolution - - # Plot - try: - fig, ax = plt.subplots() - - if indicator == 'DH': - temp = incfiles['INDUSTRY_DH'].body.pivot_table(index=['Y', 'new_area'], values=['Value'], aggfunc='sum').loc['2050'] - areas['new_col'] = temp.Value / 1e6 - ax.set_title('Industry Heat Demand (TWh)') - elif indicator == 'DE': - temp = incfiles['INDUSTRY_DE'].body.pivot_table(index=['Y', 'new_area'], values=['Value'], aggfunc='sum').loc['2050'] - areas['new_col'] = temp.Value / 1e6 - ax.set_title('Industry Electricity Demand (TWh)') - elif indicator == 'GKFX': - temp = incfiles['INDUSTRY_GKFX'].body.pivot_table(index=['Y', 'new_area'], values=['Value'], aggfunc='sum').loc['2020'] - areas['new_col'] = temp.Value - ax.set_title('Industry Generation Capacity (MW)') - - areas.plot(ax=ax, zorder=2, column='new_col', legend=True, cmap=cmap) - areas.plot(ax=ax, zorder=1, facecolor=cmap(0)) - - except KeyError: - print('New area column not created in .inc file') - print('save_new_area_to_df was probably set to False in create_industry_data function') - - return fig, ax - - - -# Need to make incfiles: -# INDUSTRY_AGKN -# INDUSTRY_CCCRRRAAA -# INDUSTRY_RRRAAA -# INDUSTRY_AAA -# INDUSTRY_GKFX -# INDUSTRY_DH -# INDUSTRY_DH_VAR_T -# INDUSTRY_DE -# INDUSTRY_DE_VAR_T - -# Later: -# INDUSTRY_XHMAXK -# INDUSTRY_XHKFX -# INDUSTRY_XHLOSS -# INDUSTRY_XHCOST -# INDUSTRY_XHINVCOST - - diff --git a/src/create_GKFX_DK.py b/src/Modules/existing_powerplants.py similarity index 100% rename from src/create_GKFX_DK.py rename to src/Modules/existing_powerplants.py diff --git a/src/create_TechData.py b/src/Modules/technology_data.py similarity index 94% rename from src/create_TechData.py rename to src/Modules/technology_data.py index 7f37720..5f7e596 100644 --- a/src/create_TechData.py +++ b/src/Modules/technology_data.py @@ -262,16 +262,5 @@ def get_value(search_series, search_strings, y0, y1, den, y0_dif): f.write('\n'.join(GDATA.index)) # f.write('\n/;') -#%% ------------------------------------- ### -### 4. Create AGKN ### -### ------------------------------------- ### -### 4.1 Hack, all techs can be invested everywhere -with open('./Output/AGKN.inc', 'w') as f: - # f.write("SET GGG 'All generation technologies'\n") - for A in areas[the_index]: - for G in GDATA.index: - f.write("AGKN('%s_A', '%s') = YES;\n"%(A, G)) - # f.write('/\n') - # f.write('\n/;') diff --git a/src/Modules/createVRE.py b/src/Modules/vre_profiles.py similarity index 100% rename from src/Modules/createVRE.py rename to src/Modules/vre_profiles.py diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 95cf88d..0000000 --- a/src/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Creating .inc Files for Balmorel - -Last updates: - -- main in 2024 -- Modules/* in 2024 -- create_Demands in 2022 -- create_GKFX_DK in 2022 -- create_Grids in 2022 -- create_SetFiles in 2022 -- create_TechData in 2022 - -Run all python scripts in this folder to process data for a Danish Balmorel case study at various spatial resolution (main.py, create_XXXX...) - -main.py is a new and more systematic way of processing data, using classes and function instead of the long, hard-to-read create_XXXX python files. It is the plan to migrate everything into this structure in the future, but main.py currently only process data for industry, district heating and hydrogen. - -A further description of how each script works is found within each python file - -NB: No data is provided in this repo, download data from https://zenodo.org/records/10960910 diff --git a/src/create_Demands.py b/src/create_Demands.py deleted file mode 100644 index 6a575fa..0000000 --- a/src/create_Demands.py +++ /dev/null @@ -1,274 +0,0 @@ -#%% -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 22 17:17:25 2022 - -@author: Mathias Berg Rosendal, Research Assistant DTU Management - -Use NUTS3 data for population density, which can be downloaded here: -https://ec.europa.eu/eurostat/web/products-datasets/product?code=demo_r_d3dens - -And demand data from: - - -This is used to create demand files for Balmorel. - - -Works with the environment.yaml distributed in XXX -""" - -import matplotlib.pyplot as plt -from matplotlib import rc -import pandas as pd -import geopandas as gpd -import cartopy.crs as ccrs -from shapely.geometry import MultiPolygon -from pyproj import Proj -import numpy as np -import matplotlib.pyplot as plt -from Modules.geofiles import prepared_geofiles -from Modules.createDH import DistrictHeat -from pyproj import Proj -import datetime as dt -from pybalmorel.functions import IncFile - - -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' - -#%% ----------------------------- ### -### 0. ASSUMPTIONS ### -### ----------------------------- ### - -### 0.1 Limit area -# If you type 'All', then no filter is applied -Afilt = 'All' - - -### 0.2 Population Year -Y = '2019' - - -### 0.3 Load geodata -# What areas to load? -choice = 'DK Municipalities' -# choice = 'NUTS1' -# choice = 'NUTS2' -# choice = 'NUTS3' -# choice = 'NordpoolReal' - -### 0.4 Input parameters -DE = pd.DataFrame({'MWh' : 37000000}, index=['DK']) ### DUMMY DATA only for DK -DH = pd.DataFrame({'MWh' : 50000000}, index=['DK']) ### DUMMY DATA only for DK -Esector = 'RESE' -Hsector = 'RESH' -Dprof = pd.read_excel(r'.\Data\Timeseries\DummyProfiles.xlsx') -profYear = 2017 # Year for profiles - -Dprof = pd.read_parquet('Data/Timeseries/DKMUNI36_DH.gzip') -### Assumptions -# 2.3 ToDo: Only one country at a time right now -# 2.3 ToDo: Should include yearly change -# 3.1 ToDo: Include different sectors -# 3.2 ToDo: Need to include data on profiles -# Right now, DK1 large and DK el demand profiles are used for all - - -#%% ----------------------------- ### -### 1. Read files ### -### ----------------------------- ###a - -### 1.1 Read NUTS3 data -pop = pd.read_csv(r'.\Data\demo_r_d3dens.tsv', sep='\t') -N3 = gpd.read_file(r'.\Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') - -## Make more readable -pop.columns = ['NUTS_ID'] + list(pd.Series(pop.columns).str.replace(' ',''))[1:] -pop['NUTS_ID'] = pop['NUTS_ID'].str.replace('PER_KM2,','') - -## Apply filter -# Only NUTS3 -pop = pop[pop.NUTS_ID.str.len() == 5] -N3 = N3[N3.NUTS_ID.str.len() == 5] -# Customised filters -if Afilt.lower() != 'all': - N3 = N3[N3.NUTS_ID.str.find(Afilt) != -1] - pop = pop[pop.NUTS_ID.str.find(Afilt) != -1] -# Year -pop = pop[['NUTS_ID', Y]] -# Convert to Numbers -pop[Y] = pop[Y].str.replace(':','0') -pop[Y] = pop[Y].astype(float) - -## Merge -N3 = pd.concat((N3.set_index('NUTS_ID'), pop.set_index('NUTS_ID')), axis=1, join='inner') - -## Change projections to a geocentric one -N3 = N3.to_crs(4328) - -## Factor in area and normalise -N3[Y] = N3[Y]*N3.geometry.area -N3[Y] = N3[Y] / N3[Y].sum() ### HAS TO BE DONE PR COUNTRY - -## Plot density heatmap -# fig, ax = newplot(fc=fc) -fig, ax = plt.subplots(facecolor=fc) -N3.plot(ax=ax, column=Y, cmap='coolwarm', legend=True, zorder=2) -ax.set_title('Fraction of population - ' + Y) - - -### 1.2 Read demand data -### DEMAND data should be on country scale, and used to iterate through in the next section - -## Plot yearly demand - - -### 1.3 Read geodata -the_index, areas, country_code = prepared_geofiles(choice) -if 'nuts' in choice.lower(): - areas = areas[(areas[the_index].str.find('DK') != -1)] -areas.plot() -areas = areas[(areas[country_code] == 'DK') | (areas[country_code] == 'DE')] # Testing DK and DE - -## Change projections to a geocentric one -if choice.lower().replace(' ', '') == 'nordpoolreal': - areas.crs = 4326 -areas = areas.to_crs(4328) - - - - -#%% ----------------------------- ### -### 2. Calculate Demands ### -### ----------------------------- ### - - -### 2.2 Calculate intersecting area of areas with N3 -### HAS TO BE NORMALISED PR COUNTRY !!!! Only works with one country at a time right now -### idea, use demand: for country in DE... -for i,row in areas.iterrows(): - # Intersecting Areas - w = N3.geometry.intersection(areas.geometry[i]).area - # Normalise with N3 total area to get area fraction of country - w = w / N3.area - # Final weight is factoring population fraction on area fractions - w = w * N3[Y] - - # Population Fraction X Intersecting Fractions - areas.loc[i, 'DE'] = (w * DE.loc['DK', 'MWh']).sum() ### NEED key - areas.loc[i, 'DH'] = (w * DH.loc['DK', 'MWh']).sum() ### NEED key for country - -areas.plot(column='DE', cmap='coolwarm', legend=True, zorder=2) - -### 2.3 Adjust to actual demands -print('DE input: %0.2f'%DE.loc['DK', 'MWh'], 'DE sum after intersect: %0.2f'%areas.DE.sum()) -print('DH input: %0.2f'%DH.loc['DK', 'MWh'], 'DH sum after intersect: %0.2f'%areas.DH.sum()) - -# Linearly adjust all to fit actual input -areas.loc[:, 'DE'] = areas.loc[:, 'DE'] * DE.loc['DK', 'MWh'] / areas.DE.sum() -areas.loc[:, 'DH'] = areas.loc[:, 'DH'] * DH.loc['DK', 'MWh'] / areas.DH.sum() - - -### 2.3 MAKE CALCULATIONS ON YEARLY PROJECTION - - -#%% ----------------------------- ### -### 3. Save ### -### ----------------------------- ### - -inc = {} # Incfile container - -### 3.1 Save DE and DH -DEtable = pd.DataFrame({'2050' : areas['DE'].values}, index=areas.index + ' . %s'%Esector) -DEtable.index.name = '' -DHtable = pd.DataFrame({'2050' : areas['DH'].values}, index='%s . '%Hsector + areas.index + '_A') -DHtable.index.name = '' - -inc['DE'] = IncFile(name='DE', path='./Output', - prefix="TABLE DE1(RRR, DEUSER, YYY) 'Annual electricity consumption (MWh)'\n", - body=DEtable.to_string(header=True, index=True), - suffix="\n;\nDE(YYY,RRR,DEUSER) = DE1(RRR,DEUSER,YYY);\nDE1(RRR,DEUSER,YYY)=0;") - -with open('./Output/DH.inc', 'w') as f: - f.write("PARAMETER DH(YYY,AAA,DHUSER) 'Annual brutto heat consumption';\n") - f.write("TABLE DH1(DHUSER, AAA, YYY)\n") - dfAsString = DHtable.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n;') - f.write("\nDH(YYY,AAA,DHUSER) = DH1(DHUSER, AAA, YYY);\nDH1(DHUSER, AAA, YYY)=0;") - - -### 3.2 Save DE and DH profiles - HACK - -#%% Convert data -# Get correct timeseries index for Balmorel -t = Dprof['Time'].dt.isocalendar() -t.index = Dprof['Time'] -t['hour'] = t.index.hour - -# Filter away first week, from last year -idx = t.index.year == t['year'] -idx = idx & (t.index.year == profYear) -t = t[idx] -DT = Dprof[idx] - -# Fix summer-period switch -t.index = np.array([t.index[0]+dt.timedelta(hours=i) for i in range(8736)]) -t = t.index.isocalendar() - -# Make seasons -t['S'] = t['week'].astype(str) -idx = t['S'].str.len() == 1 -t.loc[idx, 'S'] = '0' + t.loc[idx, 'S'] -t['S'] = 'S' + t['S'] - -# Make terms -try: - t['T'] = np.array([i for i in range(1, 169)]*52) -except ValueError: - print("\nWARNING!\nYou didn't load 8736 hours of data! Select a bit of the next year, in cutout (T parameter in beginning).") - print("The current profile will be %d too short (%d hours in total)\n"%(8736-len(t), len(t))) - - array = np.array([i for i in range(1, 169)]*52) - t['T'] = array[:len(t)] - - -t['T'] = t['T'].astype(str) -idx = t['T'].str.len() == 1 -t.loc[idx, 'T'] = '00' + t.loc[idx, 'T'] -idx = t['T'].str.len() == 2 -t.loc[idx, 'T'] = '0' + t.loc[idx, 'T'] -t['T'] = 'T' + t['T'] - - -# Create new index - ASSUMING DK1 LARGE HEAT and DK EL PROFILE -DT.index = t['S'] + ' . ' + t['T'] - -DET = pd.DataFrame(data={A : DT['El demand'] for A in areas.index}, index=DT.index) -DHT = pd.DataFrame(data={A : DT['DK1_Large'] for A in areas.index}, index=DT.index) -DHT.columns = DHT.columns + '_A' - -#%% -with open('./Output/DE_VAR_T.inc', 'w') as f: - f.write("PARAMETER DE_VAR_T(RRR,DEUSER,SSS,TTT) 'Variation in electricity demand';\n") - f.write("TABLE DE_VAR_T1(SSS,TTT,RRR)\n") - dftostring = DET.to_string(header=True, index=True) - f.write(dftostring) - f.write("\n;\n") - f.write("DE_VAR_T(RRR,'RESE',SSS,TTT) = DE_VAR_T1(SSS,TTT,RRR);\n") - f.write("DE_VAR_T1(SSS,TTT,RRR)=0;\n") - -with open('./Output/DH_VAR_T.inc', 'w') as f: - f.write("PARAMETER DH_VAR_T(AAA,DHUSER,SSS,TTT) 'Variation in heat demand';\n") - f.write("TABLE DH_VAR_T1(SSS,TTT,AAA)\n") - dftostring = DHT.to_string(header=True, index=True) - f.write(dftostring) - f.write("\n;\n") - f.write("DH_VAR_T(AAA,'RESH',SSS,TTT) = DH_VAR_T1(SSS,TTT,AAA);\n") - f.write("DH_VAR_T1(SSS,TTT,AAA)=0;\n") diff --git a/src/dot_source.txt b/src/dot_source.txt deleted file mode 100644 index 4207ab0..0000000 --- a/src/dot_source.txt +++ /dev/null @@ -1,36 +0,0 @@ -digraph snakemake_dag { - graph[bgcolor=white, margin=0]; - node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; - edge[penwidth=2, color=grey]; - 0[label = "all", color = "0.10 0.6 0.85", style="rounded"]; - 1[label = "exo_electricity_demand", color = "0.05 0.6 0.85", style="rounded,dashed"]; - 2[label = "create_conversion_dictionaries", color = "0.14 0.6 0.85", style="rounded,dashed"]; - 3[label = "format_energinet_data", color = "0.52 0.6 0.85", style="rounded,dashed"]; - 4[label = "transport_demand", color = "0.29 0.6 0.85", style="rounded"]; - 5[label = "format_dkstat_transport_data", color = "0.00 0.6 0.85", style="rounded,dashed"]; - 6[label = "exo_heat_demand", color = "0.62 0.6 0.85", style="rounded,dashed"]; - 7[label = "format_vpdk21_data", color = "0.24 0.6 0.85", style="rounded,dashed"]; - 8[label = "format_dkstat_industry_data", color = "0.38 0.6 0.85", style="rounded,dashed"]; - 9[label = "format_balmorel_data", color = "0.57 0.6 0.85", style="rounded,dashed"]; - 10[label = "geographic_sets", color = "0.19 0.6 0.85", style="rounded,dashed"]; - 11[label = "grids", color = "0.33 0.6 0.85", style="rounded,dashed"]; - 12[label = "investment_options", color = "0.43 0.6 0.85", style="rounded,dashed"]; - 1 -> 0 - 4 -> 0 - 6 -> 0 - 9 -> 0 - 10 -> 0 - 11 -> 0 - 12 -> 0 - 2 -> 1 - 3 -> 1 - 5 -> 4 - 2 -> 6 - 7 -> 6 - 8 -> 6 - 3 -> 6 - 6 -> 10 - 9 -> 11 - 2 -> 11 - 6 -> 12 -} diff --git a/src/main.py b/src/main.py deleted file mode 100644 index f9ccaa9..0000000 --- a/src/main.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Created on 11.03.2024 - -@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) -""" -#%% ------------------------------- ### -### 0. Script Settings ### -### ------------------------------- ### - -import matplotlib.pyplot as plt -import pandas as pd -from pybalmorel.utils import read_lines -from pybalmorel import IncFile -from Modules.createDH import DistrictHeat -from Modules.createINDUSTRY import Industry -from Modules.createHYDROGEN import Hydrogen -from Modules.geofiles import prepared_geofiles, calculate_intersects, assign_area_to_region -from runpy import run_module - -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' - -# Go to section X. for the execution of the functions below - -#%% ------------------------------- ### -### 1. District Heating Data ### -### ------------------------------- ### - -def generate_districtheat_files(areas): - ### 1.0 Aggregate district heating data (only danish dataset for now) - DKareas = areas[areas[the_index].str.find('DK') != -1] - DH = DistrictHeat('Denmark_Futuregas') - DH.dfint = calculate_intersects(DKareas, DH.geo) # Find intersects between district heat areas and chosen areas - DH.assign_DH(DKareas, DH.dfint) - DH.assign_DHT(DKareas, DH.dfint) - - - ### 1.1 Check that the aggregation got all data: - # Annual DH - # print('\nOriginal data, annual DH:') - # print(DH.DH[DH.DH.A.str.find('DK') != -1].pivot_table(index='A', columns='Y').sum() / 1e6) - # print('\nNew data, annual DH:') - # print(DH.dfDH.sum() / 1e6) - - - ## Plot original vs aggregated data - year = '2050' - DH.plot_original_data(year, DKareas, plot_density=True) - DH.plot_aggregated_data(year, DKareas, False) - - - ### 1.2 Save .inc files - incfiles = {} - bodies = {'DH' : DH.dfDH, - 'DH_VAR_T' : DH.dfDHT} - for file in ['DH', 'DH_VAR_T']: - incfiles[file] = IncFile(prefix=read_lines(file+'_prefix.inc', - file_path='Data/IncFilePreSuf'), - body=bodies[file], - suffix=read_lines(file+'_suffix.inc', - file_path='Data/IncFilePreSuf'), - path='Output', - name=file) - - for file in incfiles.keys(): - incfiles[file].save() - - return incfiles, DH - -#%% ------------------------------- ### -### 2. VRE Data ### -### ------------------------------- ### - -# Run createVRE.py - takes a very long time! -#run_module('Modules.createVRE') - -#%% ------------------------------- ### -### 3. Industry ### -### ------------------------------- ### - -def generate_industry_files(areas): - IND = Industry() - - ### 1.2 Assign Original Region - IND.assign_original_region() - - # Assign fraction of emissions in region - IND.assign_emission_fractions() - - incfiles = IND.create_industry_data(areas, True) - - # Plot - IND.plot_aggregated_data(incfiles, areas, 'DH') - - # Prepare inc-files - ind_areas = incfiles['INDUSTRY_DH'].body['A'].unique() - incfiles['INDUSTRY_CCCRRRAAA'].body = '\n'.join(list(ind_areas)) - incfiles['INDUSTRY_RRRAAA'].body = '\n'.join([row['R'] + ' . ' + row['A'] for i,row in assign_area_to_region(pd.Series(ind_areas), A_suffix='', - method='splitstring', splitstring='_IND').iterrows()]) - incfiles['INDUSTRY_AAA'].body = '\n'.join(list(ind_areas)) - - # GKFX requires long names - pd.set_option('display.max_colwidth', None) - incfiles['INDUSTRY_GKFX'].body_prepare(['A', 'G'], ['Y']) - pd.set_option('display.max_colwidth', 30) - incfiles['INDUSTRY_DH'].body_prepare(['DHUSER', 'A'], ['Y']) - incfiles['INDUSTRY_DH_VAR_T'].body_prepare(['S', 'T'], ['A', 'DHUSER']) - incfiles['INDUSTRY_DE'].body_prepare(['DEUSER', 'R'], ['Y']) - incfiles['INDUSTRY_AGKN'].body = '\n'.join([f"AGKN('{row['A']}', '{row['G']}') = YES;" for i,row in incfiles['INDUSTRY_AGKN'].body.iterrows()]) - - # Make dedicated industry area set - incfiles['INDUSTRY_INDUSTRY_AAA'] = IncFile(prefix=incfiles['INDUSTRY_AAA'].prefix.replace('SET AAA', 'SET INDUSTRY_AAA'), - body=incfiles['INDUSTRY_AAA'].body, - suffix=incfiles['INDUSTRY_AAA'].suffix, - name='INDUSTRY_INDUSTRY_AAA', - path='Output') - - # Make investment option for LT areas that become infeasible otherwise - for A in pd.Series(incfiles['INDUSTRY_AAA'].body.split('\n')).unique(): - # Add a woodchip investment option in LT areas - if 'IND-LT' in A: - incfiles['INDUSTRY_AGKN'].body += f"AGKN('{A}', 'GNR_IND-DF_WOODCHI_E-100_MS-10-MW_Y-2020') = YES;\n" - - for file in ['INDUSTRY_GKFX', 'INDUSTRY_DH', 'INDUSTRY_DH_VAR_T', - 'INDUSTRY_DE', 'INDUSTRY_AGKN', 'INDUSTRY_CCCRRRAAA', - 'INDUSTRY_RRRAAA', 'INDUSTRY_AAA', 'INDUSTRY_INDUSTRY_AAA', - 'INDUSTRY_DISLOSS_E_AG']: - incfiles[file].save() - - return incfiles, IND - -# Plot data -# IND.plot_original_data() -# IND.plot_aggregated_data(incfiles, areas, 'GKFX') - -#%% ------------------------------- ### -### 4. Hydrogen ### -### ------------------------------- ### - -def generate_hydrogen_files(areas, choice, the_index): - - H2 = Hydrogen(choice) - - incfiles = H2.create_hydrogen_data(areas, the_index) - - # Prepare grid files - incfiles['HYDROGEN_XH2INVCOST'].body_prepare(['Y', 'RE'], ['RI']) - incfiles['HYDROGEN_XH2LOSS'].body_prepare(['RE'], ['RI']) - - for file in incfiles.keys(): - incfiles[file].save() - - return incfiles, H2 - -#%% ------------------------------- ### -### 5. Generate Files ### -### ------------------------------- ### - -if __name__ == '__main__': - - ### 5.1 Load the desired spatial resolution - choice = 'DKMunicipalities' - the_index, areas, c = prepared_geofiles(choice) - areas = areas[areas[the_index].str.find('DK') != -1] - hierarchical = False # Need to code something that can use another geofile as R-set (and improve the assign_area_to_region function to be able to find geometries related to A within geometries related to R) - - - ### 5.2 Generate Files - DHinc, DH = generate_districtheat_files(areas) - - INDinc, IND = generate_industry_files(areas) # Note: No possiblity to meet demand in LT areas! (only storage investments allowed) - - H2inc, H2 = generate_hydrogen_files(areas, choice, the_index) - - ### 5.3 If hierarchical approach, change RRRAAA sets here - if hierarchical: - # Change RRRAAA sets - # the_index2, areas2, c2 = prepared_geofiles(choice2) - # RRRAAA - INDinc['INDUSTRY_RRRAAA'] - \ No newline at end of file From 03bc3b9b051a2964cd9e091ef4b3d221d6cd2958 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 19:06:20 +0200 Subject: [PATCH 03/19] Made clustering script work again --- src/Modules/clustering.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Modules/clustering.py b/src/Modules/clustering.py index faffd1c..85a1e47 100644 --- a/src/Modules/clustering.py +++ b/src/Modules/clustering.py @@ -19,7 +19,7 @@ from geofiles import prepared_geofiles from scipy.sparse import csr_matrix from Submodules.municipal_template import DataContainer -from createDH import DistrictHeatAAU +from exo_heat_demand import DistrictHeatAAU from sklearn.cluster import KMeans, AgglomerativeClustering from sklearn.preprocessing import StandardScaler from sklearn.neighbors import kneighbors_graph @@ -162,6 +162,7 @@ def collect_clusterdata(plot_cf: bool = False): def cluster(con: DataContainer, + data_list: list, n_clusters: int, use_connectivity: bool = True, manual_corrections: list = [ @@ -176,14 +177,7 @@ def cluster(con: DataContainer, data_remark: str = 'all combined + xy coords'): # Stack data for clustering - X = np.vstack(( - con.muni.coords['lat'].data, - con.muni.coords['lon'].data, - con.muni.electricity_demand_mwh.sum(dim=['year', 'user']), - con.muni.heat_demand_mwh.sum(dim=['year', 'user']), - con.muni.wind_cf.data, - con.muni.solar_cf.data, - )).T + X = np.vstack([eval(data_evaluation) for data_evaluation in data_list]).T # K-Means Clustering # est = KMeans(n_clusters=n_clusters) @@ -244,6 +238,7 @@ def cluster(con: DataContainer, # ax.set_title('%d clusters, %s linkage, %s'%(n_clusters, name, connection_remark)) ax.axes.axis('off') + plt.show() # fig.savefig(r'C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Deliverables\Spatial Resolution\Investigations\240912 - Initial Clustering Method Tests'+'/'+plot_title.replace('data: ', '_').replace('\n', '').replace(' clusters', 'N').replace(' ', '_').replace(',', '') + '.png', # transparent=True, @@ -273,4 +268,12 @@ def cluster(con: DataContainer, if __name__ == '__main__': collected = collect_clusterdata() - fig, ax, clustering = cluster(collected, 20) + fig, ax, clustering = cluster(collected, [ + "collected.muni.coords['lat'].data", + "collected.muni.coords['lon'].data", + "collected.muni.electricity_demand_mwh.sum(dim=['year', 'user'])", + "collected.muni.heat_demand_mwh.sum(dim=['year', 'user'])", + 'collected.muni.wind_cf.data', + 'collected.muni.solar_cf.data' + ], + 6) From 258b92be8a9b80666ce3950c13f7d8c2b022c964 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 21:14:33 +0200 Subject: [PATCH 04/19] Prepared the second workflow --- .gitignore | 9 ++-- README.md | 39 +++++++++++++- src/.workflow/clustering | 26 +++++++++ src/{snakefile => .workflow/preprocessing} | 0 src/Analysis/clustering_dag.pdf | Bin 0 -> 6126 bytes src/Analysis/dot_source.txt | 41 -------------- src/Analysis/plot_dag.py | 28 ++++++---- ...nakemake_dag.pdf => preprocessing_dag.pdf} | Bin 15182 -> 15183 bytes src/Modules/clustering.py | 51 +++++++++++------- src/clustering.bat | 6 +++ src/preprocessing.bat | 6 +++ 11 files changed, 134 insertions(+), 72 deletions(-) create mode 100644 src/.workflow/clustering rename src/{snakefile => .workflow/preprocessing} (100%) create mode 100644 src/Analysis/clustering_dag.pdf delete mode 100644 src/Analysis/dot_source.txt rename src/Analysis/{snakemake_dag.pdf => preprocessing_dag.pdf} (74%) create mode 100644 src/clustering.bat create mode 100644 src/preprocessing.bat diff --git a/.gitignore b/.gitignore index 71ad7fd..03b6004 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,12 @@ __pycache__ .new_env/* prebalm *.pyc -snakemake_dag -snakemake_dag.* -!snakemake_dag.pdf + +clustering_dot_source.txt +clustering_dag +preprocessing_dot_source.txt +preprocessing_dag + gurobi.log exo_elec_dem_conversion_dictionaries.pkl diff --git a/README.md b/README.md index 9163f29..556a9ef 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,44 @@ The necessary python packages can be installed in a virtual environment by follo conda env create -f environment.yaml ```` +This requires [anaconda](https://www.anaconda.com/download?utm_source=anacondadoc&utm_medium=documentation&utm_campaign=download&utm_content=topnavalldocs) (or the more lightweight [miniconda](https://docs.anaconda.com/miniconda/#miniconda-latest-installer-links)) installs an environment with the following packages: +```` +name: spatialstudy +channels: + - conda-forge + - bioconda +dependencies: + - Cartopy=0.23.0 + - geopandas=1.0.1 + - matplotlib=3.9.2 + - ipywidgets=8.1.3 + - nbformat=5.9.2 + - numpy=1.26.2 + - openpyxl=3.1.2 + - pandas=2.1.4 + - plotly=5.16.1 + - pyarrow=15.0.1 + - pyproj=3.6.1 + - scipy=1.11.2 + - shapely=2.0.2 + - atlite=0.2.12 + - xarray=2024.2.0 + - xlrd=2.0.1 + - graphviz + - python-graphviz + - click + - snakemake-minimal + - pip + - pip: + - gamsapi[transfer]==45.7.0 + - pybalmorel==0.3.10 +```` + ## Get Started -The processing is initiated through a snakemake command in a command-line interface in the src directory. +The processing is initiated through a snakemake command in a command-line interface in the src directory. If using windows, the pre-processing can be run by calling `preprocessing` and the clustering through `clustering`. If on other systems, do either of the following commands: +``` +snakemake -s workflow/preprocessing +snakemake -s workflow/clustering +``` A plot of the processes can be found [here](src/Analysis/snakemake_dag.pdf). Note that vre_profiles.py and existing_powerplants is currently not part of the snakemake process, but the output from vre_profiles.py is used in the offshore_wind process. \ No newline at end of file diff --git a/src/.workflow/clustering b/src/.workflow/clustering new file mode 100644 index 0000000..2c99a61 --- /dev/null +++ b/src/.workflow/clustering @@ -0,0 +1,26 @@ +# Load the config file at the top +configfile: "assumptions.yaml" + +out_path = "Output/" +data_path = "Data/" +modules_path = "Modules/" +submod_path = "Modules/Submodules/" +balmorel_path = config['balmorel_input']['model_path'] +scenario = config['balmorel_input']['scenario'] +balmorel_sc_folder = f"{balmorel_path}/{scenario}/model/" + +# 1. General Purpose +rule all: + input: + [f"{out_path}WND_VAR_T.inc", + f"{balmorel_sc_folder}{scenario}_input_data.gdx"] + + +rule clustering: + output: + [f"{balmorel_sc_folder}{scenario}_input_data.gdx", + f"{out_path}WND_VAR_T.inc"] + script: + """ + python {modules_path}clustering.py --model-path={balmorel_sc_folder} --scenario={scenario} + """ diff --git a/src/snakefile b/src/.workflow/preprocessing similarity index 100% rename from src/snakefile rename to src/.workflow/preprocessing diff --git a/src/Analysis/clustering_dag.pdf b/src/Analysis/clustering_dag.pdf new file mode 100644 index 0000000000000000000000000000000000000000..94617d1ba9643a230ead707cdfd280b99059879f GIT binary patch literal 6126 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 literal 0 HcmV?d00001 diff --git a/src/Analysis/dot_source.txt b/src/Analysis/dot_source.txt deleted file mode 100644 index 772167b..0000000 --- a/src/Analysis/dot_source.txt +++ /dev/null @@ -1,41 +0,0 @@ -digraph snakemake_dag { - graph[bgcolor=white, margin=0]; - node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2]; - edge[penwidth=2, color=grey]; - 0[label = "all", color = "0.25 0.6 0.85", style="rounded,dashed"]; - 1[label = "exo_electricity_demand", color = "0.29 0.6 0.85", style="rounded,dashed"]; - 2[label = "create_conversion_dictionaries", color = "0.17 0.6 0.85", style="rounded,dashed"]; - 3[label = "format_energinet_data", color = "0.50 0.6 0.85", style="rounded,dashed"]; - 4[label = "transport_road_demand", color = "0.00 0.6 0.85", style="rounded,dashed"]; - 5[label = "format_dkstat_transport_data", color = "0.08 0.6 0.85", style="rounded,dashed"]; - 6[label = "exo_heat_demand", color = "0.42 0.6 0.85", style="rounded,dashed"]; - 7[label = "format_vpdk21_data", color = "0.38 0.6 0.85", style="rounded,dashed"]; - 8[label = "format_dkstat_industry_data", color = "0.54 0.6 0.85", style="rounded,dashed"]; - 9[label = "format_balmorel_data", color = "0.58 0.6 0.85", style="rounded,dashed"]; - 10[label = "geographic_sets", color = "0.62 0.6 0.85", style="rounded,dashed"]; - 11[label = "grids", color = "0.04 0.6 0.85", style="rounded,dashed"]; - 12[label = "investment_options", color = "0.12 0.6 0.85", style="rounded,dashed"]; - 13[label = "transport_h2_demand", color = "0.21 0.6 0.85", style="rounded,dashed"]; - 14[label = "offshore_wind", color = "0.33 0.6 0.85", style="rounded,dashed"]; - 1 -> 0 - 4 -> 0 - 6 -> 0 - 9 -> 0 - 10 -> 0 - 11 -> 0 - 12 -> 0 - 13 -> 0 - 14 -> 0 - 2 -> 1 - 3 -> 1 - 5 -> 4 - 2 -> 6 - 7 -> 6 - 8 -> 6 - 3 -> 6 - 6 -> 10 - 9 -> 11 - 2 -> 11 - 6 -> 12 - 5 -> 13 -} diff --git a/src/Analysis/plot_dag.py b/src/Analysis/plot_dag.py index 71f29b0..af6f889 100644 --- a/src/Analysis/plot_dag.py +++ b/src/Analysis/plot_dag.py @@ -3,17 +3,27 @@ https://graphviz.org/download/ """ +import click from graphviz import Source -# Do snakemake --dag > analysis/dot_source.txt to within the dot_source below -with open('Analysis/dot_source.txt', 'r') as f: - dot_source = f.read() +@click.command() +@click.option('--workflow', type=str, required=True, help="Which workflow to plot") +@click.option('--view', type=bool, required=False, help="View the plotted DAG?") +def main(workflow: str, view: bool = False): + # Do snakemake --dag > analysis/dot_source.txt to within the dot_source below + with open('Analysis/%s_dot_source.txt'%workflow, 'r') as f: + dot_source = f.read() -# Create a Source object from the DOT source -graph = Source(dot_source) + # Create a Source object from the DOT source + graph = Source(dot_source) -# Render the diagram as a PNG file -graph.render('Analysis/snakemake_dag', format='svg', cleanup=False) + # Render the diagram as a PNG file + graph.render('Analysis/%s_dag'%workflow, format='pdf', cleanup=False) -# Optionally view the diagram (depends on your environment) -graph.view() + if view: + # Optionally view the diagram (depends on your environment) + graph.view() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/Analysis/snakemake_dag.pdf b/src/Analysis/preprocessing_dag.pdf similarity index 74% rename from src/Analysis/snakemake_dag.pdf rename to src/Analysis/preprocessing_dag.pdf index bbe6cff76052e180a0aa5d4c4769ba283b7ffd2f..b4e71603c8d56bb261ecb996dff55a463439f51f 100644 GIT binary patch delta 3782 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 delta 3754 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 UZPXBClP5bM2sjEQB}Gq03PG|~K>z>% diff --git a/src/Modules/clustering.py b/src/Modules/clustering.py index 85a1e47..223e1b7 100644 --- a/src/Modules/clustering.py +++ b/src/Modules/clustering.py @@ -13,6 +13,8 @@ import matplotlib import matplotlib.pyplot as plt import matplotlib.colors as mcol +from pybalmorel import Balmorel +import click import pandas as pd import numpy as np import xarray as xr @@ -38,14 +40,6 @@ def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100): cmap(np.linspace(minval, maxval, n))) return new_cmap -style = 'ppt' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' #%% ------------------------------- ### ### 1. @@ -266,14 +260,35 @@ def cluster(con: DataContainer, return fig, ax, clustering +@click.command() +@click.option('--model-path', type=str, required=True, help='Balmorel model path') +@click.option('--scenario', type=str, required=True, help='Balmorel scenario') +@click.option('--plot-style', type=str, required=False, help='Style of the plot. Options are "report" (bright background) or "ppt" (dark background)') +def main(model_path: str, scenario: str, plot_style: str = 'report'): + + if plot_style == 'report': + plt.style.use('default') + fc = 'white' + elif plot_style == 'ppt': + plt.style.use('dark_background') + fc = 'none' + + # Collect Balmorel input data from scenario + model = Balmorel(model_path) + model.load_incfiles(scenario) + + # Old collection and clustering + # collected = collect_clusterdata() + # fig, ax, clustering = cluster(collected, [ + # "collected.muni.coords['lat'].data", + # "collected.muni.coords['lon'].data", + # "collected.muni.electricity_demand_mwh.sum(dim=['year', 'user'])", + # "collected.muni.heat_demand_mwh.sum(dim=['year', 'user'])", + # 'collected.muni.wind_cf.data', + # 'collected.muni.solar_cf.data' + # ], + # 6) + + if __name__ == '__main__': - collected = collect_clusterdata() - fig, ax, clustering = cluster(collected, [ - "collected.muni.coords['lat'].data", - "collected.muni.coords['lon'].data", - "collected.muni.electricity_demand_mwh.sum(dim=['year', 'user'])", - "collected.muni.heat_demand_mwh.sum(dim=['year', 'user'])", - 'collected.muni.wind_cf.data', - 'collected.muni.solar_cf.data' - ], - 6) + main() \ No newline at end of file diff --git a/src/clustering.bat b/src/clustering.bat new file mode 100644 index 0000000..785acc4 --- /dev/null +++ b/src/clustering.bat @@ -0,0 +1,6 @@ +@REM Run workflow +snakemake -s .workflow/clustering + +@REM Make DAG plot +snakemake -s .workflow/clustering --dag > analysis/clustering_dot_source.txt +python analysis/plot_dag.py --workflow=clustering --view=false \ No newline at end of file diff --git a/src/preprocessing.bat b/src/preprocessing.bat new file mode 100644 index 0000000..167dc3a --- /dev/null +++ b/src/preprocessing.bat @@ -0,0 +1,6 @@ +@REM Run workflow +snakemake -s .workflow/preprocessing + +@REM Make DAG plot +snakemake -s .workflow/preprocessing --dag > analysis/preprocessing_dot_source.txt +python analysis/plot_dag.py --workflow=preprocessing --view=false \ No newline at end of file From 4cf15e54588b4f2f7f3b30fe19a9a45f3377d77f Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 21:14:58 +0200 Subject: [PATCH 05/19] Corrected link to DAG pdf --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 556a9ef..a178db2 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,4 @@ The processing is initiated through a snakemake command in a command-line interf snakemake -s workflow/preprocessing snakemake -s workflow/clustering ``` -A plot of the processes can be found [here](src/Analysis/snakemake_dag.pdf). Note that vre_profiles.py and existing_powerplants is currently not part of the snakemake process, but the output from vre_profiles.py is used in the offshore_wind process. \ No newline at end of file +A plot of the processes can be found [here](src/Analysis/preprocessing_dag.pdf). Note that vre_profiles.py and existing_powerplants is currently not part of the snakemake process, but the output from vre_profiles.py is used in the offshore_wind process. \ No newline at end of file From 4ab46994e2b2892192ed5e7dbdd162609eea14f0 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 21:48:05 +0200 Subject: [PATCH 06/19] Prepared structure in clustering snakefile --- src/Analysis/clustering_dag.pdf | Bin 6126 -> 6909 bytes src/Analysis/preprocessing_dag.pdf | Bin 15183 -> 15182 bytes src/assumptions.yaml | 2 +- src/{.workflow => }/clustering | 21 ++++++++++++++++----- src/clustering.bat | 4 ++-- src/{.workflow => }/preprocessing | 6 +++--- src/preprocessing.bat | 4 ++-- 7 files changed, 24 insertions(+), 13 deletions(-) rename src/{.workflow => }/clustering (52%) rename src/{.workflow => }/preprocessing (95%) diff --git a/src/Analysis/clustering_dag.pdf b/src/Analysis/clustering_dag.pdf index 94617d1ba9643a230ead707cdfd280b99059879f..11a7172260b5573dcc6512c027425e01b47dec01 100644 GIT binary patch delta 5456 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 GPeuw6J$Z5f delta 4654 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 diff --git a/src/Analysis/preprocessing_dag.pdf b/src/Analysis/preprocessing_dag.pdf index b4e71603c8d56bb261ecb996dff55a463439f51f..5794b0ed5766b6cb487016c28084f79fe2746861 100644 GIT binary patch delta 3587 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 JB_%~qMhcDp%!L2| delta 3564 zcmV zzxB?n-!T>HKWr15IDZctmd4X>+43jzBz$-@==Q4)0wVhwV>y(MLkDf@6NqM~N4<+X zUx>^iSY|$~g$A+sM59BteL7g~T~<-`01v)VEL+n3@AA#H30J>e$8`1YYh$nej<*_W zWqrW{57(&tbZyerkJm0<{Zm?`vcgZ-3T^+rzR7KZ*VoQ-XuSG>%Q9CVS$;k5Dg|4- z+0Frz@c|tHYLgEF9|8W8F9IxoOG*x?I+8>SC3_S0oWdq+3~X_HcY+R(Zn4)!Ey(B& z6>0V`8{&XAuW)wZ`|K0yxsF!sGfHz)N00)`jCz{{IzJ#+QfsM06IXbc1wCh6Uc@6@ zU?G0wP~m=75P}v07ory_BSbI7O4!mzI7LYMm+PBS>0HMBbbaGhxavoYiXA^(uUtoLw1)Cp@=c|LJ~ zPCNz&K;7z$-$i;hK+;x!16M%dJJDg_*ruTXuX9(qKCtS$VEqCQTQqLen<5a?4XCBd zC{(DpqgE^X?AZ3{aGrVWM`%7k3WcW$|MyLHuN<(Du(VLBH|lU0rOp1nZ|lrLgkTt=Octe`@-K@^nWs8bAoc%lTMPAqeW9O*@^ZkXYxDTUE*H+jx+oU5;(G zaz9bOBb6?8ir6B5PZgog2Ur(Hh?H08AhR5qA2KVa%>@T?DPYb$xUzvz>z;(;9h77g z+YGdkoRZ6kY_?_B-l2>R)wTlzUrN$0mEF@2f(;Qxdb$jHEe>ze@1)H;^nuhc*a6HR zND!gw^KpZ$g`$PJPpoLMk0H|K36`{Bc|uIZiej~Pbq5rGQ3DE4RPnV+Y>W!Ac$-4^ zSxGVj%DUqLGcQB%vI0VqmLVQ8TLXNU*dMi;*031(^$d>?8o1p)Y%IJ5ZMiBoh1RM> zT!f{_$&W*pN~d?z252o%eP-6}6voa@ zFjxlE*MCdUUlmh(RIIvE8BKO#s-Fcfy@H<;9DjzvRMf5I#Ut#d2Se<#Nq3{^1z|JmKcnkyJ)l#AQEWR}?SOz8;@HG-; zr3aDOCGNWGyR%`(9=w=qrvawPx(qSRQgD;iSIlTom@N=@mF|P@g~lptY2aj!@X`w$ z*?%Uk+&$J5CSFTpmnWF+Rh}(W{;7HJtN%2w~hCzGoes5()! z22x9dB%%pt7m{6@k>uD`%AWqiEcJaIp#yN~wUAwPwvM=n;HtV>HFh-7q2u8DR5;aq z1&_r8yxt$9+QqM*X@O~+eL&gQ=0!&dfr|=j`2aNxzmK02w+e9b#rtE@Onj0^(SKeT zje?-yjWqMY8A(e;A0s#EAbt3)w7 z>`UuSO$f1^Q^TQ1z@8zCie&PM8-Kzk22!&F#bujnp7OEWd1k`mvx8Q@Foi4RC{feu zV2BB2*uS|I-A9n2`N=*)z`dJNW}GU31{qrMNx~^1Xo@kA4GoV#|G_vAt~lEl3)m1P z>lJIx0gHzu9y3h@Pji9G6EUyc1?=gdWAouG%##>nvz0a{j@F$aYSYwfO@9j$>keCv zb~MBR89kkFc3}jyPpIcQ5}TQ&xv3)v;m8PGtA|n9X6&%$XqQrl@&(0!hgr~b#^psk zCS;*!<0WC&|IaZVvf9-FKM!|e`WeTcGhE^n#{g}YNbgM~dj<(W??oiCWpXw!wIBi+ zHfr*g;pZ(UdtzQmT~ z?MNhi$Erbo3e-VwW+tJu{PH;WW67%|dECzq`}|`N;+=H;&>i1c&3_H~W=E|b;e0;T zdiaj;E~;;s<#Q8P!O#c z1tPV0WsE|>zA=Wm0)G#-law4RUa#dabgdaLb^LszM`};U!D)x_tl?bGxO1>H{26R< zj5q|%&Pif2{C>h0E62Ayl)gOU=&=l660d>FlILMSJBV#lBN2DP78VAb<)K=}@qFB^ zd;vu_orh^xgR5Yun46CL_{U3Bg;5rYNq#NBYzuvPCSoo=On?1EOh||Vkz}4E)5LA^ z?r|8)Tp(+Vh)Oxk*|=L3u{W``@j4h0qGg)*0!eC06zNKLT^BD`As;N^lx(+DcwgW! z#f$A?En_{g-HdD#gk6b>OS&LwqscR8<8GDJp6bcK`Ze+q5e{1TJs#6?CCLSZSPx4A zwr2eTkIB1Wtbbga&9G!#6wi(nLVDo6$1Dpnp?*1kIr!&R@*brg+*(P|TSno$5&kj~ zgtE*qrbd!ypf)xoA|#^*1L zeXg}OFGPoZ^VNysq-!na+xwQ-Xwl&1%e`N(Z|e7U?(?#JiQjlg7PTgQVcAD55{L{z zU6SBQ=t|^;A^3q)3VvaT2vYaI5nDC}_gndxSGhQA_B2OE#m_C6fNIq$!)KOid%`W+B3p3@|+SzDa*45!tM!)R+GP2xO3KizUEr` z96s3$-elYJqxx}iiF>=8fjyEf^kALbAlvjOt)>x0;TzvC9y49h*-_00GkPy^UGWR7 zp5)qWN%_>3NkeyFd%PYY6>7AATl>MlQcS=RWUFyGVlWlK4*{^ zCy=Gp0wm1;fW&SA3I13hVKE29Dh3kj^FhRRAfXA8&@2HGIv;_A$#f909!Qwp1rc9B mrZ7V+VgwP)j8u~J0J)-3MC~)Peuv}Wy%f! diff --git a/src/assumptions.yaml b/src/assumptions.yaml index 6c28e26..ccc05f3 100644 --- a/src/assumptions.yaml +++ b/src/assumptions.yaml @@ -1,6 +1,6 @@ balmorel_input: model_path: "../../Balmorel" - scenario: "muni" + scenario: "new_muni" load_again: False grid_assumptions: diff --git a/src/.workflow/clustering b/src/clustering similarity index 52% rename from src/.workflow/clustering rename to src/clustering index 2c99a61..9ed9186 100644 --- a/src/.workflow/clustering +++ b/src/clustering @@ -13,14 +13,25 @@ balmorel_sc_folder = f"{balmorel_path}/{scenario}/model/" rule all: input: [f"{out_path}WND_VAR_T.inc", - f"{balmorel_sc_folder}{scenario}_input_data.gdx"] + ] +rule collect_balmorel_input: + output: + f"{balmorel_sc_folder}{scenario}_input_data.gdx" + run: + from pybalmorel import Balmorel + model = Balmorel(balmorel_path) + model.load_incfiles(scenario) rule clustering: + input: + [ + f"{balmorel_sc_folder}{scenario}_input_data.gdx", + f"{modules_path}clustering.py" + ] output: - [f"{balmorel_sc_folder}{scenario}_input_data.gdx", - f"{out_path}WND_VAR_T.inc"] - script: + [f"{out_path}WND_VAR_T.inc"] + shell: """ - python {modules_path}clustering.py --model-path={balmorel_sc_folder} --scenario={scenario} + python {modules_path}clustering.py --model-path={balmorel_path} --scenario={scenario} """ diff --git a/src/clustering.bat b/src/clustering.bat index 785acc4..d3d0533 100644 --- a/src/clustering.bat +++ b/src/clustering.bat @@ -1,6 +1,6 @@ @REM Run workflow -snakemake -s .workflow/clustering +snakemake -s clustering @REM Make DAG plot -snakemake -s .workflow/clustering --dag > analysis/clustering_dot_source.txt +snakemake -s clustering --dag > analysis/clustering_dot_source.txt python analysis/plot_dag.py --workflow=clustering --view=false \ No newline at end of file diff --git a/src/.workflow/preprocessing b/src/preprocessing similarity index 95% rename from src/.workflow/preprocessing rename to src/preprocessing index 793b1d8..91e692d 100644 --- a/src/.workflow/preprocessing +++ b/src/preprocessing @@ -1,10 +1,10 @@ # Load the config file at the top configfile: "assumptions.yaml" -out_path = "Output/" -data_path = "Data/" +out_path = "Output/" +data_path = "Data/" modules_path = "Modules/" -submod_path = "Modules/Submodules/" +submod_path = "Modules/Submodules/" # 1. General Purpose rule all: diff --git a/src/preprocessing.bat b/src/preprocessing.bat index 167dc3a..fbf1e7f 100644 --- a/src/preprocessing.bat +++ b/src/preprocessing.bat @@ -1,6 +1,6 @@ @REM Run workflow -snakemake -s .workflow/preprocessing +snakemake -s preprocessing @REM Make DAG plot -snakemake -s .workflow/preprocessing --dag > analysis/preprocessing_dot_source.txt +snakemake -s preprocessing --dag > analysis/preprocessing_dot_source.txt python analysis/plot_dag.py --workflow=preprocessing --view=false \ No newline at end of file From 7056343049532dbf5fe0a4cd01d2ae74b6eff0cf Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 23:15:22 +0200 Subject: [PATCH 07/19] Added data path to some hardcoded spots --- src/preprocessing | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preprocessing b/src/preprocessing index 91e692d..7bfa443 100644 --- a/src/preprocessing +++ b/src/preprocessing @@ -68,7 +68,7 @@ rule format_vpdk21_data: rule format_dkstat_industry_data: input: - [r"Data\Danmarks Statistik\Industriforbrug Type.xlsx", + [f"{data_path}Danmarks Statistik/Industriforbrug Type.xlsx", f"{modules_path}format_dkstat.py"] output: f"{data_path}Danmarks Statistik/industry_demand.nc" @@ -121,7 +121,7 @@ rule exo_heat_demand: # 4. Transport Demand rule format_dkstat_transport_data: input: - [r"Data\Danmarks Statistik\Transportforbrug Type.xlsx", + [f"{data_path}Danmarks Statistik/Transportforbrug Type.xlsx", f"{modules_path}format_dkstat.py"] output: f"{data_path}Danmarks Statistik/transport_demand.csv" From a91118da0a2774849372660ebc9d8948b89fbc4e Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 30 Sep 2024 23:18:06 +0200 Subject: [PATCH 08/19] Made .bat scripts accept more arguments for the snakemake executions --- src/clustering.bat | 4 ++-- src/preprocessing.bat | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/clustering.bat b/src/clustering.bat index d3d0533..71a438d 100644 --- a/src/clustering.bat +++ b/src/clustering.bat @@ -1,5 +1,5 @@ -@REM Run workflow -snakemake -s clustering +@REM Run workflow with additional arguments +snakemake -s clustering %* @REM Make DAG plot snakemake -s clustering --dag > analysis/clustering_dot_source.txt diff --git a/src/preprocessing.bat b/src/preprocessing.bat index fbf1e7f..6c1a7c9 100644 --- a/src/preprocessing.bat +++ b/src/preprocessing.bat @@ -1,5 +1,5 @@ -@REM Run workflow -snakemake -s preprocessing +@REM Run workflow with additional arguments +snakemake -s preprocessing %* @REM Make DAG plot snakemake -s preprocessing --dag > analysis/preprocessing_dot_source.txt From 9c24ae4298fd48dbbc09d20a80ceb665bad8250c Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 10:37:29 +0200 Subject: [PATCH 09/19] Fixed some errors --- src/Modules/investment_options.py | 166 +++++++++++++++++++++++++++--- src/Modules/offshore_wind.py | 10 +- src/preprocessing | 5 +- 3 files changed, 162 insertions(+), 19 deletions(-) diff --git a/src/Modules/investment_options.py b/src/Modules/investment_options.py index 1a02f19..d173333 100644 --- a/src/Modules/investment_options.py +++ b/src/Modules/investment_options.py @@ -33,25 +33,37 @@ def save_symbol_from_all_endofmodel(symbol: str, f.to_parquet('Data/BalmorelData/AGKN_fromKountouris2024.gzip') def base_AGKN(AGKN: pd.DataFrame, print_options: bool = False): - # 1. Special options + # Options not to be included # This was copy pasted from the outcommented print statements below - wind_off_G = [ - "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2020", + other_vre = [ + "GNR_WT-SP277-HH100_ONS_LS_L-RG2_Y-2020", + "GNR_WT-SP277-HH100_ONS_LS_L-RG2_Y-2030", + "GNR_WT-SP277-HH100_ONS_LS_L-RG2_Y-2040", + "GNR_WT-SP277-HH100_ONS_LS_L-RG2_Y-2050", + "GNR_WT-SP277-HH100_ONS_LS_L-RG3_Y-2020", + "GNR_WT-SP277-HH100_ONS_LS_L-RG3_Y-2030", + "GNR_WT-SP277-HH100_ONS_LS_L-RG3_Y-2040", + "GNR_WT-SP277-HH100_ONS_LS_L-RG3_Y-2050", "DK2-OFF1_WT_WIND_OFF_L-RG2_Y-2020", - "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2020", "DK1-OFF1_WT_WIND_OFF_L-RG2_Y-2020", - "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2030", "DK2-OFF1_WT_WIND_OFF_L-RG2_Y-2030", - "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2030", "DK1-OFF1_WT_WIND_OFF_L-RG2_Y-2030", - "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2040", "DK2-OFF1_WT_WIND_OFF_L-RG2_Y-2040", - "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2040", "DK1-OFF1_WT_WIND_OFF_L-RG2_Y-2040", - "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2050", "DK2-OFF1_WT_WIND_OFF_L-RG2_Y-2050", - "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2050", "DK1-OFF1_WT_WIND_OFF_L-RG2_Y-2050", + "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2020", + "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2020", + "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2030", + "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2030", + "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2040", + "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2040", + "DK2-OFF1_WT_WIND_OFF_L-RG1_Y-2050", + "DK1-OFF1_WT_WIND_OFF_L-RG1_Y-2050", + "GNR_PV_SUN_LS-8-MW_RG2_Y-2020", + "GNR_PV_SUN_LS-8-MW_RG2_Y-2030", + "GNR_PV_SUN_LS-8-MW_RG2_Y-2040", + "GNR_PV_SUN_LS-8-MW_RG2_Y-2050", ] solar_heating = [ @@ -61,7 +73,6 @@ def base_AGKN(AGKN: pd.DataFrame, print_options: bool = False): 'GNR_SH_SUN_LS_Y-2050', ] - # This was copy pasted from the outcommented print statements below hydrogen_options = [ 'GNR_ELYS_ELEC_AEC_Y-2020', 'GNR_ELYS_ELEC_AEC_Y-2030', @@ -86,13 +97,144 @@ def base_AGKN(AGKN: pd.DataFrame, print_options: bool = False): 'GNR_STEAM-REFORMING-CCS_E-70_Y-2020', ] + # Small scale investments + small_scale = [ + "GNR_CC_NGAS_BP_E-51_SS-10-MW_Y-2020", + "GNR_CC_NGAS_BP_E-53_SS-10-MW_Y-2030", + "GNR_CC_NGAS_BP_E-54_SS-10-MW_Y-2040", + "GNR_CC_NGAS_BP_E-55_SS-10-MW_Y-2050", + "GNR_GT_NGAS_BP_E-37_SS-5-MW_Y-2020", + "GNR_GT_NGAS_BP_E-39_SS-5-MW_Y-2030", + "GNR_GT_NGAS_BP_E-40_SS-5-MW_Y-2040", + "GNR_GT_NGAS_BP_E-40_SS-5-MW_Y-2050", + "GNR_ST_STRW_BP_E-17_SS-20-MW-FEED_Y-2020", + "GNR_ST_STRW_BP_E-17_SS-20-MW-FEED_Y-2030", + "GNR_ST_STRW_BP_E-17_SS-20-MW-FEED_Y-2040", + "GNR_ST_STRW_BP_E-17_SS-20-MW-FEED_Y-2050", + "GNR_ST_WOODCHI_BP_E-16_SS-20-MW-FEED_Y-2020", + "GNR_ST_WOODCHI_BP_E-16_SS-20-MW-FEED_Y-2030", + "GNR_ST_WOODCHI_BP_E-16_SS-20-MW-FEED_Y-2040", + "GNR_ST_WOODCHI_BP_E-16_SS-20-MW-FEED_Y-2050", + "GNR_ST_WOODPEL_BP_E-17_SS-20-MW-FEED_Y-2020", + "GNR_ST_WOODPEL_BP_E-17_SS-20-MW-FEED_Y-2030", + "GNR_ST_WOODPEL_BP_E-17_SS-20-MW-FEED_Y-2040", + "GNR_ST_WOODPEL_BP_E-17_SS-20-MW-FEED_Y-2050", + ] + + medium_scale = [ + "GNR_BO_ELEC_E-99_MS-1-MW-FEED_Y-2020", + "GNR_BO_ELEC_E-99_MS-1-MW-FEED_Y-2030", + "GNR_BO_ELEC_E-99_MS-1-MW-FEED_Y-2040", + "GNR_BO_ELEC_E-99_MS-1-MW-FEED_Y-2050", + "GNR_BO_NGAS_E-105_MS-5-MW_Y-2020", + "GNR_BO_NGAS_E-106_MS-5-MW_Y-2030", + "GNR_BO_NGAS_E-106_MS-5-MW_Y-2040", + "GNR_BO_NGAS_E-106_MS-5-MW_Y-2050", + "GNR_ST_MSW_BP_E-23_MS-80-MW-FEED_Y-2020", + "GNR_ST_MSW_BP_E-24_MS-80-MW-FEED_Y-2030", + "GNR_ST_MSW_BP_E-24_MS-80-MW-FEED_Y-2040", + "GNR_ST_MSW_BP_E-24_MS-80-MW-FEED_Y-2050", + "GNR_ST_NGAS_BP_E-7_MS-15-MW_Y-2020", + "GNR_ST_STRW_BP_E-25_MS-80-MW-FEED_Y-2020", + "GNR_ST_STRW_BP_E-25_MS-80-MW-FEED_Y-2030", + "GNR_ST_STRW_BP_E-25_MS-80-MW-FEED_Y-2040", + "GNR_ST_STRW_BP_E-25_MS-80-MW-FEED_Y-2050", + "GNR_ST_WOODCHI_BP_E-29_MS-80-MW-FEED_Y-2020", + "GNR_ST_WOODCHI_BP_E-29_MS-80-MW-FEED_Y-2030", + "GNR_ST_WOODCHI_BP_E-29_MS-80-MW-FEED_Y-2040", + "GNR_ST_WOODCHI_BP_E-29_MS-80-MW-FEED_Y-2050", + "GNR_ST_WOODPEL_BP_E-30_MS-80-MW-FEED_Y-2020", + "GNR_ST_WOODPEL_BP_E-30_MS-80-MW-FEED_Y-2030", + "GNR_ST_WOODPEL_BP_E-30_MS-80-MW-FEED_Y-2040", + "GNR_ST_WOODPEL_BP_E-30_MS-80-MW-FEED_Y-2050", + ] + + large_scale = [ + "GNR_BO_MSW_E-106_LS-35-MW-FEED_Y-2030", + "GNR_BO_MSW_E-106_LS-35-MW-FEED_Y-2040", + "GNR_BO_MSW_E-106_LS-35-MW-FEED_Y-2050", + "GNR_ST_MSW_CND_E-23_LS-220-MW-FEED_Y-2020", + "GNR_ST_MSW_CND_E-24_LS-220-MW-FEED_Y-2030", + "GNR_ST_MSW_CND_E-25_LS-220-MW-FEED_Y-2040", + "GNR_ST_MSW_CND_E-25_LS-220-MW-FEED_Y-2050", + "GNR_BO_MSW_E-106_LS-35-MW-FEED_Y-2020", + "GNR_ST_MSW_BP_E-23_LS-220-MW-FEED_Y-2020", + "GNR_ST_MSW_BP_E-24_LS-220-MW-FEED_Y-2030", + "GNR_ST_MSW_BP_E-25_LS-220-MW-FEED_Y-2040", + "GNR_ST_MSW_BP_E-25_LS-220-MW-FEED_Y-2050", + "GNR_ST_WOODCHI_CND_E-29_LS-600-MW-FEED_Y-2020", + "GNR_ST_WOODCHI_CND_E-29_LS-600-MW-FEED_Y-2030", + "GNR_ST_WOODCHI_CND_E-29_LS-600-MW-FEED_Y-2040", + "GNR_ST_WOODCHI_CND_E-29_LS-600-MW-FEED_Y-2050", + "GNR_ST_WOODPEL_CND_E-33_LS-800-MW-FEED_Y-2020", + "GNR_ST_WOODPEL_CND_E-33_LS-800-MW-FEED_Y-2030", + "GNR_ST_WOODPEL_CND_E-33_LS-800-MW-FEED_Y-2040", + "GNR_ST_WOODPEL_CND_E-33_LS-800-MW-FEED_Y-2050", + "GNR_ST_STRW_CND_E-31_LS-132-MW-FEED_Y-2020", + "GNR_ST_STRW_CND_E-31_LS-132-MW-FEED_Y-2030", + "GNR_ST_STRW_CND_E-31_LS-132-MW-FEED_Y-2040", + "GNR_ST_STRW_CND_E-31_LS-132-MW-FEED_Y-2050", + "GNR_ST_NGAS_CND_E-47_LS-400-MW_Y-2020", + "GNR_ST_NGASCCS_CND_E-47_LS-400-MW_Y-2020", + "GNR_GT_NGAS_CND_E-42_LS-40-MW_Y-2020", + "GNR_GT_NGAS_CND_E-43_LS-40-MW_Y-2030", + "GNR_GT_NGAS_CND_E-44_LS-40-MW_Y-2040", + "GNR_GT_NGAS_CND_E-44_LS-40-MW_Y-2050", + "GNR_CC_NGAS_CND_E-59_LS-100-MW_Y-2020", + "GNR_CC_NGAS_CND_E-61_LS-100-MW_Y-2030", + "GNR_CC_NGAS_CND_E-62_LS-100-MW_Y-2040", + "GNR_CC_NGAS_CND_E-63_LS-100-MW_Y-2050", + "GNR_CC_NGASCCS_CND_E-59_LS-100-MW_Y-2020", + "GNR_CC_NGASCCS_CND_E-61_LS-100-MW_Y-2030", + "GNR_CC_NGASCCS_CND_E-62_LS-100-MW_Y-2040", + "GNR_CC_NGASCCS_CND_E-63_LS-100-MW_Y-2050", + "GNR_ST_NGAS_EXT_E-47_LS-400-MW_Y-2020", + "GNR_ST_NGASCCS_EXT_E-47_LS-400-MW_Y-2020", + "GNR_ST_STRW_BP_E-31_LS-132-MW-FEED_Y-2020", + "GNR_ST_STRW_BP_E-31_LS-132-MW-FEED_Y-2030", + "GNR_ST_STRW_BP_E-31_LS-132-MW-FEED_Y-2040", + "GNR_ST_STRW_BP_E-31_LS-132-MW-FEED_Y-2050", + "GNR_ST_WOODCHI_BP_E-29_LS-600-MW-FEED_Y-2020", + "GNR_ST_WOODCHI_BP_E-29_LS-600-MW-FEED_Y-2030", + "GNR_ST_WOODCHI_BP_E-29_LS-600-MW-FEED_Y-2040", + "GNR_ST_WOODCHI_BP_E-29_LS-600-MW-FEED_Y-2050", + "GNR_ST_WOODPEL_BP_E-33_LS-800-MW-FEED_Y-2020", + "GNR_ST_WOODPEL_BP_E-33_LS-800-MW-FEED_Y-2030", + "GNR_ST_WOODPEL_BP_E-33_LS-800-MW-FEED_Y-2040", + "GNR_ST_WOODPEL_BP_E-33_LS-800-MW-FEED_Y-2050", + "GNR_GT_NGAS_BP_E-42_LS-40-MW_Y-2020", + "GNR_GT_NGAS_BP_E-43_LS-40-MW_Y-2030", + "GNR_GT_NGAS_BP_E-44_LS-40-MW_Y-2040", + "GNR_GT_NGAS_BP_E-44_LS-40-MW_Y-2050", + "GNR_BO_STRW_E-102_LS-6-MW_Y-2020", + "GNR_BO_STRW_E-102_LS-6-MW_Y-2030", + "GNR_BO_STRW_E-102_LS-6-MW_Y-2040", + "GNR_BO_STRW_E-102_LS-6-MW_Y-2050", + "GNR_BO_WOODCHI_E-115_LS-7-MW_Y-2020", + "GNR_BO_WOODCHI_E-115_LS-7-MW_Y-2030", + "GNR_BO_WOODCHI_E-115_LS-7-MW_Y-2040", + "GNR_BO_WOODCHI_E-115_LS-7-MW_Y-2050", + "GNR_BO_WOODPEL_E-100_LS-6-MW_Y-2020", + "GNR_BO_WOODPEL_E-100_LS-6-MW_Y-2030", + "GNR_BO_WOODPEL_E-100_LS-6-MW_Y-2040", + "GNR_BO_WOODPEL_E-100_LS-6-MW_Y-2050", + "GNR_CC_NGAS_EXT_E-59_LS-100-MW_Y-2020", + "GNR_CC_NGAS_EXT_E-61_LS-100-MW_Y-2030", + "GNR_CC_NGAS_EXT_E-62_LS-100-MW_Y-2040", + "GNR_CC_NGAS_EXT_E-63_LS-100-MW_Y-2050", + "GNR_CC_NGASCCS_EXT_E-59_LS-100-MW_Y-2020", + "GNR_CC_NGASCCS_EXT_E-61_LS-100-MW_Y-2030", + "GNR_CC_NGASCCS_EXT_E-62_LS-100-MW_Y-2040", + "GNR_CC_NGASCCS_EXT_E-63_LS-100-MW_Y-2050", + ] + # 2. Base and Hydrogen investments temp = AGKN.query( "~A.str.contains('IDVU') and ~A.str.contains('IND')" ) # Get base investment options and remove elements in wind_off_G from base_options (add hydrogen when the error arrives) - base_options = [option for option in temp.G.unique() if option not in wind_off_G and option not in hydrogen_options and option not in solar_heating] + base_options = [option for option in temp.G.unique() if option not in other_vre + hydrogen_options + solar_heating + medium_scale + large_scale] # Use this to inspect offshore and hydrogen investment options if print_options: diff --git a/src/Modules/offshore_wind.py b/src/Modules/offshore_wind.py index a9f65a6..7208e57 100644 --- a/src/Modules/offshore_wind.py +++ b/src/Modules/offshore_wind.py @@ -88,7 +88,7 @@ def create_profiles(profiles: xr.Dataset, year: int = 2012): suffix="\n".join([ "", ";", - "WND_VAR_T(AAA,SSS,TTT) = WND_VAR_T1(SSS,TTT,AAA);", + "WND_VAR_T(AAA,SSS,TTT)$WND_VAR_T1(SSS,TTT,AAA) = WND_VAR_T1(SSS,TTT,AAA);", "WND_VAR_T1(SSS,TTT,AAA) = 0;" ])) profiles.columns.name = '' @@ -129,16 +129,14 @@ def distribute_offshore_potential(total_potential: float, geofile: gpd.GeoDataFr temp = geofile.copy().to_crs('EPSG:4093') # To geocentric (meters) geofile['Values'] = temp.geometry.area / temp.geometry.area.sum() * total_potential geofile['TECH_GROUP'] = 'WINDTURBINE_OFFSHORE' - - print(geofile.drop(columns='geometry').pivot_table(index=['Name', 'TECH_GROUP'])) + geofile['SUBTECH_GROUP'] = 'RG1' # SUBTECHGROUPKPOT f = IncFile(name='OFFSHORE_SUBTECHGROUPKPOT', path='Output', prefix="TABLE SUBTECHGROUPKPOT(CCCRRRAAA, TECH_GROUP, SUBTECH_GROUP) 'Subtechnology group capacity restriction by geography (MW)'\n", suffix='\n;') - f.body = geofile.drop(columns='geometry').pivot_table(index=['Name', 'TECH_GROUP'], values='Values', aggfunc='sum') - f.body.columns.name = 'RG1' - f.body.index.names = ['', ''] + f.body = geofile.drop(columns='geometry') + f.body_prepare(index=['Name', 'TECH_GROUP'], columns='SUBTECH_GROUP', values='Values') f.save() #%% ------------------------------- ### diff --git a/src/preprocessing b/src/preprocessing index 7bfa443..7fd6245 100644 --- a/src/preprocessing +++ b/src/preprocessing @@ -213,7 +213,10 @@ rule grids: # 7. Other rule offshore_wind: input: - f'{out_path}VRE/2012_offshore_wind.nc' + [ + f'{out_path}VRE/2012_offshore_wind.nc', + f"{modules_path}offshore_wind.py" + ] params: weather_year=config['timeseries']['weather_year'], total_offshore_wind_potential=config['timeseries']['total_offshore_wind_potential'] From 46e043b25cefa1d033f8aef2fe1419542cb25224 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 11:17:54 +0200 Subject: [PATCH 10/19] Made last fixes to offshore --- src/Modules/grids.py | 4 ++++ src/Modules/offshore_wind.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Modules/grids.py b/src/Modules/grids.py index 1b473db..684537d 100644 --- a/src/Modules/grids.py +++ b/src/Modules/grids.py @@ -202,6 +202,10 @@ def create_tech_specific_distribution_loss(wind_offshore_loss: dict, f"DISLOSS_E_AG(IA,G)$(GDATA(G,'GDSUBTECHGROUP') EQ RG3_OFF4 AND (AGKN(IA,G) OR SUM(Y, GKFX(Y,IA,G))))={wind_offshore_loss['RG3']};", f"DISLOSS_E_AG(IA,G)$(GDATA(G,'GDSUBTECHGROUP') EQ RG3_OFF5 AND (AGKN(IA,G) OR SUM(Y, GKFX(Y,IA,G))))={wind_offshore_loss['RG3']};", ])) + + # Add larger loss for Nordsøen + f.body += f"\nDISLOSS_E_AG('Nordsoeen', G)$(GDATA(G,'GDSUBTECHGROUP') EQ RG1) = {wind_offshore_loss['RG3']};" + f.save() diff --git a/src/Modules/offshore_wind.py b/src/Modules/offshore_wind.py index 7208e57..cfc908d 100644 --- a/src/Modules/offshore_wind.py +++ b/src/Modules/offshore_wind.py @@ -129,7 +129,7 @@ def distribute_offshore_potential(total_potential: float, geofile: gpd.GeoDataFr temp = geofile.copy().to_crs('EPSG:4093') # To geocentric (meters) geofile['Values'] = temp.geometry.area / temp.geometry.area.sum() * total_potential geofile['TECH_GROUP'] = 'WINDTURBINE_OFFSHORE' - geofile['SUBTECH_GROUP'] = 'RG1' + geofile['SUBTECH_GROUP'] = 'RG1_OFF1' # SUBTECHGROUPKPOT f = IncFile(name='OFFSHORE_SUBTECHGROUPKPOT', path='Output', From 6d06794b40762cdeee030f810051a5f643e0c8f8 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 13:14:21 +0200 Subject: [PATCH 11/19] First effort to make vre profile generation 'pure' script --- src/Modules/vre_profiles.py | 989 ++++++++++++++++++------------------ 1 file changed, 491 insertions(+), 498 deletions(-) diff --git a/src/Modules/vre_profiles.py b/src/Modules/vre_profiles.py index 263b09b..2fab6cf 100644 --- a/src/Modules/vre_profiles.py +++ b/src/Modules/vre_profiles.py @@ -19,29 +19,18 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt -from matplotlib import rc -from scipy.optimize import curve_fit import atlite import geopandas as gpd import cartopy.crs as ccrs -from shapely.geometry import MultiPolygon -from pyproj import Proj, Transformer +from pyproj import Proj from rasterio.plot import show import xarray as xr from atlite.gis import shape_availability, ExclusionContainer -import os from Modules.geofiles import prepared_geofiles import logging +import click logging.basicConfig(level=logging.INFO) -style = 'report' - -if style == 'report': - plt.style.use('default') - fc = 'white' -elif style == 'ppt': - plt.style.use('dark_background') - fc = 'none' ### Function fo load duration curve analysis def doLDC(file, cols, idx, r, c, title=''): @@ -92,530 +81,534 @@ def doLDC(file, cols, idx, r, c, title=''): ### 0. Assumptions ### ### ------------------------------- ### -### 0.1 Capacity pr. km for PV and Wind -cap_per_sqkm_pv = 1.7 # MW/km2 -cap_per_sqkm_wind = 0.67 # MW/km2 According to NREL: 2 MW / 1.5 acres (0.00607028 km2) - - -### 0.2 Choice of technologies -panel = atlite.solarpanels.CSi # Possible to choose crystalline Si (CSi) or advanced cadmium-tellurium (CdTe) -wind_turbine = atlite.windturbines.Vestas_V66_1750kW -# wind_turbine = atlite.windturbines.Enercon_E82_3000kW -# wind_turbine = atlite.windturbines.Bonus_B1000_1000kW - -# Offshore wind -# wind_turbine = atlite.windturbines.NREL_ReferenceTurbine_5MW_offshore - -### 0.3 What areas to load? -# choice = 'DK Municipalities' -# choice = 'NUTS1' -# choice = 'NUTS2' -# choice = 'NUTS3' -# choice = 'NordpoolReal' -choice = 'Balmorel2024' -# choice = 'BalmorelVREAreas' - -## Cutouts -# path = "Nicolas_2015_full.nc" -path = r"M:\Project\RQII\DK+Surrounding2012.nc" -# cutout_bounds_x = (11.015880, 13.078094) # Longitude -# cutout_bounds_y = (43.239006, 45.207804) # Latitude -# cutout_bounds_x = (11.7, 12.2) # Longitude -# cutout_bounds_y = (43.85, 44.4) # Latitude -cutout_bounds_x = (3, 33) # Longitude -cutout_bounds_y = (47, 73) # Latitude - - -### 0.4 What time to load? -# T = "2011-01-01" -T = '2012' - -### 0.5 Overwrite cutout or load previous? -OverW = False - -### 0.6 Read Geodata -areas = gpd.read_file('Data/Shapefiles/2024_balmorelmapwithoffshoremunidk.gpkg') -areas = areas[(areas.NAME_0 == 'Germany') | (areas.NAME_0 == 'Norway') |\ - (areas.NAME_0 == 'Sweden') | ((areas.Type == 'Offshore') & ((areas.Country == 'DE') |\ - (areas.Country == 'DK') | (areas.Country == 'NO') | (areas.Country == 'SE'))) -] -areas.loc[areas.Type == 'Offshore', 'id'] = areas.loc[areas.Type == 'Offshore', 'Region'] - -# areas.loc[areas.Type != 'Offshore', 'id'] = areas.loc[areas.Type != 'Offshore', 'Region'] -# areas = gpd.read_file('Data/Shapefiles/2024 BalmorelHighResolutionMapWithOffshore.gpkg') -# areas.loc[areas.Type == 'Offshore', 'id'] = areas.loc[areas.Type == 'Offshore', 'Region'] -# areas.index = areas.id -# the_index='id' -# areas = areas[(areas.NAME_0 == 'Denmark') | (areas.NAME_0 == 'Germany') |\ -# (areas.NAME_0 == 'Sweden') | (areas.NAME_0 == 'Norway') |\ - # ((areas.Type == 'Offshore') & (areas.id.str.find('DK') != -1)) |\ - # ((areas.Type == 'Offshore') & (areas.id.str.find('DE') != -1)) |\ - # ((areas.Type == 'Offshore') & (areas.id.str.find('NO') != -1)) |\ - # ((areas.Type == 'Offshore') & (areas.id.str.find('SE') != -1))] - -# MUNI DK with offshore filtering -# areas = areas[(areas.Country == 'DK') & (areas.Type == 'Offshore')] # From BalmorelHighResolution -the_index, areas2, country_code = prepared_geofiles('DKMunicipalities') -areas2['id'] = areas2['NAME_2'] -areas2['muni_id'] = areas2['GID_2'] -areas = pd.concat((areas, areas2[['id', 'muni_id', 'geometry']])) -# areas = areas.loc[['DK1', 'DK2']] # Testing DK and DE -areas.geometry = areas['geometry'] -areas.index = areas.id -the_index='id' -# areas.loc[:,'GID_2'] = areas.GID_2.str.replace('.', '_') - -# Read homemade offshore potentials for DK -OFFWNDPOT = gpd.read_file(r'.\Data\RandomOffWindPot\DK.gpkg') - -# Plot -fig, ax = plt.subplots() -areas.plot(ax=ax) - -# ax.set_xlim(cutout_bounds_x) -# ax.set_ylim(cutout_bounds_y) -ax.set_title(choice) -# OFFWNDPOT.plot(ax=ax) - -# Filtering Italy -# if choice == 'Nordpool': -# areas = areas[areas.index == 'IT_NORD'] - - - -### ASSUMPTIONS -# 3.3 A quick fix for SOLH_VAR_T and SOLHFLH -# 3.4 Capacity is based on RG1 VRE technology - all others regions are set to 0 potential -# 3.4 Offshore wind potential is a hack right now - manual inputted GW - -#%% ------------------------------- ### -### 1. Load Geodata and Pre-process ### -### ------------------------------- ### - -## Projections - -UTM32 = Proj(proj='utm', zone=32, ellps='WGS84', preserve_units=False) -GM = Proj('EPSG:900913', preserve_units=False) -# transformer = Transformer.from_crs('EPSG:900913', 'EPSG:4326') -# out = Transformer(GM, UTM32, (11, 13), (43, 45), always_xy=True) - - - -### 1.2 Visualise current areas -# Set projection -crs = ccrs.UTM(32) -# Make compatible with geopandas -# projection = crs.proj4_init # doesn't work, so actually cartopy is useless - continuing only with geopandas - -# Make figure -fig, ax = plt.subplots(figsize=(10, 10), subplot_kw={"projection": crs}, - dpi=200) - - -# Add areas -ax.add_geometries(areas.geometry, crs = crs, - facecolor=[.9, .9,.9], edgecolor='grey', - linewidth=.2) - -ax.set_xlim(cutout_bounds_x) -ax.set_ylim(cutout_bounds_y) -# ax.set_xlim(7.5,16) -# ax.set_ylim(54.4,58) - - -### 1.3 Load Nordpool regions -# NP = pd.read_csv(project_dir/'geo_files/coordinates_RRR.csv') -# NP = NP.loc[df_unique['Type'] == 'region', ] - -#%% ------------------------------- ### -### 2. Calculate RE Potentials ### -### ------------------------------- ### +@click.commands() +@click.option('--cutout-path', type=str, required=True, help="The path of a cutout .nc file") +@click.option('--weather-year', type=int, required=True, help="The weather year") +@click.option('--overwrite-cutout', type=str, required=False, help="Overwrite an existing cutout?") +def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): + ### 0.1 Capacity pr. km for PV and Wind + cap_per_sqkm_pv = 1.7 # MW/km2 + cap_per_sqkm_wind = 0.67 # MW/km2 According to NREL: 2 MW / 1.5 acres (0.00607028 km2) + + + ### 0.2 Choice of technologies + panel = atlite.solarpanels.CSi # Possible to choose crystalline Si (CSi) or advanced cadmium-tellurium (CdTe) + wind_turbine = atlite.windturbines.Vestas_V66_1750kW + # wind_turbine = atlite.windturbines.Enercon_E82_3000kW + # wind_turbine = atlite.windturbines.Bonus_B1000_1000kW + + # Offshore wind + # wind_turbine = atlite.windturbines.NREL_ReferenceTurbine_5MW_offshore + + ### 0.3 What areas to load? + # choice = 'DK Municipalities' + # choice = 'NUTS1' + # choice = 'NUTS2' + # choice = 'NUTS3' + # choice = 'NordpoolReal' + choice = 'Balmorel2024' + # choice = 'BalmorelVREAreas' + + ## Cutouts + # path = "Nicolas_2015_full.nc" + # cutout_bounds_x = (11.015880, 13.078094) # Longitude + # cutout_bounds_y = (43.239006, 45.207804) # Latitude + # cutout_bounds_x = (11.7, 12.2) # Longitude + # cutout_bounds_y = (43.85, 44.4) # Latitude + cutout_bounds_x = (3, 33) # Longitude + cutout_bounds_y = (47, 73) # Latitude + # DK + Nordsøen + cutout_bounds_x = (2.2, 17.4) # Longitude + cutout_bounds_y = (53.7, 59) # Latitude + + + ### 0.4 What time to load? + # T = "2011-01-01" + + + ### 0.6 Read Geodata + areas = gpd.read_file('Data/Shapefiles/2024_balmorelmapwithoffshoremunidk.gpkg') + areas = areas[(areas.NAME_0 == 'Germany') | (areas.NAME_0 == 'Norway') |\ + (areas.NAME_0 == 'Sweden') | ((areas.Type == 'Offshore') & ((areas.Country == 'DE') |\ + (areas.Country == 'DK') | (areas.Country == 'NO') | (areas.Country == 'SE'))) + ] + areas.loc[areas.Type == 'Offshore', 'id'] = areas.loc[areas.Type == 'Offshore', 'Region'] + + # areas.loc[areas.Type != 'Offshore', 'id'] = areas.loc[areas.Type != 'Offshore', 'Region'] + # areas = gpd.read_file('Data/Shapefiles/2024 BalmorelHighResolutionMapWithOffshore.gpkg') + # areas.loc[areas.Type == 'Offshore', 'id'] = areas.loc[areas.Type == 'Offshore', 'Region'] + # areas.index = areas.id + # the_index='id' + # areas = areas[(areas.NAME_0 == 'Denmark') | (areas.NAME_0 == 'Germany') |\ + # (areas.NAME_0 == 'Sweden') | (areas.NAME_0 == 'Norway') |\ + # ((areas.Type == 'Offshore') & (areas.id.str.find('DK') != -1)) |\ + # ((areas.Type == 'Offshore') & (areas.id.str.find('DE') != -1)) |\ + # ((areas.Type == 'Offshore') & (areas.id.str.find('NO') != -1)) |\ + # ((areas.Type == 'Offshore') & (areas.id.str.find('SE') != -1))] + + # MUNI DK with offshore filtering + # areas = areas[(areas.Country == 'DK') & (areas.Type == 'Offshore')] # From BalmorelHighResolution + the_index, areas2, country_code = prepared_geofiles('DKMunicipalities') + areas2['id'] = areas2['NAME_2'] + areas2['muni_id'] = areas2['GID_2'] + areas = pd.concat((areas, areas2[['id', 'muni_id', 'geometry']])) + # areas = areas.loc[['DK1', 'DK2']] # Testing DK and DE + areas.geometry = areas['geometry'] + areas.index = areas.id + the_index='id' + # areas.loc[:,'GID_2'] = areas.GID_2.str.replace('.', '_') + + # Read homemade offshore potentials for DK + OFFWNDPOT = gpd.read_file(r'.\Data\RandomOffWindPot\DK.gpkg') + + # Plot + fig, ax = plt.subplots() + areas.plot(ax=ax) -### 2.1 Load Cutout -cutout = atlite.Cutout(path=path, - module="era5", - x=slice(cutout_bounds_x[0], cutout_bounds_x[1]), - y=slice(cutout_bounds_y[0], cutout_bounds_y[1]), - time=T - ) -cutout.prepare(overwrite=OverW) + # ax.set_xlim(cutout_bounds_x) + # ax.set_ylim(cutout_bounds_y) + ax.set_title(choice) + # OFFWNDPOT.plot(ax=ax) + # Filtering Italy + # if choice == 'Nordpool': + # areas = areas[areas.index == 'IT_NORD'] -#%% 2.2 Load Map for RE Spatial Availabilities -# CORINE = 'corine.tif' -CORINE = r'Data\CORINE\u2018_clc2018_v2020_20u1_raster100m\u2018_clc2018_v2020_20u1_raster100m\DATA\U2018_CLC2018_V2020_20u1.tif' -excluder = ExclusionContainer() -excluder.add_raster(CORINE, codes=range(20)) -# Convert crs to CORINE map -A = areas.geometry.to_crs(excluder.crs) -A.index = getattr(areas, the_index) + ### ASSUMPTIONS + # 3.3 A quick fix for SOLH_VAR_T and SOLHFLH + # 3.4 Capacity is based on RG1 VRE technology - all others regions are set to 0 potential + # 3.4 Offshore wind potential is a hack right now - manual inputted GW + #%% ------------------------------- ### + ### 1. Load Geodata and Pre-process ### + ### ------------------------------- ### + ## Projections + UTM32 = Proj(proj='utm', zone=32, ellps='WGS84', preserve_units=False) + GM = Proj('EPSG:900913', preserve_units=False) + # transformer = Transformer.from_crs('EPSG:900913', 'EPSG:4326') + # out = Transformer(GM, UTM32, (11, 13), (43, 45), always_xy=True) -### 2.3 Calculate eligible shares -masked, transform = shape_availability(A, excluder) -# eligible_share = masked.sum() * excluder.res**2 / A.loc[[3]].geometry.item().area # Only eligible share for bornholm 3 -# Eligible share of all of A -Aall = gpd.GeoDataFrame({'geometry' : [A.geometry.cascaded_union]}) -eligible_share = masked.sum() * excluder.res**2 / Aall.geometry.item().area # Only eligible share for bornholm 3 + + ### 1.2 Visualise current areas + # Set projection + crs = ccrs.UTM(32) + # Make compatible with geopandas + # projection = crs.proj4_init # doesn't work, so actually cartopy is useless - continuing only with geopandas + # Make figure + fig, ax = plt.subplots(figsize=(10, 10), subplot_kw={"projection": crs}, + dpi=200) -### 2.4 Plot figures of availabilities (green is available land) -# Simple plot -# fig, ax = plt.subplots() -# ax = show(masked, transform=transform, cmap='Greens', ax=ax) -# A.plot(ax=ax, edgecolor='k', color='None') -# ax.set_title(f'Eligible area (green) {eligible_share * 100:2.2f}%') + # Add areas + ax.add_geometries(areas.geometry, crs = crs, + facecolor=[.9, .9,.9], edgecolor='grey', + linewidth=.2) -### Plot that shows the discrete rectangles used -fig, ax = plt.subplots() -ax = show(masked, transform=transform, cmap='Greens', ax=ax) -A.plot(ax=ax, edgecolor='k', color='None') -# cutout.grid.plot(edgecolor='grey', color='None', ax=ax, ls=':') -cutout.grid.to_crs(excluder.crs).plot(edgecolor='grey', color='None', ax=ax, ls=':') -ax.set_title(f'Eligible area (green) {eligible_share * 100:2.2f}%') + ax.set_xlim(cutout_bounds_x) + ax.set_ylim(cutout_bounds_y) + # ax.set_xlim(7.5,16) + # ax.set_ylim(54.4,58) -# ax.set_xlim(4.5e6-0.1e6, 4.5e6+0.1e6) -# ax.set_ylim(2.46-0.1e6, 2.4e6+0.1e6) -### 2.5 Calculate Availability Matrix for all Regions -# Amat.index = ['Denmark'] -A = A.geometry.set_crs(excluder.crs) -Amat = cutout.availabilitymatrix(A, excluder) + ### 1.3 Load Nordpool regions + # NP = pd.read_csv(project_dir/'geo_files/coordinates_RRR.csv') + # NP = NP.loc[df_unique['Type'] == 'region', ] -### Plot first region availability -fig, ax = plt.subplots() -Amat.sel({the_index :A.index[0]}).plot(ax=ax) # Amat gives fractional availabilities in each weather cell -A.plot(ax=ax, edgecolor='k', color='None') -cutout.grid.plot(ax=ax, color='None', edgecolor='grey', ls=':') -# ax.set_xlim(7.5,16) -# ax.set_ylim(54.4,58) + #%% ------------------------------- ### + ### 2. Calculate RE Potentials ### + ### ------------------------------- ### -### Calculate areas in weather cells in sqkm -area = cutout.grid.set_index(['y', 'x']).to_crs(3035).area / 1e6 # 3035 is CRS of CORINE map -area = xr.DataArray(area, dims=('spatial')) + ### 2.1 Load Cutout + cutout = atlite.Cutout(path=cutout_path, + module="era5", + x=slice(cutout_bounds_x[0], cutout_bounds_x[1]), + y=slice(cutout_bounds_y[0], cutout_bounds_y[1]), + time=weather_year + ) + cutout.prepare(overwrite=overwrite_cutout) + #%% 2.2 Load Map for RE Spatial Availabilities + # CORINE = 'corine.tif' + CORINE = r'Data\CORINE\u2018_clc2018_v2020_20u1_raster100m\u2018_clc2018_v2020_20u1_raster100m\DATA\U2018_CLC2018_V2020_20u1.tif' + excluder = ExclusionContainer() + excluder.add_raster(CORINE, codes=range(20)) -### 2.6 Calculate PV Potential -capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_pv # Converts fraction of weather cells to -cutout.prepare() + # Convert crs to CORINE map + A = areas.geometry.to_crs(excluder.crs) + A.index = getattr(areas, the_index) -# Sum of matrix is total potential...? -# Get production -# pv = cutout.pv(matrix=capacity_matrix, panel=panel, -# orientation='latitude_optimal', index=A.index) -pv = cutout.pv(matrix=capacity_matrix, panel=panel, - orientation={'slope': 30, 'azimuth': 180.}, index=A.index) -ax = pv.to_pandas().div(1e3).plot(ylabel='Solar Power [GW]', ls='--', figsize=(15, 4)) -ax.legend(ncol=8, loc='center', bbox_to_anchor=(.5, 1.5)) -# Getting a specific profile -pv.loc[:, getattr(pv, the_index).values[0]] + ### 2.3 Calculate eligible shares + masked, transform = shape_availability(A, excluder) + # eligible_share = masked.sum() * excluder.res**2 / A.loc[[3]].geometry.item().area # Only eligible share for bornholm 3 + # Eligible share of all of A + Aall = gpd.GeoDataFrame({'geometry' : [A.geometry.cascaded_union]}) + eligible_share = masked.sum() * excluder.res**2 / Aall.geometry.item().area # Only eligible share for bornholm 3 -### 2.7 Calculate Wind Turbine Potential -capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_wind -cutout.prepare() -# Get production -wind = cutout.wind(matrix=capacity_matrix, turbine=wind_turbine, - index=A.index) -ax = wind.to_pandas().div(1e3).plot(ylabel='Wind Power [GW]', ls='--', figsize=(15, 4)) -ax.legend(ncol=8, loc='center', bbox_to_anchor=(.5, 1.5)) + ### 2.4 Plot figures of availabilities (green is available land) + # Simple plot + # fig, ax = plt.subplots() + # ax = show(masked, transform=transform, cmap='Greens', ax=ax) + # A.plot(ax=ax, edgecolor='k', color='None') + # ax.set_title(f'Eligible area (green) {eligible_share * 100:2.2f}%') -# Getting a specific profile -wind.loc[:, getattr(wind, the_index).values[0]] + ### Plot that shows the discrete rectangles used + fig, ax = plt.subplots() + ax = show(masked, transform=transform, cmap='Greens', ax=ax) + A.plot(ax=ax, edgecolor='k', color='None') + # cutout.grid.plot(edgecolor='grey', color='None', ax=ax, ls=':') + cutout.grid.to_crs(excluder.crs).plot(edgecolor='grey', color='None', ax=ax, ls=':') + ax.set_title(f'Eligible area (green) {eligible_share * 100:2.2f}%') -#%% -# Plot data (done with mix municipality and 2024 balmorelhighres ) -# a2 = areas[areas.Type != 'Offshore'] -# a2 = areas -# winddata = wind.to_pandas() - -# N_stop = 24 -# n = 0 -# for i,row in winddata.iterrows(): -# if n > N_stop: -# break -# fig, ax = plt.subplots() -# a2['Wind'] = row.values/ winddata.max() -# a2.plot(column='Wind', ax=ax) -# ax.set_title(i) -# ax.axes.axis('off') -# fig.savefig(('Output/Figures/VRE_time/wind_%s.png'%i).replace(':','').replace(' ','-')) -# plt.close(fig) -# n += 1 -# Make gif = C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Documents\Social\Friday Bar\createGIF.py - -#%% ------------------------------- ### -### 3. Create Balmorel Input ### -### ------------------------------- ### + # ax.set_xlim(4.5e6-0.1e6, 4.5e6+0.1e6) + # ax.set_ylim(2.46-0.1e6, 2.4e6+0.1e6) + ### 2.5 Calculate Availability Matrix for all Regions + # Amat.index = ['Denmark'] + A = A.geometry.set_crs(excluder.crs) + Amat = cutout.availabilitymatrix(A, excluder) -### 3.1 Convert data -# .to_pandas() can be used to store profiles from wind or pv -W = wind.to_pandas() -S = pv.to_pandas() - -# Get correct timeseries index for Balmorel -t = W.index.isocalendar() -t['hour'] = t.index.hour - -# Filter away first week, from last year -idx = t.index.year == t['year'] -t = t[idx] -W = W[idx] -S = S[idx] - -# Make seasons -t['S'] = t['week'].astype(str) -idx = t['S'].str.len() == 1 -t.loc[idx, 'S'] = '0' + t.loc[idx, 'S'] -t['S'] = 'S' + t['S'] - -# Make terms -try: - t['T'] = np.array([i for i in range(1, 169)]*52) -except ValueError: - print("\nWARNING!\nYou didn't load 8736 hours of data! Select a bit of the next year, in cutout (T parameter in beginning).") - print("The current profile will be %d too short (%d hours in total)\n"%(8736-len(t), len(t))) - - array = np.array([i for i in range(1, 169)]*52) - t['T'] = array[:len(t)] - -t['T'] = t['T'].astype(str) -idx = t['T'].str.len() == 1 -t.loc[idx, 'T'] = '00' + t['T'] -idx = t['T'].str.len() == 2 -t.loc[idx, 'T'] = '0' + t['T'] -t['T'] = 'T' + t['T'] - - -# Create new index -W.index = t['S'] + ' . ' + t['T'] -S.index = t['S'] + ' . ' + t['T'] - -# Clean up areas -W.columns = W.columns.str.replace('.', '_') -W.columns.name = '' -W.columns = W.columns + '_A' -S.columns = S.columns.str.replace('.', '_') -S.columns.name = '' -S.columns = S.columns + '_A' - -# Clean up values -# W.iloc[:,:] = W.iloc[:,:].astype(str) -# S.iloc[:,:] = S.iloc[:,:].astype(str) - - - - -### 3.2 Variation Profiles -# Format of SOLE_VAR_T and WND_VAR_T -# TABLE WND/SOLE_VAR_T1(SSS,TTT,AAA) "Variation of the wind/solar generation" -# A1 A2 ... -# S01.T001 val val -# ... -# S52.T168 val val -# ; -# - -## Saving directly to .inc files: -# Wind -# f = open('WND_VAR_T.inc', 'w') -with open('./Output/WND_VAR_T.inc', 'w') as f: - f.write('TABLE WND_VAR_T1(SSS,TTT,AAA) "Variation of the wind generation"\n') - # f.write('+') # If adding to another WND_VAR_T - dfAsString = W.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n;') - f.write('\nWND_VAR_T(AAA,SSS,TTT) = WND_VAR_T1(SSS,TTT,AAA);') - f.write('\nWND_VAR_T1(SSS,TTT,AAA) = 0;') - -# Solar -# f = open('SOLE_VAR_T.inc', 'w') -with open('./Output/SOLE_VAR_T.inc', 'w') as f: - f.write('TABLE SOLE_VAR_T1(SSS,TTT,AAA) "Variation of the solar generation"\n') - dfAsString = S.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n;\n') - f.write('SOLE_VAR_T(AAA,SSS,TTT) = SOLE_VAR_T1(SSS,TTT,AAA);\n') - f.write('SOLE_VAR_T1(SSS,TTT,AAA) = 0;\n') - - - - -### 3.3 Full load hours -# Format of SOLEFLH and WNDFLH -# TABLE WND/SOLEFLH(AAA) "Full load hours for wind/solar power" -# / -# A1 val -# A2 val -# ... -# An val -# /; -# - -# Calculating full load hours by sum of normalised timeseries -FLH_W = W.sum() / W.max() * (8736/len(t)) -FLH_S = S.sum() / S.max() * (8736/len(t)) -with open('./Output/SOLEFLH.inc', 'w') as f: - f.write('Parameter SOLEFLH(AAA) "Full load hours for solar power (hours)"\n') - f.write('/') - dfAsString = FLH_S.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n/\n;') - -with open('./Output/WNDFLH.inc', 'w') as f: - f.write('Parameter WNDFLH(AAA) "Full load hours for wind power (hours)"\n') - f.write('/') - dfAsString = FLH_W.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n/\n;') - -# Quick fix for solar heating profiles -FLH_SH = FLH_S / 5 -with open('./Output/SOLHFLH.inc', 'w') as f: - f.write('Parameter SOLHFLH(AAA) "Full load hours for solar heat (hours)"\n') - f.write('/') - dfAsString = FLH_SH.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n/\n;') - - -with open('./Output/SOLH_VAR_T.inc', 'w') as f: - f.write('TABLE SOLH_VAR_T1(SSS,TTT,AAA) "Variation of the solar generation"\n') - dfAsString = S.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n;\n') - f.write('SOLH_VAR_T(AAA,SSS,TTT) = SOLH_VAR_T1(SSS,TTT,AAA);\n') - f.write('SOLH_VAR_T1(SSS,TTT,AAA) = 0;\n') - -### X.X CALCULATE POTENTIALS -exc_points = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmGrid-Urb-GLWD123-WDPA012-MTabove1km.gpkg') -VREareas = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmorelVREAreas.gpkg') -exc_points = exc_points.set_crs(VREareas.crs) # Set CRS -exc_points_bounds = exc_points.bounds -#%% -for i,row in areas.iloc[10:13].iterrows(): # West-germany and DK in NordpoolReal - print(row['RRR']) - + ### Plot first region availability fig, ax = plt.subplots() - geo_ser = VREareas.intersection(row.geometry) - geo_ser_df = gpd.GeoDataFrame({'geometry' : geo_ser.geometry}) - geo_ser.plot(ax=ax) - xlims = ax.get_xlim() - ylims = ax.get_ylim() - - ## Narrow points down - idx = exc_points_bounds.minx > xlims[0] - idx = idx & (exc_points_bounds.maxx < xlims[1]) - idx = idx & (exc_points_bounds.miny > ylims[0]) - idx = idx & (exc_points_bounds.maxy < ylims[1]) - temp_points = exc_points[idx] - - ## Get points inside polygon intersection - temp_points = gpd.sjoin(temp_points, geo_ser_df) - - temp_points.plot(ax=ax, markersize=0.5, color='r') - - ## Get capacity - available_space = temp_points.shape[0] * 30 * 30 # km2 - - print('Potential of PV installation: %0.0f MW'%(cap_per_sqkm_pv*available_space)) - print('Potential of Wind installation: %0.0f MW'%(cap_per_sqkm_wind*available_space)) - + Amat.sel({the_index :A.index[0]}).plot(ax=ax) # Amat gives fractional availabilities in each weather cell + A.plot(ax=ax, edgecolor='k', color='None') + cutout.grid.plot(ax=ax, color='None', edgecolor='grey', ls=':') + # ax.set_xlim(7.5,16) + # ax.set_ylim(54.4,58) + + ### Calculate areas in weather cells in sqkm + area = cutout.grid.set_index(['y', 'x']).to_crs(3035).area / 1e6 # 3035 is CRS of CORINE map + area = xr.DataArray(area, dims=('spatial')) + + + + + ### 2.6 Calculate PV Potential + capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_pv # Converts fraction of weather cells to + cutout.prepare() + + # Sum of matrix is total potential...? + + # Get production + # pv = cutout.pv(matrix=capacity_matrix, panel=panel, + # orientation='latitude_optimal', index=A.index) + pv = cutout.pv(matrix=capacity_matrix, panel=panel, + orientation={'slope': 30, 'azimuth': 180.}, index=A.index) + ax = pv.to_pandas().div(1e3).plot(ylabel='Solar Power [GW]', ls='--', figsize=(15, 4)) + ax.legend(ncol=8, loc='center', bbox_to_anchor=(.5, 1.5)) + + # Getting a specific profile + pv.loc[:, getattr(pv, the_index).values[0]] + + + ### 2.7 Calculate Wind Turbine Potential + capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_wind + cutout.prepare() + + # Get production + wind = cutout.wind(matrix=capacity_matrix, turbine=wind_turbine, + index=A.index) + ax = wind.to_pandas().div(1e3).plot(ylabel='Wind Power [GW]', ls='--', figsize=(15, 4)) + ax.legend(ncol=8, loc='center', bbox_to_anchor=(.5, 1.5)) + + # Getting a specific profile + wind.loc[:, getattr(wind, the_index).values[0]] + + #%% + # Plot data (done with mix municipality and 2024 balmorelhighres ) + # a2 = areas[areas.Type != 'Offshore'] + # a2 = areas + # winddata = wind.to_pandas() + + # N_stop = 24 + # n = 0 + # for i,row in winddata.iterrows(): + # if n > N_stop: + # break + # fig, ax = plt.subplots() + # a2['Wind'] = row.values/ winddata.max() + # a2.plot(column='Wind', ax=ax) + # ax.set_title(i) + # ax.axes.axis('off') + # fig.savefig(('Output/Figures/VRE_time/wind_%s.png'%i).replace(':','').replace(' ','-')) + # plt.close(fig) + # n += 1 + # Make gif = C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Documents\Social\Friday Bar\createGIF.py + + #%% ------------------------------- ### + ### 3. Create Balmorel Input ### + ### ------------------------------- ### + + + ### 3.1 Convert data + # .to_pandas() can be used to store profiles from wind or pv + W = wind.to_pandas() + S = pv.to_pandas() + + # Get correct timeseries index for Balmorel + t = W.index.isocalendar() + t['hour'] = t.index.hour + + # Filter away first week, from last year + idx = t.index.year == t['year'] + t = t[idx] + W = W[idx] + S = S[idx] + + # Make seasons + t['S'] = t['week'].astype(str) + idx = t['S'].str.len() == 1 + t.loc[idx, 'S'] = '0' + t.loc[idx, 'S'] + t['S'] = 'S' + t['S'] + + # Make terms + try: + t['T'] = np.array([i for i in range(1, 169)]*52) + except ValueError: + print("\nWARNING!\nYou didn't load 8736 hours of data! Select a bit of the next year, in cutout (T parameter in beginning).") + print("The current profile will be %d too short (%d hours in total)\n"%(8736-len(t), len(t))) + + array = np.array([i for i in range(1, 169)]*52) + t['T'] = array[:len(t)] + + t['T'] = t['T'].astype(str) + idx = t['T'].str.len() == 1 + t.loc[idx, 'T'] = '00' + t['T'] + idx = t['T'].str.len() == 2 + t.loc[idx, 'T'] = '0' + t['T'] + t['T'] = 'T' + t['T'] - ### OFFSHORE IF STATEMENT! OR AFTERWARDS? - # print(VREareas[VREareas.intersects(row.geometry)].Region) - # l = gpd.GeoSeries(areas.loc[R].geometry).plot(ax=ax) - # ax.set_title(R) - -## Each marker is 30x30 km - - -#%% 3.4 Potentials -# Format of SOLEFLH and WNDFLH -# TABLE WND/SOLEFLH(AAA) "Full load hours for wind/solar power" -# / -# A1 val -# A2 val -# ... -# An val -# /; -# - -# Calculating capacity by maximum power output from atlite series -CAP_W = W.max() -CAP_S = S.max() - -CAP_OFFW = pd.DataFrame({'WINDTURBINE_OFFSHORE.RG1_OFF1' : [0]*len(CAP_W)}, index = CAP_W.index) -### For offshore wind - HACK! Manually created points -for i,row in OFFWNDPOT.iterrows(): - - # Assign point to nearest polygon - dist = {r[0] : r[1].geometry.distance(row.geometry) for r in areas.iterrows()} - - # Get index of closest region - ind = min(dist, key=dist.get) - - # Accumulate capacity to closest region and make new offshore area - # try: - # CAP_OFFW.loc[ind+'_A_OFF'] = CAP_OFFW.loc[ind+'_A_OFF'] + row['Pot_GW'] - # except KeyError: - # CAP_OFFW.loc[ind+'_A_OFF'] = row['Pot_GW'] - # Accumulate capacity to closest region - CAP_OFFW.loc[ind+'_A'] = CAP_OFFW.loc[ind+'_A'] + row['Pot_GW'] - - - # print(ind) - # if choice == 'NUTS3': - # pp.loc[i, 'area'] = areas.loc[ind, 'NUTS_ID'] - # elif choice == 'DK Municipalities': - # pp.loc[i, 'area'] = areas.loc[ind, 'GID_2'] - -# Make format -CAP = pd.DataFrame({'SOLARPV.RG1' : np.hstack((CAP_S.values, np.zeros(len(CAP_OFFW) - len(CAP_S)))), - 'WINDTURBINE_ONSHORE.RG1' : np.hstack((CAP_W.values, np.zeros(len(CAP_OFFW) - len(CAP_S)))), - 'WINDTURBINE_OFFSHORE.RG1_OFF1' : CAP_OFFW.values[:,0]*1e3}, # In MW - index=CAP_OFFW.index) - -with open('./Output/SUBTECHGROUPKPOT.inc', 'w') as f: - f.write("TABLE SUBTECHGROUPKPOT(CCCRRRAAA,TECH_GROUP,SUBTECH_GROUP) 'SubTechnology group capacity restriction by geography (MW)'\n") - dfAsString = CAP.to_string(header=True, index=True) - f.write(dfAsString) - f.write('\n;') - # These lines make sure that the RG2-3 OFF2-5 regions are not available (all assigned to RG1!) - f.write("\nSUBTECHGROUPKPOT(AAA,'SOLARPV',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'SOLARPV',SUBTECH_GROUP) = 0) = EPS;") - f.write("\nSUBTECHGROUPKPOT(AAA,'WINDTURBINE_ONSHORE',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'WINDTURBINE_ONSHORE',SUBTECH_GROUP) = 0) = EPS;") - f.write("\nSUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP) = 0) = EPS;") + # Create new index + W.index = t['S'] + ' . ' + t['T'] + S.index = t['S'] + ' . ' + t['T'] + + # Clean up areas + W.columns = W.columns.str.replace('.', '_') + W.columns.name = '' + W.columns = W.columns + '_A' + S.columns = S.columns.str.replace('.', '_') + S.columns.name = '' + S.columns = S.columns + '_A' + + # Clean up values + # W.iloc[:,:] = W.iloc[:,:].astype(str) + # S.iloc[:,:] = S.iloc[:,:].astype(str) + + + + + ### 3.2 Variation Profiles + # Format of SOLE_VAR_T and WND_VAR_T + # TABLE WND/SOLE_VAR_T1(SSS,TTT,AAA) "Variation of the wind/solar generation" + # A1 A2 ... + # S01.T001 val val + # ... + # S52.T168 val val + # ; + # + + ## Saving directly to .inc files: + # Wind + # f = open('WND_VAR_T.inc', 'w') + with open('./Output/WND_VAR_T.inc', 'w') as f: + f.write('TABLE WND_VAR_T1(SSS,TTT,AAA) "Variation of the wind generation"\n') + # f.write('+') # If adding to another WND_VAR_T + dfAsString = W.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n;') + f.write('\nWND_VAR_T(AAA,SSS,TTT) = WND_VAR_T1(SSS,TTT,AAA);') + f.write('\nWND_VAR_T1(SSS,TTT,AAA) = 0;') + + # Solar + # f = open('SOLE_VAR_T.inc', 'w') + with open('./Output/SOLE_VAR_T.inc', 'w') as f: + f.write('TABLE SOLE_VAR_T1(SSS,TTT,AAA) "Variation of the solar generation"\n') + dfAsString = S.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n;\n') + f.write('SOLE_VAR_T(AAA,SSS,TTT) = SOLE_VAR_T1(SSS,TTT,AAA);\n') + f.write('SOLE_VAR_T1(SSS,TTT,AAA) = 0;\n') + + + + + ### 3.3 Full load hours + # Format of SOLEFLH and WNDFLH + # TABLE WND/SOLEFLH(AAA) "Full load hours for wind/solar power" + # / + # A1 val + # A2 val + # ... + # An val + # /; + # + + # Calculating full load hours by sum of normalised timeseries + FLH_W = W.sum() / W.max() * (8736/len(t)) + FLH_S = S.sum() / S.max() * (8736/len(t)) + with open('./Output/SOLEFLH.inc', 'w') as f: + f.write('Parameter SOLEFLH(AAA) "Full load hours for solar power (hours)"\n') + f.write('/') + dfAsString = FLH_S.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n/\n;') + + with open('./Output/WNDFLH.inc', 'w') as f: + f.write('Parameter WNDFLH(AAA) "Full load hours for wind power (hours)"\n') + f.write('/') + dfAsString = FLH_W.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n/\n;') + + # Quick fix for solar heating profiles + FLH_SH = FLH_S / 5 + with open('./Output/SOLHFLH.inc', 'w') as f: + f.write('Parameter SOLHFLH(AAA) "Full load hours for solar heat (hours)"\n') + f.write('/') + dfAsString = FLH_SH.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n/\n;') + + + with open('./Output/SOLH_VAR_T.inc', 'w') as f: + f.write('TABLE SOLH_VAR_T1(SSS,TTT,AAA) "Variation of the solar generation"\n') + dfAsString = S.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n;\n') + f.write('SOLH_VAR_T(AAA,SSS,TTT) = SOLH_VAR_T1(SSS,TTT,AAA);\n') + f.write('SOLH_VAR_T1(SSS,TTT,AAA) = 0;\n') + + ### X.X CALCULATE POTENTIALS + exc_points = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmGrid-Urb-GLWD123-WDPA012-MTabove1km.gpkg') + VREareas = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmorelVREAreas.gpkg') + exc_points = exc_points.set_crs(VREareas.crs) # Set CRS + exc_points_bounds = exc_points.bounds + #%% + for i,row in areas.iloc[10:13].iterrows(): # West-germany and DK in NordpoolReal + print(row['RRR']) + + fig, ax = plt.subplots() + geo_ser = VREareas.intersection(row.geometry) + geo_ser_df = gpd.GeoDataFrame({'geometry' : geo_ser.geometry}) + geo_ser.plot(ax=ax) + xlims = ax.get_xlim() + ylims = ax.get_ylim() + + ## Narrow points down + idx = exc_points_bounds.minx > xlims[0] + idx = idx & (exc_points_bounds.maxx < xlims[1]) + idx = idx & (exc_points_bounds.miny > ylims[0]) + idx = idx & (exc_points_bounds.maxy < ylims[1]) + temp_points = exc_points[idx] + + ## Get points inside polygon intersection + temp_points = gpd.sjoin(temp_points, geo_ser_df) + + temp_points.plot(ax=ax, markersize=0.5, color='r') + + ## Get capacity + available_space = temp_points.shape[0] * 30 * 30 # km2 + + print('Potential of PV installation: %0.0f MW'%(cap_per_sqkm_pv*available_space)) + print('Potential of Wind installation: %0.0f MW'%(cap_per_sqkm_wind*available_space)) + + + ### OFFSHORE IF STATEMENT! OR AFTERWARDS? - - #%% ------------------------------- ### -### 4. Analysis ### -### ------------------------------- ### + # print(VREareas[VREareas.intersects(row.geometry)].Region) + # l = gpd.GeoSeries(areas.loc[R].geometry).plot(ax=ax) + # ax.set_title(R) + + ## Each marker is 30x30 km + + + #%% 3.4 Potentials + # Format of SOLEFLH and WNDFLH + # TABLE WND/SOLEFLH(AAA) "Full load hours for wind/solar power" + # / + # A1 val + # A2 val + # ... + # An val + # /; + # + + # Calculating capacity by maximum power output from atlite series + CAP_W = W.max() + CAP_S = S.max() + + CAP_OFFW = pd.DataFrame({'WINDTURBINE_OFFSHORE.RG1_OFF1' : [0]*len(CAP_W)}, index = CAP_W.index) + ### For offshore wind - HACK! Manually created points + for i,row in OFFWNDPOT.iterrows(): + + # Assign point to nearest polygon + dist = {r[0] : r[1].geometry.distance(row.geometry) for r in areas.iterrows()} + + # Get index of closest region + ind = min(dist, key=dist.get) + + # Accumulate capacity to closest region and make new offshore area + # try: + # CAP_OFFW.loc[ind+'_A_OFF'] = CAP_OFFW.loc[ind+'_A_OFF'] + row['Pot_GW'] + # except KeyError: + # CAP_OFFW.loc[ind+'_A_OFF'] = row['Pot_GW'] + # Accumulate capacity to closest region + CAP_OFFW.loc[ind+'_A'] = CAP_OFFW.loc[ind+'_A'] + row['Pot_GW'] + + + # print(ind) + # if choice == 'NUTS3': + # pp.loc[i, 'area'] = areas.loc[ind, 'NUTS_ID'] + # elif choice == 'DK Municipalities': + # pp.loc[i, 'area'] = areas.loc[ind, 'GID_2'] + + # Make format + CAP = pd.DataFrame({'SOLARPV.RG1' : np.hstack((CAP_S.values, np.zeros(len(CAP_OFFW) - len(CAP_S)))), + 'WINDTURBINE_ONSHORE.RG1' : np.hstack((CAP_W.values, np.zeros(len(CAP_OFFW) - len(CAP_S)))), + 'WINDTURBINE_OFFSHORE.RG1_OFF1' : CAP_OFFW.values[:,0]*1e3}, # In MW + index=CAP_OFFW.index) + + with open('./Output/SUBTECHGROUPKPOT.inc', 'w') as f: + f.write("TABLE SUBTECHGROUPKPOT(CCCRRRAAA,TECH_GROUP,SUBTECH_GROUP) 'SubTechnology group capacity restriction by geography (MW)'\n") + dfAsString = CAP.to_string(header=True, index=True) + f.write(dfAsString) + f.write('\n;') + # These lines make sure that the RG2-3 OFF2-5 regions are not available (all assigned to RG1!) + f.write("\nSUBTECHGROUPKPOT(AAA,'SOLARPV',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'SOLARPV',SUBTECH_GROUP) = 0) = EPS;") + f.write("\nSUBTECHGROUPKPOT(AAA,'WINDTURBINE_ONSHORE',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'WINDTURBINE_ONSHORE',SUBTECH_GROUP) = 0) = EPS;") + f.write("\nSUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP) = 0) = EPS;") + + + #%% ------------------------------- ### + ### 4. Analysis ### + ### ------------------------------- ### -### 4.1 Look at representative periods -# The weeks to compare to full resolution -# per = ['S01', 'S14', 'S27', 'S40'] -# # per = ['S27']I + ### 4.1 Look at representative periods + # The weeks to compare to full resolution + # per = ['S01', 'S14', 'S27', 'S40'] + # # per = ['S27']I -# idx = W.index.str.find(per[0]) != -1 -# for s in per[1:]: -# idx = idx | (W.index.str.find(s) != -1) + # idx = W.index.str.find(per[0]) != -1 + # for s in per[1:]: + # idx = idx | (W.index.str.find(s) != -1) -# for i in range(9): -# doLDC(W, W.columns[i*11:(i+1)*11], idx, 3, 4, title='Wind LDC [%]') -# doLDC(S, S.columns[i*11:(i+1)*11], idx, 3, 4, title='Solar LDC [%]') + # for i in range(9): + # doLDC(W, W.columns[i*11:(i+1)*11], idx, 3, 4, title='Wind LDC [%]') + # doLDC(S, S.columns[i*11:(i+1)*11], idx, 3, 4, title='Solar LDC [%]') +if __name__ == '__main__': + main() \ No newline at end of file From cc89231c413b91f997df58c437b3f90aa5578d9b Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 13:29:15 +0200 Subject: [PATCH 12/19] Adjusting bounds --- src/Modules/vre_profiles.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Modules/vre_profiles.py b/src/Modules/vre_profiles.py index 2fab6cf..d23c06e 100644 --- a/src/Modules/vre_profiles.py +++ b/src/Modules/vre_profiles.py @@ -118,8 +118,8 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): cutout_bounds_x = (3, 33) # Longitude cutout_bounds_y = (47, 73) # Latitude # DK + Nordsøen - cutout_bounds_x = (2.2, 17.4) # Longitude - cutout_bounds_y = (53.7, 59) # Latitude + cutout_bounds_x = (6.37, 17) # Longitude + cutout_bounds_y = (53.7, 58.5) # Latitude ### 0.4 What time to load? @@ -158,6 +158,8 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): the_index='id' # areas.loc[:,'GID_2'] = areas.GID_2.str.replace('.', '_') + areas = gpd.read_file('Data/Shapefiles/Offshore/OffshoreRegions.gpkg') + # Read homemade offshore potentials for DK OFFWNDPOT = gpd.read_file(r'.\Data\RandomOffWindPot\DK.gpkg') From 58044b620606a7230b0a70468b4499b54b891993 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 13:36:19 +0200 Subject: [PATCH 13/19] Updated requirements and stores vre profiles to .nc --- environment.yaml | 2 +- src/Modules/vre_profiles.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/environment.yaml b/environment.yaml index 068ed66..e2c3941 100644 --- a/environment.yaml +++ b/environment.yaml @@ -26,4 +26,4 @@ dependencies: - pip - pip: - gamsapi[transfer]==45.7.0 - - pybalmorel==0.3.10 \ No newline at end of file + - pybalmorel==0.3.11 \ No newline at end of file diff --git a/src/Modules/vre_profiles.py b/src/Modules/vre_profiles.py index d23c06e..7d20d59 100644 --- a/src/Modules/vre_profiles.py +++ b/src/Modules/vre_profiles.py @@ -315,6 +315,8 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # Getting a specific profile pv.loc[:, getattr(pv, the_index).values[0]] + # Save profile + pv.to_netcdf('pv_%s'%cutout_path) ### 2.7 Calculate Wind Turbine Potential capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_wind @@ -329,6 +331,9 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # Getting a specific profile wind.loc[:, getattr(wind, the_index).values[0]] + # Save profile + wind.to_netcdf('wind_%s'%cutout_path) + #%% # Plot data (done with mix municipality and 2024 balmorelhighres ) # a2 = areas[areas.Type != 'Offshore'] From b40832df0cd87c8bea33a63d33340a0ff0ead640 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 14:35:16 +0200 Subject: [PATCH 14/19] Making paths work on HPC --- .gitignore | 1 + src/Modules/geofiles.py | 18 ++++++++-------- src/Modules/vre_profiles.py | 42 ++++++++++++++++++------------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 03b6004..7857155 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +Logs src/Data/* src/Output/* !src/Data/IncFilePreSuf/README.md diff --git a/src/Modules/geofiles.py b/src/Modules/geofiles.py index 58c36df..7b3b6c3 100644 --- a/src/Modules/geofiles.py +++ b/src/Modules/geofiles.py @@ -75,7 +75,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData if choice.replace(' ','').lower() == 'dkmunicipalities': # Filter away unnescescary columns # areas = areas[['NAME_1', 'NAME_2', 'geometry']] - areas = gpd.read_file(r'.\Data\Shapefiles\Denmark\Adm\gadm36_DNK_2.shp') + areas = gpd.read_file('Data/Shapefiles/Denmark/Adm/gadm36_DNK_2.shp') # # Aggregate hovedstaden - MODIFY TO USE NUTS3 AREAS FOR CAPITAL REGION # idx = (areas.NAME_1 == 'Hovedstaden') & (areas.NAME_2 != 'Bornholm') & (areas.NAME_2 != 'Christiansø') # hovedstaden = MultiPolygon(areas[idx].geometry.cascaded_union) @@ -101,7 +101,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData if choice.replace(' ','').lower() == 'dkmunicipalities_names': # Filter away unnescescary columns # areas = areas[['NAME_1', 'NAME_2', 'geometry']] - areas = gpd.read_file(r'.\Data\Shapefiles\Denmark\Adm\gadm36_DNK_2.shp') + areas = gpd.read_file('Data/Shapefiles/Denmark/Adm/gadm36_DNK_2.shp') # # Aggregate hovedstaden - MODIFY TO USE NUTS3 AREAS FOR CAPITAL REGION # idx = (areas.NAME_1 == 'Hovedstaden') & (areas.NAME_2 != 'Bornholm') & (areas.NAME_2 != 'Christiansø') # hovedstaden = MultiPolygon(areas[idx].geometry.cascaded_union) @@ -137,7 +137,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData # NUTS3 (Also contains NUTS2, and NUTS1) elif choice.replace(' ','').lower() == 'nuts1': - areas = gpd.read_file(r'.\Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') + areas = gpd.read_file(r'Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') areas = areas[(areas.LEVL_CODE == 1)] # The index for next file @@ -147,7 +147,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData # areas = areas[areas.NUTS_ID.str.find('DK') != -1] elif choice.replace(' ','').lower() == 'nuts2': - areas = gpd.read_file(r'.\Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') + areas = gpd.read_file(r'Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') areas = areas[(areas.LEVL_CODE == 2)] # The index for next file @@ -157,7 +157,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData # areas = areas[areas.NUTS_ID.str.find('DK') != -1] elif choice.replace(' ','').lower() == 'nuts3': - areas = gpd.read_file(r'.\Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') + areas = gpd.read_file(r'Data\Shapefiles\NUTS_RG_01M_2021_4326\NUTS_RG_01M_2021_4326.shp') areas = areas[(areas.LEVL_CODE == 3)] # The index for next file @@ -168,7 +168,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData # Nordpool market regions elif choice.replace(' ','').lower() == 'nordpool': - p = r'.\Data\Shapefiles\NordpoolRegions\geojson' + p = r'Data\Shapefiles\NordpoolRegions\geojson' areas = gpd.GeoDataFrame() for file in os.listdir(p): @@ -179,7 +179,7 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData # areas = areas[(areas.zoneName == 'DK_1') | (areas.zoneName == 'DK_2')] elif choice.replace(' ','').lower() == 'nordpoolreal': - p = r'.\Data\Shapefiles\NordpoolRegions\geojson_real' + p = r'Data\Shapefiles\NordpoolRegions\geojson_real' i = 0 areas = gpd.GeoDataFrame({'RRR' : []}) @@ -205,11 +205,11 @@ def prepared_geofiles(choice: str, plot: bool = False) -> tuple[str, gpd.GeoData areas = areas[~areas.id.isnull()] elif choice.replace(' ','').lower() == 'balmorelvreareas': - areas = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmorelVREAreas.gpkg') + areas = gpd.read_file(r'Data\Shapefiles\BalmorelVRE\BalmorelVREAreas.gpkg') area_names = 'Region' country_code = 'Country' elif choice.replace(' ','').lower() == 'antbalm': - areas = gpd.read_file(r'.\Data\Shapefiles\240112 AntBalmMap.gpkg') + areas = gpd.read_file(r'Data\Shapefiles\240112 AntBalmMap.gpkg') areas.loc[(areas.ISO_A3 == 'FIN'), 'id'] = 'FIN' areas.loc[(areas.ISO_A3 == 'DZA'), 'id'] = 'DZA' areas.loc[(areas.ISO_A3 == 'EGY'), 'id'] = 'EGY' diff --git a/src/Modules/vre_profiles.py b/src/Modules/vre_profiles.py index 7d20d59..343d30a 100644 --- a/src/Modules/vre_profiles.py +++ b/src/Modules/vre_profiles.py @@ -26,7 +26,7 @@ from rasterio.plot import show import xarray as xr from atlite.gis import shape_availability, ExclusionContainer -from Modules.geofiles import prepared_geofiles +from geofiles import prepared_geofiles import logging import click logging.basicConfig(level=logging.INFO) @@ -77,11 +77,11 @@ def doLDC(file, cols, idx, r, c, title=''): return fig, axes -#%% ------------------------------- ### +### ------------------------------- ### ### 0. Assumptions ### ### ------------------------------- ### -@click.commands() +@click.command() @click.option('--cutout-path', type=str, required=True, help="The path of a cutout .nc file") @click.option('--weather-year', type=int, required=True, help="The weather year") @click.option('--overwrite-cutout', type=str, required=False, help="Overwrite an existing cutout?") @@ -123,7 +123,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): ### 0.4 What time to load? - # T = "2011-01-01" + T = "2011-01-01" ### 0.6 Read Geodata @@ -148,7 +148,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # MUNI DK with offshore filtering # areas = areas[(areas.Country == 'DK') & (areas.Type == 'Offshore')] # From BalmorelHighResolution - the_index, areas2, country_code = prepared_geofiles('DKMunicipalities') + the_index, areas2, country_code = prepared_geofiles('DKMunicipalities_names') areas2['id'] = areas2['NAME_2'] areas2['muni_id'] = areas2['GID_2'] areas = pd.concat((areas, areas2[['id', 'muni_id', 'geometry']])) @@ -161,7 +161,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): areas = gpd.read_file('Data/Shapefiles/Offshore/OffshoreRegions.gpkg') # Read homemade offshore potentials for DK - OFFWNDPOT = gpd.read_file(r'.\Data\RandomOffWindPot\DK.gpkg') + OFFWNDPOT = gpd.read_file('Data/RandomOffWindPot/DK.gpkg') # Plot fig, ax = plt.subplots() @@ -183,7 +183,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # 3.4 Capacity is based on RG1 VRE technology - all others regions are set to 0 potential # 3.4 Offshore wind potential is a hack right now - manual inputted GW - #%% ------------------------------- ### + ### ------------------------------- ### ### 1. Load Geodata and Pre-process ### ### ------------------------------- ### @@ -222,7 +222,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # NP = pd.read_csv(project_dir/'geo_files/coordinates_RRR.csv') # NP = NP.loc[df_unique['Type'] == 'region', ] - #%% ------------------------------- ### + ### ------------------------------- ### ### 2. Calculate RE Potentials ### ### ------------------------------- ### @@ -231,15 +231,15 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): module="era5", x=slice(cutout_bounds_x[0], cutout_bounds_x[1]), y=slice(cutout_bounds_y[0], cutout_bounds_y[1]), - time=weather_year + time=str(weather_year) ) cutout.prepare(overwrite=overwrite_cutout) - #%% 2.2 Load Map for RE Spatial Availabilities + ### 2.2 Load Map for RE Spatial Availabilities # CORINE = 'corine.tif' - CORINE = r'Data\CORINE\u2018_clc2018_v2020_20u1_raster100m\u2018_clc2018_v2020_20u1_raster100m\DATA\U2018_CLC2018_V2020_20u1.tif' + CORINE = 'Data/CORINE/u2018_clc2018_v2020_20u1_raster100m/u2018_clc2018_v2020_20u1_raster100m/DATA/U2018_CLC2018_V2020_20u1.tif' excluder = ExclusionContainer() excluder.add_raster(CORINE, codes=range(20)) @@ -316,7 +316,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): pv.loc[:, getattr(pv, the_index).values[0]] # Save profile - pv.to_netcdf('pv_%s'%cutout_path) + pv.to_netcdf('pv_%s'%cutout_path.split('/')[-1]) ### 2.7 Calculate Wind Turbine Potential capacity_matrix = Amat.stack(spatial=['y', 'x']) * area * cap_per_sqkm_wind @@ -332,9 +332,9 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): wind.loc[:, getattr(wind, the_index).values[0]] # Save profile - wind.to_netcdf('wind_%s'%cutout_path) + wind.to_netcdf('wind_%s'%cutout_path.split('/')[-1]) - #%% + ### # Plot data (done with mix municipality and 2024 balmorelhighres ) # a2 = areas[areas.Type != 'Offshore'] # a2 = areas @@ -353,9 +353,9 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): # fig.savefig(('Output/Figures/VRE_time/wind_%s.png'%i).replace(':','').replace(' ','-')) # plt.close(fig) # n += 1 - # Make gif = C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Documents\Social\Friday Bar\createGIF.py + # Make gif = C:/Users/mberos/Danmarks Tekniske Universitet/PhD in Transmission and Sector Coupling - Dokumenter/Documents/Social/Friday Bar/createGIF.py - #%% ------------------------------- ### + ### ------------------------------- ### ### 3. Create Balmorel Input ### ### ------------------------------- ### @@ -500,11 +500,11 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): f.write('SOLH_VAR_T1(SSS,TTT,AAA) = 0;\n') ### X.X CALCULATE POTENTIALS - exc_points = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmGrid-Urb-GLWD123-WDPA012-MTabove1km.gpkg') - VREareas = gpd.read_file(r'.\Data\Shapefiles\BalmorelVRE\BalmorelVREAreas.gpkg') + exc_points = gpd.read_file('Data/Shapefiles/BalmorelVRE/BalmGrid-Urb-GLWD123-WDPA012-MTabove1km.gpkg') + VREareas = gpd.read_file('Data/Shapefiles/BalmorelVRE/BalmorelVREAreas.gpkg') exc_points = exc_points.set_crs(VREareas.crs) # Set CRS exc_points_bounds = exc_points.bounds - #%% + for i,row in areas.iloc[10:13].iterrows(): # West-germany and DK in NordpoolReal print(row['RRR']) @@ -543,7 +543,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): ## Each marker is 30x30 km - #%% 3.4 Potentials + # 3.4 Potentials # Format of SOLEFLH and WNDFLH # TABLE WND/SOLEFLH(AAA) "Full load hours for wind/solar power" # / @@ -600,7 +600,7 @@ def main(cutout_path: str, weather_year: int, overwrite_cutout: bool = False): f.write("\nSUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP)$(SUBTECHGROUPKPOT(AAA,'WINDTURBINE_OFFSHORE',SUBTECH_GROUP) = 0) = EPS;") - #%% ------------------------------- ### + ### ------------------------------- ### ### 4. Analysis ### ### ------------------------------- ### From 85d047918efd70d989cdd296734df569fe5d2aec Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 18:26:11 +0200 Subject: [PATCH 15/19] Finished clustering in the pure coding format --- src/Modules/clustering.py | 233 ++++++++++++++++++---------- src/Modules/format_balmorel_data.py | 8 +- src/assumptions.yaml | 2 +- src/clustering | 8 +- src/clustering.yaml | 9 ++ 5 files changed, 167 insertions(+), 93 deletions(-) create mode 100644 src/clustering.yaml diff --git a/src/Modules/clustering.py b/src/Modules/clustering.py index 223e1b7..652bd79 100644 --- a/src/Modules/clustering.py +++ b/src/Modules/clustering.py @@ -14,10 +14,14 @@ import matplotlib.pyplot as plt import matplotlib.colors as mcol from pybalmorel import Balmorel +from pybalmorel.utils import symbol_to_df +from Submodules.utils import convert_names import click import pandas as pd import numpy as np import xarray as xr +import gams +import geopandas as gpd from geofiles import prepared_geofiles from scipy.sparse import csr_matrix from Submodules.municipal_template import DataContainer @@ -155,117 +159,179 @@ def collect_clusterdata(plot_cf: bool = False): return con -def cluster(con: DataContainer, - data_list: list, +def apply_filters(df: pd.DataFrame, value_name: str, aggfunc: str = 'sum'): + if 'A' in df.columns: + df['R'] = df.A.str.split('_', expand=True)[0] + + df = df.pivot_table(index='R', values='Value', aggfunc=aggfunc) + df.columns = [value_name] + + return df.query('R != "Nordsoeen"') + +columns = {'DE' : ['Y', 'R', 'DEUSER', 'Value'], + 'DH' : ['Y', 'A', 'DHUSER', 'Value'], + 'WNDFLH' : ['A', 'Value'], + 'SOLEFLH' : ['A', 'Value']} + +def gather_data(db: gams.GamsDatabase, + cluster_params: list, + aggfuncs: list): + + for i in range(len(cluster_params)): + try: + df = symbol_to_df(db, cluster_params[i], columns[cluster_params[i]]) + except KeyError: + print('Column names not found for %s'%cluster_params[i]) + df = symbol_to_df(db, cluster_params[i]) + + if i == 0: + collected_data = apply_filters(df, cluster_params[i], aggfunc=aggfuncs[i]) + continue + + collected_data = collected_data.join( + apply_filters(df, cluster_params[i], aggfunc=aggfuncs[i]), + how='outer' + ) + + return collected_data.to_xarray().rename({'R':'IRRRE'}) + +def cluster(collected_data: pd.DataFrame, n_clusters: int, use_connectivity: bool = True, manual_corrections: list = [ - ['Bornholm', 'Christiansø', 1], - ['Bornholm', 'Dragør', 1], - ['Esbjerg', 'Fanø', 1], - ['Rødovre', 'Frederiksberg', 1], + ['Bornholm', 'Christiansoe', 1], + ['Bornholm', 'Dragoer', 1], + ['Esbjerg', 'Fanoe', 1], + ['Roedovre', 'Frederiksberg', 1], ['Slagelse', 'Nyborg', 0], - ['Samsø', 'Kalundborg', 0] + ['Samsoe', 'Kalundborg', 0] ], + linkage: str = 'Ward', connection_remark: str = 'connec. included + artifical', data_remark: str = 'all combined + xy coords'): - - # Stack data for clustering - X = np.vstack([eval(data_evaluation) for data_evaluation in data_list]).T - - # K-Means Clustering - # est = KMeans(n_clusters=n_clusters) - # est.fit(X) - - # Agglomorative clustering - linkage = 'Ward' - - X = StandardScaler().fit_transform(X) # Normalise dataset - ## Make higher weighting of coordinates..? - # X[:,0] = X[:,0]*10000 - # X[:,1] = X[:,1]*10000 + # collected_data = collected_data.drop_sel(IRRRE='Christiansoe') # Connectivity if use_connectivity: ## Use connectivity from Balmorel (Submodules/get_grid.py) - connectivity = xr.load_dataset(r'Data\Power Grid\municipal_connectivity.nc') + connectivity = xr.load_dataset('Data/BalmorelData/municipal_connectivity.nc') + connectivity_old, connectivity = convert_names('Modules/Submodules/exo_grid_conversion_dictionaries.pkl', connectivity, 'connection') ## Manual Corrections for manual_connection in manual_corrections: connectivity.connection.loc[manual_connection[0], manual_connection[1]] = manual_connection[2] connectivity.connection.loc[manual_connection[1], manual_connection[0]] = manual_connection[2] - knn_graph = connectivity.connection.data # get numpy array + + ## Make symmetric index, so the indices fit + collected_data = collected_data.assign_coords(IRRRI=collected_data.coords['IRRRE'].data) + + ## Combine with data + # connectivity = connectivity.drop_sel(IRRRE='Christiansoe', IRRRI='Christiansoe') + X = collected_data.merge(connectivity) + + ## Make symmetric connectivity graph + knn_graph = X.connection.data # get numpy array knn_graph = csr_matrix(knn_graph) # make dense format + + ## Drop the connection variable again + X = X.drop_vars('connection') else: knn_graph = None # don't apply connectivity constraints + X = collected_data + # Prepare data for clustering + Y = np.vstack([X.get(variable).data for variable in X.data_vars]).T + Y = np.nan_to_num(Y) + Y = StandardScaler().fit_transform(Y) # Normalise dataset + + ## Make higher weighting of certain coordinates..? + # X[:,0] = X[:,0]*10000 + # X[:,1] = X[:,1]*10000 + # Perform Clustering agg = AgglomerativeClustering(n_clusters=n_clusters, linkage=linkage.lower(), connectivity=knn_graph) - agg.fit(X) + agg.fit(Y) + # Merge labels to xarray + X['cluster_groups'] = (['IRRRE'], agg.labels_) # Plot the different clustering techniques - clustering = con.get_polygons() - for name, labelling in [(linkage, agg.labels_)]: - - # Set labels - clustering['cluster_group'] = labelling + ## Get geofiles + the_index, geofiles, c = prepared_geofiles('DKmunicipalities_names') + geofiles.index.name = 'IRRRE' + X = X.merge(geofiles['geometry'].to_xarray()) + - # Plot clustering - fig, ax = plt.subplots() - clustering.plot(column='cluster_group', - ax=ax, - # legend=True, - cmap=truncate_colormap(cmap, 0.2, 1)) - - if knn_graph == None: - connection_remark = 'no connectivity' - - plot_title = '%s, %d clusters, %s\ndata: %s'%(name, - n_clusters, - connection_remark, - data_remark) - ax.set_title(plot_title) - # ax.set_title('%d clusters, %s linkage, %s'%(n_clusters, name, connection_remark)) - - ax.axes.axis('off') - plt.show() - - # fig.savefig(r'C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Deliverables\Spatial Resolution\Investigations\240912 - Initial Clustering Method Tests'+'/'+plot_title.replace('data: ', '_').replace('\n', '').replace(' clusters', 'N').replace(' ', '_').replace(',', '') + '.png', - # transparent=True, - # bbox_inches='tight') - - ### Label municipalities - # clustering.reset_index().apply(lambda x: ax.annotate(text=x['municipality'], xy=(x.geometry.centroid.x, x.geometry.centroid.y), ha='center'), axis=1) - - ### Look at specific coordinates - ## København region - Frederiksberg have 0 in both wind and solar cf, which may drive the clustering weirdly - # ax.set_xlim([12.3, 12.8]) - # ax.set_ylim([55.5, 55.8]) - ## Nordjylland region - Nær Læsø - # ax.set_xlim([10, 11.3]) - # ax.set_ylim([57.0, 57.5]) - ## Vestjylland region - Nær Fanø - # ax.set_xlim([8.2, 9]) - # ax.set_ylim([55.2, 55.7]) - ## Storebælt - # ax.set_xlim([10.3, 11.6]) - # ax.set_ylim([55.0, 55.6]) - ## Samsø - # ax.set_xlim([10, 11.5]) - # ax.set_ylim([55.4, 56.1]) + # Plot clustering + fig, ax = plt.subplots() + + clustering = gpd.GeoDataFrame({'cluster_group' : X.cluster_groups.data}, + geometry=X.geometry.data) + + clustering.plot(ax=ax, column='cluster_group', + cmap=truncate_colormap(cmap, 0.2, 1)) + + if knn_graph == None: + connection_remark = 'no connectivity' + + plot_title = '%s, %d clusters, %s\ndata: %s'%(linkage, + n_clusters, + connection_remark, + data_remark) + ax.set_title(plot_title) + # ax.set_title('%d clusters, %s linkage, %s'%(n_clusters, name, connection_remark)) + + ax.axes.axis('off') + plt.show() + + # fig.savefig(r'C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Deliverables\Spatial Resolution\Investigations\240912 - Initial Clustering Method Tests'+'/'+plot_title.replace('data: ', '_').replace('\n', '').replace(' clusters', 'N').replace(' ', '_').replace(',', '') + '.png', + # transparent=True, + # bbox_inches='tight') + + ### Label municipalities + # clustering.reset_index().apply(lambda x: ax.annotate(text=x['municipality'], xy=(x.geometry.centroid.x, x.geometry.centroid.y), ha='center'), axis=1) + + ### Look at specific coordinates + ## København region - Frederiksberg have 0 in both wind and solar cf, which may drive the clustering weirdly + # ax.set_xlim([12.3, 12.8]) + # ax.set_ylim([55.5, 55.8]) + ## Nordjylland region - Nær Læsø + # ax.set_xlim([10, 11.3]) + # ax.set_ylim([57.0, 57.5]) + ## Vestjylland region - Nær Fanø + # ax.set_xlim([8.2, 9]) + # ax.set_ylim([55.2, 55.7]) + ## Storebælt + # ax.set_xlim([10.3, 11.6]) + # ax.set_ylim([55.0, 55.6]) + ## Samsø + # ax.set_xlim([10, 11.5]) + # ax.set_ylim([55.4, 56.1]) return fig, ax, clustering @click.command() @click.option('--model-path', type=str, required=True, help='Balmorel model path') @click.option('--scenario', type=str, required=True, help='Balmorel scenario') +@click.option('--cluster-params', type=str, required=True, help='Comma-separated list of Balmorel input data to cluster (use the symbol names, e.g. DE for annual electricity demand)') +@click.option('--aggregation-functions', type=str, required=True, help='Comma-separated list of aggregation functions used for clustering (E.g. sum for annual electricity demand and mean for wind full-load hours)') +@click.option('--cluster-size', type=int, required=True, help='How many clusters?') @click.option('--plot-style', type=str, required=False, help='Style of the plot. Options are "report" (bright background) or "ppt" (dark background)') -def main(model_path: str, scenario: str, plot_style: str = 'report'): - +def main(model_path: str, + scenario: str, + cluster_params: str, + aggregation_functions: str, + cluster_size: int, + plot_style: str = 'report'): + + # Convert comma-separated string to list + cluster_params_list = cluster_params.replace(' ', '').split(',') + aggfuncs = aggregation_functions.replace(' ', '').split(',') + + # Plot styles if plot_style == 'report': plt.style.use('default') fc = 'white' @@ -277,17 +343,12 @@ def main(model_path: str, scenario: str, plot_style: str = 'report'): model = Balmorel(model_path) model.load_incfiles(scenario) - # Old collection and clustering - # collected = collect_clusterdata() - # fig, ax, clustering = cluster(collected, [ - # "collected.muni.coords['lat'].data", - # "collected.muni.coords['lon'].data", - # "collected.muni.electricity_demand_mwh.sum(dim=['year', 'user'])", - # "collected.muni.heat_demand_mwh.sum(dim=['year', 'user'])", - # 'collected.muni.wind_cf.data', - # 'collected.muni.solar_cf.data' - # ], - # 6) + # Get parameters for clustering + collected = gather_data(model.input_data[scenario], + cluster_params_list, aggfuncs) + + # Do clustering + fig, ax, clustering = cluster(collected, cluster_size) if __name__ == '__main__': diff --git a/src/Modules/format_balmorel_data.py b/src/Modules/format_balmorel_data.py index ed90be5..d0bb0fc 100644 --- a/src/Modules/format_balmorel_data.py +++ b/src/Modules/format_balmorel_data.py @@ -45,16 +45,16 @@ def store_balmorel_input(symbol: str, f = pd.read_parquet('Data/BalmorelData/%s.gzip'%symbol) else: # Check Balmorel input has been loaded - if not('muni_input_data.gdx' in os.listdir(os.path.join(balm.path, 'muni', 'model'))) or load_again == True: + if not('%s_input_data.gdx'%scenario in os.listdir(os.path.join(balm.path, scenario, 'model'))) or load_again == True: print('\nLoading results into %s_input_data.gdx...\n'%scenario) balm.load_incfiles(scenario) else: print('\n%s_input_data.gdx already loaded!'%scenario) - print('Loading %s_input_data.gdx...\n'%(os.path.join(balm.path, 'muni', 'model', scenario))) + print('Loading %s_input_data.gdx...\n'%(os.path.join(balm.path, scenario, 'model', scenario))) # Load the input ws = GamsWorkspace() - balm.input_data[scenario] = ws.add_database_from_gdx(os.path.join(balm.path, 'muni', 'model', '%s_input_data.gdx'%scenario)) + balm.input_data[scenario] = ws.add_database_from_gdx(os.path.join(balm.path, scenario, 'model', '%s_input_data.gdx'%scenario)) # Get symbol f = symbol_to_df(balm.input_data[scenario], symbol, columns) @@ -101,7 +101,7 @@ def get_grid(balmorel_model_path: str, XINVCOST = store_balmorel_input('XINVCOST', ['Y', 'RE', 'RI', 'connection'], balmorel_model_path, scenario, load_again, - lambda x: x.query("Y == '2050' and (RE.str.contains('DK_') and RI.str.contains('DK_'))"), + lambda x: x.query("Y == '2050' and (RE.str.contains('DK_') and RI.str.contains('DK_'))"), # Hardcoded for old database False) ## Convert names and get 0/1 2D array diff --git a/src/assumptions.yaml b/src/assumptions.yaml index ccc05f3..6c28e26 100644 --- a/src/assumptions.yaml +++ b/src/assumptions.yaml @@ -1,6 +1,6 @@ balmorel_input: model_path: "../../Balmorel" - scenario: "new_muni" + scenario: "muni" load_again: False grid_assumptions: diff --git a/src/clustering b/src/clustering index 9ed9186..67d464a 100644 --- a/src/clustering +++ b/src/clustering @@ -1,5 +1,5 @@ # Load the config file at the top -configfile: "assumptions.yaml" +configfile: "clustering.yaml" out_path = "Output/" data_path = "Data/" @@ -29,9 +29,13 @@ rule clustering: f"{balmorel_sc_folder}{scenario}_input_data.gdx", f"{modules_path}clustering.py" ] + params: + cluster_params=config['clustering']['data_for_clustering'], + aggregation_functions=config['clustering']['aggregation_functions'], + cluster_size=config['clustering']['cluster_size'] output: [f"{out_path}WND_VAR_T.inc"] shell: """ - python {modules_path}clustering.py --model-path={balmorel_path} --scenario={scenario} + python {modules_path}clustering.py --model-path={balmorel_path} --scenario={scenario} --cluster-params "{params.cluster_params}" --aggregation-functions="{params.aggregation_functions}" --cluster-size={params.cluster_size} """ diff --git a/src/clustering.yaml b/src/clustering.yaml new file mode 100644 index 0000000..54cecec --- /dev/null +++ b/src/clustering.yaml @@ -0,0 +1,9 @@ +balmorel_input: + model_path: "../../Balmorel" + scenario: "new_muni" + load_again: False + +clustering: + data_for_clustering: "DE, DH, WNDFLH, SOLEFLH" + aggregation_functions: "sum, sum, mean, mean" + cluster_size: 6 \ No newline at end of file From 264146bc3ff2bfa8faa5a9123cef078c3dafdf5d Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 19:54:40 +0200 Subject: [PATCH 16/19] Saved figure instead of doing plt.show, fixed unicode error --- src/Modules/clustering.py | 5 ++--- src/Modules/vre_profiles.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Modules/clustering.py b/src/Modules/clustering.py index 652bd79..2d16120 100644 --- a/src/Modules/clustering.py +++ b/src/Modules/clustering.py @@ -285,7 +285,6 @@ def cluster(collected_data: pd.DataFrame, # ax.set_title('%d clusters, %s linkage, %s'%(n_clusters, name, connection_remark)) ax.axes.axis('off') - plt.show() # fig.savefig(r'C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Deliverables\Spatial Resolution\Investigations\240912 - Initial Clustering Method Tests'+'/'+plot_title.replace('data: ', '_').replace('\n', '').replace(' clusters', 'N').replace(' ', '_').replace(',', '') + '.png', # transparent=True, @@ -348,8 +347,8 @@ def main(model_path: str, cluster_params_list, aggfuncs) # Do clustering - fig, ax, clustering = cluster(collected, cluster_size) - + fig, ax, clustering = cluster(collected, cluster_size, connection_remark='', data_remark=cluster_params) + fig.savefig('Output/Figures/Clustering/clustering.pdf', transparent=True, bbox_inches='tight') if __name__ == '__main__': main() \ No newline at end of file diff --git a/src/Modules/vre_profiles.py b/src/Modules/vre_profiles.py index 343d30a..0485382 100644 --- a/src/Modules/vre_profiles.py +++ b/src/Modules/vre_profiles.py @@ -10,7 +10,7 @@ REQUIRED: Using ERA5 requires an API-key from Copernicus 1) Sign up at https://cds.climate.copernicus.eu/#!/home - 2) Create a file called .cdsapirc in your home drive (typically C:\Users\USERNAME) + 2) Create a file called .cdsapirc in your home drive (typically C:/Users/USERNAME) Copy paste the UID and API key that you got into .cdsapirc in the following format: url: https://cds.climate.copernicus.eu/api/v2 key: UID:API From d01151a6d54d1dad10ee4283d07ae98536aa0340 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Tue, 1 Oct 2024 20:09:00 +0200 Subject: [PATCH 17/19] Made clustering output --- .gitignore | 1 + src/Analysis/clustering_dag.pdf | Bin 6909 -> 7427 bytes src/Modules/clustering.py | 7 +++++-- src/clustering | 12 +++++++++--- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 7857155..451e5c9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__ prebalm *.pyc +clusteroutput clustering_dot_source.txt clustering_dag preprocessing_dot_source.txt diff --git a/src/Analysis/clustering_dag.pdf b/src/Analysis/clustering_dag.pdf index 11a7172260b5573dcc6512c027425e01b47dec01..724997ea85241e3b42b38a4ac3ca913e53b64007 100644 GIT binary patch delta 5881 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 Date: Tue, 1 Oct 2024 20:42:11 +0200 Subject: [PATCH 18/19] Made clustering example workflow --- src/Analysis/clustering_dag.pdf | Bin 7427 -> 7583 bytes src/Analysis/preprocessing_dag.pdf | Bin 15182 -> 15181 bytes src/Modules/convert_demands.py | 90 +++++++++++++++++++++++++++++ src/clustering | 8 ++- src/clustering.yaml | 2 +- 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/Modules/convert_demands.py diff --git a/src/Analysis/clustering_dag.pdf b/src/Analysis/clustering_dag.pdf index 724997ea85241e3b42b38a4ac3ca913e53b64007..ecf1a94534a0b89ea5bfebde279e7df132290b1b 100644 GIT binary patch delta 5988 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 SliVB~3Nte_3MC~)Peux=Mvou> delta 5800 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 delta 3825 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 diff --git a/src/Modules/convert_demands.py b/src/Modules/convert_demands.py new file mode 100644 index 0000000..a85cc66 --- /dev/null +++ b/src/Modules/convert_demands.py @@ -0,0 +1,90 @@ + +""" +TITLE + +Description + +Created on 01.10.2024 +@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) +""" +#%% ------------------------------- ### +### 0. Script Settings ### +### ------------------------------- ### + +import matplotlib.pyplot as plt +import pandas as pd +import numpy as np +import geopandas as gpd +import click +from pybalmorel import Balmorel, IncFile +from pybalmorel.utils import symbol_to_df +import gams + +#%% ------------------------------- ### +### 1. +### ------------------------------- ### + +def convert_parameter(db: gams.GamsDatabase, + symbol: str, + clustering: gpd.GeoDataFrame): + + # Load dataframe + df = symbol_to_df(db, symbol, ['Y', 'R', 'DEUSER', 'Value']) + + old_column = 'R' + new_column = 'R_new' + aggfunc = 'sum' + + # Aggregate + # df = df.pivot_table(index='R') + + # Convert names + clustering.columns = [old_column, new_column] + df = ( + df + .merge(clustering, on=old_column, how='outer') + .drop(columns=old_column) + .rename(columns={new_column : old_column}) + .groupby(['Y', 'R', 'DEUSER']) + .aggregate({'Value' : aggfunc}) + ) + + # Make IncFile + prefix = '\n'.join([ + "TABLE DE(RRR,YYY,DEUSER) 'Annual electricity demand'", + "" + ]) + suffix = '\n;' + f = IncFile(name=symbol, path='ClusterOutput', + prefix=prefix, suffix=suffix) + f.body = df + f.body_prepare(index=['Y', 'R'], columns='DEUSER', values='Value') + f.save() + + + +#%% ------------------------------- ### +### X. Main ### +### ------------------------------- ### + +@click.command() +@click.option('--model-path', type=str, required=True, help='Balmorel model path') +@click.option('--scenario', type=str, required=True, help='Balmorel scenario') +def main(model_path: str, scenario: str): + + # Load files + m = Balmorel(model_path) + m.load_incfiles(scenario) + + # Naming of clusters + clusters = gpd.read_file('ClusterOutput/clustering.gpkg') + clusters['cluster_name'] = '' + for cluster in clusters.cluster_group.unique(): + idx = clusters.query('cluster_group == @cluster').index + clusters.loc[idx, 'cluster_name'] = 'CL%d'%cluster + + convert_parameter(m.input_data[scenario], + 'DE', clusters[['index', 'cluster_name']]) + +if __name__ == '__main__': + main() diff --git a/src/clustering b/src/clustering index 09f483b..914035d 100644 --- a/src/clustering +++ b/src/clustering @@ -12,8 +12,7 @@ balmorel_sc_folder = f"{balmorel_path}/{scenario}/model/" # 1. General Purpose rule all: input: - [f"{out_path}DE.inc", - ] + [f"{out_path}DE.inc"] rule collect_balmorel_input: output: @@ -42,6 +41,9 @@ rule clustering: rule convert_demands: input: - f"{out_path}clustering.gpkg" + [ + f"{balmorel_sc_folder}{scenario}_input_data.gdx", + f"{out_path}clustering.gpkg" + ] output: f"{out_path}DE.inc" \ No newline at end of file diff --git a/src/clustering.yaml b/src/clustering.yaml index 54cecec..e0fd420 100644 --- a/src/clustering.yaml +++ b/src/clustering.yaml @@ -6,4 +6,4 @@ balmorel_input: clustering: data_for_clustering: "DE, DH, WNDFLH, SOLEFLH" aggregation_functions: "sum, sum, mean, mean" - cluster_size: 6 \ No newline at end of file + cluster_size: 20 \ No newline at end of file From 749c1c1ec7cf6a7d1bb984101a3b763f4c9e9075 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Thu, 3 Oct 2024 13:47:30 +0200 Subject: [PATCH 19/19] Minor change --- src/Modules/clustering.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Modules/clustering.py b/src/Modules/clustering.py index 5dfd7fe..e96b351 100644 --- a/src/Modules/clustering.py +++ b/src/Modules/clustering.py @@ -16,6 +16,7 @@ from pybalmorel import Balmorel from pybalmorel.utils import symbol_to_df from Submodules.utils import convert_names +from typing import Tuple import click import pandas as pd import numpy as np @@ -83,7 +84,7 @@ def correct_VRE_data(path_to_file, generation_name: str): return vredata def convert_municipal_code_to_name(to_be_converted: pd.DataFrame, - column_to_convert: (str, int), + column_to_convert: Tuple[str, int], pivot_table: bool = False, exclude_regions: list = ['Herlev', 'Christiansø'], muni_geofile_path: str = r'C:\Users\mberos\gitRepos\balmorel-preprocessing\src\Data\Shapefiles\Denmark\Adm\gadm36_DNK_2.shp'):