"""
Sensitivity Tab Module
======================
:author: Dipl.-Ing. (FH) Jonas Pfeiffer
Performing sensitivity analysis on heat generation costs based on varying parameters, with 3D visualization.
"""
from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QLineEdit, QMessageBox
from PyQt6.QtCore import pyqtSignal
import numpy as np
import matplotlib.pyplot as plt
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')
surf = 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()