Module: OpenstudioStandards::ServiceWaterHeating

Defined in:
lib/openstudio-standards/service_water_heating/create_water_use.rb,
lib/openstudio-standards/service_water_heating/create_water_heater.rb,
lib/openstudio-standards/service_water_heating/create_piping_losses.rb,
lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb

Overview

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

Create Water Use collapse

Create Water Heater collapse

Create Piping Losses collapse

Create Loop collapse

Class Method Details

.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) ⇒ OpenStudio::Model::PlantLoop

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • water_heater_capacity (Double) (defaults to: 8000.0)

    water heater capacity, in W. Defaults to 8 kW / 27.283 kBtu/hr

  • water_heater_volume (Double) (defaults to: OpenStudio.convert(6.0, 'gal', 'm^3').get)

    water heater volume, in m^3. Defaults to 0.0227 m^3 / 6 gal

  • water_heater_fuel (String) (defaults to: 'Electricity')

    water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’.

  • on_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater on cycle parasitic fuel consumption rate, in W

  • off_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater off cycle parasitic fuel consumption rate, in W

  • service_water_temperature (Double) (defaults to: 82.2)

    water heater temperature, in degrees C. Default is 82.2 C / 180 F.

  • service_water_temperature_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the service water heating schedule. If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

  • water_heater_thermal_zone (OpenStudio::Model::ThermalZone) (defaults to: nil)

    Thermal zone for ambient heat loss. If nil, will assume 71.6 F / 22 C ambient air temperature.

  • service_water_loop (OpenStudio::Model::PlantLoop) (defaults to: nil)

    if provided, add the water heater to this loop

Returns:

  • (OpenStudio::Model::PlantLoop)

    The booster water loop OpenStudio PlantLoop object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# 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) ⇒ OpenStudio::Model::WaterHeaterMixed

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • heat_pump_type (String) (defaults to: 'PumpedCondenser')

    valid option are ‘WrappedCondenser’ or ‘PumpedCondenser’ (default). The ‘WrappedCondenser’ uses a WaterHeaterStratified tank, ‘PumpedCondenser’ uses a WaterHeaterMixed tank.

  • water_heater_capacity (Double) (defaults to: 500.0)

    water heater capacity, in W. Defaults to 500 W / 3.41 kBtu/hr

  • water_heater_volume (Double) (defaults to: OpenStudio.convert(80.0, 'gal', 'm^3').get)

    water heater volume, in m^3. Defaults to 0.303 m^3 / 80 gal

  • coefficient_of_performance (Double) (defaults to: 2.8)

    rated coefficient_of_performance

  • electric_backup_capacity (Double) (defaults to: 4500.0)

    electric heating backup capacity, in W. Default is 4500 W.

  • on_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater on cycle parasitic fuel consumption rate, in W

  • off_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater off cycle parasitic fuel consumption rate, in W

  • service_water_temperature (Double) (defaults to: OpenStudio.convert(125.0, 'F', 'C').get)

    water heater temperature, in degrees C. Default is 51.67 C / 125 F.

  • service_water_temperature_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the service water heating schedule. If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

  • set_peak_use_flowrate (Boolean) (defaults to: false)

    if true, the peak flow rate and flow rate schedule will be set.

  • peak_flowrate (Double) (defaults to: nil)

    peak flow rate in m^3/s

  • flowrate_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the flow rate fraction schedule

  • water_heater_thermal_zone (OpenStudio::Model::ThermalZone) (defaults to: nil)

    Thermal zone for ambient heat loss. If nil, will assume 71.6 F / 22 C ambient air temperature.

  • service_water_loop (OpenStudio::Model::PlantLoop) (defaults to: nil)

    if provided, add the water heater to this loop

  • use_ems_control (Boolean) (defaults to: false)

    if true, use ems control logic if using a ‘WrappedCondenser’ style HPWH.

Returns:

  • (OpenStudio::Model::WaterHeaterMixed)

    OpenStudio WaterHeaterMixed object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# 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) ⇒ OpenStudio::Model::PlantLoop

Creates a service water heating loop.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • system_name (String) (defaults to: 'Service Water Loop')

    the name of the system. nil results in the default.

  • service_water_temperature (Double) (defaults to: 60.0)

    water heater temperature, in degrees C. Default is 60 C / 140 F.

  • service_water_pump_head (Double) (defaults to: 29861.0)

    service water pump head, in Pa. Default is 29861 Pa / 10 ft.

  • service_water_pump_motor_efficiency (Double) (defaults to: 0.3)

    service water pump motor efficiency, as decimal.

  • water_heater_capacity (Double) (defaults to: nil)

    water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr

  • water_heater_volume (Double) (defaults to: nil)

    water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal

  • water_heater_fuel (String) (defaults to: 'Electricity')

    water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, ‘FuelOilNo2’, ‘SimpleHeatPump’, ‘HeatPump’, or ‘None’. If ‘None’, no water heater will be added.

  • on_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater on cycle parasitic fuel consumption rate, in W

  • off_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater off cycle parasitic fuel consumption rate, in W

  • water_heater_thermal_zone (OpenStudio::Model::ThermalZone) (defaults to: nil)

    Thermal zone for ambient heat loss. If nil, will assume 71.6 F / 22 C ambient air temperature.

  • number_of_water_heaters (Integer) (defaults to: 1)

    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).

  • add_piping_losses (Boolean) (defaults to: false)

    if true, add piping and associated heat losses to system. If false, add no pipe heat losses.

  • pipe_insulation_thickness (Double) (defaults to: 0.0127)

    the thickness of the pipe insulation, in m. Default is 0.0127 m / 0.5 inches.

  • floor_area (Double) (defaults to: nil)

    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.

  • number_of_stories (Integer) (defaults to: nil)

    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.

