From 08725231dade06bddbe31818e14ba0e2faaec60b Mon Sep 17 00:00:00 2001 From: Yueyue Zhou Date: Wed, 20 Nov 2024 19:56:26 -0700 Subject: [PATCH] change the approach to create surfaces at unit models to avoid component load error (not sum to total delivered) due to missing surfaces. Increase heating capacity to avoid unmet load --- HPXMLtoOpenStudio/measure.xml | 8 +- HPXMLtoOpenStudio/resources/geometry.rb | 88 +++++++++++-------- HPXMLtoOpenStudio/resources/model.rb | 22 ++--- workflow/hpxml_inputs.json | 3 +- ...dgtype-mf-whole-building-common-spaces.xml | 6 +- workflow/tests/util.rb | 2 +- 6 files changed, 71 insertions(+), 58 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index a75c7de113..8474766d15 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - 2bb7d5cd-06e3-406b-9eb0-96dfe7e2c6d2 - 2024-11-19T01:12:59Z + d26078da-8691-4396-af39-a5cb563e1407 + 2024-11-21T02:54:20Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -345,7 +345,7 @@ geometry.rb rb resource - 945B3E0D + 3DBAF60E hotwater_appliances.rb @@ -447,7 +447,7 @@ model.rb rb resource - ECCE7792 + ED1985F0 output.rb diff --git a/HPXMLtoOpenStudio/resources/geometry.rb b/HPXMLtoOpenStudio/resources/geometry.rb index 1080a1404c..5b19369f40 100644 --- a/HPXMLtoOpenStudio/resources/geometry.rb +++ b/HPXMLtoOpenStudio/resources/geometry.rb @@ -138,8 +138,11 @@ def self.apply_walls(runner, model, spaces, hpxml_bldg, hpxml_header) _walls_top, foundation_top = get_foundation_and_walls_top(hpxml_bldg) hpxml_bldg.walls.each do |wall| - store_adjacent_surface_ids_to_space(get_interior_space(model, spaces, HPXML::LocationConditionedSpace, hpxml_bldg), wall) - next if wall.sameas_id + hpxml_id = wall.id + if wall.sameas_id + hpxml_sameas_id = wall.sameas_id + wall = wall.sameas + end next if wall.net_area < 1.0 # skip modeling net surface area for surfaces comprised entirely of subsurface area if wall.azimuth.nil? @@ -151,6 +154,9 @@ def self.apply_walls(runner, model, spaces, hpxml_bldg, hpxml_header) else azimuths = [wall.azimuth] end + if hpxml_sameas_id + azimuths = azimuths.map { |a| (a + 180) % 360 } + end surfaces = [] @@ -173,15 +179,17 @@ def self.apply_walls(runner, model, spaces, hpxml_bldg, hpxml_header) end surface.setSurfaceType(EPlus::SurfaceTypeWall) set_surface_interior(model, spaces, surface, wall, hpxml_bldg) - set_surface_exterior(model, spaces, surface, wall, hpxml_bldg) + set_surface_exterior(model, spaces, surface, wall, hpxml_bldg) if hpxml_sameas_id.nil? if wall.is_interior surface.setSunExposure(EPlus::SurfaceSunExposureNo) surface.setWindExposure(EPlus::SurfaceWindExposureNo) end - surface.additionalProperties.setFeature('hpxmlID', wall.id) + surface.additionalProperties.setFeature('hpxmlID', hpxml_id) + surface.additionalProperties.setFeature('hpxmlSameasID', hpxml_sameas_id) unless hpxml_sameas_id.nil? end next if surfaces.empty? + next unless hpxml_sameas_id.nil? # Apply construction # The code below constructs a reasonable wall construction based on the @@ -223,8 +231,11 @@ def self.apply_rim_joists(runner, model, spaces, hpxml_bldg) _walls_top, foundation_top = get_foundation_and_walls_top(hpxml_bldg) hpxml_bldg.rim_joists.each do |rim_joist| - store_adjacent_surface_ids_to_space(get_interior_space(model, spaces, HPXML::LocationConditionedSpace, hpxml_bldg), rim_joist) - next if rim_joist.sameas_id + hpxml_id = rim_joist.id + if rim_joist.sameas_id + hpxml_sameas_id = rim_joist.sameas_id + rim_joist = rim_joist.sameas + end if rim_joist.azimuth.nil? if rim_joist.is_exterior @@ -235,6 +246,9 @@ def self.apply_rim_joists(runner, model, spaces, hpxml_bldg) else azimuths = [rim_joist.azimuth] end + if hpxml_sameas_id + azimuths = azimuths.map { |a| (a + 180) % 360 } + end surfaces = [] @@ -257,14 +271,17 @@ def self.apply_rim_joists(runner, model, spaces, hpxml_bldg) end surface.setSurfaceType(EPlus::SurfaceTypeWall) set_surface_interior(model, spaces, surface, rim_joist, hpxml_bldg) - set_surface_exterior(model, spaces, surface, rim_joist, hpxml_bldg) + set_surface_exterior(model, spaces, surface, rim_joist, hpxml_bldg) if hpxml_sameas_id.nil? if rim_joist.is_interior surface.setSunExposure(EPlus::SurfaceSunExposureNo) surface.setWindExposure(EPlus::SurfaceWindExposureNo) end - surface.additionalProperties.setFeature('hpxmlID', rim_joist.id) + surface.additionalProperties.setFeature('hpxmlID', hpxml_id) + surface.additionalProperties.setFeature('hpxmlSameasID', hpxml_sameas_id) unless hpxml_sameas_id.nil? end + next unless hpxml_sameas_id.nil? + # Apply construction inside_film = Material.AirFilmVertical @@ -310,8 +327,14 @@ def self.apply_floors(runner, model, spaces, hpxml_bldg, hpxml_header) walls_top, foundation_top = get_foundation_and_walls_top(hpxml_bldg) hpxml_bldg.floors.each do |floor| - store_adjacent_surface_ids_to_space(get_interior_space(model, spaces, HPXML::LocationConditionedSpace, hpxml_bldg), floor) - next if floor.sameas_id + hpxml_id = floor.id + if floor.sameas_id + is_ceiling = !floor.sameas.is_ceiling + hpxml_sameas_id = floor.sameas_id + floor = floor.sameas + else + is_ceiling = floor.is_ceiling + end next if floor.net_area < 1.0 # skip modeling net surface area for surfaces comprised entirely of subsurface area area = floor.net_area @@ -323,7 +346,7 @@ def self.apply_floors(runner, model, spaces, hpxml_bldg, hpxml_header) z_origin = foundation_top end - if floor.is_ceiling + if is_ceiling vertices = create_ceiling_vertices(length, width, z_origin, default_azimuths) surface = OpenStudio::Model::Surface.new(vertices, model) surface.additionalProperties.setFeature('SurfaceType', 'Ceiling') @@ -334,7 +357,7 @@ def self.apply_floors(runner, model, spaces, hpxml_bldg, hpxml_header) end surface.additionalProperties.setFeature('Tilt', 0.0) set_surface_interior(model, spaces, surface, floor, hpxml_bldg) - set_surface_exterior(model, spaces, surface, floor, hpxml_bldg) + set_surface_exterior(model, spaces, surface, floor, hpxml_bldg) if hpxml_sameas_id.nil? surface.setName(floor.id) if floor.is_interior surface.setSunExposure(EPlus::SurfaceSunExposureNo) @@ -348,7 +371,9 @@ def self.apply_floors(runner, model, spaces, hpxml_bldg, hpxml_header) end end end - surface.additionalProperties.setFeature('hpxmlID', floor.id) + surface.additionalProperties.setFeature('hpxmlID', hpxml_id) + surface.additionalProperties.setFeature('hpxmlSameasID', hpxml_sameas_id) unless hpxml_sameas_id.nil? + next unless hpxml_sameas_id.nil? # Apply construction @@ -457,10 +482,13 @@ def self.apply_foundation_walls_slabs(runner, model, spaces, weather, hpxml_bldg # calculate heat flow between two zones through the ground. int_fnd_walls = hpxml_bldg.foundation_walls.select { |fw| (fw.is_interior && fw.interior_adjacent_to == foundation_type) || fw.sameas_id } int_fnd_walls.each do |fnd_wall| - next unless (fnd_wall.is_interior || fw.sameas.is_interior) + hpxml_id = fnd_wall.id + if fnd_wall.sameas_id + hpxml_sameas_id = fnd_wall.sameas_id + fnd_wall = fnd_wall.sameas + end - store_adjacent_surface_ids_to_space(get_interior_space(model, spaces, HPXML::LocationConditionedSpace, hpxml_bldg), fnd_wall) - next if fnd_wall.sameas_id + next unless fnd_wall.is_interior ag_height = fnd_wall.height - fnd_wall.depth_below_grade ag_net_area = fnd_wall.net_area * ag_height / fnd_wall.height @@ -473,6 +501,9 @@ def self.apply_foundation_walls_slabs(runner, model, spaces, weather, hpxml_bldg else azimuth = fnd_wall.azimuth end + if hpxml_sameas_id + azimuth = (azimuth + 180) % 360 + end vertices = create_wall_vertices(length, ag_height, z_origin, azimuth) surface = OpenStudio::Model::Surface.new(vertices, model) @@ -483,10 +514,13 @@ def self.apply_foundation_walls_slabs(runner, model, spaces, weather, hpxml_bldg surface.setName(fnd_wall.id) surface.setSurfaceType(EPlus::SurfaceTypeWall) set_surface_interior(model, spaces, surface, fnd_wall, hpxml_bldg) - set_surface_exterior(model, spaces, surface, fnd_wall, hpxml_bldg) + set_surface_exterior(model, spaces, surface, fnd_wall, hpxml_bldg) if hpxml_sameas_id.nil? surface.setSunExposure(EPlus::SurfaceSunExposureNo) surface.setWindExposure(EPlus::SurfaceWindExposureNo) - surface.additionalProperties.setFeature('hpxmlID', fnd_wall.id) + surface.additionalProperties.setFeature('hpxmlID', hpxml_id) + surface.additionalProperties.setFeature('hpxmlSameasID', hpxml_sameas_id) unless hpxml_sameas_id.nil? + + next unless hpxml_sameas_id.nil? # Apply construction @@ -1142,24 +1176,6 @@ def self.get_occupancy_default_num(nbeds:) return Float(nbeds) # Per ANSI 301 for an asset calculation end - # Store the adjacent surface ID in Space additional properties, to process after merging unit models - # - # @param space [OpenStudio::Model::Space] the OpenStudio::Model::Space object of the dwelling unit - # @param hpxml_surface [HPXML::Wall or HPXML::Floor or HPXML::RimJoist or HPXML::FoundationWall] any HPXML surface - # @return [nil] - def self.store_adjacent_surface_ids_to_space(space, hpxml_surface) - return if hpxml_surface.sameas_id.nil? - - # Store adjacent surface id in the space to process later - adjacent_surface_ids = space.additionalProperties.getFeatureAsString('adjacentSurfaceIDs') - if adjacent_surface_ids.is_initialized - adjacent_surface_ids_new = [adjacent_surface_ids, hpxml_surface.sameas_id].join(', ') - space.additionalProperties.setFeature('adjacentSurfaceIDs', adjacent_surface_ids_new) - else - space.additionalProperties.setFeature('adjacentSurfaceIDs', hpxml_surface.sameas_id) - end - end - # Creates a space and zone based on contents of spaces and value of location. # Sets a "dwelling unit multiplier" equal to the number of similar units represented. # diff --git a/HPXMLtoOpenStudio/resources/model.rb b/HPXMLtoOpenStudio/resources/model.rb index ca48cc006f..55983fbafb 100644 --- a/HPXMLtoOpenStudio/resources/model.rb +++ b/HPXMLtoOpenStudio/resources/model.rb @@ -942,19 +942,15 @@ def self.merge_unit_models(model, hpxml_osm_map) end model_objects.each do |obj| - next unless obj.to_Space.is_initialized - - space = obj.to_Space.get - adjacent_surface_id_str = space.additionalProperties.getFeatureAsString('adjacentSurfaceIDs') - next unless adjacent_surface_id_str.is_initialized - - adjacent_surface_ids = adjacent_surface_id_str.to_s.split(', ') - # All the surfaces that are adjacent to the space - adjacent_surfaces = model_objects.select { |o| o.to_Surface.is_initialized && o.to_Surface.get.additionalProperties.getFeatureAsString('hpxmlID').is_initialized && (adjacent_surface_ids.include? o.to_Surface.get.additionalProperties.getFeatureAsString('hpxmlID').to_s) } - adjacent_surfaces.each do |surface_obj| - surface = surface_obj.to_Surface.get - surface.createAdjacentSurface(space) - end + next unless obj.to_Surface.is_initialized + next unless obj.to_Surface.get.additionalProperties.getFeatureAsString('hpxmlSameasID').is_initialized + + surface_obj = obj.to_Surface.get + hpxml_sameas_id = surface_obj.additionalProperties.getFeatureAsString('hpxmlSameasID').to_s + + adjacent_surface = model_objects.find { |o| o.to_Surface.is_initialized && o.to_Surface.get.additionalProperties.getFeatureAsString('hpxmlID').is_initialized && (hpxml_sameas_id == o.to_Surface.get.additionalProperties.getFeatureAsString('hpxmlID').to_s) }.to_Surface.get + surface_obj.setConstruction(adjacent_surface.construction.get.to_Construction.get.reverseConstruction) + adjacent_surface.setAdjacentSurface(surface_obj) end end diff --git a/workflow/hpxml_inputs.json b/workflow/hpxml_inputs.json index 5151befba7..827d299976 100644 --- a/workflow/hpxml_inputs.json +++ b/workflow/hpxml_inputs.json @@ -3608,7 +3608,8 @@ "clothes_dryer_present": false }, "sample_files/base-bldgtype-mf-whole-building-common-spaces.xml": { - "parent_hpxml": "sample_files/base-bldgtype-mf-whole-building.xml" + "parent_hpxml": "sample_files/base-bldgtype-mf-whole-building.xml", + "heating_system_heating_capacity": 24000 }, "sample_files/base-pv.xml": { "parent_hpxml": "sample_files/base.xml", diff --git a/workflow/sample_files/base-bldgtype-mf-whole-building-common-spaces.xml b/workflow/sample_files/base-bldgtype-mf-whole-building-common-spaces.xml index b752501b74..16376daa1d 100644 --- a/workflow/sample_files/base-bldgtype-mf-whole-building-common-spaces.xml +++ b/workflow/sample_files/base-bldgtype-mf-whole-building-common-spaces.xml @@ -229,7 +229,7 @@ electricity - 12000.0 + 24000.0 Percent 1.0 @@ -1246,7 +1246,7 @@ electricity - 12000.0 + 24000.0 Percent 1.0 @@ -1949,7 +1949,7 @@ electricity - 12000.0 + 24000.0 Percent 1.0 diff --git a/workflow/tests/util.rb b/workflow/tests/util.rb index e7635f1b67..cfc4de415b 100644 --- a/workflow/tests/util.rb +++ b/workflow/tests/util.rb @@ -423,7 +423,7 @@ def _verify_outputs(rundir, hpxml_path, results, hpxml, unit_multiplier) end assert_equal(0, num_unused_objects) # FIXME: Common space test file has 2 unused schedule by dropping the othersidecoefficient object, which contains schedules - assert_equal((hpxml_path.include? 'base-bldgtype-mf-whole-building-common-spaces') ? 4 : 0, num_unused_schedules) + assert_equal(0, num_unused_schedules) unless (hpxml_path.include? 'base-bldgtype-mf-whole-building-common-spaces') assert_equal(0, num_unused_constructions) # Check for Output:Meter and Output:Variable warnings