openstudio-standards 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards.xlsx +0 -0
  3. data/data/standards/OpenStudio_Standards_climate_zone_sets.json +96 -0
  4. data/data/standards/OpenStudio_Standards_climate_zones.json +96 -0
  5. data/data/standards/OpenStudio_Standards_construction_properties.json +11424 -0
  6. data/data/standards/OpenStudio_Standards_construction_sets.json +1746 -24
  7. data/data/standards/OpenStudio_Standards_constructions.json +409 -0
  8. data/data/standards/OpenStudio_Standards_curve_biquadratics.json +160 -0
  9. data/data/standards/OpenStudio_Standards_curve_quadratics.json +165 -0
  10. data/data/standards/OpenStudio_Standards_entryways.json +191 -0
  11. data/data/standards/OpenStudio_Standards_exterior_lighting.json +964 -0
  12. data/data/standards/OpenStudio_Standards_exterior_lighting_assumptions.json +191 -0
  13. data/data/standards/OpenStudio_Standards_ground_temperatures.json +11424 -0
  14. data/data/standards/OpenStudio_Standards_illuminated_parking_area.json +157 -0
  15. data/data/standards/OpenStudio_Standards_materials.json +2539 -745
  16. data/data/standards/OpenStudio_Standards_motors.json +420 -0
  17. data/data/standards/OpenStudio_Standards_parking.json +157 -0
  18. data/data/standards/OpenStudio_Standards_prototype_inputs.json +4410 -10
  19. data/data/standards/OpenStudio_Standards_schedules.json +13756 -8383
  20. data/data/standards/OpenStudio_Standards_space_types.json +8593 -907
  21. data/data/standards/OpenStudio_Standards_standards.json +9 -0
  22. data/data/standards/OpenStudio_Standards_templates.json +24 -0
  23. data/data/standards/OpenStudio_Standards_unitary_acs.json +924 -0
  24. data/data/standards/manage_OpenStudio_Standards.rb +1 -1
  25. data/lib/openstudio-standards/btap/btap.rb +0 -1
  26. data/lib/openstudio-standards/btap/compliance.rb +2 -5
  27. data/lib/openstudio-standards/btap/economics.rb +16 -16
  28. data/lib/openstudio-standards/btap/envelope.rb +1 -1
  29. data/lib/openstudio-standards/btap/equest.rb +7 -7
  30. data/lib/openstudio-standards/btap/fileio.rb +2 -2
  31. data/lib/openstudio-standards/btap/geometry.rb +1 -1
  32. data/lib/openstudio-standards/btap/measures.rb +16 -16
  33. data/lib/openstudio-standards/btap/mpc.rb +18 -18
  34. data/lib/openstudio-standards/btap/schedules.rb +35 -2
  35. data/lib/openstudio-standards/btap/simmanager.rb +1 -1
  36. data/lib/openstudio-standards/btap/spaceloads.rb +1 -1
  37. data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
  38. data/lib/openstudio-standards/prototypes/Prototype.Model.elevators.rb +218 -0
  39. data/lib/openstudio-standards/prototypes/Prototype.Model.exterior_lights.rb +399 -0
  40. data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +2 -2
  41. data/lib/openstudio-standards/prototypes/Prototype.Model.rb +237 -0
  42. data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +536 -0
  43. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +6 -0
  44. data/lib/openstudio-standards/standards/Standards.Model.rb +249 -10
  45. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +5 -5
  46. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +161 -0
  47. data/lib/openstudio-standards/utilities/simulation.rb +6 -4
  48. data/lib/openstudio-standards/version.rb +1 -1
  49. metadata +223 -204
  50. data/lib/openstudio-standards/btap/os_lib_schedules.rb +0 -677
@@ -342,6 +342,12 @@ class OpenStudio::Model::AirLoopHVAC
342
342
 
343
343
  # Calculate and report the total area for debugging/testing
344
344
  floor_area_served_m2 = floor_area_served
345
+
346
+ if floor_area_served_m2 == 0
347
+ OpenStudio.logFree(OpenStudio::Warn,'openstudio.standards.AirLoopHVAC', "AirLoopHVAC #{self.name.to_s} serves zero floor area. Check that it has thermal zones attached to it, and that they have non-zero floor area'.")
348
+ return allowable_fan_bhp
349
+ end
350
+
345
351
  floor_area_served_ft2 = OpenStudio.convert(floor_area_served_m2, 'm^2', 'ft^2').get
