Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ __New Features__
- Allows desuperheaters to be used with the experimental ground-source heat pump model.
- Allows optional `UsageMultiplier` for electric vehicles described using `Vehicles`.
- Water heater improvements:
- Improves electric water heater tank losses when using `EnergyFactor` as the metric; now consistent with how `UniformEnergyFactor` is handled.
- Improves HPWH tank volume defaulting, particularly when `NumberofResidents` is provided.
- Allows HPWHs to have exhaust air ducted to the outside using `HPWHDucting/ExhaustAirTermination="outside"`.
- 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.
- Improves HPWH tank volume defaulting, particularly when `NumberofResidents` is provided.
- Improves electric water heater tank losses when using `EnergyFactor` as the metric; now consistent with how `UniformEnergyFactor` is handled.
- Updated site defaults:
- `Address/CityMunicipality`, `Address/StateCode`, `GeoLocation/Latitude`, `GeoLocation/Longitude`, and `TimeZone/UTCOffset` now default based on zip code if available.
- `TimeZone/DSTObserved` now defaults to false if `Address/StateCode` is 'AZ' or 'HI'.
Expand Down
22 changes: 11 additions & 11 deletions HPXMLtoOpenStudio/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>68c49ecc-26b8-4b04-ae0a-99c4d11d6295</version_id>
<version_modified>2025-12-31T16:14:24Z</version_modified>
<version_id>47b79fc1-dfc8-465a-9ddd-d95275a5295c</version_id>
<version_modified>2026-01-06T21:09:54Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -198,7 +198,7 @@
<filename>airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>7FED4CFE</checksum>
<checksum>CA31A32C</checksum>
</file>
<file>
<filename>battery.rb</filename>
Expand Down Expand Up @@ -348,7 +348,7 @@
<filename>defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>89E488C6</checksum>
<checksum>0FAE1876</checksum>
</file>
<file>
<filename>electric_panel.rb</filename>
Expand Down Expand Up @@ -384,7 +384,7 @@
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>C9D22AA0</checksum>
<checksum>F71A6B0A</checksum>
</file>
<file>
<filename>hpxml_schema/HPXML.xsd</filename>
Expand All @@ -402,7 +402,7 @@
<filename>hpxml_schematron/EPvalidator.sch</filename>
<filetype>sch</filetype>
<usage_type>resource</usage_type>
<checksum>4F63CF69</checksum>
<checksum>2DB74696</checksum>
</file>
<file>
<filename>hpxml_schematron/iso-schematron.xsd</filename>
Expand Down Expand Up @@ -474,7 +474,7 @@
<filename>model.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>7E742AB1</checksum>
<checksum>632A2E7B</checksum>
</file>
<file>
<filename>output.rb</filename>
Expand Down Expand Up @@ -654,7 +654,7 @@
<filename>schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>15A3F181</checksum>
<checksum>662E62CE</checksum>
</file>
<file>
<filename>simcontrols.rb</filename>
Expand Down Expand Up @@ -696,7 +696,7 @@
<filename>waterheater.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>FBE1CBC7</checksum>
<checksum>01A0FB5A</checksum>
</file>
<file>
<filename>weather.rb</filename>
Expand Down Expand Up @@ -732,7 +732,7 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>4756D612</checksum>
<checksum>F3835E37</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
Expand Down Expand Up @@ -810,7 +810,7 @@
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>C04B6DB5</checksum>
<checksum>79D9847A</checksum>
</file>
<file>
<filename>test_vehicle.rb</filename>
Expand Down
28 changes: 23 additions & 5 deletions HPXMLtoOpenStudio/resources/airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2320,6 +2320,7 @@ def self.initialize_mech_vent(model, spaces, infil_program, sensors)
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
unit_multiplier = hpxml_bldg.building_construction.number_of_units

