example_files/resources/hpxml-measures/HPXMLtoOpenStudio/resources/airflow.rb in urbanopt-cli-0.5.1 vs example_files/resources/hpxml-measures/HPXMLtoOpenStudio/resources/airflow.rb in urbanopt-cli-0.5.2
- old
+ new
@@ -1,27 +1,25 @@
# frozen_string_literal: true
class Airflow
- def self.apply(model, runner, weather, spaces, air_infils, vent_fans, clothes_dryers, nbeds,
- duct_systems, infil_volume, infil_height, open_window_area,
- nv_clg_ssn_sensor, min_neighbor_distance, vented_attic, vented_crawl,
- site_type, shelter_coef, has_flue_chimney, hvac_map, eri_version,
- apply_ashrae140_assumptions, schedules_file)
+ def self.apply(model, runner, weather, spaces, hpxml, cfa, nbeds,
+ ncfl_ag, duct_systems, nv_clg_ssn_sensor, hvac_map, eri_version,
+ frac_windows_operable, apply_ashrae140_assumptions, schedules_file)
# Global variables
@runner = runner
@spaces = spaces
- @building_height = Geometry.get_max_z_of_spaces(model.getSpaces)
- @infil_volume = infil_volume
- @infil_height = infil_height
+ @infil_volume = hpxml.air_infiltration_measurements.select { |i| !i.infiltration_volume.nil? }[0].infiltration_volume
+ @infil_height = hpxml.inferred_infiltration_height(@infil_volume)
@living_space = spaces[HPXML::LocationLivingSpace]
@living_zone = @living_space.thermalZone.get
@nbeds = nbeds
+ @ncfl_ag = ncfl_ag
@eri_version = eri_version
@apply_ashrae140_assumptions = apply_ashrae140_assumptions
- @cfa = UnitConversions.convert(@living_space.floorArea, 'm^2', 'ft^2')
+ @cfa = cfa
# Global sensors
@pbar_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Outdoor Air Barometric Pressure')
@pbar_sensor.setName('out pb s')
@@ -55,11 +53,11 @@
# Ventilation fans
vent_fans_mech = []
vent_fans_kitchen = []
vent_fans_bath = []
vent_fans_whf = []
- vent_fans.each do |vent_fan|
+ hpxml.ventilation_fans.each do |vent_fan|
if vent_fan.used_for_whole_building_ventilation
vent_fans_mech << vent_fan
elsif vent_fan.used_for_seasonal_cooling_load_reduction
vent_fans_whf << vent_fan
elsif vent_fan.used_for_local_ventilation && vent_fan.fan_location == HPXML::LocationKitchen
@@ -70,11 +68,11 @@
@runner.registerWarning("Unexpected ventilation fan '#{vent_fan.id}'. The fan will not be modeled.")
end
end
# Vented clothes dryers in conditioned space
- vented_dryers = clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 && [HPXML::LocationLivingSpace, HPXML::LocationBasementConditioned].include?(cd.location) }
+ vented_dryers = hpxml.clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 && [HPXML::LocationLivingSpace, HPXML::LocationBasementConditioned].include?(cd.location) }
# Initialization
initialize_cfis(model, vent_fans_mech, hvac_map)
model.getAirLoopHVACs.each do |air_loop|
initialize_air_loop_objects(model, air_loop)
@@ -89,18 +87,31 @@
apply_ducts(model, ducts, object)
end
# Apply infiltration/ventilation
- @wind_speed = set_wind_speed_correction(model, site_type, shelter_coef, min_neighbor_distance)
- apply_natural_ventilation_and_whole_house_fan(model, weather, vent_fans_whf, open_window_area, nv_clg_ssn_sensor)
- apply_infiltration_and_ventilation_fans(model, weather, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers,
- has_flue_chimney, air_infils, vented_attic, vented_crawl, hvac_map, schedules_file)
- end
+ set_wind_speed_correction(model, hpxml.site)
+ window_area = hpxml.windows.map { |w| w.area }.sum(0.0)
+ open_window_area = window_area * frac_windows_operable * 0.5 * 0.2 # Assume A) 50% of the area of an operable window can be open, and B) 20% of openable window area is actually open
- def self.get_default_shelter_coefficient()
- return 0.5 # Table 4.2.2(1)(g)
+ vented_attic = nil
+ hpxml.attics.each do |attic|
+ next unless attic.attic_type == HPXML::AtticTypeVented
+
+ vented_attic = attic
+ end
+ vented_crawl = nil
+ hpxml.foundations.each do |foundation|
+ next unless foundation.foundation_type == HPXML::FoundationTypeCrawlspaceVented
+
+ vented_crawl = foundation
+ end
+
+ apply_natural_ventilation_and_whole_house_fan(model, weather, hpxml.site, vent_fans_whf, open_window_area, nv_clg_ssn_sensor)
+ apply_infiltration_and_ventilation_fans(model, weather, hpxml.site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers,
+ hpxml.building_construction.has_flue_or_chimney, hpxml.air_infiltration_measurements,
+ vented_attic, vented_crawl, hvac_map, schedules_file)
end
def self.get_default_fraction_of_windows_operable()
# Combining the value below with the assumption that 50% of
# the area of an operable window can be open produces the
@@ -108,15 +119,15 @@
# the window area ... can be opened for natural ventilation"
return 0.67 # 67%
end
def self.get_default_vented_attic_sla()
- return 1.0 / 300.0 # Table 4.2.2(1) - Attics
+ return (1.0 / 300.0).round(6) # Table 4.2.2(1) - Attics
end
def self.get_default_vented_crawl_sla()
- return 1.0 / 150.0 # Table 4.2.2(1) - Crawlspaces
+ return (1.0 / 150.0).round(6) # Table 4.2.2(1) - Crawlspaces
end
def self.get_default_mech_vent_fan_power(vent_fan)
# 301-2019: Table 4.2.2(1b)
# Returns fan power in W/cfm
@@ -135,64 +146,57 @@
end
end
private
- def self.set_wind_speed_correction(model, site_type, shelter_coef, min_neighbor_distance)
+ def self.set_wind_speed_correction(model, site)
+ site_ap = site.additional_properties
+
site_map = { HPXML::SiteTypeRural => 'Country', # Flat, open country
HPXML::SiteTypeSuburban => 'Suburbs', # Rough, wooded country, suburbs
HPXML::SiteTypeUrban => 'City' } # Towns, city outskirts, center of large cities
- model.getSite.setTerrain(site_map[site_type])
+ model.getSite.setTerrain(site_map[site.site_type])
- wind_speed = WindSpeed.new
- wind_speed.height = 32.8 # ft (Standard weather station height)
+ site_ap.height = 32.8 # ft (Standard weather station height)
# Open, Unrestricted at Weather Station
- wind_speed.terrain_multiplier = 1.0
- wind_speed.terrain_exponent = 0.15
- wind_speed.ashrae_terrain_thickness = 270
- wind_speed.ashrae_terrain_exponent = 0.14
+ site_ap.terrain_multiplier = 1.0
+ site_ap.terrain_exponent = 0.15
+ site_ap.ashrae_terrain_thickness = 270
+ site_ap.ashrae_terrain_exponent = 0.14
- if site_type == HPXML::SiteTypeRural
- wind_speed.site_terrain_multiplier = 0.85
- wind_speed.site_terrain_exponent = 0.20
- wind_speed.ashrae_site_terrain_thickness = 270 # Flat, open country
- wind_speed.ashrae_site_terrain_exponent = 0.14 # Flat, open country
- elsif site_type == HPXML::SiteTypeSuburban
- wind_speed.site_terrain_multiplier = 0.67
- wind_speed.site_terrain_exponent = 0.25
- wind_speed.ashrae_site_terrain_thickness = 370 # Rough, wooded country, suburbs
- wind_speed.ashrae_site_terrain_exponent = 0.22 # Rough, wooded country, suburbs
- elsif site_type == HPXML::SiteTypeUrban
- wind_speed.site_terrain_multiplier = 0.47
- wind_speed.site_terrain_exponent = 0.35
- wind_speed.ashrae_site_terrain_thickness = 460 # Towns, city outskirts, center of large cities
- wind_speed.ashrae_site_terrain_exponent = 0.33 # Towns, city outskirts, center of large cities
+ if site.site_type == HPXML::SiteTypeRural
+ site_ap.site_terrain_multiplier = 0.85
+ site_ap.site_terrain_exponent = 0.20
+ site_ap.ashrae_site_terrain_thickness = 270 # Flat, open country
+ site_ap.ashrae_site_terrain_exponent = 0.14 # Flat, open country
+ elsif site.site_type == HPXML::SiteTypeSuburban
+ site_ap.site_terrain_multiplier = 0.67
+ site_ap.site_terrain_exponent = 0.25
+ site_ap.ashrae_site_terrain_thickness = 370 # Rough, wooded country, suburbs
+ site_ap.ashrae_site_terrain_exponent = 0.22 # Rough, wooded country, suburbs
+ elsif site.site_type == HPXML::SiteTypeUrban
+ site_ap.site_terrain_multiplier = 0.47
+ site_ap.site_terrain_exponent = 0.35
+ site_ap.ashrae_site_terrain_thickness = 460 # Towns, city outskirts, center of large cities
+ site_ap.ashrae_site_terrain_exponent = 0.33 # Towns, city outskirts, center of large cities
end
- # Local Shielding
- if shelter_coef.nil?
- # FIXME: Move into HPXML defaults
- if min_neighbor_distance.nil?
- # Typical shelter for isolated rural house
- wind_speed.S_wo = 0.90
- elsif min_neighbor_distance > @building_height
- # Typical shelter caused by other building across the street
- wind_speed.S_wo = 0.70
- else
- # Typical shelter for urban buildings where sheltering obstacles
- # are less than one building height away.
- wind_speed.S_wo = 0.50
- end
- else
- wind_speed.S_wo = Float(shelter_coef)
- end
-
# S-G Shielding Coefficients are roughly 1/3 of AIM2 Shelter Coefficients
- wind_speed.shielding_coef = wind_speed.S_wo / 3.0
+ site_ap.s_g_shielding_coef = site_ap.aim2_shelter_coeff / 3.0
+ end
- return wind_speed
+ def self.get_aim2_shelter_coefficient(shielding_of_home)
+ # Mapping based on AIM-2 Model by Walker/Wilson
+ # Table 2: Estimates of Shelter Coefficient S_wo for No Flue
+ if shielding_of_home == HPXML::ShieldingNormal
+ return 0.50 # Class 4: "Very heavy shielding, many large obstructions within one house height"
+ elsif shielding_of_home == HPXML::ShieldingExposed
+ return 0.90 # Class 2: "Light local shielding with few obstructions within two house heights"
+ elsif shielding_of_home == HPXML::ShieldingWellShielded
+ return 0.30 # Class 5: "Complete shielding, with large buildings immediately adjacent"
+ end
end
def self.apply_infiltration_to_unconditioned_space(model, space, ach = nil, ela = nil, c_w_SG = nil, c_s_SG = nil)
if ach.to_f > 0
# Model ACH as constant infiltration/ventilation
@@ -216,11 +220,11 @@
leakage_area.setWindCoefficient(c_w_SG * 0.01)
leakage_area.setSpace(space)
end
end
- def self.apply_natural_ventilation_and_whole_house_fan(model, weather, vent_fans_whf, open_window_area, nv_clg_ssn_sensor)
+ def self.apply_natural_ventilation_and_whole_house_fan(model, weather, site, vent_fans_whf, open_window_area, nv_clg_ssn_sensor)
if @living_zone.thermostatSetpointDualSetpoint.is_initialized
thermostat = @living_zone.thermostatSetpointDualSetpoint.get
htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
end
@@ -308,11 +312,11 @@
area = 0.6 * open_window_area # ft^2, for Sherman-Grimsrud
max_rate = 20.0 # Air Changes per hour
max_flow_rate = max_rate * @infil_volume / UnitConversions.convert(1.0, 'hr', 'min')
neutral_level = 0.5
hor_lk_frac = 0.0
- c_w, c_s = calc_wind_stack_coeffs(hor_lk_frac, neutral_level, @living_space, @infil_height)
+ c_w, c_s = calc_wind_stack_coeffs(site, hor_lk_frac, neutral_level, @living_space, @infil_height)
max_oa_hr = 0.0115 # From BA HSP
max_oa_rh = 0.7 # From BA HSP
# Program
vent_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
@@ -565,22 +569,10 @@
def self.apply_ducts(model, ducts, object)
ducts.each do |duct|
if duct.leakage_frac.nil? == duct.leakage_cfm25.nil?
fail 'Ducts: Must provide either leakage fraction or cfm25, but not both.'
end
- if (not duct.leakage_frac.nil?) && ((duct.leakage_frac < 0) || (duct.leakage_frac > 1))
- fail 'Ducts: Leakage Fraction must be greater than or equal to 0 and less than or equal to 1.'
- end
- if (not duct.leakage_cfm25.nil?) && (duct.leakage_cfm25 < 0)
- fail 'Ducts: Leakage CFM25 must be greater than or equal to 0.'
- end
- if duct.rvalue < 0
- fail 'Ducts: Insulation Nominal R-Value must be greater than or equal to 0.'
- end
- if duct.area < 0
- fail 'Ducts: Surface Area must be greater than or equal to 0.'
- end
end
ducts.each do |duct|
duct.rvalue = get_duct_insulation_rvalue(duct.rvalue, duct.side) # Convert from nominal to actual R-value
if not duct.loc_schedule.nil?
@@ -593,21 +585,10 @@
duct.location = HPXML::LocationOutside
duct.zone = nil
end
end
- if ducts.size > 0
- # Store info for HVAC Sizing measure
- object.additionalProperties.setFeature(Constants.SizingInfoDuctExist, true)
- object.additionalProperties.setFeature(Constants.SizingInfoDuctSides, ducts.map { |duct| duct.side }.join(','))
- object.additionalProperties.setFeature(Constants.SizingInfoDuctLocations, ducts.map { |duct| duct.location.to_s }.join(','))
- object.additionalProperties.setFeature(Constants.SizingInfoDuctLeakageFracs, ducts.map { |duct| duct.leakage_frac.to_f }.join(','))
- object.additionalProperties.setFeature(Constants.SizingInfoDuctLeakageCFM25s, ducts.map { |duct| duct.leakage_cfm25.to_f }.join(','))
- object.additionalProperties.setFeature(Constants.SizingInfoDuctAreas, ducts.map { |duct| duct.area.to_f }.join(','))
- object.additionalProperties.setFeature(Constants.SizingInfoDuctRvalues, ducts.map { |duct| duct.rvalue.to_f }.join(','))
- end
-
return if ducts.size == 0 # No ducts
# get duct located zone or ambient temperature schedule objects
duct_locations = ducts.map { |duct| if duct.zone.nil? then duct.loc_schedule else duct.zone end }.uniq
@@ -1108,11 +1089,11 @@
manager.setCallingPoint('EndOfSystemTimestepAfterHVACReporting')
manager.addProgram(duct_program)
end
end
- def self.apply_infiltration_to_garage(model, weather, ach50)
+ def self.apply_infiltration_to_garage(model, weather, site, ach50)
return if @spaces[HPXML::LocationGarage].nil?
space = @spaces[HPXML::LocationGarage]
area = UnitConversions.convert(space.floorArea, 'm^2', 'ft^2')
volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3')
@@ -1120,11 +1101,11 @@
neutral_level = 0.5
sla = get_infiltration_SLA_from_ACH50(ach50, 0.65, area, volume)
ela = sla * area
ach = get_infiltration_ACH_from_SLA(sla, 8.202, weather)
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
- c_w_SG, c_s_SG = calc_wind_stack_coeffs(hor_lk_frac, neutral_level, space)
+ c_w_SG, c_s_SG = calc_wind_stack_coeffs(site, hor_lk_frac, neutral_level, space)
apply_infiltration_to_unconditioned_space(model, space, nil, ela, c_w_SG, c_s_SG)
end
def self.apply_infiltration_to_unconditioned_basement(model, weather)
return if @spaces[HPXML::LocationBasementUnconditioned].nil?
@@ -1132,13 +1113,10 @@
space = @spaces[HPXML::LocationBasementUnconditioned]
volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3')
ach = 0.1 # Assumption
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil)
-
- # Store info for HVAC Sizing measure
- space.thermalZone.get.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, cfm.to_f)
end
def self.apply_infiltration_to_vented_crawlspace(model, weather, vented_crawl)
return if @spaces[HPXML::LocationCrawlspaceVented].nil?
@@ -1146,13 +1124,10 @@
volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3')
sla = vented_crawl.vented_crawlspace_sla
ach = get_infiltration_ACH_from_SLA(sla, 8.202, weather)
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil)
-
- # Store info for HVAC Sizing measure
- space.thermalZone.get.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, cfm.to_f)
end
def self.apply_infiltration_to_unvented_crawlspace(model, weather)
return if @spaces[HPXML::LocationCrawlspaceUnvented].nil?
@@ -1160,16 +1135,13 @@
volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3')
sla = 0 # Assumption
ach = get_infiltration_ACH_from_SLA(sla, 8.202, weather)
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil)
-
- # Store info for HVAC Sizing measure
- space.thermalZone.get.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, cfm.to_f)
end
- def self.apply_infiltration_to_vented_attic(model, weather, vented_attic)
+ def self.apply_infiltration_to_vented_attic(model, weather, site, vented_attic)
return if @spaces[HPXML::LocationAtticVented].nil?
if not vented_attic.vented_attic_sla.nil?
vented_attic_sla = vented_attic.vented_attic_sla
elsif not vented_attic.vented_attic_ach.nil?
@@ -1188,23 +1160,20 @@
neutral_level = 0.5
sla = vented_attic_sla
ach = get_infiltration_ACH_from_SLA(sla, 8.202, weather)
ela = sla * vented_attic_area
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
- c_w_SG, c_s_SG = calc_wind_stack_coeffs(hor_lk_frac, neutral_level, space)
+ c_w_SG, c_s_SG = calc_wind_stack_coeffs(site, hor_lk_frac, neutral_level, space)
apply_infiltration_to_unconditioned_space(model, space, nil, ela, c_w_SG, c_s_SG)
elsif not vented_attic_const_ach.nil?
ach = vented_attic_const_ach
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil)
end
-
- # Store info for HVAC Sizing measure
- space.thermalZone.get.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, cfm.to_f)
end
- def self.apply_infiltration_to_unvented_attic(model, weather)
+ def self.apply_infiltration_to_unvented_attic(model, weather, site)
return if @spaces[HPXML::LocationAtticUnvented].nil?
space = @spaces[HPXML::LocationAtticUnvented]
area = UnitConversions.convert(space.floorArea, 'm^2', 'ft^2')
volume = UnitConversions.convert(space.volume, 'm^3', 'ft^3')
@@ -1212,15 +1181,12 @@
neutral_level = 0.5
sla = 0 # Assumption
ach = get_infiltration_ACH_from_SLA(sla, 8.202, weather)
ela = sla * area
cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume
- c_w_SG, c_s_SG = calc_wind_stack_coeffs(hor_lk_frac, neutral_level, space)
+ c_w_SG, c_s_SG = calc_wind_stack_coeffs(site, hor_lk_frac, neutral_level, space)
apply_infiltration_to_unconditioned_space(model, space, nil, ela, c_w_SG, c_s_SG)
-
- # Store info for HVAC Sizing measure
- space.thermalZone.get.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, cfm.to_f)
end
def self.apply_local_ventilation(model, vent_object_array, obj_type_name)
obj_sch_sensors = {}
vent_object_array.each_with_index do |vent_object, index|
@@ -1603,12 +1569,12 @@
infil_program.addLine(" Set #{clg_energy_actuator.name} = 0.0")
infil_program.addLine('EndIf')
end
end
- def self.apply_mechanical_ventilation(model, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers,
- range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map, has_flue_chimney, hvac_map)
+ def self.apply_infiltration_and_mechanical_ventilation(model, site, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers,
+ range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map, has_flue_chimney, hvac_map)
# Categorize fans into different types
vent_mech_preheat = vent_fans_mech.select { |vent_mech| (not vent_mech.preheating_efficiency_cop.nil?) }
vent_mech_precool = vent_fans_mech.select { |vent_mech| (not vent_mech.precooling_efficiency_cop.nil?) }
vent_mech_shared = vent_fans_mech.select { |vent_mech| vent_mech.is_shared_system }
@@ -1639,44 +1605,14 @@
# Average in-unit cfms (include recirculation from in unit cfms for shared systems)
sup_cfm_tot = vent_mech_sup_tot.map { |vent_mech| vent_mech.average_total_unit_flow_rate }.sum(0.0)
exh_cfm_tot = vent_mech_exh_tot.map { |vent_mech| vent_mech.average_total_unit_flow_rate }.sum(0.0)
bal_cfm_tot = vent_mech_bal_tot.map { |vent_mech| vent_mech.average_total_unit_flow_rate }.sum(0.0)
erv_hrv_cfm_tot = vent_mech_erv_hrv_tot.map { |vent_mech| vent_mech.average_total_unit_flow_rate }.sum(0.0)
- cfis_cfm_tot = vent_mech_cfis_tot.map { |vent_mech| vent_mech.average_total_unit_flow_rate }.sum(0.0)
- # Average preconditioned oa air cfms (only oa, recirculation will be addressed below for all shared systems)
- oa_cfm_preheat = vent_mech_preheat.map { |vent_mech| vent_mech.average_oa_unit_flow_rate * vent_mech.preheating_fraction_load_served }.sum(0.0)
- oa_cfm_precool = vent_mech_precool.map { |vent_mech| vent_mech.average_oa_unit_flow_rate * vent_mech.precooling_fraction_load_served }.sum(0.0)
- recirc_cfm_shared = vent_mech_shared.map { |vent_mech| vent_mech.average_total_unit_flow_rate - vent_mech.average_oa_unit_flow_rate }.sum(0.0)
-
- # HVAC Sizing data
- tot_sup_cfm = sup_cfm_tot + bal_cfm_tot + erv_hrv_cfm_tot + cfis_cfm_tot
- tot_exh_cfm = exh_cfm_tot + bal_cfm_tot + erv_hrv_cfm_tot
- tot_unbal_cfm = (tot_sup_cfm - tot_exh_cfm).abs
- tot_bal_cfm = [tot_exh_cfm, tot_sup_cfm].min
-
# Calculate effectivenesses for all ERV/HRV and store results in a hash
hrv_erv_effectiveness_map = calc_hrv_erv_effectiveness(vent_mech_erv_hrv_tot)
- # Calculate cfm weighted average effectivenesses for the combined balanced airflow
- weighted_vent_mech_lat_eff = 0.0
- weighted_vent_mech_apparent_sens_eff = 0.0
- vent_mech_erv_hrv_unprecond = vent_mech_erv_hrv_tot.select { |vent_mech| vent_mech.preheating_efficiency_cop.nil? && vent_mech.precooling_efficiency_cop.nil? }
- vent_mech_erv_hrv_unprecond.each do |vent_mech|
- weighted_vent_mech_lat_eff += vent_mech.average_oa_unit_flow_rate / tot_bal_cfm * hrv_erv_effectiveness_map[vent_mech][:vent_mech_lat_eff]
- weighted_vent_mech_apparent_sens_eff += vent_mech.average_oa_unit_flow_rate / tot_bal_cfm * hrv_erv_effectiveness_map[vent_mech][:vent_mech_apparent_sens_eff]
- end
- # Store info for HVAC Sizing measure
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentLatentEffectiveness, weighted_vent_mech_lat_eff)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentApparentSensibleEffectiveness, weighted_vent_mech_apparent_sens_eff)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentWholeHouseRateBalanced, tot_bal_cfm)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentWholeHouseRateUnbalanced, tot_unbal_cfm)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentWholeHouseRatePreHeated, oa_cfm_preheat)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentWholeHouseRatePreCooled, oa_cfm_precool)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentWholeHouseRateRecirculated, recirc_cfm_shared)
- model.getBuilding.additionalProperties.setFeature(Constants.SizingInfoMechVentExist, (not vent_fans_mech.empty?))
-
infil_flow = OpenStudio::Model::SpaceInfiltrationDesignFlowRate.new(model)
infil_flow.setName(Constants.ObjectNameInfiltration + ' flow')
infil_flow.setSchedule(model.alwaysOnDiscreteSchedule)
infil_flow.setSpace(@living_space)
infil_flow_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(infil_flow, *EPlus::EMSActuatorZoneInfiltrationFlowRate)
@@ -1685,11 +1621,11 @@
# Living Space Infiltration Calculation/Program
infil_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
infil_program.setName(Constants.ObjectNameInfiltration + ' program')
# Calculate infiltration without adjustment by ventilation
- apply_infiltration_to_living(living_ach50, living_const_ach, infil_program, weather, has_flue_chimney)
+ apply_infiltration_to_living(site, living_ach50, living_const_ach, infil_program, weather, has_flue_chimney)
# Common variable and load actuators across multiple mech vent calculations, create only once
fan_sens_load_actuator, fan_lat_load_actuator = setup_mech_vent_vars_actuators(model: model, program: infil_program)
# Apply CFIS
@@ -1717,22 +1653,22 @@
program_calling_manager.setName("#{infil_program.name} calling manager")
program_calling_manager.setCallingPoint('BeginTimestepBeforePredictor')
program_calling_manager.addProgram(infil_program)
end
- def self.apply_infiltration_and_ventilation_fans(model, weather, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers,
+ def self.apply_infiltration_and_ventilation_fans(model, weather, site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers,
has_flue_chimney, air_infils, vented_attic, vented_crawl, hvac_map, schedules_file)
# Get living space infiltration
living_ach50 = nil
living_const_ach = nil
air_infils.each do |air_infil|
if (air_infil.unit_of_measure == HPXML::UnitsACH) && !air_infil.house_pressure.nil?
- living_ach = air_infil.air_leakage
- living_ach50 = calc_air_leakage_at_diff_pressure(0.65, living_ach, air_infil.house_pressure, 50.0)
+ living_achXX = air_infil.air_leakage
+ living_ach50 = calc_air_leakage_at_diff_pressure(0.65, living_achXX, air_infil.house_pressure, 50.0)
elsif (air_infil.unit_of_measure == HPXML::UnitsCFM) && !air_infil.house_pressure.nil?
- living_ach = air_infil.air_leakage * 60.0 / @infil_volume # Convert CFM to ACH
- living_ach50 = calc_air_leakage_at_diff_pressure(0.65, living_ach, air_infil.house_pressure, 50.0)
+ living_achXX = air_infil.air_leakage * 60.0 / @infil_volume # Convert CFM to ACH
+ living_ach50 = calc_air_leakage_at_diff_pressure(0.65, living_achXX, air_infil.house_pressure, 50.0)
elsif air_infil.unit_of_measure == HPXML::UnitsACHNatural
if @apply_ashrae140_assumptions
living_const_ach = air_infil.air_leakage
else
sla = get_infiltration_SLA_from_ACH(air_infil.air_leakage, @infil_height, weather)
@@ -1740,30 +1676,32 @@
end
end
end
# Infiltration for unconditioned spaces
- apply_infiltration_to_garage(model, weather, living_ach50)
+ apply_infiltration_to_garage(model, weather, site, living_ach50)
apply_infiltration_to_unconditioned_basement(model, weather)
apply_infiltration_to_vented_crawlspace(model, weather, vented_crawl)
apply_infiltration_to_unvented_crawlspace(model, weather)
- apply_infiltration_to_vented_attic(model, weather, vented_attic)
- apply_infiltration_to_unvented_attic(model, weather)
+ apply_infiltration_to_vented_attic(model, weather, site, vented_attic)
+ apply_infiltration_to_unvented_attic(model, weather, site)
# Local ventilation
range_sch_sensors_map = apply_local_ventilation(model, vent_fans_kitchen, Constants.ObjectNameMechanicalVentilationRangeFan)
bath_sch_sensors_map = apply_local_ventilation(model, vent_fans_bath, Constants.ObjectNameMechanicalVentilationBathFan)
# Clothes dryer exhaust
dryer_exhaust_sch_sensors_map = apply_dryer_exhaust(model, vented_dryers, schedules_file)
# Get mechanical ventilation
- apply_mechanical_ventilation(model, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers,
- range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map, has_flue_chimney, hvac_map)
+ apply_infiltration_and_mechanical_ventilation(model, site, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers,
+ range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map, has_flue_chimney, hvac_map)
end
- def self.apply_infiltration_to_living(living_ach50, living_const_ach, infil_program, weather, has_flue_chimney)
+ def self.apply_infiltration_to_living(site, living_ach50, living_const_ach, infil_program, weather, has_flue_chimney)
+ site_ap = site.additional_properties
+
if living_ach50.to_f > 0
# Based on "Field Validation of Algebraic Equations for Stack and
# Wind Driven Air Infiltration Calculations" by Walker and Wilson (1998)
outside_air_density = UnitConversions.convert(weather.header.LocalPressure, 'atm', 'Btu/ft^3') / (Gas.Air.r * (weather.data.AnnualAvgDrybulb + 460.0))
@@ -1777,15 +1715,13 @@
delta_pref = 0.016 # inH2O
c_i = a_o * (2.0 / outside_air_density)**0.5 * delta_pref**(0.5 - n_i) * inf_conv_factor
if has_flue_chimney
y_i = 0.2 # Fraction of leakage through the flue; 0.2 is a "typical" value according to THE ALBERTA AIR INFIL1RATION MODEL, Walker and Wilson, 1990
- flue_height = @building_height + 2.0 # ft
s_wflue = 1.0 # Flue Shelter Coefficient
else
y_i = 0.0 # Fraction of leakage through the flu
- flue_height = 0.0 # ft
s_wflue = 0.0 # Flue Shelter Coefficient
end
# Leakage distributions per Iain Walker (LBL) recommendations
if not @spaces[HPXML::LocationCrawlspaceVented].nil?
@@ -1802,20 +1738,24 @@
r_i = (leakage_ceiling + leakage_floor)
x_i = (leakage_ceiling - leakage_floor)
r_i *= (1 - y_i)
x_i *= (1 - y_i)
- z_f = flue_height / (@infil_height + Geometry.get_z_origin_for_zone(@living_zone))
# Calculate Stack Coefficient
m_o = (x_i + (2.0 * n_i + 1.0) * y_i)**2.0 / (2 - r_i)
if m_o <= 1.0
m_i = m_o # eq. 10
else
m_i = 1.0 # eq. 11
end
if has_flue_chimney
+ if @ncfl_ag <= 0
+ z_f = 1.0
+ else
+ z_f = (@ncfl_ag + 0.5) / @ncfl_ag # Typical value is 1.5 according to THE ALBERTA AIR INFIL1RATION MODEL, Walker and Wilson, 1990, presumably for a single story home
+ end
x_c = r_i + (2.0 * (1.0 - r_i - y_i)) / (n_i + 1.0) - 2.0 * y_i * (z_f - 1.0)**n_i # Eq. 13
f_i = n_i * y_i * (z_f - 1.0)**((3.0 * n_i - 1.0) / 3.0) * (1.0 - (3.0 * (x_c - x_i)**2.0 * r_i**(1 - n_i)) / (2.0 * (z_f + 1.0))) # Additive flue function, Eq. 12
else
x_c = r_i + (2.0 * (1.0 - r_i - y_i)) / (n_i + 1.0) # Critical value of ceiling-floor leakage difference where the neutral level is located at the ceiling (eq. 13)
f_i = 0.0 # Additive flue function (eq. 12)
@@ -1841,24 +1781,24 @@
wind_coef = f_w * UnitConversions.convert(outside_air_density / 2.0, 'lbm/ft^3', 'inH2O/mph^2')**n_i # inH2O^n/mph^2n
living_ach = get_infiltration_ACH_from_SLA(living_sla, @infil_height, weather)
living_cfm = living_ach / UnitConversions.convert(1.0, 'hr', 'min') * @infil_volume
- infil_program.addLine("Set p_m = #{@wind_speed.ashrae_terrain_exponent}")
- infil_program.addLine("Set p_s = #{@wind_speed.ashrae_site_terrain_exponent}")
- infil_program.addLine("Set s_m = #{@wind_speed.ashrae_terrain_thickness}")
- infil_program.addLine("Set s_s = #{@wind_speed.ashrae_site_terrain_thickness}")
- infil_program.addLine("Set z_m = #{UnitConversions.convert(@wind_speed.height, 'ft', 'm')}")
+ infil_program.addLine("Set p_m = #{site_ap.ashrae_terrain_exponent}")
+ infil_program.addLine("Set p_s = #{site_ap.ashrae_site_terrain_exponent}")
+ infil_program.addLine("Set s_m = #{site_ap.ashrae_terrain_thickness}")
+ infil_program.addLine("Set s_s = #{site_ap.ashrae_site_terrain_thickness}")
+ infil_program.addLine("Set z_m = #{UnitConversions.convert(site_ap.height, 'ft', 'm')}")
infil_program.addLine("Set z_s = #{UnitConversions.convert(@infil_height, 'ft', 'm')}")
infil_program.addLine('Set f_t = (((s_m/z_m)^p_m)*((z_s/s_s)^p_s))')
infil_program.addLine("Set Tdiff = #{@tin_sensor.name}-#{@tout_sensor.name}")
infil_program.addLine('Set dT = @Abs Tdiff')
infil_program.addLine("Set c = #{((UnitConversions.convert(c_i, 'cfm', 'm^3/s') / (UnitConversions.convert(1.0, 'inH2O', 'Pa')**n_i))).round(4)}")
infil_program.addLine("Set Cs = #{(stack_coef * (UnitConversions.convert(1.0, 'inH2O/R', 'Pa/K')**n_i)).round(4)}")
infil_program.addLine("Set Cw = #{(wind_coef * (UnitConversions.convert(1.0, 'inH2O/mph^2', 'Pa*s^2/m^2')**n_i)).round(4)}")
infil_program.addLine("Set n = #{n_i}")
- infil_program.addLine("Set sft = (f_t*#{(((@wind_speed.S_wo * (1.0 - y_i)) + (s_wflue * (1.5 * y_i))))})")
+ infil_program.addLine("Set sft = (f_t*#{(((site_ap.aim2_shelter_coeff * (1.0 - y_i)) + (s_wflue * (1.5 * y_i))))})")
infil_program.addLine("Set temp1 = ((c*Cw)*((sft*#{@vwind_sensor.name})^(2*n)))^2")
infil_program.addLine('Set Qinf = (((c*Cs*(dT^n))^2)+temp1)^0.5')
infil_program.addLine('Set Qinf = (@Max Qinf 0)')
elsif living_const_ach.to_f > 0
@@ -1868,24 +1808,21 @@
infil_program.addLine("Set Qinf = #{living_ach * UnitConversions.convert(@infil_volume, 'ft^3', 'm^3') / UnitConversions.convert(1.0, 'hr', 's')}")
else
infil_program.addLine('Set Qinf = 0')
end
-
- # Store info for HVAC Sizing measure
- @living_zone.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationCFM, living_cfm.to_f)
- @living_zone.additionalProperties.setFeature(Constants.SizingInfoZoneInfiltrationACH, living_ach.to_f)
end
- def self.calc_wind_stack_coeffs(hor_lk_frac, neutral_level, space, space_height = nil)
+ def self.calc_wind_stack_coeffs(site, hor_lk_frac, neutral_level, space, space_height = nil)
+ site_ap = site.additional_properties
if space_height.nil?
space_height = Geometry.get_height_of_spaces([space])
end
coord_z = Geometry.get_z_origin_for_zone(space.thermalZone.get)
- f_t_SG = @wind_speed.site_terrain_multiplier * ((space_height + coord_z) / 32.8)**@wind_speed.site_terrain_exponent / (@wind_speed.terrain_multiplier * (@wind_speed.height / 32.8)**@wind_speed.terrain_exponent)
+ f_t_SG = site_ap.site_terrain_multiplier * ((space_height + coord_z) / 32.8)**site_ap.site_terrain_exponent / (site_ap.terrain_multiplier * (site_ap.height / 32.8)**site_ap.terrain_exponent)
f_s_SG = 2.0 / 3.0 * (1 + hor_lk_frac / 2.0) * (2.0 * neutral_level * (1.0 - neutral_level))**0.5 / (neutral_level**0.5 + (1.0 - neutral_level)**0.5)
- f_w_SG = @wind_speed.shielding_coef * (1.0 - hor_lk_frac)**(1.0 / 3.0) * f_t_SG
+ f_w_SG = site_ap.s_g_shielding_coef * (1.0 - hor_lk_frac)**(1.0 / 3.0) * f_t_SG
c_s_SG = f_s_SG**2.0 * Constants.g * space_height / (Constants.AssumedInsideTemp + 460.0)
c_w_SG = f_w_SG**2.0
return c_w_SG, c_s_SG
end
@@ -1966,12 +1903,6 @@
@leakage_cfm25 = leakage_cfm25
@area = area
@rvalue = rvalue
end
attr_accessor(:side, :loc_space, :loc_schedule, :leakage_frac, :leakage_cfm25, :area, :rvalue, :zone, :location)
-end
-
-class WindSpeed
- def initialize
- end
- attr_accessor(:height, :terrain_multiplier, :terrain_exponent, :ashrae_terrain_thickness, :ashrae_terrain_exponent, :site_terrain_multiplier, :site_terrain_exponent, :ashrae_site_terrain_thickness, :ashrae_site_terrain_exponent, :S_wo, :shielding_coef)
end