346
352
  cfm_per_ft2 = dsn_air_flow_cfm / floor_area_served_ft2
347
353
  cfm_per_hp = dsn_air_flow_cfm / allowable_fan_bhp
@@ -26,6 +26,9 @@ def load_openstudio_standards_json
26
26
  standards_files << 'OpenStudio_Standards_templates.json'
27
27
  standards_files << 'OpenStudio_Standards_unitary_acs.json'
28
28
  standards_files << 'OpenStudio_Standards_heat_rejection.json'
29
+ standards_files << 'OpenStudio_Standards_exterior_lighting.json'
30
+ standards_files << 'OpenStudio_Standards_parking.json'
31
+ standards_files << 'OpenStudio_Standards_entryways.json'
29
32
  # standards_files << 'OpenStudio_Standards_unitary_hps.json'
30
33
 
31
34
  # Combine the data from the JSON files into a single hash
@@ -2316,6 +2319,22 @@ class OpenStudio::Model::Model
2316
2319
  return desired_object
2317
2320
  end
2318
2321
 
2322
+ # Create constant ScheduleRuleset
2323
+ #
2324
+ # @param [double] constant value
2325
+ # @param [string] name
2326
+ # @return schedule
2327
+ def add_constant_schedule_ruleset(value,name = nil)
2328
+ schedule = OpenStudio::Model::ScheduleRuleset.new(self)
2329
+ if not name.nil?
2330
+ schedule.setName(name)
2331
+ schedule.defaultDaySchedule.setName("#{name} Default")
2332
+ end
2333
+ schedule.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), value)
2334
+
2335
+ return schedule
2336
+ end
2337
+
2319
2338
  # Create a schedule from the openstudio standards dataset and
2320
2339
  # add it to the model.
2321
2340
  #
@@ -3137,19 +3156,10 @@ class OpenStudio::Model::Model
3137
3156
  building_type = getBuilding.standardsBuildingType.get
3138
3157
  end
3139
3158
 
3140
- # prototype small office approx 500 m^2
3141
- # prototype medium office approx 5000 m^2
3142
- # prototype large office approx 50,000 m^2
3143
3159
  # map office building type to small medium or large
3144
3160
  if building_type == 'Office' && remap_office
3145
3161
  open_studio_area = getBuilding.floorArea
3146
- building_type = if open_studio_area < 2750
3147
- 'SmallOffice'
3148
- elsif open_studio_area < 25_250
3149
- 'MediumOffice'
3150
- else
3151
- 'LargeOffice'
3152
- end
3162
+ building_type = self.remap_office(open_studio_area)
3153
3163
  end
3154
3164
 
3155
3165
  results = {}
@@ -3159,6 +3169,23 @@ class OpenStudio::Model::Model
3159
3169
  return results
3160
3170
  end
3161
3171
 
3172
+ # remap office to one of the protptye buildings
3173
+ # @param [Double] floor area
3174
+ # @return [String] SmallOffice, MediumOffice, LargeOffice
3175
+ def remap_office(floor_area)
3176
+ # prototype small office approx 500 m^2
3177
+ # prototype medium office approx 5000 m^2
3178
+ # prototype large office approx 50,000 m^2
3179
+ # map office building type to small medium or large
3180
+ building_type = if floor_area < 2750
3181
+ 'SmallOffice'
3182
+ elsif floor_area < 25_250
3183
+ 'MediumOffice'
3184
+ else
3185
+ 'LargeOffice'
3186
+ end
3187
+ end
3188
+
3162
3189
  # user needs to pass in template as string. The building type and climate zone will come from the model.
3163
3190
  # If the building type or ASHRAE climate zone is not set in the model this will return nil
3164
3191
  # If the lookup doesn't find matching simulation results this wil return nil
@@ -4297,6 +4324,217 @@ class OpenStudio::Model::Model
4297
4324
  end
4298
4325
  end
4299
4326
 
