File size: 12,417 Bytes
4af4e9a
13cdf17
efc6bd6
 
4af4e9a
 
efc6bd6
4af4e9a
 
efc6bd6
 
 
 
 
 
 
 
aa6c57f
4af4e9a
efc6bd6
 
 
4af4e9a
13cdf17
efc6bd6
13cdf17
 
4af4e9a
efc6bd6
4af4e9a
 
13cdf17
efc6bd6
13cdf17
 
4af4e9a
63eff5d
13cdf17
4af4e9a
13cdf17
efc6bd6
 
13cdf17
4af4e9a
efc6bd6
 
4af4e9a
 
13cdf17
4af4e9a
 
efc6bd6
4af4e9a
13cdf17
efc6bd6
932295a
13cdf17
efc6bd6
13cdf17
efc6bd6
13cdf17
4af4e9a
efc6bd6
 
4af4e9a
 
13cdf17
4af4e9a
 
efc6bd6
4af4e9a
efc6bd6
 
 
 
 
 
 
13cdf17
932295a
13cdf17
efc6bd6
4af4e9a
 
13cdf17
efc6bd6
 
4af4e9a
 
efc6bd6
4af4e9a
13cdf17
efc6bd6
13cdf17
932295a
efc6bd6
 
 
 
 
 
 
13cdf17
4af4e9a
13cdf17
efc6bd6
4af4e9a
 
13cdf17
efc6bd6
 
 
4af4e9a
 
efc6bd6
4af4e9a
13cdf17
efc6bd6
 
13cdf17
932295a
efc6bd6
 
 
932295a
13cdf17
efc6bd6
 
 
932295a
efc6bd6
 
63eff5d
 
13cdf17
 
4af4e9a
63eff5d
4af4e9a
efc6bd6
 
13cdf17
efc6bd6
 
 
13cdf17
 
932295a
efc6bd6
35bc1fb
4af4e9a
 
efc6bd6
 
13cdf17
4af4e9a
 
13cdf17
4af4e9a
efc6bd6
 
4af4e9a
efc6bd6
 
 
 
 
4af4e9a
13cdf17
35bc1fb
4af4e9a
 
26950bb
13cdf17
efc6bd6
 
 
4af4e9a
 
13cdf17
4af4e9a
efc6bd6
 
 
 
35bc1fb
 
efc6bd6
 
 
 
 
 
 
4af4e9a
13cdf17
35bc1fb
4af4e9a
 
26950bb
13cdf17
efc6bd6
 
 
4af4e9a
 
13cdf17
4af4e9a
efc6bd6
 
 
 
35bc1fb
 
 
efc6bd6
 
 
 
 
4af4e9a
13cdf17
efc6bd6
4af4e9a
 
13cdf17
4af4e9a
 
13cdf17
4af4e9a
13cdf17
efc6bd6
932295a
efc6bd6
 
 
 
 
 
4af4e9a
13cdf17
efc6bd6
4af4e9a
 
efc6bd6
 
 
4af4e9a
 
13cdf17
 
efc6bd6
 
13cdf17
26950bb
efc6bd6
 
 
 
13cdf17
932295a
13cdf17
efc6bd6
4af4e9a
 
13cdf17
 
4af4e9a
 
13cdf17
4af4e9a
efc6bd6
 
 
 
4af4e9a
13cdf17
efc6bd6
4af4e9a
 
efc6bd6
 
 
4af4e9a
 
26950bb
4af4e9a
efc6bd6
 
13cdf17
efc6bd6
 
 
 
 
4af4e9a
13cdf17
4af4e9a
 
efc6bd6
 
4af4e9a
efc6bd6
26950bb
 
efc6bd6
 
 
aa6c57f
35bc1fb
 
efc6bd6
 
 
 
 
 
35bc1fb
efc6bd6
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"""
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}°")