class Object

Constants

ForStruct

******************************************************************************* The following notice pertains to the function below named convert_schedule_compact_to_schedule_ruleset ******************************************************************************* OpenStudio®, Copyright © 2008-2020, Alliance for Sustainable Energy, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

(1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

(2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

(3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission from the respective party.

(4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works may not use the “OpenStudio” trademark, “OS”, “os”, or any other confusingly similar designation without specific prior written permission from Alliance for Sustainable Energy, LLC.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************

ThroughStruct
UntilStruct

Public Instance Methods

apply_economizers(climate_zone, model) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2045
def apply_economizers(climate_zone, model)
  # Create an economizer maximum OA fraction of 70%
  # to reflect damper leakage per PNNL
  econ_max_70_pct_oa_sch = OpenStudio::Model::ScheduleRuleset.new(model)
  econ_max_70_pct_oa_sch.setName('Economizer Max OA Fraction 70 pct')
  econ_max_70_pct_oa_sch.defaultDaySchedule.setName('Economizer Max OA Fraction 70 pct Default')
  econ_max_70_pct_oa_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.7)

  # Check each airloop
  model.getAirLoopHVACs.sort.each do |air_loop|
    economizer_required = false

    if air_loop_hvac_humidifier_count(air_loop) > 0
      # If airloop includes a humidifier it is assumed
      # that exception c to 90.1-2004/7 Section 6.5.1 applies.
      if template == '90.1-2004' || template == '90.1-2007'
        economizer_required = false
      end
      # This exception exist through 90.1-2019, for hospitals
      # see Section 6.5.1 exception 4
      if @instvarbuilding_type == 'Hospital' &&
         (template == '90.1-2013' || template == '90.1-2016' || template == '90.1-2019')
        economizer_required = false
      end
    elsif @instvarbuilding_type == 'LargeOffice' &&
          air_loop.name.to_s.downcase.include?('datacenter') &&
          air_loop.name.to_s.downcase.include?('basement') &&
          !(template == '90.1-2004' || template == '90.1-2007')
      # System serving the data center in the basement of the large
      # office is assumed to be always large enough to require an
      # economizer when economizer requirement is based on equipment
      # size.
      #
      # No economizer modeled for 90.1-2004 and 2007:
      # Specific economizer requirements for computer rooms were
      # introduced in 90.1-2010. Before that, although not explicitly
      # specified, economizer requirements were aimed at comfort
      # cooling, not computer room cooling (as per input from the MSC).

      # Get the size threshold requirement
      search_criteria = {
        'template' => template,
        'climate_zone' => climate_zone,
        'data_center' => true
      }
      econ_limits = model_find_object(standards_data['economizers'], search_criteria)
      minimum_capacity_btu_per_hr = econ_limits['capacity_limit']
      economizer_required = minimum_capacity_btu_per_hr.nil? ? false : true
    elsif @instvarbuilding_type == 'LargeOffice' && air_loop_hvac_include_wshp?(air_loop)
      # WSHP serving the IT closets are assumed to always be too
      # small to require an economizer
      economizer_required = false
    elsif air_loop_hvac_economizer_required?(air_loop, climate_zone)
      economizer_required = true
    end

    if economizer_required
      # If an economizer is required, determine the economizer type
      # in the prototype buildings, which depends on climate zone.
      economizer_type = model_economizer_type(model, climate_zone)

      # Set the economizer type
      # Get the OA system and OA controller
      oa_sys = air_loop.airLoopHVACOutdoorAirSystem
      if oa_sys.is_initialized
        oa_sys = oa_sys.get
      else
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but it has no OA system.")
        next
      end
      oa_control = oa_sys.getControllerOutdoorAir
      oa_control.setEconomizerControlType(economizer_type)
      # oa_control.setMaximumFractionofOutdoorAirSchedule(econ_max_70_pct_oa_sch)

      # Check that the economizer type set by the prototypes
      # is not prohibited by code.  If it is, change to no economizer.
      unless air_loop_hvac_economizer_type_allowable?(air_loop, climate_zone)
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.Model', "#{air_loop.name} is required to have an economizer, but the type chosen, #{economizer_type} is prohibited by code for climate zone #{climate_zone}. Economizer type will be switched to No Economizer.")
        oa_control.setEconomizerControlType('NoEconomizer')
      end

    end
  end
end
calculate_max_delta_t(adj_DaySchedule, cur_DaySchedule) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 27
def calculate_max_delta_t(adj_DaySchedule, cur_DaySchedule)
  max = -999
  #  #if either one of schedule does not exist then it will return the max of the existing schedule
  #  if adj_DaySchedule.nil?
  #    0.upto(23) do |hour|
  #      time = OpenStudio::Time.new(0,hour,0,0)
  #      max = cur_DaySchedule.getValue(time) if cur_DaySchedule.getValue(time) > max
  #    end
  #  elsif cur_DaySchedule.nil?
  #    0.upto(23) do |hour|
  #      time = OpenStudio::Time.new(0,hour,0,0)
  #      max = adj_DaySchedule.getValue(time) if adj_DaySchedule.getValue(time) > max
  #    end
  #  elsif (cur_DaySchedule.nil? and cur_DaySchedule.nil?)
  #    return nil
  #  end
  if cur_DaySchedule.nil? || adj_DaySchedule.nil?
    raise 'cur_DaySchedule or adj_DaySchedule is nil'
    return nil
  end

  0.upto(23) do |hour|
    time = OpenStudio::Time.new(0, hour, 0, 0)
    difference = (cur_DaySchedule.getValue(time) - adj_DaySchedule.getValue(time)).abs
    max = difference if difference > max
  end
  return max
end
check_boolean_value(value,varname) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 718
def check_boolean_value (value,varname)
  return true if value =~ (/^(true|t|yes|y|1)$/i)
  return false if value.empty? || value =~ (/^(false|f|no|n|0)$/i)

  raise ArgumentError.new "invalid value for #{varname}: #{value}"
end
create_cbes_class_array() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 701
def create_cbes_class_array
  prototype_buildings = [
    'MediumOffice',
    'RetailStandalone',
    'SmallOffice'
  ]

  templates = {
    'CBESPre1978' => 'CBES Pre-1978',
    'CBEST241978' => 'CBES T24 1978',
    'CBEST241992' => 'CBES T24 1992',
    'CBEST242001' => 'CBES T24 2001',
    'CBEST242005' => 'CBES T24 2005',
    'CBEST242008' => 'CBES T24 2008'
  }

  class_array = []
  templates.each_pair do |template, template_string|
    # Create Prototype base class (May not be needed...)
    # Ex: class CBESPre1978_Prototype < CBESPre1978
    class_array << "
  class #{template}_Prototype < #{template}
  attr_reader :instvarbuilding_type
  def initialize
    super()
  end
end
"

    # Create Building Specific classes for each building.
    # Example class CBESPre1978_MediumOffice
    prototype_buildings.each do |building_type|
      class_array << "
  # This class represents a prototypical #{template} #{building_type}.
  class #{template}#{building_type} < #{template}
  @@building_type = \"#{building_type}\"
  register_standard (\"#{template_string}_\#{@@building_type}\")
  attr_accessor :prototype_database
  attr_accessor :prototype_input
  attr_accessor :lookup_building_type
  attr_accessor :space_type_map
  attr_accessor :geometry_file
  attr_accessor :building_story_map
  attr_accessor :system_to_space_map

  def initialize
    super()
    @instvarbuilding_type = @@building_type
    @prototype_input = self.model_find_object(standards_data['prototype_inputs'], {'template' => @template,'building_type' => @@building_type}, nil)
    if @prototype_input.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', \"Could not find prototype inputs for \#{{'template' => @template,'building_type' => @@building_type}}, cannot create model.\")
      raise(\"Could not find prototype inputs for #{template}#{building_type}, cannot create model.\")
      return false
    end
    @lookup_building_type = self.model_get_lookup_name(@building_type)
    #ideally we should map the data required to a instance variable.
    @geometry_file = 'geometry/' + @prototype_input['geometry_osm']
    hvac_map_file =  'geometry/' + @prototype_input['hvac_json']
    @system_to_space_map = load_hvac_map(hvac_map_file)
    self.set_variables()
  end

  # This method is used to extend the class with building-type-specific
  # methods, as defined in Prototype.SomeBuildingType.rb.  Each building type
  # has its own set of methods that change things which are not
  # common across all prototype buildings, even within a given Standard.
  def set_variables()
    # Will be overwritten in class reopen file.
    # add all building methods for now.
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the space types
  # available for this particular Standard.
  def define_space_type_map(building_type, climate_zone)
    return @space_type_map
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the HVAC system that will
  # be applied to those spaces.
  def define_hvac_system_map(building_type, climate_zone)
    return @system_to_space_map
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the building story
  # that they are located on.
  def define_building_story_map(building_type, climate_zone)
     return @building_story_map
  end

  # Does nothing unless implmented by the specific standard
  def model_modify_oa_controller(model)
  end

  # Does nothing unless implmented by the specific standard
  def model_reset_or_room_vav_minimum_damper(prototype_input, model)
  end

  # update exhuast fan efficiency
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @return [Boolean] returns true if successful, false if not
  def model_update_exhaust_fan_efficiency(model)
    return true
  end

  # Does nothing unless implmented by the specific standard
  def model_update_fan_efficiency(model)
  end

  # Get the name of the building type used in lookups.
  # For CBES, this lookup matches the DOE prototype building types.
  #
  # @param building_type [String] the building type
  # @return [String] returns the lookup name as a string
  def model_get_lookup_name(building_type)
    lookup_name = building_type
    case building_type
      when 'SmallOffice'
        lookup_name = 'Office'
      when 'MediumOffice'
        lookup_name = 'Office'
      when 'LargeOffice'
        lookup_name = 'Office'
      when 'LargeOfficeDetailed'
        lookup_name = 'Office'
      when 'RetailStandalone'
        lookup_name = 'Retail'
      when 'RetailStripmall'
        lookup_name = 'StripMall'
      when 'Office'
        lookup_name = 'Office'
    end
    return lookup_name
  end

  # hvac adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_hvac_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # swh adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_swh_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # geometry adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_geometry_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # daylighting adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_daylighting_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end
end
"
    end
  end
  return class_array
end
create_class_array() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 6
def create_class_array
  prototype_buildings = ['FullServiceRestaurant',
                         'Hospital',
                         'HighriseApartment',
                         'LargeHotel',
                         'LargeOffice',
                         'MediumOffice',
                         'MidriseApartment',
                         'Outpatient',
                         'PrimarySchool',
                         'QuickServiceRestaurant',
                         'RetailStandalone',
                         'SecondarySchool',
                         'SmallHotel',
                         'SmallOffice',
                         'RetailStripmall',
                         'Warehouse',
                         'SuperMarket',
                         'SmallDataCenterLowITE',
                         'SmallDataCenterHighITE',
                         'LargeDataCenterLowITE',
                         'LargeDataCenterHighITE',
                         'SmallOfficeDetailed',
                         'MediumOfficeDetailed',
                         'LargeOfficeDetailed',
                         'Laboratory',
                         'College',
                         'Courthouse',
                         'TallBuilding',
                         'SuperTallBuilding']

  templates = {
    'ASHRAE9012004' => '90.1-2004',
    'ASHRAE9012007' => '90.1-2007',
    'ASHRAE9012010' => '90.1-2010',
    'ASHRAE9012013' => '90.1-2013',
    'ASHRAE901PRM2019' => '90.1-PRM-2019',
    'ASHRAE9012016' => '90.1-2016',
    'ASHRAE9012019' => '90.1-2019',
    'DOERef1980to2004' => 'DOE Ref 1980-2004',
    'DOERefPre1980' => 'DOE Ref Pre-1980',
    'NRELZNEReady2017' => 'NREL ZNE Ready 2017'
  }
  class_array = []
  templates.each_pair do |template, template_string|
    # Create Prototype base class (May not be needed...)
    # Ex: class NECB2011_Prototype < NECB2011
    class_array << "
  class #{template}_Prototype < #{template}
  attr_reader :instvarbuilding_type
  def initialize
    super()
  end
end
"

    # Create Building Specific classes for each building.
    # Example class NECB2011Hospital
    prototype_buildings.each do |name|
      class_array << "
  # This class represents a prototypical #{template} #{name}.
  class #{template}#{name} < #{template}
  @@building_type = \"#{name}\"
  register_standard (\"#{template_string}_\#{@@building_type}\")
  attr_accessor :prototype_database
  attr_accessor :prototype_input
  attr_accessor :lookup_building_type
  attr_accessor :space_type_map
  attr_accessor :geometry_file
  attr_accessor :building_story_map
  attr_accessor :system_to_space_map
  def initialize
    super()
    @instvarbuilding_type = @@building_type
    @prototype_input = self.standards_lookup_table_first(table_name: 'prototype_inputs',search_criteria: {'template' => @template,'building_type' => @@building_type })
    if @prototype_input.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', \"Could not find prototype inputs for \#{{'template' => @template,'building_type' => @@building_type }}, cannot create model.\")
      raise(\"Could not find prototype inputs for #{template}#{name}, cannot create model.\")
      return false
    end
    @lookup_building_type = self.model_get_lookup_name(@@building_type)
    #ideally we should map the data required to a instance variable.
    @geometry_file = 'geometry/' + @prototype_input['geometry_osm']
    hvac_map_file =  'geometry/' + @prototype_input['hvac_json']
    @system_to_space_map = load_hvac_map(hvac_map_file)
    self.set_variables()
  end
  # This method is used to extend the class with building-type-specific
  # methods, as defined in Prototype.SomeBuildingType.rb.  Each building type
  # has its own set of methods that change things which are not
  # common across all prototype buildings, even within a given Standard.
  def set_variables()
    # Will be overwritten in class reopen file.
    # add all building methods for now.
    self.extend(#{name}) unless @template == 'NECB 2011'
  end
  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the space types
  # available for this particular Standard.
  def define_space_type_map(building_type, climate_zone)
    return @space_type_map
  end
  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the HVAC system that will
  # be applied to those spaces.
  def define_hvac_system_map(building_type, climate_zone)
    return @system_to_space_map
  end
  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the building story
  # that they are located on.
  def define_building_story_map(building_type, climate_zone)
     return @building_story_map
  end
  # Does nothing unless implmented by the specific standard
  def model_modify_oa_controller(model)
  end
  # Does nothing unless implmented by the specific standard
  def model_reset_or_room_vav_minimum_damper(prototype_input, model)
  end

  # update exhuast fan efficiency
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @return [Boolean] returns true if successful, false if not
  def model_update_exhaust_fan_efficiency(model)
    return true
  end

  # Does nothing unless implmented by the specific standard
  def model_update_fan_efficiency(model)
  end
  # Get the name of the building type used in lookups
  #
  # @param building_type [String] the building type
  # @return [String] returns the lookup name as a string
  # @todo Unify the lookup names and eliminate this method
  def model_get_lookup_name(building_type)
    lookup_name = building_type
    case building_type
      when 'SmallOffice'
        lookup_name = 'Office'
      when 'MediumOffice'
        lookup_name = 'Office'
      when 'LargeOffice'
        lookup_name = 'Office'
      when 'SmallOfficeDetailed'
        lookup_name = 'Office'
      when 'MediumOfficeDetailed'
        lookup_name = 'Office'
          when 'LargeOfficeDetailed'
        lookup_name = 'Office'
      when 'RetailStandalone'
        lookup_name = 'Retail'
      when 'RetailStripmall'
        lookup_name = 'StripMall'
      when 'Office'
        lookup_name = 'Office'
    end
    return lookup_name
  end

  # daylighting adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_daylighting_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end
end
"
    end
  end

  ['NECB2011',
   'NECB2015',
   'NECB2017'].each do |template|
    # Create Prototype base class (May not be needed...)
    # Ex: class NECB2011_Prototype < NECB2011
    class_array << "
  class #{template}_Prototype < #{template}
  attr_reader :instvarbuilding_type
  def initialize
    super()
  end
end
"

    # Create Building Specific classes for each building.
    # Example class NECB2011Hospital
    prototype_buildings.each do |name|
      class_array << "
  # This class represents a prototypical #{template} #{name}.
  class #{template}#{name} < #{template}
    BUILDING_TYPE = \"#{name}\"
    TEMPLATE =  \"#{template}\"
    register_standard (\"\#{TEMPLATE}_\#{BUILDING_TYPE}\")
    attr_accessor :prototype_database
    attr_accessor :prototype_input
    attr_accessor :lookup_building_type
    attr_accessor :space_type_map
    attr_accessor :geometry_file
    attr_accessor :building_story_map
    attr_accessor :system_to_space_map

    def initialize
      super()
      @building_type = BUILDING_TYPE
      @template = TEMPLATE
      @instvarbuilding_type = @building_type
      @prototype_input = self.standards_lookup_table_first(table_name: 'prototype_inputs', search_criteria: {'template' => \"#{template}\",'building_type' => \"#{name}\" })
      if @prototype_input.nil?
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', \"Could not find prototype inputs for \#{{'template' => \"#{template}\",'building_type' => \"#{name}\" }}, cannot create model.\")
        #puts JSON.pretty_generate(standards_data['prototype_inputs'])
        raise(\"Could not find prototype inputs for #{template} #{name}, cannot create model.\")
        return false
      end
      @lookup_building_type = self.model_get_lookup_name(@building_type)
      #ideally we should map the data required to a instance variable.
      @geometry_file = 'geometry/' + self.class.name + '.osm'
      hvac_map_file =  'geometry/' + self.class.name + '.hvac_map.json'
      # @system_to_space_map = load_hvac_map(hvac_map_file) # No HVAC map json files for NECB
      self.set_variables()
    end

  # This method is used to extend the class with building-type-specific
  # methods, as defined in Prototype.SomeBuildingType.rb.  Each building type
  # has its own set of methods that change things which are not
  # common across all prototype buildings, even within a given Standard.
    def set_variables()
    end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the space types
  # available for this particular Standard.
    def define_space_type_map(building_type, climate_zone)
      return @space_type_map
    end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the HVAC system that will
  # be applied to those spaces.
    def define_hvac_system_map(building_type, climate_zone)
      return @system_to_space_map
    end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the building story
  # that they are located on.
  def define_building_story_map(building_type, climate_zone)
     return @building_story_map
  end

  # Does nothing unless implmented by the specific standard
  def model_modify_oa_controller(model)
  end

  # Does nothing unless implmented by the specific standard
  def model_reset_or_room_vav_minimum_damper(prototype_input, model)
  end

  # update exhuast fan efficiency
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @return [Boolean] returns true if successful, false if not
  def model_update_exhaust_fan_efficiency(model)
    return true
  end

  # Does nothing unless implmented by the specific standard
  def model_update_fan_efficiency(model)
  end

end
"
    end
  end

  return class_array
end
create_deer_class_array() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 290
def create_deer_class_array
  prototype_buildings = [
    'Asm',
    'ECC',
    'EPr',
    'ERC',
    'ESe',
    'EUn',
    'Gro',
    'Hsp',
    'Nrs',
    'Htl',
    'Mtl',
    'MBT',
    'MLI',
    'OfL',
    'OfS',
    'RFF',
    'RSD',
    'Rt3',
    'RtL',
    'RtS',
    'SCn',
    'SUn',
    'WRf',
    'GHs',
    'DMo',
    'MFm',
    'SFm'
  ]

  # Only a subset of building type and HVAC type combinations are valid
  building_to_hvac_systems = {
    'Asm' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'ECC' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'EPr' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'WLHP'
    ],
    'ERC' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'ESe' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'EUn' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG'
    ],
    'Gro' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'Hsp' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG'
    ],
    'Nrs' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'FPFC',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG'
    ],
    'Htl' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'Mtl' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'MBT' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'MFm' => [
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'MLI' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'OfL' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'OfS' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'RFF' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'RSD' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'Rt3' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF',
      'PVVE',
      'PVVG',
      'SVVE',
      'SVVG',
      'WLHP'
    ],
    'RtL' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'RtS' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'SCn' => [
      'DXEH',
      'DXGF',
      'DXHP',
      'NCEH',
      'NCGF'
    ],
    'SUn' => ['Unc'],
    'WRf' => ['DXGF']
  }

  templates = {
    'DEERPRE1975' => 'DEER Pre-1975',
    'DEER1985' => 'DEER 1985',
    'DEER1996' => 'DEER 1996',
    'DEER2003' => 'DEER 2003',
    'DEER2007' => 'DEER 2007',
    'DEER2011' => 'DEER 2011',
    'DEER2014' => 'DEER 2014',
    'DEER2015' => 'DEER 2015',
    'DEER2017' => 'DEER 2017'
  }

  class_array = []
  templates.each_pair do |template, template_string|
    # Create Prototype base class (May not be needed...)
    # Ex: class DEER_Prototype < DEER1985
    class_array << "
  class #{template}_Prototype < #{template}
  attr_reader :instvarbuilding_type
  def initialize
    super()
  end
end
"

    # Create Building Specific classes for each building.
    # Example class DEER1985AsmDXGF
    prototype_buildings.each do |building_type|
      next if building_to_hvac_systems[building_type].nil?

      building_to_hvac_systems[building_type].each do |hvac_system|
        class_array << "
  # This class represents a prototypical #{template} #{building_type} #{hvac_system}.
  class #{template}#{building_type}#{hvac_system} < #{template}
  @@building_type = \"#{building_type}\"
  @@hvac_system = \"#{hvac_system}\"
  register_standard (\"#{template_string}_\#{@@building_type}_\#{@@hvac_system}\")
  attr_accessor :prototype_database
  attr_accessor :prototype_input
  attr_accessor :lookup_building_type
  attr_accessor :space_type_map
  attr_accessor :geometry_file
  attr_accessor :building_story_map
  attr_accessor :system_to_space_map

  def initialize
    super()
    @instvarbuilding_type = @@building_type
    @prototype_input = self.standards_lookup_table_first(table_name: 'prototype_inputs', search_criteria: {'template' => @template,'building_type' => @@building_type, 'hvac_system' => @@hvac_system})
    if @prototype_input.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', \"Could not find prototype inputs for \#{{'template' => @template,'building_type' => @@building_type, 'hvac' => @@hvac_system}}, cannot create model.\")
      raise(\"Could not find prototype inputs for #{template}#{building_type}#{hvac_system}, cannot create model.\")
      return false
    end
    @lookup_building_type = @@building_type
    #ideally we should map the data required to a instance variable.
    @geometry_file = 'geometry/' + @prototype_input['geometry_osm']
    hvac_map_file =  'geometry/' + @prototype_input['hvac_json']
    @system_to_space_map = load_hvac_map(hvac_map_file)
    self.set_variables()
  end

  # This method is used to extend the class with building-type-specific
  # methods, as defined in Prototype.SomeBuildingType.rb.  Each building type
  # has its own set of methods that change things which are not
  # common across all prototype buildings, even within a given Standard.
  def set_variables()
    # Will be overwritten in class reopen file.
    # add all building methods for now.
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the space types
  # available for this particular Standard.
  def define_space_type_map(building_type, climate_zone)
    return @space_type_map
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the HVAC system that will
  # be applied to those spaces.
  def define_hvac_system_map(building_type, climate_zone)
    return @system_to_space_map
  end

  # Returns the mapping between the names of the spaces
  # in the geometry .osm file and the building story
  # that they are located on.
  def define_building_story_map(building_type, climate_zone)
     return @building_story_map
  end

  # Does nothing unless implmented by the specific standard
  def model_modify_oa_controller(model)
  end

  # Does nothing unless implmented by the specific standard
  def model_reset_or_room_vav_minimum_damper(prototype_input, model)
  end

  # update exhuast fan efficiency
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @return [Boolean] returns true if successful, false if not
  def model_update_exhaust_fan_efficiency(model)
    return true
  end

  # Does nothing unless implmented by the specific standard
  def model_update_fan_efficiency(model)
  end

  # Get the name of the building type used in lookups.
  # For DEER, this is the building type.
  #
  # @param building_type [String] the building type
  # @return [String] returns the lookup name as a string
  def model_get_lookup_name(building_type)
    return building_type
  end

  # hvac adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_hvac_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # swh adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_swh_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # geometry adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_geometry_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end

  # daylighting adjustments specific to the prototype model
  #
  # @param model [OpenStudio::Model::Model] OpenStudio model object
  # @param building_type [String] the building type
  # @param climate_zone [String] ASHRAE climate zone, e.g. 'ASHRAE 169-2013-4A'
  # @param prototype_input [Hash] hash of prototype inputs
  # @return [Boolean] returns true if successful, false if not
  def model_custom_daylighting_tweaks(model, building_type, climate_zone, prototype_input)
    return true
  end
end
"
      end
    end
  end
  return class_array
end
create_meta_classes() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 889
def create_meta_classes
  create_class_array.each { |item| eval(item) } # rubocop:disable Security/Eval
  create_deer_class_array.each { |item| eval(item) } # rubocop:disable Security/Eval
  create_cbes_class_array.each { |item| eval(item) } # rubocop:disable Security/Eval
end
distance(loc1, loc2) click to toggle source
# File lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb, line 3
def distance(loc1, loc2)
  rad_per_deg = Math::PI / 180 # PI / 180
  rkm = 6371 # Earth radius in kilometers
  rm = rkm * 1000 # Radius in meters

  dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
  dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg

  lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
  lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }

  a = Math.sin(dlat_rad / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2)**2
  c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  rm * c # Delta in meters
end
get_additional_property_as_boolean(component, key, default = false) click to toggle source

PRM get an additional property from an OpenStudio object as a boolean, if no such additional property, then return default value. @param component [OpenStudio::Model:Component] the component to get the additional property from @param key [String] key string @param default [Boolean] the default to return when there is no matching key @return [Boolean] boolean value

# File lib/openstudio-standards/utilities/assertion.rb, line 71
def get_additional_property_as_boolean(component, key, default = false)
  value = default
  if component.additionalProperties.getFeatureAsBoolean(key).is_initialized
    value = component.additionalProperties.getFeatureAsBoolean(key).get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.utilities', "Cannot find the #{key} in component: #{component.name.get}, default value #{default} is used.")
  end
  return value
end
get_additional_property_as_double(component, key, default = 0.0) click to toggle source

PRM get an additional property from an OpenStudio object as a double, if no such additional property, then return default value. @param component [OpenStudio::Model::Component] the component to get the additional property from @param key [String] key string @param default [Double] the default to return when there is no matching key @return [Double] Double value

# File lib/openstudio-standards/utilities/assertion.rb, line 103
def get_additional_property_as_double(component, key, default = 0.0)
  value = default
  if component.additionalProperties.getFeatureAsDouble(key).is_initialized
    value = component.additionalProperties.getFeatureAsDouble(key).get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.utilities', "Cannot find the #{key} in component: #{component.name.get}, default value #{default} is used.")
  end
  return value
end
get_additional_property_as_integer(component, key, default = 0.0) click to toggle source

PRM get an additional property from an OpenStudio object as a double, if no such additional property, then return default value. @param component [OpenStudio::Model::Component] the component to get the additional property from @param key [String] key string @param default [Integer] the default to return when there is no matching key @return [Integer] Integer value

# File lib/openstudio-standards/utilities/assertion.rb, line 87
def get_additional_property_as_integer(component, key, default = 0.0)
  value = default
  if component.additionalProperties.getFeatureAsInteger(key).is_initialized
    value = component.additionalProperties.getFeatureAsInteger(key).get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.utilities', "Cannot find the #{key} in component: #{component.name.get}, default value #{default} is used.")
  end
  return value
end
get_additional_property_as_string(component, key, default = "") click to toggle source

PRM get an additional property from an OpenStudio object as a string, if no such additional property, then return default value. @param component [OpenStudio::Model::Component] the component to get the additional property from @param key [String] key string @param default [String] the default to return when there is no matching key @return [String] String value

# File lib/openstudio-standards/utilities/assertion.rb, line 119
def get_additional_property_as_string(component, key, default = "")
  value = default
  if component.additionalProperties.getFeatureAsString(key).is_initialized
    value = component.additionalProperties.getFeatureAsString(key).get
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.utilities', "Cannot find the #{key} in component: #{component.name.get}, default value #{default} is used.")
  end
  return value
end
get_building_door_info(model) click to toggle source

Get building door information to update infiltration

@param model [OpenStudio::Model::Model] OpenStudio Model return [Hash] Door infiltration information

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2144
def get_building_door_info(model)
  get_building_door_info = {}

  return get_building_door_info
end
get_logs(log_type = OpenStudio::Error) click to toggle source

Get an array of all messages of a given type in the log

@param log_type [OpenStudio::Class] type of messages to return from the log, e.g. OpenStudio::Error @return [Array<String>] An array of strings

# File lib/openstudio-standards/utilities/logging.rb, line 138
def get_logs(log_type = OpenStudio::Error)
  errors = []

  $OPENSTUDIO_LOG.logMessages.each do |msg|
    if /openstudio.*/ =~ msg.logChannel
      # Skip certain messages that are irrelevant/misleading
      next if msg.logMessage.include?('UseWeatherFile') || # 'UseWeatherFile' is not yet a supported option for YearDescription
              msg.logMessage.include?('Skipping layer') || # Annoying/bogus "Skipping layer" warnings
              msg.logChannel.include?('runmanager') || # RunManager messages
              msg.logChannel.include?('setFileExtension') || # .ddy extension unexpected
              msg.logChannel.include?('Translator') || # Forward translator and geometry translator
              msg.logMessage.include?('Successive data points') || # Successive data points (2004-Jan-31 to 2001-Feb-01, ending on line 753) are greater than 1 day apart in EPW file
              msg.logMessage.include?('has multiple parents') || # Bogus errors about curves having multiple parents
              msg.logMessage.include?('does not have an Output') || # Warning from EMS translation
              msg.logMessage.include?('Prior to OpenStudio 2.6.2, this field was returning a double, it now returns an Optional double') # Warning about OS API change

      # Only fail on the errors
      if msg.logLevel == log_type
        errors << "[#{msg.logChannel}] #{msg.logMessage}"
      end
    end
  end

  return errors
end
get_occ_guestroom_setpoint_schedules(model) click to toggle source

Helper method for model_add_guestroom_vent_sch and model_reduce_setback_sch_delay @author Xuechen (Jerry) Lei, PNNL

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1163
def get_occ_guestroom_setpoint_schedules(model)
  thermostats = model.getThermostatSetpointDualSetpoints.sort
  thermostats.each do |thermostat|
    next unless thermostat.name.to_s.include? 'GuestRoom'

    heating_schrst = thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
    cooling_schrst = thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
    next unless heating_schrst.name.to_s.include? 'Occ'
    next unless cooling_schrst.name.to_s.include? 'Occ'

    return heating_schrst, cooling_schrst
  end
  return false
end
get_schedule_max_delta_t(model, surface) click to toggle source

returns -999 if current surface does not have heating OR cooling schedule returns -997 if all adjacent surfaces does not have BOTH heating AND cooling returns nil if the surface does not have adjacent surface

# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 59
def get_schedule_max_delta_t(model, surface)
  # find spaces that share that surface.
  # load the schedules for every day type of the year. for both spaces. If the space is does not have a heating schedule..assume the temperature is the -10C always for now.
  # iterate through every hour. and determine delta T
  # Store the max delta T.
  # end iteration
  # return max deltaT
  cur_space = nil
  delta_t = -998
  adjacent_surface_found = false
  atleast_one_adjacent_surface_has_schedule = false
  model.getSpaces.each do |space|
    if space == surface.space.get
      cur_space = space
    end
  end
  if cur_space.nil?
    raise "Surface: [#{surface.name}] does not belong to any space"
  end

  model.getSpaces.each do |space|
    if space == surface.space.get
      next
    end

    space.surfaces.each do |surf|
      unless surf.adjacentSurface.empty?
        adj_surface = surf.adjacentSurface.get
        if adj_surface == surface
          adjacent_surface_found = true
          unless space.thermalZone.empty?
            adj_surface_thermal_zone = space.thermalZone.get
            cur_surface_thermal_zone = cur_space.thermalZone.get
            unless adj_surface_thermal_zone.thermostatSetpointDualSetpoint.empty?
              adj_thermostat = adj_surface_thermal_zone.thermostatSetpointDualSetpoint.get
              cur_thermostat = cur_surface_thermal_zone.thermostatSetpointDualSetpoint.get
              # if current surface does not have a thermostat schedule, then it will return -999 instead of nil because nil is reserved for surfaces without adjacent surface
              if cur_thermostat.getHeatingSchedule.empty? || cur_thermostat.getCoolingSchedule.empty?
                return -999
              end
              # after this point current surface should have BOTH heating and cooling schedule
              # if adjacent surface does not have a heating OR cooling schedule it will be skipped
              if adj_thermostat.getHeatingSchedule.empty? || adj_thermostat.getCoolingSchedule.empty?
                next
              else
                atleast_one_adjacent_surface_has_schedule = true
              end

              startDate = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 1, 2009)
              endDate = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(1), 7, 2009)

              adj_surface_heating_day_schedule_array = adj_thermostat.getHeatingSchedule.get.to_ScheduleRuleset.get.getDaySchedules(startDate, endDate) unless adj_thermostat.getHeatingSchedule.empty?
              adj_surface_cooling_day_schedule_array = adj_thermostat.getCoolingSchedule.get.to_ScheduleRuleset.get.getDaySchedules(startDate, endDate) unless adj_thermostat.getCoolingSchedule.empty?
              cur_surface_heating_day_schedule_array = cur_thermostat.getHeatingSchedule.get.to_ScheduleRuleset.get.getDaySchedules(startDate, endDate) unless cur_thermostat.getHeatingSchedule.empty?
              cur_surface_cooling_day_schedule_array = cur_thermostat.getCoolingSchedule.get.to_ScheduleRuleset.get.getDaySchedules(startDate, endDate) unless cur_thermostat.getCoolingSchedule.empty?
              delta_t = -998
              heating_delta_t = []
              cooling_delta_t = []
              1.upto(7) do |i|
                puts 'mee mee mee'
                puts calculate_max_delta_t(adj_surface_heating_day_schedule_array[i - 1], cur_surface_heating_day_schedule_array[i - 1])
                heating_delta_t << calculate_max_delta_t(adj_surface_heating_day_schedule_array[i - 1], cur_surface_heating_day_schedule_array[i - 1])
                cooling_delta_t << calculate_max_delta_t(adj_surface_cooling_day_schedule_array[i - 1], cur_surface_cooling_day_schedule_array[i - 1])
              end
              delta_t = [heating_delta_t.max, cooling_delta_t.max].max
              puts "delta_t: #{delta_t}"
            end
          end
        end
      end
    end
  end
  # adjacent_surface_found ? (atleast_one_adjacent_surface_has_schedule ? (return delta_t) : (return -997)) : (return nil)
  if adjacent_surface_found
    if atleast_one_adjacent_surface_has_schedule
      return delta_t
    else
      return -997
    end
  else
    return nil
  end
end
get_space_type_maps_from_model(model) click to toggle source

Read the space type to space map from the model instead of relying on an externally-defined mapping.

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 181
def get_space_type_maps_from_model(model)
  # Do all spaces have Spacetypes?
  # @todo is this necessary?
  # all_spaces_have_space_types = true
  # Do all spacetypes have StandardSpaceTypes
  all_space_types_have_standard_space_types = true
  space_type_map = {}
  model.getSpaces.each do |space|
    if space.spaceType.empty?
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name} does not have a Space Type assigned.")
    else
      if space.spaceType.get.standardsSpaceType.empty?
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "SpaceType #{space.spaceType.get.name} does not have a standardsSpaceType assigned.")
        all_space_types_have_standard_space_types = false
      else
        space_type_map[space.spaceType.get.standardsSpaceType.get.to_s] = [] if space_type_map[space.spaceType.get.standardsSpaceType.get.to_s].nil?
        space_type_map[space.spaceType.get.standardsSpaceType.get.to_s] << space.name.get
      end
    end
  end
  if all_space_types_have_standard_space_types
    return space_type_map
  end

  return nil
end
get_total_nominal_capacity(model) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 726
def get_total_nominal_capacity (model)
  total_nominal_capacity = 0
  model.getSpaces.sort.each do |space|
    unless space.thermalZone.empty?
      zone_name = space.thermalZone.get.name.get.upcase
      area = model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='InputVerificationandResultsSummary' AND ReportForString='Entire Facility' AND TableName='Zone Summary' AND ColumnName='Area' AND RowName='#{zone_name}'")
      area = validate_optional(area, model, -1)
      multiplier = model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='InputVerificationandResultsSummary' AND ReportForString='Entire Facility' AND TableName='Zone Summary' AND ColumnName='Multipliers' AND RowName='#{zone_name}'")
      multiplier = validate_optional(multiplier, model, -1)
      area_per_person = model.sqlFile().get().execAndReturnFirstDouble("SELECT Value FROM TabularDataWithStrings WHERE ReportName='InputVerificationandResultsSummary' AND ReportForString='Entire Facility' AND TableName='Zone Summary' AND ColumnName='People' AND RowName='#{zone_name}'")
      area_per_person = validate_optional(area_per_person, model, -1)
      next if area_per_person == 0
      puts "area: #{area}  multiplier: #{multiplier}   area_per_person: #{area_per_person}"
      total_nominal_capacity += area*multiplier/area_per_person
    end
  end
  return total_nominal_capacity
end
log_messages_to_file(file_path, debug = false) click to toggle source

Log the info, warning, and error messages to a file.

runner @param [file_path] The path to the log file debug @param [Boolean] If true, include the debug messages in the log @return [Array<String>] The array of messages, which can be used elsewhere.

# File lib/openstudio-standards/utilities/logging.rb, line 91
def log_messages_to_file(file_path, debug = false)
  messages = []

  File.open(file_path, 'w') do |file|
    $OPENSTUDIO_LOG.logMessages.each do |msg|
      # DLM: you can filter on log channel here for now
      if /openstudio.*/ =~ msg.logChannel # /openstudio\.model\..*/
        # Skip certain messages that are irrelevant/misleading
        next if msg.logMessage.include?('UseWeatherFile') || # 'UseWeatherFile' is not yet a supported option for YearDescription
                msg.logMessage.include?('Skipping layer') || # Annoying/bogus "Skipping layer" warnings
                msg.logChannel.include?('runmanager') || # RunManager messages
                msg.logChannel.include?('setFileExtension') || # .ddy extension unexpected
                msg.logChannel.include?('Translator') || # Forward translator and geometry translator
                msg.logMessage.include?('Successive data points') || # Successive data points (2004-Jan-31 to 2001-Feb-01, ending on line 753) are greater than 1 day apart in EPW file
                msg.logMessage.include?('has multiple parents') || # Bogus errors about curves having multiple parents
                msg.logMessage.include?('does not have an Output') || # Warning from EMS translation
                msg.logMessage.include?('Prior to OpenStudio 2.6.2, this field was returning a double, it now returns an Optional double') # Warning about OS API change

        # Report the message in the correct way
        if msg.logLevel == OpenStudio::Info
          s = "INFO  #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Warn
          s = "WARN  #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Error
          s = "ERROR #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Debug && debug
          s = "DEBUG #{msg.logMessage}"
          file.puts(s)
          messages << s
        end
      end
    end
  end

  return messages
end
log_messages_to_file_prm(file_path, debug = false) click to toggle source

A function to export log messages specific to PRM to a log file.

@param file_path [String] the file path to the log file @param debug [Boolean] debug mode @return [Array] array of message strings

# File lib/openstudio-standards/utilities/logging.rb, line 55
def log_messages_to_file_prm(file_path, debug = false)
  messages = []

  File.open(file_path, 'w') do |file|
    $OPENSTUDIO_LOG.logMessages.each do |msg|
      # only apply to prm
      if msg.logChannel.casecmp? 'prm.log'
        # Report the message in the correct way
        if msg.logLevel == OpenStudio::Info
          s = "[#{msg.logChannel}] INFO  #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Warn
          s = "[#{msg.logChannel}] WARN  #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Error
          s = "[#{msg.logChannel}] ERROR #{msg.logMessage}"
          file.puts(s)
          messages << s
        elsif msg.logLevel == OpenStudio::Debug && debug
          s = "[#{msg.logChannel}] DEBUG #{msg.logMessage}"
          file.puts(s)
          messages << s
        end
      end
    end
  end
  return messages
end
log_messages_to_runner(runner, debug = false) click to toggle source

Log the info, warning, and error messages to a runner.

runner @param [Runner] The Measure runner to add the messages to debug @param [Boolean] If true, include the debug messages in the log @return [Runner] The same Measure runner, with messages from the openstudio-standards library added

# File lib/openstudio-standards/utilities/logging.rb, line 10
def log_messages_to_runner(runner, debug = false)
  $OPENSTUDIO_LOG.logMessages.each do |msg|
    # DLM: you can filter on log channel here for now
    if /openstudio.*/ =~ msg.logChannel # /openstudio\.model\..*/
      # Skip certain messages that are irrelevant/misleading
      next if msg.logMessage.include?('UseWeatherFile') || # 'UseWeatherFile' is not yet a supported option for YearDescription
              msg.logMessage.include?('Skipping layer') || # Annoying/bogus "Skipping layer" warnings
              msg.logChannel.include?('runmanager') || # RunManager messages
              msg.logChannel.include?('setFileExtension') || # .ddy extension unexpected
              msg.logChannel.include?('Translator') || # Forward translator and geometry translator
              msg.logMessage.include?('Successive data points') || # Successive data points (2004-Jan-31 to 2001-Feb-01, ending on line 753) are greater than 1 day apart in EPW file
              msg.logMessage.include?('has multiple parents') || # Bogus errors about curves having multiple parents
              msg.logMessage.include?('does not have an Output') || # Warning from EMS translation
              msg.logMessage.include?('Prior to OpenStudio 2.6.2, this field was returning a double, it now returns an Optional double') # Warning about OS API change

      # Report the message in the correct way
      if msg.logLevel == OpenStudio::Info
        runner.registerInfo(msg.logMessage)
      elsif msg.logLevel == OpenStudio::Warn
        runner.registerWarning("[#{msg.logChannel}] #{msg.logMessage}")
      elsif msg.logLevel == OpenStudio::Error
        runner.registerError("[#{msg.logChannel}] #{msg.logMessage}")
      elsif msg.logLevel == OpenStudio::Debug && debug
        runner.registerInfo("DEBUG - #{msg.logMessage}")
      end
    end
  end
end
look_up_csv_data(csv_fname, search_criteria) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 662
def look_up_csv_data(csv_fname, search_criteria)
  options = { :headers    => :first_row,
    :converters => [ :numeric ] }
  unless File.exist?(csv_fname)
    raise ("File: [#{csv_fname}] Does not exist")
  end
  # we'll save the matches here
  matches = nil
  # save a copy of the headers
  headers = nil
  CSV.open( csv_fname, "r", options ) do |csv|

    # Since CSV includes Enumerable we can use 'find_all'
    # which will return all the elements of the Enumerble for
    # which the block returns true

    matches = csv.find_all do |row|
      match = true
      search_criteria.keys.each do |key|
        match = match && ( row[key].strip == search_criteria[key].strip )
      end
      match
    end
    headers = csv.headers
  end
  #puts matches
  raise("More than one match") if matches.size > 1
  puts "Zero matches found for [#{search_criteria}]" if matches.size == 0
  #return matches[0]
  return matches[0]
end
map_spacetypes() click to toggle source
# File lib/openstudio-standards/btap/utilities/space_type.rb, line 57
def map_spacetypes()

  map_list = CSV.open("cold_lake_map.csv", headers: :first_row).map(&:to_h)
  path = "/home/osdev/openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/DND*.osm"
  Dir.glob(path) do |osm_file|
    model = BTAP::FileIO::load_osm(osm_file)
    model.getSpaceTypes.each do |st|
      map_item = map_list.select { |item| item["Original Spacetype Name"] == st.name.get }
      unless map_item.empty?
        new_name = map_item.first()['New-StandardsBuildingType'].to_s + '-'+map_item.first()['New-StandardSpaceType'].to_s
        st.setName(new_name)
        st.setStandardsBuildingType(map_item.first()["New-StandardsBuildingType"])
        st.setStandardsSpaceType(map_item.first()["New-StandardSpaceType"])
        puts st
      end
    end
    BTAP::FileIO::save_osm(model,File.join(__dir__,File.basename(osm_file)))
  end
end
model_add_constructions(model, building_type, climate_zone) click to toggle source

Adds code-minimum constructions based on the building type as defined in the OpenStudio_Standards_construction_sets.json file. Where there is a separate construction set specified for the individual space type, this construction set will be created and applied to this space type, overriding the whole-building construction set.

@param model OpenStudio Model @param building_type [String] the building type @param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 259
def model_add_constructions(model, building_type, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying constructions')
  is_residential = 'No' # default is nonresidential for building level

  # The constructions lookup table uses a slightly different list of building types.
  @lookup_building_type = model_get_lookup_name(building_type)
  # @todo this is a workaround.  Need to synchronize the building type names
  #   across different parts of the code, including splitting of Office types
  case building_type
    when 'SmallOffice', 'MediumOffice', 'LargeOffice', 'SmallOfficeDetailed', 'MediumOfficeDetailed', 'LargeOfficeDetailed'
      new_lookup_building_type = building_type
    else
      new_lookup_building_type = model_get_lookup_name(building_type)
  end

  # Construct adiabatic constructions
  floor_adiabatic_construction = OpenstudioStandards::Constructions.model_get_adiabatic_floor_construction(model)
  wall_adiabatic_construction = OpenstudioStandards::Constructions.model_get_adiabatic_wall_construction(model)

  cp02_carpet_pad = OpenStudio::Model::MasslessOpaqueMaterial.new(model)
  cp02_carpet_pad.setName('CP02 CARPET PAD')
  cp02_carpet_pad.setRoughness('VeryRough')
  cp02_carpet_pad.setThermalResistance(0.21648)
  cp02_carpet_pad.setThermalAbsorptance(0.9)
  cp02_carpet_pad.setSolarAbsorptance(0.7)
  cp02_carpet_pad.setVisibleAbsorptance(0.8)

  m10_200mm_concrete_block_basement_wall = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  m10_200mm_concrete_block_basement_wall.setName('M10 200mm concrete block basement wall')
  m10_200mm_concrete_block_basement_wall.setRoughness('MediumRough')
  m10_200mm_concrete_block_basement_wall.setThickness(0.2032)
  m10_200mm_concrete_block_basement_wall.setThermalConductivity(1.326)
  m10_200mm_concrete_block_basement_wall.setDensity(1842)
  m10_200mm_concrete_block_basement_wall.setSpecificHeat(912)

  basement_wall_construction = OpenStudio::Model::Construction.new(model)
  basement_wall_construction.setName('Basement Wall construction')
  basement_wall_layers = OpenStudio::Model::MaterialVector.new
  basement_wall_layers << m10_200mm_concrete_block_basement_wall
  basement_wall_construction.setLayers(basement_wall_layers)

  basement_floor_construction = OpenStudio::Model::Construction.new(model)
  basement_floor_construction.setName('Basement Floor construction')
  basement_floor_layers = OpenStudio::Model::MaterialVector.new
  basement_floor_layers << m10_200mm_concrete_block_basement_wall
  basement_floor_layers << cp02_carpet_pad
  basement_floor_construction.setLayers(basement_floor_layers)

  # Constructs all relevant ground FC factor method constructions
  model_set_below_grade_wall_constructions(model, @lookup_building_type, climate_zone)
  model_set_floor_constructions(model, @lookup_building_type, climate_zone)

  # Set all remaining wall and floor constructions
  model.getSurfaces.sort.each do |surface|
    if surface.outsideBoundaryCondition.to_s == 'Adiabatic'
      if surface.surfaceType.to_s == 'Wall'
        surface.setConstruction(wall_adiabatic_construction)
      else
        surface.setConstruction(floor_adiabatic_construction)
      end
    elsif surface.outsideBoundaryCondition.to_s == 'OtherSideCoefficients'
      # Ground
      if surface.surfaceType.to_s == 'Wall'
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_wall_construction)
      else
        surface.setOutsideBoundaryCondition('Ground')
        surface.setConstruction(basement_floor_construction)
      end
    end
  end

  # Make the default construction set for the building
  spc_type = nil
  spc_type = 'WholeBuilding' if template == 'NECB2011'
  bldg_def_const_set = model_add_construction_set(model, climate_zone, new_lookup_building_type, spc_type, is_residential)

  if bldg_def_const_set.is_initialized
    model.getBuilding.setDefaultConstructionSet(bldg_def_const_set.get)
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Could not create default construction set for the building.')
    return false
  end

  # Make a construction set for each space type, if one is specified
  model.getSpaceTypes.sort.each do |space_type|
    # Get the standards building type
    stds_building_type = nil
    if space_type.standardsBuildingType.is_initialized
      stds_building_type = space_type.standardsBuildingType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards building type.")
    end

    # Get the standards space type
    stds_spc_type = nil
    if space_type.standardsSpaceType.is_initialized
      stds_spc_type = space_type.standardsSpaceType.get
    else
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Space type called '#{space_type.name}' has no standards space type.")
    end

    # If the standards space type is Attic the building type should be blank.
    if stds_spc_type == 'Attic'
      stds_building_type = ''
    end

    # Attempt to make a construction set for this space type and assign it if it can be created.
    spc_type_const_set = model_add_construction_set(model, climate_zone, stds_building_type, stds_spc_type, is_residential)
    if spc_type_const_set.is_initialized
      space_type.setDefaultConstructionSet(spc_type_const_set.get)
    end
  end

  # Add construction from story level, especially for the case when there are residential and nonresidential construction in the same building
  if new_lookup_building_type == 'SmallHotel' && template != 'NECB2011'
    model.getBuildingStorys.sort.each do |story|
      next if story.name.get == 'AtticStory'

      # puts "story = #{story.name}"
      is_residential = 'No' # default for building story level
      exterior_spaces_area = 0
      story_exterior_residential_area = 0

      # calculate the propotion of residential area in exterior spaces, see if this story is residential or not
      story.spaces.each do |space|
        next if space.exteriorWallArea.zero?

        space_type = space.spaceType.get
        if space_type.standardsSpaceType.is_initialized
          space_type_name = space_type.standardsSpaceType.get
        end
        data = standards_lookup_table_first(table_name: 'space_types', search_criteria: { 'template' => template,
                                                                                          'building_type' => new_lookup_building_type,
                                                                                          'space_type' => space_type_name })
        exterior_spaces_area += space.floorArea
        story_exterior_residential_area += space.floorArea if data['is_residential'] == 'Yes' # "Yes" is residential, "No" or nil is nonresidential
      end
      is_residential = 'Yes' if story_exterior_residential_area / exterior_spaces_area >= 0.5
      next if is_residential == 'No'

      # if the story is identified as residential, assign residential construction set to the spaces on this story.
      building_story_const_set = model_add_construction_set(model, climate_zone, new_lookup_building_type, nil, is_residential)
      if building_story_const_set.is_initialized
        story.spaces.each do |space|
          space.setDefaultConstructionSet(building_story_const_set.get)
        end
      end
    end
    # Standards: For whole buildings or floors where 50% or more of the spaces adjacent to exterior walls are used primarily for living and sleeping quarters
  end

  # loop through ceiling surfaces and assign the plenum acoustical tile construction if the adjacent surface is a plenum floor
  model.getSurfaces.each do |surface|
    next unless surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Surface' && surface.adjacentSurface.is_initialized

    adj_surface = surface.adjacentSurface.get
    adj_space = adj_surface.space.get
    if adj_space.spaceType.is_initialized && adj_space.spaceType.get.standardsSpaceType.is_initialized
      adj_std_space_type = adj_space.spaceType.get.standardsSpaceType.get
      if adj_std_space_type.downcase == 'plenum'
        plenum_construction = adj_surface.construction
        if plenum_construction.is_initialized
          plenum_construction = plenum_construction.get
          surface.setConstruction(plenum_construction)
        end
      end
    end
  end

  # Make skylights have the same construction as fixed windows
  # sub_surface = self.getBuilding.defaultConstructionSet.get.defaultExteriorSubSurfaceConstructions.get
  # window_construction = sub_surface.fixedWindowConstruction.get
  # sub_surface.setSkylightConstruction(window_construction)

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying constructions')

  return true
end
model_add_daylight_savings(model) click to toggle source

Set up daylight savings

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1454
def model_add_daylight_savings(model)
  start_date = '2nd Sunday in March'
  end_date = '1st Sunday in November'

  runperiodctrl_daylgtsaving = model.getRunPeriodControlDaylightSavingTime
  runperiodctrl_daylgtsaving.setStartDate(start_date)
  runperiodctrl_daylgtsaving.setEndDate(end_date)

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set Daylight Saving Start Date to #{start_date} and end date to #{end_date}.")
end
model_add_debugging_variables(model, type) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1684
def model_add_debugging_variables(model, type)
  # 'detailed'
  # 'timestep'
  # 'hourly'
  # 'daily'
  # 'monthly'

  vars = []
  case type
    when 'service_water_heating'
      var_names << ['Water Heater Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Hot Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Cold Water Volume Flow Rate', 'timestep']
      var_names << ['Water Use Equipment Hot Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Cold Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Mains Water Volume', 'timestep']
      var_names << ['Water Use Equipment Target Water Temperature', 'timestep']
      var_names << ['Water Use Equipment Mixed Water Temperature', 'timestep']
      var_names << ['Water Heater Tank Temperature', 'timestep']
      var_names << ['Water Heater Use Side Mass Flow Rate', 'timestep']
      var_names << ['Water Heater Heating Rate', 'timestep']
      var_names << ['Water Heater Water Volume Flow Rate', 'timestep']
      var_names << ['Water Heater Water Volume', 'timestep']
  end

  var_names.each do |var_name, reporting_frequency|
    output_var = OpenStudio::Model::OutputVariable.new(var_name, model)
    output_var.setReportingFrequency(reporting_frequency)
  end
end
model_add_door_infiltration(model, climate_zone) click to toggle source

Add door infiltration

@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 or not applicable

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2187
def model_add_door_infiltration(model, climate_zone)
  # Get door parameters for the building model
  bldg_door_types = get_building_door_info(model)
  return false if bldg_door_types.empty?

  bldg_door_types.each do |door_type, door_info|
    # Get infiltration flow rate at 75 Pa
    case door_type
      when 'Metal coiling'
        door_infil_flow_rate_cfm_per_ft2 = model_door_infil_flow_rate_metal_coiling_cfm_ft2(climate_zone)
      when 'Rollup'
        door_infil_flow_rate_cfm_per_ft2 = model_door_infil_flow_rate_rollup_cfm_ft2(climate_zone)
      when 'Open'
        door_infil_flow_rate_cfm_per_ft2 = model_door_infil_flow_rate_open_cfm_ft2(climate_zone)
      else
        OpenStudio.logFree(OpenStudio::Warn, 'openstudio.prototype.Model', "The #{door_type.downcase} type of door is not currently supported.")
        next
    end

    # Calculate door infiltration
    door_infil_cfm = door_info['number_of_doors'] * door_info['door_area_ft2'] * door_infil_flow_rate_cfm_per_ft2

    # Conversion factor
    conv_fact = OpenStudio.convert(1, 'm^3/s', 'ft^3/min').to_f

    # Adjust the infiltration rate to the average pressure for the prototype buildings.
    adj_door_infil_cfm = OpenstudioStandards::Infiltration.adjust_infiltration_to_prototype_building_conditions(door_infil_cfm)
    adj_door_infil_m3_per_s = adj_door_infil_cfm / conv_fact

    # Create door infiltration object
    door_infil_obj = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(model)
    door_infil_obj.setName("#{door_info['number_of_doors']} #{door_info['door_area_ft2']} ft2 #{door_type.downcase} Door Infiltration")
    door_infil_obj.setSchedule(door_info['schedule'])
    door_infil_obj.setDesignFlowRate(adj_door_infil_m3_per_s)
    door_infil_obj.setSpace(model.getSpaceByName(door_info['space']).get)
    door_infil_obj.setConstantTermCoefficient(0.0)
    door_infil_obj.setTemperatureTermCoefficient 0.0
    door_infil_obj.setVelocityTermCoefficient(0.224)
    door_infil_obj.setVelocitySquaredTermCoefficient(0.0)
  end

  return true
end
model_add_exhaust(model, kitchen_makeup = 'Adjacent') click to toggle source

Loop through thermal zones and model_run(model) thermal_zone.add_exhaust If kitchen_makeup is “None” then exhaust will be modeled in every kitchen zone without makeup air If kitchen_makeup is “Adjacent” then exhaust will be modeled in every kitchen zone. Makeup air will be provided when there as an adjacent dining,cafe, or cafeteria zone of the same building type. If kitchen_makeup is “Largest Zone” then exhaust will only be modeled in the largest kitchen zone, but the flow rate will be based on the kitchen area for all zones. Makeup air will be modeled in the largest dining,cafe, or cafeteria zone of the same building type.

@param kitchen_makeup [String] Valid choices are None, Largest Zone, Adjacent @return [Hash] Hash of newly made exhaust fan objects along with secondary exhaust and zone mixing objects

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 675
def model_add_exhaust(model, kitchen_makeup = 'Adjacent')
  zone_exhaust_fans = {}

  # apply use specified kitchen_makup logic
  if !['Adjacent', 'Largest Zone'].include?(kitchen_makeup)

    if kitchen_makeup != 'None'
      OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "#{kitchen_makeup} is an unexpected value for kitchen_makup arg, will use None.")
    end

    # loop through thermal zones
    model.getThermalZones.sort.each do |thermal_zone|
      zone_exhaust_hash = thermal_zone_add_exhaust(thermal_zone)

      # populate zone_exhaust_fans
      zone_exhaust_fans.merge!(zone_exhaust_hash)
    end

  else # common code for Adjacent and Largest Zone

    # populate standard_space_types_with_makup_air
    standard_space_types_with_makup_air = {}
    standard_space_types_with_makup_air[['FullServiceRestaurant', 'Kitchen']] = ['FullServiceRestaurant', 'Dining']
    standard_space_types_with_makup_air[['QuickServiceRestaurant', 'Kitchen']] = ['QuickServiceRestaurant', 'Dining']
    standard_space_types_with_makup_air[['Hospital', 'Kitchen']] = ['Hospital', 'Dining']
    standard_space_types_with_makup_air[['SecondarySchool', 'Kitchen']] = ['SecondarySchool', 'Cafeteria']
    standard_space_types_with_makup_air[['PrimarySchool', 'Kitchen']] = ['PrimarySchool', 'Cafeteria']
    standard_space_types_with_makup_air[['LargeHotel', 'Kitchen']] = ['LargeHotel', 'Cafe']

    # gather information on zones organized by standards building type and space type. zone may be in this multiple times if it has multiple space types
    zones_by_standards = {}

    model.getThermalZones.sort.each do |thermal_zone|
      # get space type ratio for spaces in zone
      space_type_hash = {} # key is  space type,  value hash with floor area, standards building type, standards space type, and array of adjacent zones
      thermal_zone.spaces.each do |space|
        next unless space.spaceType.is_initialized
        next unless space.partofTotalFloorArea

        space_type = space.spaceType.get
        next unless space_type.standardsBuildingType.is_initialized
        next unless space_type.standardsSpaceType.is_initialized

        # add entry in hash for space_type_standardsif it doesn't already exist
        unless space_type_hash.key?(space_type)
          space_type_hash[space_type] = {}
          space_type_hash[space_type][:effective_floor_area] = 0.0
          space_type_hash[space_type][:standards_array] = [space_type.standardsBuildingType.get, space_type.standardsSpaceType.get]
          if kitchen_makeup == 'Adjacent'
            space_type_hash[space_type][:adjacent_zones] = []
          end
        end

        # populate floor area
        space_type_hash[space_type][:effective_floor_area] += space.floorArea * space.multiplier

        # @todo populate adjacent zones (need to add methods to space and zone for this)
        if kitchen_makeup == 'Adjacent'
          space_type_hash[space_type][:adjacent_zones] << nil
        end

        # populate zones_by_standards
        unless zones_by_standards.key?(space_type_hash[space_type][:standards_array])
          zones_by_standards[space_type_hash[space_type][:standards_array]] = {}
        end
        zones_by_standards[space_type_hash[space_type][:standards_array]][thermal_zone] = space_type_hash
      end
    end

    if kitchen_makeup == 'Largest Zone'

      zones_applied = [] # add thermal zones to this ones they have had thermal_zone.add_exhaust model_run(model)  on it

      # loop through standard_space_types_with_makup_air
      standard_space_types_with_makup_air.each do |makeup_target, makeup_source|
        # hash to manage lookups
        markup_target_effective_floor_area = {}
        markup_source_effective_floor_area = {}

        if zones_by_standards.key?(makeup_target)

          # process zones of each makeup_target
          zones_by_standards[makeup_target].each do |thermal_zone, space_type_hash|
            effective_floor_area = 0.0
            space_type_hash.each do |space_type, hash|
              effective_floor_area += space_type_hash[space_type][:effective_floor_area]
            end
            markup_target_effective_floor_area[thermal_zone] = effective_floor_area
          end

          # find zone with largest effective area of this space type
          largest_target_zone = markup_target_effective_floor_area.key(markup_target_effective_floor_area.values.max)

          # find total effective area to calculate exhaust, then divide by zone multiplier when add exhaust
          target_effective_floor_area = markup_target_effective_floor_area.values.reduce(0, :+)

          # find zones that match makeup_target with makeup_source
          if zones_by_standards.key?(makeup_source)

            # process zones of each makeup_source
            zones_by_standards[makeup_source].each do |thermal_zone, space_type_hash|
              effective_floor_area = 0.0
              space_type_hash.each do |space_type, hash|
                effective_floor_area += space_type_hash[space_type][:effective_floor_area]
              end

              markup_source_effective_floor_area[thermal_zone] = effective_floor_area
            end
            # find zone with largest effective area of this space type
            largest_source_zone = markup_source_effective_floor_area.key(markup_source_effective_floor_area.values.max)
          else

            # issue warning that makeup air wont be made but still make exhaust
            OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Model has zone with #{makeup_target} but not #{makeup_source}. Exhaust will be added, but no makeup air.")
            next

          end

          OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Largest #{makeup_target} is #{largest_target_zone.name} which will provide exhaust for #{target_effective_floor_area} m^2")
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Largest #{makeup_source} is #{largest_source_zone.name} which will provide makeup air for #{makeup_target}")

          # add in extra arguments for makeup air
          exhaust_makeup_inputs = {}
          exhaust_makeup_inputs[makeup_target] = {} # for now only one makeup target per zone, but method could have multiple
          exhaust_makeup_inputs[makeup_target][:target_effective_floor_area] = target_effective_floor_area
          exhaust_makeup_inputs[makeup_target][:source_zone] = largest_source_zone

          # add exhaust
          next if zones_applied.include?(largest_target_zone) # would only hit this if zone has two space types each requesting makeup air

          zone_exhaust_hash = thermal_zone_add_exhaust(largest_target_zone, exhaust_makeup_inputs)
          zones_applied << largest_target_zone
          zone_exhaust_fans.merge!(zone_exhaust_hash)

        end
      end

      # add exhaust to zones that did not contain space types with standard_space_types_with_makup_air
      zones_by_standards.each do |standards_array, zones_hash|
        next if standard_space_types_with_makup_air.key?(standards_array)

        # loop through zones adding exhaust
        zones_hash.each do |thermal_zone, space_type_hash|
          next if zones_applied.include?(thermal_zone)

          # add exhaust
          zone_exhaust_hash = thermal_zone_add_exhaust(thermal_zone)
          zones_applied << thermal_zone
          zone_exhaust_fans.merge!(zone_exhaust_hash)
        end
      end

    else # kitchen_makeup == "Adjacent"

      zones_applied = [] # add thermal zones to this ones they have had thermal_zone.add_exhaust model_run(model)  on it

      standard_space_types_with_makup_air.each do |makeup_target, makeup_source|
        if zones_by_standards.key?(makeup_target)
          # process zones of each makeup_target
          zones_by_standards[makeup_target].each do |thermal_zone, space_type_hash|
            # get adjacent zones
            adjacent_zones = OpenstudioStandards::Geometry.thermal_zone_get_adjacent_zones_with_shared_walls(thermal_zone)

            # find adjacent zones matching key and value from standard_space_types_with_makup_air
            first_adjacent_makeup_source = nil
            adjacent_zones.each do |adjacent_zone|
              next unless first_adjacent_makeup_source.nil?

              if zones_by_standards.key?(makeup_source) && zones_by_standards[makeup_source].key?(adjacent_zone)
                first_adjacent_makeup_source = adjacent_zone

                # @todo add in extra arguments for makeup air
                exhaust_makeup_inputs = {}
                exhaust_makeup_inputs[makeup_target] = {} # for now only one makeup target per zone, but method could have multiple
                exhaust_makeup_inputs[makeup_target][:source_zone] = first_adjacent_makeup_source

                # add exhaust
                zone_exhaust_hash = thermal_zone_add_exhaust(thermal_zone, exhaust_makeup_inputs)
                zones_applied << thermal_zone
                zone_exhaust_fans.merge!(zone_exhaust_hash)
              end
            end

            if first_adjacent_makeup_source.nil?

              # issue warning that makeup air wont be made but still make exhaust
              OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Model has zone with #{makeup_target} but no adjacent zone with #{makeup_source}. Exhaust will be added, but no makeup air.")

              # add exhaust
              zone_exhaust_hash = thermal_zone_add_exhaust(thermal_zone)
              zones_applied << thermal_zone
              zone_exhaust_fans.merge!(zone_exhaust_hash)

            end
          end

        end
      end

      # add exhaust for rest of zones
      model.getThermalZones.sort.each do |thermal_zone|
        next if zones_applied.include?(thermal_zone)

        # add exhaust
        zone_exhaust_hash = thermal_zone_add_exhaust(thermal_zone)
        zone_exhaust_fans.merge!(zone_exhaust_hash)
      end

    end

  end

  return zone_exhaust_fans
end
model_add_exterior_lights(model, building_type, climate_zone, prototype_input) click to toggle source

Adds exterior lights to the building, as specified in OpenStudio_Standards_prototype_inputs

@param (see add_constructions) @return [Boolean] returns true if successful, false if not @todo translate w/linear foot of facade, door, parking, etc

into lookup table and implement that way instead of hard-coding as
inputs in the spreadsheet.
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1321
def model_add_exterior_lights(model, building_type, climate_zone, prototype_input)
  # @todo Standards - translate w/linear foot of facade, door, parking, etc
  #   into lookup table and implement that way instead of hard-coding as
  #   inputs in the spreadsheet.
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started adding exterior lights')

  # Occupancy Sensing Exterior Lights
  # which reduce to 70% power when no one is around.
  unless prototype_input['occ_sensing_exterior_lighting_power'].nil?
    occ_sens_ext_lts_power = prototype_input['occ_sensing_exterior_lighting_power']
    occ_sens_ext_lts_sch_name = prototype_input['occ_sensing_exterior_lighting_schedule']
    occ_sens_ext_lts_name = 'Occ Sensing Exterior Lights'
    occ_sens_ext_lts_def = OpenStudio::Model::ExteriorLightsDefinition.new(model)
    occ_sens_ext_lts_def.setName("#{occ_sens_ext_lts_name} Def")
    occ_sens_ext_lts_def.setDesignLevel(occ_sens_ext_lts_power)
    occ_sens_ext_lts_sch = model_add_schedule(model, occ_sens_ext_lts_sch_name)
    occ_sens_ext_lts = OpenStudio::Model::ExteriorLights.new(occ_sens_ext_lts_def, occ_sens_ext_lts_sch)
    occ_sens_ext_lts.setName("#{occ_sens_ext_lts_name} Def")
    occ_sens_ext_lts.setControlOption('AstronomicalClock')
  end

  # Building Facade and Landscape Lights
  # that don't dim at all at night.
  unless prototype_input['nondimming_exterior_lighting_power'].nil?
    nondimming_ext_lts_power = prototype_input['nondimming_exterior_lighting_power']
    nondimming_ext_lts_sch_name = prototype_input['nondimming_exterior_lighting_schedule']
    nondimming_ext_lts_name = 'NonDimming Exterior Lights'
    nondimming_ext_lts_def = OpenStudio::Model::ExteriorLightsDefinition.new(model)
    nondimming_ext_lts_def.setName("#{nondimming_ext_lts_name} Def")
    nondimming_ext_lts_def.setDesignLevel(nondimming_ext_lts_power)
    nondimming_ext_lts_sch = model_add_schedule(model, nondimming_ext_lts_sch_name)
    nondimming_ext_lts = OpenStudio::Model::ExteriorLights.new(nondimming_ext_lts_def, nondimming_ext_lts_sch)
    nondimming_ext_lts.setName("#{nondimming_ext_lts_name} Def")
    nondimming_ext_lts.setControlOption('AstronomicalClock')
  end

  # Fuel Equipment being used to model external elevators
  unless prototype_input['exterior_fuel_equipment1_power'].nil?
    fuel_ext_power = prototype_input['exterior_fuel_equipment1_power']
    fuel_ext_sch_name = prototype_input['exterior_fuel_equipment1_schedule']
    fuel_ext_name = 'Fuel equipment 1'
    fuel_ext_def = OpenStudio::Model::ExteriorFuelEquipmentDefinition.new(model)
    fuel_ext_def.setName("#{fuel_ext_name} Def")
    fuel_ext_def.setDesignLevel(fuel_ext_power)
    fuel_ext_sch = model_add_schedule(model, fuel_ext_sch_name)
    fuel_ext_lts = OpenStudio::Model::ExteriorFuelEquipment.new(fuel_ext_def, fuel_ext_sch)
    fuel_ext_lts.setFuelType('Electricity')
    fuel_ext_lts.setName(fuel_ext_name.to_s)
  end

  unless prototype_input['exterior_fuel_equipment2_power'].nil?
    fuel_ext_power = prototype_input['exterior_fuel_equipment2_power']
    fuel_ext_sch_name = prototype_input['exterior_fuel_equipment2_schedule']
    fuel_ext_name = 'Fuel equipment 2'
    fuel_ext_def = OpenStudio::Model::ExteriorFuelEquipmentDefinition.new(model)
    fuel_ext_def.setName("#{fuel_ext_name} Def")
    fuel_ext_def.setDesignLevel(fuel_ext_power)
    fuel_ext_sch = model_add_schedule(model, fuel_ext_sch_name)
    fuel_ext_lts = OpenStudio::Model::ExteriorFuelEquipment.new(fuel_ext_def, fuel_ext_sch)
    fuel_ext_lts.setFuelType('Electricity')
    fuel_ext_lts.setName(fuel_ext_name.to_s)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding exterior lights')

  return true
end
model_add_full_space_type_libs(model) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 208
def model_add_full_space_type_libs(model)
  space_type_properties_list = standards_lookup_table_many(table_name: 'space_types')
  space_type_properties_list.each do |space_type_property|
    stub_space_type = OpenStudio::Model::SpaceType.new(model)
    stub_space_type.setStandardsBuildingType(space_type_property['building_type'])
    stub_space_type.setStandardsSpaceType(space_type_property['space_type'])
    stub_space_type.setName("-#{space_type_property['building_type']}-#{space_type_property['space_type']}")
    space_type_apply_rendering_color(stub_space_type)
  end
  model_add_loads(model)
end
model_add_guestroom_vacancy_controls(model, building_type) click to toggle source

Add guestroom vacancy controls @note code_sections [90.1-2016_6.4.3.3.5]

@param model [OpenStudio::Model::Model] OpenStudio model object @param building_type [String] the building type @return [Boolean] Returns true if successful, false otherwise

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 896
def model_add_guestroom_vacancy_controls(model, building_type)
  # Guestrooms are currently only included in the small and large hotel prototypes
  return true unless (building_type == 'LargeHotel') || (building_type == 'SmallHotel')

  # Guestrooms controls only required for 90.1-2016 and onward
  return true unless (template == '90.1-2016') || (template == '90.1-2019')

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Guestroom Vacancy Controls')

  # Define guestroom vacancy maps
  # List of all spaces that represent vacant rooms
  guestroom_vacancy_map = {
    'LargeHotel' => [
      'Room_3_Mult19_Flr_3'
    ],
    'SmallHotel' => [
      'GuestRoom101',
      'GuestRoom102',
      'GuestRoom201',
      'GuestRoom215_218',
      'GuestRoom301',
      'GuestRoom302_305',
      'GuestRoom313',
      'GuestRoom319',
      'GuestRoom324',
      'GuestRoom402_405',
      'GuestRoom406_408',
      'GuestRoom413',
      'GuestRoom414'
    ]
  }

  # Iterate through spaces and apply for guestroom vacancy controls
  thermostat_schedules = {}
  thermostat_schedules['Heating'] = []
  thermostat_schedules['Cooling'] = []
  model.getSpaces.sort.each do |space|
    # Get space name
    space_name = space.name

    # Get space type
    space_type = space.spaceType.get

    # Skip space types with no standards building type
    next if space_type.standardsBuildingType.empty?

    stds_bldg_type = space_type.standardsBuildingType.get

    # Skip space types with no standards space type
    next if space_type.standardsSpaceType.empty?

    stds_spc_type = space_type.standardsSpaceType.get

    # Skip building types and space types that aren't listed in the guestroom vacancy maps
    next unless guestroom_vacancy_map.key?(stds_bldg_type)
    next unless guestroom_vacancy_map[stds_bldg_type].include?(space_name.to_s)

    # Get thermal zone and thermostat schedules associated with space
    thermal_zone = space.thermalZone.get
    if thermal_zone.thermostatSetpointDualSetpoint.is_initialized
      thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
      if thermostat.heatingSetpointTemperatureSchedule.is_initialized && thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized
        thermostat_schedules['Heating'] << thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
      end
      if thermostat.coolingSetpointTemperatureSchedule.is_initialized && thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized
        thermostat_schedules['Cooling'] << thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get
      end
    end

    # Get zone equipment fan
    # Currently prototypes with guestrooms use PTAC and 4PFC
    # @todo Implement additional system type (zonal and air loop-based)
    thermal_zone.equipment.sort.each do |zone_equipment|
      if zone_equipment.to_ZoneHVACPackagedTerminalAirConditioner.is_initialized
        equipment = zone_equipment.to_ZoneHVACPackagedTerminalAirConditioner.get
      elsif zone_equipment.to_ZoneHVACFourPipeFanCoil.is_initialized
        equipment = zone_equipment.to_ZoneHVACFourPipeFanCoil.get
      else
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "#{thermal_zone} is not served by either a packaged terminal air-conditioner or four pipe fan coil, vacancy fan schedule has not been adjusted")
        next
      end

      # Change fan operation schedule
      fan = if equipment.supplyAirFan.to_FanConstantVolume.is_initialized
              equipment.supplyAirFan.to_FanConstantVolume.get
            elsif equipment.supplyAirFan.to_FanVariableVolume.is_initialized
              equipment.supplyAirFan.to_FanVariableVolume.get
            elsif equipment.supplyAirFan.to_FanOnOff.is_initialized
              equipment.supplyAirFan.to_FanOnOff.get
            end
      fan.setAvailabilitySchedule(model_add_schedule(model, 'GuestroomVacantFanSchedule'))
    end

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished Adding Guestroom Vacancy Controls')
  end

  # Adjust thermostat schedules:
  # Increase set-up/back to comply with code requirements
  thermostat_schedules.keys.each do |sch_type|
    thermostat_schedules[sch_type].uniq.each do |sch|
      # Skip non-ruleset schedules
      next if sch.to_ScheduleRuleset.empty?

      # Get schedule modifier
      case template
        when '90.1-2016', '90.1-2019'
          case sch_type
            when 'Heating'
              sch_mult = 15.556 / 18.889 # Set thermostat to 15.556
            when 'Cooling'
              sch_mult = 26.667 / 23.333 # Set thermostat to 26.667
            else
              sch_mult = 1 # No adjustments
          end
        else
          shc_mult = 1 # No adjustments
      end

      # Modify schedules
      OpenstudioStandards::Schedules.schedule_day_multiply_by_value(sch.defaultDaySchedule, sch_mult)
    end
  end
end
model_add_guestroom_vent_sch(model, building_type) click to toggle source

Add guestroom ventilation availability schedules based on the thermostat heating setpoint schedules (to infer setback) Call to this method needs to be place before purge hour implementation of zone ventilation (if any)

@code_sections [90.1-2019_6.4.3.3.5.2] @author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio Model @param building_type [String] Building type

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1027
def model_add_guestroom_vent_sch(model, building_type)
  return true unless (template == '90.1-2016') || (template == '90.1-2019')

  # Guestrooms are currently only included in the small and large hotel prototypes
  return true unless (building_type == 'LargeHotel') || (building_type == 'SmallHotel')

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Guestroom Ventilation Schedules')

  # Define guestroom occupied maps
  # List of all spaces that represent vacant rooms
  guestroom_occupied_map = {
    'LargeHotel' => [
      'Room_1_Flr_3',
      'Room_1_Flr_6',
      'Room_2_Flr_3',
      'Room_2_Flr_6',
      'Room_3_Mult9_Flr_6',
      'Room_4_Mult19_Flr_3',
      'Room_5_Flr_3',
      'Room_6_Flr_3'
    ],
    'SmallHotel' => [
      'GuestRoom103',
      'GuestRoom104',
      'GuestRoom105',
      'GuestRoom202_205',
      'GuestRoom206_208',
      'GuestRoom209_212',
      'GuestRoom213',
      'GuestRoom214',
      'GuestRoom219',
      'GuestRoom220_223',
      'GuestRoom224',
      'GuestRoom306_308',
      'GuestRoom309_312',
      'GuestRoom314',
      'GuestRoom315_318',
      'GuestRoom320_323',
      'GuestRoom401',
      'GuestRoom409_412',
      'GuestRoom415_418',
      'GuestRoom419',
      'GuestRoom420_423',
      'GuestRoom424'
    ]
  }

  # Extract thermostat schedule as the base for ventilation schedule
  if building_type == 'LargeHotel'
    # thermostat_name = 'LargeHotel GuestRoom Thermostat'
    air_terminals = model.getAirTerminalSingleDuctConstantVolumeNoReheats.sort
  elsif building_type == 'SmallHotel'
    # thermostat_name = 'SmallHotel GuestRoom4Occ Thermostat'
    air_terminals = model.getZoneHVACPackagedTerminalAirConditioners.sort
  end

  guestroom_htg_schrst, guestroom_clg_schrst = get_occ_guestroom_setpoint_schedules(model)

  # intentionally no check so if anything is wrong, this will break
  guestroom_htg_sch = guestroom_htg_schrst.to_ScheduleRuleset.get.defaultDaySchedule
  guestroom_clg_sch = guestroom_clg_schrst.to_ScheduleRuleset.get.defaultDaySchedule

  if guestroom_htg_sch.times != guestroom_clg_sch.times
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "#{building_type} Guestroom heating and cooling schedule has different setback times, will use htg for generating the ventilation binary schedule")
  end

  # Build ventilation binary schedule
  htg_sch_values = guestroom_htg_sch.values
  htg_sch_times = guestroom_htg_sch.times

  vent_schrst = OpenStudio::Model::ScheduleRuleset.new(model)
  vent_schrst.setName("#{building_type}_GuestRoom_Vent_Ctrl_Sch")
  # add design day values (1)
  vent_winterdesignday_sch = OpenStudio::Model::ScheduleDay.new(model)
  vent_winterdesignday_sch.setName("#{building_type}_GuestRoom_Vent_Ctrl_Sch Winter Design Day")
  model_add_vals_to_sch(model, vent_winterdesignday_sch, 'Constant', [1])
  vent_schrst.setSummerDesignDaySchedule(vent_winterdesignday_sch)
  vent_summerdesignday_sch = OpenStudio::Model::ScheduleDay.new(model)
  vent_summerdesignday_sch.setName("#{building_type}_GuestRoom_Vent_Ctrl_Sch Summer Design Day")
  model_add_vals_to_sch(model, vent_summerdesignday_sch, 'Constant', [1])
  vent_schrst.setWinterDesignDaySchedule(vent_summerdesignday_sch)

  # add default ventilation schedule
  vent_day_sch = vent_schrst.defaultDaySchedule
  vent_day_sch.setName("#{building_type}_GuestRoom_Vent_Ctrl_Sch Default")
  vent_day_binary_values = []
  off_value = htg_sch_values.min
  htg_sch_values.each do |value|
    vent_day_binary_values << if value > off_value
                                1.0
                              else
                                0.0
                              end
  end
  vent_day_binary_values.each_with_index do |binary_value, i|
    vent_day_sch.addValue(htg_sch_times[i], binary_value)
  end

  # link vent schedule to guest room air terminals
  modified_zones = []
  air_terminals.each do |airterminal|
    zone_name = airterminal.name.to_s.strip.split[0]
    if guestroom_occupied_map[building_type].include? zone_name
      if building_type == 'LargeHotel'
        airterminal.setAvailabilitySchedule(vent_schrst)
      elsif building_type == 'SmallHotel'
        airterminal.setSupplyAirFanOperatingModeSchedule(vent_schrst)
      end
      modified_zones << zone_name
    end
  end
end
model_add_holidays(model) click to toggle source

Adds holidays to the model. @todo enable holidays once supported inside OpenStudio schedules

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1467
def model_add_holidays(model)
  newyear = OpenStudio::Model::RunPeriodControlSpecialDays.new('1/1', model)
  newyear.setName('New Years')
  newyear.setSpecialDayType('Holiday')

  fourth = OpenStudio::NthDayOfWeekInMonth.new(4)
  thurs = OpenStudio::DayOfWeek.new('Thursday')
  nov = OpenStudio::MonthOfYear.new('November')
  thanksgiving = OpenStudio::Model::RunPeriodControlSpecialDays.new(fourth, thurs, nov, model)
  thanksgiving.setName('Thanksgiving')
  thanksgiving.setSpecialDayType('Holiday')

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Added holidays: New Years, Thanksgiving.')
end
model_add_internal_mass(model, building_type) click to toggle source

Adds internal mass objects and constructions based on the building type

@param model OpenStudio Model @param building_type [String] the building type @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 553
def model_add_internal_mass(model, building_type)
  # Assign a material to all internal mass objects
  material = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  material.setName('Std Wood 6inch')
  material.setRoughness('MediumSmooth')
  material.setThickness(0.15)
  material.setThermalConductivity(0.12)
  material.setDensity(540)
  material.setSpecificHeat(1210)
  material.setThermalAbsorptance(0.9)
  material.setSolarAbsorptance(0.7)
  material.setVisibleAbsorptance(0.7)
  construction = OpenStudio::Model::Construction.new(model)
  construction.setName('InteriorFurnishings')
  layers = OpenStudio::Model::MaterialVector.new
  layers << material
  construction.setLayers(layers)

  # Assign the internal mass construction to existing internal mass objects
  model.getSpaces.sort.each do |space|
    internal_masses = space.internalMass
    internal_masses.each do |internal_mass|
      internal_mass.internalMassDefinition.setConstruction(construction)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Set internal mass construction for internal mass '#{internal_mass.name}' in space '#{space.name}'.")
    end
  end

  # add internal mass
  # not required for NECB2011
  unless template == 'NECB2011' ||
         building_type.include?('DataCenter') ||
         ((building_type == 'SmallHotel') &&
          (template == '90.1-2004' || template == '90.1-2007' || template == '90.1-2010' || template == '90.1-2013' || template == '90.1-2016' || template == '90.1-2019' || template == 'NREL ZNE Ready 2017'))
    internal_mass_def = OpenStudio::Model::InternalMassDefinition.new(model)
    internal_mass_def.setSurfaceAreaperSpaceFloorArea(2.0)
    internal_mass_def.setConstruction(construction)
    model.getSpaces.each do |space|
      # only add internal mass objects to conditioned spaces
      next unless OpenstudioStandards::Space.space_cooled?(space)
      next unless OpenstudioStandards::Space.space_heated?(space)

      internal_mass = OpenStudio::Model::InternalMass.new(internal_mass_def)
      internal_mass.setName("#{space.name} Mass")
      internal_mass.setSpace(space)
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added internal mass '#{internal_mass.name}' to space '#{space.name}'.")
    end
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished adding internal mass')

  return true
end
model_add_lights_shutoff(model) click to toggle source

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/common/objects/Prototype.Model.rb, line 2136
def model_add_lights_shutoff(model)
  return false
end
model_add_loads(model) click to toggle source

Adds the loads and associated schedules for each space type as defined in the OpenStudio_Standards_space_types.json file. This includes lights, plug loads, occupants, ventilation rate requirements, infiltration, gas equipment (for kitchens, etc.) and typical schedules for each. Some loads are governed by the standard, others are typical values pulled from sources such as the DOE Reference and DOE Prototype Buildings.

@return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 228
def model_add_loads(model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)')

  # Loop through all the space types currently in the model,
  # which are placeholders, and give them appropriate loads and schedules
  model.getSpaceTypes.sort.each do |space_type|
    # Rendering color
    space_type_apply_rendering_color(space_type)

    # Loads
    space_type_apply_internal_loads(space_type, true, true, true, true, true, true)

    # Schedules
    space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)')

  return true
end
model_add_occupancy_sensors(model, building_type, climate_zone) click to toggle source

Adds occupancy sensors to certain space types per the PNNL documentation.

@param (see add_constructions) @return [Boolean] returns true if successful, false if not @todo genericize and move this method to Standards.Space

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1215
def model_add_occupancy_sensors(model, building_type, climate_zone)
  # Only add occupancy sensors for 90.1-2010
  # Currently deactivated for all 90.1 versions
  # of the prototype because occupancy sensor is
  # currently modeled using different schedules
  # hence this code double counts savings from
  # sensors.
  # @todo Move occupancy sensor modeling from
  # schedule to code.
  case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', '90.1-2016', '90.1-2019'
      return true
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started Adding Occupancy Sensors')

  space_type_reduction_map = {
    'SecondarySchool' => { 'Classroom' => 0.32, 'Restroom' => 0.34, 'Office' => 0.22 },
    'PrimarySchool' => { 'Classroom' => 0.32, 'Restroom' => 0.34, 'Office' => 0.22 }
  }

  # Loop through all the space types and reduce lighting operation schedule fractions as-specified
  model.getSpaceTypes.sort.each do |space_type|
    # Skip space types with no standards building type
    next if space_type.standardsBuildingType.empty?

    stds_bldg_type = space_type.standardsBuildingType.get

    # Skip space types with no standards space type
    next if space_type.standardsSpaceType.empty?

    stds_spc_type = space_type.standardsSpaceType.get

    # Skip building types and space types that aren't listed in the hash
    next unless space_type_reduction_map.key?(stds_bldg_type)
    next unless space_type_reduction_map[stds_bldg_type].key?(stds_spc_type)

    # Get the reduction fraction multiplier
    red_multiplier = 1 - space_type_reduction_map[stds_bldg_type][stds_spc_type]

    lights_sch_names = []
    lights_schs = {}
    reduced_lights_schs = {}

    # Get all of the lights in this space type
    # and determine the list of schedules they use.
    space_type.lights.each do |light|
      # Skip lights that don't have a schedule
      next if light.schedule.empty?

      lights_sch = light.schedule.get
      lights_schs[lights_sch.name.to_s] = lights_sch
      lights_sch_names << lights_sch.name.to_s
    end

    # Loop through the unique list of lighting schedules, cloning
    # and reducing schedule fraction before and after the specified times
    lights_sch_names.uniq.each do |lights_sch_name|
      lights_sch = lights_schs[lights_sch_name]
      # Skip non-ruleset schedules
      next if lights_sch.to_ScheduleRuleset.empty?

      # Clone the schedule (so that we don't mess with lights in
      # other space types that might be using the same schedule).
      new_lights_sch = lights_sch.clone(model).to_ScheduleRuleset.get
      new_lights_sch.setName("#{lights_sch_name} OccSensor Reduction")
      reduced_lights_schs[lights_sch_name] = new_lights_sch

      # Reduce default day schedule
      OpenstudioStandards::Schedules.schedule_day_multiply_by_value(new_lights_sch.defaultDaySchedule, red_multiplier, lower_apply_limit: 0.25)

      # Reduce all other rule schedules
      new_lights_sch.scheduleRules.each do |sch_rule|
        OpenstudioStandards::Schedules.schedule_day_multiply_by_value(sch_rule.daySchedule, red_multiplier, lower_apply_limit: 0.25)
      end
    end

    # Loop through all lights instances, replacing old lights
    # schedules with the reduced schedules.
    space_type.lights.each do |light|
      # Skip lights that don't have a schedule
      next if light.schedule.empty?

      old_lights_sch_name = light.schedule.get.name.to_s
      if reduced_lights_schs[old_lights_sch_name]
        light.setSchedule(reduced_lights_schs[old_lights_sch_name])
        OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Occupancy sensor reduction added to '#{light.name}'")
      end
    end
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished Adding Occupancy Sensors')

  return true
end
model_add_transfer_air(model) click to toggle source

Add transfer to prototype for spaces that require it

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2301
def model_add_transfer_air(model)
  # Do not add transfer air if not required
  return true unless model_transfer_air_required?(model)

  # Get target and source zones
  target_and_source_zones = model_transfer_air_target_and_source_zones(model)
  return true if target_and_source_zones.empty?

  model.getFanZoneExhausts.sort.each do |exhaust_fan|
    # Target zone (zone with exhaust fan)
    target_zone = exhaust_fan.thermalZone.get

    # Get zone name of an exhaust fan
    exhaust_fan_zone_name = target_zone.name.to_s

    # Go to next exhaust fan if this zone isn't using transfer air
    next unless target_and_source_zones.keys.include? exhaust_fan_zone_name

    # Add dummy exhaust fan in source zone
    source_zone_name, transfer_air_flow_cfm = target_and_source_zones[exhaust_fan_zone_name]
    source_zone = model.getThermalZoneByName(source_zone_name).get
    transfer_air_source_zone_exhaust_fan = OpenStudio::Model::FanZoneExhaust.new(model)
    transfer_air_source_zone_exhaust_fan.setName(source_zone.name.to_s + ' Dummy Transfer Air (Source) Fan')
    transfer_air_source_zone_exhaust_fan.setAvailabilitySchedule(exhaust_fan.availabilitySchedule.get)
    # Convert transfer air flow to m3/s
    transfer_air_flow_m3s = OpenStudio.convert(transfer_air_flow_cfm, 'cfm', 'm^3/s').get
    transfer_air_source_zone_exhaust_fan.setMaximumFlowRate(transfer_air_flow_m3s)
    transfer_air_source_zone_exhaust_fan.setFanEfficiency(1.0)
    transfer_air_source_zone_exhaust_fan.setPressureRise(0.0)
    transfer_air_source_zone_exhaust_fan.addToThermalZone(source_zone)

    # Set exhaust fan balanced air flow schedule to only consider the transfer air to be balanced air flow
    balanced_air_flow_schedule = exhaust_fan.availabilitySchedule.get.clone(model).to_ScheduleRuleset.get
    balanced_air_flow_schedule.setName("#{exhaust_fan_zone_name} Exhaust Fan Balanced Air Flow Schedule")
    OpenstudioStandards::Schedules.schedule_day_multiply_by_value(balanced_air_flow_schedule.defaultDaySchedule, transfer_air_flow_m3s / exhaust_fan.maximumFlowRate.get)
    balanced_air_flow_schedule.scheduleRules.each do |sch_rule|
      OpenstudioStandards::Schedules.schedule_day_multiply_by_value(sch_rule.daySchedule, transfer_air_flow_m3s / exhaust_fan.maximumFlowRate.get)
    end
    transfer_air_source_zone_exhaust_fan.setBalancedExhaustFractionSchedule(balanced_air_flow_schedule)

    # Modify design specification OA to take into account transfer air for sizing only
    # OA is zero'd out for other days
    target_zone.spaces.sort.each do |space|
      target_zone_ventilation = space.designSpecificationOutdoorAir.get
      target_zone_ventilation.setOutdoorAirFlowperPerson(0)
      target_zone_ventilation.setOutdoorAirFlowperFloorArea(0)
      target_zone_ventilation.setOutdoorAirFlowRate(exhaust_fan.maximumFlowRate.get - transfer_air_flow_m3s)
      target_zone_ventilation.setOutdoorAirFlowRateFractionSchedule(model_add_schedule(model, 'DesignDaysOnly'))
    end
  end

  return true
end
model_apply_prototype_hvac_assumptions(model, building_type, climate_zone) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1526
def model_apply_prototype_hvac_assumptions(model, building_type, climate_zone)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying prototype HVAC assumptions.')

  # Fan pressure rise
  model.getFanConstantVolumes.sort.each { |obj| fan_constant_volume_apply_prototype_fan_pressure_rise(obj) }
  model.getFanVariableVolumes.sort.each { |obj| fan_variable_volume_apply_prototype_fan_pressure_rise(obj) }
  model.getFanOnOffs.sort.each { |obj| fan_on_off_apply_prototype_fan_pressure_rise(obj) }
  model.getFanZoneExhausts.sort.each { |obj| fan_zone_exhaust_apply_prototype_fan_pressure_rise(obj) }

  # Fan motor efficiency
  model.getFanConstantVolumes.sort.each { |obj| prototype_fan_apply_prototype_fan_efficiency(obj) }
  model.getFanVariableVolumes.sort.each { |obj| prototype_fan_apply_prototype_fan_efficiency(obj) }
  model.getFanOnOffs.sort.each { |obj| prototype_fan_apply_prototype_fan_efficiency(obj) }
  model.getFanZoneExhausts.sort.each { |obj| prototype_fan_apply_prototype_fan_efficiency(obj) }

  # Gas Heating Coil
  model.getCoilHeatingGass.sort.each { |obj| coil_heating_gas_apply_prototype_efficiency(obj) }

  # Add Economizers
  apply_economizers(climate_zone, model)

  # @todo What is the logic behind hard-sizing hot water coil convergence tolerances?
  model.getControllerWaterCoils.sort.each { |obj| controller_water_coil_set_convergence_limits(obj) }

  # Adjust defrost curve limits for coil heating dx single speed
  model.getCoilHeatingDXSingleSpeeds.sort.each { |obj| coil_heating_dx_single_speed_apply_defrost_eir_curve_limits(obj) }

  # Pump part load performances
  model.getPumpVariableSpeeds.sort.each { |obj| pump_variable_speed_control_type(obj) }

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying prototype HVAC assumptions.')
end
model_apply_prototype_hvac_efficiency_adjustments(model) click to toggle source

Applies the Prototype Building assumptions that contradict/supersede the given standard.

@param model [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1563
def model_apply_prototype_hvac_efficiency_adjustments(model)
  building_data = model_get_building_properties(model)
  building_type = building_data['building_type']
  climate_zone = building_data['climate_zone']

  # ERVs
  if building_type == 'MidriseApartment' || building_type == 'HighriseApartment'
    # Use standalone ERV in dwelling units to provide OA
    # Loads are met by mechanical cooling and the heating system with a cycling fan
    model.getAirLoopHVACs.each do |air_loop_hvac|
      # Find out if air loop has an ERV (i.e. if heat recovery is required)
      has_erv = false
      has_erv = true if air_loop_hvac_energy_recovery?(air_loop_hvac)

      serves_res_spc = false

      air_loop_hvac.thermalZones.each do |zone|
        next unless OpenstudioStandards::ThermalZone.thermal_zone_residential?(zone)

        # Exception 3 to 6.5.6.1.1
        case template
        when '90.1-2019'
          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-2006-4A',
            'ASHRAE 169-2006-4B',
            'ASHRAE 169-2006-4C',
            'ASHRAE 169-2006-5A',
            'ASHRAE 169-2006-5B',
            'ASHRAE 169-2006-5C',
            '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',
            'ASHRAE 169-2013-4A',
            'ASHRAE 169-2013-4B',
            'ASHRAE 169-2013-4C',
            'ASHRAE 169-2013-5A',
            'ASHRAE 169-2013-5B',
            'ASHRAE 169-2013-5C'
            if zone.floorArea <= OpenStudio.convert(500.0, 'ft^2', 'm^2').get
              has_erv = false
              OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Energy recovery will not be modeled for the ERV serving #{zone.name}.")
            end
          end
        end

        oa_cfm_per_ft2 = 0.0578940512546562
        oa_m3_per_m2 = OpenStudio.convert(OpenStudio.convert(oa_cfm_per_ft2, 'cfm', 'm^3/s').get, '1/ft^2', '1/m^2').get
        if has_erv
          model_add_residential_erv(model, zone, oa_m3_per_m2)
        else
          model_add_residential_ventilator(model, zone, oa_m3_per_m2)
        end

        # Shut-off air loop level OA intake
        oa_controller = air_loop_hvac.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir
        oa_controller.setMinimumOutdoorAirSchedule(model.alwaysOffDiscreteSchedule)

        serves_res_spc = true
      end

      if has_erv & serves_res_spc
        # Remove air loop ERV
        air_loop_hvac_remove_erv(air_loop_hvac)
      elsif has_erv
        # Apply regular adjustment if the ERV doesn't serve a residential space
        oa_sys = nil
        if air_loop_hvac.airLoopHVACOutdoorAirSystem.is_initialized
          oa_sys = air_loop_hvac.airLoopHVACOutdoorAirSystem.get
        else
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{air_loop_hvac.name}, ERV cannot be removed because the system has no OA intake.")
          return false
        end

        # Get the existing ERV or create an ERV and add it to the OA system
        oa_sys.oaComponents.each do |oa_comp|
          if oa_comp.to_HeatExchangerAirToAirSensibleAndLatent.is_initialized
            erv = oa_comp.to_HeatExchangerAirToAirSensibleAndLatent.get
            heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency(erv)
          end
        end
      end
    end
  else
    # Applies the DOE Prototype Building assumption that ERVs use
    # enthalpy wheels and therefore exceed the minimum effectiveness specified by 90.1
    model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency(obj) }
  end

  # Update COP for large office CRAC
  # Applies the DOE Prototype Building Model (Large Office only)
  if @instvarbuilding_type == 'LargeOffice'
    model.getCoilCoolingWaterToAirHeatPumpEquationFits.sort.each do |coil_cooling_water_to_air_heat_pump|
      if coil_cooling_water_to_air_heat_pump.name.get.downcase.include?('datacenter')
        cop = coil_cooling_water_to_air_heat_pump_standard_minimum_cop(coil_cooling_water_to_air_heat_pump, rename = false, computer_room_air_conditioner = true)
        if cop.nil?
          OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "COP for #{coil_cooling_water_to_air_heat_pump.name} is not changed")
        else
          coil_cooling_water_to_air_heat_pump.setRatedCoolingCoefficientofPerformance(cop)
        end
      end
    end
  end

  return true
