Spaces:
Sleeping
Sleeping
| """ | |
| Heat transfer calculation module for HVAC Load Calculator. | |
| This module implements heat transfer calculations for conduction, infiltration, and solar effects. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapters 16 and 18. | |
| """ | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import math | |
| import numpy as np | |
| import logging | |
| from dataclasses import dataclass | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Import utility modules | |
| from utils.psychrometrics import Psychrometrics | |
| # Import data modules | |
| from data.building_components import Orientation | |
| class SolarCalculations: | |
| """Class for solar geometry and radiation calculations.""" | |
| def validate_angle(self, angle: float, name: str, min_val: float, max_val: float) -> None: | |
| """ | |
| Validate angle inputs for solar calculations. | |
| Args: | |
| angle: Angle in degrees | |
| name: Name of the angle | |
| min_val: Minimum allowed value | |
| max_val: Maximum allowed value | |
| Raises: | |
| ValueError: If angle is out of range | |
| """ | |
| if not min_val <= angle <= max_val: | |
| raise ValueError(f"{name} {angle}° must be between {min_val}° and {max_val}°") | |
| def solar_declination(self, day_of_year: int) -> float: | |
| """ | |
| Calculate solar declination angle. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Equation 14.6. | |
| Args: | |
| day_of_year: Day of the year (1-365) | |
| Returns: | |
| Declination angle in degrees | |
| """ | |
| if not 1 <= day_of_year <= 365: | |
| raise ValueError("Day of year must be between 1 and 365") | |
| declination = 23.45 * math.sin(math.radians(360 * (284 + day_of_year) / 365)) | |
| self.validate_angle(declination, "Declination angle", -23.45, 23.45) | |
| return declination | |
| def solar_hour_angle(self, hour: float) -> float: | |
| """ | |
| Calculate solar hour angle. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Equation 14.7. | |
| Args: | |
| hour: Hour of the day (0-23) | |
| Returns: | |
| Hour angle in degrees | |
| """ | |
| if not 0 <= hour <= 24: | |
| raise ValueError("Hour must be between 0 and 24") | |
| hour_angle = (hour - 12) * 15 | |
| self.validate_angle(hour_angle, "Hour angle", -180, 180) | |
| return hour_angle | |
| def solar_altitude(self, latitude: float, declination: float, hour_angle: float) -> float: | |
| """ | |
| Calculate solar altitude angle. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Equation 14.8. | |
| Args: | |
| latitude: Latitude in degrees | |
| declination: Declination angle in degrees | |
| hour_angle: Hour angle in degrees | |
| Returns: | |
| Altitude angle in degrees | |
| """ | |
| self.validate_angle(latitude, "Latitude", -90, 90) | |
| self.validate_angle(declination, "Declination", -23.45, 23.45) | |
| self.validate_angle(hour_angle, "Hour angle", -180, 180) | |
| sin_beta = (math.sin(math.radians(latitude)) * math.sin(math.radians(declination)) + | |
| math.cos(math.radians(latitude)) * math.cos(math.radians(declination)) * | |
| math.cos(math.radians(hour_angle))) | |
| beta = math.degrees(math.asin(sin_beta)) | |
| self.validate_angle(beta, "Altitude angle", 0, 90) | |
| return beta | |
| def solar_azimuth(self, latitude: float, declination: float, hour_angle: float, altitude: float) -> float: | |
| """ | |
| Calculate solar azimuth angle. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Equation 14.9. | |
| Args: | |
| latitude: Latitude in degrees | |
| declination: Declination angle in degrees | |
| hour_angle: Hour angle in degrees | |
| altitude: Altitude angle in degrees | |
| Returns: | |
| Azimuth angle in degrees | |
| """ | |
| self.validate_angle(latitude, "Latitude", -90, 90) | |
| self.validate_angle(declination, "Declination", -23.45, 23.45) | |
| self.validate_angle(hour_angle, "Hour angle", -180, 180) | |
| self.validate_angle(altitude, "Altitude", 0, 90) | |
| sin_phi = (math.cos(math.radians(declination)) * math.sin(math.radians(hour_angle)) / | |
| math.cos(math.radians(altitude))) | |
| phi = math.degrees(math.asin(sin_phi)) | |
| if hour_angle > 0: | |
| phi = 180 - phi | |
| elif hour_angle < 0: | |
| phi = -180 - phi | |
| self.validate_angle(phi, "Azimuth angle", -180, 180) | |
| return phi | |
| class HeatTransferCalculations: | |
| """Class for heat transfer calculations.""" | |
| def __init__(self): | |
| """ | |
| Initialize heat transfer calculations with psychrometrics and solar calculations. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 16. | |
| """ | |
| self.psychrometrics = Psychrometrics() | |
| self.solar = SolarCalculations() | |
| self.debug_mode = False | |
| def conduction_heat_transfer(self, u_value: float, area: float, delta_t: float) -> float: | |
| """ | |
| Calculate heat transfer via conduction. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1. | |
| Args: | |
| u_value: U-value of the component in W/(m²·K) | |
| area: Area of the component in m² | |
| delta_t: Temperature difference in °C | |
| Returns: | |
| Heat transfer rate in W | |
| """ | |
| if u_value < 0 or area < 0: | |
| raise ValueError("U-value and area must be non-negative") | |
| q = u_value * area * delta_t | |
| return q | |
| def infiltration_heat_transfer(self, flow_rate: float, delta_t: float, | |
| t_db: float, rh: float, p_atm: float = 101325) -> float: | |
| """ | |
| Calculate sensible heat transfer due to infiltration or ventilation. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.5. | |
| Args: | |
| flow_rate: Air flow rate in m³/s | |
| delta_t: Temperature difference in °C | |
| t_db: Dry-bulb temperature for air properties in °C | |
| rh: Relative humidity in % (0-100) | |
| p_atm: Atmospheric pressure in Pa | |
| Returns: | |
| Sensible heat transfer rate in W | |
| """ | |
| if flow_rate < 0: | |
| raise ValueError("Flow rate cannot be negative") | |
| # Calculate air density and specific heat using psychrometrics | |
| w = self.psychrometrics.humidity_ratio(t_db, rh, p_atm) | |
| rho = self.psychrometrics.density(t_db, w, p_atm) | |
| c_p = 1006 + 1860 * w # Specific heat of moist air in J/(kg·K) | |
| q = flow_rate * rho * c_p * delta_t | |
| return q | |
| def infiltration_latent_heat_transfer(self, flow_rate: float, delta_w: float, | |
| t_db: float, rh: float, p_atm: float = 101325) -> float: | |
| """ | |
| Calculate latent heat transfer due to infiltration or ventilation. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.6. | |
| Args: | |
| flow_rate: Air flow rate in m³/s | |
| delta_w: Humidity ratio difference in kg/kg | |
| t_db: Dry-bulb temperature for air properties in °C | |
| rh: Relative humidity in % (0-100) | |
| p_atm: Atmospheric pressure in Pa | |
| Returns: | |
| Latent heat transfer rate in W | |
| """ | |
| if flow_rate < 0 or delta_w < 0: | |
| raise ValueError("Flow rate and humidity ratio difference cannot be negative") | |
| # Calculate air density and latent heat | |
| w = self.psychrometrics.humidity_ratio(t_db, rh, p_atm) | |
| rho = self.psychrometrics.density(t_db, w, p_atm) | |
| h_fg = 2501000 + 1840 * t_db # Latent heat of vaporization in J/kg | |
| q = flow_rate * rho * h_fg * delta_w | |
| return q | |
| def wind_pressure_difference(self, wind_speed: float) -> float: | |
| """ | |
| Calculate pressure difference due to wind. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 16, Equation 16.3. | |
| Args: | |
| wind_speed: Wind speed in m/s | |
| Returns: | |
| Pressure difference in Pa | |
| """ | |
| if wind_speed < 0: | |
| raise ValueError("Wind speed cannot be negative") | |
| c_p = 0.6 # Wind pressure coefficient | |
| rho_air = 1.2 # Air density at standard conditions in kg/m³ | |
| delta_p = 0.5 * c_p * rho_air * wind_speed**2 | |
| return delta_p | |
| def stack_pressure_difference(self, height: float, t_inside: float, t_outside: float) -> float: | |
| """ | |
| Calculate pressure difference due to stack effect. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 16, Equation 16.4. | |
| Args: | |
| height: Height of the building in m | |
| t_inside: Inside temperature in K | |
| t_outside: Outside temperature in K | |
| Returns: | |
| Pressure difference in Pa | |
| """ | |
| if height < 0 or t_inside <= 0 or t_outside <= 0: | |
| raise ValueError("Height and temperatures must be positive") | |
| g = 9.81 # Gravitational acceleration in m/s² | |
| rho_air = 1.2 # Air density at standard conditions in kg/m³ | |
| delta_p = rho_air * g * height * (1 / t_outside - 1 / t_inside) | |
| return delta_p | |
| def combined_pressure_difference(self, wind_pd: float, stack_pd: float) -> float: | |
| """ | |
| Calculate combined pressure difference from wind and stack effects. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 16, Section 16.2. | |
| Args: | |
| wind_pd: Wind pressure difference in Pa | |
| stack_pd: Stack pressure difference in Pa | |
| Returns: | |
| Combined pressure difference in Pa | |
| """ | |
| delta_p = math.sqrt(wind_pd**2 + stack_pd**2) | |
| return delta_p | |
| def crack_method_infiltration(self, crack_length: float, crack_width: float, delta_p: float) -> float: | |
| """ | |
| Calculate infiltration flow rate using crack method. | |
| Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 16, Equation 16.5. | |
| Args: | |
| crack_length: Length of cracks in m | |
| crack_width: Width of cracks in m | |
| delta_p: Pressure difference across cracks in Pa | |
| Returns: | |
| Infiltration flow rate in m³/s | |
| """ | |
| if crack_length < 0 or crack_width < 0 or delta_p < 0: | |
| raise ValueError("Crack dimensions and pressure difference cannot be negative") | |
| c_d = 0.65 # Discharge coefficient | |
| area = crack_length * crack_width | |
| rho_air = 1.2 # Air density at standard conditions in kg/m³ | |
| q = c_d * area * math.sqrt(2 * delta_p / rho_air) | |
| return q | |
| # Example usage | |
| if __name__ == "__main__": | |
| heat_transfer = HeatTransferCalculations() | |
| heat_transfer.debug_mode = True | |
| # Example conduction calculation | |
| u_value = 0.5 # W/(m²·K) | |
| area = 20.0 # m² | |
| delta_t = 26.0 # °C | |
| q_conduction = heat_transfer.conduction_heat_transfer(u_value, area, delta_t) | |
| logger.info(f"Conduction heat transfer: {q_conduction:.2f} W") | |
| # Example infiltration calculation | |
| flow_rate = 0.05 # m³/s | |
| delta_t = 26.0 # °C | |
| t_db = 21.0 # °C | |
| rh = 40.0 # % | |
| p_atm = 101325 # Pa | |
| q_infiltration = heat_transfer.infiltration_heat_transfer(flow_rate, delta_t, t_db, rh, p_atm) | |
| logger.info(f"Infiltration sensible heat transfer: {q_infiltration:.2f} W") | |
| # Example solar calculation | |
| latitude = 40.0 # degrees | |
| day_of_year = 172 # June 21 | |
| hour = 12.0 # Noon | |
| declination = heat_transfer.solar.solar_declination(day_of_year) | |
| hour_angle = heat_transfer.solar.solar_hour_angle(hour) | |
| altitude = heat_transfer.solar.solar_altitude(latitude, declination, hour_angle) | |
| azimuth = heat_transfer.solar.solar_azimuth(latitude, declination, hour_angle, altitude) | |
| logger.info(f"Solar altitude: {altitude:.2f}°, Azimuth: {azimuth:.2f}°") |