Spaces:
Sleeping
Sleeping
Update utils/cooling_load.py
Browse files- utils/cooling_load.py +119 -30
utils/cooling_load.py
CHANGED
|
@@ -29,24 +29,76 @@ 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 |
|
| 33 |
-
def validate_latitude(self, latitude:
|
| 34 |
-
"""
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
return '24N'
|
| 41 |
-
return latitude
|
| 42 |
|
| 43 |
-
def validate_month(self, month:
|
| 44 |
-
"""
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
return 'JUL'
|
| 49 |
-
return month_upper
|
| 50 |
|
| 51 |
def calculate_hourly_cooling_loads(
|
| 52 |
self,
|
|
@@ -327,10 +379,16 @@ class CoolingLoadCalculator:
|
|
| 327 |
try:
|
| 328 |
latitude = self.validate_latitude(latitude)
|
| 329 |
month = self.validate_month(month)
|
| 330 |
-
logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
|
| 333 |
-
wall_group=
|
| 334 |
orientation=wall.orientation.value,
|
| 335 |
hour=hour,
|
| 336 |
color='Dark',
|
|
@@ -341,9 +399,11 @@ class CoolingLoadCalculator:
|
|
| 341 |
)
|
| 342 |
|
| 343 |
load = wall.u_value * wall.area * cltd
|
|
|
|
| 344 |
return max(load, 0.0)
|
| 345 |
|
| 346 |
except Exception as e:
|
|
|
|
| 347 |
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
|
| 348 |
|
| 349 |
def calculate_roof_cooling_load(
|
|
@@ -370,19 +430,15 @@ class CoolingLoadCalculator:
|
|
| 370 |
Cooling load in Watts
|
| 371 |
"""
|
| 372 |
try:
|
| 373 |
-
# Validate inputs
|
| 374 |
latitude = self.validate_latitude(latitude)
|
| 375 |
month = self.validate_month(month)
|
| 376 |
-
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}")
|
| 377 |
|
| 378 |
# Validate and map roof_group
|
| 379 |
-
|
| 380 |
-
roof_group
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
roof_group = 'A'
|
| 384 |
-
else:
|
| 385 |
-
raise ValueError(f"Invalid roof group: {roof_group}. Must be one of {valid_roof_groups}")
|
| 386 |
|
| 387 |
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
|
| 388 |
roof_group=roof_group,
|
|
@@ -395,9 +451,11 @@ class CoolingLoadCalculator:
|
|
| 395 |
)
|
| 396 |
|
| 397 |
load = roof.u_value * roof.area * cltd
|
|
|
|
| 398 |
return max(load, 0.0)
|
| 399 |
|
| 400 |
except Exception as e:
|
|
|
|
| 401 |
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
|
| 402 |
|
| 403 |
def calculate_window_cooling_load(
|
|
@@ -426,10 +484,9 @@ class CoolingLoadCalculator:
|
|
| 426 |
Dictionary with conduction and solar loads in Watts
|
| 427 |
"""
|
| 428 |
try:
|
| 429 |
-
# Validate inputs
|
| 430 |
scl_latitude = self.validate_latitude(latitude)
|
| 431 |
month_upper = self.validate_month(month)
|
| 432 |
-
logger.debug(f"calculate_window_cooling_load: latitude={scl_latitude}, month={month_upper}")
|
| 433 |
|
| 434 |
# Conduction load
|
| 435 |
delta_t = outdoor_temp - indoor_temp
|
|
@@ -474,6 +531,7 @@ class CoolingLoadCalculator:
|
|
| 474 |
|
| 475 |
solar_load = window.area * window.shgc * scl * shading_coefficient
|
| 476 |
|
|
|
|
| 477 |
return {
|
| 478 |
'conduction': max(conduction_load, 0.0),
|
| 479 |
'solar': max(solar_load, 0.0),
|
|
@@ -481,6 +539,7 @@ class CoolingLoadCalculator:
|
|
| 481 |
}
|
| 482 |
|
| 483 |
except Exception as e:
|
|
|
|
| 484 |
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
|
| 485 |
|
| 486 |
def calculate_door_cooling_load(
|
|
@@ -503,9 +562,11 @@ class CoolingLoadCalculator:
|
|
| 503 |
try:
|
| 504 |
delta_t = outdoor_temp - indoor_temp
|
| 505 |
load = door.u_value * door.area * delta_t
|
|
|
|
| 506 |
return max(load, 0.0)
|
| 507 |
|
| 508 |
except Exception as e:
|
|
|
|
| 509 |
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
|
| 510 |
|
| 511 |
def calculate_people_cooling_load(
|
|
@@ -526,19 +587,24 @@ class CoolingLoadCalculator:
|
|
| 526 |
Dictionary with sensible and latent loads in Watts
|
| 527 |
"""
|
| 528 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
sensible_gain = {
|
| 530 |
'Seated/Resting': 70,
|
| 531 |
'Light Work': 100,
|
| 532 |
'Moderate Work': 150,
|
| 533 |
'Heavy Work': 200
|
| 534 |
-
}
|
| 535 |
|
| 536 |
latent_gain = {
|
| 537 |
'Seated/Resting': 45,
|
| 538 |
'Light Work': 75,
|
| 539 |
'Moderate Work': 120,
|
| 540 |
'Heavy Work': 180
|
| 541 |
-
}
|
| 542 |
|
| 543 |
logger.debug(f"Calling get_clf_people with zone_type='A', hours_occupied='6h', hour={hour}")
|
| 544 |
try:
|
|
@@ -551,6 +617,7 @@ class CoolingLoadCalculator:
|
|
| 551 |
sensible_load = num_people * sensible_gain * clf
|
| 552 |
latent_load = num_people * latent_gain
|
| 553 |
|
|
|
|
| 554 |
return {
|
| 555 |
'sensible': max(sensible_load, 0.0),
|
| 556 |
'latent': max(latent_load, 0.0),
|
|
@@ -558,6 +625,7 @@ class CoolingLoadCalculator:
|
|
| 558 |
}
|
| 559 |
|
| 560 |
except Exception as e:
|
|
|
|
| 561 |
raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
|
| 562 |
|
| 563 |
def calculate_lights_cooling_load(
|
|
@@ -588,9 +656,11 @@ class CoolingLoadCalculator:
|
|
| 588 |
logger.warning("Using default CLF=1.0 for lights")
|
| 589 |
clf = 1.0
|
| 590 |
load = power * use_factor * special_allowance * clf
|
|
|
|
| 591 |
return max(load, 0.0)
|
| 592 |
|
| 593 |
except Exception as e:
|
|
|
|
| 594 |
raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
|
| 595 |
|
| 596 |
def calculate_equipment_cooling_load(
|
|
@@ -623,6 +693,7 @@ class CoolingLoadCalculator:
|
|
| 623 |
sensible_load = power * use_factor * radiation_factor * clf
|
| 624 |
latent_load = power * use_factor * (1 - radiation_factor)
|
| 625 |
|
|
|
|
| 626 |
return {
|
| 627 |
'sensible': max(sensible_load, 0.0),
|
| 628 |
'latent': max(latent_load, 0.0),
|
|
@@ -630,6 +701,7 @@ class CoolingLoadCalculator:
|
|
| 630 |
}
|
| 631 |
|
| 632 |
except Exception as e:
|
|
|
|
| 633 |
raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
|
| 634 |
|
| 635 |
def calculate_infiltration_cooling_load(
|
|
@@ -656,12 +728,17 @@ class CoolingLoadCalculator:
|
|
| 656 |
Dictionary with sensible and latent loads in Watts
|
| 657 |
"""
|
| 658 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 659 |
air_changes_per_hour = (flow_rate * 3600) / building_volume
|
| 660 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 661 |
|
| 662 |
# Calculate humidity ratio difference
|
| 663 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 664 |
try:
|
|
|
|
| 665 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 666 |
except Exception as e:
|
| 667 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
@@ -670,6 +747,7 @@ class CoolingLoadCalculator:
|
|
| 670 |
|
| 671 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 672 |
try:
|
|
|
|
| 673 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 674 |
except Exception as e:
|
| 675 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
@@ -678,6 +756,7 @@ class CoolingLoadCalculator:
|
|
| 678 |
|
| 679 |
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
|
| 680 |
|
|
|
|
| 681 |
return {
|
| 682 |
'sensible': max(sensible_load, 0.0),
|
| 683 |
'latent': max(latent_load, 0.0),
|
|
@@ -685,6 +764,7 @@ class CoolingLoadCalculator:
|
|
| 685 |
}
|
| 686 |
|
| 687 |
except Exception as e:
|
|
|
|
| 688 |
raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
|
| 689 |
|
| 690 |
def calculate_ventilation_cooling_load(
|
|
@@ -709,11 +789,16 @@ class CoolingLoadCalculator:
|
|
| 709 |
Dictionary with sensible and latent loads in Watts
|
| 710 |
"""
|
| 711 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 713 |
|
| 714 |
# Calculate humidity ratio difference
|
| 715 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 716 |
try:
|
|
|
|
| 717 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 718 |
except Exception as e:
|
| 719 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
@@ -722,6 +807,7 @@ class CoolingLoadCalculator:
|
|
| 722 |
|
| 723 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 724 |
try:
|
|
|
|
| 725 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 726 |
except Exception as e:
|
| 727 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
@@ -730,6 +816,7 @@ class CoolingLoadCalculator:
|
|
| 730 |
|
| 731 |
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
|
| 732 |
|
|
|
|
| 733 |
return {
|
| 734 |
'sensible': max(sensible_load, 0.0),
|
| 735 |
'latent': max(latent_load, 0.0),
|
|
@@ -737,6 +824,7 @@ class CoolingLoadCalculator:
|
|
| 737 |
}
|
| 738 |
|
| 739 |
except Exception as e:
|
|
|
|
| 740 |
raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
|
| 741 |
|
| 742 |
|
|
@@ -751,7 +839,7 @@ if __name__ == "__main__":
|
|
| 751 |
orientation=Orientation.NORTH,
|
| 752 |
area=20.0,
|
| 753 |
u_value=0.5,
|
| 754 |
-
wall_group="
|
| 755 |
)],
|
| 756 |
'roofs': [Roof(
|
| 757 |
name="Main Roof",
|
|
@@ -806,6 +894,7 @@ if __name__ == "__main__":
|
|
| 806 |
{'latitude': '24N', 'month': 'Jul'},
|
| 807 |
{'latitude': '36N', 'month': 'Jul'},
|
| 808 |
{'latitude': '48N', 'month': 'Jan'},
|
|
|
|
| 809 |
{'latitude': 'invalid', 'month': 'invalid'} # Test invalid inputs
|
| 810 |
]
|
| 811 |
|
|
|
|
| 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 = [str(i) for i in range(1, 41)] # ASHRAE wall groups 1-40
|
| 33 |
+
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
| 34 |
|
| 35 |
+
def validate_latitude(self, latitude: Any) -> str:
|
| 36 |
+
"""
|
| 37 |
+
Validate and normalize latitude input.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
latitude: Latitude input (str, float, or other)
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
Valid latitude string ('24N', '36N', or '48N')
|
| 44 |
+
"""
|
| 45 |
+
try:
|
| 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 '_' in latitude or '.' in latitude:
|
| 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:
|
| 62 |
+
return '36N'
|
| 63 |
+
else:
|
| 64 |
+
return '48N'
|
| 65 |
+
except ValueError:
|
| 66 |
+
logger.warning(f"Cannot parse latitude: {latitude}. Defaulting to '24N'")
|
| 67 |
+
return '24N'
|
| 68 |
+
|
| 69 |
+
if latitude not in self.valid_latitudes:
|
| 70 |
+
logger.warning(f"Invalid latitude: {latitude}. Defaulting to '24N'")
|
| 71 |
+
return '24N'
|
| 72 |
+
|
| 73 |
+
return latitude
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
logger.error(f"Error validating latitude {latitude}: {str(e)}")
|
| 77 |
return '24N'
|
|
|
|
| 78 |
|
| 79 |
+
def validate_month(self, month: Any) -> str:
|
| 80 |
+
"""
|
| 81 |
+
Validate and normalize month input.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
month: Month input (str or other)
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
Valid month string in uppercase
|
| 88 |
+
"""
|
| 89 |
+
try:
|
| 90 |
+
if not isinstance(month, str):
|
| 91 |
+
month = str(month)
|
| 92 |
+
|
| 93 |
+
month_upper = month.strip().upper()
|
| 94 |
+
if month_upper not in self.valid_months:
|
| 95 |
+
logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
|
| 96 |
+
return 'JUL'
|
| 97 |
+
return month_upper
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
logger.error(f"Error validating month {month}: {str(e)}")
|
| 101 |
return 'JUL'
|
|
|
|
| 102 |
|
| 103 |
def calculate_hourly_cooling_loads(
|
| 104 |
self,
|
|
|
|
| 379 |
try:
|
| 380 |
latitude = self.validate_latitude(latitude)
|
| 381 |
month = self.validate_month(month)
|
| 382 |
+
logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}, wall_group={wall.wall_group}, orientation={wall.orientation.value}")
|
| 383 |
+
|
| 384 |
+
# Validate wall_group
|
| 385 |
+
wall_group = str(wall.wall_group)
|
| 386 |
+
if wall_group not in self.valid_wall_groups:
|
| 387 |
+
logger.warning(f"Invalid wall group: {wall_group}. Defaulting to '1'")
|
| 388 |
+
wall_group = '1'
|
| 389 |
|
| 390 |
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
|
| 391 |
+
wall_group=wall_group,
|
| 392 |
orientation=wall.orientation.value,
|
| 393 |
hour=hour,
|
| 394 |
color='Dark',
|
|
|
|
| 399 |
)
|
| 400 |
|
| 401 |
load = wall.u_value * wall.area * cltd
|
| 402 |
+
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
|
| 403 |
return max(load, 0.0)
|
| 404 |
|
| 405 |
except Exception as e:
|
| 406 |
+
logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
|
| 407 |
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
|
| 408 |
|
| 409 |
def calculate_roof_cooling_load(
|
|
|
|
| 430 |
Cooling load in Watts
|
| 431 |
"""
|
| 432 |
try:
|
|
|
|
| 433 |
latitude = self.validate_latitude(latitude)
|
| 434 |
month = self.validate_month(month)
|
| 435 |
+
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, roof_group={roof.roof_group}")
|
| 436 |
|
| 437 |
# Validate and map roof_group
|
| 438 |
+
roof_group = str(roof.roof_group).upper()
|
| 439 |
+
if roof_group not in self.valid_roof_groups:
|
| 440 |
+
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
|
| 441 |
+
roof_group = 'A'
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
|
| 444 |
roof_group=roof_group,
|
|
|
|
| 451 |
)
|
| 452 |
|
| 453 |
load = roof.u_value * roof.area * cltd
|
| 454 |
+
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
|
| 455 |
return max(load, 0.0)
|
| 456 |
|
| 457 |
except Exception as e:
|
| 458 |
+
logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
|
| 459 |
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
|
| 460 |
|
| 461 |
def calculate_window_cooling_load(
|
|
|
|
| 484 |
Dictionary with conduction and solar loads in Watts
|
| 485 |
"""
|
| 486 |
try:
|
|
|
|
| 487 |
scl_latitude = self.validate_latitude(latitude)
|
| 488 |
month_upper = self.validate_month(month)
|
| 489 |
+
logger.debug(f"calculate_window_cooling_load: latitude={scl_latitude}, month={month_upper}, orientation={window.orientation.value}")
|
| 490 |
|
| 491 |
# Conduction load
|
| 492 |
delta_t = outdoor_temp - indoor_temp
|
|
|
|
| 531 |
|
| 532 |
solar_load = window.area * window.shgc * scl * shading_coefficient
|
| 533 |
|
| 534 |
+
logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}")
|
| 535 |
return {
|
| 536 |
'conduction': max(conduction_load, 0.0),
|
| 537 |
'solar': max(solar_load, 0.0),
|
|
|
|
| 539 |
}
|
| 540 |
|
| 541 |
except Exception as e:
|
| 542 |
+
logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
|
| 543 |
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
|
| 544 |
|
| 545 |
def calculate_door_cooling_load(
|
|
|
|
| 562 |
try:
|
| 563 |
delta_t = outdoor_temp - indoor_temp
|
| 564 |
load = door.u_value * door.area * delta_t
|
| 565 |
+
logger.debug(f"Door load: u_value={door.u_value}, area={door.area}, delta_t={delta_t}, load={load}")
|
| 566 |
return max(load, 0.0)
|
| 567 |
|
| 568 |
except Exception as e:
|
| 569 |
+
logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
|
| 570 |
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
|
| 571 |
|
| 572 |
def calculate_people_cooling_load(
|
|
|
|
| 587 |
Dictionary with sensible and latent loads in Watts
|
| 588 |
"""
|
| 589 |
try:
|
| 590 |
+
valid_activities = ['Seated/Resting', 'Light Work', 'Moderate Work', 'Heavy Work']
|
| 591 |
+
if activity_level not in valid_activities:
|
| 592 |
+
logger.warning(f"Invalid activity_level: {activity_level}. Defaulting to 'Seated/Resting'")
|
| 593 |
+
activity_level = 'Seated/Resting'
|
| 594 |
+
|
| 595 |
sensible_gain = {
|
| 596 |
'Seated/Resting': 70,
|
| 597 |
'Light Work': 100,
|
| 598 |
'Moderate Work': 150,
|
| 599 |
'Heavy Work': 200
|
| 600 |
+
}[activity_level]
|
| 601 |
|
| 602 |
latent_gain = {
|
| 603 |
'Seated/Resting': 45,
|
| 604 |
'Light Work': 75,
|
| 605 |
'Moderate Work': 120,
|
| 606 |
'Heavy Work': 180
|
| 607 |
+
}[activity_level]
|
| 608 |
|
| 609 |
logger.debug(f"Calling get_clf_people with zone_type='A', hours_occupied='6h', hour={hour}")
|
| 610 |
try:
|
|
|
|
| 617 |
sensible_load = num_people * sensible_gain * clf
|
| 618 |
latent_load = num_people * latent_gain
|
| 619 |
|
| 620 |
+
logger.debug(f"People load: num_people={num_people}, sensible={sensible_load}, latent={latent_load}")
|
| 621 |
return {
|
| 622 |
'sensible': max(sensible_load, 0.0),
|
| 623 |
'latent': max(latent_load, 0.0),
|
|
|
|
| 625 |
}
|
| 626 |
|
| 627 |
except Exception as e:
|
| 628 |
+
logger.error(f"Error in calculate_people_cooling_load: {str(e)}")
|
| 629 |
raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
|
| 630 |
|
| 631 |
def calculate_lights_cooling_load(
|
|
|
|
| 656 |
logger.warning("Using default CLF=1.0 for lights")
|
| 657 |
clf = 1.0
|
| 658 |
load = power * use_factor * special_allowance * clf
|
| 659 |
+
logger.debug(f"Lights load: power={power}, use_factor={use_factor}, special_allowance={special_allowance}, clf={clf}, load={load}")
|
| 660 |
return max(load, 0.0)
|
| 661 |
|
| 662 |
except Exception as e:
|
| 663 |
+
logger.error(f"Error in calculate_lights_cooling_load: {str(e)}")
|
| 664 |
raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
|
| 665 |
|
| 666 |
def calculate_equipment_cooling_load(
|
|
|
|
| 693 |
sensible_load = power * use_factor * radiation_factor * clf
|
| 694 |
latent_load = power * use_factor * (1 - radiation_factor)
|
| 695 |
|
| 696 |
+
logger.debug(f"Equipment load: power={power}, use_factor={use_factor}, radiation_factor={radiation_factor}, clf={clf}, sensible={sensible_load}, latent={latent_load}")
|
| 697 |
return {
|
| 698 |
'sensible': max(sensible_load, 0.0),
|
| 699 |
'latent': max(latent_load, 0.0),
|
|
|
|
| 701 |
}
|
| 702 |
|
| 703 |
except Exception as e:
|
| 704 |
+
logger.error(f"Error in calculate_equipment_cooling_load: {str(e)}")
|
| 705 |
raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
|
| 706 |
|
| 707 |
def calculate_infiltration_cooling_load(
|
|
|
|
| 728 |
Dictionary with sensible and latent loads in Watts
|
| 729 |
"""
|
| 730 |
try:
|
| 731 |
+
if flow_rate < 0 or building_volume <= 0:
|
| 732 |
+
logger.warning(f"Invalid inputs: flow_rate={flow_rate}, building_volume={building_volume}. Returning zero loads")
|
| 733 |
+
return {'sensible': 0.0, 'latent': 0.0, 'total': 0.0}
|
| 734 |
+
|
| 735 |
air_changes_per_hour = (flow_rate * 3600) / building_volume
|
| 736 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 737 |
|
| 738 |
# Calculate humidity ratio difference
|
| 739 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 740 |
try:
|
| 741 |
+
outdoor_rh = min(max(outdoor_rh, 0), 100) # Clamp RH to 0-100
|
| 742 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 743 |
except Exception as e:
|
| 744 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
|
|
| 747 |
|
| 748 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 749 |
try:
|
| 750 |
+
indoor_rh = min(max(indoor_rh, 0), 100) # Clamp RH to 0-100
|
| 751 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 752 |
except Exception as e:
|
| 753 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
|
|
| 756 |
|
| 757 |
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
|
| 758 |
|
| 759 |
+
logger.debug(f"Infiltration load: sensible={sensible_load}, latent={latent_load}")
|
| 760 |
return {
|
| 761 |
'sensible': max(sensible_load, 0.0),
|
| 762 |
'latent': max(latent_load, 0.0),
|
|
|
|
| 764 |
}
|
| 765 |
|
| 766 |
except Exception as e:
|
| 767 |
+
logger.error(f"Error in calculate_infiltration_cooling_load: {str(e)}")
|
| 768 |
raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
|
| 769 |
|
| 770 |
def calculate_ventilation_cooling_load(
|
|
|
|
| 789 |
Dictionary with sensible and latent loads in Watts
|
| 790 |
"""
|
| 791 |
try:
|
| 792 |
+
if flow_rate < 0:
|
| 793 |
+
logger.warning(f"Invalid flow_rate={flow_rate}. Returning zero loads")
|
| 794 |
+
return {'sensible': 0.0, 'latent': 0.0, 'total': 0.0}
|
| 795 |
+
|
| 796 |
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
|
| 797 |
|
| 798 |
# Calculate humidity ratio difference
|
| 799 |
logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
|
| 800 |
try:
|
| 801 |
+
outdoor_rh = min(max(outdoor_rh, 0), 100) # Clamp RH to 0-100
|
| 802 |
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
|
| 803 |
except Exception as e:
|
| 804 |
logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
|
|
|
|
| 807 |
|
| 808 |
logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
|
| 809 |
try:
|
| 810 |
+
indoor_rh = min(max(indoor_rh, 0), 100) # Clamp RH to 0-100
|
| 811 |
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
|
| 812 |
except Exception as e:
|
| 813 |
logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
|
|
|
|
| 816 |
|
| 817 |
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
|
| 818 |
|
| 819 |
+
logger.debug(f"Ventilation load: sensible={sensible_load}, latent={latent_load}")
|
| 820 |
return {
|
| 821 |
'sensible': max(sensible_load, 0.0),
|
| 822 |
'latent': max(latent_load, 0.0),
|
|
|
|
| 824 |
}
|
| 825 |
|
| 826 |
except Exception as e:
|
| 827 |
+
logger.error(f"Error in calculate_ventilation_cooling_load: {str(e)}")
|
| 828 |
raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
|
| 829 |
|
| 830 |
|
|
|
|
| 839 |
orientation=Orientation.NORTH,
|
| 840 |
area=20.0,
|
| 841 |
u_value=0.5,
|
| 842 |
+
wall_group="1"
|
| 843 |
)],
|
| 844 |
'roofs': [Roof(
|
| 845 |
name="Main Roof",
|
|
|
|
| 894 |
{'latitude': '24N', 'month': 'Jul'},
|
| 895 |
{'latitude': '36N', 'month': 'Jul'},
|
| 896 |
{'latitude': '48N', 'month': 'Jan'},
|
| 897 |
+
{'latitude': '1_31.973N', 'month': 'Jul'}, # Test invalid latitude
|
| 898 |
{'latitude': 'invalid', 'month': 'invalid'} # Test invalid inputs
|
| 899 |
]
|
| 900 |
|