end
model_apply_sizing_parameters(model, building_type) click to toggle source

Changes the infiltration coefficients for the prototype vintages.

@param (see add_constructions) @return [Boolean] returns true if successful, false if not @todo Consistency - make sizing factors consistent

between building types, climate zones, and vintages?
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1488
def model_apply_sizing_parameters(model, building_type)
  # Default unless otherwise specified
  clg = 1.2
  htg = 1.2
  case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
      case building_type
        when 'PrimarySchool', 'SecondarySchool', 'Outpatient'
          clg = 1.5
          htg = 1.5
        when 'LargeHotel'
          clg = 1.33
          htg = 1.33
      end
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', '90.1-2016', '90.1-2019'
      # exit if is one of the 90.1 templates as their sizing paramters are explicitly specified in geometry osms
      return
    when 'CBES Pre-1978', 'CBES T24 1978', 'CBES T24 1992', 'CBES T24 2001', 'CBES T24 2005', 'CBES T24 2008'
      case building_type
        when 'Hospital', 'LargeHotel', 'MediumOffice', 'LargeOffice', 'MediumOfficeDetailed', 'LargeOfficeDetailed', 'Outpatient', 'PrimarySchool'
          clg = 1.0
          htg = 1.0
      end
    when 'NECB2011'
      raise('do not use this method for NECB')
    else
      # Use the sizing factors from 90.1 PRM
      clg = 1.15
      htg = 1.25
  end

  sizing_params = model.getSizingParameters
  sizing_params.setHeatingSizingFactor(htg)
  sizing_params.setCoolingSizingFactor(clg)

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Set sizing factors to #{htg} for heating and #{clg} for cooling.")
end
model_cast_model_object(model_object) click to toggle source

