Module: OsLib_ModelSimplification

Defined in:
lib/openstudio/extension/core/os_lib_model_simplification.rb

Overview

******************************************************************************* OpenStudio®, Copyright © Alliance for Sustainable Energy, LLC. See also openstudio.net/license *******************************************************************************

Instance Method Summary collapse

Instance Method Details

#blend_internal_loads(runner, model, source_space_or_space_type, target_space_type, ratios, collection_floor_area, space_hash) ⇒ Object

blend internal loads used when working from existing model



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 377

def blend_internal_loads(runner, model, source_space_or_space_type, target_space_type, ratios, collection_floor_area, space_hash)
  # ratios
  floor_area_ratio = ratios[:floor_area_ratio]
  num_people_ratio = ratios[:num_people_ratio]
  ext_surface_area_ratio = ratios[:ext_surface_area_ratio]
  ext_wall_area_ratio = ratios[:ext_wall_area_ratio]
  volume_ratio = ratios[:volume_ratio]

  # for normalizing design level loads I need to know effective number of spaces instance is applied to
  if source_space_or_space_type.to_Space.is_initialized
    eff_num_spaces = source_space_or_space_type.multiplier
  else
    eff_num_spaces = 0
    source_space_or_space_type.spaces.each do |space|
      eff_num_spaces += space.multiplier
    end
  end

  # array of load instacnes re-assigned to blended space
  instances_array = []

  # internal_mass
  source_space_or_space_type.internalMass.each do |load_inst|
    load_def = load_inst.definition.to_InternalMassDefinition.get
    if load_def.surfaceArea.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_InternalMass.get
        orig_design_level = cloned_load_def.surfaceArea.get
        cloned_load_def.setSurfaceAreaperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} m^2.")
        load_inst.setInternalMassDefinition(cloned_load_def)
      end
    elsif load_def.surfaceAreaperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.surfaceAreaperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # people
  source_space_or_space_type.people.each do |load_inst|
    load_def = load_inst.definition.to_PeopleDefinition.get
    if load_def.numberofPeople.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_PeopleDefinition.get
        orig_design_level = cloned_load_def.numberofPeople.get
        cloned_load_def.setPeopleperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} people.")
        load_inst.setPeopleDefinition(cloned_load_def)
      end
    elsif load_def.peopleperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.spaceFloorAreaperPerson.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # lights
  source_space_or_space_type.lights.each do |load_inst|
    load_def = load_inst.definition.to_LightsDefinition.get
    if load_def.lightingLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_LightsDefinition.get
        orig_design_level = cloned_load_def.lightingLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setLightsDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # luminaires
  source_space_or_space_type.luminaires.each do |load_inst|
    # TODO: - can't normalize luminaire. Replace it with similar normalized lights def and instance
    runner.registerWarning("Can't area normalize luminaire. Instance will be applied to every space using the blended space type")
    instances_array << load_inst
  end

  # electric_equipment
  source_space_or_space_type.electricEquipment.each do |load_inst|
    load_def = load_inst.definition.to_ElectricEquipmentDefinition.get
    if load_def.designLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_ElectricEquipmentDefinition.get
        orig_design_level = cloned_load_def.designLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setElectricEquipmentDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # gas_equipment
  source_space_or_space_type.gasEquipment.each do |load_inst|
    load_def = load_inst.definition.to_GasEquipmentDefinition.get
    if load_def.designLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_GasEquipmentDefinition.get
        orig_design_level = cloned_load_def.designLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setGasEquipmentDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # hot_water_equipment
  source_space_or_space_type.hotWaterEquipment.each do |load_inst|
    load_def = load_inst.definition.to_HotWaterDefinition.get
    if load_def.designLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_HotWaterEquipmentDefinition.get
        orig_design_level = cloned_load_def.designLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setHotWaterEquipmentDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # steam_equipment
  source_space_or_space_type.steamEquipment.each do |load_inst|
    load_def = load_inst.definition.to_SteamDefinition.get
    if load_def.designLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_SteamEquipmentDefinition.get
        orig_design_level = cloned_load_def.designLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setSteamEquipmentDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # other_equipment
  source_space_or_space_type.otherEquipment.each do |load_inst|
    load_def = load_inst.definition.to_OtherDefinition.get
    if load_def.designLevel.is_initialized
      # edit and assign a clone of definition and normalize per area based on floor area ratio
      if collection_floor_area == 0
        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.")
      else
        cloned_load_def = load_def.clone(model).to_OtherEquipmentDefinition.get
        orig_design_level = cloned_load_def.designLevel.get
        cloned_load_def.setWattsperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        cloned_load_def.setName("#{cloned_load_def.name} - pre-normalized value was #{orig_design_level.round} W.")
        load_inst.setOtherEquipmentDefinition(cloned_load_def)
      end
    elsif load_def.wattsperSpaceFloorArea.is_initialized
      load_inst.setMultiplier(load_inst.multiplier * floor_area_ratio)
    elsif load_def.wattsperPerson.is_initialized
      if num_people_ratio.nil?
        runner.registerError("#{load_def} has value defined per person, but people ratio wasn't passed in")
        return false
      else
        load_inst.setMultiplier(load_inst.multiplier * num_people_ratio)
      end
    else
      runner.registerError("Unexpected value type for #{load_def.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # space_infiltration_design_flow_rates
  source_space_or_space_type.spaceInfiltrationDesignFlowRates.each do |load_inst|
    if load_inst.designFlowRateCalculationMethod == 'Flow/Space'
      # edit load so normalized for building area
      if collection_floor_area == 0
        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.")
      else
        orig_design_level = load_inst.designFlowRate.get
        load_inst.setFlowperSpaceFloorArea(eff_num_spaces * orig_design_level / collection_floor_area)
        load_inst.setName("#{load_inst.name} -  pre-normalized value was #{orig_design_level} m^3/sec")
      end
    elsif load_inst.designFlowRateCalculationMethod == 'Flow/Area'
      load_inst.setFlowperSpaceFloorArea(load_inst.flowperSpaceFloorArea.get * floor_area_ratio)
    elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorArea'
      load_inst.setFlowperExteriorSurfaceArea(load_inst.flowperExteriorSurfaceArea.get * ext_surface_area_ratio)
    elsif load_inst.designFlowRateCalculationMethod == 'Flow/ExteriorWallArea'
      load_inst.setFlowperExteriorWallArea(load_inst.flowperExteriorWallArea.get * ext_wall_area_ratio)
    elsif load_inst.designFlowRateCalculationMethod == 'AirChanges/Hour'
      load_inst.setAirChangesperHour (load_inst.airChangesperHour.get * volume_ratio)
    else
      runner.registerError("Unexpected value type for #{load_inst.name}")
      return false
    end
    load_inst.setSpaceType(target_space_type)
    instances_array << load_inst
  end

  # space_infiltration_effective_leakage_areas
  source_space_or_space_type.spaceInfiltrationEffectiveLeakageAreas.each do |load|
    # TODO: - can't normalize space_infiltration_effective_leakage_areas. Come up with logic to address this
    runner.registerWarning("Can't area normalize space_infiltration_effective_leakage_areas. It will be applied to every space using the blended space type")
    load.setSpaceType(target_space_type)
    instances_array << load
  end

  # add OA object if it doesn't already exist
  if target_space_type.designSpecificationOutdoorAir.is_initialized
    blended_oa = target_space_type.designSpecificationOutdoorAir.get
  else
    blended_oa = OpenStudio::Model::DesignSpecificationOutdoorAir.new(model)
    blended_oa.setName('Blended OA')
    blended_oa.setOutdoorAirMethod('Sum')
    target_space_type.setDesignSpecificationOutdoorAir(blended_oa)
    instances_array << blended_oa
  end

  # update OA object
  if source_space_or_space_type.designSpecificationOutdoorAir.is_initialized
    oa = source_space_or_space_type.designSpecificationOutdoorAir.get
    oa_sch = nil
    if oa.outdoorAirFlowRateFractionSchedule.is_initialized
      # TODO: - improve logic to address multiple schedules
      runner.registerWarning("Schedule #{oa.outdoorAirFlowRateFractionSchedule.get.name} assigned to #{oa.name} will be ignored. New OA object will not have a schedule assigned")
    end
    if oa.outdoorAirMethod == 'Maximum'
      # TODO: - see if way to address this by pre-calculating the max and only entering that value for space type
      runner.registerWarning("Outdoor air method of Maximum will be ignored for #{oa.name}. New OA object will have outdoor air method of Sum.")
    end
    # adjusted ratios for oa (lowered for space type if there is hard assigned oa load for one or more spaces)
    oa_floor_area_ratio = floor_area_ratio
    oa_num_people_ratio = num_people_ratio
    if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
      source_space_or_space_type.spaces.each do |space|
        if !space.isDesignSpecificationOutdoorAirDefaulted
          if space_hash.nil?
            runner.registerWarning('No space_hash passed in and model has OA designed at space level.')
          else
            oa_floor_area_ratio -= space_hash[space][:floor_area_ratio]
            oa_num_people_ratio -= space_hash[space][:num_people_ratio]
          end
        end
      end
    end
    # add to values of blended OA load
    if oa.outdoorAirFlowperPerson > 0
      blended_oa.setOutdoorAirFlowperPerson(blended_oa.outdoorAirFlowperPerson + oa.outdoorAirFlowperPerson * oa_num_people_ratio)
    end
    if oa.outdoorAirFlowperFloorArea > 0
      blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + oa.outdoorAirFlowperFloorArea * oa_floor_area_ratio)
    end
    if oa.outdoorAirFlowRate > 0

      # calculate quantity for instance (doesn't exist as a method in api)
      if source_space_or_space_type.class.to_s == 'OpenStudio::Model::SpaceType'
        quantity = 0
        source_space_or_space_type.spaces.each do |space|
          if !space.isDesignSpecificationOutdoorAirDefaulted
            quantity += space.multiplier
          end
        end
      else
        quantity = source_space_or_space_type.multiplier
      end

      # can't normalize air flow rate, convert to air flow rate per floor area
      blended_oa.setOutdoorAirFlowperFloorArea(blended_oa.outdoorAirFlowperFloorArea + quantity * oa.outdoorAirFlowRate / collection_floor_area)
    end
    if oa.outdoorAirFlowAirChangesperHour > 0
      # floor area should be good approximation of area for multiplier
      blended_oa.setOutdoorAirFlowAirChangesperHour(blended_oa.outdoorAirFlowAirChangesperHour + oa.outdoorAirFlowAirChangesperHour * oa_floor_area_ratio)
    end
  end

  # note: water_use_equipment can't be assigned to a space type. Leave it as is, if assigned to space type
  # todo - if we use this measure with new geometry need to find a way to pull water use equipment loads into new model

  return instances_array
