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

Create and Modify HX:SensibleAndLatent methods #1835

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions lib/openstudio-standards.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ module OpenstudioStandards

# HVAC Module
require_relative 'openstudio-standards/hvac/cbecs_hvac'
require_relative 'openstudio-standards/hvac/components/create'
require_relative 'openstudio-standards/hvac/components/modify'

# CreateTypical Module
require_relative 'openstudio-standards/create_typical/enumerations'
Expand Down
168 changes: 168 additions & 0 deletions lib/openstudio-standards/hvac/components/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
module OpenstudioStandards
# The HVAC module provides methods create, modify, and get information about HVAC systems in the model
module HVAC
# @!group Component:Create
# Methods to create HVAC component objects

# creates HeatExchangerAirToAirSensibleAndLatent object with user-input effectiveness values
#
# @param model [<OpenStudio::Model::Model>] OpenStudio model
# @param name [String]
# @param type [String] Heat Exchanger Type. One of 'Plate', 'Rotary'
# @param economizer_lockout [Boolean] whether hx is locked out during economizing
# @param supply_air_outlet_temperature_control [Boolean]
# @param frost_control_type [String] HX frost control type. One of 'None', 'ExhaustAirRecirculation', 'ExhaustOnly', 'MinimumExhaustTemperature'
# @param nominal_electric_power [Float] Nominal electric power
# @param sensible_heating_100_eff [Float]
# @param sensible_heating_75_eff [Float]
# @param latent_heating_100_eff [Float]
# @param latent_heating_75_eff [Float]
# @param sensible_cooling_100_eff [Float]
# @param sensible_cooling_75_eff [Float]
# @param latent_cooling_100_eff [Float]
# @param latent_cooling_75_eff [Float]
# @return [<OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent>]
def self.create_hx_air_to_air_sensible_and_latent(model,
name: nil,
type: nil,
economizer_lockout: nil,
supply_air_outlet_temperature_control: nil,
frost_control_type: nil,
nominal_electric_power: nil,
sensible_heating_100_eff: nil,
sensible_heating_75_eff: nil,
latent_heating_100_eff: nil,
latent_heating_75_eff: nil,
sensible_cooling_100_eff: nil,
sensible_cooling_75_eff: nil,
latent_cooling_100_eff: nil,
latent_cooling_75_eff: nil)

hx = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model)
hx.setName(name) unless name.nil?

unless type.nil?
if OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.heatExchangerTypeValues.include?(type)
hx.setHeatExchangerType(type)
else
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Hvac', "Entered heat exchanger type #{type} not a valid type value. Enter one of #{OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.heatExchangerTypeValues}")
end
end

unless frost_control_type.nil?
if OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.frostControlTypeValues.include?(frost_control_type)
hx.setFrostControlType(frost_control_type)
else
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Hvac', "Entered heat exchanger frost control type #{frost_control_type} not a valid type value. Enter one of #{OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.frostControlTypeValues}")
end
end

hx.setEconomizerLockout(economizer_lockout) unless economizer_lockout.nil?
hx.setSupplyAirOutletTemperatureControl(supply_air_outlet_temperature_control) unless supply_air_outlet_temperature_control.nil?
hx.setNominalElectricPower(nominal_electric_power) unless nominal_electric_power.nil?

if model.version < OpenStudio::VersionString.new('3.8.0')
hx.setSensibleEffectivenessat100HeatingAirFlow(sensible_heating_100_eff) unless sensible_heating_100_eff.nil?
hx.setSensibleEffectivenessat75HeatingAirFlow(sensible_heating_75_eff) unless sensible_heating_75_eff.nil?
hx.setLatentEffectivenessat100HeatingAirFlow(latent_heating_100_eff) unless latent_heating_100_eff.nil?
hx.setLatentEffectivenessat75HeatingAirFlow(latent_heating_75_eff) unless latent_heating_75_eff.nil?
hx.setSensibleEffectivenessat100CoolingAirFlow(sensible_cooling_100_eff) unless sensible_cooling_100_eff.nil?
hx.setSensibleEffectivenessat75CoolingAirFlow(sensible_cooling_75_eff) unless sensible_cooling_75_eff.nil?
hx.setLatentEffectivenessat100CoolingAirFlow(latent_cooling_100_eff) unless latent_cooling_100_eff.nil?
hx.setLatentEffectivenessat75CoolingAirFlow(latent_cooling_75_eff) unless latent_cooling_75_eff.nil?
else
effectiveness_inputs = Hash.new { |hash, key| hash[key] = {} }
effectiveness_inputs['Sensible Heating'][0.75] = sensible_heating_75_eff.to_f unless sensible_heating_75_eff.nil?
effectiveness_inputs['Sensible Heating'][1.0] = sensible_heating_100_eff.to_f unless sensible_heating_100_eff.nil?
effectiveness_inputs['Latent Heating'][0.75] = latent_heating_75_eff.to_f unless latent_heating_75_eff.nil?
effectiveness_inputs['Latent Heating'][1.0] = latent_heating_100_eff.to_f unless latent_heating_100_eff.nil?
effectiveness_inputs['Sensible Cooling'][0.75] = sensible_cooling_75_eff.to_f unless sensible_cooling_75_eff.nil?
effectiveness_inputs['Sensible Cooling'][1.0] = sensible_cooling_100_eff.to_f unless sensible_cooling_100_eff.nil?
effectiveness_inputs['Latent Cooling'][0.75] = latent_cooling_75_eff.to_f unless latent_cooling_75_eff.nil?
effectiveness_inputs['Latent Cooling'][1.0] = latent_cooling_100_eff.to_f unless latent_cooling_100_eff.nil?