This method is used to automatically cast a model object based on its IDD object type

@param [OpenStudio::Model::ModelObject] @return Casted OpenStudio object or nil if the cast was not possible

# File lib/openstudio-standards/utilities/object_info.rb, line 29
def model_cast_model_object(model_object)
  model_object_type = model_object.iddObjectType.valueName.to_s.sub('OS_', '').strip.sub('_', '')
  casting_method_name = "to_#{model_object_type}"
  casted_object = nil

  # Make sure that the the casting method can be applied
  if model_object.respond_to?(casting_method_name)
    casted_object = model_object.public_send(casting_method_name).get
  end

  return casted_object
end
model_clear_and_set_example_constructions(model) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1936
def model_clear_and_set_example_constructions(model)
  # Define Materials
  opaque_mat = OpenStudio::Model::StandardOpaqueMaterial.new(model, 'Smooth', 0.0127, 0.16, 0.1, 100)
  insulation_mat = OpenStudio::Model::StandardOpaqueMaterial.new(model, 'Smooth', 0.05, 0.043, 0.1, 100)
  simple_glazing_mat = OpenStudio::Model::SimpleGlazing.new(model, 3.236460, 0.25)
  simple_glazing_mat.setThickness(0.003)
  simple_glazing_mat.setVisibleTransmittance(0.16)
  standard_glazing_mat = OpenStudio::Model::StandardGlazing.new(model, 'SpectralAverage', 0.003)
  standard_glazing_mat.setSolarTransmittanceatNormalIncidence(0.5)

  # Define Constructions
  # # Surfaces
  ext_wall = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionExtWall', [opaque_mat, insulation_mat], insulation_mat)
  ext_roof = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionExtRoof', [opaque_mat, insulation_mat], insulation_mat)
  ext_floor = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionExtFloor', [opaque_mat, insulation_mat], insulation_mat)
  grnd_wall = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionGrndWall', [opaque_mat, insulation_mat], insulation_mat)
  grnd_roof = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionGrndRoof', [opaque_mat, insulation_mat], insulation_mat)
  grnd_floor = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionGrndFloor', [opaque_mat, insulation_mat], insulation_mat)
  int_wall = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionIntWall', [opaque_mat, insulation_mat], insulation_mat)
  int_roof = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionIntRoof', [opaque_mat, insulation_mat], insulation_mat)
  int_floor = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionIntFloor', [opaque_mat, insulation_mat], insulation_mat)
  # # Subsurfaces
  fixed_window = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionFixed', [simple_glazing_mat])
  operable_window = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionOperable', [simple_glazing_mat])
  glass_door = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionDoor', [standard_glazing_mat])
  door = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionDoor', [opaque_mat, insulation_mat], insulation_mat)
  overhead_door = BTAP::Resources::Envelope::Constructions.create_construction(model, 'OpaqueConstructionOverheadDoor', [opaque_mat, insulation_mat], insulation_mat)
  skylt = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionSkylight', [standard_glazing_mat])
  daylt_dome = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionDomeConstruction', [standard_glazing_mat])
  daylt_diffuser = BTAP::Resources::Envelope::Constructions.create_construction(model, 'FenestrationConstructionDiffuserConstruction', [standard_glazing_mat])

  # Define Construction Sets
  # # Surface
  exterior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(model, 'ExteriorSet', ext_wall, ext_roof, ext_floor)
  interior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(model, 'InteriorSet', int_wall, int_roof, int_floor)
  ground_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_surface_constructions(model, 'GroundSet', grnd_wall, grnd_roof, grnd_floor)

  # # Subsurface
  subsurface_exterior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_subsurface_construction_set(model, fixed_window, operable_window, door, glass_door, overhead_door, skylt, daylt_dome, daylt_diffuser)
  subsurface_interior_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_subsurface_construction_set(model, fixed_window, operable_window, door, glass_door, overhead_door, skylt, daylt_dome, daylt_diffuser)

  # Define default construction sets.
  name = 'Construction Set 1'
  default_construction_set = BTAP::Resources::Envelope::ConstructionSets.create_default_construction_set(model, name, exterior_construction_set, interior_construction_set, ground_construction_set, subsurface_exterior_construction_set, subsurface_interior_construction_set)

  # Assign default to the model.
  model.getBuilding.setDefaultConstructionSet(default_construction_set)

  return default_construction_set
