class DEER2020

This class holds methods that apply DEER 2020 to a given model. @ref [References::DEERMASControl]

Attributes

template[R]

Public Class Methods

new() click to toggle source
Calls superclass method DEER::new
# File lib/openstudio-standards/standards/deer/deer_2020/deer_2020.rb, line 7
def initialize
  super()
  @template = 'DEER 2020'
  load_standards_database
end

Public Instance Methods

air_loop_hvac_dcv_required_when_erv(air_loop_hvac) click to toggle source

Determine if the standard has an exception for demand control ventilation when an energy recovery device is present. Unlike ASHRAE 90.1, Title 24 does not have an ERV exception to DCV. This method is a copy of what is in Standards.AirLoopHVAC.rb and ensures ERVs will not prevent DCV from being applied to DEER models.

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/deer/deer_2020/deer_2020.AirLoopHVAC.rb, line 52
def air_loop_hvac_dcv_required_when_erv(air_loop_hvac)
  dcv_required_when_erv_present = true
  return dcv_required_when_erv_present
end
air_loop_hvac_demand_control_ventilation_limits(air_loop_hvac) click to toggle source

@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/deer/deer_2020/deer_2020.AirLoopHVAC.rb, line 38
def air_loop_hvac_demand_control_ventilation_limits(air_loop_hvac)
  min_oa_without_economizer_cfm = 3000
  min_oa_with_economizer_cfm = 0
  return [min_oa_without_economizer_cfm, min_oa_with_economizer_cfm]
end
air_loop_hvac_motorized_oa_damper_required?(air_loop_hvac, climate_zone) click to toggle source

Determine if a motorized OA damper is required Defaults to true for DEER 2020.

@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/deer/deer_2020/deer_2020.AirLoopHVAC.rb, line 31
def air_loop_hvac_motorized_oa_damper_required?(air_loop_hvac, climate_zone)
  motorized_oa_damper_required = true
  return motorized_oa_damper_required
end
air_loop_hvac_supply_air_temperature_reset_required?(air_loop_hvac, climate_zone) click to toggle source

Determine if the system required supply air temperature (SAT) reset. Defaults to true for DEER 2020.

@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/deer/deer_2020/deer_2020.AirLoopHVAC.rb, line 10
def air_loop_hvac_supply_air_temperature_reset_required?(air_loop_hvac, climate_zone)
  is_sat_reset_required = true
  return is_sat_reset_required
end
air_loop_hvac_unoccupied_fan_shutoff_required?(air_loop_hvac) click to toggle source

Determine if a system’s fans must shut off when not required. Per ASHRAE 90.1 section 6.4.3.3, HVAC systems are required to have off-hour controls

@param air_loop_hvac [OpenStudio::Model::AirLoopHVAC] air loop @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/deer/deer_2020/deer_2020.AirLoopHVAC.rb, line 20
def air_loop_hvac_unoccupied_fan_shutoff_required?(air_loop_hvac)
  shutoff_required = true
  return shutoff_required
end
fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume) click to toggle source

Determines whether there is a requirement to have a VSD or some other method to reduce fan power at low part load ratios.

@param fan_variable_volume [OpenStudio::Model::FanVariableVolume] variable volume fan object @return [Boolean] returns true if required, false if not

# File lib/openstudio-standards/standards/deer/deer_2020/deer_2020.FanVariableVolume.rb, line 8
def fan_variable_volume_part_load_fan_power_limitation?(fan_variable_volume)
  part_load_control_required = false

  # Check if the fan is on a multizone or single zone system.
  # If not on an AirLoop (for example, in unitary system or zone equipment), assumed to be a single zone fan
  mz_fan = false
  if fan_variable_volume.airLoopHVAC.is_initialized
    air_loop = fan_variable_volume.airLoopHVAC.get
    mz_fan = air_loop_hvac_multizone_vav_system?(air_loop)
  end

  # No part load fan power control is required for single zone VAV systems
  unless mz_fan
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.FanVariableVolume', "For #{fan_variable_volume.name}: No part load fan power control is required for single zone VAV systems.")
    return part_load_control_required
  end

  # Assume static pressure reset for all multi-zone fans
  part_load_control_required = true

  return part_load_control_required
end
load_standards_database(data_directories = []) click to toggle source

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

Calls superclass method DEER#load_standards_database
# File lib/openstudio-standards/standards/deer/deer_2020/deer_2020.rb, line 17
def load_standards_database(data_directories = [])
  super([__dir__] + data_directories)
end
space_daylighted_area_window_width(space) click to toggle source

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/deer/deer_2020/deer_2020.Space.rb, line 11
def space_daylighted_area_window_width(space)
  method = 'proportional'
  return method
end
space_daylighting_control_required?(space, areas) click to toggle source

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/deer/deer_2020/deer_2020.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 120W 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.")
    req_pri_ctrl = false
  elsif areas['primary_sidelighted_area'] * space_lpd_w_per_m2 < 120.0
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, primary sidelighting control not required because less than 120W of lighting are present in the primary daylighted area per 130.1(d) exception 3 T24-2019.")
    req_pri_ctrl = false
  else
    # Check the size of the windows
    if areas['total_window_area'] < OpenStudio.convert(24.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 24ft2 of window per 130.1(d) exception 4 T24-2019.")
      req_pri_ctrl = false
    end
  end

  # Secondary Sidelighting
  # Check if the primary and secondary sidelit areas contains less than 120W 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.")
    req_sec_ctrl = false
  elsif (areas['primary_sidelighted_area'] + areas['secondary_sidelighted_area']) * space_lpd_w_per_m2 < 120
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, secondary sidelighting control not required because less than 120W of lighting are present in the combined primary and secondary daylighted areas per 5.5.3 prescriptive exception 1 T24-2019 NonRes ACM.")
    req_sec_ctrl = false
  else
    # Check the size of the windows
    if areas['total_window_area'] < OpenStudio.convert(24.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 24ft2 of window per 130.1(d) exception 4 T24-2019.")
      req_sec_ctrl = false
    end
  end

  # Toplighting
  # Check if the toplit area contains less than 120W 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.")
    req_top_ctrl = false
  elsif areas['toplighted_area'] * space_lpd_w_per_m2 < 120
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Space', "For #{space.name}, toplighting control not required because less than 120W of lighting are present in the toplighted area per 130.1(d) exception 3 T24-2019.")
    req_top_ctrl = false
  end

  return [req_top_ctrl, req_pri_ctrl, req_sec_ctrl]
end
space_daylighting_fractions_and_windows(space, areas, sorted_windows, sorted_skylights, req_top_ctrl, req_pri_ctrl, req_sec_ctrl) click to toggle source

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/deer/deer_2020/deer_2020.Space.rb, line 87
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

  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)
    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]
  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
thermal_zone_demand_control_ventilation_limits(thermal_zone) click to toggle source

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/deer/deer_2020/deer_2020.ThermalZone.rb, line 11
def thermal_zone_demand_control_ventilation_limits(thermal_zone)
  min_area_ft2 = 150
  min_ft2_per_occ = 40

  # Convert to SI
  min_area_m2 = OpenStudio.convert(min_area_ft2, 'ft^2', 'm^2').get
  min_m2_per_occ = OpenStudio.convert(min_ft2_per_occ, 'ft^2', 'm^2').get

  return [min_area_m2, min_m2_per_occ]
end