module OpenstudioStandards::ServiceWaterHeating

The ServiceWaterHeating module provides methods to create, modify, and get information about service water heating

The ServiceWaterHeating module provides methods to create, modify, and get information about service water heating

The ServiceWaterHeating module provides methods to create, modify, and get information about service water heating

The ServiceWaterHeating module provides methods to create, modify, and get information about service water heating

Public Class Methods

create_booster_water_heating_loop(model, water_heater_capacity: 8000.0, water_heater_volume: OpenStudio.convert(6.0, 'gal', 'm^3').get, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: 82.2, service_water_temperature_schedule: nil, water_heater_thermal_zone: nil, service_water_loop: nil) click to toggle source

Creates a booster water heater on its own loop and attaches it to the main service water heating loop.

@param model [OpenStudio::Model::Model] OpenStudio model object @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 8 kW / 27.283 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.0227 m^3 / 6 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’. @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 82.2 C / 180 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.

If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

@param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.

If nil, will assume 71.6 F / 22 C ambient air temperature.

@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @return [OpenStudio::Model::PlantLoop] The booster water loop OpenStudio PlantLoop object

# File lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb, line 181
def self.create_booster_water_heating_loop(model,
                                           water_heater_capacity: 8000.0,
                                           water_heater_volume: OpenStudio.convert(6.0, 'gal', 'm^3').get,
                                           water_heater_fuel: 'Electricity',
                                           on_cycle_parasitic_fuel_consumption_rate: 0.0,
                                           off_cycle_parasitic_fuel_consumption_rate: 0.0,
                                           service_water_temperature: 82.2,
                                           service_water_temperature_schedule: nil,
                                           water_heater_thermal_zone: nil,
                                           service_water_loop: nil)
  if service_water_loop.nil?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "#{_method_} requires the service_water_loop argument to couple the booster water heating loop with a heat exchanger.")
    return nil
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Adding booster water heater to #{service_water_loop.name}")
  end

  water_heater_volume_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
  water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get

  # Booster water heating loop
  booster_service_water_loop = OpenStudio::Model::PlantLoop.new(model)
  booster_service_water_loop.setName('Booster Service Water Loop')

  # create and add booster water heater to loop
  booster_water_heater = OpenstudioStandards::ServiceWaterHeating.create_water_heater(model,
                                                                                      water_heater_capacity: water_heater_capacity,
                                                                                      water_heater_volume: water_heater_volume,
                                                                                      water_heater_fuel: water_heater_fuel,
                                                                                      on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate,
                                                                                      off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate,
                                                                                      service_water_temperature: service_water_temperature,
                                                                                      service_water_temperature_schedule: service_water_temperature_schedule,
                                                                                      water_heater_thermal_zone: water_heater_thermal_zone,
                                                                                      service_water_loop: booster_service_water_loop)
  booster_water_heater.setName("#{water_heater_volume_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
  booster_water_heater.setEndUseSubcategory('Booster')

  # Service water heating loop controls
  swh_temp_f = OpenStudio.convert(service_water_temperature, 'C', 'F').get
  swh_delta_t_r = 9.0 # 9F delta-T
  swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
  swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                 service_water_temperature,
                                                                                 name: "Service Water Booster Temp - #{swh_temp_f.round}F",
                                                                                 schedule_type_limit: 'Temperature')
  swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch)
  swh_stpt_manager.setName('Hot water booster setpoint manager')
  swh_stpt_manager.addToNode(booster_service_water_loop.supplyOutletNode)
  sizing_plant = booster_service_water_loop.sizingPlant
  sizing_plant.setLoopType('Heating')
  sizing_plant.setDesignLoopExitTemperature(service_water_temperature)
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)

  # Booster water heating pump
  swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
  swh_pump.setName('Booster Water Loop Pump')
  swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump
  swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump
  swh_pump.setMotorEfficiency(1)
  swh_pump.setPumpControlType('Continuous')
  swh_pump.setMinimumFlowRate(0.0)
  swh_pump.addToNode(booster_service_water_loop.supplyInletNode)

  # Service water heating loop bypass pipes
  water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  booster_service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
  coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  booster_service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  supply_outlet_pipe.addToNode(booster_service_water_loop.supplyOutletNode)
  demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  demand_inlet_pipe.addToNode(booster_service_water_loop.demandInletNode)
  demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  demand_outlet_pipe.addToNode(booster_service_water_loop.demandOutletNode)

  # Heat exchanger to supply the booster water heater with normal hot water from the main service water loop
  hx = OpenStudio::Model::HeatExchangerFluidToFluid.new(model)
  hx.setName('Booster Water Heating Heat Exchanger')
  hx.setHeatExchangeModelType('Ideal')
  hx.setControlType('UncontrolledOn')
  hx.setHeatTransferMeteringEndUseType('LoopToLoop')

  # Add the HX to the supply side of the booster loop
  hx.addToNode(booster_service_water_loop.supplyInletNode)

  # Add the HX to the demand side of the main service water loop
  service_water_loop.addDemandBranchForComponent(hx)

  # Add a plant component temperature source to the demand outlet
  # of the HX to represent the fact that the water used by the booster
  # would in reality be at the mains temperature.
  mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model)
  mains_src.setName('Mains Water Makeup for SWH Booster')
  mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get)

  # use the site water mains temperature schedule if available,
  # otherwise use the annual average outdoor air temperature
  site_water_mains = model.getSiteWaterMainsTemperature
  if site_water_mains.temperatureSchedule.is_initialized
    water_mains_temp_sch = site_water_mains.temperatureSchedule.get
  elsif site_water_mains.annualAverageOutdoorAirTemperature.is_initialized
    mains_src_temp_c = site_water_mains.annualAverageOutdoorAirTemperature.get
    mains_src.setSourceTemperature(mains_src_temp_c)
    water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
    water_mains_temp_sch.setName('Booster Water Makeup Temperature')
    water_mains_temp_sch.setValue(mains_src_temp_c)
  else # assume 50F
    mains_src_temp_c = OpenStudio.convert(50.0, 'F', 'C').get
    mains_src.setSourceTemperature(mains_src_temp_c)
    water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
    water_mains_temp_sch.setName('Booster Water Makeup Temperature')
    water_mains_temp_sch.setValue(mains_src_temp_c)
  end
  mains_src.setTemperatureSpecificationType('Scheduled')
  mains_src.setSourceTemperatureSchedule(water_mains_temp_sch)

  return booster_service_water_loop