4327
+ # Create sorted hash of stories with data need to determine effective number of stories above and below grade
4328
+ # the key should be the story object, which would allow other measures the ability to for example loop through spaces of the bottom story
4329
+ #
4330
+ # @return [hash] hash of space types with data in value necessary to determine effective number of stories above and below grade
4331
+ def create_story_hash
4332
+
4333
+ story_hash = {}
4334
+
4335
+ # loop through stories
4336
+ self.getBuildingStorys.each do |story|
4337
+
4338
+ # skip of story doesn't have any spaces
4339
+ next if story.spaces.size == 0
4340
+
4341
+ story_min_z = nil
4342
+ story_zone_multipliers = []
4343
+ story_spaces_part_of_floor_area = []
4344
+ story_spaces_not_part_of_floor_area = []
4345
+ story_ext_wall_area = 0.0
4346
+ story_ground_wall_area = 0.0
4347
+
4348
+ # loop through space surfaces to find min z value
4349
+ story.spaces.each do |space|
4350
+
4351
+ # skip of space doesn't have any geometry
4352
+ next if space.surfaces.size == 0
4353
+
4354
+ # get space multiplier
4355
+ story_zone_multipliers << space.multiplier
4356
+
4357
+ # space part of floor area check
4358
+ if space.partofTotalFloorArea
4359
+ story_spaces_part_of_floor_area << space
4360
+ else
4361
+ story_spaces_not_part_of_floor_area << space
4362
+ end
4363
+
4364
+ # update exterior wall area (not sure if this is net or gross)
4365
+ story_ext_wall_area += space.exteriorWallArea
4366
+
4367
+ space_min_z = nil
4368
+ z_points = []
4369
+ space.surfaces.each do |surface|
4370
+ surface.vertices.each do |vertex|
4371
+ z_points << vertex.z
4372
+ end
4373
+
4374
+ # update count of ground wall areas
4375
+ next if not surface.surfaceType == "Wall"
4376
+ next if not surface.outsideBoundaryCondition == "Ground" # todo - make more flexible for slab/basement modeling
4377
+ story_ground_wall_area += surface.grossArea
4378
+
4379
+ end
4380
+
4381
+ # skip if surface had no vertices
4382
+ next if z_points.size == 0
4383
+
4384
+ # update story min_z
4385
+ space_min_z = z_points.min + space.zOrigin
4386
+ if story_min_z.nil? or story_min_z > space_min_z
4387
+ story_min_z = space_min_z
4388
+ end
4389
+
4390
+ end
4391
+
4392
+ # update story hash
4393
+ story_hash[story] = {}
4394
+ story_hash[story][:min_z] = story_min_z
4395
+ story_hash[story][:multipliers] = story_zone_multipliers
4396
+ story_hash[story][:part_of_floor_area] = story_spaces_part_of_floor_area
4397
+ story_hash[story][:not_part_of_floor_area] = story_spaces_not_part_of_floor_area
4398
+ story_hash[story][:ext_wall_area] = story_ext_wall_area
4399
+ story_hash[story][:ground_wall_area] = story_ground_wall_area
4400
+
4401
+ end
4402
+
4403
+ # sort hash by min_z low to high
4404
+ story_hash = story_hash.sort_by{|k,v| v[:min_z]}
4405
+
4406
+ # reassemble into hash after sorting
4407
+ hash = {}
4408
+ story_hash.each do |story, props|
4409
+ hash[story] = props
4410
+ end
4411
+
4412
+ return hash
4413
+
4414
+ end
4415
+
4416
+ # populate this method
4417
+ # Determine the effective number of stories above and below grade
4418
+ #
4419
+ # @return hash with effective_num_stories_below_grade and effective_num_stories_above_grade
4420
+ def effective_num_stories
4421
+
4422
+ below_grade = 0
4423
+ above_grade = 0
4424
+
4425
+ # call create_story_hash
4426
+ story_hash = self.create_story_hash
4427
+
4428
+ story_hash.each do |story,hash|
4429
+
4430
+ # skip if no spaces in story are included in the building area
4431
+ next if hash[:part_of_floor_area].size == 0
4432
+
4433
+ # only count as below grade if ground wall area is greater than ext wall area and story below is also below grade
4434
+ if above_grade == 0 and hash[:ground_wall_area] > hash[:ext_wall_area]
4435
+ below_grade += 1 * hash[:multipliers].min
4436
+ else
4437
+ above_grade += 1 * hash[:multipliers].min
4438
+ end
4439
+
4440
+ end
4441
+
4442
+ # populate hash
4443
+ effective_num_stories = {}
4444
+ effective_num_stories[:below_grade] = below_grade
4445
+ effective_num_stories[:above_grade] = above_grade
4446
+ effective_num_stories[:story_hash] = story_hash
4447
+
4448
+ return effective_num_stories
4449
+
4450
+ end
4451
+
4452
+ # create space_type_hash with info such as effective_num_spaces, num_units, num_meds, num_meals
4453
+ #
4454
+ # @param template [String]
4455
+ # @param trust_effective_num_spaces [Bool] defaults to false - set to true if modeled every space as a real rpp, vs. space as collection of rooms
4456
+ # @return [hash] hash of space types with misc information
4457
+ # @todo - add code when determining number of units to makeuse of trust_effective_num_spaces arg
4458
+ def create_space_type_hash(template,trust_effective_num_spaces = false)
4459
+
4460
+ # assumed class size to deduct teachers from occupant count for classrooms
4461
+ typical_class_size = 20.0
4462
+
4463
+ space_type_hash = {}
4464
+ self.getSpaceTypes.each do |space_type|
4465
+
4466
+ # get standards info
4467
+ stds_bldg_type = space_type.standardsBuildingType
4468
+ stds_space_type = space_type.standardsSpaceType
4469
+ if stds_bldg_type.is_initialized and stds_space_type.is_initialized and space_type.spaces.size > 0
4470
+ stds_bldg_type = stds_bldg_type.get
4471
+ stds_space_type = stds_space_type.get
4472
+ effective_num_spaces = 0
4473
+ floor_area = 0.0
4474
+ num_people = 0.0
4475
+ num_students = 0.0
4476
+ num_units = 0.0
4477
+ num_beds = 0.0
4478
+ num_people_bldg_total = nil # may need this in future, not same as sumo of people for all space types.
4479
+ num_meals = nil
4480
+ # determine num_elevators in another method
4481
+ # determine num_parking_spots in another method
4482
+
4483
+ # loop through spaces to get mis values
4484
+ space_type.spaces.each do |space|
4485
+ next if not space.partofTotalFloorArea
4486
+ effective_num_spaces += space.multiplier
4487
+ floor_area += space.floorArea * space.multiplier
4488
+ num_people += space.numberOfPeople * space.multiplier
4489
+
4490
+ end
4491
+
4492
+ # determine number of units
4493
+ if stds_bldg_type == "SmallHotel" && stds_space_type.include?("GuestRoom") # doesn't always == GuestRoom so use include?
4494
+ avg_unit_size = OpenStudio::convert(354.2,"ft^2","m^2").get # calculated from prototype
4495
+ num_units = floor_area / avg_unit_size
4496
+ elsif stds_bldg_type == "LargeHotel" && stds_space_type.include?("GuestRoom")
4497
+ avg_unit_size = OpenStudio::convert(279.7,"ft^2","m^2").get # calculated from prototype
4498
+ num_units = floor_area / avg_unit_size
4499
+ elsif stds_bldg_type == "MidriseApartment" && stds_space_type.include?("Apartment")
4500
+ avg_unit_size = OpenStudio::convert(949.9,"ft^2","m^2").get # calculated from prototype
4501
+ num_units = floor_area / avg_unit_size
4502
+ elsif stds_bldg_type == "HighriseApartment" && stds_space_type.include?("Apartment")
4503
+ avg_unit_size = OpenStudio::convert(949.9,"ft^2","m^2").get # calculated from prototype
4504
+ num_units = floor_area / avg_unit_size
4505
+ elsif stds_bldg_type == "StripMall"
4506
+ avg_unit_size = OpenStudio::convert(22500.0/10.0,"ft^2","m^2").get # calculated from prototype
4507
+ num_units = floor_area / avg_unit_size
4508
+ end
4509
+
4510
+ # determine number of beds
4511
+ if stds_bldg_type == "Hospital" && ["PatRoom","ICU_PatRm","ICU_Open"].include?(stds_space_type)
4512
+ num_beds = num_people
4513
+ end
4514
+
4515
+ # determine number of students
4516
+ if ["PrimarySchool","SecondarySchool"].include?(stds_bldg_type) && stds_space_type == "Classroom"
4517
+ num_students += num_people * ((typical_class_size - 1.0)/typical_class_size)
4518
+ end
4519
+
4520
+ space_type_hash[space_type] = {}
4521
+ space_type_hash[space_type][:stds_bldg_type] = stds_bldg_type
4522
+ space_type_hash[space_type][:stds_space_type] = stds_space_type
4523
+ space_type_hash[space_type][:effective_num_spaces] = effective_num_spaces
4524
+ space_type_hash[space_type][:floor_area] = floor_area
4525
+ space_type_hash[space_type][:num_people] = num_people
4526
+ space_type_hash[space_type][:num_students] = num_students
4527
+ space_type_hash[space_type][:num_units] = num_units
4528
+ space_type_hash[space_type][:num_beds] = num_beds
4529
+
4530
+ else
4531
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Model', "Cannot identify standards buidling type and space type for #{space_type.name}, it won't be added to space_type_hash.")
4532
+ end
4533
+ end
4534
+
4535
+ return space_type_hash
4536
+ end
4537
+
4300
4538
  private
