Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ANSI 301-2022: Air Distribution System Leakage Split #1629

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
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
15 changes: 13 additions & 2 deletions BuildResidentialHPXML/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2820,7 +2820,7 @@ The leakage value to outside for the supply ducts.
- **Name:** ``ducts_supply_leakage_to_outside_value``
- **Type:** ``Double``

- **Required:** ``true``
- **Required:** ``false``

<br/>

Expand All @@ -2831,7 +2831,18 @@ The leakage value to outside for the return ducts.
- **Name:** ``ducts_return_leakage_to_outside_value``
- **Type:** ``Double``

- **Required:** ``true``
- **Required:** ``false``

<br/>

**Ducts: Total Leakage to Outside Value**

The leakage value to outside for the supply and return ducts.

- **Name:** ``ducts_total_leakage_to_outside_value``
- **Type:** ``Double``

- **Required:** ``false``

<br/>

Expand Down
39 changes: 26 additions & 13 deletions BuildResidentialHPXML/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1702,16 +1702,19 @@ def arguments(model) # rubocop:disable Lint/UnusedMethodArgument
arg.setDefaultValue(HPXML::UnitsPercent)
args << arg

arg = OpenStudio::Measure::OSArgument::makeDoubleArgument('ducts_supply_leakage_to_outside_value', true)
arg = OpenStudio::Measure::OSArgument::makeDoubleArgument('ducts_supply_leakage_to_outside_value', false)
arg.setDisplayName('Ducts: Supply Leakage to Outside Value')
arg.setDescription('The leakage value to outside for the supply ducts.')
arg.setDefaultValue(0.1)
args << arg

arg = OpenStudio::Measure::OSArgument::makeDoubleArgument('ducts_return_leakage_to_outside_value', true)
arg = OpenStudio::Measure::OSArgument::makeDoubleArgument('ducts_return_leakage_to_outside_value', false)
arg.setDisplayName('Ducts: Return Leakage to Outside Value')
arg.setDescription('The leakage value to outside for the return ducts.')
arg.setDefaultValue(0.1)
args << arg

arg = OpenStudio::Measure::OSArgument::makeDoubleArgument('ducts_total_leakage_to_outside_value', false)
arg.setDisplayName('Ducts: Total Leakage to Outside Value')
arg.setDescription('The leakage value to outside for the supply and return ducts.')
args << arg

arg = OpenStudio::Measure::OSArgument::makeChoiceArgument('ducts_supply_location', duct_location_choices, false)
Expand Down Expand Up @@ -5821,15 +5824,25 @@ def self.set_hvac_distribution(hpxml_bldg, args)
end

def self.set_duct_leakages(args, hvac_distribution)
hvac_distribution.duct_leakage_measurements.add(duct_type: HPXML::DuctTypeSupply,
duct_leakage_units: args[:ducts_leakage_units],
duct_leakage_value: args[:ducts_supply_leakage_to_outside_value],
duct_leakage_total_or_to_outside: HPXML::DuctLeakageToOutside)

hvac_distribution.duct_leakage_measurements.add(duct_type: HPXML::DuctTypeReturn,
duct_leakage_units: args[:ducts_leakage_units],
duct_leakage_value: args[:ducts_return_leakage_to_outside_value],
duct_leakage_total_or_to_outside: HPXML::DuctLeakageToOutside)
if args[:ducts_supply_leakage_to_outside_value].is_initialized
hvac_distribution.duct_leakage_measurements.add(duct_type: HPXML::DuctTypeSupply,
duct_leakage_units: args[:ducts_leakage_units],
duct_leakage_value: args[:ducts_supply_leakage_to_outside_value].get,
duct_leakage_total_or_to_outside: HPXML::DuctLeakageToOutside)
end

if args[:ducts_return_leakage_to_outside_value].is_initialized
hvac_distribution.duct_leakage_measurements.add(duct_type: HPXML::DuctTypeReturn,
duct_leakage_units: args[:ducts_leakage_units],
duct_leakage_value: args[:ducts_return_leakage_to_outside_value].get,
duct_leakage_total_or_to_outside: HPXML::DuctLeakageToOutside)
end

if args[:ducts_total_leakage_to_outside_value].is_initialized
hvac_distribution.duct_leakage_measurements.add(duct_leakage_units: args[:ducts_leakage_units],
duct_leakage_value: args[:ducts_total_leakage_to_outside_value].get,
duct_leakage_total_or_to_outside: HPXML::DuctLeakageToOutside)
end
end