end
create_heatpump_water_heater(model, heat_pump_type: 'PumpedCondenser', water_heater_capacity: 500.0, water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get, coefficient_of_performance: 2.8, electric_backup_capacity: 4500.0, on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get, service_water_temperature_schedule: nil, set_peak_use_flowrate: false, peak_flowrate: nil, flowrate_schedule: nil, water_heater_thermal_zone: nil, service_water_loop: nil, use_ems_control: false) click to toggle source

Creates a heatpump water heater and attaches it to the supplied service water heating loop.

@param model [OpenStudio::Model::Model] OpenStudio model object @param heat_pump_type [String] valid option are ‘WrappedCondenser’ or ‘PumpedCondenser’ (default).

The 'WrappedCondenser' uses a WaterHeaterStratified tank, 'PumpedCondenser' uses a WaterHeaterMixed tank.

@param water_heater_capacity [Double] water heater capacity, in W. Defaults to 500 W / 3.41 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.303 m^3 / 80 gal @param coefficient_of_performance [Double] rated coefficient_of_performance @param electric_backup_capacity [Double] electric heating backup capacity, in W. Default is 4500 W. @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 51.67 C / 125 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.

If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

@param set_peak_use_flowrate [Boolean] if true, the peak flow rate and flow rate schedule will be set. @param peak_flowrate [Double] peak flow rate in m^3/s @param flowrate_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.

If nil, will assume 71.6 F / 22 C ambient air temperature.

@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @param use_ems_control [Boolean] if true, use ems control logic if using a ‘WrappedCondenser’ style HPWH. @return [OpenStudio::Model::WaterHeaterMixed] OpenStudio WaterHeaterMixed object