end
model_create_prototype_model(climate_zone, epw_file, sizing_run_dir = Dir.pwd, debug = false, measure_model = nil) click to toggle source

creates an openstudio standards version of PNNL/DOE prototype buildings

@param climate_zone [String] ASHRAE climate zone, e.g. ‘ASHRAE 169-2013-4A’ @param epw_file [] @param sizing_run_dir [String] @param debug @param measure_model @return

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 12
def model_create_prototype_model(climate_zone, epw_file, sizing_run_dir = Dir.pwd, debug = false, measure_model = nil)
  building_type = @instvarbuilding_type
  raise 'no building_type!' if @instvarbuilding_type.nil?

  model = nil
  # There are no reference models for HighriseApartment and data centers at vintages Pre-1980 and 1980-2004,
  # nor for NECB2011. This is a quick check.
  case @instvarbuilding_type
  when 'HighriseApartment', 'SmallDataCenterLowITE', 'SmallDataCenterHighITE', 'LargeDataCenterLowITE', 'LargeDataCenterHighITE', 'Laboratory', 'TallBuilding', 'SuperTallBuilding'
    if template == 'DOE Ref Pre-1980' || template == 'DOE Ref 1980-2004'
      OpenStudio.logFree(OpenStudio::Error, 'Not available', "DOE Reference models for #{@instvarbuilding_type} at   are not available, the measure is disabled for this specific type.")
      return false
    end
  end
  # optionally  determine the climate zone from the epw and stat files.
  if climate_zone == 'NECB HDD Method'
    weather_file_path = OpenstudioStandards::Weather.get_standards_weather_file_path(epw_file)
    stat_file_path = weather_file_path.gsub('.epw', '.stat')
    stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
    climate_zone = OpenstudioStandards::Weather.get_climate_zone_from_degree_days(stat_file.hdd18, stat_file.cdd10)
  else
    # this is required to be blank otherwise it may cause side effects.
    epw_file = ''
  end
  model = load_geometry_osm(@geometry_file)
  OpenstudioStandards::Weather.model_set_building_location(model, climate_zone: climate_zone)
  model_custom_geometry_tweaks(model, building_type, climate_zone, @prototype_input)
  model.getThermostatSetpointDualSetpoints(&:remove)
  model.getBuilding.setName(self.class.to_s)
  # save new basefile to new geometry folder as class name.
  model.getBuilding.setName("-#{@instvarbuilding_type}-#{climate_zone} created: #{Time.new}")
  model_add_loads(model)
  model_apply_infiltration_standard(model)
  model_modify_infiltration_coefficients(model, @instvarbuilding_type, climate_zone)
  model_add_door_infiltration(model, climate_zone)
  model_modify_surface_convection_algorithm(model)
  model_create_thermal_zones(model, @space_multiplier_map)
  model_add_hvac(model, @instvarbuilding_type, climate_zone, @prototype_input)
  model.getAirLoopHVACs.each do |air_loop|
    next unless air_loop_hvac_multizone_vav_system?(air_loop)

    model_system_outdoor_air_sizing_vrp_method(air_loop)
    air_loop_hvac_apply_vav_damper_action(air_loop)
  end
  model_add_constructions(model, @instvarbuilding_type, climate_zone)
  model_fenestration_orientation(model, climate_zone)
  model_custom_hvac_tweaks(model, building_type, climate_zone, @prototype_input)
  model_add_transfer_air(model)
  model_add_internal_mass(model, @instvarbuilding_type)
  model_add_swh(model, @instvarbuilding_type, @prototype_input)
  model_add_exterior_lights(model, @instvarbuilding_type, climate_zone, @prototype_input)
  model_add_occupancy_sensors(model, @instvarbuilding_type, climate_zone)
  model_add_daylight_savings(model)
  model_apply_sizing_parameters(model, @instvarbuilding_type)
  model.yearDescription.get.setDayofWeekforStartDay('Sunday')
  model.getBuilding.setStandardsBuildingType(building_type)
  model_add_lights_shutoff(model)
  # Perform a sizing model_run(model)
  return false if model_run_sizing_run(model, "#{sizing_run_dir}/SR1") == false

  # If there are any multizone systems, reset damper positions
  # to achieve a 60% ventilation effectiveness minimum for the system
  # following the ventilation rate procedure from 62.1
  model_apply_multizone_vav_outdoor_air_sizing(model)
  # Apply the prototype HVAC assumptions
  # which include sizing the fan pressure rises based
  # on the flow rate of the system.
  model_apply_prototype_hvac_assumptions(model, building_type, climate_zone)
  # custom economizer controls
  # For 90.1-2010 Outpatient, AHU1 doesn't have economizer and AHU2 set minimum outdoor air flow rate as 0
  model_modify_oa_controller(model)
  # For operating room 1&2 in 2010, 2013, 2016, and 2019, VAV minimum air flow is set by schedule
  model_reset_or_room_vav_minimum_damper(@prototype_input, model)
  # Apply the HVAC efficiency standard
  model_apply_hvac_efficiency_standard(model, climate_zone)
  # Apply prototype changes that supersede the HVAC efficiency standard
  model_apply_prototype_hvac_efficiency_adjustments(model)
  model_custom_swh_tweaks(model, @instvarbuilding_type, climate_zone, @prototype_input)
  # Fix EMS references.
  # Temporary workaround for OS issue #2598
  model_temp_fix_ems_references(model)
  # Add daylighting controls per standard
  # only four zones in large hotel have daylighting controls
  # @todo YXC to merge to the main function
  model_add_daylighting_controls(model)
  model_custom_daylighting_tweaks(model, building_type, climate_zone, @prototype_input)
  model_update_exhaust_fan_efficiency(model)
  model_update_fan_efficiency(model)
  # rename air loop and plant loop nodes for readability
  rename_air_loop_nodes(model)
  rename_plant_loop_nodes(model)
  # remove unused objects
  model_remove_unused_resource_objects(model)
  # Add output variables for debugging
  model_request_timeseries_outputs(model) if debug
  # If measure model is passed, then replace measure model with new model created here.
  return model if measure_model.nil?

  model_replace_model(measure_model, model)
  return measure_model
