Skip to content

Commit

Permalink
Merge pull request #1740 from NREL/furnace_heating_airflow_bugfix
Browse files Browse the repository at this point in the history
Fix furnace heating airflow bug
  • Loading branch information
shorowit authored Jun 3, 2024
2 parents 32afb15 + 3a6f553 commit 5ca2033
Show file tree
Hide file tree
Showing 12 changed files with 1,402 additions and 1,394 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## OpenStudio-HPXML v1.8.1

__Bugfixes__
- Fixes cfm/ton restriction from incorrectly applying to furnace heating airflow rate.

## OpenStudio-HPXML v1.8.0

__New Features__
Expand Down
8 changes: 4 additions & 4 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>19f754cf-2165-45e2-9387-46f301d3d453</version_id>
<version_modified>2024-05-29T18:54:28Z</version_modified>
<version_id>7d1883e2-86ba-4a5a-a8ab-82db02825ebb</version_id>
<version_modified>2024-06-03T15:59:10Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -387,7 +387,7 @@
<filename>hvac_sizing.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>258360CD</checksum>
<checksum>DF70AD3A</checksum>
</file>
<file>
<filename>lighting.rb</filename>
Expand Down Expand Up @@ -597,7 +597,7 @@
<filename>version.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>0CAA1F93</checksum>
<checksum>29402A34</checksum>
</file>
<file>
<filename>waterheater.rb</filename>
Expand Down
77 changes: 40 additions & 37 deletions HPXMLtoOpenStudio/resources/hvac_sizing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,7 @@ def self.apply_hvac_duct_loads_heating(mj, zone, hvac_loads, zone_loads, all_spa
'''
return if hvac_heating.nil? || (hvac_loads.Heat_Tot <= 0) || hvac_heating.distribution_system.nil? || hvac_heating.distribution_system.ducts.empty?

supply_air_temp = hvac_heating.additional_properties.supply_air_temp
hvac_heating_ap = hvac_heating.additional_properties

init_heat_load = hvac_loads.Heat_Tot

Expand All @@ -1603,7 +1603,8 @@ def self.apply_hvac_duct_loads_heating(mj, zone, hvac_loads, zone_loads, all_spa
heat_load_prev = heat_load_next

# Calculate the new heating air flow rate
heat_cfm = calc_airflow_rate_manual_s(mj, heat_load_next, (supply_air_temp - mj.heat_setpoint), nil)
heating_delta_t = hvac_heating_ap.supply_air_temp - mj.heat_setpoint
heat_cfm = calc_airflow_rate_manual_s(mj, heat_load_next, heating_delta_t)

dse_Qs, dse_Qr = calc_duct_leakages_cfm25(hvac_heating.distribution_system, heat_cfm)

Expand Down Expand Up @@ -1644,7 +1645,7 @@ def self.apply_hvac_duct_loads_cooling(mj, zone, hvac_loads, zone_loads, all_spa

return if hvac_cooling.nil? || (hvac_loads.Cool_Sens <= 0) || hvac_cooling.distribution_system.nil? || hvac_cooling.distribution_system.ducts.empty?

leaving_air_temp = hvac_cooling.additional_properties.leaving_air_temp
hvac_cooling_ap = hvac_cooling.additional_properties

init_cool_load_sens = hvac_loads.Cool_Sens
init_cool_load_lat = hvac_loads.Cool_Lat
Expand All @@ -1661,7 +1662,8 @@ def self.apply_hvac_duct_loads_cooling(mj, zone, hvac_loads, zone_loads, all_spa
delta = 1
cool_load_tot_next = init_cool_load_sens + init_cool_load_lat

cool_cfm = calc_airflow_rate_manual_s(mj, init_cool_load_sens, (mj.cool_setpoint - leaving_air_temp), nil)
cooling_delta_t = mj.cool_setpoint - hvac_cooling_ap.leaving_air_temp
cool_cfm = calc_airflow_rate_manual_s(mj, init_cool_load_sens, cooling_delta_t)
_dse_Qs, dse_Qr = calc_duct_leakages_cfm25(hvac_cooling.distribution_system, cool_cfm)

for _iter in 1..50
Expand All @@ -1673,11 +1675,11 @@ def self.apply_hvac_duct_loads_cooling(mj, zone, hvac_loads, zone_loads, all_spa
cool_load_tot = cool_load_lat + cool_load_sens

# Calculate the new cooling air flow rate
cool_cfm = calc_airflow_rate_manual_s(mj, cool_load_sens, (mj.cool_setpoint - leaving_air_temp), nil)
cool_cfm = calc_airflow_rate_manual_s(mj, cool_load_sens, cooling_delta_t)

dse_Qs, dse_Qr = calc_duct_leakages_cfm25(hvac_cooling.distribution_system, cool_cfm)

dse_DE, _dse_dTe_cooling, _cool_duct_sens = calc_delivery_effectiveness_cooling(mj, dse_Qs, dse_Qr, leaving_air_temp, cool_cfm, cool_load_sens, dse_Tamb_s, dse_Tamb_r, dse_As, dse_Ar, mj.cool_setpoint, dse_Fregain_s, dse_Fregain_r, cool_load_tot, dse_h_r, supply_r, return_r)
dse_DE, _dse_dTe_cooling, _cool_duct_sens = calc_delivery_effectiveness_cooling(mj, dse_Qs, dse_Qr, hvac_cooling_ap.leaving_air_temp, cool_cfm, cool_load_sens, dse_Tamb_s, dse_Tamb_r, dse_As, dse_Ar, mj.cool_setpoint, dse_Fregain_s, dse_Fregain_r, cool_load_tot, dse_h_r, supply_r, return_r)

cool_load_tot_next = (init_cool_load_sens + init_cool_load_lat) / dse_DE

Expand Down Expand Up @@ -1759,7 +1761,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
if not cooling_type.nil?
hvac_cooling_ap = hvac_cooling.additional_properties
is_ducted = !hvac_cooling.distribution_system.nil?
leaving_air_temp = hvac_cooling.additional_properties.leaving_air_temp
cooling_delta_t = mj.cool_setpoint - hvac_cooling_ap.leaving_air_temp
oversize_limit, oversize_delta, undersize_limit = get_hvac_size_limits(hvac_cooling)
end

Expand Down Expand Up @@ -1794,7 +1796,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
sens_cap_rated = cool_cap_rated * hvac_cooling_shr

# Calculate the air flow rate required for design conditions
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Cool_Load_Sens, (mj.cool_setpoint - leaving_air_temp), cool_cap_rated)
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Cool_Load_Sens, cooling_delta_t, dx_capacity: cool_cap_rated)

sensible_cap_curve_value = process_curve_fit(hvac_sizings.Cool_Airflow, hvac_sizings.Cool_Load_Tot, entering_temp)
sens_cap_design = sens_cap_rated * sensible_cap_curve_value
Expand Down Expand Up @@ -1823,7 +1825,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva

cool_load_sens_cap_design = hvac_sizings.Cool_Load_Lat / ((total_cap_curve_value / hvac_cooling_shr - \
(b_sens + d_sens * entering_temp) / \
(1.1 * mj.acf * (mj.cool_setpoint - leaving_air_temp))) / \
(1.1 * mj.acf * cooling_delta_t)) / \
(a_sens + c_sens * entering_temp) - 1.0)

# Ensure equipment is not being undersized
Expand Down Expand Up @@ -1883,7 +1885,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
end

# Calculate the final air flow rate using final sensible capacity at design
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, (mj.cool_setpoint - leaving_air_temp), hvac_sizings.Cool_Capacity)
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, cooling_delta_t, dx_capacity: hvac_sizings.Cool_Capacity)

elsif [HPXML::HVACTypeHeatPumpMiniSplit,
HPXML::HVACTypeMiniSplitAirConditioner].include?(cooling_type) && !is_ducted
Expand Down Expand Up @@ -1958,7 +1960,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva

cool_load_sens_cap_design = (hvac_sizings.Cool_Capacity_Sens * sensible_cap_curve_value /
(1.0 + (1.0 - coil_bf * bypass_factor_curve_value) *
(80.0 - mj.cool_setpoint) / (mj.cool_setpoint - leaving_air_temp)))
(80.0 - mj.cool_setpoint) / cooling_delta_t))
cool_load_lat_cap_design = hvac_sizings.Cool_Load_Tot - cool_load_sens_cap_design

# Adjust Sizing so that coil sensible at design >= CoolingLoad_Sens, and coil latent at design >= CoolingLoad_Lat, and equipment SHRRated is maintained.
Expand All @@ -1975,16 +1977,16 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
# Recalculate the air flow rate in case the oversizing limit has been used
cool_load_sens_cap_design = (hvac_sizings.Cool_Capacity_Sens * sensible_cap_curve_value /
(1.0 + (1.0 - coil_bf * bypass_factor_curve_value) *
(80.0 - mj.cool_setpoint) / (mj.cool_setpoint - leaving_air_temp)))
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, (mj.cool_setpoint - leaving_air_temp), hvac_sizings.Cool_Capacity)
(80.0 - mj.cool_setpoint) / cooling_delta_t))
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, cooling_delta_t, dx_capacity: hvac_sizings.Cool_Capacity)

elsif HPXML::HVACTypeEvaporativeCooler == cooling_type

hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot
hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Load_Sens
if mj.cool_setpoint - leaving_air_temp > 0
if cooling_delta_t > 0
# See Manual S Section 4-4 Direct Evaporative Cooling: Blower Cfm
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Cool_Load_Sens, (mj.cool_setpoint - leaving_air_temp), nil)
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Cool_Load_Sens, cooling_delta_t)
else
hvac_sizings.Cool_Airflow = hpxml_bldg.building_construction.conditioned_floor_area * 2.0 # Use industry rule of thumb sizing method adopted by HEScore
end
Expand Down Expand Up @@ -2015,7 +2017,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
if not heating_type.nil?
hvac_heating_ap = hvac_heating.additional_properties
is_ducted = !hvac_heating.distribution_system.nil?
supply_air_temp = hvac_heating.additional_properties.supply_air_temp
heating_delta_t = hvac_heating_ap.supply_air_temp - mj.heat_setpoint

if hvac_heating.is_a?(HPXML::HeatingSystem) && hvac_heating.is_heat_pump_backup_system
# Adjust heating load using the HP backup calculation
Expand All @@ -2029,7 +2031,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
hvac_sizings.Heat_Load = calculate_heat_pump_backup_load(mj, hvac_hp, hvac_sizings.Heat_Load, hp_sizing_values.Heat_Capacity, hp_heating_speed, hpxml_bldg)
end
elsif not hvac_cooling.nil? && hvac_cooling.has_integrated_heating
supply_air_temp = hvac_cooling.additional_properties.supply_air_temp
heating_delta_t = hvac_cooling_ap.supply_air_temp - mj.heat_setpoint
end

if hvac_sizings.Heat_Load <= 0
Expand All @@ -2052,7 +2054,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva

hvac_sizings.Heat_Capacity_Supp = calculate_heat_pump_backup_load(mj, hvac_heating, hvac_sizings.Heat_Load_Supp, hvac_sizings.Heat_Capacity, hvac_heating_speed, hpxml_bldg)
if (heating_type == HPXML::HVACTypeHeatPumpAirToAir) || (heating_type == HPXML::HVACTypeHeatPumpMiniSplit && is_ducted)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, (supply_air_temp - mj.heat_setpoint), hvac_sizings.Heat_Capacity, hvac_sizings.Cool_Airflow)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, heating_delta_t, dx_capacity: hvac_sizings.Heat_Capacity, hp_cooling_cfm: hvac_sizings.Cool_Airflow)
else
hvac_sizings.Heat_Airflow = calc_airflow_rate_user(hvac_sizings.Heat_Capacity, hvac_heating_ap.heat_rated_cfm_per_ton[hvac_heating_speed], hvac_heating_ap.heat_capacity_ratios[hvac_heating_speed])
end
Expand All @@ -2078,27 +2080,27 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr
cool_load_sens_cap_design = (hvac_sizings.Cool_Capacity_Sens * sensible_cap_curve_value /
(1.0 + (1.0 - gshp_coil_bf * bypass_factor_curve_value) *
(80.0 - mj.cool_setpoint) / (mj.cool_setpoint - leaving_air_temp)))
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, (mj.cool_setpoint - leaving_air_temp), hvac_sizings.Cool_Capacity)
(80.0 - mj.cool_setpoint) / cooling_delta_t))
hvac_sizings.Cool_Airflow = calc_airflow_rate_manual_s(mj, cool_load_sens_cap_design, cooling_delta_t, dx_capacity: hvac_sizings.Cool_Capacity)
else
hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load
hvac_sizings.Heat_Capacity_Supp = hvac_sizings.Heat_Load_Supp
end
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, (supply_air_temp - mj.heat_setpoint), hvac_sizings.Heat_Capacity, hvac_sizings.Cool_Airflow)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, heating_delta_t, dx_capacity: hvac_sizings.Heat_Capacity, hp_cooling_cfm: hvac_sizings.Cool_Airflow)

elsif [HPXML::HVACTypeHeatPumpWaterLoopToAir].include? heating_type

hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load
hvac_sizings.Heat_Capacity_Supp = hvac_sizings.Heat_Load_Supp

hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, (supply_air_temp - mj.heat_setpoint), hvac_sizings.Heat_Capacity)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, heating_delta_t, dx_capacity: hvac_sizings.Heat_Capacity)

elsif (heating_type == HPXML::HVACTypeFurnace) || ((not hvac_cooling.nil?) && hvac_cooling.has_integrated_heating)

hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load
hvac_sizings.Heat_Capacity_Supp = 0.0

hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, (supply_air_temp - mj.heat_setpoint), hvac_sizings.Heat_Capacity)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, heating_delta_t)

elsif [HPXML::HVACTypeStove,
HPXML::HVACTypeSpaceHeater,
Expand All @@ -2114,7 +2116,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva
hvac_sizings.Heat_Airflow = UnitConversions.convert(hvac_sizings.Heat_Capacity, 'Btu/hr', 'ton') * hvac_heating_ap.heat_rated_cfm_per_ton[0]
else
# Autosized airflow rate
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, (supply_air_temp - mj.heat_setpoint), hvac_sizings.Heat_Capacity)
hvac_sizings.Heat_Airflow = calc_airflow_rate_manual_s(mj, hvac_sizings.Heat_Capacity, heating_delta_t)
end

elsif [HPXML::HVACTypeBoiler,
Expand Down Expand Up @@ -2716,34 +2718,35 @@ def self.get_ventilation_data(hpxml_bldg)
return [q_unbal, q_bal, q_preheat, q_precool, q_recirc, bal_sens_eff, bal_lat_eff]
end

def self.calc_airflow_rate_manual_s(mj, sens_load_or_capacity, deltaT, rated_capacity, corresponding_cooling_airflow_rate = nil)
def self.calc_airflow_rate_manual_s(mj, sens_load_or_capacity, delta_t, dx_capacity: nil, hp_cooling_cfm: nil)
# Airflow sizing following Manual S based on design calculation
airflow_rate = sens_load_or_capacity / (1.1 * mj.acf * deltaT)
airflow_cfm = sens_load_or_capacity / (1.1 * mj.acf * delta_t)

if not rated_capacity.nil?
if not dx_capacity.nil?
# Ensure the air flow rate is between 300 and 400 cfm/ton for typical DX equipment.
# Recommendation by Hugh Henderson.
rated_capacity_tons = UnitConversions.convert(rated_capacity, 'Btu/hr', 'ton')
if airflow_rate / rated_capacity_tons > 400
airflow_rate = 400.0 * rated_capacity_tons
elsif airflow_rate / rated_capacity_tons < 300
airflow_rate = 300.0 * rated_capacity_tons
rated_capacity_tons = UnitConversions.convert(dx_capacity, 'Btu/hr', 'ton')
if airflow_cfm / rated_capacity_tons > 400
airflow_cfm = 400.0 * rated_capacity_tons
elsif airflow_cfm / rated_capacity_tons < 300
airflow_cfm = 300.0 * rated_capacity_tons
end
end

if corresponding_cooling_airflow_rate.to_f > 0
if hp_cooling_cfm.to_f > 0
# For a heat pump, ensure the heating airflow rate is within 30% of the cooling airflow rate.
# Recommendation by Hugh Henderson.
airflow_rate = [airflow_rate, 0.7 * corresponding_cooling_airflow_rate].max
airflow_rate = [airflow_rate, 1.3 * corresponding_cooling_airflow_rate].min
airflow_cfm = [airflow_cfm, 0.7 * hp_cooling_cfm].max
airflow_cfm = [airflow_cfm, 1.3 * hp_cooling_cfm].min
end

return airflow_rate
return airflow_cfm
end

def self.calc_airflow_rate_user(capacity, rated_cfm_per_ton, capacity_ratio)
# Airflow determined by user setting, not based on design
return rated_cfm_per_ton * capacity_ratio * UnitConversions.convert(capacity, 'Btu/hr', 'ton') # Maximum air flow under heating operation
airflow_cfm = rated_cfm_per_ton * capacity_ratio * UnitConversions.convert(capacity, 'Btu/hr', 'ton') # Maximum air flow under heating operation
return airflow_cfm
end

def self.calc_gshp_clg_curve_value(cool_cap_curve_spec, cool_sh_curve_spec, wb_temp, db_temp, w_temp, vfr_air, loop_flow = nil, rated_vfr_air = nil)
Expand Down
2 changes: 1 addition & 1 deletion HPXMLtoOpenStudio/resources/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

class Version
OS_HPXML_Version = '1.8.0' # Version of the OS-HPXML workflow
OS_HPXML_Version = '1.8.1' # Version of the OS-HPXML workflow
OS_Version = '3.8.0' # Required version of OpenStudio (can be 'X.X' or 'X.X.X')
HPXML_Version = '4.0' # HPXML schemaVersion

Expand Down
14 changes: 7 additions & 7 deletions workflow/tests/base_results/results_hers_dse.csv
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Test Case,Heat/Cool (kWh or therm),HVAC Fan (kWh)
HVAC3a.xml,557.36,450.16
HVAC3a.xml,561.89,346.7
HVAC3e.xml,6217.5,957.76
HVAC3b.xml,673.73,656.77
HVAC3c.xml,585.15,522.55
HVAC3d.xml,637.48,621.6
HVAC3f.xml,7859.29,1211.56
HVAC3g.xml,6632.49,1019.3
HVAC3h.xml,7388.32,1113.67
HVAC3b.xml,684.33,610.17
HVAC3c.xml,592.52,404.44
HVAC3d.xml,646.24,575.88
HVAC3f.xml,7859.87,1211.56
HVAC3g.xml,6633.37,1019.3
HVAC3h.xml,7389.79,1113.96
10 changes: 5 additions & 5 deletions workflow/tests/base_results/results_hers_hvac.csv
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Test Case,HVAC (kWh or therm),HVAC Fan (kWh)
HVAC1a.xml,6160.06,785.72
HVAC1b.xml,4574.55,785.72
HVAC2a.xml,635.91,620.14
HVAC2b.xml,551.12,620.14
HVAC1a.xml,6215.45,958.05
HVAC1b.xml,4618.8,958.05
HVAC2a.xml,638.17,568.85
HVAC2b.xml,553.08,568.85
HVAC2c.xml,7589.08,1187.82
HVAC2d.xml,4600.92,1187.82
HVAC2e.xml,14536.62,620.14
HVAC2e.xml,14588.2,568.85
Loading

0 comments on commit 5ca2033

Please sign in to comment.