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

SupplementalFanRunsWithAirHandlerFan input for CFIS systems #1865

Merged
merged 7 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ __New Features__
- Central Fan Integrated Supply (CFIS) mechanical ventilation enhancements:
- CFIS systems without automatic flow control of outdoor air (`CFISControls/HasOutdoorAirControl=false`).
- CFIS systems with no strategy to meet remainder of ventilation target (`CFISControls/AdditionalRuntimeOperatingMode="none"`).
- CFIS systems with supplemental fans that run simultaneously with the air handler (`CFISControls/extension/SupplementalFanRunsWithAirHandlerFan=true`).
- HVAC Manual J design load and sizing calculations:
- Adds optional `DistributionSystemType/AirDistribution/extension/ManualJInputs/BlowerFanHeatBtuh` input.
- Adds optional `DistributionSystemType/HydronicDistribution/extension/ManualJInputs/HotWaterPipingBtuh` input.
Expand Down
14 changes: 7 additions & 7 deletions HPXMLtoOpenStudio/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>e7d819f6-fda2-4bf2-b6c3-cd1ff3adb569</version_id>
<version_modified>2024-10-22T17:43:11Z</version_modified>
<version_id>6f307da4-b354-4c31-897f-de2161a9de82</version_id>
<version_modified>2024-10-22T21:49:52Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -189,7 +189,7 @@
<filename>airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>D2A99996</checksum>
<checksum>FFA79541</checksum>
</file>
<file>
<filename>battery.rb</filename>
Expand Down Expand Up @@ -327,7 +327,7 @@
<filename>defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>5B6D3CB2</checksum>
<checksum>E52E98C6</checksum>
</file>
<file>
<filename>energyplus.rb</filename>
Expand Down Expand Up @@ -357,7 +357,7 @@
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>E862E878</checksum>
<checksum>656CF61F</checksum>
</file>
<file>
<filename>hpxml_schema/HPXML.xsd</filename>
Expand All @@ -375,7 +375,7 @@
<filename>hpxml_schematron/EPvalidator.xml</filename>
<filetype>xml</filetype>
<usage_type>resource</usage_type>
<checksum>009BEB48</checksum>
<checksum>DD7BB2F9</checksum>
</file>
<file>
<filename>hpxml_schematron/iso-schematron.xsd</filename>
Expand Down Expand Up @@ -663,7 +663,7 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>9D489184</checksum>
<checksum>77AFEE2C</checksum>
</file>
<file>
<filename>test_enclosure.rb</filename>
Expand Down
74 changes: 47 additions & 27 deletions HPXMLtoOpenStudio/resources/airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ def self.create_return_air_duct_zone(model, loop_name, unit_multiplier, adiabati
# @param unavailable_periods [HPXML::UnavailablePeriods] Object that defines periods for, e.g., power outages or vacancies
# @return [TODO] TODO
def self.initialize_cfis(model, vent_fans, airloop_map, unavailable_periods)
cfis_data = { airloop: {}, t_sum_open_damper_var: {}, f_extra_open_damper_var: {} }
cfis_data = { airloop: {}, t_sum_open_damper_var: {}, f_vent_only_mode_var: {} }
return cfis_data if vent_fans[:mech].empty?

index = 0
Expand All @@ -745,19 +745,19 @@ def self.initialize_cfis(model, vent_fans, airloop_map, unavailable_periods)
cfis_data[:t_sum_open_damper_var][vent_mech.id] = Model.add_ems_global_var(
model,
var_name: "#{Constants::ObjectTypeMechanicalVentilation} cfis t sum open damper #{index}"
) # Sums the time during an hour the CFIS damper has been open
cfis_data[:f_extra_open_damper_var][vent_mech.id] = Model.add_ems_global_var(
)
cfis_data[:f_vent_only_mode_var][vent_mech.id] = Model.add_ems_global_var(
model,
var_name: "#{Constants::ObjectTypeMechanicalVentilation} cfis f open damper #{index}"
) # Fraction of timestep the CFIS blower is running while hvac is not operating. Used by infiltration and duct leakage programs
var_name: "#{Constants::ObjectTypeMechanicalVentilation} cfis f vent only mode #{index}"
)