# File lib/openstudio-standards/service_water_heating/create_water_heater.rb, line 198
def self.create_heatpump_water_heater(model,
                                      heat_pump_type: 'PumpedCondenser',
                                      water_heater_capacity: 500.0,
                                      water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get,
                                      coefficient_of_performance: 2.8,
                                      electric_backup_capacity: 4500.0,
                                      on_cycle_parasitic_fuel_consumption_rate: 0.0,
                                      off_cycle_parasitic_fuel_consumption_rate: 0.0,
                                      service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get,
                                      service_water_temperature_schedule: nil,
                                      set_peak_use_flowrate: false,
                                      peak_flowrate: nil,
                                      flowrate_schedule: nil,
                                      water_heater_thermal_zone: nil,
                                      service_water_loop: nil,
                                      use_ems_control: false)
  # create heat pump water heater
  if heat_pump_type == 'WrappedCondenser'
    hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model)
  elsif heat_pump_type == 'PumpedCondenser'
    hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model)
  end

  # calculate tank height and radius
  water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
  hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
  tank_height = (0.0188 * hpwh_vol_gal) + 0.0935 # linear relationship that gets GE height at 50 gal and AO Smith height at 80 gal
  tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5
  tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height)
  tank_ua = 3.9 # default ua assumption
  u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get
  hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr")

  # set min/max HPWH operating temperature limit
  hpwh_op_min_temp_c = OpenStudio.convert(45.0, 'F', 'C').get
  hpwh_op_max_temp_c = OpenStudio.convert(120.0, 'F', 'C').get

  if heat_pump_type == 'WrappedCondenser'
    hpwh.setMinimumInletAirTemperatureforCompressorOperation(hpwh_op_min_temp_c)
    hpwh.setMaximumInletAirTemperatureforCompressorOperation(hpwh_op_max_temp_c)
    # set sensor heights
    if hpwh_vol_gal <= 50.0
      hpwh.setDeadBandTemperatureDifference(0.5)
      h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top)
      h_le = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top)
      h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
      h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank
      h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
      hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl)
      hpwh.setControlSensor1Weight(1.0)
      hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl)
    else
      hpwh.setDeadBandTemperatureDifference(3.89)
      h_ue = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top)
      h_le = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top)
      h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
      h_condbot = 0.01 # bottom node
      h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
      h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank
      hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up)
      hpwh.setControlSensor1Weight(0.75)
      hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low)
    end
    hpwh.setCondenserBottomLocation(h_condbot)
    hpwh.setCondenserTopLocation(h_condtop)
    hpwh.setTankElementControlLogic('MutuallyExclusive')
    hpwh.autocalculateEvaporatorAirFlowRate
  elsif heat_pump_type == 'PumpedCondenser'
    hpwh.setDeadBandTemperatureDifference(3.89)
    hpwh.autosizeEvaporatorAirFlowRate
  end

  # set heat pump water heater properties
  hpwh.setFanPlacement('DrawThrough')
  hpwh.setOnCycleParasiticElectricLoad(0.0)
  hpwh.setOffCycleParasiticElectricLoad(0.0)
  hpwh.setParasiticHeatRejectionLocation('Outdoors')

  # set temperature setpoint schedule
  if service_water_temperature_schedule.nil?
    # service water heating loop controls
    swh_temp_c = service_water_temperature
    swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
    swh_delta_t_r = 9.0 # 9F delta-T
    swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get
    swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
    service_water_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                         swh_temp_c,
                                                                                                         name: "Heat Pump Water Heater Temp - #{swh_temp_f.round}F",
                                                                                                         schedule_type_limit: 'Temperature')
  end
  hpwh.setCompressorSetpointTemperatureSchedule(service_water_temperature_schedule)

  # coil curves
  hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model)
  hpwh_cap.setName('HPWH-Cap-fT')
  hpwh_cap.setCoefficient1Constant(0.563)
  hpwh_cap.setCoefficient2x(0.0437)
  hpwh_cap.setCoefficient3xPOW2(0.000039)
  hpwh_cap.setCoefficient4y(0.0055)
  hpwh_cap.setCoefficient5yPOW2(-0.000148)
  hpwh_cap.setCoefficient6xTIMESY(-0.000145)
  hpwh_cap.setMinimumValueofx(0.0)
  hpwh_cap.setMaximumValueofx(100.0)
  hpwh_cap.setMinimumValueofy(0.0)
  hpwh_cap.setMaximumValueofy(100.0)

  hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model)
  hpwh_cop.setName('HPWH-COP-fT')
  hpwh_cop.setCoefficient1Constant(1.1332)
  hpwh_cop.setCoefficient2x(0.063)
  hpwh_cop.setCoefficient3xPOW2(-0.0000979)
  hpwh_cop.setCoefficient4y(-0.00972)
  hpwh_cop.setCoefficient5yPOW2(-0.0000214)
  hpwh_cop.setCoefficient6xTIMESY(-0.000686)
  hpwh_cop.setMinimumValueofx(0.0)
  hpwh_cop.setMaximumValueofx(100.0)
  hpwh_cop.setMinimumValueofy(0.0)
  hpwh_cop.setMaximumValueofy(100.0)

  # create DX coil object
  if heat_pump_type == 'WrappedCondenser'
    coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get
    coil.setRatedCondenserWaterTemperature(48.89)
    coil.autocalculateRatedEvaporatorAirFlowRate
  elsif heat_pump_type == 'PumpedCondenser'
    coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPump.get
    coil.autosizeRatedEvaporatorAirFlowRate
  end

  # set coil properties
  coil.setName("#{hpwh.name} Coil")
  coil.setRatedHeatingCapacity(water_heater_capacity)
  coil.setRatedCOP(coefficient_of_performance)
  coil.setRatedSensibleHeatRatio(0.88) # default sensible_heat_ratio assumption
  coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get)
  coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get)
  coil.setEvaporatorFanPowerIncludedinRatedCOP(true)
  coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature')
  coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap)
  coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop)
  coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0)

  # set tank properties
  if heat_pump_type == 'WrappedCondenser'
    tank = hpwh.tank.to_WaterHeaterStratified.get
    tank.setTankHeight(tank_height)
    tank.setHeaterPriorityControl('MasterSlave')
    if hpwh_vol_gal <= 50.0
      tank.setHeater1DeadbandTemperatureDifference(25.0)
      tank.setHeater2DeadbandTemperatureDifference(30.0)
    else
      tank.setHeater1DeadbandTemperatureDifference(18.5)
      tank.setHeater2DeadbandTemperatureDifference(3.89)
    end
    hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
    hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint")
    hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
    hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint")
    tank.setHeater1Capacity(electric_backup_capacity)
    tank.setHeater1Height(h_ue)
    tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS
    tank.setHeater2Capacity(electric_backup_capacity)
    tank.setHeater2Height(h_le)
    tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp)
    tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank)
    tank.setNumberofNodes(12)
    tank.setAdditionalDestratificationConductivity(0)
    tank.setNode1AdditionalLossCoefficient(0)
    tank.setNode2AdditionalLossCoefficient(0)
    tank.setNode3AdditionalLossCoefficient(0)
    tank.setNode4AdditionalLossCoefficient(0)
    tank.setNode5AdditionalLossCoefficient(0)
    tank.setNode6AdditionalLossCoefficient(0)
    tank.setNode7AdditionalLossCoefficient(0)
    tank.setNode8AdditionalLossCoefficient(0)
    tank.setNode9AdditionalLossCoefficient(0)
    tank.setNode10AdditionalLossCoefficient(0)
    tank.setNode11AdditionalLossCoefficient(0)
    tank.setNode12AdditionalLossCoefficient(0)
    tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1)
    tank.setSourceSideDesignFlowRate(0)
    tank.setSourceSideFlowControlMode('')
    tank.setSourceSideInletHeight(0)
    tank.setSourceSideOutletHeight(0)
  elsif heat_pump_type == 'PumpedCondenser'
    tank = hpwh.tank.to_WaterHeaterMixed.get
    tank.setDeadbandTemperatureDifference(3.89)
    tank.setHeaterControlType('Cycle')
    tank.setHeaterMaximumCapacity(electric_backup_capacity)
  end
  tank.setName("#{hpwh.name} Tank")
  tank.setEndUseSubcategory('Service Hot Water')
  tank.setTankVolume(0.9 * water_heater_volume)
  tank.setMaximumTemperatureLimit(90.0)
  tank.setHeaterFuelType('Electricity')
  tank.setHeaterThermalEfficiency(1.0)
  tank.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate)
  tank.setOffCycleParasiticFuelType('Electricity')
  tank.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate)
  tank.setOnCycleParasiticFuelType('Electricity')

  # set fan properties
  fan = hpwh.fan.to_FanOnOff.get
  fan.setName("#{hpwh.name} Fan")
  fan_power = 0.0462 # watts per cfm
  if hpwh_vol_gal <= 50.0
    fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
    fan.setPressureRise(23.0)
  else
    fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
    fan.setPressureRise(65.0)
  end
  # determine maximum flow rate from water heater capacity
  # use 5.035E-5 m^3/s/W from EnergyPlus used to autocalculate the evaporator air flow rate in WaterHeater:HeatPump:PumpedCondenser and Coil:WaterHeating:AirToWaterHeatPump:Pumped
  fan_flow_rate_m3_per_s = water_heater_capacity * 5.035e-5
  fan.setMaximumFlowRate(fan_flow_rate_m3_per_s)
  fan.setMotorEfficiency(1.0)
  fan.setMotorInAirstreamFraction(1.0)
  fan.setEndUseSubcategory('Service Hot Water')

  if water_heater_thermal_zone.nil?
    # add in schedules for Tamb, RHamb, and the compressor
    # assume the water heater is indoors at 71.6F / 22C
    default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                            OpenStudio.convert(71.6, 'F', 'C').get,
                                                                                                            name: 'Water Heater Ambient Temp Schedule 70F',
                                                                                                            schedule_type_limit: 'Temperature')
    tank.setAmbientTemperatureIndicator('Schedule')
    tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
    tank.resetAmbientTemperatureThermalZone
    hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model)
    hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule")
    hpwh_rhamb.setValue(0.5)
    hpwh.setInletAirConfiguration('Schedule')
    hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch)
    hpwh.setInletAirHumiditySchedule(hpwh_rhamb)
    hpwh.setCompressorLocation('Schedule')
    hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
  else
    hpwh.addToThermalZone(water_heater_thermal_zone)
    hpwh.setInletAirConfiguration('ZoneAirOnly')
    hpwh.setCompressorLocation('Zone')
    tank.setAmbientTemperatureIndicator('ThermalZone')
    tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
    tank.resetAmbientTemperatureSchedule
  end

  if set_peak_use_flowrate
    rated_flow_rate_m3_per_s = peak_flowrate
    rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
    tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s)
    tank.setUseFlowRateFractionSchedule(flowrate_schedule) unless flowrate_schedule.nil?
  end

  # add EMS for overriding HPWH setpoints schedules (for upper/lower heating element in water tank and compressor in heat pump)
  if heat_pump_type == 'WrappedCondenser' && use_ems_control
    std = Standard.build('90.1-2013')
    hpwh_name_ems_friendly = std.ems_friendly_name(hpwh.name)

    # create an ambient temperature sensor for the air that blows through the HPWH evaporator
    if water_heater_thermal_zone.nil?
      # assume the condenser is outside
      amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Outdoor Air Drybulb Temperature')
      amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp")
      amb_temp_sensor.setKeyName('Environment')
    else
      amb_temp_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Zone Mean Air Temperature')
      amb_temp_sensor.setName("#{hpwh_name_ems_friendly}_amb_temp")
      amb_temp_sensor.setKeyName(water_heater_thermal_zone.name.to_s)
    end

    # create actuator for heat pump compressor
    if service_water_temperature_schedule.to_ScheduleConstant.is_initialized
      service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleConstant.get
      schedule_type = 'Schedule:Constant'
    elsif service_water_temperature_schedule.to_ScheduleCompact.is_initialized
      service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleCompact.get
      schedule_type = 'Schedule:Compact'
    elsif service_water_temperature_schedule.to_ScheduleRuleset.is_initialized
      service_water_temperature_schedule = service_water_temperature_schedule.to_ScheduleRuleset.get
      schedule_type = 'Schedule:Year'
    else
      OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "Unsupported schedule type for HPWH setpoint schedule #{service_water_temperature_schedule.name}.")
      return false
    end
    hpwhschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(service_water_temperature_schedule, schedule_type, 'Schedule Value')
    hpwhschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_HPWHSchedOverride")

    # create actuator for lower heating element in water tank
    leschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_bottom_element_sp, 'Schedule:Constant', 'Schedule Value')
    leschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_LESchedOverride")

    # create actuator for upper heating element in water tank
    ueschedoverride_actuator = OpenStudio::Model::EnergyManagementSystemActuator.new(hpwh_top_element_sp, 'Schedule:Constant', 'Schedule Value')
    ueschedoverride_actuator.setName("#{hpwh_name_ems_friendly}_UESchedOverride")

    # create sensor for heat pump compressor
    t_set_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
    t_set_sensor.setName("#{hpwh_name_ems_friendly}_T_set")
    t_set_sensor.setKeyName(service_water_temperature_schedule.name.to_s)

    # define control configuration
    t_offset = 9.0 # deg-C

    # get tank specifications
    upper_element_db = tank.heater1DeadbandTemperatureDifference

    # define control logic
    hpwh_ctrl_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
    hpwh_ctrl_program.setName("#{hpwh_name_ems_friendly}_Control")
    hpwh_ctrl_program.addLine("SET #{hpwhschedoverride_actuator.name} = #{t_set_sensor.name}")
    # lockout hp when ambient temperature is either too high or too low
    hpwh_ctrl_program.addLine("IF (#{amb_temp_sensor.name}<#{hpwh_op_min_temp_c}) || (#{amb_temp_sensor.name}>#{hpwh_op_max_temp_c})")
    hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name}")
    hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = #{t_set_sensor.name}")
    hpwh_ctrl_program.addLine('ELSE')
    # upper element setpoint temperature
    hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name} = #{t_set_sensor.name} - #{t_offset}")
    # upper element cut-in temperature
    hpwh_ctrl_program.addLine("SET #{ueschedoverride_actuator.name}_cut_in = #{ueschedoverride_actuator.name} - #{upper_element_db}")
    # lower element disabled
    hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name} = 0")
    # lower element disabled
    hpwh_ctrl_program.addLine("SET #{leschedoverride_actuator.name}_cut_in = 0")
    hpwh_ctrl_program.addLine('ENDIF')

    # create a program calling manager
    program_calling_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
    program_calling_manager.setName("#{hpwh_name_ems_friendly}_ProgramManager")
    program_calling_manager.setCallingPoint('InsideHVACSystemIterationLoop')
    program_calling_manager.addProgram(hpwh_ctrl_program)
  end

  # add the water heater to the service water loop if provided
  unless service_water_loop.nil?
    service_water_loop.addSupplyBranchForComponent(tank)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added heat pump water heater called #{tank.name}")

  return hpwh
