openstudio-standards 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards.xlsx +0 -0
  3. data/data/standards/OpenStudio_Standards_climate_zone_sets.json +96 -0
  4. data/data/standards/OpenStudio_Standards_climate_zones.json +96 -0
  5. data/data/standards/OpenStudio_Standards_construction_properties.json +11424 -0
  6. data/data/standards/OpenStudio_Standards_construction_sets.json +1746 -24
  7. data/data/standards/OpenStudio_Standards_constructions.json +409 -0
  8. data/data/standards/OpenStudio_Standards_curve_biquadratics.json +160 -0
  9. data/data/standards/OpenStudio_Standards_curve_quadratics.json +165 -0
  10. data/data/standards/OpenStudio_Standards_entryways.json +191 -0
  11. data/data/standards/OpenStudio_Standards_exterior_lighting.json +964 -0
  12. data/data/standards/OpenStudio_Standards_exterior_lighting_assumptions.json +191 -0
  13. data/data/standards/OpenStudio_Standards_ground_temperatures.json +11424 -0
  14. data/data/standards/OpenStudio_Standards_illuminated_parking_area.json +157 -0
  15. data/data/standards/OpenStudio_Standards_materials.json +2539 -745
  16. data/data/standards/OpenStudio_Standards_motors.json +420 -0
  17. data/data/standards/OpenStudio_Standards_parking.json +157 -0
  18. data/data/standards/OpenStudio_Standards_prototype_inputs.json +4410 -10
  19. data/data/standards/OpenStudio_Standards_schedules.json +13756 -8383
  20. data/data/standards/OpenStudio_Standards_space_types.json +8593 -907
  21. data/data/standards/OpenStudio_Standards_standards.json +9 -0
  22. data/data/standards/OpenStudio_Standards_templates.json +24 -0
  23. data/data/standards/OpenStudio_Standards_unitary_acs.json +924 -0
  24. data/data/standards/manage_OpenStudio_Standards.rb +1 -1
  25. data/lib/openstudio-standards/btap/btap.rb +0 -1
  26. data/lib/openstudio-standards/btap/compliance.rb +2 -5
  27. data/lib/openstudio-standards/btap/economics.rb +16 -16
  28. data/lib/openstudio-standards/btap/envelope.rb +1 -1
  29. data/lib/openstudio-standards/btap/equest.rb +7 -7
  30. data/lib/openstudio-standards/btap/fileio.rb +2 -2
  31. data/lib/openstudio-standards/btap/geometry.rb +1 -1
  32. data/lib/openstudio-standards/btap/measures.rb +16 -16
  33. data/lib/openstudio-standards/btap/mpc.rb +18 -18
  34. data/lib/openstudio-standards/btap/schedules.rb +35 -2
  35. data/lib/openstudio-standards/btap/simmanager.rb +1 -1
  36. data/lib/openstudio-standards/btap/spaceloads.rb +1 -1
  37. data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
  38. data/lib/openstudio-standards/prototypes/Prototype.Model.elevators.rb +218 -0
  39. data/lib/openstudio-standards/prototypes/Prototype.Model.exterior_lights.rb +399 -0
  40. data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +2 -2
  41. data/lib/openstudio-standards/prototypes/Prototype.Model.rb +237 -0
  42. data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +536 -0
  43. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +6 -0
  44. data/lib/openstudio-standards/standards/Standards.Model.rb +249 -10
  45. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +5 -5
  46. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +161 -0
  47. data/lib/openstudio-standards/utilities/simulation.rb +6 -4
  48. data/lib/openstudio-standards/version.rb +1 -1
  49. metadata +223 -204
  50. data/lib/openstudio-standards/btap/os_lib_schedules.rb +0 -677
@@ -384,7 +384,7 @@ class OpenStudio::Model::Model
384
384
  # Get the space
385
385
  space = getSpaceByName(system['return_plenum'])
386
386
  if space.empty?
387
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "No space called #{space_name} was found in the model")
387
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "No space called #{system['return_plenum']} was found in the model")
388
388
  return return_plenum
389
389
  end
390
390
  space = space.get
@@ -392,7 +392,7 @@ class OpenStudio::Model::Model
392
392
  # Get the space's zone
393
393
  zone = space.thermalZone
394
394
  if zone.empty?
395
- OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Space #{space_name} has no thermal zone; cannot be a return plenum.")
395
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Space #{space.name} has no thermal zone; cannot be a return plenum.")
396
396
  return return_plenum
397
397
  end
398
398
 
@@ -13,6 +13,8 @@ class OpenStudio::Model::Model
13
13
  require_relative 'Prototype.Model.swh'
14
14
  require_relative '../standards/Standards.Model'
15
15
  require_relative 'Prototype.building_specific_methods'
16
+ require_relative 'Prototype.Model.elevators'
17
+ require_relative 'Prototype.Model.exterior_lights'
16
18
 
17
19
  # Creates a DOE prototype building model and replaces
18
20
  # the current model with this model.
@@ -894,6 +896,241 @@ class OpenStudio::Model::Model
894
896
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished creating thermal zones')
895
897
  end
896
898
 
