Spaces:
Sleeping
Sleeping
| """ | |
| Heating load calculation module for HVAC Load Calculator. | |
| This module implements steady-state methods for calculating heating loads. | |
| """ | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import math | |
| import numpy as np | |
| import pandas as pd | |
| import os | |
| from datetime import datetime, timedelta | |
| from enum import Enum | |
| # Import data models and utilities | |
| from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType | |
| from utils.psychrometrics import Psychrometrics | |
| from utils.heat_transfer import HeatTransferCalculations | |
| # Define paths | |
| DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| class HeatingLoadCalculator: | |
| """Class for calculating heating loads using steady-state methods.""" | |
| def __init__(self): | |
| """Initialize heating load calculator.""" | |
| self.heat_transfer = HeatTransferCalculations() | |
| self.psychrometrics = Psychrometrics() | |
| def calculate_wall_heating_load(self, wall: Wall, outdoor_temp: float, indoor_temp: float) -> float: | |
| """ | |
| Calculate heating load through a wall using steady-state conduction. | |
| Args: | |
| wall: Wall object | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Heating load in W | |
| """ | |
| # Get wall properties | |
| u_value = wall.u_value | |
| area = wall.area | |
| # Calculate heating load | |
| delta_t = indoor_temp - outdoor_temp | |
| heating_load = u_value * area * delta_t | |
| return heating_load | |
| def calculate_roof_heating_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float) -> float: | |
| """ | |
| Calculate heating load through a roof using steady-state conduction. | |
| Args: | |
| roof: Roof object | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Heating load in W | |
| """ | |
| # Get roof properties | |
| u_value = roof.u_value | |
| area = roof.area | |
| # Calculate heating load | |
| delta_t = indoor_temp - outdoor_temp | |
| heating_load = u_value * area * delta_t | |
| return heating_load | |
| def calculate_floor_heating_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float: | |
| """ | |
| Calculate heating load through a floor. | |
| Args: | |
| floor: Floor object | |
| ground_temp: Ground or adjacent space temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Heating load in W | |
| """ | |
| # Get floor properties | |
| u_value = floor.u_value | |
| area = floor.area | |
| # Calculate heating load | |
| delta_t = indoor_temp - ground_temp | |
| heating_load = u_value * area * delta_t | |
| return heating_load | |
| def calculate_window_heating_load(self, window: Window, outdoor_temp: float, indoor_temp: float) -> float: | |
| """ | |
| Calculate heating load through a window using steady-state conduction. | |
| Args: | |
| window: Window object | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Heating load in W | |
| """ | |
| # Get window properties | |
| u_value = window.u_value | |
| area = window.area | |
| # Calculate heating load | |
| delta_t = indoor_temp - outdoor_temp | |
| heating_load = u_value * area * delta_t | |
| return heating_load | |
| def calculate_door_heating_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float: | |
| """ | |
| Calculate heating load through a door using steady-state conduction. | |
| Args: | |
| door: Door object | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Heating load in W | |
| """ | |
| # Get door properties | |
| u_value = door.u_value | |
| area = door.area | |
| # Calculate heating load | |
| delta_t = indoor_temp - outdoor_temp | |
| heating_load = u_value * area * delta_t | |
| return heating_load | |
| def calculate_infiltration_heating_load(self, flow_rate: float, outdoor_temp: float, indoor_temp: float, | |
| outdoor_rh: float, indoor_rh: float) -> Dict[str, float]: | |
| """ | |
| Calculate sensible and latent heating loads due to infiltration. | |
| Args: | |
| flow_rate: Infiltration flow rate in m³/s | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| outdoor_rh: Outdoor relative humidity in % | |
| indoor_rh: Indoor relative humidity in % | |
| Returns: | |
| Dictionary with sensible, latent, and total heating loads in W | |
| """ | |
| # Calculate sensible heating load | |
| sensible_load = self.heat_transfer.infiltration_heat_transfer( | |
| flow_rate=flow_rate, | |
| delta_t=indoor_temp - outdoor_temp | |
| ) | |
| # Calculate humidity ratios | |
| w_outdoor = self.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh) | |
| w_indoor = self.psychrometrics.humidity_ratio(indoor_temp, indoor_rh) | |
| # Calculate latent heating load (only if indoor humidity is higher than outdoor) | |
| delta_w = w_indoor - w_outdoor | |
| if delta_w > 0: | |
| latent_load = self.heat_transfer.infiltration_latent_heat_transfer( | |
| flow_rate=flow_rate, | |
| delta_w=delta_w | |
| ) | |
| else: | |
| latent_load = 0 | |
| # Calculate total heating load | |
| total_load = sensible_load + latent_load | |
| return { | |
| "sensible": sensible_load, | |
| "latent": latent_load, | |
| "total": total_load | |
| } | |
| def calculate_ventilation_heating_load(self, flow_rate: float, outdoor_temp: float, indoor_temp: float, | |
| outdoor_rh: float, indoor_rh: float) -> Dict[str, float]: | |
| """ | |
| Calculate sensible and latent heating loads due to ventilation. | |
| Args: | |
| flow_rate: Ventilation flow rate in m³/s | |
| outdoor_temp: Outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| outdoor_rh: Outdoor relative humidity in % | |
| indoor_rh: Indoor relative humidity in % | |
| Returns: | |
| Dictionary with sensible, latent, and total heating loads in W | |
| """ | |
| # Ventilation load calculation is the same as infiltration | |
| return self.calculate_infiltration_heating_load( | |
| flow_rate=flow_rate, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp, | |
| outdoor_rh=outdoor_rh, | |
| indoor_rh=indoor_rh | |
| ) | |
| def calculate_internal_gains_offset(self, people_load: float, lights_load: float, | |
| equipment_load: float, usage_factor: float = 0.7) -> float: | |
| """ | |
| Calculate internal gains offset for heating load. | |
| Args: | |
| people_load: Heat gain from people in W | |
| lights_load: Heat gain from lights in W | |
| equipment_load: Heat gain from equipment in W | |
| usage_factor: Usage factor for internal gains (0-1) | |
| Returns: | |
| Internal gains offset in W | |
| """ | |
| # Calculate total internal gains | |
| total_gains = people_load + lights_load + equipment_load | |
| # Apply usage factor | |
| offset = total_gains * usage_factor | |
| return offset | |
| def calculate_design_heating_load(self, building_components: Dict[str, List[Any]], | |
| outdoor_conditions: Dict[str, Any], | |
| indoor_conditions: Dict[str, Any], | |
| internal_loads: Dict[str, Any], | |
| safety_factor: float = 1.15) -> Dict[str, float]: | |
| """ | |
| Calculate design heating load for a building. | |
| Args: | |
| building_components: Dictionary with lists of building components | |
| outdoor_conditions: Dictionary with outdoor conditions | |
| indoor_conditions: Dictionary with indoor conditions | |
| internal_loads: Dictionary with internal loads | |
| safety_factor: Safety factor for heating load (default: 1.15) | |
| Returns: | |
| Dictionary with design heating loads | |
| """ | |
| # Extract building components | |
| walls = building_components.get("walls", []) | |
| roofs = building_components.get("roofs", []) | |
| floors = building_components.get("floors", []) | |
| windows = building_components.get("windows", []) | |
| doors = building_components.get("doors", []) | |
| # Extract outdoor conditions | |
| outdoor_temp = outdoor_conditions.get("design_temperature", -10.0) | |
| outdoor_rh = outdoor_conditions.get("design_relative_humidity", 80.0) | |
| ground_temp = outdoor_conditions.get("ground_temperature", 10.0) | |
| # Extract indoor conditions | |
| indoor_temp = indoor_conditions.get("temperature", 21.0) | |
| indoor_rh = indoor_conditions.get("relative_humidity", 40.0) | |
| # Extract internal loads | |
| people = internal_loads.get("people", {}) | |
| lights = internal_loads.get("lights", {}) | |
| equipment = internal_loads.get("equipment", {}) | |
| infiltration = internal_loads.get("infiltration", {}) | |
| ventilation = internal_loads.get("ventilation", {}) | |
| # Initialize loads | |
| loads = { | |
| "walls": 0, | |
| "roofs": 0, | |
| "floors": 0, | |
| "windows": 0, | |
| "doors": 0, | |
| "infiltration_sensible": 0, | |
| "infiltration_latent": 0, | |
| "ventilation_sensible": 0, | |
| "ventilation_latent": 0, | |
| "internal_gains_offset": 0, | |
| "subtotal": 0, | |
| "safety_factor": safety_factor, | |
| "total": 0 | |
| } | |
| # Calculate wall loads | |
| for wall in walls: | |
| wall_load = self.calculate_wall_heating_load( | |
| wall=wall, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp | |
| ) | |
| loads["walls"] += wall_load | |
| # Calculate roof loads | |
| for roof in roofs: | |
| roof_load = self.calculate_roof_heating_load( | |
| roof=roof, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp | |
| ) | |
| loads["roofs"] += roof_load | |
| # Calculate floor loads | |
| for floor in floors: | |
| floor_load = self.calculate_floor_heating_load( | |
| floor=floor, | |
| ground_temp=ground_temp, | |
| indoor_temp=indoor_temp | |
| ) | |
| loads["floors"] += floor_load | |
| # Calculate window loads | |
| for window in windows: | |
| window_load = self.calculate_window_heating_load( | |
| window=window, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp | |
| ) | |
| loads["windows"] += window_load | |
| # Calculate door loads | |
| for door in doors: | |
| door_load = self.calculate_door_heating_load( | |
| door=door, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp | |
| ) | |
| loads["doors"] += door_load | |
| # Calculate infiltration loads | |
| if infiltration: | |
| flow_rate = infiltration.get("flow_rate", 0.0) | |
| infiltration_loads = self.calculate_infiltration_heating_load( | |
| flow_rate=flow_rate, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp, | |
| outdoor_rh=outdoor_rh, | |
| indoor_rh=indoor_rh | |
| ) | |
| loads["infiltration_sensible"] = infiltration_loads["sensible"] | |
| loads["infiltration_latent"] = infiltration_loads["latent"] | |
| # Calculate ventilation loads | |
| if ventilation: | |
| flow_rate = ventilation.get("flow_rate", 0.0) | |
| ventilation_loads = self.calculate_ventilation_heating_load( | |
| flow_rate=flow_rate, | |
| outdoor_temp=outdoor_temp, | |
| indoor_temp=indoor_temp, | |
| outdoor_rh=outdoor_rh, | |
| indoor_rh=indoor_rh | |
| ) | |
| loads["ventilation_sensible"] = ventilation_loads["sensible"] | |
| loads["ventilation_latent"] = ventilation_loads["latent"] | |
| # Calculate internal gains offset | |
| people_load = people.get("number", 0) * people.get("sensible_gain", 70) | |
| lights_load = lights.get("power", 0) * lights.get("use_factor", 1.0) | |
| equipment_load = equipment.get("power", 0) * equipment.get("use_factor", 1.0) | |
| loads["internal_gains_offset"] = self.calculate_internal_gains_offset( | |
| people_load=people_load, | |
| lights_load=lights_load, | |
| equipment_load=equipment_load, | |
| usage_factor=internal_loads.get("usage_factor", 0.7) | |
| ) | |
| # Calculate subtotal | |
| loads["subtotal"] = ( | |
| loads["walls"] + loads["roofs"] + loads["floors"] + | |
| loads["windows"] + loads["doors"] + | |
| loads["infiltration_sensible"] + loads["infiltration_latent"] + | |
| loads["ventilation_sensible"] + loads["ventilation_latent"] - | |
| loads["internal_gains_offset"] | |
| ) | |
| # Apply safety factor | |
| loads["total"] = loads["subtotal"] * safety_factor | |
| return loads | |
| def calculate_heating_load_summary(self, design_loads: Dict[str, float]) -> Dict[str, float]: | |
| """ | |
| Calculate heating load summary. | |
| Args: | |
| design_loads: Dictionary with design heating loads | |
| Returns: | |
| Dictionary with heating load summary | |
| """ | |
| # Calculate envelope loads | |
| envelope_loads = ( | |
| design_loads["walls"] + design_loads["roofs"] + design_loads["floors"] + | |
| design_loads["windows"] + design_loads["doors"] | |
| ) | |
| # Calculate ventilation and infiltration loads | |
| ventilation_loads = design_loads["ventilation_sensible"] + design_loads["ventilation_latent"] | |
| infiltration_loads = design_loads["infiltration_sensible"] + design_loads["infiltration_latent"] | |
| # Create summary | |
| summary = { | |
| "envelope_loads": envelope_loads, | |
| "ventilation_loads": ventilation_loads, | |
| "infiltration_loads": infiltration_loads, | |
| "internal_gains_offset": design_loads["internal_gains_offset"], | |
| "subtotal": design_loads["subtotal"], | |
| "safety_factor": design_loads["safety_factor"], | |
| "total": design_loads["total"] | |
| } | |
| return summary | |
| def calculate_monthly_heating_loads(self, design_loads: Dict[str, float], | |
| monthly_temps: Dict[str, float], | |
| design_temp: float, indoor_temp: float) -> Dict[str, float]: | |
| """ | |
| Calculate monthly heating loads based on design load and monthly temperatures. | |
| Args: | |
| design_loads: Dictionary with design heating loads | |
| monthly_temps: Dictionary with monthly average temperatures | |
| design_temp: Design outdoor temperature in °C | |
| indoor_temp: Indoor temperature in °C | |
| Returns: | |
| Dictionary with monthly heating loads | |
| """ | |
| # Calculate design temperature difference | |
| design_delta_t = indoor_temp - design_temp | |
| # Calculate monthly loads | |
| monthly_loads = {} | |
| for month, temp in monthly_temps.items(): | |
| # Calculate temperature difference for this month | |
| delta_t = indoor_temp - temp | |
| # Skip months where outdoor temperature is higher than indoor | |
| if delta_t <= 0: | |
| monthly_loads[month] = 0 | |
| continue | |
| # Calculate load ratio based on temperature difference | |
| load_ratio = delta_t / design_delta_t | |
| # Calculate monthly load | |
| monthly_loads[month] = design_loads["total"] * load_ratio | |
| return monthly_loads | |
| def calculate_heating_degree_days(self, monthly_temps: Dict[str, float], | |
| base_temp: float = 18.0) -> Dict[str, float]: | |
| """ | |
| Calculate heating degree days for each month. | |
| Args: | |
| monthly_temps: Dictionary with monthly average temperatures | |
| base_temp: Base temperature for degree days in °C (default: 18°C) | |
| Returns: | |
| Dictionary with monthly heating degree days | |
| """ | |
| # Calculate monthly heating degree days | |
| monthly_hdds = {} | |
| for month, temp in monthly_temps.items(): | |
| # Calculate degree days | |
| days_in_month = 30 # Approximate | |
| if month in ["Apr", "Jun", "Sep", "Nov"]: | |
| days_in_month = 30 | |
| elif month == "Feb": | |
| days_in_month = 28 # Ignore leap years for simplicity | |
| else: | |
| days_in_month = 31 | |
| # Calculate daily degree days | |
| daily_hdd = max(0, base_temp - temp) | |
| # Calculate monthly degree days | |
| monthly_hdds[month] = daily_hdd * days_in_month | |
| return monthly_hdds | |
| def calculate_annual_heating_energy(self, monthly_loads: Dict[str, float], | |
| heating_system_efficiency: float = 0.8) -> Dict[str, float]: | |
| """ | |
| Calculate annual heating energy consumption. | |
| Args: | |
| monthly_loads: Dictionary with monthly heating loads in W | |
| heating_system_efficiency: Heating system efficiency (0-1) | |
| Returns: | |
| Dictionary with monthly and annual heating energy in kWh | |
| """ | |
| # Calculate monthly energy consumption | |
| monthly_energy = {} | |
| annual_energy = 0 | |
| for month, load in monthly_loads.items(): | |
| # Calculate hours in month | |
| hours_in_month = 24 * 30 # Approximate | |
| if month in ["Apr", "Jun", "Sep", "Nov"]: | |
| hours_in_month = 24 * 30 | |
| elif month == "Feb": | |
| hours_in_month = 24 * 28 # Ignore leap years for simplicity | |
| else: | |
| hours_in_month = 24 * 31 | |
| # Calculate energy in kWh | |
| energy = load * hours_in_month / 1000 / heating_system_efficiency | |
| # Store monthly energy | |
| monthly_energy[month] = energy | |
| # Add to annual total | |
| annual_energy += energy | |
| # Add annual total to results | |
| monthly_energy["annual"] = annual_energy | |
| return monthly_energy | |
| # Create a singleton instance | |
| heating_load_calculator = HeatingLoadCalculator() | |
| # Example usage | |
| if __name__ == "__main__": | |
| # Create sample building components | |
| from data.building_components import Wall, Roof, Window, Door, Orientation, ComponentType | |
| # Create a sample wall | |
| wall = Wall( | |
| id="wall1", | |
| name="Exterior Wall", | |
| component_type=ComponentType.WALL, | |
| u_value=0.5, | |
| area=20.0, | |
| orientation=Orientation.NORTH, | |
| wall_type="Brick", | |
| wall_group="B" | |
| ) | |
| # Create a sample roof | |
| roof = Roof( | |
| id="roof1", | |
| name="Flat Roof", | |
| component_type=ComponentType.ROOF, | |
| u_value=0.3, | |
| area=50.0, | |
| orientation=Orientation.HORIZONTAL, | |
| roof_type="Concrete", | |
| roof_group="C" | |
| ) | |
| # Create a sample window | |
| window = Window( | |
| id="window1", | |
| name="North Window", | |
| component_type=ComponentType.WINDOW, | |
| u_value=2.8, | |
| area=5.0, | |
| orientation=Orientation.NORTH, | |
| shgc=0.7, | |
| vt=0.8, | |
| window_type="Double Glazed", | |
| glazing_layers=2, | |
| gas_fill="Air", | |
| low_e_coating=False | |
| ) | |
| # Define building components | |
| building_components = { | |
| "walls": [wall], | |
| "roofs": [roof], | |
| "windows": [window], | |
| "doors": [], | |
| "floors": [] | |
| } | |
| # Define conditions | |
| outdoor_conditions = { | |
| "design_temperature": -10.0, | |
| "design_relative_humidity": 80.0, | |
| "ground_temperature": 10.0 | |
| } | |
| indoor_conditions = { | |
| "temperature": 21.0, | |
| "relative_humidity": 40.0 | |
| } | |
| # Define internal loads | |
| internal_loads = { | |
| "people": { | |
| "number": 3, | |
| "sensible_gain": 70 | |
| }, | |
| "lights": { | |
| "power": 500.0, | |
| "use_factor": 0.9 | |
| }, | |
| "equipment": { | |
| "power": 1000.0, | |
| "use_factor": 0.7 | |
| }, | |
| "infiltration": { | |
| "flow_rate": 0.05 | |
| }, | |
| "ventilation": { | |
| "flow_rate": 0.1 | |
| }, | |
| "usage_factor": 0.7 | |
| } | |
| # Calculate design heating load | |
| design_loads = heating_load_calculator.calculate_design_heating_load( | |
| building_components=building_components, | |
| outdoor_conditions=outdoor_conditions, | |
| indoor_conditions=indoor_conditions, | |
| internal_loads=internal_loads | |
| ) | |
| # Calculate heating load summary | |
| summary = heating_load_calculator.calculate_heating_load_summary(design_loads) | |
| # Define monthly temperatures | |
| monthly_temps = { | |
| "Jan": -5.0, | |
| "Feb": -3.0, | |
| "Mar": 2.0, | |
| "Apr": 8.0, | |
| "May": 14.0, | |
| "Jun": 18.0, | |
| "Jul": 21.0, | |
| "Aug": 20.0, | |
| "Sep": 16.0, | |
| "Oct": 10.0, | |
| "Nov": 4.0, | |
| "Dec": -2.0 | |
| } | |
| # Calculate monthly heating loads | |
| monthly_loads = heating_load_calculator.calculate_monthly_heating_loads( | |
| design_loads=design_loads, | |
| monthly_temps=monthly_temps, | |
| design_temp=outdoor_conditions["design_temperature"], | |
| indoor_temp=indoor_conditions["temperature"] | |
| ) | |
| # Calculate heating degree days | |
| hdds = heating_load_calculator.calculate_heating_degree_days(monthly_temps) | |
| # Calculate annual heating energy | |
| energy = heating_load_calculator.calculate_annual_heating_energy(monthly_loads) | |
| # Print results | |
| print("Heating Load Summary:") | |
| print(f"Envelope Loads: {summary['envelope_loads']:.2f} W") | |
| print(f"Ventilation Loads: {summary['ventilation_loads']:.2f} W") | |
| print(f"Infiltration Loads: {summary['infiltration_loads']:.2f} W") | |
| print(f"Internal Gains Offset: {summary['internal_gains_offset']:.2f} W") | |
| print(f"Subtotal: {summary['subtotal']:.2f} W") | |
| print(f"Safety Factor: {summary['safety_factor']:.2f}") | |
| print(f"Total: {summary['total']:.2f} W") | |
| print("\nMonthly Heating Loads:") | |
| for month, load in monthly_loads.items(): | |
| print(f"{month}: {load:.2f} W") | |
| print("\nHeating Degree Days:") | |
| for month, hdd in hdds.items(): | |
| print(f"{month}: {hdd:.2f} HDD") | |
| print("\nAnnual Heating Energy:") | |
| print(f"Total: {energy['annual']:.2f} kWh") | |