From 03891c3734dae109d4c47b1f80a2f1aea2fd98e3 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 14 Oct 2024 10:36:36 +0200 Subject: [PATCH 1/5] Started using cutout data to generate heat profiles --- src/Modules/heat_profiles.py | 183 ++++++++++++++++++++++++++++++ src/Modules/heat_profiles_func.py | 52 --------- 2 files changed, 183 insertions(+), 52 deletions(-) create mode 100644 src/Modules/heat_profiles.py delete mode 100644 src/Modules/heat_profiles_func.py diff --git a/src/Modules/heat_profiles.py b/src/Modules/heat_profiles.py new file mode 100644 index 0000000..63a3ed9 --- /dev/null +++ b/src/Modules/heat_profiles.py @@ -0,0 +1,183 @@ +""" +TITLE + +Description + +Created on 14.10.2024 +@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) +""" +#%% ------------------------------- ### +### 0. CLI ### +### ------------------------------- ### + +import matplotlib.pyplot as plt +import pandas as pd +from shapely import Point +import numpy as np +import geopandas as gpd +import xarray as xr +import click +from geofiles import prepared_geofiles +from Submodules.utils import store_balmorel_input, join_to_gpd +from pybalmorel import IncFile + +@click.group() +@click.option('--dark-style', is_flag=True, required=False, help='Dark plot style') +@click.option('--plot-ext', type=str, default='.pdf', required=False, help='The extension of the plot, defaults to ".pdf"') +@click.pass_context +def CLI(ctx, dark_style: bool, plot_ext: str): + """ + Description of the CLI + """ + + # Set global style of plot + if dark_style: + plt.style.use('dark_background') + fc = 'none' + else: + fc = 'white' + + # Store global options in the context object + ctx.ensure_object(dict) + ctx.obj['fc'] = fc + ctx.obj['plot_ext'] = plot_ext + +#%% ------------------------------- ### +### 1. Commands ### +### ------------------------------- ### + +@CLI.command() +@click.pass_context +@click.argument('cutout', type=str) +def cmd1(ctx, cutout: str): + "A command in the CLI" + + temperature = xr.load_dataset(cutout).temperature + the_index, geofile, c = prepared_geofiles('DKmunicipalities_names') + + agg_temperatures = aggregate_temperatures(temperature, geofile) + print(agg_temperatures) + +#%% ------------------------------- ### +### 2. Utils ### +### ------------------------------- ### + +def aggregate_temperatures(temperature: xr.DataArray, + geofile: gpd.GeoDataFrame, + aggfunc: str = 'mean'): + + agg_temperatures = xr.DataArray(dims=['municipality', 'time'], + coords={'municipality' : geofile.index.values, + 'time' : temperature.coords['time'].data}, + name='temperature') + + for municipality in geofile.index: + # Get the bounds of the municipality + x0, y0, x1, y1 = geofile.loc[municipality, 'geometry'].bounds + + # Get the coordinates inside the municipality + snapshot = ( + temperature.loc[:, y0:y1+0.25, x0:x1+0.25] # Order of coordinates: time, latitude, longitude. Add buffer equal to grid resolution to ensure the snapshot is not empty + .isel(time=0) # A snapshot in time, we just need coordinates at this point + .to_dataframe() + .reset_index() + ) + coords = gpd.points_from_xy(snapshot['x'], snapshot['y']) + idx = coords.within(geofile.loc[municipality, 'geometry']) + + if len(coords[idx]) == 0: + centroid_distances = ( + geofile + # .to_crs('EPSG:4093') + .loc[municipality, 'geometry'] + .centroid + .distance(coords) + ) + coords = coords[centroid_distances == centroid_distances.min()] + print('No points within %s!'%municipality, 'The closest point was %0.2f m away..'%(centroid_distances.min())) + else: + coords = coords[idx] + + # The mean temperature of coordinates in the municipality + aggregated_temperature = getattr(temperature.loc[:, coords.y, coords.x], aggfunc)(dim=['x', 'y']) + + # Store it + aggregated_temperature = aggregated_temperature.assign_coords({'municipality' : municipality}) + agg_temperatures.loc[municipality, :] = aggregated_temperature + + # Report it + meanstd = temperature.loc[:, coords.y, coords.x].std(dim=['x', 'y']).mean() + print('Mean standard deviation of temperature data inside %s:'%municipality, float(meanstd)) + + return agg_temperatures + +@click.pass_context +def plot_style(ctx, fig: plt.figure, ax: plt.axes, name: str): + + ax.set_facecolor(ctx.obj['fc']) + ax.legend(loc='center', bbox_to_anchor=(.5, 1.15), ncol=3) + + fig.savefig(name + ctx.obj['plot_ext'], bbox_inches='tight', transparent=True) + + return fig, ax + +def load_profiles_from_balmorel(ctx): + # Load DH_VAR_T + df = store_balmorel_input('DH_VAR_T', ['A', 'DHUSER', 'S', 'T', 'Value'], + ctx.obj['model_path'], ctx.obj['scenario'], ctx.obj['load_again'], + lambda x: x.loc[x.A.str.contains('DK_')].query("DHUSER == 'RESH'")) + + ## Join municipal codes ('A') to names ('NAME_2') + df = join_to_gpd(df, 'A', ctx.obj['mun'], 'NAME_2', + ['A_old', 'DHUSER', 'S', 'T', 'Value', 'A'], '_A') + + return df + +@click.pass_context +def heat_profiles_func(ctx, df): + + + ### Save DH_VAR_T.inc + incfile = IncFile(name='DH_VAR_T', path='Output', + prefix='\n'.join([ + "PARAMETER DH_VAR_T(AAA,DHUSER,SSS,TTT) 'Variation in heat demand';", + "TABLE DH_VAR_T1(SSS,TTT,AAA,DHUSER)", + "" + ]), + body=df, + suffix='\n'.join([ + "", + ";", + "DH_VAR_T(AAA,'RESH',SSS,TTT) = DH_VAR_T1(SSS,TTT,AAA,'RESH');", + "DH_VAR_T1(SSS,TTT,AAA,DHUSER) = 0;", + "DH_VAR_T('Herlev_A','RESH',SSS,TTT) = DH_VAR_T('Ballerup_A','RESH',SSS,TTT);" + ])) + incfile.body_prepare(['S', 'T'], + ['A', 'DHUSER']) + incfile.save() + + ### Save INDIVUSERS_DH_VAR_T + df['A'] = df.A.str.replace('_A', '_IDVU-SPACEHEAT') + df['DHUSER'] = 'RESIDENTIAL' + incfile = IncFile(name='INDIVUSERS_DH_VAR_T', path='Output', + prefix='\n'.join([ + "TABLE DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER)", + "" + ]), + body=df, + suffix='\n'.join([ + "", + ";", + "DH_VAR_T(AAA,DHUSER,SSS,TTT)$(SUM((S,T), DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER))) = DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER);", + "DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER) = 0;", + "DH_VAR_T('Herlev_A','RESIDENTIAL',SSS,TTT) = DH_VAR_T('Ballerup_A','RESIDENTIAL',SSS,TTT);" + ])) + incfile.body_prepare(['S', 'T'], + ['A', 'DHUSER']) + incfile.save() + +#%% ------------------------------- ### +### 3. Main ### +### ------------------------------- ### +if __name__ == '__main__': + CLI() diff --git a/src/Modules/heat_profiles_func.py b/src/Modules/heat_profiles_func.py deleted file mode 100644 index 4a4c651..0000000 --- a/src/Modules/heat_profiles_func.py +++ /dev/null @@ -1,52 +0,0 @@ -from Submodules.utils import store_balmorel_input, join_to_gpd -from pybalmorel import IncFile - -def heat_profiles_func(ctx): - - # Load DH_VAR_T - df = store_balmorel_input('DH_VAR_T', ['A', 'DHUSER', 'S', 'T', 'Value'], - ctx.obj['model_path'], ctx.obj['scenario'], ctx.obj['load_again'], - lambda x: x.loc[x.A.str.contains('DK_')].query("DHUSER == 'RESH'")) - - ## Join municipal codes ('A') to names ('NAME_2') - df = join_to_gpd(df, 'A', ctx.obj['mun'], 'NAME_2', - ['A_old', 'DHUSER', 'S', 'T', 'Value', 'A'], '_A') - - ### Save DH_VAR_T.inc - incfile = IncFile(name='DH_VAR_T', path='Output', - prefix='\n'.join([ - "PARAMETER DH_VAR_T(AAA,DHUSER,SSS,TTT) 'Variation in heat demand';", - "TABLE DH_VAR_T1(SSS,TTT,AAA,DHUSER)", - "" - ]), - body=df, - suffix='\n'.join([ - "", - ";", - "DH_VAR_T(AAA,'RESH',SSS,TTT) = DH_VAR_T1(SSS,TTT,AAA,'RESH');", - "DH_VAR_T1(SSS,TTT,AAA,DHUSER) = 0;", - "DH_VAR_T('Herlev_A','RESH',SSS,TTT) = DH_VAR_T('Ballerup_A','RESH',SSS,TTT);" - ])) - incfile.body_prepare(['S', 'T'], - ['A', 'DHUSER']) - incfile.save() - - ### Save INDIVUSERS_DH_VAR_T - df['A'] = df.A.str.replace('_A', '_IDVU-SPACEHEAT') - df['DHUSER'] = 'RESIDENTIAL' - incfile = IncFile(name='INDIVUSERS_DH_VAR_T', path='Output', - prefix='\n'.join([ - "TABLE DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER)", - "" - ]), - body=df, - suffix='\n'.join([ - "", - ";", - "DH_VAR_T(AAA,DHUSER,SSS,TTT)$(SUM((S,T), DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER))) = DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER);", - "DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER) = 0;", - "DH_VAR_T('Herlev_A','RESIDENTIAL',SSS,TTT) = DH_VAR_T('Ballerup_A','RESIDENTIAL',SSS,TTT);" - ])) - incfile.body_prepare(['S', 'T'], - ['A', 'DHUSER']) - incfile.save() \ No newline at end of file From e19765be90ff4d81d88bc2a8a574e1c81514a04f Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 14 Oct 2024 14:07:54 +0200 Subject: [PATCH 2/5] Added heat degree hour assumption and started formatting --- src/Modules/heat_profiles.py | 78 ++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/src/Modules/heat_profiles.py b/src/Modules/heat_profiles.py index 63a3ed9..370c148 100644 --- a/src/Modules/heat_profiles.py +++ b/src/Modules/heat_profiles.py @@ -1,7 +1,9 @@ """ -TITLE +Generate Heat Demand Timeseries -Description +Will use temperature coordinates to 1) find average temperatures within polygons, +2) use heating degree days assumption from Eurostat to generate heat demand profiles based on these timeseries +3) aggregate this profile with a flat profile for hot water consumption, being 25% of the total profile Created on 14.10.2024 @author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) @@ -12,8 +14,7 @@ import matplotlib.pyplot as plt import pandas as pd -from shapely import Point -import numpy as np +from pybalmorel import IncFile import geopandas as gpd import xarray as xr import click @@ -49,14 +50,56 @@ def CLI(ctx, dark_style: bool, plot_ext: str): @CLI.command() @click.pass_context @click.argument('cutout', type=str) -def cmd1(ctx, cutout: str): +@click.option('--weather-year', type=int, required=False, default=2012, help="The weather year") +@click.option('--plot', is_flag=True, required=False, help="Plot the average temperatures on a map?") +def cmd1(ctx, cutout: str, weather_year: int, plot: bool): "A command in the CLI" + # Get files temperature = xr.load_dataset(cutout).temperature the_index, geofile, c = prepared_geofiles('DKmunicipalities_names') + # Aggregate temperature for coordinates inside municipality polygons agg_temperatures = aggregate_temperatures(temperature, geofile) - print(agg_temperatures) + + plot_data(agg_temperatures, 'temperature') + + # Make heat demand profile + ## Convert temperatures to C + agg_temperatures['heat_demand'] = agg_temperatures.temperature.copy() - 273.15 + ## Apply heat degree hour function + agg_temperatures['heat_demand'] = xr.where(agg_temperatures.heat_demand <= 15, 18 - agg_temperatures.heat_demand, 0) + ## Add constant profile for hot water consumption + agg_temperatures['heat_demand'] = 0.75*agg_temperatures.heat_demand/agg_temperatures.heat_demand.sum('time') + 0.25 / len(agg_temperatures.time.data) + + plot_data(agg_temperatures, 'heat_demand') + + + # Format data for Balmorel input + df = ( + agg_temperatures.heat_demand + .to_dataframe() + .reset_index() + .pivot_table(index='time', columns=['municipality'], values='heat_demand', aggfunc='sum') + ) + + iso = pd.Series(df.index).dt.isocalendar() + + ## Sort away week 52 from last year + idx1 = iso.query('index < 672 and week == 52 and year == @weather_year-1').index + ## Sort away week 1 from next year + idx2 = iso.query('index > 8088 and week == 1 and year == @weather_year+1').index + + ## Check if there are exactly 8736 timeslices + iso = ( + iso + .drop(index=idx1) + .drop(index=idx2) + ) + assert len(iso) == 8736, 'Timeseries does not contain 52*168 slices!' + + df = df.iloc[iso.index] + #%% ------------------------------- ### ### 2. Utils ### @@ -77,7 +120,7 @@ def aggregate_temperatures(temperature: xr.DataArray, # Get the coordinates inside the municipality snapshot = ( - temperature.loc[:, y0:y1+0.25, x0:x1+0.25] # Order of coordinates: time, latitude, longitude. Add buffer equal to grid resolution to ensure the snapshot is not empty + temperature.loc[:, y0-0.25:y1+0.25, x0-0.25:x1+0.25] # Order of coordinates: time, latitude, longitude. Add buffer equal to grid resolution to ensure the snapshot is not empty .isel(time=0) # A snapshot in time, we just need coordinates at this point .to_dataframe() .reset_index() @@ -109,8 +152,29 @@ def aggregate_temperatures(temperature: xr.DataArray, meanstd = temperature.loc[:, coords.y, coords.x].std(dim=['x', 'y']).mean() print('Mean standard deviation of temperature data inside %s:'%municipality, float(meanstd)) + # Add polygons for plotting + geo = geofile['geometry'] + geo.index.name = 'municipality' + + agg_temperatures = xr.Dataset({'temperature' : agg_temperatures, 'geometry' : geo}) + agg_temperatures.geometry.attrs['crs'] = 'EPSG:4326' + return agg_temperatures +@click.pass_context +def plot_data(ctx, + aggregated_temperatures: xr.Dataset, + data: str): + fig, ax = plt.subplots() + + geo = gpd.GeoDataFrame(aggregated_temperatures.geometry.to_dataframe(), + geometry='geometry', + crs=aggregated_temperatures.geometry.crs) + geo['data'] = aggregated_temperatures['data'].mean('time').to_dataframe() + + geo.plot(column='data', ax=ax) + fig, ax = plot_style(fig, ax, 'Output/data') + @click.pass_context def plot_style(ctx, fig: plt.figure, ax: plt.axes, name: str): From d7d616f01d1359f2dede2ef6c5b6dd0bbd3fe249 Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 14 Oct 2024 15:02:14 +0200 Subject: [PATCH 3/5] Finished formatting heat profiles --- src/Modules/heat_profiles.py | 116 +++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 26 deletions(-) diff --git a/src/Modules/heat_profiles.py b/src/Modules/heat_profiles.py index 370c148..241583c 100644 --- a/src/Modules/heat_profiles.py +++ b/src/Modules/heat_profiles.py @@ -75,30 +75,45 @@ def cmd1(ctx, cutout: str, weather_year: int, plot: bool): plot_data(agg_temperatures, 'heat_demand') - # Format data for Balmorel input - df = ( - agg_temperatures.heat_demand - .to_dataframe() - .reset_index() - .pivot_table(index='time', columns=['municipality'], values='heat_demand', aggfunc='sum') - ) - - iso = pd.Series(df.index).dt.isocalendar() + # Make Balmorel input + df = format_data(agg_temperatures, weather_year) - ## Sort away week 52 from last year - idx1 = iso.query('index < 672 and week == 52 and year == @weather_year-1').index - ## Sort away week 1 from next year - idx2 = iso.query('index > 8088 and week == 1 and year == @weather_year+1').index + ## Create IncFiles + f = IncFile(name='DH_VAR_T', path='Output', + prefix="\n".join([ + "PARAMETER DH_VAR_T(AAA,DHUSER,SSS,TTT) 'Variation in heat demand';", + "TABLE DH_VAR_T1(SSS,TTT,AAA,DHUSER)", + "", + ]), + suffix="\n".join([ + "", + ";", + "DH_VAR_T(AAA,'RESH',SSS,TTT) = DH_VAR_T1(SSS,TTT,AAA,'RESH');", + "DH_VAR_T1(SSS,TTT,AAA,DHUSER) = 0;", + ])) + ### Make _A suffix + dfA = df.copy() + dfA.columns = pd.Series(dfA.columns) + '_A' + ' . RESH' + f.body = dfA + f.save() - ## Check if there are exactly 8736 timeslices - iso = ( - iso - .drop(index=idx1) - .drop(index=idx2) + dfA.columns = pd.Series(dfA.columns).str.replace('_A', '_IDVU-SPACEHEAT').str.replace('RESH', 'RESIDENTIAL') + f = IncFile( + name = 'INDIVUSERS_DH_VAR_T', + path = 'Output', + prefix='\n'.join([ + "TABLE DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER)", + "" + ]), + body= dfA, + suffix='\n'.join([ + "", + ";", + "DH_VAR_T(AAA,DHUSER,SSS,TTT)$(SUM((S,T), DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER))) = DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER);", + "DH_VAR_T_INDIVHEATING(SSS,TTT,AAA,DHUSER) = 0;" + ]) ) - assert len(iso) == 8736, 'Timeseries does not contain 52*168 slices!' - - df = df.iloc[iso.index] + f.save() #%% ------------------------------- ### @@ -161,6 +176,53 @@ def aggregate_temperatures(temperature: xr.DataArray, return agg_temperatures + +def format_data(agg_temperatures: xr.Dataset, weather_year: int) -> pd.DataFrame: + """Format timeseries to format expected by Balmorel + + Args: + agg_temperatures (xr.Dataset): The dataset with heat demands per municipality + + Returns: + pd.DataFrame: The formatted dataframe, ready for the IncFile class + """ + df = ( + agg_temperatures.heat_demand + .to_dataframe() + .reset_index() + .pivot_table(index='time', columns=['municipality'], values='heat_demand', aggfunc='sum') + ) + + iso = pd.Series(df.index).dt.isocalendar() + + ## Sort away week 52 from last year + idx1 = iso.query('index < 672 and week == 52 and year == @weather_year-1').index + ## Sort away week 1 from next year + idx2 = iso.query('index > 8088 and week == 1 and year == @weather_year+1').index + + ## Check if there are exactly 8736 timeslices + iso = ( + iso + .drop(index=idx1) + .drop(index=idx2) + ) + + assert len(iso) == 8736, 'Timeseries does not contain 52*168 slices!' + + df = df.iloc[iso.index] + + ## Make S and T index + S = ['S0%d'%i for i in range(1, 10)] + ['S%d'%i for i in range(10, 53)] + T = ['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)] + index = pd.MultiIndex.from_product((S, T)) + index = index.get_level_values(0) + ' . ' + index.get_level_values(1) + + df.index = index + df.index.name = '' + df.columns.name = '' + + return df + @click.pass_context def plot_data(ctx, aggregated_temperatures: xr.Dataset, @@ -170,16 +232,18 @@ def plot_data(ctx, geo = gpd.GeoDataFrame(aggregated_temperatures.geometry.to_dataframe(), geometry='geometry', crs=aggregated_temperatures.geometry.crs) - geo['data'] = aggregated_temperatures['data'].mean('time').to_dataframe() + geo[data] = aggregated_temperatures[data].mean('time').to_dataframe() - geo.plot(column='data', ax=ax) - fig, ax = plot_style(fig, ax, 'Output/data') + geo.plot(column=data, ax=ax) + fig, ax = plot_style(fig, ax, 'Output/Figures/%s'%data, False) @click.pass_context -def plot_style(ctx, fig: plt.figure, ax: plt.axes, name: str): +def plot_style(ctx, fig: plt.figure, ax: plt.axes, name: str, legend: bool): ax.set_facecolor(ctx.obj['fc']) - ax.legend(loc='center', bbox_to_anchor=(.5, 1.15), ncol=3) + + if legend: + ax.legend(loc='center', bbox_to_anchor=(.5, 1.15), ncol=3) fig.savefig(name + ctx.obj['plot_ext'], bbox_inches='tight', transparent=True) From 5c9657d2a81343ec92fafec62c097f281362a6af Mon Sep 17 00:00:00 2001 From: Mathias157 Date: Mon, 14 Oct 2024 15:07:38 +0200 Subject: [PATCH 4/5] Implemented new CLI commands for heat_profiles script --- src/Analysis/preprocessing_dag.pdf | Bin 15882 -> 16088 bytes src/Modules/heat_profiles.py | 2 +- src/preprocessing | 10 ++++------ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Analysis/preprocessing_dag.pdf b/src/Analysis/preprocessing_dag.pdf index 2e304603e29aa9bd6e7a9fc5e2fe925076e46cb5..ec8f663b87f7ae56ec2ef5e784dc521b379cce97 100644 GIT binary patch delta 4907 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 Date: Mon, 14 Oct 2024 15:25:25 +0200 Subject: [PATCH 5/5] Removed old links to the previous heat profile generation --- .../Submodules/read_futuregas_profiles.py | 133 ------------------ src/Modules/format_balmorel_data.py | 9 -- src/Modules/heat_profiles.py | 6 +- 3 files changed, 5 insertions(+), 143 deletions(-) delete mode 100644 src/Modules/Submodules/read_futuregas_profiles.py diff --git a/src/Modules/Submodules/read_futuregas_profiles.py b/src/Modules/Submodules/read_futuregas_profiles.py deleted file mode 100644 index b37edc9..0000000 --- a/src/Modules/Submodules/read_futuregas_profiles.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Generate heat profiles from futuregas data - -Assumes that the distribution of heat profiles that i made using FutureGas data is in the Balmorel/muni/data folder - -Created on 10.09.2024 -@author: Mathias Berg Rosendal, PhD Student at DTU Management (Energy Economics & Modelling) -""" -#%% ------------------------------- ### -### 0. Script Settings ### -### ------------------------------- ### - -from pybalmorel import Balmorel -from pybalmorel.utils import symbol_to_df -from Modules.geofiles import prepared_geofiles -from Modules.Submodules.municipal_template import DataContainer -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt - -#%% ------------------------------- ### -### 1. Read .inc Files ### -### ------------------------------- ### - -m = Balmorel(r'C:\Users\mberos\gitRepos\Balmorel') -m.load_incfiles('muni') - -#%% Get profiles -profiles = symbol_to_df(m.input_data['muni'], 'DH_VAR_T', ['A', 'DHUSER', 'S', 'T', 'Value']) - -# Remove the _A suffix -profiles.A = profiles.A.str.replace('_A', '') - -profiles = profiles.pivot_table(index=['A', 'DHUSER', 'S', 'T']) - -#%% Get municipality geofile -index, areas, c = prepared_geofiles('dkmunicipalities') -correct_names = {'Århus' : 'Aarhus', - 'Høje Taastrup' : 'Høje-Taastrup', - 'Vesthimmerland' : 'Vesthimmerlands'} -areas['NAME_2'] = areas['NAME_2'].replace(correct_names) - -#%% Get only residential heat profiles -DK_profiles = ( - profiles[ - ( - profiles - .index - .get_level_values(0) - .str - .find('DK_') != -1 - ) - & - ( - profiles - .index - .get_level_values(0) - .str - .find('_IND') == -1 - ) - ] -) - -# Fix missing data for Herlev DK_1_19_1 (not for DK_1_6_1 Christiansø) -areas2 = areas.query("NAME_2 != 'Herlev' and NAME_2 != 'Christiansø'") -converter = dict(areas2.NAME_2) - -# Convert to municipality names -DK_profiles = DK_profiles.reset_index() -DK_profiles['A'] = DK_profiles['A'].replace(converter) - -# Add assumption on Herlev data being equal to Ballerup -temp = DK_profiles.query('A == "Ballerup"') -temp.A = 'Herlev' -DK_profiles = pd.concat(( - DK_profiles, - temp -)) - -# Convert to correct column names -DK_profiles.columns = ['municipality', 'user', 'week', 'hour', 'heat_demand_profile'] - -# Convert to correct coordinate names -DK_profiles['week'] = ( - DK_profiles['week'] - .str.replace('S0', '') - .str.replace('S', '') - .astype(int) -) -DK_profiles['hour'] = ( - DK_profiles['hour'] - .str.replace('T00', '') - .str.replace('T0', '') - .str.replace('T', '') - .astype(int) -) - -# Pivot and convert to xarray -DK_profiles = DK_profiles.pivot_table(index=['municipality', 'user', 'week', 'hour'], - values='heat_demand_profile').to_xarray() - -#%% Save -DK_profiles.to_netcdf('Data/Timeseries/DK_heat_profile_futuregas.nc') - -#%% Combine with polygon data -X = DataContainer() -X.muni = X.muni.merge(DK_profiles) - -#%% Get maximum values -max_vals = X.muni.heat_demand_profile.max(('week', 'hour', 'user')) - -#%% Plot the timevariation -for i in range(1): - for j in range(24): - fig, ax = plt.subplots() - X.get_polygons().plot( - ax=ax, - column=( - X - .muni - .heat_demand_profile - .sel(user='RESH') - .isel(week=i, hour=j) - .data - )/max_vals.data, - legend=True, - vmin=0, - vmax=1 - ) - ax.axes.axis('off') - # fig.savefig() - # plt.close(fig) -# Make gif = C:\Users\mberos\Danmarks Tekniske Universitet\PhD in Transmission and Sector Coupling - Dokumenter\Documents\Social\Friday Bar\createGIF.py diff --git a/src/Modules/format_balmorel_data.py b/src/Modules/format_balmorel_data.py index ca4eeb3..64e689b 100644 --- a/src/Modules/format_balmorel_data.py +++ b/src/Modules/format_balmorel_data.py @@ -15,7 +15,6 @@ from geofiles import prepared_geofiles from Submodules.utils import store_balmorel_input from onshore_vre_func import onshore_vre_func -from heat_profiles_func import heat_profiles_func import numpy as np from pybalmorel import IncFile import pandas as pd @@ -61,14 +60,6 @@ def main(ctx, model_path: str, scenario: str, load_again: bool = False): ctx.obj['mun'] = mun - -@main.command() -@click.pass_context -def heat_profiles(ctx): - """Get heat profiles from Bramstoft, Rasmus, Amalia Pizarro-Alonso, Ida Græsted Jensen, Hans Ravn, and Marie Münster. “Modelling of Renewable Gas and Renewable Liquid Fuels in Future Integrated Energy Systems.” Applied Energy 268 (June 15, 2020): 114869. https://doi.org/10.1016/j.apenergy.2020.114869.""" - return heat_profiles_func(ctx) - - @main.command() @click.pass_context def onshore_vre(ctx): diff --git a/src/Modules/heat_profiles.py b/src/Modules/heat_profiles.py index c9ec29f..9523a90 100644 --- a/src/Modules/heat_profiles.py +++ b/src/Modules/heat_profiles.py @@ -262,8 +262,12 @@ def load_profiles_from_balmorel(ctx): return df @click.pass_context -def heat_profiles_func(ctx, df): +def heat_profiles_func(df): + """Generates heat profiles from previous Balmorel input data + Args: + df (_type_): The previous profiles input data + """ ### Save DH_VAR_T.inc incfile = IncFile(name='DH_VAR_T', path='Output',