From 532cc3be679778807a680bd8ed13d47d4b90c728 Mon Sep 17 00:00:00 2001 From: Scott Horowitz Date: Thu, 8 Aug 2024 13:01:59 -0600 Subject: [PATCH] Squashed 'hpxml-measures/' changes from 1a9c556e73..e29d955f19 e29d955f19 First try. git-subtree-dir: hpxml-measures git-subtree-split: e29d955f19775cd392622f99e354af5603998a48 --- HPXMLtoOpenStudio/measure.xml | 10 +- HPXMLtoOpenStudio/resources/hvac_sizing.rb | 111 +++++++++----------- HPXMLtoOpenStudio/tests/test_hvac_sizing.rb | 28 ++--- docs/source/workflow_inputs.rst | 2 +- 4 files changed, 68 insertions(+), 83 deletions(-) diff --git a/HPXMLtoOpenStudio/measure.xml b/HPXMLtoOpenStudio/measure.xml index 644592acc..ab8de4a0c 100644 --- a/HPXMLtoOpenStudio/measure.xml +++ b/HPXMLtoOpenStudio/measure.xml @@ -3,8 +3,8 @@ 3.1 hpxm_lto_openstudio b1543b30-9465-45ff-ba04-1d1f85e763bc - feaf25ee-bd63-4157-9dfb-dc205b9f6bba - 2024-08-07T23:34:42Z + f16fb702-6d18-408b-a85f-fc3c31bc92ef + 2024-08-08T18:51:39Z D8922A73 HPXMLtoOpenStudio HPXML to OpenStudio Translator @@ -387,7 +387,7 @@ hvac_sizing.rb rb resource - F3D7066C + 16F1A401 lighting.rb @@ -633,7 +633,7 @@ in.schedules.csv csv test - 2B9C5C95 + 1E533016 test_airflow.rb @@ -681,7 +681,7 @@ test_hvac_sizing.rb rb test - 9094E7AB + 16805567 test_lighting.rb diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index bf8388df7..f2fe361bc 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -75,7 +75,7 @@ def self.calculate(runner, weather, hpxml_bldg, hvac_systems, update_hpxml: true # Calculate HVAC equipment sizes hvac_sizings = HVACSizingValues.new apply_hvac_loads_to_hvac_sizings(hvac_sizings, hvac_loads) - apply_hvac_heat_pump_logic(hvac_sizings, hvac_cooling, frac_zone_heat_load_served, frac_zone_cool_load_served, hpxml_bldg) + apply_hvac_heat_pump_maxload_logic(hvac_sizings, hvac_cooling, frac_zone_heat_load_served, frac_zone_cool_load_served, hpxml_bldg) apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hvac_heating, hvac_cooling, hvac_system, hpxml_bldg) apply_hvac_installation_quality(mj, hvac_sizings, hvac_heating, hvac_cooling, frac_zone_heat_load_served, frac_zone_cool_load_served, hpxml_bldg) apply_hvac_autosizing_factors_and_limits(hvac_sizings, hvac_heating, hvac_cooling) @@ -1747,7 +1747,7 @@ def self.apply_hvac_loads_to_hvac_sizings(hvac_sizings, hvac_loads) hvac_sizings.Heat_Load_Supp = hvac_loads.Heat_Tot end - # Updates the design loads for a heat pump to comply with the specified heat pump sizing methodology. + # Updates the design loads for a heat pump to comply with the MaxLoad heat pump sizing methodology. # # @param hvac_sizings [HVACSizingValues] Object with sizing values for a given HVAC system # @param hvac_cooling [HPXML::CoolingSystem or HPXML::HeatPump] The cooling portion of the current HPXML HVAC system @@ -1755,14 +1755,14 @@ def self.apply_hvac_loads_to_hvac_sizings(hvac_sizings, hvac_loads) # @param frac_zone_cool_load_served [Double] Fraction of zone cooling load served by this HVAC system # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit # @return [void] - def self.apply_hvac_heat_pump_logic(hvac_sizings, hvac_cooling, frac_zone_heat_load_served, frac_zone_cool_load_served, hpxml_bldg) + def self.apply_hvac_heat_pump_maxload_logic(hvac_sizings, hvac_cooling, frac_zone_heat_load_served, frac_zone_cool_load_served, hpxml_bldg) # Only apply logic to a heat pump that provides both heating and cooling return unless hvac_cooling.is_a? HPXML::HeatPump - return if hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingACCA + return if hpxml_bldg.header.heat_pump_sizing_methodology != HPXML::HeatPumpSizingMaxLoad return if frac_zone_heat_load_served == 0 return if frac_zone_cool_load_served == 0 - # If HERS/MaxLoad methodology, use at least the larger of heating/cooling loads for heat pump sizing. + # If MaxLoad methodology, use at least the larger of heating/cooling loads for heat pump sizing. # Note: Heat_Load_Supp should NOT be adjusted; we only want to adjust the HP capacity, not the HP backup heating capacity. max_load = [hvac_sizings.Heat_Load, hvac_sizings.Cool_Load_Tot].max hvac_sizings.Heat_Load = max_load @@ -2155,13 +2155,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva d_sens = shr_biquadratic[5] # Adjust Sizing - if hvac_cooling.is_a?(HPXML::HeatPump) && (hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS) - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot - 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 - - elsif lat_cap_design < hvac_sizings.Cool_Load_Lat + if lat_cap_design < hvac_sizings.Cool_Load_Lat # Size by MJ8 Latent load, return to rated conditions # Solve for the new sensible and total capacity at design conditions: @@ -2239,18 +2233,13 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva hvac_cooling_speed = get_nominal_speed(hvac_cooling_ap, true) hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed] - if hvac_cooling.is_a?(HPXML::HeatPump) && (hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS) - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - else - entering_temp = hpxml_bldg.header.manualj_cooling_design_temp - idb_adj = adjust_indoor_condition_var_speed(entering_temp, mj.cool_indoor_wetbulb, :clg) - odb_adj = adjust_outdoor_condition_var_speed(entering_temp, hvac_cooling, :clg) - total_cap_curve_value = odb_adj * idb_adj + entering_temp = hpxml_bldg.header.manualj_cooling_design_temp + idb_adj = adjust_indoor_condition_var_speed(entering_temp, mj.cool_indoor_wetbulb, :clg) + odb_adj = adjust_outdoor_condition_var_speed(entering_temp, hvac_cooling, :clg) + total_cap_curve_value = odb_adj * idb_adj - hvac_sizings.Cool_Capacity = (hvac_sizings.Cool_Load_Tot / total_cap_curve_value) - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - end + hvac_sizings.Cool_Capacity = (hvac_sizings.Cool_Load_Tot / total_cap_curve_value) + hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr hvac_sizings.Cool_Airflow = calc_airflow_rate_user(hvac_sizings.Cool_Capacity, hvac_cooling_ap.cool_rated_cfm_per_ton[hvac_cooling_speed]) @@ -2262,16 +2251,11 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva hvac_cooling_speed = get_nominal_speed(hvac_cooling_ap, true) hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed] - if hvac_cooling.is_a?(HPXML::HeatPump) && (hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS) - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - else - entering_temp = hpxml_bldg.header.manualj_cooling_design_temp - total_cap_curve_value = MathTools.biquadratic(mj.cool_indoor_wetbulb, entering_temp, hvac_cooling_ap.cool_cap_ft_spec[hvac_cooling_speed]) + entering_temp = hpxml_bldg.header.manualj_cooling_design_temp + total_cap_curve_value = MathTools.biquadratic(mj.cool_indoor_wetbulb, entering_temp, hvac_cooling_ap.cool_cap_ft_spec[hvac_cooling_speed]) - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot / total_cap_curve_value - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - end + hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot / total_cap_curve_value + hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr hvac_sizings.Cool_Airflow = calc_airflow_rate_user(hvac_sizings.Cool_Capacity, hvac_cooling_ap.cool_rated_cfm_per_ton[0]) @@ -2293,28 +2277,23 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva bypass_factor_curve_value = MathTools.biquadratic(mj.cool_indoor_wetbulb, mj.cool_setpoint, gshp_coil_bf_ft_spec) hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed] - if hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - else - hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot / total_cap_curve_value # Note: cool_cap_design = hvac_sizings.Cool_Load_Tot - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr + hvac_sizings.Cool_Capacity = hvac_sizings.Cool_Load_Tot / total_cap_curve_value # Note: cool_cap_design = hvac_sizings.Cool_Load_Tot + 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) / cooling_delta_t)) - cool_load_lat_cap_design = hvac_sizings.Cool_Load_Tot - cool_load_sens_cap_design + 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) / 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. - cool_load_sens_cap_design = [cool_load_sens_cap_design, hvac_sizings.Cool_Load_Sens].max - cool_load_lat_cap_design = [cool_load_lat_cap_design, hvac_sizings.Cool_Load_Lat].max - cool_cap_design = cool_load_sens_cap_design + cool_load_lat_cap_design + # Adjust Sizing so that coil sensible at design >= CoolingLoad_Sens, and coil latent at design >= CoolingLoad_Lat, and equipment SHRRated is maintained. + cool_load_sens_cap_design = [cool_load_sens_cap_design, hvac_sizings.Cool_Load_Sens].max + cool_load_lat_cap_design = [cool_load_lat_cap_design, hvac_sizings.Cool_Load_Lat].max + cool_cap_design = cool_load_sens_cap_design + cool_load_lat_cap_design - # Limit total capacity via oversizing limit - cool_cap_design = [cool_cap_design, oversize_limit * hvac_sizings.Cool_Load_Tot].min - hvac_sizings.Cool_Capacity = cool_cap_design / total_cap_curve_value - hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr - end + # Limit total capacity via oversizing limit + cool_cap_design = [cool_cap_design, oversize_limit * hvac_sizings.Cool_Load_Tot].min + hvac_sizings.Cool_Capacity = cool_cap_design / total_cap_curve_value + hvac_sizings.Cool_Capacity_Sens = hvac_sizings.Cool_Capacity * hvac_cooling_shr # 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 / @@ -2388,11 +2367,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva HPXML::HVACTypeHeatPumpRoom].include? heating_type hvac_heating_speed = get_nominal_speed(hvac_heating_ap, false) - if hvac_heating.is_a?(HPXML::HeatPump) && (hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS) - hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load - else - process_heat_pump_adjustment(mj, runner, hvac_sizings, weather, hvac_heating, total_cap_curve_value, hvac_system, hvac_heating_speed, oversize_limit, oversize_delta, hpxml_bldg) - end + process_heat_pump_adjustment(mj, runner, hvac_sizings, weather, hvac_heating, total_cap_curve_value, hvac_system, hvac_heating_speed, oversize_limit, oversize_delta, hpxml_bldg) 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) @@ -2403,10 +2378,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva elsif [HPXML::HVACTypeHeatPumpGroundToAir].include? heating_type - if hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS - hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load - hvac_sizings.Heat_Capacity_Supp = hvac_sizings.Heat_Load_Supp - elsif hvac_sizings.Cool_Capacity > 0 + if hvac_sizings.Cool_Capacity > 0 hvac_sizings.Heat_Capacity = hvac_sizings.Heat_Load hvac_sizings.Heat_Capacity_Supp = hvac_sizings.Heat_Load_Supp @@ -2479,6 +2451,25 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva fail "Unexpected heating type: #{heating_type}." end + + # If HERS sizing methodology, ensure HP capacity is at least equal to larger of + # heating and sensible cooling loads. + if hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS + if (not hvac_cooling.nil?) && hvac_cooling.is_a?(HPXML::HeatPump) && (hvac_cooling.fraction_heat_load_served > 0) && (hvac_cooling.fraction_cool_load_served > 0) + min_capacity = [hvac_sizings.Heat_Load, hvac_sizings.Cool_Load_Sens].max + if hvac_sizings.Cool_Capacity < min_capacity + scaling_factor = min_capacity / hvac_sizings.Cool_Capacity + hvac_sizings.Cool_Capacity *= scaling_factor + hvac_sizings.Cool_Capacity_Sens *= scaling_factor + hvac_sizings.Cool_Airflow *= scaling_factor + end + if hvac_sizings.Heat_Capacity < min_capacity + scaling_factor = min_capacity / hvac_sizings.Heat_Capacity + hvac_sizings.Heat_Capacity *= scaling_factor + hvac_sizings.Heat_Airflow *= scaling_factor + end + end + end end # Calculates the heat pump's heating or cooling capacity at the specified indoor temperature, as a fraction diff --git a/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb b/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb index 805e9d512..64fb7acda 100644 --- a/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb +++ b/HPXMLtoOpenStudio/tests/test_hvac_sizing.rb @@ -121,24 +121,18 @@ def test_hvac_configurations if (charge_defect_ratio != 0) || (airflow_defect_ratio != 0) # Check HP capacity is greater than max(htg_load, clg_load) - if hp.fraction_heat_load_served == 0 - assert_operator(clg_cap, :>, clg_load) - elsif hp.fraction_cool_load_served == 0 - assert_operator(htg_cap, :>, htg_load) - else - assert_operator(htg_cap, :>, [htg_load, clg_load].max) - assert_operator(clg_cap, :>, [htg_load, clg_load].max) - end + operator = :> else - # Check HP capacity equals max(htg_load, clg_load) - if hp.fraction_heat_load_served == 0 - assert_in_delta(clg_cap, clg_load, 1.0) - elsif hp.fraction_cool_load_served == 0 - assert_in_delta(htg_cap, htg_load, 1.0) - else - assert_in_delta(htg_cap, [htg_load, clg_load].max, 1.0) - assert_in_delta(clg_cap, [htg_load, clg_load].max, 1.0) - end + # Check HP capacity equals at least max(htg_load, clg_load) + operator = :>= + end + if hp.fraction_heat_load_served == 0 + assert_operator(clg_cap, operator, clg_load) + elsif hp.fraction_cool_load_served == 0 + assert_operator(htg_cap, operator, htg_load) + else + assert_operator(htg_cap, operator, [htg_load, clg_load].max) + assert_operator(clg_cap, operator, [htg_load, clg_load].max) end end diff --git a/docs/source/workflow_inputs.rst b/docs/source/workflow_inputs.rst index 371947b6f..9c89cb18e 100644 --- a/docs/source/workflow_inputs.rst +++ b/docs/source/workflow_inputs.rst @@ -766,7 +766,7 @@ Additional autosizing factor inputs are available at the system level, see :ref: \- **ACCA**: autosized heat pumps have their nominal capacity sized per ACCA Manual J/S based on cooling design loads, with some oversizing allowances for larger heating design loads. - \- **HERS**: autosized heat pumps have their nominal capacity sized equal to the larger of heating/cooling design loads. + \- **HERS**: same as **ACCA** except autosized heat pumps have their nominal capacity sized equal to at least the larger of heating and sensible cooling design loads. \- **MaxLoad**: autosized heat pumps have their nominal capacity sized based on the larger of heating/cooling design loads, while taking into account the heat pump's reduced capacity at the design temperature, such that no backup heating should be necessary.