openstudio-standards 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/data/geometry/ASHRAEPrimarySchool.osm +36 -2
  3. data/data/geometry/ASHRAESecondarySchool.osm +19 -2
  4. data/data/standards/OpenStudio_Standards_elevators.json +10756 -0
  5. data/lib/openstudio-standards.rb +0 -2
  6. data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +36 -0
  7. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
  8. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.elevators.rb +175 -164
  9. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +268 -476
  10. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +625 -116
  11. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -0
  12. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +2 -19
  13. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +112 -68
  14. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +10 -2
  15. data/lib/openstudio-standards/standards/necb/necb_2011/data/space_types.json +224 -224
  16. data/lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb +8 -16
  17. data/lib/openstudio-standards/standards/necb/necb_2015/data/space_types.json +318 -318
  18. data/lib/openstudio-standards/standards/standard.rb +1 -0
  19. data/lib/openstudio-standards/version.rb +1 -1
  20. metadata +4 -4
  21. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.hvac_systems.rb +0 -15
  22. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.hvac_systems.rb +0 -15
@@ -5,18 +5,21 @@ class Standard
5
5
  #
6
6
  # @param system_name [String] the name of the system, or nil in which case it will be defaulted
7
7
  # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
8
- # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
8
+ # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
9
9
  # @param service_water_temperature [Double] service water temperature, in C
10
10
  # @param service_water_pump_head [Double] service water pump head, in Pa
11
- # @param service_water_pump_motor_efficiency [Double]
12
- # service water pump motor efficiency, as decimal.
11
+ # @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal.
13
12
  # @param water_heater_capacity [Double] water heater heating capacity, in W
14
13
  # @param water_heater_volume [Double] water heater volume, in m^3
15
- # @param water_heater_fuel [String] water heater fuel.
16
- # Valid choices are Natural Gas, Electricity
17
- # @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption
18
- # rate of the water heater, in W
19
- # @param building_type [String] the building type
14
+ # @param water_heater_fuel [String] water heater fuel. Valid choices are NaturalGas, Electricity
15
+ # @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption rate of the water heater, in W
16
+ # @param add_pipe_losses [Bool] if true, add piping and associated heat losses to system. If false, add no pipe heat losses
17
+ # @param floor_area_served [Double] area served by the SWH loop, in m^2. Used for pipe loss piping length estimation
18
+ # @param number_of_stories [Integer] number of stories served by the SWH loop. Used for pipe loss piping length estimation
19
+ # @param pipe_insulation_thickness [Double] thickness of the fiberglass batt pipe insulation, in m. Use 0 for uninsulated pipes
20
+ # @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
21
+ # Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
22
+ # lots of individual water heaters (for runtime sake).
20
23
  # @return [OpenStudio::Model::PlantLoop]
21
24
  # the resulting service water loop.
22
25
  def model_add_swh_loop(model,
@@ -29,14 +32,17 @@ class Standard
29
32
  water_heater_volume,
30
33
  water_heater_fuel,
31
34
  parasitic_fuel_consumption_rate,
32
- building_type = nil)
33
-
34
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding service water loop')
35
+ add_pipe_losses = false,
36
+ floor_area_served = 465,
37
+ number_of_stories = 1,
38
+ pipe_insulation_thickness = 0.0127, # 1/2in
39
+ number_water_heaters = 1)
40
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_swh_loop, number_water_heaters = #{number_water_heaters}")
35
41
 
36
42
  # Service water heating loop
37
43
  service_water_loop = OpenStudio::Model::PlantLoop.new(model)
38
- service_water_loop.setMinimumLoopTemperature(10)
39
- service_water_loop.setMaximumLoopTemperature(60)
44
+ service_water_loop.setMinimumLoopTemperature(10.0)
45
+ service_water_loop.setMaximumLoopTemperature(60.0)
40
46
 
41
47
  if system_name.nil?
42
48
  service_water_loop.setName('Service Water Loop')
@@ -45,17 +51,17 @@ class Standard
45
51
  end
46
52
 
47
53
  # Temperature schedule type limits
48
- temp_sch_type_limits = model_add_schedule_type_limits(model,
49
- name: "Temperature Schedule Type Limits",
50
- lower_limit_value: 0.0,
51
- upper_limit_value: 100.0,
52
- numeric_type: "Continuous",
53
- unit_type: "Temperature")
54
+ temp_sch_type_limits = model_add_schedule_type_limits(model,
55
+ name: 'Temperature Schedule Type Limits',
56
+ lower_limit_value: 0.0,
57
+ upper_limit_value: 100.0,
58
+ numeric_type: 'Continuous',
59
+ unit_type: 'Temperature')
54
60
 
55
61
  # Service water heating loop controls
56
62
  swh_temp_c = service_water_temperature
57
63
  swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
58
- swh_delta_t_r = 9 # 9F delta-T
64
+ swh_delta_t_r = 9.0 # 9F delta-T
59
65
  swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
60
66
  swh_temp_sch = model_add_constant_schedule_ruleset(model,
61
67
  swh_temp_c,
@@ -69,25 +75,28 @@ class Standard
69
75
  sizing_plant.setDesignLoopExitTemperature(swh_temp_c)
70
76
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
71
77
 
72
- # Service water heating pump
78
+ # Determine if circulating or non-circulating based on supplied head pressure
73
79
  swh_pump_head_press_pa = service_water_pump_head
74
- swh_pump_motor_efficiency = service_water_pump_motor_efficiency
75
- if swh_pump_head_press_pa.nil?
80
+ circulating = true
81
+ if swh_pump_head_press_pa.nil? || swh_pump_head_press_pa <= 1
76
82
  # As if there is no circulation pump
77
83
  swh_pump_head_press_pa = 0.001
78
- swh_pump_motor_efficiency = 1
84
+ service_water_pump_motor_efficiency = 1
85
+ circulating = false
79
86
  end
80
87
 
81
- swh_pump = case model_swh_pump_type(model, building_type)
82
- when 'ConstantSpeed'
83
- OpenStudio::Model::PumpConstantSpeed.new(model)
84
- when 'VariableSpeed'
85
- OpenStudio::Model::PumpVariableSpeed.new(model)
86
- end
87
- swh_pump.setName('Service Water Loop Pump')
88
+ # Service water heating pump
89
+ if circulating
90
+ swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
91
+ swh_pump.setName("#{service_water_loop.name} Circulator Pump")
92
+ swh_pump.setPumpControlType('Intermittent')
93
+ else
94
+ swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
95
+ swh_pump.setName("#{service_water_loop.name} Water Mains Pressure Driven")
96
+ swh_pump.setPumpControlType('Continuous')
97
+ end
88
98
  swh_pump.setRatedPumpHead(swh_pump_head_press_pa.to_f)
89
- swh_pump.setMotorEfficiency(swh_pump_motor_efficiency)
90
- swh_pump.setPumpControlType('Intermittent')
99
+ swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency)
91
100
  swh_pump.addToNode(service_water_loop.supplyInletNode)