infil_flow = Model.add_infiltration_flow_rate(
model,
Expand All @@ -2341,6 +2342,7 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx
bal_cfm_tot = vent_fans[:mech_balanced].map { |vent_mech| vent_mech.average_unit_flow_rate }.sum(0.0)
erv_hrv_cfm_tot = vent_fans[:mech_erv_hrv].map { |vent_mech| vent_mech.average_unit_flow_rate }.sum(0.0)

# Handle any cooking range hood ventilation
infil_program.addLine('Set Qrange = 0')
cooking_range_in_cond_space = hpxml_bldg.cooking_ranges.empty? ? true : HPXML::conditioned_locations_this_unit.include?(hpxml_bldg.cooking_ranges[0].location)
vent_fans[:kitchen].each_with_index do |vent_kitchen, index|
Expand All @@ -2353,6 +2355,7 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx
infil_program.addLine("Set Qrange = Qrange + #{UnitConversions.convert(vent_kitchen.flow_rate * vent_kitchen.count, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}")
end

# Handle any bathroom fan ventilation
infil_program.addLine('Set Qbath = 0')
vent_fans[:bath].each_with_index do |vent_bath, index|
# Electricity impact
Expand All @@ -2363,25 +2366,40 @@ def self.apply_infiltration_adjustment_to_conditioned(runner, model, spaces, hpx
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

# Handle any clothes dryer venting
infil_program.addLine('Set Qdryer = 0')
clothes_dryer_in_cond_space = hpxml_bldg.clothes_dryers.empty? ? true : HPXML::conditioned_locations_this_unit.include?(hpxml_bldg.clothes_dryers[0].location)
vented_dryers = hpxml_bldg.clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 }
vented_dryers.each_with_index do |vented_dryer, index|
vented_dryers_in_cond_space = hpxml_bldg.clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 && HPXML::conditioned_locations_this_unit.include?(cd.location) }
vented_dryers_in_cond_space.each_with_index do |vented_dryer, index|
next if hpxml_bldg.building_occupancy.number_of_residents == 0 # Operational calculation w/ zero occupants, zero out energy use
next unless clothes_dryer_in_cond_space

# Infiltration impact
vented_dryer_unavailable_periods = Schedule.get_unavailable_periods(runner, SchedulesFile::Columns[:ClothesDryer].name, hpxml_header.unavailable_periods)
obj_sch_sensor, cfm_mult = apply_dryer_exhaust(model, hpxml_header, vented_dryer, schedules_file, index, vented_dryer_unavailable_periods)
infil_program.addLine("Set Qdryer = Qdryer + #{UnitConversions.convert(vented_dryer.vented_flow_rate * cfm_mult, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}")
end

# Handle any HPWHs
infil_program.addLine('Set Qhpwh = 0')
ducted_hpwhs_in_cond_space = hpxml_bldg.water_heating_systems.select { |wh| wh.water_heater_type == HPXML::WaterHeaterTypeHeatPump && wh.hpwh_ducting_exhaust == HPXML::LocationOutside && HPXML::conditioned_locations_this_unit.include?(wh.location) }
ducted_hpwhs_in_cond_space.each_with_index do |ducted_hpwh, i|
# Create sensor to get HPWH airflow rate
hpwh = model.getWaterHeaterHeatPumpWrappedCondensers.find { |h| h.additionalProperties.getFeatureAsString('HPXML_ID').to_s == ducted_hpwh.id }
hpwh_flow_rate = Model.add_ems_sensor(
model,
name: "hpwh_flow_rate_#{i}",
output_var_or_meter_name: 'System Node Current Density Volume Flow Rate',
key_name: hpwh.airOutletNodeName
)

infil_program.addLine("Set Qhpwh = Qhpwh + #{hpwh_flow_rate.name} / #{unit_multiplier}")
end

infil_program.addLine("Set QWHV_sup = #{UnitConversions.convert(sup_cfm_tot + bal_cfm_tot + erv_hrv_cfm_tot, 'cfm', 'm^3/s').round(5)}")
infil_program.addLine("Set QWHV_exh = #{UnitConversions.convert(exh_cfm_tot + bal_cfm_tot + erv_hrv_cfm_tot, 'cfm', 'm^3/s').round(5)}")

# Ventilation fans
infil_program.addLine('Set Qsupply = QWHV_sup + QWHV_cfis_sup + QWHV_cfis_suppl_sup')
infil_program.addLine('Set Qexhaust = Qrange + Qbath + Qdryer + QWHV_exh + QWHV_cfis_suppl_exh')
infil_program.addLine('Set Qexhaust = Qrange + Qbath + Qdryer + Qhpwh + QWHV_exh + QWHV_cfis_suppl_exh')
infil_program.addLine('Set Qfan = (@Max Qexhaust Qsupply)')

# Duct leakage imbalance induced infiltration
Expand Down
8 changes: 4 additions & 4 deletions HPXMLtoOpenStudio/resources/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3357,10 +3357,10 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
water_heating_system.tank_volume_isdefaulted = true
end

