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

- old
+ new

@@ -1,19 +1,20 @@ -# Methods to modify construction properties module OpenstudioStandards + # The Constructions module provides methods create, modify, and get information about model Constructions module Constructions # @!group Modify + # Methods to modify Constructions # add new material layer to a construction # # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object # @param layer_index [Integer] the layer index, default is 0 # @param name [String] name of the new material layer # @param roughness [String] surface roughness of the new material layer. # Options are 'VeryRough', 'Rough', 'MediumRough', 'MediumSmooth', 'Smooth', and 'VerySmooth' # @param thickness [Double] thickness of the new material layer in meters - # @param conductivity [Double] conductivity of new material layer in W/m*K + # @param conductivity [Double] thermal conductivity of new material layer in W/m*K # @param density [Double] density of the new material layer in kg/m^3 # @param specific_heat [Double] specific heat of the new material layer in J/kg*K # @param thermal_absorptance [Double] target thermal absorptance # @param solar_absorptance [Double] target solar absorptance # @param visible_absorptance [Double] target visible absorptance @@ -52,10 +53,221 @@ construction.insertLayer(layer_index, new_material) return new_material end + # Find and set the insulation layer for a layered construction + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @return [OpenStudio::Model::OpaqueMaterial] OpenStudio OpaqueMaterial representing the insulation layer + def self.construction_find_and_set_insulation_layer(construction) + # skip and return the insulation layer if already set + return construction.insulation.get if construction.insulation.is_initialized + + # loop through construction layers to find insulation layer + min_conductance = 100.0 + insulation_material = nil + construction.layers.each do |layer| + # skip layers that aren't an OpaqueMaterial + next unless layer.to_OpaqueMaterial.is_initialized + + material = layer.to_OpaqueMaterial.get + material_conductance = OpenstudioStandards::Constructions::Materials.material_get_conductance(material) + if material_conductance < min_conductance + min_conductance = material_conductance + insulation_material = material + end + end + construction.setInsulation(insulation_material) unless insulation_material.nil? + + if construction.isOpaque && !construction.insulation.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'OpenstudioStandards::Constructions', "Unable to determine the insulation layer for construction #{construction.name.get}.") + return nil + end + + return construction.insulation.get + end + + # Sets the heat transfer coefficient (U-value) of a construction to a specified value by modifying the thickness of the insulation layer. + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @param target_u_value_ip [Double] Target heat transfer coefficient (U-Value) (Btu/ft^2*hr*R) + # @param insulation_layer_name [String] The insulation layer in this construction. If none provided, the method will attempt to determine the insulation layer. + # @param intended_surface_type [String] Intended surface type, used for determining film coefficients. + # 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 target_includes_interior_film_coefficients [Boolean] If true, subtracts off standard interior film coefficients + # from the target heat transfer coefficient before modifying insulation thickness. + # @param target_includes_exterior_film_coefficients [Boolean] If true, subtracts off standard exterior film coefficients + # from the target heat transfer coefficient before modifying insulation thickness. + # @return [Boolean] returns true if successful, false if not + def self.construction_set_u_value(construction, target_u_value_ip, + insulation_layer_name: nil, + intended_surface_type: 'ExteriorWall', + target_includes_interior_film_coefficients: true, + target_includes_exterior_film_coefficients: true) + # Skip layer-by-layer fenestration constructions + if construction.isFenestration + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Constructions', "Can only set the u-value of opaque constructions or simple glazing. #{construction.name} is not opaque or simple glazing.") + return false + end + + # Make sure an insulation layer was specified + if insulation_layer_name.nil? && target_u_value_ip == 0.0 + # Do nothing if the construction already doesn't have an insulation layer + elsif insulation_layer_name.nil? + insulation_layer_name = OpenstudioStandards::Constructions.construction_find_and_set_insulation_layer(construction).name.get + end + + # Remove the insulation layer if the specified U-value is zero. + if target_u_value_ip == 0.0 + layer_index = 0 + construction.layers.each do |layer| + break if layer.name.get == insulation_layer_name + + layer_index += 1 + end + construction.eraseLayer(layer_index) + return true + end + + min_r_value_si = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, target_includes_interior_film_coefficients, target_includes_exterior_film_coefficients) + max_u_value_si = 1.0 / min_r_value_si + max_u_value_ip = OpenStudio.convert(max_u_value_si, 'W/m^2*K', 'Btu/ft^2*hr*R').get + if target_u_value_ip >= max_u_value_ip + target_u_value_ip = 1.0 / OpenStudio.convert(min_r_value_si + 0.001, 'm^2*K/W', 'ft^2*hr*R/Btu').get + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Constructions', "Requested U-value of #{target_u_value_ip} for #{construction.name} is greater than the sum of the inside and outside resistance, and the max U-value (6.636 SI) is used instead.") + end + + # Convert the target U-value to SI + target_r_value_ip = 1.0 / target_u_value_ip.to_f + target_u_value_si = OpenStudio.convert(target_u_value_ip, 'Btu/ft^2*hr*R', 'W/m^2*K').get + target_r_value_si = 1.0 / target_u_value_si + + OpenStudio.logFree(OpenStudio::Debug, 'OpenstudioStandards::Constructions', "Setting U-Value for #{construction.name} to #{target_u_value_si.round(3)} W/m^2*K or #{target_u_value_ip.round(3)} 'Btu/ft^2*hr*R', which is an R-value of #{target_r_value_si.round(3)} m^2*K/W or #{target_r_value_ip.round(3)} 'ft^2*hr*R/Btu'.") + + # Determine the R-value of the non-insulation layers + other_layer_r_value_si = 0.0 + construction.layers.each do |layer| + next if layer.to_OpaqueMaterial.empty? + next if layer.name.get == insulation_layer_name + + other_layer_r_value_si += layer.to_OpaqueMaterial.get.thermalResistance + end + + # Determine the R-value of the air films, if requested + other_layer_r_value_si += OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, target_includes_interior_film_coefficients, target_includes_exterior_film_coefficients) + + # Determine the difference between the desired R-value + # and the R-value of the non-insulation layers and air films. + # This is the desired R-value of the insulation. + ins_r_value_si = target_r_value_si - other_layer_r_value_si + + # Set the R-value of the insulation layer + construction.layers.each_with_index do |layer, l| + next unless layer.name.get == insulation_layer_name + + # Remove insulation layer if requested R-value is lower than sum of non-insulation materials + if ins_r_value_si <= 0.0 + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Construction', "Requested U-value of #{target_u_value_ip} for #{construction.name} is too low given the other materials in the construction; insulation layer will be removed.") + construction.eraseLayer(l) + # Set the target R-value to the sum of other layers to make name match properties + target_r_value_ip = OpenStudio.convert(other_layer_r_value_si, 'm^2*K/W', 'ft^2*hr*R/Btu').get + break # Don't modify the insulation layer since it has been removed + end + + # Modify the insulation layer + ins_r_value_ip = OpenStudio.convert(ins_r_value_si, 'm^2*K/W', 'ft^2*h*R/Btu').get + if layer.to_StandardOpaqueMaterial.is_initialized + layer = layer.to_StandardOpaqueMaterial.get + layer.setThickness(ins_r_value_si * layer.conductivity) + layer.setName("#{layer.name} R-#{ins_r_value_ip.round(2)}") + break # Stop looking for the insulation layer once found + elsif layer.to_MasslessOpaqueMaterial.is_initialized + layer = layer.to_MasslessOpaqueMaterial.get + layer.setThermalResistance(ins_r_value_si) + layer.setName("#{layer.name} R-#{ins_r_value_ip.round(2)}") + break # Stop looking for the insulation layer once found + elsif layer.to_AirGap.is_initialized + layer = layer.to_AirGap.get + target_thickness = ins_r_value_si * layer.thermalConductivity + layer.setThickness(target_thickness) + layer.setName("#{layer.name} R-#{ins_r_value_ip.round(2)}") + break # Stop looking for the insulation layer once found + end + end + + # Modify the construction name + construction.setName("#{construction.name} R-#{target_r_value_ip.round(2)}") + + return true + end + + # Sets the U-value of a simple glazing construction to a specified value by modifying the SimpleGlazing material. + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object that contains SimpleGlazing + # @param target_u_value_ip [Double] Target heat transfer coefficient (U-Value) (Btu/ft^2*hr*R) + # @param target_includes_interior_film_coefficients [Boolean] If true, subtracts off standard interior film coefficients + # from the target heat transfer coefficient before modifying insulation thickness. + # @param target_includes_exterior_film_coefficients [Boolean] If true, subtracts off standard exterior film coefficients + # from the target heat transfer coefficient before modifying insulation thickness. + # @return [Boolean] returns true if successful, false if not + def self.construction_set_glazing_u_value(construction, target_u_value_ip, + target_includes_interior_film_coefficients: true, + target_includes_exterior_film_coefficients: true) + # Skip layer-by-layer fenestration constructions + unless OpenstudioStandards::Constructions.construction_simple_glazing?(construction) + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Construction', "The construction_set_glazing_u_value method can only set the u-value of simple glazing. #{construction.name} does not containg simple glazing.") + return false + end + + # Convert the target U-value to SI + target_r_value_ip = 1.0 / target_u_value_ip.to_f + target_u_value_si = OpenStudio.convert(target_u_value_ip, 'Btu/ft^2*hr*R', 'W/m^2*K').get + target_r_value_si = 1.0 / target_u_value_si + + # Determine the R-value of the air films, if requested + # In EnergyPlus, the U-factor input of the WindowMaterial:SimpleGlazingSystem + # object includes the film coefficients (see IDD description, and I/O reference + # guide) so the target_includes_interior_film_coefficients and target_includes_exterior_film_coefficients + # variable values are changed to their opposite so if the target value includes a film + # the target value is unchanged + film_coeff_r_value_si = 0.0 + film_coeff_r_value_si += OpenstudioStandards::Constructions.film_coefficients_r_value('ExteriorWindow', !target_includes_interior_film_coefficients, !target_includes_exterior_film_coefficients) + film_coeff_u_value_si = 1.0 / film_coeff_r_value_si + film_coeff_u_value_ip = OpenStudio.convert(film_coeff_u_value_si, 'W/m^2*K', 'Btu/ft^2*hr*R').get + film_coeff_r_value_ip = 1.0 / film_coeff_u_value_ip + + # Determine the difference between the desired R-value + # and the R-value of the and air films. + # This is the desired R-value of the insulation. + ins_r_value_si = target_r_value_si - film_coeff_r_value_si + if ins_r_value_si <= 0.0 + ins_r_value_si = 0.001 + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Construction', "Requested U-value of #{target_u_value_ip} Btu/ft^2*hr*R for #{construction.name} is too high given the film coefficients of U-#{film_coeff_u_value_ip.round(2)} Btu/ft^2*hr*R.") + end + ins_u_value_si = 1.0 / ins_r_value_si + + if ins_u_value_si > 7.0 + OpenStudio.logFree(OpenStudio::Warn, 'OpenstudioStandards::Construction', "Requested U-value of #{target_u_value_ip} for #{construction.name} is too high given the film coefficients of U-#{film_coeff_u_value_ip.round(2)}; setting U-value to EnergyPlus limit of 7.0 W/m^2*K (1.23 Btu/ft^2*hr*R).") + ins_u_value_si = 7.0 + end + ins_u_value_ip = OpenStudio.convert(ins_u_value_si, 'W/m^2*K', 'Btu/ft^2*hr*R').get + + # Set the U-value of the insulation layer + glazing = construction.layers.first.to_SimpleGlazing.get + starting_u_value_si = glazing.uFactor.round(2) + staring_u_value_ip = OpenStudio.convert(starting_u_value_si, 'W/m^2*K', 'Btu/ft^2*hr*R').get + OpenStudio.logFree(OpenStudio::Debug, 'OpenstudioStandards::Construction', "Construction #{construction.name} contains SimpleGlazing '#{glazing.name}' with starting u_factor #{starting_u_value_si.round(2)} 'W/m^2*K' = #{staring_u_value_ip.round(2)} 'Btu/ft^2*hr*R'. Changing to u_factor #{ins_u_value_si.round(2)} 'W/m^2*K' = #{ins_u_value_ip.round(2)} 'Btu/ft^2*hr*R'.") + glazing.setUFactor(ins_u_value_si) + + return true + end + # set construction surface properties # # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object # @param roughness [String] surface roughness # @param thermal_absorptance [Double] target thermal absorptance @@ -73,8 +285,127 @@ roughness: roughness, thermal_absorptance: thermal_absorptance, solar_absorptance: solar_absorptance, visible_absorptance: visible_absorptance) return new_material + end + + # Set the F-Factor of a slab to a specified value. + # Assumes an unheated, fully insulated slab, and modifies + # the insulation layer according to the values from 90.1-2004 + # Table A6.3 Assembly F-Factors for Slab-on-Grade Floors. + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @param target_f_factor_ip [Double] Target F-Factor (Btu/ft*h*R) + # @param insulation_layer_name [String] The name of the insulation layer in this construction + # @return [Boolean] returns true if successful, false if not + def self.construction_set_slab_f_factor(construction, target_f_factor_ip, insulation_layer_name: nil) + # Regression from table A6.3 unheated, fully insulated slab + r_value_ip = 1.0248 * target_f_factor_ip**-2.186 + u_value_ip = 1.0 / r_value_ip + + # Set the insulation U-value + OpenstudioStandards::Constructions.construction_set_u_value(construction, u_value_ip, + insulation_layer_name: insulation_layer_name, + intended_surface_type: 'GroundContactFloor', + target_includes_interior_film_coefficients: true, + target_includes_exterior_film_coefficients: true) + + # Modify the construction name + construction.setName("#{construction.name} F-#{target_f_factor_ip.round(3)}") + + return true + end + + # Set the surface specific F-factor parameters of a construction. + # This method only assumes one floor per space when calculating perimeter and area + # + # @param construction [OpenStudio::Model::FFactorGroundFloorConstruction] OpenStudio F-factor Construction object + # @param target_f_factor_ip [Float] Target F-Factor (Btu/ft*h*R) + # @param surface [OpenStudio::Model::Surface] OpenStudio Surface object + # @return [Boolean] returns true if successful, false if not + def self.construction_set_surface_slab_f_factor(construction, target_f_factor_ip, surface) + # Get space associated with surface + space = surface.space.get + + # Find this space's exposed floor area and perimeter. + perimeter = OpenstudioStandards::Geometry.space_get_f_floor_perimeter(space) + area = OpenstudioStandards::Geometry.space_get_f_floor_area(space) + + if area.zero? + OpenStudio.logFree(OpenStudio::Error, 'OpenstudioStandards::Construction', "Area for #{surface.name} was calculated to be 0 m2, slab f-factor cannot be set.") + return false + end + + # Change construction name + construction.setName("#{construction.name}_#{surface.name}_#{target_f_factor_ip}") + + # Set properties + f_factor_si = target_f_factor_ip * OpenStudio.convert(1.0, 'Btu/ft*h*R', 'W/m*K').get + construction.setFFactor(f_factor_si) + construction.setArea(area) + construction.setPerimeterExposed(perimeter) + + # Set surface outside boundary condition + surface.setOutsideBoundaryCondition('GroundFCfactorMethod') + + return true + end + + # Set the C-Factor of an underground wall to a specified value. + # Assumes continuous exterior insulation and modifies + # the insulation layer according to the values from 90.1-2004 + # Table A4.2 Assembly C-Factors for Below-Grade walls. + # + # @param construction [OpenStudio::Model::Construction] OpenStudio Construction object + # @param target_c_factor_ip [Double] Target C-Factor (Btu/ft^2*h*R) + # @param insulation_layer_name [String] The name of the insulation layer in this construction + # @return [Boolean] returns true if successful, false if not + def self.construction_set_underground_wall_c_factor(construction, target_c_factor_ip, insulation_layer_name: nil) + # Regression from table A4.2 continuous exterior insulation + r_value_ip = 0.775 * target_c_factor_ip**-1.067 + u_value_ip = 1.0 / r_value_ip + + # Set the insulation U-value + OpenstudioStandards::Constructions.construction_set_u_value(construction, u_value_ip, + insulation_layer_name: insulation_layer_name, + intended_surface_type: 'GroundContactWall', + target_includes_interior_film_coefficients: true, + target_includes_exterior_film_coefficients: true) + + # Modify the construction name + construction.setName("#{construction.name} C-#{target_c_factor_ip.round(3)}") + + return true + end + + # Set the surface specific C-factor parameters of a construction + # + # @param construction [OpenStudio::Model::CFactorUndergroundWallConstruction] OpenStudio C-factor Construction object + # @param target_c_factor_ip [Float] Target C-Factor (Btu/ft^2*h*R) + # @param surface [OpenStudio::Model::Surface] OpenStudio surface object + # @return [Boolean] returns true if successful, false if not + def self.construction_set_surface_underground_wall_c_factor(construction, target_c_factor_ip, surface) + # Get space associated with surface + space = surface.space.get + + # Get height of the first below grade wall in this space. + below_grade_wall_height = OpenstudioStandards::Geometry.space_get_below_grade_wall_height(space) + + if below_grade_wall_height.zero? + OpenStudio.logFree(OpenStudio::Error, 'OpenstudioStandards::Construction', "Below grade wall height for #{surface.name} was calculated to be 0 m2, below grade wall c-factor cannot be set.") + return false + end + + # Change construction name + construction.setName("#{construction.name}_#{surface.name}_#{target_c_factor_ip}") + + # Set properties + c_factor_si = target_c_factor_ip * OpenStudio.convert(1.0, 'Btu/ft^2*h*R', 'W/m^2*K').get + construction.setCFactor(c_factor_si) + construction.setHeight(below_grade_wall_height) + + # Set surface outside boundary condition + surface.setOutsideBoundaryCondition('GroundFCfactorMethod') end end end