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.
- checksums.yaml +4 -4
- data/data/geometry/ASHRAEPrimarySchool.osm +36 -2
- data/data/geometry/ASHRAESecondarySchool.osm +19 -2
- data/data/standards/OpenStudio_Standards_elevators.json +10756 -0
- data/lib/openstudio-standards.rb +0 -2
- data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +36 -0
- data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.elevators.rb +175 -164
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +268 -476
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +625 -116
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +2 -19
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +112 -68
- data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +10 -2
- data/lib/openstudio-standards/standards/necb/necb_2011/data/space_types.json +224 -224
- data/lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb +8 -16
- data/lib/openstudio-standards/standards/necb/necb_2015/data/space_types.json +318 -318
- data/lib/openstudio-standards/standards/standard.rb +1 -0
- data/lib/openstudio-standards/version.rb +1 -1
- metadata +4 -4
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.hvac_systems.rb +0 -15
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.hvac_systems.rb +0 -15
@@ -28,13 +28,12 @@ class Standard
|
|
28
28
|
'Main Service Water Loop',
|
29
29
|
water_heater_zone,
|
30
30
|
OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get,
|
31
|
-
prototype_input['main_service_water_pump_head'],
|
31
|
+
prototype_input['main_service_water_pump_head'].to_f,
|
32
32
|
prototype_input['main_service_water_pump_motor_efficiency'],
|
33
33
|
OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get,
|
34
34
|
OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get,
|
35
35
|
swh_fueltype,
|
36
|
-
OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get
|
37
|
-
building_type)
|
36
|
+
OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get)
|
38
37
|
end
|
39
38
|
|
40
39
|
# Attach the end uses if specified in prototype inputs
|
@@ -52,8 +51,7 @@ class Standard
|
|
52
51
|
OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
|
53
52
|
prototype_input['main_service_water_flowrate_schedule'],
|
54
53
|
OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
|
55
|
-
space_name
|
56
|
-
building_type)
|
54
|
+
space_name)
|
57
55
|
end
|
58
56
|
elsif building_type == 'LargeOfficeDetailed' && template != 'NECB2011'
|
59
57
|
|
@@ -65,8 +63,7 @@ class Standard
|
|
65
63
|
OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
|
66
64
|
prototype_input['main_service_water_flowrate_schedule'],
|
67
65
|
OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
|
68
|
-
space_name
|
69
|
-
building_type)
|
66
|
+
space_name)
|
70
67
|
end
|
71
68
|
elsif building_type == 'RetailStripmall' && template != 'NECB2011'
|
72
69
|
|
@@ -87,13 +84,12 @@ class Standard
|
|
87
84
|
"#{swh_thermal_zone.name} Service Water Loop",
|
88
85
|
swh_thermal_zone,
|
89
86
|
OpenStudio.convert(prototype_input['main_service_water_temperature'], 'F', 'C').get,
|
90
|
-
prototype_input['main_service_water_pump_head'],
|
87
|
+
prototype_input['main_service_water_pump_head'].to_f,
|
91
88
|
prototype_input['main_service_water_pump_motor_efficiency'],
|
92
89
|
OpenStudio.convert(prototype_input['main_water_heater_capacity'], 'Btu/hr', 'W').get,
|
93
90
|
OpenStudio.convert(prototype_input['main_water_heater_volume'], 'gal', 'm^3').get,
|
94
91
|
prototype_input['main_water_heater_fuel'],
|
95
|
-
OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get
|
96
|
-
building_type)
|
92
|
+
OpenStudio.convert(prototype_input['main_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get)
|
97
93
|
|
98
94
|
model_add_swh_end_uses(model,
|
99
95
|
'Main',
|
@@ -101,8 +97,7 @@ class Standard
|
|
101
97
|
rated_flow_rate_m3_per_s,
|
102
98
|
swh_sch_name,
|
103
99
|
OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
|
104
|
-
swh_space_name
|
105
|
-
building_type)
|
100
|
+
swh_space_name)
|
106
101
|
end
|
107
102
|
|
108
103
|
elsif prototype_input['main_service_water_peak_flowrate']
|
@@ -115,8 +110,7 @@ class Standard
|
|
115
110
|
OpenStudio.convert(prototype_input['main_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
|
116
111
|
prototype_input['main_service_water_flowrate_schedule'],
|
117
112
|
OpenStudio.convert(prototype_input['main_water_use_temperature'], 'F', 'C').get,
|
118
|
-
nil
|
119
|
-
building_type)
|
113
|
+
nil)
|
120
114
|
|
121
115
|
else
|
122
116
|
OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.Model', 'Adding shw by space_type_map')
|
@@ -158,11 +152,9 @@ class Standard
|
|
158
152
|
space.multiplier
|
159
153
|
end
|
160
154
|
|
161
|
-
water_fixture = model_add_swh_end_uses_by_space(model,
|
162
|
-
climate_zone,
|
155
|
+
water_fixture = model_add_swh_end_uses_by_space(model,
|
163
156
|
main_swh_loop,
|
164
|
-
|
165
|
-
space_name,
|
157
|
+
space,
|
166
158
|
space_multiplier)
|
167
159
|
unless water_fixture.nil?
|
168
160
|
water_fixtures << water_fixture
|
@@ -187,16 +179,14 @@ class Standard
|
|
187
179
|
prototype_input['booster_water_heater_fuel'],
|
188
180
|
OpenStudio.convert(prototype_input['booster_water_temperature'], 'F', 'C').get,
|
189
181
|
0,
|
190
|
-
nil
|
191
|
-
building_type)
|
182
|
+
nil)
|
192
183
|
|
193
184
|
# Attach the end uses
|
194
185
|
model_add_booster_swh_end_uses(model,
|
195
186
|
swh_booster_loop,
|
196
187
|
OpenStudio.convert(prototype_input['booster_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
|
197
188
|
prototype_input['booster_service_water_flowrate_schedule'],
|
198
|
-
OpenStudio.convert(prototype_input['booster_water_use_temperature'], 'F', 'C').get
|
199
|
-
building_type)
|
189
|
+
OpenStudio.convert(prototype_input['booster_water_use_temperature'], 'F', 'C').get)
|
200
190
|
|
201
191
|
end
|
202
192
|
|
@@ -208,13 +198,12 @@ class Standard
|
|
208
198
|
'Laundry Service Water Loop',
|
209
199
|
nil,
|
210
200
|
OpenStudio.convert(prototype_input['laundry_service_water_temperature'], 'F', 'C').get,
|
211
|
-
prototype_input['laundry_service_water_pump_head'],
|
201
|
+
prototype_input['laundry_service_water_pump_head'].to_f,
|
212
202
|
prototype_input['laundry_service_water_pump_motor_efficiency'],
|
213
203
|
OpenStudio.convert(prototype_input['laundry_water_heater_capacity'], 'Btu/hr', 'W').get,
|
214
204
|
OpenStudio.convert(prototype_input['laundry_water_heater_volume'], 'gal', 'm^3').get,
|
215
205
|
prototype_input['laundry_water_heater_fuel'],
|
216
|
-
OpenStudio.convert(prototype_input['laundry_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get
|
217
|
-
building_type)
|
206
|
+
OpenStudio.convert(prototype_input['laundry_service_water_parasitic_fuel_consumption_rate'], 'Btu/hr', 'W').get)
|
218
207
|
|
219
208
|
# Attach the end uses if specified in prototype inputs
|
220
209
|
model_add_swh_end_uses(model,
|
@@ -223,8 +212,7 @@ class Standard
|
|
223
212
|
OpenStudio.convert(prototype_input['laundry_service_water_peak_flowrate'], 'gal/min', 'm^3/s').get,
|
224
213
|
prototype_input['laundry_service_water_flowrate_schedule'],
|
225
214
|
OpenStudio.convert(prototype_input['laundry_water_use_temperature'], 'F', 'C').get,
|
226
|
-
nil
|
227
|
-
building_type)
|
215
|
+
nil)
|
228
216
|
|
229
217
|
end
|
230
218
|
|
@@ -233,17 +221,18 @@ class Standard
|
|
233
221
|
return true
|
234
222
|
end
|
235
223
|
|
236
|
-
# add swh
|
237
|
-
|
238
224
|
# add typical swh demand and supply to model
|
239
225
|
#
|
240
|
-
# @param
|
241
|
-
#
|
242
|
-
# @param pipe_insul_in [Double]
|
243
|
-
# @param circulating [String] (circulating, noncirculating, nil) nil is smart
|
226
|
+
# @param water_heater_fuel [String] water heater fuel. Valid choices are NaturalGas, Electricity, and HeatPump.
|
227
|
+
# If not supplied, a smart default will be determined based on building type.
|
228
|
+
# @param pipe_insul_in [Double] thickness of the pipe insulation, in inches.
|
229
|
+
# @param circulating [String] whether the (circulating, noncirculating, nil) nil is smart
|
244
230
|
# @return [Array] hot water loops
|
245
231
|
# @todo - add in losses from tank and pipe insulation, etc.
|
246
|
-
def model_add_typical_swh(model,
|
232
|
+
def model_add_typical_swh(model,
|
233
|
+
water_heater_fuel: nil,
|
234
|
+
pipe_insul_in: nil,
|
235
|
+
circulating: nil)
|
247
236
|
# array of hot water loops
|
248
237
|
swh_systems = []
|
249
238
|
|
@@ -253,30 +242,30 @@ class Standard
|
|
253
242
|
# create space type hash (need num_units for MidriseApartment and RetailStripmall)
|
254
243
|
space_type_hash = model_create_space_type_hash(model, trust_effective_num_spaces = false)
|
255
244
|
|
256
|
-
# add temperate schedules to hash so they can be shared across water use equipment
|
257
|
-
water_use_def_schedules = {} # key is temp C value is schedule
|
258
|
-
|
259
245
|
# loop through space types adding demand side of swh
|
260
246
|
model.getSpaceTypes.sort.each do |space_type|
|
261
247
|
next unless space_type.standardsBuildingType.is_initialized
|
262
|
-
next unless space_type.standardsSpaceType.is_initialized
|
263
248
|
next unless space_type_hash.key?(space_type) # this is used for space types without any floor area
|
264
249
|
stds_bldg_type = space_type.standardsBuildingType.get
|
265
|
-
stds_space_type = space_type.standardsSpaceType.get
|
266
250
|
|
267
251
|
# lookup space_type_properties
|
268
252
|
space_type_properties = space_type_get_standards_data(space_type)
|
269
|
-
|
270
|
-
|
253
|
+
peak_flow_rate_gal_per_hr_per_ft2 = space_type_properties['service_water_heating_peak_flow_per_area'].to_f
|
254
|
+
peak_flow_rate_gal_per_hr = space_type_properties['service_water_heating_peak_flow_rate'].to_f
|
255
|
+
swh_system_type = space_type_properties['service_water_heating_system_type']
|
271
256
|
flow_rate_fraction_schedule = model_add_schedule(model, space_type_properties['service_water_heating_schedule'])
|
272
|
-
|
257
|
+
service_water_temperature_f = space_type_properties['service_water_heating_target_temperature'].to_f
|
258
|
+
service_water_temperature_c = OpenStudio.convert(service_water_temperature_f, 'F', 'C').get
|
259
|
+
booster_water_temperature_f = space_type_properties['booster_water_heating_target_temperature'].to_f
|
260
|
+
booster_water_temperature_c = OpenStudio.convert(booster_water_temperature_f, 'F', 'C').get
|
261
|
+
booster_water_heater_fraction = space_type_properties['booster_water_heater_fraction'].to_f
|
273
262
|
service_water_fraction_sensible = space_type_properties['service_water_heating_fraction_sensible']
|
274
263
|
service_water_fraction_latent = space_type_properties['service_water_heating_fraction_latent']
|
275
|
-
|
276
|
-
|
264
|
+
floor_area_m2 = space_type_hash[space_type][:floor_area]
|
265
|
+
floor_area_ft2 = OpenStudio.convert(floor_area_m2, 'm^2', 'ft^2').get
|
277
266
|
|
278
267
|
# next if no service water heating demand
|
279
|
-
next unless
|
268
|
+
next unless peak_flow_rate_gal_per_hr_per_ft2 > 0.0 || peak_flow_rate_gal_per_hr > 0.0
|
280
269
|
|
281
270
|
# If there is no SWH schedule specified, assume
|
282
271
|
# that there should be no SWH consumption for this space type.
|
@@ -285,398 +274,233 @@ class Standard
|
|
285
274
|
flow_rate_fraction_schedule = model.alwaysOffDiscreteSchedule
|
286
275
|
end
|
287
276
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
#
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
if
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
|
306
|
-
name = "#{service_water_fraction_sensible} Fraction"
|
307
|
-
if water_use_def_schedules.key?(name)
|
308
|
-
service_water_fraction_sensible_sch = water_use_def_schedules[name]
|
309
|
-
else
|
310
|
-
service_water_fraction_sensible_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
|
311
|
-
water_use_def_schedules[name] = service_water_fraction_sensible_sch
|
312
|
-
end
|
313
|
-
water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
|
314
|
-
name = "#{service_water_fraction_latent} Fraction"
|
315
|
-
if water_use_def_schedules.key?(name)
|
316
|
-
service_water_fraction_latent_sch = water_use_def_schedules[name]
|
317
|
-
else
|
318
|
-
service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
|
319
|
-
water_use_def_schedules[name] = service_water_fraction_latent_sch
|
320
|
-
end
|
321
|
-
water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
|
322
|
-
|
323
|
-
# add water use equipment, connection, and loop for each unit
|
324
|
-
num_units.times do |i|
|
325
|
-
# add water use equipment
|
326
|
-
water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
|
327
|
-
water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
|
328
|
-
water_use_equip.setName("#{space_type.name} SWH #{i + 1}")
|
329
|
-
|
330
|
-
# add water use connection
|
331
|
-
water_use_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
332
|
-
water_use_connection.addWaterUseEquipment(water_use_equip)
|
333
|
-
water_use_connection.setName("#{space_type.name} WUC #{i + 1}")
|
334
|
-
|
335
|
-
# gather inputs for add_swh_loop
|
336
|
-
# default fuel, capacity, and volume from Table A.1. Water Heating Equipment Enhancements to ASHRAE Standard 90.1 Prototype Building Models
|
337
|
-
# temperature, pump head, motor efficiency, and parasitic load from Prototype Inputs
|
338
|
-
system_name = "#{space_type.name} Service Water Loop #{i + 1}"
|
339
|
-
water_heater_thermal_zone = nil
|
340
|
-
service_water_temperature = service_water_temperature_si
|
341
|
-
service_water_pump_head = 0.01
|
342
|
-
service_water_pump_motor_efficiency = 1.0
|
343
|
-
water_heater_fuel = if fuel.nil?
|
344
|
-
'Electric'
|
345
|
-
else
|
346
|
-
fuel
|
347
|
-
end
|
348
|
-
if stds_bldg_type == 'MidriseApartment'
|
349
|
-
water_heater_capacity = OpenStudio.convert(15.0, 'kBtu/hr', 'W').get
|
350
|
-
water_heater_volume = OpenStudio.convert(50.0, 'gal', 'm^3').get
|
351
|
-
parasitic_fuel_consumption_rate = 0.0 # Prototype inputs has 87.75W but prototype IDF's use 0
|
352
|
-
else # StripMall
|
353
|
-
water_heater_capacity = OpenStudio.convert(12.0, 'kBtu/hr', 'W').get
|
354
|
-
water_heater_volume = OpenStudio.convert(40.0, 'gal', 'm^3').get
|
355
|
-
parasitic_fuel_consumption_rate = 173.0
|
356
|
-
end
|
357
|
-
|
358
|
-
# make loop for each unit and add on water use equipment
|
359
|
-
unit_hot_water_loop = model_add_swh_loop(model,
|
360
|
-
system_name,
|
361
|
-
water_heater_thermal_zone,
|
362
|
-
service_water_temperature,
|
363
|
-
service_water_pump_head,
|
364
|
-
service_water_pump_motor_efficiency,
|
365
|
-
water_heater_capacity,
|
366
|
-
water_heater_volume,
|
367
|
-
water_heater_fuel,
|
368
|
-
parasitic_fuel_consumption_rate,
|
369
|
-
stds_bldg_type)
|
370
|
-
|
371
|
-
# Connect the water use connection to the SWH loop
|
372
|
-
unit_hot_water_loop.addDemandBranchForComponent(water_use_connection)
|
373
|
-
|
374
|
-
# apply efficiency to hot water heater
|
375
|
-
unit_hot_water_loop.supplyComponents.sort.each do |component|
|
376
|
-
next if component.to_WaterHeaterMixed.empty?
|
377
|
-
component = component.to_WaterHeaterMixed.get
|
378
|
-
water_heater_mixed_apply_efficiency(component)
|
379
|
-
end
|
380
|
-
|
381
|
-
# add to list of systems
|
382
|
-
swh_systems << unit_hot_water_loop
|
383
|
-
end
|
384
|
-
|
385
|
-
elsif stds_space_type.include?('Kitchen') || stds_space_type.include?('Laundry')
|
386
|
-
gal_hr_peak_flow_rate = gal_hr_per_area * floor_area_ip
|
387
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding dedicated water heating for #{space_type.name} space type with max flow rate of #{gal_hr_peak_flow_rate.round} gal/hr.")
|
388
|
-
|
389
|
-
# add water use equipment definition
|
390
|
-
water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
|
391
|
-
water_use_equip_def.setName("#{space_type.name} SWH def")
|
392
|
-
peak_flow_rate_si = OpenStudio.convert(gal_hr_peak_flow_rate, 'gal/hr', 'm^3/s').get
|
393
|
-
water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
|
394
|
-
target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
|
395
|
-
name = "#{target_temp} C"
|
396
|
-
if water_use_def_schedules.key?(name)
|
397
|
-
target_temperature_sch = water_use_def_schedules[name]
|
398
|
-
else
|
399
|
-
target_temperature_sch = model_add_constant_schedule_ruleset(model, target_temp, name)
|
400
|
-
water_use_def_schedules[name] = target_temperature_sch
|
401
|
-
end
|
402
|
-
water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
|
403
|
-
name = "#{service_water_fraction_sensible} Fraction"
|
404
|
-
if water_use_def_schedules.key?(name)
|
405
|
-
service_water_fraction_sensible_sch = water_use_def_schedules[name]
|
406
|
-
else
|
407
|
-
service_water_fraction_sensible_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
|
408
|
-
water_use_def_schedules[name] = service_water_fraction_sensible_sch
|
409
|
-
end
|
410
|
-
water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
|
411
|
-
name = "#{service_water_fraction_latent} Fraction"
|
412
|
-
if water_use_def_schedules.key?(name)
|
413
|
-
service_water_fraction_latent_sch = water_use_def_schedules[name]
|
414
|
-
else
|
415
|
-
service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
|
416
|
-
water_use_def_schedules[name] = service_water_fraction_latent_sch
|
417
|
-
end
|
418
|
-
water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
|
419
|
-
|
420
|
-
# add water use equipment
|
421
|
-
water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
|
422
|
-
water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
|
423
|
-
water_use_equip.setName("#{space_type.name} SWH")
|
424
|
-
|
425
|
-
# add water use connection
|
426
|
-
water_use_connection = OpenStudio::Model::WaterUseConnections.new(model)
|
427
|
-
water_use_connection.addWaterUseEquipment(water_use_equip)
|
428
|
-
water_use_connection.setName("#{space_type.name} WUC")
|
429
|
-
|
430
|
-
# gather inputs for add_swh_loop
|
431
|
-
system_name = "#{space_type.name} Service Water Loop"
|
432
|
-
water_heater_thermal_zone = nil
|
433
|
-
water_heater_temp_si = 60.0 # C
|
434
|
-
service_water_pump_head = 0.01
|
435
|
-
service_water_pump_motor_efficiency = 1.0
|
436
|
-
water_heater_fuel = if fuel.nil?
|
437
|
-
'Gas'
|
438
|
-
else
|
439
|
-
fuel
|
440
|
-
end
|
441
|
-
|
442
|
-
# find_water_heater_capacity_volume_and_parasitic
|
443
|
-
water_use_equipment_array = [water_use_equip]
|
444
|
-
water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array)
|
445
|
-
water_heater_capacity = water_heater_sizing[:water_heater_capacity]
|
446
|
-
water_heater_volume = water_heater_sizing[:water_heater_volume]
|
447
|
-
parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]
|
448
|
-
|
449
|
-
# make loop for each unit and add on water use equipment
|
450
|
-
dedicated_hot_water_loop = model_add_swh_loop(model,
|
451
|
-
system_name,
|
452
|
-
water_heater_thermal_zone,
|
453
|
-
water_heater_temp_si,
|
454
|
-
service_water_pump_head,
|
455
|
-
service_water_pump_motor_efficiency,
|
456
|
-
water_heater_capacity,
|
457
|
-
water_heater_volume,
|
458
|
-
water_heater_fuel,
|
459
|
-
parasitic_fuel_consumption_rate,
|
460
|
-
stds_bldg_type)
|
461
|
-
|
462
|
-
# Connect the water use connection to the SWH loop
|
463
|
-
dedicated_hot_water_loop.addDemandBranchForComponent(water_use_connection)
|
464
|
-
|
465
|
-
# find water heater
|
466
|
-
dedicated_hot_water_loop.supplyComponents.sort.each do |component|
|
467
|
-
next if component.to_WaterHeaterMixed.empty?
|
468
|
-
water_heater = component.to_WaterHeaterMixed.get
|
469
|
-
|
470
|
-
# apply efficiency to hot water heater
|
471
|
-
water_heater_mixed_apply_efficiency(water_heater)
|
472
|
-
end
|
473
|
-
|
474
|
-
# add to list of systems
|
475
|
-
swh_systems << dedicated_hot_water_loop
|
476
|
-
|
477
|
-
# add booster to all kitchens except for QuickServiceRestaurant (QuickServiceRestaurant assumed to use chemicals instead of hotter water)
|
478
|
-
# boosters are all 6 gal elec but heating capacity varies from 3 to 19 (kBtu/hr) for prototype buildings
|
479
|
-
if stds_space_type.include?('Kitchen') && stds_bldg_type != 'QuickServiceRestaurant'
|
480
|
-
|
481
|
-
# find_water_heater_capacity_volume_and_parasitic
|
482
|
-
water_use_equipment_array = [water_use_equip]
|
483
|
-
inlet_temp_ip = OpenStudio.convert(service_water_temperature_si, 'C', 'F').get # pre-booster temp
|
484
|
-
outlet_temp_ip = inlet_temp_ip + 40.0
|
485
|
-
peak_flow_fraction = 0.6 # assume 60% of peak for dish washing
|
486
|
-
water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array, pipe_hash = {}, 1.0, 1.0, inlet_temp_ip, outlet_temp_ip, peak_flow_fraction)
|
487
|
-
water_heater_capacity = water_heater_sizing[:water_heater_capacity]
|
488
|
-
|
489
|
-
# gather additional inputs for add_swh_booster
|
490
|
-
water_heater_volume = OpenStudio.convert(6, 'gal', 'm^3').get
|
491
|
-
water_heater_fuel = 'Electric'
|
492
|
-
booster_water_temperature = 82.22 # C
|
493
|
-
parasitic_fuel_consumption_rate = 0.0
|
494
|
-
booster_water_heater_thermal_zone = nil
|
495
|
-
|
496
|
-
# add_swh_booster
|
497
|
-
booster_service_water_loop = model_add_swh_booster(model,
|
498
|
-
dedicated_hot_water_loop,
|
499
|
-
water_heater_capacity,
|
500
|
-
water_heater_volume,
|
501
|
-
water_heater_fuel,
|
502
|
-
booster_water_temperature,
|
503
|
-
parasitic_fuel_consumption_rate,
|
504
|
-
booster_water_heater_thermal_zone,
|
505
|
-
stds_bldg_type)
|
506
|
-
|
507
|
-
# find water heater
|
508
|
-
booster_service_water_loop.supplyComponents.sort.each do |component|
|
509
|
-
next if component.to_WaterHeaterMixed.empty?
|
510
|
-
water_heater = component.to_WaterHeaterMixed.get
|
511
|
-
|
512
|
-
# apply efficiency to hot water heater
|
513
|
-
water_heater_mixed_apply_efficiency(water_heater)
|
514
|
-
end
|
515
|
-
|
516
|
-
# rename booster loop
|
517
|
-
booster_service_water_loop.setName("#{space_type.name} Booster Service Water Loop")
|
518
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding Electric Booster water heater for #{space_type.name} on a loop named #{booster_service_water_loop.name}.")
|
519
|
-
|
520
|
-
end
|
521
|
-
|
522
|
-
else # store water use equip by building type in hash so can add general building type hot water loop
|
277
|
+
# Determine flow rate
|
278
|
+
case swh_system_type
|
279
|
+
when 'One Per Unit'
|
280
|
+
water_heater_fuel = 'Electricity' if water_heater_fuel.nil?
|
281
|
+
num_units = space_type_hash[space_type][:num_units].round # First try number of units
|
282
|
+
num_units = space_type_hash[space_type][:effective_num_spaces].round if num_units.zero? # Fall back on number of spaces
|
283
|
+
peak_flow_rate_gal_per_hr = num_units * peak_flow_rate_gal_per_hr
|
284
|
+
peak_flow_rate_m3_per_s = num_units * OpenStudio.convert(peak_flow_rate_gal_per_hr, 'gal/hr', 'm^3/s').get
|
285
|
+
use_name = "#{space_type.name} #{num_units} units"
|
286
|
+
else
|
287
|
+
# TODO: - add building type or sice specific logic or just assume Gas? (SmallOffice and Warehouse are only non unit prototypes with Electric heating)
|
288
|
+
water_heater_fuel = 'NaturalGas' if water_heater_fuel.nil?
|
289
|
+
num_units = 1
|
290
|
+
peak_flow_rate_gal_per_hr = peak_flow_rate_gal_per_hr_per_ft2 * floor_area_ft2
|
291
|
+
peak_flow_rate_m3_per_s = OpenStudio.convert(peak_flow_rate_gal_per_hr, 'gal/hr', 'm^3/s').get
|
292
|
+
use_name = "#{space_type.name}"
|
293
|
+
end
|
523
294
|
|
524
|
-
|
525
|
-
|
295
|
+
# Split flow rate between main and booster uses if specified
|
296
|
+
booster_water_use_equip = nil
|
297
|
+
if booster_water_heater_fraction > 0.0
|
298
|
+
booster_peak_flow_rate_m3_per_s = peak_flow_rate_m3_per_s * booster_water_heater_fraction
|
299
|
+
peak_flow_rate_m3_per_s -= booster_peak_flow_rate_m3_per_s
|
300
|
+
|
301
|
+
# Add booster water heater equipment and connections
|
302
|
+
booster_water_use_equip = model_add_swh_end_uses(model,
|
303
|
+
"Booster #{use_name}",
|
304
|
+
loop=nil,
|
305
|
+
booster_peak_flow_rate_m3_per_s,
|
306
|
+
flow_rate_fraction_schedule.name.get,
|
307
|
+
booster_water_temperature_c,
|
308
|
+
space_name=nil,
|
309
|
+
frac_sensible: service_water_fraction_sensible,
|
310
|
+
frac_latent: service_water_fraction_latent)
|
311
|
+
end
|
526
312
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
else
|
553
|
-
service_water_fraction_latent_sch = model_add_constant_schedule_ruleset(model, service_water_fraction_sensible, name)
|
554
|
-
water_use_def_schedules[name] = service_water_fraction_latent_sch
|
555
|
-
end
|
556
|
-
water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
|
313
|
+
# Add water use equipment and connections
|
314
|
+
water_use_equip = model_add_swh_end_uses(model,
|
315
|
+
use_name,
|
316
|
+
swh_loop=nil,
|
317
|
+
peak_flow_rate_m3_per_s,
|
318
|
+
flow_rate_fraction_schedule.name.get,
|
319
|
+
service_water_temperature_c,
|
320
|
+
space_name=nil,
|
321
|
+
frac_sensible: service_water_fraction_sensible,
|
322
|
+
frac_latent: service_water_fraction_latent)
|
323
|
+
|
324
|
+
# Water heater sizing
|
325
|
+
case swh_system_type
|
326
|
+
when 'One Per Unit'
|
327
|
+
water_heater_capacity_w = num_units * OpenStudio.convert(20.0, 'kBtu/hr', 'W').get
|
328
|
+
water_heater_volume_m3 = num_units * OpenStudio.convert(50.0, 'gal', 'm^3').get
|
329
|
+
num_water_heaters = num_units
|
330
|
+
else
|
331
|
+
water_use_equips = [water_use_equip]
|
332
|
+
water_use_equips << booster_water_use_equip unless booster_water_use_equip.nil? # Include booster in sizing since flows will be preheated by main water heater
|
333
|
+
water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equips)
|
334
|
+
water_heater_capacity_w = water_heater_sizing[:water_heater_capacity]
|
335
|
+
water_heater_volume_m3 = water_heater_sizing[:water_heater_volume]
|
336
|
+
num_water_heaters = 1
|
337
|
+
end
|
557
338
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
water_use_equip.setName("#{space_type.name} SWH")
|
339
|
+
# Add either a dedicated SWH loop or save to add to shared SWH loop
|
340
|
+
case swh_system_type
|
341
|
+
when 'Shared'
|
562
342
|
|
343
|
+
# Store water use equip by building type to add to shared building hot water loop
|
563
344
|
if water_use_equipment_hash.key?(stds_bldg_type)
|
564
345
|
water_use_equipment_hash[stds_bldg_type] << water_use_equip
|
565
346
|
else
|
566
347
|
water_use_equipment_hash[stds_bldg_type] = [water_use_equip]
|
567
348
|
end
|
568
349
|
|
350
|
+
when 'One Per Unit', 'Dedicated'
|
351
|
+
pipe_insul_in = 0.0 if pipe_insul_in.nil?
|
352
|
+
|
353
|
+
# Add service water loop with water heater
|
354
|
+
swh_loop = model_add_swh_loop(model,
|
355
|
+
system_name="#{space_type.name} Service Water Loop",
|
356
|
+
water_heater_thermal_zone=nil,
|
357
|
+
service_water_temperature_c,
|
358
|
+
service_water_pump_head=0.01,
|
359
|
+
service_water_pump_motor_efficiency=1.0,
|
360
|
+
water_heater_capacity_w,
|
361
|
+
water_heater_volume_m3,
|
362
|
+
water_heater_fuel,
|
363
|
+
parasitic_fuel_consumption_rate_w=0,
|
364
|
+
add_pipe_losses=true,
|
365
|
+
floor_area_served=OpenStudio.convert(950, 'ft^2', 'm^2').get,
|
366
|
+
number_of_stories=1,
|
367
|
+
pipe_insulation_thickness=OpenStudio.convert(pipe_insul_in, 'in', 'm').get,
|
368
|
+
num_water_heaters)
|
369
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_typical, num_water_heaters = #{num_water_heaters}")
|
370
|
+
# Add loop to list
|
371
|
+
swh_systems << swh_loop
|
372
|
+
|
373
|
+
# Attach water use equipment to the loop
|
374
|
+
swh_connection = water_use_equip.waterUseConnections
|
375
|
+
swh_loop.addDemandBranchForComponent(swh_connection.get) if swh_connection.is_initialized
|
376
|
+
|
377
|
+
# If a booster fraction is specified, some percentage of the water
|
378
|
+
# is assumed to be heated beyond the normal temperature by a separate
|
379
|
+
# booster water heater. This booster water heater is fed by the
|
380
|
+
# main water heater, so the booster is responsible for a smaller delta-T.
|
381
|
+
if booster_water_heater_fraction > 0
|
382
|
+
# find_water_heater_capacity_volume_and_parasitic
|
383
|
+
booster_water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model,
|
384
|
+
[booster_water_use_equip],
|
385
|
+
htg_eff: 1.0,
|
386
|
+
inlet_temp_f: service_water_temperature_f,
|
387
|
+
target_temp_f: booster_water_temperature_f)
|
388
|
+
|
389
|
+
# Add service water booster loop with water heater
|
390
|
+
# Note that booster water heaters are always assumed to be electric resistance
|
391
|
+
swh_booster_loop = model_add_swh_booster(model,
|
392
|
+
swh_loop,
|
393
|
+
booster_water_heater_sizing[:water_heater_capacity],
|
394
|
+
water_heater_volume_m3=OpenStudio.convert(6, 'gal', 'm^3').get,
|
395
|
+
water_heater_fuel='Electricity',
|
396
|
+
booster_water_temperature_c,
|
397
|
+
parasitic_fuel_consumption_rate_w=0.0,
|
398
|
+
booster_water_heater_thermal_zone=nil)
|
399
|
+
|
400
|
+
# Rename the service water booster loop
|
401
|
+
swh_booster_loop.setName("#{space_type.name} Service Water Booster Loop")
|
402
|
+
|
403
|
+
# Attach booster water use equipment to the booster loop
|
404
|
+
booster_swh_connection = booster_water_use_equip.waterUseConnections
|
405
|
+
swh_booster_loop.addDemandBranchForComponent(booster_swh_connection.get) if booster_swh_connection.is_initialized
|
406
|
+
end
|
407
|
+
|
408
|
+
else
|
409
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "'#{swh_system_type}' is not a valid Service Water Heating System Type, cannot add SWH to #{space_type.name}. Valid choices are One Per Unit, Dedicated, and Shared.")
|
569
410
|
end
|
570
411
|
end
|
571
412
|
|
572
413
|
# get building floor area and effective number of stories
|
573
|
-
|
414
|
+
bldg_floor_area_m2 = model.getBuilding.floorArea
|
574
415
|
bldg_effective_num_stories_hash = model_effective_num_stories(model)
|
575
416
|
bldg_effective_num_stories = bldg_effective_num_stories_hash[:below_grade] + bldg_effective_num_stories_hash[:above_grade]
|
576
417
|
|
577
418
|
# add non-dedicated system(s) here. Separate systems for water use equipment from different building types
|
578
419
|
water_use_equipment_hash.sort.each do |stds_bldg_type, water_use_equipment_array|
|
579
|
-
#
|
580
|
-
|
581
|
-
|
582
|
-
water_heater_temp_si = 60.0
|
420
|
+
# TODO: find the water use equipment with the highest temperature
|
421
|
+
water_heater_temp_f = 140.0
|
422
|
+
water_heater_temp_c = OpenStudio.convert(water_heater_temp_f, 'F', 'C').get
|
583
423
|
|
584
424
|
# find pump values
|
585
425
|
# Table A.2 in PrototypeModelEnhancements_2014_0.pdf shows 10ft on everything except SecondarySchool which has 11.4ft
|
586
|
-
#
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
426
|
+
# TODO: Remove hard-coded building-type-based lookups for circulating vs. non-circulating SWH systems
|
427
|
+
circulating_bldg_types = [
|
428
|
+
# DOE building types
|
429
|
+
'Office',
|
430
|
+
'PrimarySchool',
|
431
|
+
'Outpatient',
|
432
|
+
'Hospital',
|
433
|
+
'SmallHotel',
|
434
|
+
'LargeHotel',
|
435
|
+
'FullServiceRestaurant',
|
436
|
+
'HighriseApartment',
|
437
|
+
# DEER building types
|
438
|
+
'Asm', # 'Assembly'
|
439
|
+
'ECC', # 'Education - Community College'
|
440
|
+
'EPr', # 'Education - Primary School'
|
441
|
+
'ERC', # 'Education - Relocatable Classroom'
|
442
|
+
'ESe', # 'Education - Secondary School'
|
443
|
+
'EUn', # 'Education - University'
|
444
|
+
'Gro', # 'Grocery'
|
445
|
+
'Hsp', # 'Health/Medical - Hospital'
|
446
|
+
'Htl', # 'Lodging - Hotel'
|
447
|
+
'MBT', # 'Manufacturing Biotech'
|
448
|
+
'MFm', # 'Residential Multi-family'
|
449
|
+
'Mtl', # 'Lodging - Motel'
|
450
|
+
'Nrs', # 'Health/Medical - Nursing Home'
|
451
|
+
'OfL', # 'Office - Large'
|
452
|
+
# 'RFF', # 'Restaurant - Fast-Food'
|
453
|
+
'RSD' # 'Restaurant - Sit-Down'
|
454
|
+
]
|
455
|
+
if circulating_bldg_types.include?(stds_bldg_type)
|
456
|
+
service_water_pump_head_pa = OpenStudio.convert(10.0, 'ftH_{2}O', 'Pa').get
|
598
457
|
service_water_pump_motor_efficiency = 0.3
|
599
|
-
if circulating.nil?
|
600
|
-
|
601
|
-
end
|
602
|
-
if pipe_insul_in.nil? then
|
603
|
-
pipe_insul_in = 0.5
|
604
|
-
end
|
458
|
+
circulating = true if circulating.nil?
|
459
|
+
pipe_insul_in = 0.5 if pipe_insul_in.nil?
|
605
460
|
else # values for non-circulating pump
|
606
|
-
|
461
|
+
service_water_pump_head_pa = 0.01
|
607
462
|
service_water_pump_motor_efficiency = 1.0
|
608
|
-
if circulating.nil?
|
609
|
-
|
610
|
-
end
|
611
|
-
if pipe_insul_in.nil? then
|
612
|
-
pipe_insul_in = 0.0
|
613
|
-
end
|
614
|
-
end
|
615
|
-
|
616
|
-
# TODO: - add building type or sice specific logic or just assume Gas? (SmallOffice and Warehouse are only non unit prototypes with Electric heating)
|
617
|
-
water_heater_fuel = if fuel.nil?
|
618
|
-
'Gas'
|
619
|
-
else
|
620
|
-
fuel
|
621
|
-
end
|
622
|
-
|
623
|
-
bldg_type_floor_area = 0.0
|
624
|
-
space_type_hash.sort.each do |space_type, hash|
|
625
|
-
next if hash[:stds_bldg_type] != stds_bldg_type
|
626
|
-
bldg_type_floor_area += hash[:floor_area]
|
627
|
-
end
|
628
|
-
|
629
|
-
# inputs for find_water_heater_capacity_volume_and_parasitic
|
630
|
-
pipe_hash = {}
|
631
|
-
pipe_hash[:floor_area] = bldg_type_floor_area
|
632
|
-
pipe_hash[:effective_num_stories] = bldg_effective_num_stories * (bldg_type_floor_area / bldg_floor_area)
|
633
|
-
pipe_hash[:circulating] = circulating
|
634
|
-
pipe_hash[:insulation_thickness] = pipe_insul_in
|
635
|
-
|
636
|
-
# find_water_heater_capacity_volume_and_parasitic
|
637
|
-
water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array, pipe_hash)
|
638
|
-
water_heater_capacity = water_heater_sizing[:water_heater_capacity]
|
639
|
-
water_heater_volume = water_heater_sizing[:water_heater_volume]
|
640
|
-
parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]
|
641
|
-
if parasitic_fuel_consumption_rate > 0
|
642
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding parasitic loss for #{stds_bldg_type} loop of #{parasitic_fuel_consumption_rate.round} Btu/hr.")
|
463
|
+
circulating = false if circulating.nil?
|
464
|
+
pipe_insul_in = 0.0 if pipe_insul_in.nil?
|
643
465
|
end
|
644
466
|
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
water_heater_thermal_zone,
|
649
|
-
water_heater_temp_si,
|
650
|
-
service_water_pump_head,
|
651
|
-
service_water_pump_motor_efficiency,
|
652
|
-
water_heater_capacity,
|
653
|
-
water_heater_volume,
|
654
|
-
water_heater_fuel,
|
655
|
-
parasitic_fuel_consumption_rate,
|
656
|
-
stds_bldg_type)
|
657
|
-
|
658
|
-
# find water heater
|
659
|
-
shared_hot_water_loop.supplyComponents.sort.each do |component|
|
660
|
-
next if component.to_WaterHeaterMixed.empty?
|
661
|
-
water_heater = component.to_WaterHeaterMixed.get
|
662
|
-
|
663
|
-
# apply efficiency to hot water heater
|
664
|
-
water_heater_mixed_apply_efficiency(water_heater)
|
467
|
+
bldg_type_floor_area_m2 = 0.0
|
468
|
+
space_type_hash.sort.each do |space_type, space_type_props|
|
469
|
+
bldg_type_floor_area_m2 += space_type_props[:floor_area] if space_type_props[:stds_bldg_type] == stds_bldg_type
|
665
470
|
end
|
666
471
|
|
667
|
-
#
|
472
|
+
# Calculate the number of stories covered by this building type
|
473
|
+
num_stories = bldg_effective_num_stories * (bldg_type_floor_area_m2 / bldg_floor_area_m2)
|
474
|
+
|
475
|
+
# Water heater sizing
|
476
|
+
water_heater_sizing = model_find_water_heater_capacity_volume_and_parasitic(model, water_use_equipment_array)
|
477
|
+
water_heater_capacity_w = water_heater_sizing[:water_heater_capacity]
|
478
|
+
water_heater_volume_m3 = water_heater_sizing[:water_heater_volume]
|
479
|
+
|
480
|
+
# Add a shared service water heating loop with water heater
|
481
|
+
shared_swh_loop = model_add_swh_loop(model,
|
482
|
+
"#{stds_bldg_type} Shared Service Water Loop",
|
483
|
+
water_heater_thermal_zone=nil,
|
484
|
+
water_heater_temp_c,
|
485
|
+
service_water_pump_head_pa,
|
486
|
+
service_water_pump_motor_efficiency,
|
487
|
+
water_heater_capacity_w,
|
488
|
+
water_heater_volume_m3,
|
489
|
+
water_heater_fuel,
|
490
|
+
parasitic_fuel_consumption_rate_w=0,
|
491
|
+
add_pipe_losses=true,
|
492
|
+
floor_area_served=bldg_type_floor_area_m2,
|
493
|
+
number_of_stories=num_stories,
|
494
|
+
pipe_insulation_thickness=OpenStudio.convert(pipe_insul_in, 'in', 'm').get)
|
495
|
+
|
496
|
+
# Attach all water use equipment to the shared loop
|
668
497
|
water_use_equipment_array.sort.each do |water_use_equip|
|
669
|
-
|
670
|
-
|
671
|
-
water_use_connection.addWaterUseEquipment(water_use_equip)
|
672
|
-
water_use_connection.setName(water_use_equip.name.get.gsub('SWH', 'WUC'))
|
673
|
-
|
674
|
-
# Connect the water use connection to the SWH loop
|
675
|
-
shared_hot_water_loop.addDemandBranchForComponent(water_use_connection)
|
498
|
+
swh_connection = water_use_equip.waterUseConnections
|
499
|
+
shared_swh_loop.addDemandBranchForComponent(swh_connection.get) if swh_connection.is_initialized
|
676
500
|
end
|
677
501
|
|
678
502
|
# add to list of systems
|
679
|
-
swh_systems <<
|
503
|
+
swh_systems << shared_swh_loop
|
680
504
|
|
681
505
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding shared water heating loop for #{stds_bldg_type}.")
|
682
506
|
end
|
@@ -684,21 +508,28 @@ class Standard
|
|
684
508
|
return swh_systems
|
685
509
|
end
|
686
510
|
|
687
|
-
#
|
511
|
+
# Use rules from DOE Prototype Building documentation to determine water heater capacity,
|
512
|
+
# volume, pipe dump losses, and pipe thermal losses.
|
688
513
|
#
|
689
514
|
# @param water_use_equipment_array [Array] array of water use equipment objects that will be using this water heater
|
690
|
-
# @param
|
691
|
-
# @param htg_eff [Double] fraction
|
692
|
-
# @param
|
693
|
-
# @param
|
515
|
+
# @param storage_to_cap_ratio_gal_to_kbtu_per_hr [Double] storage volume gal to kBtu/hr of capacity
|
516
|
+
# @param htg_eff [Double] water heater thermal efficiency, fraction
|
517
|
+
# @param inlet_temp_f [Double] inlet cold water temperature, degrees Fahrenheit
|
518
|
+
# @param target_temp_f [Double] target supply water temperatre from the tank, degrees Fahrenheit
|
694
519
|
# @return [Hash] hash with values needed to size water heater made with downstream method
|
695
|
-
def model_find_water_heater_capacity_volume_and_parasitic(model,
|
520
|
+
def model_find_water_heater_capacity_volume_and_parasitic(model,
|
521
|
+
water_use_equipment_array,
|
522
|
+
storage_to_cap_ratio_gal_to_kbtu_per_hr: 1.0,
|
523
|
+
htg_eff: 0.8,
|
524
|
+
inlet_temp_f: 40.0,
|
525
|
+
target_temp_f: 140.0,
|
526
|
+
peak_flow_fraction: 1.0)
|
696
527
|
# A.1.4 Total Storage Volume and Water Heater Capacity of PrototypeModelEnhancements_2014_0.pdf shows 1 gallon of storage to 1 kBtu/h of capacity
|
697
528
|
|
698
529
|
water_heater_sizing = {}
|
699
530
|
|
700
|
-
#
|
701
|
-
|
531
|
+
# Get the maximum flow rates for all pieces of water use equipment
|
532
|
+
adjusted_max_flow_rates_gal_per_hr = [] # gallons per hour
|
702
533
|
water_use_equipment_array.sort.each do |water_use_equip|
|
703
534
|
water_use_equip_sch = water_use_equip.flowRateFractionSchedule
|
704
535
|
next if water_use_equip_sch.empty?
|
@@ -712,78 +543,39 @@ class Standard
|
|
712
543
|
elsif water_use_equip_sch.to_ScheduleCompact.is_initialized
|
713
544
|
water_use_equip_sch = water_use_equip_sch.to_ScheduleCompact.get
|
714
545
|
max_sch_value = schedule_compact_annual_min_max_value(water_use_equip_sch)['max']
|
546
|
+
else
|
547
|
+
OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "The peak flow rate fraction for #{water_use_equip_sch.name} could not be determined, assuming 1 for water heater sizing purposes.")
|
548
|
+
max_sch_value = 1.0
|
715
549
|
end
|
716
550
|
|
717
|
-
#
|
718
|
-
|
719
|
-
peak_flow_rate = water_use_equip_def.peakFlowRate
|
720
|
-
|
721
|
-
# calculate adjusted flow rate
|
722
|
-
adjusted_peak_flow_rate_si = max_sch_value * peak_flow_rate
|
723
|
-
adjusted_peak_flow_rate_ip = OpenStudio.convert(adjusted_peak_flow_rate_si, 'm^3/s', 'gal/min').get
|
724
|
-
max_flow_rate_array << adjusted_peak_flow_rate_ip * 60.0 # min per hour
|
725
|
-
end
|
551
|
+
# Get peak flow rate from water use equipment definition
|
552
|
+
peak_flow_rate_m3_per_s = water_use_equip.waterUseEquipmentDefinition.peakFlowRate
|
726
553
|
|
727
|
-
|
728
|
-
|
729
|
-
OpenStudio.
|
554
|
+
# Calculate adjusted flow rate based on the peak fraction found in the flow rate fraction schedule
|
555
|
+
adjusted_peak_flow_rate_m3_per_s = max_sch_value * peak_flow_rate_m3_per_s
|
556
|
+
adjusted_max_flow_rates_gal_per_hr << OpenStudio.convert(adjusted_peak_flow_rate_m3_per_s, 'm^3/s', 'gal/hr').get
|
730
557
|
end
|
731
558
|
|
732
|
-
#
|
733
|
-
|
559
|
+
# Sum gph values from water use equipment to use in formula
|
560
|
+
total_adjusted_flow_rate_gal_per_hr = adjusted_max_flow_rates_gal_per_hr.inject(:+)
|
734
561
|
|
735
|
-
#
|
562
|
+
# Calculate capacity based on analysis of combined water use equipment maximum flow rates and schedules
|
736
563
|
# Max gal/hr * 8.4 lb/gal * 1 Btu/lb F * (120F - 40F)/0.8 = Btu/hr
|
737
|
-
|
738
|
-
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Capacity of #{
|
739
|
-
|
740
|
-
|
741
|
-
|
564
|
+
water_heater_capacity_btu_per_hr = peak_flow_fraction * total_adjusted_flow_rate_gal_per_hr * 8.4 * 1.0 * (target_temp_f - inlet_temp_f) / htg_eff
|
565
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Capacity of #{water_heater_capacity_btu_per_hr.round} Btu/hr = #{peak_flow_fraction} peak fraction * #{total_adjusted_flow_rate_gal_per_hr.round} gal/hr * 8.4 lb/gal * 1.0 Btu/lb F * (#{target_temp_f.round} - #{inlet_temp_f.round} deltaF / #{htg_eff} htg eff).")
|
566
|
+
water_heater_capacity_m3_per_s = OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get
|
567
|
+
|
568
|
+
# Calculate volume based on capacity
|
569
|
+
# Default assumption is 1 gal of volume per 1 kBtu/hr of heating capacity
|
570
|
+
water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'kBtu/hr').get
|
571
|
+
water_heater_volume_gal = water_heater_capacity_kbtu_per_hr * storage_to_cap_ratio_gal_to_kbtu_per_hr
|
742
572
|
# increase tank size to 40 galons if calculated value is smaller
|
743
|
-
if
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
water_heater_sizing[:water_heater_capacity] = water_heater_capacity_si
|
750
|
-
water_heater_sizing[:water_heater_volume] = water_heater_volume_si
|
751
|
-
|
752
|
-
# get pipe length (formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf)
|
753
|
-
if !pipe_hash.empty?
|
754
|
-
|
755
|
-
pipe_length = 2.0 * (Math.sqrt(pipe_hash[:floor_area] / pipe_hash[:effective_num_stories]) + (10.0 * (pipe_hash[:effective_num_stories] - 1.0)))
|
756
|
-
pipe_length_ip = OpenStudio.convert(pipe_length, 'm', 'ft').get
|
757
|
-
|
758
|
-
# calculate pipe dump (from A.4.1)
|
759
|
-
pipe_dump = pipe_length_ip * 0.689 # Btu/hr
|
760
|
-
|
761
|
-
pipe_loss_per_foot = if pipe_hash[:circulating]
|
762
|
-
if pipe_hash[:insulation_thickness] >= 1.0
|
763
|
-
16.10
|
764
|
-
elsif pipe_hash[:insulation_thickness] >= 0.5
|
765
|
-
17.5
|
766
|
-
else
|
767
|
-
30.8
|
768
|
-
end
|
769
|
-
else
|
770
|
-
if pipe_hash[:insulation_thickness] >= 1.0
|
771
|
-
11.27
|
772
|
-
elsif pipe_hash[:insulation_thickness] >= 0.5
|
773
|
-
12.25
|
774
|
-
else
|
775
|
-
28.07
|
776
|
-
end
|
777
|
-
end
|
778
|
-
|
779
|
-
# calculate pipe loss (from Table A.3 in section A.4.2)
|
780
|
-
pipe_loss = pipe_length * pipe_loss_per_foot # Btu/hr
|
781
|
-
|
782
|
-
# calculate parasitic loss
|
783
|
-
water_heater_sizing[:parasitic_fuel_consumption_rate] = pipe_dump + pipe_loss
|
784
|
-
else
|
785
|
-
water_heater_sizing[:parasitic_fuel_consumption_rate] = 0.0
|
786
|
-
end
|
573
|
+
water_heater_volume_gal = 40.0 if water_heater_volume_gal < 40.0 # gal
|
574
|
+
water_heater_volume_m3 = OpenStudio.convert(water_heater_volume_gal, 'gal', 'm^3').get
|
575
|
+
|
576
|
+
# Populate return hash
|
577
|
+
water_heater_sizing[:water_heater_capacity] = water_heater_capacity_m3_per_s
|
578
|
+
water_heater_sizing[:water_heater_volume] = water_heater_volume_m3
|
787
579
|
|
788
580
|
return water_heater_sizing
|
789
581
|
end
|