# CFIS Initialization Program
cfis_program = Model.add_ems_program(
model,
name: "#{Constants::ObjectTypeMechanicalVentilation} cfis init program #{index}"
)
cfis_program.addLine("Set #{cfis_data[:t_sum_open_damper_var][vent_mech.id].name} = 0")
cfis_program.addLine("Set #{cfis_data[:f_extra_open_damper_var][vent_mech.id].name} = 0")
cfis_program.addLine("Set #{cfis_data[:f_vent_only_mode_var][vent_mech.id].name} = 0")

Model.add_ems_program_calling_manager(
model,
Expand Down Expand Up @@ -1585,12 +1585,12 @@ def self.apply_duct_location(model, spaces, hpxml_bldg, ducts, object, i, duct_l

add_cfis_duct_losses = (cfis_fan.cfis_addtl_runtime_operating_mode == HPXML::CFISModeAirHandler)
if add_cfis_duct_losses
# Calculate CFIS duct losses when the outdoor air damper is open
f_extra_open_damper_var = cfis_data[:f_extra_open_damper_var][cfis_id]
# Calculate additional CFIS duct losses when the air handler is in ventilation only mode
f_vent_only_mode_var = cfis_data[:f_vent_only_mode_var][cfis_id]

duct_program.addLine("If #{f_extra_open_damper_var.name} > 0")
duct_program.addLine("If #{f_vent_only_mode_var.name} > 0")
duct_program.addLine(" Set cfis_m3s = (#{fan_data[:mfr_max_var][object].name} * #{cfis_fan.cfis_vent_mode_airflow_fraction} / 1.16097654)") # Density of 1.16097654 was back calculated using E+ results
duct_program.addLine(" Set #{fan_data[:rtf_var][object].name} = #{f_extra_open_damper_var.name}") # Need to use global vars to sync duct_program and infiltration program of different calling points
duct_program.addLine(" Set #{fan_data[:rtf_var][object].name} = #{f_vent_only_mode_var.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_data[:rtf_var][object].name}*cfis_m3s")
duct_program.addLine(" Set rho_in = (@RhoAirFnPbTdbW #{sensors[:pbar].name} #{sensors[:t_in].name} #{sensors[:w_in].name})")
duct_program.addLine(" Set #{ah_mfr_var.name} = #{ah_vfr_var.name} * rho_in")
Expand Down Expand Up @@ -1978,44 +1978,46 @@ def self.apply_cfis(runner, infil_program, vent_mech_fans, cfis_data, cfis_fan_a
end

t_sum_open_damper_var = cfis_data[:t_sum_open_damper_var][vent_mech.id]
f_extra_open_damper_var = cfis_data[:f_extra_open_damper_var][vent_mech.id]
f_vent_only_mode_var = cfis_data[:f_vent_only_mode_var][vent_mech.id]

infil_program.addLine('If @ABS(Minute - ZoneTimeStep*60) < 0.1')
infil_program.addLine(" Set #{t_sum_open_damper_var.name} = 0") # New hour, time on summation re-initializes to 0
infil_program.addLine('EndIf')

infil_program.addLine("Set t_min_hr_open = #{[vent_mech.hours_in_operation / 24.0 * 60.0, 59.999].min}") # Minimum CFIS damper open time in minutes
infil_program.addLine("Set Q_duct_oa = #{UnitConversions.convert(vent_mech.oa_unit_flow_rate, 'cfm', 'm^3/s')}")
infil_program.addLine('Set f_total_open_damper = 0') # Fraction of the timestep the CFIS damper is open
infil_program.addLine("Set #{f_extra_open_damper_var.name} = 0")
infil_program.addLine('Set f_open_damper = 0')
infil_program.addLine("Set #{f_vent_only_mode_var.name} = 0")
infil_program.addLine("Set has_additional_runtime = #{vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeNone ? 0 : 1}")
infil_program.addLine("Set has_outdoor_air_control = #{vent_mech.cfis_has_outdoor_air_control ? 1 : 0}")
if vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeSupplementalFan
infil_program.addLine("Set suppl_fan_w = #{vent_mech.cfis_supplemental_fan.unit_fan_power}") # W
end

infil_program.addLine("If #{t_sum_open_damper_var.name} < t_min_hr_open") # Check whether we've met the minimum hourly runtime
infil_program.addLine(" Set t_damper_open = 60 - (t_min_hr_open - #{t_sum_open_damper_var.name})") # Minute of the hour at which the damper must be opened
infil_program.addLine(' If ((Minute+0.00001) >= t_damper_open) && (has_additional_runtime == 1)') # Check whether the damper must be opened to achieve target minutes per hour of operation
infil_program.addLine(' Set open_damper_runtime = @Max (@ABS(Minute - t_damper_open)) (fan_rtf_hvac * ZoneTimeStep * 60)') # How many minutes this hour the damper is open
infil_program.addLine(' Set open_damper_runtime = @Max (@ABS(Minute - t_damper_open)) (fan_rtf_hvac * ZoneTimeStep * 60)') # How many minutes this timestep the damper is open
infil_program.addLine(" Set open_damper_runtime = @Min open_damper_runtime (t_min_hr_open - #{t_sum_open_damper_var.name})") # Make sure it's not exceeding target ventilation
infil_program.addLine(' Set f_total_open_damper = open_damper_runtime / (60.0 * ZoneTimeStep)') # Fraction of the timestep that the damper is open
infil_program.addLine(" Set #{t_sum_open_damper_var.name} = #{t_sum_open_damper_var.name} + open_damper_runtime")
infil_program.addLine(" Set #{f_extra_open_damper_var.name} = @Max (f_total_open_damper - fan_rtf_hvac) 0.0") # Fraction of the timestep with additional ventilation mode runtime
infil_program.addLine(" Set #{t_sum_open_damper_var.name} = #{t_sum_open_damper_var.name} + open_damper_runtime") # How many minutes this hour the damper is open
infil_program.addLine(' Set f_open_damper = open_damper_runtime / (60.0 * ZoneTimeStep)') # Fraction of the timestep that the damper is open
infil_program.addLine(" Set #{f_vent_only_mode_var.name} = @Max (f_open_damper - fan_rtf_hvac) 0.0") # Fraction of the timestep with ventilation only mode runtime
if vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeAirHandler
# Air handler meets additional runtime requirement
infil_program.addLine(" Set fan_w = #{vent_mech.unit_fan_power}") # W
infil_program.addLine(" Set #{cfis_fan_actuator.name} = #{cfis_fan_actuator.name} + fan_w * #{f_extra_open_damper_var.name}")
infil_program.addLine(" Set #{cfis_fan_actuator.name} = #{cfis_fan_actuator.name} + fan_w * #{f_vent_only_mode_var.name}")
elsif vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeSupplementalFan
# Supplemental fan meets additional runtime requirement
if vent_mech.cfis_supplemental_fan.oa_unit_flow_rate < vent_mech.average_unit_flow_rate
runner.registerWarning("CFIS supplemental fan '#{vent_mech.cfis_supplemental_fan.id}' is undersized (#{vent_mech.cfis_supplemental_fan.oa_unit_flow_rate} cfm) compared to the target hourly ventilation rate (#{vent_mech.average_unit_flow_rate} cfm).")
end
infil_program.addLine(" Set suppl_Q_oa = #{UnitConversions.convert(vent_mech.cfis_supplemental_fan.oa_unit_flow_rate, 'cfm', 'm^3/s')}")
if vent_mech.cfis_supplemental_fan.oa_unit_flow_rate > 0
infil_program.addLine(" Set suppl_f = #{f_extra_open_damper_var.name} / (suppl_Q_oa / Q_duct_oa)") # Calculate desired runtime for supplemental fan to provide remaining ventilation requirement
infil_program.addLine(" Set suppl_f = #{f_vent_only_mode_var.name} / (suppl_Q_oa / Q_duct_oa)") # Calculate desired runtime for supplemental fan to provide remaining ventilation requirement
infil_program.addLine(' Set suppl_f = @Min suppl_f 1.0') # Ensure desired runtime does not exceed 100% (if the supplemental fan is undersized)
else
infil_program.addLine(' Set suppl_f = 0.0')
end
infil_program.addLine(" Set suppl_fan_w = #{vent_mech.cfis_supplemental_fan.unit_fan_power}") # W
infil_program.addLine(" Set #{cfis_suppl_fan_actuator.name} = #{cfis_suppl_fan_actuator.name} + suppl_fan_w * suppl_f")
if vent_mech.cfis_supplemental_fan.fan_type == HPXML::MechVentTypeSupply
infil_program.addLine(' Set QWHV_cfis_suppl_sup = QWHV_cfis_suppl_sup + (suppl_f * suppl_Q_oa)')
Expand All @@ -2024,21 +2026,39 @@ def self.apply_cfis(runner, infil_program, vent_mech_fans, cfis_data, cfis_fan_a
end
end
infil_program.addLine(' Else') # No additional ventilation mode runtime
infil_program.addLine(' Set open_damper_runtime = fan_rtf_hvac * ZoneTimeStep * 60') # How many minutes this hour the damper is open
infil_program.addLine(' Set open_damper_runtime = fan_rtf_hvac * ZoneTimeStep * 60') # How many minutes this timestep the damper is open
infil_program.addLine(" If (#{t_sum_open_damper_var.name} + open_damper_runtime) > t_min_hr_open") # Damper is only open for a portion of this time step to achieve target ventilation
infil_program.addLine(" Set open_damper_runtime = t_min_hr_open - #{t_sum_open_damper_var.name}")
infil_program.addLine(' EndIf')
infil_program.addLine(' Set f_total_open_damper = open_damper_runtime / (ZoneTimeStep * 60)') # Fraction of the timestep that the damper is open
infil_program.addLine(" Set #{t_sum_open_damper_var.name} = #{t_sum_open_damper_var.name} + open_damper_runtime")
infil_program.addLine(' Set f_open_damper = open_damper_runtime / (ZoneTimeStep * 60)') # Fraction of the timestep that the damper is open
infil_program.addLine(" Set #{t_sum_open_damper_var.name} = #{t_sum_open_damper_var.name} + open_damper_runtime") # How many minutes this hour the damper is open
infil_program.addLine(' EndIf')
infil_program.addLine('EndIf')

# Calculate fraction of the timestep that the damper is open and the air handler fan is running
if vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeSupplementalFan
infil_program.addLine(" Set f_total_open_damper = @Max (f_total_open_damper - #{f_extra_open_damper_var.name}) 0.0")
infil_program.addLine("Set f_open_damper_ah = @Max (f_open_damper - #{f_vent_only_mode_var.name}) 0.0")
else
infil_program.addLine('Set f_open_damper_ah = f_open_damper')
end
infil_program.addLine('EndIf')

# If no outdoor air control, then outdoor air is introduced for at least the entire time the HVAC system is running
infil_program.addLine('If has_outdoor_air_control == 0')
infil_program.addLine(' Set f_total_open_damper = @Max f_total_open_damper fan_rtf_hvac') # Outdoor air is introduced for at least the entire time the HVAC system is running
infil_program.addLine(' Set f_open_damper_ah = @Max f_open_damper_ah fan_rtf_hvac')
infil_program.addLine('EndIf')
infil_program.addLine('Set QWHV_cfis_sup = QWHV_cfis_sup + (f_total_open_damper * Q_duct_oa)')

# Airflow brought in through air handler
infil_program.addLine('Set QWHV_cfis_sup = QWHV_cfis_sup + (f_open_damper_ah * Q_duct_oa)')

next unless vent_mech.cfis_addtl_runtime_operating_mode == HPXML::CFISModeSupplementalFan && vent_mech.cfis_supplemental_fan_runs_with_air_handler_fan

# Also run supplemental fan when damper is open and HVAC system is running
infil_program.addLine("Set #{cfis_suppl_fan_actuator.name} = #{cfis_suppl_fan_actuator.name} + (suppl_fan_w * f_open_damper_ah)")
if vent_mech.cfis_supplemental_fan.fan_type == HPXML::MechVentTypeSupply
infil_program.addLine('Set QWHV_cfis_suppl_sup = QWHV_cfis_suppl_sup + (f_open_damper_ah * suppl_Q_oa)')
elsif vent_mech.cfis_supplemental_fan.fan_type == HPXML::MechVentTypeExhaust
infil_program.addLine('Set QWHV_cfis_suppl_exh = QWHV_cfis_suppl_exh + (f_open_damper_ah * suppl_Q_oa)')
end
end
end

Expand Down
4 changes: 4 additions & 0 deletions HPXMLtoOpenStudio/resources/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2755,6 +2755,10 @@ def self.apply_ventilation_fans(hpxml_bldg, weather, eri_version)
vent_fan.cfis_vent_mode_airflow_fraction = 1.0
vent_fan.cfis_vent_mode_airflow_fraction_isdefaulted = true
end
if vent_fan.cfis_supplemental_fan_runs_with_air_handler_fan.nil? && (vent_fan.cfis_addtl_runtime_operating_mode == HPXML::CFISModeSupplementalFan)
vent_fan.cfis_supplemental_fan_runs_with_air_handler_fan = false
vent_fan.cfis_supplemental_fan_runs_with_air_handler_fan_isdefaulted = true
end
end

# Default kitchen fan
Expand Down
Loading