openstudio-standards 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|