lib/openstudio-standards/constructions/information.rb in openstudio-standards-0.5.0 vs lib/openstudio-standards/constructions/information.rb in openstudio-standards-0.6.0.rc1

- old
+ new

@@ -1,10 +1,176 @@ -# Methods to create typical models module OpenstudioStandards + # The Constructions module provides methods create, modify, and get information about model Constructions module Constructions # @!group Information + # Methods to get information about Constructions + # Gives the total R-value of the interior and exterior (if applicable) film coefficients for a particular type of surface. + # @ref [References::ASHRAE9012010] A9.4.1 Air Films + # + # @param intended_surface_type [String] + # Valid choices: 'AtticFloor', 'AtticWall', 'AtticRoof', 'DemisingFloor', 'InteriorFloor', 'InteriorCeiling', + # 'DemisingWall', 'InteriorWall', 'InteriorPartition', 'InteriorWindow', 'InteriorDoor', 'DemisingRoof', + # 'ExteriorRoof', 'Skylight', 'TubularDaylightDome', 'TubularDaylightDiffuser', 'ExteriorFloor', + # 'ExteriorWall', 'ExteriorWindow', 'ExteriorDoor', 'GlassDoor', 'OverheadDoor', 'GroundContactFloor', + # 'GroundContactWall', 'GroundContactRoof' + # @param int_film [Boolean] if true, interior film coefficient will be included in result + # @param ext_film [Boolean] if true, exterior film coefficient will be included in result + # @return [Double] Returns the R-Value of the film coefficients [m^2*K/W] + def self.film_coefficients_r_value(intended_surface_type, int_film, ext_film) + # Return zero if both interior and exterior are false + return 0.0 if !int_film && !ext_film + + # Film values from 90.1-2010 A9.4.1 Air Films + film_ext_surf_r_ip = 0.17 + film_semi_ext_surf_r_ip = 0.46 + film_int_surf_ht_flow_up_r_ip = 0.61 + film_int_surf_ht_flow_dwn_r_ip = 0.92 + fil_int_surf_vertical_r_ip = 0.68 + + film_ext_surf_r_si = OpenStudio.convert(film_ext_surf_r_ip, 'ft^2*hr*R/Btu', 'm^2*K/W').get + film_semi_ext_surf_r_si = OpenStudio.convert(film_semi_ext_surf_r_ip, 'ft^2*hr*R/Btu', 'm^2*K/W').get + film_int_surf_ht_flow_up_r_si = OpenStudio.convert(film_int_surf_ht_flow_up_r_ip, 'ft^2*hr*R/Btu', 'm^2*K/W').get + film_int_surf_ht_flow_dwn_r_si = OpenStudio.convert(film_int_surf_ht_flow_dwn_r_ip, 'ft^2*hr*R/Btu', 'm^2*K/W').get + fil_int_surf_vertical_r_si = OpenStudio.convert(fil_int_surf_vertical_r_ip, 'ft^2*hr*R/Btu', 'm^2*K/W').get + + film_r_si = 0.0 + case intended_surface_type + when 'AtticFloor' + film_r_si += film_int_surf_ht_flow_up_r_si if ext_film # Outside + film_r_si += film_semi_ext_surf_r_si if int_film # Inside @todo: this is only true if the attic is ventilated, interior film should be used otheriwse + when 'AtticWall', 'AtticRoof' + film_r_si += film_ext_surf_r_si if ext_film # Outside + film_r_si += film_semi_ext_surf_r_si if int_film # Inside @todo: this is only true if the attic is ventilated, interior film should be used otherwise + when 'DemisingFloor', 'InteriorFloor' + film_r_si += film_int_surf_ht_flow_up_r_si if ext_film # Outside + film_r_si += film_int_surf_ht_flow_dwn_r_si if int_film # Inside + when 'InteriorCeiling' + film_r_si += film_int_surf_ht_flow_dwn_r_si if ext_film # Outside + film_r_si += film_int_surf_ht_flow_up_r_si if int_film # Inside + when 'DemisingWall', 'InteriorWall', 'InteriorPartition', 'InteriorWindow', 'InteriorDoor' + film_r_si += fil_int_surf_vertical_r_si if ext_film # Outside + film_r_si += fil_int_surf_vertical_r_si if int_film # Inside + when 'DemisingRoof', 'ExteriorRoof', 'Skylight', 'TubularDaylightDome', 'TubularDaylightDiffuser' + film_r_si += film_ext_surf_r_si if ext_film # Outside + film_r_si += film_int_surf_ht_flow_up_r_si if int_film # Inside + when 'ExteriorFloor' + film_r_si += film_ext_surf_r_si if ext_film # Outside + film_r_si += film_int_surf_ht_flow_dwn_r_si if int_film # Inside + when 'ExteriorWall', 'ExteriorWindow', 'ExteriorDoor', 'GlassDoor', 'OverheadDoor' + film_r_si += film_ext_surf_r_si if ext_film # Outside + film_r_si += fil_int_surf_vertical_r_si if int_film # Inside + when 'GroundContactFloor' + film_r_si += film_int_surf_ht_flow_dwn_r_si if int_film # Inside + when 'GroundContactWall' + film_r_si += fil_int_surf_vertical_r_si if int_film # Inside + when 'GroundContactRoof' + film_r_si += film_int_surf_ht_flow_up_r_si if int_film # Inside + end + return film_r_si + end + + # @!endgroup Information + + # @!group Information:Construction + + # Determines if the construction is a simple glazing construction, + # as indicated by having a single layer of type SimpleGlazing. + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @return [Boolean] returns true if it is a simple glazing, false if not + def self.construction_simple_glazing?(construction) + # Not simple if more than 1 layer + if construction.layers.length > 1 + return false + end + + # Not simple unless the layer is a SimpleGlazing material + # if construction.layers.first.to_SimpleGlazing.empty? + if construction.layers.first.to_SimpleGlazing.empty? + return false + end + + # If here, must be simple glazing + return true + end + + # Return the thermal conductance for an OpenStudio Construction object + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @param temperature [Double] Temperature in Celsius, used for gas or gas mixture thermal conductance + # @return [Double] thermal conductance in W/m^2*K + def self.construction_get_conductance(construction, temperature: 0.0) + # check to see if it can be cast as a layered construction, otherwise error + unless construction.to_LayeredConstruction.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'OpenstudioStandards::Constructions', "Unable to determine conductance for construction #{construction.name} because it is not a LayeredConstruction.") + return nil + end + construction = construction.to_LayeredConstruction.get + + total = 0.0 + construction.layers.each do |material| + total += 1.0 / OpenstudioStandards::Constructions::Materials.material_get_conductance(material, temperature: temperature) + end + + return 1.0 / total + end + + # Get the total solar transmittance for a fenestration construction (SHGC) + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @return [Double] total solar transmittance, or 0.0 if not available + def self.construction_get_solar_transmittance(construction) + tsol = nil + if construction.isFenestration + tsol = 1.0 + construction.layers.each do |layer| + # Use shgc for simple glazing + tsol *= layer.to_SimpleGlazing.get.solarHeatGainCoefficient unless layer.to_SimpleGlazing.empty? + # Use solar transmittance for standard glazing + tsol *= layer.to_StandardGlazing.get.solarTransmittance unless layer.to_StandardGlazing.empty? + end + end + + if tsol.nil? + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Constructions', "Unable to determine total solar transmittance for construction #{construction.name} because it is not considered Fenestration in the model. Returning a total solar transmittance of 0.0.") + tsol = 0.0 + end + + return tsol + end + + # Get the total visible transmittance for a fenestration construction + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @return [Double] total visible transmittance, or 0.0 if not available + def self.construction_get_visible_transmittance(construction) + tvis = nil + if construction.isFenestration + tvis = 1.0 + construction.layers.each do |layer| + # Use visible transmittance for simple glazing if specified + unless layer.to_SimpleGlazing.empty? + val = layer.to_SimpleGlazing.get.visibleTransmittance + tvis *= val.get unless val.empty? + end + # Use visible transmittance for standard glazing if specified + unless layer.to_StandardGlazing.empty? + val = layer.to_StandardGlazing.get.visibleTransmittanceatNormalIncidence + tvis *= val.get unless val.empty? + end + end + end + + if tvis.nil? + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Constructions', "Unable to determine total visible transmittance for construction #{construction.name} because it is not considered Fenestration in the model. Returning a total visible transmittance of 0.0.") + tvis = 0.0 + end + + return tvis + end + # Returns the solar reflectance index of an exposed surface. # On a scale of 0 to 100, standard black is 0, and standard white is 100. # The calculation derived from ASTM E1980 assuming medium wind speed. # # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object @@ -17,13 +183,82 @@ sri = 123.97 - 141.35 * x + 9.6555 * x * x return sri end + # @!endgroup Information:Construction + + # @!group Information:Surfaces + + # Determine the weighted average conductance for a set of planar surfaces (surfaces or sub surfaces) + # + # @param surfaces [Array<OpenStudio::Model::PlanarSurface>] Array of OpenStudio PlanarSurface objects + # @return [Double] thermal conductance in W/m^2*K, or nil if not available + def self.surfaces_get_conductance(surfaces) + return nil if surfaces.empty? + + total_area = 0.0 + temp = 0.0 + surfaces.each do |surface| + next unless surface.construction.is_initialized + + surface_construction = surface.model.getConstructionByName(surface.construction.get.name.to_s).get + surface_conductance = OpenstudioStandards::Constructions.construction_get_conductance(surface_construction) + temp += surface.netArea * surface_conductance + total_area += surface.netArea + end + return nil if temp.zero? + + average_conductance = total_area.zero? ? 0.0 : temp / total_area + return average_conductance + end + + # Determine the weighted average solar transmittance for a set of planar surfaces (surfaces or sub surfaces) + # + # @param surfaces [Array<OpenStudio::Model::PlanarSurface>] Array of OpenStudio PlanarSurface objects + # @return [Double] total solar transmittance, or 1.0 if not available + def self.surfaces_get_solar_transmittance(surfaces) + total_area = 0.0 + temp = 0.0 + surfaces.each do |surface| + next unless surface.construction.is_initialized + + surface_construction = surface.model.getConstructionByName(surface.construction.get.name.to_s).get + surface_shgc = OpenstudioStandards::Constructions.construction_get_solar_transmittance(surface_construction) + temp += surface.netArea * surface_shgc + total_area += surface.netArea + end + ave_shgc = total_area.zero? ? 1.0 : temp / total_area + return ave_shgc + end + + # Determine the weighted average visible transmittance for a set of planar surfaces (surfaces or sub surfaces) + # + # @param surfaces [Array<OpenStudio::Model::PlanarSurface>] Array of OpenStudio PlanarSurface objects + # @return [Double] total visible transmittance, or 1.0 if not available + def self.surfaces_get_visible_transmittance(surfaces) + total_area = 0.0 + temp = 0.0 + surfaces.each do |surface| + next unless surface.construction.is_initialized + + surface_construction = surface.model.getConstructionByName(surface.construction.get.name.to_s).get + surface_tvis = OpenstudioStandards::Constructions.construction_get_visible_transmittance(surface_construction) + temp += surface.netArea * surface_tvis + total_area += surface.netArea + end + ave_tvis = total_area.zero? ? 1.0 : temp / total_area + return ave_tvis + end + + # @!endgroup Information:Surfaces + + # @!group Information:DefaultConstructionSet + # report names of constructions in a construction set # - # @param default_construction_set [OpenStudio::Model::Defaultdefault_construction_set] OpenStudio Defaultdefault_construction_set object + # @param default_construction_set [OpenStudio::Model::DefaultConstructionSet] OpenStudio DefaultConstructionSet object # @return [Array<OpenStudio::Model::Construction>] Array of OpenStudio Construction objects def self.construction_set_get_constructions(default_construction_set) construction_array = [] # populate exterior surfaces @@ -77,7 +312,164 @@ construction_array << default_construction_set.buildingShadingConstruction.get if default_construction_set.buildingShadingConstruction.is_initialized construction_array << default_construction_set.siteShadingConstruction.get if default_construction_set.siteShadingConstruction.is_initialized return construction_array end + + # @!endgroup Information:DefaultConstructionSet + + # @!group Information:Model + + # Get a unique list of constructions with a given boundary condition and surface type. + # Pulls from both default construction sets and hard-assigned constructions. + # + # @param model [OpenStudio::Model::Model] OpenStudio model object + # @param boundary_condition [String] Surface boundary condition. Valid options are: + # Adiabatic + # Surface + # Outdoors + # Ground + # @param surface_type [String] Surface type to lookup. Valid options are: + # AtticFloor + # AtticWall + # AtticRoof + # DemisingFloor + # DemisingWall + # DemisingRoof + # ExteriorFloor + # ExteriorWall + # ExteriorRoof + # ExteriorWindow + # ExteriorDoor + # GlassDoor + # GroundContactFloor + # GroundContactWall + # GroundContactRoof + # InteriorFloor + # InteriorWall + # InteriorCeiling + # InteriorPartition + # InteriorWindow + # InteriorDoor + # OverheadDoor + # Skylight + # TubularDaylightDome + # TubularDaylightDiffuser + # return [Array<OpenStudio::Model::ConstructionBase>] An array of all constructions matching the given boundary condition and surface type + def self.model_get_constructions(model, boundary_condition, surface_type) + constructions = [] + + # From default construction sets + model.getDefaultConstructionSets.sort.each do |const_set| + ext_surfs = const_set.defaultExteriorSurfaceConstructions + int_surfs = const_set.defaultInteriorSurfaceConstructions + gnd_surfs = const_set.defaultGroundContactSurfaceConstructions + ext_subsurfs = const_set.defaultExteriorSubSurfaceConstructions + int_subsurfs = const_set.defaultInteriorSubSurfaceConstructions + + # Can't handle incomplete construction sets + if ext_surfs.empty? || + int_surfs.empty? || + gnd_surfs.empty? || + ext_subsurfs.empty? || + int_subsurfs.empty? + + OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Space', "Default construction set #{const_set.name} is incomplete; constructions from this set will not be reported.") + next + end + + ext_surfs = ext_surfs.get + int_surfs = int_surfs.get + gnd_surfs = gnd_surfs.get + ext_subsurfs = ext_subsurfs.get + int_subsurfs = int_subsurfs.get + + case surface_type + # Exterior Surfaces + when 'ExteriorWall', 'AtticWall' + constructions << ext_surfs.wallConstruction + when 'ExteriorFloor' + constructions << ext_surfs.floorConstruction + when 'ExteriorRoof', 'AtticRoof' + constructions << ext_surfs.roofCeilingConstruction + # Interior Surfaces + when 'InteriorWall', 'DemisingWall', 'InteriorPartition' + constructions << int_surfs.wallConstruction + when 'InteriorFloor', 'AtticFloor', 'DemisingFloor' + constructions << int_surfs.floorConstruction + when 'InteriorCeiling', 'DemisingRoof' + constructions << int_surfs.roofCeilingConstruction + # Ground Contact Surfaces + when 'GroundContactWall' + constructions << gnd_surfs.wallConstruction + when 'GroundContactFloor' + constructions << gnd_surfs.floorConstruction + when 'GroundContactRoof' + constructions << gnd_surfs.roofCeilingConstruction + # Exterior SubSurfaces + when 'ExteriorWindow' + constructions << ext_subsurfs.fixedWindowConstruction + constructions << ext_subsurfs.operableWindowConstruction + when 'ExteriorDoor' + constructions << ext_subsurfs.doorConstruction + when 'GlassDoor' + constructions << ext_subsurfs.glassDoorConstruction + when 'OverheadDoor' + constructions << ext_subsurfs.overheadDoorConstruction + when 'Skylight' + constructions << ext_subsurfs.skylightConstruction + when 'TubularDaylightDome' + constructions << ext_subsurfs.tubularDaylightDomeConstruction + when 'TubularDaylightDiffuser' + constructions << ext_subsurfs.tubularDaylightDiffuserConstruction + # Interior SubSurfaces + when 'InteriorWindow' + constructions << int_subsurfs.fixedWindowConstruction + constructions << int_subsurfs.operableWindowConstruction + when 'InteriorDoor' + constructions << int_subsurfs.doorConstruction + end + end + + # Hard-assigned surfaces + model.getSurfaces.sort.each do |surface| + next unless surface.outsideBoundaryCondition == boundary_condition + + if surface.surfaceType == 'Floor' || surface.surfaceType == 'Wall' + next unless surface_type.include?(surface.surfaceType) + elsif surface.surfaceType == 'RoofCeiling' + next unless surface_type.include?('Roof') || surface_type.include?('Ceiling') + end + constructions << surface.construction + end + + # Hard-assigned subsurfaces + model.getSubSurfaces.sort.each do |surface| + next unless surface.outsideBoundaryCondition == boundary_condition + + if surface.subSurfaceType == 'FixedWindow' || surface.subSurfaceType == 'OperableWindow' + next unless surface_type == 'ExteriorWindow' + elsif surface.subSurfaceType == 'Door' + next unless surface_type.include?('Door') + else + next unless surface.subSurfaceType == surface_type + end + constructions << surface.construction + end + + # Throw out the empty constructions + all_constructions = [] + constructions.uniq.each do |construction| + next if construction.empty? + + all_constructions << construction.get + end + + # return unique sorted ConstructionBases + all_constructions = all_constructions.uniq.sort + + return all_constructions + end + + # @!endgroup Information:Model end end