end
create_service_water_heating_loop(model, system_name: 'Service Water Loop', service_water_temperature: 60.0, service_water_pump_head: 29861.0, service_water_pump_motor_efficiency: 0.3, water_heater_capacity: nil, water_heater_volume: nil, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, water_heater_thermal_zone: nil, number_of_water_heaters: 1, add_piping_losses: false, pipe_insulation_thickness: 0.0127, floor_area: nil, number_of_stories: nil) click to toggle source

Creates a service water heating loop.

@param model [OpenStudio::Model::Model] OpenStudio model object @param system_name [String] the name of the system. nil results in the default. @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 60 C / 140 F. @param service_water_pump_head [Double] service water pump head, in Pa. Default is 29861 Pa / 10 ft. @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal. @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, ‘FuelOilNo2’, ‘SimpleHeatPump’, ‘HeatPump’, or ‘None’.

If 'None', no water heater will be added.

@param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.

If nil, will assume 71.6 F / 22 C ambient air temperature.

@param number_of_water_heaters [Integer] the number of water heaters represented by the capacity and volume inputs.

Used to modify efficiencies for water heaters based on individual component size while avoiding having to model lots of individual water heaters (for runtime sake).

@param add_piping_losses [Boolean] if true, add piping and associated heat losses to system. If false, add no pipe heat losses. @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Default is 0.0127 m / 0.5 inches. @param floor_area [Double] the area of building served by the service water heating loop, in m^2

