"""
Sensitivity Tab Module
======================
:author: Dipl.-Ing. (FH) Jonas Pfeiffer
Performing sensitivity analysis on heat generation costs based on varying parameters, with 3D visualization.
"""
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt6.QtCore import pyqtSignal
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QWidget
from scipy.interpolate import griddata
[docs]
class SensitivityTab(QWidget):
"""
A QWidget subclass representing the SensitivityTab.
Attributes:
data_added (pyqtSignal): A signal that emits data as an object.
data_manager (DataManager): An instance of the DataManager class for managing data.
parent (QWidget): The parent widget.
results (dict): A dictionary to store results.
"""
data_added = pyqtSignal(object) # Signal, das Daten als Objekt überträgt
[docs]
def __init__(self, data_manager, parent=None):
"""
Initializes the SensitivityTab.
:param data_manager: The data manager
:type data_manager: DataManager
:param parent: The parent widget
:type parent: QWidget or None
"""
super().__init__(parent)
self.data_manager = data_manager
self.parent = parent
self.results = {}
self.data_manager.project_folder_changed.connect(self.updateDefaultPath)
self.updateDefaultPath(self.data_manager.variant_folder)
self.initUI()
[docs]
def updateDefaultPath(self, new_base_path):
"""
Updates the default base path.
:param new_base_path: The new base path
:type new_base_path: str
"""
self.base_path = new_base_path
[docs]
def initUI(self):
"""
Initializes the UI components of the SensitivityTab.
"""
self.mainLayout = QVBoxLayout(self)
self.createInputFields()
self.createPlotArea()
self.setLayout(self.mainLayout)
[docs]
def createPlotArea(self):
"""
Creates the plot area for displaying graphs.
"""
self.figure = Figure(figsize=(10, 8))
self.canvas = FigureCanvas(self.figure)
self.canvas.setMinimumSize(500, 500)
self.mainLayout.addWidget(self.canvas)
[docs]
def start_sensitivity_analysis(self):
"""
Starts the sensitivity analysis based on the input ranges.
"""
gas_range = self.parse_range(self.gasPriceRange)
electricity_range = self.parse_range(self.electricityPriceRange)
wood_range = self.parse_range(self.woodPriceRange)
if gas_range and electricity_range and wood_range:
self.parent.sensitivity(gas_range, electricity_range, wood_range)
[docs]
def parse_range(self, layout):
"""
Parses the range input fields and returns the range values.
:param layout: The layout containing the input fields
:type layout: QVBoxLayout
:return: The lower limit, upper limit, and number of points if valid, otherwise None
:rtype: tuple or None
"""
try:
lower = float(layout.itemAt(1).itemAt(1).widget().text())
upper = float(layout.itemAt(1).itemAt(3).widget().text())
num_points = int(layout.itemAt(1).itemAt(5).widget().text())
if num_points <= 0:
raise ValueError("Die Anzahl der Punkte muss größer als 0 sein.")
return lower, upper, num_points
except ValueError:
QMessageBox.warning(
self, "Ungültiger Bereich", "Bitte geben Sie einen gültigen Bereich ein (Format: von, bis, Anzahl)."
)
return None
[docs]
def plotSensitivity(self, results):
"""
Plots the sensitivity analysis results.
:param results: The results of the sensitivity analysis
:type results: list
"""
self.figure.clear()
ax = self.figure.add_subplot(111, projection="3d")
gas_prices = [res["gas_price"] for res in results]
electricity_prices = [res["electricity_price"] for res in results]
wood_prices = [res["wood_price"] for res in results]
wgk = [res["WGK_Gesamt"] + res["wgk_heat_pump_electricity"] for res in results]
sc = ax.scatter(gas_prices, electricity_prices, wood_prices, c=wgk, cmap="viridis", marker="o")
ax.set_xlabel("Gaspreis (€/MWh)")
ax.set_ylabel("Strompreis (€/MWh)")
ax.set_zlabel("Holzpreis (€/MWh)")
ax.set_title("Gesamtwärmegestehungskosten (€/MWh)")
cbar = plt.colorbar(sc, ax=ax, pad=0.1)
cbar.set_label("Wärmegestehungskosten (€/MWh)")
self.canvas.draw()
[docs]
def plotSensitivitySurface(self, results):
"""
Plots the sensitivity analysis results as a surface plot.
:param results: The results of the sensitivity analysis
:type results: list
"""
self.figure.clear()
ax = self.figure.add_subplot(111, projection="3d")
wood_prices = np.array([res["wood_price"] for res in results])
unique_wood_prices = np.unique(wood_prices)
colors = plt.cm.viridis(np.linspace(0, 1, len(unique_wood_prices)))
for i, wood_price in enumerate(unique_wood_prices):
subset_results = [res for res in results if res["wood_price"] == wood_price]
gas_prices = np.array([res["gas_price"] for res in subset_results])
electricity_prices = np.array([res["electricity_price"] for res in subset_results])
wgk = np.array([res["WGK_Gesamt"] + res["wgk_heat_pump_electricity"] for res in subset_results])
grid_x, grid_y = np.meshgrid(
np.linspace(gas_prices.min(), gas_prices.max(), len(set(gas_prices))),
np.linspace(electricity_prices.min(), electricity_prices.max(), len(set(electricity_prices))),
)
grid_wgk = griddata((gas_prices, electricity_prices), wgk, (grid_x, grid_y), method="linear")
ax.plot_surface(
grid_x,
grid_y,
grid_wgk,
color=colors[i],
edgecolor="none",
alpha=0.7,
label=f"Holzpreis: {wood_price} €/MWh",
)
ax.set_xlabel("Gaspreis (€/MWh)")
ax.set_ylabel("Strompreis (€/MWh)")
ax.set_zlabel("Wärmegestehungskosten (€/MWh)")
ax.set_title("Gesamtwärmegestehungskosten (€/MWh)")
custom_lines = [plt.Line2D([0], [0], color=colors[i], lw=4) for i in range(len(unique_wood_prices))]
ax.legend(custom_lines, [f"Holzpreis: {price} €/MWh" for price in unique_wood_prices])
self.canvas.draw()