Spaces:
Sleeping
Sleeping
Update utils/cooling_load.py
Browse files- utils/cooling_load.py +37 -36
utils/cooling_load.py
CHANGED
|
@@ -29,7 +29,7 @@ class CoolingLoadCalculator:
|
|
| 29 |
self.hours = list(range(24))
|
| 30 |
self.valid_latitudes = ['24N', '36N', '48N']
|
| 31 |
self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
|
| 32 |
-
self.valid_wall_groups = ['A', 'B', 'C', 'D']
|
| 33 |
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
| 34 |
|
| 35 |
def validate_latitude(self, latitude: Any) -> str:
|
|
@@ -46,16 +46,21 @@ class CoolingLoadCalculator:
|
|
| 46 |
if not isinstance(latitude, str):
|
| 47 |
latitude = str(latitude)
|
| 48 |
|
| 49 |
-
# Remove whitespace and convert to uppercase
|
| 50 |
latitude = latitude.strip().upper()
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
# Handle formats like '31.973N', '1_31.973N'
|
| 53 |
-
if '
|
| 54 |
-
# Extract numeric part
|
| 55 |
num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
|
| 56 |
try:
|
| 57 |
lat_val = float(num_part)
|
| 58 |
-
# Map to closest valid latitude
|
| 59 |
if lat_val <= 30:
|
| 60 |
return '24N'
|
| 61 |
elif lat_val <= 42:
|
|
@@ -142,7 +147,7 @@ class CoolingLoadCalculator:
|
|
| 142 |
# Validate inputs
|
| 143 |
latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
|
| 144 |
month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
|
| 145 |
-
logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}")
|
| 146 |
|
| 147 |
# Calculate loads for walls
|
| 148 |
for wall in building_components.get('walls', []):
|
|
@@ -384,7 +389,6 @@ class CoolingLoadCalculator:
|
|
| 384 |
# Validate wall_group
|
| 385 |
wall_group = str(wall.wall_group).upper()
|
| 386 |
if wall_group not in self.valid_wall_groups:
|
| 387 |
-
# Map numeric groups to letters (e.g., '1' -> 'A')
|
| 388 |
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
|
| 389 |
if wall_group in numeric_map:
|
| 390 |
wall_group = numeric_map[wall_group]
|
|
@@ -407,7 +411,7 @@ class CoolingLoadCalculator:
|
|
| 407 |
except Exception as e:
|
| 408 |
logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
|
| 409 |
logger.warning("Using default CLTD=10.0°C")
|
| 410 |
-
cltd = 10.0
|
| 411 |
|
| 412 |
load = wall.u_value * wall.area * cltd
|
| 413 |
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
|
|
@@ -445,7 +449,6 @@ class CoolingLoadCalculator:
|
|
| 445 |
month = self.validate_month(month)
|
| 446 |
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, roof_group={roof.roof_group}")
|
| 447 |
|
| 448 |
-
# Validate and map roof_group
|
| 449 |
roof_group = str(roof.roof_group).upper()
|
| 450 |
if roof_group not in self.valid_roof_groups:
|
| 451 |
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
|
|
@@ -464,7 +467,7 @@ class CoolingLoadCalculator:
|
|
| 464 |
except Exception as e:
|
| 465 |
logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
|
| 466 |
logger.warning("Using default CLTD=10.0°C")
|
| 467 |
-
cltd = 10.0
|
| 468 |
|
| 469 |
load = roof.u_value * roof.area * cltd
|
| 470 |
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
|
|
@@ -502,7 +505,7 @@ class CoolingLoadCalculator:
|
|
| 502 |
try:
|
| 503 |
scl_latitude = self.validate_latitude(latitude)
|
| 504 |
month_upper = self.validate_month(month)
|
| 505 |
-
logger.debug(f"calculate_window_cooling_load: latitude={scl_latitude}, month={month_upper}, orientation={window.orientation.value}")
|
| 506 |
|
| 507 |
# Conduction load
|
| 508 |
delta_t = outdoor_temp - indoor_temp
|
|
@@ -511,18 +514,17 @@ class CoolingLoadCalculator:
|
|
| 511 |
# Solar load
|
| 512 |
logger.debug(f"Calling get_scl with month={month_upper}, orientation={window.orientation.value}, hour={hour}, latitude={scl_latitude}")
|
| 513 |
|
| 514 |
-
# Try different month formats to handle potential get_scl issues
|
| 515 |
month_formats = [
|
| 516 |
-
(month_upper, "uppercase"),
|
| 517 |
-
(month_upper.capitalize(), "capitalized"),
|
| 518 |
(str({'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6,
|
| 519 |
-
'JUL': 7, 'AUG': 8, 'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12}[month_upper]), "numeric")
|
| 520 |
]
|
| 521 |
|
| 522 |
scl = None
|
| 523 |
for month_value, format_name in month_formats:
|
| 524 |
try:
|
| 525 |
-
logger.debug(f"Trying get_scl with month format '{format_name}': month={month_value}")
|
| 526 |
scl = self.ashrae_tables.get_scl(
|
| 527 |
latitude=scl_latitude,
|
| 528 |
month=month_value,
|
|
@@ -535,15 +537,19 @@ class CoolingLoadCalculator:
|
|
| 535 |
logger.warning(f"get_scl failed with month format '{format_name}': {str(e)}")
|
| 536 |
continue
|
| 537 |
|
| 538 |
-
# Final fallback: Use latitude='24N' with default month='JUL'
|
| 539 |
if scl is None:
|
| 540 |
logger.warning(f"All month formats failed. Retrying with fallback latitude='24N', month='JUL'")
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
|
| 548 |
solar_load = window.area * window.shgc * scl * shading_coefficient
|
| 549 |
|
|
@@ -751,10 +757,9 @@ class CoolingLoadCalculator:
|
|
| 751 |
air_changes_per_hour = (flow_rate * 3600) / building_volume
|
| 752 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 753 |
|
| 754 |
-
# Calculate humidity ratio difference
|
| 755 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 756 |
try:
|
| 757 |
-
outdoor_rh = min(max(outdoor_rh, 0), 100)
|
| 758 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 759 |
except Exception as e:
|
| 760 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
@@ -763,7 +768,7 @@ class CoolingLoadCalculator:
|
|
| 763 |
|
| 764 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 765 |
try:
|
| 766 |
-
indoor_rh = min(max(indoor_rh, 0), 100)
|
| 767 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 768 |
except Exception as e:
|
| 769 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
@@ -811,10 +816,9 @@ class CoolingLoadCalculator:
|
|
| 811 |
|
| 812 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 813 |
|
| 814 |
-
# Calculate humidity ratio difference
|
| 815 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 816 |
try:
|
| 817 |
-
outdoor_rh = min(max(outdoor_rh, 0), 100)
|
| 818 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 819 |
except Exception as e:
|
| 820 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
@@ -823,7 +827,7 @@ class CoolingLoadCalculator:
|
|
| 823 |
|
| 824 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 825 |
try:
|
| 826 |
-
indoor_rh = min(max(indoor_rh, 0), 100)
|
| 827 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 828 |
except Exception as e:
|
| 829 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
@@ -845,17 +849,15 @@ class CoolingLoadCalculator:
|
|
| 845 |
|
| 846 |
|
| 847 |
if __name__ == "__main__":
|
| 848 |
-
# Example usage for testing
|
| 849 |
calculator = CoolingLoadCalculator()
|
| 850 |
|
| 851 |
-
# Dummy inputs
|
| 852 |
building_components = {
|
| 853 |
'walls': [Wall(
|
| 854 |
name="North Wall",
|
| 855 |
orientation=Orientation.NORTH,
|
| 856 |
area=20.0,
|
| 857 |
u_value=0.5,
|
| 858 |
-
wall_group="1"
|
| 859 |
)],
|
| 860 |
'roofs': [Roof(
|
| 861 |
name="Main Roof",
|
|
@@ -905,13 +907,12 @@ if __name__ == "__main__":
|
|
| 905 |
|
| 906 |
building_volume = 300.0
|
| 907 |
|
| 908 |
-
# Test with multiple latitudes, months, and wall groups
|
| 909 |
test_cases = [
|
| 910 |
{'latitude': '24N', 'month': 'Jul', 'wall_group': 'A'},
|
| 911 |
{'latitude': '36N', 'month': 'Jul', 'wall_group': 'B'},
|
| 912 |
-
{'latitude': '48N', 'month': 'Jan', 'wall_group': '1'},
|
| 913 |
-
{'latitude': '
|
| 914 |
-
{'latitude': 'invalid', 'month': 'invalid', 'wall_group': '2'}
|
| 915 |
]
|
| 916 |
|
| 917 |
for case in test_cases:
|
|
|
|
| 29 |
self.hours = list(range(24))
|
| 30 |
self.valid_latitudes = ['24N', '36N', '48N']
|
| 31 |
self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
|
| 32 |
+
self.valid_wall_groups = ['A', 'B', 'C', 'D']
|
| 33 |
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
| 34 |
|
| 35 |
def validate_latitude(self, latitude: Any) -> str:
|
|
|
|
| 46 |
if not isinstance(latitude, str):
|
| 47 |
latitude = str(latitude)
|
| 48 |
|
|
|
|
| 49 |
latitude = latitude.strip().upper()
|
| 50 |
|
| 51 |
+
# Handle concatenated formats like '24N_JUL'
|
| 52 |
+
if '_' in latitude:
|
| 53 |
+
parts = latitude.split('_')
|
| 54 |
+
if len(parts) > 1:
|
| 55 |
+
lat_part = parts[0]
|
| 56 |
+
logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
|
| 57 |
+
latitude = lat_part
|
| 58 |
+
|
| 59 |
# Handle formats like '31.973N', '1_31.973N'
|
| 60 |
+
if '.' in latitude or any(c.isdigit() for c in latitude):
|
|
|
|
| 61 |
num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
|
| 62 |
try:
|
| 63 |
lat_val = float(num_part)
|
|
|
|
| 64 |
if lat_val <= 30:
|
| 65 |
return '24N'
|
| 66 |
elif lat_val <= 42:
|
|
|
|
| 147 |
# Validate inputs
|
| 148 |
latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
|
| 149 |
month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
|
| 150 |
+
logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
|
| 151 |
|
| 152 |
# Calculate loads for walls
|
| 153 |
for wall in building_components.get('walls', []):
|
|
|
|
| 389 |
# Validate wall_group
|
| 390 |
wall_group = str(wall.wall_group).upper()
|
| 391 |
if wall_group not in self.valid_wall_groups:
|
|
|
|
| 392 |
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
|
| 393 |
if wall_group in numeric_map:
|
| 394 |
wall_group = numeric_map[wall_group]
|
|
|
|
| 411 |
except Exception as e:
|
| 412 |
logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
|
| 413 |
logger.warning("Using default CLTD=10.0°C")
|
| 414 |
+
cltd = 10.0
|
| 415 |
|
| 416 |
load = wall.u_value * wall.area * cltd
|
| 417 |
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
|
|
|
|
| 449 |
month = self.validate_month(month)
|
| 450 |
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, roof_group={roof.roof_group}")
|
| 451 |
|
|
|
|
| 452 |
roof_group = str(roof.roof_group).upper()
|
| 453 |
if roof_group not in self.valid_roof_groups:
|
| 454 |
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
|
|
|
|
| 467 |
except Exception as e:
|
| 468 |
logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
|
| 469 |
logger.warning("Using default CLTD=10.0°C")
|
| 470 |
+
cltd = 10.0
|
| 471 |
|
| 472 |
load = roof.u_value * roof.area * cltd
|
| 473 |
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
|
|
|
|
| 505 |
try:
|
| 506 |
scl_latitude = self.validate_latitude(latitude)
|
| 507 |
month_upper = self.validate_month(month)
|
| 508 |
+
logger.debug(f"calculate_window_cooling_load: latitude={latitude} -> {scl_latitude}, month={month} -> {month_upper}, orientation={window.orientation.value}")
|
| 509 |
|
| 510 |
# Conduction load
|
| 511 |
delta_t = outdoor_temp - indoor_temp
|
|
|
|
| 514 |
# Solar load
|
| 515 |
logger.debug(f"Calling get_scl with month={month_upper}, orientation={window.orientation.value}, hour={hour}, latitude={scl_latitude}")
|
| 516 |
|
|
|
|
| 517 |
month_formats = [
|
| 518 |
+
(month_upper, "uppercase"),
|
| 519 |
+
(month_upper.capitalize(), "capitalized"),
|
| 520 |
(str({'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6,
|
| 521 |
+
'JUL': 7, 'AUG': 8, 'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12}[month_upper]), "numeric")
|
| 522 |
]
|
| 523 |
|
| 524 |
scl = None
|
| 525 |
for month_value, format_name in month_formats:
|
| 526 |
try:
|
| 527 |
+
logger.debug(f"Trying get_scl with month format '{format_name}': month={month_value}, latitude={scl_latitude}")
|
| 528 |
scl = self.ashrae_tables.get_scl(
|
| 529 |
latitude=scl_latitude,
|
| 530 |
month=month_value,
|
|
|
|
| 537 |
logger.warning(f"get_scl failed with month format '{format_name}': {str(e)}")
|
| 538 |
continue
|
| 539 |
|
|
|
|
| 540 |
if scl is None:
|
| 541 |
logger.warning(f"All month formats failed. Retrying with fallback latitude='24N', month='JUL'")
|
| 542 |
+
try:
|
| 543 |
+
scl = self.ashrae_tables.get_scl(
|
| 544 |
+
latitude='24N',
|
| 545 |
+
month='JUL',
|
| 546 |
+
orientation=window.orientation.value,
|
| 547 |
+
hour=hour
|
| 548 |
+
)
|
| 549 |
+
except Exception as e:
|
| 550 |
+
logger.error(f"Fallback get_scl failed: {str(e)}")
|
| 551 |
+
logger.warning("Using default SCL=100 W/m²")
|
| 552 |
+
scl = 100.0 # Conservative fallback
|
| 553 |
|
| 554 |
solar_load = window.area * window.shgc * scl * shading_coefficient
|
| 555 |
|
|
|
|
| 757 |
air_changes_per_hour = (flow_rate * 3600) / building_volume
|
| 758 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 759 |
|
|
|
|
| 760 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 761 |
try:
|
| 762 |
+
outdoor_rh = min(max(outdoor_rh, 0), 100)
|
| 763 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 764 |
except Exception as e:
|
| 765 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
|
|
| 768 |
|
| 769 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 770 |
try:
|
| 771 |
+
indoor_rh = min(max(indoor_rh, 0), 100)
|
| 772 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 773 |
except Exception as e:
|
| 774 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
|
|
| 816 |
|
| 817 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 818 |
|
|
|
|
| 819 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 820 |
try:
|
| 821 |
+
outdoor_rh = min(max(outdoor_rh, 0), 100)
|
| 822 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 823 |
except Exception as e:
|
| 824 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
|
|
| 827 |
|
| 828 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 829 |
try:
|
| 830 |
+
indoor_rh = min(max(indoor_rh, 0), 100)
|
| 831 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 832 |
except Exception as e:
|
| 833 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
|
|
| 849 |
|
| 850 |
|
| 851 |
if __name__ == "__main__":
|
|
|
|
| 852 |
calculator = CoolingLoadCalculator()
|
| 853 |
|
|
|
|
| 854 |
building_components = {
|
| 855 |
'walls': [Wall(
|
| 856 |
name="North Wall",
|
| 857 |
orientation=Orientation.NORTH,
|
| 858 |
area=20.0,
|
| 859 |
u_value=0.5,
|
| 860 |
+
wall_group="1"
|
| 861 |
)],
|
| 862 |
'roofs': [Roof(
|
| 863 |
name="Main Roof",
|
|
|
|
| 907 |
|
| 908 |
building_volume = 300.0
|
| 909 |
|
|
|
|
| 910 |
test_cases = [
|
| 911 |
{'latitude': '24N', 'month': 'Jul', 'wall_group': 'A'},
|
| 912 |
{'latitude': '36N', 'month': 'Jul', 'wall_group': 'B'},
|
| 913 |
+
{'latitude': '48N', 'month': 'Jan', 'wall_group': '1'},
|
| 914 |
+
{'latitude': '24N_JUL', 'month': 'Jul', 'wall_group': 'invalid'},
|
| 915 |
+
{'latitude': 'invalid', 'month': 'invalid', 'wall_group': '2'}
|
| 916 |
]
|
| 917 |
|
| 918 |
for case in test_cases:
|