If nil, will use the total building floor area. Only used if piping losses is true and the system is circulating.

@param number_of_stories [Integer] the number of stories served by the service water heating loop

If nil, will use the total building number of stories. Only used if piping losses is true and the system is circulating.

@return [OpenStudio::Model::PlantLoop] OpenStudio PlantLoop object of the service water loop

# File lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb, line 31
def self.create_service_water_heating_loop(model,
                                           system_name: 'Service Water Loop',
                                           service_water_temperature: 60.0,
                                           service_water_pump_head: 29861.0,
                                           service_water_pump_motor_efficiency: 0.3,
                                           water_heater_capacity: nil,
                                           water_heater_volume: nil,
                                           water_heater_fuel: 'Electricity',
                                           on_cycle_parasitic_fuel_consumption_rate: 0.0,
                                           off_cycle_parasitic_fuel_consumption_rate: 0.0,
                                           water_heater_thermal_zone: nil,
                                           number_of_water_heaters: 1,
                                           add_piping_losses: false,
                                           pipe_insulation_thickness: 0.0127,
                                           floor_area: nil,
                                           number_of_stories: nil)

  # create service water heating loop
  service_water_loop = OpenStudio::Model::PlantLoop.new(model)
  service_water_loop.setMinimumLoopTemperature(10.0)
  if service_water_temperature > 60.0
    service_water_loop.setMaximumLoopTemperature(service_water_temperature)
  else
    service_water_loop.setMaximumLoopTemperature(60.0)
  end

  if system_name.nil?
    system_name = 'Service Water Loop'
  end
  service_water_loop.setName(system_name)

  # service water heating loop controls
  swh_temp_f = OpenStudio.convert(service_water_temperature, 'C', 'F').get
  swh_delta_t_r = 9.0 # 9F delta-T
  swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
  swh_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                 service_water_temperature,
                                                                                 name: "Service Water Loop Temp - #{swh_temp_f.round}F",
                                                                                 schedule_type_limit: 'Temperature')
  swh_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, swh_temp_sch)
  swh_stpt_manager.setName('Service hot water setpoint manager')
  swh_stpt_manager.addToNode(service_water_loop.supplyOutletNode)
  sizing_plant = service_water_loop.sizingPlant
  sizing_plant.setLoopType('Heating')
  sizing_plant.setDesignLoopExitTemperature(service_water_temperature)
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)

  # determine if circulating or non-circulating based on supplied head pressure
  if service_water_pump_head.nil? || service_water_pump_head <= 1
    # set pump head pressure to near zero if there is no circulation pump
    service_water_pump_head = 0.001
    service_water_pump_motor_efficiency = 1
    circulating = false
  else
    circulating = true
  end

  # add pump
  if circulating
    swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
    swh_pump.setName("#{service_water_loop.name} Circulator Pump")
    swh_pump.setPumpControlType('Intermittent')
  else
    swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
    swh_pump.setName("#{service_water_loop.name} Water Mains Pressure Driven")
    swh_pump.setPumpControlType('Continuous')
  end
  swh_pump.setRatedPumpHead(service_water_pump_head.to_f)
  swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency)
  swh_pump.addToNode(service_water_loop.supplyInletNode)

  # add water heater
  case water_heater_fuel
  when 'None'
    # don't add a water heater
  when 'HeatPump'
    OpenstudioStandards::ServiceWaterHeating.create_heatpump_water_heater(model,
                                                                          water_heater_capacity: water_heater_capacity,
                                                                          water_heater_volume: water_heater_volume,
                                                                          on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate,
                                                                          off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate,
                                                                          service_water_temperature: service_water_temperature,
                                                                          service_water_temperature_schedule: swh_temp_sch,
                                                                          set_peak_use_flowrate: false,
                                                                          peak_flowrate: 0.0,
                                                                          flowrate_schedule: nil,
                                                                          water_heater_thermal_zone: water_heater_thermal_zone,
                                                                          service_water_loop: service_water_loop,
                                                                          use_ems_control: false)
  else
    OpenstudioStandards::ServiceWaterHeating.create_water_heater(model,
                                                                  water_heater_capacity: water_heater_capacity,
                                                                  water_heater_volume: water_heater_volume,
                                                                  water_heater_fuel: water_heater_fuel,
                                                                  on_cycle_parasitic_fuel_consumption_rate: on_cycle_parasitic_fuel_consumption_rate,
                                                                  off_cycle_parasitic_fuel_consumption_rate: off_cycle_parasitic_fuel_consumption_rate,
                                                                  service_water_temperature: service_water_temperature,
                                                                  service_water_temperature_schedule: swh_temp_sch,
                                                                  set_peak_use_flowrate: false,
                                                                  peak_flowrate: 0.0,
                                                                  flowrate_schedule: nil,
                                                                  water_heater_thermal_zone: water_heater_thermal_zone,
                                                                  number_of_water_heaters: number_of_water_heaters,
                                                                  service_water_loop: service_water_loop)
  end

  # add pipe losses if requested
  if add_piping_losses
    OpenstudioStandards::ServiceWaterHeating.create_service_water_heating_piping_losses(model,
                                                                                        service_water_loop,
                                                                                        circulating: circulating,
                                                                                        pipe_insulation_thickness: pipe_insulation_thickness,
                                                                                        floor_area: floor_area,
                                                                                        number_of_stories: number_of_stories)
  end

  # service water heating loop bypass pipes
  water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
  coil_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode)
  demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
  demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode)

  if circulating
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added circulating SWH loop called #{service_water_loop.name}")
  else
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added non-circulating SWH loop called #{service_water_loop.name}")
  end

  return service_water_loop
end
create_service_water_heating_piping_losses(model, service_water_loop, circulating: true, pipe_insulation_thickness: 0.0, floor_area: nil, number_of_stories: nil, pipe_length: 6.1, air_temperature: 21.1) click to toggle source

Adds piping losses to a service water heating Loop. Assumes the piping system use insulated 0.75 inch copper piping. For circulating systems, assume length of piping is proportional to the building floor area and number of stories. For non-circulating systems, assume that the water heaters are close to the point of use.

