Skip to content

Commit 8724df2

Browse files
authored
Merge pull request #2086 from NREL/hpwh_adjustment_confined_space
HPWH COP Adjustment in confined space
2 parents 34535d1 + f14b342 commit 8724df2

19 files changed

Lines changed: 724 additions & 61 deletions

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ __New Features__
1313
- Water heater improvements:
1414
- Improves electric water heater tank losses when using `EnergyFactor` as the metric; now consistent with how `UniformEnergyFactor` is handled.
1515
- Improves HPWH tank volume defaulting, particularly when `NumberofResidents` is provided.
16+
- Allows HPWH performance adjustment when installed in confined space per RESNET HERS Addendum 77. When `extension/HPWHInConfinedSpaceWithoutMitigation` is "true", `extension/HPWHContainmentVolume` is used to calculate the adjustment.
1617
- Updated site defaults:
1718
- `Address/CityMunicipality`, `Address/StateCode`, `GeoLocation/Latitude`, `GeoLocation/Longitude`, and `TimeZone/UTCOffset` now default based on zip code if available.
1819
- `TimeZone/DSTObserved` now defaults to false if `Address/StateCode` is 'AZ' or 'HI'.

HPXMLtoOpenStudio/measure.xml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<schema_version>3.1</schema_version>
44
<name>hpxm_lto_openstudio</name>
55
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
6-
<version_id>370efaa7-01ec-407f-92cd-2b37f15c9baa</version_id>
7-
<version_modified>2025-10-31T17:24:17Z</version_modified>
6+
<version_id>ebf347a8-a3b8-4096-a72c-078aa24e7d59</version_id>
7+
<version_modified>2025-10-31T18:46:56Z</version_modified>
88
<xml_checksum>D8922A73</xml_checksum>
99
<class_name>HPXMLtoOpenStudio</class_name>
1010
<display_name>HPXML to OpenStudio Translator</display_name>
@@ -348,7 +348,7 @@
348348
<filename>defaults.rb</filename>
349349
<filetype>rb</filetype>
350350
<usage_type>resource</usage_type>
351-
<checksum>2157E4EC</checksum>
351+
<checksum>73012C00</checksum>
352352
</file>
353353
<file>
354354
<filename>electric_panel.rb</filename>
@@ -384,7 +384,7 @@
384384
<filename>hpxml.rb</filename>
385385
<filetype>rb</filetype>
386386
<usage_type>resource</usage_type>
387-
<checksum>DBE0703D</checksum>
387+
<checksum>26FF824B</checksum>
388388
</file>
389389
<file>
390390
<filename>hpxml_schema/HPXML.xsd</filename>
@@ -402,7 +402,7 @@
402402
<filename>hpxml_schematron/EPvalidator.sch</filename>
403403
<filetype>sch</filetype>
404404
<usage_type>resource</usage_type>
405-
<checksum>4BBE1448</checksum>
405+
<checksum>565E6550</checksum>
406406
</file>
407407
<file>
408408
<filename>hpxml_schematron/iso-schematron.xsd</filename>
@@ -696,7 +696,7 @@
696696
<filename>waterheater.rb</filename>
697697
<filetype>rb</filetype>
698698
<usage_type>resource</usage_type>
699-
<checksum>003609A7</checksum>
699+
<checksum>D373B68C</checksum>
700700
</file>
701701
<file>
702702
<filename>weather.rb</filename>
@@ -732,7 +732,7 @@
732732
<filename>test_defaults.rb</filename>
733733
<filetype>rb</filetype>
734734
<usage_type>test</usage_type>
735-
<checksum>67DD734E</checksum>
735+
<checksum>5C9F88DA</checksum>
736736
</file>
737737
<file>
738738
<filename>test_electric_panel.rb</filename>
@@ -810,7 +810,7 @@
810810
<filename>test_validation.rb</filename>
811811
<filetype>rb</filetype>
812812
<usage_type>test</usage_type>
813-
<checksum>AC7D67C4</checksum>
813+
<checksum>A339372A</checksum>
814814
</file>
815815
<file>
816816
<filename>test_vehicle.rb</filename>
@@ -822,7 +822,7 @@
822822
<filename>test_water_heater.rb</filename>
823823
<filetype>rb</filetype>
824824
<usage_type>test</usage_type>
825-
<checksum>4B3C5F26</checksum>
825+
<checksum>CADD169C</checksum>
826826
</file>
827827
<file>
828828
<filename>test_weather.rb</filename>