def self.get_location(location, foundation_type, attic_type)
Expand Down
22 changes: 14 additions & 8 deletions BuildResidentialHPXML/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>build_residential_hpxml</name>
<uid>a13a8983-2b01-4930-8af2-42030b6e4233</uid>
<version_id>f33087cd-1090-4c66-b1f8-5dff15c58848</version_id>
<version_modified>2024-02-29T22:19:45Z</version_modified>
<version_id>5afe18c6-fb4a-40ae-b422-8b4d5bc41736</version_id>
<version_modified>2024-03-01T22:05:16Z</version_modified>
<xml_checksum>2C38F48B</xml_checksum>
<class_name>BuildResidentialHPXML</class_name>
<display_name>HPXML Builder</display_name>
Expand Down Expand Up @@ -3450,18 +3450,24 @@
<display_name>Ducts: Supply Leakage to Outside Value</display_name>
<description>The leakage value to outside for the supply ducts.</description>
<type>Double</type>
<required>true</required>
<required>false</required>
<model_dependent>false</model_dependent>
<default_value>0.1</default_value>
</argument>
<argument>
<name>ducts_return_leakage_to_outside_value</name>
<display_name>Ducts: Return Leakage to Outside Value</display_name>
<description>The leakage value to outside for the return ducts.</description>
<type>Double</type>
<required>true</required>
<required>false</required>
<model_dependent>false</model_dependent>
</argument>
<argument>
<name>ducts_total_leakage_to_outside_value</name>
<display_name>Ducts: Total Leakage to Outside Value</display_name>
<description>The leakage value to outside for the supply and return ducts.</description>
<type>Double</type>
<required>false</required>
<model_dependent>false</model_dependent>
<default_value>0.1</default_value>
</argument>
<argument>
<name>ducts_supply_location</name>
Expand Down Expand Up @@ -7245,7 +7251,7 @@
<filename>README.md</filename>
<filetype>md</filetype>
<usage_type>readme</usage_type>
<checksum>45412C9E</checksum>
<checksum>8F91F9A3</checksum>
</file>
<file>
<filename>README.md.erb</filename>
Expand All @@ -7262,7 +7268,7 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>5E5697F8</checksum>
<checksum>BE2125A6</checksum>
</file>
<file>
<filename>geometry.rb</filename>
Expand Down
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ __New Features__
- Allow additional outdoor design condition inputs: `DailyTemperatureRange` and `HumidityDifference`.
- Miscellaneous improvements.
- Adds more error-checking for inappropriate inputs (e.g., HVAC SHR=0 or clothes washer IMEF=0).
- Allow alternative label energy use (W) input for ceiling fans.
- Allows alternative label energy use (W) input for ceiling fans.
- Allows the total duct leakage to outside input. The total duct leakage to outside will be split equally between supply and return side of the air distribution system.

__Bugfixes__
- Fixes error if using AllowIncreasedFixedCapacities=true w/ HP detailed performance data.
Expand Down
11 changes: 9 additions & 2 deletions HPXMLtoOpenStudio/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1961,9 +1961,16 @@ def create_ducts(model, hvac_distribution, spaces)
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 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]
if not 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]
end

next if hvac_distribution.duct_leakage_measurements.any? { |duct_leakage_measurement| !duct_leakage_measurement.duct_type.nil? } # Use the total_leakage_to_outside only when leakage_to_outside for supply/returen ducts are not provided.

[HPXML::DuctTypeSupply, HPXML::DuctTypeReturn].each do |duct_type|
leakage_to_outside[duct_type] = [(duct_leakage_measurement.duct_leakage_value / 2), duct_leakage_measurement.duct_leakage_units]
end
end

# Duct location, R-value, Area
Expand Down
14 changes: 10 additions & 4 deletions HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1639,16 +1639,22 @@
<sch:pattern>
<sch:title>[AirDistributionType=RegularVelocityOrGravity]</sch:title>
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:HVAC/h:HVACDistribution/h:DistributionSystemType/h:AirDistribution[h:AirDistributionType[text()="regular velocity" or text()="gravity"]]'>
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) = 1'>Expected 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) = 1'>Expected 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) + count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &gt;= 1'>Expected 1 or more element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"] | DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) + count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &gt;= 1'>Expected 1 or more element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"] | DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
</sch:rule>
</sch:pattern>

