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

Non-transient dwelling unit energy recovery ventilation #1165

Merged
merged 15 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion data/standards/manage_OpenStudio_Standards.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def unique_properties(sheet_name)
when 'climate_zones'
['name', 'standard']
when 'energy_recovery'
['template', 'climate_zone', 'under_8000_hours']
['template', 'climate_zone', 'under_8000_hours', 'nontransient_dwelling']
when 'space_types_lighting_control'
['template', 'building_type', 'space_type']
else
Expand Down Expand Up @@ -345,6 +345,7 @@ def export_spreadsheet_to_json(spreadsheet_titles, dataset_type: 'os_stds')
bool_cols << 'hx'
bool_cols << 'data_center'
bool_cols << 'under_8000_hours'
bool_cols << 'nontransient_dwelling'
bool_cols << 'u_value_includes_interior_film_coefficient'
bool_cols << 'u_value_includes_exterior_film_coefficient'

Expand Down
1 change: 1 addition & 0 deletions lib/openstudio-standards.rb
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ module OpenstudioStandards
require_relative "#{proto}/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems"
require_relative "#{proto}/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat"
require_relative "#{proto}/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed"
require_relative "#{proto}/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.HeatExchangerAirToAirSensibleAndLatent"
# DOE Ref 1980-2004
require_relative "#{proto}/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirTerminalSingleDuctVAVReheat"
require_relative "#{proto}/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.elevators"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ASHRAE9012019 < ASHRAE901
# @!group Model

# Set sensible and latent effectiveness at 100 and 75 heating and cooling airflow;
# The values are calculated by using ERR, which is introduced in 90.1-2016 Addendum CE
#
# This function is only used for nontransient dwelling units (Mid-rise and High-rise Apartment)
def heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency_err(heat_exchanger_air_to_air_sensible_and_latent, err, basis, climate_zone)
# Assumed to be sensible and latent at all flow
err = enthalpy_recovery_ratio_design_to_typical_adjustment(err, climate_zone)
full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff = heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio_to_effectiveness(err, basis)

heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(full_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(full_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(part_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(part_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(full_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(full_cool_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(part_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(part_cool_lat_eff)

OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerSensLat', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Set sensible and latent effectiveness calculated by using ERR.")
return true
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,50 @@ def model_add_lights_shutoff(model)

return true
end

# This function is used to add zone ERV
#
# This function is only used for nontransient dwelling units (Mid-rise and High-rise Apartment)
def model_apply_prototype_hvac_efficiency_adjustments(model)
lymereJ marked this conversation as resolved.
Show resolved Hide resolved
# identify the building type
building_data = model_get_building_climate_zone_and_building_type(model)
building_type = building_data['building_type']
if building_type == 'MidriseApartment' || building_type == 'HighriseApartment'
# update effectiveness based err
climate_zone = building_data['climate_zone']
model.getAirLoopHVACs.each do |air_loop_hvac|
if air_loop_hvac_energy_recovery_ventilator_required?(air_loop_hvac, climate_zone)
# calculate the number of system operating hours based on the availability schedule
under_8000_hour = false
avail_sch = air_loop_hvac.availabilitySchedule
if avail_sch == air_loop_hvac.model.alwaysOnDiscreteSchedule
under_8000_hour = false
elsif avail_sch.to_ScheduleRuleset.is_initialized
avail_sch = avail_sch.to_ScheduleRuleset.get
ann_op_hrs = schedule_ruleset_annual_hours_above_value(avail_sch, 0.0)
if ann_op_hrs < 8000
under_8000_hour = true
end
else
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.ashrae_90_1_2019.AirLoopHVAC', "For #{air_loop_hvac.name}: could not determine annual operating hours. Assuming less than 8,000 for ERV determination.")
end
# determine search_criteria
search_criteria = {
'template' => template,
'climate_zone' => climate_zone,
'under_8000_hour' => under_8000_hour,
'nontransient_dwelling' => true
}
zones = air_loop_hvac.thermalZones
model_add_zone_erv_err(model, zones, search_criteria)
end
end
else
# ERVs
# # Applies the DOE Prototype Building assumption that ERVs use
# # enthalpy wheels and therefore exceed the minimum effectiveness specified by 90.1
model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency(obj) }
return true
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,61 @@ def model_cw_loop_cooling_tower_fan_type(model)
fan_type = 'Variable Speed Fan'
return fan_type
end

# Add zone ERV
# This function adds supply fan, exhaust fan, heat exchanger, and zone hvac
#
# This function is only used for nontransient dwelling units (Mid-rise and High-rise Apartment)
def model_add_zone_erv_err(model, thermal_zones, search_criteria)
ervs = []
thermal_zones.each do |zone|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding ERV for #{zone.name}.")

# determine the OA requirement for this zone
min_oa_flow_m3_per_s_per_m2 = thermal_zone_outdoor_airflow_rate_per_area(zone)
supply_fan = create_fan_by_name(model,
'ERV_Supply_Fan',
fan_name: "#{zone.name} ERV Supply Fan")
impeller_eff = fan_baseline_impeller_efficiency(supply_fan)
fan_change_impeller_efficiency(supply_fan, impeller_eff)
exhaust_fan = create_fan_by_name(model,
'ERV_Supply_Fan',
fan_name: "#{zone.name} ERV Exhaust Fan")
fan_change_impeller_efficiency(exhaust_fan, impeller_eff)

erv_controller = OpenStudio::Model::ZoneHVACEnergyRecoveryVentilatorController.new(model)
erv_controller.setName("#{zone.name} ERV Controller")

climate_zone = search_criteria['climate_zone']
erv_err = model_find_object(standards_data['energy_recovery'], search_criteria)
err_basis = erv_err['err_basis']
err = erv_err['err']

heat_exchanger = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model)
heat_exchanger.setName("#{zone.name} ERV HX")
heat_exchanger.setHeatExchangerType('Plate')
heat_exchanger.setEconomizerLockout(false)
heat_exchanger.setSupplyAirOutletTemperatureControl(false)
heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency_err(heat_exchanger, err, err_basis, climate_zone)

zone_hvac = OpenStudio::Model::ZoneHVACEnergyRecoveryVentilator.new(model, heat_exchanger, supply_fan, exhaust_fan)
zone_hvac.setName("#{zone.name} ERV")
zone_hvac.setVentilationRateperUnitFloorArea(min_oa_flow_m3_per_s_per_m2)
zone_hvac.setController(erv_controller)
zone_hvac.addToThermalZone(zone)

# ensure the ERV takes priority, so ventilation load is included when treated by other zonal systems
# From EnergyPlus I/O reference:
# "For situations where one or more equipment types has limited capacity or limited control capability, order the
# sequence so that the most controllable piece of equipment runs last. For example, with a dedicated outdoor air
# system (DOAS), the air terminal for the DOAS should be assigned Heating Sequence = 1 and Cooling Sequence = 1.
# Any other equipment should be assigned sequence 2 or higher so that it will see the net load after the DOAS air
# is added to the zone."
zone.setCoolingPriority(zone_hvac.to_ModelObject.get, 1)
zone.setHeatingPriority(zone_hvac.to_ModelObject.get, 1)

ervs << zone_hvac
return ervs
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ class Standard

# Sets the minimum effectiveness of the heat exchanger per
# the standard.
def heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency(heat_exchanger_air_to_air_sensible_and_latent)
def heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(heat_exchanger_air_to_air_sensible_and_latent)
# Assumed to be sensible and latent at all flow
min_effct = heat_exchanger_air_to_air_sensible_and_latent_minimum_efficiency(heat_exchanger_air_to_air_sensible_and_latent)
full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff = heat_exchanger_air_to_air_sensible_and_latent_minimum_effectiveness(heat_exchanger_air_to_air_sensible_and_latent)

heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(min_effct)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(full_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(full_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(part_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(part_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(full_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(full_cool_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(part_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(part_cool_lat_eff)

OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerSensLat', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Set sensible and latent effectiveness to #{(min_effct * 100).round}%.")
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerSensLat', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Set sensible and latent effectiveness.")

return true
end
Expand All @@ -26,8 +26,64 @@ def heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency(heat_exchange
# effectiveness at all flow rates.
#
# @param heat_exchanger_air_to_air_sensible_and_latent [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] the heat exchanger
def heat_exchanger_air_to_air_sensible_and_latent_minimum_efficiency(heat_exchanger_air_to_air_sensible_and_latent)
min_effct = 0.5
return min_effct
def heat_exchanger_air_to_air_sensible_and_latent_minimum_effectiveness(heat_exchanger_air_to_air_sensible_and_latent)
full_htg_sens_eff = 0.5
full_htg_lat_eff = 0.5
part_htg_sens_eff = 0.5
part_htg_lat_eff = 0.5
full_cool_sens_eff = 0.5
full_cool_lat_eff = 0.5
part_cool_sens_eff = 0.5
part_cool_lat_eff = 0.5
return full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff
end

# Adjust ERR from design conditions to ERR for typical conditions.
# This is only applies to the 2B and 3B climate zones. In these
# climate zones a 50% ERR at typical condition leads a ERR > 50%,
# the ERR is thus scaled down.
#
# @param err [float] Enthalpy Recovery Ratio (ERR)
# @param climate_zone [String] climate zone
# @return [float] adjusted ERR
def enthalpy_recovery_ratio_design_to_typical_adjustment(err, climate_zone)
if climate_zone.include? '2B'
err /= 0.65 / 0.55
elsif climate_zone.include? '3B'
err /= 0.62 / 0.55
end

return err
end

# Calculate a heat exchanger's effectiveness for a specific ERR and design basis.
# Regressions were determined based available manufacturer data.
#
# @param err [float] Enthalpy Recovery Ratio (ERR)
# @param basis [String] basis for effectiveness calculation, either cooling or heating
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'cooling' or 'heating'

# @return [Array] heating and cooling heat exchanger effectiveness at 100% and 75% nominal airflow
def heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio_to_effectiveness(err, basis)
case basis
when 'Cooling'
full_htg_sens_eff = (20.707 * err**2 + 41.354 * err + 40.755) / 100
full_htg_lat_eff = (127.45 * err - 18.625) / 100
part_htg_sens_eff = (-0.1214 * err + 1.111) * full_htg_sens_eff
part_htg_lat_eff = (-0.3405 * err + 1.2732) * full_htg_lat_eff
full_cool_sens_eff = (70.689 * err + 30.789) / 100
full_cool_lat_eff = (48.054 * err**2 + 83.082 * err - 12.881) / 100
part_cool_sens_eff = (-0.1214 * err + 1.111) * full_cool_sens_eff
part_cool_lat_eff = (-0.3982 * err + 1.3151) * full_cool_lat_eff
when 'Heating'
full_htg_sens_eff = err
full_htg_lat_eff = 0.0
part_htg_sens_eff = (-0.1214 * err + 1.111) * full_htg_sens_eff
part_htg_lat_eff = 0.0
full_cool_sens_eff = err * (70.689 * err + 30.789) / (20.707 * err**2 + 41.354 * err + 40.755)
full_cool_lat_eff = 0.0
part_cool_sens_eff = (-0.1214 * err + 1.111) * full_cool_sens_eff
part_cool_lat_eff = 0.0
end

return full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff
end
end
2 changes: 1 addition & 1 deletion lib/openstudio-standards/standards/Standards.Model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1548,7 +1548,7 @@ def model_apply_hvac_efficiency_standard(model, climate_zone, apply_controls: tr
model.getEvaporativeFluidCoolerTwoSpeeds.sort.each { |obj| fluid_cooler_apply_minimum_power_per_flow(obj, equipment_type: 'Closed Cooling Tower') }

# ERVs
model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_efficiency(obj) }
model.getHeatExchangerAirToAirSensibleAndLatents.each { |obj| heat_exchanger_air_to_air_sensible_and_latent_apply_effectiveness(obj) }

# Gas Heaters
model.getCoilHeatingGass.sort.each { |obj| coil_heating_gas_apply_efficiency_and_curves(obj) }
Expand Down
Loading