From 343cb0e1388d5f360ab8e1b7f073a98a87d38f7f Mon Sep 17 00:00:00 2001 From: Normann Date: Tue, 24 Dec 2024 13:11:15 +0100 Subject: [PATCH] backport visualize v3 (#279) * backport visualize v3 * test backport * compare file * old test files removed --- src/akkudoktoreos/optimization/genetic.py | 30 +- src/akkudoktoreos/utils/visualize.py | 510 ++++++++++++++++++ src/akkudoktoreos/visualize.py | 350 ------------ tests/test_class_optimize.py | 17 +- tests/test_visualize.py | 56 +- .../images/visualize_base_output_1.pdf | Bin 43879 -> 0 bytes tests/testdata/test_example_report.pdf | Bin 0 -> 37237 bytes 7 files changed, 566 insertions(+), 397 deletions(-) create mode 100644 src/akkudoktoreos/utils/visualize.py delete mode 100644 src/akkudoktoreos/visualize.py delete mode 100644 tests/testdata/images/visualize_base_output_1.pdf create mode 100644 tests/testdata/test_example_report.pdf diff --git a/src/akkudoktoreos/optimization/genetic.py b/src/akkudoktoreos/optimization/genetic.py index f3135ee..85be8f3 100644 --- a/src/akkudoktoreos/optimization/genetic.py +++ b/src/akkudoktoreos/optimization/genetic.py @@ -26,7 +26,6 @@ from akkudoktoreos.prediction.self_consumption_probability import ( self_consumption_probability_interpolator, ) from akkudoktoreos.utils.utils import NumpyEncoder -from akkudoktoreos.visualize import visualisiere_ergebnisse class OptimizationParameters(BaseModel): @@ -596,20 +595,23 @@ class optimization_problem: ac_charge, dc_charge, discharge = self.decode_charge_discharge(discharge_hours_bin) # Visualize the results - visualisiere_ergebnisse( - parameters.ems.gesamtlast, - parameters.ems.pv_prognose_wh, - parameters.ems.strompreis_euro_pro_wh, - o, - ac_charge, - dc_charge, - discharge, - parameters.temperature_forecast, - start_hour, - einspeiseverguetung_euro_pro_wh, - config=self._config, - extra_data=extra_data, + from akkudoktoreos.utils.visualize import ( # import here to prevent circular import + prepare_visualize, ) + + visualize = { + "ac_charge": ac_charge.tolist(), + "dc_charge": dc_charge.tolist(), + "discharge_allowed": discharge.tolist(), + "eautocharge_hours_float": eautocharge_hours_float, + "result": o, + "eauto_obj": ems.ev, + "start_solution": start_solution, + "spuelstart": washingstart_int, + "extra_data": extra_data, + } + + prepare_visualize(parameters, visualize, config=self._config, start_hour=start_hour) return OptimizeResponse( **{ "ac_charge": ac_charge, diff --git a/src/akkudoktoreos/utils/visualize.py b/src/akkudoktoreos/utils/visualize.py new file mode 100644 index 0000000..c036a0d --- /dev/null +++ b/src/akkudoktoreos/utils/visualize.py @@ -0,0 +1,510 @@ +import os +from collections.abc import Sequence +from typing import Callable, Optional, Union + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.backends.backend_pdf import PdfPages + +from akkudoktoreos.config import AppConfig +from akkudoktoreos.optimization.genetic import OptimizationParameters + + +class VisualizationReport: + def __init__(self, config: AppConfig, filename: str = "visualization_results.pdf") -> None: + # Initialize the report with a given filename and empty groups + self.filename = filename + self.groups: list[list[Callable[[], None]]] = [] # Store groups of charts + self.current_group: list[ + Callable[[], None] + ] = [] # Store current group of charts being created + self.config = config + self.pdf_pages = PdfPages(filename, metadata={}) # Initialize PdfPages without metadata + + def add_chart_to_group(self, chart_func: Callable[[], None]) -> None: + """Add a chart function to the current group.""" + self.current_group.append(chart_func) + + def finalize_group(self) -> None: + """Finalize the current group and prepare for a new group.""" + if self.current_group: # Check if current group has charts + self.groups.append(self.current_group) # Add current group to groups + else: + print("Finalizing an empty group!") # Warn if group is empty + self.current_group = [] # Reset current group for new charts + + def _initialize_pdf(self) -> None: + """Create the output directory if it doesn't exist and initialize the PDF.""" + output_dir = self.config.working_dir / self.config.directories.output + + # If self.filename is already a valid path, use it; otherwise, combine it with output_dir + if os.path.isabs(self.filename): + output_file = self.filename + else: + output_dir.mkdir(parents=True, exist_ok=True) + output_file = os.path.join(output_dir, self.filename) + + self.pdf_pages = PdfPages( + output_file, metadata={} + ) # Re-initialize PdfPages without metadata + + def _save_group_to_pdf(self, group: list[Callable[[], None]]) -> None: + """Save a group of charts to the PDF.""" + fig_count = len(group) # Number of charts in the group + if fig_count == 0: + print("Attempted to save an empty group to PDF!") # Warn if group is empty + return # Prevent saving an empty group + + # Create a figure layout based on the number of charts + if fig_count == 3: + # Layout for three charts: 1 full-width on top, 2 below + fig = plt.figure(figsize=(14, 10)) # Set a larger figure size + ax1 = fig.add_subplot(2, 1, 1) # Full-width subplot + ax2 = fig.add_subplot(2, 2, 3) # Bottom left subplot + ax3 = fig.add_subplot(2, 2, 4) # Bottom right subplot + + # Store axes in a list for easy access + axs = [ax1, ax2, ax3] + else: + # Dynamic layout for any other number of charts + cols = 2 if fig_count > 1 else 1 # Determine number of columns + rows = (fig_count // 2) + (fig_count % 2) # Calculate required rows + fig, axs = plt.subplots(rows, cols, figsize=(14, 7 * rows)) # Create subplots + # If axs is a 2D array of axes, flatten it into a 1D list + # if isinstance(axs, np.ndarray): + axs = list(np.array(axs).reshape(-1)) + + # Draw each chart in the corresponding axes + for idx, chart_func in enumerate(group): + plt.sca(axs[idx]) # Set current axes + chart_func() # Call the chart function to draw + + # Hide any unused axes + for idx in range(fig_count, len(axs)): + axs[idx].set_visible(False) # Hide unused axes + self.pdf_pages.savefig(fig) # Save the figure to the PDF + + plt.close(fig) # Close the figure to free up memory + + def create_line_chart( + self, + start_hour: Optional[int], + y_list: list[Union[np.ndarray, list[float]]], + title: str, + xlabel: str, + ylabel: str, + labels: Optional[list[str]] = None, + markers: Optional[list[str]] = None, + line_styles: Optional[list[str]] = None, + ) -> None: + """Create a line chart and add it to the current group.""" + + def chart() -> None: + nonlocal start_hour # Allow modifying `x` within the nested function + if start_hour is None: + start_hour = 0 + first_element = y_list[0] + x: np.ndarray + # Case 1: y_list contains np.ndarray elements + if isinstance(first_element, np.ndarray): + x = np.arange( + start_hour, start_hour + len(first_element) + ) # Start at x and extend by ndarray length + # Case 2: y_list contains float elements (1D list) + elif isinstance(first_element, float): + x = np.arange( + start_hour, start_hour + len(y_list) + ) # Start at x and extend by list length + # Case 3: y_list is a nested list of floats + elif isinstance(first_element, list) and all( + isinstance(i, float) for i in first_element + ): + max_len = max(len(sublist) for sublist in y_list) + x = np.arange( + start_hour, start_hour + max_len + ) # Start at x and extend by max sublist length + else: + print(f"Unsupported y_list structure: {type(y_list)}, {y_list}") + raise TypeError( + "y_list elements must be np.ndarray, float, or a nested list of floats" + ) + + for idx, y_data in enumerate(y_list): + label = labels[idx] if labels else None # Chart label + marker = markers[idx] if markers and idx < len(markers) else "o" # Marker style + line_style = ( + line_styles[idx] if line_styles and idx < len(line_styles) else "-" + ) # Line style + plt.plot(x, y_data, label=label, marker=marker, linestyle=line_style) # Plot line + plt.title(title) # Set title + plt.xlabel(xlabel) # Set x-axis label + plt.ylabel(ylabel) # Set y-axis label + if labels: + plt.legend() # Show legend if labels are provided + plt.grid(True) # Show grid + plt.xlim(x[0] - 0.5, x[-1] + 0.5) # Adjust x-limits + + self.add_chart_to_group(chart) # Add chart function to current group + + def create_scatter_plot( + self, + x: np.ndarray, + y: np.ndarray, + title: str, + xlabel: str, + ylabel: str, + c: Optional[np.ndarray] = None, + ) -> None: + """Create a scatter plot and add it to the current group.""" + + def chart() -> None: + scatter = plt.scatter(x, y, c=c, cmap="viridis") # Create scatter plot + plt.title(title) # Set title + plt.xlabel(xlabel) # Set x-axis label + plt.ylabel(ylabel) # Set y-axis label + if c is not None: + plt.colorbar(scatter, label="Constraint") # Add colorbar if color data is provided + plt.grid(True) # Show grid + + self.add_chart_to_group(chart) # Add chart function to current group + + def create_bar_chart( + self, + labels: list[str], + values_list: Sequence[Union[int, float, list[Union[int, float]]]], + title: str, + ylabel: str, + xlabels: Optional[list[str]] = None, + label_names: Optional[list[str]] = None, + colors: Optional[list[str]] = None, + bar_width: float = 0.35, + bottom: Optional[int] = None, + ) -> None: + """Create a bar chart and add it to the current group.""" + + def chart() -> None: + num_groups = len(values_list) # Number of data groups + num_bars = len(labels) # Number of bars (categories) + # Calculate the positions for each bar group on the x-axis + x = np.arange(num_bars) # x positions for bars + offset = np.linspace( + -bar_width * (num_groups - 1) / 2, bar_width * (num_groups - 1) / 2, num_groups + ) # Bar offsets + for i, values in enumerate(values_list): + bottom_use = None + if bottom == i + 1: # Set bottom if specified + bottom_use = 1 + color = colors[i] if colors and i < len(colors) else None # Bar color + label_name = label_names[i] if label_names else None # Bar label + plt.bar( + x + offset[i], + values, + bar_width, + label=label_name, + color=color, + zorder=2, + alpha=0.6, + bottom=bottom_use, + ) # Create bar + if xlabels: + plt.xticks(x, labels) # Add custom labels to the x-axis + plt.title(title) # Set title + plt.ylabel(ylabel) # Set y-axis label + + if colors and label_names: + plt.legend() # Show legend if colors are provided + plt.grid(True, zorder=0) # Show grid in the background + plt.xlim(-0.5, len(labels) - 0.5) # Set x-axis limits + + self.add_chart_to_group(chart) # Add chart function to current group + + def create_violin_plot( + self, data_list: list[np.ndarray], labels: list[str], title: str, xlabel: str, ylabel: str + ) -> None: + """Create a violin plot and add it to the current group.""" + + def chart() -> None: + plt.violinplot(data_list, showmeans=True, showmedians=True) # Create violin plot + plt.xticks(np.arange(1, len(labels) + 1), labels) # Set x-ticks and labels + plt.title(title) # Set title + plt.xlabel(xlabel) # Set x-axis label + plt.ylabel(ylabel) # Set y-axis label + plt.grid(True) # Show grid + + self.add_chart_to_group(chart) # Add chart function to current group + + def generate_pdf(self) -> None: + """Generate the PDF report with all the added chart groups.""" + self._initialize_pdf() # Initialize the PDF + + for group in self.groups: + self._save_group_to_pdf(group) # Save each group to the PDF + + self.pdf_pages.close() # Close the PDF to finalize the report + + +def prepare_visualize( + parameters: OptimizationParameters, + results: dict, + config: AppConfig, + filename: str = "visualization_results_new.pdf", + start_hour: Optional[int] = 0, +) -> None: + report = VisualizationReport(config, filename) + # Group 1: + report.create_line_chart( + None, + [parameters.ems.gesamtlast], + title="Load Profile", + xlabel="Hours", + ylabel="Load (Wh)", + labels=["Total Load (Wh)"], + markers=["s"], + line_styles=["-"], + ) + report.create_line_chart( + None, + [parameters.ems.pv_prognose_wh], + title="PV Forecast", + xlabel="Hours", + ylabel="PV Generation (Wh)", + ) + + report.create_line_chart( + None, + [np.full(len(parameters.ems.gesamtlast), parameters.ems.einspeiseverguetung_euro_pro_wh)], + title="Remuneration", + xlabel="Hours", + ylabel="€/Wh", + ) + if parameters.temperature_forecast: + report.create_line_chart( + None, + [parameters.temperature_forecast], + title="Temperature Forecast", + xlabel="Hours", + ylabel="°C", + ) + report.finalize_group() + + # Group 2: + report.create_line_chart( + start_hour, + [ + results["result"]["Last_Wh_pro_Stunde"], + results["result"]["Home_appliance_wh_per_hour"], + results["result"]["Netzeinspeisung_Wh_pro_Stunde"], + results["result"]["Netzbezug_Wh_pro_Stunde"], + results["result"]["Verluste_Pro_Stunde"], + ], + title="Energy Flow per Hour", + xlabel="Hours", + ylabel="Energy (Wh)", + labels=[ + "Load (Wh)", + "Household Device (Wh)", + "Grid Feed-in (Wh)", + "Grid Consumption (Wh)", + "Losses (Wh)", + ], + markers=["o", "o", "x", "^", "^"], + line_styles=["-", "--", ":", "-.", "-"], + ) + report.finalize_group() + + # Group 3: + report.create_line_chart( + start_hour, + [results["result"]["akku_soc_pro_stunde"], results["result"]["EAuto_SoC_pro_Stunde"]], + title="Battery SOC", + xlabel="Hours", + ylabel="%", + labels=[ + "Battery SOC (%)", + "Electric Vehicle SOC (%)", + ], + markers=["o", "x"], + ) + report.create_line_chart( + None, + [parameters.ems.strompreis_euro_pro_wh], + title="Electricity Price", + xlabel="Hours", + ylabel="Price (€/Wh)", + ) + + report.create_bar_chart( + list(str(i) for i in range(len(results["ac_charge"]))), + [results["ac_charge"], results["dc_charge"], results["discharge_allowed"]], + title="AC/DC Charging and Discharge Overview", + ylabel="Relative Power (0-1) / Discharge (0 or 1)", + label_names=["AC Charging (relative)", "DC Charging (relative)", "Discharge Allowed"], + colors=["blue", "green", "red"], + bottom=3, + ) + report.finalize_group() + + # Group 4: + + report.create_line_chart( + start_hour, + [ + results["result"]["Kosten_Euro_pro_Stunde"], + results["result"]["Einnahmen_Euro_pro_Stunde"], + ], + title="Financial Balance per Hour", + xlabel="Hours", + ylabel="Euro", + labels=["Costs", "Revenue"], + ) + + extra_data = results["extra_data"] + report.create_scatter_plot( + extra_data["verluste"], + extra_data["bilanz"], + title="", + xlabel="losses", + ylabel="balance", + c=extra_data["nebenbedingung"], + ) + + # Example usage + values_list = [ + [ + results["result"]["Gesamtkosten_Euro"], + results["result"]["Gesamteinnahmen_Euro"], + results["result"]["Gesamtbilanz_Euro"], + ] + ] + labels = ["Total Costs [€]", "Total Revenue [€]", "Total Balance [€]"] + + report.create_bar_chart( + labels=labels, + values_list=values_list, + title="Financial Overview", + ylabel="Euro", + xlabels=["Total Costs [€]", "Total Revenue [€]", "Total Balance [€]"], + ) + + report.finalize_group() + + # Group 1: Scatter plot of losses vs balance with color-coded constraints + f1 = np.array(extra_data["verluste"]) # Losses + f2 = np.array(extra_data["bilanz"]) # Balance + n1 = np.array(extra_data["nebenbedingung"]) # Constraints + + # Filter data where 'nebenbedingung' < 0.01 + filtered_indices = n1 < 0.01 + filtered_losses = f1[filtered_indices] + filtered_balance = f2[filtered_indices] + + # Group 2: Violin plot for filtered losses + if filtered_losses.size > 0: + report.create_violin_plot( + data_list=[filtered_losses], # Data for filtered losses + labels=["Filtered Losses"], # Label for the violin plot + title="Violin Plot for Filtered Losses (Constraint < 0.01)", + xlabel="Losses", + ylabel="Values", + ) + else: + print("No data available for filtered losses violin plot (Constraint < 0.01)") + + # Group 3: Violin plot for filtered balance + if filtered_balance.size > 0: + report.create_violin_plot( + data_list=[filtered_balance], # Data for filtered balance + labels=["Filtered Balance"], # Label for the violin plot + title="Violin Plot for Filtered Balance (Constraint < 0.01)", + xlabel="Balance", + ylabel="Values", + ) + else: + print("No data available for filtered balance violin plot (Constraint < 0.01)") + + if filtered_balance.size > 0 or filtered_losses.size > 0: + report.finalize_group() + + # Generate the PDF report + report.generate_pdf() + + +if __name__ == "__main__": + # Example usage + from akkudoktoreos.config import get_working_dir, load_config + + working_dir = get_working_dir() + config = load_config(working_dir) + report = VisualizationReport(config=config, filename="example_report.pdf") + x_hours = 0 # Define x-axis start values (e.g., hours) + + # Group 1: Adding charts to be displayed on the same page + report.create_line_chart( + x_hours, + [np.array([10, 20, 30, 40])], + title="Load Profile", + xlabel="Hours", + ylabel="Load (Wh)", + ) + report.create_line_chart( + x_hours, + [np.array([5, 15, 25, 35])], + title="PV Forecast", + xlabel="Hours", + ylabel="PV Generation (Wh)", + ) + report.create_line_chart( + x_hours, + [np.array([5, 15, 25, 35])], + title="PV Forecast", + xlabel="Hours", + ylabel="PV Generation (Wh)", + ) + # Note: If there are only 3 charts per page, the first is as wide as the page + + report.finalize_group() # Finalize the first group of charts + + # Group 2: Adding more charts to be displayed on another page + report.create_line_chart( + x_hours, + [np.array([0.2, 0.25, 0.3, 0.35])], + title="Electricity Price", + xlabel="Hours", + ylabel="Price (€/Wh)", + ) + report.create_bar_chart( + ["Costs", "Revenue", "Balance"], + [[500.0], [600.0], [100.0]], + title="Financial Overview", + ylabel="Euro", + label_names=["AC Charging (relative)", "DC Charging (relative)", "Discharge Allowed"], + colors=["red", "green", "blue"], + ) + report.create_scatter_plot( + np.array([5, 6, 7, 8]), + np.array([100, 200, 150, 250]), + title="Scatter Plot", + xlabel="Losses", + ylabel="Balance", + c=np.array([0.1, 0.2, 0.3, 0.4]), + ) + report.finalize_group() # Finalize the second group of charts + + # Group 3: Adding a violin plot + data = [np.random.normal(0, std, 100) for std in range(1, 5)] # Example data for violin plot + report.create_violin_plot( + data, + labels=["Group 1", "Group 2", "Group 3", "Group 4"], + title="Violin Plot", + xlabel="Groups", + ylabel="Values", + ) + data = [np.random.normal(0, 1, 100)] # Example data for violin plot + report.create_violin_plot( + data, labels=["Group 1"], title="Violin Plot", xlabel="Group", ylabel="Values" + ) + + report.finalize_group() # Finalize the third group of charts + + # Generate the PDF report + report.generate_pdf() diff --git a/src/akkudoktoreos/visualize.py b/src/akkudoktoreos/visualize.py deleted file mode 100644 index 4046915..0000000 --- a/src/akkudoktoreos/visualize.py +++ /dev/null @@ -1,350 +0,0 @@ -# Set the backend for matplotlib to Agg -from typing import Any, Optional - -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.backends.backend_pdf import PdfPages - -from akkudoktoreos.config import AppConfig, SetupIncomplete - -matplotlib.use("Agg") - - -def visualisiere_ergebnisse( - gesamtlast: list[float], - pv_forecast: list[float], - strompreise: list[float], - ergebnisse: dict[str, Any], - ac: np.ndarray, # AC charging allowed - dc: np.ndarray, # DC charging allowed - discharge: np.ndarray, # Discharge allowed - temperature: Optional[list[float]], - start_hour: int, - einspeiseverguetung_euro_pro_wh: np.ndarray, - config: AppConfig, - filename: str = "visualization_results.pdf", - extra_data: Optional[dict[str, Any]] = None, -) -> None: - ##################### - # 24-hour visualization - ##################### - output_dir = config.working_dir / config.directories.output - if not output_dir.is_dir(): - raise SetupIncomplete(f"Output path does not exist: {output_dir}.") - - output_file = output_dir.joinpath(filename) - with PdfPages(output_file) as pdf: - # Load and PV generation - plt.figure(figsize=(14, 14)) - plt.subplot(3, 3, 1) - hours = np.arange(0, config.eos.prediction_hours) - - gesamtlast_array = np.array(gesamtlast) - # Plot individual loads - plt.plot(hours, gesamtlast_array, label="Load (Wh)", marker="o") - - # Calculate and plot total load - plt.plot( - hours, - gesamtlast_array, - label="Total Load (Wh)", - marker="o", - linewidth=2, - linestyle="--", - ) - plt.xlabel("Hour") - plt.ylabel("Load (Wh)") - plt.title("Load Profiles") - plt.grid(True) - plt.legend() - - # PV forecast - plt.subplot(3, 2, 3) - plt.plot(hours, pv_forecast, label="PV Generation (Wh)", marker="x") - plt.title("PV Forecast") - plt.xlabel("Hour of the Day") - plt.ylabel("Wh") - plt.legend() - plt.grid(True) - - # Feed-in remuneration - plt.subplot(3, 2, 4) - plt.plot( - hours, - einspeiseverguetung_euro_pro_wh, - label="Remuneration (€/Wh)", - marker="x", - ) - plt.title("Remuneration") - plt.xlabel("Hour of the Day") - plt.ylabel("€/Wh") - plt.legend() - plt.grid(True) - - # Temperature forecast - if temperature is not None: - plt.subplot(3, 2, 5) - plt.title("Temperature Forecast (°C)") - plt.plot(hours, temperature, label="Temperature (°C)", marker="x") - plt.xlabel("Hour of the Day") - plt.ylabel("°C") - plt.legend() - plt.grid(True) - - pdf.savefig() # Save the current figure state to the PDF - plt.close() # Close the current figure to free up memory - - ##################### - # Start hour visualization - ##################### - - plt.figure(figsize=(14, 10)) - hours = np.arange(start_hour, config.eos.prediction_hours) - - # Energy flow, grid feed-in, and grid consumption - plt.subplot(3, 2, 1) - # Plot with transparency (alpha) and different linestyles - plt.plot( - hours, - ergebnisse["Last_Wh_pro_Stunde"], - label="Load (Wh)", - marker="o", - linestyle="-", - alpha=0.8, - ) - plt.plot( - hours, - ergebnisse["Home_appliance_wh_per_hour"], - label="Household Device (Wh)", - marker="o", - linestyle="--", - alpha=0.8, - ) - plt.plot( - hours, - ergebnisse["Netzeinspeisung_Wh_pro_Stunde"], - label="Grid Feed-in (Wh)", - marker="x", - linestyle=":", - alpha=0.8, - ) - plt.plot( - hours, - ergebnisse["Netzbezug_Wh_pro_Stunde"], - label="Grid Consumption (Wh)", - marker="^", - linestyle="-.", - alpha=0.8, - ) - plt.plot( - hours, - ergebnisse["Verluste_Pro_Stunde"], - label="Losses (Wh)", - marker="^", - linestyle="-", - alpha=0.8, - ) - - # Title and labels - plt.title("Energy Flow per Hour") - plt.xlabel("Hour") - plt.ylabel("Energy (Wh)") - - # Show legend with a higher number of columns to avoid overlap - plt.legend(ncol=2) - - # Electricity prices - hours_p = np.arange(0, len(strompreise)) - plt.subplot(3, 2, 3) - plt.plot( - hours_p, - strompreise, - label="Electricity Price (€/Wh)", - color="purple", - marker="s", - ) - plt.title("Electricity Prices") - plt.xlabel("Hour of the Day") - plt.ylabel("Price (€/Wh)") - plt.legend() - plt.grid(True) - - # State of charge for batteries - plt.subplot(3, 2, 2) - plt.plot(hours, ergebnisse["akku_soc_pro_stunde"], label="PV Battery (%)", marker="x") - plt.plot( - hours, - ergebnisse["EAuto_SoC_pro_Stunde"], - label="E-Car Battery (%)", - marker="x", - ) - plt.legend(loc="upper left", bbox_to_anchor=(1, 1)) # Place legend outside the plot - plt.grid(True, which="both", axis="x") # Grid for every hour - - # Plot for AC, DC charging, and Discharge status using bar charts - ax1 = plt.subplot(3, 2, 5) - hours = np.arange(0, config.eos.prediction_hours) - # Plot AC charging as bars (relative values between 0 and 1) - plt.bar(hours, ac, width=0.4, label="AC Charging (relative)", color="blue", alpha=0.6) - - # Plot DC charging as bars (relative values between 0 and 1) - plt.bar( - hours + 0.4, dc, width=0.4, label="DC Charging (relative)", color="green", alpha=0.6 - ) - - # Plot Discharge as bars (0 or 1, binary values) - plt.bar( - hours, - discharge, - width=0.4, - label="Discharge Allowed", - color="red", - alpha=0.6, - bottom=np.maximum(ac, dc), - ) - - # Configure the plot - ax1.legend(loc="upper left") - ax1.set_xlim(0, config.eos.prediction_hours) - ax1.set_xlabel("Hour") - ax1.set_ylabel("Relative Power (0-1) / Discharge (0 or 1)") - ax1.set_title("AC/DC Charging and Discharge Overview") - ax1.grid(True) - - hours = np.arange(start_hour, config.eos.prediction_hours) - - pdf.savefig() # Save the current figure state to the PDF - plt.close() # Close the current figure to free up memory - - # Financial overview - fig, axs = plt.subplots(1, 2, figsize=(14, 10)) # Create a 1x2 grid of subplots - total_costs = ergebnisse["Gesamtkosten_Euro"] - total_revenue = ergebnisse["Gesamteinnahmen_Euro"] - total_balance = ergebnisse["Gesamtbilanz_Euro"] - losses = ergebnisse["Gesamt_Verluste"] - - # Costs and revenues per hour on the first axis (axs[0]) - costs = ergebnisse["Kosten_Euro_pro_Stunde"] - revenues = ergebnisse["Einnahmen_Euro_pro_Stunde"] - - # Plot costs - axs[0].plot( - hours, - costs, - label="Costs (Euro)", - marker="o", - color="red", - ) - # Annotate costs - for hour, value in enumerate(costs): - if value is None or np.isnan(value): - value = 0 - axs[0].annotate( - f"{value:.2f}", - (hour, value), - textcoords="offset points", - xytext=(0, 5), - ha="center", - fontsize=8, - color="red", - ) - - # Plot revenues - axs[0].plot( - hours, - revenues, - label="Revenue (Euro)", - marker="x", - color="green", - ) - # Annotate revenues - for hour, value in enumerate(revenues): - if value is None or np.isnan(value): - value = 0 - axs[0].annotate( - f"{value:.2f}", - (hour, value), - textcoords="offset points", - xytext=(0, 5), - ha="center", - fontsize=8, - color="green", - ) - - # Title and labels - axs[0].set_title("Financial Balance per Hour") - axs[0].set_xlabel("Hour") - axs[0].set_ylabel("Euro") - axs[0].legend() - axs[0].grid(True) - - # Summary of finances on the second axis (axs[1]) - labels = ["Total Costs [€]", "Total Revenue [€]", "Total Balance [€]"] - values = [total_costs, total_revenue, total_balance] - colors = ["red" if value > 0 else "green" for value in values] - axs[1].bar(labels, values, color=colors) - axs[1].set_title("Financial Overview") - axs[1].set_ylabel("Euro") - - # Second axis (ax2) for losses, shared with axs[1] - ax2 = axs[1].twinx() - ax2.bar("Total Losses", losses, color="blue") - ax2.set_ylabel("Losses [Wh]", color="blue") - ax2.tick_params(axis="y", labelcolor="blue") - - pdf.savefig() # Save the complete figure to the PDF - plt.close() # Close the figure - - # Additional data visualization if provided - if extra_data is not None: - plt.figure(figsize=(14, 10)) - plt.subplot(1, 2, 1) - f1 = np.array(extra_data["verluste"]) - f2 = np.array(extra_data["bilanz"]) - n1 = np.array(extra_data["nebenbedingung"]) - scatter = plt.scatter(f1, f2, c=n1, cmap="viridis") - - # Add color legend - plt.colorbar(scatter, label="Constraint") - - pdf.savefig() # Save the complete figure to the PDF - plt.close() # Close the figure - - plt.figure(figsize=(14, 10)) - filtered_losses = np.array( - [ - v - for v, n in zip(extra_data["verluste"], extra_data["nebenbedingung"]) - if n < 0.01 - ] - ) - filtered_balance = np.array( - [b for b, n in zip(extra_data["bilanz"], extra_data["nebenbedingung"]) if n < 0.01] - ) - if filtered_losses.size != 0: - best_loss = min(filtered_losses) - worst_loss = max(filtered_losses) - best_balance = min(filtered_balance) - worst_balance = max(filtered_balance) - - data = [filtered_losses, filtered_balance] - labels = ["Losses", "Balance"] - # Create plots - fig, axs = plt.subplots( - 1, 2, figsize=(10, 6), sharey=False - ) # Two subplots, separate y-axes - - # First violin plot for losses - axs[0].violinplot(data[0], positions=[1], showmeans=True, showmedians=True) - axs[0].set(xticks=[1], xticklabels=["Losses"]) - - # Second violin plot for balance - axs[1].violinplot(data[1], positions=[1], showmeans=True, showmedians=True) - axs[1].set(xticks=[1], xticklabels=["Balance"]) - - # Fine-tuning - plt.tight_layout() - - pdf.savefig() # Save the current figure state to the PDF - plt.close() # Close the figure diff --git a/tests/test_class_optimize.py b/tests/test_class_optimize.py index 4a9d808..041a3ac 100644 --- a/tests/test_class_optimize.py +++ b/tests/test_class_optimize.py @@ -11,6 +11,7 @@ from akkudoktoreos.optimization.genetic import ( OptimizeResponse, optimization_problem, ) +from akkudoktoreos.utils.visualize import prepare_visualize DIR_TESTDATA = Path(__file__).parent / "testdata" @@ -67,16 +68,12 @@ def test_optimize( visualize_filename = str((DIR_TESTDATA / f"new_{fn_out}").with_suffix(".pdf")) - def visualize_to_file(*args, **kwargs): - from akkudoktoreos.visualize import visualisiere_ergebnisse - - # Write test output pdf to file, so we can look at it manually - kwargs["filename"] = visualize_filename - return visualisiere_ergebnisse(*args, **kwargs) - with patch( - "akkudoktoreos.optimization.genetic.visualisiere_ergebnisse", side_effect=visualize_to_file - ) as visualisiere_ergebnisse_patch: + "akkudoktoreos.utils.visualize.prepare_visualize", + side_effect=lambda parameters, results, *args, **kwargs: prepare_visualize( + parameters, results, filename=visualize_filename, **kwargs + ), + ) as prepare_visualize_patch: # Call the optimization function ergebnis = opt_class.optimierung_ems( parameters=input_data, start_hour=start_hour, ngen=ngen @@ -95,4 +92,4 @@ def test_optimize( compare_dict(ergebnis.model_dump(), expected_result.model_dump()) # The function creates a visualization result PDF as a side-effect. - visualisiere_ergebnisse_patch.assert_called_once() + prepare_visualize_patch.assert_called_once() diff --git a/tests/test_visualize.py b/tests/test_visualize.py index 3cbd6d3..81f3845 100644 --- a/tests/test_visualize.py +++ b/tests/test_visualize.py @@ -1,32 +1,42 @@ -import json +import os +import subprocess from pathlib import Path -import pytest from matplotlib.testing.compare import compare_images -from akkudoktoreos.config import AppConfig -from akkudoktoreos.visualize import visualisiere_ergebnisse +from akkudoktoreos.config import get_working_dir, load_config + +filename = "example_report.pdf" + +working_dir = get_working_dir() +config = load_config(working_dir) +output_dir = config.working_dir / config.directories.output + +# If self.filename is already a valid path, use it; otherwise, combine it with output_dir +if os.path.isabs(filename): + output_file = filename +else: + output_dir.mkdir(parents=True, exist_ok=True) + output_file = os.path.join(output_dir, filename) DIR_TESTDATA = Path(__file__).parent / "testdata" -DIR_IMAGEDATA = DIR_TESTDATA / "images" +reference_file = DIR_TESTDATA / "test_example_report.pdf" -@pytest.mark.parametrize( - "fn_in, fn_out, fn_out_base", - [("visualize_input_1.json", "visualize_output_1.pdf", "visualize_base_output_1.pdf")], -) -def test_visualisiere_ergebnisse(fn_in, fn_out, fn_out_base, tmp_config: AppConfig): - with open(DIR_TESTDATA / fn_in, "r") as f: - input_data = json.load(f) - visualisiere_ergebnisse(config=tmp_config, **input_data) - output_file: Path = tmp_config.working_dir / tmp_config.directories.output / fn_out +def test_generate_pdf_main(): + # Delete the old generated file if it exists + if os.path.isfile(output_file): + os.remove(output_file) - assert output_file.is_file() - assert ( - compare_images( - str(output_file), - str(DIR_IMAGEDATA / fn_out_base), - 0, - ) - is None - ) + # Execute the __main__ block of visualize.py by running it as a script + script_path = Path(__file__).parent.parent / "src" / "akkudoktoreos" / "utils" / "visualize.py" + subprocess.run(["python", str(script_path)], check=True) + + # Check if the file exists + assert os.path.isfile(output_file) + + # Compare the generated file with the reference file + comparison = compare_images(str(reference_file), str(output_file), tol=0) + + # Assert that there are no differences + assert comparison is None, f"Images differ: {comparison}" diff --git a/tests/testdata/images/visualize_base_output_1.pdf b/tests/testdata/images/visualize_base_output_1.pdf deleted file mode 100644 index c607cc494a55b5ce88dffd8212272d6a8a9202bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43879 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 Djn_h4 diff --git a/tests/testdata/test_example_report.pdf b/tests/testdata/test_example_report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ef05327ffd12659944e6f0dbd1f1b4c0fd7722b6 GIT binary patch literal 37237 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 AEC2ui literal 0 HcmV?d00001