example_files/resources/hpxml-measures/HPXMLtoOpenStudio/resources/airflow.rb in urbanopt-cli-0.7.1 vs example_files/resources/hpxml-measures/HPXMLtoOpenStudio/resources/airflow.rb in urbanopt-cli-0.8.0

- old
+ new

@@ -9,18 +9,20 @@ @runner = runner @spaces = spaces @year = hpxml.header.sim_calendar_year @infil_volume = hpxml.air_infiltration_measurements.select { |i| !i.infiltration_volume.nil? }[0].infiltration_volume - @infil_height = hpxml.inferred_infiltration_height(@infil_volume) + @infil_height = hpxml.air_infiltration_measurements.select { |i| !i.infiltration_height.nil? }[0].infiltration_height @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 = cfa + @cooking_range_in_cond_space = hpxml.cooking_ranges.empty? ? true : HPXML::conditioned_locations_this_unit.include?(hpxml.cooking_ranges[0].location) + @clothes_dryer_in_cond_space = hpxml.clothes_dryers.empty? ? true : HPXML::conditioned_locations_this_unit.include?(hpxml.clothes_dryers[0].location) # Global sensors @pbar_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Outdoor Air Barometric Pressure') @pbar_sensor.setName('out pb s') @@ -56,21 +58,21 @@ 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 - vent_fans_kitchen << vent_fan - elsif vent_fan.used_for_local_ventilation && vent_fan.fan_location == HPXML::LocationBath - vent_fans_bath << vent_fan - else - @runner.registerWarning("Unexpected ventilation fan '#{vent_fan.id}'. The fan will not be modeled.") + elsif vent_fan.used_for_local_ventilation + if vent_fan.fan_location == HPXML::LocationKitchen + vent_fans_kitchen << vent_fan + elsif vent_fan.fan_location == HPXML::LocationBath + vent_fans_bath << vent_fan + end end end - # Vented clothes dryers in conditioned space - vented_dryers = hpxml.clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 && HPXML::conditioned_locations_this_unit.include?(cd.location) } + # Vented clothes dryers + vented_dryers = hpxml.clothes_dryers.select { |cd| cd.is_vented && cd.vented_flow_rate.to_f > 0 } # Initialization initialize_cfis(model, vent_fans_mech, airloop_map) model.getAirLoopHVACs.each do |air_loop| initialize_fan_objects(model, air_loop) @@ -80,11 +82,11 @@ end # Apply ducts duct_systems.each do |ducts, object| - apply_ducts(model, ducts, object) + apply_ducts(model, ducts, object, vent_fans_mech) end # Apply infiltration/ventilation set_wind_speed_correction(model, hpxml.site) @@ -94,20 +96,22 @@ vented_attic = nil hpxml.attics.each do |attic| next unless attic.attic_type == HPXML::AtticTypeVented vented_attic = attic + break end vented_crawl = nil hpxml.foundations.each do |foundation| next unless foundation.foundation_type == HPXML::FoundationTypeCrawlspaceVented vented_crawl = foundation + break end apply_natural_ventilation_and_whole_house_fan(model, weather, hpxml.site, vent_fans_whf, open_window_area, clg_ssn_sensor) - apply_infiltration_and_ventilation_fans(model, weather, hpxml.site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers, + apply_infiltration_and_ventilation_fans(model, @runner, 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, clg_ssn_sensor, schedules_file) end def self.get_default_fraction_of_windows_operable() @@ -149,11 +153,11 @@ end def self.get_default_mech_vent_flow_rate(hpxml, vent_fan, infil_measurements, weather, infil_a_ext, cfa, nbeds) # Calculates Qfan cfm requirement per ASHRAE 62.2-2019 infil_volume = infil_measurements[0].infiltration_volume - infil_height = hpxml.inferred_infiltration_height(infil_volume) + infil_height = infil_measurements[0].infiltration_height infil_a_ext = 1.0 if [HPXML::ResidentialTypeSFA, HPXML::ResidentialTypeApartment].include? hpxml.building_construction.residential_facility_type tot_cb_area, ext_cb_area = hpxml.compartmentalization_boundary_areas() infil_a_ext = ext_cb_area / tot_cb_area @@ -416,11 +420,11 @@ vent_program.addLine(" Set #{whf_elec_actuator.name} = 0") vent_program.addLine('EndIf') manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model) manager.setName("#{vent_program.name} calling manager") - manager.setCallingPoint('BeginTimestepBeforePredictor') + manager.setCallingPoint('BeginZoneTimestepAfterInitHeatBalance') manager.addProgram(vent_program) end def self.create_nv_and_whf_avail_sch(model, obj_name, num_days_per_week) avail_sch = OpenStudio::Model::ScheduleRuleset.new(model) @@ -593,11 +597,11 @@ else fail "Unexpected fan: #{supply_fan.name}" end end - def self.apply_ducts(model, ducts, object) + def self.apply_ducts(model, ducts, object, vent_fans_mech) 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? # Pass MF space temperature schedule name duct.location = duct.loc_schedule.name.to_s @@ -1058,14 +1062,15 @@ duct_program.addLine("Set #{duct_actuators['liv_to_dz_flow_rate'].name} = #{duct_vars['liv_to_dz_flow_rate'].name}") end if @cfis_airloop.values.include? object - # Calculate CFIS duct losses + # Calculate additional CFIS duct losses during fan-only mode cfis_id = @cfis_airloop.key(object) + vent_mech = vent_fans_mech.select { |vfm| vfm.id == cfis_id }[0] duct_program.addLine("If #{@cfis_f_damper_extra_open_var[cfis_id].name} > 0") - duct_program.addLine(" Set cfis_m3s = (#{@fan_mfr_max_var[object].name} / 1.16097654)") # Density of 1.16097654 was back calculated using E+ results + duct_program.addLine(" Set cfis_m3s = (#{@fan_mfr_max_var[object].name} * #{vent_mech.cfis_vent_mode_airflow_fraction} / 1.16097654)") # Density of 1.16097654 was back calculated using E+ results duct_program.addLine(" Set #{@fan_rtf_var[object].name} = #{@cfis_f_damper_extra_open_var[cfis_id].name}") # Need to use global vars to sync duct_program and infiltration program of different calling points duct_program.addLine(" Set #{ah_vfr_var.name} = #{@fan_rtf_var[object].name}*cfis_m3s") duct_program.addLine(" Set rho_in = (@RhoAirFnPbTdbW #{@pbar_sensor.name} #{@tin_sensor.name} #{@win_sensor.name})") duct_program.addLine(" Set #{ah_mfr_var.name} = #{ah_vfr_var.name} * rho_in") duct_program.addLine(" Set #{ah_tout_var.name} = #{ra_t_sensor.name}") @@ -1209,75 +1214,69 @@ ach = get_default_unvented_space_ach() cfm = ach / UnitConversions.convert(1.0, 'hr', 'min') * volume apply_infiltration_to_unconditioned_space(model, space, ach, nil, nil, nil) 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| - daily_sch = [0.0] * 24 - obj_name = "#{obj_type_name} #{index}" - remaining_hrs = vent_object.hours_in_operation - for hr in 1..(vent_object.hours_in_operation.ceil) - if remaining_hrs >= 1 - daily_sch[(vent_object.start_hour + hr - 1) % 24] = 1.0 - else - daily_sch[(vent_object.start_hour + hr - 1) % 24] = remaining_hrs - end - remaining_hrs -= 1 + def self.apply_local_ventilation(model, vent_object, obj_type_name, index) + daily_sch = [0.0] * 24 + obj_name = "#{obj_type_name} #{index}" + remaining_hrs = vent_object.hours_in_operation + for hr in 1..(vent_object.hours_in_operation.ceil) + if remaining_hrs >= 1 + daily_sch[(vent_object.start_hour + hr - 1) % 24] = 1.0 + else + daily_sch[(vent_object.start_hour + hr - 1) % 24] = remaining_hrs end - obj_sch = HourlyByMonthSchedule.new(model, "#{obj_name} schedule", [daily_sch] * 12, [daily_sch] * 12, Constants.ScheduleTypeLimitsFraction, false) - obj_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') - obj_sch_sensor.setName("#{obj_name} sch s") - obj_sch_sensor.setKeyName(obj_sch.schedule.name.to_s) - obj_sch_sensors[vent_object.id] = obj_sch_sensor - - equip_def = OpenStudio::Model::ElectricEquipmentDefinition.new(model) - equip_def.setName(obj_name) - equip = OpenStudio::Model::ElectricEquipment.new(equip_def) - equip.setName(obj_name) - equip.setSpace(@living_space) - equip_def.setDesignLevel(vent_object.fan_power * vent_object.quantity) - equip_def.setFractionRadiant(0) - equip_def.setFractionLatent(0) - equip_def.setFractionLost(1) - equip.setSchedule(obj_sch.schedule) - equip.setEndUseSubcategory(Constants.ObjectNameMechanicalVentilation) + remaining_hrs -= 1 end + obj_sch = HourlyByMonthSchedule.new(model, "#{obj_name} schedule", [daily_sch] * 12, [daily_sch] * 12, Constants.ScheduleTypeLimitsFraction, false) + obj_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') + obj_sch_sensor.setName("#{obj_name} sch s") + obj_sch_sensor.setKeyName(obj_sch.schedule.name.to_s) - return obj_sch_sensors + equip_def = OpenStudio::Model::ElectricEquipmentDefinition.new(model) + equip_def.setName(obj_name) + equip = OpenStudio::Model::ElectricEquipment.new(equip_def) + equip.setName(obj_name) + equip.setSpace(@living_space) + equip_def.setDesignLevel(vent_object.fan_power * vent_object.quantity) + equip_def.setFractionRadiant(0) + equip_def.setFractionLatent(0) + equip_def.setFractionLost(1) + equip.setSchedule(obj_sch.schedule) + equip.setEndUseSubcategory(Constants.ObjectNameMechanicalVentilation) + + return obj_sch_sensor end - def self.apply_dryer_exhaust(model, vented_dryers, schedules_file) - obj_sch_sensors = {} - obj_type_name = Constants.ObjectNameClothesDryerExhaust - vented_dryers.each_with_index do |vented_dryer, index| - obj_name = "#{obj_type_name} #{index}" + def self.apply_dryer_exhaust(model, runner, vented_dryer, schedules_file, index) + obj_name = "#{Constants.ObjectNameClothesDryerExhaust} #{index}" - if not schedules_file.nil? - obj_sch = schedules_file.create_schedule_file(col_name: 'clothes_dryer') - obj_sch_name = 'clothes_dryer' - full_load_hrs = schedules_file.annual_equivalent_full_load_hrs(col_name: 'clothes_dryer') - else - cd_weekday_sch = vented_dryer.weekday_fractions - cd_weekend_sch = vented_dryer.weekend_fractions - cd_monthly_sch = vented_dryer.monthly_multipliers - obj_sch = MonthWeekdayWeekendSchedule.new(model, Constants.ObjectNameClothesDryer, cd_weekday_sch, cd_weekend_sch, cd_monthly_sch, Constants.ScheduleTypeLimitsFraction) - obj_sch = obj_sch.schedule - obj_sch_name = obj_sch.name.to_s - full_load_hrs = Schedule.annual_equivalent_full_load_hrs(@year, obj_sch) - end - # Assume standard dryer exhaust runs 1 hr/day per BA HSP - cfm_mult = Constants.NumDaysInYear(@year) * vented_dryer.usage_multiplier / full_load_hrs - - obj_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') - obj_sch_sensor.setName("#{obj_name} sch s") - obj_sch_sensor.setKeyName(obj_sch_name) - obj_sch_sensors[vented_dryer.id] = [obj_sch_sensor, cfm_mult] + # Create schedule + obj_sch = nil + if not schedules_file.nil? + obj_sch_name = SchedulesFile::ColumnClothesDryer + obj_sch = schedules_file.create_schedule_file(col_name: obj_sch_name) + full_load_hrs = schedules_file.annual_equivalent_full_load_hrs(col_name: obj_sch_name) end + if obj_sch.nil? + cd_weekday_sch = vented_dryer.weekday_fractions + cd_weekend_sch = vented_dryer.weekend_fractions + cd_monthly_sch = vented_dryer.monthly_multipliers + obj_sch = MonthWeekdayWeekendSchedule.new(model, Constants.ObjectNameClothesDryer, cd_weekday_sch, cd_weekend_sch, cd_monthly_sch, Constants.ScheduleTypeLimitsFraction) + obj_sch = obj_sch.schedule + obj_sch_name = obj_sch.name.to_s + full_load_hrs = Schedule.annual_equivalent_full_load_hrs(@year, obj_sch) + end + # Assume standard dryer exhaust runs 1 hr/day per BA HSP + cfm_mult = Constants.NumDaysInYear(@year) * vented_dryer.usage_multiplier / full_load_hrs - return obj_sch_sensors + obj_sch_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value') + obj_sch_sensor.setName("#{obj_name} sch s") + obj_sch_sensor.setKeyName(obj_sch_name) + + return obj_sch_sensor, cfm_mult end def self.calc_hrv_erv_effectiveness(vent_mech_fans) # Create the mapping between mech vent instance and the effectiveness results hrv_erv_effectiveness_map = {} @@ -1468,30 +1467,42 @@ program.addLine('Set ZoneAirEnth = (@HFnTdbW ZoneTemp ZoneW)') return fan_sens_load_actuator, fan_lat_load_actuator end - def self.apply_infiltration_adjustment(infil_program, vent_fans_kitchen, vent_fans_bath, vented_dryers, sup_cfm_tot, exh_cfm_tot, bal_cfm_tot, erv_hrv_cfm_tot, - infil_flow_actuator, range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map) + def self.apply_infiltration_adjustment_to_conditioned(model, runner, infil_program, vent_fans_kitchen, vent_fans_bath, vented_dryers, + sup_cfm_tot, exh_cfm_tot, bal_cfm_tot, erv_hrv_cfm_tot, infil_flow_actuator, schedules_file) infil_program.addLine('Set Qrange = 0') - vent_fans_kitchen.each do |vent_kitchen| - infil_program.addLine("Set Qrange = Qrange + #{UnitConversions.convert(vent_kitchen.flow_rate * vent_kitchen.quantity, 'cfm', 'm^3/s').round(4)} * #{range_sch_sensors_map[vent_kitchen.id].name}") + vent_fans_kitchen.each_with_index do |vent_kitchen, index| + # Electricity impact + obj_sch_sensor = apply_local_ventilation(model, vent_kitchen, Constants.ObjectNameMechanicalVentilationRangeFan, index) + next unless @cooking_range_in_cond_space + + # Infiltration impact + infil_program.addLine("Set Qrange = Qrange + #{UnitConversions.convert(vent_kitchen.flow_rate * vent_kitchen.quantity, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}") end infil_program.addLine('Set Qbath = 0') - vent_fans_bath.each do |vent_bath| - infil_program.addLine("Set Qbath = Qbath + #{UnitConversions.convert(vent_bath.flow_rate * vent_bath.quantity, 'cfm', 'm^3/s').round(4)} * #{bath_sch_sensors_map[vent_bath.id].name}") + vent_fans_bath.each_with_index do |vent_bath, index| + # Electricity impact + obj_sch_sensor = apply_local_ventilation(model, vent_bath, Constants.ObjectNameMechanicalVentilationBathFan, index) + # Infiltration impact + infil_program.addLine("Set Qbath = Qbath + #{UnitConversions.convert(vent_bath.flow_rate * vent_bath.quantity, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}") end infil_program.addLine('Set Qdryer = 0') - vented_dryers.each do |vented_dryer| - infil_program.addLine("Set Qdryer = Qdryer + #{UnitConversions.convert(vented_dryer.vented_flow_rate * dryer_exhaust_sch_sensors_map[vented_dryer.id][1], 'cfm', 'm^3/s').round(5)} * #{dryer_exhaust_sch_sensors_map[vented_dryer.id][0].name}") + vented_dryers.each_with_index do |vented_dryer, index| + next unless @clothes_dryer_in_cond_space + + # Infiltration impact + obj_sch_sensor, cfm_mult = apply_dryer_exhaust(model, runner, vented_dryer, schedules_file, index) + infil_program.addLine("Set Qdryer = Qdryer + #{UnitConversions.convert(vented_dryer.vented_flow_rate * cfm_mult, 'cfm', 'm^3/s').round(5)} * #{obj_sch_sensor.name}") end - infil_program.addLine("Set QWHV_sup = #{UnitConversions.convert(sup_cfm_tot, 'cfm', 'm^3/s').round(4)}") - infil_program.addLine("Set QWHV_exh = #{UnitConversions.convert(exh_cfm_tot, 'cfm', 'm^3/s').round(4)}") - infil_program.addLine("Set QWHV_bal_erv_hrv = #{UnitConversions.convert(bal_cfm_tot + erv_hrv_cfm_tot, 'cfm', 'm^3/s').round(4)}") + infil_program.addLine("Set QWHV_sup = #{UnitConversions.convert(sup_cfm_tot, 'cfm', 'm^3/s').round(5)}") + infil_program.addLine("Set QWHV_exh = #{UnitConversions.convert(exh_cfm_tot, 'cfm', 'm^3/s').round(5)}") + infil_program.addLine("Set QWHV_bal_erv_hrv = #{UnitConversions.convert(bal_cfm_tot + erv_hrv_cfm_tot, 'cfm', 'm^3/s').round(5)}") infil_program.addLine('Set Qexhaust = Qrange + Qbath + Qdryer + QWHV_exh + QWHV_bal_erv_hrv') infil_program.addLine('Set Qsupply = QWHV_sup + QWHV_bal_erv_hrv + QWHV_cfis_oa') infil_program.addLine('Set Qfan = (@Max Qexhaust Qsupply)') if Constants.ERIVersions.index(@eri_version) >= Constants.ERIVersions.index('2019') @@ -1529,14 +1540,16 @@ infil_program.addLine('Set ZoneInTemp = OASupInTemp') if not vent_mech_erv_hrv_tot.empty? # ERV/HRV EMS load model # E+ ERV model is using standard density for MFR calculation, caused discrepancy with other system types. # Therefore ERV is modeled within EMS infiltration program + infil_program.addLine("If #{q_var} > 0") vent_mech_erv_hrv_tot.each do |vent_fan| - infil_program.addLine("Set Effectiveness_Sens = Effectiveness_Sens + #{UnitConversions.convert(vent_fan.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)} / #{q_var} * #{hrv_erv_effectiveness_map[vent_fan][:vent_mech_sens_eff]}") - infil_program.addLine("Set Effectiveness_Lat = Effectiveness_Lat + #{UnitConversions.convert(vent_fan.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)} / #{q_var} * #{hrv_erv_effectiveness_map[vent_fan][:vent_mech_lat_eff]}") + infil_program.addLine(" Set Effectiveness_Sens = Effectiveness_Sens + #{UnitConversions.convert(vent_fan.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)} / #{q_var} * #{hrv_erv_effectiveness_map[vent_fan][:vent_mech_sens_eff]}") + infil_program.addLine(" Set Effectiveness_Lat = Effectiveness_Lat + #{UnitConversions.convert(vent_fan.average_oa_unit_flow_rate, 'cfm', 'm^3/s').round(4)} / #{q_var} * #{hrv_erv_effectiveness_map[vent_fan][:vent_mech_lat_eff]}") end + infil_program.addLine('EndIf') infil_program.addLine('Set ERVCpMin = (@Min OASupCp ZoneCp)') infil_program.addLine('Set ERVSupOutTemp = OASupInTemp + ERVCpMin/OASupCp * Effectiveness_Sens * (ZoneTemp - OASupInTemp)') infil_program.addLine('Set ERVSupOutW = OASupInW + ERVCpMin/OASupCp * Effectiveness_Lat * (ZoneW - OASupInW)') infil_program.addLine('Set ERVSupOutEnth = (@HFnTdbW ERVSupOutTemp ERVSupOutW)') infil_program.addLine('Set ERVSensHeatTrans = Fan_MFR * OASupCp * (ERVSupOutTemp - OASupInTemp)') @@ -1587,11 +1600,11 @@ infil_program.addLine(' If ZoneInTemp < HtgStp') infil_program.addLine(' Set FanSensToSpt = Fan_MFR * ZoneCp * (ZoneInTemp - HtgStp)') infil_program.addLine(" Set PreHeatingWatt = (-FanSensToSpt) * #{f_preheat.preheating_fraction_load_served}") infil_program.addLine(" Set #{fan_sens_load_actuator.name} = #{fan_sens_load_actuator.name} + PreHeatingWatt") - infil_program.addLine(" Set #{fan_lat_load_actuator.name} = #{fan_lat_load_actuator.name} - FanLatToLv") # Fixme:Does this assumption still apply? + infil_program.addLine(" Set #{fan_lat_load_actuator.name} = #{fan_lat_load_actuator.name} - FanLatToLv") infil_program.addLine(' Else') infil_program.addLine(' Set PreHeatingWatt = 0.0') infil_program.addLine(' EndIf') infil_program.addLine('Else') infil_program.addLine(' Set PreHeatingWatt = 0.0') @@ -1623,12 +1636,12 @@ infil_program.addLine('EndIf') infil_program.addLine("Set #{clg_energy_actuator.name} = PreCoolingWatt / #{f_precool.precooling_efficiency_cop}") end end - 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, clg_ssn_sensor) + def self.apply_infiltration_ventilation_to_conditioned(model, runner, site, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers, + has_flue_chimney, clg_ssn_sensor, schedules_file) # 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 } @@ -1675,23 +1688,23 @@ # 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(site, living_ach50, living_const_ach, infil_program, weather, has_flue_chimney) + apply_infiltration_to_conditioned(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 infil_program.addLine("Set #{cfis_fan_actuator.name} = 0.0") apply_cfis(infil_program, vent_mech_cfis_tot, cfis_fan_actuator) # Calculate Qfan, Qinf_adj # Calculate adjusted infiltration based on mechanical ventilation system - apply_infiltration_adjustment(infil_program, vent_fans_kitchen, vent_fans_bath, vented_dryers, sup_cfm_tot, exh_cfm_tot, bal_cfm_tot, erv_hrv_cfm_tot, - infil_flow_actuator, range_sch_sensors_map, bath_sch_sensors_map, dryer_exhaust_sch_sensors_map) + apply_infiltration_adjustment_to_conditioned(model, runner, infil_program, vent_fans_kitchen, vent_fans_bath, vented_dryers, + sup_cfm_tot, exh_cfm_tot, bal_cfm_tot, erv_hrv_cfm_tot, infil_flow_actuator, schedules_file) # Address load of Qfan (Qload) # Qload as variable for tracking outdoor air flow rate, excluding recirculation infil_program.addLine('Set Qload = Qfan') vent_fans_mech.each do |f| @@ -1703,15 +1716,15 @@ # Address preconditioning calculate_precond_loads(model, infil_program, vent_mech_preheat, vent_mech_precool, hrv_erv_effectiveness_map, fan_sens_load_actuator, fan_lat_load_actuator, clg_ssn_sensor) program_calling_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model) program_calling_manager.setName("#{infil_program.name} calling manager") - program_calling_manager.setCallingPoint('BeginTimestepBeforePredictor') + program_calling_manager.setCallingPoint('BeginZoneTimestepAfterInitHeatBalance') program_calling_manager.addProgram(infil_program) end - def self.apply_infiltration_and_ventilation_fans(model, weather, site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers, + def self.apply_infiltration_and_ventilation_fans(model, runner, weather, site, vent_fans_mech, vent_fans_kitchen, vent_fans_bath, vented_dryers, has_flue_chimney, air_infils, vented_attic, vented_crawl, clg_ssn_sensor, schedules_file) # Get living space infiltration living_ach50 = nil living_const_ach = nil air_infils.each do |air_infil| @@ -1737,22 +1750,15 @@ apply_infiltration_to_vented_crawlspace(model, weather, vented_crawl) apply_infiltration_to_unvented_crawlspace(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_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, clg_ssn_sensor) + # Infiltration/ventilation for conditioned space + apply_infiltration_ventilation_to_conditioned(model, runner, site, vent_fans_mech, living_ach50, living_const_ach, weather, vent_fans_kitchen, vent_fans_bath, vented_dryers, + has_flue_chimney, clg_ssn_sensor, schedules_file) end - def self.apply_infiltration_to_living(site, living_ach50, living_const_ach, infil_program, weather, has_flue_chimney) + def self.apply_infiltration_to_conditioned(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)