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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
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
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
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
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 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 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
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
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
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
# 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 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
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 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
# 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
# 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
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
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
# 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
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
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
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
# 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
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
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
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
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
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
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
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
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
# 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
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
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
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
# 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
# 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
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
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
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
# 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
# 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
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
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
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
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
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
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
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
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
# 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
# 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
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
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 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 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
# 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
# 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
# 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
resets the log
# File lib/openstudio-standards/utilities/logging.rb, line 165 def reset_log $OPENSTUDIO_LOG.resetStringStream end
# 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
# 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
# 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
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
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
# 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
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
# 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
# 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