energy-simulation

SKILL.md

Energy Simulation

Overview

This skill implements building energy simulation and analysis. Calculate thermal loads, evaluate building envelope performance, and optimize systems for energy efficiency and code compliance.

Capabilities:

  • Heating/cooling load calculations
  • Envelope thermal analysis
  • HVAC system sizing
  • Energy code compliance
  • Renewable energy integration
  • Life cycle cost analysis

Quick Start

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from enum import Enum
import numpy as np

class WallType(Enum):
    CONCRETE = "concrete"
    BRICK = "brick"
    WOOD_FRAME = "wood_frame"
    STEEL_FRAME = "steel_frame"
    CURTAIN_WALL = "curtain_wall"

@dataclass
class BuildingEnvelope:
    wall_area_m2: float
    wall_u_value: float  # W/m²K
    roof_area_m2: float
    roof_u_value: float
    floor_area_m2: float
    floor_u_value: float
    window_area_m2: float
    window_u_value: float
    window_shgc: float  # Solar Heat Gain Coefficient

@dataclass
class ClimateData:
    location: str
    heating_degree_days: float  # HDD base 18°C
    cooling_degree_days: float  # CDD base 18°C
    design_temp_winter: float
    design_temp_summer: float

def calculate_heat_loss(envelope: BuildingEnvelope, climate: ClimateData,
                       indoor_temp: float = 21) -> float:
    """Calculate design heat loss (W)"""
    delta_t = indoor_temp - climate.design_temp_winter

    # Transmission losses
    wall_loss = envelope.wall_area_m2 * envelope.wall_u_value * delta_t
    roof_loss = envelope.roof_area_m2 * envelope.roof_u_value * delta_t
    floor_loss = envelope.floor_area_m2 * envelope.floor_u_value * delta_t * 0.5  # Ground factor
    window_loss = envelope.window_area_m2 * envelope.window_u_value * delta_t

    total_loss = wall_loss + roof_loss + floor_loss + window_loss

    # Add infiltration estimate (simplified)
    volume = envelope.floor_area_m2 * 3  # Assume 3m height
    infiltration = volume * 0.5 * 0.33 * delta_t  # 0.5 ACH, 0.33 Wh/m³K

    return total_loss + infiltration

# Example
envelope = BuildingEnvelope(
    wall_area_m2=500, wall_u_value=0.35,
    roof_area_m2=200, roof_u_value=0.25,
    floor_area_m2=200, floor_u_value=0.30,
    window_area_m2=100, window_u_value=1.4, window_shgc=0.4
)

climate = ClimateData(
    location="Moscow",
    heating_degree_days=5000,
    cooling_degree_days=300,
    design_temp_winter=-25,
    design_temp_summer=30
)

heat_loss = calculate_heat_loss(envelope, climate)
print(f"Design heat loss: {heat_loss/1000:.1f} kW")

Comprehensive Energy Analysis

Building Thermal Model

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from enum import Enum
import numpy as np
from datetime import datetime

@dataclass
class MaterialLayer:
    name: str
    thickness_m: float
    conductivity: float  # W/mK
    density: float  # kg/m³
    specific_heat: float  # J/kgK

    @property
    def resistance(self) -> float:
        """Thermal resistance R (m²K/W)"""
        return self.thickness_m / self.conductivity if self.conductivity > 0 else 0

@dataclass
class WallAssembly:
    name: str
    layers: List[MaterialLayer]
    inside_surface_resistance: float = 0.13  # m²K/W
    outside_surface_resistance: float = 0.04

    @property
    def total_resistance(self) -> float:
        return (self.inside_surface_resistance +
                sum(layer.resistance for layer in self.layers) +
                self.outside_surface_resistance)

    @property
    def u_value(self) -> float:
        return 1 / self.total_resistance if self.total_resistance > 0 else 0

@dataclass
class Window:
    name: str
    u_value: float
    shgc: float
    visible_transmittance: float = 0.6
    frame_fraction: float = 0.2

@dataclass
class Zone:
    zone_id: str
    name: str
    floor_area_m2: float
    volume_m3: float
    occupancy: int
    lighting_power_density: float  # W/m²
    equipment_power_density: float  # W/m²
    ventilation_rate: float  # L/s per person
    setpoint_heating: float = 21
    setpoint_cooling: float = 24

@dataclass
class BuildingGeometry:
    zones: List[Zone]
    walls: List[Dict]  # {zone, orientation, area, assembly}
    windows: List[Dict]  # {zone, orientation, area, window_type}
    roofs: List[Dict]  # {zone, area, assembly}
    floors: List[Dict]  # {zone, area, assembly, is_ground}