HPXMLtoOpenStudio/resources/defaults.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,6 +3347,11 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
33473347
water_heating_system.operating_mode_isdefaulted = true
33483348
end
33493349

3350+
if water_heating_system.hpwh_confined_space_without_mitigation.nil?
3351+
water_heating_system.hpwh_confined_space_without_mitigation = false
3352+
water_heating_system.hpwh_confined_space_without_mitigation_isdefaulted = true
3353+
end
3354+
33503355
end
33513356
next unless water_heating_system.location.nil?
33523357

HPXMLtoOpenStudio/resources/hpxml.rb

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8614,32 +8614,34 @@ def from_doc(building)
86148614

86158615
# Object for /HPXML/Building/BuildingDetails/Systems/WaterHeating/WaterHeatingSystem.
86168616
class WaterHeatingSystem < BaseElement
8617-
ATTRS = [:id, # [String] SystemIdentifier/@id
8618-
:fuel_type, # [String] FuelType (HPXML::FuelTypeXXX)
8619-
:water_heater_type, # [String] WaterHeaterType (HPXML::WaterHeaterTypeXXX)
8620-
:location, # [String] Location (HPXML::LocationXXX)
8621-
:year_installed, # [Integer] YearInstalled
8622-
:is_shared_system, # [Boolean] IsSharedSystem
8623-
:performance_adjustment, # [Double] PerformanceAdjustment (frac)
8624-
:third_party_certification, # [String] ThirdPartyCertification
8625-
:tank_volume, # [Double] TankVolume (gal)
8626-
:fraction_dhw_load_served, # [Double] FractionDHWLoadServed (frac)
8627-
:heating_capacity, # [Double] HeatingCapacity (Btu/hr)
8628-
:backup_heating_capacity, # [Double] BackupHeatingCapacity (Btu/hr)
8629-
:energy_factor, # [Double] EnergyFactor (frac)
8630-
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
8631-
:operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterOperatingModeXXX)
8632-
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
8633-
:usage_bin, # [String] UsageBin (HPXML::WaterHeaterUsageBinXXX)
8634-
:recovery_efficiency, # [Double] RecoveryEfficiency (frac)
8635-
:jacket_r_value, # [Double] WaterHeaterInsulation/Jacket/JacketRValue (F-ft2-hr/Btu)
8636-
:standby_loss_units, # [String] StandbyLoss/Units (HPXML::UnitsXXX)
8637-
:standby_loss_value, # [Double] StandbyLoss/Value
8638-
:temperature, # [Double] HotWaterTemperature (F)
8639-
:uses_desuperheater, # [Boolean] UsesDesuperheater
8640-
:related_hvac_idref, # [String] RelatedHVACSystem/@idref
8641-
:tank_model_type, # [String] extension/TankModelType (HPXML::WaterHeaterTankModelTypeXXX)
8642-
:number_of_bedrooms_served] # [Integer] extension/NumberofBedroomsServed
8617+
ATTRS = [:id, # [String] SystemIdentifier/@id
8618+
:fuel_type, # [String] FuelType (HPXML::FuelTypeXXX)
8619+
:water_heater_type, # [String] WaterHeaterType (HPXML::WaterHeaterTypeXXX)
8620+
:location, # [String] Location (HPXML::LocationXXX)
8621+
:year_installed, # [Integer] YearInstalled
8622+
:is_shared_system, # [Boolean] IsSharedSystem
8623+
:performance_adjustment, # [Double] PerformanceAdjustment (frac)
8624+
:third_party_certification, # [String] ThirdPartyCertification
8625+
:tank_volume, # [Double] TankVolume (gal)
8626+
:fraction_dhw_load_served, # [Double] FractionDHWLoadServed (frac)
8627+
:heating_capacity, # [Double] HeatingCapacity (Btu/hr)
8628+
:backup_heating_capacity, # [Double] BackupHeatingCapacity (Btu/hr)
8629+
:energy_factor, # [Double] EnergyFactor (frac)
8630+
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
8631+
:operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterOperatingModeXXX)
8632+
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
8633+
:usage_bin, # [String] UsageBin (HPXML::WaterHeaterUsageBinXXX)
8634+
:recovery_efficiency, # [Double] RecoveryEfficiency (frac)
8635+
:jacket_r_value, # [Double] WaterHeaterInsulation/Jacket/JacketRValue (F-ft2-hr/Btu)
8636+
:standby_loss_units, # [String] StandbyLoss/Units (HPXML::UnitsXXX)
8637+
:standby_loss_value, # [Double] StandbyLoss/Value
8638+
:temperature, # [Double] HotWaterTemperature (F)
8639+
:uses_desuperheater, # [Boolean] UsesDesuperheater
8640+
:related_hvac_idref, # [String] RelatedHVACSystem/@idref
8641+
:tank_model_type, # [String] extension/TankModelType (HPXML::WaterHeaterTankModelTypeXXX)
8642+
:number_of_bedrooms_served, # [Integer] extension/NumberofBedroomsServed
8643+
:hpwh_confined_space_without_mitigation, # [Boolean] extension/HPWHInConfinedSpaceWithoutMitigation
8644+
:hpwh_containment_volume] # [Double] extension/HPWHContainmentVolume
86438645
attr_accessor(*ATTRS)
86448646

