Spaces:
Sleeping
Sleeping
Upload cooling_load.py
Browse files- cooling_load.py +1180 -0
cooling_load.py
ADDED
@@ -0,0 +1,1180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Cooling load calculation module for HVAC Load Calculator.
|
3 |
+
Implements ASHRAE steady-state methods with Cooling Load Temperature Difference (CLTD).
|
4 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.5.
|
5 |
+
|
6 |
+
Author: Dr Majed Abuseif
|
7 |
+
Date: April 2025
|
8 |
+
Version: 1.0.7
|
9 |
+
"""
|
10 |
+
|
11 |
+
from typing import Dict, List, Any, Optional, Tuple
|
12 |
+
import numpy as np
|
13 |
+
import logging
|
14 |
+
from data.ashrae_tables import ASHRAETables
|
15 |
+
from utils.heat_transfer import HeatTransferCalculations
|
16 |
+
from utils.psychrometrics import Psychrometrics
|
17 |
+
from app.component_selection import Wall, Roof, Window, Door, Skylight, Orientation
|
18 |
+
from data.drapery import Drapery
|
19 |
+
|
20 |
+
# Set up logging
|
21 |
+
logging.basicConfig(level=logging.INFO)
|
22 |
+
logger = logging.getLogger(__name__)
|
23 |
+
|
24 |
+
class CoolingLoadCalculator:
|
25 |
+
"""Class for cooling load calculations based on ASHRAE steady-state methods."""
|
26 |
+
|
27 |
+
def __init__(self, debug_mode: bool = False):
|
28 |
+
"""
|
29 |
+
Initialize cooling load calculator.
|
30 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.5.
|
31 |
+
|
32 |
+
Args:
|
33 |
+
debug_mode: Enable debug logging if True
|
34 |
+
"""
|
35 |
+
self.ashrae_tables = ASHRAETables()
|
36 |
+
self.heat_transfer = HeatTransferCalculations()
|
37 |
+
self.psychrometrics = Psychrometrics()
|
38 |
+
self.hours = list(range(24))
|
39 |
+
self.valid_latitudes = ['24N', '32N', '40N', '48N', '56N']
|
40 |
+
self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
|
41 |
+
self.valid_wall_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
|
42 |
+
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
43 |
+
self.debug_mode = debug_mode
|
44 |
+
if debug_mode:
|
45 |
+
logger.setLevel(logging.DEBUG)
|
46 |
+
|
47 |
+
def validate_latitude(self, latitude: Any) -> str:
|
48 |
+
"""
|
49 |
+
Validate and normalize latitude input.
|
50 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Section 14.2.
|
51 |
+
|
52 |
+
Args:
|
53 |
+
latitude: Latitude input (str, float, or other)
|
54 |
+
|
55 |
+
Returns:
|
56 |
+
Valid latitude string ('24N', '32N', '40N', '48N', '56N')
|
57 |
+
"""
|
58 |
+
try:
|
59 |
+
if not isinstance(latitude, str):
|
60 |
+
try:
|
61 |
+
lat_val = float(latitude)
|
62 |
+
if lat_val <= 28:
|
63 |
+
return '24N'
|
64 |
+
elif lat_val <= 36:
|
65 |
+
return '32N'
|
66 |
+
elif lat_val <= 44:
|
67 |
+
return '40N'
|
68 |
+
elif lat_val <= 52:
|
69 |
+
return '48N'
|
70 |
+
else:
|
71 |
+
return '56N'
|
72 |
+
except (ValueError, TypeError):
|
73 |
+
latitude = str(latitude)
|
74 |
+
|
75 |
+
latitude = latitude.strip().upper()
|
76 |
+
if self.debug_mode:
|
77 |
+
logger.debug(f"Validating latitude: {latitude}")
|
78 |
+
|
79 |
+
if '_' in latitude:
|
80 |
+
parts = latitude.split('_')
|
81 |
+
if len(parts) > 1:
|
82 |
+
lat_part = parts[0]
|
83 |
+
if self.debug_mode:
|
84 |
+
logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
|
85 |
+
latitude = lat_part
|
86 |
+
|
87 |
+
if '.' in latitude or any(c.isdigit() for c in latitude):
|
88 |
+
num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
|
89 |
+
try:
|
90 |
+
lat_val = float(num_part)
|
91 |
+
if lat_val <= 28:
|
92 |
+
mapped_latitude = '24N'
|
93 |
+
elif lat_val <= 36:
|
94 |
+
mapped_latitude = '32N'
|
95 |
+
elif lat_val <= 44:
|
96 |
+
mapped_latitude = '40N'
|
97 |
+
elif lat_val <= 52:
|
98 |
+
mapped_latitude = '48N'
|
99 |
+
else:
|
100 |
+
mapped_latitude = '56N'
|
101 |
+
if self.debug_mode:
|
102 |
+
logger.debug(f"Mapped numerical latitude {lat_val} to {mapped_latitude}")
|
103 |
+
return mapped_latitude
|
104 |
+
except ValueError:
|
105 |
+
if self.debug_mode:
|
106 |
+
logger.warning(f"Cannot parse numerical latitude: {latitude}. Defaulting to '32N'")
|
107 |
+
return '32N'
|
108 |
+
|
109 |
+
if latitude in self.valid_latitudes:
|
110 |
+
return latitude
|
111 |
+
|
112 |
+
if self.debug_mode:
|
113 |
+
logger.warning(f"Invalid latitude: {latitude}. Defaulting to '32N'")
|
114 |
+
return '32N'
|
115 |
+
|
116 |
+
except Exception as e:
|
117 |
+
if self.debug_mode:
|
118 |
+
logger.error(f"Error validating latitude {latitude}: {str(e)}")
|
119 |
+
return '32N'
|
120 |
+
|
121 |
+
def validate_month(self, month: Any) -> str:
|
122 |
+
"""
|
123 |
+
Validate and normalize month input.
|
124 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Section 14.2.
|
125 |
+
|
126 |
+
Args:
|
127 |
+
month: Month input (str or other)
|
128 |
+
|
129 |
+
Returns:
|
130 |
+
Valid month string in uppercase
|
131 |
+
"""
|
132 |
+
try:
|
133 |
+
if not isinstance(month, str):
|
134 |
+
month = str(month)
|
135 |
+
|
136 |
+
month_upper = month.strip().upper()
|
137 |
+
if month_upper not in self.valid_months:
|
138 |
+
if self.debug_mode:
|
139 |
+
logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
|
140 |
+
return 'JUL'
|
141 |
+
return month_upper
|
142 |
+
|
143 |
+
except Exception as e:
|
144 |
+
if self.debug_mode:
|
145 |
+
logger.error(f"Error validating month {month}: {str(e)}")
|
146 |
+
return 'JUL'
|
147 |
+
|
148 |
+
def validate_hour(self, hour: Any) -> int:
|
149 |
+
"""
|
150 |
+
Validate and normalize hour input.
|
151 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Section 14.2.
|
152 |
+
|
153 |
+
Args:
|
154 |
+
hour: Hour input (int, float, or other)
|
155 |
+
|
156 |
+
Returns:
|
157 |
+
Valid hour integer (0-23)
|
158 |
+
"""
|
159 |
+
try:
|
160 |
+
hour = int(float(str(hour)))
|
161 |
+
if not 0 <= hour <= 23:
|
162 |
+
if self.debug_mode:
|
163 |
+
logger.warning(f"Invalid hour: {hour}. Defaulting to 15")
|
164 |
+
return 15
|
165 |
+
return hour
|
166 |
+
except (ValueError, TypeError):
|
167 |
+
if self.debug_mode:
|
168 |
+
logger.warning(f"Invalid hour format: {hour}. Defaulting to 15")
|
169 |
+
return 15
|
170 |
+
|
171 |
+
def validate_conditions(self, outdoor_temp: float, indoor_temp: float,
|
172 |
+
outdoor_rh: float, indoor_rh: float) -> None:
|
173 |
+
"""
|
174 |
+
Validate temperature and relative humidity inputs.
|
175 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Section 1.2.
|
176 |
+
|
177 |
+
Args:
|
178 |
+
outdoor_temp: Outdoor temperature in °C
|
179 |
+
indoor_temp: Indoor temperature in °C
|
180 |
+
outdoor_rh: Outdoor relative humidity in %
|
181 |
+
indoor_rh: Indoor relative humidity in %
|
182 |
+
|
183 |
+
Raises:
|
184 |
+
ValueError: If inputs are invalid
|
185 |
+
"""
|
186 |
+
if not -50 <= outdoor_temp <= 60 or not -50 <= indoor_temp <= 60:
|
187 |
+
raise ValueError("Temperatures must be between -50°C and 60°C")
|
188 |
+
if not 0 <= outdoor_rh <= 100 or not 0 <= indoor_rh <= 100:
|
189 |
+
raise ValueError("Relative humidities must be between 0 and 100%")
|
190 |
+
if outdoor_temp - indoor_temp < 1:
|
191 |
+
raise ValueError("Outdoor temperature must be at least 1°C above indoor temperature for cooling")
|
192 |
+
|
193 |
+
def calculate_hourly_cooling_loads(
|
194 |
+
self,
|
195 |
+
building_components: Dict[str, List[Any]],
|
196 |
+
outdoor_conditions: Dict[str, Any],
|
197 |
+
indoor_conditions: Dict[str, Any],
|
198 |
+
internal_loads: Dict[str, Any],
|
199 |
+
building_volume: float,
|
200 |
+
p_atm: float = 101325
|
201 |
+
) -> Dict[str, Any]:
|
202 |
+
"""
|
203 |
+
Calculate hourly cooling loads for all components.
|
204 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.5.
|
205 |
+
|
206 |
+
Args:
|
207 |
+
building_components: Dictionary of building components
|
208 |
+
outdoor_conditions: Outdoor weather conditions (temperature, relative_humidity, latitude, month)
|
209 |
+
indoor_conditions: Indoor design conditions (temperature, relative_humidity)
|
210 |
+
internal_loads: Internal heat gains (people, lights, equipment, infiltration, ventilation)
|
211 |
+
building_volume: Building volume in cubic meters
|
212 |
+
p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
|
213 |
+
|
214 |
+
Returns:
|
215 |
+
Dictionary containing hourly cooling loads
|
216 |
+
"""
|
217 |
+
hourly_loads = {
|
218 |
+
'walls': {h: 0.0 for h in range(1, 25)},
|
219 |
+
'roofs': {h: 0.0 for h in range(1, 25)},
|
220 |
+
'windows_conduction': {h: 0.0 for h in range(1, 25)},
|
221 |
+
'windows_solar': {h: 0.0 for h in range(1, 25)},
|
222 |
+
'skylights_conduction': {h: 0.0 for h in range(1, 25)},
|
223 |
+
'skylights_solar': {h: 0.0 for h in range(1, 25)},
|
224 |
+
'doors': {h: 0.0 for h in range(1, 25)},
|
225 |
+
'people_sensible': {h: 0.0 for h in range(1, 25)},
|
226 |
+
'people_latent': {h: 0.0 for h in range(1, 25)},
|
227 |
+
'lights': {h: 0.0 for h in range(1, 25)},
|
228 |
+
'equipment_sensible': {h: 0.0 for h in range(1, 25)},
|
229 |
+
'equipment_latent': {h: 0.0 for h in range(1, 25)},
|
230 |
+
'infiltration_sensible': {h: 0.0 for h in range(1, 25)},
|
231 |
+
'infiltration_latent': {h: 0.0 for h in range(1, 25)},
|
232 |
+
'ventilation_sensible': {h: 0.0 for h in range(1, 25)},
|
233 |
+
'ventilation_latent': {h: 0.0 for h in range(1, 25)}
|
234 |
+
}
|
235 |
+
|
236 |
+
try:
|
237 |
+
# Validate conditions
|
238 |
+
self.validate_conditions(
|
239 |
+
outdoor_conditions['temperature'],
|
240 |
+
indoor_conditions['temperature'],
|
241 |
+
outdoor_conditions.get('relative_humidity', 50.0),
|
242 |
+
indoor_conditions.get('relative_humidity', 50.0)
|
243 |
+
)
|
244 |
+
|
245 |
+
latitude = self.validate_latitude(outdoor_conditions.get('latitude', '32N'))
|
246 |
+
month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
|
247 |
+
if self.debug_mode:
|
248 |
+
logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
|
249 |
+
|
250 |
+
# Calculate loads for walls
|
251 |
+
for wall in building_components.get('walls', []):
|
252 |
+
for hour in range(24):
|
253 |
+
load = self.calculate_wall_cooling_load(
|
254 |
+
wall=wall,
|
255 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
256 |
+
indoor_temp=indoor_conditions['temperature'],
|
257 |
+
month=month,
|
258 |
+
hour=hour,
|
259 |
+
latitude=latitude,
|
260 |
+
solar_absorptivity=wall.solar_absorptivity
|
261 |
+
)
|
262 |
+
hourly_loads['walls'][hour + 1] += load
|
263 |
+
|
264 |
+
# Calculate loads for roofs
|
265 |
+
for roof in building_components.get('roofs', []):
|
266 |
+
for hour in range(24):
|
267 |
+
load = self.calculate_roof_cooling_load(
|
268 |
+
roof=roof,
|
269 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
270 |
+
indoor_temp=indoor_conditions['temperature'],
|
271 |
+
month=month,
|
272 |
+
hour=hour,
|
273 |
+
latitude=latitude,
|
274 |
+
solar_absorptivity=roof.solar_absorptivity
|
275 |
+
)
|
276 |
+
hourly_loads['roofs'][hour + 1] += load
|
277 |
+
|
278 |
+
# Calculate loads for windows
|
279 |
+
for window in building_components.get('windows', []):
|
280 |
+
for hour in range(24):
|
281 |
+
adjusted_shgc = getattr(window, 'adjusted_shgc', None)
|
282 |
+
load_dict = self.calculate_window_cooling_load(
|
283 |
+
window=window,
|
284 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
285 |
+
indoor_temp=indoor_conditions['temperature'],
|
286 |
+
month=month,
|
287 |
+
hour=hour,
|
288 |
+
latitude=latitude,
|
289 |
+
shading_coefficient=window.shading_coefficient,
|
290 |
+
adjusted_shgc=adjusted_shgc
|
291 |
+
)
|
292 |
+
hourly_loads['windows_conduction'][hour + 1] += load_dict['conduction']
|
293 |
+
hourly_loads['windows_solar'][hour + 1] += load_dict['solar']
|
294 |
+
|
295 |
+
# Calculate loads for skylights
|
296 |
+
for skylight in building_components.get('skylights', []):
|
297 |
+
for hour in range(24):
|
298 |
+
adjusted_shgc = getattr(skylight, 'adjusted_shgc', None)
|
299 |
+
load_dict = self.calculate_skylight_cooling_load(
|
300 |
+
skylight=skylight,
|
301 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
302 |
+
indoor_temp=indoor_conditions['temperature'],
|
303 |
+
month=month,
|
304 |
+
hour=hour,
|
305 |
+
latitude=latitude,
|
306 |
+
shading_coefficient=skylight.shading_coefficient,
|
307 |
+
adjusted_shgc=adjusted_shgc
|
308 |
+
)
|
309 |
+
hourly_loads['skylights_conduction'][hour + 1] += load_dict['conduction']
|
310 |
+
hourly_loads['skylights_solar'][hour + 1] += load_dict['solar']
|
311 |
+
|
312 |
+
# Calculate loads for doors
|
313 |
+
for door in building_components.get('doors', []):
|
314 |
+
for hour in range(24):
|
315 |
+
load = self.calculate_door_cooling_load(
|
316 |
+
door=door,
|
317 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
318 |
+
indoor_temp=indoor_conditions['temperature']
|
319 |
+
)
|
320 |
+
hourly_loads['doors'][hour + 1] += load
|
321 |
+
|
322 |
+
# Calculate internal loads
|
323 |
+
for hour in range(24):
|
324 |
+
# People loads
|
325 |
+
people_load = self.calculate_people_cooling_load(
|
326 |
+
num_people=internal_loads['people']['number'],
|
327 |
+
activity_level=internal_loads['people']['activity_level'],
|
328 |
+
hour=hour
|
329 |
+
)
|
330 |
+
hourly_loads['people_sensible'][hour + 1] += people_load['sensible']
|
331 |
+
hourly_loads['people_latent'][hour + 1] += people_load['latent']
|
332 |
+
|
333 |
+
# Lighting loads
|
334 |
+
lights_load = self.calculate_lights_cooling_load(
|
335 |
+
power=internal_loads['lights']['power'],
|
336 |
+
use_factor=internal_loads['lights']['use_factor'],
|
337 |
+
special_allowance=internal_loads['lights']['special_allowance'],
|
338 |
+
hour=hour
|
339 |
+
)
|
340 |
+
hourly_loads['lights'][hour + 1] += lights_load
|
341 |
+
|
342 |
+
# Equipment loads
|
343 |
+
equipment_load = self.calculate_equipment_cooling_load(
|
344 |
+
power=internal_loads['equipment']['power'],
|
345 |
+
use_factor=internal_loads['equipment']['use_factor'],
|
346 |
+
radiation_factor=internal_loads['equipment']['radiation_factor'],
|
347 |
+
hour=hour
|
348 |
+
)
|
349 |
+
hourly_loads['equipment_sensible'][hour + 1] += equipment_load['sensible']
|
350 |
+
hourly_loads['equipment_latent'][hour + 1] += equipment_load['latent']
|
351 |
+
|
352 |
+
# Infiltration loads
|
353 |
+
infiltration_load = self.calculate_infiltration_cooling_load(
|
354 |
+
flow_rate=internal_loads['infiltration']['flow_rate'],
|
355 |
+
building_volume=building_volume,
|
356 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
357 |
+
outdoor_rh=outdoor_conditions['relative_humidity'],
|
358 |
+
indoor_temp=indoor_conditions['temperature'],
|
359 |
+
indoor_rh=indoor_conditions['relative_humidity'],
|
360 |
+
p_atm=p_atm
|
361 |
+
)
|
362 |
+
hourly_loads['infiltration_sensible'][hour + 1] += infiltration_load['sensible']
|
363 |
+
hourly_loads['infiltration_latent'][hour + 1] += infiltration_load['latent']
|
364 |
+
|
365 |
+
# Ventilation loads
|
366 |
+
ventilation_load = self.calculate_ventilation_cooling_load(
|
367 |
+
flow_rate=internal_loads['ventilation']['flow_rate'],
|
368 |
+
outdoor_temp=outdoor_conditions['temperature'],
|
369 |
+
outdoor_rh=outdoor_conditions['relative_humidity'],
|
370 |
+
indoor_temp=indoor_conditions['temperature'],
|
371 |
+
indoor_rh=indoor_conditions['relative_humidity'],
|
372 |
+
p_atm=p_atm
|
373 |
+
)
|
374 |
+
hourly_loads['ventilation_sensible'][hour + 1] += ventilation_load['sensible']
|
375 |
+
hourly_loads['ventilation_latent'][hour + 1] += ventilation_load['latent']
|
376 |
+
|
377 |
+
return hourly_loads
|
378 |
+
|
379 |
+
except Exception as e:
|
380 |
+
if self.debug_mode:
|
381 |
+
logger.error(f"Error in calculate_hourly_cooling_loads: {str(e)}")
|
382 |
+
raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
|
383 |
+
|
384 |
+
def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
|
385 |
+
"""
|
386 |
+
Calculate design cooling load based on peak hourly loads.
|
387 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.5.
|
388 |
+
|
389 |
+
Args:
|
390 |
+
hourly_loads: Dictionary of hourly cooling loads
|
391 |
+
|
392 |
+
Returns:
|
393 |
+
Dictionary containing design cooling loads
|
394 |
+
"""
|
395 |
+
try:
|
396 |
+
design_loads = {}
|
397 |
+
total_loads = []
|
398 |
+
|
399 |
+
for hour in range(1, 25):
|
400 |
+
total_load = sum([
|
401 |
+
hourly_loads['walls'][hour],
|
402 |
+
hourly_loads['roofs'][hour],
|
403 |
+
hourly_loads['windows_conduction'][hour],
|
404 |
+
hourly_loads['windows_solar'][hour],
|
405 |
+
hourly_loads['skylights_conduction'][hour],
|
406 |
+
hourly_loads['skylights_solar'][hour],
|
407 |
+
hourly_loads['doors'][hour],
|
408 |
+
hourly_loads['people_sensible'][hour],
|
409 |
+
hourly_loads['people_latent'][hour],
|
410 |
+
hourly_loads['lights'][hour],
|
411 |
+
hourly_loads['equipment_sensible'][hour],
|
412 |
+
hourly_loads['equipment_latent'][hour],
|
413 |
+
hourly_loads['infiltration_sensible'][hour],
|
414 |
+
hourly_loads['infiltration_latent'][hour],
|
415 |
+
hourly_loads['ventilation_sensible'][hour],
|
416 |
+
hourly_loads['ventilation_latent'][hour]
|
417 |
+
])
|
418 |
+
total_loads.append(total_load)
|
419 |
+
|
420 |
+
design_hour = range(1, 25)[np.argmax(total_loads)]
|
421 |
+
|
422 |
+
design_loads = {
|
423 |
+
'design_hour': design_hour,
|
424 |
+
'walls': hourly_loads['walls'][design_hour],
|
425 |
+
'roofs': hourly_loads['roofs'][design_hour],
|
426 |
+
'windows_conduction': hourly_loads['windows_conduction'][design_hour],
|
427 |
+
'windows_solar': hourly_loads['windows_solar'][design_hour],
|
428 |
+
'skylights_conduction': hourly_loads['skylights_conduction'][design_hour],
|
429 |
+
'skylights_solar': hourly_loads['skylights_solar'][design_hour],
|
430 |
+
'doors': hourly_loads['doors'][design_hour],
|
431 |
+
'people_sensible': hourly_loads['people_sensible'][design_hour],
|
432 |
+
'people_latent': hourly_loads['people_latent'][design_hour],
|
433 |
+
'lights': hourly_loads['lights'][design_hour],
|
434 |
+
'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
|
435 |
+
'equipment_latent': hourly_loads['equipment_latent'][design_hour],
|
436 |
+
'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
|
437 |
+
'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
|
438 |
+
'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
|
439 |
+
'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
|
440 |
+
}
|
441 |
+
|
442 |
+
return design_loads
|
443 |
+
|
444 |
+
except Exception as e:
|
445 |
+
if self.debug_mode:
|
446 |
+
logger.error(f"Error in calculate_design_cooling_load: {str(e)}")
|
447 |
+
raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
|
448 |
+
|
449 |
+
def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
|
450 |
+
"""
|
451 |
+
Calculate summary of cooling loads.
|
452 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.5.
|
453 |
+
|
454 |
+
Args:
|
455 |
+
design_loads: Dictionary of design cooling loads
|
456 |
+
|
457 |
+
Returns:
|
458 |
+
Dictionary containing cooling load summary
|
459 |
+
"""
|
460 |
+
try:
|
461 |
+
total_sensible = (
|
462 |
+
design_loads['walls'] +
|
463 |
+
design_loads['roofs'] +
|
464 |
+
design_loads['windows_conduction'] +
|
465 |
+
design_loads['windows_solar'] +
|
466 |
+
design_loads['skylights_conduction'] +
|
467 |
+
design_loads['skylights_solar'] +
|
468 |
+
design_loads['doors'] +
|
469 |
+
design_loads['people_sensible'] +
|
470 |
+
design_loads['lights'] +
|
471 |
+
design_loads['equipment_sensible'] +
|
472 |
+
design_loads['infiltration_sensible'] +
|
473 |
+
design_loads['ventilation_sensible']
|
474 |
+
)
|
475 |
+
|
476 |
+
total_latent = (
|
477 |
+
design_loads['people_latent'] +
|
478 |
+
design_loads['equipment_latent'] +
|
479 |
+
design_loads['infiltration_latent'] +
|
480 |
+
design_loads['ventilation_latent']
|
481 |
+
)
|
482 |
+
|
483 |
+
total = total_sensible + total_latent
|
484 |
+
|
485 |
+
return {
|
486 |
+
'total_sensible': total_sensible,
|
487 |
+
'total_latent': total_latent,
|
488 |
+
'total': total
|
489 |
+
}
|
490 |
+
|
491 |
+
except Exception as e:
|
492 |
+
if self.debug_mode:
|
493 |
+
logger.error(f"Error in calculate_cooling_load_summary: {str(e)}")
|
494 |
+
raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
|
495 |
+
|
496 |
+
def calculate_wall_cooling_load(
|
497 |
+
self,
|
498 |
+
wall: Wall,
|
499 |
+
outdoor_temp: float,
|
500 |
+
indoor_temp: float,
|
501 |
+
month: str,
|
502 |
+
hour: int,
|
503 |
+
latitude: str,
|
504 |
+
solar_absorptivity: float
|
505 |
+
) -> float:
|
506 |
+
"""
|
507 |
+
Calculate cooling load for a wall using CLTD method.
|
508 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.10.
|
509 |
+
|
510 |
+
Args:
|
511 |
+
wall: Wall component
|
512 |
+
outdoor_temp: Outdoor temperature (°C)
|
513 |
+
indoor_temp: Indoor temperature (°C)
|
514 |
+
month: Design month
|
515 |
+
hour: Hour of the day
|
516 |
+
latitude: Latitude (e.g., '24N')
|
517 |
+
solar_absorptivity: Solar absorptivity of the wall surface (0.0 to 1.0)
|
518 |
+
|
519 |
+
Returns:
|
520 |
+
Cooling load in Watts
|
521 |
+
"""
|
522 |
+
try:
|
523 |
+
latitude = self.validate_latitude(latitude)
|
524 |
+
month = self.validate_month(month)
|
525 |
+
hour = self.validate_hour(hour)
|
526 |
+
wall_group = str(wall.wall_group).upper() if hasattr(wall, 'wall_group') else 'A'
|
527 |
+
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D', '5': 'E', '6': 'F', '7': 'G', '8': 'H'}
|
528 |
+
|
529 |
+
if wall_group in numeric_map:
|
530 |
+
wall_group = numeric_map[wall_group]
|
531 |
+
if self.debug_mode:
|
532 |
+
logger.info(f"Mapped wall_group {wall.wall_group} to {wall_group}")
|
533 |
+
elif wall_group not in self.valid_wall_groups:
|
534 |
+
if self.debug_mode:
|
535 |
+
logger.warning(f"Invalid wall group: {wall_group}. Defaulting to 'A'")
|
536 |
+
wall_group = 'A'
|
537 |
+
|
538 |
+
try:
|
539 |
+
lat_value = float(latitude.replace('N', ''))
|
540 |
+
if self.debug_mode:
|
541 |
+
logger.debug(f"Converted latitude {latitude} to {lat_value} for wall CLTD")
|
542 |
+
except ValueError:
|
543 |
+
if self.debug_mode:
|
544 |
+
logger.error(f"Invalid latitude format: {latitude}. Defaulting to 32.0")
|
545 |
+
lat_value = 32.0
|
546 |
+
|
547 |
+
if self.debug_mode:
|
548 |
+
logger.debug(f"Calling get_cltd for wall: group={wall_group}, orientation={wall.orientation.value}, hour={hour}, latitude={lat_value}, solar_absorptivity={solar_absorptivity}")
|
549 |
+
|
550 |
+
try:
|
551 |
+
cltd_f = self.ashrae_tables.get_cltd(
|
552 |
+
element_type='wall',
|
553 |
+
group=wall_group,
|
554 |
+
orientation=wall.orientation.value,
|
555 |
+
hour=hour,
|
556 |
+
latitude=lat_value,
|
557 |
+
solar_absorptivity=solar_absorptivity
|
558 |
+
)
|
559 |
+
cltd = (cltd_f - 32) * 5 / 9 # Convert °F to °C
|
560 |
+
except Exception as e:
|
561 |
+
if self.debug_mode:
|
562 |
+
logger.error(f"get_cltd failed for wall_group={wall_group}, latitude={lat_value}: {str(e)}")
|
563 |
+
logger.warning("Using default CLTD=8.0°C")
|
564 |
+
cltd = 8.0
|
565 |
+
|
566 |
+
load = wall.u_value * wall.area * cltd
|
567 |
+
if self.debug_mode:
|
568 |
+
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
|
569 |
+
return max(load, 0.0)
|
570 |
+
|
571 |
+
except Exception as e:
|
572 |
+
if self.debug_mode:
|
573 |
+
logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
|
574 |
+
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
|
575 |
+
|
576 |
+
def calculate_roof_cooling_load(
|
577 |
+
self,
|
578 |
+
roof: Roof,
|
579 |
+
outdoor_temp: float,
|
580 |
+
indoor_temp: float,
|
581 |
+
month: str,
|
582 |
+
hour: int,
|
583 |
+
latitude: str,
|
584 |
+
solar_absorptivity: float
|
585 |
+
) -> float:
|
586 |
+
"""
|
587 |
+
Calculate cooling load for a roof using CLTD method.
|
588 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.10.
|
589 |
+
|
590 |
+
Args:
|
591 |
+
roof: Roof component
|
592 |
+
outdoor_temp: Outdoor temperature (°C)
|
593 |
+
indoor_temp: Indoor temperature (°C)
|
594 |
+
month: Design month
|
595 |
+
hour: Hour of the day
|
596 |
+
latitude: Latitude (e.g., '24N')
|
597 |
+
solar_absorptivity: Solar absorptivity of the roof surface (0.0 to 1.0)
|
598 |
+
|
599 |
+
Returns:
|
600 |
+
Cooling load in Watts
|
601 |
+
"""
|
602 |
+
try:
|
603 |
+
latitude = self.validate_latitude(latitude)
|
604 |
+
month = self.validate_month(month)
|
605 |
+
hour = self.validate_hour(hour)
|
606 |
+
roof_group = str(roof.roof_group).upper() if hasattr(roof, 'roof_group') else 'A'
|
607 |
+
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D', '5': 'E', '6': 'F', '7': 'G', '8': 'G'}
|
608 |
+
|
609 |
+
if roof_group in numeric_map:
|
610 |
+
roof_group = numeric_map[roof_group]
|
611 |
+
if self.debug_mode:
|
612 |
+
logger.info(f"Mapped roof_group {roof.roof_group} to {roof_group}")
|
613 |
+
elif roof_group not in self.valid_roof_groups:
|
614 |
+
if self.debug_mode:
|
615 |
+
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
|
616 |
+
roof_group = 'A'
|
617 |
+
|
618 |
+
try:
|
619 |
+
lat_value = float(latitude.replace('N', ''))
|
620 |
+
if self.debug_mode:
|
621 |
+
logger.debug(f"Converted latitude {latitude} to {lat_value} for roof CLTD")
|
622 |
+
except ValueError:
|
623 |
+
if self.debug_mode:
|
624 |
+
logger.error(f"Invalid latitude format: {latitude}. Defaulting to 32.0")
|
625 |
+
lat_value = 32.0
|
626 |
+
|
627 |
+
if self.debug_mode:
|
628 |
+
logger.debug(f"Calling get_cltd for roof: group={roof_group}, orientation={roof.orientation.value}, hour={hour}, latitude={lat_value}, solar_absorptivity={solar_absorptivity}")
|
629 |
+
|
630 |
+
try:
|
631 |
+
cltd_f = self.ashrae_tables.get_cltd(
|
632 |
+
element_type='roof',
|
633 |
+
group=roof_group,
|
634 |
+
orientation=roof.orientation.value,
|
635 |
+
hour=hour,
|
636 |
+
latitude=lat_value,
|
637 |
+
solar_absorptivity=solar_absorptivity
|
638 |
+
)
|
639 |
+
cltd = (cltd_f - 32) * 5 / 9 # Convert °F to °C
|
640 |
+
except Exception as e:
|
641 |
+
if self.debug_mode:
|
642 |
+
logger.error(f"get_cltd failed for roof_group={roof_group}, latitude={lat_value}: {str(e)}")
|
643 |
+
logger.warning("Using default CLTD=8.0°C")
|
644 |
+
cltd = 8.0
|
645 |
+
|
646 |
+
load = roof.u_value * roof.area * cltd
|
647 |
+
if self.debug_mode:
|
648 |
+
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
|
649 |
+
return max(load, 0.0)
|
650 |
+
|
651 |
+
except Exception as e:
|
652 |
+
if self.debug_mode:
|
653 |
+
logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
|
654 |
+
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
|
655 |
+
|
656 |
+
def calculate_window_cooling_load(
|
657 |
+
self,
|
658 |
+
window: Window,
|
659 |
+
outdoor_temp: float,
|
660 |
+
indoor_temp: float,
|
661 |
+
month: str,
|
662 |
+
hour: int,
|
663 |
+
latitude: str,
|
664 |
+
shading_coefficient: float,
|
665 |
+
adjusted_shgc: Optional[float] = None
|
666 |
+
) -> Dict[str, float]:
|
667 |
+
"""
|
668 |
+
Calculate cooling load for a window (conduction and solar).
|
669 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.12-18.13.
|
670 |
+
|
671 |
+
Args:
|
672 |
+
window: Window component
|
673 |
+
outdoor_temp: Outdoor temperature (°C)
|
674 |
+
indoor_temp: Indoor temperature (°C)
|
675 |
+
month: Design month
|
676 |
+
hour: Hour of the day
|
677 |
+
latitude: Latitude (e.g., '24N')
|
678 |
+
shading_coefficient: Default shading coefficient
|
679 |
+
adjusted_shgc: Adjusted SHGC from external drapery calculation (optional)
|
680 |
+
|
681 |
+
Returns:
|
682 |
+
Dictionary with conduction, solar, and total loads in Watts
|
683 |
+
"""
|
684 |
+
try:
|
685 |
+
latitude = self.validate_latitude(latitude)
|
686 |
+
month = self.validate_month(month)
|
687 |
+
hour = self.validate_hour(hour)
|
688 |
+
if self.debug_mode:
|
689 |
+
logger.debug(f"calculate_window_cooling_load: latitude={latitude}, month={month}, hour={hour}, orientation={window.orientation.value}")
|
690 |
+
|
691 |
+
# Conduction load
|
692 |
+
cltd = outdoor_temp - indoor_temp
|
693 |
+
conduction_load = window.u_value * window.area * cltd
|
694 |
+
|
695 |
+
# Determine shading coefficient
|
696 |
+
effective_shading_coefficient = adjusted_shgc if adjusted_shgc is not None else shading_coefficient
|
697 |
+
if adjusted_shgc is None and hasattr(window, 'drapery') and window.drapery and window.drapery.enabled:
|
698 |
+
try:
|
699 |
+
effective_shading_coefficient = window.drapery.get_shading_coefficient(window.shgc)
|
700 |
+
if self.debug_mode:
|
701 |
+
logger.debug(f"Using drapery shading coefficient: {effective_shading_coefficient}")
|
702 |
+
except Exception as e:
|
703 |
+
if self.debug_mode:
|
704 |
+
logger.warning(f"Error getting drapery shading coefficient: {str(e)}. Using default shading_coefficient={shading_coefficient}")
|
705 |
+
else:
|
706 |
+
if self.debug_mode:
|
707 |
+
logger.debug(f"Using shading coefficient: {effective_shading_coefficient} (adjusted_shgc={adjusted_shgc}, drapery={'enabled' if hasattr(window, 'drapery') and window.drapery and window.drapery.enabled else 'disabled'})")
|
708 |
+
|
709 |
+
# Solar load
|
710 |
+
try:
|
711 |
+
scl = self.ashrae_tables.get_scl(
|
712 |
+
latitude=latitude,
|
713 |
+
month=month,
|
714 |
+
orientation=window.orientation.value,
|
715 |
+
hour=hour
|
716 |
+
)
|
717 |
+
except Exception as e:
|
718 |
+
if self.debug_mode:
|
719 |
+
logger.error(f"get_scl failed for latitude={latitude}, month={month}, orientation={window.orientation.value}: {str(e)}")
|
720 |
+
logger.warning("Using default SCL=100 W/m²")
|
721 |
+
scl = 100.0
|
722 |
+
|
723 |
+
solar_load = window.area * window.shgc * effective_shading_coefficient * scl
|
724 |
+
|
725 |
+
total_load = conduction_load + solar_load
|
726 |
+
if self.debug_mode:
|
727 |
+
logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}, total={total_load}, effective_shading_coefficient={effective_shading_coefficient}")
|
728 |
+
|
729 |
+
return {
|
730 |
+
'conduction': max(conduction_load, 0.0),
|
731 |
+
'solar': max(solar_load, 0.0),
|
732 |
+
'total': max(total_load, 0.0)
|
733 |
+
}
|
734 |
+
|
735 |
+
except Exception as e:
|
736 |
+
if self.debug_mode:
|
737 |
+
logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
|
738 |
+
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
|
739 |
+
|
740 |
+
def calculate_skylight_cooling_load(
|
741 |
+
self,
|
742 |
+
skylight: Skylight,
|
743 |
+
outdoor_temp: float,
|
744 |
+
indoor_temp: float,
|
745 |
+
month: str,
|
746 |
+
hour: int,
|
747 |
+
latitude: str,
|
748 |
+
shading_coefficient: float,
|
749 |
+
adjusted_shgc: Optional[float] = None
|
750 |
+
) -> Dict[str, float]:
|
751 |
+
"""
|
752 |
+
Calculate cooling load for a skylight (conduction and solar).
|
753 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.12-18.13.
|
754 |
+
|
755 |
+
Args:
|
756 |
+
skylight: Skylight component
|
757 |
+
outdoor_temp: Outdoor temperature (°C)
|
758 |
+
indoor_temp: Indoor temperature (°C)
|
759 |
+
month: Design month
|
760 |
+
hour: Hour of the day
|
761 |
+
latitude: Latitude (e.g., '24N')
|
762 |
+
shading_coefficient: Default shading coefficient
|
763 |
+
adjusted_shgc: Adjusted SHGC from external drapery calculation (optional)
|
764 |
+
|
765 |
+
Returns:
|
766 |
+
Dictionary with conduction, solar, and total loads in Watts
|
767 |
+
"""
|
768 |
+
try:
|
769 |
+
latitude = self.validate_latitude(latitude)
|
770 |
+
month = self.validate_month(month)
|
771 |
+
hour = self.validate_hour(hour)
|
772 |
+
if self.debug_mode:
|
773 |
+
logger.debug(f"calculate_skylight_cooling_load: latitude={latitude}, month={month}, hour={hour}, orientation=Horizontal")
|
774 |
+
|
775 |
+
# Conduction load
|
776 |
+
cltd = outdoor_temp - indoor_temp
|
777 |
+
conduction_load = skylight.u_value * skylight.area * cltd
|
778 |
+
|
779 |
+
# Determine shading coefficient
|
780 |
+
effective_shading_coefficient = adjusted_shgc if adjusted_shgc is not None else shading_coefficient
|
781 |
+
if adjusted_shgc is None and hasattr(skylight, 'drapery') and skylight.drapery and skylight.drapery.enabled:
|
782 |
+
try:
|
783 |
+
effective_shading_coefficient = skylight.drapery.get_shading_coefficient(skylight.shgc)
|
784 |
+
if self.debug_mode:
|
785 |
+
logger.debug(f"Using drapery shading coefficient: {effective_shading_coefficient}")
|
786 |
+
except Exception as e:
|
787 |
+
if self.debug_mode:
|
788 |
+
logger.warning(f"Error getting drapery shading coefficient: {str(e)}. Using default shading_coefficient={shading_coefficient}")
|
789 |
+
else:
|
790 |
+
if self.debug_mode:
|
791 |
+
logger.debug(f"Using shading coefficient: {effective_shading_coefficient} (adjusted_shgc={adjusted_shgc}, drapery={'enabled' if hasattr(skylight, 'drapery') and skylight.drapery and skylight.drapery.enabled else 'disabled'})")
|
792 |
+
|
793 |
+
# Solar load
|
794 |
+
try:
|
795 |
+
scl = self.ashrae_tables.get_scl(
|
796 |
+
latitude=latitude,
|
797 |
+
month=month,
|
798 |
+
orientation='Horizontal',
|
799 |
+
hour=hour
|
800 |
+
)
|
801 |
+
except Exception as e:
|
802 |
+
if self.debug_mode:
|
803 |
+
logger.error(f"get_scl failed for latitude={latitude}, month={month}, orientation=Horizontal: {str(e)}")
|
804 |
+
logger.warning("Using default SCL=100 W/m²")
|
805 |
+
scl = 100.0
|
806 |
+
|
807 |
+
solar_load = skylight.area * skylight.shgc * effective_shading_coefficient * scl
|
808 |
+
|
809 |
+
total_load = conduction_load + solar_load
|
810 |
+
if self.debug_mode:
|
811 |
+
logger.debug(f"Skylight load: conduction={conduction_load}, solar={solar_load}, total={total_load}, effective_shading_coefficient={effective_shading_coefficient}")
|
812 |
+
|
813 |
+
return {
|
814 |
+
'conduction': max(conduction_load, 0.0),
|
815 |
+
'solar': max(solar_load, 0.0),
|
816 |
+
'total': max(total_load, 0.0)
|
817 |
+
}
|
818 |
+
|
819 |
+
except Exception as e:
|
820 |
+
if self.debug_mode:
|
821 |
+
logger.error(f"Error in calculate_skylight_cooling_load: {str(e)}")
|
822 |
+
raise Exception(f"Error in calculate_skylight_cooling_load: {str(e)}")
|
823 |
+
|
824 |
+
def calculate_door_cooling_load(
|
825 |
+
self,
|
826 |
+
door: Door,
|
827 |
+
outdoor_temp: float,
|
828 |
+
indoor_temp: float
|
829 |
+
) -> float:
|
830 |
+
"""
|
831 |
+
Calculate cooling load for a door.
|
832 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1.
|
833 |
+
|
834 |
+
Args:
|
835 |
+
door: Door component
|
836 |
+
outdoor_temp: Outdoor temperature (°C)
|
837 |
+
indoor_temp: Indoor temperature (°C)
|
838 |
+
|
839 |
+
Returns:
|
840 |
+
Cooling load in Watts
|
841 |
+
"""
|
842 |
+
try:
|
843 |
+
if self.debug_mode:
|
844 |
+
logger.debug(f"calculate_door_cooling_load: u_value={door.u_value}, area={door.area}")
|
845 |
+
|
846 |
+
cltd = outdoor_temp - indoor_temp
|
847 |
+
load = door.u_value * door.area * cltd
|
848 |
+
if self.debug_mode:
|
849 |
+
logger.debug(f"Door load: cltd={cltd}, load={load}")
|
850 |
+
return max(load, 0.0)
|
851 |
+
|
852 |
+
except Exception as e:
|
853 |
+
if self.debug_mode:
|
854 |
+
logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
|
855 |
+
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
|
856 |
+
|
857 |
+
def calculate_people_cooling_load(
|
858 |
+
self,
|
859 |
+
num_people: int,
|
860 |
+
activity_level: str,
|
861 |
+
hour: int
|
862 |
+
) -> Dict[str, float]:
|
863 |
+
"""
|
864 |
+
Calculate cooling load from people.
|
865 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Table 18.4.
|
866 |
+
|
867 |
+
Args:
|
868 |
+
num_people: Number of people
|
869 |
+
activity_level: Activity level ('Seated/Resting', 'Light Work', etc.)
|
870 |
+
hour: Hour of the day
|
871 |
+
|
872 |
+
Returns:
|
873 |
+
Dictionary with sensible and latent loads in Watts
|
874 |
+
"""
|
875 |
+
try:
|
876 |
+
hour = self.validate_hour(hour)
|
877 |
+
if self.debug_mode:
|
878 |
+
logger.debug(f"calculate_people_cooling_load: num_people={num_people}, activity_level={activity_level}, hour={hour}")
|
879 |
+
|
880 |
+
heat_gains = {
|
881 |
+
'Seated/Resting': {'sensible': 70, 'latent': 45},
|
882 |
+
'Light Work': {'sensible': 85, 'latent': 65},
|
883 |
+
'Moderate Work': {'sensible': 100, 'latent': 100},
|
884 |
+
'Heavy Work': {'sensible': 145, 'latent': 170}
|
885 |
+
}
|
886 |
+
|
887 |
+
gains = heat_gains.get(activity_level, heat_gains['Seated/Resting'])
|
888 |
+
if activity_level not in heat_gains:
|
889 |
+
if self.debug_mode:
|
890 |
+
logger.warning(f"Invalid activity_level: {activity_level}. Defaulting to 'Seated/Resting'")
|
891 |
+
|
892 |
+
try:
|
893 |
+
clf = self.ashrae_tables.get_clf_people(
|
894 |
+
zone_type='A',
|
895 |
+
hours_occupied='6h',
|
896 |
+
hour=hour
|
897 |
+
)
|
898 |
+
except Exception as e:
|
899 |
+
if self.debug_mode:
|
900 |
+
logger.error(f"get_clf_people failed: {str(e)}")
|
901 |
+
logger.warning("Using default CLF=0.5")
|
902 |
+
clf = 0.5
|
903 |
+
|
904 |
+
sensible_load = num_people * gains['sensible'] * clf
|
905 |
+
latent_load = num_people * gains['latent']
|
906 |
+
if self.debug_mode:
|
907 |
+
logger.debug(f"People load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
|
908 |
+
|
909 |
+
return {
|
910 |
+
'sensible': max(sensible_load, 0.0),
|
911 |
+
'latent': max(latent_load, 0.0)
|
912 |
+
}
|
913 |
+
|
914 |
+
except Exception as e:
|
915 |
+
if self.debug_mode:
|
916 |
+
logger.error(f"Error in calculate_people_cooling_load: {str(e)}")
|
917 |
+
raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
|
918 |
+
|
919 |
+
def calculate_lights_cooling_load(
|
920 |
+
self,
|
921 |
+
power: float,
|
922 |
+
use_factor: float,
|
923 |
+
special_allowance: float,
|
924 |
+
hour: int
|
925 |
+
) -> float:
|
926 |
+
"""
|
927 |
+
Calculate cooling load from lighting.
|
928 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Table 18.5.
|
929 |
+
|
930 |
+
Args:
|
931 |
+
power: Total lighting power (W)
|
932 |
+
use_factor: Usage factor (0.0 to 1.0)
|
933 |
+
special_allowance: Special allowance factor
|
934 |
+
hour: Hour of the day
|
935 |
+
|
936 |
+
Returns:
|
937 |
+
Cooling load in Watts
|
938 |
+
"""
|
939 |
+
try:
|
940 |
+
hour = self.validate_hour(hour)
|
941 |
+
if self.debug_mode:
|
942 |
+
logger.debug(f"calculate_lights_cooling_load: power={power}, use_factor={use_factor}, special_allowance={special_allowance}, hour={hour}")
|
943 |
+
|
944 |
+
try:
|
945 |
+
clf = self.ashrae_tables.get_clf_lights(
|
946 |
+
zone_type='A',
|
947 |
+
hours_occupied='6h',
|
948 |
+
hour=hour
|
949 |
+
)
|
950 |
+
except Exception as e:
|
951 |
+
if self.debug_mode:
|
952 |
+
logger.error(f"get_clf_lights failed: {str(e)}")
|
953 |
+
logger.warning("Using default CLF=0.8")
|
954 |
+
clf = 0.8
|
955 |
+
|
956 |
+
load = power * use_factor * special_allowance * clf
|
957 |
+
if self.debug_mode:
|
958 |
+
logger.debug(f"Lights load: clf={clf}, load={load}")
|
959 |
+
return max(load, 0.0)
|
960 |
+
|
961 |
+
except Exception as e:
|
962 |
+
if self.debug_mode:
|
963 |
+
logger.error(f"Error in calculate_lights_cooling_load: {str(e)}")
|
964 |
+
raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
|
965 |
+
|
966 |
+
def calculate_equipment_cooling_load(
|
967 |
+
self,
|
968 |
+
power: float,
|
969 |
+
use_factor: float,
|
970 |
+
radiation_factor: float,
|
971 |
+
hour: int
|
972 |
+
) -> Dict[str, float]:
|
973 |
+
"""
|
974 |
+
Calculate cooling load from equipment.
|
975 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Table 18.6.
|
976 |
+
|
977 |
+
Args:
|
978 |
+
power: Total equipment power (W)
|
979 |
+
use_factor: Usage factor (0.0 to 1.0)
|
980 |
+
radiation_factor: Radiation factor (0.0 to 1.0)
|
981 |
+
hour: Hour of the day
|
982 |
+
|
983 |
+
Returns:
|
984 |
+
Dictionary with sensible and latent loads in Watts
|
985 |
+
"""
|
986 |
+
try:
|
987 |
+
hour = self.validate_hour(hour)
|
988 |
+
if self.debug_mode:
|
989 |
+
logger.debug(f"calculate_equipment_cooling_load: power={power}, use_factor={use_factor}, radiation_factor={radiation_factor}, hour={hour}")
|
990 |
+
|
991 |
+
try:
|
992 |
+
clf = self.ashrae_tables.get_clf_equipment(
|
993 |
+
zone_type='A',
|
994 |
+
hours_operated='6h',
|
995 |
+
hour=hour
|
996 |
+
)
|
997 |
+
except Exception as e:
|
998 |
+
if self.debug_mode:
|
999 |
+
logger.error(f"get_clf_equipment failed: {str(e)}")
|
1000 |
+
logger.warning("Using default CLF=0.7")
|
1001 |
+
clf = 0.7
|
1002 |
+
|
1003 |
+
sensible_load = power * use_factor * radiation_factor * clf
|
1004 |
+
latent_load = power * use_factor * (1 - radiation_factor)
|
1005 |
+
if self.debug_mode:
|
1006 |
+
logger.debug(f"Equipment load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
|
1007 |
+
|
1008 |
+
return {
|
1009 |
+
'sensible': max(sensible_load, 0.0),
|
1010 |
+
'latent': max(latent_load, 0.0)
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
except Exception as e:
|
1014 |
+
if self.debug_mode:
|
1015 |
+
logger.error(f"Error in calculate_equipment_cooling_load: {str(e)}")
|
1016 |
+
raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
|
1017 |
+
|
1018 |
+
def calculate_infiltration_cooling_load(
|
1019 |
+
self,
|
1020 |
+
flow_rate: float,
|
1021 |
+
building_volume: float,
|
1022 |
+
outdoor_temp: float,
|
1023 |
+
outdoor_rh: float,
|
1024 |
+
indoor_temp: float,
|
1025 |
+
indoor_rh: float,
|
1026 |
+
p_atm: float = 101325
|
1027 |
+
) -> Dict[str, float]:
|
1028 |
+
"""
|
1029 |
+
Calculate cooling load from infiltration.
|
1030 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.5-18.6.
|
1031 |
+
|
1032 |
+
Args:
|
1033 |
+
flow_rate: Infiltration flow rate (m³/s)
|
1034 |
+
building_volume: Building volume (m³)
|
1035 |
+
outdoor_temp: Outdoor temperature (°C)
|
1036 |
+
outdoor_rh: Outdoor relative humidity (%)
|
1037 |
+
indoor_temp: Indoor temperature (°C)
|
1038 |
+
indoor_rh: Indoor relative humidity (%)
|
1039 |
+
p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
|
1040 |
+
|
1041 |
+
Returns:
|
1042 |
+
Dictionary with sensible and latent loads in Watts
|
1043 |
+
"""
|
1044 |
+
try:
|
1045 |
+
if self.debug_mode:
|
1046 |
+
logger.debug(f"calculate_infiltration_cooling_load: flow_rate={flow_rate}, building_volume={building_volume}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
|
1047 |
+
|
1048 |
+
self.validate_conditions(outdoor_temp, indoor_temp, outdoor_rh, indoor_rh)
|
1049 |
+
if flow_rate < 0 or building_volume <= 0:
|
1050 |
+
raise ValueError("Flow rate cannot be negative and building volume must be positive")
|
1051 |
+
|
1052 |
+
# Calculate air changes per hour (ACH)
|
1053 |
+
ach = (flow_rate * 3600) / building_volume if building_volume > 0 else 0.5
|
1054 |
+
if ach < 0:
|
1055 |
+
if self.debug_mode:
|
1056 |
+
logger.warning(f"Invalid ACH: {ach}. Defaulting to 0.5")
|
1057 |
+
ach = 0.5
|
1058 |
+
|
1059 |
+
# Calculate humidity ratio difference
|
1060 |
+
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh, p_atm)
|
1061 |
+
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh, p_atm)
|
1062 |
+
delta_w = max(0, outdoor_w - indoor_w)
|
1063 |
+
|
1064 |
+
# Calculate sensible and latent loads using heat_transfer methods
|
1065 |
+
sensible_load = self.heat_transfer.infiltration_heat_transfer(
|
1066 |
+
flow_rate, outdoor_temp - indoor_temp, indoor_temp, indoor_rh, p_atm
|
1067 |
+
)
|
1068 |
+
latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
|
1069 |
+
flow_rate, delta_w, indoor_temp, indoor_rh, p_atm
|
1070 |
+
)
|
1071 |
+
|
1072 |
+
if self.debug_mode:
|
1073 |
+
logger.debug(f"Infiltration load: sensible={sensible_load}, latent={latent_load}, ach={ach}, outdoor_w={outdoor_w}, indoor_w={indoor_w}")
|
1074 |
+
|
1075 |
+
return {
|
1076 |
+
'sensible': max(sensible_load, 0.0),
|
1077 |
+
'latent': max(latent_load, 0.0)
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
except Exception as e:
|
1081 |
+
if self.debug_mode:
|
1082 |
+
logger.error(f"Error in calculate_infiltration_cooling_load: {str(e)}")
|
1083 |
+
raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
|
1084 |
+
|
1085 |
+
def calculate_ventilation_cooling_load(
|
1086 |
+
self,
|
1087 |
+
flow_rate: float,
|
1088 |
+
outdoor_temp: float,
|
1089 |
+
outdoor_rh: float,
|
1090 |
+
indoor_temp: float,
|
1091 |
+
indoor_rh: float,
|
1092 |
+
p_atm: float = 101325
|
1093 |
+
) -> Dict[str, float]:
|
1094 |
+
"""
|
1095 |
+
Calculate cooling load from ventilation.
|
1096 |
+
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.5-18.6.
|
1097 |
+
|
1098 |
+
Args:
|
1099 |
+
flow_rate: Ventilation flow rate (m³/s)
|
1100 |
+
outdoor_temp: Outdoor temperature (°C)
|
1101 |
+
outdoor_rh: Outdoor relative humidity (%)
|
1102 |
+
indoor_temp: Indoor temperature (°C)
|
1103 |
+
indoor_rh: Indoor relative humidity (%)
|
1104 |
+
p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
|
1105 |
+
|
1106 |
+
Returns:
|
1107 |
+
Dictionary with sensible and latent loads in Watts
|
1108 |
+
"""
|
1109 |
+
try:
|
1110 |
+
if self.debug_mode:
|
1111 |
+
logger.debug(f"calculate_ventilation_cooling_load: flow_rate={flow_rate}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
|
1112 |
+
|
1113 |
+
self.validate_conditions(outdoor_temp, indoor_temp, outdoor_rh, indoor_rh)
|
1114 |
+
if flow_rate < 0:
|
1115 |
+
raise ValueError("Flow rate cannot be negative")
|
1116 |
+
|
1117 |
+
# Calculate humidity ratio difference
|
1118 |
+
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh, p_atm)
|
1119 |
+
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh, p_atm)
|
1120 |
+
delta_w = max(0, outdoor_w - indoor_w)
|
1121 |
+
|
1122 |
+
# Calculate sensible and latent loads using heat_transfer methods
|
1123 |
+
sensible_load = self.heat_transfer.infiltration_heat_transfer(
|
1124 |
+
flow_rate, outdoor_temp - indoor_temp, indoor_temp, indoor_rh, p_atm
|
1125 |
+
)
|
1126 |
+
latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
|
1127 |
+
flow_rate, delta_w, indoor_temp, indoor_rh, p_atm
|
1128 |
+
)
|
1129 |
+
|
1130 |
+
if self.debug_mode:
|
1131 |
+
logger.debug(f"Ventilation load: sensible={sensible_load}, latent={latent_load}, outdoor_w={outdoor_w}, indoor_w={indoor_w}")
|
1132 |
+
|
1133 |
+
return {
|
1134 |
+
'sensible': max(sensible_load, 0.0),
|
1135 |
+
'latent': max(latent_load, 0.0)
|
1136 |
+
}
|
1137 |
+
|
1138 |
+
except Exception as e:
|
1139 |
+
if self.debug_mode:
|
1140 |
+
logger.error(f"Error in calculate_ventilation_cooling_load: {str(e)}")
|
1141 |
+
raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
|
1142 |
+
|
1143 |
+
# Example usage
|
1144 |
+
if __name__ == "__main__":
|
1145 |
+
calculator = CoolingLoadCalculator(debug_mode=True)
|
1146 |
+
|
1147 |
+
# Example inputs
|
1148 |
+
components = {
|
1149 |
+
'walls': [Wall(id="w1", name="North Wall", area=20.0, u_value=0.5, orientation=Orientation.NORTH, wall_group='A', solar_absorptivity=0.6)],
|
1150 |
+
'roofs': [Roof(id="r1", name="Main Roof", area=100.0, u_value=0.3, orientation=Orientation.HORIZONTAL, roof_group='A', solar_absorptivity=0.6)],
|
1151 |
+
'windows': [Window(id="win1", name="South Window", area=10.0, u_value=2.8, orientation=Orientation.SOUTH, shgc=0.7, shading_coefficient=0.8)],
|
1152 |
+
'doors': [Door(id="d1", name="Main Door", area=2.0, u_value=2.0, orientation=Orientation.NORTH)]
|
1153 |
+
}
|
1154 |
+
outdoor_conditions = {
|
1155 |
+
'temperature': 35.0,
|
1156 |
+
'relative_humidity': 60.0,
|
1157 |
+
'latitude': '32N',
|
1158 |
+
'month': 'JUL'
|
1159 |
+
}
|
1160 |
+
indoor_conditions = {
|
1161 |
+
'temperature': 24.0,
|
1162 |
+
'relative_humidity': 50.0
|
1163 |
+
}
|
1164 |
+
internal_loads = {
|
1165 |
+
'people': {'number': 10, 'activity_level': 'Seated/Resting'},
|
1166 |
+
'lights': {'power': 1000.0, 'use_factor': 0.8, 'special_allowance': 1.0},
|
1167 |
+
'equipment': {'power': 500.0, 'use_factor': 0.7, 'radiation_factor': 0.5},
|
1168 |
+
'infiltration': {'flow_rate': 0.05},
|
1169 |
+
'ventilation': {'flow_rate': 0.1}
|
1170 |
+
}
|
1171 |
+
building_volume = 300.0
|
1172 |
+
|
1173 |
+
# Calculate hourly loads
|
1174 |
+
hourly_loads = calculator.calculate_hourly_cooling_loads(
|
1175 |
+
components, outdoor_conditions, indoor_conditions, internal_loads, building_volume
|
1176 |
+
)
|
1177 |
+
design_loads = calculator.calculate_design_cooling_load(hourly_loads)
|
1178 |
+
summary = calculator.calculate_cooling_load_summary(design_loads)
|
1179 |
+
|
1180 |
+
logger.info(f"Design Cooling Load Summary: {summary}")
|