class ThermalCalculator:
    """Calculate building thermal loads"""

    # Standard climate data (simplified)
    CLIMATE_DB = {
        'moscow': {
            'hdd': 5000, 'cdd': 300,
            'design_winter': -25, 'design_summer': 30,
            'latitude': 55.75
        },
        'new_york': {
            'hdd': 2500, 'cdd': 800,
            'design_winter': -12, 'design_summer': 33,
            'latitude': 40.71
        },
        'dubai': {
            'hdd': 50, 'cdd': 3000,
            'design_winter': 15, 'design_summer': 45,
            'latitude': 25.20
        }
    }

    def __init__(self, building: BuildingGeometry, location: str):
        self.building = building
        self.location = location.lower()
        self.climate = self.CLIMATE_DB.get(self.location, self.CLIMATE_DB['moscow'])

    def calculate_design_heating_load(self) -> Dict:
        """Calculate design heating load for each zone"""
        delta_t = 21 - self.climate['design_winter']
        results = {}

        for zone in self.building.zones:
            # Transmission losses
            wall_loss = 0
            window_loss = 0
            roof_loss = 0
            floor_loss = 0

            for wall in self.building.walls:
                if wall['zone'] == zone.zone_id:
                    u_value = wall['assembly'].u_value
                    wall_loss += wall['area'] * u_value * delta_t

            for window in self.building.windows:
                if window['zone'] == zone.zone_id:
                    window_loss += window['area'] * window['window_type'].u_value * delta_t

            for roof in self.building.roofs:
                if roof['zone'] == zone.zone_id:
                    u_value = roof['assembly'].u_value
                    roof_loss += roof['area'] * u_value * delta_t

            for floor in self.building.floors:
                if floor['zone'] == zone.zone_id:
                    u_value = floor['assembly'].u_value
                    factor = 0.5 if floor.get('is_ground', False) else 1.0
                    floor_loss += floor['area'] * u_value * delta_t * factor

            # Infiltration
            infiltration_loss = zone.volume_m3 * 0.5 * 0.33 * delta_t

            # Ventilation (if mechanical)
            ventilation_loss = zone.occupancy * zone.ventilation_rate * 1.2 * delta_t

            total = wall_loss + window_loss + roof_loss + floor_loss + infiltration_loss + ventilation_loss

            results[zone.zone_id] = {
                'zone_name': zone.name,
                'wall_loss_w': wall_loss,
                'window_loss_w': window_loss,
                'roof_loss_w': roof_loss,
                'floor_loss_w': floor_loss,
                'infiltration_w': infiltration_loss,
                'ventilation_w': ventilation_loss,
                'total_w': total,
                'total_kw': total / 1000,
                'w_per_m2': total / zone.floor_area_m2
            }

        return results

    def calculate_design_cooling_load(self) -> Dict:
        """Calculate design cooling load for each zone"""
        delta_t = self.climate['design_summer'] - 24
        results = {}

        for zone in self.building.zones:
            # Transmission gains
            transmission_gain = 0
            for wall in self.building.walls:
                if wall['zone'] == zone.zone_id:
                    u_value = wall['assembly'].u_value
                    # Apply sol-air temperature correction for orientation
                    sol_air_delta = delta_t + self._get_sol_air_correction(wall['orientation'])
                    transmission_gain += wall['area'] * u_value * sol_air_delta

            # Window solar gains
            solar_gain = 0
            for window in self.building.windows:
                if window['zone'] == zone.zone_id:
                    shgc = window['window_type'].shgc
                    irradiance = self._get_solar_irradiance(window['orientation'])
                    solar_gain += window['area'] * shgc * irradiance

            # Window conduction
            window_conduction = 0
            for window in self.building.windows:
                if window['zone'] == zone.zone_id:
                    window_conduction += window['area'] * window['window_type'].u_value * delta_t

            # Internal gains
            lighting_gain = zone.floor_area_m2 * zone.lighting_power_density
            equipment_gain = zone.floor_area_m2 * zone.equipment_power_density
            people_gain = zone.occupancy * 75  # W per person sensible

            # Ventilation
            ventilation_gain = zone.occupancy * zone.ventilation_rate * 1.2 * delta_t

            total = (transmission_gain + solar_gain + window_conduction +
                    lighting_gain + equipment_gain + people_gain + ventilation_gain)

            results[zone.zone_id] = {
                'zone_name': zone.name,
                'transmission_gain_w': transmission_gain,
                'solar_gain_w': solar_gain,
                'window_conduction_w': window_conduction,
                'lighting_gain_w': lighting_gain,
                'equipment_gain_w': equipment_gain,
                'people_gain_w': people_gain,
                'ventilation_gain_w': ventilation_gain,
                'total_w': total,
                'total_kw': total / 1000,
                'w_per_m2': total / zone.floor_area_m2
            }

        return results

    def _get_sol_air_correction(self, orientation: str) -> float:
        """Get sol-air temperature correction by orientation"""
        corrections = {
            'north': 0, 'south': 8, 'east': 4, 'west': 6,
            'northeast': 2, 'northwest': 3, 'southeast': 6, 'southwest': 7
        }
        return corrections.get(orientation.lower(), 3)

    def _get_solar_irradiance(self, orientation: str) -> float:
        """Get design solar irradiance W/m² by orientation"""
        # Simplified peak values
        irradiance = {
            'north': 150, 'south': 450, 'east': 350, 'west': 350,
            'northeast': 200, 'northwest': 200, 'southeast': 400, 'southwest': 400
        }
        return irradiance.get(orientation.lower(), 300)

