Source code for eureca_building.air_handling_unit


"""
This module includes the class to manage the Air Handling Unit
"""

__author__ = "Enrico Prataviera"
__credits__ = ["Enrico Prataviera"]
__license__ = "MIT"
__version__ = "0.1"
__maintainer__ = "Enrico Prataviera"


'''IMPORTING MODULES'''


import sys
import logging
import copy
import numpy as np

from eureca_building.fluids_properties import air_properties, vapour_properties
from eureca_building.schedule import Schedule
from eureca_building.schedule_properties import ventilation_prop
from eureca_building.ventilation import MechanicalVentilation
from eureca_building.weather import WeatherFile
from eureca_building.exceptions import (
    ScheduleOutsideBoundaryCondition,
    PropertyOutsideBoundaries
)

#%%---------------------------------------------------------------------------------------------------
# AHU class
[docs]class AirHandlingUnit: '''This class manages the air handling unit. Some general variables are set as class variables while the __init__ memorizes the inputs ''' # Class Variables cp_air = air_properties["specific_heat"] # [J/(kg K)] p_atm = air_properties["atmospheric_pressure"] # [Pa] r_0 = vapour_properties["latent_heat"] # [J/kg] cpv = vapour_properties["specific_heat"] # [J/(kg K)]
[docs] def __init__(self, name: str, mechanical_vent: MechanicalVentilation, supply_temperature: Schedule, supply_specific_humidity: Schedule, ahu_operation: Schedule, humidity_control: bool, sensible_heat_recovery_eff: float, latent_heat_recovery_eff: float, outdoor_air_ratio: float, weather: WeatherFile, thermal_zone, tag: str = None, ): """Air Handling Unit Constructor: creates the AHU object and memorizes the attributes (using properties set methods tho check types) Parameters ---------- name : str name of the Air Handling Unit mechanical_vent : eureca_building.ventilation.MechanicalVentilation ventialation object to define air flow rate supply_temperature : eureca_building.schedule.Schedule Schedule object supply_specific_humidity : eureca_building.schedule.Schedule Schedule object ahu_operation : eureca_building.schedule.Schedule Schedule object to define opeartion (-1 cooling, 1 heating, 0 fan mode) humidity_control : bool whether do humidification/dehumidification sensible_heat_recovery_eff : float sensible heat recovery efficiency, must be between 0 and 1 latent_heat_recovery_eff : float sensible heat recovery efficiency, must be between 0 and 1 outdoor_air_ratio : float outdoor air fraction, must be between 0 and 1 weather : eureca_building.weather.WeatherFile Weather object thermal_zone : eureca_building.thermal_zone.ThermalZone ThermalZone object tag : str possible tags Raises ------ TypeError checks the input type ValueError checks the input type """ # Check input data type if not isinstance(name, str): raise TypeError(f'ERROR AHU inizialization, name must be an string: Name {name}') # Inizialization self.ahu_name = name self.mechanical_ventilation = mechanical_vent self.supply_temperature = supply_temperature self.supply_specific_humidity = supply_specific_humidity self.ahu_operation = (ahu_operation, weather) self.humidity_control = humidity_control self.sensible_heat_recovery_eff = sensible_heat_recovery_eff self.latent_heat_recovery_eff = latent_heat_recovery_eff self.outdoor_air_ratio = outdoor_air_ratio self.tag = tag self.air_flow_rate_kg_S, self.vapour_flow_rate_kg_S = self.mechanical_ventilation.get_flow_rate(weather, volume = thermal_zone._volume, area = thermal_zone._net_floor_area) # Association of AHU to thermal zone try: thermal_zone.add_air_handling_unit(self, weather) except AttributeError: raise TypeError(f'ERROR AHU inizialization, thermal zone must be an ThermalZone object: thermal_zone {type(thermal_zone)}')
@property def supply_temperature(self): return self._supply_temperature @supply_temperature.setter def supply_temperature(self, value): if not isinstance(value, Schedule): raise ValueError(f"Air Handling Unit object {self.ahu_name}, supply_temperature not Schedule: {type(value)}") if np.any(np.less(value.schedule, ventilation_prop["mechanical"]['temperature limit'][0])): logging.warning( f"Air Handling Unit object {self.ahu_name}, supply temperature schedule goes below {ventilation_prop['mechanical']['temperature limit'][0]} °C") if np.any(np.greater(value.schedule, ventilation_prop["mechanical"]['temperature limit'][1])): logging.warning( f"Air Handling Unit object {self.ahu_name}, supply temperature schedule goes above {ventilation_prop['mechanical']['temperature limit'][1]} °C") self._supply_temperature = copy.deepcopy(value) @property def supply_specific_humidity(self): return self._supply_specific_humidity @supply_specific_humidity.setter def supply_specific_humidity(self, value): if not isinstance(value, Schedule): raise ValueError(f"Air Handling Unit object {self.ahu_name}, supply_specific_humidity not Schedule: {type(value)}") if np.any(np.less(value.schedule, ventilation_prop["mechanical"]['specific humidity limit'][0])): logging.warning( f"Air Handling Unit object {self.ahu_name}, supply specific humidity schedule goes below {ventilation_prop['mechanical']['specific humidity limit'][0]} kg_v/kg_as") if np.any(np.greater(value.schedule, ventilation_prop["mechanical"]['specific humidity limit'][1])): logging.warning( f"Air Handling Unit object {self.ahu_name}, supply specific humidity schedule goes above {ventilation_prop['mechanical']['specific humidity limit'][1]} kg_v/kg_as") self._supply_specific_humidity = copy.deepcopy(value) @property def ahu_operation(self): return self._ahu_operation @ahu_operation.setter def ahu_operation(self, value): sched = value[0] weather = value[1] if not isinstance(sched, Schedule): raise ValueError(f"Air Handling Unit object {self.ahu_name}, ahu_operation not Schedule: {type(sched)}") if np.any(np.not_equal(sched.schedule,1)*np.not_equal(sched.schedule,0)*np.not_equal(sched.schedule,-1)): raise ScheduleOutsideBoundaryCondition( f"Air Handling Unit object {self.ahu_name}, ahu_operation schedule with values not allowed: allowed values = [-1,0,1]") self._ahu_operation = sched # Needed to solve correctly thermal zone in free cooling ext_air_db_temp = weather.hourly_data['out_air_db_temperature'] ext_air_spec_hum = weather.hourly_data['out_air_specific_humidity'] filt = self._ahu_operation.schedule == 0 self.supply_temperature.schedule[filt] = ext_air_db_temp[filt] self.supply_specific_humidity.schedule[filt] = ext_air_spec_hum[filt] @property def humidity_control(self): return self._humidity_control @humidity_control.setter def humidity_control(self, value): if not isinstance(value, bool): raise ValueError(f"Air Handling Unit object {self.ahu_name}, humidity control not bool: {type(value)}") self._humidity_control = value @property def sensible_heat_recovery_eff(self): return self._sensible_heat_recovery_eff @sensible_heat_recovery_eff.setter def sensible_heat_recovery_eff(self, value): if not isinstance(value, float): raise ValueError(f"Air Handling Unit object {self.ahu_name}, sensible_heat_recovery_eff not float: {type(value)}") if value > 1. or value < 0.: raise PropertyOutsideBoundaries( f"Air Handling Unit object {self.ahu_name}, sensible_heat_recovery_eff in range [0.,1]: {type(value)}") self._sensible_heat_recovery_eff = value @property def latent_heat_recovery_eff(self): return self._latent_heat_recovery_eff @latent_heat_recovery_eff.setter def latent_heat_recovery_eff(self, value): if not isinstance(value, float): raise ValueError( f"Air Handling Unit object {self.ahu_name}, latent_heat_recovery_eff not float: {type(value)}") if value > 1. or value < 0.: raise PropertyOutsideBoundaries( f"Air Handling Unit object {self.ahu_name}, latent_heat_recovery_eff in range [0.,1]: {type(value)}") self._latent_heat_recovery_eff = value @property def outdoor_air_ratio(self): return self._outdoor_air_ratio @outdoor_air_ratio.setter def outdoor_air_ratio(self, value): if not isinstance(value, float): raise ValueError( f"Air Handling Unit object {self.ahu_name}, outdoor_air_ratio not float: {type(value)}") if value > 1. or value < 0.: raise PropertyOutsideBoundaries( f"Air Handling Unit object {self.ahu_name}, outdoor_air_ratio in range [0.,1]: {type(value)}") self._outdoor_air_ratio = value
[docs] def air_handling_unit_calc(self, t, weather, T_int, x_int, ): """Solution of the time step calculation. It uses outdoor conditions (from WeatherFile), and zone conditions (from zone) Parameters ---------- t : int timestep: int [-] weather : eureca_building.weather.WeatherFile WeatherFile object T_int : float zone internal temperature: float [°C] x_int : float zone internal specific humidity: float [kg_v/kg_da] """ # Check input data type if not isinstance(t, int): raise TypeError(f'ERROR AHUCalc, bd {self.ahu_name}, time step {t}, input t is not an interger: t {t}') T_ext = weather.hourly_data['out_air_db_temperature'][t] x_ext = weather.hourly_data['out_air_specific_humidity'][t] self._chart_T_ext, self._chart_x_ext = T_ext, x_ext self._chart_T_zone, self._chart_x_zone = T_int, x_int AHU_operation = self.ahu_operation.schedule[t] self.T_sup = self.supply_temperature.schedule[t] self.x_sup = self.supply_specific_humidity.schedule[t] m_vent = self.air_flow_rate_kg_S[t] OutAirRatio = self.outdoor_air_ratio # Saturation conditions check sat_cond, psat = self.checkSatCond(T_ext,x_ext,self.p_atm) if sat_cond == False: x_ext = 0.99*0.622*psat/(self.p_atm-0.99*psat) if AHU_operation == 0: self.x_sup = x_ext sat_cond, psat = self.checkSatCond(T_int,x_int,self.p_atm) if sat_cond == False: logging.warning(f'ERROR AHUCalc method, bd {self.ahu_name}, time step {t}, Zone conditions outside saturation limit') sat_cond, psat = self.checkSatCond(self.T_sup,self.x_sup,self.p_atm) if sat_cond == False: logging.warning(f'ERROR: AHUCalc method, bd {self.ahu_name}, time step {t}, Supply conditions outside saturation limit') # Pre-processing on Heat Recovery and Mixer # Enthalpy in J/(kg) self.h_ext = self.cp_air*T_ext + (self.r_0 + self.cpv*T_ext)*x_ext self.h_z = self.cp_air*T_int + (self.r_0 + self.cpv*T_int)*x_int # Heat Recovery Bypass in condition of free heating or cooling if (T_int-T_ext)*AHU_operation > 0: self.T_hr = T_ext + self.sensible_heat_recovery_eff*(T_int-T_ext) self.T_out = T_int - self.sensible_heat_recovery_eff*(T_int-T_ext) else: self.T_hr = T_ext self.T_out = T_int # This is the case of free floating (sets the outdoor air ratio to 1) OutAirRatio = 1 if (x_int-x_ext)*AHU_operation > 0: self.x_hr = x_ext + self.latent_heat_recovery_eff*(x_int-x_ext) else: self.x_hr = x_ext # Enthalpy in J/(kg) self.h_hr = self.cp_air*self.T_hr +(self.r_0 + self.cpv*self.T_hr)*self.x_hr # Mixer # Enthalpy in J/(kg) self.h_mix = OutAirRatio*self.h_hr + (1 - OutAirRatio)*self.h_z self.x_mix = OutAirRatio*self.x_hr + (1 - OutAirRatio)*x_int self.T_mix = (self.h_mix - self.r_0*self.x_mix)/(self.cp_air + self.cpv*self.x_mix) # BATTERIES DEMAND CALCULATION # SENSIBLE AND LATENT CONTROL MODE if self.humidity_control == True: # Heating mode if AHU_operation == 1: # Adiabatic Saturator self.x_as = self.x_sup p_as = self.p_atm*self.x_as/(0.622+self.x_as) if p_as >= 610.5: self.T_as = 237.3*np.log(p_as/610.5)/(17.269-np.log(p_as/610.5)) else: self.T_as = 265.5*np.log(p_as/610.5)/(21.875-np.log(p_as/610.5)) # Enthalpy in J/(kg) self.h_as = self.cp_air*self.T_as + (self.r_0 + self.cpv*self.T_as)*self.x_as # Pre-Heater control if self.h_mix < self.h_as: self.h_ph = self.h_as self.x_ph = self.x_mix self.T_ph = (self.h_ph-self.r_0*self.x_ph)/(self.cp_air+self.cpv*self.x_ph) else: # In this case the Pre-Heater is by-passed self.h_ph = self.h_mix self.x_ph = self.x_mix self.T_ph = self.T_mix # Enthalpy in J/(kg) self.h_as = self.h_ph # Humidification need check if self.x_ph > self.x_sup: self.x_as = self.x_ph self.x_sup = self.x_as self.T_as = (self.h_as - self.r_0*self.x_as)/(self.cp_air+self.cpv*self.x_as) # Controlling T_sup > T_as if self.T_sup < self.T_as: self.T_sup = self.T_as # Enthalpy in J/(kg) self.h_sup = self.cp_air*self.T_sup+(self.r_0+self.cpv*self.T_sup)*self.x_sup # Batteries Demand [W] # Pre-Heater self.preh_deu_Dem = m_vent*(self.h_ph-self.h_mix) self.preh_deu_Dem_sens = m_vent*self.cp_air*(self.T_ph-self.T_mix) self.preh_deu_Dem_lat = m_vent*self.r_0*(self.x_ph-self.x_mix) # Adiabatic Saturator self.sat_Dem = m_vent*(self.h_as-self.h_ph) self.sat_Dem_lat = m_vent*self.r_0*(self.x_as-self.x_ph) self.sat_Dem_sens = self.sat_Dem-self.sat_Dem_lat # Post-Heater self.posth_Dem = m_vent*(self.h_sup-self.h_as) self.posth_Dem_sens = m_vent*self.cp_air*(self.T_sup-self.T_as) self.posth_Dem_lat = m_vent*self.r_0*(self.x_sup - self.x_as) # Total Demand self.AHU_demand = self.preh_deu_Dem + self.sat_Dem + self.posth_Dem self.AHU_demand_sens = self.preh_deu_Dem_sens + self.sat_Dem_sens + self.posth_Dem_sens self.AHU_demand_lat = self.preh_deu_Dem_lat + self.sat_Dem_lat + self.posth_Dem_lat # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_ph, self.x_ph self._chart_T_as, self._chart_x_as = self.T_as, self.x_as self._chart_T_posth, self._chart_x_posth = self.T_sup, self.x_sup # Cooling mode elif AHU_operation == -1: # Dehumidificator self.x_de = self.x_sup p_de = self.p_atm*self.x_de/(0.622+self.x_de) if p_de >= 610.5: self.T_de = 237.3*np.log(p_de/610.5)/(17.269-np.log(p_de/610.5)) else: self.T_de = 265.5*np.log(p_de/610.5)/(21.875-np.log(p_de/610.5)) # Dehumidifcator and post-heater control if self.x_de >= self.x_mix: self.x_de = self.x_mix self.x_sup = self.x_de if self.T_sup <= self.T_mix: self.T_de = self.T_sup else: self.T_de = self.T_mix # self.T_sup = self.T_de # Why??????? self.h_de = self.cp_air*self.T_de+(self.r_0+self.cpv*self.T_de)*self.x_de self.h_sup = self.cp_air*self.T_sup+(self.r_0+self.cpv*self.T_sup)*self.x_sup # Batteries Demand [kW] # Dehumidificator self.preh_deu_Dem = m_vent*(self.h_de - self.h_mix) self.preh_deu_Dem_sens = m_vent*self.cp_air*(self.T_de - self.T_mix) self.preh_deu_Dem_lat = m_vent*self.r_0*(self.x_de - self.x_mix) self.sat_Dem = 0. self.sat_Dem_lat = 0. self.sat_Dem_sens = 0. # Post-Heater self.posth_Dem = m_vent*(self.h_sup - self.h_de) self.posth_Dem_sens = m_vent*self.cp_air*(self.T_sup - self.T_de) self.posth_Dem_lat = m_vent*self.r_0*(self.x_sup - self.x_de) # Total Demand self.AHU_demand = self.preh_deu_Dem + self.posth_Dem self.AHU_demand_sens = self.preh_deu_Dem_sens + self.posth_Dem_sens self.AHU_demand_lat = self.preh_deu_Dem_lat + self.posth_Dem_lat # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_de, self.x_de self._chart_T_as, self._chart_x_as = self.T_de, self.x_de self._chart_T_posth, self._chart_x_posth = self.T_sup, self.x_sup elif AHU_operation == 0: # Plant Off self.T_hr, self.T_mix, self.T_sup = T_ext, T_ext, T_ext self.x_hr, self.x_mix, self.x_sup = x_ext, x_ext, x_ext self.h_hr, self.h_mix, self.h_sup = self.h_ext, self.h_ext, self.h_ext # Pre-Heater self.preh_deu_Dem = 0. self.preh_deu_Dem_sens = 0. self.preh_deu_Dem_lat = 0. # Adiabatic Saturator self.sat_Dem = 0. self.sat_Dem_lat = 0. self.sat_Dem_sens = 0. # Post-Heater self.posth_Dem = 0. self.posth_Dem_sens = 0. self.posth_Dem_lat = 0. # Batteries Demand [kW] self.AHU_demand = 0 self.AHU_demand_sens = 0 self.AHU_demand_lat = 0 # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_mix, self.x_mix self._chart_T_as, self._chart_x_as = self.T_mix, self.x_mix self._chart_T_posth, self._chart_x_posth = self.T_mix, self.x_mix else: print('AHUOnOff value not allowed') # SENSIBLE CONTROL MODE if self.humidity_control == False: # Heating mode if AHU_operation == 1: # Pre-Heater and Adiabatic Saturator doesn't exist!! self.h_ph = self.h_mix self.x_ph = self.x_mix self.T_ph = self.T_mix self.h_as = self.h_ph self.x_as = self.x_ph self.T_as = self.T_ph # Post-Heater if self.T_sup <self.T_as: self.T_sup = self.T_as self.x_sup = self.x_as self.h_sup = self.cp_air*self.T_sup+(self.r_0+self.cpv*self.T_sup)*self.x_sup # Pre-Heater self.preh_deu_Dem = 0. self.preh_deu_Dem_sens = 0. self.preh_deu_Dem_lat = 0. # Adiabatic Saturator self.sat_Dem = 0. self.sat_Dem_lat = 0. self.sat_Dem_sens = 0. # Post-Heater self.posth_Dem = m_vent * self.cp_air * (self.T_sup - self.T_as) self.posth_Dem_sens = m_vent * self.cp_air * (self.T_sup - self.T_as) self.posth_Dem_lat = 0. # Batteries Demand [kW] self.AHU_demand = m_vent*self.cp_air*(self.T_sup-self.T_as) self.AHU_demand_sens = self.AHU_demand self.AHU_demand_lat = 0 # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_ph, self.x_ph self._chart_T_as, self._chart_x_as = self.T_as, self.x_as self._chart_T_posth, self._chart_x_posth = self.T_sup, self.x_sup elif AHU_operation == -1: # Cooling mode # Pre-Heater and Adiabatic Saturator doesn't exist!! self.h_ph = self.h_mix self.x_ph = self.x_mix self.T_ph = self.T_mix self.h_as = self.h_ph self.x_as = self.x_ph self.T_as = self.T_ph if self.T_sup > self.T_mix: self.T_sup = self.T_mix self.x_sup = self.x_mix self.h_sup = self.h_mix else: SatCond, psat = self.checkSatCond(self.T_sup,self.x_mix,self.p_atm) if SatCond == False: # print('Info: supply temperature is too low -- changed') if self.T_sup < 0: p_sat = 610.5*np.exp((21.875*self.T_sup)/(265.5+self.T_sup)) else: p_sat = 610.5*np.exp((17.269*self.T_sup)/(237.3+self.T_sup)) self.x_sup = 0.99*0.622*p_sat/(self.p_atm-0.99*p_sat) self.h_sup = self.cp_air*self.T_sup+(self.r_0+self.cpv*self.T_sup)*self.x_sup else: self.x_sup= self.x_mix self.h_sup = self.cp_air*self.T_sup+(self.r_0+self.cpv*self.T_sup)*self.x_sup # Pre-Heater self.preh_deu_Dem = 0. self.preh_deu_Dem_sens = 0. self.preh_deu_Dem_lat = 0. # Adiabatic Saturator self.sat_Dem = 0. self.sat_Dem_lat = 0. self.sat_Dem_sens = 0. # Post-Heater self.posth_Dem = m_vent * self.cp_air * (self.T_sup - self.T_as) self.posth_Dem_sens = m_vent * self.cp_air * (self.T_sup - self.T_as) self.posth_Dem_lat = 0. # Batteries Demand [kW] self.AHU_demand = m_vent*(self.h_sup - self.h_mix) self.AHU_demand_sens = self.AHU_demand self.AHU_demand_lat = 0 # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_de, self.x_de self._chart_T_as, self._chart_x_as = self.T_de, self.x_de self._chart_T_posth, self._chart_x_posth = self.T_sup, self.x_sup elif AHU_operation == 0: # Plant OFF self.T_hr, self.T_mix, self.T_sup = T_ext, T_ext, T_ext self.x_hr, self.x_mix, self.x_sup = x_ext, x_ext, x_ext self.h_hr, self.h_mix, self.h_sup = self.h_ext, self.h_ext, self.h_ext # Pre-Heater self.preh_deu_Dem = 0. self.preh_deu_Dem_sens = 0. self.preh_deu_Dem_lat = 0. # Adiabatic Saturator self.sat_Dem = 0. self.sat_Dem_lat = 0. self.sat_Dem_sens = 0. # Post-Heater self.posth_Dem = 0. self.posth_Dem_sens = 0. self.posth_Dem_lat = 0. # Batteries Demand [kW] self.AHU_demand = 0 self.AHU_demand_sens = 0 self.AHU_demand_lat = 0 # Conditions for chart self._chart_T_hr, self._chart_x_hr = self.T_hr, self.x_hr self._chart_T_mix, self._chart_x_mix = self.T_mix, self.x_mix self._chart_T_preh_deu, self._chart_x_preh_deu = self.T_mix, self.x_mix self._chart_T_as, self._chart_x_as = self.T_mix, self.x_mix self._chart_T_posth, self._chart_x_posth = self.T_mix, self.x_mix else: sys.exit('AHUOnOff value not allowed at time step: '+str(t))
[docs] def properties(self): """ Just a function to print the memorized conditions """ return f""" HR :\tT {self._chart_T_hr:.1f} °C,\tx {self._chart_x_hr:.5f} kg/kg,\th {self.h_hr:.1f} J/kg MIX:\tT {self._chart_T_mix:.1f} °C,\tx {self._chart_x_mix:.5f} kg/kg,\th {self.h_mix:.1f} J/kg PRE:\tT {self._chart_T_preh_deu:.1f} °C,\tx {self._chart_x_preh_deu:.5f} kg/kg,\th {self.h_ph:.1f} J/kg AS :\tT {self._chart_T_as:.1f} °C,\tx {self._chart_x_as:.5f} kg/kg,\th {self.h_as:.1f} J/kg SUP:\tT {self._chart_T_posth:.1f} °C,\tx {self._chart_x_posth:.5f} kg/kg,\th {self.h_sup:.1f} J/kg AHU_SENS:\t{self.AHU_demand_sens} W AHU_LAT:\t{self.AHU_demand_lat} W AHU_TOT:\t{self.AHU_demand} W """
#%%--------------------------------------------------------------------------------------------------- #%%
[docs] def checkSatCond(self, temp, x, p): ''' Check Saturation Condition This function takes as inputs temperature [°C] and humidity ratio [kg_vap/kg_as] to check if a point is outside saturation conditions Parameters ---------- temp : float Temperature [°C] x : float Specific Humidity [kg_vap/kg_as]. p : float Pressure [Pa]. Returns ------- tuple boolean (wheter saturation is reached), and Saturation Pressure [Pa]. ''' # Check input data type if not isinstance(temp, float): raise TypeError(f'ERROR input T is not an interger: T {temp}') if not isinstance(x, float): raise TypeError(f'ERROR input x is not an interger: x {x}') # Control input data quality if temp < -40 or temp > 70: logging.warning( f"WARNING CheckSatCond function, input temperature outside limit boundary [-15,60]: T {temp}" ) if x < 0.0005 or x > 0.040: logging.warning(f"WARNING CheckSatCond function, input humidity outside limit boundary [0.0005,0.04]: x {x}") # Is or not outside the saturation condition? True/False pp = p * x / (0.622 + x) if temp < 0: psat = 610.5 * np.exp((21.875 * temp) / (265.5 + temp)) else: psat = 610.5 * np.exp((17.269 * temp) / (237.3 + temp)) if pp - psat > 0.01: sat_cond = False else: sat_cond = True return sat_cond, psat
def _psychro_plot(self): """ Just a function to get a psychrometric chart (internal use only) """ try: import matplotlib.pyplot as plt except ModuleNotFoundError: raise ModuleNotFoundError("To run the AirHandlingUnit._psychro_plot you need to install matplotlib package") fig, ax = plt.subplots() ax.set_ylabel("Specific Humidity [" + "$g_{v}/kg_{da}$" + "]") ax.set_xlabel("Temperature [" + "$°C$" + "]") ax.set_xlim(-10, 45) ax.set_ylim(0, 30) ax.set_title("Psychrometric chart") t = np.arange(-10, 46, 1) p_sat = 6.1094 * np.exp(17.625 * t / (t + 243.04)) * 100 for ur in np.arange(0.1, 1.1, 0.1): p = p_sat * ur sh = 0.622 * (p / (101325 - p)) * 1000 ax.plot(t, sh, 'k-', linewidth=0.3) x_text = min([t[-1] - 5, 35]) y_text = min([sh[-1] - 2, 28]) ax.text(27.7, 26.9, f"100%", backgroundcolor="white", fontsize=6, ma="center") ax.text(30.5, 23.7, f"80%", backgroundcolor="white", fontsize=6, ma="center") ax.text(32.5, 20., f"60%", backgroundcolor="white", fontsize=6, ma="center") ax.text(34.5, 14.5, f"40%", backgroundcolor="white", fontsize=6, ma="center") ax.text(35.8, 8, f"20%", backgroundcolor="white", fontsize=6, ma="center") x_text, y_text = -9., 3.5 for h in np.arange(0., 200., 10.): x = (h - 1.006 * t) / (1.86 * t + 2501) * 1000 ax.plot(t, x, 'k:', linewidth=0.3) if y_text < 30.: ax.text(x_text, y_text, f"{h:.0f}" + " [" + "$kJ/kg_{da}$" + "]", backgroundcolor="white", fontsize=6) x_text += 3 y_text += 2.7 self._psychro_chart = (fig, ax)
[docs] def print_psychro_chart(self): """ Just a function to print the psychrometric chart with current transformations """ if not hasattr(self, '_psychro_chart'): self._psychro_plot() fig, ax = self._psychro_chart self.values = np.array([ [self._chart_T_ext, self._chart_x_ext*1000], [self._chart_T_hr, self._chart_x_hr*1000], [self._chart_T_mix, self._chart_x_mix*1000], [self._chart_T_preh_deu, self._chart_x_preh_deu*1000], [self._chart_T_as, self._chart_x_as*1000], [self._chart_T_posth, self._chart_x_posth*1000]]) self.values_tz = np.array([ [self._chart_T_zone, self._chart_x_zone*1000], [self._chart_T_mix, self._chart_x_mix*1000] ]) ax.plot(self.values[:,0], self.values[:,1], 'r-o', fillstyle="none") ax.plot(self.values_tz[:,0], self.values_tz[:,1], 'k--o',linewidth = 0.6, fillstyle="none") try: import matplotlib.pyplot as plt except ModuleNotFoundError: raise ModuleNotFoundError("To run the AirHandlingUnit._psychro_plot you need to install matplotlib package") plt.show()