openstudio-extension 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +2 -0
  6. data/lib/openstudio/extension/runner.rb +12 -8
  7. data/lib/openstudio/extension/runner_config.rb +33 -6
  8. data/lib/openstudio/extension/version.rb +1 -1
  9. data/openstudio-extension.gemspec +6 -6
  10. metadata +15 -66
  11. data/lib/openstudio/extension/core/CreateResults.rb +0 -1033
  12. data/lib/openstudio/extension/core/check_air_sys_temps.rb +0 -160
  13. data/lib/openstudio/extension/core/check_calibration.rb +0 -125
  14. data/lib/openstudio/extension/core/check_cond_zns.rb +0 -54
  15. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +0 -304
  16. data/lib/openstudio/extension/core/check_envelope_conductance.rb +0 -423
  17. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +0 -132
  18. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +0 -105
  19. data/lib/openstudio/extension/core/check_fan_pwr.rb +0 -68
  20. data/lib/openstudio/extension/core/check_internal_loads.rb +0 -363
  21. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +0 -196
  22. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +0 -296
  23. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +0 -434
  24. data/lib/openstudio/extension/core/check_mech_sys_type.rb +0 -109
  25. data/lib/openstudio/extension/core/check_part_loads.rb +0 -421
  26. data/lib/openstudio/extension/core/check_placeholder.rb +0 -45
  27. data/lib/openstudio/extension/core/check_plant_cap.rb +0 -93
  28. data/lib/openstudio/extension/core/check_plant_temps.rb +0 -129
  29. data/lib/openstudio/extension/core/check_plenum_loads.rb +0 -57
  30. data/lib/openstudio/extension/core/check_pump_pwr.rb +0 -78
  31. data/lib/openstudio/extension/core/check_sch_coord.rb +0 -211
  32. data/lib/openstudio/extension/core/check_schedules.rb +0 -281
  33. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +0 -128
  34. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +0 -118
  35. data/lib/openstudio/extension/core/check_weather_files.rb +0 -102
  36. data/lib/openstudio/extension/core/deer_vintages.rb +0 -281
  37. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +0 -461
  38. data/lib/openstudio/extension/core/os_lib_constructions.rb +0 -353
  39. data/lib/openstudio/extension/core/os_lib_geometry.rb +0 -1169
  40. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +0 -383
  41. data/lib/openstudio/extension/core/os_lib_hvac.rb +0 -2163
  42. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +0 -184
  43. data/lib/openstudio/extension/core/os_lib_model_generation.rb +0 -3584
  44. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +0 -1019
  45. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +0 -135
  46. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +0 -170
  47. data/lib/openstudio/extension/core/os_lib_schedules.rb +0 -933
