Skip to content

Commit

Permalink
Merge pull request #1865 from NREL/cfis_suppl_fan_sync
Browse files Browse the repository at this point in the history
`SupplementalFanRunsWithAirHandlerFan` input for CFIS systems
  • Loading branch information
shorowit authored Oct 23, 2024
2 parents 9112f44 + f604868 commit 6980e23
Show file tree
Hide file tree
Showing 16 changed files with 717 additions and 75 deletions.
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

0 comments on commit 6980e23

Please sign in to comment.