end
model_create_thermal_zones(model, space_multiplier_map = nil) click to toggle source

Creates thermal zones to contain each space, as defined for each building in the system_to_space_map inside the Prototype.building_name e.g. (Prototype.secondary_school.rb) file.

@param (see add_constructions) @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 612
def model_create_thermal_zones(model, space_multiplier_map = nil)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started creating thermal zones')

  # Retrieve zone multipliers if non assigned via the space_multiplier_map
  if space_multiplier_map.nil?
    space_multiplier_map = {}
    model.getSpaces.each do |spc|
      space_multiplier_map.store(spc.name.to_s, spc.thermalZone.get.multiplier.to_int)
    end
  end

  # Remove any Thermal zones assigned
  model.getThermalZones.each(&:remove)

  # Create a thermal zone for each space in the self
  thermostat_to_offset = []
  model.getSpaces.sort.each do |space|
    zone = OpenStudio::Model::ThermalZone.new(model)
    zone.setName("#{space.name} ZN")
    unless space_multiplier_map[space.name.to_s].nil? || (space_multiplier_map[space.name.to_s] == 1)
      zone.setMultiplier(space_multiplier_map[space.name.to_s])
    end
    space.setThermalZone(zone)

    # Skip thermostat for spaces with no space type
    next if space.spaceType.empty?

    # Add a thermostat
    space_type_name = space.spaceType.get.name.get
    thermostat_name = space_type_name + ' Thermostat'
    thermostat = model.getThermostatSetpointDualSetpointByName(thermostat_name)
    if thermostat.empty?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Thermostat #{thermostat_name} not found for space name: #{space.name}")
    else
      thermostat_clone = thermostat.get.clone(model).to_ThermostatSetpointDualSetpoint.get
      zone.setThermostatSetpointDualSetpoint(thermostat_clone)
      if template == 'NECB2011'
        # Set Ideal loads to thermal zone for sizing for NECB needs. We need this for sizing.
        ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
        ideal_loads.addToThermalZone(zone)
      end
    end

    # Modify thermostat schedules if space
    # has standby mode occupancy requirements
    if space_occupancy_standby_mode_required?(space)
      next if thermostat_to_offset.include?(thermostat_clone.name)

      space_occupancy_standby_mode(thermostat_clone)
      thermostat_to_offset << thermostat_name
    end
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
end
model_do_all_zones_have_surfaces?(model) click to toggle source