if effectiveness_inputs.values.all?(&:empty?)
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Hvac', 'Creating HX with historical effectiveness curves')
defaults = true
else
defaults = false
end

OpenstudioStandards::HVAC.heat_exchanger_air_to_air_set_effectiveness_values(hx, defaults: defaults, values: effectiveness_inputs)
end

return hx
end

# creates LookupTable objects to define effectiveness of HeatExchangerAirToAirSensibleAndLatent objects
#
# @param hx [<OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent>] OpenStudio HX object to update
# @param type [String] type of curve to create. one of 'Sensible Heating', 'Latent Heating, 'Sensible Cooling', 'Latent Cooling'
# @param values_hash [Hash{Float=>Float}] user_input flow decimal fraction => effectiveness decimal fraction pairs, e.g. {0.75 => 0.81, 1.0 => 0.76}
# @return [<OpenStudio::Model::TableLookup>] lookup table object
def self.create_hx_effectiveness_table(hx, type, values_hash)
# validate inputs
types = ['Sensible Heating', 'Latent Heating', 'Sensible Cooling', 'Latent Cooling']
unless types.include? type
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Hvac', "#{__method__} requires effectiveness type as one of #{types}")
return false
end

def self.validate_effectiveness_hash(values_hash)
values_hash.all? do |key, value|
key.is_a?(Float) && value.is_a?(Float) && key.between?(0.0, 1.0) && value.between?(0.0, 1.0)
end
end

if !validate_effectiveness_hash(values_hash)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Hvac', "#{__method__} require values_hash to have keys and values between 0.0 and 1.0: #{values_hash}")
return false
end

# look for existing curve
type_a = type.split
method_name = "#{type_a[0].downcase}Effectivenessof#{type_a[1]}AirFlowCurve"

# use existing independent variable object if found and matches inputs
iv = nil
curve = OpenStudio::Model::OptionalTableLookup.new
if hx.send(method_name).is_initialized
curve = hx.send(method_name).get.to_TableLookup.get
iv_existing = curve.independentVariables.first
if values_hash.keys.sort == iv_existing.values.sort
iv = iv_existing
end
end

# otherwise create new independent variable
if iv.nil?
iv = OpenStudio::Model::TableIndependentVariable.new(hx.model)
iv.setName("#{hx.name.get}_#{type.gsub(' ', '')}_IndependentVariable")
iv.setInterpolationMethod('Linear')
iv.setExtrapolationMethod('Linear')
iv.setMinimumValue(0.0)
iv.setMaximumValue(10.0)
iv.setUnitType('Dimensionless')
values_hash.keys.sort.each { |k| iv.addValue(k) }
end

# create new lookup table
t = OpenStudio::Model::TableLookup.new(hx.model)
t.setName("#{hx.name.get}_#{type.gsub(/ible|ent|ing|\s/, '')}Eff")
t.addIndependentVariable(iv)
t.setNormalizationMethod('DivisorOnly')
t.setMinimumOutput(0.0)
t.setMaximumOutput(10.0)
t.setOutputUnitType('Dimensionless')
values = values_hash.sort.map { |a| a[1] }
# protect against setting normalization divisor to zero for zero effectiveness
values[-1].zero? ? t.setNormalizationDivisor(1) : t.setNormalizationDivisor(values[-1])
values.each { |v| t.addOutputValue(v) }

# remove curve if found
curve.remove if curve.is_initialized

return t
end
end
end
42 changes: 42 additions & 0 deletions lib/openstudio-standards/hvac/components/modify.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module OpenstudioStandards
# The HVAC module provides methods create, modify, and get information about HVAC systems in the model
module HVAC
# @!group Component:Modify
# Methods to modify HVAC Component objects

# Applies historical default or user-input effectiveness values to a HeatExchanger:AirToAir:SensibleAndLatent object
#
# @param hx [<OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent>] OpenStudio HX object to update
# @param defaults [Boolean] flag to apply historical default curves
# @param values [Hash{String=>Hash{Float=>Float}}] user-input effectiveness values, where keys are one of
# 'Sensible Heating', 'Latent Heating, 'Sensible Cooling', 'Latent Cooling'
# and value is a hash of {flow decimal fraction => effectivess decimal fraction}, e.g. {0.75 => 0.81, 1.0 => 0.76}
# @return [<OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent>] modified OpenStudio HX object
def self.heat_exchanger_air_to_air_set_effectiveness_values(hx, defaults: false, values: nil)
if defaults
hx.assignHistoricalEffectivenessCurves
if values.nil?
return hx
end
elsif values.nil?
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Hvac', "#{__method__} called with defaults=false and no values provided. #{hx.name.get} will not be modified")
return hx
end

