diff --git a/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.rb b/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.rb
index 41b7963a2..51c2eb5f4 100644
--- a/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.rb
+++ b/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.rb
@@ -2406,6 +2406,11 @@ def run(model, runner, user_arguments)
new_fan.setName("#{air_loop_hvac.name} VFD Fan")
new_fan.setMotorEfficiency(fan_mot_eff) # from Daikin Rebel E+ file
new_fan.setFanPowerMinimumFlowRateInputMethod('Fraction')
+ fan_max_flow = stage_flows_cooling[num_cooling_stages]
+ if stage_flows_heating[num_heating_stages] > stage_flows_cooling[num_cooling_stages]
+ fan_max_flow = stage_flows_heating[num_heating_stages]
+ end
+ new_fan.setMaximumFlowRate(fan_max_flow)
# set fan total efficiency, which determines fan power
if hprtu_scenario == 'variable_speed_high_eff'
diff --git a/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.xml b/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.xml
index c27b246c8..d96976d9b 100644
--- a/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.xml
+++ b/resources/measures/upgrade_hvac_add_heat_pump_rtu/measure.xml
@@ -3,8 +3,8 @@
3.1
add_heat_pump_rtu
f4567a68-27f2-4a15-ae91-ba0f35cd08c7
- 6b6f4c1d-cf0b-4ea1-b3fd-0089a59f56ea
- 2025-07-23T23:00:34Z
+ 971c6c78-c0ed-46c3-bcb0-f8f324e58a4b
+ 2025-05-20T21:19:15Z
5E2576E4
AddHeatPumpRtu
add_heat_pump_rtu
@@ -341,7 +341,7 @@
measure.rb
rb
script
- F4B40836
+ B25E5633
call_other_measures.rb
@@ -389,7 +389,7 @@
measure_test.rb
rb
test
- 8E751853
+ 27A9A796
diff --git a/resources/measures/upgrade_hvac_add_heat_pump_rtu/tests/measure_test.rb b/resources/measures/upgrade_hvac_add_heat_pump_rtu/tests/measure_test.rb
index b58d2e408..0332cdbab 100644
--- a/resources/measures/upgrade_hvac_add_heat_pump_rtu/tests/measure_test.rb
+++ b/resources/measures/upgrade_hvac_add_heat_pump_rtu/tests/measure_test.rb
@@ -194,13 +194,13 @@ def set_weather_and_apply_measure_and_run(test_name, measure, argument_map, osm_
'Site Outdoor Air Drybulb Temperature',
'Heating Coil Crankcase Heater Electricity Rate',
'Heating Coil Defrost Electricity Rate',
- 'Zone Windows Total Transmitted Solar Radiation Rate',
+ 'Zone Windows Total Transmitted Solar Radiation Rate'
]
out_vars.each do |out_var_name|
- ov = OpenStudio::Model::OutputVariable.new('ov', model)
- ov.setKeyValue('*')
- ov.setReportingFrequency('hourly')
- ov.setVariableName(out_var_name)
+ ov = OpenStudio::Model::OutputVariable.new('ov', model)
+ ov.setKeyValue('*')
+ ov.setReportingFrequency('hourly')
+ ov.setVariableName(out_var_name)
end
model.getOutputControlFiles.setOutputCSV(true)
@@ -251,7 +251,7 @@ def test_number_of_arguments_and_argument_names
assert_equal('window', arguments[11].name)
assert_equal('sizing_run', arguments[12].name)
assert_equal('debug_verbose', arguments[13].name)
- assert_equal('modify_setbacks', arguments[14].name)
+ assert_equal('modify_setbacks', arguments[14].name)
assert_equal('setback_value', arguments[15].name)
end
@@ -265,8 +265,8 @@ def data_point_ordering_check(lookup_table_in_hash)
# Extract and sort data_point keys numerically
points = table.select { |k, _| k.to_s.match?(/^data_point\d+$/) }
- .sort_by { |k, _| k.to_s.match(/\d+/)[0].to_i }
- .map { |_, v| v.split(',').first(2).map(&:to_f) }
+ .sort_by { |k, _| k.to_s.match(/\d+/)[0].to_i }
+ .map { |_, v| v.split(',').first(2).map(&:to_f) }
# Now check if x2 varies first (should see repeated x1s for several rows)
x1s, x2s = points.transpose
@@ -285,7 +285,7 @@ def data_point_ordering_check(lookup_table_in_hash)
end
# If x1 changes more frequently while x2 is stable, the ordering is wrong
- assert(x2_first_changes >= x1_first_changes, "Invalid data point order: x1 varies before x2 in some cases")
+ assert(x2_first_changes >= x1_first_changes, 'Invalid data point order: x1 varies before x2 in some cases')
end
end
@@ -297,20 +297,18 @@ def test_table_lookup_format
path_to_jsons = "#{__dir__}/../resources/*.json"
json_files = Dir.glob(path_to_jsons)
json_files.each do |file_path|
- begin
- content = File.read(file_path)
- hash = JSON.parse(content, symbolize_names: true)
- puts("### checking json file: #{file_path}")
-
- # Now `hash` is your Ruby hash from JSON
- # You can insert your test logic here
- assert(hash[:tables], "Missing :tables key in #{file_path}")
-
- # check lookup table format
- data_point_ordering_check(hash)
- rescue JSON::ParserError => e
- flunk "JSON parsing failed for #{file_path}: #{e.message}"
- end
+ content = File.read(file_path)
+ hash = JSON.parse(content, symbolize_names: true)
+ puts("### checking json file: #{file_path}")
+
+ # Now `hash` is your Ruby hash from JSON
+ # You can insert your test logic here
+ assert(hash[:tables], "Missing :tables key in #{file_path}")
+
+ # check lookup table format
+ data_point_ordering_check(hash)
+ rescue JSON::ParserError => e
+ flunk "JSON parsing failed for #{file_path}: #{e.message}"
end
end
@@ -1069,10 +1067,10 @@ def test_sizing_model_in_alaska
test_name = 'test_sizing_model_in_alaska'
lookup_table_test = {
- 'table_name': 'c_cap_high_T',
- 'ind1': 22.22,
- 'ind2': 29.44,
- 'dep': 1.1677
+ table_name: 'c_cap_high_T',
+ ind1: 22.22,
+ ind2: 29.44,
+ dep: 1.1677
}
puts "\n######\nTEST:#{osm_name}\n######\n"
@@ -1142,6 +1140,7 @@ def test_sizing_model_in_alaska
performance_category = nil
result.stepValues.each do |input_arg|
next unless input_arg.name == 'hprtu_scenario'
+
performance_category = input_arg.valueAsString
end
@@ -1150,7 +1149,7 @@ def test_sizing_model_in_alaska
if performance_category == 'two_speed_standard_eff'
# Check if lookup table is available
lookup_table_name = lookup_table_test[:table_name]
- #table_multivar_lookups = model.getTableMultiVariableLookups
+ # table_multivar_lookups = model.getTableMultiVariableLookups
table_multivar_lookups = model.getTableLookups
lookup_table = table_multivar_lookups.find { |table| table.name.to_s == lookup_table_name }
refute_nil(lookup_table, "Cannot find table named #{lookup_table_name} from model.")
@@ -1277,15 +1276,16 @@ def test_380_Small_Office_PSZ_Gas_2A
# populate argument with specified hash value if specified
arguments.each_with_index do |arg, idx|
temp_arg_var = arg.clone
- if arg.name == 'hprtu_scenario'
+ case arg.name
+ when 'hprtu_scenario'
hprtu_scenario = arguments[idx].clone
hprtu_scenario.setValue('variable_speed_high_eff') # override std_perf arg
argument_map[arg.name] = hprtu_scenario
- elsif arg.name == 'roof'
+ when 'roof'
roof = arguments[idx].clone
roof.setValue(true)
argument_map[arg.name] = roof
- elsif arg.name == 'window'
+ when 'window'
window = arguments[idx].clone
window.setValue(true)
argument_map[arg.name] = window
@@ -1294,13 +1294,12 @@ def test_380_Small_Office_PSZ_Gas_2A
end
end
test_result = verify_hp_rtu(test_name, model, measure, argument_map, osm_path, epw_path)
-
+
# check roof/window measure implementation
roof_measure_implemented = false
window_measure_implemented = false
test_result = JSON.parse(test_result.to_s)
test_result['step_values'].each do |step_value|
-
# check if roof measure variable is available
if step_value['name'] == 'env_roof_insul_roof_area_ft_2'
roof_measure_implemented = true
@@ -1310,10 +1309,9 @@ def test_380_Small_Office_PSZ_Gas_2A
if step_value['name'] == 'env_secondary_window_fen_area_ft_2'
window_measure_implemented = true
end
-
end
- assert_equal(roof_measure_implemented, true, "cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2")
- assert_equal(window_measure_implemented, true, "cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2")
+ assert_equal(roof_measure_implemented, true, 'cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2')
+ assert_equal(window_measure_implemented, true, 'cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2')
end
def test_380_small_office_psz_gas_coil_7A
@@ -1356,7 +1354,6 @@ def test_380_small_office_psz_gas_coil_7A
window_measure_implemented = false
test_result = JSON.parse(test_result.to_s)
test_result['step_values'].each do |step_value|
-
# check if roof measure variable is available
if step_value['name'] == 'env_roof_insul_roof_area_ft_2'
roof_measure_implemented = true
@@ -1366,10 +1363,9 @@ def test_380_small_office_psz_gas_coil_7A
if step_value['name'] == 'env_secondary_window_fen_area_ft_2'
window_measure_implemented = true
end
-
end
- assert_equal(roof_measure_implemented, false, "cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2")
- assert_equal(window_measure_implemented, false, "cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2")
+ assert_equal(roof_measure_implemented, false, 'cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2')
+ assert_equal(window_measure_implemented, false, 'cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2')
end
def test_small_office_psz_not_hard_sized
@@ -1396,11 +1392,12 @@ def test_small_office_psz_not_hard_sized
# populate argument with specified hash value if specified
arguments.each_with_index do |arg, idx|
temp_arg_var = arg.clone
- if arg.name == 'hprtu_scenario'
+ case arg.name
+ when 'hprtu_scenario'
hprtu_scenario = arguments[idx].clone
hprtu_scenario.setValue('variable_speed_high_eff')
argument_map[arg.name] = hprtu_scenario
- elsif arg.name == 'roof'
+ when 'roof'
roof = arguments[idx].clone
roof.setValue(true)
argument_map[arg.name] = roof
@@ -1416,7 +1413,6 @@ def test_small_office_psz_not_hard_sized
window_measure_implemented = false
test_result = JSON.parse(test_result.to_s)
test_result['step_values'].each do |step_value|
-
# check if roof measure variable is available
if step_value['name'] == 'env_roof_insul_roof_area_ft_2'
roof_measure_implemented = true
@@ -1426,10 +1422,9 @@ def test_small_office_psz_not_hard_sized
if step_value['name'] == 'env_secondary_window_fen_area_ft_2'
window_measure_implemented = true
end
-
end
- assert_equal(roof_measure_implemented, true, "cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2")
- assert_equal(window_measure_implemented, false, "cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2")
+ assert_equal(roof_measure_implemented, true, 'cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2')
+ assert_equal(window_measure_implemented, false, 'cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2')
end
def test_380_retail_psz_gas_6B
@@ -1456,11 +1451,12 @@ def test_380_retail_psz_gas_6B
# populate argument with specified hash value if specified
arguments.each_with_index do |arg, idx|
temp_arg_var = arg.clone
- if arg.name == 'hprtu_scenario'
+ case arg.name
+ when 'hprtu_scenario'
hprtu_scenario = arguments[idx].clone
hprtu_scenario.setValue('variable_speed_high_eff') # override std_perf arg
argument_map[arg.name] = hprtu_scenario
- elsif arg.name == 'window'
+ when 'window'
window = arguments[idx].clone
window.setValue(true)
argument_map[arg.name] = window
@@ -1476,7 +1472,6 @@ def test_380_retail_psz_gas_6B
window_measure_implemented = false
test_result = JSON.parse(test_result.to_s)
test_result['step_values'].each do |step_value|
-
# check if roof measure variable is available
if step_value['name'] == 'env_roof_insul_roof_area_ft_2'
roof_measure_implemented = true
@@ -1486,10 +1481,9 @@ def test_380_retail_psz_gas_6B
if step_value['name'] == 'env_secondary_window_fen_area_ft_2'
window_measure_implemented = true
end
-
end
- assert_equal(roof_measure_implemented, false, "cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2")
- assert_equal(window_measure_implemented, true, "cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2")
+ assert_equal(roof_measure_implemented, false, 'cannot find variable that was saved in roof upgrade measure via registerValue: env_roof_insul_roof_area_ft_2')
+ assert_equal(window_measure_implemented, true, 'cannot find variable that was saved in window upgrade measure via registerValue: env_secondary_window_fen_area_ft_2')
end
##########################################################################
@@ -1561,7 +1555,35 @@ def test_380_full_service_restaurant_psz_gas_coil
kitchen_htg_coils_final = []
tz_all_other_final = []
nonkitchen_htg_coils_final = []
+ count_unitary_sys_checked = 0
model.getAirLoopHVACUnitarySystems.sort.each do |unitary_sys|
+ # assert new unitary systems all have variable speed fans
+ fan = unitary_sys.supplyFan.get
+ if fan.to_FanVariableVolume.is_initialized
+
+ count_unitary_sys_checked += 1
+
+ assert(unitary_sys.supplyAirFlowRateDuringCoolingOperation.is_initialized, 'supplyAirFlowRateDuringCoolingOperation under AirLoopHVACUnitarySystem class not initialized')
+ assert(unitary_sys.supplyAirFlowRateDuringHeatingOperation.is_initialized, 'supplyAirFlowRateDuringHeatingOperation under AirLoopHVACUnitarySystem class not initialized')
+ assert(fan.to_FanVariableVolume.get.maximumFlowRate.is_initialized, 'maximumFlowRate under FanVariableVolume class not initialized')
+
+ airflow_unitary_cooling = 0.0
+ airflow_unitary_heating = 1.0
+ airflow_fan = 2.0
+ if unitary_sys.supplyAirFlowRateDuringCoolingOperation.is_initialized
+ airflow_unitary_cooling = unitary_sys.supplyAirFlowRateDuringCoolingOperation.get
+ end
+ if unitary_sys.supplyAirFlowRateDuringHeatingOperation.is_initialized
+ airflow_unitary_heating = unitary_sys.supplyAirFlowRateDuringHeatingOperation.get
+ end
+ if fan.to_FanVariableVolume.get.maximumFlowRate.is_initialized
+ airflow_fan = fan.to_FanVariableVolume.get.maximumFlowRate.get
+ end
+
+ assert_in_epsilon(airflow_unitary_cooling, airflow_unitary_heating, 0.05, "Airflow rates for cooling and heating should match but it is not: airflow_unitary_cooling = #{airflow_unitary_cooling} | airflow_unitary_heating = #{airflow_unitary_heating}")
+ assert_equal(airflow_fan, airflow_unitary_cooling, "Airflow rates for fan and unitary system should match but it is not: airflow_fan = #{airflow_fan} | airflow_unitary_cooling = #{airflow_unitary_cooling}")
+ end
+
# skip kitchen spaces
thermal_zone_names_to_exclude = ['Kitchen', 'kitchen', 'KITCHEN']
if thermal_zone_names_to_exclude.any? { |word| (unitary_sys.name.to_s).include?(word) }
@@ -1579,6 +1601,9 @@ def test_380_full_service_restaurant_psz_gas_coil
nonkitchen_htg_coils_final << unitary_sys.heatingCoil.get
end
+ # assert if there was any unitary system checked for the unit test above
+ assert(count_unitary_sys_checked > 0, 'No unitary systems applicable to HPRTU upgrade were found. Not the purpose of this unit test.')
+
# assert no changes to kitchen unitary systems
assert_equal(tz_kitchens_final, tz_kitchens)
@@ -1605,10 +1630,10 @@ def test_380_full_service_restaurant_psz_gas_coil_std_perf
puts "\n######\nTEST:#{osm_name}\n######\n"
lookup_table_test = {
- 'table_name': 'h_cap_T',
- 'ind1': 21.11,
- 'ind2': -17.78,
- 'dep': 0.3974
+ table_name: 'h_cap_T',
+ ind1: 21.11,
+ ind2: -17.78,
+ dep: 0.3974
}
osm_path = model_input_path(osm_name)
@@ -1645,6 +1670,7 @@ def test_380_full_service_restaurant_psz_gas_coil_std_perf
performance_category = nil
result.stepValues.each do |input_arg|
next unless input_arg.name == 'hprtu_scenario'
+
performance_category = input_arg.valueAsString
end
@@ -1653,7 +1679,7 @@ def test_380_full_service_restaurant_psz_gas_coil_std_perf
if performance_category == 'two_speed_standard_eff'
# Check if lookup table is available
lookup_table_name = lookup_table_test[:table_name]
- #table_multivar_lookups = model.getTableMultiVariableLookups
+ # table_multivar_lookups = model.getTableMultiVariableLookups
table_multivar_lookups = model.getTableLookups
lookup_table = table_multivar_lookups.find { |table| table.name.to_s == lookup_table_name }
refute_nil(lookup_table, "Cannot find table named #{lookup_table_name} from model.")
@@ -1788,10 +1814,10 @@ def test_380_small_office_psz_gas_coil_7A_upsizing_std
puts "\n######\nTEST:#{osm_name}\n######\n"
lookup_table_test = {
- 'table_name': 'c_eir_high_T',
- 'ind1': 22.22,
- 'ind2': 35.0,
- 'dep': 0.9438
+ table_name: 'c_eir_high_T',
+ ind1: 22.22,
+ ind2: 35.0,
+ dep: 0.9438
}
osm_path = model_input_path(osm_name)
@@ -1847,6 +1873,7 @@ def test_380_small_office_psz_gas_coil_7A_upsizing_std
performance_category = nil
result.stepValues.each do |input_arg|
next unless input_arg.name == 'hprtu_scenario'
+
performance_category = input_arg.valueAsString
end
@@ -1855,7 +1882,7 @@ def test_380_small_office_psz_gas_coil_7A_upsizing_std
if performance_category == 'two_speed_standard_eff'
# Check if lookup table is available
lookup_table_name = lookup_table_test[:table_name]
- #table_multivar_lookups = model.getTableMultiVariableLookups
+ # table_multivar_lookups = model.getTableMultiVariableLookups
table_multivar_lookups = model.getTableLookups
lookup_table = table_multivar_lookups.find { |table| table.name.to_s == lookup_table_name }
refute_nil(lookup_table, "Cannot find table named #{lookup_table_name} from model.")
@@ -2070,7 +2097,7 @@ def test_380_full_service_restaurant_psz_gas_coil_single_erv_3A_na
assert_equal(ervs_baseline, ervs_upgrade)
end
- def test_confirm_heating_setback_change_square_wave
+ def test_confirm_heating_setback_change_square_wave
# confirm that any heating setbacks are now 2F
osm_name = 'Retail_PSZ-AC.osm'
epw_name = 'NE_Kearney_Muni_725526_16.epw'
@@ -2118,13 +2145,11 @@ def test_confirm_heating_setback_change_square_wave
schedule_deltas = [] # keep track of differences between min and max values in schedules
-
# Loop thru zones and look at temp setbacks
model.getAirLoopHVACs.sort.each do |air_loop_hvac|
puts "loop class #{air_loop_hvac.class}"
zones = air_loop_hvac.thermalZones
-
zones.sort.each do |thermal_zone|
next unless thermal_zone.thermostatSetpointDualSetpoint.is_initialized
@@ -2152,15 +2177,15 @@ def test_confirm_heating_setback_change_square_wave
# Make sure no deltas are greater than the expected setback value
deltas_out_of_range = schedule_deltas.any? { |x| x > setback_value_c }
-
- puts("Temperature deltas in schedule match expected values: #{(deltas_out_of_range == false)}")
+
+ puts("Temperature deltas in schedule match expected values: #{deltas_out_of_range == false}")
assert_equal(deltas_out_of_range, false)
-
+
true
end
-def test_confirm_heating_setback_change_opt_start
+ def test_confirm_heating_setback_change_opt_start
# confirm that any heating setbacks are now 2F
osm_name = 'Retail_PSZ-AC_updated_39_opt_start.osm'
epw_name = 'NE_Kearney_Muni_725526_16.epw'
@@ -2206,15 +2231,12 @@ def test_confirm_heating_setback_change_opt_start
assert_equal('Success', result.value.valueName)
model = load_model(model_output_path(__method__))
-
schedule_deltas = [] # keep track of differences between min and max values in schedules
-
# Loop thru zones and look at temp setbacks
model.getAirLoopHVACs.sort.each do |air_loop_hvac|
zones = air_loop_hvac.thermalZones
-
zones.sort.each do |thermal_zone|
next unless thermal_zone.thermostatSetpointDualSetpoint.is_initialized
@@ -2250,12 +2272,11 @@ def test_confirm_heating_setback_change_opt_start
# Make sure no deltas are greater than the expected setback value
deltas_out_of_range = schedule_deltas.any? { |x| x > setback_value_c }
-
-
- puts("Temperature deltas in schedule match expected values: #{(deltas_out_of_range == false)}")
+
+ puts("Temperature deltas in schedule match expected values: #{deltas_out_of_range == false}")
assert_equal(deltas_out_of_range, false)
-
+
true
end
end