@@ -1,3584 +0,0 @@
1
- # ComStock(TM), Copyright (c) 2020 Alliance for Sustainable Energy, LLC. All rights reserved.
2
- # See top level LICENSE.txt file for license terms.
3
-
4
- # *******************************************************************************
5
- # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
6
- # See also https://openstudio.net/license
7
- # *******************************************************************************
8
-
9
- # used to load haystack json in typical_building_from_model
10
- require 'json'
11
-
12
- module OsLib_ModelGeneration
13
- # simple list of building types that are valid for get_space_types_from_building_type
14
- # for general public use use extended = false
15
- def get_building_types(extended = false)
16
- # get building_types
17
- if extended
18
- doe = get_doe_building_types(true)
19
- deer = get_deer_building_types(true)
20
- else
21
- doe = get_doe_building_types
22
- deer = get_deer_building_types
23
- end
24
-
25
- # combine building_types
26
- array = OpenStudio::StringVector.new
27
- temp_array = doe.to_a + deer.to_a
28
- temp_array.each do |i|
29
- array << i
30
- end
31
-
32
- return array
33
- end
34
-
35
- # get_doe_building_types
36
- # for general public use use extended = false
37
- def get_doe_building_types(extended = false)
38
- # DOE Prototypes
39
- array = OpenStudio::StringVector.new
40
- array << 'SecondarySchool'
41
- array << 'PrimarySchool'
42
- array << 'SmallOffice'
43
- array << 'MediumOffice'
44
- array << 'LargeOffice'
45
- array << 'SmallHotel'
46
- array << 'LargeHotel'
47
- array << 'Warehouse'
48
- array << 'RetailStandalone'
49
- array << 'RetailStripmall'
50
- array << 'QuickServiceRestaurant'
51
- array << 'FullServiceRestaurant'
52
- array << 'MidriseApartment'
53
- array << 'HighriseApartment'
54
- array << 'Hospital'
55
- array << 'Outpatient'
56
- array << 'SuperMarket'
57
- array << 'Laboratory'
58
- array << 'LargeDataCenterLowITE'
59
- array << 'LargeDataCenterHighITE'
60
- array << 'SmallDataCenterLowITE'
61
- array << 'SmallDataCenterHighITE'
62
- array << 'Courthouse'
63
- array << 'College'
64
-
65
- return array
66
- end
67
-
68
- # get_deer_building_types
69
- # for general public use use extended = false
70
- def get_deer_building_types(extended = false)
71
- # DOE Prototypes
72
- array = OpenStudio::StringVector.new
73
- array << 'Asm'
74
- array << 'DMo'
75
- array << 'ECC'
76
- array << 'EPr'
77
- array << 'ERC'
78
- array << 'ESe'
79
- array << 'EUn'
80
- array << 'GHs'
81
- array << 'Gro'
82
- array << 'Hsp'
83
- array << 'Htl'
84
- array << 'MBT'
85
- array << 'MFm'
86
- array << 'MLI'
87
- array << 'Mtl'
88
- array << 'Nrs'
89
- array << 'OfL'
90
- array << 'OfS'
91
- array << 'RFF'
92
- array << 'RSD'
93
- array << 'Rt3'
94
- array << 'RtL'
95
- array << 'RtS'
96
- array << 'SCn'
97
- array << 'SFm'
98
- array << 'SUn'
99
- array << 'WRf'
100
-
101
- return array
102
- end
103
-
104
- # simple list of templates that are valid for get_space_types_from_building_type
105
- # for general public use use extended = false
106
- def get_templates(extended = false)
107
- # get templates
108
- if extended
109
- doe = get_doe_templates(true)
110
- deer = get_deer_templates(true)
111
- else
112
- doe = get_doe_templates
113
- deer = get_deer_templates
114
- end
115
-
116
- # combine templates
117
- array = OpenStudio::StringVector.new
118
- temp_array = doe.to_a + deer.to_a
119
- temp_array.each do |i|
120
- array << i
121
- end
122
-
123
- return array
124
- end
125
-
126
- # get_doe_templates
127
- # for general public use use extended = false
128
- def get_doe_templates(extended = false)
129
- array = OpenStudio::StringVector.new
130
- array << 'DOE Ref Pre-1980'
131
- array << 'DOE Ref 1980-2004'
132
- array << '90.1-2004'
133
- array << '90.1-2007'
134
- array << '90.1-2010'
135
- array << '90.1-2013'
136
- array << '90.1-2016'
137
- array << '90.1-2019'
138
- array << 'ComStock DOE Ref Pre-1980'
139
- array << 'ComStock DOE Ref 1980-2004'
140
- array << 'ComStock 90.1-2004'
141
- array << 'ComStock 90.1-2007'
142
- array << 'ComStock 90.1-2010'
143
- array << 'ComStock 90.1-2013'
144
- array << 'ComStock 90.1-2016'
145
- array << 'ComStock 90.1-2019'
146
- if extended
147
- # array << '189.1-2009' # if turn this on need to update space_type_array for RetailStripmall
148
- array << 'NREL ZNE Ready 2017'
149
- end
150
-
151
- return array
152
- end
153
-
154
- # get_deer_templates
155
- # for general public use use extended = false
156
- def get_deer_templates(extended = false)
157
- array = OpenStudio::StringVector.new
158
- array << 'DEER Pre-1975'
159
- array << 'DEER 1985'
160
- array << 'DEER 1996'
161
- array << 'DEER 2003'
162
- array << 'DEER 2007'
163
- array << 'DEER 2011'
164
- array << 'DEER 2014'
165
- array << 'DEER 2015'
166
- array << 'DEER 2017'
167
- array << 'DEER 2020'
168
- if extended
169
- array << 'DEER 2025'
170
- array << 'DEER 2030'
171
- array << 'DEER 2035'
172
- array << 'DEER 2040'
173
- array << 'DEER 2045'
174
- array << 'DEER 2050'
175
- array << 'DEER 2055'
176
- array << 'DEER 2060'
177
- array << 'DEER 2065'
178
- array << 'DEER 2070'
179
- array << 'DEER 2075'
180
- end
181
-
182
- return array
183
- end
184
-
185
- # get_climate_zones
186
- # for general public use use extended = false
187
- def get_climate_zones(extended = false, extra = nil)
188
-
189
- # get climate_zones
190
- if extended && extra != nil
191
- doe = get_doe_climate_zones(true, extra)
192
- deer = get_deer_climate_zones(true, nil)
193
- elsif extended
194
- doe = get_doe_climate_zones(true, nil)
195
- deer = get_deer_climate_zones(true, nil)
196
- elsif extra != nil
197
- doe = get_doe_climate_zones(false, extra)
198
- deer = get_deer_climate_zones(false, nil)
199
- else
200
- doe = get_doe_climate_zones
201
- deer = get_deer_climate_zones
202
- end
203
-
204
- # combine climate zones
205
- array = OpenStudio::StringVector.new
206
- temp_array = doe.to_a + deer.to_a
207
- temp_array.each do |i|
208
- array << i
209
- end
210
-
211
- return array
212
- end
213
-
214
- # get_doe_climate_zones
215
- # for general public use use extended = false
216
- def get_doe_climate_zones(extended = false, extra = nil)
217
- # Lookup From Model should be added as an option where appropriate in the measure
218
- cz_choices = OpenStudio::StringVector.new
219
- if !extra.nil?
220
- cz_choices << extra
221
- end
222
- cz_choices << 'ASHRAE 169-2013-1A'
223
- cz_choices << 'ASHRAE 169-2013-1B'
224
- cz_choices << 'ASHRAE 169-2013-2A'
225
- cz_choices << 'ASHRAE 169-2013-2B'
226
- cz_choices << 'ASHRAE 169-2013-3A'
227
- cz_choices << 'ASHRAE 169-2013-3B'
228
- cz_choices << 'ASHRAE 169-2013-3C'
229
- cz_choices << 'ASHRAE 169-2013-4A'
230
- cz_choices << 'ASHRAE 169-2013-4B'
231
- cz_choices << 'ASHRAE 169-2013-4C'
232
- cz_choices << 'ASHRAE 169-2013-5A'
233
- cz_choices << 'ASHRAE 169-2013-5B'
234
- cz_choices << 'ASHRAE 169-2013-5C'
235
- cz_choices << 'ASHRAE 169-2013-6A'
236
- cz_choices << 'ASHRAE 169-2013-6B'
237
- cz_choices << 'ASHRAE 169-2013-7A'
238
- cz_choices << 'ASHRAE 169-2013-8A'
239
- if extended
240
- cz_choices << 'ASHRAE 169-2013-0A'
241
- cz_choices << 'ASHRAE 169-2013-0B'
242
- end
243
-
244
- return cz_choices
245
- end
246
-
247
- # get_deer_climate_zones
248
- # for general public use use extended = false
249
- def get_deer_climate_zones(extended = false, extra = nil)
250
- # Lookup From Model should be added as an option where appropriate in the measure
251
- cz_choices = OpenStudio::StringVector.new
252
- if !extra.nil?
253
- cz_choices << extra
254
- end
255
- cz_choices << 'CEC T24-CEC1'
256
- cz_choices << 'CEC T24-CEC2'
257
- cz_choices << 'CEC T24-CEC3'
258
- cz_choices << 'CEC T24-CEC4'
259
- cz_choices << 'CEC T24-CEC5'
260
- cz_choices << 'CEC T24-CEC6'
261
- cz_choices << 'CEC T24-CEC7'
262
- cz_choices << 'CEC T24-CEC8'
263
- cz_choices << 'CEC T24-CEC9'
264
- cz_choices << 'CEC T24-CEC10'
265
- cz_choices << 'CEC T24-CEC11'
266
- cz_choices << 'CEC T24-CEC12'
267
- cz_choices << 'CEC T24-CEC13'
268
- cz_choices << 'CEC T24-CEC14'
269
- cz_choices << 'CEC T24-CEC15'
270
- cz_choices << 'CEC T24-CEC16'
271
-
272
- return cz_choices
273
- end
274
-
275
- # calculate aspect ratio from area and perimeter
276
- def calc_aspect_ratio(a, p)
277
- l = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
278
- w = 0.25 * (p - Math.sqrt(p**2 - 16 * a))
279
- aspect_ratio = l / w
280
-
281
- return aspect_ratio
282
- end
283
-
284
- # Building Form Defaults from Table 4.2 in Achieving the 30% Goal: Energy and Cost Savings Analysis of ASHRAE Standard 90.1-2010
285
- # aspect ratio for NA replaced with floor area to perimeter ratio from prototype model
286
- # currently no reason to split apart doe and deer inputs here
287
- def building_form_defaults(building_type)
288
- hash = {}
289
-
290
- # DOE Prototypes
291
-
292
- # calculate aspect ratios not represented on Table 4.2
293
- primary_footprint = 73958.0
294
- primary_p = 619.0 # wrote measure using calculate_perimeter method in os_lib_geometry
295
- primary_ns_ew_ratio = 2.829268293 # estimated from ratio of ns/ew total wall area
296
- primary_width = Math.sqrt(primary_footprint / primary_ns_ew_ratio)
297
- primary_p_min = 2 * (primary_width + primary_width / primary_footprint)
298
- primary_p_mult = primary_p / primary_p_min
299
-
300
- secondary_footprint = 210887.0 / 2.0 # floor area divided by area instead of true footprint 128112.0)
301
- secondary_p = 708.0 # wrote measure using calculate_perimeter method in os_lib_geometry
302
- secondary_ns_ew_ratio = 2.069230769 # estimated from ratio of ns/ew total wall area
303
- secondary_width = Math.sqrt(secondary_footprint / secondary_ns_ew_ratio)
304
- secondary_p_min = 2 * (secondary_width + secondary_width / secondary_footprint)
305
- secondary_p_mult = secondary_p / secondary_p_min
306
-
307
- outpatient_footprint = 40946.0 / 3.0 # floor area divided by area instead of true footprint 17872.0)
308
- outpatient_p = 537.0 # wrote measure using calculate_perimeter method in os_lib_geometry
309
- outpatient_ns_ew_ratio = 1.56448737 # estimated from ratio of ns/ew total wall area
310
- outpatient_width = Math.sqrt(outpatient_footprint / outpatient_ns_ew_ratio)
311
- outpatient_p_min = 2 * (outpatient_width + outpatient_footprint / outpatient_width)
312
- outpatient_p_mult = outpatient_p / outpatient_p_min
313
-
314
- # primary_aspet_ratio = calc_aspect_ratio(73958.0, 2060.0)
315
- # secondary_aspet_ratio = calc_aspect_ratio(128112.0, 2447.0)
316
- # outpatient_aspet_ratio = calc_aspect_ratio(14782.0, 588.0)
317
- supermarket_a = 45001.0
318
- supermarket_p = 866.0
319
- supermarket_wwr = 1880.0 / (supermarket_p * 20.0)
320
- supermarket_aspect_ratio = calc_aspect_ratio(supermarket_a, supermarket_p)
321
-
322
- hash['SmallOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
323
- hash['MediumOffice'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 13.0, perim_mult: 1.0 }
324
- hash['LargeOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 13.0, perim_mult: 1.0 }
325
- hash['RetailStandalone'] = { aspect_ratio: 1.28, wwr: 0.07, typical_story: 20.0, perim_mult: 1.0 }
326
- hash['RetailStripmall'] = { aspect_ratio: 4.0, wwr: 0.11, typical_story: 17.0, perim_mult: 1.0 }
327
- hash['PrimarySchool'] = { aspect_ratio: primary_ns_ew_ratio.round(1), wwr: 0.35, typical_story: 13.0, perim_mult: primary_p_mult.round(3) }
328
- hash['SecondarySchool'] = { aspect_ratio: secondary_ns_ew_ratio.round(1), wwr: 0.33, typical_story: 13.0, perim_mult: secondary_p_mult.round(3) }
329
- hash['Outpatient'] = { aspect_ratio: outpatient_ns_ew_ratio.round(1), wwr: 0.20, typical_story: 10.0, perim_mult: outpatient_p_mult.round(3) }
330
- hash['Hospital'] = { aspect_ratio: 1.33, wwr: 0.16, typical_story: 14.0, perim_mult: 1.0 }
331
- hash['SmallHotel'] = { aspect_ratio: 3.0, wwr: 0.11, typical_story: 9.0, first_story: 11.0, perim_mult: 1.0 }
332
- hash['LargeHotel'] = { aspect_ratio: 5.1, wwr: 0.27, typical_story: 10.0, first_story: 13.0, perim_mult: 1.0 }
333
-
334
- # code in get_space_types_from_building_type is used to override building wwr with space type specific wwr
335
- hash['Warehouse'] = { aspect_ratio: 2.2, wwr: 0.0, typical_story: 28.0, perim_mult: 1.0 }
336
-
337
- hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.14, typical_story: 10.0, perim_mult: 1.0 }
338
- hash['FullServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
339
- hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
340
- hash['MidriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
341
- hash['HighriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
342
- # SuperMarket inputs come from prototype model
343
- hash['SuperMarket'] = { aspect_ratio: supermarket_aspect_ratio.round(1), wwr: supermarket_wwr.round(2), typical_story: 20.0, perim_mult: 1.0 }
344
-
345
- # Add Laboratory and Data Centers
346
- hash['Laboratory'] = { aspect_ratio: 1.33, wwr: 0.12, typical_story: 10.0, perim_mult: 1.0 }
347
- hash['LargeDataCenterLowITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
348
- hash['LargeDataCenterHighITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
349
- hash['SmallDataCenterLowITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
350
- hash['SmallDataCenterHighITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
351
-
352
- # Add Courthouse and Education
353
- hash['Courthouse'] = { aspect_ratio: 2.06, wwr: 0.18, typical_story: 16.0, perim_mult: 1.0 }
354
- hash['College'] = { aspect_ratio: 2.5, wwr: 0.037, typical_story: 13.0, perim_mult: 1.0 }
355
-
356
- # DEER Prototypes
357
- hash['Asm'] = { aspect_ratio: 1.0, wwr: 0.19, typical_story: 15.0 }
358
- hash['ECC'] = { aspect_ratio: 4.0, wwr: 0.25, typical_story: 13.0 }
359
- hash['EPr'] = { aspect_ratio: 2.0, wwr: 0.16, typical_story: 12.0 }
360
- hash['ERC'] = { aspect_ratio: 1.7, wwr: 0.03, typical_story: 12.0 }
361
- hash['ESe'] = { aspect_ratio: 1.0, wwr: 0.15, typical_story: 13.0 }
362
- hash['EUn'] = { aspect_ratio: 2.5, wwr: 0.3, typical_story: 14.0 }
363
- hash['Gro'] = { aspect_ratio: 1.0, wwr: 0.07, typical_story: 25.0 }
364
- hash['Hsp'] = { aspect_ratio: 1.5, wwr: 0.11, typical_story: 13.0 }
365
- hash['Htl'] = { aspect_ratio: 3.0, wwr: 0.23, typical_story: 9.5, first_story: 12.0 }
366
- hash['MBT'] = { aspect_ratio: 10.7, wwr: 0.12, typical_story: 15.0 }
367
- hash['MFm'] = { aspect_ratio: 1.4, wwr: 0.24, typical_story: 9.5 }
368
- hash['MLI'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 35.0 }
369
- hash['Mtl'] = { aspect_ratio: 5.1, wwr: 0.41, typical_story: 9.0 }
370
- hash['Nrs'] = { aspect_ratio: 10.3, wwr: 0.2, typical_story: 13.0 }
371
- hash['OfL'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
372
- hash['OfS'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
373
- hash['RFF'] = { aspect_ratio: 1.0, wwr: 0.25, typical_story: 13.0 }
374
- hash['RSD'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 13.0 }
375
- hash['Rt3'] = { aspect_ratio: 1.0, wwr: 0.02, typical_story: 20.8 }
376
- hash['RtL'] = { aspect_ratio: 1.0, wwr: 0.03, typical_story: 20.5 }
377
- hash['RtS'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 12.0 }
378
- hash['SCn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
379
- hash['SUn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
380
- hash['WRf'] = { aspect_ratio: 1.6, wwr: 0.0, typical_story: 32.0 }
381
-
382
- return hash[building_type]
383
- end
384
-
385
- # create hash of space types and generic ratios of building floor area
386
- # currently no reason to split apart doe and deer inputs here
387
- def get_space_types_from_building_type(building_type, template, whole_building = true)
388
- hash = {}
389
-
390
- # TODO: - Confirm that these work for all standards
391
- # DOE Prototypes
392
- if building_type == 'SecondarySchool'
393
- if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'ComStock DOE Ref Pre-1980', 'ComStock DOE Ref 1980-2004'].include?(template)
394
- hash['Auditorium'] = { ratio: 0.0504, space_type_gen: true, default: false, story_height: 26.0 }
395
- hash['Cafeteria'] = { ratio: 0.0319, space_type_gen: true, default: false }
396
- hash['Classroom'] = { ratio: 0.3528, space_type_gen: true, default: true }
397
- hash['Corridor'] = { ratio: 0.2144, space_type_gen: true, default: false, circ: true }
398
- hash['Gym'] = { ratio: 0.1009, space_type_gen: true, default: false, story_height: 26.0 }
399
- hash['Gym - audience'] = { ratio: 0.0637, space_type_gen: true, default: false, story_height: 26.0 }
400
- hash['Kitchen'] = { ratio: 0.0110, space_type_gen: true, default: false }
401
- hash['Library'] = { ratio: 0.0429, space_type_gen: true, default: false }
402
- hash['Lobby'] = { ratio: 0.0214, space_type_gen: true, default: false }
403
- hash['Mechanical'] = { ratio: 0.0349, space_type_gen: true, default: false }
404
- hash['Office'] = { ratio: 0.0543, space_type_gen: true, default: false }
405
- hash['Restroom'] = { ratio: 0.0214, space_type_gen: true, default: false }
406
- else
407
- hash['Auditorium'] = { ratio: 0.0504, space_type_gen: true, default: false, story_height: 26.0 }
408
- hash['Cafeteria'] = { ratio: 0.0319, space_type_gen: true, default: false }
409
- hash['Classroom'] = { ratio: 0.3041, space_type_gen: true, default: true }
410
- hash['ComputerRoom'] = { ratio: 0.0487, space_type_gen: true, default: true }
411
- hash['Corridor'] = { ratio: 0.2144, space_type_gen: true, default: false, circ: true }
412
- hash['Gym'] = { ratio: 0.1646, space_type_gen: true, default: false, story_height: 26.0 }
413
- hash['Kitchen'] = { ratio: 0.0110, space_type_gen: true, default: false }
414
- hash['Library'] = { ratio: 0.0429, space_type_gen: true, default: false }
415
- hash['Lobby'] = { ratio: 0.0214, space_type_gen: true, default: false }
416
- hash['Mechanical'] = { ratio: 0.0349, space_type_gen: true, default: false }
417
- hash['Office'] = { ratio: 0.0543, space_type_gen: true, default: false }
418
- hash['Restroom'] = { ratio: 0.0214, space_type_gen: true, default: false }
419
- end
420
- elsif building_type == 'PrimarySchool'
421
- if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'ComStock DOE Ref Pre-1980', 'ComStock DOE Ref 1980-2004'].include?(template)
422
- # updated to 2004 which includes library vs. pre-1980
423
- hash['Cafeteria'] = { ratio: 0.0458, space_type_gen: true, default: false }
424
- hash['Classroom'] = { ratio: 0.5610, space_type_gen: true, default: true }
425
- hash['Corridor'] = { ratio: 0.1633, space_type_gen: true, default: false, circ: true }
426
- hash['Gym'] = { ratio: 0.0520, space_type_gen: true, default: false }
427
- hash['Kitchen'] = { ratio: 0.0244, space_type_gen: true, default: false }
428
- hash['Library'] = { ratio: 0.0, space_type_gen: true, default: false } # no library in model
429
- hash['Lobby'] = { ratio: 0.0249, space_type_gen: true, default: false }
430
- hash['Mechanical'] = { ratio: 0.0367, space_type_gen: true, default: false }
431
- hash['Office'] = { ratio: 0.0642, space_type_gen: true, default: false }
432
- hash['Restroom'] = { ratio: 0.0277, space_type_gen: true, default: false }
433
- else
434
- # updated to 2004 which includes library vs. pre-1980
435
- hash['Cafeteria'] = { ratio: 0.0458, space_type_gen: true, default: false }
436
- hash['Classroom'] = { ratio: 0.4793, space_type_gen: true, default: true }
437
- hash['ComputerRoom'] = { ratio: 0.0236, space_type_gen: true, default: true }
438
- hash['Corridor'] = { ratio: 0.1633, space_type_gen: true, default: false, circ: true }
439
- hash['Gym'] = { ratio: 0.0520, space_type_gen: true, default: false }
440
- hash['Kitchen'] = { ratio: 0.0244, space_type_gen: true, default: false }
441
- hash['Library'] = { ratio: 0.0581, space_type_gen: true, default: false }
442
- hash['Lobby'] = { ratio: 0.0249, space_type_gen: true, default: false }
443
- hash['Mechanical'] = { ratio: 0.0367, space_type_gen: true, default: false }
444
- hash['Office'] = { ratio: 0.0642, space_type_gen: true, default: false }
445
- hash['Restroom'] = { ratio: 0.0277, space_type_gen: true, default: false }
446
- end
447
- elsif building_type == 'SmallOffice'
448
- # TODO: - populate Small, Medium, and Large office for whole_building false
449
- if whole_building
450
- hash['WholeBuilding - Sm Office'] = { ratio: 1.0, space_type_gen: true, default: true }
451
- else
452
- hash['SmallOffice - Breakroom'] = { ratio: 0.99, space_type_gen: true, default: false }
453
- hash['SmallOffice - ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
454
- hash['SmallOffice - Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
455
- hash['SmallOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
456
- hash['SmallOffice - Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
457
- hash['SmallOffice - Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
458
- hash['SmallOffice - OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
459
- hash['SmallOffice - Restroom'] = { ratio: 0.99, space_type_gen: true, default: false }
460
- hash['SmallOffice - Stair'] = { ratio: 0.99, space_type_gen: true, default: false }
461
- hash['SmallOffice - Storage'] = { ratio: 0.99, space_type_gen: true, default: false }
462
- hash['SmallOffice - Classroom'] = { ratio: 0.99, space_type_gen: true, default: false }
463
- hash['SmallOffice - Dining'] = { ratio: 0.99, space_type_gen: true, default: false }
464
- hash['WholeBuilding - Sm Office'] = { ratio: 0.0, space_type_gen: true, default: false }
465
- end
466
- elsif building_type == 'MediumOffice'
467
- if whole_building
468
- hash['WholeBuilding - Md Office'] = { ratio: 1.0, space_type_gen: true, default: true }
469
- else
470
- hash['MediumOffice - Breakroom'] = { ratio: 0.99, space_type_gen: true, default: false }
471
- hash['MediumOffice - ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
472
- hash['MediumOffice - Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
473
- hash['MediumOffice - Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
474
- hash['MediumOffice - Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
475
- hash['MediumOffice - Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
476
- hash['MediumOffice - OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
477
- hash['MediumOffice - Restroom'] = { ratio: 0.99, space_type_gen: true, default: false }
478
- hash['MediumOffice - Stair'] = { ratio: 0.99, space_type_gen: true, default: false }
479
- hash['MediumOffice - Storage'] = { ratio: 0.99, space_type_gen: true, default: false }
480
- hash['MediumOffice - Classroom'] = { ratio: 0.99, space_type_gen: true, default: false }
481
- hash['MediumOffice - Dining'] = { ratio: 0.99, space_type_gen: true, default: false }
482
- hash['WholeBuilding - Md Office'] = { ratio: 0.0, space_type_gen: true, default: false }
483
- end
484
- elsif building_type == 'LargeOffice'
485
- if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'ComStock DOE Ref Pre-1980', 'ComStock DOE Ref 1980-2004'].include?(template)
486
- if whole_building
487
- hash['WholeBuilding - Lg Office'] = { ratio: 1.0, space_type_gen: true, default: true }
488
- else
489
- hash['BreakRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
490
- hash['ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
491
- hash['Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
492
- hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
493
- hash['Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
494
- hash['IT_Room'] = { ratio: 0.99, space_type_gen: true, default: false }
495
- hash['Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
496
- hash['OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
497
- hash['PrintRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
498
- hash['Restroom'] = { ratio: 0.99, space_type_gen: true, default: false }
499
- hash['Stair'] = { ratio: 0.99, space_type_gen: true, default: false }
500
- hash['Storage'] = { ratio: 0.99, space_type_gen: true, default: false }
501
- hash['Vending'] = { ratio: 0.99, space_type_gen: true, default: false }
502
- hash['WholeBuilding - Lg Office'] = { ratio: 0.0, space_type_gen: true, default: false }
503
- end
504
- else
505
- if whole_building
506
- hash['WholeBuilding - Lg Office'] = { ratio: 0.9737, space_type_gen: true, default: true }
507
- hash['OfficeLarge Data Center'] = { ratio: 0.0094, space_type_gen: true, default: false }
508
- hash['OfficeLarge Main Data Center'] = { ratio: 0.0169, space_type_gen: true, default: false }
509
- else
510
- hash['BreakRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
511
- hash['ClosedOffice'] = { ratio: 0.99, space_type_gen: true, default: false }
512
- hash['Conference'] = { ratio: 0.99, space_type_gen: true, default: false }
513
- hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: false, circ: true }
514
- hash['Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
515
- hash['IT_Room'] = { ratio: 0.99, space_type_gen: true, default: false }
516
- hash['Lobby'] = { ratio: 0.99, space_type_gen: true, default: false }
517
- hash['OpenOffice'] = { ratio: 0.99, space_type_gen: true, default: true }
518
- hash['PrintRoom'] = { ratio: 0.99, space_type_gen: true, default: false }
519
- hash['Restroom'] = { ratio: 0.99, space_type_gen: true, default: false }
520
- hash['Stair'] = { ratio: 0.99, space_type_gen: true, default: false }
521
- hash['Storage'] = { ratio: 0.99, space_type_gen: true, default: false }
522
- hash['Vending'] = { ratio: 0.99, space_type_gen: true, default: false }
523
- hash['WholeBuilding - Lg Office'] = { ratio: 0.0, space_type_gen: true, default: false }
524
- hash['OfficeLarge Data Center'] = { ratio: 0.0, space_type_gen: true, default: false }
525
- hash['OfficeLarge Main Data Center'] = { ratio: 0.0, space_type_gen: true, default: false }
526
- end
527
- end
528
- elsif building_type == 'SmallHotel'
529
- if ['DOE Ref Pre-1980', 'DOE Ref 1980-2004', 'ComStock DOE Ref Pre-1980', 'ComStock DOE Ref 1980-2004'].include?(template)
530
- hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false, circ: true }
531
- hash['Elec/MechRoom'] = { ratio: 0.0038, space_type_gen: true, default: false }
532
- hash['ElevatorCore'] = { ratio: 0.0113, space_type_gen: true, default: false }
533
- hash['Exercise'] = { ratio: 0.0081, space_type_gen: true, default: false }
534
- hash['GuestLounge'] = { ratio: 0.0406, space_type_gen: true, default: false }
535
- hash['GuestRoom'] = { ratio: 0.6313, space_type_gen: true, default: true }
536
- hash['Laundry'] = { ratio: 0.0244, space_type_gen: true, default: false }
537
- hash['Mechanical'] = { ratio: 0.0081, space_type_gen: true, default: false }
538
- hash['Meeting'] = { ratio: 0.0200, space_type_gen: true, default: false }
539
- hash['Office'] = { ratio: 0.0325, space_type_gen: true, default: false }
540
- hash['PublicRestroom'] = { ratio: 0.0081, space_type_gen: true, default: false }
541
- hash['StaffLounge'] = { ratio: 0.0081, space_type_gen: true, default: false }
542
- hash['Stair'] = { ratio: 0.0400, space_type_gen: true, default: false }
543
- hash['Storage'] = { ratio: 0.0325, space_type_gen: true, default: false }
544
- else
545
- hash['Corridor'] = { ratio: 0.1313, space_type_gen: true, default: false, circ: true }
546
- hash['Elec/MechRoom'] = { ratio: 0.0038, space_type_gen: true, default: false }
547
- hash['ElevatorCore'] = { ratio: 0.0113, space_type_gen: true, default: false }
548
- hash['Exercise'] = { ratio: 0.0081, space_type_gen: true, default: false }
549
- hash['GuestLounge'] = { ratio: 0.0406, space_type_gen: true, default: false }
550
- hash['GuestRoom123Occ'] = { ratio: 0.4081, space_type_gen: true, default: true }
551
- hash['GuestRoom123Vac'] = { ratio: 0.2231, space_type_gen: true, default: false }
552
- hash['Laundry'] = { ratio: 0.0244, space_type_gen: true, default: false }
553
- hash['Mechanical'] = { ratio: 0.0081, space_type_gen: true, default: false }
554
- hash['Meeting'] = { ratio: 0.0200, space_type_gen: true, default: false }
555
- hash['Office'] = { ratio: 0.0325, space_type_gen: true, default: false }
556
- hash['PublicRestroom'] = { ratio: 0.0081, space_type_gen: true, default: false }
557
- hash['StaffLounge'] = { ratio: 0.0081, space_type_gen: true, default: false }
558
- hash['Stair'] = { ratio: 0.0400, space_type_gen: true, default: false }
559
- hash['Storage'] = { ratio: 0.0325, space_type_gen: true, default: false }
560
- end
561
- elsif building_type == 'LargeHotel'
562
- hash['Banquet'] = { ratio: 0.0585, space_type_gen: true, default: false }
563
- hash['Basement'] = { ratio: 0.1744, space_type_gen: false, default: false }
564
- hash['Cafe'] = { ratio: 0.0166, space_type_gen: true, default: false }
565
- hash['Corridor'] = { ratio: 0.1736, space_type_gen: true, default: false, circ: true }
566
- hash['GuestRoom'] = { ratio: 0.4099, space_type_gen: true, default: true }
567
- hash['Kitchen'] = { ratio: 0.0091, space_type_gen: true, default: false }
568
- hash['Laundry'] = { ratio: 0.0069, space_type_gen: true, default: false }
569
- hash['Lobby'] = { ratio: 0.1153, space_type_gen: true, default: false }
570
- hash['Mechanical'] = { ratio: 0.0145, space_type_gen: true, default: false }
571
- hash['Retail'] = { ratio: 0.0128, space_type_gen: true, default: false }
572
- hash['Storage'] = { ratio: 0.0084, space_type_gen: true, default: false }
573
- elsif building_type == 'Warehouse'
574
- hash['Bulk'] = { ratio: 0.6628, space_type_gen: true, default: true }
575
- hash['Fine'] = { ratio: 0.2882, space_type_gen: true, default: false }
576
- hash['Office'] = { ratio: 0.0490, space_type_gen: true, default: false, wwr: 0.71, story_height: 14.0 }
577
- elsif building_type == 'RetailStandalone'
578
- hash['Back_Space'] = { ratio: 0.1656, space_type_gen: true, default: false }
579
- hash['Entry'] = { ratio: 0.0052, space_type_gen: true, default: false }
580
- hash['Point_of_Sale'] = { ratio: 0.0657, space_type_gen: true, default: false }
581
- hash['Retail'] = { ratio: 0.7635, space_type_gen: true, default: true }
582
- elsif building_type == 'RetailStripmall'
583
- hash['Strip mall - type 1'] = { ratio: 0.25, space_type_gen: true, default: false }
584
- hash['Strip mall - type 2'] = { ratio: 0.25, space_type_gen: true, default: false }
585
- hash['Strip mall - type 3'] = { ratio: 0.50, space_type_gen: true, default: true }
586
- elsif building_type == 'QuickServiceRestaurant'
587
- hash['Dining'] = { ratio: 0.5, space_type_gen: true, default: true }
588
- hash['Kitchen'] = { ratio: 0.5, space_type_gen: true, default: false }
589
- elsif building_type == 'FullServiceRestaurant'
590
- hash['Dining'] = { ratio: 0.7272, space_type_gen: true, default: true }
591
- hash['Kitchen'] = { ratio: 0.2728, space_type_gen: true, default: false }
592
- elsif building_type == 'MidriseApartment'
593
- hash['Apartment'] = { ratio: 0.8727, space_type_gen: true, default: true }
594
- hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false, circ: true }
595
- hash['Office'] = { ratio: 0.0282, space_type_gen: true, default: false }
596
- elsif building_type == 'HighriseApartment'
597
- hash['Apartment'] = { ratio: 0.8896, space_type_gen: true, default: true }
598
- hash['Corridor'] = { ratio: 0.0991, space_type_gen: true, default: false, circ: true }
599
- hash['Office'] = { ratio: 0.0113, space_type_gen: true, default: false }
600
- elsif building_type == 'Hospital'
601
- hash['Basement'] = { ratio: 0.1667, space_type_gen: false, default: false }
602
- hash['Corridor'] = { ratio: 0.1741, space_type_gen: true, default: false, circ: true }
603
- hash['Dining'] = { ratio: 0.0311, space_type_gen: true, default: false }
604
- hash['ER_Exam'] = { ratio: 0.0099, space_type_gen: true, default: false }
605
- hash['ER_NurseStn'] = { ratio: 0.0551, space_type_gen: true, default: false }
606
- hash['ER_Trauma'] = { ratio: 0.0025, space_type_gen: true, default: false }
607
- hash['ER_Triage'] = { ratio: 0.0050, space_type_gen: true, default: false }
608
- hash['ICU_NurseStn'] = { ratio: 0.0298, space_type_gen: true, default: false }
609
- hash['ICU_Open'] = { ratio: 0.0275, space_type_gen: true, default: false }
610
- hash['ICU_PatRm'] = { ratio: 0.0115, space_type_gen: true, default: false }
611
- hash['Kitchen'] = { ratio: 0.0414, space_type_gen: true, default: false }
612
- hash['Lab'] = { ratio: 0.0236, space_type_gen: true, default: false }
613
- hash['Lobby'] = { ratio: 0.0657, space_type_gen: true, default: false }
614
- hash['NurseStn'] = { ratio: 0.1723, space_type_gen: true, default: false }
615
- hash['Office'] = { ratio: 0.0286, space_type_gen: true, default: false }
616
- hash['OR'] = { ratio: 0.0273, space_type_gen: true, default: false }
617
- hash['PatCorridor'] = { ratio: 0.0, space_type_gen: true, default: false } # not in prototype
618
- hash['PatRoom'] = { ratio: 0.0845, space_type_gen: true, default: true }
619
- hash['PhysTherapy'] = { ratio: 0.0217, space_type_gen: true, default: false }
620
- hash['Radiology'] = { ratio: 0.0217, space_type_gen: true, default: false }
621
- elsif building_type == 'Outpatient'
622
- hash['Anesthesia'] = { ratio: 0.0026, space_type_gen: true, default: false }
623
- hash['BioHazard'] = { ratio: 0.0014, space_type_gen: true, default: false }
624
- hash['Cafe'] = { ratio: 0.0103, space_type_gen: true, default: false }
625
- hash['CleanWork'] = { ratio: 0.0071, space_type_gen: true, default: false }
626
- hash['Conference'] = { ratio: 0.0082, space_type_gen: true, default: false }
627
- hash['DressingRoom'] = { ratio: 0.0021, space_type_gen: true, default: false }
628
- hash['Elec/MechRoom'] = { ratio: 0.0109, space_type_gen: true, default: false }
629
- hash['ElevatorPumpRoom'] = { ratio: 0.0022, space_type_gen: true, default: false }
630
- hash['Exam'] = { ratio: 0.1029, space_type_gen: true, default: true }
631
- hash['Hall'] = { ratio: 0.1924, space_type_gen: true, default: false, circ: true }
632
- hash['IT_Room'] = { ratio: 0.0027, space_type_gen: true, default: false }
633
- hash['Janitor'] = { ratio: 0.0672, space_type_gen: true, default: false }
634
- hash['Lobby'] = { ratio: 0.0152, space_type_gen: true, default: false }
635
- hash['LockerRoom'] = { ratio: 0.0190, space_type_gen: true, default: false }
636
- hash['Lounge'] = { ratio: 0.0293, space_type_gen: true, default: false }
637
- hash['MedGas'] = { ratio: 0.0014, space_type_gen: true, default: false }
638
- hash['MRI'] = { ratio: 0.0107, space_type_gen: true, default: false }
639
- hash['MRI_Control'] = { ratio: 0.0041, space_type_gen: true, default: false }
640
- hash['NurseStation'] = { ratio: 0.0189, space_type_gen: true, default: false }
641
- hash['Office'] = { ratio: 0.1828, space_type_gen: true, default: false }
642
- hash['OR'] = { ratio: 0.0346, space_type_gen: true, default: false }
643
- hash['PACU'] = { ratio: 0.0232, space_type_gen: true, default: false }
644
- hash['PhysicalTherapy'] = { ratio: 0.0462, space_type_gen: true, default: false }
645
- hash['PreOp'] = { ratio: 0.0129, space_type_gen: true, default: false }
646
- hash['ProcedureRoom'] = { ratio: 0.0070, space_type_gen: true, default: false }
647
- hash['Reception'] = { ratio: 0.0365, space_type_gen: true, default: false }
648
- hash['Soil Work'] = { ratio: 0.0088, space_type_gen: true, default: false }
649
- hash['Stair'] = { ratio: 0.0146, space_type_gen: true, default: false }
650
- hash['Toilet'] = { ratio: 0.0193, space_type_gen: true, default: false }
651
- hash['Undeveloped'] = { ratio: 0.0835, space_type_gen: false, default: false }
652
- hash['Xray'] = { ratio: 0.0220, space_type_gen: true, default: false }
653
- elsif building_type == 'SuperMarket'
654
- # TODO: - populate ratios for SuperMarket
655
- hash['Bakery'] = { ratio: 0.99, space_type_gen: true, default: false }
656
- hash['Deli'] = { ratio: 0.99, space_type_gen: true, default: false }
657
- hash['DryStorage'] = { ratio: 0.99, space_type_gen: true, default: false }
658
- hash['Office'] = { ratio: 0.99, space_type_gen: true, default: false }
659
- hash['Produce'] = { ratio: 0.99, space_type_gen: true, default: true }
660
- hash['Sales'] = { ratio: 0.99, space_type_gen: true, default: true }
661
- hash['Corridor'] = { ratio: 0.99, space_type_gen: true, default: true }
662
- hash['Dining'] = { ratio: 0.99, space_type_gen: true, default: true }
663
- hash['Elec/MechRoom'] = { ratio: 0.99, space_type_gen: true, default: true }
664
- hash['Meeting'] = { ratio: 0.99, space_type_gen: true, default: true }
665
- hash['Restroom'] = { ratio: 0.99, space_type_gen: true, default: true }
666
- hash['Vestibule'] = { ratio: 0.99, space_type_gen: true, default: true }
667
- elsif building_type == 'Laboratory'
668
- hash['Office'] = { ratio: 0.50, space_type_gen: true, default: true }
669
- hash['Open lab'] = { ratio: 0.35, space_type_gen: true, default: true }
670
- hash['Equipment corridor'] = { ratio: 0.05, space_type_gen: true, default: true }
671
- hash['Lab with fume hood'] = { ratio: 0.10, space_type_gen: true, default: true }
672
- elsif building_type == 'LargeDataCenterHighITE'
673
- hash['StandaloneDataCenter'] = { ratio: 1.0, space_type_gen: true, default: true }
674
- elsif building_type == 'LargeDataCenterLowITE'
675
- hash['StandaloneDataCenter'] = { ratio: 1.0, space_type_gen: true, default: true }
676
- elsif building_type == 'SmallDataCenterHighITE'
677
- hash['ComputerRoom'] = { ratio: 1.0, space_type_gen: true, default: true }
678
- elsif building_type == 'SmallDataCenterLowITE'
679
- hash['ComputerRoom'] = { ratio: 1.0, space_type_gen: true, default: true }
680
- elsif building_type == 'Courthouse'
681
- hash['Courthouse - Break Room'] = { ratio: 0.0067, space_type_gen: true, default: false }
682
- hash['Courthouse - Cell'] = { ratio: 0.0731, space_type_gen: true, default: false }
683
- hash['Courthouse - Conference'] = { ratio: 0.0203, space_type_gen: true, default: false }
684
- hash['Courthouse - Corridor'] = { ratio: 0.0829, space_type_gen: true, default: false }
685
- hash['Courthouse - Courtroom'] = { ratio: 0.1137, space_type_gen: true, default: false }
686
- hash['Courthouse - Courtroom Waiting'] = { ratio: 0.051, space_type_gen: true, default: false }
687
- hash['Courthouse - Elevator Lobby'] = { ratio: 0.0085, space_type_gen: true, default: false }
688
- hash['Courthouse - Elevator Shaft'] = { ratio: 0.0047, space_type_gen: true, default: false }
689
- hash['Courthouse - Entrance Lobby'] = { ratio: 0.0299, space_type_gen: true, default: false }
690
- hash['Courthouse - Judges Chamber'] = { ratio: 0.0261, space_type_gen: true, default: false }
691
- hash['Courthouse - Jury Assembly'] = { ratio: 0.0355, space_type_gen: true, default: false }
692
- hash['Courthouse - Jury Deliberation'] = { ratio: 0.0133, space_type_gen: true, default: false }
693
- hash['Courthouse - Library'] = { ratio: 0.0302, space_type_gen: true, default: false }
694
- hash['Courthouse - Office'] = { ratio: 0.1930, space_type_gen: true, default: true }
695
- hash['Courthouse - Parking'] = { ratio: 0.1083, space_type_gen: true, default: false }
696
- hash['Courthouse - Restrooms'] = { ratio: 0.04, space_type_gen: true, default: false }
697
- hash['Courthouse - Security Screening'] = { ratio: 0.0132, space_type_gen: true, default: false }
698
- hash['Courthouse - Service Shaft'] = { ratio: 0.0019, space_type_gen: true, default: false }
699
- hash['Courthouse - Stairs'] = { ratio: 0.0111, space_type_gen: true, default: false }
700
- hash['Courthouse - Storage'] = { ratio: 0.0882, space_type_gen: true, default: false }
701
- hash['Courthouse - Utility'] = { ratio: 0.0484, space_type_gen: true, default: false }
702
- elsif building_type == 'College'
703
- hash['College - Art Classroom'] = { ratio: 0.1868, space_type_gen: true, default: false }
704
- hash['College - Classroom'] = { ratio: 0.2348, space_type_gen: true, default: true }
705
- hash['College - Conference'] = { ratio: 0.0215, space_type_gen: true, default: false }
706
- hash['College - Corridor'] = { ratio: 0.0716, space_type_gen: true, default: false }
707
- hash['College - Elevator Shaft'] = { ratio: 0.0074, space_type_gen: true, default: false }
708
- hash['College - Entrance Lobby'] = { ratio: 0.0117, space_type_gen: true, default: false }
709
- hash['College - Laboratory'] = { ratio: 0.0843, space_type_gen: true, default: false }
710
- hash['College - Lecture Hall'] = { ratio: 0.0421, space_type_gen: true, default: false }
711
- hash['College - Lounge'] = { ratio: 0.028, space_type_gen: true, default: false }
712
- hash['College - Media Center'] = { ratio: 0.0421, space_type_gen: true, default: false }
713
- hash['College - Office'] = { ratio: 0.1894, space_type_gen: true, default: false }
714
- hash['College - Restroom'] = { ratio: 0.0363, space_type_gen: true, default: false }
715
- hash['College - Stairs'] = { ratio: 0.0272, space_type_gen: true, default: false }
716
- hash['College - Storage'] = { ratio: 0.0117, space_type_gen: true, default: false }
717
- hash['College - Utility'] = { ratio: 0.0051, space_type_gen: true, default: false }
718
- # DEER Prototypes
719
- elsif building_type == 'Asm'
720
- hash['Auditorium'] = { ratio: 0.7658, space_type_gen: true, default: true }
721
- hash['OfficeGeneral'] = { ratio: 0.2342, space_type_gen: true, default: false }
722
- elsif building_type == 'ECC'
723
- hash['Classroom'] = { ratio: 0.5558, space_type_gen: true, default: true }
724
- hash['CompRoomClassRm'] = { ratio: 0.0319, space_type_gen: true, default: false }
725
- hash['Shop'] = { ratio: 0.1249, space_type_gen: true, default: false }
726
- hash['Dining'] = { ratio: 0.0876, space_type_gen: true, default: false }
727
- hash['Kitchen'] = { ratio: 0.0188, space_type_gen: true, default: false }
728
- hash['OfficeGeneral'] = { ratio: 0.181, space_type_gen: true, default: false }
729
- elsif building_type == 'EPr'
730
- hash['Classroom'] = { ratio: 0.53, space_type_gen: true, default: true }
731
- hash['CorridorStairway'] = { ratio: 0.1, space_type_gen: true, default: false }
732
- hash['Dining'] = { ratio: 0.15, space_type_gen: true, default: false }
733
- hash['Gymnasium'] = { ratio: 0.15, space_type_gen: true, default: false }
734
- hash['Kitchen'] = { ratio: 0.07, space_type_gen: true, default: false }
735
- elsif building_type == 'ERC'
736
- hash['Classroom'] = { ratio: 0.5, space_type_gen: true, default: true }
737
- elsif building_type == 'ESe'
738
- hash['Classroom'] = { ratio: 0.488, space_type_gen: true, default: true }
739
- hash['CompRoomClassRm'] = { ratio: 0.021, space_type_gen: true, default: false }
740
- hash['CorridorStairway'] = { ratio: 0.1, space_type_gen: true, default: false }
741
- hash['Dining'] = { ratio: 0.15, space_type_gen: true, default: false }
742
- hash['Gymnasium'] = { ratio: 0.15, space_type_gen: true, default: false }
743
- hash['Kitchen'] = { ratio: 0.07, space_type_gen: true, default: false }
744
- hash['OfficeGeneral'] = { ratio: 0.021, space_type_gen: true, default: true }
745
- elsif building_type == 'EUn'
746
- hash['Dining'] = { ratio: 0.0238, space_type_gen: true, default: false }
747
- hash['Classroom'] = { ratio: 0.3056, space_type_gen: true, default: false }
748
- hash['OfficeGeneral'] = { ratio: 0.3422, space_type_gen: true, default: true }
749
- hash['CompRoomClassRm'] = { ratio: 0.038, space_type_gen: true, default: false }
750
- hash['Kitchen'] = { ratio: 0.0105, space_type_gen: true, default: false }
751
- hash['CorridorStairway'] = { ratio: 0.03, space_type_gen: true, default: false }
752
- hash['FacMaint'] = { ratio: 0.08, space_type_gen: true, default: false }
753
- hash['DormitoryRoom'] = { ratio: 0.1699, space_type_gen: true, default: false }
754
- elsif building_type == 'Gro'
755
- hash['GrocSales'] = { ratio: 0.8002, space_type_gen: true, default: true }
756
- hash['RefWalkInCool'] = { ratio: 0.0312, space_type_gen: true, default: false }
757
- hash['OfficeGeneral'] = { ratio: 0.07, space_type_gen: true, default: false }
758
- hash['RefFoodPrep'] = { ratio: 0.0253, space_type_gen: true, default: false }
759
- hash['RefWalkInFreeze'] = { ratio: 0.0162, space_type_gen: true, default: false }
760
- hash['IndLoadDock'] = { ratio: 0.057, space_type_gen: true, default: false }
761
- elsif building_type == 'Hsp'
762
- hash['HspSurgOutptLab'] = { ratio: 0.2317, space_type_gen: true, default: false }
763
- hash['Dining'] = { ratio: 0.0172, space_type_gen: true, default: false }
764
- hash['Kitchen'] = { ratio: 0.0075, space_type_gen: true, default: false }
765
- hash['OfficeGeneral'] = { ratio: 0.3636, space_type_gen: true, default: false }
766
- hash['PatientRoom'] = { ratio: 0.38, space_type_gen: true, default: true }
767
- elsif building_type == 'Htl'
768
- hash['Dining'] = { ratio: 0.004, space_type_gen: true, default: false }
769
- hash['BarCasino'] = { ratio: 0.005, space_type_gen: true, default: false }
770
- hash['HotelLobby'] = { ratio: 0.0411, space_type_gen: true, default: false }
771
- hash['OfficeGeneral'] = { ratio: 0.0205, space_type_gen: true, default: false }
772
- hash['GuestRmCorrid'] = { ratio: 0.1011, space_type_gen: true, default: false }
773
- hash['Laundry'] = { ratio: 0.0205, space_type_gen: true, default: false }
774
- hash['GuestRmOcc'] = { ratio: 0.64224, space_type_gen: true, default: true }
775
- hash['GuestRmUnOcc'] = { ratio: 0.16056, space_type_gen: true, default: true }
776
- hash['Kitchen'] = { ratio: 0.005, space_type_gen: true, default: false }
777
- elsif building_type == 'MBT'
778
- hash['CompRoomData'] = { ratio: 0.02, space_type_gen: true, default: false }
779
- hash['Laboratory'] = { ratio: 0.4534, space_type_gen: true, default: true }
780
- hash['CorridorStairway'] = { ratio: 0.2, space_type_gen: true, default: false }
781
- hash['Conference'] = { ratio: 0.02, space_type_gen: true, default: false }
782
- hash['Dining'] = { ratio: 0.03, space_type_gen: true, default: false }
783
- hash['OfficeOpen'] = { ratio: 0.2666, space_type_gen: true, default: false }
784
- hash['Kitchen'] = { ratio: 0.01, space_type_gen: true, default: false }
785
- elsif building_type == 'MFm'
786
- hash['ResLiving'] = { ratio: 0.9297, space_type_gen: true, default: true }
787
- hash['ResPublicArea'] = { ratio: 0.0725, space_type_gen: true, default: false }
788
- elsif building_type == 'MLI'
789
- hash['StockRoom'] = { ratio: 0.2, space_type_gen: true, default: false }
790
- hash['Work'] = { ratio: 0.8, space_type_gen: true, default: true }
791
- elsif building_type == 'Mtl'
792
- hash['OfficeGeneral'] = { ratio: 0.02, space_type_gen: true, default: false }
793
- hash['GuestRmCorrid'] = { ratio: 0.649, space_type_gen: true, default: true }
794
- hash['Laundry'] = { ratio: 0.016, space_type_gen: true, default: false }
795
- hash['GuestRmOcc'] = { ratio: 0.25208, space_type_gen: true, default: false }
796
- hash['GuestRmUnOcc'] = { ratio: 0.06302, space_type_gen: true, default: false }
797
- elsif building_type == 'Nrs'
798
- hash['CorridorStairway'] = { ratio: 0.0555, space_type_gen: true, default: false }
799
- hash['Dining'] = { ratio: 0.105, space_type_gen: true, default: false }
800
- hash['Kitchen'] = { ratio: 0.045, space_type_gen: true, default: false }
801
- hash['OfficeGeneral'] = { ratio: 0.35, space_type_gen: true, default: false }
802
- hash['PatientRoom'] = { ratio: 0.4445, space_type_gen: true, default: true }
803
- elsif building_type == 'OfL'
804
- hash['LobbyWaiting'] = { ratio: 0.0412, space_type_gen: true, default: false }
805
- hash['OfficeSmall'] = { ratio: 0.3704, space_type_gen: true, default: false }
806
- hash['OfficeOpen'] = { ratio: 0.5296, space_type_gen: true, default: true }
807
- hash['MechElecRoom'] = { ratio: 0.0588, space_type_gen: true, default: false }
808
- elsif building_type == 'OfS'
809
- hash['Hall'] = { ratio: 0.3141, space_type_gen: true, default: false }
810
- hash['OfficeSmall'] = { ratio: 0.6859, space_type_gen: true, default: true }
811
- elsif building_type == 'RFF'
812
- hash['Dining'] = { ratio: 0.3997, space_type_gen: true, default: false }
813
- hash['Kitchen'] = { ratio: 0.4, space_type_gen: true, default: true }
814
- hash['LobbyWaiting'] = { ratio: 0.1501, space_type_gen: true, default: false }
815
- hash['Restroom'] = { ratio: 0.0501, space_type_gen: true, default: false }
816
- elsif building_type == 'RSD'
817
- hash['Restroom'] = { ratio: 0.0357, space_type_gen: true, default: false }
818
- hash['Dining'] = { ratio: 0.5353, space_type_gen: true, default: true }
819
- hash['LobbyWaiting'] = { ratio: 0.1429, space_type_gen: true, default: false }
820
- hash['Kitchen'] = { ratio: 0.2861, space_type_gen: true, default: false }
821
- elsif building_type == 'Rt3'
822
- hash['RetailSales'] = { ratio: 1.0, space_type_gen: true, default: true }
823
- elsif building_type == 'RtL'
824
- hash['OfficeGeneral'] = { ratio: 0.0359, space_type_gen: true, default: false }
825
- hash['Work'] = { ratio: 0.04, space_type_gen: true, default: false }
826
- hash['StockRoom'] = { ratio: 0.091, space_type_gen: true, default: false }
827
- hash['RetailSales'] = { ratio: 0.8219, space_type_gen: true, default: true }
828
- hash['Kitchen'] = { ratio: 0.0113, space_type_gen: true, default: false }
829
- elsif building_type == 'RtS'
830
- hash['RetailSales'] = { ratio: 0.8, space_type_gen: true, default: true }
831
- hash['StockRoom'] = { ratio: 0.2, space_type_gen: true, default: false }
832
- elsif building_type == 'SCn'
833
- hash['WarehouseCond'] = { ratio: 1.0, space_type_gen: true, default: true }
834
- elsif building_type == 'SUn'
835
- hash['WarehouseUnCond'] = { ratio: 1.0, space_type_gen: true, default: true }
836
- elsif building_type == 'WRf'
837
- hash['IndLoadDock'] = { ratio: 0.08, space_type_gen: true, default: false }
838
- hash['OfficeGeneral'] = { ratio: 0.02, space_type_gen: true, default: false }
839
- hash['RefStorFreezer'] = { ratio: 0.4005, space_type_gen: true, default: false }
840
- hash['RefStorCooler'] = { ratio: 0.4995, space_type_gen: true, default: true }
841
- else
842
- return false
843
- end
844
-
845
- return hash
846
- end
847
-
848
- # remove existing non resource objects from the model
849
- # technically thermostats and building stories are resources but still want to remove them.
850
- def remove_non_resource_objects(runner, model, options = nil)
851
- if options.nil?
852
- options = {}
853
- options[:remove_building_stories] = true
854
- options[:remove_thermostats] = true
855
- options[:remove_air_loops] = true
856
- options[:remove_non_swh_plant_loops] = true
857
-
858
- # leave these in by default unless requsted when method called
859
- options[:remove_swh_plant_loops] = false
860
- options[:remove_exterior_lights] = false
861
- options[:remove_site_shading] = false
862
- end
863
-
864
- num_model_objects = model.objects.size
865
-
866
- # remove non-resource objects not removed by removing the building
867
- if options[:remove_building_stories] then model.getBuildingStorys.each(&:remove) end
868
- if options[:remove_thermostats] then model.getThermostats.each(&:remove) end
869
- if options[:remove_air_loops] then model.getAirLoopHVACs.each(&:remove) end
870
- if options[:remove_exterior_lights] then model.getFacility.exteriorLights.each(&:remove) end
871
- if options[:remove_site_shading] then model.getSite.shadingSurfaceGroups.each(&:remove) end
872
-
873
- # see if plant loop is swh or not and take proper action (booter loop doesn't have water use equipment)
874
- model.getPlantLoops.each do |plant_loop|
875
- is_swh_loop = false
876
- plant_loop.supplyComponents.each do |component|
877
- if component.to_WaterHeaterMixed.is_initialized
878
- is_swh_loop = true
879
- next
880
- end
881
- end
882
-
883
- if is_swh_loop
884
- if options[:remove_swh_plant_loops] then plant_loop.remove end
885
- else
886
- if options[:remove_non_swh_plant_loops] then plant_loop.remove end
887
- end
888
- end
889
-
890
- # remove water use connections (may be removed when loop is removed)
891
- if options[:remove_swh_plant_loops] then model.getWaterConnectionss.each(&:remove) end
892
- if options[:remove_swh_plant_loops] then model.getWaterUseEquipments.each(&:remove) end
893
-
894
- # remove building but reset fields on new building object.
895
- building_fields = []
896
- building = model.getBuilding
897
- num_fields = building.numFields
898
- num_fields.times.each do |i|
899
- building_fields << building.getString(i).get
900
- end
901
- # removes spaces, space's child objects, thermal zones, zone equipment, non site surfaces, building stories and water use connections.
902
- model.getBuilding.remove
903
- building = model.getBuilding
904
- num_fields.times.each do |i|
905
- next if i == 0 # don't try and set handle
906
- building_fields << building.setString(i, building_fields[i])
907
- end
908
-
909
- # other than optionally site shading and exterior lights not messing with site characteristics
910
-
911
- if num_model_objects - model.objects.size > 0
912
- runner.registerInfo("Removed #{num_model_objects - model.objects.size} non resource objects from the model.")
913
- end
914
-
915
- return true
916
- end
917
-
918
- # create_bar(runner,model,bar_hash)
919
- # measures using this method should include OsLibGeometry and OsLibHelperMethods
920
- def create_bar(runner, model, bar_hash, story_multiplier_method = 'Basements Ground Mid Top')
921
- # warn about site shading
922
- if !model.getSite.shadingSurfaceGroups.empty?
923
- runner.registerWarning('The model has one or more site shading surafces. New geometry may not be positioned where expected, it will be centered over the center of the original geometry.')
924
- end
925
-
926
- # make custom story hash when number of stories below grade > 0
927
- # todo - update this so have option basements are not below 0? (useful for simplifying existing model and maintaining z position relative to site shading)
928
- story_hash = {}
929
- eff_below = bar_hash[:num_stories_below_grade]
930
- eff_above = bar_hash[:num_stories_above_grade]
931
- footprint_origin = bar_hash[:center_of_footprint]
932
- typical_story_height = bar_hash[:floor_height]
933
-
934
- # flatten story_hash out to individual stories included in building area
935
- stories_flat = []
936
- stories_flat_counter = 0
937
- bar_hash[:stories].each_with_index do |(k, v), i|
938
- # k is invalid in some cases, old story object that has been removed, should be from low to high including basement
939
- # skip if source story insn't included in building area
940
- if v[:story_included_in_building_area].nil? || (v[:story_included_in_building_area] == true)
941
-
942
- # add to counter
943
- stories_flat_counter += v[:story_min_multiplier]
944
-
945
- flat_hash = {}
946
- flat_hash[:story_party_walls] = v[:story_party_walls]
947
- flat_hash[:below_partial_story] = v[:below_partial_story]
948
- flat_hash[:bottom_story_ground_exposed_floor] = v[:bottom_story_ground_exposed_floor]
949
- flat_hash[:top_story_exterior_exposed_roof] = v[:top_story_exterior_exposed_roof]
950
- if i < eff_below
951
- flat_hash[:story_type] = 'B'
952
- flat_hash[:multiplier] = 1
953
- elsif i == eff_below
954
- flat_hash[:story_type] = 'Ground'
955
- flat_hash[:multiplier] = 1
956
- elsif stories_flat_counter == eff_below + eff_above.ceil
957
- flat_hash[:story_type] = 'Top'
958
- flat_hash[:multiplier] = 1
959
- else
960
- flat_hash[:story_type] = 'Mid'
961
- flat_hash[:multiplier] = v[:story_min_multiplier]
962
- end
963
-
964
- compare_hash = {}
965
- if !stories_flat.empty?
966
- stories_flat.last.each { |k, v| compare_hash[k] = flat_hash[k] if flat_hash[k] != v }
967
- end
968
- if (story_multiplier_method != 'None' && stories_flat.last == flat_hash) || (story_multiplier_method != 'None' && compare_hash.size == 1 && compare_hash.include?(:multiplier))
969
- stories_flat.last[:multiplier] += v[:story_min_multiplier]
970
- else
971
- stories_flat << flat_hash
972
- end
973
- end
974
- end
975
-
976
- if bar_hash[:num_stories_below_grade] > 0
977
-
978
- # add in below grade levels (may want to add below grade multipliers at some point if we start running deep basements)
979
- eff_below.times do |i|
980
- story_hash["B#{i + 1}"] = { space_origin_z: footprint_origin.z - typical_story_height * (i + 1), space_height: typical_story_height, multiplier: 1 }
981
- end
982
- end
983
-
984
- # add in above grade levels
985
- if eff_above > 2
986
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
987
-
988
- footprint_counter = 0
989
- effective_stories_counter = 1
990
- stories_flat.each do |hash|
991
- next if hash[:story_type] != 'Mid'
992
- if footprint_counter == 0
993
- string = 'Mid'
994
- else
995
- string = "Mid#{footprint_counter + 1}"
996
- end
997
- story_hash[string] = { space_origin_z: footprint_origin.z + typical_story_height * effective_stories_counter + typical_story_height * (hash[:multiplier] - 1) / 2.0, space_height: typical_story_height, multiplier: hash[:multiplier] }
998
- footprint_counter += 1
999
- effective_stories_counter += hash[:multiplier]
1000
- end
1001
-
1002
- story_hash['Top'] = { space_origin_z: footprint_origin.z + typical_story_height * (eff_above.ceil - 1), space_height: typical_story_height, multiplier: 1 }
1003
- elsif eff_above > 1
1004
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
1005
- story_hash['Top'] = { space_origin_z: footprint_origin.z + typical_story_height * (eff_above.ceil - 1), space_height: typical_story_height, multiplier: 1 }
1006
- else # one story only
1007
- story_hash['Ground'] = { space_origin_z: footprint_origin.z, space_height: typical_story_height, multiplier: 1 }
1008
- end
1009
-
1010
- # create footprints
1011
- if bar_hash[:bar_division_method] == 'Multiple Space Types - Simple Sliced'
1012
- footprints = []
1013
- story_hash.size.times do |i|
1014
- # adjust size of bar of top story is not a full story
1015
- if i + 1 == story_hash.size
1016
- area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
1017
- edge_multiplier = Math.sqrt(area_multiplier)
1018
- length = bar_hash[:length] * edge_multiplier
1019
- width = bar_hash[:width] * edge_multiplier
1020
- else
1021
- length = bar_hash[:length]
1022
- width = bar_hash[:width]
1023
- end
1024
- footprints << OsLib_Geometry.make_sliced_bar_simple_polygons(runner, bar_hash[:space_types], length, width, bar_hash[:center_of_footprint])
1025
- end
1026
-
1027
- elsif bar_hash[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
1028
-
1029
- # update story_hash for partial_story_above
1030
- story_hash.each_with_index do |(k, v), i|
1031
- # adjust size of bar of top story is not a full story
1032
- if i + 1 == story_hash.size
1033
- story_hash[k][:partial_story_multiplier] = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
1034
- end
1035
- end
1036
-
1037
- footprints = OsLib_Geometry.make_sliced_bar_multi_polygons(runner, bar_hash[:space_types], bar_hash[:length], bar_hash[:width], bar_hash[:center_of_footprint], story_hash)
1038
-
1039
- else
1040
- footprints = []
1041
- story_hash.size.times do |i|
1042
- # adjust size of bar of top story is not a full story
1043
- if i + 1 == story_hash.size
1044
- area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
1045
- edge_multiplier = Math.sqrt(area_multiplier)
1046
- length = bar_hash[:length] * edge_multiplier
1047
- width = bar_hash[:width] * edge_multiplier
1048
- else
1049
- length = bar_hash[:length]
1050
- width = bar_hash[:width]
1051
- end
1052
- footprints << OsLib_Geometry.make_core_and_perimeter_polygons(runner, length, width, bar_hash[:center_of_footprint]) # perimeter defaults to 15'
1053
- end
1054
-
1055
- # set primary space type to building default space type
1056
- space_types = bar_hash[:space_types].sort_by { |k, v| v[:floor_area] }
1057
- if space_types.last.first.class.to_s == 'OpenStudio::Model::SpaceType'
1058
- model.getBuilding.setSpaceType(space_types.last.first)
1059
- end
1060
-
1061
- end
1062
-
1063
- # makeSpacesFromPolygons
1064
- new_spaces = OsLib_Geometry.makeSpacesFromPolygons(runner, model, footprints, bar_hash[:floor_height], bar_hash[:num_stories], bar_hash[:center_of_footprint], story_hash)
1065
-
1066
- # put all of the spaces in the model into a vector for intersection and surface matching
1067
- spaces = OpenStudio::Model::SpaceVector.new
1068
- model.getSpaces.sort.each do |space|
1069
- spaces << space
1070
- end
1071
-
1072
- # flag for intersection and matching type
1073
- diagnostic_intersect = true
1074
-
1075
- # only intersect if make_mid_story_surfaces_adiabatic false
1076
- if diagnostic_intersect
1077
-
1078
- model.getPlanarSurfaces.sort.each do |surface|
1079
- array = []
1080
- vertices = surface.vertices
1081
- fixed = false
1082
- vertices.each do |vertex|
1083
- next if fixed
1084
- if array.include?(vertex)
1085
- # create a new set of vertices
1086
- new_vertices = OpenStudio::Point3dVector.new
1087
- array_b = []
1088
- surface.vertices.each do |vertex_b|
1089
- next if array_b.include?(vertex_b)
1090
- new_vertices << vertex_b
1091
- array_b << vertex_b
1092
- end
1093
- surface.setVertices(new_vertices)
1094
- num_removed = vertices.size - surface.vertices.size
1095
- runner.registerWarning("#{surface.name} has duplicate vertices. Started with #{vertices.size} vertices, removed #{num_removed}.")
1096
- fixed = true
1097
- else
1098
- array << vertex
1099
- end
1100
- end
1101
- end
1102
-
1103
- # remove collinear points in a surface
1104
- model.getPlanarSurfaces.sort.each do |surface|
1105
- new_vertices = OpenStudio.removeCollinear(surface.vertices)
1106
- starting_count = surface.vertices.size
1107
- final_count = new_vertices.size
1108
- if final_count < starting_count
1109
- runner.registerWarning("Removing #{starting_count - final_count} collinear vertices from #{surface.name}.")
1110
- surface.setVertices(new_vertices)
1111
- end
1112
- end
1113
-
1114
- # remove duplicate surfaces in a space (should be done after remove duplicate and collinear points)
1115
- model.getSpaces.sort.each do |space|
1116
- # secondary array to compare against
1117
- surfaces_b = space.surfaces.sort
1118
-
1119
- space.surfaces.sort.each do |surface_a|
1120
- # delete from secondary array
1121
- surfaces_b.delete(surface_a)
1122
-
1123
- surfaces_b.each do |surface_b|
1124
- next if surface_a == surface_b # dont' test against same surface
1125
- if surface_a.equalVertices(surface_b)
1126
- runner.registerWarning("#{surface_a.name} and #{surface_b.name} in #{space.name} have duplicate geometry, removing #{surface_b.name}.")
1127
- surface_b.remove
1128
- elsif surface_a.reverseEqualVertices(surface_b)
1129
- # TODO: - add logic to determine which face naormal is reversed and which is correct
1130
- runner.registerWarning("#{surface_a.name} and #{surface_b.name} in #{space.name} have reversed geometry, removing #{surface_b.name}.")
1131
- surface_b.remove
1132
- end
1133
- end
1134
- end
1135
- end
1136
-
1137
- if !(bar_hash[:make_mid_story_surfaces_adiabatic])
1138
- # intersect and surface match two pair by pair
1139
- spaces_b = model.getSpaces.sort
1140
- # looping through vector of each space
1141
- model.getSpaces.sort.each do |space_a|
1142
- spaces_b.delete(space_a)
1143
- spaces_b.each do |space_b|
1144
- # runner.registerInfo("Intersecting and matching surfaces between #{space_a.name} and #{space.name}")
1145
- spaces_temp = OpenStudio::Model::SpaceVector.new
1146
- spaces_temp << space_a
1147
- spaces_temp << space_b
1148
- # intersect and sort
1149
- OpenStudio::Model.intersectSurfaces(spaces_temp)
1150
- OpenStudio::Model.matchSurfaces(spaces_temp)
1151
- end
1152
- end
1153
- runner.registerInfo('Intersecting and matching surfaces in model, this will create additional geometry.')
1154
- else # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
1155
- model.getBuilding.buildingStories.sort.each do |story|
1156
- # intersect and surface match two pair by pair
1157
- spaces_b = story.spaces.sort
1158
- # looping through vector of each space
1159
- story.spaces.sort.each do |space_a|
1160
- spaces_b.delete(space_a)
1161
- spaces_b.each do |space_b|
1162
- spaces_temp = OpenStudio::Model::SpaceVector.new
1163
- spaces_temp << space_a
1164
- spaces_temp << space_b
1165
- # attempt to intersect and match walls on a story, but later secondary match will look for missted matches and turn them to adiabaitc
1166
- # intersect and sort
1167
- OpenStudio::Model.intersectSurfaces(spaces_temp)
1168
- OpenStudio::Model.matchSurfaces(spaces_temp)
1169
- end
1170
- end
1171
- runner.registerInfo("Intersecting and matching surfaces in story #{story.name}, this will create additional geometry. Diagnstoic intersection and matching done a in paris of spaces.")
1172
- end
1173
- end
1174
-
1175
- else
1176
-
1177
- if !(bar_hash[:make_mid_story_surfaces_adiabatic])
1178
- # intersect surfaces
1179
- # (when bottom floor has many space types and one above doesn't will end up with heavily subdivided floor. Maybe use adiabatic and don't intersect floor/ceilings)
1180
- intersect_surfaces = true
1181
- if intersect_surfaces
1182
- OpenStudio::Model.intersectSurfaces(spaces)
1183
- OpenStudio::Model.matchSurfaces(spaces)
1184
- runner.registerInfo('Intersecting and matching surfaces in model, this will create additional geometry.')
1185
- end
1186
- else # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
1187
- model.getBuilding.buildingStories.sort.each do |story|
1188
- story_spaces = OpenStudio::Model::SpaceVector.new
1189
- story.spaces.sort.each do |space|
1190
- story_spaces << space
1191
- end
1192
- # attempt to intersect and match walls on a story, but later secondary match will look for missted matches and turn them to adiabaitc
1193
- # intersect and sort
1194
- OpenStudio::Model.intersectSurfaces(story_spaces)
1195
- OpenStudio::Model.matchSurfaces(story_spaces)
1196
- runner.registerInfo("Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
1197
- end
1198
- end
1199
-
1200
- end
1201
-
1202
- # set boundary conditions if not already set when geometry was created
1203
- # todo - update this to use space original z value vs. story name
1204
- if bar_hash[:num_stories_below_grade] > 0
1205
- model.getBuildingStorys.sort.each do |story|
1206
- next if !story.name.to_s.include?('Story B')
1207
- story.spaces.sort.each do |space|
1208
- next if !new_spaces.include?(space)
1209
- space.surfaces.sort.each do |surface|
1210
- next if surface.surfaceType != 'Wall'
1211
- next if surface.outsideBoundaryCondition != 'Outdoors'
1212
- surface.setOutsideBoundaryCondition('Ground')
1213
- end
1214
- end
1215
- end
1216
- end
1217
-
1218
- # set wall boundary condtions to adiabatic if using make_mid_story_surfaces_adiabatic prior to windows being made
1219
- if bar_hash[:make_mid_story_surfaces_adiabatic]
1220
-
1221
- runner.registerInfo("Finding non-exterior walls and setting boundary condition to adiabatic")
1222
-
1223
- # need to organize by story incase top story is partial story
1224
- # should also be only for a single bar
1225
- story_bounding = {}
1226
- missed_match_count = 0
1227
-
1228
- # gather new spaces by story
1229
- new_spaces.each do |space|
1230
- story = space.buildingStory.get
1231
- if story_bounding.has_key?(story)
1232
- story_bounding[story][:spaces] << space
1233
- else
1234
- story_bounding[story] = {:spaces => [space]}
1235
- end
1236
- end
1237
-
1238
- # get bounding box for each story
1239
- story_bounding.each do |story,v|
1240
-
1241
- # get bounding_box
1242
- bounding_box = OpenStudio::BoundingBox.new
1243
- v[:spaces].each do |space|
1244
- space.surfaces.each do |space_surface|
1245
- bounding_box.addPoints(space.transformation * space_surface.vertices)
1246
- end
1247
- end
1248
- min_x = bounding_box.minX.get
1249
- min_y = bounding_box.minY.get
1250
- max_x = bounding_box.maxX.get
1251
- max_y = bounding_box.maxY.get
1252
- ext_wall_toll = 0.01
1253
-
1254
- # check surfaces again against min/max and change to adiabatic if not fully on one min or max x or y
1255
- # todo - may need to look at aidiabiatc constructions in downstream measure. Some may be exterior party wall others may be interior walls
1256
- v[:spaces].each do |space|
1257
- space.surfaces.each do |space_surface|
1258
- next if not space_surface.surfaceType == "Wall"
1259
- next if space_surface.outsideBoundaryCondition == "Surface" # if if found a match leave it alone, don't change to adiabiatc
1260
- surface_bounding_box = OpenStudio::BoundingBox.new
1261
- surface_bounding_box.addPoints(space.transformation * space_surface.vertices)
1262
- surface_on_outside = false
1263
- # check xmin
1264
- if (surface_bounding_box.minX.get - min_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - min_x).abs < ext_wall_toll then surface_on_outside = true end
1265
- # check xmax
1266
- if (surface_bounding_box.minX.get - max_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - max_x).abs < ext_wall_toll then surface_on_outside = true end
1267
- # check ymin
1268
- if (surface_bounding_box.minY.get - min_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - min_y).abs < ext_wall_toll then surface_on_outside = true end
1269
- # check ymax
1270
- if (surface_bounding_box.minY.get - max_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - max_y).abs < ext_wall_toll then surface_on_outside = true end
1271
-
1272
- # change if not exterior
1273
- if !surface_on_outside
1274
- space_surface.setOutsideBoundaryCondition("Adiabatic")
1275
- missed_match_count += 1
1276
- end
1277
- end
1278
- end
1279
- end
1280
-
1281
- if missed_match_count > 0
1282
- runner.registerInfo("#{missed_match_count} surfaces that were exterior appear to be interior walls and had boundary condition chagned to adiabiatic.")
1283
- end
1284
-
1285
- end
1286
-
1287
- # sort stories (by name for now but need better way)
1288
- sorted_stories = {}
1289
- new_spaces.each do |space|
1290
- next if !space.buildingStory.is_initialized
1291
- story = space.buildingStory.get
1292
- if !sorted_stories.key?(name.to_s)
1293
- sorted_stories[story.name.to_s] = story
1294
- end
1295
- end
1296
-
1297
- # flag space types that have wwr overrides
1298
- space_type_wwr_overrides = {}
1299
-
1300
- # loop through building stories, spaces, and surfaces
1301
- sorted_stories.sort.each_with_index do |(key, story), i|
1302
- # flag for adiabatic floor if building doesn't have ground exposed floor
1303
- if stories_flat[i][:bottom_story_ground_exposed_floor] == false
1304
- adiabatic_floor = true
1305
- end
1306
- # flag for adiabatic roof if building doesn't have exterior exposed roof
1307
- if stories_flat[i][:top_story_exterior_exposed_roof] == false
1308
- adiabatic_ceiling = true
1309
- end
1310
-
1311
- # make all mid story floor and ceilings adiabatic if requested
1312
- if bar_hash[:make_mid_story_surfaces_adiabatic]
1313
- if i > 0
1314
- adiabatic_floor = true
1315
- end
1316
- if i < sorted_stories.size - 1
1317
- adiabatic_ceiling = true
1318
- end
1319
- end
1320
-
1321
- # flag orientations for this story to recieve party walls
1322
- party_wall_facades = stories_flat[i][:story_party_walls]
1323
-
1324
- story.spaces.each do |space|
1325
- next if !new_spaces.include?(space)
1326
- space.surfaces. each do |surface|
1327
- # set floor to adiabatic if requited
1328
- if adiabatic_floor && surface.surfaceType == 'Floor'
1329
- make_surfaces_adiabatic([surface])
1330
- elsif adiabatic_ceiling && surface.surfaceType == 'RoofCeiling'
1331
- make_surfaces_adiabatic([surface])
1332
- end
1333
-
1334
- # skip of not exterior wall
1335
- next if surface.surfaceType != 'Wall'
1336
- next if surface.outsideBoundaryCondition != 'Outdoors'
1337
-
1338
- # get the absoluteAzimuth for the surface so we can categorize it
1339
- absoluteAzimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
1340
- absoluteAzimuth = absoluteAzimuth % 360.0 # should result in value between 0 and 360
1341
- absoluteAzimuth = absoluteAzimuth.round(5) # this was creating issues at 45 deg angles with opposing facades
1342
-
1343
- # target wwr values that may be changed for specific space types
1344
- wwr_n = bar_hash[:building_wwr_n]
1345
- wwr_e = bar_hash[:building_wwr_e]
1346
- wwr_s = bar_hash[:building_wwr_s]
1347
- wwr_w = bar_hash[:building_wwr_w]
1348
-
1349
- # look for space type specific wwr values
1350
- if surface.space.is_initialized && surface.space.get.spaceType.is_initialized
1351
- space_type = surface.space.get.spaceType.get
1352
-
1353
- # see if space type has wwr value
1354
- bar_hash[:space_types].each do |k, v|
1355
- if v.key?(:space_type) && space_type == v[:space_type]
1356
-
1357
- # if matching space type specifies a wwr then override the orientation specific recommendations for this surface.
1358
- if v.key?(:wwr)
1359
- wwr_n = v[:wwr]
1360
- wwr_e = v[:wwr]
1361
- wwr_s = v[:wwr]
1362
- wwr_w = v[:wwr]
1363
- space_type_wwr_overrides[space_type] = v[:wwr]
1364
- end
1365
- end
1366
- end
1367
- end
1368
-
1369
- # add fenestration (wwr for now, maybe overhang and overhead doors later)
1370
- if (absoluteAzimuth >= 315.0) || (absoluteAzimuth < 45.0)
1371
- if party_wall_facades.include?('north')
1372
- make_surfaces_adiabatic([surface])
1373
- else
1374
- surface.setWindowToWallRatio(wwr_n)
1375
- end
1376
- elsif (absoluteAzimuth >= 45.0) && (absoluteAzimuth < 135.0)
1377
- if party_wall_facades.include?('east')
1378
- make_surfaces_adiabatic([surface])
1379
- else
1380
- surface.setWindowToWallRatio(wwr_e)
1381
- end
1382
- elsif (absoluteAzimuth >= 135.0) && (absoluteAzimuth < 225.0)
1383
- if party_wall_facades.include?('south')
1384
- make_surfaces_adiabatic([surface])
1385
- else
1386
- surface.setWindowToWallRatio(wwr_s)
1387
- end
1388
- elsif (absoluteAzimuth >= 225.0) && (absoluteAzimuth < 315.0)
1389
- if party_wall_facades.include?('west')
1390
- make_surfaces_adiabatic([surface])
1391
- else
1392
- surface.setWindowToWallRatio(wwr_w)
1393
- end
1394
- else
1395
- runner.registerError('Unexpected value of facade: ' + absoluteAzimuth + '.')
1396
- return false
1397
- end
1398
- end
1399
- end
1400
- end
1401
-
1402
- # report space types with custom wwr values
1403
- space_type_wwr_overrides.each do |space_type, wwr|
1404
- runner.registerInfo("For #{space_type.name} the default building wwr was replaced with a space type specfic value of #{wwr}")
1405
- end
1406
-
1407
- new_floor_area_si = 0.0
1408
- new_spaces.each do |space|
1409
- new_floor_area_si += space.floorArea * space.multiplier
1410
- end
1411
- new_floor_area_ip = OpenStudio.convert(new_floor_area_si, 'm^2', 'ft^2').get
1412
-
1413
- final_floor_area_ip = OpenStudio.convert(model.getBuilding.floorArea, 'm^2', 'ft^2').get
1414
- if new_floor_area_ip == final_floor_area_ip
1415
- runner.registerInfo("Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2.")
1416
- else
1417
- runner.registerInfo("Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2. Total building area is #{OpenStudio.toNeatString(final_floor_area_ip, 0, true)} ft^2.")
1418
- end
1419
-
1420
- return new_spaces
1421
- end
1422
-
1423
- # make selected surfaces adiabatic
1424
- def make_surfaces_adiabatic(surfaces)
1425
- surfaces.each do |surface|
1426
- if surface.construction.is_initialized
1427
- surface.setConstruction(surface.construction.get)
1428
- end
1429
- surface.setOutsideBoundaryCondition('Adiabatic')
1430
- end
1431
- end
1432
-
1433
- # get length and width of rectangle matching bounding box aspect ratio will maintaining proper floor area
1434
- def calc_bar_reduced_bounding_box(envelope_data_hash)
1435
- bar = {}
1436
-
1437
- bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
1438
- bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
1439
- bounding_area = bounding_length * bounding_width
1440
- footprint_area = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective__num_stories].to_f
1441
- area_multiplier = footprint_area / bounding_area
1442
- edge_multiplier = Math.sqrt(area_multiplier)
1443
- bar[:length] = bounding_length * edge_multiplier
1444
- bar[:width] = bounding_width * edge_multiplier
1445
-
1446
- return bar
1447
- end
1448
-
1449
- # get length and width of rectangle matching longer of two edges, and reducing the other way until floor area matches
1450
- def calc_bar_reduced_width(envelope_data_hash)
1451
- bar = {}
1452
-
1453
- bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
1454
- bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
1455
- footprint_area = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective__num_stories].to_f
1456
-
1457
- if bounding_length >= bounding_width
1458
- bar[:length] = bounding_length
1459
- bar[:width] = footprint_area / bounding_length
1460
- else
1461
- bar[:width] = bounding_width
1462
- bar[:length] = footprint_area / bounding_width
1463
- end
1464
-
1465
- return bar
1466
- end
1467
-
1468
- # get length and width of rectangle by stretching it until both floor area and exterior wall area or perimeter match
1469
- def calc_bar_stretched(envelope_data_hash)
1470
- bar = {}
1471
-
1472
- bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
1473
- bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
1474
- a = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective__num_stories].to_f
1475
- p = envelope_data_hash[:building_perimeter]
1476
-
1477
- if bounding_length >= bounding_width
1478
- bar[:length] = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
1479
- bar[:width] = 0.25 * (p - Math.sqrt(p**2 - 16 * a))
1480
- else
1481
- bar[:length] = 0.25 * (p - Math.sqrt(p**2 - 16 * a))
1482
- bar[:width] = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
1483
- end
1484
-
1485
- return bar
1486
- end
1487
-
1488
- def bar_hash_setup_run(runner, model, args, length, width, floor_height_si, center_of_footprint, space_types_hash, num_stories)
1489
- # create envelope
1490
- # populate bar_hash and create envelope with data from envelope_data_hash and user arguments
1491
- bar_hash = {}
1492
- bar_hash[:length] = length
1493
- bar_hash[:width] = width
1494
- bar_hash[:num_stories_below_grade] = args['num_stories_below_grade']
1495
- bar_hash[:num_stories_above_grade] = args['num_stories_above_grade']
1496
- bar_hash[:floor_height] = floor_height_si
1497
- bar_hash[:center_of_footprint] = center_of_footprint
1498
- bar_hash[:bar_division_method] = args['bar_division_method']
1499
- bar_hash[:make_mid_story_surfaces_adiabatic] = args['make_mid_story_surfaces_adiabatic']
1500
- bar_hash[:space_types] = space_types_hash
1501
- bar_hash[:building_wwr_n] = args['wwr']
1502
- bar_hash[:building_wwr_s] = args['wwr']
1503
- bar_hash[:building_wwr_e] = args['wwr']
1504
- bar_hash[:building_wwr_w] = args['wwr']
1505
-
1506
- # round up non integer stoires to next integer
1507
- num_stories_round_up = num_stories.ceil
1508
- runner.registerInfo("Making bar with length of #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft and width of #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft")
1509
-
1510
- # party_walls_array to be used by orientation specific or fractional party wall values
1511
- party_walls_array = [] # this is an array of arrays, where each entry is effective building story with array of directions
1512
-
1513
- if args['party_wall_stories_north'] + args['party_wall_stories_south'] + args['party_wall_stories_east'] + args['party_wall_stories_west'] > 0
1514
-
1515
- # loop through effective number of stories add orientation specific party walls per user arguments
1516
- num_stories_round_up.times do |i|
1517
- test_value = i + 1 - bar_hash[:num_stories_below_grade]
1518
-
1519
- array = []
1520
- if args['party_wall_stories_north'] >= test_value
1521
- array << 'north'
1522
- end
1523
- if args['party_wall_stories_south'] >= test_value
1524
- array << 'south'
1525
- end
1526
- if args['party_wall_stories_east'] >= test_value
1527
- array << 'east'
1528
- end
1529
- if args['party_wall_stories_west'] >= test_value
1530
- array << 'west'
1531
- end
1532
-
1533
- # populate party_wall_array for this story
1534
- party_walls_array << array
1535
- end
1536
- end
1537
-
1538
- # calculate party walls if using party_wall_fraction method
1539
- if args['party_wall_fraction'] > 0 && !party_walls_array.empty?
1540
- runner.registerWarning('Both orientation and fractional party wall values arguments were populated, will ignore fractional party wall input')
1541
- elsif args['party_wall_fraction'] > 0
1542
-
1543
- # orientation of long and short side of building will vary based on building rotation
1544
-
1545
- # full story ext wall area
1546
- typical_length_facade_area = length * floor_height_si
1547
- typical_width_facade_area = width * floor_height_si
1548
-
1549
- # top story ext wall area, may be partial story
1550
- partial_story_multiplier = (1.0 - args['num_stories_above_grade'].ceil + args['num_stories_above_grade'])
1551
- area_multiplier = partial_story_multiplier
1552
- edge_multiplier = Math.sqrt(area_multiplier)
1553
- top_story_length = length * edge_multiplier
1554
- top_story_width = width * edge_multiplier
1555
- top_story_length_facade_area = top_story_length * floor_height_si
1556
- top_story_width_facade_area = top_story_width * floor_height_si
1557
-
1558
- total_exterior_wall_area = 2 * (length + width) * (args['num_stories_above_grade'].ceil - 1.0) * floor_height_si + 2 * (top_story_length + top_story_width) * floor_height_si
1559
- target_party_wall_area = total_exterior_wall_area * args['party_wall_fraction']
1560
-
1561
- width_counter = 0
1562
- width_area = 0.0
1563
- facade_area = typical_width_facade_area
1564
- until (width_area + facade_area >= target_party_wall_area) || (width_counter == args['num_stories_above_grade'].ceil * 2)
1565
- # update facade area for top story
1566
- if width_counter == args['num_stories_above_grade'].ceil - 1 || width_counter == args['num_stories_above_grade'].ceil * 2 - 1
1567
- facade_area = top_story_width_facade_area
1568
- else
1569
- facade_area = typical_width_facade_area
1570
- end
1571
-
1572
- width_counter += 1
1573
- width_area += facade_area
1574
-
1575
- end
1576
- width_area_remainder = target_party_wall_area - width_area
1577
-
1578
- length_counter = 0
1579
- length_area = 0.0
1580
- facade_area = typical_length_facade_area
1581
- until (length_area + facade_area >= target_party_wall_area) || (length_counter == args['num_stories_above_grade'].ceil * 2)
1582
- # update facade area for top story
1583
- if length_counter == args['num_stories_above_grade'].ceil - 1 || length_counter == args['num_stories_above_grade'].ceil * 2 - 1
1584
- facade_area = top_story_length_facade_area
1585
- else
1586
- facade_area = typical_length_facade_area
1587
- end
1588
-
1589
- length_counter += 1
1590
- length_area += facade_area
1591
- end
1592
- length_area_remainder = target_party_wall_area - length_area
1593
-
1594
- # get rotation and best fit to adjust orientation for fraction party wall
1595
- rotation = args['building_rotation'] % 360.0 # should result in value between 0 and 360
1596
- card_dir_array = [0.0, 90.0, 180.0, 270.0, 360.0]
1597
- # reverse array to properly handle 45, 135, 225, and 315
1598
- best_fit = card_dir_array.reverse.min_by { |x| (x.to_f - rotation).abs }
1599
-
1600
- if ![90.0, 270.0].include? best_fit
1601
- width_card_dir = ['east', 'west']
1602
- length_card_dir = ['north', 'south']
1603
- else # if rotation is closest to 90 or 270 then reverse which orientation is used for length and width
1604
- width_card_dir = ['north', 'south']
1605
- length_card_dir = ['east', 'west']
1606
- end
1607
-
1608
- # if dont' find enough on short sides
1609
- if width_area_remainder <= typical_length_facade_area
1610
-
1611
- num_stories_round_up.times do |i|
1612
- if i + 1 <= args['num_stories_below_grade']
1613
- party_walls_array << []
1614
- next
1615
- end
1616
- if i + 1 - args['num_stories_below_grade'] <= width_counter
1617
- if i + 1 - args['num_stories_below_grade'] <= width_counter - args['num_stories_above_grade']
1618
- party_walls_array << width_card_dir
1619
- else
1620
- party_walls_array << [width_card_dir.first]
1621
- end
1622
- else
1623
- party_walls_array << []
1624
- end
1625
- end
1626
-
1627
- else # use long sides instead
1628
-
1629
- num_stories_round_up.times do |i|
1630
- if i + 1 <= args['num_stories_below_grade']
1631
- party_walls_array << []
1632
- next
1633
- end
1634
- if i + 1 - args['num_stories_below_grade'] <= length_counter
1635
- if i + 1 - args['num_stories_below_grade'] <= length_counter - args['num_stories_above_grade']
1636
- party_walls_array << length_card_dir
1637
- else
1638
- party_walls_array << [length_card_dir.first]
1639
- end
1640
- else
1641
- party_walls_array << []
1642
- end
1643
- end
1644
-
1645
- end
1646
-
1647
- # TODO: - currently won't go past making two opposing sets of walls party walls. Info and registerValue are after create_bar in measure.rb
1648
-
1649
- end
1650
-
1651
- # populate bar hash with story information
1652
- bar_hash[:stories] = {}
1653
- num_stories_round_up.times do |i|
1654
- if party_walls_array.empty?
1655
- party_walls = []
1656
- else
1657
- party_walls = party_walls_array[i]
1658
- end
1659
-
1660
- # add below_partial_story
1661
- if num_stories.ceil > num_stories && i == num_stories_round_up - 2
1662
- below_partial_story = true
1663
- else
1664
- below_partial_story = false
1665
- end
1666
-
1667
- # bottom_story_ground_exposed_floor and top_story_exterior_exposed_roof already setup as bool
1668
- bar_hash[:stories]["key #{i}"] = { story_party_walls: party_walls, story_min_multiplier: 1, story_included_in_building_area: true, below_partial_story: below_partial_story, bottom_story_ground_exposed_floor: args['bottom_story_ground_exposed_floor'], top_story_exterior_exposed_roof: args['top_story_exterior_exposed_roof'] }
1669
- end
1670
-
1671
- # create bar
1672
- new_spaces = create_bar(runner, model, bar_hash, args['story_multiplier'])
1673
-
1674
- # check expect roof and wall area
1675
- target_footprint = bar_hash[:length] * bar_hash[:width]
1676
- ground_floor_area = 0.0
1677
- roof_area = 0.0
1678
- new_spaces.each do |space|
1679
- space.surfaces.each do |surface|
1680
- if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground'
1681
- ground_floor_area += surface.netArea
1682
- elsif surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors'
1683
- roof_area += surface.netArea
1684
- end
1685
- end
1686
- end
1687
- # TODO: - extend to address when top and or bottom story are not exposed via argument
1688
- if ground_floor_area > target_footprint + 0.001 || roof_area > target_footprint + 0.001
1689
- # runner.registerError("Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.")
1690
- # return false
1691
-
1692
- # not providing adiabatic work around when top story is partial story.
1693
- if args['num_stories_above_grade'].to_f != args['num_stories_above_grade'].ceil
1694
- runner.registerError('Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.')
1695
- return false
1696
- else
1697
- runner.registerInfo('Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error, altering impacted surfaces boundary condition to be adiabatic.')
1698
- match_error = true
1699
- end
1700
- else
1701
- match_error = false
1702
- end
1703
-
1704
- # TODO: - should be able to remove this fix after OpenStudio intersection issue is fixed. At that time turn the above message into an error with return false after it
1705
- if match_error
1706
-
1707
- # identify z value of top and bottom story
1708
- bottom_story = nil
1709
- top_story = nil
1710
- new_spaces.each do |space|
1711
- story = space.buildingStory.get
1712
- nom_z = story.nominalZCoordinate.get
1713
- if bottom_story.nil?
1714
- bottom_story = nom_z
1715
- elsif bottom_story > nom_z
1716
- bottom_story = nom_z
1717
- end
1718
- if top_story.nil?
1719
- top_story = nom_z
1720
- elsif top_story < nom_z
1721
- top_story = nom_z
1722
- end
1723
- end
1724
-
1725
- # change boundary condition and intersection as needed.
1726
- new_spaces.each do |space|
1727
- if space.buildingStory.get.nominalZCoordinate.get > bottom_story
1728
- # change floors
1729
- space.surfaces.each do |surface|
1730
- next if !(surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground')
1731
- surface.setOutsideBoundaryCondition('Adiabatic')
1732
- end
1733
- end
1734
- if space.buildingStory.get.nominalZCoordinate.get < top_story
1735
- # change ceilings
1736
- space.surfaces.each do |surface|
1737
- next if !(surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors')
1738
- surface.setOutsideBoundaryCondition('Adiabatic')
1739
- end
1740
- end
1741
- end
1742
- end
1743
- end
1744
-
1745
- # bar_arg_check_setup
1746
- def bar_arg_check_setup(model, runner, user_arguments, building_type_ratios = true)
1747
-
1748
- # assign the user inputs to variables
1749
- args = OsLib_HelperMethods.createRunVariables(runner, model, user_arguments, arguments(model))
1750
- if !args then return false end
1751
-
1752
- # add in arguments that may not be passed in
1753
- if !args.key?('double_loaded_corridor')
1754
- args['double_loaded_corridor'] = 'None' # use None when not in measure building type data may not contain this
1755
- end
1756
- if !args.key?('perim_mult')
1757
- args['perim_mult'] = 1.0 # will not make two bars for extended perimeter
1758
- end
1759
-
1760
- # lookup and replace argument values from upstream measures
1761
- if args['use_upstream_args'] == true
1762
- args.each do |arg, value|
1763
- next if arg == 'use_upstream_args' # this argument should not be changed
1764
- value_from_osw = OsLib_HelperMethods.check_upstream_measure_for_arg(runner, arg)
1765
- if !value_from_osw.empty?
1766
- runner.registerInfo("Replacing argument named #{arg} from current measure with a value of #{value_from_osw[:value]} from #{value_from_osw[:measure_name]}.")
1767
- new_val = value_from_osw[:value]
1768
- # TODO: - make code to handle non strings more robust. check_upstream_measure_for_arg could pass back the argument type
1769
- if arg == 'total_bldg_floor_area'
1770
- args[arg] = new_val.to_f
1771
- elsif arg == 'num_stories_above_grade'
1772
- args[arg] = new_val.to_f
1773
- elsif arg == 'zipcode'
1774
- args[arg] = new_val.to_i
1775
- else
1776
- args[arg] = new_val
1777
- end
1778
- end
1779
- end
1780
- end
1781
-
1782
- # check expected values of double arguments
1783
- fraction_args = ['wwr', 'party_wall_fraction']
1784
- if building_type_ratios
1785
- fraction_args << 'bldg_type_b_fract_bldg_area'
1786
- fraction_args << 'bldg_type_c_fract_bldg_area'
1787
- fraction_args << 'bldg_type_d_fract_bldg_area'
1788
- end
1789
- fraction = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 1.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => fraction_args)
1790
-
1791
- one_or_greater_args = ['num_stories_above_grade']
1792
- one_or_greater = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 1.0, 'max' => nil, 'min_eq_bool' => true, 'max_eq_bool' => false, 'arg_array' => one_or_greater_args)
1793
-
1794
- non_neg_args = ['num_stories_below_grade',
1795
- 'floor_height',
1796
- 'ns_to_ew_ratio',
1797
- 'party_wall_stories_north',
1798
- 'party_wall_stories_south',
1799
- 'party_wall_stories_east',
1800
- 'party_wall_stories_west',
1801
- 'total_bldg_floor_area',
1802
- 'single_floor_area',
1803
- 'bar_width']
1804
- non_neg = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => nil, 'min_eq_bool' => true, 'max_eq_bool' => false, 'arg_array' => non_neg_args)
1805
-
1806
- # return false if any errors fail
1807
- if !fraction then return false end
1808
- return false if !one_or_greater
1809
- return false if !non_neg
1810
-
1811
- return args
1812
-
1813
- end
1814
-
1815
- # bar_from_building_type_ratios
1816
- # used for varieties of measures that create bar from building type ratios
1817
- def bar_from_building_type_ratios(model, runner, user_arguments)
1818
-
1819
- # prep arguments
1820
- args = bar_arg_check_setup(model,runner,user_arguments)
1821
- if !args then return false end
1822
-
1823
- # check that sum of fractions for b,c, and d is less than 1.0 (so something is left for primary building type)
1824
- bldg_type_a_fract_bldg_area = 1.0 - args['bldg_type_b_fract_bldg_area'] - args['bldg_type_c_fract_bldg_area'] - args['bldg_type_d_fract_bldg_area']
1825
- if bldg_type_a_fract_bldg_area <= 0.0
1826
- runner.registerError('Primary Building Type fraction of floor area must be greater than 0. Please lower one or more of the fractions for Building Type B-D.')
1827
- return false
1828
- end
1829
-
1830
- # Make the standard applier
1831
- standard = Standard.build((args['template']).to_s)
1832
-
1833
- # report initial condition of model
1834
- runner.registerInitialCondition("The building started with #{model.getSpaces.size} spaces.")
1835
-
1836
- # determine of ns_ew needs to be mirrored
1837
- mirror_ns_ew = false
1838
- rotation = model.getBuilding.northAxis
1839
- if rotation > 45.0 && rotation < 135.0
1840
- mirror_ns_ew = true
1841
- elsif rotation > 45.0 && rotation < 135.0
1842
- mirror_ns_ew = true
1843
- end
1844
-
1845
- # remove non-resource objects not removed by removing the building
1846
- remove_non_resource_objects(runner, model)
1847
-
1848
- # rename building to infer template in downstream measure
1849
- name_array = [args['template'], args['bldg_type_a']]
1850
- if args['bldg_type_b_fract_bldg_area'] > 0 then name_array << args['bldg_type_b'] end
1851
- if args['bldg_type_c_fract_bldg_area'] > 0 then name_array << args['bldg_type_c'] end
1852
- if args['bldg_type_d_fract_bldg_area'] > 0 then name_array << args['bldg_type_d'] end
1853
- model.getBuilding.setName(name_array.join('|').to_s)
1854
-
1855
- # hash to whole building type data
1856
- building_type_hash = {}
1857
-
1858
- # gather data for bldg_type_a
1859
- building_type_hash[args['bldg_type_a']] = {}
1860
- building_type_hash[args['bldg_type_a']][:frac_bldg_area] = bldg_type_a_fract_bldg_area
1861
- # building_type_hash[args['bldg_type_a']][:num_units] = args['bldg_type_a_num_units']
1862
- building_type_hash[args['bldg_type_a']][:space_types] = get_space_types_from_building_type(args['bldg_type_a'], args['template'], true)
1863
-
1864
- # gather data for bldg_type_b
1865
- if args['bldg_type_b_fract_bldg_area'] > 0
1866
- building_type_hash[args['bldg_type_b']] = {}
1867
- building_type_hash[args['bldg_type_b']][:frac_bldg_area] = args['bldg_type_b_fract_bldg_area']
1868
- # building_type_hash[args['bldg_type_b']][:num_units] = args['bldg_type_b_num_units']
1869
- building_type_hash[args['bldg_type_b']][:space_types] = get_space_types_from_building_type(args['bldg_type_b'], args['template'], true)
1870
- end
1871
-
1872
- # gather data for bldg_type_c
1873
- if args['bldg_type_c_fract_bldg_area'] > 0
1874
- building_type_hash[args['bldg_type_c']] = {}
1875
- building_type_hash[args['bldg_type_c']][:frac_bldg_area] = args['bldg_type_c_fract_bldg_area']
1876
- # building_type_hash[args['bldg_type_c']][:num_units] = args['bldg_type_c_num_units']
1877
- building_type_hash[args['bldg_type_c']][:space_types] = get_space_types_from_building_type(args['bldg_type_c'], args['template'], true)
1878
- end
1879
-
1880
- # gather data for bldg_type_d
1881
- if args['bldg_type_d_fract_bldg_area'] > 0
1882
- building_type_hash[args['bldg_type_d']] = {}
1883
- building_type_hash[args['bldg_type_d']][:frac_bldg_area] = args['bldg_type_d_fract_bldg_area']
1884
- # building_type_hash[args['bldg_type_d']][:num_units] = args['bldg_type_d_num_units']
1885
- building_type_hash[args['bldg_type_d']][:space_types] = get_space_types_from_building_type(args['bldg_type_d'], args['template'], true)
1886
- end
1887
-
1888
- # call bar_from_building_space_type_ratios to generate bar
1889
- bar_from_space_type_ratios(model, runner, user_arguments, args, building_type_hash)
1890
-
1891
- return true
1892
-
1893
- end
1894
-
1895
- # bar_from_space_type_ratios
1896
- # used for varieties of measures that create bar from space type or building type ratios
1897
- # args and building_type_hash should both be nil or neither shoould be nill
1898
- def bar_from_space_type_ratios(model, runner, user_arguments, args = nil, building_type_hash = nil)
1899
-
1900
- # do not setup arguments if they were already passed in to this method
1901
- if args.nil?
1902
- # prep arguments
1903
- args = bar_arg_check_setup(model,runner,user_arguments,false) # false stops it from checking args on used in bar_from_building_type_ratios
1904
- if !args then return false end
1905
-
1906
- # process arg into hash
1907
- space_type_hash_name = {}
1908
- args['space_type_hash_string'][0..-1].split(/, /).each { |entry| entryMap = entry.split(/=>/); value_str = entryMap[1]; space_type_hash_name[entryMap[0].strip[0..-1].to_s] = value_str.nil? ? '' : value_str.strip[0..-1].to_f }
1909
-
1910
- # create building type hasn from space type ratios
1911
- building_type_hash = {}
1912
- building_type_fraction_of_building = 0.0
1913
- space_type_hash_name.each do |building_space_type,ratio|
1914
- building_type = building_space_type.split("|")[0].strip
1915
- space_type = building_space_type.split("|")[1].strip
1916
-
1917
- # harvest height and circ info from get_space_types_from_building_type(building_type, template, whole_building = true)
1918
- building_type_lookup_info = get_space_types_from_building_type(building_type,args['template'])
1919
- if building_type_lookup_info.size == 0
1920
- runner.registerWarning("#{building_type} looks like an invalid building type for #{args['template']}")
1921
- end
1922
- space_type_info_hash = {}
1923
- if building_type_lookup_info.key?(space_type)
1924
- if building_type_lookup_info[space_type].key?(:story_height)
1925
- space_type_info_hash[:story_height] = building_type_lookup_info[space_type][:story_height]
1926
- end
1927
- if building_type_lookup_info[space_type].key?(:default)
1928
- space_type_info_hash[:default] = building_type_lookup_info[space_type][:default]
1929
- end
1930
- if building_type_lookup_info[space_type].key?(:circ)
1931
- space_type_info_hash[:circ] = building_type_lookup_info[space_type][:circ]
1932
- end
1933
- else
1934
- runner.registerWarning("#{space_type} looks like an invalid space type for #{building_type}")
1935
- end
1936
-
1937
- # extend harvested data with custom ratios from space type ratio string argument.
1938
- if building_type_hash.key?(building_type)
1939
- building_type_hash[building_type][:frac_bldg_area] += ratio
1940
- space_type_info_hash[:ratio] = ratio
1941
- building_type_hash[building_type][:space_types][space_type] = space_type_info_hash
1942
- else
1943
- building_type_hash[building_type] = {}
1944
- building_type_hash[building_type][:frac_bldg_area] = ratio
1945
- space_type_info_hash[:ratio] = ratio
1946
- space_types = {}
1947
- space_types[space_type] = space_type_info_hash
1948
- building_type_hash[building_type][:space_types] = space_types
1949
- end
1950
- building_type_fraction_of_building += ratio
1951
- end
1952
-
1953
- # identify primary building type for building form defaults
1954
- primary_building_type = building_type_hash.keys.first # update to choose building with highest ratio
1955
- runner.registerInfo("Creating bar with space type ratio proided as argument.")
1956
- runner.registerInfo("Using building type from first ratio #{primary_building_type} as the primary building type. This is used for building form defaults.")
1957
-
1958
- # todo - confirm if this will get normalized up/down later of if I should fix or stop here instead of just a warning
1959
- if building_type_fraction_of_building > 1.0
1960
- runner.registerWarning("Sum of Space Type Ratio of #{building_type_fraction_of_building} is greater than the expected value of 1.0")
1961
- elsif building_type_fraction_of_building < 1.0
1962
- runner.registerWarning("Sum of Space Type Ratio of #{building_type_fraction_of_building} is less than the expected value of 1.0")
1963
- end
1964
-
1965
- else # else is used when bar_from_building_type_ratio is used
1966
-
1967
- # if aspect ratio, story height or wwr have argument value of 0 then use smart building type defaults
1968
- primary_building_type = args['bldg_type_a']
1969
- runner.registerInfo("Creating bar space type ratios by building type based on ratios from prototype models.")
1970
- runner.registerInfo("#{primary_building_type} will be used for building form defaults.")
1971
-
1972
- end
1973
-
1974
- # get defaults for the primary building type
1975
- building_form_defaults = building_form_defaults(primary_building_type)
1976
-
1977
- # store list of defaulted items
1978
- defaulted_args = []
1979
-
1980
- if args['ns_to_ew_ratio'] == 0.0
1981
- args['ns_to_ew_ratio'] = building_form_defaults[:aspect_ratio]
1982
- runner.registerInfo("0.0 value for aspect ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:aspect_ratio]}.")
1983
- end
1984
-
1985
- if args['perim_mult'] == 0.0
1986
- # if this is not defined then use default of 1.0
1987
- if !building_form_defaults.has_key?(:perim_mult)
1988
- args['perim_mult'] = 1.0
1989
- else
1990
- args['perim_mult'] = building_form_defaults[:perim_mult]
1991
- end
1992
- runner.registerInfo("0.0 value for minimum perimeter multiplier will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:perim_mult]}.")
1993
- elsif args['perim_mult'] < 1.0
1994
- runner.registerError("Other than the smart default value of 0, the minimum perimeter multiplier should be equal to 1.0 or greater.")
1995
- return false
1996
- end
1997
-
1998
- if args['floor_height'] == 0.0
1999
- args['floor_height'] = building_form_defaults[:typical_story]
2000
- runner.registerInfo("0.0 value for floor height will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:typical_story]}.")
2001
- defaulted_args << 'floor_height'
2002
- end
2003
- # because of this can't set wwr to 0.0. If that is desired then we can change this to check for 1.0 instead of 0.0
2004
- if args['wwr'] == 0.0
2005
- args['wwr'] = building_form_defaults[:wwr]
2006
- runner.registerInfo("0.0 value for window to wall ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:wwr]}.")
2007
- end
2008
-
2009
- # Make the standard applier
2010
- standard = Standard.build("#{args['template']}")
2011
-
2012
- # report initial condition of model
2013
- runner.registerInitialCondition("The building started with #{model.getSpaces.size} spaces.")
2014
-
2015
- # determine of ns_ew needs to be mirrored
2016
- mirror_ns_ew = false
2017
- rotation = model.getBuilding.northAxis
2018
- if rotation > 45.0 && rotation < 135.0
2019
- mirror_ns_ew = true
2020
- elsif rotation > 45.0 && rotation < 135.0
2021
- mirror_ns_ew = true
2022
- end
2023
-
2024
- # remove non-resource objects not removed by removing the building
2025
- remove_non_resource_objects(runner, model)
2026
-
2027
- # creating space types for requested building types
2028
- building_type_hash.each do |building_type, building_type_hash|
2029
- runner.registerInfo("Creating Space Types for #{building_type}.")
2030
-
2031
- # mapping building_type name is needed for a few methods
2032
- building_type = standard.model_get_lookup_name(building_type)
2033
-
2034
- # create space_type_map from array
2035
- sum_of_ratios = 0.0
2036
- building_type_hash[:space_types] = building_type_hash[:space_types].sort_by { |k, v| v[:ratio] }.to_h
2037
- building_type_hash[:space_types].each do |space_type_name, hash|
2038
- next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
2039
-
2040
- # create space type
2041
- space_type = OpenStudio::Model::SpaceType.new(model)
2042
- space_type.setStandardsBuildingType(building_type)
2043
- space_type.setStandardsSpaceType(space_type_name)
2044
- space_type.setName("#{building_type} #{space_type_name}")
2045
-
2046
- # set color
2047
- test = standard.space_type_apply_rendering_color(space_type) # this uses openstudio-standards
2048
- if !test
2049
- # TODO: - once fixed in standards un-comment this
2050
- # runner.registerWarning("Could not find color for #{args['template']} #{space_type.name}")
2051
- end
2052
-
2053
- # extend hash to hold new space type object
2054
- hash[:space_type] = space_type
2055
-
2056
- # add to sum_of_ratios counter for adjustment multiplier
2057
- sum_of_ratios += hash[:ratio]
2058
- end
2059
-
2060
- # store multiplier needed to adjust sum of ratios to equal 1.0
2061
- building_type_hash[:ratio_adjustment_multiplier] = 1.0 / sum_of_ratios
2062
- end
2063
-
2064
- # calculate length and with of bar
2065
- total_bldg_floor_area_si = OpenStudio.convert(args['total_bldg_floor_area'], 'ft^2', 'm^2').get
2066
- single_floor_area_si = OpenStudio.convert(args['single_floor_area'], 'ft^2', 'm^2').get
2067
-
2068
- # store number of stories
2069
- num_stories = args['num_stories_below_grade'] + args['num_stories_above_grade']
2070
-
2071
- # handle user-assigned single floor plate size condition
2072
- if args['single_floor_area'] > 0.0
2073
- footprint_si = single_floor_area_si
2074
- total_bldg_floor_area_si = footprint_si * num_stories.to_f
2075
- runner.registerWarning('User-defined single floor area was used for calculation of total building floor area')
2076
- # add warning if custom_height_bar is true and applicable building type is selected
2077
- if args['custom_height_bar']
2078
- runner.registerWarning('Cannot use custom height bar with single floor area method, will not create custom height bar.')
2079
- args['custom_height_bar'] = false
2080
- end
2081
- else
2082
- footprint_si = nil
2083
- end
2084
-
2085
- # populate space_types_hash
2086
- space_types_hash = {}
2087
- multi_height_space_types_hash = {}
2088
- custom_story_heights = []
2089
- if args['space_type_sort_logic'] == 'Building Type > Size'
2090
- building_type_hash = building_type_hash.sort_by { |k, v| v[:frac_bldg_area] }
2091
- end
2092
- building_type_hash.each do |building_type, building_type_hash|
2093
- if args['double_loaded_corridor'] == 'Primary Space Type'
2094
-
2095
- # see if building type has circulation space type, if so then merge that along with default space type into hash key in place of space type
2096
- default_st = nil
2097
- circ_st = nil
2098
- building_type_hash[:space_types].each do |space_type_name, hash|
2099
- if hash[:default] then default_st = space_type_name end
2100
- if hash[:circ] then circ_st = space_type_name end
2101
- end
2102
-
2103
- # update building hash
2104
- if !default_st.nil? && !circ_st.nil?
2105
- runner.registerInfo("Combining #{default_st} and #{circ_st} into a group representing a double loaded corridor")
2106
-
2107
- # add new item
2108
- building_type_hash[:space_types]['Double Loaded Corridor'] = {}
2109
- double_loaded_st = building_type_hash[:space_types]['Double Loaded Corridor']
2110
- double_loaded_st[:ratio] = building_type_hash[:space_types][default_st][:ratio] + building_type_hash[:space_types][circ_st][:ratio]
2111
- double_loaded_st[:double_loaded_corridor] = true
2112
- double_loaded_st[:space_type] = model.getBuilding
2113
- double_loaded_st[:children] = {}
2114
- building_type_hash[:space_types][default_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][default_st][:ratio]
2115
- building_type_hash[:space_types][circ_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][circ_st][:ratio]
2116
- building_type_hash[:space_types][default_st][:name] = default_st
2117
- building_type_hash[:space_types][circ_st][:name] = circ_st
2118
- double_loaded_st[:children][:default] = building_type_hash[:space_types][default_st]
2119
- double_loaded_st[:children][:circ] = building_type_hash[:space_types][circ_st]
2120
- double_loaded_st[:orig_ratio] = 0.0
2121
-
2122
- # zero out ratios from old item (don't delete because I still want the space types made)
2123
- building_type_hash[:space_types][default_st][:ratio] = 0.0
2124
- building_type_hash[:space_types][circ_st][:ratio] = 0.0
2125
- end
2126
- end
2127
-
2128
- building_type_hash[:space_types].each do |space_type_name, hash|
2129
- next if hash[:space_type_gen] == false
2130
-
2131
- space_type = hash[:space_type]
2132
- ratio_of_bldg_total = hash[:ratio] * building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area]
2133
- final_floor_area = ratio_of_bldg_total * total_bldg_floor_area_si # I think I can just pass ratio but passing in area is cleaner
2134
-
2135
- # only add custom height space if 0 is used for floor_height
2136
- if defaulted_args.include?('floor_height') && hash.key?(:story_height) && args['custom_height_bar']
2137
- multi_height_space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type, story_height: hash[:story_height] }
2138
- if hash.key?(:orig_ratio) then multi_height_space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
2139
- custom_story_heights << hash[:story_height]
2140
- if args['wwr'] == 0 && hash.key?(:wwr)
2141
- multi_height_space_types_hash[space_type][:wwr] = hash[:wwr]
2142
- end
2143
- else
2144
- # only add wwr if 0 used for wwr arg and if space type has wwr as key
2145
- space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type }
2146
- if hash.key?(:orig_ratio) then space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
2147
- if args['wwr'] == 0 && hash.key?(:wwr)
2148
- space_types_hash[space_type][:wwr] = hash[:wwr]
2149
- end
2150
- if hash[:double_loaded_corridor]
2151
- space_types_hash[space_type][:children] = hash[:children]
2152
- end
2153
- end
2154
- end
2155
- end
2156
-
2157
- # resort if not sorted by building type
2158
- if args['space_type_sort_logic'] == 'Size'
2159
- # added code to convert to hash. I use sort_by 3 other times, but those seem to be working fine as is now.
2160
- space_types_hash = Hash[space_types_hash.sort_by { |k, v| v[:floor_area] }]
2161
- end
2162
-
2163
- # calculate targets for testing
2164
- target_areas = {} # used for checks
2165
- target_areas_cust_height = 0.0
2166
- space_types_hash.each do |k, v|
2167
- if v.key?(:orig_ratio)
2168
- target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
2169
- else
2170
- target_areas[k] = v[:floor_area]
2171
- end
2172
- end
2173
- multi_height_space_types_hash.each do |k, v|
2174
- if v.key?(:orig_ratio)
2175
- target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
2176
- target_areas_cust_height += v[:orig_ratio] * total_bldg_floor_area_si
2177
- else
2178
- target_areas[k] = v[:floor_area]
2179
- target_areas_cust_height += v[:floor_area]
2180
- end
2181
- end
2182
-
2183
- # gather inputs
2184
- if footprint_si.nil?
2185
- footprint_si = (total_bldg_floor_area_si - target_areas_cust_height) / num_stories.to_f
2186
- end
2187
- floor_height_si = OpenStudio.convert(args['floor_height'], 'ft', 'm').get
2188
- min_allow_size = OpenStudio.convert(15.0, 'ft', 'm').get
2189
- specified_bar_width_si = OpenStudio.convert(args['bar_width'], 'ft', 'm').get
2190
-
2191
- # set custom width
2192
- if specified_bar_width_si > 0
2193
- runner.registerInfo('Ignoring perimeter multiplier argument when non zero width argument is used')
2194
- if footprint_si / specified_bar_width_si >= min_allow_size
2195
- width = specified_bar_width_si
2196
- length = footprint_si / width
2197
- else
2198
- length = min_allow_size
2199
- width = footprint_si / length
2200
- runner.registerWarning('User specified width results in a length that is too short, adjusting width to be narrower than specified.')
2201
- end
2202
- width_cust_height = specified_bar_width_si
2203
- else
2204
- width = Math.sqrt(footprint_si / args['ns_to_ew_ratio'])
2205
- length = footprint_si / width
2206
- width_cust_height = Math.sqrt(target_areas_cust_height / args['ns_to_ew_ratio'])
2207
- end
2208
- length_cust_height = target_areas_cust_height / width_cust_height
2209
- if args['perim_mult'] > 1.0 && target_areas_cust_height > 0.0
2210
- # TODO: - update tests that hit this warning
2211
- runner.registerWarning('Ignoring perimeter multiplier for bar that represents custom height spaces.')
2212
- end
2213
-
2214
- # check if dual bar is needed
2215
- dual_bar = false
2216
- if specified_bar_width_si > 0.0 && args['bar_division_method'] == 'Multiple Space Types - Individual Stories Sliced'
2217
- if length / width != args['ns_to_ew_ratio']
2218
-
2219
- if args['ns_to_ew_ratio'] >= 1.0 && args['ns_to_ew_ratio'] > length / width
2220
- runner.registerWarning("Can't meet target aspect ratio of #{args['ns_to_ew_ratio']}, Lowering it to #{length / width} ")
2221
- args['ns_to_ew_ratio'] = length / width
2222
- elsif args['ns_to_ew_ratio'] < 1.0 && args['ns_to_ew_ratio'] > length / width
2223
- runner.registerWarning("Can't meet target aspect ratio of #{args['ns_to_ew_ratio']}, Increasing it to #{length / width} ")
2224
- args['ns_to_ew_ratio'] = length / width
2225
- else
2226
- # check if each bar would be longer then 15 feet, then set as dual bar and override perimeter multiplier
2227
- length_alt1 = ((args['ns_to_ew_ratio'] * footprint_si) / width + 2 * args['ns_to_ew_ratio'] * width - 2 * width) / (1 + args['ns_to_ew_ratio'])
2228
- length_alt2 = length - length_alt1
2229
- if [length_alt1, length_alt2].min >= min_allow_size
2230
- dual_bar = true
2231
- else
2232
- runner.registerInfo('Second bar would be below minimum length, will model as single bar')
2233
- # swap length and width if single bar and aspect ratio less than 1
2234
- if args['ns_to_ew_ratio'] < 1.0
2235
- width = length
2236
- length = specified_bar_width_si
2237
- end
2238
- end
2239
- end
2240
- end
2241
- elsif args['perim_mult'] > 1.0 && args['bar_division_method'] == 'Multiple Space Types - Individual Stories Sliced'
2242
- runner.registerInfo('You selected a perimeter multiplier greater than 1.0 for a supported bar division method. This will result in two detached rectangular buildings if secondary bar meets minimum size requirements.')
2243
- dual_bar = true
2244
- elsif args['perim_mult'] > 1.0
2245
- runner.registerWarning("You selected a perimeter multiplier greater than 1.0 but didn't select a bar division method that supports this. The value for this argument will be ignored by the measure")
2246
- end
2247
-
2248
- # calculations for dual bar, which later will be setup to run create_bar twice
2249
- if dual_bar
2250
- min_perim = 2 * width + 2 * length
2251
- target_area = footprint_si
2252
- target_perim = min_perim * args['perim_mult']
2253
- tol_testing = 0.00001
2254
- dual_bar_calc_approach = nil # stretched, adiabatic_ends_bar_b, dual_bar
2255
- runner.registerInfo("Minimum rectangle is #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft with an area of #{OpenStudio.toNeatString(OpenStudio.convert(length * width, 'm^2', 'ft^2').get, 0, true)} ft^2. Perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(min_perim, 'm', 'ft').get, 0, true)} ft.")
2256
- runner.registerInfo("Target dual bar perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(target_perim, 'm', 'ft').get, 0, true)} ft.")
2257
-
2258
- # determine which of the three paths to hit target perimeter multiplier are possible
2259
- # A use dual bar non adiabatic
2260
- # B use dual bar adiabatic
2261
- # C use stretched bar (requires model to miss ns/ew ratio)
2262
-
2263
- # custom quadratic equation to solve two bars with common width 2l^2 - p*l + 4a = 0
2264
- if target_perim**2 - 32 * footprint_si > 0
2265
- if specified_bar_width_si > 0
2266
- runner.registerInfo('Ignoring perimeter multiplier argument and using use specified bar width.')
2267
- dual_double_end_width = specified_bar_width_si
2268
- dual_double_end_length = footprint_si / dual_double_end_width
2269
- else
2270
- dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 32 * footprint_si))
2271
- dual_double_end_width = footprint_si / dual_double_end_length
2272
- end
2273
-
2274
- # now that stretched bar is made, determine where to split it and rotate
2275
- bar_a_length = (args['ns_to_ew_ratio'] * (dual_double_end_length + dual_double_end_width) - dual_double_end_width) / (1 + args['ns_to_ew_ratio'])
2276
- bar_b_length = dual_double_end_length - bar_a_length
2277
- area_a = bar_a_length * dual_double_end_width
2278
- area_b = bar_b_length * dual_double_end_width
2279
- else
2280
- # this will throw it to adiabatic ends test
2281
- bar_a_length = 0
2282
- bar_b_length = 0
2283
- end
2284
-
2285
- if bar_a_length >= min_allow_size && bar_b_length >= min_allow_size
2286
- dual_bar_calc_approach = 'dual_bar'
2287
- else
2288
- # adiabatic bar input calcs
2289
- if target_perim**2 - 16 * footprint_si > 0
2290
- adiabatic_dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
2291
- adiabatic_dual_double_end_width = footprint_si / adiabatic_dual_double_end_length
2292
- # test for unexpected
2293
- unexpected = false
2294
- if (target_area - adiabatic_dual_double_end_length * adiabatic_dual_double_end_width).abs > tol_testing then unexpected = true end
2295
- if specified_bar_width_si == 0
2296
- if (target_perim - (adiabatic_dual_double_end_length * 2 + adiabatic_dual_double_end_width * 2)).abs > tol_testing then unexpected = true end
2297
- end
2298
- if unexpected
2299
- runner.registerWarning('Unexpected values for dual rectangle adiabatic ends bar b.')
2300
- end
2301
- # now that stretched bar is made, determine where to split it and rotate
2302
- adiabatic_bar_a_length = (args['ns_to_ew_ratio'] * (adiabatic_dual_double_end_length + adiabatic_dual_double_end_width)) / (1 + args['ns_to_ew_ratio'])
2303
- adiabatic_bar_b_length = adiabatic_dual_double_end_length - adiabatic_bar_a_length
2304
- adiabatic_area_a = adiabatic_bar_a_length * adiabatic_dual_double_end_width
2305
- adiabatic_area_b = adiabatic_bar_b_length * adiabatic_dual_double_end_width
2306
- else
2307
- # this will throw it stretched single bar
2308
- adiabatic_bar_a_length = 0
2309
- adiabatic_bar_b_length = 0
2310
- end
2311
- if adiabatic_bar_a_length >= min_allow_size && adiabatic_bar_b_length >= min_allow_size
2312
- dual_bar_calc_approach = 'adiabatic_ends_bar_b'
2313
- else
2314
- dual_bar_calc_approach = 'stretched'
2315
- end
2316
- end
2317
-
2318
- # apply prescribed approach for stretched or dual bar
2319
- if dual_bar_calc_approach == 'dual_bar'
2320
- runner.registerInfo("Stretched #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_width, 'm', 'ft').get, 0, true)} ft rectangle has an area of #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * dual_double_end_width, 'm^2', 'ft^2').get, 0, true)} ft^2. When split in two the perimeter will be #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * 2 + dual_double_end_width * 4, 'm', 'ft').get, 0, true)} ft")
2321
- if (target_area - dual_double_end_length * dual_double_end_width).abs > tol_testing || (target_perim - (dual_double_end_length * 2 + dual_double_end_width * 4)).abs > tol_testing
2322
- runner.registerWarning('Unexpected values for dual rectangle.')
2323
- end
2324
-
2325
- runner.registerInfo("For stretched split bar, to match target ns/ew aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be horizontal, with #{OpenStudio.toNeatString(OpenStudio.convert(bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(area_a + area_b, 'm^2', 'ft^2').get, 0, true)} ft^2. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length * 2 + bar_b_length * 2 + dual_double_end_width * 4, 'm', 'ft').get, 0, true)} ft")
2326
- if (target_area - (area_a + area_b)).abs > tol_testing || (target_perim - (bar_a_length * 2 + bar_b_length * 2 + dual_double_end_width * 4)).abs > tol_testing
2327
- runner.registerWarning('Unexpected values for rotated dual rectangle')
2328
- end
2329
- elsif dual_bar_calc_approach == 'adiabatic_ends_bar_b'
2330
- runner.registerInfo("Can't hit target perimeter with two rectangles, need to make two ends adiabatic")
2331
-
2332
- runner.registerInfo("For dual bar with adiabatic ends on bar b, to reach target aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be north/south, with #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_area_a + adiabatic_area_b, 'm^2', 'ft^2').get, 0, true)} ft^2}. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length * 2 + adiabatic_bar_b_length * 2 + adiabatic_dual_double_end_width * 2, 'm', 'ft').get, 0, true)} ft")
2333
- if (target_area - (adiabatic_area_a + adiabatic_area_b)).abs > tol_testing || (target_perim - (adiabatic_bar_a_length * 2 + adiabatic_bar_b_length * 2 + adiabatic_dual_double_end_width * 2)).abs > tol_testing
2334
- runner.registerWarning('Unexpected values for rotated dual rectangle adiabatic ends bar b')
2335
- end
2336
- else # stretched bar
2337
- dual_bar = false
2338
-
2339
- stretched_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
2340
- stretched_width = footprint_si / stretched_length
2341
- if (target_area - stretched_length * stretched_width).abs > tol_testing || (target_perim - (stretched_length + stretched_width) * 2) > tol_testing
2342
- runner.registerWarning('Unexpected values for single stretched')
2343
- end
2344
-
2345
- width = stretched_width
2346
- length = stretched_length
2347
- runner.registerInfo("Creating a dual bar to match the target minimum perimeter multiplier at the given aspect ratio would result in a bar with edge shorter than #{OpenStudio.toNeatString(OpenStudio.convert(min_allow_size, 'm', 'ft').get, 0, true)} ft. Will create a single stretched bar instead that hits the target perimeter with a slightly different ns/ew aspect ratio.")
2348
- end
2349
- end
2350
-
2351
- bars = {}
2352
- bars['primary'] = {}
2353
- if dual_bar
2354
- if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
2355
- bars['primary'][:length] = dual_double_end_width
2356
- bars['primary'][:width] = bar_a_length
2357
- elsif dual_bar_calc_approach == 'dual_bar'
2358
- bars['primary'][:length] = bar_a_length
2359
- bars['primary'][:width] = dual_double_end_width
2360
- elsif mirror_ns_ew
2361
- bars['primary'][:length] = adiabatic_dual_double_end_width
2362
- bars['primary'][:width] = adiabatic_bar_a_length
2363
- else
2364
- bars['primary'][:length] = adiabatic_bar_a_length
2365
- bars['primary'][:width] = adiabatic_dual_double_end_width
2366
- end
2367
- else
2368
- if mirror_ns_ew
2369
- bars['primary'][:length] = width
2370
- bars['primary'][:width] = length
2371
- else
2372
- bars['primary'][:length] = length
2373
- bars['primary'][:width] = width
2374
- end
2375
- end
2376
- bars['primary'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
2377
- bars['primary'][:num_stories] = num_stories
2378
- bars['primary'][:center_of_footprint] = OpenStudio::Point3d.new(0.0, 0.0, 0.0)
2379
- space_types_hash_secondary = {}
2380
- if dual_bar
2381
- # loop through each story and move portion for other bar to its own hash
2382
- primary_footprint = bars['primary'][:length] * bars['primary'][:width]
2383
- secondary_footprint = target_area - primary_footprint
2384
- footprint_counter = primary_footprint
2385
- secondary_footprint_counter = secondary_footprint
2386
- story_counter = 0
2387
- pri_sec_tol = 0.0001 # m^2
2388
- pri_sec_min_area = 0.0001 # m^2
2389
- space_types_hash.each do |k, v|
2390
- space_type_left = v[:floor_area]
2391
-
2392
- # do not go to next space type until this one is evaulate, which may span stories
2393
- until space_type_left == 0.0 || story_counter >= num_stories
2394
-
2395
- # use secondary footprint if any left
2396
- if secondary_footprint_counter > 0.0
2397
- hash_area = [space_type_left, secondary_footprint_counter].min
2398
-
2399
- # confirm that the part of space type use or what is left is greater than min allowed value
2400
- projected_space_type_left = space_type_left - hash_area
2401
- test_a = hash_area >= pri_sec_min_area
2402
- test_b = projected_space_type_left >= pri_sec_min_area || projected_space_type_left == 0.0 ? true : false
2403
- test_c = k == space_types_hash.keys.last # if last space type accept sliver, no other space to infil
2404
- if (test_a && test_b) || test_c
2405
- if space_types_hash_secondary.key?(k)
2406
- # add to what was added for previous story
2407
- space_types_hash_secondary[k][:floor_area] += hash_area
2408
- else
2409
- # add new space type to hash
2410
- if v.key?(:children)
2411
- space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type], children: v[:children] }
2412
- else
2413
- space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type] }
2414
- end
2415
- end
2416
- space_types_hash[k][:floor_area] -= hash_area
2417
- secondary_footprint_counter -= hash_area
2418
- space_type_left -= hash_area
2419
- else
2420
- runner.registerInfo("Shifting space types between bars to avoid sliver of #{k.name}.")
2421
- end
2422
- end
2423
-
2424
- # remove space if entirely used up by secondary bar
2425
- if space_types_hash[k][:floor_area] <= pri_sec_tol
2426
- space_types_hash.delete(k)
2427
- space_type_left = 0.0
2428
- else
2429
- # then look at primary bar
2430
- hash_area_pri = [space_type_left, footprint_counter].min
2431
- footprint_counter -= hash_area_pri
2432
- space_type_left -= hash_area_pri
2433
- end
2434
-
2435
- # reset counter when full
2436
- if footprint_counter <= pri_sec_tol && secondary_footprint_counter <= pri_sec_tol
2437
- # check if this is partial top floor
2438
- story_counter += 1
2439
- if num_stories < story_counter + 1
2440
- footprint_counter = primary_footprint * (num_stories - story_counter)
2441
- secondary_footprint_counter = secondary_footprint * (num_stories - story_counter)
2442
- else
2443
- footprint_counter = primary_footprint
2444
- secondary_footprint_counter = secondary_footprint
2445
- end
2446
- end
2447
- end
2448
- end
2449
- end
2450
-
2451
- # setup bar_hash and run create_bar
2452
- bars['primary'][:space_types_hash] = space_types_hash
2453
- bars['primary'][:args] = args
2454
- v = bars['primary']
2455
- bar_hash_setup_run(runner, model, v[:args], v[:length], v[:width], v[:floor_height_si], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
2456
-
2457
- # store offset value for multiple bars
2458
- if args.key?('bar_sep_dist_mult') && args['bar_sep_dist_mult'] > 0.0
2459
- offset_val = num_stories.ceil * floor_height_si * args['bar_sep_dist_mult']
2460
- elsif args.key?('bar_sep_dist_mult')
2461
- runner.registerWarning('Positive value is required for bar_sep_dist_mult, ignoring input and using value of 0.1')
2462
- offset_val = num_stories.ceil * floor_height_si * 0.1
2463
- else
2464
- offset_val = num_stories.ceil * floor_height_si * 10.0
2465
- end
2466
-
2467
- if dual_bar
2468
- args2 = args.clone
2469
- bars['secondary'] = {}
2470
- if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
2471
- bars['secondary'][:length] = bar_b_length
2472
- bars['secondary'][:width] = dual_double_end_width
2473
- elsif dual_bar_calc_approach == 'dual_bar'
2474
- bars['secondary'][:length] = dual_double_end_width
2475
- bars['secondary'][:width] = bar_b_length
2476
- elsif mirror_ns_ew
2477
- bars['secondary'][:length] = adiabatic_bar_b_length
2478
- bars['secondary'][:width] = adiabatic_dual_double_end_width
2479
- args2['party_wall_stories_east'] = num_stories.ceil
2480
- args2['party_wall_stories_west'] = num_stories.ceil
2481
- else
2482
- bars['secondary'][:length] = adiabatic_dual_double_end_width
2483
- bars['secondary'][:width] = adiabatic_bar_b_length
2484
- args2['party_wall_stories_south'] = num_stories.ceil
2485
- args2['party_wall_stories_north'] = num_stories.ceil
2486
- end
2487
- bars['secondary'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
2488
- bars['secondary'][:num_stories] = num_stories
2489
- bars['secondary'][:space_types_hash] = space_types_hash_secondary
2490
- if dual_bar_calc_approach == 'adiabatic_ends_bar_b'
2491
- # warn that combination of dual bar with low perimeter multiplier and use of party wall may result in discrepency between target and actual adiabatic walls
2492
- if args['party_wall_fraction'] > 0 || args['party_wall_stories_north'] > 0 || args['party_wall_stories_south'] > 0 || args['party_wall_stories_east'] > 0 || args['party_wall_stories_west'] > 0
2493
- runner.registerWarning('The combination of low perimeter multiplier and use of non zero party wall inputs may result in discrepency between target and actual adiabatic walls. This is due to the need to create adiabatic walls on secondary bar to maintian target building perimeter.')
2494
- else
2495
- runner.registerInfo('Adiabatic ends added to secondary bar because target perimeter multiplier could not be met with two full rectangular footprints.')
2496
- end
2497
- bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(adiabatic_bar_a_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val, adiabatic_bar_b_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val, 0.0)
2498
- else
2499
- bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(bar_a_length * 0.5 + dual_double_end_width * 0.5 + offset_val, bar_b_length * 0.5 + dual_double_end_width * 0.5 + offset_val, 0.0)
2500
- end
2501
- bars['secondary'][:args] = args2
2502
-
2503
- # setup bar_hash and run create_bar
2504
- v = bars['secondary']
2505
- bar_hash_setup_run(runner, model, v[:args], v[:length], v[:width], v[:floor_height_si], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
2506
-
2507
- end
2508
-
2509
- # future development (up against primary bar run intersection and surface matching after add all bars, avoid interior windows)
2510
- # I could loop through each space type and give them unique height but for now will just take largest height and make bar of that height, which is fine for prototypes
2511
- if !multi_height_space_types_hash.empty?
2512
- args3 = args.clone
2513
- bars['custom_height'] = {}
2514
- if mirror_ns_ew
2515
- bars['custom_height'][:length] = width_cust_height
2516
- bars['custom_height'][:width] = length_cust_height
2517
- else
2518
- bars['custom_height'][:length] = length_cust_height
2519
- bars['custom_height'][:width] = width_cust_height
2520
- end
2521
- if args['party_wall_stories_east'] + args['party_wall_stories_west'] + args['party_wall_stories_south'] + args['party_wall_stories_north'] > 0.0
2522
- runner.registerWarning('Ignorning party wall inputs for custom height bar')
2523
- end
2524
-
2525
- # disable party walls
2526
- args3['party_wall_stories_east'] = 0
2527
- args3['party_wall_stories_west'] = 0
2528
- args3['party_wall_stories_south'] = 0
2529
- args3['party_wall_stories_north'] = 0
2530
-
2531
- # setup stories
2532
- args3['num_stories_below_grade'] = 0
2533
- args3['num_stories_above_grade'] = 1
2534
-
2535
- bars['custom_height'][:floor_height_si] = floor_height_si # can make use of this when breaking out multi-height spaces
2536
- bars['custom_height'][:num_stories] = num_stories
2537
- bars['custom_height'][:center_of_footprint] = OpenStudio::Point3d.new(bars['primary'][:length] * -0.5 - length_cust_height * 0.5 - offset_val, 0.0, 0.0)
2538
- bars['custom_height'][:floor_height_si] = OpenStudio.convert(custom_story_heights.max, 'ft', 'm').get
2539
- bars['custom_height'][:num_stories] = 1
2540
- bars['custom_height'][:space_types_hash] = multi_height_space_types_hash
2541
- bars['custom_height'][:args] = args3
2542
-
2543
- v = bars['custom_height']
2544
- bar_hash_setup_run(runner, model, v[:args], v[:length], v[:width], v[:floor_height_si], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
2545
- end
2546
-
2547
- # diagnostic log
2548
- sum_actual = 0.0
2549
- sum_target = 0.0
2550
- throw_error = false
2551
-
2552
- # check expected floor areas against actual
2553
- model.getSpaceTypes.sort.each do |space_type|
2554
- next if !target_areas.key? space_type # space type in model not part of building type(s), maybe issue warning
2555
-
2556
- # convert to IP
2557
- actual_ip = OpenStudio.convert(space_type.floorArea, 'm^2', 'ft^2').get
2558
- target_ip = OpenStudio.convert(target_areas[space_type], 'm^2', 'ft^2').get
2559
- sum_actual += actual_ip
2560
- sum_target += target_ip
2561
-
2562
- if (space_type.floorArea - target_areas[space_type]).abs >= 1.0
2563
-
2564
- if !args['bar_division_method'].include? 'Single Space Type'
2565
- runner.registerError("#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
2566
- throw_error = true
2567
- else
2568
- # will see this if use Single Space type division method on multi-use building or single building type without whole building space type
2569
- runner.registerWarning("#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
2570
- end
2571
-
2572
- end
2573
- end
2574
-
2575
- # report summary then throw error
2576
- if throw_error
2577
- runner.registerError("Sum of actual floor area is #{sum_actual} ft^2, sum of target floor area is #{sum_target}.")
2578
- return false
2579
- end
2580
-
2581
- # check party wall fraction by looping through surfaces
2582
- if args['party_wall_fraction'] > 0
2583
- actual_ext_wall_area = model.getBuilding.exteriorWallArea
2584
- actual_party_wall_area = 0.0
2585
- model.getSurfaces.sort.each do |surface|
2586
- next if surface.outsideBoundaryCondition != 'Adiabatic'
2587
- next if surface.surfaceType != 'Wall'
2588
- actual_party_wall_area += surface.grossArea * surface.space.get.multiplier
2589
- end
2590
- actual_party_wall_fraction = actual_party_wall_area / (actual_party_wall_area + actual_ext_wall_area)
2591
- runner.registerInfo("Target party wall fraction is #{args['party_wall_fraction']}. Realized fraction is #{actual_party_wall_fraction.round(2)}")
2592
- runner.registerValue('party_wall_fraction_actual', actual_party_wall_fraction)
2593
- end
2594
-
2595
- # check ns/ew aspect ratio (harder to check when party walls are added)
2596
- wall_and_window_by_orientation = OsLib_Geometry.getExteriorWindowAndWllAreaByOrientation(model, model.getSpaces)
2597
- wall_ns = (wall_and_window_by_orientation['northWall'] + wall_and_window_by_orientation['southWall'])
2598
- wall_ew = wall_and_window_by_orientation['eastWall'] + wall_and_window_by_orientation['westWall']
2599
- wall_ns_ip = OpenStudio.convert(wall_ns, 'm^2', 'ft^2').get
2600
- wall_ew_ip = OpenStudio.convert(wall_ew, 'm^2', 'ft^2').get
2601
- runner.registerValue('wall_area_ip', wall_ns_ip + wall_ew_ip, 'ft^2')
2602
- runner.registerValue('ns_wall_area_ip', wall_ns_ip, 'ft^2')
2603
- runner.registerValue('ew_wall_area_ip', wall_ew_ip, 'ft^2')
2604
- # for now using perimeter of ground floor and average story area (building area / num_stories)
2605
- runner.registerValue('floor_area_to_perim_ratio', model.getBuilding.floorArea / (OsLib_Geometry.calculate_perimeter(model) * num_stories))
2606
- runner.registerValue('bar_width_output', OpenStudio.convert(bars['primary'][:width], 'm', 'ft').get, 'ft')
2607
-
2608
- if args['party_wall_fraction'] > 0 || args['party_wall_stories_north'] > 0 || args['party_wall_stories_south'] > 0 || args['party_wall_stories_east'] > 0 || args['party_wall_stories_west'] > 0
2609
- runner.registerInfo('Target facade area by orientation not validated when party walls are applied')
2610
- elsif args['num_stories_above_grade'] != args['num_stories_above_grade'].ceil
2611
- runner.registerInfo('Target facade area by orientation not validated when partial top story is used')
2612
- elsif dual_bar_calc_approach == 'stretched'
2613
- runner.registerInfo('Target facade area by orientation not validated when single stretched bar has to be used to meet target minimum perimeter multiplier')
2614
- elsif defaulted_args.include?('floor_height') && args['custom_height_bar'] && !multi_height_space_types_hash.empty?
2615
- runner.registerInfo('Target facade area by orientation not validated when a dedicated bar is added for space types with custom heights')
2616
- elsif args['bar_width'] > 0
2617
- runner.registerInfo('Target facade area by orientation not validated when a dedicated custom bar width is defined')
2618
- else
2619
-
2620
- # adjust length versus width based on building rotation
2621
- if mirror_ns_ew
2622
- wall_target_ns_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
2623
- wall_target_ew_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
2624
- else
2625
- wall_target_ns_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
2626
- wall_target_ew_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get * args['perim_mult'] * args['num_stories_above_grade'] * args['floor_height']
2627
- end
2628
- flag_error = false
2629
- if (wall_target_ns_ip - wall_ns_ip).abs > 0.1
2630
- runner.registerError("North/South walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ns_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ns_ip, 4, true)} ft^2)")
2631
- flag_error = true
2632
- end
2633
- if (wall_target_ew_ip - wall_ew_ip).abs > 0.1
2634
- runner.registerError("East/West walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ew_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ew_ip, 4, true)} ft^2)")
2635
- flag_error = true
2636
- end
2637
- if flag_error
2638
- return false
2639
- end
2640
- end
2641
-
2642
- # test for excessive exterior roof area (indication of problem with intersection and or surface matching)
2643
- ext_roof_area = model.getBuilding.exteriorSurfaceArea - model.getBuilding.exteriorWallArea
2644
- expected_roof_area = args['total_bldg_floor_area'] / (args['num_stories_above_grade'] + args['num_stories_below_grade']).to_f
2645
- if ext_roof_area > expected_roof_area && single_floor_area_si == 0.0 # only test if using whole-building area input
2646
- runner.registerError('Roof area larger than expected, may indicate problem with inter-floor surface intersection or matching.')
2647
- return false
2648
- end
2649
-
2650
- # set building rotation
2651
- initial_rotation = model.getBuilding.northAxis
2652
- if args['building_rotation'] != initial_rotation
2653
- model.getBuilding.setNorthAxis(args['building_rotation'])
2654
- runner.registerInfo("Set Building Rotation to #{model.getBuilding.northAxis}. Rotation altered after geometry generation is completed, as a result party wall orientation and aspect ratio may not reflect input values.")
2655
- end
2656
-
2657
- # report final condition of model
2658
- runner.registerFinalCondition("The building finished with #{model.getSpaces.size} spaces.")
2659
-
2660
- return true
2661
- end
2662
-
2663
- # typical
2664
- # used for varieties of measures that create typical building from model
2665
- def typical_building_from_model(model, runner, user_arguments)
2666
- # assign the user inputs to variables
2667
- args = OsLib_HelperMethods.createRunVariables(runner, model, user_arguments, arguments(model))
2668
- if !args then return false end
2669
-
2670
- # lookup and replace argument values from upstream measures
2671
- if args['use_upstream_args'] == true
2672
- args.each do |arg, value|
2673
- next if arg == 'use_upstream_args' # this argument should not be changed
2674
- value_from_osw = OsLib_HelperMethods.check_upstream_measure_for_arg(runner, arg)
2675
- if !value_from_osw.empty?
2676
- runner.registerInfo("Replacing argument named #{arg} from current measure with a value of #{value_from_osw[:value]} from #{value_from_osw[:measure_name]}.")
2677
- new_val = value_from_osw[:value]
2678
- # TODO: - make code to handle non strings more robust. check_upstream_measure_for_arg coudl pass bakc the argument type
2679
- if arg == 'total_bldg_floor_area'
2680
- args[arg] = new_val.to_f
2681
- elsif arg == 'num_stories_above_grade'
2682
- args[arg] = new_val.to_f
2683
- elsif arg == 'zipcode'
2684
- args[arg] = new_val.to_i
2685
- else
2686
- args[arg] = new_val
2687
- end
2688
- end
2689
- end
2690
- end
2691
-
2692
- # validate fraction parking
2693
- fraction = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 1.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => ['onsite_parking_fraction'])
2694
- if !fraction then return false end
2695
-
2696
- # validate unmet hours tolerance
2697
- unmet_hours_tolerance_valid = OsLib_HelperMethods.checkDoubleAndIntegerArguments(runner, user_arguments, 'min' => 0.0, 'max' => 5.0, 'min_eq_bool' => true, 'max_eq_bool' => true, 'arg_array' => ['unmet_hours_tolerance'])
2698
- if !unmet_hours_tolerance_valid then return false end
2699
-
2700
- # validate weekday hours of operation
2701
- wkdy_op_hrs_start_time_hr = nil
2702
- wkdy_op_hrs_start_time_min = nil
2703
- wkdy_op_hrs_duration_hr = nil
2704
- wkdy_op_hrs_duration_min = nil
2705
- if args['modify_wkdy_op_hrs']
2706
- # weekday start time hr
2707
- wkdy_op_hrs_start_time_hr = args['wkdy_op_hrs_start_time'].floor
2708
- if wkdy_op_hrs_start_time_hr < 0 || wkdy_op_hrs_start_time_hr > 24
2709
- runner.registerError("Weekday operating hours start time hrs must be between 0 and 24. #{args['wkdy_op_hrs_start_time']} was entered.")
2710
- return false
2711
- end
2712
-
2713
- # weekday start time min
2714
- wkdy_op_hrs_start_time_min = (60.0 * (args['wkdy_op_hrs_start_time'] - args['wkdy_op_hrs_start_time'].floor)).floor
2715
- if wkdy_op_hrs_start_time_min < 0 || wkdy_op_hrs_start_time_min > 59
2716
- runner.registerError("Weekday operating hours start time mins must be between 0 and 59. #{args['wkdy_op_hrs_start_time']} was entered.")
2717
- return false
2718
- end
2719
-
2720
- # weekday duration hr
2721
- wkdy_op_hrs_duration_hr = args['wkdy_op_hrs_duration'].floor
2722
- if wkdy_op_hrs_duration_hr < 0 || wkdy_op_hrs_duration_hr > 24
2723
- runner.registerError("Weekday operating hours duration hrs must be between 0 and 24. #{args['wkdy_op_hrs_duration']} was entered.")
2724
- return false
2725
- end
2726
-
2727
- # weekday duration min
2728
- wkdy_op_hrs_duration_min = (60.0 * (args['wkdy_op_hrs_duration'] - args['wkdy_op_hrs_duration'].floor)).floor
2729
- if wkdy_op_hrs_duration_min < 0 || wkdy_op_hrs_duration_min > 59
2730
- runner.registerError("Weekday operating hours duration mins must be between 0 and 59. #{args['wkdy_op_hrs_duration']} was entered.")
2731
- return false
2732
- end
2733
-
2734
- # check that weekday start time plus duration does not exceed 24 hrs
2735
- if (wkdy_op_hrs_start_time_hr + wkdy_op_hrs_duration_hr + (wkdy_op_hrs_start_time_min + wkdy_op_hrs_duration_min) / 60.0) > 24.0
2736
- runner.registerInfo("Weekday start time of #{args['wkdy_op_hrs_start']} plus duration of #{args['wkdy_op_hrs_duration']} is more than 24 hrs, hours of operation overlap midnight.")
2737
- end
2738
- end
2739
-
2740
- # validate weekend hours of operation
2741
- wknd_op_hrs_start_time_hr = nil
2742
- wknd_op_hrs_start_time_min = nil
2743
- wknd_op_hrs_duration_hr = nil
2744
- wknd_op_hrs_duration_min = nil
2745
- if args['modify_wknd_op_hrs']
2746
- # weekend start time hr
2747
- wknd_op_hrs_start_time_hr = args['wknd_op_hrs_start_time'].floor
2748
- if wknd_op_hrs_start_time_hr < 0 || wknd_op_hrs_start_time_hr > 24
2749
- runner.registerError("Weekend operating hours start time hrs must be between 0 and 24. #{args['wknd_op_hrs_start_time_change']} was entered.")
2750
- return false
2751
- end
2752
-
2753
- # weekend start time min
2754
- wknd_op_hrs_start_time_min = (60.0 * (args['wknd_op_hrs_start_time'] - args['wknd_op_hrs_start_time'].floor)).floor
2755
- if wknd_op_hrs_start_time_min < 0 || wknd_op_hrs_start_time_min > 59
2756
- runner.registerError("Weekend operating hours start time mins must be between 0 and 59. #{args['wknd_op_hrs_start_time_change']} was entered.")
2757
- return false
2758
- end
2759
-
2760
- # weekend duration hr
2761
- wknd_op_hrs_duration_hr = args['wknd_op_hrs_duration'].floor
2762
- if wknd_op_hrs_duration_hr < 0 || wknd_op_hrs_duration_hr > 24
2763
- runner.registerError("Weekend operating hours duration hrs must be between 0 and 24. #{args['wknd_op_hrs_duration']} was entered.")
2764
- return false
2765
- end
2766
-
2767
- # weekend duration min
2768
- wknd_op_hrs_duration_min = (60.0 * (args['wknd_op_hrs_duration'] - args['wknd_op_hrs_duration'].floor)).floor
2769
- if wknd_op_hrs_duration_min < 0 || wknd_op_hrs_duration_min > 59
2770
- runner.registerError("Weekend operating hours duration min smust be between 0 and 59. #{args['wknd_op_hrs_duration']} was entered.")
2771
- return false
2772
- end
2773
-
2774
- # check that weekend start time plus duration does not exceed 24 hrs
2775
- if (wknd_op_hrs_start_time_hr + wknd_op_hrs_duration_hr + (wknd_op_hrs_start_time_min + wknd_op_hrs_duration_min) / 60.0) > 24.0
2776
- runner.registerInfo("Weekend start time of #{args['wknd_op_hrs_start']} plus duration of #{args['wknd_op_hrs_duration']} is more than 24 hrs, hours of operation overlap midnight.")
2777
- end
2778
- end
2779
-
2780
- # report initial condition of model
2781
- initial_objects = model.getModelObjects.size
2782
- runner.registerInitialCondition("The building started with #{initial_objects} objects.")
2783
-
2784
- # open channel to log messages
2785
- reset_log
2786
-
2787
- # Make the standard applier
2788
- standard = Standard.build((args['template']).to_s)
2789
-
2790
- # validate climate zone
2791
- if !args.key?('climate_zone') || args['climate_zone'] == 'Lookup From Model'
2792
- climate_zone = standard.model_get_building_properties(model)['climate_zone']
2793
- runner.registerInfo("Using climate zone #{climate_zone} from model")
2794
- else
2795
- climate_zone = args['climate_zone']
2796
- runner.registerInfo("Using climate zone #{climate_zone} from user arguments")
2797
- end
2798
- if climate_zone == ''
2799
- runner.registerError("Could not determine climate zone from measure arguments or model.")
2800
- return false
2801
- end
2802
-
2803
- # if haystack_file used find the file
2804
- # todo - may want to allow NA, empty string or some other value to skip so a measure can ake this optional witht using optional measure arguments
2805
- if args['haystack_file']
2806
- haystack_file = runner.workflow.findFile(args['haystack_file'])
2807
- if haystack_file.is_initialized
2808
- haystack_file = haystack_file.get.to_s
2809
-
2810
- # load JSON file
2811
- json = nil
2812
- File.open(haystack_file, 'r') do |file|
2813
- json = file.read
2814
- # uncomment to inspect haystack json
2815
- # puts json
2816
- end
2817
-
2818
- else
2819
- runner.registerError("Did not find #{args['haystack_file']} in paths described in OSW file.")
2820
- return false
2821
- end
2822
- else
2823
- haystack_file = nil
2824
- end
2825
-
2826
- # make sure daylight savings is turned on up prior to any sizing runs being done.
2827
- if args['enable_dst']
2828
- start_date = '2nd Sunday in March'
2829
- end_date = '1st Sunday in November'
2830
-
2831
- runperiodctrl_daylgtsaving = model.getRunPeriodControlDaylightSavingTime
2832
- runperiodctrl_daylgtsaving.setStartDate(start_date)
2833
- runperiodctrl_daylgtsaving.setEndDate(end_date)
2834
- end
2835
-
2836
- # add internal loads to space types
2837
- if args['add_space_type_loads']
2838
-
2839
- # remove internal loads
2840
- if args['remove_objects']
2841
- model.getSpaceLoads.sort.each do |instance|
2842
- next if instance.name.to_s.include?('Elevator') # most prototype building types model exterior elevators with name Elevator
2843
- next if instance.to_InternalMass.is_initialized
2844
- next if instance.to_WaterUseEquipment.is_initialized
2845
- instance.remove
2846
- end
2847
- model.getDesignSpecificationOutdoorAirs.each(&:remove)
2848
- model.getDefaultScheduleSets.each(&:remove)
2849
- end
2850
-
2851
- model.getSpaceTypes.sort.each do |space_type|
2852
- # Don't add infiltration here; will be added later in the script
2853
- test = standard.space_type_apply_internal_loads(space_type, true, true, true, true, true, false)
2854
- if test == false
2855
- runner.registerWarning("Could not add loads for #{space_type.name}. Not expected for #{args['template']}")
2856
- next
2857
- end
2858
-
2859
- # apply internal load schedules
2860
- # the last bool test it to make thermostat schedules. They are now added in HVAC section instead of here
2861
- standard.space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, false)
2862
-
2863
- # extend space type name to include the args['template']. Consider this as well for load defs
2864
- space_type.setName("#{space_type.name} - #{args['template']}")
2865
- runner.registerInfo("Adding loads to space type named #{space_type.name}")
2866
- end
2867
-
2868
- # warn if spaces in model without space type
2869
- spaces_without_space_types = []
2870
- model.getSpaces.sort.each do |space|
2871
- next if space.spaceType.is_initialized
2872
- spaces_without_space_types << space
2873
- end
2874
- if !spaces_without_space_types.empty?
2875
- runner.registerWarning("#{spaces_without_space_types.size} spaces do not have space types assigned, and wont' receive internal loads from standards space type lookups.")
2876
- end
2877
- end
2878
-
2879
- # identify primary building type (used for construction, and ideally HVAC as well)
2880
- building_types = {}
2881
- model.getSpaceTypes.sort.each do |space_type|
2882
- # populate hash of building types
2883
- if space_type.standardsBuildingType.is_initialized
2884
- bldg_type = space_type.standardsBuildingType.get
2885
- if !building_types.key?(bldg_type)
2886
- building_types[bldg_type] = space_type.floorArea
2887
- else
2888
- building_types[bldg_type] += space_type.floorArea
2889
- end
2890
- else
2891
- runner.registerWarning("Can't identify building type for #{space_type.name}")
2892
- end
2893
- end
2894
- primary_bldg_type = building_types.key(building_types.values.max) # TODO: - this fails if no space types, or maybe just no space types with standards
2895
- lookup_building_type = standard.model_get_lookup_name(primary_bldg_type) # Used for some lookups in the standards gem
2896
- model.getBuilding.setStandardsBuildingType(primary_bldg_type)
2897
-
2898
- # make construction set and apply to building
2899
- if args['add_constructions']
2900
-
2901
- # remove default construction sets
2902
- if args['remove_objects']
2903
- model.getDefaultConstructionSets.each(&:remove)
2904
- end
2905
-
2906
- # TODO: - allow building type and space type specific constructions set selection.
2907
- if ['SmallHotel', 'LargeHotel', 'MidriseApartment', 'HighriseApartment'].include?(primary_bldg_type)
2908
- is_residential = 'Yes'
2909
- occ_type = 'Residential'
2910
- else
2911
- is_residential = 'No'
2912
- occ_type = 'Nonresidential'
2913
- end
2914
- bldg_def_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, is_residential)
2915
- if bldg_def_const_set.is_initialized
2916
- bldg_def_const_set = bldg_def_const_set.get
2917
- if is_residential then bldg_def_const_set.setName("Res #{bldg_def_const_set.name}") end
2918
- model.getBuilding.setDefaultConstructionSet(bldg_def_const_set)
2919
- runner.registerInfo("Adding default construction set named #{bldg_def_const_set.name}")
2920
- else
2921
- runner.registerError("Could not create default construction set for the building type #{lookup_building_type} in climate zone #{climate_zone}.")
2922
- log_messages_to_runner(runner, debug = true)
2923
- return false
2924
- end
2925
-
2926
- # Replace the construction of any outdoor-facing "AtticFloor" surfaces
2927
- # with the "ExteriorRoof" - "IEAD" construction for the specific climate zone and template.
2928
- # This prevents creation of buildings where the DOE Prototype building construction set
2929
- # assumes an attic but the supplied geometry used does not have an attic.
2930
- new_construction = nil
2931
- climate_zone_set = standard.model_find_climate_zone_set(model, climate_zone)
2932
- model.getSurfaces.sort.each do |surf|
2933
- next unless surf.outsideBoundaryCondition == 'Outdoors'
2934
- next unless surf.surfaceType == 'RoofCeiling'
2935
- next if surf.construction.empty?
2936
- construction = surf.construction.get
2937
- standards_info = construction.standardsInformation
2938
- next if standards_info.intendedSurfaceType.empty?
2939
- next unless standards_info.intendedSurfaceType.get == 'AtticFloor'
2940
- if new_construction.nil?
2941
- new_construction = standard.model_find_and_add_construction(model,
2942
- climate_zone_set,
2943
- 'ExteriorRoof',
2944
- 'IEAD',
2945
- occ_type)
2946
- end
2947
- surf.setConstruction(new_construction)
2948
- runner.registerInfo("Changed the construction for #{surf.name} from #{construction.name} to #{new_construction.name} to avoid outdoor-facing attic floor constructions in buildings with no attic space.")
2949
- end
2950
-
2951
- # address any adiabatic surfaces that don't have hard assigned constructions
2952
- model.getSurfaces.sort.each do |surface|
2953
- next if surface.outsideBoundaryCondition != 'Adiabatic'
2954
- next if surface.construction.is_initialized
2955
- surface.setAdjacentSurface(surface)
2956
- surface.setConstruction(surface.construction.get)
2957
- surface.setOutsideBoundaryCondition('Adiabatic')
2958
- end
2959
-
2960
- # modify the infiltration rates
2961
- if args['remove_objects']
2962
- model.getSpaceInfiltrationDesignFlowRates.each(&:remove)
2963
- end
2964
- standard.model_apply_infiltration_standard(model)
2965
- standard.model_modify_infiltration_coefficients(model, primary_bldg_type, climate_zone)
2966
-
2967
- # set ground temperatures from DOE prototype buildings
2968
- standard.model_add_ground_temperatures(model, primary_bldg_type, climate_zone)
2969
-
2970
- end
2971
-
2972
- # add elevators (returns ElectricEquipment object)
2973
- if args['add_elevators']
2974
-
2975
- # remove elevators as spaceLoads or exteriorLights
2976
- model.getSpaceLoads.sort.each do |instance|
2977
- next if !instance.name.to_s.include?('Elevator') # most prototype building types model exterior elevators with name Elevator
2978
- instance.remove
2979
- end
2980
- model.getExteriorLightss.sort.each do |ext_light|
2981
- next if !ext_light.name.to_s.include?('Fuel equipment') # some prototype building types model exterior elevators by this name
2982
- ext_light.remove
2983
- end
2984
-
2985
- elevators = standard.model_add_elevators(model)
2986
- if elevators.nil?
2987
- runner.registerInfo('No elevators added to the building.')
2988
- else
2989
- elevator_def = elevators.electricEquipmentDefinition
2990
- design_level = elevator_def.designLevel.get
2991
- runner.registerInfo("Adding #{elevators.multiplier.round(1)} elevators each with power of #{OpenStudio.toNeatString(design_level, 0, true)} (W), plus lights and fans.")
2992
- elevator_def.setFractionLatent(0.0)
2993
- elevator_def.setFractionRadiant(0.0)
2994
- elevator_def.setFractionLost(1.0)
2995
- end
2996
- end
2997
-
2998
- # add exterior lights (returns a hash where key is lighting type and value is exteriorLights object)
2999
- if args['add_exterior_lights']
3000
-
3001
- if args['remove_objects']
3002
- model.getExteriorLightss.sort.each do |ext_light|
3003
- next if ext_light.name.to_s.include?('Fuel equipment') # some prototype building types model exterior elevators by this name
3004
- ext_light.remove
3005
- end
3006
- end
3007
-
3008
- exterior_lights = standard.model_add_typical_exterior_lights(model, args['exterior_lighting_zone'].chars[0].to_i, args['onsite_parking_fraction'])
3009
- exterior_lights.each do |k, v|
3010
- runner.registerInfo("Adding Exterior Lights named #{v.exteriorLightsDefinition.name} with design level of #{v.exteriorLightsDefinition.designLevel} * #{OpenStudio.toNeatString(v.multiplier, 0, true)}.")
3011
- end
3012
- end
3013
-
3014
- # add_exhaust
3015
- if args['add_exhaust']
3016
-
3017
- # remove exhaust objects
3018
- if args['remove_objects']
3019
- model.getFanZoneExhausts.each(&:remove)
3020
- end
3021
-
3022
- zone_exhaust_fans = standard.model_add_exhaust(model, args['kitchen_makeup']) # second argument is strategy for finding makeup zones for exhaust zones
3023
- zone_exhaust_fans.each do |k, v|
3024
- max_flow_rate_ip = OpenStudio.convert(k.maximumFlowRate.get, 'm^3/s', 'cfm').get
3025
- if v.key?(:zone_mixing)
3026
- zone_mixing = v[:zone_mixing]
3027
- mixing_source_zone_name = zone_mixing.sourceZone.get.name
3028
- mixing_design_flow_rate_ip = OpenStudio.convert(zone_mixing.designFlowRate.get, 'm^3/s', 'cfm').get
3029
- runner.registerInfo("Adding #{OpenStudio.toNeatString(max_flow_rate_ip, 0, true)} (cfm) of exhaust to #{k.thermalZone.get.name}, with #{OpenStudio.toNeatString(mixing_design_flow_rate_ip, 0, true)} (cfm) of makeup air from #{mixing_source_zone_name}")
3030
- else
3031
- runner.registerInfo("Adding #{OpenStudio.toNeatString(max_flow_rate_ip, 0, true)} (cfm) of exhaust to #{k.thermalZone.get.name}")
3032
- end
3033
- end
3034
- end
3035
-
3036
- # add service water heating demand and supply
3037
- if args['add_swh']
3038
-
3039
- # remove water use equipment and water use connections
3040
- if args['remove_objects']
3041
- # TODO: - remove plant loops used for service water heating
3042
- model.getWaterUseEquipments.each(&:remove)
3043
- model.getWaterUseConnectionss.each(&:remove)
3044
- end
3045
-
3046
- # Infer the SWH type
3047
- if args['swh_src'] == 'Inferred'
3048
- if args['htg_src'] == 'NaturalGas' || args['htg_src'] == 'DistrictHeating'
3049
- args['swh_src'] = 'NaturalGas' # If building has gas service, probably uses natural gas for SWH
3050
- elsif args['htg_src'] == 'Electricity'
3051
- args['swh_src'] = 'Electricity' # If building is doing space heating with electricity, probably used for SWH
3052
- elsif args['htg_src'] == 'DistrictAmbient'
3053
- args['swh_src'] = 'HeatPump' # If building has district ambient loop, it is fancy and probably uses HPs for SWH
3054
- else
3055
- args['swh_src'] = nil # Use inferences built into OpenStudio Standards for each building and space type
3056
- end
3057
- end
3058
-
3059
- typical_swh = standard.model_add_typical_swh(model, water_heater_fuel: args['swh_src'])
3060
- midrise_swh_loops = []
3061
- stripmall_swh_loops = []
3062
- typical_swh.each do |loop|
3063
- if loop.name.get.include?('MidriseApartment')
3064
- midrise_swh_loops << loop
3065
- elsif loop.name.get.include?('RetailStripmall')
3066
- stripmall_swh_loops << loop
3067
- else
3068
- water_use_connections = []
3069
- loop.demandComponents.each do |component|
3070
- next if !component.to_WaterUseConnections.is_initialized
3071
- water_use_connections << component
3072
- end
3073
- runner.registerInfo("Adding #{loop.name} to the building. It has #{water_use_connections.size} water use connections.")
3074
- end
3075
- end
3076
- if !midrise_swh_loops.empty?
3077
- runner.registerInfo("Adding #{midrise_swh_loops.size} MidriseApartment service water heating loops.")
3078
- end
3079
- if !stripmall_swh_loops.empty?
3080
- runner.registerInfo("Adding #{stripmall_swh_loops.size} RetailStripmall service water heating loops.")
3081
- end
3082
- end
3083
-
3084
- # add_daylighting_controls (since outdated measure don't have this default to true if arg not found)
3085
- if !args.has_key?('add_daylighting_controls')
3086
- args['add_daylighting_controls'] = true
3087
- end
3088
- if args['add_daylighting_controls']
3089
- # remove add_daylighting_controls objects
3090
- if args['remove_objects']
3091
- model.getDaylightingControls.each(&:remove)
3092
- end
3093
-
3094
- # add daylight controls, need to perform a sizing run for 2010
3095
- if args['template'] == '90.1-2010' || args['template'] == 'ComStock 90.1-2010'
3096
- if standard.model_run_sizing_run(model, "#{Dir.pwd}/SRvt") == false
3097
- log_messages_to_runner(runner, debug = true)
3098
- return false
3099
- end
3100
- end
3101
- standard.model_add_daylighting_controls(model)
3102
- end
3103
-
3104
- # add refrigeration
3105
- if args['add_refrigeration']
3106
-
3107
- # remove refrigeration equipment
3108
- if args['remove_objects']
3109
- model.getRefrigerationSystems.each(&:remove)
3110
- end
3111
-
3112
- # Add refrigerated cases and walkins
3113
- standard.model_add_typical_refrigeration(model, primary_bldg_type)
3114
- end
3115
-
3116
- # add internal mass
3117
- if args['add_internal_mass']
3118
-
3119
- if args['remove_objects']
3120
- model.getSpaceLoads.sort.each do |instance|
3121
- next unless instance.to_InternalMass.is_initialized
3122
- instance.remove
3123
- end
3124
- end
3125
-
3126
- # add internal mass to conditioned spaces; needs to happen after thermostats are applied
3127
- standard.model_add_internal_mass(model, primary_bldg_type)
3128
- end
3129
-
3130
- # TODO: - add slab modeling and slab insulation
3131
-
3132
- # TODO: - fuel customization for cooking and laundry
3133
- # works by switching some fraction of electric loads to gas if requested (assuming base load is electric)
3134
-
3135
- # add thermostats
3136
- if args['add_thermostat']
3137
-
3138
- # remove thermostats
3139
- if args['remove_objects']
3140
- model.getThermostatSetpointDualSetpoints.each(&:remove)
3141
- end
3142
-
3143
- model.getSpaceTypes.sort.each do |space_type|
3144
- # create thermostat schedules
3145
- # skip un-recognized space types
3146
- next if standard.space_type_get_standards_data(space_type).empty?
3147
- # the last bool test it to make thermostat schedules. They are added to the model but not assigned
3148
- standard.space_type_apply_internal_load_schedules(space_type, false, false, false, false, false, false, true)
3149
-
3150
- # identify thermal thermostat and apply to zones (apply_internal_load_schedules names )
3151
- model.getThermostatSetpointDualSetpoints.sort.each do |thermostat|
3152
- next if thermostat.name.to_s != "#{space_type.name} Thermostat"
3153
- next if !thermostat.coolingSetpointTemperatureSchedule.is_initialized
3154
- next if !thermostat.heatingSetpointTemperatureSchedule.is_initialized
3155
- runner.registerInfo("Assigning #{thermostat.name} to thermal zones with #{space_type.name} assigned.")
3156
- space_type.spaces.sort.each do |space|
3157
- next if !space.thermalZone.is_initialized
3158
- space.thermalZone.get.setThermostatSetpointDualSetpoint(thermostat)
3159
- end
3160
- end
3161
- end
3162
- end
3163
-
3164
- # add hvac system
3165
- if args['add_hvac']
3166
-
3167
- # remove HVAC objects
3168
- if args['remove_objects']
3169
- standard.model_remove_prm_hvac(model)
3170
- end
3171
-
3172
- case args['system_type']
3173
- when 'Inferred'
3174
-
3175
- # Get the hvac delivery type enum
3176
- hvac_delivery = case args['hvac_delivery_type']
3177
- when 'Forced Air'
3178
- 'air'
3179
- when 'Hydronic'
3180
- 'hydronic'
3181
- end
3182
-
3183
- # Group the zones by occupancy type. Only split out non-dominant groups if their total area exceeds the limit.
3184
- sys_groups = standard.model_group_zones_by_type(model, OpenStudio.convert(20_000, 'ft^2', 'm^2').get)
3185
-
3186
- # For each group, infer the HVAC system type.
3187
- sys_groups.each do |sys_group|
3188
- # Infer the primary system type
3189
- # runner.registerInfo("template = #{args['template']}, climate_zone = #{climate_zone}, occ_type = #{sys_group['type']}, hvac_delivery = #{hvac_delivery}, htg_src = #{args['htg_src']}, clg_src = #{args['clg_src']}, area_ft2 = #{sys_group['area_ft2']}, num_stories = #{sys_group['stories']}")
3190
- sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel = standard.model_typical_hvac_system_type(model,
3191
- climate_zone,
3192
- sys_group['type'],
3193
- hvac_delivery,
3194
- args['htg_src'],
3195
- args['clg_src'],
3196
- OpenStudio.convert(sys_group['area_ft2'], 'ft^2', 'm^2').get,
3197
- sys_group['stories'])
3198
-
3199
- # Infer the secondary system type for multizone systems
3200
- sec_sys_type = case sys_type
3201
- when 'PVAV Reheat', 'VAV Reheat'
3202
- 'PSZ-AC'
3203
- when 'PVAV PFP Boxes', 'VAV PFP Boxes'
3204
- 'PSZ-HP'
3205
- else
3206
- sys_type # same as primary system type
3207
- end
3208
-
3209
- # group zones
3210
- if haystack_file.nil?
3211
- # Group zones by story
3212
- bldg_zone_lists = standard.model_group_zones_by_story(model, sys_group['zones'])
3213
- else
3214
- # todo - group zones using haystack file instead of building stories
3215
- # todo - need to do something similar to use haystack to indentify secondary zones
3216
- bldg_zone_lists = standard.model_group_zones_by_story(model, sys_group['zones'])
3217
- runner.registerInfo("***This code will define which zones are on air loops for inferred system***")
3218
- end
3219
-
3220
- # On each story, add the primary system to the primary zones
3221
- # and add the secondary system to any zones that are different.
3222
- bldg_zone_lists.each do |story_group|
3223
- # Differentiate primary and secondary zones, based on
3224
- # operating hours and internal loads (same as 90.1 PRM)
3225
- pri_sec_zone_lists = standard.model_differentiate_primary_secondary_thermal_zones(model, story_group)
3226
- system_zones = pri_sec_zone_lists['primary']
3227
-
3228
- # if the primary system type is PTAC, filter to cooled zones to prevent sizing error if no cooling
3229
- if sys_type == 'PTAC'
3230
- heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
3231
- cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
3232
- system_zones = heated_and_cooled_zones + cooled_only_zones
3233
- end
3234
-
3235
- # Add the primary system to the primary zones
3236
- unless standard.model_add_hvac_system(model, sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
3237
- runner.registerError("HVAC system type '#{sys_type}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
3238
- return false
3239
- end
3240
-
3241
- # Add the secondary system to the secondary zones (if any)
3242
- if !pri_sec_zone_lists['secondary'].empty?
3243
- system_zones = pri_sec_zone_lists['secondary']
3244
- if (sec_sys_type == 'PTAC') || (sec_sys_type == 'PSZ-AC')
3245
- heated_and_cooled_zones = system_zones.select { |zone| standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
3246
- cooled_only_zones = system_zones.select { |zone| !standard.thermal_zone_heated?(zone) && standard.thermal_zone_cooled?(zone) }
3247
- system_zones = heated_and_cooled_zones + cooled_only_zones
3248
- end
3249
- unless standard.model_add_hvac_system(model, sec_sys_type, central_htg_fuel, zone_htg_fuel, clg_fuel, system_zones)
3250
- runner.registerError("HVAC system type '#{sys_type}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
3251
- return false
3252
- end
3253
- end
3254
- end
3255
- end
3256
-
3257
- else # HVAC system_type specified
3258
-
3259
- # Group the zones by occupancy type. Only split out non-dominant groups if their total area exceeds the limit.
3260
- sys_groups = standard.model_group_zones_by_type(model, OpenStudio.convert(20_000, 'ft^2', 'm^2').get)
3261
- sys_groups.each do |sys_group|
3262
-
3263
- # group zones
3264
- if haystack_file.nil?
3265
- # Group zones by story
3266
- bldg_zone_groups = standard.model_group_zones_by_story(model, sys_group['zones'])
3267
- else
3268
- # todo - group zones using haystack file instead of building stories
3269
- # todo - need to do something similar to use haystack to indentify secondary zones
3270
- bldg_zone_groups = standard.model_group_zones_by_story(model, sys_group['zones'])
3271
- runner.registerInfo("***This code will define which zones are on air loops for user specified system***")
3272
- end
3273
-
3274
- # Add the user specified HVAC system for each story.
3275
- # Single-zone systems will get one per zone.
3276
- bldg_zone_groups.each do |zones|
3277
- unless model.add_cbecs_hvac_system(standard, args['system_type'], zones)
3278
- runner.registerError("HVAC system type '#{args['system_type']}' not recognized. Check input system type argument against Model.hvac.rb for valid hvac system type names.")
3279
- return false
3280
- end
3281
- end
3282
- end
3283
- end
3284
- end
3285
-
3286
- # hours of operation
3287
- if args['modify_wkdy_op_hrs'] || args['modify_wknd_op_hrs']
3288
- # Infer the current hours of operation schedule for the building
3289
- op_sch = standard.model_infer_hours_of_operation_building(model)
3290
-
3291
- # setup hoo_var_method (should be hours or fractional)
3292
- if args.has_key?('hoo_var_method')
3293
- hoo_var_method = args['hoo_var_method']
3294
- else
3295
- # support measures that don't supply this argument
3296
- hoo_var_method = 'hours'
3297
- end
3298
-
3299
- # Convert existing schedules in the model to parametric schedules based on current hours of operation
3300
- runner.registerInfo("Generating parametric schedules from ruleset schedules using #{hoo_var_method} variable method for hours of operation fromula.")
3301
- standard.model_setup_parametric_schedules(model, hoo_var_method: hoo_var_method)
3302
-
3303
- # Create start and end times from start time and duration supplied
3304
- wkdy_start_time = nil
3305
- wkdy_end_time = nil
3306
- wknd_start_time = nil
3307
- wknd_end_time = nil
3308
- # weekdays
3309
- if args['modify_wkdy_op_hrs']
3310
- wkdy_start_time = OpenStudio::Time.new(0, wkdy_op_hrs_start_time_hr, wkdy_op_hrs_start_time_min, 0)
3311
- wkdy_end_time = wkdy_start_time + OpenStudio::Time.new(0, wkdy_op_hrs_duration_hr, wkdy_op_hrs_duration_min, 0)
3312
- end
3313
- # weekends
3314
- if args['modify_wknd_op_hrs']
3315
- wknd_start_time = OpenStudio::Time.new(0, wknd_op_hrs_start_time_hr, wknd_op_hrs_start_time_min, 0)
3316
- wknd_end_time = wknd_start_time + OpenStudio::Time.new(0, wknd_op_hrs_duration_hr, wknd_op_hrs_duration_min, 0)
3317
- end
3318
-
3319
- # Modify hours of operation, using weekdays values for all weekdays and weekend values for Saturday and Sunday
3320
- standard.schedule_ruleset_set_hours_of_operation(op_sch,
3321
- wkdy_start_time: wkdy_start_time,
3322
- wkdy_end_time: wkdy_end_time,
3323
- sat_start_time: wknd_start_time,
3324
- sat_end_time: wknd_end_time,
3325
- sun_start_time: wknd_start_time,
3326
- sun_end_time: wknd_end_time)
3327
-
3328
- # Apply new operating hours to parametric schedules to make schedules in model reflect modified hours of operation
3329
- parametric_schedules = standard.model_apply_parametric_schedules(model, error_on_out_of_order: false)
3330
- runner.registerInfo("Updated #{parametric_schedules.size} schedules with new hours of operation.")
3331
- end
3332
-
3333
- # set hvac controls and efficiencies (this should be last model articulation element)
3334
- if args['add_hvac']
3335
- # set additional properties for building
3336
- props = model.getBuilding.additionalProperties
3337
- props.setFeature('hvac_system_type', (args['system_type']).to_s)
3338
-
3339
- case args['system_type']
3340
- when 'Ideal Air Loads'
3341
-
3342
- else
3343
- # Set the heating and cooling sizing parameters
3344
- standard.model_apply_prm_sizing_parameters(model)
3345
-
3346
- # Perform a sizing run
3347
- if standard.model_run_sizing_run(model, "#{Dir.pwd}/SR1") == false
3348
- log_messages_to_runner(runner, debug = true)
3349
- return false
3350
- end
3351
-
3352
- # If there are any multizone systems, reset damper positions
3353
- # to achieve a 60% ventilation effectiveness minimum for the system
3354
- # following the ventilation rate procedure from 62.1
3355
- standard.model_apply_multizone_vav_outdoor_air_sizing(model)
3356
-
3357
- # Apply the prototype HVAC assumptions
3358
- standard.model_apply_prototype_hvac_assumptions(model, primary_bldg_type, climate_zone)
3359
-
3360
- # Apply the HVAC efficiency standard
3361
- standard.model_apply_hvac_efficiency_standard(model, climate_zone)
3362
- end
3363
- end
3364
-
3365
- # add internal mass
3366
- if args['add_internal_mass']
3367
-
3368
- if args['remove_objects']
3369
- model.getSpaceLoads.sort.each do |instance|
3370
- next unless instance.to_InternalMass.is_initialized
3371
- instance.remove
3372
- end
3373
- end
3374
-
3375
- # add internal mass to conditioned spaces; needs to happen after thermostats are applied
3376
- standard.model_add_internal_mass(model, primary_bldg_type)
3377
- end
3378
-
3379
- # set unmet hours tolerance
3380
- unmet_hrs_tol_r = args['unmet_hours_tolerance']
3381
- unmet_hrs_tol_k = OpenStudio.convert(unmet_hrs_tol_r, 'R', 'K').get
3382
- tolerances = model.getOutputControlReportingTolerances
3383
- tolerances.setToleranceforTimeHeatingSetpointNotMet(unmet_hrs_tol_k)
3384
- tolerances.setToleranceforTimeCoolingSetpointNotMet(unmet_hrs_tol_k)
3385
-
3386
- # remove everything but spaces, zones, and stub space types (extend as needed for additional objects, may make bool arg for this)
3387
- if args['remove_objects']
3388
- model.purgeUnusedResourceObjects
3389
- objects_after_cleanup = initial_objects - model.getModelObjects.size
3390
- if objects_after_cleanup > 0
3391
- runner.registerInfo("Removing #{objects_after_cleanup} objects from model")
3392
- end
3393
- end
3394
-
3395
- # change night cycling control to "Thermostat" cycling and increase thermostat tolerance to 1.99999
3396
- manager_night_cycles = model.getAvailabilityManagerNightCycles
3397
- runner.registerInfo("Changing thermostat tollerance to 1.99999 for #{manager_night_cycles.size} night cycle manager objects.")
3398
-
3399
- manager_night_cycles.each do |night_cycle|
3400
- night_cycle.setThermostatTolerance(1.9999)
3401
- night_cycle.setCyclingRunTimeControlType("Thermostat")
3402
- end
3403
-
3404
- # report final condition of model
3405
- runner.registerFinalCondition("The building finished with #{model.getModelObjects.size} objects.")
3406
-
3407
- # log messages to info messages
3408
- log_messages_to_runner(runner, debug = false)
3409
-
3410
- return true
3411
- end
3412
-
3413
- # wizard
3414
- # used for varieties of measures that create space type and construction set wizard
3415
- def wizard(model, runner, user_arguments)
3416
- # use the built-in error checking
3417
- if !runner.validateUserArguments(arguments(model), user_arguments)
3418
- return false
3419
- end
3420
-
3421
- # assign the user inputs to variables
3422
- building_type = runner.getStringArgumentValue('building_type', user_arguments)
3423
- template = runner.getStringArgumentValue('template', user_arguments)
3424
- climate_zone = runner.getStringArgumentValue('climate_zone', user_arguments)
3425
- create_space_types = runner.getBoolArgumentValue('create_space_types', user_arguments)
3426
- create_construction_set = runner.getBoolArgumentValue('create_construction_set', user_arguments)
3427
- set_building_defaults = runner.getBoolArgumentValue('set_building_defaults', user_arguments)
3428
-
3429
- # reporting initial condition of model
3430
- starting_spaceTypes = model.getSpaceTypes.sort
3431
- starting_constructionSets = model.getDefaultConstructionSets.sort
3432
- runner.registerInitialCondition("The building started with #{starting_spaceTypes.size} space types and #{starting_constructionSets.size} construction sets.")
3433
-
3434
- # lookup space types for specified building type (false indicates not to use whole building type only)
3435
- space_type_hash = get_space_types_from_building_type(building_type, template, false)
3436
- if space_type_hash == false
3437
- runner.registerError("#{building_type} is an unexpected building type.")
3438
- return false
3439
- end
3440
-
3441
- # create space_type_map from array
3442
- space_type_map = {}
3443
- default_space_type_name = nil
3444
- space_type_hash.each do |space_type_name, hash|
3445
- next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
3446
- space_type_map[space_type_name] = [] # no spaces to pass in
3447
- if hash[:default]
3448
- default_space_type_name = space_type_name
3449
- end
3450
- end
3451
-
3452
- # Make the standard applier
3453
- standard = Standard.build(template)
3454
-
3455
- # mapping building_type name is needed for a few methods
3456
- lookup_building_type = standard.model_get_lookup_name(building_type)
3457
-
3458
- # get array of new space types
3459
- space_types_new = []
3460
-
3461
- # create_space_types
3462
- if create_space_types
3463
-
3464
- # array of starting space types
3465
- space_types_starting = model.getSpaceTypes.sort
3466
-
3467
- # create stub space types
3468
- space_type_hash.each do |space_type_name, hash|
3469
- next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
3470
-
3471
- # create space type
3472
- space_type = OpenStudio::Model::SpaceType.new(model)
3473
- space_type.setStandardsBuildingType(lookup_building_type)
3474
- space_type.setStandardsSpaceType(space_type_name)
3475
- space_type.setName("#{lookup_building_type} #{space_type_name}")
3476
-
3477
- # add to array of new space types
3478
- space_types_new << space_type
3479
-
3480
- # add internal loads (the nil check isn't necessary, but I will keep it in as a warning instad of an error)
3481
- test = standard.space_type_apply_internal_loads(space_type, true, true, true, true, true, true)
3482
- if test.nil?
3483
- runner.registerWarning("Could not add loads for #{space_type.name}. Not expected for #{template} #{lookup_building_type}")
3484
- end
3485
-
3486
- # the last bool test it to make thermostat schedules. They are added to the model but not assigned
3487
- standard.space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
3488
-
3489
- # assign colors
3490
- standard.space_type_apply_rendering_color(space_type)
3491
-
3492
- # exend space type name to include the template. Consider this as well for load defs
3493
- space_type.setName("#{space_type.name} - #{template}")
3494
- runner.registerInfo("Added space type named #{space_type.name}")
3495
- end
3496
-
3497
- end
3498
-
3499
- # add construction sets
3500
- bldg_def_const_set = nil
3501
- if create_construction_set
3502
-
3503
- # Make the default construction set for the building
3504
- is_residential = 'No' # default is nonresidential for building level
3505
- bldg_def_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, is_residential)
3506
- if bldg_def_const_set.is_initialized
3507
- bldg_def_const_set = bldg_def_const_set.get
3508
- runner.registerInfo("Added default construction set named #{bldg_def_const_set.name}")
3509
- else
3510
- runner.registerError('Could not create default construction set for the building.')
3511
- return false
3512
- end
3513
-
3514
- # make residential construction set as unused resource
3515
- if ['SmallHotel', 'LargeHotel', 'MidriseApartment', 'HighriseApartment'].include?(building_type)
3516
- res_const_set = standard.model_add_construction_set(model, climate_zone, lookup_building_type, nil, 'Yes')
3517
- if res_const_set.is_initialized
3518
- res_const_set = res_const_set.get
3519
- res_const_set.setName("#{bldg_def_const_set.name} - Residential ")
3520
- runner.registerInfo("Added residential construction set named #{res_const_set.name}")
3521
- else
3522
- runner.registerError('Could not create residential construction set for the building.')
3523
- return false
3524
- end
3525
- end
3526
-
3527
- end
3528
-
3529
- # set_building_defaults
3530
- if set_building_defaults
3531
-
3532
- # identify default space type
3533
- space_type_standards_info_hash = OsLib_HelperMethods.getSpaceTypeStandardsInformation(space_types_new)
3534
- default_space_type = nil
3535
- space_type_standards_info_hash.each do |space_type, standards_array|
3536
- standards_space_type = standards_array[1]
3537
- if default_space_type_name == standards_space_type
3538
- default_space_type = space_type
3539
- end
3540
- end
3541
-
3542
- # set default space type
3543
- building = model.getBuilding
3544
- if !default_space_type.nil?
3545
- building.setSpaceType(default_space_type)
3546
- runner.registerInfo("Setting default Space Type for building to #{building.spaceType.get.name}")
3547
- end
3548
-
3549
- # default construction
3550
- if !bldg_def_const_set.nil?
3551
- building.setDefaultConstructionSet(bldg_def_const_set)
3552
- runner.registerInfo("Setting default Construction Set for building to #{building.defaultConstructionSet.get.name}")
3553
- end
3554
-
3555
- # set climate zone
3556
- os_climate_zone = climate_zone.gsub('ASHRAE 169-2013-', '')
3557
- # trim off letter from climate zone 7 or 8
3558
- if (os_climate_zone[0] == '7') || (os_climate_zone[0] == '8')
3559
- os_climate_zone = os_climate_zone[0]
3560
- end
3561
- climate_zone = model.getClimateZones.setClimateZone('ASHRAE', os_climate_zone)
3562
- runner.registerInfo("Setting #{climate_zone.institution} Climate Zone to #{climate_zone.value}")
3563
-
3564
- # set building type
3565
- # use lookup_building_type so spaces like MediumOffice will map to Office (Supports baseline automation)
3566
- building.setStandardsBuildingType(lookup_building_type)
3567
- runner.registerInfo("Setting Standards Building Type to #{building.standardsBuildingType}")
3568
-
3569
- # rename building if it is named "Building 1"
3570
- if model.getBuilding.name.to_s == 'Building 1'
3571
- model.getBuilding.setName("#{building_type} #{template} #{os_climate_zone}")
3572
- runner.registerInfo("Renaming building to #{model.getBuilding.name}")
3573
- end
3574
-
3575
- end
3576
-
3577
- # reporting final condition of model
3578
- finishing_spaceTypes = model.getSpaceTypes.sort
3579
- finishing_constructionSets = model.getDefaultConstructionSets.sort
3580
- runner.registerFinalCondition("The building finished with #{finishing_spaceTypes.size} space types and #{finishing_constructionSets.size} construction sets.")
3581
-
3582
- return true
3583
- end
3584
- end