end

#blend_space_type_collections(runner, model, space_type_hash) ⇒ Object

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 If key for any collection is “Building” it will also opererate on spaces that don’t have space type assigned 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 load instances are maintained so that they can haave unique schedules, and can have EE measures selectivly applied.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 117

def blend_space_type_collections(runner, model, space_type_hash)
  # loop through building type hash to create multiple blends
  space_type_hash.each do |collection_name, space_types|
    if collection_name == 'Building'
      space_array = model.getSpaces.sort # use all space types, not just space types passed in
    else
      space_array = []
      space_types.each do |space_type|
        space_array.concat(space_type.spaces)
      end
    end

    # calculate metrics for all spaces included in building area to pass into space_type and space hash
    # note: in the future this may be a subset of spaces if blending into multiple space types vs. just one.
    collection_totals = {}
    collection_totals[:floor_area] = 0.0
    collection_totals[:num_people] = 0.0
    collection_totals[:ext_surface_area] = 0.0
    collection_totals[:ext_wall_area] = 0.0
    collection_totals[:volume] = 0.0
    space_array.each do |space|
      next if !space.partofTotalFloorArea
      collection_totals[:floor_area] += space.floorArea * space.multiplier
      collection_totals[:num_people] += space.numberOfPeople * space.multiplier
      collection_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
      collection_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
      collection_totals[:volume] += space.volume * space.multiplier
    end
    area_ip = OpenStudio.convert(collection_totals[:floor_area], 'm^2', 'ft^2').get
    area_ip_neat = OpenStudio.toNeatString(area_ip, 2, true)
    runner.registerInfo("#{collection_name} area is #{area_ip_neat} ft^2, number of people is #{collection_totals[:num_people].round(0)}.")

    # create hash of space types and floor area for all space types with area > 0 when spaces included in floor area
    # code to gather space type areas came from openstudio_results measure.
    space_type_hash = {}
    largest_space_type = nil
    largest_space_type_ratio = 0.00
    space_types.each do |space_type|
      next if space_type.floorArea == 0
      space_type_totals = {}
      space_type_totals[:floor_area] = 0.0
      space_type_totals[:num_people] = 0.0
      space_type_totals[:ext_surface_area] = 0.0
      space_type_totals[:ext_wall_area] = 0.0
      space_type_totals[:volume] = 0.0
      # loop through spaces so I can skip if not included in floor area
      space_type.spaces.each do |space|
        next if !space.partofTotalFloorArea
        space_type_totals[:floor_area] += space.floorArea * space.multiplier
        space_type_totals[:num_people] += space.numberOfPeople * space.multiplier
        space_type_totals[:ext_surface_area] += space.exteriorArea * space.multiplier
        space_type_totals[:ext_wall_area] += space.exteriorWallArea * space.multiplier
        space_type_totals[:volume] += space.volume * space.multiplier
      end

      # update largest space type values
      if largest_space_type.nil?
        largest_space_type = space_type
        largest_space_type_ratio = space_type_totals[:floor_area]
      elsif space_type_totals[:floor_area] > largest_space_type_ratio
        largest_space_type = space_type
        largest_space_type_ratio = space_type_totals[:floor_area]
      end

      # gather internal loads
      space_type_loads_hash = gather_internal_loads(space_type)

      # don't add to hash if no spaces used for space type are included in building area (e.g. plenum and attic)
      # todo - log these and decide what to do for them. Leave loads alone or remove, do they add to blend at all?
      next if space_type_totals[:floor_area] == 0

      if !space_type_totals[:floor_area] = space_type.floorArea # TODO: - not sure if these would ever show as different
        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")
      end

      # populate space type hash
      space_type_hash[space_type] = { int_loads: space_type_loads_hash, totals: space_type_totals }
    end

    # report initial condition of model
    runner.registerInfo("#{collection_name} accounts for #{space_type_hash.size} space types.")

    if collection_name == 'Building'
      # count area of spaces that have no space type
      no_space_type_area_counter = 0
      model.getSpaces.sort.each do |space|
        if space.spaceType.empty?
          next if !space.partofTotalFloorArea
          no_space_type_area_counter += space.floorArea * space.multiplier
        end
      end
      floor_area_ratio = no_space_type_area_counter / collection_totals[:floor_area]
      if floor_area_ratio > 0
        runner.registerInfo("#{floor_area_ratio} fraction of building area is composed of spaces without space type assignments.")
      end
    end

    # report the space ratio for hard spaces
    space_hash = {}
    space_array.each do |space|
      next if !space.partofTotalFloorArea
      space_loads_hash = gather_internal_loads(space)
      space_totals = {}
      space_totals[:floor_area] = space.floorArea * space.multiplier
      space_totals[:num_people] = space.numberOfPeople * space.multiplier
      space_totals[:ext_surface_area] = space.exteriorArea * space.multiplier
      space_totals[:ext_wall_area] = space.exteriorWallArea * space.multiplier
      space_totals[:volume] = space.volume * space.multiplier
      if !space_loads_hash[:daylighting_controls].empty?
        runner.registerWarning("#{space.name} has one or more daylighting controls. Lighting loads from blended space type may affect lighting reduction from daylighting controls.")
      end
      if !space_loads_hash[:water_use_equipment].empty?
        runner.registerInfo("One ore more water use equipment objects are associated with space #{space.name}. This can't be moved to a space type.")
      end
      # note: If generating ratios without geometry can calculate people_ratio given space_types floor_area_ratio
      space_hash[space] = { int_loads: space_loads_hash, totals: space_totals }
    end

    # create stub blended space type
    blended_space_type = OpenStudio::Model::SpaceType.new(model)
    blended_space_type.setName("#{collection_name} Blended Space Type")

    # set standards info for space type based on largest ratio (for use to apply HVAC system)
    standards_building_type = largest_space_type.standardsBuildingType
    standards_space_type = largest_space_type.standardsSpaceType
    if standards_building_type.is_initialized
      blended_space_type.setStandardsBuildingType(standards_building_type.get)
    end
    if standards_space_type.is_initialized
      blended_space_type.setStandardsSpaceType(standards_space_type.get)
    end

    # values from collection hash
    collection_floor_area = collection_totals[:floor_area]
    collection_num_people = collection_totals[:num_people]
    collection_ext_surface_area = collection_totals[:ext_surface_area]
    collection_ext_wall_area = collection_totals[:ext_wall_area]
    collection_volume = collection_totals[:volume]

    # loop through space that have one or more spaces included in the building area
    space_type_hash.each do |space_type, hash|
      # hard assign space load schedules before re-assign instances to blended space type
      space_type.hardApplySpaceLoadSchedules

      # vaules from space or space_type
      floor_area = hash[:totals][:floor_area]
      num_people = hash[:totals][:num_people]
      ext_surface_area = hash[:totals][:ext_surface_area]
      ext_wall_area = hash[:totals][:ext_wall_area]
      volume = hash[:totals][:volume]

      # ratios
      ratios = {}
      if collection_floor_area > 0
        ratios[:floor_area_ratio] = floor_area / collection_floor_area
      else
        ratios[:floor_area_ratio] = 0.0
      end
      if collection_num_people > 0
        ratios[:num_people_ratio] = num_people / collection_num_people
      else
        ratios[:num_people_ratio] = 0.0
      end
      if collection_ext_surface_area > 0
        ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
      else
        ratios[:ext_surface_area_ratio] = 0.0
      end
      if collection_ext_wall_area > 0
        ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
      else
        ratios[:ext_wall_area_ratio] = 0.0
      end
      if collection_volume > 0
        ratios[:volume_ratio] = volume / collection_volume
      else
        ratios[:volume_ratio] = 0.0
      end

      # populate blended space type with space type loads
      space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, collection_floor_area, space_hash)
      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)}")

      # hard assign any constructions assigned by space types, except for space not included in the building area
      if space_type.defaultConstructionSet.is_initialized
        runner.registerInfo("Hard assigning constructions for #{space_type.name}.")
        space_type.spaces.each(&:hardApplyConstructions)
      end

      # remove all space type assignments, except for spaces not included in building area.
      space_type.spaces.each do |space|
        next if !space.partofTotalFloorArea
        space.resetSpaceType
      end

      # delete space type. Don't want to leave in model since internal loads  have been removed from it
      space_type.remove
    end

    # loop through spaces that are included in building area
    space_hash.each do |space, hash|
      # hard assign space load schedules before re-assign instances to blended space type
      space.hardApplySpaceLoadSchedules

      # vaules from space or space_type
      floor_area = hash[:totals][:floor_area]
      num_people = hash[:totals][:num_people]
      ext_surface_area = hash[:totals][:ext_surface_area]
      ext_wall_area = hash[:totals][:ext_wall_area]
      volume = hash[:totals][:volume]

      # ratios
      ratios = {}
      if collection_floor_area > 0
        ratios[:floor_area_ratio] = floor_area / collection_floor_area
      else
        ratios[:floor_area_ratio] = 0.0
      end
      if collection_num_people > 0
        ratios[:num_people_ratio] = num_people / collection_num_people
      else
        ratios[:num_people_ratio] = 0.0
      end
      if collection_ext_surface_area > 0
        ratios[:ext_surface_area_ratio] = ext_surface_area / collection_ext_surface_area
      else
        ratios[:ext_surface_area_ratio] = 0.0
      end
      if collection_ext_wall_area > 0
        ratios[:ext_wall_area_ratio] = ext_wall_area / collection_ext_wall_area
      else
        ratios[:ext_wall_area_ratio] = 0.0
      end
      if collection_volume > 0
        ratios[:volume_ratio] = volume / collection_volume
      else
        ratios[:volume_ratio] = 0.0
      end

      # populate blended space type with space loads
      space_load_instances = blend_internal_loads(runner, model, space, blended_space_type, ratios, collection_floor_area, space_hash)
      next if space_load_instances.empty?
      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)}")
    end

    if collection_name == 'Building'
      # assign blended space type to building
      model.getBuilding.setSpaceType(blended_space_type)
      building_space_type = model.getBuilding.spaceType
    else
      space_array.each do |space|
        space.setSpaceType(blended_space_type)
      end
    end
  end

  return model.getSpaceTypes.sort
