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.