92
101
 
93
102
  water_heater = model_add_water_heater(model,
@@ -100,10 +109,21 @@ class Standard
100
109
  false,
101
110
  0.0,
102
111
  nil,
103
- water_heater_thermal_zone)
112
+ water_heater_thermal_zone,
113
+ number_water_heaters)
104
114
 
105
115
  service_water_loop.addSupplyBranchForComponent(water_heater)
106
116
 
117
+ # Pipe losses
118
+ if add_pipe_losses
119
+ model_add_piping_losses_to_swh_system(model,
120
+ service_water_loop,
121
+ circulating,
122
+ pipe_insulation_thickness: pipe_insulation_thickness,
123
+ floor_area_served: floor_area_served,
124
+ number_of_stories: number_of_stories)
125
+ end
126
+
107
127
  # Service water heating loop bypass pipes
108
128
  water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
109
129
  service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
@@ -111,40 +131,35 @@ class Standard
111
131
  service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
112
132
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
113
133
  supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode)
114
- demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
115
- demand_inlet_pipe.addToNode(service_water_loop.demandInletNode)
116
134
  demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
117
135
  demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode)
118
136
 
119
- return service_water_loop
120
- end
137
+ if circulating
138
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added circulating SWH loop called #{service_water_loop.name}")
139
+ else
140
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added non-circulating SWH loop called #{service_water_loop.name}")
141
+ end
121
142
 
122
- # Determine the type of SWH pump that a model will have. Defaults to ConstantSpeed.
123
- # @return [String] the SWH pump type: ConstantSpeed, VariableSpeed
124
- def model_swh_pump_type(model, building_type)
125
- swh_pump_type = 'ConstantSpeed'
126
- return swh_pump_type
143
+ return service_water_loop
127
144
  end
128
145
 
129
146
  # Creates a water heater and attaches it to the supplied service water heating loop.
130
147
  #
131
148
  # @param water_heater_capacity [Double] water heater capacity, in W
132
149
  # @param water_heater_volume [Double] water heater volume, in m^3
133
- # @param water_heater_fuel [Double] valid choices are
134
- # Natural Gas, Electricity
150
+ # @param water_heater_fuel [Double] valid choices are NaturalGas, Electricity
135
151
  # @param service_water_temperature [Double] water heater temperature, in C
136
- # @param parasitic_fuel_consumption_rate [Double] water heater parasitic
137
- # fuel consumption rate, in W
138
- # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating
139
- # schedule. If nil, will be defaulted.
140
- # @param set_peak_use_flowrate [Bool] if true, the peak flow rate
141
- # and flow rate schedule will be set.
152
+ # @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
153
+ # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
154
+ # @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
142
155
  # @param peak_flowrate [Double] in m^3/s
143
156
  # @param flowrate_schedule [String] name of the flow rate schedule
144
- # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
145
- # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
146
- # @return [OpenStudio::Model::WaterHeaterMixed]
147
- # the resulting water heater.
157
+ # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
158
+ # If nil, will be assumed in 70F air for heat loss.
159
+ # @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
160
+ # Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
161
+ # lots of individual water heaters (for runtime sake).
162
+ # @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
148
163
  def model_add_water_heater(model,
149
164
  water_heater_capacity,
150
165
  water_heater_volume,
@@ -155,10 +170,9 @@ class Standard
155
170
  set_peak_use_flowrate,
156
171
  peak_flowrate,
157
172
  flowrate_schedule,
158
- water_heater_thermal_zone)
159
-
160
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding water heater')
161
-
173
+ water_heater_thermal_zone,
174
+ number_water_heaters)
175
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_water_heater, number_water_heaters = #{number_water_heaters}")
162
176
  # Water heater
163
177
  # TODO Standards - Change water heater methodology to follow
164
178
  # 'Model Enhancements Appendix A.'
@@ -168,11 +182,11 @@ class Standard
168
182
 
169
183
  # Temperature schedule type limits
170
184
  temp_sch_type_limits = model_add_schedule_type_limits(model,
171
- name: "Temperature Schedule Type Limits",
185
+ name: 'Temperature Schedule Type Limits',
172
186
  lower_limit_value: 0.0,
173
187
  upper_limit_value: 100.0,
174
- numeric_type: "Continuous",
175
- unit_type: "Temperature")
188
+ numeric_type: 'Continuous',
189
+ unit_type: 'Temperature')
176
190
 
177
191
  if swh_temp_sch.nil?
178
192
  # Service water heating loop controls
@@ -189,9 +203,18 @@ class Standard
189
203
 
190
204
  # Water heater depends on the fuel type
191
205
  water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)
