""" 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}")