HVAC System Sizing

class HVACSizer:
    """Size HVAC systems based on loads"""

    def __init__(self, calculator: ThermalCalculator):
        self.calculator = calculator

    def size_heating_system(self, safety_factor: float = 1.15) -> Dict:
        """Size heating system"""
        heating_loads = self.calculator.calculate_design_heating_load()

        total_load = sum(z['total_kw'] for z in heating_loads.values())
        sized_capacity = total_load * safety_factor

        # Recommend system type
        if sized_capacity < 15:
            system_type = "Split system heat pump"
        elif sized_capacity < 50:
            system_type = "Packaged rooftop unit"
        elif sized_capacity < 200:
            system_type = "Central boiler with radiators"
        else:
            system_type = "Central plant with multiple boilers"

        return {
            'total_load_kw': total_load,
            'sized_capacity_kw': sized_capacity,
            'safety_factor': safety_factor,
            'recommended_system': system_type,
            'zone_loads': heating_loads
        }

    def size_cooling_system(self, safety_factor: float = 1.1) -> Dict:
        """Size cooling system"""
        cooling_loads = self.calculator.calculate_design_cooling_load()

        total_load = sum(z['total_kw'] for z in cooling_loads.values())
        sized_capacity = total_load * safety_factor

        # Convert to tons
        capacity_tons = sized_capacity / 3.517

        # Recommend system type
        if capacity_tons < 5:
            system_type = "Split system DX"
        elif capacity_tons < 20:
            system_type = "VRF system"
        elif capacity_tons < 100:
            system_type = "Chilled water with AHUs"
        else:
            system_type = "Central chiller plant"

        return {
            'total_load_kw': total_load,
            'total_load_tons': capacity_tons,
            'sized_capacity_kw': sized_capacity,
            'sized_capacity_tons': capacity_tons * safety_factor,
            'safety_factor': safety_factor,
            'recommended_system': system_type,
            'zone_loads': cooling_loads
        }

    def estimate_annual_energy(self) -> Dict:
        """Estimate annual energy consumption"""
        climate = self.calculator.climate

        heating_loads = self.calculator.calculate_design_heating_load()
        cooling_loads = self.calculator.calculate_design_cooling_load()

        total_heating_load = sum(z['total_kw'] for z in heating_loads.values())
        total_cooling_load = sum(z['total_kw'] for z in cooling_loads.values())

        # Simplified degree-day calculation
        # Heating energy = load * HDD * 24 / delta_t_design
        delta_t_heating = 21 - climate['design_winter']
        heating_kwh = total_heating_load * climate['hdd'] * 24 / delta_t_heating / 1000

        delta_t_cooling = climate['design_summer'] - 24
        cooling_kwh = total_cooling_load * climate['cdd'] * 24 / delta_t_cooling / 1000 if delta_t_cooling > 0 else 0

        # Apply efficiency factors
        heating_fuel_efficiency = 0.9  # Gas boiler
        cooling_cop = 3.5  # Chiller COP

        heating_consumption = heating_kwh / heating_fuel_efficiency
        cooling_consumption = cooling_kwh / cooling_cop

        return {
            'heating_load_kw': total_heating_load,
            'cooling_load_kw': total_cooling_load,
            'annual_heating_kwh': heating_kwh,
            'annual_cooling_kwh': cooling_kwh,
            'heating_fuel_kwh': heating_consumption,
            'cooling_electricity_kwh': cooling_consumption,
            'total_hvac_energy_kwh': heating_consumption + cooling_consumption
        }