899
+ # Loop through thermal zones and run thermal_zone.add_exhaust
900
+ # If kitchen_makeup is "None" then exhaust will be modeled in every kitchen zone without makeup air
901
+ # If kitchen_makeup is "Adjacent" then exhaust will be modeled in every kitchen zone. Makeup air will be provided when there as an adjacent dining,cafe, or cafeteria zone of the same buidling type.
902
+ # If kitchen_makeup is "Largest Zone" then exhaust will only be modeled in the largest kitchen zone, but the flow rate will be based on the kitchen area for all zones. Makeup air will be modeled in the largest dining,cafe, or cafeteria zone of the same building type.
903
+ #
904
+ # @param template [String] Valid choices are
905
+ # @param kitchen_makeup [String] Valid choices are
906
+ # @return [Hash] Hash of newly made exhaust fan objects along with secondary exhaust and zone mixing objects
907
+ def add_exhaust(template,kitchen_makeup = "Adjacent") # kitchen_makeup options are (None, Largest Zone, Adjacent)
908
+
909
+ zone_exhaust_fans = {}
910
+
911
+ # apply use specified kitchen_makup logic
912
+ if not ["Adjacent","Largest Zone"].include?(kitchen_makeup)
913
+
914
+ if not kitchen_makeup == "None"
915
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "#{kitchen_makeup} is an unexpected value for kitchen_makup arg, will use None.")
916
+ end
917
+
918
+ # loop through thermal zones
919
+ self.getThermalZones.each do |thermal_zone|
920
+ zone_exhaust_hash = thermal_zone.add_exhaust(template)
921
+
922
+ # populate zone_exhaust_fans
923
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
924
+ end
925
+
926
+ else # common code for Adjacent and Largest Zone
927
+
928
+ # populate standard_space_types_with_makup_air
929
+ standard_space_types_with_makup_air = {}
930
+ standard_space_types_with_makup_air[["FullServiceRestaurant","Kitchen"]] = ["FullServiceRestaurant","Dining"]
931
+ standard_space_types_with_makup_air[["QuickServiceRestaurant","Kitchen"]] = ["QuickServiceRestaurant","Dining"]
932
+ standard_space_types_with_makup_air[["Hospital","Kitchen"]] = ["Hospital","Dining"]
933
+ standard_space_types_with_makup_air[["SecondarySchool","Kitchen"]] = ["SecondarySchool","Cafeteria"]
934
+ standard_space_types_with_makup_air[["PrimarySchool","Kitchen"]] = ["PrimarySchool","Cafeteria"]
935
+ standard_space_types_with_makup_air[["LargeHotel","Kitchen"]] = ["LargeHotel","Cafe"]
936
+
937
+ # gather information on zones organized by standards building type and space type. zone may be in this multiple times if it has multiple space types
938
+ zones_by_standards = {}
939
+
940
+ self.getThermalZones.each do |thermal_zone|
941
+
942
+ # get space type ratio for spaces in zone
943
+ space_type_hash = {} # key is space type, value hash with floor area, standards building type, standards space type, and array of adjacent zones
944
+ thermal_zone.spaces.each do |space|
945
+ next if not space.spaceType.is_initialized
946
+ next if not space.partofTotalFloorArea
947
+ space_type = space.spaceType.get
948
+ next if not space_type.standardsBuildingType.is_initialized
949
+ next if not space_type.standardsSpaceType.is_initialized
950
+
951
+ # add entry in hash for space_type_standardsif it doesn't already exist
952
+ if not space_type_hash.has_key?(space_type)
953
+ space_type_hash[space_type] = {}
954
+ space_type_hash[space_type][:effective_floor_area] = 0.0
955
+ space_type_hash[space_type][:standards_array] =[space_type.standardsBuildingType.get,space_type.standardsSpaceType.get]
956
+ if kitchen_makeup == "Adjacent"
957
+ space_type_hash[space_type][:adjacent_zones] = []
958
+ end
959
+ end
960
+
961
+ # populate floor area
962
+ space_type_hash[space_type][:effective_floor_area] += space.floorArea * space.multiplier
963
+
964
+ # todo - populate adjacent zones (need to add methods to space and zone for this)
965
+ if kitchen_makeup == "Adjacent"
966
+ space_type_hash[space_type][:adjacent_zones] << nil
967
+ end
968
+
969
+ # populate zones_by_standards
970
+ if not zones_by_standards.has_key?(space_type_hash[space_type][:standards_array])
971
+ zones_by_standards[space_type_hash[space_type][:standards_array]] = {}
972
+ end
973
+ zones_by_standards[space_type_hash[space_type][:standards_array]][thermal_zone] = space_type_hash
974
+
975
+ end
976
+
977
+ end
978
+
979
+ if kitchen_makeup == "Largest Zone"
980
+
981
+ zones_applied = [] # add thermal zones to this ones they have had thermal_zone.add_exhaust run on it
982
+
983
+ # loop through standard_space_types_with_makup_air
984
+ standard_space_types_with_makup_air.each do |makeup_target,makeup_source|
985
+
986
+ # hash to manage lookups
987
+ markup_target_effective_floor_area = {}
988
+ markup_source_effective_floor_area = {}
989
+
990
+ if zones_by_standards.has_key?(makeup_target)
991
+
992
+ # process zones of each makeup_target
993
+ zones_by_standards[makeup_target].each do |thermal_zone,space_type_hash|
994
+ effective_floor_area = 0.0
995
+ space_type_hash.each do |space_type,hash|
996
+ effective_floor_area += space_type_hash[space_type][:effective_floor_area]
997
+ end
998
+ markup_target_effective_floor_area[thermal_zone] = effective_floor_area
999
+ end
1000
+
1001
+ # find zone with largest effective area of this space type
1002
+ largest_target_zone = markup_target_effective_floor_area.key(markup_target_effective_floor_area.values.max)
1003
+
1004
+ # find total effective area to calculate exhaust, then divide by zone multiplier when add exhaust
1005
+ target_effective_floor_area = markup_target_effective_floor_area.values.reduce(0, :+)
1006
+
1007
+ # find zones that match makeup_target with makeup_source
1008
+ if zones_by_standards.has_key?(makeup_source)
1009
+
1010
+ # process zones of each makeup_source
1011
+ zones_by_standards[makeup_source].each do |thermal_zone,space_type_hash|
1012
+ effective_floor_area = 0.0
1013
+ space_type_hash.each do |space_type,hash|
1014
+ effective_floor_area += space_type_hash[space_type][:effective_floor_area]
1015
+ end
1016
+
1017
+ markup_source_effective_floor_area[thermal_zone] = effective_floor_area
1018
+ end
1019
+ # find zone with largest effective area of this space type
1020
+ largest_source_zone = markup_source_effective_floor_area.key(markup_source_effective_floor_area.values.max)
1021
+ else
1022
+
1023
+ # issue warning that makeup air wont be made but still make exhaust
1024
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Model has zone with #{makeup_target} but not #{makeup_source}. Exhaust will be added, but no makeup air.")
1025
+ next
1026
+
1027
+ end
1028
+
1029
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Largest #{makeup_target} is #{largest_target_zone.name} which will provide exahust for #{target_effective_floor_area} m^2")
1030
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Largest #{makeup_source} is #{largest_source_zone.name} which will provide makeup air for #{makeup_target}")
1031
+
1032
+ # add in extra arguments for makeup air
1033
+ exhaust_makeup_inputs = {}
1034
+ exhaust_makeup_inputs[makeup_target] = {} # for now only one makeup target per zone, but method could have multiple
1035
+ exhaust_makeup_inputs[makeup_target][:target_effective_floor_area] = target_effective_floor_area
1036
+ exhaust_makeup_inputs[makeup_target][:source_zone] = largest_source_zone
1037
+
1038
+
1039
+ # add exhaust
1040
+ next if zones_applied.include?(largest_target_zone) # would only hit this if zone has two space types each requesting makeup air
1041
+ zone_exhaust_hash = largest_target_zone.add_exhaust(template,exhaust_makeup_inputs)
1042
+ zones_applied << largest_target_zone
1043
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
1044
+
1045
+ end
1046
+
1047
+ end
1048
+
1049
+ # add exhaust to zones that did not contain space types with standard_space_types_with_makup_air
1050
+ zones_by_standards.each do |standards_array,zones_hash|
1051
+ next if standard_space_types_with_makup_air.has_key?(standards_array)
1052
+
1053
+ # loop through zones adding exhaust
1054
+ zones_hash.each do |thermal_zone,space_type_hash|
1055
+ next if zones_applied.include?(thermal_zone)
1056
+
1057
+ # add exhaust
1058
+ zone_exhaust_hash = thermal_zone.add_exhaust(template)
1059
+ zones_applied << thermal_zone
1060
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
1061
+ end
1062
+
1063
+ end
1064
+
1065
+
1066
+ else #kitchen_makeup == "Adjacent"
1067
+
1068
+ zones_applied = [] # add thermal zones to this ones they have had thermal_zone.add_exhaust run on it
1069
+
1070
+ standard_space_types_with_makup_air.each do |makeup_target,makeup_source|
1071
+ if zones_by_standards.has_key?(makeup_target)
1072
+ # process zones of each makeup_target
1073
+ zones_by_standards[makeup_target].each do |thermal_zone,space_type_hash|
1074
+
1075
+ # get adjacent zones
1076
+ adjacent_zones = thermal_zone.get_adjacent_zones_with_shared_wall_areas
1077
+
1078
+ # find adjacent zones matching key and value from standard_space_types_with_makup_air
1079
+ first_adjacent_makeup_source = nil
1080
+ adjacent_zones.each do |adjacent_zone|
1081
+
1082
+ next if not first_adjacent_makeup_source.nil?
1083
+
1084
+ if zones_by_standards.has_key?(makeup_source) and zones_by_standards[makeup_source].has_key?(adjacent_zone)
1085
+ first_adjacent_makeup_source = adjacent_zone
1086
+
1087
+ # todo - add in extra arguments for makeup air
1088
+ exhaust_makeup_inputs = {}
1089
+ exhaust_makeup_inputs[makeup_target] = {} # for now only one makeup target per zone, but method could have multiple
1090
+ exhaust_makeup_inputs[makeup_target][:source_zone] = first_adjacent_makeup_source
1091
+
1092
+ # add exhaust
1093
+ zone_exhaust_hash = thermal_zone.add_exhaust(template,exhaust_makeup_inputs)
1094
+ zones_applied << thermal_zone
1095
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
1096
+ end
1097
+
1098
+ end
1099
+
1100
+ if first_adjacent_makeup_source.nil?
1101
+
1102
+ # issue warning that makeup air wont be made but still make exhaust
1103
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Model has zone with #{makeup_target} but no adjacent zone with #{makeup_source}. Exhaust will be added, but no makeup air.")
1104
+
1105
+ # add exhaust
1106
+ zone_exhaust_hash = thermal_zone.add_exhaust(template)
1107
+ zones_applied << thermal_zone
1108
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
1109
+
1110
+ end
1111
+
1112
+ end
1113
+
1114
+ end
1115
+ end
1116
+
1117
+ # add exhaust for rest of zones
1118
+ self.getThermalZones.each do |thermal_zone|
1119
+ next if zones_applied.include?(thermal_zone)
1120
+
1121
+ # add exhaust
1122
+ zone_exhaust_hash = thermal_zone.add_exhaust(template)
1123
+ zone_exhaust_fans.merge!(zone_exhaust_hash)
1124
+ end
1125
+
1126
+ end
1127
+
1128
+ end
1129
+
1130
+ return zone_exhaust_fans
1131
+
1132
+ end
1133
+
897
1134
  # Adds occupancy sensors to certain space types per
