Spaces:
Running
Running
""" | |
Heating load calculation module for HVAC Load Calculator. | |
Implements ASHRAE steady-state methods with simplified thermal lag for compatibility. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
""" | |
from typing import Dict, List, Any, Optional, Tuple | |
import math | |
import numpy as np | |
import logging | |
from enum import Enum | |
from dataclasses import dataclass | |
# Configure logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Import utility modules | |
from utils.psychrometrics import Psychrometrics | |
from utils.heat_transfer import HeatTransferCalculations | |
# Import data modules | |
from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType | |
class HeatingLoadCalculator: | |
"""Class for heating load calculations based on ASHRAE steady-state methods.""" | |
def __init__(self, debug_mode: bool = False): | |
""" | |
Initialize heating load calculator with psychrometric and heat transfer calculations. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
Args: | |
debug_mode: Enable debug logging if True | |
""" | |
self.psychrometrics = Psychrometrics() | |
self.heat_transfer = HeatTransferCalculations() | |
self.safety_factor = 1.15 # 15% safety factor for design loads | |
self.debug_mode = debug_mode | |
if debug_mode: | |
logger.setLevel(logging.DEBUG) | |
def validate_inputs(self, components: Dict[str, List[Any]], outdoor_temp: float, indoor_temp: float) -> None: | |
""" | |
Validate input parameters for heating load calculations. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
Args: | |
components: Dictionary of building components | |
outdoor_temp: Outdoor design temperature in °C | |
indoor_temp: Indoor design temperature in °C | |
Raises: | |
ValueError: If inputs are invalid | |
""" | |
if not components: | |
raise ValueError("Building components dictionary cannot be empty") | |
for component_type, comp_list in components.items(): | |
if not isinstance(comp_list, list): | |
raise ValueError(f"Components for {component_type} must be a list") | |
for comp in comp_list: | |
if not hasattr(comp, 'area') or comp.area <= 0: | |
raise ValueError(f"Invalid area for {component_type}: {comp.name}") | |
if not hasattr(comp, 'u_value') or comp.u_value <= 0: | |
raise ValueError(f"Invalid U-value for {component_type}: {comp.name}") | |
if not -50 <= outdoor_temp <= 60 or not -50 <= indoor_temp <= 60: | |
raise ValueError("Temperatures must be between -50°C and 60°C") | |
if indoor_temp - outdoor_temp < 1: | |
raise ValueError("Indoor temperature must be at least 1°C above outdoor temperature for heating") | |
def calculate_wall_heating_load(self, wall: Wall, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate heating load for a wall, with simplified thermal lag. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1. | |
Args: | |
wall: Wall component | |
outdoor_temp: Outdoor temperature in °C | |
indoor_temp: Indoor temperature in °C | |
Returns: | |
Heating load in W | |
""" | |
delta_t = indoor_temp - outdoor_temp | |
if delta_t <= 1: | |
return 0.0 # Skip calculation for small temperature differences | |
# Use default lag factor (no thermal mass adjustment) | |
lag_factor = 1.0 | |
adjusted_delta_t = delta_t * lag_factor | |
load = self.heat_transfer.conduction_heat_transfer(wall.u_value, wall.area, adjusted_delta_t) | |
return max(0, load) | |
def calculate_roof_heating_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate heating load for a roof, with simplified thermal lag. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1. | |
Args: | |
roof: Roof component | |
outdoor_temp: Outdoor temperature in °C | |
indoor_temp: Indoor temperature in °C | |
Returns: | |
Heating load in W | |
""" | |
delta_t = indoor_temp - outdoor_temp | |
if delta_t <= 1: | |
return 0.0 | |
lag_factor = 1.0 | |
adjusted_delta_t = delta_t * lag_factor | |
load = self.heat_transfer.conduction_heat_transfer(roof.u_value, roof.area, adjusted_delta_t) | |
return max(0, load) | |
def calculate_floor_heating_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate heating load for a floor, using dynamic F-factor for ground contact. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.3. | |
Args: | |
floor: Floor component | |
ground_temp: Ground temperature in °C | |
indoor_temp: Indoor temperature in °C | |
Returns: | |
Heating load in W | |
""" | |
delta_t = indoor_temp - ground_temp | |
if delta_t <= 1: | |
return 0.0 | |
if floor.is_ground_contact: | |
# Dynamic F-factor based on insulation | |
f_factor = 0.3 if floor.insulated else 0.73 # W/m·K | |
load = f_factor * floor.perimeter_length * delta_t | |
else: | |
load = self.heat_transfer.conduction_heat_transfer(floor.u_value, floor.area, delta_t) | |
if self.debug_mode: | |
logger.debug(f"Floor {floor.name} load: {load:.2f} W, Delta T: {delta_t:.2f}°C") | |
return max(0, load) | |
def calculate_window_heating_load(self, window: Window, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate heating load for a window. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1. | |
Args: | |
window: Window component | |
outdoor_temp: Outdoor temperature in °C | |
indoor_temp: Indoor temperature in °C | |
Returns: | |
Heating load in W | |
""" | |
delta_t = indoor_temp - outdoor_temp | |
if delta_t <= 1: | |
return 0.0 | |
load = self.heat_transfer.conduction_heat_transfer(window.u_value, window.area, delta_t) | |
return max(0, load) | |
def calculate_door_heating_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float: | |
""" | |
Calculate heating load for a door. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1. | |
Args: | |
door: Door component | |
outdoor_temp: Outdoor temperature in °C | |
indoor_temp: Indoor temperature in °C | |
Returns: | |
Heating load in W | |
""" | |
delta_t = indoor_temp - outdoor_temp | |
if delta_t <= 1: | |
return 0.0 | |
load = self.heat_transfer.conduction_heat_transfer(door.u_value, door.area, delta_t) | |
return max(0, load) | |
def calculate_infiltration_heating_load(self, indoor_conditions: Dict[str, float], | |
outdoor_conditions: Dict[str, float], | |
infiltration: Dict[str, float], | |
building_height: float, | |
p_atm: float = 101325) -> Tuple[float, float]: | |
""" | |
Calculate sensible and latent heating loads due to infiltration. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.5-18.6. | |
Args: | |
indoor_conditions: Indoor conditions (temperature, relative_humidity) | |
outdoor_conditions: Outdoor conditions (design_temperature, design_relative_humidity, wind_speed) | |
infiltration: Infiltration parameters (flow_rate, crack_length, height) | |
building_height: Building height in m | |
p_atm: Atmospheric pressure in Pa (default: 101325 Pa) | |
Returns: | |
Tuple of sensible and latent loads in W | |
""" | |
delta_t = indoor_conditions['temperature'] - outdoor_conditions['design_temperature'] | |
if delta_t <= 1: | |
return 0.0, 0.0 | |
# Calculate pressure differences | |
wind_pd = self.heat_transfer.wind_pressure_difference(outdoor_conditions['wind_speed']) | |
stack_pd = self.heat_transfer.stack_pressure_difference( | |
building_height, | |
indoor_conditions['temperature'] + 273.15, | |
outdoor_conditions['design_temperature'] + 273.15 | |
) | |
total_pd = self.heat_transfer.combined_pressure_difference(wind_pd, stack_pd) | |
# Calculate infiltration flow rate | |
crack_length = infiltration.get('crack_length', 20.0) | |
flow_rate = self.heat_transfer.crack_method_infiltration(crack_length, 0.0002, total_pd) | |
# Calculate humidity ratio difference | |
w_indoor = self.psychrometrics.humidity_ratio( | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
w_outdoor = self.psychrometrics.humidity_ratio( | |
outdoor_conditions['design_temperature'], | |
outdoor_conditions['design_relative_humidity'], | |
p_atm | |
) | |
delta_w = max(0, w_indoor - w_outdoor) | |
# Calculate sensible and latent loads using indoor conditions for air properties | |
sensible_load = self.heat_transfer.infiltration_heat_transfer( | |
flow_rate, delta_t, | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
latent_load = self.heat_transfer.infiltration_latent_heat_transfer( | |
flow_rate, delta_w, | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
if self.debug_mode: | |
logger.debug(f"Infiltration flow rate: {flow_rate:.6f} m³/s, Sensible load: {sensible_load:.2f} W, Latent load: {latent_load:.2f} W") | |
return max(0, sensible_load), max(0, latent_load) | |
def calculate_ventilation_heating_load(self, ventilation: Dict[str, float], | |
indoor_conditions: Dict[str, float], | |
outdoor_conditions: Dict[str, float], | |
p_atm: float = 101325) -> Tuple[float, float]: | |
""" | |
Calculate sensible and latent heating loads due to ventilation. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equations 18.5-18.6. | |
Args: | |
ventilation: Ventilation parameters (flow_rate) | |
indoor_conditions: Indoor conditions (temperature, relative_humidity) | |
outdoor_conditions: Outdoor conditions (design_temperature, design_relative_humidity) | |
p_atm: Atmospheric pressure in Pa (default: 101325 Pa) | |
Returns: | |
Tuple of sensible and latent loads in W | |
""" | |
delta_t = indoor_conditions['temperature'] - outdoor_conditions['design_temperature'] | |
if delta_t <= 1: | |
return 0.0, 0.0 | |
flow_rate = ventilation['flow_rate'] | |
w_indoor = self.psychrometrics.humidity_ratio( | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
w_outdoor = self.psychrometrics.humidity_ratio( | |
outdoor_conditions['design_temperature'], | |
outdoor_conditions['design_relative_humidity'], | |
p_atm | |
) | |
delta_w = max(0, w_indoor - w_outdoor) | |
# Calculate sensible and latent loads using indoor conditions for air properties | |
sensible_load = self.heat_transfer.infiltration_heat_transfer( | |
flow_rate, delta_t, | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
latent_load = self.heat_transfer.infiltration_latent_heat_transfer( | |
flow_rate, delta_w, | |
indoor_conditions['temperature'], | |
indoor_conditions['relative_humidity'], | |
p_atm | |
) | |
if self.debug_mode: | |
logger.debug(f"Ventilation flow rate: {flow_rate:.6f} m³/s, Sensible load: {sensible_load:.2f} W, Latent load: {latent_load:.2f} W") | |
return max(0, sensible_load), max(0, latent_load) | |
def calculate_internal_gains(self, internal_loads: Dict[str, Any]) -> float: | |
""" | |
Calculate internal heat gains from people, lighting, and equipment. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.4. | |
Args: | |
internal_loads: Internal loads (people, lights, equipment) | |
Returns: | |
Total internal gains in W | |
""" | |
total_gains = 0.0 | |
# People gains | |
people = internal_loads.get('people', {}) | |
if people.get('number', 0) > 0: | |
sensible_gain = people.get('sensible_gain', 70.0) | |
total_gains += people['number'] * sensible_gain | |
# Lighting gains | |
lights = internal_loads.get('lights', {}) | |
if lights.get('power', 0) > 0: | |
total_gains += lights['power'] * lights.get('use_factor', 0.8) | |
# Equipment gains | |
equipment = internal_loads.get('equipment', {}) | |
if equipment.get('power', 0) > 0: | |
total_gains += equipment['power'] * equipment.get('use_factor', 0.7) | |
return max(0, total_gains) | |
def calculate_design_heating_load(self, building_components: Dict[str, List[Any]], | |
outdoor_conditions: Dict[str, float], | |
indoor_conditions: Dict[str, float], | |
internal_loads: Dict[str, Any], | |
p_atm: float = 101325) -> Dict[str, float]: | |
""" | |
Calculate design heating loads for all components. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
Args: | |
building_components: Dictionary of building components | |
outdoor_conditions: Outdoor conditions (design_temperature, design_relative_humidity, ground_temperature, wind_speed) | |
indoor_conditions: Indoor conditions (temperature, relative_humidity) | |
internal_loads: Internal loads (people, lights, equipment, infiltration, ventilation) | |
p_atm: Atmospheric pressure in Pa (default: 101325 Pa) | |
Returns: | |
Dictionary of design loads in W | |
""" | |
try: | |
self.validate_inputs(building_components, outdoor_conditions['design_temperature'], indoor_conditions['temperature']) | |
except ValueError as e: | |
raise ValueError(f"Input validation failed: {str(e)}") | |
loads = { | |
'walls': 0.0, | |
'roofs': 0.0, | |
'floors': 0.0, | |
'windows': 0.0, | |
'doors': 0.0, | |
'infiltration_sensible': 0.0, | |
'infiltration_latent': 0.0, | |
'ventilation_sensible': 0.0, | |
'ventilation_latent': 0.0, | |
'internal_gains': 0.0 | |
} | |
# Calculate envelope loads | |
for wall in building_components.get('walls', []): | |
loads['walls'] += self.calculate_wall_heating_load(wall, outdoor_conditions['design_temperature'], indoor_conditions['temperature']) | |
for roof in building_components.get('roofs', []): | |
loads['roofs'] += self.calculate_roof_heating_load(roof, outdoor_conditions['design_temperature'], indoor_conditions['temperature']) | |
for floor in building_components.get('floors', []): | |
loads['floors'] += self.calculate_floor_heating_load(floor, outdoor_conditions['ground_temperature'], indoor_conditions['temperature']) | |
for window in building_components.get('windows', []): | |
loads['windows'] += self.calculate_window_heating_load(window, outdoor_conditions['design_temperature'], indoor_conditions['temperature']) | |
for door in building_components.get('doors', []): | |
loads['doors'] += self.calculate_door_heating_load(door, outdoor_conditions['design_temperature'], indoor_conditions['temperature']) | |
# Calculate infiltration and ventilation loads | |
building_height = internal_loads.get('infiltration', {}).get('height', 3.0) | |
infiltration_sensible, infiltration_latent = self.calculate_infiltration_heating_load( | |
indoor_conditions, outdoor_conditions, internal_loads.get('infiltration', {}), building_height, p_atm | |
) | |
loads['infiltration_sensible'] = infiltration_sensible | |
loads['infiltration_latent'] = infiltration_latent | |
ventilation_sensible, ventilation_latent = self.calculate_ventilation_heating_load( | |
internal_loads.get('ventilation', {}), indoor_conditions, outdoor_conditions, p_atm | |
) | |
loads['ventilation_sensible'] = ventilation_sensible | |
loads['ventilation_latent'] = ventilation_latent | |
# Calculate internal gains (negative for heating) | |
loads['internal_gains'] = -self.calculate_internal_gains(internal_loads) | |
return loads | |
def calculate_heating_load_summary(self, design_loads: Dict[str, float]) -> Dict[str, float]: | |
""" | |
Summarize heating loads with safety factor. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
Args: | |
design_loads: Dictionary of design loads in W | |
Returns: | |
Summary dictionary with total, subtotal, and safety factor | |
""" | |
subtotal = sum( | |
load for key, load in design_loads.items() | |
if key not in ['internal_gains'] and load > 0 | |
) | |
internal_gains = design_loads.get('internal_gains', 0) | |
total = max(0, subtotal + internal_gains) * self.safety_factor | |
return { | |
'subtotal': subtotal, | |
'internal_gains': internal_gains, | |
'total': total, | |
'safety_factor': self.safety_factor | |
} | |
def calculate_heating_degree_days(self, base_temp: float, monthly_temps: Dict[str, float]) -> float: | |
""" | |
Calculate heating degree days for a year. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Section 14.3. | |
Args: | |
base_temp: Base temperature for HDD calculation in °C | |
monthly_temps: Dictionary of monthly average temperatures | |
Returns: | |
Total heating degree days | |
""" | |
hdd = 0.0 | |
days_per_month = { | |
'Jan': 31, 'Feb': 28, 'Mar': 31, 'Apr': 30, 'May': 31, 'Jun': 30, | |
'Jul': 31, 'Aug': 31, 'Sep': 30, 'Oct': 31, 'Nov': 30, 'Dec': 31 | |
} | |
for month, temp in monthly_temps.items(): | |
if temp < base_temp: | |
hdd += (base_temp - temp) * days_per_month[month] | |
return hdd | |
def calculate_annual_heating_energy(self, design_loads: Dict[str, float], | |
monthly_temps: Dict[str, float], | |
indoor_temp: float, | |
operating_hours: str) -> float: | |
""" | |
Calculate annual heating energy consumption. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 14, Section 14.3. | |
Args: | |
design_loads: Dictionary of design loads in W | |
monthly_temps: Dictionary of monthly average temperatures | |
indoor_temp: Indoor design temperature in °C | |
operating_hours: Operating hours (e.g., '8:00-18:00') | |
Returns: | |
Annual heating energy in kWh | |
""" | |
base_temp = indoor_temp | |
hdd = self.calculate_heating_degree_days(base_temp, monthly_temps) | |
# Parse operating hours | |
start_hour, end_hour = map(lambda x: int(x.split(':')[0]), operating_hours.split('-')) | |
daily_hours = end_hour - start_hour | |
# Calculate design condition degree days | |
design_temp = min(monthly_temps.values()) | |
design_delta_t = indoor_temp - design_temp | |
if design_delta_t <= 1: | |
return 0.0 | |
total_load = self.calculate_heating_load_summary(design_loads)['total'] | |
# Scale load by HDD and operating hours | |
annual_energy = (total_load / design_delta_t) * hdd * (daily_hours / 24) / 1000 # kWh | |
return max(0, annual_energy) | |
def calculate_monthly_heating_loads(self, building_components: Dict[str, List[Any]], | |
outdoor_conditions: Dict[str, float], | |
indoor_conditions: Dict[str, float], | |
internal_loads: Dict[str, Any], | |
monthly_temps: Dict[str, float], | |
p_atm: float = 101325) -> Dict[str, float]: | |
""" | |
Calculate monthly heating loads. | |
Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4. | |
Args: | |
building_components: Dictionary of building components | |
outdoor_conditions: Outdoor conditions | |
indoor_conditions: Indoor conditions | |
internal_loads: Internal loads | |
monthly_temps: Dictionary of monthly average temperatures | |
p_atm: Atmospheric pressure in Pa (default: 101325 Pa) | |
Returns: | |
Dictionary of monthly heating loads in kW | |
""" | |
monthly_loads = {} | |
days_per_month = { | |
'Jan': 31, 'Feb': 28, 'Mar': 31, 'Apr': 30, 'May': 31, 'Jun': 30, | |
'Jul': 31, 'Aug': 31, 'Sep': 30, 'Oct': 31, 'Nov': 30, 'Dec': 31 | |
} | |
for month, temp in monthly_temps.items(): | |
modified_outdoor = outdoor_conditions.copy() | |
modified_outdoor['design_temperature'] = temp | |
modified_outdoor['ground_temperature'] = temp | |
try: | |
design_loads = self.calculate_design_heating_load( | |
building_components, modified_outdoor, indoor_conditions, internal_loads, p_atm | |
) | |
summary = self.calculate_heating_load_summary(design_loads) | |
monthly_loads[month] = summary['total'] / 1000 # kW | |
except ValueError: | |
monthly_loads[month] = 0.0 # Skip invalid months | |
return monthly_loads | |
# Example usage | |
if __name__ == "__main__": | |
calculator = HeatingLoadCalculator(debug_mode=True) | |
# Example building components | |
components = { | |
'walls': [Wall(id="w1", name="North Wall", area=20.0, u_value=0.5, orientation=Orientation.NORTH)], | |
'roofs': [Roof(id="r1", name="Main Roof", area=100.0, u_value=0.3, orientation=Orientation.HORIZONTAL)], | |
'floors': [Floor(id="f1", name="Ground Floor", area=100.0, u_value=0.4, perimeter_length=40.0, | |
is_ground_contact=True, insulated=True, ground_temperature_c=10.0)], | |
'windows': [Window(id="win1", name="South Window", area=10.0, u_value=2.8, orientation=Orientation.SOUTH, | |
shgc=0.7, shading_coefficient=0.8)], | |
'doors': [Door(id="d1", name="Main Door", area=2.0, u_value=2.0, orientation=Orientation.NORTH)] | |
} | |
outdoor_conditions = { | |
'design_temperature': -5.0, | |
'design_relative_humidity': 80.0, | |
'ground_temperature': 10.0, | |
'wind_speed': 4.0 | |
} | |
indoor_conditions = { | |
'temperature': 21.0, | |
'relative_humidity': 40.0 | |
} | |
internal_loads = { | |
'people': {'number': 10, 'sensible_gain': 70.0, 'operating_hours': '8:00-18:00'}, | |
'lights': {'power': 1000.0, 'use_factor': 0.8, 'hours_operation': '8h'}, | |
'equipment': {'power': 500.0, 'use_factor': 0.7, 'hours_operation': '8h'}, | |
'infiltration': {'flow_rate': 0.05, 'height': 3.0, 'crack_length': 20.0}, | |
'ventilation': {'flow_rate': 0.1}, | |
'operating_hours': '8:00-18:00' | |
} | |
monthly_temps = { | |
'Jan': -5.0, 'Feb': -3.0, 'Mar': 2.0, 'Apr': 8.0, 'May': 14.0, 'Jun': 19.0, | |
'Jul': 22.0, 'Aug': 21.0, 'Sep': 16.0, 'Oct': 10.0, 'Nov': 4.0, 'Dec': -2.0 | |
} | |
# Calculate design loads | |
design_loads = calculator.calculate_design_heating_load(components, outdoor_conditions, indoor_conditions, internal_loads) | |
summary = calculator.calculate_heating_load_summary(design_loads) | |
# Log results | |
logger.info(f"Total Heating Load: {summary['total']:.2f} W") | |
logger.info(f"Wall Load: {design_loads['walls']:.2f} W") | |
logger.info(f"Roof Load: {design_loads['roofs']:.2f} W") | |
logger.info(f"Floor Load: {design_loads['floors']:.2f} W") | |
logger.info(f"Window Load: {design_loads['windows']:.2f} W") | |
logger.info(f"Door Load: {design_loads['doors']:.2f} W") | |
logger.info(f"Infiltration Sensible Load: {design_loads['infiltration_sensible']:.2f} W") | |
logger.info(f"Infiltration Latent Load: {design_loads['infiltration_latent']:.2f} W") | |
logger.info(f"Ventilation Sensible Load: {design_loads['ventilation_sensible']:.2f} W") | |
logger.info(f"Ventilation Latent Load: {design_loads['ventilation_latent']:.2f} W") | |
logger.info(f"Internal Gains: {design_loads['internal_gains']:.2f} W") | |
# Calculate annual energy | |
annual_energy = calculator.calculate_annual_heating_energy( | |
design_loads, monthly_temps, indoor_conditions['temperature'], internal_loads['operating_hours'] | |
) | |
logger.info(f"Annual Heating Energy: {annual_energy:.2f} kWh") | |
# Calculate monthly loads | |
monthly_loads = calculator.calculate_monthly_heating_loads( | |
components, outdoor_conditions, indoor_conditions, internal_loads, monthly_temps | |
) | |
logger.info("Monthly Heating Loads (kW):") | |
for month, load in monthly_loads.items(): | |
logger.info(f"{month}: {load:.2f} kW") |