192
- water_heater.setName("#{water_heater_vol_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
206
+
207
+ # Assign a quantity to the water heater if it represents multiple water heaters
208
+ if number_water_heaters > 1
209
+ water_heater.setName("#{number_water_heaters}X #{(water_heater_vol_gal/number_water_heaters).round}gal #{water_heater_fuel} Water Heater - #{(water_heater_capacity_kbtu_per_hr/number_water_heaters).round}kBtu/hr")
210
+ water_heater.set_component_quantity(number_water_heaters)
211
+ else
212
+ water_heater.setName("#{water_heater_vol_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
213
+ end
214
+
193
215
  water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
194
216
  water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
217
+ water_heater.setDeadbandTemperatureDifference(2.0)
195
218
 
196
219
  if water_heater_thermal_zone.nil?
197
220
  # Assume the water heater is indoors at 70F or 72F
@@ -214,7 +237,7 @@ class Standard
214
237
  water_heater.resetAmbientTemperatureSchedule
215
238
  end
216
239
 
217
- water_heater.setMaximumTemperatureLimit(OpenStudio.convert(180, 'F', 'C').get)
240
+ water_heater.setMaximumTemperatureLimit(service_water_temperature)
218
241
  water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
219
242
  water_heater.setHeaterControlType('Cycle')
220
243
  water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
@@ -229,7 +252,7 @@ class Standard
229
252
  water_heater.setOnCycleParasiticFuelType('Electricity')
230
253
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
231
254
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
232
- elsif water_heater_fuel == 'Natural Gas'
255
+ elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
233
256
  water_heater.setHeaterFuelType('Gas')
234
257
  water_heater.setHeaterThermalEfficiency(0.78)
235
258
  water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
@@ -238,6 +261,34 @@ class Standard
238
261
  water_heater.setOnCycleParasiticFuelType('Gas')
239
262
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
240
263
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
264
+ elsif water_heater_fuel == 'HeatPump'
265
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', 'Simple but crappy workaround to represent heat pump water heaters without incurring significant runtime penalty associated with using correct objects.')
266
+ # Make a part-load efficiency modifier curve with a value above 1, which
267
+ # is multiplied by the nominal efficiency of 100% to represent
268
+ # the COP of a HPWH.
269
+ # TODO could make this workaround better by using EMS
270
+ # to modify this curve output in realtime based on
271
+ # the OA temperature.
272
+ hpwh_cop = 2.8
273
+ eff_f_of_plr = OpenStudio::Model::CurveCubic.new(model)
274
+ eff_f_of_plr.setName("HPWH_COP_#{hpwh_cop}")
275
+ eff_f_of_plr.setCoefficient1Constant(hpwh_cop)
276
+ eff_f_of_plr.setCoefficient2x(0.0)
277
+ eff_f_of_plr.setCoefficient3xPOW2(0.0)
278
+ eff_f_of_plr.setCoefficient4xPOW3(0.0)
279
+ eff_f_of_plr.setMinimumValueofx(0.0)
280
+ eff_f_of_plr.setMaximumValueofx(1.0)
281
+ water_heater.setHeaterFuelType('Electricity')
282
+ water_heater.setHeaterThermalEfficiency(1.0)
283
+ water_heater.setPartLoadFactorCurve(eff_f_of_plr)
284
+ water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
285
+ water_heater.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
286
+ water_heater.setOffCycleParasiticFuelType('Electricity')
287
+ water_heater.setOnCycleParasiticFuelType('Electricity')
288
+ water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
289
+ water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
290
+ else
291
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "#{water_heater_fuel} is not a valid water heater fuel. Valid choices are Electricity, NaturalGas, and HeatPump.")
241
292
  end
242
293
 
243
294
  if set_peak_use_flowrate
@@ -249,9 +300,284 @@ class Standard
249
300
  water_heater.setUseFlowRateFractionSchedule(schedule)
250
301
  end
251
302
 
303
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added water heater called #{water_heater.name}")
304
+
252
305
  return water_heater
253
306
  end
254
307
 
308
+ # Creates a heatpump water heater and attaches it to the supplied service water heating loop.
309
+ #
310
+ # @param water_heater_capacity [Double] water heater capacity, in W
311
+ # @param water_heater_volume [Double] water heater volume, in m^3
312
+ # @param service_water_temperature [Double] water heater temperature, in C
313
+ # @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
314
+ # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
315
+ # @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
316
+ # @param peak_flowrate [Double] in m^3/s
317
+ # @param flowrate_schedule [String] name of the flow rate schedule
318
+ # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
319
+ # If nil, will be assumed in 70F air for heat loss.
320
+ # @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
321
+ def model_add_heatpump_water_heater(model,
322
+ type: 'PumpedCondenser',
323
+ water_heater_capacity: 500,
324
+ electric_backup_capacity: 4500,
325
+ water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get,
326
+ service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get,
327
+ parasitic_fuel_consumption_rate: 3.0,
328
+ swh_temp_sch: nil,
329
+ cop: 2.8,
330
+ shr: 0.88,
331
+ tank_ua: 3.9,
332
+ set_peak_use_flowrate: false,
333
+ peak_flowrate: 0.0,
334
+ flowrate_schedule: nil,
335
+ water_heater_thermal_zone: nil)
336
+
337
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding heat pump water heater')
338
+
339
+ # create heat pump water heater
340
+ if type == 'WrappedCondenser'
341
+ hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model)
342
+ elsif type == 'PumpedCondenser'
343
+ hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model)
344
+ end
345
+
346
+ # calculate tank height and radius
347
+ water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
348
+ hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
349
+ 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
350
+ tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5
351
+ tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height)
352
+ u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get
353
+ hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr")
354
+
355
+ if type == 'WrappedCondenser'
356
+ hpwh.setMinimumInletAirTemperatureforCompressorOperation(OpenStudio.convert(45.0, 'F', 'C').get)
357
+ hpwh.setMaximumInletAirTemperatureforCompressorOperation(OpenStudio.convert(120.0, 'F', 'C').get)
358
+ # set sensor heights
359
+ if hpwh_vol_gal <= 50.0
360
+ hpwh.setDeadBandTemperatureDifference(0.5)
361
+ h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top)
362
+ h_LE = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top)
363
+ h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
364
+ h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank
365
+ h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
366
+ hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl)
367
+ hpwh.setControlSensor1Weight(1.0)
368
+ hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl)
369
+ else
370
+ hpwh.setDeadBandTemperatureDifference(3.89)
371
+ h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top)
372
+ h_LE = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top)
373
+ h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
374
+ h_condbot = 0.01 # bottom node
375
+ h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
376
+ h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank
377
+ hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up)
378
+ hpwh.setControlSensor1Weight(0.75)
379
+ hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low)
380
+ end
381
+ hpwh.setCondenserBottomLocation(h_condbot)
382
+ hpwh.setCondenserTopLocation(h_condtop)
383
+ hpwh.setTankElementControlLogic('MutuallyExclusive')
384
+ elsif type == 'PumpedCondenser'
385
+ hpwh.setDeadBandTemperatureDifference(3.89)
386
+ end
387
+
388
+ # set heat pump water heater properties
389
+ hpwh.setEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
390
+ hpwh.setFanPlacement('DrawThrough')
391
+ hpwh.setOnCycleParasiticElectricLoad(0.0)
392
+ hpwh.setOffCycleParasiticElectricLoad(0.0)
393
+ hpwh.setParasiticHeatRejectionLocation('Outdoors')
394
+
395
+ # set temperature setpoint schedule
396
+ if swh_temp_sch.nil?
397
+ # temperature schedule type limits
398
+ temp_sch_type_limits = model_add_schedule_type_limits(model,
399
+ name: 'Temperature Schedule Type Limits',
400
+ lower_limit_value: 0.0,
401
+ upper_limit_value: 100.0,
402
+ numeric_type: 'Continuous',
403
+ unit_type: 'Temperature')
404
+ # service water heating loop controls
405
+ swh_temp_c = service_water_temperature
406
+ swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
407
+ swh_delta_t_r = 9.0 # 9F delta-T
408
+ swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get
409
+ swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
410
+ swh_temp_sch = model_add_constant_schedule_ruleset(model,
411
+ swh_temp_c,
412
+ name = "Heat Pump Water Heater Temp - #{swh_temp_f.round}F")
413
+ swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
414
+ end
415
+ hpwh.setCompressorSetpointTemperatureSchedule(swh_temp_sch)
416
+
417
+ # coil curves
418
+ hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model)
419
+ hpwh_cap.setName('HPWH-Cap-fT')
420
+ hpwh_cap.setCoefficient1Constant(0.563)
421
+ hpwh_cap.setCoefficient2x(0.0437)
422
+ hpwh_cap.setCoefficient3xPOW2(0.000039)
423
+ hpwh_cap.setCoefficient4y(0.0055)
424
+ hpwh_cap.setCoefficient5yPOW2(-0.000148)
425
+ hpwh_cap.setCoefficient6xTIMESY(-0.000145)
426
+ hpwh_cap.setMinimumValueofx(0.0)
427
+ hpwh_cap.setMaximumValueofx(100.0)
428
+ hpwh_cap.setMinimumValueofy(0.0)
429
+ hpwh_cap.setMaximumValueofy(100.0)
430
+
431
+ hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model)
432
+ hpwh_cop.setName('HPWH-COP-fT')
433
+ hpwh_cop.setCoefficient1Constant(1.1332)
434
+ hpwh_cop.setCoefficient2x(0.063)
435
+ hpwh_cop.setCoefficient3xPOW2(-0.0000979)
436
+ hpwh_cop.setCoefficient4y(-0.00972)
437
+ hpwh_cop.setCoefficient5yPOW2(-0.0000214)
438
+ hpwh_cop.setCoefficient6xTIMESY(-0.000686)
439
+ hpwh_cop.setMinimumValueofx(0.0)
440
+ hpwh_cop.setMaximumValueofx(100.0)
441
+ hpwh_cop.setMinimumValueofy(0.0)
442
+ hpwh_cop.setMaximumValueofy(100.0)
443
+
444
+ # create DX coil object
445
+ if type == 'WrappedCondenser'
446
+ coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get
447
+ coil.setRatedCondenserWaterTemperature(48.89)
448
+ elsif type == 'PumpedCondenser'
449
+ coil = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPump.new(model)
450
+ hpwh.setDXCoil(coil)
451
+ end
452
+
453
+ # set coil properties
454
+ coil.setName("#{hpwh.name} Coil")
455
+ coil.setRatedHeatingCapacity(water_heater_capacity * cop)
456
+ coil.setRatedCOP(cop)
457
+ coil.setRatedSensibleHeatRatio(shr)
458
+ coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get)
459
+ coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get)
460
+ coil.setRatedEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
461
+ coil.setEvaporatorFanPowerIncludedinRatedCOP(true)
462
+ coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature')
463
+ coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap)
464
+ coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop)
465
+ coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0)
466
+
467
+ # set tank properties
468
+ if type == 'WrappedCondenser'
469
+ tank = hpwh.tank.to_WaterHeaterStratified.get
470
+ tank.setTankHeight(tank_height)
471
+ tank.setHeaterPriorityControl('MasterSlave')
472
+ if hpwh_vol_gal <= 50.0
473
+ tank.setHeater1DeadbandTemperatureDifference(25.0)
474
+ tank.setHeater2DeadbandTemperatureDifference(30.0)
475
+ else
476
+ tank.setHeater1DeadbandTemperatureDifference(18.5)
477
+ tank.setHeater2DeadbandTemperatureDifference(3.89)
478
+ end
479
+ hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
480
+ hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint")
481
+ hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
482
+ hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint")
483
+ tank.setHeater1Capacity(electric_backup_capacity)
484
+ tank.setHeater1Height(h_UE)
485
+ tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS
486
+ tank.setHeater2Capacity(electric_backup_capacity)
487
+ tank.setHeater2Height(h_LE)
488
+ tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp)
489
+ tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank)
490
+ tank.setNumberofNodes(12)
491
+ tank.setAdditionalDestratificationConductivity(0)
492
+ tank.setNode1AdditionalLossCoefficient(0)
493
+ tank.setNode2AdditionalLossCoefficient(0)
494
+ tank.setNode3AdditionalLossCoefficient(0)
495
+ tank.setNode4AdditionalLossCoefficient(0)
496
+ tank.setNode5AdditionalLossCoefficient(0)
497
+ tank.setNode6AdditionalLossCoefficient(0)
498
+ tank.setNode7AdditionalLossCoefficient(0)
499
+ tank.setNode8AdditionalLossCoefficient(0)
500
+ tank.setNode9AdditionalLossCoefficient(0)
501
+ tank.setNode10AdditionalLossCoefficient(0)
502
+ tank.setNode11AdditionalLossCoefficient(0)
503
+ tank.setNode12AdditionalLossCoefficient(0)
504
+ tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1)
505
+ tank.setSourceSideDesignFlowRate(0)
506
+ tank.setSourceSideFlowControlMode('')
507
+ tank.setSourceSideInletHeight(0)
508
+ tank.setSourceSideOutletHeight(0)
509
+ elsif type == 'PumpedCondenser'
510
+ tank = OpenStudio::Model::WaterHeaterMixed.new(model)
511
+ hpwh.setTank(tank)
512
+ tank.setDeadbandTemperatureDifference(3.89)
513
+ tank.setHeaterControlType('Cycle')
514
+ tank.setHeaterMaximumCapacity(electric_backup_capacity)
515
+ end
516
+ tank.setName("#{hpwh.name} Tank")
517
+ tank.setEndUseSubcategory('Service Hot Water')
518
+ tank.setTankVolume(0.9 * water_heater_volume)
519
+ tank.setMaximumTemperatureLimit(90.0)
520
+ tank.setHeaterFuelType('Electricity')
521
+ tank.setHeaterThermalEfficiency(1.0)
522
+ tank.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
523
+ tank.setOffCycleParasiticFuelType('Electricity')
524
+ tank.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
525
+ tank.setOnCycleParasiticFuelType('Electricity')
526
+
527
+ # set fan properties
528
+ fan = hpwh.fan.to_FanOnOff.get
529
+ fan.setName("#{hpwh.name} Fan")
530
+ fan_power = 0.0462 # watts per cfm
531
+ if hpwh_vol_gal <= 50.0
532
+ fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
533
+ fan.setPressureRise(23.0)
534
+ else
535
+ fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
536
+ fan.setPressureRise(65.0)
537
+ end
538
+ fan.setMaximumFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
539
+ fan.setMotorEfficiency(1.0)
540
+ fan.setMotorInAirstreamFraction(1.0)
541
+ fan.setEndUseSubcategory('Service Hot Water')
542
+
543
+ if water_heater_thermal_zone.nil?
544
+ # add in schedules for Tamb, RHamb, and the compressor
545
+ # assume the water heater is indoors at 70F for now
546
+ default_water_heater_ambient_temp_sch = model_add_constant_schedule_ruleset(model,
547
+ OpenStudio.convert(70.0, 'F', 'C').get,
548
+ name = 'Water Heater Ambient Temp Schedule - 70F')
549
+ default_water_heater_ambient_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
550
+ tank.setAmbientTemperatureIndicator('Schedule')
551
+ tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
552
+ tank.resetAmbientTemperatureThermalZone
553
+ hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model)
554
+ hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule")
555
+ hpwh_rhamb.setValue(0.5)
556
+ hpwh.setInletAirConfiguration('Schedule')
557
+ hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch)
558
+ hpwh.setInletAirHumiditySchedule(hpwh_rhamb)
559
+ hpwh.setCompressorLocation('Schedule')
560
+ hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
561
+ else
562
+ hpwh.addToThermalZone(water_heater_thermal_zone)
563
+ hpwh.setInletAirConfiguration('ZoneAirOnly')
564
+ hpwh.setCompressorLocation('Zone')
565
+ tank.setAmbientTemperatureIndicator('ThermalZone')
566
+ tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
567
+ tank.resetAmbientTemperatureSchedule
568
+ end
569
+
570
+ if set_peak_use_flowrate
571
+ rated_flow_rate_m3_per_s = peak_flowrate
572
+ rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
573
+ tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s)
574
+ schedule = model_add_schedule(model, flowrate_schedule)
575
+ tank.setUseFlowRateFractionSchedule(schedule)
576
+ end
577
+
578
+ return hpwh
579
+ end
580
+
255
581
  # Creates a booster water heater and attaches it
