lib/measures/add_hpwh/measure.rb in openstudio-load-flexibility-measures-0.3.1 vs lib/measures/add_hpwh/measure.rb in openstudio-load-flexibility-measures-0.3.2

- old
+ new

@@ -85,100 +85,82 @@ def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # create argument for removal of existing water heater tanks on selected loop remove_wh = OpenStudio::Measure::OSArgument.makeBoolArgument('remove_wh', true) - remove_wh.setDisplayName('Remove existing water heater on selected loop') + remove_wh.setDisplayName('Remove existing water heater?') remove_wh.setDescription('') remove_wh.setDefaultValue(true) args << remove_wh - # find available plant loops (heating) - loop_names = [] + # find available water heaters and get default volume + if !model.getWaterHeaterMixeds.empty? + wheaters = model.getWaterHeaterMixeds + end - unless model.getPlantLoops.empty? - loops = model.getPlantLoops - loops.each do |lp| - unless lp.sizingPlant.loopType.empty? - next unless lp.sizingPlant.loopType.to_s == 'Heating' - loop_names << lp.name.to_s - end + default_vol = 80.0 # gallons + wh_names = ['All Water Heaters (Simplified Only)'] + wheaters.each do |w| + if w.tankVolume.to_f > OpenStudio.convert(39, 'gal', 'm^3').to_f + wh_names << w.name.to_s + default_vol = [default_vol, (w.tankVolume.to_f / 0.0037854118).round(1)].max end end - loop_names << 'Error: No Service Water Loop Found' if loop_names.empty? + wh = OpenStudio::Measure::OSArgument.makeChoiceArgument('wh', wh_names, true) + wh.setDisplayName('Select 40+ gallon water heater to replace or augment') + wh.setDescription("All can only be used with the 'Simplified' model") + wh.setDefaultValue(wh_names[0]) + args << wh - # create argument for loop selection - loop = OpenStudio::Measure::OSArgument.makeChoiceArgument('loop', loop_names.sort, true) - loop.setDisplayName('Select hot water loop') - loop.setDescription('The water tank will be placed on the supply side of this loop.') - loop.setDefaultValue(loop_names.sort[0]) - args << loop + # create argument for hot water tank volume + vol = OpenStudio::Measure::OSArgument.makeDoubleArgument('vol', false) + vol.setDisplayName('Set hot water tank volume [gal]') + vol.setDescription('Enter 0 to use existing tank volume(s). Values less than 5 are treated as sizing multipliers.') + vol.setUnits('gal') + vol.setDefaultValue(0) + args << vol + # create argument for water heater type + type = OpenStudio::Measure::OSArgument.makeChoiceArgument('type', + ['Simplified', 'PumpedCondenser', 'WrappedCondenser'], true) + type.setDisplayName('Select heat pump water heater type') + type.setDescription('') + type.setDefaultValue('Simplified') + args << type + # find available spaces for heater location zone_names = [] - unless model.getThermalZones.empty? zones = model.getThermalZones zones.each do |zn| zone_names << zn.name.to_s end zone_names.sort! end zone_names << 'Error: No Thermal Zones Found' if zone_names.empty? + zone_names = ['N/A - Simplified'] + zone_names # create argument for thermal zone selection (location of water heater) zone = OpenStudio::Measure::OSArgument.makeChoiceArgument('zone', zone_names, true) - zone.setDisplayName('Select thermal zone') - zone.setDescription('This is where the water heater tank will be placed') + zone.setDisplayName('Select thermal zone for HP evaporator') + zone.setDescription("Does not apply to 'Simplified' cases") zone.setDefaultValue(zone_names[0]) args << zone - # create argument for water heater type - type = OpenStudio::Measure::OSArgument.makeChoiceArgument('type', - ['PumpedCondenser', 'WrappedCondenser', 'Simplified'], true) - type.setDisplayName('Select heat pump water heater type') - type.setDescription('') - type.setDefaultValue('PumpedCondenser') - args << type - - # find largest current water heater volume - if any mixed tanks are already present. Default is 80 gal. - default_vol = 80.0 # gal - - wheaters = if !model.getWaterHeaterMixeds.empty? - model.getWaterHeaterMixeds - else - [] - end - - unless wheaters.empty? - wheaters.each do |wh| - unless wh.tankVolume.empty? - default_vol = [default_vol, (wh.tankVolume.to_f / 0.0037854118).round(1)].max # convert m^3 to gal - end - end - end - - # create argument for hot water tank volume - vol = OpenStudio::Measure::OSArgument.makeDoubleArgument('vol', true) - vol.setDisplayName('Set hot water tank volume') - vol.setDescription('[gal]') - vol.setDefaultValue(default_vol) - args << vol - # create argument for heat pump capacity cap = OpenStudio::Measure::OSArgument.makeDoubleArgument('cap', true) cap.setDisplayName('Set heat pump heating capacity') cap.setDescription('[kW]') cap.setDefaultValue((23.446 * (default_vol / 80.0)).round(1)) args << cap # create argument for heat pump rated cop cop = OpenStudio::Measure::OSArgument.makeDoubleArgument('cop', true) cop.setDisplayName('Set heat pump rated COP (heating)') - cop.setDefaultValue(2.8) + cop.setDefaultValue(3.2) args << cop # create argument for electric backup capacity bu_cap = OpenStudio::Measure::OSArgument.makeDoubleArgument('bu_cap', true) bu_cap.setDisplayName('Set electric backup heating capacity') @@ -287,64 +269,57 @@ minutes = [] flex_times = [] # assign the user inputs to variables remove_wh = runner.getBoolArgumentValue('remove_wh', user_arguments) - loop = runner.getStringArgumentValue('loop', user_arguments) - zone = runner.getStringArgumentValue('zone', user_arguments) + wh = runner.getStringArgumentValue('wh', user_arguments) + vol = runner.getDoubleArgumentValue('vol', user_arguments) type = runner.getStringArgumentValue('type', user_arguments) + zone = runner.getStringArgumentValue('zone', user_arguments) cap = runner.getDoubleArgumentValue('cap', user_arguments) cop = runner.getDoubleArgumentValue('cop', user_arguments) bu_cap = runner.getDoubleArgumentValue('bu_cap', user_arguments) - vol = runner.getDoubleArgumentValue('vol', user_arguments) max_temp = runner.getDoubleArgumentValue('max_temp', user_arguments) min_temp = runner.getDoubleArgumentValue('min_temp', user_arguments) db_temp = runner.getDoubleArgumentValue('db_temp', user_arguments) sched = runner.getStringArgumentValue('sched', user_arguments) 4.times do |n| flex << runner.getStringArgumentValue('flex' + n.to_s, user_arguments) flex_hrs << runner.getStringArgumentValue('flex_hrs' + n.to_s, user_arguments) end - # check for error inputs - if loop.include?('Error') - runner.registerError('No service hot water loop was found. Measure did not run.') - end - - if zone.include?('Error') - runner.registerError('No thermal zone was found. Measure did not run.') - end - # check capacity, volume, and temps for reasonableness if cap < 5 runner.registerWarning('HPWH heating capacity is less than 5kW ( 17kBtu/hr)') end if bu_cap < 5 runner.registerWarning('Backup heating capaicty is less than 5kW ( 17kBtu/hr).') end - if vol < 40 + if vol == 0 + runner.registerInfo('Tank volume was not specified, using existing tank capacity.') + elsif vol < 40 runner.registerWarning('Tank has less than 40 gallon capacity; check heat pump sizing if model fails.') end if min_temp < 120 runner.registerWarning('Minimum tank temperature is very low; consider increasing to at least 120F.') runner.registerWarning('Do not store water for long periods at temperatures below 135-140F as those ' \ 'conditions facilitate the growth of Legionella.') end - if max_temp > 180 - runner.registerWarning('Maximum charging temperature exceeded practical limits; reset to 180F.') - max_temp = 180.0 + if max_temp > 185 + runner.registerWarning('Maximum charging temperature exceeded practical limits; reset to 185F.') + max_temp = 185.0 end - if max_temp > 160 + if max_temp > 170 runner.registerWarning("#{max_temp}F is above or near the limit of the HP performance curves. If the " \ 'simulation fails with cooling capacity less than 0, you have exceeded performance ' \ - 'limits. Consider setting max temp to less than 160F.') + 'limits. Consider setting max temp to less than 170F.') end # check selected schedule and set flag for later use sched_flag = false # flag for either creating new (false) or modifying existing (true) schedule if sched == '--Create New @ 140F--' @@ -481,112 +456,135 @@ flex_times.each do |ft| d_day.addValue(ft, new_values[idx]) idx += 1 end - ## CONTROLS: TANK TEMPERATURE SETPOINT SCHEDULE (ELECTRIC BACKUP) -------------------------------------------------- + ## END CONTROLS: TANK TEMPERATURE SETPOINT SCHEDULE (ELECTRIC BACKUP) ---------------------------------------------- ## HARDWARE -------------------------------------------------------------------------------------------------------- # This section adds the selected type of heat pump water heater to the supply side of the selected loop. If # selected, measure will remove any existing water heaters on the supply side of the loop. If old heater(s) are left # in place, the new HPWH tank will be placed in front (to the left) of them. # use OS standards build - arbitrary selection, but NZE Ready seems appropriate std = Standard.build('NREL ZNE Ready 2017') - # create empty arrays and initialize variables for later use - old_heater = [] - count = 0 + ##### + # get the selected water heaters + whtrs = [] + model.getWaterHeaterMixeds.each do |w| + if wh == 'All Water Heaters (Simplified Only)' + # exclude booster tanks (<10gal): + if w.tankVolume.to_f < 0.037854 + next + else + whtrs << w + end + elsif w.name.to_s == wh + whtrs << w + end + end - # convert loop and zone names from STRINGS into OS model OBJECTS - zone = model.getThermalZoneByName(zone).get - loop = model.getPlantLoopByName(loop).get + whtrs.each do |wh| + # create empty arrays and initialize variables for later use + old_heater = [] + count = 0 - # find and locate old water heater on selected loop, if applicable - loop_equip = loop.supplyComponents - loop_equip.each do |le| - if le.iddObject.name.include?('WaterHeater:Mixed') - old_heater << model.getWaterHeaterMixedByName(le.name.to_s).get - count += 1 - elsif le.iddObject.name.include?('WaterHeater:Stratified') - old_heater << model.getWaterHeaterStratifiedByName(le.name.to_s).get - count += 1 + # get the appropriate plant loop + loop = '' + loops = model.getPlantLoops + loops.each do |l| + l.supplyComponents.each do |c| + if c.name.to_s == wh.name.to_s + loop = l + end + end end - end - unless old_heater.empty? - inlet = old_heater[0].supplyInletModelObject.get.to_Node.get - outlet = old_heater[0].supplyOutletModelObject.get.to_Node.get - end + # use existing tank volume unless otherwise specified + # values between 0.0 and 5.0 are considered tank sizing multipliers + if vol == 0 + v = wh.tankVolume + elsif (vol > 0.0) && (vol < 5.0) + v = wh.tankVolume.to_f * vol + else + v = OpenStudio.convert(vol, 'gal', 'm^3').get + end - # Add heat pump water heater and attach to selected loop - # Reference: https://github.com/NREL/openstudio-standards/blob/master/lib/ - # => openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb - if type != 'Simplified' - hpwh = std.model_add_heatpump_water_heater(model, # model - type: type, # type - water_heater_capacity: (cap * 1000 / cop), # water_heater_capacity - electric_backup_capacity: (bu_cap * 1000), # electric_backup_capacity - water_heater_volume: OpenStudio.convert(vol, 'gal', 'm^3').get, # water_heater_volume - service_water_temperature: OpenStudio.convert(140.0, 'F', 'C').get, # service_water_temperature - parasitic_fuel_consumption_rate: 3.0, # parasitic_fuel_consumption_rate - swh_temp_sch: sched, # swh_temp_sch - cop: cop, # cop - shr: 0.88, # shr - tank_ua: 3.9, # tank_ua - set_peak_use_flowrate: false, # set_peak_use_flowrate - peak_flowrate: 0.0, # peak_flowrate - flowrate_schedule: nil, # flowrate_schedule - water_heater_thermal_zone: zone) # water_heater_thermal_zone - else - hpwh = std.model_add_water_heater(model, # model - (cap * 1000), # water_heater_capacity - OpenStudio.convert(vol, 'gal', 'm^3').get, # water_heater_volume - 'HeatPump', # water_heater_fuel - OpenStudio.convert(140.0, 'F', 'C').get, # service_water_temperature - 3.0, # parasitic_fuel_consumption_rate - sched, # swh_temp_sch - false, # set_peak_use_flowrate - 0.0, # peak_flowrate - nil, # flowrate_schedule - zone, # water_heater_thermal_zone - 1) # number_water_heaters - end + inlet = wh.supplyInletModelObject.get.to_Node.get + outlet = wh.supplyOutletModelObject.get.to_Node.get - # add tank to appropriate branch and node (will be placed first in series if old tanks not removed) - # modify objects as ncessary - if old_heater.empty? - loop.addSupplyBranchForComponent(hpwh.tank) - elsif type != 'Simplified' - hpwh.tank.addToNode(inlet) - hpwh.setDeadBandTemperatureDifference(db_temp / 1.8) - runner.registerInfo("#{hpwh.tank.name} was added to the model on #{loop.name}") - else - hpwh.addToNode(inlet) - hpwh.setMaximumTemperatureLimit(OpenStudio.convert(max_temp, 'F', 'C').get) - runner.registerInfo("#{hpwh.name} was added to the model on #{loop.name}") - end + # Add heat pump water heater and attach to selected loop + # Reference: https://github.com/NREL/openstudio-standards/blob/master/lib/ + # => openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb + if type != 'Simplified' + # convert zone name from STRING into OS model OBJECT + zone = model.getThermalZoneByName(zone).get + hpwh = std.model_add_heatpump_water_heater(model, # model + type: type, # type + water_heater_capacity: (cap * 1000 / cop), # water_heater_capacity + electric_backup_capacity: (bu_cap * 1000), # electric_backup_capacity + water_heater_volume: v, # water_heater_volume + service_water_temperature: OpenStudio.convert(140.0, 'F', 'C').get, # service_water_temperature + parasitic_fuel_consumption_rate: 3.0, # parasitic_fuel_consumption_rate + swh_temp_sch: sched, # swh_temp_sch + cop: cop, # cop + shr: 0.88, # shr + tank_ua: 3.9, # tank_ua + set_peak_use_flowrate: false, # set_peak_use_flowrate + peak_flowrate: 0.0, # peak_flowrate + flowrate_schedule: nil, # flowrate_schedule + water_heater_thermal_zone: zone) # water_heater_thermal_zone + else + # zone = wh.ambientTemperatureThermalZone.get + runner.registerWarning(wh.to_s) + hpwh = std.model_add_water_heater(model, # model + (cap * 1000), # water_heater_capacity + v.to_f, # water_heater_volume + 'HeatPump', # water_heater_fuel + OpenStudio.convert(140.0, 'F', 'C').to_f, # service_water_temperature + 3.0, # parasitic_fuel_consumption_rate + sched, # swh_temp_sch + false, # set_peak_use_flowrate + 0.0, # peak_flowrate + nil, # flowrate_schedule + model.getThermalZones[0], # water_heater_thermal_zone + 1) # number_water_heaters + # set COP in PLF curve + cop_curve = hpwh.partLoadFactorCurve.get + cop_curve.setName(cop_curve.name.get.gsub('2.8', cop.to_s)) + cop_curve.setCoefficient1Constant(cop) + end - # remove old tank objects if necessary - if remove_wh - old_heater.each do |oh| - runner.registerInfo("#{oh.name} was removed from the model.") - oh.remove + # add tank to appropriate branch and node (will be placed first in series if old tanks not removed) + # modify objects as ncessary + if type != 'Simplified' + hpwh.tank.addToNode(inlet) + hpwh.setDeadBandTemperatureDifference(db_temp / 1.8) + runner.registerInfo("#{hpwh.tank.name} was added to the model on #{loop.name}") + else + hpwh.addToNode(inlet) + hpwh.setMaximumTemperatureLimit(OpenStudio.convert(max_temp, 'F', 'C').get) + runner.registerInfo("#{hpwh.name} was added to the model on #{loop.name}") end - end - ## END HARDWARE ---------------------------------------------------------------------------------------------------- - ## CONTROLS MODIFICATIONS FOR TANK --------------------------------------------------------------------------------- - # apply schedule to tank - if type == 'PumpedCondenser' - hpwh.tank.to_WaterHeaterMixed.get.setSetpointTemperatureSchedule(tank_sched) - elsif type == 'WrappedCondenser' - hpwh.tank.to_WaterHeaterStratified.get.setHeater1SetpointTemperatureSchedule(tank_sched) - hpwh.tank.to_WaterHeaterStratified.get.setHeater2SetpointTemperatureSchedule(tank_sched) - elsif type == 'Simplified' - runner.registerInfo('Line 492 was used. Nothing done here yet... Check tank temperature schedules...') + # remove old tank objects if necessary + if remove_wh + runner.registerInfo("#{wh.name} was removed from the model.") + wh.remove + end + + # CONTROLS MODIFICATIONS FOR TANK --------------------------------------------------------------------------------- + # apply schedule to tank + if type == 'PumpedCondenser' + hpwh.tank.to_WaterHeaterMixed.get.setSetpointTemperatureSchedule(tank_sched) + elsif type == 'WrappedCondenser' + hpwh.tank.to_WaterHeaterStratified.get.setHeater1SetpointTemperatureSchedule(tank_sched) + hpwh.tank.to_WaterHeaterStratified.get.setHeater2SetpointTemperatureSchedule(tank_sched) + end + # END CONTROLS MODIFICATIONS FOR TANK ----------------------------------------------------------------------------- end - ## END CONTROLS MODIFICATIONS FOR TANK ----------------------------------------------------------------------------- + ## END HARDWARE ---------------------------------------------------------------------------------------------------- ## ADD REPORTED VARIABLES ------------------------------------------------------------------------------------------ ovar_names = ['Cooling Coil Total Cooling Rate', 'Cooling Coil Total Water Heating Rate',