test-29 / utils /cooling_load.py
mabuseif's picture
Upload 31 files
91ba325 verified
"""
Cooling load calculation module for HVAC Load Calculator.
This module implements the CLTD/CLF method for calculating cooling loads.
"""
from typing import Dict, List, Any, Optional, Tuple
import math
import numpy as np
import pandas as pd
import os
from datetime import datetime, timedelta
from enum import Enum
# Import data models and utilities
from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType
from data.ashrae_tables import ashrae_tables
from utils.psychrometrics import Psychrometrics
from utils.heat_transfer import HeatTransfer
# Define paths
DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class CoolingLoad:
"""Class for calculating cooling loads using the CLTD/CLF method."""
def __init__(self):
"""Initialize cooling load calculator."""
self.heat_transfer = HeatTransfer()
self.psychrometrics = Psychrometrics()
self.ashrae_tables = ashrae_tables
def calculate_wall_cooling_load(self, wall: Wall, outdoor_temp: float, indoor_temp: float,
month: str, hour: int, latitude: str = "40N",
color: str = "Dark") -> float:
"""
Calculate cooling load through a wall using the CLTD method.
Args:
wall: Wall object
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
month: Month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
hour: Hour of the day (0-23)
latitude: Latitude (24N, 32N, 40N, 48N, 56N)
color: Surface color (Dark, Medium, Light)
Returns:
Cooling load in W
"""
# Get wall properties
u_value = wall.u_value
area = wall.area
orientation = wall.orientation.value
wall_group = wall.wall_group
# Calculate corrected CLTD
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
wall_group=wall_group,
orientation=orientation,
hour=hour,
color=color,
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
# Calculate cooling load
cooling_load = u_value * area * cltd
return cooling_load
def calculate_roof_cooling_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float,
month: str, hour: int, latitude: str = "40N",
color: str = "Dark") -> float:
"""
Calculate cooling load through a roof using the CLTD method.
Args:
roof: Roof object
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
month: Month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
hour: Hour of the day (0-23)
latitude: Latitude (24N, 32N, 40N, 48N, 56N)
color: Surface color (Dark, Medium, Light)
Returns:
Cooling load in W
"""
# Get roof properties
u_value = roof.u_value
area = roof.area
roof_group = roof.roof_group
# Calculate corrected CLTD
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
roof_group=roof_group,
hour=hour,
color=color,
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
# Calculate cooling load
cooling_load = u_value * area * cltd
return cooling_load
def calculate_window_cooling_load(self, window: Window, outdoor_temp: float, indoor_temp: float,
month: str, hour: int, latitude: str = "40N_JUL",
shading_coefficient: float = 1.0) -> Dict[str, float]:
"""
Calculate cooling load through a window using the CLTD/SCL method.
Args:
window: Window object
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
month: Month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
hour: Hour of the day (0-23)
latitude: Latitude and month key (default: "40N_JUL")
shading_coefficient: Shading coefficient (0-1)
Returns:
Dictionary with conduction, solar, and total cooling loads in W
"""
# Get window properties
u_value = window.u_value
area = window.area
orientation = window.orientation.value
shgc = window.shgc
# Calculate conduction cooling load
delta_t = outdoor_temp - indoor_temp
conduction_load = u_value * area * delta_t
# Calculate solar cooling load
scl = self.ashrae_tables.get_scl(orientation, hour, latitude)
solar_load = area * shgc * shading_coefficient * scl
# Calculate total cooling load
total_load = conduction_load + solar_load
return {
"conduction": conduction_load,
"solar": solar_load,
"total": total_load
}
def calculate_door_cooling_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float:
"""
Calculate cooling load through a door using simple conduction.
Args:
door: Door object
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
Returns:
Cooling load in W
"""
# Get door properties
u_value = door.u_value
area = door.area
# Calculate cooling load
delta_t = outdoor_temp - indoor_temp
cooling_load = u_value * area * delta_t
return cooling_load
def calculate_floor_cooling_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float:
"""
Calculate cooling load through a floor.
Args:
floor: Floor object
ground_temp: Ground or adjacent space temperature in °C
indoor_temp: Indoor temperature in °C
Returns:
Cooling load in W
"""
# Get floor properties
u_value = floor.u_value
area = floor.area
# Calculate cooling load
delta_t = ground_temp - indoor_temp
cooling_load = u_value * area * delta_t
# Return positive value for heat gain, zero for heat loss
return max(0, cooling_load)
def calculate_infiltration_cooling_load(self, flow_rate: float, outdoor_temp: float, indoor_temp: float,
outdoor_rh: float, indoor_rh: float) -> Dict[str, float]:
"""
Calculate sensible and latent cooling loads due to infiltration.
Args:
flow_rate: Infiltration flow rate in m³/s
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
outdoor_rh: Outdoor relative humidity in %
indoor_rh: Indoor relative humidity in %
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Calculate sensible cooling load
sensible_load = self.heat_transfer.infiltration_heat_transfer(
flow_rate=flow_rate,
delta_t=outdoor_temp - indoor_temp
)
# Calculate humidity ratios
outdoor_w = self.psychrometrics.relative_humidity_to_humidity_ratio(
temperature=outdoor_temp,
relative_humidity=outdoor_rh
)
indoor_w = self.psychrometrics.relative_humidity_to_humidity_ratio(
temperature=indoor_temp,
relative_humidity=indoor_rh
)
# Calculate latent cooling load
latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
flow_rate=flow_rate,
delta_w=outdoor_w - indoor_w
)
# Calculate total cooling load
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load
}
def calculate_ventilation_cooling_load(self, flow_rate: float, outdoor_temp: float, indoor_temp: float,
outdoor_rh: float, indoor_rh: float) -> Dict[str, float]:
"""
Calculate sensible and latent cooling loads due to ventilation.
Args:
flow_rate: Ventilation flow rate in m³/s
outdoor_temp: Outdoor temperature in °C
indoor_temp: Indoor temperature in °C
outdoor_rh: Outdoor relative humidity in %
indoor_rh: Indoor relative humidity in %
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Ventilation load calculation is similar to infiltration
return self.calculate_infiltration_cooling_load(
flow_rate=flow_rate,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
outdoor_rh=outdoor_rh,
indoor_rh=indoor_rh
)
def calculate_people_cooling_load(self, num_people: int, activity_level: str, hour: int) -> Dict[str, float]:
"""
Calculate sensible and latent cooling loads due to people.
Args:
num_people: Number of people
activity_level: Activity level (Seated/Resting, Light Work, Medium Work, Heavy Work)
hour: Hour of the day (0-23)
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Get heat gain values based on activity level
if activity_level == "Seated/Resting":
sensible_gain_per_person = 70 # W
latent_gain_per_person = 45 # W
elif activity_level == "Light Work":
sensible_gain_per_person = 75 # W
latent_gain_per_person = 55 # W
elif activity_level == "Medium Work":
sensible_gain_per_person = 75 # W
latent_gain_per_person = 115 # W
elif activity_level == "Heavy Work":
sensible_gain_per_person = 80 # W
latent_gain_per_person = 175 # W
else:
# Default to light work
sensible_gain_per_person = 75 # W
latent_gain_per_person = 55 # W
# Calculate instantaneous heat gains
instantaneous_sensible = num_people * sensible_gain_per_person
instantaneous_latent = num_people * latent_gain_per_person
# Apply CLF for sensible load
clf = self.ashrae_tables.get_clf_people(hour)
sensible_load = instantaneous_sensible * clf
# Latent load doesn't use CLF
latent_load = instantaneous_latent
# Calculate total cooling load
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load
}
def calculate_lighting_cooling_load(self, power: float, usage_factor: float,
special_allowance_factor: float, hour: int) -> float:
"""
Calculate cooling load due to lighting.
Args:
power: Lighting power in W
usage_factor: Usage factor (0-1)
special_allowance_factor: Special allowance factor for fixtures (1.0 for normal fixtures)
hour: Hour of the day (0-23)
Returns:
Cooling load in W
"""
# Calculate instantaneous heat gain
instantaneous_gain = power * usage_factor * special_allowance_factor
# Apply CLF
clf = self.ashrae_tables.get_clf_lights(hour)
cooling_load = instantaneous_gain * clf
return cooling_load
def calculate_equipment_cooling_load(self, power: float, usage_factor: float,
radiation_factor: float, hour: int) -> Dict[str, float]:
"""
Calculate sensible and latent cooling loads due to equipment.
Args:
power: Equipment power in W
usage_factor: Usage factor (0-1)
radiation_factor: Radiation factor (0-1)
hour: Hour of the day (0-23)
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Calculate instantaneous heat gain
instantaneous_gain = power * usage_factor
# Split into radiant and convective components
radiant_gain = instantaneous_gain * radiation_factor
convective_gain = instantaneous_gain * (1 - radiation_factor)
# Apply CLF to radiant component
clf = self.ashrae_tables.get_clf_equipment(hour)
radiant_load = radiant_gain * clf
# Convective component doesn't use CLF
convective_load = convective_gain
# Calculate total sensible load
sensible_load = radiant_load + convective_load
# Most equipment doesn't have latent load
latent_load = 0
# Calculate total cooling load
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load
}
def calculate_total_cooling_load(self, building_components: Dict[str, List[Any]],
building_info: Dict[str, Any],
climate_data: Dict[str, Any],
internal_loads: Dict[str, Any],
hour: int = 15) -> Dict[str, Any]:
"""
Calculate total cooling load for a building.
Args:
building_components: Dictionary with lists of building components
building_info: Dictionary with building information
climate_data: Dictionary with climate data
internal_loads: Dictionary with internal loads information
hour: Design hour (default: 15, which is 3 PM)
Returns:
Dictionary with detailed cooling load results
"""
# Initialize results dictionary
results = {
"walls": 0,
"roofs": 0,
"floors": 0,
"windows_conduction": 0,
"windows_solar": 0,
"doors": 0,
"infiltration_sensible": 0,
"infiltration_latent": 0,
"ventilation_sensible": 0,
"ventilation_latent": 0,
"people_sensible": 0,
"people_latent": 0,
"lights": 0,
"equipment_sensible": 0,
"equipment_latent": 0,
"detailed_loads": {
"walls": [],
"roofs": [],
"floors": [],
"windows": [],
"doors": []
}
}
# Get design conditions
outdoor_temp = climate_data.get("design_cooling_temp", 35)
indoor_temp = building_info.get("indoor_cooling_temp", 24)
outdoor_rh = climate_data.get("design_cooling_rh", 50)
indoor_rh = building_info.get("indoor_cooling_rh", 50)
month = climate_data.get("design_cooling_month", "Jul")
latitude = climate_data.get("latitude", "40N")
# Calculate wall cooling loads
for wall in building_components.get("walls", []):
wall_load = self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude
)
results["walls"] += wall_load
# Add detailed load
results["detailed_loads"]["walls"].append({
"name": wall.name,
"orientation": wall.orientation.value,
"area": wall.area,
"u_value": wall.u_value,
"cltd": (wall_load / (wall.area * wall.u_value)) if (wall.area * wall.u_value) > 0 else 0,
"load": wall_load / 1000 # Convert to kW
})
# Calculate roof cooling loads
for roof in building_components.get("roofs", []):
roof_load = self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude
)
results["roofs"] += roof_load
# Add detailed load
results["detailed_loads"]["roofs"].append({
"name": roof.name,
"orientation": "Horizontal",
"area": roof.area,
"u_value": roof.u_value,
"cltd": (roof_load / (roof.area * roof.u_value)) if (roof.area * roof.u_value) > 0 else 0,
"load": roof_load / 1000 # Convert to kW
})
# Calculate floor cooling loads
for floor in building_components.get("floors", []):
ground_temp = climate_data.get("ground_temp", 15)
floor_load = self.calculate_floor_cooling_load(
floor=floor,
ground_temp=ground_temp,
indoor_temp=indoor_temp
)
results["floors"] += floor_load
# Add detailed load
results["detailed_loads"]["floors"].append({
"name": floor.name,
"area": floor.area,
"u_value": floor.u_value,
"delta_t": ground_temp - indoor_temp,
"load": floor_load / 1000 # Convert to kW
})
# Calculate window cooling loads
for window in building_components.get("windows", []):
window_load = self.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=f"{latitude}_{month.upper()[:3]}",
shading_coefficient=window.shading_coefficient
)
results["windows_conduction"] += window_load["conduction"]
results["windows_solar"] += window_load["solar"]
# Add detailed load
results["detailed_loads"]["windows"].append({
"name": window.name,
"orientation": window.orientation.value,
"area": window.area,
"u_value": window.u_value,
"shgc": window.shgc,
"scl": window_load["solar"] / (window.area * window.shgc * window.shading_coefficient) if (window.area * window.shgc * window.shading_coefficient) > 0 else 0,
"conduction_load": window_load["conduction"] / 1000, # Convert to kW
"solar_load": window_load["solar"] / 1000, # Convert to kW
"load": window_load["total"] / 1000 # Convert to kW
})
# Calculate door cooling loads
for door in building_components.get("doors", []):
door_load = self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp
)
results["doors"] += door_load
# Add detailed load
results["detailed_loads"]["doors"].append({
"name": door.name,
"orientation": door.orientation.value,
"area": door.area,
"u_value": door.u_value,
"delta_t": outdoor_temp - indoor_temp,
"load": door_load / 1000 # Convert to kW
})
# Calculate infiltration cooling load
if "infiltration" in building_info:
infiltration_info = building_info["infiltration"]
# Convert ACH to flow rate
if "ach" in infiltration_info:
volume = building_info.get("volume", building_info.get("floor_area", 100) * 3) # Assume 3m ceiling height if not specified
flow_rate = self.heat_transfer.air_exchange_rate_to_flow_rate(
ach=infiltration_info["ach"],
volume=volume
)
else:
flow_rate = infiltration_info.get("flow_rate", 0.05) # Default to 0.05 m³/s
infiltration_load = self.calculate_infiltration_cooling_load(
flow_rate=flow_rate,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
outdoor_rh=outdoor_rh,
indoor_rh=indoor_rh
)
results["infiltration_sensible"] += infiltration_load["sensible"]
results["infiltration_latent"] += infiltration_load["latent"]
# Calculate ventilation cooling load
if "ventilation" in building_info:
ventilation_info = building_info["ventilation"]
# Convert ACH to flow rate
if "ach" in ventilation_info:
volume = building_info.get("volume", building_info.get("floor_area", 100) * 3) # Assume 3m ceiling height if not specified
flow_rate = self.heat_transfer.air_exchange_rate_to_flow_rate(
ach=ventilation_info["ach"],
volume=volume
)
else:
flow_rate = ventilation_info.get("flow_rate", 0.1) # Default to 0.1 m³/s
ventilation_load = self.calculate_ventilation_cooling_load(
flow_rate=flow_rate,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
outdoor_rh=outdoor_rh,
indoor_rh=indoor_rh
)
results["ventilation_sensible"] += ventilation_load["sensible"]
results["ventilation_latent"] += ventilation_load["latent"]
# Calculate people cooling load
if "people" in internal_loads:
people_info = internal_loads["people"]
num_people = people_info.get("count", 1)
activity_level = people_info.get("activity", "Light Work")
people_load = self.calculate_people_cooling_load(
num_people=num_people,
activity_level=activity_level,
hour=hour
)
results["people_sensible"] += people_load["sensible"]
results["people_latent"] += people_load["latent"]
# Calculate lighting cooling load
if "lighting" in internal_loads:
lighting_info = internal_loads["lighting"]
power = lighting_info.get("power", 0)
usage_factor = lighting_info.get("usage_factor", 1.0)
special_allowance_factor = lighting_info.get("special_allowance_factor", 1.0)
lighting_load = self.calculate_lighting_cooling_load(
power=power,
usage_factor=usage_factor,
special_allowance_factor=special_allowance_factor,
hour=hour
)
results["lights"] += lighting_load
# Calculate equipment cooling load
if "equipment" in internal_loads:
equipment_info = internal_loads["equipment"]
power = equipment_info.get("power", 0)
usage_factor = equipment_info.get("usage_factor", 1.0)
radiation_factor = equipment_info.get("radiation_factor", 0.3)
equipment_load = self.calculate_equipment_cooling_load(
power=power,
usage_factor=usage_factor,
radiation_factor=radiation_factor,
hour=hour
)
results["equipment_sensible"] += equipment_load["sensible"]
results["equipment_latent"] += equipment_load["latent"]
# Calculate subtotals
envelope_load = (
results["walls"] +
results["roofs"] +
results["floors"] +
results["windows_conduction"] +
results["windows_solar"] +
results["doors"]
)
infiltration_load = results["infiltration_sensible"] + results["infiltration_latent"]
ventilation_load = results["ventilation_sensible"] + results["ventilation_latent"]
people_load = results["people_sensible"] + results["people_latent"]
equipment_load = results["equipment_sensible"] + results["equipment_latent"]
internal_load = people_load + results["lights"] + equipment_load
# Calculate totals
sensible_load = (
envelope_load +
results["infiltration_sensible"] +
results["ventilation_sensible"] +
results["people_sensible"] +
results["lights"] +
results["equipment_sensible"]
)
latent_load = (
results["infiltration_latent"] +
results["ventilation_latent"] +
results["people_latent"] +
results["equipment_latent"]
)
total_load = sensible_load + latent_load
# Calculate sensible heat ratio
sensible_heat_ratio = sensible_load / total_load if total_load > 0 else 1.0
# Add subtotals and totals to results
results.update({
"envelope": envelope_load,
"infiltration": infiltration_load,
"ventilation": ventilation_load,
"internal": internal_load,
"sensible": sensible_load,
"latent": latent_load,
"total": total_load,
"sensible_heat_ratio": sensible_heat_ratio
})
# Add per area metrics
floor_area = building_info.get("floor_area", 100) # Default to 100 m²
results["load_per_area"] = total_load / floor_area if floor_area > 0 else 0
return results