<sch:pattern>
<sch:title>[AirDistributionType=FanCoil]</sch:title>
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:HVAC/h:HVACDistribution/h:DistributionSystemType/h:AirDistribution[h:AirDistributionType[text()="fan coil"]]'>
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50") and h:TotalOrToOutside="to outside"]) &lt;= 1'>Expected 0 or 1 element(s) for xpath: DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="supply"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) + count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &gt;= 0'>Expected 0 or more element(s) for xpath: DuctLeakageMeasurement[DuctType="supply"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"] | DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
<sch:assert role='ERROR' test='count(h:DuctLeakageMeasurement[h:DuctType="return"]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) + count(h:DuctLeakageMeasurement[not(h:DuctType)]/h:DuctLeakage[(h:Units="CFM25" or h:Units="CFM50" or h:Units="Percent") and h:TotalOrToOutside="to outside"]) &gt;= 0'>Expected 0 or more element(s) for xpath: DuctLeakageMeasurement[DuctType="return"]/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"] | DuctLeakageMeasurement/DuctLeakage[(Units="CFM25" or Units="CFM50" or Units="Percent") and TotalOrToOutside="to outside"]</sch:assert> <!-- See [DuctLeakage=CFM] or [DuctLeakage=Percent] -->
</sch:rule>
</sch:pattern>

Expand Down
29 changes: 21 additions & 8 deletions HPXMLtoOpenStudio/resources/hvac_sizing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2425,14 +2425,27 @@ def self.calc_duct_leakages_cfm25(distribution_system, system_cfm)

distribution_system.duct_leakage_measurements.each do |m|
next if m.duct_leakage_total_or_to_outside != HPXML::DuctLeakageToOutside
next unless [HPXML::DuctTypeSupply, HPXML::DuctTypeReturn].include? m.duct_type

if m.duct_leakage_units == HPXML::UnitsPercent
cfms[m.duct_type] += m.duct_leakage_value * 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)

if not m.duct_type.nil?
duct_leakage = m.duct_leakage_value
if m.duct_leakage_units == HPXML::UnitsPercent
cfms[m.duct_type] += duct_leakage * system_cfm
elsif m.duct_leakage_units == HPXML::UnitsCFM25
cfms[m.duct_type] += duct_leakage
elsif m.duct_leakage_units == HPXML::UnitsCFM50
cfms[m.duct_type] += Airflow.calc_air_leakage_at_diff_pressure(0.65, duct_leakage, 50.0, 25.0)
end
else # Split the air distribution leakage equally between supply and return side when the air distribution system leakage split between the supply and return is not measured
duct_leakage = m.duct_leakage_value / 2
[HPXML::DuctTypeSupply, HPXML::DuctTypeReturn].each do |duct_type|
if m.duct_leakage_units == HPXML::UnitsPercent
cfms[duct_type] += duct_leakage * system_cfm
elsif m.duct_leakage_units == HPXML::UnitsCFM25
cfms[duct_type] += duct_leakage
elsif m.duct_leakage_units == HPXML::UnitsCFM50
cfms[duct_type] += Airflow.calc_air_leakage_at_diff_pressure(0.65, duct_leakage, 50.0, 25.0)
end
end
end
end

Expand Down
16 changes: 16 additions & 0 deletions HPXMLtoOpenStudio/tests/test_airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,22 @@ def test_ducts_leakage_percent
assert_in_epsilon(return_leakage_frac, program_values['f_ret'].sum, 0.01)
end

def test_ducts_total_leakage_to_outside
args_hash = {}
args_hash['hpxml_path'] = File.absolute_path(File.join(@sample_files_path, 'base-hvac-ducts-total-leakage-to-outside.xml'))
model, _hpxml, hpxml_bldg = _test_measure(args_hash)

# Get HPXML values
leakage = hpxml_bldg.hvac_distributions[0].duct_leakage_measurements.select { |m| m.duct_type.nil? }[0]
supply_leakage_cfm25 = leakage.duct_leakage_value / 2
return_leakage_cfm25 = leakage.duct_leakage_value / 2

# Check ducts program
program_values = get_ems_values(model.getEnergyManagementSystemSubroutines, 'duct subroutine')
assert_in_epsilon(supply_leakage_cfm25, UnitConversions.convert(program_values['f_sup'].sum, 'm^3/s', 'cfm'), 0.01)
assert_in_epsilon(return_leakage_cfm25, UnitConversions.convert(program_values['f_ret'].sum, 'm^3/s', 'cfm'), 0.01)
end

def test_ducts_ua
['base.xml',
'base-hvac-ducts-area-multipliers.xml',
Expand Down
Loading