diff --git a/src/pvt_model/analysis/__utils__.py b/src/pvt_model/analysis/__utils__.py index 2c1211c..7473784 100644 --- a/src/pvt_model/analysis/__utils__.py +++ b/src/pvt_model/analysis/__utils__.py @@ -13,6 +13,7 @@ """ import enum +import math import os from logging import Logger @@ -23,6 +24,8 @@ import numpy +import seaborn as sns + from matplotlib.axes import Axes from matplotlib import cm from matplotlib import pyplot as plt @@ -46,6 +49,7 @@ __all__ = ( "GraphDetail", "load_model_data", + "multi_layer_temperature_plot", "plot", "plot_figure", "plot_two_dimensional_figure", @@ -365,7 +369,7 @@ def save_figure(figure_name: str, transparent: bool = False) -> None: filenames.reverse() # Incriment all files in the old_figures directory. - for filename in tqdm(filenames, desc="renaming old files", uint="file"): + for filename in tqdm(filenames, desc="renaming old files", unit="file"): file_match = re.match(file_regex, filename) if file_match is None: continue @@ -587,6 +591,150 @@ def plot_figure( # pylint: disable=too-many-branches save_figure(figure_name, transparent) +def multi_layer_temperature_plot(logger, model_data, *, entry_number) -> None: + """ + Plot a figure with a temperature heatmap for each layer. + + :param logger: + The logger used for the analysis module. + + :param model_data: + The data outputted from the model. + + :param entry_number: + If provided, this is used to compute the entry to plot. Otherwise, hour and + minute are used. + + """ + + def _process_layer_data(key_to_process: str) -> numpy.ndarray: + """ + Process the data for a layer + + :param: key_to_process + The key for the layer being processed. + + :return: The array for the data. + + """ + x_series = [ + int(re.match(coordinate_regex, coordinate).group("x_index")) + for coordinate in data_entry[key_to_process] + ] + y_series = [ + int(re.match(coordinate_regex, coordinate).group("y_index")) + for coordinate in data_entry[key_to_process] + ] + z_series = list(data_entry[key_to_process].values()) + + # Reshape the data for plotting. + array_shape = (len(set(y_series)), len(set(x_series))) + + z_array = numpy.zeros(array_shape) + for index, value in enumerate(z_series): + z_array[len(set(y_series)) - (y_series[index] + 1), x_series[index]] = value + + return z_array + + # Determine the data index number based on the time. + if entry_number is None: + entry_number = int(len(model_data) * ((hour / 24) + (minute / (24 * 60)))) + try: + data_entry = model_data[entry_number] + except KeyError: + try: + data_entry = model_data[str(entry_number)] + except KeyError as e: + logger.error( + "Key lookup failed for data entry number %s: %s", entry_number, str(e) + ) + raise + + coordinate_regex = re.compile(r"\((?P[0-9]*), (?P[0-9]*)\)") + + # Process the data sets + from matplotlib import rc + + rc("font", **{"family": "sans-serif", "sans-serif": ["Arial"]}) + + glass_array = _process_layer_data("layer_temperature_map_glass") + pv_array = _process_layer_data("layer_temperature_map_pv") + absorber_array = _process_layer_data("layer_temperature_map_absorber") + + vmin = math.floor(min(numpy.min(glass_array), numpy.min(pv_array), numpy.min(absorber_array))) + vmax = math.ceil(max(numpy.max(glass_array), numpy.max(pv_array), numpy.max(absorber_array))) + + # Form the data into the right shape for each layer + _, axes = plt.subplots(1, 3, figsize=(48 / 5, 32 / 5)) + palette = sns.cubehelix_palette(start=1.5, rot=-0.5, dark=0.05, light=0.95, as_cmap=True) + palette = sns.diverging_palette(205, 18, s=100, l=50, n=100) + plt.subplots_adjust(hspace=0.3) + + sns.set_context("paper") + sns.set_style("ticks") + + # Plots + linewidth: float = 0 + sns.heatmap(glass_array, cmap=palette, ax=axes[0], vmin=vmin, vmax=vmax, cbar=False, square=False, linewidths=linewidth, linecolor="white") + sns.heatmap(pv_array, cmap=palette, ax=axes[1], vmin=vmin, vmax=vmax, cbar=False, square=False, linewidths=linewidth, linecolor="white") + sns.heatmap(absorber_array, cmap=palette, ax=axes[2], vmin=vmin, vmax=vmax, cbar_kws={"pad": 0.02, "label":"Temperature / $^\circ$C"}, cbar=True, square=False, linewidths=linewidth, linecolor="white") + + axes[0].set_title("Glass", weight="bold") + axes[0].set_xlabel("X-index") + axes[0].set_ylabel("Y-index") + axes[0].text( + -0.05, + 1.04, + "a.", + transform=axes[0].transAxes, + fontsize=12, + fontweight="bold", + va="top", + ha="right", + ) + + axes[1].set_title("PV", weight="bold") + axes[1].set_xlabel("X-index") + axes[1].text( + -0.05, + 1.04, + "b.", + transform=axes[1].transAxes, + fontsize=12, + fontweight="bold", + va="top", + ha="right", + ) + + axes[2].set_title("Absorber", weight="bold") + axes[2].set_xlabel("X-index") + axes[2].text( + -0.05, + 1.04, + "c.", + transform=axes[2].transAxes, + fontsize=12, + fontweight="bold", + va="top", + ha="right", + ) + + axes[1].tick_params(labelleft=False) + axes[2].tick_params(labelleft=False) + + import pdb + + pdb.set_trace() + + figname: str = "pvt_temperatures_1.png" + + plt.savefig( + figname, + transparent=True, + dpi=300, + bbox_inches="tight" + ) + def plot_two_dimensional_figure( figure_name: str, logger: Logger, @@ -594,11 +742,12 @@ def plot_two_dimensional_figure( thing_to_plot: str, *, axis_label: str, - plot_title: str, + plot_title: Optional[str] = None, entry_number: Optional[int] = None, hold: bool = False, hour: Optional[int] = None, minute: Optional[int] = None, + palette: sns.palettes._ColorPalette = sns.diverging_palette(213, 347, as_cmap=True), x_axis_label: str = "Y element index", ) -> None: """ @@ -607,7 +756,7 @@ def plot_two_dimensional_figure( :param figure_name: The name to use when saving the figure. - :param loger: + :param logger: The logger used for the analysis module. :param model_data: @@ -715,15 +864,20 @@ def plot_two_dimensional_figure( surface = plt.imshow( # axes3D, z_array, - cmap=cm.coolwarm, # pylint: disable=no-member + cmap=palette, # pylint: disable=no-member # linewidth=0, # antialiased=False, aspect=array_shape[1] / array_shape[0], ) - plt.title(plot_title) + if plot_title is not None: + plt.title(plot_title) plt.xlabel("Element x index") plt.ylabel("Element y index") + import pdb + + pdb.set_trace() + # Add axes and colour scale. fig3D.colorbar(surface, shrink=0.5, aspect=5, label=axis_label) diff --git a/src/pvt_model/analysis/analysis.py b/src/pvt_model/analysis/analysis.py index b318d3a..d450887 100644 --- a/src/pvt_model/analysis/analysis.py +++ b/src/pvt_model/analysis/analysis.py @@ -35,6 +35,7 @@ from .__utils__ import ( GraphDetail, load_model_data, + multi_layer_temperature_plot, plot_figure, plot_two_dimensional_figure, ) @@ -874,113 +875,110 @@ def analyse_decoupled_steady_state_data( for temperature in data.keys(): try: temperature_string = str(round(float(temperature), 2)).replace(".", "_") + save_fig_name = "steady_state_{layer}_layer_{temperature}degC_input".format( + temperature_string + ) except ValueError: - temperature_string = temperature + save_fig_name = "{temperature}_{layer}" if not skip_2d_plots: - # Glass Temperatures - try: - logger.info( - "Plotting 3D upper glass profile at %s degC.", temperature_string - ) - plot_two_dimensional_figure( - "steady_state_upper_glass_layer_{}degC_input".format( - temperature_string - ), - logger, - data, - axis_label="Temperature / deg C", - entry_number=temperature, - plot_title="Upper glass layer temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_upper_glass", - ) - except TypeError: - logger.info( - "Upper-glass temperature profile could not be plotted due to no data." - ) - - # Glass Temperatures - try: - logger.info("Plotting 3D glass profile at %s degC.", temperature_string) - plot_two_dimensional_figure( - "steady_state_glass_layer_{}degC_input".format(temperature_string), - logger, - data, - axis_label="Temperature / deg C", - entry_number=temperature, - plot_title="Glass layer temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_glass", - ) - except TypeError: - print("Glass temperature profile could not be plotted due to no data.") - - # PV Temperatures - logger.info("Plotting 3D PV profile at %s degC.", temperature_string) - plot_two_dimensional_figure( - "steady_state_pv_layer_{}degC_input".format(temperature_string), - logger, - data, - axis_label="Temperature / deg C", - entry_number=temperature, - plot_title="PV layer temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_pv", - ) - - # Collector Temperatures - logger.info("Plotting 3D absorber profile at %s degC.", temperature_string) - plot_two_dimensional_figure( - "steady_state_absorber_layer_{}degC_input".format(temperature_string), + multi_layer_temperature_plot( logger, data, - axis_label="Temperature / deg C", entry_number=temperature, - plot_title="Collector layer temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_absorber", ) - # Pipe Temperatures - logger.info( - "Plotting 3D pipe profile at %s degC. NOTE: The profile will appear 2D if " - "only one pipe is present.", - temperature_string, - ) - plot_two_dimensional_figure( - "steady_state_pipe_{}degC_input".format(temperature_string), - logger, - data, - axis_label="Pipe temperature / deg C", - entry_number=temperature, - plot_title="Pipe temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_pipe", - ) - - # Bulk-water Temperatures - logger.info( - "Plotting 3D bulk-water profile at %s degC. NOTE: The profile will appear " - "2D if only one pipe is present.", - temperature_string, - ) - plot_two_dimensional_figure( - "steady_state_bulk_water_{}degC_input".format(temperature_string), - logger, - data, - axis_label="Bulk-water temperature / deg C", - entry_number=temperature, - plot_title="Bulk-water temperature with {} K input HTF".format( - round(float(temperature), 2) - ), - thing_to_plot="layer_temperature_map_bulk_water", - ) + # # Glass Temperatures + # try: + # logger.info( + # "Plotting 3D upper glass profile: %s", temperature + # ) + # plot_two_dimensional_figure( + # save_fig_name.format(temperature=temperature, layer="upper_glass"), + # logger, + # data, + # axis_label="Temperature / deg C", + # entry_number=temperature, + # thing_to_plot="layer_temperature_map_upper_glass", + # ) + # except TypeError: + # logger.info( + # "Upper-glass temperature profile could not be plotted due to no data." + # ) + + # # Glass Temperatures + # try: + # logger.info( + # "Plotting 3D glass profile: %s", temperature + # ) + # plot_two_dimensional_figure( + # save_fig_name.format(temperature=temperature, layer="glass"), + # logger, + # data, + # axis_label="Temperature / deg C", + # entry_number=temperature, + # thing_to_plot="layer_temperature_map_glass", + # ) + # except TypeError: + # print("Glass temperature profile could not be plotted due to no data.") + + # # PV Temperatures + # logger.info("Plotting 3D PV profile: %s degC.", temperature) + # plot_two_dimensional_figure( + # save_fig_name.format(temperature=temperature, layer="pv"), + # logger, + # data, + # axis_label="Temperature / deg C", + # entry_number=temperature, + # thing_to_plot="layer_temperature_map_pv", + # ) + + # # Collector Temperatures + # logger.info("Plotting 3D absorber profileL %s", temperature) + # plot_two_dimensional_figure( + # save_fig_name.format(temperature=temperature, layer="pv"), + # logger, + # data, + # axis_label="Temperature / deg C", + # entry_number=temperature, + # thing_to_plot="layer_temperature_map_absorber", + # ) + + # # Pipe Temperatures + # logger.info( + # "Plotting 3D pipe profile at %s degC. NOTE: The profile will appear 2D if " + # "only one pipe is present.", + # temperature_string, + # ) + # plot_two_dimensional_figure( + # "steady_state_pipe_{}degC_input".format(temperature_string), + # logger, + # data, + # axis_label="Pipe temperature / deg C", + # entry_number=temperature, + # plot_title="Pipe temperature with {} K input HTF".format( + # round(float(temperature), 2) + # ), + # thing_to_plot="layer_temperature_map_pipe", + # ) + + # # Bulk-water Temperatures + # logger.info( + # "Plotting 3D bulk-water profile at %s degC. NOTE: The profile will appear " + # "2D if only one pipe is present.", + # temperature_string, + # ) + # plot_two_dimensional_figure( + # "steady_state_bulk_water_{}degC_input".format(temperature_string), + # logger, + # data, + # axis_label="Bulk-water temperature / deg C", + # entry_number=temperature, + # plot_title="Bulk-water temperature with {} K input HTF".format( + # round(float(temperature), 2) + # ), + # thing_to_plot="layer_temperature_map_bulk_water", + # ) # Parse the thermal-efficiency data. with open( @@ -1047,7 +1045,7 @@ def analyse_decoupled_steady_state_data( first_axis_things_to_plot=["collector_temperature_gain"], first_axis_label="Collector temperature gain / K", x_axis_label="Collector input temperature / degC", - use_data_keys_as_x_axis=True, + use_data_keys_as_x_axis=False, plot_title="Collector temperature gain against input temperature", disable_lines=True, override_axis=ax1, diff --git a/system_data/steady_state_data/feb_24_runs.yaml b/system_data/steady_state_data/feb_24_runs.yaml new file mode 100644 index 0000000..bef0cde --- /dev/null +++ b/system_data/steady_state_data/feb_24_runs.yaml @@ -0,0 +1,47 @@ +--- +################################################################################ +# autotherm.yaml - Steady-state experimental data used for verification. +# +# Copyright 2021, Ben Winchester +################################################################################ + +# Data values obtained from +# http://intergeo.sk/wp-content/uploads/GLAZED-TR-UNIEN12975-2-ENG-ITA-COMPLETO-1.pdf +# + +# Ambient temperature measured in Celcius. +# Collector input temperatures measured in Celcius. +# Irradiance measured in Watts per meter squared. +# Mass flow rate is measured in litres per minute. +# Wind speed measured in meters per second. +# + +- ambient_temperature: 22.2 + average_bulk_water_temperature: 17.85 + collector_input_temperature: 15 + collector_temperature_gain: 5.7 + irradiance: 983 + mass_flow_rate: 8 + thermal_efficiency: 0.5 + thermal_power: 684 + wind_speed: 1.63 + +- ambient_temperature: 22.2 + average_bulk_water_temperature: 17.85 + collector_input_temperature: 45 + collector_temperature_gain: 5.7 + irradiance: 983 + mass_flow_rate: 8 + thermal_efficiency: 0.5 + thermal_power: 684 + wind_speed: 1.63 + +- ambient_temperature: 22.2 + average_bulk_water_temperature: 17.85 + collector_input_temperature: 45 + collector_temperature_gain: 5.7 + irradiance: 983 + mass_flow_rate: 8 + thermal_efficiency: 0.5 + thermal_power: 684 + wind_speed: 1.63