@param model [OpenStudio::Model::Model] OpenStudio model object @param service_water_loop [OpenStudio::Model::PlantLoop] the service water heating loop @param circulating [Boolean] use true for circulating systems, false for non-circulating systems @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation @param floor_area [Double] the area of building served by the service water heating loop, in m^2

If nil, will use the total building floor area. Only used if circulating is true.

@param number_of_stories [Integer] the number of stories served by the service water heating loop

If nil, will use the total building number of stories. Only used if circulating is true.

@param pipe_length [Double] the length of the pipe in meters. Default is 6.1 m / 20 ft.

Only used if circulating is false.

@param air_temperature [Double] the temperature of the air surrounding the piping, in C. Default is 21.1 C / 70 F. @return [Boolean] returns true if successful, false if not

# File lib/openstudio-standards/service_water_heating/create_piping_losses.rb, line 24
def self.create_service_water_heating_piping_losses(model,
                                                    service_water_loop,
                                                    circulating: true,
                                                    pipe_insulation_thickness: 0.0,
                                                    floor_area: nil,
                                                    number_of_stories: nil,
                                                    pipe_length: 6.1,
                                                    air_temperature: 21.1)

  # Estimate pipe length
  if circulating
    # For circulating systems, get pipe length based on the size of the building.
    # Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf

    # get the floor area
    floor_area = model.getBuilding.floorArea if floor_area.nil?
    floor_area_ft2 = OpenStudio.convert(floor_area, 'm^2', 'ft^2').get

    # get the number of stories
    number_of_stories = model.getBuilding.buildingStories.size if number_of_stories.nil?

    # calculate the piping length
    pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0)))
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Pipe length #{pipe_length_ft.round}ft = 2.0 * ( (#{floor_area_ft2.round}ft2 / #{number_of_stories} stories)^0.5 + (10.0ft * (#{number_of_stories} stories - 1.0) ) )")
  else
    # For non-circulating systems, assume water heater is close to point of use

    # get pipe length
    pipe_length_m = pipe_length.nil? ? 6.1 : pipe_length

    pipe_length_ft = OpenStudio.convert(pipe_length_m, 'm', 'ft').get
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Pipe length #{pipe_length_ft.round}ft. For non-circulating systems, assume water heater is close to point of use.")
  end

  # For systems whose water heater object represents multiple pieces
  # of equipment, multiply the piping length by the number of pieces of equipment.
  service_water_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc|
    next unless sc.to_WaterHeaterMixed.is_initialized

    water_heater = sc.to_WaterHeaterMixed.get

    # get number of water heaters
    if water_heater.additionalProperties.getFeatureAsInteger('component_quantity').is_initialized
      comp_qty = water_heater.additionalProperties.getFeatureAsInteger('component_quantity').get
    else
      comp_qty = 1
    end

    # if more than 1 water heater, multiply the pipe length by the number of water heaters,
    # unless the user has specified a pipe length
    if comp_qty > 1 && pipe_length.nil?
      OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Piping length has been multiplied by #{comp_qty}X because #{water_heater.name} represents #{comp_qty} pieces of equipment.")
      pipe_length_ft *= comp_qty
      break
    end
  end

  # Service water heating piping heat loss scheduled air temperature
  air_temperature_f = OpenStudio.convert(air_temperature, 'C', 'F').get
  swh_piping_air_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                            air_temperature,
                                                                                            name: "#{service_water_loop.name} Piping Air Temp - #{air_temperature_f.round}F",
                                                                                            schedule_type_limit: 'Temperature')

  # Service water heating piping heat loss scheduled air velocity
  swh_piping_air_velocity_m_per_s = 0.3
  swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get
  swh_piping_air_velocity_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                swh_piping_air_velocity_m_per_s,
                                                                                                name: "#{service_water_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph")

  # Material for 3/4in type L (heavy duty) copper pipe
  copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model)
  copper_pipe.setName('Copper pipe 0.75in type L')
  copper_pipe.setRoughness('Smooth')
  copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get)
  copper_pipe.setThermalConductivity(386.0)
  copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get)
  copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get)
  copper_pipe.setThermalAbsorptance(0.9) # @todo find reference for property
  copper_pipe.setSolarAbsorptance(0.7) # @todo find reference for property
  copper_pipe.setVisibleAbsorptance(0.7) # @todo find reference for property

  # Construction for pipe
  pipe_construction = OpenStudio::Model::Construction.new(model)

  # Add insulation material to insulated pipe
  if pipe_insulation_thickness > 0
    # Material for fiberglass insulation
    # R-value from Owens-Corning 1/2in fiberglass pipe insulation
    # https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22
    # but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air
    pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get
    insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model)
    insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in")
    insulation.setRoughness('Smooth')
    insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get)
    insulation.setThermalConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get)
    insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get)
    insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get)
    insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used
    insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
    insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used

    pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt")
    pipe_construction.setLayers([insulation, copper_pipe])
  else
    pipe_construction.setName('Uninsulated copper pipe 0.75in type L')
    pipe_construction.setLayers([copper_pipe])
  end

  heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model)
  heat_loss_pipe.setName("#{service_water_loop.name} Pipe #{pipe_length_ft.round}ft")
  heat_loss_pipe.setEnvironmentType('Schedule')
  heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch)
  heat_loss_pipe.setAmbientAirVelocitySchedule(swh_piping_air_velocity_sch)
  heat_loss_pipe.setConstruction(pipe_construction)
  heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get)
  heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get)

  heat_loss_pipe.addToNode(service_water_loop.demandInletNode)

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added #{pipe_length_ft.round}ft of #{pipe_construction.name} losing heat to #{air_temperature_f.round}F air to #{service_water_loop.name}.")
  return true
end
create_water_heater(model, water_heater_capacity: nil, water_heater_volume: nil, water_heater_fuel: 'Electricity', on_cycle_parasitic_fuel_consumption_rate: 0.0, off_cycle_parasitic_fuel_consumption_rate: 0.0, service_water_temperature: 60.0, service_water_temperature_schedule: nil, set_peak_use_flowrate: false, peak_flowrate: nil, flowrate_schedule: nil, water_heater_thermal_zone: nil, number_of_water_heaters: 1, service_water_loop: nil) click to toggle source