4301
4539
 
4302
4540
  # Helper method to fill in hourly values
@@ -4401,4 +4639,5 @@ class OpenStudio::Model::Model
4401
4639
  return true
4402
4640
 
4403
4641
  end
4642
+
4404
4643
  end
@@ -125,7 +125,7 @@ class OpenStudio::Model::SpaceType
125
125
  end
126
126
 
127
127
  # Modify the definition of the instance
128
- instances.each do |inst|
128
+ people.sort.each do |inst|
129
129
  definition = inst.peopleDefinition
130
130
  unless occupancy_per_area.zero?
131
131
  definition.setPeopleperSpaceFloorArea(OpenStudio.convert(occupancy_per_area / 1000, 'people/ft^2', 'people/m^2').get)
@@ -207,7 +207,7 @@ class OpenStudio::Model::SpaceType
207
207
  end
208
208
 
209
209
  # Modify the definition of the instance
210
- instances.each do |inst|
210
+ lights.sort.each do |inst|
211
211
  definition = inst.lightsDefinition
212
212
  unless lighting_per_area.zero?
213
213
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area.to_f, 'W/ft^2', 'W/m^2').get)
@@ -276,7 +276,7 @@ class OpenStudio::Model::SpaceType
276
276
  end
277
277
 
278
278
  # Modify the definition of the instance
