HVAC-testing / utils /cooling_load.py
mabuseif's picture
Update utils/cooling_load.py
0b53b9e verified
raw
history blame
26.2 kB
"""
Cooling load calculation module for HVAC Load Calculator.
Based on ASHRAE steady-state calculation methods.
Author: Dr Majed Abuseif
Date: March 2025
Version: 1.0.0
"""
from typing import Dict, List, Any, Optional, Tuple
import numpy as np
from datetime import datetime
from data.ashrae_tables import ASHRAETables
from utils.heat_transfer import HeatTransferCalculations
from app.component_selection import Wall, Roof, Window, Door, Orientation
class CoolingLoadCalculator:
"""Class for cooling load calculations."""
def __init__(self):
"""Initialize cooling load calculator."""
self.ashrae_tables = ASHRAETables()
self.heat_transfer = HeatTransferCalculations()
self.hours = list(range(1, 25))
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],
building_volume: float
) -> Dict[str, Any]:
"""
Calculate hourly cooling loads for all components.
Args:
building_components: Dictionary of building components
outdoor_conditions: Outdoor weather conditions
indoor_conditions: Indoor design conditions
internal_loads: Internal heat gains
building_volume: Building volume in cubic meters
Returns:
Dictionary containing hourly cooling loads
"""
hourly_loads = {
'walls': {h: 0.0 for h in self.hours},
'roofs': {h: 0.0 for h in self.hours},
'windows_conduction': {h: 0.0 for h in self.hours},
'windows_solar': {h: 0.0 for h in self.hours},
'doors': {h: 0.0 for h in self.hours},
'people_sensible': {h: 0.0 for h in self.hours},
'people_latent': {h: 0.0 for h in self.hours},
'lights': {h: 0.0 for h in self.hours},
'equipment_sensible': {h: 0.0 for h in self.hours},
'equipment_latent': {h: 0.0 for h in self.hours},
'infiltration_sensible': {h: 0.0 for h in self.hours},
'infiltration_latent': {h: 0.0 for h in self.hours},
'ventilation_sensible': {h: 0.0 for h in self.hours},
'ventilation_latent': {h: 0.0 for h in self.hours}
}
try:
# Calculate loads for walls
for wall in building_components.get('walls', []):
for hour in self.hours:
load = self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=hour,
latitude=outdoor_conditions['latitude']
)
hourly_loads['walls'][hour] += load
# Calculate loads for roofs
for roof in building_components.get('roofs', []):
for hour in self.hours:
load = self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=hour,
latitude=outdoor_conditions['latitude']
)
hourly_loads['roofs'][hour] += load
# Calculate loads for windows
for window in building_components.get('windows', []):
for hour in self.hours:
load_dict = self.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=hour,
latitude=outdoor_conditions['latitude'],
shading_coefficient=window.shading_coefficient
)
hourly_loads['windows_conduction'][hour] += load_dict['conduction']
hourly_loads['windows_solar'][hour] += load_dict['solar']
# Calculate loads for doors
for door in building_components.get('doors', []):
for hour in self.hours:
load = self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature']
)
hourly_loads['doors'][hour] += load
# Calculate internal loads
for hour in self.hours:
# People loads
people_load = self.calculate_people_cooling_load(
num_people=internal_loads['people']['number'],
activity_level=internal_loads['people']['activity_level'],
hour=hour
)
hourly_loads['people_sensible'][hour] += people_load['sensible']
hourly_loads['people_latent'][hour] += people_load['latent']
# Lighting loads
lights_load = self.calculate_lights_cooling_load(
power=internal_loads['lights']['power'],
use_factor=internal_loads['lights']['use_factor'],
special_allowance=internal_loads['lights']['special_allowance'],
hour=hour
)
hourly_loads['lights'][hour] += lights_load
# Equipment loads
equipment_load = self.calculate_equipment_cooling_load(
power=internal_loads['equipment']['power'],
use_factor=internal_loads['equipment']['use_factor'],
radiation_factor=internal_loads['equipment']['radiation_factor'],
hour=hour
)
hourly_loads['equipment_sensible'][hour] += equipment_load['sensible']
hourly_loads['equipment_latent'][hour] += equipment_load['latent']
# Infiltration loads
infiltration_load = self.calculate_infiltration_cooling_load(
flow_rate=internal_loads['infiltration']['flow_rate'],
building_volume=building_volume,
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['infiltration_sensible'][hour] += infiltration_load['sensible']
hourly_loads['infiltration_latent'][hour] += infiltration_load['latent']
# Ventilation loads
ventilation_load = self.calculate_ventilation_cooling_load(
flow_rate=internal_loads['ventilation']['flow_rate'],
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['ventilation_sensible'][hour] += ventilation_load['sensible']
hourly_loads['ventilation_latent'][hour] += ventilation_load['latent']
return hourly_loads
except Exception as e:
raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate design cooling load based on peak hourly loads.
Args:
hourly_loads: Dictionary of hourly cooling loads
Returns:
Dictionary containing design cooling loads
"""
try:
design_loads = {}
total_loads = []
for hour in self.hours:
total_load = sum([
hourly_loads['walls'][hour],
hourly_loads['roofs'][hour],
hourly_loads['windows_conduction'][hour],
hourly_loads['windows_solar'][hour],
hourly_loads['doors'][hour],
hourly_loads['people_sensible'][hour],
hourly_loads['people_latent'][hour],
hourly_loads['lights'][hour],
hourly_loads['equipment_sensible'][hour],
hourly_loads['equipment_latent'][hour],
hourly_loads['infiltration_sensible'][hour],
hourly_loads['infiltration_latent'][hour],
hourly_loads['ventilation_sensible'][hour],
hourly_loads['ventilation_latent'][hour]
])
total_loads.append(total_load)
design_hour = self.hours[np.argmax(total_loads)]
design_loads = {
'design_hour': design_hour,
'walls': hourly_loads['walls'][design_hour],
'roofs': hourly_loads['roofs'][design_hour],
'windows_conduction': hourly_loads['windows_conduction'][design_hour],
'windows_solar': hourly_loads['windows_solar'][design_hour],
'doors': hourly_loads['doors'][design_hour],
'people_sensible': hourly_loads['people_sensible'][design_hour],
'people_latent': hourly_loads['people_latent'][design_hour],
'lights': hourly_loads['lights'][design_hour],
'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
'equipment_latent': hourly_loads['equipment_latent'][design_hour],
'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
}
return design_loads
except Exception as e:
raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
"""
Calculate summary of cooling loads.
Args:
design_loads: Dictionary of design cooling loads
Returns:
Dictionary containing cooling load summary
"""
try:
total_sensible = (
design_loads['walls'] +
design_loads['roofs'] +
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']
)
total_latent = (
design_loads['people_latent'] +
design_loads['equipment_latent'] +
design_loads['infiltration_latent'] +
design_loads['ventilation_latent']
)
total = total_sensible + total_latent
return {
'total_sensible': total_sensible,
'total_latent': total_latent,
'total': total
}
except Exception as e:
raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
def calculate_wall_cooling_load(
self,
wall: Wall,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a wall.
Args:
wall: Wall component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '40N')
Returns:
Cooling load in Watts
"""
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
wall_group=wall.wall_group,
orientation=wall.orientation.value,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
load = wall.u_value * wall.area * cltd
return max(load, 0.0)
except Exception as e:
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
def calculate_roof_cooling_load(
self,
roof: Roof,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a roof.
Args:
roof: Roof component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '40N')
Returns:
Cooling load in Watts
"""
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
roof_group=roof.roof_group,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
load = roof.u_value * roof.area * cltd
return max(load, 0.0)
except Exception as e:
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
def calculate_window_cooling_load(
self,
window: Window,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str,
shading_coefficient: float
) -> Dict[str, float]:
"""
Calculate cooling load for a window (conduction and solar).
Args:
window: Window component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '40N')
shading_coefficient: Shading coefficient for drapery
Returns:
Dictionary with conduction and solar loads in Watts
"""
try:
# Conduction load
delta_t = outdoor_temp - indoor_temp
conduction_load = window.u_value * window.area * delta_t
# Solar load
solar_altitude = self.heat_transfer.solar.solar_altitude
scl_latitude = f"{float(latitude[:-1])}_{month.upper()}"
scl = self.ashrae_tables.get_scl(
orientation=window.orientation.value,
hour=hour,
latitude=scl_latitude
)
solar_load = window.area * window.shgc * scl * shading_coefficient
return {
'conduction': max(conduction_load, 0.0),
'solar': max(solar_load, 0.0),
'total': max(conduction_load + solar_load, 0.0)
}
except Exception as e:
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
def calculate_door_cooling_load(
self,
door: Door,
outdoor_temp: float,
indoor_temp: float
) -> float:
"""
Calculate cooling load for a door.
Args:
door: Door component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
Returns:
Cooling load in Watts
"""
try:
delta_t = outdoor_temp - indoor_temp
load = door.u_value * door.area * delta_t
return max(load, 0.0)
except Exception as e:
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
def calculate_people_cooling_load(
self,
num_people: int,
activity_level: str,
hour: int
) -> Dict[str, float]:
"""
Calculate cooling load from people.
Args:
num_people: Number of people
activity_level: Activity level
hour: Hour of the day
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
sensible_gain = {
'Seated/Resting': 70,
'Light Work': 100,
'Moderate Work': 150,
'Heavy Work': 200
}.get(activity_level, 70)
latent_gain = {
'Seated/Resting': 45,
'Light Work': 75,
'Moderate Work': 120,
'Heavy Work': 180
}.get(activity_level, 45)
clf = self.ashrae_tables.get_clf_people(hour, '8h')
sensible_load = num_people * sensible_gain * clf
latent_load = num_people * latent_gain
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0),
'total': max(sensible_load + latent_load, 0.0)
}
except Exception as e:
raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
def calculate_lights_cooling_load(
self,
power: float,
use_factor: float,
special_allowance: float,
hour: int
) -> float:
"""
Calculate cooling load from lighting.
Args:
power: Lighting power (W)
use_factor: Usage factor
special_allowance: Special allowance factor
hour: Hour of the day
Returns:
Cooling load in Watts
"""
try:
clf = self.ashrae_tables.get_clf_lights(hour, '8h')
load = power * use_factor * special_allowance * clf
return max(load, 0.0)
except Exception as e:
raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
def calculate_equipment_cooling_load(
self,
power: float,
use_factor: float,
radiation_factor: float,
hour: int
) -> Dict[str, float]:
"""
Calculate cooling load from equipment.
Args:
power: Equipment power (W)
use_factor: Usage factor
radiation_factor: Radiation factor
hour: Hour of the day
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
clf = self.ashrae_tables.get_clf_equipment(hour, '8h')
sensible_load = power * use_factor * radiation_factor * clf
latent_load = power * use_factor * (1 - radiation_factor)
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0),
'total': max(sensible_load + latent_load, 0.0)
}
except Exception as e:
raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
def calculate_infiltration_cooling_load(
self,
flow_rate: float,
building_volume: float,
outdoor_temp: float,
outdoor_rh: float,
indoor_temp: float,
indoor_rh: float
) -> Dict[str, float]:
"""
Calculate cooling load from infiltration.
Args:
flow_rate: Infiltration flow rate (m³/s)
building_volume: Building volume (m³)
outdoor_temp: Outdoor temperature (°C)
outdoor_rh: Outdoor relative humidity (%)
indoor_temp: Indoor temperature (°C)
indoor_rh: Indoor relative humidity (%)
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
air_changes_per_hour = (flow_rate * 3600) / building_volume
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
# Calculate humidity ratio difference
outdoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
indoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(indoor_temp, indoor_rh)
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0),
'total': max(sensible_load + latent_load, 0.0)
}
except Exception as e:
raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
def calculate_ventilation_cooling_load(
self,
flow_rate: float,
outdoor_temp: float,
outdoor_rh: float,
indoor_temp: float,
indoor_rh: float
) -> Dict[str, float]:
"""
Calculate cooling load from ventilation.
Args:
flow_rate: Ventilation flow rate (m³/s)
outdoor_temp: Outdoor temperature (°C)
outdoor_rh: Outdoor relative humidity (%)
indoor_temp: Indoor temperature (°C)
indoor_rh: Indoor relative humidity (%)
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
# Calculate humidity ratio difference
outdoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
indoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(indoor_temp, indoor_rh)
latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0),
'total': max(sensible_load + latent_load, 0.0)
}
except Exception as e:
raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
if __name__ == "__main__":
# Example usage for testing
calculator = CoolingLoadCalculator()
# Dummy inputs
building_components = {
'walls': [Wall(
name="North Wall",
orientation=Orientation.NORTH,
area=20.0,
u_value=0.5,
wall_group="A"
)],
'roofs': [Roof(
name="Main Roof",
orientation=Orientation.HORIZONTAL,
area=100.0,
u_value=0.3,
roof_group="1"
)],
'windows': [Window(
name="South Window",
orientation=Orientation.SOUTH,
area=10.0,
u_value=2.8,
shgc=0.7,
shading_device="Medium drapery",
shading_coefficient=0.8
)],
'doors': [Door(
name="Main Door",
orientation=Orientation.NORTH,
area=2.0,
u_value=2.0
)]
}
outdoor_conditions = {
'temperature': 35.0,
'relative_humidity': 50.0,
'ground_temperature': 20.0,
'month': 'Jul',
'latitude': '31.973N',
'wind_speed': 4.0,
'day_of_year': 204
}
indoor_conditions = {
'temperature': 24.0,
'relative_humidity': 50.0
}
internal_loads = {
'people': {
'number': 10,
'activity_level': 'Seated/Resting',
'operating_hours': '8:00-18:00'
},
'lights': {
'power': 1000.0,
'use_factor': 0.8,
'special_allowance': 0.1,
'hours_operation': '8h'
},
'equipment': {
'power': 500.0,
'use_factor': 0.7,
'radiation_factor': 0.3,
'hours_operation': '8h'
},
'infiltration': {
'flow_rate': 0.05,
'height': 3.0,
'crack_length': 10.0
},
'ventilation': {
'flow_rate': 0.1
},
'operating_hours': '8:00-18:00'
}
building_volume = 300.0
# Calculate loads
hourly_loads = calculator.calculate_hourly_cooling_loads(
building_components=building_components,
outdoor_conditions=outdoor_conditions,
indoor_conditions=indoor_conditions,
internal_loads=internal_loads,
building_volume=building_volume
)
design_loads = calculator.calculate_design_cooling_load(hourly_loads)
summary = calculator.calculate_cooling_load_summary(design_loads)
print("Cooling Load Summary:", summary)