class DEER2025

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

Attributes

template[R]

Public Class Methods

new() click to toggle source
# File lib/openstudio-standards/standards/deer/deer_2025/deer_2025.rb, line 7
def initialize
  @template = 'DEER 2025'
  load_standards_database
end

Public Instance Methods

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 2025.

@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_2025/deer_2025.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 2025.

@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_2025/deer_2025.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_2025/deer_2025.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_2025/deer_2025.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_2025/deer_2025.rb, line 16
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_2025/deer_2025.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_2025/deer_2025.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.0
    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.0
    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.0
    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_2025/deer_2025.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