86458647
# Returns any branch circuits that the component may be attached to.
@@ -8746,10 +8748,12 @@ def to_doc(building)
87468748
related_hvac_idref_el = XMLHelper.add_element(water_heating_system, 'RelatedHVACSystem')
87478749
XMLHelper.add_attribute(related_hvac_idref_el, 'idref', @related_hvac_idref)
87488750
end
8749-
if (not @tank_model_type.nil?) || (not @number_of_bedrooms_served.nil?)
8751+
if (not @tank_model_type.nil?) || (not @number_of_bedrooms_served.nil?) || (not @hpwh_confined_space_without_mitigation.nil?) || (not @hpwh_containment_volume.nil?)
87508752
extension = XMLHelper.create_elements_as_needed(water_heating_system, ['extension'])
87518753
XMLHelper.add_element(extension, 'TankModelType', @tank_model_type, :string, @tank_model_type_isdefaulted) unless @tank_model_type.nil?
87528754
XMLHelper.add_element(extension, 'NumberofBedroomsServed', @number_of_bedrooms_served, :integer, @number_of_bedrooms_served_isdefaulted) unless @number_of_bedrooms_served.nil?
8755+
XMLHelper.add_element(extension, 'HPWHInConfinedSpaceWithoutMitigation', @hpwh_confined_space_without_mitigation, :boolean, @hpwh_confined_space_without_mitigation_isdefaulted) unless @hpwh_confined_space_without_mitigation.nil?
8756+
XMLHelper.add_element(extension, 'HPWHContainmentVolume', @hpwh_containment_volume, :float, @hpwh_containment_volume_isdefaulted) unless @hpwh_containment_volume.nil?
87538757
end
87548758
end
87558759

@@ -8786,6 +8790,8 @@ def from_doc(water_heating_system)
87868790
@related_hvac_idref = HPXML::get_idref(XMLHelper.get_element(water_heating_system, 'RelatedHVACSystem'))
87878791
@tank_model_type = XMLHelper.get_value(water_heating_system, 'extension/TankModelType', :string)
87888792
@number_of_bedrooms_served = XMLHelper.get_value(water_heating_system, 'extension/NumberofBedroomsServed', :integer)
8793+
@hpwh_confined_space_without_mitigation = XMLHelper.get_value(water_heating_system, 'extension/HPWHInConfinedSpaceWithoutMitigation', :boolean)
8794+
@hpwh_containment_volume = XMLHelper.get_value(water_heating_system, 'extension/HPWHContainmentVolume', :float)
87898795
end
87908796
end
87918797

HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.sch

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,13 +2571,25 @@
25712571
<sch:assert role='ERROR' test='count(h:WaterHeaterInsulation/h:Jacket/h:JacketRValue) &lt;= 1'>Expected 0 or 1 element(s) for xpath: WaterHeaterInsulation/Jacket/JacketRValue</sch:assert>
25722572
<sch:assert role='ERROR' test='count(h:HotWaterTemperature) &lt;= 1'>Expected 0 or 1 element(s) for xpath: HotWaterTemperature</sch:assert>
25732573
<sch:assert role='ERROR' test='count(h:UsesDesuperheater) &lt;= 1'>Expected 0 or 1 element(s) for xpath: UsesDesuperheater</sch:assert> <!-- See [Desuperheater] -->
2574+
<sch:assert role='ERROR' test='count(h:extension/h:HPWHInConfinedSpaceWithoutMitigation) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/HPWHInConfinedSpaceWithoutMitigation</sch:assert> <!-- See [HPWHInConfinedSpaceWithoutMitigation] -->
2575+
<sch:assert role='ERROR' test='h:extension/h:HPWHInConfinedSpaceWithoutMitigation[text()="true" or text()="false"] or not(h:extension/h:HPWHInConfinedSpaceWithoutMitigation)'>Expected extension/HPWHInConfinedSpaceWithoutMitigation to be 'true' or 'false'</sch:assert>
25742576
<!-- Moved/deprecated extension/OperatingMode input; see https://github.com/NREL/OpenStudio-HPXML/pull/1289 -->
25752577
<sch:assert role='ERROR' test='count(h:extension/h:OperatingMode) = 0'>extension/OperatingMode has been replaced by HPWHOperatingMode</sch:assert>
25762578
<!-- Warnings -->
25772579
<sch:report role='WARN' test='number(h:HotWaterTemperature) &lt; 110'>Hot water setpoint should typically be greater than or equal to 110 deg-F.</sch:report>
25782580
</sch:rule>
25792581
</sch:pattern>
25802582

2583+
<sch:pattern>
2584+
<sch:title>[HPWHInConfinedSpaceWithoutMitigation]</sch:title>
2585+
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:WaterHeating/h:WaterHeatingSystem/h:extension[h:HPWHInConfinedSpaceWithoutMitigation="true"]'>
2586+
<sch:assert role='ERROR' test='count(h:HPWHContainmentVolume) = 1'>Expected 1 element(s) for xpath: HPWHContainmentVolume</sch:assert>
2587+
<sch:assert role='ERROR' test='number(h:HPWHContainmentVolume) &gt; 0 or not(h:HPWHContainmentVolume)'>Expected HPWHContainmentVolume to be greater than 0.</sch:assert>
2588+
<!-- Warnings -->
2589+
<sch:report role='WARN' test='number(h:HPWHContainmentVolume) &gt; 1500'>HPWHContainmentVolume should typically be less than 1500 cuft when the HPWH is in confined space.</sch:report>
2590+
</sch:rule>
2591+
</sch:pattern>
2592+
25812593
<sch:pattern>
25822594
<sch:title>[WaterHeatingSystemType=CombiIndirect]</sch:title>
25832595
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:WaterHeating/h:WaterHeatingSystem[h:WaterHeaterType="space-heating boiler with storage tank"]'>