898
1135
  # the PNNL documentation.
899
1136
  #
@@ -277,4 +277,540 @@ class OpenStudio::Model::Model
277
277
 
278
278
  return true
279
279
  end # add swh
280
+
281
+ # add typical swh demand and supply to model
282
+ #
283
+ # @param [String] template
284
+ # @param [Bool] trust_effective_num_spaces
285
+ # @param [String] fuel (gas, electric, nil) nil is smart
286
+ # @param [Double] pipe_insul_in
287
+ # @param [String] circulating, (circulating, noncirculating, nil) nil is smart
288
+ # @return [Array] hot water loops
289
+ # @todo - add in losses from tank and pipe insulation, etc.
290
+ def add_typical_swh(template, trust_effective_num_spaces = false, fuel = nil, pipe_insul_in = nil, circulating = nil)
291
+
292
+ # array of hot water loops
293
+ swh_systems = []
294
+
295
+ # hash of general water use equipment awaiting loop
296
+ water_use_equipment_hash = {} # key is standards building type value is array of water use equipment
297
+
298
+ # create space type hash (need num_units for MidriseApartment and RetailStripmall)
299
+ space_type_hash = self.create_space_type_hash(template,trust_effective_num_spaces = false)
300
+
301
+ # add temperate schedules to hash so they can be shared across water use equipment
302
+ water_use_def_schedules = {} # key is temp C value is schedule
303
+
304
+ # loop through space types adding demand side of swh
305
+ self.getSpaceTypes.each do |space_type|
306
+ next if not space_type.standardsBuildingType.is_initialized
307
+ next if not space_type.standardsSpaceType.is_initialized
308
+ next if not space_type_hash.has_key?(space_type) # this is used for space types without any floor area
309
+ stds_bldg_type = space_type.standardsBuildingType.get
310
+ stds_space_type = space_type.standardsSpaceType.get
311
+
312
+ # lookup space_type_properties
313
+ space_type_properties = space_type.get_standards_data(template)
314
+ gal_hr_per_area = space_type_properties['service_water_heating_peak_flow_per_area']
315
+ gal_hr_peak_flow_rate = space_type_properties['service_water_heating_peak_flow_rate']
316
+ flow_rate_fraction_schedule = self.add_schedule(space_type_properties['service_water_heating_schedule'])
317
+ service_water_temperature_si = space_type_properties['service_water_heating_target_temperature']
318
+ service_water_fraction_sensible = space_type_properties['service_water_heating_fraction_sensible']
319
+ service_water_fraction_latent = space_type_properties['service_water_heating_fraction_latent']
320
+ floor_area_si = space_type_hash[space_type][:floor_area]
321
+ floor_area_ip = OpenStudio::convert(floor_area_si,"m^2","ft^2").get
322
+
323
+ # next if no service water heating demand
324
+ next if not (gal_hr_per_area.to_f > 0.0 || gal_hr_peak_flow_rate.to_f > 0.0)
325
+
326
+ if (stds_bldg_type == "MidriseApartment" && stds_space_type.include?("Apartment")) || stds_bldg_type == "StripMall"
327
+ num_units = space_type_hash[space_type][:num_units].round
328
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding dedicated water heating fpr #{num_units} #{space_type.name} units, each with max flow rate of #{gal_hr_peak_flow_rate} gal/hr per.")
329
+
330
+ # add water use equipment definition
331
+ water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(self)
332
+ water_use_equip_def.setName("#{space_type.name} SWH def")
333
+ peak_flow_rate_si = OpenStudio::convert(gal_hr_peak_flow_rate,"gal/hr","m^3/s").get
334
+ water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
335
+ target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
336
+ name = "#{target_temp} C"
337
+ if water_use_def_schedules.has_key?(name)
338
+ target_temperature_sch = water_use_def_schedules[name]
339
+ else
340
+ target_temperature_sch = self.add_constant_schedule_ruleset(target_temp,name)
341
+ water_use_def_schedules[name] = target_temperature_sch
342
+ end
343
+ water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
344
+ name = "#{service_water_fraction_sensible} Fraction"
345
+ if water_use_def_schedules.has_key?(name)
346
+ service_water_fraction_sensible_sch = water_use_def_schedules[name]
347
+ else
348
+ service_water_fraction_sensible_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
349
+ water_use_def_schedules[name] = service_water_fraction_sensible_sch
350
+ end
351
+ water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
352
+ name = "#{service_water_fraction_latent} Fraction"
353
+ if water_use_def_schedules.has_key?(name)
354
+ service_water_fraction_latent_sch = water_use_def_schedules[name]
355
+ else
356
+ service_water_fraction_latent_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
357
+ water_use_def_schedules[name] = service_water_fraction_latent_sch
358
+ end
359
+ water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
360
+
361
+ # add water use equipment, connection, and loop for each unit
362
+ num_units.times do |i|
363
+
364
+ # add water use equipment
365
+ water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
366
+ water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
367
+ water_use_equip.setName("#{space_type.name} SWH #{i+1}")
368
+
369
+ # add water use connection
370
+ water_use_connection = OpenStudio::Model::WaterUseConnections.new(self)
371
+ water_use_connection.addWaterUseEquipment(water_use_equip)
372
+ water_use_connection.setName("#{space_type.name} WUC #{i+1}")
373
+
374
+ # gather inputs for add_swh_loop
375
+ # default fuel, capacity, and volume from Table A.1. Water Heating Equipment Enhancements to ASHRAE Standard 90.1 Prototype Building Models
376
+ # temperature, pump head, motor efficiency, and parasitic load from Prototype Inputs
377
+ sys_name = "#{space_type.name} Service Water Loop #{i+1}"
378
+ water_heater_thermal_zone = nil
379
+ service_water_temperature = service_water_temperature_si
380
+ service_water_pump_head = 0.01
381
+ service_water_pump_motor_efficiency = 1.0
382
+ if fuel.nil?
383
+ water_heater_fuel = "Electric"
384
+ else
385
+ water_heater_fuel = fuel
386
+ end
387
+ if stds_bldg_type == "MidriseApartment"
388
+ water_heater_capacity = OpenStudio::convert(15.0,"kBtu/hr","W").get
389
+ water_heater_volume = OpenStudio::convert(50.0,"gal","m^3").get
390
+ parasitic_fuel_consumption_rate = 0.0 # Prototype inputs has 87.75W but prototype IDF's use 0
391
+ else # StripMall
392
+ water_heater_capacity = OpenStudio::convert(12.0,"kBtu/hr","W").get
393
+ water_heater_volume = OpenStudio::convert(40.0,"gal","m^3").get
394
+ parasitic_fuel_consumption_rate = 173.0
395
+ end
396
+
397
+ # make loop for each unit and add on water use equipment
398
+ unit_hot_water_loop = add_swh_loop(template,
399
+ sys_name,
400
+ water_heater_thermal_zone,
401
+ service_water_temperature,
402
+ service_water_pump_head,
403
+ service_water_pump_motor_efficiency,
404
+ water_heater_capacity,
405
+ water_heater_volume,
406
+ water_heater_fuel,
407
+ parasitic_fuel_consumption_rate,
408
+ stds_bldg_type)
409
+
410
+ # Connect the water use connection to the SWH loop
411
+ unit_hot_water_loop.addDemandBranchForComponent(water_use_connection)
412
+
413
+ # apply efficiency to hot water heater
414
+ unit_hot_water_loop.supplyComponents.each do |component|
415
+ next if not component.to_WaterHeaterMixed.is_initialized
416
+ component = component.to_WaterHeaterMixed.get
417
+ component.apply_efficiency(template)
418
+ end
419
+
420
+ # add to list of systems
421
+ swh_systems << unit_hot_water_loop
422
+
423
+ end
424
+
425
+ elsif stds_space_type.include?("Kitchen") || stds_space_type.include?("Laundry")
426
+ gal_hr_peak_flow_rate = gal_hr_per_area * floor_area_ip
427
+ 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} gal/hr.")
428
+
429
+ # add water use equipment definition
430
+ water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(self)
431
+ water_use_equip_def.setName("#{space_type.name} SWH def")
432
+ peak_flow_rate_si = OpenStudio::convert(gal_hr_peak_flow_rate,"gal/hr","m^3/s").get
433
+ water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
434
+ target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
435
+ name = "#{target_temp} C"
436
+ if water_use_def_schedules.has_key?(name)
437
+ target_temperature_sch = water_use_def_schedules[name]
438
+ else
439
+ target_temperature_sch = self.add_constant_schedule_ruleset(target_temp,name)
440
+ water_use_def_schedules[name] = target_temperature_sch
441
+ end
442
+ water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
443
+ name = "#{service_water_fraction_sensible} Fraction"
444
+ if water_use_def_schedules.has_key?(name)
445
+ service_water_fraction_sensible_sch = water_use_def_schedules[name]
446
+ else
447
+ service_water_fraction_sensible_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
448
+ water_use_def_schedules[name] = service_water_fraction_sensible_sch
449
+ end
450
+ water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
451
+ name = "#{service_water_fraction_latent} Fraction"
452
+ if water_use_def_schedules.has_key?(name)
453
+ service_water_fraction_latent_sch = water_use_def_schedules[name]
454
+ else
455
+ service_water_fraction_latent_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
456
+ water_use_def_schedules[name] = service_water_fraction_latent_sch
457
+ end
458
+ water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
459
+
460
+ # add water use equipment
461
+ water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
462
+ water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
463
+ water_use_equip.setName("#{space_type.name} SWH")
464
+
465
+ # add water use connection
466
+ water_use_connection = OpenStudio::Model::WaterUseConnections.new(self)
467
+ water_use_connection.addWaterUseEquipment(water_use_equip)
468
+ water_use_connection.setName("#{space_type.name} WUC")
469
+
470
+ # gather inputs for add_swh_loop
471
+ sys_name = "#{space_type.name} Service Water Loop"
472
+ water_heater_thermal_zone = nil
473
+ water_heater_temp_si = 60.0 # C
474
+ service_water_pump_head = 0.01
475
+ service_water_pump_motor_efficiency = 1.0
476
+ if fuel.nil?
477
+ water_heater_fuel = "Gas"
478
+ else
479
+ water_heater_fuel = fuel
480
+ end
481
+
482
+ # find_water_heater_capacity_volume_and_parasitic
483
+ water_use_equipment_array = [water_use_equip]
484
+ water_heater_sizing = find_water_heater_capacity_volume_and_parasitic(water_use_equipment_array)
485
+ water_heater_capacity = water_heater_sizing[:water_heater_capacity]
486
+ water_heater_volume = water_heater_sizing[:water_heater_volume]
487
+ parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]
488
+
489
+ # make loop for each unit and add on water use equipment
490
+ dedicated_hot_water_loop = add_swh_loop(template,
491
+ sys_name,
492
+ water_heater_thermal_zone,
493
+ water_heater_temp_si,
494
+ service_water_pump_head,
495
+ service_water_pump_motor_efficiency,
496
+ water_heater_capacity,
497
+ water_heater_volume,
498
+ water_heater_fuel,
499
+ parasitic_fuel_consumption_rate,
500
+ stds_bldg_type)
501
+
502
+ # Connect the water use connection to the SWH loop
503
+ dedicated_hot_water_loop.addDemandBranchForComponent(water_use_connection)
504
+
505
+ # find water heater
506
+ dedicated_hot_water_loop.supplyComponents.each do |component|
507
+ next if not component.to_WaterHeaterMixed.is_initialized
508
+ water_heater = component.to_WaterHeaterMixed.get
509
+
510
+ # apply efficiency to hot water heater
511
+ water_heater.apply_efficiency(template)
512
+ end
513
+
514
+ # add to list of systems
515
+ swh_systems << dedicated_hot_water_loop
516
+
517
+ # add booster to all kitchens except for QuickServiceRestaurant (QuickServiceRestaurant assumed to use chemicals instead of hotter water)
518
+ # boosters are all 6 gal elec but heating capacity varies from 3 to 19 (kBtu/hr) for prototype buildings
519
+ if stds_space_type.include?("Kitchen") && stds_bldg_type != "QuickServiceRestaurant"
520
+
521
+ # find_water_heater_capacity_volume_and_parasitic
522
+ water_use_equipment_array = [water_use_equip]
523
+ inlet_temp_ip = OpenStudio::convert(service_water_temperature_si,"C","F").get # pre-booster temp
524
+ outlet_temp_ip = inlet_temp_ip + 40.0
525
+ peak_flow_fraction = 0.6 # assume 60% of peak for dish washing
526
+ water_heater_sizing = find_water_heater_capacity_volume_and_parasitic(water_use_equipment_array,pipe_hash = {},1.0,1.0,inlet_temp_ip,outlet_temp_ip,peak_flow_fraction)
527
+ water_heater_capacity = water_heater_sizing[:water_heater_capacity]
528
+
529
+ # gather additional inputs for add_swh_booster
530
+ water_heater_volume = OpenStudio::convert(6,"gal",'m^3').get
531
+ water_heater_fuel = "Electric"
532
+ booster_water_temperature = 82.22 # C
533
+ parasitic_fuel_consumption_rate = 0.0
534
+ booster_water_heater_thermal_zone = nil
535
+
536
+ # add_swh_booster
537
+ booster_service_water_loop = add_swh_booster(template,
538
+ dedicated_hot_water_loop,
539
+ water_heater_capacity,
540
+ water_heater_volume,
541
+ water_heater_fuel,
542
+ booster_water_temperature,
543
+ parasitic_fuel_consumption_rate,
544
+ booster_water_heater_thermal_zone,
545
+ stds_bldg_type)
546
+
547
+
548
+ # find water heater
549
+ booster_service_water_loop.supplyComponents.each do |component|
550
+ next if not component.to_WaterHeaterMixed.is_initialized
551
+ water_heater = component.to_WaterHeaterMixed.get
552
+
553
+ # apply efficiency to hot water heater
554
+ water_heater.apply_efficiency(template)
555
+ end
556
+
557
+ # rename booster loop
558
+ booster_service_water_loop.setName("#{space_type.name} Booster Service Water Loop")
559
+ 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}.")
560
+
561
+ end
562
+
563
+ else # store water use equip by building type in hash so can add general building type hot water loop
564
+
565
+ gal_hr_peak_flow_rate = gal_hr_per_area * floor_area_ip
566
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding water heating for #{space_type.name} space type with max flow rate of #{gal_hr_peak_flow_rate} gal/hr on a shared loop.")
567
+
568
+ # add water use equipment definition
569
+ water_use_equip_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(self)
570
+ water_use_equip_def.setName("#{space_type.name} SWH def")
571
+ peak_flow_rate_si = OpenStudio::convert(gal_hr_peak_flow_rate,"gal/hr","m^3/s").get
572
+ water_use_equip_def.setPeakFlowRate(peak_flow_rate_si)
573
+ target_temp = service_water_temperature_si # in spreadsheet in si, no conversion needed unless that changes
574
+ name = "#{target_temp} C"
575
+ if water_use_def_schedules.has_key?(name)
576
+ target_temperature_sch = water_use_def_schedules[name]
577
+ else
578
+ target_temperature_sch = self.add_constant_schedule_ruleset(target_temp,name)
579
+ water_use_def_schedules[name] = target_temperature_sch
580
+ end
581
+ water_use_equip_def.setTargetTemperatureSchedule(target_temperature_sch)
582
+ name = "#{service_water_fraction_sensible} Fraction"
583
+ if water_use_def_schedules.has_key?(name)
584
+ service_water_fraction_sensible_sch = water_use_def_schedules[name]
585
+ else
586
+ service_water_fraction_sensible_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
587
+ water_use_def_schedules[name] = service_water_fraction_sensible_sch
588
+ end
589
+ water_use_equip_def.setSensibleFractionSchedule(service_water_fraction_sensible_sch)
590
+ name = "#{service_water_fraction_latent} Fraction"
591
+ if water_use_def_schedules.has_key?(name)
592
+ service_water_fraction_latent_sch = water_use_def_schedules[name]
593
+ else
594
+ service_water_fraction_latent_sch = self.add_constant_schedule_ruleset(service_water_fraction_sensible,name)
595
+ water_use_def_schedules[name] = service_water_fraction_latent_sch
596
+ end
597
+ water_use_equip_def.setLatentFractionSchedule(service_water_fraction_latent_sch)
598
+
599
+ # add water use equipment
600
+ water_use_equip = OpenStudio::Model::WaterUseEquipment.new(water_use_equip_def)
601
+ water_use_equip.setFlowRateFractionSchedule(flow_rate_fraction_schedule)
602
+ water_use_equip.setName("#{space_type.name} SWH")
603
+
604
+ if water_use_equipment_hash.has_key?(stds_bldg_type)
605
+ water_use_equipment_hash[stds_bldg_type] << water_use_equip
606
+ else
607
+ water_use_equipment_hash[stds_bldg_type] = [water_use_equip]
608
+ end
609
+
610
+ end
611
+
612
+ end
613
+
614
+ # get building floor area and effective number of stories
615
+ bldg_floor_area = self.getBuilding.floorArea
616
+ bldg_effective_num_stories_hash = self.effective_num_stories
617
+ bldg_effective_num_stories = bldg_effective_num_stories_hash[:below_grade] + bldg_effective_num_stories_hash[:above_grade]
618
+
619
+ # add non-dedicated system(s) here. Separate systems for water use equipment from different building types
620
+ water_use_equipment_hash.each do |stds_bldg_type,water_use_equipment_array|
621
+
622
+ # gather inputs for add_swh_loop
623
+ sys_name = "#{stds_bldg_type} Shared Service Water Loop"
624
+ water_heater_thermal_zone = nil
625
+ water_heater_temp_si = 60.0
626
+
627
+ # find pump values
628
+ # Table A.2 in PrototypeModelEnhancements_2014_0.pdf shows 10ft on everything except SecondarySchool which has 11.4ft
629
+ # todo - if SmallOffice then shouldn't have circulating pump
630
+ if ["Office","PrimarySchool","Outpatient","Hospital","SmallHotel","LargeHotel","FullServiceRestaurant","HighriseApartment"].include?(stds_bldg_type)
631
+ service_water_pump_head = OpenStudio::convert(10.0,"ftH_{2}O","Pa").get
632
+ service_water_pump_motor_efficiency = 0.3
633
+ if circulating.nil? then irculating = true end
634
+ if pipe_insul_in.nil? then pipe_insul_in = 0.5 end
635
+ elsif ["SecondarySchool"].include?(stds_bldg_type)
636
+ service_water_pump_head = OpenStudio::convert(11.4,"ftH_{2}O","Pa").get
637
+ service_water_pump_motor_efficiency = 0.3
638
+ if circulating.nil? then irculating = true end
639
+ if pipe_insul_in.nil? then pipe_insul_in = 0.5 end
640
+ else # values for non-circulating pump
641
+ service_water_pump_head = 0.01
642
+ service_water_pump_motor_efficiency = 1.0
643
+ if circulating.nil? then irculating = false end
644
+ if pipe_insul_in.nil? then pipe_insul_in = 0.0 end
645
+ end
646
+
647
+ # todo - add building type or sice specific logic or just assume Gas? (SmallOffice and Warehouse are only non unit prototypes with Electric heating)
648
+ if fuel.nil?
649
+ water_heater_fuel = "Gas"
650
+ else
651
+ water_heater_fuel = fuel
652
+ end
653
+
654
+ bldg_type_floor_area = 0.0
655
+ space_type_hash.each do |space_type,hash|
656
+ next if not hash[:stds_bldg_type] == stds_bldg_type
657
+ bldg_type_floor_area += hash[:floor_area]
658
+ end
659
+
660
+ # inputs for find_water_heater_capacity_volume_and_parasitic
661
+ pipe_hash = {}
662
+ pipe_hash[:floor_area] = bldg_type_floor_area
663
+ pipe_hash[:effective_num_stories] = bldg_effective_num_stories * (bldg_type_floor_area/bldg_floor_area)
664
+ pipe_hash[:circulating] = circulating
665
+ pipe_hash[:insulation_thickness] = pipe_insul_in
666
+
667
+ # find_water_heater_capacity_volume_and_parasitic
668
+ water_heater_sizing = find_water_heater_capacity_volume_and_parasitic(water_use_equipment_array, pipe_hash)
669
+ water_heater_capacity = water_heater_sizing[:water_heater_capacity]
670
+ water_heater_volume = water_heater_sizing[:water_heater_volume]
671
+ parasitic_fuel_consumption_rate = water_heater_sizing[:parasitic_fuel_consumption_rate]
672
+ if parasitic_fuel_consumption_rate > 0
673
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding parasitic loss for #{stds_bldg_type} loopo of #{parasitic_fuel_consumption_rate.round} Btu/hr.")
674
+ end
675
+
676
+ # make loop for each unit and add on water use equipment
677
+ shared_hot_water_loop = add_swh_loop(template,
678
+ sys_name,
679
+ water_heater_thermal_zone,
680
+ water_heater_temp_si,
681
+ service_water_pump_head,
682
+ service_water_pump_motor_efficiency,
683
+ water_heater_capacity,
684
+ water_heater_volume,
685
+ water_heater_fuel,
686
+ parasitic_fuel_consumption_rate,
687
+ stds_bldg_type)
688
+
689
+ # find water heater
690
+ shared_hot_water_loop.supplyComponents.each do |component|
691
+ next if not component.to_WaterHeaterMixed.is_initialized
692
+ water_heater = component.to_WaterHeaterMixed.get
693
+
694
+ # apply efficiency to hot water heater
695
+ water_heater.apply_efficiency(template)
696
+ end
697
+
698
+ # loop through water use equipment
699
+ water_use_equipment_array.each do |water_use_equip|
700
+ # add water use connection
701
+ water_use_connection = OpenStudio::Model::WaterUseConnections.new(self)
702
+ water_use_connection.addWaterUseEquipment(water_use_equip)
703
+ water_use_connection.setName(water_use_equip.name.get.gsub("SWH","WUC"))
704
+
705
+ # Connect the water use connection to the SWH loop
706
+ shared_hot_water_loop.addDemandBranchForComponent(water_use_connection)
707
+ end
708
+
709
+ # add to list of systems
710
+ swh_systems << shared_hot_water_loop
711
+
712
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Adding shared water heating loop for #{stds_bldg_type}.")
713
+
714
+ end
715
+
716
+ return swh_systems
717
+
718
+ end
719
+
720
+ # set capacity, volume, and parasitic
721
+ #
722
+ # @param [Array] array of water use equipment objects that will be using this water heater
723
+ # @param [Double] storage_to_cap_ratio gal of storage to kBtu/hr of capacitiy
724
+ # @param [Double] htg_eff fraction
725
+ # @param [Double] cld_wtr_temp_ip cold water temperature F
726
+ # @param [Double] target_temp F
727
+ # @return [Hash] hash with values needed to size water heater made with downstream method
728
+ def find_water_heater_capacity_volume_and_parasitic(water_use_equipment_array, pipe_hash = {}, storage_to_cap_ratio = 1.0,htg_eff = 0.8,inlet_temp_ip = 40.0,target_temp_ip = 140.0,peak_flow_fraction = 1.0)
729
+
730
+ # 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
731
+
732
+ water_heater_sizing = {}
733
+
734
+ # get water use equipment
735
+ max_flow_rate_array = [] # gallons per hour
736
+ water_use_equipment_array.each do |water_use_equip|
737
+ water_use_equip_sch = water_use_equip.flowRateFractionSchedule
738
+ next if not water_use_equip_sch.is_initialized and water_use_equip_sch.get.to_ScheduleRuleset.is_initialized
739
+ water_use_equip_sch = water_use_equip_sch.get.to_ScheduleRuleset.get
740
+ max_sch_value = water_use_equip_sch.annual_min_max_value['max']
741
+
742
+ # get water_use_equip_def to get max flow rate
743
+ water_use_equip_def = water_use_equip.waterUseEquipmentDefinition
744
+ peak_flow_rate = water_use_equip_def.peakFlowRate
745
+
746
+ # calculate adjusted flow rate
747
+ adjusted_peak_flow_rate_si = max_sch_value * peak_flow_rate
748
+ adjusted_peak_flow_rate_ip = OpenStudio::convert(adjusted_peak_flow_rate_si,"m^3/s","gal/min").get
749
+ max_flow_rate_array << adjusted_peak_flow_rate_ip * 60.0 # min per hour
750
+ end
751
+
752
+ # warn if max_flow_rate_array size doesn't match equipment size (one or more didn't have ruleset schedule)
753
+ if max_flow_rate_array.size != water_use_equipment_array.size
754
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "One or more Water Use Equipment Fraction Flow Rate Scheules were not Schedule Rulestes and were excluding from Water Heating Sizing.")
755
+ end
756
+
757
+ # sum gpm values from water use equipment to use in formula
758
+ adjusted_flow_rate_sum = max_flow_rate_array.inject(:+)
759
+
760
+ # use formula to calculate volume and capacity based on analysis of combined water use equipment maximum flow rates and schedules
761
+ # Max gal/hr * 8.4 lb/gal * 1 Btu/lb F * (120F - 40F)/0.8 = Btu/hr
762
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Capacity is #{peak_flow_fraction} * #{adjusted_flow_rate_sum} gal/hr * 8.4 * 1.0 * (#{target_temp_ip} - #{inlet_temp_ip}/ #{htg_eff}).")
763
+ water_heater_capacity_ip = peak_flow_fraction * adjusted_flow_rate_sum * 8.4 * 1.0 * (target_temp_ip - inlet_temp_ip) / htg_eff
764
+ water_heater_capacity_si = OpenStudio::convert(water_heater_capacity_ip,"Btu/hr","W").get
765
+ water_heater_volume_ip = OpenStudio::convert(water_heater_capacity_ip,"Btu/hr","kBtu/hr").get
766
+ # increase tank size to 40 galons if calculated value is smaller
767
+ if water_heater_volume_ip < 40.0 # gal
768
+ water_heater_volume_ip = 40.0
769
+ end
770
+ water_heater_volume_si = OpenStudio::convert(water_heater_volume_ip,"gal","m^3").get
771
+
772
+ # populate return hash
773
+ water_heater_sizing[:water_heater_capacity] = water_heater_capacity_si
774
+ water_heater_sizing[:water_heater_volume] = water_heater_volume_si
775
+
776
+ # get pipe length (formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf)
777
+ if pipe_hash.size > 0
778
+
779
+ pipe_length = 2.0 * (Math.sqrt(pipe_hash[:floor_area]/pipe_hash[:effective_num_stories]) + (10.0 * (pipe_hash[:effective_num_stories]-1.0)))
780
+ pipe_length_ip = OpenStudio::convert(pipe_length,"m","ft").get
781
+
782
+ # calculate pipe dump (from A.4.1)
783
+ pipe_dump = pipe_length_ip * 0.689 # Btu/hr
784
+
785
+ if pipe_hash[:circulating]
786
+ if pipe_hash[:insulation_thickness] >= 1.0
787
+ pipe_loss_per_foot = 16.10
788
+ elsif pipe_hash[:insulation_thickness] >= 0.5
789
+ pipe_loss_per_foot = 17.5
790
+ else
791
+ pipe_loss_per_foot = 30.8
792
+ end
793
+ else
794
+ if pipe_hash[:insulation_thickness] >= 1.0
795
+ pipe_loss_per_foot = 11.27
796
+ elsif pipe_hash[:insulation_thickness] >= 0.5
797
+ pipe_loss_per_foot = 12.25
798
+ else
799
+ pipe_loss_per_foot = 28.07
800
+ end
801
+ end
802
+
803
+ # calculate pipe loss (from Table A.3 in section A.4.2)
804
+ pipe_loss = pipe_length * pipe_loss_per_foot # Btu/hr
805
+
806
+ # calculate parasitic loss
807
+ water_heater_sizing[:parasitic_fuel_consumption_rate] = pipe_dump + pipe_loss
808
+ else
809
+ water_heater_sizing[:parasitic_fuel_consumption_rate] = 0.0
810
+ end
811
+
812
+ return water_heater_sizing
813
+
814
+ end
815
+
280
816
  end