Source code for archetypal.template.conditioning

"""archetypal ZoneConditioning."""

import collections
import logging as lg
import math
import sqlite3
from enum import Enum

import numpy as np
from sigfig import round
from sklearn.preprocessing import Binarizer
from validator_collection import checkers, validators

from archetypal.reportdata import ReportData
from archetypal.template.schedule import UmiSchedule
from archetypal.template.umi_base import UmiBase
from archetypal.utils import float_round, log


class UmiBaseEnum(Enum):
    """An Enum base class."""

    def __lt__(self, other):
        """Assert if self is lower than other."""
        return self._value_ < other._value_

    def __gt__(self, other):
        """Assert if self is greater than other."""
        return self._value_ > other._value_


class FuelType(Enum):
    """Fuel types taken from EnergyPlus 9.2 .idd file for OtherEquipment."""

    NONE = 0
    Electricity = 1
    NaturalGas = 2
    PropaneGas = 3
    FuelOil1 = 4
    FuelOil2 = 5
    Diesel = 6
    Gasoline = 7
    Coal = 8
    OtherFuel1 = 9
    OtherFuel2 = 10
    Steam = 11
    DistrictHeating = 12
    DistrictCooling = 13


class HeatRecoveryTypes(UmiBaseEnum):
    """Heat recovery types."""

    NONE = 0
    Enthalpy = 1
    Sensible = 2


class EconomizerTypes(UmiBaseEnum):
    """Economizer types."""

    NoEconomizer = 0
    DifferentialDryBulb = 1
    DifferentialEnthalpy = 2
    DifferentialEnthalphy = 2  # fix for the UMI bug


class IdealSystemLimit(UmiBaseEnum):
    """Ideal System Limit.

    LimitFlowRate means that the heating supply air flow rate will be
    limited to the value specified in the next input field. LimitCapacity means that
    the sensible heating capacity will be limited to the value specified in the
    Maximum Sensible Heating Capacity field. LimitFlowRateAndCapacity means that both
    flow rate and capacity will be limited. NoLimit (the default) means that there will
    not be any limit on the heating supply air flow rate or capacity and the subsequent
    two fields will be ignored.
    """

    NoLimit = 0
    LimitFlowRate = 1
    LimitCapacity = 2
    LimitFlowRateAndCapacity = 3