end

#blend_space_types_from_floor_area_ratio(runner, model, space_type_ratio_hash) ⇒ Object

blend_space_types_from_floor_area_ratio used when working from space type ratio and un-assigned space types



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 39

def blend_space_types_from_floor_area_ratio(runner, model, space_type_ratio_hash)
  # create stub blended space type
  blended_space_type = OpenStudio::Model::SpaceType.new(model)
  blended_space_type.setName('Blended Space Type')

  # TODO: - inspect people instances and see if any defs are not normalized per area. If find any issue warning

  # gather inputs
  sum_of_num_people_per_m_2 = 0.0
  space_type_ratio_hash.each do |space_type, ratios|
    # get number of peple per m 2 for space type. Can do this without looking at instances
    sum_of_num_people_per_m_2 += space_type.getPeoplePerFloorArea(1.0)
  end

  # raw num_people_ratios
  sum_area_adj_num_people_ratio = 0.0
  space_type_ratio_hash.each do |space_type, ratios|
    # calculate num_people_ratios
    area_adj_num_people_ratio  = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
    sum_area_adj_num_people_ratio += area_adj_num_people_ratio
  end

  # set ratios
  largest_space_type = nil
  largest_space_type_ratio = 0.00
  space_type_ratio_hash.each do |space_type, ratios|
    # calculate num_people_ratios
    area_adj_num_people_ratio = (space_type.getPeoplePerFloorArea(1.0) / sum_of_num_people_per_m_2) * ratios[:floor_area_ratio]
    normalized_area_adj_num_people_ratio = area_adj_num_people_ratio / sum_area_adj_num_people_ratio

    # ratios[:floor_area_ratio] is already defined
    ratios[:num_people_ratio] = normalized_area_adj_num_people_ratio.round(4)
    ratios[:ext_surface_area_ratio] = ratios[:floor_area_ratio]
    ratios[:ext_wall_area_ratio] = ratios[:floor_area_ratio]
    ratios[:volume_ratio] = ratios[:floor_area_ratio]

    # update largest space type values
    if largest_space_type.nil?
      largest_space_type = space_type
      largest_space_type_ratio = ratios[:floor_area_ratio]
    elsif ratios[:floor_area_ratio] > largest_space_type_ratio
      largest_space_type = space_type
      largest_space_type_ratio = ratios[:floor_area_ratio]
    end
  end

  if largest_space_type.nil?
    runner.registerError("Didn't find any space types in model matching user argument string.")
    return nil
  end

  # set standards info for space type based on largest ratio (for use to apply HVAC system)
  standards_building_type = largest_space_type.standardsBuildingType
  standards_space_type = largest_space_type.standardsSpaceType
  if standards_building_type.is_initialized
    blended_space_type.setStandardsBuildingType(standards_building_type.get)
  end
  if standards_space_type.is_initialized
    blended_space_type.setStandardsSpaceType(standards_space_type.get)
  end

  # loop therough space types to get instances from and then remove
  space_type_ratio_hash.each do |space_type, ratios|
    # blend internal loads (nil is space_hash)
    space_type_load_instances = blend_internal_loads(runner, model, space_type, blended_space_type, ratios, model.getBuilding.floorArea, nil)
    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]}.")

    # delete space type. Don't want to leave in model since internal loads  have been removed from it
    space_type.remove
  end

  return blended_space_type
