class ASHRAE9012016
This class holds methods that apply ASHRAE 90.1-2016 to a given model. @ref [References::ASHRAE9012016]
Attributes
Public Class Methods
ASHRAE901::new
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.rb, line 8 def initialize super() @template = '90.1-2016' load_standards_database end
Public Instance Methods
Determines the OA flow rates above which an economizer is required. Two separate rates, one for systems with an economizer and another for systems without.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Array<Double>] [min_oa_without_economizer_cfm, min_oa_with_economizer_cfm]
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 264 def air_loop_hvac_demand_control_ventilation_limits(air_loop_hvac) min_oa_without_economizer_cfm = 3000 min_oa_with_economizer_cfm = 750 return [min_oa_without_economizer_cfm, min_oa_with_economizer_cfm] end
Determine the limits for the type of economizer present on the AirLoopHVAC, if any.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Array<Double>] [drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f]
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 41 def air_loop_hvac_economizer_limits(air_loop_hvac, climate_zone) drybulb_limit_f = nil enthalpy_limit_btu_per_lb = nil dewpoint_limit_f = nil # Get the OA system and OA controller oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem return [nil, nil, nil] unless oa_sys.is_initialized oa_sys = oa_sys.get oa_control = oa_sys.getControllerOutdoorAir economizer_type = oa_control.getEconomizerControlType oa_control.resetEconomizerMinimumLimitDryBulbTemperature case economizer_type when 'NoEconomizer' OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name} no economizer") return [nil, nil, nil] when 'FixedDryBulb' search_criteria = { 'template' => template, 'climate_zone' => climate_zone } econ_limits = model_find_object(standards_data['economizers'], search_criteria) drybulb_limit_f = econ_limits['fixed_dry_bulb_high_limit_shutoff_temp'] when 'FixedEnthalpy' enthalpy_limit_btu_per_lb = 28.0 when 'FixedDewPointAndDryBulb' drybulb_limit_f = 75.0 dewpoint_limit_f = 55.0 when 'DifferentialDryBulb', 'DifferentialEnthalpy' OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Economizer type = #{economizer_type}, no limits defined.") end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Economizer type = #{economizer_type}, limits [#{drybulb_limit_f},#{enthalpy_limit_btu_per_lb},#{dewpoint_limit_f}]") return [drybulb_limit_f, enthalpy_limit_btu_per_lb, dewpoint_limit_f] end
Check the economizer type currently specified in the ControllerOutdoorAir object on this air loop is acceptable per the standard.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] Returns true if allowable, if the system has no economizer or no OA system.
Returns false if the economizer type is not allowable.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 97 def air_loop_hvac_economizer_type_allowable?(air_loop_hvac, climate_zone) # EnergyPlus economizer types # 'NoEconomizer' # 'FixedDryBulb' # 'FixedEnthalpy' # 'DifferentialDryBulb' # 'DifferentialEnthalpy' # 'FixedDewPointAndDryBulb' # 'ElectronicEnthalpy' # 'DifferentialDryBulbAndEnthalpy' # Get the OA system and OA controller oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem return true unless oa_sys.is_initialized oa_sys = oa_sys.get oa_control = oa_sys.getControllerOutdoorAir economizer_type = oa_control.getEconomizerControlType # Return true if no economizer is present if economizer_type == 'NoEconomizer' return true end # Determine the prohibited types prohibited_types = [] case climate_zone when 'ASHRAE 169-2006-0B', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3B', 'ASHRAE 169-2013-3C', 'ASHRAE 169-2013-4B', 'ASHRAE 169-2013-4C', 'ASHRAE 169-2013-5B', 'ASHRAE 169-2013-6B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' prohibited_types = ['FixedEnthalpy'] when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-4A' prohibited_types = ['FixedDryBulb', 'DifferentialDryBulb'] when 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2013-5A', 'ASHRAE 169-2013-6A' prohibited_types = [] end # Check if the specified type is allowed economizer_type_allowed = true if prohibited_types.include?(economizer_type) economizer_type_allowed = false end return economizer_type_allowed end
Determine the airflow limits that govern whether or not an ERV is required. Based on climate zone and % OA, plus the number of operating hours the system has.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param pct_oa [Double] percentage of outdoor air @return [Double] the flow rate above which an ERV is required. if nil, ERV is never required.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 397 def air_loop_hvac_energy_recovery_ventilator_flow_limit(air_loop_hvac, climate_zone, pct_oa) # Calculate the number of system operating hours # based on the availability schedule. ann_op_hrs = 0.0 avail_sch = air_loop_hvac.availabilitySchedule if avail_sch == air_loop_hvac.model.alwaysOnDiscreteSchedule ann_op_hrs = 8760.0 elsif avail_sch.to_ScheduleRuleset.is_initialized avail_sch = avail_sch.to_ScheduleRuleset.get ann_op_hrs = OpenstudioStandards::Schedules.schedule_ruleset_get_hours_above_value(avail_sch, 0.0) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.AirLoopHVAC', "For #{air_loop_hvac.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 search_criteria = { 'template' => template, 'climate_zone' => climate_zone, 'under_8000_hours' => true } energy_recovery_limits = model_find_object(standards_data['energy_recovery'], search_criteria) if energy_recovery_limits.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.AirLoopHVAC', "Cannot find energy recovery limits for template '#{template}', climate zone '#{climate_zone}', and under 8000 hours, assuming no energy recovery required.") return nil end if pct_oa < 0.1 erv_cfm = nil elsif pct_oa >= 0.1 && pct_oa < 0.2 erv_cfm = energy_recovery_limits['10_to_20_percent_oa'] elsif pct_oa >= 0.2 && pct_oa < 0.3 erv_cfm = energy_recovery_limits['20_to_30_percent_oa'] elsif pct_oa >= 0.3 && pct_oa < 0.4 erv_cfm = energy_recovery_limits['30_to_40_percent_oa'] elsif pct_oa >= 0.4 && pct_oa < 0.5 erv_cfm = energy_recovery_limits['40_to_50_percent_oa'] elsif pct_oa >= 0.5 && pct_oa < 0.6 erv_cfm = energy_recovery_limits['50_to_60_percent_oa'] elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = energy_recovery_limits['60_to_70_percent_oa'] elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = energy_recovery_limits['70_to_80_percent_oa'] elsif pct_oa >= 0.8 erv_cfm = energy_recovery_limits['greater_than_80_percent_oa'] end else # Table 6.5.6.1-2, above 8000 hrs search_criteria = { 'template' => template, 'climate_zone' => climate_zone, 'under_8000_hours' => false } energy_recovery_limits = model_find_object(standards_data['energy_recovery'], search_criteria) if energy_recovery_limits.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.AirLoopHVAC', "Cannot find energy recovery limits for template '#{template}', climate zone '#{climate_zone}', and under 8000 hours, assuming no energy recovery required.") return nil end if pct_oa < 0.1 erv_cfm = nil elsif pct_oa >= 0.1 && pct_oa < 0.2 erv_cfm = energy_recovery_limits['10_to_20_percent_oa'] elsif pct_oa >= 0.2 && pct_oa < 0.3 erv_cfm = energy_recovery_limits['20_to_30_percent_oa'] elsif pct_oa >= 0.3 && pct_oa < 0.4 erv_cfm = energy_recovery_limits['30_to_40_percent_oa'] elsif pct_oa >= 0.4 && pct_oa < 0.5 erv_cfm = energy_recovery_limits['40_to_50_percent_oa'] elsif pct_oa >= 0.5 && pct_oa < 0.6 erv_cfm = energy_recovery_limits['50_to_60_percent_oa'] elsif pct_oa >= 0.6 && pct_oa < 0.7 erv_cfm = energy_recovery_limits['60_to_70_percent_oa'] elsif pct_oa >= 0.7 && pct_oa < 0.8 erv_cfm = energy_recovery_limits['70_to_80_percent_oa'] elsif pct_oa >= 0.8 erv_cfm = energy_recovery_limits['greater_than_80_percent_oa'] end end return erv_cfm end
Determine if the system economizer must be integrated or not. All economizers must be integrated in 90.1-2016
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if required, false if not
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 86 def air_loop_hvac_integrated_economizer_required?(air_loop_hvac, climate_zone) return true end
Determine the air flow and number of story limits for whether motorized OA damper is required.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Array<Double>] [minimum_oa_flow_cfm, maximum_stories]. If both nil, never required
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 275 def air_loop_hvac_motorized_oa_damper_limits(air_loop_hvac, climate_zone) case climate_zone when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-0B', '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-2013-0A', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-3B', 'ASHRAE 169-2013-3C' minimum_oa_flow_cfm = 0 maximum_stories = 999 # Any number of stories else minimum_oa_flow_cfm = 0 maximum_stories = 0 end return [minimum_oa_flow_cfm, maximum_stories] end
Determine if multizone vav optimization is required. @note code_sections [90.1-2016_6.5.3.3]
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if required, false if not @todo Add exception logic for systems with AIA healthcare ventilation requirements dual duct systems
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 185 def air_loop_hvac_multizone_vav_optimization_required?(air_loop_hvac, climate_zone) multizone_opt_required = false # Not required for systems with fan-powered terminals num_fan_powered_terminals = 0 air_loop_hvac.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::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, multizone vav optimization is not required because the system has #{num_fan_powered_terminals} fan-powered terminals.") return multizone_opt_required end # Get the OA intake controller_oa = nil controller_mv = nil oa_system = nil if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized oa_system = air_loop_hvac.airLoopHVACOutdoorAirSystem.get controller_oa = oa_system.getControllerOutdoorAir controller_mv = controller_oa.controllerMechanicalVentilation else OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.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 air_loop_hvac.designSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = air_loop_hvac.designSupplyAirFlowRate.get elsif air_loop_hvac.autosizedDesignSupplyAirFlowRate.is_initialized dsn_flow_m3_per_s = air_loop_hvac.autosizedDesignSupplyAirFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name} design supply air flow rate is not available, cannot apply efficiency standard.") return multizone_opt_required end dsn_flow_cfm = OpenStudio.convert(dsn_flow_m3_per_s, 'm^3/s', 'cfm').get # Get the minimum OA flow rate min_oa_flow_m3_per_s = nil if controller_oa.minimumOutdoorAirFlowRate.is_initialized min_oa_flow_m3_per_s = controller_oa.minimumOutdoorAirFlowRate.get elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized 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.") return multizone_opt_required end min_oa_flow_cfm = OpenStudio.convert(min_oa_flow_m3_per_s, 'm^3/s', 'cfm').get # Calculate the percent OA at design airflow pct_oa = min_oa_flow_m3_per_s / dsn_flow_m3_per_s # Not required for systems where # exhaust is more than 70% of the total OA intake. if pct_oa > 0.7 OpenStudio.logFree(OpenStudio::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 # if self.isDualDuct # 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
Determine the number of stages that should be used as controls for single zone DX systems. 90.1-2016 depends on the cooling capacity of the system.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Integer] the number of stages: 0, 1, 2
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 311 def air_loop_hvac_single_zone_controls_num_stages(air_loop_hvac, climate_zone) min_clg_cap_btu_per_hr = 65_000 clg_cap_btu_per_hr = OpenStudio.convert(air_loop_hvac_total_cooling_capacity(air_loop_hvac), 'W', 'Btu/hr').get if clg_cap_btu_per_hr >= min_clg_cap_btu_per_hr num_stages = 2 OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: two-stage control is required since cooling capacity of #{clg_cap_btu_per_hr.round} Btu/hr exceeds the minimum of #{min_clg_cap_btu_per_hr.round} Btu/hr .") else num_stages = 1 OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: two-stage control is not required since cooling capacity of #{clg_cap_btu_per_hr.round} Btu/hr is less than the minimum of #{min_clg_cap_btu_per_hr.round} Btu/hr .") end return num_stages end
Determine if the system required supply air temperature (SAT) reset. For 90.1-2016, SAT reset requirements are based on climate zone.
@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if required, false if not
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 331 def air_loop_hvac_supply_air_temperature_reset_required?(air_loop_hvac, climate_zone) is_sat_reset_required = false # Only required for multizone VAV systems unless air_loop_hvac_multizone_vav_system?(air_loop_hvac) return is_sat_reset_required end case climate_zone when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-3A' OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Supply air temperature reset is not required per 6.5.3.4 Exception 1, the system is located in climate zone #{climate_zone}.") return is_sat_reset_required when 'ASHRAE 169-2006-0B', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2006-5C', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3B', 'ASHRAE 169-2013-3C', 'ASHRAE 169-2013-4A', 'ASHRAE 169-2013-4B', 'ASHRAE 169-2013-4C', 'ASHRAE 169-2013-5A', 'ASHRAE 169-2013-5B', 'ASHRAE 169-2013-5C', 'ASHRAE 169-2013-6A', 'ASHRAE 169-2013-6B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' is_sat_reset_required = true OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}: Supply air temperature reset is required.") return is_sat_reset_required end end
@!group AirTerminalSingleDuctVAVReheat Set the initial minimum damper position based on OA rate of the space and the template. Zones with low OA per area get lower initial guesses. Final position will be adjusted upward as necessary by Standards.AirLoopHVAC.apply_minimum_vav_damper_positions
@param air_terminal_single_duct_vav_reheat [OpenStudio::Model::AirTerminalSingleDuctVAVReheat] the air terminal object @param zone_oa_per_area [Double] the zone outdoor air per area in m^3/s*m^2 @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb, line 10 def air_terminal_single_duct_vav_reheat_apply_initial_prototype_damper_position(air_terminal_single_duct_vav_reheat, zone_oa_per_area) min_damper_position = case air_terminal_single_duct_vav_reheat_reheat_type(air_terminal_single_duct_vav_reheat) when 'Electricity', 'NaturalGas' 0.3 else # 'HotWater', other 0.2 end # Set the minimum flow fraction air_terminal_single_duct_vav_reheat.setConstantMinimumAirFlowFraction(min_damper_position) return true end
Specifies the minimum damper position for VAV dampers. For terminals with hot water heat and DDC, the minimum is 20%, otherwise the minimum is 30%.
@param air_terminal_single_duct_vav_reheat [OpenStudio::Model::AirTerminalSingleDuctVAVReheat] the air terminal object @param has_ddc [Boolean] whether or not there is DDC control of the VAV terminal in question @return [Double] minimum damper position
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb, line 10 def air_terminal_single_duct_vav_reheat_minimum_damper_position(air_terminal_single_duct_vav_reheat, has_ddc = false) min_damper_position = nil case air_terminal_single_duct_vav_reheat_reheat_type(air_terminal_single_duct_vav_reheat) when 'HotWater' min_damper_position = if has_ddc 0.2 else 0.3 end when 'Electricity', 'NaturalGas' min_damper_position = 0.3 end return min_damper_position end
Determine what part load efficiency degredation curve should be used for a boiler
@param boiler_hot_water [OpenStudio::Model::BoilerHotWater] hot water boiler object @return [String] returns name of the boiler curve to be used, or nil if not applicable
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.BoilerHotWater.rb, line 6 def boiler_get_eff_fplr(boiler_hot_water) capacity_w = boiler_hot_water_find_capacity(boiler_hot_water) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get fplr = capacity_btu_per_hr >= 1_000_000 ? 'Boiler with Minimum Turndown' : 'Boiler with No Minimum Turndown' return fplr end
Get applicable performance curve for capacity as a function of temperature
@param chiller_electric_eir [OpenStudio::Model::ChillerElectricEIR] chiller object @param compressor_type [String] compressor type @param cooling_type [String] cooling type (‘AirCooled’ or ‘WaterCooled’) @param chiller_tonnage [Double] chiller capacity in ton @return [String] name of applicable cuvre, nil if not found @todo the current assingment is meant to replicate what was in the data, it probably needs to be reviewed
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ChillerElectricEIR.rb, line 10 def chiller_electric_eir_get_cap_f_t_curve_name(chiller_electric_eir, compressor_type, cooling_type, chiller_tonnage, compliance_path) case cooling_type when 'AirCooled' return 'AirCooled_Chiller_2010_PathA_CAPFT' when 'WaterCooled' case compressor_type when 'Centrifugal' if compliance_path == 'Path A' return 'WaterCooled_Centrifugal_Chiller_2010_PathA_CAPFT' elsif compliance_path == 'Path B' return 'WaterCooled_Centrifugal_Chiller_2010_PathB_CAPFT' else return nil end when 'Reciprocating', 'Rotary Screw', 'Scroll' return 'ChlrWtrPosDispPathAAllQRatio_fTchwsTcwsSI' else return nil end else return nil end end
Get applicable performance curve for EIR as a function of part load ratio
@param chiller_electric_eir [OpenStudio::Model::ChillerElectricEIR] chiller object @param compressor_type [String] compressor type @param cooling_type [String] cooling type (‘AirCooled’ or ‘WaterCooled’) @param chiller_tonnage [Double] chiller capacity in ton @return [String] name of applicable cuvre, nil if not found @todo the current assingment is meant to replicate what was in the data, it probably needs to be reviewed
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ChillerElectricEIR.rb, line 74 def chiller_electric_eir_get_eir_f_plr_curve_name(chiller_electric_eir, compressor_type, cooling_type, chiller_tonnage, compliance_path) case cooling_type when 'AirCooled' return 'AirCooled_Chiller_AllCapacities_2004_2010_EIRFPLR' when 'WaterCooled' case compressor_type when 'Centrifugal' return 'ChlrWtrCentPathAAllEIRRatio_fQRatio' when 'Reciprocating', 'Rotary Screw', 'Scroll' return 'ChlrWtrPosDispPathAAllEIRRatio_fQRatio' else return nil end else return nil end end
Get applicable performance curve for EIR as a function of temperature
@param chiller_electric_eir [OpenStudio::Model::ChillerElectricEIR] chiller object @param compressor_type [String] compressor type @param cooling_type [String] cooling type (‘AirCooled’ or ‘WaterCooled’) @param chiller_tonnage [Double] chiller capacity in ton @return [String] name of applicable cuvre, nil if not found @todo the current assingment is meant to replicate what was in the data, it probably needs to be reviewed
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ChillerElectricEIR.rb, line 42 def chiller_electric_eir_get_eir_f_t_curve_name(chiller_electric_eir, compressor_type, cooling_type, chiller_tonnage, compliance_path) case cooling_type when 'AirCooled' return 'AirCooled_Chiller_2010_PathA_EIRFT' when 'WaterCooled' case compressor_type when 'Centrifugal' if compliance_path == 'Path A' return 'WaterCooled_Centrifugal_Chiller_2010_PathA_EIRFT' elsif compliance_path == 'Path B' return 'WaterCooled_Centrifugal_Chiller_2010_PathB_EIRFT' else return nil end when 'Reciprocating', 'Rotary Screw', 'Scroll' return 'ChlrWtrPosDispPathAAllEIRRatio_fTchwsTcwsSI' else return nil end else return nil end end
Apply the efficiency, plus Multicell heat rejection with VSD
@param cooling_tower_variable_speed [OpenStudio::Model::CoolingTowerVariableSpeed] variable speed cooling tower @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.CoolingTowerVariableSpeed.rb, line 10 def cooling_tower_variable_speed_apply_efficiency_and_curves(cooling_tower_variable_speed) cooling_tower_apply_minimum_power_per_flow(cooling_tower_variable_speed) cooling_tower_variable_speed.setCellControl('MaximalCell') return true end
Determine the prototype fan pressure rise for a constant volume fan on an AirLoopHVAC based on system airflow. Defaults to the logic from ASHRAE 90.1-2004 prototypes.
@param fan_constant_volume [OpenStudio::Model::FanConstantVolume] constant volume fan object @return [Double] pressure rise in inches H20
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.FanConstantVolume.rb, line 9 def fan_constant_volume_airloop_fan_pressure_rise(fan_constant_volume) # Get the max flow rate from the fan. maximum_flow_rate_m3_per_s = nil if fan_constant_volume.maximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_constant_volume.maximumFlowRate.get elsif fan_constant_volume.autosizedMaximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_constant_volume.autosizedMaximumFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.FanConstantVolume', "For #{fan_constant_volume.name} max flow rate is not available, cannot apply prototype assumptions.") return false end # Convert max flow rate to cfm maximum_flow_rate_cfm = OpenStudio.convert(maximum_flow_rate_m3_per_s, 'm^3/s', 'cfm').get # Determine the pressure rise pressure_rise_in_h2o = if maximum_flow_rate_cfm < 7437 2.5 else # Over 7,437 cfm 4.09 end return pressure_rise_in_h2o end
Determine the prototype fan pressure rise for an on off fan on an AirLoopHVAC or inside a unitary system based on system airflow. Defaults to the logic from ASHRAE 90.1-2004 prototypes.
@param fan_on_off [OpenStudio::Model::FanOnOff] on off fan object @return [Double] pressure rise in inches H20
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.FanOnOff.rb, line 9 def fan_on_off_airloop_or_unitary_fan_pressure_rise(fan_on_off) # Get the max flow rate from the fan. maximum_flow_rate_m3_per_s = nil if fan_on_off.maximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_on_off.maximumFlowRate.get elsif fan_on_off.autosizedMaximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_on_off.autosizedMaximumFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.FanOnOff', "For #{fan_on_off.name} max flow rate is not available, cannot apply prototype assumptions.") return false end # Convert max flow rate to cfm maximum_flow_rate_cfm = OpenStudio.convert(maximum_flow_rate_m3_per_s, 'm^3/s', 'cfm').get # Determine the pressure rise pressure_rise_in_h2o = if maximum_flow_rate_cfm < 7437 2.5 else # Over 7,437 cfm 4.09 end return pressure_rise_in_h2o end
Determine the prototype fan pressure rise for a variable volume fan on an AirLoopHVAC based on system airflow. Defaults to the logic from ASHRAE 90.1-2004 prototypes.
@param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Double] pressure rise in inches H20
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.FanVariableVolume.rb, line 9 def fan_variable_volume_airloop_fan_pressure_rise(fan_variable_volume) # Get the max flow rate from the fan. maximum_flow_rate_m3_per_s = nil if fan_variable_volume.maximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_variable_volume.maximumFlowRate.get elsif fan_variable_volume.autosizedMaximumFlowRate.is_initialized maximum_flow_rate_m3_per_s = fan_variable_volume.autosizedMaximumFlowRate.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.FanVariableVolume', "For #{fan_variable_volume.name} max flow rate is not available, cannot apply prototype assumptions.") return false end # Convert max flow rate to cfm maximum_flow_rate_cfm = OpenStudio.convert(maximum_flow_rate_m3_per_s, 'm^3/s', 'cfm').get # Determine the pressure rise pressure_rise_in_h2o = if maximum_flow_rate_cfm < 4648 4.0 else # Over 7,437 cfm 5.58 end return pressure_rise_in_h2o end
The threhold capacity below which part load control is not required. Per 90.1-2016, table 6.5.3.2.1: the cooling capacity threshold is 75000 instead of 110000 as of 1/1/2014
@param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Double] the limit, in Btu/hr. Return nil for no limit by default.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.FanVariableVolume.rb, line 27 def fan_variable_volume_part_load_fan_power_limitation_capacity_limit(fan_variable_volume) cap_limit_btu_per_hr = case fan_variable_volume_cooling_system_type(fan_variable_volume) when 'dx' 65_000 end return cap_limit_btu_per_hr end
The threhold horsepower below which part load control is not required. Per 90.1-2016, table 6.5.3.2.1: the fan motor size for chiller-water and evaporative cooling is 0.25 hp as of 1/1/2014 instead of 5 hp
@param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Double] the limit, in horsepower. Return nil for no limit by default.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.FanVariableVolume.rb, line 10 def fan_variable_volume_part_load_fan_power_limitation_hp_limit(fan_variable_volume) hp_limit = case fan_variable_volume_cooling_system_type(fan_variable_volume) when 'dx' 0.0 when 'chw', 'evap' 0.25 end return hp_limit end
Loads the openstudio standards dataset for this standard.
@param data_directories [Array<String>] array of file paths that contain standards data @return [Hash] a hash of standards data
ASHRAE901#load_standards_database
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.rb, line 18 def load_standards_database(data_directories = []) super([__dir__] + data_directories) end
Implement occupancy based lighting level threshold (0.02 W/sqft). This is only for ASHRAE 90.1 2016 onwards. @note code_sections [90.1-2016_9.4.1.1.h/i] @author Xuechen (Jerry) Lei, PNNL
@param model [OpenStudio::Model::Model] OpenStudio Model @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb, line 237 def model_add_lights_shutoff(model) zones = model.getThermalZones num_zones = 0 business_sch_name = prototype_input['business_schedule'] return if business_sch_name.nil? # This is only for 10 prototypes that do not have continuous operation. # Add business schedule model_add_schedule(model, business_sch_name) # Add EMS object for business schedule variable business_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') business_sensor.setKeyName(business_sch_name) business_sensor.setName('Business_Sensor') business_sensor_name = business_sensor.name.to_s space_types_affected = [] zones.sort.each do |zone| spaces = zone.spaces if spaces.length != 1 puts 'warning, there are more than one spaces in the zone, need to confirm the implementation' end space = spaces[0] space_lights = space.lights lights_defined_by_spacetype = false if space_lights.empty? space_lights = space.spaceType.get.lights lights_defined_by_spacetype = true space_types_affected << space.spaceType end space_people = space.people if space_people.empty? space_people = space.spaceType.get.people end # guard clause to skip space with no lights next if space_lights.empty? # if lights are defined at the space type level, clone each lights object and make it individual to the space new_space_lights = [] if lights_defined_by_spacetype space_lights.each do |lights| new_lights = lights.clone.to_Lights.get new_lights.setName("#{space.name}-#{lights.name}") new_lights.setSpace(space) new_space_lights << new_lights end space_lights = new_space_lights end zone_name = zone.name.to_s next if zone_name =~ /data\s*center/i # skip data centers # EnergyPlus v9.4.0 / OpenStudio v3.1.0 variable name change from 'Zone Lights Electric Power' to 'Zone Lights Electricity Rate' # EnergyPlus v9.6.0 / OpenStudio v3.3.0 added Space objects, variable name change from 'Zone Lights Electricity Rate' to 'Space Lights Electricity Rate' # https://github.com/NREL/OpenStudio/pull/4104 if model.version < OpenStudio::VersionString.new('3.1.0') light_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Lights Electric Power') key_name = zone_name elsif model.version < OpenStudio::VersionString.new('3.3.0') light_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Lights Electricity Rate') key_name = zone_name else light_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Space Lights Electricity Rate') key_name = space.name.to_s end light_sensor.setKeyName(key_name) light_sensor_name_ems = "#{ems_friendly_name(key_name)}_LSr" light_sensor.setName(light_sensor_name_ems) # get the space floor area for calculations space_floor_area = space.floorArea # account for multiple lights (also work for single light) big_light = space_lights[0] # find the light with highest power (assuming specified by watts/area) space_lights.each do |light_x| big_light_power = big_light.definition.to_LightsDefinition.get.wattsperSpaceFloorArea.to_f light_x_power = light_x.definition.to_LightsDefinition.get.wattsperSpaceFloorArea.to_f if light_x_power > big_light_power big_light = light_x end end add_lights_prog_0 = '' add_lights_prog_null = '' light_id = 0 space_lights.each do |light_x| light_id += 1 # EnergyPlus v9.4 name change for EMS actuators # https://github.com/NREL/OpenStudio/pull/4104 if model.version < OpenStudio::VersionString.new('3.1.0') light_x_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(light_x, 'Lights', 'Electric Power Level') else light_x_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(light_x, 'Lights', 'Electricity Rate') end light_x_actuator_name_ems = "#{ems_friendly_name(key_name)}_Light#{light_id}_Actuator" light_x_actuator.setName(light_x_actuator_name_ems) add_lights_prog_null += "\n SET #{light_x_actuator_name_ems} = NULL," if light_x == big_light add_lights_prog_0 += "\n SET #{light_x_actuator_name_ems} = 0.02*#{space_floor_area}/0.09290304," next end add_lights_prog_0 += "\n SET #{light_x_actuator_name_ems} = 0," end light_ems_prog = OpenStudio::Model::EnergyManagementSystemProgram.new(model) light_ems_prog.setName("SET_#{ems_friendly_name(key_name)}_Light_EMS_Program") light_ems_prog_body = <<-EMS SET #{light_sensor_name_ems}_IP=0.093*#{light_sensor_name_ems}/#{space_floor_area}, IF (#{business_sensor_name} <= 0) && (#{light_sensor_name_ems}_IP >= 0.02),#{add_lights_prog_0} ELSE,#{add_lights_prog_null} ENDIF EMS light_ems_prog.setBody(light_ems_prog_body) light_ems_prog_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model) light_ems_prog_manager.setName("SET_#{ems_friendly_name(key_name)}_Light_EMS_Program_Manager") light_ems_prog_manager.setCallingPoint('AfterPredictorAfterHVACManagers') light_ems_prog_manager.addProgram(light_ems_prog) end # remove lights at the space type level space_types_affected.each do |space_type| space_type.get.lights.each(&:remove) end return true end
Determine which type of fan the cooling tower will have. Variable Speed Fan
for ASHRAE 90.1-2016.
@param model [OpenStudio::Model::Model] OpenStudio model object @return [String] the fan type: TwoSpeed Fan
, Variable Speed Fan
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.hvac_systems.rb, line 9 def model_cw_loop_cooling_tower_fan_type(model) fan_type = 'Variable Speed Fan' return fan_type end
Metal coiling door code minimum infiltration rate at 75 Pa @note code_sections [90.1-2019_5.4.3.2]
@param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Double] Minimum infiltration rate for metal coiling doors
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb, line 215 def model_door_infil_flow_rate_metal_coiling_cfm_ft2(climate_zone) case climate_zone when 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' return 0.4 else return 1.0 end end
Determine the prototypical economizer type for the model.
@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [String] the economizer type. Possible values are:
'NoEconomizer' 'FixedDryBulb' 'FixedEnthalpy' 'DifferentialDryBulb' 'DifferentialEnthalpy' 'FixedDewPointAndDryBulb' 'ElectronicEnthalpy' 'DifferentialDryBulbAndEnthalpy'
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb, line 17 def model_economizer_type(model, climate_zone) economizer_type = case climate_zone when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-4A' 'DifferentialEnthalpy' else 'DifferentialDryBulb' end return economizer_type end
Determines the power of the elevator ventilation fan. 90.1-2016 has a requirement for ventilation fan efficiency.
@param model [OpenStudio::Model::Model] OpenStudio model object @param vent_rate_cfm [Double] the ventilation rate in ft^3/min @return [Double] the ventilation fan power in watts
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.elevators.rb, line 21 def model_elevator_fan_pwr(model, vent_rate_cfm) vent_pwr_per_flow_w_per_cfm = 0.33 vent_pwr_w = vent_pwr_per_flow_w_per_cfm * vent_rate_cfm # addendum 90.1-2007 aj has requirement on efficiency vent_pwr_w = vent_pwr_w * 0.29 / 0.70 return vent_pwr_w end
Determines the percentage of the elevator cab lighting that is incandescent. The remainder is assumed to be LED. Defaults to 0% incandescent (100% LED), representing newer elevators.
@param model [OpenStudio::Model::Model] OpenStudio model object @return [Double] incandescent lighting percentage
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.elevators.rb, line 10 def model_elevator_lighting_pct_incandescent(model) pct_incandescent = 0.0 # 100% LED return pct_incandescent end
Adjust model to comply with fenestration orientation requirements @note code_sections [90.1-2013_5.5.4.5]
@param model [OpenStudio::Model::Model] OpenStudio model object @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] Returns true if successful, false otherwise
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb, line 10 def model_fenestration_orientation(model, climate_zone) # Building rotation to meet the same code requirement for # 90.1-2010 are kept if model.getBuilding.standardsBuildingType.is_initialized building_type = model.getBuilding.standardsBuildingType.get case building_type when 'Hospital' # Rotate the building counter-clockwise OpenstudioStandards::Geometry.model_set_building_north_axis(model, 270.0) when 'SmallHotel' # Rotate the building clockwise OpenstudioStandards::Geometry.model_set_building_north_axis(model, 180) end end wwr = false # Section 6.2.1.2 in the ANSI/ASHRAE/IES Standard 90.1-2013 Determination # of Energy Savings: Quantitative Analysis mentions that the SHGC trade-off # path is most likely to be used by designers for compliance. # # The following adjustment are only made for models with simple glazing objects non_simple_glazing = false shgc_a = 0 model.getSpaces.each do |space| # Get thermal zone multiplier multiplier = space.thermalZone.get.multiplier space.surfaces.each do |surface| surface.subSurfaces.each do |subsurface| # Get window subsurface type subsurface_type = subsurface.subSurfaceType.to_s.downcase # Window, glass doors next unless (subsurface_type.include? 'window') || (subsurface_type.include? 'glass') # Check if non simple glazing fenestration objects are used subsurface_cons = subsurface.construction.get.to_Construction.get non_simple_glazing = true unless subsurface_cons.layers[0].to_SimpleGlazing.is_initialized if non_simple_glazing OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2013.model', 'Fenestration objects in the model use non-simple glazing models, fenestration requirements are not applied') return false end # Get subsurface's simple glazing object subsurface_shgc = subsurface_cons.layers[0].to_SimpleGlazing.get.solarHeatGainCoefficient # Get subsurface area subsurface_area = subsurface.grossArea * subsurface.multiplier * multiplier # SHGC * Area shgc_a += subsurface_shgc * subsurface_area end end end # Calculate West, East and total fenestration area a_n = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)['north_window'] a_s = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)['south_window'] a_e = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)['east_window'] a_w = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)['west_window'] a_t = a_n + a_s + a_e + a_w return true if a_t.abs < 0.01 # For prototypes SHGC_c assumed to be the building's weighted average SHGC shgc_c = shgc_a / a_t shgc_c = shgc_c.round(2) # West and East facing WWR wwr_w = OpenstudioStandards::Geometry.model_get_exterior_window_to_wall_ratio(model, cardinal_direction: 'W') wwr_e = OpenstudioStandards::Geometry.model_get_exterior_window_to_wall_ratio(model, cardinal_direction: 'E') # Calculate new SHGC for west and east facing fenestration; # Create new simple glazing object and assign it to all # West and East fenestration # # Exception 5 is applied when applicable shgc_w = 0 shgc_e = 0 # Determine requirement criteria case climate_zone when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-0B', '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-2013-0A', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-3B', 'ASHRAE 169-2013-3C' criteria = 4 when 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2006-5C', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-4A', 'ASHRAE 169-2013-4B', 'ASHRAE 169-2013-4C', 'ASHRAE 169-2013-5A', 'ASHRAE 169-2013-5B', 'ASHRAE 169-2013-5C', 'ASHRAE 169-2013-6A', 'ASHRAE 169-2013-6B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' criteria = 5 else return false end if !((a_w <= a_t / criteria) && (a_e <= a_t / criteria)) # Calculate new SHGC if wwr_w > 0.2 shgc_w = a_t * shgc_c / (criteria * a_w) end if wwr_e > 0.2 shgc_e = a_t * shgc_c / (criteria * a_w) end # No SHGC adjustment needed return true if shgc_w == 0 && shgc_e == 0 model.getSpaces.each do |space| # Get thermal zone multiplier multiplier = space.thermalZone.get.multiplier space.surfaces.each do |surface| # Proceed only for East and West facing surfaces that are required # to have their SHGC adjusted next unless (OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'W' && shgc_w > 0) || (OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'E' && shgc_e > 0) surface.subSurfaces.each do |subsurface| # Get window subsurface type subsurface_type = subsurface.subSurfaceType.to_s.downcase # Window, glass doors next unless (subsurface_type.include? 'window') || (subsurface_type.include? 'glass') new_shgc = OpenstudioStandards::Geometry.surface_get_cardinal_direction(surface) == 'W' ? shgc_w : shgc_e new_shgc = new_shgc.round(2) # Get construction/simple glazing associated with the subsurface subsurface_org_cons = subsurface.construction.get.to_Construction.get subsurface_org_cons_mat = subsurface_org_cons.layers[0].to_SimpleGlazing.get # Only proceed if new SHGC is different than orignal one next unless (new_shgc - subsurface_org_cons_mat.solarHeatGainCoefficient).abs > 0 # Clone construction/simple glazing associated with the subsurface subsurface_new_cons = subsurface_org_cons.clone(model).to_Construction.get subsurface_new_cons.setName("#{subsurface.name} Wind Cons U-#{OpenStudio.convert(subsurface_org_cons_mat.uFactor, 'W/m^2*K', 'Btu/ft^2*h*R').get.round(2)} SHGC #{new_shgc}") subsurface_new_cons_mat = subsurface_org_cons_mat.clone(model).to_SimpleGlazing.get subsurface_new_cons_mat.setName("#{subsurface.name} Wind SG Mat U-#{OpenStudio.convert(subsurface_org_cons_mat.uFactor, 'W/m^2*K', 'Btu/ft^2*h*R').get.round(2)} SHGC #{new_shgc}") subsurface_new_cons_mat.setSolarHeatGainCoefficient(new_shgc) new_layers = OpenStudio::Model::MaterialVector.new new_layers << subsurface_new_cons_mat subsurface_new_cons.setLayers(new_layers) # Assign new construction to sub surface subsurface.setConstruction(subsurface_new_cons) end end end end return true end
Is transfer air required? @note code_sections [90.1-2016_6.5.7.1]
@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if transfer air is required, false if not
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb, line 206 def model_transfer_air_required?(model) return true end
Determine type of pump part load control type @note code_sections [90.1-2016_6.5.4.2]
@param pump [OpenStudio::Model::PumpVariableSpeed] OpenStudio pump object @param plant_loop_type [String] Type of plant loop @param pump_nominal_hp [Float] Pump
nominal horsepower @return [String] Pump
part load control type
# File lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb, line 11 def pump_variable_speed_get_control_type(pump, plant_loop_type, pump_nominal_hp) # Sizing factor to take into account that pumps # are typically sized to handle a ~10% pressure # increase and ~10% flow increase. design_sizing_factor = 1.25 # Get climate zone climate_zone = pump.plantLoop.get.model.getClimateZones.getClimateZone(0) climate_zone = "#{climate_zone.institution} 169-#{climate_zone.year}-#{climate_zone.value}" # Get nameplate hp threshold: # The thresholds below represent the nameplate # hp one level lower than the threshold in the # code. Motor size from table in section 10 are # used as reference. case plant_loop_type when 'Heating' case climate_zone when 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' threshold = 3 when 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-5A', 'ASHRAE 169-2006-5C', 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2013-3C', 'ASHRAE 169-2013-5A', 'ASHRAE 169-2013-5C', 'ASHRAE 169-2013-6A', 'ASHRAE 169-2013-6B' threshold = 5 when 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-4C', 'ASHRAE 169-2006-5B', 'ASHRAE 169-2013-4A', 'ASHRAE 169-2013-4C', 'ASHRAE 169-2013-5B' threshold = 7.5 when 'ASHRAE 169-2006-4B', 'ASHRAE 169-2013-4B' threshold = 10 when 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-2B', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-3B' threshold = 20 when 'ASHRAE 169-2006-1B', 'ASHRAE 169-2013-1B' threshold = 75 when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-0B', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1A' threshold = 150 else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.PumpVariableSpeed', "Pump flow control requirement missing for heating water pumps in climate zone: #{climate_zone}.") end when 'Cooling' case climate_zone when 'ASHRAE 169-2006-0A', 'ASHRAE 169-2006-0B', 'ASHRAE 169-2006-1A', 'ASHRAE 169-2006-1B', 'ASHRAE 169-2006-2B', 'ASHRAE 169-2013-0A', 'ASHRAE 169-2013-0B', 'ASHRAE 169-2013-1A', 'ASHRAE 169-2013-1B', 'ASHRAE 169-2013-2B' threshold = 1.5 when 'ASHRAE 169-2006-2A', 'ASHRAE 169-2006-3B', 'ASHRAE 169-2013-2A', 'ASHRAE 169-2013-3B' threshold = 2 when 'ASHRAE 169-2006-3A', 'ASHRAE 169-2006-3C', 'ASHRAE 169-2006-4A', 'ASHRAE 169-2006-4B', 'ASHRAE 169-2013-3A', 'ASHRAE 169-2013-3C', 'ASHRAE 169-2013-4A', 'ASHRAE 169-2013-4B' threshold = 3 when '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-2013-4C', 'ASHRAE 169-2013-5A', 'ASHRAE 169-2013-5B', 'ASHRAE 169-2013-5C', 'ASHRAE 169-2013-6A', 'ASHRAE 169-2013-6B' threshold = 5 when 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-7B', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2006-8B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-7B', 'ASHRAE 169-2013-8A', 'ASHRAE 169-2013-8B' threshold = 10 else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.PumpVariableSpeed', "Pump flow control requirement missing for chilled water pumps in climate zone: #{climate_zone}.") end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2016.PumpVariableSpeed', "No pump flow requirement for #{plant_loop_type} plant loops.") return false end return 'VSD DP Reset' if pump_nominal_hp * design_sizing_factor > threshold # else return 'Riding Curve' end
Determines the method used to extend the daylighted area horizontally next to a window. If the method is ‘fixed’, 2 ft is added to the width of each window. If the method is ‘proportional’, a distance equal to half of the head height of the window is added. If the method is ‘none’, no additional width is added.
@return [String] returns ‘fixed’ or ‘proportional’
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb, line 11 def space_daylighted_area_window_width(space) method = 'proportional' return method end
Determine if the space requires daylighting controls for toplighting, primary sidelighting, and secondary sidelighting. Defaults to false for all types.
@param space [OpenStudio::Model::Space] the space in question @param areas [Hash] a hash of daylighted areas @return [Array<Bool>] req_top_ctrl, req_pri_ctrl, req_sec_ctrl
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb, line 23 def space_daylighting_control_required?(space, areas) req_top_ctrl = true req_pri_ctrl = true req_sec_ctrl = true # Get the LPD of the space space_lpd_w_per_m2 = space.lightingPowerPerFloorArea # Primary Sidelighting # Check if the primary sidelit area contains less than 150W of lighting if areas['primary_sidelighted_area'] < 0.01 OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{space.name}, primary sidelighting control not required because primary sidelighted area = 0ft2 per 9.4.1.1(e).") req_pri_ctrl = false elsif areas['primary_sidelighted_area'] * space_lpd_w_per_m2 < 150.0 OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, primary sidelighting control not required because less than 150W of lighting are present in the primary daylighted area per 9.4.1.1(e).") req_pri_ctrl = false else # Check the size of the windows if areas['total_window_area'] < OpenStudio.convert(20.0, 'ft^2', 'm^2').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, primary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.") req_pri_ctrl = false end end # Secondary Sidelighting # Check if the primary and secondary sidelit areas contains less than 300W of lighting if areas['secondary_sidelighted_area'] < 0.01 OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{space.name}, secondary sidelighting control not required because secondary sidelighted area = 0ft2 per 9.4.1.1(e).") req_sec_ctrl = false elsif (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area']) * space_lpd_w_per_m2 < 300 OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, secondary sidelighting control not required because less than 300W of lighting are present in the combined primary and secondary daylighted areas per 9.4.1.1(e).") req_sec_ctrl = false else # Check the size of the windows if areas['total_window_area'] < OpenStudio.convert(20.0, 'ft^2', 'm^2').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, secondary sidelighting control not required because there are less than 20ft2 of window per 9.4.1.1(e) Exception 2.") req_sec_ctrl = false end end # Toplighting # Check if the toplit area contains less than 150W of lighting if areas['toplighted_area'] < 0.01 OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Space', "For #{space.name}, toplighting control not required because toplighted area = 0ft2 per 9.4.1.1(f).") req_top_ctrl = false elsif areas['toplighted_area'] * space_lpd_w_per_m2 < 150 OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, toplighting control not required because less than 150W of lighting are present in the toplighted area per 9.4.1.1(f).") req_top_ctrl = false end # Exceptions if space.spaceType.is_initialized case space.spaceType.get.standardsSpaceType.to_s when 'Core_Retail' # Retail spaces exception (c) to Section 9.4.1.4 # req_sec_ctrl set to true to create a second reference point req_pri_ctrl = false req_sec_ctrl = true when 'Entry', 'Front_Retail', 'Point_of_Sale', 'Strip mall - type 1', 'Strip mall - type 2', 'Strip mall - type 3' # Retail, Strip mall req_pri_ctrl = false req_sec_ctrl = false when 'Apartment', 'Apartment_topfloor_NS', 'Apartment_topfloor_WE' # Residential apartments req_top_ctrl = false req_pri_ctrl = false req_sec_ctrl = false end end return [req_top_ctrl, req_pri_ctrl, req_sec_ctrl] end
Determine the fraction controlled by each sensor and which window each sensor should go near.
@param space [OpenStudio::Model::Space] space object @param areas [Hash] a hash of daylighted areas @param sorted_windows [Hash] a hash of windows, sorted by priority @param sorted_skylights [Hash] a hash of skylights, sorted by priority @param req_top_ctrl [Boolean] if toplighting controls are required @param req_pri_ctrl [Boolean] if primary sidelighting controls are required @param req_sec_ctrl [Boolean] if secondary sidelighting controls are required @return [Array] array of 4 items
[sensor 1 fraction, sensor 2 fraction, sensor 1 window, sensor 2 window]
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb, line 107 def space_daylighting_fractions_and_windows(space, areas, sorted_windows, sorted_skylights, req_top_ctrl, req_pri_ctrl, req_sec_ctrl) sensor_1_frac = 0.0 sensor_2_frac = 0.0 sensor_1_window = nil sensor_2_window = nil # Get the area of the space space_area_m2 = space.floorArea # get the climate zone climate_zone = OpenstudioStandards::Weather.model_get_climate_zone(space.model) if req_top_ctrl && req_pri_ctrl && req_sec_ctrl # Sensor 1 controls toplighted area sensor_1_frac = areas['toplighted_area'] / space_area_m2 sensor_1_window = sorted_skylights[0] # Sensor 2 controls primary + secondary area sensor_2_frac = (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area']) / space_area_m2 sensor_2_window = sorted_windows[0] elsif !req_top_ctrl && req_pri_ctrl && req_sec_ctrl # Sensor 1 controls primary area sensor_1_frac = areas['primary_sidelighted_area'] / space_area_m2 sensor_1_window = sorted_windows[0] # Sensor 2 controls secondary area sensor_2_frac = (areas['secondary_sidelighted_area'] / space_area_m2) sensor_2_window = sorted_windows[0] elsif req_top_ctrl && !req_pri_ctrl && req_sec_ctrl # Sensor 1 controls toplighted area sensor_1_frac = areas['toplighted_area'] / space_area_m2 sensor_1_window = sorted_skylights[0] # Sensor 2 controls secondary area sensor_2_frac = (areas['secondary_sidelighted_area'] / space_area_m2) # sorted_skylights[0] assigned to sensor_2_window so a second reference point is added for top daylighting sensor_2_window = sorted_skylights[0] elsif req_top_ctrl && !req_pri_ctrl && !req_sec_ctrl case climate_zone when 'ASHRAE 169-2006-6A', 'ASHRAE 169-2006-6B', 'ASHRAE 169-2006-7A', 'ASHRAE 169-2006-8A', 'ASHRAE 169-2013-6A', 'ASHRAE 169-2013-6B', 'ASHRAE 169-2013-7A', 'ASHRAE 169-2013-8A' # Sensor 1 controls toplighted area sensor_1_frac = areas['toplighted_area'] / space_area_m2 sensor_1_window = sorted_skylights[0] else # Sensor 1 controls toplighted area num_sensors = 2 sensor_1_frac = areas['toplighted_area'] / space_area_m2 / num_sensors sensor_1_window = sorted_skylights[0] sensor_2_frac = sensor_1_frac sensor_2_window = sensor_1_window end elsif !req_top_ctrl && req_pri_ctrl && !req_sec_ctrl # Sensor 1 controls primary area sensor_1_frac = areas['primary_sidelighted_area'] / space_area_m2 sensor_1_window = sorted_windows[0] elsif !req_top_ctrl && !req_pri_ctrl && req_sec_ctrl # Sensor 1 controls secondary area sensor_1_frac = areas['secondary_sidelighted_area'] / space_area_m2 sensor_1_window = sorted_windows[0] end return [sensor_1_frac, sensor_2_frac, sensor_1_window, sensor_2_window] end
Determine the base infiltration rate at 75 Pa.
@return [Double] the baseline infiltration rate, in cfm/ft^2 defaults to no infiltration.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb, line 185 def space_infiltration_rate_75_pa(space = nil) basic_infil_rate_cfm_per_ft2 = 1.0 return basic_infil_rate_cfm_per_ft2 end
Determine the area and occupancy level limits for demand control ventilation.
@param thermal_zone [OpenStudio::Model::ThermalZone] the thermal zone @return [Array<Double>] the minimum area, in m^2 and the minimum occupancy density in m^2/person. Returns nil if there is no requirement.
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ThermalZone.rb, line 46 def thermal_zone_demand_control_ventilation_limits(thermal_zone) min_area_ft2 = 500 min_occ_per_1000_ft2 = 25 # Convert to SI min_area_m2 = OpenStudio.convert(min_area_ft2, 'ft^2', 'm^2').get min_occ_per_ft2 = min_occ_per_1000_ft2 / 1000.0 min_ft2_per_occ = 1.0 / min_occ_per_ft2 min_m2_per_occ = OpenStudio.convert(min_ft2_per_occ, 'ft^2', 'm^2').get return [min_area_m2, min_m2_per_occ] end
Determine the thermal zone’s occupancy type category. Options are: residential, nonresidential, publicassembly, retail
@return [String] the occupancy type category @todo Add public assembly building types
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ThermalZone.rb, line 9 def thermal_zone_occupancy_type(thermal_zone) occ_type = if OpenstudioStandards::ThermalZone.thermal_zone_residential?(thermal_zone) 'residential' else 'nonresidential' end # Based on the space type that # represents a majority of the zone. space_type = OpenstudioStandards::ThermalZone.thermal_zone_get_space_type(thermal_zone) if space_type.is_initialized space_type = space_type.get bldg_type = space_type.standardsBuildingType if bldg_type.is_initialized bldg_type = bldg_type.get case bldg_type when 'Retail', 'StripMall', 'SuperMarket' occ_type = 'retail' # when 'SomeBuildingType' # @todo add publicassembly building types # occ_type = 'publicassembly' end end end # OpenStudio::logFree(OpenStudio::Info, "openstudio.Standards.ThermalZone", "For #{self.name}, occupancy type = #{occ_type}.") return occ_type end
Determine if vestibule heating control is required. Required for 90.1-2016 per 6.4.3.9.
@ref [References::ASHRAE9012016] 6.4.3.9 @param zone_hvac_component [OpenStudio::Model::ZoneHVACComponent] zone hvac component @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ZoneHVACComponent.rb, line 10 def zone_hvac_component_vestibule_heating_control_required?(zone_hvac_component) # Ensure that the equipment is assigned to a thermal zone if zone_hvac_component.thermalZone.empty? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.ZoneHVACComponent', "For #{zone_hvac_component.name}: equipment is not assigned to a thermal zone, cannot apply vestibule heating control.") return false end # Only applies to equipment that is in vestibule zones return true if OpenstudioStandards::ThermalZone.thermal_zone_vestibule?(zone_hvac_component.thermalZone.get) # If here, vestibule heating control not required return false end