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.
- checksums.yaml +4 -4
- data/data/standards/OpenStudio_Standards.xlsx +0 -0
- data/data/standards/OpenStudio_Standards_climate_zone_sets.json +96 -0
- data/data/standards/OpenStudio_Standards_climate_zones.json +96 -0
- data/data/standards/OpenStudio_Standards_construction_properties.json +11424 -0
- data/data/standards/OpenStudio_Standards_construction_sets.json +1746 -24
- data/data/standards/OpenStudio_Standards_constructions.json +409 -0
- data/data/standards/OpenStudio_Standards_curve_biquadratics.json +160 -0
- data/data/standards/OpenStudio_Standards_curve_quadratics.json +165 -0
- data/data/standards/OpenStudio_Standards_entryways.json +191 -0
- data/data/standards/OpenStudio_Standards_exterior_lighting.json +964 -0
- data/data/standards/OpenStudio_Standards_exterior_lighting_assumptions.json +191 -0
- data/data/standards/OpenStudio_Standards_ground_temperatures.json +11424 -0
- data/data/standards/OpenStudio_Standards_illuminated_parking_area.json +157 -0
- data/data/standards/OpenStudio_Standards_materials.json +2539 -745
- data/data/standards/OpenStudio_Standards_motors.json +420 -0
- data/data/standards/OpenStudio_Standards_parking.json +157 -0
- data/data/standards/OpenStudio_Standards_prototype_inputs.json +4410 -10
- data/data/standards/OpenStudio_Standards_schedules.json +13756 -8383
- data/data/standards/OpenStudio_Standards_space_types.json +8593 -907
- data/data/standards/OpenStudio_Standards_standards.json +9 -0
- data/data/standards/OpenStudio_Standards_templates.json +24 -0
- data/data/standards/OpenStudio_Standards_unitary_acs.json +924 -0
- data/data/standards/manage_OpenStudio_Standards.rb +1 -1
- data/lib/openstudio-standards/btap/btap.rb +0 -1
- data/lib/openstudio-standards/btap/compliance.rb +2 -5
- data/lib/openstudio-standards/btap/economics.rb +16 -16
- data/lib/openstudio-standards/btap/envelope.rb +1 -1
- data/lib/openstudio-standards/btap/equest.rb +7 -7
- data/lib/openstudio-standards/btap/fileio.rb +2 -2
- data/lib/openstudio-standards/btap/geometry.rb +1 -1
- data/lib/openstudio-standards/btap/measures.rb +16 -16
- data/lib/openstudio-standards/btap/mpc.rb +18 -18
- data/lib/openstudio-standards/btap/schedules.rb +35 -2
- data/lib/openstudio-standards/btap/simmanager.rb +1 -1
- data/lib/openstudio-standards/btap/spaceloads.rb +1 -1
- data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
- data/lib/openstudio-standards/prototypes/Prototype.Model.elevators.rb +218 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.exterior_lights.rb +399 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +2 -2
- data/lib/openstudio-standards/prototypes/Prototype.Model.rb +237 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +536 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +6 -0
- data/lib/openstudio-standards/standards/Standards.Model.rb +249 -10
- data/lib/openstudio-standards/standards/Standards.SpaceType.rb +5 -5
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +161 -0
- data/lib/openstudio-standards/utilities/simulation.rb +6 -4
- data/lib/openstudio-standards/version.rb +1 -1
- metadata +223 -204
- 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 #{
|
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 #{
|
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
|