Spaces:
Sleeping
Sleeping
| """ | |
| Shared calculation functions module for HVAC Load Calculator. | |
| This module implements common heat transfer calculations used in both cooling and heating load calculations. | |
| """ | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import math | |
| import numpy as np | |
| import pandas as pd | |
| import os | |
| # Import data models and utilities | |
| from data.building_components import Wall, Roof, Floor, Window, Door, Orientation | |
| from utils.psychrometrics import Psychrometrics | |
| # Define constants | |
| STEFAN_BOLTZMANN_CONSTANT = 5.67e-8 # W/(m²·K⁴) | |
| SOLAR_CONSTANT = 1367 # W/m² | |
| EARTH_TILT_ANGLE = 23.45 # degrees | |
| class HeatTransferCalculations: | |
| """Class for shared heat transfer calculations.""" | |
| def conduction_heat_transfer(u_value: float, area: float, delta_t: float) -> float: | |
| """ | |
| Calculate conduction heat transfer through a building component. | |
| Args: | |
| u_value: U-value of the component in W/(m²·K) | |
| area: Area of the component in m² | |
| delta_t: Temperature difference across the component in K (or °C) | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| return u_value * area * delta_t | |
| def convection_heat_transfer(h_c: float, area: float, delta_t: float) -> float: | |
| """ | |
| Calculate convection heat transfer. | |
| Args: | |
| h_c: Convection heat transfer coefficient in W/(m²·K) | |
| area: Surface area in m² | |
| delta_t: Temperature difference between surface and fluid in K (or °C) | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| return h_c * area * delta_t | |
| def radiation_heat_transfer(emissivity: float, area: float, t_surface: float, t_surroundings: float) -> float: | |
| """ | |
| Calculate radiation heat transfer. | |
| Args: | |
| emissivity: Surface emissivity (0-1) | |
| area: Surface area in m² | |
| t_surface: Surface temperature in K | |
| t_surroundings: Surroundings temperature in K | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| return emissivity * STEFAN_BOLTZMANN_CONSTANT * area * (t_surface**4 - t_surroundings**4) | |
| def infiltration_heat_transfer(flow_rate: float, delta_t: float, density: float = 1.2, specific_heat: float = 1006) -> float: | |
| """ | |
| Calculate sensible heat transfer due to infiltration or ventilation. | |
| Args: | |
| flow_rate: Volumetric flow rate in m³/s | |
| delta_t: Temperature difference between indoor and outdoor air in K (or °C) | |
| density: Air density in kg/m³ (default: 1.2 kg/m³) | |
| specific_heat: Specific heat capacity of air in J/(kg·K) (default: 1006 J/(kg·K)) | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| return flow_rate * density * specific_heat * delta_t | |
| def infiltration_latent_heat_transfer(flow_rate: float, delta_w: float, density: float = 1.2, latent_heat: float = 2501000) -> float: | |
| """ | |
| Calculate latent heat transfer due to infiltration or ventilation. | |
| Args: | |
| flow_rate: Volumetric flow rate in m³/s | |
| delta_w: Humidity ratio difference between indoor and outdoor air in kg/kg | |
| density: Air density in kg/m³ (default: 1.2 kg/m³) | |
| latent_heat: Latent heat of vaporization in J/kg (default: 2501000 J/kg) | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| return flow_rate * density * latent_heat * delta_w | |
| def air_exchange_rate_to_flow_rate(ach: float, volume: float) -> float: | |
| """ | |
| Convert air changes per hour to volumetric flow rate. | |
| Args: | |
| ach: Air changes per hour (1/h) | |
| volume: Room or building volume in m³ | |
| Returns: | |
| Volumetric flow rate in m³/s | |
| """ | |
| return ach * volume / 3600 | |
| def flow_rate_to_air_exchange_rate(flow_rate: float, volume: float) -> float: | |
| """ | |
| Convert volumetric flow rate to air changes per hour. | |
| Args: | |
| flow_rate: Volumetric flow rate in m³/s | |
| volume: Room or building volume in m³ | |
| Returns: | |
| Air changes per hour (1/h) | |
| """ | |
| return flow_rate * 3600 / volume | |
| def crack_method_infiltration(crack_length: float, coefficient: float, pressure_difference: float, exponent: float = 0.65) -> float: | |
| """ | |
| Calculate infiltration using the crack method. | |
| Args: | |
| crack_length: Length of cracks in m | |
| coefficient: Flow coefficient in m³/(s·m·Pa^n) | |
| pressure_difference: Pressure difference in Pa | |
| exponent: Flow exponent (default: 0.65) | |
| Returns: | |
| Infiltration flow rate in m³/s | |
| """ | |
| return coefficient * crack_length * pressure_difference**exponent | |
| def wind_pressure_difference(wind_speed: float, wind_coefficient: float, density: float = 1.2) -> float: | |
| """ | |
| Calculate pressure difference due to wind. | |
| Args: | |
| wind_speed: Wind speed in m/s | |
| wind_coefficient: Wind pressure coefficient (dimensionless) | |
| density: Air density in kg/m³ (default: 1.2 kg/m³) | |
| Returns: | |
| Pressure difference in Pa | |
| """ | |
| return 0.5 * density * wind_speed**2 * wind_coefficient | |
| def stack_pressure_difference(height: float, indoor_temp: float, outdoor_temp: float, | |
| neutral_plane_height: float = None, gravity: float = 9.81) -> float: | |
| """ | |
| Calculate pressure difference due to stack effect. | |
| Args: | |
| height: Height from reference level in m | |
| indoor_temp: Indoor temperature in K | |
| outdoor_temp: Outdoor temperature in K | |
| neutral_plane_height: Height of neutral pressure plane in m (default: half of height) | |
| gravity: Acceleration due to gravity in m/s² (default: 9.81 m/s²) | |
| Returns: | |
| Pressure difference in Pa | |
| """ | |
| if neutral_plane_height is None: | |
| neutral_plane_height = height / 2 | |
| # Calculate pressure difference | |
| return gravity * (height - neutral_plane_height) * (outdoor_temp - indoor_temp) / outdoor_temp | |
| def combined_pressure_difference(wind_pd: float, stack_pd: float) -> float: | |
| """ | |
| Calculate combined pressure difference from wind and stack effects. | |
| Args: | |
| wind_pd: Pressure difference due to wind in Pa | |
| stack_pd: Pressure difference due to stack effect in Pa | |
| Returns: | |
| Combined pressure difference in Pa | |
| """ | |
| # Simple quadrature combination | |
| return math.sqrt(wind_pd**2 + stack_pd**2) | |
| def solar_declination(day_of_year: int) -> float: | |
| """ | |
| Calculate solar declination angle. | |
| Args: | |
| day_of_year: Day of the year (1-365) | |
| Returns: | |
| Solar declination angle in degrees | |
| """ | |
| return EARTH_TILT_ANGLE * math.sin(2 * math.pi * (day_of_year - 81) / 365) | |
| def solar_hour_angle(solar_time: float) -> float: | |
| """ | |
| Calculate solar hour angle. | |
| Args: | |
| solar_time: Solar time in hours (0-24) | |
| Returns: | |
| Solar hour angle in degrees | |
| """ | |
| return 15 * (solar_time - 12) | |
| def solar_altitude(latitude: float, declination: float, hour_angle: float) -> float: | |
| """ | |
| Calculate solar altitude angle. | |
| Args: | |
| latitude: Latitude in degrees | |
| declination: Solar declination angle in degrees | |
| hour_angle: Solar hour angle in degrees | |
| Returns: | |
| Solar altitude angle in degrees | |
| """ | |
| # Convert angles to radians | |
| lat_rad = math.radians(latitude) | |
| decl_rad = math.radians(declination) | |
| hour_rad = math.radians(hour_angle) | |
| # Calculate solar altitude | |
| sin_altitude = (math.sin(lat_rad) * math.sin(decl_rad) + | |
| math.cos(lat_rad) * math.cos(decl_rad) * math.cos(hour_rad)) | |
| return math.degrees(math.asin(sin_altitude)) | |
| def solar_azimuth(latitude: float, declination: float, hour_angle: float, altitude: float) -> float: | |
| """ | |
| Calculate solar azimuth angle. | |
| Args: | |
| latitude: Latitude in degrees | |
| declination: Solar declination angle in degrees | |
| hour_angle: Solar hour angle in degrees | |
| altitude: Solar altitude angle in degrees | |
| Returns: | |
| Solar azimuth angle in degrees (0° = South, positive westward) | |
| """ | |
| # Convert angles to radians | |
| lat_rad = math.radians(latitude) | |
| decl_rad = math.radians(declination) | |
| hour_rad = math.radians(hour_angle) | |
| alt_rad = math.radians(altitude) | |
| # Calculate solar azimuth | |
| cos_azimuth = ((math.sin(decl_rad) * math.cos(lat_rad) - | |
| math.cos(decl_rad) * math.sin(lat_rad) * math.cos(hour_rad)) / | |
| math.cos(alt_rad)) | |
| # Constrain to [-1, 1] to avoid domain errors | |
| cos_azimuth = max(-1, min(1, cos_azimuth)) | |
| # Calculate azimuth angle | |
| azimuth = math.degrees(math.acos(cos_azimuth)) | |
| # Adjust for morning hours (negative hour angle) | |
| if hour_angle < 0: | |
| azimuth = -azimuth | |
| return azimuth | |
| def incident_angle(surface_tilt: float, surface_azimuth: float, | |
| solar_altitude: float, solar_azimuth: float) -> float: | |
| """ | |
| Calculate angle of incidence on a surface. | |
| Args: | |
| surface_tilt: Surface tilt angle from horizontal in degrees (0° = horizontal, 90° = vertical) | |
| surface_azimuth: Surface azimuth angle in degrees (0° = South, positive westward) | |
| solar_altitude: Solar altitude angle in degrees | |
| solar_azimuth: Solar azimuth angle in degrees | |
| Returns: | |
| Incident angle in degrees | |
| """ | |
| # Convert angles to radians | |
| surf_tilt_rad = math.radians(surface_tilt) | |
| surf_azim_rad = math.radians(surface_azimuth) | |
| solar_alt_rad = math.radians(solar_altitude) | |
| solar_azim_rad = math.radians(solar_azimuth) | |
| # Calculate incident angle | |
| cos_incident = (math.sin(solar_alt_rad) * math.cos(surf_tilt_rad) + | |
| math.cos(solar_alt_rad) * math.sin(surf_tilt_rad) * | |
| math.cos(solar_azim_rad - surf_azim_rad)) | |
| # Constrain to [-1, 1] to avoid domain errors | |
| cos_incident = max(-1, min(1, cos_incident)) | |
| return math.degrees(math.acos(cos_incident)) | |
| def direct_normal_irradiance(altitude: float, atmospheric_clearness: float = 1.0) -> float: | |
| """ | |
| Calculate direct normal irradiance. | |
| Args: | |
| altitude: Solar altitude angle in degrees | |
| atmospheric_clearness: Atmospheric clearness factor (0-1) | |
| Returns: | |
| Direct normal irradiance in W/m² | |
| """ | |
| if altitude <= 0: | |
| return 0 | |
| # Simple model based on air mass | |
| air_mass = 1 / math.sin(math.radians(altitude)) | |
| # Limit air mass to reasonable values | |
| air_mass = min(air_mass, 38) | |
| # Calculate direct normal irradiance | |
| dni = SOLAR_CONSTANT * atmospheric_clearness**air_mass | |
| return dni | |
| def diffuse_horizontal_irradiance(dni: float, altitude: float, clearness: float = 0.2) -> float: | |
| """ | |
| Calculate diffuse horizontal irradiance. | |
| Args: | |
| dni: Direct normal irradiance in W/m² | |
| altitude: Solar altitude angle in degrees | |
| clearness: Sky clearness factor (0-1) | |
| Returns: | |
| Diffuse horizontal irradiance in W/m² | |
| """ | |
| if altitude <= 0: | |
| return 0 | |
| # Simple model for diffuse irradiance | |
| return dni * clearness * math.sin(math.radians(altitude)) | |
| def global_horizontal_irradiance(dni: float, dhi: float, altitude: float) -> float: | |
| """ | |
| Calculate global horizontal irradiance. | |
| Args: | |
| dni: Direct normal irradiance in W/m² | |
| dhi: Diffuse horizontal irradiance in W/m² | |
| altitude: Solar altitude angle in degrees | |
| Returns: | |
| Global horizontal irradiance in W/m² | |
| """ | |
| if altitude <= 0: | |
| return 0 | |
| # Calculate direct horizontal component | |
| direct_horizontal = dni * math.sin(math.radians(altitude)) | |
| # Calculate global horizontal irradiance | |
| return direct_horizontal + dhi | |
| def irradiance_on_surface(dni: float, dhi: float, incident_angle: float, | |
| surface_tilt: float, ground_reflectance: float = 0.2) -> float: | |
| """ | |
| Calculate total irradiance on a surface. | |
| Args: | |
| dni: Direct normal irradiance in W/m² | |
| dhi: Diffuse horizontal irradiance in W/m² | |
| incident_angle: Incident angle in degrees | |
| surface_tilt: Surface tilt angle from horizontal in degrees | |
| ground_reflectance: Ground reflectance (albedo) (0-1) | |
| Returns: | |
| Total irradiance on the surface in W/m² | |
| """ | |
| # Convert angles to radians | |
| incident_rad = math.radians(incident_angle) | |
| tilt_rad = math.radians(surface_tilt) | |
| # Calculate direct component | |
| if incident_angle < 90: | |
| direct = dni * math.cos(incident_rad) | |
| else: | |
| direct = 0 | |
| # Calculate diffuse component (simple isotropic model) | |
| diffuse = dhi * (1 + math.cos(tilt_rad)) / 2 | |
| # Calculate ground-reflected component | |
| reflected = (dni * math.sin(math.radians(incident_angle)) + dhi) * ground_reflectance * (1 - math.cos(tilt_rad)) / 2 | |
| # Calculate total irradiance | |
| return direct + diffuse + reflected | |
| def solar_heat_gain(irradiance: float, area: float, shgc: float, | |
| shading_coefficient: float = 1.0, frame_factor: float = 0.85) -> float: | |
| """ | |
| Calculate solar heat gain through a window. | |
| Args: | |
| irradiance: Total irradiance on the window in W/m² | |
| area: Window area in m² | |
| shgc: Solar Heat Gain Coefficient (0-1) | |
| shading_coefficient: External shading coefficient (0-1) | |
| frame_factor: Ratio of glazing area to total window area (0-1) | |
| Returns: | |
| Solar heat gain in W | |
| """ | |
| return irradiance * area * shgc * shading_coefficient * frame_factor | |
| def internal_gains(occupants: int, lights_power: float, equipment_power: float, | |
| occupant_sensible_gain: float = 70, occupant_latent_gain: float = 45) -> Dict[str, float]: | |
| """ | |
| Calculate internal heat gains. | |
| Args: | |
| occupants: Number of occupants | |
| lights_power: Lighting power in W | |
| equipment_power: Equipment power in W | |
| occupant_sensible_gain: Sensible heat gain per occupant in W (default: 70 W) | |
| occupant_latent_gain: Latent heat gain per occupant in W (default: 45 W) | |
| Returns: | |
| Dictionary with sensible, latent, and total heat gains in W | |
| """ | |
| # Calculate occupant gains | |
| occupant_sensible = occupants * occupant_sensible_gain | |
| occupant_latent = occupants * occupant_latent_gain | |
| # Calculate total sensible and latent gains | |
| sensible_gain = occupant_sensible + lights_power + equipment_power | |
| latent_gain = occupant_latent | |
| return { | |
| "sensible": sensible_gain, | |
| "latent": latent_gain, | |
| "total": sensible_gain + latent_gain | |
| } | |
| def thermal_mass_effect(mass: float, specific_heat: float, delta_t: float) -> float: | |
| """ | |
| Calculate heat storage in thermal mass. | |
| Args: | |
| mass: Mass of the material in kg | |
| specific_heat: Specific heat capacity in J/(kg·K) | |
| delta_t: Temperature change in K (or °C) | |
| Returns: | |
| Heat stored in J | |
| """ | |
| return mass * specific_heat * delta_t | |
| def thermal_lag_factor(thermal_mass: float, time_constant: float, time_step: float) -> float: | |
| """ | |
| Calculate thermal lag factor for dynamic heat transfer. | |
| Args: | |
| thermal_mass: Thermal mass in J/K | |
| time_constant: Time constant in hours | |
| time_step: Time step in hours | |
| Returns: | |
| Thermal lag factor (0-1) | |
| """ | |
| return 1 - math.exp(-time_step / time_constant) | |
| def temperature_swing(heat_gain: float, thermal_mass: float) -> float: | |
| """ | |
| Calculate temperature swing due to heat gain and thermal mass. | |
| Args: | |
| heat_gain: Heat gain in J | |
| thermal_mass: Thermal mass in J/K | |
| Returns: | |
| Temperature swing in K (or °C) | |
| """ | |
| return heat_gain / thermal_mass | |
| def sol_air_temperature(outdoor_temp: float, solar_irradiance: float, | |
| surface_absorptivity: float, surface_resistance: float) -> float: | |
| """ | |
| Calculate sol-air temperature. | |
| Args: | |
| outdoor_temp: Outdoor air temperature in °C | |
| solar_irradiance: Solar irradiance on the surface in W/m² | |
| surface_absorptivity: Surface solar absorptivity (0-1) | |
| surface_resistance: Surface heat transfer resistance in m²·K/W | |
| Returns: | |
| Sol-air temperature in °C | |
| """ | |
| return outdoor_temp + solar_irradiance * surface_absorptivity * surface_resistance | |
| # Create a singleton instance | |
| heat_transfer = HeatTransferCalculations() | |
| # Example usage | |
| if __name__ == "__main__": | |
| # Calculate conduction heat transfer | |
| q_cond = heat_transfer.conduction_heat_transfer(u_value=0.5, area=10, delta_t=20) | |
| print(f"Conduction heat transfer: {q_cond:.2f} W") | |
| # Calculate infiltration heat transfer | |
| q_inf = heat_transfer.infiltration_heat_transfer(flow_rate=0.1, delta_t=20) | |
| print(f"Infiltration heat transfer: {q_inf:.2f} W") | |
| # Calculate solar heat gain | |
| q_solar = heat_transfer.solar_heat_gain(irradiance=500, area=5, shgc=0.7) | |
| print(f"Solar heat gain: {q_solar:.2f} W") | |
| # Calculate internal gains | |
| gains = heat_transfer.internal_gains(occupants=3, lights_power=200, equipment_power=500) | |
| print(f"Internal gains - Sensible: {gains['sensible']:.2f} W, Latent: {gains['latent']:.2f} W, Total: {gains['total']:.2f} W") | |