279
- instances.each do |inst|
279
+ electricEquipment.sort.each do |inst|
280
280
  definition = inst.electricEquipmentDefinition
281
281
  unless elec_equip_per_area.zero?
282
282
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(elec_equip_per_area.to_f, 'W/ft^2', 'W/m^2').get)
@@ -324,7 +324,7 @@ class OpenStudio::Model::SpaceType
324
324
  end
325
325
 
326
326
  # Modify the definition of the instance
327
- instances.each do |inst|
327
+ gasEquipment.sort.each do |inst|
328
328
  definition = inst.gasEquipmentDefinition
329
329
  unless gas_equip_per_area.zero?
330
330
  definition.setWattsperSpaceFloorArea(OpenStudio.convert(gas_equip_per_area.to_f, 'Btu/hr*ft^2', 'W/m^2').get)
@@ -419,7 +419,7 @@ class OpenStudio::Model::SpaceType
419
419
  end
420
420
 
421
421
  # Modify each instance
422
- instances.each do |inst|
422
+ spaceInfiltrationDesignFlowRates.sort.each do |inst|
423
423
  unless infiltration_per_area_ext.zero?
424
424
  inst.setFlowperExteriorSurfaceArea(OpenStudio.convert(infiltration_per_area_ext.to_f, 'ft^3/min*ft^2', 'm^3/s*m^2').get)
425
425
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{name} set infiltration to #{ventilation_ach} per ft^2 exterior surface area.")
@@ -1401,4 +1401,165 @@ class OpenStudio::Model::ThermalZone
1401
1401
 
1402
1402
  return dcv_required
1403
1403
  end
