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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.md +1 -1
- data/README.md +2 -0
- data/lib/openstudio/extension/runner.rb +12 -8
- data/lib/openstudio/extension/runner_config.rb +33 -6
- data/lib/openstudio/extension/version.rb +1 -1
- data/openstudio-extension.gemspec +6 -6
- metadata +15 -66
- data/lib/openstudio/extension/core/CreateResults.rb +0 -1033
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +0 -160
- data/lib/openstudio/extension/core/check_calibration.rb +0 -125
- data/lib/openstudio/extension/core/check_cond_zns.rb +0 -54
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +0 -304
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +0 -423
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +0 -132
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +0 -105
- data/lib/openstudio/extension/core/check_fan_pwr.rb +0 -68
- data/lib/openstudio/extension/core/check_internal_loads.rb +0 -363
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +0 -196
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +0 -296
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +0 -434
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +0 -109
- data/lib/openstudio/extension/core/check_part_loads.rb +0 -421
- data/lib/openstudio/extension/core/check_placeholder.rb +0 -45
- data/lib/openstudio/extension/core/check_plant_cap.rb +0 -93
- data/lib/openstudio/extension/core/check_plant_temps.rb +0 -129
- data/lib/openstudio/extension/core/check_plenum_loads.rb +0 -57
- data/lib/openstudio/extension/core/check_pump_pwr.rb +0 -78
- data/lib/openstudio/extension/core/check_sch_coord.rb +0 -211
- data/lib/openstudio/extension/core/check_schedules.rb +0 -281
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +0 -128
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +0 -118
- data/lib/openstudio/extension/core/check_weather_files.rb +0 -102
- data/lib/openstudio/extension/core/deer_vintages.rb +0 -281
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +0 -461
- data/lib/openstudio/extension/core/os_lib_constructions.rb +0 -353
- data/lib/openstudio/extension/core/os_lib_geometry.rb +0 -1169
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +0 -383
- data/lib/openstudio/extension/core/os_lib_hvac.rb +0 -2163
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +0 -184
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +0 -3584
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +0 -1019
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +0 -135
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +0 -170
- data/lib/openstudio/extension/core/os_lib_schedules.rb +0 -933
@@ -1,1019 +0,0 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
module OsLib_ModelSimplification
|
7
|
-
# get all loads for a space_or_space_type and place in hash by type
|
8
|
-
def gather_internal_loads(space_or_space_type)
|
9
|
-
internal_load_hash = {}
|
10
|
-
|
11
|
-
# gather different load types (all vectors except dsoa which will be turned into an array)
|
12
|
-
internal_load_hash[:internal_mass] = space_or_space_type.internalMass
|
13
|
-
internal_load_hash[:people] = space_or_space_type.people
|
14
|
-
internal_load_hash[:lights] = space_or_space_type.lights
|
15
|
-
internal_load_hash[:luminaires] = space_or_space_type.luminaires
|
16
|
-
internal_load_hash[:electric_equipment] = space_or_space_type.electricEquipment
|
17
|
-
internal_load_hash[:gas_equipment] = space_or_space_type.gasEquipment
|
18
|
-
internal_load_hash[:hot_water_equipment] = space_or_space_type.hotWaterEquipment
|
19
|
-
internal_load_hash[:steam_equipment] = space_or_space_type.steamEquipment
|
20
|
-
internal_load_hash[:other_equipment] = space_or_space_type.otherEquipment
|
21
|
-
internal_load_hash[:space_infiltration_design_flow_rates] = space_or_space_type.spaceInfiltrationDesignFlowRates
|
22
|
-
internal_load_hash[:space_infiltration_effective_leakage_areas] = space_or_space_type.spaceInfiltrationEffectiveLeakageAreas
|
23
|
-
if space_or_space_type.designSpecificationOutdoorAir.nil?
|
24
|
-
internal_load_hash[:design_specification_outdoor_air] = []
|
25
|
-
else
|
26
|
-
internal_load_hash[:design_specification_outdoor_air] = [space_or_space_type.designSpecificationOutdoorAir]
|
27
|
-
end
|
28
|
-
if space_or_space_type.class.to_s == 'OpenStudio::Model::Space'
|
29
|
-
internal_load_hash[:water_use_equipment] = space_or_space_type.waterUseEquipment # don't think this reports
|
30
|
-
internal_load_hash[:daylighting_controls] = space_or_space_type.daylightingControls
|
31
|
-
end
|
32
|
-
|
33
|
-
# TODO: - warn if daylighting controls in spaces (should I alter fraction controled based on lighting per area ratio)
|
34
|
-
|
35
|
-
return internal_load_hash
|
36
|
-
end
|
37
|
-
|
38
|
-
# blend_space_types_from_floor_area_ratio used when working from space type ratio and un-assigned space types
|
39
|
-
def blend_space_types_from_floor_area_ratio(runner, model, space_type_ratio_hash)
|
40
|
-
# create stub blended space type
|
41
|
-
blended_space_type = OpenStudio::Model::SpaceType.new(model)
|
42
|
-
blended_space_type.setName('Blended Space Type')
|
43
|
-
|
44
|
-
# TODO: - inspect people instances and see if any defs are not normalized per area. If find any issue warning
|
45
|
-
|
46
|
-
# gather inputs
|
47
|
-
sum_of_num_people_per_m_2 = 0.0
|
48
|
-
space_type_ratio_hash.each do |space_type, ratios|
|
49
|
-
# get number of peple per m 2 for space type. Can do this without looking at instances
|
50
|
-
sum_of_num_people_per_m_2 += space_type.getPeoplePerFloorArea(1.0)
|
51
|
-
end
|
52
|
-
|
53
|
-
# raw num_people_ratios
|
54
|
-
sum_area_adj_num_people_ratio = 0.0
|
55
|
-
space_type_ratio_hash.each do |space_type, ratios|
|
56
|
-
# calculate num_people_ratios
|
57
|
-
area_adj_num_people_ratio = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
|
58
|
-
sum_area_adj_num_people_ratio += area_adj_num_people_ratio
|
59
|
-
end
|
60
|
-
|
61
|
-
# set ratios
|
62
|
-
largest_space_type = nil
|
63
|
-
largest_space_type_ratio = 0.00
|
64
|
-
space_type_ratio_hash.each do |space_type, ratios|
|
65
|
-
# calculate num_people_ratios
|
66
|
-
area_adj_num_people_ratio = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
|
67
|
-
normalized_area_adj_num_people_ratio = area_adj_num_people_ratio / sum_area_adj_num_people_ratio
|
68
|
-
|
69
|
-
# ratios[:floor_area_ratio] is already defined
|
70
|
-
ratios[:num_people_ratio] = normalized_area_adj_num_people_ratio.round(4)
|
71
|
-
ratios[:ext_surface_area_ratio] = ratios[:floor_area_ratio]
|
72
|
-
ratios[:ext_wall_area_ratio] = ratios[:floor_area_ratio]
|
73
|
-
ratios[:volume_ratio] = ratios[:floor_area_ratio]
|
74
|
-
|
75
|
-
# update largest space type values
|
76
|
-
if largest_space_type.nil?
|
77
|
-
largest_space_type = space_type
|
78
|
-
largest_space_type_ratio = ratios[:floor_area_ratio]
|
79
|
-
elsif ratios[:floor_area_ratio] > largest_space_type_ratio
|
80
|
-
largest_space_type = space_type
|
81
|
-
largest_space_type_ratio = ratios[:floor_area_ratio]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
if largest_space_type.nil?
|
86
|
-
runner.registerError("Didn't find any space types in model matching user argument string.")
|
87
|
-
return nil
|
88
|
-
end
|
89
|
-
|
90
|
-
# set standards info for space type based on largest ratio (for use to apply HVAC system)
|
91
|
-
standards_building_type = largest_space_type.standardsBuildingType
|
92
|
-
standards_space_type = largest_space_type.standardsSpaceType
|
93
|
-
if standards_building_type.is_initialized
|
94
|
-
blended_space_type.setStandardsBuildingType(standards_building_type.get)
|
95
|
-
end
|
96
|
-
if standards_space_type.is_initialized
|
97
|
-
blended_space_type.setStandardsSpaceType(standards_space_type.get)
|
98
|
-
end
|
99
|
-
|
100
|
-
# loop therough space types to get instances from and then remove
|
101
|
-
space_type_ratio_hash.each do |space_type, ratios|
|
102
|
-
# blend internal loads (nil is space_hash)
|
103
|
-
space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, model.getBuilding.floorArea, nil)
|
104
|
-
runner.registerInfo("Blending #{space_type.name.get} with floor area ratio of #{ratios[:floor_area_ratio]} and number of people ratio of #{ratios[:num_people_ratio]}.")
|
105
|
-
|
106
|
-
# delete space type. Don't want to leave in model since internal loads have been removed from it
|
107
|
-
space_type.remove
|
108
|
-
end
|
109
|
-
|
110
|
-
return blended_space_type
|
111
|
-
end
|
112
|
-
|
113
|
-
# takes in space type hash where each hash value is a colleciton of space types. Each collection is blended into it's own space type
|
114
|
-
# If key for any collection is "Building" it will also opererate on spaces that don't have space type assigned
|
115
|
-
# where a space assigned to a space type from a collection has space loads, those space loads are normalized and added to the blended space type
|
116
|
-
# load instances are maintained so that they can haave unique schedules, and can have EE measures selectivly applied.
|
117
|
-
def blend_space_type_collections(runner, model, space_type_hash)
|
118
|
-
# loop through building type hash to create multiple blends
|
119
|
-
space_type_hash.each do |collection_name, space_types|
|
120
|
-
if collection_name == 'Building'
|
121
|
-
space_array = model.getSpaces.sort # use all space types, not just space types passed in
|
122
|
-
else
|
123
|
-
space_array = []
|
124
|
-
space_types.each do |space_type|
|
125
|
-
space_array.concat(space_type.spaces)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# calculate metrics for all spaces included in building area to pass into space_type and space hash
|
130
|
-
# note: in the future this may be a subset of spaces if blending into multiple space types vs. just one.
|
131
|
-
collection_totals = {}
|
132
|
-
collection_totals[:floor_area] = 0.0
|
133
|
-
collection_totals[:num_people] = 0.0
|
134
|
-
collection_totals[:ext_surface_area] = 0.0
|
135
|
-
collection_totals[:ext_wall_area] = 0.0
|
136
|
-
collection_totals[:volume] = 0.0
|
137
|
-
space_array.each do |space|
|
138
|
-
next if !space.partofTotalFloorArea
|
139
|
-
collection_totals[:floor_area] += space.floorArea * space.multiplier
|
140
|
-
collection_totals[:num_people] += space.numberOfPeople * space.multiplier
|
141
|
-
collection_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
|
142
|
-
collection_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
|
143
|
-
collection_totals[:volume] += space.volume * space.multiplier
|
144
|
-
end
|
145
|
-
area_ip = OpenStudio.convert(collection_totals[:floor_area], 'm^2', 'ft^2').get
|
146
|
-
area_ip_neat = OpenStudio.toNeatString(area_ip, 2, true)
|
147
|
-
runner.registerInfo("#{collection_name} area is #{area_ip_neat} ft^2, number of people is #{collection_totals[:num_people].round(0)}.")
|
148
|
-
|
149
|
-
# create hash of space types and floor area for all space types with area > 0 when spaces included in floor area
|
150
|
-
# code to gather space type areas came from openstudio_results measure.
|
151
|
-
space_type_hash = {}
|
152
|
-
largest_space_type = nil
|
153
|
-
largest_space_type_ratio = 0.00
|
154
|
-
space_types.each do |space_type|
|
155
|
-
next if space_type.floorArea == 0
|
156
|
-
space_type_totals = {}
|
157
|
-
space_type_totals[:floor_area] = 0.0
|
158
|
-
space_type_totals[:num_people] = 0.0
|
159
|
-
space_type_totals[:ext_surface_area] = 0.0
|
160
|
-
space_type_totals[:ext_wall_area] = 0.0
|
161
|
-
space_type_totals[:volume] = 0.0
|
162
|
-
# loop through spaces so I can skip if not included in floor area
|
163
|
-
space_type.spaces.each do |space|
|
164
|
-
next if !space.partofTotalFloorArea
|
165
|
-
space_type_totals[:floor_area] += space.floorArea * space.multiplier
|
166
|
-
space_type_totals[:num_people] += space.numberOfPeople * space.multiplier
|
167
|
-
space_type_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
|
168
|
-
space_type_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
|
169
|
-
space_type_totals[:volume] += space.volume * space.multiplier
|
170
|
-
end
|
171
|
-
|
172
|
-
# update largest space type values
|
173
|
-
if largest_space_type.nil?
|
174
|
-
largest_space_type = space_type
|
175
|
-
largest_space_type_ratio = space_type_totals[:floor_area]
|
176
|
-
elsif space_type_totals[:floor_area] > largest_space_type_ratio
|
177
|
-
largest_space_type = space_type
|
178
|
-
largest_space_type_ratio = space_type_totals[:floor_area]
|
179
|
-
end
|
180
|
-
|
181
|
-
# gather internal loads
|
182
|
-
space_type_loads_hash = gather_internal_loads(space_type)
|
183
|
-
|
184
|
-
# don't add to hash if no spaces used for space type are included in building area (e.g. plenum and attic)
|
185
|
-
# todo - log these and decide what to do for them. Leave loads alone or remove, do they add to blend at all?
|
186
|
-
next if space_type_totals[:floor_area] == 0
|
187
|
-
|
188
|
-
if !space_type_totals[:floor_area] = space_type.floorArea # TODO: - not sure if these would ever show as different
|
189
|
-
runner.registerWarning("Some but not all spaces of #{space_type.name} space type are not included in the building floor area. May have unexpected results")
|
190
|
-
end
|
191
|
-
|
192
|
-
# populate space type hash
|
193
|
-
space_type_hash[space_type] = { int_loads: space_type_loads_hash, totals: space_type_totals }
|
194
|
-
end
|
195
|
-
|
196
|
-
# report initial condition of model
|
197
|
-
runner.registerInfo("#{collection_name} accounts for #{space_type_hash.size} space types.")
|
198
|
-
|
199
|
-
if collection_name == 'Building'
|
200
|
-
# count area of spaces that have no space type
|
201
|
-
no_space_type_area_counter = 0
|
202
|
-
model.getSpaces.sort.each do |space|
|
203
|
-
if space.spaceType.empty?
|
204
|
-
next if !space.partofTotalFloorArea
|
205
|
-
no_space_type_area_counter += space.floorArea * space.multiplier
|
206
|
-
end
|
207
|
-
end
|
208
|
-
floor_area_ratio = no_space_type_area_counter / collection_totals[:floor_area]
|
209
|
-
if floor_area_ratio > 0
|
210
|
-
runner.registerInfo("#{floor_area_ratio} fraction of building area is composed of spaces without space type assignments.")
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
# report the space ratio for hard spaces
|
215
|
-
space_hash = {}
|
216
|
-
space_array.each do |space|
|
217
|
-
next if !space.partofTotalFloorArea
|
218
|
-
space_loads_hash = gather_internal_loads(space)
|
219
|
-
space_totals = {}
|
220
|
-
space_totals[:floor_area] = space.floorArea * space.multiplier
|
221
|
-
space_totals[:num_people] = space.numberOfPeople * space.multiplier
|
222
|
-
space_totals[:ext_surface_area] = space.exteriorArea * space.multiplier
|
223
|
-
space_totals[:ext_wall_area] = space.exteriorWallArea * space.multiplier
|
224
|
-
space_totals[:volume] = space.volume * space.multiplier
|
225
|
-
if !space_loads_hash[:daylighting_controls].empty?
|
226
|
-
runner.registerWarning("#{space.name} has one or more daylighting controls. Lighting loads from blended space type may affect lighting reduction from daylighting controls.")
|
227
|
-
end
|
228
|
-
if !space_loads_hash[:water_use_equipment].empty?
|
229
|
-
runner.registerInfo("One ore more water use equipment objects are associated with space #{space.name}. This can't be moved to a space type.")
|
230
|
-
end
|
231
|
-
# note: If generating ratios without geometry can calculate people_ratio given space_types floor_area_ratio
|
232
|
-
space_hash[space] = { int_loads: space_loads_hash, totals: space_totals }
|
233
|
-
end
|
234
|
-
|
235
|
-
# create stub blended space type
|
236
|
-
blended_space_type = OpenStudio::Model::SpaceType.new(model)
|
237
|
-
blended_space_type.setName("#{collection_name} Blended Space Type")
|
238
|
-
|
239
|
-
# set standards info for space type based on largest ratio (for use to apply HVAC system)
|
240
|
-
standards_building_type = largest_space_type.standardsBuildingType
|
241
|
-
standards_space_type = largest_space_type.standardsSpaceType
|
242
|
-
if standards_building_type.is_initialized
|
243
|
-
blended_space_type.setStandardsBuildingType(standards_building_type.get)
|
244
|
-
end
|
245
|
-
if standards_space_type.is_initialized
|
246
|
-
blended_space_type.setStandardsSpaceType(standards_space_type.get)
|
247
|
-
end
|
248
|
-
|
249
|
-
# values from collection hash
|
250
|
-
collection_floor_area = collection_totals[:floor_area]
|
251
|
-
collection_num_people = collection_totals[:num_people]
|
252
|
-
collection_ext_surface_area = collection_totals[:ext_surface_area]
|
253
|
-
collection_ext_wall_area = collection_totals[:ext_wall_area]
|
254
|
-
collection_volume = collection_totals[:volume]
|
255
|
-
|
256
|
-
# loop through space that have one or more spaces included in the building area
|
257
|
-
space_type_hash.each do |space_type, hash|
|
258
|
-
# hard assign space load schedules before re-assign instances to blended space type
|
259
|
-
space_type.hardApplySpaceLoadSchedules
|
260
|
-
|
261
|
-
# vaules from space or space_type
|
262
|
-
floor_area = hash[:totals][:floor_area]
|
263
|
-
num_people = hash[:totals][:num_people]
|
264
|
-
ext_surface_area = hash[:totals][:ext_surface_area]
|
265
|
-
ext_wall_area = hash[:totals][:ext_wall_area]
|
266
|
-
volume = hash[:totals][:volume]
|
267
|
-
|
268
|
-
# ratios
|
269
|
-
ratios = {}
|
270
|
-
if collection_floor_area > 0
|
271
|
-
ratios[:floor_area_ratio] = floor_area / collection_floor_area
|
272
|
-
else
|
273
|
-
ratios[:floor_area_ratio] = 0.0
|
274
|
-
end
|
275
|
-
if collection_num_people > 0
|
276
|
-
ratios[:num_people_ratio] = num_people / collection_num_people
|
277
|
-
else
|
278
|
-
ratios[:num_people_ratio] = 0.0
|
279
|
-
end
|
280
|
-
if collection_ext_surface_area > 0
|
281
|
-
ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
|
282
|
-
else
|
283
|
-
ratios[:ext_surface_area_ratio] = 0.0
|
284
|
-
end
|
285
|
-
if collection_ext_wall_area > 0
|
286
|
-
ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
|
287
|
-
else
|
288
|
-
ratios[:ext_wall_area_ratio] = 0.0
|
289
|
-
end
|
290
|
-
if collection_volume > 0
|
291
|
-
ratios[:volume_ratio] = volume / collection_volume
|
292
|
-
else
|
293
|
-
ratios[:volume_ratio] = 0.0
|
294
|
-
end
|
295
|
-
|
296
|
-
# populate blended space type with space type loads
|
297
|
-
space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, collection_floor_area, space_hash)
|
298
|
-
runner.registerInfo("Blending space type #{space_type.name}. Floor area ratio is #{(hash[:totals][:floor_area] / collection_totals[:floor_area]).round(3)}. People ratio is #{(hash[:totals][:num_people] / collection_totals[:num_people]).round(3)}")
|
299
|
-
|
300
|
-
# hard assign any constructions assigned by space types, except for space not included in the building area
|
301
|
-
if space_type.defaultConstructionSet.is_initialized
|
302
|
-
runner.registerInfo("Hard assigning constructions for #{space_type.name}.")
|
303
|
-
space_type.spaces.each(&:hardApplyConstructions)
|
304
|
-
end
|
305
|
-
|
306
|
-
# remove all space type assignments, except for spaces not included in building area.
|
307
|
-
space_type.spaces.each do |space|
|
308
|
-
next if !space.partofTotalFloorArea
|
309
|
-
space.resetSpaceType
|
310
|
-
end
|
311
|
-
|
312
|
-
# delete space type. Don't want to leave in model since internal loads have been removed from it
|
313
|
-
space_type.remove
|
314
|
-
end
|
315
|
-
|
316
|
-
# loop through spaces that are included in building area
|
317
|
-
space_hash.each do |space, hash|
|
318
|
-
# hard assign space load schedules before re-assign instances to blended space type
|
319
|
-
space.hardApplySpaceLoadSchedules
|
320
|
-
|
321
|
-
# vaules from space or space_type
|
322
|
-
floor_area = hash[:totals][:floor_area]
|
323
|
-
num_people = hash[:totals][:num_people]
|
324
|
-
ext_surface_area = hash[:totals][:ext_surface_area]
|
325
|
-
ext_wall_area = hash[:totals][:ext_wall_area]
|
326
|
-
volume = hash[:totals][:volume]
|
327
|
-
|
328
|
-
# ratios
|
329
|
-
ratios = {}
|
330
|
-
if collection_floor_area > 0
|
331
|
-
ratios[:floor_area_ratio] = floor_area / collection_floor_area
|
332
|
-
else
|
333
|
-
ratios[:floor_area_ratio] = 0.0
|
334
|
-
end
|
335
|
-
if collection_num_people > 0
|
336
|
-
ratios[:num_people_ratio] = num_people / collection_num_people
|
337
|
-
else
|
338
|
-
ratios[:num_people_ratio] = 0.0
|
339
|
-
end
|
340
|
-
if collection_ext_surface_area > 0
|
341
|
-
ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
|
342
|
-
else
|
343
|
-
ratios[:ext_surface_area_ratio] = 0.0
|
344
|
-
end
|
345
|
-
if collection_ext_wall_area > 0
|
346
|
-
ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
|
347
|
-
else
|
348
|
-
ratios[:ext_wall_area_ratio] = 0.0
|
349
|
-
end
|
350
|
-
if collection_volume > 0
|
351
|
-
ratios[:volume_ratio] = volume / collection_volume
|
352
|
-
else
|
353
|
-
ratios[:volume_ratio] = 0.0
|
354
|
-
end
|
355
|
-
|
356
|
-
# populate blended space type with space loads
|
357
|
-
space_load_instances = blend_internal_loads(runner, model, space, blended_space_type, ratios, collection_floor_area, space_hash)
|
358
|
-
next if space_load_instances.empty?
|
359
|
-
runner.registerInfo("Blending space #{space.name}. Floor area ratio is #{(hash[:totals][:floor_area] / collection_totals[:floor_area]).round(3)}. People ratio is #{(hash[:totals][:num_people] / collection_totals[:num_people]).round(3)}")
|
360
|
-
end
|
361
|
-
|
362
|
-
if collection_name == 'Building'
|
363
|
-
# assign blended space type to building
|
364
|
-
model.getBuilding.setSpaceType(blended_space_type)
|
365
|
-
building_space_type = model.getBuilding.spaceType
|
366
|
-
else
|
367
|
-
space_array.each do |space|
|
368
|
-
space.setSpaceType(blended_space_type)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
return model.getSpaceTypes.sort
|
374
|
-
end
|
375
|
-
|
376
|
-
# blend internal loads used when working from existing model
|
377
|
-
def blend_internal_loads(runner, model, source_space_or_space_type, target_space_type, ratios, collection_floor_area, space_hash)
|
378
|
-
# ratios
|
379
|
-
floor_area_ratio = ratios[:floor_area_ratio]
|
380
|
-
num_people_ratio = ratios[:num_people_ratio]
|
381
|
-
ext_surface_area_ratio = ratios[:ext_surface_area_ratio]
|
382
|
-
ext_wall_area_ratio = ratios[:ext_wall_area_ratio]
|
383
|
-
volume_ratio = ratios[:volume_ratio]
|
384
|
-
|
385
|
-
# for normalizing design level loads I need to know effective number of spaces instance is applied to
|
386
|
-
if source_space_or_space_type.to_Space.is_initialized
|
387
|
-
eff_num_spaces = source_space_or_space_type.multiplier
|
388
|
-
else
|
389
|
-
eff_num_spaces = 0
|
390
|
-
source_space_or_space_type.spaces.each do |space|
|
391
|
-
eff_num_spaces += space.multiplier
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
# array of load instacnes re-assigned to blended space
|
396
|
-
instances_array = []
|
397
|
-
|
398
|
-
# internal_mass
|
399
|
-
source_space_or_space_type.internalMass.each do |load_inst|
|
400
|
-
load_def = load_inst.definition.to_InternalMassDefinition.get
|
401
|
-
if load_def.surfaceArea.is_initialized
|
402
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
403
|
-
if collection_floor_area == 0
|
404
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
405
|
-
else
|
406
|
-
cloned_load_def = load_def.clone(model).to_InternalMass.get
|
407
|
-
orig_design_level = cloned_load_def.surfaceArea.get
|
408
|
-
cloned_load_def.setSurfaceAreaperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
409
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} m^2.")
|
410
|
-
load_inst.setInternalMassDefinition(cloned_load_def)
|
411
|
-
end
|
412
|
-
elsif load_def.surfaceAreaperSpaceFloorArea.is_initialized
|
413
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
414
|
-
elsif load_def.surfaceAreaperPerson.is_initialized
|
415
|
-
if num_people_ratio.nil?
|
416
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
417
|
-
return false
|
418
|
-
else
|
419
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
420
|
-
end
|
421
|
-
else
|
422
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
423
|
-
return false
|
424
|
-
end
|
425
|
-
load_inst.setSpaceType(target_space_type)
|
426
|
-
instances_array << load_inst
|
427
|
-
end
|
428
|
-
|
429
|
-
# people
|
430
|
-
source_space_or_space_type.people.each do |load_inst|
|
431
|
-
load_def = load_inst.definition.to_PeopleDefinition.get
|
432
|
-
if load_def.numberofPeople.is_initialized
|
433
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
434
|
-
if collection_floor_area == 0
|
435
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
436
|
-
else
|
437
|
-
cloned_load_def = load_def.clone(model).to_PeopleDefinition.get
|
438
|
-
orig_design_level = cloned_load_def.numberofPeople.get
|
439
|
-
cloned_load_def.setPeopleperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
440
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} people.")
|
441
|
-
load_inst.setPeopleDefinition(cloned_load_def)
|
442
|
-
end
|
443
|
-
elsif load_def.peopleperSpaceFloorArea.is_initialized
|
444
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
445
|
-
elsif load_def.spaceFloorAreaperPerson.is_initialized
|
446
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
447
|
-
else
|
448
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
449
|
-
return false
|
450
|
-
end
|
451
|
-
load_inst.setSpaceType(target_space_type)
|
452
|
-
instances_array << load_inst
|
453
|
-
end
|
454
|
-
|
455
|
-
# lights
|
456
|
-
source_space_or_space_type.lights.each do |load_inst|
|
457
|
-
load_def = load_inst.definition.to_LightsDefinition.get
|
458
|
-
if load_def.lightingLevel.is_initialized
|
459
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
460
|
-
if collection_floor_area == 0
|
461
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
462
|
-
else
|
463
|
-
cloned_load_def = load_def.clone(model).to_LightsDefinition.get
|
464
|
-
orig_design_level = cloned_load_def.lightingLevel.get
|
465
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
466
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
467
|
-
load_inst.setLightsDefinition(cloned_load_def)
|
468
|
-
end
|
469
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
470
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
471
|
-
elsif load_def.wattsperPerson.is_initialized
|
472
|
-
if num_people_ratio.nil?
|
473
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
474
|
-
return false
|
475
|
-
else
|
476
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
477
|
-
end
|
478
|
-
else
|
479
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
480
|
-
return false
|
481
|
-
end
|
482
|
-
load_inst.setSpaceType(target_space_type)
|
483
|
-
instances_array << load_inst
|
484
|
-
end
|
485
|
-
|
486
|
-
# luminaires
|
487
|
-
source_space_or_space_type.luminaires.each do |load_inst|
|
488
|
-
# TODO: - can't normalize luminaire. Replace it with similar normalized lights def and instance
|
489
|
-
runner.registerWarning("Can't area normalize luminaire. Instance will be applied to every space using the blended space type")
|
490
|
-
instances_array << load_inst
|
491
|
-
end
|
492
|
-
|
493
|
-
# electric_equipment
|
494
|
-
source_space_or_space_type.electricEquipment.each do |load_inst|
|
495
|
-
load_def = load_inst.definition.to_ElectricEquipmentDefinition.get
|
496
|
-
if load_def.designLevel.is_initialized
|
497
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
498
|
-
if collection_floor_area == 0
|
499
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
500
|
-
else
|
501
|
-
cloned_load_def = load_def.clone(model).to_ElectricEquipmentDefinition.get
|
502
|
-
orig_design_level = cloned_load_def.designLevel.get
|
503
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
504
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
505
|
-
load_inst.setElectricEquipmentDefinition(cloned_load_def)
|
506
|
-
end
|
507
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
508
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
509
|
-
elsif load_def.wattsperPerson.is_initialized
|
510
|
-
if num_people_ratio.nil?
|
511
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
512
|
-
return false
|
513
|
-
else
|
514
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
515
|
-
end
|
516
|
-
else
|
517
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
518
|
-
return false
|
519
|
-
end
|
520
|
-
load_inst.setSpaceType(target_space_type)
|
521
|
-
instances_array << load_inst
|
522
|
-
end
|
523
|
-
|
524
|
-
# gas_equipment
|
525
|
-
source_space_or_space_type.gasEquipment.each do |load_inst|
|
526
|
-
load_def = load_inst.definition.to_GasEquipmentDefinition.get
|
527
|
-
if load_def.designLevel.is_initialized
|
528
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
529
|
-
if collection_floor_area == 0
|
530
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
531
|
-
else
|
532
|
-
cloned_load_def = load_def.clone(model).to_GasEquipmentDefinition.get
|
533
|
-
orig_design_level = cloned_load_def.designLevel.get
|
534
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
535
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
536
|
-
load_inst.setGasEquipmentDefinition(cloned_load_def)
|
537
|
-
end
|
538
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
539
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
540
|
-
elsif load_def.wattsperPerson.is_initialized
|
541
|
-
if num_people_ratio.nil?
|
542
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
543
|
-
return false
|
544
|
-
else
|
545
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
546
|
-
end
|
547
|
-
else
|
548
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
549
|
-
return false
|
550
|
-
end
|
551
|
-
load_inst.setSpaceType(target_space_type)
|
552
|
-
instances_array << load_inst
|
553
|
-
end
|
554
|
-
|
555
|
-
# hot_water_equipment
|
556
|
-
source_space_or_space_type.hotWaterEquipment.each do |load_inst|
|
557
|
-
load_def = load_inst.definition.to_HotWaterDefinition.get
|
558
|
-
if load_def.designLevel.is_initialized
|
559
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
560
|
-
if collection_floor_area == 0
|
561
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
562
|
-
else
|
563
|
-
cloned_load_def = load_def.clone(model).to_HotWaterEquipmentDefinition.get
|
564
|
-
orig_design_level = cloned_load_def.designLevel.get
|
565
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
566
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
567
|
-
load_inst.setHotWaterEquipmentDefinition(cloned_load_def)
|
568
|
-
end
|
569
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
570
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
571
|
-
elsif load_def.wattsperPerson.is_initialized
|
572
|
-
if num_people_ratio.nil?
|
573
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
574
|
-
return false
|
575
|
-
else
|
576
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
577
|
-
end
|
578
|
-
else
|
579
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
580
|
-
return false
|
581
|
-
end
|
582
|
-
load_inst.setSpaceType(target_space_type)
|
583
|
-
instances_array << load_inst
|
584
|
-
end
|
585
|
-
|
586
|
-
# steam_equipment
|
587
|
-
source_space_or_space_type.steamEquipment.each do |load_inst|
|
588
|
-
load_def = load_inst.definition.to_SteamDefinition.get
|
589
|
-
if load_def.designLevel.is_initialized
|
590
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
591
|
-
if collection_floor_area == 0
|
592
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
593
|
-
else
|
594
|
-
cloned_load_def = load_def.clone(model).to_SteamEquipmentDefinition.get
|
595
|
-
orig_design_level = cloned_load_def.designLevel.get
|
596
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
597
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
598
|
-
load_inst.setSteamEquipmentDefinition(cloned_load_def)
|
599
|
-
end
|
600
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
601
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
602
|
-
elsif load_def.wattsperPerson.is_initialized
|
603
|
-
if num_people_ratio.nil?
|
604
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
605
|
-
return false
|
606
|
-
else
|
607
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
608
|
-
end
|
609
|
-
else
|
610
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
611
|
-
return false
|
612
|
-
end
|
613
|
-
load_inst.setSpaceType(target_space_type)
|
614
|
-
instances_array << load_inst
|
615
|
-
end
|
616
|
-
|
617
|
-
# other_equipment
|
618
|
-
source_space_or_space_type.otherEquipment.each do |load_inst|
|
619
|
-
load_def = load_inst.definition.to_OtherDefinition.get
|
620
|
-
if load_def.designLevel.is_initialized
|
621
|
-
# edit and assign a clone of definition and normalize per area based on floor area ratio
|
622
|
-
if collection_floor_area == 0
|
623
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
624
|
-
else
|
625
|
-
cloned_load_def = load_def.clone(model).to_OtherEquipmentDefinition.get
|
626
|
-
orig_design_level = cloned_load_def.designLevel.get
|
627
|
-
cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
628
|
-
cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
|
629
|
-
load_inst.setOtherEquipmentDefinition(cloned_load_def)
|
630
|
-
end
|
631
|
-
elsif load_def.wattsperSpaceFloorArea.is_initialized
|
632
|
-
load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
|
633
|
-
elsif load_def.wattsperPerson.is_initialized
|
634
|
-
if num_people_ratio.nil?
|
635
|
-
runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
|
636
|
-
return false
|
637
|
-
else
|
638
|
-
load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
|
639
|
-
end
|
640
|
-
else
|
641
|
-
runner.registerError("Unexpected value type for #{load_def.name}")
|
642
|
-
return false
|
643
|
-
end
|
644
|
-
load_inst.setSpaceType(target_space_type)
|
645
|
-
instances_array << load_inst
|
646
|
-
end
|
647
|
-
|
648
|
-
# space_infiltration_design_flow_rates
|
649
|
-
source_space_or_space_type.spaceInfiltrationDesignFlowRates.each do |load_inst|
|
650
|
-
if load_inst.designFlowRateCalculationMethod == 'Flow/Space'
|
651
|
-
# edit load so normalized for building area
|
652
|
-
if collection_floor_area == 0
|
653
|
-
runner.registerWarning("Can't determine building floor area to normalize #{load_def}. #{load_inst} will be asigned the the blended space without altering its values.")
|
654
|
-
else
|
655
|
-
orig_design_level = load_inst.designFlowRate.get
|
656
|
-
load_inst.setFlowperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
|
657
|
-
load_inst.setName("#{load_inst.name} - pre-normalized value was #{orig_design_level} m^3/sec")
|
658
|
-
end
|
659
|
-
elsif load_inst.designFlowRateCalculationMethod == 'Flow/Area'
|
660
|
-
load_inst.setFlowperSpaceFloorArea(load_inst.flowperSpaceFloorArea.get * floor_area_ratio)
|
661
|
-
elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorArea'
|
662
|
-
load_inst.setFlowperExteriorSurfaceArea(load_inst.flowperExteriorSurfaceArea.get * ext_surface_area_ratio)
|
663
|
-
elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorWallArea'
|
664
|
-
load_inst.setFlowperExteriorWallArea(load_inst.flowperExteriorWallArea.get * ext_wall_area_ratio)
|
665
|
-
elsif load_inst.designFlowRateCalculationMethod == 'AirChanges/Hour'
|
666
|
-
load_inst.setAirChangesperHour (load_inst.airChangesperHour.get * volume_ratio)
|
667
|
-
else
|
668
|
-
runner.registerError("Unexpected value type for #{load_inst.name}")
|
669
|
-
return false
|
670
|
-
end
|
671
|
-
load_inst.setSpaceType(target_space_type)
|
672
|
-
instances_array << load_inst
|
673
|
-
end
|
674
|
-
|
675
|
-
# space_infiltration_effective_leakage_areas
|
676
|
-
source_space_or_space_type.spaceInfiltrationEffectiveLeakageAreas.each do |load|
|
677
|
-
# TODO: - can't normalize space_infiltration_effective_leakage_areas. Come up with logic to address this
|
678
|
-
runner.registerWarning("Can't area normalize space_infiltration_effective_leakage_areas. It will be applied to every space using the blended space type")
|
679
|
-
load.setSpaceType(target_space_type)
|
680
|
-
instances_array << load
|
681
|
-
end
|
682
|
-
|
683
|
-
# add OA object if it doesn't already exist
|
684
|
-
if target_space_type.designSpecificationOutdoorAir.is_initialized
|
685
|
-
blended_oa = target_space_type.designSpecificationOutdoorAir.get
|
686
|
-
else
|
687
|
-
blended_oa = OpenStudio::Model::DesignSpecificationOutdoorAir.new(model)
|
688
|
-
blended_oa.setName('Blended OA')
|
689
|
-
blended_oa.setOutdoorAirMethod('Sum')
|
690
|
-
target_space_type.setDesignSpecificationOutdoorAir(blended_oa)
|
691
|
-
instances_array << blended_oa
|
692
|
-
end
|
693
|
-
|
694
|
-
# update OA object
|
695
|
-
if source_space_or_space_type.designSpecificationOutdoorAir.is_initialized
|
696
|
-
oa = source_space_or_space_type.designSpecificationOutdoorAir.get
|
697
|
-
oa_sch = nil
|
698
|
-
if oa.outdoorAirFlowRateFractionSchedule.is_initialized
|
699
|
-
# TODO: - improve logic to address multiple schedules
|
700
|
-
runner.registerWarning("Schedule #{oa.outdoorAirFlowRateFractionSchedule.get.name} assigned to #{oa.name} will be ignored. New OA object will not have a schedule assigned")
|
701
|
-
end
|
702
|
-
if oa.outdoorAirMethod == 'Maximum'
|
703
|
-
# TODO: - see if way to address this by pre-calculating the max and only entering that value for space type
|
704
|
-
runner.registerWarning("Outdoor air method of Maximum will be ignored for #{oa.name}. New OA object will have outdoor air method of Sum.")
|
705
|
-
end
|
706
|
-
# adjusted ratios for oa (lowered for space type if there is hard assigned oa load for one or more spaces)
|
707
|
-
oa_floor_area_ratio = floor_area_ratio
|
708
|
-
oa_num_people_ratio = num_people_ratio
|
709
|
-
if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
|
710
|
-
source_space_or_space_type.spaces.each do |space|
|
711
|
-
if !space.isDesignSpecificationOutdoorAirDefaulted
|
712
|
-
if space_hash.nil?
|
713
|
-
runner.registerWarning('No space_hash passed in and model has OA designed at space level.')
|
714
|
-
else
|
715
|
-
oa_floor_area_ratio -= space_hash[space][:floor_area_ratio]
|
716
|
-
oa_num_people_ratio -= space_hash[space][:num_people_ratio]
|
717
|
-
end
|
718
|
-
end
|
719
|
-
end
|
720
|
-
end
|
721
|
-
# add to values of blended OA load
|
722
|
-
if oa.outdoorAirFlowperPerson > 0
|
723
|
-
blended_oa.setOutdoorAirFlowperPerson(blended_oa.outdoorAirFlowperPerson + oa.outdoorAirFlowperPerson * oa_num_people_ratio)
|
724
|
-
end
|
725
|
-
if oa.outdoorAirFlowperFloorArea > 0
|
726
|
-
blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + oa.outdoorAirFlowperFloorArea * oa_floor_area_ratio)
|
727
|
-
end
|
728
|
-
if oa.outdoorAirFlowRate > 0
|
729
|
-
|
730
|
-
# calculate quantity for instance (doesn't exist as a method in api)
|
731
|
-
if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
|
732
|
-
quantity = 0
|
733
|
-
source_space_or_space_type.spaces.each do |space|
|
734
|
-
if !space.isDesignSpecificationOutdoorAirDefaulted
|
735
|
-
quantity += space.multiplier
|
736
|
-
end
|
737
|
-
end
|
738
|
-
else
|
739
|
-
quantity = source_space_or_space_type.multiplier
|
740
|
-
end
|
741
|
-
|
742
|
-
# can't normalize air flow rate, convert to air flow rate per floor area
|
743
|
-
blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + quantity * oa.outdoorAirFlowRate / collection_floor_area)
|
744
|
-
end
|
745
|
-
if oa.outdoorAirFlowAirChangesperHour > 0
|
746
|
-
# floor area should be good approximation of area for multiplier
|
747
|
-
blended_oa.setOutdoorAirFlowAirChangesperHour(blended_oa.outdoorAirFlowAirChangesperHour + oa.outdoorAirFlowAirChangesperHour * oa_floor_area_ratio)
|
748
|
-
end
|
749
|
-
end
|
750
|
-
|
751
|
-
# note: water_use_equipment can't be assigned to a space type. Leave it as is, if assigned to space type
|
752
|
-
# todo - if we use this measure with new geometry need to find a way to pull water use equipment loads into new model
|
753
|
-
|
754
|
-
return instances_array
|
755
|
-
end
|
756
|
-
|
757
|
-
# sort building stories
|
758
|
-
def sort_building_stories_and_get_min_multiplier(model)
|
759
|
-
sorted_building_stories = {}
|
760
|
-
# loop through stories
|
761
|
-
model.getBuildingStorys.sort.each do |story|
|
762
|
-
story_min_z = nil
|
763
|
-
# loop through spaces in story.
|
764
|
-
story.spaces.sort.each do |space|
|
765
|
-
space_z_min = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).min + space.zOrigin
|
766
|
-
if story_min_z.nil? || (story_min_z > space_z_min)
|
767
|
-
story_min_z = space_z_min
|
768
|
-
end
|
769
|
-
end
|
770
|
-
sorted_building_stories[story] = story_min_z
|
771
|
-
end
|
772
|
-
|
773
|
-
return sorted_building_stories
|
774
|
-
end
|
775
|
-
|
776
|
-
# gather_envelope_data for envelope simplification
|
777
|
-
def gather_envelope_data(runner, model)
|
778
|
-
runner.registerInfo('Gathering envelope data.')
|
779
|
-
|
780
|
-
# hash to contain envelope data
|
781
|
-
envelope_data_hash = {}
|
782
|
-
|
783
|
-
# used for overhang and party wall orientation catigorization
|
784
|
-
facade_options = {
|
785
|
-
'northEast' => 45,
|
786
|
-
'southEast' => 125,
|
787
|
-
'southWest' => 225,
|
788
|
-
'northWest' => 315
|
789
|
-
}
|
790
|
-
|
791
|
-
# get building level inputs
|
792
|
-
envelope_data_hash[:north_axis] = model.getBuilding.northAxis
|
793
|
-
envelope_data_hash[:building_floor_area] = model.getBuilding.floorArea
|
794
|
-
envelope_data_hash[:building_exterior_surface_area] = model.getBuilding.exteriorSurfaceArea
|
795
|
-
envelope_data_hash[:building_exterior_wall_area] = model.getBuilding.exteriorWallArea
|
796
|
-
envelope_data_hash[:building_exterior_roof_area] = envelope_data_hash[:building_exterior_surface_area] - envelope_data_hash[:building_exterior_wall_area]
|
797
|
-
envelope_data_hash[:building_air_volume] = model.getBuilding.airVolume
|
798
|
-
envelope_data_hash[:building_perimeter] = nil # will be applied for first story without ground walls
|
799
|
-
|
800
|
-
# get bounding_box
|
801
|
-
bounding_box = OpenStudio::BoundingBox.new
|
802
|
-
model.getSpaces.sort.each do |space|
|
803
|
-
space.surfaces.sort.each do |spaceSurface|
|
804
|
-
bounding_box.addPoints(space.transformation * spaceSurface.vertices)
|
805
|
-
end
|
806
|
-
end
|
807
|
-
min_x = bounding_box.minX.get
|
808
|
-
min_y = bounding_box.minY.get
|
809
|
-
min_z = bounding_box.minZ.get
|
810
|
-
max_x = bounding_box.maxX.get
|
811
|
-
max_y = bounding_box.maxY.get
|
812
|
-
max_z = bounding_box.maxZ.get
|
813
|
-
envelope_data_hash[:building_min_xyz] = [min_x, min_y, min_z]
|
814
|
-
envelope_data_hash[:building_max_xyz] = [max_x, max_y, max_z]
|
815
|
-
|
816
|
-
# add orientation specific wwr
|
817
|
-
ext_surfaces_hash = OsLib_Geometry.getExteriorWindowAndWllAreaByOrientation(model, model.getSpaces.sort.to_a)
|
818
|
-
envelope_data_hash[:building_wwr_n] = ext_surfaces_hash['northWindow'] / ext_surfaces_hash['northWall']
|
819
|
-
envelope_data_hash[:building_wwr_s] = ext_surfaces_hash['southWindow'] / ext_surfaces_hash['southWall']
|
820
|
-
envelope_data_hash[:building_wwr_e] = ext_surfaces_hash['eastWindow'] / ext_surfaces_hash['eastWall']
|
821
|
-
envelope_data_hash[:building_wwr_w] = ext_surfaces_hash['westWindow'] / ext_surfaces_hash['westWall']
|
822
|
-
envelope_data_hash[:stories] = {} # each entry will be hash with buildingStory as key and attributes has values
|
823
|
-
envelope_data_hash[:space_types] = {} # each entry will be hash with spaceType as key and attributes has values
|
824
|
-
|
825
|
-
# as rough estimate overhang area / glazing area should be close to projection factor assuming overhang is same width as windows
|
826
|
-
# will only add building shading surfaces assoicated with a sub-surface.
|
827
|
-
building_overhang_area_n = 0.0
|
828
|
-
building_overhang_area_s = 0.0
|
829
|
-
building_overhang_area_e = 0.0
|
830
|
-
building_overhang_area_w = 0.0
|
831
|
-
|
832
|
-
# loop through stories based on mine z height of surfaces.
|
833
|
-
sorted_stories = sort_building_stories_and_get_min_multiplier(model).sort_by { |k, v| v }
|
834
|
-
sorted_stories.each do |story, story_min_z|
|
835
|
-
story_min_multiplier = nil
|
836
|
-
story_footprint = nil
|
837
|
-
story_multiplied_floor_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'floorArea')['totalArea']
|
838
|
-
# goal of footprint calc is to count multiplier for hotel room on facade,but not to count what is intended as a story multiplier
|
839
|
-
story_multiplied_exterior_surface_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorArea')['totalArea']
|
840
|
-
story_multiplied_exterior_wall_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorWallArea')['totalArea']
|
841
|
-
story_multiplied_exterior_roof_area = story_multiplied_exterior_surface_area - story_multiplied_exterior_wall_area
|
842
|
-
story_has_ground_walls = []
|
843
|
-
story_has_adiabatic_walls = []
|
844
|
-
story_included_in_building_area = false # will be true if any spaces on story are inclued in building area
|
845
|
-
story_max_z = nil
|
846
|
-
|
847
|
-
# loop through spaces for story gathering information
|
848
|
-
story.spaces.each do |space|
|
849
|
-
# get min multiplier value
|
850
|
-
multiplier = space.multiplier
|
851
|
-
if story_min_multiplier.nil? || (story_min_multiplier > multiplier)
|
852
|
-
story_min_multiplier = multiplier
|
853
|
-
end
|
854
|
-
|
855
|
-
# calculate footprint
|
856
|
-
story_footprint = story_multiplied_floor_area / story_min_multiplier
|
857
|
-
|
858
|
-
# see if part of floor area
|
859
|
-
if space.partofTotalFloorArea
|
860
|
-
story_included_in_building_area = true
|
861
|
-
|
862
|
-
# add to space type ratio hash when space is included in building floor area
|
863
|
-
if space.spaceType.is_initialized
|
864
|
-
space_type = space.spaceType.get
|
865
|
-
space_floor_area = space.floorArea * space.multiplier
|
866
|
-
if envelope_data_hash[:space_types].key?(space_type)
|
867
|
-
envelope_data_hash[:space_types][space_type][:floor_area] += space_floor_area
|
868
|
-
else
|
869
|
-
envelope_data_hash[:space_types][space_type] = {}
|
870
|
-
envelope_data_hash[:space_types][space_type][:floor_area] = space_floor_area
|
871
|
-
|
872
|
-
# make hash for heating and cooling setpoints
|
873
|
-
envelope_data_hash[:space_types][space_type][:htg_setpoint] = {}
|
874
|
-
envelope_data_hash[:space_types][space_type][:clg_setpoint] = {}
|
875
|
-
|
876
|
-
end
|
877
|
-
|
878
|
-
# add heating and cooling setpoints
|
879
|
-
if space.thermalZone.is_initialized && space.thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
|
880
|
-
thermostat = space.thermalZone.get.thermostatSetpointDualSetpoint.get
|
881
|
-
|
882
|
-
# log heating schedule
|
883
|
-
if thermostat.heatingSetpointTemperatureSchedule.is_initialized
|
884
|
-
htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
|
885
|
-
if envelope_data_hash[:space_types][space_type][:htg_setpoint].key?(htg_sch)
|
886
|
-
envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] += space_floor_area
|
887
|
-
else
|
888
|
-
envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] = space_floor_area
|
889
|
-
end
|
890
|
-
else
|
891
|
-
runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
|
892
|
-
end
|
893
|
-
|
894
|
-
# log cooling schedule
|
895
|
-
if thermostat.coolingSetpointTemperatureSchedule.is_initialized
|
896
|
-
clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
|
897
|
-
if envelope_data_hash[:space_types][space_type][:clg_setpoint].key?(clg_sch)
|
898
|
-
envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] += space_floor_area
|
899
|
-
else
|
900
|
-
envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] = space_floor_area
|
901
|
-
end
|
902
|
-
else
|
903
|
-
runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
|
904
|
-
end
|
905
|
-
|
906
|
-
else
|
907
|
-
runner.registerWarning("#{space.name} either isn't in a thermal zone or doesn't have a thermostat assigned")
|
908
|
-
end
|
909
|
-
|
910
|
-
else
|
911
|
-
runner.regsiterWarning("#{space.name} is included in the building floor area but isn't assigned a space type.")
|
912
|
-
end
|
913
|
-
|
914
|
-
end
|
915
|
-
|
916
|
-
# check for walls with adiabatic and ground boundary condition
|
917
|
-
space.surfaces.each do |surface|
|
918
|
-
next if surface.surfaceType != 'Wall'
|
919
|
-
if surface.outsideBoundaryCondition == 'Ground'
|
920
|
-
story_has_ground_walls << surface
|
921
|
-
elsif surface.outsideBoundaryCondition == 'Adiabatic'
|
922
|
-
story_has_adiabatic_walls << surface
|
923
|
-
end
|
924
|
-
end
|
925
|
-
|
926
|
-
# populate overhang values
|
927
|
-
space.surfaces.each do |surface|
|
928
|
-
surface.subSurfaces.each do |sub_surface|
|
929
|
-
sub_surface.shadingSurfaceGroups.each do |shading_surface_group|
|
930
|
-
shading_surface_group.shadingSurfaces.each do |shading_surface|
|
931
|
-
absoluteAzimuth = OpenStudio.convert(sub_surface.azimuth, 'rad', 'deg').get + sub_surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
|
932
|
-
absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
|
933
|
-
# add to hash based on orientation
|
934
|
-
if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East overhang
|
935
|
-
building_overhang_area_e += shading_surface.grossArea * space.multiplier
|
936
|
-
elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South overhang
|
937
|
-
building_overhang_area_s += shading_surface.grossArea * space.multiplier
|
938
|
-
elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West overhang
|
939
|
-
building_overhang_area_w += shading_surface.grossArea * space.multiplier
|
940
|
-
else # North overhang
|
941
|
-
building_overhang_area_n += shading_surface.grossArea * space.multiplier
|
942
|
-
end
|
943
|
-
end
|
944
|
-
end
|
945
|
-
end
|
946
|
-
end
|
947
|
-
|
948
|
-
# get max z
|
949
|
-
space_z_max = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).max + space.zOrigin
|
950
|
-
if story_max_z.nil? || (story_max_z > space_z_max)
|
951
|
-
story_max_z = space_z_max
|
952
|
-
end
|
953
|
-
end
|
954
|
-
|
955
|
-
# populate hash for story data
|
956
|
-
envelope_data_hash[:stories][story] = {}
|
957
|
-
envelope_data_hash[:stories][story][:story_min_height] = story_min_z
|
958
|
-
envelope_data_hash[:stories][story][:story_max_height] = story_max_z
|
959
|
-
envelope_data_hash[:stories][story][:story_min_multiplier] = story_min_multiplier
|
960
|
-
envelope_data_hash[:stories][story][:story_has_ground_walls] = story_has_ground_walls
|
961
|
-
envelope_data_hash[:stories][story][:story_has_adiabatic_walls] = story_has_adiabatic_walls
|
962
|
-
envelope_data_hash[:stories][story][:story_included_in_building_area] = story_included_in_building_area
|
963
|
-
envelope_data_hash[:stories][story][:story_footprint] = story_footprint
|
964
|
-
envelope_data_hash[:stories][story][:story_multiplied_floor_area] = story_multiplied_floor_area
|
965
|
-
envelope_data_hash[:stories][story][:story_exterior_surface_area] = story_multiplied_exterior_surface_area
|
966
|
-
envelope_data_hash[:stories][story][:story_multiplied_exterior_wall_area] = story_multiplied_exterior_wall_area
|
967
|
-
envelope_data_hash[:stories][story][:story_multiplied_exterior_roof_area] = story_multiplied_exterior_roof_area
|
968
|
-
|
969
|
-
# get perimeter and adiabatic walls that appear to be party walls
|
970
|
-
perimeter_and_party_walls = OsLib_Geometry.calculate_story_exterior_wall_perimeter(runner, story, story_min_multiplier, ['Outdoors', 'Ground', 'Adiabatic'], bounding_box)
|
971
|
-
envelope_data_hash[:stories][story][:story_perimeter] = perimeter_and_party_walls[:perimeter]
|
972
|
-
envelope_data_hash[:stories][story][:story_party_walls] = []
|
973
|
-
east = false
|
974
|
-
south = false
|
975
|
-
west = false
|
976
|
-
north = false
|
977
|
-
perimeter_and_party_walls[:party_walls].each do |surface|
|
978
|
-
absoluteAzimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
|
979
|
-
absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
|
980
|
-
|
981
|
-
# add to hash based on orientation (initially added array of sourfaces, but swtiched to just true/false flag)
|
982
|
-
if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East party walls
|
983
|
-
east = true
|
984
|
-
elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South party walls
|
985
|
-
south = true
|
986
|
-
elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West party walls
|
987
|
-
west = true
|
988
|
-
else # North party walls
|
989
|
-
north = true
|
990
|
-
end
|
991
|
-
end
|
992
|
-
|
993
|
-
if east then envelope_data_hash[:stories][story][:story_party_walls] << 'east' end
|
994
|
-
if south then envelope_data_hash[:stories][story][:story_party_walls] << 'south' end
|
995
|
-
if west then envelope_data_hash[:stories][story][:story_party_walls] << 'west' end
|
996
|
-
if north then envelope_data_hash[:stories][story][:story_party_walls] << 'north' end
|
997
|
-
|
998
|
-
# store perimeter from first story that doesn't have ground walls
|
999
|
-
if story_has_ground_walls.empty? && envelope_data_hash[:building_perimeter].nil?
|
1000
|
-
envelope_data_hash[:building_perimeter] = envelope_data_hash[:stories][story][:story_perimeter]
|
1001
|
-
runner.registerInfo(" * #{story.name} is the first above grade story and will be used for the building perimeter.")
|
1002
|
-
end
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
envelope_data_hash[:building_overhang_proj_factor_n] = building_overhang_area_n / ext_surfaces_hash['northWindow']
|
1006
|
-
envelope_data_hash[:building_overhang_proj_factor_s] = building_overhang_area_s / ext_surfaces_hash['southWindow']
|
1007
|
-
envelope_data_hash[:building_overhang_proj_factor_e] = building_overhang_area_e / ext_surfaces_hash['eastWindow']
|
1008
|
-
envelope_data_hash[:building_overhang_proj_factor_w] = building_overhang_area_w / ext_surfaces_hash['westWindow']
|
1009
|
-
|
1010
|
-
# warn for spaces that are not on a story (in future could infer stories for these)
|
1011
|
-
model.getSpaces.sort.each do |space|
|
1012
|
-
if !space.buildingStory.is_initialized
|
1013
|
-
runner.registerWarning("#{space.name} is not on a building story, may have unexpected results.")
|
1014
|
-
end
|
1015
|
-
end
|
1016
|
-
|
1017
|
-
return envelope_data_hash
|
1018
|
-
end
|
1019
|
-
end
|