diff --git a/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml b/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml index 2cb6b07991..4f6c78ab44 100644 --- a/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml +++ b/HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.xml @@ -338,7 +338,7 @@ [HVACSizingControl] Expected 0 or 1 element(s) for xpath: HeatPumpSizingMethodology - Expected HeatPumpSizingMethodology to be 'ACCA' or 'HERS' or 'MaxLoad' + Expected HeatPumpSizingMethodology to be 'ACCA2023' or 'ACCA' or 'HERS' or 'MaxLoad' Expected 0 or 1 element(s) for xpath: HeatPumpBackupSizingMethodology Expected HeatPumpBackupSizingMethodology to be 'emergency' or 'supplemental' Expected 0 or 1 element(s) for xpath: AllowIncreasedFixedCapacities diff --git a/HPXMLtoOpenStudio/resources/hvac_sizing.rb b/HPXMLtoOpenStudio/resources/hvac_sizing.rb index 832db9cec8..a6724ba4d6 100644 --- a/HPXMLtoOpenStudio/resources/hvac_sizing.rb +++ b/HPXMLtoOpenStudio/resources/hvac_sizing.rb @@ -1711,26 +1711,105 @@ def self.apply_fractions_load_served(hvac_heating, hvac_loads, frac_zone_heat_lo end # Returns the ACCA Manual S sizing allowances for a given type of HVAC equipment. - # These sizing allowances are used in the logic that determines how to convert heating/cooling + # These sizing allowances are used in the logic that determines how to convert heating/cooling # design loads into corresponding equipment capacities. - # - # @param hvac_cooling [HPXML::CoolingSystem or HPXML::HeatPump] The cooling portion of the current HPXML HVAC system - # @return [Array] Oversize fraction (frac), oversize delta (Btu/hr), undersize fraction (frac) - def self.get_hvac_size_limits(hvac_cooling) - oversize_limit = 1.15 - oversize_delta = 15000.0 - undersize_limit = 0.9 - if not hvac_cooling.nil? - if hvac_cooling.compressor_type == HPXML::HVACCompressorTypeTwoStage - oversize_limit = 1.2 - elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeVariableSpeed - oversize_limit = 1.3 - end - end + # New ACCA Manual S specifies different size limits for clg only/HPs depending on + # the sizing condition (Standard, Dry, Variable Speed, etc.) + # and total cooling load (24,000 BTU/hr cutoff) - return oversize_limit, oversize_delta, undersize_limit - end + # get_hvac_size_limits() does not address section/table N2.3.3 Two-Speed Heat Pump Sizing Condition + # or section/table N2.3.4 Variable-Capacity Equipment Sizing Condition + # since these size conditions also include heating size factors and minimum compressor heating size factors + # To maintain consistency w/ previous implementation in ACCA Man. S 2014, only cooling size limits are returned. + # Therefore, only sections/tables N2.3.1 and N2.3.2 from ACCA Man. S 2023 are addressed in this method. + + # @param hvac_cooling [HPXML::CoolingSystem or HPXML::HeatPump] The cooling portion of the current HPXML HVAC system + # @param hvac_sizings [HVACSizingValues] Object with sizing values for a given HVAC system + # @param hpxml_bldg [HPXML::Building] HPXML Building object representing an individual dwelling unit + # @param weather [WeatherFile] Weather object containing EPW information + # @return [Array] total clg oversize limit (frac), + # total clg undersize limit (frac), sens. clg undersize limit (frac), lat. clg undersize limit (frac) + + def self.get_hvac_size_limits(hvac_cooling, hvac_sizings, hpxml_bldg, weather) + + load_shr = hvac_sizings.Cool_Load_Sens / hvac_sizings.Cool_Load_Tot + # calculate Climate JSHR to determine standard vs dry sizing condition (new ACCA) + + if hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingACCA2023 + if not hvac_cooling.nil? + if load_shr < 0.95 + # larger latent load + # Section N.2.3.1 ACCA Man. S 2023, Standard Sizing Condition + total_clg_undersize_limit = 0.90 + sens_clg_undersize_limit = 0.90 + lat_clg_undersize_limit = 1.00 + if hvac_cooling.compressor_type == HPXML::HVACCompressorTypeSingleStage && hvac_sizings.Cool_Load_Tot <= 24000 # BTU/hr + total_clg_oversize_limit = 1.20 + elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeSingleStage && hvac_sizings.Cool_Load_Tot > 24000 # BTU/hr + total_clg_oversize_limit = 1.15 + elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeTwoStage + total_clg_oversize_limit = 1.25 + end + elsif load_shr >= 0.95 # Section N.2.3.2 ACCA Man. S 2023, Dry Sizing Condition + if hvac_cooling.compressor_type == HPXML::HVACCompressorTypeSingleStage + # Total Cooling Capacity / (Total Cooling Load + 6 kBtuh) <= 1.00 + # 1.00 = non-load adjusted oversize limit + # goal --> determine "load adjusted" oversize limit + # Total Cooling Capacity <= 1.00 * (Total Cooling Load + 6 kBtuh) + # Total Cooling Capacity <= 1.00 * Total Cooling Load + 1.00 * 6 kBtuh + # Total Cooling Capacity / Total Cooling Load <= 1.00 + (1.00 * 6 kBtuh) / Total Cooling Load + # Total Cooling Capacity Factor <= 1.00 + 6 kBtuh / Total Cooling Load + # Effective Total Cooling Oversize Limit = 1.00 + 6 kBtuh / Total Cooling Load + total_clg_oversize_limit = 1 + 6000 / hvac_sizings.Cool_Load_Tot + total_clg_undersize_limit = 0.90 + sens_clg_undersize_limit = 0.90 + lat_clg_undersize_limit = 1.00 + elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeTwoStage + total_clg_oversize_limit = 1.15 # minimum compressor speed! TODO: incorporate minimum speed logic elsewhere + total_clg_undersize_limit = 0.90 # total clg undersize limit for dry climate, 2-stage compressor is as specified in ACCA 2014 + # ACCA 2023 Table N2.3.2 does not specify total cooling undersize limit. + sens_clg_undersize_limit = 0.90 + lat_clg_undersize_limit = 1.00 + end + end + end + elsif hpxml_bldg.header.heat_pump_sizing_methodology == HPXML::HeatPumpSizingACCA + # References "Overview of Size Limits for Residential HVAC Equipment" from Man. S 2014 + if ((weather.data.HDD65F / weather.data.CDD50F) < 2.0) || (load_shr < 0.95) + # mild winter or has latent cooling load + if not hvac_cooling.nil? + total_clg_undersize_limit = 0.90 + sens_clg_undersize_limit = 0.90 + lat_clg_undersize_limit = 1.00 + if hvac_cooling.compressor_type == HPXML::HVACCompressorTypeSingleStage + total_clg_oversize_limit = 1.15 + elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeTwoStage + total_clg_oversize_limit = 1.20 + elsif hvac_cooling.compressor_type == HPXML::HVACCompressorTypeVariableSpeed + total_clg_oversize_limit = 1.30 + end + end + elsif ((weather.data.HDD65F / weather.data.CDD50F) >= 2.0) && (load_shr >= 0.95) + # cold winter and no latent cooling load + # Manual S 2023 doesn't specify latent/sensible capacity undersize limits in this situation. + # from software perspective, could either return zero, nil, or the same latent/sensible + # undersize limits as the situation with latent cooling load/mild winter. + total_clg_undersize_limit = 0.90 + sens_clg_undersize_limit = nil + lat_clg_undersize_limit = nil + # Max Total Cooling Capacity = Total Cooling Load + 15 kBtuh + # i.e. Total Cooling Capacity <= Total Cooling Load + 15 kBtuh + # <= means solve for oversize limit, size factor (capacity/load) <= oversize limit + # Total Cooling Capacity / Total Cooling Load <= (Total Cooling Load + 15 kBtuh) / Total Cooling Load + # Total Cooling Oversize Limit = (Total Cooling Load + 15 kBtuh) / Total Cooling Load + total_clg_oversize_limit = (hvac_sizings.Cool_Load_Tot + 15000) / hvac_sizings.Cool_Load_Tot + end + end + + return total_clg_oversize_limit, total_clg_undersize_limit, sens_clg_undersize_limit, lat_clg_undersize_limit + + end # Transfers the design load totals from the HVAC loads object to the HVAC sizings object. # @@ -2108,7 +2187,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva hvac_cooling_ap = hvac_cooling.additional_properties is_ducted = !hvac_cooling.distribution_system.nil? cooling_delta_t = mj.cool_setpoint - hvac_cooling_ap.leaving_air_temp - oversize_limit, oversize_delta, undersize_limit = get_hvac_size_limits(hvac_cooling) + tot_clg_oversize_limit, tot_clg_undersize_limit, sens_clg_undersize_limit, lat_clg_undersize_limit = get_hvac_size_limits(hvac_cooling) end if hvac_sizings.Cool_Load_Tot <= 0 @@ -2218,11 +2297,11 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva # If the adjusted equipment size is negative (occurs at altitude), use oversize limit (the adjustment # almost always hits the oversize limit in this case, making this a safe assumption) if (cool_cap_design < 0) || (cool_sens_cap_design < 0) - cool_cap_design = oversize_limit * hvac_sizings.Cool_Load_Tot + cool_cap_design = tot_clg_oversize_limit * hvac_sizings.Cool_Load_Tot end # Limit total capacity to oversize limit - cool_cap_design = [cool_cap_design, oversize_limit * hvac_sizings.Cool_Load_Tot].min + cool_cap_design = [cool_cap_design, tot_clg_oversize_limit * hvac_sizings.Cool_Load_Tot].min # Determine rated capacities hvac_sizings.Cool_Capacity = cool_cap_design / total_cap_curve_value @@ -2231,7 +2310,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva # Determine the final sensible capacity at design using the SHR cool_sens_cap_design = cool_cap_design * design_shr - elsif cool_sens_cap_design < undersize_limit * hvac_sizings.Cool_Load_Sens + elsif cool_sens_cap_design < sens_undersize_limit * hvac_sizings.Cool_Load_Sens # Size by MJ8 Sensible Load, return to rated conditions, find rated sensible capacity with SHRRated. Limit total # capacity to oversizing limit. @@ -2241,7 +2320,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva cool_cap_design = cool_sens_cap_design / design_shr # Limit total capacity to oversize limit - cool_cap_design = [cool_cap_design, oversize_limit * hvac_sizings.Cool_Load_Tot].min + cool_cap_design = [cool_cap_design, tot_clg_oversize_limit * hvac_sizings.Cool_Load_Tot].min # rated capacities hvac_sizings.Cool_Capacity = cool_cap_design / total_cap_curve_value @@ -2344,7 +2423,7 @@ def self.apply_hvac_equipment_adjustments(mj, runner, hvac_sizings, weather, hva 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 + cool_cap_design = [cool_cap_design, tot_clg_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 @@ -3168,8 +3247,10 @@ def self.calculate_heat_pump_adj_factor_at_outdoor_temperature(mj, hvac_heating, # @return [Double] Heat pump backup load (Btu/hr) def self.calculate_heat_pump_backup_load(mj, hvac_heating, heating_load, hp_nominal_heating_capacity, hvac_heating_speed, hpxml_bldg) if hpxml_bldg.header.heat_pump_backup_sizing_methodology == HPXML::HeatPumpBackupSizingEmergency - # Size backup to meet full design load in case heat pump fails - return heating_load + # Size backup to meet 85% of design load in case heat pump fails + # New ACCA Man S (2024)--> emergency heating load is 85% of heating load + # See Table N1.16.3.2 Electric Resistance Emergency Heat + return 0.85*heating_load elsif hpxml_bldg.header.heat_pump_backup_sizing_methodology == HPXML::HeatPumpBackupSizingSupplemental if not hvac_heating.backup_heating_switchover_temp.nil? min_compressor_temp = hvac_heating.backup_heating_switchover_temp