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

Add Uniform Energy Factor conversion #1633

Merged
merged 8 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
128 changes: 106 additions & 22 deletions lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,29 +85,21 @@ def water_heater_mixed_apply_efficiency(water_heater_mixed)
base_ef = wh_props['energy_factor_base']
vol_drt = wh_props['energy_factor_volume_derate']
ef = base_ef - (vol_drt * volume_gal)
# Calculate the skin loss coefficient (UA)
# differently depending on the fuel type
if fuel_type == 'Electricity'
# Fixed water heater efficiency per PNNL
water_heater_eff = 1.0
ua_btu_per_hr_per_f = (41_094 * (1 / ef - 1)) / (24 * 67.5)
elsif fuel_type == 'NaturalGas'
# Fixed water heater thermal efficiency per PNNL
water_heater_eff = 0.82
# Calculate the Recovery Efficiency (RE)
# based on a fixed capacity of 75,000 Btu/hr
# and a fixed volume of 40 gallons by solving
# this system of equations:
# ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap)))
# 0.82 = (ua*67.5+cap*re)/cap
# Solutions to the system of equations were determined
# for discrete values of EF and modeled using a regression
re = -0.1137 * ef**2 + 0.1997 * ef + 0.731
# Calculate the skin loss coefficient (UA)
# Input capacity is assumed to be the output capacity
# divided by a burner efficiency of 80%
ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 0.8 / 67.5
water_heater_eff, ua_btu_per_hr_per_f = water_heater_convert_energy_factor_to_thermal_efficiency_and_ua(fuel_type, ef, capacity_btu_per_hr)
# Two booster water heaters
ua_btu_per_hr_per_f = water_heater_mixed.name.to_s.include?('Booster') ? ua_btu_per_hr_per_f * 2 : ua_btu_per_hr_per_f
end

if (wh_props['uniform_energy_factor_base'] && wh_props['uniform_energy_factor_volume_allowance']) || wh_props['uniform_energy_factor']
if wh_props['uniform_energy_factor']
uef = wh_props['uniform_energy_factor']
else
base_uef = wh_props['uniform_energy_factor_base']
vol_drt = wh_props['uniform_energy_factor_volume_allowance']
uef = base_uef - (vol_drt * volume_gal)
end
ef = water_heater_convert_uniform_energy_factor_to_energy_factor(fuel_type, uef, capacity_btu_per_hr, volume_gal)
water_heater_eff, ua_btu_per_hr_per_f = water_heater_convert_energy_factor_to_thermal_efficiency_and_ua(fuel_type, ef, capacity_btu_per_hr)
# Two booster water heaters
ua_btu_per_hr_per_f = water_heater_mixed.name.to_s.include?('Booster') ? ua_btu_per_hr_per_f * 2 : ua_btu_per_hr_per_f
end
Expand Down Expand Up @@ -235,4 +227,96 @@ def water_heater_mixed_find_capacity(water_heater_mixed)

return capacity_btu_per_hr
end

# Get water heater sub type
#
# @param fuel_type [String] water heater fuel type
# @param capacity_btu_per_hr [Float] water heater capacity
# @param volume_gal [Float] water heater storage volume in gallons
# @return [String] returns water heater sub type
def water_heater_determine_sub_type(fuel_type, capacity_btu_per_hr, volume_gal)
sub_type = nil
capacity_w = OpenStudio.convert(capacity_btu_per_hr, 'Btu/hr', 'W').get
# source: https://energycodeace.com/site/custom/public/reference-ace-2019/index.html#!Documents/52residentialwaterheatingequipment.htm
if fuel_type == 'NaturalGas' && capacity_btu_per_hr <= 75_000 && (volume_gal >= 20 && volume_gal <= 100)
sub_type = 'consumer_storage'
elsif fuel_type == 'Electricity' && capacity_w <= 12_000 && (volume_gal >= 20 && volume_gal <= 120)
sub_type = 'consumer_storage'
elsif fuel_type == 'NaturalGas' && capacity_btu_per_hr < 105_000 && volume_gal < 120
sub_type = 'residential_duty'
elsif fuel_type == 'Oil' && capacity_btu_per_hr < 140_000 && volume_gal < 120
sub_type = 'residential_duty'
elsif fuel_type == 'Electricity' && capacity_w < 58_600 && volume_gal <= 2
sub_type = 'residential_duty'
elsif volume_gal <= 2
sub_type = 'instantaneous'
end

return sub_type
end

