openstudio-standards 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
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