diff --git a/BuildResidentialHPXML/measure.rb b/BuildResidentialHPXML/measure.rb index b45579f0c6..2e83b99efc 100644 --- a/BuildResidentialHPXML/measure.rb +++ b/BuildResidentialHPXML/measure.rb @@ -3547,7 +3547,7 @@ def run(model, runner, user_arguments) return false end - Model.reset(model, runner) + Model.reset(runner, model) Version.check_openstudio_version() @@ -4084,20 +4084,20 @@ def self.create_geometry_envelope(runner, model, args) case args[:geometry_unit_type] when HPXML::ResidentialTypeSFD - success = Geometry.create_single_family_detached(runner: runner, model: model, **args) + success = Geometry.create_single_family_detached(runner, model, **args) when HPXML::ResidentialTypeSFA - success = Geometry.create_single_family_attached(model: model, **args) + success = Geometry.create_single_family_attached(model, **args) when HPXML::ResidentialTypeApartment - success = Geometry.create_apartment(model: model, **args) + success = Geometry.create_apartment(model, **args) when HPXML::ResidentialTypeManufactured - success = Geometry.create_single_family_detached(runner: runner, model: model, **args) + success = Geometry.create_single_family_detached(runner, model, **args) end return false if not success - success = Geometry.create_doors(runner: runner, model: model, **args) + success = Geometry.create_doors(runner, model, **args) return false if not success - success = Geometry.create_windows_and_skylights(runner: runner, model: model, **args) + success = Geometry.create_windows_and_skylights(runner, model, **args) return false if not success return true @@ -4664,7 +4664,7 @@ def self.set_neighbor_buildings(hpxml_bldg, args) distance, neighbor_height = data next if distance == 0 - azimuth = Geometry.get_azimuth_from_facade(facade: facade, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_azimuth_from_facade(facade, args[:geometry_unit_orientation]) if (distance > 0) && (not neighbor_height.nil?) height = neighbor_height @@ -4846,17 +4846,17 @@ def self.set_roofs(hpxml_bldg, args, sorted_surfaces) next if surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors next if surface.surfaceType != EPlus::SurfaceTypeRoofCeiling - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next if [HPXML::LocationOtherHousingUnit].include? interior_adjacent_to if args[:geometry_attic_type] == HPXML::AtticTypeFlatRoof azimuth = nil else - azimuth = Geometry.get_surface_azimuth(surface: surface, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_surface_azimuth(surface, args[:geometry_unit_orientation]) end hpxml_bldg.roofs.add(id: "Roof#{hpxml_bldg.roofs.size + 1}", - interior_adjacent_to: Geometry.get_adjacent_to(surface: surface), + interior_adjacent_to: Geometry.get_surface_adjacent_to(surface), azimuth: azimuth, area: UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2'), roof_type: args[:roof_material_type], @@ -4889,9 +4889,9 @@ def self.set_rim_joists(hpxml_bldg, model, args, sorted_surfaces) sorted_surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeWall next unless [EPlus::BoundaryConditionOutdoors, EPlus::BoundaryConditionAdiabatic].include? surface.outsideBoundaryCondition - next unless Geometry.surface_is_rim_joist(surface: surface, height: args[:geometry_rim_joist_height]) + next unless Geometry.surface_is_rim_joist(surface, args[:geometry_rim_joist_height]) - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next unless [HPXML::LocationBasementConditioned, HPXML::LocationBasementUnconditioned, HPXML::LocationCrawlspaceUnvented, @@ -4900,7 +4900,7 @@ def self.set_rim_joists(hpxml_bldg, model, args, sorted_surfaces) exterior_adjacent_to = HPXML::LocationOutside if surface.outsideBoundaryCondition == EPlus::BoundaryConditionAdiabatic # can be adjacent to foundation space - adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model: model, surface: surface) + adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model, surface) if adjacent_surface.nil? # adjacent to a space that is not explicitly in the model unless [HPXML::ResidentialTypeSFD].include?(args[:geometry_unit_type]) exterior_adjacent_to = interior_adjacent_to @@ -4909,7 +4909,7 @@ def self.set_rim_joists(hpxml_bldg, model, args, sorted_surfaces) end end else # adjacent to a space that is explicitly in the model - exterior_adjacent_to = Geometry.get_adjacent_to(surface: adjacent_surface) + exterior_adjacent_to = Geometry.get_surface_adjacent_to(adjacent_surface) end end @@ -4923,7 +4923,7 @@ def self.set_rim_joists(hpxml_bldg, model, args, sorted_surfaces) insulation_assembly_r_value = args[:rim_joist_assembly_r] end - azimuth = Geometry.get_surface_azimuth(surface: surface, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_surface_azimuth(surface, args[:geometry_unit_orientation]) hpxml_bldg.rim_joists.add(id: "RimJoist#{hpxml_bldg.rim_joists.size + 1}", exterior_adjacent_to: exterior_adjacent_to, @@ -4951,23 +4951,23 @@ def self.set_rim_joists(hpxml_bldg, model, args, sorted_surfaces) def self.set_walls(hpxml_bldg, model, args, sorted_surfaces) sorted_surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeWall - next if Geometry.surface_is_rim_joist(surface: surface, height: args[:geometry_rim_joist_height]) + next if Geometry.surface_is_rim_joist(surface, args[:geometry_rim_joist_height]) - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next unless [HPXML::LocationConditionedSpace, HPXML::LocationAtticUnvented, HPXML::LocationAtticVented, HPXML::LocationGarage].include? interior_adjacent_to exterior_adjacent_to = HPXML::LocationOutside if surface.adjacentSurface.is_initialized - exterior_adjacent_to = Geometry.get_adjacent_to(surface: surface.adjacentSurface.get) + exterior_adjacent_to = Geometry.get_surface_adjacent_to(surface.adjacentSurface.get) elsif surface.outsideBoundaryCondition == EPlus::BoundaryConditionAdiabatic # can be adjacent to conditioned space, attic - adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model: model, surface: surface) + adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model, surface) if adjacent_surface.nil? # adjacent to a space that is not explicitly in the model exterior_adjacent_to = interior_adjacent_to if exterior_adjacent_to == HPXML::LocationConditionedSpace # conditioned space adjacent to conditioned space exterior_adjacent_to = HPXML::LocationOtherHousingUnit end else # adjacent to a space that is explicitly in the model - exterior_adjacent_to = Geometry.get_adjacent_to(surface: adjacent_surface) + exterior_adjacent_to = Geometry.get_surface_adjacent_to(adjacent_surface) end end @@ -4992,7 +4992,7 @@ def self.set_walls(hpxml_bldg, model, args, sorted_surfaces) end end - azimuth = Geometry.get_surface_azimuth(surface: surface, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_surface_azimuth(surface, args[:geometry_unit_orientation]) hpxml_bldg.walls.add(id: "Wall#{hpxml_bldg.walls.size + 1}", exterior_adjacent_to: exterior_adjacent_to, @@ -5046,9 +5046,9 @@ def self.set_foundation_walls(hpxml_bldg, model, args, sorted_surfaces) sorted_surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeWall next unless [EPlus::BoundaryConditionFoundation, EPlus::BoundaryConditionAdiabatic].include? surface.outsideBoundaryCondition - next if Geometry.surface_is_rim_joist(surface: surface, height: args[:geometry_rim_joist_height]) + next if Geometry.surface_is_rim_joist(surface, args[:geometry_rim_joist_height]) - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next unless [HPXML::LocationBasementConditioned, HPXML::LocationBasementUnconditioned, HPXML::LocationCrawlspaceUnvented, @@ -5057,7 +5057,7 @@ def self.set_foundation_walls(hpxml_bldg, model, args, sorted_surfaces) exterior_adjacent_to = HPXML::LocationGround if surface.outsideBoundaryCondition == EPlus::BoundaryConditionAdiabatic # can be adjacent to foundation space - adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model: model, surface: surface) + adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model, surface) if adjacent_surface.nil? # adjacent to a space that is not explicitly in the model unless [HPXML::ResidentialTypeSFD].include?(args[:geometry_unit_type]) exterior_adjacent_to = interior_adjacent_to @@ -5066,7 +5066,7 @@ def self.set_foundation_walls(hpxml_bldg, model, args, sorted_surfaces) end end else # adjacent to a space that is explicitly in the model - exterior_adjacent_to = Geometry.get_adjacent_to(surface: adjacent_surface) + exterior_adjacent_to = Geometry.get_surface_adjacent_to(adjacent_surface) end end @@ -5097,7 +5097,7 @@ def self.set_foundation_walls(hpxml_bldg, model, args, sorted_surfaces) end end - azimuth = Geometry.get_surface_azimuth(surface: surface, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_surface_azimuth(surface, args[:geometry_unit_orientation]) hpxml_bldg.foundation_walls.add(id: "FoundationWall#{hpxml_bldg.foundation_walls.size + 1}", exterior_adjacent_to: exterior_adjacent_to, @@ -5143,12 +5143,12 @@ def self.set_floors(hpxml_bldg, args, sorted_surfaces) next if surface.outsideBoundaryCondition == EPlus::BoundaryConditionFoundation next unless [EPlus::SurfaceTypeFloor, EPlus::SurfaceTypeRoofCeiling].include? surface.surfaceType - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next unless [HPXML::LocationConditionedSpace, HPXML::LocationGarage].include? interior_adjacent_to exterior_adjacent_to = HPXML::LocationOutside if surface.adjacentSurface.is_initialized - exterior_adjacent_to = Geometry.get_adjacent_to(surface: surface.adjacentSurface.get) + exterior_adjacent_to = Geometry.get_surface_adjacent_to(surface.adjacentSurface.get) elsif surface.outsideBoundaryCondition == EPlus::BoundaryConditionAdiabatic exterior_adjacent_to = HPXML::LocationOtherHousingUnit if surface.surfaceType == EPlus::SurfaceTypeFloor @@ -5218,7 +5218,7 @@ def self.set_slabs(hpxml_bldg, model, args, sorted_surfaces) next unless [EPlus::BoundaryConditionFoundation].include? surface.outsideBoundaryCondition next if surface.surfaceType != EPlus::SurfaceTypeFloor - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) next if [HPXML::LocationOutside, HPXML::LocationOtherHousingUnit].include? interior_adjacent_to has_foundation_walls = false @@ -5229,7 +5229,7 @@ def self.set_slabs(hpxml_bldg, model, args, sorted_surfaces) HPXML::LocationBasementConditioned].include? interior_adjacent_to has_foundation_walls = true end - exposed_perimeter = Geometry.calculate_exposed_perimeter(model: model, ground_floor_surfaces: [surface], has_foundation_walls: has_foundation_walls).round(1) + exposed_perimeter = Geometry.calculate_exposed_perimeter(model, ground_floor_surfaces: [surface], has_foundation_walls: has_foundation_walls).round(1) next if exposed_perimeter == 0 if has_foundation_walls @@ -5289,8 +5289,8 @@ def self.set_windows(hpxml_bldg, model, args, sorted_subsurfaces) surface = sub_surface.surface.get - sub_surface_height = Geometry.get_surface_height(surface: sub_surface) - sub_surface_facade = Geometry.get_facade_for_surface(surface: sub_surface) + sub_surface_height = Geometry.get_surface_height(sub_surface) + sub_surface_facade = Geometry.get_surface_facade(sub_surface) if (sub_surface_facade == Constants::FacadeFront) && ((args[:overhangs_front_depth] > 0) || args[:overhangs_front_distance_to_top_of_window] > 0) overhangs_depth = args[:overhangs_front_depth] @@ -5312,7 +5312,7 @@ def self.set_windows(hpxml_bldg, model, args, sorted_subsurfaces) # Get max z coordinate of eaves eaves_z = args[:geometry_average_ceiling_height] * args[:geometry_unit_num_floors_above_grade] + args[:geometry_rim_joist_height] if args[:geometry_attic_type] == HPXML::AtticTypeConditioned - eaves_z += Geometry.get_conditioned_attic_height(spaces: model.getSpaces) + eaves_z += Geometry.get_conditioned_attic_height(model.getSpaces) end if args[:geometry_foundation_type] == HPXML::FoundationTypeAmbient eaves_z += args[:geometry_foundation_height] @@ -5326,7 +5326,7 @@ def self.set_windows(hpxml_bldg, model, args, sorted_subsurfaces) overhangs_distance_to_bottom_of_window = (overhangs_distance_to_top_of_window + sub_surface_height).round(1) end - azimuth = Geometry.get_azimuth_from_facade(facade: sub_surface_facade, orientation: args[:geometry_unit_orientation]) + azimuth = Geometry.get_azimuth_from_facade(sub_surface_facade, args[:geometry_unit_orientation]) wall_idref = @surface_ids[surface.name.to_s] next if wall_idref.nil? @@ -5375,8 +5375,8 @@ def self.set_skylights(hpxml_bldg, args, sorted_subsurfaces) surface = sub_surface.surface.get - sub_surface_facade = Geometry.get_facade_for_surface(surface: sub_surface) - azimuth = Geometry.get_azimuth_from_facade(facade: sub_surface_facade, orientation: args[:geometry_unit_orientation]) + sub_surface_facade = Geometry.get_surface_facade(sub_surface) + azimuth = Geometry.get_azimuth_from_facade(sub_surface_facade, args[:geometry_unit_orientation]) roof_idref = @surface_ids[surface.name.to_s] next if roof_idref.nil? @@ -5416,10 +5416,10 @@ def self.set_doors(hpxml_bldg, model, args, sorted_subsurfaces) surface = sub_surface.surface.get - interior_adjacent_to = Geometry.get_adjacent_to(surface: surface) + interior_adjacent_to = Geometry.get_surface_adjacent_to(surface) if [HPXML::LocationOtherHousingUnit].include?(interior_adjacent_to) - adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model: model, surface: surface) + adjacent_surface = Geometry.get_adiabatic_adjacent_surface(model, surface) next if adjacent_surface.nil? end diff --git a/BuildResidentialHPXML/measure.xml b/BuildResidentialHPXML/measure.xml index 825d48dc41..0f33428204 100644 --- a/BuildResidentialHPXML/measure.xml +++ b/BuildResidentialHPXML/measure.xml @@ -3,8 +3,8 @@ 3.1 build_residential_hpxml a13a8983-2b01-4930-8af2-42030b6e4233 - df4fe828-a827-4cc2-8a4d-9c8daf649202 - 2024-12-20T20:07:27Z + 9020722b-d574-4332-96a0-10d947d437fc + 2025-01-02T23:48:53Z 2C38F48B BuildResidentialHPXML HPXML Builder @@ -7544,7 +7544,7 @@ measure.rb rb script - 612549F4 + DF604C07 constants.rb @@ -7556,7 +7556,7 @@ geometry.rb rb resource - 425682E4 + 170EAEAB version.txt diff --git a/BuildResidentialHPXML/resources/geometry.rb b/BuildResidentialHPXML/resources/geometry.rb index 6bf1d4ef74..3530800384 100644 --- a/BuildResidentialHPXML/resources/geometry.rb +++ b/BuildResidentialHPXML/resources/geometry.rb @@ -21,8 +21,7 @@ module Geometry # @param geometry_roof_type [String] roof type of the building # @param geometry_roof_pitch [Double] ratio of vertical rise to horizontal run (frac) # @return [Boolean] true if model is successfully updated with a single-family detached unit - def self.create_single_family_detached(runner:, - model:, + def self.create_single_family_detached(runner, model, geometry_unit_cfa:, geometry_average_ceiling_height:, geometry_unit_num_floors_above_grade:, @@ -137,7 +136,7 @@ def self.create_single_family_detached(runner:, # make space garage_space = OpenStudio::Model::Space::fromFloorPrint(garage_polygon, average_ceiling_height, model) garage_space = garage_space.get - assign_indexes(model: model, footprint_polygon: garage_polygon, space: garage_space) + assign_indexes(model, footprint_polygon: garage_polygon, space: garage_space) garage_space.setName(garage_space_name) garage_space_type = OpenStudio::Model::SpaceType.new(model) garage_space_type.setStandardsSpaceType(garage_space_name) @@ -146,7 +145,7 @@ def self.create_single_family_detached(runner:, # set this to the garage zone garage_space.setThermalZone(garage_zone) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = z garage_space.changeTransformation(OpenStudio::Transformation.new(m)) @@ -227,7 +226,7 @@ def self.create_single_family_detached(runner:, # make space conditioned_space = OpenStudio::Model::Space::fromFloorPrint(conditioned_polygon, average_ceiling_height, model) conditioned_space = conditioned_space.get - assign_indexes(model: model, footprint_polygon: conditioned_polygon, space: conditioned_space) + assign_indexes(model, footprint_polygon: conditioned_polygon, space: conditioned_space) if floor > 0 conditioned_space_name = "#{HPXML::LocationConditionedSpace} story #{floor + 1}" @@ -242,7 +241,7 @@ def self.create_single_family_detached(runner:, # set these to the conditioned zone conditioned_space.setThermalZone(conditioned_zone) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = z conditioned_space.changeTransformation(OpenStudio::Transformation.new(m)) end @@ -306,24 +305,24 @@ def self.create_single_family_detached(runner:, end # make surfaces - surface_floor = create_surface(polygon: polygon_floor, model: model) + surface_floor = create_surface(model, polygon_floor) surface_floor.setSurfaceType(EPlus::SurfaceTypeFloor) surface_floor.setOutsideBoundaryCondition(EPlus::BoundaryConditionSurface) - surface_n_roof = create_surface(polygon: polygon_n_roof, model: model) + surface_n_roof = create_surface(model, polygon_n_roof) surface_n_roof.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) surface_n_roof.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_e_wall = create_surface(polygon: polygon_e_wall, model: model) + surface_e_wall = create_surface(model, polygon_e_wall) surface_e_wall.setSurfaceType(side_type) surface_e_wall.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_s_roof = create_surface(polygon: polygon_s_roof, model: model) + surface_s_roof = create_surface(model, polygon_s_roof) surface_s_roof.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) surface_s_roof.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_w_wall = create_surface(polygon: polygon_w_wall, model: model) + surface_w_wall = create_surface(model, polygon_w_wall) surface_w_wall.setSurfaceType(side_type) surface_w_wall.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) # assign surfaces to the space - attic_space = create_space(model: model) + attic_space = create_space(model) surface_floor.setSpace(attic_space) surface_s_roof.setSpace(attic_space) surface_n_roof.setSpace(attic_space) @@ -351,7 +350,7 @@ def self.create_single_family_detached(runner:, attic_space_type.setStandardsSpaceType(attic_space_name) attic_space.setSpaceType(attic_space_type) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = z attic_space.changeTransformation(OpenStudio::Transformation.new(m)) @@ -380,7 +379,7 @@ def self.create_single_family_detached(runner:, # make space foundation_space = OpenStudio::Model::Space::fromFloorPrint(foundation_polygon, foundation_height, model) foundation_space = foundation_space.get - assign_indexes(model: model, footprint_polygon: foundation_polygon, space: foundation_space) + assign_indexes(model, footprint_polygon: foundation_polygon, space: foundation_space) case foundation_type when HPXML::FoundationTypeCrawlspaceVented foundation_space_name = HPXML::LocationCrawlspaceVented @@ -411,7 +410,7 @@ def self.create_single_family_detached(runner:, # set foundation walls outside boundary condition spaces = model.getSpaces spaces.each do |space| - next unless get_space_floor_z(space: space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 + next unless get_space_floor_z(space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 surfaces = space.surfaces surfaces.each do |surface| @@ -421,12 +420,12 @@ def self.create_single_family_detached(runner:, end end - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = z foundation_space.changeTransformation(OpenStudio::Transformation.new(m)) # Rim Joist - add_rim_joist(model: model, polygon: foundation_polygon_with_wrong_zs, space: foundation_space, rim_joist_height: rim_joist_height, z: foundation_height) + add_rim_joist(model, polygon: foundation_polygon_with_wrong_zs, space: foundation_space, rim_joist_height: rim_joist_height, z: foundation_height) end # put all of the spaces in the model into a vector @@ -510,19 +509,19 @@ def self.create_single_family_detached(runner:, polygon_n_wall = make_polygon(nw_point, roof_n_point, ne_point) polygon_s_wall = make_polygon(sw_point, se_point, roof_s_point) - wall_n = create_surface(polygon: polygon_n_wall, model: model) + wall_n = create_surface(model, polygon_n_wall) wall_n.setSurfaceType(EPlus::SurfaceTypeWall) - deck_e = create_surface(polygon: polygon_e_roof, model: model) + deck_e = create_surface(model, polygon_e_roof) deck_e.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) deck_e.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - wall_s = create_surface(polygon: polygon_s_wall, model: model) + wall_s = create_surface(model, polygon_s_wall) wall_s.setSurfaceType(EPlus::SurfaceTypeWall) wall_s.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - deck_w = create_surface(polygon: polygon_w_roof, model: model) + deck_w = create_surface(model, polygon_w_roof) deck_w.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) deck_w.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - garage_attic_space = create_space(model: model) + garage_attic_space = create_space(model) deck_w.setSpace(garage_attic_space) deck_e.setSpace(garage_attic_space) wall_n.setSpace(garage_attic_space) @@ -542,7 +541,7 @@ def self.create_single_family_detached(runner:, end surface.createAdjacentSurface(garage_attic_space) # garage attic floor - surface.adjacentSurface.get.additionalProperties.setFeature('Index', indexer(model: model)) + surface.adjacentSurface.get.additionalProperties.setFeature('Index', indexer(model)) garage_attic_space.setName(garage_attic_space_name) garage_attic_space_type = OpenStudio::Model::SpaceType.new(model) garage_attic_space_type.setStandardsSpaceType(garage_attic_space_name) @@ -572,7 +571,7 @@ def self.create_single_family_detached(runner:, end garage_attic_space.surfaces.each do |surface| - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = -attic_space.zOrigin transformation = OpenStudio::Transformation.new(m) new_vertices = transformation * surface.vertices @@ -591,7 +590,7 @@ def self.create_single_family_detached(runner:, next if surface2.surfaceType != EPlus::SurfaceTypeRoofCeiling next if surface1 == surface2 - if has_same_vertices(surface1: surface1, surface2: surface2) + if has_same_vertices(surface1, surface2) surface1.remove surface2.remove end @@ -602,14 +601,14 @@ def self.create_single_family_detached(runner:, end end - garage_spaces = get_garage_spaces(spaces: model.getSpaces) + garage_spaces = get_garage_spaces(model.getSpaces) # set foundation outside boundary condition to Kiva "foundation" model.getSurfaces.each do |surface| if surface.outsideBoundaryCondition == EPlus::BoundaryConditionGround surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionFoundation) if foundation_type != HPXML::FoundationTypeAmbient surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) if foundation_type == HPXML::FoundationTypeAmbient - elsif (UnitConversions.convert(rim_joist_height, 'm', 'ft') - get_surface_height(surface: surface)).abs < 0.001 + elsif (UnitConversions.convert(rim_joist_height, 'm', 'ft') - get_surface_height(surface)).abs < 0.001 next if surface.surfaceType != EPlus::SurfaceTypeWall garage_spaces.each do |garage_space| @@ -645,9 +644,9 @@ def self.create_single_family_detached(runner:, end end - assign_remaining_surface_indexes(model: model) + assign_remaining_surface_indexes(model) - apply_ambient_foundation_shift(model: model, foundation_type: foundation_type, foundation_height: foundation_height) + apply_ambient_foundation_shift(model, foundation_type: foundation_type, foundation_height: foundation_height) return true end @@ -670,7 +669,7 @@ def self.create_single_family_detached(runner:, # @param geometry_unit_front_wall_is_adiabatic [Boolean] presence of an adiabatic front wall # @param geometry_unit_back_wall_is_adiabatic [Boolean] presence of an adiabatic back wall # @return [Boolean] true if model is successfully updated with a single-family attached unit - def self.create_single_family_attached(model:, + def self.create_single_family_attached(model, geometry_unit_cfa:, geometry_average_ceiling_height:, geometry_unit_num_floors_above_grade:, @@ -746,7 +745,7 @@ def self.create_single_family_attached(model:, # first floor conditioned_space = OpenStudio::Model::Space::fromFloorPrint(conditioned_polygon, average_ceiling_height, model) conditioned_space = conditioned_space.get - assign_indexes(model: model, footprint_polygon: conditioned_polygon, space: conditioned_space) + assign_indexes(model, footprint_polygon: conditioned_polygon, space: conditioned_space) conditioned_space.setName(HPXML::LocationConditionedSpace) conditioned_space_type = OpenStudio::Model::SpaceType.new(model) conditioned_space_type.setStandardsSpaceType(HPXML::LocationConditionedSpace) @@ -760,7 +759,7 @@ def self.create_single_family_attached(model:, # Make surfaces adiabatic model.getSpaces.each do |space| space.surfaces.each do |surface| - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) next unless surface.surfaceType == EPlus::SurfaceTypeWall next unless adb_facades.include? os_facade @@ -776,11 +775,11 @@ def self.create_single_family_attached(model:, # additional floors for story in 2..num_floors new_conditioned_space = conditioned_space.clone.to_Space.get - assign_indexes(model: model, footprint_polygon: conditioned_polygon, space: new_conditioned_space) + assign_indexes(model, footprint_polygon: conditioned_polygon, space: new_conditioned_space) new_conditioned_space.setName("conditioned space|story #{story}") new_conditioned_space.setSpaceType(conditioned_space_type) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = average_ceiling_height * (story - 1) new_conditioned_space.setTransformation(OpenStudio::Transformation.new(m)) new_conditioned_space.setThermalZone(conditioned_zone) @@ -789,7 +788,7 @@ def self.create_single_family_attached(model:, # attic attic_spaces = [] if attic_type != HPXML::AtticTypeFlatRoof - attic_space = get_attic_space(model: model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) + attic_space = get_attic_space(model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) if attic_type == HPXML::AtticTypeConditioned attic_space_name = HPXML::LocationConditionedSpace attic_space.setName(attic_space_name) @@ -808,8 +807,8 @@ def self.create_single_family_attached(model:, # foundation front foundation_space = OpenStudio::Model::Space::fromFloorPrint(foundation_polygon, foundation_height, model) foundation_space = foundation_space.get - assign_indexes(model: model, footprint_polygon: foundation_polygon, space: foundation_space) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + assign_indexes(model, footprint_polygon: foundation_polygon, space: foundation_space) + m = initialize_transformation_matrix() m[2, 3] = foundation_height foundation_space.changeTransformation(OpenStudio::Transformation.new(m)) foundation_space.setXOrigin(0) @@ -843,7 +842,7 @@ def self.create_single_family_attached(model:, foundation_space.setThermalZone(foundation_zone) # Rim Joist - add_rim_joist(model: model, polygon: foundation_polygon, space: foundation_space, rim_joist_height: rim_joist_height, z: 0) + add_rim_joist(model, polygon: foundation_polygon, space: foundation_space, rim_joist_height: rim_joist_height, z: 0) # put all of the spaces in the model into a vector spaces = OpenStudio::Model::SpaceVector.new @@ -858,13 +857,13 @@ def self.create_single_family_attached(model:, # Foundation space boundary conditions spaces = model.getSpaces spaces.each do |space| - next unless get_space_floor_z(space: space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 + next unless get_space_floor_z(space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 surfaces = space.surfaces surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeWall - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) if adb_facades.include? os_facade surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionAdiabatic) elsif get_surface_z_values(surfaceArray: [surface]).min < 0 @@ -892,7 +891,7 @@ def self.create_single_family_attached(model:, attic_spaces.each do |attic_space| attic_space.remove end - attic_space = get_attic_space(model: model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) + attic_space = get_attic_space(model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) # set these to the attic zone case attic_type @@ -916,7 +915,7 @@ def self.create_single_family_attached(model:, # Adiabatic gable walls if [HPXML::AtticTypeVented, HPXML::AtticTypeUnvented, HPXML::AtticTypeConditioned].include? attic_type attic_space.surfaces.each do |surface| - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) next unless surface.surfaceType == EPlus::SurfaceTypeWall next unless adb_facades.include? os_facade @@ -947,9 +946,9 @@ def self.create_single_family_attached(model:, surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) if foundation_type == HPXML::FoundationTypeAmbient end - assign_remaining_surface_indexes(model: model) + assign_remaining_surface_indexes(model) - apply_ambient_foundation_shift(model: model, foundation_type: foundation_type, foundation_height: foundation_height) + apply_ambient_foundation_shift(model, foundation_type: foundation_type, foundation_height: foundation_height) return true end @@ -972,7 +971,7 @@ def self.create_single_family_attached(model:, # @param geometry_unit_front_wall_is_adiabatic [Boolean] presence of an adiabatic front wall # @param geometry_unit_back_wall_is_adiabatic [Boolean] presence of an adiabatic back wall # @return [Boolean] true if model is successfully updated with an apartment unit - def self.create_apartment(model:, + def self.create_apartment(model, geometry_unit_cfa:, geometry_average_ceiling_height:, geometry_unit_num_floors_above_grade:, @@ -1048,7 +1047,7 @@ def self.create_apartment(model:, # first floor conditioned_space = OpenStudio::Model::Space::fromFloorPrint(conditioned_polygon, average_ceiling_height, model) conditioned_space = conditioned_space.get - assign_indexes(model: model, footprint_polygon: conditioned_polygon, space: conditioned_space) + assign_indexes(model, footprint_polygon: conditioned_polygon, space: conditioned_space) conditioned_space.setName(HPXML::LocationConditionedSpace) conditioned_space_type = OpenStudio::Model::SpaceType.new(model) conditioned_space_type.setStandardsSpaceType(HPXML::LocationConditionedSpace) @@ -1071,7 +1070,7 @@ def self.create_apartment(model:, # Make conditioned space surfaces adiabatic model.getSpaces.each do |space| space.surfaces.each do |surface| - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) if surface.surfaceType == EPlus::SurfaceTypeWall if adb_facades.include? os_facade x_ft = UnitConversions.convert(x, 'm', 'ft') @@ -1092,7 +1091,7 @@ def self.create_apartment(model:, # attic attic_spaces = [] if [HPXML::AtticTypeVented, HPXML::AtticTypeUnvented].include? attic_type - attic_space = get_attic_space(model: model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) + attic_space = get_attic_space(model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) attic_spaces << attic_space end @@ -1102,8 +1101,8 @@ def self.create_apartment(model:, # foundation front foundation_space = OpenStudio::Model::Space::fromFloorPrint(foundation_polygon, foundation_height, model) foundation_space = foundation_space.get - assign_indexes(model: model, footprint_polygon: foundation_polygon, space: foundation_space) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + assign_indexes(model, footprint_polygon: foundation_polygon, space: foundation_space) + m = initialize_transformation_matrix() m[2, 3] = foundation_height + rim_joist_height foundation_space.changeTransformation(OpenStudio::Transformation.new(m)) foundation_space.setXOrigin(0) @@ -1137,7 +1136,7 @@ def self.create_apartment(model:, foundation_space.setThermalZone(foundation_zone) # Rim Joist - add_rim_joist(model: model, polygon: foundation_polygon, space: foundation_space, rim_joist_height: rim_joist_height, z: 0) + add_rim_joist(model, polygon: foundation_polygon, space: foundation_space, rim_joist_height: rim_joist_height, z: 0) # put all of the spaces in the model into a vector spaces = OpenStudio::Model::SpaceVector.new @@ -1151,13 +1150,13 @@ def self.create_apartment(model:, # Foundation space boundary conditions model.getSpaces.each do |space| - next unless get_space_floor_z(space: space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 # Foundation + next unless get_space_floor_z(space) + UnitConversions.convert(space.zOrigin, 'm', 'ft') < 0 # Foundation surfaces = space.surfaces surfaces.each do |surface| next unless surface.surfaceType == EPlus::SurfaceTypeWall - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) if adb_facades.include?(os_facade) && (os_facade != EPlus::SurfaceTypeRoofCeiling) && (os_facade != EPlus::SurfaceTypeFloor) surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionAdiabatic) elsif get_surface_z_values(surfaceArray: [surface]).min < 0 @@ -1184,7 +1183,7 @@ def self.create_apartment(model:, attic_spaces.each do |attic_space| attic_space.remove end - attic_space = get_attic_space(model: model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) + attic_space = get_attic_space(model, x: x, y: y, average_ceiling_height: average_ceiling_height, num_floors: num_floors, roof_pitch: roof_pitch, roof_type: roof_type, rim_joist_height: rim_joist_height) # set these to the attic zone case attic_type @@ -1206,7 +1205,7 @@ def self.create_apartment(model:, # Adiabatic surfaces for attic walls attic_space.surfaces.each do |surface| - os_facade = get_facade_for_surface(surface: surface) + os_facade = get_surface_facade(surface) next unless surface.surfaceType == EPlus::SurfaceTypeWall next unless adb_facades.include? os_facade @@ -1237,9 +1236,9 @@ def self.create_apartment(model:, surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) if foundation_type == HPXML::FoundationTypeAmbient end - assign_remaining_surface_indexes(model: model) + assign_remaining_surface_indexes(model) - apply_ambient_foundation_shift(model: model, foundation_type: foundation_type, foundation_height: foundation_height) + apply_ambient_foundation_shift(model, foundation_type: foundation_type, foundation_height: foundation_height) return true end @@ -1250,10 +1249,7 @@ def self.create_apartment(model:, # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param door_area [Double] the area of the opaque door(s) (ft2) # @return [Boolean] true if successful - def self.create_doors(runner:, - model:, - door_area:, - **) + def self.create_doors(runner, model, door_area:, **) # error checking if door_area == 0 runner.registerFinalCondition('No doors added because door area was set to 0.') @@ -1269,12 +1265,12 @@ def self.create_doors(runner:, avail_walls = [] facades.each do |_facade| sorted_spaces = model.getSpaces.sort_by { |s| s.additionalProperties.getFeatureAsInteger('Index').get } - get_conditioned_spaces(spaces: sorted_spaces).each do |space| - next if space_is_below_grade(space: space) + get_conditioned_spaces(sorted_spaces).each do |space| + next if space_is_below_grade(space) sorted_surfaces = space.surfaces.sort_by { |s| s.additionalProperties.getFeatureAsInteger('Index').get } sorted_surfaces.each do |surface| - next unless get_facade_for_surface(surface: surface) == Constants::FacadeFront + next unless get_surface_facade(surface) == Constants::FacadeFront next unless (surface.outsideBoundaryCondition == EPlus::BoundaryConditionOutdoors) || (surface.outsideBoundaryCondition == EPlus::BoundaryConditionAdiabatic) next if (90 - surface.tilt * 180 / Math::PI).abs > 0.01 # Not a vertical wall @@ -1312,7 +1308,7 @@ def self.create_doors(runner:, # Try to place door on any surface with enough area next if door_area >= wall_gross_area - facade = get_facade_for_surface(surface: min_story_avail_wall) + facade = get_surface_facade(min_story_avail_wall) if (door_offset + door_width) * door_height > wall_gross_area # Reduce door offset to fit door on surface @@ -1366,7 +1362,7 @@ def self.create_doors(runner:, door_polygon << door_vertex end - door_sub_surface = create_sub_surface(polygon: door_polygon, model: model) + door_sub_surface = create_sub_surface(model, door_polygon) door_sub_surface.setName("#{min_story_avail_wall.name} - Door") door_sub_surface.setSurface(min_story_avail_wall) door_sub_surface.setSubSurfaceType(EPlus::SubSurfaceTypeDoor) @@ -1399,21 +1395,10 @@ def self.create_doors(runner:, # @param skylight_area_left [Double] amount of skylight area on the unit's left conditioned roof facade (ft2) # @param skylight_area_right [Double] amount of skylight area on the unit's right conditioned roof facade (ft2) # @return [Boolean] true if successful - def self.create_windows_and_skylights(runner:, - model:, - window_front_wwr:, - window_back_wwr:, - window_left_wwr:, - window_right_wwr:, - window_area_front:, - window_area_back:, - window_area_left:, - window_area_right:, - window_aspect_ratio:, - skylight_area_front:, - skylight_area_back:, - skylight_area_left:, - skylight_area_right:, + def self.create_windows_and_skylights(runner, model, + window_front_wwr:, window_back_wwr:, window_left_wwr:, window_right_wwr:, + window_area_front:, window_area_back:, window_area_left:, window_area_right:, window_aspect_ratio:, + skylight_area_front:, skylight_area_back:, skylight_area_left:, skylight_area_right:, **) facades = [Constants::FacadeBack, Constants::FacadeRight, Constants::FacadeFront, Constants::FacadeLeft] @@ -1443,13 +1428,13 @@ def self.create_windows_and_skylights(runner:, Constants::FacadeNone => [] } sorted_spaces = model.getSpaces.sort_by { |s| s.additionalProperties.getFeatureAsInteger('Index').get } - get_conditioned_spaces(spaces: sorted_spaces).each do |space| + get_conditioned_spaces(sorted_spaces).each do |space| sorted_surfaces = space.surfaces.sort_by { |s| s.additionalProperties.getFeatureAsInteger('Index').get } sorted_surfaces.each do |surface| next unless (surface.surfaceType == EPlus::SurfaceTypeWall) && (surface.outsideBoundaryCondition == EPlus::BoundaryConditionOutdoors) next if (90 - surface.tilt * 180 / Math::PI).abs > 0.01 # Not a vertical wall - facade = get_facade_for_surface(surface: surface) + facade = get_surface_facade(surface) next if facade.nil? wall_surfaces[facade] << surface @@ -1460,7 +1445,7 @@ def self.create_windows_and_skylights(runner:, sorted_surfaces.each do |surface| next unless (surface.surfaceType == EPlus::SurfaceTypeRoofCeiling) && (surface.outsideBoundaryCondition == EPlus::BoundaryConditionOutdoors) - facade = get_facade_for_surface(surface: surface) + facade = get_surface_facade(surface) if facade.nil? if surface.tilt == 0 # flat roof roof_surfaces[Constants::FacadeNone] << surface @@ -1563,7 +1548,7 @@ def self.create_windows_and_skylights(runner:, facade_win_area = 0 wall_surfaces[facade].each do |surface| next if surface_window_area[surface] == 0 - if not add_windows_to_wall(surface: surface, window_area: surface_window_area[surface], window_gap_y: window_gap_y, window_gap_x: window_gap_x, window_aspect_ratio: window_aspect_ratio, max_single_window_area: max_single_window_area, facade: facade, model: model, runner: runner) + if not add_windows_to_wall(runner, model, surface: surface, window_area: surface_window_area[surface], window_gap_y: window_gap_y, window_gap_x: window_gap_x, window_aspect_ratio: window_aspect_ratio, max_single_window_area: max_single_window_area, facade: facade) return false end @@ -1599,10 +1584,10 @@ def self.create_windows_and_skylights(runner:, end surfaces.each do |surface| - if (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface: surface)) > get_surface_length(surface: surface) - skylight_aspect_ratio = get_surface_length(surface: surface) / (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface: surface)) # aspect ratio of the roof surface + if (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface)) > get_surface_length(surface) + skylight_aspect_ratio = get_surface_length(surface) / (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface)) # aspect ratio of the roof surface else - skylight_aspect_ratio = (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface: surface)) / get_surface_length(surface: surface) # aspect ratio of the roof surface + skylight_aspect_ratio = (UnitConversions.convert(surface.grossArea, 'm^2', 'ft^2') / get_surface_length(surface)) / get_surface_length(surface) # aspect ratio of the roof surface end skylight_width = Math.sqrt(UnitConversions.convert(skylight_area, 'ft^2', 'm^2') / skylight_aspect_ratio) @@ -1636,7 +1621,7 @@ def self.create_windows_and_skylights(runner:, skylight_polygon << skylight_vertex end - sub_surface = create_sub_surface(polygon: skylight_polygon, model: model) + sub_surface = create_sub_surface(model, skylight_polygon) sub_surface.setName("#{surface.name} - Skylight") sub_surface.setSurface(surface) @@ -1655,7 +1640,7 @@ def self.create_windows_and_skylights(runner:, # # @param surface [OpenStudio::Model::Surface] the surface of interest # @return [String] the HPXML location assigned to the OpenStudio Surface object - def self.get_adjacent_to(surface:) + def self.get_surface_adjacent_to(surface) space = surface.space.get st = space.spaceType.get space_type = st.standardsSpaceType.get @@ -1668,10 +1653,9 @@ def self.get_adjacent_to(surface:) # @param surface [OpenStudio::Model::Surface] the surface of interest # @param orientation [Double] the orientation of the building measured clockwise from north (degrees) # @return [Double] the absolute azimuth based on surface facade and building orientation - def self.get_surface_azimuth(surface:, - orientation:) - facade = get_facade_for_surface(surface: surface) - return get_azimuth_from_facade(facade: facade, orientation: orientation) + def self.get_surface_azimuth(surface, orientation) + facade = get_surface_facade(surface) + return get_azimuth_from_facade(facade, orientation) end # Identify whether an OpenStudio Surface object is a rim joist. @@ -1679,9 +1663,8 @@ def self.get_surface_azimuth(surface:, # @param surface [OpenStudio::Model::Surface] the surface of interest # @param height [Double] height of the rim joist (ft) # @return [Boolean] true if successful - def self.surface_is_rim_joist(surface:, - height:) - return false unless (height - get_surface_height(surface: surface)).abs < 0.00001 + def self.surface_is_rim_joist(surface, height) + return false unless (height - get_surface_height(surface)).abs < 0.00001 return false unless get_surface_z_values(surfaceArray: [surface]).max > 0 return true @@ -1695,7 +1678,7 @@ def self.surface_is_rim_joist(surface:, # @param ground_floor_surfaces [Array] the array of OpenStudio Surface objects for which to calculate exposed perimeter # @param has_foundation_walls [Boolean] whether the ground floor surfaces have foundation walls # @return [Double] the exposed perimeter (ft) - def self.calculate_exposed_perimeter(model:, + def self.calculate_exposed_perimeter(model, ground_floor_surfaces:, has_foundation_walls: false) perimeter = 0 @@ -1703,7 +1686,7 @@ def self.calculate_exposed_perimeter(model:, # Get ground edges if not has_foundation_walls # Use edges from floor surface - ground_edges = get_edges_for_surfaces(surfaces: ground_floor_surfaces, use_top_edge: false) + ground_edges = get_edges_for_surfaces(ground_floor_surfaces, use_top_edge: false) else # Use top edges from foundation walls instead surfaces = [] @@ -1724,7 +1707,7 @@ def self.calculate_exposed_perimeter(model:, surfaces << surface end end - ground_edges = get_edges_for_surfaces(surfaces: surfaces, use_top_edge: true) + ground_edges = get_edges_for_surfaces(surfaces, use_top_edge: true) end # Get bottom edges of exterior walls (building footprint) surfaces = [] @@ -1734,7 +1717,7 @@ def self.calculate_exposed_perimeter(model:, surfaces << surface end - model_edges = get_edges_for_surfaces(surfaces: surfaces, use_top_edge: false) + model_edges = get_edges_for_surfaces(surfaces, use_top_edge: false) # compare edges for overlap ground_edges.each do |e1| @@ -1778,7 +1761,7 @@ def self.get_unexposed_garage_perimeter(geometry_garage_protrusion:, # # @param surface [OpenStudio::Model::Surface] the surface of interest # @return [String] front, back, left, or right based on the OpenStudio Surface outward normal - def self.get_facade_for_surface(surface:) + def self.get_surface_facade(surface) tol = 0.001 n = surface.outwardNormal facade = nil @@ -1811,8 +1794,7 @@ def self.get_facade_for_surface(surface:) # @param facade [String] front, back, left, or right # @param orientation [Double] the orientation of the building measured clockwise from north (degrees) # @return [Double] the absolute azimuth based on relative azimuth of the facade and building orientation - def self.get_azimuth_from_facade(facade:, - orientation:) + def self.get_azimuth_from_facade(facade, orientation) case facade when Constants::FacadeFront return get_abs_azimuth(relative_azimuth: 0, building_orientation: orientation) @@ -1832,8 +1814,7 @@ def self.get_azimuth_from_facade(facade:, # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param surface [OpenStudio::Model::Surface] the surface of interest # @return [OpenStudio::Model::Surface] the adiabatic adjacent OpenStudio Surface - def self.get_adiabatic_adjacent_surface(model:, - surface:) + def self.get_adiabatic_adjacent_surface(model, surface) return if surface.outsideBoundaryCondition != EPlus::BoundaryConditionAdiabatic adjacentSurfaceType = EPlus::SurfaceTypeWall @@ -1847,7 +1828,7 @@ def self.get_adiabatic_adjacent_surface(model:, next if surface == adjacent_surface next if adjacent_surface.surfaceType != adjacentSurfaceType next if adjacent_surface.outsideBoundaryCondition != EPlus::BoundaryConditionAdiabatic - next unless has_same_vertices(surface1: surface, surface2: adjacent_surface) + next unless has_same_vertices(surface, adjacent_surface) return adjacent_surface end @@ -1860,9 +1841,7 @@ def self.get_adiabatic_adjacent_surface(model:, # @param roof_pitch [Double] roof pitch in vertical rise inches for every 12 inches of horizontal run # @param latitude [Double] latitude (degrees) # @return [Double] absolute tilt - def self.get_absolute_tilt(tilt_str:, - roof_pitch:, - latitude:) + def self.get_absolute_tilt(tilt_str:, roof_pitch:, latitude:) tilt_str = tilt_str.downcase if tilt_str.start_with? 'roofpitch' roof_angle = Math.atan(roof_pitch / 12.0) * 180.0 / Math::PI @@ -1878,25 +1857,25 @@ def self.get_absolute_tilt(tilt_str:, # # @param spaces [Array] array of OpenStudio::Model::Space objects # @return [Double] the height of the conditioned attic (ft) - def self.get_conditioned_attic_height(spaces:) + def self.get_conditioned_attic_height(spaces) # gable roof type - get_conditioned_spaces(spaces: spaces).each do |space| + get_conditioned_spaces(spaces).each do |space| space.surfaces.each do |surface| next if surface.vertices.size != 3 next if surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors next if surface.surfaceType != EPlus::SurfaceTypeWall - return get_height_of_spaces(spaces: [space]) + return get_space_height(space) end end # hip roof type - get_conditioned_spaces(spaces: spaces).each do |space| + get_conditioned_spaces(spaces).each do |space| space.surfaces.each do |surface| next if surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors next if surface.surfaceType != EPlus::SurfaceTypeRoofCeiling - return get_height_of_spaces(spaces: [space]) + return get_space_height(space) end end @@ -1908,8 +1887,7 @@ def self.get_conditioned_attic_height(spaces:) # @param relative_azimuth [Double] relative azimuth (degrees) # @param building_orientation [Double] dwelling unit orientation (degrees) # @return [Double] absolute azimuth - def self.get_abs_azimuth(relative_azimuth:, - building_orientation:) + def self.get_abs_azimuth(relative_azimuth:, building_orientation:) azimuth = relative_azimuth + building_orientation # Ensure azimuth is >=0 and <360 @@ -1932,11 +1910,7 @@ def self.get_abs_azimuth(relative_azimuth:, # @param rim_joist_height [Double] height of the rim joists (ft) # @param z [Double] z coordinate of the bottom of the rim joists # @return [nil] - def self.add_rim_joist(model:, - polygon:, - space:, - rim_joist_height:, - z:) + def self.add_rim_joist(model, polygon:, space:, rim_joist_height:, z:) if rim_joist_height > 0 # make polygons p = OpenStudio::Point3dVector.new @@ -1948,7 +1922,7 @@ def self.add_rim_joist(model:, # make space rim_joist_space = OpenStudio::Model::Space::fromFloorPrint(rim_joist_polygon, rim_joist_height, model) rim_joist_space = rim_joist_space.get - assign_indexes(model: model, footprint_polygon: rim_joist_polygon, space: rim_joist_space) + assign_indexes(model, footprint_polygon: rim_joist_polygon, space: rim_joist_space) space.surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeRoofCeiling @@ -1976,15 +1950,13 @@ def self.add_rim_joist(model:, # @param footprint_polygon [OpenStudio::Point3dVector] an OpenStudio::Point3dVector object # @param space [OpenStudio::Model::Space] an OpenStudio::Model::Space object # @return [nil] - def self.assign_indexes(model:, - footprint_polygon:, - space:) - space.additionalProperties.setFeature('Index', indexer(model: model)) + def self.assign_indexes(model, footprint_polygon:, space:) + space.additionalProperties.setFeature('Index', indexer(model)) space.surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeFloor - surface.additionalProperties.setFeature('Index', indexer(model: model)) + surface.additionalProperties.setFeature('Index', indexer(model)) end num_points = footprint_polygon.size @@ -2007,14 +1979,14 @@ def self.assign_indexes(model:, end next if num_points_matched < 2 # match at least 2 points of the footprint_polygon and you've found the correct wall surface - surface.additionalProperties.setFeature('Index', indexer(model: model)) + surface.additionalProperties.setFeature('Index', indexer(model)) end end space.surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeRoofCeiling - surface.additionalProperties.setFeature('Index', indexer(model: model)) + surface.additionalProperties.setFeature('Index', indexer(model)) end end @@ -2023,11 +1995,11 @@ def self.assign_indexes(model:, # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @return [nil] - def self.assign_remaining_surface_indexes(model:) + def self.assign_remaining_surface_indexes(model) model.getSurfaces.each do |surface| next if surface.additionalProperties.getFeatureAsInteger('Index').is_initialized - surface.additionalProperties.setFeature('Index', indexer(model: model)) + surface.additionalProperties.setFeature('Index', indexer(model)) end end @@ -2035,33 +2007,31 @@ def self.assign_remaining_surface_indexes(model:) # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @return [OpenStudio::Model::Space] the newly created space - def self.create_space(model:) + def self.create_space(model) space = OpenStudio::Model::Space.new(model) - space.additionalProperties.setFeature('Index', indexer(model: model)) + space.additionalProperties.setFeature('Index', indexer(model)) return space end # Create a new OpenStudio surface and assign an Index to it. # - # @param polygon [OpenStudio::Point3dVector] the vertices for the surface # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param polygon [OpenStudio::Point3dVector] the vertices for the surface # @return [OpenStudio::Model::Surface] the newly created surface - def self.create_surface(polygon:, - model:) + def self.create_surface(model, polygon) surface = OpenStudio::Model::Surface.new(polygon, model) - surface.additionalProperties.setFeature('Index', indexer(model: model)) + surface.additionalProperties.setFeature('Index', indexer(model)) return surface end # Create a new OpenStudio subsurface and assign an Index to it. # - # @param polygon [OpenStudio::Point3dVector] the vertices for the subsurface # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param polygon [OpenStudio::Point3dVector] the vertices for the subsurface # @return [OpenStudio::Model::SubSurface] the newly created subsurface - def self.create_sub_surface(polygon:, - model:) + def self.create_sub_surface(model, polygon) sub_surface = OpenStudio::Model::SubSurface.new(polygon, model) - sub_surface.additionalProperties.setFeature('Index', indexer(model: model)) + sub_surface.additionalProperties.setFeature('Index', indexer(model)) return sub_surface end @@ -2070,7 +2040,7 @@ def self.create_sub_surface(polygon:, # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @return [Integer] the incremented Index value - def self.indexer(model:) + def self.indexer(model) indexes = [0] (model.getSpaces + model.getSurfaces + model.getSubSurfaces).each do |s| next if !s.additionalProperties.getFeatureAsInteger('Index').is_initialized @@ -2085,8 +2055,7 @@ def self.indexer(model:) # @param surface1 [OpenStudio::Model::Surface] the first surface to compare # @param surface2 [OpenStudio::Model::Surface] the second surface to compare # @return [Boolean] true if two surfaces share the same vertices - def self.has_same_vertices(surface1:, - surface2:) + def self.has_same_vertices(surface1, surface2) if get_surface_x_values(surfaceArray: [surface1]).sort == get_surface_x_values(surfaceArray: [surface2]).sort && get_surface_y_values(surfaceArray: [surface1]).sort == get_surface_y_values(surfaceArray: [surface2]).sort && get_surface_z_values(surfaceArray: [surface1]).sort == get_surface_z_values(surfaceArray: [surface2]).sort && @@ -2111,9 +2080,9 @@ def self.make_polygon(*pts) # Initialize an identity matrix by setting the main diagonal elements to one. # - # @param m [OpenStudio::Matrix] a 4x4 OpenStudio::Matrix object # @return [OpenStudio::Matrix] a modified 4x4 OpenStudio::Matrix object - def self.initialize_transformation_matrix(m:) + def self.initialize_transformation_matrix() + m = OpenStudio::Matrix.new(4, 4, 0) m[0, 0] = 1 m[1, 1] = 1 m[2, 2] = 1 @@ -2125,7 +2094,7 @@ def self.initialize_transformation_matrix(m:) # # @param space [OpenStudio::Model::Space] the space of interest # @return [Double] the z value corresponding to floor surface in the provided space - def self.get_space_floor_z(space:) + def self.get_space_floor_z(space) space.surfaces.each do |surface| next unless surface.surfaceType == EPlus::SurfaceTypeFloor @@ -2139,32 +2108,30 @@ def self.get_space_floor_z(space:) # @param min_wall_height [Double] Minimum wall height needed to support windows (ft) # @param min_wall_width [Double] Minimum wall length needed to support windows (ft) # @return [Double] the gross area of the surface for which windows may be applied (ft2) - def self.get_wall_area_for_windows(surface:, - min_wall_height:, - min_wall_width:) + def self.get_wall_area_for_windows(surface:, min_wall_height:, min_wall_width:) # Skip surfaces with doors if surface.subSurfaces.size > 0 return 0.0 end # Only allow on gable and rectangular walls - if not (is_rectangular_wall(surface: surface) || is_gable_wall(surface: surface)) + if not (is_suface_rectangular_wall(surface) || is_surface_gable_wall(surface)) return 0.0 end # Can't fit the smallest window? - if get_surface_length(surface: surface) < min_wall_width + if get_surface_length(surface) < min_wall_width return 0.0 end # Wall too short? - if min_wall_height > get_surface_height(surface: surface) + if min_wall_height > get_surface_height(surface) return 0.0 end # Gable too short? # super crude safety factor of 1.5 - if is_gable_wall(surface: surface) && (min_wall_height > get_surface_height(surface: surface) / 1.5) + if is_surface_gable_wall(surface) && (min_wall_height > get_surface_height(surface) / 1.5) return 0.0 end @@ -2173,6 +2140,8 @@ def self.get_wall_area_for_windows(surface:, # Adds pairs of windows to the given wall that achieve the desired window area. # + # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param surface [OpenStudio::Model::Surface] the wall of interest # @param window_area [Double] amount of window area (ft2) # @param window_gap_y [Double] distance from top of wall (ft) @@ -2180,20 +2149,11 @@ def self.get_wall_area_for_windows(surface:, # @param window_aspect_ratio [Double] ratio of window height to width (frac) # @param max_single_window_area [Double] maximum area for a single window (ft2) # @param facade [String] front, back, left, or right - # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @return [Boolean] true if successful - def self.add_windows_to_wall(surface:, - window_area:, - window_gap_y:, - window_gap_x:, - window_aspect_ratio:, - max_single_window_area:, - facade:, - model:, - runner:) - wall_width = get_surface_length(surface: surface) # ft - average_ceiling_height = get_surface_height(surface: surface) # ft + def self.add_windows_to_wall(runner, model, surface:, window_area:, window_gap_y:, window_gap_x:, + window_aspect_ratio:, max_single_window_area:, facade:) + wall_width = get_surface_length(surface) # ft + average_ceiling_height = get_surface_height(surface) # ft # Calculate number of windows needed num_windows = (window_area / max_single_window_area).ceil @@ -2237,7 +2197,7 @@ def self.add_windows_to_wall(surface:, window_vertices << vertex end - sub_surface = create_sub_surface(polygon: window_vertices, model: model) + sub_surface = create_sub_surface(model, window_vertices) sub_surface.setName("#{surface.name} - Window 1") sub_surface.setSurface(surface) sub_surface.setSubSurfaceType(EPlus::SubSurfaceTypeWindow) @@ -2246,7 +2206,7 @@ def self.add_windows_to_wall(surface:, # Position window from top of surface win_top = average_ceiling_height - window_gap_y - if is_gable_wall(surface: surface) + if is_surface_gable_wall(surface) # For gable surfaces, position windows from bottom of surface so they fit win_top = window_height + window_gap_y end @@ -2262,13 +2222,13 @@ def self.add_windows_to_wall(surface:, if not ((i == num_window_groups) && (num_windows % 2 == 1)) # Two windows in group win_num += 1 - add_window_to_wall(surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx - window_width / 2.0 - window_gap_x / 2.0, win_center_y: group_cy, win_num: win_num, facade: facade, model: model) + add_window_to_wall(model, surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx - window_width / 2.0 - window_gap_x / 2.0, win_center_y: group_cy, win_num: win_num, facade: facade) win_num += 1 - add_window_to_wall(surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx + window_width / 2.0 + window_gap_x / 2.0, win_center_y: group_cy, win_num: win_num, facade: facade, model: model) + add_window_to_wall(model, surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx + window_width / 2.0 + window_gap_x / 2.0, win_center_y: group_cy, win_num: win_num, facade: facade) else # One window in group win_num += 1 - add_window_to_wall(surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx, win_center_y: group_cy, win_num: win_num, facade: facade, model: model) + add_window_to_wall(model, surface: surface, win_width: window_width, win_height: window_height, win_center_x: group_cx, win_center_y: group_cy, win_num: win_num, facade: facade) end end @@ -2277,6 +2237,7 @@ def self.add_windows_to_wall(surface:, # Adds a single window to the given wall with the specified location/size. # + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param surface [OpenStudio::Model::Surface] the wall of interest # @param win_width [Double] width of the window (ft) # @param win_height [Double] height of the window (ft) @@ -2284,16 +2245,9 @@ def self.add_windows_to_wall(surface:, # @param win_center_y [Double] y-position of the window's center (ft) # @param win_num [Integer] The window number for the current surface # @param facade [String] front, back, left, or right - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @return [nil] - def self.add_window_to_wall(surface:, - win_width:, - win_height:, - win_center_x:, - win_center_y:, - win_num:, - facade:, - model:) + def self.add_window_to_wall(model, surface:, win_width:, win_height:, win_center_x:, win_center_y:, + win_num:, facade:) # Create window vertices in relative coordinates, ft upperleft = [win_center_x - win_width / 2.0, win_center_y + win_height / 2.0] upperright = [win_center_x + win_width / 2.0, win_center_y + win_height / 2.0] @@ -2331,7 +2285,7 @@ def self.add_window_to_wall(surface:, window_vertex = OpenStudio::Point3d.new(newx, newy, newz) window_polygon << window_vertex end - sub_surface = create_sub_surface(polygon: window_polygon, model: model) + sub_surface = create_sub_surface(model, window_polygon) sub_surface.setName("#{surface.name} - Window #{win_num}") sub_surface.setSurface(surface) sub_surface.setSubSurfaceType(EPlus::SubSurfaceTypeWindow) @@ -2341,7 +2295,7 @@ def self.add_window_to_wall(surface:, # # @param spaces [Array] array of OpenStudio::Model::Space objects # @return [Array] array of conditioned OpenStudio spaces - def self.get_conditioned_spaces(spaces:) + def self.get_conditioned_spaces(spaces) conditioned_spaces = [] spaces.each do |space| next unless space.spaceType.get.standardsSpaceType.get == HPXML::LocationConditionedSpace @@ -2355,7 +2309,7 @@ def self.get_conditioned_spaces(spaces:) # # @param spaces [Array] array of OpenStudio::Model::Space objects # @return [Array] array of garage OpenStudio spaces - def self.get_garage_spaces(spaces:) + def self.get_garage_spaces(spaces) garage_spaces = [] spaces.each do |space| next unless space.spaceType.get.standardsSpaceType.get == HPXML::LocationGarage @@ -2372,7 +2326,7 @@ def self.get_garage_spaces(spaces:) # # @param surface [OpenStudio::Model::Surface] the surface of interest # @return [Boolean] true if surface satisfies rectangular wall criteria - def self.is_rectangular_wall(surface:) + def self.is_suface_rectangular_wall(surface) if ((surface.surfaceType != EPlus::SurfaceTypeWall) || (surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors)) return false end @@ -2402,7 +2356,7 @@ def self.is_rectangular_wall(surface:) # # @param surface [OpenStudio::Model::Surface] the surface of interest # @return [Boolean] true if surface satisfies gable wall criteria - def self.is_gable_wall(surface:) + def self.is_surface_gable_wall(surface) if ((surface.surfaceType != EPlus::SurfaceTypeWall) || (surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors)) return false end @@ -2414,7 +2368,7 @@ def self.is_gable_wall(surface:) end space = surface.space.get - if not space_has_roof(space: space) + if not space_has_roof(space) return false end @@ -2428,7 +2382,7 @@ def self.is_gable_wall(surface:) # # @param space [OpenStudio::Model::Space] the space of interest # @return [Boolean] true if space has a roof deck - def self.space_has_roof(space:) + def self.space_has_roof(space) space.surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeRoofCeiling next if surface.outsideBoundaryCondition != EPlus::BoundaryConditionOutdoors @@ -2450,14 +2404,8 @@ def self.space_has_roof(space:) # @param roof_type [String] roof type of the building # @param rim_joist_height [Double] height of the rim joists (ft) # @return [OpenStudio::Model::Space] the newly created attic space - def self.get_attic_space(model:, - x:, - y:, - average_ceiling_height:, - num_floors:, - roof_pitch:, - roof_type:, - rim_joist_height:) + def self.get_attic_space(model, x:, y:, average_ceiling_height:, num_floors:, + roof_pitch:, roof_type:, rim_joist_height:) y_rear = 0 y_peak = -y / 2 y_tot = y @@ -2517,23 +2465,23 @@ def self.get_attic_space(model:, side_type = EPlus::SurfaceTypeRoofCeiling end - surface_floor = create_surface(polygon: attic_polygon, model: model) + surface_floor = create_surface(model, attic_polygon) surface_floor.setSurfaceType(EPlus::SurfaceTypeFloor) surface_floor.setOutsideBoundaryCondition(EPlus::BoundaryConditionSurface) - surface_w_roof = create_surface(polygon: polygon_w_roof, model: model) + surface_w_roof = create_surface(model, polygon_w_roof) surface_w_roof.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) surface_w_roof.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_e_roof = create_surface(polygon: polygon_e_roof, model: model) + surface_e_roof = create_surface(model, polygon_e_roof) surface_e_roof.setSurfaceType(EPlus::SurfaceTypeRoofCeiling) surface_e_roof.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_s_wall = create_surface(polygon: polygon_s_wall, model: model) + surface_s_wall = create_surface(model, polygon_s_wall) surface_s_wall.setSurfaceType(side_type) surface_s_wall.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - surface_n_wall = create_surface(polygon: polygon_n_wall, model: model) + surface_n_wall = create_surface(model, polygon_n_wall) surface_n_wall.setSurfaceType(side_type) surface_n_wall.setOutsideBoundaryCondition(EPlus::BoundaryConditionOutdoors) - attic_space = create_space(model: model) + attic_space = create_space(model) surface_floor.setSpace(attic_space) surface_w_roof.setSpace(attic_space) @@ -2550,11 +2498,9 @@ def self.get_attic_space(model:, # @param foundation_type [String] HPXML location for foundation type # @param foundation_height [Double] height of the foundation (m) # @return [nil] - def self.apply_ambient_foundation_shift(model:, - foundation_type:, - foundation_height:) + def self.apply_ambient_foundation_shift(model, foundation_type:, foundation_height:) if [HPXML::FoundationTypeAmbient, HPXML::FoundationTypeBellyAndWing].include?(foundation_type) - m = initialize_transformation_matrix(m: OpenStudio::Matrix.new(4, 4, 0)) + m = initialize_transformation_matrix() m[2, 3] = -foundation_height model.getSpaces.each do |space| space.changeTransformation(OpenStudio::Transformation.new(m)) @@ -2569,7 +2515,7 @@ def self.apply_ambient_foundation_shift(model:, # # @param space [OpenStudio::Model::Space] the space of interest # @return [Boolean] true if space is below grade - def self.space_is_below_grade(space:) + def self.space_is_below_grade(space) space.surfaces.each do |surface| next if surface.surfaceType != EPlus::SurfaceTypeWall if surface.outsideBoundaryCondition == EPlus::BoundaryConditionFoundation @@ -2585,9 +2531,7 @@ def self.space_is_below_grade(space:) # @param v1 [OpenStudio::Point3d] the first vertex to check against # @param v2 [OpenStudio::Point3d] the second vertex to check against # @return [Boolean] true if point is between the other two points - def self.is_point_between(p:, - v1:, - v2:) + def self.is_point_between(p:, v1:, v2:) is_between = false tol = 0.001 if ((p[2] - v1[2]).abs <= tol) && ((p[2] - v2[2]).abs <= tol) # equal z @@ -2655,7 +2599,7 @@ def self.get_walls_connected_to_floor(wall_surfaces:, # @param surfaces [Array] array of OpenStudio::Model::Surface objects # @param use_top_edge [Boolean] true if matching on max z values for surfaces # @return [Array] List of edges, where each edge is an array with two vertices and a facade - def self.get_edges_for_surfaces(surfaces:, + def self.get_edges_for_surfaces(surfaces, use_top_edge:) edges = [] surfaces.each do |surface| @@ -2677,7 +2621,7 @@ def self.get_edges_for_surfaces(surfaces:, vertex.z + surface.space.get.zOrigin] end - facade = get_facade_for_surface(surface: surface) + facade = get_surface_facade(surface) # make edges counter = 0 diff --git a/BuildResidentialScheduleFile/measure.rb b/BuildResidentialScheduleFile/measure.rb index 65ba8865ee..55a29b78bd 100644 --- a/BuildResidentialScheduleFile/measure.rb +++ b/BuildResidentialScheduleFile/measure.rb @@ -269,7 +269,7 @@ def get_generator_inputs(hpxml_bldg, weather, args) args[:column_names] = args[:schedules_column_names].split(',').map(&:strip) if !args[:schedules_column_names].nil? if hpxml_bldg.building_occupancy.number_of_residents.nil? - args[:geometry_num_occupants] = Geometry.get_occupancy_default_num(nbeds: hpxml_bldg.building_construction.number_of_bedrooms) + args[:geometry_num_occupants] = Geometry.get_occupancy_default_num(hpxml_bldg.building_construction.number_of_bedrooms) else args[:geometry_num_occupants] = hpxml_bldg.building_occupancy.number_of_residents end diff --git a/BuildResidentialScheduleFile/measure.xml b/BuildResidentialScheduleFile/measure.xml index 5ac98b35e0..f892fc288f 100644 --- a/BuildResidentialScheduleFile/measure.xml +++ b/BuildResidentialScheduleFile/measure.xml @@ -3,8 +3,8 @@ 3.1 build_residential_schedule_file f770b2db-1a9f-4e99-99a7-7f3161a594b1 - 12b68002-26b0-4e2d-bb89-f337226f0ac3 - 2024-12-09T21:40:29Z + c5bb6758-f028-455a-a49d-25f4df40e500 + 2025-01-02T23:31:30Z 03F02484 BuildResidentialScheduleFile Schedule File Builder @@ -133,7 +133,7 @@ measure.rb rb script - 655C4010 + F7D77A2C README.md diff --git a/HPXMLtoOpenStudio/measure.rb b/HPXMLtoOpenStudio/measure.rb index 0b68841d76..93bbd7cd54 100644 --- a/HPXMLtoOpenStudio/measure.rb +++ b/HPXMLtoOpenStudio/measure.rb @@ -109,7 +109,7 @@ def run(model, runner, user_arguments) end Version.check_openstudio_version() - Model.reset(model, runner) + Model.reset(runner, model) args = runner.getArgumentValues(arguments(model), user_arguments) set_file_paths(args) @@ -327,7 +327,7 @@ def create_unit_model(hpxml, hpxml_bldg, runner, model, epw_path, weather, sched # Conditioned space & setpoints spaces = {} # Map of HPXML locations => OpenStudio Space objects Geometry.create_or_get_space(model, spaces, HPXML::LocationConditionedSpace, hpxml_bldg) - hvac_days = HVAC.apply_setpoints(model, runner, weather, spaces, hpxml_bldg, hpxml.header, schedules_file) + hvac_days = HVAC.apply_setpoints(runner, model, weather, spaces, hpxml_bldg, hpxml.header, schedules_file) # Geometry & Enclosure Geometry.apply_roofs(runner, model, spaces, hpxml_bldg, hpxml.header) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index c3d6b5e1ed..cb2376a019 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 60ec36e7-bde1-4d70-a3e4-1bb0262b1e99 - 2025-01-02T22:20:22Z + 95e76879-5bd0-4f40-9bc9-a6f07b622400 + 2025-01-03T03:27:12Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -183,13 +183,13 @@ measure.rb rb script - CA101CA3 + 28965567 airflow.rb rb resource - 14A94278 + 29D991BE battery.rb @@ -327,7 +327,7 @@ defaults.rb rb resource - F2AE9AD3 + EB4D5A1A energyplus.rb @@ -345,7 +345,7 @@ geometry.rb rb resource - 58D1C43A + B6CCA5A7 hotwater_appliances.rb @@ -387,19 +387,19 @@ hvac.rb rb resource - 759FC93A + 0A9C42C1 hvac_sizing.rb rb resource - A3B81222 + A079BB8E internal_gains.rb rb resource - 206BE2B6 + A9CC95F5 lighting.rb @@ -429,7 +429,7 @@ meta_measure.rb rb resource - F335EDC8 + 377E6136 minitest_helper.rb @@ -447,7 +447,7 @@ model.rb rb resource - 87A7EB64 + F0F4648E output.rb @@ -627,7 +627,7 @@ waterheater.rb rb resource - B6FE7ABC + ECEBF85F weather.rb @@ -669,7 +669,7 @@ test_enclosure.rb rb test - 2DA78F4F + 9D653777 test_generator.rb diff --git a/HPXMLtoOpenStudio/resources/airflow.rb b/HPXMLtoOpenStudio/resources/airflow.rb index c4f4056ad3..4646118e59 100644 --- a/HPXMLtoOpenStudio/resources/airflow.rb +++ b/HPXMLtoOpenStudio/resources/airflow.rb @@ -7,10 +7,10 @@ module Airflow InfilPressureExponent = 0.65 AssumedInsideTemp = 73.5 # (F) Gravity = 32.174 # acceleration of gravity (ft/s2) - UnventedSpaceACH = 0.1 # Assumption + UnventedSpaceACH = 0.1 # natural air changes per hour, assumption + ReferenceHeight = 8.202 # reference height per ASHRAE 62.2 (ft) # Adds HPXML Air Infiltration and HPXML HVAC Distribution to the OpenStudio model. - # TODO for adding more description (e.g., around checks and warnings) # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param model [OpenStudio::Model::Model] OpenStudio Model object @@ -22,7 +22,7 @@ module Airflow # @param airloop_map [Hash] Map of HPXML System ID => OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) objects # @return [nil] def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedules_file, airloop_map) - sensors = create_sensors(runner, model, spaces, hpxml_header) + sensors = create_sensors(runner, model, weather, spaces, hpxml_bldg, hpxml_header) # Ventilation fans vent_fans = { mech: [], cfis_suppl: [], whf: [], kitchen: [], bath: [] } @@ -59,56 +59,38 @@ def self.apply(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedul end # Apply ducts - duct_lk_imbals = [] - adiabatic_const = nil - duct_systems = create_duct_systems(model, spaces, hpxml_bldg, airloop_map) - check_duct_leakage(runner, hpxml_bldg) - duct_systems.each do |ducts, object| - adiabatic_const = apply_ducts(model, spaces, hpxml_bldg, ducts, object, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, adiabatic_const) - end + apply_ducts(runner, model, spaces, hpxml_bldg, airloop_map, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors) # Apply infiltration/ventilation set_wind_speed_correction(model, hpxml_bldg) infil_values = get_values_from_air_infiltration_measurements(hpxml_bldg, weather) - # Cooling season schedule - # Applies to natural ventilation, not HVAC equipment. - # Uses BAHSP cooling season, not user-specified cooling season (which may be, e.g., year-round). - _, default_cooling_months = HVAC.get_building_america_hvac_seasons(weather, hpxml_bldg.latitude) - clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), default_cooling_months, EPlus::ScheduleTypeLimitsFraction) - clg_ssn_sensor = Model.add_ems_sensor( - model, - name: 'cool_season', - output_var_or_meter_name: 'Schedule Value', - key_name: clg_season_sch.schedule.name - ) - # Natural ventilation and whole house fans - apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hpxml_bldg, hpxml_header, vent_fans, clg_ssn_sensor, infil_values, sensors) + apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hpxml_bldg, hpxml_header, vent_fans, infil_values, sensors) - # Infiltration/ventilation for unconditioned spaces + # Infiltration/ventilation apply_infiltration_to_garage(model, spaces, hpxml_bldg, infil_values, duct_lk_imbals) apply_infiltration_to_unconditioned_basement(model, spaces, duct_lk_imbals) apply_infiltration_to_vented_crawlspace(model, spaces, weather, hpxml_bldg, duct_lk_imbals) apply_infiltration_to_unvented_crawlspace(model, spaces, duct_lk_imbals) apply_infiltration_to_vented_attic(model, spaces, weather, hpxml_bldg, hpxml_header, duct_lk_imbals) apply_infiltration_to_unvented_attic(model, spaces, duct_lk_imbals) - - # Infiltration/ventilation for conditioned space apply_infiltration_ventilation_to_conditioned(runner, model, spaces, weather, hpxml_bldg, hpxml_header, vent_fans, infil_values, - clg_ssn_sensor, schedules_file, duct_lk_imbals, cfis_data, fan_data, sensors) + schedules_file, duct_lk_imbals, cfis_data, fan_data, sensors) end # Creates a variety of EMS sensors used in airflow calculations. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param weather [WeatherFile] Weather object containing EPW information # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects + # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @return [Hash] Map of :sensor_types => EMS sensors - def self.create_sensors(runner, model, spaces, hpxml_header) + # @return [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects + def self.create_sensors(runner, model, weather, spaces, hpxml_bldg, hpxml_header) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get @@ -172,6 +154,17 @@ def self.create_sensors(runner, model, spaces, hpxml_header) sensors[:hvac_avail].additionalProperties.setFeature('ObjectType', Constants::ObjectTypeHVACAvailabilitySensor) end + # Create cooling season schedule sensor (applies only to natural ventilation, not HVAC equipment). + # Uses BAHSP cooling season, not user-specified cooling season (which may be, e.g., year-round). + _, default_cooling_months = HVAC.get_building_america_hvac_seasons(weather, hpxml_bldg.latitude) + clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), default_cooling_months, EPlus::ScheduleTypeLimitsFraction) + sensors[:clg_ssn] = Model.add_ems_sensor( + model, + name: 'cool_season', + output_var_or_meter_name: 'Schedule Value', + key_name: clg_season_sch.schedule.name + ) + return sensors end @@ -208,11 +201,11 @@ def self.get_infiltration_measurement_of_interest(hpxml_bldg, manualj_infiltrati fail 'Could not find air infiltration measurement.' end - # TODO + # Returns a standard set of infiltration values for the HPXML Building. # # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param weather [WeatherFile] Weather object containing EPW information - # @return [TODO] TODO + # @return [Hash] Map with various infiltration key-value pairs (SLA, infiltration volume & height, etc.) def self.get_values_from_air_infiltration_measurements(hpxml_bldg, weather) cfa = hpxml_bldg.building_construction.conditioned_floor_area measurement = get_infiltration_measurement_of_interest(hpxml_bldg) @@ -222,43 +215,49 @@ def self.get_values_from_air_infiltration_measurements(hpxml_bldg, weather) if infil_height.nil? infil_height = hpxml_bldg.inferred_infiltration_height(infil_volume) end + infil_avg_ceil_height = infil_volume / cfa sla, ach50, nach = nil if [HPXML::UnitsACH, HPXML::UnitsCFM].include?(measurement.unit_of_measure) if measurement.unit_of_measure == HPXML::UnitsACH - ach50 = calc_air_leakage_at_diff_pressure(InfilPressureExponent, measurement.air_leakage, measurement.house_pressure, 50.0) + ach50 = calc_infiltration_at_diff_pressure(measurement.air_leakage, measurement.house_pressure, 50.0) elsif measurement.unit_of_measure == HPXML::UnitsCFM achXX = measurement.air_leakage * 60.0 / infil_volume # Convert CFM to ACH - ach50 = calc_air_leakage_at_diff_pressure(InfilPressureExponent, achXX, measurement.house_pressure, 50.0) + ach50 = calc_infiltration_at_diff_pressure(achXX, measurement.house_pressure, 50.0) end - sla = get_infiltration_SLA_from_ACH50(ach50, InfilPressureExponent, cfa, infil_volume) - nach = get_infiltration_ACH_from_SLA(sla, infil_height, weather) + sla = get_infiltration_SLA_from_ACH50(ach50, infil_avg_ceil_height) + nach = get_infiltration_ACH_from_SLA(sla, infil_height, infil_avg_ceil_height, weather) elsif [HPXML::UnitsACHNatural, HPXML::UnitsCFMNatural].include? measurement.unit_of_measure if measurement.unit_of_measure == HPXML::UnitsACHNatural nach = measurement.air_leakage elsif measurement.unit_of_measure == HPXML::UnitsCFMNatural nach = measurement.air_leakage * 60.0 / infil_volume # Convert CFM to ACH end - avg_ceiling_height = hpxml_bldg.building_construction.average_ceiling_height - sla = get_infiltration_SLA_from_ACH(nach, infil_height, avg_ceiling_height, weather) - ach50 = get_infiltration_ACH50_from_SLA(sla, InfilPressureExponent, cfa, infil_volume) + sla = get_infiltration_SLA_from_ACH(nach, infil_height, infil_avg_ceil_height, weather) + ach50 = get_infiltration_ACH50_from_SLA(sla, infil_avg_ceil_height) elsif !measurement.effective_leakage_area.nil? sla = UnitConversions.convert(measurement.effective_leakage_area, 'in^2', 'ft^2') / cfa - ach50 = get_infiltration_ACH50_from_SLA(sla, InfilPressureExponent, cfa, infil_volume) - nach = get_infiltration_ACH_from_SLA(sla, infil_height, weather) + ach50 = get_infiltration_ACH50_from_SLA(sla, infil_avg_ceil_height) + nach = get_infiltration_ACH_from_SLA(sla, infil_height, infil_avg_ceil_height, weather) else fail 'Unexpected error.' end if measurement.infiltration_type == HPXML::InfiltrationTypeUnitTotal - a_ext = measurement.a_ext # Adjustment ratio for SFA/MF units; exterior envelope area divided by total envelope area + a_ext = measurement.a_ext # Ratio of exterior envelope area to total envelope area for SFA/MF units end a_ext = 1.0 if a_ext.nil? - return { sla: sla, ach50: ach50, nach: nach, volume: infil_volume, height: infil_height, a_ext: a_ext } + return { sla: sla, + ach50: ach50, + nach: nach, + volume: infil_volume, + height: infil_height, + avg_ceil_height: infil_avg_ceil_height, + a_ext: a_ext } end - # TODO + # Sets terrain/shielding coefficients to the HPXML Site object for the AIM-2 infiltration algorithm. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit @@ -313,15 +312,17 @@ def self.set_wind_speed_correction(model, hpxml_bldg) site_ap.s_g_shielding_coef = site_ap.aim2_shelter_coeff / 3.0 end - # TODO + # Adds infiltration/ventilation to the OpenStudio unconditioned space; the infiltration may be characterized + # using either nACH or ELA. Also adds any duct leakage induced infiltration (when there are ducts in the + # unconditioned space and the ducts have supply leakage != return leakage). # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param space [OpenStudio::Model::Space] an OpenStudio::Model::Space object - # @param ach [TODO] TODO - # @param ela [TODO] TODO - # @param c_w_SG [TODO] TODO - # @param c_s_SG [TODO] TODO - # @param duct_lk_imbals [TODO] TODO + # @param ach [Double] Annual average air changes per hour + # @param ela [Double] Effective Leakage Area (sq. in.) + # @param c_w_SG [Double] Sherman-Grimsrud (ASHRAE Basic Model) wind coefficient + # @param c_s_SG [Double] Sherman-Grimsrud (ASHRAE Basic Model) stack coefficient + # @param duct_lk_imbals [Array] List of duct leakage imbalance information # @return [nil] def self.apply_infiltration_to_unconditioned_space(model, space, ach, ela, c_w_SG, c_s_SG, duct_lk_imbals) # Infiltration/Ventilation @@ -390,26 +391,24 @@ def self.apply_infiltration_to_unconditioned_space(model, space, ach, ela, c_w_S end end - # TODO + # Adds an EMS program to introduce airflow due to natural ventilation (ventilation through open operable + # windows) and, if present, the whole house fan. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param vent_fans [TODO] TODO - # @param nv_clg_ssn_sensor [TODO] TODO - # @param infil_values [Hash] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param infil_values [Hash] Map with various infiltration key-value pairs (SLA, infiltration volume & height, etc.) + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects # @return [nil] - def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hpxml_bldg, hpxml_header, vent_fans, nv_clg_ssn_sensor, - infil_values, sensors) - + def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hpxml_bldg, hpxml_header, vent_fans, infil_values, sensors) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get - # NV Availability Schedule - nv_avail_sch = create_nv_and_whf_avail_sch(model, Constants::ObjectTypeNaturalVentilation, hpxml_bldg.header.natvent_days_per_week, hpxml_header.unavailable_periods) + # Natural Ventilation availability schedule and sensor + nv_avail_sch = create_sched_from_num_days_per_week(model, Constants::ObjectTypeNaturalVentilation, hpxml_bldg.header.natvent_days_per_week, hpxml_header.unavailable_periods) nv_avail_sensor = Model.add_ems_sensor( model, @@ -418,14 +417,14 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp key_name: nv_avail_sch.name ) - # Availability Schedules paired with vent fan class + # Whole House Fan availability schedule(s) and sensor(s) # If whf_num_days_per_week is exposed, can handle multiple fans with different days of operation whf_avail_sensors = {} vent_fans[:whf].each_with_index do |vent_whf, index| whf_num_days_per_week = 7 # FUTURE: Expose via HPXML? obj_name = "#{Constants::ObjectTypeWholeHouseFan} #{index}" whf_unavailable_periods = Schedule.get_unavailable_periods(runner, SchedulesFile::Columns[:WholeHouseFan].name, hpxml_header.unavailable_periods) - whf_avail_sch = create_nv_and_whf_avail_sch(model, obj_name, whf_num_days_per_week, whf_unavailable_periods) + whf_avail_sch = create_sched_from_num_days_per_week(model, obj_name, whf_num_days_per_week, whf_unavailable_periods) whf_avail_sensors[vent_whf.id] = Model.add_ems_sensor( model, @@ -526,7 +525,7 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp window_area = hpxml_bldg.windows.map { |w| w.area }.sum(0.0) open_window_area = window_area * hpxml_bldg.additional_properties.initial_frac_windows_operable * 0.5 * 0.2 - area = 0.6 * open_window_area # ft^2, for Sherman-Grimsrud + area = 0.6 * open_window_area # ft^2, for Sherman-Grimsrud (ASHRAE Basic Model) max_rate = 20.0 # Air Changes per hour max_flow_rate = max_rate * infil_values[:volume] / UnitConversions.convert(1.0, 'hr', 'min') neutral_level = 0.5 @@ -566,7 +565,7 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp vent_program.addLine("Set Tnvsp = (#{default_htg_sp} + #{default_clg_sp}) / 2") end vent_program.addLine("Set NVavail = #{nv_avail_sensor.name}") - vent_program.addLine("Set ClgSsnAvail = #{nv_clg_ssn_sensor.name}") + vent_program.addLine("Set ClgSsnAvail = #{sensors[:clg_ssn].name}") vent_program.addLine('Set Qnv = 0') # Init vent_program.addLine('Set Qwhf = 0') # Init vent_program.addLine("Set #{cond_to_zone_flow_rate_actuator.name} = 0") unless whf_zone.nil? # Init @@ -638,14 +637,16 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp vent_program.addLine("Set #{q_whf_var.name} = Qwhf") end - # TODO + # Returns an OpenStudio schedule w/ the specified number of days per week that the schedule is active. + # The active days of the week are arbitrary and currently hard-coded. Used for natural ventilation and + # whole house fan schedules. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param obj_name [String] Name for the OpenStudio object - # @param num_days_per_week [TODO] TODO + # @param num_days_per_week [Integer] Number of days per week that the schedule should be active (0-7) # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies - # @return [TODO] TODO - def self.create_nv_and_whf_avail_sch(model, obj_name, num_days_per_week, unavailable_periods) + # @return [OpenStudio::Model::ScheduleRuleset] The newly created schedule + def self.create_sched_from_num_days_per_week(model, obj_name, num_days_per_week, unavailable_periods) sch_name = "#{obj_name} schedule" avail_sch = Model.add_schedule_ruleset( model, @@ -675,15 +676,15 @@ def self.create_nv_and_whf_avail_sch(model, obj_name, num_days_per_week, unavail return avail_sch end - # TODO + # Creates a return plenum thermal zone for use in the EMS duct model calculations. + # The zone is meant to have essentially zero heat transfer other than what the + # duct calculations add/remove. # # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param loop_name [TODO] TODO + # @param loop_name [String] OpenStudio AirLoopHVAC name # @param unit_multiplier [Integer] Number of similar dwelling units - # @param adiabatic_const [TODO] TODO - # @return [TODO] TODO - def self.create_return_air_duct_zone(model, loop_name, unit_multiplier, adiabatic_const) - # Create the return air plenum zone, space + # @return [OpenStudio::Model::ThermalZone] The newly created return plenum zone + def self.create_return_air_duct_zone(model, loop_name, unit_multiplier) ra_duct_zone = OpenStudio::Model::ThermalZone.new(model) ra_duct_zone.setMultiplier(unit_multiplier) ra_duct_zone.setName(loop_name + ' ret air zone') @@ -700,21 +701,25 @@ def self.create_return_air_duct_zone(model, loop_name, unit_multiplier, adiabati ra_space.setName(loop_name + ' ret air space') ra_space.setThermalZone(ra_duct_zone) - ra_space.surfaces.each do |surface| - if adiabatic_const.nil? - adiabatic_mat = Model.add_massless_material( - model, - name: 'Adiabatic', - rvalue: 176.1 - ) + adiabatic_mat = model.getMaterials.find { |m| m.name.to_s == 'Adiabatic' } + if adiabatic_mat.nil? + adiabatic_mat = Model.add_massless_material( + model, + name: 'Adiabatic', + rvalue: 176.1 + ) + end - adiabatic_const = Model.add_construction( - model, - name: 'AdiabaticConst', - layers: [adiabatic_mat] - ) - end + adiabatic_const = model.getConstructions.find { |c| c.name.to_s == 'AdiabaticConst' } + if adiabatic_const.nil? + adiabatic_const = Model.add_construction( + model, + name: 'AdiabaticConst', + layers: [adiabatic_mat] + ) + end + ra_space.surfaces.each do |surface| surface.setConstruction(adiabatic_const) surface.setOutsideBoundaryCondition(EPlus::BoundaryConditionAdiabatic) surface.setSunExposure(EPlus::SurfaceSunExposureNo) @@ -725,16 +730,16 @@ def self.create_return_air_duct_zone(model, loop_name, unit_multiplier, adiabati surface_property_convection_coefficients.setConvectionCoefficient1(30) end - return ra_duct_zone, adiabatic_const + return ra_duct_zone end - # TODO + # Initializes the EMS program if there is a CFIS mechanical ventilation system. # # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param vent_fans [TODO] TODO + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans # @param airloop_map [Hash] Map of HPXML System ID => OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) objects # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies - # @return [TODO] TODO + # @return [Hash] Map with various CFIS-relative OpenStudio model objects def self.initialize_cfis(model, vent_fans, airloop_map, unavailable_periods) cfis_data = { airloop: {}, sum_oa_cfm_var: {}, f_vent_only_mode_var: {} } return cfis_data if vent_fans[:mech].empty? @@ -790,20 +795,21 @@ def self.initialize_cfis(model, vent_fans, airloop_map, unavailable_periods) return cfis_data end - # TODO + # Updates the fan_data hash with various EMS objects associated with the given AirLoopHVAC (or + # ZoneHVACFourPipeFanCoil) object. # # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param osm_object [TODO] TODO - # @param fan_data [TODO] TODO - # @return [TODO] TODO - def self.initialize_fan_objects(model, osm_object, fan_data) + # @param object [OpenStudio::Model::XXX] OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil) object + # @param fan_data [Hash] Map of HVAC blower fan properties => values + # @return [nil] + def self.initialize_fan_objects(model, object, fan_data) # Get the supply fan - if osm_object.is_a? OpenStudio::Model::ZoneHVACFourPipeFanCoil - supply_fan = osm_object.supplyAirFan - elsif osm_object.is_a? OpenStudio::Model::AirLoopHVAC - system = HVAC.get_unitary_system_from_air_loop_hvac(osm_object) + if object.is_a? OpenStudio::Model::ZoneHVACFourPipeFanCoil + supply_fan = object.supplyAirFan + elsif object.is_a? OpenStudio::Model::AirLoopHVAC + system = HVAC.get_unitary_system_from_air_loop_hvac(object) if system.nil? # Evaporative cooler supply fan directly on air loop - supply_fan = osm_object.supplyFan.get + supply_fan = object.supplyFan.get else supply_fan = system.supplyFan.get end @@ -811,21 +817,21 @@ def self.initialize_fan_objects(model, osm_object, fan_data) fail 'Unexpected object type.' end - fan_data[:rtf_var][osm_object] = Model.add_ems_global_var( + fan_data[:rtf_var][object] = Model.add_ems_global_var( model, - var_name: "#{osm_object.name} Fan RTF" + var_name: "#{object.name} Fan RTF" ) # Supply fan maximum mass flow rate - fan_data[:mfr_max_var][osm_object] = Model.add_ems_internal_var( + fan_data[:mfr_max_var][object] = Model.add_ems_internal_var( model, - name: "#{osm_object.name} max sup fan mfr", + name: "#{object.name} max sup fan mfr", model_object: supply_fan, type: EPlus::EMSIntVarFanMFR ) if supply_fan.to_FanSystemModel.is_initialized - fan_data[:rtf_sensor][osm_object] = [] + fan_data[:rtf_sensor][object] = [] num_speeds = supply_fan.to_FanSystemModel.get.numberofSpeeds for i in 1..num_speeds if num_speeds == 1 @@ -833,9 +839,9 @@ def self.initialize_fan_objects(model, osm_object, fan_data) else var_name = "Fan Runtime Fraction Speed #{i}" end - fan_data[:rtf_sensor][osm_object] << Model.add_ems_sensor( + fan_data[:rtf_sensor][object] << Model.add_ems_sensor( model, - name: "#{fan_data[:rtf_var][osm_object].name} s", + name: "#{fan_data[:rtf_var][object].name} s", output_var_or_meter_name: var_name, key_name: supply_fan.name ) @@ -845,14 +851,13 @@ def self.initialize_fan_objects(model, osm_object, fan_data) end end - # TODO + # Issues warnings if duct leakage to outside is detected to be suspiciously high. We check here + # instead of in the Schematron validator in case duct locations are defaulted. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @return [nil] def self.check_duct_leakage(runner, hpxml_bldg) - # Duct leakage to outside warnings? - # Need to check here instead of in schematron in case duct locations are defaulted cfa = hpxml_bldg.building_construction.conditioned_floor_area hpxml_bldg.hvac_distributions.each do |hvac_distribution| next unless hvac_distribution.distribution_system_type == HPXML::HVACDistributionTypeAir @@ -894,45 +899,77 @@ def self.check_duct_leakage(runner, hpxml_bldg) end end + # Applies all air distribution systems' ducts to the OpenStudio model. + # + # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object + # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects + # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit + # @param airloop_map [Hash] Map of HPXML System ID => OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) objects + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param cfis_data [Hash] Map with various CFIS-relative OpenStudio model objects + # @param fan_data [Hash] Map of HVAC blower fan properties => values + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects + # @return [OpenStudio::Model::Construction] Adiabatic construction used by the duct model + def self.apply_ducts(runner, model, spaces, hpxml_bldg, airloop_map, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors) + check_duct_leakage(runner, hpxml_bldg) + + # Apply ducts for each air distribution system + hpxml_bldg.hvac_distributions.each do |hvac_distribution| + next unless hvac_distribution.distribution_system_type == HPXML::HVACDistributionTypeAir + + duct_infos = create_duct_infos(model, hvac_distribution, spaces) + next if duct_infos.empty? + + objects = hvac_distribution.hvac_systems.map { |hvac_system| airloop_map[hvac_system.id] }.select { |o| !o.nil? }.uniq + objects.each do |object| + apply_ducts_for_distribution_system(model, spaces, hpxml_bldg, duct_infos, object, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors) + end + end + end + # Creates an EMS program to calculate duct losses for a given air distribution system. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param ducts [TODO] TODO - # @param object [TODO] TODO - # @param vent_fans [TODO] TODO - # @param cfis_data [TODO] TODO - # @param fan_data [TODO] TODO - # @param duct_lk_imbals [TODO] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors - # @param adiabatic_const [TODO] TODO - # @return [TODO] TODO - def self.apply_ducts(model, spaces, hpxml_bldg, ducts, object, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, adiabatic_const) + # @param duct_infos [Array] List of duct info Hashes + # @param object [OpenStudio::Model::XXX] OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) object + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param cfis_data [Hash] Map with various CFIS-relative OpenStudio model objects + # @param fan_data [Hash] Map of HVAC blower fan properties => values + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects + # @return [nil] + def self.apply_ducts_for_distribution_system(model, spaces, hpxml_bldg, duct_infos, object, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get unit_multiplier = hpxml_bldg.building_construction.number_of_units - ducts.each do |duct| - if not duct.loc_schedule.nil? - # Pass MF space temperature schedule name - duct.location = duct.loc_schedule.name.to_s - elsif not duct.loc_space.nil? - duct.location = duct.loc_space.name.to_s - duct.zone = duct.loc_space.thermalZone.get - else # Outside/RoofDeck - duct.location = HPXML::LocationOutside - duct.zone = nil + duct_infos.each do |duct_info| + if not duct_info[:loc_schedule].nil? + # MF space temperature schedule name + duct_info[:location] = duct_info[:loc_schedule].name.to_s + duct_info[:zone] = nil + elsif not duct_info[:loc_space].nil? + # Thermal zone + duct_info[:location] = duct_info[:loc_space].name.to_s + duct_info[:zone] = duct_info[:loc_space].thermalZone.get + else + # Outside/RoofDeck + duct_info[:location] = HPXML::LocationOutside + duct_info[:zone] = nil end end - return if ducts.size == 0 # No ducts + return if duct_infos.size == 0 # No ducts if object.is_a? OpenStudio::Model::AirLoopHVAC # Most system types # Set the return plenum - ra_duct_zone, adiabatic_const = create_return_air_duct_zone(model, object.name.to_s, unit_multiplier, adiabatic_const) + ra_duct_zone = create_return_air_duct_zone(model, object.name.to_s, unit_multiplier) ra_duct_space = ra_duct_zone.spaces[0] conditioned_zone.setReturnPlenum(ra_duct_zone, object) @@ -1048,29 +1085,27 @@ def self.apply_ducts(model, spaces, hpxml_bldg, ducts, object, vent_fans, cfis_d duct_sensors[:ra_w] = [ra_w_var, ra_w_sensor] # Get unique set of duct locations - duct_locations = ducts.map { |duct| if duct.zone.nil? then duct.loc_schedule else duct.zone end }.uniq + duct_locations = duct_infos.map { |duct_info| if duct_info[:zone].nil? then duct_info[:loc_schedule] else duct_info[:zone] end }.uniq # Assign ducts to each duct location - duct_locations_ducts = {} + duct_locations_infos = {} duct_locations.each do |duct_location| - duct_locations_ducts[duct_location] = [] - ducts.each do |duct| - next unless (duct_location.nil? && duct.zone.nil?) || - (!duct_location.nil? && !duct.zone.nil? && (duct.zone.name.to_s == duct_location.name.to_s)) || - (!duct_location.nil? && !duct.loc_schedule.nil? && (duct.loc_schedule.name.to_s == duct_location.name.to_s)) + duct_locations_infos[duct_location] = [] + duct_infos.each do |duct_info| + next unless (duct_location.nil? && duct_info[:zone].nil?) || + (!duct_location.nil? && !duct_info[:zone].nil? && (duct_info[:zone].name.to_s == duct_location.name.to_s)) || + (!duct_location.nil? && !duct_info[:loc_schedule].nil? && (duct_info[:loc_schedule].name.to_s == duct_location.name.to_s)) - duct_locations_ducts[duct_location] << duct + duct_locations_infos[duct_location] << duct_info end end # Create one duct program for each duct location zone (plus an extra one for CFIS ducts from outside, if appropriate) - duct_locations_ducts.each_with_index do |(duct_location, duct_location_ducts), i| + duct_locations_infos.each_with_index do |(duct_location, duct_location_info), index| next if (not duct_location.nil?) && (duct_location.name.to_s == conditioned_zone.name.to_s) - apply_duct_location(model, spaces, hpxml_bldg, duct_location_ducts, object, i, duct_location, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, duct_sensors, ra_duct_space) + apply_ducts_for_distribution_system_location(model, spaces, hpxml_bldg, duct_location_info, object, index, duct_location, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, duct_sensors, ra_duct_space) end - - return adiabatic_const end # Creates an EMS program to calculate duct losses for a given location (e.g., vented @@ -1079,24 +1114,24 @@ def self.apply_ducts(model, spaces, hpxml_bldg, ducts, object, vent_fans, cfis_d # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param ducts [TODO] TODO - # @param object [TODO] TODO - # @param i [TODO] TODO - # @param duct_location [TODO] TODO - # @param vent_fans [TODO] TODO - # @param cfis_data [TODO] TODO - # @param fan_data [TODO] TODO - # @param duct_lk_imbals [TODO] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors - # @param duct_sensors [TODO] TODO - # @param ra_duct_space [TODO] TODO + # @param duct_infos [Array] List of duct info Hashes + # @param object [OpenStudio::Model::XXX] OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) object + # @param index [Integer] Index number of the current duct location + # @param duct_location [OpenStudio::Model::ScheduleConstant or OpenStudio::Model::ThermalZone or nil] Location of the ducts + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param cfis_data [Hash] Map with various CFIS-relative OpenStudio model objects + # @param fan_data [Hash] Map of HVAC blower fan properties => values + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @param sensors [Hash] Map of :sensor_type => OpenStudio::Model::EnergyManagementSystemSensor objects + # @param duct_sensors [Hash] Map of :sensor_type => (EMS global var, EMS sensor) + # @param ra_duct_space [OpenStudio::Model::Space] Return air duct space used in the EMS calculations # @return [nil] - def self.apply_duct_location(model, spaces, hpxml_bldg, ducts, object, i, duct_location, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, duct_sensors, ra_duct_space) + def self.apply_ducts_for_distribution_system_location(model, spaces, hpxml_bldg, duct_infos, object, index, duct_location, vent_fans, cfis_data, fan_data, duct_lk_imbals, sensors, duct_sensors, ra_duct_space) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get unit_multiplier = hpxml_bldg.building_construction.number_of_units - object_name_idx = "#{object.name}_#{i}" + object_name_idx = "#{object.name}_#{index}" ah_mfr_var, ah_mfr_sensor = duct_sensors[:ah_mfr] ah_vfr_var, ah_vfr_sensor = duct_sensors[:ah_vfr] @@ -1371,18 +1406,19 @@ def self.apply_duct_location(model, spaces, hpxml_bldg, ducts, object, i, duct_l leakage_fracs = { HPXML::DuctTypeSupply => nil, HPXML::DuctTypeReturn => nil } leakage_cfm25s = { HPXML::DuctTypeSupply => nil, HPXML::DuctTypeReturn => nil } ua_values = { HPXML::DuctTypeSupply => 0, HPXML::DuctTypeReturn => 0 } - ducts.each do |duct| - if not duct.leakage_frac.nil? - leakage_fracs[duct.side] = 0 if leakage_fracs[duct.side].nil? - leakage_fracs[duct.side] += duct.leakage_frac - elsif not duct.leakage_cfm25.nil? - leakage_cfm25s[duct.side] = 0 if leakage_cfm25s[duct.side].nil? - leakage_cfm25s[duct.side] += duct.leakage_cfm25 - elsif not duct.leakage_cfm50.nil? - leakage_cfm25s[duct.side] = 0 if leakage_cfm25s[duct.side].nil? - leakage_cfm25s[duct.side] += calc_air_leakage_at_diff_pressure(InfilPressureExponent, duct.leakage_cfm50, 50.0, 25.0) + duct_infos.each do |duct_info| + side = duct_info[:side] + if not duct_info[:leakage_frac].nil? + leakage_fracs[side] = 0 if leakage_fracs[side].nil? + leakage_fracs[side] += duct_info[:leakage_frac] + elsif not duct_info[:leakage_cfm25].nil? + leakage_cfm25s[side] = 0 if leakage_cfm25s[side].nil? + leakage_cfm25s[side] += duct_info[:leakage_cfm25] + elsif not duct_info[:leakage_cfm50].nil? + leakage_cfm25s[side] = 0 if leakage_cfm25s[side].nil? + leakage_cfm25s[side] += calc_infiltration_at_diff_pressure(duct_info[:leakage_cfm50], 50.0, 25.0) end - ua_values[duct.side] += duct.area / duct.effective_rvalue + ua_values[side] += duct_info[:area] / duct_info[:effective_rvalue] end # Check if the duct location is a vented space @@ -1656,14 +1692,14 @@ def self.apply_duct_location(model, spaces, hpxml_bldg, ducts, object, i, duct_l ) end - # TODO + # Adds infiltration to the OpenStudio garage space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param infil_values [Hash] TODO - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param infil_values [Hash] Map with various infiltration key-value pairs (SLA, infiltration volume & height, etc.) + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_garage(model, spaces, hpxml_bldg, infil_values, duct_lk_imbals) return if spaces[HPXML::LocationGarage].nil? @@ -1672,20 +1708,20 @@ def self.apply_infiltration_to_garage(model, spaces, hpxml_bldg, infil_values, d space = spaces[HPXML::LocationGarage] area = UnitConversions.convert(space.floorArea, 'm^2', 'ft^2') volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3') - hor_lk_frac = 0.4 - neutral_level = 0.5 - sla = get_infiltration_SLA_from_ACH50(ach50, InfilPressureExponent, area, volume) + hor_lk_frac = 0.4 # DOE-2 Default + neutral_level = 0.5 # DOE-2 Default + sla = get_infiltration_SLA_from_ACH50(ach50, volume / area) ela = sla * area c_w_SG, c_s_SG = calc_wind_stack_coeffs(hpxml_bldg, hor_lk_frac, neutral_level, space) apply_infiltration_to_unconditioned_space(model, space, nil, ela, c_w_SG, c_s_SG, duct_lk_imbals) end - # TODO + # Adds infiltration to the OpenStudio unconditioned basement space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_unconditioned_basement(model, spaces, duct_lk_imbals) return if spaces[HPXML::LocationBasementUnconditioned].nil? @@ -1694,31 +1730,31 @@ def self.apply_infiltration_to_unconditioned_basement(model, spaces, duct_lk_imb apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil, duct_lk_imbals) end - # TODO + # Adds infiltration to the OpenStudio vented crawlspace space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param weather [WeatherFile] Weather object containing EPW information # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_vented_crawlspace(model, spaces, weather, hpxml_bldg, duct_lk_imbals) return if spaces[HPXML::LocationCrawlspaceVented].nil? vented_crawl = hpxml_bldg.foundations.find { |foundation| foundation.foundation_type == HPXML::FoundationTypeCrawlspaceVented } space = spaces[HPXML::LocationCrawlspaceVented] - height = Geometry.get_height_of_spaces(spaces: [space]) + height = Geometry.get_space_height(space) sla = vented_crawl.vented_crawlspace_sla - ach = get_infiltration_ACH_from_SLA(sla, height, weather) + ach = get_infiltration_ACH_from_SLA(sla, height, ReferenceHeight, weather) apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil, duct_lk_imbals) end - # TODO + # Adds infiltration to the OpenStudio unvented crawlspace space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_unvented_crawlspace(model, spaces, duct_lk_imbals) return if spaces[HPXML::LocationCrawlspaceUnvented].nil? @@ -1727,22 +1763,22 @@ def self.apply_infiltration_to_unvented_crawlspace(model, spaces, duct_lk_imbals apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil, duct_lk_imbals) end - # TODO + # Adds infiltration to the OpenStudio vented attic space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param weather [WeatherFile] Weather object containing EPW information # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_vented_attic(model, spaces, weather, hpxml_bldg, hpxml_header, duct_lk_imbals) return if spaces[HPXML::LocationAtticVented].nil? vented_attic = hpxml_bldg.attics.find { |attic| attic.attic_type == HPXML::AtticTypeVented } if not vented_attic.vented_attic_sla.nil? if hpxml_header.apply_ashrae140_assumptions - vented_attic_const_ach = get_infiltration_ACH_from_SLA(vented_attic.vented_attic_sla, 8.202, weather) + vented_attic_const_ach = get_infiltration_ACH_from_SLA(vented_attic.vented_attic_sla, ReferenceHeight, ReferenceHeight, weather) else vented_attic_sla = vented_attic.vented_attic_sla end @@ -1750,15 +1786,15 @@ def self.apply_infiltration_to_vented_attic(model, spaces, weather, hpxml_bldg, if hpxml_header.apply_ashrae140_assumptions vented_attic_const_ach = vented_attic.vented_attic_ach else - vented_attic_sla = get_infiltration_SLA_from_ACH(vented_attic.vented_attic_ach, 8.202, 8.202, weather) + vented_attic_sla = get_infiltration_SLA_from_ACH(vented_attic.vented_attic_ach, ReferenceHeight, ReferenceHeight, weather) end end space = spaces[HPXML::LocationAtticVented] if not vented_attic_sla.nil? vented_attic_area = UnitConversions.convert(space.floorArea, 'm^2', 'ft^2') - hor_lk_frac = 0.75 - neutral_level = 0.5 + hor_lk_frac = 0.75 # Same as Energy Gauge USA Attic Model + neutral_level = 0.5 # DOE-2 Default sla = vented_attic_sla ela = sla * vented_attic_area c_w_SG, c_s_SG = calc_wind_stack_coeffs(hpxml_bldg, hor_lk_frac, neutral_level, space) @@ -1769,12 +1805,12 @@ def self.apply_infiltration_to_vented_attic(model, spaces, weather, hpxml_bldg, end end - # TODO + # Adds infiltration to the OpenStudio unvented attic space. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param duct_lk_imbals [TODO] TODO - # @return [TODO] TODO + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @return [nil] def self.apply_infiltration_to_unvented_attic(model, spaces, duct_lk_imbals) return if spaces[HPXML::LocationAtticUnvented].nil? @@ -1783,24 +1819,24 @@ def self.apply_infiltration_to_unvented_attic(model, spaces, duct_lk_imbals) apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil, duct_lk_imbals) end - # TODO + # Adds fan energy associated with the HPXML Local VentilationFan to an OpenStudio ElectricEquipment object. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param vent_object [TODO] TODO - # @param obj_type_name [TODO] TODO - # @param index [TODO] TODO + # @param vent_fan [HPXML::VentilationFan] The HPXML VentilationFan of interest + # @param obj_type [String] The type of ventilation fan + # @param index [Integer] Index number of the current local ventilation fan # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies - # @return [TODO] TODO - def self.apply_local_ventilation(model, spaces, vent_object, obj_type_name, index, unavailable_periods) + # @return [OpenStudio::Model::EnergyManagementSystemSensor] An EMS sensor for the newly created ventilation fan's schedule + def self.apply_local_vent_fan_power(model, spaces, vent_fan, obj_type, index, unavailable_periods) daily_sch = [0.0] * 24 - obj_name = "#{obj_type_name} #{index}" - remaining_hrs = vent_object.hours_in_operation - for hr in 1..(vent_object.hours_in_operation.ceil) + obj_name = "#{obj_type} #{index}" + remaining_hrs = vent_fan.hours_in_operation + for hr in 1..(vent_fan.hours_in_operation.ceil) if remaining_hrs >= 1 - daily_sch[(vent_object.start_hour + hr - 1) % 24] = 1.0 + daily_sch[(vent_fan.start_hour + hr - 1) % 24] = 1.0 else - daily_sch[(vent_object.start_hour + hr - 1) % 24] = remaining_hrs + daily_sch[(vent_fan.start_hour + hr - 1) % 24] = remaining_hrs end remaining_hrs -= 1 end @@ -1817,7 +1853,7 @@ def self.apply_local_ventilation(model, spaces, vent_object, obj_type_name, inde name: obj_name, end_use: Constants::ObjectTypeMechanicalVentilation, space: spaces[HPXML::LocationConditionedSpace], # no heat gain, so assign the equipment to an arbitrary space - design_level: vent_object.fan_power * vent_object.count, + design_level: vent_fan.fan_power * vent_fan.count, frac_radiant: 0, frac_latent: 0, frac_lost: 1, @@ -1827,15 +1863,15 @@ def self.apply_local_ventilation(model, spaces, vent_object, obj_type_name, inde return obj_sch_sensor end - # TODO + # Adds the clothes dryer exhaust schedule to the OpenStudio model. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param vented_dryer [TODO] TODO + # @param vented_dryer [HPXML::ClothesDryer] The HPXML Clothes Dryer of interest # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files - # @param index [TODO] TODO + # @param index [Integer] Index number of the current vented clothes dryer # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies - # @return [TODO] TODO + # @return [Array] Vented dryer schedule sensor, airflow multiplier def self.apply_dryer_exhaust(model, hpxml_header, vented_dryer, schedules_file, index, unavailable_periods) obj_name = "#{Constants::ObjectTypeClothesDryer} exhaust #{index}" @@ -1871,12 +1907,11 @@ def self.apply_dryer_exhaust(model, hpxml_header, vented_dryer, schedules_file, return obj_sch_sensor, cfm_mult end - # TODO + # Returns the sensible, latent, and apparent sensible effectiveness for each ERV/HRV. # - # @param vent_mech_fans [TODO] TODO - # @return [TODO] TODO + # @param vent_mech_fans [Array] List of HPXML Mechanical VentilationFans of type ERV/HRV + # @return [Hash] Map of HPXML VentilationFan => Hash of effectiveness values def self.calc_hrv_erv_effectiveness(vent_mech_fans) - # Create the mapping between mech vent instance and the effectiveness results hrv_erv_effectiveness_map = {} p_atm = UnitConversions.convert(1.0, 'atm', 'psi') vent_mech_fans.each do |vent_mech| @@ -1968,15 +2003,15 @@ def self.calc_hrv_erv_effectiveness(vent_mech_fans) return hrv_erv_effectiveness_map end - # TODO + # Updates the EMS program to add CFIS airflow and fan energy calculations. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings - # @param infil_program [TODO] TODO - # @param vent_mech_fans [TODO] TODO - # @param cfis_data [TODO] TODO - # @param cfis_fan_actuator [TODO] TODO - # @param cfis_suppl_fan_actuator [TODO] TODO - # @param fan_data [TODO] TODO + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations + # @param vent_mech_fans [Array] List of HPXML Mechanical VentilationFans + # @param cfis_data [Hash] Map with various CFIS-relative OpenStudio model objects + # @param cfis_fan_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuator to the CFIS's ElectricEquipment object + # @param cfis_suppl_fan_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuator to the CFIS supplemental fan's ElectricEquipment object + # @param fan_data [Hash] Map of HVAC blower fan properties => values # @return [nil] def self.apply_cfis(runner, infil_program, vent_mech_fans, cfis_data, cfis_fan_actuator, cfis_suppl_fan_actuator, fan_data) infil_program.addLine("Set #{cfis_fan_actuator.name} = 0.0") @@ -2125,18 +2160,18 @@ def self.apply_cfis(runner, infil_program, vent_mech_fans, cfis_data, cfis_fan_a end end - # TODO + # Adds fan power associated with the HPXML Mechanical VentilationFan to an OpenStudio ElectricEquipment object. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param obj_name [String] Name for the OpenStudio object - # @param sup_fans [TODO] TODO - # @param exh_fans [TODO] TODO - # @param bal_fans [TODO] TODO - # @param erv_hrv_fans [TODO] TODO + # @param sup_fans [Array] List of supply-only HPXML Mechanical VentilationFans + # @param exh_fans [Array] List of exhaust-only HPXML MechanicalVentilationFans + # @param bal_fans [Array] List of balanced HPXML MechanicalVentilationFans without heat/energy recovery + # @param erv_hrv_fans [Array] List of balanced HPXML MechanicalVentilationFans with heat/energy recovery # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies - # @return [TODO] TODO - def self.add_ee_for_vent_fan_power(model, spaces, obj_name, sup_fans = [], exh_fans = [], bal_fans = [], erv_hrv_fans = [], unavailable_periods = []) + # @return [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuator for the newly created ElectricEquipment object + def self.add_mech_vent_fan_power(model, spaces, obj_name, sup_fans = [], exh_fans = [], bal_fans = [], erv_hrv_fans = [], unavailable_periods = []) # Calculate fan heat fraction # 1.0: Fan heat does not enter space (e.g., exhaust) # 0.0: Fan heat does enter space (e.g., supply) @@ -2191,14 +2226,14 @@ def self.add_ee_for_vent_fan_power(model, spaces, obj_name, sup_fans = [], exh_f return equip_actuator end - # TODO + # Initialize EMS program and actuators for mechanical ventilation calculations. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param program [TODO] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors - # @return [TODO] TODO - def self.setup_mech_vent_vars_actuators(model, spaces, program, sensors) + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects + # @return [Array] EMS actuators for sensible and latent loads + def self.initialize_mech_vent(model, spaces, infil_program, sensors) conditioned_space = spaces[HPXML::LocationConditionedSpace] # Actuators for mech vent fan @@ -2240,42 +2275,56 @@ def self.setup_mech_vent_vars_actuators(model, spaces, program, sensors) comp_type_and_control: EPlus::EMSActuatorOtherEquipmentPower ) - program.addLine("Set #{fan_sens_load_actuator.name} = 0.0") - program.addLine("Set #{fan_lat_load_actuator.name} = 0.0") + infil_program.addLine("Set #{fan_sens_load_actuator.name} = 0.0") + infil_program.addLine("Set #{fan_lat_load_actuator.name} = 0.0") # Air property at inlet nodes on both sides - program.addLine("Set OASupInPb = #{sensors[:pbar].name}") # oa barometric pressure - program.addLine("Set OASupInTemp = #{sensors[:t_out].name}") # oa db temperature - program.addLine("Set OASupInW = #{sensors[:w_out].name}") # oa humidity ratio - program.addLine('Set OASupRho = (@RhoAirFnPbTdbW OASupInPb OASupInTemp OASupInW)') - program.addLine('Set OASupCp = (@CpAirFnW OASupInW)') - program.addLine('Set OASupInEnth = (@HFnTdbW OASupInTemp OASupInW)') - - program.addLine("Set ZoneTemp = #{sensors[:t_in].name}") # zone air temperature - program.addLine("Set ZoneW = #{sensors[:w_in].name}") # zone air humidity ratio - program.addLine('Set ZoneCp = (@CpAirFnW ZoneW)') - program.addLine('Set ZoneAirEnth = (@HFnTdbW ZoneTemp ZoneW)') + infil_program.addLine("Set OASupInPb = #{sensors[:pbar].name}") # oa barometric pressure + infil_program.addLine("Set OASupInTemp = #{sensors[:t_out].name}") # oa db temperature + infil_program.addLine("Set OASupInW = #{sensors[:w_out].name}") # oa humidity ratio + infil_program.addLine('Set OASupRho = (@RhoAirFnPbTdbW OASupInPb OASupInTemp OASupInW)') + infil_program.addLine('Set OASupCp = (@CpAirFnW OASupInW)') + infil_program.addLine('Set OASupInEnth = (@HFnTdbW OASupInTemp OASupInW)') + + infil_program.addLine("Set ZoneTemp = #{sensors[:t_in].name}") # zone air temperature + infil_program.addLine("Set ZoneW = #{sensors[:w_in].name}") # zone air humidity ratio + infil_program.addLine('Set ZoneCp = (@CpAirFnW ZoneW)') + infil_program.addLine('Set ZoneAirEnth = (@HFnTdbW ZoneTemp ZoneW)') return fan_sens_load_actuator, fan_lat_load_actuator end - # TODO + # Updates the infiltration EMS program to calculate the adjusted infiltration (total air exchange minus + # ventilation airflow), which is assigned to the OpenStudio model. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param infil_program [TODO] TODO - # @param vent_fans [TODO] TODO - # @param duct_lk_imbals [TODO] TODO - # @param infil_flow_actuator [TODO] TODO + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param duct_lk_imbals [Array] List of duct leakage imbalance information # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files # @return [nil] - def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpxml_bldg, hpxml_header, infil_program, vent_fans, duct_lk_imbals, infil_flow_actuator, schedules_file) + def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpxml_bldg, hpxml_header, infil_program, vent_fans, duct_lk_imbals, schedules_file) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get + infil_flow = Model.add_infiltration_flow_rate( + model, + name: "#{Constants::ObjectTypeInfiltration} flow", + space: spaces[HPXML::LocationConditionedSpace], + ach: nil + ) + + infil_flow_actuator = Model.add_ems_actuator( + name: "#{infil_flow.name} act", + model_object: infil_flow, + comp_type_and_control: EPlus::EMSActuatorZoneInfiltrationFlowRate + ) + infil_flow.additionalProperties.setFeature('ObjectType', Constants::ObjectTypeInfiltration) + # Average in-unit CFMs (include recirculation from in unit CFMs for shared systems) sup_cfm_tot = vent_fans[:mech_supply].map { |vent_mech| vent_mech.average_unit_flow_rate }.sum(0.0) exh_cfm_tot = vent_fans[:mech_exhaust].map { |vent_mech| vent_mech.average_unit_flow_rate }.sum(0.0) @@ -2287,7 +2336,7 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx vent_fans[:kitchen].each_with_index do |vent_kitchen, index| # Electricity impact vent_kitchen_unavailable_periods = Schedule.get_unavailable_periods(runner, SchedulesFile::Columns[:KitchenFan].name, hpxml_header.unavailable_periods) - obj_sch_sensor = apply_local_ventilation(model, spaces, vent_kitchen, Constants::ObjectTypeMechanicalVentilationRangeFan, index, vent_kitchen_unavailable_periods) + obj_sch_sensor = apply_local_vent_fan_power(model, spaces, vent_kitchen, Constants::ObjectTypeMechanicalVentilationRangeFan, index, vent_kitchen_unavailable_periods) next unless cooking_range_in_cond_space # Infiltration impact @@ -2298,7 +2347,8 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx vent_fans[:bath].each_with_index do |vent_bath, index| # Electricity impact vent_bath_unavailable_periods = Schedule.get_unavailable_periods(runner, SchedulesFile::Columns[:BathFan].name, hpxml_header.unavailable_periods) - obj_sch_sensor = apply_local_ventilation(model, spaces, vent_bath, Constants::ObjectTypeMechanicalVentilationBathFan, index, vent_bath_unavailable_periods) + obj_sch_sensor = apply_local_vent_fan_power(model, spaces, vent_bath, Constants::ObjectTypeMechanicalVentilationBathFan, index, vent_bath_unavailable_periods) + # Infiltration impact infil_program.addLine("Set Qbath = Qbath + #{UnitConversions.convert(vent_bath.flow_rate * vent_bath.count, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}") end @@ -2387,17 +2437,17 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx infil_program.addLine("Set #{q_fan_var.name} = Qfan") end - # TODO + # Updates the infiltration EMS program to calculate the mechanical ventilation sensible/latent loads. # - # @param infil_program [TODO] TODO - # @param vent_mech_erv_hrv_tot [TODO] TODO - # @param hrv_erv_effectiveness_map [TODO] TODO - # @param fan_sens_load_actuator [TODO] TODO - # @param fan_lat_load_actuator [TODO] TODO - # @param q_var [TODO] TODO - # @param preconditioned [TODO] TODO + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations + # @param vent_mech_fans [Array] List of HPXML Mechanical VentilationFans of type ERV/HRV + # @param hrv_erv_effectiveness_map [Hash] Map of HPXML VentilationFan => Hash of effectiveness values + # @param fan_sens_load_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuators for sensible load + # @param fan_lat_load_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuators for latent load + # @param q_var [String] Name of the EMS variable with the mechanical ventilation airflow rate + # @param preconditioned [Boolean] Whether loads are being calculated for a pre-heating/pre-cooling ventilation system # @return [nil] - def self.calculate_fan_loads(infil_program, vent_mech_erv_hrv_tot, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, q_var, preconditioned = false) + def self.calculate_fan_loads(infil_program, vent_mech_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, q_var, preconditioned = false) # Variables for combined effectiveness infil_program.addLine('Set Effectiveness_Sens = 0.0') infil_program.addLine('Set Effectiveness_Lat = 0.0') @@ -2407,12 +2457,12 @@ def self.calculate_fan_loads(infil_program, vent_mech_erv_hrv_tot, hrv_erv_effec infil_program.addLine("Set Fan_MFR = #{q_var} * OASupRho") infil_program.addLine('Set ZoneInEnth = OASupInEnth') infil_program.addLine('Set ZoneInTemp = OASupInTemp') - if not vent_mech_erv_hrv_tot.empty? + if not vent_mech_fans.empty? # ERV/HRV EMS load model # E+ ERV model is using standard density for MFR calculation, caused discrepancy with other system types. # Therefore ERV is modeled within EMS infiltration program infil_program.addLine("If #{q_var} > 0") - vent_mech_erv_hrv_tot.each do |vent_fan| + vent_mech_fans.each do |vent_fan| sens_eff = hrv_erv_effectiveness_map[vent_fan][:vent_mech_sens_eff] lat_eff = hrv_erv_effectiveness_map[vent_fan][:vent_mech_lat_eff] avg_oa_m3s = UnitConversions.convert(vent_fan.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4) @@ -2443,18 +2493,19 @@ def self.calculate_fan_loads(infil_program, vent_mech_erv_hrv_tot, hrv_erv_effec end end - # TODO + # Updates the infiltration EMS program to calculate the mechanical ventilation preconditioning sensible/ + # latent loads and energy consumption. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param infil_program [TODO] TODO - # @param vent_fans [TODO] TODO - # @param hrv_erv_effectiveness_map [TODO] TODO - # @param fan_sens_load_actuator [TODO] TODO - # @param fan_lat_load_actuator [TODO] TODO - # @param clg_ssn_sensor [TODO] TODO + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param hrv_erv_effectiveness_map [Hash] Map of HPXML VentilationFan => Hash of effectiveness values + # @param fan_sens_load_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuators for sensible load + # @param fan_lat_load_actuator [OpenStudio::Model::EnergyManagementSystemActuator] EMS actuators for latent load + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects # @return [nil] - def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, clg_ssn_sensor) + def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, sensors) conditioned_space = spaces[HPXML::LocationConditionedSpace] conditioned_zone = conditioned_space.thermalZone.get @@ -2479,7 +2530,7 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er infil_program.addLine("Set ClgStp = #{clg_stp_sensor.name}") # cooling thermostat setpoint end vent_fans[:mech_preheat].each_with_index do |f_preheat, i| - infil_program.addLine("If (OASupInTemp < HtgStp) && (#{clg_ssn_sensor.name} < 1)") + infil_program.addLine("If (OASupInTemp < HtgStp) && (#{sensors[:clg_ssn].name} < 1)") cnt = model.getOtherEquipments.count { |e| e.endUseSubcategory.start_with? Constants::ObjectTypeMechanicalVentilationPreheating } # Ensure unique meter for each preheating system other_equip = Model.add_other_equipment( @@ -2504,11 +2555,11 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er infil_program.addLine(" Set Qpreheat = #{UnitConversions.convert(f_preheat.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)}") if [HPXML::MechVentTypeERV, HPXML::MechVentTypeHRV].include? f_preheat.fan_type - vent_mech_erv_hrv_tot = [f_preheat] + vent_mech_fans = [f_preheat] else - vent_mech_erv_hrv_tot = [] + vent_mech_fans = [] end - calculate_fan_loads(infil_program, vent_mech_erv_hrv_tot, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qpreheat', true) + calculate_fan_loads(infil_program, vent_mech_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qpreheat', true) infil_program.addLine(' If ZoneInTemp < HtgStp') infil_program.addLine(' Set FanSensToSpt = Fan_MFR * ZoneCp * (ZoneInTemp - HtgStp)') @@ -2524,7 +2575,7 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er infil_program.addLine("Set #{htg_energy_actuator.name} = PreHeatingWatt / #{f_preheat.preheating_efficiency_cop}") end vent_fans[:mech_precool].each_with_index do |f_precool, i| - infil_program.addLine("If (OASupInTemp > ClgStp) && (#{clg_ssn_sensor.name} > 0)") + infil_program.addLine("If (OASupInTemp > ClgStp) && (#{sensors[:clg_ssn].name} > 0)") cnt = model.getOtherEquipments.count { |e| e.endUseSubcategory.start_with? Constants::ObjectTypeMechanicalVentilationPrecooling } # Ensure unique meter for each precooling system other_equip = Model.add_other_equipment( @@ -2549,11 +2600,11 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er infil_program.addLine(" Set Qprecool = #{UnitConversions.convert(f_precool.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)}") if [HPXML::MechVentTypeERV, HPXML::MechVentTypeHRV].include? f_precool.fan_type - vent_mech_erv_hrv_tot = [f_precool] + vent_mech_fans = [f_precool] else - vent_mech_erv_hrv_tot = [] + vent_mech_fans = [] end - calculate_fan_loads(infil_program, vent_mech_erv_hrv_tot, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qprecool', true) + calculate_fan_loads(infil_program, vent_mech_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qprecool', true) infil_program.addLine(' If ZoneInTemp > ClgStp') infil_program.addLine(' Set FanSensToSpt = Fan_MFR * ZoneCp * (ZoneInTemp - ClgStp)') @@ -2570,7 +2621,7 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er end end - # TODO + # Adds infiltration and ventilation fans to the OpenStudio conditioned space. # # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param model [OpenStudio::Model::Model] OpenStudio Model object @@ -2578,17 +2629,16 @@ def self.calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_er # @param weather [WeatherFile] Weather object containing EPW information # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param vent_fans [TODO] TODO - # @param infil_values [Hash] TODO - # @param clg_ssn_sensor [TODO] TODO + # @param vent_fans [Hash] Map of vent fan types => list of HPXML VentilationFans + # @param infil_values [Hash] Map with various infiltration key-value pairs (SLA, infiltration volume & height, etc.) # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files - # @param duct_lk_imbals [TODO] TODO - # @param cfis_data [TODO] TODO - # @param fan_data [TODO] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors + # @param duct_lk_imbals [Array] List of duct leakage imbalance information + # @param cfis_data [Hash] Map with various CFIS-relative OpenStudio model objects + # @param fan_data [Hash] Map of HVAC blower fan properties => values + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects # @return [nil] def self.apply_infiltration_ventilation_to_conditioned(runner, model, spaces, weather, hpxml_bldg, hpxml_header, vent_fans, infil_values, - clg_ssn_sensor, schedules_file, duct_lk_imbals, cfis_data, fan_data, sensors) + schedules_file, duct_lk_imbals, cfis_data, fan_data, sensors) # Categorize fans into different types vent_fans[:mech_preheat] = vent_fans[:mech].select { |vent_mech| (not vent_mech.preheating_efficiency_cop.nil?) } vent_fans[:mech_precool] = vent_fans[:mech].select { |vent_mech| (not vent_mech.precooling_efficiency_cop.nil?) } @@ -2600,39 +2650,22 @@ def self.apply_infiltration_ventilation_to_conditioned(runner, model, spaces, we # Non-CFIS fan power house_fan_unavailable_periods = Schedule.get_unavailable_periods(runner, SchedulesFile::Columns[:HouseFan].name, hpxml_header.unavailable_periods) - add_ee_for_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFan, - vent_fans[:mech_supply], vent_fans[:mech_exhaust], vent_fans[:mech_balanced], vent_fans[:mech_erv_hrv], house_fan_unavailable_periods) + add_mech_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFan, + vent_fans[:mech_supply], vent_fans[:mech_exhaust], vent_fans[:mech_balanced], vent_fans[:mech_erv_hrv], house_fan_unavailable_periods) # CFIS ventilation mode fan power - cfis_fan_actuator = add_ee_for_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFanCFIS) # Fan heat enters space + cfis_fan_actuator = add_mech_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFanCFIS) # Fan heat enters space # CFIS ventilation mode supplemental fan power if not vent_fans[:cfis_suppl].empty? vent_mech_cfis_suppl_sup_tot = vent_fans[:cfis_suppl].select { |vent_mech| vent_mech.fan_type == HPXML::MechVentTypeSupply } vent_mech_cfis_suppl_exh_tot = vent_fans[:cfis_suppl].select { |vent_mech| vent_mech.fan_type == HPXML::MechVentTypeExhaust } - cfis_suppl_fan_actuator = add_ee_for_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFanCFISSupplFan, - vent_mech_cfis_suppl_sup_tot, vent_mech_cfis_suppl_exh_tot) + cfis_suppl_fan_actuator = add_mech_vent_fan_power(model, spaces, Constants::ObjectTypeMechanicalVentilationHouseFanCFISSupplFan, + vent_mech_cfis_suppl_sup_tot, vent_mech_cfis_suppl_exh_tot) else cfis_suppl_fan_actuator = nil end - # Calculate effectiveness for all ERV/HRV and store results in a hash - hrv_erv_effectiveness_map = calc_hrv_erv_effectiveness(vent_fans[:mech_erv_hrv]) - - infil_flow = Model.add_infiltration_flow_rate( - model, - name: "#{Constants::ObjectTypeInfiltration} flow", - space: spaces[HPXML::LocationConditionedSpace], - ach: nil - ) - - infil_flow_actuator = Model.add_ems_actuator( - name: "#{infil_flow.name} act", - model_object: infil_flow, - comp_type_and_control: EPlus::EMSActuatorZoneInfiltrationFlowRate - ) - infil_flow.additionalProperties.setFeature('ObjectType', Constants::ObjectTypeInfiltration) - # Conditioned Space Infiltration Calculation/Program infil_program = Model.add_ems_program( model, @@ -2644,14 +2677,13 @@ def self.apply_infiltration_ventilation_to_conditioned(runner, model, spaces, we apply_infiltration_to_conditioned(spaces, hpxml_bldg, hpxml_header, infil_program, weather, infil_values, sensors) # Common variable and load actuators across multiple mech vent calculations, create only once - fan_sens_load_actuator, fan_lat_load_actuator = setup_mech_vent_vars_actuators(model, spaces, infil_program, sensors) + fan_sens_load_actuator, fan_lat_load_actuator = initialize_mech_vent(model, spaces, infil_program, sensors) # Apply CFIS apply_cfis(runner, infil_program, vent_fans[:mech_cfis], cfis_data, cfis_fan_actuator, cfis_suppl_fan_actuator, fan_data) - # Calculate combined air exchange (infiltration and mechanical ventilation) - apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpxml_bldg, hpxml_header, infil_program, vent_fans, duct_lk_imbals, - infil_flow_actuator, schedules_file) + # Calculate adjusted infiltration (infiltration adjusted by mechanical ventilation) + apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpxml_bldg, hpxml_header, infil_program, vent_fans, duct_lk_imbals, schedules_file) # Address load of Qfan (Qload) # Qload as variable for tracking outdoor air flow rate, excluding recirculation @@ -2663,10 +2695,12 @@ def self.apply_infiltration_ventilation_to_conditioned(runner, model, spaces, we # Subtract recirculation air flow rate from Qfan, only come from supply side as exhaust is not allowed to have recirculation infil_program.addLine("Set Qload = Qload - #{UnitConversions.convert(recirc_flow_rate, 'cfm', 'm^3/s').round(4)}") end - calculate_fan_loads(infil_program, vent_fans[:mech_erv_hrv], hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qload') - # Address preconditioning - calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, clg_ssn_sensor) + # Calculate effectiveness for all ERV/HRV and store results in a hash + hrv_erv_effectiveness_map = calc_hrv_erv_effectiveness(vent_fans[:mech_erv_hrv]) + + calculate_fan_loads(infil_program, vent_fans[:mech_erv_hrv], hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, 'Qload') + calculate_precond_loads(model, spaces, infil_program, vent_fans, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, sensors) Model.add_ems_program_calling_manager( model, @@ -2676,17 +2710,19 @@ def self.apply_infiltration_ventilation_to_conditioned(runner, model, spaces, we ) end - # TODO + # Updates the infiltration EMS program with calculations for the conditioned space. Uses the Alberta Air + # Infiltration Model version 2 (AIM-2), also known as the ASHRAE Enhanced model. # # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) - # @param infil_program [TODO] TODO + # @param infil_program [OpenStudio::Model::EnergyManagementSystemProgram] EMS program for the infiltration calculations # @param weather [WeatherFile] Weather object containing EPW information - # @param infil_values [Hash] TODO - # @param sensors [Hash] Map of :sensor_types => EMS sensors + # @param infil_values [Hash] Map with various infiltration key-value pairs (SLA, infiltration volume & height, etc.) + # @param sensors [Hash] Map of :sensor_types => OpenStudio::Model::EnergyManagementSystemSensor objects + # @param n_i [Double] Infiltration test flow exponent # @return [nil] - def self.apply_infiltration_to_conditioned(spaces, hpxml_bldg, hpxml_header, infil_program, weather, infil_values, sensors) + def self.apply_infiltration_to_conditioned(spaces, hpxml_bldg, hpxml_header, infil_program, weather, infil_values, sensors, n_i = InfilPressureExponent) site_ap = hpxml_bldg.site.additional_properties if hpxml_header.apply_ashrae140_assumptions @@ -2702,10 +2738,10 @@ def self.apply_infiltration_to_conditioned(spaces, hpxml_bldg, hpxml_header, inf p_atm = UnitConversions.convert(Psychrometrics.Pstd_fZ(hpxml_bldg.elevation), 'psi', 'atm') outside_air_density = UnitConversions.convert(p_atm, 'atm', 'Btu/ft^3') / (Gas.Air.r * UnitConversions.convert(weather.data.AnnualAvgDrybulb, 'F', 'R')) - n_i = InfilPressureExponent + sla = get_infiltration_SLA_from_ACH50(ach50, infil_values[:avg_ceil_height]) + cfa = hpxml_bldg.building_construction.conditioned_floor_area - conditioned_sla = get_infiltration_SLA_from_ACH50(ach50, n_i, cfa, infil_values[:volume]) # Calculate SLA - a_o = conditioned_sla * cfa # Effective Leakage Area (ft2) + a_o = sla * cfa # Effective Leakage Area (ft2) # Flow Coefficient (cfm/inH2O^n) (based on ASHRAE HoF) inf_conv_factor = 776.25 # [ft/min]/[inH2O^(1/2)*ft^(3/2)/lbm^(1/2)] @@ -2800,18 +2836,18 @@ def self.apply_infiltration_to_conditioned(spaces, hpxml_bldg, hpxml_header, inf end end - # TODO + # Returns wind and stack coefficients for the Sherman-Grimsrud (ASHRAE Basic Model) infiltration algorithm. # # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param hor_lk_frac [TODO] TODO - # @param neutral_level [TODO] TODO + # @param hor_lk_frac [Double] Fraction of leakage that is in the floor and ceiling + # @param neutral_level [Double] Fraction of space height at which the indoor-outdoor pressure difference due to stack effect is zero # @param space [OpenStudio::Model::Space] an OpenStudio::Model::Space object - # @param space_height [TODO] TODO - # @return [TODO] TODO + # @param space_height [Double] Height of the space (ft) + # @return [Array] Wind and stack coefficients def self.calc_wind_stack_coeffs(hpxml_bldg, hor_lk_frac, neutral_level, space, space_height = nil) site_ap = hpxml_bldg.site.additional_properties if space_height.nil? - space_height = Geometry.get_height_of_spaces(spaces: [space]) + space_height = Geometry.get_space_height(space) end coord_z = Geometry.get_z_origin_for_zone(space.thermalZone.get) f_t_SG = site_ap.site_terrain_multiplier * ((space_height + coord_z) / 32.8)**site_ap.site_terrain_exponent / (site_ap.terrain_multiplier * (site_ap.height / 32.8)**site_ap.terrain_exponent) @@ -2824,111 +2860,110 @@ def self.calc_wind_stack_coeffs(hpxml_bldg, hor_lk_frac, neutral_level, space, s # Returns infiltration normalized leakage given SLA. # - # @param sla [TODO] TODO - # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft2) - # @return [TODO] TODO + # Source: ANSI/RESNET/ICC 301-2022 Addendum C Appendix C2.2 Eq. 1 + # + # @param sla [Double] Specific leakage area + # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft) + # @return [Double] Normalized leakage def self.get_infiltration_NL_from_SLA(sla, infil_height) - return 1000.0 * sla * (infil_height / 8.202)**0.4 + return 1000.0 * sla * (infil_height / ReferenceHeight)**0.4 end # Returns the infiltration annual average ACH given a SLA. # - # @param sla [TODO] TODO - # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft2) + # Source: ANSI/RESNET/ICC 301-2022 Addendum C Appendix C2.2 Eq. 6 + # + # @param sla [Double] Specific leakage area + # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft) + # @param infil_avg_ceil_height [Double] Average floor to ceiling height (ft) # @param weather [WeatherFile] Weather object containing EPW information - # @return [TODO] TODO - def self.get_infiltration_ACH_from_SLA(sla, infil_height, weather) - # Equation from RESNET 380-2016 Equation 9 + # @return [Double] Annual average air changes per hour + def self.get_infiltration_ACH_from_SLA(sla, infil_height, infil_avg_ceil_height, weather) norm_leakage = get_infiltration_NL_from_SLA(sla, infil_height) - - # Equation from ASHRAE 136-1993 - return norm_leakage * weather.data.WSF + return norm_leakage * weather.data.WSF * ReferenceHeight / infil_avg_ceil_height end # Returns the infiltration SLA given an annual average ACH. # - # @param ach [TODO] TODO - # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft2) - # @param avg_ceiling_height [Double] Average floor to ceiling height within conditioned space (ft2) + # Source: ANSI/RESNET/ICC 301-2022 Addendum C Appendix C2.2 Eq. 5 + # + # @param ach [Double] Annual average air changes per hour + # @param infil_height [Double] Vertical distance between the lowest and highest above-grade points within the pressure boundary, per ASHRAE 62.2 (ft) + # @param infil_avg_ceil_height [Double] Average floor to ceiling height (ft) # @param weather [WeatherFile] Weather object containing EPW information - # @return [TODO] TODO - def self.get_infiltration_SLA_from_ACH(ach, infil_height, avg_ceiling_height, weather) - return ach * (avg_ceiling_height / 8.202) / (weather.data.WSF * 1000 * (infil_height / 8.202)**0.4) + # @return [Double] Specific leakage area + def self.get_infiltration_SLA_from_ACH(ach, infil_height, infil_avg_ceil_height, weather) + return ach * (infil_avg_ceil_height / ReferenceHeight) / (1000.0 * weather.data.WSF * (infil_height / ReferenceHeight)**0.4) end # Returns the infiltration SLA given a ACH50. # - # @param ach50 [TODO] TODO - # @param n_i [TODO] TODO - # @param floor_area [TODO] TODO - # @param volume [TODO] TODO - # @return [TODO] TODO - def self.get_infiltration_SLA_from_ACH50(ach50, n_i, floor_area, volume) - return ((ach50 * 0.283316 * 4.0**n_i * volume) / (floor_area * UnitConversions.convert(1.0, 'ft^2', 'in^2') * 50.0**n_i * 60.0)) + # Source: ANSI/RESNET/ICC 301-2022 Addendum C Appendix C2.2 Eq. 16 + # + # @param ach50 [Double] Air changes per hour at 50 Pa + # @param infil_avg_ceil_height [Double] Average floor to ceiling height (ft) + # @param n_i [Double] Infiltration test flow exponent + # @return [Double] Specific leakage area + def self.get_infiltration_SLA_from_ACH50(ach50, infil_avg_ceil_height, n_i = InfilPressureExponent) + return ((ach50 * 0.283316 * 4.0**n_i) / (50.0**n_i * 60.0 * UnitConversions.convert(1.0, 'ft^2', 'in^2') / infil_avg_ceil_height)) end # Returns the infiltration ACH50 given a SLA. # - # @param sla [TODO] TODO - # @param n_i [TODO] TODO - # @param floor_area [TODO] TODO - # @param volume [TODO] TODO - # @return [TODO] TODO - def self.get_infiltration_ACH50_from_SLA(sla, n_i, floor_area, volume) - return ((sla * floor_area * UnitConversions.convert(1.0, 'ft^2', 'in^2') * 50.0**n_i * 60.0) / (0.283316 * 4.0**n_i * volume)) - end - - # Returns the effective annual average infiltration rate in cfm. + # Source: ANSI/RESNET/ICC 301-2022 Addendum C Appendix C2.2 Eq. 15 # - # @param nl [TODO] TODO - # @param weather [WeatherFile] Weather object containing EPW information - # @param cfa [Double] Conditioned floor area in the dwelling unit (ft2) - # @return [TODO] TODO - def self.get_infiltration_Qinf_from_NL(nl, weather, cfa) - return nl * weather.data.WSF * cfa * 8.202 / 60.0 + # @param sla [Double] Specific leakage area + # @param infil_avg_ceil_height [Double] Average floor to ceiling height (ft) + # @param n_i [Double] Infiltration test flow exponent + # @return [Double] Air changes per hour at 50 Pa + def self.get_infiltration_ACH50_from_SLA(sla, infil_avg_ceil_height, n_i = InfilPressureExponent) + return sla / (0.283316 * 4.0**n_i) * (50.0**n_i * 60.0 * UnitConversions.convert(1.0, 'ft^2', 'in^2') / infil_avg_ceil_height) end - # TODO + # Returns the building infiltration at a different pressure (e.g., CFM50 -> CFM25). # - # @param q_old [TODO] TODO - # @param p_old [TODO] TODO - # @param p_new [TODO] TODO - # @return [TODO] TODO - def self.calc_duct_leakage_at_diff_pressure(q_old, p_old, p_new) - return q_old * (p_new / p_old)**0.6 # Derived from Equation C-1 (Annex C), p34, ASHRAE Standard 152-2004. + # @param q_old [Double] Original infiltration value + # @param p_old [Double] Original infiltration pressure + # @param p_new [Double] Desired infiltration pressure + # @param n_i [Double] Infiltration test flow exponent + # @return [Double] Infiltration value at the desired pressure + def self.calc_infiltration_at_diff_pressure(q_old, p_old, p_new, n_i = InfilPressureExponent) + return q_old * (p_new / p_old)**n_i end - # TODO + # Returns Qinf, the effective annual average infiltration rate, used to calculate + # the mechanical ventilation fan airflow rate (Qfan) needed to meet the ASHRAE 62.2 + # total air exchange rate (Qtot). # - # @param n_i [TODO] TODO - # @param q_old [TODO] TODO - # @param p_old [TODO] TODO - # @param p_new [TODO] TODO - # @return [TODO] TODO - def self.calc_air_leakage_at_diff_pressure(n_i, q_old, p_old, p_new) - return q_old * (p_new / p_old)**n_i + # @param nl [Double] Normalized leakage + # @param weather [WeatherFile] Weather object containing EPW information + # @param cfa [Double] Conditioned floor area in the dwelling unit (ft2) + # @return [Double] Infiltration airflow rate (cfm) + def self.get_mech_vent_qinf_cfm(nl, weather, cfa) + return nl * weather.data.WSF * cfa * ReferenceHeight / 60.0 end - # Returns Qtot cfm per ASHRAE 62.2. + # Returns Qtot, the total required air exchange rate, per ASHRAE 62.2. # # @param nbeds [Integer] Number of bedrooms in the dwelling unit # @param cfa [Double] Conditioned floor area in the dwelling unit (ft2) - # @return [TODO] TODO + # @return [Double] Total airflow rate (cfm) def self.get_mech_vent_qtot_cfm(nbeds, cfa) return (nbeds + 1.0) * 7.5 + 0.03 * cfa end - # TODO + # Returns Qfan, the mechanical ventilation fan airflow rate needed in conjunction with + # the infiltration rate (Qinf) to meet the ASHRAE 62.2 total air exchange rate (Qtot). # - # @param q_tot [TODO] TODO - # @param q_inf [TODO] TODO - # @param is_balanced [TODO] TODO - # @param frac_imbal [TODO] TODO - # @param a_ext [TODO] TODO + # @param q_tot [Double] Total airflow rate (cfm) + # @param q_inf [Double] Infiltration airflow rate (cfm) + # @param is_balanced [Double] Whether the mechanical ventilation fan is balanced (supply airflow equal to exhaust airflow) + # @param frac_imbal [Double] The fraction of total mechanical ventilation airflow that is imbalanced + # @param a_ext [Double] Ratio of exterior envelope area to total envelope area for SFA/MF units # @param unit_type [String] Type of dwelling unit (HXPML::ResidentialTypeXXX) # @param eri_version [String] Version of the ANSI/RESNET/ICC 301 Standard to use for equations/assumptions - # @param hours_in_operation [TODO] TODO - # @return [TODO] TODO + # @param hours_in_operation [Double] Hours/day that the fan is operating + # @return [Double] Mechanical ventilation fan airflow rate (cfm) def self.get_mech_vent_qfan_cfm(q_tot, q_inf, is_balanced, frac_imbal, a_ext, unit_type, eri_version, hours_in_operation) q_inf_eff = q_inf * a_ext if Constants::ERIVersions.index(eri_version) >= Constants::ERIVersions.index('2022') @@ -2970,59 +3005,20 @@ def self.get_mech_vent_qfan_cfm(q_tot, q_inf, is_balanced, frac_imbal, a_ext, un return [q_fan, 0.0].max end - # TODO - # - # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit - # @param airloop_map [Hash] Map of HPXML System ID => OpenStudio AirLoopHVAC (or ZoneHVACFourPipeFanCoil or ZoneHVACBaseboardConvectiveWater) objects - # @return [TODO] TODO - def self.create_duct_systems(model, spaces, hpxml_bldg, airloop_map) - duct_systems = {} - hpxml_bldg.hvac_distributions.each do |hvac_distribution| - next unless hvac_distribution.distribution_system_type == HPXML::HVACDistributionTypeAir - - air_ducts = create_ducts(model, hvac_distribution, spaces) - next if air_ducts.empty? - - # Connect AirLoopHVACs to ducts - added_ducts = false - hvac_distribution.hvac_systems.each do |hvac_system| - next if airloop_map[hvac_system.id].nil? - - object = airloop_map[hvac_system.id] - if duct_systems[air_ducts].nil? - duct_systems[air_ducts] = object - added_ducts = true - elsif duct_systems[air_ducts] != object - # Multiple air loops associated with this duct system, treat - # as separate duct systems. - air_ducts2 = create_ducts(model, hvac_distribution, spaces) - duct_systems[air_ducts2] = object - added_ducts = true - end - end - if not added_ducts - fail 'Unexpected error adding ducts to model.' - end - end - return duct_systems - end - - # TODO + # Creates an array of duct info hashes for the given HPXML HVAC Distribution system. # # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param hvac_distribution [HPXML::HVACDistribution] HPXML HVAC Distribution object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects - # @return [Array] list of initialized Duct class objects from the airflow resource file - def self.create_ducts(model, hvac_distribution, spaces) - air_ducts = [] + # @return [Array] List of duct info Hashes + def self.create_duct_infos(model, hvac_distribution, spaces) + duct_infos = [] # Duct leakage (supply/return => [value, units]) leakage_to_outside = { HPXML::DuctTypeSupply => [0.0, nil], HPXML::DuctTypeReturn => [0.0, nil] } hvac_distribution.duct_leakage_measurements.each do |duct_leakage_measurement| - next unless [HPXML::UnitsCFM25, HPXML::UnitsCFM50, HPXML::UnitsPercent].include?(duct_leakage_measurement.duct_leakage_units) && (duct_leakage_measurement.duct_leakage_total_or_to_outside == 'to outside') + next unless [HPXML::UnitsCFM25, HPXML::UnitsCFM50, HPXML::UnitsPercent].include?(duct_leakage_measurement.duct_leakage_units) && (duct_leakage_measurement.duct_leakage_total_or_to_outside == HPXML::DuctLeakageToOutside) next if duct_leakage_measurement.duct_type.nil? leakage_to_outside[duct_leakage_measurement.duct_type] = [duct_leakage_measurement.duct_leakage_value, duct_leakage_measurement.duct_leakage_units] @@ -3039,7 +3035,7 @@ def self.create_ducts(model, hvac_distribution, spaces) total_unconditioned_duct_area[ducts.duct_type] += ducts.duct_surface_area * ducts.duct_surface_area_multiplier end - # Create duct objects + # Create duct hashes hvac_distribution.ducts.each do |ducts| next if HPXML::conditioned_locations_this_unit.include? ducts.duct_location next if ducts.duct_type.nil? @@ -3062,18 +3058,21 @@ def self.create_ducts(model, hvac_distribution, spaces) fail "#{ducts.duct_type.capitalize} ducts exist but leakage was not specified for distribution system '#{hvac_distribution.id}'." end - air_ducts << Duct.new(ducts.duct_type, duct_loc_space, duct_loc_schedule, duct_leakage_frac, duct_leakage_cfm25, duct_leakage_cfm50, - ducts.duct_surface_area * ducts.duct_surface_area_multiplier, ducts.duct_effective_r_value, ducts.duct_buried_insulation_level) + duct_infos << { side: ducts.duct_type, + loc_space: duct_loc_space, + loc_schedule: duct_loc_schedule, + leakage_frac: duct_leakage_frac, + leakage_cfm25: duct_leakage_cfm25, + leakage_cfm50: duct_leakage_cfm50, + area: ducts.duct_surface_area * ducts.duct_surface_area_multiplier, + effective_rvalue: ducts.duct_effective_r_value, + buried_level: ducts.duct_buried_insulation_level } end # If all ducts are in conditioned space, model leakage as going to outside [HPXML::DuctTypeSupply, HPXML::DuctTypeReturn].each do |duct_side| next unless (leakage_to_outside[duct_side][0] > 0) && (total_unconditioned_duct_area[duct_side] == 0) - duct_area = 0.0 - duct_effective_r_value = 99 # arbitrary - duct_loc_space = nil # outside - duct_loc_schedule = nil # outside duct_leakage_value = leakage_to_outside[duct_side][0] duct_leakage_units = leakage_to_outside[duct_side][1] @@ -3088,35 +3087,17 @@ def self.create_ducts(model, hvac_distribution, spaces) fail "#{duct_side.capitalize} ducts exist but leakage was not specified for distribution system '#{hvac_distribution.id}'." end - air_ducts << Duct.new(duct_side, duct_loc_space, duct_loc_schedule, duct_leakage_frac, duct_leakage_cfm25, duct_leakage_cfm50, duct_area, - duct_effective_r_value, HPXML::DuctBuriedInsulationNone) + duct_infos << { side: duct_side, + loc_space: nil, # outside + loc_schedule: nil, # outside + leakage_frac: duct_leakage_frac, + leakage_cfm25: duct_leakage_cfm25, + leakage_cfm50: duct_leakage_cfm50, + area: 0.0, + effective_rvalue: 99, # Arbitrary + buried_level: HPXML::DuctBuriedInsulationNone } end - return air_ducts - end -end - -# TODO -class Duct - # @param side [String] Whether the duct is on the supply or return side (HPXML::DuctTypeXXX) - # @param loc_space [OpenStudio::Model::Space] The space where duct is located - # @param loc_schedule [OpenStudio::Model::ScheduleConstant] The temperature schedule for where the duct is located, if not in a space - # @param leakage_frac [TODO] TODO - # @param leakage_cfm25 [TODO] TODO - # @param leakage_cfm50 [TODO] TODO - # @param area [TODO] TODO - # @param effective_rvalue [Double] Duct effective R-value, accounting for air films and adjusted for round/buried ducts (hr-ft2-F/Btu) - # @param buried_level [String] How deeply the duct is buried in loose-fill insulation (HPXML::DuctBuriedInsulationXXX) - def initialize(side, loc_space, loc_schedule, leakage_frac, leakage_cfm25, leakage_cfm50, area, effective_rvalue, buried_level) - @side = side - @loc_space = loc_space - @loc_schedule = loc_schedule - @leakage_frac = leakage_frac - @leakage_cfm25 = leakage_cfm25 - @leakage_cfm50 = leakage_cfm50 - @area = area - @effective_rvalue = effective_rvalue - @buried_level = buried_level + return duct_infos end - attr_accessor(:side, :loc_space, :loc_schedule, :leakage_frac, :leakage_cfm25, :leakage_cfm50, :area, :effective_rvalue, :zone, :location, :buried_level) end diff --git a/HPXMLtoOpenStudio/resources/defaults.rb b/HPXMLtoOpenStudio/resources/defaults.rb index 696b614e4d..9fbd257932 100644 --- a/HPXMLtoOpenStudio/resources/defaults.rb +++ b/HPXMLtoOpenStudio/resources/defaults.rb @@ -870,8 +870,9 @@ def self.apply_building_construction(hpxml_header, hpxml_bldg) cond_crawl_volume = hpxml_bldg.inferred_conditioned_crawlspace_volume() nbeds = hpxml_bldg.building_construction.number_of_bedrooms if hpxml_bldg.building_construction.average_ceiling_height.nil? - # ASHRAE 62.2 default for average floor to ceiling height - hpxml_bldg.building_construction.average_ceiling_height = 8.2 + # Note: We do not try to calculate it from CFA & ConditionedBuildingVolume since + # that is not a reliable assumption if there is a, e.g., conditioned crawlspace. + hpxml_bldg.building_construction.average_ceiling_height = 8.2 # ASHRAE 62.2 default hpxml_bldg.building_construction.average_ceiling_height_isdefaulted = true end if hpxml_bldg.building_construction.conditioned_building_volume.nil? @@ -4725,7 +4726,7 @@ def self.get_mech_vent_flow_rate_for_vent_fan(hpxml_bldg, vent_fan, weather, eri unit_type = hpxml_bldg.building_construction.residential_facility_type nl = Airflow.get_infiltration_NL_from_SLA(infil_values[:sla], infil_values[:height]) - q_inf = Airflow.get_infiltration_Qinf_from_NL(nl, weather, cfa) + q_inf = Airflow.get_mech_vent_qinf_cfm(nl, weather, cfa) q_tot = Airflow.get_mech_vent_qtot_cfm(nbeds, cfa) if vent_fan.is_balanced is_balanced, frac_imbal = true, 0.0 @@ -4768,7 +4769,7 @@ def self.get_mech_vent_fan_efficiency(vent_fan) # @param cfa [Double] Conditioned floor area in the dwelling unit (ft2) # @param ncfl_ag [Double] Number of conditioned floors above grade # @param year_built [Integer] Year the dwelling unit is built - # @param avg_ceiling_height [Double] Average floor to ceiling height within conditioned space (ft2) + # @param avg_ceiling_height [Double] Average floor to ceiling height within conditioned space (ft) # @param infil_volume [Double] Volume of space most impacted by the blower door test (ft3) # @param iecc_cz [String] IECC climate zone # @param fnd_type_fracs [Hash] Map of foundation type => area fraction @@ -4875,7 +4876,9 @@ def self.get_infiltration_ach50(cfa, ncfl_ag, year_built, avg_ceiling_height, in # Specific Leakage Area sla = nl / (1000.0 * ncfl_ag**0.3) - ach50 = Airflow.get_infiltration_ACH50_from_SLA(sla, 0.65, cfa, infil_volume) + # ACH50 + infil_avg_ceil_height = infil_volume / cfa + ach50 = Airflow.get_infiltration_ACH50_from_SLA(sla, infil_avg_ceil_height) return ach50 end diff --git a/HPXMLtoOpenStudio/resources/geometry.rb b/HPXMLtoOpenStudio/resources/geometry.rb index cc284bbddf..2e887da364 100644 --- a/HPXMLtoOpenStudio/resources/geometry.rb +++ b/HPXMLtoOpenStudio/resources/geometry.rb @@ -1064,7 +1064,7 @@ def self.get_foundation_and_walls_top(hpxml_bldg) # # @param surface [OpenStudio::Model::Surface] an OpenStudio::Model::Surface object # @return [Double] the max z value minus the min x value - def self.get_surface_height(surface:) + def self.get_surface_height(surface) zvalues = get_surface_z_values(surfaceArray: [surface]) zrange = zvalues.max - zvalues.min return zrange @@ -1123,7 +1123,7 @@ def self.get_surface_z_values(surfaceArray:) # # @param nbeds [Integer] Number of bedrooms in the dwelling unit # @return [Double] Number of occupants in the dwelling unit - def self.get_occupancy_default_num(nbeds:) + def self.get_occupancy_default_num(nbeds) return Float(nbeds) # Per ANSI/RESNET/ICC 301 for an asset calculation end @@ -1967,26 +1967,20 @@ def self.get_space_from_location(location, spaces) return spaces[location] end - # Calculates space heights as the max z coordinate minus the min z coordinate. + # Calculates space height as the max z coordinate minus the min z coordinate. # - # @param spaces [Array] array of OpenStudio::Model::Space objects - # @return [Double] max z coordinate minus min z coordinate for a collection of spaces (ft) - def self.get_height_of_spaces(spaces:) - minzs = [] - maxzs = [] - spaces.each do |space| - zvalues = get_surface_z_values(surfaceArray: space.surfaces) - minzs << zvalues.min + UnitConversions.convert(space.zOrigin, 'm', 'ft') - maxzs << zvalues.max + UnitConversions.convert(space.zOrigin, 'm', 'ft') - end - return maxzs.max - minzs.min + # @param space [OpenStudio::Model::Space] an OpenStudio::Model::Space object + # @return [Double] space height (ft) + def self.get_space_height(space) + zvalues = get_surface_z_values(surfaceArray: space.surfaces) + return zvalues.max - zvalues.min end # Determine the length of an OpenStudio Surface by calculating the maximum difference between x and y coordinates. # # @param surface [OpenStudio::Model::Surface] an OpenStudio::Model::Surface object # @return [Double] length of the OpenStudio Surface (ft) - def self.get_surface_length(surface:) + def self.get_surface_length(surface) xvalues = get_surface_x_values(surfaceArray: [surface]) yvalues = get_surface_y_values(surfaceArray: [surface]) xrange = xvalues.max - xvalues.min diff --git a/HPXMLtoOpenStudio/resources/hvac.rb b/HPXMLtoOpenStudio/resources/hvac.rb index 61b3939cce..0095bd575a 100644 --- a/HPXMLtoOpenStudio/resources/hvac.rb +++ b/HPXMLtoOpenStudio/resources/hvac.rb @@ -92,7 +92,7 @@ def self.apply_cooling_system(runner, model, weather, spaces, hpxml_bldg, hpxml_ case cooling_system.cooling_system_type when HPXML::HVACTypeCentralAirConditioner, HPXML::HVACTypeRoomAirConditioner, HPXML::HVACTypeMiniSplitAirConditioner, HPXML::HVACTypePTAC - airloop_map[sys_id] = apply_air_source_hvac_systems(model, runner, weather, cooling_system, heating_system, hvac_sequential_load_fracs, + airloop_map[sys_id] = apply_air_source_hvac_systems(runner, model, weather, cooling_system, heating_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods, schedules_file, hpxml_bldg, hpxml_header) when HPXML::HVACTypeEvaporativeCooler airloop_map[sys_id] = apply_evaporative_cooler(model, cooling_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods, @@ -152,10 +152,10 @@ def self.apply_heating_system(runner, model, weather, spaces, hpxml_bldg, hpxml_ sys_id = heating_system.id case heating_system.heating_system_type when HPXML::HVACTypeFurnace - airloop_map[sys_id] = apply_air_source_hvac_systems(model, runner, weather, nil, heating_system, hvac_sequential_load_fracs, + airloop_map[sys_id] = apply_air_source_hvac_systems(runner, model, weather, nil, heating_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods, schedules_file, hpxml_bldg, hpxml_header) when HPXML::HVACTypeBoiler - airloop_map[sys_id] = apply_boiler(model, runner, heating_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods) + airloop_map[sys_id] = apply_boiler(runner, model, heating_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods) when HPXML::HVACTypeElectricResistance apply_electric_baseboard(model, heating_system, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods) when HPXML::HVACTypeStove, HPXML::HVACTypeSpaceHeater, HPXML::HVACTypeWallFurnace, @@ -215,10 +215,10 @@ def self.apply_heat_pump(runner, model, weather, spaces, hpxml_bldg, hpxml_heade airloop_map[sys_id] = apply_water_loop_to_air_heat_pump(model, heat_pump, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods) when HPXML::HVACTypeHeatPumpAirToAir, HPXML::HVACTypeHeatPumpMiniSplit, HPXML::HVACTypeHeatPumpPTHP, HPXML::HVACTypeHeatPumpRoom - airloop_map[sys_id] = apply_air_source_hvac_systems(model, runner, weather, heat_pump, heat_pump, hvac_sequential_load_fracs, + airloop_map[sys_id] = apply_air_source_hvac_systems(runner, model, weather, heat_pump, heat_pump, hvac_sequential_load_fracs, conditioned_zone, hvac_unavailable_periods, schedules_file, hpxml_bldg, hpxml_header) when HPXML::HVACTypeHeatPumpGroundToAir - airloop_map[sys_id] = apply_ground_to_air_heat_pump(model, runner, weather, heat_pump, hvac_sequential_load_fracs, + airloop_map[sys_id] = apply_ground_to_air_heat_pump(runner, model, weather, heat_pump, hvac_sequential_load_fracs, conditioned_zone, hpxml_bldg.site.ground_conductivity, hpxml_bldg.site.ground_diffusivity, hvac_unavailable_periods, hpxml_bldg.building_construction.number_of_units) end @@ -235,8 +235,8 @@ def self.apply_heat_pump(runner, model, weather, spaces, hpxml_bldg, hpxml_heade # TODO # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param weather [WeatherFile] Weather object containing EPW information # @param cooling_system [TODO] TODO # @param heating_system [TODO] TODO @@ -247,7 +247,7 @@ def self.apply_heat_pump(runner, model, weather, spaces, hpxml_bldg, hpxml_heade # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) # @return [OpenStudio::Model::AirLoopHVAC] The newly created air loop hvac object - def self.apply_air_source_hvac_systems(model, runner, weather, cooling_system, heating_system, hvac_sequential_load_fracs, + def self.apply_air_source_hvac_systems(runner, model, weather, cooling_system, heating_system, hvac_sequential_load_fracs, control_zone, hvac_unavailable_periods, schedules_file, hpxml_bldg, hpxml_header) is_heatpump = false @@ -465,7 +465,7 @@ def self.apply_air_source_hvac_systems(model, runner, weather, cooling_system, h add_supplemental_coil_ems_program(model, htg_supp_coil, control_zone, htg_coil, is_onoff_thermostat_ddb, cooling_system) - add_variable_speed_power_ems_program(model, runner, air_loop_unitary, control_zone, heating_system, cooling_system, htg_supp_coil, clg_coil, htg_coil, schedules_file) + add_variable_speed_power_ems_program(runner, model, air_loop_unitary, control_zone, heating_system, cooling_system, htg_supp_coil, clg_coil, htg_coil, schedules_file) if is_heatpump && hpxml_header.defrost_model_type == HPXML::AdvancedResearchDefrostModelTypeAdvanced apply_advanced_defrost(model, htg_coil, air_loop_unitary, control_zone.spaces[0], htg_supp_coil, cooling_system, q_dot_defrost) @@ -535,8 +535,8 @@ def self.apply_evaporative_cooler(model, cooling_system, hvac_sequential_load_fr # TODO # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param weather [WeatherFile] Weather object containing EPW information # @param heat_pump [TODO] TODO # @param hvac_sequential_load_fracs [Array] Array of daily fractions of remaining heating/cooling load to bet met by the HVAC system @@ -546,7 +546,7 @@ def self.apply_evaporative_cooler(model, cooling_system, hvac_sequential_load_fr # @param hvac_unavailable_periods [Hash] Map of htg/clg => HPXML::UnavailablePeriods for heating/cooling # @param unit_multiplier [Integer] Number of similar dwelling units # @return [OpenStudio::Model::AirLoopHVAC] The newly created air loop hvac object - def self.apply_ground_to_air_heat_pump(model, runner, weather, heat_pump, hvac_sequential_load_fracs, + def self.apply_ground_to_air_heat_pump(runner, model, weather, heat_pump, hvac_sequential_load_fracs, control_zone, ground_conductivity, ground_diffusivity, hvac_unavailable_periods, unit_multiplier) @@ -813,14 +813,14 @@ def self.apply_water_loop_to_air_heat_pump(model, heat_pump, hvac_sequential_loa # TODO # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param heating_system [TODO] TODO # @param hvac_sequential_load_fracs [Array] Array of daily fractions of remaining heating/cooling load to bet met by the HVAC system # @param control_zone [OpenStudio::Model::ThermalZone] Conditioned space thermal zone # @param hvac_unavailable_periods [Hash] Map of htg/clg => HPXML::UnavailablePeriods for heating/cooling # @return [OpenStudio::Model::ZoneHVACFourPipeFanCoil or OpenStudio::Model::ZoneHVACBaseboardConvectiveWater] The newly created zone hvac object - def self.apply_boiler(model, runner, heating_system, hvac_sequential_load_fracs, control_zone, hvac_unavailable_periods) + def self.apply_boiler(runner, model, heating_system, hvac_sequential_load_fracs, control_zone, hvac_unavailable_periods) obj_name = Constants::ObjectTypeBoiler is_condensing = false # FUTURE: Expose as input; default based on AFUE oat_reset_enabled = false @@ -1342,15 +1342,15 @@ def self.apply_ceiling_fans(runner, model, spaces, weather, hpxml_bldg, hpxml_he # Adds an HPXML HVAC Control to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param weather [WeatherFile] Weather object containing EPW information # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files # @return [Hash] Map of htg/clg => Array of 365 days with 1s during the heating/cooling season and 0s otherwise - def self.apply_setpoints(model, runner, weather, spaces, hpxml_bldg, hpxml_header, schedules_file) + def self.apply_setpoints(runner, model, weather, spaces, hpxml_bldg, hpxml_header, schedules_file) return {} if hpxml_bldg.hvac_controls.size == 0 hvac_control = hpxml_bldg.hvac_controls[0] @@ -3920,8 +3920,8 @@ def self.add_two_speed_staging_ems_program(model, unitary_system, htg_supp_coil, # Apply maximum power ratio schedule for variable speed system. # Creates EMS program to determine and control the stage that can reach the maximum power constraint. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param air_loop_unitary [OpenStudio::Model::AirLoopHVACUnitarySystem] Air loop for the HVAC system # @param control_zone [OpenStudio::Model::ThermalZone] Conditioned space thermal zone # @param heating_system [HPXML::HeatingSystem or HPXML::HeatPump] The HPXML heating system or heat pump of interest @@ -3931,7 +3931,7 @@ def self.add_two_speed_staging_ems_program(model, unitary_system, htg_supp_coil, # @param htg_coil [OpenStudio::Model::CoilHeatingDXMultiSpeed] OpenStudio MultiStage Heating Coil object # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files # @return [nil] - def self.add_variable_speed_power_ems_program(model, runner, air_loop_unitary, control_zone, heating_system, cooling_system, htg_supp_coil, clg_coil, htg_coil, schedules_file) + def self.add_variable_speed_power_ems_program(runner, model, air_loop_unitary, control_zone, heating_system, cooling_system, htg_supp_coil, clg_coil, htg_coil, schedules_file) return if schedules_file.nil? return if clg_coil.nil? && htg_coil.nil? diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index 400c639997..0d99851bd7 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -4199,7 +4199,7 @@ def self.calc_duct_leakages_cfm25(distribution_system, system_cfm) elsif m.duct_leakage_units == HPXML::UnitsCFM25 cfms[m.duct_type] += m.duct_leakage_value elsif m.duct_leakage_units == HPXML::UnitsCFM50 - cfms[m.duct_type] += Airflow.calc_air_leakage_at_diff_pressure(0.65, m.duct_leakage_value, 50.0, 25.0) + cfms[m.duct_type] += Airflow.calc_infiltration_at_diff_pressure(m.duct_leakage_value, 50.0, 25.0) end end @@ -4340,7 +4340,7 @@ def self.get_space_ua_values(mj, location, weather, hpxml_bldg) ach = vented_attic.vented_attic_ach end end - ach = Airflow.get_infiltration_ACH_from_SLA(sla, 8.202, weather) if ach.nil? + ach = Airflow.get_infiltration_ACH_from_SLA(sla, Airflow::ReferenceHeight, Airflow::ReferenceHeight, weather) if ach.nil? else # Unvented space ach = Airflow::UnventedSpaceACH end @@ -5479,7 +5479,7 @@ def self.append_detailed_output(output_format, hpxml_bldg, all_zone_loads, all_s # # @param obj [HPXML::Building or HPXML::Zone or HPXML::Space] The HPXML building, zone, or space of interest # @param additional_property_type [Symbol] Name of property on obj.additional_properties - # @return [Hash] Map of HPXML::XXX object => DetailedOutputValues object + # @return [Hash] Map of HPXML::XXX object => DetailedOutputValues object def self.get_surfaces_with_property(obj, additional_property_type) objs = (obj.surfaces + obj.subsurfaces).select { |s| s.additional_properties.respond_to?(additional_property_type) } props = {} diff --git a/HPXMLtoOpenStudio/resources/internal_gains.rb b/HPXMLtoOpenStudio/resources/internal_gains.rb index 281f2c0b81..93f3b6f7f5 100644 --- a/HPXMLtoOpenStudio/resources/internal_gains.rb +++ b/HPXMLtoOpenStudio/resources/internal_gains.rb @@ -13,7 +13,7 @@ module InternalGains # @return [nil] def self.apply_building_occupants(runner, model, hpxml_bldg, hpxml_header, spaces, schedules_file) if hpxml_bldg.building_occupancy.number_of_residents.nil? # Asset calculation - n_occ = Geometry.get_occupancy_default_num(nbeds: hpxml_bldg.building_construction.number_of_bedrooms) + n_occ = Geometry.get_occupancy_default_num(hpxml_bldg.building_construction.number_of_bedrooms) else # Operational calculation n_occ = hpxml_bldg.building_occupancy.number_of_residents end diff --git a/HPXMLtoOpenStudio/resources/meta_measure.rb b/HPXMLtoOpenStudio/resources/meta_measure.rb index 22462098e1..e3647eb04e 100644 --- a/HPXMLtoOpenStudio/resources/meta_measure.rb +++ b/HPXMLtoOpenStudio/resources/meta_measure.rb @@ -465,7 +465,7 @@ def run_measure(model, measure, argument_map, runner) return true end -# Convert contents of a Hash to single String using provided delimiter and seperator characters. +# Convert contents of a Hash to single String using provided delimiter and separator characters. # # @param hash [Hash] Map of keys to values # @param delim [String] character between each key and value diff --git a/HPXMLtoOpenStudio/resources/model.rb b/HPXMLtoOpenStudio/resources/model.rb index 946760878b..99cae6a506 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -865,10 +865,10 @@ def self.ems_friendly_name(name) # Resets the existing model if it already has objects in it. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @return [nil] - def self.reset(model, runner) + def self.reset(runner, model) handles = OpenStudio::UUIDVector.new model.objects.each do |obj| handles << obj.handle diff --git a/HPXMLtoOpenStudio/resources/waterheater.rb b/HPXMLtoOpenStudio/resources/waterheater.rb index 3d9ec8a762..b7c2d3aeaf 100644 --- a/HPXMLtoOpenStudio/resources/waterheater.rb +++ b/HPXMLtoOpenStudio/resources/waterheater.rb @@ -21,13 +21,13 @@ def self.apply_dhw_appliances(runner, model, weather, spaces, hpxml_bldg, hpxml_ hpxml_bldg.water_heating_systems.each do |dhw_system| case dhw_system.water_heater_type when HPXML::WaterHeaterTypeStorage - apply_tank(model, runner, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) + apply_tank(runner, model, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) when HPXML::WaterHeaterTypeTankless - apply_tankless(model, runner, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) + apply_tankless(runner, model, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) when HPXML::WaterHeaterTypeHeatPump - apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) + apply_hpwh(runner, model, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) when HPXML::WaterHeaterTypeCombiStorage, HPXML::WaterHeaterTypeCombiTankless - apply_combi(model, runner, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) + apply_combi(runner, model, spaces, hpxml_bldg, hpxml_header, dhw_system, schedules_file, unavailable_periods, plantloop_map) else fail "Unhandled water heater (#{dhw_system.water_heater_type})." end @@ -41,8 +41,8 @@ def self.apply_dhw_appliances(runner, model, weather, spaces, hpxml_bldg, hpxml_ # Adds a conventional storage tank water heater to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) @@ -51,7 +51,7 @@ def self.apply_dhw_appliances(runner, model, weather, spaces, hpxml_bldg, hpxml_ # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies # @param plantloop_map [Hash] Map of HPXML System ID => OpenStudio PlantLoop objects # @return [nil] - def self.apply_tank(model, runner, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) + def self.apply_tank(runner, model, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) loc_space, loc_schedule = Geometry.get_space_or_schedule_from_location(water_heating_system.location, model, spaces) unit_multiplier = hpxml_bldg.building_construction.number_of_units solar_fraction = get_water_heater_solar_fraction(water_heating_system, hpxml_bldg) @@ -60,14 +60,13 @@ def self.apply_tank(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati act_vol = calc_storage_tank_actual_vol(water_heating_system.tank_volume, water_heating_system.fuel_type) u, ua, eta_c = disaggregate_tank_losses_and_burner_efficiency(act_vol, water_heating_system, solar_fraction, hpxml_bldg.building_construction.number_of_bedrooms) - water_heater = apply_water_heater(name: Constants::ObjectTypeWaterHeater, + water_heater = apply_water_heater(runner, model, + name: Constants::ObjectTypeWaterHeater, water_heating_system: water_heating_system, act_vol: act_vol, t_set_c: t_set_c, loc_space: loc_space, loc_schedule: loc_schedule, - model: model, - runner: runner, u: u, ua: ua, eta_c: eta_c, @@ -77,15 +76,15 @@ def self.apply_tank(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati plant_loop.addSupplyBranchForComponent(water_heater) apply_ec_adj_program(model, hpxml_bldg, water_heater, loc_space, water_heating_system, unit_multiplier) - apply_desuperheater(model, runner, water_heating_system, water_heater, loc_space, loc_schedule, plant_loop, unit_multiplier) + apply_desuperheater(runner, model, water_heating_system, water_heater, loc_space, loc_schedule, plant_loop, unit_multiplier) plantloop_map[water_heating_system.id] = plant_loop end # Adds a tankless water heater to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) @@ -94,7 +93,7 @@ def self.apply_tank(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies # @param plantloop_map [Hash] Map of HPXML System ID => OpenStudio PlantLoop objects # @return [nil] - def self.apply_tankless(model, runner, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) + def self.apply_tankless(runner, model, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) loc_space, loc_schedule = Geometry.get_space_or_schedule_from_location(water_heating_system.location, model, spaces) unit_multiplier = hpxml_bldg.building_construction.number_of_units water_heating_system.heating_capacity = 100000000000.0 * unit_multiplier @@ -104,14 +103,13 @@ def self.apply_tankless(model, runner, spaces, hpxml_bldg, hpxml_header, water_h act_vol = 1.0 * unit_multiplier _u, ua, eta_c = disaggregate_tank_losses_and_burner_efficiency(act_vol, water_heating_system, solar_fraction, hpxml_bldg.building_construction.number_of_bedrooms) - water_heater = apply_water_heater(name: Constants::ObjectTypeWaterHeater, + water_heater = apply_water_heater(runner, model, + name: Constants::ObjectTypeWaterHeater, water_heating_system: water_heating_system, act_vol: act_vol, t_set_c: t_set_c, loc_space: loc_space, loc_schedule: loc_schedule, - model: model, - runner: runner, ua: ua, eta_c: eta_c, schedules_file: schedules_file, @@ -121,15 +119,15 @@ def self.apply_tankless(model, runner, spaces, hpxml_bldg, hpxml_header, water_h plant_loop.addSupplyBranchForComponent(water_heater) apply_ec_adj_program(model, hpxml_bldg, water_heater, loc_space, water_heating_system, unit_multiplier) - apply_desuperheater(model, runner, water_heating_system, water_heater, loc_space, loc_schedule, plant_loop, unit_multiplier) + apply_desuperheater(runner, model, water_heating_system, water_heater, loc_space, loc_schedule, plant_loop, unit_multiplier) plantloop_map[water_heating_system.id] = plant_loop end # Adds a heat pump water heater to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) @@ -138,7 +136,7 @@ def self.apply_tankless(model, runner, spaces, hpxml_bldg, hpxml_header, water_h # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies # @param plantloop_map [Hash] Map of HPXML System ID => OpenStudio PlantLoop objects # @return [nil] - def self.apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) + def self.apply_hpwh(runner, model, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) loc_space, loc_schedule = Geometry.get_space_or_schedule_from_location(water_heating_system.location, model, spaces) unit_multiplier = hpxml_bldg.building_construction.number_of_units obj_name = Constants::ObjectTypeWaterHeater @@ -200,13 +198,13 @@ def self.apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati max_temp = 120.0 # F # Coil:WaterHeating:AirToWaterHeatPump:Wrapped - coil = apply_hpwh_dxcoil(model, runner, water_heating_system, hpxml_bldg.elevation, obj_name, airflow_rate, unit_multiplier) + coil = apply_hpwh_dxcoil(runner, model, water_heating_system, hpxml_bldg.elevation, obj_name, airflow_rate, unit_multiplier) # WaterHeater:Stratified tank = apply_hpwh_stratified_tank(model, water_heating_system, obj_name, solar_fraction, hpwh_tamb, top_element_sp, bottom_element_sp, unit_multiplier, hpxml_bldg.building_construction.number_of_bedrooms) plant_loop.addSupplyBranchForComponent(tank) - apply_desuperheater(model, runner, water_heating_system, tank, loc_space, loc_schedule, plant_loop, unit_multiplier) + apply_desuperheater(runner, model, water_heating_system, tank, loc_space, loc_schedule, plant_loop, unit_multiplier) # Fan:SystemModel fan_power = 0.0462 # W/cfm, Based on 1st gen AO Smith HPWH, could be updated but pretty minor impact @@ -228,7 +226,7 @@ def self.apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati hpwh_zone_heat_gain_program = apply_hpwh_zone_heat_gain_program(model, obj_name, loc_space, hpwh_tamb, hpwh_rhamb, tank, coil, fan, amb_temp_sensor, amb_rh_sensors, unit_multiplier) # EMS for the HPWH control logic - hpwh_ctrl_program = apply_hpwh_control_program(model, runner, obj_name, water_heating_system, amb_temp_sensor, top_element_sp, bottom_element_sp, min_temp, max_temp, sensed_setpoint_schedule, control_setpoint_schedule, schedules_file) + hpwh_ctrl_program = apply_hpwh_control_program(runner, model, obj_name, water_heating_system, amb_temp_sensor, top_element_sp, bottom_element_sp, min_temp, max_temp, sensed_setpoint_schedule, control_setpoint_schedule, schedules_file) # ProgramCallingManagers Model.add_ems_program_calling_manager( @@ -249,8 +247,8 @@ def self.apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati # This makes the code much simpler and makes the EnergyPlus results more robust. # # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param spaces [Hash] Map of HPXML locations => OpenStudio Space objects # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @param hpxml_header [HPXML::Header] HPXML Header object (one per HPXML file) @@ -259,7 +257,7 @@ def self.apply_hpwh(model, runner, spaces, hpxml_bldg, hpxml_header, water_heati # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies # @param plantloop_map [Hash] Map of HPXML System ID => OpenStudio PlantLoop objects # @return [nil] - def self.apply_combi(model, runner, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) + def self.apply_combi(runner, model, spaces, hpxml_bldg, hpxml_header, water_heating_system, schedules_file, unavailable_periods, plantloop_map) loc_space, loc_schedule = Geometry.get_space_or_schedule_from_location(water_heating_system.location, model, spaces) unit_multiplier = hpxml_bldg.building_construction.number_of_units solar_fraction = get_water_heater_solar_fraction(water_heating_system, hpxml_bldg) @@ -288,14 +286,13 @@ def self.apply_combi(model, runner, spaces, hpxml_bldg, hpxml_header, water_heat plant_loop = add_plant_loop(model, t_set_c, hpxml_header.eri_calculation_version, unit_multiplier) # Create water heater - water_heater = apply_water_heater(name: obj_name_combi, + water_heater = apply_water_heater(runner, model, + name: obj_name_combi, water_heating_system: water_heating_system, act_vol: act_vol, t_set_c: t_set_c, loc_space: loc_space, loc_schedule: loc_schedule, - model: model, - runner: runner, ua: ua, is_combi: true, schedules_file: schedules_file, @@ -938,15 +935,15 @@ def self.apply_hpwh_wrapped_condenser(model, obj_name, coil, tank, fan, airflow_ # Adds a CoilWaterHeatingAirToWaterHeatPumpWrapped object for the HPWH to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param water_heating_system [HPXML::WaterHeatingSystem] The HPXML water heating system of interest # @param elevation [Double] Elevation of the building site (ft) # @param obj_name [String] Name for the OpenStudio object # @param airflow_rate [Double] HPWH fan airflow rate (cfm) # @param unit_multiplier [Integer] Number of similar dwelling units # @return [OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPumpWrapped] The HPWH DX coil - def self.apply_hpwh_dxcoil(model, runner, water_heating_system, elevation, obj_name, airflow_rate, unit_multiplier) + def self.apply_hpwh_dxcoil(runner, model, water_heating_system, elevation, obj_name, airflow_rate, unit_multiplier) # Curves hpwh_cap = Model.add_curve_biquadratic( model, @@ -1297,8 +1294,8 @@ def self.apply_hpwh_zone_heat_gain_program(model, obj_name, loc_space, hpwh_tamb # Adds an EMS program to control the HPWH upper and lower elements. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param obj_name [String] Name for the OpenStudio object # @param water_heating_system [HPXML::WaterHeatingSystem] The HPXML water heating system of interest # @param amb_temp_sensor [OpenStudio::Model::EnergyManagementSystemSensor] HPWH ambient temperature sensor @@ -1310,7 +1307,7 @@ def self.apply_hpwh_zone_heat_gain_program(model, obj_name, loc_space, hpwh_tamb # @param control_setpoint_schedule [OpenStudio::Model::ScheduleConstant or OpenStudio::Model::ScheduleRuleset] Setpoint temperature schedule (controlled) # @param schedules_file [SchedulesFile] SchedulesFile wrapper class instance of detailed schedule files # @return [OpenStudio::Model::EnergyManagementSystemProgram] The HPWH control program - def self.apply_hpwh_control_program(model, runner, obj_name, water_heating_system, amb_temp_sensor, hpwh_top_element_sp, hpwh_bottom_element_sp, min_temp, max_temp, sensted_setpoint_schedule, control_setpoint_schedule, schedules_file) + def self.apply_hpwh_control_program(runner, model, obj_name, water_heating_system, amb_temp_sensor, hpwh_top_element_sp, hpwh_bottom_element_sp, min_temp, max_temp, sensted_setpoint_schedule, control_setpoint_schedule, schedules_file) # Lower element is enabled if the ambient air temperature prevents the HP from running leschedoverride_actuator = Model.add_ems_actuator( name: "#{obj_name} LESchedOverride", @@ -1483,8 +1480,8 @@ def self.get_combi_boiler_and_plant_loop(model, heating_source_id) # Adds a desuperheater for the given HPXML water heating system to the OpenStudio model. # - # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param water_heating_system [HPXML::WaterHeatingSystem] The HPXML water heating system of interest # @param tank [OpenStudio::Model::WaterHeaterMixed or OpenStudio::Model::WaterHeaterStratified] The water heater tank # @param loc_space [OpenStudio::Model::Space] The space where the water heater is located @@ -1492,7 +1489,7 @@ def self.get_combi_boiler_and_plant_loop(model, heating_source_id) # @param plant_loop [OpenStudio::Model::PlantLoop] The DHW plant loop # @param unit_multiplier [Integer] Number of similar dwelling units # @return [nil] - def self.apply_desuperheater(model, runner, water_heating_system, tank, loc_space, loc_schedule, plant_loop, unit_multiplier) + def self.apply_desuperheater(runner, model, water_heating_system, tank, loc_space, loc_schedule, plant_loop, unit_multiplier) return unless water_heating_system.uses_desuperheater # Get the HVAC cooling coil for the desuperheater @@ -1525,13 +1522,12 @@ def self.apply_desuperheater(model, runner, water_heating_system, tank, loc_spac t_set_c = get_t_set_c(water_heating_system.temperature - 5.0, HPXML::WaterHeaterTypeStorage) end - storage_tank = apply_water_heater(name: storage_tank_name, + storage_tank = apply_water_heater(runner, model, + name: storage_tank_name, act_vol: storage_vol_actual, t_set_c: t_set_c, loc_space: loc_space, loc_schedule: loc_schedule, - model: model, - runner: runner, ua: assumed_ua, is_dsh_storage: true, unit_multiplier: unit_multiplier) @@ -1937,14 +1933,14 @@ def self.apply_shared_adjustment(water_heating_system, ua, nbeds) # Adds a water heater object to the OpenStudio model. # + # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings + # @param model [OpenStudio::Model::Model] OpenStudio Model object # @param name [String] Name for the OpenStudio object # @param water_heating_system [HPXML::WaterHeatingSystem] The HPXML water heating system of interest # @param act_vol [Double] Actual tank volume (gal) # @param t_set_c [Double] Water heater setpoint including deadband (C) # @param loc_space [OpenStudio::Model::Space] The space where the water heater is located # @param loc_schedule [OpenStudio::Model::ScheduleConstant] The temperature schedule, if not located in a space - # @param model [OpenStudio::Model::Model] OpenStudio Model object - # @param runner [OpenStudio::Measure::OSRunner] Object typically used to display warnings # @param u [Double] Tank loss coefficient (FIXME) # @param ua [Double] Tank loss UA factor (Btu/hr-F) # @param eta_c [Double] Burner efficiency (frac) @@ -1954,7 +1950,7 @@ def self.apply_shared_adjustment(water_heating_system, ua, nbeds) # @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies # @param unit_multiplier [Integer] Number of similar dwelling units # @return [OpenStudio::Model::WaterHeaterMixed or OpenStudio::Model::WaterHeaterStratified] Water heater object - def self.apply_water_heater(name:, water_heating_system: nil, act_vol:, t_set_c: nil, loc_space:, loc_schedule: nil, model:, runner:, u: nil, ua:, eta_c: nil, is_dsh_storage: false, is_combi: false, schedules_file: nil, unavailable_periods: [], unit_multiplier: 1.0) + def self.apply_water_heater(runner, model, name:, water_heating_system: nil, act_vol:, t_set_c: nil, loc_space:, loc_schedule: nil, u: nil, ua:, eta_c: nil, is_dsh_storage: false, is_combi: false, schedules_file: nil, unavailable_periods: [], unit_multiplier: 1.0) # storage tank doesn't require water_heating_system class argument being passed if is_dsh_storage || is_combi fuel = nil diff --git a/HPXMLtoOpenStudio/tests/test_enclosure.rb b/HPXMLtoOpenStudio/tests/test_enclosure.rb index 9d805b7fbc..c9fa7b816b 100644 --- a/HPXMLtoOpenStudio/tests/test_enclosure.rb +++ b/HPXMLtoOpenStudio/tests/test_enclosure.rb @@ -833,7 +833,7 @@ def test_foundation_properties osm_fwalls = model.getSurfaces.select { |s| s.outsideBoundaryCondition == EPlus::BoundaryConditionFoundation && s.adjacentFoundation.get == foundation && s.surfaceType == EPlus::SurfaceTypeWall } if not osm_fwalls.empty? - osm_fwalls_length = osm_fwalls.map { |s| Geometry.get_surface_length(surface: s) }.sum + osm_fwalls_length = osm_fwalls.map { |s| Geometry.get_surface_length(s) }.sum assert_in_epsilon(osm_exposed_perimeter, osm_fwalls_length, 0.01) end end @@ -860,7 +860,7 @@ def test_foundation_properties ext_fwall_int_adj_tos.each do |int_adj_to, fwalls| osm_fwalls = model.getSurfaces.select { |s| s.surfaceType == EPlus::SurfaceTypeWall && s.outsideBoundaryCondition == EPlus::BoundaryConditionFoundation && s.space.get.name.to_s.start_with?(int_adj_to) } - osm_heights = osm_fwalls.map { |s| Geometry.get_surface_height(surface: s) }.uniq.sort + osm_heights = osm_fwalls.map { |s| Geometry.get_surface_height(s) }.uniq.sort hpxml_heights = fwalls.map { |fw| fw.height }.uniq.sort assert_equal(hpxml_heights, osm_heights) diff --git a/workflow/tests/base_results/results_simulations_bills.csv b/workflow/tests/base_results/results_simulations_bills.csv index 254be846e1..4f43b2efb0 100644 --- a/workflow/tests/base_results/results_simulations_bills.csv +++ b/workflow/tests/base_results/results_simulations_bills.csv @@ -490,7 +490,7 @@ house042.xml,3720.55,144.0,1493.17,0.0,1637.17,144.0,1939.38,2083.38,0.0,0.0,0.0 house043.xml,2706.97,144.0,1114.41,0.0,1258.41,144.0,1304.56,1448.56,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house044.xml,3759.32,144.0,1620.59,0.0,1764.59,144.0,1850.73,1994.73,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house045.xml,2779.26,144.0,1310.52,0.0,1454.52,144.0,1180.74,1324.74,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -house046.xml,914.78,144.0,770.78,0.0,914.78,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +house046.xml,929.73,144.0,785.73,0.0,929.73,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house047.xml,1011.29,144.0,656.63,0.0,800.63,144.0,66.66,210.66,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house048.xml,2469.63,144.0,1487.33,0.0,1631.33,144.0,694.3,838.3,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house049.xml,1510.97,144.0,1189.95,0.0,1333.95,144.0,33.02,177.02,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/workflow/tests/base_results/results_simulations_energy.csv b/workflow/tests/base_results/results_simulations_energy.csv index bb1a5740a6..f49c3e7f86 100644 --- a/workflow/tests/base_results/results_simulations_energy.csv +++ b/workflow/tests/base_results/results_simulations_energy.csv @@ -490,7 +490,7 @@ house042.xml,233.649,233.649,39.866,39.866,193.783,0.0,0.0,0.0,0.0,0.0,0.0,3.955 house043.xml,160.105,160.105,29.754,29.754,130.351,0.0,0.0,0.0,0.0,0.0,0.0,2.499,0.0,0.0,1.986,0.048,0.0,0.0,0.0,6.558,0.213,0.514,0.093,0.0,0.0,0.0,2.124,0.0,0.0,0.447,0.338,2.514,1.529,0.0,2.116,8.775,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,110.453,0.0,19.898,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house044.xml,228.193,228.193,43.268,43.268,184.925,0.0,0.0,0.0,0.0,0.0,0.0,4.741,0.0,0.0,2.157,0.085,0.0,0.0,0.0,12.947,0.315,0.974,0.037,0.0,0.0,0.0,2.098,0.0,0.0,0.447,0.338,2.514,1.529,0.0,2.116,12.97,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,162.358,0.0,22.567,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house045.xml,152.969,152.969,34.99,34.99,117.979,0.0,0.0,0.0,0.0,0.0,0.0,2.789,0.0,0.0,2.495,0.157,0.0,0.0,0.0,9.06,0.315,0.75,1.793,0.0,0.0,0.0,2.142,0.0,0.0,0.447,0.338,2.514,1.529,0.0,2.116,8.545,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,95.526,0.0,22.453,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 -house046.xml,24.953,24.953,24.953,24.953,0.0,0.0,0.0,0.0,0.0,0.0,5.226,0.522,0.296,0.012,3.862,0.983,4.921,0.0,0.0,1.029,0.0,0.082,0.0,0.0,0.0,0.0,1.668,0.0,0.0,0.256,0.005,0.482,1.262,0.0,1.645,2.701,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +house046.xml,25.437,25.437,25.437,25.437,0.0,0.0,0.0,0.0,0.0,0.0,5.525,0.551,0.396,0.015,3.905,0.993,4.921,0.0,0.0,1.029,0.0,0.082,0.0,0.0,0.0,0.0,1.668,0.0,0.0,0.256,0.005,0.482,1.262,0.0,1.645,2.701,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house047.xml,21.267,21.267,14.887,14.887,6.38,0.0,0.0,0.0,0.0,0.0,0.0,0.141,0.0,0.0,1.115,0.001,4.484,0.0,0.0,0.92,0.0,0.463,0.182,0.0,0.0,0.0,1.337,0.0,0.0,0.256,0.111,0.738,1.262,0.0,1.645,2.229,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,6.38,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house048.xml,92.183,92.183,39.96,39.96,52.223,0.0,0.0,0.0,0.0,0.0,0.0,0.519,0.0,0.0,13.54,3.307,0.0,0.0,0.0,3.689,0.085,0.499,2.962,0.0,0.0,0.0,2.321,0.0,0.0,0.474,1.108,0.67,0.114,0.0,2.351,8.322,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,36.298,0.0,12.585,0.0,3.34,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 house049.xml,35.928,35.928,32.43,32.43,3.498,0.0,0.0,0.0,0.0,0.0,7.591,0.047,0.0,0.0,8.025,0.206,2.637,0.249,0.0,1.473,0.057,0.099,2.092,0.0,0.0,0.0,2.962,0.0,0.0,0.329,0.006,0.053,0.096,0.0,1.88,4.63,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.698,2.8,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/workflow/tests/base_results/results_simulations_hvac.csv b/workflow/tests/base_results/results_simulations_hvac.csv index d85488c244..fc3b1c3d02 100644 --- a/workflow/tests/base_results/results_simulations_hvac.csv +++ b/workflow/tests/base_results/results_simulations_hvac.csv @@ -490,7 +490,7 @@ house042.xml,-13.72,81.14,90000.0,24000.0,0.0,96150.0,0.0,17465.0,0.0,1112.0,415 house043.xml,-13.72,81.14,90000.0,30000.0,0.0,64332.0,0.0,11581.0,0.0,2533.0,29951.0,0.0,202.0,1896.0,1519.0,16650.0,0.0,0.0,16137.0,0.0,5002.0,0.0,572.0,3716.0,0.0,3.0,0.0,1109.0,436.0,0.0,3320.0,0.0,1979.0,1468.0,0.0,668.0,0.0,800.0 house044.xml,-13.72,81.14,110000.0,36000.0,0.0,82332.0,0.0,8422.0,0.0,1467.0,29628.0,1911.0,5521.0,1706.0,3592.0,30085.0,0.0,0.0,21809.0,0.0,6118.0,0.0,269.0,3897.0,368.0,208.0,0.0,2623.0,787.0,0.0,3320.0,0.0,4218.0,2005.0,0.0,1205.0,0.0,800.0 house045.xml,-13.72,81.14,70000.0,30000.0,0.0,52400.0,0.0,8558.0,455.0,472.0,24152.0,1464.0,31.0,1726.0,1367.0,14175.0,0.0,0.0,14801.0,0.0,7799.0,810.0,110.0,1109.0,193.0,0.0,0.0,999.0,461.0,0.0,3320.0,0.0,0.0,1506.0,0.0,706.0,0.0,800.0 -house046.xml,24.62,91.58,18000.0,18000.0,17065.0,16969.0,3903.0,1800.0,0.0,182.0,2847.0,0.0,0.0,0.0,1604.0,6633.0,0.0,0.0,15182.0,3716.0,2178.0,0.0,110.0,1595.0,0.0,0.0,0.0,1823.0,1399.0,0.0,2860.0,0.0,1500.0,2698.0,483.0,1815.0,0.0,400.0 +house046.xml,24.62,91.58,18000.0,18000.0,17065.0,17705.0,3915.0,1800.0,0.0,182.0,2847.0,0.0,0.0,0.0,1604.0,7357.0,0.0,0.0,15370.0,3752.0,2178.0,0.0,110.0,1595.0,0.0,0.0,0.0,1823.0,1551.0,0.0,2860.0,0.0,1500.0,2896.0,483.0,2012.0,0.0,400.0 house047.xml,19.22,86.72,20000.0,18000.0,0.0,7271.0,1053.0,1216.0,0.0,0.0,630.0,0.0,0.0,662.0,0.0,3710.0,0.0,0.0,4199.0,0.0,516.0,0.0,0.0,200.0,0.0,0.0,0.0,0.0,623.0,0.0,2860.0,0.0,0.0,1652.0,0.0,1252.0,0.0,400.0 house048.xml,25.88,98.42,63000.0,46500.0,0.0,51897.0,11933.0,4499.0,0.0,694.0,9939.0,828.0,63.0,10750.0,2249.0,7887.0,3053.0,0.0,31390.0,8097.0,4822.0,0.0,589.0,7960.0,547.0,57.0,0.0,1959.0,2188.0,1621.0,3550.0,0.0,0.0,4760.0,1126.0,1513.0,1121.0,1000.0 house049.xml,33.26,106.16,39000.0,16000.0,0.0,19031.0,0.0,5635.0,0.0,0.0,5319.0,0.0,0.0,2258.0,1357.0,3370.0,1091.0,0.0,21783.0,0.0,7246.0,0.0,0.0,6460.0,0.0,0.0,0.0,2075.0,1986.0,926.0,3090.0,0.0,0.0,-351.0,0.0,-717.0,-233.0,600.0 diff --git a/workflow/tests/base_results/results_simulations_loads.csv b/workflow/tests/base_results/results_simulations_loads.csv index fe4c569ae5..9f37e4981e 100644 --- a/workflow/tests/base_results/results_simulations_loads.csv +++ b/workflow/tests/base_results/results_simulations_loads.csv @@ -490,7 +490,7 @@ house042.xml,166.493,0.0,2.669,15.624,3.233,0.0,0.0,0.0,9.57,40.359,4.072,43.231 house043.xml,105.198,0.0,2.711,13.078,2.212,0.0,0.0,0.0,3.281,23.375,2.29,33.537,5.516,22.767,-9.072,0.0,0.0,0.546,9.368,-0.464,28.874,0.0,1.565,0.0,0.0,-12.765,-4.981,0.0,0.045,-0.631,-0.071,1.591,-0.366,-1.957,5.38,0.0,0.0,-0.068,-4.072,-0.463,-1.631,-1.145,-0.147,0.0,0.0,4.777,1.578 house044.xml,150.834,0.0,3.568,13.078,4.457,0.0,0.0,4.444,7.14,36.897,9.136,18.805,2.746,18.064,-10.877,0.0,0.0,12.729,14.544,-0.833,62.079,0.0,1.433,0.0,0.0,-16.533,-10.044,0.328,0.532,-0.927,-0.047,0.781,-0.111,-0.545,6.275,0.0,0.0,-1.082,-5.313,-0.831,-2.595,-1.076,-0.096,0.0,0.0,5.488,2.904 house045.xml,88.743,0.0,4.07,13.078,4.37,0.0,0.0,3.462,3.112,14.812,2.231,32.392,1.105,17.316,-10.653,0.942,-0.367,0.083,11.762,-0.164,20.335,0.0,10.619,0.0,0.0,-12.456,-6.546,-0.035,-0.053,-1.298,-0.144,0.665,-0.11,-1.445,8.123,-0.086,0.447,-0.015,-4.871,-0.163,-1.476,-2.104,-1.531,0.0,0.0,5.769,2.514 -house046.xml,13.653,0.308,13.406,4.302,0.617,0.0,0.0,0.0,2.558,3.98,0.0,0.0,0.321,2.09,-1.792,0.0,0.0,-0.157,0.0,-0.355,7.442,0.0,0.367,0.0,2.816,-3.146,-0.451,0.0,1.258,2.647,0.0,0.0,0.016,0.943,2.846,0.0,0.0,-0.156,0.0,-0.354,-0.533,-0.212,0.01,0.0,1.898,4.551,0.578 +house046.xml,14.657,0.412,13.426,4.302,0.617,0.0,0.0,0.0,2.564,3.988,0.0,0.0,0.323,2.102,-1.809,0.0,0.0,-0.162,0.0,-0.362,8.318,0.0,0.369,0.0,2.985,-3.185,-0.456,0.0,1.275,2.666,0.0,0.0,0.019,0.962,2.83,0.0,0.0,-0.161,0.0,-0.361,-0.538,-0.204,0.012,0.0,1.918,4.512,0.573 house047.xml,6.201,0.0,1.663,4.201,0.0,0.0,0.0,0.0,-0.001,0.813,0.132,0.0,0.0,1.833,-0.735,0.0,0.0,0.0,1.414,-0.024,1.4,0.0,5.485,0.0,0.209,-3.763,-0.585,0.0,-0.001,0.138,0.038,0.0,0.0,0.153,0.808,0.0,0.0,0.0,-1.09,-0.024,-0.107,-0.369,-0.857,0.0,0.0,2.646,0.335 house048.xml,29.549,0.0,54.328,7.248,2.653,0.0,0.0,1.024,2.697,12.338,0.0,0.0,0.802,4.251,-2.964,0.0,0.0,0.057,1.907,-0.636,7.198,0.0,4.184,0.0,6.454,-6.406,-1.469,1.353,1.08,9.591,0.0,0.0,0.56,4.493,5.009,0.0,0.0,0.074,9.865,-0.624,0.686,-0.667,1.868,0.0,7.946,11.088,2.22 house049.xml,6.786,0.0,32.274,4.261,1.297,0.0,0.0,0.0,1.65,4.852,0.0,0.0,0.0,5.015,-7.631,0.0,0.0,0.0,1.135,-0.162,3.019,0.0,2.213,0.0,0.0,-2.871,-0.606,0.0,1.938,7.58,0.0,0.0,0.0,4.657,10.233,0.0,0.0,0.0,3.177,-0.162,0.353,-3.495,1.022,0.0,0.0,6.291,0.867 diff --git a/workflow/tests/base_results/results_simulations_misc.csv b/workflow/tests/base_results/results_simulations_misc.csv index 601a70d93b..1d16c5a629 100644 --- a/workflow/tests/base_results/results_simulations_misc.csv +++ b/workflow/tests/base_results/results_simulations_misc.csv @@ -490,7 +490,7 @@ house042.xml,0.0,0.0,1857.6,1860.1,14896.3,4852.9,2709.1,3579.6,3579.6,88.345,19 house043.xml,0.0,0.0,1610.8,1575.4,12168.1,4288.2,1955.0,3113.4,3113.4,54.697,14.375,0.0 house044.xml,0.0,0.0,1610.8,1575.4,12168.1,4288.2,3129.5,4163.0,4163.0,80.998,20.38,0.0 house045.xml,0.0,0.0,1610.8,1575.4,12168.1,4288.2,2332.6,3409.7,3409.7,47.006,14.537,0.0 -house046.xml,0.0,1.0,596.8,442.4,5543.4,2208.5,3753.7,2457.2,3753.7,15.844,13.647,0.0 +house046.xml,0.0,1.0,596.8,442.4,5543.4,2208.6,3978.5,2457.6,3978.5,16.611,13.589,0.0 house047.xml,0.0,0.0,251.7,442.4,5772.6,1524.2,872.9,999.0,999.0,4.775,2.725,0.0 house048.xml,0.0,0.0,130.3,818.0,11617.7,3495.1,1511.5,5412.9,5412.9,42.888,34.051,0.0 house049.xml,0.0,206.0,728.6,567.6,7439.3,922.6,4339.6,2852.8,4339.6,12.15,15.823,0.0