-
Notifications
You must be signed in to change notification settings - Fork 58
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
Building Energy Standards Water Heater Data Update #1680
Changes from all commits
59864b6
7acbbde
97b964c
24d491e
a64ebca
7c89a90
6d95303
d9e5d08
9890117
a12429c
13ebb54
710306c
86d49a1
6963ad5
8b9bd8b
3bd7aac
b815089
485fb80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2362,14 +2362,17 @@ def model_apply_infiltration_standard(model) | |
# the objects will only be returned if the specified area is between the minimum_area and maximum_area values. | ||
# @param num_floors [Double] capacity of the object in question. If num_floors is supplied, | ||
# the objects will only be returned if the specified num_floors is between the minimum_floors and maximum_floors values. | ||
# @param fan_motor_hp [Double] fan motor brake horsepower. | ||
# @param volume [Double] Equipment storage capacity in gallons. | ||
# @param capacity_per_volume [Double] Equipment capacity per storage capacity in Btu/h/gal. | ||
# @return [Array] returns an array of hashes, one hash per object. Array is empty if no results. | ||
# @example Find all the schedule rules that match the name | ||
# rules = model_find_objects(standards_data['schedules'], 'name' => schedule_name) | ||
# if rules.size.zero? | ||
# OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Model', "Cannot find data for schedule: #{schedule_name}, will not be created.") | ||
# return false | ||
# end | ||
def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil) | ||
def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil, volume = nil, capacity_per_volume = nil) | ||
matching_objects = [] | ||
if hash_of_objects.is_a?(Hash) && hash_of_objects.key?('table') | ||
hash_of_objects = hash_of_objects['table'] | ||
|
@@ -2423,6 +2426,48 @@ def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = | |
end | ||
end | ||
|
||
# If volume was specified, narrow down the matching objects | ||
unless volume.nil? | ||
# Skip objects that don't have fields for minimum_storage and maximum_storage | ||
matching_objects = matching_objects.reject { |object| !object.key?('minimum_storage') || !object.key?('maximum_storage') } | ||
|
||
# Skip objects that don't have values specified for minimum_storage and maximum_storage | ||
matching_objects = matching_objects.reject { |object| object['minimum_storage'].nil? || object['maximum_storage'].nil? } | ||
|
||
# Skip objects whose the minimum volume is below or maximum volume above the specified volume | ||
matching_volume_objects = matching_objects.reject { |object| volume.to_f < object['minimum_storage'].to_f || volume.to_f > object['maximum_storage'].to_f } | ||
|
||
# If no object was found, round the volume down in case the number fell between the limits in the json file. | ||
if matching_volume_objects.size.zero? | ||
volume *= 0.99 | ||
# Skip objects whose minimum volume is below or maximum volume above the specified volume | ||
matching_objects = matching_objects.reject { |object| volume.to_f <= object['minimum_storage'].to_f || volume.to_f >= object['maximum_storage'].to_f } | ||
else | ||
matching_objects = matching_volume_objects | ||
end | ||
end | ||
|
||
# If capacity_per_volume was specified, narrow down the matching objects | ||
unless capacity_per_volume.nil? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider the ratio of capacity per volume as a lookup key. |
||
# Skip objects that don't have fields for minimum_capacity_per_storage and maximum_capacity_per_storage | ||
matching_objects = matching_objects.reject { |object| !object.key?('minimum_capacity_per_storage') || !object.key?('maximum_capacity_per_storage') } | ||
|
||
# Skip objects that don't have values specified for minimum_capacity_per_storage and maximum_capacity_per_storage | ||
matching_objects = matching_objects.reject { |object| object['minimum_capacity_per_storage'].nil? || object['maximum_capacity_per_storage'].nil? } | ||
|
||
# Skip objects whose the minimum capacity_per_volume is below or maximum capacity_per_volume above the specified capacity_per_volume | ||
matching_capacity_per_volume_objects = matching_objects.reject { |object| capacity_per_volume.to_f <= object['minimum_capacity_per_storage'].to_f || capacity_per_volume.to_f >= object['maximum_capacity_per_storage'].to_f } | ||
|
||
# If no object was found, round the volume down in case the number fell between the limits in the json file. | ||
if matching_capacity_per_volume_objects.size.zero? | ||
capacity_per_volume *= 0.99 | ||
# Skip objects whose minimum capacity_per_volume is below or maximum capacity_per_volume above the specified capacity_per_volume | ||
matching_objects = matching_objects.reject { |object| capacity_per_volume.to_f <= object['minimum_capacity_per_storage'].to_f || capacity_per_volume.to_f >= object['maximum_capacity_per_storage'].to_f } | ||
else | ||
matching_objects = matching_capacity_per_volume_objects | ||
end | ||
end | ||
|
||
# If fan_motor_bhp was specified, narrow down the matching objects | ||
unless fan_motor_bhp.nil? | ||
# Skip objects that don't have fields for minimum_capacity and maximum_capacity | ||
|
@@ -2512,8 +2557,8 @@ def model_find_objects(hash_of_objects, search_criteria, capacity = nil, date = | |
# 'type' => 'Enclosed', | ||
# } | ||
# motor_properties = self.model.find_object(motors, search_criteria, capacity: 2.5) | ||
def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil) | ||
matching_objects = model_find_objects(hash_of_objects, search_criteria, capacity, date, area, num_floors, fan_motor_bhp) | ||
def model_find_object(hash_of_objects, search_criteria, capacity = nil, date = nil, area = nil, num_floors = nil, fan_motor_bhp = nil, volume = nil, capacity_per_volume = nil) | ||
matching_objects = model_find_objects(hash_of_objects, search_criteria, capacity, date, area, num_floors, fan_motor_bhp, volume, capacity_per_volume) | ||
|
||
# Check the number of matching objects found | ||
if matching_objects.size.zero? | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,23 +54,15 @@ def water_heater_mixed_apply_efficiency(water_heater_mixed) | |
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, fuel type of #{fuel_type} is not yet supported, standard will not be applied.") | ||
end | ||
|
||
# Get the water heater properties | ||
search_criteria = {} | ||
search_criteria['template'] = template | ||
search_criteria['fuel_type'] = fuel_type | ||
wh_props = model_find_object(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr) | ||
unless wh_props | ||
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, cannot find water heater properties, cannot apply efficiency standard.") | ||
return false | ||
end | ||
wh_props = water_heater_mixed_get_efficiency_requirement(water_heater_mixed, fuel_type, capacity_btu_per_hr, volume_gal) | ||
return false if wh_props == {} | ||
|
||
# Calculate the water heater efficiency and | ||
# skin loss coefficient (UA) using different methods, | ||
# depending on the metrics specified by the standard | ||
water_heater_efficiency = nil | ||
ua_btu_per_hr_per_f = nil | ||
|
||
# Rarely specified by thermal efficiency alone | ||
if wh_props['thermal_efficiency'] && !wh_props['standby_loss_capacity_allowance'] | ||
thermal_efficiency = wh_props['thermal_efficiency'] | ||
water_heater_efficiency = thermal_efficiency | ||
|
@@ -98,20 +90,25 @@ def water_heater_mixed_apply_efficiency(water_heater_mixed) | |
vol_drt = wh_props['uniform_energy_factor_volume_allowance'] | ||
uniform_energy_factor = base_uniform_energy_factor - (vol_drt * volume_gal) | ||
end | ||
energy_factor = water_heater_convert_uniform_energy_factor_to_energy_factor(fuel_type, uniform_energy_factor, capacity_btu_per_hr, volume_gal) | ||
energy_factor = water_heater_convert_uniform_energy_factor_to_energy_factor(water_heater_mixed, fuel_type, uniform_energy_factor, capacity_btu_per_hr, volume_gal) | ||
water_heater_efficiency, ua_btu_per_hr_per_f = water_heater_convert_energy_factor_to_thermal_efficiency_and_ua(fuel_type, energy_factor, 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 | ||
|
||
# Typically specified this way for large electric water heaters | ||
if wh_props['standby_loss_base'] && wh_props['standby_loss_volume_allowance'] | ||
if wh_props['standby_loss_base'] && (wh_props['standby_loss_volume_allowance'] || wh_props['standby_loss_square_root_volume_allowance']) | ||
# Fixed water heater efficiency per PNNL | ||
water_heater_efficiency = 1.0 | ||
# Calculate the max allowable standby loss (SL) | ||
sl_base = wh_props['standby_loss_base'] | ||
sl_drt = wh_props['standby_loss_volume_allowance'] | ||
sl_btu_per_hr = sl_base + (sl_drt * Math.sqrt(volume_gal)) | ||
if wh_props['standby_loss_square_root_volume_allowance'] | ||
sl_drt = wh_props['standby_loss_square_root_volume_allowance'] | ||
sl_btu_per_hr = sl_base + (sl_drt * Math.sqrt(volume_gal)) | ||
else # standby_loss_volume_allowance | ||
sl_drt = wh_props['standby_loss_volume_allowance'] | ||
sl_btu_per_hr = sl_base + (sl_drt * volume_gal) | ||
end | ||
# Calculate the skin loss coefficient (UA) | ||
ua_btu_per_hr_per_f = @instvarbuilding_type == 'MidriseApartment' ? sl_btu_per_hr / 70 * 23 : sl_btu_per_hr / 70 | ||
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 | ||
|
@@ -124,21 +121,28 @@ def water_heater_mixed_apply_efficiency(water_heater_mixed) | |
# Calculate the percent loss per hr | ||
hr_loss_base = wh_props['hourly_loss_base'] | ||
hr_loss_allow = wh_props['hourly_loss_volume_allowance'] | ||
hrly_loss_pct = hr_loss_base + (hr_loss_allow / volume_gal) / 100.0 | ||
hrly_loss_pct = hr_loss_base + hr_loss_allow / volume_gal | ||
# Convert to Btu/hr, assuming: | ||
# Water at 120F, density = 8.25 lb/gal | ||
# 1 Btu to raise 1 lb of water 1 F | ||
# Therefore 8.25 Btu / gal of water * deg F | ||
# 70F delta-T between water and zone | ||
hrly_loss_btu_per_hr = hrly_loss_pct * volume_gal * 8.25 * 70 | ||
hrly_loss_btu_per_hr = (hrly_loss_pct / 100) * volume_gal * 8.25 * 70 | ||
Comment on lines
-127
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
# Calculate the skin loss coefficient (UA) | ||
ua_btu_per_hr_per_f = hrly_loss_btu_per_hr / 70 | ||
end | ||
|
||
# Typically specified this way for large natural gas water heaters | ||
if wh_props['standby_loss_capacity_allowance'] && wh_props['standby_loss_volume_allowance'] && wh_props['thermal_efficiency'] | ||
if wh_props['standby_loss_capacity_allowance'] && (wh_props['standby_loss_volume_allowance'] || wh_props['standby_loss_square_root_volume_allowance']) && wh_props['thermal_efficiency'] | ||
sl_cap_adj = wh_props['standby_loss_capacity_allowance'] | ||
sl_vol_drt = wh_props['standby_loss_volume_allowance'] | ||
if !wh_props['standby_loss_volume_allowance'].nil? | ||
sl_vol_drt = wh_props['standby_loss_volume_allowance'] | ||
elsif !wh_props['standby_loss_square_root_volume_allowance'].nil? | ||
sl_vol_drt = wh_props['standby_loss_square_root_volume_allowance'] | ||
else | ||
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.WaterHeaterMixed', "For #{water_heater_mixed.name}, could not retrieve the standby loss volume allowance.") | ||
return false | ||
end | ||
et = wh_props['thermal_efficiency'] | ||
# Estimate storage tank volume | ||
tank_volume = volume_gal > 100 ? (volume_gal - 100).round(0) : 0 | ||
|
@@ -194,6 +198,56 @@ def water_heater_mixed_apply_efficiency(water_heater_mixed) | |
return true | ||
end | ||
|
||
# @param water_heater_mixed [OpenStudio::Model::WaterHeaterMixed] water heater mixed object | ||
# @param fuel_type [Float] water heater fuel type | ||
# @param capacity_btu_per_hr [Float] water heater capacity in Btu/h | ||
# @param volume_gal [Float] water heater gallons of storage | ||
# @return [Hash] returns a hash wwith the applicable efficiency requirements | ||
def water_heater_mixed_get_efficiency_requirement(water_heater_mixed, fuel_type, capacity_btu_per_hr, volume_gal) | ||
# Get the water heater properties | ||
search_criteria = {} | ||
search_criteria['template'] = template | ||
search_criteria['fuel_type'] = fuel_type | ||
search_criteria['equipment_type'] = 'Storage Water Heaters' | ||
|
||
# Search base on capacity first | ||
wh_props_capacity = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr) | ||
wh_props_capacity_and_volume = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, volume_gal.round(0)) | ||
wh_props_capacity_and_capacity_btu_per_hr = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, nil, capacity_btu_per_hr) | ||
wh_props_capacity_and_volume_and_capacity_per_volume = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, volume_gal, capacity_btu_per_hr / volume_gal) | ||
|
||
# We consider that the lookup is successful if only one set of record is returned | ||
if wh_props_capacity.size == 1 | ||
wh_props = wh_props_capacity[0] | ||
elsif wh_props_capacity_and_volume.size == 1 | ||
wh_props = wh_props_capacity_and_volume[0] | ||
elsif wh_props_capacity_and_capacity_btu_per_hr == 1 | ||
wh_props = wh_props_capacity_and_capacity_btu_per_hr[0] | ||
elsif wh_props_capacity_and_volume_and_capacity_per_volume == 1 | ||
wh_props = wh_props_capacity_and_volume_and_capacity_per_volume[0] | ||
else | ||
# Search again with additional criteria | ||
search_criteria = water_heater_mixed_additional_search_criteria(water_heater_mixed, search_criteria) | ||
wh_props_capacity = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr) | ||
wh_props_capacity_and_volume = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, volume_gal.round(0)) | ||
wh_props_capacity_and_capacity_btu_per_hr = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, nil, capacity_btu_per_hr) | ||
wh_props_capacity_and_volume_and_capacity_per_volume = model_find_objects(standards_data['water_heaters'], search_criteria, capacity_btu_per_hr, nil, nil, nil, nil, volume_gal, capacity_btu_per_hr / volume_gal) | ||
if wh_props_capacity.size == 1 | ||
wh_props = wh_props_capacity[0] | ||
elsif wh_props_capacity_and_volume.size == 1 | ||
wh_props = wh_props_capacity_and_volume[0] | ||
elsif wh_props_capacity_and_capacity_btu_per_hr == 1 | ||
wh_props = wh_props_capacity_and_capacity_btu_per_hr[0] | ||
elsif wh_props_capacity_and_volume_and_capacity_per_volume == 1 | ||
wh_props = wh_props_capacity_and_volume_and_capacity_per_volume[0] | ||
else | ||
return {} | ||
end | ||
end | ||
Comment on lines
+220
to
+246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a "stepped" approach to identify what the applicable requirement should be. |
||
|
||
return wh_props | ||
end | ||
|
||
# Applies the correct fuel type for the water heaters | ||
# in the baseline model. For most standards and for most building | ||
# types, the baseline uses the same fuel type as the proposed. | ||
|
@@ -257,12 +311,13 @@ def water_heater_determine_sub_type(fuel_type, capacity_btu_per_hr, volume_gal) | |
|
||
# Convert Uniform Energy Factor (UEF) to Energy Factor (EF) | ||
# | ||
# @param water_heater_mixed [OpenStudio::Model::WaterHeaterMixed] water heater mixed object | ||
# @param fuel_type [String] water heater fuel type | ||
# @param uniform_energy_factor [Float] water heater Uniform Energy Factor (UEF) | ||
# @param capacity_btu_per_hr [Float] water heater capacity | ||
# @param volume_gal [Float] water heater storage volume in gallons | ||
# @return [Float] returns Energy Factor (EF) | ||
def water_heater_convert_uniform_energy_factor_to_energy_factor(fuel_type, uniform_energy_factor, capacity_btu_per_hr, volume_gal) | ||
def water_heater_convert_uniform_energy_factor_to_energy_factor(water_heater_mixed, fuel_type, uniform_energy_factor, 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) | ||
|
||
|
@@ -319,4 +374,13 @@ def water_heater_convert_energy_factor_to_thermal_efficiency_and_ua(fuel_type, e | |
|
||
return water_heater_efficiency, ua_btu_per_hr_per_f | ||
end | ||
|
||
# Add additional search criteria for water heater lookup efficiency. | ||
# | ||
# @param water_heater_mixed [OpenStudio::Model::WaterHeaterMixed] water heater mixed object | ||
# @param search_criteria [Hash] search criteria for looking up water heater data | ||
# @return [Hash] updated search criteria | ||
def water_heater_mixed_additional_search_criteria(water_heater_mixed, search_criteria) | ||
return search_criteria | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider storage volume as a lookup key.