HVAC / heating_load.py
mabuseif's picture
Upload 13 files
a1d7129 verified
"""
ASHRAE Heating Load Calculation Module
This module implements the ASHRAE method for calculating heating loads in residential buildings.
It calculates the heat loss from the building envelope and unwanted ventilation/infiltration.
"""
import numpy as np
import pandas as pd
class HeatingLoadCalculator:
"""
A class to calculate heating loads using the ASHRAE method.
"""
def __init__(self):
"""Initialize the heating load calculator with default values."""
# Specific heat capacity of air × density of air
self.air_heat_factor = 0.33
# Default values for internal heat gains (W)
self.heat_gain_per_person = 75
self.heat_gain_kitchen = 1000
def calculate_conduction_heat_loss(self, area, u_value, temp_diff):
"""
Calculate conduction heat loss through building components.
Args:
area (float): Area of the building component in m²
u_value (float): U-value of the component in W/m²°C
temp_diff (float): Temperature difference (inside - outside) in °C
Returns:
float: Heat loss in Watts
"""
return area * u_value * temp_diff
def calculate_wall_solar_heat_gain(self, area, u_value, orientation, daily_range='medium', latitude='medium'):
"""
Calculate solar heat gain through walls based on orientation.
Args:
area (float): Area of the wall in m²
u_value (float): U-value of the wall in W/m²°C
orientation (str): Wall orientation ('north', 'east', 'south', 'west')
daily_range (str): Daily temperature range ('low', 'medium', 'high')
latitude (str): Latitude category ('low', 'medium', 'high')
Returns:
float: Heat gain in Watts
"""
# Solar intensity factors based on orientation - for heating, south-facing walls (northern hemisphere)
# or north-facing walls (southern hemisphere) receive more solar gain in winter
# These are simplified factors for demonstration
orientation_factors = {
'north': 0.6, # Higher in southern hemisphere during winter
'east': 0.4,
'south': 0.2, # Lower in southern hemisphere during winter
'west': 0.4,
'horizontal': 0.3
}
# Adjustments for latitude
latitude_factors = {
'low': 0.9, # Closer to equator - less winter sun angle
'medium': 1.0, # Mid latitudes
'high': 1.1 # Closer to poles - more winter sun angle variation
}
# Adjustments for daily temperature range
range_factors = {
'low': 0.95, # Less than 8.5°C
'medium': 1.0, # Between 8.5°C and 14°C
'high': 1.05 # Over 14°C
}
# Base solar heat gain through walls (W/m²) - lower in winter
base_solar_gain = 10.0
# Get factors
orientation_factor = orientation_factors.get(orientation.lower(), 0.5) # Default to south if not found
latitude_factor = latitude_factors.get(latitude.lower(), 1.0)
range_factor = range_factors.get(daily_range.lower(), 1.0)
# Calculate solar heat gain
solar_gain = area * base_solar_gain * orientation_factor * latitude_factor * range_factor
# Factor in the U-value (walls with higher U-values transmit more solar heat)
u_value_factor = min(u_value / 0.5, 2.0) # Normalize against a typical U-value of 0.5
return solar_gain * u_value_factor
def calculate_infiltration_heat_loss(self, volume, air_changes, temp_diff):
"""
Calculate heat loss due to infiltration and ventilation.
Args:
volume (float): Volume of the space in m³
air_changes (float): Number of air changes per hour
temp_diff (float): Temperature difference (inside - outside) in °C
Returns:
float: Heat loss in Watts
"""
return self.air_heat_factor * volume * air_changes * temp_diff
def calculate_internal_heat_gain(self, num_people, has_kitchen=False, equipment_watts=0):
"""
Calculate internal heat gain from people, kitchen, and equipment.
Args:
num_people (int): Number of occupants
has_kitchen (bool): Whether the space includes a kitchen
equipment_watts (float): Additional equipment heat gain in Watts
Returns:
float: Heat gain in Watts
"""
people_gain = num_people * self.heat_gain_per_person
kitchen_gain = self.heat_gain_kitchen if has_kitchen else 0
return people_gain + kitchen_gain + equipment_watts
def calculate_annual_heating_energy(self, total_heat_loss, heating_degree_days, correction_factor=1.0):
"""
Calculate annual heating energy requirement using heating degree days.
Args:
total_heat_loss (float): Total heat loss in Watts
heating_degree_days (float): Number of heating degree days
correction_factor (float): Correction factor for occupancy
Returns:
float: Annual heating energy in kWh
"""
# Convert W to kW
heat_loss_kw = total_heat_loss / 1000
# Calculate annual heating energy (kWh)
# 24 hours in a day
annual_energy = heat_loss_kw * 24 * heating_degree_days * correction_factor
return annual_energy
def get_outdoor_design_temperature(self, location):
"""
Get the outdoor design temperature for a location.
Args:
location (str): Location name
Returns:
float: Outdoor design temperature in °C
"""
# This is a simplified version - in a real implementation, this would use lookup tables
# based on the AIRAH Design Data Manual
# Example data for Australian locations
temperatures = {
'sydney': 7.0,
'melbourne': 4.0,
'brisbane': 9.0,
'perth': 7.0,
'adelaide': 5.0,
'hobart': 2.0,
'darwin': 15.0,
'canberra': -1.0,
'mildura': 4.5
}
return temperatures.get(location.lower(), 5.0) # Default to 5°C if location not found
def get_heating_degree_days(self, location, base_temp=18):
"""
Get the heating degree days for a location.
Args:
location (str): Location name
base_temp (int): Base temperature for HDD calculation (default: 18°C)
Returns:
float: Heating degree days
"""
# This is a simplified version - in a real implementation, this would use lookup tables
# or API data from Bureau of Meteorology
# Example data for Australian locations with base temperature of 18°C
hdd_data = {
'sydney': 740,
'melbourne': 1400,
'brisbane': 320,
'perth': 760,
'adelaide': 1100,
'hobart': 1800,
'darwin': 0,
'canberra': 2000,
'mildura': 1200
}
return hdd_data.get(location.lower(), 1000) # Default to 1000 if location not found
def get_occupancy_correction_factor(self, occupancy_type):
"""
Get the correction factor for occupancy type.
Args:
occupancy_type (str): Type of occupancy
Returns:
float: Correction factor
"""
# Correction factors based on occupancy patterns
factors = {
'continuous': 1.0, # Continuously heated
'intermittent': 0.8, # Heated during occupied hours
'night_setback': 0.9, # Temperature setback at night
'weekend_off': 0.85, # Heating off during weekends
'vacation_home': 0.6 # Occasionally occupied
}
return factors.get(occupancy_type.lower(), 1.0) # Default to continuous if not found
def calculate_total_heating_load(self, building_components, infiltration, internal_gains=None):
"""
Calculate the total peak heating load.
Args:
building_components (list): List of dicts with 'area', 'u_value', 'temp_diff', and 'orientation' for each component
infiltration (dict): Dict with 'volume', 'air_changes', and 'temp_diff'
internal_gains (dict): Dict with 'num_people', 'has_kitchen', and 'equipment_watts'
Returns:
dict: Dictionary with component heat losses and total heating load in Watts
"""
# Calculate conduction heat loss through building components
component_losses = {}
total_conduction_loss = 0
wall_solar_gain = 0
for comp in building_components:
name = comp.get('name', f"Component {len(component_losses) + 1}")
loss = self.calculate_conduction_heat_loss(comp['area'], comp['u_value'], comp['temp_diff'])
component_losses[name] = loss
total_conduction_loss += loss
# Calculate solar gain for walls based on orientation
if 'orientation' in comp:
daily_range = comp.get('daily_range', 'medium')
latitude = comp.get('latitude', 'medium')
solar_gain = self.calculate_wall_solar_heat_gain(
comp['area'],
comp['u_value'],
comp['orientation'],
daily_range,
latitude
)
wall_solar_gain += solar_gain
# Calculate infiltration heat loss
infiltration_loss = self.calculate_infiltration_heat_loss(
infiltration['volume'], infiltration['air_changes'], infiltration['temp_diff']
)
# Calculate internal heat gain if provided
internal_gain = 0
if internal_gains:
internal_gain = self.calculate_internal_heat_gain(
internal_gains.get('num_people', 0),
internal_gains.get('has_kitchen', False),
internal_gains.get('equipment_watts', 0)
)
# Calculate total heating load (subtract solar gain and internal gains as they reduce heating load)
total_load = total_conduction_loss + infiltration_loss - wall_solar_gain - internal_gain
return {
'component_losses': component_losses,
'total_conduction_loss': total_conduction_loss,
'infiltration_loss': infiltration_loss,
'wall_solar_gain': wall_solar_gain,
'internal_gain': internal_gain,
'total_load': total_load
}
def calculate_annual_heating_requirement(self, total_load, location, occupancy_type='continuous', base_temp=18):
"""
Calculate the annual heating energy requirement.
Args:
total_load (float): Total heating load in Watts
location (str): Location name
occupancy_type (str): Type of occupancy
base_temp (int): Base temperature for HDD calculation
Returns:
dict: Dictionary with annual heating energy in kWh and related factors
"""
# Get heating degree days for the location
hdd = self.get_heating_degree_days(location, base_temp)
# Get correction factor for occupancy
correction_factor = self.get_occupancy_correction_factor(occupancy_type)
# Calculate annual heating energy
annual_energy = self.calculate_annual_heating_energy(total_load, hdd, correction_factor)
return {
'heating_degree_days': hdd,
'correction_factor': correction_factor,
'annual_energy_kwh': annual_energy,
'annual_energy_mj': annual_energy * 3.6 # Convert kWh to MJ
}
# Example usage
if __name__ == "__main__":
calculator = HeatingLoadCalculator()
# Example data for a simple room in Mildura
building_components = [
{'name': 'Floor', 'area': 50, 'u_value': 1.47, 'temp_diff': 16.5}, # Concrete slab
{'name': 'Walls', 'area': 80, 'u_value': 1.5, 'temp_diff': 16.5}, # External walls
{'name': 'Ceiling', 'area': 50, 'u_value': 0.9, 'temp_diff': 16.5}, # Ceiling
{'name': 'Windows', 'area': 8, 'u_value': 5.8, 'temp_diff': 16.5} # Windows
]
infiltration = {'volume': 125, 'air_changes': 0.5, 'temp_diff': 16.5}
# Calculate peak heating load
result = calculator.calculate_total_heating_load(building_components, infiltration)
print("Heating Load Calculation Results:")
for key, value in result.items():
if key == 'component_losses':
print("Component Losses:")
for comp, loss in value.items():
print(f" {comp}: {loss:.2f} W")
else:
print(f"{key}: {value:.2f} W")
# Calculate annual heating requirement
annual_result = calculator.calculate_annual_heating_requirement(result['total_load'], 'mildura')
print("\nAnnual Heating Requirement:")
for key, value in annual_result.items():
print(f"{key}: {value:.2f}")