# Convert UEF to EF
#
# @param fuel_type [String] water heater fuel type
# @param uef [Float] water heater UEF
# @param capacity_btu_per_hr [Float] water heater capacity
# @param volume_gal [Float] water heater storage volume in gallons
# @return [Float] returns EF, energy factor
def water_heater_convert_uniform_energy_factor_to_energy_factor(fuel_type, uef, capacity_btu_per_hr, volume_gal)
# Get water heater sub type
sub_type = water_heater_determine_sub_type(fuel_type, capacity_btu_per_hr, volume_gal)

# source: RESNET, https://www.resnet.us/wp-content/uploads/RESNET-EF-Calculator-2017.xlsx
if sub_type.nil?
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "No sub type identified for #{water_heater_mixed.name}, EF = UEF is assumed.")
return uef
elsif sub_type == 'consumer_storage' && fuel_type == 'NaturalGas'
return 0.9066 * uef + 0.0711
elsif sub_type == 'consumer_storage' && fuel_type == 'Electricity'
return 2.4029 * uef - 1.2844
elsif sub_type == 'residential_duty' && (fuel_type == 'NaturalGas' || fuel_type == 'Oil')
return 1.0005 * uef + 0.0019
elsif sub_type == 'residential_duty' && fuel_type == 'Electricity'
return 1.0219 * uef - 0.0025
elsif sub_type == 'instantaneous'
return uef
else
OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.WaterHeaterMixed', "Invalid sub_type for #{water_heater_mixed.name}, EF = UEF is assumed.")
return uef
end
end

# Convert EF to Thermal Efficiency and storage tank UA
#
# @param fuel_type [String] water heater fuel type
# @param ef [Float] water heater EF, energy factor
# @param capacity_btu_per_hr [Float] water heater capacity in Btu/h
# @return [Array] returns water heater thermal efficiency and storage tank UA
def water_heater_convert_energy_factor_to_thermal_efficiency_and_ua(fuel_type, ef, capacity_btu_per_hr)
# Calculate the skin loss coefficient (UA)
# differently depending on the fuel type
if fuel_type == 'Electricity'
# Fixed water heater efficiency per PNNL
water_heater_eff = 1.0
ua_btu_per_hr_per_f = (41_094 * (1 / ef - 1)) / (24 * 67.5)
elsif fuel_type == 'NaturalGas'
# Fixed water heater thermal efficiency per PNNL
water_heater_eff = 0.82
# Calculate the Recovery Efficiency (RE)
# based on a fixed capacity of 75,000 Btu/hr
# and a fixed volume of 40 gallons by solving
# this system of equations:
# ua = (1/.95-1/re)/(67.5*(24/41094-1/(re*cap)))
# 0.82 = (ua*67.5+cap*re)/cap
# Solutions to the system of equations were determined
# for discrete values of EF and modeled using a regression
re = -0.1137 * ef**2 + 0.1997 * ef + 0.731
# Calculate the skin loss coefficient (UA)
# Input capacity is assumed to be the output capacity
# divided by a burner efficiency of 80%
ua_btu_per_hr_per_f = (water_heater_eff - re) * capacity_btu_per_hr / 0.8 / 67.5
end

return water_heater_eff, ua_btu_per_hr_per_f
end
end
23 changes: 23 additions & 0 deletions test/os_stds_methods/test_service_water_heating.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,27 @@ def test_add_hpwh_without_ems
# run the model to make sure it applies correctly
# model.save("output/#{test_name}_out.osm", true)
end

def test_water_heater_sub_type
std = Standard.build('90.1-2019')

# Gas water heaters
assert(std.water_heater_determine_sub_type('NaturalGas', 74000, 5) == "residential_duty")
assert(std.water_heater_determine_sub_type('NaturalGas', 74000, 20) == "consumer_storage")
assert(std.water_heater_determine_sub_type('NaturalGas', 76000, 5) == "residential_duty")

# Electricity water heaters
assert(std.water_heater_determine_sub_type('Electricity', 74000, 5).nil?)
assert(std.water_heater_determine_sub_type('Electricity', 74000, 2) == "residential_duty")
assert(std.water_heater_determine_sub_type('Electricity', 300000, 2) == "instantaneous")
end

def test_uef_to_ef()
std = Standard.build('90.1-2019')
assert(std.water_heater_convert_uniform_energy_factor_to_energy_factor('Electricity', 1, 1, 1) == 1.0194)
assert(std.water_heater_convert_uniform_energy_factor_to_energy_factor('Electricity', 1, 300000, 2) == 1)
assert(std.water_heater_convert_uniform_energy_factor_to_energy_factor('Electricity', 0, 74000, 2) == -0.0025)
assert(std.water_heater_convert_uniform_energy_factor_to_energy_factor('NaturalGas', 0, 76000, 5) == 0.0019)
assert(std.water_heater_convert_uniform_energy_factor_to_energy_factor('NaturalGas', 0, 74000, 20) == 0.0711)
end
end