HVAC-testing / utils /cooling_load.py
mabuseif's picture
Upload 23 files
4af4e9a verified
raw
history blame
29.2 kB
"""
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 HeatTransferCalculations
# Define paths
DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class CoolingLoadCalculator:
"""Class for calculating cooling loads using the CLTD/CLF method."""
def __init__(self):
"""Initialize cooling load calculator."""
self.heat_transfer = HeatTransferCalculations()
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
w_outdoor = self.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
w_indoor = self.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
# Calculate latent cooling load
latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
flow_rate=flow_rate,
delta_w=w_outdoor - w_indoor
)
# 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 the same as 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,
hours_occupancy: 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)
hours_occupancy: Hours of occupancy (8h, 10h, 12h, 14h, 16h, 18h, 24h)
hour: Hour of the day (0-23)
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Define heat gains for different activity levels
activity_gains = {
"Seated/Resting": {"sensible": 70, "latent": 45},
"Light work": {"sensible": 75, "latent": 55},
"Medium work": {"sensible": 85, "latent": 80},
"Heavy work": {"sensible": 95, "latent": 145}
}
# Get heat gains for the specified activity level
if activity_level not in activity_gains:
raise ValueError(f"Invalid activity level: {activity_level}")
sensible_gain = activity_gains[activity_level]["sensible"]
latent_gain = activity_gains[activity_level]["latent"]
# Get CLF for the specified hour and occupancy
clf = self.ashrae_tables.get_clf_people(hour, hours_occupancy)
# Calculate cooling loads
sensible_load = num_people * sensible_gain * clf
latent_load = num_people * latent_gain # Latent load is not affected by CLF
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load
}
def calculate_lights_cooling_load(self, power: float, use_factor: float,
special_allowance: float, hours_operation: str,
hour: int) -> float:
"""
Calculate cooling load due to lights.
Args:
power: Installed lighting power in W
use_factor: Usage factor (0-1)
special_allowance: Special allowance factor for fixtures (0-1)
hours_operation: Hours of operation (8h, 10h, 12h, 14h, 16h, 18h, 24h)
hour: Hour of the day (0-23)
Returns:
Cooling load in W
"""
# Get CLF for the specified hour and operation
clf = self.ashrae_tables.get_clf_lights(hour, hours_operation)
# Calculate cooling load
cooling_load = power * use_factor * (1 + special_allowance) * clf
return cooling_load
def calculate_equipment_cooling_load(self, power: float, use_factor: float,
radiation_factor: float, hours_operation: str,
hour: int) -> Dict[str, float]:
"""
Calculate sensible and latent cooling loads due to equipment.
Args:
power: Equipment power in W
use_factor: Usage factor (0-1)
radiation_factor: Radiation factor (0-1)
hours_operation: Hours of operation (8h, 10h, 12h, 14h, 16h, 18h, 24h)
hour: Hour of the day (0-23)
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Get CLF for the specified hour and operation
clf = self.ashrae_tables.get_clf_equipment(hour, hours_operation)
# Calculate sensible cooling load
sensible_load = power * use_factor * radiation_factor * clf
# Calculate latent cooling load (if any)
latent_load = power * use_factor * (1 - radiation_factor)
# Calculate total cooling load
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load
}
def calculate_hourly_cooling_loads(self, building_components: Dict[str, List[Any]],
outdoor_conditions: Dict[str, Any],
indoor_conditions: Dict[str, Any],
internal_loads: Dict[str, Any]) -> Dict[int, Dict[str, float]]:
"""
Calculate hourly cooling loads for a building.
Args:
building_components: Dictionary with lists of building components
outdoor_conditions: Dictionary with outdoor conditions
indoor_conditions: Dictionary with indoor conditions
internal_loads: Dictionary with internal loads
Returns:
Dictionary with hourly cooling loads
"""
# Extract building components
walls = building_components.get("walls", [])
roofs = building_components.get("roofs", [])
floors = building_components.get("floors", [])
windows = building_components.get("windows", [])
doors = building_components.get("doors", [])
# Extract outdoor conditions
outdoor_temp = outdoor_conditions.get("temperature", 35.0)
outdoor_rh = outdoor_conditions.get("relative_humidity", 50.0)
ground_temp = outdoor_conditions.get("ground_temperature", 20.0)
month = outdoor_conditions.get("month", "Jul")
latitude = outdoor_conditions.get("latitude", "40N")
# Extract indoor conditions
indoor_temp = indoor_conditions.get("temperature", 24.0)
indoor_rh = indoor_conditions.get("relative_humidity", 50.0)
# Extract internal loads
people = internal_loads.get("people", {})
lights = internal_loads.get("lights", {})
equipment = internal_loads.get("equipment", {})
infiltration = internal_loads.get("infiltration", {})
ventilation = internal_loads.get("ventilation", {})
# Initialize hourly cooling loads
hourly_loads = {}
# Calculate cooling loads for each hour
for hour in range(24):
# Initialize loads for this hour
loads = {
"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,
"total_sensible": 0,
"total_latent": 0,
"total": 0
}
# Calculate wall loads
for wall in walls:
wall_load = self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude,
color=wall.color if hasattr(wall, "color") else "Dark"
)
loads["walls"] += wall_load
# Calculate roof loads
for roof in roofs:
roof_load = self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude,
color=roof.color if hasattr(roof, "color") else "Dark"
)
loads["roofs"] += roof_load
# Calculate floor loads
for floor in floors:
floor_load = self.calculate_floor_cooling_load(
floor=floor,
ground_temp=ground_temp,
indoor_temp=indoor_temp
)
loads["floors"] += floor_load
# Calculate window loads
for window in windows:
window_loads = self.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=f"{latitude}_{month.upper()}",
shading_coefficient=window.shading_coefficient if hasattr(window, "shading_coefficient") else 1.0
)
loads["windows_conduction"] += window_loads["conduction"]
loads["windows_solar"] += window_loads["solar"]
# Calculate door loads
for door in doors:
door_load = self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp
)
loads["doors"] += door_load
# Calculate infiltration loads
if infiltration:
flow_rate = infiltration.get("flow_rate", 0.0)
infiltration_loads = 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
)
loads["infiltration_sensible"] = infiltration_loads["sensible"]
loads["infiltration_latent"] = infiltration_loads["latent"]
# Calculate ventilation loads
if ventilation:
flow_rate = ventilation.get("flow_rate", 0.0)
ventilation_loads = 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
)
loads["ventilation_sensible"] = ventilation_loads["sensible"]
loads["ventilation_latent"] = ventilation_loads["latent"]
# Calculate people loads
if people:
num_people = people.get("number", 0)
activity_level = people.get("activity_level", "Seated/Resting")
hours_occupancy = people.get("hours_occupancy", "8h")
people_loads = self.calculate_people_cooling_load(
num_people=num_people,
activity_level=activity_level,
hours_occupancy=hours_occupancy,
hour=hour
)
loads["people_sensible"] = people_loads["sensible"]
loads["people_latent"] = people_loads["latent"]
# Calculate lights loads
if lights:
power = lights.get("power", 0.0)
use_factor = lights.get("use_factor", 1.0)
special_allowance = lights.get("special_allowance", 0.0)
hours_operation = lights.get("hours_operation", "8h")
lights_load = self.calculate_lights_cooling_load(
power=power,
use_factor=use_factor,
special_allowance=special_allowance,
hours_operation=hours_operation,
hour=hour
)
loads["lights"] = lights_load
# Calculate equipment loads
if equipment:
power = equipment.get("power", 0.0)
use_factor = equipment.get("use_factor", 1.0)
radiation_factor = equipment.get("radiation_factor", 0.7)
hours_operation = equipment.get("hours_operation", "8h")
equipment_loads = self.calculate_equipment_cooling_load(
power=power,
use_factor=use_factor,
radiation_factor=radiation_factor,
hours_operation=hours_operation,
hour=hour
)
loads["equipment_sensible"] = equipment_loads["sensible"]
loads["equipment_latent"] = equipment_loads["latent"]
# Calculate total loads
loads["total_sensible"] = (
loads["walls"] + loads["roofs"] + loads["floors"] +
loads["windows_conduction"] + loads["windows_solar"] +
loads["doors"] + loads["infiltration_sensible"] +
loads["ventilation_sensible"] + loads["people_sensible"] +
loads["lights"] + loads["equipment_sensible"]
)
loads["total_latent"] = (
loads["infiltration_latent"] + loads["ventilation_latent"] +
loads["people_latent"] + loads["equipment_latent"]
)
loads["total"] = loads["total_sensible"] + loads["total_latent"]
# Store loads for this hour
hourly_loads[hour] = loads
return hourly_loads
def calculate_design_cooling_load(self, hourly_loads: Dict[int, Dict[str, float]]) -> Dict[str, float]:
"""
Calculate design cooling load based on hourly loads.
Args:
hourly_loads: Dictionary with hourly cooling loads
Returns:
Dictionary with design cooling loads
"""
# Find hour with maximum total load
max_hour = max(hourly_loads.keys(), key=lambda h: hourly_loads[h]["total"])
# Get loads for the design hour
design_loads = hourly_loads[max_hour].copy()
# Add design hour information
design_loads["design_hour"] = max_hour
return design_loads
def calculate_cooling_load_summary(self, design_loads: Dict[str, float]) -> Dict[str, float]:
"""
Calculate cooling load summary.
Args:
design_loads: Dictionary with design cooling loads
Returns:
Dictionary with cooling load summary
"""
# Calculate envelope loads
envelope_loads = (
design_loads["walls"] + design_loads["roofs"] + design_loads["floors"] +
design_loads["windows_conduction"] + design_loads["windows_solar"] +
design_loads["doors"]
)
# Calculate ventilation and infiltration loads
ventilation_loads = design_loads["ventilation_sensible"] + design_loads["ventilation_latent"]
infiltration_loads = design_loads["infiltration_sensible"] + design_loads["infiltration_latent"]
# Calculate internal loads
internal_loads = (
design_loads["people_sensible"] + design_loads["people_latent"] +
design_loads["lights"] + design_loads["equipment_sensible"] + design_loads["equipment_latent"]
)
# Calculate sensible heat ratio
shr = design_loads["total_sensible"] / design_loads["total"] if design_loads["total"] > 0 else 1.0
# Create summary
summary = {
"envelope_loads": envelope_loads,
"ventilation_loads": ventilation_loads,
"infiltration_loads": infiltration_loads,
"internal_loads": internal_loads,
"total_sensible": design_loads["total_sensible"],
"total_latent": design_loads["total_latent"],
"total": design_loads["total"],
"sensible_heat_ratio": shr,
"design_hour": design_loads["design_hour"]
}
return summary
# Create a singleton instance
cooling_load_calculator = CoolingLoadCalculator()
# Example usage
if __name__ == "__main__":
# Create sample building components
from data.building_components import Wall, Roof, Window, Door, Orientation, ComponentType
# Create a sample wall
wall = Wall(
id="wall1",
name="Exterior Wall",
component_type=ComponentType.WALL,
u_value=0.5,
area=20.0,
orientation=Orientation.SOUTH,
wall_type="Brick",
wall_group="B"
)
# Create a sample roof
roof = Roof(
id="roof1",
name="Flat Roof",
component_type=ComponentType.ROOF,
u_value=0.3,
area=50.0,
orientation=Orientation.HORIZONTAL,
roof_type="Concrete",
roof_group="C"
)
# Create a sample window
window = Window(
id="window1",
name="South Window",
component_type=ComponentType.WINDOW,
u_value=2.8,
area=5.0,
orientation=Orientation.SOUTH,
shgc=0.7,
vt=0.8,
window_type="Double Glazed",
glazing_layers=2,
gas_fill="Air",
low_e_coating=False
)
# Define building components
building_components = {
"walls": [wall],
"roofs": [roof],
"windows": [window],
"doors": [],
"floors": []
}
# Define conditions
outdoor_conditions = {
"temperature": 35.0,
"relative_humidity": 50.0,
"ground_temperature": 20.0,
"month": "Jul",
"latitude": "40N"
}
indoor_conditions = {
"temperature": 24.0,
"relative_humidity": 50.0
}
# Define internal loads
internal_loads = {
"people": {
"number": 3,
"activity_level": "Seated/Resting",
"hours_occupancy": "8h"
},
"lights": {
"power": 500.0,
"use_factor": 0.9,
"special_allowance": 0.1,
"hours_operation": "8h"
},
"equipment": {
"power": 1000.0,
"use_factor": 0.7,
"radiation_factor": 0.7,
"hours_operation": "8h"
},
"infiltration": {
"flow_rate": 0.05
},
"ventilation": {
"flow_rate": 0.1
}
}
# Calculate hourly cooling loads
hourly_loads = cooling_load_calculator.calculate_hourly_cooling_loads(
building_components=building_components,
outdoor_conditions=outdoor_conditions,
indoor_conditions=indoor_conditions,
internal_loads=internal_loads
)
# Calculate design cooling load
design_loads = cooling_load_calculator.calculate_design_cooling_load(hourly_loads)
# Calculate cooling load summary
summary = cooling_load_calculator.calculate_cooling_load_summary(design_loads)
# Print results
print("Cooling Load Summary:")
print(f"Envelope Loads: {summary['envelope_loads']:.2f} W")
print(f"Ventilation Loads: {summary['ventilation_loads']:.2f} W")
print(f"Infiltration Loads: {summary['infiltration_loads']:.2f} W")
print(f"Internal Loads: {summary['internal_loads']:.2f} W")
print(f"Total Sensible: {summary['total_sensible']:.2f} W")
print(f"Total Latent: {summary['total_latent']:.2f} W")
print(f"Total: {summary['total']:.2f} W")
print(f"Sensible Heat Ratio: {summary['sensible_heat_ratio']:.2f}")
print(f"Design Hour: {summary['design_hour']}")