Spaces:
Sleeping
Sleeping
| """ | |
| 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}") | |