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

- old
+ new

@@ -1,175 +1,187 @@ # Reopen the OpenStudio class to add methods to apply standards to this object class OpenStudio::Model::AirLoopHVAC - # Apply multizone vav outdoor air method and # adjust multizone VAV damper positions # to achieve a system minimum ventilation effectiveness # of 0.6 per PNNL. Hard-size the resulting min OA # into the sizing:system object. # # return [Bool] returns true if successful, false if not def apply_multizone_vav_outdoor_air_sizing(template) - - # TODO enable damper position adjustment for legacy IDFS + # 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.") + 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' # (2) AHU1 in 2004-2013 - if self.is_multizone_vav_system && !(self.name.to_s.include? "Outpatient F1") - self.adjust_minimum_vav_damper_positions + if multizone_vav_system? && !(name.to_s.include? 'Outpatient F1') + 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 + if (name.to_s.include? 'Outpatient') && (template == '90.1-2010' || template == '90.1-2013') + adjust_minimum_vav_damper_positions_outpatient end return true - end # Apply all standard required controls to the airloop # - # @param (see #is_economizer_required) + # @param (see #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 + if energy_recovery_ventilator_required?(template, climate_zone) + apply_energy_recovery_ventilator end # Economizers - self.set_economizer_limits(template, climate_zone) - self.set_economizer_integration(template, climate_zone) + apply_economizer_limits(template, climate_zone) + apply_economizer_integration(template, climate_zone) # Multizone VAV Systems - if self.is_multizone_vav_system + if multizone_vav_system? # VAV Reheat Control - self.set_vav_damper_action(template) + apply_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 + unless name.to_s.include? 'Outpatient F1' + if multizone_vav_optimization_required?(template, climate_zone) + enable_multizone_vav_optimization else - self.disable_multizone_vav_optimization + 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| - if fan.to_FanVariableVolume.is_initialized - fan.set_control_type('Multi Zone VAV with Static Pressure Reset') + # Static Pressure Reset + # assume no systems have DDC control of VAV terminals + has_ddc = false + spr_req = static_pressure_reset_required?(template, has_ddc) + supply_return_exhaust_relief_fans.each do |fan| + if fan.to_FanVariableVolume.is_initialized + plr_req = fan.part_load_fan_power_limitation?(template) + # Part Load Fan Pressure Control & Static Pressure Reset + if plr_req && spr_req + fan.set_control_type('Multi Zone VAV with VSD and Static Pressure Reset') + # Part Load Fan Pressure Control only + elsif plr_req && !spr_req + fan.set_control_type('Multi Zone VAV with VSD and Fixed SP Setpoint') + # Static Pressure Reset only + elsif !plr_req && spr_req + fan.set_control_type('Multi Zone VAV with VSD and Fixed SP Setpoint') + # No Control Required 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.") + fan.set_control_type('Multi Zone VAV with AF or BI Riding Curve') end + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.AirLoopHVAC', "For #{name}: there is a constant volume fan on a multizone vav system. Cannot apply static pressure reset controls.") end end end # Single zone systems - # if self.thermalZones.size == 1 - # self.apply_single_zone_controls(template, climate_zone) - # end + if self.thermalZones.size == 1 + supply_return_exhaust_relief_fans.each do |fan| + if fan.to_FanVariableVolume.is_initialized + fan.set_control_type('Single Zone VAV Fan') + end + end + # self.apply_single_zone_controls(template, climate_zone) + end # DCV - if self.is_demand_control_ventilation_required(template, climate_zone) - self.enable_demand_control_ventilation - else - # TODO Need to convert the design spec OA objects - # to per-area only so that if VRP is enabled we - # don't get DCV accidentally? See PNNL Achieving 30% 5.2.2.21, - # not convinced that this is actually necessary with current E+ - # capabilities. + if demand_control_ventilation_required?(template, climate_zone) + enable_demand_control_ventilation(template, climate_zone) end # SAT reset # 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) + if supply_air_temperature_reset_required?(template, climate_zone) + enable_supply_air_temperature_reset_warmest_zone(template) end # Unoccupied shutdown - if self.is_unoccupied_fan_shutoff_required(template) - self.enable_unoccupied_fan_shutoff + if unoccupied_fan_shutoff_required?(template) + enable_unoccupied_fan_shutoff else - self.setAvailabilitySchedule(self.model.alwaysOnDiscreteSchedule) + setAvailabilitySchedule(model.alwaysOnDiscreteSchedule) end # Motorized OA damper - if self.is_motorized_oa_damper_required(template, climate_zone) + if 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) + add_motorized_oa_damper(0.15, availabilitySchedule) else - self.remove_motorized_oa_damper + remove_motorized_oa_damper end - # TODO Optimum Start + # 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 # will replace the AvailabilityManager:NightCycle. - 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) + # @param (see #economizer_required?) # @return [Bool] returns true if successful, false if not - def apply_performance_rating_method_baseline_controls(template, climate_zone) - + def apply_prm_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) + if prm_baseline_economizer_required?(template, climate_zone) + apply_prm_baseline_economizer(template, climate_zone) end # Multizone VAV Systems - if self.is_multizone_vav_system + if multizone_vav_system? + # VSD no Static Pressure Reset on all VAV systems + # per G3.1.3.15 + supply_return_exhaust_relief_fans.each do |fan| + if fan.to_FanVariableVolume.is_initialized + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Setting fan part load curve per G3.1.3.15.") + fan.set_control_type('Multi Zone VAV with VSD and Fixed SP Setpoint') + end + end + # 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) + enable_supply_air_temperature_reset_warmest_zone(template) end end # Unoccupied shutdown - self.enable_unoccupied_fan_shutoff + enable_unoccupied_fan_shutoff return true - end # Calculate and apply the performance rating method # baseline fan power to this air loop. # Fan motor efficiency will be set, and then @@ -179,70 +191,67 @@ # of any parallel PIU terminals on the system. # # @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) - + def apply_prm_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) + allowable_fan_bhp = 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 = supply_return_exhaust_relief_fans + 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 all_fans.each do |fan| - fan.set_standard_minimum_motor_efficiency(template, allowable_fan_bhp) + fan.apply_standard_minimum_motor_efficiency(template, allowable_fan_bhp) 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| + 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) + pfp_term.apply_prm_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") - + 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 - 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 = Autosized Design Supply Air Flow Rate.") + if autosizedDesignSupplyAirFlowRate.is_initialized + dsn_air_flow_m3_per_s = autosizedDesignSupplyAirFlowRate.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 = 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.") + dsn_air_flow_m3_per_s = 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 - # TODO determine the presence of MERV filters and other stuff + # 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) @@ -250,113 +259,109 @@ # 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") + 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") + fan_pwr_adjustment_bhp = fan_pwr_adjustment_in_wc * dsn_air_flow_cfm / 4131 + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{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") - + 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 - 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 = Autosized Design Supply Air Flow Rate.") + if autosizedDesignSupplyAirFlowRate.is_initialized + dsn_air_flow_m3_per_s = autosizedDesignSupplyAirFlowRate.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 = 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.") + dsn_air_flow_m3_per_s = 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 # Get the fan limitation pressure drop adjustment bhp - fan_pwr_adjustment_bhp = self.fan_power_limitation_pressure_drop_adjustment_brake_horsepower + fan_pwr_adjustment_bhp = fan_power_limitation_pressure_drop_adjustment_brake_horsepower # Determine the number of zones the system serves - num_zones_served = self.thermalZones.size + num_zones_served = 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| + supplyComponents.reverse.each do |comp| if comp.to_FanConstantVolume.is_initialized || comp.to_FanOnOff.is_initialized - fan_pwr_limit_type = "constant volume" + fan_pwr_limit_type = 'constant volume' elsif comp.to_FanVariableVolume.is_initialized - fan_pwr_limit_type = "variable volume" + fan_pwr_limit_type = 'variable volume' elsif comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.is_initialized fan = comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.get.supplyAirFan if fan.to_FanConstantVolume.is_initialized || comp.to_FanOnOff.is_initialized - fan_pwr_limit_type = "constant volume" + fan_pwr_limit_type = 'constant volume' elsif fan.to_FanVariableVolume.is_initialized - fan_pwr_limit_type = "variable volume" + fan_pwr_limit_type = 'variable volume' end elsif comp.to_AirLoopHVACUnitarySystem.is_initialized fan = comp.to_AirLoopHVACUnitarySystem.get.supplyFan if fan.to_FanConstantVolume.is_initialized || comp.to_FanOnOff.is_initialized - fan_pwr_limit_type = "constant volume" + fan_pwr_limit_type = 'constant volume' elsif fan.to_FanVariableVolume.is_initialized - fan_pwr_limit_type = "variable volume" + fan_pwr_limit_type = 'variable volume' end end end # 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.") + 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 #{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 + 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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_m2 = 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.") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{name}: area served = #{floor_area_served_ft2.round} ft^2.") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{name}: flow per area = #{cfm_per_ft2.round} cfm/ft^2.") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{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 = supplyComponents + sup_and_oa_comps += oaComponents sup_and_oa_comps.each do |comp| if comp.to_FanConstantVolume.is_initialized fans << comp.to_FanConstantVolume.get elsif comp.to_FanVariableVolume.is_initialized fans << comp.to_FanVariableVolume.get @@ -380,34 +385,32 @@ 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 - def system_fan_brake_horsepower(include_terminal_fans = true, template = "ASHRAE 90.1-2007") - - # TODO get the template from the parent model itself? + 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{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 += supply_return_exhaust_relief_fans # Fans inside of fan-powered terminals if include_terminal_fans - self.demandComponents.each do |comp| + demandComponents.each do |comp| if comp.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized term_fan = comp.to_AirTerminalSingleDuctSeriesPIUReheat.get.supplyAirFan if term_fan.to_FanConstantVolume.is_initialized fans << term_fan.to_FanConstantVolume.get end @@ -422,154 +425,149 @@ # 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 + sys_fan_bhp += fan.brake_horsepower end return sys_fan_bhp - 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' - def set_baseline_fan_pressure_rise(template = "ASHRAE 90.1-2007") + def apply_baseline_fan_pressure_rise(template = 'ASHRAE 90.1-2007') + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{name}-Setting #{template} baseline fan power.") - 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) + proposed_sys_bhp = system_fan_brake_horsepower(true) # Get the allowable fan brake horsepower - allowable_fan_bhp = self.allowable_system_brake_horsepower(template) + allowable_fan_bhp = 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 + fan_pwr_adjustment_bhp = fan_power_limitation_pressure_drop_adjustment_brake_horsepower # Subtract the fan power adjustment - allowable_fan_bhp = allowable_fan_bhp-fan_pwr_adjustment_bhp + allowable_fan_bhp -= fan_pwr_adjustment_bhp # Get all fans - fans = self.supply_return_exhaust_relief_fans + fans = supply_return_exhaust_relief_fans - # TODO improve description + # 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") + 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}") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', fan.name.to_s) # Get the bhp of the fan on the proposed system - proposed_fan_bhp = fan.brakeHorsepower + proposed_fan_bhp = fan.brake_horsepower # 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.") + 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, # 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.") + baseline_impeller_eff = fan.baseline_impeller_efficiency(template) + fan.change_impeller_efficiency(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) + fan.change_motor_efficiency(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 - OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "* #{dsn_air_flow_cfm.round} cfm = Autosized Design Supply Air Flow Rate.") + 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 = Autosized Design Supply Air Flow Rate.") 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.") + 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.") + 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.") # Calculate the bhp of the fan to make sure it matches - calc_bhp = fan.brakeHorsepower - 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}.") + calc_bhp = fan.brake_horsepower + 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}.") + calc_sys_bhp = 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', "#{name} baseline system bhp supposed to be #{allowable_fan_bhp}, but is #{calc_sys_bhp}.") end - end # Get the total cooling capacity for the air loop # # @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| + supplyComponents.each do |sc| # CoilCoolingDXSingleSpeed if sc.to_CoilCoolingDXSingleSpeed.is_initialized coil = sc.to_CoilCoolingDXSingleSpeed.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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingWater elsif sc.to_CoilCoolingWater.is_initialized coil = sc.to_CoilCoolingWater.get - if coil.autosizedDesignCoilLoad.is_initialized # TODO Change to pull water coil nominal capacity instead of design load + if coil.autosizedDesignCoilLoad.is_initialized # TODO: Change to pull water coil nominal capacity instead of design load 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end elsif sc.to_AirLoopHVACUnitarySystem.is_initialized unitary = sc.to_AirLoopHVACUnitarySystem.get if unitary.coolingCoil.is_initialized clg_coil = unitary.coolingCoil.get @@ -579,39 +577,39 @@ if coil.ratedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedTotalCoolingCapacity.get elsif coil.autosizedRatedTotalCoolingCapacity.is_initialized 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingWater elsif clg_coil.to_CoilCoolingWater.is_initialized coil = clg_coil.to_CoilCoolingWater.get - if coil.autosizedDesignCoilLoad.is_initialized # TODO Change to pull water coil nominal capacity instead of design load + if coil.autosizedDesignCoilLoad.is_initialized # TODO: Change to pull water coil nominal capacity instead of design load 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end end end elsif sc.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized unitary = sc.to_AirLoopHVACUnitaryHeatPumpAirToAir.get @@ -622,37 +620,37 @@ if coil.ratedTotalCoolingCapacity.is_initialized total_cooling_capacity_w += coil.ratedTotalCoolingCapacity.get elsif coil.autosizedRatedTotalCoolingCapacity.is_initialized 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 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 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end # CoilCoolingWater elsif clg_coil.to_CoilCoolingWater.is_initialized coil = clg_coil.to_CoilCoolingWater.get - if coil.autosizedDesignCoilLoad.is_initialized # TODO Change to pull water coil nominal capacity instead of design load + if coil.autosizedDesignCoilLoad.is_initialized # TODO: Change to pull water coil nominal capacity instead of design load 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{coil.name} is not available, total cooling capacity of air loop will be incorrect when applying standard.") end end elsif sc.to_CoilCoolingDXMultiSpeed.is_initialized || - sc.to_CoilCoolingCooledBeam.is_initialized || - sc.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.is_initialized || - sc.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized || - sc.to_AirLoopHVACUnitarySystem.is_initialized - 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.") + sc.to_CoilCoolingCooledBeam.is_initialized || + sc.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.is_initialized || + sc.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized || + sc.to_AirLoopHVACUnitarySystem.is_initialized + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "#{name} has a cooling coil named #{sc.name}, whose type is not yet covered by economizer checks.") # CoilCoolingDXMultiSpeed # CoilCoolingCooledBeam # CoilCoolingWaterToAirHeatPumpEquationFit # AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass # AirLoopHVACUnitaryHeatPumpAirToAir @@ -660,11 +658,10 @@ # AirLoopHVACUnitarySystem end end return total_cooling_capacity_w - end # Determine whether or not this system # is required to have an economizer. # @@ -672,24 +669,23 @@ # @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' # @return [Bool] returns true if an economizer is required, false if not - def is_economizer_required(template, climate_zone) - + def economizer_required?(template, climate_zone) economizer_required = false - return economizer_required if self.name.to_s.include? "Outpatient F1" + return economizer_required if name.to_s.include? 'Outpatient F1' # A big number of btu per hr as the minimum requirement - infinity_btu_per_hr = 999999999999 + infinity_btu_per_hr = 999_999_999_999 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 + if data_center_area_served > 0 is_dc = true end # Determine the minimum capacity that requires an economizer case template @@ -706,19 +702,19 @@ '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 = 35000 + minimum_capacity_btu_per_hr = 35_000 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 + minimum_capacity_btu_per_hr = 65_000 end when '90.1-2010', '90.1-2013' if is_dc # data center / computer room case climate_zone when 'ASHRAE 169-2006-1A', @@ -732,19 +728,19 @@ '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 = 135_000 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 + minimum_capacity_btu_per_hr = 65_000 end else case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B' @@ -764,47 +760,45 @@ '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 = 54000 + minimum_capacity_btu_per_hr = 54_000 end end when 'NECB 2011' - minimum_capacity_btu_per_hr = 68243 # NECB requires economizer for cooling cap > 20 kW + minimum_capacity_btu_per_hr = 68_243 # 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 + total_cooling_capacity_w = 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 if is_dc - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{self.name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr for data centers.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds 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} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{name} requires an economizer because the total cooling capacity of #{total_cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum capacity of #{minimum_capacity_btu_per_hr.round} Btu/hr.") end economizer_required = true 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "#{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 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) + # @param (see #economizer_required?) # @return [Bool] returns true if successful, false if not - def set_economizer_limits(template, climate_zone) - + def apply_economizer_limits(template, climate_zone) # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' @@ -812,11 +806,11 @@ # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' # 'DifferentialDryBulbAndEnthalpy' # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end @@ -894,51 +888,55 @@ drybulb_limit_f = 75 dewpoint_limit_f = 55 end end + # Reset the limits + oa_control.resetEconomizerMaximumLimitDryBulbTemperature + oa_control.resetEconomizerMaximumLimitEnthalpy + oa_control.resetEconomizerMaximumLimitDewpointTemperature + oa_control.resetEconomizerMinimumLimitDryBulbTemperature + # Set the limits case economizer_type when 'FixedDryBulb' if drybulb_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get oa_control.setEconomizerMaximumLimitDryBulbTemperature(drybulb_limit_c) - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F") end when 'FixedEnthalpy' if enthalpy_limit_btu_per_lb enthalpy_limit_j_per_kg = OpenStudio.convert(enthalpy_limit_btu_per_lb, 'Btu/lb', 'J/kg').get oa_control.setEconomizerMaximumLimitEnthalpy(enthalpy_limit_j_per_kg) - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, enthalpy limit = #{enthalpy_limit_btu_per_lb}Btu/lb") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, enthalpy limit = #{enthalpy_limit_btu_per_lb}Btu/lb") end when 'FixedDewPointAndDryBulb' if drybulb_limit_f && dewpoint_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get 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") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F, dew-point limit = #{dewpoint_limit_f}F") 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) + # via #economizer_required? + # @param (see #economizer_required?) # @return [Bool] returns true if successful, false if not - def set_economizer_integration(template, climate_zone) - + def apply_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| + supplyComponents.reverse.each do |comp| if comp.to_FanVariableVolume.is_initialized is_vav = true elsif comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.is_initialized fan = comp.to_AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass.get.supplyAirFan if fan.to_FanVariableVolume.is_initialized @@ -953,31 +951,31 @@ end end end # Determine the number of zones the system serves - num_zones_served = self.thermalZones.size + num_zones_served = thermalZones.size # A big number of btu per hr as the minimum requirement - infinity_btu_per_hr = 999999999999 + infinity_btu_per_hr = 999_999_999_999 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' - minimum_capacity_btu_per_hr = 65000 - minimum_capacity_w = OpenStudio.convert(minimum_capacity_btu_per_hr, "Btu/hr", "W").get + minimum_capacity_btu_per_hr = 65_000 + 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 integrated_economizer_required = false - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: non-integrated economizer per 6.5.1.3 exception a, DX VAV system.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: non-integrated economizer per 6.5.1.3 exception a, DX VAV system.") # Exception b, DX units less than 65,000 Btu/hr - elsif self.total_cooling_capacity < minimum_capacity_w + elsif total_cooling_capacity < minimum_capacity_w integrated_economizer_required = false - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: non-integrated economizer per 6.5.1.3 exception b, DX system less than #{minimum_capacity_btu_per_hr}Btu/hr.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: non-integrated economizer per 6.5.1.3 exception b, DX system less than #{minimum_capacity_btu_per_hr}Btu/hr.") else # Exception c, Systems in climate zones 1,2,3a,4a,5a,5b,6,7,8 case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', @@ -992,11 +990,11 @@ 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' integrated_economizer_required = false - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: non-integrated economizer per 6.5.1.3 exception c, climate zone #{climate_zone}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: non-integrated economizer per 6.5.1.3 exception c, climate zone #{climate_zone}.") when 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5C' @@ -1010,11 +1008,11 @@ # (NoLockout); as per 5.2.2.8(3) integrated_economizer_required = true end # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end @@ -1026,23 +1024,21 @@ else oa_control.setLockoutType('LockoutWithCompressor') end return true - end # Determine if an economizer is required per the PRM. # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @return [Bool] returns true if required, false if not - def is_performance_rating_method_baseline_economizer_required(template, climate_zone) - + def prm_baseline_economizer_required?(template, climate_zone) economizer_required = false # A big number of ft2 as the minimum requirement - infinity_ft2 = 999999999999 + infinity_ft2 = 999_999_999_999 min_int_area_served_ft2 = infinity_ft2 min_ext_area_served_ft2 = infinity_ft2 # Determine the minimum capacity that requires an economizer case template @@ -1060,21 +1056,21 @@ 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' - min_int_area_served_ft2 = 15000 + min_int_area_served_ft2 = 15_000 min_ext_area_served_ft2 = infinity_ft2 # No requirement 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' - min_int_area_served_ft2 = 10000 - min_ext_area_served_ft2 = 25000 + min_int_area_served_ft2 = 10_000 + min_ext_area_served_ft2 = 25_000 end when '90.1-2007', '90.1-2010', '90.1-2013' case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', @@ -1089,41 +1085,39 @@ 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 + 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 + int_area_served_m2 = floor_area_served_interior_zones + ext_area_served_m2 = 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}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer required for the performance rating method baseline.") return economizer_required - end # Apply the PRM economizer type and set temperature limits # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @return [Bool] returns true if successful, false if not - def apply_performance_rating_method_baseline_economizer(template, climate_zone) - + def apply_prm_baseline_economizer(template, climate_zone) # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' @@ -1131,11 +1125,11 @@ # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' # 'DifferentialDryBulbAndEnthalpy' # Determine the type and limits - economizer_type = nil + economizer_type = 'NoEconomizer' drybulb_limit_f = nil enthalpy_limit_btu_per_lb = nil dewpoint_limit_f = nil case template when '90.1-2004', '90.1-2007', '90.1-2010' @@ -1161,11 +1155,11 @@ drybulb_limit_f = 70 else economizer_type = 'FixedDryBulb' drybulb_limit_f = 65 end - when '90.1-2013' + when '90.1-2013' case climate_zone when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', @@ -1195,54 +1189,61 @@ drybulb_limit_f = 65 end end # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = 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 economizer type + oa_control.setEconomizerControlType(economizer_type) + + # Reset the limits + oa_control.resetEconomizerMaximumLimitDryBulbTemperature + oa_control.resetEconomizerMaximumLimitEnthalpy + oa_control.resetEconomizerMaximumLimitDewpointTemperature + oa_control.resetEconomizerMinimumLimitDryBulbTemperature + # Set the limits case economizer_type when 'FixedDryBulb' if drybulb_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get oa_control.setEconomizerMaximumLimitDryBulbTemperature(drybulb_limit_c) - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F") end when 'FixedEnthalpy' if enthalpy_limit_btu_per_lb enthalpy_limit_j_per_kg = OpenStudio.convert(enthalpy_limit_btu_per_lb, 'Btu/lb', 'J/kg').get oa_control.setEconomizerMaximumLimitEnthalpy(enthalpy_limit_j_per_kg) - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Economizer type = #{economizer_type}, enthalpy limit = #{enthalpy_limit_btu_per_lb}Btu/lb") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, enthalpy limit = #{enthalpy_limit_btu_per_lb}Btu/lb") end when 'FixedDewPointAndDryBulb' if drybulb_limit_f && dewpoint_limit_f drybulb_limit_c = OpenStudio.convert(drybulb_limit_f, 'F', 'C').get 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") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Economizer type = #{economizer_type}, dry bulb limit = #{drybulb_limit_f}F, dew-point limit = #{dewpoint_limit_f}F") 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) + # @param (see #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) - + def economizer_type_allowable?(template, climate_zone) # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' @@ -1250,11 +1251,11 @@ # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' # 'DifferentialDryBulbAndEnthalpy' # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return true # No OA system end @@ -1293,11 +1294,11 @@ when 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', prohibited_types = [] end - when '90.1-2010', '90.1-2013' + when '90.1-2010', '90.1-2013' case climate_zone when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', @@ -1328,19 +1329,18 @@ 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) + # @param (see #economizer_required?) # @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) + def energy_recovery_ventilator_required?(template, climate_zone) # 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') || @@ -1349,61 +1349,60 @@ # 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" + if 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" + if name.to_s.include? 'VAV_ER' erv_required = false return erv_required - elsif self.name.to_s.include? "VAV_OR" + elsif 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 + when '90.1-2004', '90.1-2007' + if name.to_s.include? 'VAV_ICU' + erv_required = false + return erv_required + elsif 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. controller_oa = nil controller_mv = nil oa_system = nil - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 - dsn_flow_m3_per_s = self.autosizedDesignSupplyAirFlowRate.get + if designSupplyAirFlowRate.is_initialized + dsn_flow_m3_per_s = designSupplyAirFlowRate.get + elsif autosizedDesignSupplyAirFlowRate.is_initialized + dsn_flow_m3_per_s = autosizedDesignSupplyAirFlowRate.get else - OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name} design supply air flow rate is not available, cannot apply efficiency standard.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -1411,27 +1410,28 @@ if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get else - OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.") + 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 + 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 - erv_cfm = nil - else - erv_cfm = 5000 - end + erv_cfm = if pct_oa < 0.7 + nil + else + # @Todo: Add exceptions (eg: e. cooling systems in climate zones 3C, 4C, 5B, 5C, 6B, 7 and 8 | d. Heating systems in climate zones 1 to 3) + 5000 + end when '90.1-2010' # Table 6.5.6.1 case climate_zone when 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5B' if pct_oa < 0.3 @@ -1455,23 +1455,23 @@ elsif pct_oa >= 0.3 && pct_oa < 0.4 erv_cfm = nil elsif pct_oa >= 0.4 && pct_oa < 0.5 erv_cfm = nil elsif pct_oa >= 0.5 && pct_oa < 0.6 - erv_cfm = 26000 + erv_cfm = 26_000 elsif pct_oa >= 0.6 && pct_oa < 0.7 - erv_cfm = 12000 + erv_cfm = 12_000 elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = 5000 elsif pct_oa >= 0.8 erv_cfm = 4000 end when 'ASHRAE 169-2006-6B' if pct_oa < 0.3 erv_cfm = nil elsif pct_oa >= 0.3 && pct_oa < 0.4 - erv_cfm = 11000 + erv_cfm = 11_000 elsif pct_oa >= 0.4 && pct_oa < 0.5 erv_cfm = 5500 elsif pct_oa >= 0.5 && pct_oa < 0.6 erv_cfm = 4500 elsif pct_oa >= 0.6 && pct_oa < 0.7 @@ -1513,54 +1513,173 @@ elsif pct_oa >= 0.8 erv_cfm = 0 end end when '90.1-2013' - # Table 6.5.6.1-2 - case climate_zone - when 'ASHRAE 169-2006-3C' - erv_cfm = nil - when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5C' - if pct_oa < 0.1 - erv_cfm = nil - elsif pct_oa >= 0.1 && pct_oa < 0.2 - erv_cfm = nil - elsif pct_oa >= 0.2 && pct_oa < 0.3 - erv_cfm = 19500 - elsif pct_oa >= 0.3 && pct_oa < 0.4 - erv_cfm = 9000 - elsif pct_oa >= 0.4 && pct_oa < 0.5 - erv_cfm = 5000 - elsif pct_oa >= 0.5 && pct_oa < 0.6 - 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 - erv_cfm = 0 + # Calculate the number of system operating hours + # based on the availability schedule. + ann_op_hrs = 0.0 + avail_sch = availabilitySchedule + if avail_sch == model.alwaysOnDiscreteSchedule + ann_op_hrs = 8760.0 + elsif avail_sch.to_ScheduleRuleset.is_initialized + avail_sch = avail_sch.to_ScheduleRuleset.get + ann_op_hrs = avail_sch.annual_hours_above_value(0.0) + else + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}: could not determine annual operating hours. Assuming less than 8,000 for ERV determination.") + end + + if ann_op_hrs < 8000.0 + # Table 6.5.6.1-1, less than 8000 hrs + case climate_zone + when 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5B' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = nil + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = nil + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = nil + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = nil + elsif pct_oa >= 0.5 && pct_oa < 0.6 + 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 = nil + elsif pct_oa >= 0.8 + erv_cfm = nil + end + when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-5C' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = nil + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = nil + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = nil + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = nil + elsif pct_oa >= 0.5 && pct_oa < 0.6 + erv_cfm = 26_000 + elsif pct_oa >= 0.6 && pct_oa < 0.7 + erv_cfm = 12_000 + elsif pct_oa >= 0.7 && pct_oa < 0.8 + erv_cfm = 5000 + elsif pct_oa >= 0.8 + erv_cfm = 4000 + end + when 'ASHRAE 169-2006-6B' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = 28_000 + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = 26_500 + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = 11_000 + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = 5500 + elsif pct_oa >= 0.5 && pct_oa < 0.6 + 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 + erv_cfm = 1500 + 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.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = 26_000 + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = 16_000 + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = 5500 + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = 4500 + elsif pct_oa >= 0.5 && pct_oa < 0.6 + 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 + erv_cfm = 0 + end + when 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = 4500 + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = 4000 + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = 2500 + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = 1000 + elsif pct_oa >= 0.5 && pct_oa < 0.6 + 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 + erv_cfm = 0 + end 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 + else + # Table 6.5.6.1-2, above 8000 hrs + case climate_zone + when 'ASHRAE 169-2006-3C' erv_cfm = nil - elsif pct_oa >= 0.1 && pct_oa < 0.2 - erv_cfm = 2500 - elsif pct_oa >= 0.2 && pct_oa < 0.3 - erv_cfm = 2000 - elsif pct_oa >= 0.3 && pct_oa < 0.4 - erv_cfm = 1000 - elsif pct_oa >= 0.4 && pct_oa < 0.5 - erv_cfm = 500 - elsif pct_oa >= 0.5 - erv_cfm = 0 + when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5C' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = nil + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = 19_500 + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = 9000 + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = 5000 + elsif pct_oa >= 0.5 && pct_oa < 0.6 + 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 + 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 + elsif pct_oa >= 0.1 && pct_oa < 0.2 + erv_cfm = 2500 + elsif pct_oa >= 0.2 && pct_oa < 0.3 + erv_cfm = 2000 + elsif pct_oa >= 0.3 && pct_oa < 0.4 + erv_cfm = 1000 + elsif pct_oa >= 0.4 && pct_oa < 0.5 + erv_cfm = 500 + elsif pct_oa >= 0.5 + erv_cfm = 0 + end + when 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' + if pct_oa < 0.1 + erv_cfm = nil + elsif pct_oa >= 0.1 + erv_cfm = 0 + end end - when 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B' - if pct_oa < 0.1 - erv_cfm = nil - elsif pct_oa >= 0.1 - 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 # erv_cfm set to nil here as placeholder, will lead to erv_required = false @@ -1568,129 +1687,121 @@ 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}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}.") 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, ERV not required based on #{(pct_oa * 100).round}% OA flow, design supply air 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 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, ERV required based on #{(pct_oa * 100).round}% OA flow, design supply air flow of #{dsn_flow_cfm.round}cfm, and climate zone #{climate_zone}. Exceeds minimum flow requirement of #{erv_cfm}cfm.") 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' # get all zones in the model - zones = self.thermalZones + zones = thermalZones # initialize counters sum_zone_oa = 0.0 - sum_zoneoaTimesheatDesignT = 0.0 + sum_zone_oa_times_heat_design_t = 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 + heat_design_t = 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 + unless space.designSpecificationOutdoorAir.empty? # if empty, don't do anything 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 + oa_flow = oa_flow_per_person * num_people # oa flow for the space + zone_oa += oa_flow # add up oa flow for all spaces to get zone air flow end + end # space loop - end # space loop + sum_zone_oa += zone_oa # sum of all zone oa flows to get system oa flow + sum_zone_oa_times_heat_design_t += (zone_oa * heat_design_t) # calculated to get oa flow weighted average of design exhaust temperature + end # zone 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 + avg_exhaust_temp = sum_zone_oa_times_heat_design_t / sum_zone_oa # for debugging/testing -# puts "average exhaust temp = #{avg_exhaust_temp}" -# puts "sum_zone_oa = #{sum_zone_oa}" + # 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) + weather_file = BTAP::Environment::WeatherFile.new(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}" + # for debugging/testing + # 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}" + # puts "exhaust heat content = #{exhaust_heat_content}" - # Modify erv_required based on exhaust heat content - if ( exhaust_heat_content > 150.0 ) then + if exhaust_heat_content > 150.0 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 #{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 #{name}, ERV not required based on exhaust heat content.") end + end # of NECB 2011 condition - - end # of NECB 2011 condition - # for debugging/testing -# puts "erv_required = #{erv_required}" + # puts "erv_required = #{erv_required}" return erv_required - end # Add an ERV to this airloop. # Will be a rotary-type HX # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @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() - + def apply_energy_recovery_ventilator # Get the oa system oa_system = nil - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get else - OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}, ERV cannot be added because the system has no OA intake.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model) + erv.setName("#{name} ERV") erv.setSensibleEffectivenessat100HeatingAirFlow(0.7) erv.setLatentEffectivenessat100HeatingAirFlow(0.6) erv.setSensibleEffectivenessat75HeatingAirFlow(0.7) erv.setLatentEffectivenessat75HeatingAirFlow(0.6) erv.setSensibleEffectivenessat100CoolingAirFlow(0.75) @@ -1731,25 +1842,23 @@ # Attach to the outlet of the ERV erv_outlet = erv.primaryAirOutletModelObject.get.to_Node.get spm_oa_pretreat.addToNode(erv_outlet) # Apply the prototype Heat Exchanger power assumptions. - erv.setPrototypeNominalElectricPower + erv.apply_prototype_nominal_electric_power return true - end # Determine if multizone vav optimization is required. # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @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) - + def 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' @@ -1758,47 +1867,47 @@ 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| + demandComponents.each do |comp| 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") + if energy_recovery? + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 - dsn_flow_m3_per_s = self.autosizedDesignSupplyAirFlowRate.get + if designSupplyAirFlowRate.is_initialized + dsn_flow_m3_per_s = designSupplyAirFlowRate.get + elsif autosizedDesignSupplyAirFlowRate.is_initialized + dsn_flow_m3_per_s = autosizedDesignSupplyAirFlowRate.get else - OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{self.name} design supply air flow rate is not available, cannot apply efficiency standard.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -1806,138 +1915,132 @@ if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.autosizedMinimumOutdoorAirFlowRate.get else - OpenStudio::logFree(OpenStudio::Warn, "openstudio.standards.AirLoopHVAC", "For #{controller_oa.name}: minimum OA flow rate is not available, cannot apply efficiency standard.") + 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 + 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{controller_oa.name}: multizone optimization is not applicable because system is more than 70% OA.") return multizone_opt_required end - # TODO Not required for dual-duct systems + # 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 + # OpenStudio::logFree(OpenStudio::Info, "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 # 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. def enable_multizone_vav_optimization - # Enable multizone vav optimization # at each timestep. - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}, cannot enable multizone vav optimization because the system has no OA intake.") return false 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 + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}, cannot disable multizone vav optimization because the system has no OA intake.") return false end - end - # Set the minimum VAV damper positions + # Set the minimum VAV damper positions. # - # - def set_minimum_vav_damper_positions(template) - - self.thermalZones.each do |zone| + # @param template [String] the building template + # @param has_ddc [Bool] if true, will assume that there + # is DDC control of vav terminals. If false, assumes otherwise. + # @return [Bool] true if successful, false if not + def apply_minimum_vav_damper_positions(template, has_ddc = true) + 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 + zone_oa = zone.outdoor_airflow_rate vav_terminal = equip.to_AirTerminalSingleDuctVAVReheat.get - vav_terminal.set_minimum_damper_position(template, zone_oa_per_area) + vav_terminal.apply_minimum_damper_position(template, zone_oa, has_ddc) end end end return true - end # Adjust minimum VAV damper positions to the values # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @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| + 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 = if autosizedDesignSupplyAirFlowRate.is_initialized + autosizedDesignSupplyAirFlowRate.get + else + 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 #{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| - + thermalZones.sort.each do |zone| # Breathing zone airflow rate v_bz = zone.outdoor_airflow_rate # Zone air distribution, assumed 1 per PNNL e_z = 1.0 @@ -1954,63 +2057,74 @@ clg_dsn_flow = clg_dsn_flow.get if clg_dsn_flow > v_pz v_pz = clg_dsn_flow end else - OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: #{zone.name} clg_dsn_flow could not be found.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}: #{zone.name} clg_dsn_flow could not be found.") end htg_dsn_flow = zone.autosizedHeatingDesignAirFlowRate if htg_dsn_flow.is_initialized htg_dsn_flow = htg_dsn_flow.get if htg_dsn_flow > v_pz 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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}: #{zone.name} htg_dsn_flow could not be found.") end # Get the minimum damper position - mdp = 1.0 + mdp_term = 1.0 + min_zn_flow = 0.0 zone.equipment.each do |equip| if equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolNoReheat.get - mdp = term.zoneMinimumAirFlowFraction + mdp_term = term.zoneMinimumAirFlowFraction elsif equip.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get - mdp = term.zoneMinimumAirFlowFraction + mdp_term = term.zoneMinimumAirFlowFraction elsif equip.to_AirTerminalSingleDuctVAVNoReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVNoReheat.get if term.constantMinimumAirFlowFraction.is_initialized - mdp = term.constantMinimumAirFlowFraction.get + mdp_term = term.constantMinimumAirFlowFraction.get end elsif equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get - mdp = term.constantMinimumAirFlowFraction + mdp_term = term.constantMinimumAirFlowFraction + min_zn_flow = term.fixedMinimumAirFlowRate end end + # For VAV Reheat terminals, min flow is greater of mdp + # and min flow rate / design flow rate. + mdp = mdp_term + mdp_oa = min_zn_flow / v_ps + if min_zn_flow > 0.0 + mdp = [mdp_term, mdp_oa].max.round(2) + end + # OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Zone #{zone.name} mdp_term = #{mdp_term.round(2)}, mdp_oa = #{mdp_oa.round(2)}; mdp_final = #{mdp}") + # Zone minimum discharge airflow rate - v_dz = v_pz*mdp + 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 + 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)}.") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{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 + 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 @@ -2020,11 +2134,11 @@ if mdp_adj > 1.0 mdp_adj = 1.0 end # Zone ventilation effectiveness - e_vz_adj = 1+x_s-z_d_adj + 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 @@ -2044,17 +2158,16 @@ 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)}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -2069,39 +2182,38 @@ 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)}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 = 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" # Reference: <Achieving the 30% Goal: Energy and Cost Savings Analysis of ASHRAE Standard 90.1-2010> Page109-111 # For implementation purpose, since it is time-consuming to perform autosizing in three climate zones, just use # the results of the current climate zone def adjust_minimum_vav_damper_positions_outpatient - self.model.getSpaces.each do |space| + model.getSpaces.each do |space| zone = space.thermalZone.get - sizingZone = zone.sizingZone + sizing_zone = zone.sizingZone space_area = space.floorArea - if sizingZone.coolingDesignAirFlowMethod == 'DesignDay' + if sizing_zone.coolingDesignAirFlowMethod == 'DesignDay' next - elsif sizingZone.coolingDesignAirFlowMethod == 'DesignDayWithLimit' - minimum_airflow_per_zone_floor_area = sizingZone.coolingMinimumAirFlowperZoneFloorArea + elsif sizing_zone.coolingDesignAirFlowMethod == 'DesignDayWithLimit' + minimum_airflow_per_zone_floor_area = sizing_zone.coolingMinimumAirFlowperZoneFloorArea minimum_airflow_per_zone = minimum_airflow_per_zone_floor_area * space_area # get the autosized maximum air flow of the VAV terminal zone.equipment.each do |equip| if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized vav_terminal = equip.to_AirTerminalSingleDuctVAVReheat.get @@ -2121,175 +2233,162 @@ end # Determine if demand control ventilation (DCV) is # required for this air loop. # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @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) - + def 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' || template == 'NECB 2011' - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}: #{self.name}: DCV is not required for any system.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{template} #{climate_zone}: #{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.") + if energy_recovery? + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 + # OA flow limits min_oa_without_economizer_cfm = 0 min_oa_with_economizer_cfm = 0 case template when '90.1-2004' - min_area_ft2 = 0 - min_occ_per_1000_ft2 = 100 min_oa_without_economizer_cfm = 3000 min_oa_with_economizer_cfm = 0 when '90.1-2007', '90.1-2010' - min_area_ft2 = 500 - min_occ_per_1000_ft2 = 40 min_oa_without_economizer_cfm = 3000 min_oa_with_economizer_cfm = 1200 when '90.1-2013' - 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 - - 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 oa_flow_m3_per_s = 0 - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 + if oa_flow_cfm < min_oa_without_economizer_cfm && 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") end # Message if doesn't have economizer - if self.has_economizer == false - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: DCV is not required since the system does not have an economizer.") + if economizer? == false + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: DCV is not required since the system does not have an economizer.") end return dcv_required end # 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.") + if oa_flow_cfm < min_oa_with_economizer_cfm && economizer? + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 + # Check area and density limits + # for all of zones on the loop + any_zones_req_dcv = false + thermalZones.sort.each do |zone| + if zone.demand_control_ventilation_required?(template, climate_zone) + any_zones_req_dcv = true + break + end + end + unless any_zones_req_dcv + return dcv_required + end + # If here, DCV is required dcv_required = true return dcv_required - end # Enable demand control ventilation (DCV) for this air loop. + # Zones on this loop that require DCV preserve + # both per-area and per-person OA reqs. + # Other zones have OA reqs converted + # to per-area values only so that DCV won't impact these zones. # # @return [Bool] Returns true if required, false if not. - def enable_demand_control_ventilation() - + def enable_demand_control_ventilation(template, climate_zone) # Get the OA intake controller_oa = nil controller_mv = nil - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{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 + # Zones that require DCV preserve + # both per-area and per-person OA reqs. + # Other zones have OA reqs converted + # to per-area values only so that DCV + thermalZones.sort.each do |zone| + if zone.demand_control_ventilation_required?(template, climate_zone) + zone.convert_oa_req_to_per_area + end + end + return true end # Determine if the system required supply air temperature # (SAT) reset. # - # @param (see #is_economizer_required) + # @param (see #economizer_required?) # @return [Bool] Returns true if required, false if not. - def is_supply_air_temperature_reset_required(template, climate_zone) - + def 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 + return is_sat_reset_required unless 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' case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A' - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Supply air temperature reset is not required per 6.5.3.4 Exception 1, the system is located in climate zone #{climate_zone}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Supply air temperature reset is not required per 6.5.3.4 Exception 1, the system is located in climate zone #{climate_zone}.") when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4A', @@ -2303,117 +2402,110 @@ '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 #{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. # # @param template [String] valid choices: '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013' # @return [Bool] Returns true if successful, false if not. def enable_supply_air_temperature_reset_warmest_zone(template) - # Get the current setpoint and calculate # the new setpoint. - sizing_system = self.sizingSystem + sizing_system = sizingSystem design_sat_c = sizing_system.centralCoolingDesignSupplyAirTemperature - design_sat_f = OpenStudio::convert(design_sat_c, 'C','F').get + design_sat_f = OpenStudio.convert(design_sat_c, 'C', 'F').get - case template - when '90.1-2004' - # 2004 has a 10F sat reset - sat_reset_r = 10 - when '90.1-2007', '90.1-2010', '90.1-2013' - sat_reset_r = 5 + when '90.1-2004' + # 2004 has a 10F sat reset + sat_reset_r = 10 + when '90.1-2007', '90.1-2010', '90.1-2013' + sat_reset_r = 5 end sat_reset_k = OpenStudio.convert(sat_reset_r, 'R', 'K').get max_sat_f = design_sat_f + sat_reset_r max_sat_c = design_sat_c + sat_reset_k # Create a setpoint manager sat_warmest_reset = OpenStudio::Model::SetpointManagerWarmest.new(model) - sat_warmest_reset.setName("#{self.name} SAT Warmest Reset") + sat_warmest_reset.setName("#{name} SAT Warmest Reset") sat_warmest_reset.setStrategy('MaximumTemperature') sat_warmest_reset.setMinimumSetpointTemperature(design_sat_c) sat_warmest_reset.setMaximumSetpointTemperature(max_sat_c) # Attach the setpoint manager to the # supply outlet node of the system. - sat_warmest_reset.addToNode(self.supplyOutletNode) + sat_warmest_reset.addToNode(supplyOutletNode) - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: Supply air temperature reset was enabled using a SPM Warmest with a min SAT of #{design_sat_c.round}C // #{design_sat_f.round}F and a max SAT of #{max_sat_c.round}C // #{max_sat_f.round}F.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Supply air temperature reset was enabled using a SPM Warmest with a min SAT of #{design_sat_f.round}F and a max SAT of #{max_sat_f.round}F.") return true - end # Enable supply air temperature (SAT) reset based # 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. - def enable_supply_air_temperature_reset_outdoor_temperature() - + 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' + return true if name.get == 'PVAV Outpatient F1' # Get the current setpoint and calculate # the new setpoint. - sizing_system = self.sizingSystem + sizing_system = 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_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.setName("#{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) + sat_oa_reset.addToNode(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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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. - def has_economizer() - + def economizer? # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end @@ -2424,52 +2516,67 @@ 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. - def is_multizone_vav_system() + def multizone_vav_system? + multizone_vav_system = false - is_multizone_vav_system = false - # Must serve more than 1 zone - if self.thermalZones.size < 2 - return is_multizone_vav_system + if thermalZones.size < 2 + return multizone_vav_system end # Must be a variable volume system has_vav_fan = false - self.supplyComponents.each do |comp| + 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 + return multizone_vav_system end # If here, it's a multizone VAV system - is_multizone_vav_system = true + multizone_vav_system = true - return is_multizone_vav_system + return multizone_vav_system + end + # Determine if the system has terminal reheat + # + # @return [Bool] returns true if has one or more reheat terminals, false if it doesn't. + def terminal_reheat? + has_term_rht = false + demandComponents.each do |sc| + if sc.to_AirTerminalSingleDuctConstantVolumeReheat.is_initialized || + sc.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized || + sc.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized || + sc.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.is_initialized || + sc.to_AirTerminalSingleDuctVAVReheat.is_initialized + has_term_rht = true + break + end + end + + return has_term_rht end # Determine if the system has energy recovery already # # @return [Bool] Returns true if an ERV is present, false if not. - def has_energy_recovery() - + def energy_recovery? has_erv = false # Get the OA system - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return has_erv # No OA system end @@ -2480,19 +2587,18 @@ has_erv = true end end return has_erv - end # Set the VAV damper control to single maximum or # dual maximum control depending on the standard. # # @return [Bool] Returns true if successful, false if not # @todo see if this impacts the sizing run. - def set_vav_damper_action(template) + def apply_vav_damper_action(template) damper_action = nil 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' @@ -2507,39 +2613,49 @@ damper_action_eplus = 'Reverse' end # Set the control for any VAV reheat terminals # on this airloop. - self.demandComponents.each do |equip| + control_type_set = false + demandComponents.each do |equip| if equip.to_AirTerminalSingleDuctVAVReheat.is_initialized term = equip.to_AirTerminalSingleDuctVAVReheat.get - term.setDamperHeatingAction(damper_action_eplus) + # Dual maximum only applies to terminals with HW reheat coils + if damper_action == 'Dual Maximum' + if term.reheatCoil.to_CoilHeatingWater.is_initialized + term.setDamperHeatingAction(damper_action_eplus) + control_type_set = true + end + else + term.setDamperHeatingAction(damper_action_eplus) + control_type_set = true + end end end - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: VAV damper action was set to #{damper_action} control.") + if control_type_set + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: VAV damper action was set to #{damper_action} control.") + end return true - end # Determine if a motorized OA damper is required - def is_motorized_oa_damper_required(template, climate_zone) - + def motorized_oa_damper_required?(template, climate_zone) motorized_oa_damper_required = false - if self.name.to_s.include? "Outpatient F1" + if 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 + if 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -2564,11 +2680,11 @@ maximum_stories = 999 # Any number of stories else minimum_oa_flow_cfm = 300 maximum_stories = 3 end - when '90.1-2010', '90.1-2013' + when '90.1-2010', '90.1-2013' case climate_zone when 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-2B', @@ -2582,46 +2698,45 @@ maximum_stories = 0 end end # Get the number of stories - num_stories = self.model.getBuildingStorys.size + num_stories = 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}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 oa_flow_m3_per_s = 0 - if self.airLoopHVACOutdoorAirSystem.is_initialized - oa_system = self.airLoopHVACOutdoorAirSystem.get + if airLoopHVACOutdoorAirSystem.is_initialized + oa_system = airLoopHVACOutdoorAirSystem.get 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 # 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -2635,22 +2750,21 @@ # @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 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) + occ_sch = 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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}.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Setting motorized OA damper schedule to #{occ_sch.name}.") end # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end @@ -2658,34 +2772,31 @@ # Set the minimum OA schedule to follow occupancy oa_control.setMinimumOutdoorAirSchedule(occ_sch) return true - 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 + oa_sys = 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) + oa_control.setMinimumOutdoorAirSchedule(model.alwaysOnDiscreteSchedule) return true - 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 @@ -2697,17 +2808,16 @@ # # @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 def get_occupancy_schedule(occupied_percentage_threshold = 0.05) - # Get all the occupancy schedules in every space in every zone # served by this airloop. Include people added via the SpaceType # in addition to people hard-assigned to the Space itself. occ_schedules_num_occ = {} max_occ_on_airloop = 0 - self.thermalZones.each do |zone| + thermalZones.each do |zone| # Get the people objects zone.spaces.each do |space| # From the space type if space.spaceType.is_initialized space.spaceType.get.people.each do |people| @@ -2718,15 +2828,14 @@ 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 else occ_schedules_num_occ[num_ppl_sch] += num_ppl - max_occ_on_airloop += num_ppl end + max_occ_on_airloop += num_ppl end end end # From the space space.people.each do |people| @@ -2737,61 +2846,56 @@ 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 else occ_schedules_num_occ[num_ppl_sch] += num_ppl - max_occ_on_airloop += num_ppl end + max_occ_on_airloop += num_ppl end end end end - OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", "#{self.name} has #{occ_schedules_num_occ.size} unique occ schedules.") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "#{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") + 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}") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', " Total #{max_occ_on_airloop.round} people on #{name}") # For each day of the year, determine - #time_value_pairs = [] - year = self.model.getYearDescription + # time_value_pairs = [] + year = model.getYearDescription yearly_data = [] yearly_times = OpenStudio::DateTimeVector.new yearly_values = [] - for i in 1..365 - + (1..365).each do |i| 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 + 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 = [] daily_values = [] daily_occs = [] times_on_this_day.uniq.sort.each do |time| - os_time = OpenStudio::Time.new(time) os_date_time = OpenStudio::DateTime.new(os_date, os_time) # Total number of people at each time tot_occ_at_time = 0 day_sch_num_occ.each do |day_sch, num_occ| @@ -2809,163 +2913,157 @@ # Add this data to the daily arrays daily_times << time daily_os_times << os_time daily_values << occ_status 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| - 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] + daily_values.each_with_index do |value, j| + next if value == daily_values[j + 1] + simple_daily_times << daily_times[j] + simple_daily_os_times << daily_os_times[j] + simple_daily_values << daily_values[j] + simple_daily_occs << daily_occs[j] 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} - + 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') + # 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_name = "#{name} Occ Sch" + sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) + sch_ruleset.setName(sch_name.to_s) # 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) # Winter Design Day - All Occupied - day_sch = OpenStudio::Model::ScheduleDay.new(self.model) + day_sch = OpenStudio::Model::ScheduleDay.new(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) # Summer Design Day - All Occupied - day_sch = OpenStudio::Model::ScheduleDay.new(self.model) + day_sch = OpenStudio::Model::ScheduleDay.new(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}") + ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].each do |weekday| + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', weekday.to_s) end_of_prev_rule = yearly_data[0]['date'] - yearly_data.each_with_index do |daily_data, i| + yearly_data.each_with_index do |daily_data, k| # Skip unless it is the day of week # currently under inspection day = daily_data['day_of_week'] - next unless day == day_of_week + next unless day == weekday 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'] + unless yearly_data[k + 7].nil? + next_day_times = yearly_data[k + 7]['times'] + next_day_values = yearly_data[k + 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}") + OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "Making a new rule for #{weekday} from #{end_of_prev_rule} to #{date}") sch_rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset) - sch_rule.setName("#{sch_name} #{day_of_week} Rule") + sch_rule.setName("#{sch_name} #{weekday} Rule") day_sch = sch_rule.daySchedule - day_sch.setName("#{sch_name} #{day_of_week}") - daily_os_times.each_with_index do |time, i| - value = values[i] - next if value == values[i+1] # Don't add breaks if same value + day_sch.setName("#{sch_name} #{weekday}") + daily_os_times.each_with_index do |time, t| + value = values[t] + next if value == values[t + 1] # Don't add breaks if same value day_sch.addValue(time, value) - OpenStudio::logFree(OpenStudio::Debug, "openstudio.standards.AirLoopHVAC", " Adding value #{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 - sch_rule.setApplyMonday(true) if day_of_week == 'Monday' - sch_rule.setApplyTuesday(true) if day_of_week == 'Tuesday' - 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' + sch_rule.setApplyMonday(true) if weekday == 'Monday' + sch_rule.setApplyTuesday(true) if weekday == 'Tuesday' + sch_rule.setApplyWednesday(true) if weekday == 'Wednesday' + sch_rule.setApplyThursday(true) if weekday == 'Thursday' + sch_rule.setApplyFriday(true) if weekday == 'Friday' + sch_rule.setApplySaturday(true) if weekday == 'Saturday' + sch_rule.setApplySunday(true) if weekday == '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 # 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 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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' + 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('_','') + sn = name.get.to_s + snc = sn.gsub(/\W/, '').delete('_') # Get the zone name - zone = self.thermalZones[0] + zone = thermalZones[0] zone_name = zone.name.get.to_s - zn_name_clean = zone_name.gsub(/\W/,'_') + zn_name_clean = zone_name.gsub(/\W/, '_') # Zone air node zone_air_node_name = zone.zoneAirNode.name.get # Get the OA system and OA controller - oa_sys = self.airLoopHVACOutdoorAirSystem + oa_sys = airLoopHVACOutdoorAirSystem if oa_sys.is_initialized oa_sys = oa_sys.get else return false # No OA system end @@ -2973,68 +3071,68 @@ 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 + min_oa_sch_name = if oa_control.minimumOutdoorAirSchedule.is_initialized + oa_control.minimumOutdoorAirSchedule.get.name.get + else + 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.") + if supplyFan.empty? + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: No supply fan found, cannot apply DX fan/economizer control.") return false end - fan = self.supplyFan.get + fan = supplyFan.get fan_name = fan.name.get # Supply outlet node - sup_out_node = self.supplyOutletNode + sup_out_node = supplyOutletNode sup_out_node_name = sup_out_node.name.get # DX Cooling Coil dx_coil = nil - self.supplyComponents.each do |equip| + supplyComponents.each do |equip| if equip.to_CoilCoolingDXSingleSpeed.is_initialized dx_coil = equip.to_CoilCoolingDXSingleSpeed.get elsif equip.to_CoilCoolingDXTwoSpeed.is_initialized dx_coil = equip.to_CoilCoolingDXTwoSpeed.get end end if dx_coil.nil? - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No DX cooling coil found, cannot apply DX fan/economizer control.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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| + supplyComponents.each do |equip| if equip.to_CoilHeatingGas.is_initialized htg_coil = equip.to_CoilHeatingGas.get elsif equip.to_CoilHeatingElectric.is_initialized htg_coil = equip.to_CoilHeatingElectric.get elsif equip.to_CoilHeatingWater.is_initialized htg_coil = equip.to_CoilHeatingWater.get end end if htg_coil.nil? - OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}: No heating coil found, cannot apply DX fan/economizer control.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 = OpenStudio::Model::ScheduleRuleset.new(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) + max_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.7) oa_control.setMaximumFractionofOutdoorAirSchedule(max_oa_sch) ems = " ! Sensors @@ -3272,15 +3370,14 @@ " # Write the ems out # File.open("#{Dir.pwd}/#{snc}_ems.idf", 'w') do |file| - # file.puts ems + # 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 @@ -3288,48 +3385,45 @@ # # @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 # be added to the OpenStudio data model. - # @param template [String] the standard + # @param template [String] the template base requirements on # @param has_ddc [Bool] whether or not the system has DDC control # 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) - + def 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 + infinity_btu_per_hr = 999_999_999_999 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' if has_ddc sp_reset_required = true - OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.AirLoopHVAC", "For #{self.name}: static pressure reset is required because the system has DDC control of VAV terminals.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: Static pressure reset is required because the system has DDC control of VAV terminals.") else - 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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) - + def 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 @@ -3338,22 +3432,21 @@ 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 + supply_return_exhaust_relief_fans.each do |fan| + total_hp += fan.motor_horsepower 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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 @@ -3366,110 +3459,100 @@ # # @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 + unless demandComponents('OS:AirTerminal:SingleDuct:ParallelPIU:Reheat'.to_IddObjectType).empty? night_cycle_type = 'CycleOnAnyZoneFansOnly' end - self.setNightCycleControlType(night_cycle_type) + 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.") + avail_sch = availabilitySchedule + unless avail_sch == model.alwaysOnDiscreteSchedule + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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) + loop_occ_sch = 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.") + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{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) + 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() - + def floor_area_served total_area = 0.0 - self.thermalZones.each do |zone| + 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() - + def floor_area_served_interior_zones total_area = 0.0 - self.thermalZones.each do |zone| + 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 # 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() - + def floor_area_served_exterior_zones total_area = 0.0 - self.thermalZones.each do |zone| + thermalZones.each do |zone| # Skip zones that have no exterior surface area - next if zone.exteriorSurfaceArea == 0 + next if zone.exteriorSurfaceArea.zero? 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 - def find_design_supply_air_flow_rate() - + def find_design_supply_air_flow_rate # Get the design_supply_air_flow_rate design_supply_air_flow_rate = nil - if self.designSupplyAirFlowRate.is_initialized - design_supply_air_flow_rate = self.designSupplyAirFlowRate.get - elsif self.autosizedDesignSupplyAirFlowRate.is_initialized - design_supply_air_flow_rate = self.autosizedDesignSupplyAirFlowRate.get + if designSupplyAirFlowRate.is_initialized + design_supply_air_flow_rate = designSupplyAirFlowRate.get + elsif autosizedDesignSupplyAirFlowRate.is_initialized + design_supply_air_flow_rate = autosizedDesignSupplyAirFlowRate.get else - OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} design sypply air flow rate is not available.") + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} design sypply air flow rate is not available.") end return design_supply_air_flow_rate - end # Determine how much data center # area the airloop serves. # @@ -3477,15 +3560,14 @@ # 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() - + def data_center_area_served dc_area_m2 = 0.0 - self.thermalZones.each do |zone| + 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 next if space_type.standardsSpaceType.empty? @@ -3495,9 +3577,105 @@ dc_area_m2 += space.floorArea end end return dc_area_m2 + end + # Sets the maximum reheat temperature to the specified + # value for all reheat terminals (of any type) on the loop. + # + # @param max_reheat_c [Double] the maximum reheat temperature, in C + # @return [Bool] returns true if successful, false if not. + def apply_maximum_reheat_temperature(max_reheat_c) + demandComponents.each do |sc| + if sc.to_AirTerminalSingleDuctConstantVolumeReheat.is_initialized + term = sc.to_AirTerminalSingleDuctConstantVolumeReheat.get + term.setMaximumReheatAirTemperature(max_reheat_c) + elsif sc.to_AirTerminalSingleDuctParallelPIUReheat.is_initialized + # No control option available + elsif sc.to_AirTerminalSingleDuctSeriesPIUReheat.is_initialized + # No control option available + elsif sc.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.is_initialized + term = sc.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get + term.setMaximumReheatAirTemperature(max_reheat_c) + elsif sc.to_AirTerminalSingleDuctVAVReheat.is_initialized + term = sc.to_AirTerminalSingleDuctVAVReheat.get + term.setMaximumReheatAirTemperature(max_reheat_c) + end + end + + max_reheat_f = OpenStudio.convert(max_reheat_c, 'C', 'F').get + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: reheat terminal maximum set to #{max_reheat_f.round} F.") + + return true end + # Set the system sizing properties based on the zone sizing information + # + # @return [Bool] true if successful, false if not. + def apply_prm_sizing_temperatures + # Get the design heating and cooling SAT information + # for all zones served by the system. + htg_setpts_c = [] + clg_setpts_c = [] + thermalZones.each do |zone| + sizing_zone = zone.sizingZone + htg_setpts_c << sizing_zone.zoneHeatingDesignSupplyAirTemperature + clg_setpts_c << sizing_zone.zoneCoolingDesignSupplyAirTemperature + end + + # Cooling SAT set to minimum zone cooling design SAT + clg_sat_c = clg_setpts_c.min + + # If the system has terminal reheat, + # heating SAT is set to the same value as cooling SAT + # and the terminals are expected to do the heating. + # If not, heating SAT set to maximum zone heating design SAT. + has_term_rht = terminal_reheat? + htg_sat_c = if has_term_rht + clg_sat_c + else + htg_setpts_c.max + end + + # Set the central SAT values + sizing_system = sizingSystem + sizing_system.setCentralCoolingDesignSupplyAirTemperature(clg_sat_c) + sizing_system.setCentralHeatingDesignSupplyAirTemperature(htg_sat_c) + + clg_sat_f = OpenStudio.convert(clg_sat_c, 'C', 'F').get + htg_sat_f = OpenStudio.convert(htg_sat_c, 'C', 'F').get + OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}: central heating SAT set to #{htg_sat_f.round} F, cooling SAT set to #{clg_sat_f.round} F.") + + # If it's a terminal reheat system, set the reheat terminal setpoints too + if has_term_rht + rht_c = htg_setpts_c.max + apply_maximum_reheat_temperature(rht_c) + end + + return true + end + + # Determine if every zone on the system has an identical + # multiplier. If so, return this number. If not, return 1. + # @return [Integer] an integer representing the system multiplier. + def system_multiplier + mult = 1 + + # Get all the zone multipliers + zn_mults = [] + thermalZones.each do |zone| + zn_mults << zone.multiplier + end + + # Warn if there are different multipliers + uniq_mults = zn_mults.uniq + if uniq_mults.size > 1 + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name}: not all zones on the system have an identical zone multiplier. Multipliers are: #{uniq_mults.join(', ')}.") + else + mult = uniq_mults[0] + end + + return mult + end end