Method to check if all zones have surfaces. This is required to run a simulation.

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/utilities/simulation.rb, line 222
def model_do_all_zones_have_surfaces?(model)
  # Check to see if all zones have surfaces.
  model.getThermalZones.each do |zone|
    surfaces = zone.spaces.sort.flat_map(&:surfaces)
    if surfaces.empty?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.simulation', "Thermal zone #{zone.name} does not contain surfaces.\n")
      return false
    end
  end
  return true
end
model_door_infil_flow_rate_metal_coiling_cfm_ft2(climate_zone) click to toggle source

Metal coiling door code minimum infiltration rate at 75 Pa

@param [String] Climate zone @return [Double] Minimum infiltration rate for metal coiling doors

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2154
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'
      return 0.4
    else
      return 4.4
  end
end
model_door_infil_flow_rate_open_cfm_ft2(climate_zone) click to toggle source

Open door infiltration rate at 75 Pa

@param [String] Climate zone @return [Double] Open door infiltration rate

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2178
def model_door_infil_flow_rate_open_cfm_ft2(climate_zone)
  return 9.7875
end
model_door_infil_flow_rate_rollup_cfm_ft2(climate_zone) click to toggle source

Metal rollup door code minimum infiltration rate at 75 Pa

@param [String] Climate zone @return [Double] Minimum infiltration rate for metal coiling doors

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2170
def model_door_infil_flow_rate_rollup_cfm_ft2(climate_zone)
  return 0.4
end
model_economizer_type(model, climate_zone) click to toggle source

Determine the prototypical economizer type for the model. Defaults to FixedDryBulb based on anecdotal evidence of this being the most common type encountered in the field, combined with this being the default option for many equipment manufacturers, and being the strategy recommended in the 2010 ASHRAE journal article “Economizer High Limit Devices and Why Enthalpy Economizers Don’t Work” by Steven Taylor and Hwakong Cheng. tayloreng.egnyte.com/dl/mN0c9t4WSO/ASHRAE_Journal_-Economizer_High_Limit_Devices_and_Why_Enthalpy_Economizers_Dont_Work.pdf

@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/prototypes/common/objects/Prototype.Model.rb, line 2040
def model_economizer_type(model, climate_zone)
  economizer_type = 'FixedDryBulb'
  return economizer_type
end
model_fenestration_orientation(model, climate_zone) click to toggle source

Adjust model to comply with fenestration orientation

@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/common/objects/Prototype.Model.rb, line 2275
def model_fenestration_orientation(model, climate_zone)
  return true
end
model_get_object_hash(obj) click to toggle source

Get hash of object field names and values @author: Xuechen (Jerry) Lei, PNNL @param obj [object]

@return [Hash<String>] FieldName:Value

# File lib/openstudio-standards/utilities/object_info.rb, line 10
def model_get_object_hash(obj)
  fields_array = obj.to_s.split(/\n/)
  output_hash = { 'object type' => fields_array.shift.split(/,/)[0] }
  right = nil
  fields_array.each do |ori_field|
    left, right = ori_field.split(/[,;]/)
    left = left.strip
    right.slice!('!-')
    right = right.strip
    output_hash[right] = left
  end
  return output_hash
end
model_get_standards_building_type(model) click to toggle source

Return the dominant standards building type

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1988
def model_get_standards_building_type(model)
  # determine areas of each building type
  building_type_areas = {}
  model.getSpaces.each do |space|
    # ignore space if not part of total area
    next unless space.partofTotalFloorArea

    if space.spaceType.is_initialized
      space_type = space.spaceType.get
      if space_type.standardsBuildingType.is_initialized
        building_type = space_type.standardsBuildingType.get
        if building_type_areas[building_type].nil?
          building_type_areas[building_type] = space.floorArea
        else
          building_type_areas[building_type] += space.floorArea
        end
      end
    end
  end

  # return largest building type area
  building_type = building_type_areas.key(building_type_areas.values.max)

  if building_type.nil?
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Model has no dominant standards building type.')
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "#{building_type} is the dominant standards building type.")
  end

  return building_type
end
model_get_window_area_info(model, wwr = true) click to toggle source

Calculate a model’s window or WWR Disregard space conditioning (assume all spaces are conditioned) which is true for most of not all prototypes

@param model [OpenStudio::Model::Model] OpenStudio model object @param wwr [Boolean] @return [Numeric] Returns window to wall ratio (percentage) or window area.

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2238
def model_get_window_area_info(model, wwr = true)
  window_area = 0
  wall_area = 0

  model.getSpaces.each do |space|
    # Get zone multipler
    multiplier = space.thermalZone.get.multiplier
    space.surfaces.each do |surface|
      next if surface.surfaceType != 'Wall'
      next if surface.outsideBoundaryCondition != 'Outdoors'

      # Get wall and window area
      wall_area += surface.grossArea * multiplier
      surface.subSurfaces.each do |subsurface|
        subsurface_type = subsurface.subSurfaceType.to_s.downcase
        # Do not count doors
        next unless (subsurface_type.include? 'window') || (subsurface_type.include? 'glass')

        window_area += subsurface.grossArea * subsurface.multiplier * multiplier
      end
    end
  end

  # check wall area is non-zero
  if wwr && wall_area > 0
    return window_area / wall_area * 100
  end

  # else
  return window_area
end
model_modify_infiltration_coefficients(model, building_type, climate_zone) click to toggle source

Changes the infiltration coefficients for the prototype vintages.

@param (see add_constructions) @return [Boolean] returns true if successful, false if not @todo Consistency - make prototype and reference vintages consistent

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1394
def model_modify_infiltration_coefficients(model, building_type, climate_zone)
  # Select the terrain type, which
  # impacts wind speed, and in turn infiltration
  terrain = 'City'
  case template
    when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013', '90.1-2016', '90.1-2019', 'NREL ZNE Ready 2017'
      case building_type
        when 'Warehouse'
          terrain = 'Urban'
        when 'SmallHotel'
          terrain = 'Suburbs'
      end
  end
  # Set the terrain type
  model.getSite.setTerrain(terrain)

  # modify the infiltration coefficients
  case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
      # @todo make this consistent with newer prototypes
      const_coeff = 1.0
      temp_coeff = 0.0
      velo_coeff = 0.0
      velo_sq_coeff = 0.0
    else
      # Includes a wind-velocity-based term
      const_coeff = 0.0
      temp_coeff = 0.0
      velo_coeff = 0.224
      velo_sq_coeff = 0.0
  end

  model.getSpaceInfiltrationDesignFlowRates.sort.each do |infiltration|
    infiltration.setConstantTermCoefficient(const_coeff)
    infiltration.setTemperatureTermCoefficient(temp_coeff)
    infiltration.setVelocityTermCoefficient(velo_coeff)
    infiltration.setVelocitySquaredTermCoefficient(velo_sq_coeff)
  end
end
model_modify_surface_convection_algorithm(model) click to toggle source

Sets the inside and outside convection algorithms for different vintages

@param (see add_constructions) @return [Boolean] returns true if successful, false if not @todo Consistency - make prototype and reference vintages consistent

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1439
def model_modify_surface_convection_algorithm(model)
  inside = model.getInsideSurfaceConvectionAlgorithm
  outside = model.getOutsideSurfaceConvectionAlgorithm

  case template
    when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
      inside.setAlgorithm('TARP')
      outside.setAlgorithm('DOE-2')
    else
      inside.setAlgorithm('TARP')
      outside.setAlgorithm('TARP')
  end
end
model_offset_schedule_value(schedule, time_offset_hash) click to toggle source

Offset values of a schedule Main usage is for modeling occupancy standby mode where the thermostat schedule in these mode are required to setup/back their thermostat setpoints

@param time_offset_hash [Hash] Hash providing time (key) and schedule value offset (values) @param schedule [OpenStudio::Model::ScheduleRuleset] OpenStudio schedule object @return [OpenStudio::Model::ScheduleRuleset] Modified OpenStudio schedule object

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2363
def model_offset_schedule_value(schedule, time_offset_hash)
  offset_sch = schedule.clone(schedule.model).to_ScheduleRuleset.get

  # Get day schedule
  day_schedules = []
  default_day_schedule = offset_sch.defaultDaySchedule
  day_schedules << default_day_schedule
  offset_sch.scheduleRules.each do |rule|
    day_schedules << rule.daySchedule
  end

  # Offset schedule values
  day_schedules.each do |day_schedule|
    (0..23).each do |hr|
      next if !time_offset_hash.key?(hr.to_s)

      t = OpenStudio::Time.new(0, hr, 0, 0)

      # Get schedule value
      value = day_schedule.getValue(t)

      # Offset schedule value
      day_schedule.addValue(t, value)
      t_p_1 = OpenStudio::Time.new(0, hr + 1, 0, 0)
      day_schedule.addValue(t_p_1, value + time_offset_hash[hr.to_s])
    end
  end

  offset_sch.setName("#{schedule.name} - offset")
  return offset_sch
end
model_out_path(test_name) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 11
def model_out_path(test_name)
  "#{run_dir(test_name)}/ExampleModel.osm"
end
model_reduce_setback_sch_delay(model, building_type) click to toggle source

Reduce thermostat temperature setpoint delay (when switching from occupied to unoccupied ) by 10 mins

@code_sections [90.1-2019_6.4.3.3.5.1] @author Xuechen (Jerry) Lei, PNNL @param model [OpenStudio::Model::Model] OpenStudio Model @param building_type [String] Building type

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1146
def model_reduce_setback_sch_delay(model, building_type)
  # Guestrooms are currently only included in the small and large hotel prototypes
  return true unless (building_type == 'LargeHotel') || (building_type == 'SmallHotel')

  # Guestrooms setback schedule delay modifications are only added to 2019
  return true unless template == '90.1-2019'

  heating_schrst, cooling_schrst = get_occ_guestroom_setpoint_schedules(model)
  heating_default_day_sch = heating_schrst.defaultDaySchedule
  schedule_reduce_reset_delay_10min(heating_default_day_sch, heating_default_day_sch.values.min)
  cooling_default_day_sch = cooling_schrst.defaultDaySchedule
  schedule_reduce_reset_delay_10min(cooling_default_day_sch, cooling_default_day_sch.values.max)
end
model_replace_model(model_to_replace, new_model, runner = nil) click to toggle source

Replaces the contents of ‘model_to_replace’ with the contents of ‘new_model.’ This method can be used when the memory location of model_to_replace needs to be preserved, for example, when a measure is passed.

@param model_to_replace [OpenStudio::Model::Model] OpenStudio model object @param new_model [OpenStudio::Model::Model] OpenStudio model object @return [OpenStudio::Model::Model] OpenStudio model object

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 121
def model_replace_model(model_to_replace, new_model, runner = nil)
  # remove existing objects from model
  handles = OpenStudio::UUIDVector.new
  model_to_replace.objects.each do |obj|
    handles << obj.handle
  end
  model_to_replace.removeObjects(handles)

  # put contents of new_model into model_to_replace
  model_to_replace.addObjects(new_model.toIdfFile.objects)
  BTAP.runner_register('Info', "Model name is now #{model_to_replace.building.get.name}.", runner)
  return model_to_replace
end
model_replace_model_from_osm(model, rel_path_to_osm) click to toggle source

Replaces all objects in the current model with the objects in the .osm. Typically used to load a model as a starting point.

@param model [OpenStudio::Model::Model] OpenStudio model object @param rel_path_to_osm [String] the path to an .osm file, relative to this file @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 142
def model_replace_model_from_osm(model, rel_path_to_osm)
  # Take the existing model and remove all the objects
  # (this is cheesy), but need to keep the same memory block
  handles = OpenStudio::UUIDVector.new
  model.objects.each { |objects| handles << objects.handle }
  model.removeObjects(handles)
  model = nil
  if File.dirname(__FILE__)[0] == ':'
    # running from embedded location

    # Load geometry from the saved geometry.osm
    geom_model_string = load_resource_relative(rel_path_to_osm)
    puts geom_model_string
    # version translate from string
    version_translator = OpenStudio::OSVersion::VersionTranslator.new
    geom_model = version_translator.loadModelFromString(geom_model_string)

  else
    abs_path = File.join(File.dirname(__FILE__), rel_path_to_osm)

    # version translate from string
    version_translator = OpenStudio::OSVersion::VersionTranslator.new
    geom_model = version_translator.loadModel(abs_path)
    raise
  end

  if geom_model.empty?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Version translation failed for #{rel_path_to_osm}")
    return false
  end
  geom_model = geom_model.get

  # Add the objects from the geometry model to the working model
  model.addObjects(geom_model.toIdfFile.objects)
  return true
end
model_request_timeseries_outputs(model) click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1869
def model_request_timeseries_outputs(model)
  # "detailed"
  # "timestep"
  # "hourly"
  # "daily"
  # "monthly"

  vars = []
  # vars << ['Heating Coil Gas Rate', 'detailed']
  # vars << ['Zone Thermostat Air Temperature', 'detailed']
  # vars << ['Zone Thermostat Heating Setpoint Temperature', 'detailed']
  # vars << ['Zone Thermostat Cooling Setpoint Temperature', 'detailed']
  # vars << ['Zone Air System Sensible Heating Rate', 'detailed']
  # vars << ['Zone Air System Sensible Cooling Rate', 'detailed']
  # vars << ['Fan Electric Power', 'detailed']
  # vars << ['Zone Mechanical Ventilation Standard Density Volume Flow Rate', 'detailed']
  # vars << ['Air System Outdoor Air Mass Flow Rate', 'detailed']
  # vars << ['Air System Outdoor Air Flow Fraction', 'detailed']
  # vars << ['Air System Outdoor Air Minimum Flow Fraction', 'detailed']

  # vars << ['Water Use Equipment Hot Water Volume Flow Rate', 'hourly']
  # vars << ['Water Use Equipment Cold Water Volume Flow Rate', 'hourly']
  # vars << ['Water Use Equipment Total Volume Flow Rate', 'hourly']
  # vars << ['Water Use Equipment Hot Water Temperature', 'hourly']
  # vars << ['Water Use Equipment Cold Water Temperature', 'hourly']
  # vars << ['Water Use Equipment Target Water Temperature', 'hourly']
  # vars << ['Water Use Equipment Mixed Water Temperature', 'hourly']

  # vars << ['Water Use Connections Hot Water Volume Flow Rate', 'hourly']
  # vars << ['Water Use Connections Cold Water Volume Flow Rate', 'hourly']
  # vars << ['Water Use Connections Total Volume Flow Rate', 'hourly']
  # vars << ['Water Use Connections Hot Water Temperature', 'hourly']
  # vars << ['Water Use Connections Cold Water Temperature', 'hourly']
  # vars << ['Water Use Connections Plant Hot Water Energy', 'hourly']
  # vars << ['Water Use Connections Return Water Temperature', 'hourly']

  # vars << ['Air System Outdoor Air Economizer Status','timestep']
  # vars << ['Air System Outdoor Air Heat Recovery Bypass Status','timestep']
  # vars << ['Air System Outdoor Air High Humidity Control Status','timestep']
  # vars << ['Air System Outdoor Air Flow Fraction','timestep']
  # vars << ['Air System Outdoor Air Minimum Flow Fraction','timestep']
  # vars << ['Air System Outdoor Air Mass Flow Rate','timestep']
  # vars << ['Air System Mixed Air Mass Flow Rate','timestep']

  # vars << ['Heating Coil Gas Rate','timestep']
  vars << ['Boiler Part Load Ratio', 'timestep']
  vars << ['Boiler Gas Rate', 'timestep']
  # vars << ['Boiler Gas Rate','timestep']
  # vars << ['Fan Electric Power','timestep']

  vars << ['Pump Electric Power', 'timestep']
  vars << ['Pump Outlet Temperature', 'timestep']
  vars << ['Pump Mass Flow Rate', 'timestep']

  # vars << ['Zone Air Terminal VAV Damper Position','timestep']
  # vars << ['Zone Air Terminal Minimum Air Flow Fraction','timestep']
  # vars << ['Zone Air Terminal Outdoor Air Volume Flow Rate','timestep']
  # vars << ['Zone Lights Electric Power','hourly']
  # vars << ['Daylighting Lighting Power Multiplier','hourly']
  # vars << ['Schedule Value','hourly']

  vars.each do |var, freq|
    output_var = OpenStudio::Model::OutputVariable.new(var, model)
    output_var.setReportingFrequency(freq)
  end
end
model_run(model, run_dir = " click to toggle source
# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1715
def model_run(model, run_dir = "#{Dir.pwd}/Run")
  # If the model_run(model)  directory is not specified
  # model_run(model)  in the current working directory

  # Make the directory if it doesn't exist
  unless Dir.exist?(run_dir)
    Dir.mkdir(run_dir)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Started simulation in '#{run_dir}'")

  # Change the simulation to only model_run(model)  the weather file
  # and not model_run(model)  the sizing day simulations
  sim_control = model.getSimulationControl
  sim_control.setRunSimulationforSizingPeriods(false)
  sim_control.setRunSimulationforWeatherFileRunPeriods(true)

  # Save the model to energyplus idf
  idf_name = 'in.idf'
  osm_name = 'in.osm'
  forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
  idf = forward_translator.translateModel(model)
  idf_path = OpenStudio::Path.new("#{run_dir}/#{idf_name}")
  osm_path = OpenStudio::Path.new("#{run_dir}/#{osm_name}")
  idf.save(idf_path, true)
  save(osm_path, true)

  # Set up the sizing simulation
  # Find the weather file
  epw_path = nil
  if model.weatherFile.is_initialized
    epw_path = model.weatherFile.get.path
    if epw_path.is_initialized
      if File.exist?(epw_path.get.to_s)
        epw_path = epw_path.get
      else
        # If this is an always-run Measure, need to check a different path
        alt_weath_path = File.expand_path(File.join(File.dirname(__FILE__), '../../../resources'))
        alt_epw_path = File.expand_path(File.join(alt_weath_path, epw_path.get.to_s))
        if File.exist?(alt_epw_path)
          epw_path = OpenStudio::Path.new(alt_epw_path)
        else
          OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', "Model has been assigned a weather file, but the file is not in the specified location of '#{epw_path.get}'.")
          return false
        end
      end
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'Model has a weather file assigned, but the weather file path has been deleted.')
      return false
    end
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.prototype.Model', 'Model has not been assigned a weather file.3')
    return false
  end

  # If running on a regular desktop, use RunManager.
  # If running on OpenStudio Server, use WorkFlowMananger
  # to avoid slowdown from the sizing run.
  use_runmanager = true

  begin
    require 'openstudio-workflow'
    use_runmanager = false
  rescue LoadError
    use_runmanager = true
  end

  sql_path = nil
  if use_runmanager
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Running sizing model_run(model)  with RunManager.')

    # Find EnergyPlus
    ep_dir = OpenStudio.getEnergyPlusDirectory
    ep_path = OpenStudio.getEnergyPlusExecutable
    ep_tool = OpenStudio::Runmanager::ToolInfo.new(ep_path)
    idd_path = OpenStudio::Path.new(ep_dir.to_s + '/Energy+.idd')
    output_path = OpenStudio::Path.new("#{run_dir}/")

    # Make a run manager and queue up the sizing model_run(model)
    run_manager_db_path = OpenStudio::Path.new("#{run_dir}/run.db")
    run_manager = OpenStudio::Runmanager::RunManager.new(run_manager_db_path, true, false, false, false)
    job = OpenStudio::Runmanager::JobFactory.createEnergyPlusJob(ep_tool,
                                                                 idd_path,
                                                                 idf_path,
                                                                 epw_path,
                                                                 output_path)

    run_manager.enqueue(job, true)

    # Start the sizing model_run(model)  and wait for it to finish.
    while run_manager.workPending
      sleep 1
      OpenStudio::Application.instance.processEvents
    end

    sql_path = OpenStudio::Path.new("#{run_dir}/Energyplus/eplusout.sql")

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Finished sizing model_run(model)  in #{(Time.new - start_time).round}sec.")

  else # Use the openstudio-workflow gem
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', 'Running sizing model_run(model)  with openstudio-workflow gem.')

    # Copy the weather file to this directory
    FileUtils.copy(epw_path.to_s, run_dir)

    # Run the simulation
    sim = OpenStudio::Workflow.run_energyplus('Local', run_dir)
    final_state = model_run(sim)

    if final_state == :finished
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.prototype.Model', "Finished sizing model_run(model)  in #{(Time.new - start_time).round}sec.")
    end

    sql_path = OpenStudio::Path.new("#{run_dir}/run/eplusout.sql")

  end

  # Load the sql file created by the sizing model_run(model)
  sql_path = OpenStudio::Path.new("#{run_dir}/Energyplus/eplusout.sql")
  if OpenStudio.exists(sql_path)
    sql = OpenStudio::SqlFile.new(sql_path)
    # Check to make sure the sql file is readable,
    # which won't be true if EnergyPlus crashed during simulation.
    unless sql.connectionOpen
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The model_run(model)  failed.  Look at the eplusout.err file in #{File.dirname(sql_path.to_s)} to see the cause.")
      return false
    end
    # Attach the sql file from the model_run(model)  to the sizing model
    model.setSqlFile(sql)
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Results for the sizing model_run(model)  couldn't be found here: #{sql_path}.")
    return false
  end

  # Check that the model_run(model)  finished without severe errors
  error_query = "SELECT ErrorMessage
      FROM Errors
      WHERE ErrorType='1'"

  errs = model.sqlFile.get.execAndReturnVectorOfString(error_query)
  if errs.is_initialized
    errs = errs.get
    unless errs.empty?
      errs = errs.get
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The model_run(model)  failed with the following severe errors: #{errs.join('\n')}.")
      return false
    end
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Finished simulation in '#{run_dir}'")

  return true