256
582
  # to the supplied service water heating loop.
257
583
  #
@@ -266,7 +592,6 @@ class Standard
266
592
  # fuel consumption rate, in W
267
593
  # @param booster_water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
268
594
  # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
269
- # @param building_type [String] the building type
270
595
  # @return [OpenStudio::Model::PlantLoop]
271
596
  # the resulting booster water loop.
272
597
  def model_add_swh_booster(model,
@@ -276,8 +601,7 @@ class Standard
276
601
  water_heater_fuel,
277
602
  booster_water_temperature,
278
603
  parasitic_fuel_consumption_rate,
279
- booster_water_heater_thermal_zone,
280
- building_type = nil)
604
+ booster_water_heater_thermal_zone)
281
605
 
282
606
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding booster water heater to #{main_service_water_loop.name}")
283
607
 
@@ -311,12 +635,13 @@ class Standard
311
635
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
312
636
 
313
637
  # Booster water heating pump
314
- swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
638
+ swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
315
639
  swh_pump.setName('Booster Water Loop Pump')
316
- swh_pump_head_press_pa = 0.0 # As if there is no circulation pump
317
- swh_pump.setRatedPumpHead(swh_pump_head_press_pa)
640
+ swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump
641
+ swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump
318
642
  swh_pump.setMotorEfficiency(1)
