Skip to content
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

Fix oversize limits w/ MaxLoad sizing methodology #1512

Merged
Merged
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ __New Features__
- Always validate the HPXML file before applying defaults and only optionally validate the final HPXML file.
- Battery losses now split between charging and discharging.
- Interior/exterior window shading multipliers are now modeled using the EnergyPlus incident solar multiplier.
- Improvements to HERS & MaxLoad heat pump sizing methodologies.

__Bugfixes__
- Fixes battery resilience output to properly incorporate battery losses.
Expand All @@ -33,7 +34,7 @@ __Bugfixes__
- Fixes error if conditioned basement has `InsulationSpansEntireSlab=true`.
- Fixes ReportSimulationOutput outputs for the Parametric Analysis Tool (PAT).
- Fixes missing radiation exchange between window and sky when an interior/exterior window shading multiplier less than 1 exists.
- Fixes AC/HP cooling bug in cooling adjustment.
- Fixes AC/HP cooling bug when applying cooling equipment adjustment.
- BuildResidentialHPXML measure: Fixes air distribution CFA served when there is not a central system that meets 100% of the load.

## OpenStudio-HPXML v1.6.0
Expand Down
10 changes: 5 additions & 5 deletions HPXMLtoOpenStudio/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>33bfe3c0-3f94-4206-9957-994c2b3f88f6</version_id>
<version_modified>2023-10-19T15:37:24Z</version_modified>
<version_id>d4f0d106-db04-4938-8e22-479b0b8c487f</version_id>
<version_modified>2023-10-20T15:31:58Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -238,7 +238,7 @@
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>1CFB3AB3</checksum>
<checksum>EDEAFE81</checksum>
</file>
<file>
<filename>hpxml_defaults.rb</filename>
Expand Down Expand Up @@ -280,7 +280,7 @@
<filename>hvac_sizing.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>BA8B1F77</checksum>
<checksum>CB7E432C</checksum>
</file>
<file>
<filename>lighting.rb</filename>
Expand Down Expand Up @@ -532,7 +532,7 @@
<filename>test_hvac_sizing.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>99A79F16</checksum>
<checksum>89BFFDED</checksum>
</file>
<file>
<filename>test_lighting.rb</filename>
Expand Down
16 changes: 11 additions & 5 deletions HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4069,11 +4069,17 @@ def distribution_system
end

def is_dual_fuel
if @backup_heating_fuel.nil?
return false
end
if @backup_heating_fuel.to_s == @heat_pump_fuel.to_s
return false
if backup_system.nil?
if @backup_heating_fuel.nil?
return false
end
if @backup_heating_fuel.to_s == @heat_pump_fuel.to_s
return false
end
else
if backup_system.heating_system_fuel.to_s == @heat_pump_fuel.to_s
return false
end
end

return true
Expand Down
109 changes: 60 additions & 49 deletions HPXMLtoOpenStudio/resources/hvac_sizing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1134,22 +1134,18 @@ def self.apply_hvac_size_limits(hvac_cooling)
end

def self.apply_hvac_heat_pump_logic(hvac_sizing_values, hvac_cooling)
# If HERS/MaxLoad methodology, uses at least the larger of heating and cooling loads for heat pump sizing (required for ERI).
return unless hvac_cooling.is_a? HPXML::HeatPump
return if @fraction_cool_load_served == 0
return if @fraction_heat_load_served == 0

if (@hpxml.header.heat_pump_sizing_methodology != HPXML::HeatPumpSizingACCA)
if @hpxml.header.heat_pump_sizing_methodology != HPXML::HeatPumpSizingACCA
# If HERS/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_sizing_values.Heat_Load, hvac_sizing_values.Cool_Load_Tot].max
hvac_sizing_values.Heat_Load = max_load
hvac_sizing_values.Cool_Load_Sens *= max_load / hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Load_Lat *= max_load / hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Load_Tot = max_load

# Override Manual S oversize allowances:
@oversize_limit = 1.0
@oversize_delta = 0.0
end
end

