Source code for districtheatingsim.heat_generators.STES_animation

"""
STES Animation Module
================================

Interactive 3D visualization for Seasonal Thermal Energy Storage systems.

:author: Dipl.-Ing. (FH) Jonas Pfeiffer
"""

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
from matplotlib.animation import FuncAnimation
from typing import Optional, Callable, Any
import numpy as np

[docs] class STESAnimation: """ Interactive 3D animation for STES temperature stratification. :param storage: STES instance with simulation results :type storage: STES .. note:: Provides real-time animation control and temporal navigation. """
[docs] def __init__(self, storage) -> None: """ Initialize interactive STES animation system with 3D visualization and control interfaces. :param storage: STES instance with simulation results :type storage: STES :raises ValueError: If storage lacks simulation data :raises AttributeError: If storage doesn't have T_sto_layers attribute """ self.storage = storage self.is_animating = False self.anim_speed = 100 # Default animation speed (ms per frame) self.step_size = 1 # Default step size self.anim = None self.current_frame = 0 # Validate storage data if not hasattr(storage, 'T_sto_layers'): raise AttributeError("Storage system must have T_sto_layers data for visualization") if storage.T_sto_layers.size == 0: raise ValueError("Storage system contains no temperature stratification data") # Initialize the figure and 3D axes self.fig, self.ax = plt.subplots(subplot_kw={"projection": "3d"}, figsize=(12, 8)) plt.subplots_adjust(left=0.1, bottom=0.35) # Configure figure properties self.fig.suptitle(f'STES Temperature Stratification Animation - {storage.name}', fontsize=14, fontweight='bold') # Add interactive UI elements self._add_slider() self._add_buttons() # Initialize visualization state self.storage.labels_exist = False self.storage.colorbar_exists = False self.update_plot(0)
def _add_slider(self) -> None: """ Add interactive sliders for time navigation, speed control, and step size adjustment. .. note:: Creates time step slider (0 to hours-1), speed slider (50-300ms), and step size slider (1-100 steps). """ # Slider for time step navigation self.ax_slider = plt.axes([0.1, 0.1, 0.65, 0.03], facecolor='lightgoldenrodyellow') self.slider = Slider( self.ax_slider, 'Time Step', 0, self.storage.hours - 1, valinit=self.current_frame, valstep=1, valfmt='%d hours' ) self.slider.on_changed(lambda val: self.update_plot(val)) # Slider for animation speed control self.ax_speed = plt.axes([0.75, 0.25, 0.15, 0.04]) self.speed_slider = Slider( self.ax_speed, 'Speed (ms)', 50, 300, valinit=self.anim_speed, valfmt='%d ms' ) self.speed_slider.on_changed(lambda val: self.adjust_speed(val)) # Slider for step size control self.ax_step = plt.axes([0.75, 0.1, 0.15, 0.04]) self.step_slider = Slider( self.ax_step, 'Step Size', 1, 100, valinit=1, valstep=1, valfmt='%d steps' ) self.step_slider.on_changed(lambda val: self.set_step_size(val)) def _add_buttons(self) -> None: """ Add interactive control buttons (Start, Stop, Forward, Backward) with hover effects. .. note:: All buttons include visual feedback and responsive hover color changes for intuitive interaction. """ # Start animation button self.ax_start = plt.axes([0.1, 0.25, 0.1, 0.04]) self.start_button = Button( self.ax_start, 'Start', color='lightblue', hovercolor='lightgreen' ) self.start_button.on_clicked(lambda event: self.start_animation()) # Stop animation button self.ax_stop = plt.axes([0.25, 0.25, 0.1, 0.04]) self.stop_button = Button( self.ax_stop, 'Stop', color='lightblue', hovercolor='lightcoral' ) self.stop_button.on_clicked(lambda event: self.stop_animation()) # Forward step button self.ax_forward = plt.axes([0.4, 0.25, 0.1, 0.04]) self.forward_button = Button( self.ax_forward, 'Forward', color='lightblue', hovercolor='lightyellow' ) self.forward_button.on_clicked(lambda event: self.forward()) # Backward step button self.ax_backward = plt.axes([0.55, 0.25, 0.1, 0.04]) self.backward_button = Button( self.ax_backward, 'Backward', color='lightblue', hovercolor='lightyellow' ) self.backward_button.on_clicked(lambda event: self.backward())
[docs] def set_step_size(self, val: float) -> None: """ Set animation step size for temporal resolution control. :param val: Step size value (1-100 time steps per frame) :type val: float .. note:: Fine resolution (1-5), medium (10-20), coarse (50-100) for different analysis needs. """ self.step_size = int(val)
[docs] def update_plot(self, val: float) -> None: """ Update 3D temperature distribution visualization for specified time step. :param val: Time step value (0 to hours-1) :type val: float .. note:: Updates geometry, color mapping, title with temporal/thermal info, and axes scaling. """ self.current_frame = int(val) self.ax.cla() # Clear the axis for fresh rendering # Update 3D temperature distribution self.storage.plot_3d_temperature_distribution(self.ax, self.current_frame) # Set comprehensive title with temporal and thermal information temps = self.storage.T_sto_layers[self.current_frame, :] temp_gradient = temps.max() - temps.min() avg_temp = temps.mean() self.ax.set_title( f'Temperature Distribution - Time: {self.current_frame:04d}h\n' f'Avg: {avg_temp:.1f}°C, Gradient: {temp_gradient:.1f}K, ' f'Top: {temps[0]:.1f}°C, Bottom: {temps[-1]:.1f}°C', fontsize=12, pad=20 ) # Force display update plt.draw()
[docs] def animate(self, i: int) -> None: """ Animation callback for automatic temporal progression. :param i: Current animation frame index :type i: int .. note:: Updates visualization, synchronizes slider, and forces redraw for smooth animation. """ self.current_frame = i self.update_plot(i) self.slider.set_val(i) # Synchronize slider position plt.draw() # Force GUI update
[docs] def start_animation(self) -> None: """ Initialize and start automatic animation progression with current settings. .. note:: Creates FuncAnimation from current position to end with user-defined speed and step size. """ if not self.is_animating: self.anim = FuncAnimation( self.fig, self.animate, frames=range(self.current_frame, self.storage.hours, self.step_size), interval=self.anim_speed, blit=False, repeat=True ) self.is_animating = True plt.draw()
[docs] def stop_animation(self) -> None: """ Stop automatic animation and maintain current visualization state. .. note:: Halts animation while preserving current position and all interface elements. """ if self.anim is not None: self.anim.event_source.stop() self.is_animating = False plt.draw()
[docs] def forward(self) -> None: """ Advance visualization by one time step forward with automatic slider sync. .. note:: Provides manual navigation for detailed frame-by-frame analysis. """ self.current_frame = min(self.current_frame + 1, self.storage.hours - 1) self.slider.set_val(self.current_frame) plt.draw()
[docs] def backward(self) -> None: """ Move visualization one time step backward with automatic slider sync. .. note:: Enables reverse navigation for comparative temporal analysis. """ self.current_frame = max(self.current_frame - 1, 0) self.slider.set_val(self.current_frame) plt.draw()
[docs] def adjust_speed(self, val: float) -> None: """ Dynamically adjust animation frame rate during operation. :param val: Animation speed in milliseconds per frame (50-300ms) :type val: float .. note:: Fast (50-100ms), medium (100-200ms), slow (200-300ms) for different viewing purposes. """ self.anim_speed = int(val) if self.is_animating: self.stop_animation() self.start_animation()
[docs] def show(self) -> None: """ Display interactive STES animation interface with all controls and 3D rendering. .. note:: Professional-quality visualization suitable for research, education, and operational planning. """ plt.show()
[docs] def save_animation(self, filename: str, fps: int = 10, duration: Optional[int] = None) -> None: """ Export animation as video file for documentation and presentation. :param filename: Output filename with extension (.mp4, .gif, .avi) :type filename: str :param fps: Frames per second, defaults to 10 :type fps: int :param duration: Animation duration in seconds, optional :type duration: Optional[int] .. note:: Creates high-quality video suitable for publications and presentations. """ # Implementation would depend on specific export requirements # and available matplotlib animation writers pass
[docs] def get_current_analysis(self) -> dict: """ Extract current thermal analysis data for detailed examination. :return: Dict with time_hours, layer_temperatures, average_temperature, temperature_gradient, storage_state, thermal_energy :rtype: dict .. note:: Provides quantitative data complementing visual analysis for performance assessment. """ temps = self.storage.T_sto_layers[self.current_frame, :] return { 'time_hours': self.current_frame, 'layer_temperatures': temps.copy(), 'average_temperature': np.average(temps, weights=self.storage.layer_volume), 'temperature_gradient': temps.max() - temps.min(), 'storage_state': self.storage.storage_state[self.current_frame] if hasattr(self.storage, 'storage_state') else 0.0, 'thermal_energy': self.storage.Q_sto[self.current_frame] if hasattr(self.storage, 'Q_sto') else 0.0 }