-
Notifications
You must be signed in to change notification settings - Fork 17
/
room.py
1382 lines (1189 loc) Β· 63.5 KB
/
room.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# coding=utf-8
"""Room Energy Properties."""
# import honeybee-core and ladybug-geometry modules
from ladybug_geometry.geometry2d.pointvector import Vector2D
from ladybug_geometry.geometry3d.pointvector import Point3D
from honeybee.boundarycondition import Outdoors, Surface, boundary_conditions
from honeybee.facetype import Wall, RoofCeiling, Floor, AirBoundary
from honeybee.aperture import Aperture
# import the main types of assignable objects
from ..programtype import ProgramType
from ..constructionset import ConstructionSet
from ..load.people import People
from ..load.lighting import Lighting
from ..load.equipment import ElectricEquipment, GasEquipment
from ..load.hotwater import ServiceHotWater
from ..load.infiltration import Infiltration
from ..load.ventilation import Ventilation
from ..load.setpoint import Setpoint
from ..load.daylight import DaylightingControl
from ..internalmass import InternalMass
from ..ventcool.control import VentilationControl
from ..ventcool.crack import AFNCrack
from ..ventcool.opening import VentilationOpening
from ..construction.opaque import OpaqueConstruction
# import all hvac and shw modules
from ..hvac import HVAC_TYPES_DICT
from ..hvac._base import _HVACSystem
from ..hvac.idealair import IdealAirSystem
from ..shw import SHWSystem
# import the libraries of constructionsets and programs
from ..lib.constructionsets import generic_construction_set
from ..lib.schedules import always_on
from ..lib.programtypes import plenum_program
class RoomEnergyProperties(object):
"""Energy Properties for Honeybee Room.
Args:
host: A honeybee_core Room object that hosts these properties.
program_type: A honeybee ProgramType object to specify all default
schedules and loads for the Room. If None, the Room will have a Plenum
program (with no loads or setpoints). Default: None.
construction_set: A honeybee ConstructionSet object to specify all
default constructions for the Faces of the Room. If None, the Room
will use the honeybee default construction set, which is not
representative of a particular building code or climate zone.
Default: None.
hvac: A honeybee HVAC object (such as an IdealAirSystem) that specifies
how the Room is conditioned. If None, it will be assumed that the
Room is not conditioned. Default: None.
Properties:
* host
* program_type
* construction_set
* hvac
* shw
* people
* lighting
* electric_equipment
* gas_equipment
* service_hot_water
* infiltration
* ventilation
* setpoint
* daylighting_control
* window_vent_control
* internal_masses
* is_conditioned
"""
__slots__ = ('_host', '_program_type', '_construction_set', '_hvac', '_shw',
'_people', '_lighting', '_electric_equipment', '_gas_equipment',
'_service_hot_water', '_infiltration', '_ventilation', '_setpoint',
'_daylighting_control', '_window_vent_control', '_internal_masses')
def __init__(
self, host, program_type=None, construction_set=None, hvac=None, shw=None):
"""Initialize Room energy properties."""
# set the main properties of the Room
self._host = host
self.program_type = program_type
self.construction_set = construction_set
self.hvac = hvac
self.shw = shw
# set the Room's specific properties that override the program_type to None
self._people = None
self._lighting = None
self._electric_equipment = None
self._gas_equipment = None
self._service_hot_water = None
self._infiltration = None
self._ventilation = None
self._setpoint = None
self._daylighting_control = None
self._window_vent_control = None
self._internal_masses = []
@property
def host(self):
"""Get the Room object hosting these properties."""
return self._host
@property
def program_type(self):
"""Get or set the ProgramType object for the Room.
If not set, it will default to a plenum ProgramType (with no loads assigned).
"""
if self._program_type is not None: # set by the user
return self._program_type
else:
return plenum_program
@program_type.setter
def program_type(self, value):
if value is not None:
assert isinstance(value, ProgramType), \
'Expected ProgramType for Room program_type. Got {}'.format(type(value))
value.lock() # lock in case program type has multiple references
self._program_type = value
@property
def construction_set(self):
"""Get or set the Room ConstructionSet object.
If not set, it will be the Honeybee default generic ConstructionSet.
"""
if self._construction_set is not None: # set by the user
return self._construction_set
else:
return generic_construction_set
@construction_set.setter
def construction_set(self, value):
if value is not None:
assert isinstance(value, ConstructionSet), \
'Expected ConstructionSet. Got {}'.format(type(value))
value.lock() # lock in case construction set has multiple references
self._construction_set = value
@property
def hvac(self):
"""Get or set the HVAC object for the Room.
If None, it will be assumed that the Room is not conditioned.
"""
return self._hvac
@hvac.setter
def hvac(self, value):
if value is not None:
assert isinstance(value, _HVACSystem), \
'Expected HVACSystem for Room hvac. Got {}'.format(type(value))
value.lock() # lock in case hvac has multiple references
self._hvac = value
@property
def shw(self):
"""Get or set the SHWSystem object for the Room.
If None, all hot water loads will be met with a system that doesn't compute
fuel or electricity usage.
"""
return self._shw
@shw.setter
def shw(self, value):
if value is not None:
assert isinstance(value, SHWSystem), \
'Expected SHWSystem for Room shw. Got {}'.format(type(value))
value.lock() # lock in case shw has multiple references
self._shw = value
@property
def people(self):
"""Get or set a People object to describe the occupancy of the Room."""
if self._people is not None: # set by the user
return self._people
else:
return self.program_type.people
@people.setter
def people(self, value):
if value is not None:
assert isinstance(value, People), \
'Expected People for Room people. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._people = value
@property
def lighting(self):
"""Get or set a Lighting object to describe the lighting usage of the Room."""
if self._lighting is not None: # set by the user
return self._lighting
else:
return self.program_type.lighting
@lighting.setter
def lighting(self, value):
if value is not None:
assert isinstance(value, Lighting), \
'Expected Lighting for Room lighting. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._lighting = value
@property
def electric_equipment(self):
"""Get or set an ElectricEquipment object to describe the equipment usage."""
if self._electric_equipment is not None: # set by the user
return self._electric_equipment
else:
return self.program_type.electric_equipment
@electric_equipment.setter
def electric_equipment(self, value):
if value is not None:
assert isinstance(value, ElectricEquipment), 'Expected ElectricEquipment ' \
'for Room electric_equipment. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._electric_equipment = value
@property
def gas_equipment(self):
"""Get or set a GasEquipment object to describe the equipment usage."""
if self._gas_equipment is not None: # set by the user
return self._gas_equipment
else:
return self.program_type.gas_equipment
@gas_equipment.setter
def gas_equipment(self, value):
if value is not None:
assert isinstance(value, GasEquipment), 'Expected GasEquipment ' \
'for Room gas_equipment. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._gas_equipment = value
@property
def service_hot_water(self):
"""Get or set a ServiceHotWater object to describe the hot water usage."""
if self._service_hot_water is not None: # set by the user
return self._service_hot_water
else:
return self.program_type.service_hot_water
@service_hot_water.setter
def service_hot_water(self, value):
if value is not None:
assert isinstance(value, ServiceHotWater), 'Expected ServiceHotWater ' \
'for Room service_hot_water. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._service_hot_water = value
@property
def infiltration(self):
"""Get or set a Infiltration object to to describe the outdoor air leakage."""
if self._infiltration is not None: # set by the user
return self._infiltration
else:
return self.program_type.infiltration
@infiltration.setter
def infiltration(self, value):
if value is not None:
assert isinstance(value, Infiltration), 'Expected Infiltration ' \
'for Room infiltration. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._infiltration = value
@property
def ventilation(self):
"""Get or set a Ventilation object for the minimum outdoor air requirement."""
if self._ventilation is not None: # set by the user
return self._ventilation
else:
return self.program_type.ventilation
@ventilation.setter
def ventilation(self, value):
if value is not None:
assert isinstance(value, Ventilation), 'Expected Ventilation ' \
'for Room ventilation. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._ventilation = value
@property
def setpoint(self):
"""Get or set a Setpoint object for the temperature setpoints of the Room."""
if self._setpoint is not None: # set by the user
return self._setpoint
else:
return self.program_type.setpoint
@setpoint.setter
def setpoint(self, value):
if value is not None:
assert isinstance(value, Setpoint), 'Expected Setpoint ' \
'for Room setpoint. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._setpoint = value
@property
def daylighting_control(self):
"""Get or set a DaylightingControl object to dictate the dimming of lights.
If None, the lighting will respond only to the schedule and not the
daylight conditions within the room.
"""
return self._daylighting_control
@daylighting_control.setter
def daylighting_control(self, value):
if value is not None:
assert isinstance(value, DaylightingControl), 'Expected DaylightingControl' \
' object for Room daylighting_control. Got {}'.format(type(value))
value._parent = self.host
self._daylighting_control = value
@property
def window_vent_control(self):
"""Get or set a VentilationControl object to dictate the opening of windows.
If None, the windows will never open.
"""
return self._window_vent_control
@window_vent_control.setter
def window_vent_control(self, value):
if value is not None:
assert isinstance(value, VentilationControl), 'Expected VentilationControl' \
' object for Room window_vent_control. Got {}'.format(type(value))
value.lock() # lock because we don't duplicate the object
self._window_vent_control = value
@property
def internal_masses(self):
"""Get or set an array of InternalMass objects for mass exposed to Room air.
Note that internal masses assigned this way cannot "see" solar radiation that
may potentially hit them and, as such, caution should be taken when using this
component with internal mass objects that are not always in shade. Masses are
factored into the the thermal calculations of the Room by undergoing heat
transfer with the indoor air.
"""
return tuple(self._internal_masses)
@internal_masses.setter
def internal_masses(self, value):
for val in value:
assert isinstance(val, InternalMass), 'Expected InternalMass' \
' object for Room internal_masses. Got {}'.format(type(val))
val.lock() # lock because we don't duplicate the object
self._internal_masses = list(value)
@property
def is_conditioned(self):
"""Boolean to note whether the Room is conditioned."""
return self._hvac is not None
def abolute_people(self, person_count, conversion=1):
"""Set the abolute number of people in the Room.
This overwrites the RoomEnergyProperties's people per area but preserves
all schedules and other people properties. If the Room has no people definition,
a new one with an Always On schedule will be created. Note that, if the
host Room has no floors, the people load will be zero.
Args:
person_count: Number for the maximum quantity of people in the room.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
people = self._dup_load('people', People)
self._absolute_by_floor(people, 'people_per_area', person_count, conversion)
self.people = people
def abolute_lighting(self, watts, conversion=1):
"""Set the abolute wattage of lighting in the Room.
This overwrites the RoomEnergyProperties's lighting per area but preserves all
schedules and other lighting properties. If the Room has no lighting definition,
a new one with an Always On schedule will be created. Note that, if the
host Room has no floors, the lighting load will be zero.
Args:
watts: Number for the installed wattage of lighting in the room.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
lighting = self._dup_load('lighting', Lighting)
self._absolute_by_floor(lighting, 'watts_per_area', watts, conversion)
self.lighting = lighting
def abolute_electric_equipment(self, watts, conversion=1):
"""Set the abolute wattage of electric equipment in the Room.
This overwrites the RoomEnergyProperties's electric equipment per area but
preserves all schedules and other properties. If the Room has no electric
equipment definition, a new one with an Always On schedule will be created.
Note that, if the host Room has no floors, the electric equipment load
will be zero.
Args:
watts: Number for the installed wattage of electric equipment in the room.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
elect_equip = self._dup_load('electric_equipment', ElectricEquipment)
self._absolute_by_floor(elect_equip, 'watts_per_area', watts, conversion)
self.electric_equipment = elect_equip
def abolute_gas_equipment(self, watts, conversion=1):
"""Set the abolute wattage of gas equipment in the Room.
This overwrites the RoomEnergyProperties's gas equipment per area but
preserves all schedules and other properties. If the Room has no gas
equipment definition, a new one with an Always On schedule will be created.
Note that, if the host Room has no floors, the gas equipment load
will be zero.
Args:
watts: Number for the installed wattage of gas equipment in the room.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
gas_equipment = self._dup_load('gas_equipment', GasEquipment)
self._absolute_by_floor(gas_equipment, 'watts_per_area', watts, conversion)
self.gas_equipment = gas_equipment
def abolute_service_hot_water(self, flow, conversion=1):
"""Set the abolute flow rate of service hot water use in the Room.
This overwrites the RoomEnergyProperties's hot water flow per area but
preserves all schedules and other properties. If the Room has no service
hot water definition, a new one with an Always On schedule will be created.
Note that, if the host Room has no floors, the service hot water flow
will be zero.
Args:
flow: Number for the peak flow rate of service hot water in the room.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
shw = self._dup_load('service_hot_water', ServiceHotWater)
self._absolute_by_floor(shw, 'flow_per_area', flow, conversion)
self.service_hot_water = shw
def abolute_infiltration(self, flow_rate, conversion=1):
"""Set the abolute flow rate of infiltration for the Room in m3/s.
This overwrites the RoomEnergyProperties's infiltration flow per exterior area
but preserves all schedules and other properties. If the Room has no
infiltration definition, a new one with an Always On schedule will be created.
Note that, if the host Room has no exterior faces, the infiltration load
will be zero.
Args:
flow_rate: Number for the infiltration flow rate in m3/s.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
infiltration = self._dup_load('infiltration', Infiltration)
try:
ext_area = self.host.exposed_area * conversion ** 2
infiltration.flow_per_exterior_area = flow_rate / ext_area
except ZeroDivisionError:
pass # no exposed area; just leave the load level as is
self.infiltration = infiltration
def abolute_infiltration_ach(self, air_changes_per_hour, conversion=1):
"""Set the abolute flow rate of infiltration for the Room in ACH.
This overwrites the RoomEnergyProperties's infiltration flow per exterior area
but preserves all schedules and other properties. If the Room has no
infiltration definition, a new one with an Always On schedule will be created.
Note that, if the host Room has no exterior faces, the infiltration load
will be zero.
Args:
air_changes_per_hour: Number for the infiltration flow rate in ACH.
conversion: Factor to account for the case where host Room geometry is
not in meters. This will be multiplied by the floor area so it should
be 0.001 for millimeters, 0.305 for feet, etc. (Default: 1).
"""
room_vol = self.host.volume * conversion ** 3
self.abolute_infiltration((air_changes_per_hour * room_vol) / 3600., conversion)
def abolute_ventilation(self, flow_rate):
"""Set the abolute flow rate of outdoor air ventilation for the Room in m3/s.
This overwrites all values of the RoomEnergyProperties's ventilation flow
but preserves the schedule. If the Room has no ventilation definition, a
new one with an Always On schedule will be created.
Args:
flow_rate: A number for the abolute of flow of outdoor air ventilation
for the room in cubic meters per second (m3/s). Note that inputting
a value here will overwrite all specification of outdoor air
ventilation currently on the room (per_floor, per_person, ach).
"""
ventilation = self._dup_load('ventilation', Ventilation)
ventilation.flow_per_person = 0
ventilation.flow_per_area = 0
ventilation.air_changes_per_hour = 0
ventilation.flow_per_zone = flow_rate
self.ventilation = ventilation
def add_internal_mass(self, internal_mass):
"""Add an InternalMass to this Room.
Args:
internal_mass: An InternalMass to add to this Room.
"""
assert isinstance(internal_mass, InternalMass), \
'Expected InternalMass. Got {}.'.format(type(internal_mass))
internal_mass.lock() # lock because we don't duplicate the object
self._internal_masses.append(internal_mass)
def remove_internal_masses(self):
"""Remove all internal masses from the Room."""
self._internal_masses = []
def remove_child_constructions(self):
"""Remove constructions assigned to the Room's Faces, Apertures, Doors and Shades.
This means that all constructions of the Room will be assigned by the Room's
construction_set (or the Honeybee default ConstructionSet if the Room has
no construction set).
"""
for shade in self.host.shades:
shade.properties.energy.construction = None
for face in self.host.faces:
face.properties.energy.construction = None
for shade in face.shades:
shade.properties.energy.construction = None
for ap in face._apertures:
ap.properties.energy.construction = None
for shade in ap.shades:
shade.properties.energy.construction = None
for dr in face._doors:
dr.properties.energy.construction = None
for shade in dr.shades:
shade.properties.energy.construction = None
def window_construction_by_orientation(
self, construction, orientation=0, offset=45, north_vector=Vector2D(0, 1)):
"""Set the construction of exterior Apertures in Walls facing a given orientation.
This is useful for testing orientation-specific energy conservation
strategies or creating AHSRAE baseline buildings.
Args:
construction: A WindowConstruction that will be assigned to all of the
room's Apertures in Walls that are facing a certain orientation.
orientation: A number between 0 and 360 that represents the orientation
in degrees to which the construction will be assigned. 0 = North,
90 = East, 180 = South, 270 = West. (Default: 0 for North).
offset: A number between 0 and 180 that represents the offset from the
orientation in degrees for which the construction will be assigned.
For example, a value of 45 indicates that any Apertures falling
in the 90 degree range around the orientation will get the input
construction. (Default: 45).
north_vector: A ladybug_geometry Vector3D for the north direction.
Default is the Y-axis (0, 1).
"""
# determine the min and max values for orientation
ori_min = orientation - offset
ori_max = orientation + offset
ori_min = ori_min + 360 if ori_min < 0 else ori_min
ori_max = ori_max - 360 if ori_max > 360 else ori_max
rev_vars = True if ori_min > ori_max else False
# loop through the faces an determine if they meet the criteria
for face in self.host.faces:
if isinstance(face.boundary_condition, Outdoors) and \
isinstance(face.type, Wall) and len(face._apertures) > 0:
if rev_vars:
if face.horizontal_orientation(north_vector) > ori_min \
or face.horizontal_orientation(north_vector) < ori_max:
for ap in face._apertures:
ap.properties.energy.construction = construction
else:
if ori_min < face.horizontal_orientation(north_vector) < ori_max:
for ap in face._apertures:
ap.properties.energy.construction = construction
def add_default_ideal_air(self):
"""Add a default IdealAirSystem to this Room.
The identifier of this system will be derived from the room identifier
and will align with the naming convention that EnergyPlus uses for
templates Ideal Air systems.
"""
hvac_id = '{} Ideal Loads Air System'.format(self.host.identifier)
self.hvac = IdealAirSystem(hvac_id)
def add_daylight_control_to_center(
self, distance_from_floor, illuminance_setpoint=300, control_fraction=1,
min_power_input=0.3, min_light_output=0.2, off_at_minimum=False):
"""Try to assign a DaylightingControl object to the center of the Room.
If the Room is too concave and the center point does not lie within the
Room volume, this method wil return None and no daylighting control will
be assigned.
Args:
distance_from_floor: A number for the distance that the daylight sensor
is from the floor. Typical values are around 0.8 meters.
illuminance_setpoint: A number for the illuminance setpoint in lux
beyond which electric lights are dimmed if there is sufficient
daylight. (Default: 300 lux).
control_fraction: A number between 0 and 1 that represents the fraction of
the Room lights that are dimmed when the illuminance at the sensor
position is at the specified illuminance. 1 indicates that all lights are
dim-able while 0 indicates that no lights are dim-able. Deeper rooms
should have lower control fractions to account for the face that the
lights in the back of the space do not dim in response to suitable
daylight at the front of the room. (Default: 1).
min_power_input: A number between 0 and 1 for the the lowest power the
lighting system can dim down to, expressed as a fraction of maximum
input power. (Default: 0.3).
min_light_output: A number between 0 and 1 the lowest lighting output the
lighting system can dim down to, expressed as a fraction of maximum
light output. (Default: 0.2).
off_at_minimum: Boolean to note whether lights should switch off completely
when they get to the minimum power input. (Default: False).
Returns:
A DaylightingControl object if the sensor was successfully assigned
to the center of the Room. Will be None if the zone was so concave
that a sensor would not be assigned.
"""
cen_pt, min_pt = self.host.geometry.center, self.host.geometry.min
sensor_pt = Point3D(cen_pt.x, cen_pt.y, min_pt.z + distance_from_floor)
if self.host.geometry.is_point_inside(sensor_pt):
dl_control = DaylightingControl(
sensor_pt, illuminance_setpoint, control_fraction,
min_power_input, min_light_output, off_at_minimum)
self.daylighting_control = dl_control
return dl_control
def assign_ventilation_opening(self, vent_opening):
"""Assign a VentilationOpening object to all operable Apertures on this Room.
This method will handle the duplication of the VentilationOpening object to
ensure that each aperture gets a unique object that can export the correct
area and height properties of its parent.
Args:
vent_opening: A VentilationOpening object to be duplicated and assigned
to all of the operable apertures of the Room.
Returns:
A list of Apertures for which ventilation opening properties were set.
This can be used to perform additional operations on the apertures, such
as changing their construction.
"""
operable_aps = []
for face in self.host.faces:
for ap in face.apertures:
if ap.is_operable:
ap.properties.energy.vent_opening = vent_opening.duplicate()
operable_aps.append(ap)
return operable_aps
def remove_ventilation_opening(self):
"""Remove all VentilationOpening objects assigned to the Room's Apertures."""
for face in self.host.faces:
for ap in face.apertures:
ap.properties.energy.vent_opening = None
def exterior_afn_from_infiltration_load(self, exterior_face_groups,
air_density=1.2041, delta_pressure=4):
"""Assign AirflowNetwork parameters using the room's infiltration rate.
This will assign air leakage parameters to the Room's exterior Faces that
produce a total air flow rate equivalent to the room infiltration rate at
an envelope pressure difference of 4 Pa. However, the individual flow air
leakage parameters are not meant to be representative of real values, since the
infiltration flow rate is an average of the actual, variable surface flow
dynamics.
VentilationOpening objects will be added to Aperture and Door objects if not
already defined, with the fraction_area_operable set to 0. If VentilationOpening
objects are already defined, only the parameters defining leakage when the
openings are closed will be overwritten. AFNCrack objects will be added
to all external and internal Face objects, and any existing AFNCrack
objects will be overwritten.
Args:
exterior_face_groups: A tuple with five types of the exterior room envelope
- ext_walls - A list of exterior Wall type Face objects.
- ext_roofs - A list of exterior RoofCeiling type Face objects.
- ext_floors - A list of exterior Floor type Face objects, like you
would find in a cantilevered Room.
- ext_apertures - A list of exterior Aperture Face objects.
- ext_doors - A list of exterior Door Face objects.
air_density: Air density in kg/m3. (Default: 1.2041 represents
air density at a temperature of 20 C and 101325 Pa).
delta_pressure: Reference air pressure difference across the building
envelope orifice in Pascals used to calculate infiltration crack flow
coefficients. The resulting average simulated air pressure difference
will roughly equal this delta_pressure times the nth root of the ratio
between the simulated and target room infiltration rates::
dP_sim = (Q_sim / Q_target)^(1/n) * dP_ref
where:
dP: delta_pressure, the reference air pressure difference [Pa]
dP_sim: Simulated air pressure difference [Pa]
Q_sim: Simulated volumetric air flow rate per area [m3/s/m2]
Q_target: Target volumetric air flow rate per area [m3/s/m2]
n: Air mass flow exponent [-]
If attempting to replicate the room infiltration rate per exterior area,
delta_pressure should be set to an approximation of the simulated air
pressure difference described in the above formula. Default 4 represents
typical building pressures.
"""
# simplify parameters
ext_walls, ext_roofs, ext_floors, ext_apertures, ext_doors = exterior_face_groups
ext_faces = ext_walls + ext_roofs + ext_floors
ext_openings = ext_apertures + ext_doors
infil_flow = self.infiltration.flow_per_exterior_area
# derive normalized flow coefficient
flow_cof_per_area = self.solve_norm_area_flow_coefficient(
infil_flow, air_density=air_density, delta_pressure=delta_pressure)
# add exterior crack leakage components
for ext_face in ext_faces:
# Note: this calculation includes opening areas to be consistent with
# assumption behind the Infiltration Flow per Exterior Area measure.
flow_cof = flow_cof_per_area * ext_face.area
ext_face.properties.energy.vent_crack = AFNCrack(flow_cof)
# add exterior opening leakage components
for ext_opening in ext_openings:
if ext_opening.properties.energy.vent_opening is None:
if isinstance(ext_opening, Aperture):
ext_opening.is_operable = True
ext_opening.properties.energy.vent_opening = \
VentilationOpening(fraction_area_operable=0.0)
vent_opening = ext_opening.properties.energy.vent_opening
# Note: can be calculated with solve_norm_perimeter_flow_coefficient
# but it adds an additional degree of freedom when attempting to calculate
# reference delta pressure from simulated delta pressure and infiltration
# data. Setting to zero simplifies assumptions by constraining infiltration
# to just area-based method.
vent_opening.flow_coefficient_closed = 0.0
vent_opening.flow_exponent_closed = 0.5
def envelope_components_by_type(self):
"""Get groups for room envelope components by boundary condition and type.
The groups created by this function correspond to the structure of the
crack template data used to generate the AirflowNetwork but can be
useful for other purposes. However, any parts of the envelope with a
boundary condition other than Outdoors and Surface will be excluded
(eg. Ground or Adiabatic).
Return:
A tuple with five groups of exterior envelope types
- ext_walls - A list of exterior Wall type Face objects.
- ext_roofs - A list of exterior RoofCeiling type Face objects.
- ext_floors - A list of exterior Floor type Face objects, like you
would find in a cantilevered Room.
- ext_apertures - A list of exterior Aperture Face objects.
- ext_doors - A list of exterior Door Face objects.
A tuple with four groups of interior faces types
- int_walls: List of interior Wall type Face objects.
- int_floorceilings: List of interior RoofCeiling and Floor type Face
objects.
- int_apertures: List of interior Aperture Face objects.
- int_doors: List of interior Door Face objects.
- int_air: List of interior Faces with AirBoundary face type.
"""
ext_walls, ext_roofs, ext_floors, ext_apertures, ext_doors = \
[], [], [], [], []
int_walls, int_floorceilings, int_apertures, int_doors, int_air = \
[], [], [], [], []
for face in self.host.faces:
if isinstance(face.boundary_condition, Outdoors):
if isinstance(face.type, Wall):
ext_walls.append(face)
ext_apertures.extend(face.apertures)
ext_doors.extend(face.doors)
elif isinstance(face.type, RoofCeiling):
ext_roofs.append(face)
ext_apertures.extend(face.apertures) # exterior skylights
elif isinstance(face.type, Floor):
ext_floors.append(face)
elif isinstance(face.boundary_condition, Surface):
if isinstance(face.type, Wall):
int_walls.append(face)
int_apertures.extend(face.apertures)
int_doors.extend(face.doors)
elif isinstance(face.type, RoofCeiling) or isinstance(face.type, Floor):
int_floorceilings.append(face)
int_apertures.extend(face.apertures) # interior skylights
elif isinstance(face.type, AirBoundary):
int_air.append(face)
ext_faces = (ext_walls, ext_roofs, ext_floors,
ext_apertures, ext_doors)
int_faces = (int_walls, int_floorceilings,
int_apertures, int_doors, int_air)
return ext_faces, int_faces
def move(self, moving_vec):
"""Move this object along a vector.
Args:
moving_vec: A ladybug_geometry Vector3D with the direction and distance
to move the object.
"""
if self.daylighting_control is not None:
self.daylighting_control.move(moving_vec)
def rotate(self, angle, axis, origin):
"""Rotate this object by a certain angle around an axis and origin.
Args:
angle: An angle for rotation in degrees.
axis: Rotation axis as a Vector3D.
origin: A ladybug_geometry Point3D for the origin around which the
object will be rotated.
"""
if self.daylighting_control is not None:
self.daylighting_control.rotate(angle, axis, origin)
def rotate_xy(self, angle, origin):
"""Rotate this object counterclockwise in the world XY plane by a certain angle.
Args:
angle: An angle in degrees.
origin: A ladybug_geometry Point3D for the origin around which the
object will be rotated.
"""
if self.daylighting_control is not None:
self.daylighting_control.rotate_xy(angle, origin)
def reflect(self, plane):
"""Reflect this object across a plane.
Args:
plane: A ladybug_geometry Plane across which the object will
be reflected.
"""
if self.daylighting_control is not None:
self.daylighting_control.reflect(plane)
def scale(self, factor, origin=None):
"""Scale this object by a factor from an origin point.
Args:
factor: A number representing how much the object should be scaled.
origin: A ladybug_geometry Point3D representing the origin from which
to scale. If None, it will be scaled from the World origin (0, 0, 0).
"""
if self.daylighting_control is not None:
self.daylighting_control.scale(factor, origin)
def make_plenum(self, conditioned=False, remove_infiltration=False):
"""Turn the host Room into a plenum with no internal loads.
This includes removing all people, lighting, equipment, hot water, and
mechanical ventilation. By
default, the heating/cooling system and setpoints will also be removed but they
can optionally be kept. Infiltration is kept by default but can optionally be
removed as well.
This is useful to appropriately assign properties for closets, underfloor spaces,
and drop ceilings.
Args:
conditioned: Boolean to indicate whether the plenum is conditioned with a
heating/cooling system. If True, the setpoints of the Room will also
be kept in addition to the heating/cooling system (Default: False).
remove_infiltration: Boolean to indicate whether infiltration should be
removed from the Rooms. (Default: False).
"""
# remove or add the HVAC system as needed
if conditioned and not self.is_conditioned:
self.add_default_ideal_air()
elif not conditioned:
self.hvac = None
self._shw = None
# remove the loads and reapply infiltration/setpoints as needed
infiltration = None if remove_infiltration else self.infiltration
setpt = self.setpoint if conditioned else None
self._program_type = None
self._people = None
self._lighting = None
self._electric_equipment = None
self._gas_equipment = None
self._service_hot_water = None
self._ventilation = None
self._infiltration = infiltration
self._setpoint = setpt
def make_ground(self, soil_construction):
"""Change the properties of the host Room to reflect those of a ground surface.
This is particularly useful for setting up outdoor thermal comfort maps
to account for the surface temperature of the ground. Modeling the ground
as a room this way will ensure that shadows other objects cast upon it
are accounted for along with the storage of heat in the ground surface.
The turning of a Room into a ground entails:
* Setting all constructions to be indicative of a certain soil type.
* Setting all Faces except the roof to have a Ground boundary condition.
* Removing all loads and schedules assigned to the Room.
Args:
soil_construction: An OpaqueConstruction that reflects the soil type of
the ground. If a multi-layered construction is input, the multiple
layers will only be used for the roof Face of the Room and all other
Faces will get a construction with the inner-most layer assigned.
"""
# process the input soil_construction
assert isinstance(soil_construction, OpaqueConstruction), 'Expected ' \
'OpaqueConstruction for soil_construction. Got {}.'.format(
type(soil_construction))
int_soil = soil_construction if len(soil_construction.materials) == 1 else \
OpaqueConstruction('{}_BelowGrade'.format(soil_construction.identifier),
(soil_construction.materials[-1],))
# reset all of the properties of the room to reflect the ground
self.reset_to_default()
for face in self.host.faces:
face.remove_sub_faces()
if isinstance(face.type, RoofCeiling):
face.boundary_condition = boundary_conditions.outdoors
face.properties.energy.construction = soil_construction
else:
face.boundary_condition = boundary_conditions.ground
face.properties.energy.construction = int_soil
def reset_to_default(self):
"""Reset all of the properties assigned at the level of this Room to the default.
"""
self._program_type = None
self._construction_set = None
self._hvac = None
self._shw = None
self._people = None
self._lighting = None
self._electric_equipment = None
self._gas_equipment = None
self._service_hot_water = None
self._infiltration = None
self._ventilation = None
self._setpoint = None
self._daylighting_control = None
self._window_vent_control = None
self._internal_masses = []
@classmethod
def from_dict(cls, data, host):
"""Create RoomEnergyProperties from a dictionary.
Note that the dictionary must be a non-abridged version for this
classmethod to work.
Args:
data: A dictionary representation of RoomEnergyProperties with the