"""archetypal VentilationSetting."""
import collections
import logging as lg
from enum import Enum
import numpy as np
import pandas as pd
from sigfig import round
from validator_collection import checkers, validators
from archetypal.template.schedule import UmiSchedule
from archetypal.template.umi_base import UmiBase
from archetypal.utils import log, timeit, top, weighted_mean
def resolve_temp(temp, idf):
"""Resolve the temperature given a float or a string.
If a float is passed, simply return it. If a str
is passed, get the schedule and return the mean value.
Args:
temp (float or str):
idf (IDF): the idf object
"""
if isinstance(temp, float):
return temp
elif isinstance(temp, str):
epbunch = idf.schedules_dict[temp.upper()]
sched = UmiSchedule.from_epbunch(epbunch)
return sched.all_values.mean()
class VentilationType(Enum):
"""EnergyPlus Ventilation Types for ZoneVentilation:DesignFlowrate.
This alpha character string defines the type of ventilation as one of the
following options: Natural, Exhaust, Intake, or Balanced. Natural ventilation is
assumed to be air movement/exchange as a result of openings in the building
façade and will not consume any fan energy. Values for fan pressure and
efficiency for natural ventilation are ignored. For either Exhaust or Intake,
values for fan pressure and efficiency define the fan electric consumption. For
Natural and Exhaust ventilation, the conditions of the air entering the space are
assumed to be equivalent to outside air conditions. For Intake and Balanced
ventilation, an appropriate amount of fan heat is added to the entering air
stream. For Balanced ventilation, both an intake fan and an exhaust fan are
assumed to co-exist, both having the same flow rate and power consumption (using
the entered values for fan pressure rise and fan total efficiency). Thus,
the fan electric consumption for Balanced ventilation is twice that for the
Exhaust or Intake ventilation types which employ only a single fan.
"""
Natural = 0
Intake = 1
Exhaust = 2
Balanced = 3
[docs]class VentilationSetting(UmiBase):
"""Zone Ventilation Settings.
.. image:: ../images/template/zoneinfo-ventilation.png
"""
__slots__ = (
"_infiltration",
"_is_infiltration_on",
"_is_buoyancy_on",
"_is_nat_vent_on",
"_is_scheduled_ventilation_on",
"_is_wind_on",
"_natural_ventilation_max_outdoor_air_temp",
"_natural_ventilation_max_relative_humidity",
"_natural_ventilation_min_outdoor_air_temp",
"_natural_ventilation_zone_setpoint_temp",
"_scheduled_ventilation_ach",
"_scheduled_ventilation_setpoint",
"_scheduled_ventilation_schedule",
"_nat_ventilation_schedule",
"_ventilation_type",
"_afn",
"_area",
"_volume",
)
def __init__(
self,
Name,
Infiltration=0.1,
IsInfiltrationOn=True,
IsNatVentOn=False,
NatVentSchedule=None,
IsWindOn=False,
IsBuoyancyOn=True,
NatVentMaxOutdoorAirTemp=30,
NatVentMaxRelHumidity=90,
NatVentMinOutdoorAirTemp=0,
NatVentZoneTempSetpoint=18,
ScheduledVentilationAch=0.6,
ScheduledVentilationSchedule=None,
ScheduledVentilationSetpoint=18,
IsScheduledVentilationOn=False,
VentilationType=VentilationType.Exhaust,
Afn=False,
area=1,
volume=1,
**kwargs,
):
"""Initialize a new VentilationSetting (for zone) object.
Args:
NatVentSchedule (UmiSchedule): The name of the schedule
(Day | Week | Year) which ultimately modifies the Opening Area
value. In its current implementation, any
value greater than 0 will consider an open window.
ScheduledVentilationSchedule (UmiSchedule, optional): The name of
the schedule (Schedules Tab) that modifies the maximum design
volume flow rate. This fraction is between 0.0 and 1.0.
Afn (bool): Todo: Not Used.
Infiltration (float): Infiltration rate in ACH.
IsBuoyancyOn (bool): If True, simulation takes into account the
stack effect in the infiltration calculation
IsInfiltrationOn (bool): If yes, there is heat transfer between the
building and the outside caused by infiltration.
IsNatVentOn (bool): If True, Natural ventilation (air
movement/exchange as a result of openings in the building façade
not consuming any fan energy).
IsScheduledVentilationOn (bool): If True, Ventilation (flow of air
from the outdoor environment directly into a thermal zone) is ON
IsWindOn (bool): If True, simulation takes into account the wind
effect in the infiltration calculation
NatVentMaxOutdoorAirTemp (float): The outdoor temperature (in
Celsius) above which ventilation is shut off. The minimum value
for this field is -100.0°C and the maximum value is 100.0°C. The
default value is 100.0°C if the field is left blank. This upper
temperature limit is intended to avoid overheating a space,
which could result in a cooling load.
NatVentMaxRelHumidity (float): Defines the dehumidifying relative
humidity setpoint, expressed as a percentage (0-100), for each
timestep of the simulation.
NatVentMinOutdoorAirTemp (float): The outdoor temperature (in
Celsius) below which ventilation is shut off. The minimum value
for this field is -100.0°C and the maximum value is 100.0°C. The
default value is -100.0°C if the field is left blank. This lower
temperature limit is intended to avoid overcooling a space,
which could result in a heating load.
NatVentZoneTempSetpoint (float):
ScheduledVentilationAch (float): This factor, along with the Zone
Volume, will be used to determine the Design Flow Rate.
ScheduledVentilationSetpoint (float): The indoor temperature (in
Celsius) below which ventilation is shutoff. The minimum value
for this field is -100.0°C and the maximum value is 100.0°C. The
default value is -100.0°C if the field is left blank. This lower
temperature limit is intended to avoid overcooling a space and
thus result in a heating load. For example, if the user
specifies a minimum temperature of 20°C, ventilation is assumed
to be available if the zone air temperature is above 20°C. If
the zone air temperature drops below 20°C, then ventilation is
automatically turned off.
VentilationType (int): This alpha character string defines the type of
ventilation as one of the following options: Natural, Exhaust,
Intake, or Balanced. Natural ventilation is assumed to be air
movement/exchange as a result of openings in the building façade and
will not consume any fan energy. Values for fan pressure and
efficiency for natural ventilation are ignored. For either Exhaust or
Intake, values for fan pressure and efficiency define the fan
electric consumption. For Natural and Exhaust ventilation,
the conditions of the air entering the space are assumed to be
equivalent to outside air conditions. For Intake and Balanced
ventilation, an appropriate amount of fan heat is added to the entering
air stream. For Balanced ventilation, both an intake fan and an
exhaust fan are assumed to co-exist, both having the same flow rate
and power consumption (using the entered values for fan pressure rise
and fan total efficiency). Thus, the fan electric consumption for
Balanced ventilation is twice that for the Exhaust or Intake
ventilation types which employ only a single fan.
**kwargs: keywords passed to the constructor.
"""
super(VentilationSetting, self).__init__(Name, **kwargs)
self.Infiltration = Infiltration
self.IsInfiltrationOn = IsInfiltrationOn
self.IsNatVentOn = IsNatVentOn
self.NatVentSchedule = NatVentSchedule
self.IsWindOn = IsWindOn
self.IsBuoyancyOn = IsBuoyancyOn
self.NatVentMaxOutdoorAirTemp = NatVentMaxOutdoorAirTemp
self.NatVentMaxRelHumidity = NatVentMaxRelHumidity
self.NatVentMinOutdoorAirTemp = NatVentMinOutdoorAirTemp
self.NatVentZoneTempSetpoint = NatVentZoneTempSetpoint
self.ScheduledVentilationAch = ScheduledVentilationAch
self.ScheduledVentilationSetpoint = ScheduledVentilationSetpoint
self.ScheduledVentilationSchedule = ScheduledVentilationSchedule
self.IsScheduledVentilationOn = IsScheduledVentilationOn
self.VentilationType = VentilationType
self.Afn = Afn
self.area = area
self.volume = volume
@property
def NatVentSchedule(self):
"""Get or set the natural ventilation schedule.
Hint:
This schedule ultimately modifies the Opening Area value.
"""
return self._nat_ventilation_schedule
@NatVentSchedule.setter
def NatVentSchedule(self, value):
if value is not None:
assert isinstance(value, UmiSchedule), (
f"Input error with value {value}. NatVentSchedule must "
f"be an UmiSchedule, not a {type(value)}"
)
self._nat_ventilation_schedule = value
@property
def ScheduledVentilationSchedule(self):
"""Get or set the scheduled ventilation schedule."""
return self._scheduled_ventilation_schedule
@ScheduledVentilationSchedule.setter
def ScheduledVentilationSchedule(self, value):
if value is not None:
assert isinstance(value, UmiSchedule), (
f"Input error with value {value}. ScheduledVentilationSchedule must "
f"be an UmiSchedule, not a {type(value)}"
)
value.quantity = self.ScheduledVentilationAch
self._scheduled_ventilation_schedule = value
@property
def Infiltration(self):
"""Get or set the infiltration air change rate [ach]."""
return self._infiltration
@Infiltration.setter
def Infiltration(self, value):
if value is None:
value = 0
value = validators.float(value, minimum=0)
if value == 0:
self.IsInfiltrationOn = False
self._infiltration = value
@property
def IsInfiltrationOn(self):
"""Get or set the the infiltration [bool]."""
return self._is_infiltration_on
@IsInfiltrationOn.setter
def IsInfiltrationOn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. IsInfiltrationOn must "
f"be an boolean, not a {type(value)}"
)
self._is_infiltration_on = value
@property
def IsBuoyancyOn(self):
"""Get or set the buoyancy boolean."""
return self._is_buoyancy_on
@IsBuoyancyOn.setter
def IsBuoyancyOn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. IsBuoyancyOn must "
f"be an boolean, not a {type(value)}"
)
self._is_buoyancy_on = value
@property
def IsNatVentOn(self):
"""Get or set the natural ventilation [bool]."""
return self._is_nat_vent_on
@IsNatVentOn.setter
def IsNatVentOn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. IsNatVentOn must "
f"be an boolean, not a {type(value)}"
)
self._is_nat_vent_on = value
@property
def IsScheduledVentilationOn(self):
"""Get or set the scheduled ventilation [bool]."""
return self._is_scheduled_ventilation_on
@IsScheduledVentilationOn.setter
def IsScheduledVentilationOn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. IsScheduledVentilationOn must "
f"be an boolean, not a {type(value)}"
)
if value:
assert (
self.ScheduledVentilationAch > 0
and self.ScheduledVentilationSchedule is not None
), (
f"IsScheduledVentilationOn cannot be 'True' if ScheduledVentilationAch "
f"is 0 or if ScheduledVentilationSchedule is None."
)
self._is_scheduled_ventilation_on = value
@property
def IsWindOn(self):
"""Get or set the wind effect [bool]."""
return self._is_wind_on
@IsWindOn.setter
def IsWindOn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. IsWindOn must "
f"be an boolean, not a {type(value)}"
)
self._is_wind_on = value
@property
def NatVentMaxOutdoorAirTemp(self):
"""Get or set the natural ventilation maximum outdoor air temperature [degC]."""
return self._natural_ventilation_max_outdoor_air_temp
@NatVentMaxOutdoorAirTemp.setter
def NatVentMaxOutdoorAirTemp(self, value):
self._natural_ventilation_max_outdoor_air_temp = validators.float(
value, minimum=-100, maximum=100
)
@property
def NatVentMaxRelHumidity(self):
"""Get or set the natural ventilation relative humidity setpoint [%]."""
return self._natural_ventilation_max_relative_humidity
@NatVentMaxRelHumidity.setter
def NatVentMaxRelHumidity(self, value):
self._natural_ventilation_max_relative_humidity = validators.float(
value, minimum=0, maximum=100
)
@property
def NatVentMinOutdoorAirTemp(self):
"""Get or set the natural ventilation minimum outdoor air temperature [degC]."""
return self._natural_ventilation_min_outdoor_air_temp
@NatVentMinOutdoorAirTemp.setter
def NatVentMinOutdoorAirTemp(self, value):
self._natural_ventilation_min_outdoor_air_temp = validators.float(
value, minimum=-100, maximum=100
)
@property
def NatVentZoneTempSetpoint(self):
"""Get or set the natural ventilation zone temperature setpoint [degC]."""
return self._natural_ventilation_zone_setpoint_temp
@NatVentZoneTempSetpoint.setter
def NatVentZoneTempSetpoint(self, value):
self._natural_ventilation_zone_setpoint_temp = validators.float(
value,
minimum=self.NatVentMinOutdoorAirTemp,
maximum=self.NatVentMaxOutdoorAirTemp,
)
@property
def ScheduledVentilationAch(self):
"""Get or set the scheduled ventilation air changes per hours [-]."""
return self._scheduled_ventilation_ach
@ScheduledVentilationAch.setter
def ScheduledVentilationAch(self, value):
if value is None:
value = 0
self._scheduled_ventilation_ach = validators.float(value, minimum=0)
@property
def ScheduledVentilationSetpoint(self):
"""Get or set the scheduled ventilation setpoint."""
return self._scheduled_ventilation_setpoint
@ScheduledVentilationSetpoint.setter
def ScheduledVentilationSetpoint(self, value):
self._scheduled_ventilation_setpoint = validators.float(
value, minimum=-100, maximum=100
)
@property
def VentilationType(self):
"""Get or set the ventilation type.
Choices are (<VentilationType.Natural: 0>, <VentilationType.Intake: 1>,
<VentilationType.Exhaust: 2>, <VentilationType.Balanced: 3>).
"""
return self._ventilation_type
@VentilationType.setter
def VentilationType(self, value):
if checkers.is_string(value):
assert VentilationType[value], (
f"Input value error for '{value}'. "
f"Expected one of {tuple(a for a in VentilationType)}"
)
self._ventilation_type = VentilationType[value]
elif checkers.is_numeric(value):
assert VentilationType[value], (
f"Input value error for '{value}'. "
f"Expected one of {tuple(a for a in VentilationType)}"
)
self._ventilation_type = VentilationType(value)
self._ventilation_type = value
@property
def Afn(self):
"""Get or set the use of the airflow network [bool]."""
return self._afn
@Afn.setter
def Afn(self, value):
assert isinstance(value, bool), (
f"Input error with value {value}. Afn must "
f"be an boolean, not a {type(value)}"
)
self._afn = value
@property
def area(self):
"""Get or set the area of the zone [m²]."""
return self._area
@area.setter
def area(self, value):
self._area = validators.float(value, minimum=0)
@property
def volume(self):
"""Get or set the volume of the zone [m³]."""
return self._volume
@volume.setter
def volume(self, value):
self._volume = validators.float(value, minimum=0)
[docs] @classmethod
def from_dict(cls, data, schedules, **kwargs):
"""Create a VentilationSetting from a dictionary.
Args:
data (dict): The python dictionary.
schedules (dict): A dictionary of UmiSchedules with their id as keys.
**kwargs: keywords passed parent constructor.
.. code-block:: python
{
"$id": "162",
"Afn": false,
"IsBuoyancyOn": true,
"Infiltration": 0.35,
"IsInfiltrationOn": true,
"IsNatVentOn": false,
"IsScheduledVentilationOn": false,
"NatVentMaxRelHumidity": 80.0,
"NatVentMaxOutdoorAirTemp": 26.0,
"NatVentMinOutdoorAirTemp": 20.0,
"NatVentSchedule": {
"$ref": "151"
},
"NatVentZoneTempSetpoint": 22.0,
"ScheduledVentilationAch": 0.6,
"ScheduledVentilationSchedule": {
"$ref": "151"
},
"ScheduledVentilationSetpoint": 22.0,
"IsWindOn": false,
"Category": "Office Spaces",
"Comments": null,
"DataSource": "MIT_SDL",
"Name": "B_Off_0 ventilation"
}
"""
vent_sch = schedules[data.pop("ScheduledVentilationSchedule")["$ref"]]
nat_sch = schedules[data.pop("NatVentSchedule")["$ref"]]
_id = data.pop("$id")
return cls(
id=_id,
ScheduledVentilationSchedule=vent_sch,
NatVentSchedule=nat_sch,
**data,
**kwargs,
)
[docs] def to_dict(self):
"""Return VentilationSetting dictionary representation."""
self.validate() # Validate object before trying to get json format
data_dict = collections.OrderedDict()
data_dict["$id"] = str(self.id)
data_dict["Afn"] = self.Afn
data_dict["IsBuoyancyOn"] = self.IsBuoyancyOn
data_dict["Infiltration"] = round(self.Infiltration, 3)
data_dict["IsInfiltrationOn"] = self.IsInfiltrationOn
data_dict["IsNatVentOn"] = self.IsNatVentOn
data_dict["IsScheduledVentilationOn"] = self.IsScheduledVentilationOn
data_dict["NatVentMaxRelHumidity"] = round(self.NatVentMaxRelHumidity, 3)
data_dict["NatVentMaxOutdoorAirTemp"] = round(self.NatVentMaxOutdoorAirTemp, 3)
data_dict["NatVentMinOutdoorAirTemp"] = round(self.NatVentMinOutdoorAirTemp, 3)
data_dict["NatVentSchedule"] = self.NatVentSchedule.to_ref()
data_dict["NatVentZoneTempSetpoint"] = round(self.NatVentZoneTempSetpoint, 3)
data_dict["ScheduledVentilationAch"] = round(self.ScheduledVentilationAch, 3)
data_dict[
"ScheduledVentilationSchedule"
] = self.ScheduledVentilationSchedule.to_ref()
data_dict["ScheduledVentilationSetpoint"] = round(
self.ScheduledVentilationSetpoint, 3
)
data_dict["IsWindOn"] = self.IsWindOn
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
@classmethod
@timeit
def from_zone(cls, zone, zone_ep, **kwargs):
"""Create VentilationSetting from a zone object.
Args:
zone_ep:
zone (template.zone.Zone): zone to gets information from
"""
# If Zone is not part of Conditioned Area, it should not have a
# VentilationSetting object.
if not zone.is_part_of_total_floor_area:
return None
name = zone.Name + "_VentilationSetting"
df = {"a": zone_ep.theidf.sql()}
ni_df = nominal_infiltration(df)
sched_df = nominal_mech_ventilation(df)
nat_df = nominal_nat_ventilation(df)
index = ("a", zone.Name.upper())
# Do infiltration
Infiltration, IsInfiltrationOn = do_infiltration(index, ni_df)
# Do natural ventilation
(
IsNatVentOn,
IsWindOn,
IsBuoyancyOn,
NatVentMaxOutdoorAirTemp,
NatVentMaxRelHumidity,
NatVentMinOutdoorAirTemp,
NatVentSchedule,
NatVentZoneTempSetpoint,
) = do_natural_ventilation(index, nat_df, zone, zone_ep)
# Do scheduled ventilation
(
ScheduledVentilationSchedule,
IsScheduledVentilationOn,
ScheduledVentilationAch,
ScheduledVentilationSetpoint,
) = do_scheduled_ventilation(index, sched_df, zone)
z_vent = cls(
Name=name,
zone=zone,
Infiltration=Infiltration,
IsInfiltrationOn=IsInfiltrationOn,
IsWindOn=IsWindOn,
IsBuoyancyOn=IsBuoyancyOn,
IsNatVentOn=IsNatVentOn,
NatVentSchedule=NatVentSchedule,
NatVentMaxRelHumidity=NatVentMaxRelHumidity,
NatVentMaxOutdoorAirTemp=NatVentMaxOutdoorAirTemp,
NatVentMinOutdoorAirTemp=NatVentMinOutdoorAirTemp,
NatVentZoneTempSetpoint=NatVentZoneTempSetpoint,
ScheduledVentilationSchedule=ScheduledVentilationSchedule,
IsScheduledVentilationOn=IsScheduledVentilationOn,
ScheduledVentilationAch=ScheduledVentilationAch,
ScheduledVentilationSetpoint=ScheduledVentilationSetpoint,
Category=zone.DataSource,
**kwargs,
)
return z_vent
[docs] def combine(self, other, **kwargs):
"""Combine VentilationSetting objects together.
Args:
other (VentilationSetting):
kwargs: keywords passed to constructor.
Returns:
(VentilationSetting): the combined VentilationSetting object.
"""
# Check if other is None. Simply return self or if other is not the same as self
if not self and not other:
return None
elif self == other:
area = 1 if self.area + other.area == 2 else self.area + other.area
volume = (
1 if self.volume + other.volume == 2 else self.volume + other.volume
)
new_obj = self.duplicate()
new_obj.area = area
new_obj.volume = volume
return new_obj
elif not self or not other:
new_obj = (self or other).duplicate()
return new_obj
# 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)
meta = self._get_predecessors_meta(other)
# create a new object with the combined attributes
new_obj = self.__class__(
NatVentSchedule=UmiSchedule.combine(
self.NatVentSchedule, other.NatVentSchedule, [self.area, other.area]
),
ScheduledVentilationSchedule=UmiSchedule.combine(
self.ScheduledVentilationSchedule,
other.ScheduledVentilationSchedule,
weights=[self.volume, other.volume],
quantity=True,
),
Afn=any((self.Afn, other.Afn)),
Infiltration=self.float_mean(
other, "Infiltration", [self.area, other.area]
),
IsBuoyancyOn=any((self.IsBuoyancyOn, other.IsBuoyancyOn)),
IsInfiltrationOn=any((self.IsInfiltrationOn, other.IsInfiltrationOn)),
IsNatVentOn=any((self.IsNatVentOn, other.IsNatVentOn)),
IsScheduledVentilationOn=any(
(self.IsScheduledVentilationOn, other.IsScheduledVentilationOn)
),
IsWindOn=any((self.IsWindOn, other.IsWindOn)),
NatVentMaxOutdoorAirTemp=self.float_mean(
other, "NatVentMaxOutdoorAirTemp", [self.area, other.area]
),
NatVentMaxRelHumidity=self.float_mean(
other, "NatVentMaxRelHumidity", [self.area, other.area]
),
NatVentMinOutdoorAirTemp=self.float_mean(
other, "NatVentMinOutdoorAirTemp", [self.area, other.area]
),
NatVentZoneTempSetpoint=self.float_mean(
other, "NatVentZoneTempSetpoint", [self.area, other.area]
),
ScheduledVentilationAch=self.float_mean(
other, "ScheduledVentilationAch", [self.volume, other.volume]
),
ScheduledVentilationSetpoint=self.float_mean(
other, "ScheduledVentilationSetpoint", [self.area, other.area]
),
area=1 if self.area + other.area == 2 else self.area + other.area,
volume=1 if self.volume + other.volume == 2 else self.volume + other.volume,
**meta,
**kwargs,
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 not self.NatVentSchedule:
self.NatVentSchedule = UmiSchedule.constant_schedule(
value=0, Name="AlwaysOff", allow_duplicates=True
)
if not self.ScheduledVentilationSchedule:
self.ScheduledVentilationSchedule = UmiSchedule.constant_schedule(
value=0, Name="AlwaysOff", allow_duplicates=True
)
return self
[docs] def mapping(self, validate=True):
"""Get a dict based on the object properties, useful for dict repr.
Args:
validate:
"""
self.validate()
return dict(
Afn=self.Afn,
IsBuoyancyOn=self.IsBuoyancyOn,
Infiltration=self.Infiltration,
IsInfiltrationOn=self.IsInfiltrationOn,
IsNatVentOn=self.IsNatVentOn,
IsScheduledVentilationOn=self.IsScheduledVentilationOn,
NatVentMaxRelHumidity=self.NatVentMaxRelHumidity,
NatVentMaxOutdoorAirTemp=self.NatVentMaxOutdoorAirTemp,
NatVentMinOutdoorAirTemp=self.NatVentMinOutdoorAirTemp,
NatVentSchedule=self.NatVentSchedule,
NatVentZoneTempSetpoint=self.NatVentZoneTempSetpoint,
ScheduledVentilationAch=self.ScheduledVentilationAch,
ScheduledVentilationSchedule=self.ScheduledVentilationSchedule,
ScheduledVentilationSetpoint=self.ScheduledVentilationSetpoint,
IsWindOn=self.IsWindOn,
Category=self.Category,
Comments=self.Comments,
DataSource=self.DataSource,
Name=self.Name,
)
[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 __key__(self):
"""Get a tuple of attributes. Useful for hashing and comparing."""
return (
self.NatVentSchedule,
self.ScheduledVentilationSchedule,
self.Afn,
self.Infiltration,
self.IsBuoyancyOn,
self.IsInfiltrationOn,
self.IsNatVentOn,
self.IsScheduledVentilationOn,
self.IsWindOn,
self.NatVentMaxOutdoorAirTemp,
self.NatVentMaxRelHumidity,
self.NatVentMinOutdoorAirTemp,
self.NatVentZoneTempSetpoint,
self.ScheduledVentilationAch,
self.ScheduledVentilationSetpoint,
)
def __eq__(self, other):
"""Assert self is equivalent to other."""
if not isinstance(other, VentilationSetting):
return NotImplemented
else:
return self.__key__() == other.__key__()
def __copy__(self):
"""Create a copy of self."""
return self.__class__(
**self.mapping(validate=False), area=self.area, volume=self.volume
)
[docs] def to_epbunch(self, idf, zone_name, opening_area=0.0):
"""Convert self to the EpBunches given an idf model, a zone name.
Notes:
Note that attr:`IsInfiltrationOn`, attr:`IsScheduledVentilationOn` and
attr:`IsNatVentOn` must be `True` for their respective EpBunch objects
to be created.
Args:
idf (IDF): The idf model in which the EpBunch is created.
zone_name (str): The zone name to associate this EpBunch.
opening_area (float): The opening area exposed to outdoors (m2)
in a zone.
.. code-block::
ZONEINFILTRATION:DESIGNFLOWRATE,
Zone Infiltration, !- Name
Zone 1, !- Zone or ZoneList Name
AlwaysOn, !- Schedule Name
AirChanges/Hour, !- Design Flow Rate Calculation Method
, !- Design Flow Rate
, !- Flow per Zone Floor Area
, !- Flow per Exterior Surface Area
0.1, !- Air Changes per Hour
1, !- Constant Term Coefficient
0, !- Temperature Term Coefficient
0, !- Velocity Term Coefficient
0; !- Velocity Squared Term Coefficient
ZONEVENTILATION:DESIGNFLOWRATE,
Zone 1 Ventilation, !- Name
Zone 1, !- Zone or ZoneList Name
AlwaysOn, !- Schedule Name
AirChanges/Hour, !- Design Flow Rate Calculation Method
, !- Design Flow Rate
, !- Flow Rate per Zone Floor Area
, !- Flow Rate per Person
0.6, !- Air Changes per Hour
Exhaust, !- Ventilation Type
67, !- Fan Pressure Rise
0.7, !- Fan Total Efficiency
1, !- Constant Term Coefficient
0, !- Temperature Term Coefficient
0, !- Velocity Term Coefficient
0, !- Velocity Squared Term Coefficient
-100, !- Minimum Indoor Temperature
, !- Minimum Indoor Temperature Schedule Name
100, !- Maximum Indoor Temperature
, !- Maximum Indoor Temperature Schedule Name
-100, !- Delta Temperature
, !- Delta Temperature Schedule Name
-100, !- Minimum Outdoor Temperature
, !- Minimum Outdoor Temperature Schedule Name
100, !- Maximum Outdoor Temperature
, !- Maximum Outdoor Temperature Schedule Name
40; !- Maximum Wind Speed)
ZONEVENTILATION:WINDANDSTACKOPENAREA,
, !- Name
, !- Zone Name
0, !- Opening Area
, !- Opening Area Fraction Schedule Name
Autocalculate, !- Opening Effectiveness
0, !- Effective Angle
0, !- Height Difference
Autocalculate, !- Discharge Coefficient for Opening
-100, !- Minimum Indoor Temperature
, !- Minimum Indoor Temperature Schedule Name
100, !- Maximum Indoor Temperature
, !- Maximum Indoor Temperature Schedule Name
-100, !- Delta Temperature
, !- Delta Temperature Schedule Name
-100, !- Minimum Outdoor Temperature
, !- Minimum Outdoor Temperature Schedule Name
100, !- Maximum Outdoor Temperature
, !- Maximum Outdoor Temperature Schedule Name
40; !- Maximum Wind Speed
Returns:
tuple: A 3-tuple of EpBunch objects added to the idf model.
"""
if self.IsInfiltrationOn:
infiltration_epbunch = idf.newidfobject(
key="ZONEINFILTRATION:DESIGNFLOWRATE",
Name=f"{zone_name} Infiltration",
Zone_or_ZoneList_Name=zone_name,
Schedule_Name=idf.newidfobject(
key="SCHEDULE:CONSTANT", Name="AlwaysOn", Hourly_Value=1
).Name,
Design_Flow_Rate_Calculation_Method="AirChanges/Hour",
Air_Changes_per_Hour=self.Infiltration,
Constant_Term_Coefficient=1,
Temperature_Term_Coefficient=0,
Velocity_Term_Coefficient=0,
Velocity_Squared_Term_Coefficient=0,
)
else:
infiltration_epbunch = None
log("No epbunch created since IsInfiltrationOn == False.")
if self.IsScheduledVentilationOn:
ventilation_epbunch = idf.newidfobject(
key="ZONEVENTILATION:DESIGNFLOWRATE",
Name=f"{zone_name} Ventilation",
Zone_or_ZoneList_Name=zone_name,
Schedule_Name=self.ScheduledVentilationSchedule.to_year_week_day()[
0
].to_epbunch(idf).Name, # take the YearSchedule and get the name.
Design_Flow_Rate_Calculation_Method="AirChanges/Hour",
Design_Flow_Rate="",
Flow_Rate_per_Zone_Floor_Area="",
Flow_Rate_per_Person="",
Air_Changes_per_Hour=self.ScheduledVentilationAch,
Ventilation_Type=self.VentilationType.name,
Fan_Pressure_Rise=67.0,
Fan_Total_Efficiency=0.7,
Constant_Term_Coefficient=1.0,
Temperature_Term_Coefficient=0.0,
Velocity_Term_Coefficient=0.0,
Velocity_Squared_Term_Coefficient=0.0,
Minimum_Indoor_Temperature=-100,
Minimum_Indoor_Temperature_Schedule_Name="",
Maximum_Indoor_Temperature=100.0,
Maximum_Indoor_Temperature_Schedule_Name="",
Delta_Temperature=-100.0,
Delta_Temperature_Schedule_Name="",
Minimum_Outdoor_Temperature=-100.0,
Minimum_Outdoor_Temperature_Schedule_Name="",
Maximum_Outdoor_Temperature=100.0,
Maximum_Outdoor_Temperature_Schedule_Name="",
Maximum_Wind_Speed=40.0,
)
else:
ventilation_epbunch = None
log("No epbunch created since IsScheduledVentilationOn == False.")
if self.IsNatVentOn:
natural_epbunch = idf.newidfobject(
key="ZONEVENTILATION:WINDANDSTACKOPENAREA",
Name=f"{zone_name} Natural Ventilation",
Zone_Name=zone_name,
Opening_Area=opening_area,
Opening_Area_Fraction_Schedule_Name="",
Opening_Effectiveness="Autocalculate",
Effective_Angle=0.0,
Height_Difference=1,
Discharge_Coefficient_for_Opening="Autocalculate",
Minimum_Indoor_Temperature=self.NatVentZoneTempSetpoint,
Minimum_Indoor_Temperature_Schedule_Name="",
Maximum_Indoor_Temperature=100.0,
Maximum_Indoor_Temperature_Schedule_Name="",
Delta_Temperature=-100.0,
Delta_Temperature_Schedule_Name="",
Minimum_Outdoor_Temperature=self.NatVentMinOutdoorAirTemp,
Minimum_Outdoor_Temperature_Schedule_Name="",
Maximum_Outdoor_Temperature=self.NatVentMaxOutdoorAirTemp,
Maximum_Outdoor_Temperature_Schedule_Name="",
Maximum_Wind_Speed=40.0,
)
else:
natural_epbunch = None
log("No epbunch created since IsNatVentOn == False.")
return infiltration_epbunch, ventilation_epbunch, natural_epbunch
def do_infiltration(index, inf_df):
"""Get infiltration information of the zone.
Args:
index (tuple): Zone name
inf_df (dataframe): Dataframe with infiltration information for each
zone.
"""
if not inf_df.empty:
try:
Infiltration = inf_df.loc[index, "ACH - Air Changes per Hour"]
IsInfiltrationOn = any(inf_df.loc[index, "Name"])
except Exception:
Infiltration = 0
IsInfiltrationOn = False
else:
Infiltration = 0
IsInfiltrationOn = False
return Infiltration, IsInfiltrationOn
def do_natural_ventilation(index, nat_df, zone, zone_ep):
"""Get natural ventilation information of the zone.
Args:
zone_ep:
index (tuple): Zone name
nat_df:
zone (template.zone.Zone): zone to gets information from
"""
if not nat_df.empty:
try:
IsNatVentOn = any(nat_df.loc[index, "Name"])
schedule_name_ = nat_df.loc[index, "Schedule Name"]
quantity = nat_df.loc[index, "Volume Flow Rate/Floor Area {m3/s/m2}"]
if schedule_name_.upper() in zone.idf.schedules_dict:
epbunch = zone.idf.schedules_dict[schedule_name_.upper()]
NatVentSchedule = UmiSchedule.from_epbunch(epbunch, quantity=quantity)
else:
raise KeyError
except KeyError:
# todo: For some reason, a ZoneVentilation:WindandStackOpenArea
# 'Opening Area Fraction Schedule Name' is read as Constant-0.0
# in the nat_df. For the mean time, a zone containing such an
# object will be turned on with an AlwaysOn schedule.
IsNatVentOn = True
NatVentSchedule = UmiSchedule.constant_schedule(allow_duplicates=True)
except Exception:
IsNatVentOn = False
NatVentSchedule = UmiSchedule.constant_schedule(allow_duplicates=True)
finally:
try:
NatVentMaxRelHumidity = 90 # todo: not sure if it is being used
NatVentMaxOutdoorAirTemp = resolve_temp(
nat_df.loc[index, "Maximum Outdoor Temperature{C}/Schedule"],
zone_ep.theidf,
)
NatVentMinOutdoorAirTemp = resolve_temp(
nat_df.loc[index, "Minimum Outdoor Temperature{C}/Schedule"],
zone_ep.theidf,
)
NatVentZoneTempSetpoint = resolve_temp(
nat_df.loc[index, "Minimum Indoor Temperature{C}/Schedule"],
zone_ep.theidf,
)
except KeyError:
# this zone is not in the nat_df. Revert to defaults.
NatVentMaxRelHumidity = 90
NatVentMaxOutdoorAirTemp = 30
NatVentMinOutdoorAirTemp = 0
NatVentZoneTempSetpoint = 18
else:
IsNatVentOn = False
NatVentSchedule = UmiSchedule.constant_schedule(allow_duplicates=True)
NatVentMaxRelHumidity = 90
NatVentMaxOutdoorAirTemp = 30
NatVentMinOutdoorAirTemp = 0
NatVentZoneTempSetpoint = 18
# Is Wind ON
if not zone_ep.theidf.idfobjects[
"ZoneVentilation:WindandStackOpenArea".upper()
].list1:
IsWindOn = False
IsBuoyancyOn = False
else:
IsWindOn = True
IsBuoyancyOn = True
return (
IsNatVentOn,
IsWindOn,
IsBuoyancyOn,
NatVentMaxOutdoorAirTemp,
NatVentMaxRelHumidity,
NatVentMinOutdoorAirTemp,
NatVentSchedule,
NatVentZoneTempSetpoint,
)
def do_scheduled_ventilation(index, scd_df, zone):
"""Get schedule ventilation information of the zone.
Args:
index (tuple): Zone name
scd_df:
zone (template.zone.Zone): zone to gets information from
"""
if not scd_df.empty:
try:
IsScheduledVentilationOn = any(scd_df.loc[index, "Name"])
schedule_name_ = scd_df.loc[index, "Schedule Name"]
epbunch = zone.idf.schedules_dict[schedule_name_.upper()]
ScheduledVentilationSchedule = UmiSchedule.from_epbunch(epbunch)
ScheduledVentilationAch = scd_df.loc[index, "ACH - Air Changes per Hour"]
ScheduledVentilationSetpoint = resolve_temp(
scd_df.loc[index, "Minimum Indoor Temperature{C}/Schedule"],
zone.idf,
)
except Exception:
ScheduledVentilationSchedule = UmiSchedule.constant_schedule(
value=0, Name="AlwaysOff", allow_duplicates=True
)
IsScheduledVentilationOn = False
ScheduledVentilationAch = 0
ScheduledVentilationSetpoint = 18
else:
ScheduledVentilationSchedule = UmiSchedule.constant_schedule(
value=0, Name="AlwaysOff", allow_duplicates=True
)
IsScheduledVentilationOn = False
ScheduledVentilationAch = 0
ScheduledVentilationSetpoint = 18
return (
ScheduledVentilationSchedule,
IsScheduledVentilationOn,
ScheduledVentilationAch,
ScheduledVentilationSetpoint,
)
def nominal_nat_ventilation(df):
"""Get the Nominal Natural Ventilation."""
_nom_vent = nominal_ventilation(df)
if _nom_vent.empty:
return _nom_vent
nom_natvent = (
_nom_vent.reset_index()
.set_index(["Archetype", "Zone Name"])
.loc[
lambda e: e["Fan Type {Exhaust;Intake;Natural}"].str.contains("Natural"), :
]
if not _nom_vent.empty
else None
)
return nom_natvent
def nominal_mech_ventilation(df):
"""Get the Nominal Mechanical Ventilation."""
_nom_vent = nominal_ventilation(df)
if _nom_vent.empty:
return _nom_vent
nom_vent = (
_nom_vent.reset_index()
.set_index(["Archetype", "Zone Name"])
.loc[
lambda e: ~e["Fan Type {Exhaust;Intake;Natural}"].str.contains("Natural"), :
]
if not _nom_vent.empty
else None
)
return nom_vent
def nominal_infiltration(df):
"""Get the Nominal Infiltration.
References:
* `Nominal Infiltration Table \
<https://bigladdersoftware.com/epx/docs/8-9/output-details-and \
-examples/eplusout-sql.html#nominalinfiltration-table>`_
"""
df = get_from_tabulardata(df)
report_name = "Initialization Summary"
table_name = "ZoneInfiltration Airflow Stats Nominal"
tbstr = df[
(df.ReportName == report_name) & (df.TableName == table_name)
].reset_index()
if tbstr.empty:
log(
"Table {} does not exist. "
"Returning an empty DataFrame".format(table_name),
lg.WARNING,
)
return pd.DataFrame([])
tbpiv = tbstr.pivot_table(
index=["Archetype", "RowName"],
columns="ColumnName",
values="Value",
aggfunc=lambda x: " ".join(x),
)
tbpiv.replace({"N/A": np.nan}, inplace=True)
return (
tbpiv.reset_index()
.groupby(["Archetype", "Zone Name"])
.agg(lambda x: pd.to_numeric(x, errors="ignore").sum())
)
def nominal_ventilation(df):
"""Nominal Ventilation.
References:
* `Nominal Ventilation Table \
<https://bigladdersoftware.com/epx/docs/8-9/output-details-and \
-examples/eplusout-sql.html#nominalventilation-table>`_
"""
df = get_from_tabulardata(df)
report_name = "Initialization Summary"
table_name = "ZoneVentilation Airflow Stats Nominal"
tbstr = df[
(df.ReportName == report_name) & (df.TableName == table_name)
].reset_index()
if tbstr.empty:
log(
"Table {} does not exist. "
"Returning an empty DataFrame".format(table_name),
lg.WARNING,
)
return pd.DataFrame([])
tbpiv = tbstr.pivot_table(
index=["Archetype", "RowName"],
columns="ColumnName",
values="Value",
aggfunc=lambda x: " ".join(x),
)
tbpiv = tbpiv.replace({"N/A": np.nan}).apply(
lambda x: pd.to_numeric(x, errors="ignore")
)
tbpiv = (
tbpiv.reset_index()
.groupby(["Archetype", "Zone Name", "Fan Type {Exhaust;Intake;Natural}"])
.apply(nominal_ventilation_aggregation)
)
return tbpiv
# .reset_index().groupby(['Archetype', 'Zone Name']).agg(
# lambda x: pd.to_numeric(x, errors='ignore').sum())
def nominal_ventilation_aggregation(x):
"""Aggregate the ventilation objects whithin a single zone_loads name.
Implies that .groupby(['Archetype', 'Zone Name']) is performed before calling
this function).
Args:
x:
Returns:
A DataFrame with at least one entry per ('Archetype', 'Zone Name'),
aggregated accordingly.
"""
how_dict = {
"Name": top(x["Name"], x, "Zone Floor Area {m2}"),
"Schedule Name": top(x["Schedule Name"], x, "Zone Floor Area {m2}"),
"Zone Floor Area {m2}": top(
x["Zone Floor Area {m2}"], x, "Zone Floor Area {m2}"
),
"# Zone Occupants": top(x["# Zone Occupants"], x, "Zone Floor Area {m2}"),
"Design Volume Flow Rate {m3/s}": weighted_mean(
x["Design Volume Flow Rate {m3/s}"], x, "Zone Floor Area {m2}"
),
"Volume Flow Rate/Floor Area {m3/s/m2}": weighted_mean(
x.filter(like="Volume Flow Rate/Floor Area").squeeze(axis=1),
x,
"Zone Floor Area {m2}",
),
"Volume Flow Rate/person Area {m3/s/person}": weighted_mean(
x.filter(like="Volume Flow Rate/person Area").squeeze(axis=1),
x,
"Zone Floor " "Area {m2}",
),
"ACH - Air Changes per Hour": weighted_mean(
x["ACH - Air Changes per Hour"], x, "Zone Floor Area {m2}"
),
"Fan Pressure Rise {Pa}": weighted_mean(
x["Fan Pressure Rise {Pa}"], x, "Zone Floor Area {m2}"
),
"Fan Efficiency {}": weighted_mean(
x["Fan Efficiency {}"], x, "Zone Floor Area {m2}"
),
"Equation A - Constant Term Coefficient {}": top(
x["Equation A - Constant Term Coefficient {}"], x, "Zone Floor Area {m2}"
),
"Equation B - Temperature Term Coefficient {1/C}": top(
x["Equation B - Temperature Term Coefficient {1/C}"],
x,
"Zone Floor Area {m2}",
),
"Equation C - Velocity Term Coefficient {s/m}": top(
x["Equation C - Velocity Term Coefficient {s/m}"], x, "Zone Floor Area {m2}"
),
"Equation D - Velocity Squared Term Coefficient {s2/m2}": top(
x["Equation D - Velocity Squared Term Coefficient {s2/m2}"],
x,
"Zone Floor Area {m2}",
),
"Minimum Indoor Temperature{C}/Schedule": top(
x["Minimum Indoor Temperature{C}/Schedule"], x, "Zone Floor Area {m2}"
),
"Maximum Indoor Temperature{C}/Schedule": top(
x["Maximum Indoor Temperature{C}/Schedule"], x, "Zone Floor Area {m2}"
),
"Delta Temperature{C}/Schedule": top(
x["Delta Temperature{C}/Schedule"], x, "Zone Floor Area {m2}"
),
"Minimum Outdoor Temperature{C}/Schedule": top(
x["Minimum Outdoor Temperature{C}/Schedule"], x, "Zone Floor Area {m2}"
),
"Maximum Outdoor Temperature{C}/Schedule": top(
x["Maximum Outdoor Temperature{C}/Schedule"], x, "Zone Floor Area {m2}"
),
"Maximum WindSpeed{m/s}": top(
x["Maximum WindSpeed{m/s}"], x, "Zone Floor Area {m2}"
),
}
try:
df = pd.DataFrame(how_dict, index=range(0, 1)) # range should always be
# one since we are trying to merge zones
except Exception as e:
log("{}".format(e))
else:
return df
def get_from_tabulardata(results):
"""Return a DataFrame from the 'TabularDataWithStrings' table.
A MultiIndex is returned with names ['Archetype', 'Index'].
"""
tab_data_wstring = pd.concat(
[value["TabularDataWithStrings"] for value in results.values()],
keys=results.keys(),
names=["Archetype"],
)
tab_data_wstring.index.names = ["Archetype", "Index"] #
# strip whitespaces
tab_data_wstring.Value = tab_data_wstring.Value.str.strip()
tab_data_wstring.RowName = tab_data_wstring.RowName.str.strip()
return tab_data_wstring