Spaces:
Sleeping
Sleeping
""" | |
ASHRAE tables module for HVAC Load Calculator. | |
Integrates CLTD, SCL, CLF tables, cooling load calculations, climatic corrections, and visualization. | |
Combines data from original ashrae_tables.py and enhanced versions with ashrae_tables (3).py. | |
""" | |
from typing import Dict, List, Any, Optional, Tuple | |
import pandas as pd | |
import numpy as np | |
import os | |
import matplotlib.pyplot as plt | |
from enum import Enum | |
# Define paths | |
DATA_DIR = os.path.dirname(os.path.abspath(__file__)) | |
class WallGroup(Enum): | |
"""Enumeration for ASHRAE wall groups.""" | |
A = "A" # Light construction | |
B = "B" | |
C = "C" | |
D = "D" | |
E = "E" | |
F = "F" | |
G = "G" | |
H = "H" # Heavy construction | |
class RoofGroup(Enum): | |
"""Enumeration for ASHRAE roof groups.""" | |
A = "A" # Light construction | |
B = "B" | |
C = "C" | |
D = "D" | |
E = "E" | |
F = "F" | |
G = "G" # Heavy construction | |
class Orientation(Enum): | |
"""Enumeration for building component orientations.""" | |
N = "North" | |
NE = "Northeast" | |
E = "East" | |
SE = "Southeast" | |
S = "South" | |
SW = "Southwest" | |
W = "West" | |
NW = "Northwest" | |
HOR = "Horizontal" # For roofs and floors | |
class ASHRAETables: | |
"""Class for managing ASHRAE tables for load calculations.""" | |
def __init__(self): | |
"""Initialize ASHRAE tables.""" | |
# Load tables | |
self.cltd_wall = self._load_cltd_wall_table() | |
self.cltd_roof = self._load_cltd_roof_table() | |
self.scl = self._load_scl_table() | |
self.clf_lights = self._load_clf_lights_table() | |
self.clf_people = self._load_clf_people_table() | |
self.clf_equipment = self._load_clf_equipment_table() | |
self.heat_gain = self._load_heat_gain_table() | |
# Load correction factors | |
self.latitude_correction = self._load_latitude_correction() | |
self.color_correction = self._load_color_correction() | |
self.month_correction = self._load_month_correction() | |
# Load thermal properties and roof classifications | |
self.thermal_properties = self._load_thermal_properties() | |
self.roof_classifications = self._load_roof_classifications() | |
def _validate_cltd_inputs(self, group: str, orientation: str, hour: int, latitude: str, month: str, color: str, is_wall: bool = True) -> Tuple[bool, str]: | |
"""Validate inputs for CLTD calculations.""" | |
valid_groups = [e.value for e in WallGroup] if is_wall else [e.value for e in RoofGroup] | |
valid_orientations = [e.value for e in Orientation] | |
valid_latitudes = ['24N', '32N', '40N', '48N', '56N'] | |
valid_months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
valid_colors = ['Dark', 'Medium', 'Light'] | |
if group not in valid_groups: | |
return False, f"Invalid {'wall' if is_wall else 'roof'} group: {group}. Valid groups: {valid_groups}" | |
if orientation not in valid_orientations: | |
return False, f"Invalid orientation: {orientation}. Valid orientations: {valid_orientations}" | |
if hour not in range(24): | |
return False, "Hour must be between 0 and 23." | |
# Handle numeric latitude values and ensure comprehensive mapping | |
if latitude not in valid_latitudes: | |
# Try to convert numeric latitude to standard format | |
try: | |
# First, handle string representations that might contain direction indicators | |
if isinstance(latitude, str): | |
# Extract numeric part, removing 'N' or 'S' | |
lat_str = latitude.upper().strip() | |
num_part = ''.join(c for c in lat_str if c.isdigit() or c == '.') | |
lat_val = float(num_part) | |
# Adjust for southern hemisphere if needed | |
if 'S' in lat_str: | |
lat_val = -lat_val | |
else: | |
# Handle direct numeric input | |
lat_val = float(latitude) | |
# Take absolute value for mapping purposes | |
abs_lat = abs(lat_val) | |
# Map to the closest standard latitude | |
if abs_lat < 28: | |
mapped_latitude = '24N' | |
elif abs_lat < 36: | |
mapped_latitude = '32N' | |
elif abs_lat < 44: | |
mapped_latitude = '40N' | |
elif abs_lat < 52: | |
mapped_latitude = '48N' | |
else: | |
mapped_latitude = '56N' | |
# Use the mapped latitude for validation | |
latitude = mapped_latitude | |
except (ValueError, TypeError): | |
return False, f"Invalid latitude: {latitude}. Valid latitudes: {valid_latitudes}" | |
if latitude not in valid_latitudes: | |
return False, f"Invalid latitude: {latitude}. Valid latitudes: {valid_latitudes}" | |
if month not in valid_months: | |
return False, f"Invalid month: {month}. Valid months: {valid_months}" | |
if color not in valid_colors: | |
return False, f"Invalid color: {color}. Valid colors: {valid_colors}" | |
return True, "Valid inputs." | |
def _load_cltd_wall_table(self) -> Dict[str, pd.DataFrame]: | |
""" | |
Load CLTD tables for walls at 24°N (July). | |
Returns: Dictionary of DataFrames with CLTD values for each wall group. | |
""" | |
hours = list(range(24)) | |
# CLTD data for wall types 1-12 mapped to groups A-H | |
wall_data = { | |
"A": { # Type 1: Lightest construction | |
'N': [1, 0, -1, -2, -3, -2, 5, 13, 17, 18, 19, 22, 26, 28, 30, 32, 34, 34, 27, 17, 11, 7, 5, 3], | |
'NE': [1, 0, -1, -2, -3, 0, 17, 39, 51, 53, 48, 39, 32, 30, 30, 30, 30, 28, 24, 18, 13, 10, 7, 5], | |
'E': [1, 0, -1, -2, -3, 0, 18, 44, 59, 63, 59, 48, 36, 32, 31, 30, 32, 32, 29, 24, 19, 13, 10, 7], | |
'SE': [1, 0, -1, -2, -3, -2, 8, 25, 38, 44, 45, 42, 35, 32, 31, 30, 32, 32, 27, 24, 18, 13, 10, 7], | |
'S': [1, 0, -1, -2, -3, -3, -1, 3, 8, 12, 18, 24, 29, 31, 31, 30, 32, 32, 27, 23, 18, 13, 9, 7], | |
'SW': [1, 0, 1, 2, 3, 3, 1, 3, 8, 13, 17, 22, 27, 42, 59, 73, 30, 32, 27, 23, 18, 20, 12, 8], | |
'W': [2, 0, 2, 2, 3, 1, 3, 8, 13, 17, 22, 27, 42, 59, 73, 30, 32, 27, 23, 18, 20, 12, 8, 5], | |
'NW': [2, 0, 1, 2, 2, 3, 1, 3, 8, 13, 17, 22, 27, 42, 59, 73, 30, 32, 27, 23, 18, 20, 12, 8] | |
}, | |
"B": { # Type 2 | |
'N': [2, 1, 0, -1, -2, -1, 6, 14, 18, 19, 20, 23, 27, 29, 31, 33, 35, 35, 28, 18, 12, 8, 6, 4], | |
'NE': [2, 1, 0, -1, -2, 1, 18, 40, 52, 54, 49, 40, 33, 31, 31, 31, 31, 29, 25, 19, 14, 11, 8, 6], | |
'E': [2, 1, 0, -1, -2, 1, 19, 45, 60, 64, 60, 49, 37, 33, 32, 31, 33, 33, 30, 25, 20, 14, 11, 8], | |
'SE': [2, 1, 0, -1, -2, -1, 9, 26, 39, 45, 46, 43, 36, 33, 32, 31, 33, 33, 28, 25, 19, 14, 11, 8], | |
'S': [2, 1, 0, -1, -2, -2, 0, 4, 9, 13, 19, 25, 30, 32, 32, 31, 33, 33, 28, 24, 19, 14, 10, 8], | |
'SW': [2, 1, 2, 3, 4, 4, 2, 4, 9, 14, 18, 23, 28, 43, 60, 74, 31, 33, 28, 24, 19, 21, 13, 9], | |
'W': [3, 1, 3, 3, 4, 2, 4, 9, 14, 18, 23, 28, 43, 60, 74, 31, 33, 28, 24, 19, 21, 13, 9, 6], | |
'NW': [3, 1, 2, 3, 3, 4, 2, 4, 9, 14, 18, 23, 28, 43, 60, 74, 31, 33, 28, 24, 19, 21, 13, 9] | |
}, | |
"C": { # Type 3 | |
'N': [3, 2, 1, 0, -1, 0, 7, 15, 19, 20, 21, 24, 28, 30, 32, 34, 36, 36, 29, 19, 13, 9, 7, 5], | |
'NE': [3, 2, 1, 0, -1, 2, 19, 41, 53, 55, 50, 41, 34, 32, 32, 32, 32, 30, 26, 20, 15, 12, 9, 7], | |
'E': [3, 2, 1, 0, -1, 2, 20, 46, 61, 65, 61, 50, 38, 34, 33, 32, 34, 34, 31, 26, 21, 15, 12, 9], | |
'SE': [3, 2, 1, 0, -1, 0, 10, 27, 40, 46, 47, 44, 37, 34, 33, 32, 34, 34, 29, 26, 20, 15, 12, 9], | |
'S': [3, 2, 1, 0, -1, -1, 1, 5, 10, 14, 20, 26, 31, 33, 33, 32, 34, 34, 29, 25, 20, 15, 11, 9], | |
'SW': [3, 2, 3, 4, 5, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10], | |
'W': [4, 2, 4, 4, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10, 7], | |
'NW': [4, 2, 3, 4, 4, 5, 3, 5, 10, 15, 19, 24, 29, 44, 61, 75, 32, 34, 29, 25, 20, 22, 14, 10] | |
}, | |
"D": { # Type 4 | |
'N': [4, 3, 2, 1, 0, 1, 8, 16, 20, 21, 22, 25, 29, 31, 33, 35, 37, 37, 30, 20, 14, 10, 8, 6], | |
'NE': [4, 3, 2, 1, 0, 3, 20, 42, 54, 56, 51, 42, 35, 33, 33, 33, 33, 31, 27, 21, 16, 13, 10, 8], | |
'E': [4, 3, 2, 1, 0, 3, 21, 47, 62, 66, 62, 51, 39, 35, 34, 33, 35, 35, 32, 27, 22, 16, 13, 10], | |
'SE': [4, 3, 2, 1, 0, 1, 11, 28, 41, 47, 48, 45, 38, 35, 34, 33, 35, 35, 30, 27, 21, 16, 13, 10], | |
'S': [4, 3, 2, 1, 0, 0, 2, 6, 11, 15, 21, 27, 32, 34, 34, 33, 35, 35, 30, 26, 21, 16, 12, 10], | |
'SW': [4, 3, 4, 5, 6, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11], | |
'W': [5, 3, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11, 8], | |
'NW': [5, 3, 4, 5, 5, 6, 4, 6, 11, 16, 20, 25, 30, 45, 62, 76, 33, 35, 30, 26, 21, 23, 15, 11] | |
}, | |
"E": { # Type 5 | |
'N': [13, 11, 9, 7, 5, 3, 2, 3, 5, 7, 10, 12, 14, 16, 19, 21, 23, 25, 27, 27, 25, 22, 20, 16], | |
'NE': [13, 11, 8, 7, 5, 3, 3, 6, 12, 20, 26, 31, 33, 33, 32, 32, 32, 33, 31, 29, 27, 24, 21, 18], | |
'E': [14, 11, 9, 7, 5, 4, 3, 6, 13, 22, 31, 36, 39, 39, 39, 39, 39, 31, 31, 29, 26, 22, 19, 18], | |
'SE': [13, 10, 8, 6, 5, 3, 2, 4, 8, 14, 20, 25, 28, 30, 30, 30, 30, 30, 28, 26, 24, 21, 18, 16], | |
'S': [11, 9, 7, 6, 4, 3, 2, 1, 1, 3, 5, 7, 11, 14, 16, 20, 22, 23, 23, 23, 20, 18, 16, 14], | |
'SW': [18, 15, 12, 9, 7, 5, 3, 3, 3, 4, 5, 8, 11, 14, 16, 20, 26, 32, 33, 31, 41, 40, 36, 31], | |
'W': [23, 19, 15, 12, 9, 7, 5, 4, 4, 4, 6, 8, 11, 14, 16, 20, 28, 37, 35, 31, 51, 41, 41, 41], | |
'NW': [21, 17, 14, 11, 8, 6, 4, 3, 3, 4, 6, 8, 11, 14, 16, 20, 28, 37, 35, 31, 41, 41, 41, 41] | |
}, | |
"F": { # Type 6 | |
'N': [10, 8, 6, 4, 2, 1, 1, 2, 4, 6, 9, 11, 13, 15, 18, 20, 22, 24, 26, 26, 24, 21, 19, 15], | |
'NE': [10, 8, 6, 4, 2, 2, 2, 5, 11, 19, 25, 30, 32, 32, 31, 31, 31, 32, 30, 28, 26, 23, 20, 17], | |
'E': [11, 8, 6, 4, 2, 3, 2, 5, 12, 21, 30, 35, 38, 38, 38, 38, 38, 30, 30, 28, 25, 21, 18, 17], | |
'SE': [10, 7, 5, 3, 2, 2, 1, 3, 7, 13, 19, 24, 27, 29, 29, 29, 29, 29, 27, 25, 23, 20, 17, 15], | |
'S': [8, 6, 4, 3, 1, 2, 1, 0, 0, 2, 4, 6, 10, 13, 15, 19, 21, 22, 22, 22, 19, 17, 15, 13], | |
'SW': [15, 12, 9, 6, 4, 3, 2, 2, 2, 3, 4, 7, 10, 13, 15, 19, 25, 31, 32, 30, 40, 39, 35, 30], | |
'W': [20, 16, 12, 9, 6, 4, 3, 3, 3, 3, 5, 7, 10, 13, 15, 19, 27, 36, 34, 30, 50, 40, 40, 40], | |
'NW': [18, 14, 11, 8, 5, 4, 3, 2, 2, 3, 5, 7, 10, 13, 15, 19, 27, 36, 34, 30, 40, 40, 40, 40] | |
}, | |
"G": { # Type 7 | |
'N': [7, 5, 3, 1, -1, 0, 0, 1, 3, 5, 8, 10, 12, 14, 17, 19, 21, 23, 25, 25, 23, 20, 18, 14], | |
'NE': [7, 5, 3, 1, -1, 1, 1, 4, 10, 18, 24, 29, 31, 31, 30, 30, 30, 31, 29, 27, 25, 22, 19, 16], | |
'E': [8, 5, 3, 1, -1, 2, 1, 4, 11, 20, 29, 34, 37, 37, 37, 37, 37, 29, 29, 27, 24, 20, 17, 16], | |
'SE': [7, 4, 2, 0, -1, 1, 0, 2, 6, 12, 18, 23, 26, 28, 28, 28, 28, 28, 26, 24, 22, 19, 16, 14], | |
'S': [5, 3, 1, 0, -2, 1, 0, -1, -1, 1, 3, 5, 9, 12, 14, 18, 20, 21, 21, 21, 18, 16, 14, 12], | |
'SW': [12, 9, 6, 3, 1, 2, 1, 1, 1, 2, 3, 6, 9, 12, 14, 18, 24, 30, 31, 29, 39, 38, 34, 29], | |
'W': [17, 13, 9, 6, 3, 2, 2, 2, 2, 2, 4, 6, 9, 12, 14, 18, 26, 35, 33, 29, 49, 39, 39, 39], | |
'NW': [15, 11, 8, 5, 2, 3, 2, 1, 1, 2, 4, 6, 9, 12, 14, 18, 26, 35, 33, 29, 39, 39, 39, 39] | |
}, | |
"H": { # Interpolated from types 8-12: Heaviest construction | |
'N': [4, 2, 0, -2, -4, -1, -1, 0, 2, 4, 7, 9, 11, 13, 16, 18, 20, 22, 24, 24, 22, 19, 17, 13], | |
'NE': [4, 2, 0, -2, -4, 0, 0, 3, 9, 17, 23, 28, 30, 30, 29, 29, 29, 30, 28, 26, 24, 21, 18, 15], | |
'E': [5, 2, 0, -2, -4, 1, 0, 3, 10, 19, 28, 33, 36, 36, 36, 36, 36, 28, 28, 26, 23, 19, 16, 15], | |
'SE': [4, 1, -1, -3, -4, 0, -1, 1, 5, 11, 17, 22, 25, 27, 27, 27, 27, 27, 25, 23, 21, 18, 15, 13], | |
'S': [2, 0, -2, -3, -5, 0, -1, -2, -2, 0, 2, 4, 8, 11, 13, 17, 19, 20, 20, 20, 17, 15, 13, 11], | |
'SW': [9, 6, 3, 0, -2, 1, 0, 0, 0, 1, 2, 5, 8, 11, 13, 17, 23, 29, 30, 28, 38, 37, 33, 28], | |
'W': [14, 10, 6, 3, 0, 1, 1, 1, 1, 1, 3, 5, 8, 11, 13, 17, 25, 34, 32, 28, 48, 38, 38, 38], | |
'NW': [12, 8, 5, 2, -1, 2, 1, 0, 0, 1, 3, 5, 8, 11, 13, 17, 25, 34, 32, 28, 38, 38, 38, 38] | |
} | |
} | |
wall_groups = {group: pd.DataFrame(data, index=hours) for group, data in wall_data.items()} | |
return wall_groups | |
def _load_cltd_roof_table(self) -> Dict[str, pd.DataFrame]: | |
""" | |
Load CLTD tables for roofs at 24°N, 36°N, 48°N (July). | |
Returns: Dictionary of DataFrames with CLTD values for each roof group and latitude. | |
""" | |
hours = list(range(24)) | |
# CLTD data for roof types mapped to groups A-G across latitudes | |
roof_data = { | |
"24N": { | |
"A": [0, 4, 5, 6, 6, 3, 9, 16, 44, 62, 76, 87, 92, 92, 86, 74, 58, 39, 23, 14, 8, 4, 2, 0], # Type 1 | |
"B": [12, 8, 5, 2, 0, -2, -2, 3, 11, 22, 35, 47, 59, 68, 74, 77, 74, 68, 58, 47, 37, 29, 22, 16], # Type 3 | |
"C": [21, 16, 12, 8, 5, 3, 1, 1, 1, 10, 19, 20, 22, 23, 49, 49, 54, 58, 58, 56, 52, 47, 42, 37], # Type 5 | |
"D": [31, 25, 20, 16, 12, 9, 6, 4, 3, 5, 10, 17, 26, 36, 46, 54, 61, 65, 66, 63, 58, 51, 44, 47], # Type 9 | |
"E": [34, 31, 28, 25, 22, 20, 17, 16, 15, 19, 23, 28, 29, 32, 38, 38, 43, 43, 49, 49, 49, 46, 43, 40], # Type 13 | |
"F": [35, 32, 30, 28, 25, 23, 21, 19, 20, 22, 23, 23, 24, 25, 39, 39, 40, 40, 40, 45, 46, 46, 44, 42], # Type 14 | |
"G": [36, 33, 31, 29, 27, 25, 23, 21, 20, 22, 24, 25, 26, 27, 40, 41, 42, 42, 42, 47, 48, 48, 45, 43] # Interpolated | |
}, | |
"36N": { | |
"A": [0, 2, 4, 5, 6, 6, 12, 28, 45, 61, 75, 84, 90, 90, 84, 79, 71, 62, 66, 59, 50, 42, 47, 0], # Type 1 | |
"B": [12, 8, 5, 2, 0, -2, -1, 14, 13, 24, 25, 26, 27, 28, 38, 39, 40, 40, 43, 45, 46, 46, 43, 40], # Type 3 | |
"C": [21, 16, 12, 8, 5, 3, 1, 12, 15, 12, 21, 22, 23, 32, 39, 40, 40, 40, 40, 45, 46, 46, 43, 40], # Type 5 | |
"D": [32, 26, 21, 16, 13, 10, 8, 14, 17, 19, 20, 22, 23, 24, 39, 40, 40, 40, 40, 45, 46, 46, 43, 40], # Type 9 | |
"E": [34, 31, 28, 25, 23, 20, 18, 16, 16, 20, 22, 22, 23, 24, 39, 39, 40, 40, 40, 45, 46, 46, 44, 42], # Type 13 | |
"F": [35, 32, 30, 28, 25, 23, 21, 19, 20, 22, 23, 23, 24, 25, 39, 39, 40, 40, 40, 45, 46, 46, 44, 42], # Type 14 | |
"G": [36, 33, 31, 29, 27, 25, 23, 21, 20, 22, 24, 25, 26, 27, 40, 41, 42, 42, 42, 47, 48, 48, 45, 43] # Interpolated | |
}, | |
"48N": { | |
"A": [0, 2, 4, 5, 6, 5, 3, 15, 29, 44, 58, 69, 78, 83, 83, 79, 71, 59, 44, 49, 49, 49, 5, 2], # Type 1 | |
"B": [12, 8, 5, 2, 0, -1, 1, 16, 16, 20, 22, 23, 24, 25, 39, 39, 40, 40, 40, 45, 46, 46, 43, 40], # Type 3 | |
"C": [21, 16, 12, 8, 5, 3, 2, 16, 19, 20, 22, 23, 24, 25, 39, 39, 40, 40, 40, 45, 46, 46, 43, 40], # Type 5 | |
"D": [31, 26, 21, 16, 12, 9, 6, 5, 5, 20, 22, 23, 24, 25, 39, 39, 40, 40, 40, 45, 46, 46, 43, 40], # Type 9 | |
"E": [33, 30, 27, 25, 22, 20, 17, 16, 16, 20, 22, 23, 24, 25, 39, 39, 40, 40, 40, 47, 48, 47, 45, 40], # Type 13 | |
"F": [34, 32, 29, 27, 25, 23, 21, 20, 19, 20, 22, 23, 24, 25, 39, 39, 40, 40, 40, 48, 48, 48, 43, 40], # Type 14 | |
"G": [35, 33, 31, 29, 27, 25, 23, 21, 20, 22, 24, 25, 26, 27, 40, 41, 42, 42, 42, 48, 49, 49, 45, 43] # Interpolated | |
} | |
} | |
roof_groups = {} | |
for lat, groups in roof_data.items(): | |
for group, data in groups.items(): | |
roof_groups[f"{group}_{lat}"] = pd.DataFrame({"HOR": data}, index=hours) | |
return roof_groups | |
def _load_scl_table(self) -> Dict[str, pd.DataFrame]: | |
""" | |
Load SCL (Solar Cooling Load) tables for windows. | |
Returns: Dictionary of DataFrames with SCL values for each latitude/month. | |
""" | |
hours = list(range(24)) | |
# Base SCL data for 40°N (July) | |
scl_40n_jul = { | |
"N": [11, 8, 6, 6, 6, 9, 13, 16, 19, 21, 22, 23, 23, 22, 20, 17, 14, 11, 11, 11, 11, 11, 11, 11], | |
"NE": [11, 8, 6, 6, 6, 19, 75, 113, 121, 103, 75, 40, 31, 27, 23, 19, 14, 11, 11, 11, 11, 11, 11, 11], | |
"E": [11, 8, 6, 6, 6, 13, 55, 159, 232, 251, 222, 157, 82, 43, 32, 24, 17, 11, 11, 11, 11, 11, 11, 11], | |
"SE": [11, 8, 6, 6, 6, 10, 33, 98, 187, 251, 276, 264, 214, 139, 74, 37, 21, 11, 11, 11, 11, 11, 11, 11], | |
"S": [11, 8, 6, 6, 6, 8, 14, 27, 66, 139, 209, 254, 268, 251, 203, 139, 66, 27, 14, 11, 11, 11, 11, 11], | |
"SW": [11, 8, 6, 6, 6, 8, 14, 19, 24, 37, 74, 139, 214, 264, 276, 251, 187, 98, 33, 14, 11, 11, 11, 11], | |
"W": [11, 8, 6, 6, 6, 8, 14, 19, 24, 32, 43, 82, 157, 222, 251, 232, 159, 55, 13, 11, 11, 11, 11, 11], | |
"NW": [11, 8, 6, 6, 6, 8, 14, 19, 24, 27, 31, 40, 75, 103, 121, 113, 75, 19, 11, 11, 11, 11, 11, 11], | |
"HOR": [11, 8, 6, 6, 6, 19, 69, 135, 201, 254, 290, 308, 308, 290, 254, 201, 135, 69, 19, 11, 11, 11, 11, 11] | |
} | |
scl_tables = {"40N_Jul": pd.DataFrame(scl_40n_jul, index=hours)} | |
latitudes = ["24N", "32N", "40N", "48N", "56N"] | |
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] | |
for lat in latitudes: | |
for month in months: | |
key = f"{lat}_{month}" | |
if key == "40N_Jul": | |
continue | |
lat_factor = (40 - float(lat[:-1])) / 40 | |
month_idx = months.index(month) | |
month_factor = 1 + (month_idx - 6) / 24 | |
scl_data = {} | |
for orient in scl_40n_jul: | |
base_scl = scl_40n_jul[orient] | |
scl_data[orient] = [max(6, round(v * (1 - lat_factor * 0.2) * month_factor)) for v in base_scl] | |
scl_tables[key] = pd.DataFrame(scl_data, index=hours) | |
return scl_tables | |
def _load_clf_lights_table(self) -> pd.DataFrame: | |
""" | |
Load CLF (Cooling Load Factor) table for lights. | |
Returns: DataFrame with CLF values for lights by zone type and hours. | |
""" | |
hours = list(range(24)) | |
clf_lights_data = { | |
"A_8h": [0.85, 0.92, 0.95, 0.95, 0.97, 0.97, 0.98, 0.13, 0.06, 0.04, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"A_10h": [0.85, 0.93, 0.95, 0.97, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"A_12h": [0.86, 0.93, 0.96, 0.97, 0.97, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.98, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_8h": [0.75, 0.85, 0.90, 0.93, 0.94, 0.95, 0.95, 0.95, 0.12, 0.08, 0.05, 0.04, 0.04, 0.03, 0.03, 0.03, 0.03, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_10h": [0.75, 0.86, 0.91, 0.93, 0.94, 0.95, 0.95, 0.95, 0.96, 0.97, 0.24, 0.13, 0.08, 0.06, 0.05, 0.04, 0.04, 0.03, 0.03, 0.03, 0.03, 0.03, 0.02, 0.02], | |
"B_12h": [0.76, 0.86, 0.91, 0.93, 0.95, 0.95, 0.95, 0.95, 0.97, 0.97, 0.97, 0.97, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03], | |
"C_8h": [0.70, 0.80, 0.85, 0.88, 0.90, 0.92, 0.93, 0.94, 0.10, 0.07, 0.04, 0.03, 0.03, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"C_10h": [0.70, 0.81, 0.86, 0.89, 0.91, 0.92, 0.93, 0.94, 0.95, 0.96, 0.20, 0.11, 0.07, 0.05, 0.04, 0.03, 0.03, 0.02, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01], | |
"C_12h": [0.71, 0.82, 0.87, 0.90, 0.92, 0.93, 0.94, 0.95, 0.96, 0.96, 0.96, 0.96, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"D_8h": [0.65, 0.75, 0.80, 0.83, 0.85, 0.87, 0.88, 0.89, 0.08, 0.06, 0.03, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"D_10h": [0.65, 0.76, 0.81, 0.84, 0.86, 0.88, 0.89, 0.90, 0.91, 0.92, 0.16, 0.09, 0.06, 0.04, 0.03, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"D_12h": [0.66, 0.77, 0.82, 0.85, 0.87, 0.89, 0.90, 0.91, 0.92, 0.92, 0.92, 0.92, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01] | |
} | |
return pd.DataFrame(clf_lights_data, index=hours) | |
def _load_clf_people_table(self) -> pd.DataFrame: | |
""" | |
Load CLF (Cooling Load Factor) table for people. | |
Returns: DataFrame with CLF values for people by zone type and hours. | |
""" | |
hours = list(range(24)) | |
clf_people_data = { | |
"A_2h": [0.75, 0.88, 0.18, 0.08, 0.04, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"A_4h": [0.75, 0.88, 0.93, 0.95, 0.97, 0.10, 0.05, 0.03, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"A_6h": [0.75, 0.88, 0.93, 0.95, 0.97, 0.97, 0.33, 0.11, 0.06, 0.04, 0.03, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"B_2h": [0.65, 0.75, 0.81, 0.85, 0.89, 0.91, 0.93, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_4h": [0.65, 0.75, 0.82, 0.87, 0.90, 0.92, 0.94, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_6h": [0.65, 0.75, 0.82, 0.87, 0.90, 0.92, 0.94, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"C_2h": [0.60, 0.70, 0.76, 0.80, 0.84, 0.86, 0.88, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"C_4h": [0.60, 0.70, 0.77, 0.82, 0.85, 0.87, 0.89, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"C_6h": [0.60, 0.70, 0.77, 0.82, 0.85, 0.87, 0.89, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"D_2h": [0.55, 0.65, 0.71, 0.75, 0.79, 0.81, 0.83, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"D_4h": [0.55, 0.65, 0.72, 0.77, 0.80, 0.82, 0.84, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"D_6h": [0.55, 0.65, 0.72, 0.77, 0.80, 0.82, 0.84, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00] | |
} | |
return pd.DataFrame(clf_people_data, index=hours) | |
def _load_clf_equipment_table(self) -> pd.DataFrame: | |
""" | |
Load CLF (Cooling Load Factor) table for equipment. | |
Returns: DataFrame with CLF values for equipment by zone type and hours. | |
""" | |
hours = list(range(24)) | |
clf_equipment_data = { | |
"A_2h": [0.54, 0.83, 0.26, 0.11, 0.05, 0.03, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"A_4h": [0.64, 0.83, 0.90, 0.93, 0.31, 0.14, 0.07, 0.04, 0.03, 0.03, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"A_6h": [0.64, 0.83, 0.90, 0.93, 0.95, 0.95, 0.33, 0.11, 0.06, 0.04, 0.03, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"B_2h": [0.50, 0.75, 0.81, 0.85, 0.89, 0.91, 0.93, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_4h": [0.50, 0.75, 0.82, 0.87, 0.90, 0.92, 0.94, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"B_6h": [0.50, 0.75, 0.82, 0.87, 0.90, 0.92, 0.94, 0.95, 0.96, 0.97, 0.98, 0.98, 0.99, 0.99, 0.99, 0.99, 0.99, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02], | |
"C_2h": [0.46, 0.70, 0.76, 0.80, 0.84, 0.86, 0.88, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"C_4h": [0.46, 0.70, 0.77, 0.82, 0.85, 0.87, 0.89, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"C_6h": [0.46, 0.70, 0.77, 0.82, 0.85, 0.87, 0.89, 0.90, 0.91, 0.92, 0.93, 0.93, 0.94, 0.94, 0.94, 0.94, 0.94, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01], | |
"D_2h": [0.42, 0.65, 0.71, 0.75, 0.79, 0.81, 0.83, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"D_4h": [0.42, 0.65, 0.72, 0.77, 0.80, 0.82, 0.84, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], | |
"D_6h": [0.42, 0.65, 0.72, 0.77, 0.80, 0.82, 0.84, 0.85, 0.86, 0.87, 0.88, 0.88, 0.89, 0.89, 0.89, 0.89, 0.89, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00] | |
} | |
return pd.DataFrame(clf_equipment_data, index=hours) | |
def _load_heat_gain_table(self) -> pd.DataFrame: | |
""" | |
Load heat gain table for internal sources. | |
Returns: DataFrame with heat gain values (Btu/h or Btu/h-ft²). | |
""" | |
data = { | |
"source": ["people_sensible", "people_latent", "lights", "equipment"], | |
"gain": [250, 200, 3.4, 500] | |
} | |
return pd.DataFrame(data) | |
def _load_thermal_properties(self) -> pd.DataFrame: | |
""" | |
Load thermal properties for building materials. | |
Returns: DataFrame with U-values, R-values, and density. | |
""" | |
data = { | |
"material": [ | |
"Brick_4in", "Brick_8in", "Concrete_6in", "Concrete_12in", | |
"Wood_1in", "Wood_2in", "Insulation_1in", "Insulation_2in", | |
"Gypsum_0.5in", "Steel_1in" | |
], | |
"U_value": [0.45, 0.32, 0.51, 0.48, 0.12, 0.08, 0.03, 0.015, 0.32, 0.65], # Btu/h-ft²-°F | |
"R_value": [2.22, 3.13, 1.96, 2.08, 8.33, 12.5, 33.33, 66.67, 3.13, 1.54], # ft²-°F-h/Btu | |
"density": [120, 120, 140, 140, 35, 35, 1.5, 1.5, 40, 490] # lb/ft³ | |
} | |
return pd.DataFrame(data) | |
def _load_roof_classifications(self) -> pd.DataFrame: | |
""" | |
Load roof classification data. | |
Returns: DataFrame with roof type descriptions and properties. | |
""" | |
data = { | |
"type": [1, 2, 3, 4, 5, 8, 9, 10, 13, 14], | |
"description": [ | |
"Light roof, no insulation", "Light roof, minimal insulation", | |
"Medium roof, R-10 insulation", "Medium roof, R-15 insulation", | |
"Heavy roof, R-20 insulation", "Heavy roof, R-25 insulation", | |
"Concrete slab, R-15 insulation", "Concrete slab, R-20 insulation", | |
"Metal deck, R-30 insulation", "Metal deck, R-35 insulation" | |
], | |
"U_value": [0.5, 0.4, 0.3, 0.25, 0.2, 0.15, 0.18, 0.14, 0.1, 0.08], | |
"mass": [10, 15, 50, 60, 100, 120, 150, 160, 80, 90] # lb/ft² | |
} | |
return pd.DataFrame(data) | |
def _load_latitude_correction(self) -> Dict[str, Dict[str, float]]: | |
""" | |
Load latitude correction factors for CLTD/SCL values. | |
Returns: Dictionary of correction factors for different latitudes and months. | |
""" | |
return { | |
"24N": {"Jan": -5.0, "Feb": -3.5, "Mar": -1.0, "Apr": 2.0, "May": 4.0, "Jun": 5.0, "Jul": 4.5, "Aug": 3.0, "Sep": 1.0, "Oct": -1.5, "Nov": -4.0, "Dec": -5.5}, | |
"32N": {"Jan": -4.0, "Feb": -2.5, "Mar": 0.0, "Apr": 2.5, "May": 4.5, "Jun": 5.5, "Jul": 5.0, "Aug": 3.5, "Sep": 1.5, "Oct": -0.5, "Nov": -3.0, "Dec": -4.5}, | |
"40N": {"Jan": -3.0, "Feb": -1.5, "Mar": 1.0, "Apr": 3.0, "May": 5.0, "Jun": 6.0, "Jul": 5.5, "Aug": 4.0, "Sep": 2.0, "Oct": 0.0, "Nov": -2.0, "Dec": -3.5}, | |
"48N": {"Jan": -2.0, "Feb": -0.5, "Mar": 2.0, "Apr": 4.0, "May": 6.0, "Jun": 7.0, "Jul": 6.5, "Aug": 5.0, "Sep": 3.0, "Oct": 1.0, "Nov": -1.0, "Dec": -2.5}, | |
"56N": {"Jan": -1.0, "Feb": 0.5, "Mar": 3.0, "Apr": 5.0, "May": 7.0, "Jun": 8.0, "Jul": 7.5, "Aug": 6.0, "Sep": 4.0, "Oct": 2.0, "Nov": 0.0, "Dec": -1.5} | |
} | |
def _load_color_correction(self) -> Dict[str, float]: | |
""" | |
Load color correction factors for CLTD values. | |
Returns: Dictionary of correction factors for different colors. | |
""" | |
return {"Dark": 0.0, "Medium": -1.0, "Light": -2.0} | |
def _load_month_correction(self) -> Dict[str, float]: | |
""" | |
Load month correction factors for CLTD values. | |
Returns: Dictionary of correction factors for different months. | |
""" | |
return { | |
"Jan": -6.0, "Feb": -5.0, "Mar": -3.0, "Apr": -1.0, "May": 1.0, | |
"Jun": 2.0, "Jul": 2.0, "Aug": 2.0, "Sep": 1.0, "Oct": -1.0, | |
"Nov": -3.0, "Dec": -5.0 | |
} | |
def _apply_climatic_corrections(self, cltd: float, latitude: str, month: str, color: str, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Apply climatic corrections to CLTD values based on latitude, month, color, and temperature. | |
Args: | |
cltd (float): Base CLTD value. | |
latitude (str): Latitude (e.g., '32N'). | |
month (str): Month (e.g., 'Jul'). | |
color (str): Surface color ('Dark', 'Medium', 'Light'). | |
outdoor_temp (float): Outdoor design temperature (°C). | |
indoor_temp (float): Indoor design temperature (°C). | |
Returns: | |
float: Corrected CLTD value (°C). | |
""" | |
try: | |
# Convert temperatures to °F for ASHRAE corrections | |
outdoor_temp_f = outdoor_temp * 9/5 + 32 | |
indoor_temp_f = indoor_temp * 9/5 + 32 | |
# Get correction factors | |
lat_corr = self.latitude_correction.get(latitude, {}).get(month, 0.0) | |
month_corr = self.month_correction.get(month, 0.0) | |
color_corr = self.color_correction.get(color, 0.0) | |
# Apply temperature difference correction (ASHRAE CLTD correction formula) | |
temp_diff = outdoor_temp_f - indoor_temp_f | |
design_temp_diff = 85 - 78 # ASHRAE base conditions: 85°F outdoor, 78°F indoor | |
temp_corr = (temp_diff - design_temp_diff) * 0.5556 # Convert °F to °C | |
# Total correction | |
corrected_cltd = cltd + lat_corr + month_corr + color_corr + temp_corr | |
# Ensure non-negative CLTD | |
return max(0.0, corrected_cltd) | |
except Exception as e: | |
raise ValueError(f"Error applying climatic corrections: {str(e)}") | |
def get_cltd_wall(self, wall_group: str, orientation: str, hour: int) -> float: | |
"""Get CLTD value for a wall.""" | |
if wall_group not in self.cltd_wall: | |
raise ValueError(f"Invalid wall group: {wall_group}") | |
orientation_map = {e.value: e.name for e in Orientation} | |
orientation_abbr = orientation_map.get(orientation, orientation) | |
if orientation_abbr not in self.cltd_wall[wall_group].columns: | |
raise ValueError(f"Invalid orientation: {orientation}") | |
if hour not in self.cltd_wall[wall_group].index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.cltd_wall[wall_group].loc[hour, orientation_abbr]) | |
def get_cltd_roof(self, roof_group: str, latitude: str, hour: int) -> float: | |
"""Get CLTD value for a roof.""" | |
# Map latitude to standard format before forming the key | |
valid_latitudes = ['24N', '36N', '48N'] | |
# Handle numeric or non-standard latitude values | |
if latitude not in valid_latitudes: | |
# Try to convert to standard format | |
try: | |
# First, handle string representations that might contain direction indicators | |
if isinstance(latitude, str): | |
# Extract numeric part, removing 'N' or 'S' | |
lat_str = latitude.upper().strip() | |
num_part = ''.join(c for c in lat_str if c.isdigit() or c == '.') | |
lat_val = float(num_part) | |
# Adjust for southern hemisphere if needed | |
if 'S' in lat_str: | |
lat_val = -lat_val | |
else: | |
# Handle direct numeric input | |
lat_val = float(latitude) | |
# Take absolute value for mapping purposes | |
abs_lat = abs(lat_val) | |
# Map to the closest standard latitude for roof data | |
if abs_lat < 30: | |
latitude = '24N' | |
elif abs_lat < 42: | |
latitude = '36N' | |
else: | |
latitude = '48N' | |
except (ValueError, TypeError): | |
raise ValueError(f"Invalid latitude format: {latitude}") | |
key = f"{roof_group}_{latitude}" | |
if key not in self.cltd_roof: | |
raise ValueError(f"Invalid roof group or latitude: {key}") | |
if hour not in self.cltd_roof[key].index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.cltd_roof[key].loc[hour, "HOR"]) | |
def get_scl(self, latitude: str, month: str, orientation: str, hour: int) -> float: | |
"""Get SCL value for a window.""" | |
# Map latitude to standard format before forming the key | |
valid_latitudes = ['24N', '32N', '40N', '48N', '56N'] | |
# Handle numeric or non-standard latitude values | |
if latitude not in valid_latitudes: | |
# Try to convert to standard format | |
try: | |
# First, handle string representations that might contain direction indicators | |
if isinstance(latitude, str): | |
# Extract numeric part, removing 'N' or 'S' | |
lat_str = latitude.upper().strip() | |
num_part = ''.join(c for c in lat_str if c.isdigit() or c == '.') | |
lat_val = float(num_part) | |
# Adjust for southern hemisphere if needed | |
if 'S' in lat_str: | |
lat_val = -lat_val | |
else: | |
# Handle direct numeric input | |
lat_val = float(latitude) | |
# Take absolute value for mapping purposes | |
abs_lat = abs(lat_val) | |
# Map to the closest standard latitude for SCL data | |
if abs_lat < 28: | |
latitude = '24N' | |
elif abs_lat < 36: | |
latitude = '32N' | |
elif abs_lat < 44: | |
latitude = '40N' | |
elif abs_lat < 52: | |
latitude = '48N' | |
else: | |
latitude = '56N' | |
except (ValueError, TypeError): | |
raise ValueError(f"Invalid latitude format: {latitude}") | |
key = f"{latitude}_{month}" | |
if key not in self.scl: | |
raise ValueError(f"Invalid latitude or month: {key}") | |
orientation_map = {e.value: e.name for e in Orientation} | |
orientation_abbr = orientation_map.get(orientation, orientation) | |
if orientation_abbr not in self.scl[key].columns: | |
raise ValueError(f"Invalid orientation: {orientation}") | |
if hour not in self.scl[key].index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.scl[key].loc[hour, orientation_abbr]) | |
def get_clf_lights(self, zone_type: str, hours_on: str, hour: int) -> float: | |
"""Get CLF value for lights.""" | |
key = f"{zone_type}_{hours_on}" | |
if key not in self.clf_lights.columns: | |
raise ValueError(f"Invalid zone type or hours: {key}") | |
if hour not in self.clf_lights.index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.clf_lights.loc[hour, key]) | |
def get_clf_people(self, zone_type: str, hours_occupied: str, hour: int) -> float: | |
"""Get CLF value for people.""" | |
key = f"{zone_type}_{hours_occupied}" | |
if key not in self.clf_people.columns: | |
raise ValueError(f"Invalid zone type or hours: {key}") | |
if hour not in self.clf_people.index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.clf_people.loc[hour, key]) | |
def get_clf_equipment(self, zone_type: str, hours_operated: str, hour: int) -> float: | |
"""Get CLF value for equipment.""" | |
key = f"{zone_type}_{hours_operated}" | |
if key not in self.clf_equipment.columns: | |
raise ValueError(f"Invalid zone type or hours: {key}") | |
if hour not in self.clf_equipment.index: | |
raise ValueError(f"Invalid hour: {hour}") | |
return float(self.clf_equipment.loc[hour, key]) | |
def get_thermal_property(self, material: str, property_type: str) -> float: | |
""" | |
Get thermal property for a material. | |
Args: | |
material (str): Material name (e.g., 'Brick_4in'). | |
property_type (str): Property to retrieve ('U_value', 'R_value', 'density'). | |
Returns: | |
float: Value of the specified thermal property. | |
Raises: | |
ValueError: If material or property_type is invalid. | |
""" | |
if material not in self.thermal_properties['material'].values: | |
raise ValueError(f"Invalid material: {material}") | |
if property_type not in ['U_value', 'R_value', 'density']: | |
raise ValueError(f"Invalid property type: {property_type}") | |
return float(self.thermal_properties.loc[self.thermal_properties['material'] == material, property_type].iloc[0]) | |
def get_heat_gain(self, source: str) -> float: | |
""" | |
Get heat gain value for an internal source. | |
Args: | |
source (str): Source type ('people_sensible', 'people_latent', 'lights', 'equipment'). | |
Returns: | |
float: Heat gain value (Btu/h or Btu/h-ft²). | |
Raises: | |
ValueError: If source is invalid. | |
""" | |
if source not in self.heat_gain['source'].values: | |
raise ValueError(f"Invalid source: {source}") | |
return float(self.heat_gain.loc[self.heat_gain['source'] == source, 'gain'].iloc[0]) | |
def plot_cooling_load(self, cooling_loads: List[float], title: str = "Cooling Load Profile", filename: str = "cooling_load.png") -> None: | |
""" | |
Plot the cooling load profile over 24 hours. | |
Args: | |
cooling_loads (List[float]): List of cooling load values for each hour. | |
title (str): Plot title. | |
filename (str): Output filename for the plot. | |
""" | |
if len(cooling_loads) != 24: | |
raise ValueError("Cooling loads must contain 24 hourly values") | |
plt.figure(figsize=(10, 6)) | |
hours = list(range(24)) | |
plt.plot(hours, cooling_loads, marker='o', linestyle='-', color='b') | |
plt.title(title) | |
plt.xlabel("Hour of Day") | |
plt.ylabel("Cooling Load (Btu/h)") | |
plt.grid(True) | |
plt.xticks(hours) | |
plt.savefig(filename) | |
plt.close() | |
def calculate_corrected_cltd_wall(self, wall_group: str, orientation: str, hour: int, latitude: str, month: str, color: str, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate corrected CLTD for a wall with climatic corrections. | |
Args: | |
wall_group (str): Wall group (e.g., 'A', 'B', ..., 'H'). | |
orientation (str): Wall orientation (e.g., 'North', 'East', etc.). | |
hour (int): Hour of the day (0-23). | |
latitude (str): Latitude (e.g., '32N'). | |
month (str): Month (e.g., 'Jul'). | |
color (str): Surface color ('Dark', 'Medium', 'Light'). | |
outdoor_temp (float): Outdoor design temperature (°C). | |
indoor_temp (float): Indoor design temperature (°C). | |
Returns: | |
float: Corrected CLTD value (°C). | |
Raises: | |
ValueError: If inputs are invalid or correction fails. | |
""" | |
valid, message = self._validate_cltd_inputs(wall_group, orientation, hour, latitude, month, color, is_wall=True) | |
if not valid: | |
raise ValueError(message) | |
try: | |
# Get base CLTD | |
base_cltd = self.get_cltd_wall(wall_group, orientation, hour) | |
# Apply climatic corrections | |
corrected_cltd = self._apply_climatic_corrections(base_cltd, latitude, month, color, outdoor_temp, indoor_temp) | |
return corrected_cltd | |
except Exception as e: | |
raise ValueError(f"Error calculating corrected CLTD for wall: {str(e)}") | |
def calculate_corrected_cltd_roof(self, roof_group: str, latitude: str, hour: int, month: str, color: str, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate corrected CLTD for a roof with climatic corrections. | |
Args: | |
roof_group (str): Roof group (e.g., 'A', 'B', ..., 'G'). | |
latitude (str): Latitude (e.g., '24N', '36N', '48N'). | |
hour (int): Hour of the day (0-23). | |
month (str): Month (e.g., 'Jul'). | |
color (str): Surface color ('Dark', 'Medium', 'Light'). | |
outdoor_temp (float): Outdoor design temperature (°C). | |
indoor_temp (float): Indoor design temperature (°C). | |
Returns: | |
float: Corrected CLTD value (°C). | |
Raises: | |
ValueError: If inputs are invalid or correction fails. | |
""" | |
valid, message = self._validate_cltd_inputs(roof_group, 'Horizontal', hour, latitude, month, color, is_wall=False) | |
if not valid: | |
raise ValueError(message) | |
try: | |
# Get base CLTD | |
base_cltd = self.get_cltd_roof(roof_group, latitude, hour) | |
# Apply climatic corrections | |
corrected_cltd = self._apply_climatic_corrections(base_cltd, latitude, month, color, outdoor_temp, indoor_temp) | |
return corrected_cltd | |
except Exception as e: | |
raise ValueError(f"Error calculating corrected CLTD for roof: {str(e)}") |