319
- swh_pump.setPumpControlType('Intermittent')
643
+ swh_pump.setPumpControlType('Continuous')
644
+ swh_pump.setMinimumFlowRate(0.0)
320
645
  swh_pump.addToNode(booster_service_water_loop.supplyInletNode)
321
646
 
322
647
  # Water heater
@@ -331,6 +656,8 @@ class Standard
331
656
  water_heater.setName("#{water_heater_vol_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
332
657
  water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
333
658
  water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
659
+ water_heater.setDeadbandTemperatureDifference(2.0)
660
+ water_heater.setEndUseSubcategory('Booster')
334
661
 
335
662
  if booster_water_heater_thermal_zone.nil?
336
663
  # Assume the water heater is indoors at 70F or 72F
@@ -353,7 +680,7 @@ class Standard
353
680
  water_heater.resetAmbientTemperatureSchedule
354
681
  end
355
682
 
356
- water_heater.setMaximumTemperatureLimit(OpenStudio.convert(180, 'F', 'C').get)
683
+ water_heater.setMaximumTemperatureLimit(swh_temp_c)
357
684
  water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
358
685
  water_heater.setHeaterControlType('Cycle')
359
686
  water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
@@ -368,7 +695,7 @@ class Standard
368
695
  water_heater.setOnCycleParasiticFuelType('Electricity')
369
696
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
370
697
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
371
- elsif water_heater_fuel == 'Natural Gas'
698
+ elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
372
699
  water_heater.setHeaterFuelType('Gas')
373
700
  water_heater.setHeaterThermalEfficiency(0.8)
374
701
  water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
@@ -379,15 +706,6 @@ class Standard
379
706
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
380
707
  end
381
708
 
382
- if water_heater_fuel == 'Electricity'
383
- water_heater.setHeaterFuelType('Electricity')
384
- water_heater.setOffCycleParasiticFuelType('Electricity')
385
- water_heater.setOnCycleParasiticFuelType('Electricity')
386
- elsif water_heater_fuel == 'Natural Gas'
387
- water_heater.setHeaterFuelType('Gas')
388
- water_heater.setOffCycleParasiticFuelType('Gas')
389
- water_heater.setOnCycleParasiticFuelType('Gas')
390
- end
391
709
  booster_service_water_loop.addSupplyBranchForComponent(water_heater)
392
710
 
393
711
  # Service water heating loop bypass pipes
@@ -417,6 +735,43 @@ class Standard
417
735
  # the main service water loop.
418
736
  main_service_water_loop.addDemandBranchForComponent(hx)
419
737
 
738
+ # Add a plant component temperature source to the demand outlet
739
+ # of the HX to represent the fact that the water used by the booster
740
+ # would in reality be at the mains temperature.
741
+ mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model)
742
+ mains_src.setName('Mains Water Makeup for SWH Booster')
743
+ mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get)
744
+
745
+ # Mains water temperature sensor
746
+ mains_water_temp_sen = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Mains Water Temperature')
747
+ mains_water_temp_sen.setName('Mains_Water_Temp_Sen')
748
+ mains_water_temp_sen.setKeyName('Environment')
749
+
750
+ # Schedule to actuate
751
+ water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
752
+ water_mains_temp_sch.setName('Mains Water Temperature')
753
+ water_mains_temp_sch.setValue(OpenStudio.convert(50, 'F', 'C').get)
754
+
755
+ # Actuator for mains water temperature schedule
756
+ mains_water_temp_sch_act = OpenStudio::Model::EnergyManagementSystemActuator.new(water_mains_temp_sch, 'Schedule:Constant', 'Schedule Value')
757
+ mains_water_temp_sch_act.setName('Mains_Water_Temp_Act')
758
+
759
+ # Program
760
+ mains_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
761
+ mains_prg.setName('Mains_Water_Prg')
762
+ mains_prg_body = "SET #{mains_water_temp_sch_act.handle} = #{mains_water_temp_sen.handle}"
763
+ mains_prg.setBody(mains_prg_body)
764
+
765
+ # Program Calling Manager
766
+ mains_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
767
+ mains_mgr.setName("Mains_Water_Prg_Mgr")
768
+ mains_mgr.setCallingPoint('BeginTimestepBeforePredictor')
769
+ mains_mgr.addProgram(mains_prg)
770
+
771
+ # Make the plant component use the actuated schedule
772
+ mains_src.setTemperatureSpecificationType('Scheduled')
773
+ mains_src.setSourceTemperatureSchedule(water_mains_temp_sch)
774
+
420
775
  return booster_service_water_loop