values.each do |type, values_hash|
# ensure values_hash has one value at 100% flow
unless values_hash.key?(1.0)
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Hvac', "Effectiveness Values for #{type} do not include 100% flow effectivenss. Cannot set effectiveness curves")
return false
end

lookup_table = OpenstudioStandards::HVAC.create_hx_effectiveness_table(hx, type, values_hash)
type_a = type.split
hx.send("set#{type_a[0]}Effectivenessat100#{type_a[1]}AirFlow", values_hash[1.0])
hx.send("set#{type_a[0]}Effectivenessof#{type_a[1]}AirFlowCurve", lookup_table)
end

return hx
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,27 @@ def heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_nominal_electr
# @param heat_exchanger_air_to_air_sensible_and_latent [OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent] hx
# @return [Boolean] returns true if successful, false if not
def heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency(heat_exchanger_air_to_air_sensible_and_latent)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(0.7)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(0.7)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(0.75)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(0.75)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(0.6)
if heat_exchanger_air_to_air_sensible_and_latent.model.version < OpenStudio::VersionString.new('3.8.0')
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(0.7)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(0.7)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(0.75)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(0.6)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(0.75)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(0.6)
else
values = Hash.new{|hash, key| hash[key] = Hash.new}
values['Sensible Heating'][0.75] = 0.7
values['Sensible Heating'][1.0] = 0.7
values['Latent Heating'][0.75] = 0.6
values['Latent Heating'][1.0] = 0.6
values['Sensible Cooling'][0.75] = 0.75
values['Sensible Cooling'][1.0] = 0.75
values['Latent Cooling'][0.75] = 0.6
values['Latent Cooling'][1.0] = 0.6
OpenstudioStandards.heat_exchanger_air_to_air_set_effectiveness_values(heat_exchanger_air_to_air_sensible_and_latent, defaults: false, values: values)
end

OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerAirToAirSensibleAndLatent', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Changed sensible and latent effectiveness to ~70% per DOE Prototype assumptions for an enthalpy wheel.")

Expand Down Expand Up @@ -116,24 +129,29 @@ def heat_exchanger_air_to_air_sensible_and_latent_apply_prototype_efficiency_ent
enthalpy_recovery_ratio = enthalpy_recovery_ratio_design_to_typical_adjustment(enthalpy_recovery_ratio, climate_zone)
full_htg_sens_eff, full_htg_lat_eff, part_htg_sens_eff, part_htg_lat_eff, full_cool_sens_eff, full_cool_lat_eff, part_cool_sens_eff, part_cool_lat_eff = heat_exchanger_air_to_air_sensible_and_latent_enthalpy_recovery_ratio_to_effectiveness(enthalpy_recovery_ratio, design_conditions)
end

heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(full_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(full_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(full_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(full_cool_lat_eff)
if heat_exchanger_air_to_air_sensible_and_latent.model.version < OpenStudio::VersionString.new('3.8.0')
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100HeatingAirFlow(full_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100HeatingAirFlow(full_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat100CoolingAirFlow(full_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat100CoolingAirFlow(full_cool_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(part_htg_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(part_htg_lat_eff)
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(part_cool_sens_eff)
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(part_cool_lat_eff)
else
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75HeatingAirFlow(part_htg_sens_eff) unless part_htg_sens_eff.zero?
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75HeatingAirFlow(part_htg_lat_eff) unless part_htg_lat_eff.zero?
heat_exchanger_air_to_air_sensible_and_latent.setSensibleEffectivenessat75CoolingAirFlow(part_cool_sens_eff) unless part_cool_sens_eff.zero?
heat_exchanger_air_to_air_sensible_and_latent.setLatentEffectivenessat75CoolingAirFlow(part_cool_lat_eff) unless part_cool_lat_eff.zero?
values = Hash.new{|hash, key| hash[key] = Hash.new}
values['Sensible Heating'][0.75] = part_htg_sens_eff
values['Sensible Heating'][1.0] = full_htg_sens_eff
values['Latent Heating'][0.75] = part_htg_lat_eff
values['Latent Heating'][1.0] = full_htg_lat_eff
values['Sensible Cooling'][0.75] = part_cool_sens_eff
values['Sensible Cooling'][1.0] = full_cool_sens_eff
values['Latent Cooling'][0.75] = part_cool_lat_eff
values['Latent Cooling'][1.0] = full_cool_lat_eff
OpenstudioStandards.heat_exchanger_air_to_air_set_effectiveness_values(heat_exchanger_air_to_air_sensible_and_latent, defaults: false, values: values)
end

OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerSensLat', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Set sensible and latent effectiveness calculated by using ERR.")
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.HeatExchangerSensLat', "For #{heat_exchanger_air_to_air_sensible_and_latent.name}: Set sensible and latent effectiveness calculated by using Enthalpy Recovery Ratio.")
return true
end
end
Loading