Expand Down Expand Up @@ -1372,7 +1368,13 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat
d_sens = shr_biquadratic[5]

# Adjust Sizing
if lat_cap_design < hvac_sizing_values.Cool_Load_Lat
if hvac_cooling.is_a?(HPXML::HeatPump) && (@hpxml.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS)
hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr

cool_load_sens_cap_design = hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value

elsif lat_cap_design < hvac_sizing_values.Cool_Load_Lat
# Size by MJ8 Latent load, return to rated conditions

# Solve for the new sensible and total capacity at design conditions:
Expand Down Expand Up @@ -1402,15 +1404,12 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat
# Limit total capacity to oversize limit
cool_cap_design = [cool_cap_design, @oversize_limit * hvac_sizing_values.Cool_Load_Tot].min

# Determine the final sensible capacity at design using the SHR
cool_load_sens_cap_design = shr_design * cool_cap_design

# Determine rated capacities
hvac_sizing_values.Cool_Capacity = cool_cap_design / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr

# Calculate the final air flow rate using final sensible capacity at design
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_manual_s(cool_load_sens_cap_design, (@cool_setpoint - @leaving_air_temp), hvac_sizing_values.Cool_Capacity)
# Determine the final sensible capacity at design using the SHR
cool_load_sens_cap_design = shr_design * cool_cap_design

elsif sens_cap_design < @undersize_limit * hvac_sizing_values.Cool_Load_Sens
# Size by MJ8 Sensible load, return to rated conditions, find Sens with SHRRated. Limit total
Expand All @@ -1436,45 +1435,43 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat

# Recalculate the air flow rate in case the oversizing limit has been used
cool_load_sens_cap_design = hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_manual_s(cool_load_sens_cap_design, (@cool_setpoint - @leaving_air_temp), hvac_sizing_values.Cool_Capacity)

else
hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr

cool_load_sens_cap_design = hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_manual_s(cool_load_sens_cap_design, (@cool_setpoint - @leaving_air_temp), hvac_sizing_values.Cool_Capacity)
end

elsif [HPXML::HVACTypeHeatPumpMiniSplit,
HPXML::HVACTypeMiniSplitAirConditioner].include? @cooling_type

entering_temp = @hpxml.header.manualj_cooling_design_temp
hvac_cooling_speed = get_sizing_speed(hvac_cooling_ap)
coefficients = hvac_cooling_ap.cool_cap_ft_spec[hvac_cooling_speed]

total_cap_curve_value = MathTools.biquadratic(@wetbulb_indoor_cooling, entering_temp, coefficients)
hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed]

hvac_sizing_values.Cool_Capacity = (hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value)
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_user(hvac_sizing_values.Cool_Capacity, hvac_cooling_ap.cool_rated_cfm_per_ton[-1], hvac_cooling_ap.cool_capacity_ratios[-1])
# Calculate the final air flow rate using final sensible capacity at design
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_manual_s(cool_load_sens_cap_design, (@cool_setpoint - @leaving_air_temp), hvac_sizing_values.Cool_Capacity)

elsif [HPXML::HVACTypeRoomAirConditioner,
elsif [HPXML::HVACTypeHeatPumpMiniSplit,
HPXML::HVACTypeMiniSplitAirConditioner,
HPXML::HVACTypeRoomAirConditioner,
HPXML::HVACTypePTAC,
HPXML::HVACTypeHeatPumpPTHP,
HPXML::HVACTypeHeatPumpRoom].include? @cooling_type

entering_temp = @hpxml.header.manualj_cooling_design_temp
hvac_cooling_speed = get_sizing_speed(hvac_cooling_ap)
total_cap_curve_value = MathTools.biquadratic(@wetbulb_indoor_cooling, entering_temp, hvac_cooling_ap.cool_cap_ft_spec[hvac_cooling_speed])
hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed]

hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
hvac_sizing_values.Cool_Airflow = calc_airflow_rate_user(hvac_sizing_values.Cool_Capacity, hvac_cooling_ap.cool_rated_cfm_per_ton[hvac_cooling_speed], 1.0)
if hvac_cooling.is_a?(HPXML::HeatPump) && (@hpxml.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS)
hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
else
entering_temp = @hpxml.header.manualj_cooling_design_temp
coefficients = hvac_cooling_ap.cool_cap_ft_spec[hvac_cooling_speed]
total_cap_curve_value = MathTools.biquadratic(@wetbulb_indoor_cooling, entering_temp, coefficients)

hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
end

hvac_sizing_values.Cool_Airflow = calc_airflow_rate_user(hvac_sizing_values.Cool_Capacity, hvac_cooling_ap.cool_rated_cfm_per_ton[-1], hvac_cooling_ap.cool_capacity_ratios[-1])

elsif HPXML::HVACTypeHeatPumpGroundToAir == @cooling_type

coil_bf = gshp_coil_bf
entering_temp = hvac_cooling_ap.design_chw
hvac_cooling_speed = get_sizing_speed(hvac_cooling_ap)
Expand All @@ -1495,23 +1492,28 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat
bypass_factor_curve_value = MathTools.biquadratic(@wetbulb_indoor_cooling, @cool_setpoint, gshp_coil_bf_ft_spec)
hvac_cooling_shr = hvac_cooling_ap.cool_rated_shrs_gross[hvac_cooling_speed]

hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value # Note: cool_cap_design = hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
if @hpxml.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS
hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
else
hvac_sizing_values.Cool_Capacity = hvac_sizing_values.Cool_Load_Tot / total_cap_curve_value # Note: cool_cap_design = hvac_sizing_values.Cool_Load_Tot
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr

cool_load_sens_cap_design = (hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value /
(1.0 + (1.0 - coil_bf * bypass_factor_curve_value) *
(80.0 - @cool_setpoint) / (@cool_setpoint - @leaving_air_temp)))
cool_load_lat_cap_design = hvac_sizing_values.Cool_Load_Tot - cool_load_sens_cap_design
cool_load_sens_cap_design = (hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value /
(1.0 + (1.0 - coil_bf * bypass_factor_curve_value) *
(80.0 - @cool_setpoint) / (@cool_setpoint - @leaving_air_temp)))
cool_load_lat_cap_design = hvac_sizing_values.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_sizing_values.Cool_Load_Sens].max
cool_load_lat_cap_design = [cool_load_lat_cap_design, hvac_sizing_values.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_sizing_values.Cool_Load_Sens].max
cool_load_lat_cap_design = [cool_load_lat_cap_design, hvac_sizing_values.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_sizing_values.Cool_Load_Tot].min
hvac_sizing_values.Cool_Capacity = cool_cap_design / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
# Limit total capacity via oversizing limit
cool_cap_design = [cool_cap_design, @oversize_limit * hvac_sizing_values.Cool_Load_Tot].min
hvac_sizing_values.Cool_Capacity = cool_cap_design / total_cap_curve_value
hvac_sizing_values.Cool_Capacity_Sens = hvac_sizing_values.Cool_Capacity * hvac_cooling_shr
end

# Recalculate the air flow rate in case the oversizing limit has been used
cool_load_sens_cap_design = (hvac_sizing_values.Cool_Capacity_Sens * sensible_cap_curve_value /
Expand Down Expand Up @@ -1565,7 +1567,13 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat
HPXML::HVACTypeHeatPumpMiniSplit,
HPXML::HVACTypeHeatPumpPTHP,
HPXML::HVACTypeHeatPumpRoom].include? @heating_type
process_heat_pump_adjustment(hvac_sizing_values, weather, hvac_heating, total_cap_curve_value, hvac_system)

if hvac_heating.is_a?(HPXML::HeatPump) && (@hpxml.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS)
hvac_sizing_values.Heat_Capacity = hvac_sizing_values.Heat_Load
else
process_heat_pump_adjustment(hvac_sizing_values, weather, hvac_heating, total_cap_curve_value, hvac_system)
end