HPXMLtoOpenStudio/resources/waterheater.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,22 @@ def self.apply_hpwh_dxcoil(runner, model, water_heating_system, elevation, obj_n
976976

977977
cop = water_heating_system.additional_properties.cop
978978

979+
# Adjust COP based on RESNET HERS Addendum 77
980+
if not water_heating_system.hpwh_containment_volume.nil?
981+
if not water_heating_system.hpwh_confined_space_without_mitigation
982+
if water_heating_system.hpwh_containment_volume < 1000.0
983+
runner.registerWarning("HPWH COP adjustment based on HPWHContainmentVolume will not be applied to #{water_heating_system.id} because HPWHInConfinedSpaceWithoutMitigation is not 'true'.")
984+
end
985+
else
986+
# FUTURE: apply for 120V HPWH and other system types that the correction may not be accurate for
987+
if water_heating_system.hpwh_containment_volume < 450.0 && (water_heating_system.backup_heating_capacity == 0.0)
988+
runner.registerWarning("Heat pump water heater: #{water_heating_system.id} has no backup electric resistance element, COP adjustment for confined space may not be accurate when the containment space volume is below 450 cubic feet.")
989+
end
990+
rv = [water_heating_system.hpwh_containment_volume / 1500.0, 1.0].min
991+
cop = (cop - 0.92) * (1 - (1.009 * Math.exp(-5.492 * rv))) + 0.92
992+
end
993+
end
994+
979995
coil = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPumpWrapped.new(model)
980996
coil.setName("#{obj_name} coil")
981997
coil.setRatedHeatingCapacity(cap)

HPXMLtoOpenStudio/tests/test_defaults.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3635,32 +3635,35 @@ def test_heat_pump_water_heaters
36353635
hpxml_bldg.water_heating_systems[0].operating_mode = HPXML::WaterHeaterOperatingModeHeatPumpOnly
36363636
hpxml_bldg.water_heating_systems[0].heating_capacity = 4000.0
36373637
hpxml_bldg.water_heating_systems[0].backup_heating_capacity = 5000.0
3638+
hpxml_bldg.water_heating_systems[0].hpwh_confined_space_without_mitigation = true
3639+
hpxml_bldg.water_heating_systems[0].hpwh_containment_volume = 800.0
36383640
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
36393641
_default_hpxml, default_hpxml_bldg = _test_measure()
3640-
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [44.0, HPXML::WaterHeaterOperatingModeHeatPumpOnly, 4000.0, 5000.0])
3642+
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [44.0, HPXML::WaterHeaterOperatingModeHeatPumpOnly, 4000.0, 5000.0, true])
36413643

36423644
# Test defaults
36433645
hpxml_bldg.water_heating_systems[0].tank_volume = nil
36443646
hpxml_bldg.water_heating_systems[0].operating_mode = nil
36453647
hpxml_bldg.water_heating_systems[0].heating_capacity = nil
36463648
hpxml_bldg.water_heating_systems[0].backup_heating_capacity = nil
3649+
hpxml_bldg.water_heating_systems[0].hpwh_confined_space_without_mitigation = nil
36473650
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
36483651
_default_hpxml, default_hpxml_bldg = _test_measure()
3649-
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [66.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
3652+
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [66.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])
36503653

36513654
# Test defaults w/ num occupants = 1, num bedrooms = 1
36523655
hpxml_bldg.building_construction.number_of_bedrooms = 1
36533656
hpxml_bldg.building_occupancy.number_of_residents = 1
36543657
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
36553658
_default_hpxml, default_hpxml_bldg = _test_measure()
3656-
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [50.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
3659+
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [50.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])
36573660

36583661
# Test defaults w/ num occupants = 10, num bedrooms = 1
36593662
hpxml_bldg.building_construction.number_of_bedrooms = 1
36603663
hpxml_bldg.building_occupancy.number_of_residents = 10
36613664
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
36623665
_default_hpxml, default_hpxml_bldg = _test_measure()
3663-
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [80.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
3666+
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [80.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])
36643667
end
36653668

36663669
def test_indirect_water_heaters
@@ -6302,12 +6305,13 @@ def _test_default_heat_pump_water_heater_values(hpxml_bldg, *expected_wh_values)
63026305
heat_pump_water_heaters = hpxml_bldg.water_heating_systems.select { |w| w.water_heater_type == HPXML::WaterHeaterTypeHeatPump }
63036306
assert_equal(expected_wh_values.size, heat_pump_water_heaters.size)
63046307
heat_pump_water_heaters.each_with_index do |wh_system, idx|
6305-
tank_volume, operating_mode, htg_cap, backup_htg_cap = expected_wh_values[idx]
6308+
tank_volume, operating_mode, htg_cap, backup_htg_cap, hpwh_confined_space_without_mitigation = expected_wh_values[idx]
63066309

63076310
assert_equal(tank_volume, wh_system.tank_volume)
63086311
assert_equal(operating_mode, wh_system.operating_mode)
63096312
assert_in_epsilon(htg_cap, wh_system.heating_capacity, 0.01)
63106313
assert_in_epsilon(backup_htg_cap, wh_system.backup_heating_capacity, 0.01)
6314+
assert_equal(hpwh_confined_space_without_mitigation, wh_system.hpwh_confined_space_without_mitigation)
63116315
end
63126316
end
63136317

0 commit comments

Comments
 (0)