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