Creates a water heater and attaches it to the supplied service water heating loop.

@param model [OpenStudio::Model::Model] OpenStudio model object @param water_heater_capacity [Double] water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr @param water_heater_volume [Double] water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal @param water_heater_fuel [String] water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, or ‘HeatPump’ @param on_cycle_parasitic_fuel_consumption_rate [Double] water heater on cycle parasitic fuel consumption rate, in W @param off_cycle_parasitic_fuel_consumption_rate [Double] water heater off cycle parasitic fuel consumption rate, in W @param service_water_temperature [Double] water heater temperature, in degrees C. Default is 60 C / 140 F. @param service_water_temperature_schedule [OpenStudio::Model::Schedule] the service water heating schedule.

If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

@param set_peak_use_flowrate [Boolean] if true, the peak flow rate and flow rate schedule will be set. @param peak_flowrate [Double] peak flow rate in m^3/s @param flowrate_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] Thermal zone for ambient heat loss.

If nil, will assume 71.6 F / 22 C ambient air temperature.

@param number_of_water_heaters [Integer] the number of water heaters represented by the capacity and volume inputs.

Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
lots of individual water heaters (for runtime sake).

@param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water heater to this loop @return [OpenStudio::Model::WaterHeaterMixed] OpenStudio WaterHeaterMixed object