end

#gather_envelope_data(runner, model) ⇒ Object

gather_envelope_data for envelope simplification



777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 777

def gather_envelope_data(runner, model)
  runner.registerInfo('Gathering envelope data.')

  # hash to contain envelope data
  envelope_data_hash = {}

  # used for overhang and party wall orientation catigorization
  facade_options = {
    'northEast' => 45,
    'southEast' => 125,
    'southWest' => 225,
    'northWest' => 315
  }

  # get building level inputs
  envelope_data_hash[:north_axis] = model.getBuilding.northAxis
  envelope_data_hash[:building_floor_area] = model.getBuilding.floorArea
  envelope_data_hash[:building_exterior_surface_area] = model.getBuilding.exteriorSurfaceArea
  envelope_data_hash[:building_exterior_wall_area] = model.getBuilding.exteriorWallArea
  envelope_data_hash[:building_exterior_roof_area] = envelope_data_hash[:building_exterior_surface_area] - envelope_data_hash[:building_exterior_wall_area]
  envelope_data_hash[:building_air_volume] = model.getBuilding.airVolume
  envelope_data_hash[:building_perimeter] = nil # will be applied for first story without ground walls

  # get bounding_box
  bounding_box = OpenStudio::BoundingBox.new
  model.getSpaces.sort.each do |space|
    space.surfaces.sort.each do |spaceSurface|
      bounding_box.addPoints(space.transformation * spaceSurface.vertices)
    end
  end
  min_x = bounding_box.minX.get
  min_y = bounding_box.minY.get
  min_z = bounding_box.minZ.get
  max_x = bounding_box.maxX.get
  max_y = bounding_box.maxY.get
  max_z = bounding_box.maxZ.get
  envelope_data_hash[:building_min_xyz] = [min_x, min_y, min_z]
  envelope_data_hash[:building_max_xyz] = [max_x, max_y, max_z]

  # add orientation specific wwr
  ext_surfaces_hash = OsLib_Geometry.getExteriorWindowAndWllAreaByOrientation(model, model.getSpaces.sort.to_a)
  envelope_data_hash[:building_wwr_n] = ext_surfaces_hash['northWindow'] / ext_surfaces_hash['northWall']
  envelope_data_hash[:building_wwr_s] = ext_surfaces_hash['southWindow'] / ext_surfaces_hash['southWall']
  envelope_data_hash[:building_wwr_e] = ext_surfaces_hash['eastWindow'] / ext_surfaces_hash['eastWall']
  envelope_data_hash[:building_wwr_w] = ext_surfaces_hash['westWindow'] / ext_surfaces_hash['westWall']
  envelope_data_hash[:stories] = {} # each entry will be hash with buildingStory as key and attributes has values
  envelope_data_hash[:space_types] = {} # each entry will be hash with spaceType as key and attributes has values

  # as rough estimate overhang area / glazing area should be close to projection factor assuming overhang is same width as windows
  # will only add building shading surfaces assoicated with a sub-surface.
  building_overhang_area_n = 0.0
  building_overhang_area_s = 0.0
  building_overhang_area_e = 0.0
  building_overhang_area_w = 0.0

  # loop through stories based on mine z height of surfaces.
  sorted_stories = sort_building_stories_and_get_min_multiplier(model).sort_by { |k, v| v }
  sorted_stories.each do |story, story_min_z|
    story_min_multiplier = nil
    story_footprint = nil
    story_multiplied_floor_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'floorArea')['totalArea']
    # goal of footprint calc is to count multiplier for hotel room on facade,but not to count what is intended as a story multiplier
    story_multiplied_exterior_surface_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorArea')['totalArea']
    story_multiplied_exterior_wall_area = OsLib_HelperMethods.getAreaOfSpacesInArray(model, story.spaces, 'exteriorWallArea')['totalArea']
    story_multiplied_exterior_roof_area = story_multiplied_exterior_surface_area - story_multiplied_exterior_wall_area
    story_has_ground_walls = []
    story_has_adiabatic_walls = []
    story_included_in_building_area = false # will be true if any spaces on story are inclued in building area
    story_max_z = nil

    # loop through spaces for story gathering information
    story.spaces.each do |space|
      # get min multiplier value
      multiplier = space.multiplier
      if story_min_multiplier.nil? || (story_min_multiplier > multiplier)
        story_min_multiplier = multiplier
      end

      # calculate footprint
      story_footprint = story_multiplied_floor_area / story_min_multiplier

      # see if part of floor area
      if space.partofTotalFloorArea
        story_included_in_building_area = true

        # add to space type ratio hash when space is included in building floor area
        if space.spaceType.is_initialized
          space_type = space.spaceType.get
          space_floor_area = space.floorArea * space.multiplier
          if envelope_data_hash[:space_types].key?(space_type)
            envelope_data_hash[:space_types][space_type][:floor_area] += space_floor_area
          else
            envelope_data_hash[:space_types][space_type] = {}
            envelope_data_hash[:space_types][space_type][:floor_area] = space_floor_area

            # make hash for heating and cooling setpoints
            envelope_data_hash[:space_types][space_type][:htg_setpoint] = {}
            envelope_data_hash[:space_types][space_type][:clg_setpoint] = {}

          end

          # add heating and cooling setpoints
          if space.thermalZone.is_initialized && space.thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
            thermostat = space.thermalZone.get.thermostatSetpointDualSetpoint.get

            # log heating schedule
            if thermostat.heatingSetpointTemperatureSchedule.is_initialized
              htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
              if envelope_data_hash[:space_types][space_type][:htg_setpoint].key?(htg_sch)
                envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] += space_floor_area
              else
                envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] = space_floor_area
              end
            else
              runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
            end

            # log cooling schedule
            if thermostat.coolingSetpointTemperatureSchedule.is_initialized
              clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
              if envelope_data_hash[:space_types][space_type][:clg_setpoint].key?(clg_sch)
                envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] += space_floor_area
              else
                envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] = space_floor_area
              end
            else
              runner.registerWarning("#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
            end

          else
            runner.registerWarning("#{space.name} either isn't in a thermal zone or doesn't have a thermostat assigned")
          end

        else
          runner.regsiterWarning("#{space.name} is included in the building floor area but isn't assigned a space type.")
        end

      end

      # check for walls with adiabatic and ground boundary condition
      space.surfaces.each do |surface|
        next if surface.surfaceType != 'Wall'
        if surface.outsideBoundaryCondition == 'Ground'
          story_has_ground_walls << surface
        elsif surface.outsideBoundaryCondition == 'Adiabatic'
          story_has_adiabatic_walls << surface
        end
      end

      # populate overhang values
      space.surfaces.each do |surface|
        surface.subSurfaces.each do |sub_surface|
          sub_surface.shadingSurfaceGroups.each do |shading_surface_group|
            shading_surface_group.shadingSurfaces.each do |shading_surface|
              absoluteAzimuth = OpenStudio.convert(sub_surface.azimuth, 'rad', 'deg').get + sub_surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
              absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
              # add to hash based on orientation
              if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East overhang
                building_overhang_area_e += shading_surface.grossArea * space.multiplier
              elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South overhang
                building_overhang_area_s += shading_surface.grossArea * space.multiplier
              elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West overhang
                building_overhang_area_w += shading_surface.grossArea * space.multiplier
              else # North overhang
                building_overhang_area_n += shading_surface.grossArea * space.multiplier
              end
            end
          end
        end
      end

      # get max z
      space_z_max = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).max + space.zOrigin
      if story_max_z.nil? || (story_max_z > space_z_max)
        story_max_z = space_z_max
      end
    end

    # populate hash for story data
    envelope_data_hash[:stories][story] = {}
    envelope_data_hash[:stories][story][:story_min_height] = story_min_z
    envelope_data_hash[:stories][story][:story_max_height] = story_max_z
    envelope_data_hash[:stories][story][:story_min_multiplier] = story_min_multiplier
    envelope_data_hash[:stories][story][:story_has_ground_walls] = story_has_ground_walls
    envelope_data_hash[:stories][story][:story_has_adiabatic_walls] = story_has_adiabatic_walls
    envelope_data_hash[:stories][story][:story_included_in_building_area] = story_included_in_building_area
    envelope_data_hash[:stories][story][:story_footprint] = story_footprint
    envelope_data_hash[:stories][story][:story_multiplied_floor_area] = story_multiplied_floor_area
    envelope_data_hash[:stories][story][:story_exterior_surface_area] = story_multiplied_exterior_surface_area
    envelope_data_hash[:stories][story][:story_multiplied_exterior_wall_area] = story_multiplied_exterior_wall_area
    envelope_data_hash[:stories][story][:story_multiplied_exterior_roof_area] = story_multiplied_exterior_roof_area

    # get perimeter and adiabatic walls that appear to be party walls
    perimeter_and_party_walls = OsLib_Geometry.calculate_story_exterior_wall_perimeter(runner, story, story_min_multiplier, ['Outdoors', 'Ground', 'Adiabatic'], bounding_box)
    envelope_data_hash[:stories][story][:story_perimeter] = perimeter_and_party_walls[:perimeter]
    envelope_data_hash[:stories][story][:story_party_walls] = []
    east = false
    south = false
    west = false
    north = false
    perimeter_and_party_walls[:party_walls].each do |surface|
      absoluteAzimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
      absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0

      # add to hash based on orientation (initially added array of sourfaces, but swtiched to just true/false flag)
      if (facade_options['northEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southEast']) # East party walls
        east = true
      elsif (facade_options['southEast'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['southWest']) # South party walls
        south = true
      elsif (facade_options['southWest'] <= absoluteAzimuth) && (absoluteAzimuth < facade_options['northWest']) # West party walls
        west = true
      else # North party walls
        north = true
      end
    end

    if east then envelope_data_hash[:stories][story][:story_party_walls] << 'east' end
    if south then envelope_data_hash[:stories][story][:story_party_walls] << 'south' end
    if west then envelope_data_hash[:stories][story][:story_party_walls] << 'west' end
    if north then envelope_data_hash[:stories][story][:story_party_walls] << 'north' end

    # store perimeter from first story that doesn't have ground walls
    if story_has_ground_walls.empty? && envelope_data_hash[:building_perimeter].nil?
      envelope_data_hash[:building_perimeter] = envelope_data_hash[:stories][story][:story_perimeter]
      runner.registerInfo(" * #{story.name} is the first above grade story and will be used for the building perimeter.")
    end
  end

  envelope_data_hash[:building_overhang_proj_factor_n] = building_overhang_area_n / ext_surfaces_hash['northWindow']
  envelope_data_hash[:building_overhang_proj_factor_s] = building_overhang_area_s / ext_surfaces_hash['southWindow']
  envelope_data_hash[:building_overhang_proj_factor_e] = building_overhang_area_e / ext_surfaces_hash['eastWindow']
  envelope_data_hash[:building_overhang_proj_factor_w] = building_overhang_area_w / ext_surfaces_hash['westWindow']

  # warn for spaces that are not on a story (in future could infer stories for these)
  model.getSpaces.sort.each do |space|
    if !space.buildingStory.is_initialized
      runner.registerWarning("#{space.name} is not on a building story, may have unexpected results.")
    end
  end

  return envelope_data_hash
end

#gather_internal_loads(space_or_space_type) ⇒ Object

get all loads for a space_or_space_type and place in hash by type



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 8

def gather_internal_loads(space_or_space_type)
  internal_load_hash = {}

  # gather different load types (all vectors except dsoa which will be turned into an array)
  internal_load_hash[:internal_mass] = space_or_space_type.internalMass
  internal_load_hash[:people] = space_or_space_type.people
  internal_load_hash[:lights] = space_or_space_type.lights
  internal_load_hash[:luminaires] = space_or_space_type.luminaires
  internal_load_hash[:electric_equipment] = space_or_space_type.electricEquipment
  internal_load_hash[:gas_equipment] = space_or_space_type.gasEquipment
  internal_load_hash[:hot_water_equipment] = space_or_space_type.hotWaterEquipment
  internal_load_hash[:steam_equipment] = space_or_space_type.steamEquipment
  internal_load_hash[:other_equipment] = space_or_space_type.otherEquipment
  internal_load_hash[:space_infiltration_design_flow_rates] = space_or_space_type.spaceInfiltrationDesignFlowRates
  internal_load_hash[:space_infiltration_effective_leakage_areas] = space_or_space_type.spaceInfiltrationEffectiveLeakageAreas
  if space_or_space_type.designSpecificationOutdoorAir.nil?
    internal_load_hash[:design_specification_outdoor_air] = []
  else
    internal_load_hash[:design_specification_outdoor_air] = [space_or_space_type.designSpecificationOutdoorAir]
  end
  if space_or_space_type.class.to_s == 'OpenStudio::Model::Space'
    internal_load_hash[:water_use_equipment] = space_or_space_type.waterUseEquipment # don't think this reports
    internal_load_hash[:daylighting_controls] = space_or_space_type.daylightingControls
  end

  # TODO: - warn if daylighting controls in spaces (should I alter fraction controled based on lighting per area ratio)

  return internal_load_hash
end

#sort_building_stories_and_get_min_multiplier(model) ⇒ Object

sort building stories



758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
# File 'lib/openstudio/extension/core/os_lib_model_simplification.rb', line 758

def sort_building_stories_and_get_min_multiplier(model)
  sorted_building_stories = {}
  # loop through stories
  model.getBuildingStorys.sort.each do |story|
    story_min_z = nil
    # loop through spaces in story.
    story.spaces.sort.each do |space|
      space_z_min = OsLib_Geometry.getSurfaceZValues(space.surfaces.to_a).min + space.zOrigin
      if story_min_z.nil? || (story_min_z > space_z_min)
        story_min_z = space_z_min
      end
    end
    sorted_building_stories[story] = story_min_z
  end

  return sorted_building_stories
end