Energy Code Compliance

@dataclass
class EnergyCodeRequirements:
    code_name: str
    climate_zone: str
    wall_u_max: float
    roof_u_max: float
    floor_u_max: float
    window_u_max: float
    window_shgc_max: float
    lighting_lpd_max: float  # W/m²

class ComplianceChecker:
    """Check energy code compliance"""

    CODES = {
        'ASHRAE_90.1_2019_4A': EnergyCodeRequirements(
            code_name="ASHRAE 90.1-2019",
            climate_zone="4A",
            wall_u_max=0.45,
            roof_u_max=0.27,
            floor_u_max=0.32,
            window_u_max=2.0,
            window_shgc_max=0.40,
            lighting_lpd_max=9.0
        ),
        'IECC_2021_5A': EnergyCodeRequirements(
            code_name="IECC 2021",
            climate_zone="5A",
            wall_u_max=0.35,
            roof_u_max=0.20,
            floor_u_max=0.30,
            window_u_max=1.7,
            window_shgc_max=0.40,
            lighting_lpd_max=8.5
        )
    }

    def __init__(self, code_key: str):
        self.requirements = self.CODES.get(code_key)
        if not self.requirements:
            raise ValueError(f"Unknown code: {code_key}")

    def check_envelope(self, building: BuildingGeometry) -> Dict:
        """Check envelope compliance"""
        results = {
            'code': self.requirements.code_name,
            'climate_zone': self.requirements.climate_zone,
            'compliant': True,
            'issues': []
        }

        # Check walls
        for wall in building.walls:
            u_value = wall['assembly'].u_value
            if u_value > self.requirements.wall_u_max:
                results['compliant'] = False
                results['issues'].append({
                    'element': f"Wall {wall['zone']} {wall['orientation']}",
                    'actual': u_value,
                    'required': self.requirements.wall_u_max,
                    'issue': 'Exceeds maximum U-value'
                })

        # Check windows
        for window in building.windows:
            u_value = window['window_type'].u_value
            shgc = window['window_type'].shgc

            if u_value > self.requirements.window_u_max:
                results['compliant'] = False
                results['issues'].append({
                    'element': f"Window {window['zone']} {window['orientation']}",
                    'actual': u_value,
                    'required': self.requirements.window_u_max,
                    'issue': 'Exceeds maximum U-value'
                })

            if shgc > self.requirements.window_shgc_max:
                results['compliant'] = False
                results['issues'].append({
                    'element': f"Window {window['zone']} {window['orientation']}",
                    'actual': shgc,
                    'required': self.requirements.window_shgc_max,
                    'issue': 'Exceeds maximum SHGC'
                })

        # Check roof
        for roof in building.roofs:
            u_value = roof['assembly'].u_value
            if u_value > self.requirements.roof_u_max:
                results['compliant'] = False
                results['issues'].append({
                    'element': f"Roof {roof['zone']}",
                    'actual': u_value,
                    'required': self.requirements.roof_u_max,
                    'issue': 'Exceeds maximum U-value'
                })

        return results

    def check_lighting(self, zones: List[Zone]) -> Dict:
        """Check lighting power density compliance"""
        results = {
            'compliant': True,
            'issues': []
        }

        for zone in zones:
            if zone.lighting_power_density > self.requirements.lighting_lpd_max:
                results['compliant'] = False
                results['issues'].append({
                    'zone': zone.name,
                    'actual_lpd': zone.lighting_power_density,
                    'required_max': self.requirements.lighting_lpd_max
                })

        return results

Quick Reference

Component Good U-Value Code Maximum
Wall < 0.25 W/m²K 0.35-0.45
Roof < 0.15 W/m²K 0.20-0.27
Floor < 0.20 W/m²K 0.25-0.32
Window < 1.2 W/m²K 1.7-2.0

Resources

  • ASHRAE 90.1: Energy standard for buildings
  • IECC: International Energy Conservation Code
  • EnergyPlus: DOE building simulation
  • DDC Website: https://datadrivenconstruction.io

Next Steps

  • See co2-estimation for carbon analysis
  • See cost-prediction for energy cost modeling
  • See bim-validation-pipeline for model integration
Weekly Installs
4
GitHub Stars
55
First Seen
11 days ago
Installed on
opencode4
gemini-cli4
antigravity4
github-copilot4
codex4
kimi-cli4