end
model_run_simulation_and_log_errors(model, run_dir = " click to toggle source

Runs an annual simulation

@param model [OpenStudio::Model::Model] OpenStudio model object @param run_dir [String] file path location for the annual run, defaults to ‘Run’ in the current directory @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/utilities/simulation.rb, line 9
def model_run_simulation_and_log_errors(model, run_dir = "#{Dir.pwd}/Run")
  # Make the directory if it doesn't exist
  unless Dir.exist?(run_dir)
    FileUtils.mkdir_p(run_dir)
  end

  # Save the model to energyplus idf
  idf_name = 'in.idf'
  osm_name = 'in.osm'
  osw_name = 'in.osw'
  OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Model', "Starting simulation here: #{run_dir}.")
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Started simulation #{run_dir} at #{Time.now.strftime('%T.%L')}")
  forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
  idf = forward_translator.translateModel(model)
  idf_path = OpenStudio::Path.new("#{run_dir}/#{idf_name}")
  osm_path = OpenStudio::Path.new("#{run_dir}/#{osm_name}")
  osw_path = OpenStudio::Path.new("#{run_dir}/#{osw_name}")
  idf.save(idf_path, true)
  model.save(osm_path, true)

  # Set up the simulation
  # Find the weather file
  epw_path = OpenstudioStandards::Weather.model_get_full_weather_file_path(model)
  if epw_path.empty?
    return false
  end

  epw_path = epw_path.get

  # close current sql file
  model.resetSqlFile

  # If running on a regular desktop, use RunManager.
  # If running on OpenStudio Server, use WorkFlowMananger
  # to avoid slowdown from the run.
  use_runmanager = true

  begin
    workflow = OpenStudio::WorkflowJSON.new
    use_runmanager = false
  rescue NameError
    use_runmanager = true
  end

  sql_path = nil
  if use_runmanager
    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Model', 'Running with RunManager.')

    # Find EnergyPlus
    ep_dir = OpenStudio.getEnergyPlusDirectory
    ep_path = OpenStudio.getEnergyPlusExecutable
    ep_tool = OpenStudio::Runmanager::ToolInfo.new(ep_path)
    idd_path = OpenStudio::Path.new(ep_dir.to_s + '/Energy+.idd')
    output_path = OpenStudio::Path.new("#{run_dir}/")

    # Make a run manager and queue up the run
    run_manager_db_path = OpenStudio::Path.new("#{run_dir}/run.db")
    # HACK: workaround for Mac with Qt 5.4, need to address in the future.
    OpenStudio::Application.instance.application(false)
    run_manager = OpenStudio::Runmanager::RunManager.new(run_manager_db_path, true, false, false, false)
    job = OpenStudio::Runmanager::JobFactory.createEnergyPlusJob(ep_tool,
                                                                 idd_path,
                                                                 idf_path,
                                                                 epw_path,
                                                                 output_path)

    run_manager.enqueue(job, true)

    # Start the run and wait for it to finish.
    while run_manager.workPending
      sleep 1
      OpenStudio::Application.instance.processEvents
    end

    sql_path = OpenStudio::Path.new("#{run_dir}/EnergyPlus/eplusout.sql")

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Finished simulation #{run_dir} at #{Time.now.strftime('%T.%L')}")

  else # method to running simulation within measure using OpenStudio 2.x WorkflowJSON

    OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Model', 'Running with OS 2.x WorkflowJSON.')

    # Copy the weather file to this directory
    epw_name = 'in.epw'
    begin
      FileUtils.copy(epw_path.to_s, "#{run_dir}/#{epw_name}")
    rescue StandardError
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Due to limitations on Windows file path lengths, this measure won't work unless your project is located in a directory whose filepath is less than 90 characters long, including slashes.")
      return false
    end

    workflow.setSeedFile(osm_name)
    workflow.setWeatherFile(epw_name)
    workflow.saveAs(File.absolute_path(osw_path.to_s))

    # 'touch' the weather file - for some odd reason this fixes the simulation not running issue we had on openstudio-server.
    # Removed for until further investigation completed.
    # FileUtils.touch("#{run_dir}/#{epw_name}")

    cli_path = OpenStudio.getOpenStudioCLI
    cmd = "\"#{cli_path}\" run -w \"#{osw_path}\""
    # cmd = "\"#{cli_path}\" --verbose run -w \"#{osw_path}\""
    puts cmd

    # Run the sizing run
    OpenstudioStandards.run_command(cmd)

    OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Finished simulation #{run_dir} at #{Time.now.strftime('%T.%L')}")

    sql_path = OpenStudio::Path.new("#{run_dir}/run/eplusout.sql")

  end

  # @todo Delete the eplustbl.htm and other files created by the run for cleanliness.

  if OpenStudio.exists(sql_path)
    sql = OpenStudio::SqlFile.new(sql_path)
    # Check to make sure the sql file is readable,
    # which won't be true if EnergyPlus crashed during simulation.
    unless sql.connectionOpen
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The run failed, cannot create model.  Look at the eplusout.err file in #{File.dirname(sql_path.to_s)} to see the cause.")
      return false
    end
    # Attach the sql file from the run to the model
    model.setSqlFile(sql)
  else
    # If the sql file does not exist, it is likely that EnergyPlus crashed,
    # in which case the useful errors are inside the eplusout.err file.
    err_file_path_string = "#{run_dir}/run/eplusout.err"
    err_file_path = OpenStudio::Path.new(err_file_path_string)
    if OpenStudio.exists(err_file_path)
      if __dir__[0] == ':' # Running from OpenStudio CLI
        errs = EmbeddedScripting.getFileAsString(err_file_path_string)
      else
        errs = File.read(err_file_path_string)
      end
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The run did not finish because of the following errors: #{errs}")
      return false
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Results for the run couldn't be found here: #{sql_path}.")
      return false
    end
  end

  # Report severe or fatal errors in the run
  error_query = "SELECT ErrorMessage
      FROM Errors
      WHERE ErrorType in(1,2)"
  errs = model.sqlFile.get.execAndReturnVectorOfString(error_query)
  if errs.is_initialized
    errs = errs.get
  end

  # Check that the run completed successfully
  end_file_stringpath = "#{run_dir}/run/eplusout.end"
  end_file_path = OpenStudio::Path.new(end_file_stringpath)
  if OpenStudio.exists(end_file_path)
    endstring = File.read(end_file_stringpath)
  end

  if !endstring.include?('EnergyPlus Completed Successfully')
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "The run did not finish and had following errors: #{errs.join('\n')}")
    return false
  end

  # Log any severe errors that did not cause simulation to fail
  unless errs.empty?
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "The run completed but had the following severe errors: #{errs.join('\n')}")
  end

  return true
end
model_run_sizing_run(model, sizing_run_dir = " click to toggle source

A helper method to run a sizing run and pull any values calculated during autosizing back into the model

@param model [OpenStudio::Model::Model] OpenStudio model object @param sizing_run_dir [String] file path location for the sizing run, defaults to ‘SR’ in the current directory @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/utilities/simulation.rb, line 187
def model_run_sizing_run(model, sizing_run_dir = "#{Dir.pwd}/SR")
  # Change the simulation to only run the sizing days
  sim_control = model.getSimulationControl
  sim_control.setRunSimulationforSizingPeriods(true)
  sim_control.setRunSimulationforWeatherFileRunPeriods(false)
  if model.version >= OpenStudio::VersionString.new('3.0.0')
    sim_control.setDoHVACSizingSimulationforSizingPeriods(true)
    sim_control.setMaximumNumberofHVACSizingSimulationPasses(1)
  end
  if model.version >= OpenStudio::VersionString.new('3.8.0')
    sim_control.setDoZoneSizingCalculation(true)
    sim_control.setDoSystemSizingCalculation(true)
    sim_control.setDoPlantSizingCalculation(true)
  end

  # check that all zones have surfaces.
  raise 'Error: Sizing Run Failed. Thermal Zones with no surfaces exist.' unless model_do_all_zones_have_surfaces?(model)

  # Run the sizing run
  success = model_run_simulation_and_log_errors(model, sizing_run_dir)

  # Change the model back to running the weather file
  sim_control.setRunSimulationforSizingPeriods(false)
  sim_control.setRunSimulationforWeatherFileRunPeriods(true)
  if model.version >= OpenStudio::VersionString.new('3.0.0')
    sim_control.setDoHVACSizingSimulationforSizingPeriods(false)
  end

  return success
end
model_run_space_sizing_run(sizing_run_dir = " click to toggle source

A helper method to run a space sizing run and pull any values calculated during autosizing back into the model.

@param model [OpenStudio::Model::Model] OpenStudio model object @param sizing_run_dir [String] file path location for the sizing run, defaults to ‘SpaceSR’ in the current directory @return [OpenStudio::Model::Model] returns the model if successful

# File lib/openstudio-standards/utilities/simulation.rb, line 239
def model_run_space_sizing_run(sizing_run_dir = "#{Dir.pwd}/SpaceSR")
  puts '*************Runing sizing space Run ***************************'
  # Make copy of model
  model = BTAP::FileIO.deep_copy(model, true)
  space_load_array = []

  # Make sure the model is good to run.
  # 1. Ensure External surfaces are set to a construction
  ext_surfaces = BTAP::Geometry::Surfaces.filter_by_boundary_condition(model.getSurfaces, ['Outdoors',
                                                                                           'Ground',
                                                                                           'GroundFCfactorMethod',
                                                                                           'GroundSlabPreprocessorAverage',
                                                                                           'GroundSlabPreprocessorCore',
                                                                                           'GroundSlabPreprocessorPerimeter',
                                                                                           'GroundBasementPreprocessorAverageWall',
                                                                                           'GroundBasementPreprocessorAverageFloor',
                                                                                           'GroundBasementPreprocessorUpperWall',
                                                                                           'GroundBasementPreprocessorLowerWall'])
  fail = false
  ext_surfaces.each do |surface|
    if surface.construction.nil?
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Ext Surface #{surface.name} does not have a construction.Cannot perform sizing.")
      fail = true
    end
  end
  puts "#{ext_surfaces.size} External Surfaces counted."
  raise "Can't run sizing since envelope is not set." if fail == true

  # remove any thermal zones.
  model.getThermalZones.each(&:remove)

  # assign a zone to each space.
  # Create a thermal zone for each space in the model
  model.getSpaces.each do |space|
    zone = OpenStudio::Model::ThermalZone.new(model)
    zone.setName("#{space.name} ZN")
    space.setThermalZone(zone)
  end
  # Add a thermostat
  BTAP::Compliance::NECB2011.set_zones_thermostat_schedule_based_on_space_type_schedules(model)
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
  # Add ideal loads to every zone/space and run
  # a sizing run to determine heating/cooling loads,
  # which will impact HVAC systems.
  model.getThermalZones.each do |zone|
    ideal_loads = OpenStudio::Model::ZoneHVACIdealLoadsAirSystem.new(model)
    ideal_loads.addToThermalZone(zone)
  end
  model_run_sizing_run(model, sizing_run_dir)
  model.getSpaces.each do |space|
    unless space.thermalZone.empty?
      # error if zone design load methods are not available
      if space.model.version < OpenStudio::VersionString.new('3.6.0')
        OpenStudio.logFree(OpenStudio::Error, 'openstudio.simulation', 'Required ThermalZone methods .autosizedHeatingDesignLoad and .autosizedCoolingDesignLoad are not available in pre-OpenStudio 3.6.0 versions. Use a more recent version of OpenStudio.')
      end

      space_cooling_load = 0.0
      if space.thermalZone.get.autosizedCoolingDesignLoad.is_initialized
        space_cooling_load = space.thermalZone.get.autosizedCoolingDesignLoad.get
      end

      space_heating_load = 0.0
      if space.thermalZone.get.autosizedHeatingDesignLoad.is_initialized
        space_heating_load = space.thermalZone.get.autosizedHeatingDesignLoad.get
      end

      space_load_array << { 'space_name' => space.name, 'CoolingDesignLoad' => space_cooling_load, 'HeatingDesignLoad' => space_heating_load }
    end
  end
  puts space_load_array
  puts '*************Done Runing sizing space Run ***************************'
  return model
end
model_set_below_grade_wall_constructions(model, building_type, climate_zone) click to toggle source

Creates and sets below grade wall constructions for 90.1 prototype building models. These utilize CFactorUndergroundWallConstruction and require some additional parameters when compared to Construction

@param model OpenStudio Model @param climate_zone [String climate zone as described for prototype models. C-Factor is based on this parameter @param building_type [String the building type @return [void]

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 446
def model_set_below_grade_wall_constructions(model, building_type, climate_zone)
  # Find ground contact wall building category
  construction_set_data = model_get_construction_set(building_type)
  building_type_category = construction_set_data['exterior_wall_building_category']

  wall_construction_properties = model_get_construction_properties(model, 'GroundContactWall', 'Mass', building_type_category, climate_zone)

  # If no construction properties are found at all, return and allow code to use default constructions
  return if wall_construction_properties.nil?

  c_factor_ip = wall_construction_properties['assembly_maximum_c_factor']

  # If no c-factor is found in construction properties, return and allow code to use defaults
  return if c_factor_ip.nil?

  # convert to SI
  c_factor_si = c_factor_ip * OpenStudio.convert(1.0, 'Btu/ft^2*h*R', 'W/m^2*K').get

  # iterate through spaces and set any necessary CFactorUndergroundWallConstructions
  model.getSpaces.each do |space|
    # Get height of the first below grade wall in this space. Will return nil if none are found.
    below_grade_wall_height = OpenstudioStandards::Geometry.space_get_below_grade_wall_height(space)
    next if below_grade_wall_height.nil?

    c_factor_wall_name = "Basement Wall C-Factor #{c_factor_si.round(2)} Height #{below_grade_wall_height.round(2)}"

    # Check if the wall construction has been constructed already. If so, look it up in the model
    if model.getCFactorUndergroundWallConstructionByName(c_factor_wall_name).is_initialized
      basement_wall_construction = model.getCFactorUndergroundWallConstructionByName(c_factor_wall_name).get
    else
      # Create CFactorUndergroundWallConstruction objects
      basement_wall_construction = OpenStudio::Model::CFactorUndergroundWallConstruction.new(model)
      basement_wall_construction.setCFactor(c_factor_si)
      basement_wall_construction.setName(c_factor_wall_name)
      basement_wall_construction.setHeight(below_grade_wall_height)
    end

    # Set surface construction for walls adjacent to ground (i.e. basement walls)
    space.surfaces.each do |surface|
      if surface.surfaceType == 'Wall' && surface.outsideBoundaryCondition == 'OtherSideCoefficients'
        surface.setConstruction(basement_wall_construction)
        surface.setOutsideBoundaryCondition('GroundFCfactorMethod')
      end
    end
  end
end
model_set_floor_constructions(model, building_type, climate_zone) click to toggle source

Searches a model for spaces adjacent to ground. If the slab’s perimeter is adjacent to ground, the length is calculated. Used for F-Factor floors that require additional parameters.

@param model [OpenStudio Model] OpenStudio model being modified @param building_type [String the building type @param climate_zone [String climate zone as described for prototype models. F-Factor is based on this parameter

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 499
def model_set_floor_constructions(model, building_type, climate_zone)
  # Find ground contact wall building category
  construction_set_data = model_get_construction_set(building_type)
  building_type_category = construction_set_data['ground_contact_floor_building_category']

  # Find Floor F factor
  floor_construction_properties = model_get_construction_properties(model, 'GroundContactFloor', 'Unheated', building_type_category, climate_zone)

  # If no construction properties are found at all, return and allow code to use default constructions
  return if floor_construction_properties.nil?

  f_factor_ip = floor_construction_properties['assembly_maximum_f_factor']

  # If no f-factor is found in construction properties, return and allow code to use defaults
  return if f_factor_ip.nil?

  f_factor_si = f_factor_ip * OpenStudio.convert(1.0, 'Btu/ft*h*R', 'W/m*K').get

  # iterate through spaces and set FFactorGroundFloorConstruction to surfaces if applicable
  model.getSpaces.each do |space|
    # Find this space's exposed floor area and perimeter. NOTE: this assumes only only floor per space.
    perimeter = OpenstudioStandards::Geometry.space_get_f_floor_perimeter(space)
    area = OpenstudioStandards::Geometry.space_get_f_floor_area(space)
    next if area == 0 # skip floors not adjacent to ground

    # Record combination of perimeter and area. Each unique combination requires a FFactorGroundFloorConstruction.
    f_floor_const_name = "Foundation F #{f_factor_si.round(2)}W/m*K Perim #{perimeter.round(2)}m Area #{area.round(2)}m2"

    # Check if the floor construction has been constructed already. If so, look it up in the model
    if model.getFFactorGroundFloorConstructionByName(f_floor_const_name).is_initialized
      f_floor_construction = model.getFFactorGroundFloorConstructionByName(f_floor_const_name).get
    else
      f_floor_construction = OpenStudio::Model::FFactorGroundFloorConstruction.new(model)
      f_floor_construction.setName(f_floor_const_name)
      f_floor_construction.setFFactor(f_factor_si)
      f_floor_construction.setArea(area)
      f_floor_construction.setPerimeterExposed(perimeter)
    end

    # Set surface construction for floors adjacent to ground
    space.surfaces.each do |surface|
      if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground'
        surface.setConstruction(f_floor_construction)
        surface.setOutsideBoundaryCondition('GroundFCfactorMethod')
      end
    end
  end
end
model_set_schedule_value(schedule, time_value_hash) click to toggle source

Set/change values of a schedule Main usage is for modeling occupancy standby mode where the thermostat schedule in these mode are required to setup/back their thermostat setpoints

@param time_offset_hash [Hash] Hash providing time (key) and schedule value offset (values) @param schedule [OpenStudio::Model::ScheduleRuleset] OpenStudio schedule object @return [OpenStudio::Model::ScheduleRuleset] Modified OpenStudio schedule object

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2403
def model_set_schedule_value(schedule, time_value_hash)
  return nil unless schedule.to_ScheduleRuleset.is_initialized

  new_sch = schedule.clone(schedule.model).to_ScheduleRuleset.get

  # Get day schedule
  day_schedules = []
  default_day_schedule = new_sch.defaultDaySchedule
  day_schedules << default_day_schedule
  new_sch.scheduleRules.each do |rule|
    day_schedules << rule.daySchedule
  end

  # Set schedule values
  day_schedules.each do |day_schedule|
    (0..23).each do |hr|
      next if !time_value_hash.key?(hr.to_s)

      t = OpenStudio::Time.new(0, hr, 0, 0)

      # Get schedule value
      value = day_schedule.getValue(t)

      # Set schedule value
      day_schedule.addValue(t, value)
      t_p_1 = OpenStudio::Time.new(0, hr + 1, 0, 0)
      day_schedule.addValue(t_p_1, time_value_hash[hr.to_s])
    end
  end

  new_sch.setName("#{schedule.name} - adjusted")
  return new_sch
end
model_transfer_air_required?(model) click to toggle source

Is transfer air required?

@param model [OpenStudio::Model::Model] OpenStudio model object @return [Boolean] true if transfer air is required, false otherwise

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2283
def model_transfer_air_required?(model)
  return false
end
model_transfer_air_target_and_source_zones(model) click to toggle source

List transfer air target and source zones, and air flow (cfm)

code_sections [90.1-2019_6.5.7.1], [90.1-2016_6.5.7.1] @return [Hash] target zones (key) and source zones (value) and air flow (value)

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2291
def model_transfer_air_target_and_source_zones(model)
  model_transfer_air_target_and_source_zones_hash = {}

  return model_transfer_air_target_and_source_zones_hash
end
necb_2011_qaqc(qaqc, model) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 777
def necb_2011_qaqc(qaqc, model)
  #Now perform basic QA/QC on items for NECB2011
  qaqc[:information] = []
  qaqc[:warnings] =[]
  qaqc[:errors] = []
  qaqc[:unique_errors]=[]


  #    #Padmassun's Code Start
  csv_file_name ="#{File.dirname(__FILE__)}/necb_2011_spacetype_info.csv"
  puts csv_file_name
  qaqc[:spaces].each do |space|
    building_type =""
    space_type =""
    if space[:space_type_name].include? 'Space Function '
      space_type = (space[:space_type_name].to_s.rpartition('Space Function '))[2].strip
      building_type = 'Space Function'
    elsif space[:space_type_name].include? ' WholeBuilding'
      space_type = (space[:space_type_name].to_s.rpartition(' WholeBuilding'))[0].strip
      building_type = 'WholeBuilding'
    end
    row = look_up_csv_data(csv_file_name,{2 => space_type, 1 => building_type})
    if row.nil?
      #raise ("space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]")
      qaqc[:ruby_warnings] << "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]"
      puts "space type of [#{space_type}] and/or building type of [#{building_type}] was not found in the excel sheet for space: [#{space[:name]}]"
    else
      #correct the data from the csv file to include a multiplier of 0.9 for specific space types.

      reduceLPDSpaces = ["Classroom/lecture/training", "Conf./meet./multi-purpose", "Lounge/recreation",
        "Washroom-sch-A", "Washroom-sch-B", "Washroom-sch-C", "Washroom-sch-D", "Washroom-sch-E",
        "Washroom-sch-F", "Washroom-sch-G", "Washroom-sch-H", "Washroom-sch-I", "Dress./fitt. - performance arts",
        "Locker room", "Retail - dressing/fitting","Locker room-sch-A","Locker room-sch-B","Locker room-sch-C",
        "Locker room-sch-D","Locker room-sch-E","Locker room-sch-F","Locker room-sch-G","Locker room-sch-H",
        "Locker room-sch-I", "Office - open plan - occsens", "Office - enclosed - occsens", "Storage area - occsens",
        "Hospital - medical supply - occsens", "Storage area - refrigerated - occsens"]

      if reduceLPDSpaces.include?(space_type)
        row[3] = row[3]*0.9
        puts "\n============================\nspace_type: #{space_type}\n============================\n"
      end

      # Start of Space Compliance
      necb_section_name = "NECB2011-Section 8.4.3.6"
      data = {}
      data[:lighting_per_area]                  = [ row[3],'==',space[:lighting_w_per_m2] , "Table 4.2.1.6"     ,1 ] unless space[:lighting_w_per_m2].nil?
      data[:occupancy_per_area]           = [ row[4],'==',space[:occ_per_m2]        , "Table A-8.4.3.3.1" ,3 ] unless space[:occ_per_m2].nil?
      data[:occupancy_schedule]           = [ row[5],'==',space[:occupancy_schedule], "Table A-8.4.3.3.1" ,nil ] unless space[:occupancy_schedule].nil?
      data[:electric_equipment_per_area]  = [ row[6],'==',space[:electric_w_per_m2] , "Table A-8.4.3.3.1" ,1 ] unless space[:electric_w_per_m2].nil?
      data.each do |key,value|
        #puts key
        necb_section_test(
          qaqc,
          value[0],
          value[1],
          value[2],
          value[3],
          "[SPACE][#{space[:name]}]-[TYPE:][#{space_type}]#{key}",
          value[4]
        )
      end
    end#space Compliance
  end
  #Padmassun's Code End


  # Envelope
  necb_section_name = "NECB2011-Section 3.2.1.4"
  #store hdd in short form
  hdd = qaqc[:geography][:hdd]
  #calculate fdwr based on hdd.
  fdwr = 0
  if hdd < 4000
    fdwr = 0.40
  elsif hdd >= 4000 and hdd <=7000
    fdwr = (2000-0.2 * hdd)/3000
  elsif hdd >7000
    fdwr = 0.20
  end
  #hardset srr to 0.05
  srr = 0.05
  #create table of expected values and results.
  data = {}
  data[:fenestration_to_door_and_window_percentage]  = [ fdwr * 100,qaqc[:envelope][:fdwr].round(3)]
  data[:skylight_to_roof_percentage]  = [  srr * 100,qaqc[:envelope][:srr].round(3)]
  #perform test. result must be less than or equal to.
  data.each {|key,value| necb_section_test(
      qaqc,
      value[0],
      '>=',
      value[1],
      necb_section_name,
      "[ENVELOPE]#{key}",
      1 #padmassun added tollerance
    )
  }

  #Infiltration
  necb_section_name = "NECB2011-Section 8.4.3.6"
  qaqc[:spaces].each do |spaceinfo|
    data = {}
    data[:infiltration_method]          = [ "Flow/ExteriorArea", spaceinfo[:infiltration_method] , nil ]
    data[:infiltration_flow_per_m2] = [ 0.00025,                         spaceinfo[:infiltration_flow_per_m2], 5 ]
    data.each do |key,value|
      #puts key
      necb_section_test(
        qaqc,
        value[0],
        '==',
        value[1],
        necb_section_name,
        "[SPACE][#{spaceinfo[:name]}]-#{key}",
        value[2]
      )
    end
  end
  #Exterior Opaque
  necb_section_name = "NECB2011-Section 3.2.2.2"
  climate_index = NECB2011.new().get_climate_zone_index(qaqc[:geography][:hdd])
  result_value_index = 6
  round_precision = 3
  data = {}
  data[:ext_wall_conductances]        =  [0.315,0.278,0.247,0.210,0.210,0.183,qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_walls_average_conductance_w_per_m2_k].nil?
  data[:ext_roof_conductances]        =  [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_roofs_average_conductance_w_per_m2_k].nil?
  data[:ext_floor_conductances]       =  [0.227,0.183,0.183,0.162,0.162,0.142,qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:outdoor_floors_average_conductance_w_per_m2_k].nil?

  data.each {|key,value| necb_section_test(
      qaqc,
      value[result_value_index],
      '==',
      value[climate_index],
      necb_section_name,
      "[ENVELOPE]#{key}",
      round_precision
    )
  }
  #Exterior Fenestration
  necb_section_name = "NECB2011-Section 3.2.2.3"
  climate_index = NECB2011.new().get_climate_zone_index(qaqc[:geography][:hdd])
  result_value_index = 6
  round_precision = 3
  data = {}
  data[:ext_window_conductances]      =     [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:windows_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:windows_average_conductance_w_per_m2_k].nil?
  data[:ext_door_conductances]        =     [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:doors_average_conductance_w_per_m2_k]]   unless qaqc[:envelope][:doors_average_conductance_w_per_m2_k].nil?
  data[:ext_overhead_door_conductances] =   [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:overhead_doors_average_conductance_w_per_m2_k].nil?
  data[:ext_skylight_conductances]  =       [2.400,2.200,2.200,2.200,2.200,1.600,qaqc[:envelope][:skylights_average_conductance_w_per_m2_k]] unless qaqc[:envelope][:skylights_average_conductance_w_per_m2_k].nil?
  data.each do |key,value|

    #puts key
    necb_section_test(
      qaqc,
      value[result_value_index].round(round_precision),
      '==',
      value[climate_index].round(round_precision),
      necb_section_name,
      "[ENVELOPE]#{key}",
      round_precision
    )
  end
  #Exterior Ground surfaces
  necb_section_name = "NECB2011-Section 3.2.3.1"
  climate_index = NECB2011.new().get_climate_zone_index(qaqc[:geography][:hdd])
  result_value_index = 6
  round_precision = 3
  data = {}
  data[:ground_wall_conductances]  = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k] ]  unless qaqc[:envelope][:ground_walls_average_conductance_w_per_m2_k].nil?
  data[:ground_roof_conductances]  = [ 0.568,0.379,0.284,0.284,0.284,0.210, qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k] ]  unless qaqc[:envelope][:ground_roofs_average_conductance_w_per_m2_k].nil?
  data[:ground_floor_conductances] = [ 0.757,0.757,0.757,0.757,0.757,0.379, qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k] ] unless qaqc[:envelope][:ground_floors_average_conductance_w_per_m2_k].nil?
  data.each {|key,value| necb_section_test(
      qaqc,
      value[result_value_index],
      '==',
      value[climate_index],
      necb_section_name,
      "[ENVELOPE]#{key}",
      round_precision
    )
  }
  #Zone Sizing and design supply temp tests
  necb_section_name = "NECB2011-?"
  qaqc[:thermal_zones].each do |zoneinfo|
    #    skipping undefined schedules
    if zoneinfo[:name].to_s.include?"- undefined -"
      next
    end
    data = {}
    data[:heating_sizing_factor] = [1.3 , zoneinfo[:heating_sizing_factor]]
    data[:cooling_sizing_factor] = [1.1 ,zoneinfo[:cooling_sizing_factor]]
    data[:heating_design_supply_air_temp] =   [43.0, zoneinfo[:zone_heating_design_supply_air_temperature] ] #unless zoneinfo[:zone_heating_design_supply_air_temperature].nil?
    data[:cooling_design_supply_temp]   =   [13.0, zoneinfo[:zone_cooling_design_supply_air_temperature] ]
    data.each do |key,value|
      #puts key
      necb_section_test(
        qaqc,
        value[0],
        '==',
        value[1],
        necb_section_name,
        "[ZONE][#{zoneinfo[:name]}] #{key}",
        round_precision
      )
    end
  end
  #Air flow sizing check
  #determine correct economizer usage according to section 5.2.2.7 of NECB2011
  necb_section_name = "NECB2011-5.2.2.7"
  qaqc[:air_loops].each do |air_loop_info|
    #    air_loop_info[:name]
    #    air_loop_info[:thermal_zones]
    #    air_loop_info[:total_floor_area_served]
    #    air_loop_info[:cooling_coils][:dx_single_speed]
    #    air_loop_info[:cooling_coils][:dx_two_speed]
    #    air_loop_info[:supply_fan][:max_air_flow_rate]
    #
    #    air_loop_info[:heating_coils][:coil_heating_gas][:nominal_capacity]
    #    air_loop_info[:heating_coils][:coil_heating_electric][:nominal_capacity]
    #    air_loop_info[:heating_coils][:coil_heating_water][:nominal_capacity]
    #
    #    air_loop_info[:economizer][:control_type]

    capacity = -1.0

    if !air_loop_info[:cooling_coils][:dx_single_speed][0].nil?
      puts "air_loop_info[:heating_coils][:coil_heating_gas][0][:nominal_capacity]"
      capacity = air_loop_info[:cooling_coils][:dx_single_speed][0][:nominal_total_capacity_w]
    elsif !air_loop_info[:cooling_coils][:dx_two_speed][0].nil?
      puts "capacity = air_loop_info[:heating_coils][:coil_heating_electric]"
      capacity = air_loop_info[:cooling_coils][:dx_two_speed][0][:cop_high]
    end
    puts capacity
    if capacity == -1.0
      #This should not happen
      puts "air_loop_info[:heating_coils] does not have a capacity or the type is not gas/electric/water for #{air_loop_info[:name]}"
    else
      #check for correct economizer usage
      puts "air_loop_info[:supply_fan][:max_air_flow_rate]: #{air_loop_info[:supply_fan][:max_air_flow_rate]}"
      unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0
        #capacity should be in kW
        if capacity > 20000 or air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]*1000 >1500
          #diff enth
          #puts "diff"
          necb_section_test(
            qaqc,
            "DifferentialEnthalpy",
            '==',
            air_loop_info[:economizer][:control_type],
            necb_section_name,
            "[AIR LOOP][#{air_loop_info[:name]}][:economizer][:control_type]"
          )
        else
          #no economizer
          #puts "no econ"
          necb_section_test(
            qaqc,
            'NoEconomizer',
            '==',
            air_loop_info[:economizer][:control_type],
            necb_section_name,
            "[AIR LOOP][#{air_loop_info[:name]}][:economizer][:control_type]"
          )
        end
      end
    end
  end

  # @todo ?
  necb_section_name = "NECB2011-5.2.10.1"
  qaqc[:air_loops].each do |air_loop_info|
    unless air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] == -1.0
      weather_file_path = model.weatherFile.get.path.get.to_s
      stat_file_path = weather_file_path.gsub('.epw', '.stat')
      stat_file = OpenstudioStandards::Weather::StatFile.new(stat_file_path)
      db990 = stat_file.heating_design_info[2]
      hrv_calc = 0.00123 * air_loop_info[:outdoor_air_L_per_s] * (21 - db990) #=AP46*(21-O$1)
      hrv_reqd = hrv_calc > 150 ? true : false
      #qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}"
      hrv_present = false
      unless air_loop_info[:heat_exchanger].empty?
        hrv_present = true
      end
      necb_section_test(
        qaqc,
        hrv_reqd,
        '==',
        hrv_present,
        necb_section_name,
        "[AIR LOOP][:heat_exchanger] for [#{air_loop_info[:name]}] is present?"
      )

    end
  end

  necb_section_name = "NECB2011-5.2.3.3"
  qaqc[:air_loops].each do |air_loop_info|
    #necb_clg_cop = air_loop_info[:cooling_coils][:dx_single_speed][:cop] #*assuming that the cop is defined correctly*
    if air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s].nil?
      qaqc[:ruby_warnings] << "air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s] is nil"
      next
    end
    necb_supply_fan_w = (air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]*1000*1.6).round(2)

    if air_loop_info[:name].include? "PSZ"
      necb_supply_fan_w = (air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]*1000*1.6).round(2)
    elsif air_loop_info[:name].include? "VAV"
      necb_supply_fan_w = (air_loop_info[:supply_fan][:max_air_flow_rate_m3_per_s]*1000*2.65).round(2)
    end

    if air_loop_info[:supply_fan][:rated_electric_power_w].nil?
      qaqc[:ruby_warnings] << "air_loop_info[:supply_fan][:rated_electric_power_w] is nil"
      next
    end

    supply_fan_w = (air_loop_info[:supply_fan][:rated_electric_power_w]).round(3)
    absolute_diff = (necb_supply_fan_w - supply_fan_w).to_f.abs
    if absolute_diff < 10
      #This case should ALWAYS PASS
      necb_section_test(
        qaqc,
        10,
        '>=',
        absolute_diff,
        necb_section_name,
        "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Absolute Difference from NECB value [#{necb_supply_fan_w}]"
      )
      next
    else
      #The test should pass if and only if the percent difference is less than 10%
      percent_diff = ((necb_supply_fan_w - supply_fan_w).to_f.abs/necb_supply_fan_w * 100).round(3)
      necb_section_test(
        qaqc,
        10,
        '>=',
        percent_diff,
        necb_section_name,
        "[AIR LOOP][#{air_loop_info[:name]}][:supply_fan][:rated_electric_power_w] [#{supply_fan_w}] Percent Diff from NECB value [#{necb_supply_fan_w}]"
      )
    end
  end

  necb_section_name = "SANITY-??"
  qaqc[:plant_loops].each do |plant_loop_info|
    pump_head = plant_loop_info[:pumps][0][:head_pa]
    flow_rate = plant_loop_info[:pumps][0][:water_flow_m3_per_s]*1000
    hp_check = ((flow_rate*60*60)/1000*1000*9.81*pump_head*0.000101997)/3600000
    puts "\npump_head #{pump_head}"
    puts "name: #{qaqc[:building][:name]}"
    puts "name: #{plant_loop_info[:name]}"
    puts "flow_rate #{flow_rate}"
    puts "hp_check #{hp_check}\n"
    pump_power_hp = plant_loop_info[:pumps][0][:electric_power_w]/1000*0.746
    percent_diff = (hp_check - pump_power_hp).to_f.abs/hp_check * 100

    if percent_diff.nan?
      qaqc[:ruby_warnings] << "(hp_check - pump_power_hp).to_f.abs/hp_check * 100 for #{plant_loop_info[:name]} is NaN"
      next
    end

    necb_section_test(
      qaqc,
      20, #diff of 20%
      '>=',
      percent_diff,
      necb_section_name,
      "[PLANT LOOP][#{plant_loop_info[:name]}][:pumps][0][:electric_power_hp] [#{pump_power_hp}] Percent Diff from NECB value [#{hp_check}]"
    )

  end
