|
|
""" |
|
|
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.""" |
|
|
|
|
|
self.air_heat_factor = 0.33 |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
orientation_factors = { |
|
|
'north': 0.6, |
|
|
'east': 0.4, |
|
|
'south': 0.2, |
|
|
'west': 0.4, |
|
|
'horizontal': 0.3 |
|
|
} |
|
|
|
|
|
|
|
|
latitude_factors = { |
|
|
'low': 0.9, |
|
|
'medium': 1.0, |
|
|
'high': 1.1 |
|
|
} |
|
|
|
|
|
|
|
|
range_factors = { |
|
|
'low': 0.95, |
|
|
'medium': 1.0, |
|
|
'high': 1.05 |
|
|
} |
|
|
|
|
|
|
|
|
base_solar_gain = 10.0 |
|
|
|
|
|
|
|
|
orientation_factor = orientation_factors.get(orientation.lower(), 0.5) |
|
|
latitude_factor = latitude_factors.get(latitude.lower(), 1.0) |
|
|
range_factor = range_factors.get(daily_range.lower(), 1.0) |
|
|
|
|
|
|
|
|
solar_gain = area * base_solar_gain * orientation_factor * latitude_factor * range_factor |
|
|
|
|
|
|
|
|
u_value_factor = min(u_value / 0.5, 2.0) |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
heat_loss_kw = total_heat_loss / 1000 |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
factors = { |
|
|
'continuous': 1.0, |
|
|
'intermittent': 0.8, |
|
|
'night_setback': 0.9, |
|
|
'weekend_off': 0.85, |
|
|
'vacation_home': 0.6 |
|
|
} |
|
|
|
|
|
return factors.get(occupancy_type.lower(), 1.0) |
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
infiltration_loss = self.calculate_infiltration_heat_loss( |
|
|
infiltration['volume'], infiltration['air_changes'], infiltration['temp_diff'] |
|
|
) |
|
|
|
|
|
|
|
|
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) |
|
|
) |
|
|
|
|
|
|
|
|
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 |
|
|
""" |
|
|
|
|
|
hdd = self.get_heating_degree_days(location, base_temp) |
|
|
|
|
|
|
|
|
correction_factor = self.get_occupancy_correction_factor(occupancy_type) |
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
calculator = HeatingLoadCalculator() |
|
|
|
|
|
|
|
|
building_components = [ |
|
|
{'name': 'Floor', 'area': 50, 'u_value': 1.47, 'temp_diff': 16.5}, |
|
|
{'name': 'Walls', 'area': 80, 'u_value': 1.5, 'temp_diff': 16.5}, |
|
|
{'name': 'Ceiling', 'area': 50, 'u_value': 0.9, 'temp_diff': 16.5}, |
|
|
{'name': 'Windows', 'area': 8, 'u_value': 5.8, 'temp_diff': 16.5} |
|
|
] |
|
|
|
|
|
infiltration = {'volume': 125, 'air_changes': 0.5, 'temp_diff': 16.5} |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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}") |
|
|
|