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.
- checksums.yaml +4 -4
- data/data/standards/OpenStudio_Standards.xlsx +0 -0
- data/data/standards/OpenStudio_Standards_climate_zone_sets.json +96 -0
- data/data/standards/OpenStudio_Standards_climate_zones.json +96 -0
- data/data/standards/OpenStudio_Standards_construction_properties.json +11424 -0
- data/data/standards/OpenStudio_Standards_construction_sets.json +1746 -24
- data/data/standards/OpenStudio_Standards_constructions.json +409 -0
- data/data/standards/OpenStudio_Standards_curve_biquadratics.json +160 -0
- data/data/standards/OpenStudio_Standards_curve_quadratics.json +165 -0
- data/data/standards/OpenStudio_Standards_entryways.json +191 -0
- data/data/standards/OpenStudio_Standards_exterior_lighting.json +964 -0
- data/data/standards/OpenStudio_Standards_exterior_lighting_assumptions.json +191 -0
- data/data/standards/OpenStudio_Standards_ground_temperatures.json +11424 -0
- data/data/standards/OpenStudio_Standards_illuminated_parking_area.json +157 -0
- data/data/standards/OpenStudio_Standards_materials.json +2539 -745
- data/data/standards/OpenStudio_Standards_motors.json +420 -0
- data/data/standards/OpenStudio_Standards_parking.json +157 -0
- data/data/standards/OpenStudio_Standards_prototype_inputs.json +4410 -10
- data/data/standards/OpenStudio_Standards_schedules.json +13756 -8383
- data/data/standards/OpenStudio_Standards_space_types.json +8593 -907
- data/data/standards/OpenStudio_Standards_standards.json +9 -0
- data/data/standards/OpenStudio_Standards_templates.json +24 -0
- data/data/standards/OpenStudio_Standards_unitary_acs.json +924 -0
- data/data/standards/manage_OpenStudio_Standards.rb +1 -1
- data/lib/openstudio-standards/btap/btap.rb +0 -1
- data/lib/openstudio-standards/btap/compliance.rb +2 -5
- data/lib/openstudio-standards/btap/economics.rb +16 -16
- data/lib/openstudio-standards/btap/envelope.rb +1 -1
- data/lib/openstudio-standards/btap/equest.rb +7 -7
- data/lib/openstudio-standards/btap/fileio.rb +2 -2
- data/lib/openstudio-standards/btap/geometry.rb +1 -1
- data/lib/openstudio-standards/btap/measures.rb +16 -16
- data/lib/openstudio-standards/btap/mpc.rb +18 -18
- data/lib/openstudio-standards/btap/schedules.rb +35 -2
- data/lib/openstudio-standards/btap/simmanager.rb +1 -1
- data/lib/openstudio-standards/btap/spaceloads.rb +1 -1
- data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
- data/lib/openstudio-standards/prototypes/Prototype.Model.elevators.rb +218 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.exterior_lights.rb +399 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +2 -2
- data/lib/openstudio-standards/prototypes/Prototype.Model.rb +237 -0
- data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +536 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +6 -0
- data/lib/openstudio-standards/standards/Standards.Model.rb +249 -10
- data/lib/openstudio-standards/standards/Standards.SpaceType.rb +5 -5
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +161 -0
- data/lib/openstudio-standards/utilities/simulation.rb +6 -4
- data/lib/openstudio-standards/version.rb +1 -1
- metadata +223 -204
- 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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|