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.
- 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
|