421
776
  end
422
777
 
@@ -432,7 +787,6 @@ class Standard
432
787
  # @param water_use_temperature [Double] mixed water use temperature, in C
433
788
  # @param space_name [String] the name of the space to add the water fixture to,
434
789
  # or nil, in which case it will not be assigned to any particular space.
435
- # @param building_type [String] the building type
436
790
  # @return [OpenStudio::Model::WaterUseEquipment]
437
791
  # the resulting water fixture.
438
792
  def model_add_swh_end_uses(model,
@@ -442,10 +796,8 @@ class Standard
442
796
  flowrate_schedule,
443
797
  water_use_temperature,
444
798
  space_name,
445
- building_type = nil)
446
-
447
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
448
-
799
+ frac_sensible: 0.2,
800
+ frac_latent: 0.05)
449
801
  # Water use connection
450
802
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
451
803
 
@@ -453,8 +805,7 @@ class Standard
453
805
  water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
454
806
  rated_flow_rate_m3_per_s = peak_flowrate
455
807
  rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
456
- frac_sensible = 0.2
457
- frac_latent = 0.05
808
+
458
809
  water_use_sensible_frac_sch = model_add_constant_schedule_ruleset(model,
459
810
  frac_sensible,
460
811
  name = "Fraction Sensible - #{frac_sensible}",
@@ -466,7 +817,7 @@ class Standard
466
817
  water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
467
818
  water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
468
819
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
469
- water_fixture_def.setName("#{use_name.capitalize} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gal/min")
820
+ water_fixture_def.setName("#{use_name} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
470
821
  # Target mixed water temperature
471
822
  mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
472
823
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
@@ -480,9 +831,11 @@ class Standard
480
831
  water_fixture.setFlowRateFractionSchedule(schedule)
481
832
 
482
833
  if space_name.nil?
483
- water_fixture.setName("#{use_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
834
+ water_fixture.setName("#{use_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
835
+ swh_connection.setName("#{use_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
484
836
  else
485
- water_fixture.setName("#{space_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
837
+ water_fixture.setName("#{space_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
838
+ swh_connection.setName("#{space_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
486
839
  end
487
840
 
488
841
  unless space_name.nil?
@@ -494,31 +847,69 @@ class Standard
494
847
  swh_connection.addWaterUseEquipment(water_fixture)
495
848
 
496
849
  # Connect the water use connection to the SWH loop
497
- swh_loop.addDemandBranchForComponent(swh_connection)
850
+ unless swh_loop.nil?
851
+ swh_loop.addDemandBranchForComponent(swh_connection)
852
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
853
+ end
854
+
855
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{water_fixture.name}.")
498
856
 
499
857
  return water_fixture
500
858
  end
501
859
 
502
- # This method will add an swh water fixture to the model for the space.
503
- # if the it will return a water fixture object, or NIL if there is no water load at all.
504
- def model_add_swh_end_uses_by_space(model, building_type, climate_zone, swh_loop, space_type_name, space_name, space_multiplier = nil, is_flow_per_area = true)
860
+ # This method will add a swh water fixture to the model for the space.
861
+ # It will return a water fixture object, or NIL if there is no water load at all.
862
+ #
863
+ # Adds a WaterUseEquipment object representing the SWH loads of the supplied Space.
864
+ # Attaches this WaterUseEquipment to the supplied PlantLoop via a new WaterUseConnections object.
865
+ #
866
+ # @param model [OpenStudio::Model::Model] the model
867
+ # @param swh_loop [OpenStudio::Model::PlantLoop] the SWH loop to connect the WaterUseEquipment to
868
+ # @space [OpenStudio::Model::Space] the Space to add a WaterUseEquipment for
869
+ # @space_multiplier [Double] the multiplier to use if the supplied Space actually represents
870
+ # more area than is shown in the model.
871
+ # @param is_flow_per_area [Bool] if true, use the value in the 'service_water_heating_peak_flow_per_area'
872
+ # field of the space_types JSON. If false, use the value in the 'service_water_heating_peak_flow_rate' field.
873
+ # @return [OpenStudio::Model::WaterUseEquipment] the WaterUseEquipment for the
874
+ def model_add_swh_end_uses_by_space(model,
875
+ swh_loop,
876
+ space,
877
+ space_multiplier = 1.0,
878
+ is_flow_per_area = true)
879
+ # SpaceType
880
+ if space.spaceType.empty?
881
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name} does not have a Space Type assigned, cannot add SWH end uses.")
882
+ return nil
883
+ end
884
+ space_type = space.spaceType.get
885
+
886
+ # Standards Building Type
887
+ if space_type.standardsBuildingType.empty?
888
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Building Type assigned, cannot add SWH end uses.")
889
+ return nil
890
+ end
891
+ stds_bldg_type = space_type.standardsBuildingType.get
892
+ building_type = model_get_lookup_name(stds_bldg_type)
893
+
894
+ # Standards Space Type
895
+ if space_type.standardsSpaceType.empty?
896
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Space Type assigned, cannot add SWH end uses.")
897
+ return nil
898
+ end
899
+ stds_spc_type = space_type.standardsSpaceType.get
900
+
505
901
  # find the specific space_type properties from standard.json
506
902
  search_criteria = {
507
903
  'template' => template,
508
904
  'building_type' => building_type,
509
- 'space_type' => space_type_name
905
+ 'space_type' => stds_spc_type
510
906
  }
511
907
  data = model_find_object(standards_data['space_types'], search_criteria)
512
908
  if data.nil?
513
909
  OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "Could not find space type for: #{search_criteria}.")
514
910
  return nil
515
911
  end
516
- space = model.getSpaceByName(space_name)
517
- space = space.get
518
912
  space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
519
- if space_multiplier.nil?
520
- space_multiplier = 1
521
- end
522
913
 
523
914
  # If there is no service hot water load.. Don't bother adding anything.
524
915
  if data['service_water_heating_peak_flow_per_area'].to_f == 0.0 &&
@@ -550,10 +941,10 @@ class Standard
550
941
  water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
551
942
  water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
552
943
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
553
- water_fixture_def.setName("#{space_name.capitalize} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gal/min")
944
+ water_fixture_def.setName("#{space.name.get} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
554
945
  # Target mixed water temperature
555
- mixed_water_temp_c = data['service_water_heating_target_temperature']
556
- mixed_water_temp_f = OpenStudio.convert(mixed_water_temp_c, 'C', 'F').get
946
+ mixed_water_temp_f = data['service_water_heating_target_temperature']
947
+ mixed_water_temp_c = OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get
557
948
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
558
949
  mixed_water_temp_c,
559
950
  name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
@@ -563,7 +954,7 @@ class Standard
563
954
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
564
955
  schedule = model_add_schedule(model, data['service_water_heating_schedule'])
565
956
  water_fixture.setFlowRateFractionSchedule(schedule)
566
- water_fixture.setName("#{space_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
957
+ water_fixture.setName("#{space.name.get} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm")
567
958
  swh_connection.addWaterUseEquipment(water_fixture)
568
959
  # Assign water fixture to a space
569
960
  water_fixture.setSpace(space) if model_attach_water_fixtures_to_spaces?(model)
@@ -586,17 +977,13 @@ class Standard
586
977
  # @param peak_flowrate [Double] in m^3/s
587
978
  # @param flowrate_schedule [String] name of the flow rate schedule
588
979
  # @param water_use_temperature [Double] mixed water use temperature, in C
589
- # @param building_type [String] the building type
590
980
  # @return [OpenStudio::Model::WaterUseEquipment]
591
981
  # the resulting water fixture.
592
982
  def model_add_booster_swh_end_uses(model,
593
983
  swh_booster_loop,
594
984
  peak_flowrate,
595
985
  flowrate_schedule,
596
- water_use_temperature,
597
- building_type = nil)
598
-
599
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
986
+ water_use_temperature)
600
987
 
601
988
  # Water use connection
602
989
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
@@ -605,10 +992,10 @@ class Standard
605
992
  water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
606
993
  rated_flow_rate_m3_per_s = peak_flowrate
607
994
  rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
608
- water_fixture_def.setName("Water Fixture Def - #{rated_flow_rate_gal_per_min} gal/min")
995
+ water_fixture_def.setName("Booster Water Fixture Def - #{rated_flow_rate_gal_per_min.round(2)} gpm")
609
996
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
610
997
  # Target mixed water temperature
611
- mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'F', 'C').get
998
+ mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
612
999
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
613
1000
  OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get,
614
1001
  name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
@@ -616,15 +1003,137 @@ class Standard
616
1003
 
617
1004
  # Water use equipment
618
1005
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
619
- water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min} gal/min at #{mixed_water_temp_f}F")
1006
+ water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min.round(2)} gpm at #{mixed_water_temp_f.round}F")
620
1007
  schedule = model_add_schedule(model, flowrate_schedule)
621
1008
  water_fixture.setFlowRateFractionSchedule(schedule)
622
1009
  swh_connection.addWaterUseEquipment(water_fixture)
623
1010
 
624
1011
  # Connect the water use connection to the SWH loop
625
- swh_booster_loop.addDemandBranchForComponent(swh_connection)
1012
+ unless swh_booster_loop.nil?
1013
+ swh_booster_loop.addDemandBranchForComponent(swh_connection)
1014
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
1015
+ end
626
1016
 
627
1017
  return water_fixture
628
1018
  end
629
1019
 
1020
+ # Adds insulated 0.75in copper piping to the model.
1021
+ # For circulating systems, assume length of piping is proportional
1022
+ # to the area and number of stories in the building.
1023
+ # For non-circulating systems, assume that the water heaters
1024
+ # are close to the point of use.
1025
+ # Assume that piping is located in a zone
1026
+ #
1027
+ # @param model [OpenStudio::Model::Model] the model
1028
+ # @param swh_loop [OpenStudio::Model::PlantLoop] the service water heating loop
1029
+ # @param floor_area_served [Double] the area of building served by the service water heating loop, in m^2
1030
+ # @param number_of_stories [Integer] the number of stories served by the service water heating loop
1031
+ # @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation
1032
+ # @param circulating [Bool] use true for circulating systems, false for non-circulating systems
1033
+ # @param air_temp_surrounding_piping [Double] the temperature of the air surrounding the piping, in C.
1034
+ def model_add_piping_losses_to_swh_system(model,
1035
+ swh_loop,
1036
+ circulating,
1037
+ pipe_insulation_thickness: 0,
1038
+ floor_area_served: 465,
1039
+ number_of_stories: 1,
1040
+ air_temp_surrounding_piping: 21.1111)
1041
+
1042
+ # Estimate pipe length
1043
+ if circulating
1044
+ # For circulating systems, get pipe length based on the size of the building.
1045
+ # Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf
1046
+ floor_area_ft2 = OpenStudio.convert(floor_area_served, 'm^2', 'ft^2').get
1047
+ pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0)))
1048
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "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) ) )")
1049
+ else
1050
+ # For non-circulating systems, assume water heater is close to point of use
1051
+ pipe_length_ft = 20.0
1052
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Pipe length #{pipe_length_ft.round}ft. For non-circulating systems, assume water heater is close to point of use.")
1053
+ end
1054
+
1055
+ # For systems whose water heater object represents multiple pieces
1056
+ # of equipment, multiply the piping length by the number of pieces of equipment.
1057
+ swh_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc|
1058
+ next unless sc.to_WaterHeaterMixed.is_initialized
1059
+ water_heater = sc.to_WaterHeaterMixed.get
1060
+ comp_qty = water_heater.component_quantity
1061
+ if comp_qty > 1
1062
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Piping length has been multiplied by #{comp_qty}X because #{water_heater.name} represents #{comp_qty} pieces of equipment.")
1063
+ pipe_length_ft *= comp_qty
1064
+ break
1065
+ end
1066
+ end
1067
+
1068
+ # Service water heating piping heat loss scheduled air temperature
1069
+ swh_piping_air_temp_c = air_temp_surrounding_piping
1070
+ swh_piping_air_temp_f = OpenStudio.convert(swh_piping_air_temp_c, 'C', 'F').get
1071
+ swh_piping_air_temp_sch = model_add_constant_schedule_ruleset(model,
1072
+ swh_piping_air_temp_c,
1073
+ name = "#{swh_loop.name} Piping Air Temp - #{swh_piping_air_temp_f.round}F")
1074
+
1075
+ # Service water heating piping heat loss scheduled air velocity
1076
+ swh_piping_air_velocity_m_per_s = 0.3
1077
+ swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get
1078
+ swh_piping_air_velocity_sch = model_add_constant_schedule_ruleset(model,
1079
+ swh_piping_air_velocity_m_per_s,
1080
+ name = "#{swh_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph")
1081
+
1082
+ # Material for 3/4in type L (heavy duty) copper pipe
1083
+ copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1084
+ copper_pipe.setName("Copper pipe 0.75in type L")
1085
+ copper_pipe.setRoughness('Smooth')
1086
+ copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get)
1087
+ copper_pipe.setConductivity(386.0)
1088
+ copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get)
1089
+ copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get)
1090
+ copper_pipe.setThermalAbsorptance(0.9) # TODO: find reference for property
1091
+ copper_pipe.setSolarAbsorptance(0.7) # TODO: find reference for property
1092
+ copper_pipe.setVisibleAbsorptance(0.7) # TODO: find reference for property
1093
+
1094
+ # Construction for pipe
1095
+ pipe_construction = OpenStudio::Model::Construction.new(model)
1096
+
1097
+ # Add insulation material to insulated pipe
1098
+ if pipe_insulation_thickness > 0
1099
+ # Material for fiberglass insulation
1100
+ # R-value from Owens-Corning 1/2in fiberglass pipe insulation
1101
+ # https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22
1102
+ # but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air
1103
+ pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get
1104
+ insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1105
+ insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in")
1106
+ insulation.setRoughness('Smooth')
1107
+ insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get)
1108
+ insulation.setConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get)
1109
+ insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get)
1110
+ insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get)
1111
+ insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used
1112
+ insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
1113
+ insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
1114
+
1115
+ pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt")
1116
+ pipe_construction.setLayers([insulation, copper_pipe])
1117
+ else
1118
+ pipe_construction.setName("Uninsulated copper pipe 0.75in type L")
1119
+ pipe_construction.setLayers([copper_pipe])
1120
+ end
1121
+
1122
+ heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model)
1123
+ heat_loss_pipe.setName("#{swh_loop.name} Pipe #{pipe_length_ft}ft")
1124
+ heat_loss_pipe.setEnvironmentType('Schedule')
1125
+ # heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch) # TODO: schedule type registry error for this setter
1126
+ heat_loss_pipe.setPointer(7, swh_piping_air_temp_sch.handle)
1127
+ # heat_loss_pipe.setAmbientAirVelocitySchedule(model.alwaysOffDiscreteSchedule) # TODO: schedule type registry error for this setter
1128
+ heat_loss_pipe.setPointer(8, swh_piping_air_velocity_sch.handle)
1129
+ heat_loss_pipe.setConstruction(pipe_construction)
1130
+ heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get)
1131
+ heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get)
1132
+
1133
+ heat_loss_pipe.addToNode(swh_loop.demandInletNode)
1134
+
1135
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{pipe_length_ft.round}ft of #{pipe_construction.name} losing heat to #{swh_piping_air_temp_f.round}F air to #{swh_loop.name}.")
1136
+ return true
1137
+ end
1138
+
630
1139
  end