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