# coding=utf-8
"""Definition of window opening for ventilative cooling."""
from __future__ import division
from .control import VentilationControl
from ..reader import parse_idf_string
from ..writer import generate_idf_string
from honeybee._lockable import lockable
from honeybee.typing import float_in_range, float_positive, valid_string, valid_ep_string
[docs]
@lockable
class VentilationFan(object):
"""Definition of a fan for ventilative cooling.
This fan is not connected to any heating or cooling system and is meant to
represent the intentional circulation of unconditioned outdoor air for the
purposes of keeping a space cooler, drier or free of indoor pollutants (as in
the case of kitchen or bathroom exhaust fans).
Args:
identifier: Text string for a unique VentilationFan ID. Must be < 100 characters
and not contain any EnergyPlus special characters. This will be used to
identify the object across a model and in the exported IDF.
flow_rate: A positive number for the flow rate of the fan in m3/s.
ventilation_type: Text to indicate the type of type of ventilation. Choose
from the options below. For either Exhaust or Intake, values for
fan pressure and efficiency define the fan electric consumption. For 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. (Default: Balanced).
* Exhaust
* Intake
* Balanced
pressure_rise: A number for the the pressure rise across the fan in Pascals
(N/m2). This is often a function of the fan speed and the conditions in
which the fan is operating since having the fan blow air through filters
or narrow ducts will increase the pressure rise that is needed to
deliver the input flow rate. The pressure rise plays an important role in
determining the amount of energy consumed by the fan. Smaller fans like
a 0.05 m3/s desk fan tend to have lower pressure rises around 60 Pa.
Larger fans, such as a 6 m3/s fan used for ventilating a large room tend
to have higher pressure rises around 400 Pa. The highest pressure rises
are typically for large fans blowing air through ducts and filters, which
can have pressure rises as high as 1000 Pa. If this input is None,
the pressure rise will be estimated from the flow_rate, with higher
flow rates corresponding to larger pressure rises (up to 400 Pa). These
estimated pressure rises are generally assumed to have minimal obstructions
between the fan and the room and they should be increased if the fan is
blowing air through ducts or filters. (Default: None).
efficiency: A number between 0 and 1 for the overall efficiency of the fan.
Specifically, this is the ratio of the power delivered to the fluid
to the electrical input power. It is the product of the fan motor
efficiency and the fan impeller efficiency. Fans that have a higher blade
diameter and operate at lower speeds with smaller pressure rises for
their size tend to have higher efficiencies. Because motor efficiencies
are typically between 0.8 and 0.9, the best overall fan efficiencies
tend to be around 0.7 with most typical fan efficiencies between 0.5 and
0.7. The lowest efficiencies often happen for small fans in situations
with high pressure rises for their size, which can result in efficiencies
as low as 0.15. If None, this input will be estimated from the fan
flow rate and pressure rise with large fans operating at low pressure
rises for their size having up to 0.7 efficiency and small fans
operating at high pressure rises for their size having as low as
0.15 efficiency. (Default: None).
control: A VentilationControl object that dictates the conditions under
which the fan is turned on. If None, a default VentilationControl will
be generated, which will keep the fan on all of the time. (Default: None).
Properties:
* identifier
* display_name
* flow_rate
* ventilation_type
* pressure_rise
* efficiency
* control
"""
__slots__ = (
'_identifier', '_display_name', '_flow_rate', '_ventilation_type',
'_pressure_rise', '_efficiency', '_control', '_locked')
# types of ventilation
VENTILATION_TYPES = ('Exhaust', 'Intake', 'Balanced')
# values relating flow rates to pressure rises
PRESSURE_RISES = (
(0.01, 10),
(0.05, 60),
(0.25, 120),
(0.5, 200),
(2, 300),
(6, 400)
)
def __init__(self, identifier, flow_rate, ventilation_type='Balanced',
pressure_rise=None, efficiency=None, control=None):
"""Initialize VentilationControl."""
self._locked = False # unlocked by default
self.identifier = identifier
self._display_name = None
self.flow_rate = flow_rate
self.ventilation_type = ventilation_type
self.pressure_rise = pressure_rise
self.efficiency = efficiency
self.control = control
@property
def identifier(self):
"""Get or set the text string for object identifier."""
return self._identifier
@identifier.setter
def identifier(self, identifier):
self._identifier = valid_ep_string(identifier)
@property
def display_name(self):
"""Get or set a string for the object name without any character restrictions.
If not set, this will be equal to the identifier.
"""
if self._display_name is None:
return self._identifier
return self._display_name
@display_name.setter
def display_name(self, value):
if value is not None:
try:
value = str(value)
except UnicodeEncodeError: # Python 2 machine lacking the character set
pass # keep it as unicode
self._display_name = value
@property
def flow_rate(self):
"""Get or set a number for the fan flow rate in m3/s."""
return self._flow_rate
@flow_rate.setter
def flow_rate(self, value):
self._flow_rate = float_positive(value, 'fan flow rate')
@property
def ventilation_type(self):
"""Get or set text to indicate the type of ventilation.
Choose from the following options:
* Exhaust
* Intake
* Balanced
"""
return self._ventilation_type
@ventilation_type.setter
def ventilation_type(self, value):
clean_input = valid_string(value).lower()
for key in self.VENTILATION_TYPES:
if key.lower() == clean_input:
value = key
break
else:
raise ValueError(
'ventilation_type {} is not recognized.\nChoose from the '
'following:\n{}'.format(value, self.VENTILATION_TYPES))
self._ventilation_type = value
@property
def pressure_rise(self):
"""Get or set a number for the fan flow rate in m3/s."""
if self._pressure_rise is not None:
return self._pressure_rise
return self._default_pressure_rise()
@pressure_rise.setter
def pressure_rise(self, value):
if value is not None:
value = float_positive(value, 'fan pressure rise')
self._pressure_rise = value
@property
def efficiency(self):
"""Get or set a number between 0 and 1 for the fan efficiency."""
if self._efficiency is not None:
return self._efficiency
return self._default_efficiency()
@efficiency.setter
def efficiency(self, value):
if value is not None:
value = float_in_range(value, 0, 1, 'fan efficiency')
self._efficiency = value
@property
def control(self):
"""Get or set a VentilationControl object to dictate when the fan comes on."""
return self._control
@control.setter
def control(self, value):
if value is not None:
assert isinstance(value, VentilationControl), 'Expected VentilationControl' \
' object for Fan control. Got {}'.format(type(value))
else:
value = VentilationControl()
self._control = value
[docs]
@classmethod
def from_idf(cls, idf_string, schedule_dict):
"""Create a VentilationFan object from an EnergyPlus IDF text string.
Note that the ZoneVentilation:DesignFlowRate idf_string must use
the 'Flow/Zone' method in order to be successfully imported.
Args:
idf_string: A text string fully describing an EnergyPlus
ZoneVentilation:DesignFlowRate definition.
schedule_dict: A dictionary with schedule identifiers as keys and honeybee
schedule objects as values (either ScheduleRuleset or
ScheduleFixedInterval). These will be used to assign the schedules to
the VentilationControl object that governs the control of the fan.
Returns:
A tuple with two elements
- fan: An VentilationFan object loaded from the idf_string.
- zone_id: The identifier of the zone to which the
VentilationFan object should be assigned.
"""
# check the inputs
ep_strs = parse_idf_string(idf_string, 'ZoneVentilation:DesignFlowRate,')
# check the inputs
assert len(ep_strs) >= 9, 'ZoneVentilation:DesignFlowRate does not contain ' \
'enough information to be loaded to a VentilationFan.'
assert ep_strs[3].lower() == 'flow/zone', 'ZoneVentilation:DesignFlowRate ' \
'must use Flow/Zone method to be loaded to a VentilationFan.'
assert ep_strs[8] != '' and ep_strs[8].lower() != 'natural', \
'ZoneVentilation:DesignFlowRate cannot use Natural ventilation ' \
'to be loaded to a VentilationFan.'
# extract the properties from the string
sched = None
flow = 0
vt = 'Balanced'
pressure = 0
eff = 1
min_in_t = -100
max_in_t = 100
delta_t = -100
min_out_t = -100
max_out_t = 100
try:
sched = ep_strs[2] if ep_strs[2] != '' else None
flow = ep_strs[4] if ep_strs[4] != '' else 0
vt = ep_strs[8] if ep_strs[8] != '' else 'Balanced'
pressure = ep_strs[9] if ep_strs[9] != '' else 0
eff = ep_strs[10] if ep_strs[10] != '' else 1
min_in_t = ep_strs[15] if ep_strs[15] != '' else -100
max_in_t = ep_strs[17] if ep_strs[17] != '' else 100
delta_t = ep_strs[19] if ep_strs[19] != '' else -100
min_out_t = ep_strs[21] if ep_strs[21] != '' else -100
max_out_t = ep_strs[23] if ep_strs[23] != '' else 100
except IndexError:
pass # shorter IDF definition lacking specifications
# extract the schedules from the string
try:
if sched is not None:
sched = schedule_dict[sched]
except KeyError as e:
raise ValueError('Failed to find {} in the schedule_dict.'.format(e))
control = VentilationControl(
min_in_t, max_in_t, min_out_t, max_out_t, delta_t, sched)
# return the fan object and the zone identifier for the fan object
obj_id = ep_strs[0].split('..')[0]
zone_id = ep_strs[2]
fan = cls(obj_id, flow, vt, pressure, eff, control)
return fan, zone_id
[docs]
@classmethod
def from_dict(cls, data):
"""Create a VentilationFan from a dictionary.
Args:
data: A python dictionary in the following format
.. code-block:: python
{
"type": 'VentilationFan',
"flow_rate": 1.0,
"ventilation_type": "Exhaust",
"pressure_rise": 200,
"efficiency": 0.7,
"control": {} # dictionary of a VentilationControl
}
"""
assert data['type'] == 'VentilationFan', \
'Expected VentilationFan. Got {}.'.format(data['type'])
vt, pr, eff = cls._default_dict_values(data)
ctrl = VentilationControl.from_dict(data['control']) \
if 'control' in data and data['control'] is not None else None
new_obj = cls(data['identifier'], data['flow_rate'], vt, pr, eff, ctrl)
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
return new_obj
[docs]
@classmethod
def from_dict_abridged(cls, data, schedule_dict):
"""Create a VentilationFan from an abridged dictionary.
Args:
data: A VentilationFanAbridged dictionary with the format below.
schedule_dict: A dictionary with schedule identifiers as keys and
honeybee schedule objects as values. These will be used to
assign the schedule to the VentilationControl object.
.. code-block:: python
{
"type": 'VentilationFanAbridged',
"flow_rate": 1.0,
"ventilation_type": "Exhaust",
"pressure_rise": 200,
"efficiency": 0.7,
"control": {} # dictionary of a VentilationControlAbridged
}
"""
assert data['type'] == 'VentilationFanAbridged', \
'Expected VentilationFanAbridged. Got {}.'.format(data['type'])
vt, pr, eff = cls._default_dict_values(data)
ctrl = VentilationControl.from_dict_abridged(data['control'], schedule_dict) \
if 'control' in data and data['control'] is not None else None
new_obj = cls(data['identifier'], data['flow_rate'], vt, pr, eff, ctrl)
if 'display_name' in data and data['display_name'] is not None:
new_obj.display_name = data['display_name']
return new_obj
[docs]
def to_idf(self, zone_identifier):
"""IDF string representation of VentilationFan object.
Note that this method does not return full definitions of the VentilationControl
schedules and so this objects's schedules must also be translated into
the final IDF file.
Args:
zone_identifier: Text for the zone identifier that the VentilationFan
object is assigned to.
.. code-block::
ZoneVentilation:DesignFlowRate,
Ventilation 1, !- Name
ZONE 2, !- Zone Name
Simple Vent, !- Schedule Name
Flow/Zone, !- Design Volume Flow Rate calculation method
6.131944, !- Design Volume Flow Rate {m3/s}
, !- Flow Rate per Floor Area {m3/s-m2}
, !- Flow Rate per Person {m3/s-person}
, !- Air Changes per Hour
INTAKE, !- Ventilation Type
400.0, !- Fan Pressure Rise{Pa}
0.9, !- Fan Total Efficiency
0.6060000 , !- Constant Term Coefficient
2.0199999E-02, !- Temperature Term Coefficient
5.9800001E-04, !- Velocity Term Coefficient
0.0000000E+00!- Velocity Squared Term Coefficient
18.0, !- Minimum Indoor Temperature {C}
, !- Minimum Indoor Temperature Schedule Name
, !- Maximum Indoor Temperature {C}
, !- Maximum Indoor Temperature Schedule Name
1.0; !- Delta temperature {C}
"""
# process the properties on this object into IDF format
cntrl = self.control
values = (
'{}..{}'.format(self.identifier, zone_identifier), zone_identifier,
cntrl.schedule.identifier, 'Flow/Zone', self.flow_rate, '', '', '',
self.ventilation_type, self.pressure_rise, self.efficiency, '1', '', '', '',
cntrl.min_indoor_temperature, '', cntrl.max_indoor_temperature, '',
cntrl.delta_temperature, '',
cntrl.min_outdoor_temperature, '', cntrl.max_outdoor_temperature, '', 40)
comments = (
'name', 'zone name', 'schedule', 'flow calculation method',
'flow rate {m3/s}', 'flow per floor {m3/s-m2}',
'flow per person {m3/s-person}' 'flow ach', 'ventilation type',
'fan pressure rise', 'fan total efficiency', 'constant term',
'temperature term', 'velocity term', 'velocity squared term',
'min indoor temperature {C}', 'min in temp schedule',
'max indoor temperature {C}', 'max in temp schedule',
'delta temperature {C}', 'delta temp schedule',
'min outdoor temperature {C}', 'min out temp schedule',
'max outdoor temperature {C}', 'max out temp schedule', 'max wind speed')
return generate_idf_string(
'ZoneVentilation:DesignFlowRate', values, comments)
[docs]
def to_dict(self, abridged=False):
"""Ventilation Fan dictionary representation."""
base = {'type': 'VentilationFan'} if not \
abridged else {'type': 'VentilationFanAbridged'}
base['identifier'] = self.identifier
base['flow_rate'] = self.flow_rate
base['ventilation_type'] = self.ventilation_type
base['pressure_rise'] = self.pressure_rise
base['efficiency'] = self.efficiency
base['control'] = self.control.to_dict(abridged)
if self._display_name is not None:
base['display_name'] = self.display_name
return base
[docs]
def lock(self):
"""The lock() method will also lock the control."""
self._locked = True
self._control.lock()
[docs]
def unlock(self):
"""The unlock() method will also unlock the control."""
self._locked = False
self._control.unlock()
[docs]
def duplicate(self):
"""Get a copy of this object."""
return self.__copy__()
def _default_pressure_rise(self):
"""Calculate the pressure rise from the assigned flow rate."""
if self._flow_rate < self.PRESSURE_RISES[0][0]:
return self.PRESSURE_RISES[0][1]
if self._flow_rate > self.PRESSURE_RISES[-1][0]:
return self.PRESSURE_RISES[-1][1]
for i, (flow, pr) in enumerate(self.PRESSURE_RISES):
if flow <= self._flow_rate <= self.PRESSURE_RISES[i + 1][0]:
f_num = self._flow_rate - flow
f_denom = self.PRESSURE_RISES[i + 1][0] - flow
f_dist = f_num / f_denom
return pr + (f_dist * (self.PRESSURE_RISES[i + 1][1] - pr))
def _default_efficiency(self):
"""Calculate the efficiency from the assigned flow rate and pressure rise."""
pr_rise_ratio = self.pressure_rise / self._default_pressure_rise()
eff_est = 0.8 - (pr_rise_ratio * 0.1)
if eff_est > 0.7:
return 0.7
if eff_est < 0.15:
return 0.15
return eff_est
@staticmethod
def _default_dict_values(data):
"""Process dictionary values and include defaults for missing values."""
vt = data['ventilation_type'] if 'ventilation_type' in data \
and data['ventilation_type'] is not None else 'Balanced'
pr = data['pressure_rise'] if 'pressure_rise' in data else None
eff = data['efficiency'] if 'efficiency' in data else None
return vt, pr, eff
def __copy__(self):
new_obj = VentilationFan(
self._identifier, self._flow_rate, self._ventilation_type,
self._pressure_rise, self._efficiency, self._control.duplicate())
new_obj._display_name = self._display_name
return new_obj
def __key(self):
"""A tuple based on the object properties, useful for hashing."""
return (self.identifier, self.flow_rate, self.ventilation_type,
self.pressure_rise, self.efficiency, hash(self.control))
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
return isinstance(other, VentilationFan) and self.__key() == other.__key()
def __ne__(self, other):
return not self.__eq__(other)
[docs]
def ToString(self):
"""Overwrite .NET ToString."""
return self.__repr__()
def __repr__(self):
return 'VentilationFan: {}'.format(self.display_name)