lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb in openstudio-standards-0.1.3 vs lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb in openstudio-standards-0.1.4

- old
+ new

@@ -14,65 +14,65 @@ # TODO enable damper position adjustment for legacy IDFS if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004' OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "Damper positions not modified for DOE Ref Pre-1980 or DOE Ref 1980-2004 vintages.") return true end - + # First time adjustment: # Only applies to multi-zone vav systems - # exclusion: for Outpatient: (1) both AHU1 and AHU2 in 'DOE Ref Pre-1980' and 'DOE Ref 1980-2004' + # exclusion: for Outpatient: (1) both AHU1 and AHU2 in 'DOE Ref Pre-1980' and 'DOE Ref 1980-2004' # (2) AHU1 in 2004-2013 if self.is_multizone_vav_system && !(self.name.to_s.include? "Outpatient F1") self.adjust_minimum_vav_damper_positions end - + # Second time adjustment: # Only apply to 2010 and 2013 Outpatient (both AHU1 and AHU2) # TODO maybe apply to hospital as well? if (self.name.to_s.include? "Outpatient") && (template == '90.1-2010' || template == '90.1-2013') self.adjust_minimum_vav_damper_positions_outpatient end - + return true - - end + end + # Apply all standard required controls to the airloop # # @param (see #is_economizer_required) # @return [Bool] returns true if successful, false if not # @todo optimum start # @todo night damper shutoff # @todo nightcycle control # @todo night fan shutoff def apply_standard_controls(template, climate_zone) - + # Energy Recovery Ventilation if self.is_energy_recovery_ventilator_required(template, climate_zone) self.apply_energy_recovery_ventilator end - + # Economizers self.set_economizer_limits(template, climate_zone) - self.set_economizer_integration(template, climate_zone) - + self.set_economizer_integration(template, climate_zone) + # Multizone VAV Systems if self.is_multizone_vav_system - + # VAV Reheat Control self.set_vav_damper_action(template) - + # Multizone VAV Optimization # This rule does not apply to two hospital and one outpatient systems (TODO add hospital two systems as exception) if !(self.name.to_s.include? "Outpatient F1") if self.is_multizone_vav_optimization_required(template, climate_zone) self.enable_multizone_vav_optimization else self.disable_multizone_vav_optimization end end - + # VAV Static Pressure Reset # assume all systems have DDC control of VAV terminals has_ddc = true if self.is_static_pressure_reset_required(template, has_ddc) self.supply_return_exhaust_relief_fans.each do |fan| @@ -81,13 +81,13 @@ else OpenStudio::logFree(OpenStudio::Error, "openstudio.standards.AirLoopHVAC","For #{self.name}: there is a constant volume fan on a multizone vav system. Cannot apply static pressure reset controls.") end end end - + end - + # Single zone systems # if self.thermalZones.size == 1 # self.apply_single_zone_controls(template, climate_zone) # end @@ -106,72 +106,72 @@ # TODO Prototype buildings use OAT-based SAT reset, # but PRM RM suggests Warmest zone based SAT reset. if self.is_supply_air_temperature_reset_required(template, climate_zone) self.enable_supply_air_temperature_reset_outdoor_temperature # self.enable_supply_air_temperature_reset_warmest_zone(template) - end - + end + # Unoccupied shutdown if self.is_unoccupied_fan_shutoff_required(template) self.enable_unoccupied_fan_shutoff else self.setAvailabilitySchedule(self.model.alwaysOnDiscreteSchedule) end - + # Motorized OA damper if self.is_motorized_oa_damper_required(template, climate_zone) # Assume that the availability schedule has already been # set to reflect occupancy and use this for the OA damper. self.add_motorized_oa_damper(0.15, self.availabilitySchedule) else self.remove_motorized_oa_damper end - + # TODO Optimum Start # for systems exceeding 10,000 cfm # Don't think that OS will be able to do this. # OS currently only allows 1 availability manager - # at a time on an AirLoopHVAC. If we add an - # AvailabilityManager:OptimumStart, it + # at a time on an AirLoopHVAC. If we add an + # AvailabilityManager:OptimumStart, it # will replace the AvailabilityManager:NightCycle. - - end + end + # Apply all PRM baseline required controls to the airloop. # Only applies those controls that differ from the normal # prescriptive controls, which are added via # AirLoopHVAC.apply_standard_controls # # @param (see #is_economizer_required) # @return [Bool] returns true if successful, false if not def apply_performance_rating_method_baseline_controls(template, climate_zone) - + # Economizers if self.is_performance_rating_method_baseline_economizer_required(template, climate_zone) self.apply_performance_rating_method_baseline_economizer(template, climate_zone) end # Multizone VAV Systems if self.is_multizone_vav_system - # SAT Reset + # SAT Reset # G3.1.3.12 SAT reset required for all Multizone VAV systems, # even if not required by prescriptive section. case template when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' self.enable_supply_air_temperature_reset_warmest_zone(template) end end - + # Unoccupied shutdown self.enable_unoccupied_fan_shutoff - + return true - - end + end + # Calculate and apply the performance rating method # baseline fan power to this air loop. # Fan motor efficiency will be set, and then # fan pressure rise adjusted so that the # fan power is the maximum allowable. @@ -180,22 +180,22 @@ # # @todo Figure out how to split fan power between multiple fans # if the proposed model had multiple fans (supply, return, exhaust, etc.) # return [Bool] true if successful, false if not. def set_performance_rating_method_baseline_fan_power(template) - + # Main AHU fans - + # Calculate the allowable fan motor bhp # for the entire airloop. allowable_fan_bhp = self.allowable_system_brake_horsepower(template) # Divide the allowable power evenly between the fans # on this airloop. - all_fans = self.supply_return_exhaust_relief_fans - allowable_fan_bhp = allowable_fan_bhp / all_fans.size - + all_fans = self.supply_return_exhaust_relief_fans + allowable_fan_bhp = allowable_fan_bhp / all_fans.size + # Set the motor efficiencies # for all fans based on the calculated # allowed brake hp. Then calculate the allowable # fan power for each fan and adjust # the fan pressure rise accordingly @@ -204,31 +204,31 @@ allowable_power_w = allowable_fan_bhp * 746 / fan.motorEfficiency fan.adjust_pressure_rise_to_meet_fan_power(allowable_power_w) end # Fan powered terminal fans - + # Adjust each terminal fan self.demandComponents.each do |dc| next if dc.to_AirTerminalSingleDuctParallelPIUReheat.empty? pfp_term = dc.to_AirTerminalSingleDuctParallelPIUReheat.get pfp_term.set_performance_rating_method_baseline_fan_power(template) end - + return true - + end - + # Determine the fan power limitation pressure drop adjustment # Per Table 6.5.3.1.1B # # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' # @return [Double] fan power limitation pressure drop adjustment # units = horsepower # @todo Determine the presence of MERV filters and other stuff in Table 6.5.3.1.1B. May need to extend AirLoopHVAC data model def fan_power_limitation_pressure_drop_adjustment_brake_horsepower(template = "ASHRAE 90.1-2007") - + # Get design supply air flow rate (whether autosized or hard-sized) dsn_air_flow_m3_per_s = 0 dsn_air_flow_cfm = 0 if self.autosizedDesignSupplyAirFlowRate.is_initialized dsn_air_flow_m3_per_s = self.autosizedDesignSupplyAirFlowRate.get @@ -236,44 +236,44 @@ OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "* #{dsn_air_flow_cfm.round} cfm = Autosized Design Supply Air Flow Rate.") else dsn_air_flow_m3_per_s = self.designSupplyAirFlowRate.get dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, "m^3/s", "cfm").get OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "* #{dsn_air_flow_cfm.round} cfm = Hard sized Design Supply Air Flow Rate.") - end - + end + # TODO determine the presence of MERV filters and other stuff # in Table 6.5.3.1.1B # perhaps need to extend AirLoopHVAC data model has_fully_ducted_return_and_or_exhaust_air_systems = false - + # Calculate Fan Power Limitation Pressure Drop Adjustment (in wc) fan_pwr_adjustment_in_wc = 0 - + # Fully ducted return and/or exhaust air systems if has_fully_ducted_return_and_or_exhaust_air_systems adj_in_wc = 0.5 fan_pwr_adjustment_in_wc += adj_in_wc OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC","--Added #{adj_in_wc} in wc for Fully ducted return and/or exhaust air systems") end - + # Convert the pressure drop adjustment to brake horsepower (bhp) # assuming that all supply air passes through all devices fan_pwr_adjustment_bhp = fan_pwr_adjustment_in_wc*dsn_air_flow_cfm / 4131 OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC","For #{self.name}: Fan Power Limitation Pressure Drop Adjustment = #{(fan_pwr_adjustment_bhp.round(2))} bhp") - + return fan_pwr_adjustment_bhp - + end # Determine the allowable fan system brake horsepower # Per Table 6.5.3.1.1A # # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' # @return [Double] allowable fan system brake horsepower # units = horsepower def allowable_system_brake_horsepower(template = "ASHRAE 90.1-2007") - + # Get design supply air flow rate (whether autosized or hard-sized) dsn_air_flow_m3_per_s = 0 dsn_air_flow_cfm = 0 if self.autosizedDesignSupplyAirFlowRate.is_initialized dsn_air_flow_m3_per_s = self.autosizedDesignSupplyAirFlowRate.get @@ -285,14 +285,14 @@ OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "* #{dsn_air_flow_cfm.round} cfm = Hard sized Design Supply Air Flow Rate.") end # Get the fan limitation pressure drop adjustment bhp fan_pwr_adjustment_bhp = self.fan_power_limitation_pressure_drop_adjustment_brake_horsepower - + # Determine the number of zones the system serves num_zones_served = self.thermalZones.size - + # Get the supply air fan and determine whether VAV or CAV system. # Assume that supply air fan is fan closest to the demand outlet node. # The fan may be inside of a piece of unitary equipment. fan_pwr_limit_type = nil self.supplyComponents.reverse.each do |comp| @@ -312,53 +312,53 @@ if fan.to_FanConstantVolume.is_initialized || comp.to_FanOnOff.is_initialized fan_pwr_limit_type = "constant volume" elsif fan.to_FanVariableVolume.is_initialized fan_pwr_limit_type = "variable volume" end - end + end end - - # For 90.1-2010, single-zone VAV systems use the + + # For 90.1-2010, single-zone VAV systems use the # constant volume limitation per 6.5.3.1.1 if template == "ASHRAE 90.1-2010" && fan_pwr_limit_type = "variable volume" && num_zones_served == 1 fan_pwr_limit_type = "constant volume" OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC","For #{self.name}: Using the constant volume limitation because single-zone VAV system.") end - + # Calculate the Allowable Fan System brake horsepower per Table G3.1.2.9 allowable_fan_bhp = 0 if fan_pwr_limit_type == "constant volume" allowable_fan_bhp = dsn_air_flow_cfm*0.00094+fan_pwr_adjustment_bhp elsif fan_pwr_limit_type == "variable volume" allowable_fan_bhp = dsn_air_flow_cfm*0.0013+fan_pwr_adjustment_bhp end OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC","For #{self.name}: Allowable brake horsepower = #{(allowable_fan_bhp).round(2)}HP based on #{dsn_air_flow_cfm.round} cfm and #{fan_pwr_adjustment_bhp.round(2)} bhp of adjustment.") - + # Calculate and report the total area for debugging/testing floor_area_served_m2 = self.floor_area_served floor_area_served_ft2 = OpenStudio.convert(floor_area_served_m2, 'm^2', 'ft^2').get cfm_per_ft2 = dsn_air_flow_cfm / floor_area_served_ft2 cfm_per_hp = dsn_air_flow_cfm / allowable_fan_bhp OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC","For #{self.name}: area served = #{floor_area_served_ft2.round} ft^2.") OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC","For #{self.name}: flow per area = #{cfm_per_ft2.round} cfm/ft^2.") OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC","For #{self.name}: flow per hp = #{cfm_per_hp.round} cfm/hp.") - + return allowable_fan_bhp end # Get all of the supply, return, exhaust, and relief fans on this system # # @return [Array] an array of FanConstantVolume, FanVariableVolume, and FanOnOff objects - def supply_return_exhaust_relief_fans() - + def supply_return_exhaust_relief_fans() + # Fans on the supply side of the airloop directly, or inside of unitary equipment. fans = [] sup_and_oa_comps = self.supplyComponents sup_and_oa_comps += self.oaComponents sup_and_oa_comps.each do |comp| - if comp.to_FanConstantVolume.is_initialized + if comp.to_FanConstantVolume.is_initialized fans << comp.to_FanConstantVolume.get elsif comp.to_FanVariableVolume.is_initialized fans << comp.to_FanVariableVolume.get elsif comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.is_initialized sup_fan = comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.get.supplyAirFan @@ -374,37 +374,37 @@ if sup_fan.to_FanConstantVolume.is_initialized fans << sup_fan.to_FanConstantVolume.get elsif sup_fan.to_FanOnOff.is_initialized fans << sup_fan.to_FanOnOff.get elsif sup_fan.to_FanVariableVolume.is_initialized - fans << sup_fan.to_FanVariableVolume.get - end + fans << sup_fan.to_FanVariableVolume.get + end end - end - + end + return fans - + end - + # Determine the total brake horsepower of the fans on the system # with or without the fans inside of fan powered terminals. # # @param include_terminal_fans [Bool] if true, power from fan powered terminals will be included # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' # @return [Double] total brake horsepower of the fans on the system - # units = horsepower + # units = horsepower def system_fan_brake_horsepower(include_terminal_fans = true, template = "ASHRAE 90.1-2007") # TODO get the template from the parent model itself? # Or not because maybe you want to see the difference between two standards? OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC","#{self.name}-Determining #{template} allowable system fan power.") - + # Get all fans fans = [] # Supply, exhaust, relief, and return fans fans += self.supply_return_exhaust_relief_fans - + # Fans inside of fan-powered terminals if include_terminal_fans self.demandComponents.each do |comp| if comp.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized term_fan = comp.to_AirTerminalSingleDuctSeriesPIUReheat.get.supplyAirFan @@ -413,80 +413,80 @@ end elsif comp.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized term_fan = comp.to_AirTerminalSingleDuctParallelPIUReheat.get.fan if term_fan.to_FanConstantVolume.is_initialized fans << term_fan.to_FanConstantVolume.get - end + end end end end - + # Loop through all fans on the system and # sum up their brake horsepower values. sys_fan_bhp = 0 fans.sort.each do |fan| sys_fan_bhp += fan.brakeHorsepower end - + return sys_fan_bhp - - end - + + end + # Set the fan pressure rises that will result in # the system hitting the baseline allowable fan power # - # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' + # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' def set_baseline_fan_pressure_rise(template = "ASHRAE 90.1-2007") OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "#{self.name}-Setting #{template} baseline fan power.") - + # Get the total system bhp from the proposed system, including terminal fans proposed_sys_bhp = self.system_fan_brake_horsepower(true) - + # Get the allowable fan brake horsepower allowable_fan_bhp = self.allowable_system_brake_horsepower(template) # Get the fan power limitation from proposed system fan_pwr_adjustment_bhp = self.fan_power_limitation_pressure_drop_adjustment_brake_horsepower - + # Subtract the fan power adjustment allowable_fan_bhp = allowable_fan_bhp-fan_pwr_adjustment_bhp - + # Get all fans - fans = self.supply_return_exhaust_relief_fans - + fans = self.supply_return_exhaust_relief_fans + # TODO improve description # Loop through the fans, changing the pressure rise # until the fan bhp is the same percentage of the baseline allowable bhp # as it was on the proposed system. fans.each do |fan| # TODO: Yixing Check the model of the Fan Coil Unit next if fan.name.to_s.include?("Fan Coil fan") next if fan.name.to_s.include?("UnitHeater Fan") OpenStudio::logFree(OpenStudio::Info, "#{fan.name}") - + # Get the bhp of the fan on the proposed system proposed_fan_bhp = fan.brakeHorsepower - + # Get the bhp of the fan on the proposed system proposed_fan_bhp_frac = proposed_fan_bhp / proposed_sys_bhp - + # Determine the target bhp of the fan on the baseline system baseline_fan_bhp = proposed_fan_bhp_frac*allowable_fan_bhp OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "* #{(baseline_fan_bhp).round(1)} bhp = Baseline fan brake horsepower.") - - # Set the baseline impeller eff of the fan, + + # Set the baseline impeller eff of the fan, # preserving the proposed motor eff. baseline_impeller_eff = fan.baselineImpellerEfficiency(template) fan.changeImpellerEfficiency(baseline_impeller_eff) OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "* #{(baseline_impeller_eff*100).round(1)}% = Baseline fan impeller efficiency.") - + # Set the baseline motor efficiency for the specified bhp baseline_motor_eff = fan.standardMinimumMotorEfficiency(template, standards, allowable_fan_bhp) fan.changeMotorEfficiency(baseline_motor_eff) - + # Get design supply air flow rate (whether autosized or hard-sized) dsn_air_flow_m3_per_s = 0 if fan.autosizedDesignSupplyAirFlowRate.is_initialized dsn_air_flow_m3_per_s = fan.autosizedDesignSupplyAirFlowRate.get dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, "m^3/s", "cfm").get @@ -494,11 +494,11 @@ else dsn_air_flow_m3_per_s = fan.designSupplyAirFlowRate.get dsn_air_flow_cfm = OpenStudio.convert(dsn_air_flow_m3_per_s, "m^3/s", "cfm").get OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "* #{dsn_air_flow_cfm.round} cfm = User entered Design Supply Air Flow Rate.") end - + # Determine the fan pressure rise that will result in the target bhp # pressure_rise_pa = fan_bhp*746 / fan_motor_eff*fan_total_eff / dsn_air_flow_m3_per_s baseline_pressure_rise_pa = baseline_fan_bhp*746 / fan.motorEfficiency*fan.fanEfficiency / dsn_air_flow_m3_per_s baseline_pressure_rise_in_wc = OpenStudio.convert(fan_pressure_rise_pa, "Pa", "inH_{2}O",).get OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "* #{(fan_pressure_rise_in_wc).round(2)} in w.c. = Pressure drop to achieve allowable fan power.") @@ -508,11 +508,11 @@ if ((calc_bhp-baseline_fan_bhp) / baseline_fan_bhp).abs > 0.02 OpenStudio::logFree(OpenStudio::Error, "openstudio.standards.AirLoopHVAC", "#{fan.name} baseline fan bhp supposed to be #{baseline_fan_bhp}, but is #{calc_bhp}.") end end - + # Calculate the total bhp of the system to make sure it matches the goal calc_sys_bhp = self.system_fan_brake_horsepower(false) if ((calc_sys_bhp-allowable_fan_bhp) / allowable_fan_bhp).abs > 0.02 OpenStudio::logFree(OpenStudio::Error, "openstudio.standards.AirLoopHVAC", "#{self.name} baseline system bhp supposed to be #{allowable_fan_bhp}, but is #{calc_sys_bhp}.") end @@ -524,11 +524,11 @@ # @return [Double] total cooling capacity # units = Watts (W) # @todo Change to pull water coil nominal capacity instead of design load; not a huge difference, but water coil nominal capacity not available in sizing table. # @todo Handle all additional cooling coil types. Currently only handles CoilCoolingDXSingleSpeed, CoilCoolingDXTwoSpeed, and CoilCoolingWater def total_cooling_capacity - + # Sum the cooling capacity for all cooling components # on the airloop, which may be inside of unitary systems. total_cooling_capacity_w = 0 self.supplyComponents.each do |sc| # CoilCoolingDXSingleSpeed @@ -540,11 +540,11 @@ total_cooling_capacity_w += coil.autosizedRatedTotalCoolingCapacity.get else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingDXTwoSpeed - elsif sc.to_CoilCoolingDXTwoSpeed.is_initialized + elsif sc.to_CoilCoolingDXTwoSpeed.is_initialized coil = sc.to_CoilCoolingDXTwoSpeed.get if coil.ratedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedHighSpeedTotalCoolingCapacity.get elsif coil.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.autosizedRatedHighSpeedTotalCoolingCapacity.get @@ -558,11 +558,11 @@ total_cooling_capacity_w += coil.autosizedDesignCoilLoad.get else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingWaterToAirHeatPumpEquationFit - elsif sc.to_CoilCoolingWaterToAirHeatPumpEquationFit.is_initialized + elsif sc.to_CoilCoolingWaterToAirHeatPumpEquationFit.is_initialized coil = sc.to_CoilCoolingWaterToAirHeatPumpEquationFit.get if coil.ratedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedTotalCoolingCapacity.get elsif coil.autosizedRatedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.autosizedRatedTotalCoolingCapacity.get @@ -582,11 +582,11 @@ total_cooling_capacity_w += coil.autosizedRatedTotalCoolingCapacity.get else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingDXTwoSpeed - elsif clg_coil.to_CoilCoolingDXTwoSpeed.is_initialized + elsif clg_coil.to_CoilCoolingDXTwoSpeed.is_initialized coil = clg_coil.to_CoilCoolingDXTwoSpeed.get if coil.ratedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedHighSpeedTotalCoolingCapacity.get elsif coil.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.autosizedRatedHighSpeedTotalCoolingCapacity.get @@ -600,11 +600,11 @@ total_cooling_capacity_w += coil.autosizedDesignCoilLoad.get else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingWaterToAirHeatPumpEquationFit - elsif clg_coil.to_CoilCoolingWaterToAirHeatPumpEquationFit.is_initialized + elsif clg_coil.to_CoilCoolingWaterToAirHeatPumpEquationFit.is_initialized coil = clg_coil.to_CoilCoolingWaterToAirHeatPumpEquationFit.get if coil.ratedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedTotalCoolingCapacity.get elsif coil.autosizedRatedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.autosizedRatedTotalCoolingCapacity.get @@ -625,11 +625,11 @@ total_cooling_capacity_w += coil.autosizedRatedTotalCoolingCapacity.get else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingDXTwoSpeed - elsif clg_coil.to_CoilCoolingDXTwoSpeed.is_initialized + elsif clg_coil.to_CoilCoolingDXTwoSpeed.is_initialized coil = clg_coil.to_CoilCoolingDXTwoSpeed.get if coil.ratedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedHighSpeedTotalCoolingCapacity.get elsif coil.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.autosizedRatedHighSpeedTotalCoolingCapacity.get @@ -653,46 +653,46 @@ OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "#{self.name} has a cooling coil named #{sc.name}, whose type is not yet covered by economizer checks.") # CoilCoolingDXMultiSpeed # CoilCoolingCooledBeam # CoilCoolingWaterToAirHeatPumpEquationFit # AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass - # AirLoopHVACUnitaryHeatPumpAirToAir - # AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed + # AirLoopHVACUnitaryHeatPumpAirToAir + # AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed # AirLoopHVACUnitarySystem end end return total_cooling_capacity_w - + end - + # Determine whether or not this system # is required to have an economizer. # # @param template [String] valid choices: 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' # @param climate_zone [String] valid choices: 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-2B', # 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', # 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2006-5C', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', - # 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' + # 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' # @return [Bool] returns true if an economizer is required, false if not def is_economizer_required(template, climate_zone) - + economizer_required = false - + return economizer_required if self.name.to_s.include? "Outpatient F1" - + # A big number of btu per hr as the minimum requirement infinity_btu_per_hr = 999999999999 minimum_capacity_btu_per_hr = infinity_btu_per_hr - + # Determine if the airloop serves any computer rooms # / data centers, which changes the economizer. is_dc = false if self.data_center_area_served > 0 is_dc = true end - + # Determine the minimum capacity that requires an economizer case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' case climate_zone when 'ASHRAE 169-2006-1A', @@ -732,21 +732,21 @@ 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' - minimum_capacity_btu_per_hr = 135000 + minimum_capacity_btu_per_hr = 135000 when 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2006-5C', 'ASHRAE 169-2006-6B' minimum_capacity_btu_per_hr = 65000 end - else + else case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B' minimum_capacity_btu_per_hr = infinity_btu_per_hr # No requirement when 'ASHRAE 169-2006-2A', @@ -770,11 +770,11 @@ end end when 'NECB 2011' minimum_capacity_btu_per_hr = 68243 # NECB requires economizer for cooling cap > 20 kW end - + # Check whether the system requires an economizer by comparing # the system capacity to the minimum capacity. total_cooling_capacity_w = self.total_cooling_capacity total_cooling_capacity_btu_per_hr = OpenStudio.convert(total_cooling_capacity_w, "W", "Btu/hr").get if total_cooling_capacity_btu_per_hr >= minimum_capacity_btu_per_hr @@ -787,49 +787,49 @@ else if is_dc OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{self.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.") else OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{self.name} does not require an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr is less than the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.") - end + end end - + return economizer_required - + end - + # Set the economizer limits per the standard. Limits are based on the economizer # type currently specified in the ControllerOutdoorAir object on this air loop. # # @param (see #is_economizer_required) # @return [Bool] returns true if successful, false if not def set_economizer_limits(template, climate_zone) - + # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' # 'DifferentialEnthalpy' # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' - # 'DifferentialDryBulbAndEnthalpy' - + # 'DifferentialDryBulbAndEnthalpy' + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir economizer_type = oa_control.getEconomizerControlType - + # Return false if no economizer is present if economizer_type == 'NoEconomizer' return false end - + # Determine the limits according to the type drybulb_limit_f = nil enthalpy_limit_btu_per_lb = nil dewpoint_limit_f = nil case template @@ -892,12 +892,12 @@ enthalpy_limit_btu_per_lb = 28 when 'FixedDewPointAndDryBulb' drybulb_limit_f = 75 dewpoint_limit_f = 55 end - end - + end + # Set the limits case economizer_type when 'FixedDryBulb' if drybulb_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get @@ -916,25 +916,25 @@ dewpoint_limit_c = OpenStudio.convert(dewpoint_limit_f, 'F', 'C').get oa_control.setEconomizerMaximumLimitDryBulbTemperature(drybulb_limit_c) oa_control.setEconomizerMaximumLimitDewpointTemperature(dewpoint_limit_c) OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F, dew-point limit = #{dewpoint_limit_f}F") end - end + end return true - + end # For systems required to have an economizer, set the economizer # to integrated on non-integrated per the standard. # # @note this method assumes you previously checked that an economizer is required at all # via #is_economizer_required # @param (see #is_economizer_required) # @return [Bool] returns true if successful, false if not def set_economizer_integration(template, climate_zone) - + # Determine if the system is a VAV system based on the fan # which may be inside of a unitary system. is_vav = false self.supplyComponents.reverse.each do |comp| if comp.to_FanVariableVolume.is_initialized @@ -949,24 +949,24 @@ if fan.is_initialized if fan.get.to_FanVariableVolume.is_initialized is_vav = true end end - end + end end # Determine the number of zones the system serves num_zones_served = self.thermalZones.size - + # A big number of btu per hr as the minimum requirement infinity_btu_per_hr = 999999999999 minimum_capacity_btu_per_hr = infinity_btu_per_hr - + # Determine if an integrated economizer is required integrated_economizer_required = true case template - when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' + when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' minimum_capacity_btu_per_hr = 65000 minimum_capacity_w = OpenStudio.convert(minimum_capacity_btu_per_hr, "Btu/hr", "W").get # 6.5.1.3 Integrated Economizer Control # Exception a, DX VAV systems if is_vav == true && num_zones_served > 1 @@ -1005,47 +1005,47 @@ end when '90.1-2010', '90.1-2013' integrated_economizer_required = true when 'NECB 2011' # this means that compressor allowed to turn on when economizer is open - # (NoLockout); as per 5.2.2.8(3) + # (NoLockout); as per 5.2.2.8(3) integrated_economizer_required = true end - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end - oa_control = oa_sys.getControllerOutdoorAir - + oa_control = oa_sys.getControllerOutdoorAir + # Apply integrated or non-integrated economizer if integrated_economizer_required oa_control.setLockoutType('NoLockout') else oa_control.setLockoutType('LockoutWithCompressor') end return true - + end - + # Determine if an economizer is required per the PRM. # # @param (see #is_economizer_required) # @return [Bool] returns true if required, false if not def is_performance_rating_method_baseline_economizer_required(template, climate_zone) - + economizer_required = false - + # A big number of ft2 as the minimum requirement infinity_ft2 = 999999999999 min_int_area_served_ft2 = infinity_ft2 min_ext_area_served_ft2 = infinity_ft2 - + # Determine the minimum capacity that requires an economizer case template when '90.1-2004' case climate_zone when 'ASHRAE 169-2006-1A', @@ -1081,58 +1081,58 @@ 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A' min_int_area_served_ft2 = infinity_ft2 # No requirement min_ext_area_served_ft2 = infinity_ft2 # No requirement - else + else min_int_area_served_ft2 = 0 # Always required min_ext_area_served_ft2 = 0 # Always required end end - + # Check whether the system requires an economizer by comparing # the system capacity to the minimum capacity. min_int_area_served_m2 = OpenStudio.convert(min_int_area_served_ft2, "ft^2", "m^2").get min_ext_area_served_m2 = OpenStudio.convert(min_ext_area_served_ft2, "ft^2", "m^2").get - + # Get the interior and exterior area served int_area_served_m2 = self.floor_area_served_interior_zones ext_area_served_m2 = self.floor_area_served_exterior_zones - + # Check the floor area exception if int_area_served_m2 < min_int_area_served_m2 && ext_area_served_m2 < min_ext_area_served_m2 if min_int_area_served_ft2 == infinity_ft2 && min_ext_area_served_ft2 == infinity_ft2 OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer not required for climate zone #{climate_zone}.") else OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer not required for because the interior area served of #{int_area_served_m2} ft2 < minimum of #{min_int_area_served_m2} and the perimeter area served of #{ext_area_served_m2} ft2 < minimum of #{min_ext_area_served_m2} for climate zone #{climate_zone}.") end return economizer_required end - + # If here, economizer required economizer_required = true OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer required for the performance rating method baseline.") - - return economizer_required + return economizer_required + end - + # Apply the PRM economizer type and set temperature limits # # @param (see #is_economizer_required) # @return [Bool] returns true if successful, false if not def apply_performance_rating_method_baseline_economizer(template, climate_zone) - + # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' # 'DifferentialEnthalpy' # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' - # 'DifferentialDryBulbAndEnthalpy' + # 'DifferentialDryBulbAndEnthalpy' # Determine the type and limits economizer_type = nil drybulb_limit_f = nil enthalpy_limit_btu_per_lb = nil @@ -1176,37 +1176,37 @@ 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' - economizer_type = 'FixedDryBulb' + economizer_type = 'FixedDryBulb' drybulb_limit_f = 75 when 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A' economizer_type = 'FixedEnthalpy' enthalpy_limit_btu_per_lb = 28 when 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-7A' - economizer_type = 'FixedDryBulb' + economizer_type = 'FixedDryBulb' drybulb_limit_f = 70 else - economizer_type = 'FixedDryBulb' + economizer_type = 'FixedDryBulb' drybulb_limit_f = 65 end end - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir - + # Set the limits case economizer_type when 'FixedDryBulb' if drybulb_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get @@ -1225,49 +1225,49 @@ dewpoint_limit_c = OpenStudio.convert(dewpoint_limit_f, 'F', 'C').get oa_control.setEconomizerMaximumLimitDryBulbTemperature(drybulb_limit_c) oa_control.setEconomizerMaximumLimitDewpointTemperature(dewpoint_limit_c) OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F, dew-point limit = #{dewpoint_limit_f}F") end - end + end return true end - + # Check the economizer type currently specified in the ControllerOutdoorAir object on this air loop # is acceptable per the standard. # # @param (see #is_economizer_required) # @return [Bool] Returns true if allowable, if the system has no economizer or no OA system. # Returns false if the economizer type is not allowable. def is_economizer_type_allowable(template, climate_zone) - + # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' # 'DifferentialEnthalpy' # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' # 'DifferentialDryBulbAndEnthalpy' - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return true # No OA system end oa_control = oa_sys.getControllerOutdoorAir economizer_type = oa_control.getEconomizerControlType - + # Return true if no economizer is present if economizer_type == 'NoEconomizer' return true end - + # Determine the minimum capacity that requires an economizer prohibited_types = [] case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' case climate_zone @@ -1288,11 +1288,11 @@ 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A' prohibited_types = ['DifferentialDryBulb'] - when + when 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', prohibited_types = [] end when '90.1-2010', '90.1-2013' @@ -1314,69 +1314,88 @@ 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A' prohibited_types = ['FixedDryBulb', 'DifferentialDryBulb'] - when + when 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', prohibited_types = [] end end - + # Check if the specified type is allowed economizer_type_allowed = true if prohibited_types.include?(economizer_type) economizer_type_allowed = false end - + return economizer_type_allowed - + end - + # Check if ERV is required on this airloop. # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. # @todo Add exception logic for systems serving parking garage, warehouse, or multifamily def is_energy_recovery_ventilator_required(template, climate_zone) - - # ERV Not Applicable for AHUs that serve + # ERV Not Applicable for AHUs that serve # parking garage, warehouse, or multifamily # if space_types_served_names.include?('PNNL_Asset_Rating_Apartment_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_LowRiseApartment_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_ParkingGarage_Space_Type') || # space_types_served_names.include?('PNNL_Asset_Rating_Warehouse_Space_Type') # OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not applicable because it because it serves parking garage, warehouse, or multifamily.") # return false # end - + erv_required = nil # ERV not applicable for medical AHUs (AHU1 in Outpatient), per AIA 2001 - 7.31.D2. if self.name.to_s.include? "Outpatient F1" erv_required = false return erv_required end - + + # ERV not applicable for medical AHUs, per AIA 2001 - 7.31.D2. + if self.name.to_s.include? "VAV_ER" + erv_required = false + return erv_required + elsif self.name.to_s.include? "VAV_OR" + erv_required = false + return erv_required + end + case template + when '90.1-2004', '90.1-2007' + if self.name.to_s.include? "VAV_ICU" + erv_required = false + return erv_required + elsif self.name.to_s.include? "VAV_PATRMS" + erv_required = false + return erv_required + end + end + # ERV Not Applicable for AHUs that have DCV - # or that have no OA intake. + # or that have no OA intake. controller_oa = nil controller_mv = nil oa_system = nil if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation if controller_mv.demandControlledVentilation == true OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not applicable because DCV enabled.") return false end else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not applicable because it has no OA intake.") return false end + # Get the AHU design supply air flow rate dsn_flow_m3_per_s = nil if self.designSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = self.designSupplyAirFlowRate.get elsif self.autosizedDesignSupplyAirFlowRate.is_initialized @@ -1384,11 +1403,11 @@ else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name} design supply air flow rate is not available, cannot apply efficiency standard.") return false end dsn_flow_cfm = OpenStudio.convert(dsn_flow_m3_per_s, 'm^3/s', 'cfm').get - + # Get the minimum OA flow rate min_oa_flow_m3_per_s = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized @@ -1396,14 +1415,14 @@ else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.") return false end min_oa_flow_cfm = OpenStudio.convert(min_oa_flow_m3_per_s, 'm^3/s', 'cfm').get - + # Calculate the percent OA at design airflow pct_oa = min_oa_flow_m3_per_s/dsn_flow_m3_per_s - + case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004' erv_cfm = nil # Not required when '90.1-2004', '90.1-2007' if pct_oa < 0.7 @@ -1425,11 +1444,11 @@ erv_cfm = nil elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = nil elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 5000 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 5000 end when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-5C' if pct_oa < 0.3 erv_cfm = nil @@ -1441,11 +1460,11 @@ erv_cfm = 26000 elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = 12000 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 5000 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 4000 end when 'ASHRAE 169-2006-6B' if pct_oa < 0.3 erv_cfm = nil @@ -1457,13 +1476,13 @@ erv_cfm = 4500 elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = 3500 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 2500 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 1500 - end + end when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A' if pct_oa < 0.3 erv_cfm = nil elsif pct_oa >= 0.3 && pct_oa < 0.4 erv_cfm = 5500 @@ -1473,13 +1492,13 @@ erv_cfm = 3500 elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = 2000 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 1000 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 0 - end + end when 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' if pct_oa < 0.3 erv_cfm = nil elsif pct_oa >= 0.3 && pct_oa < 0.4 erv_cfm = 2500 @@ -1489,13 +1508,13 @@ erv_cfm = 0 elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = 0 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 0 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 0 - end + end end when '90.1-2013' # Table 6.5.6.1-2 case climate_zone when 'ASHRAE 169-2006-3C' @@ -1515,11 +1534,11 @@ erv_cfm = 4000 elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = 3000 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 1500 - elsif pct_oa >= 0.8 + elsif pct_oa >= 0.8 erv_cfm = 0 end when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-5B' if pct_oa < 0.1 erv_cfm = nil @@ -1541,122 +1560,122 @@ erv_cfm = 0 end end when 'NECB 2011' # The NECB 2011 requirement is that systems with an exhaust heat content > 150 kW require an HRV - # The calculation for this is done below, to modify erv_required + # The calculation for this is done below, to modify erv_required # erv_cfm set to nil here as placeholder, will lead to erv_required = false erv_cfm = nil end - + # Determine if an ERV is required # erv_required = nil if erv_cfm.nil? OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not required based on #{(pct_oa*100).round}% OA flow, design flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}.") - erv_required = false + erv_required = false elsif dsn_flow_cfm < erv_cfm OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not required based on #{(pct_oa*100).round}% OA flow, design flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Does not exceed minimum flow requirement of #{erv_cfm}cfm.") - erv_required = false + erv_required = false else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV required based on #{(pct_oa*100).round}% OA flow, design flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Exceeds minimum flow requirement of #{erv_cfm}cfm.") - erv_required = true + erv_required = true end - + # This code modifies erv_required for NECB 2011 # Calculation of exhaust heat content and check whether it is > 150 kW - - if template == 'NECB 2011' - + + if template == 'NECB 2011' + # get all zones in the model zones = self.thermalZones - + # initialize counters sum_zone_oa = 0.0 sum_zoneoaTimesheatDesignT = 0.0 - + # zone loop zones.each do |zone| - + # get design heat temperature for each zone; this is equivalent to design exhaust temperature zone_sizing = zone.sizingZone heatDesignTemp = zone_sizing.zoneHeatingDesignSupplyAirTemperature # initialize counter zone_oa = 0.0 # outdoor defined at space level; get OA flow for all spaces within zone spaces = zone.spaces - + # space loop spaces.each do |space| if not space.designSpecificationOutdoorAir.empty? # if empty, don't do anything - outdoor_air = space.designSpecificationOutdoorAir.get - + outdoor_air = space.designSpecificationOutdoorAir.get + # in bTAP, outdoor air specified as outdoor air per person (m3/s/person) oa_flow_per_person = outdoor_air.outdoorAirFlowperPerson num_people = space.peoplePerFloorArea * space.floorArea oa_flow = oa_flow_per_person * num_people # oa flow for the space zone_oa = zone_oa + oa_flow # add up oa flow for all spaces to get zone air flow - end - + end + end # space loop - + sum_zone_oa = sum_zone_oa + zone_oa # sum of all zone oa flows to get system oa flow sum_zoneoaTimesheatDesignT = sum_zoneoaTimesheatDesignT + (zone_oa * heatDesignTemp) # calculated to get oa flow weighted average of design exhaust temperature - + end # zone loop - + # Calculate average exhaust temperature (oa flow weighted average) - avg_exhaust_temp = sum_zoneoaTimesheatDesignT / sum_zone_oa - - # for debugging/testing + avg_exhaust_temp = sum_zoneoaTimesheatDesignT / sum_zone_oa + + # for debugging/testing # puts "average exhaust temp = #{avg_exhaust_temp}" # puts "sum_zone_oa = #{sum_zone_oa}" - + # Get January winter design temperature # get model weather file name weather_file = BTAP::Environment::WeatherFile.new(self.model.weatherFile.get.path.get) - + # get winter(heating) design temp stored in array # Note that the NECB 2011 specifies using the 2.5% january design temperature # The outdoor temperature used here is the 0.4% heating design temperature of the coldest month, available in stat file outdoor_temp = weather_file.heating_design_info[1] - + # for debugging/testing -# puts "outdoor design temp = #{outdoor_temp}" - +# puts "outdoor design temp = #{outdoor_temp}" + # Calculate exhaust heat content exhaust_heat_content = 0.00123 * sum_zone_oa * 1000.0 * (avg_exhaust_temp - outdoor_temp) - + # for debugging/testing # puts "exhaust heat content = #{exhaust_heat_content}" - - + + # Modify erv_required based on exhaust heat content if ( exhaust_heat_content > 150.0 ) then erv_required = true - OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV required based on exhaust heat content.") + OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV required based on exhaust heat content.") else erv_required = false - OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not required based on exhaust heat content.") + OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV not required based on exhaust heat content.") end - - - + + + end # of NECB 2011 condition - + # for debugging/testing -# puts "erv_required = #{erv_required}" - +# puts "erv_required = #{erv_required}" + return erv_required - - end - + + end + # Add an ERV to this airloop. # Will be a rotary-type HX # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. # @todo Add exception logic for systems serving parking garage, warehouse, or multifamily def apply_energy_recovery_ventilator() # Get the oa system oa_system = nil @@ -1664,11 +1683,11 @@ oa_system = self.airLoopHVACOutdoorAirSystem.get else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV cannot be added because the system has no OA intake.") return false end - + # Create an ERV erv = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(self.model) erv.setName("#{self.name} ERV") erv.setSensibleEffectivenessat100HeatingAirFlow(0.7) erv.setLatentEffectivenessat100HeatingAirFlow(0.6) @@ -1676,30 +1695,30 @@ erv.setLatentEffectivenessat75HeatingAirFlow(0.6) erv.setSensibleEffectivenessat100CoolingAirFlow(0.75) erv.setLatentEffectivenessat100CoolingAirFlow(0.6) erv.setSensibleEffectivenessat75CoolingAirFlow(0.75) erv.setLatentEffectivenessat75CoolingAirFlow(0.6) - erv.setSupplyAirOutletTemperatureControl(true) + erv.setSupplyAirOutletTemperatureControl(true) erv.setHeatExchangerType('Rotary') erv.setFrostControlType('ExhaustOnly') erv.setEconomizerLockout(true) erv.setThresholdTemperature(-23.3) # -10F erv.setInitialDefrostTimeFraction(0.167) erv.setRateofDefrostTimeFractionIncrease(1.44) - + # Add the ERV to the OA system - erv.addToNode(oa_system.outboardOANode.get) + erv.addToNode(oa_system.outboardOANode.get) # Add a setpoint manager OA pretreat # to control the ERV spm_oa_pretreat = OpenStudio::Model::SetpointManagerOutdoorAirPretreat.new(model) spm_oa_pretreat.setMinimumSetpointTemperature(-99.0) spm_oa_pretreat.setMaximumSetpointTemperature(99.0) spm_oa_pretreat.setMinimumSetpointHumidityRatio(0.00001) spm_oa_pretreat.setMaximumSetpointHumidityRatio(1.0) - # Reference setpoint node and - # Mixed air stream node are outlet + # Reference setpoint node and + # Mixed air stream node are outlet # node of the OA system mixed_air_node = oa_system.mixedAirModelObject.get.to_Node.get spm_oa_pretreat.setReferenceSetpointNode(mixed_air_node) spm_oa_pretreat.setMixedAirStreamNode(mixed_air_node) # Outdoor air node is @@ -1713,65 +1732,65 @@ erv_outlet = erv.primaryAirOutletModelObject.get.to_Node.get spm_oa_pretreat.addToNode(erv_outlet) # Apply the prototype Heat Exchanger power assumptions. erv.setPrototypeNominalElectricPower - + return true - - end - + + end + # Determine if multizone vav optimization is required. # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. - # @todo Add exception logic for + # @return [Bool] Returns true if required, false if not. + # @todo Add exception logic for # systems with AIA healthcare ventilation requirements # dual duct systems def is_multizone_vav_optimization_required(template, climate_zone) multizone_opt_required = false - + case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' - + # Not required before 90.1-2010 return multizone_opt_required - + when '90.1-2010', '90.1-2013' - + # Not required for systems with fan-powered terminals num_fan_powered_terminals = 0 self.demandComponents.each do |comp| - if comp.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized || comp.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized + if comp.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized || comp.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized num_fan_powered_terminals += 1 end end if num_fan_powered_terminals > 0 OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name}, multizone vav optimization is not required because the system has #{num_fan_powered_terminals} fan-powered terminals.") return multizone_opt_required end - + # Not required for systems that require an ERV if self.has_energy_recovery OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: multizone vav optimization is not required because the system has Energy Recovery.") return multizone_opt_required end - + # Get the OA intake controller_oa = nil controller_mv = nil oa_system = nil if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, multizone optimization is not applicable because system has no OA intake.") return multizone_opt_required end - + # Get the AHU design supply air flow rate dsn_flow_m3_per_s = nil if self.designSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = self.designSupplyAirFlowRate.get elsif self.autosizedDesignSupplyAirFlowRate.is_initialized @@ -1779,11 +1798,11 @@ else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name} design supply air flow rate is not available, cannot apply efficiency standard.") return multizone_opt_required end dsn_flow_cfm = OpenStudio.convert(dsn_flow_m3_per_s, 'm^3/s', 'cfm').get - + # Get the minimum OA flow rate min_oa_flow_m3_per_s = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized @@ -1791,14 +1810,14 @@ else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.") return multizone_opt_required end min_oa_flow_cfm = OpenStudio.convert(min_oa_flow_m3_per_s, 'm^3/s', 'cfm').get - + # Calculate the percent OA at design airflow pct_oa = min_oa_flow_m3_per_s/dsn_flow_m3_per_s - + # Not required for systems where # exhaust is more than 70% of the total OA intake. if pct_oa > 0.7 OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: multizone optimization is not applicable because system is more than 70% OA.") return multizone_opt_required @@ -1807,127 +1826,129 @@ # TODO Not required for dual-duct systems # if self.isDualDuct # OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: multizone optimization is not applicable because it is a dual duct system") # return multizone_opt_required # end - + # If here, multizone vav optimization is required multizone_opt_required = true - + return multizone_opt_required - + end - - end - + + end + # Enable multizone vav optimization by changing the Outdoor Air Method # in the Controller:MechanicalVentilation object to 'VentilationRateProcedure' # - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. def enable_multizone_vav_optimization - + # Enable multizone vav optimization # at each timestep. if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation controller_mv.setSystemOutdoorAirMethod('VentilationRateProcedure') else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name}, cannot enable multizone vav optimization because the system has no OA intake.") return false end - - end - + + end + # Disable multizone vav optimization by changing the Outdoor Air Method # in the Controller:MechanicalVentilation object to 'ZoneSum' # # @return [Bool] Returns true if required, false if not. def disable_multizone_vav_optimization - + # Disable multizone vav optimization # at each timestep. if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation controller_mv.setSystemOutdoorAirMethod('ZoneSum') else OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name}, cannot disable multizone vav optimization because the system has no OA intake.") return false end - - end + end + # Set the minimum VAV damper positions # - # + # def set_minimum_vav_damper_positions(template) - + self.thermalZones.each do |zone| zone.equipment.each do |equip| if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized zone_oa_per_area = zone.outdoor_airflow_rate_per_area vav_terminal = equip.to_AirTerminalSingleDuctVAVReheat.get vav_terminal.set_minimum_damper_position(template, zone_oa_per_area) end end end - + return true - + end - + # Adjust minimum VAV damper positions to the values # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. # @todo Add exception logic for systems serving parking garage, warehouse, or multifamily def adjust_minimum_vav_damper_positions - + # Total uncorrected outdoor airflow rate v_ou = 0.0 self.thermalZones.each do |zone| v_ou += zone.outdoor_airflow_rate end + v_ou_cfm = OpenStudio.convert(v_ou, 'm^3/s', 'cfm').get - + # System primary airflow rate (whether autosized or hard-sized) v_ps = 0.0 + if self.autosizedDesignSupplyAirFlowRate.is_initialized v_ps = self.autosizedDesignSupplyAirFlowRate.get else v_ps = self.designSupplyAirFlowRate.get end v_ps_cfm = OpenStudio.convert(v_ps, 'm^3/s', 'cfm').get - + # Average outdoor air fraction x_s = v_ou / v_ps - - OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: v_ou = #{v_ou_cfm.round} cfm, v_ps = #{v_ps_cfm.round} cfm, x_s = #{x_s.round(2)}.") - + + OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: v_ou = #{v_ou_cfm.round} cfm, v_ps = #{v_ps_cfm.round} cfm, x_s = #{x_s.round(2)}.") + # Determine the zone ventilation effectiveness # for every zone on the system. # When ventilation effectiveness is too low, # increase the minimum damper position. e_vzs = [] e_vzs_adj = [] num_zones_adj = 0 self.thermalZones.sort.each do |zone| - + # Breathing zone airflow rate - v_bz = zone.outdoor_airflow_rate - + v_bz = zone.outdoor_airflow_rate + # Zone air distribution, assumed 1 per PNNL - e_z = 1.0 - + e_z = 1.0 + # Zone airflow rate - v_oz = v_bz / e_z - + v_oz = v_bz / e_z + # Primary design airflow rate - # max of heating and cooling + # max of heating and cooling # design air flow rates v_pz = 0.0 clg_dsn_flow = zone.autosizedCoolingDesignAirFlowRate if clg_dsn_flow.is_initialized clg_dsn_flow = clg_dsn_flow.get @@ -1944,11 +1965,11 @@ v_pz = htg_dsn_flow end else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: #{zone.name} htg_dsn_flow could not be found.") end - + # Get the minimum damper position mdp = 1.0 zone.equipment.each do |equip| if equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.get @@ -1964,50 +1985,50 @@ elsif equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get mdp = term.constantMinimumAirFlowFraction end end - + # Zone minimum discharge airflow rate v_dz = v_pz*mdp - + # Zone discharge air fraction z_d = v_oz / v_dz - + # Zone ventilation effectiveness e_vz = 1+x_s-z_d - + # Store the ventilation effectiveness e_vzs << e_vz - + OpenStudio::logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Zone #{zone.name} v_oz = #{v_oz.round(2)} m^3/s, v_pz = #{v_pz.round(2)} m^3/s, v_dz = #{v_dz.round(2)}, z_d = #{z_d.round(2)}.") - + # Check the ventilation effectiveness against # the minimum limit per PNNL and increase # as necessary. if e_vz < 0.6 - + # Adjusted discharge air fraction z_d_adj = 1+x_s-0.6 - + # Adjusted min discharge airflow rate v_dz_adj = v_oz / z_d_adj - + # Adjusted minimum damper position mdp_adj = v_dz_adj / v_pz - + # Don't allow values > 1 if mdp_adj > 1.0 mdp_adj = 1.0 end - + # Zone ventilation effectiveness e_vz_adj = 1+x_s-z_d_adj - + # Store the ventilation effectiveness e_vzs_adj << e_vz_adj - + # Set the adjusted minimum damper position zone.equipment.each do |equip| if equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.get term.setZoneMinimumAirFlowFraction(mdp_adj) @@ -2020,50 +2041,50 @@ elsif equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get term.setConstantMinimumAirFlowFraction(mdp_adj) end end - + num_zones_adj += 1 - + OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Zone #{zone.name} has a ventilation effectiveness of #{e_vz.round(2)}. Increasing to #{e_vz_adj.round(2)} by increasing minimum damper position from #{mdp.round(2)} to #{mdp_adj.round(2)}.") else # Store the unadjusted value e_vzs_adj << e_vz end - + end - + # Min system zone ventilation effectiveness e_v = e_vzs.min - - # Total system outdoor intake flow rate + + # Total system outdoor intake flow rate v_ot = v_ou / e_v v_ot_cfm = OpenStudio.convert(v_ot, 'm^3/s', 'cfm').get - + # Min system zone ventilation effectiveness e_v_adj = e_vzs_adj.min - - # Total system outdoor intake flow rate + + # Total system outdoor intake flow rate v_ot_adj = v_ou / e_v_adj v_ot_adj_cfm = OpenStudio.convert(v_ot_adj, 'm^3/s', 'cfm').get - + # Report out the results of the multizone calculations if num_zones_adj > 0 OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: the multizone outdoor air calculation method was applied. A simple summation of the zone outdoor air requirements gives a value of #{v_ou_cfm.round} cfm. Applying the multizone method gives a value of #{v_ot_cfm.round} cfm, with an original system ventilation effectiveness of #{e_v.round(2)}. After increasing the minimum damper position in #{num_zones_adj} critical zones, the resulting requirement is #{v_ot_adj_cfm.round} cfm with a system ventilation effectiveness of #{e_v_adj.round(2)}.") else OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: the multizone outdoor air calculation method was applied. A simple summation of the zone requirements gives a value of #{v_ou_cfm.round} cfm. However, applying the multizone method requires #{v_ot_adj_cfm.round} cfm based on the ventilation effectiveness of the system.") end - + # Hard-size the sizing:system # object with the calculated min OA flow rate sizing_system = self.sizingSystem sizing_system.setDesignOutdoorAirFlowRate(v_ot_adj) - + return true - + end # For critical zones of Outpatient, if the minimum airflow rate required by the accreditation standard (AIA 2001) is significantly # less than the autosized peak design airflow in any of the three climate zones (Houston, Baltimore and Burlington), the minimum # airflow fraction of the terminal units is reduced to the value: "required minimum airflow rate / autosized peak design flow" @@ -2096,34 +2117,34 @@ end end end return true end - + # Determine if demand control ventilation (DCV) is # required for this air loop. # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. - # @todo Add exception logic for + # @return [Bool] Returns true if required, false if not. + # @todo Add exception logic for # systems that serve multifamily, parking garage, warehouse def is_demand_control_ventilation_required(template, climate_zone) - + dcv_required = false - + # Not required by the old vintages - if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004' - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required for any system.") + if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004' || template == 'NECB 2011' + OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}: #{self.name}: DCV is not required for any system.") return dcv_required end - + # Not required for systems that require an ERV if self.has_energy_recovery OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system has Energy Recovery.") return dcv_required end - + # Area, occupant density, and OA flow limits min_area_ft2 = 0 min_occ_per_1000_ft2 = 0 min_oa_without_economizer_cfm = 0 min_oa_with_economizer_cfm = 0 @@ -2142,54 +2163,54 @@ min_area_ft2 = 500 min_occ_per_1000_ft2 = 25 min_oa_without_economizer_cfm = 3000 min_oa_with_economizer_cfm = 750 end - + # Get the area served and the number of occupants area_served_m2 = 0 num_people = 0 self.thermalZones.each do |zone| zone.spaces.each do |space| area_served_m2 += space.floorArea num_people += space.numberOfPeople end end - + # Check the minimum area area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get if area_served_ft2 < min_area_ft2 OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system serves #{area_served_ft2.round} ft2, but the minimum size is #{min_area_ft2.round} ft2.") return dcv_required end - + # Check the minimum occupancy density occ_per_ft2 = num_people / area_served_ft2 - occ_per_1000_ft2 = occ_per_ft2*1000 - + occ_per_1000_ft2 = occ_per_ft2*1000 + if occ_per_1000_ft2 < min_occ_per_1000_ft2 OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system occupant density is #{occ_per_1000_ft2.round} people/1000 ft2, but the minimum occupant density is #{min_occ_per_1000_ft2.round} people/1000 ft2.") return dcv_required end - - # Get the min OA flow rate + + # Get the min OA flow rate oa_flow_m3_per_s = 0 if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir if controller_oa.minimumOutdoorAirFlowRate.is_initialized oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get end else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, DCV not applicable because it has no OA intake.") return dcv_required end oa_flow_cfm = OpenStudio.convert(oa_flow_m3_per_s, 'm^3/s', 'cfm').get - - + + # Check for min OA without an economizer OR has economizer if oa_flow_cfm < min_oa_without_economizer_cfm && self.has_economizer == false # Message if doesn't pass OA limit if oa_flow_cfm < min_oa_without_economizer_cfm OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system min oa flow is #{oa_flow_cfm.round} cfm, less than the minimum of #{min_oa_without_economizer_cfm.round} cfm.") @@ -2204,61 +2225,61 @@ # If has economizer, cfm limit is lower if oa_flow_cfm < min_oa_with_economizer_cfm && self.has_economizer OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system has an economizer, but the min oa flow is #{oa_flow_cfm.round} cfm, less than the minimum of #{min_oa_with_economizer_cfm.round} cfm for systems with an economizer.") return dcv_required end - + # If here, DCV is required dcv_required = true - + return dcv_required - - end + end + # Enable demand control ventilation (DCV) for this air loop. # # @return [Bool] Returns true if required, false if not. def enable_demand_control_ventilation() - + # Get the OA intake controller_oa = nil controller_mv = nil if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation if controller_mv.demandControlledVentilation == true OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV was already enabled.") return true end else OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Could not enable DCV since the system has no OA intake.") return false end - + # Change the min flow rate in the controller outdoor air controller_oa.setMinimumOutdoorAirFlowRate(0.0) - + # Enable DCV in the controller mechanical ventilation controller_mv.setDemandControlledVentilation(true) return true end - + # Determine if the system required supply air temperature # (SAT) reset. # # @param (see #is_economizer_required) - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. def is_supply_air_temperature_reset_required(template, climate_zone) - + is_sat_reset_required = false - + # Only required for multizone VAV systems return is_sat_reset_required unless self.is_multizone_vav_system - + # Not required until 90.1-2010 case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007' return is_sat_reset_required when '90.1-2010', '90.1-2013' @@ -2282,15 +2303,15 @@ 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' is_sat_reset_required = true - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Supply air temperature reset is required.") + OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Supply air temperature reset is required.") return is_sat_reset_required end end - + end # Enable supply air temperature (SAT) reset based # on the cooling demand of the warmest zone. # @@ -2339,129 +2360,129 @@ # on outdoor air conditions. SAT will be kept at the # current design temperature when outdoor air is above 70F, # increased by 5F when outdoor air is below 50F, and reset # linearly when outdoor air is between 50F and 70F. # - # @return [Bool] Returns true if successful, false if not. + # @return [Bool] Returns true if successful, false if not. def enable_supply_air_temperature_reset_outdoor_temperature() - + # for AHU1 in Outpatient, SAT is 52F constant, no reset return true if self.name.get == 'PVAV Outpatient F1' - - # Get the current setpoint and calculate + + # Get the current setpoint and calculate # the new setpoint. sizing_system = self.sizingSystem sat_at_hi_oat_c = sizing_system.centralCoolingDesignSupplyAirTemperature sat_at_hi_oat_f = OpenStudio.convert(sat_at_hi_oat_c, 'C', 'F').get # 5F increase when it's cold outside, # and therefore less cooling capacity is likely required. increase_f = 5.0 sat_at_lo_oat_f = sat_at_hi_oat_f+increase_f sat_at_lo_oat_c = OpenStudio.convert(sat_at_lo_oat_f, 'F', 'C').get - + # Define the high and low outdoor air temperatures lo_oat_f = 50 lo_oat_c = OpenStudio.convert(lo_oat_f, 'F', 'C').get hi_oat_f = 70 hi_oat_c = OpenStudio.convert(hi_oat_f, 'F', 'C').get - + # Create a setpoint manager sat_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model) sat_oa_reset.setName("#{self.name} SAT Reset") sat_oa_reset.setControlVariable('Temperature') sat_oa_reset.setSetpointatOutdoorLowTemperature(sat_at_lo_oat_c) sat_oa_reset.setOutdoorLowTemperature(lo_oat_c) sat_oa_reset.setSetpointatOutdoorHighTemperature(sat_at_hi_oat_c) sat_oa_reset.setOutdoorHighTemperature(hi_oat_c) - + # Attach the setpoint manager to the # supply outlet node of the system. sat_oa_reset.addToNode(self.supplyOutletNode) - + OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Supply air temperature reset was enabled. When OAT > #{hi_oat_f.round}F, SAT is #{sat_at_hi_oat_f.round}F. When OAT < #{lo_oat_f.round}F, SAT is #{sat_at_lo_oat_f.round}F. It varies linearly in between these points.") - + return true - + end - + # Determine if the system has an economizer # - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. def has_economizer() - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir economizer_type = oa_control.getEconomizerControlType - + # Return false if no economizer is present if economizer_type == 'NoEconomizer' return false else return true end - + end - + # Determine if the system is a multizone VAV system # - # @return [Bool] Returns true if required, false if not. + # @return [Bool] Returns true if required, false if not. def is_multizone_vav_system() - + is_multizone_vav_system = false - + # Must serve more than 1 zone if self.thermalZones.size < 2 return is_multizone_vav_system end - + # Must be a variable volume system has_vav_fan = false self.supplyComponents.each do |comp| if comp.to_FanVariableVolume.is_initialized has_vav_fan = true end end if has_vav_fan == false return is_multizone_vav_system end - + # If here, it's a multizone VAV system is_multizone_vav_system = true - + return is_multizone_vav_system end - + # Determine if the system has energy recovery already # - # @return [Bool] Returns true if an ERV is present, false if not. + # @return [Bool] Returns true if an ERV is present, false if not. def has_energy_recovery() - + has_erv = false - + # Get the OA system oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return has_erv # No OA system - end - + end + # Find any ERV on the OA system oa_sys.oaComponents.each do |oa_comp| if oa_comp.to_HeatExchangerAirToAirSensibleAndLatent.is_initialized has_erv = true end end - + return has_erv end # Set the VAV damper control to single maximum or @@ -2469,65 +2490,65 @@ # # @return [Bool] Returns true if successful, false if not # @todo see if this impacts the sizing run. def set_vav_damper_action(template) damper_action = nil - case template + case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', 'NECB 2011' damper_action = 'Single Maximum' when '90.1-2007', '90.1-2010', '90.1-2013' damper_action = 'Dual Maximum' end - + # Interpret this as an EnergyPlus input damper_action_eplus = nil if damper_action == 'Single Maximum' damper_action_eplus = 'Normal' elsif damper_action == 'Dual Maximum' damper_action_eplus = 'Reverse' end - + # Set the control for any VAV reheat terminals # on this airloop. self.demandComponents.each do |equip| if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get term.setDamperHeatingAction(damper_action_eplus) end - end - + end + OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: VAV damper action was set to #{damper_action} control.") - + return true - + end # Determine if a motorized OA damper is required def is_motorized_oa_damper_required(template, climate_zone) - + motorized_oa_damper_required = false if self.name.to_s.include? "Outpatient F1" motorized_oa_damper_required = true OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: always has a damper, the minimum OA schedule is the same as airloop availability schedule.") return motorized_oa_damper_required end - + # If the system has an economizer, it must have # a motorized damper. if self.has_economizer motorized_oa_damper_required = true OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Because the system has an economizer, it requires a motorized OA damper.") return motorized_oa_damper_required end # Determine the exceptions based on - # number of stories, climate zone, and + # number of stories, climate zone, and # outdoor air intake rates. minimum_oa_flow_cfm = 0 maximum_stories = 0 - case template + case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004' # Assuming that older buildings always # used backdraft gravity dampers return motorized_oa_damper_required when '90.1-2004', '90.1-2007' @@ -2559,121 +2580,121 @@ else minimum_oa_flow_cfm = 300 maximum_stories = 0 end end - + # Get the number of stories num_stories = self.model.getBuildingStorys.size - + # Check the number of stories exception, # which is climate-zone dependent. if num_stories < maximum_stories OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Motorized OA damper not required because the building has #{num_stories} stories, less than the maximum of #{maximum_stories} stories for climate zone #{climate_zone}.") return motorized_oa_damper_required - end - - # Get the min OA flow rate + end + + # Get the min OA flow rate oa_flow_m3_per_s = 0 if self.airLoopHVACOutdoorAirSystem.is_initialized oa_system = self.airLoopHVACOutdoorAirSystem.get - controller_oa = oa_system.getControllerOutdoorAir + controller_oa = oa_system.getControllerOutdoorAir if controller_oa.minimumOutdoorAirFlowRate.is_initialized oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get end else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, Motorized OA damper not applicable because it has no OA intake.") return motorized_oa_damper_required end - oa_flow_cfm = OpenStudio.convert(oa_flow_m3_per_s, 'm^3/s', 'cfm').get - + oa_flow_cfm = OpenStudio.convert(oa_flow_m3_per_s, 'm^3/s', 'cfm').get + # Check the OA flow rate exception if oa_flow_cfm < minimum_oa_flow_cfm OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Motorized OA damper not required because the system OA intake of #{oa_flow_cfm.round} cfm is less than the minimum threshold of #{minimum_oa_flow_cfm} cfm.") return motorized_oa_damper_required end # If here, motorized damper is required motorized_oa_damper_required = true - + return motorized_oa_damper_required end - + # Add a motorized damper by modifying the OA schedule # to require zero OA during unoccupied hours. This means # that even during morning warmup or nightcyling, no OA will # be brought into the building, lowering heating/cooling load. # If no occupancy schedule is supplied, one will be created. # In this case, occupied is defined as the total percent # occupancy for the loop for all zones served. - # + # # @param min_occ_pct [Double] the fractional value below which # the system will be considered unoccupied. # @param occ_sch [OpenStudio::Model::Schedule] the occupancy schedule. # If not supplied, one will be created based on the supplied # occupancy threshold. - # @return [Bool] true if successful, false if not + # @return [Bool] true if successful, false if not def add_motorized_oa_damper(min_occ_pct = 0.15, occ_sch = nil) - + # Get the airloop occupancy schedule if none supplied if occ_sch.nil? occ_sch = self.get_occupancy_schedule(min_occ_pct) flh = occ_sch.annual_equivalent_full_load_hrs OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: Annual occupied hours = #{flh.round} hr/yr, assuming a #{min_occ_pct} occupancy threshold. This schedule will be used to close OA damper during unoccupied hours.") else OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: Setting motorized OA damper schedule to #{occ_sch.name}.") end - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir - + # Set the minimum OA schedule to follow occupancy - oa_control.setMinimumOutdoorAirSchedule(occ_sch) - + oa_control.setMinimumOutdoorAirSchedule(occ_sch) + return true - - end - + + end + # Remove a motorized OA damper by modifying the OA schedule # to require full OA at all times. Whenever the fan operates, # the damper will be open and OA will be brought into the building. # This reflects the use of a backdraft gravity damper, and # increases building loads unnecessarily during unoccupied hours. def remove_motorized_oa_damper - + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir - + # Set the minimum OA schedule to always 1 (100%) oa_control.setMinimumOutdoorAirSchedule(self.model.alwaysOnDiscreteSchedule) return true - - end + end + # This method creates a schedule where the value is zero when # the overall occupancy for all zones on the airloop is below # the specified threshold, and one when the overall occupancy is # greater than or equal to the threshold. This method is designed # to use the total number of people on the airloop, so if there is # a zone that is continuously occupied by a few people, but other - # zones that are intermittently occupied by many people, the + # zones that are intermittently occupied by many people, the # first zone doesn't drive the entire system. # # @param occupied_percentage_threshold [Double] the minimum fraction (0 to 1) that counts as occupied # @return [ScheduleRuleset] a ScheduleRuleset where 0 = unoccupied, 1 = occupied # @todo Speed up this method. Bottleneck is ScheduleRule.getDaySchedules @@ -2697,11 +2718,11 @@ next if num_ppl_sch.empty? # Skip non-ruleset schedules num_ppl_sch = num_ppl_sch.get num_ppl = people.getNumberOfPeople(space.floorArea) if occ_schedules_num_occ[num_ppl_sch].nil? occ_schedules_num_occ[num_ppl_sch] = num_ppl - max_occ_on_airloop += num_ppl + max_occ_on_airloop += num_ppl else occ_schedules_num_occ[num_ppl_sch] += num_ppl max_occ_on_airloop += num_ppl end end @@ -2716,26 +2737,26 @@ next if num_ppl_sch.empty? # Skip non-ruleset schedules num_ppl_sch = num_ppl_sch.get num_ppl = people.getNumberOfPeople(space.floorArea) if occ_schedules_num_occ[num_ppl_sch].nil? occ_schedules_num_occ[num_ppl_sch] = num_ppl - max_occ_on_airloop += num_ppl + max_occ_on_airloop += num_ppl else occ_schedules_num_occ[num_ppl_sch] += num_ppl max_occ_on_airloop += num_ppl end end end end end - + OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "#{self.name} has #{occ_schedules_num_occ.size} unique occ schedules.") occ_schedules_num_occ.each do |occ_sch, num_occ| OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", " #{occ_sch.name} - #{num_occ.round} people") end OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", " Total #{max_occ_on_airloop.round} people on #{self.name}") - + # For each day of the year, determine #time_value_pairs = [] year = self.model.getYearDescription yearly_data = [] yearly_times = OpenStudio::DateTimeVector.new @@ -2743,25 +2764,25 @@ for i in 1..365 times_on_this_day = [] os_date = year.makeDate(i) day_of_week = os_date.dayOfWeek.valueName - + # Get the unique time indices and corresponding day schedules occ_schedules_day_schs = {} day_sch_num_occ = {} occ_schedules_num_occ.each do |occ_sch, num_occ| - + # Get the day schedules for this day # (there should only be one) day_schs = occ_sch.getDaySchedules(os_date, os_date) OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "Schedule #{occ_sch.name} has #{day_schs.size} day schs") unless day_schs.size == 1 day_schs[0].times.each do |time| times_on_this_day << time.toString end day_sch_num_occ[day_schs[0]] = num_occ - + end # Determine the total fraction for the airloop at each time daily_times = [] daily_os_times = [] @@ -2775,11 +2796,11 @@ tot_occ_at_time = 0 day_sch_num_occ.each do |day_sch, num_occ| occ_frac = day_sch.getValue(os_time) tot_occ_at_time += occ_frac * num_occ end - + # Total fraction for the airloop at each time air_loop_occ_frac = tot_occ_at_time / max_occ_on_airloop occ_status = 0 # unoccupied if air_loop_occ_frac >= occupied_percentage_threshold occ_status = 1 @@ -2792,39 +2813,39 @@ daily_occs << air_loop_occ_frac.round(2) end # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "#{daily_times.join(', ')} #{daily_values.join(', ')}") - + # Simplify the daily times to eliminate intermediate # points with the same value as the following point. simple_daily_times = [] simple_daily_os_times = [] simple_daily_values = [] simple_daily_occs = [] - daily_values.each_with_index do |value, i| + daily_values.each_with_index do |value, i| next if value == daily_values[i+1] simple_daily_times << daily_times[i] simple_daily_os_times << daily_os_times[i] simple_daily_values << daily_values[i] simple_daily_occs << daily_occs[i] end - + # OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "#{simple_daily_times.join(', ')} {simple_daily_values.join(', ')}") - + # Store the daily values yearly_data << {'date'=>os_date,'day_of_week'=>day_of_week,'times'=>simple_daily_times,'values'=>simple_daily_values,'daily_os_times'=>simple_daily_os_times, 'daily_occs'=>simple_daily_occs} end - + # Create a TimeSeries from the data #time_series = OpenStudio::TimeSeries.new(times, values, 'unitless') # Make a schedule ruleset sch_name = "#{self.name} Occ Sch" sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(self.model) - sch_ruleset.setName("#{sch_name}") + sch_ruleset.setName("#{sch_name}") # Default - All Occupied day_sch = sch_ruleset.defaultDaySchedule day_sch.setName("#{sch_name} Default") day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1) @@ -2832,19 +2853,19 @@ # Winter Design Day - All Occupied day_sch = OpenStudio::Model::ScheduleDay.new(self.model) sch_ruleset.setWinterDesignDaySchedule(day_sch) day_sch = sch_ruleset.winterDesignDaySchedule day_sch.setName("#{sch_name} Winter Design Day") - day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1) + day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1) # Summer Design Day - All Occupied day_sch = OpenStudio::Model::ScheduleDay.new(self.model) sch_ruleset.setSummerDesignDaySchedule(day_sch) day_sch = sch_ruleset.summerDesignDaySchedule day_sch.setName("#{sch_name} Summer Design Day") day_sch.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1) - + # Create ruleset schedules, attempting to create # the minimum number of unique rules. ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'].each do |day_of_week| OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "#{day_of_week}") end_of_prev_rule = yearly_data[0]['date'] @@ -2855,25 +2876,25 @@ next unless day == day_of_week date = daily_data['date'] times = daily_data['times'] values = daily_data['values'] daily_occs = daily_data['daily_occs'] - + # If the next (Monday, Tuesday, etc.) # is the same as today, keep going. # If the next is different, or if # we've reached the end of the year, # create a new rule if !yearly_data[i+7].nil? next_day_times = yearly_data[i+7]['times'] next_day_values = yearly_data[i+7]['values'] next if times == next_day_times && values == next_day_values end - + daily_os_times = daily_data['daily_os_times'] daily_occs = daily_data['daily_occs'] - + # If here, we need to make a rule to cover from the previous # rule to today OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "Making a new rule for #{day_of_week} from #{end_of_prev_rule.to_s} to #{date}") sch_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset) sch_rule.setName("#{sch_name} #{day_of_week} Rule") @@ -2883,11 +2904,11 @@ value = values[i] next if value == values[i+1] # Don't add breaks if same value day_sch.addValue(time, value) OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", " Adding value #{time}, #{value}") end - + # Set the dates when the rule applies sch_rule.setStartDate(end_of_prev_rule) sch_rule.setEndDate(date) # Individual Days @@ -2896,84 +2917,84 @@ sch_rule.setApplyWednesday(true) if day_of_week == 'Wednesday' sch_rule.setApplyThursday(true) if day_of_week == 'Thursday' sch_rule.setApplyFriday(true) if day_of_week == 'Friday' sch_rule.setApplySaturday(true) if day_of_week == 'Saturday' sch_rule.setApplySunday(true) if day_of_week == 'Sunday' - + # Reset the previous rule end date end_of_prev_rule = date + OpenStudio::Time.new(0, 24, 0, 0) - + end - + end return sch_ruleset - - end - + + end + # Generate the EMS used to implement the economizer # and staging controls for packaged single zone units. # @note The resulting EMS doesn't actually get added to # the IDF yet. # def apply_single_zone_controls(template, climate_zone) - + # Number of stages is determined by the template num_stages = nil - case template + case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'NECB 2011' OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No special economizer controls were modeled.") return true when '90.1-2004', '90.1-2007' num_stages = 1 when '90.1-2010', '90.1-2013' num_stages = 2 end - + # Scrub special characters from the system name sn = self.name.get.to_s snc = sn.gsub(/\W/,'').gsub('_','') - + # Get the zone name zone = self.thermalZones[0] zone_name = zone.name.get.to_s zn_name_clean = zone_name.gsub(/\W/,'_') - + # Zone air node - zone_air_node_name = zone.zoneAirNode.name.get - + zone_air_node_name = zone.zoneAirNode.name.get + # Get the OA system and OA controller oa_sys = self.airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end oa_control = oa_sys.getControllerOutdoorAir oa_control_name = oa_control.name.get oa_node_name = oa_sys.outboardOANode.get.name.get - + # Get the name of the min oa schedule min_oa_sch_name = nil if oa_control.minimumOutdoorAirSchedule.is_initialized min_oa_sch_name = oa_control.minimumOutdoorAirSchedule.get.name.get else min_oa_sch_name = self.model.alwaysOnDiscreteSchedule.name.get end - + # Get the supply fan if self.supplyFan.empty? OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No supply fan found, cannot apply DX fan/economizer control.") return false end fan = self.supplyFan.get fan_name = fan.name.get - + # Supply outlet node sup_out_node = self.supplyOutletNode sup_out_node_name = sup_out_node.name.get - + # DX Cooling Coil dx_coil = nil self.supplyComponents.each do |equip| if equip.to_CoilCoolingDXSingleSpeed.is_initialized dx_coil = equip.to_CoilCoolingDXSingleSpeed.get @@ -2985,11 +3006,11 @@ OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No DX cooling coil found, cannot apply DX fan/economizer control.") return false end dx_coil_name = dx_coil.name.get dx_coilsys_name = "#{dx_coil_name} CoilSystem" - + # Heating Coil htg_coil = nil self.supplyComponents.each do |equip| if equip.to_CoilHeatingGas.is_initialized htg_coil = equip.to_CoilHeatingGas.get @@ -3002,61 +3023,61 @@ if htg_coil.nil? OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No heating coil found, cannot apply DX fan/economizer control.") return false end htg_coil_name = htg_coil.name.get - + # Create an economizer maximum OA fraction schedule with # a maximum of 70% to reflect damper leakage per PNNL max_oa_sch_name = "#{snc}maxOASch" max_oa_sch = OpenStudio::Model::ScheduleRuleset.new(self.model) max_oa_sch.setName(max_oa_sch_name) max_oa_sch.defaultDaySchedule.setName("#{max_oa_sch_name}Default") max_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0,24,0,0), 0.7) oa_control.setMaximumFractionofOutdoorAirSchedule(max_oa_sch) - + ems = " - + ! Sensors - + EnergyManagementSystem:Sensor, - #{snc}OASch, - #{min_oa_sch_name}, !- Output:Variable or Output:Meter Index Key Name, + #{snc}OASch, + #{min_oa_sch_name}, !- Output:Variable or Output:Meter Index Key Name, Schedule Value; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, #{zn_name_clean}Temp, #{zone_air_node_name}, !- Output:Variable or Output:Meter Index Key Name System Node Temperature; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}OAFlowMass, + #{snc}OAFlowMass, #{oa_node_name}, !- Output:Variable or Output:Meter Index Key Name System Node Mass Flow Rate; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}HeatingRTF, + #{snc}HeatingRTF, #{htg_coil_name}, !- Output:Variable or Output:Meter Index Key Name Heating Coil Runtime Fraction; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}RTF, + #{snc}RTF, #{dx_coil_name}, !- Output:Variable or Output:Meter Index Key Name Cooling Coil Runtime Fraction; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}SpeedRatio, + #{snc}SpeedRatio, #{dx_coilsys_name}, !- Output:Variable or Output:Meter Index Key Name Coil System Compressor Speed Ratio; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}DATRqd, + #{snc}DATRqd, #{sup_out_node_name}, !- Output:Variable or Output:Meter Index Key Name System Node Setpoint Temperature; !- Output:Variable or Output:Meter Name EnergyManagementSystem:Sensor, - #{snc}EconoStatus, + #{snc}EconoStatus, #{sn}, !- Output:Variable or Output:Meter Index Key Name Air System Outdoor Air Economizer Status; !- Output:Variable or Output:Meter Name ! Internal Variables @@ -3064,23 +3085,23 @@ #{snc}FanDesignPressure, #{fan_name}, !- Internal Data Index Key Name Fan Nominal Pressure Rise; !- Internal Data Type EnergyManagementSystem:InternalVariable, - #{snc}DesignFlowMass, + #{snc}DesignFlowMass, #{oa_control_name},!- Internal Data Index Key Name Outdoor Air Controller Maximum Mass Flow Rate; !- Internal Data Type EnergyManagementSystem:InternalVariable, - #{snc}OADesignMass, + #{snc}OADesignMass, #{oa_control_name},!- Internal Data Index Key Name Outdoor Air Controller Minimum Mass Flow Rate; !- Internal Data Type ! Actuators EnergyManagementSystem:Actuator, - #{snc}FanPressure, + #{snc}FanPressure, #{fan_name}, !- Actuated Component Unique Name Fan, !- Actuated Component Type Fan Pressure Rise; !- Actuated Component Control Type EnergyManagementSystem:Actuator, @@ -3091,130 +3112,130 @@ EnergyManagementSystem:GlobalVariable, #{snc}FanPwrExp, !- Erl Variable 1 Name #{snc}Stg1Spd, !- Erl Variable 2 Name #{snc}Stg2Spd, !- Erl Variable 3 Name - #{snc}HeatSpeed, - #{snc}VenSpeed, - #{snc}NumberofStages; + #{snc}HeatSpeed, + #{snc}VenSpeed, + #{snc}NumberofStages; EnergyManagementSystem:Program, #{snc}EconomizerCTRLProg, - SET #{snc}TimestepEconEff = 0.7, - SET #{snc}MaxE = 0.7, + SET #{snc}TimestepEconEff = 0.7, + SET #{snc}MaxE = 0.7, SET #{snc}DATRqd = (#{snc}DATRqd*1.8)+32, SET OATF = (OATF*1.8)+32, SET OAwbF = (OAwbF*1.8)+32, IF #{snc}OAFlowMass > (#{snc}OADesignMass*#{snc}OASch), SET #{snc}EconoActive = 1, - ELSE, + ELSE, SET #{snc}EconoActive = 0, - ENDIF, + ENDIF, SET #{snc}dTNeeded = 75-#{snc}DATRqd, SET #{snc}CoolDesdT = ((98*0.15)+(75*(1-0.15)))-55, SET #{snc}CoolLoad = #{snc}dTNeeded/ #{snc}CoolDesdT, - IF #{snc}CoolLoad > 1, + IF #{snc}CoolLoad > 1, SET #{snc}CoolLoad = 1, ELSEIF #{snc}CoolLoad < 0, SET #{snc}CoolLoad = 0, - ENDIF, + ENDIF, IF #{snc}EconoActive == 1, SET #{snc}Stage = #{snc}NumberofStages, - IF #{snc}Stage == 2, + IF #{snc}Stage == 2, IF #{snc}CoolLoad < 0.6, SET #{snc}TimestepEconEff = #{snc}MaxE, - ELSE, + ELSE, SET #{snc}ECOEff = 0-2.18919863612305, SET #{snc}ECOEff = #{snc}ECOEff+(0-0.674461284910428*#{snc}CoolLoad), SET #{snc}ECOEff = #{snc}ECOEff+(0.000459106275872404*(OATF^2)), SET #{snc}ECOEff = #{snc}ECOEff+(0-0.00000484778537945252*(OATF^3)), SET #{snc}ECOEff = #{snc}ECOEff+(0.182915713033586*OAwbF), SET #{snc}ECOEff = #{snc}ECOEff+(0-0.00382838660261133*(OAwbF^2)), SET #{snc}ECOEff = #{snc}ECOEff+(0.0000255567460240583*(OAwbF^3)), SET #{snc}TimestepEconEff = #{snc}ECOEff, - ENDIF, - ELSE, + ENDIF, + ELSE, SET #{snc}ECOEff = 2.36337942464462, SET #{snc}ECOEff = #{snc}ECOEff+(0-0.409939515512619*#{snc}CoolLoad), SET #{snc}ECOEff = #{snc}ECOEff+(0-0.0565205596792225*OAwbF), SET #{snc}ECOEff = #{snc}ECOEff+(0-0.0000632612294169389*(OATF^2)), SET #{snc}TimestepEconEff = #{snc}ECOEff+(0.000571724868775081*(OAwbF^2)), - ENDIF, + ENDIF, IF #{snc}TimestepEconEff > #{snc}MaxE, SET #{snc}TimestepEconEff = #{snc}MaxE, ELSEIF #{snc}TimestepEconEff < (#{snc}OADesignMass*#{snc}OASch), SET #{snc}TimestepEconEff = (#{snc}OADesignMass*#{snc}OASch), - ENDIF, - ENDIF; + ENDIF, + ENDIF; EnergyManagementSystem:Program, - #{snc}SetFanPar, - IF #{snc}NumberofStages == 1, - Return, - ENDIF, + #{snc}SetFanPar, + IF #{snc}NumberofStages == 1, + Return, + ENDIF, SET #{snc}FanPwrExp = 2.2, SET #{snc}OAFrac = #{snc}OAFlowMass/#{snc}DesignFlowMass, IF #{snc}OAFrac < 0.66, SET #{snc}VenSpeed = 0.66, SET #{snc}Stg1Spd = 0.66, - ELSE, + ELSE, SET #{snc}VenSpeed = #{snc}OAFrac, SET #{snc}Stg1Spd = #{snc}OAFrac, - ENDIF, + ENDIF, SET #{snc}Stg2Spd = 1.0, SET #{snc}HeatSpeed = 1.0; EnergyManagementSystem:Program, - #{snc}FanControl, - IF #{snc}NumberofStages == 1, - Return, - ENDIF, + #{snc}FanControl, + IF #{snc}NumberofStages == 1, + Return, + ENDIF, IF #{snc}HeatingRTF > 0, SET #{snc}Heating = #{snc}HeatingRTF, SET #{snc}Ven = 1-#{snc}HeatingRTF, - SET #{snc}Eco = 0, - SET #{snc}Stage1 = 0, - SET #{snc}Stage2 = 0, - ELSE, + SET #{snc}Eco = 0, + SET #{snc}Stage1 = 0, + SET #{snc}Stage2 = 0, + ELSE, SET #{snc}Heating = 0, SET #{snc}EcoSpeed = #{snc}VenSpeed, IF #{snc}SpeedRatio == 0, - IF #{snc}RTF > 0, + IF #{snc}RTF > 0, SET #{snc}Stage1 = #{snc}RTF, - SET #{snc}Stage2 = 0, + SET #{snc}Stage2 = 0, SET #{snc}Ven = 1-#{snc}RTF, - SET #{snc}Eco = 0, + SET #{snc}Eco = 0, IF #{snc}OAFlowMass > (#{snc}OADesignMass*#{snc}OASch), SET #{snc}Stg1Spd = 1.0, - ENDIF, - ELSE, - SET #{snc}Stage1 = 0, - SET #{snc}Stage2 = 0, + ENDIF, + ELSE, + SET #{snc}Stage1 = 0, + SET #{snc}Stage2 = 0, IF #{snc}OAFlowMass > (#{snc}OADesignMass*#{snc}OASch), - SET #{snc}Eco = 1.0, - SET #{snc}Ven = 0, - !Calculate the expected discharge air temperature if the system runs at its low speed + SET #{snc}Eco = 1.0, + SET #{snc}Ven = 0, + !Calculate the expected discharge air temperature if the system runs at its low speed SET #{snc}ExpDAT = #{snc}DATRqd-(1-#{snc}VenSpeed)*#{zn_name_clean}Temp, SET #{snc}ExpDAT = #{snc}ExpDAT/#{snc}VenSpeed, IF OATF > #{snc}ExpDAT, SET #{snc}EcoSpeed = #{snc}Stg2Spd, - ENDIF, - ELSE, - SET #{snc}Eco = 0, - SET #{snc}Ven = 1.0, - ENDIF, - ENDIF, - ELSE, + ENDIF, + ELSE, + SET #{snc}Eco = 0, + SET #{snc}Ven = 1.0, + ENDIF, + ENDIF, + ELSE, SET #{snc}Stage1 = 1-#{snc}SpeedRatio, SET #{snc}Stage2 = #{snc}SpeedRatio, - SET #{snc}Ven = 0, - SET #{snc}Eco = 0, + SET #{snc}Ven = 0, + SET #{snc}Eco = 0, IF #{snc}OAFlowMass > (#{snc}OADesignMass*#{snc}OASch), SET #{snc}Stg1Spd = 1.0, - ENDIF, - ENDIF, - ENDIF, + ENDIF, + ENDIF, + ENDIF, ! For each mode, (percent time in mode)*(fanSpeer^PwrExp) is the contribution to weighted fan power over time step SET #{snc}FPR = #{snc}Ven*(#{snc}VenSpeed ^ #{snc}FanPwrExp), SET #{snc}FPR = #{snc}FPR+#{snc}Eco*(#{snc}EcoSpeed^#{snc}FanPwrExp), SET #{snc}FPR1 = #{snc}Stage1*(#{snc}Stg1Spd^#{snc}FanPwrExp), SET #{snc}FPR = #{snc}FPR+#{snc}FPR1, @@ -3233,11 +3254,11 @@ #{snc}SetNumberofStagesCallingManager, BeginNewEnvironment, #{snc}SetNumberofStages; !- Program Name 1 EnergyManagementSystem:ProgramCallingManager, - #{snc}ECOManager, + #{snc}ECOManager, InsideHVACSystemIterationLoop, !- EnergyPlus Model Calling Point #{snc}EconomizerCTRLProg; !- Program Name 1 EnergyManagementSystem:ProgramCallingManager, #{snc}FanParametermanager, @@ -3248,23 +3269,23 @@ #{snc}FanMainManager, BeginTimestepBeforePredictor, #{snc}FanControl; " - + # Write the ems out - # File.open("#{Dir.pwd}/#{snc}_ems.idf", 'w') do |file| + # File.open("#{Dir.pwd}/#{snc}_ems.idf", 'w') do |file| # file.puts ems # end - + return ems - + end - + # Determine if static pressure reset is required for this # system. For 90.1, this determination needs information - # about whether or not the system has DDC control over the + # about whether or not the system has DDC control over the # VAV terminals. # # @todo Instead of requiring the input of whether a system # has DDC control of VAV terminals or not, determine this # from the system itself. This may require additional information @@ -3274,15 +3295,15 @@ # over VAV terminals. # return [Bool] returns true if static pressure reset is required, false if not def is_static_pressure_reset_required(template, has_ddc) sp_reset_required = false - + # A big number of btu per hr as the minimum requirement infinity_btu_per_hr = 999999999999 minimum_capacity_btu_per_hr = infinity_btu_per_hr - + # Determine the minimum capacity that requires an economizer case template when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004' # static pressure reset not required when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' @@ -3293,48 +3314,48 @@ OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: static pressure reset not required because the system does not have DDC control of VAV terminals.") end when 'NECB 2011' # static pressure reset not required end - + return sp_reset_required - + end # Determine if a system's fans must shut off when # not required. # # @param template [String] # @return [Bool] true if required, false if not def is_unoccupied_fan_shutoff_required(template) - + shutoff_required = true - + # Per 90.1 6.4.3.4.5, systems less than 0.75 HP # must turn off when unoccupied. minimum_fan_hp = nil case template - when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' + when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', 'NECB 2011' minimum_fan_hp = 0.75 end - + # Determine the system fan horsepower total_hp = 0.0 self.supply_return_exhaust_relief_fans.each do |fan| total_hp += fan.motorHorsepower end - + # Check the HP exception if total_hp < minimum_fan_hp shutoff_required = false OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: Unoccupied fan shutoff not required because system fan HP of #{total_hp.round(2)} HP is less than the minimum threshold of #{minimum_fan_hp} HP.") end return shutoff_required - + end - + # Shut off the system during unoccupied periods. # During these times, systems will cycle on briefly # if temperature drifts below setpoint. For systems # with fan-powered terminals, only the terminal fans will # cycle on. If the system already has a schedule other than @@ -3345,91 +3366,91 @@ # # @param min_occ_pct [Double] the fractional value below which # the system will be considered unoccupied. # @return [Bool] true if successful, false if not def enable_unoccupied_fan_shutoff(min_occ_pct = 0.15) - + # Set the system to night cycle night_cycle_type = 'CycleOnAny' # For VAV with PFP boxes, cycle zone fans only if self.demandComponents('OS:AirTerminal:SingleDuct:ParallelPIU:Reheat'.to_IddObjectType).size > 0 night_cycle_type = 'CycleOnAnyZoneFansOnly' end self.setNightCycleControlType(night_cycle_type) - + # Check if already using a schedule other than always on avail_sch = self.availabilitySchedule unless avail_sch == self.model.alwaysOnDiscreteSchedule OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: Availability schedule is already set to #{avail_sch.name}. Will assume this includes unoccupied shut down; no changes will be made.") return true end - + # Get the airloop occupancy schedule loop_occ_sch = self.get_occupancy_schedule(min_occ_pct) flh = loop_occ_sch.annual_equivalent_full_load_hrs OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: Annual occupied hours = #{flh.round} hr/yr, assuming a #{min_occ_pct} occupancy threshold. This schedule will be used as the HVAC operation schedule.") # Set HVAC availability schedule to follow occupancy self.setAvailabilitySchedule(loop_occ_sch) return true - + end - + # Calculate the total floor area of all zones attached # to the air loop, in m^2. # # return [Double] the total floor area of all zones attached # to the air loop, in m^2. def floor_area_served() - + total_area = 0.0 - + self.thermalZones.each do |zone| total_area += zone.floorArea end return total_area - + end # Calculate the total floor area of all zones attached # to the air loop that have no exterior surfaces, in m^2. # # return [Double] the total floor area of all zones attached # to the air loop, in m^2. def floor_area_served_interior_zones() - + total_area = 0.0 - + self.thermalZones.each do |zone| # Skip zones that have exterior surface area next if zone.exteriorSurfaceArea > 0 total_area += zone.floorArea end return total_area - - end - + + end + # Calculate the total floor area of all zones attached # to the air loop that have at least one exterior surface, in m^2. # # return [Double] the total floor area of all zones attached # to the air loop, in m^2. def floor_area_served_exterior_zones() - + total_area = 0.0 - + self.thermalZones.each do |zone| # Skip zones that have no exterior surface area next if zone.exteriorSurfaceArea == 0 total_area += zone.floorArea end return total_area - + end # find design_supply_air_flow_rate # # @return [Double] design_supply_air_flow_rate m^3/s @@ -3446,24 +3467,24 @@ end return design_supply_air_flow_rate end - + # Determine how much data center # area the airloop serves. # # @return [Double] the area of data center is served, # in m^2. # @todo Add an is_data_center field to the # standards space type spreadsheet instead # of relying on the standards space type name to # identify a data center. def data_center_area_served() - + dc_area_m2 = 0.0 - + self.thermalZones.each do |zone| zone.spaces.each do |space| # Skip spaces with no space type next if space.spaceType.empty? space_type = space.spaceType.get @@ -3472,11 +3493,11 @@ # Counts as a data center if the name includes 'data' next unless standards_space_type.downcase.include?('data') dc_area_m2 += space.floorArea end end - + return dc_area_m2 - + end - + end