Spaces:
Running
Running
""" | |
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}°") |