hvac_sizing_values.Heat_Capacity_Supp = hvac_sizing_values.Heat_Load_Supp
if @heating_type == HPXML::HVACTypeHeatPumpAirToAir
hvac_sizing_values.Heat_Airflow = calc_airflow_rate_manual_s(hvac_sizing_values.Heat_Capacity, (@supply_air_temp - @heat_setpoint), hvac_sizing_values.Heat_Capacity)
Expand All @@ -1576,7 +1584,10 @@ def self.apply_hvac_equipment_adjustments(hvac_sizing_values, weather, hvac_heat

elsif [HPXML::HVACTypeHeatPumpGroundToAir].include? @heating_type

if hvac_sizing_values.Cool_Capacity > 0
if @hpxml.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingHERS
hvac_sizing_values.Heat_Capacity = hvac_sizing_values.Heat_Load
hvac_sizing_values.Heat_Capacity_Supp = hvac_sizing_values.Heat_Load_Supp
elsif hvac_sizing_values.Cool_Capacity > 0
hvac_sizing_values.Heat_Capacity = hvac_sizing_values.Heat_Load
hvac_sizing_values.Heat_Capacity_Supp = hvac_sizing_values.Heat_Load_Supp

Expand Down
52 changes: 40 additions & 12 deletions HPXMLtoOpenStudio/tests/test_hvac_sizing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_hvac_configurations
hp_sizing_methodologies = [nil]
end

hp_capacity_acca, hp_capacity_hers, hp_capacity_maxload = nil, nil, nil
hp_capacity_acca, hp_capacity_maxload = nil, nil
hp_sizing_methodologies.each do |hp_sizing_methodology|
test_name = hvac_hpxml.gsub('base-hvac-', "#{location}-hvac-autosize-")
if not hp_sizing_methodology.nil?
Expand All @@ -67,23 +67,51 @@ def test_hvac_configurations
'HVAC Capacity: Cooling (Btu/h)' => clg_cap.round(1),
'HVAC Capacity: Heat Pump Backup (Btu/h)' => hp_backup_cap.round(1) }

if hpxml.heat_pumps.size == 1
if hp_sizing_methodology == HPXML::HeatPumpSizingACCA
hp_capacity_acca = autosized_hpxml.heat_pumps[0].heating_capacity
elsif hp_sizing_methodology == HPXML::HeatPumpSizingHERS
hp_capacity_hers = autosized_hpxml.heat_pumps[0].heating_capacity
elsif hp_sizing_methodology == HPXML::HeatPumpSizingMaxLoad
hp_capacity_maxload = autosized_hpxml.heat_pumps[0].heating_capacity
next unless hpxml.heat_pumps.size == 1

htg_load = autosized_hpxml.hvac_plant.hdl_total
clg_load = autosized_hpxml.hvac_plant.cdl_sens_total + autosized_hpxml.hvac_plant.cdl_lat_total
hp = autosized_hpxml.heat_pumps[0]
htg_cap = hp.heating_capacity
clg_cap = hp.cooling_capacity
charge_defect_ratio = hp.charge_defect_ratio.to_f
airflow_defect_ratio = hp.airflow_defect_ratio.to_f

if hp_sizing_methodology == HPXML::HeatPumpSizingACCA
hp_capacity_acca = htg_cap
elsif hp_sizing_methodology == HPXML::HeatPumpSizingHERS
next if hp.is_dual_fuel

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
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
end
elsif hp_sizing_methodology == HPXML::HeatPumpSizingMaxLoad
hp_capacity_maxload = htg_cap
end
end

next unless hpxml.heat_pumps.size == 1

# Check that MaxLoad >= HERS >= ACCA for heat pump heating capacity
# FIXME: Temporarily disabled
# assert_operator(hp_capacity_maxload, :>=, hp_capacity_hers)
# assert_operator(hp_capacity_hers, :>=, hp_capacity_acca)
# Check that MaxLoad >= >= ACCA for heat pump heating capacity
assert_operator(hp_capacity_maxload, :>=, hp_capacity_acca)
end
end

Expand Down
Loading