not-sure / utils /cooling_load.py
mabuseif's picture
Upload 25 files
6cc22a6 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 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 based on ASHRAE Fundamentals
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,
"activity_level": activity_level, # Include activity level in the return value for debugging
"sensible_gain_per_person": sensible_gain, # Include per-person gain for debugging
"latent_gain_per_person": latent_gain # Include per-person gain for debugging
}
def calculate_lights_cooling_load(self, power: float, use_factor: float,
special_allowance: float, hours_operation: str,
hour: int, lighting_type: str = "Fluorescent") -> Dict[str, 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)
lighting_type: Type of lighting (LED, Fluorescent, Halogen, Incandescent)
Returns:
Dictionary with cooling load information
"""
# Define heat emission factors for different lighting types
# These factors represent the ratio of heat emitted to power consumed
lighting_factors = {
"LED": 0.80, # LEDs convert ~80% of power to heat
"Fluorescent": 0.85, # Fluorescent lights convert ~85% of power to heat
"Halogen": 0.95, # Halogen lights convert ~95% of power to heat
"Incandescent": 0.98 # Incandescent lights convert ~98% of power to heat
}
# Get heat emission factor for the specified lighting type
if lighting_type not in lighting_factors:
raise ValueError(f"Invalid lighting type: {lighting_type}")
heat_emission_factor = lighting_factors[lighting_type]
# 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) * heat_emission_factor * clf
return {
"total": cooling_load,
"lighting_type": lighting_type,
"heat_emission_factor": heat_emission_factor,
"clf": clf
}
def calculate_equipment_cooling_load(self, power: float, use_factor: float,
radiation_factor: float, hours_operation: str,
hour: int, equipment_type: str = "General") -> 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)
equipment_type: Type of equipment (General, Computer, Kitchen, Medical, Laboratory)
Returns:
Dictionary with sensible, latent, and total cooling loads in W
"""
# Define equipment type factors based on ASHRAE Fundamentals
# These factors adjust the base power for different equipment types
equipment_factors = {
"General": {"sensible_factor": 1.0, "latent_factor": 0.0},
"Computer": {"sensible_factor": 0.95, "latent_factor": 0.0},
"Kitchen": {"sensible_factor": 0.80, "latent_factor": 0.20},
"Medical": {"sensible_factor": 0.90, "latent_factor": 0.10},
"Laboratory": {"sensible_factor": 0.85, "latent_factor": 0.15}
}
# Get factors for the specified equipment type
if equipment_type not in equipment_factors:
raise ValueError(f"Invalid equipment type: {equipment_type}")
sensible_factor = equipment_factors[equipment_type]["sensible_factor"]
latent_factor = equipment_factors[equipment_type]["latent_factor"]
# Get CLF for the specified hour and operation
clf = self.ashrae_tables.get_clf_equipment(hour, hours_operation)
# Calculate adjusted power based on equipment type
adjusted_power = power * use_factor
# Calculate sensible cooling load
sensible_load = adjusted_power * sensible_factor * radiation_factor * clf
# Calculate latent cooling load (if any)
latent_load = adjusted_power * latent_factor
# Calculate total cooling load
total_load = sensible_load + latent_load
return {
"sensible": sensible_load,
"latent": latent_load,
"total": total_load,
"equipment_type": equipment_type,
"sensible_factor": sensible_factor,
"latent_factor": latent_factor,
"clf": clf
}
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,
"people_sensible": 0,
"people_latent": 0,
"lights": 0,
"equipment_sensible": 0,
"equipment_latent": 0,
"infiltration_sensible": 0,
"infiltration_latent": 0,
"ventilation_sensible": 0,
"ventilation_latent": 0
}
# Calculate wall loads
for wall in walls:
loads["walls"] += self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude
)
# Calculate roof loads
for roof in roofs:
loads["roofs"] += self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp,
month=month,
hour=hour,
latitude=latitude
)
# Calculate floor loads
for floor in floors:
loads["floors"] += self.calculate_floor_cooling_load(
floor=floor,
ground_temp=ground_temp,
indoor_temp=indoor_temp
)
# 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()}"
)
loads["windows_conduction"] += window_loads["conduction"]
loads["windows_solar"] += window_loads["solar"]
# Calculate door loads
for door in doors:
loads["doors"] += self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_temp,
indoor_temp=indoor_temp
)
# Calculate people loads
if "number" in people and "activity_level" in people and "hours_occupancy" in people:
people_loads = self.calculate_people_cooling_load(
num_people=people["number"],
activity_level=people["activity_level"],
hours_occupancy=people["hours_occupancy"],
hour=hour
)
loads["people_sensible"] += people_loads["sensible"]
loads["people_latent"] += people_loads["latent"]
# Calculate lighting loads
if "power" in lights and "use_factor" in lights and "hours_operation" in lights:
lighting_loads = self.calculate_lights_cooling_load(
power=lights["power"],
use_factor=lights["use_factor"],
special_allowance=lights.get("special_allowance", 0.1),
hours_operation=lights["hours_operation"],
hour=hour,
lighting_type=lights.get("lighting_type", "Fluorescent")
)
loads["lights"] += lighting_loads["total"]
# Calculate equipment loads
if "power" in equipment and "use_factor" in equipment and "hours_operation" in equipment:
equipment_loads = self.calculate_equipment_cooling_load(
power=equipment["power"],
use_factor=equipment["use_factor"],
radiation_factor=equipment.get("radiation_factor", 0.3),
hours_operation=equipment["hours_operation"],
hour=hour,
equipment_type=equipment.get("equipment_type", "General")
)
loads["equipment_sensible"] += equipment_loads["sensible"]
loads["equipment_latent"] += equipment_loads["latent"]
# Calculate infiltration loads
if "flow_rate" in infiltration:
infiltration_loads = self.calculate_infiltration_cooling_load(
flow_rate=infiltration["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 "flow_rate" in ventilation:
ventilation_loads = self.calculate_ventilation_cooling_load(
flow_rate=ventilation["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"]
# 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 from hourly loads.
Args:
hourly_loads: Dictionary with hourly cooling loads
Returns:
Dictionary with design cooling loads
"""
# Initialize design loads
design_loads = {
"walls": 0,
"roofs": 0,
"floors": 0,
"windows_conduction": 0,
"windows_solar": 0,
"doors": 0,
"people_sensible": 0,
"people_latent": 0,
"lights": 0,
"equipment_sensible": 0,
"equipment_latent": 0,
"infiltration_sensible": 0,
"infiltration_latent": 0,
"ventilation_sensible": 0,
"ventilation_latent": 0,
"design_hour": 0
}
# Calculate total load for each hour
hourly_totals = {}
for hour, loads in hourly_loads.items():
total = sum(loads.values())
hourly_totals[hour] = total
# Find design hour (hour with maximum total load)
design_hour = max(hourly_totals, key=hourly_totals.get)
design_loads["design_hour"] = design_hour
# Get loads for design hour
design_hour_loads = hourly_loads[design_hour]
# Copy loads for design hour to design loads
for key, value in design_hour_loads.items():
design_loads[key] = value
return design_loads
def calculate_cooling_load_summary(self, design_loads: Dict[str, float]) -> Dict[str, float]:
"""
Calculate cooling load summary from design loads.
Args:
design_loads: Dictionary with design cooling loads
Returns:
Dictionary with cooling load summary
"""
# Calculate total sensible load
total_sensible = (
design_loads["walls"] +
design_loads["roofs"] +
design_loads["floors"] +
design_loads["windows_conduction"] +
design_loads["windows_solar"] +
design_loads["doors"] +
design_loads["people_sensible"] +
design_loads["lights"] +
design_loads["equipment_sensible"] +
design_loads["infiltration_sensible"] +
design_loads["ventilation_sensible"]
)
# Calculate total latent load
total_latent = (
design_loads["people_latent"] +
design_loads["equipment_latent"] +
design_loads["infiltration_latent"] +
design_loads["ventilation_latent"]
)
# Calculate total load
total = total_sensible + total_latent
# Calculate safety factor (10%)
safety_factor = 0.1 * total
# Calculate grand total
grand_total = total + safety_factor
return {
"total_sensible": total_sensible,
"total_latent": total_latent,
"total": total,
"safety_factor": safety_factor,
"grand_total": grand_total
}