1404
+
1405
+ # Add Exhaust Fans based on space type lookup
1406
+ # This measure doesn't look if DCV is needed. Others methods can check if DCV needed and add it
1407
+ #
1408
+ # @param template [String] Valid choices are
1409
+ # @return [Hash] Hash of newly made exhaust fan objects along with secondary exhaust and zone mixing objects
1410
+ # @todo - Combine availability and fraction flow schedule to make zone mixing schedule
1411
+ def add_exhaust(template,exhaust_makeup_inputs = {})
1412
+
1413
+ exhaust_fans = {} # key is primary exhaust value is hash of arrays of secondary objects
1414
+
1415
+ # hash to store space type information
1416
+ space_type_hash = {} # key is space type value is floor_area_si
1417
+
1418
+ # get space type ratio for spaces in zone, making more than one exhaust fan if necessary
1419
+ self.spaces.each do |space|
1420
+ next if not space.spaceType.is_initialized
1421
+ next if not space.partofTotalFloorArea
1422
+ space_type = space.spaceType.get
1423
+ if space_type_hash.has_key?(space_type)
1424
+ space_type_hash[space_type] += space.floorArea # excluding space.multiplier since used to calc loads in zone
1425
+ else
1426
+ next if not space_type.standardsBuildingType.is_initialized
1427
+ next if not space_type.standardsSpaceType.is_initialized
1428
+ space_type_hash[space_type] = space.floorArea # excluding space.multiplier since used to calc loads in zone
1429
+ end
1430
+
1431
+ end
1432
+
1433
+ # loop through space type hash and add exhaust as needed
1434
+ space_type_hash.each do |space_type,floor_area|
1435
+
1436
+ # get floor custom or calculated floor area for max flow rate calculation
1437
+ makeup_target = [space_type.standardsBuildingType.get,space_type.standardsSpaceType.get]
1438
+ if exhaust_makeup_inputs.has_key?(makeup_target) and exhaust_makeup_inputs[makeup_target].has_key?(:target_effective_floor_area)
1439
+ # pass in custom floor area
1440
+ floor_area_si = exhaust_makeup_inputs[makeup_target][:target_effective_floor_area] / self.multiplier.to_f
1441
+ floor_area_ip = OpenStudio.convert(floor_area_si,'m^2','ft^2').get
1442
+ else
1443
+ floor_area_ip = OpenStudio.convert(floor_area,'m^2','ft^2').get
1444
+ end
1445
+
1446
+ space_type_properties = space_type.get_standards_data(template)
1447
+ exhaust_per_area = space_type_properties['exhaust_per_area']
1448
+ next if exhaust_per_area.nil?
1449
+ maximum_flow_rate_ip = exhaust_per_area * floor_area_ip
1450
+ maximum_flow_rate_si = OpenStudio.convert(maximum_flow_rate_ip,'cfm','m^3/s').get
1451
+ if space_type_properties['exhaust_schedule'].nil?
1452
+ exhaust_schedule = model.alwaysOnDiscreteSchedule
1453
+ else
1454
+ sch_name = space_type_properties['exhaust_schedule']
1455
+ exhaust_schedule = model.add_schedule(sch_name)
1456
+ end
1457
+
1458
+ # add exhaust fans
1459
+ zone_exhaust_fan = OpenStudio::Model::FanZoneExhaust.new(model)
1460
+ zone_exhaust_fan.setName(self.name.to_s + ' Exhaust Fan')
1461
+ zone_exhaust_fan.setAvailabilitySchedule(exhaust_schedule)
1462
+ # not using zone_exhaust_fan.setFlowFractionSchedule. Exhaust fans are on when available
1463
+ zone_exhaust_fan.setMaximumFlowRate(maximum_flow_rate_si)
1464
+ zone_exhaust_fan.setEndUseSubcategory('Zone Exhaust Fans')
1465
+ zone_exhaust_fan.addToThermalZone(self)
1466
+ exhaust_fans[zone_exhaust_fan] = {} # keys are :zone_mixing and :transfer_air_source_zone_exhaust
1467
+
1468
+ # set fan pressure rise
1469
+ zone_exhaust_fan.apply_prototype_fan_pressure_rise
1470
+
1471
+ # update efficiency and pressure rise
1472
+ zone_exhaust_fan.apply_prototype_fan_efficiency(template)
1473
+
1474
+ # add and alter objectxs related to zone exhaust makeup air
1475
+ if exhaust_makeup_inputs.has_key?(makeup_target) and exhaust_makeup_inputs[makeup_target][:source_zone]
1476
+
1477
+ # add balanced schedule to zone_exhaust_fan
1478
+ balanced_sch_name = space_type_properties['balanced_exhaust_fraction_schedule']
1479
+ balanced_exhaust_schedule = model.add_schedule(balanced_sch_name).to_ScheduleRuleset.get
1480
+ zone_exhaust_fan.setBalancedExhaustFractionSchedule(balanced_exhaust_schedule)
1481
+
1482
+ # use max value of balanced exhaust fraction schedule for maximum flow rate
1483
+ max_sch_val = balanced_exhaust_schedule.annual_min_max_value['max']
1484
+ transfer_air_zone_mixing_si = maximum_flow_rate_si * max_sch_val
1485
+
1486
+ # add dummy exhaust fan to a transfer_air_source_zones
1487
+ transfer_air_source_zone_exhaust = OpenStudio::Model::FanZoneExhaust.new(model)
1488
+ transfer_air_source_zone_exhaust.setName(self.name.to_s + ' Transfer Air Source')
1489
+ transfer_air_source_zone_exhaust.setAvailabilitySchedule(exhaust_schedule)
1490
+ # not using zone_exhaust_fan.setFlowFractionSchedule. Exhaust fans are on when available
1491
+ transfer_air_source_zone_exhaust.setMaximumFlowRate(transfer_air_zone_mixing_si)
1492
+ transfer_air_source_zone_exhaust.setFanEfficiency(1.0)
1493
+ transfer_air_source_zone_exhaust.setPressureRise(0.0)
1494
+ transfer_air_source_zone_exhaust.setEndUseSubcategory('Zone Exhaust Fans')
1495
+ transfer_air_source_zone_exhaust.addToThermalZone(exhaust_makeup_inputs[makeup_target][:source_zone])
1496
+ exhaust_fans[zone_exhaust_fan][:transfer_air_source_zone_exhaust] = transfer_air_source_zone_exhaust
1497
+
1498
+ # todo - make zone mixing schedule by combining exhaust availability and fraction flow
1499
+ zone_mixing_schedule = exhaust_schedule
1500
+
1501
+ # add zone mixing
1502
+ zone_mixing = OpenStudio::Model::ZoneMixing.new(self)
1503
+ zone_mixing.setSchedule(zone_mixing_schedule)
1504
+ zone_mixing.setSourceZone(exhaust_makeup_inputs[makeup_target][:source_zone])
1505
+ zone_mixing.setDesignFlowRate(transfer_air_zone_mixing_si)
1506
+ exhaust_fans[zone_exhaust_fan][:zone_mixing] = zone_mixing
1507
+
1508
+ end
1509
+
1510
+ end
1511
+
1512
+ return exhaust_fans
1513
+
1514
+ end
1515
+
1516
+ # returns adjacant_zones_with_shared_wall_areas
1517
+ #
1518
+ # @param [Bool] same_floor (only valid option for now is true)
1519
+ # @return [Array] adjacent zones
1520
+ def get_adjacent_zones_with_shared_wall_areas(same_floor = true)
1521
+
1522
+ adjacent_zones = []
1523
+
1524
+ self.spaces.each do |space|
1525
+ adj_spaces = space.get_adjacent_spaces_with_shared_wall_areas
1526
+ adj_spaces.each do |k,v|
1527
+ # skip if space is in current thermal zone.
1528
+ next if not space.thermalZone.is_initialized
1529
+ next if k.thermalZone.get == self
1530
+ adjacent_zones << k.thermalZone.get
1531
+ end
1532
+ end
1533
+
1534
+ adjacent_zones = adjacent_zones.uniq
1535
+
1536
+ return adjacent_zones
1537
+
1538
+ end
1539
+
1540
+ # returns true if DCV is required for exhaust fan for specified tempate
1541
+ #
1542
+ # @param template [String] Valid choices are
1543
+ # @return [Bool] returns true if DCV is required for exhaust fan for specified tempate
1544
+ def exhaust_fan_dcv_required?(template)
1545
+
1546
+ end
1547
+
1548
+ # Add DCV to exhaust fan and if requsted to related objects
1549
+ #
1550
+ # @param template [Bool] defaults to true to change associated objects
1551
+ # @param template [Array] related zone mixing objects
1552
+ # @param template [Array] related transfer_air_source_zones
1553
+ # @param template [Bool] defaults to true to change associated objects
1554
+ # @return [Bool] not sure if there is anything to turn here other than if it was sucessful, no new objects made?
1555
+ def add_exhaust_fan_dcv(change_related_objects = true,zone_mixing_objects = [],transfer_air_source_zones = [])
1556
+
1557
+ # set flow fraction schedule for all zone exhaust fans and then set zone mixing schedule to the intersection of exhaust avaialability and exhaust fractional schedule
1558
+
1559
+ # are there associated zone mixing or dummy exhaust objects that need to change when this changes?
1560
+ # How are these ojects identifed?
1561
+ # If this is run directly after add_exhaust it will return a hash where each key is an exhaust object and hash is a hash of related zone mizing and dummy exhaust from the source zone
1562
+
1563
+ end
1564
+
1404
1565
  end