Source code for twin4build.systems.coil.coil_torch_system

# Standard library imports
import datetime
from typing import Optional

# Third party imports
import numpy as np
import torch
import torch.nn as nn

# Local application imports
import twin4build.core as core
import twin4build.utils.types as tps
from twin4build.utils.constants import Constants


[docs] class CoilTorchSystem(core.System, nn.Module): r""" A coil system model implemented with PyTorch for gradient-based optimization. This model represents a heating/cooling coil that transfers heat between air and water, calculating the required heating or cooling power based on air flow rate and temperature differences. Mathematical Formulation ----------------------- The heating/cooling power is calculated using the following equations: For heating mode (when :math:`T_{in} < T_{out,set}`): .. math:: P_{heat} = \dot{m}_{air} \cdot c_{p,air} \cdot (T_{out,set} - T_{in}) .. math:: P_{cool} = 0 For cooling mode (when :math:`T_{in} \geq T_{out,set}`): .. math:: P_{heat} = 0 .. math:: P_{cool} = \dot{m}_{air} \cdot c_{p,air} \cdot (T_{in} - T_{out,set}) where: - :math:`P_{heat}` is the heating power [W] - :math:`P_{cool}` is the cooling power [W] - :math:`\dot{m}_{air}` is the air flow rate [kg/s] - :math:`c_{p,air}` is the specific heat capacity of air [J/(kg·K)] - :math:`T_{in}` is the inlet air temperature [°C] - :math:`T_{out,set}` is the outlet air temperature setpoint [°C] Notes ----- Model Assumptions: - Perfect heat transfer (outlet temperature equals setpoint) - Constant specific heat capacity of air - No heat losses to the environment - No water-side calculations (focus on air-side performance) Implementation Details: - If air flow rate is below threshold (1e-5 kg/s), both heating and cooling powers are set to zero - The model uses PyTorch tensors for gradient-based optimization - All calculations are performed in SI units - The specific heat capacity is stored as a non-trainable PyTorch parameter """ def __init__(self, **kwargs): """ Initialize the coil system model. """ super().__init__(**kwargs) nn.Module.__init__(self) # Store specific heat capacity as tps.Parameter with private variable self._specificHeatCapacityAir = tps.Parameter( torch.tensor(Constants.specificHeatCapacity["air"], dtype=torch.float64), requires_grad=False, ) # Define inputs and outputs as private variables self._input = { "inletAirTemperature": tps.Scalar(), "outletAirTemperatureSetpoint": tps.Scalar(), "airFlowRate": tps.Scalar(), } self._output = { "heatingPower": tps.Scalar(), "coolingPower": tps.Scalar(), "outletAirTemperature": tps.Scalar(), } # Define parameters for calibration self.parameter = {} self._config = {"parameters": list(self.parameter.keys())} self.INITIALIZED = False @property def config(self): """Get the configuration of the coil system.""" return self._config @property def input(self) -> dict: """ Get the input ports of the coil system. Returns: dict: Dictionary containing input ports: - "inletAirTemperature": Inlet air temperature [°C] - "outletAirTemperatureSetpoint": Outlet air temperature setpoint [°C] - "airFlowRate": Air flow rate [kg/s] """ return self._input @property def output(self) -> dict: """ Get the output ports of the coil system. Returns: dict: Dictionary containing output ports: - "heatingPower": Heating power [W] - "coolingPower": Cooling power [W] - "outletAirTemperature": Outlet air temperature [°C] """ return self._output @property def specificHeatCapacityAir(self) -> tps.Parameter: """ Get the specific heat capacity of air. Returns: tps.Parameter: Specific heat capacity of air [J/(kg·K)]. """ return self._specificHeatCapacityAir @specificHeatCapacityAir.setter def specificHeatCapacityAir(self, value: tps.Parameter) -> None: """ Set the specific heat capacity of air. Args: value (tps.Parameter): Specific heat capacity of air [J/(kg·K)]. """ self._specificHeatCapacityAir = value
[docs] def initialize( self, start_time: datetime.datetime, end_time: datetime.datetime, step_size: int, simulator: core.Simulator, ) -> None: """Initialize the coil system.""" # Initialize I/O for input in self.input.values(): input.initialize( start_time=start_time, end_time=end_time, step_size=step_size, simulator=simulator, ) for output in self.output.values(): output.initialize( start_time=start_time, end_time=end_time, step_size=step_size, simulator=simulator, ) self.INITIALIZED = True
[docs] def do_step( self, secondTime: float, dateTime: datetime.datetime, step_size: int, stepIndex: int, ) -> None: """ Perform one step of the coil system simulation. The model calculates heating/cooling power based on: - Air flow rate - Inlet air temperature - Outlet air temperature setpoint If the air flow rate is zero, the output power is set to 0. """ # Get inputs (assumed to be tensors) inlet_air_temp = self.input["inletAirTemperature"].get() outlet_air_temp_setpoint = self.input["outletAirTemperatureSetpoint"].get() air_flow_rate = self.input["airFlowRate"].get() # Calculate heating/cooling power based on temperature difference tol = torch.tensor(1e-5, dtype=torch.float64) if air_flow_rate > tol: if inlet_air_temp < outlet_air_temp_setpoint: # Heating mode heating_power = ( air_flow_rate * self.specificHeatCapacityAir.get() * (outlet_air_temp_setpoint - inlet_air_temp) ) cooling_power = torch.tensor(0.0, dtype=torch.float64) else: # Cooling mode heating_power = torch.tensor(0.0, dtype=torch.float64) cooling_power = ( air_flow_rate * self.specificHeatCapacityAir.get() * (inlet_air_temp - outlet_air_temp_setpoint) ) else: # No flow heating_power = torch.tensor(0.0, dtype=torch.float64) cooling_power = torch.tensor(0.0, dtype=torch.float64) # Update outputs self.output["heatingPower"].set(heating_power, stepIndex) self.output["coolingPower"].set(cooling_power, stepIndex) self.output["outletAirTemperature"].set(outlet_air_temp_setpoint, stepIndex)