end
necb_section_test(qaqc,result_value,bool_operator,expected_value,necb_section_name,test_text,tolerance = nil) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 695
def necb_section_test(qaqc,result_value,bool_operator,expected_value,necb_section_name,test_text,tolerance = nil)
  test = "eval_failed"
  command = ''
  if tolerance.is_a?(Integer)
    command = "#{result_value}.round(#{tolerance}) #{bool_operator} #{expected_value}.round(#{tolerance})"
  elsif expected_value.is_a?(String) and result_value.is_a?(String)
    command = "'#{result_value}' #{bool_operator} '#{expected_value}'"
  else
    command = "#{result_value} #{bool_operator} #{expected_value}"
  end
  test = eval(command)
  test == 'true' ? true :false
  raise ("Eval command failed #{test}") if !!test != test
  if test
    qaqc[:information] << "[Info][TEST-PASS][#{necb_section_name}]:#{test_text} result value:#{result_value} #{bool_operator} expected value:#{expected_value}"
  else
    qaqc[:errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}"
    unless (expected_value == -1.0 or expected_value == 'N/A')
      qaqc[:unique_errors] << "[ERROR][TEST-FAIL][#{necb_section_name}]:#{test_text} expected value:#{expected_value} #{bool_operator} result value:#{result_value}"
    end
  end
end
prm_get_component_name(component) click to toggle source

This is a PRM handler to get a name from an OpenStudio object instance If the object instance does not have a name, then it will return the object name.

@param component [OpenStudio::Model:Component] an OpenStudio object @return [String] the name of the instance or object name

# File lib/openstudio-standards/utilities/assertion.rb, line 54
def prm_get_component_name(component)
  return 'Model' if component.is_a?(OpenStudio::Model::Model)

  name = component.iddObjectType.valueName.to_s
  if component.name.is_initialized
    name = component.name.get
  end
  return name
end
prm_get_optional_handler(component, log_dir, data_key, *remaining_keys) click to toggle source

This is a PRM handler function handles the .get from an optional object The handler will try to access the OpenStudio Object data key And do it recursively until all the keys have been checked and final object get or raise exception for non_initialized objects. @param component [OpenStudio::Model::Component] an OpenStudio object @param log_dir [string] directory to save the log @param data_key [string] The data key to retrieve the data from the OpenStudio object @param remaining_keys [str] Any additional keys in the path @return [OpenStudio::Model::Component] the OpenStudio Object or exception raise

# File lib/openstudio-standards/utilities/assertion.rb, line 39
def prm_get_optional_handler(component, log_dir, data_key, *remaining_keys)
  target_data = component.send(data_key)
  prm_raise(target_data.is_initialized,
            log_dir,
            "Failed to retrieve data: #{data_key} from #{prm_get_component_name(component)}"
            )
  target_data_get = target_data.get
  return remaining_keys.length == 0 ? target_data_get : prm_get_optional_handler(target_data_get, log_dir, remaining_keys[0], *remaining_keys[1...])
end
prm_raise(bool, log_dir, err_msg) click to toggle source

PRM assertion method Raise assertion if the test (bool) is failed

Before raise the exception, the method will generate the prm.log for debugging @param bool [Boolean, Object] an object for truthy evaluation @param log_dir [String] log file directory @param err_msg [String] message raise the exception

# File lib/openstudio-standards/utilities/assertion.rb, line 12
def prm_raise(bool, log_dir, err_msg)
  unless bool
    # OpenStudio.logFree(OpenStudio::Debug, 'prm.log', log_msg)
    log_messages_to_file_prm("#{log_dir}/prm.log", true)
    raise PRMError, "#{err_msg} - Check debug log at #{log_dir}/prm.log for more information"
  end
end
prm_read_user_data(user_data, key, default = nil) click to toggle source

PRM reading function reads user data from a hash map. Handles key existence, value is nil and value string is empty @param user_data [Hash] a hash contains a user data @param key [String] key string @param default [Object] values assigned if the data is not available. @return [String] a string

# File lib/openstudio-standards/utilities/assertion.rb, line 26
def prm_read_user_data(user_data, key, default = nil)
  return user_data.key?(key) && !user_data[key].nil? && !user_data[key].to_s.empty? ? user_data[key] : default
end
read_space_types() click to toggle source
# File lib/openstudio-standards/btap/utilities/space_type.rb, line 13
def read_space_types()
  path = "/home/osdev/openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/DND*.osm"
  spacetype_list = []
  Dir.glob(path) do |osm_file|
    model = BTAP::FileIO::load_osm(osm_file)
    standard = NECB2011.new()
    model.getSpaces.each do |space|
      unless space.spaceType.empty?
        name = space.spaceType.get.name.get
        bt = space.spaceType.get.standardsBuildingType.get unless space.spaceType.get.standardsBuildingType.empty?
        st = space.spaceType.get.standardsSpaceType.get unless space.spaceType.get.standardsSpaceType.empty?
        spacetype_list << {'name': name, 'Original-StandardsBuildingType': bt, 'Original-StandardSpaceType': st}

      end
    end

  end
  unique_list = spacetype_list.uniq.sort_by { |st| st[:name] }


  CSV.open("data.csv", "wb") do |csv|
    csv << unique_list.first.keys # adds the attributes name on the first line
    unique_list.each do |hash|
      csv << hash.values
    end
  end

  standard_space_type_list = Standard.build('NECB2011').standards_data['space_types'].map { |space_types| {'building_type': space_types['building_type'],
                                                                                                           'space_type': space_types['space_type'],
                                                                                                           "necb_hvac_system_selection_type": space_types['necb_hvac_system_selection_type'],
                                                                                                           "necb_schedule_type": space_types['necb_schedule_type']


  } }
  standards_space_type_list.s

  CSV.open("spacetypes.csv", "wb") do |csv|
    csv << standard_space_type_list.first.keys # adds the attributes name on the first line
    standard_space_type_list.each do |hash|
      csv << hash.values
    end
  end
end
remove_meta_class_file() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 900
def remove_meta_class_file
  filepath = "#{File.dirname(__FILE__)}/do_not_edit_metaclasses.rb"
  FileUtils.rm(filepath)
end
report_path(test_name) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 23
def report_path(test_name)
  "#{run_dir(test_name)}/report.html"
end
reset_log() click to toggle source

resets the log

# File lib/openstudio-standards/utilities/logging.rb, line 165
def reset_log
  $OPENSTUDIO_LOG.resetStringStream
end
run_dir(test_name) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 6
def run_dir(test_name)
  # always generate test output in specially named 'output' directory so result files are not made part of the measure
  "#{File.dirname(__FILE__)}/output/#{test_name}"
end
sanity_check(qaqc) click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 745
def sanity_check(qaqc)
  qaqc[:sanity_check] = {}
  qaqc[:sanity_check][:fail] = []
  qaqc[:sanity_check][:pass] = []
  #Padmassun's code for isConditioned start
  qaqc[:thermal_zones].each do |zoneinfo|
    zoneinfo[:spaces].each do |space|
      #skip plenums and undefined spaces/zones
      if zoneinfo[:name].to_s.include?"- undefined -"
        next
      end
      if zoneinfo[:space_type_name].to_s.include?"Space Function - undefined -"
        if zoneinfo[:is_conditioned].to_s == "No"
          qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [""No""] and found as #{zoneinfo[:is_conditioned]}"
        else
          qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [""No""] but found as #{zoneinfo[:is_conditioned]}"
        end
      else
        if zoneinfo[:is_conditioned].to_s == "Yes"
          qaqc[:sanity_check][:pass] << "[TEST-PASS][SANITY_CHECK-PASS] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [""Yes""] and found as #{zoneinfo[:is_conditioned]}"
        elsif zoneinfo[:name]
          qaqc[:sanity_check][:fail] << "[ERROR][SANITY_CHECK-FAIL] for [SPACE][#{space[:name]}] and [THERMAL ZONE] [#{zoneinfo[:name]}] where isConditioned is supposed to be [""Yes""] but found as #{zoneinfo[:is_conditioned]}"
        end
      end
    end
  end
  qaqc[:sanity_check][:fail] = qaqc[:sanity_check][:fail].sort
  qaqc[:sanity_check][:pass] = qaqc[:sanity_check][:pass].sort
  #Padmassun's code for isConditioned end
end
save_meta_classes_to_file() click to toggle source
# File lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb, line 895
def save_meta_classes_to_file
  filepath = "#{File.dirname(__FILE__)}/do_not_edit_metaclasses.rb"
  File.open(filepath, 'w') { |f| create_class_array.each { |item| f << item } }
end
schedule_reduce_reset_delay_10min(sch, off_value) click to toggle source

Helper method for model_reduce_setback_sch_delay @author Xuechen (Jerry) Lei, PNNL

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 1181
def schedule_reduce_reset_delay_10min(sch, off_value)
  sch_values = sch.values
  sch_times = sch.times
  ten_mins = OpenStudio::Time.new(0, 0, 10, 0)
  new_times = []
  (0..(sch_values.length - 2)).each do |i|
    current_time = sch_times[i]
    current_value = sch_values[i]
    next_value = sch_values[i + 1]
    if ((current_value - off_value).abs >= 0.01) && ((next_value - off_value).abs < 0.01) # reduce occupied (current) time by 10 min if next value is off_value
      new_times << (current_time - ten_mins)
    else
      new_times << current_time
    end
  end
  new_times << sch_times[-1]

  # remove old values
  sch_times.each do |old_time|
    sch.removeValue(old_time)
  end

  # add new time
  (0..(new_times.length - 1)).each do |i|
    sch.addValue(new_times[i], sch_values[i])
  end
end
space_occupancy_standby_mode(thermostat) click to toggle source

Modify thermostat schedule to account for a thermostat setback/up

@param thermostat [OpenStudio::Model::ThermostatSetpointDualSetpoint] OpenStudio ThermostatSetpointDualSetpoint object @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb, line 2441
def space_occupancy_standby_mode(thermostat)
  return false
end
sql_path(test_name) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 19
def sql_path(test_name)
  "#{run_dir(test_name)}/ModelToIdf/EnergyPlusPreProcess-0/EnergyPlus-0/eplusout.sql"
end
terminate_prm_write_log(msg, log_path, debug = false) click to toggle source

A function that writes error message to log file and stop the execution.

@param msg [String] error message @param log_path [String] the log file directory @param debug [Boolean] debug mode

# File lib/openstudio-standards/utilities/logging.rb, line 44
def terminate_prm_write_log(msg, log_path, debug = false)
  OpenStudio.logFree(OpenStudio::Error, 'prm.log', msg)
  log_messages_to_file_prm("#{log_path}/prm.log", debug)
  raise msg
end
validate_optional(var, model, return_value = "N/A") click to toggle source
# File lib/openstudio-standards/btap/btap_result.rb, line 654
def validate_optional (var, model, return_value = "N/A")
  if var.empty?
    return return_value
  else
    return var.get
  end
end
workspace_path(test_name) click to toggle source
# File lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb, line 15
def workspace_path(test_name)
  "#{run_dir(test_name)}/ModelToIdf/in.idf"
end