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