# File lib/openstudio-standards/service_water_heating/create_water_heater.rb, line 28
def self.create_water_heater(model,
                             water_heater_capacity: nil,
                             water_heater_volume: nil,
                             water_heater_fuel: 'Electricity',
                             on_cycle_parasitic_fuel_consumption_rate: 0.0,
                             off_cycle_parasitic_fuel_consumption_rate: 0.0,
                             service_water_temperature: 60.0,
                             service_water_temperature_schedule: nil,
                             set_peak_use_flowrate: false,
                             peak_flowrate: nil,
                             flowrate_schedule: nil,
                             water_heater_thermal_zone: nil,
                             number_of_water_heaters: 1,
                             service_water_loop: nil)
  # create water heater object
  # @todo Standards - Change water heater methodology to follow 'Model Enhancements Appendix A.'
  water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)

  # default water heater capacity if nil
  if water_heater_capacity.nil?
    water_heater_capacity = OpenStudio.convert(200.0, 'kBtu/hr', 'W').get
  end
  water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
  water_heater.setHeaterMaximumCapacity(water_heater_capacity)

  # default water heater volume if nil
  if water_heater_volume.nil?
    water_heater_volume = OpenStudio.convert(100.0, 'gal', 'm^3').get
  end
  water_heater_volume_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
  water_heater.setTankVolume(water_heater_volume)

  # set the water heater fuel
  case water_heater_fuel
  when 'Natural Gas', 'NaturalGas', 'Gas'
    water_heater.setHeaterFuelType('Gas')
    water_heater.setHeaterThermalEfficiency(0.78)
    water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOnCycleParasiticFuelType('Gas')
    water_heater.setOffCycleParasiticFuelType('Gas')
    water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
    water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
  when 'Electricity', 'Electric', 'Elec'
    water_heater.setHeaterFuelType('Electricity')
    water_heater.setHeaterThermalEfficiency(1.0)
    water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOnCycleParasiticFuelType('Electricity')
    water_heater.setOffCycleParasiticFuelType('Electricity')
    water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
    water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
  when 'FuelOilNo2'
    water_heater.setHeaterFuelType('FuelOilNo2')
    water_heater.setHeaterThermalEfficiency(0.78)
    water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOnCycleParasiticFuelType('FuelOilNo2')
    water_heater.setOffCycleParasiticFuelType('FuelOilNo2')
    water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
    water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
  when 'HeatPump', 'SimpleHeatPump'
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.ServiceWaterHeating', 'Simple workaround to represent heat pump water heaters without incurring significant runtime penalty associated with using correct objects.')
    # Make a part-load efficiency modifier curve with a value above 1, which is multiplied by the nominal efficiency of 100% to represent the COP of a HPWH.
    # @todo could make this workaround better by using EMS to modify this curve output in realtime based on the OA temperature.
    hpwh_cop = 2.8
    water_heater.setHeaterFuelType('Electricity')
    water_heater.setHeaterThermalEfficiency(1.0)
    eff_f_of_plr = OpenStudio::Model::CurveCubic.new(model)
    eff_f_of_plr.setName("HPWH_COP_#{hpwh_cop}")
    eff_f_of_plr.setCoefficient1Constant(hpwh_cop)
    eff_f_of_plr.setCoefficient2x(0.0)
    eff_f_of_plr.setCoefficient3xPOW2(0.0)
    eff_f_of_plr.setCoefficient4xPOW3(0.0)
    eff_f_of_plr.setMinimumValueofx(0.0)
    eff_f_of_plr.setMaximumValueofx(1.0)
    water_heater.setPartLoadFactorCurve(eff_f_of_plr)
    water_heater.setOnCycleParasiticFuelConsumptionRate(on_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOffCycleParasiticFuelConsumptionRate(off_cycle_parasitic_fuel_consumption_rate)
    water_heater.setOnCycleParasiticFuelType('Electricity')
    water_heater.setOffCycleParasiticFuelType('Electricity')
    water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
    water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.ServiceWaterHeating', "#{water_heater_fuel} is not a valid water heater fuel.  Valid choices are NaturalGas, Electricity, and HeatPump.")
  end

  # set water temperature properties
  water_heater.setDeadbandTemperatureDifference(2.0)
  water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
  water_heater.setHeaterControlType('Cycle')
  water_heater.setOffCycleParasiticHeatFractiontoTank(0.8)
  water_heater.setIndirectWaterHeatingRecoveryTime(1.5) # 1.5hrs

  # create service water temperature schedule based on the service_water_temperature if none provided
  if service_water_temperature_schedule.nil?
    swh_temp_c = service_water_temperature
    swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
    service_water_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                         swh_temp_c,
                                                                                                         name: "Service Water Loop Temp - #{swh_temp_f.round}F",
                                                                                                         schedule_type_limit: 'Temperature')
  end
  water_heater.setMaximumTemperatureLimit(service_water_temperature)
  water_heater.setSetpointTemperatureSchedule(service_water_temperature_schedule)

  # set peak flow rate characteristics
  if set_peak_use_flowrate
    water_heater.setPeakUseFlowRate(peak_flowrate) unless peak_flowrate.nil?
    water_heater.setUseFlowRateFractionSchedule(flowrate_schedule) unless flowrate_schedule.nil?
  end

  # set the water heater ambient conditions
  if water_heater_thermal_zone.nil?
    # assume the water heater is indoors at 71.6F / 22C
    indoor_temp_f = 71.6
    indoor_temp_c = OpenStudio.convert(indoor_temp_f, 'F', 'C').get
    default_water_heater_ambient_temp_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                            indoor_temp_c,
                                                                                                            name: "Water Heater Ambient Temp Schedule #{indoor_temp_f}F",
                                                                                                            schedule_type_limit: 'Temperature')
    water_heater.setAmbientTemperatureIndicator('Schedule')
    water_heater.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
    water_heater.resetAmbientTemperatureThermalZone
  else
    water_heater.setAmbientTemperatureIndicator('ThermalZone')
    water_heater.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
    water_heater.resetAmbientTemperatureSchedule
  end

  # assign a quantity to the water heater if it represents multiple water heaters
  if number_of_water_heaters > 1
    water_heater.setName("#{number_of_water_heaters}X #{(water_heater_volume_gal / number_of_water_heaters).round}gal #{water_heater_fuel} Water Heater - #{(water_heater_capacity_kbtu_per_hr / number_of_water_heaters).round}kBtu/hr")
    water_heater.additionalProperties.setFeature('component_quantity', number_of_water_heaters)
  else
    water_heater.setName("#{water_heater_volume_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
  end

  # add the water heater to the service water loop if provided
  unless service_water_loop.nil?
    service_water_loop.addSupplyBranchForComponent(water_heater)
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added water heater called #{water_heater.name}")

  return water_heater
end
create_water_use(model, name: 'Main Water Use', flow_rate: 0.0, flow_rate_fraction_schedule: nil, water_use_temperature: 43.3, water_use_temperature_schedule: nil, sensible_fraction: 0.2, latent_fraction: 0.05, service_water_loop: nil, space: nil) click to toggle source

Creates a water use and attaches it to a service water loop and a space, if provided

@param model [OpenStudio::Model::Model] OpenStudio model object @param name [String] Use name of the water use object, e.g. main or laundry @param flow_rate [Double] the peak flow rate of the water use in m^3/s @param flow_rate_fraction_schedule [OpenStudio::Model::Schedule] the flow rate fraction schedule @param water_use_temperature [Double] mixed water use temperature at the fixture, in degrees C. Default is 43.3 C / 110 F. @param water_use_temperature_schedule [OpenStudio::Model::Schedule] water use temperature schedule.

If nil, will be defaulted to a constant temperature schedule based on the water_use_temperature

@param sensible_fraction [Double] the water use equipment sensible fraction to the space @param latent_fraction [Double] the water use equipment latent fraction to the space @param service_water_loop [OpenStudio::Model::PlantLoop] if provided, add the water use fixture to this loop @param space [OpenStudio::Model::Space] OpenStudio Space object @return [OpenStudio::Model::WaterUseEquipment] OpenStudio WaterUseEquipment object

# File lib/openstudio-standards/service_water_heating/create_water_use.rb, line 21
def self.create_water_use(model,
                          name: 'Main Water Use',
                          flow_rate: 0.0,
                          flow_rate_fraction_schedule: nil,
                          water_use_temperature: 43.3,
                          water_use_temperature_schedule: nil,
                          sensible_fraction: 0.2,
                          latent_fraction: 0.05,
                          service_water_loop: nil,
                          space: nil)
  # IP conversions for naming
  flow_rate_gpm = OpenStudio.convert(flow_rate, 'm^3/s', 'gal/min').get
  water_use_temperature_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get

  # default name
  name = 'Main Water Use' if name.nil?

  # water use definition
  water_use_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)

  # set sensible and latent fractions
  water_use_sensible_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                sensible_fraction,
                                                                                                name: "Fraction Sensible - #{sensible_fraction}",
                                                                                                schedule_type_limit: 'Fractional')
  water_use_latent_frac_sch = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                              latent_fraction,
                                                                                              name: "Fraction Latent - #{latent_fraction}",
                                                                                              schedule_type_limit: 'Fractional')
  water_use_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
  water_use_def.setLatentFractionSchedule(water_use_latent_frac_sch)
  water_use_def.setPeakFlowRate(flow_rate)
  water_use_def.setName("#{name} #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F")

  # target mixed water temperature
  if water_use_temperature_schedule.nil?
    water_use_temperature_schedule = OpenstudioStandards::Schedules.create_constant_schedule_ruleset(model,
                                                                                                     water_use_temperature,
                                                                                                     name: "Mixed Water At Faucet Temp - #{water_use_temperature_f.round}F",
                                                                                                     schedule_type_limit: 'Temperature')
  end
  water_use_def.setTargetTemperatureSchedule(water_use_temperature_schedule)

  # create water use equipment
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_use_def)
  water_fixture.setFlowRateFractionSchedule(flow_rate_fraction_schedule)

  # create water use connection
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
  swh_connection.addWaterUseEquipment(water_fixture)

  # add to the space if provided
  if space.nil?
    water_fixture.setName("#{name} Service Water Use #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F")
    swh_connection.setName("#{name} WUC #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F")
  else
    water_fixture.setName("#{space.name} Service Water Use #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F")
    swh_connection.setName("#{space.name} WUC #{flow_rate_gpm.round(2)}gpm #{water_use_temperature_f.round}F")
    water_fixture.setSpace(space)
  end

  # add to the service water loop if provided
  unless service_water_loop.nil?
    service_water_loop.addDemandBranchForComponent(swh_connection)
    OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Adding water fixture to #{service_water_loop.name}.")
  end

  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ServiceWaterHeating', "Added #{water_fixture.name}.")

  return water_fixture
end