[docs]class ZoneConditioning(UmiBase): """HVAC settings for the zone. .. image:: ../images/template/zoninfo-conditioning.png """ __slots__ = ( "_cooling_setpoint", "_heating_setpoint", "_max_cool_flow", "_max_heat_flow", "_max_heating_capacity", "_max_cooling_capacity", "_min_fresh_air_per_person", "_min_fresh_air_per_area", "_is_heating_on", "_heating_schedule", "_heating_limit_type", "_heating_fuel_type", "_heating_coeff_of_perf", "_is_cooling_on", "_cooling_schedule", "_cooling_limit_type", "_cooling_fuel_type", "_cooling_coeff_of_perf", "_is_mech_vent_on", "_economizer_type", "_mech_vent_schedule", "_heat_recovery_type", "_heat_recovery_efficiency_latent", "_heat_recovery_efficiency_sensible", "_area", ) def __init__( self, Name, IsHeatingOn=False, # HeatingSetpoint=20, # HeatingSchedule=None, # HeatingLimitType=IdealSystemLimit.NoLimit, # HeatingFuelType=FuelType.NaturalGas, # MaxHeatingCapacity=100, # MaxHeatFlow=100, # HeatingCoeffOfPerf=1, # IsCoolingOn=False, # CoolingSetpoint=26, # CoolingSchedule=None, # CoolingLimitType=IdealSystemLimit.NoLimit, # CoolingFuelType=FuelType.Electricity, # MaxCoolingCapacity=100, # MaxCoolFlow=100, # CoolingCoeffOfPerf=1, # IsMechVentOn=False, # EconomizerType=EconomizerTypes.NoEconomizer, # MechVentSchedule=None, # MinFreshAirPerArea=0, # MinFreshAirPerPerson=0, # HeatRecoveryType=HeatRecoveryTypes.NONE, # HeatRecoveryEfficiencyLatent=0.65, HeatRecoveryEfficiencySensible=0.7, area=1, **kwargs, ): """Initialize a new :class:`ZoneConditioning` object. Args: Name (str): Name of the object. Must be Unique. IsHeatingOn (bool): Whether or not heating is available. HeatingSetpoint (float): The temperature below which zone heating is turned on. Here, we take the mean value over the ye HeatingSchedule (UmiSchedule): The availability schedule for space heating in this zone. If the value is 0, heating is not available, and heating is not supplied to the zone. HeatingLimitType (int): The input must be either LimitFlowRate = 1, LimitCapacity = 2, LimitFlowRateAndCapacity = 3 or NoLimit = 0. MaxHeatingCapacity (float): The maximum allowed sensible heating capacity in Watts if Heating Limit is set to LimitCapacity or LimitFlowRateAndCapacity MaxHeatFlow (float): The maximum heating supply air flow rate in cubic meters per second if heating limit is set to LimitFlowRate or LimitFlowRateAndCapacity HeatingCoeffOfPerf (float): Efficiency of heating system. The COP is of each zone is equal, and refer to the COP of the entire building. IsCoolingOn (bool): Whether or not cooling is available. CoolingSetpoint (float): The temperature above which the zone heating is turned on. Here, we take the mean value over the ye CoolingSchedule (UmiSchedule): The availability schedule for space cooling in this zone. If the value is 0, cooling is not available, and cooling is not supplied to the zone. CoolingLimitType (str): The input must be either LimitFlowRate = 1, LimitCapacity = 2, LimitFlowRateAndCapacity = 3 or NoLimit = 0. MaxCoolingCapacity (float): The maximum allowed total (sensible plus latent) cooling capacity in Watts per square meter. MaxCoolFlow (float): The maximum cooling supply air flow rate in cubic meters per second if Cooling Limit is set to LimitFlowRate or LimitFlowRateAndCapacity CoolingCoeffOfPerf (float): Performance factor of the cooling system. This value is used to calculate the total cooling energy use by dividing the cooling load by the COP. The COP of the zone shared with all zones and refers to the COP of the entire building. IsMechVentOn (bool): If True, an outdoor air quantity for use by the model is calculated. EconomizerType (int): Specifies if there is an outdoor air economizer. The choices are: NoEconomizer = 0, DifferentialDryBulb = 1, or DifferentialEnthalpy = 2. For the moment, the EconomizerType is applied for the entire building (every zone with the same EconomizerType). Moreover, since UMI does not support all Economizer Types, some assumptions are made: - If 'NoEconomizer' in EnergyPlus, EconomizerType='NoEconomizer' - If 'DifferentialEnthalpy' in EnergyPlus,EconomizerType = 'DifferentialEnthalpy' - If 'DifferentialDryBulb' in EnergyPlus, EconomizerType = 'DifferentialDryBulb' - If 'FixedDryBulb' in EnergyPlus, EconomizerType = 'DifferentialDryBulb' - If 'FixedEnthalpy' in EnergyPlus, EconomizerType = 'DifferentialEnthalpy' - If 'ElectronicEnthalpy' in EnergyPlus, EconomizerType = 'DifferentialEnthalpy' - If 'FixedDewPointAndDryBulb' in EnergyPlus, EconomizerType = 'DifferentialDryBulb' - If 'DifferentialDryBulbAndEnthalpy' in EnergyPlus, EconomizerType = 'DifferentialEnthalpy' MechVentSchedule (UmiSchedule): The availability schedule of the mechanical ventilation. If the value is 0, the mechanical ventilation is not available and air flow is not requested. MinFreshAirPerArea (flaot): The design outdoor air volume flow rate per square meter of floor area (units are m3/s-m2). This input is used if Outdoor Air Method is Flow/Area, Sum or Maximum MinFreshAirPerPerson (float): The design outdoor air volume flow rate per person for this zone in cubic meters per second per person. The default is 0.00944 (20 cfm per person). HeatRecoveryType (int): Select from None = 0, Sensible = 1, or Enthalpy = 2. None means that there is no heat recovery. Sensible means that there is sensible heat recovery whenever the zone exhaust air temperature is more favorable than the outdoor air temperature. Enthalpy means that there is latent and sensible heat recovery whenever the zone exhaust air enthalpy is more favorable than the outdoor air enthalpy. The default is None HeatRecoveryEfficiencyLatent (float): The latent heat recovery effectiveness, where effectiveness is defined as the change in supply humidity ratio divided by the difference in entering supply and relief air humidity ratios. The default is 0.65. - If the HeatExchanger is an AirToAir FlatPlate, HeatRecoveryEfficiencyLatent = HeatRecoveryEfficiencySensible - 0.05 - If the HeatExchanger is an AirToAir SensibleAndLatent, we suppose that HeatRecoveryEfficiencyLatent = Latent Effectiveness at 100% Heating Air Flow - If the HeatExchanger is a Desiccant BalancedFlow, we use the default value for the efficiency (=0.65). HeatRecoveryEfficiencySensible (float): The sensible heat recovery effectiveness, where effectiveness is defined as the change in supply temperature divided by the difference in entering supply and relief air temperatures. The default is 0.70. - If the HeatExchanger is an AirToAir FlatPlate, HeatRecoveryEfficiencySensible = (Supply Air Outlet T°C - Supply Air Inlet T°C)/(Secondary Air Inlet T°C - Supply Air Inlet T°C) - If the HeatExchanger is an AirToAir SensibleAndLatent, we suppose that HeatRecoveryEfficiencySensible = Sensible Effectiveness at 100% Heating Air Flow - If the HeatExchanger is a Desiccant BalancedFlow, we use the default value for the efficiency (=0.70) **kwargs: Other arguments passed to the base class :class:`archetypal.template.UmiBase` """ super(ZoneConditioning, self).__init__(Name, **kwargs) self.MechVentSchedule = MechVentSchedule self.HeatingSchedule = HeatingSchedule self.CoolingSchedule = CoolingSchedule self.CoolingCoeffOfPerf = CoolingCoeffOfPerf self.CoolingLimitType = IdealSystemLimit(CoolingLimitType) self.CoolingFuelType = FuelType(CoolingFuelType) self._cooling_setpoint = CoolingSetpoint # setter without check self.EconomizerType = EconomizerTypes(EconomizerType) self.HeatRecoveryEfficiencyLatent = HeatRecoveryEfficiencyLatent self.HeatRecoveryEfficiencySensible = HeatRecoveryEfficiencySensible self.HeatRecoveryType = HeatRecoveryTypes(HeatRecoveryType) self.HeatingCoeffOfPerf = HeatingCoeffOfPerf self.HeatingLimitType = IdealSystemLimit(HeatingLimitType) self.HeatingFuelType = FuelType(HeatingFuelType) self.HeatingSetpoint = HeatingSetpoint self.IsCoolingOn = IsCoolingOn self.IsHeatingOn = IsHeatingOn self.IsMechVentOn = IsMechVentOn self.MaxCoolFlow = MaxCoolFlow self.MaxCoolingCapacity = MaxCoolingCapacity self.MaxHeatFlow = MaxHeatFlow self.MaxHeatingCapacity = MaxHeatingCapacity self.MinFreshAirPerArea = MinFreshAirPerArea self.MinFreshAirPerPerson = MinFreshAirPerPerson self.area = area @property def area(self): """Get or set the area of the zone associated to this object [m²].""" return self._area @area.setter def area(self, value): self._area = value @property def CoolingSetpoint(self): """Get or set the cooling setpoint [degC].""" return self._cooling_setpoint @CoolingSetpoint.setter def CoolingSetpoint(self, value): assert ( self._heating_setpoint < value ), "Heating setpoint must be lower than the cooling setpoint." self._cooling_setpoint = validators.float(value, minimum=-100, maximum=50) @property def HeatingSetpoint(self): """Get or set the heating setpoint [degC].""" return self._heating_setpoint @HeatingSetpoint.setter def HeatingSetpoint(self, value): assert ( value < self._cooling_setpoint ), "Heating setpoint must be lower than the cooling setpoint." self._heating_setpoint = validators.float(value) @property def MaxCoolFlow(self): """Get or set the maximum cooling flowrate [m³/s/m²].""" return self._max_cool_flow @MaxCoolFlow.setter def MaxCoolFlow(self, value): self._max_cool_flow = validators.float(value, minimum=0) @property def MaxHeatFlow(self): """Get or set the maximum heating flowrate [m³/s/m²].""" return self._max_heat_flow @MaxHeatFlow.setter def MaxHeatFlow(self, value): self._max_heat_flow = validators.float(value, minimum=0) @property def MaxHeatingCapacity(self): """Get or set the maximum heating capacity [W/m²].""" return float(self._max_heating_capacity) @MaxHeatingCapacity.setter def MaxHeatingCapacity(self, value): self._max_heating_capacity = validators.float(value, minimum=0) @property def MaxCoolingCapacity(self): """Get or set the maximum cooling capacity [W/m²].""" return self._max_cooling_capacity @MaxCoolingCapacity.setter def MaxCoolingCapacity(self, value): self._max_cooling_capacity = validators.float(value, minimum=0) @property def MinFreshAirPerArea(self): """Get or set the minimum fresh air per area [m³/s/m²].""" return self._min_fresh_air_per_area @MinFreshAirPerArea.setter def MinFreshAirPerArea(self, value): self._min_fresh_air_per_area = validators.float(value, minimum=0) @property def MinFreshAirPerPerson(self): """Get or set the minimum fresh air per person [m³/s/p].""" return self._min_fresh_air_per_person @MinFreshAirPerPerson.setter def MinFreshAirPerPerson(self, value): self._min_fresh_air_per_person = validators.float(value, minimum=0) @property def IsHeatingOn(self): """Get or set the availability of heating [bool].""" return self._is_heating_on @IsHeatingOn.setter def IsHeatingOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsHeatingOn must " f"be a boolean, not a {type(value)}" ) self._is_heating_on = value @property def HeatingSchedule(self): """Get or set the heating availability schedule.""" return self._heating_schedule @HeatingSchedule.setter def HeatingSchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input error with value {value}. HeatingSchedule must " f"be an UmiSchedule, not a {type(value)}" ) self._heating_schedule = value @property def HeatingLimitType(self): """Get or set the heating limit type [enum].""" return self._heating_limit_type @HeatingLimitType.setter def HeatingLimitType(self, value): if checkers.is_string(value): assert IdealSystemLimit[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in IdealSystemLimit)}" ) self._heating_limit_type = IdealSystemLimit[value] elif checkers.is_numeric(value): assert IdealSystemLimit[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in IdealSystemLimit)}" ) self._heating_limit_type = IdealSystemLimit(value) elif isinstance(value, IdealSystemLimit): self._heating_limit_type = value @property def HeatingFuelType(self): """Get or set the heating fuel type [enum].""" return self._heating_fuel_type @HeatingFuelType.setter def HeatingFuelType(self, value): if checkers.is_string(value): assert FuelType[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in FuelType)}" ) self._heating_fuel_type = FuelType[value] elif checkers.is_numeric(value): assert FuelType[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in FuelType)}" ) self._heating_fuel_type = FuelType(value) elif isinstance(value, FuelType): self._heating_fuel_type = value @property def HeatingCoeffOfPerf(self): """Get or set the heating COP [-].""" return self._heating_coeff_of_perf @HeatingCoeffOfPerf.setter def HeatingCoeffOfPerf(self, value): self._heating_coeff_of_perf = validators.float(value, minimum=0) @property def IsCoolingOn(self): """Get or set the availability of cooling [bool].""" return self._is_cooling_on @IsCoolingOn.setter def IsCoolingOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsCoolingOn must " f"be a boolean, not a {type(value)}" ) self._is_cooling_on = value @property def CoolingSchedule(self): """Get or set the cooling availability schedule.""" return self._cooling_schedule @CoolingSchedule.setter def CoolingSchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input error with value {value}. CoolingSchedule must " f"be an UmiSchedule, not a {type(value)}" ) self._cooling_schedule = value @property def CoolingLimitType(self): """Get or set the cooling limit type [enum].""" return self._cooling_limit_type @CoolingLimitType.setter def CoolingLimitType(self, value): if checkers.is_string(value): assert IdealSystemLimit[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in IdealSystemLimit)}" ) self._cooling_limit_type = IdealSystemLimit[value] elif checkers.is_numeric(value): assert IdealSystemLimit[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in IdealSystemLimit)}" ) self._cooling_limit_type = IdealSystemLimit(value) elif isinstance(value, IdealSystemLimit): self._cooling_limit_type = value @property def CoolingFuelType(self): """Get or set the cooling fuel type [enum].""" return self._cooling_fuel_type @CoolingFuelType.setter def CoolingFuelType(self, value): if checkers.is_string(value): assert FuelType[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in FuelType)}" ) self._cooling_fuel_type = FuelType[value] elif checkers.is_numeric(value): assert FuelType[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in FuelType)}" ) self._cooling_fuel_type = FuelType(value) elif isinstance(value, FuelType): self._cooling_fuel_type = value @property def CoolingCoeffOfPerf(self): """Get or set the cooling COP [-].""" return self._cooling_coeff_of_perf @CoolingCoeffOfPerf.setter def CoolingCoeffOfPerf(self, value): self._cooling_coeff_of_perf = validators.float(value, minimum=0) @property def IsMechVentOn(self): """Get or set the availability of mechanical ventilation [bool].""" return self._is_mech_vent_on @IsMechVentOn.setter def IsMechVentOn(self, value): assert isinstance(value, bool), ( f"Input error with value {value}. IsMechVentOn must " f"be a boolean, not a {type(value)}" ) self._is_mech_vent_on = value @property def EconomizerType(self): """Get or set the economizer type [enum].""" return self._economizer_type @EconomizerType.setter def EconomizerType(self, value): if checkers.is_string(value): assert EconomizerTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in EconomizerTypes)}" ) self._economizer_type = EconomizerTypes[value] elif checkers.is_numeric(value): assert EconomizerTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in EconomizerTypes)}" ) self._economizer_type = EconomizerTypes(value) elif isinstance(value, EconomizerTypes): self._economizer_type = value @property def MechVentSchedule(self): """Get or set the outdoor air requirements over time.""" return self._mech_vent_schedule @MechVentSchedule.setter def MechVentSchedule(self, value): if value is not None: assert isinstance(value, UmiSchedule), ( f"Input error with value {value}. MechVentSchedule must " f"be an UmiSchedule, not a {type(value)}" ) self._mech_vent_schedule = value @property def HeatRecoveryType(self): """Get or set the heat recovery type.""" return self._heat_recovery_type @HeatRecoveryType.setter def HeatRecoveryType(self, value): if checkers.is_string(value): assert HeatRecoveryTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in HeatRecoveryTypes)}" ) self._heat_recovery_type = HeatRecoveryTypes[value] elif checkers.is_numeric(value): assert HeatRecoveryTypes[value], ( f"Input value error for '{value}'. " f"Expected one of {tuple(a for a in HeatRecoveryTypes)}" ) self._heat_recovery_type = HeatRecoveryTypes(value) elif isinstance(value, HeatRecoveryTypes): self._heat_recovery_type = value @property def HeatRecoveryEfficiencyLatent(self): """Get or set the latent heat recovery effectiveness [-].""" return self._heat_recovery_efficiency_latent @HeatRecoveryEfficiencyLatent.setter def HeatRecoveryEfficiencyLatent(self, value): self._heat_recovery_efficiency_latent = validators.float( value, minimum=0, maximum=1 ) @property def HeatRecoveryEfficiencySensible(self): """Get or set the sensible heat recovery effectiveness [-].""" return self._heat_recovery_efficiency_sensible @HeatRecoveryEfficiencySensible.setter def HeatRecoveryEfficiencySensible(self, value): self._heat_recovery_efficiency_sensible = validators.float( value, minimum=0, maximum=1 )
[docs] @classmethod def from_dict(cls, data, schedules, **kwargs): """Create a ZoneConditioning from a dictionary. Args: data (dict): The python dictionary. schedules (dict): A dictionary of UmiSchedules with their id as keys. **kwargs: keywords passed to parent constructor. .. code-block:: python { "$id": "165", "CoolingSchedule": { $ref: "1" }, "CoolingCoeffOfPerf": 3.0, "CoolingSetpoint": 24.0, "CoolingLimitType": 0, "CoolingFuelType": 1, "EconomizerType": 0, "HeatingCoeffOfPerf": 0.9, "HeatingLimitType": 0, "HeatingFuelType": 2, "HeatingSchedule": { $ref: "2" }, "HeatingSetpoint": 20.0, "HeatRecoveryEfficiencyLatent": 0.65, "HeatRecoveryEfficiencySensible": 0.7, "HeatRecoveryType": 0, "IsCoolingOn": True, "IsHeatingOn": True, "IsMechVentOn": True, "MaxCoolFlow": 100.0, "MaxCoolingCapacity": 100.0, "MaxHeatFlow": 100.0, "MaxHeatingCapacity": 100.0, "MechVentSchedule": { $ref: "3" }, "MinFreshAirPerArea": 0.0003, "MinFreshAirPerPerson": 0.0025, "Category": "Office Spaces", "Comments": None, "DataSource": "MIT_SDL", "Name": "B_Off_0 Conditioning", } """ _id = data.pop("$id") cooling_schedule = schedules[data.pop("CoolingSchedule")["$ref"]] heating_schedule = schedules[data.pop("HeatingSchedule")["$ref"]] mech_vent_schedule = schedules[data.pop("MechVentSchedule")["$ref"]] return cls( id=_id, CoolingSchedule=cooling_schedule, HeatingSchedule=heating_schedule, MechVentSchedule=mech_vent_schedule, **data, **kwargs, )
[docs] def to_dict(self): """Return ZoneConditioning dictionary representation.""" self.validate() # Validate object before trying to get json format data_dict = collections.OrderedDict() data_dict["$id"] = str(self.id) data_dict["CoolingSchedule"] = self.CoolingSchedule.to_ref() data_dict["CoolingCoeffOfPerf"] = round(self.CoolingCoeffOfPerf, 3) data_dict["CoolingSetpoint"] = ( round(self.CoolingSetpoint, 3) if not math.isnan(self.CoolingSetpoint) else 26 ) data_dict["CoolingLimitType"] = self.CoolingLimitType.value data_dict["CoolingFuelType"] = self.CoolingFuelType.value data_dict["EconomizerType"] = self.EconomizerType.value data_dict["HeatingCoeffOfPerf"] = round(self.HeatingCoeffOfPerf, 3) data_dict["HeatingLimitType"] = self.HeatingLimitType.value data_dict["HeatingFuelType"] = self.HeatingFuelType.value data_dict["HeatingSchedule"] = self.HeatingSchedule.to_ref() data_dict["HeatingSetpoint"] = ( round(self.HeatingSetpoint, 3) if not math.isnan(self.HeatingSetpoint) else 20 ) data_dict["HeatRecoveryEfficiencyLatent"] = round( self.HeatRecoveryEfficiencyLatent, 3 ) data_dict["HeatRecoveryEfficiencySensible"] = round( self.HeatRecoveryEfficiencySensible, 3 ) data_dict["HeatRecoveryType"] = self.HeatRecoveryType.value data_dict["IsCoolingOn"] = self.IsCoolingOn data_dict["IsHeatingOn"] = self.IsHeatingOn data_dict["IsMechVentOn"] = self.IsMechVentOn data_dict["MaxCoolFlow"] = round(self.MaxCoolFlow, 3) data_dict["MaxCoolingCapacity"] = round(self.MaxCoolingCapacity, 3) data_dict["MaxHeatFlow"] = round(self.MaxHeatFlow, 3) data_dict["MaxHeatingCapacity"] = round(self.MaxHeatingCapacity, 3) data_dict["MechVentSchedule"] = self.MechVentSchedule.to_ref() data_dict["MinFreshAirPerArea"] = round(self.MinFreshAirPerArea, 3) data_dict["MinFreshAirPerPerson"] = round(self.MinFreshAirPerPerson, 3) data_dict["Category"] = self.Category data_dict["Comments"] = validators.string(self.Comments, allow_empty=True) data_dict["DataSource"] = self.DataSource data_dict["Name"] = self.Name return data_dict
[docs] @classmethod def from_zone(cls, zone, zone_ep, nolimit=False, **kwargs): """Create a ZoneConditioning object from a zone. Args: zone_ep: zone (archetypal.template.zone.Zone): zone to gets information from. """ # If Zone is not part of Conditioned Area, it should not have a ZoneLoad object. if zone.is_part_of_conditioned_floor_area and zone.is_part_of_total_floor_area: # First create placeholder object. name = zone.Name + "_ZoneConditioning" z_cond = cls(Name=name, zone=zone, Category=zone.DataSource, **kwargs) z_cond._set_thermostat_setpoints(zone, zone_ep) z_cond._set_zone_cops(zone, zone_ep, nolimit=nolimit) z_cond._set_heat_recovery(zone, zone_ep) z_cond._set_mechanical_ventilation(zone, zone_ep) z_cond._set_economizer(zone, zone_ep) return z_cond else: return None
def _set_economizer(self, zone, zone_ep): """Set economizer parameters. Todo: - Here EconomizerType is for the entire building, try to do it for each zone. - Fix typo in DifferentialEnthalpy (extra h) when issue is resolved at Basilisk project: https://github.com/MITSustainableDesignLab/basilisk/issues/32 Args: zone_ep: zone (Zone): The zone object. """ # Economizer controllers_in_idf = zone_ep.theidf.idfobjects["Controller:OutdoorAir".upper()] self.EconomizerType = EconomizerTypes.NoEconomizer # default value for object in controllers_in_idf: if object.Economizer_Control_Type == "NoEconomizer": self.EconomizerType = EconomizerTypes.NoEconomizer elif object.Economizer_Control_Type == "DifferentialEnthalphy": self.EconomizerType = EconomizerTypes.DifferentialEnthalphy elif object.Economizer_Control_Type == "DifferentialDryBulb": self.EconomizerType = EconomizerTypes.DifferentialDryBulb elif object.Economizer_Control_Type == "FixedDryBulb": self.EconomizerType = EconomizerTypes.DifferentialDryBulb elif object.Economizer_Control_Type == "FixedEnthalpy": self.EconomizerType = EconomizerTypes.DifferentialEnthalphy elif object.Economizer_Control_Type == "ElectronicEnthalpy": self.EconomizerType = EconomizerTypes.DifferentialEnthalphy elif object.Economizer_Control_Type == "FixedDewPointAndDryBulb": self.EconomizerType = EconomizerTypes.DifferentialDryBulb elif object.Economizer_Control_Type == "DifferentialDryBulbAndEnthalpy": self.EconomizerType = EconomizerTypes.DifferentialEnthalphy def _set_mechanical_ventilation(self, zone, zone_ep): """Set mechanical ventilation settings. Notes: Mechanical Ventilation in UMI (or Archsim-based models) is applied to an `ZoneHVAC:IdealLoadsAirSystem` through the `Design Specification Outdoor Air Object Name` which in turn is a `DesignSpecification:OutdoorAir` object. It is this last object that performs the calculation for the outdoor air flowrate. Moreover, UMI defaults to the "sum" method, meaning that the Outdoor Air Flow per Person {m3/s-person} and the Outdoor Air Flow per Area { m3/s-m2} are summed to obtain the zone outdoor air flow rate. Moreover, not all models have the `DesignSpecification:OutdoorAir` object which poses a difficulty when trying to resolve the mechanical ventilation parameters. Two general cases exist: 1) models with a `Sizing:Zone` object (and possibly no `DesignSpecification:OutdoorAir`) and 2) models with Args: zone_ep: zone (Zone): The zone object. """ # For models with ZoneSizes try: try: ( self.IsMechVentOn, self.MinFreshAirPerArea, self.MinFreshAirPerPerson, self.MechVentSchedule, ) = self.fresh_air_from_zone_sizes(zone) except (ValueError, StopIteration): ( self.IsMechVentOn, self.MinFreshAirPerArea, self.MinFreshAirPerPerson, self.MechVentSchedule, ) = self.fresh_air_from_ideal_loads(zone, zone_ep) except Exception: # Set elements to None so that .combine works correctly self.IsMechVentOn = False self.MinFreshAirPerPerson = 0 self.MinFreshAirPerArea = 0 self.MechVentSchedule = None
[docs] @staticmethod def get_equipment_list(zone, zone_ep): """Get zone equipment list. Args: zone_ep: """ connections = zone_ep.getreferingobjs( iddgroups=["Zone HVAC Equipment Connections"], fields=["Zone_Name"] ) referenced_object = next(iter(connections)).get_referenced_object( "Zone_Conditioning_Equipment_List_Name" ) # EquipmentList can have 18 objects. Filter out the None objects. return filter( None, [ referenced_object.get_referenced_object(f"Zone_Equipment_{i}_Name") for i in range(1, 19) ], )
[docs] def fresh_air_from_ideal_loads(self, zone, zone_ep): """Resolve fresh air requirements for Ideal Loads Air System. Args: zone_ep: zone: Returns: 4-tuple: (IsMechVentOn, MinFreshAirPerArea, MinFreshAirPerPerson, MechVentSchedule) """ equip_list = self.get_equipment_list(zone, zone_ep) equipment = next( iter( [ eq for eq in equip_list if eq.key.lower() == "ZoneHVAC:IdealLoadsAirSystem".lower() ] ) ) oa_spec = equipment.get_referenced_object( "Design_Specification_Outdoor_Air_Object_Name" ) oa_area = float(oa_spec.Outdoor_Air_Flow_per_Zone_Floor_Area) oa_person = float(oa_spec.Outdoor_Air_Flow_per_Person) mechvent_schedule = self._mechanical_schedule_from_outdoorair_object( oa_spec, zone ) return True, oa_area, oa_person, mechvent_schedule
[docs] def fresh_air_from_zone_sizes(self, zone): """Return the Mechanical Ventilation from the ZoneSizes Table in the sql db. Args: zone (ZoneDefinition): Returns: 4-tuple: (IsMechVentOn, MinFreshAirPerArea, MinFreshAirPerPerson, MechVentSchedule) """ import sqlite3 import pandas as pd # create database connection with sqlite3 with sqlite3.connect(zone.idf.sql_file) as conn: sql_query = f""" select t.ColumnName, t.Value from TabularDataWithStrings t where TableName == 'Zone Sensible Heating' and RowName == '{zone.Name.upper()}'""" oa = ( pd.read_sql_query(sql_query, con=conn, coerce_float=True) .set_index("ColumnName") .squeeze() ) oa = pd.to_numeric(oa, errors="coerce") oa_design = oa["Minimum Outdoor Air Flow Rate"] # m3/s isoa = oa["Calculated Design Air Flow"] > 0 # True if ach > 0 oa_area = oa_design / zone.area if zone.occupants > 0: oa_person = oa_design / zone.occupants else: oa_person = np.NaN designobjs = zone._epbunch.getreferingobjs( iddgroups=["HVAC Design Objects"], fields=["Zone_or_ZoneList_Name"] ) obj = next(iter(eq for eq in designobjs if eq.key.lower() == "sizing:zone")) oa_spec = obj.get_referenced_object( "Design_Specification_Outdoor_Air_Object_Name" ) mechvent_schedule = self._mechanical_schedule_from_outdoorair_object( oa_spec, zone ) return isoa, oa_area, oa_person, mechvent_schedule
def _mechanical_schedule_from_outdoorair_object(self, oa_spec, zone) -> UmiSchedule: """Get mechanical ventilation schedule for zone and OutdoorAir:DesignSpec.""" if oa_spec.Outdoor_Air_Schedule_Name != "": epbunch = zone.idf.schedules_dict[oa_spec.Outdoor_Air_Schedule_Name.upper()] umi_schedule = UmiSchedule.from_epbunch(epbunch) log( f"Mechanical Ventilation Schedule set as {UmiSchedule} for " f"zone {zone.Name}", lg.DEBUG, ) return umi_schedule else: # Schedule is not specified, # Try to get try: values = ( self.idf.variables.OutputVariable[ "Air_System_Outdoor_Air_Minimum_Flow_Fraction" ] .values() # return values .mean(axis=1) # for more than one system, return mean .values # get numpy array ) except KeyError: # if no Air_System_Outdoor_Air_Minimum_Flow_Fraction defined, # then create an always off schedule as a backup. log( f"No Mechanical Ventilation Schedule specified for zone " f"{zone.Name}" ) return UmiSchedule.constant_schedule( value=0, Name="AlwaysOff", allow_duplicates=True ) else: log( f"Mechanical Ventilation Schedule specified for zone " f"{zone.Name} as AirSystemOutdoorAirMinimumFlowFraction" ) return UmiSchedule.from_values( Name="AirSystemOutdoorAirMinimumFlowFraction", Values=values, idf=zone.idf, ) def _set_zone_cops(self, zone, zone_ep, nolimit=False): """Set the zone COPs. Todo: - Make this method zone-independent. - This method takes 75% of the `from_zone` constructor. Args: zone_ep: zone (Zone): """ # COPs (heating and cooling) # Heating heating_meters = ( "Heating__Electricity", "Heating__Gas", "Heating__DistrictHeating", "Heating__Oil", ) total_input_heating_energy = 0 for meter in heating_meters: try: total_input_heating_energy += ( zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() ) except KeyError: pass # pass if meter does not exist for model heating_energy_transfer_meters = ( "HeatingCoils__EnergyTransfer", "Baseboard__EnergyTransfer", ) total_output_heating_energy = 0 for meter in heating_energy_transfer_meters: try: total_output_heating_energy += ( zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() ) except KeyError: pass # pass if meter does not exist for model if total_output_heating_energy == 0: # IdealLoadsAirSystem try: total_output_heating_energy += ( zone_ep.theidf.meters.OutputMeter["Heating__EnergyTransfer"] .values("kWh") .sum() ) except KeyError: pass cooling_meters = ( "Cooling__Electricity", "Cooling__Gas", "Cooling__DistrictCooling", "HeatRejection__Electricity", # includes cooling towers "Refrigeration__Electricity", ) total_input_cooling_energy = 0 for meter in cooling_meters: try: total_input_cooling_energy += ( zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() ) except KeyError: pass # pass if meter does not exist for model cooling_energy_transfer_meters = ( "CoolingCoils__EnergyTransfer", "Refrigeration__EnergyTransfer", ) total_output_cooling_energy = 0 for meter in cooling_energy_transfer_meters: try: total_output_cooling_energy += ( zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() ) except KeyError: pass # pass if meter does not exist for model if total_output_cooling_energy == 0: # IdealLoadsAirSystem try: total_output_cooling_energy += ( zone_ep.theidf.meters.OutputMeter["Cooling__EnergyTransfer"] .values("kWh") .sum() ) except KeyError: pass ratio_cooling = total_output_cooling_energy / ( total_output_cooling_energy + total_output_heating_energy ) ratio_heating = total_output_heating_energy / ( total_output_cooling_energy + total_output_heating_energy ) # estimate fans electricity for cooling and heating try: fans_energy = ( zone_ep.theidf.meters.OutputMeter["Fans__Electricity"] .values("kWh") .sum() ) fans_cooling = fans_energy * ratio_cooling fans_heating = fans_energy * ratio_heating except KeyError: fans_energy = 0 fans_cooling = 0 fans_heating = 0 # estimate pumps electricity for cooling and heating try: pumps_energy = ( zone_ep.theidf.meters.OutputMeter["Pumps__Electricity"] .values("kWh") .sum() ) pumps_cooling = pumps_energy * ratio_cooling pumps_heating = pumps_energy * ratio_heating except KeyError: pumps_energy = 0 pumps_cooling = 0 pumps_heating = 0 # Add fans and pumps to total_inputs total_input_cooling_energy += fans_cooling total_input_heating_energy += fans_heating total_input_cooling_energy += pumps_cooling total_input_heating_energy += pumps_heating # Calculate COPs cooling_cop = total_output_cooling_energy / total_input_cooling_energy heating_cop = total_output_heating_energy / total_input_heating_energy # Capacity limits (heating and cooling) zone_size = zone_ep.theidf.sql()["ZoneSizes"][ zone_ep.theidf.sql()["ZoneSizes"]["ZoneName"] == zone.Name.upper() ] # Heating HeatingLimitType, heating_cap, heating_flow = self._get_design_limits( zone, zone_size, load_name="Heating", nolimit=nolimit ) # Cooling CoolingLimitType, cooling_cap, cooling_flow = self._get_design_limits( zone, zone_size, load_name="Cooling", nolimit=nolimit ) self.HeatingLimitType = HeatingLimitType self.MaxHeatingCapacity = heating_cap self.MaxHeatFlow = heating_flow self.CoolingLimitType = CoolingLimitType self.MaxCoolingCapacity = cooling_cap self.MaxCoolFlow = cooling_flow self.CoolingCoeffOfPerf = float(cooling_cop) self.HeatingCoeffOfPerf = float(heating_cop) # If cop calc == infinity, COP = 1 because we need a value in json file. if math.isnan(heating_cop): self.HeatingCoeffOfPerf = 1 if math.isnan(cooling_cop): self.CoolingCoeffOfPerf = 1 def _set_thermostat_setpoints(self, zone, zone_ep): """Set the thermostat settings and schedules for this zone. Args: zone_ep: zone (Zone): The zone object. """ # Set Thermostat set points # Heating and Cooling set points and schedules with sqlite3.connect(zone_ep.theidf.sql_file) as conn: sql_query = f""" SELECT t.ReportVariableDataDictionaryIndex FROM ReportVariableDataDictionary t WHERE VariableName == 'Zone Thermostat Heating Setpoint Temperature' and KeyValue == '{zone.Name.upper()}';""" index = conn.execute(sql_query).fetchone() if index: sql_query = f""" SELECT t.VariableValue FROM ReportVariableData t WHERE ReportVariableDataDictionaryIndex == {index[0]};""" h_array = conn.execute(sql_query).fetchall() if h_array: h_array = np.array(h_array).round(2) scaler = Binarizer(threshold=np.array(h_array).mean() - 0.1) heating_availability = scaler.fit_transform(h_array).flatten() heating_sched = UmiSchedule.from_values( Name=zone.Name + "_Heating_Schedule", Values=heating_availability, Type="Fraction", allow_duplicates=True, ) else: heating_sched = None sql_query = f""" SELECT t.ReportVariableDataDictionaryIndex FROM ReportVariableDataDictionary t WHERE VariableName == 'Zone Thermostat Cooling Setpoint Temperature' and KeyValue == '{zone.Name.upper()}';""" index = conn.execute(sql_query).fetchone() if index: sql_query = f""" SELECT t.VariableValue FROM ReportVariableData t WHERE ReportVariableDataDictionaryIndex == {index[0]};""" c_array = conn.execute(sql_query).fetchall() if c_array: c_array = np.array(c_array).round(2) scaler = Binarizer(threshold=c_array.mean() + 0.1) cooling_availability = scaler.fit_transform(c_array).flatten() cooling_sched = UmiSchedule.from_values( Name=zone.Name + "_Cooling_Schedule", Values=1 - cooling_availability, # take flipped Type="Fraction", allow_duplicates=True, ) else: cooling_sched = None self.HeatingSetpoint = max(h_array)[0] self.HeatingSchedule = heating_sched self.CoolingSetpoint = min(c_array)[0] self.CoolingSchedule = cooling_sched # If HeatingSetpoint == nan, means there is no heat or cold input, # therefore system is off. if self.HeatingSetpoint == 0: self.IsHeatingOn = False else: self.IsHeatingOn = True if self.CoolingSetpoint == 0: self.IsCoolingOn = False else: self.IsCoolingOn = True def _set_heat_recovery(self, zone, zone_ep): """Set the heat recovery parameters for this zone. Heat Recovery Parameters: - HeatRecoveryEfficiencyLatent (float): The latent heat recovery effectiveness. - HeatRecoveryEfficiencySensible (float): The sensible heat recovery effectiveness. - HeatRecoveryType (int): None = 0, Sensible = 1 or Enthalpy = 2. - comment (str): A comment to append to the class comment attribute. Args: zone_ep: zone (Zone): The Zone object. """ from itertools import chain # Todo: Implement loop that detects HVAC linked to Zone; than parse heat # recovery. Needs to happen when a zone has a ZoneHVAC:IdealLoadsAirSystem # connections = zone._epbunch.getreferingobjs( # iddgroups=["Zone HVAC Equipment Connections"], fields=["Zone_Name"] # ) # nodes = [ # con.get_referenced_object("Zone_Air_Inlet_Node_or_NodeList_Name") # for con in connections # ] # get possible heat recovery objects from idd heat_recovery_objects = zone_ep.theidf.getiddgroupdict()["Heat Recovery"] # get possible heat recovery objects from this idf heat_recovery_in_idf = list( chain.from_iterable( zone_ep.theidf.idfobjects[key.upper()] for key in heat_recovery_objects ) ) # Set defaults HeatRecoveryEfficiencyLatent = 0.65 HeatRecoveryEfficiencySensible = 0.7 HeatRecoveryType = HeatRecoveryTypes.NONE comment = "" # iterate over those objects. If the list is empty, it will simply pass. for object in heat_recovery_in_idf: if object.key.upper() == "HeatExchanger:AirToAir:FlatPlate".upper(): # Do HeatExchanger:AirToAir:FlatPlate nsaot = object.Nominal_Supply_Air_Outlet_Temperature nsait = object.Nominal_Supply_Air_Inlet_Temperature n2ait = object.Nominal_Secondary_Air_Inlet_Temperature HeatRecoveryEfficiencySensible = (nsaot - nsait) / (n2ait - nsait) # Hypotheses: HeatRecoveryEfficiencySensible - 0.05 HeatRecoveryEfficiencyLatent = HeatRecoveryEfficiencySensible - 0.05 HeatRecoveryType = HeatRecoveryTypes.Enthalpy comment = ( "HeatRecoveryEfficiencySensible was calculated " "using this formula: (Supply Air Outlet T°C -; " "Supply Air Inlet T°C)/(Secondary Air Inlet T°C - " "Supply Air Inlet T°C)" ) elif ( object.key.upper() == "HeatExchanger:AirToAir:SensibleAndLatent".upper() ): # Do HeatExchanger:AirToAir:SensibleAndLatent calculation HeatRecoveryEfficiencyLatent = ( object.Latent_Effectiveness_at_100_Heating_Air_Flow ) HeatRecoveryEfficiencySensible = ( object.Sensible_Effectiveness_at_100_Heating_Air_Flow ) HeatRecoveryType = HeatRecoveryTypes.Enthalpy elif object.key.upper() == "HeatExchanger:Desiccant:BalancedFlow".upper(): # Do HeatExchanger:Dessicant:BalancedFlow # Use default values HeatRecoveryEfficiencyLatent = 0.65 HeatRecoveryEfficiencySensible = 0.7 HeatRecoveryType = HeatRecoveryTypes.Enthalpy elif ( object.key.upper() == "HeatExchanger:Desiccant:BalancedFlow" ":PerformanceDataType1".upper() ): # This is not an actual HeatExchanger, pass pass else: msg = 'Heat exchanger object "{}" is not ' "implemented".format(object) raise NotImplementedError(msg) self.HeatRecoveryEfficiencyLatent = HeatRecoveryEfficiencyLatent self.HeatRecoveryEfficiencySensible = HeatRecoveryEfficiencySensible self.HeatRecoveryType = HeatRecoveryType self.Comments += comment @staticmethod def _get_recoverty_effectiveness(object, zone, zone_ep): rd = ReportData.from_sql_dict(zone_ep.theidf.sql()) effectiveness = ( rd.filter_report_data( name=( "Heat Exchanger Sensible Effectiveness", "Heat Exchanger Latent Effectiveness", ) ) .loc[lambda x: x.Value > 0] .groupby(["KeyValue", "Name"]) .Value.mean() .unstack(level=-1) ) HeatRecoveryEfficiencySensible = effectiveness.loc[ object.Name.upper(), "Heat Exchanger Sensible Effectiveness" ] HeatRecoveryEfficiencyLatent = effectiveness.loc[ object.Name.upper(), "Heat Exchanger Latent Effectiveness" ] return HeatRecoveryEfficiencyLatent, HeatRecoveryEfficiencySensible @staticmethod def _get_design_limits(zone, zone_size, load_name, nolimit=False): """Get design limits for heating and cooling systems. Args: zone (archetypal.template.zone.Zone): zone to gets information from zone_size (df): Dataframe from the sql EnergyPlus outpout, with the sizing of the heating and cooling systems load_name (str): 'Heating' or 'Cooling' depending on what system we want to characterize """ if nolimit: return IdealSystemLimit.NoLimit, 100, 100 try: cap = ( zone_size[zone_size["LoadType"] == load_name]["UserDesLoad"].values[0] / zone.area ) flow = ( zone_size[zone_size["LoadType"] == load_name]["UserDesFlow"].values[0] / zone.area ) LimitType = IdealSystemLimit.LimitFlowRateAndCapacity except Exception: cap = 100 flow = 100 LimitType = IdealSystemLimit.NoLimit return LimitType, cap, flow @staticmethod def _get_cop(zone, energy_in_list, energy_out_variable_name): """Calculate COP for heating or cooling systems. Args: zone (archetypal.template.zone.Zone): zone to gets information from energy_in_list (str or tuple): list of the energy sources for a system (e.g. [Heating:Electricity, Heating:Gas] for heating system) energy_out_variable_name (str or tuple): Name of the output in the sql for the energy given to the zone from the system (e.g. 'Air System Total Heating Energy') """ from archetypal.reportdata import ReportData rd = ReportData.from_sql_dict(zone.idf.sql()) energy_out = rd.filter_report_data(name=tuple(energy_out_variable_name)) energy_in = rd.filter_report_data(name=tuple(energy_in_list)) outs = energy_out.groupby("KeyValue").Value.sum() ins = energy_in.Value.sum() cop = float_round(outs.sum() / ins, 3) return cop
[docs] def combine(self, other, weights=None): """Combine two ZoneConditioning objects together. Args: other (ZoneConditioning): The other ZoneConditioning object to combine with. weights (list-like, optional): A list-like object of len 2. If None, the volume of the zones for which self and other belongs is used. Returns: (ZoneConditioning): the combined ZoneConditioning object. """ # Check if other is None. Simply return self if not other: return self if not self: return other # Check if other is the same type as self if not isinstance(other, self.__class__): msg = "Cannot combine %s with %s" % ( self.__class__.__name__, other.__class__.__name__, ) raise NotImplementedError(msg) # Check if other is not the same as self if self == other: return self meta = self._get_predecessors_meta(other) if not weights: weights = [self.area, other.area] new_attr = dict( CoolingCoeffOfPerf=UmiBase.float_mean( self, other, "CoolingCoeffOfPerf", weights ), CoolingLimitType=max(self.CoolingLimitType, other.CoolingLimitType), CoolingSetpoint=UmiBase.float_mean(self, other, "CoolingSetpoint", weights), EconomizerType=max(self.EconomizerType, other.EconomizerType), HeatRecoveryEfficiencyLatent=UmiBase.float_mean( self, other, "HeatRecoveryEfficiencyLatent", weights ), HeatRecoveryEfficiencySensible=UmiBase.float_mean( self, other, "HeatRecoveryEfficiencySensible", weights ), HeatRecoveryType=max(self.HeatRecoveryType, other.HeatRecoveryType), HeatingCoeffOfPerf=UmiBase.float_mean( self, other, "HeatingCoeffOfPerf", weights ), HeatingLimitType=max(self.HeatingLimitType, other.HeatingLimitType), HeatingSetpoint=UmiBase.float_mean(self, other, "HeatingSetpoint", weights), IsCoolingOn=any((self.IsCoolingOn, other.IsCoolingOn)), IsHeatingOn=any((self.IsHeatingOn, other.IsHeatingOn)), IsMechVentOn=any((self.IsMechVentOn, other.IsMechVentOn)), MaxCoolFlow=UmiBase.float_mean(self, other, "MaxCoolFlow", weights), MaxCoolingCapacity=UmiBase.float_mean( self, other, "MaxCoolingCapacity", weights ), MaxHeatFlow=UmiBase.float_mean(self, other, "MaxHeatFlow", weights), MaxHeatingCapacity=UmiBase.float_mean( self, other, "MaxHeatingCapacity", weights ), MinFreshAirPerArea=UmiBase.float_mean( self, other, "MinFreshAirPerArea", weights ), MinFreshAirPerPerson=UmiBase.float_mean( self, other, "MinFreshAirPerPerson", weights ), HeatingSchedule=UmiSchedule.combine( self.HeatingSchedule, other.HeatingSchedule, weights ), CoolingSchedule=UmiSchedule.combine( self.CoolingSchedule, other.CoolingSchedule, weights ), MechVentSchedule=UmiSchedule.combine( self.MechVentSchedule, other.MechVentSchedule, weights ), area=1 if self.area + other.area == 2 else self.area + other.area, ) # create a new object with the previous attributes new_obj = self.__class__( **meta, **new_attr, allow_duplicates=self.allow_duplicates ) new_obj.predecessors.update(self.predecessors + other.predecessors) return new_obj
[docs] def validate(self): """Validate object and fill in missing values.""" if self.HeatingSchedule is None: self.HeatingSchedule = UmiSchedule.constant_schedule() if self.CoolingSchedule is None: self.CoolingSchedule = UmiSchedule.constant_schedule() if self.MechVentSchedule is None: self.MechVentSchedule = UmiSchedule.constant_schedule() if not self.IsMechVentOn: self.IsMechVentOn = False if not self.MinFreshAirPerPerson: self.MinFreshAirPerPerson = 0 if not self.MinFreshAirPerArea: self.MinFreshAirPerArea = 0
[docs] def mapping(self, validate=True): """Get a dict based on the object properties, useful for dict repr. Args: validate (bool): If True, try to validate object before returning the mapping. """ if validate: self.validate() base = super(ZoneConditioning, self).mapping(validate=validate) data = dict( CoolingSchedule=self.CoolingSchedule, CoolingCoeffOfPerf=self.CoolingCoeffOfPerf, CoolingSetpoint=self.CoolingSetpoint, CoolingLimitType=self.CoolingLimitType, CoolingFuelType=self.CoolingFuelType, EconomizerType=self.EconomizerType, HeatingCoeffOfPerf=self.HeatingCoeffOfPerf, HeatingLimitType=self.HeatingLimitType, HeatingFuelType=self.HeatingFuelType, HeatingSchedule=self.HeatingSchedule, HeatingSetpoint=self.HeatingSetpoint, HeatRecoveryEfficiencyLatent=self.HeatRecoveryEfficiencyLatent, HeatRecoveryEfficiencySensible=self.HeatRecoveryEfficiencySensible, HeatRecoveryType=self.HeatRecoveryType, IsCoolingOn=self.IsCoolingOn, IsHeatingOn=self.IsHeatingOn, IsMechVentOn=self.IsMechVentOn, MaxCoolFlow=self.MaxCoolFlow, MaxCoolingCapacity=self.MaxCoolingCapacity, MaxHeatFlow=self.MaxHeatFlow, MaxHeatingCapacity=self.MaxHeatingCapacity, MechVentSchedule=self.MechVentSchedule, MinFreshAirPerArea=self.MinFreshAirPerArea, MinFreshAirPerPerson=self.MinFreshAirPerPerson, ) data.update(base) return data
[docs] def to_epbunch(self, idf, zone_name, design_specification_outdoor_air_object): """Convert self to an EpBunch given an IDF model. Args: idf: zone_name: Returns: EpBunch: The EpBunch object added to the idf model. """ return idf.newidfobject( key="ZONEHVAC:IDEALLOADSAIRSYSTEM", Name=f"{zone_name} Ideal Loads Air System", Availability_Schedule_Name="", Zone_Supply_Air_Node_Name="", Zone_Exhaust_Air_Node_Name="", System_Inlet_Air_Node_Name="", Maximum_Heating_Supply_Air_Temperature="50", Minimum_Cooling_Supply_Air_Temperature="13", Maximum_Heating_Supply_Air_Humidity_Ratio="0.0156", Minimum_Cooling_Supply_Air_Humidity_Ratio="0.0077", Heating_Limit=self.HeatingLimitType.name, Maximum_Heating_Air_Flow_Rate=self.MaxHeatFlow, Maximum_Sensible_Heating_Capacity=self.MaxHeatingCapacity, Cooling_Limit=self.CoolingLimitType.name, Maximum_Cooling_Air_Flow_Rate=self.MaxCoolFlow, Maximum_Total_Cooling_Capacity=self.MaxCoolingCapacity, Heating_Availability_Schedule_Name=self.HeatingSchedule, Cooling_Availability_Schedule_Name=self.CoolingSchedule, Dehumidification_Control_Type="ConstantSensibleHeatRatio", Cooling_Sensible_Heat_Ratio="0.7", Humidification_Control_Type="None", Design_Specification_Outdoor_Air_Object_Name=design_specification_outdoor_air_object.Name, Outdoor_Air_Inlet_Node_Name="", Demand_Controlled_Ventilation_Type="None", Outdoor_Air_Economizer_Type=self.EconomizerType.name, Heat_Recovery_Type=self.HeatRecoveryType.name, Sensible_Heat_Recovery_Effectiveness=self.HeatRecoveryEfficiencySensible, Latent_Heat_Recovery_Effectiveness=self.HeatRecoveryEfficiencyLatent, )
[docs] def duplicate(self): """Get copy of self.""" return self.__copy__()
def __add__(self, other): """Combine self and other.""" return self.combine(other) def __hash__(self): """Return the hash value of self.""" return hash( (self.__class__.__name__, getattr(self, "Name", None), self.DataSource) ) def __eq__(self, other): """Assert self is equivalent to other.""" if not isinstance(other, ZoneConditioning): return NotImplemented else: return all( [ self.CoolingCoeffOfPerf == other.CoolingCoeffOfPerf, self.CoolingLimitType == other.CoolingLimitType, self.CoolingSetpoint == other.CoolingSetpoint, self.CoolingSchedule == other.CoolingSchedule, self.EconomizerType == other.EconomizerType, self.HeatRecoveryEfficiencyLatent == other.HeatRecoveryEfficiencyLatent, self.HeatRecoveryEfficiencySensible == other.HeatRecoveryEfficiencySensible, self.HeatRecoveryType == other.HeatRecoveryType, self.HeatingCoeffOfPerf == other.HeatingCoeffOfPerf, self.HeatingLimitType == other.HeatingLimitType, self.HeatingSetpoint == other.HeatingSetpoint, self.HeatingSchedule == other.HeatingSchedule, self.IsCoolingOn == other.IsCoolingOn, self.IsHeatingOn == other.IsHeatingOn, self.IsMechVentOn == other.IsMechVentOn, self.MaxCoolFlow == other.MaxCoolFlow, self.MaxCoolingCapacity == other.MaxCoolingCapacity, self.MaxHeatFlow == other.MaxHeatFlow, self.MaxHeatingCapacity == other.MaxHeatingCapacity, self.MinFreshAirPerArea == other.MinFreshAirPerArea, self.MinFreshAirPerPerson == other.MinFreshAirPerPerson, self.MechVentSchedule == other.MechVentSchedule, ] ) def __copy__(self): """Create a copy of self.""" return self.__class__(**self.mapping(validate=False))