schedules_file_includes_water_heater_operating_mode = (schedules_file.nil? ? false : schedules_file.includes_col_name(SchedulesFile::Columns[:WaterHeaterOperatingMode].name))
if water_heating_system.operating_mode.nil? && !schedules_file_includes_water_heater_operating_mode
water_heating_system.operating_mode = HPXML::WaterHeaterOperatingModeHybridAuto
water_heating_system.operating_mode_isdefaulted = true
schedules_file_includes_water_heater_operating_mode = (schedules_file.nil? ? false : schedules_file.includes_col_name(SchedulesFile::Columns[:WaterHeaterHPWHOperatingMode].name))
if water_heating_system.hpwh_operating_mode.nil? && !schedules_file_includes_water_heater_operating_mode
water_heating_system.hpwh_operating_mode = HPXML::WaterHeaterHPWHOperatingModeHybridAuto
water_heating_system.hpwh_operating_mode_isdefaulted = true
end

if water_heating_system.hpwh_confined_space_without_mitigation.nil?
Expand Down
19 changes: 14 additions & 5 deletions HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,8 @@ class HPXML < Object
WallTypeWoodStud = 'WoodStud'
WaterFixtureTypeFaucet = 'faucet'
WaterFixtureTypeShowerhead = 'shower head'
WaterHeaterOperatingModeHybridAuto = 'hybrid/auto'
WaterHeaterOperatingModeHeatPumpOnly = 'heat pump only'
WaterHeaterHPWHOperatingModeHybridAuto = 'hybrid/auto'
WaterHeaterHPWHOperatingModeHeatPumpOnly = 'heat pump only'
WaterHeaterTankModelTypeMixed = 'mixed'
WaterHeaterTankModelTypeStratified = 'stratified'
WaterHeaterTypeCombiStorage = 'space-heating boiler with storage tank'
Expand Down Expand Up @@ -8587,7 +8587,9 @@ class WaterHeatingSystem < BaseElement
:backup_heating_capacity, # [Double] BackupHeatingCapacity (Btu/hr)
:energy_factor, # [Double] EnergyFactor (frac)
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
:operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterOperatingModeXXX)
:hpwh_operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterHPWHOperatingModeXXX)
:hpwh_ducting_supply, # [String] HPWHDucting/SupplyAirSource (HPXML::LocationXXX)
Comment thread
shorowit marked this conversation as resolved.
:hpwh_ducting_exhaust, # [String] HPWHDucting/ExhaustAirTermination (HPXML::LocationXXX)
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
:usage_bin, # [String] UsageBin (HPXML::WaterHeaterUsageBinXXX)
:recovery_efficiency, # [Double] RecoveryEfficiency (frac)
Expand Down Expand Up @@ -8688,7 +8690,12 @@ def to_doc(building)
XMLHelper.add_element(water_heating_system, 'BackupHeatingCapacity', @backup_heating_capacity, :float, @backup_heating_capacity_isdefaulted) unless @backup_heating_capacity.nil?
XMLHelper.add_element(water_heating_system, 'EnergyFactor', @energy_factor, :float, @energy_factor_isdefaulted) unless @energy_factor.nil?
XMLHelper.add_element(water_heating_system, 'UniformEnergyFactor', @uniform_energy_factor, :float) unless @uniform_energy_factor.nil?
XMLHelper.add_element(water_heating_system, 'HPWHOperatingMode', @operating_mode, :string, @operating_mode_isdefaulted) unless @operating_mode.nil?
XMLHelper.add_element(water_heating_system, 'HPWHOperatingMode', @hpwh_operating_mode, :string, @hpwh_operating_mode_isdefaulted) unless @hpwh_operating_mode.nil?
if (not @hpwh_ducting_exhaust.nil?) || (not @hpwh_ducting_supply.nil?)
hpwh_ducting = XMLHelper.add_element(water_heating_system, 'HPWHDucting')
XMLHelper.add_element(hpwh_ducting, 'SupplyAirSource', @hpwh_ducting_supply, :string, @hpwh_ducting_supply_isdefaulted) unless @hpwh_ducting_supply.nil?
XMLHelper.add_element(hpwh_ducting, 'ExhaustAirTermination', @hpwh_ducting_exhaust, :string, @hpwh_ducting_exhaust_isdefaulted) unless @hpwh_ducting_exhaust.nil?
end
XMLHelper.add_element(water_heating_system, 'FirstHourRating', @first_hour_rating, :float) unless @first_hour_rating.nil?
XMLHelper.add_element(water_heating_system, 'UsageBin', @usage_bin, :string, @usage_bin_isdefaulted) unless @usage_bin.nil?
XMLHelper.add_element(water_heating_system, 'RecoveryEfficiency', @recovery_efficiency, :float, @recovery_efficiency_isdefaulted) unless @recovery_efficiency.nil?
Expand Down Expand Up @@ -8738,7 +8745,9 @@ def from_doc(water_heating_system)
@backup_heating_capacity = XMLHelper.get_value(water_heating_system, 'BackupHeatingCapacity', :float)
@energy_factor = XMLHelper.get_value(water_heating_system, 'EnergyFactor', :float)
@uniform_energy_factor = XMLHelper.get_value(water_heating_system, 'UniformEnergyFactor', :float)
@operating_mode = XMLHelper.get_value(water_heating_system, 'HPWHOperatingMode', :string)
@hpwh_operating_mode = XMLHelper.get_value(water_heating_system, 'HPWHOperatingMode', :string)
@hpwh_ducting_supply = XMLHelper.get_value(water_heating_system, 'HPWHDucting/SupplyAirSource', :string)
@hpwh_ducting_exhaust = XMLHelper.get_value(water_heating_system, 'HPWHDucting/ExhaustAirTermination', :string)
@first_hour_rating = XMLHelper.get_value(water_heating_system, 'FirstHourRating', :float)
@usage_bin = XMLHelper.get_value(water_heating_system, 'UsageBin', :string)
@recovery_efficiency = XMLHelper.get_value(water_heating_system, 'RecoveryEfficiency', :float)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2587,6 +2587,7 @@
<sch:assert role='ERROR' test='number(h:EnergyFactor) &gt; 1 or not(h:EnergyFactor)'>Expected EnergyFactor to be greater than 1</sch:assert>
<sch:assert role='ERROR' test='count(h:HPWHOperatingMode) &lt;= 1'>Expected 0 or 1 element(s) for xpath: HPWHOperatingMode</sch:assert>
<sch:assert role='ERROR' test='h:HPWHOperatingMode[text()="hybrid/auto" or text()="heat pump only"] or not(h:HPWHOperatingMode)'>Expected HPWHOperatingMode to be 'hybrid/auto' or 'heat pump only'</sch:assert>
<sch:assert role='ERROR' test='count(h:HPWHDucting/h:ExhaustAirTermination) &lt;= 1'>Expected 0 or 1 element(s) for xpath: HPWHDucting/ExhaustAirTermination</sch:assert>
<sch:assert role='ERROR' test='count(h:UsageBin) + count(h:FirstHourRating) &gt;= 0'>Expected 0 or more element(s) for xpath: UsageBin | FirstHourRating</sch:assert>
<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>
<sch:assert role='ERROR' test='count(h:HotWaterTemperature) &lt;= 1'>Expected 0 or 1 element(s) for xpath: HotWaterTemperature</sch:assert>
Expand Down
8 changes: 7 additions & 1 deletion HPXMLtoOpenStudio/resources/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,13 @@ def self.create_adjacent_surfaces_for_whole_mf_building(merged_model_objects, hp
# @param unit_number [Integer] index number corresponding to an HPXML Building object
# @return [String] the new OpenStudio object name with unique unit prefix
def self.make_variable_name(obj_name, unit_number)
return ems_friendly_name("unit#{unit_number + 1}_#{obj_name}")
new_name = ems_friendly_name("unit#{unit_number + 1}_#{obj_name}")

# Need to fix HWPH outlet node name
Comment thread
shorowit marked this conversation as resolved.
if new_name.include?('_Outlet') && new_name.include?(ems_friendly_name(Constants::ObjectTypeWaterHeater))
new_name.gsub!('_Outlet', ' Outlet')
end
return new_name
end

# Prefix all object names using using a provided unit number.
Expand Down
2 changes: 1 addition & 1 deletion HPXMLtoOpenStudio/resources/schedules.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ def initialize(name, used_by_unavailable_periods, can_be_stochastic, type)
HeatingSetpoint: Column.new('heating_setpoint', false, false, :setpoint),
CoolingSetpoint: Column.new('cooling_setpoint', false, false, :setpoint),
WaterHeaterSetpoint: Column.new('water_heater_setpoint', false, false, :setpoint),
WaterHeaterOperatingMode: Column.new('water_heater_operating_mode', false, false, :zero_or_one),
WaterHeaterHPWHOperatingMode: Column.new('water_heater_operating_mode', false, false, :zero_or_one),
Battery: Column.new('battery', false, false, :neg_one_to_one),
BatteryCharging: Column.new('battery_charging', true, false, nil),
BatteryDischarging: Column.new('battery_discharging', true, false, nil),
Expand Down
Loading