Returns:

  • (OpenStudio::Model::PlantLoop)

    OpenStudio PlantLoop object of the service water loop



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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) ⇒ Boolean

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.

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • service_water_loop (OpenStudio::Model::PlantLoop)

    the service water heating loop

  • circulating (Boolean) (defaults to: true)

    use true for circulating systems, false for non-circulating systems

  • pipe_insulation_thickness (Double) (defaults to: 0.0)

    the thickness of the pipe insulation, in m. Use 0 for no insulation

  • floor_area (Double) (defaults to: nil)

    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.

  • number_of_stories (Integer) (defaults to: nil)

    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.

  • pipe_length (Double) (defaults to: 6.1)

    the length of the pipe in meters. Default is 6.1 m / 20 ft. Only used if circulating is false.

  • air_temperature (Double) (defaults to: 21.1)

    the temperature of the air surrounding the piping, in C. Default is 21.1 C / 70 F.

Returns:

  • (Boolean)

    returns true if successful, false if not



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# 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) ⇒ OpenStudio::Model::WaterHeaterMixed

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • water_heater_capacity (Double) (defaults to: nil)

    water heater capacity, in W. Defaults to 58.6 kW / 200 kBtu/hr

  • water_heater_volume (Double) (defaults to: nil)

    water heater volume, in m^3. Defaults to 0.378 m^3 / 100 gal

  • water_heater_fuel (String) (defaults to: 'Electricity')

    water heating fuel. Valid choices are ‘NaturalGas’, ‘Electricity’, or ‘HeatPump’

  • on_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater on cycle parasitic fuel consumption rate, in W

  • off_cycle_parasitic_fuel_consumption_rate (Double) (defaults to: 0.0)

    water heater off cycle parasitic fuel consumption rate, in W

  • service_water_temperature (Double) (defaults to: 60.0)

    water heater temperature, in degrees C. Default is 60 C / 140 F.

  • service_water_temperature_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the service water heating schedule. If nil, will be defaulted to a constant temperature schedule based on the service_water_temperature

  • set_peak_use_flowrate (Boolean) (defaults to: false)

    if true, the peak flow rate and flow rate schedule will be set.

  • peak_flowrate (Double) (defaults to: nil)

    peak flow rate in m^3/s

  • flowrate_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the flow rate fraction schedule

  • water_heater_thermal_zone (OpenStudio::Model::ThermalZone) (defaults to: nil)

    Thermal zone for ambient heat loss. If nil, will assume 71.6 F / 22 C ambient air temperature.

  • number_of_water_heaters (Integer) (defaults to: 1)

    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).

  • service_water_loop (OpenStudio::Model::PlantLoop) (defaults to: nil)

    if provided, add the water heater to this loop

Returns:

  • (OpenStudio::Model::WaterHeaterMixed)

    OpenStudio WaterHeaterMixed object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# 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) ⇒ OpenStudio::Model::WaterUseEquipment

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

Parameters:

  • model (OpenStudio::Model::Model)

    OpenStudio model object

  • name (String) (defaults to: 'Main Water Use')

    Use name of the water use object, e.g. main or laundry

  • flow_rate (Double) (defaults to: 0.0)

    the peak flow rate of the water use in m^3/s

  • flow_rate_fraction_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

    the flow rate fraction schedule

  • water_use_temperature (Double) (defaults to: 43.3)

    mixed water use temperature at the fixture, in degrees C. Default is 43.3 C / 110 F.

  • water_use_temperature_schedule (OpenStudio::Model::Schedule) (defaults to: nil)

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

  • sensible_fraction (Double) (defaults to: 0.2)

    the water use equipment sensible fraction to the space

  • latent_fraction (Double) (defaults to: 0.05)

    the water use equipment latent fraction to the space

  • service_water_loop (OpenStudio::Model::PlantLoop) (defaults to: nil)

    if provided, add the water use fixture to this loop

  • space (OpenStudio::Model::Space) (defaults to: nil)

    OpenStudio Space object

Returns:

  • (OpenStudio::Model::WaterUseEquipment)

    OpenStudio WaterUseEquipment object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# 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